diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml
index b04f4260c..6cb1b3499 100644
--- a/.github/workflows/storybook.yml
+++ b/.github/workflows/storybook.yml
@@ -16,12 +16,22 @@ jobs:
 
     steps:
     - uses: actions/checkout@v3.3.0
+      if: github.event_name != 'pull_request_target'
       with:
         fetch-depth: 0
         submodules: true
-    - name: Checkout HEAD
+    - uses: actions/checkout@v3.3.0
       if: github.event_name == 'pull_request_target'
-      run: git checkout ${{ github.head_ref }}
+      with:
+        fetch-depth: 0
+        submodules: true
+        ref: "refs/pull/${{ github.event.number }}/merge"
+    - name: Checkout actual HEAD
+      if: github.event_name == 'pull_request_target'
+      id: rev
+      run: |
+        echo "base=$(git rev-list --parents -n1 HEAD | cut -d" " -f2)" >> $GITHUB_OUTPUT
+        git checkout $(git rev-list --parents -n1 HEAD | cut -d" " -f3)
     - name: Install pnpm
       uses: pnpm/action-setup@v2
       with:
@@ -68,7 +78,7 @@ jobs:
       if: github.event_name == 'pull_request_target'
       id: chromatic_pull_request
       run: |
-        DIFF="${{ github.base_ref }} HEAD"
+        DIFF="${{ steps.rev.outputs.base }} HEAD"
         if [ "$DIFF" = "0000000000000000000000000000000000000000 HEAD" ]; then
           DIFF="HEAD"
         fi
@@ -76,7 +86,11 @@ jobs:
         if [ "$CHROMATIC_PARAMETER" = " --skip" ]; then
           echo "skip=true" >> $GITHUB_OUTPUT
         fi
-        pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static $(echo "$CHROMATIC_PARAMETER")
+        BRANCH="${{ github.event.pull_request.head.user.login }}:${{ github.event.pull_request.head.ref }}"
+        if [ "$BRANCH" = "misskey-dev:${{ github.event.pull_request.head.ref }}" ]; then
+          BRANCH="${{ github.event.pull_request.head.ref }}"
+        fi
+        pnpm --filter frontend chromatic --exit-once-uploaded -d storybook-static --branch-name $BRANCH $(echo "$CHROMATIC_PARAMETER")
       env:
         CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
     - name: Notify that Chromatic detects changes
@@ -91,18 +105,6 @@ jobs:
             commit_sha: context.sha,
             body: 'Chromatic detects changes. Please [review the changes on Chromatic](https://www.chromatic.com/builds?appId=6428f7d7b962f0b79f97d6e4).'
           })
-    - name: Notify that Chromatic will skip testing
-      uses: actions/github-script@v6.4.0
-      if: github.event_name == 'pull_request_target' && steps.chromatic_pull_request.outputs.skip == 'true'
-      with:
-        github-token: ${{ secrets.GITHUB_TOKEN }}
-        script: |
-          github.rest.issues.createComment({
-            issue_number: context.issue.number,
-            owner: context.repo.owner,
-            repo: context.repo.repo,
-            body: 'Chromatic will skip testing but you may still have to [review the changes on Chromatic](https://www.chromatic.com/pullrequests?appId=6428f7d7b962f0b79f97d6e4).'
-          })
     - name: Upload Artifacts
       uses: actions/upload-artifact@v3
       with:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8594c42d1..a06300656 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,44 @@
 
 -->
 
+## 13.13.0
+
+### General
+- カスタム絵文字ごとにそれをリアクションとして使えるロールを設定できるように
+- カスタム絵文字ごとに連合するかどうか設定できるように
+- カスタム絵文字ごとにセンシティブフラグを設定できるように
+- センシティブなカスタム絵文字のリアクションを受け入れない設定が可能に
+- タイムラインにフォロイーの行った他人へのリプライを含めるかどうかの設定をアカウントに保存するのをやめるように
+	- 今後はAPI呼び出し時およびストリーミング接続時に設定するようになります
+- リストを公開できるようになりました
+
+### Client
+- リアクションの取り消し/変更時に確認ダイアログを出すように
+- 開発者モードを追加
+- AiScriptを0.13.3に更新
+- Deck UIを使用している場合、`/`以外にアクセスした際にZen UIで表示するように
+	- メインカラムを設置していない場合の問題を解決
+- ハッシュタグのノート一覧ページから、そのハッシュタグで投稿するボタンを追加
+- アカウント初期設定ウィザードに戻るボタンを追加
+- アカウントの初期設定ウィザードにあとでボタンを追加
+- サーバーにカスタム絵文字の種類が多い場合のパフォーマンスの改善
+- Fix: URLプレビューで情報が取得できなかった際の挙動を修正
+- Fix: Safari、Firefoxでの新規登録時、パスワードマネージャーにメールアドレスが登録されていた挙動を修正
+- Fix: ロールタイムラインが無効でも投稿が流れてしまう問題の修正
+- Fix: ロールタイムラインにて全ての投稿が流れてしまう問題の修正
+- Fix: 「アクセストークンの管理」画面でアプリの情報が表示されない問題の修正
+- Fix: Firefoxにおける絵文字ピッカーのTabキーフォーカス問題の修正
+- Fix: フォローボタンがテーマのカラースキームによって視認性が悪くなる問題を修正
+  - 新しいプロパティ `fgOnWhite` が追加されました
+
+### Server
+- bullをbull-mqにアップグレードし、ジョブキューのパフォーマンスを改善
+- ストリーミングのパフォーマンスを改善
+- Fix: 無効化されたアンテナにアクセスがあった際に再度有効化するように
+- Fix: お知らせの画像URLを空にできない問題を修正
+- Fix: i/notificationsのsinceIdが機能しない問題を修正
+- Fix: pageのピン留めを解除することができない問題を修正
+
 ## 13.12.2
 
 ## NOTE
@@ -87,6 +125,7 @@ Meilisearchの設定に`index`が必要になりました。値はMisskeyサー
   * 画像が全て隠れた状態で表示されるようになります
 - 閲覧注意設定された画像は表示した状態でもそれが閲覧注意だと分かる表示をするように
 - モデレーターはノートに添付された画像上から直接NSFW設定できるように
+- 1枚だけのメディアリストの画像のアスペクト比を画像に応じて縦長にするように
 - プロフィール設定「追加情報」の項目の削除と並び替えができるように
 - 新しい実績を追加
 - AiScriptを0.13.2に更新
@@ -334,6 +373,7 @@ Meilisearchの設定に`index`が必要になりました。値はMisskeyサー
 - アンテナでCWも検索対象にするように
 - ノートの操作部をホバー時のみ表示するオプションを追加
 - サウンドを追加
+- enhance(client): MFMのx2, scale, positionが含まれていたらノートをたたむように
 - サーバーのパフォーマンスを改善
 
 ### Bugfixes
diff --git a/cypress/support/e2e.js b/cypress/support/e2e.js
index 9185be344..827a326d1 100644
--- a/cypress/support/e2e.js
+++ b/cypress/support/e2e.js
@@ -21,6 +21,8 @@ import './commands'
 
 Cypress.on('uncaught:exception', (err, runnable) => {
 	if ([
+		'The source image cannot be decoded',
+
 		// Chrome
 		'ResizeObserver loop limit exceeded',
 
diff --git a/docs/DONATORS.md b/docs/DONATORS.md
deleted file mode 100644
index 9da5c1a94..000000000
--- a/docs/DONATORS.md
+++ /dev/null
@@ -1,25 +0,0 @@
-DONATORS
-========
-The list of people who have sent donation for Misskey.
-
-(In random order, honorific titles are omitted.)
-
-* らふぁ
-* 俺様
-* なぎうり
-* スルメ https://surume.tk/
-* 藍
-* 音船 https://otofune.me/
-* aqz https://misskey.xyz/aqz
-* kotodu "虚無創作中"
-* Maya Minatsuki
-* Knzk https://knzk.me/@Knzk
-* ねじりわさび https://knzk.me/@y
-* NCLS https://knzk.me/@imncls]
-* こじま @skoji@sandbox.skoji.jp
-
-:heart: Thanks for donating, guys!
-
----
-
-If your name is missing, please contact us!
diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml
index bfca086f5..5d0fd201b 100644
--- a/locales/ar-SA.yml
+++ b/locales/ar-SA.yml
@@ -2,6 +2,7 @@
 _lang_: "العربية"
 headlineMisskey: "شبكة مرتبطة بالملاحظات"
 introMisskey: "اهلا بك! ميسكي هو منصة تدوين مصغر لا مركزية ومفتوحة المصدر.\nيمكنك مشاركة \"ملاحظات\" عن ما يجري حولك، وإخبار الجميع عن نفسك 📡\nتسمح لك \"الانفعالات\" بتعبير عن شعورك حول ملاحظات الآخرين 👍\nاكتشف عالمًا جديدًا 🚀"
+poweredByMisskeyDescription: "{name} هو إحدى الخِدمات التي تستخدم المنصة مفتوحة المصدر <b>ميسكي</b> (يشار إليه كمثيل ميسكي)"
 monthAndDay: "{day}/{month}"
 search: "البحث"
 notifications: "الإشعارات"
@@ -49,6 +50,7 @@ deleteAndEdit: "إزالة وإعادة الصياغة"
 deleteAndEditConfirm: "أمتأكد من حذف الملاحظة؟ ستفقد كل مشاركاتها، والتفاعلات، والردود عليها."
 addToList: "أضفه إلى قائمة"
 sendMessage: "أرسل رسالة"
+copyRSS: "انسخ رابط RSS"
 copyUsername: "انسخ اسم المستخدم"
 searchUser: "ابحث عن مستخدمين"
 reply: "رد"
@@ -102,6 +104,8 @@ renoted: "أُعيد نشره"
 cantRenote: "لا يمكن إعادة نشر الملاحظة"
 cantReRenote: "لا يمكنك إعادة نشر ملاحظة معاد نشرها"
 quote: "اقتبس"
+inChannelRenote: "إعادة نشر في قناة"
+inChannelQuote: "اقتباس في قناة"
 pinnedNote: "ملاحظة مدبسة"
 pinned: "دبّسها على الصفحة الشخصية"
 you: "أنت"
@@ -119,6 +123,8 @@ unmarkAsSensitive: "ألغ تعيينه كمحتوى حساس"
 enterFileName: "ادخل اسم الملف"
 mute: "اكتم"
 unmute: "إلغاء الكتم"
+renoteMute: "اكتم إعادة النشر"
+renoteUnmute: "ارفع الكتم عن إعادة النشر"
 block: "احجب"
 unblock: "إلغاء الحجب"
 suspend: "علِق"
@@ -141,6 +147,7 @@ emojiUrl: "رابط الإيموجي"
 addEmoji: "إضافة إيموجي"
 settingGuide: "الإعدادات المستحسنة"
 cacheRemoteFiles: "خزن مؤقتا الملفات البعيدة"
+cacheRemoteFilesDescription: "إذا عُطل هذا الإعداد، ستُحمل الملفات من المثيل البعيد، هذا سيقلل من المساحة المستغلة على القرص لكن سيزيد حجم تدفق البيانات وهذا لأن الصور المصغرة لن تولّد."
 flagAsBot: "علّمه كحساب آلي"
 flagAsBotDescription: "فعّل هذا الخيار إذا كان هذا الحساب يُدار عبر برمجية. إذا فُعل فسيكون بمثابة علامة للمطورين الآخرين لتجنب سلاسل لا متناهية من التفاعل بين حسابات الآلية وضبط أنظمة ميسكي للتعامل مع هذا الحساب كآلي."
 flagAsCat: "علّم هذا الحساب كحساب قط"
@@ -253,14 +260,15 @@ startMessaging: "ابدأ محادثة"
 nUsersRead: "قرأه {n}"
 agreeTo: "اوافق على {0}"
 agree: "أقبل"
+agreeBelow: "أقبل ما يلي"
 basicNotesBeforeCreateAccount: "ملاحظات مهمة"
 termsOfService: "شروط الخدمة"
 start: "البداية"
 home: "الرئيسي"
 remoteUserCaution: "هذه المعلومات قد لا تكون مكتملة بما أن المستخدم من مثيل بعيد."
 activity: "النشاط"
-images: "الصور"
-image: "الصور"
+images: "صور"
+image: "صور"
 birthday: "تاريخ الميلاد"
 yearsOld: "{age} سنة"
 registeredDate: "انضم في"
@@ -362,6 +370,7 @@ antennaExcludeKeywords: "الكلمات المفتاحية المستثناة"
 antennaKeywordsDescription: "افصل بينهم بمسافة لاستخدام معامل \"و\" أو بسطر لاستخدام معامل \"أو\""
 notifyAntenna: "نبهني بصول ملاحظات جديدة"
 withFileAntenna: "ملاحظات تحوي ملفات فقط"
+enableServiceworker: "فعّل إرسال الإشعارات للمتصفح"
 antennaUsersDescription: "اكتب اسم مستخدم لكل سطر"
 caseSensitive: "حساسية حالة الأحرف"
 withReplies: "بالردود"
@@ -391,6 +400,7 @@ moderation: "الإشراف"
 nUsersMentioned: "{n} مستخدمين أُشير إليهم"
 securityKey: "مفتاح الأمان"
 lastUsed: "آخر استخدام"
+lastUsedAt: "آخر استخدام: {t}"
 unregister: "إلغاء التسجيل"
 passwordLessLogin: "لِج مِن دون كلمة سرية"
 resetPassword: "أعد تعيين كلمتك السرية"
@@ -441,6 +451,7 @@ or: "أو"
 language: "اللغة"
 uiLanguage: "لغة واجهة المستخدم"
 aboutX: "عن {x}"
+emojiStyle: "نمط الوجوه التعبيرية"
 noHistory: "السجل فارغ"
 signinHistory: "تاريخ تسجيل الدخول"
 doing: "انتظر لحظة"
@@ -451,6 +462,7 @@ createAccount: "أنشئ حسابًا"
 existingAccount: "الحسابات الموجودة"
 regenerate: "أعِد التوليد"
 fontSize: "حجم الخط"
+limitTo: "سقفهُ لـ{x}"
 noFollowRequests: "ليس لديك طلبات متابعة معلقة"
 openImageInNewTab: "إفتح الصورة بصفحة جديدة"
 dashboard: "لوحة التحكم"
@@ -479,6 +491,7 @@ objectStorageUseProxyDesc: "عطل هذا الخيار إذا لم ترد است
 serverLogs: "سجلات الخادم"
 deleteAll: "حذف الكل"
 showFixedPostForm: "أظهر نموذج الكتابة في أعلى الصفحة"
+showFixedPostFormInChannel: "أظهر نموذج الكتابة في أعلى الخط الزمني (قنوات)"
 newNoteRecived: "هناك ملاحظات جديدة"
 sounds: "الرنات"
 sound: "الرنات"
@@ -514,6 +527,7 @@ userSilenced: "كُتم هذا المستخدم."
 yourAccountSuspendedTitle: "هذا الحساب معلق"
 yourAccountSuspendedDescription: "عُلق الحساب بسبب انتهاك شروط خدمة المثيل و ما شابه. إذا أردت معرفة التفصيل تواصل مع مدير المثيل. رجاءً لا تنشئ حساب جديد."
 accountDeleted: "حُذف الحساب"
+accountDeletedDescription: "حُذف هذا الحساب."
 menu: "القائمة"
 divider: "فاصل"
 addItem: "إضافة عنصر"
@@ -539,6 +553,7 @@ author: "الكاتب"
 leaveConfirm: "لديك تغييرات غير محفوظة. أتريد المتابعة دون حفظها؟"
 manage: "إدارة "
 plugins: "الإضافات"
+preferencesBackups: "النُسخ الاحتياطية للإعدادات"
 useFullReactionPicker: "استخدم الحجم الكامل لمنتقي التفاعلات"
 width: "العرض"
 height: "الإرتفاع"
@@ -648,6 +663,7 @@ contact: "التواصل"
 useSystemFont: "استخدم الخط الافتراضية للنظام"
 clips: "مشابك"
 experimentalFeatures: "ميّزات اختبارية"
+experimental: "اختباري"
 developer: "المطور"
 makeExplorable: "أظهر الحساب في صفحة \"استكشاف\""
 makeExplorableDescription: "بتعطيل هذا الخيار لن يظهر حسابك في صفحة \"استكشاف\""
@@ -669,6 +685,7 @@ accentColor: "طابع لوني"
 textColor: "لون النص"
 saveAs: "احفظ كـ..."
 advanced: "متقدم"
+advancedSettings: "إعدادات متقدمة"
 value: "القيمة"
 createdAt: "أُنشئ في"
 updatedAt: "حُدّث في"
@@ -733,6 +750,7 @@ popularPosts: "المشاركات المتداولة"
 shareWithNote: "شاركه في ملاحظة"
 ads: "الإعلانات"
 expiration: "ينتهي استطلاع الرأي في"
+startingperiod: "ابدأ"
 memo: "تذكير"
 priority: "الأولوية"
 high: "عالية"
@@ -763,6 +781,7 @@ lastCommunication: "آخر تواصل"
 resolved: "عولج"
 unresolved: "لم يعالج"
 breakFollow: "إلغاء الاشتراك"
+breakFollowConfirm: "أمتأكد من إزالة المتابِع ؟"
 itsOn: "مفعّل"
 itsOff: "معطّل"
 emailRequiredForSignup: "عنوان البريد الإلكتروني إلزامي للتسجيل"
@@ -777,6 +796,7 @@ muteThread: "اكتم النقاش"
 unmuteThread: "ارفع الكتم عن النقاش"
 ffVisibility: "مرئية المتابِعين/المتابَعين"
 ffVisibilityDescription: "يسمح لك بتحديد من يمكنهم رؤية متابِعيك ومتابَعيك."
+continueThread: "اعرض بقية النقاش"
 deleteAccountConfirm: "سيحذف حسابك نهائيًا، أتريد المتابعة؟"
 incorrectPassword: "كلمة السر خاطئة."
 voteConfirm: "متيقِّن من تصويتك لـ {choice}؟"
@@ -798,25 +818,127 @@ tenMinutes: "10 دقائق"
 oneHour: "ساعة"
 oneDay: "يوم"
 oneWeek: "أسبوع"
+oneMonth: "شهر"
 failedToFetchAccountInformation: "تعذر جلب معلومات الحساب"
+cropNo: "استخدمها كما هي"
 file: "الملفات"
+recentNHours: "آخر {n} ساعة"
+recentNDays: "آخر {n} أيام"
+noEmailServerWarning: "خادم البريد غير مضبوط."
+thereIsUnresolvedAbuseReportWarning: "توجد بلاغات غير معالجة."
+recommended: "مقترح"
+driveCapOverrideLabel: "غيّر حجم قرص التخزين لهذا المستخدم"
+driveCapOverrideCaption: "أعد الحجم إلى القيمة الافتراضية بإدخال 0 أو أقل."
+requireAdminForView: "لاستعراض هذه الصفحة وجب عليك الولوج كمدير."
+typeToConfirm: "أدخل {x} للتأكيد"
+deleteAccount: "احذف الحساب"
+document: "التوثيق"
+numberOfPageCache: "عدد الصفحات المخزنة مؤقتًا"
+logoutConfirm: "أتريد الخروج؟"
+lastActiveDate: "آخر استخدام"
+statusbar: "شريط الحالة"
+pleaseSelect: "حدد خيارًا"
 reverse: "اقلب"
 colored: "ملوّن"
 label: "التسمية"
+type: "نوع"
+speed: "سرعة"
+slow: "بطيء"
+fast: "سريع"
+sensitiveMediaDetection: "التعرف على المحتوى الحساس"
 localOnly: "المحلي فقط"
+failedToUpload: "فشل الرفع"
+cannotUploadBecauseInappropriate: "تعذر رفع الملف لوجود محتوى حساس فيه."
+cannotUploadBecauseNoFreeSpace: "تعذر رفع الملف لنقص مساحة التخزين."
+cannotUploadBecauseExceedsFileSizeLimit: "تعذر رفع الملف بسبب تجاوز حجمه للحد المسموح"
+beta: "بيتا"
+navbar: "شريط التنقل"
+shuffle: "خلط"
 account: "الحسابات"
+move: "أنقل"
+pushNotification: "إرسال الإشعارات"
+subscribePushNotification: "فعّل إرسال الإشعارات"
+unsubscribePushNotification: "عطل إرسال الإشعارات"
+pushNotificationAlreadySubscribed: "إرسال الإشعارات مفعل سلفًا"
+pushNotificationNotSupported: "متصفحك لا يدعم إرسال الإشعارات أو المثيل لا يدعمها."
+sendPushNotificationReadMessage: "احذف الإشعارات فور قراءتها"
+sendPushNotificationReadMessageCaption: "هذا قد يزيد من معدل استهلاك الطاقة لجهازك."
+caption: "التعليق التوضيحي"
+tools: "أدوات"
 cannotLoad: "تعذر التحميل"
 like: "أعجبني"
+unlike: "ألغِ الإعجاب"
 show: "المظهر"
+neverShow: "لا تظهره مجددًا"
+didYouLikeMisskey: "هل أعجبك ميسكي؟"
+roles: "الأدوار"
+role: "الدور"
+noRole: "لم يُعثر على دور"
+normalUser: "مستخدم عادي"
+undefined: "غير معرّف"
 color: "اللون"
+manageCustomEmojis: "إدارة الإيموجي المخصصة"
+cannotPerformTemporary: "غير متاح مؤقتاً"
+permissionDeniedError: "رُفضة العملية"
+preset: "إعدادات مسبقة"
+selectFromPresets: "اختر من الإعدادات المسبقة"
+achievements: "الإنجازات"
+gotInvalidResponseError: "استجابة غير متوقعة من الخادم"
+gotInvalidResponseErrorDescription: "يتعذر الوصول إلى الخادم أوأنه يُصان، رجاءً حاول لاحقًا."
+thisPostMayBeAnnoying: "هذا قد يزعج الآخرين."
+thisPostMayBeAnnoyingHome: "أنشر في الخط الزمني الرئيس"
+thisPostMayBeAnnoyingCancel: "ألغِ"
+internalServerError: "خطأ داخلي في الخادم"
+internalServerErrorDescription: "واجه الخادم خطأ غي متوقع."
+copyErrorInfo: "انسخ تفاصيل الخطأ"
+joinThisServer: "سجل في هذا المثيل"
+exploreOtherServers: "اعثر على مثيل آخر"
+disableFederationOk: "عطّل"
+invitationRequiredToRegister: "هذا المثيل للمدعوين فقط. لتسجيل فيه تحتاج رمزًا صالحًا."
+postToTheChannel: "انشر في قناة"
+cannotBeChangedLater: "لا يمكن تغييره لاحقًا."
+reactionAcceptance: "قبول التفاعلات"
+rolesAssignedToMe: "الأدوار المسندة إلي"
+resetPasswordConfirm: "هل تريد إعادة تعيين كلمة السر؟"
+noteIdOrUrl: "معرف الملاحظة أو رابطها"
+video: "فيديو"
+videos: "فيديوهات"
+accountMigration: "ترحيل الحساب"
+accountMoved: "نقل هذا المستخدم حسابه:"
+accountMovedShort: "رُحل هذا الحساب."
+operationForbidden: "عملية ممنوعة"
+forceShowAds: "أظهر الإعلانات التجارية دائما"
+vertical: "عمودي"
 horizontal: "جانبي"
+position: "الموضع"
+serverRules: "قوانين الخادم"
+pleaseConfirmBelowBeforeSignup: "رجاءً وافق على ما يلي قبل التسجيل."
+pleaseAgreeAllToContinue: "للمتابعة وافق على الحقول أعلاه."
+continue: "متابعة"
+preservedUsernames: "أسماء المستخدمين المحجوزة"
+preservedUsernamesDescription: "قائمة بأسماء المستخدمين المحجوزة كلٌ في سطر. لن يُقبل التسجيل بهذه الأسماء وستبقى محصورة على التسجيل اليدوي بواسطة المديرين. لن يتأثر المستخدمون الذين يملكون هذه الأسماء سلفًا."
+archive: "الأرشيف"
 youFollowing: "متابَع"
+options: "خيارات"
 _role:
+  new: "دور جديد"
+  edit: "حرر الأدوار"
+  name: "اسم الدور"
+  description: "وصف الدور"
+  permission: "أذونات الدور"
+  assignTarget: "نوع الإسناد"
+  options: "خيارات"
+  policies: "السياسة العامة"
   priority: "الأولوية"
   _priority:
     low: "منخفضة"
     middle: "متوسط"
     high: "عالية"
+  _options:
+    canManageCustomEmojis: "إدارة الإيموجي المخصصة"
+  _condition:
+    isLocal: "مستخدم محلي"
+    isRemote: "مستخدم بعيد"
 _emailUnavailable:
   used: "هذا البريد الإلكتروني مستخدم"
   format: "صيغة البريد الإلكتروني غير صالحة"
@@ -1064,6 +1186,7 @@ _widgets:
   onlineUsers: "المتّصلون"
   jobQueue: "قائمة الانتظار"
   serverMetric: "إحصائيات الخادم"
+  userList: "قائمة المستخدمين"
   _userList:
     chooseList: "اختر قائمة"
 _cw:
@@ -1127,6 +1250,7 @@ _profile:
   changeBanner: "غيّر اللافتة"
 _exportOrImport:
   allNotes: "كل الملاحظات"
+  favoritedNotes: " الملاحظات المفضلة"
   followingList: "المتابَعون"
   muteList: "المستخدمون المكتومون"
   blockingList: "المستخدمون المحجوبون"
@@ -1145,6 +1269,8 @@ _charts:
   notesTotal: "إجمالي الملاحظات"
   filesIncDec: "تباين عدد الملفات"
   filesTotal: "العدد الإجمالي للملفات"
+  storageUsageIncDec: "التباين في استغلال مساحة التخزين"
+  storageUsageTotal: "اجمالي مساحة التخزين المستغلة"
 _instanceCharts:
   requests: "الطلبات"
   users: "تباين عدد المستخدمين"
@@ -1205,7 +1331,7 @@ _pages:
     text: "نص"
     textarea: "حقل نصي"
     section: "قسم"
-    image: "الصور"
+    image: "صور"
     button: "زرّ"
     note: "ملاحظة مضمّنة"
     _note:
@@ -1264,3 +1390,5 @@ _deck:
 _webhookSettings:
   name: "الإسم"
   active: "مفعّل"
+  _events:
+    reaction: "عند تلقي تفاعل"
diff --git a/locales/de-DE.yml b/locales/de-DE.yml
index 843470cf4..c4c12cb1a 100644
--- a/locales/de-DE.yml
+++ b/locales/de-DE.yml
@@ -52,6 +52,8 @@ addToList: "Zu Liste hinzufügen"
 sendMessage: "Nachricht senden"
 copyRSS: "RSS kopieren"
 copyUsername: "Benutzernamen kopieren"
+copyUserId: "Benutzer-ID kopieren"
+copyNoteId: "Notiz-ID kopieren"
 searchUser: "Nach einem Benutzer suchen"
 reply: "Antworten"
 loadMore: "Mehr laden"
@@ -790,6 +792,7 @@ noMaintainerInformationWarning: "Betreiberinformationen sind nicht konfiguriert.
 noBotProtectionWarning: "Schutz vor Bots ist nicht konfiguriert."
 configure: "Konfigurieren"
 postToGallery: "Neuen Galeriebeitrag erstellen"
+postToHashtag: "Mit diesem Hashtag senden"
 gallery: "Galerie"
 recentPosts: "Neue Beiträge"
 popularPosts: "Beliebte Beiträge"
@@ -823,6 +826,7 @@ translatedFrom: "Aus {x} übersetzt"
 accountDeletionInProgress: "Die Löschung deines Benutzerkontos ist momentan in Bearbeitung."
 usernameInfo: "Ein Name, durch den dein Benutzerkonto auf diesem Server identifiziert werden kann. Du kannst das Alphabet (a~z, A~Z), Ziffern (0~9) oder Unterstriche (_) verwenden. Benutzernamen können später nicht geändert werden."
 aiChanMode: "Ai-Modus"
+devMode: "Entwicklermodus"
 keepCw: "Inhaltswarnungen beibehalten"
 pubSub: "Pub/Sub Benutzerkonten"
 lastCommunication: "Letzte Kommunikation"
@@ -832,6 +836,8 @@ breakFollow: "Follower entfernen"
 breakFollowConfirm: "Diesen Follower wirklich entfernen?"
 itsOn: "Eingeschaltet"
 itsOff: "Ausgeschaltet"
+on: "An"
+off: "Aus"
 emailRequiredForSignup: "Angabe einer Email-Adresse als benötigt markieren"
 unread: "Ungelesen"
 filter: "Filter"
@@ -986,6 +992,8 @@ cannotBeChangedLater: "Kann später nicht mehr geändert werden."
 reactionAcceptance: "Reaktionsannahme"
 likeOnly: "Nur \"Gefällt mir\""
 likeOnlyForRemote: "Nur \"Gefällt mir\" für fremde Instanzen"
+nonSensitiveOnly: "Keine Sensitiven"
+nonSensitiveOnlyForLocalLikeOnlyForRemote: "Keine Sensitiven (Nur \"Gefällt mir\" von fremden Instanzen)"
 rolesAssignedToMe: "Mir zugewiesene Rollen"
 resetPasswordConfirm: "Wirklich Passwort zurücksetzen?"
 sensitiveWords: "Sensible Wörter"
@@ -1043,6 +1051,17 @@ preventAiLearning: "Verwendung in machinellem Lernen (Generative bzw. Prediktive
 preventAiLearningDescription: "Fordert Crawler auf, gepostetes Text- oder Bildmaterial usw. nicht in Datensätzen für maschinelles Lernen (Generative bzw. Prediktive AI/KI) zu verwenden. Dies wird durch das Hinzufügen einer \"noai\"-Flag in der HTML-Antwort des jeweiligen Inhalts erreicht. Da diese Flag jedoch ignoriert werden kann, ist eine vollständige Verhinderung hierdurch nicht möglich."
 options: "Optionen"
 specifyUser: "Spezifischer Benutzer"
+failedToPreviewUrl: "Vorschau nicht anzeigbar"
+update: "Aktualisieren"
+rolesThatCanBeUsedThisEmojiAsReaction: "Rollen, die dieses Emoji als Reaktion verwenden können"
+rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "Sind keine Rollen angegeben, kann jeder dieses Emoji als Reaktion verwenden."
+rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "Diese Rollen müssen öffentlich sein."
+cancelReactionConfirm: "Möchtest du deine Reaktion wirklich löschen?"
+changeReactionConfirm: "Möchtest du deine Reaktion wirklich ändern?"
+later: "Später"
+goToMisskey: "Zu Misskey"
+additionalEmojiDictionary: "Zusätzliche Emoji-Wörterbücher"
+installed: "Installiert"
 _initialAccountSetting:
   accountCreated: "Dein Konto wurde erfolgreich erstellt!"
   letsStartAccountSetup: "Lass uns nun dein Konto einrichten."
@@ -1057,6 +1076,7 @@ _initialAccountSetting:
   haveFun: "Viel Spaß mit {name}!"
   ifYouNeedLearnMore: "Besuche {link}, falls du mehr über {name} (Misskey) lernen möchtest."
   skipAreYouSure: "Die Kontoeinrichtung wirklich überspringen?"
+  laterAreYouSure: "Die Kontoeinrichtung wirklich später erledigen?"
 _serverRules:
   description: "Eine Reihe von Regeln, die vor der Registrierung angezeigt werden. Eine Zusammenfassung der Nutzungsbedingungen anzuzeigen ist empfohlen."
 _accountMigration:
diff --git a/locales/en-US.yml b/locales/en-US.yml
index 3ea2313b2..0f1c7c89f 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -52,6 +52,8 @@ addToList: "Add to list"
 sendMessage: "Send a message"
 copyRSS: "Copy RSS"
 copyUsername: "Copy username"
+copyUserId: "Copy user ID"
+copyNoteId: "Copy note ID"
 searchUser: "Search for a user"
 reply: "Reply"
 loadMore: "Load more"
@@ -790,6 +792,7 @@ noMaintainerInformationWarning: "Maintainer information is not configured."
 noBotProtectionWarning: "Bot protection is not configured."
 configure: "Configure"
 postToGallery: "Create new gallery post"
+postToHashtag: "Post to this hashtag"
 gallery: "Gallery"
 recentPosts: "Recent posts"
 popularPosts: "Popular posts"
@@ -823,6 +826,7 @@ 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."
 aiChanMode: "Ai Mode"
+devMode: "Developer mode"
 keepCw: "Keep content warnings"
 pubSub: "Pub/Sub Accounts"
 lastCommunication: "Last communication"
@@ -832,6 +836,8 @@ breakFollow: "Remove follower"
 breakFollowConfirm: "Really remove this follower?"
 itsOn: "Enabled"
 itsOff: "Disabled"
+on: "On"
+off: "Off"
 emailRequiredForSignup: "Require email address for sign-up"
 unread: "Unread"
 filter: "Filter"
@@ -986,6 +992,8 @@ cannotBeChangedLater: "This cannot be changed later."
 reactionAcceptance: "Reaction Acceptance"
 likeOnly: "Only likes"
 likeOnlyForRemote: "Only likes for remote instances"
+nonSensitiveOnly: "Non-sensitive only"
+nonSensitiveOnlyForLocalLikeOnlyForRemote: "Non-sensitive only (Only likes from remote)"
 rolesAssignedToMe: "Roles assigned to me"
 resetPasswordConfirm: "Really reset your password?"
 sensitiveWords: "Sensitive words"
@@ -1043,6 +1051,17 @@ 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"
+failedToPreviewUrl: "Could not preview"
+update: "Update"
+rolesThatCanBeUsedThisEmojiAsReaction: "Roles that can use this emoji as reaction"
+rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "If no roles are specified, anyone can use this emoji as reaction."
+rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "These roles must be public."
+cancelReactionConfirm: "Really delete your reaction?"
+changeReactionConfirm: "Really change your reaction?"
+later: "Later"
+goToMisskey: "To Misskey"
+additionalEmojiDictionary: "Additional emoji dictionaries"
+installed: "Installed"
 _initialAccountSetting:
   accountCreated: "Your account was successfully created!"
   letsStartAccountSetup: "For starters, let's set up your profile."
@@ -1057,6 +1076,7 @@ _initialAccountSetting:
   haveFun: "Enjoy {name}!"
   ifYouNeedLearnMore: "If you'd like to learn more about how to use {name} (Misskey), please visit {link}."
   skipAreYouSure: "Really skip profile setup?"
+  laterAreYouSure: "Really do profile setup later?"
 _serverRules:
   description: "A set of rules to be displayed before registration. Setting a summary of the Terms of Service is recommended."
 _accountMigration:
diff --git a/locales/es-ES.yml b/locales/es-ES.yml
index b043ecf3c..a5dd18e3f 100644
--- a/locales/es-ES.yml
+++ b/locales/es-ES.yml
@@ -20,6 +20,7 @@ noNotes: "No hay notas"
 noNotifications: "No hay notificaciones"
 instance: "Instancia"
 settings: "Configuración"
+notificationSettings: "Configurar las notificaciones"
 basicSettings: "Configuración Básica"
 otherSettings: "Configuración avanzada"
 openInWindow: "Abrir en una ventana"
@@ -262,8 +263,10 @@ noMoreHistory: "El historial se ha acabado"
 startMessaging: "Iniciar chat"
 nUsersRead: "Leído por {n} personas"
 agreeTo: "De acuerdo con {0}"
+agree: "De acuerdo."
 agreeBelow: "Estoy de acuerdo con lo siguiente"
 basicNotesBeforeCreateAccount: "Notas básicas"
+termsOfService: "Términos y condiciones"
 start: "Comenzar"
 home: "Inicio"
 remoteUserCaution: "Para el usuario remoto, la información está incompleta"
@@ -473,6 +476,8 @@ createAccount: "Crear cuenta"
 existingAccount: "Cuenta existente"
 regenerate: "Regenerar"
 fontSize: "Tamaño de la letra"
+mediaListWithOneImageAppearance: "Altura de la lista de medios con una sola imagen."
+limitTo: "{x} hasta un máximo de"
 noFollowRequests: "No hay solicitudes de seguimiento"
 openImageInNewTab: "Abrir imagen en nueva pestaña"
 dashboard: "Panel de control"
@@ -555,6 +560,7 @@ accountDeletedDescription: "Esta cuenta ha sido borrada."
 menu: "Menú"
 divider: "Divisor"
 addItem: "Agregar elemento"
+rearrange: "Ordenar"
 relays: "Relés"
 addRelay: "Agregar relé"
 inboxUrl: "Inbox URL"
@@ -698,6 +704,8 @@ contact: "Contacto"
 useSystemFont: "Utilizar la tipografía por defecto del sistema"
 clips: "Clip"
 experimentalFeatures: "Características experimentales"
+experimental: "Función experimental"
+thisIsExperimentalFeature: "Se trata de una función experimental. Las especificaciones pueden cambiar o puede que no funcione correctamente."
 developer: "Desarrolladores"
 makeExplorable: "Hacer visible la cuenta en \"Explorar\""
 makeExplorableDescription: "Si desactiva esta opción, su cuenta no aparecerá en la sección \"Explorar\"."
@@ -991,9 +999,12 @@ largeNoteReactions: "Agrandar las reacciones de las notas"
 noteIdOrUrl: "ID o URL de la nota"
 accountMigration: "Migración de cuenta"
 accountMoved: "Este usuario se ha mudado a una nueva cuenta:"
+accountMovedShort: "Esta cuenta ha sido migrada."
 horizontal: "Horizontal"
 youFollowing: "Siguiendo"
 options: "Opción"
+_initialAccountSetting:
+  accountCreated: "¡La cuenta ha sido creada!"
 _accountMigration:
   moveFrom: "Trasladar de otra cuenta a ésta"
   moveFromLabel: "Cuenta desde la que se realiza el traslado:"
@@ -1174,6 +1185,8 @@ _achievements:
     _client30min:
       title: "Un descansito"
       description: "30 minutos dedicados a Misskey"
+    _client60min:
+      title: "Viendo mucho Misskey."
     _noteDeletedWithin1min:
       title: "Ah... Mejor no..."
       description: "Borrar una nota antes que de pase 1 minuto"
diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml
index d8ac41c92..173380805 100644
--- a/locales/fr-FR.yml
+++ b/locales/fr-FR.yml
@@ -908,12 +908,14 @@ neverShow: "Ne plus afficher"
 remindMeLater: "Peut-être plus tard"
 roles: "Rôles"
 role: "Rôles"
+noRole: "Aucun rôle"
 normalUser: "Simple utilisateur·rice"
 assign: "Attribuer"
 color: "Couleur"
 manageCustomEmojis: "Gestion des émojis personnalisés"
 preset: "Préréglage"
 selectFromPresets: "Sélectionner à partir des préréglages"
+thisPostMayBeAnnoying: "Cette note peut gêner d'autres personnes."
 thisPostMayBeAnnoyingCancel: "Annuler"
 license: "Licence"
 video: "Vidéo"
@@ -921,6 +923,7 @@ videos: "Vidéos"
 dataSaver: "Économiseur de données"
 accountMigration: "Migration de compte"
 accountMoved: "Cet·te utilisateur·rice a migré son compte vers :"
+addMemo: "Ajouter un mémo"
 notificationDisplay: "Style des notifications"
 leftTop: "En haut à gauche"
 rightTop: "En haut à droite"
@@ -935,6 +938,8 @@ _achievements:
     _notes1:
       description: "Publiez votre première note"
       flavor: "Passez un bon moment avec Misskey !"
+    _notes100:
+      title: "Beaucoup de notes"
     _notes100000:
       title: "ALL YOUR NOTE ARE BELONG TO US"
     _login3:
@@ -985,6 +990,8 @@ _achievements:
       title: "Joyeux Anniversaire !"
     _loggedInOnNewYearsDay:
       title: "Bonne année !"
+    _cookieClicked:
+      flavor: "Attendez une minute, vous êtes sur le mauvais site web ?"
 _role:
   assignTarget: "Attribuer"
   priority: "Priorité"
diff --git a/locales/generateDTS.js b/locales/generateDTS.js
new file mode 100644
index 000000000..5949aee7c
--- /dev/null
+++ b/locales/generateDTS.js
@@ -0,0 +1,72 @@
+const fs = require('fs');
+const yaml = require('js-yaml');
+const ts = require('typescript');
+
+function createMembers(record) {
+	return Object.entries(record)
+		.map(([k, v]) => ts.factory.createPropertySignature(
+			undefined,
+			ts.factory.createStringLiteral(k),
+			undefined,
+			typeof v === 'string'
+				? ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
+				: ts.factory.createTypeLiteralNode(createMembers(v)),
+		));
+}
+
+module.exports = function generateDTS() {
+	const locale = yaml.load(fs.readFileSync(`${__dirname}/ja-JP.yml`, 'utf-8'));
+	const members = createMembers(locale);
+	const elements = [
+		ts.factory.createInterfaceDeclaration(
+			[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
+			ts.factory.createIdentifier('Locale'),
+			undefined,
+			undefined,
+			members,
+		),
+		ts.factory.createVariableStatement(
+			[ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)],
+			ts.factory.createVariableDeclarationList(
+				[ts.factory.createVariableDeclaration(
+					ts.factory.createIdentifier('locales'),
+					undefined,
+					ts.factory.createTypeLiteralNode([ts.factory.createIndexSignature(
+						undefined,
+						[ts.factory.createParameterDeclaration(
+							undefined,
+							undefined,
+							ts.factory.createIdentifier('lang'),
+							undefined,
+							ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
+							undefined,
+						)],
+						ts.factory.createTypeReferenceNode(
+							ts.factory.createIdentifier('Locale'),
+							undefined,
+						),
+					)]),
+					undefined,
+				)],
+				ts.NodeFlags.Const | ts.NodeFlags.Ambient | ts.NodeFlags.ContextFlags,
+			),
+		),
+		ts.factory.createExportAssignment(
+			undefined,
+			true,
+			ts.factory.createIdentifier('locales'),
+		),
+	];
+	const printed = ts.createPrinter({
+		newLine: ts.NewLineKind.LineFeed,
+	}).printList(
+		ts.ListFormat.MultiLine,
+		ts.factory.createNodeArray(elements),
+		ts.createSourceFile('index.d.ts', '', ts.ScriptTarget.ESNext, true, ts.ScriptKind.TS),
+	);
+
+	fs.writeFileSync(`${__dirname}/index.d.ts`, `/* eslint-disable */
+// This file is generated by locales/generateDTS.js
+// Do not edit this file directly.
+${printed}`, 'utf-8');
+}
diff --git a/locales/id-ID.yml b/locales/id-ID.yml
index df42697cc..70217caa2 100644
--- a/locales/id-ID.yml
+++ b/locales/id-ID.yml
@@ -20,6 +20,7 @@ noNotes: "Tidak ada catatan"
 noNotifications: "Tidak ada pemberitahuan"
 instance: "Instansi"
 settings: "Pengaturan"
+notificationSettings: "Atur Notifikasi"
 basicSettings: "Pengaturan umum"
 otherSettings: "Pengaturan lainnya"
 openInWindow: "Buka di jendela"
@@ -261,8 +262,10 @@ noMoreHistory: "Tidak ada sejarah lagi"
 startMessaging: "Mulai mengirim pesan"
 nUsersRead: "Dibaca oleh {n}"
 agreeTo: "Saya setuju kepada {0}"
+agree: "Setuju"
 agreeBelow: "Saya setuju dengan di bawah ini"
 basicNotesBeforeCreateAccount: "Catatan penting"
+termsOfService: "Syarat dan ketentuan"
 start: "Mulai"
 home: "Beranda"
 remoteUserCaution: "Informasi ini mungkin tidak mutakhir, karena pengguna ini berasal dari instansi luar."
diff --git a/locales/index.d.ts b/locales/index.d.ts
index fe3edb445..7047f42ef 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -1,3 +1,2150 @@
-declare const locales: { [lang: string]: any };
-
+/* eslint-disable */
+// This file is generated by locales/generateDTS.js
+// Do not edit this file directly.
+export interface Locale {
+    "_lang_": string;
+    "headlineMisskey": string;
+    "introMisskey": string;
+    "poweredByMisskeyDescription": string;
+    "monthAndDay": string;
+    "search": string;
+    "notifications": string;
+    "username": string;
+    "password": string;
+    "forgotPassword": string;
+    "fetchingAsApObject": string;
+    "ok": string;
+    "gotIt": string;
+    "cancel": string;
+    "noThankYou": string;
+    "enterUsername": string;
+    "renotedBy": string;
+    "noNotes": string;
+    "noNotifications": string;
+    "instance": string;
+    "settings": string;
+    "notificationSettings": string;
+    "basicSettings": string;
+    "otherSettings": string;
+    "openInWindow": string;
+    "profile": string;
+    "timeline": string;
+    "noAccountDescription": string;
+    "login": string;
+    "loggingIn": string;
+    "logout": string;
+    "signup": string;
+    "uploading": string;
+    "save": string;
+    "users": string;
+    "addUser": string;
+    "favorite": string;
+    "favorites": string;
+    "unfavorite": string;
+    "favorited": string;
+    "alreadyFavorited": string;
+    "cantFavorite": string;
+    "pin": string;
+    "unpin": string;
+    "copyContent": string;
+    "copyLink": string;
+    "delete": string;
+    "deleteAndEdit": string;
+    "deleteAndEditConfirm": string;
+    "addToList": string;
+    "sendMessage": string;
+    "copyRSS": string;
+    "copyUsername": string;
+    "copyUserId": string;
+    "copyNoteId": string;
+    "searchUser": string;
+    "reply": string;
+    "loadMore": string;
+    "showMore": string;
+    "showLess": string;
+    "youGotNewFollower": string;
+    "receiveFollowRequest": string;
+    "followRequestAccepted": string;
+    "mention": string;
+    "mentions": string;
+    "directNotes": string;
+    "importAndExport": string;
+    "import": string;
+    "export": string;
+    "files": string;
+    "download": string;
+    "driveFileDeleteConfirm": string;
+    "unfollowConfirm": string;
+    "exportRequested": string;
+    "importRequested": string;
+    "lists": string;
+    "noLists": string;
+    "note": string;
+    "notes": string;
+    "following": string;
+    "followers": string;
+    "followsYou": string;
+    "createList": string;
+    "manageLists": string;
+    "error": string;
+    "somethingHappened": string;
+    "retry": string;
+    "pageLoadError": string;
+    "pageLoadErrorDescription": string;
+    "serverIsDead": string;
+    "youShouldUpgradeClient": string;
+    "enterListName": string;
+    "privacy": string;
+    "makeFollowManuallyApprove": string;
+    "defaultNoteVisibility": string;
+    "follow": string;
+    "followRequest": string;
+    "followRequests": string;
+    "unfollow": string;
+    "followRequestPending": string;
+    "enterEmoji": string;
+    "renote": string;
+    "unrenote": string;
+    "renoted": string;
+    "cantRenote": string;
+    "cantReRenote": string;
+    "quote": string;
+    "inChannelRenote": string;
+    "inChannelQuote": string;
+    "pinnedNote": string;
+    "pinned": string;
+    "you": string;
+    "clickToShow": string;
+    "sensitive": string;
+    "add": string;
+    "reaction": string;
+    "reactions": string;
+    "reactionSetting": string;
+    "reactionSettingDescription2": string;
+    "rememberNoteVisibility": string;
+    "attachCancel": string;
+    "markAsSensitive": string;
+    "unmarkAsSensitive": string;
+    "enterFileName": string;
+    "mute": string;
+    "unmute": string;
+    "renoteMute": string;
+    "renoteUnmute": string;
+    "block": string;
+    "unblock": string;
+    "suspend": string;
+    "unsuspend": string;
+    "blockConfirm": string;
+    "unblockConfirm": string;
+    "suspendConfirm": string;
+    "unsuspendConfirm": string;
+    "selectList": string;
+    "selectChannel": string;
+    "selectAntenna": string;
+    "selectWidget": string;
+    "editWidgets": string;
+    "editWidgetsExit": string;
+    "customEmojis": string;
+    "emoji": string;
+    "emojis": string;
+    "emojiName": string;
+    "emojiUrl": string;
+    "addEmoji": string;
+    "settingGuide": string;
+    "cacheRemoteFiles": string;
+    "cacheRemoteFilesDescription": string;
+    "flagAsBot": string;
+    "flagAsBotDescription": string;
+    "flagAsCat": string;
+    "flagAsCatDescription": string;
+    "flagShowTimelineReplies": string;
+    "flagShowTimelineRepliesDescription": string;
+    "autoAcceptFollowed": string;
+    "addAccount": string;
+    "reloadAccountsList": string;
+    "loginFailed": string;
+    "showOnRemote": string;
+    "general": string;
+    "wallpaper": string;
+    "setWallpaper": string;
+    "removeWallpaper": string;
+    "searchWith": string;
+    "youHaveNoLists": string;
+    "followConfirm": string;
+    "proxyAccount": string;
+    "proxyAccountDescription": string;
+    "host": string;
+    "selectUser": string;
+    "recipient": string;
+    "annotation": string;
+    "federation": string;
+    "instances": string;
+    "registeredAt": string;
+    "latestRequestReceivedAt": string;
+    "latestStatus": string;
+    "storageUsage": string;
+    "charts": string;
+    "perHour": string;
+    "perDay": string;
+    "stopActivityDelivery": string;
+    "blockThisInstance": string;
+    "operations": string;
+    "software": string;
+    "version": string;
+    "metadata": string;
+    "withNFiles": string;
+    "monitor": string;
+    "jobQueue": string;
+    "cpuAndMemory": string;
+    "network": string;
+    "disk": string;
+    "instanceInfo": string;
+    "statistics": string;
+    "clearQueue": string;
+    "clearQueueConfirmTitle": string;
+    "clearQueueConfirmText": string;
+    "clearCachedFiles": string;
+    "clearCachedFilesConfirm": string;
+    "blockedInstances": string;
+    "blockedInstancesDescription": string;
+    "muteAndBlock": string;
+    "mutedUsers": string;
+    "blockedUsers": string;
+    "noUsers": string;
+    "editProfile": string;
+    "noteDeleteConfirm": string;
+    "pinLimitExceeded": string;
+    "intro": string;
+    "done": string;
+    "processing": string;
+    "preview": string;
+    "default": string;
+    "defaultValueIs": string;
+    "noCustomEmojis": string;
+    "noJobs": string;
+    "federating": string;
+    "blocked": string;
+    "suspended": string;
+    "all": string;
+    "subscribing": string;
+    "publishing": string;
+    "notResponding": string;
+    "instanceFollowing": string;
+    "instanceFollowers": string;
+    "instanceUsers": string;
+    "changePassword": string;
+    "security": string;
+    "retypedNotMatch": string;
+    "currentPassword": string;
+    "newPassword": string;
+    "newPasswordRetype": string;
+    "attachFile": string;
+    "more": string;
+    "featured": string;
+    "usernameOrUserId": string;
+    "noSuchUser": string;
+    "lookup": string;
+    "announcements": string;
+    "imageUrl": string;
+    "remove": string;
+    "removed": string;
+    "removeAreYouSure": string;
+    "deleteAreYouSure": string;
+    "resetAreYouSure": string;
+    "saved": string;
+    "messaging": string;
+    "upload": string;
+    "keepOriginalUploading": string;
+    "keepOriginalUploadingDescription": string;
+    "fromDrive": string;
+    "fromUrl": string;
+    "uploadFromUrl": string;
+    "uploadFromUrlDescription": string;
+    "uploadFromUrlRequested": string;
+    "uploadFromUrlMayTakeTime": string;
+    "explore": string;
+    "messageRead": string;
+    "noMoreHistory": string;
+    "startMessaging": string;
+    "nUsersRead": string;
+    "agreeTo": string;
+    "agree": string;
+    "agreeBelow": string;
+    "basicNotesBeforeCreateAccount": string;
+    "termsOfService": string;
+    "start": string;
+    "home": string;
+    "remoteUserCaution": string;
+    "activity": string;
+    "images": string;
+    "image": string;
+    "birthday": string;
+    "yearsOld": string;
+    "registeredDate": string;
+    "location": string;
+    "theme": string;
+    "themeForLightMode": string;
+    "themeForDarkMode": string;
+    "light": string;
+    "dark": string;
+    "lightThemes": string;
+    "darkThemes": string;
+    "syncDeviceDarkMode": string;
+    "drive": string;
+    "fileName": string;
+    "selectFile": string;
+    "selectFiles": string;
+    "selectFolder": string;
+    "selectFolders": string;
+    "renameFile": string;
+    "folderName": string;
+    "createFolder": string;
+    "renameFolder": string;
+    "deleteFolder": string;
+    "addFile": string;
+    "emptyDrive": string;
+    "emptyFolder": string;
+    "unableToDelete": string;
+    "inputNewFileName": string;
+    "inputNewDescription": string;
+    "inputNewFolderName": string;
+    "circularReferenceFolder": string;
+    "hasChildFilesOrFolders": string;
+    "copyUrl": string;
+    "rename": string;
+    "avatar": string;
+    "banner": string;
+    "nsfw": string;
+    "whenServerDisconnected": string;
+    "disconnectedFromServer": string;
+    "reload": string;
+    "doNothing": string;
+    "reloadConfirm": string;
+    "watch": string;
+    "unwatch": string;
+    "accept": string;
+    "reject": string;
+    "normal": string;
+    "instanceName": string;
+    "instanceDescription": string;
+    "maintainerName": string;
+    "maintainerEmail": string;
+    "tosUrl": string;
+    "thisYear": string;
+    "thisMonth": string;
+    "today": string;
+    "dayX": string;
+    "monthX": string;
+    "yearX": string;
+    "pages": string;
+    "integration": string;
+    "connectService": string;
+    "disconnectService": string;
+    "enableLocalTimeline": string;
+    "enableGlobalTimeline": string;
+    "disablingTimelinesInfo": string;
+    "registration": string;
+    "enableRegistration": string;
+    "invite": string;
+    "driveCapacityPerLocalAccount": string;
+    "driveCapacityPerRemoteAccount": string;
+    "inMb": string;
+    "iconUrl": string;
+    "bannerUrl": string;
+    "backgroundImageUrl": string;
+    "basicInfo": string;
+    "pinnedUsers": string;
+    "pinnedUsersDescription": string;
+    "pinnedPages": string;
+    "pinnedPagesDescription": string;
+    "pinnedClipId": string;
+    "pinnedNotes": string;
+    "hcaptcha": string;
+    "enableHcaptcha": string;
+    "hcaptchaSiteKey": string;
+    "hcaptchaSecretKey": string;
+    "recaptcha": string;
+    "enableRecaptcha": string;
+    "recaptchaSiteKey": string;
+    "recaptchaSecretKey": string;
+    "turnstile": string;
+    "enableTurnstile": string;
+    "turnstileSiteKey": string;
+    "turnstileSecretKey": string;
+    "avoidMultiCaptchaConfirm": string;
+    "antennas": string;
+    "manageAntennas": string;
+    "name": string;
+    "antennaSource": string;
+    "antennaKeywords": string;
+    "antennaExcludeKeywords": string;
+    "antennaKeywordsDescription": string;
+    "notifyAntenna": string;
+    "withFileAntenna": string;
+    "enableServiceworker": string;
+    "antennaUsersDescription": string;
+    "caseSensitive": string;
+    "withReplies": string;
+    "connectedTo": string;
+    "notesAndReplies": string;
+    "withFiles": string;
+    "silence": string;
+    "silenceConfirm": string;
+    "unsilence": string;
+    "unsilenceConfirm": string;
+    "popularUsers": string;
+    "recentlyUpdatedUsers": string;
+    "recentlyRegisteredUsers": string;
+    "recentlyDiscoveredUsers": string;
+    "exploreUsersCount": string;
+    "exploreFediverse": string;
+    "popularTags": string;
+    "userList": string;
+    "about": string;
+    "aboutMisskey": string;
+    "administrator": string;
+    "token": string;
+    "2fa": string;
+    "totp": string;
+    "totpDescription": string;
+    "moderator": string;
+    "moderation": string;
+    "nUsersMentioned": string;
+    "securityKeyAndPasskey": string;
+    "securityKey": string;
+    "lastUsed": string;
+    "lastUsedAt": string;
+    "unregister": string;
+    "passwordLessLogin": string;
+    "passwordLessLoginDescription": string;
+    "resetPassword": string;
+    "newPasswordIs": string;
+    "reduceUiAnimation": string;
+    "share": string;
+    "notFound": string;
+    "notFoundDescription": string;
+    "uploadFolder": string;
+    "cacheClear": string;
+    "markAsReadAllNotifications": string;
+    "markAsReadAllUnreadNotes": string;
+    "markAsReadAllTalkMessages": string;
+    "help": string;
+    "inputMessageHere": string;
+    "close": string;
+    "invites": string;
+    "members": string;
+    "transfer": string;
+    "title": string;
+    "text": string;
+    "enable": string;
+    "next": string;
+    "retype": string;
+    "noteOf": string;
+    "quoteAttached": string;
+    "quoteQuestion": string;
+    "noMessagesYet": string;
+    "newMessageExists": string;
+    "onlyOneFileCanBeAttached": string;
+    "signinRequired": string;
+    "invitations": string;
+    "invitationCode": string;
+    "checking": string;
+    "available": string;
+    "unavailable": string;
+    "usernameInvalidFormat": string;
+    "tooShort": string;
+    "tooLong": string;
+    "weakPassword": string;
+    "normalPassword": string;
+    "strongPassword": string;
+    "passwordMatched": string;
+    "passwordNotMatched": string;
+    "signinWith": string;
+    "signinFailed": string;
+    "or": string;
+    "language": string;
+    "uiLanguage": string;
+    "aboutX": string;
+    "emojiStyle": string;
+    "native": string;
+    "disableDrawer": string;
+    "showNoteActionsOnlyHover": string;
+    "noHistory": string;
+    "signinHistory": string;
+    "enableAdvancedMfm": string;
+    "enableAnimatedMfm": string;
+    "doing": string;
+    "category": string;
+    "tags": string;
+    "docSource": string;
+    "createAccount": string;
+    "existingAccount": string;
+    "regenerate": string;
+    "fontSize": string;
+    "mediaListWithOneImageAppearance": string;
+    "limitTo": string;
+    "noFollowRequests": string;
+    "openImageInNewTab": string;
+    "dashboard": string;
+    "local": string;
+    "remote": string;
+    "total": string;
+    "weekOverWeekChanges": string;
+    "dayOverDayChanges": string;
+    "appearance": string;
+    "clientSettings": string;
+    "accountSettings": string;
+    "promotion": string;
+    "promote": string;
+    "numberOfDays": string;
+    "hideThisNote": string;
+    "showFeaturedNotesInTimeline": string;
+    "objectStorage": string;
+    "useObjectStorage": string;
+    "objectStorageBaseUrl": string;
+    "objectStorageBaseUrlDesc": string;
+    "objectStorageBucket": string;
+    "objectStorageBucketDesc": string;
+    "objectStoragePrefix": string;
+    "objectStoragePrefixDesc": string;
+    "objectStorageEndpoint": string;
+    "objectStorageEndpointDesc": string;
+    "objectStorageRegion": string;
+    "objectStorageRegionDesc": string;
+    "objectStorageUseSSL": string;
+    "objectStorageUseSSLDesc": string;
+    "objectStorageUseProxy": string;
+    "objectStorageUseProxyDesc": string;
+    "objectStorageSetPublicRead": string;
+    "s3ForcePathStyleDesc": string;
+    "serverLogs": string;
+    "deleteAll": string;
+    "showFixedPostForm": string;
+    "showFixedPostFormInChannel": string;
+    "newNoteRecived": string;
+    "sounds": string;
+    "sound": string;
+    "listen": string;
+    "none": string;
+    "showInPage": string;
+    "popout": string;
+    "volume": string;
+    "masterVolume": string;
+    "details": string;
+    "chooseEmoji": string;
+    "unableToProcess": string;
+    "recentUsed": string;
+    "install": string;
+    "uninstall": string;
+    "installedApps": string;
+    "nothing": string;
+    "installedDate": string;
+    "lastUsedDate": string;
+    "state": string;
+    "sort": string;
+    "ascendingOrder": string;
+    "descendingOrder": string;
+    "scratchpad": string;
+    "scratchpadDescription": string;
+    "output": string;
+    "script": string;
+    "disablePagesScript": string;
+    "updateRemoteUser": string;
+    "deleteAllFiles": string;
+    "deleteAllFilesConfirm": string;
+    "removeAllFollowing": string;
+    "removeAllFollowingDescription": string;
+    "userSuspended": string;
+    "userSilenced": string;
+    "yourAccountSuspendedTitle": string;
+    "yourAccountSuspendedDescription": string;
+    "tokenRevoked": string;
+    "tokenRevokedDescription": string;
+    "accountDeleted": string;
+    "accountDeletedDescription": string;
+    "menu": string;
+    "divider": string;
+    "addItem": string;
+    "rearrange": string;
+    "relays": string;
+    "addRelay": string;
+    "inboxUrl": string;
+    "addedRelays": string;
+    "serviceworkerInfo": string;
+    "deletedNote": string;
+    "invisibleNote": string;
+    "enableInfiniteScroll": string;
+    "visibility": string;
+    "poll": string;
+    "useCw": string;
+    "enablePlayer": string;
+    "disablePlayer": string;
+    "expandTweet": string;
+    "themeEditor": string;
+    "description": string;
+    "describeFile": string;
+    "enterFileDescription": string;
+    "author": string;
+    "leaveConfirm": string;
+    "manage": string;
+    "plugins": string;
+    "preferencesBackups": string;
+    "deck": string;
+    "undeck": string;
+    "useBlurEffectForModal": string;
+    "useFullReactionPicker": string;
+    "width": string;
+    "height": string;
+    "large": string;
+    "medium": string;
+    "small": string;
+    "generateAccessToken": string;
+    "permission": string;
+    "enableAll": string;
+    "disableAll": string;
+    "tokenRequested": string;
+    "pluginTokenRequestedDescription": string;
+    "notificationType": string;
+    "edit": string;
+    "emailServer": string;
+    "enableEmail": string;
+    "emailConfigInfo": string;
+    "email": string;
+    "emailAddress": string;
+    "smtpConfig": string;
+    "smtpHost": string;
+    "smtpPort": string;
+    "smtpUser": string;
+    "smtpPass": string;
+    "emptyToDisableSmtpAuth": string;
+    "smtpSecure": string;
+    "smtpSecureInfo": string;
+    "testEmail": string;
+    "wordMute": string;
+    "regexpError": string;
+    "regexpErrorDescription": string;
+    "instanceMute": string;
+    "userSaysSomething": string;
+    "makeActive": string;
+    "display": string;
+    "copy": string;
+    "metrics": string;
+    "overview": string;
+    "logs": string;
+    "delayed": string;
+    "database": string;
+    "channel": string;
+    "create": string;
+    "notificationSetting": string;
+    "notificationSettingDesc": string;
+    "useGlobalSetting": string;
+    "useGlobalSettingDesc": string;
+    "other": string;
+    "regenerateLoginToken": string;
+    "regenerateLoginTokenDescription": string;
+    "setMultipleBySeparatingWithSpace": string;
+    "fileIdOrUrl": string;
+    "behavior": string;
+    "sample": string;
+    "abuseReports": string;
+    "reportAbuse": string;
+    "reportAbuseOf": string;
+    "fillAbuseReportDescription": string;
+    "abuseReported": string;
+    "reporter": string;
+    "reporteeOrigin": string;
+    "reporterOrigin": string;
+    "forwardReport": string;
+    "forwardReportIsAnonymous": string;
+    "send": string;
+    "abuseMarkAsResolved": string;
+    "openInNewTab": string;
+    "openInSideView": string;
+    "defaultNavigationBehaviour": string;
+    "editTheseSettingsMayBreakAccount": string;
+    "instanceTicker": string;
+    "waitingFor": string;
+    "random": string;
+    "system": string;
+    "switchUi": string;
+    "desktop": string;
+    "clip": string;
+    "createNew": string;
+    "optional": string;
+    "createNewClip": string;
+    "unclip": string;
+    "confirmToUnclipAlreadyClippedNote": string;
+    "public": string;
+    "i18nInfo": string;
+    "manageAccessTokens": string;
+    "accountInfo": string;
+    "notesCount": string;
+    "repliesCount": string;
+    "renotesCount": string;
+    "repliedCount": string;
+    "renotedCount": string;
+    "followingCount": string;
+    "followersCount": string;
+    "sentReactionsCount": string;
+    "receivedReactionsCount": string;
+    "pollVotesCount": string;
+    "pollVotedCount": string;
+    "yes": string;
+    "no": string;
+    "driveFilesCount": string;
+    "driveUsage": string;
+    "noCrawle": string;
+    "noCrawleDescription": string;
+    "lockedAccountInfo": string;
+    "alwaysMarkSensitive": string;
+    "loadRawImages": string;
+    "disableShowingAnimatedImages": string;
+    "verificationEmailSent": string;
+    "notSet": string;
+    "emailVerified": string;
+    "noteFavoritesCount": string;
+    "pageLikesCount": string;
+    "pageLikedCount": string;
+    "contact": string;
+    "useSystemFont": string;
+    "clips": string;
+    "experimentalFeatures": string;
+    "experimental": string;
+    "thisIsExperimentalFeature": string;
+    "developer": string;
+    "makeExplorable": string;
+    "makeExplorableDescription": string;
+    "showGapBetweenNotesInTimeline": string;
+    "duplicate": string;
+    "left": string;
+    "center": string;
+    "wide": string;
+    "narrow": string;
+    "reloadToApplySetting": string;
+    "needReloadToApply": string;
+    "showTitlebar": string;
+    "clearCache": string;
+    "onlineUsersCount": string;
+    "nUsers": string;
+    "nNotes": string;
+    "sendErrorReports": string;
+    "sendErrorReportsDescription": string;
+    "myTheme": string;
+    "backgroundColor": string;
+    "accentColor": string;
+    "textColor": string;
+    "saveAs": string;
+    "advanced": string;
+    "advancedSettings": string;
+    "value": string;
+    "createdAt": string;
+    "updatedAt": string;
+    "saveConfirm": string;
+    "deleteConfirm": string;
+    "invalidValue": string;
+    "registry": string;
+    "closeAccount": string;
+    "currentVersion": string;
+    "latestVersion": string;
+    "youAreRunningUpToDateClient": string;
+    "newVersionOfClientAvailable": string;
+    "usageAmount": string;
+    "capacity": string;
+    "inUse": string;
+    "editCode": string;
+    "apply": string;
+    "receiveAnnouncementFromInstance": string;
+    "emailNotification": string;
+    "publish": string;
+    "inChannelSearch": string;
+    "useReactionPickerForContextMenu": string;
+    "typingUsers": string;
+    "jumpToSpecifiedDate": string;
+    "showingPastTimeline": string;
+    "clear": string;
+    "markAllAsRead": string;
+    "goBack": string;
+    "unlikeConfirm": string;
+    "fullView": string;
+    "quitFullView": string;
+    "addDescription": string;
+    "userPagePinTip": string;
+    "notSpecifiedMentionWarning": string;
+    "info": string;
+    "userInfo": string;
+    "unknown": string;
+    "onlineStatus": string;
+    "hideOnlineStatus": string;
+    "hideOnlineStatusDescription": string;
+    "online": string;
+    "active": string;
+    "offline": string;
+    "notRecommended": string;
+    "botProtection": string;
+    "instanceBlocking": string;
+    "selectAccount": string;
+    "switchAccount": string;
+    "enabled": string;
+    "disabled": string;
+    "quickAction": string;
+    "user": string;
+    "administration": string;
+    "accounts": string;
+    "switch": string;
+    "noMaintainerInformationWarning": string;
+    "noBotProtectionWarning": string;
+    "configure": string;
+    "postToGallery": string;
+    "postToHashtag": string;
+    "gallery": string;
+    "recentPosts": string;
+    "popularPosts": string;
+    "shareWithNote": string;
+    "ads": string;
+    "expiration": string;
+    "startingperiod": string;
+    "memo": string;
+    "priority": string;
+    "high": string;
+    "middle": string;
+    "low": string;
+    "emailNotConfiguredWarning": string;
+    "ratio": string;
+    "previewNoteText": string;
+    "customCss": string;
+    "customCssWarn": string;
+    "global": string;
+    "squareAvatars": string;
+    "sent": string;
+    "received": string;
+    "searchResult": string;
+    "hashtags": string;
+    "troubleshooting": string;
+    "useBlurEffect": string;
+    "learnMore": string;
+    "misskeyUpdated": string;
+    "whatIsNew": string;
+    "translate": string;
+    "translatedFrom": string;
+    "accountDeletionInProgress": string;
+    "usernameInfo": string;
+    "aiChanMode": string;
+    "devMode": string;
+    "keepCw": string;
+    "pubSub": string;
+    "lastCommunication": string;
+    "resolved": string;
+    "unresolved": string;
+    "breakFollow": string;
+    "breakFollowConfirm": string;
+    "itsOn": string;
+    "itsOff": string;
+    "on": string;
+    "off": string;
+    "emailRequiredForSignup": string;
+    "unread": string;
+    "filter": string;
+    "controlPanel": string;
+    "manageAccounts": string;
+    "makeReactionsPublic": string;
+    "makeReactionsPublicDescription": string;
+    "classic": string;
+    "muteThread": string;
+    "unmuteThread": string;
+    "ffVisibility": string;
+    "ffVisibilityDescription": string;
+    "continueThread": string;
+    "deleteAccountConfirm": string;
+    "incorrectPassword": string;
+    "voteConfirm": string;
+    "hide": string;
+    "useDrawerReactionPickerForMobile": string;
+    "welcomeBackWithName": string;
+    "clickToFinishEmailVerification": string;
+    "overridedDeviceKind": string;
+    "smartphone": string;
+    "tablet": string;
+    "auto": string;
+    "themeColor": string;
+    "size": string;
+    "numberOfColumn": string;
+    "searchByGoogle": string;
+    "instanceDefaultLightTheme": string;
+    "instanceDefaultDarkTheme": string;
+    "instanceDefaultThemeDescription": string;
+    "mutePeriod": string;
+    "period": string;
+    "indefinitely": string;
+    "tenMinutes": string;
+    "oneHour": string;
+    "oneDay": string;
+    "oneWeek": string;
+    "oneMonth": string;
+    "reflectMayTakeTime": string;
+    "failedToFetchAccountInformation": string;
+    "rateLimitExceeded": string;
+    "cropImage": string;
+    "cropImageAsk": string;
+    "cropYes": string;
+    "cropNo": string;
+    "file": string;
+    "recentNHours": string;
+    "recentNDays": string;
+    "noEmailServerWarning": string;
+    "thereIsUnresolvedAbuseReportWarning": string;
+    "recommended": string;
+    "check": string;
+    "driveCapOverrideLabel": string;
+    "driveCapOverrideCaption": string;
+    "requireAdminForView": string;
+    "isSystemAccount": string;
+    "typeToConfirm": string;
+    "deleteAccount": string;
+    "document": string;
+    "numberOfPageCache": string;
+    "numberOfPageCacheDescription": string;
+    "logoutConfirm": string;
+    "lastActiveDate": string;
+    "statusbar": string;
+    "pleaseSelect": string;
+    "reverse": string;
+    "colored": string;
+    "refreshInterval": string;
+    "label": string;
+    "type": string;
+    "speed": string;
+    "slow": string;
+    "fast": string;
+    "sensitiveMediaDetection": string;
+    "localOnly": string;
+    "remoteOnly": string;
+    "failedToUpload": string;
+    "cannotUploadBecauseInappropriate": string;
+    "cannotUploadBecauseNoFreeSpace": string;
+    "cannotUploadBecauseExceedsFileSizeLimit": string;
+    "beta": string;
+    "enableAutoSensitive": string;
+    "enableAutoSensitiveDescription": string;
+    "activeEmailValidationDescription": string;
+    "navbar": string;
+    "shuffle": string;
+    "account": string;
+    "move": string;
+    "pushNotification": string;
+    "subscribePushNotification": string;
+    "unsubscribePushNotification": string;
+    "pushNotificationAlreadySubscribed": string;
+    "pushNotificationNotSupported": string;
+    "sendPushNotificationReadMessage": string;
+    "sendPushNotificationReadMessageCaption": string;
+    "windowMaximize": string;
+    "windowMinimize": string;
+    "windowRestore": string;
+    "caption": string;
+    "loggedInAsBot": string;
+    "tools": string;
+    "cannotLoad": string;
+    "numberOfProfileView": string;
+    "like": string;
+    "unlike": string;
+    "numberOfLikes": string;
+    "show": string;
+    "neverShow": string;
+    "remindMeLater": string;
+    "didYouLikeMisskey": string;
+    "pleaseDonate": string;
+    "roles": string;
+    "role": string;
+    "noRole": string;
+    "normalUser": string;
+    "undefined": string;
+    "assign": string;
+    "unassign": string;
+    "color": string;
+    "manageCustomEmojis": string;
+    "youCannotCreateAnymore": string;
+    "cannotPerformTemporary": string;
+    "cannotPerformTemporaryDescription": string;
+    "invalidParamError": string;
+    "invalidParamErrorDescription": string;
+    "permissionDeniedError": string;
+    "permissionDeniedErrorDescription": string;
+    "preset": string;
+    "selectFromPresets": string;
+    "achievements": string;
+    "gotInvalidResponseError": string;
+    "gotInvalidResponseErrorDescription": string;
+    "thisPostMayBeAnnoying": string;
+    "thisPostMayBeAnnoyingHome": string;
+    "thisPostMayBeAnnoyingCancel": string;
+    "thisPostMayBeAnnoyingIgnore": string;
+    "collapseRenotes": string;
+    "internalServerError": string;
+    "internalServerErrorDescription": string;
+    "copyErrorInfo": string;
+    "joinThisServer": string;
+    "exploreOtherServers": string;
+    "letsLookAtTimeline": string;
+    "disableFederationConfirm": string;
+    "disableFederationConfirmWarn": string;
+    "disableFederationOk": string;
+    "invitationRequiredToRegister": string;
+    "emailNotSupported": string;
+    "postToTheChannel": string;
+    "cannotBeChangedLater": string;
+    "reactionAcceptance": string;
+    "likeOnly": string;
+    "likeOnlyForRemote": string;
+    "nonSensitiveOnly": string;
+    "nonSensitiveOnlyForLocalLikeOnlyForRemote": string;
+    "rolesAssignedToMe": string;
+    "resetPasswordConfirm": string;
+    "sensitiveWords": string;
+    "sensitiveWordsDescription": string;
+    "sensitiveWordsDescription2": string;
+    "notesSearchNotAvailable": string;
+    "license": string;
+    "unfavoriteConfirm": string;
+    "myClips": string;
+    "drivecleaner": string;
+    "retryAllQueuesNow": string;
+    "retryAllQueuesConfirmTitle": string;
+    "retryAllQueuesConfirmText": string;
+    "enableChartsForRemoteUser": string;
+    "enableChartsForFederatedInstances": string;
+    "showClipButtonInNoteFooter": string;
+    "largeNoteReactions": string;
+    "noteIdOrUrl": string;
+    "video": string;
+    "videos": string;
+    "dataSaver": string;
+    "accountMigration": string;
+    "accountMoved": string;
+    "accountMovedShort": string;
+    "operationForbidden": string;
+    "forceShowAds": string;
+    "addMemo": string;
+    "editMemo": string;
+    "reactionsList": string;
+    "renotesList": string;
+    "notificationDisplay": string;
+    "leftTop": string;
+    "rightTop": string;
+    "leftBottom": string;
+    "rightBottom": string;
+    "stackAxis": string;
+    "vertical": string;
+    "horizontal": string;
+    "position": string;
+    "serverRules": string;
+    "pleaseConfirmBelowBeforeSignup": string;
+    "pleaseAgreeAllToContinue": string;
+    "continue": string;
+    "preservedUsernames": string;
+    "preservedUsernamesDescription": string;
+    "createNoteFromTheFile": string;
+    "archive": string;
+    "channelArchiveConfirmTitle": string;
+    "channelArchiveConfirmDescription": string;
+    "thisChannelArchived": string;
+    "displayOfNote": string;
+    "initialAccountSetting": string;
+    "youFollowing": string;
+    "preventAiLearning": string;
+    "preventAiLearningDescription": string;
+    "options": string;
+    "specifyUser": string;
+    "failedToPreviewUrl": string;
+    "update": string;
+    "rolesThatCanBeUsedThisEmojiAsReaction": string;
+    "rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription": string;
+    "rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn": string;
+    "cancelReactionConfirm": string;
+    "changeReactionConfirm": string;
+    "later": string;
+    "goToMisskey": string;
+    "additionalEmojiDictionary": string;
+    "installed": string;
+    "_initialAccountSetting": {
+        "accountCreated": string;
+        "letsStartAccountSetup": string;
+        "letsFillYourProfile": string;
+        "profileSetting": string;
+        "privacySetting": string;
+        "theseSettingsCanEditLater": string;
+        "youCanEditMoreSettingsInSettingsPageLater": string;
+        "followUsers": string;
+        "pushNotificationDescription": string;
+        "initialAccountSettingCompleted": string;
+        "haveFun": string;
+        "ifYouNeedLearnMore": string;
+        "skipAreYouSure": string;
+        "laterAreYouSure": string;
+    };
+    "_serverRules": {
+        "description": string;
+    };
+    "_accountMigration": {
+        "moveFrom": string;
+        "moveFromSub": string;
+        "moveFromLabel": string;
+        "moveFromDescription": string;
+        "moveTo": string;
+        "moveToLabel": string;
+        "moveCannotBeUndone": string;
+        "moveAccountDescription": string;
+        "moveAccountHowTo": string;
+        "startMigration": string;
+        "migrationConfirm": string;
+        "movedAndCannotBeUndone": string;
+        "postMigrationNote": string;
+        "movedTo": string;
+    };
+    "_achievements": {
+        "earnedAt": string;
+        "_types": {
+            "_notes1": {
+                "title": string;
+                "description": string;
+                "flavor": string;
+            };
+            "_notes10": {
+                "title": string;
+                "description": string;
+            };
+            "_notes100": {
+                "title": string;
+                "description": string;
+            };
+            "_notes500": {
+                "title": string;
+                "description": string;
+            };
+            "_notes1000": {
+                "title": string;
+                "description": string;
+            };
+            "_notes5000": {
+                "title": string;
+                "description": string;
+            };
+            "_notes10000": {
+                "title": string;
+                "description": string;
+            };
+            "_notes20000": {
+                "title": string;
+                "description": string;
+            };
+            "_notes30000": {
+                "title": string;
+                "description": string;
+            };
+            "_notes40000": {
+                "title": string;
+                "description": string;
+            };
+            "_notes50000": {
+                "title": string;
+                "description": string;
+            };
+            "_notes60000": {
+                "title": string;
+                "description": string;
+            };
+            "_notes70000": {
+                "title": string;
+                "description": string;
+            };
+            "_notes80000": {
+                "title": string;
+                "description": string;
+            };
+            "_notes90000": {
+                "title": string;
+                "description": string;
+            };
+            "_notes100000": {
+                "title": string;
+                "description": string;
+                "flavor": string;
+            };
+            "_login3": {
+                "title": string;
+                "description": string;
+                "flavor": string;
+            };
+            "_login7": {
+                "title": string;
+                "description": string;
+                "flavor": string;
+            };
+            "_login15": {
+                "title": string;
+                "description": string;
+            };
+            "_login30": {
+                "title": string;
+                "description": string;
+            };
+            "_login60": {
+                "title": string;
+                "description": string;
+            };
+            "_login100": {
+                "title": string;
+                "description": string;
+                "flavor": string;
+            };
+            "_login200": {
+                "title": string;
+                "description": string;
+            };
+            "_login300": {
+                "title": string;
+                "description": string;
+            };
+            "_login400": {
+                "title": string;
+                "description": string;
+            };
+            "_login500": {
+                "title": string;
+                "description": string;
+                "flavor": string;
+            };
+            "_login600": {
+                "title": string;
+                "description": string;
+            };
+            "_login700": {
+                "title": string;
+                "description": string;
+            };
+            "_login800": {
+                "title": string;
+                "description": string;
+            };
+            "_login900": {
+                "title": string;
+                "description": string;
+            };
+            "_login1000": {
+                "title": string;
+                "description": string;
+                "flavor": string;
+            };
+            "_noteClipped1": {
+                "title": string;
+                "description": string;
+            };
+            "_noteFavorited1": {
+                "title": string;
+                "description": string;
+            };
+            "_myNoteFavorited1": {
+                "title": string;
+                "description": string;
+            };
+            "_profileFilled": {
+                "title": string;
+                "description": string;
+            };
+            "_markedAsCat": {
+                "title": string;
+                "description": string;
+                "flavor": string;
+            };
+            "_following1": {
+                "title": string;
+                "description": string;
+            };
+            "_following10": {
+                "title": string;
+                "description": string;
+            };
+            "_following50": {
+                "title": string;
+                "description": string;
+            };
+            "_following100": {
+                "title": string;
+                "description": string;
+            };
+            "_following300": {
+                "title": string;
+                "description": string;
+            };
+            "_followers1": {
+                "title": string;
+                "description": string;
+            };
+            "_followers10": {
+                "title": string;
+                "description": string;
+            };
+            "_followers50": {
+                "title": string;
+                "description": string;
+            };
+            "_followers100": {
+                "title": string;
+                "description": string;
+            };
+            "_followers300": {
+                "title": string;
+                "description": string;
+            };
+            "_followers500": {
+                "title": string;
+                "description": string;
+            };
+            "_followers1000": {
+                "title": string;
+                "description": string;
+            };
+            "_collectAchievements30": {
+                "title": string;
+                "description": string;
+            };
+            "_viewAchievements3min": {
+                "title": string;
+                "description": string;
+            };
+            "_iLoveMisskey": {
+                "title": string;
+                "description": string;
+                "flavor": string;
+            };
+            "_foundTreasure": {
+                "title": string;
+                "description": string;
+            };
+            "_client30min": {
+                "title": string;
+                "description": string;
+            };
+            "_client60min": {
+                "title": string;
+                "description": string;
+            };
+            "_noteDeletedWithin1min": {
+                "title": string;
+                "description": string;
+            };
+            "_postedAtLateNight": {
+                "title": string;
+                "description": string;
+                "flavor": string;
+            };
+            "_postedAt0min0sec": {
+                "title": string;
+                "description": string;
+                "flavor": string;
+            };
+            "_selfQuote": {
+                "title": string;
+                "description": string;
+            };
+            "_htl20npm": {
+                "title": string;
+                "description": string;
+            };
+            "_viewInstanceChart": {
+                "title": string;
+                "description": string;
+            };
+            "_outputHelloWorldOnScratchpad": {
+                "title": string;
+                "description": string;
+            };
+            "_open3windows": {
+                "title": string;
+                "description": string;
+            };
+            "_driveFolderCircularReference": {
+                "title": string;
+                "description": string;
+            };
+            "_reactWithoutRead": {
+                "title": string;
+                "description": string;
+            };
+            "_clickedClickHere": {
+                "title": string;
+                "description": string;
+            };
+            "_justPlainLucky": {
+                "title": string;
+                "description": string;
+            };
+            "_setNameToSyuilo": {
+                "title": string;
+                "description": string;
+            };
+            "_passedSinceAccountCreated1": {
+                "title": string;
+                "description": string;
+            };
+            "_passedSinceAccountCreated2": {
+                "title": string;
+                "description": string;
+            };
+            "_passedSinceAccountCreated3": {
+                "title": string;
+                "description": string;
+            };
+            "_loggedInOnBirthday": {
+                "title": string;
+                "description": string;
+            };
+            "_loggedInOnNewYearsDay": {
+                "title": string;
+                "description": string;
+                "flavor": string;
+            };
+            "_cookieClicked": {
+                "title": string;
+                "description": string;
+                "flavor": string;
+            };
+            "_brainDiver": {
+                "title": string;
+                "description": string;
+                "flavor": string;
+            };
+        };
+    };
+    "_role": {
+        "new": string;
+        "edit": string;
+        "name": string;
+        "description": string;
+        "permission": string;
+        "descriptionOfPermission": string;
+        "assignTarget": string;
+        "descriptionOfAssignTarget": string;
+        "manual": string;
+        "conditional": string;
+        "condition": string;
+        "isConditionalRole": string;
+        "isPublic": string;
+        "descriptionOfIsPublic": string;
+        "options": string;
+        "policies": string;
+        "baseRole": string;
+        "useBaseValue": string;
+        "chooseRoleToAssign": string;
+        "iconUrl": string;
+        "asBadge": string;
+        "descriptionOfAsBadge": string;
+        "isExplorable": string;
+        "descriptionOfIsExplorable": string;
+        "displayOrder": string;
+        "descriptionOfDisplayOrder": string;
+        "canEditMembersByModerator": string;
+        "descriptionOfCanEditMembersByModerator": string;
+        "priority": string;
+        "_priority": {
+            "low": string;
+            "middle": string;
+            "high": string;
+        };
+        "_options": {
+            "gtlAvailable": string;
+            "ltlAvailable": string;
+            "canPublicNote": string;
+            "canInvite": string;
+            "canManageCustomEmojis": string;
+            "driveCapacity": string;
+            "alwaysMarkNsfw": string;
+            "pinMax": string;
+            "antennaMax": string;
+            "wordMuteMax": string;
+            "webhookMax": string;
+            "clipMax": string;
+            "noteEachClipsMax": string;
+            "userListMax": string;
+            "userEachUserListsMax": string;
+            "rateLimitFactor": string;
+            "descriptionOfRateLimitFactor": string;
+            "canHideAds": string;
+            "canSearchNotes": string;
+        };
+        "_condition": {
+            "isLocal": string;
+            "isRemote": string;
+            "createdLessThan": string;
+            "createdMoreThan": string;
+            "followersLessThanOrEq": string;
+            "followersMoreThanOrEq": string;
+            "followingLessThanOrEq": string;
+            "followingMoreThanOrEq": string;
+            "notesLessThanOrEq": string;
+            "notesMoreThanOrEq": string;
+            "and": string;
+            "or": string;
+            "not": string;
+        };
+    };
+    "_sensitiveMediaDetection": {
+        "description": string;
+        "sensitivity": string;
+        "sensitivityDescription": string;
+        "setSensitiveFlagAutomatically": string;
+        "setSensitiveFlagAutomaticallyDescription": string;
+        "analyzeVideos": string;
+        "analyzeVideosDescription": string;
+    };
+    "_emailUnavailable": {
+        "used": string;
+        "format": string;
+        "disposable": string;
+        "mx": string;
+        "smtp": string;
+    };
+    "_ffVisibility": {
+        "public": string;
+        "followers": string;
+        "private": string;
+    };
+    "_signup": {
+        "almostThere": string;
+        "emailAddressInfo": string;
+        "emailSent": string;
+    };
+    "_accountDelete": {
+        "accountDelete": string;
+        "mayTakeTime": string;
+        "sendEmail": string;
+        "requestAccountDelete": string;
+        "started": string;
+        "inProgress": string;
+    };
+    "_ad": {
+        "back": string;
+        "reduceFrequencyOfThisAd": string;
+        "hide": string;
+    };
+    "_forgotPassword": {
+        "enterEmail": string;
+        "ifNoEmail": string;
+        "contactAdmin": string;
+    };
+    "_gallery": {
+        "my": string;
+        "liked": string;
+        "like": string;
+        "unlike": string;
+    };
+    "_email": {
+        "_follow": {
+            "title": string;
+        };
+        "_receiveFollowRequest": {
+            "title": string;
+        };
+    };
+    "_plugin": {
+        "install": string;
+        "installWarn": string;
+        "manage": string;
+    };
+    "_preferencesBackups": {
+        "list": string;
+        "saveNew": string;
+        "loadFile": string;
+        "apply": string;
+        "save": string;
+        "inputName": string;
+        "cannotSave": string;
+        "nameAlreadyExists": string;
+        "applyConfirm": string;
+        "saveConfirm": string;
+        "deleteConfirm": string;
+        "renameConfirm": string;
+        "noBackups": string;
+        "createdAt": string;
+        "updatedAt": string;
+        "cannotLoad": string;
+        "invalidFile": string;
+    };
+    "_registry": {
+        "scope": string;
+        "key": string;
+        "keys": string;
+        "domain": string;
+        "createKey": string;
+    };
+    "_aboutMisskey": {
+        "about": string;
+        "contributors": string;
+        "allContributors": string;
+        "source": string;
+        "translation": string;
+        "donate": string;
+        "morePatrons": string;
+        "patrons": string;
+    };
+    "_nsfw": {
+        "respect": string;
+        "ignore": string;
+        "force": string;
+    };
+    "_instanceTicker": {
+        "none": string;
+        "remote": string;
+        "always": string;
+    };
+    "_serverDisconnectedBehavior": {
+        "reload": string;
+        "dialog": string;
+        "quiet": string;
+    };
+    "_channel": {
+        "create": string;
+        "edit": string;
+        "setBanner": string;
+        "removeBanner": string;
+        "featured": string;
+        "owned": string;
+        "following": string;
+        "usersCount": string;
+        "notesCount": string;
+        "nameAndDescription": string;
+        "nameOnly": string;
+    };
+    "_menuDisplay": {
+        "sideFull": string;
+        "sideIcon": string;
+        "top": string;
+        "hide": string;
+    };
+    "_wordMute": {
+        "muteWords": string;
+        "muteWordsDescription": string;
+        "muteWordsDescription2": string;
+        "softDescription": string;
+        "hardDescription": string;
+        "soft": string;
+        "hard": string;
+        "mutedNotes": string;
+    };
+    "_instanceMute": {
+        "instanceMuteDescription": string;
+        "instanceMuteDescription2": string;
+        "title": string;
+        "heading": string;
+    };
+    "_theme": {
+        "explore": string;
+        "install": string;
+        "manage": string;
+        "code": string;
+        "description": string;
+        "installed": string;
+        "installedThemes": string;
+        "builtinThemes": string;
+        "alreadyInstalled": string;
+        "invalid": string;
+        "make": string;
+        "base": string;
+        "addConstant": string;
+        "constant": string;
+        "defaultValue": string;
+        "color": string;
+        "refProp": string;
+        "refConst": string;
+        "key": string;
+        "func": string;
+        "funcKind": string;
+        "argument": string;
+        "basedProp": string;
+        "alpha": string;
+        "darken": string;
+        "lighten": string;
+        "inputConstantName": string;
+        "importInfo": string;
+        "deleteConstantConfirm": string;
+        "keys": {
+            "accent": string;
+            "bg": string;
+            "fg": string;
+            "focus": string;
+            "indicator": string;
+            "panel": string;
+            "shadow": string;
+            "header": string;
+            "navBg": string;
+            "navFg": string;
+            "navHoverFg": string;
+            "navActive": string;
+            "navIndicator": string;
+            "link": string;
+            "hashtag": string;
+            "mention": string;
+            "mentionMe": string;
+            "renote": string;
+            "modalBg": string;
+            "divider": string;
+            "scrollbarHandle": string;
+            "scrollbarHandleHover": string;
+            "dateLabelFg": string;
+            "infoBg": string;
+            "infoFg": string;
+            "infoWarnBg": string;
+            "infoWarnFg": string;
+            "cwBg": string;
+            "cwFg": string;
+            "cwHoverBg": string;
+            "toastBg": string;
+            "toastFg": string;
+            "buttonBg": string;
+            "buttonHoverBg": string;
+            "inputBorder": string;
+            "listItemHoverBg": string;
+            "driveFolderBg": string;
+            "wallpaperOverlay": string;
+            "badge": string;
+            "messageBg": string;
+            "accentDarken": string;
+            "accentLighten": string;
+            "fgHighlighted": string;
+        };
+    };
+    "_sfx": {
+        "note": string;
+        "noteMy": string;
+        "notification": string;
+        "chat": string;
+        "chatBg": string;
+        "antenna": string;
+        "channel": string;
+    };
+    "_ago": {
+        "future": string;
+        "justNow": string;
+        "secondsAgo": string;
+        "minutesAgo": string;
+        "hoursAgo": string;
+        "daysAgo": string;
+        "weeksAgo": string;
+        "monthsAgo": string;
+        "yearsAgo": string;
+        "invalid": string;
+    };
+    "_time": {
+        "second": string;
+        "minute": string;
+        "hour": string;
+        "day": string;
+    };
+    "_timelineTutorial": {
+        "title": string;
+        "step1_1": string;
+        "step1_2": string;
+        "step2_1": string;
+        "step2_2": string;
+        "step3_1": string;
+        "step3_2": string;
+        "step4_1": string;
+        "step4_2": string;
+    };
+    "_2fa": {
+        "alreadyRegistered": string;
+        "registerTOTP": string;
+        "passwordToTOTP": string;
+        "step1": string;
+        "step2": string;
+        "step2Click": string;
+        "step2Url": string;
+        "step3Title": string;
+        "step3": string;
+        "step4": string;
+        "securityKeyNotSupported": string;
+        "registerTOTPBeforeKey": string;
+        "securityKeyInfo": string;
+        "chromePasskeyNotSupported": string;
+        "registerSecurityKey": string;
+        "securityKeyName": string;
+        "tapSecurityKey": string;
+        "removeKey": string;
+        "removeKeyConfirm": string;
+        "whyTOTPOnlyRenew": string;
+        "renewTOTP": string;
+        "renewTOTPConfirm": string;
+        "renewTOTPOk": string;
+        "renewTOTPCancel": string;
+    };
+    "_permissions": {
+        "read:account": string;
+        "write:account": string;
+        "read:blocks": string;
+        "write:blocks": string;
+        "read:drive": string;
+        "write:drive": string;
+        "read:favorites": string;
+        "write:favorites": string;
+        "read:following": string;
+        "write:following": string;
+        "read:messaging": string;
+        "write:messaging": string;
+        "read:mutes": string;
+        "write:mutes": string;
+        "write:notes": string;
+        "read:notifications": string;
+        "write:notifications": string;
+        "read:reactions": string;
+        "write:reactions": string;
+        "write:votes": string;
+        "read:pages": string;
+        "write:pages": string;
+        "read:page-likes": string;
+        "write:page-likes": string;
+        "read:user-groups": string;
+        "write:user-groups": string;
+        "read:channels": string;
+        "write:channels": string;
+        "read:gallery": string;
+        "write:gallery": string;
+        "read:gallery-likes": string;
+        "write:gallery-likes": string;
+    };
+    "_auth": {
+        "shareAccessTitle": string;
+        "shareAccess": string;
+        "shareAccessAsk": string;
+        "permission": string;
+        "permissionAsk": string;
+        "pleaseGoBack": string;
+        "callback": string;
+        "denied": string;
+        "pleaseLogin": string;
+    };
+    "_antennaSources": {
+        "all": string;
+        "homeTimeline": string;
+        "users": string;
+        "userList": string;
+    };
+    "_weekday": {
+        "sunday": string;
+        "monday": string;
+        "tuesday": string;
+        "wednesday": string;
+        "thursday": string;
+        "friday": string;
+        "saturday": string;
+    };
+    "_widgets": {
+        "profile": string;
+        "instanceInfo": string;
+        "memo": string;
+        "notifications": string;
+        "timeline": string;
+        "calendar": string;
+        "trends": string;
+        "clock": string;
+        "rss": string;
+        "rssTicker": string;
+        "activity": string;
+        "photos": string;
+        "digitalClock": string;
+        "unixClock": string;
+        "federation": string;
+        "instanceCloud": string;
+        "postForm": string;
+        "slideshow": string;
+        "button": string;
+        "onlineUsers": string;
+        "jobQueue": string;
+        "serverMetric": string;
+        "aiscript": string;
+        "aiscriptApp": string;
+        "aichan": string;
+        "userList": string;
+        "_userList": {
+            "chooseList": string;
+        };
+        "clicker": string;
+    };
+    "_cw": {
+        "hide": string;
+        "show": string;
+        "chars": string;
+        "files": string;
+    };
+    "_poll": {
+        "noOnlyOneChoice": string;
+        "choiceN": string;
+        "noMore": string;
+        "canMultipleVote": string;
+        "expiration": string;
+        "infinite": string;
+        "at": string;
+        "after": string;
+        "deadlineDate": string;
+        "deadlineTime": string;
+        "duration": string;
+        "votesCount": string;
+        "totalVotes": string;
+        "vote": string;
+        "showResult": string;
+        "voted": string;
+        "closed": string;
+        "remainingDays": string;
+        "remainingHours": string;
+        "remainingMinutes": string;
+        "remainingSeconds": string;
+    };
+    "_visibility": {
+        "public": string;
+        "publicDescription": string;
+        "home": string;
+        "homeDescription": string;
+        "followers": string;
+        "followersDescription": string;
+        "specified": string;
+        "specifiedDescription": string;
+        "disableFederation": string;
+        "disableFederationDescription": string;
+    };
+    "_postForm": {
+        "replyPlaceholder": string;
+        "quotePlaceholder": string;
+        "channelPlaceholder": string;
+        "_placeholders": {
+            "a": string;
+            "b": string;
+            "c": string;
+            "d": string;
+            "e": string;
+            "f": string;
+        };
+    };
+    "_profile": {
+        "name": string;
+        "username": string;
+        "description": string;
+        "youCanIncludeHashtags": string;
+        "metadata": string;
+        "metadataEdit": string;
+        "metadataDescription": string;
+        "metadataLabel": string;
+        "metadataContent": string;
+        "changeAvatar": string;
+        "changeBanner": string;
+    };
+    "_exportOrImport": {
+        "allNotes": string;
+        "favoritedNotes": string;
+        "followingList": string;
+        "muteList": string;
+        "blockingList": string;
+        "userLists": string;
+        "excludeMutingUsers": string;
+        "excludeInactiveUsers": string;
+    };
+    "_charts": {
+        "federation": string;
+        "apRequest": string;
+        "usersIncDec": string;
+        "usersTotal": string;
+        "activeUsers": string;
+        "notesIncDec": string;
+        "localNotesIncDec": string;
+        "remoteNotesIncDec": string;
+        "notesTotal": string;
+        "filesIncDec": string;
+        "filesTotal": string;
+        "storageUsageIncDec": string;
+        "storageUsageTotal": string;
+    };
+    "_instanceCharts": {
+        "requests": string;
+        "users": string;
+        "usersTotal": string;
+        "notes": string;
+        "notesTotal": string;
+        "ff": string;
+        "ffTotal": string;
+        "cacheSize": string;
+        "cacheSizeTotal": string;
+        "files": string;
+        "filesTotal": string;
+    };
+    "_timelines": {
+        "home": string;
+        "local": string;
+        "social": string;
+        "global": string;
+    };
+    "_play": {
+        "new": string;
+        "edit": string;
+        "created": string;
+        "updated": string;
+        "deleted": string;
+        "pageSetting": string;
+        "editThisPage": string;
+        "viewSource": string;
+        "my": string;
+        "liked": string;
+        "featured": string;
+        "title": string;
+        "script": string;
+        "summary": string;
+    };
+    "_pages": {
+        "newPage": string;
+        "editPage": string;
+        "readPage": string;
+        "created": string;
+        "updated": string;
+        "deleted": string;
+        "pageSetting": string;
+        "nameAlreadyExists": string;
+        "invalidNameTitle": string;
+        "invalidNameText": string;
+        "editThisPage": string;
+        "viewSource": string;
+        "viewPage": string;
+        "like": string;
+        "unlike": string;
+        "my": string;
+        "liked": string;
+        "featured": string;
+        "inspector": string;
+        "contents": string;
+        "content": string;
+        "variables": string;
+        "title": string;
+        "url": string;
+        "summary": string;
+        "alignCenter": string;
+        "hideTitleWhenPinned": string;
+        "font": string;
+        "fontSerif": string;
+        "fontSansSerif": string;
+        "eyeCatchingImageSet": string;
+        "eyeCatchingImageRemove": string;
+        "chooseBlock": string;
+        "selectType": string;
+        "contentBlocks": string;
+        "inputBlocks": string;
+        "specialBlocks": string;
+        "blocks": {
+            "text": string;
+            "textarea": string;
+            "section": string;
+            "image": string;
+            "button": string;
+            "note": string;
+            "_note": {
+                "id": string;
+                "idDescription": string;
+                "detailed": string;
+            };
+        };
+    };
+    "_relayStatus": {
+        "requesting": string;
+        "accepted": string;
+        "rejected": string;
+    };
+    "_notification": {
+        "fileUploaded": string;
+        "youGotMention": string;
+        "youGotReply": string;
+        "youGotQuote": string;
+        "youRenoted": string;
+        "youWereFollowed": string;
+        "youReceivedFollowRequest": string;
+        "yourFollowRequestAccepted": string;
+        "pollEnded": string;
+        "unreadAntennaNote": string;
+        "emptyPushNotificationMessage": string;
+        "achievementEarned": string;
+        "_types": {
+            "all": string;
+            "follow": string;
+            "mention": string;
+            "reply": string;
+            "renote": string;
+            "quote": string;
+            "reaction": string;
+            "pollEnded": string;
+            "receiveFollowRequest": string;
+            "followRequestAccepted": string;
+            "achievementEarned": string;
+            "app": string;
+        };
+        "_actions": {
+            "followBack": string;
+            "reply": string;
+            "renote": string;
+        };
+    };
+    "_deck": {
+        "alwaysShowMainColumn": string;
+        "columnAlign": string;
+        "addColumn": string;
+        "configureColumn": string;
+        "swapLeft": string;
+        "swapRight": string;
+        "swapUp": string;
+        "swapDown": string;
+        "stackLeft": string;
+        "popRight": string;
+        "profile": string;
+        "newProfile": string;
+        "deleteProfile": string;
+        "introduction": string;
+        "introduction2": string;
+        "widgetsIntroduction": string;
+        "_columns": {
+            "main": string;
+            "widgets": string;
+            "notifications": string;
+            "tl": string;
+            "antenna": string;
+            "list": string;
+            "channel": string;
+            "mentions": string;
+            "direct": string;
+            "roleTimeline": string;
+        };
+    };
+    "_dialog": {
+        "charactersExceeded": string;
+        "charactersBelow": string;
+    };
+    "_disabledTimeline": {
+        "title": string;
+        "description": string;
+    };
+    "_drivecleaner": {
+        "orderBySizeDesc": string;
+        "orderByCreatedAtAsc": string;
+    };
+    "_webhookSettings": {
+        "createWebhook": string;
+        "name": string;
+        "secret": string;
+        "events": string;
+        "active": string;
+        "_events": {
+            "follow": string;
+            "followed": string;
+            "note": string;
+            "reply": string;
+            "renote": string;
+            "reaction": string;
+            "mention": string;
+        };
+    };
+}
+declare const locales: {
+    [lang: string]: Locale;
+};
 export = locales;
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 0b7108fe6..fcba3fb82 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -52,6 +52,8 @@ addToList: "リストに追加"
 sendMessage: "メッセージを送信"
 copyRSS: "RSSをコピー"
 copyUsername: "ユーザー名をコピー"
+copyUserId: "ユーザーIDをコピー"
+copyNoteId: "ノートIDをコピー"
 searchUser: "ユーザーを検索"
 reply: "返信"
 loadMore: "もっと見る"
@@ -790,6 +792,7 @@ noMaintainerInformationWarning: "管理者情報が設定されていません
 noBotProtectionWarning: "Botプロテクションが設定されていません。"
 configure: "設定する"
 postToGallery: "ギャラリーへ投稿"
+postToHashtag: "このハッシュタグで投稿"
 gallery: "ギャラリー"
 recentPosts: "最近の投稿"
 popularPosts: "人気の投稿"
@@ -823,6 +826,7 @@ translatedFrom: "{x}から翻訳"
 accountDeletionInProgress: "アカウントの削除が進行中です"
 usernameInfo: "サーバー上であなたのアカウントを一意に識別するための名前。アルファベット(a~z, A~Z)、数字(0~9)、およびアンダーバー(_)が使用できます。ユーザー名は後から変更することは出来ません。"
 aiChanMode: "藍モード"
+devMode: "開発者モード"
 keepCw: "CWを維持する"
 pubSub: "Pub/Subのアカウント"
 lastCommunication: "直近の通信"
@@ -987,7 +991,9 @@ postToTheChannel: "チャンネルに投稿"
 cannotBeChangedLater: "後から変更できません。"
 reactionAcceptance: "リアクションの受け入れ"
 likeOnly: "いいねのみ"
-likeOnlyForRemote: "リモートからはいいねのみ"
+likeOnlyForRemote: "全て (リモートはいいねのみ)"
+nonSensitiveOnly: "非センシティブのみ"
+nonSensitiveOnlyForLocalLikeOnlyForRemote: "非センシティブのみ (リモートはいいねのみ)"
 rolesAssignedToMe: "自分に割り当てられたロール"
 resetPasswordConfirm: "パスワードリセットしますか?"
 sensitiveWords: "センシティブワード"
@@ -1045,6 +1051,17 @@ preventAiLearning: "生成AIによる学習を拒否"
 preventAiLearningDescription: "外部の文章生成AIや画像生成AIに対して、投稿したノートや画像などのコンテンツを学習の対象にしないように要求します。これはnoaiフラグをHTMLレスポンスに含めることによって実現されますが、この要求に従うかはそのAI次第であるため、学習を完全に防止するものではありません。"
 options: "オプション"
 specifyUser: "ユーザー指定"
+failedToPreviewUrl: "プレビューできません"
+update: "更新"
+rolesThatCanBeUsedThisEmojiAsReaction: "リアクションとして使えるロール"
+rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "ロールの指定が一つもない場合、誰でもリアクションとして使えます。"
+rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "ロールは公開ロールである必要があります。"
+cancelReactionConfirm: "リアクションを取り消しますか?"
+changeReactionConfirm: "リアクションを変更しますか?"
+later: "あとで"
+goToMisskey: "Misskeyへ"
+additionalEmojiDictionary: "絵文字の追加辞書"
+installed: "インストール済み"
 
 _initialAccountSetting:
   accountCreated: "アカウントの作成が完了しました!"
@@ -1060,6 +1077,7 @@ _initialAccountSetting:
   haveFun: "{name}をお楽しみください!"
   ifYouNeedLearnMore: "{name}(Misskey)の使い方などを詳しく知るには{link}をご覧ください。"
   skipAreYouSure: "初期設定をスキップしますか?"
+  laterAreYouSure: "初期設定をあとでやり直しますか?"
 
 _serverRules:
   description: "新規登録前に表示する、サーバーの簡潔なルールを設定します。内容は利用規約の要約とすることを推奨します。"
diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml
index d09f75155..652814ca9 100644
--- a/locales/ja-KS.yml
+++ b/locales/ja-KS.yml
@@ -8,7 +8,7 @@ search: "探す"
 notifications: "通知"
 username: "ユーザー名"
 password: "パスワード"
-forgotPassword: "パスワード忘れてもうた"
+forgotPassword: "パスワード忘れたん?"
 fetchingAsApObject: "今ちと連合に照会しとるで"
 ok: "ええで"
 gotIt: "ほい"
@@ -47,11 +47,13 @@ copyContent: "内容をコピー"
 copyLink: "リンクをコピー"
 delete: "ほかす"
 deleteAndEdit: "ほかして直す"
-deleteAndEditConfirm: "このノートをほかしてもっかい直す?このノートへのリアクション、Renote、返信も全部消えるんやけどそれでもええん?"
+deleteAndEditConfirm: "このノートをほかしてもっかい直す?このノートへのツッコミ、Renote、返信も全部消えるんやけどそれでもええん?"
 addToList: "リストに入れたる"
 sendMessage: "メッセージを送る"
 copyRSS: "RSSをコピー"
 copyUsername: "ユーザー名をコピー"
+copyUserId: "ユーザーIDをコピー"
+copyNoteId: "ノートIDをコピー"
 searchUser: "ユーザーを検索"
 reply: "返事"
 loadMore: "まだまだあるで!"
@@ -1043,6 +1045,10 @@ preventAiLearning: "生成AIの学習に使わんといて"
 preventAiLearningDescription: "他の文章生成AIとか画像生成AIに、投稿したノートとか画像なんかを勝手に使わんように頼むで。具体的にはnoaiフラグをHTMLレスポンスに含めるんやけど、これ聞いてくれるんはAIの気分次第やから、使われる可能性もちょっとはあるな。"
 options: "オプション"
 specifyUser: "ユーザー指定"
+rolesThatCanBeUsedThisEmojiAsReaction: "ツッコミとして使えるロール"
+rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "ロールが一個も指定されてへんかったら、誰でもツッコミとして使えるで。"
+cancelReactionConfirm: "ツッコむんをやっぱやめるか?"
+changeReactionConfirm: "ツッコミを別のに変えるか?"
 _initialAccountSetting:
   accountCreated: "アカウント作り終わったで。"
   letsStartAccountSetup: "アカウントの初期設定をしよか。"
@@ -1614,7 +1620,7 @@ _timelineTutorial:
   step2_2: "最初のノートは、自己紹介とか「{name}始めてみたんや」とかがええと思うで。"
   step3_1: "投稿できた?"
   step3_2: "あんたのノートがタイムラインに出てきたら成功や。"
-  step4_1: "ノートには、「リアクション」を付けれるで。"
+  step4_1: "ノートには、「ツッコミ」を付けれるで。"
   step4_2: "ツッコむんやったら、ノートの「+」マークを押して、好きな絵文字を選ぶで。"
 _2fa:
   alreadyRegistered: "もう設定終わっとるわ。"
diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml
index 39574d332..fd46eef1f 100644
--- a/locales/ko-KR.yml
+++ b/locales/ko-KR.yml
@@ -52,6 +52,8 @@ addToList: "리스트에 추가"
 sendMessage: "메시지 보내기"
 copyRSS: "RSS 복사"
 copyUsername: "유저명 복사"
+copyUserId: "유저 ID 복사"
+copyNoteId: "노트 ID 복사"
 searchUser: "사용자 검색"
 reply: "답글"
 loadMore: "더 보기"
@@ -505,7 +507,7 @@ objectStoragePrefixDesc: "이 Prefix 의 디렉토리 아래에 파일이 저장
 objectStorageEndpoint: "Endpoint"
 objectStorageEndpointDesc: "AWS S3의 경우 공란, 다른 서비스의 경우 각 서비스의 가이드에 맞게 endpoint를 설정해주세요. '<host>' 혹은 '<host>:<port>' 와 같이 지정합니다."
 objectStorageRegion: "Region"
-objectStorageRegionDesc: "'xx-east-1'와 같이 region을 지정해주세요. 사용하는 서비스에 region 개념이 없는 경우, 비워 두거나 'us-east-1'으로 설정해 주세요."
+objectStorageRegionDesc: "'xx-east-1'와 같이 region을 지정해 주세요. 사용하는 서비스에 region 개념이 없는 경우 'us-east-1'으로 설정해 주세요. AWS 설정 파일 또는 환경 변수를 참조할 경우에는 비워주세요."
 objectStorageUseSSL: "SSL 사용"
 objectStorageUseSSLDesc: "API 호출시 HTTPS 를 사용하지 않는 경우 OFF 로 설정해 주세요"
 objectStorageUseProxy: "연결에 프록시를 사용"
@@ -790,6 +792,7 @@ noMaintainerInformationWarning: "관리자 정보가 설정되어 있지 않습
 noBotProtectionWarning: "Bot 방어가 설정되어 있지 않습니다."
 configure: "설정하기"
 postToGallery: "갤러리에 업로드"
+postToHashtag: "이 해시태그에 게시"
 gallery: "갤러리"
 recentPosts: "최근 포스트"
 popularPosts: "인기 포스트"
@@ -823,6 +826,7 @@ translatedFrom: "{x}에서 번역"
 accountDeletionInProgress: "계정 삭제 작업을 진행하고 있습니다"
 usernameInfo: "서버상에서 계정을 식별하기 위한 이름. 알파벳(a~z, A~Z), 숫자(0~9) 및 언더바(_)를 사용할 수 있습니다. 사용자명은 나중에 변경할 수 없습니다."
 aiChanMode: "아이 모드"
+devMode: "개발자 모드"
 keepCw: "CW 유지하기"
 pubSub: "Pub/Sub 계정"
 lastCommunication: "마지막 통신"
@@ -830,8 +834,10 @@ resolved: "해결됨"
 unresolved: "해결되지 않음"
 breakFollow: "팔로워 해제"
 breakFollowConfirm: "팔로우를 해제하시겠습니까?"
-itsOn: "켜짐"
-itsOff: "꺼짐"
+itsOn: "켜져 있습니다"
+itsOff: "꺼져 있습니다"
+on: "켜짐"
+off: "꺼짐"
 emailRequiredForSignup: "가입할 때 이메일 주소 입력을 필수로 하기"
 unread: "읽지 않음"
 filter: "필터"
@@ -864,7 +870,7 @@ instanceDefaultLightTheme: "서버 기본 라이트 테마"
 instanceDefaultDarkTheme: "서버 기본 다크 테마"
 instanceDefaultThemeDescription: "객체 형식의 테마 코드를 입력해 주세요."
 mutePeriod: "뮤트할 기간"
-period: "투표 기한"
+period: "기간"
 indefinitely: "무기한"
 tenMinutes: "10분"
 oneHour: "1시간"
@@ -986,10 +992,13 @@ cannotBeChangedLater: "나중에 변경할 수 없습니다."
 reactionAcceptance: "리액션 수신"
 likeOnly: "좋아요만 받기"
 likeOnlyForRemote: "리모트에서는 좋아요만 받기"
+nonSensitiveOnly: "열람 주의로 설정되지 않았을 때만 받기"
+nonSensitiveOnlyForLocalLikeOnlyForRemote: "열람 주의로 설정되지 않았을 때만 받기 (리모트에서는 좋아요만 받기)"
 rolesAssignedToMe: "나에게 할당된 역할"
 resetPasswordConfirm: "비밀번호를 재설정하시겠습니까?"
 sensitiveWords: "민감한 단어"
 sensitiveWordsDescription: "설정한 단어가 포함된 노트의 공개 범위를 '홈'으로 강제합니다. 개행으로 구분하여 여러 개를 지정할 수 있습니다."
+sensitiveWordsDescription2: "공백으로 구분하면 AND 지정이 되며, 키워드를 슬래시로 둘러싸면 정규 표현식이 됩니다."
 notesSearchNotAvailable: "노트 검색을 이용하실 수 없습니다."
 license: "라이선스"
 unfavoriteConfirm: "즐겨찾기를 해제하시겠습니까?"
@@ -1038,31 +1047,47 @@ thisChannelArchived: "이 채널은 아카이브되었습니다."
 displayOfNote: "노트 표시"
 initialAccountSetting: "초기 설정"
 youFollowing: "팔로잉"
+preventAiLearning: "기계학습(생성형 AI)으로의 사용을 거부"
+preventAiLearningDescription: "외부의 문장 생성 AI나 이미지 생성 AI에 대해 제출한 노트나 이미지 등의 콘텐츠를 학습의 대상으로 사용하지 않도록 요구합니다. 다만, 이 요구사항을 지킬 의무는 없기 때문에 학습을 완전히 방지하는 것은 아닙니다."
 options: "옵션"
+specifyUser: "사용자 지정"
+failedToPreviewUrl: "미리 볼 수 없음"
+update: "업데이트"
+rolesThatCanBeUsedThisEmojiAsReaction: "이 이모지를 리액션으로 사용할 수 있는 역할"
+rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "역할을 지정하지 않으면, 누구나 이 이모지를 리액션으로 사용할 수 있습니다."
+rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "역할은 공개로 설정되어 있어야 합니다."
+cancelReactionConfirm: "리액션을 취소하시겠습니까?"
+changeReactionConfirm: "리액션을 변경하시겠습니까?"
+later: "나중에"
+goToMisskey: "Misskey로"
+additionalEmojiDictionary: "이모지 추가 사전"
+installed: "설치됨"
 _initialAccountSetting:
   accountCreated: "계정 생성이 완료되었습니다!"
   letsStartAccountSetup: "계정의 초기 설정을 진행합니다."
   letsFillYourProfile: "우선 나의 프로필을 설정해 보아요."
   profileSetting: "프로필 설정"
+  privacySetting: "프라이버시 설정"
   theseSettingsCanEditLater: "이 설정들은 나중에도 변경할 수 있습니다."
-  youCanEditMoreSettingsInSettingsPageLater: "이 외에도 '설정' 페이지에서 다양한 설정을 나의 입맛에 맛게 조절할 수 있습니다. 꼭 확인해 보세요!"
+  youCanEditMoreSettingsInSettingsPageLater: "이 외에도 '설정' 페이지에서 다양한 설정을 나의 입맛에 맞게 조절할 수 있습니다. 꼭 확인해 보세요!"
   followUsers: "관심사가 맞는 유저를 팔로우하여 타임라인을 가꾸어 봅시다."
   pushNotificationDescription: "푸시 알림을 활성화하면 {name}의 알림을 나의 기기에서 받아볼 수 있게 됩니다."
   initialAccountSettingCompleted: "초기 설정을 모두 마쳤습니다!"
   haveFun: "{name}와 함께 즐거운 시간 보내세요!"
   ifYouNeedLearnMore: "{name}(Misskey)의 사용 방법에 대해 자세히 알아보려면 {link}를 참고해 주세요."
-  skipAreYouSure: "초기 설정을 넘기시겠습니까?"
+  skipAreYouSure: "초기 설정을 중단하시겠습니까?"
+  laterAreYouSure: "초기 설정을 나중에 진행하시겠습니까?"
 _serverRules:
   description: "회원 가입 이전에 간단하게 표시할 서버 규칙입니다. 이용 약관의 요약으로 구성하는 것을 추천합니다."
 _accountMigration:
   moveFrom: "다른 계정에서 이 계정으로 이사"
   moveFromSub: "다른 계정에 대한 별칭을 생성"
-  moveFromLabel: "기존 계정:"
+  moveFromLabel: "기존 계정 #{n}"
   moveFromDescription: "다른 계정에서 이 계정으로 팔로워를 가져오려면, 우선 여기에서 별칭을 지정해야 합니다. 반드시 이사하기 전에 지정해야 합니다! 기존 계정을 다음과 같은 형식으로 입력해 주십시오: @person@instance.com"
   moveTo: "이 계정에서 다른 계정으로 이사"
   moveToLabel: "이사할 계정:"
   moveCannotBeUndone: "한 번 이사하면, 두 번 다시 되돌릴 수 없습니다."
-  moveAccountDescription: "이 작업은 취소할 수 없습니다. 먼저 이사할 계정에서 이 계정에 대한 별칭을 지정하였는지 다시 한 번 확인해 주십시오. 별칭을 지정한 다음, 이사할 계정을 다음과 같은 형식으로 입력해 주십시오: @person@instance.com"
+  moveAccountDescription: "새 계정으로 이전합니다.\n ・팔로워가 새 계정을 자동으로 팔로우 합니다\n ・이 계정에서 팔로우는 모두 해제됩니다\n ・이 계정으로는 노트 작성 등을 할 수 없게 됩니다\n\n팔로워는 자동으로 이전되지만, 팔로우는 수동으로 진행해야 합니다. 이전하기 전에 이 계정에서 팔로우를 내보내고, 이전 후에는 즉시 이전한 계정에서 가져오기를 진행하십시오.\n리스트・뮤트・차단에 대해서도 마찬가지이므로 수동으로 이전해야 합니다.\n\n(이 설명은 이 서버(Misskey v13.12.0 이후)의 사양입니다. Mastodon 등의 다른 ActivityPub 소프트웨어에서는 작동이 다를 수 있습니다.)"
   moveAccountHowTo: "계정을 이사하려면 우선 이사갈 계정에서 이 계정에 대한 별칭을 지정해야 합니다.\n별칭을 작성한 다음, 이사갈 계정을 다음과 같이 입력하십시오:\n@username@server.example.com"
   startMigration: "이사하기"
   migrationConfirm: "정말로 이 계정을 {account} 으로 이전하시겠습니까? 한 번 이전한 다음에는 취소할 수 없으며, 두 번 다시 원래 상태로 복구할 수 없습니다.\n이사할 계정에서 계정 별칭을 지정하였는지 다시 한 번 확인하십시오."
@@ -1491,7 +1516,7 @@ _menuDisplay:
   hide: "숨기기"
 _wordMute:
   muteWords: "뮤트할 단어"
-  muteWordsDescription: "공백으로 구분하는 경우 AND, 줄바꿈으로 구분하는 경우 OR로 지정됩니다。"
+  muteWordsDescription: "공백으로 구분하는 경우 AND, 줄바꿈으로 구분하는 경우 OR로 지정됩니다."
   muteWordsDescription2: "정규 표현식을 사용하려면 키워드를 빗금표(/)로 감싸 주세요."
   softDescription: "지정한 조건의 노트를 타임라인에서 숨깁니다."
   hardDescription: "지정한 조건의 노트를 타임라인에 추가하지 않습니다. 타임라인에 추가되지 않은 노트는 조건을 변경해도 표시되지 않습니다."
diff --git a/locales/no-NO.yml b/locales/no-NO.yml
index 36c29295f..ec2900527 100644
--- a/locales/no-NO.yml
+++ b/locales/no-NO.yml
@@ -1,21 +1,31 @@
 ---
 _lang_: "Norsk Bokmål"
+headlineMisskey: "Et nettverk forbundet med Notes"
+introMisskey: "Velkommen! Misskey er en desentralisert mikrobloggtjeneste med åpen kildekode.\nOpprett \"Notes\" for å dele tankene dine med alle rundt deg. 📡\nMed \"reaksjoner\" kan du også raskt gi uttrykk for hva du synes om alles Notes. 👍\nLa oss utforske en ny verden! 🚀"
+monthAndDay: "{day}-{month}"
 search: "Søk"
 notifications: "Varsler"
 username: "Brukernavn"
 password: "Passord"
 forgotPassword: "Glemt passord"
+fetchingAsApObject: "Henter fra Fediverse..."
 ok: "OK"
 gotIt: "Skjønner"
 cancel: "Avbryt"
-noThankYou: "Avbryt"
+noThankYou: "Ikke nå"
+enterUsername: "Skriv inn brukernavn"
+renotedBy: "Renotes av {user}"
+noNotes: "Ingen Notes"
 noNotifications: "Ingen varsler"
 instance: "Server"
 settings: "Innstillinger"
 notificationSettings: "Varslingsinnstillinger"
+basicSettings: "Grunnleggende innstillinger"
 otherSettings: "Andre innstillinger"
+openInWindow: "Åpne i vindu"
 profile: "Profil"
 timeline: "Tidslinje"
+noAccountDescription: "Denne brukeren har ikke skrevet sin biografi ennå."
 login: "Logg inn"
 loggingIn: "Logget inn"
 logout: "Logg ut"
@@ -24,17 +34,21 @@ uploading: "Laster opp"
 save: "Lagre"
 users: "Brukere"
 addUser: "Legg til bruker"
-favorite: "Favoritt"
+favorite: "Legg til i favoritter"
 favorites: "Favoritter"
-unfavorite: "Fjern favoritt"
-pin: "Fest"
-unpin: "Opphev festing"
+unfavorite: "Fjern fra favoritter"
+favorited: "Lagt til i favoritter."
+alreadyFavorited: "Allerede lagt til i favoritter."
+cantFavorite: "Kunne ikke legge til i favoritter."
+pin: "Fest til profil"
+unpin: "Fjern fra profil"
 copyContent: "Kopier innhold"
 copyLink: "Kopier lenke"
 delete: "Slett"
 deleteAndEdit: "Slett og rediger"
+deleteAndEditConfirm: "Er du sikker på at du vil slette denne Noten og redigere den? Du vil miste alle reaksjoner, Renotes og svar på den."
 addToList: "Legg til i liste"
-sendMessage: "Send melding"
+sendMessage: "Send en melding"
 copyRSS: "Kopier RSS"
 copyUsername: "Kopier brukernavn"
 searchUser: "Søk brukere"
@@ -42,140 +56,290 @@ reply: "Svar"
 loadMore: "Vis mer"
 showMore: "Vis mer"
 showLess: "Lukk"
+youGotNewFollower: "fulgte deg"
+followRequestAccepted: "Følgeforespørsel akseptert"
+importAndExport: "Importer og eksporter"
+import: "Importer"
+export: "Eksporter"
 files: "Filer"
 download: "Nedlastinger"
+driveFileDeleteConfirm: "Er du sikker på at du vil slette \"{name}\"? Det vil også forsvinne fra alt innhold som bruker det."
+unfollowConfirm: "Er du sikker på at du vil slutte å følge {name}?"
+importRequested: "Du har bedt om import. Dette kan ta en stund."
 lists: "Lister"
 noLists: "Ingen lister"
-following: "Følg"
+note: "Note"
+notes: "Notes"
+following: "Følger"
 followers: "Følgere"
 followsYou: "Følger deg"
 createList: "Opprett liste"
 error: "Feil"
+somethingHappened: "En feil har oppstått"
 retry: "Prøv igjen"
 pageLoadError: "Kunne ikke hente side."
+serverIsDead: "Denne serveren svarer ikke. Vennligst vent en stund og prøv igjen."
+enterListName: "Skriv inn et navn på listen"
 privacy: "Personvern"
+defaultNoteVisibility: "Standard synlighet"
 follow: "Følg"
 followRequest: "Følgeforespørsel"
 followRequests: "Følgeforespørsel"
 unfollow: "Avfølg"
 followRequestPending: "Venter på godkjenning"
+enterEmoji: "Skriv inn en emoji"
+renote: "Renote"
+renoted: "Renotet."
+cantRenote: "Dette innlegget kan ikke renotes."
+cantReRenote: "En Renote kan ikke renotes."
 quote: "Sitat"
-pinned: "Fest"
+inChannelRenote: "Renote kun for kanal"
+inChannelQuote: "Sitat kun for kanal"
+pinnedNote: "Festet Note"
+pinned: "Fest til profil"
 you: "Du"
 clickToShow: "Klikk for å vise"
 add: "Legg til"
 reaction: "Reaksjon"
 reactions: "Reaksjoner"
+reactionSetting: "Reaksjoner som vises i reaksjonsvelgeren"
+reactionSettingDescription2: "Dra for å endre rekkefølgen, klikk for å slette, trykk \"+\" for å legge til."
+rememberNoteVisibility: "Husk innstillingene for synlighet av Notes"
+attachCancel: "Fjern vedlegg"
+enterFileName: "Skriv inn filnavn"
 mute: "Skjul"
 unmute: "Vis"
+renoteMute: "Skjul Renotes"
+renoteUnmute: "Vis Renotes"
 block: "Blokker"
 unblock: "Opphev blokkering"
-blockConfirm: "Blokker?"
-selectList: "Velg liste"
-selectChannel: "Velg kanal"
+suspend: "Suspender"
+blockConfirm: "Er du sikker på at du vil blokke denne kontoen?"
+unblockConfirm: "Er du sikker på at du vil oppheve blokkeringen av denne kontoen?"
+suspendConfirm: "Er du sikker på at du vil suspendere denne kontoen?"
+selectList: "Velg en liste"
+selectChannel: "Velg en kanal"
+selectAntenna: "Velg en antenne"
+selectWidget: "Velg en widget"
+editWidgets: "Rediger widgeter"
+editWidgetsExit: "Ferdig"
 emoji: "Emoji"
 emojis: "Emojier"
 addEmoji: "Legg til emoji"
+settingGuide: "Anbefalte innstillinger"
+flagAsBot: "Merk denne kontoen som en bot"
+flagAsBotDescription: "Aktiver dette alternativet hvis denne kontoen styres av et program. Hvis det er aktivert, vil det fungere som et flagg for andre utviklere for å forhindre endeløse interaksjonskjeder med andre roboter og justere Misskeys interne systemer til å behandle denne kontoen som en bot."
+flagAsCat: "Merk denne kontoen som en katt"
+flagAsCatDescription: "Aktiver dette alternativet for å merke denne kontoen som en katt."
+flagShowTimelineReplies: "Vis svar i tidslinje"
 addAccount: "Legg til konto"
-selectUser: "Velg bruker"
-instances: "Server"
+reloadAccountsList: "Last inn kontoliste på nytt"
+loginFailed: "Kunne ikke logge inn"
+general: "Generelt"
+searchWith: "Søk: {q}"
+youHaveNoLists: "Du har ingen lister"
+followConfirm: "Er du sikker på at du vil følge {name}?"
+host: "Vert"
+selectUser: "Velg en bruker"
+recipient: "Mottaker"
+annotation: "Kommentarer"
+federation: "Føderasjon"
+instances: "Servere"
 registeredAt: "Registrerte seg"
+latestRequestReceivedAt: "Siste forespørsel mottatt"
+latestStatus: "Siste status"
+charts: "Diagrammer"
 perHour: "Per time"
 perDay: "Per dag"
+stopActivityDelivery: "Slutt å sende aktiviteter"
 blockThisInstance: "Blokker denne serveren"
+operations: "Operasjoner"
+software: "Programvare"
 version: "Versjon"
+metadata: "Metadata"
+withNFiles: "{n} fil(er)"
+network: "Nettverk"
+instanceInfo: "Serverinformasjon"
 statistics: "Statistikk"
 clearQueue: "Tøm kø"
-clearQueueConfirmTitle: "Vil du tømme kø?"
+clearQueueConfirmTitle: "Er du sikker på at du vil tømme køen?"
 blockedInstances: "Blokkerte severe"
+blockedInstancesDescription: "Skriv opp vertsnavnene til serverne du vil blokkere, atskilt med linjeskift. Serverne i listen vil ikke lenger kunne kommunisere med denne serveren."
 muteAndBlock: "Skjul og blokker"
 mutedUsers: "Skjulte brukere"
 blockedUsers: "Blokkerte brukere"
+noUsers: "Det er ingen brukere"
 editProfile: "Rediger profil"
+noteDeleteConfirm: "Er du sikker på at du vil slette denne Noten?"
 pinLimitExceeded: "Du kan ikke feste flere."
-noCustomEmojis: "Ingen emoji"
+intro: "Installasjonen av Misskey er ferdig! Vennligst opprett en administratorkonto."
+done: "Ferdig"
+default: "Standard"
+defaultValueIs: "Standard: {value}"
+noCustomEmojis: "Det er ingen emoji"
+noJobs: "Det er ingen jobber"
 blocked: "Blokkert"
+suspended: "Suspendert"
 all: "Alle"
+notResponding: "Svarer ikke"
 changePassword: "Endre passord"
 security: "Sikkerhet"
+retypedNotMatch: "Inngangene stemmer ikke overens."
+currentPassword: "Nåværende passord"
 newPassword: "Nytt passord"
 newPasswordRetype: "Nytt passord (gjenta)"
+attachFile: "Legg ved filer"
 more: "Mer!"
+noSuchUser: "Bruker ikke funnet"
+announcements: "Kunngjøringer"
 remove: "Slett"
-removed: "Slettet"
+removed: "Vellykket slettet"
+removeAreYouSure: "Er du sikker på at du vil fjerne \"{x}\"?"
+deleteAreYouSure: "Er du sikker på at du vil slette \"{x}\"?"
 saved: "Lagret"
 upload: "Laste opp"
+keepOriginalUploading: "Behold originalbildet"
+fromUrl: "Fra URL"
+uploadFromUrl: "Last opp fra en URL"
+uploadFromUrlDescription: "URL til filen du vil laste opp"
 explore: "Utforsk"
 messageRead: "Lest"
-agree: "Jeg godtar"
+nUsersRead: "lest av {n}"
+agreeTo: "Jeg godtar {0}"
+agree: "Godta"
+agreeBelow: "Jeg godtar følgende"
+basicNotesBeforeCreateAccount: "Viktige merknader"
+termsOfService: "Vilkår for bruk"
 home: "Hjem"
+activity: "Aktivitet"
 images: "Bilder"
-image: "Bilder"
+image: "Bilde"
 birthday: "Bursdag"
 yearsOld: "{age} år gammel"
+theme: "Temaer"
 light: "Lys"
 dark: "Mørk"
+lightThemes: "Lyse temaer"
+darkThemes: "Mørke temaer"
+syncDeviceDarkMode: "Synkroniser mørkmodus med enhetens innstillinger"
 fileName: "Filnavn"
-selectFile: "Velg fil"
-selectFiles: "Velg fil"
-selectFolder: "Velg mappe"
-selectFolders: "Velg mappe"
+selectFile: "Velg en fil"
+selectFiles: "Velg filer"
+selectFolder: "Velg en mappe"
+selectFolders: "Velg mapper"
 renameFile: "Endre filnavn"
 folderName: "Mappenavn"
-createFolder: "Opprett mappe"
+createFolder: "Opprett en mappe"
 renameFolder: "Endre mappenavn"
-deleteFolder: "Slett mappe"
-addFile: "Legg til fil"
+deleteFolder: "Slett denne mappen"
+addFile: "Legg til en fil"
 emptyFolder: "Denne mappen er tom"
+unableToDelete: "Kan ikke slette"
+inputNewFileName: "Skriv inn et nytt filnavn"
+inputNewDescription: "Skriv inn ny bildetekst"
+inputNewFolderName: "Skriv inn et nytt mappenavn"
+circularReferenceFolder: "Målmappen er en undermappe til mappen du ønsker å flytte."
+hasChildFilesOrFolders: "Siden denne mappen ikke er tom, kan den ikke slettes."
 copyUrl: "Kopier URL"
 rename: "Endre navn"
-doNothing: "Gjør ingenting"
+avatar: "Avatar"
+banner: "Banner"
+doNothing: "Ignorer"
 accept: "Tillatt"
 reject: "Avslå"
 instanceName: "Servernavn"
-thisYear: "I år"
+instanceDescription: "Serverbeskrivelse"
+thisYear: "År"
+thisMonth: "Måned"
 today: "I dag"
+dayX: "{day}"
+monthX: "{month}"
+yearX: "{year}"
 pages: "Sider"
-pinnedUsers: "Festete brukrere"
-pinnedPages: "Festete sider"
+integration: "Integrasjon"
+enableLocalTimeline: "Aktiver lokal tidslinje"
+enableGlobalTimeline: "Aktiver global tidslinje"
+disablingTimelinesInfo: "Administratorer og Moderatorer vil alltid ha tilgang til alle tidslinjer, selv om de ikke er aktivert."
+registration: "Registrer"
+enableRegistration: "Aktiver registrering av nye brukere"
+invite: "Inviter"
+basicInfo: "Grunnleggende informasjon"
+pinnedUsers: "Festede brukrere"
+pinnedUsersDescription: "Liste over brukernavn atskilt med linjeskift som skal festes i \"Utforsk\" fanen."
+pinnedPages: "Festede sider"
+pinnedNotes: "Festet Note"
 hcaptcha: "hCaptcha"
+enableHcaptcha: "Aktiver hCaptcha"
 recaptcha: "reCAPTCHA"
+enableRecaptcha: "Aktiver reCAPTCHA"
+turnstile: "Turnstile"
+enableTurnstile: "Aktiver Turnstile"
+antennas: "Antenner"
 name: "Navn"
+antennaSource: "Antennekilde"
+notifyAntenna: "Varsle om nye Notes"
+withFileAntenna: "Bare Notes med filer"
+notesAndReplies: "Notes og svar"
 popularUsers: "Populære brukere"
 exploreUsersCount: "Det finnes {count} brukere"
+exploreFediverse: "Utforsk Fediverse"
 userList: "Lister"
-about: "Infomasjon"
+about: "Informasjon"
 aboutMisskey: "Om Misskey"
+newPasswordIs: "Det nye passordet er \"{password}\"."
 share: "Del"
+notFound: "Ikke funnet"
+markAsReadAllNotifications: "Merk alle varsler som lest"
+markAsReadAllUnreadNotes: "Merk alle Notes som lest"
 help: "Hjelp"
+inputMessageHere: "Skriv inn melding her"
 close: "Lukk"
+invites: "Inviter"
 members: "Medlemmer"
+title: "Tittel"
 text: "Tekst"
 next: "Neste"
 retype: "Gjenta"
+quoteAttached: "Sitat"
+noMessagesYet: "Ingen meldinger ennå"
+newMessageExists: "Det er nye meldinger"
+onlyOneFileCanBeAttached: "Du kan bare legge ved én fil i en melding"
+invitations: "Inviter"
 available: "Tilgjengelig"
 unavailable: "Utilgjengelig"
 tooShort: "For kort"
 tooLong: "For langt"
+weakPassword: "Svakt passord"
+normalPassword: "Gjennomsnittlig passord"
+strongPassword: "Sterkt passord"
+signinWith: "Logg inn med {x}"
+signinFailed: "Kunne ikke logge inn. Det oppgitte brukernavnet eller passordet er feil."
 or: "eller"
 language: "Språk"
 aboutX: "Om {x}"
-category: "Kategorier"
+category: "Kategori"
 createAccount: "Opprett konto"
+openImageInNewTab: "Åpne bilder i ny fane"
+clientSettings: "Klientinnstillinger"
+accountSettings: "Kontoinnstillinger"
 objectStorageRegion: "Region"
 objectStorageUseSSL: "Bruk SSL"
 objectStorageUseProxy: "Bruk Proxy"
 deleteAll: "Slett alt"
+newNoteRecived: "Det er nye Notes"
 listen: "Lytt"
 none: "Ingen"
+volume: "Volum"
 chooseEmoji: "Velg emoji"
 recentUsed: "Sist brukte"
 install: "Installer"
+uninstall: "Avinstaller"
 nothing: "Ingenting"
 deleteAllFiles: "Slett alle filer"
-deleteAllFilesConfirm: "Vil du slette alle filer?"
+deleteAllFilesConfirm: "Er du sikker på at du vil slette alle filer?"
+userSuspended: "Denne brukeren har blitt suspendert."
 accountDeleted: "Kontoen blir slettet"
-accountDeletedDescription: "Denne kontoen blir slettet"
+accountDeletedDescription: "Denne kontoen har blitt slettet."
 menu: "Meny"
 poll: "Avstemning"
 description: "Beskrivelse"
@@ -186,6 +350,7 @@ small: "Liten"
 notificationType: "Varseltype"
 edit: "Rediger"
 email: "E-post"
+smtpHost: "Vert"
 smtpUser: "Brukernavn"
 smtpPass: "Passord"
 userSaysSomething: "{name} sa noe"
@@ -201,16 +366,25 @@ reportAbuse: "Rappoter"
 send: "Send"
 openInNewTab: "Åpne i ny fane"
 waitingFor: "Venter på {x}"
+random: "Tilfeldig"
 system: "System"
+desktop: "Skrivebord"
+i18nInfo: "Misskey oversettes til flere språk av frivillige. Du kan hjelpe til på {link}."
 followingCount: "Følger"
 followersCount: "Følgere"
 yes: "Ja"
 no: "Nei"
+contact: "Kontakt"
+developer: "Utvikler"
+makeExplorable: "Gjør konto synlig i \"Utforsk\""
+makeExplorableDescription: "Hvis du slår av dette, vises ikke kontoen din i \"Utforsk\" delen."
 left: "Venstre"
+nNotes: "{n} Notes"
 saveAs: "Lagre som"
 value: "Verdi"
 deleteConfirm: "Vil du slette?"
 invalidValue: "Verdien er ugyldig."
+closeAccount: "Avslutt konto"
 emailNotification: "E-postvarsler"
 inChannelSearch: "Søk i kanal"
 clear: "Tøm"
@@ -224,16 +398,23 @@ accounts: "Kontoer"
 switch: "Bytt"
 gallery: "Galleri"
 ads: "Annonser"
+memo: "Notat"
 high: "Høy"
 low: "Lav"
-sent: "Send"
+sent: "Sendt"
+received: "Mottatt"
 learnMore: "Les mer"
+misskeyUpdated: "Misskey har blitt oppdatert!"
 translate: "Oversett"
+translatedFrom: "Oversatt fra {x}"
 unread: "Ulest"
 manageAccounts: "Administrer konto"
 classic: "Klassisk"
 muteThread: "Skjul denne tråden"
 unmuteThread: "Vis denne tråden"
+continueThread: "Vis fortsettelse av tråden"
+hide: "Skjul"
+smartphone: "Smarttelefon"
 tablet: "Nettbrett"
 auto: "Automatisk"
 size: "Størrelse"
@@ -249,10 +430,10 @@ check: "Sjekk"
 deleteAccount: "Slett konto"
 document: "Dokumenter"
 logoutConfirm: "Vil du logge ut?"
-pleaseSelect: "Vennligst velg"
+pleaseSelect: "Velg et alternativ"
 type: "Type"
 beta: "Beta"
-account: "Kontoer"
+account: "Konto"
 move: "Flytt"
 pushNotification: "Push-varsler"
 tools: "Verktøy"
@@ -268,6 +449,7 @@ role: "Rolle"
 color: "Farge"
 youCannotCreateAnymore: "Du kan ikke opprette flere."
 cannotPerformTemporary: "Midlertidig utilgjengelig"
+achievements: "Prestasjoner"
 thisPostMayBeAnnoyingCancel: "Avbryt"
 exploreOtherServers: "Utforsk andre severe"
 letsLookAtTimeline: "La oss se på tidslinje"
@@ -283,6 +465,26 @@ _initialAccountSetting:
   theseSettingsCanEditLater: "Du kan endre disse innstillingene senere."
 _achievements:
   _types:
+    _notes10:
+      title: "Noen Notes"
+    _notes100:
+      title: "Mange Notes"
+    _notes500:
+      title: "Dekket i Notes"
+    _notes1000:
+      title: "Et fjell av Notes"
+    _notes5000:
+      title: "Overfylte Notes"
+    _notes10000:
+      title: "Super Notes"
+    _notes20000:
+      title: "Trenger... mer... Notes..."
+    _notes30000:
+      title: "Notes Notes Notes!"
+    _notes40000:
+      title: "Note fabrikk"
+    _notes50000:
+      title: "Planet av Notes"
     _notes100000:
       flavor: "Du har jammen mye å si."
     _noteFavorited1:
@@ -311,11 +513,25 @@ _achievements:
     _justPlainLucky:
       title: "Rett og slett heldig"
     _setNameToSyuilo:
-      description: "Du har satt navnet ditt til \"syuilo\""
+      description: "Du satte navnet ditt til \"syuilo\""
+    _passedSinceAccountCreated1:
+      title: "Ett års jubileum"
+      description: "Det har gått ett år siden kontoen din ble opprettet"
+    _passedSinceAccountCreated2:
+      title: "To års jubileum"
+      description: "Det har gått to år siden kontoen din ble opprettet"
+    _passedSinceAccountCreated3:
+      title: "Tre års jubileum"
+      description: "Det har gått tre år siden kontoen din ble opprettet"
     _loggedInOnBirthday:
       title: "Gratulerer med dagen"
+      description: "Du logget inn på bursdagen din"
     _loggedInOnNewYearsDay:
       title: "Godt nytt år"
+      description: "Du logget inn på årets første dag"
+    _cookieClicked:
+      description: "Du klikket på kjeksen"
+      flavor: "Er du på riktig nettsted?"
     _brainDiver:
       title: "Brain Diver"
       flavor: "Misskey-Misskey La-Tu-Ma"
@@ -333,6 +549,9 @@ _ad:
 _gallery:
   like: "Liker!"
   unlike: "Liker ikke"
+_email:
+  _follow:
+    title: "fulgte deg"
 _preferencesBackups:
   saveNew: "Lagre som ny"
   cannotSave: "Kunne ikke lagre"
@@ -351,6 +570,8 @@ _channel:
   featured: "Populært"
   following: "Følger"
   nameAndDescription: "Navn og beskrivelse"
+_menuDisplay:
+  hide: "Skjul"
 _wordMute:
   soft: "Myk"
   hard: "Hard"
@@ -360,15 +581,17 @@ _theme:
   key: "Nøkkel"
   keys:
     link: "Lenke"
+    renote: "Renote"
 _sfx:
+  note: "Notes"
   notification: "Varsler"
 _ago:
   future: "Fremitid"
   justNow: "Akkurat nå"
-  secondsAgo: "{n} sekunder siden"
-  minutesAgo: "{n} minutter siden"
-  hoursAgo: "{n} timer siden"
-  daysAgo: "{n} dager siden"
+  secondsAgo: "{n}s siden"
+  minutesAgo: "{n}m siden"
+  hoursAgo: "{n}t siden"
+  daysAgo: "{n}d siden"
   weeksAgo: "{n} uker siden"
   monthsAgo: "{n} måneder siden"
   yearsAgo: "{n} år siden"
@@ -380,6 +603,7 @@ _time:
   day: "Dager"
 _timelineTutorial:
   title: "Hvordan bruke Misskey"
+  step2_2: "Hva med å skrive en selvpresentasjon, eller bare \"Hei {name}!\" hvis du ikke har lyst?"
 _2fa:
   renewTOTPCancel: "Avbryt"
 _weekday:
@@ -392,18 +616,22 @@ _weekday:
   saturday: "Lørdag"
 _widgets:
   profile: "Profil"
+  instanceInfo: "Serverinformasjon"
   notifications: "Varsler"
   timeline: "Tidslinje"
   calendar: "Kalender"
   trends: "Populært"
   clock: "Klokke"
+  activity: "Aktivitet"
   photos: "Bilder"
+  federation: "Føderasjon"
   button: "Knapp"
   aiscriptApp: "AiScript App"
   userList: "Brukerliste"
   _userList:
     chooseList: "Velg liste"
 _cw:
+  hide: "Skjul"
   show: "Vis mer"
 _poll:
   noOnlyOneChoice: "Trenger minst to valger."
@@ -424,6 +652,7 @@ _postForm:
 _profile:
   name: "Navn"
   username: "Brukernavn"
+  description: "Biografi"
   metadataContent: "Innhold"
 _exportOrImport:
   followingList: "Følg"
@@ -431,6 +660,7 @@ _exportOrImport:
   blockingList: "Blokker"
   userLists: "Lister"
 _charts:
+  federation: "Føderasjon"
   filesIncDec: "Forskjell på antall filer"
 _instanceCharts:
   users: "Forskjell på antall brukere"
@@ -442,14 +672,18 @@ _play:
   new: "Opprett Play"
   edit: "Rediger Play"
   featured: "Populært"
+  title: "Tittel"
   summary: "Beskrivelse"
 _pages:
+  invalidNameText: "Pass på at sidetittelen ikke er tom"
   like: "Liker"
   unlike: "Liker ikke"
   my: "Mine sider"
   featured: "Populært"
   contents: "Innhold"
+  title: "Tittel"
   url: "Side URL"
+  hideTitleWhenPinned: "Skjul sidetittel når festet til profil"
   fontSerif: "Serif"
   fontSansSerif: "Sans Serif"
   selectType: "Velg type"
@@ -459,13 +693,18 @@ _pages:
     image: "Bilde"
     button: "Knapp"
 _notification:
+  youWereFollowed: "fulgte deg"
+  unreadAntennaNote: "Antenne {name}"
+  achievementEarned: "Prestasjon låst opp"
   _types:
-    follow: "Følg"
+    follow: "Nye følgere"
     reply: "Svar"
-    quote: "Sitat"
-    reaction: "Reaksjon"
+    renote: "Renotes"
+    quote: "Sitater"
+    reaction: "Reaksjoner"
   _actions:
     reply: "Svar"
+    renote: "Renote"
 _deck:
   swapLeft: "Flytt til venstre"
   swapRight: "Flytt til høyre"
@@ -477,6 +716,7 @@ _deck:
   _columns:
     notifications: "Varsler"
     tl: "Tidslinje"
+    antenna: "Antenner"
     list: "Lister"
     channel: "Kanaler"
     direct: "Direkte"
diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml
index a123ad726..e92449fdb 100644
--- a/locales/ru-RU.yml
+++ b/locales/ru-RU.yml
@@ -2,7 +2,7 @@
 _lang_: "Русский"
 headlineMisskey: "Сеть, сплетённая из заметок"
 introMisskey: "Добро пожаловать! Misskey — это децентрализованный сервис микроблогов с открытым исходным кодом.\nПишите «заметки» — делитесь со всеми происходящим вокруг или рассказывайте о себе 📡\nСтавьте «реакции» — выражайте свои чувства и эмоции от заметок других 👍\nОткройте для себя новый мир 🚀"
-poweredByMisskeyDescription: "{name} – один из инстансов (также называемый экземпляром Misskey), использующий платформу с открытым исходным кодом <b>Misskey</b>."
+poweredByMisskeyDescription: "{name} – сервис на платформе с открытым исходным кодом <b>Misskey</b>, называемый инстансом Misskey."
 monthAndDay: "{day}.{month}"
 search: "Поиск"
 notifications: "Уведомления"
@@ -560,6 +560,7 @@ accountDeletedDescription: "Эта учетная запись удалена"
 menu: "Меню"
 divider: "Линия-разделитель"
 addItem: "Добавить элемент"
+rearrange: "Сортировать по"
 relays: "Ретрансляторы"
 addRelay: "Добавить ретранслятор"
 inboxUrl: "URL ящика входящих сообщений"
@@ -648,8 +649,8 @@ abuseReported: "Жалоба отправлена. Большое спасибо
 reporter: "Сообщивший"
 reporteeOrigin: "О ком сообщено"
 reporterOrigin: "Кто сообщил"
-forwardReport: "Перенаправление отчета на инстант."
-forwardReportIsAnonymous: "Удаленный инстант не сможет увидеть вашу информацию и будет отображаться как анонимная системная учетная запись."
+forwardReport: "Отправить жалобу на инстанс автора."
+forwardReportIsAnonymous: "Жалоба на удалённый инстанс будет отправлена анонимно. Вместо ваших данных у получателя будет отображена системная учётная запись."
 send: "Отправить"
 abuseMarkAsResolved: "Отметить жалобу как решённую"
 openInNewTab: "Открыть в новой вкладке"
@@ -822,6 +823,7 @@ translatedFrom: "Перевод. Язык оригинала — {x}"
 accountDeletionInProgress: "В настоящее время выполняется удаление учетной записи"
 usernameInfo: "Имя, которое отличает вашу учетную запись от других на этом сервере. Вы можете использовать алфавит (a~z, A~Z), цифры (0~9) или символы подчеркивания (_). Имена пользователей не могут быть изменены позже."
 aiChanMode: "Режим Ай"
+devMode: "Режим разработчика"
 keepCw: "Сохраняйте Предупреждения о содержимом"
 pubSub: "Учётные записи Pub/Sub"
 lastCommunication: "Последнее сообщение"
@@ -913,8 +915,8 @@ cannotUploadBecauseInappropriate: "Файл не может быть загру
 cannotUploadBecauseNoFreeSpace: "Файл не может быть загружен, так как не осталось места на диске"
 cannotUploadBecauseExceedsFileSizeLimit: "Файл не может быть загружен, так как он превышает лимит размера файла."
 beta: "Бета"
-enableAutoSensitive: "Автоматическое определение NSFW"
-enableAutoSensitiveDescription: "Если доступно, используйте машинное обучение для автоматической установки флага NSFW на носителе. Даже если эта функция отключена, она может быть установлена ​​автоматически в зависимости от инстанта."
+enableAutoSensitive: "Автоматическое определение содержимого не для всех"
+enableAutoSensitiveDescription: "Позволяет определять наличие содержимого не для всех при помощи искусственного интеллекта там, где это возможно. Даже если эту опцию отключить, она всё равно может быть включена на весь инстанс."
 activeEmailValidationDescription: "Если включено, будет проводиться более строгая проверка адреса электронной почты, в том числе на то, что он действительный и не временный. Если же отключено, то проверяется только корректность написания адреса."
 navbar: "Панель навигации"
 shuffle: "Перемешать"
@@ -989,6 +991,7 @@ rolesAssignedToMe: "Мои роли"
 resetPasswordConfirm: "Сбросить пароль?"
 sensitiveWords: "Чувствительные слова"
 sensitiveWordsDescription: "Установите общедоступный диапазон заметки, содержащей заданное слово, на домашний. Можно сделать несколько настроек, разделив их переносами строк."
+sensitiveWordsDescription2: "Разделение пробелом создаёт спецификацию AND, а разделение косой чертой создаёт регулярное выражение."
 notesSearchNotAvailable: "Поиск заметок недоступен"
 license: "Лицензия"
 unfavoriteConfirm: "Удалить избранное?"
@@ -1004,6 +1007,7 @@ noteIdOrUrl: "ID или ссылка на заметку"
 video: "Видео"
 videos: "Видео"
 dataSaver: "Экономия трафика"
+renotesList: "Репосты"
 horizontal: "Сбоку"
 youFollowing: "Подписки"
 options: "Настройки ролей"
@@ -1178,6 +1182,9 @@ _achievements:
     _client30min:
       title: "Перерыв на обед"
       description: "Прошло 30 минут с момента запуска клиента"
+    _client60min:
+      title: "Не наглядеться на Misskey"
+      description: "Misskey был открыт 60 минут подряд"
     _noteDeletedWithin1min:
       title: "Ой, нет!"
       description: "Заметка удалена через минуту после публикации"
@@ -1280,6 +1287,7 @@ _role:
     canInvite: "Может создавать пригласительные коды"
     canManageCustomEmojis: "Управлять пользовательскими эмодзи"
     driveCapacity: "Доступное пространство на «диске»"
+    alwaysMarkNsfw: "Всегда отмечать файлы как «не для всех»"
     pinMax: "Доступное количество закреплённых заметок"
     antennaMax: "Доступное количество антенн"
     wordMuteMax: "Доступное количество знаков в списке скрытия слов"
@@ -1307,7 +1315,7 @@ _sensitiveMediaDetection:
   description: "Машинное обучение может быть использовано для автоматического обнаружения чувствительных медиа для модерации. Нагрузка на сервер увеличивается незначительно."
   sensitivity: "Чувствительность обнаружения"
   sensitivityDescription: "Более низкая чувствительность уменьшает количество ложных срабатываний (false positives). Повышение чувствительности уменьшает утечку при обнаружении (ложноотрицательные результаты)."
-  setSensitiveFlagAutomatically: "Установить флаг NSFW"
+  setSensitiveFlagAutomatically: "Обозначить как не для всех"
   setSensitiveFlagAutomaticallyDescription: "Даже если этот параметр отключен, результат оценки сохраняется внутри системы."
   analyzeVideos: "Анализировать видео?"
   analyzeVideosDescription: "Анализируйте видео в дополнение к неподвижным изображениям. Нагрузка на сервер немного увеличивается."
@@ -1526,6 +1534,16 @@ _time:
   minute: "мин"
   hour: "ч"
   day: "сут"
+_timelineTutorial:
+  title: "Как пользоваться Misskey"
+  step1_1: "Это лицо Misskey, так называемая лента. Ваш инстанс, {name}, покажет тут все опубликованные на нём заметки в хронологическом порядке."
+  step1_2: "Здесь есть несколько лент. К примеру «персональная» лента отображает заметки тех, на кого вы подписаны. А «местная» — заметки тех, кого приютил {name}."
+  step2_1: "Что ж, теперь самое время опубликовать заметку. Если нажать вверху страницы на изображение карандаша, появится форма для текста."
+  step2_2: "Почему бы не написать немного о себе? Ну, или хотя бы «Привет, {name}»?"
+  step3_1: "Справились с первой заметкой?"
+  step3_2: "Отлично, теперь она должна появиться в вашей ленте."
+  step4_1: "А ещё здесь можно делиться своими реакциями на заметки."
+  step4_2: "Отмечайте реакции, нажимая на символ «+» под заметкой и выбирая значок по душе."
 _2fa:
   alreadyRegistered: "Двухфакторная аутентификация уже настроена."
   registerTOTP: "Начните настраивать приложение-аутентификатор"
@@ -1866,6 +1884,9 @@ _deck:
 _dialog:
   charactersExceeded: "Превышено максимальное количество символов! У вас {current} / из   {max}"
   charactersBelow: "Это ниже минимального количества символов! У вас {current} / из {min}"
+_disabledTimeline:
+  title: "Лента отключена"
+  description: "Ваша текущая роль не позволяет пользоваться этой лентой."
 _webhookSettings:
   name: "Название"
   active: "Вкл."
diff --git a/locales/th-TH.yml b/locales/th-TH.yml
index 22f110eba..d8e68202d 100644
--- a/locales/th-TH.yml
+++ b/locales/th-TH.yml
@@ -560,6 +560,7 @@ accountDeletedDescription: "บัญชีนี้ถูกลบไปแล
 menu: "เมนู"
 divider: "ตัวแบ่ง"
 addItem: "เพิ่มรายการ"
+rearrange: "จัดใหม่"
 relays: "รีเลย์"
 addRelay: "เพิ่มรีเลย์"
 inboxUrl: "อินบ็อกซ์ URL"
@@ -1030,6 +1031,7 @@ continue: "ดำเนินการต่อ"
 preservedUsernames: "ชื่อผู้ใช้ที่สงวนไว้"
 preservedUsernamesDescription: "ลิสต์ชื่อผู้ใช้ที่จะสำรองโดยคั่นด้วยการแบ่งบรรทัดนั้น เพราะสิ่งเหล่านี้จะไม่สามารถทำได้ในระหว่างการสร้างบัญชีตามปกติ บัญชีที่มีอยู่แล้วนั้นโดยใช้ชื่อผู้ใช้เหล่านี้จะไม่ได้รับผลกระทบอะไร"
 createNoteFromTheFile: "เรียบเรียงโน้ตจากไฟล์นี้"
+archive: "เก็บถาวร"
 youFollowing: "ติดตามแล้ว"
 options: "ตัวเลือกบทบาท"
 _serverRules:
@@ -1329,6 +1331,7 @@ _role:
     canInvite: "สร้างรหัสเชิญอินสแตนซ์"
     canManageCustomEmojis: "จัดการอีโมจิแบบกำหนดเอง"
     driveCapacity: "ความจุของไดรฟ์"
+    alwaysMarkNsfw: "ทำเครื่องหมายไฟล์ว่าเป็น NSFW เสมอ"
     pinMax: "จํานวนสูงสุดของโน้ตที่ปักหมุดไว้"
     antennaMax: "จำนวนสูงสุดของเสาอากาศ"
     wordMuteMax: "จำนวนอักขระสูงสุดที่อนุญาตในการปิดเสียงคำ"
@@ -1580,6 +1583,12 @@ _time:
   minute: "นาที"
   hour: "ชั่วโมง"
   day: "วัน"
+_timelineTutorial:
+  title: "วิธีใช้งาน Misskey"
+  step3_1: "เสร็จสิ้นการโพสต์โน้ตย่อแรกของคุณแล้วอย่างงั้นหรอ?"
+  step3_2: "ไชโย! ตอนนี้โน้ตย่อแรกของคุณได้ปรากฏบนไทม์ไลน์ของคุณแล้วนะ"
+  step4_1: "คุณยังสามารถแนบ \"ปฏิกิริยา\" ไปกับโน้ตได้อีกด้วยนะค่ะ"
+  step4_2: "หากต้องการแนบการแสดงความรู้สึก ให้กดเครื่องหมาย \"+\" บนโน้ตแล้วเลือกอิโมจิที่คุณต้องการแสดงความรู้สึกที่ตนเองชอบได้เลย"
 _2fa:
   alreadyRegistered: "คุณได้ลงทะเบียนอุปกรณ์ยืนยันตัวตนแบบ 2 ชั้นแล้ว"
   registerTOTP: "ลงทะเบียนแอพตัวตรวจสอบสิทธิ์"
diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml
index 638cc1cf8..9c278ea75 100644
--- a/locales/zh-CN.yml
+++ b/locales/zh-CN.yml
@@ -52,6 +52,8 @@ addToList: "添加至列表"
 sendMessage: "发送"
 copyRSS: "复制RSS"
 copyUsername: "复制用户名"
+copyUserId: "复制用户ID"
+copyNoteId: "复制帖子ID"
 searchUser: "搜索用户"
 reply: "回复"
 loadMore: "查看更多"
@@ -560,6 +562,7 @@ accountDeletedDescription: "此帐户已经被删除。"
 menu: "菜单"
 divider: "分割线"
 addItem: "添加项目"
+rearrange: "排序方式"
 relays: "中继"
 addRelay: "添加中继"
 inboxUrl: "Inbox URL"
@@ -789,6 +792,7 @@ noMaintainerInformationWarning: "管理人员信息未设置。"
 noBotProtectionWarning: "Bot保护未设置。"
 configure: "设置"
 postToGallery: "发送到图库"
+postToHashtag: "投稿到这个标签"
 gallery: "图库"
 recentPosts: "最新发布"
 popularPosts: "热门投稿"
@@ -822,6 +826,7 @@ translatedFrom: "从 {x} 翻译"
 accountDeletionInProgress: "正在删除账户"
 usernameInfo: "在服务器上唯一标识您的帐户的名称。您可以使用字母 (a ~ z, A ~ Z)、数字 (0 ~ 9) 和下划线 (_)。用户名以后不能更改。"
 aiChanMode: "小蓝模式"
+devMode: "开发者模式"
 keepCw: "回复时维持隐藏内容"
 pubSub: "Pub/Sub账户"
 lastCommunication: "最近通信"
@@ -831,6 +836,8 @@ breakFollow: "移除关注者"
 breakFollowConfirm: "你想取消关注吗?"
 itsOn: "已开启"
 itsOff: "已关闭"
+on: "开启"
+off: "关闭"
 emailRequiredForSignup: "注册账户需要电子邮件地址"
 unread: "未读"
 filter: "筛选"
@@ -985,10 +992,13 @@ cannotBeChangedLater: "之后不能再更改。"
 reactionAcceptance: "接受表情回应"
 likeOnly: "仅点赞"
 likeOnlyForRemote: "远程仅点赞"
+nonSensitiveOnly: "仅限非敏感内容"
+nonSensitiveOnlyForLocalLikeOnlyForRemote: "仅限非敏感内容(远程仅点赞)"
 rolesAssignedToMe: "指派给自己的角色"
 resetPasswordConfirm: "确定重置密码?"
 sensitiveWords: "敏感词"
 sensitiveWordsDescription: "将包含设置词的帖子的可见范围设置为首页。可以通过用换行符分隔来设置多个。"
+sensitiveWordsDescription2: "用空格分割关键词作为AND格式,用斜线包裹关键字来构成正则表达式。"
 notesSearchNotAvailable: "帖子检索不可用"
 license: "许可信息"
 unfavoriteConfirm: "确定要取消收藏吗?"
@@ -1028,28 +1038,60 @@ pleaseConfirmBelowBeforeSignup: "在这个服务器上注册账号前,请确
 pleaseAgreeAllToContinue: "必须全部勾选「同意」才能够继续。"
 continue: "继续"
 preservedUsernames: "保留的用户名"
+preservedUsernamesDescription: "列出需要保留的用户名,使用换行来作为分割。被指定的用户名在建立账户时无法使用,但由管理员所创建的账户不受该限制。此外,现有的账户也不会受到影响。"
 createNoteFromTheFile: "从文件创建帖子"
+archive: "归档"
+channelArchiveConfirmTitle: "要将{name}归档吗?"
+channelArchiveConfirmDescription: "归档后,在频道列表与搜索结果中不会显示,也无法发布新的贴文。"
+thisChannelArchived: "该频道已被归档。"
+displayOfNote: "显示帖子"
+initialAccountSetting: "初始设置"
 youFollowing: "正在关注"
+preventAiLearning: "拒绝接受生成式AI的学习"
+preventAiLearningDescription: "要求文章生成AI或图像生成AI不能够以发布的帖子和图像等内容作为学习对象。这是通过在HTML响应中包含noai标志来实现的,这不能完全阻止AI学习你的发布内容,并不是所有AI都会遵守这类请求。"
 options: "选项"
+specifyUser: "用户指定"
+failedToPreviewUrl: "无法预览"
+update: "更新"
+rolesThatCanBeUsedThisEmojiAsReaction: "可以使用表情作为回应的角色"
+rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "在没有指定角色的情况下,任何人都可以使用表情作为回应。"
+rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "角色必须是公开的。"
+cancelReactionConfirm: "要取消回应吗?"
+changeReactionConfirm: "要更改回应吗?"
+later: "一会再说"
+goToMisskey: "去往Misskey"
+installed: "已安装"
 _initialAccountSetting:
   accountCreated: "账户创建完成了!"
   letsStartAccountSetup: "来进行帐户的初始设置吧。"
   letsFillYourProfile: "首先,来设定你的个人档案吧!"
   profileSetting: "个人资料设置"
+  privacySetting: "隐私设置"
   theseSettingsCanEditLater: "也可以在稍后修改这里的设置。"
+  youCanEditMoreSettingsInSettingsPageLater: "还可以在「设置」页面进行其它各种设置,稍后就来确认一下看看吧。"
+  followUsers: "为了建立属于你自己的时间线,试着去关注你感兴趣的用户吧。"
+  pushNotificationDescription: "启用推送通知的话,就可以在设备上受到来自{name}的通知了。"
+  initialAccountSettingCompleted: "初始设定已经完成了!"
+  haveFun: "希望{name}在这里玩得开心!"
+  ifYouNeedLearnMore: "关于{name}(Misskey)的使用方法,详见{link}。"
+  skipAreYouSure: "要跳过初始设置吗?"
+  laterAreYouSure: "要稍后再进行初始设定吗?"
 _serverRules:
   description: "在新用户注册前显示服务器的简单规则。推荐显示服务条款的主要内容。"
 _accountMigration:
   moveFrom: "从别的账号迁移到此账户"
+  moveFromSub: "为另一个账户建立别名"
   moveFromLabel: "迁移前的账户"
   moveFromDescription: "如果迁移时需要继承其他账户的关注者,请在此创造别名。此操作需要在实行迁移之前完成!请如已下输入需要迁移的账户:@person@instance.com"
   moveTo: "把这个账户迁移到新的账户"
   moveToLabel: "迁移后的账户"
   moveCannotBeUndone: "一旦迁移账户,就无法撤销。"
   moveAccountDescription: "此操作无法取消。请先确认您已在迁移后的账户上,为此账户创造了别名。创造别名后,请如以下输入您的迁移后的账户:@person@instance.com"
+  moveAccountHowTo: "要进行账户迁移,请现在目标账户中为此账户建立一个别名。\n建立别名后,请像这样输入目标账户:@username@server.example.com"
   startMigration: "迁移"
   migrationConfirm: "确定要把此账户迁移到{account}吗?一旦确定后,此操作无法取消,此账户也无法以原来的状态使用。\n同时,请确认迁移后的账户,已创造别名。"
   movedAndCannotBeUndone: "该账户已被迁移。\n迁移操作无法撤销。"
+  postMigrationNote: "这个账户的关注会在迁移操作后的24小时后解除。该账户的「关注中」和「关注者」皆会变为0。由于不会解除关注关系,你的关注者仍然可以继续查看该账户发补给关注者的帖子。"
   movedTo: "迁移后的账户"
 _achievements:
   earnedAt: "达成时间"
@@ -1331,6 +1373,7 @@ _role:
     canInvite: "发放服务器邀请码"
     canManageCustomEmojis: "管理自定义表情符号"
     driveCapacity: "网盘容量"
+    alwaysMarkNsfw: "总是将文件标记为NSFW"
     pinMax: "帖子置顶数量限制"
     antennaMax: "可创建的最大天线数量"
     wordMuteMax: "屏蔽词的字数限制"
@@ -1583,8 +1626,15 @@ _time:
   hour: "小时"
   day: "日"
 _timelineTutorial:
+  title: "Misskey的使用方法"
+  step1_1: "这个画面是「时间线」。{name}的投稿会按照帖子的发布时间顺序来显示。"
+  step1_2: "时间线有许多种类,比如在「首页时间线」中展现的是你关注的人的贴文;而在「本地时间线」中展现的是{name}里全部用户的贴文。"
+  step2_1: "那么接下来,试着写一些什么东西来发布吧!你可以通过点击屏幕上的铅笔图标来打开投稿页面。"
+  step2_2: "第一次发布的帖子内容,建议包含自我介绍,以及「开始使用{name}了」。"
   step3_1: "将想说的话发出去了吗?"
   step3_2: "太棒了!现在你可以在你的时间线中看到刚刚发布的帖子了。"
+  step4_1: "试着对帖子使用「回应」吧!"
+  step4_2: "在他人的帖子上按下「+」图标,即可选择想要的表情来进行「回应」。"
 _2fa:
   alreadyRegistered: "此设备已被注册"
   registerTOTP: "开始设置认证应用"
diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml
index d2b42313a..ef0baeef5 100644
--- a/locales/zh-TW.yml
+++ b/locales/zh-TW.yml
@@ -52,6 +52,8 @@ addToList: "加入至清單"
 sendMessage: "發送訊息"
 copyRSS: "複製RSS"
 copyUsername: "複製使用者名稱"
+copyUserId: "複製使用者ID"
+copyNoteId: "複製貼文ID"
 searchUser: "搜尋使用者"
 reply: "回覆"
 loadMore: "載入更多"
@@ -790,6 +792,7 @@ noMaintainerInformationWarning: "尚未設定管理員信息。"
 noBotProtectionWarning: "尚未設定Bot防護。"
 configure: "設定"
 postToGallery: "發佈到相簿"
+postToHashtag: "以此主題標籤發布"
 gallery: "相簿"
 recentPosts: "最新貼文"
 popularPosts: "熱門的貼文"
@@ -823,6 +826,7 @@ translatedFrom: "從 {x} 翻譯"
 accountDeletionInProgress: "正在刪除帳戶"
 usernameInfo: "在伺服器上您的帳戶是唯一的識別名稱。您可以使用字母 (a ~ z, A ~ Z)、數字 (0 ~ 9) 和下底線 (_)。之後帳戶名是不能更改的。"
 aiChanMode: "小藍模式"
+devMode: "開發者模式"
 keepCw: "保持CW"
 pubSub: "Pub/Sub 帳戶"
 lastCommunication: "最近的通信"
@@ -832,6 +836,8 @@ breakFollow: "解除追隨者"
 breakFollowConfirm: "確定要取消被追隨嗎?"
 itsOn: "已開啟"
 itsOff: "已關閉"
+on: "開啟"
+off: "關閉"
 emailRequiredForSignup: "註冊帳戶需要電子郵件地址"
 unread: "未讀"
 filter: "篩選"
@@ -986,6 +992,8 @@ cannotBeChangedLater: "之後不能變更。"
 reactionAcceptance: "接受表情反應"
 likeOnly: "僅限讚"
 likeOnlyForRemote: "遠端僅限讚"
+nonSensitiveOnly: "僅限非敏感"
+nonSensitiveOnlyForLocalLikeOnlyForRemote: "僅限非敏感(遠端僅限按讚)"
 rolesAssignedToMe: "指派給自己的角色"
 resetPasswordConfirm: "重設密碼?"
 sensitiveWords: "敏感詞"
@@ -1039,14 +1047,27 @@ thisChannelArchived: "這個頻道已被封存。"
 displayOfNote: "顯示貼文"
 initialAccountSetting: "初始設定"
 youFollowing: "追隨中"
-preventAiLearning: "拒絕接受產生式AI的學習"
-preventAiLearningDescription: "要求外部的文章產生AI或圖像產生AI不以發布的貼文和圖像等內容為學習對象。這是透過在HTML響應中包含noai旗標來實現的,但不能完全防止AI的學習,因為這要看該AI是否遵守這個要求。"
+preventAiLearning: "拒絕接受生成式AI的訓練"
+preventAiLearningDescription: "要求外部的文章生成式AI或圖像生成式AI不以發布的貼文和圖像等內容為學習對象。這是透過在HTML響應中包含noai旗標來實現的,但不能完全防止AI的學習,因為這要看該AI是否遵守這個要求。"
 options: "選項"
+specifyUser: "指定使用者"
+failedToPreviewUrl: "無法預覽"
+update: "更新"
+rolesThatCanBeUsedThisEmojiAsReaction: "可以當成反應使用的角色"
+rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "如果是未指定角色的情況,則任何人都可以被當成反應來使用。"
+rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "角色必須是公開的角色。"
+cancelReactionConfirm: "要取消做出的反應嗎?"
+changeReactionConfirm: "要變更做出的反應嗎?"
+later: "稍後再說"
+goToMisskey: "往Misskey"
+additionalEmojiDictionary: "表情符號的附加辭典"
+installed: "已安裝"
 _initialAccountSetting:
   accountCreated: "帳戶已建立完成!"
   letsStartAccountSetup: "來進行帳戶的初始設定吧。"
   letsFillYourProfile: "首先,來設定您的個人檔案吧。"
   profileSetting: "個人檔案設定"
+  privacySetting: "隱私設定"
   theseSettingsCanEditLater: "這裡的設定可以在之後變更。"
   youCanEditMoreSettingsInSettingsPageLater: "除此之外,還可以在「設定」頁面進行各種設定。之後請確認看看。"
   followUsers: "為了構築時間軸,試著追蹤您感興趣的使用者吧。"
@@ -1055,6 +1076,7 @@ _initialAccountSetting:
   haveFun: "盡情享受{name}吧!"
   ifYouNeedLearnMore: "關於如何使用{name}(Misskey)的詳細資訊,請見{link}。"
   skipAreYouSure: "要略過初始設定嗎?"
+  laterAreYouSure: "稍後再重新進行初始設定嗎?"
 _serverRules:
   description: "設定伺服器的簡要規則,在新的註冊之前顯示。建議的內容是使用條款的摘要。"
 _accountMigration:
diff --git a/package.json b/package.json
index a21d7ab9e..8cf7d37f6 100644
--- a/package.json
+++ b/package.json
@@ -1,12 +1,12 @@
 {
 	"name": "misskey",
-	"version": "13.12.2",
+	"version": "13.13.0",
 	"codename": "nasubi",
 	"repository": {
 		"type": "git",
 		"url": "https://github.com/misskey-dev/misskey.git"
 	},
-	"packageManager": "pnpm@8.3.1",
+	"packageManager": "pnpm@8.6.0",
 	"workspaces": [
 		"packages/frontend",
 		"packages/backend",
@@ -51,16 +51,16 @@
 		"gulp-replace": "1.1.4",
 		"gulp-terser": "2.1.0",
 		"js-yaml": "4.1.0",
-		"typescript": "5.0.4"
+		"typescript": "5.1.3"
 	},
 	"devDependencies": {
 		"@types/gulp": "4.0.10",
 		"@types/gulp-rename": "2.0.1",
-		"@typescript-eslint/eslint-plugin": "5.59.5",
-		"@typescript-eslint/parser": "5.59.5",
+		"@typescript-eslint/eslint-plugin": "5.59.8",
+		"@typescript-eslint/parser": "5.59.8",
 		"cross-env": "7.0.3",
-		"cypress": "12.12.0",
-		"eslint": "8.40.0",
+		"cypress": "12.13.0",
+		"eslint": "8.41.0",
 		"start-server-and-test": "2.0.0"
 	},
 	"optionalDependencies": {
diff --git a/packages/backend/migration/1683847157541-UserList.js b/packages/backend/migration/1683847157541-UserList.js
new file mode 100644
index 000000000..b50a50eed
--- /dev/null
+++ b/packages/backend/migration/1683847157541-UserList.js
@@ -0,0 +1,13 @@
+export class UserList1683847157541 {
+    name = 'UserList1683847157541'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "user_list" ADD "isPublic" boolean NOT NULL DEFAULT false`);
+        await queryRunner.query(`CREATE INDEX "IDX_48a00f08598662b9ca540521eb" ON "user_list" ("isPublic") `);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`DROP INDEX "public"."IDX_48a00f08598662b9ca540521eb"`);
+        await queryRunner.query(`ALTER TABLE "user_list" DROP COLUMN "isPublic"`);
+    }
+}
diff --git a/packages/backend/migration/1683869758873-UserListFavorites.js b/packages/backend/migration/1683869758873-UserListFavorites.js
new file mode 100644
index 000000000..ac9c4c42b
--- /dev/null
+++ b/packages/backend/migration/1683869758873-UserListFavorites.js
@@ -0,0 +1,19 @@
+export class UserListFavorites1683869758873 {
+    name = 'UserListFavorites1683869758873'
+
+    async up(queryRunner) {
+        await queryRunner.query(`CREATE TABLE "user_list_favorite" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "userListId" character varying(32) NOT NULL, CONSTRAINT "PK_c0974b21e18502a4c8178e09fe6" PRIMARY KEY ("id"))`);
+        await queryRunner.query(`CREATE INDEX "IDX_016f613dc4feb807e03e3e7da9" ON "user_list_favorite" ("userId") `);
+        await queryRunner.query(`CREATE UNIQUE INDEX "IDX_d6765a8c2a4c17c33f9d7f948b" ON "user_list_favorite" ("userId", "userListId") `);
+        await queryRunner.query(`ALTER TABLE "user_list_favorite" ADD CONSTRAINT "FK_016f613dc4feb807e03e3e7da92" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+        await queryRunner.query(`ALTER TABLE "user_list_favorite" ADD CONSTRAINT "FK_4d52b20bfe32c8552e7a61e80d2" FOREIGN KEY ("userListId") REFERENCES "user_list"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "user_list_favorite" DROP CONSTRAINT "FK_4d52b20bfe32c8552e7a61e80d2"`);
+        await queryRunner.query(`ALTER TABLE "user_list_favorite" DROP CONSTRAINT "FK_016f613dc4feb807e03e3e7da92"`);
+        await queryRunner.query(`DROP INDEX "public"."IDX_d6765a8c2a4c17c33f9d7f948b"`);
+        await queryRunner.query(`DROP INDEX "public"."IDX_016f613dc4feb807e03e3e7da9"`);
+        await queryRunner.query(`DROP TABLE "user_list_favorite"`);
+    }
+}
diff --git a/packages/backend/migration/1684206886988-remove-showTimelineReplies.js b/packages/backend/migration/1684206886988-remove-showTimelineReplies.js
new file mode 100644
index 000000000..690653bd7
--- /dev/null
+++ b/packages/backend/migration/1684206886988-remove-showTimelineReplies.js
@@ -0,0 +1,11 @@
+export class RemoveShowTimelineReplies1684206886988 {
+    name = 'RemoveShowTimelineReplies1684206886988'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "showTimelineReplies"`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "user" ADD "showTimelineReplies" boolean NOT NULL DEFAULT false`);
+    }
+}
diff --git a/packages/backend/migration/1684386446061-emoji-improve.js b/packages/backend/migration/1684386446061-emoji-improve.js
new file mode 100644
index 000000000..40b0a2bc5
--- /dev/null
+++ b/packages/backend/migration/1684386446061-emoji-improve.js
@@ -0,0 +1,15 @@
+export class EmojiImprove1684386446061 {
+    name = 'EmojiImprove1684386446061'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "emoji" ADD "localOnly" boolean NOT NULL DEFAULT false`);
+        await queryRunner.query(`ALTER TABLE "emoji" ADD "isSensitive" boolean NOT NULL DEFAULT false`);
+        await queryRunner.query(`ALTER TABLE "emoji" ADD "roleIdsThatCanBeUsedThisEmojiAsReaction" character varying(128) array NOT NULL DEFAULT '{}'`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "roleIdsThatCanBeUsedThisEmojiAsReaction"`);
+        await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "isSensitive"`);
+        await queryRunner.query(`ALTER TABLE "emoji" DROP COLUMN "localOnly"`);
+    }
+}
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 4bab4a734..56ecbc2ea 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -35,49 +35,52 @@
 		"@swc/core-win32-x64-msvc": "1.3.56",
 		"@tensorflow/tfjs": "4.4.0",
 		"@tensorflow/tfjs-node": "4.4.0",
-		"slacc-android-arm-eabi": "0.0.7",
-		"slacc-android-arm64": "0.0.7",
-		"slacc-darwin-arm64": "0.0.7",
-		"slacc-darwin-universal": "0.0.7",
-		"slacc-darwin-x64": "0.0.7",
-		"slacc-linux-arm-gnueabihf": "0.0.7",
-		"slacc-linux-arm64-gnu": "0.0.7",
-		"slacc-linux-arm64-musl": "0.0.7",
-		"slacc-linux-x64-gnu": "0.0.7",
-		"slacc-win32-arm64-msvc": "0.0.7",
-		"slacc-win32-x64-msvc": "0.0.7"
+		"bufferutil": "^4.0.7",
+		"slacc-android-arm-eabi": "0.0.9",
+		"slacc-android-arm64": "0.0.9",
+		"slacc-darwin-arm64": "0.0.9",
+		"slacc-darwin-universal": "0.0.9",
+		"slacc-darwin-x64": "0.0.9",
+		"slacc-freebsd-x64": "0.0.9",
+		"slacc-linux-arm-gnueabihf": "0.0.9",
+		"slacc-linux-arm64-gnu": "0.0.9",
+		"slacc-linux-arm64-musl": "0.0.9",
+		"slacc-linux-x64-gnu": "0.0.9",
+		"slacc-win32-arm64-msvc": "0.0.9",
+		"slacc-win32-x64-msvc": "0.0.9",
+		"utf-8-validate": "^6.0.3"
 	},
 	"dependencies": {
 		"@aws-sdk/client-s3": "3.321.1",
 		"@aws-sdk/lib-storage": "3.321.1",
 		"@aws-sdk/node-http-handler": "3.321.1",
-		"@bull-board/api": "5.1.2",
-		"@bull-board/fastify": "5.1.2",
-		"@bull-board/ui": "5.1.2",
+		"@bull-board/api": "5.2.0",
+		"@bull-board/fastify": "5.2.0",
+		"@bull-board/ui": "5.2.0",
 		"@discordapp/twemoji": "14.1.2",
 		"@fastify/accepts": "4.1.0",
 		"@fastify/cookie": "8.3.0",
-		"@fastify/cors": "8.2.1",
+		"@fastify/cors": "8.3.0",
 		"@fastify/http-proxy": "9.1.0",
 		"@fastify/multipart": "7.6.0",
-		"@fastify/static": "6.10.1",
+		"@fastify/static": "6.10.2",
 		"@fastify/view": "7.4.1",
-		"@nestjs/common": "9.4.0",
-		"@nestjs/core": "9.4.0",
-		"@nestjs/testing": "9.4.0",
+		"@nestjs/common": "9.4.2",
+		"@nestjs/core": "9.4.2",
+		"@nestjs/testing": "9.4.2",
 		"@peertube/http-signature": "1.7.0",
-		"@sinonjs/fake-timers": "10.0.2",
+		"@sinonjs/fake-timers": "10.2.0",
 		"@swc/cli": "0.1.62",
-		"@swc/core": "1.3.56",
+		"@swc/core": "1.3.61",
 		"accepts": "1.3.8",
 		"ajv": "8.12.0",
 		"archiver": "5.3.1",
 		"autwh": "0.1.0",
 		"bcryptjs": "2.4.3",
 		"blurhash": "2.0.5",
-		"bull": "4.10.4",
+		"bullmq": "3.15.0",
 		"cacheable-lookup": "6.1.0",
-		"cbor": "8.1.0",
+		"cbor": "9.0.0",
 		"chalk": "5.2.0",
 		"chalk-template": "0.4.0",
 		"chokidar": "3.5.3",
@@ -93,30 +96,30 @@
 		"fluent-ffmpeg": "2.1.2",
 		"form-data": "4.0.0",
 		"got": "12.6.0",
-		"happy-dom": "9.16.0",
+		"happy-dom": "9.20.3",
 		"hpagent": "1.2.0",
 		"ioredis": "5.3.2",
 		"ip-cidr": "3.1.0",
 		"is-svg": "4.3.2",
 		"js-yaml": "4.1.0",
-		"jsdom": "21.1.1",
+		"jsdom": "22.1.0",
 		"json5": "2.2.3",
-		"jsonld": "8.1.1",
-		"meilisearch": "0.32.3",
+		"jsonld": "8.2.0",
 		"jsrsasign": "10.8.6",
+		"meilisearch": "0.32.5",
 		"mfm-js": "0.23.3",
 		"mime-types": "2.1.35",
 		"misskey-js": "workspace:*",
 		"ms": "3.0.0-canary.1",
 		"nested-property": "4.0.0",
 		"node-fetch": "3.3.1",
-		"nodemailer": "6.9.2",
+		"nodemailer": "6.9.3",
 		"nsfwjs": "2.4.2",
 		"oauth": "0.10.0",
 		"os-utils": "0.0.14",
 		"otpauth": "9.1.2",
 		"parse5": "7.1.2",
-		"pg": "8.10.0",
+		"pg": "8.11.0",
 		"private-ip": "3.0.0",
 		"probe-image-size": "7.2.3",
 		"promise-limit": "2.7.0",
@@ -126,7 +129,7 @@
 		"qrcode": "1.5.3",
 		"random-seed": "0.3.0",
 		"ratelimiter": "3.4.1",
-		"re2": "1.18.0",
+		"re2": "1.19.0",
 		"redis-lock": "0.1.4",
 		"reflect-metadata": "0.1.13",
 		"rename": "1.0.4",
@@ -136,27 +139,26 @@
 		"s-age": "1.1.2",
 		"sanitize-html": "2.10.0",
 		"seedrandom": "3.0.5",
-		"semver": "7.5.0",
+		"semver": "7.5.1",
 		"sharp": "0.32.1",
 		"sharp-read-bmp": "github:misskey-dev/sharp-read-bmp",
-		"slacc": "0.0.7",
+		"slacc": "0.0.9",
 		"strict-event-emitter-types": "2.0.0",
 		"stringz": "2.1.0",
 		"summaly": "github:misskey-dev/summaly",
-		"systeminformation": "5.17.12",
+		"systeminformation": "5.17.16",
 		"tinycolor2": "1.6.0",
 		"tmp": "0.2.1",
 		"tsc-alias": "1.8.6",
 		"tsconfig-paths": "4.2.0",
 		"twemoji-parser": "14.0.0",
 		"typeorm": "0.3.16",
-		"typescript": "5.0.4",
+		"typescript": "5.1.3",
 		"ulid": "2.3.0",
-		"unzipper": "0.10.11",
+		"unzipper": "0.10.14",
 		"uuid": "9.0.0",
 		"vary": "1.1.2",
 		"web-push": "3.6.1",
-		"websocket": "1.0.34",
 		"ws": "8.13.0",
 		"xev": "3.0.2"
 	},
@@ -166,23 +168,22 @@
 		"@types/accepts": "1.3.5",
 		"@types/archiver": "5.3.2",
 		"@types/bcryptjs": "2.4.2",
-		"@types/bull": "4.10.0",
 		"@types/cbor": "6.0.0",
 		"@types/color-convert": "2.0.0",
 		"@types/content-disposition": "0.5.5",
 		"@types/escape-regexp": "0.0.1",
 		"@types/fluent-ffmpeg": "2.1.21",
-		"@types/jest": "29.5.1",
+		"@types/jest": "29.5.2",
 		"@types/js-yaml": "4.0.5",
 		"@types/jsdom": "21.1.1",
 		"@types/jsonld": "1.5.8",
 		"@types/jsrsasign": "10.5.8",
 		"@types/mime-types": "2.1.1",
-		"@types/node": "20.1.3",
+		"@types/node": "20.2.5",
 		"@types/node-fetch": "3.0.3",
-		"@types/nodemailer": "6.4.7",
+		"@types/nodemailer": "6.4.8",
 		"@types/oauth": "0.9.1",
-		"@types/pg": "8.6.6",
+		"@types/pg": "8.10.1",
 		"@types/pug": "2.0.6",
 		"@types/punycode": "2.1.0",
 		"@types/qrcode": "1.5.0",
@@ -196,17 +197,17 @@
 		"@types/sinonjs__fake-timers": "8.1.2",
 		"@types/tinycolor2": "1.4.3",
 		"@types/tmp": "0.2.3",
-		"@types/unzipper": "0.10.5",
+		"@types/unzipper": "0.10.6",
 		"@types/uuid": "9.0.1",
 		"@types/vary": "1.1.0",
 		"@types/web-push": "3.3.2",
 		"@types/websocket": "1.0.5",
 		"@types/ws": "8.5.4",
-		"@typescript-eslint/eslint-plugin": "5.59.5",
-		"@typescript-eslint/parser": "5.59.5",
+		"@typescript-eslint/eslint-plugin": "5.59.8",
+		"@typescript-eslint/parser": "5.59.8",
 		"aws-sdk-client-mock": "2.1.1",
 		"cross-env": "7.0.3",
-		"eslint": "8.40.0",
+		"eslint": "8.41.0",
 		"eslint-plugin-import": "2.27.5",
 		"execa": "6.1.0",
 		"jest": "29.5.0",
diff --git a/packages/backend/src/GlobalModule.ts b/packages/backend/src/GlobalModule.ts
index 5fb4e8ef3..406e3192b 100644
--- a/packages/backend/src/GlobalModule.ts
+++ b/packages/backend/src/GlobalModule.ts
@@ -4,7 +4,7 @@ import * as Redis from 'ioredis';
 import { DataSource } from 'typeorm';
 import { MeiliSearch } from 'meilisearch';
 import { DI } from './di-symbols.js';
-import { loadConfig } from './config.js';
+import { Config, loadConfig } from './config.js';
 import { createPostgresDataSource } from './postgres.js';
 import { RepositoryModule } from './models/RepositoryModule.js';
 import type { Provider, OnApplicationShutdown } from '@nestjs/common';
@@ -25,7 +25,7 @@ const $db: Provider = {
 
 const $meilisearch: Provider = {
 	provide: DI.meilisearch,
-	useFactory: (config) => {
+	useFactory: (config: Config) => {
 		if (config.meilisearch) {
 			return new MeiliSearch({
 				host: `${config.meilisearch.ssl ? 'https' : 'http' }://${config.meilisearch.host}:${config.meilisearch.port}`,
@@ -40,7 +40,7 @@ const $meilisearch: Provider = {
 
 const $redis: Provider = {
 	provide: DI.redis,
-	useFactory: (config) => {
+	useFactory: (config: Config) => {
 		return new Redis.Redis({
 			port: config.redis.port,
 			host: config.redis.host,
@@ -55,7 +55,7 @@ const $redis: Provider = {
 
 const $redisForPub: Provider = {
 	provide: DI.redisForPub,
-	useFactory: (config) => {
+	useFactory: (config: Config) => {
 		const redis = new Redis.Redis({
 			port: config.redisForPubsub.port,
 			host: config.redisForPubsub.host,
@@ -71,7 +71,7 @@ const $redisForPub: Provider = {
 
 const $redisForSub: Provider = {
 	provide: DI.redisForSub,
-	useFactory: (config) => {
+	useFactory: (config: Config) => {
 		const redis = new Redis.Redis({
 			port: config.redisForPubsub.port,
 			host: config.redisForPubsub.host,
@@ -100,7 +100,7 @@ export class GlobalModule implements OnApplicationShutdown {
 		@Inject(DI.redisForSub) private redisForSub: Redis.Redis,
 	) {}
 
-	async onApplicationShutdown(signal: string): Promise<void> {
+	public async dispose(): Promise<void> {
 		if (process.env.NODE_ENV === 'test') {
 			// XXX:
 			// Shutting down the existing connections causes errors on Jest as
@@ -116,4 +116,8 @@ export class GlobalModule implements OnApplicationShutdown {
 			this.redisForSub.disconnect(),
 		]);
 	}
+
+	async onApplicationShutdown(signal: string): Promise<void> {
+		await this.dispose();
+	}
 }
diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts
index c6e107538..9d1945e4d 100644
--- a/packages/backend/src/config.ts
+++ b/packages/backend/src/config.ts
@@ -144,7 +144,7 @@ export function loadConfig() {
 	const clientManifestExists = fs.existsSync(_dirname + '/../../../built/_vite_/manifest.json');
 	const clientManifest = clientManifestExists ?
 		JSON.parse(fs.readFileSync(`${_dirname}/../../../built/_vite_/manifest.json`, 'utf-8'))
-		: { 'src/init.ts': { file: 'src/init.ts' } };
+		: { 'src/_boot_.ts': { file: 'src/_boot_.ts' } };
 	const config = yaml.load(fs.readFileSync(path, 'utf-8')) as Source;
 
 	const mixin = {} as Mixin;
@@ -165,7 +165,7 @@ export function loadConfig() {
 	mixin.authUrl = `${mixin.scheme}://${mixin.host}/auth`;
 	mixin.driveUrl = `${mixin.scheme}://${mixin.host}/files`;
 	mixin.userAgent = `Misskey/${meta.version} (${config.url})`;
-	mixin.clientEntry = clientManifest['src/init.ts'];
+	mixin.clientEntry = clientManifest['src/_boot_.ts'];
 	mixin.clientManifestExists = clientManifestExists;
 
 	const externalMediaProxy = config.mediaProxy ?
@@ -190,6 +190,6 @@ function tryCreateUrl(url: string) {
 	try {
 		return new URL(url);
 	} catch (e) {
-		throw `url="${url}" is not a valid URL.`;
+		throw new Error(`url="${url}" is not a valid URL.`);
 	}
 }
diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts
index 2d4226a32..d8df37191 100644
--- a/packages/backend/src/core/AntennaService.ts
+++ b/packages/backend/src/core/AntennaService.ts
@@ -55,11 +55,6 @@ export class AntennaService implements OnApplicationShutdown {
 		this.redisForSub.on('message', this.onRedisMessage);
 	}
 
-	@bindThis
-	public onApplicationShutdown(signal?: string | undefined) {
-		this.redisForSub.off('message', this.onRedisMessage);
-	}
-
 	@bindThis
 	private async onRedisMessage(_: string, data: string): Promise<void> {
 		const obj = JSON.parse(data);
@@ -196,4 +191,14 @@ export class AntennaService implements OnApplicationShutdown {
 	
 		return this.antennas;
 	}
+
+	@bindThis
+	public dispose(): void {
+		this.redisForSub.off('message', this.onRedisMessage);
+	}
+
+	@bindThis
+	public onApplicationShutdown(signal?: string | undefined): void {
+		this.dispose();
+	}
 }
diff --git a/packages/backend/src/core/CacheService.ts b/packages/backend/src/core/CacheService.ts
index cf1e81ffc..de33e4c24 100644
--- a/packages/backend/src/core/CacheService.ts
+++ b/packages/backend/src/core/CacheService.ts
@@ -166,7 +166,12 @@ export class CacheService implements OnApplicationShutdown {
 	}
 
 	@bindThis
-	public onApplicationShutdown(signal?: string | undefined) {
+	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/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts
index 7aaa1b833..1a52a229c 100644
--- a/packages/backend/src/core/CaptchaService.ts
+++ b/packages/backend/src/core/CaptchaService.ts
@@ -30,7 +30,7 @@ export class CaptchaService {
 		}, { throwErrorWhenResponseNotOk: false });
 	
 		if (!res.ok) {
-			throw `${res.status}`;
+			throw new Error(`${res.status}`);
 		}
 	
 		return await res.json() as CaptchaResponse;
@@ -39,48 +39,48 @@ export class CaptchaService {
 	@bindThis
 	public async verifyRecaptcha(secret: string, response: string | null | undefined): Promise<void> {
 		if (response == null) {
-			throw 'recaptcha-failed: no response provided';
+			throw new Error('recaptcha-failed: no response provided');
 		}
 
 		const result = await this.getCaptchaResponse('https://www.recaptcha.net/recaptcha/api/siteverify', secret, response).catch(err => {
-			throw `recaptcha-request-failed: ${err}`;
+			throw new Error(`recaptcha-request-failed: ${err}`);
 		});
 
 		if (result.success !== true) {
 			const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : '';
-			throw `recaptcha-failed: ${errorCodes}`;
+			throw new Error(`recaptcha-failed: ${errorCodes}`);
 		}
 	}
 
 	@bindThis
 	public async verifyHcaptcha(secret: string, response: string | null | undefined): Promise<void> {
 		if (response == null) {
-			throw 'hcaptcha-failed: no response provided';
+			throw new Error('hcaptcha-failed: no response provided');
 		}
 
 		const result = await this.getCaptchaResponse('https://hcaptcha.com/siteverify', secret, response).catch(err => {
-			throw `hcaptcha-request-failed: ${err}`;
+			throw new Error(`hcaptcha-request-failed: ${err}`);
 		});
 
 		if (result.success !== true) {
 			const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : '';
-			throw `hcaptcha-failed: ${errorCodes}`;
+			throw new Error(`hcaptcha-failed: ${errorCodes}`);
 		}
 	}
 
 	@bindThis
 	public async verifyTurnstile(secret: string, response: string | null | undefined): Promise<void> {
 		if (response == null) {
-			throw 'turnstile-failed: no response provided';
+			throw new Error('turnstile-failed: no response provided');
 		}
 	
 		const result = await this.getCaptchaResponse('https://challenges.cloudflare.com/turnstile/v0/siteverify', secret, response).catch(err => {
-			throw `turnstile-request-failed: ${err}`;
+			throw new Error(`turnstile-request-failed: ${err}`);
 		});
 
 		if (result.success !== true) {
 			const errorCodes = result['error-codes'] ? result['error-codes'].join(', ') : '';
-			throw `turnstile-failed: ${errorCodes}`;
+			throw new Error(`turnstile-failed: ${errorCodes}`);
 		}
 	}
 }
diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts
index 93557ce61..3499df38b 100644
--- a/packages/backend/src/core/CustomEmojiService.ts
+++ b/packages/backend/src/core/CustomEmojiService.ts
@@ -7,7 +7,7 @@ import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
 import type { DriveFile } from '@/models/entities/DriveFile.js';
 import type { Emoji } from '@/models/entities/Emoji.js';
-import type { EmojisRepository } from '@/models/index.js';
+import type { EmojisRepository, Role } from '@/models/index.js';
 import { bindThis } from '@/decorators.js';
 import { MemoryKVCache, RedisSingleCache } from '@/misc/cache.js';
 import { UtilityService } from '@/core/UtilityService.js';
@@ -15,6 +15,8 @@ import type { Config } from '@/config.js';
 import { query } from '@/misc/prelude/url.js';
 import type { Serialized } from '@/server/api/stream/types.js';
 
+const parseEmojiStrRegexp = /^(\w+)(?:@([\w.-]+))?$/;
+
 @Injectable()
 export class CustomEmojiService {
 	private cache: MemoryKVCache<Emoji | null>;
@@ -63,6 +65,9 @@ export class CustomEmojiService {
 		aliases: string[];
 		host: string | null;
 		license: string | null;
+		isSensitive: boolean;
+		localOnly: boolean;
+		roleIdsThatCanBeUsedThisEmojiAsReaction: Role['id'][];
 	}): Promise<Emoji> {
 		const emoji = await this.emojisRepository.insert({
 			id: this.idService.genId(),
@@ -75,6 +80,9 @@ export class CustomEmojiService {
 			publicUrl: data.driveFile.webpublicUrl ?? data.driveFile.url,
 			type: data.driveFile.webpublicType ?? data.driveFile.type,
 			license: data.license,
+			isSensitive: data.isSensitive,
+			localOnly: data.localOnly,
+			roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction,
 		}).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0]));
 
 		if (data.host == null) {
@@ -90,10 +98,14 @@ export class CustomEmojiService {
 
 	@bindThis
 	public async update(id: Emoji['id'], data: {
+		driveFile?: DriveFile;
 		name?: string;
 		category?: string | null;
 		aliases?: string[];
 		license?: string | null;
+		isSensitive?: boolean;
+		localOnly?: boolean;
+		roleIdsThatCanBeUsedThisEmojiAsReaction?: Role['id'][];
 	}): Promise<void> {
 		const emoji = await this.emojisRepository.findOneByOrFail({ id: id });
 		const sameNameEmoji = await this.emojisRepository.findOneBy({ name: data.name, host: IsNull() });
@@ -105,6 +117,12 @@ export class CustomEmojiService {
 			category: data.category,
 			aliases: data.aliases,
 			license: data.license,
+			isSensitive: data.isSensitive,
+			localOnly: data.localOnly,
+			originalUrl: data.driveFile != null ? data.driveFile.url : undefined,
+			publicUrl: data.driveFile != null ? (data.driveFile.webpublicUrl ?? data.driveFile.url) : undefined,
+			type: data.driveFile != null ? (data.driveFile.webpublicType ?? data.driveFile.type) : undefined,
+			roleIdsThatCanBeUsedThisEmojiAsReaction: data.roleIdsThatCanBeUsedThisEmojiAsReaction ?? undefined,
 		});
 
 		this.localEmojisCache.refresh();
@@ -259,7 +277,7 @@ export class CustomEmojiService {
 
 	@bindThis
 	public parseEmojiStr(emojiName: string, noteUserHost: string | null) {
-		const match = emojiName.match(/^(\w+)(?:@([\w.-]+))?$/);
+		const match = emojiName.match(parseEmojiStrRegexp);
 		if (!match) return { name: null, host: null };
 
 		const name = match[1];
diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts
index 8103d5afe..9de633350 100644
--- a/packages/backend/src/core/FetchInstanceMetadataService.ts
+++ b/packages/backend/src/core/FetchInstanceMetadataService.ts
@@ -116,14 +116,14 @@ export class FetchInstanceMetadataService {
 			const wellknown = await this.httpRequestService.getJson('https://' + instance.host + '/.well-known/nodeinfo')
 				.catch(err => {
 					if (err.statusCode === 404) {
-						throw 'No nodeinfo provided';
+						throw new Error('No nodeinfo provided');
 					} else {
 						throw err.statusCode ?? err.message;
 					}
 				}) as Record<string, unknown>;
 	
 			if (wellknown.links == null || !Array.isArray(wellknown.links)) {
-				throw 'No wellknown links';
+				throw new Error('No wellknown links');
 			}
 	
 			const links = wellknown.links as any[];
@@ -134,7 +134,7 @@ export class FetchInstanceMetadataService {
 			const link = lnik2_1 ?? lnik2_0 ?? lnik1_0;
 	
 			if (link == null) {
-				throw 'No nodeinfo link provided';
+				throw new Error('No nodeinfo link provided');
 			}
 	
 			const info = await this.httpRequestService.getJson(link.href)
diff --git a/packages/backend/src/core/MetaService.ts b/packages/backend/src/core/MetaService.ts
index 0b861be8d..5acc9ad9a 100644
--- a/packages/backend/src/core/MetaService.ts
+++ b/packages/backend/src/core/MetaService.ts
@@ -120,8 +120,13 @@ export class MetaService implements OnApplicationShutdown {
 	}
 
 	@bindThis
-	public onApplicationShutdown(signal?: string | undefined) {
+	public dispose(): void {
 		clearInterval(this.intervalId);
 		this.redisForSub.off('message', this.onMessage);
 	}
+
+	@bindThis
+	public onApplicationShutdown(signal?: string | undefined): void {
+		this.dispose();
+	}
 }
diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts
index 9b2d5dc0f..dffee16e0 100644
--- a/packages/backend/src/core/MfmService.ts
+++ b/packages/backend/src/core/MfmService.ts
@@ -83,7 +83,7 @@ export class MfmService {
 					if (hashtagNames && href && hashtagNames.map(x => x.toLowerCase()).includes(txt.toLowerCase())) {
 						text += txt;
 					// メンション
-					} else if (txt.startsWith('@') && !(rel && rel.value.match(/^me /))) {
+					} else if (txt.startsWith('@') && !(rel && rel.value.startsWith('me '))) {
 						const part = txt.split('@');
 	
 						if (part.length === 2 && href) {
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 977c9052c..1c8491bf5 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -510,7 +510,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 
 		if (data.poll && data.poll.expiresAt) {
 			const delay = data.poll.expiresAt.getTime() - Date.now();
-			this.queueService.endedPollNotificationQueue.add({
+			this.queueService.endedPollNotificationQueue.add(note.id, {
 				noteId: note.id,
 			}, {
 				delay,
@@ -790,7 +790,13 @@ export class NoteCreateService implements OnApplicationShutdown {
 		return mentionedUsers;
 	}
 
-	onApplicationShutdown(signal?: string | undefined) {
+	@bindThis
+	public dispose(): void {
 		this.#shutdownController.abort();
 	}
+
+	@bindThis
+	public onApplicationShutdown(signal?: string | undefined): void {
+		this.dispose();
+	}
 }
diff --git a/packages/backend/src/core/NoteReadService.ts b/packages/backend/src/core/NoteReadService.ts
index 1129bd159..e57e57d31 100644
--- a/packages/backend/src/core/NoteReadService.ts
+++ b/packages/backend/src/core/NoteReadService.ts
@@ -122,7 +122,13 @@ export class NoteReadService implements OnApplicationShutdown {
 		}
 	}
 
-	onApplicationShutdown(signal?: string | undefined): void {
+	@bindThis
+	public dispose(): void {
 		this.#shutdownController.abort();
 	}
+
+	@bindThis
+	public onApplicationShutdown(signal?: string | undefined): void {
+		this.dispose();
+	}
 }
diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts
index a245908c9..ed47165f7 100644
--- a/packages/backend/src/core/NotificationService.ts
+++ b/packages/backend/src/core/NotificationService.ts
@@ -152,7 +152,13 @@ export class NotificationService implements OnApplicationShutdown {
 		*/
 	}
 
-	onApplicationShutdown(signal?: string | undefined): void {
+	@bindThis
+	public dispose(): void {
 		this.#shutdownController.abort();
 	}
+
+	@bindThis
+	public onApplicationShutdown(signal?: string | undefined): void {
+		this.dispose();
+	}
 }
diff --git a/packages/backend/src/core/QueryService.ts b/packages/backend/src/core/QueryService.ts
index 0cee2076b..bf50a1cde 100644
--- a/packages/backend/src/core/QueryService.ts
+++ b/packages/backend/src/core/QueryService.ts
@@ -208,7 +208,7 @@ export class QueryService {
 	}
 
 	@bindThis
-	public generateRepliesQuery(q: SelectQueryBuilder<any>, me?: Pick<User, 'id' | 'showTimelineReplies'> | null): void {
+	public generateRepliesQuery(q: SelectQueryBuilder<any>, withReplies: boolean, me?: Pick<User, 'id'> | null): void {
 		if (me == null) {
 			q.andWhere(new Brackets(qb => { qb
 				.where('note.replyId IS NULL') // 返信ではない
@@ -217,7 +217,7 @@ export class QueryService {
 					.andWhere('note.replyUserId = note.userId');
 				}));
 			}));
-		} else if (!me.showTimelineReplies) {
+		} else if (!withReplies) {
 			q.andWhere(new Brackets(qb => { qb
 				.where('note.replyId IS NULL') // 返信ではない
 				.orWhere('note.replyUserId = :meId', { meId: me.id }) // 返信だけど自分のノートへの返信
diff --git a/packages/backend/src/core/QueueModule.ts b/packages/backend/src/core/QueueModule.ts
index 1d7394777..3384ca457 100644
--- a/packages/backend/src/core/QueueModule.ts
+++ b/packages/backend/src/core/QueueModule.ts
@@ -1,42 +1,11 @@
 import { setTimeout } from 'node:timers/promises';
 import { Inject, Module, OnApplicationShutdown } from '@nestjs/common';
-import Bull from 'bull';
+import * as Bull from 'bullmq';
 import { DI } from '@/di-symbols.js';
 import type { Config } from '@/config.js';
+import { QUEUE, baseQueueOptions } from '@/queue/const.js';
 import type { Provider } from '@nestjs/common';
-import type { DeliverJobData, InboxJobData, DbJobData, ObjectStorageJobData, EndedPollNotificationJobData, WebhookDeliverJobData, RelationshipJobData, DbJobMap } from '../queue/types.js';
-
-function q<T>(config: Config, name: string, limitPerSec = -1) {
-	return new Bull<T>(name, {
-		redis: {
-			port: config.redisForJobQueue.port,
-			host: config.redisForJobQueue.host,
-			family: config.redisForJobQueue.family == null ? 0 : config.redisForJobQueue.family,
-			password: config.redisForJobQueue.pass,
-			db: config.redisForJobQueue.db ?? 0,
-		},
-		prefix: config.redisForJobQueue.prefix ? `${config.redisForJobQueue.prefix}:queue` : 'queue',
-		limiter: limitPerSec > 0 ? {
-			max: limitPerSec,
-			duration: 1000,
-		} : undefined,
-		settings: {
-			backoffStrategies: {
-				apBackoff,
-			},
-		},
-	});
-}
-
-// ref. https://github.com/misskey-dev/misskey/pull/7635#issue-971097019
-function apBackoff(attemptsMade: number, err: Error) {
-	const baseDelay = 60 * 1000;	// 1min
-	const maxBackoff = 8 * 60 * 60 * 1000;	// 8hours
-	let backoff = (Math.pow(2, attemptsMade) - 1) * baseDelay;
-	backoff = Math.min(backoff, maxBackoff);
-	backoff += Math.round(backoff * Math.random() * 0.2);
-	return backoff;
-}
+import type { DeliverJobData, InboxJobData, EndedPollNotificationJobData, WebhookDeliverJobData, RelationshipJobData } from '../queue/types.js';
 
 export type SystemQueue = Bull.Queue<Record<string, unknown>>;
 export type EndedPollNotificationQueue = Bull.Queue<EndedPollNotificationJobData>;
@@ -49,49 +18,49 @@ export type WebhookDeliverQueue = Bull.Queue<WebhookDeliverJobData>;
 
 const $system: Provider = {
 	provide: 'queue:system',
-	useFactory: (config: Config) => q(config, 'system'),
+	useFactory: (config: Config) => new Bull.Queue(QUEUE.SYSTEM, baseQueueOptions(config, QUEUE.SYSTEM)),
 	inject: [DI.config],
 };
 
 const $endedPollNotification: Provider = {
 	provide: 'queue:endedPollNotification',
-	useFactory: (config: Config) => q(config, 'endedPollNotification'),
+	useFactory: (config: Config) => new Bull.Queue(QUEUE.ENDED_POLL_NOTIFICATION, baseQueueOptions(config, QUEUE.ENDED_POLL_NOTIFICATION)),
 	inject: [DI.config],
 };
 
 const $deliver: Provider = {
 	provide: 'queue:deliver',
-	useFactory: (config: Config) => q(config, 'deliver', config.deliverJobPerSec ?? 128),
+	useFactory: (config: Config) => new Bull.Queue(QUEUE.DELIVER, baseQueueOptions(config, QUEUE.DELIVER)),
 	inject: [DI.config],
 };
 
 const $inbox: Provider = {
 	provide: 'queue:inbox',
-	useFactory: (config: Config) => q(config, 'inbox', config.inboxJobPerSec ?? 16),
+	useFactory: (config: Config) => new Bull.Queue(QUEUE.INBOX, baseQueueOptions(config, QUEUE.INBOX)),
 	inject: [DI.config],
 };
 
 const $db: Provider = {
 	provide: 'queue:db',
-	useFactory: (config: Config) => q(config, 'db'),
+	useFactory: (config: Config) => new Bull.Queue(QUEUE.DB, baseQueueOptions(config, QUEUE.DB)),
 	inject: [DI.config],
 };
 
 const $relationship: Provider = {
 	provide: 'queue:relationship',
-	useFactory: (config: Config) => q(config, 'relationship', config.relashionshipJobPerSec ?? 64),
+	useFactory: (config: Config) => new Bull.Queue(QUEUE.RELATIONSHIP, baseQueueOptions(config, QUEUE.RELATIONSHIP)),
 	inject: [DI.config],
 };
 
 const $objectStorage: Provider = {
 	provide: 'queue:objectStorage',
-	useFactory: (config: Config) => q(config, 'objectStorage'),
+	useFactory: (config: Config) => new Bull.Queue(QUEUE.OBJECT_STORAGE, baseQueueOptions(config, QUEUE.OBJECT_STORAGE)),
 	inject: [DI.config],
 };
 
 const $webhookDeliver: Provider = {
 	provide: 'queue:webhookDeliver',
-	useFactory: (config: Config) => q(config, 'webhookDeliver', 64),
+	useFactory: (config: Config) => new Bull.Queue(QUEUE.WEBHOOK_DELIVER, baseQueueOptions(config, QUEUE.WEBHOOK_DELIVER)),
 	inject: [DI.config],
 };
 
@@ -131,7 +100,7 @@ export class QueueModule implements OnApplicationShutdown {
 		@Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue,
 	) {}
 
-	async onApplicationShutdown(signal: string): Promise<void> {
+	public async dispose(): Promise<void> {
 		if (process.env.NODE_ENV === 'test') {
 			// XXX:
 			// Shutting down the existing connections causes errors on Jest as
@@ -151,4 +120,8 @@ export class QueueModule implements OnApplicationShutdown {
 			this.webhookDeliverQueue.close(),
 		]);
 	}
+
+	async onApplicationShutdown(signal: string): Promise<void> {
+		await this.dispose();
+	}
 }
diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts
index b4ffffecc..2ae8a2b75 100644
--- a/packages/backend/src/core/QueueService.ts
+++ b/packages/backend/src/core/QueueService.ts
@@ -1,6 +1,5 @@
 import { Inject, Injectable } from '@nestjs/common';
 import { v4 as uuid } from 'uuid';
-import Bull from 'bull';
 import type { IActivity } from '@/core/activitypub/type.js';
 import type { DriveFile } from '@/models/entities/DriveFile.js';
 import type { Webhook, webhookEventTypes } from '@/models/entities/Webhook.js';
@@ -11,6 +10,7 @@ import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js';
 import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, RelationshipQueue, SystemQueue, WebhookDeliverQueue } from './QueueModule.js';
 import type { DbJobData, RelationshipJobData, ThinUser } from '../queue/types.js';
 import type httpSignature from '@peertube/http-signature';
+import type * as Bull from 'bullmq';
 
 @Injectable()
 export class QueueService {
@@ -26,7 +26,43 @@ export class QueueService {
 		@Inject('queue:relationship') public relationshipQueue: RelationshipQueue,
 		@Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue,
 		@Inject('queue:webhookDeliver') public webhookDeliverQueue: WebhookDeliverQueue,
-	) {}
+	) {
+		this.systemQueue.add('tickCharts', {
+		}, {
+			repeat: { pattern: '55 * * * *' },
+			removeOnComplete: true,
+		});
+
+		this.systemQueue.add('resyncCharts', {
+		}, {
+			repeat: { pattern: '0 0 * * *' },
+			removeOnComplete: true,
+		});
+
+		this.systemQueue.add('cleanCharts', {
+		}, {
+			repeat: { pattern: '0 0 * * *' },
+			removeOnComplete: true,
+		});
+
+		this.systemQueue.add('aggregateRetention', {
+		}, {
+			repeat: { pattern: '0 0 * * *' },
+			removeOnComplete: true,
+		});
+
+		this.systemQueue.add('clean', {
+		}, {
+			repeat: { pattern: '0 0 * * *' },
+			removeOnComplete: true,
+		});
+
+		this.systemQueue.add('checkExpiredMutings', {
+		}, {
+			repeat: { pattern: '*/5 * * * *' },
+			removeOnComplete: true,
+		});
+	}
 
 	@bindThis
 	public deliver(user: ThinUser, content: IActivity | null, to: string | null, isSharedInbox: boolean) {
@@ -42,11 +78,10 @@ export class QueueService {
 			isSharedInbox,
 		};
 
-		return this.deliverQueue.add(data, {
+		return this.deliverQueue.add(to, data, {
 			attempts: this.config.deliverJobMaxAttempts ?? 12,
-			timeout: 1 * 60 * 1000,	// 1min
 			backoff: {
-				type: 'apBackoff',
+				type: 'custom',
 			},
 			removeOnComplete: true,
 			removeOnFail: true,
@@ -60,11 +95,10 @@ export class QueueService {
 			signature,
 		};
 
-		return this.inboxQueue.add(data, {
+		return this.inboxQueue.add('', data, {
 			attempts: this.config.inboxJobMaxAttempts ?? 8,
-			timeout: 5 * 60 * 1000,	// 5min
 			backoff: {
-				type: 'apBackoff',
+				type: 'custom',
 			},
 			removeOnComplete: true,
 			removeOnFail: true,
@@ -212,7 +246,7 @@ export class QueueService {
 	private generateToDbJobData<T extends 'importFollowingToDb' | 'importBlockingToDb', D extends DbJobData<T>>(name: T, data: D): {
 		name: string,
 		data: D,
-		opts: Bull.JobOptions,
+		opts: Bull.JobsOptions,
 	} {
 		return {
 			name,
@@ -299,10 +333,10 @@ export class QueueService {
 	}
 
 	@bindThis
-	private generateRelationshipJobData(name: 'follow' | 'unfollow' | 'block' | 'unblock', data: RelationshipJobData, opts: Bull.JobOptions = {}): {
+	private generateRelationshipJobData(name: 'follow' | 'unfollow' | 'block' | 'unblock', data: RelationshipJobData, opts: Bull.JobsOptions = {}): {
 		name: string,
 		data: RelationshipJobData,
-		opts: Bull.JobOptions,
+		opts: Bull.JobsOptions,
 	} {
 		return {
 			name,
@@ -351,11 +385,10 @@ export class QueueService {
 			eventId: uuid(),
 		};
 
-		return this.webhookDeliverQueue.add(data, {
+		return this.webhookDeliverQueue.add(webhook.id, data, {
 			attempts: 4,
-			timeout: 1 * 60 * 1000,	// 1min
 			backoff: {
-				type: 'apBackoff',
+				type: 'custom',
 			},
 			removeOnComplete: true,
 			removeOnFail: true,
@@ -367,11 +400,11 @@ export class QueueService {
 		this.deliverQueue.once('cleaned', (jobs, status) => {
 			//deliverLogger.succ(`Cleaned ${jobs.length} ${status} jobs`);
 		});
-		this.deliverQueue.clean(0, 'delayed');
+		this.deliverQueue.clean(0, Infinity, 'delayed');
 
 		this.inboxQueue.once('cleaned', (jobs, status) => {
 			//inboxLogger.succ(`Cleaned ${jobs.length} ${status} jobs`);
 		});
-		this.inboxQueue.clean(0, 'delayed');
+		this.inboxQueue.clean(0, Infinity, 'delayed');
 	}
 }
diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts
index a274b19e4..4b01b6af7 100644
--- a/packages/backend/src/core/ReactionService.ts
+++ b/packages/backend/src/core/ReactionService.ts
@@ -20,6 +20,7 @@ import { bindThis } from '@/decorators.js';
 import { UtilityService } from '@/core/UtilityService.js';
 import { UserBlockingService } from '@/core/UserBlockingService.js';
 import { CustomEmojiService } from '@/core/CustomEmojiService.js';
+import { RoleService } from '@/core/RoleService.js';
 
 const FALLBACK = '❤';
 
@@ -54,6 +55,9 @@ type DecodedReaction = {
 	host?: string | null;
 };
 
+const isCustomEmojiRegexp = /^:([\w+-]+)(?:@\.)?:$/;
+const decodeCustomEmojiRegexp = /^:([\w+-]+)(?:@([\w.-]+))?:$/;
+
 @Injectable()
 export class ReactionService {
 	constructor(
@@ -72,6 +76,7 @@ export class ReactionService {
 		private utilityService: UtilityService,
 		private metaService: MetaService,
 		private customEmojiService: CustomEmojiService,
+		private roleService: RoleService,
 		private userEntityService: UserEntityService,
 		private noteEntityService: NoteEntityService,
 		private userBlockingService: UserBlockingService,
@@ -85,7 +90,7 @@ export class ReactionService {
 	}
 
 	@bindThis
-	public async create(user: { id: User['id']; host: User['host']; isBot: User['isBot'] }, note: Note, reaction?: string | null) {
+	public async create(user: { id: User['id']; host: User['host']; isBot: User['isBot'] }, note: Note, _reaction?: string | null) {
 		// Check blocking
 		if (note.userId !== user.id) {
 			const blocked = await this.userBlockingService.checkBlocked(note.userId, user.id);
@@ -99,10 +104,41 @@ export class ReactionService {
 			throw new IdentifiableError('68e9d2d1-48bf-42c2-b90a-b20e09fd3d48', 'Note not accessible for you.');
 		}
 
-		if (note.reactionAcceptance === 'likeOnly' || ((note.reactionAcceptance === 'likeOnlyForRemote') && (user.host != null))) {
+		let reaction = _reaction ?? FALLBACK;
+
+		if (note.reactionAcceptance === 'likeOnly' || ((note.reactionAcceptance === 'likeOnlyForRemote' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote') && (user.host != null))) {
 			reaction = '❤️';
-		} else {
-			reaction = await this.toDbReaction(reaction, user.host);
+		} else if (_reaction) {
+			const custom = reaction.match(isCustomEmojiRegexp);
+			if (custom) {
+				const reacterHost = this.utilityService.toPunyNullable(user.host);
+
+				const name = custom[1];
+				const emoji = reacterHost == null
+					? (await this.customEmojiService.localEmojisCache.fetch()).get(name)
+					: await this.emojisRepository.findOneBy({
+						host: reacterHost,
+						name,
+					});
+
+				if (emoji) {
+					if (emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0 || (await this.roleService.getUserRoles(user.id)).some(r => emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.includes(r.id))) {
+						reaction = reacterHost ? `:${name}@${reacterHost}:` : `:${name}:`;
+
+						// センシティブ
+						if ((note.reactionAcceptance === 'nonSensitiveOnly') && emoji.isSensitive) {
+							reaction = FALLBACK;
+						}
+					} else {
+						// リアクションとして使う権限がない
+						reaction = FALLBACK;
+					}
+				} else {
+					reaction = FALLBACK;
+				}
+			} else {
+				reaction = this.normalize(reaction ?? null);
+			}
 		}
 
 		const record: NoteReaction = {
@@ -288,11 +324,9 @@ export class ReactionService {
 	}
 
 	@bindThis
-	public async toDbReaction(reaction?: string | null, reacterHost?: string | null): Promise<string> {
+	public normalize(reaction: string | null): string {
 		if (reaction == null) return FALLBACK;
 
-		reacterHost = this.utilityService.toPunyNullable(reacterHost);
-
 		// 文字列タイプのリアクションを絵文字に変換
 		if (Object.keys(legacies).includes(reaction)) return legacies[reaction];
 
@@ -306,25 +340,12 @@ export class ReactionService {
 			return unicode.match('\u200d') ? unicode : unicode.replace(/\ufe0f/g, '');
 		}
 
-		const custom = reaction.match(/^:([\w+-]+)(?:@\.)?:$/);
-		if (custom) {
-			const name = custom[1];
-			const emoji = reacterHost == null
-				? (await this.customEmojiService.localEmojisCache.fetch()).get(name)
-				: await this.emojisRepository.findOneBy({
-					host: reacterHost,
-					name,
-				});
-
-			if (emoji) return reacterHost ? `:${name}@${reacterHost}:` : `:${name}:`;
-		}
-
 		return FALLBACK;
 	}
 
 	@bindThis
 	public decodeReaction(str: string): DecodedReaction {
-		const custom = str.match(/^:([\w+-]+)(?:@([\w.-]+))?:$/);
+		const custom = str.match(decodeCustomEmojiRegexp);
 
 		if (custom) {
 			const name = custom[1];
diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts
index 68087ccc3..40ae10666 100644
--- a/packages/backend/src/core/RoleService.ts
+++ b/packages/backend/src/core/RoleService.ts
@@ -306,6 +306,14 @@ export class RoleService implements OnApplicationShutdown {
 		return user.isRoot || (await this.getUserRoles(user.id)).some(r => r.isAdministrator);
 	}
 
+	@bindThis
+	public async isExplorable(role: { id: Role['id']} | null): Promise<boolean> {
+		if (role == null) return false;
+		const check = await this.rolesRepository.findOneBy({ id: role.id });
+		if (check == null) return false;
+		return check.isExplorable;
+	}
+
 	@bindThis
 	public async getModeratorIds(includeAdmins = true): Promise<User['id'][]> {
 		const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
@@ -425,7 +433,12 @@ export class RoleService implements OnApplicationShutdown {
 	}
 
 	@bindThis
-	public onApplicationShutdown(signal?: string | undefined) {
+	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/WebfingerService.ts b/packages/backend/src/core/WebfingerService.ts
index 3ee799064..f58a6a10f 100644
--- a/packages/backend/src/core/WebfingerService.ts
+++ b/packages/backend/src/core/WebfingerService.ts
@@ -16,6 +16,9 @@ type IWebFinger = {
 	subject: string;
 };
 
+const urlRegex = /^https?:\/\//;
+const mRegex = /^([^@]+)@(.*)/;
+
 @Injectable()
 export class WebfingerService {
 	constructor(
@@ -35,12 +38,12 @@ export class WebfingerService {
 
 	@bindThis
 	private genUrl(query: string): string {
-		if (query.match(/^https?:\/\//)) {
+		if (query.match(urlRegex)) {
 			const u = new URL(query);
 			return `${u.protocol}//${u.hostname}/.well-known/webfinger?` + urlQuery({ resource: query });
 		}
 
-		const m = query.match(/^([^@]+)@(.*)/);
+		const m = query.match(mRegex);
 		if (m) {
 			const hostname = m[2];
 			const useHttp = process.env.MISSKEY_WEBFINGER_USE_HTTP && process.env.MISSKEY_WEBFINGER_USE_HTTP.toLowerCase() === 'true';
diff --git a/packages/backend/src/core/WebhookService.ts b/packages/backend/src/core/WebhookService.ts
index 57baade77..467755a07 100644
--- a/packages/backend/src/core/WebhookService.ts
+++ b/packages/backend/src/core/WebhookService.ts
@@ -81,7 +81,12 @@ export class WebhookService implements OnApplicationShutdown {
 	}
 
 	@bindThis
-	public onApplicationShutdown(signal?: string | undefined) {
+	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/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts
index 60e19bfca..d8b95ca4d 100644
--- a/packages/backend/src/core/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/activitypub/ApRendererService.ts
@@ -277,7 +277,7 @@ export class ApRendererService {
 			const name = reaction.replaceAll(':', '');
 			const emoji = (await this.customEmojiService.localEmojisCache.fetch()).get(name);
 
-			if (emoji) object.tag = [this.renderEmoji(emoji)];
+			if (emoji && !emoji.localOnly) object.tag = [this.renderEmoji(emoji)];
 		}
 
 		return object;
@@ -400,7 +400,7 @@ export class ApRendererService {
 		}));
 
 		const emojis = await this.getEmojis(note.emojis);
-		const apemojis = emojis.map(emoji => this.renderEmoji(emoji));
+		const apemojis = emojis.filter(emoji => !emoji.localOnly).map(emoji => this.renderEmoji(emoji));
 
 		const tag = [
 			...hashtagTags,
@@ -479,7 +479,7 @@ export class ApRendererService {
 		}
 
 		const emojis = await this.getEmojis(user.emojis);
-		const apemojis = emojis.map(emoji => this.renderEmoji(emoji));
+		const apemojis = emojis.filter(emoji => !emoji.localOnly).map(emoji => this.renderEmoji(emoji));
 
 		const hashtagTags = (user.tags ?? []).map(tag => this.renderHashtag(tag));
 
diff --git a/packages/backend/src/core/activitypub/LdSignatureService.ts b/packages/backend/src/core/activitypub/LdSignatureService.ts
index 2dc1a410a..20fe2a0a7 100644
--- a/packages/backend/src/core/activitypub/LdSignatureService.ts
+++ b/packages/backend/src/core/activitypub/LdSignatureService.ts
@@ -94,7 +94,7 @@ class LdSignature {
 	@bindThis
 	private getLoader() {
 		return async (url: string): Promise<any> => {
-			if (!url.match('^https?\:\/\/')) throw `Invalid URL ${url}`;
+			if (!url.match('^https?\:\/\/')) throw new Error(`Invalid URL ${url}`);
 
 			if (this.preLoad) {
 				if (url in CONTEXTS) {
@@ -126,7 +126,7 @@ class LdSignature {
 			timeout: this.loderTimeout,
 		}, { throwErrorWhenResponseNotOk: false }).then(res => {
 			if (!res.ok) {
-				throw `${res.status} ${res.statusText}`;
+				throw new Error(`${res.status} ${res.statusText}`);
 			} else {
 				return res.json();
 			}
diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts
index 87a9db405..76757f530 100644
--- a/packages/backend/src/core/activitypub/models/ApNoteService.ts
+++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts
@@ -18,6 +18,7 @@ import { PollService } from '@/core/PollService.js';
 import { StatusError } from '@/misc/status-error.js';
 import { UtilityService } from '@/core/UtilityService.js';
 import { bindThis } from '@/decorators.js';
+import { checkHttps } from '@/misc/check-https.js';
 import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js';
 // eslint-disable-next-line @typescript-eslint/consistent-type-imports
 import { ApLoggerService } from '../ApLoggerService.js';
@@ -32,7 +33,6 @@ import { ApQuestionService } from './ApQuestionService.js';
 import { ApImageService } from './ApImageService.js';
 import type { Resolver } from '../ApResolverService.js';
 import type { IObject, IPost } from '../type.js';
-import { checkHttps } from '@/misc/check-https.js';
 
 @Injectable()
 export class ApNoteService {
@@ -230,7 +230,7 @@ export class ApNoteService {
 			quote = results.filter((x): x is { status: 'ok', res: Note | null } => x.status === 'ok').map(x => x.res).find(x => x);
 			if (!quote) {
 				if (results.some(x => x.status === 'temperror')) {
-					throw 'quote resolve failed';
+					throw new Error('quote resolve failed');
 				}
 			}
 		}
@@ -311,7 +311,7 @@ export class ApNoteService {
 	
 		// ブロックしてたら中断
 		const meta = await this.metaService.fetch();
-		if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) throw { statusCode: 451 };
+		if (this.utilityService.isBlockedHost(meta.blockedHosts, this.utilityService.extractDbHost(uri))) throw new StatusError('blocked host', 451);
 	
 		const unlock = await this.appLockService.getApLock(uri);
 	
diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts
index eea1d1b84..f52ebed10 100644
--- a/packages/backend/src/core/activitypub/models/ApPersonService.ts
+++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts
@@ -32,6 +32,8 @@ import type { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { bindThis } from '@/decorators.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';
 import { getApId, getApType, getOneApHrefNullable, isActor, isCollection, isCollectionOrOrderedCollection, isPropertyValue } from '../type.js';
 import { extractApHashtags } from './tag.js';
 import type { OnModuleInit } from '@nestjs/common';
@@ -42,8 +44,6 @@ import type { ApLoggerService } from '../ApLoggerService.js';
 // eslint-disable-next-line @typescript-eslint/consistent-type-imports
 import type { ApImageService } from './ApImageService.js';
 import type { IActor, IObject } from '../type.js';
-import type { AccountMoveService } from '@/core/AccountMoveService.js';
-import { checkHttps } from '@/misc/check-https.js';
 
 const nameLength = 128;
 const summaryLength = 2048;
@@ -306,7 +306,6 @@ export class ApPersonService implements OnModuleInit {
 					tags,
 					isBot,
 					isCat: (person as any).isCat === true,
-					showTimelineReplies: false,
 				})) as RemoteUser;
 
 				await transactionalEntityManager.save(new UserProfile({
@@ -696,7 +695,7 @@ export class ApPersonService implements OnModuleInit {
 		if (!dst.alsoKnownAs || dst.alsoKnownAs.length === 0) {
 			return 'skip: dst.alsoKnownAs is empty';
 		}
-		if (!dst.alsoKnownAs?.includes(src.uri)) {
+		if (!dst.alsoKnownAs.includes(src.uri)) {
 			return 'skip: alsoKnownAs does not include from.uri';
 		}
 
diff --git a/packages/backend/src/core/chart/ChartManagementService.ts b/packages/backend/src/core/chart/ChartManagementService.ts
index 03e361265..b0e9e534d 100644
--- a/packages/backend/src/core/chart/ChartManagementService.ts
+++ b/packages/backend/src/core/chart/ChartManagementService.ts
@@ -60,7 +60,8 @@ export class ChartManagementService implements OnApplicationShutdown {
 		}, 1000 * 60 * 20);
 	}
 
-	async onApplicationShutdown(signal: string): Promise<void> {
+	@bindThis
+	public async dispose(): Promise<void> {
 		clearInterval(this.saveIntervalId);
 		if (process.env.NODE_ENV !== 'test') {
 			await Promise.all(
@@ -68,4 +69,9 @@ export class ChartManagementService implements OnApplicationShutdown {
 			);
 		}
 	}
+
+	@bindThis
+	async onApplicationShutdown(signal: string): Promise<void> {
+		await this.dispose();
+	}
 }
diff --git a/packages/backend/src/core/entities/EmojiEntityService.ts b/packages/backend/src/core/entities/EmojiEntityService.ts
index 3bad048bc..4a18cd1b3 100644
--- a/packages/backend/src/core/entities/EmojiEntityService.ts
+++ b/packages/backend/src/core/entities/EmojiEntityService.ts
@@ -26,6 +26,8 @@ export class EmojiEntityService {
 			category: emoji.category,
 			// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
 			url: emoji.publicUrl || emoji.originalUrl,
+			isSensitive: emoji.isSensitive ? true : undefined,
+			roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length > 0 ? emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : undefined,
 		};
 	}
 
@@ -51,6 +53,9 @@ export class EmojiEntityService {
 			// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
 			url: emoji.publicUrl || emoji.originalUrl,
 			license: emoji.license,
+			isSensitive: emoji.isSensitive,
+			localOnly: emoji.localOnly,
+			roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction,
 		};
 	}
 
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index 7f61e1d6f..bfd506ea8 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -466,7 +466,6 @@ export class UserEntityService implements OnModuleInit {
 				mutedInstances: profile!.mutedInstances,
 				mutingNotificationTypes: profile!.mutingNotificationTypes,
 				emailNotificationTypes: profile!.emailNotificationTypes,
-				showTimelineReplies: user.showTimelineReplies ?? falsy,
 				achievements: profile!.achievements,
 				loggedInDays: profile!.loggedInDates.length,
 				policies: this.roleService.getUserPolicies(user.id),
diff --git a/packages/backend/src/core/entities/UserListEntityService.ts b/packages/backend/src/core/entities/UserListEntityService.ts
index 2461cb2c1..862881927 100644
--- a/packages/backend/src/core/entities/UserListEntityService.ts
+++ b/packages/backend/src/core/entities/UserListEntityService.ts
@@ -35,6 +35,7 @@ export class UserListEntityService {
 			createdAt: userList.createdAt.toISOString(),
 			name: userList.name,
 			userIds: users.map(x => x.userId),
+			isPublic: userList.isPublic,
 		};
 	}
 }
diff --git a/packages/backend/src/daemons/JanitorService.ts b/packages/backend/src/daemons/JanitorService.ts
index 8cdfb703f..f826d5062 100644
--- a/packages/backend/src/daemons/JanitorService.ts
+++ b/packages/backend/src/daemons/JanitorService.ts
@@ -34,7 +34,12 @@ export class JanitorService implements OnApplicationShutdown {
 	}
 
 	@bindThis
-	public onApplicationShutdown(signal?: string | undefined) {
+	public dispose(): void {
 		clearInterval(this.intervalId);
 	}
+
+	@bindThis
+	public onApplicationShutdown(signal?: string | undefined): void {
+		this.dispose();
+	}
 }
diff --git a/packages/backend/src/daemons/QueueStatsService.ts b/packages/backend/src/daemons/QueueStatsService.ts
index b717434e0..53a0d14cd 100644
--- a/packages/backend/src/daemons/QueueStatsService.ts
+++ b/packages/backend/src/daemons/QueueStatsService.ts
@@ -1,7 +1,11 @@
-import { Injectable } from '@nestjs/common';
+import { Inject, Injectable } from '@nestjs/common';
 import Xev from 'xev';
+import * as Bull from 'bullmq';
 import { QueueService } from '@/core/QueueService.js';
 import { bindThis } from '@/decorators.js';
+import { DI } from '@/di-symbols.js';
+import type { Config } from '@/config.js';
+import { QUEUE, baseQueueOptions } from '@/queue/const.js';
 import type { OnApplicationShutdown } from '@nestjs/common';
 
 const ev = new Xev();
@@ -13,6 +17,9 @@ export class QueueStatsService implements OnApplicationShutdown {
 	private intervalId: NodeJS.Timer;
 
 	constructor(
+		@Inject(DI.config)
+		private config: Config,
+
 		private queueService: QueueService,
 	) {
 	}
@@ -31,11 +38,14 @@ export class QueueStatsService implements OnApplicationShutdown {
 		let activeDeliverJobs = 0;
 		let activeInboxJobs = 0;
 
-		this.queueService.deliverQueue.on('global:active', () => {
+		const deliverQueueEvents = new Bull.QueueEvents(QUEUE.DELIVER, baseQueueOptions(this.config, QUEUE.DELIVER));
+		const inboxQueueEvents = new Bull.QueueEvents(QUEUE.INBOX, baseQueueOptions(this.config, QUEUE.INBOX));
+
+		deliverQueueEvents.on('active', () => {
 			activeDeliverJobs++;
 		});
 
-		this.queueService.inboxQueue.on('global:active', () => {
+		inboxQueueEvents.on('active', () => {
 			activeInboxJobs++;
 		});
 
@@ -71,9 +81,14 @@ export class QueueStatsService implements OnApplicationShutdown {
 
 		this.intervalId = setInterval(tick, interval);
 	}
-
+	
 	@bindThis
-	public onApplicationShutdown(signal?: string | undefined) {
+	public dispose(): void {
 		clearInterval(this.intervalId);
 	}
+
+	@bindThis
+	public onApplicationShutdown(signal?: string | undefined): void {
+		this.dispose();
+	}
 }
diff --git a/packages/backend/src/daemons/ServerStatsService.ts b/packages/backend/src/daemons/ServerStatsService.ts
index bb190cf60..6cd71c0e2 100644
--- a/packages/backend/src/daemons/ServerStatsService.ts
+++ b/packages/backend/src/daemons/ServerStatsService.ts
@@ -63,9 +63,14 @@ export class ServerStatsService implements OnApplicationShutdown {
 	}
 
 	@bindThis
-	public onApplicationShutdown(signal?: string | undefined) {
+	public dispose(): void {
 		clearInterval(this.intervalId);
 	}
+
+	@bindThis
+	public onApplicationShutdown(signal?: string | undefined): void {
+		this.dispose();
+	}
 }
 
 // CPU STAT
diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts
index c06c7a715..4a073f102 100644
--- a/packages/backend/src/di-symbols.ts
+++ b/packages/backend/src/di-symbols.ts
@@ -25,6 +25,7 @@ export const DI = {
 	userSecurityKeysRepository: Symbol('userSecurityKeysRepository'),
 	userPublickeysRepository: Symbol('userPublickeysRepository'),
 	userListsRepository: Symbol('userListsRepository'),
+	userListFavoritesRepository: Symbol('userListFavoritesRepository'),
 	userListJoiningsRepository: Symbol('userListJoiningsRepository'),
 	userNotePiningsRepository: Symbol('userNotePiningsRepository'),
 	userIpsRepository: Symbol('userIpsRepository'),
diff --git a/packages/backend/src/misc/id/aid.ts b/packages/backend/src/misc/id/aid.ts
index 9e206ee98..f0cbc9900 100644
--- a/packages/backend/src/misc/id/aid.ts
+++ b/packages/backend/src/misc/id/aid.ts
@@ -21,7 +21,7 @@ function getNoise(): string {
 
 export function genAid(date: Date): string {
 	const t = date.getTime();
-	if (isNaN(t)) throw 'Failed to create AID: Invalid Date';
+	if (isNaN(t)) throw new Error('Failed to create AID: Invalid Date');
 	counter++;
 	return getTime(t) + getNoise();
 }
diff --git a/packages/backend/src/misc/prelude/time.ts b/packages/backend/src/misc/prelude/time.ts
index 34e8b6b17..b21978b18 100644
--- a/packages/backend/src/misc/prelude/time.ts
+++ b/packages/backend/src/misc/prelude/time.ts
@@ -5,15 +5,16 @@ const dateTimeIntervals = {
 };
 
 export function dateUTC(time: number[]): Date {
-	const d = time.length === 2 ? Date.UTC(time[0], time[1])
-					: time.length === 3 ? Date.UTC(time[0], time[1], time[2])
-					: time.length === 4 ? Date.UTC(time[0], time[1], time[2], time[3])
-					: time.length === 5 ? Date.UTC(time[0], time[1], time[2], time[3], time[4])
-					: time.length === 6 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5])
-					: time.length === 7 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5], time[6])
-					: null;
+	const d =
+		time.length === 2 ? Date.UTC(time[0], time[1])
+		: time.length === 3 ? Date.UTC(time[0], time[1], time[2])
+		: time.length === 4 ? Date.UTC(time[0], time[1], time[2], time[3])
+		: time.length === 5 ? Date.UTC(time[0], time[1], time[2], time[3], time[4])
+		: time.length === 6 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5])
+		: time.length === 7 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5], time[6])
+		: null;
 
-	if (!d) throw 'wrong number of arguments';
+	if (!d) throw new Error('wrong number of arguments');
 
 	return new Date(d);
 }
diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts
index 588c98b58..4231acc04 100644
--- a/packages/backend/src/models/RepositoryModule.ts
+++ b/packages/backend/src/models/RepositoryModule.ts
@@ -1,6 +1,6 @@
 import { Module } from '@nestjs/common';
 import { DI } from '@/di-symbols.js';
-import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite, UserMemo } from './index.js';
+import { User, Note, Announcement, AnnouncementRead, App, NoteFavorite, NoteThreadMuting, NoteReaction, NoteUnread, Poll, PollVote, UserProfile, UserKeypair, UserPending, AttestationChallenge, UserSecurityKey, UserPublickey, UserList, UserListJoining, UserNotePining, UserIp, UsedUsername, Following, FollowRequest, Instance, Emoji, DriveFile, DriveFolder, Meta, Muting, RenoteMuting, Blocking, SwSubscription, Hashtag, AbuseUserReport, RegistrationTicket, AuthSession, AccessToken, Signin, Page, PageLike, GalleryPost, GalleryLike, ModerationLog, Clip, ClipNote, Antenna, PromoNote, PromoRead, Relay, MutedNote, Channel, ChannelFollowing, ChannelFavorite, RegistryItem, Webhook, Ad, PasswordResetRequest, RetentionAggregation, FlashLike, Flash, Role, RoleAssignment, ClipFavorite, UserMemo, UserListFavorite } from './index.js';
 import type { DataSource } from 'typeorm';
 import type { Provider } from '@nestjs/common';
 
@@ -112,6 +112,12 @@ const $userListsRepository: Provider = {
 	inject: [DI.db],
 };
 
+const $userListFavoritesRepository: Provider = {
+	provide: DI.userListFavoritesRepository,
+	useFactory: (db: DataSource) => db.getRepository(UserListFavorite),
+	inject: [DI.db],
+};
+
 const $userListJoiningsRepository: Provider = {
 	provide: DI.userListJoiningsRepository,
 	useFactory: (db: DataSource) => db.getRepository(UserListJoining),
@@ -416,6 +422,7 @@ const $userMemosRepository: Provider = {
 		$userSecurityKeysRepository,
 		$userPublickeysRepository,
 		$userListsRepository,
+		$userListFavoritesRepository,
 		$userListJoiningsRepository,
 		$userNotePiningsRepository,
 		$userIpsRepository,
@@ -483,6 +490,7 @@ const $userMemosRepository: Provider = {
 		$userSecurityKeysRepository,
 		$userPublickeysRepository,
 		$userListsRepository,
+		$userListFavoritesRepository,
 		$userListJoiningsRepository,
 		$userNotePiningsRepository,
 		$userIpsRepository,
diff --git a/packages/backend/src/models/entities/Emoji.ts b/packages/backend/src/models/entities/Emoji.ts
index dbb437d43..8fd3e65f5 100644
--- a/packages/backend/src/models/entities/Emoji.ts
+++ b/packages/backend/src/models/entities/Emoji.ts
@@ -60,4 +60,20 @@ export class Emoji {
 		length: 1024, nullable: true,
 	})
 	public license: string | null;
+
+	@Column('boolean', {
+		default: false,
+	})
+	public localOnly: boolean;
+
+	@Column('boolean', {
+		default: false,
+	})
+	public isSensitive: boolean;
+
+	// TODO: 定期ジョブで存在しなくなったロールIDを除去するようにする
+	@Column('varchar', {
+		array: true, length: 128, default: '{}',
+	})
+	public roleIdsThatCanBeUsedThisEmojiAsReaction: string[];
 }
diff --git a/packages/backend/src/models/entities/Note.ts b/packages/backend/src/models/entities/Note.ts
index df508b4dc..4f49a0595 100644
--- a/packages/backend/src/models/entities/Note.ts
+++ b/packages/backend/src/models/entities/Note.ts
@@ -90,7 +90,7 @@ export class Note {
 	@Column('varchar', {
 		length: 64, nullable: true,
 	})
-	public reactionAcceptance: 'likeOnly' | 'likeOnlyForRemote' | null;
+	public reactionAcceptance: 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null;
 
 	@Column('smallint', {
 		default: 0,
diff --git a/packages/backend/src/models/entities/User.ts b/packages/backend/src/models/entities/User.ts
index 8e10f999b..6669890cf 100644
--- a/packages/backend/src/models/entities/User.ts
+++ b/packages/backend/src/models/entities/User.ts
@@ -232,12 +232,6 @@ export class User {
 	})
 	public followersUri: string | null;
 
-	@Column('boolean', {
-		default: false,
-		comment: 'Whether to show users replying to other users in the timeline.',
-	})
-	public showTimelineReplies: boolean;
-
 	@Index({ unique: true })
 	@Column('char', {
 		length: 16, nullable: true, unique: true,
diff --git a/packages/backend/src/models/entities/UserList.ts b/packages/backend/src/models/entities/UserList.ts
index b8a4b54d4..94f3dc3cb 100644
--- a/packages/backend/src/models/entities/UserList.ts
+++ b/packages/backend/src/models/entities/UserList.ts
@@ -19,6 +19,12 @@ export class UserList {
 	})
 	public userId: User['id'];
 
+	@Index()
+	@Column('boolean', {
+		default: false,
+	})
+	public isPublic: boolean;
+
 	@ManyToOne(type => User, {
 		onDelete: 'CASCADE',
 	})
diff --git a/packages/backend/src/models/entities/UserListFavorite.ts b/packages/backend/src/models/entities/UserListFavorite.ts
new file mode 100644
index 000000000..e57abb460
--- /dev/null
+++ b/packages/backend/src/models/entities/UserListFavorite.ts
@@ -0,0 +1,33 @@
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
+import { id } from '../id.js';
+import { User } from './User.js';
+import { UserList } from './UserList.js';
+
+@Entity()
+@Index(['userId', 'userListId'], { unique: true })
+export class UserListFavorite {
+	@PrimaryColumn(id())
+	public id: string;
+
+	@Column('timestamp with time zone')
+	public createdAt: Date;
+
+	@Index()
+	@Column(id())
+	public userId: User['id'];
+
+	@ManyToOne(type => User, {
+		onDelete: 'CASCADE',
+	})
+	@JoinColumn()
+	public user: User | null;
+
+	@Column(id())
+	public userListId: UserList['id'];
+
+	@ManyToOne(type => UserList, {
+		onDelete: 'CASCADE',
+	})
+	@JoinColumn()
+	public userList: UserList | null;
+}
diff --git a/packages/backend/src/models/index.ts b/packages/backend/src/models/index.ts
index b8ba28db9..4b230ab74 100644
--- a/packages/backend/src/models/index.ts
+++ b/packages/backend/src/models/index.ts
@@ -49,6 +49,7 @@ import { User } from '@/models/entities/User.js';
 import { UserIp } from '@/models/entities/UserIp.js';
 import { UserKeypair } from '@/models/entities/UserKeypair.js';
 import { UserList } from '@/models/entities/UserList.js';
+import { UserListFavorite } from './entities/UserListFavorite.js';
 import { UserListJoining } from '@/models/entities/UserListJoining.js';
 import { UserNotePining } from '@/models/entities/UserNotePining.js';
 import { UserPending } from '@/models/entities/UserPending.js';
@@ -117,6 +118,7 @@ export {
 	UserIp,
 	UserKeypair,
 	UserList,
+	UserListFavorite,
 	UserListJoining,
 	UserNotePining,
 	UserPending,
@@ -184,6 +186,7 @@ export type UsersRepository = Repository<User>;
 export type UserIpsRepository = Repository<UserIp>;
 export type UserKeypairsRepository = Repository<UserKeypair>;
 export type UserListsRepository = Repository<UserList>;
+export type UserListFavoritesRepository = Repository<UserListFavorite>;
 export type UserListJoiningsRepository = Repository<UserListJoining>;
 export type UserNotePiningsRepository = Repository<UserNotePining>;
 export type UserPendingsRepository = Repository<UserPending>;
diff --git a/packages/backend/src/models/json-schema/emoji.ts b/packages/backend/src/models/json-schema/emoji.ts
index db4fd62cf..63f56e77c 100644
--- a/packages/backend/src/models/json-schema/emoji.ts
+++ b/packages/backend/src/models/json-schema/emoji.ts
@@ -22,6 +22,19 @@ export const packedEmojiSimpleSchema = {
 			type: 'string',
 			optional: false, nullable: false,
 		},
+		isSensitive: {
+			type: 'boolean',
+			optional: true, nullable: false,
+		},
+		roleIdsThatCanBeUsedThisEmojiAsReaction: {
+			type: 'array',
+			optional: true, nullable: false,
+			items: {
+				type: 'string',
+				optional: false, nullable: false,
+				format: 'id',
+			},
+		},
 	},
 } as const;
 
@@ -63,5 +76,22 @@ export const packedEmojiDetailedSchema = {
 			type: 'string',
 			optional: false, nullable: true,
 		},
+		isSensitive: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		localOnly: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		roleIdsThatCanBeUsedThisEmojiAsReaction: {
+			type: 'array',
+			optional: false, nullable: false,
+			items: {
+				type: 'string',
+				optional: false, nullable: false,
+				format: 'id',
+			},
+		},
 	},
 } as const;
diff --git a/packages/backend/src/models/json-schema/user-list.ts b/packages/backend/src/models/json-schema/user-list.ts
index 3ba5dc4a8..1e620516e 100644
--- a/packages/backend/src/models/json-schema/user-list.ts
+++ b/packages/backend/src/models/json-schema/user-list.ts
@@ -25,5 +25,10 @@ export const packedUserListSchema = {
 				format: 'id',
 			},
 		},
+		isPublic: {
+			type: 'boolean',
+			nullable: false,
+			optional: false,
+		},
 	},
 } as const;
diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts
index f3d404e6c..488979c40 100644
--- a/packages/backend/src/postgres.ts
+++ b/packages/backend/src/postgres.ts
@@ -57,6 +57,7 @@ import { User } from '@/models/entities/User.js';
 import { UserIp } from '@/models/entities/UserIp.js';
 import { UserKeypair } from '@/models/entities/UserKeypair.js';
 import { UserList } from '@/models/entities/UserList.js';
+import { UserListFavorite } from '@/models/entities/UserListFavorite.js';
 import { UserListJoining } from '@/models/entities/UserListJoining.js';
 import { UserNotePining } from '@/models/entities/UserNotePining.js';
 import { UserPending } from '@/models/entities/UserPending.js';
@@ -132,6 +133,7 @@ export const entities = [
 	UserKeypair,
 	UserPublickey,
 	UserList,
+	UserListFavorite,
 	UserListJoining,
 	UserNotePining,
 	UserSecurityKey,
diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts
index dc025f988..42f9c1af7 100644
--- a/packages/backend/src/queue/QueueProcessorService.ts
+++ b/packages/backend/src/queue/QueueProcessorService.ts
@@ -1,10 +1,9 @@
-import { Inject, Injectable } from '@nestjs/common';
+import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
+import * as Bull from 'bullmq';
 import type { Config } from '@/config.js';
 import { DI } from '@/di-symbols.js';
 import type Logger from '@/logger.js';
-import { QueueService } from '@/core/QueueService.js';
 import { bindThis } from '@/decorators.js';
-import { getJobInfo } from './get-job-info.js';
 import { WebhookDeliverProcessorService } from './processors/WebhookDeliverProcessorService.js';
 import { EndedPollNotificationProcessorService } from './processors/EndedPollNotificationProcessorService.js';
 import { DeliverProcessorService } from './processors/DeliverProcessorService.js';
@@ -35,17 +34,51 @@ import { CheckExpiredMutingsProcessorService } from './processors/CheckExpiredMu
 import { CleanProcessorService } from './processors/CleanProcessorService.js';
 import { AggregateRetentionProcessorService } from './processors/AggregateRetentionProcessorService.js';
 import { QueueLoggerService } from './QueueLoggerService.js';
+import { QUEUE, baseQueueOptions } from './const.js';
+
+// ref. https://github.com/misskey-dev/misskey/pull/7635#issue-971097019
+function httpRelatedBackoff(attemptsMade: number) {
+	const baseDelay = 60 * 1000;	// 1min
+	const maxBackoff = 8 * 60 * 60 * 1000;	// 8hours
+	let backoff = (Math.pow(2, attemptsMade) - 1) * baseDelay;
+	backoff = Math.min(backoff, maxBackoff);
+	backoff += Math.round(backoff * Math.random() * 0.2);
+	return backoff;
+}
+
+function getJobInfo(job: Bull.Job | undefined, increment = false): string {
+	if (job == null) return '-';
+
+	const age = Date.now() - job.timestamp;
+
+	const formated = age > 60000 ? `${Math.floor(age / 1000 / 60)}m`
+		: age > 10000 ? `${Math.floor(age / 1000)}s`
+		: `${age}ms`;
+
+	// onActiveとかonCompletedのattemptsMadeがなぜか0始まりなのでインクリメントする
+	const currentAttempts = job.attemptsMade + (increment ? 1 : 0);
+	const maxAttempts = job.opts ? job.opts.attempts : 0;
+
+	return `id=${job.id} attempts=${currentAttempts}/${maxAttempts} age=${formated}`;
+}
 
 @Injectable()
-export class QueueProcessorService {
+export class QueueProcessorService implements OnApplicationShutdown {
 	private logger: Logger;
+	private systemQueueWorker: Bull.Worker;
+	private dbQueueWorker: Bull.Worker;
+	private deliverQueueWorker: Bull.Worker;
+	private inboxQueueWorker: Bull.Worker;
+	private webhookDeliverQueueWorker: Bull.Worker;
+	private relationshipQueueWorker: Bull.Worker;
+	private objectStorageQueueWorker: Bull.Worker;
+	private endedPollNotificationQueueWorker: Bull.Worker;
 
 	constructor(
 		@Inject(DI.config)
 		private config: Config,
 
 		private queueLoggerService: QueueLoggerService,
-		private queueService: QueueService,
 		private webhookDeliverProcessorService: WebhookDeliverProcessorService,
 		private endedPollNotificationProcessorService: EndedPollNotificationProcessorService,
 		private deliverProcessorService: DeliverProcessorService,
@@ -77,10 +110,7 @@ export class QueueProcessorService {
 		private cleanProcessorService: CleanProcessorService,
 	) {
 		this.logger = this.queueLoggerService.logger;
-	}
 
-	@bindThis
-	public start() {
 		function renderError(e: Error): any {
 			if (e) { // 何故かeがundefinedで来ることがある
 				return {
@@ -97,146 +127,232 @@ export class QueueProcessorService {
 			}
 		}
 
-		const systemLogger = this.logger.createSubLogger('system');
-		const deliverLogger = this.logger.createSubLogger('deliver');
-		const webhookLogger = this.logger.createSubLogger('webhook');
-		const inboxLogger = this.logger.createSubLogger('inbox');
-		const dbLogger = this.logger.createSubLogger('db');
-		const relationshipLogger = this.logger.createSubLogger('relationship');
-		const objectStorageLogger = this.logger.createSubLogger('objectStorage');
+		//#region system
+		this.systemQueueWorker = new Bull.Worker(QUEUE.SYSTEM, (job) => {
+			switch (job.name) {
+				case 'tickCharts': return this.tickChartsProcessorService.process();
+				case 'resyncCharts': return this.resyncChartsProcessorService.process();
+				case 'cleanCharts': return this.cleanChartsProcessorService.process();
+				case 'aggregateRetention': return this.aggregateRetentionProcessorService.process();
+				case 'checkExpiredMutings': return this.checkExpiredMutingsProcessorService.process();
+				case 'clean': return this.cleanProcessorService.process();
+				default: throw new Error(`unrecognized job type ${job.name} for system`);
+			}
+		}, {
+			...baseQueueOptions(this.config, QUEUE.SYSTEM),
+			autorun: false,
+		});
 
-		this.queueService.systemQueue
-			.on('waiting', (jobId) => systemLogger.debug(`waiting id=${jobId}`))
+		const systemLogger = this.logger.createSubLogger('system');
+
+		this.systemQueueWorker
 			.on('active', (job) => systemLogger.debug(`active id=${job.id}`))
 			.on('completed', (job, result) => systemLogger.debug(`completed(${result}) id=${job.id}`))
-			.on('failed', (job, err) => systemLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) }))
-			.on('error', (job: any, err: Error) => systemLogger.error(`error ${err}`, { job, e: renderError(err) }))
-			.on('stalled', (job) => systemLogger.warn(`stalled id=${job.id}`));
+			.on('failed', (job, err) => systemLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
+			.on('error', (err: Error) => systemLogger.error(`error ${err}`, { e: renderError(err) }))
+			.on('stalled', (jobId) => systemLogger.warn(`stalled id=${jobId}`));
+		//#endregion
 
-		this.queueService.deliverQueue
-			.on('waiting', (jobId) => deliverLogger.debug(`waiting id=${jobId}`))
-			.on('active', (job) => deliverLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
-			.on('completed', (job, result) => deliverLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
-			.on('failed', (job, err) => deliverLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`))
-			.on('error', (job: any, err: Error) => deliverLogger.error(`error ${err}`, { job, e: renderError(err) }))
-			.on('stalled', (job) => deliverLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`));
+		//#region db
+		this.dbQueueWorker = new Bull.Worker(QUEUE.DB, (job) => {
+			switch (job.name) {
+				case 'deleteDriveFiles': return this.deleteDriveFilesProcessorService.process(job);
+				case 'exportCustomEmojis': return this.exportCustomEmojisProcessorService.process(job);
+				case 'exportNotes': return this.exportNotesProcessorService.process(job);
+				case 'exportFavorites': return this.exportFavoritesProcessorService.process(job);
+				case 'exportFollowing': return this.exportFollowingProcessorService.process(job);
+				case 'exportMuting': return this.exportMutingProcessorService.process(job);
+				case 'exportBlocking': return this.exportBlockingProcessorService.process(job);
+				case 'exportUserLists': return this.exportUserListsProcessorService.process(job);
+				case 'exportAntennas': return this.exportAntennasProcessorService.process(job);
+				case 'importFollowing': return this.importFollowingProcessorService.process(job);
+				case 'importFollowingToDb': return this.importFollowingProcessorService.processDb(job);
+				case 'importMuting': return this.importMutingProcessorService.process(job);
+				case 'importBlocking': return this.importBlockingProcessorService.process(job);
+				case 'importBlockingToDb': return this.importBlockingProcessorService.processDb(job);
+				case 'importUserLists': return this.importUserListsProcessorService.process(job);
+				case 'importCustomEmojis': return this.importCustomEmojisProcessorService.process(job);
+				case 'importAntennas': return this.importAntennasProcessorService.process(job);
+				case 'deleteAccount': return this.deleteAccountProcessorService.process(job);
+				default: throw new Error(`unrecognized job type ${job.name} for db`);
+			}
+		}, {
+			...baseQueueOptions(this.config, QUEUE.DB),
+			autorun: false,
+		});
 
-		this.queueService.inboxQueue
-			.on('waiting', (jobId) => inboxLogger.debug(`waiting id=${jobId}`))
-			.on('active', (job) => inboxLogger.debug(`active ${getJobInfo(job, true)}`))
-			.on('completed', (job, result) => inboxLogger.debug(`completed(${result}) ${getJobInfo(job, true)}`))
-			.on('failed', (job, err) => inboxLogger.warn(`failed(${err}) ${getJobInfo(job)} activity=${job.data.activity ? job.data.activity.id : 'none'}`, { job, e: renderError(err) }))
-			.on('error', (job: any, err: Error) => inboxLogger.error(`error ${err}`, { job, e: renderError(err) }))
-			.on('stalled', (job) => inboxLogger.warn(`stalled ${getJobInfo(job)} activity=${job.data.activity ? job.data.activity.id : 'none'}`));
+		const dbLogger = this.logger.createSubLogger('db');
 
-		this.queueService.dbQueue
-			.on('waiting', (jobId) => dbLogger.debug(`waiting id=${jobId}`))
+		this.dbQueueWorker
 			.on('active', (job) => dbLogger.debug(`active id=${job.id}`))
 			.on('completed', (job, result) => dbLogger.debug(`completed(${result}) id=${job.id}`))
-			.on('failed', (job, err) => dbLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) }))
-			.on('error', (job: any, err: Error) => dbLogger.error(`error ${err}`, { job, e: renderError(err) }))
-			.on('stalled', (job) => dbLogger.warn(`stalled id=${job.id}`));
+			.on('failed', (job, err) => dbLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
+			.on('error', (err: Error) => dbLogger.error(`error ${err}`, { e: renderError(err) }))
+			.on('stalled', (jobId) => dbLogger.warn(`stalled id=${jobId}`));
+		//#endregion
 
-		this.queueService.relationshipQueue
-			.on('waiting', (jobId) => relationshipLogger.debug(`waiting id=${jobId}`))
-			.on('active', (job) => relationshipLogger.debug(`active id=${job.id}`))
-			.on('completed', (job, result) => relationshipLogger.debug(`completed(${result}) id=${job.id}`))
-			.on('failed', (job, err) => relationshipLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) }))
-			.on('error', (job: any, err: Error) => relationshipLogger.error(`error ${err}`, { job, e: renderError(err) }))
-			.on('stalled', (job) => relationshipLogger.warn(`stalled id=${job.id}`));
+		//#region deliver
+		this.deliverQueueWorker = new Bull.Worker(QUEUE.DELIVER, (job) => this.deliverProcessorService.process(job), {
+			...baseQueueOptions(this.config, QUEUE.DELIVER),
+			autorun: false,
+			concurrency: this.config.deliverJobConcurrency ?? 128,
+			limiter: {
+				max: this.config.deliverJobPerSec ?? 128,
+				duration: 1000,
+			},
+			settings: {
+				backoffStrategy: httpRelatedBackoff,
+			},
+		});
 
-		this.queueService.objectStorageQueue
-			.on('waiting', (jobId) => objectStorageLogger.debug(`waiting id=${jobId}`))
-			.on('active', (job) => objectStorageLogger.debug(`active id=${job.id}`))
-			.on('completed', (job, result) => objectStorageLogger.debug(`completed(${result}) id=${job.id}`))
-			.on('failed', (job, err) => objectStorageLogger.warn(`failed(${err}) id=${job.id}`, { job, e: renderError(err) }))
-			.on('error', (job: any, err: Error) => objectStorageLogger.error(`error ${err}`, { job, e: renderError(err) }))
-			.on('stalled', (job) => objectStorageLogger.warn(`stalled id=${job.id}`));
+		const deliverLogger = this.logger.createSubLogger('deliver');
 
-		this.queueService.webhookDeliverQueue
-			.on('waiting', (jobId) => webhookLogger.debug(`waiting id=${jobId}`))
+		this.deliverQueueWorker
+			.on('active', (job) => deliverLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
+			.on('completed', (job, result) => deliverLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
+			.on('failed', (job, err) => deliverLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`))
+			.on('error', (err: Error) => deliverLogger.error(`error ${err}`, { e: renderError(err) }))
+			.on('stalled', (jobId) => deliverLogger.warn(`stalled id=${jobId}`));
+		//#endregion
+
+		//#region inbox
+		this.inboxQueueWorker = new Bull.Worker(QUEUE.INBOX, (job) => this.inboxProcessorService.process(job), {
+			...baseQueueOptions(this.config, QUEUE.INBOX),
+			autorun: false,
+			concurrency: this.config.inboxJobConcurrency ?? 16,
+			limiter: {
+				max: this.config.inboxJobPerSec ?? 16,
+				duration: 1000,
+			},
+			settings: {
+				backoffStrategy: httpRelatedBackoff,
+			},
+		});
+
+		const inboxLogger = this.logger.createSubLogger('inbox');
+
+		this.inboxQueueWorker
+			.on('active', (job) => inboxLogger.debug(`active ${getJobInfo(job, true)}`))
+			.on('completed', (job, result) => inboxLogger.debug(`completed(${result}) ${getJobInfo(job, true)}`))
+			.on('failed', (job, err) => inboxLogger.warn(`failed(${err}) ${getJobInfo(job)} activity=${job ? (job.data.activity ? job.data.activity.id : 'none') : '-'}`, { job, e: renderError(err) }))
+			.on('error', (err: Error) => inboxLogger.error(`error ${err}`, { e: renderError(err) }))
+			.on('stalled', (jobId) => inboxLogger.warn(`stalled id=${jobId}`));
+		//#endregion
+
+		//#region webhook deliver
+		this.webhookDeliverQueueWorker = new Bull.Worker(QUEUE.WEBHOOK_DELIVER, (job) => this.webhookDeliverProcessorService.process(job), {
+			...baseQueueOptions(this.config, QUEUE.WEBHOOK_DELIVER),
+			autorun: false,
+			concurrency: 64,
+			limiter: {
+				max: 64,
+				duration: 1000,
+			},
+			settings: {
+				backoffStrategy: httpRelatedBackoff,
+			},
+		});
+
+		const webhookLogger = this.logger.createSubLogger('webhook');
+
+		this.webhookDeliverQueueWorker
 			.on('active', (job) => webhookLogger.debug(`active ${getJobInfo(job, true)} to=${job.data.to}`))
 			.on('completed', (job, result) => webhookLogger.debug(`completed(${result}) ${getJobInfo(job, true)} to=${job.data.to}`))
-			.on('failed', (job, err) => webhookLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job.data.to}`))
-			.on('error', (job: any, err: Error) => webhookLogger.error(`error ${err}`, { job, e: renderError(err) }))
-			.on('stalled', (job) => webhookLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`));
+			.on('failed', (job, err) => webhookLogger.warn(`failed(${err}) ${getJobInfo(job)} to=${job ? job.data.to : '-'}`))
+			.on('error', (err: Error) => webhookLogger.error(`error ${err}`, { e: renderError(err) }))
+			.on('stalled', (jobId) => webhookLogger.warn(`stalled id=${jobId}`));
+		//#endregion
 
-		this.queueService.systemQueue.add('tickCharts', {
+		//#region relationship
+		this.relationshipQueueWorker = new Bull.Worker(QUEUE.RELATIONSHIP, (job) => {
+			switch (job.name) {
+				case 'follow': return this.relationshipProcessorService.processFollow(job);
+				case 'unfollow': return this.relationshipProcessorService.processUnfollow(job);
+				case 'block': return this.relationshipProcessorService.processBlock(job);
+				case 'unblock': return this.relationshipProcessorService.processUnblock(job);
+				default: throw new Error(`unrecognized job type ${job.name} for relationship`);
+			}
 		}, {
-			repeat: { cron: '55 * * * *' },
-			removeOnComplete: true,
+			...baseQueueOptions(this.config, QUEUE.RELATIONSHIP),
+			autorun: false,
+			concurrency: this.config.relashionshipJobConcurrency ?? 16,
+			limiter: {
+				max: this.config.relashionshipJobPerSec ?? 64,
+				duration: 1000,
+			},
 		});
 
-		this.queueService.systemQueue.add('resyncCharts', {
-		}, {
-			repeat: { cron: '0 0 * * *' },
-			removeOnComplete: true,
-		});
-
-		this.queueService.systemQueue.add('cleanCharts', {
-		}, {
-			repeat: { cron: '0 0 * * *' },
-			removeOnComplete: true,
-		});
-
-		this.queueService.systemQueue.add('aggregateRetention', {
-		}, {
-			repeat: { cron: '0 0 * * *' },
-			removeOnComplete: true,
-		});
-
-		this.queueService.systemQueue.add('clean', {
-		}, {
-			repeat: { cron: '0 0 * * *' },
-			removeOnComplete: true,
-		});
-
-		this.queueService.systemQueue.add('checkExpiredMutings', {
-		}, {
-			repeat: { cron: '*/5 * * * *' },
-			removeOnComplete: true,
-		});
-
-		this.queueService.deliverQueue.process(this.config.deliverJobConcurrency ?? 128, (job) => this.deliverProcessorService.process(job));
-		this.queueService.inboxQueue.process(this.config.inboxJobConcurrency ?? 16, (job) => this.inboxProcessorService.process(job));
-		this.queueService.endedPollNotificationQueue.process((job, done) => this.endedPollNotificationProcessorService.process(job, done));
-		this.queueService.webhookDeliverQueue.process(64, (job) => this.webhookDeliverProcessorService.process(job));
-
-		this.queueService.dbQueue.process('deleteDriveFiles', (job, done) => this.deleteDriveFilesProcessorService.process(job, done));
-		this.queueService.dbQueue.process('exportCustomEmojis', (job, done) => this.exportCustomEmojisProcessorService.process(job, done));
-		this.queueService.dbQueue.process('exportNotes', (job, done) => this.exportNotesProcessorService.process(job, done));
-		this.queueService.dbQueue.process('exportFavorites', (job, done) => this.exportFavoritesProcessorService.process(job, done));
-		this.queueService.dbQueue.process('exportFollowing', (job, done) => this.exportFollowingProcessorService.process(job, done));
-		this.queueService.dbQueue.process('exportMuting', (job, done) => this.exportMutingProcessorService.process(job, done));
-		this.queueService.dbQueue.process('exportBlocking', (job, done) => this.exportBlockingProcessorService.process(job, done));
-		this.queueService.dbQueue.process('exportUserLists', (job, done) => this.exportUserListsProcessorService.process(job, done));
-		this.queueService.dbQueue.process('exportAntennas', (job, done) => this.exportAntennasProcessorService.process(job, done));
-		this.queueService.dbQueue.process('importFollowing', (job, done) => this.importFollowingProcessorService.process(job, done));
-		this.queueService.dbQueue.process('importFollowingToDb', (job) => this.importFollowingProcessorService.processDb(job));
-		this.queueService.dbQueue.process('importMuting', (job, done) => this.importMutingProcessorService.process(job, done));
-		this.queueService.dbQueue.process('importBlocking', (job, done) => this.importBlockingProcessorService.process(job, done));
-		this.queueService.dbQueue.process('importBlockingToDb', (job) => this.importBlockingProcessorService.processDb(job));
-		this.queueService.dbQueue.process('importUserLists', (job, done) => this.importUserListsProcessorService.process(job, done));
-		this.queueService.dbQueue.process('importCustomEmojis', (job, done) => this.importCustomEmojisProcessorService.process(job, done));
-		this.queueService.dbQueue.process('importAntennas', (job, done) => this.importAntennasProcessorService.process(job, done));
-		this.queueService.dbQueue.process('deleteAccount', (job) => this.deleteAccountProcessorService.process(job));
-
-		this.queueService.objectStorageQueue.process('deleteFile', 16, (job) => this.deleteFileProcessorService.process(job));
-		this.queueService.objectStorageQueue.process('cleanRemoteFiles', 16, (job, done) => this.cleanRemoteFilesProcessorService.process(job, done));
+		const relationshipLogger = this.logger.createSubLogger('relationship');
 	
-		{
-			const maxJobs = this.config.relashionshipJobConcurrency ?? 16;
-			this.queueService.relationshipQueue.process('follow', maxJobs, (job) => this.relationshipProcessorService.processFollow(job));
-			this.queueService.relationshipQueue.process('unfollow', maxJobs, (job) => this.relationshipProcessorService.processUnfollow(job));
-			this.queueService.relationshipQueue.process('block', maxJobs, (job) => this.relationshipProcessorService.processBlock(job));
-			this.queueService.relationshipQueue.process('unblock', maxJobs, (job) => this.relationshipProcessorService.processUnblock(job));
-		}
+		this.relationshipQueueWorker
+			.on('active', (job) => relationshipLogger.debug(`active id=${job.id}`))
+			.on('completed', (job, result) => relationshipLogger.debug(`completed(${result}) id=${job.id}`))
+			.on('failed', (job, err) => relationshipLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
+			.on('error', (err: Error) => relationshipLogger.error(`error ${err}`, { e: renderError(err) }))
+			.on('stalled', (jobId) => relationshipLogger.warn(`stalled id=${jobId}`));
+		//#endregion
 
-		this.queueService.systemQueue.process('tickCharts', (job, done) => this.tickChartsProcessorService.process(job, done));
-		this.queueService.systemQueue.process('resyncCharts', (job, done) => this.resyncChartsProcessorService.process(job, done));
-		this.queueService.systemQueue.process('cleanCharts', (job, done) => this.cleanChartsProcessorService.process(job, done));
-		this.queueService.systemQueue.process('aggregateRetention', (job, done) => this.aggregateRetentionProcessorService.process(job, done));
-		this.queueService.systemQueue.process('checkExpiredMutings', (job, done) => this.checkExpiredMutingsProcessorService.process(job, done));
-		this.queueService.systemQueue.process('clean', (job, done) => this.cleanProcessorService.process(job, done));
+		//#region object storage
+		this.objectStorageQueueWorker = new Bull.Worker(QUEUE.OBJECT_STORAGE, (job) => {
+			switch (job.name) {
+				case 'deleteFile': return this.deleteFileProcessorService.process(job);
+				case 'cleanRemoteFiles': return this.cleanRemoteFilesProcessorService.process(job);
+				default: throw new Error(`unrecognized job type ${job.name} for objectStorage`);
+			}
+		}, {
+			...baseQueueOptions(this.config, QUEUE.OBJECT_STORAGE),
+			autorun: false,
+			concurrency: 16,
+		});
+
+		const objectStorageLogger = this.logger.createSubLogger('objectStorage');
+
+		this.objectStorageQueueWorker
+			.on('active', (job) => objectStorageLogger.debug(`active id=${job.id}`))
+			.on('completed', (job, result) => objectStorageLogger.debug(`completed(${result}) id=${job.id}`))
+			.on('failed', (job, err) => objectStorageLogger.warn(`failed(${err}) id=${job ? job.id : '-'}`, { job, e: renderError(err) }))
+			.on('error', (err: Error) => objectStorageLogger.error(`error ${err}`, { e: renderError(err) }))
+			.on('stalled', (jobId) => objectStorageLogger.warn(`stalled id=${jobId}`));
+		//#endregion
+
+		//#region ended poll notification
+		this.endedPollNotificationQueueWorker = new Bull.Worker(QUEUE.ENDED_POLL_NOTIFICATION, (job) => this.endedPollNotificationProcessorService.process(job), {
+			...baseQueueOptions(this.config, QUEUE.ENDED_POLL_NOTIFICATION),
+			autorun: false,
+		});
+		//#endregion
+	}
+
+	@bindThis
+	public async start(): Promise<void> {
+		await Promise.all([
+			this.systemQueueWorker.run(),
+			this.dbQueueWorker.run(),
+			this.deliverQueueWorker.run(),
+			this.inboxQueueWorker.run(),
+			this.webhookDeliverQueueWorker.run(),
+			this.relationshipQueueWorker.run(),
+			this.objectStorageQueueWorker.run(),
+			this.endedPollNotificationQueueWorker.run(),
+		]);
+	}
+
+	@bindThis
+	public async stop(): Promise<void> {
+		await Promise.all([
+			this.systemQueueWorker.close(),
+			this.dbQueueWorker.close(),
+			this.deliverQueueWorker.close(),
+			this.inboxQueueWorker.close(),
+			this.webhookDeliverQueueWorker.close(),
+			this.relationshipQueueWorker.close(),
+			this.objectStorageQueueWorker.close(),
+			this.endedPollNotificationQueueWorker.close(),
+		]);
+	}
+
+	@bindThis
+	public async onApplicationShutdown(signal?: string | undefined): Promise<void> {
+		await this.stop();
 	}
 }
diff --git a/packages/backend/src/queue/const.ts b/packages/backend/src/queue/const.ts
new file mode 100644
index 000000000..d240fe70e
--- /dev/null
+++ b/packages/backend/src/queue/const.ts
@@ -0,0 +1,26 @@
+import { Config } from '@/config.js';
+import type * as Bull from 'bullmq';
+
+export const QUEUE = {
+	DELIVER: 'deliver',
+	INBOX: 'inbox',
+	SYSTEM: 'system',
+	ENDED_POLL_NOTIFICATION: 'endedPollNotification',
+	DB: 'db',
+	RELATIONSHIP: 'relationship',
+	OBJECT_STORAGE: 'objectStorage',
+	WEBHOOK_DELIVER: 'webhookDeliver',
+};
+
+export function baseQueueOptions(config: Config, queueName: typeof QUEUE[keyof typeof QUEUE]): Bull.QueueOptions {
+	return {
+		connection: {
+			port: config.redisForJobQueue.port,
+			host: config.redisForJobQueue.host,
+			family: config.redisForJobQueue.family == null ? 0 : config.redisForJobQueue.family,
+			password: config.redisForJobQueue.pass,
+			db: config.redisForJobQueue.db ?? 0,
+		},
+		prefix: config.redisForJobQueue.prefix ? `${config.redisForJobQueue.prefix}:queue:${queueName}` : `queue:${queueName}`,
+	};
+}
diff --git a/packages/backend/src/queue/get-job-info.ts b/packages/backend/src/queue/get-job-info.ts
deleted file mode 100644
index d33e349c3..000000000
--- a/packages/backend/src/queue/get-job-info.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import Bull from 'bull';
-
-export function getJobInfo(job: Bull.Job, increment = false) {
-	const age = Date.now() - job.timestamp;
-
-	const formated = age > 60000 ? `${Math.floor(age / 1000 / 60)}m`
-		: age > 10000 ? `${Math.floor(age / 1000)}s`
-		: `${age}ms`;
-
-	// onActiveとかonCompletedのattemptsMadeがなぜか0始まりなのでインクリメントする
-	const currentAttempts = job.attemptsMade + (increment ? 1 : 0);
-	const maxAttempts = job.opts ? job.opts.attempts : 0;
-
-	return `id=${job.id} attempts=${currentAttempts}/${maxAttempts} age=${formated}`;
-}
diff --git a/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts b/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts
index e2720b4fe..600ce0828 100644
--- a/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts
+++ b/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts
@@ -9,7 +9,7 @@ import { deepClone } from '@/misc/clone.js';
 import { IdService } from '@/core/IdService.js';
 import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
+import type * as Bull from 'bullmq';
 
 @Injectable()
 export class AggregateRetentionProcessorService {
@@ -32,7 +32,7 @@ export class AggregateRetentionProcessorService {
 	}
 
 	@bindThis
-	public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
+	public async process(): Promise<void> {
 		this.logger.info('Aggregating retention...');
 
 		const now = new Date();
@@ -62,7 +62,6 @@ export class AggregateRetentionProcessorService {
 		} catch (err) {
 			if (isDuplicateKeyValueError(err)) {
 				this.logger.succ('Skip because it has already been processed by another worker.');
-				done();
 				return;
 			}
 			throw err;
@@ -88,6 +87,5 @@ export class AggregateRetentionProcessorService {
 		}
 
 		this.logger.succ('Retention aggregated.');
-		done();
 	}
 }
diff --git a/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts b/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts
index 2476d71a5..c4ee212ba 100644
--- a/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts
+++ b/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts
@@ -7,7 +7,7 @@ import type Logger from '@/logger.js';
 import { bindThis } from '@/decorators.js';
 import { UserMutingService } from '@/core/UserMutingService.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
+import type * as Bull from 'bullmq';
 
 @Injectable()
 export class CheckExpiredMutingsProcessorService {
@@ -27,7 +27,7 @@ export class CheckExpiredMutingsProcessorService {
 	}
 
 	@bindThis
-	public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
+	public async process(): Promise<void> {
 		this.logger.info('Checking expired mutings...');
 
 		const expired = await this.mutingsRepository.createQueryBuilder('muting')
@@ -41,6 +41,5 @@ export class CheckExpiredMutingsProcessorService {
 		}
 
 		this.logger.succ('All expired mutings checked.');
-		done();
 	}
 }
diff --git a/packages/backend/src/queue/processors/CleanChartsProcessorService.ts b/packages/backend/src/queue/processors/CleanChartsProcessorService.ts
index b45816704..22d7c1b4f 100644
--- a/packages/backend/src/queue/processors/CleanChartsProcessorService.ts
+++ b/packages/backend/src/queue/processors/CleanChartsProcessorService.ts
@@ -16,7 +16,7 @@ import PerUserDriveChart from '@/core/chart/charts/per-user-drive.js';
 import ApRequestChart from '@/core/chart/charts/ap-request.js';
 import { bindThis } from '@/decorators.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
+import type * as Bull from 'bullmq';
 
 @Injectable()
 export class CleanChartsProcessorService {
@@ -45,7 +45,7 @@ export class CleanChartsProcessorService {
 	}
 
 	@bindThis
-	public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
+	public async process(): Promise<void> {
 		this.logger.info('Clean charts...');
 
 		await Promise.all([
@@ -64,6 +64,5 @@ export class CleanChartsProcessorService {
 		]);
 
 		this.logger.succ('All charts successfully cleaned.');
-		done();
 	}
 }
diff --git a/packages/backend/src/queue/processors/CleanProcessorService.ts b/packages/backend/src/queue/processors/CleanProcessorService.ts
index 1936e8df2..cefa6da5e 100644
--- a/packages/backend/src/queue/processors/CleanProcessorService.ts
+++ b/packages/backend/src/queue/processors/CleanProcessorService.ts
@@ -7,7 +7,7 @@ import type Logger from '@/logger.js';
 import { bindThis } from '@/decorators.js';
 import { IdService } from '@/core/IdService.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
+import type * as Bull from 'bullmq';
 
 @Injectable()
 export class CleanProcessorService {
@@ -36,7 +36,7 @@ export class CleanProcessorService {
 	}
 
 	@bindThis
-	public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
+	public async process(): Promise<void> {
 		this.logger.info('Cleaning...');
 
 		this.userIpsRepository.delete({
@@ -72,6 +72,5 @@ export class CleanProcessorService {
 		}
 
 		this.logger.succ('Cleaned.');
-		done();
 	}
 }
diff --git a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts
index 5a33c2718..c54bf59ae 100644
--- a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts
+++ b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts
@@ -5,9 +5,9 @@ import type { DriveFilesRepository } from '@/models/index.js';
 import type { Config } from '@/config.js';
 import type Logger from '@/logger.js';
 import { DriveService } from '@/core/DriveService.js';
-import { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
 import { bindThis } from '@/decorators.js';
+import { QueueLoggerService } from '../QueueLoggerService.js';
+import type * as Bull from 'bullmq';
 
 @Injectable()
 export class CleanRemoteFilesProcessorService {
@@ -27,7 +27,7 @@ export class CleanRemoteFilesProcessorService {
 	}
 
 	@bindThis
-	public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
+	public async process(job: Bull.Job<Record<string, unknown>>): Promise<void> {
 		this.logger.info('Deleting cached remote files...');
 
 		let deletedCount = 0;
@@ -47,7 +47,7 @@ export class CleanRemoteFilesProcessorService {
 			});
 
 			if (files.length === 0) {
-				job.progress(100);
+				job.updateProgress(100);
 				break;
 			}
 
@@ -62,10 +62,9 @@ export class CleanRemoteFilesProcessorService {
 				isLink: false,
 			});
 
-			job.progress(deletedCount / total);
+			job.updateProgress(deletedCount / total);
 		}
 
 		this.logger.succ('All cached remote files has been deleted.');
-		done();
 	}
 }
diff --git a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts
index e36a78de6..39dd801af 100644
--- a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts
+++ b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts
@@ -8,10 +8,10 @@ import { DriveService } from '@/core/DriveService.js';
 import type { DriveFile } from '@/models/entities/DriveFile.js';
 import type { Note } from '@/models/entities/Note.js';
 import { EmailService } from '@/core/EmailService.js';
-import { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
-import type { DbUserDeleteJobData } from '../types.js';
 import { bindThis } from '@/decorators.js';
+import { QueueLoggerService } from '../QueueLoggerService.js';
+import type * as Bull from 'bullmq';
+import type { DbUserDeleteJobData } from '../types.js';
 
 @Injectable()
 export class DeleteAccountProcessorService {
diff --git a/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts b/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts
index 604497cf5..6772c5dc7 100644
--- a/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts
+++ b/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts
@@ -5,10 +5,10 @@ import type { UsersRepository, DriveFilesRepository } from '@/models/index.js';
 import type { Config } from '@/config.js';
 import type Logger from '@/logger.js';
 import { DriveService } from '@/core/DriveService.js';
-import { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
-import type { DbJobDataWithUser } from '../types.js';
 import { bindThis } from '@/decorators.js';
+import { QueueLoggerService } from '../QueueLoggerService.js';
+import type * as Bull from 'bullmq';
+import type { DbJobDataWithUser } from '../types.js';
 
 @Injectable()
 export class DeleteDriveFilesProcessorService {
@@ -31,12 +31,11 @@ export class DeleteDriveFilesProcessorService {
 	}
 
 	@bindThis
-	public async process(job: Bull.Job<DbJobDataWithUser>, done: () => void): Promise<void> {
+	public async process(job: Bull.Job<DbJobDataWithUser>): Promise<void> {
 		this.logger.info(`Deleting drive files of ${job.data.user.id} ...`);
 
 		const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
 		if (user == null) {
-			done();
 			return;
 		}
 
@@ -56,7 +55,7 @@ export class DeleteDriveFilesProcessorService {
 			});
 
 			if (files.length === 0) {
-				job.progress(100);
+				job.updateProgress(100);
 				break;
 			}
 
@@ -71,10 +70,9 @@ export class DeleteDriveFilesProcessorService {
 				userId: user.id,
 			});
 
-			job.progress(deletedCount / total);
+			job.updateProgress(deletedCount / total);
 		}
 
 		this.logger.succ(`All drive files (${deletedCount}) of ${user.id} has been deleted.`);
-		done();
 	}
 }
diff --git a/packages/backend/src/queue/processors/DeleteFileProcessorService.ts b/packages/backend/src/queue/processors/DeleteFileProcessorService.ts
index 2fb2f56f8..edf87bd92 100644
--- a/packages/backend/src/queue/processors/DeleteFileProcessorService.ts
+++ b/packages/backend/src/queue/processors/DeleteFileProcessorService.ts
@@ -3,10 +3,10 @@ import { DI } from '@/di-symbols.js';
 import type { Config } from '@/config.js';
 import type Logger from '@/logger.js';
 import { DriveService } from '@/core/DriveService.js';
-import { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
-import type { ObjectStorageFileJobData } from '../types.js';
 import { bindThis } from '@/decorators.js';
+import { QueueLoggerService } from '../QueueLoggerService.js';
+import type * as Bull from 'bullmq';
+import type { ObjectStorageFileJobData } from '../types.js';
 
 @Injectable()
 export class DeleteFileProcessorService {
diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts
index f293bd4d7..406e9df85 100644
--- a/packages/backend/src/queue/processors/DeliverProcessorService.ts
+++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts
@@ -1,4 +1,5 @@
 import { Inject, Injectable } from '@nestjs/common';
+import * as Bull from 'bullmq';
 import { DI } from '@/di-symbols.js';
 import type { DriveFilesRepository, InstancesRepository } from '@/models/index.js';
 import type { Config } from '@/config.js';
@@ -16,7 +17,6 @@ import { StatusError } from '@/misc/status-error.js';
 import { UtilityService } from '@/core/UtilityService.js';
 import { bindThis } from '@/decorators.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
 import type { DeliverJobData } from '../types.js';
 
 @Injectable()
@@ -121,15 +121,13 @@ export class DeliverProcessorService {
 								isSuspended: true,
 							});
 						});
-						return `${host} is gone`;
+						throw new Bull.UnrecoverableError(`${host} is gone`);
 					}
-					// HTTPステータスコード4xxはクライアントエラーであり、それはつまり
-					// 何回再送しても成功することはないということなのでエラーにはしないでおく
-					return `${res.statusCode} ${res.statusMessage}`;
+					throw new Bull.UnrecoverableError(`${res.statusCode} ${res.statusMessage}`);
 				}
 
 				// 5xx etc.
-				throw `${res.statusCode} ${res.statusMessage}`;
+				throw new Error(`${res.statusCode} ${res.statusMessage}`);
 			} else {
 				// DNS error, socket error, timeout ...
 				throw res;
diff --git a/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts b/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts
index 501ed4090..21501592f 100644
--- a/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts
+++ b/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts
@@ -6,7 +6,7 @@ import type Logger from '@/logger.js';
 import { NotificationService } from '@/core/NotificationService.js';
 import { bindThis } from '@/decorators.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
+import type * as Bull from 'bullmq';
 import type { EndedPollNotificationJobData } from '../types.js';
 
 @Injectable()
@@ -30,10 +30,9 @@ export class EndedPollNotificationProcessorService {
 	}
 
 	@bindThis
-	public async process(job: Bull.Job<EndedPollNotificationJobData>, done: () => void): Promise<void> {
+	public async process(job: Bull.Job<EndedPollNotificationJobData>): Promise<void> {
 		const note = await this.notesRepository.findOneBy({ id: job.data.noteId });
 		if (note == null || !note.hasPoll) {
-			done();
 			return;
 		}
 
@@ -51,7 +50,5 @@ export class EndedPollNotificationProcessorService {
 				noteId: note.id,
 			});
 		}
-
-		done();
 	}
 }
diff --git a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts
index 894903e79..ac52325c8 100644
--- a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts
@@ -12,7 +12,7 @@ import { createTemp } from '@/misc/create-temp.js';
 import { UtilityService } from '@/core/UtilityService.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
 import type { DBExportAntennasData } from '../types.js';
-import type Bull from 'bull';
+import type * as Bull from 'bullmq';
 
 @Injectable()
 export class ExportAntennasProcessorService {
@@ -39,10 +39,9 @@ export class ExportAntennasProcessorService {
 	}
 
 	@bindThis
-	public async process(job: Bull.Job<DBExportAntennasData>, done: () => void): Promise<void> {
+	public async process(job: Bull.Job<DBExportAntennasData>): Promise<void> {
 		const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
 		if (user == null) {
-			done();
 			return;
 		}
 		const [path, cleanup] = await createTemp();
@@ -96,7 +95,6 @@ export class ExportAntennasProcessorService {
 			this.logger.succ('Exported to: ' + driveFile.id);
 		} finally {
 			cleanup();
-			done();
 		}
 	}
 }
diff --git a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts
index c7b54070d..eb758e162 100644
--- a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts
@@ -9,10 +9,10 @@ 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 { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
-import type { DbJobDataWithUser } from '../types.js';
 import { bindThis } from '@/decorators.js';
+import { QueueLoggerService } from '../QueueLoggerService.js';
+import type * as Bull from 'bullmq';
+import type { DbJobDataWithUser } from '../types.js';
 
 @Injectable()
 export class ExportBlockingProcessorService {
@@ -36,12 +36,11 @@ export class ExportBlockingProcessorService {
 	}
 
 	@bindThis
-	public async process(job: Bull.Job<DbJobDataWithUser>, done: () => void): Promise<void> {
+	public async process(job: Bull.Job<DbJobDataWithUser>): Promise<void> {
 		this.logger.info(`Exporting blocking of ${job.data.user.id} ...`);
 
 		const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
 		if (user == null) {
-			done();
 			return;
 		}
 
@@ -69,7 +68,7 @@ export class ExportBlockingProcessorService {
 				});
 
 				if (blockings.length === 0) {
-					job.progress(100);
+					job.updateProgress(100);
 					break;
 				}
 
@@ -99,7 +98,7 @@ export class ExportBlockingProcessorService {
 					blockerId: user.id,
 				});
 
-				job.progress(exportedCount / total);
+				job.updateProgress(exportedCount / total);
 			}
 
 			stream.end();
@@ -112,7 +111,5 @@ export class ExportBlockingProcessorService {
 		} finally {
 			cleanup();
 		}
-
-		done();
 	}
 }
diff --git a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts
index b50f373ef..3203d9f3e 100644
--- a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts
@@ -13,7 +13,7 @@ import { createTemp, createTempDir } from '@/misc/create-temp.js';
 import { DownloadService } from '@/core/DownloadService.js';
 import { bindThis } from '@/decorators.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
+import type * as Bull from 'bullmq';
 
 @Injectable()
 export class ExportCustomEmojisProcessorService {
@@ -37,12 +37,11 @@ export class ExportCustomEmojisProcessorService {
 	}
 
 	@bindThis
-	public async process(job: Bull.Job, done: () => void): Promise<void> {
+	public async process(job: Bull.Job): Promise<void> {
 		this.logger.info('Exporting custom emojis ...');
 
 		const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
 		if (user == null) {
-			done();
 			return;
 		}
 
@@ -117,24 +116,26 @@ export class ExportCustomEmojisProcessorService {
 		metaStream.end();
 
 		// Create archive
-		const [archivePath, archiveCleanup] = await createTemp();
-		const archiveStream = fs.createWriteStream(archivePath);
-		const archive = archiver('zip', {
-			zlib: { level: 0 },
-		});
-		archiveStream.on('close', async () => {
-			this.logger.succ(`Exported to: ${archivePath}`);
+		await new Promise<void>(async (resolve) => {
+			const [archivePath, archiveCleanup] = await createTemp();
+			const archiveStream = fs.createWriteStream(archivePath);
+			const archive = archiver('zip', {
+				zlib: { level: 0 },
+			});
+			archiveStream.on('close', async () => {
+				this.logger.succ(`Exported to: ${archivePath}`);
 
-			const fileName = 'custom-emojis-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.zip';
-			const driveFile = await this.driveService.addFile({ user, path: archivePath, name: fileName, force: true });
+				const fileName = 'custom-emojis-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.zip';
+				const driveFile = await this.driveService.addFile({ user, path: archivePath, name: fileName, force: true });
 
-			this.logger.succ(`Exported to: ${driveFile.id}`);
-			cleanup();
-			archiveCleanup();
-			done();
+				this.logger.succ(`Exported to: ${driveFile.id}`);
+				cleanup();
+				archiveCleanup();
+				resolve();
+			});
+			archive.pipe(archiveStream);
+			archive.directory(path, false);
+			archive.finalize();
 		});
-		archive.pipe(archiveStream);
-		archive.directory(path, false);
-		archive.finalize();
 	}
 }
diff --git a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts
index f2f2383a8..76c38a6b8 100644
--- a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts
@@ -12,7 +12,7 @@ import type { Poll } from '@/models/entities/Poll.js';
 import type { Note } from '@/models/entities/Note.js';
 import { bindThis } from '@/decorators.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
+import type * as Bull from 'bullmq';
 import type { DbJobDataWithUser } from '../types.js';
 
 @Injectable()
@@ -42,12 +42,11 @@ export class ExportFavoritesProcessorService {
 	}
 
 	@bindThis
-	public async process(job: Bull.Job<DbJobDataWithUser>, done: () => void): Promise<void> {
+	public async process(job: Bull.Job<DbJobDataWithUser>): Promise<void> {
 		this.logger.info(`Exporting favorites of ${job.data.user.id} ...`);
 
 		const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
 		if (user == null) {
-			done();
 			return;
 		}
 
@@ -91,7 +90,7 @@ export class ExportFavoritesProcessorService {
 				}) as (NoteFavorite & { note: Note & { user: User } })[];
 
 				if (favorites.length === 0) {
-					job.progress(100);
+					job.updateProgress(100);
 					break;
 				}
 
@@ -112,7 +111,7 @@ export class ExportFavoritesProcessorService {
 					userId: user.id,
 				});
 
-				job.progress(exportedFavoritesCount / total);
+				job.updateProgress(exportedFavoritesCount / total);
 			}
 
 			await write(']');
@@ -127,8 +126,6 @@ export class ExportFavoritesProcessorService {
 		} finally {
 			cleanup();
 		}
-
-		done();
 	}
 }
 
diff --git a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts
index fa9c1ac1e..8726cb140 100644
--- a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts
@@ -10,10 +10,10 @@ import { DriveService } from '@/core/DriveService.js';
 import { createTemp } from '@/misc/create-temp.js';
 import type { Following } from '@/models/entities/Following.js';
 import { UtilityService } from '@/core/UtilityService.js';
-import { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
-import type { DbExportFollowingData } from '../types.js';
 import { bindThis } from '@/decorators.js';
+import { QueueLoggerService } from '../QueueLoggerService.js';
+import type * as Bull from 'bullmq';
+import type { DbExportFollowingData } from '../types.js';
 
 @Injectable()
 export class ExportFollowingProcessorService {
@@ -40,12 +40,11 @@ export class ExportFollowingProcessorService {
 	}
 
 	@bindThis
-	public async process(job: Bull.Job<DbExportFollowingData>, done: () => void): Promise<void> {
+	public async process(job: Bull.Job<DbExportFollowingData>): Promise<void> {
 		this.logger.info(`Exporting following of ${job.data.user.id} ...`);
 
 		const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
 		if (user == null) {
-			done();
 			return;
 		}
 
@@ -116,7 +115,5 @@ export class ExportFollowingProcessorService {
 		} finally {
 			cleanup();
 		}
-
-		done();
 	}
 }
diff --git a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts
index b14bf5f5b..0f11a9e84 100644
--- a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts
@@ -9,10 +9,10 @@ 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 { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
-import type { DbJobDataWithUser } from '../types.js';
 import { bindThis } from '@/decorators.js';
+import { QueueLoggerService } from '../QueueLoggerService.js';
+import type * as Bull from 'bullmq';
+import type { DbJobDataWithUser } from '../types.js';
 
 @Injectable()
 export class ExportMutingProcessorService {
@@ -39,12 +39,11 @@ export class ExportMutingProcessorService {
 	}
 
 	@bindThis
-	public async process(job: Bull.Job<DbJobDataWithUser>, done: () => void): Promise<void> {
+	public async process(job: Bull.Job<DbJobDataWithUser>): Promise<void> {
 		this.logger.info(`Exporting muting of ${job.data.user.id} ...`);
 
 		const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
 		if (user == null) {
-			done();
 			return;
 		}
 
@@ -73,7 +72,7 @@ export class ExportMutingProcessorService {
 				});
 
 				if (mutes.length === 0) {
-					job.progress(100);
+					job.updateProgress(100);
 					break;
 				}
 
@@ -103,7 +102,7 @@ export class ExportMutingProcessorService {
 					muterId: user.id,
 				});
 
-				job.progress(exportedCount / total);
+				job.updateProgress(exportedCount / total);
 			}
 
 			stream.end();
@@ -116,7 +115,5 @@ export class ExportMutingProcessorService {
 		} finally {
 			cleanup();
 		}
-
-		done();
 	}
 }
diff --git a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts
index e4f12ad10..24fb33188 100644
--- a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts
@@ -12,7 +12,7 @@ import type { Poll } from '@/models/entities/Poll.js';
 import type { Note } from '@/models/entities/Note.js';
 import { bindThis } from '@/decorators.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
+import type * as Bull from 'bullmq';
 import type { DbJobDataWithUser } from '../types.js';
 
 @Injectable()
@@ -39,12 +39,11 @@ export class ExportNotesProcessorService {
 	}
 
 	@bindThis
-	public async process(job: Bull.Job<DbJobDataWithUser>, done: () => void): Promise<void> {
+	public async process(job: Bull.Job<DbJobDataWithUser>): Promise<void> {
 		this.logger.info(`Exporting notes of ${job.data.user.id} ...`);
 
 		const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
 		if (user == null) {
-			done();
 			return;
 		}
 
@@ -87,7 +86,7 @@ export class ExportNotesProcessorService {
 				}) as Note[];
 
 				if (notes.length === 0) {
-					job.progress(100);
+					job.updateProgress(100);
 					break;
 				}
 
@@ -108,7 +107,7 @@ export class ExportNotesProcessorService {
 					userId: user.id,
 				});
 
-				job.progress(exportedNotesCount / total);
+				job.updateProgress(exportedNotesCount / total);
 			}
 
 			await write(']');
@@ -123,8 +122,6 @@ export class ExportNotesProcessorService {
 		} finally {
 			cleanup();
 		}
-
-		done();
 	}
 }
 
diff --git a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts
index 54bde4404..ec6335805 100644
--- a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts
@@ -9,10 +9,10 @@ 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 { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
-import type { DbJobDataWithUser } from '../types.js';
 import { bindThis } from '@/decorators.js';
+import { QueueLoggerService } from '../QueueLoggerService.js';
+import type * as Bull from 'bullmq';
+import type { DbJobDataWithUser } from '../types.js';
 
 @Injectable()
 export class ExportUserListsProcessorService {
@@ -39,12 +39,11 @@ export class ExportUserListsProcessorService {
 	}
 
 	@bindThis
-	public async process(job: Bull.Job<DbJobDataWithUser>, done: () => void): Promise<void> {
+	public async process(job: Bull.Job<DbJobDataWithUser>): Promise<void> {
 		this.logger.info(`Exporting user lists of ${job.data.user.id} ...`);
 
 		const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
 		if (user == null) {
-			done();
 			return;
 		}
 
@@ -92,7 +91,5 @@ export class ExportUserListsProcessorService {
 		} finally {
 			cleanup();
 		}
-
-		done();
 	}
 }
diff --git a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts
index d06131b8c..575cad69d 100644
--- a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts
+++ b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts
@@ -8,7 +8,7 @@ import { DI } from '@/di-symbols.js';
 import { bindThis } from '@/decorators.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
 import { DBAntennaImportJobData } from '../types.js';
-import type Bull from 'bull';
+import type * as Bull from 'bullmq';
 
 const validate = new Ajv().compile({
 	type: 'object',
@@ -59,7 +59,7 @@ export class ImportAntennasProcessorService {
 	}
 
 	@bindThis
-	public async process(job: Bull.Job<DBAntennaImportJobData>, done: () => void): Promise<void> {
+	public async process(job: Bull.Job<DBAntennaImportJobData>): Promise<void> {
 		const now = new Date();
 		try {
 			for (const antenna of job.data.antenna) {
@@ -89,8 +89,6 @@ export class ImportAntennasProcessorService {
 			}
 		} catch (err: any) {
 			this.logger.error(err);
-		} finally {
-			done();
 		}
 	}
 }
diff --git a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts
index 3f075b02d..2f1a9e5b0 100644
--- a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts
+++ b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts
@@ -7,11 +7,11 @@ import * as Acct from '@/misc/acct.js';
 import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
 import { DownloadService } from '@/core/DownloadService.js';
 import { UtilityService } from '@/core/UtilityService.js';
-import { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
-import type { DbUserImportJobData, DbUserImportToDbJobData } from '../types.js';
 import { bindThis } from '@/decorators.js';
 import { QueueService } from '@/core/QueueService.js';
+import { QueueLoggerService } from '../QueueLoggerService.js';
+import type * as Bull from 'bullmq';
+import type { DbUserImportJobData, DbUserImportToDbJobData } from '../types.js';
 
 @Injectable()
 export class ImportBlockingProcessorService {
@@ -34,12 +34,11 @@ export class ImportBlockingProcessorService {
 	}
 
 	@bindThis
-	public async process(job: Bull.Job<DbUserImportJobData>, done: () => void): Promise<void> {
+	public async process(job: Bull.Job<DbUserImportJobData>): Promise<void> {
 		this.logger.info(`Importing blocking of ${job.data.user.id} ...`);
 
 		const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
 		if (user == null) {
-			done();
 			return;
 		}
 
@@ -47,7 +46,6 @@ export class ImportBlockingProcessorService {
 			id: job.data.fileId,
 		});
 		if (file == null) {
-			done();
 			return;
 		}
 
@@ -56,7 +54,6 @@ export class ImportBlockingProcessorService {
 		this.queueService.createImportBlockingToDbJob({ id: user.id }, targets);
 
 		this.logger.succ('Import jobs created');
-		done();
 	}
 
 	@bindThis
@@ -85,7 +82,7 @@ export class ImportBlockingProcessorService {
 			}
 
 			if (target == null) {
-				throw `Unable to resolve user: @${username}@${host}`;
+				throw new Error(`Unable to resolve user: @${username}@${host}`);
 			}
 
 			// skip myself
diff --git a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts
index cf78d8330..d86256787 100644
--- a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts
+++ b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts
@@ -12,7 +12,7 @@ import { DriveService } from '@/core/DriveService.js';
 import { DownloadService } from '@/core/DownloadService.js';
 import { bindThis } from '@/decorators.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
+import type * as Bull from 'bullmq';
 import type { DbUserImportJobData } from '../types.js';
 
 // TODO: 名前衝突時の動作を選べるようにする
@@ -45,14 +45,13 @@ export class ImportCustomEmojisProcessorService {
 	}
 
 	@bindThis
-	public async process(job: Bull.Job<DbUserImportJobData>, done: () => void): Promise<void> {
+	public async process(job: Bull.Job<DbUserImportJobData>): Promise<void> {
 		this.logger.info('Importing custom emojis ...');
 
 		const file = await this.driveFilesRepository.findOneBy({
 			id: job.data.fileId,
 		});
 		if (file == null) {
-			done();
 			return;
 		}
 
@@ -107,13 +106,15 @@ export class ImportCustomEmojisProcessorService {
 					aliases: emojiInfo.aliases,
 					driveFile,
 					license: emojiInfo.license,
+					isSensitive: emojiInfo.isSensitive,
+					localOnly: emojiInfo.localOnly,
+					roleIdsThatCanBeUsedThisEmojiAsReaction: [],
 				});
 			}
 
 			cleanup();
 	
 			this.logger.succ('Imported');
-			done();
 		});
 		unzipStream.pipe(extractor);
 		this.logger.succ(`Unzipping to ${outputPath}`);
diff --git a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts
index aa5cf12c5..15bee9672 100644
--- a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts
+++ b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts
@@ -7,11 +7,11 @@ import * as Acct from '@/misc/acct.js';
 import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
 import { DownloadService } from '@/core/DownloadService.js';
 import { UtilityService } from '@/core/UtilityService.js';
-import { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
-import type { DbUserImportJobData, DbUserImportToDbJobData } from '../types.js';
 import { bindThis } from '@/decorators.js';
 import { QueueService } from '@/core/QueueService.js';
+import { QueueLoggerService } from '../QueueLoggerService.js';
+import type * as Bull from 'bullmq';
+import type { DbUserImportJobData, DbUserImportToDbJobData } from '../types.js';
 
 @Injectable()
 export class ImportFollowingProcessorService {
@@ -34,12 +34,11 @@ export class ImportFollowingProcessorService {
 	}
 
 	@bindThis
-	public async process(job: Bull.Job<DbUserImportJobData>, done: () => void): Promise<void> {
+	public async process(job: Bull.Job<DbUserImportJobData>): Promise<void> {
 		this.logger.info(`Importing following of ${job.data.user.id} ...`);
 
 		const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
 		if (user == null) {
-			done();
 			return;
 		}
 
@@ -47,7 +46,6 @@ export class ImportFollowingProcessorService {
 			id: job.data.fileId,
 		});
 		if (file == null) {
-			done();
 			return;
 		}
 
@@ -56,7 +54,6 @@ export class ImportFollowingProcessorService {
 		this.queueService.createImportFollowingToDbJob({ id: user.id }, targets);
 
 		this.logger.succ('Import jobs created');
-		done();
 	}
 
 	@bindThis
@@ -85,7 +82,7 @@ export class ImportFollowingProcessorService {
 			}
 
 			if (target == null) {
-				throw `Unable to resolve user: @${username}@${host}`;
+				throw new Error(`Unable to resolve user: @${username}@${host}`);
 			}
 
 			// skip myself
diff --git a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts
index 379994ee7..723935cd3 100644
--- a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts
+++ b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts
@@ -9,10 +9,10 @@ import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
 import { DownloadService } from '@/core/DownloadService.js';
 import { UserMutingService } from '@/core/UserMutingService.js';
 import { UtilityService } from '@/core/UtilityService.js';
-import { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
-import type { DbUserImportJobData } from '../types.js';
 import { bindThis } from '@/decorators.js';
+import { QueueLoggerService } from '../QueueLoggerService.js';
+import type * as Bull from 'bullmq';
+import type { DbUserImportJobData } from '../types.js';
 
 @Injectable()
 export class ImportMutingProcessorService {
@@ -38,12 +38,11 @@ export class ImportMutingProcessorService {
 	}
 
 	@bindThis
-	public async process(job: Bull.Job<DbUserImportJobData>, done: () => void): Promise<void> {
+	public async process(job: Bull.Job<DbUserImportJobData>): Promise<void> {
 		this.logger.info(`Importing muting of ${job.data.user.id} ...`);
 
 		const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
 		if (user == null) {
-			done();
 			return;
 		}
 
@@ -51,7 +50,6 @@ export class ImportMutingProcessorService {
 			id: job.data.fileId,
 		});
 		if (file == null) {
-			done();
 			return;
 		}
 
@@ -83,7 +81,7 @@ export class ImportMutingProcessorService {
 				}
 
 				if (target == null) {
-					throw `cannot resolve user: @${username}@${host}`;
+					throw new Error(`cannot resolve user: @${username}@${host}`);
 				}
 
 				// skip myself
@@ -98,6 +96,5 @@ export class ImportMutingProcessorService {
 		}
 
 		this.logger.succ('Imported');
-		done();
 	}
 }
diff --git a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts
index c42386341..824ee8157 100644
--- a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts
+++ b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts
@@ -12,7 +12,7 @@ import { IdService } from '@/core/IdService.js';
 import { UtilityService } from '@/core/UtilityService.js';
 import { bindThis } from '@/decorators.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
+import type * as Bull from 'bullmq';
 import type { DbUserImportJobData } from '../types.js';
 
 @Injectable()
@@ -46,12 +46,11 @@ export class ImportUserListsProcessorService {
 	}
 
 	@bindThis
-	public async process(job: Bull.Job<DbUserImportJobData>, done: () => void): Promise<void> {
+	public async process(job: Bull.Job<DbUserImportJobData>): Promise<void> {
 		this.logger.info(`Importing user lists of ${job.data.user.id} ...`);
 
 		const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
 		if (user == null) {
-			done();
 			return;
 		}
 
@@ -59,7 +58,6 @@ export class ImportUserListsProcessorService {
 			id: job.data.fileId,
 		});
 		if (file == null) {
-			done();
 			return;
 		}
 
@@ -109,6 +107,5 @@ export class ImportUserListsProcessorService {
 		}
 
 		this.logger.succ('Imported');
-		done();
 	}
 }
diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts
index ab8b1e9e2..ce1d7aaa1 100644
--- a/packages/backend/src/queue/processors/InboxProcessorService.ts
+++ b/packages/backend/src/queue/processors/InboxProcessorService.ts
@@ -1,8 +1,8 @@
 import { URL } from 'node:url';
 import { Inject, Injectable } from '@nestjs/common';
 import httpSignature from '@peertube/http-signature';
+import * as Bull from 'bullmq';
 import { DI } from '@/di-symbols.js';
-import type { InstancesRepository, DriveFilesRepository } from '@/models/index.js';
 import type { Config } from '@/config.js';
 import type Logger from '@/logger.js';
 import { MetaService } from '@/core/MetaService.js';
@@ -23,10 +23,8 @@ import { LdSignatureService } from '@/core/activitypub/LdSignatureService.js';
 import { ApInboxService } from '@/core/activitypub/ApInboxService.js';
 import { bindThis } from '@/decorators.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
 import type { InboxJobData } from '../types.js';
 
-// ユーザーのinboxにアクティビティが届いた時の処理
 @Injectable()
 export class InboxProcessorService {
 	private logger: Logger;
@@ -35,12 +33,6 @@ export class InboxProcessorService {
 		@Inject(DI.config)
 		private config: Config,
 
-		@Inject(DI.instancesRepository)
-		private instancesRepository: InstancesRepository,
-
-		@Inject(DI.driveFilesRepository)
-		private driveFilesRepository: DriveFilesRepository,
-
 		private utilityService: UtilityService,
 		private metaService: MetaService,
 		private apInboxService: ApInboxService,
@@ -93,24 +85,24 @@ export class InboxProcessorService {
 			try {
 				authUser = await this.apDbResolverService.getAuthUserFromApId(getApId(activity.actor));
 			} catch (err) {
-			// 対象が4xxならスキップ
+				// 対象が4xxならスキップ
 				if (err instanceof StatusError) {
 					if (err.isClientError) {
-						return `skip: Ignored deleted actors on both ends ${activity.actor} - ${err.statusCode}`;
+						throw new Bull.UnrecoverableError(`skip: Ignored deleted actors on both ends ${activity.actor} - ${err.statusCode}`);
 					}
-					throw `Error in actor ${activity.actor} - ${err.statusCode ?? err}`;
+					throw new Error(`Error in actor ${activity.actor} - ${err.statusCode ?? err}`);
 				}
 			}
 		}
 
 		// それでもわからなければ終了
 		if (authUser == null) {
-			return 'skip: failed to resolve user';
+			throw new Bull.UnrecoverableError('skip: failed to resolve user');
 		}
 
 		// publicKey がなくても終了
 		if (authUser.key == null) {
-			return 'skip: failed to resolve user publicKey';
+			throw new Bull.UnrecoverableError('skip: failed to resolve user publicKey');
 		}
 
 		// HTTP-Signatureの検証
@@ -118,10 +110,10 @@ export class InboxProcessorService {
 
 		// また、signatureのsignerは、activity.actorと一致する必要がある
 		if (!httpSignatureValidated || authUser.user.uri !== activity.actor) {
-		// 一致しなくても、でもLD-Signatureがありそうならそっちも見る
+			// 一致しなくても、でもLD-Signatureがありそうならそっちも見る
 			if (activity.signature) {
 				if (activity.signature.type !== 'RsaSignature2017') {
-					return `skip: unsupported LD-signature type ${activity.signature.type}`;
+					throw new Bull.UnrecoverableError(`skip: unsupported LD-signature type ${activity.signature.type}`);
 				}
 
 				// activity.signature.creator: https://example.oom/users/user#main-key
@@ -134,32 +126,32 @@ export class InboxProcessorService {
 				// keyIdからLD-Signatureのユーザーを取得
 				authUser = await this.apDbResolverService.getAuthUserFromKeyId(activity.signature.creator);
 				if (authUser == null) {
-					return 'skip: LD-Signatureのユーザーが取得できませんでした';
+					throw new Bull.UnrecoverableError('skip: LD-Signatureのユーザーが取得できませんでした');
 				}
 
 				if (authUser.key == null) {
-					return 'skip: LD-SignatureのユーザーはpublicKeyを持っていませんでした';
+					throw new Bull.UnrecoverableError('skip: LD-SignatureのユーザーはpublicKeyを持っていませんでした');
 				}
 
 				// LD-Signature検証
 				const ldSignature = this.ldSignatureService.use();
 				const verified = await ldSignature.verifyRsaSignature2017(activity, authUser.key.keyPem).catch(() => false);
 				if (!verified) {
-					return 'skip: LD-Signatureの検証に失敗しました';
+					throw new Bull.UnrecoverableError('skip: LD-Signatureの検証に失敗しました');
 				}
 
 				// もう一度actorチェック
 				if (authUser.user.uri !== activity.actor) {
-					return `skip: LD-Signature user(${authUser.user.uri}) !== activity.actor(${activity.actor})`;
+					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)) {
-					return `Blocked request: ${ldHost}`;
+					throw new Bull.UnrecoverableError(`Blocked request: ${ldHost}`);
 				}
 			} else {
-				return `skip: http-signature verification failed and no LD-Signature. keyId=${signature.keyId}`;
+				throw new Bull.UnrecoverableError(`skip: http-signature verification failed and no LD-Signature. keyId=${signature.keyId}`);
 			}
 		}
 
@@ -168,7 +160,7 @@ export class InboxProcessorService {
 			const signerHost = this.utilityService.extractDbHost(authUser.user.uri!);
 			const activityIdHost = this.utilityService.extractDbHost(activity.id);
 			if (signerHost !== activityIdHost) {
-				return `skip: signerHost(${signerHost}) !== activity.id host(${activityIdHost}`;
+				throw new Bull.UnrecoverableError(`skip: signerHost(${signerHost}) !== activity.id host(${activityIdHost}`);
 			}
 		}
 
diff --git a/packages/backend/src/queue/processors/RelationshipProcessorService.ts b/packages/backend/src/queue/processors/RelationshipProcessorService.ts
index ff454df45..722260d94 100644
--- a/packages/backend/src/queue/processors/RelationshipProcessorService.ts
+++ b/packages/backend/src/queue/processors/RelationshipProcessorService.ts
@@ -1,5 +1,5 @@
 import { Inject, Injectable } from '@nestjs/common';
-import type Bull from 'bull';
+import type * as Bull from 'bullmq';
 
 import { UserFollowingService } from '@/core/UserFollowingService.js';
 import { UserBlockingService } from '@/core/UserBlockingService.js';
diff --git a/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts b/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts
index e5840f3da..eab8e1e68 100644
--- a/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts
+++ b/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts
@@ -15,7 +15,7 @@ import PerUserDriveChart from '@/core/chart/charts/per-user-drive.js';
 import ApRequestChart from '@/core/chart/charts/ap-request.js';
 import { bindThis } from '@/decorators.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
+import type * as Bull from 'bullmq';
 
 @Injectable()
 export class ResyncChartsProcessorService {
@@ -43,7 +43,7 @@ export class ResyncChartsProcessorService {
 	}
 
 	@bindThis
-	public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
+	public async process(): Promise<void> {
 		this.logger.info('Resync charts...');
 
 		// TODO: ユーザーごとのチャートも更新する
@@ -55,6 +55,5 @@ export class ResyncChartsProcessorService {
 		]);
 
 		this.logger.succ('All charts successfully resynced.');
-		done();
 	}
 }
diff --git a/packages/backend/src/queue/processors/TickChartsProcessorService.ts b/packages/backend/src/queue/processors/TickChartsProcessorService.ts
index 7ff84c15a..f1696bf56 100644
--- a/packages/backend/src/queue/processors/TickChartsProcessorService.ts
+++ b/packages/backend/src/queue/processors/TickChartsProcessorService.ts
@@ -16,7 +16,7 @@ import PerUserDriveChart from '@/core/chart/charts/per-user-drive.js';
 import ApRequestChart from '@/core/chart/charts/ap-request.js';
 import { bindThis } from '@/decorators.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
+import type * as Bull from 'bullmq';
 
 @Injectable()
 export class TickChartsProcessorService {
@@ -45,7 +45,7 @@ export class TickChartsProcessorService {
 	}
 
 	@bindThis
-	public async process(job: Bull.Job<Record<string, unknown>>, done: () => void): Promise<void> {
+	public async process(): Promise<void> {
 		this.logger.info('Tick charts...');
 
 		await Promise.all([
@@ -64,6 +64,5 @@ export class TickChartsProcessorService {
 		]);
 
 		this.logger.succ('All charts successfully ticked.');
-		done();
 	}
 }
diff --git a/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts b/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts
index 84a5c21c4..8b40c1674 100644
--- a/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts
+++ b/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts
@@ -1,4 +1,5 @@
 import { Inject, Injectable } from '@nestjs/common';
+import * as Bull from 'bullmq';
 import { DI } from '@/di-symbols.js';
 import type { WebhooksRepository } from '@/models/index.js';
 import type { Config } from '@/config.js';
@@ -7,7 +8,6 @@ import { HttpRequestService } from '@/core/HttpRequestService.js';
 import { StatusError } from '@/misc/status-error.js';
 import { bindThis } from '@/decorators.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
 import type { WebhookDeliverJobData } from '../types.js';
 
 @Injectable()
@@ -66,11 +66,11 @@ export class WebhookDeliverProcessorService {
 			if (res instanceof StatusError) {
 				// 4xx
 				if (res.isClientError) {
-					return `${res.statusCode} ${res.statusMessage}`;
+					throw new Bull.UnrecoverableError(`${res.statusCode} ${res.statusMessage}`);
 				}
 	
 				// 5xx etc.
-				throw `${res.statusCode} ${res.statusMessage}`;
+				throw new Error(`${res.statusCode} ${res.statusMessage}`);
 			} else {
 				// DNS error, socket error, timeout ...
 				throw res;
diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts
index e675d9cf1..455acd1e4 100644
--- a/packages/backend/src/server/ActivityPubServerService.ts
+++ b/packages/backend/src/server/ActivityPubServerService.ts
@@ -585,7 +585,7 @@ export class ActivityPubServerService {
 				name: request.params.emoji,
 			});
 
-			if (emoji == null) {
+			if (emoji == null || emoji.localOnly) {
 				reply.code(404);
 				return;
 			}
diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts
index 9257fee13..c3d45e4ad 100644
--- a/packages/backend/src/server/ServerService.ts
+++ b/packages/backend/src/server/ServerService.ts
@@ -194,7 +194,7 @@ export class ServerService implements OnApplicationShutdown {
 
 		fastify.register(this.clientServerService.createServer);
 
-		this.streamingApiServerService.attachStreamingApi(fastify.server);
+		this.streamingApiServerService.attach(fastify.server);
 
 		fastify.server.on('error', err => {
 			switch ((err as any).code) {
@@ -222,7 +222,14 @@ export class ServerService implements OnApplicationShutdown {
 		await fastify.ready();
 	}
 
-	async onApplicationShutdown(signal: string): Promise<void> {
+	@bindThis
+	public async dispose(): Promise<void> {
+    await this.streamingApiServerService.detach();
 		await this.#fastify.close();
 	}
+
+	@bindThis
+	async onApplicationShutdown(signal: string): Promise<void> {
+		await this.dispose();
+	}
 }
diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts
index e3483c82c..dad1a4132 100644
--- a/packages/backend/src/server/api/ApiCallService.ts
+++ b/packages/backend/src/server/api/ApiCallService.ts
@@ -359,7 +359,12 @@ export class ApiCallService implements OnApplicationShutdown {
 	}
 
 	@bindThis
-	public onApplicationShutdown(signal?: string | undefined) {
+	public dispose(): void {
 		clearInterval(this.userIpHistoriesClearIntervalId);
 	}
+
+	@bindThis
+	public onApplicationShutdown(signal?: string | undefined): void {
+		this.dispose();
+	}
 }
diff --git a/packages/backend/src/server/api/AuthenticateService.ts b/packages/backend/src/server/api/AuthenticateService.ts
index 6548c475b..e23591d87 100644
--- a/packages/backend/src/server/api/AuthenticateService.ts
+++ b/packages/backend/src/server/api/AuthenticateService.ts
@@ -36,7 +36,7 @@ export class AuthenticateService {
 	}
 
 	@bindThis
-	public async authenticate(token: string | null | undefined): Promise<[LocalUser | null | undefined, AccessToken | null | undefined]> {
+	public async authenticate(token: string | null | undefined): Promise<[LocalUser | null, AccessToken | null]> {
 		if (token == null) {
 			return [null, null];
 		}
diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts
index ee1aae5b6..1e32e9988 100644
--- a/packages/backend/src/server/api/EndpointsModule.ts
+++ b/packages/backend/src/server/api/EndpointsModule.ts
@@ -321,6 +321,9 @@ import * as ep___users_lists_pull from './endpoints/users/lists/pull.js';
 import * as ep___users_lists_push from './endpoints/users/lists/push.js';
 import * as ep___users_lists_show from './endpoints/users/lists/show.js';
 import * as ep___users_lists_update from './endpoints/users/lists/update.js';
+import * as ep___users_lists_favorite from './endpoints/users/lists/favorite.js';
+import * as ep___users_lists_unfavorite from './endpoints/users/lists/unfavorite.js';
+import * as ep___users_lists_create_from_public from './endpoints/users/lists/create-from-public.js';
 import * as ep___users_notes from './endpoints/users/notes.js';
 import * as ep___users_pages from './endpoints/users/pages.js';
 import * as ep___users_reactions from './endpoints/users/reactions.js';
@@ -659,6 +662,9 @@ const $users_lists_pull: Provider = { provide: 'ep:users/lists/pull', useClass:
 const $users_lists_push: Provider = { provide: 'ep:users/lists/push', useClass: ep___users_lists_push.default };
 const $users_lists_show: Provider = { provide: 'ep:users/lists/show', useClass: ep___users_lists_show.default };
 const $users_lists_update: Provider = { provide: 'ep:users/lists/update', useClass: ep___users_lists_update.default };
+const $users_lists_favorite: Provider = { provide: 'ep:users/lists/favorite', useClass: ep___users_lists_favorite.default };
+const $users_lists_unfavorite: Provider = { provide: 'ep:users/lists/unfavorite', useClass: ep___users_lists_unfavorite.default };
+const $users_lists_create_from_public: Provider = { provide: 'ep:users/lists/create-from-public', useClass: ep___users_lists_create_from_public.default };
 const $users_notes: Provider = { provide: 'ep:users/notes', useClass: ep___users_notes.default };
 const $users_pages: Provider = { provide: 'ep:users/pages', useClass: ep___users_pages.default };
 const $users_reactions: Provider = { provide: 'ep:users/reactions', useClass: ep___users_reactions.default };
@@ -1001,6 +1007,9 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$users_lists_push,
 		$users_lists_show,
 		$users_lists_update,
+		$users_lists_favorite,
+		$users_lists_unfavorite,
+		$users_lists_create_from_public,
 		$users_notes,
 		$users_pages,
 		$users_reactions,
@@ -1335,6 +1344,9 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$users_lists_push,
 		$users_lists_show,
 		$users_lists_update,
+		$users_lists_favorite,
+		$users_lists_unfavorite,
+		$users_lists_create_from_public,
 		$users_notes,
 		$users_pages,
 		$users_reactions,
diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts
index 258e8de03..893dfe956 100644
--- a/packages/backend/src/server/api/StreamingApiServerService.ts
+++ b/packages/backend/src/server/api/StreamingApiServerService.ts
@@ -1,23 +1,27 @@
 import { EventEmitter } from 'events';
 import { Inject, Injectable } from '@nestjs/common';
 import * as Redis from 'ioredis';
-import * as websocket from 'websocket';
+import * as WebSocket from 'ws';
 import { DI } from '@/di-symbols.js';
-import type { UsersRepository, BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository, RenoteMutingsRepository } from '@/models/index.js';
+import type { UsersRepository, AccessToken } from '@/models/index.js';
 import type { Config } from '@/config.js';
 import { NoteReadService } from '@/core/NoteReadService.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
 import { NotificationService } from '@/core/NotificationService.js';
 import { bindThis } from '@/decorators.js';
 import { CacheService } from '@/core/CacheService.js';
-import { AuthenticateService } from './AuthenticateService.js';
+import { LocalUser } from '@/models/entities/User';
+import { AuthenticateService, AuthenticationError } from './AuthenticateService.js';
 import MainStreamConnection from './stream/index.js';
 import { ChannelsService } from './stream/ChannelsService.js';
-import type { ParsedUrlQuery } from 'querystring';
 import type * as http from 'node:http';
 
 @Injectable()
 export class StreamingApiServerService {
+	#wss: WebSocket.WebSocketServer;
+	#connections = new Map<WebSocket.WebSocket, number>();
+	#cleanConnectionsIntervalId: NodeJS.Timeout | null = null;
+
 	constructor(
 		@Inject(DI.config)
 		private config: Config,
@@ -28,24 +32,6 @@ export class StreamingApiServerService {
 		@Inject(DI.usersRepository)
 		private usersRepository: UsersRepository,
 
-		@Inject(DI.followingsRepository)
-		private followingsRepository: FollowingsRepository,
-
-		@Inject(DI.mutingsRepository)
-		private mutingsRepository: MutingsRepository,
-
-		@Inject(DI.renoteMutingsRepository)
-		private renoteMutingsRepository: RenoteMutingsRepository,
-
-		@Inject(DI.blockingsRepository)
-		private blockingsRepository: BlockingsRepository,
-
-		@Inject(DI.channelFollowingsRepository)
-		private channelFollowingsRepository: ChannelFollowingsRepository,
-
-		@Inject(DI.userProfilesRepository)
-		private userProfilesRepository: UserProfilesRepository,
-	
 		private cacheService: CacheService,
 		private noteReadService: NoteReadService,
 		private authenticateService: AuthenticateService,
@@ -55,25 +41,65 @@ export class StreamingApiServerService {
 	}
 
 	@bindThis
-	public attachStreamingApi(server: http.Server) {
-		// Init websocket server
-		const ws = new websocket.server({
-			httpServer: server,
+	public attach(server: http.Server): void {
+		this.#wss = new WebSocket.WebSocketServer({
+			noServer: true,
 		});
 
-		ws.on('request', async (request) => {
-			const q = request.resourceURL.query as ParsedUrlQuery;
-
-			// TODO: トークンが間違ってるなどしてauthenticateに失敗したら
-			// コネクション切断するなりエラーメッセージ返すなりする
-			// (現状はエラーがキャッチされておらずサーバーのログに流れて邪魔なので)
-			const [user, miapp] = await this.authenticateService.authenticate(q.i as string);
-
-			if (user?.isSuspended) {
-				request.reject(400);
+		server.on('upgrade', async (request, socket, head) => {
+			if (request.url == null) {
+				socket.write('HTTP/1.1 400 Bad Request\r\n\r\n');
+				socket.destroy();
 				return;
 			}
 
+			const q = new URL(request.url, `http://${request.headers.host}`).searchParams;
+
+			let user: LocalUser | null = null;
+			let app: AccessToken | null = null;
+
+			try {
+				[user, app] = await this.authenticateService.authenticate(q.get('i'));
+			} catch (e) {
+				if (e instanceof AuthenticationError) {
+					socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
+				} else {
+					socket.write('HTTP/1.1 500 Internal Server Error\r\n\r\n');
+				}
+				socket.destroy();
+				return;
+			}
+
+			if (user?.isSuspended) {
+				socket.write('HTTP/1.1 403 Forbidden\r\n\r\n');
+				socket.destroy();
+				return;
+			}
+
+			const stream = new MainStreamConnection(
+				this.channelsService,
+				this.noteReadService,
+				this.notificationService,
+				this.cacheService,
+				user, app,
+			);
+
+			await stream.init();
+
+			this.#wss.handleUpgrade(request, socket, head, (ws) => {
+				this.#wss.emit('connection', ws, request, {
+					stream, user, app,
+				});
+			});
+		});
+
+		this.#wss.on('connection', async (connection: WebSocket.WebSocket, request: http.IncomingMessage, ctx: {
+			stream: MainStreamConnection,
+			user: LocalUser | null;
+			app: AccessToken | null
+		}) => {
+			const { stream, user, app } = ctx;
+
 			const ev = new EventEmitter();
 
 			async function onRedisMessage(_: string, data: string): Promise<void> {
@@ -83,21 +109,11 @@ export class StreamingApiServerService {
 
 			this.redisForSub.on('message', onRedisMessage);
 
-			const main = new MainStreamConnection(
-				this.channelsService,
-				this.noteReadService,
-				this.notificationService,
-				this.cacheService,
-				ev, user, miapp,
-			);
+			await stream.listen(ev, connection);
 
-			await main.init();
+			this.#connections.set(connection, Date.now());
 
-			const connection = request.accept();
-
-			main.init2(connection);
-
-			const intervalId = user ? setInterval(() => {
+			const userUpdateIntervalId = user ? setInterval(() => {
 				this.usersRepository.update(user.id, {
 					lastActiveDate: new Date(),
 				});
@@ -110,16 +126,38 @@ export class StreamingApiServerService {
 
 			connection.once('close', () => {
 				ev.removeAllListeners();
-				main.dispose();
+				stream.dispose();
 				this.redisForSub.off('message', onRedisMessage);
-				if (intervalId) clearInterval(intervalId);
+				if (userUpdateIntervalId) clearInterval(userUpdateIntervalId);
 			});
 
 			connection.on('message', async (data) => {
-				if (data.type === 'utf8' && data.utf8Data === 'ping') {
+				this.#connections.set(connection, Date.now());
+				if (data.toString() === 'ping') {
 					connection.send('pong');
 				}
 			});
 		});
+
+		this.#cleanConnectionsIntervalId = setInterval(() => {
+			const now = Date.now();
+			for (const [connection, lastActive] of this.#connections.entries()) {
+				if (now - lastActive > 1000 * 60 * 5) {
+					connection.terminate();
+					this.#connections.delete(connection);
+				}
+			}
+		}, 1000 * 60 * 5);
+	}
+
+	@bindThis
+	public detach(): Promise<void> {
+		if (this.#cleanConnectionsIntervalId) {
+			clearInterval(this.#cleanConnectionsIntervalId);
+			this.#cleanConnectionsIntervalId = null;
+		}
+		return new Promise((resolve) => {
+			this.#wss.close(() => resolve());
+		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index 09bd7cbff..7e678a640 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -320,6 +320,9 @@ import * as ep___users_lists_list from './endpoints/users/lists/list.js';
 import * as ep___users_lists_pull from './endpoints/users/lists/pull.js';
 import * as ep___users_lists_push from './endpoints/users/lists/push.js';
 import * as ep___users_lists_show from './endpoints/users/lists/show.js';
+import * as ep___users_lists_favorite from './endpoints/users/lists/favorite.js';
+import * as ep___users_lists_unfavorite from './endpoints/users/lists/unfavorite.js';
+import * as ep___users_lists_create_from_public from './endpoints/users/lists/create-from-public.js';
 import * as ep___users_lists_update from './endpoints/users/lists/update.js';
 import * as ep___users_notes from './endpoints/users/notes.js';
 import * as ep___users_pages from './endpoints/users/pages.js';
@@ -656,7 +659,10 @@ const eps = [
 	['users/lists/pull', ep___users_lists_pull],
 	['users/lists/push', ep___users_lists_push],
 	['users/lists/show', ep___users_lists_show],
+	['users/lists/favorite', ep___users_lists_favorite],
+	['users/lists/unfavorite', ep___users_lists_unfavorite],
 	['users/lists/update', ep___users_lists_update],
+	['users/lists/create-from-public', ep___users_lists_create_from_public],
 	['users/notes', ep___users_notes],
 	['users/pages', ep___users_pages],
 	['users/reactions', ep___users_reactions],
diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts
index 2393c2441..12db1f78f 100644
--- a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts
@@ -25,7 +25,7 @@ export const paramDef = {
 		id: { type: 'string', format: 'misskey:id' },
 		title: { type: 'string', minLength: 1 },
 		text: { type: 'string', minLength: 1 },
-		imageUrl: { type: 'string', nullable: true, minLength: 1 },
+		imageUrl: { type: 'string', nullable: true, minLength: 0 },
 	},
 	required: ['id', 'title', 'text', 'imageUrl'],
 } as const;
@@ -46,7 +46,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				updatedAt: new Date(),
 				title: ps.title,
 				text: ps.text,
-				imageUrl: ps.imageUrl,
+				/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- 空の文字列の場合、nullを渡すようにするため */
+				imageUrl: ps.imageUrl || null, 
 			});
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
index 2fb3e489e..509224e9c 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
@@ -25,9 +25,24 @@ export const meta = {
 export const paramDef = {
 	type: 'object',
 	properties: {
+		name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' },
 		fileId: { type: 'string', format: 'misskey:id' },
+		category: {
+			type: 'string',
+			nullable: true,
+			description: 'Use `null` to reset the category.',
+		},
+		aliases: { type: 'array', items: {
+			type: 'string',
+		} },
+		license: { type: 'string', nullable: true },
+		isSensitive: { type: 'boolean' },
+		localOnly: { type: 'boolean' },
+		roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
+			type: 'string',
+		} },
 	},
-	required: ['fileId'],
+	required: ['name', 'fileId'],
 } as const;
 
 // TODO: ロジックをサービスに切り出す
@@ -45,18 +60,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
-
 			if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
 
-			const name = driveFile.name.split('.')[0].match(/^[a-z0-9_]+$/) ? driveFile.name.split('.')[0] : `_${rndstr('a-z0-9', 8)}_`;
-
 			const emoji = await this.customEmojiService.add({
 				driveFile,
-				name,
-				category: null,
-				aliases: [],
+				name: ps.name,
+				category: ps.category ?? null,
+				aliases: ps.aliases ?? [],
 				host: null,
-				license: null,
+				license: ps.license ?? null,
+				isSensitive: ps.isSensitive ?? false,
+				localOnly: ps.localOnly ?? false,
+				roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [],
 			});
 
 			this.moderationLogService.insertModerationLog(me, 'addEmoji', {
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
index f63348b60..fb22bdc47 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
@@ -1,6 +1,8 @@
 import { Inject, Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { CustomEmojiService } from '@/core/CustomEmojiService.js';
+import type { DriveFilesRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
 import { ApiError } from '../../../error.js';
 
 export const meta = {
@@ -15,6 +17,11 @@ export const meta = {
 			code: 'NO_SUCH_EMOJI',
 			id: '684dec9d-a8c2-4364-9aa8-456c49cb1dc8',
 		},
+		noSuchFile: {
+			message: 'No such file.',
+			code: 'NO_SUCH_FILE',
+			id: '14fb9fd9-0731-4e2f-aeb9-f09e4740333d',
+		},
 		sameNameEmojiExists: {
 			message: 'Emoji that have same name already exists.',
 			code: 'SAME_NAME_EMOJI_EXISTS',
@@ -28,6 +35,7 @@ export const paramDef = {
 	properties: {
 		id: { type: 'string', format: 'misskey:id' },
 		name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' },
+		fileId: { type: 'string', format: 'misskey:id' },
 		category: {
 			type: 'string',
 			nullable: true,
@@ -37,6 +45,11 @@ export const paramDef = {
 			type: 'string',
 		} },
 		license: { type: 'string', nullable: true },
+		isSensitive: { type: 'boolean' },
+		localOnly: { type: 'boolean' },
+		roleIdsThatCanBeUsedThisEmojiAsReaction: { type: 'array', items: {
+			type: 'string',
+		} },
 	},
 	required: ['id', 'name', 'aliases'],
 } as const;
@@ -45,14 +58,28 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> {
 	constructor(
+		@Inject(DI.driveFilesRepository)
+		private driveFilesRepository: DriveFilesRepository,
+
 		private customEmojiService: CustomEmojiService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
+			let driveFile;
+
+			if (ps.fileId) {
+				driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
+				if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
+			}
+	
 			await this.customEmojiService.update(ps.id, {
+				driveFile,
 				name: ps.name,
 				category: ps.category ?? null,
 				aliases: ps.aliases,
 				license: ps.license ?? null,
+				isSensitive: ps.isSensitive,
+				localOnly: ps.localOnly,
+				roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction,
 			});
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/admin/relays/add.ts b/packages/backend/src/server/api/endpoints/admin/relays/add.ts
index f12738bd3..f2d4aa899 100644
--- a/packages/backend/src/server/api/endpoints/admin/relays/add.ts
+++ b/packages/backend/src/server/api/endpoints/admin/relays/add.ts
@@ -62,7 +62,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			try {
-				if (new URL(ps.inbox).protocol !== 'https:') throw 'https only';
+				if (new URL(ps.inbox).protocol !== 'https:') throw new Error('https only');
 			} catch {
 				throw new ApiError(meta.errors.invalidUrl);
 			}
diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts
index dca0f443b..e756a9b51 100644
--- a/packages/backend/src/server/api/endpoints/antennas/notes.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts
@@ -113,6 +113,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			}
 
 			this.antennasRepository.update(antenna.id, {
+				isActive: true,
 				lastUsedAt: new Date(),
 			});
 
diff --git a/packages/backend/src/server/api/endpoints/auth/accept.ts b/packages/backend/src/server/api/endpoints/auth/accept.ts
index cb2e661bf..05842460c 100644
--- a/packages/backend/src/server/api/endpoints/auth/accept.ts
+++ b/packages/backend/src/server/api/endpoints/auth/accept.ts
@@ -55,7 +55,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				throw new ApiError(meta.errors.noSuchSession);
 			}
 
-			// Generate access token
 			const accessToken = secureRndstr(32, true);
 
 			// Fetch exist access token
@@ -65,7 +64,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			});
 
 			if (exist == null) {
-				// Lookup app
 				const app = await this.appsRepository.findOneByOrFail({ id: session.appId });
 
 				// Generate Hash
@@ -75,7 +73,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 
 				const now = new Date();
 
-				// Insert access token doc
 				await this.accessTokensRepository.insert({
 					id: this.idService.genId(),
 					createdAt: now,
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts
index ad33398da..e8985a9cd 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts
@@ -1,6 +1,6 @@
 import { promisify } from 'node:util';
 import bcrypt from 'bcryptjs';
-import * as cbor from 'cbor';
+import cbor from 'cbor';
 import { Inject, Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
diff --git a/packages/backend/src/server/api/endpoints/i/apps.ts b/packages/backend/src/server/api/endpoints/i/apps.ts
index 3361e5a4d..48fb03a8a 100644
--- a/packages/backend/src/server/api/endpoints/i/apps.ts
+++ b/packages/backend/src/server/api/endpoints/i/apps.ts
@@ -26,7 +26,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const query = this.accessTokensRepository.createQueryBuilder('token')
-				.where('token.userId = :userId', { userId: me.id });
+				.where('token.userId = :userId', { userId: me.id })
+				.leftJoinAndSelect('token.app', 'app');
 
 			switch (ps.sort) {
 				case '+createdAt': query.orderBy('token.createdAt', 'DESC'); break;
@@ -40,7 +41,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 
 			return await Promise.all(tokens.map(token => ({
 				id: token.id,
-				name: token.name,
+				name: token.name ?? token.app?.name,
 				createdAt: token.createdAt,
 				lastUsedAt: token.lastUsedAt,
 				permission: token.permission,
diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts
index e141be764..f5662f4a0 100644
--- a/packages/backend/src/server/api/endpoints/i/notifications.ts
+++ b/packages/backend/src/server/api/endpoints/i/notifications.ts
@@ -91,18 +91,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			const includeTypes = ps.includeTypes && ps.includeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof notificationTypes[number][];
 			const excludeTypes = ps.excludeTypes && ps.excludeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof notificationTypes[number][];
 
-			const limit = ps.limit + (ps.untilId ? 1 : 0); // untilIdに指定したものも含まれるため+1
+			const limit = ps.limit + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1
 			const notificationsRes = await this.redisClient.xrevrange(
 				`notificationTimeline:${me.id}`,
 				ps.untilId ? this.idService.parse(ps.untilId).date.getTime() : '+',
-				'-',
+				ps.sinceId ? this.idService.parse(ps.sinceId).date.getTime() : '-',
 				'COUNT', limit);
 
 			if (notificationsRes.length === 0) {
 				return [];
 			}
 
-			let notifications = notificationsRes.map(x => JSON.parse(x[1][1])).filter(x => x.id !== ps.untilId) as Notification[];
+			let notifications = notificationsRes.map(x => JSON.parse(x[1][1])).filter(x => x.id !== ps.untilId && x !== ps.sinceId) as Notification[];
 
 			if (includeTypes && includeTypes.length > 0) {
 				notifications = notifications.filter(notification => includeTypes.includes(notification.type));
diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts
index 74be00a8b..8f5e6177c 100644
--- a/packages/backend/src/server/api/endpoints/i/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/update.ts
@@ -141,13 +141,12 @@ export const paramDef = {
 		preventAiLearning: { type: 'boolean' },
 		isBot: { type: 'boolean' },
 		isCat: { type: 'boolean' },
-		showTimelineReplies: { type: 'boolean' },
 		injectFeaturedNote: { type: 'boolean' },
 		receiveAnnouncementEmail: { type: 'boolean' },
 		alwaysMarkNsfw: { type: 'boolean' },
 		autoSensitive: { type: 'boolean' },
 		ffVisibility: { type: 'string', enum: ['public', 'followers', 'private'] },
-		pinnedPageId: { type: 'string', format: 'misskey:id' },
+		pinnedPageId: { type: 'string', format: 'misskey:id', nullable: true },
 		mutedWords: { type: 'array' },
 		mutedInstances: { type: 'array', items: {
 			type: 'string',
@@ -239,7 +238,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			if (typeof ps.hideOnlineStatus === 'boolean') updates.hideOnlineStatus = ps.hideOnlineStatus;
 			if (typeof ps.publicReactions === 'boolean') profileUpdates.publicReactions = ps.publicReactions;
 			if (typeof ps.isBot === 'boolean') updates.isBot = ps.isBot;
-			if (typeof ps.showTimelineReplies === 'boolean') updates.showTimelineReplies = ps.showTimelineReplies;
 			if (typeof ps.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot;
 			if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;
 			if (typeof ps.noCrawle === 'boolean') profileUpdates.noCrawle = ps.noCrawle;
diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts
index 584ea07c3..53d724a9d 100644
--- a/packages/backend/src/server/api/endpoints/meta.ts
+++ b/packages/backend/src/server/api/endpoints/meta.ts
@@ -1,5 +1,6 @@
 import { IsNull, LessThanOrEqual, MoreThan } from 'typeorm';
 import { Inject, Injectable } from '@nestjs/common';
+import * as JSON5 from 'json5';
 import type { AdsRepository, UsersRepository } from '@/models/index.js';
 import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
@@ -292,8 +293,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				backgroundImageUrl: instance.backgroundImageUrl,
 				logoImageUrl: instance.logoImageUrl,
 				maxNoteTextLength: MAX_NOTE_TEXT_LENGTH,
-				defaultLightTheme: instance.defaultLightTheme,
-				defaultDarkTheme: instance.defaultDarkTheme,
+				// クライアントの手間を減らすためあらかじめJSONに変換しておく
+				defaultLightTheme: instance.defaultLightTheme ? JSON.stringify(JSON5.parse(instance.defaultLightTheme)) : null,
+				defaultDarkTheme: instance.defaultDarkTheme ? JSON.stringify(JSON5.parse(instance.defaultDarkTheme)) : null,
 				ads: ads.map(ad => ({
 					id: ad.id,
 					url: ad.url,
diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index 3f7f2cdec..96be5ed84 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -99,7 +99,7 @@ export const paramDef = {
 		} },
 		cw: { type: 'string', nullable: true, maxLength: 100 },
 		localOnly: { type: 'boolean', default: false },
-		reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote'], default: null },
+		reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote', 'nonSensitiveOnly', 'nonSensitiveOnlyForLocalLikeOnlyForRemote'], default: null },
 		noExtractMentions: { type: 'boolean', default: false },
 		noExtractHashtags: { type: 'boolean', default: false },
 		noExtractEmojis: { type: 'boolean', default: false },
diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
index c11c1eac4..88c1ca7f5 100644
--- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
@@ -34,11 +34,8 @@ export const meta = {
 export const paramDef = {
 	type: 'object',
 	properties: {
-		withFiles: {
-			type: 'boolean',
-			default: false,
-			description: 'Only show notes that have attached files.',
-		},
+		withFiles: { type: 'boolean', default: false },
+		withReplies: { type: 'boolean', default: false },
 		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
 		sinceId: { type: 'string', format: 'misskey:id' },
 		untilId: { type: 'string', format: 'misskey:id' },
@@ -78,7 +75,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				.leftJoinAndSelect('reply.user', 'replyUser')
 				.leftJoinAndSelect('renote.user', 'renoteUser');
 
-			this.queryService.generateRepliesQuery(query, me);
+			this.queryService.generateRepliesQuery(query, ps.withReplies, me);
 			if (me) {
 				this.queryService.generateMutedUserQuery(query, me);
 				this.queryService.generateMutedNoteQuery(query, me);
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 89abd91c7..7a3581e6e 100644
--- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
@@ -46,11 +46,8 @@ export const paramDef = {
 		includeMyRenotes: { type: 'boolean', default: true },
 		includeRenotedMyNotes: { type: 'boolean', default: true },
 		includeLocalRenotes: { type: 'boolean', default: true },
-		withFiles: {
-			type: 'boolean',
-			default: false,
-			description: 'Only show notes that have attached files.',
-		},
+		withFiles: { type: 'boolean', default: false },
+		withReplies: { type: 'boolean', default: false },
 	},
 	required: [],
 } as const;
@@ -98,7 +95,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				.setParameters(followingQuery.getParameters());
 
 			this.queryService.generateChannelQuery(query, me);
-			this.queryService.generateRepliesQuery(query, me);
+			this.queryService.generateRepliesQuery(query, ps.withReplies, me);
 			this.queryService.generateVisibilityQuery(query, me);
 			this.queryService.generateMutedUserQuery(query, me);
 			this.queryService.generateMutedNoteQuery(query, me);
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 afdafc7c5..2ee549232 100644
--- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
@@ -36,11 +36,8 @@ export const meta = {
 export const paramDef = {
 	type: 'object',
 	properties: {
-		withFiles: {
-			type: 'boolean',
-			default: false,
-			description: 'Only show notes that have attached files.',
-		},
+		withFiles: { type: 'boolean', default: false },
+		withReplies: { type: 'boolean', default: false },
 		fileType: { type: 'array', items: {
 			type: 'string',
 		} },
@@ -86,7 +83,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				.leftJoinAndSelect('renote.user', 'renoteUser');
 
 			this.queryService.generateChannelQuery(query, me);
-			this.queryService.generateRepliesQuery(query, me);
+			this.queryService.generateRepliesQuery(query, ps.withReplies, me);
 			this.queryService.generateVisibilityQuery(query, me);
 			if (me) this.queryService.generateMutedUserQuery(query, me);
 			if (me) this.queryService.generateMutedNoteQuery(query, me);
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 2956bf1cb..742df0ca9 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
@@ -82,14 +82,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 
 			try {
 				if (ps.tag) {
-					if (!safeForSql(normalizeForSearch(ps.tag))) throw 'Injection';
+					if (!safeForSql(normalizeForSearch(ps.tag))) throw new Error('Injection');
 					query.andWhere(`'{"${normalizeForSearch(ps.tag)}"}' <@ note.tags`);
 				} else {
 					query.andWhere(new Brackets(qb => {
 						for (const tags of ps.query!) {
 							qb.orWhere(new Brackets(qb => {
 								for (const tag of tags) {
-									if (!safeForSql(normalizeForSearch(tag))) throw 'Injection';
+									if (!safeForSql(normalizeForSearch(tag))) throw new Error('Injection');
 									qb.andWhere(`'{"${normalizeForSearch(tag)}"}' <@ note.tags`);
 								}
 							}));
diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts
index c6ee1e5c2..e1f286439 100644
--- a/packages/backend/src/server/api/endpoints/notes/timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts
@@ -35,11 +35,8 @@ export const paramDef = {
 		includeMyRenotes: { type: 'boolean', default: true },
 		includeRenotedMyNotes: { type: 'boolean', default: true },
 		includeLocalRenotes: { type: 'boolean', default: true },
-		withFiles: {
-			type: 'boolean',
-			default: false,
-			description: 'Only show notes that have attached files.',
-		},
+		withFiles: { type: 'boolean', default: false },
+		withReplies: { type: 'boolean', default: false },
 	},
 	required: [],
 } as const;
@@ -84,7 +81,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			}
 
 			this.queryService.generateChannelQuery(query, me);
-			this.queryService.generateRepliesQuery(query, me);
+			this.queryService.generateRepliesQuery(query, ps.withReplies, me);
 			this.queryService.generateVisibilityQuery(query, me);
 			this.queryService.generateMutedUserQuery(query, me);
 			this.queryService.generateMutedNoteQuery(query, me);
diff --git a/packages/backend/src/server/api/endpoints/reset-db.ts b/packages/backend/src/server/api/endpoints/reset-db.ts
index 4ced6d3ff..1d4825f81 100644
--- a/packages/backend/src/server/api/endpoints/reset-db.ts
+++ b/packages/backend/src/server/api/endpoints/reset-db.ts
@@ -34,7 +34,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		private redisClient: Redis.Redis,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			if (process.env.NODE_ENV !== 'test') throw 'NODE_ENV is not a test';
+			if (process.env.NODE_ENV !== 'test') throw new Error('NODE_ENV is not a test');
 
 			await redisClient.flushdb();
 			await resetDb(this.db);
diff --git a/packages/backend/src/server/api/endpoints/roles/notes.ts b/packages/backend/src/server/api/endpoints/roles/notes.ts
index 6202c740f..42e36cb04 100644
--- a/packages/backend/src/server/api/endpoints/roles/notes.ts
+++ b/packages/backend/src/server/api/endpoints/roles/notes.ts
@@ -93,6 +93,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 
 			const query = this.notesRepository.createQueryBuilder('note')
 				.where('note.id IN (:...noteIds)', { noteIds: noteIds })
+				.andWhere('(note.visibility = \'public\')')
 				.innerJoinAndSelect('note.user', 'user')
 				.leftJoinAndSelect('note.reply', 'reply')
 				.leftJoinAndSelect('note.renote', 'renote')
diff --git a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts
new file mode 100644
index 000000000..8591e4ab9
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts
@@ -0,0 +1,148 @@
+import { Inject, Injectable } from '@nestjs/common';
+import type { UserListsRepository, UserListJoiningsRepository, BlockingsRepository } from '@/models/index.js';
+import { IdService } from '@/core/IdService.js';
+import type { UserList } from '@/models/entities/UserList.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { GetterService } from '@/server/api/GetterService.js';
+import { UserListEntityService } from '@/core/entities/UserListEntityService.js';
+import { DI } from '@/di-symbols.js';
+import { ApiError } from '@/server/api/error.js';
+import { RoleService } from '@/core/RoleService.js';
+import { UserListService } from '@/core/UserListService.js';
+
+export const meta = {
+	requireCredential: true,
+	prohibitMoved: true,
+	res: {
+		type: 'object',
+		optional: false, nullable: false,
+		ref: 'UserList',
+	},
+
+	errors: {
+		tooManyUserLists: {
+			message: 'You cannot create user list any more.',
+			code: 'TOO_MANY_USERLISTS',
+			id: 'e9c105b2-c595-47de-97fb-7f7c2c33e92f',
+		},
+		noSuchList: {
+			message: 'No such list.',
+			code: 'NO_SUCH_LIST',
+			id: '9292f798-6175-4f7d-93f4-b6742279667d',
+		},
+		noSuchUser: {
+			message: 'No such user.',
+			code: 'NO_SUCH_USER',
+			id: '13c457db-a8cb-4d88-b70a-211ceeeabb5f',
+		},
+
+		alreadyAdded: {
+			message: 'That user has already been added to that list.',
+			code: 'ALREADY_ADDED',
+			id: 'c3ad6fdb-692b-47ee-a455-7bd12c7af615',
+		},
+
+		youHaveBeenBlocked: {
+			message: 'You cannot push this user because you have been blocked by this user.',
+			code: 'YOU_HAVE_BEEN_BLOCKED',
+			id: 'a2497f2a-2389-439c-8626-5298540530f4',
+		},
+
+		tooManyUsers: {
+			message: 'You can not push users any more.',
+			code: 'TOO_MANY_USERS',
+			id: '1845ea77-38d1-426e-8e4e-8b83b24f5bd7',
+		},
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		name: { type: 'string', minLength: 1, maxLength: 100 },
+		listId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['name', 'listId'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+	constructor(
+		@Inject(DI.userListsRepository)
+		private userListsRepository: UserListsRepository,
+
+		@Inject(DI.userListJoiningsRepository)
+		private userListJoiningsRepository: UserListJoiningsRepository,
+
+		@Inject(DI.blockingsRepository)
+		private blockingsRepository: BlockingsRepository,
+
+		private userListService: UserListService,
+		private userListEntityService: UserListEntityService,
+		private idService: IdService,
+		private getterService: GetterService,
+		private roleService: RoleService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			const list = await this.userListsRepository.findOneBy({
+				id: ps.listId,
+				isPublic: true,
+			});
+			if (list === null) throw new ApiError(meta.errors.noSuchList);
+			const currentCount = await this.userListsRepository.countBy({
+				userId: me.id,
+			});
+			if (currentCount > (await this.roleService.getUserPolicies(me.id)).userListLimit) {
+				throw new ApiError(meta.errors.tooManyUserLists);
+			}
+			
+			const userList = await this.userListsRepository.insert({
+				id: this.idService.genId(),
+				createdAt: new Date(),
+				userId: me.id,
+				name: ps.name,
+			} as UserList).then(x => this.userListsRepository.findOneByOrFail(x.identifiers[0]));
+
+			const users = (await this.userListJoiningsRepository.findBy({
+				userListId: ps.listId,
+			})).map(x => x.userId);
+
+			for (const user of users) {
+				const currentUser = await this.getterService.getUser(user).catch(err => {
+					if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+					throw err;
+				});
+
+				if (currentUser.id !== me.id) {
+					const block = await this.blockingsRepository.findOneBy({
+						blockerId: currentUser.id,
+						blockeeId: me.id,
+					});
+					if (block) {
+						throw new ApiError(meta.errors.youHaveBeenBlocked);
+					}
+				}
+
+				const exist = await this.userListJoiningsRepository.findOneBy({
+					userListId: userList.id,
+					userId: currentUser.id,
+				});
+	
+				if (exist) {
+					throw new ApiError(meta.errors.alreadyAdded);
+				}
+
+				try {
+					await this.userListService.push(currentUser, userList, me);
+				} catch (err) {
+					if (err instanceof UserListService.TooManyUsersError) {
+						throw new ApiError(meta.errors.tooManyUsers);
+					}
+					throw err;
+				}
+			}
+			return await this.userListEntityService.pack(userList);
+		});
+	}
+}
+
diff --git a/packages/backend/src/server/api/endpoints/users/lists/favorite.ts b/packages/backend/src/server/api/endpoints/users/lists/favorite.ts
new file mode 100644
index 000000000..263852fde
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/users/lists/favorite.ts
@@ -0,0 +1,70 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import type { UserListFavoritesRepository, UserListsRepository } from '@/models/index.js';
+import { IdService } from '@/core/IdService.js';
+import { ApiError } from '@/server/api/error.js';
+import { DI } from '@/di-symbols.js';
+
+export const meta = {
+	requireCredential: true,
+	errors: {
+		noSuchList: {
+			message: 'No such user list.',
+			code: 'NO_SUCH_USER_LIST',
+			id: '7dbaf3cf-7b42-4b8f-b431-b3919e580dbe',
+		},
+
+		alreadyFavorited: {
+			message: 'The list has already been favorited.',
+			code: 'ALREADY_FAVORITED',
+			id: '6425bba0-985b-461e-af1b-518070e72081',
+		},
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		listId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['listId'],
+} as const;
+
+@Injectable() // eslint-disable-next-line import/no-default-export
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+	constructor (
+		@Inject(DI.userListsRepository)
+		private userListsRepository: UserListsRepository,
+
+		@Inject(DI.userListFavoritesRepository)
+		private userListFavoritesRepository: UserListFavoritesRepository,
+		private idService: IdService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			const userList = await this.userListsRepository.findOneBy({ 
+				id: ps.listId,
+				isPublic: true,
+			});
+
+			if (userList === null) {
+				throw new ApiError(meta.errors.noSuchList);
+			}
+
+			const exist = await this.userListFavoritesRepository.findOneBy({
+				userId: me.id,
+				userListId: ps.listId,
+			});
+
+			if (exist !== null) {
+				throw new ApiError(meta.errors.alreadyFavorited);
+			}
+
+			await this.userListFavoritesRepository.insert({
+				id: this.idService.genId(),
+				createdAt: new Date(),
+				userId: me.id,
+				userListId: ps.listId,
+			});
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/users/lists/list.ts b/packages/backend/src/server/api/endpoints/users/lists/list.ts
index 2104c4377..eab29944b 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/list.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/list.ts
@@ -1,13 +1,14 @@
 import { Inject, Injectable } from '@nestjs/common';
-import type { UserListsRepository } from '@/models/index.js';
+import type { UserListsRepository, UsersRepository } from '@/models/index.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { UserListEntityService } from '@/core/entities/UserListEntityService.js';
+import { ApiError } from '@/server/api/error.js';
 import { DI } from '@/di-symbols.js';
 
 export const meta = {
 	tags: ['lists', 'account'],
 
-	requireCredential: true,
+	requireCredential: false,
 
 	kind: 'read:account',
 
@@ -22,26 +23,58 @@ export const meta = {
 			ref: 'UserList',
 		},
 	},
+	errors: {
+		noSuchUser: {
+			message: 'No such user.',
+			code: 'NO_SUCH_USER',
+			id: 'a8af4a82-0980-4cc4-a6af-8b0ffd54465e',
+		},
+		remoteUser: {
+			message: 'Not allowed to load the remote user\'s list',
+			code: 'REMOTE_USER_NOT_ALLOWED',
+			id: '53858f1b-3315-4a01-81b7-db9b48d4b79a',
+		},
+		invalidParam: {
+			message: 'Invalid param.',
+			code: 'INVALID_PARAM',
+			id: 'ab36de0e-29e9-48cb-9732-d82f1281620d',
+		},
+	},
 } as const;
 
 export const paramDef = {
 	type: 'object',
-	properties: {},
+	properties: {
+		userId: { type: 'string', format: 'misskey:id' },
+	},
 	required: [],
 } as const;
 
-// eslint-disable-next-line import/no-default-export
-@Injectable()
+@Injectable() // eslint-disable-next-line import/no-default-export
 export default class extends Endpoint<typeof meta, typeof paramDef> {
 	constructor(
+		@Inject(DI.usersRepository)
+		private usersRepository: UsersRepository,
+
 		@Inject(DI.userListsRepository)
 		private userListsRepository: UserListsRepository,
 
 		private userListEntityService: UserListEntityService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const userLists = await this.userListsRepository.findBy({
+			if (typeof ps.userId !== 'undefined') {
+				const user = await this.usersRepository.findOneBy({ id: ps.userId });
+				if (user === null) throw new ApiError(meta.errors.noSuchUser);
+				if (user.host !== null) throw new ApiError(meta.errors.remoteUser);
+			} else if (me === null) {
+				throw new ApiError(meta.errors.invalidParam);
+			}
+
+			const userLists = await this.userListsRepository.findBy(typeof ps.userId === 'undefined' && me !== null ? {
 				userId: me.id,
+			} : {
+				userId: ps.userId,
+				isPublic: true,
 			});
 
 			return await Promise.all(userLists.map(x => this.userListEntityService.pack(x)));
diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts
index 77f9cba80..8077841c8 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/show.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts
@@ -1,5 +1,5 @@
 import { Inject, Injectable } from '@nestjs/common';
-import type { UserListsRepository } from '@/models/index.js';
+import type { UserListsRepository, UserListFavoritesRepository } from '@/models/index.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { UserListEntityService } from '@/core/entities/UserListEntityService.js';
 import { DI } from '@/di-symbols.js';
@@ -8,7 +8,7 @@ import { ApiError } from '../../../error.js';
 export const meta = {
 	tags: ['lists', 'account'],
 
-	requireCredential: true,
+	requireCredential: false,
 
 	kind: 'read:account',
 
@@ -33,31 +33,54 @@ export const paramDef = {
 	type: 'object',
 	properties: {
 		listId: { type: 'string', format: 'misskey:id' },
+		forPublic: { type: 'boolean', default: false },
 	},
 	required: ['listId'],
 } as const;
 
-// eslint-disable-next-line import/no-default-export
-@Injectable()
+@Injectable() // eslint-disable-next-line import/no-default-export
 export default class extends Endpoint<typeof meta, typeof paramDef> {
 	constructor(
 		@Inject(DI.userListsRepository)
 		private userListsRepository: UserListsRepository,
 
+		@Inject(DI.userListFavoritesRepository)
+		private userListFavoritesRepository: UserListFavoritesRepository,
+
 		private userListEntityService: UserListEntityService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
+			const additionalProperties: Partial<{ likedCount: number, isLiked: boolean }> = {};
 			// Fetch the list
-			const userList = await this.userListsRepository.findOneBy({
+			const userList = await this.userListsRepository.findOneBy(!ps.forPublic && me !== null ? {
 				id: ps.listId,
 				userId: me.id,
+			} : {
+				id: ps.listId,
+				isPublic: true,
 			});
 
 			if (userList == null) {
 				throw new ApiError(meta.errors.noSuchList);
 			}
 
-			return await this.userListEntityService.pack(userList);
+			if (ps.forPublic && userList.isPublic) {
+				additionalProperties.likedCount = await this.userListFavoritesRepository.countBy({
+					userListId: ps.listId,
+				});
+				if (me !== null) {
+					additionalProperties.isLiked = (await this.userListFavoritesRepository.findOneBy({
+						userId: me.id,
+						userListId: ps.listId,
+					}) !== null);
+				} else {
+					additionalProperties.isLiked = false;
+				}
+			}
+			return {
+				...await this.userListEntityService.pack(userList),
+				...additionalProperties,
+			};
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts b/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts
new file mode 100644
index 000000000..be8e31781
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts
@@ -0,0 +1,63 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import type { UserListFavoritesRepository, UserListsRepository } from '@/models/index.js';
+import { ApiError } from '@/server/api/error.js';
+import { DI } from '@/di-symbols.js';
+
+export const meta = {
+	requireCredential: true,
+	errors: {
+		noSuchList: {
+			message: 'No such user list.',
+			code: 'NO_SUCH_USER_LIST',
+			id: 'baedb33e-76b8-4b0c-86a8-9375c0a7b94b',
+		},
+
+		notFavorited: {
+			message: 'You have not favorited the list.',
+			code: 'ALREADY_FAVORITED',
+			id: '835c4b27-463d-4cfa-969b-a9058678d465',
+		},
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		listId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['listId'],
+} as const;
+
+@Injectable() // eslint-disable-next-line import/no-default-export
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+	constructor (
+		@Inject(DI.userListsRepository)
+		private userListsRepository: UserListsRepository,
+
+		@Inject(DI.userListFavoritesRepository)
+		private userListFavoritesRepository: UserListFavoritesRepository,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			const userList = await this.userListsRepository.findOneBy({
+				id: ps.listId,
+				isPublic: true,
+			});
+
+			if (userList === null) {
+				throw new ApiError(meta.errors.noSuchList);
+			}
+
+			const exist = await this.userListFavoritesRepository.findOneBy({
+				userListId: ps.listId,
+				userId: me.id,
+			});
+
+			if (exist === null) {
+				throw new ApiError(meta.errors.notFavorited);
+			}
+
+			await this.userListFavoritesRepository.delete({ id: exist.id });
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/users/lists/update.ts b/packages/backend/src/server/api/endpoints/users/lists/update.ts
index 6453d7d98..b0a95a2f2 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/update.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/update.ts
@@ -34,8 +34,9 @@ export const paramDef = {
 	properties: {
 		listId: { type: 'string', format: 'misskey:id' },
 		name: { type: 'string', minLength: 1, maxLength: 100 },
+		isPublic: { type: 'boolean' },
 	},
-	required: ['listId', 'name'],
+	required: ['listId'],
 } as const;
 
 // eslint-disable-next-line import/no-default-export
@@ -48,7 +49,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		private userListEntityService: UserListEntityService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			// Fetch the list
 			const userList = await this.userListsRepository.findOneBy({
 				id: ps.listId,
 				userId: me.id,
@@ -60,6 +60,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 
 			await this.userListsRepository.update(userList.id, {
 				name: ps.name,
+				isPublic: ps.isPublic,
 			});
 
 			return await this.userListEntityService.pack(userList.id);
diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts
index 5454836fe..d3339072c 100644
--- a/packages/backend/src/server/api/stream/channels/global-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts
@@ -13,6 +13,7 @@ class GlobalTimelineChannel extends Channel {
 	public readonly chName = 'globalTimeline';
 	public static shouldShare = true;
 	public static requireCredential = false;
+	private withReplies: boolean;
 
 	constructor(
 		private metaService: MetaService,
@@ -31,6 +32,8 @@ class GlobalTimelineChannel extends Channel {
 		const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
 		if (!policies.gtlAvailable) return;
 
+		this.withReplies = params.withReplies as boolean;
+
 		// Subscribe events
 		this.subscriber.on('notesStream', this.onNote);
 	}
@@ -54,7 +57,7 @@ class GlobalTimelineChannel extends Channel {
 		}
 
 		// 関係ない返信は除外
-		if (note.reply && !this.user!.showTimelineReplies) {
+		if (note.reply && !this.withReplies) {
 			const reply = note.reply;
 			// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
 			if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts
index ee874ad81..1755aa94c 100644
--- a/packages/backend/src/server/api/stream/channels/home-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts
@@ -11,6 +11,7 @@ class HomeTimelineChannel extends Channel {
 	public readonly chName = 'homeTimeline';
 	public static shouldShare = true;
 	public static requireCredential = true;
+	private withReplies: boolean;
 
 	constructor(
 		private noteEntityService: NoteEntityService,
@@ -24,6 +25,8 @@ class HomeTimelineChannel extends Channel {
 
 	@bindThis
 	public async init(params: any) {
+		this.withReplies = params.withReplies as boolean;
+	
 		this.subscriber.on('notesStream', this.onNote);
 	}
 
@@ -63,7 +66,7 @@ class HomeTimelineChannel extends Channel {
 		}
 
 		// 関係ない返信は除外
-		if (note.reply && !this.user!.showTimelineReplies) {
+		if (note.reply && !this.withReplies) {
 			const reply = note.reply;
 			// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
 			if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
index 4f7b4e78b..5a33e13cf 100644
--- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
@@ -13,6 +13,7 @@ class HybridTimelineChannel extends Channel {
 	public readonly chName = 'hybridTimeline';
 	public static shouldShare = true;
 	public static requireCredential = true;
+	private withReplies: boolean;
 
 	constructor(
 		private metaService: MetaService,
@@ -31,6 +32,8 @@ class HybridTimelineChannel extends Channel {
 		const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
 		if (!policies.ltlAvailable) return;
 
+		this.withReplies = params.withReplies as boolean;
+
 		// Subscribe events
 		this.subscriber.on('notesStream', this.onNote);
 	}
@@ -75,7 +78,7 @@ class HybridTimelineChannel extends Channel {
 		if (isInstanceMuted(note, new Set<string>(this.userProfile!.mutedInstances ?? []))) return;
 
 		// 関係ない返信は除外
-		if (note.reply && !this.user!.showTimelineReplies) {
+		if (note.reply && !this.withReplies) {
 			const reply = note.reply;
 			// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
 			if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return;
diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts
index 09b0005ac..9ca4db8ce 100644
--- a/packages/backend/src/server/api/stream/channels/local-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts
@@ -12,6 +12,7 @@ class LocalTimelineChannel extends Channel {
 	public readonly chName = 'localTimeline';
 	public static shouldShare = true;
 	public static requireCredential = false;
+	private withReplies: boolean;
 
 	constructor(
 		private metaService: MetaService,
@@ -30,6 +31,8 @@ class LocalTimelineChannel extends Channel {
 		const policies = await this.roleService.getUserPolicies(this.user ? this.user.id : null);
 		if (!policies.ltlAvailable) return;
 
+		this.withReplies = params.withReplies as boolean;
+
 		// Subscribe events
 		this.subscriber.on('notesStream', this.onNote);
 	}
@@ -54,7 +57,7 @@ class LocalTimelineChannel extends Channel {
 		}
 
 		// 関係ない返信は除外
-		if (note.reply && this.user && !this.user.showTimelineReplies) {
+		if (note.reply && this.user && !this.withReplies) {
 			const reply = note.reply;
 			// 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合
 			if (reply.userId !== this.user.id && note.userId !== this.user.id && reply.userId !== note.userId) return;
diff --git a/packages/backend/src/server/api/stream/channels/role-timeline.ts b/packages/backend/src/server/api/stream/channels/role-timeline.ts
index 9d106c8b2..ab9c1aa0b 100644
--- a/packages/backend/src/server/api/stream/channels/role-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/role-timeline.ts
@@ -5,15 +5,17 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import { bindThis } from '@/decorators.js';
 import Channel from '../channel.js';
 import { StreamMessages } from '../types.js';
+import { RoleService } from '@/core/RoleService.js';
 
 class RoleTimelineChannel extends Channel {
 	public readonly chName = 'roleTimeline';
 	public static shouldShare = false;
 	public static requireCredential = false;
 	private roleId: string;
-
+	
 	constructor(
 		private noteEntityService: NoteEntityService,
+		private roleservice: RoleService,
 
 		id: string,
 		connection: Channel['connection'],
@@ -34,6 +36,11 @@ class RoleTimelineChannel extends Channel {
 		if (data.type === 'note') {
 			const note = data.body;
 
+			if (!(await this.roleservice.isExplorable({ id: this.roleId }))) {
+				return;
+			}
+			if (note.visibility !== 'public') return;
+
 			// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
 			if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
 			// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
@@ -61,6 +68,7 @@ export class RoleTimelineChannelService {
 
 	constructor(
 		private noteEntityService: NoteEntityService,
+		private roleservice: RoleService,
 	) {
 	}
 
@@ -68,6 +76,7 @@ export class RoleTimelineChannelService {
 	public create(id: string, connection: Channel['connection']): RoleTimelineChannel {
 		return new RoleTimelineChannel(
 			this.noteEntityService,
+			this.roleservice,
 			id,
 			connection,
 		);
diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts
index a6f914595..8b1c2c09c 100644
--- a/packages/backend/src/server/api/stream/index.ts
+++ b/packages/backend/src/server/api/stream/index.ts
@@ -1,3 +1,4 @@
+import * as WebSocket from 'ws';
 import type { User } from '@/models/entities/User.js';
 import type { AccessToken } from '@/models/entities/AccessToken.js';
 import type { Packed } from '@/misc/json-schema.js';
@@ -7,7 +8,6 @@ import { bindThis } from '@/decorators.js';
 import { CacheService } from '@/core/CacheService.js';
 import { UserProfile } from '@/models/index.js';
 import type { ChannelsService } from './ChannelsService.js';
-import type * as websocket from 'websocket';
 import type { EventEmitter } from 'events';
 import type Channel from './channel.js';
 import type { StreamEventEmitter, StreamMessages } from './types.js';
@@ -18,7 +18,7 @@ import type { StreamEventEmitter, StreamMessages } from './types.js';
 export default class Connection {
 	public user?: User;
 	public token?: AccessToken;
-	private wsConnection: websocket.connection;
+	private wsConnection: WebSocket.WebSocket;
 	public subscriber: StreamEventEmitter;
 	private channels: Channel[] = [];
 	private subscribingNotes: any = {};
@@ -37,11 +37,9 @@ export default class Connection {
 		private notificationService: NotificationService,
 		private cacheService: CacheService,
 
-		subscriber: EventEmitter,
 		user: User | null | undefined,
 		token: AccessToken | null | undefined,
 	) {
-		this.subscriber = subscriber;
 		if (user) this.user = user;
 		if (token) this.token = token;
 	}
@@ -70,12 +68,16 @@ export default class Connection {
 		if (this.user != null) {
 			await this.fetch();
 
-			this.fetchIntervalId = setInterval(this.fetch, 1000 * 10);
+			if (!this.fetchIntervalId) {
+				this.fetchIntervalId = setInterval(this.fetch, 1000 * 10);
+			}
 		}
 	}
 
 	@bindThis
-	public async init2(wsConnection: websocket.connection) {
+	public async listen(subscriber: EventEmitter, wsConnection: WebSocket.WebSocket) {
+		this.subscriber = subscriber;
+
 		this.wsConnection = wsConnection;
 		this.wsConnection.on('message', this.onWsConnectionMessage);
 
@@ -88,14 +90,11 @@ export default class Connection {
 	 * クライアントからメッセージ受信時
 	 */
 	@bindThis
-	private async onWsConnectionMessage(data: websocket.Message) {
-		if (data.type !== 'utf8') return;
-		if (data.utf8Data == null) return;
-
+	private async onWsConnectionMessage(data: WebSocket.RawData) {
 		let obj: Record<string, any>;
 
 		try {
-			obj = JSON.parse(data.utf8Data);
+			obj = JSON.parse(data.toString());
 		} catch (e) {
 			return;
 		}
@@ -246,7 +245,7 @@ export default class Connection {
 
 		const ch: Channel = channelService.create(id, this);
 		this.channels.push(ch);
-		ch.init(params);
+		ch.init(params ?? {});
 
 		if (pong) {
 			this.sendMessageToWs('connected', {
diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js
index fd7f54da5..38ae8ad2e 100644
--- a/packages/backend/src/server/web/boot.js
+++ b/packages/backend/src/server/web/boot.js
@@ -116,9 +116,9 @@
 			}
 		}
 	}
-	const colorSchema = localStorage.getItem('colorSchema');
-	if (colorSchema) {
-		document.documentElement.style.setProperty('color-schema', colorSchema);
+	const colorScheme = localStorage.getItem('colorScheme');
+	if (colorScheme) {
+		document.documentElement.style.setProperty('color-scheme', colorScheme);
 	}
 	//#endregion
 
@@ -160,37 +160,41 @@
 				<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>
 			</svg>
-			<h1>An error has occurred!</h1>
-			<button class="button-big" onclick="location.reload();">
-				<span class="button-label-big">Refresh</span>
+			<h1>Failed to load<br>読み込みに失敗しました</h1>
+			<button class="button-big" onclick="location.reload(true);">
+				<span class="button-label-big">Reload / リロード</span>
 			</button>
-			<p class="dont-worry">Don't worry, it's (probably) not your fault.</p>
-			<p>If the problem persists after refreshing, please contact your instance's administrator.<br>You may also try the following options:</p>
-			<p>Update your os and browser.</p>
-			<p>Disable an adblocker.</p>
-			<a href="/flush">
-				<button class="button-small">
-					<span class="button-label-small">Clear preferences and cache</span>
-				</button>
-			</a>
-			<br>
-			<a href="/cli">
-				<button class="button-small">
-					<span class="button-label-small">Start the simple client</span>
-				</button>
-			</a>
-			<br>
-			<a href="/bios">
-				<button class="button-small">
-					<span class="button-label-small">Start the repair tool</span>
-				</button>
-			</a>
+			<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>
+			<details style="color: #86b300;">
+				<summary>Other options / その他のオプション</summary>
+				<a href="/flush">
+					<button class="button-small">
+						<span class="button-label-small">Clear preferences and cache</span>
+					</button>
+				</a>
+				<br>
+				<a href="/cli">
+					<button class="button-small">
+						<span class="button-label-small">Start the simple client</span>
+					</button>
+				</a>
+				<br>
+				<a href="/bios">
+					<button class="button-small">
+						<span class="button-label-small">Start the repair tool</span>
+					</button>
+				</a>
+			</details>
 			<br>
 			<div id="errors"></div>
 			`;
 			errorsElement = document.getElementById('errors');
 		}
 		const detailsElement = document.createElement('details');
+		detailsElement.id = 'errorInfo';
 		detailsElement.innerHTML = `
 		<br>
 		<summary>
@@ -247,7 +251,7 @@
 		.button-label-big {
 			color: #222;
 			font-weight: bold;
-			font-size: 20px;
+			font-size: 1.2em;
 			padding: 12px;
 		}
 
@@ -267,11 +271,6 @@
 			font-size: 16px;
 		}
 
-		.dont-worry,
-		#msg {
-			font-size: 18px;
-		}
-
 		.icon-warning {
 			color: #dec340;
 			height: 4rem;
@@ -279,14 +278,15 @@
 		}
 
 		h1 {
-			font-size: 32px;
+			font-size: 1.5em;
+			margin: 1em;
 		}
 
 		code {
 			font-family: Fira, FiraCode, monospace;
 		}
 
-		details {
+		#errorInfo {
 			background: #333;
 			margin-bottom: 2rem;
 			padding: 0.5rem 1rem;
@@ -296,16 +296,16 @@
 			margin: auto;
 		}
 
-		summary {
+		#errorInfo summary {
 			cursor: pointer;
 		}
 
-		summary > * {
+		#errorInfo summary > * {
 			display: inline;
 		}
 
 		@media screen and (max-width: 500px) {
-			details {
+			#errorInfo {
 				width: 50%;
 			}
 		`)
diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug
index cb5d05a40..69b3f68e0 100644
--- a/packages/backend/src/server/web/views/base.pug
+++ b/packages/backend/src/server/web/views/base.pug
@@ -25,7 +25,6 @@ html
 		meta(name='referrer' content='origin')
 		meta(name='theme-color' content= themeColor || '#86b300')
 		meta(name='theme-color-orig' content= themeColor || '#86b300')
-		meta(property='twitter:card' content='summary')
 		meta(property='og:site_name' content= instanceName || 'Misskey')
 		meta(name='viewport' content='width=device-width, initial-scale=1')
 		link(rel='icon' href= icon || '/favicon.ico')
@@ -36,7 +35,7 @@ html
 		link(rel='prefetch' href='https://xn--931a.moe/assets/not-found.jpg')
 		link(rel='prefetch' href='https://xn--931a.moe/assets/error.jpg')
 		//- https://github.com/misskey-dev/misskey/issues/9842
-		link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.17.0')
+		link(rel='stylesheet' href='/assets/tabler-icons/tabler-icons.min.css?v2.21.0')
 		link(rel='modulepreload' href=`/vite/${clientEntry.file}`)
 
 		if !config.clientManifestExists
@@ -59,6 +58,7 @@ html
 			meta(property='og:title'       content= title || 'Misskey') 
 			meta(property='og:description' content= desc || '✨🌎✨ A interplanetary communication platform ✨🚀✨') 
 			meta(property='og:image'       content= img)
+			meta(property='twitter:card'   content='summary')
 
 		style
 			include ../style.css
diff --git a/packages/backend/src/server/web/views/channel.pug b/packages/backend/src/server/web/views/channel.pug
index 486f0ecc4..c514025e0 100644
--- a/packages/backend/src/server/web/views/channel.pug
+++ b/packages/backend/src/server/web/views/channel.pug
@@ -16,3 +16,4 @@ block og
 	meta(property='og:description' content= channel.description)
 	meta(property='og:url'         content= url)
 	meta(property='og:image'       content= channel.bannerUrl)
+	meta(property='twitter:card'   content='summary')
diff --git a/packages/backend/src/server/web/views/clip.pug b/packages/backend/src/server/web/views/clip.pug
index 74dc62f1e..5a0018803 100644
--- a/packages/backend/src/server/web/views/clip.pug
+++ b/packages/backend/src/server/web/views/clip.pug
@@ -17,6 +17,7 @@ block og
 	meta(property='og:description' content= clip.description)
 	meta(property='og:url'         content= url)
 	meta(property='og:image'       content= avatarUrl)
+	meta(property='twitter:card'   content='summary')
 
 block meta
 	if profile.noCrawle
diff --git a/packages/backend/src/server/web/views/flash.pug b/packages/backend/src/server/web/views/flash.pug
index 5594fcdfb..1549aa790 100644
--- a/packages/backend/src/server/web/views/flash.pug
+++ b/packages/backend/src/server/web/views/flash.pug
@@ -17,6 +17,7 @@ block og
 	meta(property='og:description' content= flash.summary)
 	meta(property='og:url'         content= url)
 	meta(property='og:image'       content= avatarUrl)
+	meta(property='twitter:card'   content='summary')
 
 block meta
 	if profile.noCrawle
diff --git a/packages/backend/src/server/web/views/gallery-post.pug b/packages/backend/src/server/web/views/gallery-post.pug
index 10f2d269b..a458d7f8c 100644
--- a/packages/backend/src/server/web/views/gallery-post.pug
+++ b/packages/backend/src/server/web/views/gallery-post.pug
@@ -17,6 +17,7 @@ block og
 	meta(property='og:description' content= post.description)
 	meta(property='og:url'         content= url)
 	meta(property='og:image'       content= post.files[0].thumbnailUrl)
+	meta(property='twitter:card'   content='summary_large_image')
 
 block meta
 	if user.host || profile.noCrawle
diff --git a/packages/backend/src/server/web/views/note.pug b/packages/backend/src/server/web/views/note.pug
index badfcccd6..874c48c60 100644
--- a/packages/backend/src/server/web/views/note.pug
+++ b/packages/backend/src/server/web/views/note.pug
@@ -5,6 +5,8 @@ block vars
 	- const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`;
 	- const url = `${config.url}/notes/${note.id}`;
 	- const isRenote = note.renote && note.text == null && note.fileIds.length == 0 && note.poll == null;
+	- const image = (note.files || []).find(file => file.type.startsWith('image/') && !file.type.isSensitive)
+	- const video = (note.files || []).find(file => file.type.startsWith('video/') && !file.type.isSensitive)
 
 block title
 	= `${title} | ${instanceName}`
@@ -17,7 +19,19 @@ block og
 	meta(property='og:title'       content= title)
 	meta(property='og:description' content= summary)
 	meta(property='og:url'         content= url)
-	meta(property='og:image'       content= avatarUrl)
+	if video
+		meta(property='og:video:url'        content= video.url)
+		meta(property='og:video:secure_url' content= video.url)
+		meta(property='og:video:type'       content= video.type)
+		// FIXME: add width and height
+		// FIXME: add embed player for Twitter
+	if image
+		meta(property='twitter:card' content='summary_large_image')
+		meta(property='og:image'     content= image.url)
+	else
+		meta(property='twitter:card' content='summary')
+		meta(property='og:image'     content= avatarUrl)
+
 
 block meta
 	if user.host || isRenote || profile.noCrawle
diff --git a/packages/backend/src/server/web/views/page.pug b/packages/backend/src/server/web/views/page.pug
index ddffc361c..08bb08ffe 100644
--- a/packages/backend/src/server/web/views/page.pug
+++ b/packages/backend/src/server/web/views/page.pug
@@ -17,6 +17,7 @@ block og
 	meta(property='og:description' content= page.summary)
 	meta(property='og:url'         content= url)
 	meta(property='og:image'       content= page.eyeCatchingImage ? page.eyeCatchingImage.thumbnailUrl : avatarUrl)
+	meta(property='twitter:card'   content= page.eyeCatchingImage ? 'summary_large_image' : 'summary')
 
 block meta
 	if profile.noCrawle
diff --git a/packages/backend/src/server/web/views/user.pug b/packages/backend/src/server/web/views/user.pug
index f4c83aa89..83d57349a 100644
--- a/packages/backend/src/server/web/views/user.pug
+++ b/packages/backend/src/server/web/views/user.pug
@@ -16,6 +16,7 @@ block og
 	meta(property='og:description' content= profile.description)
 	meta(property='og:url'         content= url)
 	meta(property='og:image'       content= avatarUrl)
+	meta(property='twitter:card'   content='summary')
 
 block meta
 	if user.host || profile.noCrawle
diff --git a/packages/backend/test/e2e/2fa.ts b/packages/backend/test/e2e/2fa.ts
index 0addb430c..5da997f28 100644
--- a/packages/backend/test/e2e/2fa.ts
+++ b/packages/backend/test/e2e/2fa.ts
@@ -2,7 +2,7 @@ process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
 import * as crypto from 'node:crypto';
-import * as cbor from 'cbor';
+import cbor from 'cbor';
 import * as OTPAuth from 'otpauth';
 import { loadConfig } from '../../src/config.js';
 import { signup, api, post, react, startServer, waitFire } from '../utils.js';
diff --git a/packages/backend/test/e2e/antennas.ts b/packages/backend/test/e2e/antennas.ts
new file mode 100644
index 000000000..dd3b09f85
--- /dev/null
+++ b/packages/backend/test/e2e/antennas.ts
@@ -0,0 +1,653 @@
+process.env.NODE_ENV = 'test';
+
+import * as assert from 'assert';
+import { inspect } from 'node:util';
+import { DEFAULT_POLICIES } from '@/core/RoleService.js';
+import type { Packed } from '@/misc/json-schema.js';
+import {
+	signup,
+	post,
+	userList,
+	page,
+	role,
+	startServer,
+	api,
+	successfulApiCall,
+	failedApiCall,
+	uploadFile,
+	testPaginationConsistency,
+} from '../utils.js';
+import type * as misskey from 'misskey-js';
+import type { INestApplicationContext } from '@nestjs/common';
+
+const compareBy = <T extends { id: string }>(selector: (s: T) => string = (s: T): string => s.id) => (a: T, b: T): number => {
+	return selector(a).localeCompare(selector(b));
+};
+
+describe('アンテナ', () => {
+	// エンティティとしてのアンテナを主眼においたテストを記述する
+	// (Antennaを返すエンドポイント、Antennaエンティティを書き換えるエンドポイント、Antennaからノートを取得するエンドポイントをテストする)
+
+	// BUG misskey-jsとjson-schemaが一致していない。
+	// - srcのenumにgroupが残っている
+	// - userGroupIdが残っている, isActiveがない
+	type Antenna = misskey.entities.Antenna | Packed<'Antenna'>;
+	type User = misskey.entities.MeDetailed & { token: string };
+	type Note = misskey.entities.Note;
+
+	// アンテナを作成できる最小のパラメタ
+	const defaultParam = {
+		caseSensitive: false,
+		excludeKeywords: [['']],
+		keywords: [['keyword']],
+		name: 'test',
+		notify: false,
+		src: 'all' as const,
+		userListId: null,
+		users: [''],
+		withFile: false,
+		withReplies: false,
+	};
+
+	let app: INestApplicationContext;
+
+	let root: User;
+	let alice: User;
+	let bob: User;
+	let carol: User;
+
+	let alicePost: Note;
+	let aliceList: misskey.entities.UserList;
+	let bobFile: misskey.entities.DriveFile;
+	let bobList: misskey.entities.UserList;
+
+	let userNotExplorable: User;
+	let userLocking: User;
+	let userSilenced: User;
+	let userSuspended: User;
+	let userDeletedBySelf: User;
+	let userDeletedByAdmin: User;
+	let userFollowingAlice: User;
+	let userFollowedByAlice: User;
+	let userBlockingAlice: User;
+	let userBlockedByAlice: User;
+	let userMutingAlice: User;
+	let userMutedByAlice: User;
+
+	beforeAll(async () => {
+		app = await startServer();
+	}, 1000 * 60 * 2);
+
+	beforeAll(async () => {
+		root = await signup({ username: 'root' });
+		alice = await signup({ username: 'alice' });
+		alicePost = await post(alice, { text: 'test' });
+		aliceList = await userList(alice, {});
+		bob = await signup({ username: 'bob' });
+		aliceList = await userList(alice, {});
+		bobFile = (await uploadFile(bob)).body;
+		bobList = await userList(bob);
+		carol = await signup({ username: 'carol' });
+		await api('users/lists/push', { listId: aliceList.id, userId: bob.id }, alice);
+		await api('users/lists/push', { listId: aliceList.id, userId: carol.id }, alice);
+
+		userNotExplorable = await signup({ username: 'userNotExplorable' });
+		await post(userNotExplorable, { text: 'test' });
+		await api('i/update', { isExplorable: false }, userNotExplorable);
+		userLocking = await signup({ username: 'userLocking' });
+		await post(userLocking, { text: 'test' });
+		await api('i/update', { isLocked: true }, userLocking);
+		userSilenced = await signup({ username: 'userSilenced' });
+		await post(userSilenced, { text: 'test' });
+		const roleSilenced = await role(root, {}, { canPublicNote: { priority: 0, useDefault: false, value: false } });
+		await api('admin/roles/assign', { userId: userSilenced.id, roleId: roleSilenced.id }, root);
+		userSuspended = await signup({ username: 'userSuspended' });
+		await post(userSuspended, { text: 'test' });
+		await successfulApiCall({ endpoint: 'i/update', parameters: { description: '#user_testuserSuspended' }, user: userSuspended });
+		await api('admin/suspend-user', { userId: userSuspended.id }, root);
+		userDeletedBySelf = await signup({ username: 'userDeletedBySelf', password: 'userDeletedBySelf' });
+		await post(userDeletedBySelf, { text: 'test' });
+		await api('i/delete-account', { password: 'userDeletedBySelf' }, userDeletedBySelf);
+		userDeletedByAdmin = await signup({ username: 'userDeletedByAdmin' });
+		await post(userDeletedByAdmin, { text: 'test' });
+		await api('admin/delete-account', { userId: userDeletedByAdmin.id }, root);
+		userFollowedByAlice = await signup({ username: 'userFollowedByAlice' });
+		await post(userFollowedByAlice, { text: 'test' });
+		await api('following/create', { userId: userFollowedByAlice.id }, alice);
+		userFollowingAlice = await signup({ username: 'userFollowingAlice' });
+		await post(userFollowingAlice, { text: 'test' });
+		await api('following/create', { userId: alice.id }, userFollowingAlice);
+		userBlockingAlice = await signup({ username: 'userBlockingAlice' });
+		await post(userBlockingAlice, { text: 'test' });
+		await api('blocking/create', { userId: alice.id }, userBlockingAlice);
+		userBlockedByAlice = await signup({ username: 'userBlockedByAlice' });
+		await post(userBlockedByAlice, { text: 'test' });
+		await api('blocking/create', { userId: userBlockedByAlice.id }, alice);
+		userMutingAlice = await signup({ username: 'userMutingAlice' });
+		await post(userMutingAlice, { text: 'test' });
+		await api('mute/create', { userId: alice.id }, userMutingAlice);
+		userMutedByAlice = await signup({ username: 'userMutedByAlice' });
+		await post(userMutedByAlice, { text: 'test' });
+		await api('mute/create', { userId: userMutedByAlice.id }, alice);
+	}, 1000 * 60 * 10);
+
+	afterAll(async () => {
+		await app.close();
+	});
+
+	beforeEach(async () => {
+		// テスト間で影響し合わないように毎回全部消す。
+		for (const user of [alice, bob]) {
+			const list = await api('/antennas/list', {}, user);
+			for (const antenna of list.body) {
+				await api('/antennas/delete', { antennaId: antenna.id }, user);
+			}
+		}
+	});
+
+	//#region 作成(antennas/create)
+
+	test('が作成できること、キーが過不足なく入っていること。', async () => {
+		const response = await successfulApiCall({
+			endpoint: 'antennas/create',
+			parameters: { ...defaultParam },
+			user: alice,
+		});
+		assert.match(response.id, /[0-9a-z]{10}/);
+		const expected = {
+			id: response.id,
+			caseSensitive: false,
+			createdAt: new Date(response.createdAt).toISOString(),
+			excludeKeywords: [['']],
+			hasUnreadNote: false,
+			isActive: true,
+			keywords: [['keyword']],
+			name: 'test',
+			notify: false,
+			src: 'all',
+			userListId: null,
+			users: [''],
+			withFile: false,
+			withReplies: false,
+		} as Antenna;
+		assert.deepStrictEqual(response, expected);
+	});
+
+	test('が上限いっぱいまで作成できること', async () => {
+		// antennaLimit + 1まで作れるのがキモ
+		const response = await Promise.all([...Array(DEFAULT_POLICIES.antennaLimit + 1)].map(() => successfulApiCall({
+			endpoint: 'antennas/create',
+			parameters: { ...defaultParam },
+			user: alice,
+		})));
+
+		const expected = await successfulApiCall({ endpoint: 'antennas/list', parameters: {}, user: alice });
+		assert.deepStrictEqual(
+			response.sort(compareBy(s => s.id)),
+			expected.sort(compareBy(s => s.id)));
+
+		failedApiCall({
+			endpoint: 'antennas/create',
+			parameters: { ...defaultParam },
+			user: alice,
+		}, {
+			status: 400,
+			code: 'TOO_MANY_ANTENNAS',
+			id: 'faf47050-e8b5-438c-913c-db2b1576fde4',
+		});
+	});
+
+	test('を作成するとき他人のリストを指定したらエラーになる', async () => {
+		failedApiCall({
+			endpoint: 'antennas/create',
+			parameters: { ...defaultParam, src: 'list', userListId: bobList.id },
+			user: alice,
+		}, {
+			status: 400,
+			code: 'NO_SUCH_USER_LIST',
+			id: '95063e93-a283-4b8b-9aa5-bcdb8df69a7f',
+		});
+	});
+
+	const antennaParamPattern = [
+		{ parameters: (): object => ({ name: 'x'.repeat(100) }) },
+		{ parameters: (): object => ({ name: 'x' }) },
+		{ parameters: (): object => ({ src: 'home' }) },
+		{ parameters: (): object => ({ src: 'all' }) },
+		{ parameters: (): object => ({ src: 'users' }) },
+		{ parameters: (): object => ({ src: 'list' }) },
+		{ parameters: (): object => ({ userListId: null }) },
+		{ parameters: (): object => ({ src: 'list', userListId: aliceList.id }) },
+		{ parameters: (): object => ({ keywords: [['x']] }) },
+		{ parameters: (): object => ({ keywords: [['a', 'b', 'c'], ['x'], ['y'], ['z']] }) },
+		{ parameters: (): object => ({ excludeKeywords: [['a', 'b', 'c'], ['x'], ['y'], ['z']] }) },
+		{ parameters: (): object => ({ users: [alice.username] }) },
+		{ parameters: (): object => ({ users: [alice.username, bob.username, carol.username] }) },
+		{ parameters: (): object => ({ caseSensitive: false }) },
+		{ parameters: (): object => ({ caseSensitive: true }) },
+		{ parameters: (): object => ({ withReplies: false }) },
+		{ parameters: (): object => ({ withReplies: true }) },
+		{ parameters: (): object => ({ withFile: false }) },
+		{ parameters: (): object => ({ withFile: true }) },
+		{ parameters: (): object => ({ notify: false }) },
+		{ parameters: (): object => ({ notify: true }) },
+	];
+	test.each(antennaParamPattern)('を作成できること($#)', async ({ parameters }) => {
+		const response = await successfulApiCall({
+			endpoint: 'antennas/create',
+			parameters: { ...defaultParam, ...parameters() },
+			user: alice,
+		});
+		const expected = { ...response, ...parameters() };
+		assert.deepStrictEqual(response, expected);
+	});
+
+	//#endregion
+	//#region 更新(antennas/update)
+
+	test.each(antennaParamPattern)('を変更できること($#)', async ({ parameters }) => {
+		const antenna = await successfulApiCall({ endpoint: 'antennas/create', parameters: defaultParam, user: alice });
+		const response = await successfulApiCall({
+			endpoint: 'antennas/update',
+			parameters: { antennaId: antenna.id, ...defaultParam, ...parameters() },
+			user: alice,
+		});
+		const expected = { ...response, ...parameters() };
+		assert.deepStrictEqual(response, expected);
+	});
+	test.todo('は他人のものは変更できない');
+
+	test('を変更するとき他人のリストを指定したらエラーになる', async () => {
+		const antenna = await successfulApiCall({ endpoint: 'antennas/create', parameters: defaultParam, user: alice });
+		failedApiCall({
+			endpoint: 'antennas/update',
+			parameters: { antennaId: antenna.id, ...defaultParam, src: 'list', userListId: bobList.id },
+			user: alice,
+		}, {
+			status: 400,
+			code: 'NO_SUCH_USER_LIST',
+			id: '1c6b35c9-943e-48c2-81e4-2844989407f7',
+		});
+	});
+
+	//#endregion
+	//#region 表示(antennas/show)
+
+	test('をID指定で表示できること。', async () => {
+		const antenna = await successfulApiCall({ endpoint: 'antennas/create', parameters: defaultParam, user: alice });
+		const response = await successfulApiCall({
+			endpoint: 'antennas/show',
+			parameters: { antennaId: antenna.id },
+			user: alice,
+		});
+		const expected = { ...antenna };
+		assert.deepStrictEqual(response, expected);
+	});
+	test.todo('は他人のものをID指定で表示できない');
+
+	//#endregion
+	//#region 一覧(antennas/list)
+
+	test('をリスト形式で取得できること。', async () => {
+		const antenna = await successfulApiCall({ endpoint: 'antennas/create', parameters: defaultParam, user: alice });
+		await successfulApiCall({ endpoint: 'antennas/create', parameters: defaultParam, user: bob });
+		const response = await successfulApiCall({
+			endpoint: 'antennas/list',
+			parameters: {},
+			user: alice,
+		});
+		const expected = [{ ...antenna }];
+		assert.deepStrictEqual(response, expected);
+	});
+
+	//#endregion
+	//#region 削除(antennas/delete)
+
+	test('を削除できること。', async () => {
+		const antenna = await successfulApiCall({ endpoint: 'antennas/create', parameters: defaultParam, user: alice });
+		const response = await successfulApiCall({
+			endpoint: 'antennas/delete',
+			parameters: { antennaId: antenna.id },
+			user: alice,
+		});
+		assert.deepStrictEqual(response, null);
+		const list = await successfulApiCall({ endpoint: 'antennas/list', parameters: {}, user: alice });
+		assert.deepStrictEqual(list, []);
+	});
+	test.todo('は他人のものを削除できない');
+
+	//#endregion
+
+	describe('のノート', () => {
+		//#region アンテナのノート取得(antennas/notes)
+
+		test('を取得できること。', async () => {
+			const keyword = 'キーワード';
+			await post(bob, { text: `test ${keyword} beforehand` });
+			const antenna = await successfulApiCall({
+				endpoint: 'antennas/create',
+				parameters: { ...defaultParam, keywords: [[keyword]] },
+				user: alice,
+			});
+			const note = await post(bob, { text: `test ${keyword}` });
+			const response = await successfulApiCall({
+				endpoint: 'antennas/notes',
+				parameters: { antennaId: antenna.id },
+				user: alice,
+			});
+			const expected = [note];
+			assert.deepStrictEqual(response, expected);
+		});
+
+		const keyword = 'キーワード';
+		test.each([
+			{
+				label: '全体から',
+				parameters: (): object => ({ src: 'all' }),
+				posts: [
+					{ note: (): Promise<Note> => post(alice, { text: `${keyword}` }), included: true },
+					{ note: (): Promise<Note> => post(userFollowedByAlice, { text: `${keyword}` }), included: true },
+					{ note: (): Promise<Note> => post(bob, { text: `test ${keyword}` }), included: true },
+					{ note: (): Promise<Note> => post(carol, { text: `test ${keyword}` }), included: true },
+				],
+			},
+			{
+				// BUG e4144a1 以降home指定は壊れている(allと同じ)
+				label: 'ホーム指定はallと同じ',
+				parameters: (): object => ({ src: 'home' }),
+				posts: [
+					{ note: (): Promise<Note> => post(alice, { text: `${keyword}` }), included: true },
+					{ note: (): Promise<Note> => post(userFollowedByAlice, { text: `${keyword}` }), included: true },
+					{ note: (): Promise<Note> => post(bob, { text: `test ${keyword}` }), included: true },
+					{ note: (): Promise<Note> => post(carol, { text: `test ${keyword}` }), included: true },
+				],
+			},
+			{
+				// https://github.com/misskey-dev/misskey/issues/9025
+				label: 'ただし、フォロワー限定投稿とDM投稿を含まない。フォロワーであっても。',
+				parameters: (): object => ({}),
+				posts: [
+					{ note: (): Promise<Note> => post(userFollowedByAlice, { text: `${keyword}`, visibility: 'public' }), included: true },
+					{ note: (): Promise<Note> => post(userFollowedByAlice, { text: `${keyword}`, visibility: 'home' }), included: true },
+					{ note: (): Promise<Note> => post(userFollowedByAlice, { text: `${keyword}`, visibility: 'followers' }) },
+					{ note: (): Promise<Note> => post(userFollowedByAlice, { text: `${keyword}`, visibility: 'specified', visibleUserIds: [alice.id] }) },
+				],
+			},
+			{
+				label: 'ブロックしているユーザーのノートは含む',
+				parameters: (): object => ({}),
+				posts: [
+					{ note: (): Promise<Note> => post(userBlockedByAlice, { text: `${keyword}` }), included: true },
+				],
+			},
+			{
+				label: 'ブロックされているユーザーのノートは含まない',
+				parameters: (): object => ({}),
+				posts: [
+					{ note: (): Promise<Note> => post(userBlockingAlice, { text: `${keyword}` }) },
+				],
+			},
+			{
+				label: 'ミュートしているユーザーのノートは含まない',
+				parameters: (): object => ({}),
+				posts: [
+					{ note: (): Promise<Note> => post(userMutedByAlice, { text: `${keyword}` }) },
+				],
+			},
+			{
+				label: 'ミュートされているユーザーのノートは含む',
+				parameters: (): object => ({}),
+				posts: [
+					{ note: (): Promise<Note> => post(userMutingAlice, { text: `${keyword}` }), included: true },
+				],
+			},
+			{
+				label: '「見つけやすくする」がOFFのユーザーのノートも含まれる',
+				parameters: (): object => ({}),
+				posts: [
+					{ note: (): Promise<Note> => post(userNotExplorable, { text: `${keyword}` }), included: true },
+				],
+			},
+			{
+				label: '鍵付きユーザーのノートも含まれる',
+				parameters: (): object => ({}),
+				posts: [
+					{ note: (): Promise<Note> => post(userLocking, { text: `${keyword}` }), included: true },
+				],
+			},
+			{
+				label: 'サイレンスのノートも含まれる',
+				parameters: (): object => ({}),
+				posts: [
+					{ note: (): Promise<Note> => post(userSilenced, { text: `${keyword}` }), included: true },
+				],
+			},
+			{
+				label: '削除ユーザーのノートも含まれる',
+				parameters: (): object => ({}),
+				posts: [
+					{ note: (): Promise<Note> => post(userDeletedBySelf, { text: `${keyword}` }), included: true },
+					{ note: (): Promise<Note> => post(userDeletedByAdmin, { text: `${keyword}` }), included: true },
+				],
+			},
+			{
+				label: 'ユーザー指定で',
+				parameters: (): object => ({ src: 'users', users: [`@${bob.username}`, `@${carol.username}`] }),
+				posts: [
+					{ note: (): Promise<Note> => post(alice, { text: `test ${keyword}` }) },
+					{ note: (): Promise<Note> => post(bob, { text: `test ${keyword}` }), included: true },
+					{ note: (): Promise<Note> => post(carol, { text: `test ${keyword}` }), included: true },
+				],
+			},
+			{
+				label: 'リスト指定で',
+				parameters: (): object => ({ src: 'list', userListId: aliceList.id }),
+				posts: [
+					{ note: (): Promise<Note> => post(alice, { text: `test ${keyword}` }) },
+					{ note: (): Promise<Note> => post(bob, { text: `test ${keyword}` }), included: true },
+					{ note: (): Promise<Note> => post(carol, { text: `test ${keyword}` }), included: true },
+				],
+			},
+			{
+				label: 'CWにもマッチする',
+				parameters: (): object => ({ keywords: [[keyword]] }),
+				posts: [
+					{ note: (): Promise<Note> => post(bob, { text: 'test', cw: `cw ${keyword}` }), included: true },
+				],
+			},
+			{
+				label: 'キーワード1つ',
+				parameters: (): object => ({ keywords: [[keyword]] }),
+				posts: [
+					{ note: (): Promise<Note> => post(alice, { text: 'test' }) },
+					{ note: (): Promise<Note> => post(bob, { text: `test ${keyword}` }), included: true },
+					{ note: (): Promise<Note> => post(carol, { text: 'test' }) },
+				],
+			},
+			{
+				label: 'キーワード3つ(AND)',
+				parameters: (): object => ({ keywords: [['A', 'B', 'C']] }),
+				posts: [
+					{ note: (): Promise<Note> => post(bob, { text: 'test A' }) },
+					{ note: (): Promise<Note> => post(bob, { text: 'test A B' }) },
+					{ note: (): Promise<Note> => post(bob, { text: 'test B C' }) },
+					{ note: (): Promise<Note> => post(bob, { text: 'test A B C' }), included: true },
+					{ note: (): Promise<Note> => post(bob, { text: 'test C B A A B C' }), included: true },
+				],
+			},
+			{
+				label: 'キーワード3つ(OR)',
+				parameters: (): object => ({ keywords: [['A'], ['B'], ['C']] }),
+				posts: [
+					{ note: (): Promise<Note> => post(bob, { text: 'test' }) },
+					{ note: (): Promise<Note> => post(bob, { text: 'test A' }), included: true },
+					{ note: (): Promise<Note> => post(bob, { text: 'test A B' }), included: true },
+					{ note: (): Promise<Note> => post(bob, { text: 'test B C' }), included: true },
+					{ note: (): Promise<Note> => post(bob, { text: 'test B C A' }), included: true },
+					{ note: (): Promise<Note> => post(bob, { text: 'test C B' }), included: true },
+					{ note: (): Promise<Note> => post(bob, { text: 'test C' }), included: true },
+				],
+			},
+			{
+				label: '除外ワード3つ(AND)',
+				parameters: (): object => ({ excludeKeywords: [['A', 'B', 'C']] }),
+				posts: [
+					{ note: (): Promise<Note> => post(bob, { text: `test ${keyword}` }), included: true },
+					{ note: (): Promise<Note> => post(bob, { text: `test ${keyword} A` }), included: true },
+					{ note: (): Promise<Note> => post(bob, { text: `test ${keyword} A B` }), included: true },
+					{ note: (): Promise<Note> => post(bob, { text: `test ${keyword} B C` }), included: true },
+					{ note: (): Promise<Note> => post(bob, { text: `test ${keyword} B C A` }) },
+					{ note: (): Promise<Note> => post(bob, { text: `test ${keyword} C B` }), included: true },
+					{ note: (): Promise<Note> => post(bob, { text: `test ${keyword} C` }), included: true },
+				],
+			},
+			{
+				label: '除外ワード3つ(OR)',
+				parameters: (): object => ({ excludeKeywords: [['A'], ['B'], ['C']] }),
+				posts: [
+					{ note: (): Promise<Note> => post(bob, { text: `test ${keyword}` }), included: true },
+					{ note: (): Promise<Note> => post(bob, { text: `test ${keyword} A` }) },
+					{ note: (): Promise<Note> => post(bob, { text: `test ${keyword} A B` }) },
+					{ note: (): Promise<Note> => post(bob, { text: `test ${keyword} B C` }) },
+					{ note: (): Promise<Note> => post(bob, { text: `test ${keyword} B C A` }) },
+					{ note: (): Promise<Note> => post(bob, { text: `test ${keyword} C B` }) },
+					{ note: (): Promise<Note> => post(bob, { text: `test ${keyword} C` }) },
+				],
+			},
+			{
+				label: 'キーワード1つ(大文字小文字区別する)',
+				parameters: (): object => ({ keywords: [['KEYWORD']], caseSensitive: true }),
+				posts: [
+					{ note: (): Promise<Note> => post(bob, { text: 'keyword' }) },
+					{ note: (): Promise<Note> => post(bob, { text: 'kEyWoRd' }) },
+					{ note: (): Promise<Note> => post(bob, { text: 'KEYWORD' }), included: true },
+				],
+			},
+			{
+				label: 'キーワード1つ(大文字小文字区別しない)',
+				parameters: (): object => ({ keywords: [['KEYWORD']], caseSensitive: false }),
+				posts: [
+					{ note: (): Promise<Note> => post(bob, { text: 'keyword' }), included: true },
+					{ note: (): Promise<Note> => post(bob, { text: 'kEyWoRd' }), included: true },
+					{ note: (): Promise<Note> => post(bob, { text: 'KEYWORD' }), included: true },
+				],
+			},
+			{
+				label: '除外ワード1つ(大文字小文字区別する)',
+				parameters: (): object => ({ excludeKeywords: [['KEYWORD']], caseSensitive: true }),
+				posts: [
+					{ note: (): Promise<Note> => post(bob, { text: `${keyword}` }), included: true },
+					{ note: (): Promise<Note> => post(bob, { text: `${keyword} keyword` }), included: true },
+					{ note: (): Promise<Note> => post(bob, { text: `${keyword} kEyWoRd` }), included: true },
+					{ note: (): Promise<Note> => post(bob, { text: `${keyword} KEYWORD` }) },
+				],
+			},
+			{
+				label: '除外ワード1つ(大文字小文字区別しない)',
+				parameters: (): object => ({ excludeKeywords: [['KEYWORD']], caseSensitive: false }),
+				posts: [
+					{ note: (): Promise<Note> => post(bob, { text: `${keyword}` }), included: true },
+					{ note: (): Promise<Note> => post(bob, { text: `${keyword} keyword` }) },
+					{ note: (): Promise<Note> => post(bob, { text: `${keyword} kEyWoRd` }) },
+					{ note: (): Promise<Note> => post(bob, { text: `${keyword} KEYWORD` }) },
+				],
+			},
+			{
+				label: '添付ファイルを問わない',
+				parameters: (): object => ({ withFile: false }),
+				posts: [
+					{ note: (): Promise<Note> => post(bob, { text: `${keyword}`, fileIds: [bobFile.id] }), included: true },
+					{ note: (): Promise<Note> => post(bob, { text: `${keyword}` }), included: true },
+				],
+			},
+			{
+				label: '添付ファイル付きのみ',
+				parameters: (): object => ({ withFile: true }),
+				posts: [
+					{ note: (): Promise<Note> => post(bob, { text: `${keyword}`, fileIds: [bobFile.id] }), included: true },
+					{ note: (): Promise<Note> => post(bob, { text: `${keyword}` }) },
+				],
+			},
+			{
+				label: 'リプライ以外',
+				parameters: (): object => ({ withReplies: false }),
+				posts: [
+					{ note: (): Promise<Note> => post(bob, { text: `${keyword}`, replyId: alicePost.id }) },
+					{ note: (): Promise<Note> => post(bob, { text: `${keyword}` }), included: true },
+				],
+			},
+			{
+				label: 'リプライも含む',
+				parameters: (): object => ({ withReplies: true }),
+				posts: [
+					{ note: (): Promise<Note> => post(bob, { text: `${keyword}`, replyId: alicePost.id }), included: true },
+					{ note: (): Promise<Note> => post(bob, { text: `${keyword}` }), included: true },
+				],
+			},
+		])('が取得できること($label)', async ({ parameters, posts }) => {
+			const antenna = await successfulApiCall({
+				endpoint: 'antennas/create',
+				parameters: { ...defaultParam, keywords: [[keyword]], ...parameters() },
+				user: alice,
+			});
+
+			const notes = await posts.reduce(async (prev, current) => {
+				// includedに関わらずnote()は評価して投稿する。
+				const p = await prev;
+				const n = await current.note();
+				if (current.included) return p.concat(n);
+				return p;
+			}, Promise.resolve([] as Note[]));
+
+			// alice視点でNoteを取り直す
+			const expected = await Promise.all(notes.reverse().map(s => successfulApiCall({
+				endpoint: 'notes/show',
+				parameters: { noteId: s.id },
+				user: alice,
+			})));
+
+			const response = await successfulApiCall({
+				endpoint: 'antennas/notes',
+				parameters: { antennaId: antenna.id },
+				user: alice,
+			});
+			assert.deepStrictEqual(
+				response.map(({ userId, id, text }) => ({ userId, id, text })),
+				expected.map(({ userId, id, text }) => ({ userId, id, text })));
+			assert.deepStrictEqual(response, expected);
+		});
+
+		test.skip('が取得でき、日付指定のPaginationに一貫性があること', async () => { });
+		test.each([
+			{ label: 'ID指定', offsetBy: 'id' },
+
+			// BUG sinceDate, untilDateはsinceIdや他のエンドポイントとは異なり、その時刻に一致するレコードを含んでしまう。
+			// { label: '日付指定', offsetBy: 'createdAt' },
+		] as const)('が取得でき、$labelのPaginationに一貫性があること', async ({ offsetBy }) => {
+			const antenna = await successfulApiCall({
+				endpoint: 'antennas/create',
+				parameters: { ...defaultParam, keywords: [[keyword]] },
+				user: alice,
+			});
+			const notes = await [...Array(30)].reduce(async (prev, current, index) => {
+				const p = await prev;
+				const n = await post(alice, { text: `${keyword} (${index})` });
+				return [n].concat(p);
+			}, Promise.resolve([] as Note[]));
+
+			// antennas/notesは降順のみで、昇順をサポートしない。
+			await testPaginationConsistency(notes, async (paginationParam) => {
+				return successfulApiCall({
+					endpoint: 'antennas/notes',
+					parameters: { antennaId: antenna.id, ...paginationParam },
+					user: alice,
+				}) as any as Note[];
+			}, offsetBy, 'desc');
+		});
+
+		// BUG 7日過ぎると作り直すしかない。 https://github.com/misskey-dev/misskey/issues/10476
+		test.todo('を取得したときActiveに戻る');
+
+		//#endregion
+	});
+});
diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts
index a7f8210c8..02684c93b 100644
--- a/packages/backend/test/e2e/users.ts
+++ b/packages/backend/test/e2e/users.ts
@@ -43,7 +43,6 @@ describe('ユーザー', () => {
 
 	type MeDetailed = UserDetailedNotMe & 
 		misskey.entities.MeDetailed & {
-		showTimelineReplies: boolean,
 		achievements: object[],
 		loggedInDays: number,
 		policies: object,
@@ -160,7 +159,6 @@ describe('ユーザー', () => {
 			mutedInstances: user.mutedInstances,
 			mutingNotificationTypes: user.mutingNotificationTypes,
 			emailNotificationTypes: user.emailNotificationTypes,
-			showTimelineReplies: user.showTimelineReplies,
 			achievements: user.achievements, 
 			loggedInDays: user.loggedInDays,
 			policies: user.policies,
@@ -406,7 +404,6 @@ describe('ユーザー', () => {
 		assert.deepStrictEqual(response.mutedInstances, []);
 		assert.deepStrictEqual(response.mutingNotificationTypes, []);
 		assert.deepStrictEqual(response.emailNotificationTypes, ['follow', 'receiveFollowRequest']);
-		assert.strictEqual(response.showTimelineReplies, false);
 		assert.deepStrictEqual(response.achievements, []);
 		assert.deepStrictEqual(response.loggedInDays, 0);
 		assert.deepStrictEqual(response.policies, DEFAULT_POLICIES); 
@@ -470,8 +467,6 @@ describe('ユーザー', () => {
 		{ parameters: (): object => ({ isBot: false }) },
 		{ parameters: (): object => ({ isCat: true }) },
 		{ parameters: (): object => ({ isCat: false }) },
-		{ parameters: (): object => ({ showTimelineReplies: true }) },
-		{ parameters: (): object => ({ showTimelineReplies: false }) },
 		{ parameters: (): object => ({ injectFeaturedNote: true }) },
 		{ parameters: (): object => ({ injectFeaturedNote: false }) },
 		{ parameters: (): object => ({ receiveAnnouncementEmail: true }) },
diff --git a/packages/backend/test/misc/mock-resolver.ts b/packages/backend/test/misc/mock-resolver.ts
index 6b31e6861..a7bcd859a 100644
--- a/packages/backend/test/misc/mock-resolver.ts
+++ b/packages/backend/test/misc/mock-resolver.ts
@@ -52,11 +52,7 @@ export class MockResolver extends Resolver {
 		const r = this._rs.get(value);
 
 		if (!r) {
-			throw {
-				name: 'StatusError',
-				statusCode: 404,
-				message: 'Not registed for mock',
-			};
+			throw new Error('Not registed for mock');
 		}
 
 		const object = JSON.parse(r.content);
diff --git a/packages/backend/test/unit/ReactionService.ts b/packages/backend/test/unit/ReactionService.ts
index 38db081ac..aa68f4117 100644
--- a/packages/backend/test/unit/ReactionService.ts
+++ b/packages/backend/test/unit/ReactionService.ts
@@ -15,78 +15,74 @@ describe('ReactionService', () => {
 		reactionService = app.get<ReactionService>(ReactionService);
 	});
 
-	describe('toDbReaction', () => {
+	describe('normalize', () => {
 		test('絵文字リアクションはそのまま', async () => {
-			assert.strictEqual(await reactionService.toDbReaction('👍'), '👍');
-			assert.strictEqual(await reactionService.toDbReaction('🍅'), '🍅');
+			assert.strictEqual(await reactionService.normalize('👍'), '👍');
+			assert.strictEqual(await reactionService.normalize('🍅'), '🍅');
 		});
 
 		test('既存のリアクションは絵文字化する pudding', async () => {
-			assert.strictEqual(await reactionService.toDbReaction('pudding'), '🍮');
+			assert.strictEqual(await reactionService.normalize('pudding'), '🍮');
 		});
 
 		test('既存のリアクションは絵文字化する like', async () => {
-			assert.strictEqual(await reactionService.toDbReaction('like'), '👍');
+			assert.strictEqual(await reactionService.normalize('like'), '👍');
 		});
 
 		test('既存のリアクションは絵文字化する love', async () => {
-			assert.strictEqual(await reactionService.toDbReaction('love'), '❤');
+			assert.strictEqual(await reactionService.normalize('love'), '❤');
 		});
 
 		test('既存のリアクションは絵文字化する laugh', async () => {
-			assert.strictEqual(await reactionService.toDbReaction('laugh'), '😆');
+			assert.strictEqual(await reactionService.normalize('laugh'), '😆');
 		});
 
 		test('既存のリアクションは絵文字化する hmm', async () => {
-			assert.strictEqual(await reactionService.toDbReaction('hmm'), '🤔');
+			assert.strictEqual(await reactionService.normalize('hmm'), '🤔');
 		});
 
 		test('既存のリアクションは絵文字化する surprise', async () => {
-			assert.strictEqual(await reactionService.toDbReaction('surprise'), '😮');
+			assert.strictEqual(await reactionService.normalize('surprise'), '😮');
 		});
 
 		test('既存のリアクションは絵文字化する congrats', async () => {
-			assert.strictEqual(await reactionService.toDbReaction('congrats'), '🎉');
+			assert.strictEqual(await reactionService.normalize('congrats'), '🎉');
 		});
 
 		test('既存のリアクションは絵文字化する angry', async () => {
-			assert.strictEqual(await reactionService.toDbReaction('angry'), '💢');
+			assert.strictEqual(await reactionService.normalize('angry'), '💢');
 		});
 
 		test('既存のリアクションは絵文字化する confused', async () => {
-			assert.strictEqual(await reactionService.toDbReaction('confused'), '😥');
+			assert.strictEqual(await reactionService.normalize('confused'), '😥');
 		});
 
 		test('既存のリアクションは絵文字化する rip', async () => {
-			assert.strictEqual(await reactionService.toDbReaction('rip'), '😇');
+			assert.strictEqual(await reactionService.normalize('rip'), '😇');
 		});
 
 		test('既存のリアクションは絵文字化する star', async () => {
-			assert.strictEqual(await reactionService.toDbReaction('star'), '⭐');
+			assert.strictEqual(await reactionService.normalize('star'), '⭐');
 		});
 
 		test('異体字セレクタ除去', async () => {
-			assert.strictEqual(await reactionService.toDbReaction('㊗️'), '㊗');
+			assert.strictEqual(await reactionService.normalize('㊗️'), '㊗');
 		});
 
 		test('異体字セレクタ除去 必要なし', async () => {
-			assert.strictEqual(await reactionService.toDbReaction('㊗'), '㊗');
-		});
-
-		test('fallback - undefined', async () => {
-			assert.strictEqual(await reactionService.toDbReaction(undefined), '❤');
+			assert.strictEqual(await reactionService.normalize('㊗'), '㊗');
 		});
 
 		test('fallback - null', async () => {
-			assert.strictEqual(await reactionService.toDbReaction(null), '❤');
+			assert.strictEqual(await reactionService.normalize(null), '❤');
 		});
 
 		test('fallback - empty', async () => {
-			assert.strictEqual(await reactionService.toDbReaction(''), '❤');
+			assert.strictEqual(await reactionService.normalize(''), '❤');
 		});
 
 		test('fallback - unknown', async () => {
-			assert.strictEqual(await reactionService.toDbReaction('unknown'), '❤');
+			assert.strictEqual(await reactionService.normalize('unknown'), '❤');
 		});
 	});
 });
diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts
index 809ed2c66..22f7d81e4 100644
--- a/packages/backend/test/utils.ts
+++ b/packages/backend/test/utils.ts
@@ -124,6 +124,13 @@ export const react = async (user: any, note: any, reaction: string): Promise<any
 	}, user);
 };
 
+export const userList = async (user: any, userList: any = {}): Promise<any> => {
+	const res = await api('users/lists/create', {
+		name: 'test',
+	}, user);
+	return res.body;
+};
+
 export const page = async (user: any, page: any = {}): Promise<any> => {
 	const res = await api('pages/create', {
 		alignCenter: false,
@@ -380,8 +387,98 @@ export const simpleGet = async (path: string, accept = '*/*', cookie: any = unde
 	};
 };
 
+/**
+ * あるAPIエンドポイントのPaginationが複数の条件で一貫した挙動であることをテストします。
+ * (sinceId, untilId, sinceDate, untilDate, offset, limit)
+ * @param expected 期待値となるEntityの並び(例:Note[])昇順降順が一致している必要がある
+ * @param fetchEntities Entity[]を返却するテスト対象のAPIを呼び出す関数
+ * @param offsetBy 何をキーとしてPaginationするか。
+ * @param ordering 昇順・降順
+ */
+export async function testPaginationConsistency<Entity extends { id: string, createdAt?: string }>(
+	expected: Entity[],
+	fetchEntities: (paginationParam: {
+		limit?: number,
+		offset?: number,
+		sinceId?: string,
+		untilId?: string,
+		sinceDate?: number,
+		untilDate?: number,
+	}) => Promise<Entity[]>,
+	offsetBy: 'offset' | 'id' | 'createdAt' = 'id',
+	ordering: 'desc' | 'asc' = 'desc'): Promise<void> {
+	const rangeToParam = (p: { limit?: number, until?: Entity, since?: Entity }): object => {
+		if (offsetBy === 'id') {
+			return { limit: p.limit, sinceId: p.since?.id, untilId: p.until?.id };
+		} else {
+			const sinceDate = p.since?.createdAt !== undefined ? new Date(p.since.createdAt).getTime() : undefined;
+			const untilDate = p.until?.createdAt !== undefined ? new Date(p.until.createdAt).getTime() : undefined;
+			return { limit: p.limit, sinceDate, untilDate };
+		}
+	};
+
+	for (const limit of [1, 5, 10, 100, undefined]) {
+		// 1. sinceId/DateとuntilId/Dateで両端を指定して取得した結果が期待通りになっていること
+		if (ordering === 'desc') {
+			const end = expected[expected.length - 1];
+			let last = await fetchEntities(rangeToParam({ limit, since: end }));
+			const actual: Entity[] = [];
+			while (last.length !== 0) {
+				actual.push(...last);
+				last = await fetchEntities(rangeToParam({ limit, until: last[last.length - 1], since: end }));
+			}
+			actual.push(end);
+			assert.deepStrictEqual(
+				actual.map(({ id, createdAt }) => id + ':' + createdAt),
+				expected.map(({ id, createdAt }) => id + ':' + createdAt));
+		}
+
+		// 2. sinceId/Date指定+limitで取得してつなぎ合わせた結果が期待通りになっていること
+		if (ordering === 'asc') {
+			// 昇順にしたときの先頭(一番古いもの)をもってくる(expected[1]を基準に降順にして0番目)
+			let last = await fetchEntities({ limit: 1, untilId: expected[1].id });
+			const actual: Entity[] = [];
+			while (last.length !== 0) {
+				actual.push(...last);
+				last = await fetchEntities(rangeToParam({ limit, since: last[last.length - 1] }));
+			}
+			assert.deepStrictEqual(
+				actual.map(({ id, createdAt }) => id + ':' + createdAt),
+				expected.map(({ id, createdAt }) => id + ':' + createdAt));
+		}
+
+		// 3. untilId指定+limitで取得してつなぎ合わせた結果が期待通りになっていること
+		if (ordering === 'desc') {
+			let last = await fetchEntities({ limit });
+			const actual: Entity[] = [];
+			while (last.length !== 0) {
+				actual.push(...last);
+				last = await fetchEntities(rangeToParam({ limit, until: last[last.length - 1] }));
+			}
+			assert.deepStrictEqual(
+				actual.map(({ id, createdAt }) => id + ':' + createdAt),
+				expected.map(({ id, createdAt }) => id + ':' + createdAt));
+		}
+
+		// 4. offset指定+limitで取得してつなぎ合わせた結果が期待通りになっていること
+		if (offsetBy === 'offset') {
+			let last = await fetchEntities({ limit, offset: 0 });
+			let offset = limit ?? 10;
+			const actual: Entity[] = [];
+			while (last.length !== 0) {
+				actual.push(...last);
+				last = await fetchEntities({ limit, offset });
+				offset += limit ?? 10;
+			}
+			assert.deepStrictEqual(
+				actual.map(({ id, createdAt }) => id + ':' + createdAt),
+				expected.map(({ id, createdAt }) => id + ':' + createdAt));
+		}
+	}
+}
+
 export async function initTestDb(justBorrow = false, initEntities?: any[]) {
-	if (process.env.NODE_ENV !== 'test') throw 'NODE_ENV is not a test';
+	if (process.env.NODE_ENV !== 'test') throw new Error('NODE_ENV is not a test');
 
 	const db = new DataSource({
 		type: 'postgres',
diff --git a/packages/frontend/.eslintrc.js b/packages/frontend/.eslintrc.js
index e8e0e57d2..24c3ad4b8 100644
--- a/packages/frontend/.eslintrc.js
+++ b/packages/frontend/.eslintrc.js
@@ -56,14 +56,15 @@ module.exports = {
 		'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-destructure': 'warn',
 		'vue/max-attributes-per-line': 'off',
 		'vue/html-self-closing': 'off',
 		'vue/singleline-html-element-content-newline': 'off',
-		// (vue/vue3-recommended disabled the autofix for Vue 2 compatibility)
-		'vue/v-on-event-hyphenation': ['warn', 'always', { autofix: true }],
+		'vue/v-on-event-hyphenation': ['error', 'never', { autofix: true }],
+		'vue/attribute-hyphenation': ['error', 'never'],
 	},
 	globals: {
 		// Node.js
diff --git a/packages/frontend/.storybook/generate.tsx b/packages/frontend/.storybook/generate.tsx
index 7c51d4c00..f44242210 100644
--- a/packages/frontend/.storybook/generate.tsx
+++ b/packages/frontend/.storybook/generate.tsx
@@ -397,6 +397,7 @@ function toStories(component: string): string {
 Promise.all([
 	glob('src/components/global/*.vue'),
 	glob('src/components/Mk{A,B}*.vue'),
+	glob('src/components/MkDigitalClock.vue'),
 	glob('src/components/MkGalleryPostPreview.vue'),
 	glob('src/components/MkSignupServerRules.vue'),
 	glob('src/components/MkUserSetupDialog.vue'),
diff --git a/packages/frontend/.storybook/preview-head.html b/packages/frontend/.storybook/preview-head.html
index ab694f64f..f6a9a4875 100644
--- a/packages/frontend/.storybook/preview-head.html
+++ b/packages/frontend/.storybook/preview-head.html
@@ -1,6 +1,6 @@
 <link rel="preload" href="https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/about-icon.png?raw=true" as="image" type="image/png" crossorigin="anonymous">
 <link rel="preload" href="https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true" as="image" type="image/jpeg" crossorigin="anonymous">
-<link rel="stylesheet" href="https://unpkg.com/@tabler/icons-webfont@2.12.0/tabler-icons.min.css">
+<link rel="stylesheet" href="https://unpkg.com/@tabler/icons-webfont@2.21.0/tabler-icons.min.css">
 <link rel="stylesheet" href="https://unpkg.com/@fontsource/m-plus-rounded-1c/index.css">
 <style>
 	html {
diff --git a/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.test.ts b/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.test.ts
new file mode 100644
index 000000000..3929bf060
--- /dev/null
+++ b/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.test.ts
@@ -0,0 +1,597 @@
+import { parse } from 'acorn';
+import { generate } from 'astring';
+import { describe, expect, it } from 'vitest';
+import { normalizeClass, unwindCssModuleClassName } from './rollup-plugin-unwind-css-module-class-name';
+import type * as estree from 'estree';
+
+function parseExpression(code: string): estree.Expression {
+	const program = parse(code, { ecmaVersion: 'latest', sourceType: 'module' }) as unknown as estree.Program;
+	const statement = program.body[0] as estree.ExpressionStatement;
+	return statement.expression;
+}
+
+describe(normalizeClass.name, () => {
+	it('should normalize string', () => {
+		expect(normalizeClass(parseExpression('"a b c"'))).toBe('a b c');
+	});
+	it('should trim redundant spaces', () => {
+		expect(normalizeClass(parseExpression('" a b  c "'))).toBe('a b c');
+	});
+	it('should ignore undefined', () => {
+		expect(normalizeClass(parseExpression('undefined'))).toBe('');
+	});
+	it('should ignore non string literals', () => {
+		expect(normalizeClass(parseExpression('0'))).toBe('');
+		expect(normalizeClass(parseExpression('true'))).toBe('');
+		expect(normalizeClass(parseExpression('null'))).toBe('');
+		expect(normalizeClass(parseExpression('/I.D/'))).toBe('');
+	});
+	it('should not normalize identifiers', () => {
+		expect(normalizeClass(parseExpression('EScape'))).toBeNull();
+	});
+	it('should normalize recursively array', () => {
+		expect(normalizeClass(parseExpression('["from", ...["Utopia"]]'))).toBe('from Utopia');
+		expect(normalizeClass(parseExpression('["from", ...[Utopia]]'))).toBeNull();
+	});
+	it('should normalize recursively template literal', () => {
+		expect(normalizeClass(parseExpression('`name ${"shiho"} code ${33}`'))).toBe('name shiho code');
+		expect(normalizeClass(parseExpression('`name ${shiho.name} code ${33}`'))).toBeNull();
+	});
+	it('should normalize recursively binary expression', () => {
+		expect(normalizeClass(parseExpression('"mirage" + "mirror"'))).toBe('miragemirror');
+		expect(normalizeClass(parseExpression('"mirage" + mirror'))).toBeNull();
+	});
+	it('should normalize recursively object expression', () => {
+		expect(normalizeClass(parseExpression('({ a: true, b: "c" })'))).toBe('a b');
+		expect(normalizeClass(parseExpression('({ a: false, b: "c" })'))).toBe('b');
+		expect(normalizeClass(parseExpression('({ a: true, b: c })'))).toBeNull();
+		expect(normalizeClass(parseExpression('({ a: true, b: "c", ...({ d: true }) })'))).toBe('a b d');
+		expect(normalizeClass(parseExpression('({ a: true, [b]: "c" })'))).toBeNull();
+		expect(normalizeClass(parseExpression('({ a: true, b: false, c: !false, d: !!0 })'))).toBe('a c');
+	});
+});
+
+it('Composition API (standard)', () => {
+	const ast = parse(`
+import { c as api, d as defaultStore, i as i18n, aD as notePage, bN as ImgWithBlurhash, bY as getStaticImageUrl, _ as _export_sfc } from './app-!~{001}~.js';
+import { M as MkContainer } from './MkContainer-!~{03M}~.js';
+import { b as defineComponent, a as ref, e as onMounted, z as resolveComponent, g as openBlock, h as createBlock, i as withCtx, K as createTextVNode, E as toDisplayString, u as unref, l as createBaseVNode, q as normalizeClass, B as createCommentVNode, k as createElementBlock, F as Fragment, C as renderList, A as createVNode } from './vue-!~{002}~.js';
+import './photoswipe-!~{003}~.js';
+
+const _hoisted_1 = /* @__PURE__ */ createBaseVNode("i", { class: "ti ti-photo" }, null, -1);
+const _sfc_main = /* @__PURE__ */ defineComponent({
+  __name: "index.photos",
+  props: {
+    user: {}
+  },
+  setup(__props) {
+    const props = __props;
+    let fetching = ref(true);
+    let images = ref([]);
+    function thumbnail(image) {
+      return defaultStore.state.disableShowingAnimatedImages ? getStaticImageUrl(image.url) : image.thumbnailUrl;
+    }
+    onMounted(() => {
+      const image = [
+        "image/jpeg",
+        "image/webp",
+        "image/avif",
+        "image/png",
+        "image/gif",
+        "image/apng",
+        "image/vnd.mozilla.apng"
+      ];
+      api("users/notes", {
+        userId: props.user.id,
+        fileType: image,
+        excludeNsfw: defaultStore.state.nsfw !== "ignore",
+        limit: 10
+      }).then((notes) => {
+        for (const note of notes) {
+          for (const file of note.files) {
+            images.value.push({
+              note,
+              file
+            });
+          }
+        }
+        fetching.value = false;
+      });
+    });
+    return (_ctx, _cache) => {
+      const _component_MkLoading = resolveComponent("MkLoading");
+      const _component_MkA = resolveComponent("MkA");
+      return openBlock(), createBlock(MkContainer, {
+        "max-height": 300,
+        foldable: true
+      }, {
+        icon: withCtx(() => [
+          _hoisted_1
+        ]),
+        header: withCtx(() => [
+          createTextVNode(toDisplayString(unref(i18n).ts.images), 1)
+        ]),
+        default: withCtx(() => [
+          createBaseVNode("div", {
+            class: normalizeClass(_ctx.$style.root)
+          }, [
+            unref(fetching) ? (openBlock(), createBlock(_component_MkLoading, { key: 0 })) : createCommentVNode("", true),
+            !unref(fetching) && unref(images).length > 0 ? (openBlock(), createElementBlock("div", {
+              key: 1,
+              class: normalizeClass(_ctx.$style.stream)
+            }, [
+              (openBlock(true), createElementBlock(Fragment, null, renderList(unref(images), (image) => {
+                return openBlock(), createBlock(_component_MkA, {
+                  key: image.note.id + image.file.id,
+                  class: normalizeClass(_ctx.$style.img),
+                  to: unref(notePage)(image.note)
+                }, {
+                  default: withCtx(() => [
+                    createVNode(ImgWithBlurhash, {
+                      hash: image.file.blurhash,
+                      src: thumbnail(image.file),
+                      title: image.file.name
+                    }, null, 8, ["hash", "src", "title"])
+                  ]),
+                  _: 2
+                }, 1032, ["class", "to"]);
+              }), 128))
+            ], 2)) : createCommentVNode("", true),
+            !unref(fetching) && unref(images).length == 0 ? (openBlock(), createElementBlock("p", {
+              key: 2,
+              class: normalizeClass(_ctx.$style.empty)
+            }, toDisplayString(unref(i18n).ts.nothing), 3)) : createCommentVNode("", true)
+          ], 2)
+        ]),
+        _: 1
+      });
+    };
+  }
+});
+
+const root = "xenMW";
+const stream = "xaZzf";
+const img = "xtA8t";
+const empty = "xhYKj";
+const style0 = {
+        root: root,
+        stream: stream,
+        img: img,
+        empty: empty
+};
+
+const cssModules = {
+  "$style": style0
+};
+const index_photos = /* @__PURE__ */ _export_sfc(_sfc_main, [["__cssModules", cssModules]]);
+
+export { index_photos as default };
+`.slice(1), { ecmaVersion: 'latest', sourceType: 'module' });
+	unwindCssModuleClassName(ast);
+	expect(generate(ast)).toBe(`
+import {c as api, d as defaultStore, i as i18n, aD as notePage, bN as ImgWithBlurhash, bY as getStaticImageUrl, _ as _export_sfc} from './app-!~{001}~.js';
+import {M as MkContainer} from './MkContainer-!~{03M}~.js';
+import {b as defineComponent, a as ref, e as onMounted, z as resolveComponent, g as openBlock, h as createBlock, i as withCtx, K as createTextVNode, E as toDisplayString, u as unref, l as createBaseVNode, q as normalizeClass, B as createCommentVNode, k as createElementBlock, F as Fragment, C as renderList, A as createVNode} from './vue-!~{002}~.js';
+import './photoswipe-!~{003}~.js';
+const _hoisted_1 = createBaseVNode("i", {
+  class: "ti ti-photo"
+}, null, -1);
+const _sfc_main = defineComponent({
+  __name: "index.photos",
+  props: {
+    user: {}
+  },
+  setup(__props) {
+    const props = __props;
+    let fetching = ref(true);
+    let images = ref([]);
+    function thumbnail(image) {
+      return defaultStore.state.disableShowingAnimatedImages ? getStaticImageUrl(image.url) : image.thumbnailUrl;
+    }
+    onMounted(() => {
+      const image = ["image/jpeg", "image/webp", "image/avif", "image/png", "image/gif", "image/apng", "image/vnd.mozilla.apng"];
+      api("users/notes", {
+        userId: props.user.id,
+        fileType: image,
+        excludeNsfw: defaultStore.state.nsfw !== "ignore",
+        limit: 10
+      }).then(notes => {
+        for (const note of notes) {
+          for (const file of note.files) {
+            images.value.push({
+              note,
+              file
+            });
+          }
+        }
+        fetching.value = false;
+      });
+    });
+    return (_ctx, _cache) => {
+      const _component_MkLoading = resolveComponent("MkLoading");
+      const _component_MkA = resolveComponent("MkA");
+      return (openBlock(), createBlock(MkContainer, {
+        "max-height": 300,
+        foldable: true
+      }, {
+        icon: withCtx(() => [_hoisted_1]),
+        header: withCtx(() => [createTextVNode(toDisplayString(unref(i18n).ts.images), 1)]),
+        default: withCtx(() => [createBaseVNode("div", {
+          class: "xenMW"
+        }, [unref(fetching) ? (openBlock(), createBlock(_component_MkLoading, {
+          key: 0
+        })) : createCommentVNode("", true), !unref(fetching) && unref(images).length > 0 ? (openBlock(), createElementBlock("div", {
+          key: 1,
+          class: "xaZzf"
+        }, [(openBlock(true), createElementBlock(Fragment, null, renderList(unref(images), image => {
+          return (openBlock(), createBlock(_component_MkA, {
+            key: image.note.id + image.file.id,
+            class: "xtA8t",
+            to: unref(notePage)(image.note)
+          }, {
+            default: withCtx(() => [createVNode(ImgWithBlurhash, {
+              hash: image.file.blurhash,
+              src: thumbnail(image.file),
+              title: image.file.name
+            }, null, 8, ["hash", "src", "title"])]),
+            _: 2
+          }, 1032, ["class", "to"]));
+        }), 128))], 2)) : createCommentVNode("", true), !unref(fetching) && unref(images).length == 0 ? (openBlock(), createElementBlock("p", {
+          key: 2,
+          class: "xhYKj"
+        }, toDisplayString(unref(i18n).ts.nothing), 3)) : createCommentVNode("", true)], 2)]),
+        _: 1
+      }));
+    };
+  }
+});
+const root = "xenMW";
+const stream = "xaZzf";
+const img = "xtA8t";
+const empty = "xhYKj";
+const style0 = {
+  root: root,
+  stream: stream,
+  img: img,
+  empty: empty
+};
+const cssModules = {
+  "$style": style0
+};
+const index_photos = _sfc_main;
+export {index_photos as default};
+`.slice(1));
+});
+
+it('Composition API (with `useCssModule()`)', () => {
+	const ast = parse(`
+import { a7 as getCurrentInstance, b as defineComponent, G as useCssModule, a1 as h, H as TransitionGroup } from './!~{002}~.js';
+import { d as defaultStore, aK as toast, b5 as MkAd, i as i18n, _ as _export_sfc } from './app-!~{001}~.js';
+
+function isDebuggerEnabled(id) {
+  try {
+    return localStorage.getItem(\`DEBUG_\${id}\`) !== null;
+  } catch {
+    return false;
+  }
+}
+function stackTraceInstances() {
+  let instance = getCurrentInstance();
+  const stack = [];
+  while (instance) {
+    stack.push(instance);
+    instance = instance.parent;
+  }
+  return stack;
+}
+
+const _sfc_main = defineComponent({
+  props: {
+    items: {
+      type: Array,
+      required: true
+    },
+    direction: {
+      type: String,
+      required: false,
+      default: "down"
+    },
+    reversed: {
+      type: Boolean,
+      required: false,
+      default: false
+    },
+    noGap: {
+      type: Boolean,
+      required: false,
+      default: false
+    },
+    ad: {
+      type: Boolean,
+      required: false,
+      default: false
+    }
+  },
+  setup(props, { slots, expose }) {
+    const $style = useCssModule();
+    function getDateText(time) {
+      const date = new Date(time).getDate();
+      const month = new Date(time).getMonth() + 1;
+      return i18n.t("monthAndDay", {
+        month: month.toString(),
+        day: date.toString()
+      });
+    }
+    if (props.items.length === 0)
+      return;
+    const renderChildrenImpl = () => props.items.map((item, i) => {
+      if (!slots || !slots.default)
+        return;
+      const el = slots.default({
+        item
+      })[0];
+      if (el.key == null && item.id)
+        el.key = item.id;
+      if (i !== props.items.length - 1 && new Date(item.createdAt).getDate() !== new Date(props.items[i + 1].createdAt).getDate()) {
+        const separator = h("div", {
+          class: $style["separator"],
+          key: item.id + ":separator"
+        }, h("p", {
+          class: $style["date"]
+        }, [
+          h("span", {
+            class: $style["date-1"]
+          }, [
+            h("i", {
+              class: \`ti ti-chevron-up \${$style["date-1-icon"]}\`
+            }),
+            getDateText(item.createdAt)
+          ]),
+          h("span", {
+            class: $style["date-2"]
+          }, [
+            getDateText(props.items[i + 1].createdAt),
+            h("i", {
+              class: \`ti ti-chevron-down \${$style["date-2-icon"]}\`
+            })
+          ])
+        ]));
+        return [el, separator];
+      } else {
+        if (props.ad && item._shouldInsertAd_) {
+          return [h(MkAd, {
+            key: item.id + ":ad",
+            prefer: ["horizontal", "horizontal-big"]
+          }), el];
+        } else {
+          return el;
+        }
+      }
+    });
+    const renderChildren = () => {
+      const children = renderChildrenImpl();
+      if (isDebuggerEnabled(6864)) {
+        const nodes = children.flatMap((node) => node ?? []);
+        const keys = new Set(nodes.map((node) => node.key));
+        if (keys.size !== nodes.length) {
+          const id = crypto.randomUUID();
+          const instances = stackTraceInstances();
+          toast(instances.reduce((a, c) => \`\${a} at \${c.type.name}\`, \`[DEBUG_6864 (\${id})]: \${nodes.length - keys.size} duplicated keys found\`));
+          console.warn({ id, debugId: 6864, stack: instances });
+        }
+      }
+      return children;
+    };
+    function onBeforeLeave(el) {
+      el.style.top = \`\${el.offsetTop}px\`;
+      el.style.left = \`\${el.offsetLeft}px\`;
+    }
+    function onLeaveCanceled(el) {
+      el.style.top = "";
+      el.style.left = "";
+    }
+    return () => h(
+      defaultStore.state.animation ? TransitionGroup : "div",
+      {
+        class: {
+          [$style["date-separated-list"]]: true,
+          [$style["date-separated-list-nogap"]]: props.noGap,
+          [$style["reversed"]]: props.reversed,
+          [$style["direction-down"]]: props.direction === "down",
+          [$style["direction-up"]]: props.direction === "up"
+        },
+        ...defaultStore.state.animation ? {
+          name: "list",
+          tag: "div",
+          onBeforeLeave,
+          onLeaveCanceled
+        } : {}
+      },
+      { default: renderChildren }
+    );
+  }
+});
+
+const reversed = "xxiZh";
+const separator = "xxeDx";
+const date = "xxawD";
+const style0 = {
+        "date-separated-list": "xfKPa",
+        "date-separated-list-nogap": "xf9zr",
+        "direction-up": "x7AeO",
+        "direction-down": "xBIqc",
+        reversed: reversed,
+        separator: separator,
+        date: date,
+        "date-1": "xwtmh",
+        "date-1-icon": "xsNPa",
+        "date-2": "x1xvw",
+        "date-2-icon": "x9ZiG"
+};
+
+const cssModules = {
+  "$style": style0
+};
+const MkDateSeparatedList = /* @__PURE__ */ _export_sfc(_sfc_main, [["__cssModules", cssModules]]);
+
+export { MkDateSeparatedList as M };
+`.slice(1), { ecmaVersion: 'latest', sourceType: 'module' });
+	unwindCssModuleClassName(ast);
+	expect(generate(ast)).toBe(`
+import {a7 as getCurrentInstance, b as defineComponent, G as useCssModule, a1 as h, H as TransitionGroup} from './!~{002}~.js';
+import {d as defaultStore, aK as toast, b5 as MkAd, i as i18n, _ as _export_sfc} from './app-!~{001}~.js';
+function isDebuggerEnabled(id) {
+  try {
+    return localStorage.getItem(\`DEBUG_\${id}\`) !== null;
+  } catch {
+    return false;
+  }
+}
+function stackTraceInstances() {
+  let instance = getCurrentInstance();
+  const stack = [];
+  while (instance) {
+    stack.push(instance);
+    instance = instance.parent;
+  }
+  return stack;
+}
+const _sfc_main = defineComponent({
+  props: {
+    items: {
+      type: Array,
+      required: true
+    },
+    direction: {
+      type: String,
+      required: false,
+      default: "down"
+    },
+    reversed: {
+      type: Boolean,
+      required: false,
+      default: false
+    },
+    noGap: {
+      type: Boolean,
+      required: false,
+      default: false
+    },
+    ad: {
+      type: Boolean,
+      required: false,
+      default: false
+    }
+  },
+  setup(props, {slots, expose}) {
+    const $style = useCssModule();
+    function getDateText(time) {
+      const date = new Date(time).getDate();
+      const month = new Date(time).getMonth() + 1;
+      return i18n.t("monthAndDay", {
+        month: month.toString(),
+        day: date.toString()
+      });
+    }
+    if (props.items.length === 0) return;
+    const renderChildrenImpl = () => props.items.map((item, i) => {
+      if (!slots || !slots.default) return;
+      const el = slots.default({
+        item
+      })[0];
+      if (el.key == null && item.id) el.key = item.id;
+      if (i !== props.items.length - 1 && new Date(item.createdAt).getDate() !== new Date(props.items[i + 1].createdAt).getDate()) {
+        const separator = h("div", {
+          class: $style["separator"],
+          key: item.id + ":separator"
+        }, h("p", {
+          class: $style["date"]
+        }, [h("span", {
+          class: $style["date-1"]
+        }, [h("i", {
+          class: \`ti ti-chevron-up \${$style["date-1-icon"]}\`
+        }), getDateText(item.createdAt)]), h("span", {
+          class: $style["date-2"]
+        }, [getDateText(props.items[i + 1].createdAt), h("i", {
+          class: \`ti ti-chevron-down \${$style["date-2-icon"]}\`
+        })])]));
+        return [el, separator];
+      } else {
+        if (props.ad && item._shouldInsertAd_) {
+          return [h(MkAd, {
+            key: item.id + ":ad",
+            prefer: ["horizontal", "horizontal-big"]
+          }), el];
+        } else {
+          return el;
+        }
+      }
+    });
+    const renderChildren = () => {
+      const children = renderChildrenImpl();
+      if (isDebuggerEnabled(6864)) {
+        const nodes = children.flatMap(node => node ?? []);
+        const keys = new Set(nodes.map(node => node.key));
+        if (keys.size !== nodes.length) {
+          const id = crypto.randomUUID();
+          const instances = stackTraceInstances();
+          toast(instances.reduce((a, c) => \`\${a} at \${c.type.name}\`, \`[DEBUG_6864 (\${id})]: \${nodes.length - keys.size} duplicated keys found\`));
+          console.warn({
+            id,
+            debugId: 6864,
+            stack: instances
+          });
+        }
+      }
+      return children;
+    };
+    function onBeforeLeave(el) {
+      el.style.top = \`\${el.offsetTop}px\`;
+      el.style.left = \`\${el.offsetLeft}px\`;
+    }
+    function onLeaveCanceled(el) {
+      el.style.top = "";
+      el.style.left = "";
+    }
+    return () => h(defaultStore.state.animation ? TransitionGroup : "div", {
+      class: {
+        [$style["date-separated-list"]]: true,
+        [$style["date-separated-list-nogap"]]: props.noGap,
+        [$style["reversed"]]: props.reversed,
+        [$style["direction-down"]]: props.direction === "down",
+        [$style["direction-up"]]: props.direction === "up"
+      },
+      ...defaultStore.state.animation ? {
+        name: "list",
+        tag: "div",
+        onBeforeLeave,
+        onLeaveCanceled
+      } : {}
+    }, {
+      default: renderChildren
+    });
+  }
+});
+const reversed = "xxiZh";
+const separator = "xxeDx";
+const date = "xxawD";
+const style0 = {
+  "date-separated-list": "xfKPa",
+  "date-separated-list-nogap": "xf9zr",
+  "direction-up": "x7AeO",
+  "direction-down": "xBIqc",
+  reversed: reversed,
+  separator: separator,
+  date: date,
+  "date-1": "xwtmh",
+  "date-1-icon": "xsNPa",
+  "date-2": "x1xvw",
+  "date-2-icon": "x9ZiG"
+};
+const cssModules = {
+  "$style": style0
+};
+const MkDateSeparatedList = _export_sfc(_sfc_main, [["__cssModules", cssModules]]);
+export {MkDateSeparatedList as M};
+`.slice(1));
+});
diff --git a/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.ts b/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.ts
new file mode 100644
index 000000000..a18f0d904
--- /dev/null
+++ b/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.ts
@@ -0,0 +1,275 @@
+import { generate } from 'astring';
+import * as estree from 'estree';
+import { walk } from '../node_modules/estree-walker/src/index.js';
+import type * as estreeWalker from 'estree-walker';
+import type { Plugin } from 'vite';
+
+function isFalsyIdentifier(identifier: estree.Identifier): boolean {
+	return identifier.name === 'undefined' || identifier.name === 'NaN';
+}
+
+function normalizeClassWalker(tree: estree.Node): string | null {
+	if (tree.type === 'Identifier') return isFalsyIdentifier(tree) ? '' : null;
+	if (tree.type === 'Literal') return typeof tree.value === 'string' ? tree.value : '';
+	if (tree.type === 'BinaryExpression') {
+		if (tree.operator !== '+') return null;
+		const left = normalizeClassWalker(tree.left);
+		const right = normalizeClassWalker(tree.right);
+		if (left === null || right === null) return null;
+		return `${left}${right}`;
+	}
+	if (tree.type === 'TemplateLiteral') {
+		if (tree.expressions.some((x) => x.type !== 'Literal' && (x.type !== 'Identifier' || !isFalsyIdentifier(x)))) return null;
+		return tree.quasis.reduce((a, c, i) => {
+			const v = i === tree.quasis.length - 1 ? '' : (tree.expressions[i] as Partial<estree.Literal>).value;
+			return a + c.value.raw + (typeof v === 'string' ? v : '');
+		}, '');
+	}
+	if (tree.type === 'ArrayExpression') {
+		const values = tree.elements.map((treeNode) => {
+			if (treeNode === null) return '';
+			if (treeNode.type === 'SpreadElement') return normalizeClassWalker(treeNode.argument);
+			return normalizeClassWalker(treeNode);
+		});
+		if (values.some((x) => x === null)) return null;
+		return values.join(' ');
+	}
+	if (tree.type === 'ObjectExpression') {
+		const values = tree.properties.map((treeNode) => {
+			if (treeNode.type === 'SpreadElement') return normalizeClassWalker(treeNode.argument);
+			let x = treeNode.value;
+			let inveted = false;
+			while (x.type === 'UnaryExpression' && x.operator === '!') {
+				x = x.argument;
+				inveted = !inveted;
+			}
+			if (x.type === 'Literal') {
+				if (inveted === !x.value) {
+					return treeNode.key.type === 'Identifier' ? treeNode.computed ? null : treeNode.key.name : treeNode.key.type === 'Literal' ? treeNode.key.value : '';
+				} else {
+					return '';
+				}
+			}
+			if (x.type === 'Identifier') {
+				if (inveted !== isFalsyIdentifier(x)) {
+					return '';
+				} else {
+					return null;
+				}
+			}
+			return null;
+		});
+		if (values.some((x) => x === null)) return null;
+		return values.join(' ');
+	}
+	console.error(`Unexpected node type: ${tree.type}`);
+	return null;
+}
+
+export function normalizeClass(tree: estree.Node): string | null {
+	const walked = normalizeClassWalker(tree);
+	return walked && walked.replace(/^\s+|\s+(?=\s)|\s+$/g, '');
+}
+
+export function unwindCssModuleClassName(ast: estree.Node): void {
+	(walk as typeof estreeWalker.walk)(ast, {
+		enter(node, parent): void {
+			if (parent?.type !== 'Program') return;
+			if (node.type !== 'VariableDeclaration') return;
+			if (node.declarations.length !== 1) return;
+			if (node.declarations[0].id.type !== 'Identifier') return;
+			const name = node.declarations[0].id.name;
+			if (node.declarations[0].init?.type !== 'CallExpression') return;
+			if (node.declarations[0].init.callee.type !== 'Identifier') return;
+			if (node.declarations[0].init.callee.name !== '_export_sfc') return;
+			if (node.declarations[0].init.arguments.length !== 2) return;
+			if (node.declarations[0].init.arguments[0].type !== 'Identifier') return;
+			const ident = node.declarations[0].init.arguments[0].name;
+			if (!ident.startsWith('_sfc_main')) return;
+			if (node.declarations[0].init.arguments[1].type !== 'ArrayExpression') return;
+			if (node.declarations[0].init.arguments[1].elements.length === 0) return;
+			const __cssModulesIndex = node.declarations[0].init.arguments[1].elements.findIndex((x) => {
+				if (x?.type !== 'ArrayExpression') return false;
+				if (x.elements.length !== 2) return false;
+				if (x.elements[0]?.type !== 'Literal') return false;
+				if (x.elements[0].value !== '__cssModules') return false;
+				if (x.elements[1]?.type !== 'Identifier') return false;
+				return true;
+			});
+			if (!~__cssModulesIndex) return;
+			const cssModuleForestName = ((node.declarations[0].init.arguments[1].elements[__cssModulesIndex] as estree.ArrayExpression).elements[1] as estree.Identifier).name;
+			const cssModuleForestNode = parent.body.find((x) => {
+				if (x.type !== 'VariableDeclaration') return false;
+				if (x.declarations.length !== 1) return false;
+				if (x.declarations[0].id.type !== 'Identifier') return false;
+				if (x.declarations[0].id.name !== cssModuleForestName) return false;
+				if (x.declarations[0].init?.type !== 'ObjectExpression') return false;
+				return true;
+			}) as unknown as estree.VariableDeclaration;
+			const moduleForest = new Map((cssModuleForestNode.declarations[0].init as estree.ObjectExpression).properties.flatMap((property) => {
+				if (property.type !== 'Property') return [];
+				if (property.key.type !== 'Literal') return [];
+				if (property.value.type !== 'Identifier') return [];
+				return [[property.key.value as string, property.value.name as string]];
+			}));
+			const sfcMain = parent.body.find((x) => {
+				if (x.type !== 'VariableDeclaration') return false;
+				if (x.declarations.length !== 1) return false;
+				if (x.declarations[0].id.type !== 'Identifier') return false;
+				if (x.declarations[0].id.name !== ident) return false;
+				return true;
+			}) as unknown as estree.VariableDeclaration;
+			if (sfcMain.declarations[0].init?.type !== 'CallExpression') return;
+			if (sfcMain.declarations[0].init.callee.type !== 'Identifier') return;
+			if (sfcMain.declarations[0].init.callee.name !== 'defineComponent') return;
+			if (sfcMain.declarations[0].init.arguments.length !== 1) return;
+			if (sfcMain.declarations[0].init.arguments[0].type !== 'ObjectExpression') return;
+			const setup = sfcMain.declarations[0].init.arguments[0].properties.find((x) => {
+				if (x.type !== 'Property') return false;
+				if (x.key.type !== 'Identifier') return false;
+				if (x.key.name !== 'setup') return false;
+				return true;
+			}) as unknown as estree.Property;
+			if (setup.value.type !== 'FunctionExpression') return;
+			const render = setup.value.body.body.find((x) => {
+				if (x.type !== 'ReturnStatement') return false;
+				return true;
+			}) as unknown as estree.ReturnStatement;
+			if (render.argument?.type !== 'ArrowFunctionExpression') return;
+			if (render.argument.params.length !== 2) return;
+			const ctx = render.argument.params[0];
+			if (ctx.type !== 'Identifier') return;
+			if (ctx.name !== '_ctx') return;
+			if (render.argument.body.type !== 'BlockStatement') return;
+			for (const [key, value] of moduleForest) {
+				const cssModuleTreeNode = parent.body.find((x) => {
+					if (x.type !== 'VariableDeclaration') return false;
+					if (x.declarations.length !== 1) return false;
+					if (x.declarations[0].id.type !== 'Identifier') return false;
+					if (x.declarations[0].id.name !== value) return false;
+					return true;
+				}) as unknown as estree.VariableDeclaration;
+				if (cssModuleTreeNode.declarations[0].init?.type !== 'ObjectExpression') return;
+				const moduleTree = new Map(cssModuleTreeNode.declarations[0].init.properties.flatMap((property) => {
+					if (property.type !== 'Property') return [];
+					const actualKey = property.key.type === 'Identifier' ? property.key.name : property.key.type === 'Literal' ? property.key.value : null;
+					if (typeof actualKey !== 'string') return [];
+					if (property.value.type === 'Literal') return [[actualKey, property.value.value as string]];
+					if (property.value.type !== 'Identifier') return [];
+					const labelledValue = property.value.name;
+					const actualValue = parent.body.find((x) => {
+						if (x.type !== 'VariableDeclaration') return false;
+						if (x.declarations.length !== 1) return false;
+						if (x.declarations[0].id.type !== 'Identifier') return false;
+						if (x.declarations[0].id.name !== labelledValue) return false;
+						return true;
+					}) as unknown as estree.VariableDeclaration;
+					if (actualValue.declarations[0].init?.type !== 'Literal') return [];
+					return [[actualKey, actualValue.declarations[0].init.value as string]];
+				}));
+				(walk as typeof estreeWalker.walk)(render.argument.body, {
+					enter(childNode) {
+						if (childNode.type !== 'MemberExpression') return;
+						if (childNode.object.type !== 'MemberExpression') return;
+						if (childNode.object.object.type !== 'Identifier') return;
+						if (childNode.object.object.name !== ctx.name) return;
+						if (childNode.object.property.type !== 'Identifier') return;
+						if (childNode.object.property.name !== key) return;
+						if (childNode.property.type !== 'Identifier') return;
+						const actualValue = moduleTree.get(childNode.property.name);
+						if (actualValue === undefined) return;
+						this.replace({
+							type: 'Literal',
+							value: actualValue,
+						});
+					},
+				});
+				(walk as typeof estreeWalker.walk)(render.argument.body, {
+					enter(childNode) {
+						if (childNode.type !== 'MemberExpression') return;
+						if (childNode.object.type !== 'MemberExpression') return;
+						if (childNode.object.object.type !== 'Identifier') return;
+						if (childNode.object.object.name !== ctx.name) return;
+						if (childNode.object.property.type !== 'Identifier') return;
+						if (childNode.object.property.name !== key) return;
+						if (childNode.property.type !== 'Identifier') return;
+						console.error(`Undefined style detected: ${key}.${childNode.property.name} (in ${name})`);
+						this.replace({
+							type: 'Identifier',
+							name: 'undefined',
+						});
+					},
+				});
+				(walk as typeof estreeWalker.walk)(render.argument.body, {
+					enter(childNode) {
+						if (childNode.type !== 'CallExpression') return;
+						if (childNode.callee.type !== 'Identifier') return;
+						if (childNode.callee.name !== 'normalizeClass') return;
+						if (childNode.arguments.length !== 1) return;
+						const normalized = normalizeClass(childNode.arguments[0]);
+						if (normalized === null) return;
+						this.replace({
+							type: 'Literal',
+							value: normalized,
+						});
+					},
+				});
+			}
+			if (node.declarations[0].init.arguments[1].elements.length === 1) {
+				this.replace({
+					type: 'VariableDeclaration',
+					declarations: [{
+						type: 'VariableDeclarator',
+						id: {
+							type: 'Identifier',
+							name: node.declarations[0].id.name,
+						},
+						init: {
+							type: 'Identifier',
+							name: ident,
+						},
+					}],
+					kind: 'const',
+				});
+			} else {
+				this.replace({
+					type: 'VariableDeclaration',
+					declarations: [{
+						type: 'VariableDeclarator',
+						id: {
+							type: 'Identifier',
+							name: node.declarations[0].id.name,
+						},
+						init: {
+							type: 'CallExpression',
+							callee: {
+								type: 'Identifier',
+								name: '_export_sfc',
+							},
+							arguments: [{
+								type: 'Identifier',
+								name: ident,
+							}, {
+								type: 'ArrayExpression',
+								elements: node.declarations[0].init.arguments[1].elements.slice(0, __cssModulesIndex).concat(node.declarations[0].init.arguments[1].elements.slice(__cssModulesIndex + 1)),
+							}],
+						},
+					}],
+					kind: 'const',
+				});
+			}
+		},
+	});
+}
+
+// eslint-disable-next-line import/no-default-export
+export default function pluginUnwindCssModuleClassName(): Plugin {
+	return {
+		name: 'UnwindCssModuleClassName',
+		renderChunk(code): { code: string } {
+			const ast = this.parse(code) as unknown as estree.Node;
+			unwindCssModuleClassName(ast);
+			return { code: generate(ast) };
+		},
+	};
+}
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index 5b4004d8e..506d18790 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -19,26 +19,28 @@
 		"@rollup/plugin-json": "6.0.0",
 		"@rollup/plugin-replace": "5.0.2",
 		"@rollup/pluginutils": "5.0.2",
-		"@syuilo/aiscript": "0.13.2",
-		"@tabler/icons-webfont": "2.17.0",
-		"@vitejs/plugin-vue": "4.2.2",
-		"@vue-macros/reactivity-transform": "0.3.6",
-		"@vue/compiler-sfc": "3.3.1",
-		"autosize": "5.0.2",
-		"blurhash": "2.0.5",
-		"broadcast-channel": "4.20.2",
+		"@syuilo/aiscript": "0.13.3",
+		"@tabler/icons-webfont": "2.21.0",
+		"@vitejs/plugin-vue": "4.2.3",
+		"@vue-macros/reactivity-transform": "0.3.9",
+		"@vue/compiler-sfc": "3.3.4",
+		"astring": "1.8.6",
+		"autosize": "6.0.1",
+		"broadcast-channel": "5.1.0",
 		"browser-image-resizer": "github:misskey-dev/browser-image-resizer#v2.2.1-misskey.3",
+		"buraha": "github:misskey-dev/buraha",
 		"canvas-confetti": "1.6.0",
 		"chart.js": "4.3.0",
 		"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": "6.17.4",
+		"chromatic": "6.18.0",
 		"compare-versions": "5.0.3",
 		"cropperjs": "2.0.0-beta.2",
 		"date-fns": "2.30.0",
 		"escape-regexp": "0.0.1",
+		"estree-walker": "^3.0.3",
 		"eventemitter3": "5.0.1",
 		"gsap": "3.11.5",
 		"idb-keyval": "6.2.1",
@@ -53,7 +55,7 @@
 		"punycode": "2.3.0",
 		"querystring": "0.2.1",
 		"rndstr": "1.0.0",
-		"rollup": "3.21.6",
+		"rollup": "3.23.0",
 		"s-age": "1.1.2",
 		"sanitize-html": "2.10.0",
 		"sass": "1.62.1",
@@ -61,71 +63,70 @@
 		"strict-event-emitter-types": "2.0.0",
 		"syuilo-password-strength": "0.0.1",
 		"textarea-caret": "3.1.0",
-		"three": "0.151.3",
+		"three": "0.153.0",
 		"throttle-debounce": "5.0.0",
 		"tinycolor2": "1.6.0",
 		"tsc-alias": "1.8.6",
 		"tsconfig-paths": "4.2.0",
 		"twemoji-parser": "14.0.0",
-		"typescript": "5.0.4",
+		"typescript": "5.1.3",
 		"uuid": "9.0.0",
 		"vanilla-tilt": "1.8.0",
-		"vite": "4.3.5",
-		"vue": "3.3.1",
-		"vue-plyr": "7.0.0",
+		"vite": "4.3.9",
+		"vue": "3.3.4",
 		"vue-prism-editor": "2.0.0-alpha.2",
 		"vuedraggable": "next"
 	},
 	"devDependencies": {
-		"@storybook/addon-actions": "7.0.10",
-		"@storybook/addon-essentials": "7.0.10",
-		"@storybook/addon-interactions": "7.0.10",
-		"@storybook/addon-links": "7.0.10",
-		"@storybook/addon-storysource": "7.0.10",
-		"@storybook/addons": "7.0.10",
-		"@storybook/blocks": "7.0.10",
-		"@storybook/core-events": "7.0.10",
+		"@storybook/addon-actions": "7.0.18",
+		"@storybook/addon-essentials": "7.0.18",
+		"@storybook/addon-interactions": "7.0.18",
+		"@storybook/addon-links": "7.0.18",
+		"@storybook/addon-storysource": "7.0.18",
+		"@storybook/addons": "7.0.18",
+		"@storybook/blocks": "7.0.18",
+		"@storybook/core-events": "7.0.18",
 		"@storybook/jest": "0.1.0",
-		"@storybook/manager-api": "7.0.10",
-		"@storybook/preview-api": "7.0.10",
-		"@storybook/react": "7.0.10",
-		"@storybook/react-vite": "7.0.10",
+		"@storybook/manager-api": "7.0.18",
+		"@storybook/preview-api": "7.0.18",
+		"@storybook/react": "7.0.18",
+		"@storybook/react-vite": "7.0.18",
 		"@storybook/testing-library": "0.1.0",
-		"@storybook/theming": "7.0.10",
-		"@storybook/types": "7.0.10",
-		"@storybook/vue3": "7.0.10",
-		"@storybook/vue3-vite": "7.0.10",
+		"@storybook/theming": "7.0.18",
+		"@storybook/types": "7.0.18",
+		"@storybook/vue3": "7.0.18",
+		"@storybook/vue3-vite": "7.0.18",
 		"@testing-library/jest-dom": "5.16.5",
 		"@testing-library/vue": "7.0.0",
 		"@types/escape-regexp": "0.0.1",
 		"@types/estree": "1.0.1",
 		"@types/gulp": "4.0.10",
 		"@types/gulp-rename": "2.0.2",
-		"@types/matter-js": "0.18.3",
+		"@types/matter-js": "0.18.5",
 		"@types/micromatch": "4.0.2",
-		"@types/node": "20.1.3",
+		"@types/node": "20.2.5",
 		"@types/punycode": "2.1.0",
 		"@types/sanitize-html": "2.9.0",
 		"@types/seedrandom": "3.0.5",
-		"@types/testing-library__jest-dom": "^5.14.5",
+		"@types/testing-library__jest-dom": "^5.14.6",
 		"@types/throttle-debounce": "5.0.0",
 		"@types/tinycolor2": "1.4.3",
 		"@types/uuid": "9.0.1",
 		"@types/websocket": "1.0.5",
 		"@types/ws": "8.5.4",
-		"@typescript-eslint/eslint-plugin": "5.59.5",
-		"@typescript-eslint/parser": "5.59.5",
-		"@vitest/coverage-c8": "0.31.0",
-		"@vue/runtime-core": "3.3.1",
-		"astring": "1.8.4",
+		"@typescript-eslint/eslint-plugin": "5.59.8",
+		"@typescript-eslint/parser": "5.59.8",
+		"@vitest/coverage-c8": "0.31.4",
+		"@vue/runtime-core": "3.3.4",
+		"acorn": "^8.8.2",
 		"chokidar-cli": "3.0.0",
 		"cross-env": "7.0.3",
-		"cypress": "12.12.0",
-		"eslint": "8.40.0",
+		"cypress": "12.13.0",
+		"eslint": "8.41.0",
 		"eslint-plugin-import": "2.27.5",
-		"eslint-plugin-vue": "9.12.0",
+		"eslint-plugin-vue": "9.14.1",
 		"fast-glob": "3.2.12",
-		"happy-dom": "9.16.0",
+		"happy-dom": "9.20.3",
 		"micromatch": "3.1.10",
 		"msw": "1.2.1",
 		"msw-storybook-addon": "1.8.0",
@@ -133,13 +134,13 @@
 		"react": "18.2.0",
 		"react-dom": "18.2.0",
 		"start-server-and-test": "2.0.0",
-		"storybook": "7.0.10",
+		"storybook": "7.0.18",
 		"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
 		"summaly": "github:misskey-dev/summaly",
 		"vite-plugin-turbosnap": "1.0.2",
-		"vitest": "0.31.0",
+		"vitest": "0.31.4",
 		"vitest-fetch-mock": "0.2.2",
-		"vue-eslint-parser": "9.2.1",
-		"vue-tsc": "1.6.4"
+		"vue-eslint-parser": "9.3.0",
+		"vue-tsc": "1.6.5"
 	}
 }
diff --git a/packages/frontend/src/_boot_.ts b/packages/frontend/src/_boot_.ts
new file mode 100644
index 000000000..921c16176
--- /dev/null
+++ b/packages/frontend/src/_boot_.ts
@@ -0,0 +1,14 @@
+// https://vitejs.dev/config/build-options.html#build-modulepreload
+import 'vite/modulepreload-polyfill';
+
+import '@/style.scss';
+import { mainBoot } from './boot/main-boot';
+import { subBoot } from './boot/sub-boot';
+
+const subBootPaths = ['/share', '/auth', '/miauth', '/signup-complete'];
+
+if (subBootPaths.some(i => location.pathname === i || location.pathname.startsWith(i + '/'))) {
+	subBoot();
+} else {
+	mainBoot();
+}
diff --git a/packages/frontend/src/account.ts b/packages/frontend/src/account.ts
index 9b104391d..4770f616a 100644
--- a/packages/frontend/src/account.ts
+++ b/packages/frontend/src/account.ts
@@ -3,11 +3,11 @@ import * as misskey from 'misskey-js';
 import { showSuspendedDialog } from './scripts/show-suspended-dialog';
 import { i18n } from './i18n';
 import { miLocalStorage } from './local-storage';
+import { MenuButton } from './types/menu';
 import { del, get, set } from '@/scripts/idb-proxy';
 import { apiUrl } from '@/config';
 import { waiting, api, popup, popupMenu, success, alert } from '@/os';
 import { unisonReload, reloadChannel } from '@/scripts/unison-reload';
-import { MenuButton } from './types/menu';
 
 // TODO: 他のタブと永続化されたstateを同期
 
@@ -101,57 +101,57 @@ function fetchAccount(token: string, id?: string, forceShowDialog?: boolean): Pr
 				'Content-Type': 'application/json',
 			},
 		})
-		.then(res => new Promise<Account | { error: Record<string, any> }>((done2, fail2) => {
-			if (res.status >= 500 && res.status < 600) {
+			.then(res => new Promise<Account | { error: Record<string, any> }>((done2, fail2) => {
+				if (res.status >= 500 && res.status < 600) {
 				// サーバーエラー(5xx)の場合をrejectとする
 				// (認証エラーなど4xxはresolve)
-				return fail2(res);
-			}
-			res.json().then(done2, fail2);
-		}))
-		.then(async res => {
-			if (res.error) {
-				if (res.error.id === 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370') {
+					return fail2(res);
+				}
+				res.json().then(done2, fail2);
+			}))
+			.then(async res => {
+				if (res.error) {
+					if (res.error.id === 'a8c724b3-6e9c-4b46-b1a8-bc3ed6258370') {
 					// SUSPENDED
-					if (forceShowDialog || $i && (token === $i.token || id === $i.id)) {
-						await showSuspendedDialog();
-					}
-				} else if (res.error.id === 'e5b3b9f0-2b8f-4b9f-9c1f-8c5c1b2e1b1a') {
+						if (forceShowDialog || $i && (token === $i.token || id === $i.id)) {
+							await showSuspendedDialog();
+						}
+					} else if (res.error.id === 'e5b3b9f0-2b8f-4b9f-9c1f-8c5c1b2e1b1a') {
 					// USER_IS_DELETED
 					// アカウントが削除されている
-					if (forceShowDialog || $i && (token === $i.token || id === $i.id)) {
-						await alert({
-							type: 'error',
-							title: i18n.ts.accountDeleted,
-							text: i18n.ts.accountDeletedDescription,
-						});
-					}
-				} else if (res.error.id === 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14') {
+						if (forceShowDialog || $i && (token === $i.token || id === $i.id)) {
+							await alert({
+								type: 'error',
+								title: i18n.ts.accountDeleted,
+								text: i18n.ts.accountDeletedDescription,
+							});
+						}
+					} else if (res.error.id === 'b0a7f5f8-dc2f-4171-b91f-de88ad238e14') {
 					// AUTHENTICATION_FAILED
 					// トークンが無効化されていたりアカウントが削除されたりしている
-					if (forceShowDialog || $i && (token === $i.token || id === $i.id)) {
+						if (forceShowDialog || $i && (token === $i.token || id === $i.id)) {
+							await alert({
+								type: 'error',
+								title: i18n.ts.tokenRevoked,
+								text: i18n.ts.tokenRevokedDescription,
+							});
+						}
+					} else {
 						await alert({
 							type: 'error',
-							title: i18n.ts.tokenRevoked,
-							text: i18n.ts.tokenRevokedDescription,
+							title: i18n.ts.failedToFetchAccountInformation,
+							text: JSON.stringify(res.error),
 						});
 					}
-				} else {
-					await alert({
-						type: 'error',
-						title: i18n.ts.failedToFetchAccountInformation,
-						text: JSON.stringify(res.error),
-					});
-				}
 
-				// rejectかつ理由がtrueの場合、削除対象であることを示す
-				fail(true);
-			} else {
-				(res as Account).token = token;
-				done(res as Account);
-			}
-		})
-		.catch(fail);
+					// rejectかつ理由がtrueの場合、削除対象であることを示す
+					fail(true);
+				} else {
+					(res as Account).token = token;
+					done(res as Account);
+				}
+			})
+			.catch(fail);
 	});
 }
 
@@ -305,3 +305,7 @@ export async function openAccountMenu(opts: {
 		});
 	}
 }
+
+if (_DEV_) {
+	(window as any).$i = $i;
+}
diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts
new file mode 100644
index 000000000..e1b12fe7d
--- /dev/null
+++ b/packages/frontend/src/boot/common.ts
@@ -0,0 +1,262 @@
+import { computed, createApp, watch, markRaw, version as vueVersion, defineAsyncComponent, App } from 'vue';
+import { compareVersions } from 'compare-versions';
+import widgets from '@/widgets';
+import directives from '@/directives';
+import components from '@/components';
+import { version, ui, lang, updateLocale } from '@/config';
+import { applyTheme } from '@/scripts/theme';
+import { isDeviceDarkmode } from '@/scripts/is-device-darkmode';
+import { i18n, updateI18n } from '@/i18n';
+import { confirm, alert, post, popup, toast } from '@/os';
+import { $i, refreshAccount, login, updateAccount, signout } from '@/account';
+import { defaultStore, ColdDeviceStorage } from '@/store';
+import { fetchInstance, instance } from '@/instance';
+import { deviceKind } from '@/scripts/device-kind';
+import { reloadChannel } from '@/scripts/unison-reload';
+import { reactionPicker } from '@/scripts/reaction-picker';
+import { getUrlWithoutLoginId } from '@/scripts/login-id';
+import { getAccountFromId } from '@/scripts/get-account-from-id';
+import { deckStore } from '@/ui/deck/deck-store';
+import { miLocalStorage } from '@/local-storage';
+import { fetchCustomEmojis } from '@/custom-emojis';
+import { mainRouter } from '@/router';
+
+export async function common(createVue: () => App<Element>) {
+	console.info(`Misskey v${version}`);
+
+	if (_DEV_) {
+		console.warn('Development mode!!!');
+
+		console.info(`vue ${vueVersion}`);
+
+		// eslint-disable-next-line @typescript-eslint/no-explicit-any
+		(window as any).$i = $i;
+		// eslint-disable-next-line @typescript-eslint/no-explicit-any
+		(window as any).$store = defaultStore;
+
+		window.addEventListener('error', event => {
+			console.error(event);
+			/*
+			alert({
+				type: 'error',
+				title: 'DEV: Unhandled error',
+				text: event.message
+			});
+			*/
+		});
+
+		window.addEventListener('unhandledrejection', event => {
+			console.error(event);
+			/*
+			alert({
+				type: 'error',
+				title: 'DEV: Unhandled promise rejection',
+				text: event.reason
+			});
+			*/
+		});
+	}
+
+	const splash = document.getElementById('splash');
+	// 念のためnullチェック(HTMLが古い場合があるため(そのうち消す))
+	if (splash) splash.addEventListener('transitionend', () => {
+		splash.remove();
+	});
+
+	let isClientUpdated = false;
+
+	//#region クライアントが更新されたかチェック
+	const lastVersion = miLocalStorage.getItem('lastVersion');
+	if (lastVersion !== version) {
+		miLocalStorage.setItem('lastVersion', version);
+
+		// テーマリビルドするため
+		miLocalStorage.removeItem('theme');
+
+		try { // 変なバージョン文字列来るとcompareVersionsでエラーになるため
+			if (lastVersion != null && compareVersions(version, lastVersion) === 1) {
+				isClientUpdated = true;
+			}
+		} catch (err) { /* empty */ }
+	}
+	//#endregion
+
+	//#region Detect language & fetch translations
+	const localeVersion = miLocalStorage.getItem('localeVersion');
+	const localeOutdated = (localeVersion == null || localeVersion !== version);
+	if (localeOutdated) {
+		const res = await window.fetch(`/assets/locales/${lang}.${version}.json`);
+		if (res.status === 200) {
+			const newLocale = await res.text();
+			const parsedNewLocale = JSON.parse(newLocale);
+			miLocalStorage.setItem('locale', newLocale);
+			miLocalStorage.setItem('localeVersion', version);
+			updateLocale(parsedNewLocale);
+			updateI18n(parsedNewLocale);
+		}
+	}
+	//#endregion
+
+	// タッチデバイスでCSSの:hoverを機能させる
+	document.addEventListener('touchend', () => {}, { passive: true });
+
+	// 一斉リロード
+	reloadChannel.addEventListener('message', path => {
+		if (path !== null) location.href = path;
+		else location.reload();
+	});
+
+	// If mobile, insert the viewport meta tag
+	if (['smartphone', 'tablet'].includes(deviceKind)) {
+		const viewport = document.getElementsByName('viewport').item(0);
+		viewport.setAttribute('content',
+			`${viewport.getAttribute('content')}, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover`);
+	}
+
+	//#region Set lang attr
+	const html = document.documentElement;
+	html.setAttribute('lang', lang);
+	//#endregion
+
+	await defaultStore.ready;
+	await deckStore.ready;
+
+	const fetchInstanceMetaPromise = fetchInstance();
+
+	fetchInstanceMetaPromise.then(() => {
+		miLocalStorage.setItem('v', instance.version);
+	});
+
+	//#region loginId
+	const params = new URLSearchParams(location.search);
+	const loginId = params.get('loginId');
+
+	if (loginId) {
+		const target = getUrlWithoutLoginId(location.href);
+
+		if (!$i || $i.id !== loginId) {
+			const account = await getAccountFromId(loginId);
+			if (account) {
+				await login(account.token, target);
+			}
+		}
+
+		history.replaceState({ misskey: 'loginId' }, '', target);
+	}
+	//#endregion
+
+	// NOTE: この処理は必ずクライアント更新チェック処理より後に来ること(テーマ再構築のため)
+	watch(defaultStore.reactiveState.darkMode, (darkMode) => {
+		applyTheme(darkMode ? ColdDeviceStorage.get('darkTheme') : ColdDeviceStorage.get('lightTheme'));
+	}, { immediate: miLocalStorage.getItem('theme') == null });
+
+	const darkTheme = computed(ColdDeviceStorage.makeGetterSetter('darkTheme'));
+	const lightTheme = computed(ColdDeviceStorage.makeGetterSetter('lightTheme'));
+
+	watch(darkTheme, (theme) => {
+		if (defaultStore.state.darkMode) {
+			applyTheme(theme);
+		}
+	});
+
+	watch(lightTheme, (theme) => {
+		if (!defaultStore.state.darkMode) {
+			applyTheme(theme);
+		}
+	});
+
+	//#region Sync dark mode
+	if (ColdDeviceStorage.get('syncDeviceDarkMode')) {
+		defaultStore.set('darkMode', isDeviceDarkmode());
+	}
+
+	window.matchMedia('(prefers-color-scheme: dark)').addListener(mql => {
+		if (ColdDeviceStorage.get('syncDeviceDarkMode')) {
+			defaultStore.set('darkMode', mql.matches);
+		}
+	});
+	//#endregion
+
+	fetchInstanceMetaPromise.then(() => {
+		if (defaultStore.state.themeInitial) {
+			if (instance.defaultLightTheme != null) ColdDeviceStorage.set('lightTheme', JSON.parse(instance.defaultLightTheme));
+			if (instance.defaultDarkTheme != null) ColdDeviceStorage.set('darkTheme', JSON.parse(instance.defaultDarkTheme));
+			defaultStore.set('themeInitial', false);
+		}
+	});
+
+	watch(defaultStore.reactiveState.useBlurEffectForModal, v => {
+		document.documentElement.style.setProperty('--modalBgFilter', v ? 'blur(4px)' : 'none');
+	}, { immediate: true });
+
+	watch(defaultStore.reactiveState.useBlurEffect, v => {
+		if (v) {
+			document.documentElement.style.removeProperty('--blur');
+		} else {
+			document.documentElement.style.setProperty('--blur', 'none');
+		}
+	}, { immediate: true });
+
+	//#region Fetch user
+	if ($i && $i.token) {
+		if (_DEV_) {
+			console.log('account cache found. refreshing...');
+		}
+
+		refreshAccount();
+	}
+	//#endregion
+
+	try {
+		await fetchCustomEmojis();
+	} catch (err) { /* empty */ }
+
+	const app = createVue();
+
+	if (_DEV_) {
+		app.config.performance = true;
+	}
+
+	widgets(app);
+	directives(app);
+	components(app);
+
+	// https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210
+	// なぜか2回実行されることがあるため、mountするdivを1つに制限する
+	const rootEl = ((): HTMLElement => {
+		const MISSKEY_MOUNT_DIV_ID = 'misskey_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;
+	})();
+
+	app.mount(rootEl);
+
+	// boot.jsのやつを解除
+	window.onerror = null;
+	window.onunhandledrejection = null;
+
+	removeSplash();
+
+	return {
+		isClientUpdated,
+		app,
+	};
+}
+
+function removeSplash() {
+	const splash = document.getElementById('splash');
+	if (splash) {
+		splash.style.opacity = '0';
+		splash.style.pointerEvents = 'none';
+	}
+}
diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts
new file mode 100644
index 000000000..76e8c5072
--- /dev/null
+++ b/packages/frontend/src/boot/main-boot.ts
@@ -0,0 +1,254 @@
+import { computed, createApp, watch, markRaw, version as vueVersion, defineAsyncComponent } from 'vue';
+import { common } from './common';
+import { version, ui, lang, updateLocale } from '@/config';
+import { i18n, updateI18n } from '@/i18n';
+import { confirm, alert, post, popup, toast } from '@/os';
+import { useStream } from '@/stream';
+import * as sound from '@/scripts/sound';
+import { $i, refreshAccount, login, updateAccount, signout } from '@/account';
+import { defaultStore, ColdDeviceStorage } from '@/store';
+import { makeHotkey } from '@/scripts/hotkey';
+import { reactionPicker } from '@/scripts/reaction-picker';
+import { miLocalStorage } from '@/local-storage';
+import { claimAchievement, claimedAchievements } from '@/scripts/achievements';
+import { mainRouter } from '@/router';
+import { initializeSw } from '@/scripts/initialize-sw';
+
+export async function mainBoot() {
+	const { isClientUpdated } = await common(() => createApp(
+		new URLSearchParams(window.location.search).has('zen') || (ui === 'deck' && location.pathname !== '/') ? defineAsyncComponent(() => import('@/ui/zen.vue')) :
+		!$i ? defineAsyncComponent(() => import('@/ui/visitor.vue')) :
+		ui === 'deck' ? defineAsyncComponent(() => import('@/ui/deck.vue')) :
+		ui === 'classic' ? defineAsyncComponent(() => import('@/ui/classic.vue')) :
+		defineAsyncComponent(() => import('@/ui/universal.vue')),
+	));
+
+	reactionPicker.init();
+
+	if (isClientUpdated && $i) {
+		popup(defineAsyncComponent(() => import('@/components/MkUpdated.vue')), {}, {}, 'closed');
+	}
+
+	const stream = useStream();
+
+	let reloadDialogShowing = false;
+	stream.on('_disconnected_', async () => {
+		if (defaultStore.state.serverDisconnectedBehavior === 'reload') {
+			location.reload();
+		} else if (defaultStore.state.serverDisconnectedBehavior === 'dialog') {
+			if (reloadDialogShowing) return;
+			reloadDialogShowing = true;
+			const { canceled } = await confirm({
+				type: 'warning',
+				title: i18n.ts.disconnectedFromServer,
+				text: i18n.ts.reloadConfirm,
+			});
+			reloadDialogShowing = false;
+			if (!canceled) {
+				location.reload();
+			}
+		}
+	});
+
+	for (const plugin of ColdDeviceStorage.get('plugins').filter(p => p.active)) {
+		import('../plugin').then(async ({ install }) => {
+			// Workaround for https://bugs.webkit.org/show_bug.cgi?id=242740
+			await new Promise(r => setTimeout(r, 0));
+			install(plugin);
+		});
+	}
+
+	const hotkeys = {
+		'd': (): void => {
+			defaultStore.set('darkMode', !defaultStore.state.darkMode);
+		},
+		's': (): void => {
+			mainRouter.push('/search');
+		},
+	};
+
+	if ($i) {
+		// only add post shortcuts if logged in
+		hotkeys['p|n'] = post;
+
+		defaultStore.loaded.then(() => {
+			if (defaultStore.state.accountSetupWizard !== -1) {
+				popup(defineAsyncComponent(() => import('@/components/MkUserSetupDialog.vue')), {}, {}, 'closed');
+			}
+		});
+
+		if ($i.isDeleted) {
+			alert({
+				type: 'warning',
+				text: i18n.ts.accountDeletionInProgress,
+			});
+		}
+
+		const now = new Date();
+		const m = now.getMonth() + 1;
+		const d = now.getDate();
+		
+		if ($i.birthday) {
+			const bm = parseInt($i.birthday.split('-')[1]);
+			const bd = parseInt($i.birthday.split('-')[2]);
+			if (m === bm && d === bd) {
+				claimAchievement('loggedInOnBirthday');
+			}
+		}
+
+		if (m === 1 && d === 1) {
+			claimAchievement('loggedInOnNewYearsDay');
+		}
+
+		if ($i.loggedInDays >= 3) claimAchievement('login3');
+		if ($i.loggedInDays >= 7) claimAchievement('login7');
+		if ($i.loggedInDays >= 15) claimAchievement('login15');
+		if ($i.loggedInDays >= 30) claimAchievement('login30');
+		if ($i.loggedInDays >= 60) claimAchievement('login60');
+		if ($i.loggedInDays >= 100) claimAchievement('login100');
+		if ($i.loggedInDays >= 200) claimAchievement('login200');
+		if ($i.loggedInDays >= 300) claimAchievement('login300');
+		if ($i.loggedInDays >= 400) claimAchievement('login400');
+		if ($i.loggedInDays >= 500) claimAchievement('login500');
+		if ($i.loggedInDays >= 600) claimAchievement('login600');
+		if ($i.loggedInDays >= 700) claimAchievement('login700');
+		if ($i.loggedInDays >= 800) claimAchievement('login800');
+		if ($i.loggedInDays >= 900) claimAchievement('login900');
+		if ($i.loggedInDays >= 1000) claimAchievement('login1000');
+
+		if ($i.notesCount > 0) claimAchievement('notes1');
+		if ($i.notesCount >= 10) claimAchievement('notes10');
+		if ($i.notesCount >= 100) claimAchievement('notes100');
+		if ($i.notesCount >= 500) claimAchievement('notes500');
+		if ($i.notesCount >= 1000) claimAchievement('notes1000');
+		if ($i.notesCount >= 5000) claimAchievement('notes5000');
+		if ($i.notesCount >= 10000) claimAchievement('notes10000');
+		if ($i.notesCount >= 20000) claimAchievement('notes20000');
+		if ($i.notesCount >= 30000) claimAchievement('notes30000');
+		if ($i.notesCount >= 40000) claimAchievement('notes40000');
+		if ($i.notesCount >= 50000) claimAchievement('notes50000');
+		if ($i.notesCount >= 60000) claimAchievement('notes60000');
+		if ($i.notesCount >= 70000) claimAchievement('notes70000');
+		if ($i.notesCount >= 80000) claimAchievement('notes80000');
+		if ($i.notesCount >= 90000) claimAchievement('notes90000');
+		if ($i.notesCount >= 100000) claimAchievement('notes100000');
+
+		if ($i.followersCount > 0) claimAchievement('followers1');
+		if ($i.followersCount >= 10) claimAchievement('followers10');
+		if ($i.followersCount >= 50) claimAchievement('followers50');
+		if ($i.followersCount >= 100) claimAchievement('followers100');
+		if ($i.followersCount >= 300) claimAchievement('followers300');
+		if ($i.followersCount >= 500) claimAchievement('followers500');
+		if ($i.followersCount >= 1000) claimAchievement('followers1000');
+
+		if (Date.now() - new Date($i.createdAt).getTime() > 1000 * 60 * 60 * 24 * 365) {
+			claimAchievement('passedSinceAccountCreated1');
+		}
+		if (Date.now() - new Date($i.createdAt).getTime() > 1000 * 60 * 60 * 24 * 365 * 2) {
+			claimAchievement('passedSinceAccountCreated2');
+		}
+		if (Date.now() - new Date($i.createdAt).getTime() > 1000 * 60 * 60 * 24 * 365 * 3) {
+			claimAchievement('passedSinceAccountCreated3');
+		}
+
+		if (claimedAchievements.length >= 30) {
+			claimAchievement('collectAchievements30');
+		}
+
+		window.setInterval(() => {
+			if (Math.floor(Math.random() * 20000) === 0) {
+				claimAchievement('justPlainLucky');
+			}
+		}, 1000 * 10);
+
+		window.setTimeout(() => {
+			claimAchievement('client30min');
+		}, 1000 * 60 * 30);
+
+		window.setTimeout(() => {
+			claimAchievement('client60min');
+		}, 1000 * 60 * 60);
+
+		const lastUsed = miLocalStorage.getItem('lastUsed');
+		if (lastUsed) {
+			const lastUsedDate = parseInt(lastUsed, 10);
+			// 二時間以上前なら
+			if (Date.now() - lastUsedDate > 1000 * 60 * 60 * 2) {
+				toast(i18n.t('welcomeBackWithName', {
+					name: $i.name || $i.username,
+				}));
+			}
+		}
+		miLocalStorage.setItem('lastUsed', Date.now().toString());
+
+		const latestDonationInfoShownAt = miLocalStorage.getItem('latestDonationInfoShownAt');
+		const neverShowDonationInfo = miLocalStorage.getItem('neverShowDonationInfo');
+		if (neverShowDonationInfo !== 'true' && (new Date($i.createdAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 3))) && !location.pathname.startsWith('/miauth')) {
+			if (latestDonationInfoShownAt == null || (new Date(latestDonationInfoShownAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 30)))) {
+				popup(defineAsyncComponent(() => import('@/components/MkDonation.vue')), {}, {}, 'closed');
+			}
+		}
+
+		if ('Notification' in window) {
+			// 許可を得ていなかったらリクエスト
+			if (Notification.permission === 'default') {
+				Notification.requestPermission();
+			}
+		}
+
+		const main = markRaw(stream.useChannel('main', null, 'System'));
+
+		// 自分の情報が更新されたとき
+		main.on('meUpdated', i => {
+			updateAccount(i);
+		});
+
+		main.on('readAllNotifications', () => {
+			updateAccount({ hasUnreadNotification: false });
+		});
+
+		main.on('unreadNotification', () => {
+			updateAccount({ hasUnreadNotification: true });
+		});
+
+		main.on('unreadMention', () => {
+			updateAccount({ hasUnreadMentions: true });
+		});
+
+		main.on('readAllUnreadMentions', () => {
+			updateAccount({ hasUnreadMentions: false });
+		});
+
+		main.on('unreadSpecifiedNote', () => {
+			updateAccount({ hasUnreadSpecifiedNotes: true });
+		});
+
+		main.on('readAllUnreadSpecifiedNotes', () => {
+			updateAccount({ hasUnreadSpecifiedNotes: false });
+		});
+
+		main.on('readAllAntennas', () => {
+			updateAccount({ hasUnreadAntenna: false });
+		});
+
+		main.on('unreadAntenna', () => {
+			updateAccount({ hasUnreadAntenna: true });
+			sound.play('antenna');
+		});
+
+		main.on('readAllAnnouncements', () => {
+			updateAccount({ hasUnreadAnnouncement: false });
+		});
+
+		// トークンが再生成されたとき
+		// このままではMisskeyが利用できないので強制的にサインアウトさせる
+		main.on('myTokenRegenerated', () => {
+			signout();
+		});
+	}
+
+	// shortcut
+	document.addEventListener('keydown', makeHotkey(hotkeys));
+
+	initializeSw();
+}
diff --git a/packages/frontend/src/boot/sub-boot.ts b/packages/frontend/src/boot/sub-boot.ts
new file mode 100644
index 000000000..c2664f6c1
--- /dev/null
+++ b/packages/frontend/src/boot/sub-boot.ts
@@ -0,0 +1,8 @@
+import { computed, createApp, watch, markRaw, version as vueVersion, defineAsyncComponent } from 'vue';
+import { common } from './common';
+
+export async function subBoot() {
+	const { isClientUpdated } = await common(() => createApp(
+		defineAsyncComponent(() => import('@/ui/minimum.vue')),
+	));
+}
diff --git a/packages/frontend/src/components/MkAbuseReportWindow.vue b/packages/frontend/src/components/MkAbuseReportWindow.vue
index 9f2bf9933..48236782d 100644
--- a/packages/frontend/src/components/MkAbuseReportWindow.vue
+++ b/packages/frontend/src/components/MkAbuseReportWindow.vue
@@ -1,5 +1,5 @@
 <template>
-<MkWindow ref="uiWindow" :initial-width="400" :initial-height="500" :can-resize="true" @closed="emit('closed')">
+<MkWindow ref="uiWindow" :initialWidth="400" :initialHeight="500" :canResize="true" @closed="emit('closed')">
 	<template #header>
 		<i class="ti ti-exclamation-circle" style="margin-right: 0.5em;"></i>
 		<I18n :src="i18n.ts.reportAbuseOf" tag="span">
@@ -8,8 +8,8 @@
 			</template>
 		</I18n>
 	</template>
-	<MkSpacer :margin-min="20" :margin-max="28">
-		<div class="dpvffvvy _gaps_m">
+	<MkSpacer :marginMin="20" :marginMax="28">
+		<div class="_gaps_m" :class="$style.root">
 			<div class="">
 				<MkTextarea v-model="comment">
 					<template #label>{{ i18n.ts.details }}</template>
@@ -60,8 +60,8 @@ function send() {
 }
 </script>
 
-<style lang="scss" scoped>
-.dpvffvvy {
+<style lang="scss" module>
+.root {
 	--root-margin: 16px;
 }
 </style>
diff --git a/packages/frontend/src/components/MkAccountMoved.vue b/packages/frontend/src/components/MkAccountMoved.vue
index b02bfdc2b..bc07b9ba5 100644
--- a/packages/frontend/src/components/MkAccountMoved.vue
+++ b/packages/frontend/src/components/MkAccountMoved.vue
@@ -7,11 +7,11 @@
 </template>
 
 <script lang="ts" setup>
+import { ref } from 'vue';
+import { UserLite } from 'misskey-js/built/entities';
 import MkMention from './MkMention.vue';
 import { i18n } from '@/i18n';
 import { host as localHost } from '@/config';
-import { ref } from 'vue';
-import { UserLite } from 'misskey-js/built/entities';
 import { api } from '@/os';
 
 const user = ref<UserLite>();
diff --git a/packages/frontend/src/components/MkAchievements.vue b/packages/frontend/src/components/MkAchievements.vue
index d30037dcf..3fdb261da 100644
--- a/packages/frontend/src/components/MkAchievements.vue
+++ b/packages/frontend/src/components/MkAchievements.vue
@@ -3,7 +3,14 @@
 	<div v-if="achievements" :class="$style.root">
 		<div v-for="achievement in achievements" :key="achievement" :class="$style.achievement" class="_panel">
 			<div :class="$style.icon">
-				<div :class="[$style.iconFrame, $style['iconFrame_' + ACHIEVEMENT_BADGES[achievement.name].frame]]">
+				<div
+					:class="[$style.iconFrame, {
+						[$style.iconFrame_bronze]: ACHIEVEMENT_BADGES[achievement.name].frame === 'bronze',
+						[$style.iconFrame_silver]: ACHIEVEMENT_BADGES[achievement.name].frame === 'silver',
+						[$style.iconFrame_gold]: ACHIEVEMENT_BADGES[achievement.name].frame === 'gold',
+						[$style.iconFrame_platinum]: ACHIEVEMENT_BADGES[achievement.name].frame === 'platinum',
+					}]"
+				>
 					<div :class="[$style.iconInner]" :style="{ background: ACHIEVEMENT_BADGES[achievement.name].bg }">
 						<img :class="$style.iconImg" :src="ACHIEVEMENT_BADGES[achievement.name].img">
 					</div>
diff --git a/packages/frontend/src/components/MkAnalogClock.stories.impl.ts b/packages/frontend/src/components/MkAnalogClock.stories.impl.ts
index e7fbb4728..0aebdccf4 100644
--- a/packages/frontend/src/components/MkAnalogClock.stories.impl.ts
+++ b/packages/frontend/src/components/MkAnalogClock.stories.impl.ts
@@ -1,7 +1,7 @@
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
 import { StoryObj } from '@storybook/vue3';
+import isChromatic from 'chromatic/isChromatic';
 import MkAnalogClock from './MkAnalogClock.vue';
-import isChromatic from 'chromatic';
 export const Default = {
 	render(args) {
 		return {
diff --git a/packages/frontend/src/components/MkAnalogClock.vue b/packages/frontend/src/components/MkAnalogClock.vue
index f12020f81..05caffe7d 100644
--- a/packages/frontend/src/components/MkAnalogClock.vue
+++ b/packages/frontend/src/components/MkAnalogClock.vue
@@ -39,6 +39,7 @@
 	-->
 
 	<line
+		ref="sLine"
 		:class="[$style.s, { [$style.animate]: !disableSAnimate && sAnimation !== 'none', [$style.elastic]: sAnimation === 'elastic', [$style.easeOut]: sAnimation === 'easeOut' }]"
 		:x1="5 - (0 * (sHandLengthRatio * handsTailLength))"
 		:y1="5 + (1 * (sHandLengthRatio * handsTailLength))"
@@ -73,9 +74,10 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, onMounted, onBeforeUnmount } from 'vue';
+import { computed, onMounted, onBeforeUnmount, ref } from 'vue';
 import tinycolor from 'tinycolor2';
 import { globalEvents } from '@/events.js';
+import { defaultIdlingRenderScheduler } from '@/scripts/idle-render.js';
 
 // https://stackoverflow.com/questions/1878907/how-can-i-find-the-difference-between-two-angles
 const angleDiff = (a: number, b: number) => {
@@ -145,6 +147,7 @@ let mAngle = $ref<number>(0);
 let sAngle = $ref<number>(0);
 let disableSAnimate = $ref(false);
 let sOneRound = false;
+const sLine = ref<SVGPathElement>();
 
 function tick() {
 	const now = props.now();
@@ -160,17 +163,21 @@ function tick() {
 	}
 	hAngle = Math.PI * (h % (props.twentyfour ? 24 : 12) + (m + s / 60) / 60) / (props.twentyfour ? 12 : 6);
 	mAngle = Math.PI * (m + s / 60) / 30;
-	if (sOneRound) { // 秒針が一周した際のアニメーションをよしなに処理する(これが無いと秒が59->0になったときに期待したアニメーションにならない)
+	if (sOneRound && sLine.value) { // 秒針が一周した際のアニメーションをよしなに処理する(これが無いと秒が59->0になったときに期待したアニメーションにならない)
 		sAngle = Math.PI * 60 / 30;
-		window.setTimeout(() => {
+		defaultIdlingRenderScheduler.delete(tick);
+		sLine.value.addEventListener('transitionend', () => {
 			disableSAnimate = true;
-			window.setTimeout(() => {
+			requestAnimationFrame(() => {
 				sAngle = 0;
-				window.setTimeout(() => {
+				requestAnimationFrame(() => {
 					disableSAnimate = false;
-				}, 100);
-			}, 100);
-		}, 700);
+					if (enabled) {
+						defaultIdlingRenderScheduler.add(tick);
+					}
+				});
+			});
+		}, { once: true });
 	} else {
 		sAngle = Math.PI * s / 30;
 	}
@@ -194,20 +201,13 @@ function calcColors() {
 calcColors();
 
 onMounted(() => {
-	const update = () => {
-		if (enabled) {
-			tick();
-			window.setTimeout(update, 1000);
-		}
-	};
-	update();
-
+	defaultIdlingRenderScheduler.add(tick);
 	globalEvents.on('themeChanged', calcColors);
 });
 
 onBeforeUnmount(() => {
 	enabled = false;
-
+	defaultIdlingRenderScheduler.delete(tick);
 	globalEvents.off('themeChanged', calcColors);
 });
 </script>
diff --git a/packages/frontend/src/components/MkAnimBg.vue b/packages/frontend/src/components/MkAnimBg.vue
new file mode 100644
index 000000000..575ea7c5e
--- /dev/null
+++ b/packages/frontend/src/components/MkAnimBg.vue
@@ -0,0 +1,243 @@
+<template>
+<canvas ref="canvasEl" style="width: 100%; height: 100%; pointer-events: none;"></canvas>
+</template>
+
+<script lang="ts" setup>
+import { onMounted, onUnmounted, shallowRef } from 'vue';
+import isChromatic from 'chromatic/isChromatic';
+
+const canvasEl = shallowRef<HTMLCanvasElement>();
+
+const props = withDefaults(defineProps<{
+	scale?: number;
+	focus?: number;
+}>(), {
+	scale: 1.0,
+	focus: 1.0,
+});
+
+function loadShader(gl, type, source) {
+	const shader = gl.createShader(type);
+
+	gl.shaderSource(shader, source);
+	gl.compileShader(shader);
+
+	if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
+		alert(
+			`falied to compile shader: ${gl.getShaderInfoLog(shader)}`,
+		);
+		gl.deleteShader(shader);
+		return null;
+	}
+
+	return shader;
+}
+
+function initShaderProgram(gl, vsSource, fsSource) {
+	const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
+	const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
+
+	const shaderProgram = gl.createProgram();
+	gl.attachShader(shaderProgram, vertexShader);
+	gl.attachShader(shaderProgram, fragmentShader);
+	gl.linkProgram(shaderProgram);
+
+	if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
+		alert(
+			`failed to init shader: ${gl.getProgramInfoLog(
+				shaderProgram,
+			)}`,
+		);
+		return null;
+	}
+
+	return shaderProgram;
+}
+
+let handle: ReturnType<typeof window['requestAnimationFrame']> | null = null;
+
+onMounted(() => {
+	const canvas = canvasEl.value!;
+	canvas.width = canvas.offsetWidth;
+	canvas.height = canvas.offsetHeight;
+
+	const gl = canvas.getContext('webgl', { premultipliedAlpha: true });
+	if (gl == null) return;
+
+	gl.clearColor(0.0, 0.0, 0.0, 0.0);
+	gl.clear(gl.COLOR_BUFFER_BIT);
+
+	const positionBuffer = gl.createBuffer();
+	gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
+
+	const shaderProgram = initShaderProgram(gl, `
+		attribute vec2 vertex;
+
+		uniform vec2 u_scale;
+
+		varying vec2 v_pos;
+
+		void main() {
+			gl_Position = vec4(vertex, 0.0, 1.0);
+			v_pos = vertex / u_scale;
+		}
+	`, `
+		precision mediump float;
+
+		vec3 mod289(vec3 x) {
+			return x - floor(x * (1.0 / 289.0)) * 289.0;
+		}
+
+		vec2 mod289(vec2 x) {
+			return x - floor(x * (1.0 / 289.0)) * 289.0;
+		}
+
+		vec3 permute(vec3 x) {
+			return mod289(((x*34.0)+1.0)*x);
+		}
+
+		float snoise(vec2 v) {
+			const vec4 C = vec4(0.211324865405187,
+													0.366025403784439,
+												-0.577350269189626,
+													0.024390243902439);
+
+			vec2 i  = floor(v + dot(v, C.yy) );
+			vec2 x0 = v -   i + dot(i, C.xx);
+
+			vec2 i1;
+			i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
+			vec4 x12 = x0.xyxy + C.xxzz;
+			x12.xy -= i1;
+
+			i = mod289(i);
+			vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))
+				+ i.x + vec3(0.0, i1.x, 1.0 ));
+
+			vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);
+			m = m*m ;
+			m = m*m ;
+
+			vec3 x = 2.0 * fract(p * C.www) - 1.0;
+			vec3 h = abs(x) - 0.5;
+			vec3 ox = floor(x + 0.5);
+			vec3 a0 = x - ox;
+
+			m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );
+
+			vec3 g;
+			g.x  = a0.x  * x0.x  + h.x  * x0.y;
+			g.yz = a0.yz * x12.xz + h.yz * x12.yw;
+			return 130.0 * dot(m, g);
+		}
+
+		uniform float u_time;
+		uniform vec2 u_resolution;
+		uniform float u_spread;
+		uniform float u_speed;
+		uniform float u_warp;
+		uniform float u_focus;
+		uniform float u_itensity;
+
+		varying vec2 v_pos;
+
+		float circle( in vec2 _pos, in vec2 _origin, in float _radius ) {
+			float SPREAD = 0.7 * u_spread;
+			float SPEED = 0.00055 * u_speed;
+			float WARP = 1.5 * u_warp;
+			float FOCUS = 1.15 * u_focus;
+
+			vec2 dist = _pos - _origin;
+
+			float distortion = snoise( vec2(
+				_pos.x * 1.587 * WARP + u_time * SPEED * 0.5,
+				_pos.y * 1.192 * WARP + u_time * SPEED * 0.3
+			) ) * 0.5 + 0.5;
+
+			float feather = 0.01 + SPREAD * pow( distortion, FOCUS );
+
+			return 1.0 - smoothstep(
+				_radius - ( _radius * feather ),
+				_radius + ( _radius * feather ),
+				dot( dist, dist ) * 4.0
+			);
+		}
+
+		void main() {
+			vec3 green = vec3( 1.0 ) - vec3( 153.0 / 255.0, 211.0 / 255.0, 221.0 / 255.0 );
+			vec3 purple = vec3( 1.0 ) - vec3( 195.0 / 255.0, 165.0 / 255.0, 242.0 / 255.0 );
+			vec3 orange = vec3( 1.0 ) - vec3( 255.0 / 255.0, 156.0 / 255.0, 136.0 / 255.0 );
+
+			float ratio = u_resolution.x / u_resolution.y;
+
+			vec2 uv = vec2( v_pos.x, v_pos.y / ratio ) * 0.5 + 0.5;
+
+			vec3 color = vec3( 0.0 );
+
+			float greenMix = snoise( v_pos * 1.31 + u_time * 0.8 * 0.00017 ) * 0.5 + 0.5;
+			float purpleMix = snoise( v_pos * 1.26 + u_time * 0.8 * -0.0001 ) * 0.5 + 0.5;
+			float orangeMix = snoise( v_pos * 1.34 + u_time * 0.8 * 0.00015 ) * 0.5 + 0.5;
+
+			float alphaOne = 0.35 + 0.65 * pow( snoise( vec2( u_time * 0.00012, uv.x ) ) * 0.5 + 0.5, 1.2 );
+			float alphaTwo = 0.35 + 0.65 * pow( snoise( vec2( ( u_time + 1561.0 ) * 0.00014, uv.x ) ) * 0.5 + 0.5, 1.2 );
+			float alphaThree = 0.35 + 0.65 * pow( snoise( vec2( ( u_time + 3917.0 ) * 0.00013, uv.x ) ) * 0.5 + 0.5, 1.2 );
+
+			color += vec3( circle( uv, vec2( 0.22 + sin( u_time * 0.000201 ) * 0.06, 0.80 + cos( u_time * 0.000151 ) * 0.06 ), 0.15 ) ) * alphaOne * ( purple * purpleMix + orange * orangeMix );
+			color += vec3( circle( uv, vec2( 0.90 + cos( u_time * 0.000166 ) * 0.06, 0.42 + sin( u_time * 0.000138 ) * 0.06 ), 0.18 ) ) * alphaTwo * ( green * greenMix + purple * purpleMix );
+			color += vec3( circle( uv, vec2( 0.19 + sin( u_time * 0.000112 ) * 0.06, 0.25 + sin( u_time * 0.000192 ) * 0.06 ), 0.09 ) ) * alphaThree * ( orange * orangeMix );
+
+			color *= u_itensity + 1.0 * pow( snoise( vec2( v_pos.y + u_time * 0.00013, v_pos.x + u_time * -0.00009 ) ) * 0.5 + 0.5, 2.0 );
+
+			vec3 inverted = vec3( 1.0 ) - color;
+			gl_FragColor = vec4( color, max(max(color.x, color.y), color.z) );
+		}
+	`);
+
+	gl.useProgram(shaderProgram);
+	const u_resolution = gl.getUniformLocation(shaderProgram, 'u_resolution');
+	const u_time = gl.getUniformLocation(shaderProgram, 'u_time');
+	const u_spread = gl.getUniformLocation(shaderProgram, 'u_spread');
+	const u_speed = gl.getUniformLocation(shaderProgram, 'u_speed');
+	const u_warp = gl.getUniformLocation(shaderProgram, 'u_warp');
+	const u_focus = gl.getUniformLocation(shaderProgram, 'u_focus');
+	const u_itensity = gl.getUniformLocation(shaderProgram, 'u_itensity');
+	const u_scale = gl.getUniformLocation(shaderProgram, 'u_scale');
+	gl.uniform2fv(u_resolution, [canvas.width, canvas.height]);
+	gl.uniform1f(u_spread, 1.0);
+	gl.uniform1f(u_speed, 1.0);
+	gl.uniform1f(u_warp, 1.0);
+	gl.uniform1f(u_focus, props.focus);
+	gl.uniform1f(u_itensity, 0.5);
+	gl.uniform2fv(u_scale, [props.scale, props.scale]);
+
+	const vertex = gl.getAttribLocation(shaderProgram, 'vertex');
+	gl.enableVertexAttribArray(vertex);
+	gl.vertexAttribPointer(vertex, 2, gl.FLOAT, false, 0, 0);
+
+	const vertices = [1.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, -1.0];
+	gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.DYNAMIC_DRAW);
+
+	if (isChromatic()) {
+		gl!.uniform1f(u_time, 0);
+		gl!.drawArrays(gl!.TRIANGLE_STRIP, 0, 4);
+	} else {
+		function render(timeStamp) {
+			gl!.uniform1f(u_time, timeStamp);
+			gl!.drawArrays(gl!.TRIANGLE_STRIP, 0, 4);
+
+			handle = window.requestAnimationFrame(render);
+		}
+
+		handle = window.requestAnimationFrame(render);
+	}
+});
+
+onUnmounted(() => {
+	if (handle) {
+		window.cancelAnimationFrame(handle);
+	}
+});
+</script>
+
+<style lang="scss" module>
+</style>
diff --git a/packages/frontend/src/components/MkAsUi.vue b/packages/frontend/src/components/MkAsUi.vue
index 6ade5316c..8bfcfa6aa 100644
--- a/packages/frontend/src/components/MkAsUi.vue
+++ b/packages/frontend/src/components/MkAsUi.vue
@@ -11,29 +11,29 @@
 	<div v-else-if="c.type === 'buttons'" class="_buttons" :style="{ justifyContent: align }">
 		<MkButton v-for="button in c.buttons" :primary="button.primary" :rounded="button.rounded" :disabled="button.disabled" inline :small="size === 'small'" @click="button.onClick">{{ button.text }}</MkButton>
 	</div>
-	<MkSwitch v-else-if="c.type === 'switch'" :model-value="valueForSwitch" @update:model-value="onSwitchUpdate">
+	<MkSwitch v-else-if="c.type === 'switch'" :modelValue="valueForSwitch" @update:modelValue="onSwitchUpdate">
 		<template v-if="c.label" #label>{{ c.label }}</template>
 		<template v-if="c.caption" #caption>{{ c.caption }}</template>
 	</MkSwitch>
-	<MkTextarea v-else-if="c.type === 'textarea'" :model-value="c.default" @update:model-value="c.onInput">
+	<MkTextarea v-else-if="c.type === 'textarea'" :modelValue="c.default" @update:modelValue="c.onInput">
 		<template v-if="c.label" #label>{{ c.label }}</template>
 		<template v-if="c.caption" #caption>{{ c.caption }}</template>
 	</MkTextarea>
-	<MkInput v-else-if="c.type === 'textInput'" :small="size === 'small'" :model-value="c.default" @update:model-value="c.onInput">
+	<MkInput v-else-if="c.type === 'textInput'" :small="size === 'small'" :modelValue="c.default" @update:modelValue="c.onInput">
 		<template v-if="c.label" #label>{{ c.label }}</template>
 		<template v-if="c.caption" #caption>{{ c.caption }}</template>
 	</MkInput>
-	<MkInput v-else-if="c.type === 'numberInput'" :small="size === 'small'" :model-value="c.default" type="number" @update:model-value="c.onInput">
+	<MkInput v-else-if="c.type === 'numberInput'" :small="size === 'small'" :modelValue="c.default" type="number" @update:modelValue="c.onInput">
 		<template v-if="c.label" #label>{{ c.label }}</template>
 		<template v-if="c.caption" #caption>{{ c.caption }}</template>
 	</MkInput>
-	<MkSelect v-else-if="c.type === 'select'" :small="size === 'small'" :model-value="c.default" @update:model-value="c.onChange">
+	<MkSelect v-else-if="c.type === 'select'" :small="size === 'small'" :modelValue="c.default" @update:modelValue="c.onChange">
 		<template v-if="c.label" #label>{{ c.label }}</template>
 		<template v-if="c.caption" #caption>{{ c.caption }}</template>
 		<option v-for="item in c.items" :key="item.value" :value="item.value">{{ item.text }}</option>
 	</MkSelect>
 	<MkButton v-else-if="c.type === 'postFormButton'" :primary="c.primary" :rounded="c.rounded" :small="size === 'small'" inline @click="openPostForm">{{ c.text }}</MkButton>
-	<MkFolder v-else-if="c.type === 'folder'" :default-open="c.opened">
+	<MkFolder v-else-if="c.type === 'folder'" :defaultOpen="c.opened">
 		<template #label>{{ c.title }}</template>
 		<template v-for="child in c.children" :key="child">
 			<MkAsUi v-if="!g(child).hidden" :component="g(child)" :components="props.components" :size="size"/>
diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue
index 663c57623..fd892d817 100644
--- a/packages/frontend/src/components/MkAutocomplete.vue
+++ b/packages/frontend/src/components/MkAutocomplete.vue
@@ -10,7 +10,7 @@
 		</li>
 		<li tabindex="-1" :class="$style.item" @click="chooseUser()" @keydown="onKeydown">{{ i18n.ts.selectUser }}</li>
 	</ol>
-	<ol v-else-if="hashtags.length > 0" ref="suggests" :class="[$style.list, $style.hashtags]">
+	<ol v-else-if="hashtags.length > 0" ref="suggests" :class="$style.list">
 		<li v-for="hashtag in hashtags" tabindex="-1" :class="$style.item" @click="complete(type, hashtag)" @keydown="onKeydown">
 			<span class="name">{{ hashtag }}</span>
 		</li>
@@ -42,7 +42,7 @@ import { acct } from '@/filters/user';
 import * as os from '@/os';
 import { MFM_TAGS } from '@/scripts/mfm-tags';
 import { defaultStore } from '@/store';
-import { emojilist } from '@/scripts/emojilist';
+import { emojilist, getEmojiName } from '@/scripts/emojilist';
 import { i18n } from '@/i18n';
 import { miLocalStorage } from '@/local-storage';
 import { customEmojis } from '@/custom-emojis';
@@ -71,14 +71,14 @@ const emojiDb = computed(() => {
 		url: char2path(x.char),
 	}));
 
-	for (const x of lib) {
-		if (x.keywords) {
-			for (const k of x.keywords) {
+	for (const index of Object.values(defaultStore.state.additionalUnicodeEmojiIndexes)) {
+		for (const [emoji, keywords] of Object.entries(index)) {
+			for (const k of keywords) {
 				unicodeEmojiDB.push({
-					emoji: x.char,
+					emoji: emoji,
 					name: k,
-					aliasOf: x.name,
-					url: char2path(x.char),
+					aliasOf: getEmojiName(emoji)!,
+					url: char2path(emoji),
 				});
 			}
 		}
diff --git a/packages/frontend/src/components/MkAvatars.vue b/packages/frontend/src/components/MkAvatars.vue
index 995a72e51..630620fc0 100644
--- a/packages/frontend/src/components/MkAvatars.vue
+++ b/packages/frontend/src/components/MkAvatars.vue
@@ -1,7 +1,7 @@
 <template>
 <div>
 	<div v-for="user in users" :key="user.id" style="display:inline-block;width:32px;height:32px;margin-right:8px;">
-		<MkAvatar :user="user" style="width:32px;height:32px;" indicator link preview/>
+		<MkAvatar :user="user" style="width:32px; height:32px;" indicator link preview/>
 	</div>
 </div>
 </template>
diff --git a/packages/frontend/src/components/MkButton.vue b/packages/frontend/src/components/MkButton.vue
index 0ddee34f0..16e44ec61 100644
--- a/packages/frontend/src/components/MkButton.vue
+++ b/packages/frontend/src/components/MkButton.vue
@@ -2,23 +2,23 @@
 <button
 	v-if="!link"
 	ref="el" class="_button"
-	:class="[$style.root, { [$style.inline]: inline, [$style.primary]: primary, [$style.gradate]: gradate, [$style.danger]: danger, [$style.rounded]: rounded, [$style.full]: full, [$style.small]: small, [$style.large]: large, [$style.asLike]: asLike }]"
+	:class="[$style.root, { [$style.inline]: inline, [$style.primary]: primary, [$style.gradate]: gradate, [$style.danger]: danger, [$style.rounded]: rounded, [$style.full]: full, [$style.small]: small, [$style.large]: large, [$style.transparent]: transparent, [$style.asLike]: asLike }]"
 	:type="type"
 	@click="emit('click', $event)"
 	@mousedown="onMousedown"
 >
-	<div ref="ripples" :class="$style.ripples"></div>
+	<div ref="ripples" :class="$style.ripples" :data-children-class="$style.ripple"></div>
 	<div :class="$style.content">
 		<slot></slot>
 	</div>
 </button>
 <MkA
 	v-else class="_button"
-	:class="[$style.root, { [$style.inline]: inline, [$style.primary]: primary, [$style.gradate]: gradate, [$style.danger]: danger, [$style.rounded]: rounded, [$style.full]: full, [$style.small]: small, [$style.large]: large, [$style.asLike]: asLike }]"
+	:class="[$style.root, { [$style.inline]: inline, [$style.primary]: primary, [$style.gradate]: gradate, [$style.danger]: danger, [$style.rounded]: rounded, [$style.full]: full, [$style.small]: small, [$style.large]: large, [$style.transparent]: transparent, [$style.asLike]: asLike }]"
 	:to="to"
 	@mousedown="onMousedown"
 >
-	<div ref="ripples" :class="$style.ripples"></div>
+	<div ref="ripples" :class="$style.ripples" :data-children-class="$style.ripple"></div>
 	<div :class="$style.content">
 		<slot></slot>
 	</div>
@@ -26,9 +26,7 @@
 </template>
 
 <script lang="ts" setup>
-import { nextTick, onMounted, useCssModule } from 'vue';
-
-const $style = useCssModule();
+import { nextTick, onMounted } from 'vue';
 
 const props = defineProps<{
 	type?: 'button' | 'submit' | 'reset';
@@ -44,6 +42,7 @@ const props = defineProps<{
 	full?: boolean;
 	small?: boolean;
 	large?: boolean;
+	transparent?: boolean;
 	asLike?: boolean;
 }>();
 
@@ -80,7 +79,7 @@ function onMousedown(evt: MouseEvent): void {
 	const rect = target.getBoundingClientRect();
 
 	const ripple = document.createElement('div');
-	ripple.classList.add($style.ripple);
+	ripple.classList.add(ripples!.dataset.childrenClass!);
 	ripple.style.top = (evt.clientY - rect.top - 1).toString() + 'px';
 	ripple.style.left = (evt.clientX - rect.left - 1).toString() + 'px';
 
@@ -194,6 +193,10 @@ function onMousedown(evt: MouseEvent): void {
 		}
 	}
 
+	&.transparent {
+		background: transparent;
+	}
+
 	&.gradate {
 		font-weight: bold;
 		color: var(--fgOnAccent) !important;
diff --git a/packages/frontend/src/components/MkChannelFollowButton.vue b/packages/frontend/src/components/MkChannelFollowButton.vue
index 9e275d617..7b7bef478 100644
--- a/packages/frontend/src/components/MkChannelFollowButton.vue
+++ b/packages/frontend/src/components/MkChannelFollowButton.vue
@@ -1,20 +1,20 @@
 <template>
 <button
-	class="hdcaacmi _button"
-	:class="{ wait, active: isFollowing, full }"
+	class="_button"
+	:class="[$style.root, { [$style.wait]: wait, [$style.active]: isFollowing, [$style.full]: full }]"
 	:disabled="wait"
 	@click="onClick"
 >
 	<template v-if="!wait">
 		<template v-if="isFollowing">
-			<span v-if="full">{{ i18n.ts.unfollow }}</span><i class="ti ti-minus"></i>
+			<span v-if="full" :class="$style.text">{{ i18n.ts.unfollow }}</span><i class="ti ti-minus"></i>
 		</template>
 		<template v-else>
-			<span v-if="full">{{ i18n.ts.follow }}</span><i class="ti ti-plus"></i>
+			<span v-if="full" :class="$style.text">{{ i18n.ts.follow }}</span><i class="ti ti-plus"></i>
 		</template>
 	</template>
 	<template v-else>
-		<span v-if="full">{{ i18n.ts.processing }}</span><MkLoading :em="true"/>
+		<span v-if="full" :class="$style.text">{{ i18n.ts.processing }}</span><MkLoading :em="true"/>
 	</template>
 </button>
 </template>
@@ -57,8 +57,8 @@ async function onClick() {
 }
 </script>
 
-<style lang="scss" scoped>
-.hdcaacmi {
+<style lang="scss" module>
+.root {
 	position: relative;
 	display: inline-block;
 	font-weight: bold;
@@ -103,7 +103,7 @@ async function onClick() {
 	}
 
 	&.active {
-		color: #fff;
+		color: var(--fgOnAccent);
 		background: var(--accent);
 
 		&:hover {
@@ -121,9 +121,9 @@ async function onClick() {
 		cursor: wait !important;
 		opacity: 0.7;
 	}
+}
 
-	> span {
-		margin-right: 6px;
-	}
+.text {
+	margin-right: 6px;
 }
 </style>
diff --git a/packages/frontend/src/components/MkChannelList.vue b/packages/frontend/src/components/MkChannelList.vue
index 408eab739..4050520eb 100644
--- a/packages/frontend/src/components/MkChannelList.vue
+++ b/packages/frontend/src/components/MkChannelList.vue
@@ -26,6 +26,3 @@ const props = withDefaults(defineProps<{
 	extractor: (item) => item,
 });
 </script>
-
-<style lang="scss" scoped>
-</style>
diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue
index 06d5b9949..00ff98774 100644
--- a/packages/frontend/src/components/MkChart.vue
+++ b/packages/frontend/src/components/MkChart.vue
@@ -1,8 +1,8 @@
 <template>
-<div class="cbbedffa">
+<div :class="$style.root">
 	<canvas ref="chartEl"></canvas>
 	<MkChartLegend ref="legendEl" style="margin-top: 8px;"/>
-	<div v-if="fetching" class="fetching">
+	<div v-if="fetching" :class="$style.fetching">
 		<MkLoading/>
 	</div>
 </div>
@@ -817,22 +817,22 @@ onMounted(() => {
 /* eslint-enable id-denylist */
 </script>
 
-<style lang="scss" scoped>
-.cbbedffa {
+<style lang="scss" module>
+.root {
 	position: relative;
+}
 
-	> .fetching {
-		position: absolute;
-		top: 0;
-		left: 0;
-		width: 100%;
-		height: 100%;
-		-webkit-backdrop-filter: var(--blur, blur(12px));
-		backdrop-filter: var(--blur, blur(12px));
-		display: flex;
-		justify-content: center;
-		align-items: center;
-		cursor: wait;
-	}
+.fetching {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	-webkit-backdrop-filter: var(--blur, blur(12px));
+	backdrop-filter: var(--blur, blur(12px));
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	cursor: wait;
 }
 </style>
diff --git a/packages/frontend/src/components/MkChartTooltip.vue b/packages/frontend/src/components/MkChartTooltip.vue
index 7cfe535ed..fe5b78754 100644
--- a/packages/frontend/src/components/MkChartTooltip.vue
+++ b/packages/frontend/src/components/MkChartTooltip.vue
@@ -1,5 +1,5 @@
 <template>
-<MkTooltip ref="tooltip" :showing="showing" :x="x" :y="y" :max-width="340" :direction="'top'" :inner-margin="16" @closed="emit('closed')">
+<MkTooltip ref="tooltip" :showing="showing" :x="x" :y="y" :maxWidth="340" :direction="'top'" :innerMargin="16" @closed="emit('closed')">
 	<div v-if="title || series">
 		<div v-if="title" :class="$style.title">{{ title }}</div>
 		<template v-if="series">
diff --git a/packages/frontend/src/components/MkClickerGame.vue b/packages/frontend/src/components/MkClickerGame.vue
index da6439fd2..a6ab5aded 100644
--- a/packages/frontend/src/components/MkClickerGame.vue
+++ b/packages/frontend/src/components/MkClickerGame.vue
@@ -3,7 +3,7 @@
 	<div v-if="game.ready" :class="$style.game">
 		<div :class="$style.cps" class="">{{ number(cps) }}cps</div>
 		<div :class="$style.count" class=""><i class="ti ti-cookie" style="font-size: 70%;"></i> {{ number(cookies) }}</div>
-		<button v-click-anime class="_button" :class="$style.button" @click="onClick">
+		<button v-click-anime class="_button" @click="onClick">
 			<img src="/client-assets/cookie.png" :class="$style.img">
 		</button>
 	</div>
@@ -84,10 +84,6 @@ onUnmounted(() => {
 	margin-bottom: 6px;
 }
 
-.button {
-
-}
-
 .img {
 	max-width: 90px;
 }
diff --git a/packages/frontend/src/components/MkContainer.vue b/packages/frontend/src/components/MkContainer.vue
index d03331a6e..af1c57b34 100644
--- a/packages/frontend/src/components/MkContainer.vue
+++ b/packages/frontend/src/components/MkContainer.vue
@@ -1,12 +1,12 @@
 <template>
-<div ref="rootEl" class="_panel" :class="[$style.root, { [$style.naked]: naked, [$style.thin]: thin, [$style.hideHeader]: !showHeader, [$style.scrollable]: scrollable, [$style.closed]: !showBody }]">
+<div ref="rootEl" class="_panel" :class="[$style.root, { [$style.naked]: naked, [$style.thin]: thin, [$style.scrollable]: scrollable }]">
 	<header v-if="showHeader" ref="headerEl" :class="$style.header">
 		<div :class="$style.title">
 			<span :class="$style.titleIcon"><slot name="icon"></slot></span>
 			<slot name="header"></slot>
 		</div>
 		<div :class="$style.headerSub">
-			<slot name="func" :button-style-class="$style.headerButton"></slot>
+			<slot name="func" :buttonStyleClass="$style.headerButton"></slot>
 			<button v-if="foldable" :class="$style.headerButton" class="_button" @click="() => showBody = !showBody">
 				<template v-if="showBody"><i class="ti ti-chevron-up"></i></template>
 				<template v-else><i class="ti ti-chevron-down"></i></template>
@@ -14,14 +14,14 @@
 		</div>
 	</header>
 	<Transition
-		:enter-active-class="defaultStore.state.animation ? $style.transition_toggle_enterActive : ''"
-		:leave-active-class="defaultStore.state.animation ? $style.transition_toggle_leaveActive : ''"
-		:enter-from-class="defaultStore.state.animation ? $style.transition_toggle_enterFrom : ''"
-		:leave-to-class="defaultStore.state.animation ? $style.transition_toggle_leaveTo : ''"
+		:enterActiveClass="defaultStore.state.animation ? $style.transition_toggle_enterActive : ''"
+		:leaveActiveClass="defaultStore.state.animation ? $style.transition_toggle_leaveActive : ''"
+		:enterFromClass="defaultStore.state.animation ? $style.transition_toggle_enterFrom : ''"
+		:leaveToClass="defaultStore.state.animation ? $style.transition_toggle_leaveTo : ''"
 		@enter="enter"
-		@after-enter="afterEnter"
+		@afterEnter="afterEnter"
 		@leave="leave"
-		@after-leave="afterLeave"
+		@afterLeave="afterLeave"
 	>
 		<div v-show="showBody" ref="contentEl" :class="[$style.content, { [$style.omitted]: omitted }]">
 			<slot></slot>
@@ -34,7 +34,7 @@
 </template>
 
 <script lang="ts" setup>
-import { onMounted, ref, shallowRef, watch } from 'vue';
+import { onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
 import { defaultStore } from '@/store';
 import { i18n } from '@/i18n';
 
@@ -83,13 +83,19 @@ function afterLeave(el) {
 
 const calcOmit = () => {
 	if (omitted.value || ignoreOmit.value || props.maxHeight == null) return;
+	if (!contentEl.value) return;
 	const height = contentEl.value.offsetHeight;
 	omitted.value = height > props.maxHeight;
 };
 
+const omitObserver = new ResizeObserver((entries, observer) => {
+	calcOmit();
+});
+
 onMounted(() => {
 	watch(showBody, v => {
-		const headerHeight = props.showHeader ? headerEl.value.offsetHeight : 0;
+		if (!rootEl.value) return;
+		const headerHeight = props.showHeader ? headerEl.value?.offsetHeight ?? 0 : 0;
 		rootEl.value.style.minHeight = `${headerHeight}px`;
 		if (v) {
 			rootEl.value.style.flexBasis = 'auto';
@@ -100,13 +106,15 @@ onMounted(() => {
 		immediate: true,
 	});
 
-	rootEl.value.style.setProperty('--maxHeight', props.maxHeight + 'px');
+	if (rootEl.value) rootEl.value.style.setProperty('--maxHeight', props.maxHeight + 'px');
 
 	calcOmit();
 
-	new ResizeObserver((entries, observer) => {
-		calcOmit();
-	}).observe(contentEl.value);
+	if (contentEl.value) omitObserver.observe(contentEl.value);
+});
+
+onUnmounted(() => {
+	omitObserver.disconnect();
 });
 </script>
 
diff --git a/packages/frontend/src/components/MkContextMenu.vue b/packages/frontend/src/components/MkContextMenu.vue
index b81c806b0..fb11834f4 100644
--- a/packages/frontend/src/components/MkContextMenu.vue
+++ b/packages/frontend/src/components/MkContextMenu.vue
@@ -1,10 +1,10 @@
 <template>
 <Transition
 	appear
-	:enter-active-class="defaultStore.state.animation ? $style.transition_fade_enterActive : ''"
-	:leave-active-class="defaultStore.state.animation ? $style.transition_fade_leaveActive : ''"
-	:enter-from-class="defaultStore.state.animation ? $style.transition_fade_enterFrom : ''"
-	:leave-to-class="defaultStore.state.animation ? $style.transition_fade_leaveTo : ''"
+	:enterActiveClass="defaultStore.state.animation ? $style.transition_fade_enterActive : ''"
+	:leaveActiveClass="defaultStore.state.animation ? $style.transition_fade_leaveActive : ''"
+	:enterFromClass="defaultStore.state.animation ? $style.transition_fade_enterFrom : ''"
+	:leaveToClass="defaultStore.state.animation ? $style.transition_fade_leaveTo : ''"
 >
 	<div ref="rootEl" :class="$style.root" :style="{ zIndex }" @contextmenu.prevent.stop="() => {}">
 		<MkMenu :items="items" :align="'left'" @close="$emit('closed')"/>
diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue
index 043a614e4..82363499b 100644
--- a/packages/frontend/src/components/MkCropperDialog.vue
+++ b/packages/frontend/src/components/MkCropperDialog.vue
@@ -4,7 +4,7 @@
 	:width="800"
 	:height="500"
 	:scroll="false"
-	:with-ok-button="true"
+	:withOkButton="true"
 	@close="cancel()"
 	@ok="ok()"
 	@closed="$emit('closed')"
diff --git a/packages/frontend/src/components/MkDateSeparatedList.vue b/packages/frontend/src/components/MkDateSeparatedList.vue
index d6303f967..6942a0e6c 100644
--- a/packages/frontend/src/components/MkDateSeparatedList.vue
+++ b/packages/frontend/src/components/MkDateSeparatedList.vue
@@ -36,7 +36,7 @@ export default defineComponent({
 	},
 
 	setup(props, { slots, expose }) {
-		const $style = useCssModule();
+		const $style = useCssModule(); // カスタムレンダラなので使っても大丈夫
 		function getDateText(time: string) {
 			const date = new Date(time).getDate();
 			const month = new Date(time).getMonth() + 1;
diff --git a/packages/frontend/src/components/MkDialog.vue b/packages/frontend/src/components/MkDialog.vue
index 9f5404ce1..4d5df0bba 100644
--- a/packages/frontend/src/components/MkDialog.vue
+++ b/packages/frontend/src/components/MkDialog.vue
@@ -1,10 +1,18 @@
 <template>
-<MkModal ref="modal" :prefer-type="'dialog'" :z-priority="'high'" @click="done(true)" @closed="emit('closed')">
+<MkModal ref="modal" :preferType="'dialog'" :zPriority="'high'" @click="done(true)" @closed="emit('closed')">
 	<div :class="$style.root">
 		<div v-if="icon" :class="$style.icon">
 			<i :class="icon"></i>
 		</div>
-		<div v-else-if="!input && !select" :class="[$style.icon, $style['type_' + type]]">
+		<div
+			v-else-if="!input && !select"
+			:class="[$style.icon, {
+				[$style.type_success]: type === 'success',
+				[$style.type_error]: type === 'error',
+				[$style.type_warning]: type === 'warning',
+				[$style.type_info]: type === 'info',
+			}]"
+		>
 			<i v-if="type === 'success'" :class="$style.iconInner" class="ti ti-check"></i>
 			<i v-else-if="type === 'error'" :class="$style.iconInner" class="ti ti-circle-x"></i>
 			<i v-else-if="type === 'warning'" :class="$style.iconInner" class="ti ti-alert-triangle"></i>
diff --git a/packages/frontend/src/components/MkDigitalClock.stories.impl.ts b/packages/frontend/src/components/MkDigitalClock.stories.impl.ts
new file mode 100644
index 000000000..344f6de47
--- /dev/null
+++ b/packages/frontend/src/components/MkDigitalClock.stories.impl.ts
@@ -0,0 +1,32 @@
+/* eslint-disable @typescript-eslint/explicit-function-return-type */
+import { StoryObj } from '@storybook/vue3';
+import isChromatic from 'chromatic/isChromatic';
+import MkDigitalClock from './MkDigitalClock.vue';
+export const Default = {
+	render(args) {
+		return {
+			components: {
+				MkDigitalClock,
+			},
+			setup() {
+				return {
+					args,
+				};
+			},
+			computed: {
+				props() {
+					return {
+						...this.args,
+					};
+				},
+			},
+			template: '<MkDigitalClock v-bind="props" />',
+		};
+	},
+	args: {
+		now: isChromatic() ? () => new Date('2023-01-01T10:10:30') : undefined,
+	},
+	parameters: {
+		layout: 'centered',
+	},
+} satisfies StoryObj<typeof MkDigitalClock>;
diff --git a/packages/frontend/src/components/MkDigitalClock.vue b/packages/frontend/src/components/MkDigitalClock.vue
index 278dc8a5e..aea20f248 100644
--- a/packages/frontend/src/components/MkDigitalClock.vue
+++ b/packages/frontend/src/components/MkDigitalClock.vue
@@ -11,19 +11,21 @@
 </template>
 
 <script lang="ts" setup>
-import { onUnmounted, ref, watch } from 'vue';
+import { onMounted, onUnmounted, ref, watch } from 'vue';
+import { defaultIdlingRenderScheduler } from '@/scripts/idle-render.js';
 
 const props = withDefaults(defineProps<{
 	showS?: boolean;
 	showMs?: boolean;
 	offset?: number;
+	now?: () => Date;
 }>(), {
 	showS: true,
 	showMs: false,
 	offset: 0 - new Date().getTimezoneOffset(),
+	now: () => new Date(),
 });
 
-let intervalId;
 const hh = ref('');
 const mm = ref('');
 const ss = ref('');
@@ -39,9 +41,9 @@ watch(showColon, (v) => {
 	}
 });
 
-const tick = () => {
-	const now = new Date();
-	now.setMinutes(now.getMinutes() + (new Date().getTimezoneOffset() + props.offset));
+const tick = (): void => {
+	const now = props.now();
+	now.setMinutes(now.getMinutes() + now.getTimezoneOffset() + props.offset);
 	hh.value = now.getHours().toString().padStart(2, '0');
 	mm.value = now.getMinutes().toString().padStart(2, '0');
 	ss.value = now.getSeconds().toString().padStart(2, '0');
@@ -52,13 +54,12 @@ const tick = () => {
 
 tick();
 
-watch(() => props.showMs, () => {
-	if (intervalId) window.clearInterval(intervalId);
-	intervalId = window.setInterval(tick, props.showMs ? 10 : 1000);
-}, { immediate: true });
+onMounted(() => {
+	defaultIdlingRenderScheduler.add(tick);
+});
 
 onUnmounted(() => {
-	window.clearInterval(intervalId);
+	defaultIdlingRenderScheduler.delete(tick);
 });
 </script>
 
diff --git a/packages/frontend/src/components/MkDrive.file.vue b/packages/frontend/src/components/MkDrive.file.vue
index ab408b500..f0641161b 100644
--- a/packages/frontend/src/components/MkDrive.file.vue
+++ b/packages/frontend/src/components/MkDrive.file.vue
@@ -1,7 +1,6 @@
 <template>
 <div
-	class="ncvczrfv"
-	:class="{ isSelected }"
+	:class="[$style.root, { [$style.isSelected]: isSelected }]"
 	draggable="true"
 	:title="title"
 	@click="onClick"
@@ -9,25 +8,27 @@
 	@dragstart="onDragstart"
 	@dragend="onDragend"
 >
-	<div v-if="$i?.avatarId == file.id" class="label">
-		<img src="/client-assets/label.svg"/>
-		<p>{{ i18n.ts.avatar }}</p>
-	</div>
-	<div v-if="$i?.bannerId == file.id" class="label">
-		<img src="/client-assets/label.svg"/>
-		<p>{{ i18n.ts.banner }}</p>
-	</div>
-	<div v-if="file.isSensitive" class="label red">
-		<img src="/client-assets/label-red.svg"/>
-		<p>{{ i18n.ts.nsfw }}</p>
-	</div>
+	<div style="pointer-events: none;">
+		<div v-if="$i?.avatarId == file.id" :class="[$style.label]">
+			<img :class="$style.labelImg" src="/client-assets/label.svg"/>
+			<p :class="$style.labelText">{{ i18n.ts.avatar }}</p>
+		</div>
+		<div v-if="$i?.bannerId == file.id" :class="[$style.label]">
+			<img :class="$style.labelImg" src="/client-assets/label.svg"/>
+			<p :class="$style.labelText">{{ i18n.ts.banner }}</p>
+		</div>
+		<div v-if="file.isSensitive" :class="[$style.label, $style.red]">
+			<img :class="$style.labelImg" src="/client-assets/label-red.svg"/>
+			<p :class="$style.labelText">{{ i18n.ts.nsfw }}</p>
+		</div>
 
-	<MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/>
+		<MkDriveFileThumbnail :class="$style.thumbnail" :file="file" fit="contain"/>
 
-	<p class="name">
-		<span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span>
-		<span v-if="file.name.lastIndexOf('.') != -1" class="ext">{{ file.name.substr(file.name.lastIndexOf('.')) }}</span>
-	</p>
+		<p :class="$style.name">
+			<span>{{ file.name.lastIndexOf('.') != -1 ? file.name.substr(0, file.name.lastIndexOf('.')) : file.name }}</span>
+			<span v-if="file.name.lastIndexOf('.') != -1" style="opacity: 0.5;">{{ file.name.substr(file.name.lastIndexOf('.')) }}</span>
+		</p>
+	</div>
 </div>
 </template>
 
@@ -88,20 +89,13 @@ function onDragend() {
 }
 </script>
 
-<style lang="scss" scoped>
-.ncvczrfv {
+<style lang="scss" module>
+.root {
 	position: relative;
 	padding: 8px 0 0 0;
 	min-height: 180px;
 	border-radius: 8px;
-
-	&, * {
-		cursor: pointer;
-	}
-
-	> * {
-		pointer-events: none;
-	}
+	cursor: pointer;
 
 	&:hover {
 		background: rgba(#000, 0.05);
@@ -165,82 +159,78 @@ function onDragend() {
 			color: #fff;
 		}
 	}
+}
 
-	> .label {
+.label {
+	position: absolute;
+	top: 0;
+	left: 0;
+	pointer-events: none;
+
+	&:before,
+	&:after {
+		content: "";
+		display: block;
 		position: absolute;
-		top: 0;
-		left: 0;
-		pointer-events: none;
+		z-index: 1;
+		background: #0c7ac9;
+	}
 
+	&:before {
+		top: 0;
+		left: 57px;
+		width: 28px;
+		height: 8px;
+	}
+
+	&:after {
+		top: 57px;
+		left: 0;
+		width: 8px;
+		height: 28px;
+	}
+
+	&.red {
 		&:before,
 		&:after {
-			content: "";
-			display: block;
-			position: absolute;
-			z-index: 1;
-			background: #0c7ac9;
-		}
-
-		&:before {
-			top: 0;
-			left: 57px;
-			width: 28px;
-			height: 8px;
-		}
-
-		&:after {
-			top: 57px;
-			left: 0;
-			width: 8px;
-			height: 28px;
-		}
-
-		&.red {
-			&:before,
-			&:after {
-				background: #c12113;
-			}
-		}
-
-		> img {
-			position: absolute;
-			z-index: 2;
-			top: 0;
-			left: 0;
-		}
-
-		> p {
-			position: absolute;
-			z-index: 3;
-			top: 19px;
-			left: -28px;
-			width: 120px;
-			margin: 0;
-			text-align: center;
-			line-height: 28px;
-			color: #fff;
-			transform: rotate(-45deg);
-		}
-	}
-
-	> .thumbnail {
-		width: 110px;
-		height: 110px;
-		margin: auto;
-	}
-
-	> .name {
-		display: block;
-		margin: 4px 0 0 0;
-		font-size: 0.8em;
-		text-align: center;
-		word-break: break-all;
-		color: var(--fg);
-		overflow: hidden;
-
-		> .ext {
-			opacity: 0.5;
+			background: #c12113;
 		}
 	}
 }
+
+.labelImg {
+	position: absolute;
+	z-index: 2;
+	top: 0;
+	left: 0;
+}
+
+.labelText {
+	position: absolute;
+	z-index: 3;
+	top: 19px;
+	left: -28px;
+	width: 120px;
+	margin: 0;
+	text-align: center;
+	line-height: 28px;
+	color: #fff;
+	transform: rotate(-45deg);
+}
+
+.thumbnail {
+	width: 110px;
+	height: 110px;
+	margin: auto;
+}
+
+.name {
+	display: block;
+	margin: 4px 0 0 0;
+	font-size: 0.8em;
+	text-align: center;
+	word-break: break-all;
+	color: var(--fg);
+	overflow: hidden;
+}
 </style>
diff --git a/packages/frontend/src/components/MkDrive.folder.vue b/packages/frontend/src/components/MkDrive.folder.vue
index 156013b9a..196934240 100644
--- a/packages/frontend/src/components/MkDrive.folder.vue
+++ b/packages/frontend/src/components/MkDrive.folder.vue
@@ -1,7 +1,6 @@
 <template>
 <div
-	class="rghtznwe"
-	:class="{ draghover }"
+	:class="[$style.root, { [$style.draghover]: draghover }]"
 	draggable="true"
 	:title="title"
 	@click="onClick"
@@ -15,15 +14,15 @@
 	@dragstart="onDragstart"
 	@dragend="onDragend"
 >
-	<p class="name">
-		<template v-if="hover"><i class="ti ti-folder ti-fw"></i></template>
-		<template v-if="!hover"><i class="ti ti-folder ti-fw"></i></template>
+	<p :class="$style.name">
+		<template v-if="hover"><i :class="$style.icon" class="ti ti-folder ti-fw"></i></template>
+		<template v-if="!hover"><i :class="$style.icon" class="ti ti-folder ti-fw"></i></template>
 		{{ folder.name }}
 	</p>
-	<p v-if="defaultStore.state.uploadFolder == folder.id" class="upload">
+	<p v-if="defaultStore.state.uploadFolder == folder.id" :class="$style.upload">
 		{{ i18n.ts.uploadFolder }}
 	</p>
-	<button v-if="selectMode" class="checkbox _button" :class="{ checked: isSelected }" @click.prevent.stop="checkboxClicked"></button>
+	<button v-if="selectMode" class="_button" :class="[$style.checkbox, { [$style.checked]: isSelected }]" @click.prevent.stop="checkboxClicked"></button>
 </div>
 </template>
 
@@ -267,35 +266,14 @@ function onContextmenu(ev: MouseEvent) {
 }
 </script>
 
-<style lang="scss" scoped>
-.rghtznwe {
+<style lang="scss" module>
+.root {
 	position: relative;
 	padding: 8px;
 	height: 64px;
 	background: var(--driveFolderBg);
 	border-radius: 4px;
-
-	&, * {
-		cursor: pointer;
-	}
-
-	*:not(.checkbox) {
-		pointer-events: none;
-	}
-
-	> .checkbox {
-		position: absolute;
-		bottom: 8px;
-		right: 8px;
-		width: 16px;
-		height: 16px;
-		background: #fff;
-		border: solid 1px #000;
-
-		&.checked {
-			background: var(--accent);
-		}
-	}
+	cursor: pointer;
 
 	&.draghover {
 		&:after {
@@ -310,24 +288,38 @@ function onContextmenu(ev: MouseEvent) {
 			border-radius: 4px;
 		}
 	}
+}
 
-	> .name {
-		margin: 0;
-		font-size: 0.9em;
-		color: var(--desktopDriveFolderFg);
+.checkbox {
+	position: absolute;
+	bottom: 8px;
+	right: 8px;
+	width: 16px;
+	height: 16px;
+	background: #fff;
+	border: solid 1px #000;
 
-		> i {
-			margin-right: 4px;
-			margin-left: 2px;
-			text-align: left;
-		}
-	}
-
-	> .upload {
-		margin: 4px 4px;
-		font-size: 0.8em;
-		text-align: right;
-		color: var(--desktopDriveFolderFg);
+	&.checked {
+		background: var(--accent);
 	}
 }
+
+.name {
+	margin: 0;
+	font-size: 0.9em;
+	color: var(--desktopDriveFolderFg);
+}
+
+.icon {
+	margin-right: 4px;
+	margin-left: 2px;
+	text-align: left;
+}
+
+.upload {
+	margin: 4px 4px;
+	font-size: 0.8em;
+	text-align: right;
+	color: var(--desktopDriveFolderFg);
+}
 </style>
diff --git a/packages/frontend/src/components/MkDrive.navFolder.vue b/packages/frontend/src/components/MkDrive.navFolder.vue
index dbbfef5f0..3349603d3 100644
--- a/packages/frontend/src/components/MkDrive.navFolder.vue
+++ b/packages/frontend/src/components/MkDrive.navFolder.vue
@@ -1,13 +1,13 @@
 <template>
-<div class="drylbebk"
-	:class="{ draghover }"
+<div
+	:class="[$style.root, { [$style.draghover]: draghover }]"
 	@click="onClick"
 	@dragover.prevent.stop="onDragover"
 	@dragenter="onDragenter"
 	@dragleave="onDragleave"
 	@drop.stop="onDrop"
 >
-	<i v-if="folder == null" class="ti ti-cloud"></i>
+	<i v-if="folder == null" class="ti ti-cloud" style="margin-right: 4px;"></i>
 	<span>{{ folder == null ? i18n.ts.drive : folder.name }}</span>
 </div>
 </template>
@@ -130,18 +130,10 @@ function onDrop(ev: DragEvent) {
 }
 </script>
 
-<style lang="scss" scoped>
-.drylbebk {
-	> * {
-		pointer-events: none;
-	}
-
+<style lang="scss" module>
+.root {
 	&.draghover {
 		background: #eee;
 	}
-
-	> i {
-		margin-right: 4px;
-	}
 }
 </style>
diff --git a/packages/frontend/src/components/MkDrive.vue b/packages/frontend/src/components/MkDrive.vue
index bfec57d6a..52aef450d 100644
--- a/packages/frontend/src/components/MkDrive.vue
+++ b/packages/frontend/src/components/MkDrive.vue
@@ -1,89 +1,90 @@
 <template>
-<div class="yfudmmck">
-	<nav>
-		<div class="path" @contextmenu.prevent.stop="() => {}">
+<div :class="$style.root">
+	<nav :class="$style.nav">
+		<div :class="$style.navPath" @contextmenu.prevent.stop="() => {}">
 			<XNavFolder
-				:class="{ current: folder == null }"
-				:parent-folder="folder"
+				:class="[$style.navPathItem, { [$style.navCurrent]: folder == null }]"
+				:parentFolder="folder"
 				@move="move"
 				@upload="upload"
-				@remove-file="removeFile"
-				@remove-folder="removeFolder"
+				@removeFile="removeFile"
+				@removeFolder="removeFolder"
 			/>
 			<template v-for="f in hierarchyFolders">
-				<span class="separator"><i class="ti ti-chevron-right"></i></span>
+				<span :class="[$style.navPathItem, $style.navSeparator]"><i class="ti ti-chevron-right"></i></span>
 				<XNavFolder
 					:folder="f"
-					:parent-folder="folder"
+					:parentFolder="folder"
+					:class="[$style.navPathItem]"
 					@move="move"
 					@upload="upload"
-					@remove-file="removeFile"
-					@remove-folder="removeFolder"
+					@removeFile="removeFile"
+					@removeFolder="removeFolder"
 				/>
 			</template>
-			<span v-if="folder != null" class="separator"><i class="ti ti-chevron-right"></i></span>
-			<span v-if="folder != null" class="folder current">{{ folder.name }}</span>
+			<span v-if="folder != null" :class="[$style.navPathItem, $style.navSeparator]"><i class="ti ti-chevron-right"></i></span>
+			<span v-if="folder != null" :class="[$style.navPathItem, $style.navCurrent]">{{ folder.name }}</span>
 		</div>
-		<button class="menu _button" @click="showMenu"><i class="ti ti-dots"></i></button>
+		<button class="_button" :class="$style.navMenu" @click="showMenu"><i class="ti ti-dots"></i></button>
 	</nav>
 	<div
-		ref="main" class="main"
-		:class="{ uploading: uploadings.length > 0, fetching }"
+		ref="main"
+		:class="[$style.main, { [$style.uploading]: uploadings.length > 0, [$style.fetching]: fetching }]"
 		@dragover.prevent.stop="onDragover"
 		@dragenter="onDragenter"
 		@dragleave="onDragleave"
 		@drop.prevent.stop="onDrop"
 		@contextmenu.stop="onContextmenu"
 	>
-		<div ref="contents" class="contents">
-			<div v-show="folders.length > 0" ref="foldersContainer" class="folders">
+		<div ref="contents">
+			<div v-show="folders.length > 0" ref="foldersContainer" :class="$style.folders">
 				<XFolder
 					v-for="(f, i) in folders"
 					:key="f.id"
 					v-anim="i"
-					class="folder"
+					:class="$style.folder"
 					:folder="f"
-					:select-mode="select === 'folder'"
-					:is-selected="selectedFolders.some(x => x.id === f.id)"
+					:selectMode="select === 'folder'"
+					:isSelected="selectedFolders.some(x => x.id === f.id)"
 					@chosen="chooseFolder"
 					@move="move"
 					@upload="upload"
-					@remove-file="removeFile"
-					@remove-folder="removeFolder"
+					@removeFile="removeFile"
+					@removeFolder="removeFolder"
 					@dragstart="isDragSource = true"
 					@dragend="isDragSource = false"
 				/>
 				<!-- SEE: https://stackoverflow.com/questions/18744164/flex-box-align-last-row-to-grid -->
-				<div v-for="(n, i) in 16" :key="i" class="padding"></div>
+				<div v-for="(n, i) in 16" :key="i" :class="$style.padding"></div>
 				<MkButton v-if="moreFolders" ref="moreFolders">{{ i18n.ts.loadMore }}</MkButton>
 			</div>
-			<div v-show="files.length > 0" ref="filesContainer" class="files">
+			<div v-show="files.length > 0" ref="filesContainer" :class="$style.files">
 				<XFile
 					v-for="(file, i) in files"
 					:key="file.id"
 					v-anim="i"
-					class="file"
+					:class="$style.file"
 					:file="file"
-					:select-mode="select === 'file'"
-					:is-selected="selectedFiles.some(x => x.id === file.id)"
+					:selectMode="select === 'file'"
+					:isSelected="selectedFiles.some(x => x.id === file.id)"
 					@chosen="chooseFile"
 					@dragstart="isDragSource = true"
 					@dragend="isDragSource = false"
 				/>
 				<!-- SEE: https://stackoverflow.com/questions/18744164/flex-box-align-last-row-to-grid -->
-				<div v-for="(n, i) in 16" :key="i" class="padding"></div>
+				<div v-for="(n, i) in 16" :key="i" :class="$style.padding"></div>
 				<MkButton v-show="moreFiles" ref="loadMoreFiles" @click="fetchMoreFiles">{{ i18n.ts.loadMore }}</MkButton>
 			</div>
-			<div v-if="files.length == 0 && folders.length == 0 && !fetching" class="empty">
-				<p v-if="draghover">{{ i18n.t('empty-draghover') }}</p>
-				<p v-if="!draghover && folder == null"><strong>{{ i18n.ts.emptyDrive }}</strong><br/>{{ i18n.t('empty-drive-description') }}</p>
-				<p v-if="!draghover && folder != null">{{ i18n.ts.emptyFolder }}</p>
+			<div v-if="files.length == 0 && folders.length == 0 && !fetching" :class="$style.empty">
+				<div v-if="draghover">{{ i18n.t('empty-draghover') }}</div>
+				<div v-if="!draghover && folder == null"><strong>{{ i18n.ts.emptyDrive }}</strong><br/>{{ i18n.t('empty-drive-description') }}</div>
+				<div v-if="!draghover && folder != null">{{ i18n.ts.emptyFolder }}</div>
 			</div>
 		</div>
 		<MkLoading v-if="fetching"/>
 	</div>
-	<div v-if="draghover" class="dropzone"></div>
-	<input ref="fileInput" type="file" accept="*/*" multiple tabindex="-1" @change="onChangeFileInput"/>
+	<div v-if="draghover" :class="$style.dropzone"></div>
+	<input ref="fileInput" style="display: none;" type="file" accept="*/*" multiple tabindex="-1" @change="onChangeFileInput"/>
 </div>
 </template>
 
@@ -95,7 +96,7 @@ import XNavFolder from '@/components/MkDrive.navFolder.vue';
 import XFolder from '@/components/MkDrive.folder.vue';
 import XFile from '@/components/MkDrive.file.vue';
 import * as os from '@/os';
-import { stream } from '@/stream';
+import { useStream } from '@/stream';
 import { defaultStore } from '@/store';
 import { i18n } from '@/i18n';
 import { uploadFile, uploads } from '@/scripts/upload';
@@ -131,7 +132,7 @@ const hierarchyFolders = ref<Misskey.entities.DriveFolder[]>([]);
 const selectedFiles = ref<Misskey.entities.DriveFile[]>([]);
 const selectedFolders = ref<Misskey.entities.DriveFolder[]>([]);
 const uploadings = uploads;
-const connection = stream.useChannel('drive');
+const connection = useStream().useChannel('drive');
 const keepOriginal = ref<boolean>(defaultStore.state.keepOriginalUploading); // 外部渡しが多いので$refは使わないほうがよい
 
 // ドロップされようとしているか
@@ -658,147 +659,116 @@ onBeforeUnmount(() => {
 });
 </script>
 
-<style lang="scss" scoped>
-.yfudmmck {
+<style lang="scss" module>
+.root {
 	display: flex;
 	flex-direction: column;
 	height: 100%;
+}
 
-	> nav {
-		display: flex;
-		z-index: 2;
-		width: 100%;
-		padding: 0 8px;
-		box-sizing: border-box;
-		overflow: auto;
-		font-size: 0.9em;
-		box-shadow: 0 1px 0 var(--divider);
+.nav {
+	display: flex;
+	z-index: 2;
+	width: 100%;
+	padding: 0 8px;
+	box-sizing: border-box;
+	overflow: auto;
+	font-size: 0.9em;
+	box-shadow: 0 1px 0 var(--divider);
+	user-select: none;
+}
 
-		&, * {
-			user-select: none;
-		}
+.navPath {
+	display: inline-block;
+	vertical-align: bottom;
+	line-height: 42px;
+	white-space: nowrap;
+}
 
-		> .path {
-			display: inline-block;
-			vertical-align: bottom;
-			line-height: 42px;
-			white-space: nowrap;
+.navPathItem {
+	display: inline-block;
+	margin: 0;
+	padding: 0 8px;
+	line-height: 42px;
+	cursor: pointer;
 
-			> * {
-				display: inline-block;
-				margin: 0;
-				padding: 0 8px;
-				line-height: 42px;
-				cursor: pointer;
+	&:hover {
+		text-decoration: underline;
+	}
 
-				* {
-					pointer-events: none;
-				}
+	&.navCurrent {
+		font-weight: bold;
+		cursor: default;
 
-				&:hover {
-					text-decoration: underline;
-				}
-
-				&.current {
-					font-weight: bold;
-					cursor: default;
-
-					&:hover {
-						text-decoration: none;
-					}
-				}
-
-				&.separator {
-					margin: 0;
-					padding: 0;
-					opacity: 0.5;
-					cursor: default;
-
-					> i {
-						margin: 0;
-					}
-				}
-			}
-		}
-
-		> .menu {
-			margin-left: auto;
-			padding: 0 12px;
+		&:hover {
+			text-decoration: none;
 		}
 	}
 
-	> .main {
-		flex: 1;
-		overflow: auto;
-		padding: var(--margin);
-
-		&, * {
-			user-select: none;
-		}
-
-		&.fetching {
-			cursor: wait !important;
-
-			* {
-				pointer-events: none;
-			}
-
-			> .contents {
-				opacity: 0.5;
-			}
-		}
-
-		&.uploading {
-			height: calc(100% - 38px - 100px);
-		}
-
-		> .contents {
-
-			> .folders,
-			> .files {
-				display: flex;
-				flex-wrap: wrap;
-
-				> .folder,
-				> .file {
-					flex-grow: 1;
-					width: 128px;
-					margin: 4px;
-					box-sizing: border-box;
-				}
-
-				> .padding {
-					flex-grow: 1;
-					pointer-events: none;
-					width: 128px + 8px;
-				}
-			}
-
-			> .empty {
-				padding: 16px;
-				text-align: center;
-				pointer-events: none;
-				opacity: 0.5;
-
-				> p {
-					margin: 0;
-				}
-			}
-		}
+	&.navSeparator {
+		margin: 0;
+		padding: 0;
+		opacity: 0.5;
+		cursor: default;
 	}
+}
 
-	> .dropzone {
-		position: absolute;
-		left: 0;
-		top: 38px;
-		width: 100%;
-		height: calc(100% - 38px);
-		border: dashed 2px var(--focus);
+.navMenu {
+	margin-left: auto;
+	padding: 0 12px;
+}
+
+.main {
+	flex: 1;
+	overflow: auto;
+	padding: var(--margin);
+	user-select: none;
+
+	&.fetching {
+		cursor: wait !important;
+		opacity: 0.5;
 		pointer-events: none;
 	}
 
-	> input {
-		display: none;
+	&.uploading {
+		height: calc(100% - 38px - 100px);
 	}
 }
+
+.folders,
+.files {
+	display: flex;
+	flex-wrap: wrap;
+}
+
+.folder,
+.file {
+	flex-grow: 1;
+	width: 128px;
+	margin: 4px;
+	box-sizing: border-box;
+}
+
+.padding {
+	flex-grow: 1;
+	pointer-events: none;
+	width: 128px + 8px;
+}
+
+.empty {
+	padding: 16px;
+	text-align: center;
+	pointer-events: none;
+	opacity: 0.5;
+}
+
+.dropzone {
+	position: absolute;
+	left: 0;
+	top: 38px;
+	width: 100%;
+	height: calc(100% - 38px);
+	border: dashed 2px var(--focus);
+	pointer-events: none;
+}
 </style>
diff --git a/packages/frontend/src/components/MkDriveFileThumbnail.vue b/packages/frontend/src/components/MkDriveFileThumbnail.vue
index 33379ed5c..490aed6e0 100644
--- a/packages/frontend/src/components/MkDriveFileThumbnail.vue
+++ b/packages/frontend/src/components/MkDriveFileThumbnail.vue
@@ -1,16 +1,16 @@
 <template>
-<div ref="thumbnail" class="zdjebgpv">
+<div ref="thumbnail" :class="$style.root">
 	<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 icon"></i>
-	<i v-else-if="is === 'video'" class="ti ti-video icon"></i>
-	<i v-else-if="is === 'audio' || is === 'midi'" class="ti ti-file-music icon"></i>
-	<i v-else-if="is === 'csv'" class="ti ti-file-text icon"></i>
-	<i v-else-if="is === 'pdf'" class="ti ti-file-text icon"></i>
-	<i v-else-if="is === 'textfile'" class="ti ti-file-text icon"></i>
-	<i v-else-if="is === 'archive'" class="ti ti-file-zip icon"></i>
-	<i v-else class="ti ti-file icon"></i>
+	<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>
+	<i v-else-if="is === 'audio' || is === 'midi'" class="ti ti-file-music" :class="$style.icon"></i>
+	<i v-else-if="is === 'csv'" class="ti ti-file-text" :class="$style.icon"></i>
+	<i v-else-if="is === 'pdf'" class="ti ti-file-text" :class="$style.icon"></i>
+	<i v-else-if="is === 'textfile'" class="ti ti-file-text" :class="$style.icon"></i>
+	<i v-else-if="is === 'archive'" class="ti ti-file-zip" :class="$style.icon"></i>
+	<i v-else class="ti ti-file" :class="$style.icon"></i>
 
-	<i v-if="isThumbnailAvailable && is === 'video'" class="ti ti-video icon-sub"></i>
+	<i v-if="isThumbnailAvailable && is === 'video'" class="ti ti-video" :class="$style.iconSub"></i>
 </div>
 </template>
 
@@ -53,28 +53,28 @@ const isThumbnailAvailable = computed(() => {
 });
 </script>
 
-<style lang="scss" scoped>
-.zdjebgpv {
+<style lang="scss" module>
+.root {
 	position: relative;
 	display: flex;
 	background: var(--panel);
 	border-radius: 8px;
 	overflow: clip;
+}
 
-	> .icon-sub {
-		position: absolute;
-		width: 30%;
-		height: auto;
-		margin: 0;
-		right: 4%;
-		bottom: 4%;
-	}
+.iconSub {
+	position: absolute;
+	width: 30%;
+	height: auto;
+	margin: 0;
+	right: 4%;
+	bottom: 4%;
+}
 
-	> .icon {
-		pointer-events: none;
-		margin: auto;
-		font-size: 32px;
-		color: #777;
-	}
+.icon {
+	pointer-events: none;
+	margin: auto;
+	font-size: 32px;
+	color: #777;
 }
 </style>
diff --git a/packages/frontend/src/components/MkDriveSelectDialog.vue b/packages/frontend/src/components/MkDriveSelectDialog.vue
index 8d2b19c01..da873cb90 100644
--- a/packages/frontend/src/components/MkDriveSelectDialog.vue
+++ b/packages/frontend/src/components/MkDriveSelectDialog.vue
@@ -3,8 +3,8 @@
 	ref="dialog"
 	:width="800"
 	:height="500"
-	:with-ok-button="true"
-	:ok-button-disabled="(type === 'file') && (selected.length === 0)"
+	:withOkButton="true"
+	:okButtonDisabled="(type === 'file') && (selected.length === 0)"
 	@click="cancel()"
 	@close="cancel()"
 	@ok="ok()"
@@ -14,7 +14,7 @@
 		{{ multiple ? ((type === 'file') ? i18n.ts.selectFiles : i18n.ts.selectFolders) : ((type === 'file') ? i18n.ts.selectFile : i18n.ts.selectFolder) }}
 		<span v-if="selected.length > 0" style="margin-left: 8px; opacity: 0.5;">({{ number(selected.length) }})</span>
 	</template>
-	<XDrive :multiple="multiple" :select="type" @change-selection="onChangeSelection" @selected="ok()"/>
+	<XDrive :multiple="multiple" :select="type" @changeSelection="onChangeSelection" @selected="ok()"/>
 </MkModalWindow>
 </template>
 
diff --git a/packages/frontend/src/components/MkDriveWindow.vue b/packages/frontend/src/components/MkDriveWindow.vue
index 8b2abc15a..64ccbec9c 100644
--- a/packages/frontend/src/components/MkDriveWindow.vue
+++ b/packages/frontend/src/components/MkDriveWindow.vue
@@ -1,15 +1,15 @@
 <template>
 <MkWindow
 	ref="window"
-	:initial-width="800"
-	:initial-height="500"
-	:can-resize="true"
+	:initialWidth="800"
+	:initialHeight="500"
+	:canResize="true"
 	@closed="emit('closed')"
 >
 	<template #header>
 		{{ i18n.ts.drive }}
 	</template>
-	<XDrive :initial-folder="initialFolder"/>
+	<XDrive :initialFolder="initialFolder"/>
 </MkWindow>
 </template>
 
diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue
index 9eaf16374..cf856fd31 100644
--- a/packages/frontend/src/components/MkEmojiPicker.vue
+++ b/packages/frontend/src/components/MkEmojiPicker.vue
@@ -1,7 +1,8 @@
 <template>
 <div class="omfetrab" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer, asWindow }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }">
 	<input ref="searchEl" :value="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" type="search" @input="input()" @paste.stop="paste" @keydown.stop.prevent.enter="onEnter">
-	<div ref="emojisEl" class="emojis">
+	<!-- FirefoxのTabフォーカスが想定外の挙動となるためtabindex="-1"を追加 https://github.com/misskey-dev/misskey/issues/10744 -->
+	<div ref="emojisEl" class="emojis" tabindex="-1">
 		<section class="result">
 			<div v-if="searchResultCustom.length > 0" class="body">
 				<button
@@ -69,8 +70,8 @@
 			<XSection
 				v-for="category in customEmojiCategories"
 				:key="`custom:${category}`"
-				:initial-shown="false"
-				:emojis="computed(() => customEmojis.filter(e => category === null ? (e.category === 'null' || !e.category) : e.category === category).map(e => `:${e.name}:`))"
+				:initialShown="false"
+				:emojis="computed(() => customEmojis.filter(e => category === null ? (e.category === 'null' || !e.category) : e.category === category).filter(filterAvailable).map(e => `:${e.name}:`))"
 				@chosen="chosen"
 			>
 				{{ category || i18n.ts.other }}
@@ -101,7 +102,8 @@ import { isTouchUsing } from '@/scripts/touch';
 import { deviceKind } from '@/scripts/device-kind';
 import { i18n } from '@/i18n';
 import { defaultStore } from '@/store';
-import { customEmojiCategories, customEmojis } from '@/custom-emojis';
+import { customEmojiCategories, customEmojis, customEmojisMap } from '@/custom-emojis';
+import { $i } from '@/account';
 
 const props = withDefaults(defineProps<{
 	showPinned?: boolean;
@@ -222,7 +224,6 @@ watch(q, () => {
 		if (newQ.includes(' ')) { // AND検索
 			const keywords = newQ.split(' ');
 
-			// 名前にキーワードが含まれている
 			for (const emoji of emojis) {
 				if (keywords.every(keyword => emoji.name.includes(keyword))) {
 					matches.add(emoji);
@@ -231,11 +232,12 @@ watch(q, () => {
 			}
 			if (matches.size >= max) return matches;
 
-			// 名前またはエイリアスにキーワードが含まれている
-			for (const emoji of emojis) {
-				if (keywords.every(keyword => emoji.name.includes(keyword) || emoji.keywords.some(alias => alias.includes(keyword)))) {
-					matches.add(emoji);
-					if (matches.size >= max) break;
+			for (const index of Object.values(defaultStore.state.additionalUnicodeEmojiIndexes)) {
+				for (const emoji of emojis) {
+					if (keywords.every(keyword => index[emoji.char].some(k => k.includes(keyword)))) {
+						matches.add(emoji);
+						if (matches.size >= max) break;
+					}
 				}
 			}
 		} else {
@@ -247,13 +249,14 @@ watch(q, () => {
 			}
 			if (matches.size >= max) return matches;
 
-			for (const emoji of emojis) {
-				if (emoji.keywords.some(keyword => keyword.startsWith(newQ))) {
-					matches.add(emoji);
-					if (matches.size >= max) break;
+			for (const index of Object.values(defaultStore.state.additionalUnicodeEmojiIndexes)) {
+				for (const emoji of emojis) {
+					if (index[emoji.char].some(k => k.startsWith(newQ))) {
+						matches.add(emoji);
+						if (matches.size >= max) break;
+					}
 				}
 			}
-			if (matches.size >= max) return matches;
 
 			for (const emoji of emojis) {
 				if (emoji.name.includes(newQ)) {
@@ -263,10 +266,12 @@ watch(q, () => {
 			}
 			if (matches.size >= max) return matches;
 
-			for (const emoji of emojis) {
-				if (emoji.keywords.some(keyword => keyword.includes(newQ))) {
-					matches.add(emoji);
-					if (matches.size >= max) break;
+			for (const index of Object.values(defaultStore.state.additionalUnicodeEmojiIndexes)) {
+				for (const emoji of emojis) {
+					if (index[emoji.char].some(k => k.includes(newQ))) {
+						matches.add(emoji);
+						if (matches.size >= max) break;
+					}
 				}
 			}
 		}
@@ -274,10 +279,14 @@ watch(q, () => {
 		return matches;
 	};
 
-	searchResultCustom.value = Array.from(searchCustom());
+	searchResultCustom.value = Array.from(searchCustom()).filter(filterAvailable);
 	searchResultUnicode.value = Array.from(searchUnicode());
 });
 
+function filterAvailable(emoji: Misskey.entities.CustomEmoji): boolean {
+	return (emoji.roleIdsThatCanBeUsedThisEmojiAsReaction == null || emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0) || ($i && $i.roles.some(r => emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.includes(r.id)));
+}
+
 function focus() {
 	if (!['smartphone', 'tablet'].includes(deviceKind) && !isTouchUsing) {
 		searchEl.value?.focus({
@@ -347,7 +356,7 @@ function done(query?: string): boolean | void {
 	if (query == null || typeof query !== 'string') return;
 
 	const q2 = query.replace(/:/g, '');
-	const exactMatchCustom = customEmojis.value.find(emoji => emoji.name === q2);
+	const exactMatchCustom = customEmojisMap.get(q2);
 	if (exactMatchCustom) {
 		chosen(exactMatchCustom);
 		return true;
diff --git a/packages/frontend/src/components/MkEmojiPickerDialog.vue b/packages/frontend/src/components/MkEmojiPickerDialog.vue
index c568d4ed5..cfb65e3b6 100644
--- a/packages/frontend/src/components/MkEmojiPickerDialog.vue
+++ b/packages/frontend/src/components/MkEmojiPickerDialog.vue
@@ -2,10 +2,10 @@
 <MkModal
 	ref="modal"
 	v-slot="{ type, maxHeight }"
-	:z-priority="'middle'"
-	:prefer-type="asReactionPicker && defaultStore.state.reactionPickerUseDrawerForMobile === false ? 'popup' : 'auto'"
-	:transparent-bg="true"
-	:manual-showing="manualShowing"
+	:zPriority="'middle'"
+	:preferType="asReactionPicker && defaultStore.state.reactionPickerUseDrawerForMobile === false ? 'popup' : 'auto'"
+	:transparentBg="true"
+	:manualShowing="manualShowing"
 	:src="src"
 	@click="modal?.close()"
 	@opening="opening"
@@ -14,11 +14,11 @@
 >
 	<MkEmojiPicker
 		ref="picker"
-		class="ryghynhb _popup _shadow"
-		:class="{ drawer: type === 'drawer' }"
-		:show-pinned="showPinned"
-		:as-reaction-picker="asReactionPicker"
-		:as-drawer="type === 'drawer'"
+		class="_popup _shadow"
+		:class="{ [$style.drawer]: type === 'drawer' }"
+		:showPinned="showPinned"
+		:asReactionPicker="asReactionPicker"
+		:asDrawer="type === 'drawer'"
 		:max-height="maxHeight"
 		@chosen="chosen"
 	/>
@@ -67,12 +67,10 @@ function opening() {
 }
 </script>
 
-<style lang="scss" scoped>
-.ryghynhb {
-	&.drawer {
-		border-radius: 24px;
-		border-bottom-right-radius: 0;
-		border-bottom-left-radius: 0;
-	}
+<style lang="scss" module>
+.drawer {
+	border-radius: 24px;
+	border-bottom-right-radius: 0;
+	border-bottom-left-radius: 0;
 }
 </style>
diff --git a/packages/frontend/src/components/MkEmojiPickerWindow.vue b/packages/frontend/src/components/MkEmojiPickerWindow.vue
index 84970410e..9fecfd608 100644
--- a/packages/frontend/src/components/MkEmojiPickerWindow.vue
+++ b/packages/frontend/src/components/MkEmojiPickerWindow.vue
@@ -1,13 +1,14 @@
 <template>
-<MkWindow ref="window"
-	:initial-width="300"
-	:initial-height="290"
-	:can-resize="true"
+<MkWindow
+	ref="window"
+	:initialWidth="300"
+	:initialHeight="290"
+	:canResize="true"
 	:mini="true"
 	:front="true"
 	@closed="emit('closed')"
 >
-	<MkEmojiPicker :show-pinned="showPinned" :as-reaction-picker="asReactionPicker" as-window :class="$style.picker" @chosen="chosen"/>
+	<MkEmojiPicker :showPinned="showPinned" :asReactionPicker="asReactionPicker" asWindow :class="$style.picker" @chosen="chosen"/>
 </MkWindow>
 </template>
 
diff --git a/packages/frontend/src/components/MkFileCaptionEditWindow.vue b/packages/frontend/src/components/MkFileCaptionEditWindow.vue
index 95eef45df..61b87bda7 100644
--- a/packages/frontend/src/components/MkFileCaptionEditWindow.vue
+++ b/packages/frontend/src/components/MkFileCaptionEditWindow.vue
@@ -3,14 +3,14 @@
 	ref="dialog"
 	:width="400"
 	:height="450"
-	:with-ok-button="true"
-	:ok-button-disabled="false"
+	:withOkButton="true"
+	:okButtonDisabled="false"
 	@ok="ok()"
 	@close="dialog.close()"
 	@closed="emit('closed')"
 >
 	<template #header>{{ i18n.ts.describeFile }}</template>
-	<MkSpacer :margin-min="20" :margin-max="28">
+	<MkSpacer :marginMin="20" :marginMax="28">
 		<MkDriveFileThumbnail :file="file" fit="contain" style="height: 100px; margin-bottom: 16px;"/>
 		<MkTextarea v-model="caption" autofocus :placeholder="i18n.ts.inputNewDescription">
 			<template #label>{{ i18n.ts.caption }}</template>
diff --git a/packages/frontend/src/components/MkFoldableSection.vue b/packages/frontend/src/components/MkFoldableSection.vue
index 475e01c8d..5dd07fc7d 100644
--- a/packages/frontend/src/components/MkFoldableSection.vue
+++ b/packages/frontend/src/components/MkFoldableSection.vue
@@ -1,9 +1,9 @@
 <template>
-<div class="ssazuxis">
-	<header class="_button" :style="{ background: bg }" @click="showBody = !showBody">
-		<div class="title"><div><slot name="header"></slot></div></div>
-		<div class="divider"></div>
-		<button class="_button">
+<div ref="el" :class="$style.root">
+	<header :class="$style.header" class="_button" :style="{ background: bg }" @click="showBody = !showBody">
+		<div :class="$style.title"><div><slot name="header"></slot></div></div>
+		<div :class="$style.divider"></div>
+		<button class="_button" :class="$style.button">
 			<template v-if="showBody"><i class="ti ti-chevron-up"></i></template>
 			<template v-else><i class="ti ti-chevron-down"></i></template>
 		</button>
@@ -11,9 +11,9 @@
 	<Transition
 		:name="defaultStore.state.animation ? 'folder-toggle' : ''"
 		@enter="enter"
-		@after-enter="afterEnter"
+		@afterEnter="afterEnter"
 		@leave="leave"
-		@after-leave="afterLeave"
+		@afterLeave="afterLeave"
 	>
 		<div v-show="showBody">
 			<slot></slot>
@@ -22,84 +22,71 @@
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { onMounted, ref, shallowRef, watch } from 'vue';
 import tinycolor from 'tinycolor2';
 import { miLocalStorage } from '@/local-storage';
 import { defaultStore } from '@/store';
 
 const miLocalStoragePrefix = 'ui:folder:' as const;
 
-export default defineComponent({
-	props: {
-		expanded: {
-			type: Boolean,
-			required: false,
-			default: true,
-		},
-		persistKey: {
-			type: String,
-			required: false,
-			default: null,
-		},
-	},
-	data() {
-		return {
-			defaultStore,
-			bg: null,
-			showBody: (this.persistKey && miLocalStorage.getItem(`${miLocalStoragePrefix}${this.persistKey}`)) ? (miLocalStorage.getItem(`${miLocalStoragePrefix}${this.persistKey}`) === 't') : this.expanded,
-		};
-	},
-	watch: {
-		showBody() {
-			if (this.persistKey) {
-				miLocalStorage.setItem(`${miLocalStoragePrefix}${this.persistKey}`, this.showBody ? 't' : 'f');
-			}
-		},
-	},
-	mounted() {
-		function getParentBg(el: Element | null): string {
-			if (el == null || el.tagName === 'BODY') return 'var(--bg)';
-			const bg = el.style.background || el.style.backgroundColor;
-			if (bg) {
-				return bg;
-			} else {
-				return getParentBg(el.parentElement);
-			}
-		}
-		const rawBg = getParentBg(this.$el);
-		const bg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg);
-		bg.setAlpha(0.85);
-		this.bg = bg.toRgbString();
-	},
-	methods: {
-		toggleContent(show: boolean) {
-			this.showBody = show;
-		},
+const props = withDefaults(defineProps<{
+	expanded?: boolean;
+	persistKey?: string;
+}>(), {
+	expanded: true,
+});
 
-		enter(el) {
-			const elementHeight = el.getBoundingClientRect().height;
-			el.style.height = 0;
-			el.offsetHeight; // reflow
-			el.style.height = elementHeight + 'px';
-		},
-		afterEnter(el) {
-			el.style.height = null;
-		},
-		leave(el) {
-			const elementHeight = el.getBoundingClientRect().height;
-			el.style.height = elementHeight + 'px';
-			el.offsetHeight; // reflow
-			el.style.height = 0;
-		},
-		afterLeave(el) {
-			el.style.height = null;
-		},
-	},
+const el = shallowRef<HTMLDivElement>();
+const bg = ref<string | null>(null);
+const showBody = ref((props.persistKey && miLocalStorage.getItem(`${miLocalStoragePrefix}${props.persistKey}`)) ? (miLocalStorage.getItem(`${miLocalStoragePrefix}${props.persistKey}`) === 't') : props.expanded);
+
+watch(showBody, () => {
+	if (props.persistKey) {
+		miLocalStorage.setItem(`${miLocalStoragePrefix}${props.persistKey}`, showBody.value ? 't' : 'f');
+	}
+});
+
+function enter(el: Element) {
+	const elementHeight = el.getBoundingClientRect().height;
+	el.style.height = 0;
+	el.offsetHeight; // reflow
+	el.style.height = elementHeight + 'px';
+}
+
+function afterEnter(el: Element) {
+	el.style.height = null;
+}
+
+function leave(el: Element) {
+	const elementHeight = el.getBoundingClientRect().height;
+	el.style.height = elementHeight + 'px';
+	el.offsetHeight; // reflow
+	el.style.height = 0;
+}
+
+function afterLeave(el: Element) {
+	el.style.height = null;
+}
+
+onMounted(() => {
+	function getParentBg(el: HTMLElement | null): string {
+		if (el == null || el.tagName === 'BODY') return 'var(--bg)';
+		const bg = el.style.background || el.style.backgroundColor;
+		if (bg) {
+			return bg;
+		} else {
+			return getParentBg(el.parentElement);
+		}
+	}
+	const rawBg = getParentBg(el.value);
+	const _bg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg);
+	_bg.setAlpha(0.85);
+	bg.value = _bg.toRgbString();
 });
 </script>
 
-<style lang="scss" scoped>
+<style lang="scss" module>
 .folder-toggle-enter-active, .folder-toggle-leave-active {
 	overflow-y: clip;
 	transition: opacity 0.5s, height 0.5s !important;
@@ -111,45 +98,41 @@ export default defineComponent({
 	opacity: 0;
 }
 
-.ssazuxis {
+.root {
 	position: relative;
+}
 
-	> header {
-		display: flex;
-		position: relative;
-		z-index: 10;
-		position: sticky;
-		top: var(--stickyTop, 0px);
-		-webkit-backdrop-filter: var(--blur, blur(8px));
-		backdrop-filter: var(--blur, blur(20px));
+.header {
+	display: flex;
+	position: relative;
+	z-index: 10;
+	position: sticky;
+	top: var(--stickyTop, 0px);
+	-webkit-backdrop-filter: var(--blur, blur(8px));
+	backdrop-filter: var(--blur, blur(20px));
+}
 
-		> .title {
-			display: grid;
-			place-content: center;
-			margin: 0;
-			padding: 12px 16px 12px 0;
-		}
+.title {
+	display: grid;
+	place-content: center;
+	margin: 0;
+	padding: 12px 16px 12px 0;
+}
 
-		> .divider {
-			flex: 1;
-			margin: auto;
-			height: 1px;
-			background: var(--divider);
-		}
+.divider {
+	flex: 1;
+	margin: auto;
+	height: 1px;
+	background: var(--divider);
+}
 
-		> button {
-			padding: 12px 0 12px 16px;
-		}
-	}
+.button {
+	padding: 12px 0 12px 16px;
 }
 
 @container (max-width: 500px) {
-	.ssazuxis {
-		> header {
-			> .title {
-				padding: 8px 10px 8px 0;
-			}
-		}
+	.title {
+		padding: 8px 10px 8px 0;
 	}
 }
 </style>
diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue
index 10eee6aab..70f0cc5cd 100644
--- a/packages/frontend/src/components/MkFolder.vue
+++ b/packages/frontend/src/components/MkFolder.vue
@@ -5,8 +5,8 @@
 			<div :class="[$style.header, { [$style.opened]: opened }]" class="_button" role="button" data-cy-folder-header @click="toggle">
 				<div :class="$style.headerIcon"><slot name="icon"></slot></div>
 				<div :class="$style.headerText">
-					<div :class="$style.headerTextMain">
-						<slot name="label"></slot>
+					<div>
+						<MkCondensedLine :minScale="2 / 3"><slot name="label"></slot></MkCondensedLine>
 					</div>
 					<div :class="$style.headerTextSub">
 						<slot name="caption"></slot>
@@ -22,18 +22,18 @@
 
 		<div v-if="openedAtLeastOnce" :class="[$style.body, { [$style.bgSame]: bgSame }]" :style="{ maxHeight: maxHeight ? `${maxHeight}px` : null, overflow: maxHeight ? `auto` : null }" :aria-hidden="!opened">
 			<Transition
-				:enter-active-class="defaultStore.state.animation ? $style.transition_toggle_enterActive : ''"
-				:leave-active-class="defaultStore.state.animation ? $style.transition_toggle_leaveActive : ''"
-				:enter-from-class="defaultStore.state.animation ? $style.transition_toggle_enterFrom : ''"
-				:leave-to-class="defaultStore.state.animation ? $style.transition_toggle_leaveTo : ''"
+				:enterActiveClass="defaultStore.state.animation ? $style.transition_toggle_enterActive : ''"
+				:leaveActiveClass="defaultStore.state.animation ? $style.transition_toggle_leaveActive : ''"
+				:enterFromClass="defaultStore.state.animation ? $style.transition_toggle_enterFrom : ''"
+				:leaveToClass="defaultStore.state.animation ? $style.transition_toggle_leaveTo : ''"
 				@enter="enter"
-				@after-enter="afterEnter"
+				@afterEnter="afterEnter"
 				@leave="leave"
-				@after-leave="afterLeave"
+				@afterLeave="afterLeave"
 			>
 				<KeepAlive>
 					<div v-show="opened">
-						<MkSpacer :margin-min="14" :margin-max="22">
+						<MkSpacer :marginMin="14" :marginMax="22">
 							<slot></slot>
 						</MkSpacer>
 					</div>
@@ -185,10 +185,6 @@ onMounted(() => {
 	padding-right: 12px;
 }
 
-.headerTextMain {
-
-}
-
 .headerTextSub {
 	color: var(--fgTransparentWeak);
 	font-size: .85em;
diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue
index beee21c64..b732fbb2b 100644
--- a/packages/frontend/src/components/MkFollowButton.vue
+++ b/packages/frontend/src/components/MkFollowButton.vue
@@ -1,30 +1,30 @@
 <template>
 <button
-	class="kpoogebi _button"
-	:class="{ wait, active: isFollowing || hasPendingFollowRequestFromYou, full, large }"
+	class="_button"
+	:class="[$style.root, { [$style.wait]: wait, [$style.active]: isFollowing || hasPendingFollowRequestFromYou, [$style.full]: full, [$style.large]: large }]"
 	:disabled="wait"
 	@click="onClick"
 >
 	<template v-if="!wait">
 		<template v-if="hasPendingFollowRequestFromYou && user.isLocked">
-			<span v-if="full">{{ i18n.ts.followRequestPending }}</span><i class="ti ti-hourglass-empty"></i>
+			<span v-if="full" :class="$style.text">{{ i18n.ts.followRequestPending }}</span><i class="ti ti-hourglass-empty"></i>
 		</template>
 		<template v-else-if="hasPendingFollowRequestFromYou && !user.isLocked">
 			<!-- つまりリモートフォローの場合。 -->
-			<span v-if="full">{{ i18n.ts.processing }}</span><MkLoading :em="true" :colored="false"/>
+			<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">{{ i18n.ts.unfollow }}</span><i class="ti ti-minus"></i>
+			<span v-if="full" :class="$style.text">{{ i18n.ts.unfollow }}</span><i class="ti ti-minus"></i>
 		</template>
 		<template v-else-if="!isFollowing && user.isLocked">
-			<span v-if="full">{{ i18n.ts.followRequest }}</span><i class="ti ti-plus"></i>
+			<span v-if="full" :class="$style.text">{{ i18n.ts.followRequest }}</span><i class="ti ti-plus"></i>
 		</template>
 		<template v-else-if="!isFollowing && !user.isLocked">
-			<span v-if="full">{{ i18n.ts.follow }}</span><i class="ti ti-plus"></i>
+			<span v-if="full" :class="$style.text">{{ i18n.ts.follow }}</span><i class="ti ti-plus"></i>
 		</template>
 	</template>
 	<template v-else>
-		<span v-if="full">{{ i18n.ts.processing }}</span><MkLoading :em="true" :colored="false"/>
+		<span v-if="full" :class="$style.text">{{ i18n.ts.processing }}</span><MkLoading :em="true" :colored="false"/>
 	</template>
 </button>
 </template>
@@ -33,7 +33,7 @@
 import { onBeforeUnmount, onMounted } from 'vue';
 import * as Misskey from 'misskey-js';
 import * as os from '@/os';
-import { stream } from '@/stream';
+import { useStream } from '@/stream';
 import { i18n } from '@/i18n';
 import { claimAchievement } from '@/scripts/achievements';
 import { $i } from '@/account';
@@ -50,7 +50,7 @@ const props = withDefaults(defineProps<{
 let isFollowing = $ref(props.user.isFollowing);
 let hasPendingFollowRequestFromYou = $ref(props.user.hasPendingFollowRequestFromYou);
 let wait = $ref(false);
-const connection = stream.useChannel('main');
+const connection = useStream().useChannel('main');
 
 if (props.user.isFollowing == null) {
 	os.api('users/show', {
@@ -126,13 +126,12 @@ onBeforeUnmount(() => {
 });
 </script>
 
-<style lang="scss" scoped>
-.kpoogebi {
+<style lang="scss" module>
+.root {
 	position: relative;
 	display: inline-block;
 	font-weight: bold;
-	color: var(--accent);
-	background: transparent;
+	color: var(--fgOnWhite);
 	border: solid 1px var(--accent);
 	padding: 0;
 	height: 31px;
@@ -196,9 +195,9 @@ onBeforeUnmount(() => {
 		cursor: wait !important;
 		opacity: 0.7;
 	}
+}
 
-	> span {
-		margin-right: 6px;
-	}
+.text {
+	margin-right: 6px;
 }
 </style>
diff --git a/packages/frontend/src/components/MkForgotPassword.vue b/packages/frontend/src/components/MkForgotPassword.vue
index 0befa7e3a..1264c4233 100644
--- a/packages/frontend/src/components/MkForgotPassword.vue
+++ b/packages/frontend/src/components/MkForgotPassword.vue
@@ -8,27 +8,28 @@
 >
 	<template #header>{{ i18n.ts.forgotPassword }}</template>
 
-	<form v-if="instance.enableEmail" class="bafeceda" @submit.prevent="onSubmit">
-		<div class="main _gaps_m">
-			<MkInput v-model="username" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autofocus required>
-				<template #label>{{ i18n.ts.username }}</template>
-				<template #prefix>@</template>
-			</MkInput>
+	<MkSpacer :marginMin="20" :marginMax="28">
+		<form v-if="instance.enableEmail" @submit.prevent="onSubmit">
+			<div class="_gaps_m">
+				<MkInput v-model="username" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autofocus required>
+					<template #label>{{ i18n.ts.username }}</template>
+					<template #prefix>@</template>
+				</MkInput>
 
-			<MkInput v-model="email" type="email" :spellcheck="false" required>
-				<template #label>{{ i18n.ts.emailAddress }}</template>
-				<template #caption>{{ i18n.ts._forgotPassword.enterEmail }}</template>
-			</MkInput>
+				<MkInput v-model="email" type="email" :spellcheck="false" required>
+					<template #label>{{ i18n.ts.emailAddress }}</template>
+					<template #caption>{{ i18n.ts._forgotPassword.enterEmail }}</template>
+				</MkInput>
 
-			<MkButton type="submit" :disabled="processing" primary style="margin: 0 auto;">{{ i18n.ts.send }}</MkButton>
+				<MkButton type="submit" rounded :disabled="processing" primary style="margin: 0 auto;">{{ i18n.ts.send }}</MkButton>
+
+				<MkInfo>{{ i18n.ts._forgotPassword.ifNoEmail }}</MkInfo>
+			</div>
+		</form>
+		<div v-else>
+			{{ i18n.ts._forgotPassword.contactAdmin }}
 		</div>
-		<div class="sub">
-			<MkA to="/about" class="_link">{{ i18n.ts._forgotPassword.ifNoEmail }}</MkA>
-		</div>
-	</form>
-	<div v-else class="bafecedb">
-		{{ i18n.ts._forgotPassword.contactAdmin }}
-	</div>
+	</MkSpacer>
 </MkModalWindow>
 </template>
 
@@ -37,6 +38,7 @@ import { } from 'vue';
 import MkModalWindow from '@/components/MkModalWindow.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
+import MkInfo from '@/components/MkInfo.vue';
 import * as os from '@/os';
 import { instance } from '@/instance';
 import { i18n } from '@/i18n';
@@ -62,20 +64,3 @@ async function onSubmit() {
 	dialog.close();
 }
 </script>
-
-<style lang="scss" scoped>
-.bafeceda {
-	> .main {
-		padding: 24px;
-	}
-
-	> .sub {
-		border-top: solid 0.5px var(--divider);
-		padding: 24px;
-	}
-}
-
-.bafecedb {
-	padding: 24px;
-}
-</style>
diff --git a/packages/frontend/src/components/MkFormDialog.vue b/packages/frontend/src/components/MkFormDialog.vue
index 979df2e7c..6d2b391e6 100644
--- a/packages/frontend/src/components/MkFormDialog.vue
+++ b/packages/frontend/src/components/MkFormDialog.vue
@@ -2,9 +2,9 @@
 <MkModalWindow
 	ref="dialog"
 	:width="450"
-	:can-close="false"
-	:with-ok-button="true"
-	:ok-button-disabled="false"
+	:canClose="false"
+	:withOkButton="true"
+	:okButtonDisabled="false"
 	@click="cancel()"
 	@ok="ok()"
 	@close="cancel()"
@@ -14,7 +14,7 @@
 		{{ title }}
 	</template>
 
-	<MkSpacer :margin-min="20" :margin-max="32">
+	<MkSpacer :marginMin="20" :marginMax="32">
 		<div class="_gaps_m">
 			<template v-for="item in Object.keys(form).filter(item => !form[item].hidden)">
 				<MkInput v-if="form[item].type === 'number'" v-model="values[item]" type="number" :step="form[item].step || 1">
@@ -41,7 +41,7 @@
 					<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ i18n.ts.optional }})</span></template>
 					<option v-for="item in form[item].options" :key="item.value" :value="item.value">{{ item.label }}</option>
 				</MkRadios>
-				<MkRange v-else-if="form[item].type === 'range'" v-model="values[item]" :min="form[item].min" :max="form[item].max" :step="form[item].step" :text-converter="form[item].textConverter">
+				<MkRange v-else-if="form[item].type === 'range'" v-model="values[item]" :min="form[item].min" :max="form[item].max" :step="form[item].step" :textConverter="form[item].textConverter">
 					<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ i18n.ts.optional }})</span></template>
 					<template v-if="form[item].description" #caption>{{ form[item].description }}</template>
 				</MkRange>
@@ -54,8 +54,8 @@
 </MkModalWindow>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { reactive, shallowRef } from 'vue';
 import MkInput from './MkInput.vue';
 import MkTextarea from './MkTextarea.vue';
 import MkSwitch from './MkSwitch.vue';
@@ -66,58 +66,36 @@ import MkRadios from './MkRadios.vue';
 import MkModalWindow from '@/components/MkModalWindow.vue';
 import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		MkModalWindow,
-		MkInput,
-		MkTextarea,
-		MkSwitch,
-		MkSelect,
-		MkRange,
-		MkButton,
-		MkRadios,
-	},
+const props = defineProps<{
+	title: string;
+	form: any;
+}>();
 
-	props: {
-		title: {
-			type: String,
-			required: true,
-		},
-		form: {
-			type: Object,
-			required: true,
-		},
-	},
+const emit = defineEmits<{
+	(ev: 'done', v: {
+		canceled?: boolean;
+		result?: any;
+	}): void;
+}>();
 
-	emits: ['done'],
+const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
+const values = reactive({});
 
-	data() {
-		return {
-			values: {},
-			i18n,
-		};
-	},
+for (const item in props.form) {
+	values[item] = props.form[item].default ?? null;
+}
 
-	created() {
-		for (const item in this.form) {
-			this.values[item] = this.form[item].default ?? null;
-		}
-	},
+function ok() {
+	emit('done', {
+		result: values,
+	});
+	dialog.value.close();
+}
 
-	methods: {
-		ok() {
-			this.$emit('done', {
-				result: this.values,
-			});
-			this.$refs.dialog.close();
-		},
-
-		cancel() {
-			this.$emit('done', {
-				canceled: true,
-			});
-			this.$refs.dialog.close();
-		},
-	},
-});
+function cancel() {
+	emit('done', {
+		canceled: true,
+	});
+	dialog.value.close();
+}
 </script>
diff --git a/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts b/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts
index 57b3e7551..72ac0a58f 100644
--- a/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts
+++ b/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts
@@ -44,6 +44,10 @@ export const Default = {
 	],
 	parameters: {
 		layout: 'centered',
+		chromatic: {
+			// FIXME: flaky
+			disableSnapshot: true,
+		},
 	},
 } satisfies StoryObj<typeof MkGalleryPostPreview>;
 export const Hover = {
diff --git a/packages/frontend/src/components/MkGalleryPostPreview.vue b/packages/frontend/src/components/MkGalleryPostPreview.vue
index 4f8f7b945..3a39ad963 100644
--- a/packages/frontend/src/components/MkGalleryPostPreview.vue
+++ b/packages/frontend/src/components/MkGalleryPostPreview.vue
@@ -5,16 +5,13 @@
 			<ImgWithBlurhash
 				class="img layered"
 				:transition="safe ? null : {
-					enterActiveClass: $style.transition_toggle_enterActive,
+					duration: 500,
 					leaveActiveClass: $style.transition_toggle_leaveActive,
-					enterFromClass: $style.transition_toggle_enterFrom,
 					leaveToClass: $style.transition_toggle_leaveTo,
-					enterToClass: $style.transition_toggle_enterTo,
-					leaveFromClass: $style.transition_toggle_leaveFrom,
 				}"
 				:src="post.files[0].thumbnailUrl"
 				:hash="post.files[0].blurhash"
-				:force-blurhash="!show"
+				:forceBlurhash="!show"
 			/>
 		</Transition>
 	</div>
@@ -53,24 +50,16 @@ function leaveHover(): void {
 </script>
 
 <style lang="scss" module>
-.transition_toggle_enterActive,
 .transition_toggle_leaveActive {
-	transition: opacity 0.5s;
+	transition: opacity .5s;
 	position: absolute;
 	top: 0;
 	left: 0;
 }
 
-.transition_toggle_enterFrom,
 .transition_toggle_leaveTo {
 	opacity: 0;
 }
-
-.transition_toggle_enterTo,
-.transition_toggle_leaveFrom {
-	transition: none;
-	opacity: 1;
-}
 </style>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/components/MkImageViewer.vue b/packages/frontend/src/components/MkImageViewer.vue
deleted file mode 100644
index a90e27e50..000000000
--- a/packages/frontend/src/components/MkImageViewer.vue
+++ /dev/null
@@ -1,78 +0,0 @@
-<template>
-<MkModal ref="modal" :z-priority="'middle'" @click="modal.close()" @closed="emit('closed')">
-	<div class="xubzgfga">
-		<header>{{ image.name }}</header>
-		<img :src="image.url" :alt="image.comment" :title="image.comment" @click="modal.close()"/>
-		<footer>
-			<span>{{ image.type }}</span>
-			<span>{{ bytes(image.size) }}</span>
-			<span v-if="image.properties && image.properties.width">{{ number(image.properties.width) }}px × {{ number(image.properties.height) }}px</span>
-		</footer>
-	</div>
-</MkModal>
-</template>
-
-<script lang="ts" setup>
-import { } from 'vue';
-import * as misskey from 'misskey-js';
-import bytes from '@/filters/bytes';
-import number from '@/filters/number';
-import MkModal from '@/components/MkModal.vue';
-
-const props = withDefaults(defineProps<{
-	image: misskey.entities.DriveFile;
-}>(), {
-});
-
-const emit = defineEmits<{
-	(ev: 'closed'): void;
-}>();
-
-const modal = $shallowRef<InstanceType<typeof MkModal>>();
-</script>
-
-<style lang="scss" scoped>
-.xubzgfga {
-	margin: auto;
-	display: flex;
-	flex-direction: column;
-	height: 100%;
-
-	> header,
-	> footer {
-		align-self: center;
-		display: inline-block;
-		padding: 6px 9px;
-		font-size: 90%;
-		background: rgba(0, 0, 0, 0.5);
-		border-radius: 6px;
-		color: #fff;
-	}
-
-	> header {
-		margin-bottom: 8px;
-		opacity: 0.9;
-	}
-
-	> img {
-		display: block;
-		flex: 1;
-		min-height: 0;
-		object-fit: contain;
-		width: 100%;
-		cursor: zoom-out;
-		image-orientation: from-image;
-	}
-
-	> footer {
-		margin-top: 8px;
-		opacity: 0.8;
-
-		> span + span {
-			margin-left: 0.5em;
-			padding-left: 0.5em;
-			border-left: solid 1px rgba(255, 255, 255, 0.5);
-		}
-	}
-}
-</style>
diff --git a/packages/frontend/src/components/MkImgWithBlurhash.vue b/packages/frontend/src/components/MkImgWithBlurhash.vue
index 6406a3506..672a28f6d 100644
--- a/packages/frontend/src/components/MkImgWithBlurhash.vue
+++ b/packages/frontend/src/components/MkImgWithBlurhash.vue
@@ -1,30 +1,60 @@
 <template>
-<div :class="[$style.root, { [$style.cover]: cover }]" :title="title ?? ''">
-	<img v-if="!loaded && src && !forceBlurhash" :class="$style.loader" :src="src" @load="onLoad"/>
-	<Transition
-		mode="in-out"
-		:enter-active-class="defaultStore.state.animation && (props.transition?.enterActiveClass ?? $style['transition_toggle_enterActive']) || undefined"
-		:leave-active-class="defaultStore.state.animation && (props.transition?.leaveActiveClass ?? $style['transition_toggle_leaveActive']) || undefined"
-		:enter-from-class="defaultStore.state.animation && props.transition?.enterFromClass || undefined"
-		:leave-to-class="defaultStore.state.animation && props.transition?.leaveToClass || undefined"
-		:enter-to-class="defaultStore.state.animation && (props.transition?.enterToClass ?? $style['transition_toggle_enterTo']) || undefined"
-		:leave-from-class="defaultStore.state.animation && (props.transition?.leaveFromClass ?? $style['transition_toggle_leaveFrom']) || undefined"
+<div ref="root" :class="['chromatic-ignore', $style.root, { [$style.cover]: cover }]" :title="title ?? ''">
+	<TransitionGroup
+		:duration="defaultStore.state.animation && props.transition?.duration || undefined"
+		:enterActiveClass="defaultStore.state.animation && props.transition?.enterActiveClass || undefined"
+		:leaveActiveClass="defaultStore.state.animation && (props.transition?.leaveActiveClass ?? $style.transition_leaveActive) || undefined"
+		:enterFromClass="defaultStore.state.animation && props.transition?.enterFromClass || undefined"
+		:leaveToClass="defaultStore.state.animation && props.transition?.leaveToClass || undefined"
+		:enterToClass="defaultStore.state.animation && props.transition?.enterToClass || undefined"
+		:leaveFromClass="defaultStore.state.animation && props.transition?.leaveFromClass || undefined"
 	>
-		<canvas v-if="!loaded || forceBlurhash" ref="canvas" :class="$style.canvas" :width="width" :height="height" :title="title ?? undefined"/>
-		<img v-else :class="$style.img" :src="src ?? undefined" :title="title ?? undefined" :alt="alt ?? undefined"/>
-	</Transition>
+		<canvas v-show="hide" key="canvas" ref="canvas" :class="$style.canvas" :width="canvasWidth" :height="canvasHeight" :title="title ?? undefined"/>
+		<img v-show="!hide" key="img" ref="img" :height="imgHeight" :width="imgWidth" :class="$style.img" :src="src ?? undefined" :title="title ?? undefined" :alt="alt ?? undefined" loading="eager" decoding="async"/>
+	</TransitionGroup>
 </div>
 </template>
 
-<script lang="ts" setup>
-import { onMounted, shallowRef, useCssModule, watch } from 'vue';
-import { decode } from 'blurhash';
-import { defaultStore } from '@/store';
+<script lang="ts">
+import { $ref } from 'vue/macros';
+import DrawBlurhash from '@/workers/draw-blurhash?worker';
+import TestWebGL2 from '@/workers/test-webgl2?worker';
+import { WorkerMultiDispatch } from '@/scripts/worker-multi-dispatch';
+import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash';
 
-const $style = useCssModule();
+const workerPromise = new Promise<WorkerMultiDispatch | null>(resolve => {
+	// テスト環境で Web Worker インスタンスは作成できない
+	if (import.meta.env.MODE === 'test') {
+		resolve(null);
+		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 {
+			resolve(null);
+			if (_DEV_) console.log('WebGL2 in worker is not supported...');
+		}
+		testWorker.terminate();
+	});
+});
+</script>
+
+<script lang="ts" setup>
+import { computed, nextTick, onMounted, onUnmounted, shallowRef, watch } from 'vue';
+import { v4 as uuid } from 'uuid';
+import { render } from 'buraha';
+import { defaultStore } from '@/store';
 
 const props = withDefaults(defineProps<{
 	transition?: {
+		duration?: number | { enter: number; leave: number; };
 		enterActiveClass?: string;
 		leaveActiveClass?: string;
 		enterFromClass?: string;
@@ -51,67 +81,141 @@ const props = withDefaults(defineProps<{
 	forceBlurhash: false,
 });
 
+const viewId = uuid();
 const canvas = shallowRef<HTMLCanvasElement>();
+const root = shallowRef<HTMLDivElement>();
+const img = shallowRef<HTMLImageElement>();
 let loaded = $ref(false);
-let width = $ref(props.width);
-let height = $ref(props.height);
+let canvasWidth = $ref(64);
+let canvasHeight = $ref(64);
+let imgWidth = $ref(props.width);
+let imgHeight = $ref(props.height);
+let bitmapTmp = $ref<CanvasImageSource | undefined>();
+const hide = computed(() => !loaded || props.forceBlurhash);
 
-function onLoad() {
-	loaded = true;
+function waitForDecode() {
+	if (props.src != null && props.src !== '') {
+		nextTick()
+			.then(() => img.value?.decode())
+			.then(() => {
+				loaded = true;
+			}, error => {
+				console.error('Error occured during decoding image', img.value, error);
+				throw Error(error);
+			});
+	} else {
+		loaded = false;
+	}
 }
 
-watch([() => props.width, () => props.height], () => {
+watch([() => props.width, () => props.height, root], () => {
 	const ratio = props.width / props.height;
 	if (ratio > 1) {
-		width = Math.round(64 * ratio);
-		height = 64;
+		canvasWidth = Math.round(64 * ratio);
+		canvasHeight = 64;
 	} else {
-		width = 64;
-		height = Math.round(64 / ratio);
+		canvasWidth = 64;
+		canvasHeight = Math.round(64 / ratio);
 	}
+
+	const clientWidth = root.value?.clientWidth ?? 300;
+	imgWidth = clientWidth;
+	imgHeight = Math.round(clientWidth / ratio);
 }, {
 	immediate: true,
 });
 
-function draw() {
-	if (props.hash == null || !canvas.value) return;
-	const pixels = decode(props.hash, width, height);
+function drawImage(bitmap: CanvasImageSource) {
+	// canvasがない(mountedされていない)場合はTmpに保存しておく
+	if (!canvas.value) {
+		bitmapTmp = bitmap;
+		return;
+	}
+
+	// canvasがあれば描画する
+	bitmapTmp = undefined;
 	const ctx = canvas.value.getContext('2d');
-	const imageData = ctx!.createImageData(width, height);
-	imageData.data.set(pixels);
-	ctx!.putImageData(imageData, 0, 0);
+	if (!ctx) return;
+	ctx.drawImage(bitmap, 0, 0, canvasWidth, canvasHeight);
 }
 
-watch([() => props.hash, canvas], () => {
+async function draw() {
+	if (!canvas.value || props.hash == null) return;
+
+	const ctx = canvas.value.getContext('2d');
+	if (!ctx) return;
+
+	// avgColorでお茶をにごす
+	ctx.beginPath();
+	ctx.fillStyle = extractAvgColorFromBlurhash(props.hash) ?? '#888';
+	ctx.fillRect(0, 0, canvasWidth, canvasHeight);
+
+	const workers = await workerPromise;
+	if (workers) {
+		workers.postMessage(
+			{
+				id: viewId,
+				hash: props.hash,
+				width: canvasWidth,
+				height: canvasHeight,
+			},
+			undefined,
+		);
+	} else {
+		try {
+			const work = document.createElement('canvas');
+			work.width = canvasWidth;
+			work.height = canvasHeight;
+			render(props.hash, work);
+			ctx.drawImage(work, 0, 0, canvasWidth, canvasHeight);
+		} catch (error) {
+			console.error('Error occured during drawing blurhash', error);
+		}
+	}
+}
+
+function workerOnMessage(event: MessageEvent) {
+	if (event.data.id !== viewId) return;
+	drawImage(event.data.bitmap as ImageBitmap);
+}
+
+workerPromise.then(worker => {
+	if (worker) {
+		worker.addListener(workerOnMessage);
+	}
+
+	draw();
+});
+
+watch(() => props.src, () => {
+	waitForDecode();
+});
+
+watch(() => props.hash, () => {
 	draw();
 });
 
 onMounted(() => {
-	draw();
+	// drawImageがmountedより先に呼ばれている場合はここで描画する
+	if (bitmapTmp) {
+		drawImage(bitmapTmp);
+	}
+	waitForDecode();
+});
+
+onUnmounted(() => {
+	workerPromise.then(worker => {
+		worker?.removeListener(workerOnMessage);
+	});
 });
 </script>
 
 <style lang="scss" module>
-.transition_toggle_enterActive,
-.transition_toggle_leaveActive {
+.transition_leaveActive {
 	position: absolute;
 	top: 0;
 	left: 0;
 }
-
-.transition_toggle_enterTo,
-.transition_toggle_leaveFrom {
-	opacity: 0;
-}
-
-.loader {
-	position: absolute;
-	top: 0;
-	left: 0;
-	width: 0;
-	height: 0;
-}
-
 .root {
 	position: relative;
 	width: 100%;
diff --git a/packages/frontend/src/components/MkKeyValue.vue b/packages/frontend/src/components/MkKeyValue.vue
index ff69c7964..4b6a77563 100644
--- a/packages/frontend/src/components/MkKeyValue.vue
+++ b/packages/frontend/src/components/MkKeyValue.vue
@@ -1,9 +1,9 @@
 <template>
-<div class="alqyeyti" :class="{ oneline }">
-	<div class="key">
+<div :class="[$style.root, { [$style.oneline]: oneline }]">
+	<div :class="$style.key">
 		<slot name="key"></slot>
 	</div>
-	<div class="value">
+	<div :class="$style.value">
 		<slot name="value"></slot>
 		<button v-if="copy" v-tooltip="i18n.ts.copy" class="_textButton" style="margin-left: 0.5em;" @click="copy_"><i class="ti ti-copy"></i></button>
 	</div>
@@ -30,24 +30,18 @@ const copy_ = () => {
 };
 </script>
 
-<style lang="scss" scoped>
-.alqyeyti {
-	> .key {
-		font-size: 0.85em;
-		padding: 0 0 0.25em 0;
-		opacity: 0.75;
-	}
-
+<style lang="scss" module>
+.root {
 	&.oneline {
 		display: flex;
 
-		> .key {
+		.key {
 			width: 30%;
 			font-size: 1em;
 			padding: 0 8px 0 0;
 		}
 
-		> .value {
+		.value {
 			width: 70%;
 			white-space: nowrap;
 			overflow: hidden;
@@ -55,4 +49,10 @@ const copy_ = () => {
 		}
 	}
 }
+
+.key {
+	font-size: 0.85em;
+	padding: 0 0 0.25em 0;
+	opacity: 0.75;
+}
 </style>
diff --git a/packages/frontend/src/components/MkLaunchPad.vue b/packages/frontend/src/components/MkLaunchPad.vue
index 80e5cc827..926277861 100644
--- a/packages/frontend/src/components/MkLaunchPad.vue
+++ b/packages/frontend/src/components/MkLaunchPad.vue
@@ -1,5 +1,5 @@
 <template>
-<MkModal ref="modal" v-slot="{ type, maxHeight }" :prefer-type="preferedModalType" :anchor="anchor" :transparent-bg="true" :src="src" @click="modal.close()" @closed="emit('closed')">
+<MkModal ref="modal" v-slot="{ type, maxHeight }" :preferType="preferedModalType" :anchor="anchor" :transparentBg="true" :src="src" @click="modal.close()" @closed="emit('closed')">
 	<div class="szkkfdyq _popup _shadow" :class="{ asDrawer: type === 'drawer' }" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : '' }">
 		<div class="main">
 			<template v-for="item in items">
diff --git a/packages/frontend/src/components/MkMediaBanner.vue b/packages/frontend/src/components/MkMediaBanner.vue
index 5ca4c5051..5902d6fd2 100644
--- a/packages/frontend/src/components/MkMediaBanner.vue
+++ b/packages/frontend/src/components/MkMediaBanner.vue
@@ -1,27 +1,27 @@
 <template>
-<div class="mk-media-banner">
-	<div v-if="media.isSensitive && hide" class="sensitive" @click="hide = false">
-		<span class="icon"><i class="ti ti-alert-triangle"></i></span>
+<div :class="$style.root">
+	<div v-if="media.isSensitive && hide" :class="$style.sensitive" @click="hide = false">
+		<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>
-	<div v-else-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" class="audio">
-		<VuePlyr :options="{ volume: 0.5 }">
-			<audio controls preload="metadata">
-				<source
-					:src="media.url"
-					:type="media.type"
-				/>
-			</audio>
-		</VuePlyr>
+	<div v-else-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" :class="$style.audio">
+		<audio
+			ref="audioEl"
+			:src="media.url"
+			:title="media.name"
+			controls
+			preload="metadata"
+			@volumechange="volumechange"
+		/>
 	</div>
 	<a
-		v-else class="download"
+		v-else :class="$style.download"
 		:href="media.url"
 		:title="media.name"
 		:download="media.name"
 	>
-		<span class="icon"><i class="ti ti-download"></i></span>
+		<span style="font-size: 1.6em;"><i class="ti ti-download"></i></span>
 		<b>{{ media.name }}</b>
 	</a>
 </div>
@@ -30,9 +30,7 @@
 <script lang="ts" setup>
 import { onMounted } from 'vue';
 import * as misskey from 'misskey-js';
-import VuePlyr from 'vue-plyr';
 import { soundConfigStore } from '@/scripts/sound';
-import 'vue-plyr/dist/vue-plyr.css';
 import { i18n } from '@/i18n';
 
 const props = withDefaults(defineProps<{
@@ -52,55 +50,34 @@ onMounted(() => {
 });
 </script>
 
-<style lang="scss" scoped>
-.mk-media-banner {
+<style lang="scss" module>
+.root {
 	width: 100%;
 	border-radius: 4px;
 	margin-top: 4px;
-	// overflow: clip;
+	overflow: clip;
+}
 
-	--plyr-color-main: var(--accent);
-	--plyr-audio-controls-background: var(--bg);
-	--plyr-audio-controls-color: var(--accentLighten);
+.download,
+.sensitive {
+	display: flex;
+	align-items: center;
+	font-size: 12px;
+	padding: 8px 12px;
+	white-space: nowrap;
+}
 
-	> .download,
-	> .sensitive {
-		display: flex;
-		align-items: center;
-		font-size: 12px;
-		padding: 8px 12px;
-		white-space: nowrap;
+.download {
+	background: var(--noteAttachedFile);
+}
 
-		> * {
-			display: block;
-		}
+.sensitive {
+	background: #111;
+	color: #fff;
+}
 
-		> b {
-			overflow: hidden;
-			text-overflow: ellipsis;
-		}
-
-		> *:not(:last-child) {
-			margin-right: .2em;
-		}
-
-		> .icon {
-			font-size: 1.6em;
-		}
-	}
-
-	> .download {
-		background: var(--noteAttachedFile);
-	}
-
-	> .sensitive {
-		background: #111;
-		color: #fff;
-	}
-
-	> .audio {
-		border-radius: 8px;
-		// overflow: clip;
-	}
+.audio {
+	border-radius: 8px;
+	overflow: clip;
 }
 </style>
diff --git a/packages/frontend/src/components/MkMediaImage.vue b/packages/frontend/src/components/MkMediaImage.vue
index 42dc9e79f..b29871c36 100644
--- a/packages/frontend/src/components/MkMediaImage.vue
+++ b/packages/frontend/src/components/MkMediaImage.vue
@@ -1,29 +1,39 @@
 <template>
-<div v-if="hide" :class="$style.hidden" @click="hide = false">
-	<ImgWithBlurhash style="filter: brightness(0.5);" :hash="image.blurhash" :title="image.comment" :alt="image.comment" :width="image.properties.width" :height="image.properties.height" :force-blurhash="defaultStore.state.enableDataSaverMode"/>
-	<div :class="$style.hiddenText">
-		<div :class="$style.hiddenTextWrapper">
-			<b v-if="image.isSensitive" style="display: block;"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.enableDataSaverMode ? ` (${i18n.ts.image}${image.size ? ' ' + bytes(image.size) : ''})` : '' }}</b>
-			<b v-else style="display: block;"><i class="ti ti-photo"></i> {{ defaultStore.state.enableDataSaverMode && image.size ? bytes(image.size) : i18n.ts.image }}</b>
-			<span style="display: block;">{{ i18n.ts.clickToShow }}</span>
-		</div>
-	</div>
-</div>
-<div v-else :class="$style.visible" :style="darkMode ? '--c: rgb(255 255 255 / 2%);' : '--c: rgb(0 0 0 / 2%);'">
+<div :class="hide ? $style.hidden : $style.visible" :style="darkMode ? '--c: rgb(255 255 255 / 2%);' : '--c: rgb(0 0 0 / 2%);'" @click="onclick">
 	<a
 		:class="$style.imageContainer"
 		:href="image.url"
 		:title="image.name"
 	>
-		<ImgWithBlurhash :hash="image.blurhash" :src="url" :alt="image.comment || image.name" :title="image.comment || image.name" :width="image.properties.width" :height="image.properties.height" :cover="false"/>
+		<ImgWithBlurhash
+			:hash="image.blurhash"
+			:src="(defaultStore.state.enableDataSaverMode && hide) ? null : url"
+			:forceBlurhash="hide"
+			:cover="hide"
+			:alt="image.comment || image.name"
+			:title="image.comment || image.name"
+			:width="image.properties.width"
+			:height="image.properties.height"
+			:style="hide ? 'filter: brightness(0.5);' : null"
+		/>
 	</a>
-	<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);">NSFW</div>
-	</div>
-	<button v-tooltip="i18n.ts.hide" :class="$style.hide" class="_button" @click="hide = true"><i class="ti ti-eye-off"></i></button>
-	<button :class="$style.menu" class="_button" @click.stop="showMenu"><i class="ti ti-dots"></i></button>
+	<template v-if="hide">
+		<div :class="$style.hiddenText">
+			<div :class="$style.hiddenTextWrapper">
+				<b v-if="image.isSensitive" style="display: block;"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.enableDataSaverMode ? ` (${i18n.ts.image}${image.size ? ' ' + bytes(image.size) : ''})` : '' }}</b>
+				<b v-else style="display: block;"><i class="ti ti-photo"></i> {{ defaultStore.state.enableDataSaverMode && image.size ? bytes(image.size) : i18n.ts.image }}</b>
+				<span style="display: block;">{{ i18n.ts.clickToShow }}</span>
+			</div>
+		</div>
+	</template>
+	<template v-else>
+		<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);">NSFW</div>
+		</div>
+		<button :class="$style.menu" class="_button" @click.stop="showMenu"><i class="ti ti-dots" style="vertical-align: middle;"></i></button>
+	</template>
 </div>
 </template>
 
@@ -53,6 +63,12 @@ const url = $computed(() => (props.raw || defaultStore.state.loadRawImages)
 		: props.image.thumbnailUrl,
 );
 
+function onclick() {
+	if (hide) {
+		hide = false;
+	}
+}
+
 // Plugin:register_note_view_interruptor を使って書き換えられる可能性があるためwatchする
 watch(() => props.image, () => {
 	hide = (defaultStore.state.nsfw === 'force' || defaultStore.state.enableDataSaverMode) ? true : (props.image.isSensitive && defaultStore.state.nsfw !== 'ignore');
@@ -62,9 +78,16 @@ watch(() => props.image, () => {
 });
 
 function showMenu(ev: MouseEvent) {
-	os.popupMenu([...(iAmModerator ? [{
-		text: i18n.ts.markAsSensitive,
+	os.popupMenu([{
+		text: i18n.ts.hide,
 		icon: 'ti ti-eye-off',
+		action: () => {
+			hide = 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 });
 		},
@@ -105,34 +128,20 @@ function showMenu(ev: MouseEvent) {
 	background-size: 16px 16px;
 }
 
-.hide {
-	display: block;
-	position: absolute;
-	border-radius: 6px;
-	background-color: var(--accentedBg);
-	-webkit-backdrop-filter: var(--blur, blur(15px));
-	backdrop-filter: var(--blur, blur(15px));
-	color: var(--accent);
-	font-size: 0.8em;
-	padding: 6px 8px;
-	text-align: center;
-	top: 12px;
-	right: 12px;
-}
-
 .menu {
 	display: block;
 	position: absolute;
-	border-radius: 6px;
+	border-radius: 999px;
 	background-color: rgba(0, 0, 0, 0.3);
 	-webkit-backdrop-filter: var(--blur, blur(15px));
 	backdrop-filter: var(--blur, blur(15px));
 	color: #fff;
 	font-size: 0.8em;
-	padding: 6px 8px;
+	width: 32px;
+	height: 32px;
 	text-align: center;
-	bottom: 12px;
-	right: 12px;
+	bottom: 10px;
+	right: 10px;
 }
 
 .imageContainer {
@@ -149,12 +158,10 @@ function showMenu(ev: MouseEvent) {
 .indicators {
 	display: inline-flex;
 	position: absolute;
-	top: 12px;
-	left: 12px;
-	text-align: center;
+	top: 10px;
+	left: 10px;
 	pointer-events: none;
 	opacity: .5;
-	font-size: 14px;
 	gap: 6px;
 }
 
@@ -165,7 +172,7 @@ function showMenu(ev: MouseEvent) {
 	color: var(--accentLighten);
 	display: inline-block;
 	font-weight: bold;
-	font-size: 12px;
-	padding: 2px 6px;
+	font-size: 0.8em;
+	padding: 2px 5px;
 }
 </style>
diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue
index e456ff3ee..a0a245005 100644
--- a/packages/frontend/src/components/MkMediaList.vue
+++ b/packages/frontend/src/components/MkMediaList.vue
@@ -6,7 +6,11 @@
 			ref="gallery"
 			:class="[
 				$style.medias,
-				count <= 4 ? $style['n' + count] : $style.nMany,
+				count === 1 ? [$style.n1, {
+					[$style.n116_9]: defaultStore.reactiveState.mediaListWithOneImageAppearance.value === '16_9',
+					[$style.n11_1]: defaultStore.reactiveState.mediaListWithOneImageAppearance.value === '1_1',
+					[$style.n12_3]: defaultStore.reactiveState.mediaListWithOneImageAppearance.value === '2_3',
+				}] : count === 2 ? $style.n2 : count === 3 ? $style.n3 : count === 4 ? $style.n4 : $style.nMany,
 			]"
 		>
 			<template v-for="media in mediaList.filter(media => previewable(media))">
@@ -19,7 +23,7 @@
 </template>
 
 <script lang="ts" setup>
-import { onMounted, ref, useCssModule, watch } from 'vue';
+import { onMounted, watch, shallowRef } from 'vue';
 import * as misskey from 'misskey-js';
 import PhotoSwipeLightbox from 'photoswipe/lightbox';
 import PhotoSwipe from 'photoswipe';
@@ -36,13 +40,42 @@ const props = defineProps<{
 	raw?: boolean;
 }>();
 
-const $style = useCssModule();
-
-const gallery = ref<HTMLDivElement>();
+const gallery = shallowRef<HTMLDivElement>();
 const pswpZIndex = os.claimZIndex('middle');
 document.documentElement.style.setProperty('--mk-pswp-root-z-index', pswpZIndex.toString());
 const count = $computed(() => props.mediaList.filter(media => previewable(media)).length);
 
+function calcAspectRatio() {
+	if (!gallery.value) return;
+
+	let img = props.mediaList[0];
+
+	if (props.mediaList.length !== 1 || !(img.properties.width && img.properties.height)) {
+		gallery.value.style.aspectRatio = '';
+		return;
+	}
+
+	// アスペクト比上限設定では、横長の場合は高さを縮小させる
+	const ratioMax = (ratio: number) => `${Math.max(ratio, img.properties.width / img.properties.height).toString()} / 1`;
+
+	switch (defaultStore.state.mediaListWithOneImageAppearance) {
+		case '16_9':
+			gallery.value.style.aspectRatio = ratioMax(16 / 9);
+			break;
+		case '1_1':
+			gallery.value.style.aspectRatio = ratioMax(1);
+			break;
+		case '2_3':
+			gallery.value.style.aspectRatio = ratioMax(2 / 3);
+			break;
+		default:
+			gallery.value.style.aspectRatio = '';
+			break;
+	}
+}
+
+watch([defaultStore.reactiveState.mediaListWithOneImageAppearance, gallery], () => calcAspectRatio());
+
 onMounted(() => {
 	const lightbox = new PhotoSwipeLightbox({
 		dataSource: props.mediaList
@@ -64,7 +97,7 @@ onMounted(() => {
 				return item;
 			}),
 		gallery: gallery.value,
-		mainClass: $style.pswp,
+		mainClass: 'pswp',
 		children: '.image',
 		thumbSelector: '.image',
 		loop: false,
@@ -162,12 +195,37 @@ const previewable = (file: misskey.entities.DriveFile): boolean => {
 	display: grid;
 	grid-gap: 8px;
 
-	// for webkit
 	height: 100%;
+	width: 100%;
 
 	&.n1 {
-		aspect-ratio: 16/9;
 		grid-template-rows: 1fr;
+
+		// default (expand)
+		min-height: 64px;
+		max-height: clamp(
+			64px,
+			50cqh,
+			min(360px, 50vh)
+		);
+
+		&.n116_9 {
+			min-height: none;
+			max-height: none;
+			aspect-ratio: 16 / 9; // fallback
+		}
+
+		&.n11_1{
+			min-height: none;
+			max-height: none;
+			aspect-ratio: 1 / 1; // fallback
+		}
+
+		&.n12_3 {
+			min-height: none;
+			max-height: none;
+			aspect-ratio: 2 / 3; // fallback
+		}
 	}
 
 	&.n2 {
@@ -211,7 +269,7 @@ const previewable = (file: misskey.entities.DriveFile): boolean => {
 	border-radius: 8px;
 }
 
-.pswp {
+:global(.pswp) {
 	--pswp-root-z-index: var(--mk-pswp-root-z-index, 2000700) !important;
 	--pswp-bg: var(--modalBg) !important;
 }
diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue
index a4b76300e..40bae90b5 100644
--- a/packages/frontend/src/components/MkMediaVideo.vue
+++ b/packages/frontend/src/components/MkMediaVideo.vue
@@ -1,26 +1,28 @@
 <template>
-<div v-if="hide" class="icozogqfvdetwohsdglrbswgrejoxbdj" @click="hide = false">
+<div v-if="hide" :class="$style.hidden" @click="hide = false">
 	<!-- 【注意】dataSaverMode が有効になっている際には、hide が false になるまでサムネイルや動画を読み込まないようにすること -->
-	<div>
-		<b v-if="video.isSensitive"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.enableDataSaverMode ? ` (${i18n.ts.video}${video.size ? ' ' + bytes(video.size) : ''})` : '' }}</b>
-		<b v-else><i class="ti ti-movie"></i> {{ defaultStore.state.enableDataSaverMode && video.size ? bytes(video.size) : i18n.ts.video }}</b>
+	<div :class="$style.sensitive">
+		<b v-if="video.isSensitive" style="display: block;"><i class="ti ti-alert-triangle"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.enableDataSaverMode ? ` (${i18n.ts.video}${video.size ? ' ' + bytes(video.size) : ''})` : '' }}</b>
+		<b v-else style="display: block;"><i class="ti ti-movie"></i> {{ defaultStore.state.enableDataSaverMode && video.size ? bytes(video.size) : i18n.ts.video }}</b>
 		<span>{{ i18n.ts.clickToShow }}</span>
 	</div>
 </div>
-<div v-else class="kkjnbbplepmiyuadieoenjgutgcmtsvu">
-	<VuePlyr :options="{ volume: 0.5 }">
-		<video
-			controls
-			:data-poster="video.thumbnailUrl"
+<div v-else :class="$style.visible">
+	<video
+		:class="$style.video"
+		:poster="video.thumbnailUrl"
+		:title="video.comment"
+		:alt="video.comment"
+		preload="none"
+		controls
+		@contextmenu.stop
+	>
+		<source 
+			:src="video.url" 
+			:type="video.type"
 		>
-			<source
-				size="720"
-				:src="video.url" 
-				:type="video.type"
-			/>
-		</video>
-	</VuePlyr>
-	<i class="ti ti-eye-off" @click="hide = true"></i>
+	</video>
+	<i class="ti ti-eye-off" :class="$style.hide" @click="hide = true"></i>
 </div>
 </template>
 
@@ -28,9 +30,7 @@
 import { ref } from 'vue';
 import * as misskey from 'misskey-js';
 import bytes from '@/filters/bytes';
-import VuePlyr from 'vue-plyr';
 import { defaultStore } from '@/store';
-import 'vue-plyr/dist/vue-plyr.css';
 import { i18n } from '@/i18n';
 
 const props = defineProps<{
@@ -40,56 +40,49 @@ const props = defineProps<{
 const hide = ref((defaultStore.state.nsfw === 'force' || defaultStore.state.enableDataSaverMode) ? true : (props.video.isSensitive && defaultStore.state.nsfw !== 'ignore'));
 </script>
 
-<style lang="scss" scoped>
-.kkjnbbplepmiyuadieoenjgutgcmtsvu {
+<style lang="scss" module>
+.visible {
 	position: relative;
-
-	--plyr-color-main: var(--accent);
-
-	> i {
-		display: block;
-		position: absolute;
-		border-radius: 6px;
-		background-color: var(--fg);
-		color: var(--accentLighten);
-		font-size: 14px;
-		opacity: .5;
-		padding: 3px 6px;
-		text-align: center;
-		cursor: pointer;
-		top: 12px;
-		right: 12px;
-	}
-
-	> video {
-		display: flex;
-		justify-content: center;
-		align-items: center;
-
-		font-size: 3.5em;
-		overflow: hidden;
-		background-position: center;
-		background-size: cover;
-		width: 100%;
-		height: 100%;
-	}
 }
 
-.icozogqfvdetwohsdglrbswgrejoxbdj {
+.hide {
+	display: block;
+	position: absolute;
+	border-radius: 6px;
+	background-color: var(--fg);
+	color: var(--accentLighten);
+	font-size: 14px;
+	opacity: .5;
+	padding: 3px 6px;
+	text-align: center;
+	cursor: pointer;
+	top: 12px;
+	right: 12px;
+}
+
+.video {
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	font-size: 3.5em;
+	overflow: hidden;
+	background-position: center;
+	background-size: cover;
+	width: 100%;
+	height: 100%;
+}
+
+.hidden {
 	display: flex;
 	justify-content: center;
 	align-items: center;
 	background: #111;
 	color: #fff;
+}
 
-	> div {
-		display: table-cell;
-		text-align: center;
-		font-size: 12px;
-
-		> b {
-			display: block;
-		}
-	}
+.sensitive {
+	display: table-cell;
+	text-align: center;
+	font-size: 12px;
 }
 </style>
diff --git a/packages/frontend/src/components/MkMention.vue b/packages/frontend/src/components/MkMention.vue
index 481c3710c..bb256c394 100644
--- a/packages/frontend/src/components/MkMention.vue
+++ b/packages/frontend/src/components/MkMention.vue
@@ -2,7 +2,7 @@
 <MkA v-user-preview="canonical" :class="[$style.root, { [$style.isMe]: isMe }]" :to="url" :style="{ background: bgCss }">
 	<img :class="$style.icon" :src="`/avatar/@${username}@${host}`" alt="">
 	<span>
-		<span :class="$style.username">@{{ username }}</span>
+		<span>@{{ username }}</span>
 		<span v-if="(host != localHost) || defaultStore.state.showFullAcct" :class="$style.host">@{{ toUnicode(host) }}</span>
 	</span>
 </MkA>
diff --git a/packages/frontend/src/components/MkMenu.child.vue b/packages/frontend/src/components/MkMenu.child.vue
index e0935efbe..4fedfe701 100644
--- a/packages/frontend/src/components/MkMenu.child.vue
+++ b/packages/frontend/src/components/MkMenu.child.vue
@@ -1,6 +1,6 @@
 <template>
 <div ref="el" :class="$style.root">
-	<MkMenu :items="items" :align="align" :width="width" :as-drawer="false" @close="onChildClosed"/>
+	<MkMenu :items="items" :align="align" :width="width" :asDrawer="false" @close="onChildClosed"/>
 </div>
 </template>
 
diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue
index e513a65a3..7dd6a8c88 100644
--- a/packages/frontend/src/components/MkMenu.vue
+++ b/packages/frontend/src/components/MkMenu.vue
@@ -49,8 +49,8 @@
 			<span>{{ i18n.ts.none }}</span>
 		</span>
 	</div>
-	<div v-if="childMenu" :class="$style.child">
-		<XChild ref="child" :items="childMenu" :target-element="childTarget" :root-element="itemsEl" showing @actioned="childActioned"/>
+	<div v-if="childMenu">
+		<XChild ref="child" :items="childMenu" :targetElement="childTarget" :rootElement="itemsEl" showing @actioned="childActioned"/>
 	</div>
 </div>
 </template>
diff --git a/packages/frontend/src/components/MkModal.vue b/packages/frontend/src/components/MkModal.vue
index 99df9e815..bb5c6c7aa 100644
--- a/packages/frontend/src/components/MkModal.vue
+++ b/packages/frontend/src/components/MkModal.vue
@@ -1,11 +1,31 @@
 <template>
 <Transition
 	:name="transitionName"
-	:enter-active-class="$style['transition_' + transitionName + '_enterActive']"
-	:leave-active-class="$style['transition_' + transitionName + '_leaveActive']"
-	:enter-from-class="$style['transition_' + transitionName + '_enterFrom']"
-	:leave-to-class="$style['transition_' + transitionName + '_leaveTo']"
-	:duration="transitionDuration" appear @after-leave="emit('closed')" @enter="emit('opening')" @after-enter="onOpened"
+	:enterActiveClass="normalizeClass({
+		[$style.transition_modalDrawer_enterActive]: transitionName === 'modal-drawer',
+		[$style.transition_modalPopup_enterActive]: transitionName === 'modal-popup',
+		[$style.transition_modal_enterActive]: transitionName === 'modal',
+		[$style.transition_send_enterActive]: transitionName === 'send',
+	})"
+	:leaveActiveClass="normalizeClass({
+		[$style.transition_modalDrawer_leaveActive]: transitionName === 'modal-drawer',
+		[$style.transition_modalPopup_leaveActive]: transitionName === 'modal-popup',
+		[$style.transition_modal_leaveActive]: transitionName === 'modal',
+		[$style.transition_send_leaveActive]: transitionName === 'send',
+	})"
+	:enterFromClass="normalizeClass({
+		[$style.transition_modalDrawer_enterFrom]: transitionName === 'modal-drawer',
+		[$style.transition_modalPopup_enterFrom]: transitionName === 'modal-popup',
+		[$style.transition_modal_enterFrom]: transitionName === 'modal',
+		[$style.transition_send_enterFrom]: transitionName === 'send',
+	})"
+	:leaveToClass="normalizeClass({
+		[$style.transition_modalDrawer_leaveTo]: transitionName === 'modal-drawer',
+		[$style.transition_modalPopup_leaveTo]: transitionName === 'modal-popup',
+		[$style.transition_modal_leaveTo]: transitionName === 'modal',
+		[$style.transition_send_leaveTo]: transitionName === 'send',
+	})"
+	:duration="transitionDuration" appear @afterLeave="emit('closed')" @enter="emit('opening')" @afterEnter="onOpened"
 >
 	<div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" :class="[$style.root, { [$style.drawer]: type === 'drawer', [$style.dialog]: type === 'dialog', [$style.popup]: type === 'popup' }]" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }">
 		<div data-cy-bg :data-cy-transparent="isEnableBgTransparent" class="_modalBg" :class="[$style.bg, { [$style.bgTransparent]: isEnableBgTransparent }]" :style="{ zIndex }" @click="onBgClick" @mousedown="onBgClick" @contextmenu.prevent.stop="() => {}"></div>
@@ -17,7 +37,7 @@
 </template>
 
 <script lang="ts" setup>
-import { nextTick, onMounted, watch, provide } from 'vue';
+import { nextTick, normalizeClass, onMounted, onUnmounted, provide, watch } from 'vue';
 import * as os from '@/os';
 import { isTouchUsing } from '@/scripts/touch';
 import { defaultStore } from '@/store';
@@ -38,7 +58,7 @@ type ModalTypes = 'popup' | 'dialog' | 'drawer';
 const props = withDefaults(defineProps<{
 	manualShowing?: boolean | null;
 	anchor?: { x: string; y: string; };
-	src?: HTMLElement;
+	src?: HTMLElement | null;
 	preferType?: ModalTypes | 'auto';
 	zPriority?: 'low' | 'middle' | 'high';
 	noOverlap?: boolean;
@@ -264,6 +284,10 @@ const onOpened = () => {
 	}, { passive: true });
 };
 
+const alignObserver = new ResizeObserver((entries, observer) => {
+	align();
+});
+
 onMounted(() => {
 	watch(() => props.src, async () => {
 		if (props.src) {
@@ -278,12 +302,14 @@ onMounted(() => {
 	}, { immediate: true });
 
 	nextTick(() => {
-		new ResizeObserver((entries, observer) => {
-			align();
-		}).observe(content!);
+		alignObserver.observe(content!);
 	});
 });
 
+onUnmounted(() => {
+	alignObserver.disconnect();
+});
+
 defineExpose({
 	close,
 });
@@ -339,8 +365,8 @@ defineExpose({
 	}
 }
 
-.transition_modal-popup_enterActive,
-.transition_modal-popup_leaveActive {
+.transition_modalPopup_enterActive,
+.transition_modalPopup_leaveActive {
 	> .bg {
 		transition: opacity 0.1s !important;
 	}
@@ -350,8 +376,8 @@ defineExpose({
 		transition: opacity 0.1s cubic-bezier(0, 0, 0.2, 1), transform 0.1s cubic-bezier(0, 0, 0.2, 1) !important;
 	}
 }
-.transition_modal-popup_enterFrom,
-.transition_modal-popup_leaveTo {
+.transition_modalPopup_enterFrom,
+.transition_modalPopup_leaveTo {
 	> .bg {
 		opacity: 0;
 	}
@@ -364,7 +390,7 @@ defineExpose({
 	}
 }
 
-.transition_modal-drawer_enterActive {
+.transition_modalDrawer_enterActive {
 	> .bg {
 		transition: opacity 0.2s !important;
 	}
@@ -373,7 +399,7 @@ defineExpose({
 		transition: transform 0.2s cubic-bezier(0,.5,0,1) !important;
 	}
 }
-.transition_modal-drawer_leaveActive {
+.transition_modalDrawer_leaveActive {
 	> .bg {
 		transition: opacity 0.2s !important;
 	}
@@ -382,8 +408,8 @@ defineExpose({
 		transition: transform 0.2s cubic-bezier(0,.5,0,1) !important;
 	}
 }
-.transition_modal-drawer_enterFrom,
-.transition_modal-drawer_leaveTo {
+.transition_modalDrawer_enterFrom,
+.transition_modalDrawer_leaveTo {
 	> .bg {
 		opacity: 0;
 	}
diff --git a/packages/frontend/src/components/MkModalPageWindow.vue b/packages/frontend/src/components/MkModalPageWindow.vue
deleted file mode 100644
index b38865f52..000000000
--- a/packages/frontend/src/components/MkModalPageWindow.vue
+++ /dev/null
@@ -1,182 +0,0 @@
-<template>
-<MkModal ref="modal" @click="$emit('click')" @closed="$emit('closed')">
-	<div ref="rootEl" class="hrmcaedk" :style="{ width: `${width}px`, height: (height ? `min(${height}px, 100%)` : '100%') }">
-		<div class="header" @contextmenu="onContextmenu">
-			<button v-if="history.length > 0" v-tooltip="i18n.ts.goBack" class="_button" @click="back()"><i class="ti ti-arrow-left"></i></button>
-			<span v-else style="display: inline-block; width: 20px"></span>
-			<span v-if="pageMetadata?.value" class="title">
-				<i v-if="pageMetadata?.value.icon" class="icon" :class="pageMetadata?.value.icon"></i>
-				<span>{{ pageMetadata?.value.title }}</span>
-			</span>
-			<button class="_button" @click="$refs.modal.close()"><i class="ti ti-x"></i></button>
-		</div>
-		<div class="body" style="container-type: inline-size;">
-			<MkStickyContainer>
-				<template #header><MkPageHeader v-if="pageMetadata?.value && !pageMetadata?.value.hideHeader" :info="pageMetadata?.value"/></template>
-				<RouterView :router="router"/>
-			</MkStickyContainer>
-		</div>
-	</div>
-</MkModal>
-</template>
-
-<script lang="ts" setup>
-import { ComputedRef, provide } from 'vue';
-import MkModal from '@/components/MkModal.vue';
-import { popout as _popout } from '@/scripts/popout';
-import copyToClipboard from '@/scripts/copy-to-clipboard';
-import { url } from '@/config';
-import * as os from '@/os';
-import { mainRouter, routes } from '@/router';
-import { i18n } from '@/i18n';
-import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata';
-import { Router } from '@/nirax';
-
-const props = defineProps<{
-	initialPath: string;
-}>();
-
-defineEmits<{
-	(ev: 'closed'): void;
-	(ev: 'click'): void;
-}>();
-
-const router = new Router(routes, props.initialPath);
-
-router.addListener('push', ctx => {
-	
-});
-
-let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
-let rootEl = $ref();
-let modal = $shallowRef<InstanceType<typeof MkModal>>();
-let path = $ref(props.initialPath);
-let width = $ref(860);
-let height = $ref(660);
-const history = [];
-
-provide('router', router);
-provideMetadataReceiver((info) => {
-	pageMetadata = info;
-});
-provide('shouldOmitHeaderTitle', true);
-provide('shouldHeaderThin', true);
-
-const pageUrl = $computed(() => url + path);
-const contextmenu = $computed(() => {
-	return [{
-		type: 'label',
-		text: path,
-	}, {
-		icon: 'ti ti-player-eject',
-		text: i18n.ts.showInPage,
-		action: expand,
-	}, {
-		icon: 'ti ti-window-maximize',
-		text: i18n.ts.popout,
-		action: popout,
-	}, null, {
-		icon: 'ti ti-external-link',
-		text: i18n.ts.openInNewTab,
-		action: () => {
-			window.open(pageUrl, '_blank');
-			modal.close();
-		},
-	}, {
-		icon: 'ti ti-link',
-		text: i18n.ts.copyLink,
-		action: () => {
-			copyToClipboard(pageUrl);
-		},
-	}];
-});
-
-function navigate(path, record = true) {
-	if (record) history.push(router.getCurrentPath());
-	router.push(path);
-}
-
-function back() {
-	navigate(history.pop(), false);
-}
-
-function expand() {
-	mainRouter.push(path);
-	modal.close();
-}
-
-function popout() {
-	_popout(path, rootEl);
-	modal.close();
-}
-
-function onContextmenu(ev: MouseEvent) {
-	os.contextMenu(contextmenu, ev);
-}
-</script>
-
-<style lang="scss" scoped>
-.hrmcaedk {
-	margin: auto;
-	overflow: hidden;
-	display: flex;
-	flex-direction: column;
-	contain: content;
-	border-radius: var(--radius);
-
-	--root-margin: 24px;
-
-	@media (max-width: 500px) {
-		--root-margin: 16px;
-	}
-
-	> .header {
-		$height: 52px;
-		$height-narrow: 42px;
-		display: flex;
-		flex-shrink: 0;
-		height: $height;
-		line-height: $height;
-		font-weight: bold;
-		white-space: nowrap;
-		overflow: hidden;
-		text-overflow: ellipsis;
-		background: var(--windowHeader);
-		-webkit-backdrop-filter: var(--blur, blur(15px));
-		backdrop-filter: var(--blur, blur(15px));
-
-		> button {
-			height: $height;
-			width: $height;
-
-			&:hover {
-				color: var(--fgHighlighted);
-			}
-		}
-
-		@media (max-width: 500px) {
-			height: $height-narrow;
-			line-height: $height-narrow;
-			padding-left: 16px;
-
-			> button {
-				height: $height-narrow;
-				width: $height-narrow;
-			}
-		}
-
-		> .title {
-			flex: 1;
-
-			> .icon {
-				margin-right: 0.5em;
-			}
-		}
-	}
-
-	> .body {
-		overflow: auto;
-		background: var(--bg);
-	}
-}
-</style>
diff --git a/packages/frontend/src/components/MkModalWindow.vue b/packages/frontend/src/components/MkModalWindow.vue
index 63c55b904..08569b4d6 100644
--- a/packages/frontend/src/components/MkModalWindow.vue
+++ b/packages/frontend/src/components/MkModalWindow.vue
@@ -1,5 +1,5 @@
 <template>
-<MkModal ref="modal" :prefer-type="'dialog'" @click="onBgClick" @closed="$emit('closed')">
+<MkModal ref="modal" :preferType="'dialog'" @click="onBgClick" @closed="$emit('closed')">
 	<div ref="rootEl" :class="$style.root" :style="{ width: `${width}px`, height: `min(${height}px, 100%)` }" @keydown="onKeydown">
 		<div ref="headerEl" :class="$style.header">
 			<button v-if="withOkButton" :class="$style.headerButton" class="_button" @click="$emit('close')"><i class="ti ti-x"></i></button>
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index d95f8de31..7c9ddadbf 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -44,8 +44,8 @@
 		<div v-if="appearNote.channel" :class="$style.colorBar" :style="{ background: appearNote.channel.color }"></div>
 		<MkAvatar :class="$style.avatar" :user="appearNote.user" link preview/>
 		<div :class="$style.main">
-			<MkNoteHeader :class="$style.header" :note="appearNote" :mini="true"/>
-			<MkInstanceTicker v-if="showTicker" :class="$style.ticker" :instance="appearNote.user.instance"/>
+			<MkNoteHeader :note="appearNote" :mini="true"/>
+			<MkInstanceTicker v-if="showTicker" :instance="appearNote.user.instance"/>
 			<div style="container-type: inline-size;">
 				<p v-if="appearNote.cw != null" :class="$style.cw">
 					<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :i="$i"/>
@@ -55,17 +55,17 @@
 					<div :class="$style.text">
 						<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
 						<MkA v-if="appearNote.replyId" :class="$style.replyIcon" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
-						<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :emoji-urls="appearNote.emojis"/>
+						<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :emojiUrls="appearNote.emojis"/>
 						<div v-if="translating || translation" :class="$style.translation">
 							<MkLoading v-if="translating" mini/>
-							<div v-else :class="$style.translated">
+							<div v-else>
 								<b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b>
-								<Mfm :text="translation.text" :author="appearNote.user" :i="$i" :emoji-urls="appearNote.emojis"/>
+								<Mfm :text="translation.text" :author="appearNote.user" :i="$i" :emojiUrls="appearNote.emojis"/>
 							</div>
 						</div>
 					</div>
-					<div v-if="appearNote.files.length > 0" :class="$style.files">
-						<MkMediaList :media-list="appearNote.files"/>
+					<div v-if="appearNote.files.length > 0">
+						<MkMediaList :mediaList="appearNote.files"/>
 					</div>
 					<MkPoll v-if="appearNote.poll" :note="appearNote" :class="$style.poll"/>
 					<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" :class="$style.urlPreview"/>
@@ -79,7 +79,7 @@
 				</div>
 				<MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA>
 			</div>
-			<MkReactionsViewer :note="appearNote" :max-number="16">
+			<MkReactionsViewer :note="appearNote" :maxNumber="16">
 				<template #more>
 					<button class="_button" :class="$style.reactionDetailsButton" @click="showReactions">
 						{{ i18n.ts.more }}
@@ -205,8 +205,11 @@ const isMyRenote = $i && ($i.id === note.userId);
 const showContent = ref(false);
 const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)) : null;
 const isLong = (appearNote.cw == null && appearNote.text != null && (
+	(appearNote.text.includes('$[x2')) ||
 	(appearNote.text.includes('$[x3')) ||
 	(appearNote.text.includes('$[x4')) ||
+	(appearNote.text.includes('$[scale')) ||
+	(appearNote.text.includes('$[position')) ||
 	(appearNote.text.split('\n').length > 9) ||
 	(appearNote.text.length > 500) ||
 	(appearNote.files.length >= 5) ||
@@ -274,7 +277,7 @@ function renote(viaKeyboard = false) {
 					const y = rect.top + (el.offsetHeight / 2);
 					os.popup(MkRippleEffect, { x, y }, {}, 'end');
 				}
-					
+
 				os.api('notes/create', {
 					renoteId: appearNote.id,
 					channelId: appearNote.channelId,
@@ -305,7 +308,7 @@ function renote(viaKeyboard = false) {
 				const y = rect.top + (el.offsetHeight / 2);
 				os.popup(MkRippleEffect, { x, y }, {}, 'end');
 			}
-				
+
 			os.api('notes/create', {
 				renoteId: appearNote.id,
 			}).then(() => {
@@ -379,6 +382,8 @@ function undoReact(note): void {
 function onContextmenu(ev: MouseEvent): void {
 	const isLink = (el: HTMLElement) => {
 		if (el.tagName === 'A') return true;
+		// 再生速度の選択などのために、Audio要素のコンテキストメニューはブラウザデフォルトとする。
+		if (el.tagName === 'AUDIO') return true;
 		if (el.parentElement) {
 			return isLink(el.parentElement);
 		}
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index 0d6d329d9..a65039277 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -4,25 +4,25 @@
 	v-show="!isDeleted"
 	ref="el"
 	v-hotkey="keymap"
-	class="lxwezrsl"
-	:tabindex="!isDeleted ? '-1' : null"
-	:class="{ renote: isRenote }"
+	:class="$style.root"
 >
-	<MkNoteSub v-for="note in conversation" :key="note.id" class="reply-to-more" :note="note"/>
-	<MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to"/>
-	<div v-if="isRenote" class="renote">
-		<MkAvatar class="avatar" :user="note.user" link preview/>
-		<i class="ti ti-repeat"></i>
-		<I18n :src="i18n.ts.renotedBy" tag="span">
-			<template #user>
-				<MkA v-user-preview="note.userId" class="name" :to="userPage(note.user)">
-					<MkUserName :user="note.user"/>
-				</MkA>
-			</template>
-		</I18n>
-		<div class="info">
-			<button ref="renoteTime" class="_button time" @click="showRenoteMenu()">
-				<i v-if="isMyRenote" class="ti ti-dots dropdownIcon"></i>
+	<MkNoteSub v-for="note in conversation" :key="note.id" :class="$style.replyToMore" :note="note"/>
+	<MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" :class="$style.replyTo"/>
+	<div v-if="isRenote" :class="$style.renote">
+		<MkAvatar :class="$style.renoteAvatar" :user="note.user" link preview/>
+		<i class="ti ti-repeat" style="margin-right: 4px;"></i>
+		<span :class="$style.renoteText">
+			<I18n :src="i18n.ts.renotedBy" tag="span">
+				<template #user>
+					<MkA v-user-preview="note.userId" :class="$style.renoteName" :to="userPage(note.user)">
+						<MkUserName :user="note.user"/>
+					</MkA>
+				</template>
+			</I18n>
+		</span>
+		<div :class="$style.renoteInfo">
+			<button ref="renoteTime" class="_button" :class="$style.renoteTime" @click="showRenoteMenu()">
+				<i v-if="isMyRenote" class="ti ti-dots" style="margin-right: 4px;"></i>
 				<MkTime :time="note.createdAt"/>
 			</button>
 			<span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[note.visibility]">
@@ -33,16 +33,16 @@
 			<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="article" @contextmenu.stop="onContextmenu">
-		<header class="header">
-			<MkAvatar class="avatar" :user="appearNote.user" indicator link preview/>
-			<div class="body">
-				<div class="top">
-					<MkA v-user-preview="appearNote.user.id" class="name" :to="userPage(appearNote.user)">
+	<article :class="$style.note" @contextmenu.stop="onContextmenu">
+		<header :class="$style.noteHeader">
+			<MkAvatar :class="$style.noteHeaderAvatar" :user="appearNote.user" indicator link preview/>
+			<div :class="$style.noteHeaderBody">
+				<div>
+					<MkA v-user-preview="appearNote.user.id" :class="$style.noteHeaderName" :to="userPage(appearNote.user)">
 						<MkUserName :nowrap="false" :user="appearNote.user"/>
 					</MkA>
-					<span v-if="appearNote.user.isBot" class="is-bot">bot</span>
-					<div class="info">
+					<span v-if="appearNote.user.isBot" :class="$style.isBot">bot</span>
+					<div :class="$style.noteHeaderInfo">
 						<span v-if="appearNote.visibility !== 'public'" style="margin-left: 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>
@@ -51,84 +51,81 @@
 						<span v-if="appearNote.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ti ti-rocket-off"></i></span>
 					</div>
 				</div>
-				<div class="username"><MkAcct :user="appearNote.user"/></div>
-				<MkInstanceTicker v-if="showTicker" class="ticker" :instance="appearNote.user.instance"/>
+				<div :class="$style.noteHeaderUsername"><MkAcct :user="appearNote.user"/></div>
+				<MkInstanceTicker v-if="showTicker" :instance="appearNote.user.instance"/>
 			</div>
 		</header>
-		<div class="main">
-			<div class="body">
-				<p v-if="appearNote.cw != null" class="cw">
-					<Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$i"/>
-					<MkCwButton v-model="showContent" :note="appearNote"/>
-				</p>
-				<div v-show="appearNote.cw == null || showContent" class="content">
-					<div class="text">
-						<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
-						<MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
-						<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :emoji-urls="appearNote.emojis"/>
-						<a v-if="appearNote.renote != null" class="rp">RN:</a>
-						<div v-if="translating || translation" class="translation">
-							<MkLoading v-if="translating" mini/>
-							<div v-else class="translated">
-								<b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b>
-								<Mfm :text="translation.text" :author="appearNote.user" :i="$i" :emoji-urls="appearNote.emojis"/>
-							</div>
-						</div>
+		<div :class="$style.noteContent">
+			<p v-if="appearNote.cw != null" :class="$style.cw">
+				<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :i="$i"/>
+				<MkCwButton v-model="showContent" :note="appearNote"/>
+			</p>
+			<div v-show="appearNote.cw == null || showContent">
+				<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
+				<MkA v-if="appearNote.replyId" :class="$style.noteReplyTarget" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
+				<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i" :emojiUrls="appearNote.emojis"/>
+				<a v-if="appearNote.renote != null" :class="$style.rn">RN:</a>
+				<div v-if="translating || translation" :class="$style.translation">
+					<MkLoading v-if="translating" mini/>
+					<div v-else>
+						<b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b>
+						<Mfm :text="translation.text" :author="appearNote.user" :i="$i" :emojiUrls="appearNote.emojis"/>
 					</div>
-					<div v-if="appearNote.files.length > 0" class="files">
-						<MkMediaList :media-list="appearNote.files"/>
-					</div>
-					<MkPoll v-if="appearNote.poll" ref="pollViewer" :note="appearNote" class="poll"/>
-					<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="true" class="url-preview"/>
-					<div v-if="appearNote.renote" class="renote"><MkNoteSimple :note="appearNote.renote" class="note"/></div>
 				</div>
-				<MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA>
+				<div v-if="appearNote.files.length > 0">
+					<MkMediaList :mediaList="appearNote.files"/>
+				</div>
+				<MkPoll v-if="appearNote.poll" ref="pollViewer" :note="appearNote" :class="$style.poll"/>
+				<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="true" style="margin-top: 6px;"/>
+				<div v-if="appearNote.renote" :class="$style.quote"><MkNoteSimple :note="appearNote.renote" :class="$style.quoteNote"/></div>
 			</div>
-			<footer class="footer">
-				<div class="info">
-					<MkA class="created-at" :to="notePage(appearNote)">
-						<MkTime :time="appearNote.createdAt" mode="detail"/>
-					</MkA>
-				</div>
-				<MkReactionsViewer ref="reactionsViewer" :note="appearNote"/>
-				<button class="button _button" @click="reply()">
-					<i class="ti ti-arrow-back-up"></i>
-					<p v-if="appearNote.repliesCount > 0" class="count">{{ appearNote.repliesCount }}</p>
-				</button>
-				<button
-					v-if="canRenote"
-					ref="renoteButton"
-					class="button _button"
-					@mousedown="renote()"
-				>
-					<i class="ti ti-repeat"></i>
-					<p v-if="appearNote.renoteCount > 0" class="count">{{ appearNote.renoteCount }}</p>
-				</button>
-				<button v-else class="button _button" disabled>
-					<i class="ti ti-ban"></i>
-				</button>
-				<button v-if="appearNote.myReaction == null" ref="reactButton" class="button _button" @mousedown="react()">
-					<i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
-					<i v-else class="ti ti-plus"></i>
-				</button>
-				<button v-if="appearNote.myReaction != null" ref="reactButton" class="button _button reacted" @click="undoReact(appearNote)">
-					<i class="ti ti-minus"></i>
-				</button>
-				<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" class="button _button" @mousedown="clip()">
-					<i class="ti ti-paperclip"></i>
-				</button>
-				<button ref="menuButton" class="button _button" @mousedown="menu()">
-					<i class="ti ti-dots"></i>
-				</button>
-			</footer>
+			<MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA>
 		</div>
+		<footer>
+			<div :class="$style.noteFooterInfo">
+				<MkA :to="notePage(appearNote)">
+					<MkTime :time="appearNote.createdAt" mode="detail"/>
+				</MkA>
+			</div>
+			<MkReactionsViewer ref="reactionsViewer" :note="appearNote"/>
+			<button class="_button" :class="$style.noteFooterButton" @click="reply()">
+				<i class="ti ti-arrow-back-up"></i>
+				<p v-if="appearNote.repliesCount > 0" :class="$style.noteFooterButtonCount">{{ appearNote.repliesCount }}</p>
+			</button>
+			<button
+				v-if="canRenote"
+				ref="renoteButton"
+				class="_button"
+				:class="$style.noteFooterButton"
+				@mousedown="renote()"
+			>
+				<i class="ti ti-repeat"></i>
+				<p v-if="appearNote.renoteCount > 0" :class="$style.noteFooterButtonCount">{{ appearNote.renoteCount }}</p>
+			</button>
+			<button v-else class="_button" :class="$style.noteFooterButton" disabled>
+				<i class="ti ti-ban"></i>
+			</button>
+			<button v-if="appearNote.myReaction == null" ref="reactButton" :class="$style.noteFooterButton" class="_button" @mousedown="react()">
+				<i v-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i>
+				<i v-else class="ti ti-plus"></i>
+			</button>
+			<button v-if="appearNote.myReaction != null" ref="reactButton" class="_button" :class="[$style.noteFooterButton, $style.reacted]" @click="undoReact(appearNote)">
+				<i class="ti ti-minus"></i>
+			</button>
+			<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @mousedown="clip()">
+				<i class="ti ti-paperclip"></i>
+			</button>
+			<button ref="menuButton" class="_button" :class="$style.noteFooterButton" @mousedown="menu()">
+				<i class="ti ti-dots"></i>
+			</button>
+		</footer>
 	</article>
-	<MkNoteSub v-for="note in replies" :key="note.id" :note="note" class="reply" :detail="true"/>
+	<MkNoteSub v-for="note in replies" :key="note.id" :note="note" :class="$style.reply" :detail="true"/>
 </div>
-<div v-else class="_panel muted" @click="muted = false">
+<div v-else class="_panel" :class="$style.muted" @click="muted = false">
 	<I18n :src="i18n.ts.userSaysSomething" tag="small">
 		<template #name>
-			<MkA v-user-preview="appearNote.userId" class="name" :to="userPage(appearNote.user)">
+			<MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)">
 				<MkUserName :user="appearNote.user"/>
 			</MkA>
 		</template>
@@ -438,318 +435,249 @@ if (appearNote.replyId) {
 }
 </script>
 
-<style lang="scss" scoped>
-.lxwezrsl {
+<style lang="scss" module>
+.root {
 	position: relative;
 	transition: box-shadow 0.1s ease;
 	overflow: clip;
 	contain: content;
+}
 
-	&:focus-visible {
-		outline: none;
+.replyTo {
+	opacity: 0.7;
+	padding-bottom: 0;
+}
 
-		&: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 1px var(--focus);
-			border-radius: var(--radius);
-			box-sizing: border-box;
-		}
-	}
+.replyToMore {
+	opacity: 0.7;
+}
 
-	&:hover > .article > .main > .footer > .button {
+.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: 32px;
+	font-size: 1.2em;
+
+	&:hover > .main > .footer > .button {
 		opacity: 1;
 	}
-
-	> .reply-to {
-		opacity: 0.7;
-		padding-bottom: 0;
-	}
-
-	> .reply-to-more {
-		opacity: 0.7;
-	}
-
-	> .renote {
-		display: flex;
-		align-items: center;
-		padding: 16px 32px 8px 32px;
-		line-height: 28px;
-		white-space: pre;
-		color: var(--renote);
-
-		> .avatar {
-			flex-shrink: 0;
-			display: inline-block;
-			width: 28px;
-			height: 28px;
-			margin: 0 8px 0 0;
-			border-radius: 6px;
-		}
-
-		> i {
-			margin-right: 4px;
-		}
-
-		> span {
-			overflow: hidden;
-			flex-shrink: 1;
-			text-overflow: ellipsis;
-			white-space: nowrap;
-
-			> .name {
-				font-weight: bold;
-			}
-		}
-
-		> .info {
-			margin-left: auto;
-			font-size: 0.9em;
-
-			> .time {
-				flex-shrink: 0;
-				color: inherit;
-
-				> .dropdownIcon {
-					margin-right: 4px;
-				}
-			}
-		}
-	}
-
-	> .renote + .article {
-		padding-top: 8px;
-	}
-
-	> .article {
-		padding: 32px;
-		font-size: 1.2em;
-
-		> .header {
-			display: flex;
-			position: relative;
-			margin-bottom: 16px;
-			align-items: center;
-
-			> .avatar {
-				display: block;
-				flex-shrink: 0;
-				width: 58px;
-				height: 58px;
-			}
-
-			> .body {
-				flex: 1;
-				display: flex;
-				flex-direction: column;
-				justify-content: center;
-				padding-left: 16px;
-				font-size: 0.95em;
-
-				> .top {
-					> .name {
-						font-weight: bold;
-						line-height: 1.3;
-					}
-
-					> .is-bot {
-						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;
-					}
-
-					> .info {
-						float: right;
-					}
-				}
-
-				> .username {
-					margin-bottom: 2px;
-					line-height: 1.3;
-					word-wrap: anywhere;
-				}
-			}
-		}
-
-		> .main {
-			> .body {
-				container-type: inline-size;
-
-				> .cw {
-					cursor: default;
-					display: block;
-					margin: 0;
-					padding: 0;
-					overflow-wrap: break-word;
-
-					> .text {
-						margin-right: 8px;
-					}
-				}
-
-				> .content {
-					> .text {
-						overflow-wrap: break-word;
-
-						> .reply {
-							color: var(--accent);
-							margin-right: 0.5em;
-						}
-
-						> .rp {
-							margin-left: 4px;
-							font-style: oblique;
-							color: var(--renote);
-						}
-
-						> .translation {
-							border: solid 0.5px var(--divider);
-							border-radius: var(--radius);
-							padding: 12px;
-							margin-top: 8px;
-						}
-					}
-
-					> .url-preview {
-						margin-top: 8px;
-					}
-
-					> .poll {
-						font-size: 80%;
-					}
-
-					> .renote {
-						padding: 8px 0;
-
-						> .note {
-							padding: 16px;
-							border: dashed 1px var(--renote);
-							border-radius: 8px;
-						}
-					}
-				}
-
-				> .channel {
-					opacity: 0.7;
-					font-size: 80%;
-				}
-			}
-
-			> .footer {
-				> .info {
-					margin: 16px 0;
-					opacity: 0.7;
-					font-size: 0.9em;
-				}
-
-				> .button {
-					margin: 0;
-					padding: 8px;
-					opacity: 0.7;
-
-					&:not(:last-child) {
-						margin-right: 28px;
-					}
-
-					&:hover {
-						color: var(--fgHighlighted);
-					}
-
-					> .count {
-						display: inline;
-						margin: 0 0 0 8px;
-						opacity: 0.7;
-					}
-
-					&.reacted {
-						color: var(--accent);
-					}
-				}
-			}
-		}
-	}
-
-	> .reply {
-		border-top: solid 0.5px var(--divider);
-	}
+}
+
+.noteHeader {
+	display: flex;
+	position: relative;
+	margin-bottom: 16px;
+	align-items: center;
+}
+
+.noteHeaderAvatar {
+	display: block;
+	flex-shrink: 0;
+	width: 58px;
+	height: 58px;
+}
+
+.noteHeaderBody {
+	flex: 1;
+	display: flex;
+	flex-direction: column;
+	justify-content: center;
+	padding-left: 16px;
+	font-size: 0.95em;
+}
+
+.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 {
+	float: right;
+}
+
+.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);
+}
+
+.translation {
+	border: solid 0.5px var(--divider);
+	border-radius: var(--radius);
+	padding: 12px;
+	margin-top: 8px;
+}
+
+.poll {
+	font-size: 80%;
+}
+
+.quote {
+	padding: 8px 0;
+}
+
+.quoteNote {
+	padding: 16px;
+	border: dashed 1px var(--renote);
+	border-radius: 8px;
+}
+
+.channel {
+	opacity: 0.7;
+	font-size: 80%;
+}
+
+.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);
+	}
+}
+
+.noteFooterButtonCount {
+	display: inline;
+	margin: 0 0 0 8px;
+	opacity: 0.7;
+
+	&.reacted {
+		color: var(--accent);
+	}
+}
+
+.reply {
+	border-top: solid 0.5px var(--divider);
 }
 
 @container (max-width: 500px) {
-	.lxwezrsl {
+	.root {
 		font-size: 0.9em;
 	}
 }
 
 @container (max-width: 450px) {
-	.lxwezrsl {
-		> .renote {
-			padding: 8px 16px 0 16px;
-		}
+	.renote {
+		padding: 8px 16px 0 16px;
+	}
 
-		> .article {
-			padding: 16px;
+	.note {
+		padding: 16px;
+	}
 
-			> .header {
-				> .avatar {
-					width: 50px;
-					height: 50px;
-				}
-			}
-		}
+	.noteHeaderAvatar {
+		width: 50px;
+		height: 50px;
 	}
 }
 
 @container (max-width: 350px) {
-	.lxwezrsl {
-		> .article {
-			> .main {
-				> .footer {
-					> .button {
-						&:not(:last-child) {
-							margin-right: 18px;
-						}
-					}
-				}
-			}
+	.noteFooterButton {
+		&:not(:last-child) {
+			margin-right: 18px;
 		}
 	}
 }
 
 @container (max-width: 300px) {
-	.lxwezrsl {
+	.root {
 		font-size: 0.825em;
+	}
 
-		> .article {
-			> .header {
-				> .avatar {
-					width: 50px;
-					height: 50px;
-				}
-			}
+	.noteHeaderAvatar {
+		width: 50px;
+		height: 50px;
+	}
 
-			> .main {
-				> .footer {
-					> .button {
-						&:not(:last-child) {
-							margin-right: 12px;
-						}
-					}
-				}
-			}
+	.noteFooterButton {
+		&:not(:last-child) {
+			margin-right: 12px;
 		}
 	}
 }
diff --git a/packages/frontend/src/components/MkNotePreview.vue b/packages/frontend/src/components/MkNotePreview.vue
index 6b55c2786..6786f8b25 100644
--- a/packages/frontend/src/components/MkNotePreview.vue
+++ b/packages/frontend/src/components/MkNotePreview.vue
@@ -6,7 +6,7 @@
 			<MkUserName :user="$i" :nowrap="true"/>
 		</div>
 		<div>
-			<div :class="$style.content">
+			<div>
 				<Mfm :text="text.trim()" :author="$i" :i="$i"/>
 			</div>
 		</div>
diff --git a/packages/frontend/src/components/MkNoteSimple.vue b/packages/frontend/src/components/MkNoteSimple.vue
index bd27a43b6..21be1454a 100644
--- a/packages/frontend/src/components/MkNoteSimple.vue
+++ b/packages/frontend/src/components/MkNoteSimple.vue
@@ -5,7 +5,7 @@
 		<MkNoteHeader :class="$style.header" :note="note" :mini="true"/>
 		<div>
 			<p v-if="note.cw != null" :class="$style.cw">
-				<Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :i="$i" :emoji-urls="note.emojis"/>
+				<Mfm v-if="note.cw != ''" style="margin-right: 8px;" :text="note.cw" :author="note.user" :i="$i" :emojiUrls="note.emojis"/>
 				<MkCwButton v-model="showContent" :note="note"/>
 			</p>
 			<div v-show="note.cw == null || showContent">
diff --git a/packages/frontend/src/components/MkNotes.vue b/packages/frontend/src/components/MkNotes.vue
index a4e949c89..9cc2b7a96 100644
--- a/packages/frontend/src/components/MkNotes.vue
+++ b/packages/frontend/src/components/MkNotes.vue
@@ -15,7 +15,7 @@
 				:items="notes"
 				:direction="pagination.reversed ? 'up' : 'down'"
 				:reversed="pagination.reversed"
-				:no-gap="noGap"
+				:noGap="noGap"
 				:ad="true"
 				:class="$style.notes"
 			>
diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue
index efae687e6..d25332b10 100644
--- a/packages/frontend/src/components/MkNotification.vue
+++ b/packages/frontend/src/components/MkNotification.vue
@@ -5,7 +5,19 @@
 		<MkAvatar v-else-if="notification.type === 'achievementEarned'" :class="$style.icon" :user="$i" link preview/>
 		<MkAvatar v-else-if="notification.user" :class="$style.icon" :user="notification.user" link preview/>
 		<img v-else-if="notification.icon" :class="$style.icon" :src="notification.icon" alt=""/>
-		<div :class="[$style.subIcon, $style['t_' + notification.type]]">
+		<div
+			:class="[$style.subIcon, {
+				[$style.t_follow]: notification.type === 'follow',
+				[$style.t_followRequestAccepted]: notification.type === 'followRequestAccepted',
+				[$style.t_receiveFollowRequest]: notification.type === 'receiveFollowRequest',
+				[$style.t_renote]: notification.type === 'renote',
+				[$style.t_reply]: notification.type === 'reply',
+				[$style.t_mention]: notification.type === 'mention',
+				[$style.t_quote]: notification.type === 'quote',
+				[$style.t_pollEnded]: notification.type === 'pollEnded',
+				[$style.t_achievementEarned]: notification.type === 'achievementEarned',
+			}]"
+		>
 			<i v-if="notification.type === 'follow'" class="ti ti-plus"></i>
 			<i v-else-if="notification.type === 'receiveFollowRequest'" class="ti ti-clock"></i>
 			<i v-else-if="notification.type === 'followRequestAccepted'" class="ti ti-check"></i>
@@ -20,8 +32,8 @@
 				v-else-if="notification.type === 'reaction'"
 				ref="reactionRef"
 				:reaction="notification.reaction ? notification.reaction.replace(/^:(\w+):$/, ':$1@.:') : notification.reaction"
-				:custom-emojis="notification.note.emojis"
-				:no-style="true"
+				:customEmojis="notification.note.emojis"
+				:noStyle="true"
 				style="width: 100%; height: 100%;"
 			/>
 		</div>
@@ -34,7 +46,7 @@
 			<span v-else>{{ notification.header }}</span>
 			<MkTime v-if="withTime" :time="notification.createdAt" :class="$style.headerTime"/>
 		</header>
-		<div :class="$style.content">
+		<div>
 			<MkA v-if="notification.type === 'reaction'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
 				<i class="ti ti-quote" :class="$style.quote"></i>
 				<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="true" :author="notification.note.user"/>
@@ -243,9 +255,6 @@ useTooltip(reactionRef, (showing) => {
 	font-size: 0.9em;
 }
 
-.content {
-}
-
 .text {
 	display: flex;
 	width: 100%;
diff --git a/packages/frontend/src/components/MkNotificationSettingWindow.vue b/packages/frontend/src/components/MkNotificationSettingWindow.vue
index f6d0e5681..598d3a055 100644
--- a/packages/frontend/src/components/MkNotificationSettingWindow.vue
+++ b/packages/frontend/src/components/MkNotificationSettingWindow.vue
@@ -3,15 +3,15 @@
 	ref="dialog"
 	:width="400"
 	:height="450"
-	:with-ok-button="true"
-	:ok-button-disabled="false"
+	:withOkButton="true"
+	:okButtonDisabled="false"
 	@ok="ok()"
 	@close="dialog?.close()"
 	@closed="emit('closed')"
 >
 	<template #header>{{ i18n.ts.notificationSetting }}</template>
 
-	<MkSpacer :margin-min="20" :margin-max="28">
+	<MkSpacer :marginMin="20" :marginMax="28">
 		<div class="_gaps_m">
 			<template v-if="showGlobalToggle">
 				<MkSwitch v-model="useGlobalSetting">
diff --git a/packages/frontend/src/components/MkNotifications.vue b/packages/frontend/src/components/MkNotifications.vue
index 1aea95fe0..70224bffa 100644
--- a/packages/frontend/src/components/MkNotifications.vue
+++ b/packages/frontend/src/components/MkNotifications.vue
@@ -8,9 +8,9 @@
 	</template>
 
 	<template #default="{ items: notifications }">
-		<MkDateSeparatedList v-slot="{ item: notification }" :class="$style.list" :items="notifications" :no-gap="true">
+		<MkDateSeparatedList v-slot="{ item: notification }" :class="$style.list" :items="notifications" :noGap="true">
 			<MkNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id" :note="notification.note"/>
-			<XNotification v-else :key="notification.id" :notification="notification" :with-time="true" :full="true" class="_panel notification"/>
+			<XNotification v-else :key="notification.id" :notification="notification" :withTime="true" :full="true" class="_panel notification"/>
 		</MkDateSeparatedList>
 	</template>
 </MkPagination>
@@ -22,7 +22,7 @@ import MkPagination, { Paging } from '@/components/MkPagination.vue';
 import XNotification from '@/components/MkNotification.vue';
 import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue';
 import MkNote from '@/components/MkNote.vue';
-import { stream } from '@/stream';
+import { useStream } from '@/stream';
 import { $i } from '@/account';
 import { i18n } from '@/i18n';
 import { notificationTypes } from '@/const';
@@ -45,7 +45,7 @@ const pagination: Paging = {
 const onNotification = (notification) => {
 	const isMuted = props.includeTypes ? !props.includeTypes.includes(notification.type) : $i.mutingNotificationTypes.includes(notification.type);
 	if (isMuted || document.visibilityState === 'visible') {
-		stream.send('readNotification');
+		useStream().send('readNotification');
 	}
 
 	if (!isMuted) {
@@ -56,7 +56,7 @@ const onNotification = (notification) => {
 let connection;
 
 onMounted(() => {
-	connection = stream.useChannel('main');
+	connection = useStream().useChannel('main');
 	connection.on('notification', onNotification);
 });
 
diff --git a/packages/frontend/src/components/MkObjectView.value.vue b/packages/frontend/src/components/MkObjectView.value.vue
index e7fc73bce..d48e7886e 100644
--- a/packages/frontend/src/components/MkObjectView.value.vue
+++ b/packages/frontend/src/components/MkObjectView.value.vue
@@ -28,54 +28,38 @@
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent, reactive } from 'vue';
+<script lang="ts" setup>
+import { reactive } from 'vue';
 import number from '@/filters/number';
+import XValue from '@/components/MkObjectView.value.vue';
 
-export default defineComponent({
-	name: 'XValue',
+const props = defineProps<{
+	value: any;
+}>();
 
-	props: {
-		value: {
-			required: true,
-		},
-	},
+const collapsed = reactive({});
 
-	setup(props) {
-		const collapsed = reactive({});
+if (isObject(props.value)) {
+	for (const key in props.value) {
+		collapsed[key] = collapsable(props.value[key]);
+	}
+}
 
-		if (isObject(props.value)) {
-			for (const key in props.value) {
-				collapsed[key] = collapsable(props.value[key]);
-			}
-		}
+function isObject(v): boolean {
+	return typeof v === 'object' && !Array.isArray(v) && v !== null;
+}
 
-		function isObject(v): boolean {
-			return typeof v === 'object' && !Array.isArray(v) && v !== null;
-		}
+function isArray(v): boolean {
+	return Array.isArray(v);
+}
 
-		function isArray(v): boolean {
-			return Array.isArray(v);
-		}
+function isEmpty(v): boolean {
+	return (isArray(v) && v.length === 0) || (isObject(v) && Object.keys(v).length === 0);
+}
 
-		function isEmpty(v): boolean {
-			return (isArray(v) && v.length === 0) || (isObject(v) && Object.keys(v).length === 0);
-		}
-
-		function collapsable(v): boolean {
-			return (isObject(v) || isArray(v)) && !isEmpty(v);
-		}
-
-		return {
-			number,
-			collapsed,
-			isObject,
-			isArray,
-			isEmpty,
-			collapsable,
-		};
-	},
-});
+function collapsable(v): boolean {
+	return (isObject(v) || isArray(v)) && !isEmpty(v);
+}
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/components/MkObjectView.vue b/packages/frontend/src/components/MkObjectView.vue
index 55578a37f..8b1ed7414 100644
--- a/packages/frontend/src/components/MkObjectView.vue
+++ b/packages/frontend/src/components/MkObjectView.vue
@@ -1,5 +1,5 @@
 <template>
-<div class="zhyxdalp">
+<div>
 	<XValue :value="value" :collapsed="false"/>
 </div>
 </template>
@@ -12,9 +12,3 @@ const props = defineProps<{
 	value: Record<string, unknown>;
 }>();
 </script>
-
-<style lang="scss" scoped>
-.zhyxdalp {
-
-}
-</style>
diff --git a/packages/frontend/src/components/MkOmit.vue b/packages/frontend/src/components/MkOmit.vue
index e2d68d12c..668f9ff5a 100644
--- a/packages/frontend/src/components/MkOmit.vue
+++ b/packages/frontend/src/components/MkOmit.vue
@@ -8,7 +8,7 @@
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { onMounted, onUnmounted } from 'vue';
 import { i18n } from '@/i18n';
 
 const props = withDefaults(defineProps<{
@@ -21,16 +21,22 @@ let content = $shallowRef<HTMLElement>();
 let omitted = $ref(false);
 let ignoreOmit = $ref(false);
 
-onMounted(() => {
-	const calcOmit = () => {
-		if (omitted || ignoreOmit) return;
-		omitted = content.offsetHeight > props.maxHeight;
-	};
+const calcOmit = () => {
+	if (omitted || ignoreOmit) return;
+	omitted = content.offsetHeight > props.maxHeight;
+};
 
+const omitObserver = new ResizeObserver((entries, observer) => {
 	calcOmit();
-	new ResizeObserver((entries, observer) => {
-		calcOmit();
-	}).observe(content);
+});
+
+onMounted(() => {
+	calcOmit();
+	omitObserver.observe(content);
+});
+
+onUnmounted(() => {
+	omitObserver.disconnect();
 });
 </script>
 
diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue
index 02ce58451..709b5a52d 100644
--- a/packages/frontend/src/components/MkPageWindow.vue
+++ b/packages/frontend/src/components/MkPageWindow.vue
@@ -1,23 +1,23 @@
 <template>
 <MkWindow
 	ref="windowEl"
-	:initial-width="500"
-	:initial-height="500"
-	:can-resize="true"
-	:close-button="true"
-	:buttons-left="buttonsLeft"
-	:buttons-right="buttonsRight"
+	:initialWidth="500"
+	:initialHeight="500"
+	:canResize="true"
+	:closeButton="true"
+	:buttonsLeft="buttonsLeft"
+	:buttonsRight="buttonsRight"
 	:contextmenu="contextmenu"
 	@closed="$emit('closed')"
 >
 	<template #header>
 		<template v-if="pageMetadata?.value">
-			<i v-if="pageMetadata.value.icon" class="icon" :class="pageMetadata.value.icon" style="margin-right: 0.5em;"></i>
+			<i v-if="pageMetadata.value.icon" :class="pageMetadata.value.icon" style="margin-right: 0.5em;"></i>
 			<span>{{ pageMetadata.value.title }}</span>
 		</template>
 	</template>
 
-	<div :class="$style.root" :style="{ background: pageMetadata?.value?.bg }" style="container-type: inline-size;">
+	<div :class="$style.root" style="container-type: inline-size;">
 		<RouterView :key="reloadCount" :router="router"/>
 	</div>
 </MkWindow>
diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue
index cd8af560e..740094b11 100644
--- a/packages/frontend/src/components/MkPagination.vue
+++ b/packages/frontend/src/components/MkPagination.vue
@@ -1,9 +1,9 @@
 <template>
 <Transition
-	:enter-active-class="defaultStore.state.animation ? $style.transition_fade_enterActive : ''"
-	:leave-active-class="defaultStore.state.animation ? $style.transition_fade_leaveActive : ''"
-	:enter-from-class="defaultStore.state.animation ? $style.transition_fade_enterFrom : ''"
-	:leave-to-class="defaultStore.state.animation ? $style.transition_fade_leaveTo : ''"
+	:enterActiveClass="defaultStore.state.animation ? $style.transition_fade_enterActive : ''"
+	:leaveActiveClass="defaultStore.state.animation ? $style.transition_fade_leaveActive : ''"
+	:enterFromClass="defaultStore.state.animation ? $style.transition_fade_enterFrom : ''"
+	:leaveToClass="defaultStore.state.animation ? $style.transition_fade_leaveTo : ''"
 	mode="out-in"
 >
 	<MkLoading v-if="fetching"/>
diff --git a/packages/frontend/src/components/MkPoll.vue b/packages/frontend/src/components/MkPoll.vue
index 0810061ff..464e34011 100644
--- a/packages/frontend/src/components/MkPoll.vue
+++ b/packages/frontend/src/components/MkPoll.vue
@@ -1,19 +1,19 @@
 <template>
-<div class="tivcixzd" :class="{ done: closed || isVoted }">
-	<ul>
-		<li v-for="(choice, i) in note.poll.choices" :key="i" :class="{ voted: choice.voted }" @click="vote(i)">
-			<div class="backdrop" :style="{ 'width': `${showResult ? (choice.votes / total * 100) : 0}%` }"></div>
-			<span>
-				<template v-if="choice.isVoted"><i class="ti ti-check"></i></template>
+<div :class="{ [$style.done]: closed || isVoted }">
+	<ul :class="$style.choices">
+		<li v-for="(choice, i) in note.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>
 				<Mfm :text="choice.text" :plain="true"/>
-				<span v-if="showResult" class="votes">({{ i18n.t('_poll.votesCount', { n: choice.votes }) }})</span>
+				<span v-if="showResult" style="margin-left: 4px; opacity: 0.7;">({{ i18n.t('_poll.votesCount', { n: choice.votes }) }})</span>
 			</span>
 		</li>
 	</ul>
-	<p v-if="!readOnly">
+	<p v-if="!readOnly" :class="$style.info">
 		<span>{{ i18n.t('_poll.totalVotes', { n: total }) }}</span>
 		<span> · </span>
-		<a v-if="!closed && !isVoted" @click="showResult = !showResult">{{ showResult ? i18n.ts._poll.vote : i18n.ts._poll.showResult }}</a>
+		<a v-if="!closed && !isVoted" style="color: inherit;" @click="showResult = !showResult">{{ showResult ? i18n.ts._poll.vote : i18n.ts._poll.showResult }}</a>
 		<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>
@@ -86,67 +86,51 @@ const vote = async (id) => {
 };
 </script>
 
-<style lang="scss" scoped>
-.tivcixzd {
-	> ul {
-		display: block;
-		margin: 0;
-		padding: 0;
-		list-style: none;
+<style lang="scss" module>
+.choices {
+	display: block;
+	margin: 0;
+	padding: 0;
+	list-style: none;
+}
 
-		> li {
-			display: block;
-			position: relative;
-			margin: 4px 0;
-			padding: 4px;
-			//border: solid 0.5px var(--divider);
-			background: var(--accentedBg);
-			border-radius: 4px;
-			overflow: clip;
-			cursor: pointer;
+.choice {
+	display: block;
+	position: relative;
+	margin: 4px 0;
+	padding: 4px;
+	//border: solid 0.5px var(--divider);
+	background: var(--accentedBg);
+	border-radius: 4px;
+	overflow: clip;
+	cursor: pointer;
+}
 
-			> .backdrop {
-				position: absolute;
-				top: 0;
-				left: 0;
-				height: 100%;
-				background: var(--accent);
-				background: linear-gradient(90deg,var(--buttonGradateA),var(--buttonGradateB));
-				transition: width 1s ease;
-			}
+.bg {
+	position: absolute;
+	top: 0;
+	left: 0;
+	height: 100%;
+	background: var(--accent);
+	background: linear-gradient(90deg,var(--buttonGradateA),var(--buttonGradateB));
+	transition: width 1s ease;
+}
 
-			> span {
-				position: relative;
-				display: inline-block;
-				padding: 3px 5px;
-				background: var(--panel);
-				border-radius: 3px;
+.fg {
+	position: relative;
+	display: inline-block;
+	padding: 3px 5px;
+	background: var(--panel);
+	border-radius: 3px;
+}
 
-				> i {
-					margin-right: 4px;
-					color: var(--accent);
-				}
+.info {
+	color: var(--fg);
+}
 
-				> .votes {
-					margin-left: 4px;
-					opacity: 0.7;
-				}
-			}
-		}
-	}
-
-	> p {
-		color: var(--fg);
-
-		a {
-			color: inherit;
-		}
-	}
-
-	&.done {
-		> ul > li {
-			cursor: default;
-		}
+.done {
+	.choice {
+		cursor: default;
 	}
 }
 </style>
diff --git a/packages/frontend/src/components/MkPollEditor.vue b/packages/frontend/src/components/MkPollEditor.vue
index 471ec3916..2da933994 100644
--- a/packages/frontend/src/components/MkPollEditor.vue
+++ b/packages/frontend/src/components/MkPollEditor.vue
@@ -5,7 +5,7 @@
 	</p>
 	<ul>
 		<li v-for="(choice, i) in choices" :key="i">
-			<MkInput class="input" small :model-value="choice" :placeholder="i18n.t('_poll.choiceN', { n: i + 1 })" @update:model-value="onInput(i, $event)">
+			<MkInput class="input" small :modelValue="choice" :placeholder="i18n.t('_poll.choiceN', { n: i + 1 })" @update:modelValue="onInput(i, $event)">
 			</MkInput>
 			<button class="_button" @click="remove(i)">
 				<i class="ti ti-x"></i>
diff --git a/packages/frontend/src/components/MkPopupMenu.vue b/packages/frontend/src/components/MkPopupMenu.vue
index 93b9eb401..30af36566 100644
--- a/packages/frontend/src/components/MkPopupMenu.vue
+++ b/packages/frontend/src/components/MkPopupMenu.vue
@@ -1,6 +1,6 @@
 <template>
-<MkModal ref="modal" v-slot="{ type, maxHeight }" :z-priority="'high'" :src="src" :transparent-bg="true" @click="modal.close()" @close="emit('closing')" @closed="emit('closed')">
-	<MkMenu :items="items" :align="align" :width="width" :max-height="maxHeight" :as-drawer="type === 'drawer'" :class="{ [$style.drawer]: type === 'drawer' }" @close="modal.close()"/>
+<MkModal ref="modal" v-slot="{ type, maxHeight }" :zPriority="'high'" :src="src" :transparentBg="true" @click="modal.close()" @close="emit('closing')" @closed="emit('closed')">
+	<MkMenu :items="items" :align="align" :width="width" :max-height="maxHeight" :asDrawer="type === 'drawer'" :class="{ [$style.drawer]: type === 'drawer' }" @close="modal.close()"/>
 </MkModal>
 </template>
 
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index c65cb7d6e..5c6556968 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -22,21 +22,21 @@
 					<span v-if="visibility === 'specified'"><i class="ti ti-mail"></i></span>
 					<span :class="$style.headerRightButtonText">{{ i18n.ts._visibility[visibility] }}</span>
 				</button>
-				<button v-else :class="['_button', $style.headerRightItem, $style.visibility]" disabled>
+				<button v-else class="_button" :class="[$style.headerRightItem, $style.visibility]" disabled>
 					<span><i class="ti ti-device-tv"></i></span>
 					<span :class="$style.headerRightButtonText">{{ channel.name }}</span>
 				</button>
 			</template>
-			<button v-click-anime v-tooltip="i18n.ts._visibility.disableFederation" :class="['_button', $style.headerRightItem, $style.localOnly, { [$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'" @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>
-			<button v-click-anime v-tooltip="i18n.ts.reactionAcceptance" :class="['_button', $style.headerRightItem, $style.reactionAcceptance, { [$style.danger]: reactionAcceptance }]" @click="toggleReactionAcceptance">
+			<button v-click-anime v-tooltip="i18n.ts.reactionAcceptance" class="_button" :class="[$style.headerRightItem, { [$style.danger]: reactionAcceptance === 'likeOnly' }]" @click="toggleReactionAcceptance">
 				<span v-if="reactionAcceptance === 'likeOnly'"><i class="ti ti-heart"></i></span>
 				<span v-else-if="reactionAcceptance === 'likeOnlyForRemote'"><i class="ti ti-heart-plus"></i></span>
 				<span v-else><i class="ti ti-icons"></i></span>
 			</button>
-			<button v-click-anime class="_button" :class="[$style.submit, { [$style.submitPosting]: posting }]" :disabled="!canPost" data-cy-open-post-form-submit @click="post">
+			<button v-click-anime class="_button" :class="$style.submit" :disabled="!canPost" data-cy-open-post-form-submit @click="post">
 				<div :class="$style.submitInner">
 					<template v-if="posted"></template>
 					<template v-else-if="posting"><MkEllipsis/></template>
@@ -66,7 +66,7 @@
 		<div v-if="maxTextLength - textLength < 100" :class="['_acrylic', $style.textCount, { [$style.textOver]: textLength > maxTextLength }]">{{ maxTextLength - textLength }}</div>
 	</div>
 	<input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" :class="$style.hashtags" :placeholder="i18n.ts.hashtags" list="hashtags">
-	<XPostFormAttaches v-model="files" :class="$style.attaches" @detach="detachFile" @change-sensitive="updateFileSensitive" @change-name="updateFileName"/>
+	<XPostFormAttaches v-model="files" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/>
 	<MkPollEditor v-if="poll" v-model="poll" @destroyed="poll = null"/>
 	<MkNotePreview v-if="showPreview" :class="$style.preview" :text="text"/>
 	<div v-if="showingOptions" style="padding: 8px 16px;">
@@ -484,8 +484,10 @@ async function toggleReactionAcceptance() {
 		title: i18n.ts.reactionAcceptance,
 		items: [
 			{ value: null, text: i18n.ts.all },
-			{ value: 'likeOnly' as const, text: i18n.ts.likeOnly },
 			{ value: 'likeOnlyForRemote' as const, text: i18n.ts.likeOnlyForRemote },
+			{ value: 'nonSensitiveOnly' as const, text: i18n.ts.nonSensitiveOnly },
+			{ value: 'nonSensitiveOnlyForLocalLikeOnlyForRemote' as const, text: i18n.ts.nonSensitiveOnlyForLocalLikeOnlyForRemote },
+			{ value: 'likeOnly' as const, text: i18n.ts.likeOnly },
 		],
 		default: reactionAcceptance,
 	});
diff --git a/packages/frontend/src/components/MkPostFormAttaches.vue b/packages/frontend/src/components/MkPostFormAttaches.vue
index 760c6e5d0..18fa142eb 100644
--- a/packages/frontend/src/components/MkPostFormAttaches.vue
+++ b/packages/frontend/src/components/MkPostFormAttaches.vue
@@ -1,16 +1,16 @@
 <template>
-<div v-show="props.modelValue.length != 0" class="skeikyzd">
-	<Sortable :model-value="props.modelValue" class="files" item-key="id" :animation="150" :delay="100" :delay-on-touch-only="true" @update:model-value="v => emit('update:modelValue', v)">
+<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="file" @click="showFileMenu(element, $event)" @contextmenu.prevent="showFileMenu(element, $event)">
-				<MkDriveFileThumbnail :data-id="element.id" class="thumbnail" :file="element" fit="cover"/>
-				<div v-if="element.isSensitive" class="sensitive">
-					<i class="ti ti-alert-triangle icon"></i>
+			<div :class="$style.file" @click="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-alert-triangle" style="margin: auto;"></i>
 				</div>
 			</div>
 		</template>
 	</Sortable>
-	<p class="remain">{{ 16 - props.modelValue.length }}/16</p>
+	<p :class="$style.remain">{{ 16 - props.modelValue.length }}/16</p>
 </div>
 </template>
 
@@ -93,7 +93,7 @@ function showFileMenu(file, ev: MouseEvent) {
 		action: () => { rename(file); },
 	}, {
 		text: file.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive,
-		icon: file.isSensitive ? 'ti ti-eye-off' : 'ti ti-eye',
+		icon: file.isSensitive ? 'ti ti-eye-exclamation' : 'ti ti-eye',
 		action: () => { toggleSensitive(file); },
 	}, {
 		text: i18n.ts.describeFile,
@@ -108,60 +108,53 @@ function showFileMenu(file, ev: MouseEvent) {
 }
 </script>
 
-<style lang="scss" scoped>
-.skeikyzd {
+<style lang="scss" module>
+.root {
 	padding: 8px 16px;
 	position: relative;
+}
 
-	> .files {
-		display: flex;
-		flex-wrap: wrap;
+.files {
+	display: flex;
+	flex-wrap: wrap;
+}
 
-		> .file {
-			position: relative;
-			width: 64px;
-			height: 64px;
-			margin-right: 4px;
-			border-radius: 4px;
-			overflow: hidden;
-			cursor: move;
+.file {
+	position: relative;
+	width: 64px;
+	height: 64px;
+	margin-right: 4px;
+	border-radius: 4px;
+	overflow: hidden;
+	cursor: move;
+}
 
-			&:hover > .remove {
-				display: block;
-			}
+.thumbnail {
+	width: 100%;
+	height: 100%;
+	z-index: 1;
+	color: var(--fg);
+}
 
-			> .thumbnail {
-				width: 100%;
-				height: 100%;
-				z-index: 1;
-				color: var(--fg);
-			}
+.sensitive {
+	display: flex;
+	position: absolute;
+	width: 64px;
+	height: 64px;
+	top: 0;
+	left: 0;
+	z-index: 2;
+	background: rgba(17, 17, 17, .7);
+	color: #fff;
+}
 
-			> .sensitive {
-				display: flex;
-				position: absolute;
-				width: 64px;
-				height: 64px;
-				top: 0;
-				left: 0;
-				z-index: 2;
-				background: rgba(17, 17, 17, .7);
-				color: #fff;
-
-				> .icon {
-					margin: auto;
-				}
-			}
-		}
-	}
-
-	> .remain {
-		display: block;
-		position: absolute;
-		top: 8px;
-		right: 8px;
-		margin: 0;
-		padding: 0;
-	}
+.remain {
+	display: block;
+	position: absolute;
+	top: 8px;
+	right: 8px;
+	margin: 0;
+	padding: 0;
+	font-size: 90%;
 }
 </style>
diff --git a/packages/frontend/src/components/MkPostFormDialog.vue b/packages/frontend/src/components/MkPostFormDialog.vue
index 6326c498d..98af92c6f 100644
--- a/packages/frontend/src/components/MkPostFormDialog.vue
+++ b/packages/frontend/src/components/MkPostFormDialog.vue
@@ -1,6 +1,6 @@
 <template>
-<MkModal ref="modal" :prefer-type="'dialog'" @click="modal.close()" @closed="onModalClosed()">
-	<MkPostForm ref="form" style="margin: 0 auto auto auto;" v-bind="props" autofocus freeze-after-posted @posted="onPosted" @cancel="modal.close()" @esc="modal.close()"/>
+<MkModal ref="modal" :preferType="'dialog'" @click="modal.close()" @closed="onModalClosed()">
+	<MkPostForm ref="form" style="margin: 0 auto auto auto;" v-bind="props" autofocus freezeAfterPosted @posted="onPosted" @cancel="modal.close()" @esc="modal.close()"/>
 </MkModal>
 </template>
 
diff --git a/packages/frontend/src/components/MkPushNotificationAllowButton.vue b/packages/frontend/src/components/MkPushNotificationAllowButton.vue
index b98c814f2..448084d9b 100644
--- a/packages/frontend/src/components/MkPushNotificationAllowButton.vue
+++ b/packages/frontend/src/components/MkPushNotificationAllowButton.vue
@@ -72,28 +72,28 @@ function subscribe() {
 		userVisibleOnly: true,
 		applicationServerKey: urlBase64ToUint8Array(instance.swPublickey),
 	})
-	.then(async subscription => {
-		pushSubscription = subscription;
+		.then(async subscription => {
+			pushSubscription = subscription;
 
-		// Register
-		pushRegistrationInServer = await api('sw/register', {
-			endpoint: subscription.endpoint,
-			auth: encode(subscription.getKey('auth')),
-			publickey: encode(subscription.getKey('p256dh')),
-		});
-	}, async err => { // When subscribe failed
+			// Register
+			pushRegistrationInServer = await api('sw/register', {
+				endpoint: subscription.endpoint,
+				auth: encode(subscription.getKey('auth')),
+				publickey: encode(subscription.getKey('p256dh')),
+			});
+		}, async err => { // When subscribe failed
 		// 通知が許可されていなかったとき
-		if (err?.name === 'NotAllowedError') {
-			console.info('User denied the notification permission request.');
-			return;
-		}
+			if (err?.name === 'NotAllowedError') {
+				console.info('User denied the notification permission request.');
+				return;
+			}
 
-		// 違うapplicationServerKey (または gcm_sender_id)のサブスクリプションが
-		// 既に存在していることが原因でエラーになった可能性があるので、
-		// そのサブスクリプションを解除しておく
-		// (これは実行されなさそうだけど、おまじない的に古い実装から残してある)
-		await unsubscribe();
-	}), null, null);
+			// 違うapplicationServerKey (または gcm_sender_id)のサブスクリプションが
+			// 既に存在していることが原因でエラーになった可能性があるので、
+			// そのサブスクリプションを解除しておく
+			// (これは実行されなさそうだけど、おまじない的に古い実装から残してある)
+			await unsubscribe();
+		}), null, null);
 }
 
 async function unsubscribe() {
diff --git a/packages/frontend/src/components/MkRadios.vue b/packages/frontend/src/components/MkRadios.vue
index e2240fb4e..84be10078 100644
--- a/packages/frontend/src/components/MkRadios.vue
+++ b/packages/frontend/src/components/MkRadios.vue
@@ -1,37 +1,27 @@
 <script lang="ts">
-import { VNode, defineComponent, h } from 'vue';
+import { VNode, defineComponent, h, ref, watch } from 'vue';
 import MkRadio from './MkRadio.vue';
 
 export default defineComponent({
-	components: {
-		MkRadio,
-	},
 	props: {
 		modelValue: {
 			required: false,
 		},
 	},
-	data() {
-		return {
-			value: this.modelValue,
-		};
-	},
-	watch: {
-		value() {
-			this.$emit('update:modelValue', this.value);
-		},
-	},
-	render() {
-		console.log(this.$slots, this.$slots.label && this.$slots.label());
-		if (!this.$slots.default) return null;
-		let options = this.$slots.default();
-		const label = this.$slots.label && this.$slots.label();
-		const caption = this.$slots.caption && this.$slots.caption();
+	setup(props, context) {
+		const value = ref(props.modelValue);
+		watch(value, () => {
+			context.emit('update:modelValue', value.value);
+		});
+		if (!context.slots.default) return null;
+		let options = context.slots.default();
+		const label = context.slots.label && context.slots.label();
+		const caption = context.slots.caption && context.slots.caption();
 
 		// なぜかFragmentになることがあるため
 		if (options.length === 1 && options[0].props == null) options = options[0].children as VNode[];
 
-		return h('div', {
+		return () => h('div', {
 			class: 'novjtcto',
 		}, [
 			...(label ? [h('div', {
@@ -42,8 +32,8 @@ export default defineComponent({
 			}, options.map(option => h(MkRadio, {
 				key: option.key,
 				value: option.props?.value,
-				modelValue: this.value,
-				'onUpdate:modelValue': value => this.value = value,
+				modelValue: value.value,
+				'onUpdate:modelValue': _v => value.value = _v,
 			}, () => option.children)),
 			),
 			...(caption ? [h('div', {
diff --git a/packages/frontend/src/components/MkReactedUsersDialog.vue b/packages/frontend/src/components/MkReactedUsersDialog.vue
index 0c0cc3669..cd2a359d5 100644
--- a/packages/frontend/src/components/MkReactedUsersDialog.vue
+++ b/packages/frontend/src/components/MkReactedUsersDialog.vue
@@ -8,7 +8,7 @@
 >
 	<template #header>{{ i18n.ts.reactionsList }}</template>
 
-	<MkSpacer :margin-min="20" :margin-max="28">
+	<MkSpacer :marginMin="20" :marginMax="28">
 		<div v-if="note" class="_gaps">
 			<div v-if="reactions.length === 0" class="_fullinfo">
 				<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
@@ -22,7 +22,7 @@
 					</button>
 				</div>
 				<MkA v-for="user in users" :key="user.id" :to="userPage(user)" @click="dialog.close()">
-					<MkUserCardMini :user="user" :with-chart="false"/>
+					<MkUserCardMini :user="user" :withChart="false"/>
 				</MkA>
 			</template>
 		</div>
diff --git a/packages/frontend/src/components/MkReactionIcon.vue b/packages/frontend/src/components/MkReactionIcon.vue
index 29b3f9b85..dfb06f63c 100644
--- a/packages/frontend/src/components/MkReactionIcon.vue
+++ b/packages/frontend/src/components/MkReactionIcon.vue
@@ -1,6 +1,6 @@
 <template>
-<MkCustomEmoji v-if="reaction[0] === ':'" :name="reaction" :normal="true" :no-style="noStyle" :url="emojiUrl"/>
-<MkEmoji v-else :emoji="reaction" :normal="true" :no-style="noStyle"/>
+<MkCustomEmoji v-if="reaction[0] === ':'" :name="reaction" :normal="true" :noStyle="noStyle" :url="emojiUrl"/>
+<MkEmoji v-else :emoji="reaction" :normal="true" :noStyle="noStyle"/>
 </template>
 
 <script lang="ts" setup>
diff --git a/packages/frontend/src/components/MkReactionTooltip.vue b/packages/frontend/src/components/MkReactionTooltip.vue
index 4d67dc3da..34afa7223 100644
--- a/packages/frontend/src/components/MkReactionTooltip.vue
+++ b/packages/frontend/src/components/MkReactionTooltip.vue
@@ -1,7 +1,7 @@
 <template>
-<MkTooltip ref="tooltip" :showing="showing" :target-element="targetElement" :max-width="340" @closed="emit('closed')">
+<MkTooltip ref="tooltip" :showing="showing" :targetElement="targetElement" :maxWidth="340" @closed="emit('closed')">
 	<div :class="$style.root">
-		<MkReactionIcon :reaction="reaction" :class="$style.icon" :no-style="true"/>
+		<MkReactionIcon :reaction="reaction" :class="$style.icon" :noStyle="true"/>
 		<div :class="$style.name">{{ reaction.replace('@.', '') }}</div>
 	</div>
 </MkTooltip>
diff --git a/packages/frontend/src/components/MkReactionsViewer.details.vue b/packages/frontend/src/components/MkReactionsViewer.details.vue
index f5e611c62..99960f5d2 100644
--- a/packages/frontend/src/components/MkReactionsViewer.details.vue
+++ b/packages/frontend/src/components/MkReactionsViewer.details.vue
@@ -1,8 +1,8 @@
 <template>
-<MkTooltip ref="tooltip" :showing="showing" :target-element="targetElement" :max-width="340" @closed="emit('closed')">
+<MkTooltip ref="tooltip" :showing="showing" :targetElement="targetElement" :maxWidth="340" @closed="emit('closed')">
 	<div :class="$style.root">
 		<div :class="$style.reaction">
-			<MkReactionIcon :reaction="reaction" :class="$style.reactionIcon" :no-style="true"/>
+			<MkReactionIcon :reaction="reaction" :class="$style.reactionIcon" :noStyle="true"/>
 			<div :class="$style.reactionName">{{ getReactionName(reaction) }}</div>
 		</div>
 		<div :class="$style.users">
diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
index 9480af510..aabebb3ab 100644
--- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue
+++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
@@ -6,7 +6,7 @@
 	:class="[$style.root, { [$style.reacted]: note.myReaction == reaction, [$style.canToggle]: canToggle, [$style.large]: defaultStore.state.largeNoteReactions }]"
 	@click="toggleReaction()"
 >
-	<MkReactionIcon :class="$style.icon" :reaction="reaction" :emoji-url="note.reactionEmojis[reaction.substr(1, reaction.length - 2)]"/>
+	<MkReactionIcon :class="$style.icon" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substr(1, reaction.length - 2)]"/>
 	<span :class="$style.count">{{ count }}</span>
 </button>
 </template>
@@ -22,6 +22,7 @@ import { $i } from '@/account';
 import MkReactionEffect from '@/components/MkReactionEffect.vue';
 import { claimAchievement } from '@/scripts/achievements';
 import { defaultStore } from '@/store';
+import { i18n } from '@/i18n';
 
 const props = defineProps<{
 	reaction: string;
@@ -34,11 +35,19 @@ const buttonEl = shallowRef<HTMLElement>();
 
 const canToggle = computed(() => !props.reaction.match(/@\w/) && $i);
 
-const toggleReaction = () => {
+async function toggleReaction() {
 	if (!canToggle.value) return;
 
+	// TODO: その絵文字を使う権限があるかどうか確認
+
 	const oldReaction = props.note.myReaction;
 	if (oldReaction) {
+		const confirm = await os.confirm({
+			type: 'warning',
+			text: oldReaction !== props.reaction ? i18n.ts.changeReactionConfirm : i18n.ts.cancelReactionConfirm,
+		});
+		if (confirm.canceled) return;
+
 		os.api('notes/reactions/delete', {
 			noteId: props.note.id,
 		}).then(() => {
@@ -58,9 +67,9 @@ const toggleReaction = () => {
 			claimAchievement('reactWithoutRead');
 		}
 	}
-};
+}
 
-const anime = () => {
+function anime() {
 	if (document.hidden) return;
 	if (!defaultStore.state.animation) return;
 
@@ -68,7 +77,7 @@ const anime = () => {
 	const x = rect.left + 16;
 	const y = rect.top + (buttonEl.value.offsetHeight / 2);
 	os.popup(MkReactionEffect, { reaction: props.reaction, x, y }, {}, 'end');
-};
+}
 
 watch(() => props.count, (newCount, oldCount) => {
 	if (oldCount < newCount) anime();
diff --git a/packages/frontend/src/components/MkReactionsViewer.vue b/packages/frontend/src/components/MkReactionsViewer.vue
index 3219c8a92..ce146463e 100644
--- a/packages/frontend/src/components/MkReactionsViewer.vue
+++ b/packages/frontend/src/components/MkReactionsViewer.vue
@@ -1,13 +1,13 @@
 <template>
 <TransitionGroup
-	:enter-active-class="defaultStore.state.animation ? $style.transition_x_enterActive : ''"
-	:leave-active-class="defaultStore.state.animation ? $style.transition_x_leaveActive : ''"
-	:enter-from-class="defaultStore.state.animation ? $style.transition_x_enterFrom : ''"
-	:leave-to-class="defaultStore.state.animation ? $style.transition_x_leaveTo : ''"
-	:move-class="defaultStore.state.animation ? $style.transition_x_move : ''"
+	:enterActiveClass="defaultStore.state.animation ? $style.transition_x_enterActive : ''"
+	:leaveActiveClass="defaultStore.state.animation ? $style.transition_x_leaveActive : ''"
+	:enterFromClass="defaultStore.state.animation ? $style.transition_x_enterFrom : ''"
+	:leaveToClass="defaultStore.state.animation ? $style.transition_x_leaveTo : ''"
+	:moveClass="defaultStore.state.animation ? $style.transition_x_move : ''"
 	tag="div" :class="$style.root"
 >
-	<XReaction v-for="[reaction, count] in reactions" :key="reaction" :reaction="reaction" :count="count" :is-initial="initialReactions.has(reaction)" :note="note"/>
+	<XReaction v-for="[reaction, count] in reactions" :key="reaction" :reaction="reaction" :count="count" :isInitial="initialReactions.has(reaction)" :note="note"/>
 	<slot v-if="hasMoreReactions" name="more"/>
 </TransitionGroup>
 </template>
diff --git a/packages/frontend/src/components/MkRenotedUsersDialog.vue b/packages/frontend/src/components/MkRenotedUsersDialog.vue
index 56025535f..814a68d4d 100644
--- a/packages/frontend/src/components/MkRenotedUsersDialog.vue
+++ b/packages/frontend/src/components/MkRenotedUsersDialog.vue
@@ -8,7 +8,7 @@
 >
 	<template #header>{{ i18n.ts.renotesList }}</template>
 
-	<MkSpacer :margin-min="20" :margin-max="28">
+	<MkSpacer :marginMin="20" :marginMax="28">
 		<div v-if="renotes" class="_gaps">
 			<div v-if="renotes.length === 0" class="_fullinfo">
 				<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
@@ -16,7 +16,7 @@
 			</div>
 			<template v-else>
 				<MkA v-for="user in users" :key="user.id" :to="userPage(user)" @click="dialog.close()">
-					<MkUserCardMini :user="user" :with-chart="false"/>
+					<MkUserCardMini :user="user" :withChart="false"/>
 				</MkA>
 			</template>
 		</div>
diff --git a/packages/frontend/src/components/MkRetentionLineChart.vue b/packages/frontend/src/components/MkRetentionLineChart.vue
index 8bd027980..9f56189f3 100644
--- a/packages/frontend/src/components/MkRetentionLineChart.vue
+++ b/packages/frontend/src/components/MkRetentionLineChart.vue
@@ -124,7 +124,3 @@ onMounted(async () => {
 	});
 });
 </script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/packages/frontend/src/components/MkRippleEffect.vue b/packages/frontend/src/components/MkRippleEffect.vue
index 9d93211d5..60c3a4738 100644
--- a/packages/frontend/src/components/MkRippleEffect.vue
+++ b/packages/frontend/src/components/MkRippleEffect.vue
@@ -1,7 +1,7 @@
 <template>
-<div class="vswabwbm" :style="{ zIndex, top: `${y - 64}px`, left: `${x - 64}px` }">
+<div :class="$style.root" :style="{ zIndex, top: `${y - 64}px`, left: `${x - 64}px` }">
 	<svg width="128" height="128" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg">
-		<circle fill="none" cx="64" cy="64">
+		<circle fill="none" cx="64" cy="64" style="stroke: var(--accent);">
 			<animate
 				attributeName="r"
 				begin="0s" dur="0.5s"
@@ -22,7 +22,7 @@
 			/>
 		</circle>
 		<g fill="none" fill-rule="evenodd">
-			<circle v-for="(particle, i) in particles" :key="i" :fill="particle.color">
+			<circle v-for="(particle, i) in particles" :key="i" :fill="particle.color" style="stroke: var(--accent);">
 				<animate
 					attributeName="r"
 					begin="0s" dur="0.8s"
@@ -100,17 +100,11 @@ onMounted(() => {
 });
 </script>
 
-<style lang="scss" scoped>
-.vswabwbm {
+<style lang="scss" module>
+.root {
 	pointer-events: none;
 	position: fixed;
 	width: 128px;
 	height: 128px;
-
-	> svg {
-		> circle {
-			stroke: var(--accent);
-		}
-	}
 }
 </style>
diff --git a/packages/frontend/src/components/MkRolePreview.vue b/packages/frontend/src/components/MkRolePreview.vue
index 2f5866f34..9fbe1ec99 100644
--- a/packages/frontend/src/components/MkRolePreview.vue
+++ b/packages/frontend/src/components/MkRolePreview.vue
@@ -12,8 +12,10 @@
 			</template>
 		</span>
 		<span :class="$style.name">{{ role.name }}</span>
-		<span v-if="role.target === 'manual'" :class="$style.users">{{ role.usersCount }} users</span>
-		<span v-else-if="role.target === 'conditional'" :class="$style.users">({{ i18n.ts._role.conditional }})</span>
+		<template v-if="detailed">
+			<span v-if="role.target === 'manual'" :class="$style.users">{{ role.usersCount }} users</span>
+			<span v-else-if="role.target === 'conditional'" :class="$style.users">({{ i18n.ts._role.conditional }})</span>
+		</template>
 	</div>
 	<div :class="$style.description">{{ role.description }}</div>
 </MkA>
@@ -23,10 +25,13 @@
 import { } from 'vue';
 import { i18n } from '@/i18n';
 
-const props = defineProps<{
+const props = withDefaults(defineProps<{
 	role: any;
 	forModeration: boolean;
-}>();
+	detailed: boolean;
+}>(), {
+	detailed: true,
+});
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/components/MkSample.vue b/packages/frontend/src/components/MkSample.vue
deleted file mode 100644
index 922b862b4..000000000
--- a/packages/frontend/src/components/MkSample.vue
+++ /dev/null
@@ -1,118 +0,0 @@
-<template>
-<div class="">
-	<div class="">
-		<MkInput v-model="text">
-			<template #label>Text</template>
-		</MkInput>
-		<MkSwitch v-model="flag">
-			<span>Switch is now {{ flag ? 'on' : 'off' }}</span>
-		</MkSwitch>
-		<div style="margin: 32px 0;">
-			<MkRadio v-model="radio" value="misskey">Misskey</MkRadio>
-			<MkRadio v-model="radio" value="mastodon">Mastodon</MkRadio>
-			<MkRadio v-model="radio" value="pleroma">Pleroma</MkRadio>
-		</div>
-		<MkButton inline>This is</MkButton>
-		<MkButton inline primary>the button</MkButton>
-	</div>
-	<div class="" style="pointer-events: none;">
-		<Mfm :text="mfm"/>
-	</div>
-	<div class="">
-		<MkButton inline primary @click="openMenu">Open menu</MkButton>
-		<MkButton inline primary @click="openDialog">Open dialog</MkButton>
-		<MkButton inline primary @click="openForm">Open form</MkButton>
-		<MkButton inline primary @click="openDrive">Open drive</MkButton>
-	</div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import MkButton from '@/components/MkButton.vue';
-import MkInput from '@/components/MkInput.vue';
-import MkSwitch from '@/components/MkSwitch.vue';
-import MkTextarea from '@/components/MkTextarea.vue';
-import MkRadio from '@/components/MkRadio.vue';
-import * as os from '@/os';
-import * as config from '@/config';
-import { $i } from '@/account';
-
-export default defineComponent({
-	components: {
-		MkButton,
-		MkInput,
-		MkSwitch,
-		MkTextarea,
-		MkRadio,
-	},
-
-	data() {
-		return {
-			text: '',
-			flag: true,
-			radio: 'misskey',
-			$i,
-			mfm: `Hello world! This is an @example mention. BTW you are @${this.$i ? this.$i.username : 'guest'}.\nAlso, here is ${config.url} and [example link](${config.url}). for more details, see https://example.com.\nAs you know #misskey is open-source software.`,
-		};
-	},
-
-	methods: {
-		async openDialog() {
-			os.alert({
-				type: 'warning',
-				title: 'Oh my Aichan',
-				text: 'Lorem ipsum dolor sit amet, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
-			});
-		},
-
-		async openForm() {
-			os.form('Example form', {
-				foo: {
-					type: 'boolean',
-					default: true,
-					label: 'This is a boolean property',
-				},
-				bar: {
-					type: 'number',
-					default: 300,
-					label: 'This is a number property',
-				},
-				baz: {
-					type: 'string',
-					default: 'Misskey makes you happy.',
-					label: 'This is a string property',
-				},
-			});
-		},
-
-		async openDrive() {
-			os.selectDriveFile(false);
-		},
-
-		async selectUser() {
-			os.selectUser();
-		},
-
-		async openMenu(ev) {
-			os.popupMenu([{
-				type: 'label',
-				text: 'Fruits',
-			}, {
-				text: 'Create some apples',
-				action: () => {},
-			}, {
-				text: 'Read some oranges',
-				action: () => {},
-			}, {
-				text: 'Update some melons',
-				action: () => {},
-			}, null, {
-				text: 'Delete some bananas',
-				danger: true,
-				action: () => {},
-			}], ev.currentTarget ?? ev.target);
-		},
-	},
-});
-</script>
diff --git a/packages/frontend/src/components/MkSignin.vue b/packages/frontend/src/components/MkSignin.vue
index ffc5e82b5..b1a509b9e 100644
--- a/packages/frontend/src/components/MkSignin.vue
+++ b/packages/frontend/src/components/MkSignin.vue
@@ -1,16 +1,16 @@
 <template>
-<form class="eppvobhk" :class="{ signing, totpLogin }" @submit.prevent="onSubmit">
-	<div class="auth _gaps_m">
-		<div v-show="withAvatar" class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null, marginBottom: message ? '1.5em' : null }"></div>
+<form :class="{ signing, totpLogin }" @submit.prevent="onSubmit">
+	<div class="_gaps_m">
+		<div v-show="withAvatar" :class="$style.avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null, marginBottom: message ? '1.5em' : null }"></div>
 		<MkInfo v-if="message">
 			{{ message }}
 		</MkInfo>
 		<div v-if="!totpLogin" class="normal-signin _gaps_m">
-			<MkInput v-model="username" :placeholder="i18n.ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autocomplete="username" autofocus required data-cy-signin-username @update:model-value="onUsernameChange">
+			<MkInput v-model="username" :placeholder="i18n.ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autocomplete="username" autofocus required data-cy-signin-username @update:modelValue="onUsernameChange">
 				<template #prefix>@</template>
 				<template #suffix>@{{ host }}</template>
 			</MkInput>
-			<MkInput v-if="!user || user && !user.usePasswordLessLogin" v-model="password" :placeholder="i18n.ts.password" type="password" autocomplete="current-password" :with-password-toggle="true" required data-cy-signin-password>
+			<MkInput v-if="!user || user && !user.usePasswordLessLogin" v-model="password" :placeholder="i18n.ts.password" type="password" autocomplete="current-password" :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>
@@ -28,7 +28,7 @@
 			</div>
 			<div class="twofa-group totp-group">
 				<p style="margin-bottom:0;">{{ i18n.ts.twoStepAuthentication }}</p>
-				<MkInput v-if="user && user.usePasswordLessLogin" v-model="password" type="password" autocomplete="current-password" :with-password-toggle="true" required>
+				<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>
@@ -236,18 +236,14 @@ function resetPassword() {
 }
 </script>
 
-<style lang="scss" scoped>
-.eppvobhk {
-	> .auth {
-		> .avatar {
-			margin: 0 auto 0 auto;
-			width: 64px;
-			height: 64px;
-			background: #ddd;
-			background-position: center;
-			background-size: cover;
-			border-radius: 100%;
-		}
-	}
+<style lang="scss" module>
+.avatar {
+	margin: 0 auto 0 auto;
+	width: 64px;
+	height: 64px;
+	background: #ddd;
+	background-position: center;
+	background-size: cover;
+	border-radius: 100%;
 }
 </style>
diff --git a/packages/frontend/src/components/MkSigninDialog.vue b/packages/frontend/src/components/MkSigninDialog.vue
index 08e41d6ae..eb5876e58 100644
--- a/packages/frontend/src/components/MkSigninDialog.vue
+++ b/packages/frontend/src/components/MkSigninDialog.vue
@@ -8,8 +8,8 @@
 >
 	<template #header>{{ i18n.ts.login }}</template>
 
-	<MkSpacer :margin-min="20" :margin-max="28">
-		<MkSignin :auto-set="autoSet" :message="message" @login="onLogin"/>
+	<MkSpacer :marginMin="20" :marginMax="28">
+		<MkSignin :autoSet="autoSet" :message="message" @login="onLogin"/>
 	</MkSpacer>
 </MkModalWindow>
 </template>
diff --git a/packages/frontend/src/components/MkSignupDialog.form.vue b/packages/frontend/src/components/MkSignupDialog.form.vue
index 0e8bdb321..472269aba 100644
--- a/packages/frontend/src/components/MkSignupDialog.form.vue
+++ b/packages/frontend/src/components/MkSignupDialog.form.vue
@@ -3,13 +3,13 @@
 	<div :class="$style.banner">
 		<i class="ti ti-user-edit"></i>
 	</div>
-	<MkSpacer :margin-min="20" :margin-max="32">
+	<MkSpacer :marginMin="20" :marginMax="32">
 		<form class="_gaps_m" autocomplete="new-password" @submit.prevent="onSubmit">
 			<MkInput v-if="instance.disableRegistration" v-model="invitationCode" type="text" :spellcheck="false" required>
 				<template #label>{{ i18n.ts.invitationCode }}</template>
 				<template #prefix><i class="ti ti-key"></i></template>
 			</MkInput>
-			<MkInput v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-signup-username @update:model-value="onChangeUsername">
+			<MkInput v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" autocomplete="username" required data-cy-signup-username @update:modelValue="onChangeUsername">
 				<template #label>{{ i18n.ts.username }} <div v-tooltip:dialog="i18n.ts.usernameInfo" class="_button _help"><i class="ti ti-help-circle"></i></div></template>
 				<template #prefix>@</template>
 				<template #suffix>@{{ host }}</template>
@@ -24,7 +24,7 @@
 					<span v-else-if="usernameState === 'max-range'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.tooLong }}</span>
 				</template>
 			</MkInput>
-			<MkInput v-if="instance.emailRequiredForSignup" v-model="email" :debounce="true" type="email" :spellcheck="false" required data-cy-signup-email @update:model-value="onChangeEmail">
+			<MkInput v-if="instance.emailRequiredForSignup" v-model="email" :debounce="true" type="email" :spellcheck="false" required data-cy-signup-email @update:modelValue="onChangeEmail">
 				<template #label>{{ i18n.ts.emailAddress }} <div v-tooltip:dialog="i18n.ts._signup.emailAddressInfo" class="_button _help"><i class="ti ti-help-circle"></i></div></template>
 				<template #prefix><i class="ti ti-mail"></i></template>
 				<template #caption>
@@ -39,7 +39,7 @@
 					<span v-else-if="emailState === 'error'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.error }}</span>
 				</template>
 			</MkInput>
-			<MkInput v-model="password" type="password" autocomplete="new-password" required data-cy-signup-password @update:model-value="onChangePassword">
+			<MkInput v-model="password" type="password" autocomplete="new-password" required data-cy-signup-password @update:modelValue="onChangePassword">
 				<template #label>{{ i18n.ts.password }}</template>
 				<template #prefix><i class="ti ti-lock"></i></template>
 				<template #caption>
@@ -48,7 +48,7 @@
 					<span v-if="passwordStrength == 'high'" style="color: var(--success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.strongPassword }}</span>
 				</template>
 			</MkInput>
-			<MkInput v-model="retypedPassword" type="password" autocomplete="new-password" required data-cy-signup-password-retype @update:model-value="onChangePasswordRetype">
+			<MkInput v-model="retypedPassword" type="password" autocomplete="new-password" required data-cy-signup-password-retype @update:modelValue="onChangePasswordRetype">
 				<template #label>{{ i18n.ts.password }} ({{ i18n.ts.retype }})</template>
 				<template #prefix><i class="ti ti-lock"></i></template>
 				<template #caption>
diff --git a/packages/frontend/src/components/MkSignupDialog.rules.vue b/packages/frontend/src/components/MkSignupDialog.rules.vue
index 6da81c3bc..b6ffba6cc 100644
--- a/packages/frontend/src/components/MkSignupDialog.rules.vue
+++ b/packages/frontend/src/components/MkSignupDialog.rules.vue
@@ -3,7 +3,7 @@
 	<div :class="$style.banner">
 		<i class="ti ti-checklist"></i>
 	</div>
-	<MkSpacer :margin-min="20" :margin-max="28">
+	<MkSpacer :marginMin="20" :marginMax="28">
 		<div class="_gaps_m">
 			<div v-if="instance.disableRegistration">
 				<MkInfo warn>{{ i18n.ts.invitationRequiredToRegister }}</MkInfo>
@@ -11,7 +11,7 @@
 
 			<div style="text-align: center;">{{ i18n.ts.pleaseConfirmBelowBeforeSignup }}</div>
 
-			<MkFolder v-if="availableServerRules" :default-open="true">
+			<MkFolder v-if="availableServerRules" :defaultOpen="true">
 				<template #label>{{ i18n.ts.serverRules }}</template>
 				<template #suffix><i v-if="agreeServerRules" class="ti ti-check" style="color: var(--success)"></i></template>
 
@@ -22,7 +22,7 @@
 				<MkSwitch v-model="agreeServerRules" style="margin-top: 16px;">{{ i18n.ts.agree }}</MkSwitch>
 			</MkFolder>
 
-			<MkFolder v-if="availableTos" :default-open="true">
+			<MkFolder v-if="availableTos" :defaultOpen="true">
 				<template #label>{{ i18n.ts.termsOfService }}</template>
 				<template #suffix><i v-if="agreeTos" class="ti ti-check" style="color: var(--success)"></i></template>
 
@@ -31,7 +31,7 @@
 				<MkSwitch v-model="agreeTos" style="margin-top: 16px;">{{ i18n.ts.agree }}</MkSwitch>
 			</MkFolder>
 
-			<MkFolder :default-open="true">
+			<MkFolder :defaultOpen="true">
 				<template #label>{{ i18n.ts.basicNotesBeforeCreateAccount }}</template>
 				<template #suffix><i v-if="agreeNote" class="ti ti-check" style="color: var(--success)"></i></template>
 
diff --git a/packages/frontend/src/components/MkSignupDialog.vue b/packages/frontend/src/components/MkSignupDialog.vue
index 17f8b8642..d8d002fdb 100644
--- a/packages/frontend/src/components/MkSignupDialog.vue
+++ b/packages/frontend/src/components/MkSignupDialog.vue
@@ -11,16 +11,16 @@
 	<div style="overflow-x: clip;">
 		<Transition
 			mode="out-in"
-			:enter-active-class="$style.transition_x_enterActive"
-			:leave-active-class="$style.transition_x_leaveActive"
-			:enter-from-class="$style.transition_x_enterFrom"
-			:leave-to-class="$style.transition_x_leaveTo"
+			:enterActiveClass="$style.transition_x_enterActive"
+			:leaveActiveClass="$style.transition_x_leaveActive"
+			:enterFromClass="$style.transition_x_enterFrom"
+			:leaveToClass="$style.transition_x_leaveTo"
 		>
 			<template v-if="!isAcceptedServerRule">
 				<XServerRules @done="isAcceptedServerRule = true" @cancel="dialog.close()"/>
 			</template>
 			<template v-else>
-				<XSignup :auto-set="autoSet" @signup="onSignup" @signup-email-pending="onSignupEmailPending"/>
+				<XSignup :autoSet="autoSet" @signup="onSignup" @signupEmailPending="onSignupEmailPending"/>
 			</template>
 		</Transition>
 	</div>
diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue
index 1ac7107aa..3a050889c 100644
--- a/packages/frontend/src/components/MkSubNoteContent.vue
+++ b/packages/frontend/src/components/MkSubNoteContent.vue
@@ -1,15 +1,15 @@
 <template>
 <div :class="[$style.root, { [$style.collapsed]: collapsed }]">
-	<div :class="$style.body">
+	<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.deleted }})</span>
 		<MkA v-if="note.replyId" :class="$style.reply" :to="`/notes/${note.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
-		<Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i" :emoji-urls="note.emojis"/>
+		<Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i" :emojiUrls="note.emojis"/>
 		<MkA v-if="note.renoteId" :class="$style.rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA>
 	</div>
 	<details v-if="note.files.length > 0">
 		<summary>({{ i18n.t('withNFiles', { n: note.files.length }) }})</summary>
-		<MkMediaList :media-list="note.files"/>
+		<MkMediaList :mediaList="note.files"/>
 	</details>
 	<details v-if="note.poll">
 		<summary>{{ i18n.ts.poll }}</summary>
@@ -76,10 +76,6 @@ const collapsed = $ref(
 	}
 }
 
-.body {
-
-}
-
 .reply {
 	margin-right: 6px;
 	color: var(--accent);
diff --git a/packages/frontend/src/components/MkSuperMenu.vue b/packages/frontend/src/components/MkSuperMenu.vue
index 2a8e43c57..72b70416d 100644
--- a/packages/frontend/src/components/MkSuperMenu.vue
+++ b/packages/frontend/src/components/MkSuperMenu.vue
@@ -23,22 +23,13 @@
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
 
-export default defineComponent({
-	props: {
-		def: {
-			type: Array,
-			required: true,
-		},
-		grid: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-	},
-});
+defineProps<{
+	def: any[];
+	grid?: boolean;
+}>();
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/components/MkTab.vue b/packages/frontend/src/components/MkTab.vue
index 6f819bbbd..7274f9b31 100644
--- a/packages/frontend/src/components/MkTab.vue
+++ b/packages/frontend/src/components/MkTab.vue
@@ -7,17 +7,17 @@ export default defineComponent({
 			required: true,
 		},
 	},
-	render() {
-		const options = this.$slots.default();
+	setup(props, { emit, slots }) {
+		const options = slots.default();
 
-		return h('div', {
+		return () => h('div', {
 			class: 'pxhvhrfw',
 		}, options.map(option => withDirectives(h('button', {
-			class: ['_button', { active: this.modelValue === option.props.value }],
+			class: ['_button', { active: props.modelValue === option.props.value }],
 			key: option.key,
-			disabled: this.modelValue === option.props.value,
+			disabled: props.modelValue === option.props.value,
 			onClick: () => {
-				this.$emit('update:modelValue', option.props.value);
+				emit('update:modelValue', option.props.value);
 			},
 		}, option.children), [
 			[resolveDirective('click-anime')],
diff --git a/packages/frontend/src/components/MkTagCloud.vue b/packages/frontend/src/components/MkTagCloud.vue
index 4e8d5bab7..6e4e054aa 100644
--- a/packages/frontend/src/components/MkTagCloud.vue
+++ b/packages/frontend/src/components/MkTagCloud.vue
@@ -1,7 +1,7 @@
 <template>
-<div ref="rootEl" class="meijqfqm">
-	<canvas :id="idForCanvas" ref="canvasEl" class="canvas" :width="width" height="300" @contextmenu.prevent="() => {}"></canvas>
-	<div :id="idForTags" ref="tagsEl" class="tags">
+<div ref="rootEl" :class="$style.root">
+	<canvas :id="idForCanvas" ref="canvasEl" style="display: block;" :width="width" height="300" @contextmenu.prevent="() => {}"></canvas>
+	<div :id="idForTags" ref="tagsEl" :class="$style.tags">
 		<ul>
 			<slot></slot>
 		</ul>
@@ -70,21 +70,17 @@ defineExpose({
 });
 </script>
 
-<style lang="scss" scoped>
-.meijqfqm {
+<style lang="scss" module>
+.root {
 	position: relative;
 	overflow: clip;
 	display: grid;
 	place-items: center;
+}
 
-	> .canvas {
-		display: block;
-	}
-
-	> .tags {
-		position: absolute;
-		top: 999px;
-		left: 999px;
-	}
+.tags {
+	position: absolute;
+	top: 999px;
+	left: 999px;
 }
 </style>
diff --git a/packages/frontend/src/components/MkTextarea.vue b/packages/frontend/src/components/MkTextarea.vue
index 82b631edd..83b2ed244 100644
--- a/packages/frontend/src/components/MkTextarea.vue
+++ b/packages/frontend/src/components/MkTextarea.vue
@@ -1,12 +1,12 @@
 <template>
-<div class="adhpbeos">
-	<div class="label" @click="focus"><slot name="label"></slot></div>
-	<div class="input" :class="{ disabled, focused, tall, pre }">
+<div>
+	<div :class="$style.label" @click="focus"><slot name="label"></slot></div>
+	<div :class="{ [$style.disabled]: disabled, [$style.focused]: focused, [$style.tall]: tall, [$style.pre]: pre }" style="position: relative;">
 		<textarea
 			ref="inputEl"
 			v-model="v"
 			v-adaptive-border
-			:class="{ code, _monospace: code }"
+			:class="[$style.textarea, { _monospace: code }]"
 			:disabled="disabled"
 			:required="required"
 			:readonly="readonly"
@@ -20,243 +20,173 @@
 			@input="onInput"
 		></textarea>
 	</div>
-	<div class="caption"><slot name="caption"></slot></div>
+	<div :class="$style.caption"><slot name="caption"></slot></div>
 
-	<MkButton v-if="manualSave && changed" primary class="save" @click="updated"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
+	<MkButton v-if="manualSave && changed" primary :class="$style.save" @click="updated"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent, onMounted, nextTick, ref, watch, computed, toRefs } from 'vue';
+<script lang="ts" setup>
+import { onMounted, nextTick, ref, watch, computed, toRefs, shallowRef } from 'vue';
 import { debounce } from 'throttle-debounce';
 import MkButton from '@/components/MkButton.vue';
 import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		MkButton,
-	},
+const props = defineProps<{
+	modelValue: string | null;
+	required?: boolean;
+	readonly?: boolean;
+	disabled?: boolean;
+	pattern?: string;
+	placeholder?: string;
+	autofocus?: boolean;
+	autocomplete?: string;
+	spellcheck?: boolean;
+	debounce?: boolean;
+	manualSave?: boolean;
+	code?: boolean;
+	tall?: boolean;
+	pre?: boolean;
+}>();
 
-	props: {
-		modelValue: {
-			required: true,
-		},
-		type: {
-			type: String,
-			required: false,
-		},
-		required: {
-			type: Boolean,
-			required: false,
-		},
-		readonly: {
-			type: Boolean,
-			required: false,
-		},
-		disabled: {
-			type: Boolean,
-			required: false,
-		},
-		pattern: {
-			type: String,
-			required: false,
-		},
-		placeholder: {
-			type: String,
-			required: false,
-		},
-		autofocus: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-		autocomplete: {
-			required: false,
-		},
-		spellcheck: {
-			required: false,
-		},
-		code: {
-			type: Boolean,
-			required: false,
-		},
-		tall: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-		pre: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-		debounce: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-		manualSave: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-	},
+const emit = defineEmits<{
+	(ev: 'change', _ev: KeyboardEvent): void;
+	(ev: 'keydown', _ev: KeyboardEvent): void;
+	(ev: 'enter'): void;
+	(ev: 'update:modelValue', value: string): void;
+}>();
 
-	emits: ['change', 'keydown', 'enter', 'update:modelValue'],
+const { modelValue, autofocus } = toRefs(props);
+const v = ref<string>(modelValue.value ?? '');
+const focused = ref(false);
+const changed = ref(false);
+const invalid = ref(false);
+const filled = computed(() => v.value !== '' && v.value != null);
+const inputEl = shallowRef<HTMLTextAreaElement>();
 
-	setup(props, context) {
-		const { modelValue, autofocus } = toRefs(props);
-		const v = ref(modelValue.value);
-		const focused = ref(false);
-		const changed = ref(false);
-		const invalid = ref(false);
-		const filled = computed(() => v.value !== '' && v.value != null);
-		const inputEl = ref(null);
+const focus = () => inputEl.value.focus();
+const onInput = (ev) => {
+	changed.value = true;
+	emit('change', ev);
+};
+const onKeydown = (ev: KeyboardEvent) => {
+	if (ev.isComposing || ev.key === 'Process' || ev.keyCode === 229) return;
 
-		const focus = () => inputEl.value.focus();
-		const onInput = (ev) => {
-			changed.value = true;
-			context.emit('change', ev);
-		};
-		const onKeydown = (ev: KeyboardEvent) => {
-			if (ev.isComposing || ev.key === 'Process' || ev.keyCode === 229) return;
+	emit('keydown', ev);
 
-			context.emit('keydown', ev);
+	if (ev.code === 'Enter') {
+		emit('enter');
+	}
+};
 
-			if (ev.code === 'Enter') {
-				context.emit('enter');
-			}
-		};
+const updated = () => {
+	changed.value = false;
+	emit('update:modelValue', v.value ?? '');
+};
 
-		const updated = () => {
-			changed.value = false;
-			context.emit('update:modelValue', v.value);
-		};
+const debouncedUpdated = debounce(1000, updated);
 
-		const debouncedUpdated = debounce(1000, updated);
+watch(modelValue, newValue => {
+	v.value = newValue;
+});
 
-		watch(modelValue, newValue => {
-			v.value = newValue;
-		});
+watch(v, newValue => {
+	if (!props.manualSave) {
+		if (props.debounce) {
+			debouncedUpdated();
+		} else {
+			updated();
+		}
+	}
 
-		watch(v, newValue => {
-			if (!props.manualSave) {
-				if (props.debounce) {
-					debouncedUpdated();
-				} else {
-					updated();
-				}
-			}
+	invalid.value = inputEl.value.validity.badInput;
+});
 
-			invalid.value = inputEl.value.validity.badInput;
-		});
-
-		onMounted(() => {
-			nextTick(() => {
-				if (autofocus.value) {
-					focus();
-				}
-			});
-		});
-
-		return {
-			v,
-			focused,
-			invalid,
-			changed,
-			filled,
-			inputEl,
-			focus,
-			onInput,
-			onKeydown,
-			updated,
-			i18n,
-		};
-	},
+onMounted(() => {
+	nextTick(() => {
+		if (autofocus.value) {
+			focus();
+		}
+	});
 });
 </script>
 
-<style lang="scss" scoped>
-.adhpbeos {
-	> .label {
-		font-size: 0.85em;
-		padding: 0 0 8px 0;
-		user-select: none;
+<style lang="scss" module>
+.label {
+	font-size: 0.85em;
+	padding: 0 0 8px 0;
+	user-select: none;
 
-		&:empty {
-			display: none;
-		}
-	}
-
-	> .caption {
-		font-size: 0.85em;
-		padding: 8px 0 0 0;
-		color: var(--fgTransparentWeak);
-
-		&:empty {
-			display: none;
-		}
-	}
-
-	> .input {
-		position: relative;
-
-		> textarea {
-			appearance: none;
-			-webkit-appearance: none;
-			display: block;
-			width: 100%;
-			min-width: 100%;
-			max-width: 100%;
-			min-height: 130px;
-			margin: 0;
-			padding: 12px;
-			font: inherit;
-			font-weight: normal;
-			font-size: 1em;
-			color: var(--fg);
-			background: var(--panel);
-			border: solid 1px var(--panel);
-			border-radius: 6px;
-			outline: none;
-			box-shadow: none;
-			box-sizing: border-box;
-			transition: border-color 0.1s ease-out;
-
-			&:hover {
-				border-color: var(--inputBorderHover) !important;
-			}
-		}
-
-		&.focused {
-			> textarea {
-				border-color: var(--accent) !important;
-			}
-		}
-
-		&.disabled {
-			opacity: 0.7;
-
-			&, * {
-				cursor: not-allowed !important;
-			}
-		}
-
-		&.tall {
-			> textarea {
-				min-height: 200px;
-			}
-		}
-
-		&.pre {
-			> textarea {
-				white-space: pre;
-			}
-		}
-	}
-
-	> .save {
-		margin: 8px 0 0 0;
+	&:empty {
+		display: none;
 	}
 }
+
+.caption {
+	font-size: 0.85em;
+	padding: 8px 0 0 0;
+	color: var(--fgTransparentWeak);
+
+	&:empty {
+		display: none;
+	}
+}
+
+.textarea {
+	appearance: none;
+	-webkit-appearance: none;
+	display: block;
+	width: 100%;
+	min-width: 100%;
+	max-width: 100%;
+	min-height: 130px;
+	margin: 0;
+	padding: 12px;
+	font: inherit;
+	font-weight: normal;
+	font-size: 1em;
+	color: var(--fg);
+	background: var(--panel);
+	border: solid 1px var(--panel);
+	border-radius: 6px;
+	outline: none;
+	box-shadow: none;
+	box-sizing: border-box;
+	transition: border-color 0.1s ease-out;
+
+	&:hover {
+		border-color: var(--inputBorderHover) !important;
+	}
+}
+
+.focused {
+	> .textarea {
+		border-color: var(--accent) !important;
+	}
+}
+
+.disabled {
+	opacity: 0.7;
+	cursor: not-allowed !important;
+
+	> .textarea {
+		cursor: not-allowed !important;
+	}
+}
+
+.tall {
+	> .textarea {
+		min-height: 200px;
+	}
+}
+
+.pre {
+	> .textarea {
+		white-space: pre;
+	}
+}
+
+.save {
+	margin: 8px 0 0 0;
+}
 </style>
diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue
index fb0a3a4b6..2595ebc45 100644
--- a/packages/frontend/src/components/MkTimeline.vue
+++ b/packages/frontend/src/components/MkTimeline.vue
@@ -1,11 +1,11 @@
 <template>
-<MkNotes ref="tlComponent" :no-gap="!defaultStore.state.showGapBetweenNotesInTimeline" :pagination="pagination" @queue="emit('queue', $event)"/>
+<MkNotes ref="tlComponent" :noGap="!defaultStore.state.showGapBetweenNotesInTimeline" :pagination="pagination" @queue="emit('queue', $event)"/>
 </template>
 
 <script lang="ts" setup>
 import { computed, provide, onUnmounted } from 'vue';
 import MkNotes from '@/components/MkNotes.vue';
-import { stream } from '@/stream';
+import { useStream } from '@/stream';
 import * as sound from '@/scripts/sound';
 import { $i } from '@/account';
 import { defaultStore } from '@/store';
@@ -46,17 +46,13 @@ const onUserRemoved = () => {
 	tlComponent.pagingComponent?.reload();
 };
 
-const onChangeFollowing = () => {
-	if (!tlComponent.pagingComponent?.backed) {
-		tlComponent.pagingComponent?.reload();
-	}
-};
-
 let endpoint;
 let query;
 let connection;
 let connection2;
 
+const stream = useStream();
+
 if (props.src === 'antenna') {
 	endpoint = 'antennas/notes';
 	query = {
@@ -68,23 +64,41 @@ if (props.src === 'antenna') {
 	connection.on('note', prepend);
 } else if (props.src === 'home') {
 	endpoint = 'notes/timeline';
-	connection = stream.useChannel('homeTimeline');
+	query = {
+		withReplies: defaultStore.state.showTimelineReplies,
+	};
+	connection = stream.useChannel('homeTimeline', {
+		withReplies: defaultStore.state.showTimelineReplies,
+	});
 	connection.on('note', prepend);
 
 	connection2 = stream.useChannel('main');
-	connection2.on('follow', onChangeFollowing);
-	connection2.on('unfollow', onChangeFollowing);
 } else if (props.src === 'local') {
 	endpoint = 'notes/local-timeline';
-	connection = stream.useChannel('localTimeline');
+	query = {
+		withReplies: defaultStore.state.showTimelineReplies,
+	};
+	connection = stream.useChannel('localTimeline', {
+		withReplies: defaultStore.state.showTimelineReplies,
+	});
 	connection.on('note', prepend);
 } else if (props.src === 'social') {
 	endpoint = 'notes/hybrid-timeline';
-	connection = stream.useChannel('hybridTimeline');
+	query = {
+		withReplies: defaultStore.state.showTimelineReplies,
+	};
+	connection = stream.useChannel('hybridTimeline', {
+		withReplies: defaultStore.state.showTimelineReplies,
+	});
 	connection.on('note', prepend);
 } else if (props.src === 'global') {
 	endpoint = 'notes/global-timeline';
-	connection = stream.useChannel('globalTimeline');
+	query = {
+		withReplies: defaultStore.state.showTimelineReplies,
+	};
+	connection = stream.useChannel('globalTimeline', {
+		withReplies: defaultStore.state.showTimelineReplies,
+	});
 	connection.on('note', prepend);
 } else if (props.src === 'mentions') {
 	endpoint = 'notes/mentions';
diff --git a/packages/frontend/src/components/MkToast.vue b/packages/frontend/src/components/MkToast.vue
index ad53c7f28..e135f5647 100644
--- a/packages/frontend/src/components/MkToast.vue
+++ b/packages/frontend/src/components/MkToast.vue
@@ -1,11 +1,11 @@
 <template>
 <div>
 	<Transition
-		:enter-active-class="defaultStore.state.animation ? $style.transition_toast_enterActive : ''"
-		:leave-active-class="defaultStore.state.animation ? $style.transition_toast_leaveActive : ''"
-		:enter-from-class="defaultStore.state.animation ? $style.transition_toast_enterFrom : ''"
-		:leave-to-class="defaultStore.state.animation ? $style.transition_toast_leaveTo : ''"
-		appear @after-leave="emit('closed')"
+		:enterActiveClass="defaultStore.state.animation ? $style.transition_toast_enterActive : ''"
+		:leaveActiveClass="defaultStore.state.animation ? $style.transition_toast_leaveActive : ''"
+		:enterFromClass="defaultStore.state.animation ? $style.transition_toast_enterFrom : ''"
+		:leaveToClass="defaultStore.state.animation ? $style.transition_toast_leaveTo : ''"
+		appear @afterLeave="emit('closed')"
 	>
 		<div v-if="showing" class="_acrylic" :class="$style.root" :style="{ zIndex }">
 			<div style="padding: 16px 24px;">
diff --git a/packages/frontend/src/components/MkTokenGenerateWindow.vue b/packages/frontend/src/components/MkTokenGenerateWindow.vue
index 56be04440..3ddd81aae 100644
--- a/packages/frontend/src/components/MkTokenGenerateWindow.vue
+++ b/packages/frontend/src/components/MkTokenGenerateWindow.vue
@@ -3,16 +3,16 @@
 	ref="dialog"
 	:width="400"
 	:height="450"
-	:with-ok-button="true"
-	:ok-button-disabled="false"
-	:can-close="false"
+	:withOkButton="true"
+	:okButtonDisabled="false"
+	:canClose="false"
 	@close="dialog.close()"
 	@closed="$emit('closed')"
 	@ok="ok()"
 >
 	<template #header>{{ title || i18n.ts.generateAccessToken }}</template>
 
-	<MkSpacer :margin-min="20" :margin-max="28">
+	<MkSpacer :marginMin="20" :marginMax="28">
 		<div class="_gaps_m">
 			<div v-if="information">
 				<MkInfo warn>{{ information }}</MkInfo>
diff --git a/packages/frontend/src/components/MkTooltip.vue b/packages/frontend/src/components/MkTooltip.vue
index 2d34b090e..91c9b70a5 100644
--- a/packages/frontend/src/components/MkTooltip.vue
+++ b/packages/frontend/src/components/MkTooltip.vue
@@ -1,10 +1,10 @@
 <template>
 <Transition
-	:enter-active-class="defaultStore.state.animation ? $style.transition_tooltip_enterActive : ''"
-	:leave-active-class="defaultStore.state.animation ? $style.transition_tooltip_leaveActive : ''"
-	:enter-from-class="defaultStore.state.animation ? $style.transition_tooltip_enterFrom : ''"
-	:leave-to-class="defaultStore.state.animation ? $style.transition_tooltip_leaveTo : ''"
-	appear @after-leave="emit('closed')"
+	:enterActiveClass="defaultStore.state.animation ? $style.transition_tooltip_enterActive : ''"
+	:leaveActiveClass="defaultStore.state.animation ? $style.transition_tooltip_leaveActive : ''"
+	:enterFromClass="defaultStore.state.animation ? $style.transition_tooltip_enterFrom : ''"
+	:leaveToClass="defaultStore.state.animation ? $style.transition_tooltip_leaveTo : ''"
+	appear @afterLeave="emit('closed')"
 >
 	<div v-show="showing" ref="el" :class="$style.root" class="_acrylic _shadow" :style="{ zIndex, maxWidth: maxWidth + 'px' }">
 		<slot>
@@ -41,6 +41,9 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
+// タイミングによっては最初から showing = false な場合があり、その場合に closed 扱いにしないと永久にDOMに残ることになる
+if (!props.showing) emit('closed');
+
 const el = shallowRef<HTMLElement>();
 const zIndex = os.claimZIndex('high');
 
@@ -66,10 +69,8 @@ onMounted(() => {
 		setPosition();
 
 		const loop = () => {
-			loopHandler = window.requestAnimationFrame(() => {
-				setPosition();
-				loop();
-			});
+			setPosition();
+			loopHandler = window.requestAnimationFrame(loop);
 		};
 
 		loop();
diff --git a/packages/frontend/src/components/MkUpdated.vue b/packages/frontend/src/components/MkUpdated.vue
index eed7fa71f..3a0b2abb4 100644
--- a/packages/frontend/src/components/MkUpdated.vue
+++ b/packages/frontend/src/components/MkUpdated.vue
@@ -1,5 +1,5 @@
 <template>
-<MkModal ref="modal" :z-priority="'middle'" @click="$refs.modal.close()" @closed="$emit('closed')">
+<MkModal ref="modal" :zPriority="'middle'" @click="$refs.modal.close()" @closed="$emit('closed')">
 	<div :class="$style.root">
 		<div :class="$style.title"><MkSparkle>{{ i18n.ts.misskeyUpdated }}</MkSparkle></div>
 		<div :class="$style.version">✨{{ version }}🚀</div>
diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue
index 9c5622b1c..fcad5b806 100644
--- a/packages/frontend/src/components/MkUrlPreview.vue
+++ b/packages/frontend/src/components/MkUrlPreview.vue
@@ -22,7 +22,7 @@
 	</div>
 </template>
 <template v-else-if="tweetId && tweetExpanded">
-	<div ref="twitter" :class="$style.twitter">
+	<div ref="twitter">
 		<iframe ref="tweet" scrolling="no" frameborder="no" :style="{ position: 'relative', width: '100%', height: `${tweetHeight}px` }" :src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&amp;hideCard=false&amp;hideThread=false&amp;lang=en&amp;theme=${defaultStore.state.darkMode ? 'dark' : 'light'}&amp;id=${tweetId}`"></iframe>
 	</div>
 	<div :class="$style.action">
@@ -31,7 +31,7 @@
 		</MkButton>
 	</div>
 </template>
-<div v-else :class="$style.urlPreview">
+<div v-else>
 	<component :is="self ? 'MkA' : 'a'" :class="[$style.link, { [$style.compact]: compact }]" :[attr]="self ? url.substr(local.length) : url" rel="nofollow noopener" :target="target" :title="url">
 		<div v-if="thumbnail" :class="$style.thumbnail" :style="`background-image: url('${thumbnail}')`">
 		</div>
@@ -41,14 +41,14 @@
 				<h1 v-else-if="fetching" :class="$style.title"><MkEllipsis/></h1>
 				<h1 v-else :class="$style.title" :title="title ?? undefined">{{ title }}</h1>
 			</header>
-			<p v-if="unknownUrl" :class="$style.text">{{ i18n.ts.cannotLoad }}</p>
+			<p v-if="unknownUrl" :class="$style.text">{{ i18n.ts.failedToPreviewUrl }}</p>
 			<p v-else-if="fetching" :class="$style.text"><MkEllipsis/></p>
 			<p v-else-if="description" :class="$style.text" :title="description">{{ description.length > 85 ? description.slice(0, 85) + '…' : description }}</p>
 			<footer :class="$style.footer">
 				<img v-if="icon" :class="$style.siteIcon" :src="icon"/>
-				<p v-if="unknownUrl" :class="$style.siteName">?</p>
+				<p v-if="unknownUrl" :class="$style.siteName">{{ requestUrl.host }}</p>
 				<p v-else-if="fetching" :class="$style.siteName"><MkEllipsis/></p>
-				<p v-else :class="$style.siteName" :title="sitename ?? undefined">{{ sitename }}</p>
+				<p v-else :class="$style.siteName" :title="sitename ?? requestUrl.host">{{ sitename ?? requestUrl.host }}</p>
 			</footer>
 		</article>
 	</component>
@@ -128,17 +128,33 @@ if (requestUrl.hostname === 'music.youtube.com' && requestUrl.pathname.match('^/
 
 requestUrl.hash = '';
 
-window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${versatileLang}`).then(res => {
-	res.json().then((info: SummalyResult) => {
+window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${versatileLang}`)
+	.then(res => {
+		if (!res.ok) {
+			fetching = false;
+			unknownUrl = true;
+			return;
+		}
+
+		return res.json();
+	})
+	.then((info: SummalyResult) => {
+		if (info.url == null) {
+			fetching = false;
+			unknownUrl = true;
+			return;
+		}
+
+		fetching = false;
+		unknownUrl = false;
+
 		title = info.title;
 		description = info.description;
 		thumbnail = info.thumbnail;
 		icon = info.icon;
 		sitename = info.sitename;
-		fetching = false;
 		player = info.player;
 	});
-});
 
 function adjustTweetHeight(message: any) {
 	if (message.origin !== 'https://platform.twitter.com') return;
@@ -194,13 +210,6 @@ onUnmounted(() => {
 	width: 100%;
 }
 
-.twitter {
-
-}
-
-.urlPreview {
-}
-
 .link {
 	position: relative;
 	display: block;
diff --git a/packages/frontend/src/components/MkUrlPreviewPopup.vue b/packages/frontend/src/components/MkUrlPreviewPopup.vue
index e244be3e9..36a9e2f73 100644
--- a/packages/frontend/src/components/MkUrlPreviewPopup.vue
+++ b/packages/frontend/src/components/MkUrlPreviewPopup.vue
@@ -1,6 +1,6 @@
 <template>
-<div class="fgmtyycl" :style="{ zIndex, top: top + 'px', left: left + 'px' }">
-	<Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" @after-leave="emit('closed')">
+<div :class="$style.root" :style="{ zIndex, top: top + 'px', left: left + 'px' }">
+	<Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" @afterLeave="emit('closed')">
 		<MkUrlPreview v-if="showing" class="_popup _shadow" :url="url"/>
 	</Transition>
 </div>
@@ -36,8 +36,8 @@ onMounted(() => {
 });
 </script>
 
-<style lang="scss" scoped>
-.fgmtyycl {
+<style lang="scss" module>
+.root {
 	position: absolute;
 	width: 500px;
 	max-width: calc(90vw - 12px);
diff --git a/packages/frontend/src/components/MkUserInfo.vue b/packages/frontend/src/components/MkUserInfo.vue
index f560ebcd8..172b51751 100644
--- a/packages/frontend/src/components/MkUserInfo.vue
+++ b/packages/frontend/src/components/MkUserInfo.vue
@@ -8,7 +8,7 @@
 	</div>
 	<span v-if="$i && $i.id !== user.id && user.isFollowed" :class="$style.followed">{{ i18n.ts.followsYou }}</span>
 	<div :class="$style.description">
-		<div v-if="user.description" class="mfm">
+		<div v-if="user.description" :class="$style.mfm">
 			<Mfm :text="user.description" :author="user" :i="$i"/>
 		</div>
 		<span v-else style="opacity: 0.7;">{{ i18n.ts.noAccountDescription }}</span>
@@ -105,7 +105,7 @@ defineProps<{
 .mfm {
 	display: -webkit-box;
 	-webkit-line-clamp: 3;
-	-webkit-box-orient: vertical;  
+	-webkit-box-orient: vertical;
 	overflow: hidden;
 }
 
diff --git a/packages/frontend/src/components/MkUserOnlineIndicator.vue b/packages/frontend/src/components/MkUserOnlineIndicator.vue
index 251ab5d79..a2c2b53b0 100644
--- a/packages/frontend/src/components/MkUserOnlineIndicator.vue
+++ b/packages/frontend/src/components/MkUserOnlineIndicator.vue
@@ -1,5 +1,13 @@
 <template>
-<div v-tooltip="text" :class="[$style.root, $style['status_' + user.onlineStatus]]"></div>
+<div
+	v-tooltip="text"
+	:class="[$style.root, {
+		[$style.status_online]: user.onlineStatus === 'online',
+		[$style.status_active]: user.onlineStatus === 'active',
+		[$style.status_offline]: user.onlineStatus === 'offline',
+		[$style.status_unknown]: user.onlineStatus === 'unknown',
+	}]"
+></div>
 </template>
 
 <script lang="ts" setup>
diff --git a/packages/frontend/src/components/MkUserPopup.vue b/packages/frontend/src/components/MkUserPopup.vue
index 8ca035544..c3b777a12 100644
--- a/packages/frontend/src/components/MkUserPopup.vue
+++ b/packages/frontend/src/components/MkUserPopup.vue
@@ -1,10 +1,10 @@
 <template>
 <Transition
-	:enter-active-class="defaultStore.state.animation ? $style.transition_popup_enterActive : ''"
-	:leave-active-class="defaultStore.state.animation ? $style.transition_popup_leaveActive : ''"
-	:enter-from-class="defaultStore.state.animation ? $style.transition_popup_enterFrom : ''"
-	:leave-to-class="defaultStore.state.animation ? $style.transition_popup_leaveTo : ''"
-	appear @after-leave="emit('closed')"
+	:enterActiveClass="defaultStore.state.animation ? $style.transition_popup_enterActive : ''"
+	:leaveActiveClass="defaultStore.state.animation ? $style.transition_popup_leaveActive : ''"
+	:enterFromClass="defaultStore.state.animation ? $style.transition_popup_enterFrom : ''"
+	:leaveToClass="defaultStore.state.animation ? $style.transition_popup_leaveTo : ''"
+	appear @afterLeave="emit('closed')"
 >
 	<div v-if="showing" :class="$style.root" class="_popup _shadow" :style="{ zIndex, top: top + 'px', left: left + 'px' }" @mouseover="() => { emit('mouseover'); }" @mouseleave="() => { emit('mouseleave'); }">
 		<div v-if="user != null">
@@ -22,7 +22,7 @@
 				<div :class="$style.username"><MkAcct :user="user"/></div>
 			</div>
 			<div :class="$style.description">
-				<Mfm v-if="user.description" :text="user.description" :author="user" :i="$i"/>
+				<Mfm v-if="user.description" :class="$style.mfm" :text="user.description" :author="user" :i="$i"/>
 				<div v-else style="opacity: 0.7;">{{ i18n.ts.noAccountDescription }}</div>
 			</div>
 			<div :class="$style.status">
@@ -192,6 +192,13 @@ onMounted(() => {
 	border-bottom: solid 1px var(--divider);
 }
 
+.mfm {
+	display: -webkit-box;
+	-webkit-line-clamp: 5;
+	-webkit-box-orient: vertical;  
+	overflow: hidden;
+}
+
 .status {
 	padding: 16px 26px 16px 26px;
 }
diff --git a/packages/frontend/src/components/MkUserSelectDialog.vue b/packages/frontend/src/components/MkUserSelectDialog.vue
index dc78bbf42..792ff7afd 100644
--- a/packages/frontend/src/components/MkUserSelectDialog.vue
+++ b/packages/frontend/src/components/MkUserSelectDialog.vue
@@ -1,22 +1,22 @@
 <template>
 <MkModalWindow
 	ref="dialogEl"
-	:with-ok-button="true"
-	:ok-button-disabled="selected == null"
+	:withOkButton="true"
+	:okButtonDisabled="selected == null"
 	@click="cancel()"
 	@close="cancel()"
 	@ok="ok()"
 	@closed="$emit('closed')"
 >
 	<template #header>{{ i18n.ts.selectUser }}</template>
-	<div :class="$style.root">
+	<div>
 		<div :class="$style.form">
-			<FormSplit :min-width="170">
-				<MkInput v-model="username" :autofocus="true" @update:model-value="search">
+			<FormSplit :minWidth="170">
+				<MkInput v-model="username" :autofocus="true" @update:modelValue="search">
 					<template #label>{{ i18n.ts.username }}</template>
 					<template #prefix>@</template>
 				</MkInput>
-				<MkInput v-model="host" :datalist="[hostname]" @update:model-value="search">
+				<MkInput v-model="host" :datalist="[hostname]" @update:modelValue="search">
 					<template #label>{{ i18n.ts.host }}</template>
 					<template #prefix>@</template>
 				</MkInput>
@@ -126,8 +126,6 @@ onMounted(() => {
 </script>
 
 <style lang="scss" module>
-.root {
-}
 
 .form {
 	padding: 0 var(--root-margin);
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Follow.vue b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue
index a2a195cb0..789f88a8f 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.Follow.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue
@@ -2,7 +2,7 @@
 <div class="_gaps">
 	<div style="text-align: center;">{{ i18n.ts._initialAccountSetting.followUsers }}</div>
 
-	<MkFolder :default-open="true">
+	<MkFolder :defaultOpen="true">
 		<template #label>{{ i18n.ts.recommended }}</template>
 
 		<MkPagination :pagination="pinnedUsers">
@@ -14,7 +14,7 @@
 		</MkPagination>
 	</MkFolder>
 
-	<MkFolder :default-open="true">
+	<MkFolder :defaultOpen="true">
 		<template #label>{{ i18n.ts.popularUsers }}</template>
 
 		<MkPagination :pagination="popularUsers">
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue b/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue
index e9f4f68df..5cea67ccf 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue
@@ -4,6 +4,7 @@
 
 	<MkFolder>
 		<template #label>{{ i18n.ts.makeFollowManuallyApprove }}</template>
+		<template #icon><i class="ti ti-lock"></i></template>
 		<template #suffix>{{ isLocked ? i18n.ts.on : i18n.ts.off }}</template>
 
 		<MkSwitch v-model="isLocked">{{ i18n.ts.makeFollowManuallyApprove }}<template #caption>{{ i18n.ts.lockedAccountInfo }}</template></MkSwitch>
@@ -11,6 +12,7 @@
 
 	<MkFolder>
 		<template #label>{{ i18n.ts.hideOnlineStatus }}</template>
+		<template #icon><i class="ti ti-eye-off"></i></template>
 		<template #suffix>{{ hideOnlineStatus ? i18n.ts.on : i18n.ts.off }}</template>
 
 		<MkSwitch v-model="hideOnlineStatus">{{ i18n.ts.hideOnlineStatus }}<template #caption>{{ i18n.ts.hideOnlineStatusDescription }}</template></MkSwitch>
@@ -18,6 +20,7 @@
 
 	<MkFolder>
 		<template #label>{{ i18n.ts.noCrawle }}</template>
+		<template #icon><i class="ti ti-world-x"></i></template>
 		<template #suffix>{{ noCrawle ? i18n.ts.on : i18n.ts.off }}</template>
 
 		<MkSwitch v-model="noCrawle">{{ i18n.ts.noCrawle }}<template #caption>{{ i18n.ts.noCrawleDescription }}</template></MkSwitch>
@@ -25,6 +28,7 @@
 
 	<MkFolder>
 		<template #label>{{ i18n.ts.preventAiLearning }}</template>
+		<template #icon><i class="ti ti-photo-shield"></i></template>
 		<template #suffix>{{ preventAiLearning ? i18n.ts.on : i18n.ts.off }}</template>
 
 		<MkSwitch v-model="preventAiLearning">{{ i18n.ts.preventAiLearning }}<template #caption>{{ i18n.ts.preventAiLearningDescription }}</template></MkSwitch>
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue
index f26ea1121..3107209b9 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue
@@ -12,11 +12,11 @@
 		</div>
 	</FormSlot>
 
-	<MkInput v-model="name" :max="30" manual-save data-cy-user-setup-user-name>
+	<MkInput v-model="name" :max="30" manualSave data-cy-user-setup-user-name>
 		<template #label>{{ i18n.ts._profile.name }}</template>
 	</MkInput>
 
-	<MkTextarea v-model="description" :max="500" tall manual-save data-cy-user-setup-user-description>
+	<MkTextarea v-model="description" :max="500" tall manualSave data-cy-user-setup-user-description>
 		<template #label>{{ i18n.ts._profile.description }}</template>
 	</MkTextarea>
 
@@ -37,8 +37,8 @@ import { chooseFileFromPc } from '@/scripts/select-file';
 import * as os from '@/os';
 import { $i } from '@/account';
 
-const name = ref('');
-const description = ref('');
+const name = ref($i.name ?? '');
+const description = ref($i.description ?? '');
 
 watch(name, () => {
 	os.apiWithDialog('i/update', {
diff --git a/packages/frontend/src/components/MkUserSetupDialog.vue b/packages/frontend/src/components/MkUserSetupDialog.vue
index 4e80a5c0f..566441213 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.vue
@@ -7,10 +7,10 @@
 	@close="close(true)"
 	@closed="emit('closed')"
 >
-	<template v-if="page === 1" #header>{{ i18n.ts._initialAccountSetting.profileSetting }}</template>
-	<template v-else-if="page === 2" #header>{{ i18n.ts._initialAccountSetting.privacySetting }}</template>
-	<template v-else-if="page === 3" #header>{{ i18n.ts.follow }}</template>
-	<template v-else-if="page === 4" #header>{{ i18n.ts.pushNotification }}</template>
+	<template v-if="page === 1" #header><i class="ti ti-user-edit"></i> {{ i18n.ts._initialAccountSetting.profileSetting }}</template>
+	<template v-else-if="page === 2" #header><i class="ti ti-lock"></i> {{ i18n.ts._initialAccountSetting.privacySetting }}</template>
+	<template v-else-if="page === 3" #header><i class="ti ti-user-plus"></i> {{ i18n.ts.follow }}</template>
+	<template v-else-if="page === 4" #header><i class="ti ti-bell-plus"></i> {{ i18n.ts.pushNotification }}</template>
 	<template v-else-if="page === 5" #header>{{ i18n.ts.done }}</template>
 	<template v-else #header>{{ i18n.ts.initialAccountSetting }}</template>
 
@@ -20,65 +20,80 @@
 		</div>
 		<Transition
 			mode="out-in"
-			:enter-active-class="$style.transition_x_enterActive"
-			:leave-active-class="$style.transition_x_leaveActive"
-			:enter-from-class="$style.transition_x_enterFrom"
-			:leave-to-class="$style.transition_x_leaveTo"
+			:enterActiveClass="$style.transition_x_enterActive"
+			:leaveActiveClass="$style.transition_x_leaveActive"
+			:enterFromClass="$style.transition_x_enterFrom"
+			:leaveToClass="$style.transition_x_leaveTo"
 		>
 			<template v-if="page === 0">
 				<div :class="$style.centerPage">
-					<MkSpacer :margin-min="20" :margin-max="28">
+					<MkAnimBg style="position: absolute; top: 0;" :scale="1.5"/>
+					<MkSpacer :marginMin="20" :marginMax="28">
 						<div class="_gaps" style="text-align: center;">
 							<i class="ti ti-confetti" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i>
 							<div style="font-size: 120%;">{{ i18n.ts._initialAccountSetting.accountCreated }}</div>
 							<div>{{ i18n.ts._initialAccountSetting.letsStartAccountSetup }}</div>
 							<MkButton primary rounded gradate style="margin: 16px auto 0 auto;" data-cy-user-setup-continue @click="page++">{{ i18n.ts._initialAccountSetting.profileSetting }} <i class="ti ti-arrow-right"></i></MkButton>
+							<MkButton style="margin: 0 auto;" transparent rounded @click="later(true)">{{ i18n.ts.later }}</MkButton>
 						</div>
 					</MkSpacer>
 				</div>
 			</template>
 			<template v-else-if="page === 1">
 				<div style="height: 100cqh; overflow: auto;">
-					<MkSpacer :margin-min="20" :margin-max="28">
+					<MkSpacer :marginMin="20" :marginMax="28">
 						<XProfile/>
-						<MkButton primary rounded gradate style="margin: 16px auto 0 auto;" data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
+						<div class="_buttonsCenter" style="margin-top: 16px;">
+							<MkButton rounded data-cy-user-setup-back @click="page--"><i class="ti ti-arrow-left"></i> {{ i18n.ts.goBack }}</MkButton>
+							<MkButton primary rounded gradate data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
+						</div>
 					</MkSpacer>
 				</div>
 			</template>
 			<template v-else-if="page === 2">
 				<div style="height: 100cqh; overflow: auto;">
-					<MkSpacer :margin-min="20" :margin-max="28">
+					<MkSpacer :marginMin="20" :marginMax="28">
 						<XPrivacy/>
-						<MkButton primary rounded gradate style="margin: 16px auto 0 auto;" data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
+						<div class="_buttonsCenter" style="margin-top: 16px;">
+							<MkButton rounded data-cy-user-setup-back @click="page--"><i class="ti ti-arrow-left"></i> {{ i18n.ts.goBack }}</MkButton>
+							<MkButton primary rounded gradate data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
+						</div>
 					</MkSpacer>
 				</div>
 			</template>
 			<template v-else-if="page === 3">
 				<div style="height: 100cqh; overflow: auto;">
-					<MkSpacer :margin-min="20" :margin-max="28">
+					<MkSpacer :marginMin="20" :marginMax="28">
 						<XFollow/>
 					</MkSpacer>
 					<div :class="$style.pageFooter">
-						<MkButton primary rounded gradate style="margin: 0 auto;" data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
+						<div class="_buttonsCenter">
+							<MkButton rounded data-cy-user-setup-back @click="page--"><i class="ti ti-arrow-left"></i> {{ i18n.ts.goBack }}</MkButton>
+							<MkButton primary rounded gradate style="" data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
+						</div>
 					</div>
 				</div>
 			</template>
 			<template v-else-if="page === 4">
 				<div :class="$style.centerPage">
-					<MkSpacer :margin-min="20" :margin-max="28">
+					<MkSpacer :marginMin="20" :marginMax="28">
 						<div class="_gaps" style="text-align: center;">
 							<i class="ti ti-bell-ringing-2" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i>
 							<div style="font-size: 120%;">{{ i18n.ts.pushNotification }}</div>
 							<div style="padding: 0 16px;">{{ i18n.t('_initialAccountSetting.pushNotificationDescription', { name: instance.name ?? host }) }}</div>
-							<MkPushNotificationAllowButton primary show-only-to-register style="margin: 0 auto;"/>
-							<MkButton primary rounded gradate style="margin: 16px auto 0 auto;" data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
+							<MkPushNotificationAllowButton primary showOnlyToRegister style="margin: 0 auto;"/>
+							<div class="_buttonsCenter" style="margin-top: 16px;">
+								<MkButton rounded data-cy-user-setup-back @click="page--"><i class="ti ti-arrow-left"></i> {{ i18n.ts.goBack }}</MkButton>
+								<MkButton primary rounded gradate data-cy-user-setup-continue @click="page++">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton>
+							</div>
 						</div>
 					</MkSpacer>
 				</div>
 			</template>
 			<template v-else-if="page === 5">
 				<div :class="$style.centerPage">
-					<MkSpacer :margin-min="20" :margin-max="28">
+					<MkAnimBg style="position: absolute; top: 0;" :scale="1.5"/>
+					<MkSpacer :marginMin="20" :marginMax="28">
 						<div class="_gaps" style="text-align: center;">
 							<i class="ti ti-check" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i>
 							<div style="font-size: 120%;">{{ i18n.ts._initialAccountSetting.initialAccountSettingCompleted }}</div>
@@ -89,7 +104,10 @@
 								</template>
 							</I18n>
 							<div>{{ i18n.t('_initialAccountSetting.haveFun', { name: instance.name ?? host }) }}</div>
-							<MkButton primary rounded gradate style="margin: 16px auto 0 auto;" data-cy-user-setup-continue @click="close(false)">{{ i18n.ts.close }}</MkButton>
+							<div class="_buttonsCenter" style="margin-top: 16px;">
+								<MkButton rounded data-cy-user-setup-back @click="page--"><i class="ti ti-arrow-left"></i> {{ i18n.ts.goBack }}</MkButton>
+								<MkButton primary rounded gradate data-cy-user-setup-continue @click="close(false)">{{ i18n.ts.close }}</MkButton>
+							</div>
 						</div>
 					</MkSpacer>
 				</div>
@@ -106,6 +124,7 @@ import MkButton from '@/components/MkButton.vue';
 import XProfile from '@/components/MkUserSetupDialog.Profile.vue';
 import XFollow from '@/components/MkUserSetupDialog.Follow.vue';
 import XPrivacy from '@/components/MkUserSetupDialog.Privacy.vue';
+import MkAnimBg from '@/components/MkAnimBg.vue';
 import { i18n } from '@/i18n';
 import { instance } from '@/instance';
 import { host } from '@/config';
@@ -137,6 +156,19 @@ async function close(skip: boolean) {
 	dialog.value.close();
 	defaultStore.set('accountSetupWizard', -1);
 }
+
+async function later(later: boolean) {
+	if (later) {
+		const { canceled } = await os.confirm({
+			type: 'warning',
+			text: i18n.ts._initialAccountSetting.laterAreYouSure,
+		});
+		if (canceled) return;
+	}
+
+	dialog.value.close();
+	defaultStore.set('accountSetupWizard', 0);
+}
 </script>
 
 <style lang="scss" module>
@@ -183,7 +215,7 @@ async function close(skip: boolean) {
 	left: 0;
 	padding: 12px;
 	border-top: solid 0.5px var(--divider);
-	-webkit-backdrop-filter: var(--blur, blur(15px));
-	backdrop-filter: var(--blur, blur(15px));
+	-webkit-backdrop-filter: blur(15px);
+	backdrop-filter: blur(15px);
 }
 </style>
diff --git a/packages/frontend/src/components/MkUsersTooltip.vue b/packages/frontend/src/components/MkUsersTooltip.vue
index d0f95fced..0b80c2edc 100644
--- a/packages/frontend/src/components/MkUsersTooltip.vue
+++ b/packages/frontend/src/components/MkUsersTooltip.vue
@@ -1,11 +1,11 @@
 <template>
-<MkTooltip ref="tooltip" :showing="showing" :target-element="targetElement" :max-width="250" @closed="emit('closed')">
+<MkTooltip ref="tooltip" :showing="showing" :targetElement="targetElement" :maxWidth="250" @closed="emit('closed')">
 	<div :class="$style.root">
 		<div v-for="u in users" :key="u.id" :class="$style.user">
 			<MkAvatar :class="$style.avatar" :user="u"/>
-			<MkUserName :class="$style.name" :user="u" :nowrap="true"/>
+			<MkUserName :user="u" :nowrap="true"/>
 		</div>
-		<div v-if="users.length < count" :class="$style.omitted">+{{ count - users.length }}</div>
+		<div v-if="users.length < count">+{{ count - users.length }}</div>
 	</div>
 </MkTooltip>
 </template>
@@ -43,14 +43,6 @@ const emit = defineEmits<{
 	}
 }
 
-.name {
-
-}
-
-.omitted {
-	
-}
-
 .avatar {
 	width: 24px;
 	height: 24px;
diff --git a/packages/frontend/src/components/MkVisibilityPicker.vue b/packages/frontend/src/components/MkVisibilityPicker.vue
index c181d84bc..c8dbe9094 100644
--- a/packages/frontend/src/components/MkVisibilityPicker.vue
+++ b/packages/frontend/src/components/MkVisibilityPicker.vue
@@ -1,5 +1,5 @@
 <template>
-<MkModal ref="modal" v-slot="{ type }" :z-priority="'high'" :src="src" @click="modal.close()" @closed="emit('closed')">
+<MkModal ref="modal" v-slot="{ type }" :zPriority="'high'" :src="src" @click="modal.close()" @closed="emit('closed')">
 	<div class="_popup" :class="{ [$style.root]: true, [$style.asDrawer]: type === 'drawer' }">
 		<div :class="[$style.label, $style.item]">
 			{{ i18n.ts.visibility }}
diff --git a/packages/frontend/src/components/MkVisitorDashboard.vue b/packages/frontend/src/components/MkVisitorDashboard.vue
index 622676812..9566cc651 100644
--- a/packages/frontend/src/components/MkVisitorDashboard.vue
+++ b/packages/frontend/src/components/MkVisitorDashboard.vue
@@ -39,7 +39,7 @@
 			<MkTimeline src="local"/>
 		</div>
 	</div>
-	<div :class="[$style.activeUsersChart, $style.panel]">
+	<div :class="$style.panel">
 		<XActiveUsersChart/>
 	</div>
 </div>
@@ -220,8 +220,4 @@ function exploreOtherServers() {
 	height: 350px;
 	overflow: auto;
 }
-
-.activeUsersChart {
-
-}
 </style>
diff --git a/packages/frontend/src/components/MkWaitingDialog.vue b/packages/frontend/src/components/MkWaitingDialog.vue
index da98da29d..1b6ab1f13 100644
--- a/packages/frontend/src/components/MkWaitingDialog.vue
+++ b/packages/frontend/src/components/MkWaitingDialog.vue
@@ -1,5 +1,5 @@
 <template>
-<MkModal ref="modal" :prefer-type="'dialog'" :z-priority="'high'" @click="success ? done() : () => {}" @closed="emit('closed')">
+<MkModal ref="modal" :preferType="'dialog'" :zPriority="'high'" @click="success ? done() : () => {}" @closed="emit('closed')">
 	<div :class="[$style.root, { [$style.iconOnly]: (text == null) || success }]">
 		<i v-if="success" :class="[$style.icon, $style.success]" class="ti ti-check"></i>
 		<MkLoading v-else :class="[$style.icon, $style.waiting]" :em="true"/>
diff --git a/packages/frontend/src/components/MkWidgets.vue b/packages/frontend/src/components/MkWidgets.vue
index ad1c02a48..30547c744 100644
--- a/packages/frontend/src/components/MkWidgets.vue
+++ b/packages/frontend/src/components/MkWidgets.vue
@@ -1,7 +1,7 @@
 <template>
 <div :class="$style.root">
 	<template v-if="edit">
-		<header :class="$style['edit-header']">
+		<header :class="$style.editHeader">
 			<MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--margin)" data-cy-widget-select>
 				<template #label>{{ i18n.ts.selectWidget }}</template>
 				<option v-for="widget in widgetDefs" :key="widget" :value="widget">{{ i18n.t(`_widgets.${widget}`) }}</option>
@@ -10,26 +10,26 @@
 			<MkButton inline @click="$emit('exit')">{{ i18n.ts.close }}</MkButton>
 		</header>
 		<Sortable
-			:model-value="props.widgets"
-			item-key="id"
+			:modelValue="props.widgets"
+			itemKey="id"
 			handle=".handle"
 			:animation="150"
 			:group="{ name: 'SortableMkWidgets' }"
-			:class="$style['edit-editing']"
-			@update:model-value="v => emit('updateWidgets', v)"
+			:class="$style.editEditing"
+			@update:modelValue="v => emit('updateWidgets', v)"
 		>
 			<template #item="{element}">
-				<div :class="[$style.widget, $style['customize-container']]" data-cy-customize-container>
-					<button :class="$style['customize-container-config']" class="_button" @click.prevent.stop="configWidget(element.id)"><i class="ti ti-settings"></i></button>
-					<button :class="$style['customize-container-remove']" data-cy-customize-container-remove class="_button" @click.prevent.stop="removeWidget(element)"><i class="ti ti-x"></i></button>
+				<div :class="[$style.widget, $style.customizeContainer]" data-cy-customize-container>
+					<button :class="$style.customizeContainerConfig" class="_button" @click.prevent.stop="configWidget(element.id)"><i class="ti ti-settings"></i></button>
+					<button :class="$style.customizeContainerRemove" data-cy-customize-container-remove class="_button" @click.prevent.stop="removeWidget(element)"><i class="ti ti-x"></i></button>
 					<div class="handle">
-						<component :is="`widget-${element.name}`" :ref="el => widgetRefs[element.id] = el" class="widget" :class="$style['customize-container-handle-widget']" :widget="element" @update-props="updateWidget(element.id, $event)"/>
+						<component :is="`widget-${element.name}`" :ref="el => widgetRefs[element.id] = el" class="widget" :class="$style.customizeContainerHandleWidget" :widget="element" @updateProps="updateWidget(element.id, $event)"/>
 					</div>
 				</div>
 			</template>
 		</Sortable>
 	</template>
-	<component :is="`widget-${widget.name}`" v-for="widget in widgets" v-else :key="widget.id" :ref="el => widgetRefs[widget.id] = el" :class="$style.widget" :widget="widget" @update-props="updateWidget(widget.id, $event)" @contextmenu.stop="onContextmenu(widget, $event)"/>
+	<component :is="`widget-${widget.name}`" v-for="widget in widgets" v-else :key="widget.id" :ref="el => widgetRefs[widget.id] = el" :class="$style.widget" :widget="widget" @updateProps="updateWidget(widget.id, $event)" @contextmenu.stop="onContextmenu(widget, $event)"/>
 </div>
 </template>
 
@@ -130,7 +130,7 @@ function onContextmenu(widget: Widget, ev: MouseEvent) {
 }
 
 .edit {
-	&-header {
+	&Header {
 		margin: 16px 0;
 
 		> * {
@@ -139,17 +139,17 @@ function onContextmenu(widget: Widget, ev: MouseEvent) {
 		}
 	}
 
-	&-editing {
+	&Editing {
 		min-height: 100px;
 	}
 }
 
-.customize-container {
+.customizeContainer {
 	position: relative;
 	cursor: move;
 
-	&-config,
-	&-remove {
+	&Config,
+	&Remove {
 		position: absolute;
 		z-index: 10000;
 		top: 8px;
@@ -160,17 +160,17 @@ function onContextmenu(widget: Widget, ev: MouseEvent) {
 		border-radius: 4px;
 	}
 
-	&-config {
+	&Config {
 		right: 8px + 8px + 32px;
 	}
 
-	&-remove {
+	&Remove {
 		right: 8px;
 	}
 
-	&-handle {
+	&Handle {
 
-		&-widget {
+		&Widget {
 			pointer-events: none;
 		}
 	}
diff --git a/packages/frontend/src/components/MkWindow.vue b/packages/frontend/src/components/MkWindow.vue
index b662479b2..dafabf2ba 100644
--- a/packages/frontend/src/components/MkWindow.vue
+++ b/packages/frontend/src/components/MkWindow.vue
@@ -1,11 +1,11 @@
 <template>
 <Transition
-	:enter-active-class="defaultStore.state.animation ? $style.transition_window_enterActive : ''"
-	:leave-active-class="defaultStore.state.animation ? $style.transition_window_leaveActive : ''"
-	:enter-from-class="defaultStore.state.animation ? $style.transition_window_enterFrom : ''"
-	:leave-to-class="defaultStore.state.animation ? $style.transition_window_leaveTo : ''"
+	:enterActiveClass="defaultStore.state.animation ? $style.transition_window_enterActive : ''"
+	:leaveActiveClass="defaultStore.state.animation ? $style.transition_window_leaveActive : ''"
+	:enterFromClass="defaultStore.state.animation ? $style.transition_window_enterFrom : ''"
+	:leaveToClass="defaultStore.state.animation ? $style.transition_window_leaveTo : ''"
 	appear
-	@after-leave="$emit('closed')"
+	@afterLeave="$emit('closed')"
 >
 	<div v-if="showing" ref="rootEl" :class="[$style.root, { [$style.maximized]: maximized }]">
 		<div :class="$style.body" class="_shadow" @mousedown="onBodyMousedown" @keydown="onKeydown">
diff --git a/packages/frontend/src/components/MkYouTubePlayer.vue b/packages/frontend/src/components/MkYouTubePlayer.vue
index 4d765fe2f..0edfd98ef 100644
--- a/packages/frontend/src/components/MkYouTubePlayer.vue
+++ b/packages/frontend/src/components/MkYouTubePlayer.vue
@@ -1,5 +1,5 @@
 <template>
-<MkWindow :initial-width="640" :initial-height="402" :can-resize="true" :close-button="true">
+<MkWindow :initialWidth="640" :initialHeight="402" :canResize="true" :closeButton="true">
 	<template #header>
 		<i class="icon ti ti-brand-youtube" style="margin-right: 0.5em;"></i>
 		<span>{{ title ?? 'YouTube' }}</span>
diff --git a/packages/frontend/src/components/form/link.vue b/packages/frontend/src/components/form/link.vue
index a1775c0bd..22b5edc3c 100644
--- a/packages/frontend/src/components/form/link.vue
+++ b/packages/frontend/src/components/form/link.vue
@@ -1,19 +1,19 @@
 <template>
-<div class="ffcbddfc" :class="{ inline }">
-	<a v-if="external" class="main _button" :href="to" target="_blank">
-		<span class="icon"><slot name="icon"></slot></span>
-		<span class="text"><slot></slot></span>
-		<span class="right">
-			<span class="text"><slot name="suffix"></slot></span>
-			<i class="ti ti-external-link icon"></i>
+<div :class="[$style.root, { [$style.inline]: inline }]">
+	<a v-if="external" :class="$style.main" class="_button" :href="to" target="_blank">
+		<span :class="$style.icon"><slot name="icon"></slot></span>
+		<span :class="$style.text"><slot></slot></span>
+		<span :class="$style.suffix">
+			<span :class="$style.suffixText"><slot name="suffix"></slot></span>
+			<i class="ti ti-external-link"></i>
 		</span>
 	</a>
-	<MkA v-else class="main _button" :class="{ active }" :to="to" :behavior="behavior">
-		<span class="icon"><slot name="icon"></slot></span>
-		<span class="text"><slot></slot></span>
-		<span class="right">
-			<span class="text"><slot name="suffix"></slot></span>
-			<i class="ti ti-chevron-right icon"></i>
+	<MkA v-else :class="[$style.main, { [$style.active]: active }]" class="_button" :to="to" :behavior="behavior">
+		<span :class="$style.icon"><slot name="icon"></slot></span>
+		<span :class="$style.text"><slot></slot></span>
+		<span :class="$style.suffix">
+			<span :class="$style.suffixText"><slot name="suffix"></slot></span>
+			<i class="ti ti-chevron-right"></i>
 		</span>
 	</MkA>
 </div>
@@ -26,70 +26,70 @@ const props = defineProps<{
 	to: string;
 	active?: boolean;
 	external?: boolean;
-	behavior?: null | 'window' | 'browser' | 'modalWindow';
+	behavior?: null | 'window' | 'browser';
 	inline?: boolean;
 }>();
 </script>
 
-<style lang="scss" scoped>
-.ffcbddfc {
+<style lang="scss" module>
+.root {
 	display: block;
 
 	&.inline {
 		display: inline-block;
 	}
+}
 
-	> .main {
-		display: flex;
-		align-items: center;
-		width: 100%;
-		box-sizing: border-box;
-		padding: 10px 14px;
-		background: var(--buttonBg);
-		border-radius: 6px;
-		font-size: 0.9em;
+.main {
+	display: flex;
+	align-items: center;
+	width: 100%;
+	box-sizing: border-box;
+	padding: 10px 14px;
+	background: var(--buttonBg);
+	border-radius: 6px;
+	font-size: 0.9em;
 
-		&:hover {
-			text-decoration: none;
-			background: var(--buttonHoverBg);
-		}
+	&:hover {
+		text-decoration: none;
+		background: var(--buttonHoverBg);
+	}
 
-		&.active {
-			color: var(--accent);
-			background: var(--buttonHoverBg);
-		}
+	&.active {
+		color: var(--accent);
+		background: var(--buttonHoverBg);
+	}
+}
 
-		> .icon {
-			margin-right: 0.75em;
-			flex-shrink: 0;
-			text-align: center;
-			color: var(--fgTransparentWeak);
+.icon {
+	margin-right: 0.75em;
+	flex-shrink: 0;
+	text-align: center;
+	color: var(--fgTransparentWeak);
 
-			&:empty {
-				display: none;
+	&:empty {
+		display: none;
 
-				& + .text {
-					padding-left: 4px;
-				}
-			}
-		}
-
-		> .text {
-			flex-shrink: 1;
-			white-space: normal;
-			padding-right: 12px;
-			text-align: center;
-		}
-
-		> .right {
-			margin-left: auto;
-			opacity: 0.7;
-			white-space: nowrap;
-
-			> .text:not(:empty) {
-				margin-right: 0.75em;
-			}
+		& + .text {
+			padding-left: 4px;
 		}
 	}
 }
+
+.text {
+	flex-shrink: 1;
+	white-space: normal;
+	padding-right: 12px;
+	text-align: center;
+}
+
+.suffix {
+	margin-left: auto;
+	opacity: 0.7;
+	white-space: nowrap;
+
+	> .suffixText:not(:empty) {
+		margin-right: 0.75em;
+	}
+}
 </style>
diff --git a/packages/frontend/src/components/form/slot.vue b/packages/frontend/src/components/form/slot.vue
index 79ce8fe51..809d80620 100644
--- a/packages/frontend/src/components/form/slot.vue
+++ b/packages/frontend/src/components/form/slot.vue
@@ -1,10 +1,10 @@
 <template>
-<div class="adhpbeou">
-	<div class="label" @click="focus"><slot name="label"></slot></div>
-	<div class="content">
+<div>
+	<div :class="$style.label" @click="focus"><slot name="label"></slot></div>
+	<div>
 		<slot></slot>
 	</div>
-	<div class="caption"><slot name="caption"></slot></div>
+	<div :class="$style.caption"><slot name="caption"></slot></div>
 </div>
 </template>
 
@@ -16,26 +16,24 @@ function focus() {
 }
 </script>
 
-<style lang="scss" scoped>
-.adhpbeou {
-	> .label {
-		font-size: 0.85em;
-		padding: 0 0 8px 0;
-		user-select: none;
+<style lang="scss" module>
+.label {
+	font-size: 0.85em;
+	padding: 0 0 8px 0;
+	user-select: none;
 
-		&:empty {
-			display: none;
-		}
+	&:empty {
+		display: none;
 	}
+}
 
-	> .caption {
-		font-size: 0.85em;
-		padding: 8px 0 0 0;
-		color: var(--fgTransparentWeak);
+.caption {
+	font-size: 0.85em;
+	padding: 8px 0 0 0;
+	color: var(--fgTransparentWeak);
 
-		&:empty {
-			display: none;
-		}
+	&:empty {
+		display: none;
 	}
 }
 </style>
diff --git a/packages/frontend/src/components/form/suspense.vue b/packages/frontend/src/components/form/suspense.vue
index 3a44c3da3..b3d8c22b2 100644
--- a/packages/frontend/src/components/form/suspense.vue
+++ b/packages/frontend/src/components/form/suspense.vue
@@ -1,102 +1,66 @@
 <template>
-<Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in">
-	<div v-if="pending">
-		<MkLoading/>
+<div v-if="pending">
+	<MkLoading/>
+</div>
+<div v-else-if="resolved">
+	<slot :result="result"></slot>
+</div>
+<div v-else>
+	<div :class="$style.error">
+		<div><i class="ti ti-alert-triangle"></i> {{ i18n.ts.somethingHappened }}</div>
+		<MkButton inline style="margin-top: 16px;" @click="retry"><i class="ti ti-reload"></i> {{ i18n.ts.retry }}</MkButton>
 	</div>
-	<div v-else-if="resolved">
-		<slot :result="result"></slot>
-	</div>
-	<div v-else>
-		<div class="wszdbhzo">
-			<div><i class="ti ti-alert-triangle"></i> {{ i18n.ts.somethingHappened }}</div>
-			<MkButton inline class="retry" @click="retry"><i class="ti ti-reload"></i> {{ i18n.ts.retry }}</MkButton>
-		</div>
-	</div>
-</Transition>
+</div>
 </template>
 
-<script lang="ts">
-import { defineComponent, PropType, ref, watch } from 'vue';
+<script lang="ts" setup>
+import { ref, watch } from 'vue';
 import MkButton from '@/components/MkButton.vue';
 import { defaultStore } from '@/store';
 import { i18n } from '@/i18n';
 
-export default defineComponent({
-	components: {
-		MkButton,
-	},
+const props = defineProps<{
+	p: () => Promise<any>;
+}>();
 
-	props: {
-		p: {
-			type: Function as PropType<() => Promise<any>>,
-			required: true,
-		},
-	},
+const pending = ref(true);
+const resolved = ref(false);
+const rejected = ref(false);
+const result = ref(null);
 
-	setup(props, context) {
-		const pending = ref(true);
-		const resolved = ref(false);
-		const rejected = ref(false);
-		const result = ref(null);
+const process = () => {
+	if (props.p == null) {
+		return;
+	}
+	const promise = props.p();
+	pending.value = true;
+	resolved.value = false;
+	rejected.value = false;
+	promise.then((_result) => {
+		pending.value = false;
+		resolved.value = true;
+		result.value = _result;
+	});
+	promise.catch(() => {
+		pending.value = false;
+		rejected.value = true;
+	});
+};
 
-		const process = () => {
-			if (props.p == null) {
-				return;
-			}
-			const promise = props.p();
-			pending.value = true;
-			resolved.value = false;
-			rejected.value = false;
-			promise.then((_result) => {
-				pending.value = false;
-				resolved.value = true;
-				result.value = _result;
-			});
-			promise.catch(() => {
-				pending.value = false;
-				rejected.value = true;
-			});
-		};
-
-		watch(() => props.p, () => {
-			process();
-		}, {
-			immediate: true,
-		});
-
-		const retry = () => {
-			process();
-		};
-
-		return {
-			pending,
-			resolved,
-			rejected,
-			result,
-			retry,
-			defaultStore,
-			i18n,
-		};
-	},
+watch(() => props.p, () => {
+	process();
+}, {
+	immediate: true,
 });
+
+const retry = () => {
+	process();
+};
 </script>
 
-<style lang="scss" scoped>
-.fade-enter-active,
-.fade-leave-active {
-	transition: opacity 0.125s ease;
-}
-.fade-enter-from,
-.fade-leave-to {
-	opacity: 0;
-}
-
-.wszdbhzo {
+<style lang="scss" module>
+.error {
 	padding: 16px;
 	text-align: center;
-
-	> .retry {
-		margin-top: 16px;
-	}
 }
 </style>
diff --git a/packages/frontend/src/components/global/MkA.vue b/packages/frontend/src/components/global/MkA.vue
index 40d134dff..4e608c6ef 100644
--- a/packages/frontend/src/components/global/MkA.vue
+++ b/packages/frontend/src/components/global/MkA.vue
@@ -15,7 +15,7 @@ import { useRouter } from '@/router';
 const props = withDefaults(defineProps<{
 	to: string;
 	activeClass?: null | string;
-	behavior?: null | 'window' | 'browser' | 'modalWindow';
+	behavior?: null | 'window' | 'browser';
 }>(), {
 	activeClass: null,
 	behavior: null,
@@ -70,14 +70,6 @@ function openWindow() {
 	os.pageWindow(props.to);
 }
 
-function modalWindow() {
-	os.modalPageWindow(props.to);
-}
-
-function popout() {
-	popout_(props.to);
-}
-
 function nav(ev: MouseEvent) {
 	if (props.behavior === 'browser') {
 		location.href = props.to;
@@ -87,8 +79,6 @@ function nav(ev: MouseEvent) {
 	if (props.behavior) {
 		if (props.behavior === 'window') {
 			return openWindow();
-		} else if (props.behavior === 'modalWindow') {
-			return modalWindow();
 		}
 	}
 
diff --git a/packages/frontend/src/components/global/MkAcct.vue b/packages/frontend/src/components/global/MkAcct.vue
index 59358aef7..f93659f5e 100644
--- a/packages/frontend/src/components/global/MkAcct.vue
+++ b/packages/frontend/src/components/global/MkAcct.vue
@@ -1,5 +1,5 @@
 <template>
-<MkCondensedLine v-if="defaultStore.state.enableCondensedLineForAcct" :min-scale="2 / 3">
+<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>
diff --git a/packages/frontend/src/components/global/MkAd.vue b/packages/frontend/src/components/global/MkAd.vue
index aa975600f..8b25ab1b6 100644
--- a/packages/frontend/src/components/global/MkAd.vue
+++ b/packages/frontend/src/components/global/MkAd.vue
@@ -1,6 +1,14 @@
 <template>
 <div v-if="chosen && !shouldHide" :class="$style.root">
-	<div v-if="!showMenu" :class="[$style.main, $style['form_' + chosen.place]]">
+	<div
+		v-if="!showMenu"
+		:class="[$style.main, {
+			[$style.form_square]: chosen.place === 'square',
+			[$style.form_horizontal]: chosen.place === 'horizontal',
+			[$style.form_horizontalBig]: chosen.place === 'horizontal-big',
+			[$style.form_vertical]: chosen.place === 'vertical',
+		}]"
+	>
 		<a :href="chosen.url" target="_blank" :class="$style.link">
 			<img :src="chosen.imageUrl" :class="$style.img">
 			<button class="_button" :class="$style.i" @click.prevent.stop="toggleMenu"><i :class="$style.iIcon" class="ti ti-info-circle"></i></button>
@@ -122,7 +130,7 @@ function reduceFrequency(): void {
 		}
 	}
 
-	&.form_horizontal-big {
+	&.form_horizontalBig {
 		padding: 8px;
 
 		> .link,
diff --git a/packages/frontend/src/components/global/MkAvatar.vue b/packages/frontend/src/components/global/MkAvatar.vue
index 42abdcbdc..422b35c9d 100644
--- a/packages/frontend/src/components/global/MkAvatar.vue
+++ b/packages/frontend/src/components/global/MkAvatar.vue
@@ -1,6 +1,6 @@
 <template>
 <component :is="link ? MkA : 'span'" v-user-preview="preview ? user.id : undefined" v-bind="bound" class="_noSelect" :class="[$style.root, { [$style.animation]: animation, [$style.cat]: user.isCat, [$style.square]: squareAvatars }]" :style="{ color }" :title="acct(user)" @click="onClick">
-	<img :class="$style.inner" :src="url" decoding="async"/>
+	<MkImgWithBlurhash :class="$style.inner" :src="url" :hash="user?.avatarBlurhash" :cover="true"/>
 	<MkUserOnlineIndicator v-if="indicator" :class="$style.indicator" :user="user"/>
 	<div v-if="user.isCat" :class="[$style.ears]">
 		<div :class="$style.earLeft">
@@ -24,6 +24,7 @@
 <script lang="ts" setup>
 import { watch } from 'vue';
 import * as misskey from 'misskey-js';
+import MkImgWithBlurhash from '../MkImgWithBlurhash.vue';
 import MkA from './MkA.vue';
 import { getStaticImageUrl } from '@/scripts/media-proxy';
 import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash';
diff --git a/packages/frontend/src/components/global/MkCondensedLine.vue b/packages/frontend/src/components/global/MkCondensedLine.vue
index 1d46ff1ec..4b2e8e475 100644
--- a/packages/frontend/src/components/global/MkCondensedLine.vue
+++ b/packages/frontend/src/components/global/MkCondensedLine.vue
@@ -13,13 +13,20 @@ interface Props {
 
 const contentSymbol = Symbol();
 const observer = new ResizeObserver((entries) => {
+  const results: {
+    container: HTMLSpanElement;
+    transform: string;
+  }[] = [];
 	for (const entry of entries) {
 		const content = (entry.target[contentSymbol] ? entry.target : entry.target.firstElementChild) as HTMLSpanElement;
 		const props: Required<Props> = content[contentSymbol];
 		const container = content.parentElement as HTMLSpanElement;
 		const contentWidth = content.getBoundingClientRect().width;
 		const containerWidth = container.getBoundingClientRect().width;
-		container.style.transform = `scaleX(${Math.max(props.minScale, Math.min(1, containerWidth / contentWidth))})`;
+		results.push({ container, transform: `scaleX(${Math.max(props.minScale, Math.min(1, containerWidth / contentWidth))})` });
+	}
+	for (const result of results) {
+		result.container.style.transform = result.transform;
 	}
 });
 </script>
diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue
index 0cb31ffcb..e8a7f17cc 100644
--- a/packages/frontend/src/components/global/MkCustomEmoji.vue
+++ b/packages/frontend/src/components/global/MkCustomEmoji.vue
@@ -7,7 +7,7 @@
 import { computed } from 'vue';
 import { getProxiedImageUrl, getStaticImageUrl } from '@/scripts/media-proxy';
 import { defaultStore } from '@/store';
-import { customEmojis } from '@/custom-emojis';
+import { customEmojisMap } from '@/custom-emojis';
 
 const props = defineProps<{
 	name: string;
@@ -26,7 +26,7 @@ const rawUrl = computed(() => {
 		return props.url;
 	}
 	if (isLocal.value) {
-		return customEmojis.value.find(x => x.name === customEmojiName.value)?.url ?? null;
+		return customEmojisMap.get(customEmojiName.value)?.url ?? null;
 	}
 	return props.host ? `/emoji/${customEmojiName.value}@${props.host}.webp` : `/emoji/${customEmojiName.value}.webp`;
 });
diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.stories.impl.ts b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.stories.impl.ts
index f6811b674..685b3b8b8 100644
--- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.stories.impl.ts
@@ -1,8 +1,8 @@
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
 import { StoryObj } from '@storybook/vue3';
-import MkMisskeyFlavoredMarkdown from './MkMisskeyFlavoredMarkdown.vue';
 import { within } from '@storybook/testing-library';
 import { expect } from '@storybook/jest';
+import MkMisskeyFlavoredMarkdown from './MkMisskeyFlavoredMarkdown.ts';
 export const Default = {
 	render(args) {
 		return {
diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts
new file mode 100644
index 000000000..2a50a3439
--- /dev/null
+++ b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts
@@ -0,0 +1,367 @@
+import { VNode, h } from 'vue';
+import * as mfm from 'mfm-js';
+import * as Misskey from 'misskey-js';
+import MkUrl from '@/components/global/MkUrl.vue';
+import MkLink from '@/components/MkLink.vue';
+import MkMention from '@/components/MkMention.vue';
+import MkEmoji from '@/components/global/MkEmoji.vue';
+import MkCustomEmoji from '@/components/global/MkCustomEmoji.vue';
+import MkCode from '@/components/MkCode.vue';
+import MkGoogle from '@/components/MkGoogle.vue';
+import MkSparkle from '@/components/MkSparkle.vue';
+import MkA from '@/components/global/MkA.vue';
+import { host } from '@/config';
+import { defaultStore } from '@/store';
+
+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(' ');
+
+export default function(props: {
+	text: string;
+	plain?: boolean;
+	nowrap?: boolean;
+	author?: Misskey.entities.UserLite;
+	i?: Misskey.entities.UserLite;
+	isNote?: boolean;
+	emojiUrls?: string[];
+	rootScale?: number;
+}) {
+	const isNote = props.isNote !== undefined ? props.isNote : true;
+
+	if (props.text == null || props.text === '') return;
+
+	const ast = (props.plain ? mfm.parseSimple : mfm.parse)(props.text);
+
+	const validTime = (t: string | null | undefined) => {
+		if (t == null) return null;
+		return t.match(/^[0-9.]+s$/) ? t : null;
+	};
+
+	const useAnim = defaultStore.state.advancedMfm && defaultStore.state.animatedMfm;
+
+	/**
+	 * Gen Vue Elements from MFM AST
+	 * @param ast MFM AST
+	 * @param scale How times large the text is
+	 */
+	const genEl = (ast: mfm.MfmNode[], scale: number) => ast.map((token): VNode | string | (VNode | string)[] => {
+		switch (token.type) {
+			case 'text': {
+				const text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n');
+
+				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;
+				switch (token.props.name) {
+					case 'tada': {
+						const speed = validTime(token.props.args.speed) ?? '1s';
+						style = 'font-size: 150%;' + (useAnim ? `animation: tada ${speed} linear infinite both;` : '');
+						break;
+					}
+					case 'jelly': {
+						const speed = validTime(token.props.args.speed) ?? '1s';
+						style = (useAnim ? `animation: mfm-rubberBand ${speed} linear infinite both;` : '');
+						break;
+					}
+					case 'twitch': {
+						const speed = validTime(token.props.args.speed) ?? '0.5s';
+						style = useAnim ? `animation: mfm-twitch ${speed} ease infinite;` : '';
+						break;
+					}
+					case 'shake': {
+						const speed = validTime(token.props.args.speed) ?? '0.5s';
+						style = useAnim ? `animation: mfm-shake ${speed} ease infinite;` : '';
+						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';
+						style = useAnim ? `animation: ${anime} ${speed} linear infinite; animation-direction: ${direction};` : '';
+						break;
+					}
+					case 'jump': {
+						const speed = validTime(token.props.args.speed) ?? '0.75s';
+						style = useAnim ? `animation: mfm-jump ${speed} linear infinite;` : '';
+						break;
+					}
+					case 'bounce': {
+						const speed = validTime(token.props.args.speed) ?? '0.75s';
+						style = useAnim ? `animation: mfm-bounce ${speed} linear infinite; transform-origin: center bottom;` : '';
+						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: defaultStore.state.advancedMfm ? 'mfm-x2' : '',
+						}, genEl(token.children, scale * 2));
+					}
+					case 'x3': {
+						return h('span', {
+							class: defaultStore.state.advancedMfm ? 'mfm-x3' : '',
+						}, genEl(token.children, scale * 3));
+					}
+					case 'x4': {
+						return h('span', {
+							class: defaultStore.state.advancedMfm ? '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': {
+						const speed = validTime(token.props.args.speed) ?? '1s';
+						style = useAnim ? `animation: mfm-rainbow ${speed} linear infinite;` : '';
+						break;
+					}
+					case 'sparkle': {
+						if (!useAnim) {
+							return genEl(token.children, scale);
+						}
+						return h(MkSparkle, {}, genEl(token.children, scale));
+					}
+					case 'rotate': {
+						const degrees = parseFloat(token.props.args.deg ?? '90');
+						style = `transform: rotate(${degrees}deg); transform-origin: center center;`;
+						break;
+					}
+					case 'position': {
+						if (!defaultStore.state.advancedMfm) break;
+						const x = parseFloat(token.props.args.x ?? '0');
+						const y = parseFloat(token.props.args.y ?? '0');
+						style = `transform: translateX(${x}em) translateY(${y}em);`;
+						break;
+					}
+					case 'scale': {
+						if (!defaultStore.state.advancedMfm) {
+							style = '';
+							break;
+						}
+						const x = Math.min(parseFloat(token.props.args.x ?? '1'), 5);
+						const y = Math.min(parseFloat(token.props.args.y ?? '1'), 5);
+						style = `transform: scale(${x}, ${y});`; 
+						scale = scale * Math.max(x, y);
+						break;
+					}
+					case 'fg': {
+						let color = token.props.args.color;
+						if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00';
+						style = `color: #${color};`;
+						break;
+					}
+					case 'bg': {
+						let color = token.props.args.color;
+						if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00';
+						style = `background-color: #${color};`;
+						break;
+					}
+				}
+				if (style == null) {
+					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;',
+				}, genEl(token.children, scale))];
+			}
+
+			case 'url': {
+				return [h(MkUrl, {
+					key: Math.random(),
+					url: token.props.url,
+					rel: 'nofollow noopener',
+				})];
+			}
+
+			case 'link': {
+				return [h(MkLink, {
+					key: Math.random(),
+					url: token.props.url,
+					rel: 'nofollow noopener',
+				}, genEl(token.children, scale))];
+			}
+
+			case 'mention': {
+				return [h(MkMention, {
+					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(MkA, {
+					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(MkCode, {
+					key: Math.random(),
+					code: token.props.code,
+					lang: token.props.lang,
+				})];
+			}
+
+			case 'inlineCode': {
+				return [h(MkCode, {
+					key: Math.random(),
+					code: token.props.code,
+					inline: true,
+				})];
+			}
+
+			case 'quote': {
+				if (!props.nowrap) {
+					return [h('div', {
+						style: QUOTE_STYLE,
+					}, genEl(token.children, scale))];
+				} else {
+					return [h('span', {
+						style: QUOTE_STYLE,
+					}, genEl(token.children, scale))];
+				}
+			}
+
+			case 'emojiCode': {
+				// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+				if (props.author?.host == null) {
+					return [h(MkCustomEmoji, {
+						key: Math.random(),
+						name: token.props.name,
+						normal: props.plain,
+						host: null,
+						useOriginalSize: scale >= 2.5,
+					})];
+				} 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(MkCustomEmoji, {
+							key: Math.random(),
+							name: token.props.name,
+							// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+							url: props.emojiUrls ? props.emojiUrls[token.props.name] : null,
+							normal: props.plain,
+							host: props.author.host,
+							useOriginalSize: scale >= 2.5,
+						})];
+					}
+				}
+			}
+
+			case 'unicodeEmoji': {
+				return [h(MkEmoji, {
+					key: Math.random(),
+					emoji: token.props.emoji,
+				})];
+			}
+
+			case 'mathInline': {
+				return [h('code', token.props.formula)];
+			}
+
+			case 'mathBlock': {
+				return [h('code', token.props.formula)];
+			}
+
+			case 'search': {
+				return [h(MkGoogle, {
+					key: Math.random(),
+					q: token.props.query,
+				})];
+			}
+
+			case 'plain': {
+				return [h('span', genEl(token.children, scale))];
+			}
+
+			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('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(ast, props.rootScale ?? 1));
+}
diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.vue b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.vue
deleted file mode 100644
index 28a0d1c98..000000000
--- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.vue
+++ /dev/null
@@ -1,171 +0,0 @@
-<template>
-<MfmCore :text="text" :plain="plain" :nowrap="nowrap" :author="author" :is-note="isNote" :class="[$style.root, { [$style.nowrap]: nowrap }]"/>
-</template>
-
-<script lang="ts" setup>
-import { } from 'vue';
-import MfmCore from '@/components/mfm';
-
-const props = withDefaults(defineProps<{
-	text: string;
-	plain?: boolean;
-	nowrap?: boolean;
-	author?: any;
-	isNote?: boolean;
-}>(), {
-	plain: false,
-	nowrap: false,
-	author: null,
-	isNote: true,
-});
-</script>
-
-<style lang="scss">
-._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%;
-		}
-	}
-}
-
-@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%); }
-}
-</style>
-
-<style lang="scss" module>
-.root {
-	white-space: pre-wrap;
-
-	&.nowrap {
-		white-space: pre;
-		word-wrap: normal; // https://codeday.me/jp/qa/20190424/690106.html
-		overflow: hidden;
-		text-overflow: ellipsis;
-	}
-}
-</style>
diff --git a/packages/frontend/src/components/global/MkPageHeader.tabs.vue b/packages/frontend/src/components/global/MkPageHeader.tabs.vue
index 9e1da64e6..d71343baf 100644
--- a/packages/frontend/src/components/global/MkPageHeader.tabs.vue
+++ b/packages/frontend/src/components/global/MkPageHeader.tabs.vue
@@ -15,8 +15,8 @@
 					{{ t.title }}
 				</div>
 				<Transition
-					v-else mode="in-out" @enter="enter" @after-enter="afterEnter" @leave="leave"
-					@after-leave="afterLeave"
+					v-else mode="in-out" @enter="enter" @afterEnter="afterEnter" @leave="leave"
+					@afterLeave="afterLeave"
 				>
 					<div v-show="t.key === tab" :class="[$style.tabTitle, $style.animate]">{{ t.title }}</div>
 				</Transition>
diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue
index b91d378b1..0a21d39bc 100644
--- a/packages/frontend/src/components/global/MkPageHeader.vue
+++ b/packages/frontend/src/components/global/MkPageHeader.vue
@@ -21,7 +21,7 @@
 					</div>
 				</div>
 			</div>
-			<XTabs v-if="!narrow || hideTitle" :class="$style.tabs" :tab="tab" :tabs="tabs" :root-el="el" @update:tab="key => emit('update:tab', key)" @tab-click="onTabClick"/>
+			<XTabs v-if="!narrow || hideTitle" :class="$style.tabs" :tab="tab" :tabs="tabs" :rootEl="el" @update:tab="key => emit('update:tab', key)" @tabClick="onTabClick"/>
 		</template>
 		<div v-if="(!thin_ && narrow && !hideTitle) || (actions && actions.length > 0)" :class="$style.buttonsRight">
 			<template v-for="action in actions">
@@ -30,7 +30,7 @@
 		</div>
 	</div>
 	<div v-if="(narrow && !hideTitle) && hasTabs" :class="[$style.lower, { [$style.slim]: narrow, [$style.thin]: thin_ }]">
-		<XTabs :class="$style.tabs" :tab="tab" :tabs="tabs" :root-el="el" @update:tab="key => emit('update:tab', key)" @tab-click="onTabClick"/>
+		<XTabs :class="$style.tabs" :tab="tab" :tabs="tabs" :rootEl="el" @update:tab="key => emit('update:tab', key)" @tabClick="onTabClick"/>
 	</div>
 </div>
 </template>
diff --git a/packages/frontend/src/components/global/MkStickyContainer.vue b/packages/frontend/src/components/global/MkStickyContainer.vue
index 44c02088d..e5dba54b4 100644
--- a/packages/frontend/src/components/global/MkStickyContainer.vue
+++ b/packages/frontend/src/components/global/MkStickyContainer.vue
@@ -14,6 +14,7 @@
 
 <script lang="ts" setup>
 import { onMounted, onUnmounted, provide, inject, Ref, ref, watch } from 'vue';
+import { $$ } from 'vue/macros';
 import { CURRENT_STICKY_BOTTOM, CURRENT_STICKY_TOP } from '@/const';
 
 const rootEl = $shallowRef<HTMLElement>();
@@ -83,8 +84,8 @@ onMounted(() => {
 onUnmounted(() => {
 	observer.disconnect();
 });
+
+defineExpose({
+	rootEl: $$(rootEl),
+});
 </script>
-
-<style lang="scss" module>
-
-</style>
diff --git a/packages/frontend/src/components/global/MkTime.vue b/packages/frontend/src/components/global/MkTime.vue
index 261cc0ee1..dfc3c8979 100644
--- a/packages/frontend/src/components/global/MkTime.vue
+++ b/packages/frontend/src/components/global/MkTime.vue
@@ -58,7 +58,6 @@ function tick() {
 
 if (props.mode === 'relative' || props.mode === 'detail') {
 	tick();
-
 	onUnmounted(() => {
 		window.clearTimeout(tickId);
 	});
diff --git a/packages/frontend/src/components/global/MkUrl.vue b/packages/frontend/src/components/global/MkUrl.vue
index 2a9278030..c1efd9a06 100644
--- a/packages/frontend/src/components/global/MkUrl.vue
+++ b/packages/frontend/src/components/global/MkUrl.vue
@@ -6,7 +6,7 @@
 	<template v-if="!self">
 		<span :class="$style.schema">{{ schema }}//</span>
 		<span :class="$style.hostname">{{ hostname }}</span>
-		<span v-if="port != ''" :class="$style.port">:{{ port }}</span>
+		<span v-if="port != ''">:{{ port }}</span>
 	</template>
 	<template v-if="pathname === '/' && self">
 		<span :class="$style.self">{{ hostname }}</span>
diff --git a/packages/frontend/src/components/global/MkUserName.vue b/packages/frontend/src/components/global/MkUserName.vue
index 4186a4a4f..c9e85c546 100644
--- a/packages/frontend/src/components/global/MkUserName.vue
+++ b/packages/frontend/src/components/global/MkUserName.vue
@@ -1,5 +1,5 @@
 <template>
-<Mfm :text="user.name ?? user.username" :author="user" :plain="true" :nowrap="nowrap" :emoji-urls="user.emojis"/>
+<Mfm :text="user.name ?? user.username" :author="user" :plain="true" :nowrap="nowrap" :emojiUrls="user.emojis"/>
 </template>
 
 <script lang="ts" setup>
diff --git a/packages/frontend/src/components/global/i18n.ts b/packages/frontend/src/components/global/i18n.ts
index 1fd293ba1..2708b759a 100644
--- a/packages/frontend/src/components/global/i18n.ts
+++ b/packages/frontend/src/components/global/i18n.ts
@@ -1,42 +1,24 @@
-import { h, defineComponent } from 'vue';
+import { h } from 'vue';
 
-export default defineComponent({
-	props: {
-		src: {
-			type: String,
-			required: true,
-		},
-		tag: {
-			type: String,
-			required: false,
-			default: 'span',
-		},
-		textTag: {
-			type: String,
-			required: false,
-			default: null,
-		},
-	},
-	render() {
-		let str = this.src;
-		const parsed = [] as (string | { arg: string; })[];
-		while (true) {
-			const nextBracketOpen = str.indexOf('{');
-			const nextBracketClose = str.indexOf('}');
+export default function(props: { src: string; tag?: string; textTag?: string; }, { slots }) {
+	let str = props.src;
+	const parsed = [] as (string | { arg: string; })[];
+	while (true) {
+		const nextBracketOpen = str.indexOf('{');
+		const nextBracketClose = str.indexOf('}');
 
-			if (nextBracketOpen === -1) {
-				parsed.push(str);
-				break;
-			} else {
-				if (nextBracketOpen > 0) parsed.push(str.substr(0, nextBracketOpen));
-				parsed.push({
-					arg: str.substring(nextBracketOpen + 1, nextBracketClose),
-				});
-			}
-
-			str = str.substr(nextBracketClose + 1);
+		if (nextBracketOpen === -1) {
+			parsed.push(str);
+			break;
+		} else {
+			if (nextBracketOpen > 0) parsed.push(str.substr(0, nextBracketOpen));
+			parsed.push({
+				arg: str.substring(nextBracketOpen + 1, nextBracketClose),
+			});
 		}
 
-		return h(this.tag, parsed.map(x => typeof x === 'string' ? (this.textTag ? h(this.textTag, x) : x) : this.$slots[x.arg]()));
-	},
-});
+		str = str.substr(nextBracketClose + 1);
+	}
+
+	return h(props.tag ?? 'span', parsed.map(x => typeof x === 'string' ? (props.textTag ? h(props.textTag, x) : x) : slots[x.arg]()));
+}
diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts
index 4ef8111da..ee2a2bc7b 100644
--- a/packages/frontend/src/components/index.ts
+++ b/packages/frontend/src/components/index.ts
@@ -1,6 +1,6 @@
 import { App } from 'vue';
 
-import Mfm from './global/MkMisskeyFlavoredMarkdown.vue';
+import Mfm from './global/MkMisskeyFlavoredMarkdown.ts';
 import MkA from './global/MkA.vue';
 import MkAcct from './global/MkAcct.vue';
 import MkAvatar from './global/MkAvatar.vue';
diff --git a/packages/frontend/src/components/mfm.ts b/packages/frontend/src/components/mfm.ts
deleted file mode 100644
index c3c07b583..000000000
--- a/packages/frontend/src/components/mfm.ts
+++ /dev/null
@@ -1,390 +0,0 @@
-import { VNode, defineComponent, h } from 'vue';
-import * as mfm from 'mfm-js';
-import MkUrl from '@/components/global/MkUrl.vue';
-import MkLink from '@/components/MkLink.vue';
-import MkMention from '@/components/MkMention.vue';
-import MkEmoji from '@/components/global/MkEmoji.vue';
-import MkCustomEmoji from '@/components/global/MkCustomEmoji.vue';
-import MkCode from '@/components/MkCode.vue';
-import MkGoogle from '@/components/MkGoogle.vue';
-import MkSparkle from '@/components/MkSparkle.vue';
-import MkA from '@/components/global/MkA.vue';
-import { host } from '@/config';
-import { defaultStore } from '@/store';
-
-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(' ');
-
-export default defineComponent({
-	props: {
-		text: {
-			type: String,
-			required: true,
-		},
-		plain: {
-			type: Boolean,
-			default: false,
-		},
-		nowrap: {
-			type: Boolean,
-			default: false,
-		},
-		author: {
-			type: Object,
-			default: null,
-		},
-		i: {
-			type: Object,
-			default: null,
-		},
-		isNote: {
-			type: Boolean,
-			default: true,
-		},
-		emojiUrls: {
-			type: Object,
-			default: null,
-		},
-		rootScale: {
-			type: Number,
-			default: 1,
-		}
-	},
-
-	render() {
-		if (this.text == null || this.text === '') return;
-
-		const ast = (this.plain ? mfm.parseSimple : mfm.parse)(this.text);
-
-		const validTime = (t: string | null | undefined) => {
-			if (t == null) return null;
-			return t.match(/^[0-9.]+s$/) ? t : null;
-		};
-
-		const useAnim = defaultStore.state.advancedMfm && defaultStore.state.animatedMfm;
-
-		/**
-		 * Gen Vue Elements from MFM AST
-		 * @param ast MFM AST
-		 * @param scale How times large the text is
-		 */
-		const genEl = (ast: mfm.MfmNode[], scale: number) => ast.map((token): VNode | string | (VNode | string)[] => {
-			switch (token.type) {
-				case 'text': {
-					const text = token.props.text.replace(/(\r\n|\n|\r)/g, '\n');
-
-					if (!this.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;
-					switch (token.props.name) {
-						case 'tada': {
-							const speed = validTime(token.props.args.speed) ?? '1s';
-							style = 'font-size: 150%;' + (useAnim ? `animation: tada ${speed} linear infinite both;` : '');
-							break;
-						}
-						case 'jelly': {
-							const speed = validTime(token.props.args.speed) ?? '1s';
-							style = (useAnim ? `animation: mfm-rubberBand ${speed} linear infinite both;` : '');
-							break;
-						}
-						case 'twitch': {
-							const speed = validTime(token.props.args.speed) ?? '0.5s';
-							style = useAnim ? `animation: mfm-twitch ${speed} ease infinite;` : '';
-							break;
-						}
-						case 'shake': {
-							const speed = validTime(token.props.args.speed) ?? '0.5s';
-							style = useAnim ? `animation: mfm-shake ${speed} ease infinite;` : '';
-							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';
-							style = useAnim ? `animation: ${anime} ${speed} linear infinite; animation-direction: ${direction};` : '';
-							break;
-						}
-						case 'jump': {
-							const speed = validTime(token.props.args.speed) ?? '0.75s';
-							style = useAnim ? `animation: mfm-jump ${speed} linear infinite;` : '';
-							break;
-						}
-						case 'bounce': {
-							const speed = validTime(token.props.args.speed) ?? '0.75s';
-							style = useAnim ? `animation: mfm-bounce ${speed} linear infinite; transform-origin: center bottom;` : '';
-							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: defaultStore.state.advancedMfm ? 'mfm-x2' : '',
-							}, genEl(token.children, scale * 2));
-						}
-						case 'x3': {
-							return h('span', {
-								class: defaultStore.state.advancedMfm ? 'mfm-x3' : '',
-							}, genEl(token.children, scale * 3));
-						}
-						case 'x4': {
-							return h('span', {
-								class: defaultStore.state.advancedMfm ? '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': {
-							const speed = validTime(token.props.args.speed) ?? '1s';
-							style = useAnim ? `animation: mfm-rainbow ${speed} linear infinite;` : '';
-							break;
-						}
-						case 'sparkle': {
-							if (!useAnim) {
-								return genEl(token.children, scale);
-							}
-							return h(MkSparkle, {}, genEl(token.children, scale));
-						}
-						case 'rotate': {
-							const degrees = parseFloat(token.props.args.deg ?? '90');
-							style = `transform: rotate(${degrees}deg); transform-origin: center center;`;
-							break;
-						}
-						case 'position': {
-							if (!defaultStore.state.advancedMfm) break;
-							const x = parseFloat(token.props.args.x ?? '0');
-							const y = parseFloat(token.props.args.y ?? '0');
-							style = `transform: translateX(${x}em) translateY(${y}em);`;
-							break;
-						}
-						case 'scale': {
-							if (!defaultStore.state.advancedMfm) {
-								style = '';
-								break;
-							}
-							const x = Math.min(parseFloat(token.props.args.x ?? '1'), 5);
-							const y = Math.min(parseFloat(token.props.args.y ?? '1'), 5);
-							style = `transform: scale(${x}, ${y});`; 
-							scale = scale * Math.max(x, y);
-							break;
-						}
-						case 'fg': {
-							let color = token.props.args.color;
-							if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00';
-							style = `color: #${color};`;
-							break;
-						}
-						case 'bg': {
-							let color = token.props.args.color;
-							if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00';
-							style = `background-color: #${color};`;
-							break;
-						}
-					}
-					if (style == null) {
-						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;',
-					}, genEl(token.children, scale))];
-				}
-
-				case 'url': {
-					return [h(MkUrl, {
-						key: Math.random(),
-						url: token.props.url,
-						rel: 'nofollow noopener',
-					})];
-				}
-
-				case 'link': {
-					return [h(MkLink, {
-						key: Math.random(),
-						url: token.props.url,
-						rel: 'nofollow noopener',
-					}, genEl(token.children, scale))];
-				}
-
-				case 'mention': {
-					return [h(MkMention, {
-						key: Math.random(),
-						host: (token.props.host == null && this.author && this.author.host != null ? this.author.host : token.props.host) || host,
-						username: token.props.username,
-					})];
-				}
-
-				case 'hashtag': {
-					return [h(MkA, {
-						key: Math.random(),
-						to: this.isNote ? `/tags/${encodeURIComponent(token.props.hashtag)}` : `/user-tags/${encodeURIComponent(token.props.hashtag)}`,
-						style: 'color:var(--hashtag);',
-					}, `#${token.props.hashtag}`)];
-				}
-
-				case 'blockCode': {
-					return [h(MkCode, {
-						key: Math.random(),
-						code: token.props.code,
-						lang: token.props.lang,
-					})];
-				}
-
-				case 'inlineCode': {
-					return [h(MkCode, {
-						key: Math.random(),
-						code: token.props.code,
-						inline: true,
-					})];
-				}
-
-				case 'quote': {
-					if (!this.nowrap) {
-						return [h('div', {
-							style: QUOTE_STYLE,
-						}, genEl(token.children, scale))];
-					} else {
-						return [h('span', {
-							style: QUOTE_STYLE,
-						}, genEl(token.children, scale))];
-					}
-				}
-
-				case 'emojiCode': {
-					// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-					if (this.author?.host == null) {
-						return [h(MkCustomEmoji, {
-							key: Math.random(),
-							name: token.props.name,
-							normal: this.plain,
-							host: null,
-							useOriginalSize: scale >= 2.5,
-						})];
-					} else {
-						// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-						if (this.emojiUrls && (this.emojiUrls[token.props.name] == null)) {
-							return [h('span', `:${token.props.name}:`)];
-						} else {
-							return [h(MkCustomEmoji, {
-								key: Math.random(),
-								name: token.props.name,
-								// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-								url: this.emojiUrls ? this.emojiUrls[token.props.name] : null,
-								normal: this.plain,
-								host: this.author.host,
-								useOriginalSize: scale >= 2.5,
-							})];
-						}
-					}
-				}
-
-				case 'unicodeEmoji': {
-					return [h(MkEmoji, {
-						key: Math.random(),
-						emoji: token.props.emoji,
-					})];
-				}
-
-				case 'mathInline': {
-					return [h('code', token.props.formula)];
-				}
-
-				case 'mathBlock': {
-					return [h('code', token.props.formula)];
-				}
-
-				case 'search': {
-					return [h(MkGoogle, {
-						key: Math.random(),
-						q: token.props.query,
-					})];
-				}
-
-				case 'plain': {
-					return [h('span', genEl(token.children, scale))];
-				}
-
-				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)[];
-
-		// Parse ast to DOM
-		return h('span', genEl(ast, this.rootScale));
-	},
-});
diff --git a/packages/frontend/src/components/page/block.type.ts b/packages/frontend/src/components/page/block.type.ts
new file mode 100644
index 000000000..71249a8af
--- /dev/null
+++ b/packages/frontend/src/components/page/block.type.ts
@@ -0,0 +1,29 @@
+export type BlockBase = {
+	id: string;
+	type: string;
+};
+
+export type TextBlock = BlockBase & {
+	type: 'text';
+	text: string;
+};
+
+export type SectionBlock = BlockBase & {
+	type: 'section';
+	title: string;
+	children: Block[];
+};
+
+export type ImageBlock = BlockBase & {
+	type: 'image';
+	fileId: string | null;
+};
+
+export type NoteBlock = BlockBase & {
+	type: 'note';
+	detailed: boolean;
+	note: string | null;
+};
+
+export type Block =
+	TextBlock | SectionBlock | ImageBlock | NoteBlock;
diff --git a/packages/frontend/src/components/page/page.block.vue b/packages/frontend/src/components/page/page.block.vue
index f3e776460..2bf3d12da 100644
--- a/packages/frontend/src/components/page/page.block.vue
+++ b/packages/frontend/src/components/page/page.block.vue
@@ -1,44 +1,29 @@
 <template>
-<component :is="'x-' + block.type" :key="block.id" :block="block" :hpml="hpml" :h="h"/>
+<component :is="getComponent(block.type)" :key="block.id" :page="page" :block="block" :h="h"/>
 </template>
 
-<script lang="ts">
-import { defineComponent, PropType } from 'vue';
+<script lang="ts" setup>
+import { } from 'vue';
+import * as Misskey from 'misskey-js';
 import XText from './page.text.vue';
 import XSection from './page.section.vue';
 import XImage from './page.image.vue';
-import XButton from './page.button.vue';
-import XNumberInput from './page.number-input.vue';
-import XTextInput from './page.text-input.vue';
-import XTextareaInput from './page.textarea-input.vue';
-import XSwitch from './page.switch.vue';
-import XIf from './page.if.vue';
-import XTextarea from './page.textarea.vue';
-import XPost from './page.post.vue';
-import XCounter from './page.counter.vue';
-import XRadioButton from './page.radio-button.vue';
-import XCanvas from './page.canvas.vue';
 import XNote from './page.note.vue';
-import { Hpml } from '@/scripts/hpml/evaluator';
-import { Block } from '@/scripts/hpml/block';
+import { Block } from './block.type';
 
-export default defineComponent({
-	components: {
-		XText, XSection, XImage, XButton, XNumberInput, XTextInput, XTextareaInput, XTextarea, XPost, XSwitch, XIf, XCounter, XRadioButton, XCanvas, XNote,
-	},
-	props: {
-		block: {
-			type: Object as PropType<Block>,
-			required: true,
-		},
-		hpml: {
-			type: Object as PropType<Hpml>,
-			required: true,
-		},
-		h: {
-			type: Number,
-			required: true,
-		},
-	},
-});
+function getComponent(type: string) {
+	switch (type) {
+		case 'text': return XText;
+		case 'section': return XSection;
+		case 'image': return XImage;
+		case 'note': return XNote;
+		default: return null;
+	}
+}
+
+defineProps<{
+	block: Block,
+	h: number,
+	page: Misskey.entities.Page,
+}>();
 </script>
diff --git a/packages/frontend/src/components/page/page.button.vue b/packages/frontend/src/components/page/page.button.vue
deleted file mode 100644
index 83931021d..000000000
--- a/packages/frontend/src/components/page/page.button.vue
+++ /dev/null
@@ -1,66 +0,0 @@
-<template>
-<div>
-	<MkButton class="kudkigyw" :primary="block.primary" @click="click()">{{ hpml.interpolate(block.text) }}</MkButton>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent, PropType, unref } from 'vue';
-import MkButton from '../MkButton.vue';
-import * as os from '@/os';
-import { ButtonBlock } from '@/scripts/hpml/block';
-import { Hpml } from '@/scripts/hpml/evaluator';
-
-export default defineComponent({
-	components: {
-		MkButton,
-	},
-	props: {
-		block: {
-			type: Object as PropType<ButtonBlock>,
-			required: true,
-		},
-		hpml: {
-			type: Object as PropType<Hpml>,
-			required: true,
-		},
-	},
-	methods: {
-		click() {
-			if (this.block.action === 'dialog') {
-				this.hpml.eval();
-				os.alert({
-					text: this.hpml.interpolate(this.block.content),
-				});
-			} else if (this.block.action === 'resetRandom') {
-				this.hpml.updateRandomSeed(Math.random());
-				this.hpml.eval();
-			} else if (this.block.action === 'pushEvent') {
-				os.api('page-push', {
-					pageId: this.hpml.page.id,
-					event: this.block.event,
-					...(this.block.var ? {
-						var: unref(this.hpml.vars)[this.block.var],
-					} : {}),
-				});
-
-				os.alert({
-					type: 'success',
-					text: this.hpml.interpolate(this.block.message),
-				});
-			} else if (this.block.action === 'callAiScript') {
-				this.hpml.callAiScript(this.block.fn);
-			}
-		},
-	},
-});
-</script>
-
-<style lang="scss" scoped>
-.kudkigyw {
-	display: inline-block;
-	min-width: 200px;
-	max-width: 450px;
-	margin: 8px 0;
-}
-</style>
diff --git a/packages/frontend/src/components/page/page.canvas.vue b/packages/frontend/src/components/page/page.canvas.vue
deleted file mode 100644
index 82ff36ec3..000000000
--- a/packages/frontend/src/components/page/page.canvas.vue
+++ /dev/null
@@ -1,48 +0,0 @@
-<template>
-<div class="ysrxegms">
-	<canvas ref="canvas" :width="block.width" :height="block.height"/>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent, onMounted, PropType, Ref, ref } from 'vue';
-import { CanvasBlock } from '@/scripts/hpml/block';
-import { Hpml } from '@/scripts/hpml/evaluator';
-
-export default defineComponent({
-	props: {
-		block: {
-			type: Object as PropType<CanvasBlock>,
-			required: true,
-		},
-		hpml: {
-			type: Object as PropType<Hpml>,
-			required: true,
-		},
-	},
-	setup(props, ctx) {
-		const canvas: Ref<any> = ref(null);
-
-		onMounted(() => {
-			props.hpml.registerCanvas(props.block.name, canvas.value);
-		});
-
-		return {
-			canvas,
-		};
-	},
-});
-</script>
-
-<style lang="scss" scoped>
-.ysrxegms {
-	display: inline-block;
-	vertical-align: bottom;
-	overflow: auto;
-	max-width: 100%;
-
-	> canvas {
-		display: block;
-	}
-}
-</style>
diff --git a/packages/frontend/src/components/page/page.counter.vue b/packages/frontend/src/components/page/page.counter.vue
deleted file mode 100644
index 63fde6a12..000000000
--- a/packages/frontend/src/components/page/page.counter.vue
+++ /dev/null
@@ -1,51 +0,0 @@
-<template>
-<div>
-	<MkButton class="llumlmnx" @click="click()">{{ hpml.interpolate(block.text) }}</MkButton>
-</div>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent, PropType } from 'vue';
-import MkButton from '../MkButton.vue';
-import { CounterVarBlock } from '@/scripts/hpml/block';
-import { Hpml } from '@/scripts/hpml/evaluator';
-
-export default defineComponent({
-	components: {
-		MkButton,
-	},
-	props: {
-		block: {
-			type: Object as PropType<CounterVarBlock>,
-			required: true,
-		},
-		hpml: {
-			type: Object as PropType<Hpml>,
-			required: true,
-		},
-	},
-	setup(props, ctx) {
-		const value = computed(() => {
-			return props.hpml.vars.value[props.block.name];
-		});
-
-		function click() {
-			props.hpml.updatePageVar(props.block.name, value.value + (props.block.inc || 1));
-			props.hpml.eval();
-		}
-
-		return {
-			click,
-		};
-	},
-});
-</script>
-
-<style lang="scss" scoped>
-.llumlmnx {
-	display: inline-block;
-	min-width: 300px;
-	max-width: 450px;
-	margin: 8px 0;
-}
-</style>
diff --git a/packages/frontend/src/components/page/page.if.vue b/packages/frontend/src/components/page/page.if.vue
deleted file mode 100644
index 372a15f0c..000000000
--- a/packages/frontend/src/components/page/page.if.vue
+++ /dev/null
@@ -1,31 +0,0 @@
-<template>
-<div v-show="hpml.vars.value[block.var]">
-	<XBlock v-for="child in block.children" :key="child.id" :block="child" :hpml="hpml" :h="h"/>
-</div>
-</template>
-
-<script lang="ts">
-import { IfBlock } from '@/scripts/hpml/block';
-import { Hpml } from '@/scripts/hpml/evaluator';
-import { defineComponent, defineAsyncComponent, PropType } from 'vue';
-
-export default defineComponent({
-	components: {
-		XBlock: defineAsyncComponent(() => import('./page.block.vue')),
-	},
-	props: {
-		block: {
-			type: Object as PropType<IfBlock>,
-			required: true,
-		},
-		hpml: {
-			type: Object as PropType<Hpml>,
-			required: true,
-		},
-		h: {
-			type: Number,
-			required: true,
-		},
-	},
-});
-</script>
diff --git a/packages/frontend/src/components/page/page.image.vue b/packages/frontend/src/components/page/page.image.vue
index 6ea81d257..2edcfb8b1 100644
--- a/packages/frontend/src/components/page/page.image.vue
+++ b/packages/frontend/src/components/page/page.image.vue
@@ -5,15 +5,15 @@
 </template>
 
 <script lang="ts" setup>
-import { PropType } from 'vue';
+import { } from 'vue';
+import * as Misskey from 'misskey-js';
+import { ImageBlock } from './block.type';
 import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
-import { ImageBlock } from '@/scripts/hpml/block';
-import { Hpml } from '@/scripts/hpml/evaluator';
 
 const props = defineProps<{
-	block: PropType<ImageBlock>,
-	hpml: PropType<Hpml>,
+	block: ImageBlock,
+	page: Misskey.entities.Page,
 }>();
 
-const image = props.hpml.page.attachedFiles.find(x => x.id === props.block.fileId);
+const image = props.page.attachedFiles.find(x => x.id === props.block.fileId);
 </script>
diff --git a/packages/frontend/src/components/page/page.note.vue b/packages/frontend/src/components/page/page.note.vue
index 8c65dabf0..7133a7f5a 100644
--- a/packages/frontend/src/components/page/page.note.vue
+++ b/packages/frontend/src/components/page/page.note.vue
@@ -1,47 +1,29 @@
 <template>
-<div class="voxdxuby">
+<div style="margin: 1em 0;">
 	<MkNote v-if="note && !block.detailed" :key="note.id + ':normal'" v-model:note="note"/>
 	<MkNoteDetailed v-if="note && block.detailed" :key="note.id + ':detail'" v-model:note="note"/>
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent, onMounted, PropType, Ref, ref } from 'vue';
+<script lang="ts" setup>
+import { onMounted, Ref, ref } from 'vue';
+import * as Misskey from 'misskey-js';
+import { NoteBlock } from './block.type';
 import MkNote from '@/components/MkNote.vue';
 import MkNoteDetailed from '@/components/MkNoteDetailed.vue';
 import * as os from '@/os';
-import { NoteBlock } from '@/scripts/hpml/block';
 
-export default defineComponent({
-	components: {
-		MkNote,
-		MkNoteDetailed,
-	},
-	props: {
-		block: {
-			type: Object as PropType<NoteBlock>,
-			required: true,
-		},
-	},
-	setup(props, ctx) {
-		const note: Ref<Record<string, any> | null> = ref(null);
+const props = defineProps<{
+	block: NoteBlock,
+	page: Misskey.entities.Page,
+}>();
 
-		onMounted(() => {
-			os.api('notes/show', { noteId: props.block.note })
-			.then(result => {
-				note.value = result;
-			});
+const note: Ref<Misskey.entities.Note | null> = ref(null);
+
+onMounted(() => {
+	os.api('notes/show', { noteId: props.block.note })
+		.then(result => {
+			note.value = result;
 		});
-
-		return {
-			note,
-		};
-	},
 });
 </script>
-
-<style lang="scss" scoped>
-.voxdxuby {
-	margin: 1em 0;
-}
-</style>
diff --git a/packages/frontend/src/components/page/page.number-input.vue b/packages/frontend/src/components/page/page.number-input.vue
deleted file mode 100644
index 72c1b6deb..000000000
--- a/packages/frontend/src/components/page/page.number-input.vue
+++ /dev/null
@@ -1,54 +0,0 @@
-<template>
-<div>
-	<MkInput class="kudkigyw" :model-value="value" type="number" @update:model-value="updateValue($event)">
-		<template #label>{{ hpml.interpolate(block.text) }}</template>
-	</MkInput>
-</div>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent, PropType } from 'vue';
-import MkInput from '../MkInput.vue';
-import { Hpml } from '@/scripts/hpml/evaluator';
-import { NumberInputVarBlock } from '@/scripts/hpml/block';
-
-export default defineComponent({
-	components: {
-		MkInput,
-	},
-	props: {
-		block: {
-			type: Object as PropType<NumberInputVarBlock>,
-			required: true,
-		},
-		hpml: {
-			type: Object as PropType<Hpml>,
-			required: true,
-		},
-	},
-	setup(props, ctx) {
-		const value = computed(() => {
-			return props.hpml.vars.value[props.block.name];
-		});
-
-		function updateValue(newValue) {
-			props.hpml.updatePageVar(props.block.name, newValue);
-			props.hpml.eval();
-		}
-
-		return {
-			value,
-			updateValue,
-		};
-	},
-});
-</script>
-
-<style lang="scss" scoped>
-.kudkigyw {
-	display: inline-block;
-	min-width: 300px;
-	max-width: 450px;
-	margin: 8px 0;
-}
-</style>
diff --git a/packages/frontend/src/components/page/page.post.vue b/packages/frontend/src/components/page/page.post.vue
deleted file mode 100644
index 55da610cb..000000000
--- a/packages/frontend/src/components/page/page.post.vue
+++ /dev/null
@@ -1,111 +0,0 @@
-<template>
-<div class="ngbfujlo">
-	<MkTextarea :model-value="text" readonly style="margin: 0;"></MkTextarea>
-	<MkButton class="button" primary :disabled="posting || posted" @click="post()">
-		<i v-if="posted" class="ti ti-check"></i>
-		<i v-else class="ti ti-send"></i>
-	</MkButton>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent, PropType } from 'vue';
-import MkTextarea from '../MkTextarea.vue';
-import MkButton from '../MkButton.vue';
-import { apiUrl } from '@/config';
-import * as os from '@/os';
-import { PostBlock } from '@/scripts/hpml/block';
-import { Hpml } from '@/scripts/hpml/evaluator';
-import { defaultStore } from '@/store';
-import { $i } from '@/account';
-
-export default defineComponent({
-	components: {
-		MkTextarea,
-		MkButton,
-	},
-	props: {
-		block: {
-			type: Object as PropType<PostBlock>,
-			required: true,
-		},
-		hpml: {
-			type: Object as PropType<Hpml>,
-			required: true,
-		},
-	},
-	data() {
-		return {
-			text: this.hpml.interpolate(this.block.text),
-			posted: false,
-			posting: false,
-		};
-	},
-	watch: {
-		'hpml.vars': {
-			handler() {
-				this.text = this.hpml.interpolate(this.block.text);
-			},
-			deep: true,
-		},
-	},
-	methods: {
-		upload() {
-			const promise = new Promise((ok) => {
-				const canvas = this.hpml.canvases[this.block.canvasId];
-				canvas.toBlob(blob => {
-					const formData = new FormData();
-					formData.append('file', blob);
-					formData.append('i', $i.token);
-					if (defaultStore.state.uploadFolder) {
-						formData.append('folderId', defaultStore.state.uploadFolder);
-					}
-
-					window.fetch(apiUrl + '/drive/files/create', {
-						method: 'POST',
-						body: formData,
-					})
-						.then(response => response.json())
-						.then(f => {
-							ok(f);
-						});
-				});
-			});
-			os.promiseDialog(promise);
-			return promise;
-		},
-		async post() {
-			this.posting = true;
-			const file = this.block.attachCanvasImage ? await this.upload() : null;
-			os.apiWithDialog('notes/create', {
-				text: this.text === '' ? null : this.text,
-				fileIds: file ? [file.id] : undefined,
-			}).then(() => {
-				this.posted = true;
-			});
-		},
-	},
-});
-</script>
-
-<style lang="scss" scoped>
-.ngbfujlo {
-	position: relative;
-	padding: 32px;
-	border-radius: 6px;
-	box-shadow: 0 2px 8px var(--shadow);
-	z-index: 1;
-
-	> .button {
-		margin-top: 32px;
-	}
-
-	@media (max-width: 600px) {
-		padding: 16px;
-
-		> .button {
-			margin-top: 16px;
-		}
-	}
-}
-</style>
diff --git a/packages/frontend/src/components/page/page.radio-button.vue b/packages/frontend/src/components/page/page.radio-button.vue
deleted file mode 100644
index ce8f252e4..000000000
--- a/packages/frontend/src/components/page/page.radio-button.vue
+++ /dev/null
@@ -1,44 +0,0 @@
-<template>
-<div>
-	<div>{{ hpml.interpolate(block.title) }}</div>
-	<MkRadio v-for="item in block.values" :key="item" :modelValue="value" :value="item" @update:model-value="updateValue($event)">{{ item }}</MkRadio>
-</div>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent, PropType } from 'vue';
-import MkRadio from '../MkRadio.vue';
-import { Hpml } from '@/scripts/hpml/evaluator';
-import { RadioButtonVarBlock } from '@/scripts/hpml/block';
-
-export default defineComponent({
-	components: {
-		MkRadio,
-	},
-	props: {
-		block: {
-			type: Object as PropType<RadioButtonVarBlock>,
-			required: true,
-		},
-		hpml: {
-			type: Object as PropType<Hpml>,
-			required: true,
-		},
-	},
-	setup(props, ctx) {
-		const value = computed(() => {
-			return props.hpml.vars.value[props.block.name];
-		});
-
-		function updateValue(newValue: string) {
-			props.hpml.updatePageVar(props.block.name, newValue);
-			props.hpml.eval();
-		}
-
-		return {
-			value,
-			updateValue,
-		};
-	},
-});
-</script>
diff --git a/packages/frontend/src/components/page/page.section.vue b/packages/frontend/src/components/page/page.section.vue
index 50181b390..83a16ae0a 100644
--- a/packages/frontend/src/components/page/page.section.vue
+++ b/packages/frontend/src/components/page/page.section.vue
@@ -1,59 +1,49 @@
 <template>
-<section class="sdgxphyu">
-	<component :is="'h' + h">{{ block.title }}</component>
+<section>
+	<component
+		:is="'h' + h"
+		:class="{
+			'h2': h === 2,
+			'h3': h === 3,
+			'h4': h === 4,
+		}"
+	>
+		{{ block.title }}
+	</component>
 
-	<div class="children">
-		<XBlock v-for="child in block.children" :key="child.id" :block="child" :hpml="hpml" :h="h + 1"/>
+	<div class="_gaps">
+		<XBlock v-for="child in block.children" :key="child.id" :page="page" :block="child" :h="h + 1"/>
 	</div>
 </section>
 </template>
 
-<script lang="ts">
-import { defineComponent, defineAsyncComponent, PropType } from 'vue';
-import { SectionBlock } from '@/scripts/hpml/block';
-import { Hpml } from '@/scripts/hpml/evaluator';
+<script lang="ts" setup>
+import { defineAsyncComponent } from 'vue';
+import * as Misskey from 'misskey-js';
+import { SectionBlock } from './block.type';
 
-export default defineComponent({
-	components: {
-		XBlock: defineAsyncComponent(() => import('./page.block.vue')),
-	},
-	props: {
-		block: {
-			type: Object as PropType<SectionBlock>,
-			required: true,
-		},
-		hpml: {
-			type: Object as PropType<Hpml>,
-			required: true,
-		},
-		h: {
-			required: true,
-		},
-	},
-});
+const XBlock = defineAsyncComponent(() => import('./page.block.vue'));
+
+defineProps<{
+	block: SectionBlock,
+	h: number,
+	page: Misskey.entities.Page,
+}>();
 </script>
 
-<style lang="scss" scoped>
-.sdgxphyu {
-	margin: 1.5em 0;
+<style lang="scss" module>
+.h2 {
+	font-size: 1.35em;
+	margin: 0 0 0.5em 0;
+}
 
-	> h2 {
-		font-size: 1.35em;
-		margin: 0 0 0.5em 0;
-	}
+.h3 {
+	font-size: 1em;
+	margin: 0 0 0.5em 0;
+}
 
-	> h3 {
-		font-size: 1em;
-		margin: 0 0 0.5em 0;
-	}
-
-	> h4 {
-		font-size: 1em;
-		margin: 0 0 0.5em 0;
-	}
-
-	> .children {
-		//padding 16px
-	}
+.h4 {
+	font-size: 1em;
+	margin: 0 0 0.5em 0;
 }
 </style>
diff --git a/packages/frontend/src/components/page/page.switch.vue b/packages/frontend/src/components/page/page.switch.vue
deleted file mode 100644
index b5f346451..000000000
--- a/packages/frontend/src/components/page/page.switch.vue
+++ /dev/null
@@ -1,54 +0,0 @@
-<template>
-<div class="hkcxmtwj">
-	<MkSwitch :model-value="value" @update:model-value="updateValue($event)">{{ hpml.interpolate(block.text) }}</MkSwitch>
-</div>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent, PropType } from 'vue';
-import MkSwitch from '../MkSwitch.vue';
-import { Hpml } from '@/scripts/hpml/evaluator';
-import { SwitchVarBlock } from '@/scripts/hpml/block';
-
-export default defineComponent({
-	components: {
-		MkSwitch,
-	},
-	props: {
-		block: {
-			type: Object as PropType<SwitchVarBlock>,
-			required: true,
-		},
-		hpml: {
-			type: Object as PropType<Hpml>,
-			required: true,
-		},
-	},
-	setup(props, ctx) {
-		const value = computed(() => {
-			return props.hpml.vars.value[props.block.name];
-		});
-
-		function updateValue(newValue: boolean) {
-			props.hpml.updatePageVar(props.block.name, newValue);
-			props.hpml.eval();
-		}
-
-		return {
-			value,
-			updateValue,
-		};
-	},
-});
-</script>
-
-<style lang="scss" scoped>
-.hkcxmtwj {
-	display: inline-block;
-	margin: 16px auto;
-
-	& + .hkcxmtwj {
-		margin-left: 16px;
-	}
-}
-</style>
diff --git a/packages/frontend/src/components/page/page.text-input.vue b/packages/frontend/src/components/page/page.text-input.vue
deleted file mode 100644
index d020a99de..000000000
--- a/packages/frontend/src/components/page/page.text-input.vue
+++ /dev/null
@@ -1,54 +0,0 @@
-<template>
-<div>
-	<MkInput class="kudkigyw" :model-value="value" type="text" @update:model-value="updateValue($event)">
-		<template #label>{{ hpml.interpolate(block.text) }}</template>
-	</MkInput>
-</div>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent, PropType } from 'vue';
-import MkInput from '../MkInput.vue';
-import { Hpml } from '@/scripts/hpml/evaluator';
-import { TextInputVarBlock } from '@/scripts/hpml/block';
-
-export default defineComponent({
-	components: {
-		MkInput,
-	},
-	props: {
-		block: {
-			type: Object as PropType<TextInputVarBlock>,
-			required: true,
-		},
-		hpml: {
-			type: Object as PropType<Hpml>,
-			required: true,
-		},
-	},
-	setup(props, ctx) {
-		const value = computed(() => {
-			return props.hpml.vars.value[props.block.name];
-		});
-
-		function updateValue(newValue) {
-			props.hpml.updatePageVar(props.block.name, newValue);
-			props.hpml.eval();
-		}
-
-		return {
-			value,
-			updateValue,
-		};
-	},
-});
-</script>
-
-<style lang="scss" scoped>
-.kudkigyw {
-	display: inline-block;
-	min-width: 300px;
-	max-width: 450px;
-	margin: 8px 0;
-}
-</style>
diff --git a/packages/frontend/src/components/page/page.text.vue b/packages/frontend/src/components/page/page.text.vue
index e0e4959ef..48ce4b0e1 100644
--- a/packages/frontend/src/components/page/page.text.vue
+++ b/packages/frontend/src/components/page/page.text.vue
@@ -1,70 +1,24 @@
 <template>
-<div class="mrdgzndn">
-	<Mfm :key="text" :text="text" :is-note="false" :i="$i"/>
-	<MkUrlPreview v-for="url in urls" :key="url" :url="url" class="url"/>
+<div class="_gaps">
+	<Mfm :text="block.text" :isNote="false" :i="$i"/>
+	<MkUrlPreview v-for="url in urls" :key="url" :url="url"/>
 </div>
 </template>
 
-<script lang="ts">
-import { defineAsyncComponent, defineComponent, PropType } from 'vue';
+<script lang="ts" setup>
+import { defineAsyncComponent } from 'vue';
 import * as mfm from 'mfm-js';
-import { TextBlock } from '@/scripts/hpml/block';
-import { Hpml } from '@/scripts/hpml/evaluator';
+import * as Misskey from 'misskey-js';
+import { TextBlock } from './block.type';
 import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm';
 import { $i } from '@/account';
 
-export default defineComponent({
-	components: {
-		MkUrlPreview: defineAsyncComponent(() => import('@/components/MkUrlPreview.vue')),
-	},
-	props: {
-		block: {
-			type: Object as PropType<TextBlock>,
-			required: true,
-		},
-		hpml: {
-			type: Object as PropType<Hpml>,
-			required: true,
-		},
-	},
-	data() {
-		return {
-			text: this.hpml.interpolate(this.block.text),
-			$i,
-		};
-	},
-	computed: {
-		urls(): string[] {
-			if (this.text) {
-				return extractUrlFromMfm(mfm.parse(this.text));
-			} else {
-				return [];
-			}
-		},
-	},
-	watch: {
-		'hpml.vars': {
-			handler() {
-				this.text = this.hpml.interpolate(this.block.text);
-			},
-			deep: true,
-		},
-	},
-});
+const MkUrlPreview = defineAsyncComponent(() => import('@/components/MkUrlPreview.vue'));
+
+const props = defineProps<{
+	block: TextBlock,
+	page: Misskey.entities.Page,
+}>();
+
+const urls = props.block.text ? extractUrlFromMfm(mfm.parse(props.block.text)) : [];
 </script>
-
-<style lang="scss" scoped>
-.mrdgzndn {
-	&:not(:first-child) {
-		margin-top: 0.5em;
-	}
-
-	&:not(:last-child) {
-		margin-bottom: 0.5em;
-	}
-
-	> .url {
-		margin: 0.5em 0;
-	}
-}
-</style>
diff --git a/packages/frontend/src/components/page/page.textarea-input.vue b/packages/frontend/src/components/page/page.textarea-input.vue
deleted file mode 100644
index db3a96dd1..000000000
--- a/packages/frontend/src/components/page/page.textarea-input.vue
+++ /dev/null
@@ -1,45 +0,0 @@
-<template>
-<div>
-	<MkTextarea :model-value="value" @update:model-value="updateValue($event)">
-		<template #label>{{ hpml.interpolate(block.text) }}</template>
-	</MkTextarea>
-</div>
-</template>
-
-<script lang="ts">
-import { computed, defineComponent, PropType } from 'vue';
-import MkTextarea from '../MkTextarea.vue';
-import { Hpml } from '@/scripts/hpml/evaluator';
-import { TextInputVarBlock } from '@/scripts/hpml/block';
-
-export default defineComponent({
-	components: {
-		MkTextarea,
-	},
-	props: {
-		block: {
-			type: Object as PropType<TextInputVarBlock>,
-			required: true,
-		},
-		hpml: {
-			type: Object as PropType<Hpml>,
-			required: true,
-		},
-	},
-	setup(props, ctx) {
-		const value = computed(() => {
-			return props.hpml.vars.value[props.block.name];
-		});
-
-		function updateValue(newValue) {
-			props.hpml.updatePageVar(props.block.name, newValue);
-			props.hpml.eval();
-		}
-
-		return {
-			value,
-			updateValue,
-		};
-	},
-});
-</script>
diff --git a/packages/frontend/src/components/page/page.textarea.vue b/packages/frontend/src/components/page/page.textarea.vue
deleted file mode 100644
index 9b82412e8..000000000
--- a/packages/frontend/src/components/page/page.textarea.vue
+++ /dev/null
@@ -1,39 +0,0 @@
-<template>
-<MkTextarea :model-value="text" readonly></MkTextarea>
-</template>
-
-<script lang="ts">
-import { TextBlock } from '@/scripts/hpml/block';
-import { Hpml } from '@/scripts/hpml/evaluator';
-import { defineComponent, PropType } from 'vue';
-import MkTextarea from '../MkTextarea.vue';
-
-export default defineComponent({
-	components: {
-		MkTextarea,
-	},
-	props: {
-		block: {
-			type: Object as PropType<TextBlock>,
-			required: true,
-		},
-		hpml: {
-			type: Object as PropType<Hpml>,
-			required: true,
-		},
-	},
-	data() {
-		return {
-			text: this.hpml.interpolate(this.block.text),
-		};
-	},
-	watch: {
-		'hpml.vars': {
-			handler() {
-				this.text = this.hpml.interpolate(this.block.text);
-			},
-			deep: true,
-		},
-	},
-});
-</script>
diff --git a/packages/frontend/src/components/page/page.vue b/packages/frontend/src/components/page/page.vue
index 5f1f62581..c2c269322 100644
--- a/packages/frontend/src/components/page/page.vue
+++ b/packages/frontend/src/components/page/page.vue
@@ -1,56 +1,25 @@
 <template>
-<div v-if="hpml" class="iroscrza" :class="{ center: page.alignCenter, serif: page.font === 'serif' }">
-	<XBlock v-for="child in page.content" :key="child.id" :block="child" :hpml="hpml" :h="2"/>
+<div :class="{ [$style.center]: page.alignCenter, [$style.serif]: page.font === 'serif' }">
+	<XBlock v-for="child in page.content" :key="child.id" :page="page" :block="child" :h="2"/>
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent, onMounted, nextTick, PropType } from 'vue';
+<script lang="ts" setup>
+import { onMounted, nextTick } from 'vue';
+import * as Misskey from 'misskey-js';
 import XBlock from './page.block.vue';
-import { Hpml } from '@/scripts/hpml/evaluator';
-import { url } from '@/config';
-import { $i } from '@/account';
 
-export default defineComponent({
-	components: {
-		XBlock,
-	},
-	props: {
-		page: {
-			type: Object as PropType<Record<string, any>>,
-			required: true,
-		},
-	},
-	setup(props, ctx) {
-		const hpml = new Hpml(props.page, {
-			randomSeed: Math.random(),
-			visitor: $i,
-			url: url,
-		});
-
-		onMounted(() => {
-			nextTick(() => {
-				hpml.eval();
-			});
-		});
-
-		return {
-			hpml,
-		};
-	},
-});
+defineProps<{
+	page: Misskey.entities.Page,
+}>();
 </script>
 
-<style lang="scss" scoped>
-.iroscrza {
-	&.serif {
-		> div {
-			font-family: serif;
-		}
-	}
+<style lang="scss" module>
+.serif {
+	font-family: serif;
+}
 
-	&.center {
-		text-align: center;
-	}
+.center {
+	text-align: center;
 }
 </style>
diff --git a/packages/frontend/src/custom-emojis.ts b/packages/frontend/src/custom-emojis.ts
index a89a420d7..9b738b2fd 100644
--- a/packages/frontend/src/custom-emojis.ts
+++ b/packages/frontend/src/custom-emojis.ts
@@ -1,8 +1,7 @@
-import { shallowRef, computed, markRaw } from 'vue';
+import { shallowRef, computed, markRaw, watch } from 'vue';
 import * as Misskey from 'misskey-js';
 import { api, apiGet } from './os';
-import { miLocalStorage } from './local-storage';
-import { stream } from '@/stream';
+import { useStream } from '@/stream';
 import { get, set } from '@/scripts/idb-proxy';
 
 const storageCache = await get('emojis');
@@ -17,6 +16,17 @@ export const customEmojiCategories = computed<[ ...string[], null ]>(() => {
 	return markRaw([...Array.from(categories), null]);
 });
 
+export const customEmojisMap = new Map<string, Misskey.entities.CustomEmoji>();
+watch(customEmojis, emojis => {
+	customEmojisMap.clear();
+	for (const emoji of emojis) {
+		customEmojisMap.set(emoji.name, emoji);
+	}
+}, { immediate: true });
+
+// TODO: ここら辺副作用なのでいい感じにする
+const stream = useStream();
+
 stream.on('emojiAdded', emojiData => {
 	customEmojis.value = [emojiData.emoji, ...customEmojis.value];
 	set('emojis', customEmojis.value);
@@ -34,10 +44,9 @@ stream.on('emojiDeleted', emojiData => {
 
 export async function fetchCustomEmojis(force = false) {
 	const now = Date.now();
-	const needsMigration = miLocalStorage.getItem('emojis') != null;
 
 	let res;
-	if (force || needsMigration) {
+	if (force) {
 		res = await api('emojis', {});
 	} else {
 		const lastFetchedAt = await get('lastEmojisFetchedAt');
@@ -48,10 +57,6 @@ export async function fetchCustomEmojis(force = false) {
 	customEmojis.value = res.emojis;
 	set('emojis', res.emojis);
 	set('lastEmojisFetchedAt', now);
-	if (needsMigration) {
-		miLocalStorage.removeItem('emojis');
-		miLocalStorage.removeItem('lastEmojisFetchedAt');
-	}
 }
 
 let cachedTags;
diff --git a/packages/frontend/src/directives/tooltip.ts b/packages/frontend/src/directives/tooltip.ts
index 5d13497b5..373141fa3 100644
--- a/packages/frontend/src/directives/tooltip.ts
+++ b/packages/frontend/src/directives/tooltip.ts
@@ -5,7 +5,7 @@ import { defineAsyncComponent, Directive, ref } from 'vue';
 import { isTouchUsing } from '@/scripts/touch';
 import { popup, alert } from '@/os';
 
-const start = isTouchUsing ? 'touchstart' : 'mouseover';
+const start = isTouchUsing ? 'touchstart' : 'mouseenter';
 const end = isTouchUsing ? 'touchend' : 'mouseleave';
 
 export default {
@@ -63,16 +63,24 @@ export default {
 			ev.preventDefault();
 		});
 
-		el.addEventListener(start, () => {
+		el.addEventListener(start, (ev) => {
 			window.clearTimeout(self.showTimer);
 			window.clearTimeout(self.hideTimer);
-			self.showTimer = window.setTimeout(self.show, delay);
+			if (delay === 0) {
+				self.show();
+			} else {
+				self.showTimer = window.setTimeout(self.show, delay);
+			}
 		}, { passive: true });
 
 		el.addEventListener(end, () => {
 			window.clearTimeout(self.showTimer);
 			window.clearTimeout(self.hideTimer);
-			self.hideTimer = window.setTimeout(self.close, delay);
+			if (delay === 0) {
+				self.close();
+			} else {
+				self.hideTimer = window.setTimeout(self.close, delay);
+			}
 		}, { passive: true });
 
 		el.addEventListener('click', () => {
diff --git a/packages/frontend/src/emojilist.json b/packages/frontend/src/emojilist.json
index 402e82e33..fde06a4aa 100644
--- a/packages/frontend/src/emojilist.json
+++ b/packages/frontend/src/emojilist.json
@@ -1,1785 +1,1784 @@
 [
-	{ "category": "face", "char": "😀", "name": "grinning", "keywords": ["face", "smile", "happy", "joy", ": D", "grin"] },
-	{ "category": "face", "char": "😬", "name": "grimacing", "keywords": ["face", "grimace", "teeth"] },
-	{ "category": "face", "char": "😁", "name": "grin", "keywords": ["face", "happy", "smile", "joy", "kawaii"] },
-	{ "category": "face", "char": "😂", "name": "joy", "keywords": ["face", "cry", "tears", "weep", "happy", "happytears", "haha"] },
-	{ "category": "face", "char": "🤣", "name": "rofl", "keywords": ["face", "rolling", "floor", "laughing", "lol", "haha"] },
-	{ "category": "face", "char": "🥳", "name": "partying", "keywords": ["face", "celebration", "woohoo"] },
-	{ "category": "face", "char": "😃", "name": "smiley", "keywords": ["face", "happy", "joy", "haha", ": D", ": )", "smile", "funny"] },
-	{ "category": "face", "char": "😄", "name": "smile", "keywords": ["face", "happy", "joy", "funny", "haha", "laugh", "like", ": D", ": )"] },
-	{ "category": "face", "char": "😅", "name": "sweat_smile", "keywords": ["face", "hot", "happy", "laugh", "sweat", "smile", "relief"] },
-	{ "category": "face", "char": "🥲", "name": "smiling_face_with_tear", "keywords": ["face"] },
-	{ "category": "face", "char": "😆", "name": "laughing", "keywords": ["happy", "joy", "lol", "satisfied", "haha", "face", "glad", "XD", "laugh"] },
-	{ "category": "face", "char": "😇", "name": "innocent", "keywords": ["face", "angel", "heaven", "halo"] },
-	{ "category": "face", "char": "😉", "name": "wink", "keywords": ["face", "happy", "mischievous", "secret", ";)", "smile", "eye"] },
-	{ "category": "face", "char": "😊", "name": "blush", "keywords": ["face", "smile", "happy", "flushed", "crush", "embarrassed", "shy", "joy"] },
-	{ "category": "face", "char": "🙂", "name": "slightly_smiling_face", "keywords": ["face", "smile"] },
-	{ "category": "face", "char": "🙃", "name": "upside_down_face", "keywords": ["face", "flipped", "silly", "smile"] },
-	{ "category": "face", "char": "☺️", "name": "relaxed", "keywords": ["face", "blush", "massage", "happiness"] },
-	{ "category": "face", "char": "😋", "name": "yum", "keywords": ["happy", "joy", "tongue", "smile", "face", "silly", "yummy", "nom", "delicious", "savouring"] },
-	{ "category": "face", "char": "😌", "name": "relieved", "keywords": ["face", "relaxed", "phew", "massage", "happiness"] },
-	{ "category": "face", "char": "😍", "name": "heart_eyes", "keywords": ["face", "love", "like", "affection", "valentines", "infatuation", "crush", "heart"] },
-	{ "category": "face", "char": "🥰", "name": "smiling_face_with_three_hearts", "keywords": ["face", "love", "like", "affection", "valentines", "infatuation", "crush", "hearts", "adore"] },
-	{ "category": "face", "char": "😘", "name": "kissing_heart", "keywords": ["face", "love", "like", "affection", "valentines", "infatuation", "kiss"] },
-	{ "category": "face", "char": "😗", "name": "kissing", "keywords": ["love", "like", "face", "3", "valentines", "infatuation", "kiss"] },
-	{ "category": "face", "char": "😙", "name": "kissing_smiling_eyes", "keywords": ["face", "affection", "valentines", "infatuation", "kiss"] },
-	{ "category": "face", "char": "😚", "name": "kissing_closed_eyes", "keywords": ["face", "love", "like", "affection", "valentines", "infatuation", "kiss"] },
-	{ "category": "face", "char": "😜", "name": "stuck_out_tongue_winking_eye", "keywords": ["face", "prank", "childish", "playful", "mischievous", "smile", "wink", "tongue"] },
-	{ "category": "face", "char": "🤪", "name": "zany", "keywords": ["face", "goofy", "crazy"] },
-	{ "category": "face", "char": "🤨", "name": "raised_eyebrow", "keywords": ["face", "distrust", "scepticism", "disapproval", "disbelief", "surprise"] },
-	{ "category": "face", "char": "🧐", "name": "monocle", "keywords": ["face", "stuffy", "wealthy"] },
-	{ "category": "face", "char": "😝", "name": "stuck_out_tongue_closed_eyes", "keywords": ["face", "prank", "playful", "mischievous", "smile", "tongue"] },
-	{ "category": "face", "char": "😛", "name": "stuck_out_tongue", "keywords": ["face", "prank", "childish", "playful", "mischievous", "smile", "tongue"] },
-	{ "category": "face", "char": "🤑", "name": "money_mouth_face", "keywords": ["face", "rich", "dollar", "money"] },
-	{ "category": "face", "char": "🤓", "name": "nerd_face", "keywords": ["face", "nerdy", "geek", "dork"] },
-	{ "category": "face", "char": "🥸", "name": "disguised_face", "keywords": ["face", "nose", "glasses", "incognito"] },
-	{ "category": "face", "char": "😎", "name": "sunglasses", "keywords": ["face", "cool", "smile", "summer", "beach", "sunglass"] },
-	{ "category": "face", "char": "🤩", "name": "star_struck", "keywords": ["face", "smile", "starry", "eyes", "grinning"] },
-	{ "category": "face", "char": "🤡", "name": "clown_face", "keywords": ["face"] },
-	{ "category": "face", "char": "🤠", "name": "cowboy_hat_face", "keywords": ["face", "cowgirl", "hat"] },
-	{ "category": "face", "char": "🤗", "name": "hugs", "keywords": ["face", "smile", "hug"] },
-	{ "category": "face", "char": "😏", "name": "smirk", "keywords": ["face", "smile", "mean", "prank", "smug", "sarcasm"] },
-	{ "category": "face", "char": "😶", "name": "no_mouth", "keywords": ["face", "hellokitty"] },
-	{ "category": "face", "char": "😐", "name": "neutral_face", "keywords": ["indifference", "meh", ": |", "neutral"] },
-	{ "category": "face", "char": "😑", "name": "expressionless", "keywords": ["face", "indifferent", "-_-", "meh", "deadpan"] },
-	{ "category": "face", "char": "😒", "name": "unamused", "keywords": ["indifference", "bored", "straight face", "serious", "sarcasm", "unimpressed", "skeptical", "dubious", "side_eye"] },
-	{ "category": "face", "char": "🙄", "name": "roll_eyes", "keywords": ["face", "eyeroll", "frustrated"] },
-	{ "category": "face", "char": "🤔", "name": "thinking", "keywords": ["face", "hmmm", "think", "consider"] },
-	{ "category": "face", "char": "🤥", "name": "lying_face", "keywords": ["face", "lie", "pinocchio"] },
-	{ "category": "face", "char": "🤭", "name": "hand_over_mouth", "keywords": ["face", "whoops", "shock", "surprise"] },
-	{ "category": "face", "char": "🤫", "name": "shushing", "keywords": ["face", "quiet", "shhh"] },
-	{ "category": "face", "char": "🤬", "name": "symbols_over_mouth", "keywords": ["face", "swearing", "cursing", "cussing", "profanity", "expletive"] },
-	{ "category": "face", "char": "🤯", "name": "exploding_head", "keywords": ["face", "shocked", "mind", "blown"] },
-	{ "category": "face", "char": "😳", "name": "flushed", "keywords": ["face", "blush", "shy", "flattered"] },
-	{ "category": "face", "char": "😞", "name": "disappointed", "keywords": ["face", "sad", "upset", "depressed", ": ("] },
-	{ "category": "face", "char": "😟", "name": "worried", "keywords": ["face", "concern", "nervous", ": ("] },
-	{ "category": "face", "char": "😠", "name": "angry", "keywords": ["mad", "face", "annoyed", "frustrated"] },
-	{ "category": "face", "char": "😡", "name": "rage", "keywords": ["angry", "mad", "hate", "despise"] },
-	{ "category": "face", "char": "😔", "name": "pensive", "keywords": ["face", "sad", "depressed", "upset"] },
-	{ "category": "face", "char": "😕", "name": "confused", "keywords": ["face", "indifference", "huh", "weird", "hmmm", ": /"] },
-	{ "category": "face", "char": "🙁", "name": "slightly_frowning_face", "keywords": ["face", "frowning", "disappointed", "sad", "upset"] },
-	{ "category": "face", "char": "☹", "name": "frowning_face", "keywords": ["face", "sad", "upset", "frown"] },
-	{ "category": "face", "char": "😣", "name": "persevere", "keywords": ["face", "sick", "no", "upset", "oops"] },
-	{ "category": "face", "char": "😖", "name": "confounded", "keywords": ["face", "confused", "sick", "unwell", "oops", ": S"] },
-	{ "category": "face", "char": "😫", "name": "tired_face", "keywords": ["sick", "whine", "upset", "frustrated"] },
-	{ "category": "face", "char": "😩", "name": "weary", "keywords": ["face", "tired", "sleepy", "sad", "frustrated", "upset"] },
-	{ "category": "face", "char": "🥺", "name": "pleading", "keywords": ["face", "begging", "mercy"] },
-	{ "category": "face", "char": "😤", "name": "triumph", "keywords": ["face", "gas", "phew", "proud", "pride"] },
-	{ "category": "face", "char": "😮", "name": "open_mouth", "keywords": ["face", "surprise", "impressed", "wow", "whoa", ": O"] },
-	{ "category": "face", "char": "😱", "name": "scream", "keywords": ["face", "munch", "scared", "omg"] },
-	{ "category": "face", "char": "😨", "name": "fearful", "keywords": ["face", "scared", "terrified", "nervous", "oops", "huh"] },
-	{ "category": "face", "char": "😰", "name": "cold_sweat", "keywords": ["face", "nervous", "sweat"] },
-	{ "category": "face", "char": "😯", "name": "hushed", "keywords": ["face", "woo", "shh"] },
-	{ "category": "face", "char": "😦", "name": "frowning", "keywords": ["face", "aw", "what"] },
-	{ "category": "face", "char": "😧", "name": "anguished", "keywords": ["face", "stunned", "nervous"] },
-	{ "category": "face", "char": "😢", "name": "cry", "keywords": ["face", "tears", "sad", "depressed", "upset", ": '("] },
-	{ "category": "face", "char": "😥", "name": "disappointed_relieved", "keywords": ["face", "phew", "sweat", "nervous"] },
-	{ "category": "face", "char": "🤤", "name": "drooling_face", "keywords": ["face"] },
-	{ "category": "face", "char": "😪", "name": "sleepy", "keywords": ["face", "tired", "rest", "nap"] },
-	{ "category": "face", "char": "😓", "name": "sweat", "keywords": ["face", "hot", "sad", "tired", "exercise"] },
-	{ "category": "face", "char": "🥵", "name": "hot", "keywords": ["face", "feverish", "heat", "red", "sweating"] },
-	{ "category": "face", "char": "🥶", "name": "cold", "keywords": ["face", "blue", "freezing", "frozen", "frostbite", "icicles"] },
-	{ "category": "face", "char": "😭", "name": "sob", "keywords": ["face", "cry", "tears", "sad", "upset", "depressed"] },
-	{ "category": "face", "char": "😵", "name": "dizzy_face", "keywords": ["spent", "unconscious", "xox", "dizzy"] },
-	{ "category": "face", "char": "😲", "name": "astonished", "keywords": ["face", "xox", "surprised", "poisoned"] },
-	{ "category": "face", "char": "🤐", "name": "zipper_mouth_face", "keywords": ["face", "sealed", "zipper", "secret"] },
-	{ "category": "face", "char": "🤢", "name": "nauseated_face", "keywords": ["face", "vomit", "gross", "green", "sick", "throw up", "ill"] },
-	{ "category": "face", "char": "🤧", "name": "sneezing_face", "keywords": ["face", "gesundheit", "sneeze", "sick", "allergy"] },
-	{ "category": "face", "char": "🤮", "name": "vomiting", "keywords": ["face", "sick"] },
-	{ "category": "face", "char": "😷", "name": "mask", "keywords": ["face", "sick", "ill", "disease"] },
-	{ "category": "face", "char": "🤒", "name": "face_with_thermometer", "keywords": ["sick", "temperature", "thermometer", "cold", "fever"] },
-	{ "category": "face", "char": "🤕", "name": "face_with_head_bandage", "keywords": ["injured", "clumsy", "bandage", "hurt"] },
-	{ "category": "face", "char": "🥴", "name": "woozy", "keywords": ["face", "dizzy", "intoxicated", "tipsy", "wavy"] },
-	{ "category": "face", "char": "🥱", "name": "yawning", "keywords": ["face", "tired", "yawning"] },
-	{ "category": "face", "char": "😴", "name": "sleeping", "keywords": ["face", "tired", "sleepy", "night", "zzz"] },
-	{ "category": "face", "char": "💤", "name": "zzz", "keywords": ["sleepy", "tired", "dream"] },
-	{ "category": "face", "char": "\uD83D\uDE36\u200D\uD83C\uDF2B\uFE0F", "name": "face_in_clouds", "keywords": [] },
-	{ "category": "face", "char": "\uD83D\uDE2E\u200D\uD83D\uDCA8", "name": "face_exhaling", "keywords": [] },
-	{ "category": "face", "char": "\uD83D\uDE35\u200D\uD83D\uDCAB", "name": "face_with_spiral_eyes", "keywords": [] },
-	{ "category": "face", "char": "\uD83E\uDEE0", "name": "melting_face", "keywords": ["disappear", "dissolve", "liquid", "melt", "toketa"] },
-	{ "category": "face", "char": "\uD83E\uDEE2", "name": "face_with_open_eyes_and_hand_over_mouth", "keywords": ["amazement", "awe", "disbelief", "embarrass", "scared", "surprise", "ohoho"] },
-	{ "category": "face", "char": "\uD83E\uDEE3", "name": "face_with_peeking_eye", "keywords": ["captivated", "peep", "stare", "chunibyo"] },
-	{ "category": "face", "char": "\uD83E\uDEE1", "name": "saluting_face", "keywords": ["ok", "salute", "sunny", "troops", "yes", "raja"] },
-	{ "category": "face", "char": "\uD83E\uDEE5", "name": "dotted_line_face", "keywords": ["depressed", "disappear", "hide", "introvert", "invisible", "tensen"] },
-	{ "category": "face", "char": "\uD83E\uDEE4", "name": "face_with_diagonal_mouth", "keywords": ["disappointed", "meh", "skeptical", "unsure"] },
-	{ "category": "face", "char": "\uD83E\uDD79", "name": "face_holding_back_tears", "keywords": ["angry", "cry", "proud", "resist", "sad"] },
-	{ "category": "face", "char": "💩", "name": "poop", "keywords": ["hankey", "shitface", "fail", "turd", "shit"] },
-	{ "category": "face", "char": "😈", "name": "smiling_imp", "keywords": ["devil", "horns"] },
-	{ "category": "face", "char": "👿", "name": "imp", "keywords": ["devil", "angry", "horns"] },
-	{ "category": "face", "char": "👹", "name": "japanese_ogre", "keywords": ["monster", "red", "mask", "halloween", "scary", "creepy", "devil", "demon", "japanese", "ogre"] },
-	{ "category": "face", "char": "👺", "name": "japanese_goblin", "keywords": ["red", "evil", "mask", "monster", "scary", "creepy", "japanese", "goblin"] },
-	{ "category": "face", "char": "💀", "name": "skull", "keywords": ["dead", "skeleton", "creepy", "death"] },
-	{ "category": "face", "char": "👻", "name": "ghost", "keywords": ["halloween", "spooky", "scary"] },
-	{ "category": "face", "char": "👽", "name": "alien", "keywords": ["UFO", "paul", "weird", "outer_space"] },
-	{ "category": "face", "char": "🤖", "name": "robot", "keywords": ["computer", "machine", "bot"] },
-	{ "category": "face", "char": "😺", "name": "smiley_cat", "keywords": ["animal", "cats", "happy", "smile"] },
-	{ "category": "face", "char": "😸", "name": "smile_cat", "keywords": ["animal", "cats", "smile"] },
-	{ "category": "face", "char": "😹", "name": "joy_cat", "keywords": ["animal", "cats", "haha", "happy", "tears"] },
-	{ "category": "face", "char": "😻", "name": "heart_eyes_cat", "keywords": ["animal", "love", "like", "affection", "cats", "valentines", "heart"] },
-	{ "category": "face", "char": "😼", "name": "smirk_cat", "keywords": ["animal", "cats", "smirk"] },
-	{ "category": "face", "char": "😽", "name": "kissing_cat", "keywords": ["animal", "cats", "kiss"] },
-	{ "category": "face", "char": "🙀", "name": "scream_cat", "keywords": ["animal", "cats", "munch", "scared", "scream"] },
-	{ "category": "face", "char": "😿", "name": "crying_cat_face", "keywords": ["animal", "tears", "weep", "sad", "cats", "upset", "cry"] },
-	{ "category": "face", "char": "😾", "name": "pouting_cat", "keywords": ["animal", "cats"] },
-	{ "category": "people", "char": "🤲", "name": "palms_up", "keywords": ["hands", "gesture", "cupped", "prayer"] },
-	{ "category": "people", "char": "🙌", "name": "raised_hands", "keywords": ["gesture", "hooray", "yea", "celebration", "hands"] },
-	{ "category": "people", "char": "👏", "name": "clap", "keywords": ["hands", "praise", "applause", "congrats", "yay"] },
-	{ "category": "people", "char": "👋", "name": "wave", "keywords": ["hands", "gesture", "goodbye", "solong", "farewell", "hello", "hi", "palm"] },
-	{ "category": "people", "char": "🤙", "name": "call_me_hand", "keywords": ["hands", "gesture"] },
-	{ "category": "people", "char": "👍", "name": "+1", "keywords": ["thumbsup", "yes", "awesome", "good", "agree", "accept", "cool", "hand", "like"] },
-	{ "category": "people", "char": "👎", "name": "-1", "keywords": ["thumbsdown", "no", "dislike", "hand"] },
-	{ "category": "people", "char": "👊", "name": "facepunch", "keywords": ["angry", "violence", "fist", "hit", "attack", "hand"] },
-	{ "category": "people", "char": "✊", "name": "fist", "keywords": ["fingers", "hand", "grasp"] },
-	{ "category": "people", "char": "🤛", "name": "fist_left", "keywords": ["hand", "fistbump"] },
-	{ "category": "people", "char": "🤜", "name": "fist_right", "keywords": ["hand", "fistbump"] },
-	{ "category": "people", "char": "✌", "name": "v", "keywords": ["fingers", "ohyeah", "hand", "peace", "victory", "two"] },
-	{ "category": "people", "char": "👌", "name": "ok_hand", "keywords": ["fingers", "limbs", "perfect", "ok", "okay"] },
-	{ "category": "people", "char": "✋", "name": "raised_hand", "keywords": ["fingers", "stop", "highfive", "palm", "ban"] },
-	{ "category": "people", "char": "🤚", "name": "raised_back_of_hand", "keywords": ["fingers", "raised", "backhand"] },
-	{ "category": "people", "char": "👐", "name": "open_hands", "keywords": ["fingers", "butterfly", "hands", "open"] },
-	{ "category": "people", "char": "💪", "name": "muscle", "keywords": ["arm", "flex", "hand", "summer", "strong", "biceps"] },
-	{ "category": "people", "char": "🦾", "name": "mechanical_arm", "keywords": ["flex", "hand", "strong", "biceps"] },
-	{ "category": "people", "char": "🙏", "name": "pray", "keywords": ["please", "hope", "wish", "namaste", "highfive"] },
-	{ "category": "people", "char": "🦶", "name": "foot", "keywords": ["kick", "stomp"] },
-	{ "category": "people", "char": "🦵", "name": "leg", "keywords": ["kick", "limb"] },
-	{ "category": "people", "char": "🦿", "name": "mechanical_leg", "keywords": ["kick", "limb"] },
-	{ "category": "people", "char": "🤝", "name": "handshake", "keywords": ["agreement", "shake"] },
-	{ "category": "people", "char": "☝", "name": "point_up", "keywords": ["hand", "fingers", "direction", "up"] },
-	{ "category": "people", "char": "👆", "name": "point_up_2", "keywords": ["fingers", "hand", "direction", "up"] },
-	{ "category": "people", "char": "👇", "name": "point_down", "keywords": ["fingers", "hand", "direction", "down"] },
-	{ "category": "people", "char": "👈", "name": "point_left", "keywords": ["direction", "fingers", "hand", "left"] },
-	{ "category": "people", "char": "👉", "name": "point_right", "keywords": ["fingers", "hand", "direction", "right"] },
-	{ "category": "people", "char": "🖕", "name": "fu", "keywords": ["hand", "fingers", "rude", "middle", "flipping"] },
-	{ "category": "people", "char": "🖐", "name": "raised_hand_with_fingers_splayed", "keywords": ["hand", "fingers", "palm"] },
-	{ "category": "people", "char": "🤟", "name": "love_you", "keywords": ["hand", "fingers", "gesture"] },
-	{ "category": "people", "char": "🤘", "name": "metal", "keywords": ["hand", "fingers", "evil_eye", "sign_of_horns", "rock_on"] },
-	{ "category": "people", "char": "🤞", "name": "crossed_fingers", "keywords": ["good", "lucky"] },
-	{ "category": "people", "char": "🖖", "name": "vulcan_salute", "keywords": ["hand", "fingers", "spock", "star trek"] },
-	{ "category": "people", "char": "✍", "name": "writing_hand", "keywords": ["lower_left_ballpoint_pen", "stationery", "write", "compose"] },
-	{ "category": "people", "char": "\uD83E\uDEF0", "name": "hand_with_index_finger_and_thumb_crossed", "keywords": [] },
-	{ "category": "people", "char": "\uD83E\uDEF1", "name": "rightwards_hand", "keywords": [] },
-	{ "category": "people", "char": "\uD83E\uDEF2", "name": "leftwards_hand", "keywords": [] },
-	{ "category": "people", "char": "\uD83E\uDEF3", "name": "palm_down_hand", "keywords": [] },
-	{ "category": "people", "char": "\uD83E\uDEF4", "name": "palm_up_hand", "keywords": [] },
-	{ "category": "people", "char": "\uD83E\uDEF5", "name": "index_pointing_at_the_viewer", "keywords": [] },
-	{ "category": "people", "char": "\uD83E\uDEF6", "name": "heart_hands", "keywords": ["moemoekyun"] },
-	{ "category": "people", "char": "🤏", "name": "pinching_hand", "keywords": ["hand", "fingers"] },
-	{ "category": "people", "char": "🤌", "name": "pinched_fingers", "keywords": ["hand", "fingers"] },
-	{ "category": "people", "char": "🤳", "name": "selfie", "keywords": ["camera", "phone"] },
-	{ "category": "people", "char": "💅", "name": "nail_care", "keywords": ["beauty", "manicure", "finger", "fashion", "nail"] },
-	{ "category": "people", "char": "👄", "name": "lips", "keywords": ["mouth", "kiss"] },
-	{ "category": "people", "char": "\uD83E\uDEE6", "name": "biting_lip", "keywords": [] },
-	{ "category": "people", "char": "🦷", "name": "tooth", "keywords": ["teeth", "dentist"] },
-	{ "category": "people", "char": "👅", "name": "tongue", "keywords": ["mouth", "playful"] },
-	{ "category": "people", "char": "👂", "name": "ear", "keywords": ["face", "hear", "sound", "listen"] },
-	{ "category": "people", "char": "🦻", "name": "ear_with_hearing_aid", "keywords": ["face", "hear", "sound", "listen"] },
-	{ "category": "people", "char": "👃", "name": "nose", "keywords": ["smell", "sniff"] },
-	{ "category": "people", "char": "👁", "name": "eye", "keywords": ["face", "look", "see", "watch", "stare"] },
-	{ "category": "people", "char": "👀", "name": "eyes", "keywords": ["look", "watch", "stalk", "peek", "see"] },
-	{ "category": "people", "char": "🧠", "name": "brain", "keywords": ["smart", "intelligent"] },
-	{ "category": "people", "char": "🫀", "name": "anatomical_heart", "keywords": [] },
-	{ "category": "people", "char": "🫁", "name": "lungs", "keywords": [] },
-	{ "category": "people", "char": "👤", "name": "bust_in_silhouette", "keywords": ["user", "person", "human"] },
-	{ "category": "people", "char": "👥", "name": "busts_in_silhouette", "keywords": ["user", "person", "human", "group", "team"] },
-	{ "category": "people", "char": "🗣", "name": "speaking_head", "keywords": ["user", "person", "human", "sing", "say", "talk"] },
-	{ "category": "people", "char": "👶", "name": "baby", "keywords": ["child", "boy", "girl", "toddler"] },
-	{ "category": "people", "char": "🧒", "name": "child", "keywords": ["gender-neutral", "young"] },
-	{ "category": "people", "char": "👦", "name": "boy", "keywords": ["man", "male", "guy", "teenager"] },
-	{ "category": "people", "char": "👧", "name": "girl", "keywords": ["female", "woman", "teenager"] },
-	{ "category": "people", "char": "🧑", "name": "adult", "keywords": ["gender-neutral", "person"] },
-	{ "category": "people", "char": "👨", "name": "man", "keywords": ["mustache", "father", "dad", "guy", "classy", "sir", "moustache"] },
-	{ "category": "people", "char": "👩", "name": "woman", "keywords": ["female", "girls", "lady"] },
-	{ "category": "people", "char": "🧑‍🦱", "name": "curly_hair", "keywords": ["curly", "afro", "braids", "ringlets"] },
-	{ "category": "people", "char": "👩‍🦱", "name": "curly_hair_woman", "keywords": ["woman", "female", "girl", "curly", "afro", "braids", "ringlets"] },
-	{ "category": "people", "char": "👨‍🦱", "name": "curly_hair_man", "keywords": ["man", "male", "boy", "guy", "curly", "afro", "braids", "ringlets"] },
-	{ "category": "people", "char": "🧑‍🦰", "name": "red_hair", "keywords": ["redhead"] },
-	{ "category": "people", "char": "👩‍🦰", "name": "red_hair_woman", "keywords": ["woman", "female", "girl", "ginger", "redhead"] },
-	{ "category": "people", "char": "👨‍🦰", "name": "red_hair_man", "keywords": ["man", "male", "boy", "guy", "ginger", "redhead"] },
-	{ "category": "people", "char": "👱‍♀️", "name": "blonde_woman", "keywords": ["woman", "female", "girl", "blonde", "person"] },
-	{ "category": "people", "char": "👱", "name": "blonde_man", "keywords": ["man", "male", "boy", "blonde", "guy", "person"] },
-	{ "category": "people", "char": "🧑‍🦳", "name": "white_hair", "keywords": ["gray", "old", "white"] },
-	{ "category": "people", "char": "👩‍🦳", "name": "white_hair_woman", "keywords": ["woman", "female", "girl", "gray", "old", "white"] },
-	{ "category": "people", "char": "👨‍🦳", "name": "white_hair_man", "keywords": ["man", "male", "boy", "guy", "gray", "old", "white"] },
-	{ "category": "people", "char": "🧑‍🦲", "name": "bald", "keywords": ["bald", "chemotherapy", "hairless", "shaven"] },
-	{ "category": "people", "char": "👩‍🦲", "name": "bald_woman", "keywords": ["woman", "female", "girl", "bald", "chemotherapy", "hairless", "shaven"] },
-	{ "category": "people", "char": "👨‍🦲", "name": "bald_man", "keywords": ["man", "male", "boy", "guy", "bald", "chemotherapy", "hairless", "shaven"] },
-	{ "category": "people", "char": "🧔", "name": "bearded_person", "keywords": ["person", "bewhiskered"] },
-	{ "category": "people", "char": "🧓", "name": "older_adult", "keywords": ["human", "elder", "senior", "gender-neutral"] },
-	{ "category": "people", "char": "👴", "name": "older_man", "keywords": ["human", "male", "men", "old", "elder", "senior"] },
-	{ "category": "people", "char": "👵", "name": "older_woman", "keywords": ["human", "female", "women", "lady", "old", "elder", "senior"] },
-	{ "category": "people", "char": "👲", "name": "man_with_gua_pi_mao", "keywords": ["male", "boy", "chinese"] },
-	{ "category": "people", "char": "🧕", "name": "woman_with_headscarf", "keywords": ["female", "hijab", "mantilla", "tichel"] },
-	{ "category": "people", "char": "👳‍♀️", "name": "woman_with_turban", "keywords": ["female", "indian", "hinduism", "arabs", "woman"] },
-	{ "category": "people", "char": "👳", "name": "man_with_turban", "keywords": ["male", "indian", "hinduism", "arabs"] },
-	{ "category": "people", "char": "👮‍♀️", "name": "policewoman", "keywords": ["woman", "police", "law", "legal", "enforcement", "arrest", "911", "female"] },
-	{ "category": "people", "char": "👮", "name": "policeman", "keywords": ["man", "police", "law", "legal", "enforcement", "arrest", "911"] },
-	{ "category": "people", "char": "👷‍♀️", "name": "construction_worker_woman", "keywords": ["female", "human", "wip", "build", "construction", "worker", "labor", "woman"] },
-	{ "category": "people", "char": "👷", "name": "construction_worker_man", "keywords": ["male", "human", "wip", "guy", "build", "construction", "worker", "labor"] },
-	{ "category": "people", "char": "💂‍♀️", "name": "guardswoman", "keywords": ["uk", "gb", "british", "female", "royal", "woman"] },
-	{ "category": "people", "char": "💂", "name": "guardsman", "keywords": ["uk", "gb", "british", "male", "guy", "royal"] },
-	{ "category": "people", "char": "🕵️‍♀️", "name": "female_detective", "keywords": ["human", "spy", "detective", "female", "woman"] },
-	{ "category": "people", "char": "🕵", "name": "male_detective", "keywords": ["human", "spy", "detective"] },
-	{ "category": "people", "char": "🧑‍⚕️", "name": "health_worker", "keywords": ["doctor", "nurse", "therapist", "healthcare", "human"] },
-	{ "category": "people", "char": "👩‍⚕️", "name": "woman_health_worker", "keywords": ["doctor", "nurse", "therapist", "healthcare", "woman", "human"] },
-	{ "category": "people", "char": "👨‍⚕️", "name": "man_health_worker", "keywords": ["doctor", "nurse", "therapist", "healthcare", "man", "human"] },
-	{ "category": "people", "char": "🧑‍🌾", "name": "farmer", "keywords": ["rancher", "gardener", "human"] },
-	{ "category": "people", "char": "👩‍🌾", "name": "woman_farmer", "keywords": ["rancher", "gardener", "woman", "human"] },
-	{ "category": "people", "char": "👨‍🌾", "name": "man_farmer", "keywords": ["rancher", "gardener", "man", "human"] },
-	{ "category": "people", "char": "🧑‍🍳", "name": "cook", "keywords": ["chef", "human"] },
-	{ "category": "people", "char": "👩‍🍳", "name": "woman_cook", "keywords": ["chef", "woman", "human"] },
-	{ "category": "people", "char": "👨‍🍳", "name": "man_cook", "keywords": ["chef", "man", "human"] },
-	{ "category": "people", "char": "🧑‍🎓", "name": "student", "keywords": ["graduate", "human"] },
-	{ "category": "people", "char": "👩‍🎓", "name": "woman_student", "keywords": ["graduate", "woman", "human"] },
-	{ "category": "people", "char": "👨‍🎓", "name": "man_student", "keywords": ["graduate", "man", "human"] },
-	{ "category": "people", "char": "🧑‍🎤", "name": "singer", "keywords": ["rockstar", "entertainer", "human"] },
-	{ "category": "people", "char": "👩‍🎤", "name": "woman_singer", "keywords": ["rockstar", "entertainer", "woman", "human"] },
-	{ "category": "people", "char": "👨‍🎤", "name": "man_singer", "keywords": ["rockstar", "entertainer", "man", "human"] },
-	{ "category": "people", "char": "🧑‍🏫", "name": "teacher", "keywords": ["instructor", "professor", "human"] },
-	{ "category": "people", "char": "👩‍🏫", "name": "woman_teacher", "keywords": ["instructor", "professor", "woman", "human"] },
-	{ "category": "people", "char": "👨‍🏫", "name": "man_teacher", "keywords": ["instructor", "professor", "man", "human"] },
-	{ "category": "people", "char": "🧑‍🏭", "name": "factory_worker", "keywords": ["assembly", "industrial", "human"] },
-	{ "category": "people", "char": "👩‍🏭", "name": "woman_factory_worker", "keywords": ["assembly", "industrial", "woman", "human"] },
-	{ "category": "people", "char": "👨‍🏭", "name": "man_factory_worker", "keywords": ["assembly", "industrial", "man", "human"] },
-	{ "category": "people", "char": "🧑‍💻", "name": "technologist", "keywords": ["coder", "developer", "engineer", "programmer", "software", "human", "laptop", "computer"] },
-	{ "category": "people", "char": "👩‍💻", "name": "woman_technologist", "keywords": ["coder", "developer", "engineer", "programmer", "software", "woman", "human", "laptop", "computer"] },
-	{ "category": "people", "char": "👨‍💻", "name": "man_technologist", "keywords": ["coder", "developer", "engineer", "programmer", "software", "man", "human", "laptop", "computer"] },
-	{ "category": "people", "char": "🧑‍💼", "name": "office_worker", "keywords": ["business", "manager", "human"] },
-	{ "category": "people", "char": "👩‍💼", "name": "woman_office_worker", "keywords": ["business", "manager", "woman", "human"] },
-	{ "category": "people", "char": "👨‍💼", "name": "man_office_worker", "keywords": ["business", "manager", "man", "human"] },
-	{ "category": "people", "char": "🧑‍🔧", "name": "mechanic", "keywords": ["plumber", "human", "wrench"] },
-	{ "category": "people", "char": "👩‍🔧", "name": "woman_mechanic", "keywords": ["plumber", "woman", "human", "wrench"] },
-	{ "category": "people", "char": "👨‍🔧", "name": "man_mechanic", "keywords": ["plumber", "man", "human", "wrench"] },
-	{ "category": "people", "char": "🧑‍🔬", "name": "scientist", "keywords": ["biologist", "chemist", "engineer", "physicist", "human"] },
-	{ "category": "people", "char": "👩‍🔬", "name": "woman_scientist", "keywords": ["biologist", "chemist", "engineer", "physicist", "woman", "human"] },
-	{ "category": "people", "char": "👨‍🔬", "name": "man_scientist", "keywords": ["biologist", "chemist", "engineer", "physicist", "man", "human"] },
-	{ "category": "people", "char": "🧑‍🎨", "name": "artist", "keywords": ["painter", "human"] },
-	{ "category": "people", "char": "👩‍🎨", "name": "woman_artist", "keywords": ["painter", "woman", "human"] },
-	{ "category": "people", "char": "👨‍🎨", "name": "man_artist", "keywords": ["painter", "man", "human"] },
-	{ "category": "people", "char": "🧑‍🚒", "name": "firefighter", "keywords": ["fireman", "human"] },
-	{ "category": "people", "char": "👩‍🚒", "name": "woman_firefighter", "keywords": ["fireman", "woman", "human"] },
-	{ "category": "people", "char": "👨‍🚒", "name": "man_firefighter", "keywords": ["fireman", "man", "human"] },
-	{ "category": "people", "char": "🧑‍✈️", "name": "pilot", "keywords": ["aviator", "plane", "human"] },
-	{ "category": "people", "char": "👩‍✈️", "name": "woman_pilot", "keywords": ["aviator", "plane", "woman", "human"] },
-	{ "category": "people", "char": "👨‍✈️", "name": "man_pilot", "keywords": ["aviator", "plane", "man", "human"] },
-	{ "category": "people", "char": "🧑‍🚀", "name": "astronaut", "keywords": ["space", "rocket", "human"] },
-	{ "category": "people", "char": "👩‍🚀", "name": "woman_astronaut", "keywords": ["space", "rocket", "woman", "human"] },
-	{ "category": "people", "char": "👨‍🚀", "name": "man_astronaut", "keywords": ["space", "rocket", "man", "human"] },
-	{ "category": "people", "char": "🧑‍⚖️", "name": "judge", "keywords": ["justice", "court", "human"] },
-	{ "category": "people", "char": "👩‍⚖️", "name": "woman_judge", "keywords": ["justice", "court", "woman", "human"] },
-	{ "category": "people", "char": "👨‍⚖️", "name": "man_judge", "keywords": ["justice", "court", "man", "human"] },
-	{ "category": "people", "char": "🦸‍♀️", "name": "woman_superhero", "keywords": ["woman", "female", "good", "heroine", "superpowers"] },
-	{ "category": "people", "char": "🦸‍♂️", "name": "man_superhero", "keywords": ["man", "male", "good", "hero", "superpowers"] },
-	{ "category": "people", "char": "🦹‍♀️", "name": "woman_supervillain", "keywords": ["woman", "female", "evil", "bad", "criminal", "heroine", "superpowers"] },
-	{ "category": "people", "char": "🦹‍♂️", "name": "man_supervillain", "keywords": ["man", "male", "evil", "bad", "criminal", "hero", "superpowers"] },
-	{ "category": "people", "char": "🤶", "name": "mrs_claus", "keywords": ["woman", "female", "xmas", "mother christmas"] },
-	{ "category": "people", "char": "\uD83E\uDDD1\u200D\uD83C\uDF84", "name": "mx_claus", "keywords": ["xmas", "christmas"] },
-	{ "category": "people", "char": "🎅", "name": "santa", "keywords": ["festival", "man", "male", "xmas", "father christmas"] },
-	{ "category": "people", "char": "🥷", "name": "ninja", "keywords": [] },
-	{ "category": "people", "char": "🧙‍♀️", "name": "sorceress", "keywords": ["woman", "female", "mage", "witch"] },
-	{ "category": "people", "char": "🧙‍♂️", "name": "wizard", "keywords": ["man", "male", "mage", "sorcerer"] },
-	{ "category": "people", "char": "🧝‍♀️", "name": "woman_elf", "keywords": ["woman", "female"] },
-	{ "category": "people", "char": "🧝‍♂️", "name": "man_elf", "keywords": ["man", "male"] },
-	{ "category": "people", "char": "🧛‍♀️", "name": "woman_vampire", "keywords": ["woman", "female"] },
-	{ "category": "people", "char": "🧛‍♂️", "name": "man_vampire", "keywords": ["man", "male", "dracula"] },
-	{ "category": "people", "char": "🧟‍♀️", "name": "woman_zombie", "keywords": ["woman", "female", "undead", "walking dead"] },
-	{ "category": "people", "char": "🧟‍♂️", "name": "man_zombie", "keywords": ["man", "male", "dracula", "undead", "walking dead"] },
-	{ "category": "people", "char": "🧞‍♀️", "name": "woman_genie", "keywords": ["woman", "female"] },
-	{ "category": "people", "char": "🧞‍♂️", "name": "man_genie", "keywords": ["man", "male"] },
-	{ "category": "people", "char": "🧜‍♀️", "name": "mermaid", "keywords": ["woman", "female", "merwoman", "ariel"] },
-	{ "category": "people", "char": "🧜‍♂️", "name": "merman", "keywords": ["man", "male", "triton"] },
-	{ "category": "people", "char": "🧚‍♀️", "name": "woman_fairy", "keywords": ["woman", "female"] },
-	{ "category": "people", "char": "🧚‍♂️", "name": "man_fairy", "keywords": ["man", "male"] },
-	{ "category": "people", "char": "👼", "name": "angel", "keywords": ["heaven", "wings", "halo"] },
-	{ "category": "people", "char": "\uD83E\uDDCC", "name": "troll", "keywords": [] },
-	{ "category": "people", "char": "🤰", "name": "pregnant_woman", "keywords": ["baby"] },
-	{ "category": "people", "char": "\uD83E\uDEC3", "name": "pregnant_man", "keywords": [] },
-	{ "category": "people", "char": "\uD83E\uDEC4", "name": "pregnant_person", "keywords": [] },
-	{ "category": "people", "char": "\uD83E\uDEC5", "name": "person_with_crown", "keywords": [] },
-	{ "category": "people", "char": "🤱", "name": "breastfeeding", "keywords": ["nursing", "baby"] },
-	{ "category": "people", "char": "\uD83D\uDC69\u200D\uD83C\uDF7C", "name": "woman_feeding_baby", "keywords": [] },
-	{ "category": "people", "char": "\uD83D\uDC68\u200D\uD83C\uDF7C", "name": "man_feeding_baby", "keywords": [] },
-	{ "category": "people", "char": "\uD83E\uDDD1\u200D\uD83C\uDF7C", "name": "person_feeding_baby", "keywords": [] },
-	{ "category": "people", "char": "👸", "name": "princess", "keywords": ["girl", "woman", "female", "blond", "crown", "royal", "queen"] },
-	{ "category": "people", "char": "🤴", "name": "prince", "keywords": ["boy", "man", "male", "crown", "royal", "king"] },
-	{ "category": "people", "char": "👰", "name": "person_with_veil", "keywords": ["couple", "marriage", "wedding", "woman", "bride"] },
-	{ "category": "people", "char": "👰", "name": "bride_with_veil", "keywords": ["couple", "marriage", "wedding", "woman", "bride"] },
-	{ "category": "people", "char": "🤵", "name": "person_in_tuxedo", "keywords": ["couple", "marriage", "wedding", "groom"] },
-	{ "category": "people", "char": "🤵", "name": "man_in_tuxedo", "keywords": ["couple", "marriage", "wedding", "groom"] },
-	{ "category": "people", "char": "🏃‍♀️", "name": "running_woman", "keywords": ["woman", "walking", "exercise", "race", "running", "female"] },
-	{ "category": "people", "char": "🏃", "name": "running_man", "keywords": ["man", "walking", "exercise", "race", "running"] },
-	{ "category": "people", "char": "🚶‍♀️", "name": "walking_woman", "keywords": ["human", "feet", "steps", "woman", "female"] },
-	{ "category": "people", "char": "🚶", "name": "walking_man", "keywords": ["human", "feet", "steps"] },
-	{ "category": "people", "char": "💃", "name": "dancer", "keywords": ["female", "girl", "woman", "fun"] },
-	{ "category": "people", "char": "🕺", "name": "man_dancing", "keywords": ["male", "boy", "fun", "dancer"] },
-	{ "category": "people", "char": "👯", "name": "dancing_women", "keywords": ["female", "bunny", "women", "girls"] },
-	{ "category": "people", "char": "👯‍♂️", "name": "dancing_men", "keywords": ["male", "bunny", "men", "boys"] },
-	{ "category": "people", "char": "👫", "name": "couple", "keywords": ["pair", "people", "human", "love", "date", "dating", "like", "affection", "valentines", "marriage"] },
-	{ "category": "people", "char": "\uD83E\uDDD1\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1", "name": "people_holding_hands", "keywords": ["pair", "couple", "love", "like", "bromance", "friendship", "people", "human"] },
-	{ "category": "people", "char": "👬", "name": "two_men_holding_hands", "keywords": ["pair", "couple", "love", "like", "bromance", "friendship", "people", "man", "human"] },
-	{ "category": "people", "char": "👭", "name": "two_women_holding_hands", "keywords": ["pair", "couple", "love", "like", "bromance", "friendship", "people", "female", "human"] },
-	{ "category": "people", "char": "🫂", "name": "people_hugging", "keywords": [] },
-	{ "category": "people", "char": "🙇‍♀️", "name": "bowing_woman", "keywords": ["woman", "female", "girl"] },
-	{ "category": "people", "char": "🙇", "name": "bowing_man", "keywords": ["man", "male", "boy"] },
-	{ "category": "people", "char": "🤦‍♂️", "name": "man_facepalming", "keywords": ["man", "male", "boy", "disbelief"] },
-	{ "category": "people", "char": "🤦‍♀️", "name": "woman_facepalming", "keywords": ["woman", "female", "girl", "disbelief"] },
-	{ "category": "people", "char": "🤷", "name": "woman_shrugging", "keywords": ["woman", "female", "girl", "confused", "indifferent", "doubt"] },
-	{ "category": "people", "char": "🤷‍♂️", "name": "man_shrugging", "keywords": ["man", "male", "boy", "confused", "indifferent", "doubt"] },
-	{ "category": "people", "char": "💁", "name": "tipping_hand_woman", "keywords": ["female", "girl", "woman", "human", "information"] },
-	{ "category": "people", "char": "💁‍♂️", "name": "tipping_hand_man", "keywords": ["male", "boy", "man", "human", "information"] },
-	{ "category": "people", "char": "🙅", "name": "no_good_woman", "keywords": ["female", "girl", "woman", "nope"] },
-	{ "category": "people", "char": "🙅‍♂️", "name": "no_good_man", "keywords": ["male", "boy", "man", "nope"] },
-	{ "category": "people", "char": "🙆", "name": "ok_woman", "keywords": ["women", "girl", "female", "pink", "human", "woman"] },
-	{ "category": "people", "char": "🙆‍♂️", "name": "ok_man", "keywords": ["men", "boy", "male", "blue", "human", "man"] },
-	{ "category": "people", "char": "🙋", "name": "raising_hand_woman", "keywords": ["female", "girl", "woman"] },
-	{ "category": "people", "char": "🙋‍♂️", "name": "raising_hand_man", "keywords": ["male", "boy", "man"] },
-	{ "category": "people", "char": "🙎", "name": "pouting_woman", "keywords": ["female", "girl", "woman"] },
-	{ "category": "people", "char": "🙎‍♂️", "name": "pouting_man", "keywords": ["male", "boy", "man"] },
-	{ "category": "people", "char": "🙍", "name": "frowning_woman", "keywords": ["female", "girl", "woman", "sad", "depressed", "discouraged", "unhappy"] },
-	{ "category": "people", "char": "🙍‍♂️", "name": "frowning_man", "keywords": ["male", "boy", "man", "sad", "depressed", "discouraged", "unhappy"] },
-	{ "category": "people", "char": "💇", "name": "haircut_woman", "keywords": ["female", "girl", "woman"] },
-	{ "category": "people", "char": "💇‍♂️", "name": "haircut_man", "keywords": ["male", "boy", "man"] },
-	{ "category": "people", "char": "💆", "name": "massage_woman", "keywords": ["female", "girl", "woman", "head"] },
-	{ "category": "people", "char": "💆‍♂️", "name": "massage_man", "keywords": ["male", "boy", "man", "head"] },
-	{ "category": "people", "char": "🧖‍♀️", "name": "woman_in_steamy_room", "keywords": ["female", "woman", "spa", "steamroom", "sauna"] },
-	{ "category": "people", "char": "🧖‍♂️", "name": "man_in_steamy_room", "keywords": ["male", "man", "spa", "steamroom", "sauna"] },
-	{ "category": "people", "char": "🧏‍♀️", "name": "woman_deaf", "keywords": ["woman", "female"] },
-	{ "category": "people", "char": "🧏‍♂️", "name": "man_deaf", "keywords": ["man", "male"] },
-	{ "category": "people", "char": "🧍‍♀️", "name": "woman_standing", "keywords": ["woman", "female"] },
-	{ "category": "people", "char": "🧍‍♂️", "name": "man_standing", "keywords": ["man", "male"] },
-	{ "category": "people", "char": "🧎‍♀️", "name": "woman_kneeling", "keywords": ["woman", "female"] },
-	{ "category": "people", "char": "🧎‍♂️", "name": "man_kneeling", "keywords": ["man", "male"] },
-	{ "category": "people", "char": "🧑‍🦯", "name": "person_with_probing_cane", "keywords": ["accessibility", "blind"] },
-	{ "category": "people", "char": "👩‍🦯", "name": "woman_with_probing_cane", "keywords": ["woman", "female", "accessibility", "blind"] },
-	{ "category": "people", "char": "👨‍🦯", "name": "man_with_probing_cane", "keywords": ["man", "male", "accessibility", "blind"] },
-	{ "category": "people", "char": "🧑‍🦼", "name": "person_in_motorized_wheelchair", "keywords": ["accessibility"] },
-	{ "category": "people", "char": "👩‍🦼", "name": "woman_in_motorized_wheelchair", "keywords": ["woman", "female", "accessibility"] },
-	{ "category": "people", "char": "👨‍🦼", "name": "man_in_motorized_wheelchair", "keywords": ["man", "male", "accessibility"] },
-	{ "category": "people", "char": "🧑‍🦽", "name": "person_in_manual_wheelchair", "keywords": ["accessibility"] },
-	{ "category": "people", "char": "👩‍🦽", "name": "woman_in_manual_wheelchair", "keywords": ["woman", "female", "accessibility"] },
-	{ "category": "people", "char": "👨‍🦽", "name": "man_in_manual_wheelchair", "keywords": ["man", "male", "accessibility"] },
-	{ "category": "people", "char": "💑", "name": "couple_with_heart_woman_man", "keywords": ["pair", "love", "like", "affection", "human", "dating", "valentines", "marriage"] },
-	{ "category": "people", "char": "👩‍❤️‍👩", "name": "couple_with_heart_woman_woman", "keywords": ["pair", "love", "like", "affection", "human", "dating", "valentines", "marriage"] },
-	{ "category": "people", "char": "👨‍❤️‍👨", "name": "couple_with_heart_man_man", "keywords": ["pair", "love", "like", "affection", "human", "dating", "valentines", "marriage"] },
-	{ "category": "people", "char": "💏", "name": "couplekiss_man_woman", "keywords": ["pair", "valentines", "love", "like", "dating", "marriage"] },
-	{ "category": "people", "char": "👩‍❤️‍💋‍👩", "name": "couplekiss_woman_woman", "keywords": ["pair", "valentines", "love", "like", "dating", "marriage"] },
-	{ "category": "people", "char": "👨‍❤️‍💋‍👨", "name": "couplekiss_man_man", "keywords": ["pair", "valentines", "love", "like", "dating", "marriage"] },
-	{ "category": "people", "char": "👪", "name": "family_man_woman_boy", "keywords": ["home", "parents", "child", "mom", "dad", "father", "mother", "people", "human"] },
-	{ "category": "people", "char": "👨‍👩‍👧", "name": "family_man_woman_girl", "keywords": ["home", "parents", "people", "human", "child"] },
-	{ "category": "people", "char": "👨‍👩‍👧‍👦", "name": "family_man_woman_girl_boy", "keywords": ["home", "parents", "people", "human", "children"] },
-	{ "category": "people", "char": "👨‍👩‍👦‍👦", "name": "family_man_woman_boy_boy", "keywords": ["home", "parents", "people", "human", "children"] },
-	{ "category": "people", "char": "👨‍👩‍👧‍👧", "name": "family_man_woman_girl_girl", "keywords": ["home", "parents", "people", "human", "children"] },
-	{ "category": "people", "char": "👩‍👩‍👦", "name": "family_woman_woman_boy", "keywords": ["home", "parents", "people", "human", "children"] },
-	{ "category": "people", "char": "👩‍👩‍👧", "name": "family_woman_woman_girl", "keywords": ["home", "parents", "people", "human", "children"] },
-	{ "category": "people", "char": "👩‍👩‍👧‍👦", "name": "family_woman_woman_girl_boy", "keywords": ["home", "parents", "people", "human", "children"] },
-	{ "category": "people", "char": "👩‍👩‍👦‍👦", "name": "family_woman_woman_boy_boy", "keywords": ["home", "parents", "people", "human", "children"] },
-	{ "category": "people", "char": "👩‍👩‍👧‍👧", "name": "family_woman_woman_girl_girl", "keywords": ["home", "parents", "people", "human", "children"] },
-	{ "category": "people", "char": "👨‍👨‍👦", "name": "family_man_man_boy", "keywords": ["home", "parents", "people", "human", "children"] },
-	{ "category": "people", "char": "👨‍👨‍👧", "name": "family_man_man_girl", "keywords": ["home", "parents", "people", "human", "children"] },
-	{ "category": "people", "char": "👨‍👨‍👧‍👦", "name": "family_man_man_girl_boy", "keywords": ["home", "parents", "people", "human", "children"] },
-	{ "category": "people", "char": "👨‍👨‍👦‍👦", "name": "family_man_man_boy_boy", "keywords": ["home", "parents", "people", "human", "children"] },
-	{ "category": "people", "char": "👨‍👨‍👧‍👧", "name": "family_man_man_girl_girl", "keywords": ["home", "parents", "people", "human", "children"] },
-	{ "category": "people", "char": "👩‍👦", "name": "family_woman_boy", "keywords": ["home", "parent", "people", "human", "child"] },
-	{ "category": "people", "char": "👩‍👧", "name": "family_woman_girl", "keywords": ["home", "parent", "people", "human", "child"] },
-	{ "category": "people", "char": "👩‍👧‍👦", "name": "family_woman_girl_boy", "keywords": ["home", "parent", "people", "human", "children"] },
-	{ "category": "people", "char": "👩‍👦‍👦", "name": "family_woman_boy_boy", "keywords": ["home", "parent", "people", "human", "children"] },
-	{ "category": "people", "char": "👩‍👧‍👧", "name": "family_woman_girl_girl", "keywords": ["home", "parent", "people", "human", "children"] },
-	{ "category": "people", "char": "👨‍👦", "name": "family_man_boy", "keywords": ["home", "parent", "people", "human", "child"] },
-	{ "category": "people", "char": "👨‍👧", "name": "family_man_girl", "keywords": ["home", "parent", "people", "human", "child"] },
-	{ "category": "people", "char": "👨‍👧‍👦", "name": "family_man_girl_boy", "keywords": ["home", "parent", "people", "human", "children"] },
-	{ "category": "people", "char": "👨‍👦‍👦", "name": "family_man_boy_boy", "keywords": ["home", "parent", "people", "human", "children"] },
-	{ "category": "people", "char": "👨‍👧‍👧", "name": "family_man_girl_girl", "keywords": ["home", "parent", "people", "human", "children"] },
-	{ "category": "people", "char": "🧶", "name": "yarn", "keywords": ["ball", "crochet", "knit"] },
-	{ "category": "people", "char": "🧵", "name": "thread", "keywords": ["needle", "sewing", "spool", "string"] },
-	{ "category": "people", "char": "🧥", "name": "coat", "keywords": ["jacket"] },
-	{ "category": "people", "char": "🥼", "name": "labcoat", "keywords": ["doctor", "experiment", "scientist", "chemist"] },
-	{ "category": "people", "char": "👚", "name": "womans_clothes", "keywords": ["fashion", "shopping_bags", "female"] },
-	{ "category": "people", "char": "👕", "name": "tshirt", "keywords": ["fashion", "cloth", "casual", "shirt", "tee"] },
-	{ "category": "people", "char": "👖", "name": "jeans", "keywords": ["fashion", "shopping"] },
-	{ "category": "people", "char": "👔", "name": "necktie", "keywords": ["shirt", "suitup", "formal", "fashion", "cloth", "business"] },
-	{ "category": "people", "char": "👗", "name": "dress", "keywords": ["clothes", "fashion", "shopping"] },
-	{ "category": "people", "char": "👙", "name": "bikini", "keywords": ["swimming", "female", "woman", "girl", "fashion", "beach", "summer"] },
-	{ "category": "people", "char": "🩱", "name": "one_piece_swimsuit", "keywords": ["swimming", "female", "woman", "girl", "fashion", "beach", "summer"] },
-	{ "category": "people", "char": "👘", "name": "kimono", "keywords": ["dress", "fashion", "women", "female", "japanese"] },
-	{ "category": "people", "char": "🥻", "name": "sari", "keywords": ["dress", "fashion", "women", "female"] },
-	{ "category": "people", "char": "🩲", "name": "briefs", "keywords": ["dress", "fashion"] },
-	{ "category": "people", "char": "🩳", "name": "shorts", "keywords": ["dress", "fashion"] },
-	{ "category": "people", "char": "💄", "name": "lipstick", "keywords": ["female", "girl", "fashion", "woman"] },
-	{ "category": "people", "char": "💋", "name": "kiss", "keywords": ["face", "lips", "love", "like", "affection", "valentines"] },
-	{ "category": "people", "char": "👣", "name": "footprints", "keywords": ["feet", "tracking", "walking", "beach"] },
-	{ "category": "people", "char": "🥿", "name": "flat_shoe", "keywords": ["ballet", "slip-on", "slipper"] },
-	{ "category": "people", "char": "👠", "name": "high_heel", "keywords": ["fashion", "shoes", "female", "pumps", "stiletto"] },
-	{ "category": "people", "char": "👡", "name": "sandal", "keywords": ["shoes", "fashion", "flip flops"] },
-	{ "category": "people", "char": "👢", "name": "boot", "keywords": ["shoes", "fashion"] },
-	{ "category": "people", "char": "👞", "name": "mans_shoe", "keywords": ["fashion", "male"] },
-	{ "category": "people", "char": "👟", "name": "athletic_shoe", "keywords": ["shoes", "sports", "sneakers"] },
-	{ "category": "people", "char": "🩴", "name": "thong_sandal", "keywords": [] },
-	{ "category": "people", "char": "🩰", "name": "ballet_shoes", "keywords": ["shoes", "sports"] },
-	{ "category": "people", "char": "🧦", "name": "socks", "keywords": ["stockings", "clothes"] },
-	{ "category": "people", "char": "🧤", "name": "gloves", "keywords": ["hands", "winter", "clothes"] },
-	{ "category": "people", "char": "🧣", "name": "scarf", "keywords": ["neck", "winter", "clothes"] },
-	{ "category": "people", "char": "👒", "name": "womans_hat", "keywords": ["fashion", "accessories", "female", "lady", "spring"] },
-	{ "category": "people", "char": "🎩", "name": "tophat", "keywords": ["magic", "gentleman", "classy", "circus"] },
-	{ "category": "people", "char": "🧢", "name": "billed_hat", "keywords": ["cap", "baseball"] },
-	{ "category": "people", "char": "⛑", "name": "rescue_worker_helmet", "keywords": ["construction", "build"] },
-	{ "category": "people", "char": "🪖", "name": "military_helmet", "keywords": [] },
-	{ "category": "people", "char": "🎓", "name": "mortar_board", "keywords": ["school", "college", "degree", "university", "graduation", "cap", "hat", "legal", "learn", "education"] },
-	{ "category": "people", "char": "👑", "name": "crown", "keywords": ["king", "kod", "leader", "royalty", "lord"] },
-	{ "category": "people", "char": "🎒", "name": "school_satchel", "keywords": ["student", "education", "bag", "backpack"] },
-	{ "category": "people", "char": "🧳", "name": "luggage", "keywords": ["packing", "travel"] },
-	{ "category": "people", "char": "👝", "name": "pouch", "keywords": ["bag", "accessories", "shopping"] },
-	{ "category": "people", "char": "👛", "name": "purse", "keywords": ["fashion", "accessories", "money", "sales", "shopping"] },
-	{ "category": "people", "char": "👜", "name": "handbag", "keywords": ["fashion", "accessory", "accessories", "shopping"] },
-	{ "category": "people", "char": "💼", "name": "briefcase", "keywords": ["business", "documents", "work", "law", "legal", "job", "career"] },
-	{ "category": "people", "char": "👓", "name": "eyeglasses", "keywords": ["fashion", "accessories", "eyesight", "nerdy", "dork", "geek"] },
-	{ "category": "people", "char": "🕶", "name": "dark_sunglasses", "keywords": ["face", "cool", "accessories"] },
-	{ "category": "people", "char": "🥽", "name": "goggles", "keywords": ["eyes", "protection", "safety"] },
-	{ "category": "people", "char": "💍", "name": "ring", "keywords": ["wedding", "propose", "marriage", "valentines", "diamond", "fashion", "jewelry", "gem", "engagement"] },
-	{ "category": "people", "char": "🌂", "name": "closed_umbrella", "keywords": ["weather", "rain", "drizzle"] },
-	{ "category": "animals_and_nature", "char": "🐶", "name": "dog", "keywords": ["animal", "friend", "nature", "woof", "puppy", "pet", "faithful"] },
-	{ "category": "animals_and_nature", "char": "🐱", "name": "cat", "keywords": ["animal", "meow", "nature", "pet", "kitten"] },
-	{ "category": "animals_and_nature", "char": "🐈‍⬛", "name": "black_cat", "keywords": ["animal", "meow", "nature", "pet", "kitten"] },
-	{ "category": "animals_and_nature", "char": "🐭", "name": "mouse", "keywords": ["animal", "nature", "cheese_wedge", "rodent"] },
-	{ "category": "animals_and_nature", "char": "🐹", "name": "hamster", "keywords": ["animal", "nature"] },
-	{ "category": "animals_and_nature", "char": "🐰", "name": "rabbit", "keywords": ["animal", "nature", "pet", "spring", "magic", "bunny"] },
-	{ "category": "animals_and_nature", "char": "🦊", "name": "fox_face", "keywords": ["animal", "nature", "face"] },
-	{ "category": "animals_and_nature", "char": "🐻", "name": "bear", "keywords": ["animal", "nature", "wild"] },
-	{ "category": "animals_and_nature", "char": "🐼", "name": "panda_face", "keywords": ["animal", "nature", "panda"] },
-	{ "category": "animals_and_nature", "char": "🐨", "name": "koala", "keywords": ["animal", "nature"] },
-	{ "category": "animals_and_nature", "char": "🐯", "name": "tiger", "keywords": ["animal", "cat", "danger", "wild", "nature", "roar"] },
-	{ "category": "animals_and_nature", "char": "🦁", "name": "lion", "keywords": ["animal", "nature"] },
-	{ "category": "animals_and_nature", "char": "🐮", "name": "cow", "keywords": ["beef", "ox", "animal", "nature", "moo", "milk"] },
-	{ "category": "animals_and_nature", "char": "🐷", "name": "pig", "keywords": ["animal", "oink", "nature"] },
-	{ "category": "animals_and_nature", "char": "🐽", "name": "pig_nose", "keywords": ["animal", "oink"] },
-	{ "category": "animals_and_nature", "char": "🐸", "name": "frog", "keywords": ["animal", "nature", "croak", "toad"] },
-	{ "category": "animals_and_nature", "char": "🦑", "name": "squid", "keywords": ["animal", "nature", "ocean", "sea"] },
-	{ "category": "animals_and_nature", "char": "🐙", "name": "octopus", "keywords": ["animal", "creature", "ocean", "sea", "nature", "beach"] },
-	{ "category": "animals_and_nature", "char": "🦐", "name": "shrimp", "keywords": ["animal", "ocean", "nature", "seafood"] },
-	{ "category": "animals_and_nature", "char": "🐵", "name": "monkey_face", "keywords": ["animal", "nature", "circus"] },
-	{ "category": "animals_and_nature", "char": "🦍", "name": "gorilla", "keywords": ["animal", "nature", "circus"] },
-	{ "category": "animals_and_nature", "char": "🙈", "name": "see_no_evil", "keywords": ["monkey", "animal", "nature", "haha"] },
-	{ "category": "animals_and_nature", "char": "🙉", "name": "hear_no_evil", "keywords": ["animal", "monkey", "nature"] },
-	{ "category": "animals_and_nature", "char": "🙊", "name": "speak_no_evil", "keywords": ["monkey", "animal", "nature", "omg"] },
-	{ "category": "animals_and_nature", "char": "🐒", "name": "monkey", "keywords": ["animal", "nature", "banana", "circus"] },
-	{ "category": "animals_and_nature", "char": "🐔", "name": "chicken", "keywords": ["animal", "cluck", "nature", "bird"] },
-	{ "category": "animals_and_nature", "char": "🐧", "name": "penguin", "keywords": ["animal", "nature"] },
-	{ "category": "animals_and_nature", "char": "🐦", "name": "bird", "keywords": ["animal", "nature", "fly", "tweet", "spring"] },
-	{ "category": "animals_and_nature", "char": "🐤", "name": "baby_chick", "keywords": ["animal", "chicken", "bird"] },
-	{ "category": "animals_and_nature", "char": "🐣", "name": "hatching_chick", "keywords": ["animal", "chicken", "egg", "born", "baby", "bird"] },
-	{ "category": "animals_and_nature", "char": "🐥", "name": "hatched_chick", "keywords": ["animal", "chicken", "baby", "bird"] },
-	{ "category": "animals_and_nature", "char": "🦆", "name": "duck", "keywords": ["animal", "nature", "bird", "mallard"] },
-	{ "category": "animals_and_nature", "char": "🦅", "name": "eagle", "keywords": ["animal", "nature", "bird"] },
-	{ "category": "animals_and_nature", "char": "🦉", "name": "owl", "keywords": ["animal", "nature", "bird", "hoot"] },
-	{ "category": "animals_and_nature", "char": "🦇", "name": "bat", "keywords": ["animal", "nature", "blind", "vampire"] },
-	{ "category": "animals_and_nature", "char": "🐺", "name": "wolf", "keywords": ["animal", "nature", "wild"] },
-	{ "category": "animals_and_nature", "char": "🐗", "name": "boar", "keywords": ["animal", "nature"] },
-	{ "category": "animals_and_nature", "char": "🐴", "name": "horse", "keywords": ["animal", "brown", "nature"] },
-	{ "category": "animals_and_nature", "char": "🦄", "name": "unicorn", "keywords": ["animal", "nature", "mystical"] },
-	{ "category": "animals_and_nature", "char": "🐝", "name": "honeybee", "keywords": ["animal", "insect", "nature", "bug", "spring", "honey"] },
-	{ "category": "animals_and_nature", "char": "🐛", "name": "bug", "keywords": ["animal", "insect", "nature", "worm"] },
-	{ "category": "animals_and_nature", "char": "🦋", "name": "butterfly", "keywords": ["animal", "insect", "nature", "caterpillar"] },
-	{ "category": "animals_and_nature", "char": "🐌", "name": "snail", "keywords": ["slow", "animal", "shell"] },
-	{ "category": "animals_and_nature", "char": "🐞", "name": "lady_beetle", "keywords": ["animal", "insect", "nature", "ladybug"] },
-	{ "category": "animals_and_nature", "char": "🐜", "name": "ant", "keywords": ["animal", "insect", "nature", "bug"] },
-	{ "category": "animals_and_nature", "char": "🦗", "name": "grasshopper", "keywords": ["animal", "cricket", "chirp"] },
-	{ "category": "animals_and_nature", "char": "🕷", "name": "spider", "keywords": ["animal", "arachnid"] },
-	{ "category": "animals_and_nature", "char": "🪲", "name": "beetle", "keywords": ["animal"] },
-	{ "category": "animals_and_nature", "char": "🪳", "name": "cockroach", "keywords": ["animal"] },
-	{ "category": "animals_and_nature", "char": "🪰", "name": "fly", "keywords": ["animal"] },
-	{ "category": "animals_and_nature", "char": "🪱", "name": "worm", "keywords": ["animal"] },
-	{ "category": "animals_and_nature", "char": "🦂", "name": "scorpion", "keywords": ["animal", "arachnid"] },
-	{ "category": "animals_and_nature", "char": "🦀", "name": "crab", "keywords": ["animal", "crustacean"] },
-	{ "category": "animals_and_nature", "char": "🐍", "name": "snake", "keywords": ["animal", "evil", "nature", "hiss", "python"] },
-	{ "category": "animals_and_nature", "char": "🦎", "name": "lizard", "keywords": ["animal", "nature", "reptile"] },
-	{ "category": "animals_and_nature", "char": "🦖", "name": "t-rex", "keywords": ["animal", "nature", "dinosaur", "tyrannosaurus", "extinct"] },
-	{ "category": "animals_and_nature", "char": "🦕", "name": "sauropod", "keywords": ["animal", "nature", "dinosaur", "brachiosaurus", "brontosaurus", "diplodocus", "extinct"] },
-	{ "category": "animals_and_nature", "char": "🐢", "name": "turtle", "keywords": ["animal", "slow", "nature", "tortoise"] },
-	{ "category": "animals_and_nature", "char": "🐠", "name": "tropical_fish", "keywords": ["animal", "swim", "ocean", "beach", "nemo"] },
-	{ "category": "animals_and_nature", "char": "🐟", "name": "fish", "keywords": ["animal", "food", "nature"] },
-	{ "category": "animals_and_nature", "char": "🐡", "name": "blowfish", "keywords": ["animal", "nature", "food", "sea", "ocean"] },
-	{ "category": "animals_and_nature", "char": "🐬", "name": "dolphin", "keywords": ["animal", "nature", "fish", "sea", "ocean", "flipper", "fins", "beach"] },
-	{ "category": "animals_and_nature", "char": "🦈", "name": "shark", "keywords": ["animal", "nature", "fish", "sea", "ocean", "jaws", "fins", "beach"] },
-	{ "category": "animals_and_nature", "char": "🐳", "name": "whale", "keywords": ["animal", "nature", "sea", "ocean"] },
-	{ "category": "animals_and_nature", "char": "🐋", "name": "whale2", "keywords": ["animal", "nature", "sea", "ocean"] },
-	{ "category": "animals_and_nature", "char": "🐊", "name": "crocodile", "keywords": ["animal", "nature", "reptile", "lizard", "alligator"] },
-	{ "category": "animals_and_nature", "char": "🐆", "name": "leopard", "keywords": ["animal", "nature"] },
-	{ "category": "animals_and_nature", "char": "🦓", "name": "zebra", "keywords": ["animal", "nature", "stripes", "safari"] },
-	{ "category": "animals_and_nature", "char": "🐅", "name": "tiger2", "keywords": ["animal", "nature", "roar"] },
-	{ "category": "animals_and_nature", "char": "🐃", "name": "water_buffalo", "keywords": ["animal", "nature", "ox", "cow"] },
-	{ "category": "animals_and_nature", "char": "🐂", "name": "ox", "keywords": ["animal", "cow", "beef"] },
-	{ "category": "animals_and_nature", "char": "🐄", "name": "cow2", "keywords": ["beef", "ox", "animal", "nature", "moo", "milk"] },
-	{ "category": "animals_and_nature", "char": "🦌", "name": "deer", "keywords": ["animal", "nature", "horns", "venison"] },
-	{ "category": "animals_and_nature", "char": "🐪", "name": "dromedary_camel", "keywords": ["animal", "hot", "desert", "hump"] },
-	{ "category": "animals_and_nature", "char": "🐫", "name": "camel", "keywords": ["animal", "nature", "hot", "desert", "hump"] },
-	{ "category": "animals_and_nature", "char": "🦒", "name": "giraffe", "keywords": ["animal", "nature", "spots", "safari"] },
-	{ "category": "animals_and_nature", "char": "🐘", "name": "elephant", "keywords": ["animal", "nature", "nose", "th", "circus"] },
-	{ "category": "animals_and_nature", "char": "🦏", "name": "rhinoceros", "keywords": ["animal", "nature", "horn"] },
-	{ "category": "animals_and_nature", "char": "🐐", "name": "goat", "keywords": ["animal", "nature"] },
-	{ "category": "animals_and_nature", "char": "🐏", "name": "ram", "keywords": ["animal", "sheep", "nature"] },
-	{ "category": "animals_and_nature", "char": "🐑", "name": "sheep", "keywords": ["animal", "nature", "wool", "shipit"] },
-	{ "category": "animals_and_nature", "char": "🐎", "name": "racehorse", "keywords": ["animal", "gamble", "luck"] },
-	{ "category": "animals_and_nature", "char": "🐖", "name": "pig2", "keywords": ["animal", "nature"] },
-	{ "category": "animals_and_nature", "char": "🐀", "name": "rat", "keywords": ["animal", "mouse", "rodent"] },
-	{ "category": "animals_and_nature", "char": "🐁", "name": "mouse2", "keywords": ["animal", "nature", "rodent"] },
-	{ "category": "animals_and_nature", "char": "🐓", "name": "rooster", "keywords": ["animal", "nature", "chicken"] },
-	{ "category": "animals_and_nature", "char": "🦃", "name": "turkey", "keywords": ["animal", "bird"] },
-	{ "category": "animals_and_nature", "char": "🕊", "name": "dove", "keywords": ["animal", "bird"] },
-	{ "category": "animals_and_nature", "char": "🐕", "name": "dog2", "keywords": ["animal", "nature", "friend", "doge", "pet", "faithful"] },
-	{ "category": "animals_and_nature", "char": "🐩", "name": "poodle", "keywords": ["dog", "animal", "101", "nature", "pet"] },
-	{ "category": "animals_and_nature", "char": "🐈", "name": "cat2", "keywords": ["animal", "meow", "pet", "cats"] },
-	{ "category": "animals_and_nature", "char": "🐇", "name": "rabbit2", "keywords": ["animal", "nature", "pet", "magic", "spring"] },
-	{ "category": "animals_and_nature", "char": "🐿", "name": "chipmunk", "keywords": ["animal", "nature", "rodent", "squirrel"] },
-	{ "category": "animals_and_nature", "char": "🦔", "name": "hedgehog", "keywords": ["animal", "nature", "spiny"] },
-	{ "category": "animals_and_nature", "char": "🦝", "name": "raccoon", "keywords": ["animal", "nature"] },
-	{ "category": "animals_and_nature", "char": "🦙", "name": "llama", "keywords": ["animal", "nature", "alpaca"] },
-	{ "category": "animals_and_nature", "char": "🦛", "name": "hippopotamus", "keywords": ["animal", "nature"] },
-	{ "category": "animals_and_nature", "char": "🦘", "name": "kangaroo", "keywords": ["animal", "nature", "australia", "joey", "hop", "marsupial"] },
-	{ "category": "animals_and_nature", "char": "🦡", "name": "badger", "keywords": ["animal", "nature", "honey"] },
-	{ "category": "animals_and_nature", "char": "🦢", "name": "swan", "keywords": ["animal", "nature", "bird"] },
-	{ "category": "animals_and_nature", "char": "🦚", "name": "peacock", "keywords": ["animal", "nature", "peahen", "bird"] },
-	{ "category": "animals_and_nature", "char": "🦜", "name": "parrot", "keywords": ["animal", "nature", "bird", "pirate", "talk"] },
-	{ "category": "animals_and_nature", "char": "🦞", "name": "lobster", "keywords": ["animal", "nature", "bisque", "claws", "seafood"] },
-	{ "category": "animals_and_nature", "char": "🦠", "name": "microbe", "keywords": ["amoeba", "bacteria", "germs"] },
-	{ "category": "animals_and_nature", "char": "🦟", "name": "mosquito", "keywords": ["animal", "nature", "insect", "malaria"] },
-	{ "category": "animals_and_nature", "char": "🦬", "name": "bison", "keywords": ["animal", "nature"] },
-	{ "category": "animals_and_nature", "char": "🦣", "name": "mammoth", "keywords": ["animal", "nature"] },
-	{ "category": "animals_and_nature", "char": "🦫", "name": "beaver", "keywords": ["animal", "nature"] },
-	{ "category": "animals_and_nature", "char": "🐻‍❄️", "name": "polar_bear", "keywords": ["animal", "nature"] },
-	{ "category": "animals_and_nature", "char": "🦤", "name": "dodo", "keywords": ["animal", "nature"] },
-	{ "category": "animals_and_nature", "char": "🪶", "name": "feather", "keywords": ["animal", "nature"] },
-	{ "category": "animals_and_nature", "char": "🦭", "name": "seal", "keywords": ["animal", "nature"] },
-	{ "category": "animals_and_nature", "char": "🐾", "name": "paw_prints", "keywords": ["animal", "tracking", "footprints", "dog", "cat", "pet", "feet"] },
-	{ "category": "animals_and_nature", "char": "🐉", "name": "dragon", "keywords": ["animal", "myth", "nature", "chinese", "green"] },
-	{ "category": "animals_and_nature", "char": "🐲", "name": "dragon_face", "keywords": ["animal", "myth", "nature", "chinese", "green"] },
-	{ "category": "animals_and_nature", "char": "🦧", "name": "orangutan", "keywords": ["animal", "nature"] },
-	{ "category": "animals_and_nature", "char": "🦮", "name": "guide_dog", "keywords": ["animal", "nature"] },
-	{ "category": "animals_and_nature", "char": "🐕‍🦺", "name": "service_dog", "keywords": ["animal", "nature"] },
-	{ "category": "animals_and_nature", "char": "🦥", "name": "sloth", "keywords": ["animal", "nature"] },
-	{ "category": "animals_and_nature", "char": "🦦", "name": "otter", "keywords": ["animal", "nature"] },
-	{ "category": "animals_and_nature", "char": "🦨", "name": "skunk", "keywords": ["animal", "nature"] },
-	{ "category": "animals_and_nature", "char": "🦩", "name": "flamingo", "keywords": ["animal", "nature"] },
-	{ "category": "animals_and_nature", "char": "🌵", "name": "cactus", "keywords": ["vegetable", "plant", "nature"] },
-	{ "category": "animals_and_nature", "char": "🎄", "name": "christmas_tree", "keywords": ["festival", "vacation", "december", "xmas", "celebration"] },
-	{ "category": "animals_and_nature", "char": "🌲", "name": "evergreen_tree", "keywords": ["plant", "nature"] },
-	{ "category": "animals_and_nature", "char": "🌳", "name": "deciduous_tree", "keywords": ["plant", "nature"] },
-	{ "category": "animals_and_nature", "char": "🌴", "name": "palm_tree", "keywords": ["plant", "vegetable", "nature", "summer", "beach", "mojito", "tropical"] },
-	{ "category": "animals_and_nature", "char": "🌱", "name": "seedling", "keywords": ["plant", "nature", "grass", "lawn", "spring"] },
-	{ "category": "animals_and_nature", "char": "🌿", "name": "herb", "keywords": ["vegetable", "plant", "medicine", "weed", "grass", "lawn"] },
-	{ "category": "animals_and_nature", "char": "☘", "name": "shamrock", "keywords": ["vegetable", "plant", "nature", "irish", "clover"] },
-	{ "category": "animals_and_nature", "char": "🍀", "name": "four_leaf_clover", "keywords": ["vegetable", "plant", "nature", "lucky", "irish"] },
-	{ "category": "animals_and_nature", "char": "🎍", "name": "bamboo", "keywords": ["plant", "nature", "vegetable", "panda", "pine_decoration"] },
-	{ "category": "animals_and_nature", "char": "🎋", "name": "tanabata_tree", "keywords": ["plant", "nature", "branch", "summer"] },
-	{ "category": "animals_and_nature", "char": "🍃", "name": "leaves", "keywords": ["nature", "plant", "tree", "vegetable", "grass", "lawn", "spring"] },
-	{ "category": "animals_and_nature", "char": "🍂", "name": "fallen_leaf", "keywords": ["nature", "plant", "vegetable", "leaves"] },
-	{ "category": "animals_and_nature", "char": "🍁", "name": "maple_leaf", "keywords": ["nature", "plant", "vegetable", "ca", "fall"] },
-	{ "category": "animals_and_nature", "char": "🌾", "name": "ear_of_rice", "keywords": ["nature", "plant"] },
-	{ "category": "animals_and_nature", "char": "🌺", "name": "hibiscus", "keywords": ["plant", "vegetable", "flowers", "beach"] },
-	{ "category": "animals_and_nature", "char": "🌻", "name": "sunflower", "keywords": ["nature", "plant", "fall"] },
-	{ "category": "animals_and_nature", "char": "🌹", "name": "rose", "keywords": ["flowers", "valentines", "love", "spring"] },
-	{ "category": "animals_and_nature", "char": "🥀", "name": "wilted_flower", "keywords": ["plant", "nature", "flower"] },
-	{ "category": "animals_and_nature", "char": "🌷", "name": "tulip", "keywords": ["flowers", "plant", "nature", "summer", "spring"] },
-	{ "category": "animals_and_nature", "char": "🌼", "name": "blossom", "keywords": ["nature", "flowers", "yellow"] },
-	{ "category": "animals_and_nature", "char": "🌸", "name": "cherry_blossom", "keywords": ["nature", "plant", "spring", "flower"] },
-	{ "category": "animals_and_nature", "char": "💐", "name": "bouquet", "keywords": ["flowers", "nature", "spring"] },
-	{ "category": "animals_and_nature", "char": "🍄", "name": "mushroom", "keywords": ["plant", "vegetable"] },
-	{ "category": "animals_and_nature", "char": "🪴", "name": "potted_plant", "keywords": ["plant"] },
-	{ "category": "animals_and_nature", "char": "🌰", "name": "chestnut", "keywords": ["food", "squirrel"] },
-	{ "category": "animals_and_nature", "char": "🎃", "name": "jack_o_lantern", "keywords": ["halloween", "light", "pumpkin", "creepy", "fall"] },
-	{ "category": "animals_and_nature", "char": "🐚", "name": "shell", "keywords": ["nature", "sea", "beach"] },
-	{ "category": "animals_and_nature", "char": "🕸", "name": "spider_web", "keywords": ["animal", "insect", "arachnid", "silk"] },
-	{ "category": "animals_and_nature", "char": "🌎", "name": "earth_americas", "keywords": ["globe", "world", "USA", "international"] },
-	{ "category": "animals_and_nature", "char": "🌍", "name": "earth_africa", "keywords": ["globe", "world", "international"] },
-	{ "category": "animals_and_nature", "char": "🌏", "name": "earth_asia", "keywords": ["globe", "world", "east", "international"] },
-	{ "category": "animals_and_nature", "char": "🪐", "name": "ringed_planet", "keywords": ["saturn"] },
-	{ "category": "animals_and_nature", "char": "🌕", "name": "full_moon", "keywords": ["nature", "yellow", "twilight", "planet", "space", "night", "evening", "sleep"] },
-	{ "category": "animals_and_nature", "char": "🌖", "name": "waning_gibbous_moon", "keywords": ["nature", "twilight", "planet", "space", "night", "evening", "sleep", "waxing_gibbous_moon"] },
-	{ "category": "animals_and_nature", "char": "🌗", "name": "last_quarter_moon", "keywords": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"] },
-	{ "category": "animals_and_nature", "char": "🌘", "name": "waning_crescent_moon", "keywords": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"] },
-	{ "category": "animals_and_nature", "char": "🌑", "name": "new_moon", "keywords": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"] },
-	{ "category": "animals_and_nature", "char": "🌒", "name": "waxing_crescent_moon", "keywords": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"] },
-	{ "category": "animals_and_nature", "char": "🌓", "name": "first_quarter_moon", "keywords": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"] },
-	{ "category": "animals_and_nature", "char": "🌔", "name": "waxing_gibbous_moon", "keywords": ["nature", "night", "sky", "gray", "twilight", "planet", "space", "evening", "sleep"] },
-	{ "category": "animals_and_nature", "char": "🌚", "name": "new_moon_with_face", "keywords": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"] },
-	{ "category": "animals_and_nature", "char": "🌝", "name": "full_moon_with_face", "keywords": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"] },
-	{ "category": "animals_and_nature", "char": "🌛", "name": "first_quarter_moon_with_face", "keywords": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"] },
-	{ "category": "animals_and_nature", "char": "🌜", "name": "last_quarter_moon_with_face", "keywords": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"] },
-	{ "category": "animals_and_nature", "char": "🌞", "name": "sun_with_face", "keywords": ["nature", "morning", "sky"] },
-	{ "category": "animals_and_nature", "char": "🌙", "name": "crescent_moon", "keywords": ["night", "sleep", "sky", "evening", "magic"] },
-	{ "category": "animals_and_nature", "char": "⭐", "name": "star", "keywords": ["night", "yellow"] },
-	{ "category": "animals_and_nature", "char": "🌟", "name": "star2", "keywords": ["night", "sparkle", "awesome", "good", "magic"] },
-	{ "category": "animals_and_nature", "char": "💫", "name": "dizzy", "keywords": ["star", "sparkle", "shoot", "magic"] },
-	{ "category": "animals_and_nature", "char": "✨", "name": "sparkles", "keywords": ["stars", "shine", "shiny", "cool", "awesome", "good", "magic"] },
-	{ "category": "animals_and_nature", "char": "☄", "name": "comet", "keywords": ["space"] },
-	{ "category": "animals_and_nature", "char": "☀️", "name": "sunny", "keywords": ["weather", "nature", "brightness", "summer", "beach", "spring"] },
-	{ "category": "animals_and_nature", "char": "🌤", "name": "sun_behind_small_cloud", "keywords": ["weather"] },
-	{ "category": "animals_and_nature", "char": "⛅", "name": "partly_sunny", "keywords": ["weather", "nature", "cloudy", "morning", "fall", "spring"] },
-	{ "category": "animals_and_nature", "char": "🌥", "name": "sun_behind_large_cloud", "keywords": ["weather"] },
-	{ "category": "animals_and_nature", "char": "🌦", "name": "sun_behind_rain_cloud", "keywords": ["weather"] },
-	{ "category": "animals_and_nature", "char": "☁️", "name": "cloud", "keywords": ["weather", "sky"] },
-	{ "category": "animals_and_nature", "char": "🌧", "name": "cloud_with_rain", "keywords": ["weather"] },
-	{ "category": "animals_and_nature", "char": "⛈", "name": "cloud_with_lightning_and_rain", "keywords": ["weather", "lightning"] },
-	{ "category": "animals_and_nature", "char": "🌩", "name": "cloud_with_lightning", "keywords": ["weather", "thunder"] },
-	{ "category": "animals_and_nature", "char": "⚡", "name": "zap", "keywords": ["thunder", "weather", "lightning bolt", "fast"] },
-	{ "category": "animals_and_nature", "char": "🔥", "name": "fire", "keywords": ["hot", "cook", "flame"] },
-	{ "category": "animals_and_nature", "char": "💥", "name": "boom", "keywords": ["bomb", "explode", "explosion", "collision", "blown"] },
-	{ "category": "animals_and_nature", "char": "❄️", "name": "snowflake", "keywords": ["winter", "season", "cold", "weather", "christmas", "xmas"] },
-	{ "category": "animals_and_nature", "char": "🌨", "name": "cloud_with_snow", "keywords": ["weather"] },
-	{ "category": "animals_and_nature", "char": "⛄", "name": "snowman", "keywords": ["winter", "season", "cold", "weather", "christmas", "xmas", "frozen", "without_snow"] },
-	{ "category": "animals_and_nature", "char": "☃", "name": "snowman_with_snow", "keywords": ["winter", "season", "cold", "weather", "christmas", "xmas", "frozen"] },
-	{ "category": "animals_and_nature", "char": "🌬", "name": "wind_face", "keywords": ["gust", "air"] },
-	{ "category": "animals_and_nature", "char": "💨", "name": "dash", "keywords": ["wind", "air", "fast", "shoo", "fart", "smoke", "puff"] },
-	{ "category": "animals_and_nature", "char": "🌪", "name": "tornado", "keywords": ["weather", "cyclone", "twister"] },
-	{ "category": "animals_and_nature", "char": "🌫", "name": "fog", "keywords": ["weather"] },
-	{ "category": "animals_and_nature", "char": "☂", "name": "open_umbrella", "keywords": ["weather", "spring"] },
-	{ "category": "animals_and_nature", "char": "☔", "name": "umbrella", "keywords": ["rainy", "weather", "spring"] },
-	{ "category": "animals_and_nature", "char": "💧", "name": "droplet", "keywords": ["water", "drip", "faucet", "spring"] },
-	{ "category": "animals_and_nature", "char": "💦", "name": "sweat_drops", "keywords": ["water", "drip", "oops"] },
-	{ "category": "animals_and_nature", "char": "🌊", "name": "ocean", "keywords": ["sea", "water", "wave", "nature", "tsunami", "disaster"] },
-	{ "category": "animals_and_nature", "char": "\uD83E\uDEB7", "name": "lotus", "keywords": [] },
-	{ "category": "animals_and_nature", "char": "\uD83E\uDEB8", "name": "coral", "keywords": [] },
-	{ "category": "animals_and_nature", "char": "\uD83E\uDEB9", "name": "empty_nest", "keywords": [] },
-	{ "category": "animals_and_nature", "char": "\uD83E\uDEBA", "name": "nest_with_eggs", "keywords": [] },
-	{ "category": "food_and_drink", "char": "🍏", "name": "green_apple", "keywords": ["fruit", "nature"] },
-	{ "category": "food_and_drink", "char": "🍎", "name": "apple", "keywords": ["fruit", "mac", "school"] },
-	{ "category": "food_and_drink", "char": "🍐", "name": "pear", "keywords": ["fruit", "nature", "food"] },
-	{ "category": "food_and_drink", "char": "🍊", "name": "tangerine", "keywords": ["food", "fruit", "nature", "orange"] },
-	{ "category": "food_and_drink", "char": "🍋", "name": "lemon", "keywords": ["fruit", "nature"] },
-	{ "category": "food_and_drink", "char": "🍌", "name": "banana", "keywords": ["fruit", "food", "monkey"] },
-	{ "category": "food_and_drink", "char": "🍉", "name": "watermelon", "keywords": ["fruit", "food", "picnic", "summer"] },
-	{ "category": "food_and_drink", "char": "🍇", "name": "grapes", "keywords": ["fruit", "food", "wine"] },
-	{ "category": "food_and_drink", "char": "🍓", "name": "strawberry", "keywords": ["fruit", "food", "nature"] },
-	{ "category": "food_and_drink", "char": "🍈", "name": "melon", "keywords": ["fruit", "nature", "food"] },
-	{ "category": "food_and_drink", "char": "🍒", "name": "cherries", "keywords": ["food", "fruit"] },
-	{ "category": "food_and_drink", "char": "🍑", "name": "peach", "keywords": ["fruit", "nature", "food"] },
-	{ "category": "food_and_drink", "char": "🍍", "name": "pineapple", "keywords": ["fruit", "nature", "food"] },
-	{ "category": "food_and_drink", "char": "🥥", "name": "coconut", "keywords": ["fruit", "nature", "food", "palm"] },
-	{ "category": "food_and_drink", "char": "🥝", "name": "kiwi_fruit", "keywords": ["fruit", "food"] },
-	{ "category": "food_and_drink", "char": "🥭", "name": "mango", "keywords": ["fruit", "food", "tropical"] },
-	{ "category": "food_and_drink", "char": "🥑", "name": "avocado", "keywords": ["fruit", "food"] },
-	{ "category": "food_and_drink", "char": "🥦", "name": "broccoli", "keywords": ["fruit", "food", "vegetable"] },
-	{ "category": "food_and_drink", "char": "🍅", "name": "tomato", "keywords": ["fruit", "vegetable", "nature", "food"] },
-	{ "category": "food_and_drink", "char": "🍆", "name": "eggplant", "keywords": ["vegetable", "nature", "food", "aubergine"] },
-	{ "category": "food_and_drink", "char": "🥒", "name": "cucumber", "keywords": ["fruit", "food", "pickle"] },
-	{ "category": "food_and_drink", "char": "🫐", "name": "blueberries", "keywords": ["fruit", "food"] },
-	{ "category": "food_and_drink", "char": "🫒", "name": "olive", "keywords": ["fruit", "food"] },
-	{ "category": "food_and_drink", "char": "🫑", "name": "bell_pepper", "keywords": ["fruit", "food"] },
-	{ "category": "food_and_drink", "char": "🥕", "name": "carrot", "keywords": ["vegetable", "food", "orange"] },
-	{ "category": "food_and_drink", "char": "🌶", "name": "hot_pepper", "keywords": ["food", "spicy", "chilli", "chili"] },
-	{ "category": "food_and_drink", "char": "🥔", "name": "potato", "keywords": ["food", "tuber", "vegatable", "starch"] },
-	{ "category": "food_and_drink", "char": "🌽", "name": "corn", "keywords": ["food", "vegetable", "plant"] },
-	{ "category": "food_and_drink", "char": "🥬", "name": "leafy_greens", "keywords": ["food", "vegetable", "plant", "bok choy", "cabbage", "kale", "lettuce"] },
-	{ "category": "food_and_drink", "char": "🍠", "name": "sweet_potato", "keywords": ["food", "nature"] },
-	{ "category": "food_and_drink", "char": "🥜", "name": "peanuts", "keywords": ["food", "nut"] },
-	{ "category": "food_and_drink", "char": "🧄", "name": "garlic", "keywords": ["food"] },
-	{ "category": "food_and_drink", "char": "🧅", "name": "onion", "keywords": ["food"] },
-	{ "category": "food_and_drink", "char": "🍯", "name": "honey_pot", "keywords": ["bees", "sweet", "kitchen"] },
-	{ "category": "food_and_drink", "char": "🥐", "name": "croissant", "keywords": ["food", "bread", "french"] },
-	{ "category": "food_and_drink", "char": "🍞", "name": "bread", "keywords": ["food", "wheat", "breakfast", "toast"] },
-	{ "category": "food_and_drink", "char": "🥖", "name": "baguette_bread", "keywords": ["food", "bread", "french"] },
-	{ "category": "food_and_drink", "char": "🥯", "name": "bagel", "keywords": ["food", "bread", "bakery", "schmear"] },
-	{ "category": "food_and_drink", "char": "🥨", "name": "pretzel", "keywords": ["food", "bread", "twisted"] },
-	{ "category": "food_and_drink", "char": "🧀", "name": "cheese", "keywords": ["food", "chadder"] },
-	{ "category": "food_and_drink", "char": "🥚", "name": "egg", "keywords": ["food", "chicken", "breakfast"] },
-	{ "category": "food_and_drink", "char": "🥓", "name": "bacon", "keywords": ["food", "breakfast", "pork", "pig", "meat"] },
-	{ "category": "food_and_drink", "char": "🥩", "name": "steak", "keywords": ["food", "cow", "meat", "cut", "chop", "lambchop", "porkchop"] },
-	{ "category": "food_and_drink", "char": "🥞", "name": "pancakes", "keywords": ["food", "breakfast", "flapjacks", "hotcakes"] },
-	{ "category": "food_and_drink", "char": "🍗", "name": "poultry_leg", "keywords": ["food", "meat", "drumstick", "bird", "chicken", "turkey"] },
-	{ "category": "food_and_drink", "char": "🍖", "name": "meat_on_bone", "keywords": ["good", "food", "drumstick"] },
-	{ "category": "food_and_drink", "char": "🦴", "name": "bone", "keywords": ["skeleton"] },
-	{ "category": "food_and_drink", "char": "🍤", "name": "fried_shrimp", "keywords": ["food", "animal", "appetizer", "summer"] },
-	{ "category": "food_and_drink", "char": "🍳", "name": "fried_egg", "keywords": ["food", "breakfast", "kitchen", "egg"] },
-	{ "category": "food_and_drink", "char": "🍔", "name": "hamburger", "keywords": ["meat", "fast food", "beef", "cheeseburger", "mcdonalds", "burger king"] },
-	{ "category": "food_and_drink", "char": "🍟", "name": "fries", "keywords": ["chips", "snack", "fast food"] },
-	{ "category": "food_and_drink", "char": "🥙", "name": "stuffed_flatbread", "keywords": ["food", "flatbread", "stuffed", "gyro"] },
-	{ "category": "food_and_drink", "char": "🌭", "name": "hotdog", "keywords": ["food", "frankfurter"] },
-	{ "category": "food_and_drink", "char": "🍕", "name": "pizza", "keywords": ["food", "party"] },
-	{ "category": "food_and_drink", "char": "🥪", "name": "sandwich", "keywords": ["food", "lunch", "bread"] },
-	{ "category": "food_and_drink", "char": "🥫", "name": "canned_food", "keywords": ["food", "soup"] },
-	{ "category": "food_and_drink", "char": "🍝", "name": "spaghetti", "keywords": ["food", "italian", "noodle"] },
-	{ "category": "food_and_drink", "char": "🌮", "name": "taco", "keywords": ["food", "mexican"] },
-	{ "category": "food_and_drink", "char": "🌯", "name": "burrito", "keywords": ["food", "mexican"] },
-	{ "category": "food_and_drink", "char": "🥗", "name": "green_salad", "keywords": ["food", "healthy", "lettuce"] },
-	{ "category": "food_and_drink", "char": "🥘", "name": "shallow_pan_of_food", "keywords": ["food", "cooking", "casserole", "paella"] },
-	{ "category": "food_and_drink", "char": "🍜", "name": "ramen", "keywords": ["food", "japanese", "noodle", "chopsticks"] },
-	{ "category": "food_and_drink", "char": "🍲", "name": "stew", "keywords": ["food", "meat", "soup"] },
-	{ "category": "food_and_drink", "char": "🍥", "name": "fish_cake", "keywords": ["food", "japan", "sea", "beach", "narutomaki", "pink", "swirl", "kamaboko", "surimi", "ramen"] },
-	{ "category": "food_and_drink", "char": "🥠", "name": "fortune_cookie", "keywords": ["food", "prophecy"] },
-	{ "category": "food_and_drink", "char": "🍣", "name": "sushi", "keywords": ["food", "fish", "japanese", "rice"] },
-	{ "category": "food_and_drink", "char": "🍱", "name": "bento", "keywords": ["food", "japanese", "box"] },
-	{ "category": "food_and_drink", "char": "🍛", "name": "curry", "keywords": ["food", "spicy", "hot", "indian"] },
-	{ "category": "food_and_drink", "char": "🍙", "name": "rice_ball", "keywords": ["food", "japanese"] },
-	{ "category": "food_and_drink", "char": "🍚", "name": "rice", "keywords": ["food", "china", "asian"] },
-	{ "category": "food_and_drink", "char": "🍘", "name": "rice_cracker", "keywords": ["food", "japanese"] },
-	{ "category": "food_and_drink", "char": "🍢", "name": "oden", "keywords": ["food", "japanese"] },
-	{ "category": "food_and_drink", "char": "🍡", "name": "dango", "keywords": ["food", "dessert", "sweet", "japanese", "barbecue", "meat"] },
-	{ "category": "food_and_drink", "char": "🍧", "name": "shaved_ice", "keywords": ["hot", "dessert", "summer"] },
-	{ "category": "food_and_drink", "char": "🍨", "name": "ice_cream", "keywords": ["food", "hot", "dessert"] },
-	{ "category": "food_and_drink", "char": "🍦", "name": "icecream", "keywords": ["food", "hot", "dessert", "summer"] },
-	{ "category": "food_and_drink", "char": "🥧", "name": "pie", "keywords": ["food", "dessert", "pastry"] },
-	{ "category": "food_and_drink", "char": "🍰", "name": "cake", "keywords": ["food", "dessert"] },
-	{ "category": "food_and_drink", "char": "🧁", "name": "cupcake", "keywords": ["food", "dessert", "bakery", "sweet"] },
-	{ "category": "food_and_drink", "char": "🥮", "name": "moon_cake", "keywords": ["food", "autumn"] },
-	{ "category": "food_and_drink", "char": "🎂", "name": "birthday", "keywords": ["food", "dessert", "cake"] },
-	{ "category": "food_and_drink", "char": "🍮", "name": "custard", "keywords": ["dessert", "food"] },
-	{ "category": "food_and_drink", "char": "🍬", "name": "candy", "keywords": ["snack", "dessert", "sweet", "lolly"] },
-	{ "category": "food_and_drink", "char": "🍭", "name": "lollipop", "keywords": ["food", "snack", "candy", "sweet"] },
-	{ "category": "food_and_drink", "char": "🍫", "name": "chocolate_bar", "keywords": ["food", "snack", "dessert", "sweet"] },
-	{ "category": "food_and_drink", "char": "🍿", "name": "popcorn", "keywords": ["food", "movie theater", "films", "snack"] },
-	{ "category": "food_and_drink", "char": "🥟", "name": "dumpling", "keywords": ["food", "empanada", "pierogi", "potsticker"] },
-	{ "category": "food_and_drink", "char": "🍩", "name": "doughnut", "keywords": ["food", "dessert", "snack", "sweet", "donut"] },
-	{ "category": "food_and_drink", "char": "🍪", "name": "cookie", "keywords": ["food", "snack", "oreo", "chocolate", "sweet", "dessert"] },
-	{ "category": "food_and_drink", "char": "🧇", "name": "waffle", "keywords": ["food"] },
-	{ "category": "food_and_drink", "char": "🧆", "name": "falafel", "keywords": ["food"] },
-	{ "category": "food_and_drink", "char": "🧈", "name": "butter", "keywords": ["food"] },
-	{ "category": "food_and_drink", "char": "🦪", "name": "oyster", "keywords": ["food"] },
-	{ "category": "food_and_drink", "char": "🫓", "name": "flatbread", "keywords": ["food"] },
-	{ "category": "food_and_drink", "char": "🫔", "name": "tamale", "keywords": ["food"] },
-	{ "category": "food_and_drink", "char": "🫕", "name": "fondue", "keywords": ["food"] },
-	{ "category": "food_and_drink", "char": "🥛", "name": "milk_glass", "keywords": ["beverage", "drink", "cow"] },
-	{ "category": "food_and_drink", "char": "🍺", "name": "beer", "keywords": ["relax", "beverage", "drink", "drunk", "party", "pub", "summer", "alcohol", "booze"] },
-	{ "category": "food_and_drink", "char": "🍻", "name": "beers", "keywords": ["relax", "beverage", "drink", "drunk", "party", "pub", "summer", "alcohol", "booze"] },
-	{ "category": "food_and_drink", "char": "🥂", "name": "clinking_glasses", "keywords": ["beverage", "drink", "party", "alcohol", "celebrate", "cheers", "wine", "champagne", "toast"] },
-	{ "category": "food_and_drink", "char": "🍷", "name": "wine_glass", "keywords": ["drink", "beverage", "drunk", "alcohol", "booze"] },
-	{ "category": "food_and_drink", "char": "🥃", "name": "tumbler_glass", "keywords": ["drink", "beverage", "drunk", "alcohol", "liquor", "booze", "bourbon", "scotch", "whisky", "glass", "shot"] },
-	{ "category": "food_and_drink", "char": "🍸", "name": "cocktail", "keywords": ["drink", "drunk", "alcohol", "beverage", "booze", "mojito"] },
-	{ "category": "food_and_drink", "char": "🍹", "name": "tropical_drink", "keywords": ["beverage", "cocktail", "summer", "beach", "alcohol", "booze", "mojito"] },
-	{ "category": "food_and_drink", "char": "🍾", "name": "champagne", "keywords": ["drink", "wine", "bottle", "celebration"] },
-	{ "category": "food_and_drink", "char": "🍶", "name": "sake", "keywords": ["wine", "drink", "drunk", "beverage", "japanese", "alcohol", "booze"] },
-	{ "category": "food_and_drink", "char": "🍵", "name": "tea", "keywords": ["drink", "bowl", "breakfast", "green", "british"] },
-	{ "category": "food_and_drink", "char": "🥤", "name": "cup_with_straw", "keywords": ["drink", "soda"] },
-	{ "category": "food_and_drink", "char": "☕", "name": "coffee", "keywords": ["beverage", "caffeine", "latte", "espresso"] },
-	{ "category": "food_and_drink", "char": "🫖", "name": "teapot", "keywords": [] },
-	{ "category": "food_and_drink", "char": "🧋", "name": "bubble_tea", "keywords": ["tapioca"] },
-	{ "category": "food_and_drink", "char": "🍼", "name": "baby_bottle", "keywords": ["food", "container", "milk"] },
-	{ "category": "food_and_drink", "char": "🧃", "name": "beverage_box", "keywords": ["food", "drink"] },
-	{ "category": "food_and_drink", "char": "🧉", "name": "mate", "keywords": ["food", "drink"] },
-	{ "category": "food_and_drink", "char": "🧊", "name": "ice_cube", "keywords": ["food"] },
-	{ "category": "food_and_drink", "char": "🧂", "name": "salt", "keywords": ["condiment", "shaker"] },
-	{ "category": "food_and_drink", "char": "🥄", "name": "spoon", "keywords": ["cutlery", "kitchen", "tableware"] },
-	{ "category": "food_and_drink", "char": "🍴", "name": "fork_and_knife", "keywords": ["cutlery", "kitchen"] },
-	{ "category": "food_and_drink", "char": "🍽", "name": "plate_with_cutlery", "keywords": ["food", "eat", "meal", "lunch", "dinner", "restaurant"] },
-	{ "category": "food_and_drink", "char": "🥣", "name": "bowl_with_spoon", "keywords": ["food", "breakfast", "cereal", "oatmeal", "porridge"] },
-	{ "category": "food_and_drink", "char": "🥡", "name": "takeout_box", "keywords": ["food", "leftovers"] },
-	{ "category": "food_and_drink", "char": "🥢", "name": "chopsticks", "keywords": ["food"] },
-	{ "category": "food_and_drink", "char": "\uD83E\uDED7", "name": "pouring_liquid", "keywords": [] },
-	{ "category": "food_and_drink", "char": "\uD83E\uDED8", "name": "beans", "keywords": [] },
-	{ "category": "food_and_drink", "char": "\uD83E\uDED9", "name": "jar", "keywords": [] },
-	{ "category": "activity", "char": "⚽", "name": "soccer", "keywords": ["sports", "football"] },
-	{ "category": "activity", "char": "🏀", "name": "basketball", "keywords": ["sports", "balls", "NBA"] },
-	{ "category": "activity", "char": "🏈", "name": "football", "keywords": ["sports", "balls", "NFL"] },
-	{ "category": "activity", "char": "⚾", "name": "baseball", "keywords": ["sports", "balls"] },
-	{ "category": "activity", "char": "🥎", "name": "softball", "keywords": ["sports", "balls"] },
-	{ "category": "activity", "char": "🎾", "name": "tennis", "keywords": ["sports", "balls", "green"] },
-	{ "category": "activity", "char": "🏐", "name": "volleyball", "keywords": ["sports", "balls"] },
-	{ "category": "activity", "char": "🏉", "name": "rugby_football", "keywords": ["sports", "team"] },
-	{ "category": "activity", "char": "🥏", "name": "flying_disc", "keywords": ["sports", "frisbee", "ultimate"] },
-	{ "category": "activity", "char": "🎱", "name": "8ball", "keywords": ["pool", "hobby", "game", "luck", "magic"] },
-	{ "category": "activity", "char": "⛳", "name": "golf", "keywords": ["sports", "business", "flag", "hole", "summer"] },
-	{ "category": "activity", "char": "🏌️‍♀️", "name": "golfing_woman", "keywords": ["sports", "business", "woman", "female"] },
-	{ "category": "activity", "char": "🏌", "name": "golfing_man", "keywords": ["sports", "business"] },
-	{ "category": "activity", "char": "🏓", "name": "ping_pong", "keywords": ["sports", "pingpong"] },
-	{ "category": "activity", "char": "🏸", "name": "badminton", "keywords": ["sports"] },
-	{ "category": "activity", "char": "🥅", "name": "goal_net", "keywords": ["sports"] },
-	{ "category": "activity", "char": "🏒", "name": "ice_hockey", "keywords": ["sports"] },
-	{ "category": "activity", "char": "🏑", "name": "field_hockey", "keywords": ["sports"] },
-	{ "category": "activity", "char": "🥍", "name": "lacrosse", "keywords": ["sports", "ball", "stick"] },
-	{ "category": "activity", "char": "🏏", "name": "cricket", "keywords": ["sports"] },
-	{ "category": "activity", "char": "🎿", "name": "ski", "keywords": ["sports", "winter", "cold", "snow"] },
-	{ "category": "activity", "char": "⛷", "name": "skier", "keywords": ["sports", "winter", "snow"] },
-	{ "category": "activity", "char": "🏂", "name": "snowboarder", "keywords": ["sports", "winter"] },
-	{ "category": "activity", "char": "🤺", "name": "person_fencing", "keywords": ["sports", "fencing", "sword"] },
-	{ "category": "activity", "char": "🤼‍♀️", "name": "women_wrestling", "keywords": ["sports", "wrestlers"] },
-	{ "category": "activity", "char": "🤼‍♂️", "name": "men_wrestling", "keywords": ["sports", "wrestlers"] },
-	{ "category": "activity", "char": "🤸‍♀️", "name": "woman_cartwheeling", "keywords": ["gymnastics"] },
-	{ "category": "activity", "char": "🤸‍♂️", "name": "man_cartwheeling", "keywords": ["gymnastics"] },
-	{ "category": "activity", "char": "🤾‍♀️", "name": "woman_playing_handball", "keywords": ["sports"] },
-	{ "category": "activity", "char": "🤾‍♂️", "name": "man_playing_handball", "keywords": ["sports"] },
-	{ "category": "activity", "char": "⛸", "name": "ice_skate", "keywords": ["sports"] },
-	{ "category": "activity", "char": "🥌", "name": "curling_stone", "keywords": ["sports"] },
-	{ "category": "activity", "char": "🛹", "name": "skateboard", "keywords": ["board"] },
-	{ "category": "activity", "char": "🛷", "name": "sled", "keywords": ["sleigh", "luge", "toboggan"] },
-	{ "category": "activity", "char": "🏹", "name": "bow_and_arrow", "keywords": ["sports"] },
-	{ "category": "activity", "char": "🎣", "name": "fishing_pole_and_fish", "keywords": ["food", "hobby", "summer"] },
-	{ "category": "activity", "char": "🥊", "name": "boxing_glove", "keywords": ["sports", "fighting"] },
-	{ "category": "activity", "char": "🥋", "name": "martial_arts_uniform", "keywords": ["judo", "karate", "taekwondo"] },
-	{ "category": "activity", "char": "🚣‍♀️", "name": "rowing_woman", "keywords": ["sports", "hobby", "water", "ship", "woman", "female"] },
-	{ "category": "activity", "char": "🚣", "name": "rowing_man", "keywords": ["sports", "hobby", "water", "ship"] },
-	{ "category": "activity", "char": "🧗‍♀️", "name": "climbing_woman", "keywords": ["sports", "hobby", "woman", "female", "rock"] },
-	{ "category": "activity", "char": "🧗‍♂️", "name": "climbing_man", "keywords": ["sports", "hobby", "man", "male", "rock"] },
-	{ "category": "activity", "char": "🏊‍♀️", "name": "swimming_woman", "keywords": ["sports", "exercise", "human", "athlete", "water", "summer", "woman", "female"] },
-	{ "category": "activity", "char": "🏊", "name": "swimming_man", "keywords": ["sports", "exercise", "human", "athlete", "water", "summer"] },
-	{ "category": "activity", "char": "🤽‍♀️", "name": "woman_playing_water_polo", "keywords": ["sports", "pool"] },
-	{ "category": "activity", "char": "🤽‍♂️", "name": "man_playing_water_polo", "keywords": ["sports", "pool"] },
-	{ "category": "activity", "char": "🧘‍♀️", "name": "woman_in_lotus_position", "keywords": ["woman", "female", "meditation", "yoga", "serenity", "zen", "mindfulness"] },
-	{ "category": "activity", "char": "🧘‍♂️", "name": "man_in_lotus_position", "keywords": ["man", "male", "meditation", "yoga", "serenity", "zen", "mindfulness"] },
-	{ "category": "activity", "char": "🏄‍♀️", "name": "surfing_woman", "keywords": ["sports", "ocean", "sea", "summer", "beach", "woman", "female"] },
-	{ "category": "activity", "char": "🏄", "name": "surfing_man", "keywords": ["sports", "ocean", "sea", "summer", "beach"] },
-	{ "category": "activity", "char": "🛀", "name": "bath", "keywords": ["clean", "shower", "bathroom"] },
-	{ "category": "activity", "char": "⛹️‍♀️", "name": "basketball_woman", "keywords": ["sports", "human", "woman", "female"] },
-	{ "category": "activity", "char": "⛹", "name": "basketball_man", "keywords": ["sports", "human"] },
-	{ "category": "activity", "char": "🏋️‍♀️", "name": "weight_lifting_woman", "keywords": ["sports", "training", "exercise", "woman", "female"] },
-	{ "category": "activity", "char": "🏋", "name": "weight_lifting_man", "keywords": ["sports", "training", "exercise"] },
-	{ "category": "activity", "char": "🚴‍♀️", "name": "biking_woman", "keywords": ["sports", "bike", "exercise", "hipster", "woman", "female"] },
-	{ "category": "activity", "char": "🚴", "name": "biking_man", "keywords": ["sports", "bike", "exercise", "hipster"] },
-	{ "category": "activity", "char": "🚵‍♀️", "name": "mountain_biking_woman", "keywords": ["transportation", "sports", "human", "race", "bike", "woman", "female"] },
-	{ "category": "activity", "char": "🚵", "name": "mountain_biking_man", "keywords": ["transportation", "sports", "human", "race", "bike"] },
-	{ "category": "activity", "char": "🏇", "name": "horse_racing", "keywords": ["animal", "betting", "competition", "gambling", "luck"] },
-	{ "category": "activity", "char": "🤿", "name": "diving_mask", "keywords": ["sports"] },
-	{ "category": "activity", "char": "🪀", "name": "yo_yo", "keywords": ["sports"] },
-	{ "category": "activity", "char": "🪁", "name": "kite", "keywords": ["sports"] },
-	{ "category": "activity", "char": "🦺", "name": "safety_vest", "keywords": ["sports"] },
-	{ "category": "activity", "char": "🪡", "name": "sewing_needle", "keywords": [] },
-	{ "category": "activity", "char": "🪢", "name": "knot", "keywords": [] },
-	{ "category": "activity", "char": "🕴", "name": "business_suit_levitating", "keywords": ["suit", "business", "levitate", "hover", "jump"] },
-	{ "category": "activity", "char": "🏆", "name": "trophy", "keywords": ["win", "award", "contest", "place", "ftw", "ceremony"] },
-	{ "category": "activity", "char": "🎽", "name": "running_shirt_with_sash", "keywords": ["play", "pageant"] },
-	{ "category": "activity", "char": "🏅", "name": "medal_sports", "keywords": ["award", "winning"] },
-	{ "category": "activity", "char": "🎖", "name": "medal_military", "keywords": ["award", "winning", "army"] },
-	{ "category": "activity", "char": "🥇", "name": "1st_place_medal", "keywords": ["award", "winning", "first"] },
-	{ "category": "activity", "char": "🥈", "name": "2nd_place_medal", "keywords": ["award", "second"] },
-	{ "category": "activity", "char": "🥉", "name": "3rd_place_medal", "keywords": ["award", "third"] },
-	{ "category": "activity", "char": "🎗", "name": "reminder_ribbon", "keywords": ["sports", "cause", "support", "awareness"] },
-	{ "category": "activity", "char": "🏵", "name": "rosette", "keywords": ["flower", "decoration", "military"] },
-	{ "category": "activity", "char": "🎫", "name": "ticket", "keywords": ["event", "concert", "pass"] },
-	{ "category": "activity", "char": "🎟", "name": "tickets", "keywords": ["sports", "concert", "entrance"] },
-	{ "category": "activity", "char": "🎭", "name": "performing_arts", "keywords": ["acting", "theater", "drama"] },
-	{ "category": "activity", "char": "🎨", "name": "art", "keywords": ["design", "paint", "draw", "colors"] },
-	{ "category": "activity", "char": "🎪", "name": "circus_tent", "keywords": ["festival", "carnival", "party"] },
-	{ "category": "activity", "char": "🤹‍♀️", "name": "woman_juggling", "keywords": ["juggle", "balance", "skill", "multitask"] },
-	{ "category": "activity", "char": "🤹‍♂️", "name": "man_juggling", "keywords": ["juggle", "balance", "skill", "multitask"] },
-	{ "category": "activity", "char": "🎤", "name": "microphone", "keywords": ["sound", "music", "PA", "sing", "talkshow"] },
-	{ "category": "activity", "char": "🎧", "name": "headphones", "keywords": ["music", "score", "gadgets"] },
-	{ "category": "activity", "char": "🎼", "name": "musical_score", "keywords": ["treble", "clef", "compose"] },
-	{ "category": "activity", "char": "🎹", "name": "musical_keyboard", "keywords": ["piano", "instrument", "compose"] },
-	{ "category": "activity", "char": "🥁", "name": "drum", "keywords": ["music", "instrument", "drumsticks", "snare"] },
-	{ "category": "activity", "char": "🎷", "name": "saxophone", "keywords": ["music", "instrument", "jazz", "blues"] },
-	{ "category": "activity", "char": "🎺", "name": "trumpet", "keywords": ["music", "brass"] },
-	{ "category": "activity", "char": "🎸", "name": "guitar", "keywords": ["music", "instrument"] },
-	{ "category": "activity", "char": "🎻", "name": "violin", "keywords": ["music", "instrument", "orchestra", "symphony"] },
-	{ "category": "activity", "char": "🪕", "name": "banjo", "keywords": ["music", "instrument"] },
-	{ "category": "activity", "char": "🪗", "name": "accordion", "keywords": ["music", "instrument"] },
-	{ "category": "activity", "char": "🪘", "name": "long_drum", "keywords": ["music", "instrument"] },
-	{ "category": "activity", "char": "🎬", "name": "clapper", "keywords": ["movie", "film", "record"] },
-	{ "category": "activity", "char": "🎮", "name": "video_game", "keywords": ["play", "console", "PS4", "controller"] },
-	{ "category": "activity", "char": "👾", "name": "space_invader", "keywords": ["game", "arcade", "play"] },
-	{ "category": "activity", "char": "🎯", "name": "dart", "keywords": ["game", "play", "bar", "target", "bullseye"] },
-	{ "category": "activity", "char": "🎲", "name": "game_die", "keywords": ["dice", "random", "tabletop", "play", "luck"] },
-	{ "category": "activity", "char": "♟️", "name": "chess_pawn", "keywords": ["expendable"] },
-	{ "category": "activity", "char": "🎰", "name": "slot_machine", "keywords": ["bet", "gamble", "vegas", "fruit machine", "luck", "casino"] },
-	{ "category": "activity", "char": "🧩", "name": "jigsaw", "keywords": ["interlocking", "puzzle", "piece"] },
-	{ "category": "activity", "char": "🎳", "name": "bowling", "keywords": ["sports", "fun", "play"] },
-	{ "category": "activity", "char": "🪄", "name": "magic_wand", "keywords": [] },
-	{ "category": "activity", "char": "🪅", "name": "pinata", "keywords": [] },
-	{ "category": "activity", "char": "🪆", "name": "nesting_dolls", "keywords": [] },
-	{ "category": "activity", "char": "\uD83E\uDEAC", "name": "hamsa", "keywords": [] },
-	{ "category": "activity", "char": "\uD83E\uDEA9", "name": "mirror_ball", "keywords": [] },
-	{ "category": "travel_and_places", "char": "🚗", "name": "red_car", "keywords": ["red", "transportation", "vehicle"] },
-	{ "category": "travel_and_places", "char": "🚕", "name": "taxi", "keywords": ["uber", "vehicle", "cars", "transportation"] },
-	{ "category": "travel_and_places", "char": "🚙", "name": "blue_car", "keywords": ["transportation", "vehicle"] },
-	{ "category": "travel_and_places", "char": "🚌", "name": "bus", "keywords": ["car", "vehicle", "transportation"] },
-	{ "category": "travel_and_places", "char": "🚎", "name": "trolleybus", "keywords": ["bart", "transportation", "vehicle"] },
-	{ "category": "travel_and_places", "char": "🏎", "name": "racing_car", "keywords": ["sports", "race", "fast", "formula", "f1"] },
-	{ "category": "travel_and_places", "char": "🚓", "name": "police_car", "keywords": ["vehicle", "cars", "transportation", "law", "legal", "enforcement"] },
-	{ "category": "travel_and_places", "char": "🚑", "name": "ambulance", "keywords": ["health", "911", "hospital"] },
-	{ "category": "travel_and_places", "char": "🚒", "name": "fire_engine", "keywords": ["transportation", "cars", "vehicle"] },
-	{ "category": "travel_and_places", "char": "🚐", "name": "minibus", "keywords": ["vehicle", "car", "transportation"] },
-	{ "category": "travel_and_places", "char": "🚚", "name": "truck", "keywords": ["cars", "transportation"] },
-	{ "category": "travel_and_places", "char": "🚛", "name": "articulated_lorry", "keywords": ["vehicle", "cars", "transportation", "express"] },
-	{ "category": "travel_and_places", "char": "🚜", "name": "tractor", "keywords": ["vehicle", "car", "farming", "agriculture"] },
-	{ "category": "travel_and_places", "char": "🛴", "name": "kick_scooter", "keywords": ["vehicle", "kick", "razor"] },
-	{ "category": "travel_and_places", "char": "🏍", "name": "motorcycle", "keywords": ["race", "sports", "fast"] },
-	{ "category": "travel_and_places", "char": "🚲", "name": "bike", "keywords": ["sports", "bicycle", "exercise", "hipster"] },
-	{ "category": "travel_and_places", "char": "🛵", "name": "motor_scooter", "keywords": ["vehicle", "vespa", "sasha"] },
-	{ "category": "travel_and_places", "char": "🦽", "name": "manual_wheelchair", "keywords": ["vehicle"] },
-	{ "category": "travel_and_places", "char": "🦼", "name": "motorized_wheelchair", "keywords": ["vehicle"] },
-	{ "category": "travel_and_places", "char": "🛺", "name": "auto_rickshaw", "keywords": ["vehicle"] },
-	{ "category": "travel_and_places", "char": "🪂", "name": "parachute", "keywords": ["vehicle"] },
-	{ "category": "travel_and_places", "char": "🚨", "name": "rotating_light", "keywords": ["police", "ambulance", "911", "emergency", "alert", "error", "pinged", "law", "legal"] },
-	{ "category": "travel_and_places", "char": "🚔", "name": "oncoming_police_car", "keywords": ["vehicle", "law", "legal", "enforcement", "911"] },
-	{ "category": "travel_and_places", "char": "🚍", "name": "oncoming_bus", "keywords": ["vehicle", "transportation"] },
-	{ "category": "travel_and_places", "char": "🚘", "name": "oncoming_automobile", "keywords": ["car", "vehicle", "transportation"] },
-	{ "category": "travel_and_places", "char": "🚖", "name": "oncoming_taxi", "keywords": ["vehicle", "cars", "uber"] },
-	{ "category": "travel_and_places", "char": "🚡", "name": "aerial_tramway", "keywords": ["transportation", "vehicle", "ski"] },
-	{ "category": "travel_and_places", "char": "🚠", "name": "mountain_cableway", "keywords": ["transportation", "vehicle", "ski"] },
-	{ "category": "travel_and_places", "char": "🚟", "name": "suspension_railway", "keywords": ["vehicle", "transportation"] },
-	{ "category": "travel_and_places", "char": "🚃", "name": "railway_car", "keywords": ["transportation", "vehicle", "train"] },
-	{ "category": "travel_and_places", "char": "🚋", "name": "train", "keywords": ["transportation", "vehicle", "carriage", "public", "travel"] },
-	{ "category": "travel_and_places", "char": "🚝", "name": "monorail", "keywords": ["transportation", "vehicle"] },
-	{ "category": "travel_and_places", "char": "🚄", "name": "bullettrain_side", "keywords": ["transportation", "vehicle"] },
-	{ "category": "travel_and_places", "char": "🚅", "name": "bullettrain_front", "keywords": ["transportation", "vehicle", "speed", "fast", "public", "travel"] },
-	{ "category": "travel_and_places", "char": "🚈", "name": "light_rail", "keywords": ["transportation", "vehicle"] },
-	{ "category": "travel_and_places", "char": "🚞", "name": "mountain_railway", "keywords": ["transportation", "vehicle"] },
-	{ "category": "travel_and_places", "char": "🚂", "name": "steam_locomotive", "keywords": ["transportation", "vehicle", "train"] },
-	{ "category": "travel_and_places", "char": "🚆", "name": "train2", "keywords": ["transportation", "vehicle"] },
-	{ "category": "travel_and_places", "char": "🚇", "name": "metro", "keywords": ["transportation", "blue-square", "mrt", "underground", "tube"] },
-	{ "category": "travel_and_places", "char": "🚊", "name": "tram", "keywords": ["transportation", "vehicle"] },
-	{ "category": "travel_and_places", "char": "🚉", "name": "station", "keywords": ["transportation", "vehicle", "public"] },
-	{ "category": "travel_and_places", "char": "🛸", "name": "flying_saucer", "keywords": ["transportation", "vehicle", "ufo"] },
-	{ "category": "travel_and_places", "char": "🚁", "name": "helicopter", "keywords": ["transportation", "vehicle", "fly"] },
-	{ "category": "travel_and_places", "char": "🛩", "name": "small_airplane", "keywords": ["flight", "transportation", "fly", "vehicle"] },
-	{ "category": "travel_and_places", "char": "✈️", "name": "airplane", "keywords": ["vehicle", "transportation", "flight", "fly"] },
-	{ "category": "travel_and_places", "char": "🛫", "name": "flight_departure", "keywords": ["airport", "flight", "landing"] },
-	{ "category": "travel_and_places", "char": "🛬", "name": "flight_arrival", "keywords": ["airport", "flight", "boarding"] },
-	{ "category": "travel_and_places", "char": "⛵", "name": "sailboat", "keywords": ["ship", "summer", "transportation", "water", "sailing"] },
-	{ "category": "travel_and_places", "char": "🛥", "name": "motor_boat", "keywords": ["ship"] },
-	{ "category": "travel_and_places", "char": "🚤", "name": "speedboat", "keywords": ["ship", "transportation", "vehicle", "summer"] },
-	{ "category": "travel_and_places", "char": "⛴", "name": "ferry", "keywords": ["boat", "ship", "yacht"] },
-	{ "category": "travel_and_places", "char": "🛳", "name": "passenger_ship", "keywords": ["yacht", "cruise", "ferry"] },
-	{ "category": "travel_and_places", "char": "🚀", "name": "rocket", "keywords": ["launch", "ship", "staffmode", "NASA", "outer space", "outer_space", "fly"] },
-	{ "category": "travel_and_places", "char": "🛰", "name": "artificial_satellite", "keywords": ["communication", "gps", "orbit", "spaceflight", "NASA", "ISS"] },
-	{ "category": "travel_and_places", "char": "🛻", "name": "pickup_truck", "keywords": ["car"] },
-	{ "category": "travel_and_places", "char": "🛼", "name": "roller_skate", "keywords": [] },
-	{ "category": "travel_and_places", "char": "💺", "name": "seat", "keywords": ["sit", "airplane", "transport", "bus", "flight", "fly"] },
-	{ "category": "travel_and_places", "char": "🛶", "name": "canoe", "keywords": ["boat", "paddle", "water", "ship"] },
-	{ "category": "travel_and_places", "char": "⚓", "name": "anchor", "keywords": ["ship", "ferry", "sea", "boat"] },
-	{ "category": "travel_and_places", "char": "🚧", "name": "construction", "keywords": ["wip", "progress", "caution", "warning"] },
-	{ "category": "travel_and_places", "char": "⛽", "name": "fuelpump", "keywords": ["gas station", "petroleum"] },
-	{ "category": "travel_and_places", "char": "🚏", "name": "busstop", "keywords": ["transportation", "wait"] },
-	{ "category": "travel_and_places", "char": "🚦", "name": "vertical_traffic_light", "keywords": ["transportation", "driving"] },
-	{ "category": "travel_and_places", "char": "🚥", "name": "traffic_light", "keywords": ["transportation", "signal"] },
-	{ "category": "travel_and_places", "char": "🏁", "name": "checkered_flag", "keywords": ["contest", "finishline", "race", "gokart"] },
-	{ "category": "travel_and_places", "char": "🚢", "name": "ship", "keywords": ["transportation", "titanic", "deploy"] },
-	{ "category": "travel_and_places", "char": "🎡", "name": "ferris_wheel", "keywords": ["photo", "carnival", "londoneye"] },
-	{ "category": "travel_and_places", "char": "🎢", "name": "roller_coaster", "keywords": ["carnival", "playground", "photo", "fun"] },
-	{ "category": "travel_and_places", "char": "🎠", "name": "carousel_horse", "keywords": ["photo", "carnival"] },
-	{ "category": "travel_and_places", "char": "🏗", "name": "building_construction", "keywords": ["wip", "working", "progress"] },
-	{ "category": "travel_and_places", "char": "🌁", "name": "foggy", "keywords": ["photo", "mountain"] },
-	{ "category": "travel_and_places", "char": "🏭", "name": "factory", "keywords": ["building", "industry", "pollution", "smoke"] },
-	{ "category": "travel_and_places", "char": "⛲", "name": "fountain", "keywords": ["photo", "summer", "water", "fresh"] },
-	{ "category": "travel_and_places", "char": "🎑", "name": "rice_scene", "keywords": ["photo", "japan", "asia", "tsukimi"] },
-	{ "category": "travel_and_places", "char": "⛰", "name": "mountain", "keywords": ["photo", "nature", "environment"] },
-	{ "category": "travel_and_places", "char": "🏔", "name": "mountain_snow", "keywords": ["photo", "nature", "environment", "winter", "cold"] },
-	{ "category": "travel_and_places", "char": "🗻", "name": "mount_fuji", "keywords": ["photo", "mountain", "nature", "japanese"] },
-	{ "category": "travel_and_places", "char": "🌋", "name": "volcano", "keywords": ["photo", "nature", "disaster"] },
-	{ "category": "travel_and_places", "char": "🗾", "name": "japan", "keywords": ["nation", "country", "japanese", "asia"] },
-	{ "category": "travel_and_places", "char": "🏕", "name": "camping", "keywords": ["photo", "outdoors", "tent"] },
-	{ "category": "travel_and_places", "char": "⛺", "name": "tent", "keywords": ["photo", "camping", "outdoors"] },
-	{ "category": "travel_and_places", "char": "🏞", "name": "national_park", "keywords": ["photo", "environment", "nature"] },
-	{ "category": "travel_and_places", "char": "🛣", "name": "motorway", "keywords": ["road", "cupertino", "interstate", "highway"] },
-	{ "category": "travel_and_places", "char": "🛤", "name": "railway_track", "keywords": ["train", "transportation"] },
-	{ "category": "travel_and_places", "char": "🌅", "name": "sunrise", "keywords": ["morning", "view", "vacation", "photo"] },
-	{ "category": "travel_and_places", "char": "🌄", "name": "sunrise_over_mountains", "keywords": ["view", "vacation", "photo"] },
-	{ "category": "travel_and_places", "char": "🏜", "name": "desert", "keywords": ["photo", "warm", "saharah"] },
-	{ "category": "travel_and_places", "char": "🏖", "name": "beach_umbrella", "keywords": ["weather", "summer", "sunny", "sand", "mojito"] },
-	{ "category": "travel_and_places", "char": "🏝", "name": "desert_island", "keywords": ["photo", "tropical", "mojito"] },
-	{ "category": "travel_and_places", "char": "🌇", "name": "city_sunrise", "keywords": ["photo", "good morning", "dawn"] },
-	{ "category": "travel_and_places", "char": "🌆", "name": "city_sunset", "keywords": ["photo", "evening", "sky", "buildings"] },
-	{ "category": "travel_and_places", "char": "🏙", "name": "cityscape", "keywords": ["photo", "night life", "urban"] },
-	{ "category": "travel_and_places", "char": "🌃", "name": "night_with_stars", "keywords": ["evening", "city", "downtown"] },
-	{ "category": "travel_and_places", "char": "🌉", "name": "bridge_at_night", "keywords": ["photo", "sanfrancisco"] },
-	{ "category": "travel_and_places", "char": "🌌", "name": "milky_way", "keywords": ["photo", "space", "stars"] },
-	{ "category": "travel_and_places", "char": "🌠", "name": "stars", "keywords": ["night", "photo"] },
-	{ "category": "travel_and_places", "char": "🎇", "name": "sparkler", "keywords": ["stars", "night", "shine"] },
-	{ "category": "travel_and_places", "char": "🎆", "name": "fireworks", "keywords": ["photo", "festival", "carnival", "congratulations"] },
-	{ "category": "travel_and_places", "char": "🌈", "name": "rainbow", "keywords": ["nature", "happy", "unicorn_face", "photo", "sky", "spring"] },
-	{ "category": "travel_and_places", "char": "🏘", "name": "houses", "keywords": ["buildings", "photo"] },
-	{ "category": "travel_and_places", "char": "🏰", "name": "european_castle", "keywords": ["building", "royalty", "history"] },
-	{ "category": "travel_and_places", "char": "🏯", "name": "japanese_castle", "keywords": ["photo", "building"] },
-	{ "category": "travel_and_places", "char": "🗼", "name": "tokyo_tower", "keywords": ["photo", "japanese"] },
-	{ "category": "travel_and_places", "char": "", "name": "shibuya_109", "keywords": ["photo", "japanese"] },
-	{ "category": "travel_and_places", "char": "🏟", "name": "stadium", "keywords": ["photo", "place", "sports", "concert", "venue"] },
-	{ "category": "travel_and_places", "char": "🗽", "name": "statue_of_liberty", "keywords": ["american", "newyork"] },
-	{ "category": "travel_and_places", "char": "🏠", "name": "house", "keywords": ["building", "home"] },
-	{ "category": "travel_and_places", "char": "🏡", "name": "house_with_garden", "keywords": ["home", "plant", "nature"] },
-	{ "category": "travel_and_places", "char": "🏚", "name": "derelict_house", "keywords": ["abandon", "evict", "broken", "building"] },
-	{ "category": "travel_and_places", "char": "🏢", "name": "office", "keywords": ["building", "bureau", "work"] },
-	{ "category": "travel_and_places", "char": "🏬", "name": "department_store", "keywords": ["building", "shopping", "mall"] },
-	{ "category": "travel_and_places", "char": "🏣", "name": "post_office", "keywords": ["building", "envelope", "communication"] },
-	{ "category": "travel_and_places", "char": "🏤", "name": "european_post_office", "keywords": ["building", "email"] },
-	{ "category": "travel_and_places", "char": "🏥", "name": "hospital", "keywords": ["building", "health", "surgery", "doctor"] },
-	{ "category": "travel_and_places", "char": "🏦", "name": "bank", "keywords": ["building", "money", "sales", "cash", "business", "enterprise"] },
-	{ "category": "travel_and_places", "char": "🏨", "name": "hotel", "keywords": ["building", "accomodation", "checkin"] },
-	{ "category": "travel_and_places", "char": "🏪", "name": "convenience_store", "keywords": ["building", "shopping", "groceries"] },
-	{ "category": "travel_and_places", "char": "🏫", "name": "school", "keywords": ["building", "student", "education", "learn", "teach"] },
-	{ "category": "travel_and_places", "char": "🏩", "name": "love_hotel", "keywords": ["like", "affection", "dating"] },
-	{ "category": "travel_and_places", "char": "💒", "name": "wedding", "keywords": ["love", "like", "affection", "couple", "marriage", "bride", "groom"] },
-	{ "category": "travel_and_places", "char": "🏛", "name": "classical_building", "keywords": ["art", "culture", "history"] },
-	{ "category": "travel_and_places", "char": "⛪", "name": "church", "keywords": ["building", "religion", "christ"] },
-	{ "category": "travel_and_places", "char": "🕌", "name": "mosque", "keywords": ["islam", "worship", "minaret"] },
-	{ "category": "travel_and_places", "char": "🕍", "name": "synagogue", "keywords": ["judaism", "worship", "temple", "jewish"] },
-	{ "category": "travel_and_places", "char": "🕋", "name": "kaaba", "keywords": ["mecca", "mosque", "islam"] },
-	{ "category": "travel_and_places", "char": "⛩", "name": "shinto_shrine", "keywords": ["temple", "japan", "kyoto"] },
-	{ "category": "travel_and_places", "char": "🛕", "name": "hindu_temple", "keywords": ["temple"] },
-	{ "category": "travel_and_places", "char": "🪨", "name": "rock", "keywords": [] },
-	{ "category": "travel_and_places", "char": "🪵", "name": "wood", "keywords": [] },
-	{ "category": "travel_and_places", "char": "🛖", "name": "hut", "keywords": [] },
-	{ "category": "travel_and_places", "char": "\uD83D\uDEDD", "name": "playground_slide", "keywords": [] },
-	{ "category": "travel_and_places", "char": "\uD83D\uDEDE", "name": "wheel", "keywords": [] },
-	{ "category": "travel_and_places", "char": "\uD83D\uDEDF", "name": "ring_buoy", "keywords": [] },
-	{ "category": "objects", "char": "⌚", "name": "watch", "keywords": ["time", "accessories"] },
-	{ "category": "objects", "char": "📱", "name": "iphone", "keywords": ["technology", "apple", "gadgets", "dial"] },
-	{ "category": "objects", "char": "📲", "name": "calling", "keywords": ["iphone", "incoming"] },
-	{ "category": "objects", "char": "💻", "name": "computer", "keywords": ["technology", "laptop", "screen", "display", "monitor"] },
-	{ "category": "objects", "char": "⌨", "name": "keyboard", "keywords": ["technology", "computer", "type", "input", "text"] },
-	{ "category": "objects", "char": "🖥", "name": "desktop_computer", "keywords": ["technology", "computing", "screen"] },
-	{ "category": "objects", "char": "🖨", "name": "printer", "keywords": ["paper", "ink"] },
-	{ "category": "objects", "char": "🖱", "name": "computer_mouse", "keywords": ["click"] },
-	{ "category": "objects", "char": "🖲", "name": "trackball", "keywords": ["technology", "trackpad"] },
-	{ "category": "objects", "char": "🕹", "name": "joystick", "keywords": ["game", "play"] },
-	{ "category": "objects", "char": "🗜", "name": "clamp", "keywords": ["tool"] },
-	{ "category": "objects", "char": "💽", "name": "minidisc", "keywords": ["technology", "record", "data", "disk", "90s"] },
-	{ "category": "objects", "char": "💾", "name": "floppy_disk", "keywords": ["oldschool", "technology", "save", "90s", "80s"] },
-	{ "category": "objects", "char": "💿", "name": "cd", "keywords": ["technology", "dvd", "disk", "disc", "90s"] },
-	{ "category": "objects", "char": "📀", "name": "dvd", "keywords": ["cd", "disk", "disc"] },
-	{ "category": "objects", "char": "📼", "name": "vhs", "keywords": ["record", "video", "oldschool", "90s", "80s"] },
-	{ "category": "objects", "char": "📷", "name": "camera", "keywords": ["gadgets", "photography"] },
-	{ "category": "objects", "char": "📸", "name": "camera_flash", "keywords": ["photography", "gadgets"] },
-	{ "category": "objects", "char": "📹", "name": "video_camera", "keywords": ["film", "record"] },
-	{ "category": "objects", "char": "🎥", "name": "movie_camera", "keywords": ["film", "record"] },
-	{ "category": "objects", "char": "📽", "name": "film_projector", "keywords": ["video", "tape", "record", "movie"] },
-	{ "category": "objects", "char": "🎞", "name": "film_strip", "keywords": ["movie"] },
-	{ "category": "objects", "char": "📞", "name": "telephone_receiver", "keywords": ["technology", "communication", "dial"] },
-	{ "category": "objects", "char": "☎️", "name": "phone", "keywords": ["technology", "communication", "dial", "telephone"] },
-	{ "category": "objects", "char": "📟", "name": "pager", "keywords": ["bbcall", "oldschool", "90s"] },
-	{ "category": "objects", "char": "📠", "name": "fax", "keywords": ["communication", "technology"] },
-	{ "category": "objects", "char": "📺", "name": "tv", "keywords": ["technology", "program", "oldschool", "show", "television"] },
-	{ "category": "objects", "char": "📻", "name": "radio", "keywords": ["communication", "music", "podcast", "program"] },
-	{ "category": "objects", "char": "🎙", "name": "studio_microphone", "keywords": ["sing", "recording", "artist", "talkshow"] },
-	{ "category": "objects", "char": "🎚", "name": "level_slider", "keywords": ["scale"] },
-	{ "category": "objects", "char": "🎛", "name": "control_knobs", "keywords": ["dial"] },
-	{ "category": "objects", "char": "🧭", "name": "compass", "keywords": ["magnetic", "navigation", "orienteering"] },
-	{ "category": "objects", "char": "⏱", "name": "stopwatch", "keywords": ["time", "deadline"] },
-	{ "category": "objects", "char": "⏲", "name": "timer_clock", "keywords": ["alarm"] },
-	{ "category": "objects", "char": "⏰", "name": "alarm_clock", "keywords": ["time", "wake"] },
-	{ "category": "objects", "char": "🕰", "name": "mantelpiece_clock", "keywords": ["time"] },
-	{ "category": "objects", "char": "⏳", "name": "hourglass_flowing_sand", "keywords": ["oldschool", "time", "countdown"] },
-	{ "category": "objects", "char": "⌛", "name": "hourglass", "keywords": ["time", "clock", "oldschool", "limit", "exam", "quiz", "test"] },
-	{ "category": "objects", "char": "📡", "name": "satellite", "keywords": ["communication", "future", "radio", "space"] },
-	{ "category": "objects", "char": "🔋", "name": "battery", "keywords": ["power", "energy", "sustain"] },
-	{ "category": "objects", "char": "\uD83E\uDEAB", "name": "battery", "keywords": [] },
-	{ "category": "objects", "char": "🔌", "name": "electric_plug", "keywords": ["charger", "power"] },
-	{ "category": "objects", "char": "💡", "name": "bulb", "keywords": ["light", "electricity", "idea"] },
-	{ "category": "objects", "char": "🔦", "name": "flashlight", "keywords": ["dark", "camping", "sight", "night"] },
-	{ "category": "objects", "char": "🕯", "name": "candle", "keywords": ["fire", "wax"] },
-	{ "category": "objects", "char": "🧯", "name": "fire_extinguisher", "keywords": ["quench"] },
-	{ "category": "objects", "char": "🗑", "name": "wastebasket", "keywords": ["bin", "trash", "rubbish", "garbage", "toss"] },
-	{ "category": "objects", "char": "🛢", "name": "oil_drum", "keywords": ["barrell"] },
-	{ "category": "objects", "char": "💸", "name": "money_with_wings", "keywords": ["dollar", "bills", "payment", "sale"] },
-	{ "category": "objects", "char": "💵", "name": "dollar", "keywords": ["money", "sales", "bill", "currency"] },
-	{ "category": "objects", "char": "💴", "name": "yen", "keywords": ["money", "sales", "japanese", "dollar", "currency"] },
-	{ "category": "objects", "char": "💶", "name": "euro", "keywords": ["money", "sales", "dollar", "currency"] },
-	{ "category": "objects", "char": "💷", "name": "pound", "keywords": ["british", "sterling", "money", "sales", "bills", "uk", "england", "currency"] },
-	{ "category": "objects", "char": "💰", "name": "moneybag", "keywords": ["dollar", "payment", "coins", "sale"] },
-	{ "category": "objects", "char": "🪙", "name": "coin", "keywords": ["dollar", "payment", "coins", "sale"] },
-	{ "category": "objects", "char": "💳", "name": "credit_card", "keywords": ["money", "sales", "dollar", "bill", "payment", "shopping"] },
-	{ "category": "objects", "char": "\uD83E\uDEAB", "name": "identification_card", "keywords": [] },
-	{ "category": "objects", "char": "💎", "name": "gem", "keywords": ["blue", "ruby", "diamond", "jewelry"] },
-	{ "category": "objects", "char": "⚖", "name": "balance_scale", "keywords": ["law", "fairness", "weight"] },
-	{ "category": "objects", "char": "🧰", "name": "toolbox", "keywords": ["tools", "diy", "fix", "maintainer", "mechanic"] },
-	{ "category": "objects", "char": "🔧", "name": "wrench", "keywords": ["tools", "diy", "ikea", "fix", "maintainer"] },
-	{ "category": "objects", "char": "🔨", "name": "hammer", "keywords": ["tools", "build", "create"] },
-	{ "category": "objects", "char": "⚒", "name": "hammer_and_pick", "keywords": ["tools", "build", "create"] },
-	{ "category": "objects", "char": "🛠", "name": "hammer_and_wrench", "keywords": ["tools", "build", "create"] },
-	{ "category": "objects", "char": "⛏", "name": "pick", "keywords": ["tools", "dig"] },
-	{ "category": "objects", "char": "🪓", "name": "axe", "keywords": ["tools"] },
-	{ "category": "objects", "char": "🦯", "name": "probing_cane", "keywords": ["tools"] },
-	{ "category": "objects", "char": "🔩", "name": "nut_and_bolt", "keywords": ["handy", "tools", "fix"] },
-	{ "category": "objects", "char": "⚙", "name": "gear", "keywords": ["cog"] },
-	{ "category": "objects", "char": "🪃", "name": "boomerang", "keywords": ["tool"] },
-	{ "category": "objects", "char": "🪚", "name": "carpentry_saw", "keywords": ["tool"] },
-	{ "category": "objects", "char": "🪛", "name": "screwdriver", "keywords": ["tool"] },
-	{ "category": "objects", "char": "🪝", "name": "hook", "keywords": ["tool"] },
-	{ "category": "objects", "char": "🪜", "name": "ladder", "keywords": ["tool"] },
-	{ "category": "objects", "char": "🧱", "name": "brick", "keywords": ["bricks"] },
-	{ "category": "objects", "char": "⛓", "name": "chains", "keywords": ["lock", "arrest"] },
-	{ "category": "objects", "char": "🧲", "name": "magnet", "keywords": ["attraction", "magnetic"] },
-	{ "category": "objects", "char": "🔫", "name": "gun", "keywords": ["violence", "weapon", "pistol", "revolver"] },
-	{ "category": "objects", "char": "💣", "name": "bomb", "keywords": ["boom", "explode", "explosion", "terrorism"] },
-	{ "category": "objects", "char": "🧨", "name": "firecracker", "keywords": ["dynamite", "boom", "explode", "explosion", "explosive"] },
-	{ "category": "objects", "char": "🔪", "name": "hocho", "keywords": ["knife", "blade", "cutlery", "kitchen", "weapon"] },
-	{ "category": "objects", "char": "🗡", "name": "dagger", "keywords": ["weapon"] },
-	{ "category": "objects", "char": "⚔", "name": "crossed_swords", "keywords": ["weapon"] },
-	{ "category": "objects", "char": "🛡", "name": "shield", "keywords": ["protection", "security"] },
-	{ "category": "objects", "char": "🚬", "name": "smoking", "keywords": ["kills", "tobacco", "cigarette", "joint", "smoke"] },
-	{ "category": "objects", "char": "☠", "name": "skull_and_crossbones", "keywords": ["poison", "danger", "deadly", "scary", "death", "pirate", "evil"] },
-	{ "category": "objects", "char": "⚰", "name": "coffin", "keywords": ["vampire", "dead", "die", "death", "rip", "graveyard", "cemetery", "casket", "funeral", "box"] },
-	{ "category": "objects", "char": "⚱", "name": "funeral_urn", "keywords": ["dead", "die", "death", "rip", "ashes"] },
-	{ "category": "objects", "char": "🏺", "name": "amphora", "keywords": ["vase", "jar"] },
-	{ "category": "objects", "char": "🔮", "name": "crystal_ball", "keywords": ["disco", "party", "magic", "circus", "fortune_teller"] },
-	{ "category": "objects", "char": "📿", "name": "prayer_beads", "keywords": ["dhikr", "religious"] },
-	{ "category": "objects", "char": "🧿", "name": "nazar_amulet", "keywords": ["bead", "charm"] },
-	{ "category": "objects", "char": "💈", "name": "barber", "keywords": ["hair", "salon", "style"] },
-	{ "category": "objects", "char": "⚗", "name": "alembic", "keywords": ["distilling", "science", "experiment", "chemistry"] },
-	{ "category": "objects", "char": "🔭", "name": "telescope", "keywords": ["stars", "space", "zoom", "science", "astronomy"] },
-	{ "category": "objects", "char": "🔬", "name": "microscope", "keywords": ["laboratory", "experiment", "zoomin", "science", "study"] },
-	{ "category": "objects", "char": "🕳", "name": "hole", "keywords": ["embarrassing"] },
-	{ "category": "objects", "char": "💊", "name": "pill", "keywords": ["health", "medicine", "doctor", "pharmacy", "drug"] },
-	{ "category": "objects", "char": "💉", "name": "syringe", "keywords": ["health", "hospital", "drugs", "blood", "medicine", "needle", "doctor", "nurse"] },
-	{ "category": "objects", "char": "🩸", "name": "drop_of_blood", "keywords": ["health", "hospital", "medicine", "needle", "doctor", "nurse"] },
-	{ "category": "objects", "char": "🩹", "name": "adhesive_bandage", "keywords": ["health", "hospital", "medicine", "needle", "doctor", "nurse"] },
-	{ "category": "objects", "char": "🩺", "name": "stethoscope", "keywords": ["health", "hospital", "medicine", "needle", "doctor", "nurse"] },
-	{ "category": "objects", "char": "🪒", "name": "razor", "keywords": ["health"] },
-	{ "category": "objects", "char": "\uD83E\uDE7B", "name": "xray", "keywords": [] },
-	{ "category": "objects", "char": "\uD83E\uDE7C", "name": "crutch", "keywords": [] },
-	{ "category": "objects", "char": "🧬", "name": "dna", "keywords": ["biologist", "genetics", "life"] },
-	{ "category": "objects", "char": "🧫", "name": "petri_dish", "keywords": ["bacteria", "biology", "culture", "lab"] },
-	{ "category": "objects", "char": "🧪", "name": "test_tube", "keywords": ["chemistry", "experiment", "lab", "science"] },
-	{ "category": "objects", "char": "🌡", "name": "thermometer", "keywords": ["weather", "temperature", "hot", "cold"] },
-	{ "category": "objects", "char": "🧹", "name": "broom", "keywords": ["cleaning", "sweeping", "witch"] },
-	{ "category": "objects", "char": "🧺", "name": "basket", "keywords": ["laundry"] },
-	{ "category": "objects", "char": "🧻", "name": "toilet_paper", "keywords": ["roll"] },
-	{ "category": "objects", "char": "🏷", "name": "label", "keywords": ["sale", "tag"] },
-	{ "category": "objects", "char": "🔖", "name": "bookmark", "keywords": ["favorite", "label", "save"] },
-	{ "category": "objects", "char": "🚽", "name": "toilet", "keywords": ["restroom", "wc", "washroom", "bathroom", "potty"] },
-	{ "category": "objects", "char": "🚿", "name": "shower", "keywords": ["clean", "water", "bathroom"] },
-	{ "category": "objects", "char": "🛁", "name": "bathtub", "keywords": ["clean", "shower", "bathroom"] },
-	{ "category": "objects", "char": "🧼", "name": "soap", "keywords": ["bar", "bathing", "cleaning", "lather"] },
-	{ "category": "objects", "char": "🧽", "name": "sponge", "keywords": ["absorbing", "cleaning", "porous"] },
-	{ "category": "objects", "char": "🧴", "name": "lotion_bottle", "keywords": ["moisturizer", "sunscreen"] },
-	{ "category": "objects", "char": "🔑", "name": "key", "keywords": ["lock", "door", "password"] },
-	{ "category": "objects", "char": "🗝", "name": "old_key", "keywords": ["lock", "door", "password"] },
-	{ "category": "objects", "char": "🛋", "name": "couch_and_lamp", "keywords": ["read", "chill"] },
-	{ "category": "objects", "char": "🪔", "name": "diya_Lamp", "keywords": ["light", "oil"] },
-	{ "category": "objects", "char": "🛌", "name": "sleeping_bed", "keywords": ["bed", "rest"] },
-	{ "category": "objects", "char": "🛏", "name": "bed", "keywords": ["sleep", "rest"] },
-	{ "category": "objects", "char": "🚪", "name": "door", "keywords": ["house", "entry", "exit"] },
-	{ "category": "objects", "char": "🪑", "name": "chair", "keywords": ["house", "desk"] },
-	{ "category": "objects", "char": "🛎", "name": "bellhop_bell", "keywords": ["service"] },
-	{ "category": "objects", "char": "🧸", "name": "teddy_bear", "keywords": ["plush", "stuffed"] },
-	{ "category": "objects", "char": "🖼", "name": "framed_picture", "keywords": ["photography"] },
-	{ "category": "objects", "char": "🗺", "name": "world_map", "keywords": ["location", "direction"] },
-	{ "category": "objects", "char": "🛗", "name": "elevator", "keywords": ["household"] },
-	{ "category": "objects", "char": "🪞", "name": "mirror", "keywords": ["household"] },
-	{ "category": "objects", "char": "🪟", "name": "window", "keywords": ["household"] },
-	{ "category": "objects", "char": "🪠", "name": "plunger", "keywords": ["household"] },
-	{ "category": "objects", "char": "🪤", "name": "mouse_trap", "keywords": ["household"] },
-	{ "category": "objects", "char": "🪣", "name": "bucket", "keywords": ["household"] },
-	{ "category": "objects", "char": "🪥", "name": "toothbrush", "keywords": ["household"] },
-	{ "category": "objects", "char": "\uD83E\uDEE7", "name": "bubbles", "keywords": [] },
-	{ "category": "objects", "char": "⛱", "name": "parasol_on_ground", "keywords": ["weather", "summer"] },
-	{ "category": "objects", "char": "🗿", "name": "moyai", "keywords": ["rock", "easter island", "moai"] },
-	{ "category": "objects", "char": "🛍", "name": "shopping", "keywords": ["mall", "buy", "purchase"] },
-	{ "category": "objects", "char": "🛒", "name": "shopping_cart", "keywords": ["trolley"] },
-	{ "category": "objects", "char": "🎈", "name": "balloon", "keywords": ["party", "celebration", "birthday", "circus"] },
-	{ "category": "objects", "char": "🎏", "name": "flags", "keywords": ["fish", "japanese", "koinobori", "carp", "banner"] },
-	{ "category": "objects", "char": "🎀", "name": "ribbon", "keywords": ["decoration", "pink", "girl", "bowtie"] },
-	{ "category": "objects", "char": "🎁", "name": "gift", "keywords": ["present", "birthday", "christmas", "xmas"] },
-	{ "category": "objects", "char": "🎊", "name": "confetti_ball", "keywords": ["festival", "party", "birthday", "circus"] },
-	{ "category": "objects", "char": "🎉", "name": "tada", "keywords": ["party", "congratulations", "birthday", "magic", "circus", "celebration"] },
-	{ "category": "objects", "char": "🎎", "name": "dolls", "keywords": ["japanese", "toy", "kimono"] },
-	{ "category": "objects", "char": "🎐", "name": "wind_chime", "keywords": ["nature", "ding", "spring", "bell"] },
-	{ "category": "objects", "char": "🎌", "name": "crossed_flags", "keywords": ["japanese", "nation", "country", "border"] },
-	{ "category": "objects", "char": "🏮", "name": "izakaya_lantern", "keywords": ["light", "paper", "halloween", "spooky"] },
-	{ "category": "objects", "char": "🧧", "name": "red_envelope", "keywords": ["gift"] },
-	{ "category": "objects", "char": "✉️", "name": "email", "keywords": ["letter", "postal", "inbox", "communication"] },
-	{ "category": "objects", "char": "📩", "name": "envelope_with_arrow", "keywords": ["email", "communication"] },
-	{ "category": "objects", "char": "📨", "name": "incoming_envelope", "keywords": ["email", "inbox"] },
-	{ "category": "objects", "char": "📧", "name": "e-mail", "keywords": ["communication", "inbox"] },
-	{ "category": "objects", "char": "💌", "name": "love_letter", "keywords": ["email", "like", "affection", "envelope", "valentines"] },
-	{ "category": "objects", "char": "📮", "name": "postbox", "keywords": ["email", "letter", "envelope"] },
-	{ "category": "objects", "char": "📪", "name": "mailbox_closed", "keywords": ["email", "communication", "inbox"] },
-	{ "category": "objects", "char": "📫", "name": "mailbox", "keywords": ["email", "inbox", "communication"] },
-	{ "category": "objects", "char": "📬", "name": "mailbox_with_mail", "keywords": ["email", "inbox", "communication"] },
-	{ "category": "objects", "char": "📭", "name": "mailbox_with_no_mail", "keywords": ["email", "inbox"] },
-	{ "category": "objects", "char": "📦", "name": "package", "keywords": ["mail", "gift", "cardboard", "box", "moving"] },
-	{ "category": "objects", "char": "📯", "name": "postal_horn", "keywords": ["instrument", "music"] },
-	{ "category": "objects", "char": "📥", "name": "inbox_tray", "keywords": ["email", "documents"] },
-	{ "category": "objects", "char": "📤", "name": "outbox_tray", "keywords": ["inbox", "email"] },
-	{ "category": "objects", "char": "📜", "name": "scroll", "keywords": ["documents", "ancient", "history", "paper"] },
-	{ "category": "objects", "char": "📃", "name": "page_with_curl", "keywords": ["documents", "office", "paper"] },
-	{ "category": "objects", "char": "📑", "name": "bookmark_tabs", "keywords": ["favorite", "save", "order", "tidy"] },
-	{ "category": "objects", "char": "🧾", "name": "receipt", "keywords": ["accounting", "expenses"] },
-	{ "category": "objects", "char": "📊", "name": "bar_chart", "keywords": ["graph", "presentation", "stats"] },
-	{ "category": "objects", "char": "📈", "name": "chart_with_upwards_trend", "keywords": ["graph", "presentation", "stats", "recovery", "business", "economics", "money", "sales", "good", "success"] },
-	{ "category": "objects", "char": "📉", "name": "chart_with_downwards_trend", "keywords": ["graph", "presentation", "stats", "recession", "business", "economics", "money", "sales", "bad", "failure"] },
-	{ "category": "objects", "char": "📄", "name": "page_facing_up", "keywords": ["documents", "office", "paper", "information"] },
-	{ "category": "objects", "char": "📅", "name": "date", "keywords": ["calendar", "schedule"] },
-	{ "category": "objects", "char": "📆", "name": "calendar", "keywords": ["schedule", "date", "planning"] },
-	{ "category": "objects", "char": "🗓", "name": "spiral_calendar", "keywords": ["date", "schedule", "planning"] },
-	{ "category": "objects", "char": "📇", "name": "card_index", "keywords": ["business", "stationery"] },
-	{ "category": "objects", "char": "🗃", "name": "card_file_box", "keywords": ["business", "stationery"] },
-	{ "category": "objects", "char": "🗳", "name": "ballot_box", "keywords": ["election", "vote"] },
-	{ "category": "objects", "char": "🗄", "name": "file_cabinet", "keywords": ["filing", "organizing"] },
-	{ "category": "objects", "char": "📋", "name": "clipboard", "keywords": ["stationery", "documents"] },
-	{ "category": "objects", "char": "🗒", "name": "spiral_notepad", "keywords": ["memo", "stationery"] },
-	{ "category": "objects", "char": "📁", "name": "file_folder", "keywords": ["documents", "business", "office"] },
-	{ "category": "objects", "char": "📂", "name": "open_file_folder", "keywords": ["documents", "load"] },
-	{ "category": "objects", "char": "🗂", "name": "card_index_dividers", "keywords": ["organizing", "business", "stationery"] },
-	{ "category": "objects", "char": "🗞", "name": "newspaper_roll", "keywords": ["press", "headline"] },
-	{ "category": "objects", "char": "📰", "name": "newspaper", "keywords": ["press", "headline"] },
-	{ "category": "objects", "char": "📓", "name": "notebook", "keywords": ["stationery", "record", "notes", "paper", "study"] },
-	{ "category": "objects", "char": "📕", "name": "closed_book", "keywords": ["read", "library", "knowledge", "textbook", "learn"] },
-	{ "category": "objects", "char": "📗", "name": "green_book", "keywords": ["read", "library", "knowledge", "study"] },
-	{ "category": "objects", "char": "📘", "name": "blue_book", "keywords": ["read", "library", "knowledge", "learn", "study"] },
-	{ "category": "objects", "char": "📙", "name": "orange_book", "keywords": ["read", "library", "knowledge", "textbook", "study"] },
-	{ "category": "objects", "char": "📔", "name": "notebook_with_decorative_cover", "keywords": ["classroom", "notes", "record", "paper", "study"] },
-	{ "category": "objects", "char": "📒", "name": "ledger", "keywords": ["notes", "paper"] },
-	{ "category": "objects", "char": "📚", "name": "books", "keywords": ["literature", "library", "study"] },
-	{ "category": "objects", "char": "📖", "name": "open_book", "keywords": ["book", "read", "library", "knowledge", "literature", "learn", "study"] },
-	{ "category": "objects", "char": "🧷", "name": "safety_pin", "keywords": ["diaper"] },
-	{ "category": "objects", "char": "🔗", "name": "link", "keywords": ["rings", "url"] },
-	{ "category": "objects", "char": "📎", "name": "paperclip", "keywords": ["documents", "stationery"] },
-	{ "category": "objects", "char": "🖇", "name": "paperclips", "keywords": ["documents", "stationery"] },
-	{ "category": "objects", "char": "✂️", "name": "scissors", "keywords": ["stationery", "cut"] },
-	{ "category": "objects", "char": "📐", "name": "triangular_ruler", "keywords": ["stationery", "math", "architect", "sketch"] },
-	{ "category": "objects", "char": "📏", "name": "straight_ruler", "keywords": ["stationery", "calculate", "length", "math", "school", "drawing", "architect", "sketch"] },
-	{ "category": "objects", "char": "🧮", "name": "abacus", "keywords": ["calculation"] },
-	{ "category": "objects", "char": "📌", "name": "pushpin", "keywords": ["stationery", "mark", "here"] },
-	{ "category": "objects", "char": "📍", "name": "round_pushpin", "keywords": ["stationery", "location", "map", "here"] },
-	{ "category": "objects", "char": "🚩", "name": "triangular_flag_on_post", "keywords": ["mark", "milestone", "place"] },
-	{ "category": "objects", "char": "🏳", "name": "white_flag", "keywords": ["losing", "loser", "lost", "surrender", "give up", "fail"] },
-	{ "category": "objects", "char": "🏴", "name": "black_flag", "keywords": ["pirate"] },
-	{ "category": "objects", "char": "🏳️‍🌈", "name": "rainbow_flag", "keywords": ["flag", "rainbow", "pride", "gay", "lgbt", "glbt", "queer", "homosexual", "lesbian", "bisexual", "transgender"] },
-	{ "category": "objects", "char": "🏳️‍⚧️", "name": "transgender_flag", "keywords": ["flag", "transgender"] },
-	{ "category": "objects", "char": "🔐", "name": "closed_lock_with_key", "keywords": ["security", "privacy"] },
-	{ "category": "objects", "char": "🔒", "name": "lock", "keywords": ["security", "password", "padlock"] },
-	{ "category": "objects", "char": "🔓", "name": "unlock", "keywords": ["privacy", "security"] },
-	{ "category": "objects", "char": "🔏", "name": "lock_with_ink_pen", "keywords": ["security", "secret"] },
-	{ "category": "objects", "char": "🖊", "name": "pen", "keywords": ["stationery", "writing", "write"] },
-	{ "category": "objects", "char": "🖋", "name": "fountain_pen", "keywords": ["stationery", "writing", "write"] },
-	{ "category": "objects", "char": "✒️", "name": "black_nib", "keywords": ["pen", "stationery", "writing", "write"] },
-	{ "category": "objects", "char": "📝", "name": "memo", "keywords": ["write", "documents", "stationery", "pencil", "paper", "writing", "legal", "exam", "quiz", "test", "study", "compose"] },
-	{ "category": "objects", "char": "✏️", "name": "pencil2", "keywords": ["stationery", "write", "paper", "writing", "school", "study"] },
-	{ "category": "objects", "char": "🖍", "name": "crayon", "keywords": ["drawing", "creativity"] },
-	{ "category": "objects", "char": "🖌", "name": "paintbrush", "keywords": ["drawing", "creativity", "art"] },
-	{ "category": "objects", "char": "🔍", "name": "mag", "keywords": ["search", "zoom", "find", "detective"] },
-	{ "category": "objects", "char": "🔎", "name": "mag_right", "keywords": ["search", "zoom", "find", "detective"] },
-	{ "category": "objects", "char": "🪦", "name": "headstone", "keywords": [] },
-	{ "category": "objects", "char": "🪧", "name": "placard", "keywords": [] },
-	{ "category": "symbols", "char": "💯", "name": "100", "keywords": ["score", "perfect", "numbers", "century", "exam", "quiz", "test", "pass", "hundred"] },
-	{ "category": "symbols", "char": "🔢", "name": "1234", "keywords": ["numbers", "blue-square"] },
-	{ "category": "symbols", "char": "❤️", "name": "heart", "keywords": ["love", "like", "affection", "valentines"] },
-	{ "category": "symbols", "char": "🧡", "name": "orange_heart", "keywords": ["love", "like", "affection", "valentines"] },
-	{ "category": "symbols", "char": "💛", "name": "yellow_heart", "keywords": ["love", "like", "affection", "valentines"] },
-	{ "category": "symbols", "char": "💚", "name": "green_heart", "keywords": ["love", "like", "affection", "valentines"] },
-	{ "category": "symbols", "char": "💙", "name": "blue_heart", "keywords": ["love", "like", "affection", "valentines"] },
-	{ "category": "symbols", "char": "💜", "name": "purple_heart", "keywords": ["love", "like", "affection", "valentines"] },
-	{ "category": "symbols", "char": "🤎", "name": "brown_heart", "keywords": ["love", "like", "affection", "valentines"] },
-	{ "category": "symbols", "char": "🖤", "name": "black_heart", "keywords": ["love", "like", "affection", "valentines"] },
-	{ "category": "symbols", "char": "🤍", "name": "white_heart", "keywords": ["love", "like", "affection", "valentines"] },
-	{ "category": "symbols", "char": "💔", "name": "broken_heart", "keywords": ["sad", "sorry", "break", "heart", "heartbreak"] },
-	{ "category": "symbols", "char": "❣", "name": "heavy_heart_exclamation", "keywords": ["decoration", "love"] },
-	{ "category": "symbols", "char": "💕", "name": "two_hearts", "keywords": ["love", "like", "affection", "valentines", "heart"] },
-	{ "category": "symbols", "char": "💞", "name": "revolving_hearts", "keywords": ["love", "like", "affection", "valentines"] },
-	{ "category": "symbols", "char": "💓", "name": "heartbeat", "keywords": ["love", "like", "affection", "valentines", "pink", "heart"] },
-	{ "category": "symbols", "char": "💗", "name": "heartpulse", "keywords": ["like", "love", "affection", "valentines", "pink"] },
-	{ "category": "symbols", "char": "💖", "name": "sparkling_heart", "keywords": ["love", "like", "affection", "valentines"] },
-	{ "category": "symbols", "char": "💘", "name": "cupid", "keywords": ["love", "like", "heart", "affection", "valentines"] },
-	{ "category": "symbols", "char": "💝", "name": "gift_heart", "keywords": ["love", "valentines"] },
-	{ "category": "symbols", "char": "💟", "name": "heart_decoration", "keywords": ["purple-square", "love", "like"] },
-	{ "category": "symbols", "char": "\u2764\uFE0F\u200D\uD83D\uDD25", "name": "heart_on_fire", "keywords": [] },
-	{ "category": "symbols", "char": "\u2764\uFE0F\u200D\uD83E\uDE79", "name": "mending_heart", "keywords": [] },
-	{ "category": "symbols", "char": "☮", "name": "peace_symbol", "keywords": ["hippie"] },
-	{ "category": "symbols", "char": "✝", "name": "latin_cross", "keywords": ["christianity"] },
-	{ "category": "symbols", "char": "☪", "name": "star_and_crescent", "keywords": ["islam"] },
-	{ "category": "symbols", "char": "🕉", "name": "om", "keywords": ["hinduism", "buddhism", "sikhism", "jainism"] },
-	{ "category": "symbols", "char": "☸", "name": "wheel_of_dharma", "keywords": ["hinduism", "buddhism", "sikhism", "jainism"] },
-	{ "category": "symbols", "char": "✡", "name": "star_of_david", "keywords": ["judaism"] },
-	{ "category": "symbols", "char": "🔯", "name": "six_pointed_star", "keywords": ["purple-square", "religion", "jewish", "hexagram"] },
-	{ "category": "symbols", "char": "🕎", "name": "menorah", "keywords": ["hanukkah", "candles", "jewish"] },
-	{ "category": "symbols", "char": "☯", "name": "yin_yang", "keywords": ["balance"] },
-	{ "category": "symbols", "char": "☦", "name": "orthodox_cross", "keywords": ["suppedaneum", "religion"] },
-	{ "category": "symbols", "char": "🛐", "name": "place_of_worship", "keywords": ["religion", "church", "temple", "prayer"] },
-	{ "category": "symbols", "char": "⛎", "name": "ophiuchus", "keywords": ["sign", "purple-square", "constellation", "astrology"] },
-	{ "category": "symbols", "char": "♈", "name": "aries", "keywords": ["sign", "purple-square", "zodiac", "astrology"] },
-	{ "category": "symbols", "char": "♉", "name": "taurus", "keywords": ["purple-square", "sign", "zodiac", "astrology"] },
-	{ "category": "symbols", "char": "♊", "name": "gemini", "keywords": ["sign", "zodiac", "purple-square", "astrology"] },
-	{ "category": "symbols", "char": "♋", "name": "cancer", "keywords": ["sign", "zodiac", "purple-square", "astrology"] },
-	{ "category": "symbols", "char": "♌", "name": "leo", "keywords": ["sign", "purple-square", "zodiac", "astrology"] },
-	{ "category": "symbols", "char": "♍", "name": "virgo", "keywords": ["sign", "zodiac", "purple-square", "astrology"] },
-	{ "category": "symbols", "char": "♎", "name": "libra", "keywords": ["sign", "purple-square", "zodiac", "astrology"] },
-	{ "category": "symbols", "char": "♏", "name": "scorpius", "keywords": ["sign", "zodiac", "purple-square", "astrology", "scorpio"] },
-	{ "category": "symbols", "char": "♐", "name": "sagittarius", "keywords": ["sign", "zodiac", "purple-square", "astrology"] },
-	{ "category": "symbols", "char": "♑", "name": "capricorn", "keywords": ["sign", "zodiac", "purple-square", "astrology"] },
-	{ "category": "symbols", "char": "♒", "name": "aquarius", "keywords": ["sign", "purple-square", "zodiac", "astrology"] },
-	{ "category": "symbols", "char": "♓", "name": "pisces", "keywords": ["purple-square", "sign", "zodiac", "astrology"] },
-	{ "category": "symbols", "char": "🆔", "name": "id", "keywords": ["purple-square", "words"] },
-	{ "category": "symbols", "char": "⚛", "name": "atom_symbol", "keywords": ["science", "physics", "chemistry"] },
-	{ "category": "symbols", "char": "⚧️", "name": "transgender_symbol", "keywords": ["purple-square", "woman", "female", "toilet", "loo", "restroom", "gender"] },
-	{ "category": "symbols", "char": "🈳", "name": "u7a7a", "keywords": ["kanji", "japanese", "chinese", "empty", "sky", "blue-square", "aki"] },
-	{ "category": "symbols", "char": "🈹", "name": "u5272", "keywords": ["cut", "divide", "chinese", "kanji", "pink-square", "waribiki"] },
-	{ "category": "symbols", "char": "☢", "name": "radioactive", "keywords": ["nuclear", "danger"] },
-	{ "category": "symbols", "char": "☣", "name": "biohazard", "keywords": ["danger"] },
-	{ "category": "symbols", "char": "📴", "name": "mobile_phone_off", "keywords": ["mute", "orange-square", "silence", "quiet"] },
-	{ "category": "symbols", "char": "📳", "name": "vibration_mode", "keywords": ["orange-square", "phone"] },
-	{ "category": "symbols", "char": "🈶", "name": "u6709", "keywords": ["orange-square", "chinese", "have", "kanji", "ari"] },
-	{ "category": "symbols", "char": "🈚", "name": "u7121", "keywords": ["nothing", "chinese", "kanji", "japanese", "orange-square", "nashi"] },
-	{ "category": "symbols", "char": "🈸", "name": "u7533", "keywords": ["chinese", "japanese", "kanji", "orange-square", "moushikomi"] },
-	{ "category": "symbols", "char": "🈺", "name": "u55b6", "keywords": ["japanese", "opening hours", "orange-square", "eigyo"] },
-	{ "category": "symbols", "char": "🈷️", "name": "u6708", "keywords": ["chinese", "month", "moon", "japanese", "orange-square", "kanji", "tsuki", "tsukigime", "getsugaku"] },
-	{ "category": "symbols", "char": "✴️", "name": "eight_pointed_black_star", "keywords": ["orange-square", "shape", "polygon"] },
-	{ "category": "symbols", "char": "🆚", "name": "vs", "keywords": ["words", "orange-square"] },
-	{ "category": "symbols", "char": "🉑", "name": "accept", "keywords": ["ok", "good", "chinese", "kanji", "agree", "yes", "orange-circle"] },
-	{ "category": "symbols", "char": "💮", "name": "white_flower", "keywords": ["japanese", "spring"] },
-	{ "category": "symbols", "char": "🉐", "name": "ideograph_advantage", "keywords": ["chinese", "kanji", "obtain", "get", "circle"] },
-	{ "category": "symbols", "char": "㊙️", "name": "secret", "keywords": ["privacy", "chinese", "sshh", "kanji", "red-circle"] },
-	{ "category": "symbols", "char": "㊗️", "name": "congratulations", "keywords": ["chinese", "kanji", "japanese", "red-circle"] },
-	{ "category": "symbols", "char": "🈴", "name": "u5408", "keywords": ["japanese", "chinese", "join", "kanji", "red-square", "goukaku", "pass"] },
-	{ "category": "symbols", "char": "🈵", "name": "u6e80", "keywords": ["full", "chinese", "japanese", "red-square", "kanji", "man"] },
-	{ "category": "symbols", "char": "🈲", "name": "u7981", "keywords": ["kanji", "japanese", "chinese", "forbidden", "limit", "restricted", "red-square", "kinshi"] },
-	{ "category": "symbols", "char": "🅰️", "name": "a", "keywords": ["red-square", "alphabet", "letter"] },
-	{ "category": "symbols", "char": "🅱️", "name": "b", "keywords": ["red-square", "alphabet", "letter"] },
-	{ "category": "symbols", "char": "🆎", "name": "ab", "keywords": ["red-square", "alphabet"] },
-	{ "category": "symbols", "char": "🆑", "name": "cl", "keywords": ["alphabet", "words", "red-square"] },
-	{ "category": "symbols", "char": "🅾️", "name": "o2", "keywords": ["alphabet", "red-square", "letter"] },
-	{ "category": "symbols", "char": "🆘", "name": "sos", "keywords": ["help", "red-square", "words", "emergency", "911"] },
-	{ "category": "symbols", "char": "⛔", "name": "no_entry", "keywords": ["limit", "security", "privacy", "bad", "denied", "stop", "circle"] },
-	{ "category": "symbols", "char": "📛", "name": "name_badge", "keywords": ["fire", "forbid"] },
-	{ "category": "symbols", "char": "🚫", "name": "no_entry_sign", "keywords": ["forbid", "stop", "limit", "denied", "disallow", "circle"] },
-	{ "category": "symbols", "char": "❌", "name": "x", "keywords": ["no", "delete", "remove", "cancel", "red"] },
-	{ "category": "symbols", "char": "⭕", "name": "o", "keywords": ["circle", "round"] },
-	{ "category": "symbols", "char": "🛑", "name": "stop_sign", "keywords": ["stop"] },
-	{ "category": "symbols", "char": "💢", "name": "anger", "keywords": ["angry", "mad"] },
-	{ "category": "symbols", "char": "♨️", "name": "hotsprings", "keywords": ["bath", "warm", "relax"] },
-	{ "category": "symbols", "char": "🚷", "name": "no_pedestrians", "keywords": ["rules", "crossing", "walking", "circle"] },
-	{ "category": "symbols", "char": "🚯", "name": "do_not_litter", "keywords": ["trash", "bin", "garbage", "circle"] },
-	{ "category": "symbols", "char": "🚳", "name": "no_bicycles", "keywords": ["cyclist", "prohibited", "circle"] },
-	{ "category": "symbols", "char": "🚱", "name": "non-potable_water", "keywords": ["drink", "faucet", "tap", "circle"] },
-	{ "category": "symbols", "char": "🔞", "name": "underage", "keywords": ["18", "drink", "pub", "night", "minor", "circle"] },
-	{ "category": "symbols", "char": "📵", "name": "no_mobile_phones", "keywords": ["iphone", "mute", "circle"] },
-	{ "category": "symbols", "char": "❗", "name": "exclamation", "keywords": ["heavy_exclamation_mark", "danger", "surprise", "punctuation", "wow", "warning"] },
-	{ "category": "symbols", "char": "❕", "name": "grey_exclamation", "keywords": ["surprise", "punctuation", "gray", "wow", "warning"] },
-	{ "category": "symbols", "char": "❓", "name": "question", "keywords": ["doubt", "confused"] },
-	{ "category": "symbols", "char": "❔", "name": "grey_question", "keywords": ["doubts", "gray", "huh", "confused"] },
-	{ "category": "symbols", "char": "‼️", "name": "bangbang", "keywords": ["exclamation", "surprise"] },
-	{ "category": "symbols", "char": "⁉️", "name": "interrobang", "keywords": ["wat", "punctuation", "surprise"] },
-	{ "category": "symbols", "char": "🔅", "name": "low_brightness", "keywords": ["sun", "afternoon", "warm", "summer"] },
-	{ "category": "symbols", "char": "🔆", "name": "high_brightness", "keywords": ["sun", "light"] },
-	{ "category": "symbols", "char": "🔱", "name": "trident", "keywords": ["weapon", "spear"] },
-	{ "category": "symbols", "char": "⚜", "name": "fleur_de_lis", "keywords": ["decorative", "scout"] },
-	{ "category": "symbols", "char": "〽️", "name": "part_alternation_mark", "keywords": ["graph", "presentation", "stats", "business", "economics", "bad"] },
-	{ "category": "symbols", "char": "⚠️", "name": "warning", "keywords": ["exclamation", "wip", "alert", "error", "problem", "issue"] },
-	{ "category": "symbols", "char": "🚸", "name": "children_crossing", "keywords": ["school", "warning", "danger", "sign", "driving", "yellow-diamond"] },
-	{ "category": "symbols", "char": "🔰", "name": "beginner", "keywords": ["badge", "shield"] },
-	{ "category": "symbols", "char": "♻️", "name": "recycle", "keywords": ["arrow", "environment", "garbage", "trash"] },
-	{ "category": "symbols", "char": "🈯", "name": "u6307", "keywords": ["chinese", "point", "green-square", "kanji", "reserved", "shiteiseki"] },
-	{ "category": "symbols", "char": "💹", "name": "chart", "keywords": ["green-square", "graph", "presentation", "stats"] },
-	{ "category": "symbols", "char": "❇️", "name": "sparkle", "keywords": ["stars", "green-square", "awesome", "good", "fireworks"] },
-	{ "category": "symbols", "char": "✳️", "name": "eight_spoked_asterisk", "keywords": ["star", "sparkle", "green-square"] },
-	{ "category": "symbols", "char": "❎", "name": "negative_squared_cross_mark", "keywords": ["x", "green-square", "no", "deny"] },
-	{ "category": "symbols", "char": "✅", "name": "white_check_mark", "keywords": ["green-square", "ok", "agree", "vote", "election", "answer", "tick"] },
-	{ "category": "symbols", "char": "💠", "name": "diamond_shape_with_a_dot_inside", "keywords": ["jewel", "blue", "gem", "crystal", "fancy"] },
-	{ "category": "symbols", "char": "🌀", "name": "cyclone", "keywords": ["weather", "swirl", "blue", "cloud", "vortex", "spiral", "whirlpool", "spin", "tornado", "hurricane", "typhoon"] },
-	{ "category": "symbols", "char": "➿", "name": "loop", "keywords": ["tape", "cassette"] },
-	{ "category": "symbols", "char": "🌐", "name": "globe_with_meridians", "keywords": ["earth", "international", "world", "internet", "interweb", "i18n"] },
-	{ "category": "symbols", "char": "Ⓜ️", "name": "m", "keywords": ["alphabet", "blue-circle", "letter"] },
-	{ "category": "symbols", "char": "🏧", "name": "atm", "keywords": ["money", "sales", "cash", "blue-square", "payment", "bank"] },
-	{ "category": "symbols", "char": "🈂️", "name": "sa", "keywords": ["japanese", "blue-square", "katakana"] },
-	{ "category": "symbols", "char": "🛂", "name": "passport_control", "keywords": ["custom", "blue-square"] },
-	{ "category": "symbols", "char": "🛃", "name": "customs", "keywords": ["passport", "border", "blue-square"] },
-	{ "category": "symbols", "char": "🛄", "name": "baggage_claim", "keywords": ["blue-square", "airport", "transport"] },
-	{ "category": "symbols", "char": "🛅", "name": "left_luggage", "keywords": ["blue-square", "travel"] },
-	{ "category": "symbols", "char": "♿", "name": "wheelchair", "keywords": ["blue-square", "disabled", "a11y", "accessibility"] },
-	{ "category": "symbols", "char": "🚭", "name": "no_smoking", "keywords": ["cigarette", "blue-square", "smell", "smoke"] },
-	{ "category": "symbols", "char": "🚾", "name": "wc", "keywords": ["toilet", "restroom", "blue-square"] },
-	{ "category": "symbols", "char": "🅿️", "name": "parking", "keywords": ["cars", "blue-square", "alphabet", "letter"] },
-	{ "category": "symbols", "char": "🚰", "name": "potable_water", "keywords": ["blue-square", "liquid", "restroom", "cleaning", "faucet"] },
-	{ "category": "symbols", "char": "🚹", "name": "mens", "keywords": ["toilet", "restroom", "wc", "blue-square", "gender", "male"] },
-	{ "category": "symbols", "char": "🚺", "name": "womens", "keywords": ["purple-square", "woman", "female", "toilet", "loo", "restroom", "gender"] },
-	{ "category": "symbols", "char": "🚼", "name": "baby_symbol", "keywords": ["orange-square", "child"] },
-	{ "category": "symbols", "char": "🚻", "name": "restroom", "keywords": ["blue-square", "toilet", "refresh", "wc", "gender"] },
-	{ "category": "symbols", "char": "🚮", "name": "put_litter_in_its_place", "keywords": ["blue-square", "sign", "human", "info"] },
-	{ "category": "symbols", "char": "🎦", "name": "cinema", "keywords": ["blue-square", "record", "film", "movie", "curtain", "stage", "theater"] },
-	{ "category": "symbols", "char": "📶", "name": "signal_strength", "keywords": ["blue-square", "reception", "phone", "internet", "connection", "wifi", "bluetooth", "bars"] },
-	{ "category": "symbols", "char": "🈁", "name": "koko", "keywords": ["blue-square", "here", "katakana", "japanese", "destination"] },
-	{ "category": "symbols", "char": "🆖", "name": "ng", "keywords": ["blue-square", "words", "shape", "icon"] },
-	{ "category": "symbols", "char": "🆗", "name": "ok", "keywords": ["good", "agree", "yes", "blue-square"] },
-	{ "category": "symbols", "char": "🆙", "name": "up", "keywords": ["blue-square", "above", "high"] },
-	{ "category": "symbols", "char": "🆒", "name": "cool", "keywords": ["words", "blue-square"] },
-	{ "category": "symbols", "char": "🆕", "name": "new", "keywords": ["blue-square", "words", "start"] },
-	{ "category": "symbols", "char": "🆓", "name": "free", "keywords": ["blue-square", "words"] },
-	{ "category": "symbols", "char": "0️⃣", "name": "zero", "keywords": ["0", "numbers", "blue-square", "null"] },
-	{ "category": "symbols", "char": "1️⃣", "name": "one", "keywords": ["blue-square", "numbers", "1"] },
-	{ "category": "symbols", "char": "2️⃣", "name": "two", "keywords": ["numbers", "2", "prime", "blue-square"] },
-	{ "category": "symbols", "char": "3️⃣", "name": "three", "keywords": ["3", "numbers", "prime", "blue-square"] },
-	{ "category": "symbols", "char": "4️⃣", "name": "four", "keywords": ["4", "numbers", "blue-square"] },
-	{ "category": "symbols", "char": "5️⃣", "name": "five", "keywords": ["5", "numbers", "blue-square", "prime"] },
-	{ "category": "symbols", "char": "6️⃣", "name": "six", "keywords": ["6", "numbers", "blue-square"] },
-	{ "category": "symbols", "char": "7️⃣", "name": "seven", "keywords": ["7", "numbers", "blue-square", "prime"] },
-	{ "category": "symbols", "char": "8️⃣", "name": "eight", "keywords": ["8", "blue-square", "numbers"] },
-	{ "category": "symbols", "char": "9️⃣", "name": "nine", "keywords": ["blue-square", "numbers", "9"] },
-	{ "category": "symbols", "char": "🔟", "name": "keycap_ten", "keywords": ["numbers", "10", "blue-square"] },
-	{ "category": "symbols", "char": "*⃣", "name": "asterisk", "keywords": ["star", "keycap"] },
-	{ "category": "symbols", "char": "⏏️", "name": "eject_button", "keywords": ["blue-square"] },
-	{ "category": "symbols", "char": "▶️", "name": "arrow_forward", "keywords": ["blue-square", "right", "direction", "play"] },
-	{ "category": "symbols", "char": "⏸", "name": "pause_button", "keywords": ["pause", "blue-square"] },
-	{ "category": "symbols", "char": "⏭", "name": "next_track_button", "keywords": ["forward", "next", "blue-square"] },
-	{ "category": "symbols", "char": "⏹", "name": "stop_button", "keywords": ["blue-square"] },
-	{ "category": "symbols", "char": "⏺", "name": "record_button", "keywords": ["blue-square"] },
-	{ "category": "symbols", "char": "⏯", "name": "play_or_pause_button", "keywords": ["blue-square", "play", "pause"] },
-	{ "category": "symbols", "char": "⏮", "name": "previous_track_button", "keywords": ["backward"] },
-	{ "category": "symbols", "char": "⏩", "name": "fast_forward", "keywords": ["blue-square", "play", "speed", "continue"] },
-	{ "category": "symbols", "char": "⏪", "name": "rewind", "keywords": ["play", "blue-square"] },
-	{ "category": "symbols", "char": "🔀", "name": "twisted_rightwards_arrows", "keywords": ["blue-square", "shuffle", "music", "random"] },
-	{ "category": "symbols", "char": "🔁", "name": "repeat", "keywords": ["loop", "record"] },
-	{ "category": "symbols", "char": "🔂", "name": "repeat_one", "keywords": ["blue-square", "loop"] },
-	{ "category": "symbols", "char": "◀️", "name": "arrow_backward", "keywords": ["blue-square", "left", "direction"] },
-	{ "category": "symbols", "char": "🔼", "name": "arrow_up_small", "keywords": ["blue-square", "triangle", "direction", "point", "forward", "top"] },
-	{ "category": "symbols", "char": "🔽", "name": "arrow_down_small", "keywords": ["blue-square", "direction", "bottom"] },
-	{ "category": "symbols", "char": "⏫", "name": "arrow_double_up", "keywords": ["blue-square", "direction", "top"] },
-	{ "category": "symbols", "char": "⏬", "name": "arrow_double_down", "keywords": ["blue-square", "direction", "bottom"] },
-	{ "category": "symbols", "char": "➡️", "name": "arrow_right", "keywords": ["blue-square", "next"] },
-	{ "category": "symbols", "char": "⬅️", "name": "arrow_left", "keywords": ["blue-square", "previous", "back"] },
-	{ "category": "symbols", "char": "⬆️", "name": "arrow_up", "keywords": ["blue-square", "continue", "top", "direction"] },
-	{ "category": "symbols", "char": "⬇️", "name": "arrow_down", "keywords": ["blue-square", "direction", "bottom"] },
-	{ "category": "symbols", "char": "↗️", "name": "arrow_upper_right", "keywords": ["blue-square", "point", "direction", "diagonal", "northeast"] },
-	{ "category": "symbols", "char": "↘️", "name": "arrow_lower_right", "keywords": ["blue-square", "direction", "diagonal", "southeast"] },
-	{ "category": "symbols", "char": "↙️", "name": "arrow_lower_left", "keywords": ["blue-square", "direction", "diagonal", "southwest"] },
-	{ "category": "symbols", "char": "↖️", "name": "arrow_upper_left", "keywords": ["blue-square", "point", "direction", "diagonal", "northwest"] },
-	{ "category": "symbols", "char": "↕️", "name": "arrow_up_down", "keywords": ["blue-square", "direction", "way", "vertical"] },
-	{ "category": "symbols", "char": "↔️", "name": "left_right_arrow", "keywords": ["shape", "direction", "horizontal", "sideways"] },
-	{ "category": "symbols", "char": "🔄", "name": "arrows_counterclockwise", "keywords": ["blue-square", "sync", "cycle"] },
-	{ "category": "symbols", "char": "↪️", "name": "arrow_right_hook", "keywords": ["blue-square", "return", "rotate", "direction"] },
-	{ "category": "symbols", "char": "↩️", "name": "leftwards_arrow_with_hook", "keywords": ["back", "return", "blue-square", "undo", "enter"] },
-	{ "category": "symbols", "char": "⤴️", "name": "arrow_heading_up", "keywords": ["blue-square", "direction", "top"] },
-	{ "category": "symbols", "char": "⤵️", "name": "arrow_heading_down", "keywords": ["blue-square", "direction", "bottom"] },
-	{ "category": "symbols", "char": "#️⃣", "name": "hash", "keywords": ["symbol", "blue-square", "twitter"] },
-	{ "category": "symbols", "char": "ℹ️", "name": "information_source", "keywords": ["blue-square", "alphabet", "letter"] },
-	{ "category": "symbols", "char": "🔤", "name": "abc", "keywords": ["blue-square", "alphabet"] },
-	{ "category": "symbols", "char": "🔡", "name": "abcd", "keywords": ["blue-square", "alphabet"] },
-	{ "category": "symbols", "char": "🔠", "name": "capital_abcd", "keywords": ["alphabet", "words", "blue-square"] },
-	{ "category": "symbols", "char": "🔣", "name": "symbols", "keywords": ["blue-square", "music", "note", "ampersand", "percent", "glyphs", "characters"] },
-	{ "category": "symbols", "char": "🎵", "name": "musical_note", "keywords": ["score", "tone", "sound"] },
-	{ "category": "symbols", "char": "🎶", "name": "notes", "keywords": ["music", "score"] },
-	{ "category": "symbols", "char": "〰️", "name": "wavy_dash", "keywords": ["draw", "line", "moustache", "mustache", "squiggle", "scribble"] },
-	{ "category": "symbols", "char": "➰", "name": "curly_loop", "keywords": ["scribble", "draw", "shape", "squiggle"] },
-	{ "category": "symbols", "char": "✔️", "name": "heavy_check_mark", "keywords": ["ok", "nike", "answer", "yes", "tick"] },
-	{ "category": "symbols", "char": "🔃", "name": "arrows_clockwise", "keywords": ["sync", "cycle", "round", "repeat"] },
-	{ "category": "symbols", "char": "➕", "name": "heavy_plus_sign", "keywords": ["math", "calculation", "addition", "more", "increase"] },
-	{ "category": "symbols", "char": "➖", "name": "heavy_minus_sign", "keywords": ["math", "calculation", "subtract", "less"] },
-	{ "category": "symbols", "char": "➗", "name": "heavy_division_sign", "keywords": ["divide", "math", "calculation"] },
-	{ "category": "symbols", "char": "✖️", "name": "heavy_multiplication_x", "keywords": ["math", "calculation"] },
-	{ "category": "symbols", "char": "\uD83D\uDFF0", "name": "heavy_equals_sign", "keywords": [] },
-	{ "category": "symbols", "char": "♾", "name": "infinity", "keywords": ["forever"] },
-	{ "category": "symbols", "char": "💲", "name": "heavy_dollar_sign", "keywords": ["money", "sales", "payment", "currency", "buck"] },
-	{ "category": "symbols", "char": "💱", "name": "currency_exchange", "keywords": ["money", "sales", "dollar", "travel"] },
-	{ "category": "symbols", "char": "©️", "name": "copyright", "keywords": ["ip", "license", "circle", "law", "legal"] },
-	{ "category": "symbols", "char": "®️", "name": "registered", "keywords": ["alphabet", "circle"] },
-	{ "category": "symbols", "char": "™️", "name": "tm", "keywords": ["trademark", "brand", "law", "legal"] },
-	{ "category": "symbols", "char": "🔚", "name": "end", "keywords": ["words", "arrow"] },
-	{ "category": "symbols", "char": "🔙", "name": "back", "keywords": ["arrow", "words", "return"] },
-	{ "category": "symbols", "char": "🔛", "name": "on", "keywords": ["arrow", "words"] },
-	{ "category": "symbols", "char": "🔝", "name": "top", "keywords": ["words", "blue-square"] },
-	{ "category": "symbols", "char": "🔜", "name": "soon", "keywords": ["arrow", "words"] },
-	{ "category": "symbols", "char": "☑️", "name": "ballot_box_with_check", "keywords": ["ok", "agree", "confirm", "black-square", "vote", "election", "yes", "tick"] },
-	{ "category": "symbols", "char": "🔘", "name": "radio_button", "keywords": ["input", "old", "music", "circle"] },
-	{ "category": "symbols", "char": "⚫", "name": "black_circle", "keywords": ["shape", "button", "round"] },
-	{ "category": "symbols", "char": "⚪", "name": "white_circle", "keywords": ["shape", "round"] },
-	{ "category": "symbols", "char": "🔴", "name": "red_circle", "keywords": ["shape", "error", "danger"] },
-	{ "category": "symbols", "char": "🟠", "name": "orange_circle", "keywords": ["shape"] },
-	{ "category": "symbols", "char": "🟡", "name": "yellow_circle", "keywords": ["shape"] },
-	{ "category": "symbols", "char": "🟢", "name": "green_circle", "keywords": ["shape"] },
-	{ "category": "symbols", "char": "🔵", "name": "large_blue_circle", "keywords": ["shape", "icon", "button"] },
-	{ "category": "symbols", "char": "🟣", "name": "purple_circle", "keywords": ["shape"] },
-	{ "category": "symbols", "char": "🟤", "name": "brown_circle", "keywords": ["shape"] },
-	{ "category": "symbols", "char": "🔸", "name": "small_orange_diamond", "keywords": ["shape", "jewel", "gem"] },
-	{ "category": "symbols", "char": "🔹", "name": "small_blue_diamond", "keywords": ["shape", "jewel", "gem"] },
-	{ "category": "symbols", "char": "🔶", "name": "large_orange_diamond", "keywords": ["shape", "jewel", "gem"] },
-	{ "category": "symbols", "char": "🔷", "name": "large_blue_diamond", "keywords": ["shape", "jewel", "gem"] },
-	{ "category": "symbols", "char": "🔺", "name": "small_red_triangle", "keywords": ["shape", "direction", "up", "top"] },
-	{ "category": "symbols", "char": "▪️", "name": "black_small_square", "keywords": ["shape", "icon"] },
-	{ "category": "symbols", "char": "▫️", "name": "white_small_square", "keywords": ["shape", "icon"] },
-	{ "category": "symbols", "char": "⬛", "name": "black_large_square", "keywords": ["shape", "icon", "button"] },
-	{ "category": "symbols", "char": "⬜", "name": "white_large_square", "keywords": ["shape", "icon", "stone", "button"] },
-	{ "category": "symbols", "char": "🟥", "name": "red_square", "keywords": ["shape"] },
-	{ "category": "symbols", "char": "🟧", "name": "orange_square", "keywords": ["shape"] },
-	{ "category": "symbols", "char": "🟨", "name": "yellow_square", "keywords": ["shape"] },
-	{ "category": "symbols", "char": "🟩", "name": "green_square", "keywords": ["shape"] },
-	{ "category": "symbols", "char": "🟦", "name": "blue_square", "keywords": ["shape"] },
-	{ "category": "symbols", "char": "🟪", "name": "purple_square", "keywords": ["shape"] },
-	{ "category": "symbols", "char": "🟫", "name": "brown_square", "keywords": ["shape"] },
-	{ "category": "symbols", "char": "🔻", "name": "small_red_triangle_down", "keywords": ["shape", "direction", "bottom"] },
-	{ "category": "symbols", "char": "◼️", "name": "black_medium_square", "keywords": ["shape", "button", "icon"] },
-	{ "category": "symbols", "char": "◻️", "name": "white_medium_square", "keywords": ["shape", "stone", "icon"] },
-	{ "category": "symbols", "char": "◾", "name": "black_medium_small_square", "keywords": ["icon", "shape", "button"] },
-	{ "category": "symbols", "char": "◽", "name": "white_medium_small_square", "keywords": ["shape", "stone", "icon", "button"] },
-	{ "category": "symbols", "char": "🔲", "name": "black_square_button", "keywords": ["shape", "input", "frame"] },
-	{ "category": "symbols", "char": "🔳", "name": "white_square_button", "keywords": ["shape", "input"] },
-	{ "category": "symbols", "char": "🔈", "name": "speaker", "keywords": ["sound", "volume", "silence", "broadcast"] },
-	{ "category": "symbols", "char": "🔉", "name": "sound", "keywords": ["volume", "speaker", "broadcast"] },
-	{ "category": "symbols", "char": "🔊", "name": "loud_sound", "keywords": ["volume", "noise", "noisy", "speaker", "broadcast"] },
-	{ "category": "symbols", "char": "🔇", "name": "mute", "keywords": ["sound", "volume", "silence", "quiet"] },
-	{ "category": "symbols", "char": "📣", "name": "mega", "keywords": ["sound", "speaker", "volume"] },
-	{ "category": "symbols", "char": "📢", "name": "loudspeaker", "keywords": ["volume", "sound"] },
-	{ "category": "symbols", "char": "🔔", "name": "bell", "keywords": ["sound", "notification", "christmas", "xmas", "chime"] },
-	{ "category": "symbols", "char": "🔕", "name": "no_bell", "keywords": ["sound", "volume", "mute", "quiet", "silent"] },
-	{ "category": "symbols", "char": "🃏", "name": "black_joker", "keywords": ["poker", "cards", "game", "play", "magic"] },
-	{ "category": "symbols", "char": "🀄", "name": "mahjong", "keywords": ["game", "play", "chinese", "kanji"] },
-	{ "category": "symbols", "char": "♠️", "name": "spades", "keywords": ["poker", "cards", "suits", "magic"] },
-	{ "category": "symbols", "char": "♣️", "name": "clubs", "keywords": ["poker", "cards", "magic", "suits"] },
-	{ "category": "symbols", "char": "♥️", "name": "hearts", "keywords": ["poker", "cards", "magic", "suits"] },
-	{ "category": "symbols", "char": "♦️", "name": "diamonds", "keywords": ["poker", "cards", "magic", "suits"] },
-	{ "category": "symbols", "char": "🎴", "name": "flower_playing_cards", "keywords": ["game", "sunset", "red"] },
-	{ "category": "symbols", "char": "💭", "name": "thought_balloon", "keywords": ["bubble", "cloud", "speech", "thinking", "dream"] },
-	{ "category": "symbols", "char": "🗯", "name": "right_anger_bubble", "keywords": ["caption", "speech", "thinking", "mad"] },
-	{ "category": "symbols", "char": "💬", "name": "speech_balloon", "keywords": ["bubble", "words", "message", "talk", "chatting"] },
-	{ "category": "symbols", "char": "🗨", "name": "left_speech_bubble", "keywords": ["words", "message", "talk", "chatting"] },
-	{ "category": "symbols", "char": "🕐", "name": "clock1", "keywords": ["time", "late", "early", "schedule"] },
-	{ "category": "symbols", "char": "🕑", "name": "clock2", "keywords": ["time", "late", "early", "schedule"] },
-	{ "category": "symbols", "char": "🕒", "name": "clock3", "keywords": ["time", "late", "early", "schedule"] },
-	{ "category": "symbols", "char": "🕓", "name": "clock4", "keywords": ["time", "late", "early", "schedule"] },
-	{ "category": "symbols", "char": "🕔", "name": "clock5", "keywords": ["time", "late", "early", "schedule"] },
-	{ "category": "symbols", "char": "🕕", "name": "clock6", "keywords": ["time", "late", "early", "schedule", "dawn", "dusk"] },
-	{ "category": "symbols", "char": "🕖", "name": "clock7", "keywords": ["time", "late", "early", "schedule"] },
-	{ "category": "symbols", "char": "🕗", "name": "clock8", "keywords": ["time", "late", "early", "schedule"] },
-	{ "category": "symbols", "char": "🕘", "name": "clock9", "keywords": ["time", "late", "early", "schedule"] },
-	{ "category": "symbols", "char": "🕙", "name": "clock10", "keywords": ["time", "late", "early", "schedule"] },
-	{ "category": "symbols", "char": "🕚", "name": "clock11", "keywords": ["time", "late", "early", "schedule"] },
-	{ "category": "symbols", "char": "🕛", "name": "clock12", "keywords": ["time", "noon", "midnight", "midday", "late", "early", "schedule"] },
-	{ "category": "symbols", "char": "🕜", "name": "clock130", "keywords": ["time", "late", "early", "schedule"] },
-	{ "category": "symbols", "char": "🕝", "name": "clock230", "keywords": ["time", "late", "early", "schedule"] },
-	{ "category": "symbols", "char": "🕞", "name": "clock330", "keywords": ["time", "late", "early", "schedule"] },
-	{ "category": "symbols", "char": "🕟", "name": "clock430", "keywords": ["time", "late", "early", "schedule"] },
-	{ "category": "symbols", "char": "🕠", "name": "clock530", "keywords": ["time", "late", "early", "schedule"] },
-	{ "category": "symbols", "char": "🕡", "name": "clock630", "keywords": ["time", "late", "early", "schedule"] },
-	{ "category": "symbols", "char": "🕢", "name": "clock730", "keywords": ["time", "late", "early", "schedule"] },
-	{ "category": "symbols", "char": "🕣", "name": "clock830", "keywords": ["time", "late", "early", "schedule"] },
-	{ "category": "symbols", "char": "🕤", "name": "clock930", "keywords": ["time", "late", "early", "schedule"] },
-	{ "category": "symbols", "char": "🕥", "name": "clock1030", "keywords": ["time", "late", "early", "schedule"] },
-	{ "category": "symbols", "char": "🕦", "name": "clock1130", "keywords": ["time", "late", "early", "schedule"] },
-	{ "category": "symbols", "char": "🕧", "name": "clock1230", "keywords": ["time", "late", "early", "schedule"] },
-	{ "category": "flags", "char": "🇦🇫", "name": "afghanistan", "keywords": ["af", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇦🇽", "name": "aland_islands", "keywords": ["Åland", "islands", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇦🇱", "name": "albania", "keywords": ["al", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇩🇿", "name": "algeria", "keywords": ["dz", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇦🇸", "name": "american_samoa", "keywords": ["american", "ws", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇦🇩", "name": "andorra", "keywords": ["ad", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇦🇴", "name": "angola", "keywords": ["ao", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇦🇮", "name": "anguilla", "keywords": ["ai", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇦🇶", "name": "antarctica", "keywords": ["aq", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇦🇬", "name": "antigua_barbuda", "keywords": ["antigua", "barbuda", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇦🇷", "name": "argentina", "keywords": ["ar", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇦🇲", "name": "armenia", "keywords": ["am", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇦🇼", "name": "aruba", "keywords": ["aw", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇦🇨", "name": "ascension_island", "keywords": ["flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇦🇺", "name": "australia", "keywords": ["au", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇦🇹", "name": "austria", "keywords": ["at", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇦🇿", "name": "azerbaijan", "keywords": ["az", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇧🇸", "name": "bahamas", "keywords": ["bs", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇧🇭", "name": "bahrain", "keywords": ["bh", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇧🇩", "name": "bangladesh", "keywords": ["bd", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇧🇧", "name": "barbados", "keywords": ["bb", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇧🇾", "name": "belarus", "keywords": ["by", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇧🇪", "name": "belgium", "keywords": ["be", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇧🇿", "name": "belize", "keywords": ["bz", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇧🇯", "name": "benin", "keywords": ["bj", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇧🇲", "name": "bermuda", "keywords": ["bm", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇧🇹", "name": "bhutan", "keywords": ["bt", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇧🇴", "name": "bolivia", "keywords": ["bo", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇧🇶", "name": "caribbean_netherlands", "keywords": ["bonaire", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇧🇦", "name": "bosnia_herzegovina", "keywords": ["bosnia", "herzegovina", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇧🇼", "name": "botswana", "keywords": ["bw", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇧🇷", "name": "brazil", "keywords": ["br", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇮🇴", "name": "british_indian_ocean_territory", "keywords": ["british", "indian", "ocean", "territory", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇻🇬", "name": "british_virgin_islands", "keywords": ["british", "virgin", "islands", "bvi", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇧🇳", "name": "brunei", "keywords": ["bn", "darussalam", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇧🇬", "name": "bulgaria", "keywords": ["bg", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇧🇫", "name": "burkina_faso", "keywords": ["burkina", "faso", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇧🇮", "name": "burundi", "keywords": ["bi", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇨🇻", "name": "cape_verde", "keywords": ["cabo", "verde", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇰🇭", "name": "cambodia", "keywords": ["kh", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇨🇲", "name": "cameroon", "keywords": ["cm", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇨🇦", "name": "canada", "keywords": ["ca", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇮🇨", "name": "canary_islands", "keywords": ["canary", "islands", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇰🇾", "name": "cayman_islands", "keywords": ["cayman", "islands", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇨🇫", "name": "central_african_republic", "keywords": ["central", "african", "republic", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇹🇩", "name": "chad", "keywords": ["td", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇨🇱", "name": "chile", "keywords": ["flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇨🇳", "name": "cn", "keywords": ["china", "chinese", "prc", "flag", "country", "nation", "banner"] },
-	{ "category": "flags", "char": "🇨🇽", "name": "christmas_island", "keywords": ["christmas", "island", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇨🇨", "name": "cocos_islands", "keywords": ["cocos", "keeling", "islands", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇨🇴", "name": "colombia", "keywords": ["co", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇰🇲", "name": "comoros", "keywords": ["km", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇨🇬", "name": "congo_brazzaville", "keywords": ["congo", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇨🇩", "name": "congo_kinshasa", "keywords": ["congo", "democratic", "republic", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇨🇰", "name": "cook_islands", "keywords": ["cook", "islands", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇨🇷", "name": "costa_rica", "keywords": ["costa", "rica", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇭🇷", "name": "croatia", "keywords": ["hr", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇨🇺", "name": "cuba", "keywords": ["cu", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇨🇼", "name": "curacao", "keywords": ["curaçao", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇨🇾", "name": "cyprus", "keywords": ["cy", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇨🇿", "name": "czech_republic", "keywords": ["cz", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇩🇰", "name": "denmark", "keywords": ["dk", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇩🇯", "name": "djibouti", "keywords": ["dj", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇩🇲", "name": "dominica", "keywords": ["dm", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇩🇴", "name": "dominican_republic", "keywords": ["dominican", "republic", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇪🇨", "name": "ecuador", "keywords": ["ec", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇪🇬", "name": "egypt", "keywords": ["eg", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇸🇻", "name": "el_salvador", "keywords": ["el", "salvador", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇬🇶", "name": "equatorial_guinea", "keywords": ["equatorial", "gn", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇪🇷", "name": "eritrea", "keywords": ["er", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇪🇪", "name": "estonia", "keywords": ["ee", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇪🇹", "name": "ethiopia", "keywords": ["et", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇪🇺", "name": "eu", "keywords": ["european", "union", "flag", "banner"] },
-	{ "category": "flags", "char": "🇫🇰", "name": "falkland_islands", "keywords": ["falkland", "islands", "malvinas", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇫🇴", "name": "faroe_islands", "keywords": ["faroe", "islands", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇫🇯", "name": "fiji", "keywords": ["fj", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇫🇮", "name": "finland", "keywords": ["fi", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇫🇷", "name": "fr", "keywords": ["banner", "flag", "nation", "france", "french", "country"] },
-	{ "category": "flags", "char": "🇬🇫", "name": "french_guiana", "keywords": ["french", "guiana", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇵🇫", "name": "french_polynesia", "keywords": ["french", "polynesia", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇹🇫", "name": "french_southern_territories", "keywords": ["french", "southern", "territories", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇬🇦", "name": "gabon", "keywords": ["ga", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇬🇲", "name": "gambia", "keywords": ["gm", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇬🇪", "name": "georgia", "keywords": ["ge", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇩🇪", "name": "de", "keywords": ["german", "nation", "flag", "country", "banner"] },
-	{ "category": "flags", "char": "🇬🇭", "name": "ghana", "keywords": ["gh", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇬🇮", "name": "gibraltar", "keywords": ["gi", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇬🇷", "name": "greece", "keywords": ["gr", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇬🇱", "name": "greenland", "keywords": ["gl", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇬🇩", "name": "grenada", "keywords": ["gd", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇬🇵", "name": "guadeloupe", "keywords": ["gp", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇬🇺", "name": "guam", "keywords": ["gu", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇬🇹", "name": "guatemala", "keywords": ["gt", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇬🇬", "name": "guernsey", "keywords": ["gg", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇬🇳", "name": "guinea", "keywords": ["gn", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇬🇼", "name": "guinea_bissau", "keywords": ["gw", "bissau", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇬🇾", "name": "guyana", "keywords": ["gy", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇭🇹", "name": "haiti", "keywords": ["ht", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇭🇳", "name": "honduras", "keywords": ["hn", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇭🇰", "name": "hong_kong", "keywords": ["hong", "kong", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇭🇺", "name": "hungary", "keywords": ["hu", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇮🇸", "name": "iceland", "keywords": ["is", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇮🇳", "name": "india", "keywords": ["in", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇮🇩", "name": "indonesia", "keywords": ["flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇮🇷", "name": "iran", "keywords": ["iran, ", "islamic", "republic", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇮🇶", "name": "iraq", "keywords": ["iq", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇮🇪", "name": "ireland", "keywords": ["ie", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇮🇲", "name": "isle_of_man", "keywords": ["isle", "man", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇮🇱", "name": "israel", "keywords": ["il", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇮🇹", "name": "it", "keywords": ["italy", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇨🇮", "name": "cote_divoire", "keywords": ["ivory", "coast", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇯🇲", "name": "jamaica", "keywords": ["jm", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇯🇵", "name": "jp", "keywords": ["japanese", "nation", "flag", "country", "banner"] },
-	{ "category": "flags", "char": "🇯🇪", "name": "jersey", "keywords": ["je", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇯🇴", "name": "jordan", "keywords": ["jo", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇰🇿", "name": "kazakhstan", "keywords": ["kz", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇰🇪", "name": "kenya", "keywords": ["ke", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇰🇮", "name": "kiribati", "keywords": ["ki", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇽🇰", "name": "kosovo", "keywords": ["xk", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇰🇼", "name": "kuwait", "keywords": ["kw", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇰🇬", "name": "kyrgyzstan", "keywords": ["kg", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇱🇦", "name": "laos", "keywords": ["lao", "democratic", "republic", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇱🇻", "name": "latvia", "keywords": ["lv", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇱🇧", "name": "lebanon", "keywords": ["lb", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇱🇸", "name": "lesotho", "keywords": ["ls", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇱🇷", "name": "liberia", "keywords": ["lr", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇱🇾", "name": "libya", "keywords": ["ly", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇱🇮", "name": "liechtenstein", "keywords": ["li", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇱🇹", "name": "lithuania", "keywords": ["lt", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇱🇺", "name": "luxembourg", "keywords": ["lu", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇲🇴", "name": "macau", "keywords": ["macao", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇲🇰", "name": "macedonia", "keywords": ["macedonia, ", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇲🇬", "name": "madagascar", "keywords": ["mg", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇲🇼", "name": "malawi", "keywords": ["mw", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇲🇾", "name": "malaysia", "keywords": ["my", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇲🇻", "name": "maldives", "keywords": ["mv", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇲🇱", "name": "mali", "keywords": ["ml", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇲🇹", "name": "malta", "keywords": ["mt", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇲🇭", "name": "marshall_islands", "keywords": ["marshall", "islands", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇲🇶", "name": "martinique", "keywords": ["mq", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇲🇷", "name": "mauritania", "keywords": ["mr", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇲🇺", "name": "mauritius", "keywords": ["mu", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇾🇹", "name": "mayotte", "keywords": ["yt", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇲🇽", "name": "mexico", "keywords": ["mx", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇫🇲", "name": "micronesia", "keywords": ["micronesia, ", "federated", "states", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇲🇩", "name": "moldova", "keywords": ["moldova, ", "republic", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇲🇨", "name": "monaco", "keywords": ["mc", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇲🇳", "name": "mongolia", "keywords": ["mn", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇲🇪", "name": "montenegro", "keywords": ["me", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇲🇸", "name": "montserrat", "keywords": ["ms", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇲🇦", "name": "morocco", "keywords": ["ma", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇲🇿", "name": "mozambique", "keywords": ["mz", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇲🇲", "name": "myanmar", "keywords": ["mm", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇳🇦", "name": "namibia", "keywords": ["na", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇳🇷", "name": "nauru", "keywords": ["nr", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇳🇵", "name": "nepal", "keywords": ["np", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇳🇱", "name": "netherlands", "keywords": ["nl", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇳🇨", "name": "new_caledonia", "keywords": ["new", "caledonia", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇳🇿", "name": "new_zealand", "keywords": ["new", "zealand", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇳🇮", "name": "nicaragua", "keywords": ["ni", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇳🇪", "name": "niger", "keywords": ["ne", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇳🇬", "name": "nigeria", "keywords": ["flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇳🇺", "name": "niue", "keywords": ["nu", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇳🇫", "name": "norfolk_island", "keywords": ["norfolk", "island", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇲🇵", "name": "northern_mariana_islands", "keywords": ["northern", "mariana", "islands", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇰🇵", "name": "north_korea", "keywords": ["north", "korea", "nation", "flag", "country", "banner"] },
-	{ "category": "flags", "char": "🇳🇴", "name": "norway", "keywords": ["no", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇴🇲", "name": "oman", "keywords": ["om_symbol", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇵🇰", "name": "pakistan", "keywords": ["pk", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇵🇼", "name": "palau", "keywords": ["pw", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇵🇸", "name": "palestinian_territories", "keywords": ["palestine", "palestinian", "territories", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇵🇦", "name": "panama", "keywords": ["pa", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇵🇬", "name": "papua_new_guinea", "keywords": ["papua", "new", "guinea", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇵🇾", "name": "paraguay", "keywords": ["py", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇵🇪", "name": "peru", "keywords": ["pe", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇵🇭", "name": "philippines", "keywords": ["ph", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇵🇳", "name": "pitcairn_islands", "keywords": ["pitcairn", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇵🇱", "name": "poland", "keywords": ["pl", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇵🇹", "name": "portugal", "keywords": ["pt", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇵🇷", "name": "puerto_rico", "keywords": ["puerto", "rico", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇶🇦", "name": "qatar", "keywords": ["qa", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇷🇪", "name": "reunion", "keywords": ["réunion", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇷🇴", "name": "romania", "keywords": ["ro", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇷🇺", "name": "ru", "keywords": ["russian", "federation", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇷🇼", "name": "rwanda", "keywords": ["rw", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇧🇱", "name": "st_barthelemy", "keywords": ["saint", "barthélemy", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇸🇭", "name": "st_helena", "keywords": ["saint", "helena", "ascension", "tristan", "cunha", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇰🇳", "name": "st_kitts_nevis", "keywords": ["saint", "kitts", "nevis", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇱🇨", "name": "st_lucia", "keywords": ["saint", "lucia", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇵🇲", "name": "st_pierre_miquelon", "keywords": ["saint", "pierre", "miquelon", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇻🇨", "name": "st_vincent_grenadines", "keywords": ["saint", "vincent", "grenadines", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇼🇸", "name": "samoa", "keywords": ["ws", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇸🇲", "name": "san_marino", "keywords": ["san", "marino", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇸🇹", "name": "sao_tome_principe", "keywords": ["sao", "tome", "principe", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇸🇦", "name": "saudi_arabia", "keywords": ["flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇸🇳", "name": "senegal", "keywords": ["sn", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇷🇸", "name": "serbia", "keywords": ["rs", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇸🇨", "name": "seychelles", "keywords": ["sc", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇸🇱", "name": "sierra_leone", "keywords": ["sierra", "leone", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇸🇬", "name": "singapore", "keywords": ["sg", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇸🇽", "name": "sint_maarten", "keywords": ["sint", "maarten", "dutch", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇸🇰", "name": "slovakia", "keywords": ["sk", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇸🇮", "name": "slovenia", "keywords": ["si", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇸🇧", "name": "solomon_islands", "keywords": ["solomon", "islands", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇸🇴", "name": "somalia", "keywords": ["so", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇿🇦", "name": "south_africa", "keywords": ["south", "africa", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇬🇸", "name": "south_georgia_south_sandwich_islands", "keywords": ["south", "georgia", "sandwich", "islands", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇰🇷", "name": "kr", "keywords": ["south", "korea", "nation", "flag", "country", "banner"] },
-	{ "category": "flags", "char": "🇸🇸", "name": "south_sudan", "keywords": ["south", "sd", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇪🇸", "name": "es", "keywords": ["spain", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇱🇰", "name": "sri_lanka", "keywords": ["sri", "lanka", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇸🇩", "name": "sudan", "keywords": ["sd", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇸🇷", "name": "suriname", "keywords": ["sr", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇸🇿", "name": "swaziland", "keywords": ["sz", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇸🇪", "name": "sweden", "keywords": ["se", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇨🇭", "name": "switzerland", "keywords": ["ch", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇸🇾", "name": "syria", "keywords": ["syrian", "arab", "republic", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇹🇼", "name": "taiwan", "keywords": ["tw", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇹🇯", "name": "tajikistan", "keywords": ["tj", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇹🇿", "name": "tanzania", "keywords": ["tanzania, ", "united", "republic", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇹🇭", "name": "thailand", "keywords": ["th", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇹🇱", "name": "timor_leste", "keywords": ["timor", "leste", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇹🇬", "name": "togo", "keywords": ["tg", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇹🇰", "name": "tokelau", "keywords": ["tk", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇹🇴", "name": "tonga", "keywords": ["to", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇹🇹", "name": "trinidad_tobago", "keywords": ["trinidad", "tobago", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇹🇦", "name": "tristan_da_cunha", "keywords": ["flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇹🇳", "name": "tunisia", "keywords": ["tn", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇹🇷", "name": "tr", "keywords": ["turkey", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇹🇲", "name": "turkmenistan", "keywords": ["flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇹🇨", "name": "turks_caicos_islands", "keywords": ["turks", "caicos", "islands", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇹🇻", "name": "tuvalu", "keywords": ["flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇺🇬", "name": "uganda", "keywords": ["ug", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇺🇦", "name": "ukraine", "keywords": ["ua", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇦🇪", "name": "united_arab_emirates", "keywords": ["united", "arab", "emirates", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇬🇧", "name": "uk", "keywords": ["united", "kingdom", "great", "britain", "northern", "ireland", "flag", "nation", "country", "banner", "british", "UK", "english", "england", "union jack"] },
-	{ "category": "flags", "char": "🏴󠁧󠁢󠁥󠁮󠁧󠁿", "name": "england", "keywords": ["flag", "english"] },
-	{ "category": "flags", "char": "🏴󠁧󠁢󠁳󠁣󠁴󠁿", "name": "scotland", "keywords": ["flag", "scottish"] },
-	{ "category": "flags", "char": "🏴󠁧󠁢󠁷󠁬󠁳󠁿", "name": "wales", "keywords": ["flag", "welsh"] },
-	{ "category": "flags", "char": "🇺🇸", "name": "us", "keywords": ["united", "states", "america", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇻🇮", "name": "us_virgin_islands", "keywords": ["virgin", "islands", "us", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇺🇾", "name": "uruguay", "keywords": ["uy", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇺🇿", "name": "uzbekistan", "keywords": ["uz", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇻🇺", "name": "vanuatu", "keywords": ["vu", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇻🇦", "name": "vatican_city", "keywords": ["vatican", "city", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇻🇪", "name": "venezuela", "keywords": ["ve", "bolivarian", "republic", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇻🇳", "name": "vietnam", "keywords": ["viet", "nam", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇼🇫", "name": "wallis_futuna", "keywords": ["wallis", "futuna", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇪🇭", "name": "western_sahara", "keywords": ["western", "sahara", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇾🇪", "name": "yemen", "keywords": ["ye", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇿🇲", "name": "zambia", "keywords": ["zm", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇿🇼", "name": "zimbabwe", "keywords": ["zw", "flag", "nation", "country", "banner"] },
-	{ "category": "flags", "char": "🇺🇳", "name": "united_nations", "keywords": ["un", "flag", "banner"] },
-	{ "category": "flags", "char": "🏴‍☠️", "name": "pirate_flag", "keywords": ["skull", "crossbones", "flag", "banner"] }
+	["😀", "grinning", 0],
+	["😬", "grimacing", 0],
+	["😁", "grin", 0],
+	["😂", "joy", 0],
+	["🤣", "rofl", 0],
+	["🥳", "partying", 0],
+	["😃", "smiley", 0],
+	["😄", "smile", 0],
+	["😅", "sweat_smile", 0],
+	["🥲", "smiling_face_with_tear", 0],
+	["😆", "laughing", 0],
+	["😇", "innocent", 0],
+	["😉", "wink", 0],
+	["😊", "blush", 0],
+	["🙂", "slightly_smiling_face", 0],
+	["🙃", "upside_down_face", 0],
+	["☺️", "relaxed", 0],
+	["😋", "yum", 0],
+	["😌", "relieved", 0],
+	["😍", "heart_eyes", 0],
+	["🥰", "smiling_face_with_three_hearts", 0],
+	["😘", "kissing_heart", 0],
+	["😗", "kissing", 0],
+	["😙", "kissing_smiling_eyes", 0],
+	["😚", "kissing_closed_eyes", 0],
+	["😜", "stuck_out_tongue_winking_eye", 0],
+	["🤪", "zany", 0],
+	["🤨", "raised_eyebrow", 0],
+	["🧐", "monocle", 0],
+	["😝", "stuck_out_tongue_closed_eyes", 0],
+	["😛", "stuck_out_tongue", 0],
+	["🤑", "money_mouth_face", 0],
+	["🤓", "nerd_face", 0],
+	["🥸", "disguised_face", 0],
+	["😎", "sunglasses", 0],
+	["🤩", "star_struck", 0],
+	["🤡", "clown_face", 0],
+	["🤠", "cowboy_hat_face", 0],
+	["🤗", "hugs", 0],
+	["😏", "smirk", 0],
+	["😶", "no_mouth", 0],
+	["😐", "neutral_face", 0],
+	["😑", "expressionless", 0],
+	["😒", "unamused", 0],
+	["🙄", "roll_eyes", 0],
+	["🤔", "thinking", 0],
+	["🤥", "lying_face", 0],
+	["🤭", "hand_over_mouth", 0],
+	["🤫", "shushing", 0],
+	["🤬", "symbols_over_mouth", 0],
+	["🤯", "exploding_head", 0],
+	["😳", "flushed", 0],
+	["😞", "disappointed", 0],
+	["😟", "worried", 0],
+	["😠", "angry", 0],
+	["😡", "rage", 0],
+	["😔", "pensive", 0],
+	["😕", "confused", 0],
+	["🙁", "slightly_frowning_face", 0],
+	["☹", "frowning_face", 0],
+	["😣", "persevere", 0],
+	["😖", "confounded", 0],
+	["😫", "tired_face", 0],
+	["😩", "weary", 0],
+	["🥺", "pleading", 0],
+	["😤", "triumph", 0],
+	["😮", "open_mouth", 0],
+	["😱", "scream", 0],
+	["😨", "fearful", 0],
+	["😰", "cold_sweat", 0],
+	["😯", "hushed", 0],
+	["😦", "frowning", 0],
+	["😧", "anguished", 0],
+	["😢", "cry", 0],
+	["😥", "disappointed_relieved", 0],
+	["🤤", "drooling_face", 0],
+	["😪", "sleepy", 0],
+	["😓", "sweat", 0],
+	["🥵", "hot", 0],
+	["🥶", "cold", 0],
+	["😭", "sob", 0],
+	["😵", "dizzy_face", 0],
+	["😲", "astonished", 0],
+	["🤐", "zipper_mouth_face", 0],
+	["🤢", "nauseated_face", 0],
+	["🤧", "sneezing_face", 0],
+	["🤮", "vomiting", 0],
+	["😷", "mask", 0],
+	["🤒", "face_with_thermometer", 0],
+	["🤕", "face_with_head_bandage", 0],
+	["🥴", "woozy", 0],
+	["🥱", "yawning", 0],
+	["😴", "sleeping", 0],
+	["💤", "zzz", 0],
+	["😶‍🌫️", "face_in_clouds", 0],
+	["😮‍💨", "face_exhaling", 0],
+	["😵‍💫", "face_with_spiral_eyes", 0],
+	["🫠", "melting_face", 0],
+	["🫢", "face_with_open_eyes_and_hand_over_mouth", 0],
+	["🫣", "face_with_peeking_eye", 0],
+	["🫡", "saluting_face", 0],
+	["🫥", "dotted_line_face", 0],
+	["🫤", "face_with_diagonal_mouth", 0],
+	["🥹", "face_holding_back_tears", 0],
+	["💩", "poop", 0],
+	["😈", "smiling_imp", 0],
+	["👿", "imp", 0],
+	["👹", "japanese_ogre", 0],
+	["👺", "japanese_goblin", 0],
+	["💀", "skull", 0],
+	["👻", "ghost", 0],
+	["👽", "alien", 0],
+	["🤖", "robot", 0],
+	["😺", "smiley_cat", 0],
+	["😸", "smile_cat", 0],
+	["😹", "joy_cat", 0],
+	["😻", "heart_eyes_cat", 0],
+	["😼", "smirk_cat", 0],
+	["😽", "kissing_cat", 0],
+	["🙀", "scream_cat", 0],
+	["😿", "crying_cat_face", 0],
+	["😾", "pouting_cat", 0],
+	["🤲", "palms_up", 1],
+	["🙌", "raised_hands", 1],
+	["👏", "clap", 1],
+	["👋", "wave", 1],
+	["🤙", "call_me_hand", 1],
+	["👍", "+1", 1],
+	["👎", "-1", 1],
+	["👊", "facepunch", 1],
+	["✊", "fist", 1],
+	["🤛", "fist_left", 1],
+	["🤜", "fist_right", 1],
+	["✌", "v", 1],
+	["👌", "ok_hand", 1],
+	["✋", "raised_hand", 1],
+	["🤚", "raised_back_of_hand", 1],
+	["👐", "open_hands", 1],
+	["💪", "muscle", 1],
+	["🦾", "mechanical_arm", 1],
+	["🙏", "pray", 1],
+	["🦶", "foot", 1],
+	["🦵", "leg", 1],
+	["🦿", "mechanical_leg", 1],
+	["🤝", "handshake", 1],
+	["☝", "point_up", 1],
+	["👆", "point_up_2", 1],
+	["👇", "point_down", 1],
+	["👈", "point_left", 1],
+	["👉", "point_right", 1],
+	["🖕", "fu", 1],
+	["🖐", "raised_hand_with_fingers_splayed", 1],
+	["🤟", "love_you", 1],
+	["🤘", "metal", 1],
+	["🤞", "crossed_fingers", 1],
+	["🖖", "vulcan_salute", 1],
+	["✍", "writing_hand", 1],
+	["🫰", "hand_with_index_finger_and_thumb_crossed", 1],
+	["🫱", "rightwards_hand", 1],
+	["🫲", "leftwards_hand", 1],
+	["🫳", "palm_down_hand", 1],
+	["🫴", "palm_up_hand", 1],
+	["🫵", "index_pointing_at_the_viewer", 1],
+	["🫶", "heart_hands", 1],
+	["🤏", "pinching_hand", 1],
+	["🤌", "pinched_fingers", 1],
+	["🤳", "selfie", 1],
+	["💅", "nail_care", 1],
+	["👄", "lips", 1],
+	["🫦", "biting_lip", 1],
+	["🦷", "tooth", 1],
+	["👅", "tongue", 1],
+	["👂", "ear", 1],
+	["🦻", "ear_with_hearing_aid", 1],
+	["👃", "nose", 1],
+	["👁", "eye", 1],
+	["👀", "eyes", 1],
+	["🧠", "brain", 1],
+	["🫀", "anatomical_heart", 1],
+	["🫁", "lungs", 1],
+	["👤", "bust_in_silhouette", 1],
+	["👥", "busts_in_silhouette", 1],
+	["🗣", "speaking_head", 1],
+	["👶", "baby", 1],
+	["🧒", "child", 1],
+	["👦", "boy", 1],
+	["👧", "girl", 1],
+	["🧑", "adult", 1],
+	["👨", "man", 1],
+	["👩", "woman", 1],
+	["🧑‍🦱", "curly_hair", 1],
+	["👩‍🦱", "curly_hair_woman", 1],
+	["👨‍🦱", "curly_hair_man", 1],
+	["🧑‍🦰", "red_hair", 1],
+	["👩‍🦰", "red_hair_woman", 1],
+	["👨‍🦰", "red_hair_man", 1],
+	["👱‍♀️", "blonde_woman", 1],
+	["👱", "blonde_man", 1],
+	["🧑‍🦳", "white_hair", 1],
+	["👩‍🦳", "white_hair_woman", 1],
+	["👨‍🦳", "white_hair_man", 1],
+	["🧑‍🦲", "bald", 1],
+	["👩‍🦲", "bald_woman", 1],
+	["👨‍🦲", "bald_man", 1],
+	["🧔", "bearded_person", 1],
+	["🧓", "older_adult", 1],
+	["👴", "older_man", 1],
+	["👵", "older_woman", 1],
+	["👲", "man_with_gua_pi_mao", 1],
+	["🧕", "woman_with_headscarf", 1],
+	["👳‍♀️", "woman_with_turban", 1],
+	["👳", "man_with_turban", 1],
+	["👮‍♀️", "policewoman", 1],
+	["👮", "policeman", 1],
+	["👷‍♀️", "construction_worker_woman", 1],
+	["👷", "construction_worker_man", 1],
+	["💂‍♀️", "guardswoman", 1],
+	["💂", "guardsman", 1],
+	["🕵️‍♀️", "female_detective", 1],
+	["🕵", "male_detective", 1],
+	["🧑‍⚕️", "health_worker", 1],
+	["👩‍⚕️", "woman_health_worker", 1],
+	["👨‍⚕️", "man_health_worker", 1],
+	["🧑‍🌾", "farmer", 1],
+	["👩‍🌾", "woman_farmer", 1],
+	["👨‍🌾", "man_farmer", 1],
+	["🧑‍🍳", "cook", 1],
+	["👩‍🍳", "woman_cook", 1],
+	["👨‍🍳", "man_cook", 1],
+	["🧑‍🎓", "student", 1],
+	["👩‍🎓", "woman_student", 1],
+	["👨‍🎓", "man_student", 1],
+	["🧑‍🎤", "singer", 1],
+	["👩‍🎤", "woman_singer", 1],
+	["👨‍🎤", "man_singer", 1],
+	["🧑‍🏫", "teacher", 1],
+	["👩‍🏫", "woman_teacher", 1],
+	["👨‍🏫", "man_teacher", 1],
+	["🧑‍🏭", "factory_worker", 1],
+	["👩‍🏭", "woman_factory_worker", 1],
+	["👨‍🏭", "man_factory_worker", 1],
+	["🧑‍💻", "technologist", 1],
+	["👩‍💻", "woman_technologist", 1],
+	["👨‍💻", "man_technologist", 1],
+	["🧑‍💼", "office_worker", 1],
+	["👩‍💼", "woman_office_worker", 1],
+	["👨‍💼", "man_office_worker", 1],
+	["🧑‍🔧", "mechanic", 1],
+	["👩‍🔧", "woman_mechanic", 1],
+	["👨‍🔧", "man_mechanic", 1],
+	["🧑‍🔬", "scientist", 1],
+	["👩‍🔬", "woman_scientist", 1],
+	["👨‍🔬", "man_scientist", 1],
+	["🧑‍🎨", "artist", 1],
+	["👩‍🎨", "woman_artist", 1],
+	["👨‍🎨", "man_artist", 1],
+	["🧑‍🚒", "firefighter", 1],
+	["👩‍🚒", "woman_firefighter", 1],
+	["👨‍🚒", "man_firefighter", 1],
+	["🧑‍✈️", "pilot", 1],
+	["👩‍✈️", "woman_pilot", 1],
+	["👨‍✈️", "man_pilot", 1],
+	["🧑‍🚀", "astronaut", 1],
+	["👩‍🚀", "woman_astronaut", 1],
+	["👨‍🚀", "man_astronaut", 1],
+	["🧑‍⚖️", "judge", 1],
+	["👩‍⚖️", "woman_judge", 1],
+	["👨‍⚖️", "man_judge", 1],
+	["🦸‍♀️", "woman_superhero", 1],
+	["🦸‍♂️", "man_superhero", 1],
+	["🦹‍♀️", "woman_supervillain", 1],
+	["🦹‍♂️", "man_supervillain", 1],
+	["🤶", "mrs_claus", 1],
+	["🧑‍🎄", "mx_claus", 1],
+	["🎅", "santa", 1],
+	["🥷", "ninja", 1],
+	["🧙‍♀️", "sorceress", 1],
+	["🧙‍♂️", "wizard", 1],
+	["🧝‍♀️", "woman_elf", 1],
+	["🧝‍♂️", "man_elf", 1],
+	["🧛‍♀️", "woman_vampire", 1],
+	["🧛‍♂️", "man_vampire", 1],
+	["🧟‍♀️", "woman_zombie", 1],
+	["🧟‍♂️", "man_zombie", 1],
+	["🧞‍♀️", "woman_genie", 1],
+	["🧞‍♂️", "man_genie", 1],
+	["🧜‍♀️", "mermaid", 1],
+	["🧜‍♂️", "merman", 1],
+	["🧚‍♀️", "woman_fairy", 1],
+	["🧚‍♂️", "man_fairy", 1],
+	["👼", "angel", 1],
+	["🧌", "troll", 1],
+	["🤰", "pregnant_woman", 1],
+	["🫃", "pregnant_man", 1],
+	["🫄", "pregnant_person", 1],
+	["🫅", "person_with_crown", 1],
+	["🤱", "breastfeeding", 1],
+	["👩‍🍼", "woman_feeding_baby", 1],
+	["👨‍🍼", "man_feeding_baby", 1],
+	["🧑‍🍼", "person_feeding_baby", 1],
+	["👸", "princess", 1],
+	["🤴", "prince", 1],
+	["👰", "person_with_veil", 1],
+	["👰", "bride_with_veil", 1],
+	["🤵", "person_in_tuxedo", 1],
+	["🤵", "man_in_tuxedo", 1],
+	["🏃‍♀️", "running_woman", 1],
+	["🏃", "running_man", 1],
+	["🚶‍♀️", "walking_woman", 1],
+	["🚶", "walking_man", 1],
+	["💃", "dancer", 1],
+	["🕺", "man_dancing", 1],
+	["👯", "dancing_women", 1],
+	["👯‍♂️", "dancing_men", 1],
+	["👫", "couple", 1],
+	["🧑‍🤝‍🧑", "people_holding_hands", 1],
+	["👬", "two_men_holding_hands", 1],
+	["👭", "two_women_holding_hands", 1],
+	["🫂", "people_hugging", 1],
+	["🙇‍♀️", "bowing_woman", 1],
+	["🙇", "bowing_man", 1],
+	["🤦‍♂️", "man_facepalming", 1],
+	["🤦‍♀️", "woman_facepalming", 1],
+	["🤷", "woman_shrugging", 1],
+	["🤷‍♂️", "man_shrugging", 1],
+	["💁", "tipping_hand_woman", 1],
+	["💁‍♂️", "tipping_hand_man", 1],
+	["🙅", "no_good_woman", 1],
+	["🙅‍♂️", "no_good_man", 1],
+	["🙆", "ok_woman", 1],
+	["🙆‍♂️", "ok_man", 1],
+	["🙋", "raising_hand_woman", 1],
+	["🙋‍♂️", "raising_hand_man", 1],
+	["🙎", "pouting_woman", 1],
+	["🙎‍♂️", "pouting_man", 1],
+	["🙍", "frowning_woman", 1],
+	["🙍‍♂️", "frowning_man", 1],
+	["💇", "haircut_woman", 1],
+	["💇‍♂️", "haircut_man", 1],
+	["💆", "massage_woman", 1],
+	["💆‍♂️", "massage_man", 1],
+	["🧖‍♀️", "woman_in_steamy_room", 1],
+	["🧖‍♂️", "man_in_steamy_room", 1],
+	["🧏‍♀️", "woman_deaf", 1],
+	["🧏‍♂️", "man_deaf", 1],
+	["🧍‍♀️", "woman_standing", 1],
+	["🧍‍♂️", "man_standing", 1],
+	["🧎‍♀️", "woman_kneeling", 1],
+	["🧎‍♂️", "man_kneeling", 1],
+	["🧑‍🦯", "person_with_probing_cane", 1],
+	["👩‍🦯", "woman_with_probing_cane", 1],
+	["👨‍🦯", "man_with_probing_cane", 1],
+	["🧑‍🦼", "person_in_motorized_wheelchair", 1],
+	["👩‍🦼", "woman_in_motorized_wheelchair", 1],
+	["👨‍🦼", "man_in_motorized_wheelchair", 1],
+	["🧑‍🦽", "person_in_manual_wheelchair", 1],
+	["👩‍🦽", "woman_in_manual_wheelchair", 1],
+	["👨‍🦽", "man_in_manual_wheelchair", 1],
+	["💑", "couple_with_heart_woman_man", 1],
+	["👩‍❤️‍👩", "couple_with_heart_woman_woman", 1],
+	["👨‍❤️‍👨", "couple_with_heart_man_man", 1],
+	["💏", "couplekiss_man_woman", 1],
+	["👩‍❤️‍💋‍👩", "couplekiss_woman_woman", 1],
+	["👨‍❤️‍💋‍👨", "couplekiss_man_man", 1],
+	["👪", "family_man_woman_boy", 1],
+	["👨‍👩‍👧", "family_man_woman_girl", 1],
+	["👨‍👩‍👧‍👦", "family_man_woman_girl_boy", 1],
+	["👨‍👩‍👦‍👦", "family_man_woman_boy_boy", 1],
+	["👨‍👩‍👧‍👧", "family_man_woman_girl_girl", 1],
+	["👩‍👩‍👦", "family_woman_woman_boy", 1],
+	["👩‍👩‍👧", "family_woman_woman_girl", 1],
+	["👩‍👩‍👧‍👦", "family_woman_woman_girl_boy", 1],
+	["👩‍👩‍👦‍👦", "family_woman_woman_boy_boy", 1],
+	["👩‍👩‍👧‍👧", "family_woman_woman_girl_girl", 1],
+	["👨‍👨‍👦", "family_man_man_boy", 1],
+	["👨‍👨‍👧", "family_man_man_girl", 1],
+	["👨‍👨‍👧‍👦", "family_man_man_girl_boy", 1],
+	["👨‍👨‍👦‍👦", "family_man_man_boy_boy", 1],
+	["👨‍👨‍👧‍👧", "family_man_man_girl_girl", 1],
+	["👩‍👦", "family_woman_boy", 1],
+	["👩‍👧", "family_woman_girl", 1],
+	["👩‍👧‍👦", "family_woman_girl_boy", 1],
+	["👩‍👦‍👦", "family_woman_boy_boy", 1],
+	["👩‍👧‍👧", "family_woman_girl_girl", 1],
+	["👨‍👦", "family_man_boy", 1],
+	["👨‍👧", "family_man_girl", 1],
+	["👨‍👧‍👦", "family_man_girl_boy", 1],
+	["👨‍👦‍👦", "family_man_boy_boy", 1],
+	["👨‍👧‍👧", "family_man_girl_girl", 1],
+	["🧶", "yarn", 1],
+	["🧵", "thread", 1],
+	["🧥", "coat", 1],
+	["🥼", "labcoat", 1],
+	["👚", "womans_clothes", 1],
+	["👕", "tshirt", 1],
+	["👖", "jeans", 1],
+	["👔", "necktie", 1],
+	["👗", "dress", 1],
+	["👙", "bikini", 1],
+	["🩱", "one_piece_swimsuit", 1],
+	["👘", "kimono", 1],
+	["🥻", "sari", 1],
+	["🩲", "briefs", 1],
+	["🩳", "shorts", 1],
+	["💄", "lipstick", 1],
+	["💋", "kiss", 1],
+	["👣", "footprints", 1],
+	["🥿", "flat_shoe", 1],
+	["👠", "high_heel", 1],
+	["👡", "sandal", 1],
+	["👢", "boot", 1],
+	["👞", "mans_shoe", 1],
+	["👟", "athletic_shoe", 1],
+	["🩴", "thong_sandal", 1],
+	["🩰", "ballet_shoes", 1],
+	["🧦", "socks", 1],
+	["🧤", "gloves", 1],
+	["🧣", "scarf", 1],
+	["👒", "womans_hat", 1],
+	["🎩", "tophat", 1],
+	["🧢", "billed_hat", 1],
+	["⛑", "rescue_worker_helmet", 1],
+	["🪖", "military_helmet", 1],
+	["🎓", "mortar_board", 1],
+	["👑", "crown", 1],
+	["🎒", "school_satchel", 1],
+	["🧳", "luggage", 1],
+	["👝", "pouch", 1],
+	["👛", "purse", 1],
+	["👜", "handbag", 1],
+	["💼", "briefcase", 1],
+	["👓", "eyeglasses", 1],
+	["🕶", "dark_sunglasses", 1],
+	["🥽", "goggles", 1],
+	["💍", "ring", 1],
+	["🌂", "closed_umbrella", 1],
+	["🐶", "dog", 2],
+	["🐱", "cat", 2],
+	["🐈‍⬛", "black_cat", 2],
+	["🐭", "mouse", 2],
+	["🐹", "hamster", 2],
+	["🐰", "rabbit", 2],
+	["🦊", "fox_face", 2],
+	["🐻", "bear", 2],
+	["🐼", "panda_face", 2],
+	["🐨", "koala", 2],
+	["🐯", "tiger", 2],
+	["🦁", "lion", 2],
+	["🐮", "cow", 2],
+	["🐷", "pig", 2],
+	["🐽", "pig_nose", 2],
+	["🐸", "frog", 2],
+	["🦑", "squid", 2],
+	["🐙", "octopus", 2],
+	["🦐", "shrimp", 2],
+	["🐵", "monkey_face", 2],
+	["🦍", "gorilla", 2],
+	["🙈", "see_no_evil", 2],
+	["🙉", "hear_no_evil", 2],
+	["🙊", "speak_no_evil", 2],
+	["🐒", "monkey", 2],
+	["🐔", "chicken", 2],
+	["🐧", "penguin", 2],
+	["🐦", "bird", 2],
+	["🐤", "baby_chick", 2],
+	["🐣", "hatching_chick", 2],
+	["🐥", "hatched_chick", 2],
+	["🦆", "duck", 2],
+	["🦅", "eagle", 2],
+	["🦉", "owl", 2],
+	["🦇", "bat", 2],
+	["🐺", "wolf", 2],
+	["🐗", "boar", 2],
+	["🐴", "horse", 2],
+	["🦄", "unicorn", 2],
+	["🐝", "honeybee", 2],
+	["🐛", "bug", 2],
+	["🦋", "butterfly", 2],
+	["🐌", "snail", 2],
+	["🐞", "lady_beetle", 2],
+	["🐜", "ant", 2],
+	["🦗", "grasshopper", 2],
+	["🕷", "spider", 2],
+	["🪲", "beetle", 2],
+	["🪳", "cockroach", 2],
+	["🪰", "fly", 2],
+	["🪱", "worm", 2],
+	["🦂", "scorpion", 2],
+	["🦀", "crab", 2],
+	["🐍", "snake", 2],
+	["🦎", "lizard", 2],
+	["🦖", "t-rex", 2],
+	["🦕", "sauropod", 2],
+	["🐢", "turtle", 2],
+	["🐠", "tropical_fish", 2],
+	["🐟", "fish", 2],
+	["🐡", "blowfish", 2],
+	["🐬", "dolphin", 2],
+	["🦈", "shark", 2],
+	["🐳", "whale", 2],
+	["🐋", "whale2", 2],
+	["🐊", "crocodile", 2],
+	["🐆", "leopard", 2],
+	["🦓", "zebra", 2],
+	["🐅", "tiger2", 2],
+	["🐃", "water_buffalo", 2],
+	["🐂", "ox", 2],
+	["🐄", "cow2", 2],
+	["🦌", "deer", 2],
+	["🐪", "dromedary_camel", 2],
+	["🐫", "camel", 2],
+	["🦒", "giraffe", 2],
+	["🐘", "elephant", 2],
+	["🦏", "rhinoceros", 2],
+	["🐐", "goat", 2],
+	["🐏", "ram", 2],
+	["🐑", "sheep", 2],
+	["🐎", "racehorse", 2],
+	["🐖", "pig2", 2],
+	["🐀", "rat", 2],
+	["🐁", "mouse2", 2],
+	["🐓", "rooster", 2],
+	["🦃", "turkey", 2],
+	["🕊", "dove", 2],
+	["🐕", "dog2", 2],
+	["🐩", "poodle", 2],
+	["🐈", "cat2", 2],
+	["🐇", "rabbit2", 2],
+	["🐿", "chipmunk", 2],
+	["🦔", "hedgehog", 2],
+	["🦝", "raccoon", 2],
+	["🦙", "llama", 2],
+	["🦛", "hippopotamus", 2],
+	["🦘", "kangaroo", 2],
+	["🦡", "badger", 2],
+	["🦢", "swan", 2],
+	["🦚", "peacock", 2],
+	["🦜", "parrot", 2],
+	["🦞", "lobster", 2],
+	["🦠", "microbe", 2],
+	["🦟", "mosquito", 2],
+	["🦬", "bison", 2],
+	["🦣", "mammoth", 2],
+	["🦫", "beaver", 2],
+	["🐻‍❄️", "polar_bear", 2],
+	["🦤", "dodo", 2],
+	["🪶", "feather", 2],
+	["🦭", "seal", 2],
+	["🐾", "paw_prints", 2],
+	["🐉", "dragon", 2],
+	["🐲", "dragon_face", 2],
+	["🦧", "orangutan", 2],
+	["🦮", "guide_dog", 2],
+	["🐕‍🦺", "service_dog", 2],
+	["🦥", "sloth", 2],
+	["🦦", "otter", 2],
+	["🦨", "skunk", 2],
+	["🦩", "flamingo", 2],
+	["🌵", "cactus", 2],
+	["🎄", "christmas_tree", 2],
+	["🌲", "evergreen_tree", 2],
+	["🌳", "deciduous_tree", 2],
+	["🌴", "palm_tree", 2],
+	["🌱", "seedling", 2],
+	["🌿", "herb", 2],
+	["☘", "shamrock", 2],
+	["🍀", "four_leaf_clover", 2],
+	["🎍", "bamboo", 2],
+	["🎋", "tanabata_tree", 2],
+	["🍃", "leaves", 2],
+	["🍂", "fallen_leaf", 2],
+	["🍁", "maple_leaf", 2],
+	["🌾", "ear_of_rice", 2],
+	["🌺", "hibiscus", 2],
+	["🌻", "sunflower", 2],
+	["🌹", "rose", 2],
+	["🥀", "wilted_flower", 2],
+	["🌷", "tulip", 2],
+	["🌼", "blossom", 2],
+	["🌸", "cherry_blossom", 2],
+	["💐", "bouquet", 2],
+	["🍄", "mushroom", 2],
+	["🪴", "potted_plant", 2],
+	["🌰", "chestnut", 2],
+	["🎃", "jack_o_lantern", 2],
+	["🐚", "shell", 2],
+	["🕸", "spider_web", 2],
+	["🌎", "earth_americas", 2],
+	["🌍", "earth_africa", 2],
+	["🌏", "earth_asia", 2],
+	["🪐", "ringed_planet", 2],
+	["🌕", "full_moon", 2],
+	["🌖", "waning_gibbous_moon", 2],
+	["🌗", "last_quarter_moon", 2],
+	["🌘", "waning_crescent_moon", 2],
+	["🌑", "new_moon", 2],
+	["🌒", "waxing_crescent_moon", 2],
+	["🌓", "first_quarter_moon", 2],
+	["🌔", "waxing_gibbous_moon", 2],
+	["🌚", "new_moon_with_face", 2],
+	["🌝", "full_moon_with_face", 2],
+	["🌛", "first_quarter_moon_with_face", 2],
+	["🌜", "last_quarter_moon_with_face", 2],
+	["🌞", "sun_with_face", 2],
+	["🌙", "crescent_moon", 2],
+	["⭐", "star", 2],
+	["🌟", "star2", 2],
+	["💫", "dizzy", 2],
+	["✨", "sparkles", 2],
+	["☄", "comet", 2],
+	["☀️", "sunny", 2],
+	["🌤", "sun_behind_small_cloud", 2],
+	["⛅", "partly_sunny", 2],
+	["🌥", "sun_behind_large_cloud", 2],
+	["🌦", "sun_behind_rain_cloud", 2],
+	["☁️", "cloud", 2],
+	["🌧", "cloud_with_rain", 2],
+	["⛈", "cloud_with_lightning_and_rain", 2],
+	["🌩", "cloud_with_lightning", 2],
+	["⚡", "zap", 2],
+	["🔥", "fire", 2],
+	["💥", "boom", 2],
+	["❄️", "snowflake", 2],
+	["🌨", "cloud_with_snow", 2],
+	["⛄", "snowman", 2],
+	["☃", "snowman_with_snow", 2],
+	["🌬", "wind_face", 2],
+	["💨", "dash", 2],
+	["🌪", "tornado", 2],
+	["🌫", "fog", 2],
+	["☂", "open_umbrella", 2],
+	["☔", "umbrella", 2],
+	["💧", "droplet", 2],
+	["💦", "sweat_drops", 2],
+	["🌊", "ocean", 2],
+	["🪷", "lotus", 2],
+	["🪸", "coral", 2],
+	["🪹", "empty_nest", 2],
+	["🪺", "nest_with_eggs", 2],
+	["🍏", "green_apple", 3],
+	["🍎", "apple", 3],
+	["🍐", "pear", 3],
+	["🍊", "tangerine", 3],
+	["🍋", "lemon", 3],
+	["🍌", "banana", 3],
+	["🍉", "watermelon", 3],
+	["🍇", "grapes", 3],
+	["🍓", "strawberry", 3],
+	["🍈", "melon", 3],
+	["🍒", "cherries", 3],
+	["🍑", "peach", 3],
+	["🍍", "pineapple", 3],
+	["🥥", "coconut", 3],
+	["🥝", "kiwi_fruit", 3],
+	["🥭", "mango", 3],
+	["🥑", "avocado", 3],
+	["🥦", "broccoli", 3],
+	["🍅", "tomato", 3],
+	["🍆", "eggplant", 3],
+	["🥒", "cucumber", 3],
+	["🫐", "blueberries", 3],
+	["🫒", "olive", 3],
+	["🫑", "bell_pepper", 3],
+	["🥕", "carrot", 3],
+	["🌶", "hot_pepper", 3],
+	["🥔", "potato", 3],
+	["🌽", "corn", 3],
+	["🥬", "leafy_greens", 3],
+	["🍠", "sweet_potato", 3],
+	["🥜", "peanuts", 3],
+	["🧄", "garlic", 3],
+	["🧅", "onion", 3],
+	["🍯", "honey_pot", 3],
+	["🥐", "croissant", 3],
+	["🍞", "bread", 3],
+	["🥖", "baguette_bread", 3],
+	["🥯", "bagel", 3],
+	["🥨", "pretzel", 3],
+	["🧀", "cheese", 3],
+	["🥚", "egg", 3],
+	["🥓", "bacon", 3],
+	["🥩", "steak", 3],
+	["🥞", "pancakes", 3],
+	["🍗", "poultry_leg", 3],
+	["🍖", "meat_on_bone", 3],
+	["🦴", "bone", 3],
+	["🍤", "fried_shrimp", 3],
+	["🍳", "fried_egg", 3],
+	["🍔", "hamburger", 3],
+	["🍟", "fries", 3],
+	["🥙", "stuffed_flatbread", 3],
+	["🌭", "hotdog", 3],
+	["🍕", "pizza", 3],
+	["🥪", "sandwich", 3],
+	["🥫", "canned_food", 3],
+	["🍝", "spaghetti", 3],
+	["🌮", "taco", 3],
+	["🌯", "burrito", 3],
+	["🥗", "green_salad", 3],
+	["🥘", "shallow_pan_of_food", 3],
+	["🍜", "ramen", 3],
+	["🍲", "stew", 3],
+	["🍥", "fish_cake", 3],
+	["🥠", "fortune_cookie", 3],
+	["🍣", "sushi", 3],
+	["🍱", "bento", 3],
+	["🍛", "curry", 3],
+	["🍙", "rice_ball", 3],
+	["🍚", "rice", 3],
+	["🍘", "rice_cracker", 3],
+	["🍢", "oden", 3],
+	["🍡", "dango", 3],
+	["🍧", "shaved_ice", 3],
+	["🍨", "ice_cream", 3],
+	["🍦", "icecream", 3],
+	["🥧", "pie", 3],
+	["🍰", "cake", 3],
+	["🧁", "cupcake", 3],
+	["🥮", "moon_cake", 3],
+	["🎂", "birthday", 3],
+	["🍮", "custard", 3],
+	["🍬", "candy", 3],
+	["🍭", "lollipop", 3],
+	["🍫", "chocolate_bar", 3],
+	["🍿", "popcorn", 3],
+	["🥟", "dumpling", 3],
+	["🍩", "doughnut", 3],
+	["🍪", "cookie", 3],
+	["🧇", "waffle", 3],
+	["🧆", "falafel", 3],
+	["🧈", "butter", 3],
+	["🦪", "oyster", 3],
+	["🫓", "flatbread", 3],
+	["🫔", "tamale", 3],
+	["🫕", "fondue", 3],
+	["🥛", "milk_glass", 3],
+	["🍺", "beer", 3],
+	["🍻", "beers", 3],
+	["🥂", "clinking_glasses", 3],
+	["🍷", "wine_glass", 3],
+	["🥃", "tumbler_glass", 3],
+	["🍸", "cocktail", 3],
+	["🍹", "tropical_drink", 3],
+	["🍾", "champagne", 3],
+	["🍶", "sake", 3],
+	["🍵", "tea", 3],
+	["🥤", "cup_with_straw", 3],
+	["☕", "coffee", 3],
+	["🫖", "teapot", 3],
+	["🧋", "bubble_tea", 3],
+	["🍼", "baby_bottle", 3],
+	["🧃", "beverage_box", 3],
+	["🧉", "mate", 3],
+	["🧊", "ice_cube", 3],
+	["🧂", "salt", 3],
+	["🥄", "spoon", 3],
+	["🍴", "fork_and_knife", 3],
+	["🍽", "plate_with_cutlery", 3],
+	["🥣", "bowl_with_spoon", 3],
+	["🥡", "takeout_box", 3],
+	["🥢", "chopsticks", 3],
+	["🫗", "pouring_liquid", 3],
+	["🫘", "beans", 3],
+	["🫙", "jar", 3],
+	["⚽", "soccer", 4],
+	["🏀", "basketball", 4],
+	["🏈", "football", 4],
+	["⚾", "baseball", 4],
+	["🥎", "softball", 4],
+	["🎾", "tennis", 4],
+	["🏐", "volleyball", 4],
+	["🏉", "rugby_football", 4],
+	["🥏", "flying_disc", 4],
+	["🎱", "8ball", 4],
+	["⛳", "golf", 4],
+	["🏌️‍♀️", "golfing_woman", 4],
+	["🏌", "golfing_man", 4],
+	["🏓", "ping_pong", 4],
+	["🏸", "badminton", 4],
+	["🥅", "goal_net", 4],
+	["🏒", "ice_hockey", 4],
+	["🏑", "field_hockey", 4],
+	["🥍", "lacrosse", 4],
+	["🏏", "cricket", 4],
+	["🎿", "ski", 4],
+	["⛷", "skier", 4],
+	["🏂", "snowboarder", 4],
+	["🤺", "person_fencing", 4],
+	["🤼‍♀️", "women_wrestling", 4],
+	["🤼‍♂️", "men_wrestling", 4],
+	["🤸‍♀️", "woman_cartwheeling", 4],
+	["🤸‍♂️", "man_cartwheeling", 4],
+	["🤾‍♀️", "woman_playing_handball", 4],
+	["🤾‍♂️", "man_playing_handball", 4],
+	["⛸", "ice_skate", 4],
+	["🥌", "curling_stone", 4],
+	["🛹", "skateboard", 4],
+	["🛷", "sled", 4],
+	["🏹", "bow_and_arrow", 4],
+	["🎣", "fishing_pole_and_fish", 4],
+	["🥊", "boxing_glove", 4],
+	["🥋", "martial_arts_uniform", 4],
+	["🚣‍♀️", "rowing_woman", 4],
+	["🚣", "rowing_man", 4],
+	["🧗‍♀️", "climbing_woman", 4],
+	["🧗‍♂️", "climbing_man", 4],
+	["🏊‍♀️", "swimming_woman", 4],
+	["🏊", "swimming_man", 4],
+	["🤽‍♀️", "woman_playing_water_polo", 4],
+	["🤽‍♂️", "man_playing_water_polo", 4],
+	["🧘‍♀️", "woman_in_lotus_position", 4],
+	["🧘‍♂️", "man_in_lotus_position", 4],
+	["🏄‍♀️", "surfing_woman", 4],
+	["🏄", "surfing_man", 4],
+	["🛀", "bath", 4],
+	["⛹️‍♀️", "basketball_woman", 4],
+	["⛹", "basketball_man", 4],
+	["🏋️‍♀️", "weight_lifting_woman", 4],
+	["🏋", "weight_lifting_man", 4],
+	["🚴‍♀️", "biking_woman", 4],
+	["🚴", "biking_man", 4],
+	["🚵‍♀️", "mountain_biking_woman", 4],
+	["🚵", "mountain_biking_man", 4],
+	["🏇", "horse_racing", 4],
+	["🤿", "diving_mask", 4],
+	["🪀", "yo_yo", 4],
+	["🪁", "kite", 4],
+	["🦺", "safety_vest", 4],
+	["🪡", "sewing_needle", 4],
+	["🪢", "knot", 4],
+	["🕴", "business_suit_levitating", 4],
+	["🏆", "trophy", 4],
+	["🎽", "running_shirt_with_sash", 4],
+	["🏅", "medal_sports", 4],
+	["🎖", "medal_military", 4],
+	["🥇", "1st_place_medal", 4],
+	["🥈", "2nd_place_medal", 4],
+	["🥉", "3rd_place_medal", 4],
+	["🎗", "reminder_ribbon", 4],
+	["🏵", "rosette", 4],
+	["🎫", "ticket", 4],
+	["🎟", "tickets", 4],
+	["🎭", "performing_arts", 4],
+	["🎨", "art", 4],
+	["🎪", "circus_tent", 4],
+	["🤹‍♀️", "woman_juggling", 4],
+	["🤹‍♂️", "man_juggling", 4],
+	["🎤", "microphone", 4],
+	["🎧", "headphones", 4],
+	["🎼", "musical_score", 4],
+	["🎹", "musical_keyboard", 4],
+	["🥁", "drum", 4],
+	["🎷", "saxophone", 4],
+	["🎺", "trumpet", 4],
+	["🎸", "guitar", 4],
+	["🎻", "violin", 4],
+	["🪕", "banjo", 4],
+	["🪗", "accordion", 4],
+	["🪘", "long_drum", 4],
+	["🎬", "clapper", 4],
+	["🎮", "video_game", 4],
+	["👾", "space_invader", 4],
+	["🎯", "dart", 4],
+	["🎲", "game_die", 4],
+	["♟️", "chess_pawn", 4],
+	["🎰", "slot_machine", 4],
+	["🧩", "jigsaw", 4],
+	["🎳", "bowling", 4],
+	["🪄", "magic_wand", 4],
+	["🪅", "pinata", 4],
+	["🪆", "nesting_dolls", 4],
+	["🪬", "hamsa", 4],
+	["🪩", "mirror_ball", 4],
+	["🚗", "red_car", 5],
+	["🚕", "taxi", 5],
+	["🚙", "blue_car", 5],
+	["🚌", "bus", 5],
+	["🚎", "trolleybus", 5],
+	["🏎", "racing_car", 5],
+	["🚓", "police_car", 5],
+	["🚑", "ambulance", 5],
+	["🚒", "fire_engine", 5],
+	["🚐", "minibus", 5],
+	["🚚", "truck", 5],
+	["🚛", "articulated_lorry", 5],
+	["🚜", "tractor", 5],
+	["🛴", "kick_scooter", 5],
+	["🏍", "motorcycle", 5],
+	["🚲", "bike", 5],
+	["🛵", "motor_scooter", 5],
+	["🦽", "manual_wheelchair", 5],
+	["🦼", "motorized_wheelchair", 5],
+	["🛺", "auto_rickshaw", 5],
+	["🪂", "parachute", 5],
+	["🚨", "rotating_light", 5],
+	["🚔", "oncoming_police_car", 5],
+	["🚍", "oncoming_bus", 5],
+	["🚘", "oncoming_automobile", 5],
+	["🚖", "oncoming_taxi", 5],
+	["🚡", "aerial_tramway", 5],
+	["🚠", "mountain_cableway", 5],
+	["🚟", "suspension_railway", 5],
+	["🚃", "railway_car", 5],
+	["🚋", "train", 5],
+	["🚝", "monorail", 5],
+	["🚄", "bullettrain_side", 5],
+	["🚅", "bullettrain_front", 5],
+	["🚈", "light_rail", 5],
+	["🚞", "mountain_railway", 5],
+	["🚂", "steam_locomotive", 5],
+	["🚆", "train2", 5],
+	["🚇", "metro", 5],
+	["🚊", "tram", 5],
+	["🚉", "station", 5],
+	["🛸", "flying_saucer", 5],
+	["🚁", "helicopter", 5],
+	["🛩", "small_airplane", 5],
+	["✈️", "airplane", 5],
+	["🛫", "flight_departure", 5],
+	["🛬", "flight_arrival", 5],
+	["⛵", "sailboat", 5],
+	["🛥", "motor_boat", 5],
+	["🚤", "speedboat", 5],
+	["⛴", "ferry", 5],
+	["🛳", "passenger_ship", 5],
+	["🚀", "rocket", 5],
+	["🛰", "artificial_satellite", 5],
+	["🛻", "pickup_truck", 5],
+	["🛼", "roller_skate", 5],
+	["💺", "seat", 5],
+	["🛶", "canoe", 5],
+	["⚓", "anchor", 5],
+	["🚧", "construction", 5],
+	["⛽", "fuelpump", 5],
+	["🚏", "busstop", 5],
+	["🚦", "vertical_traffic_light", 5],
+	["🚥", "traffic_light", 5],
+	["🏁", "checkered_flag", 5],
+	["🚢", "ship", 5],
+	["🎡", "ferris_wheel", 5],
+	["🎢", "roller_coaster", 5],
+	["🎠", "carousel_horse", 5],
+	["🏗", "building_construction", 5],
+	["🌁", "foggy", 5],
+	["🏭", "factory", 5],
+	["⛲", "fountain", 5],
+	["🎑", "rice_scene", 5],
+	["⛰", "mountain", 5],
+	["🏔", "mountain_snow", 5],
+	["🗻", "mount_fuji", 5],
+	["🌋", "volcano", 5],
+	["🗾", "japan", 5],
+	["🏕", "camping", 5],
+	["⛺", "tent", 5],
+	["🏞", "national_park", 5],
+	["🛣", "motorway", 5],
+	["🛤", "railway_track", 5],
+	["🌅", "sunrise", 5],
+	["🌄", "sunrise_over_mountains", 5],
+	["🏜", "desert", 5],
+	["🏖", "beach_umbrella", 5],
+	["🏝", "desert_island", 5],
+	["🌇", "city_sunrise", 5],
+	["🌆", "city_sunset", 5],
+	["🏙", "cityscape", 5],
+	["🌃", "night_with_stars", 5],
+	["🌉", "bridge_at_night", 5],
+	["🌌", "milky_way", 5],
+	["🌠", "stars", 5],
+	["🎇", "sparkler", 5],
+	["🎆", "fireworks", 5],
+	["🌈", "rainbow", 5],
+	["🏘", "houses", 5],
+	["🏰", "european_castle", 5],
+	["🏯", "japanese_castle", 5],
+	["🗼", "tokyo_tower", 5],
+	["", "shibuya_109", 5],
+	["🏟", "stadium", 5],
+	["🗽", "statue_of_liberty", 5],
+	["🏠", "house", 5],
+	["🏡", "house_with_garden", 5],
+	["🏚", "derelict_house", 5],
+	["🏢", "office", 5],
+	["🏬", "department_store", 5],
+	["🏣", "post_office", 5],
+	["🏤", "european_post_office", 5],
+	["🏥", "hospital", 5],
+	["🏦", "bank", 5],
+	["🏨", "hotel", 5],
+	["🏪", "convenience_store", 5],
+	["🏫", "school", 5],
+	["🏩", "love_hotel", 5],
+	["💒", "wedding", 5],
+	["🏛", "classical_building", 5],
+	["⛪", "church", 5],
+	["🕌", "mosque", 5],
+	["🕍", "synagogue", 5],
+	["🕋", "kaaba", 5],
+	["⛩", "shinto_shrine", 5],
+	["🛕", "hindu_temple", 5],
+	["🪨", "rock", 5],
+	["🪵", "wood", 5],
+	["🛖", "hut", 5],
+	["🛝", "playground_slide", 5],
+	["🛞", "wheel", 5],
+	["🛟", "ring_buoy", 5],
+	["⌚", "watch", 6],
+	["📱", "iphone", 6],
+	["📲", "calling", 6],
+	["💻", "computer", 6],
+	["⌨", "keyboard", 6],
+	["🖥", "desktop_computer", 6],
+	["🖨", "printer", 6],
+	["🖱", "computer_mouse", 6],
+	["🖲", "trackball", 6],
+	["🕹", "joystick", 6],
+	["🗜", "clamp", 6],
+	["💽", "minidisc", 6],
+	["💾", "floppy_disk", 6],
+	["💿", "cd", 6],
+	["📀", "dvd", 6],
+	["📼", "vhs", 6],
+	["📷", "camera", 6],
+	["📸", "camera_flash", 6],
+	["📹", "video_camera", 6],
+	["🎥", "movie_camera", 6],
+	["📽", "film_projector", 6],
+	["🎞", "film_strip", 6],
+	["📞", "telephone_receiver", 6],
+	["☎️", "phone", 6],
+	["📟", "pager", 6],
+	["📠", "fax", 6],
+	["📺", "tv", 6],
+	["📻", "radio", 6],
+	["🎙", "studio_microphone", 6],
+	["🎚", "level_slider", 6],
+	["🎛", "control_knobs", 6],
+	["🧭", "compass", 6],
+	["⏱", "stopwatch", 6],
+	["⏲", "timer_clock", 6],
+	["⏰", "alarm_clock", 6],
+	["🕰", "mantelpiece_clock", 6],
+	["⏳", "hourglass_flowing_sand", 6],
+	["⌛", "hourglass", 6],
+	["📡", "satellite", 6],
+	["🔋", "battery", 6],
+	["🪫", "battery", 6],
+	["🔌", "electric_plug", 6],
+	["💡", "bulb", 6],
+	["🔦", "flashlight", 6],
+	["🕯", "candle", 6],
+	["🧯", "fire_extinguisher", 6],
+	["🗑", "wastebasket", 6],
+	["🛢", "oil_drum", 6],
+	["💸", "money_with_wings", 6],
+	["💵", "dollar", 6],
+	["💴", "yen", 6],
+	["💶", "euro", 6],
+	["💷", "pound", 6],
+	["💰", "moneybag", 6],
+	["🪙", "coin", 6],
+	["💳", "credit_card", 6],
+	["🪫", "identification_card", 6],
+	["💎", "gem", 6],
+	["⚖", "balance_scale", 6],
+	["🧰", "toolbox", 6],
+	["🔧", "wrench", 6],
+	["🔨", "hammer", 6],
+	["⚒", "hammer_and_pick", 6],
+	["🛠", "hammer_and_wrench", 6],
+	["⛏", "pick", 6],
+	["🪓", "axe", 6],
+	["🦯", "probing_cane", 6],
+	["🔩", "nut_and_bolt", 6],
+	["⚙", "gear", 6],
+	["🪃", "boomerang", 6],
+	["🪚", "carpentry_saw", 6],
+	["🪛", "screwdriver", 6],
+	["🪝", "hook", 6],
+	["🪜", "ladder", 6],
+	["🧱", "brick", 6],
+	["⛓", "chains", 6],
+	["🧲", "magnet", 6],
+	["🔫", "gun", 6],
+	["💣", "bomb", 6],
+	["🧨", "firecracker", 6],
+	["🔪", "hocho", 6],
+	["🗡", "dagger", 6],
+	["⚔", "crossed_swords", 6],
+	["🛡", "shield", 6],
+	["🚬", "smoking", 6],
+	["☠", "skull_and_crossbones", 6],
+	["⚰", "coffin", 6],
+	["⚱", "funeral_urn", 6],
+	["🏺", "amphora", 6],
+	["🔮", "crystal_ball", 6],
+	["📿", "prayer_beads", 6],
+	["🧿", "nazar_amulet", 6],
+	["💈", "barber", 6],
+	["⚗", "alembic", 6],
+	["🔭", "telescope", 6],
+	["🔬", "microscope", 6],
+	["🕳", "hole", 6],
+	["💊", "pill", 6],
+	["💉", "syringe", 6],
+	["🩸", "drop_of_blood", 6],
+	["🩹", "adhesive_bandage", 6],
+	["🩺", "stethoscope", 6],
+	["🪒", "razor", 6],
+	["🩻", "xray", 6],
+	["🩼", "crutch", 6],
+	["🧬", "dna", 6],
+	["🧫", "petri_dish", 6],
+	["🧪", "test_tube", 6],
+	["🌡", "thermometer", 6],
+	["🧹", "broom", 6],
+	["🧺", "basket", 6],
+	["🧻", "toilet_paper", 6],
+	["🏷", "label", 6],
+	["🔖", "bookmark", 6],
+	["🚽", "toilet", 6],
+	["🚿", "shower", 6],
+	["🛁", "bathtub", 6],
+	["🧼", "soap", 6],
+	["🧽", "sponge", 6],
+	["🧴", "lotion_bottle", 6],
+	["🔑", "key", 6],
+	["🗝", "old_key", 6],
+	["🛋", "couch_and_lamp", 6],
+	["🪔", "diya_Lamp", 6],
+	["🛌", "sleeping_bed", 6],
+	["🛏", "bed", 6],
+	["🚪", "door", 6],
+	["🪑", "chair", 6],
+	["🛎", "bellhop_bell", 6],
+	["🧸", "teddy_bear", 6],
+	["🖼", "framed_picture", 6],
+	["🗺", "world_map", 6],
+	["🛗", "elevator", 6],
+	["🪞", "mirror", 6],
+	["🪟", "window", 6],
+	["🪠", "plunger", 6],
+	["🪤", "mouse_trap", 6],
+	["🪣", "bucket", 6],
+	["🪥", "toothbrush", 6],
+	["🫧", "bubbles", 6],
+	["⛱", "parasol_on_ground", 6],
+	["🗿", "moyai", 6],
+	["🛍", "shopping", 6],
+	["🛒", "shopping_cart", 6],
+	["🎈", "balloon", 6],
+	["🎏", "flags", 6],
+	["🎀", "ribbon", 6],
+	["🎁", "gift", 6],
+	["🎊", "confetti_ball", 6],
+	["🎉", "tada", 6],
+	["🎎", "dolls", 6],
+	["🎐", "wind_chime", 6],
+	["🎌", "crossed_flags", 6],
+	["🏮", "izakaya_lantern", 6],
+	["🧧", "red_envelope", 6],
+	["✉️", "email", 6],
+	["📩", "envelope_with_arrow", 6],
+	["📨", "incoming_envelope", 6],
+	["📧", "e-mail", 6],
+	["💌", "love_letter", 6],
+	["📮", "postbox", 6],
+	["📪", "mailbox_closed", 6],
+	["📫", "mailbox", 6],
+	["📬", "mailbox_with_mail", 6],
+	["📭", "mailbox_with_no_mail", 6],
+	["📦", "package", 6],
+	["📯", "postal_horn", 6],
+	["📥", "inbox_tray", 6],
+	["📤", "outbox_tray", 6],
+	["📜", "scroll", 6],
+	["📃", "page_with_curl", 6],
+	["📑", "bookmark_tabs", 6],
+	["🧾", "receipt", 6],
+	["📊", "bar_chart", 6],
+	["📈", "chart_with_upwards_trend", 6],
+	["📉", "chart_with_downwards_trend", 6],
+	["📄", "page_facing_up", 6],
+	["📅", "date", 6],
+	["📆", "calendar", 6],
+	["🗓", "spiral_calendar", 6],
+	["📇", "card_index", 6],
+	["🗃", "card_file_box", 6],
+	["🗳", "ballot_box", 6],
+	["🗄", "file_cabinet", 6],
+	["📋", "clipboard", 6],
+	["🗒", "spiral_notepad", 6],
+	["📁", "file_folder", 6],
+	["📂", "open_file_folder", 6],
+	["🗂", "card_index_dividers", 6],
+	["🗞", "newspaper_roll", 6],
+	["📰", "newspaper", 6],
+	["📓", "notebook", 6],
+	["📕", "closed_book", 6],
+	["📗", "green_book", 6],
+	["📘", "blue_book", 6],
+	["📙", "orange_book", 6],
+	["📔", "notebook_with_decorative_cover", 6],
+	["📒", "ledger", 6],
+	["📚", "books", 6],
+	["📖", "open_book", 6],
+	["🧷", "safety_pin", 6],
+	["🔗", "link", 6],
+	["📎", "paperclip", 6],
+	["🖇", "paperclips", 6],
+	["✂️", "scissors", 6],
+	["📐", "triangular_ruler", 6],
+	["📏", "straight_ruler", 6],
+	["🧮", "abacus", 6],
+	["📌", "pushpin", 6],
+	["📍", "round_pushpin", 6],
+	["🚩", "triangular_flag_on_post", 6],
+	["🏳", "white_flag", 6],
+	["🏴", "black_flag", 6],
+	["🏳️‍🌈", "rainbow_flag", 6],
+	["🏳️‍⚧️", "transgender_flag", 6],
+	["🔐", "closed_lock_with_key", 6],
+	["🔒", "lock", 6],
+	["🔓", "unlock", 6],
+	["🔏", "lock_with_ink_pen", 6],
+	["🖊", "pen", 6],
+	["🖋", "fountain_pen", 6],
+	["✒️", "black_nib", 6],
+	["📝", "memo", 6],
+	["✏️", "pencil2", 6],
+	["🖍", "crayon", 6],
+	["🖌", "paintbrush", 6],
+	["🔍", "mag", 6],
+	["🔎", "mag_right", 6],
+	["🪦", "headstone", 6],
+	["🪧", "placard", 6],
+	["💯", "100", 7],
+	["🔢", "1234", 7],
+	["❤️", "heart", 7],
+	["🧡", "orange_heart", 7],
+	["💛", "yellow_heart", 7],
+	["💚", "green_heart", 7],
+	["💙", "blue_heart", 7],
+	["💜", "purple_heart", 7],
+	["🤎", "brown_heart", 7],
+	["🖤", "black_heart", 7],
+	["🤍", "white_heart", 7],
+	["💔", "broken_heart", 7],
+	["❣", "heavy_heart_exclamation", 7],
+	["💕", "two_hearts", 7],
+	["💞", "revolving_hearts", 7],
+	["💓", "heartbeat", 7],
+	["💗", "heartpulse", 7],
+	["💖", "sparkling_heart", 7],
+	["💘", "cupid", 7],
+	["💝", "gift_heart", 7],
+	["💟", "heart_decoration", 7],
+	["❤️‍🔥", "heart_on_fire", 7],
+	["❤️‍🩹", "mending_heart", 7],
+	["☮", "peace_symbol", 7],
+	["✝", "latin_cross", 7],
+	["☪", "star_and_crescent", 7],
+	["🕉", "om", 7],
+	["☸", "wheel_of_dharma", 7],
+	["✡", "star_of_david", 7],
+	["🔯", "six_pointed_star", 7],
+	["🕎", "menorah", 7],
+	["☯", "yin_yang", 7],
+	["☦", "orthodox_cross", 7],
+	["🛐", "place_of_worship", 7],
+	["⛎", "ophiuchus", 7],
+	["♈", "aries", 7],
+	["♉", "taurus", 7],
+	["♊", "gemini", 7],
+	["♋", "cancer", 7],
+	["♌", "leo", 7],
+	["♍", "virgo", 7],
+	["♎", "libra", 7],
+	["♏", "scorpius", 7],
+	["♐", "sagittarius", 7],
+	["♑", "capricorn", 7],
+	["♒", "aquarius", 7],
+	["♓", "pisces", 7],
+	["🆔", "id", 7],
+	["⚛", "atom_symbol", 7],
+	["⚧️", "transgender_symbol", 7],
+	["🈳", "u7a7a", 7],
+	["🈹", "u5272", 7],
+	["☢", "radioactive", 7],
+	["☣", "biohazard", 7],
+	["📴", "mobile_phone_off", 7],
+	["📳", "vibration_mode", 7],
+	["🈶", "u6709", 7],
+	["🈚", "u7121", 7],
+	["🈸", "u7533", 7],
+	["🈺", "u55b6", 7],
+	["🈷️", "u6708", 7],
+	["✴️", "eight_pointed_black_star", 7],
+	["🆚", "vs", 7],
+	["🉑", "accept", 7],
+	["💮", "white_flower", 7],
+	["🉐", "ideograph_advantage", 7],
+	["㊙️", "secret", 7],
+	["㊗️", "congratulations", 7],
+	["🈴", "u5408", 7],
+	["🈵", "u6e80", 7],
+	["🈲", "u7981", 7],
+	["🅰️", "a", 7],
+	["🅱️", "b", 7],
+	["🆎", "ab", 7],
+	["🆑", "cl", 7],
+	["🅾️", "o2", 7],
+	["🆘", "sos", 7],
+	["⛔", "no_entry", 7],
+	["📛", "name_badge", 7],
+	["🚫", "no_entry_sign", 7],
+	["❌", "x", 7],
+	["⭕", "o", 7],
+	["🛑", "stop_sign", 7],
+	["💢", "anger", 7],
+	["♨️", "hotsprings", 7],
+	["🚷", "no_pedestrians", 7],
+	["🚯", "do_not_litter", 7],
+	["🚳", "no_bicycles", 7],
+	["🚱", "non-potable_water", 7],
+	["🔞", "underage", 7],
+	["📵", "no_mobile_phones", 7],
+	["❗", "exclamation", 7],
+	["❕", "grey_exclamation", 7],
+	["❓", "question", 7],
+	["❔", "grey_question", 7],
+	["‼️", "bangbang", 7],
+	["⁉️", "interrobang", 7],
+	["🔅", "low_brightness", 7],
+	["🔆", "high_brightness", 7],
+	["🔱", "trident", 7],
+	["⚜", "fleur_de_lis", 7],
+	["〽️", "part_alternation_mark", 7],
+	["⚠️", "warning", 7],
+	["🚸", "children_crossing", 7],
+	["🔰", "beginner", 7],
+	["♻️", "recycle", 7],
+	["🈯", "u6307", 7],
+	["💹", "chart", 7],
+	["❇️", "sparkle", 7],
+	["✳️", "eight_spoked_asterisk", 7],
+	["❎", "negative_squared_cross_mark", 7],
+	["✅", "white_check_mark", 7],
+	["💠", "diamond_shape_with_a_dot_inside", 7],
+	["🌀", "cyclone", 7],
+	["➿", "loop", 7],
+	["🌐", "globe_with_meridians", 7],
+	["Ⓜ️", "m", 7],
+	["🏧", "atm", 7],
+	["🈂️", "sa", 7],
+	["🛂", "passport_control", 7],
+	["🛃", "customs", 7],
+	["🛄", "baggage_claim", 7],
+	["🛅", "left_luggage", 7],
+	["♿", "wheelchair", 7],
+	["🚭", "no_smoking", 7],
+	["🚾", "wc", 7],
+	["🅿️", "parking", 7],
+	["🚰", "potable_water", 7],
+	["🚹", "mens", 7],
+	["🚺", "womens", 7],
+	["🚼", "baby_symbol", 7],
+	["🚻", "restroom", 7],
+	["🚮", "put_litter_in_its_place", 7],
+	["🎦", "cinema", 7],
+	["📶", "signal_strength", 7],
+	["🈁", "koko", 7],
+	["🆖", "ng", 7],
+	["🆗", "ok", 7],
+	["🆙", "up", 7],
+	["🆒", "cool", 7],
+	["🆕", "new", 7],
+	["🆓", "free", 7],
+	["0️⃣", "zero", 7],
+	["1️⃣", "one", 7],
+	["2️⃣", "two", 7],
+	["3️⃣", "three", 7],
+	["4️⃣", "four", 7],
+	["5️⃣", "five", 7],
+	["6️⃣", "six", 7],
+	["7️⃣", "seven", 7],
+	["8️⃣", "eight", 7],
+	["9️⃣", "nine", 7],
+	["🔟", "keycap_ten", 7],
+	["*⃣", "asterisk", 7],
+	["⏏️", "eject_button", 7],
+	["▶️", "arrow_forward", 7],
+	["⏸", "pause_button", 7],
+	["⏭", "next_track_button", 7],
+	["⏹", "stop_button", 7],
+	["⏺", "record_button", 7],
+	["⏯", "play_or_pause_button", 7],
+	["⏮", "previous_track_button", 7],
+	["⏩", "fast_forward", 7],
+	["⏪", "rewind", 7],
+	["🔀", "twisted_rightwards_arrows", 7],
+	["🔁", "repeat", 7],
+	["🔂", "repeat_one", 7],
+	["◀️", "arrow_backward", 7],
+	["🔼", "arrow_up_small", 7],
+	["🔽", "arrow_down_small", 7],
+	["⏫", "arrow_double_up", 7],
+	["⏬", "arrow_double_down", 7],
+	["➡️", "arrow_right", 7],
+	["⬅️", "arrow_left", 7],
+	["⬆️", "arrow_up", 7],
+	["⬇️", "arrow_down", 7],
+	["↗️", "arrow_upper_right", 7],
+	["↘️", "arrow_lower_right", 7],
+	["↙️", "arrow_lower_left", 7],
+	["↖️", "arrow_upper_left", 7],
+	["↕️", "arrow_up_down", 7],
+	["↔️", "left_right_arrow", 7],
+	["🔄", "arrows_counterclockwise", 7],
+	["↪️", "arrow_right_hook", 7],
+	["↩️", "leftwards_arrow_with_hook", 7],
+	["⤴️", "arrow_heading_up", 7],
+	["⤵️", "arrow_heading_down", 7],
+	["#️⃣", "hash", 7],
+	["ℹ️", "information_source", 7],
+	["🔤", "abc", 7],
+	["🔡", "abcd", 7],
+	["🔠", "capital_abcd", 7],
+	["🔣", "symbols", 7],
+	["🎵", "musical_note", 7],
+	["🎶", "notes", 7],
+	["〰️", "wavy_dash", 7],
+	["➰", "curly_loop", 7],
+	["✔️", "heavy_check_mark", 7],
+	["🔃", "arrows_clockwise", 7],
+	["➕", "heavy_plus_sign", 7],
+	["➖", "heavy_minus_sign", 7],
+	["➗", "heavy_division_sign", 7],
+	["✖️", "heavy_multiplication_x", 7],
+	["🟰", "heavy_equals_sign", 7],
+	["♾", "infinity", 7],
+	["💲", "heavy_dollar_sign", 7],
+	["💱", "currency_exchange", 7],
+	["©️", "copyright", 7],
+	["®️", "registered", 7],
+	["™️", "tm", 7],
+	["🔚", "end", 7],
+	["🔙", "back", 7],
+	["🔛", "on", 7],
+	["🔝", "top", 7],
+	["🔜", "soon", 7],
+	["☑️", "ballot_box_with_check", 7],
+	["🔘", "radio_button", 7],
+	["⚫", "black_circle", 7],
+	["⚪", "white_circle", 7],
+	["🔴", "red_circle", 7],
+	["🟠", "orange_circle", 7],
+	["🟡", "yellow_circle", 7],
+	["🟢", "green_circle", 7],
+	["🔵", "large_blue_circle", 7],
+	["🟣", "purple_circle", 7],
+	["🟤", "brown_circle", 7],
+	["🔸", "small_orange_diamond", 7],
+	["🔹", "small_blue_diamond", 7],
+	["🔶", "large_orange_diamond", 7],
+	["🔷", "large_blue_diamond", 7],
+	["🔺", "small_red_triangle", 7],
+	["▪️", "black_small_square", 7],
+	["▫️", "white_small_square", 7],
+	["⬛", "black_large_square", 7],
+	["⬜", "white_large_square", 7],
+	["🟥", "red_square", 7],
+	["🟧", "orange_square", 7],
+	["🟨", "yellow_square", 7],
+	["🟩", "green_square", 7],
+	["🟦", "blue_square", 7],
+	["🟪", "purple_square", 7],
+	["🟫", "brown_square", 7],
+	["🔻", "small_red_triangle_down", 7],
+	["◼️", "black_medium_square", 7],
+	["◻️", "white_medium_square", 7],
+	["◾", "black_medium_small_square", 7],
+	["◽", "white_medium_small_square", 7],
+	["🔲", "black_square_button", 7],
+	["🔳", "white_square_button", 7],
+	["🔈", "speaker", 7],
+	["🔉", "sound", 7],
+	["🔊", "loud_sound", 7],
+	["🔇", "mute", 7],
+	["📣", "mega", 7],
+	["📢", "loudspeaker", 7],
+	["🔔", "bell", 7],
+	["🔕", "no_bell", 7],
+	["🃏", "black_joker", 7],
+	["🀄", "mahjong", 7],
+	["♠️", "spades", 7],
+	["♣️", "clubs", 7],
+	["♥️", "hearts", 7],
+	["♦️", "diamonds", 7],
+	["🎴", "flower_playing_cards", 7],
+	["💭", "thought_balloon", 7],
+	["🗯", "right_anger_bubble", 7],
+	["💬", "speech_balloon", 7],
+	["🗨", "left_speech_bubble", 7],
+	["🕐", "clock1", 7],
+	["🕑", "clock2", 7],
+	["🕒", "clock3", 7],
+	["🕓", "clock4", 7],
+	["🕔", "clock5", 7],
+	["🕕", "clock6", 7],
+	["🕖", "clock7", 7],
+	["🕗", "clock8", 7],
+	["🕘", "clock9", 7],
+	["🕙", "clock10", 7],
+	["🕚", "clock11", 7],
+	["🕛", "clock12", 7],
+	["🕜", "clock130", 7],
+	["🕝", "clock230", 7],
+	["🕞", "clock330", 7],
+	["🕟", "clock430", 7],
+	["🕠", "clock530", 7],
+	["🕡", "clock630", 7],
+	["🕢", "clock730", 7],
+	["🕣", "clock830", 7],
+	["🕤", "clock930", 7],
+	["🕥", "clock1030", 7],
+	["🕦", "clock1130", 7],
+	["🕧", "clock1230", 7],
+	["🇦🇫", "afghanistan", 8],
+	["🇦🇽", "aland_islands", 8],
+	["🇦🇱", "albania", 8],
+	["🇩🇿", "algeria", 8],
+	["🇦🇸", "american_samoa", 8],
+	["🇦🇩", "andorra", 8],
+	["🇦🇴", "angola", 8],
+	["🇦🇮", "anguilla", 8],
+	["🇦🇶", "antarctica", 8],
+	["🇦🇬", "antigua_barbuda", 8],
+	["🇦🇷", "argentina", 8],
+	["🇦🇲", "armenia", 8],
+	["🇦🇼", "aruba", 8],
+	["🇦🇨", "ascension_island", 8],
+	["🇦🇺", "australia", 8],
+	["🇦🇹", "austria", 8],
+	["🇦🇿", "azerbaijan", 8],
+	["🇧🇸", "bahamas", 8],
+	["🇧🇭", "bahrain", 8],
+	["🇧🇩", "bangladesh", 8],
+	["🇧🇧", "barbados", 8],
+	["🇧🇾", "belarus", 8],
+	["🇧🇪", "belgium", 8],
+	["🇧🇿", "belize", 8],
+	["🇧🇯", "benin", 8],
+	["🇧🇲", "bermuda", 8],
+	["🇧🇹", "bhutan", 8],
+	["🇧🇴", "bolivia", 8],
+	["🇧🇶", "caribbean_netherlands", 8],
+	["🇧🇦", "bosnia_herzegovina", 8],
+	["🇧🇼", "botswana", 8],
+	["🇧🇷", "brazil", 8],
+	["🇮🇴", "british_indian_ocean_territory", 8],
+	["🇻🇬", "british_virgin_islands", 8],
+	["🇧🇳", "brunei", 8],
+	["🇧🇬", "bulgaria", 8],
+	["🇧🇫", "burkina_faso", 8],
+	["🇧🇮", "burundi", 8],
+	["🇨🇻", "cape_verde", 8],
+	["🇰🇭", "cambodia", 8],
+	["🇨🇲", "cameroon", 8],
+	["🇨🇦", "canada", 8],
+	["🇮🇨", "canary_islands", 8],
+	["🇰🇾", "cayman_islands", 8],
+	["🇨🇫", "central_african_republic", 8],
+	["🇹🇩", "chad", 8],
+	["🇨🇱", "chile", 8],
+	["🇨🇳", "cn", 8],
+	["🇨🇽", "christmas_island", 8],
+	["🇨🇨", "cocos_islands", 8],
+	["🇨🇴", "colombia", 8],
+	["🇰🇲", "comoros", 8],
+	["🇨🇬", "congo_brazzaville", 8],
+	["🇨🇩", "congo_kinshasa", 8],
+	["🇨🇰", "cook_islands", 8],
+	["🇨🇷", "costa_rica", 8],
+	["🇭🇷", "croatia", 8],
+	["🇨🇺", "cuba", 8],
+	["🇨🇼", "curacao", 8],
+	["🇨🇾", "cyprus", 8],
+	["🇨🇿", "czech_republic", 8],
+	["🇩🇰", "denmark", 8],
+	["🇩🇯", "djibouti", 8],
+	["🇩🇲", "dominica", 8],
+	["🇩🇴", "dominican_republic", 8],
+	["🇪🇨", "ecuador", 8],
+	["🇪🇬", "egypt", 8],
+	["🇸🇻", "el_salvador", 8],
+	["🇬🇶", "equatorial_guinea", 8],
+	["🇪🇷", "eritrea", 8],
+	["🇪🇪", "estonia", 8],
+	["🇪🇹", "ethiopia", 8],
+	["🇪🇺", "eu", 8],
+	["🇫🇰", "falkland_islands", 8],
+	["🇫🇴", "faroe_islands", 8],
+	["🇫🇯", "fiji", 8],
+	["🇫🇮", "finland", 8],
+	["🇫🇷", "fr", 8],
+	["🇬🇫", "french_guiana", 8],
+	["🇵🇫", "french_polynesia", 8],
+	["🇹🇫", "french_southern_territories", 8],
+	["🇬🇦", "gabon", 8],
+	["🇬🇲", "gambia", 8],
+	["🇬🇪", "georgia", 8],
+	["🇩🇪", "de", 8],
+	["🇬🇭", "ghana", 8],
+	["🇬🇮", "gibraltar", 8],
+	["🇬🇷", "greece", 8],
+	["🇬🇱", "greenland", 8],
+	["🇬🇩", "grenada", 8],
+	["🇬🇵", "guadeloupe", 8],
+	["🇬🇺", "guam", 8],
+	["🇬🇹", "guatemala", 8],
+	["🇬🇬", "guernsey", 8],
+	["🇬🇳", "guinea", 8],
+	["🇬🇼", "guinea_bissau", 8],
+	["🇬🇾", "guyana", 8],
+	["🇭🇹", "haiti", 8],
+	["🇭🇳", "honduras", 8],
+	["🇭🇰", "hong_kong", 8],
+	["🇭🇺", "hungary", 8],
+	["🇮🇸", "iceland", 8],
+	["🇮🇳", "india", 8],
+	["🇮🇩", "indonesia", 8],
+	["🇮🇷", "iran", 8],
+	["🇮🇶", "iraq", 8],
+	["🇮🇪", "ireland", 8],
+	["🇮🇲", "isle_of_man", 8],
+	["🇮🇱", "israel", 8],
+	["🇮🇹", "it", 8],
+	["🇨🇮", "cote_divoire", 8],
+	["🇯🇲", "jamaica", 8],
+	["🇯🇵", "jp", 8],
+	["🇯🇪", "jersey", 8],
+	["🇯🇴", "jordan", 8],
+	["🇰🇿", "kazakhstan", 8],
+	["🇰🇪", "kenya", 8],
+	["🇰🇮", "kiribati", 8],
+	["🇽🇰", "kosovo", 8],
+	["🇰🇼", "kuwait", 8],
+	["🇰🇬", "kyrgyzstan", 8],
+	["🇱🇦", "laos", 8],
+	["🇱🇻", "latvia", 8],
+	["🇱🇧", "lebanon", 8],
+	["🇱🇸", "lesotho", 8],
+	["🇱🇷", "liberia", 8],
+	["🇱🇾", "libya", 8],
+	["🇱🇮", "liechtenstein", 8],
+	["🇱🇹", "lithuania", 8],
+	["🇱🇺", "luxembourg", 8],
+	["🇲🇴", "macau", 8],
+	["🇲🇰", "macedonia", 8],
+	["🇲🇬", "madagascar", 8],
+	["🇲🇼", "malawi", 8],
+	["🇲🇾", "malaysia", 8],
+	["🇲🇻", "maldives", 8],
+	["🇲🇱", "mali", 8],
+	["🇲🇹", "malta", 8],
+	["🇲🇭", "marshall_islands", 8],
+	["🇲🇶", "martinique", 8],
+	["🇲🇷", "mauritania", 8],
+	["🇲🇺", "mauritius", 8],
+	["🇾🇹", "mayotte", 8],
+	["🇲🇽", "mexico", 8],
+	["🇫🇲", "micronesia", 8],
+	["🇲🇩", "moldova", 8],
+	["🇲🇨", "monaco", 8],
+	["🇲🇳", "mongolia", 8],
+	["🇲🇪", "montenegro", 8],
+	["🇲🇸", "montserrat", 8],
+	["🇲🇦", "morocco", 8],
+	["🇲🇿", "mozambique", 8],
+	["🇲🇲", "myanmar", 8],
+	["🇳🇦", "namibia", 8],
+	["🇳🇷", "nauru", 8],
+	["🇳🇵", "nepal", 8],
+	["🇳🇱", "netherlands", 8],
+	["🇳🇨", "new_caledonia", 8],
+	["🇳🇿", "new_zealand", 8],
+	["🇳🇮", "nicaragua", 8],
+	["🇳🇪", "niger", 8],
+	["🇳🇬", "nigeria", 8],
+	["🇳🇺", "niue", 8],
+	["🇳🇫", "norfolk_island", 8],
+	["🇲🇵", "northern_mariana_islands", 8],
+	["🇰🇵", "north_korea", 8],
+	["🇳🇴", "norway", 8],
+	["🇴🇲", "oman", 8],
+	["🇵🇰", "pakistan", 8],
+	["🇵🇼", "palau", 8],
+	["🇵🇸", "palestinian_territories", 8],
+	["🇵🇦", "panama", 8],
+	["🇵🇬", "papua_new_guinea", 8],
+	["🇵🇾", "paraguay", 8],
+	["🇵🇪", "peru", 8],
+	["🇵🇭", "philippines", 8],
+	["🇵🇳", "pitcairn_islands", 8],
+	["🇵🇱", "poland", 8],
+	["🇵🇹", "portugal", 8],
+	["🇵🇷", "puerto_rico", 8],
+	["🇶🇦", "qatar", 8],
+	["🇷🇪", "reunion", 8],
+	["🇷🇴", "romania", 8],
+	["🇷🇺", "ru", 8],
+	["🇷🇼", "rwanda", 8],
+	["🇧🇱", "st_barthelemy", 8],
+	["🇸🇭", "st_helena", 8],
+	["🇰🇳", "st_kitts_nevis", 8],
+	["🇱🇨", "st_lucia", 8],
+	["🇵🇲", "st_pierre_miquelon", 8],
+	["🇻🇨", "st_vincent_grenadines", 8],
+	["🇼🇸", "samoa", 8],
+	["🇸🇲", "san_marino", 8],
+	["🇸🇹", "sao_tome_principe", 8],
+	["🇸🇦", "saudi_arabia", 8],
+	["🇸🇳", "senegal", 8],
+	["🇷🇸", "serbia", 8],
+	["🇸🇨", "seychelles", 8],
+	["🇸🇱", "sierra_leone", 8],
+	["🇸🇬", "singapore", 8],
+	["🇸🇽", "sint_maarten", 8],
+	["🇸🇰", "slovakia", 8],
+	["🇸🇮", "slovenia", 8],
+	["🇸🇧", "solomon_islands", 8],
+	["🇸🇴", "somalia", 8],
+	["🇿🇦", "south_africa", 8],
+	["🇬🇸", "south_georgia_south_sandwich_islands", 8],
+	["🇰🇷", "kr", 8],
+	["🇸🇸", "south_sudan", 8],
+	["🇪🇸", "es", 8],
+	["🇱🇰", "sri_lanka", 8],
+	["🇸🇩", "sudan", 8],
+	["🇸🇷", "suriname", 8],
+	["🇸🇿", "swaziland", 8],
+	["🇸🇪", "sweden", 8],
+	["🇨🇭", "switzerland", 8],
+	["🇸🇾", "syria", 8],
+	["🇹🇼", "taiwan", 8],
+	["🇹🇯", "tajikistan", 8],
+	["🇹🇿", "tanzania", 8],
+	["🇹🇭", "thailand", 8],
+	["🇹🇱", "timor_leste", 8],
+	["🇹🇬", "togo", 8],
+	["🇹🇰", "tokelau", 8],
+	["🇹🇴", "tonga", 8],
+	["🇹🇹", "trinidad_tobago", 8],
+	["🇹🇦", "tristan_da_cunha", 8],
+	["🇹🇳", "tunisia", 8],
+	["🇹🇷", "tr", 8],
+	["🇹🇲", "turkmenistan", 8],
+	["🇹🇨", "turks_caicos_islands", 8],
+	["🇹🇻", "tuvalu", 8],
+	["🇺🇬", "uganda", 8],
+	["🇺🇦", "ukraine", 8],
+	["🇦🇪", "united_arab_emirates", 8],
+	["🇬🇧", "uk", 8],
+	["🏴󠁧󠁢󠁥󠁮󠁧󠁿", "england", 8],
+	["🏴󠁧󠁢󠁳󠁣󠁴󠁿", "scotland", 8],
+	["🏴󠁧󠁢󠁷󠁬󠁳󠁿", "wales", 8],
+	["🇺🇸", "us", 8],
+	["🇻🇮", "us_virgin_islands", 8],
+	["🇺🇾", "uruguay", 8],
+	["🇺🇿", "uzbekistan", 8],
+	["🇻🇺", "vanuatu", 8],
+	["🇻🇦", "vatican_city", 8],
+	["🇻🇪", "venezuela", 8],
+	["🇻🇳", "vietnam", 8],
+	["🇼🇫", "wallis_futuna", 8],
+	["🇪🇭", "western_sahara", 8],
+	["🇾🇪", "yemen", 8],
+	["🇿🇲", "zambia", 8],
+	["🇿🇼", "zimbabwe", 8],
+	["🇺🇳", "united_nations", 8],
+	["🏴‍☠️", "pirate_flag", 8]
 ]
-
diff --git a/packages/frontend/src/i18n.ts b/packages/frontend/src/i18n.ts
index 220c6210c..30771ec1b 100644
--- a/packages/frontend/src/i18n.ts
+++ b/packages/frontend/src/i18n.ts
@@ -1,8 +1,9 @@
 import { markRaw } from 'vue';
+import type { Locale } from '../../../locales';
 import { locale } from '@/config';
 import { I18n } from '@/scripts/i18n';
 
-export const i18n = markRaw(new I18n(locale));
+export const i18n = markRaw(new I18n<Locale>(locale));
 
 export function updateI18n(newLocale) {
 	i18n.ts = newLocale;
diff --git a/packages/frontend/src/init.ts b/packages/frontend/src/init.ts
deleted file mode 100644
index 49e7bb400..000000000
--- a/packages/frontend/src/init.ts
+++ /dev/null
@@ -1,527 +0,0 @@
-/**
- * Client entry point
- */
-// https://vitejs.dev/config/build-options.html#build-modulepreload
-import 'vite/modulepreload-polyfill';
-
-import '@/style.scss';
-
-import { computed, createApp, watch, markRaw, version as vueVersion, defineAsyncComponent } from 'vue';
-import { compareVersions } from 'compare-versions';
-import JSON5 from 'json5';
-
-import widgets from '@/widgets';
-import directives from '@/directives';
-import components from '@/components';
-import { version, ui, lang, updateLocale } from '@/config';
-import { applyTheme } from '@/scripts/theme';
-import { isDeviceDarkmode } from '@/scripts/is-device-darkmode';
-import { i18n, updateI18n } from '@/i18n';
-import { confirm, alert, post, popup, toast } from '@/os';
-import { stream } from '@/stream';
-import * as sound from '@/scripts/sound';
-import { $i, refreshAccount, login, updateAccount, signout } from '@/account';
-import { defaultStore, ColdDeviceStorage } from '@/store';
-import { fetchInstance, instance } from '@/instance';
-import { makeHotkey } from '@/scripts/hotkey';
-import { deviceKind } from '@/scripts/device-kind';
-import { initializeSw } from '@/scripts/initialize-sw';
-import { reloadChannel } from '@/scripts/unison-reload';
-import { reactionPicker } from '@/scripts/reaction-picker';
-import { getUrlWithoutLoginId } from '@/scripts/login-id';
-import { getAccountFromId } from '@/scripts/get-account-from-id';
-import { deckStore } from '@/ui/deck/deck-store';
-import { miLocalStorage } from '@/local-storage';
-import { claimAchievement, claimedAchievements } from '@/scripts/achievements';
-import { fetchCustomEmojis } from '@/custom-emojis';
-import { mainRouter } from '@/router';
-
-console.info(`Misskey v${version}`);
-
-if (_DEV_) {
-	console.warn('Development mode!!!');
-
-	console.info(`vue ${vueVersion}`);
-
-	// eslint-disable-next-line @typescript-eslint/no-explicit-any
-	(window as any).$i = $i;
-	// eslint-disable-next-line @typescript-eslint/no-explicit-any
-	(window as any).$store = defaultStore;
-
-	window.addEventListener('error', event => {
-		console.error(event);
-		/*
-		alert({
-			type: 'error',
-			title: 'DEV: Unhandled error',
-			text: event.message
-		});
-		*/
-	});
-
-	window.addEventListener('unhandledrejection', event => {
-		console.error(event);
-		/*
-		alert({
-			type: 'error',
-			title: 'DEV: Unhandled promise rejection',
-			text: event.reason
-		});
-		*/
-	});
-}
-
-//#region Detect language & fetch translations
-const localeVersion = miLocalStorage.getItem('localeVersion');
-const localeOutdated = (localeVersion == null || localeVersion !== version);
-if (localeOutdated) {
-	const res = await window.fetch(`/assets/locales/${lang}.${version}.json`);
-	if (res.status === 200) {
-		const newLocale = await res.text();
-		const parsedNewLocale = JSON.parse(newLocale);
-		miLocalStorage.setItem('locale', newLocale);
-		miLocalStorage.setItem('localeVersion', version);
-		updateLocale(parsedNewLocale);
-		updateI18n(parsedNewLocale);
-	}
-}
-//#endregion
-
-// タッチデバイスでCSSの:hoverを機能させる
-document.addEventListener('touchend', () => {}, { passive: true });
-
-// 一斉リロード
-reloadChannel.addEventListener('message', path => {
-	if (path !== null) location.href = path;
-	else location.reload();
-});
-
-// If mobile, insert the viewport meta tag
-if (['smartphone', 'tablet'].includes(deviceKind)) {
-	const viewport = document.getElementsByName('viewport').item(0);
-	viewport.setAttribute('content',
-		`${viewport.getAttribute('content')}, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover`);
-}
-
-//#region Set lang attr
-const html = document.documentElement;
-html.setAttribute('lang', lang);
-//#endregion
-
-//#region loginId
-const params = new URLSearchParams(location.search);
-const loginId = params.get('loginId');
-
-if (loginId) {
-	const target = getUrlWithoutLoginId(location.href);
-
-	if (!$i || $i.id !== loginId) {
-		const account = await getAccountFromId(loginId);
-		if (account) {
-			await login(account.token, target);
-		}
-	}
-
-	history.replaceState({ misskey: 'loginId' }, '', target);
-}
-
-//#endregion
-
-//#region Fetch user
-if ($i && $i.token) {
-	if (_DEV_) {
-		console.log('account cache found. refreshing...');
-	}
-
-	refreshAccount();
-} else {
-	if (_DEV_) {
-		console.log('no account cache found.');
-	}
-
-	// 連携ログインの場合用にCookieを参照する
-	const i = (document.cookie.match(/igi=(\w+)/) ?? [null, null])[1];
-
-	if (i != null && i !== 'null') {
-		if (_DEV_) {
-			console.log('signing...');
-		}
-
-		try {
-			document.body.innerHTML = '<div>Please wait...</div>';
-			await login(i);
-		} catch (err) {
-			// Render the error screen
-			// TODO: ちゃんとしたコンポーネントをレンダリングする(v10とかのトラブルシューティングゲーム付きのやつみたいな)
-			document.body.innerHTML = '<div id="err">Oops!</div>';
-		}
-	} else {
-		if (_DEV_) {
-			console.log('not signed in');
-		}
-	}
-}
-//#endregion
-
-const fetchInstanceMetaPromise = fetchInstance();
-
-fetchInstanceMetaPromise.then(() => {
-	miLocalStorage.setItem('v', instance.version);
-
-	// Init service worker
-	initializeSw();
-});
-
-try {
-	await fetchCustomEmojis();
-} catch (err) { /* empty */ }
-
-const app = createApp(
-	new URLSearchParams(window.location.search).has('zen') ? defineAsyncComponent(() => import('@/ui/zen.vue')) :
-	!$i ? defineAsyncComponent(() => import('@/ui/visitor.vue')) :
-	ui === 'deck' ? defineAsyncComponent(() => import('@/ui/deck.vue')) :
-	ui === 'classic' ? defineAsyncComponent(() => import('@/ui/classic.vue')) :
-	defineAsyncComponent(() => import('@/ui/universal.vue')),
-);
-
-if (_DEV_) {
-	app.config.performance = true;
-}
-
-widgets(app);
-directives(app);
-components(app);
-
-const splash = document.getElementById('splash');
-// 念のためnullチェック(HTMLが古い場合があるため(そのうち消す))
-if (splash) splash.addEventListener('transitionend', () => {
-	splash.remove();
-});
-
-await deckStore.ready;
-
-// https://github.com/misskey-dev/misskey/pull/8575#issuecomment-1114239210
-// なぜかinit.tsの内容が2回実行されることがあるため、mountするdivを1つに制限する
-const rootEl = ((): HTMLElement => {
-	const MISSKEY_MOUNT_DIV_ID = 'misskey_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;
-})();
-
-app.mount(rootEl);
-
-// boot.jsのやつを解除
-window.onerror = null;
-window.onunhandledrejection = null;
-
-reactionPicker.init();
-
-if (splash) {
-	splash.style.opacity = '0';
-	splash.style.pointerEvents = 'none';
-}
-
-// クライアントが更新されたか?
-const lastVersion = miLocalStorage.getItem('lastVersion');
-if (lastVersion !== version) {
-	miLocalStorage.setItem('lastVersion', version);
-
-	// テーマリビルドするため
-	miLocalStorage.removeItem('theme');
-
-	try { // 変なバージョン文字列来るとcompareVersionsでエラーになるため
-		if (lastVersion != null && compareVersions(version, lastVersion) === 1) {
-			// ログインしてる場合だけ
-			if ($i) {
-				popup(defineAsyncComponent(() => import('@/components/MkUpdated.vue')), {}, {}, 'closed');
-			}
-		}
-	} catch (err) { /* empty */ }
-}
-
-await defaultStore.ready;
-
-// NOTE: この処理は必ず↑のクライアント更新時処理より後に来ること(テーマ再構築のため)
-watch(defaultStore.reactiveState.darkMode, (darkMode) => {
-	applyTheme(darkMode ? ColdDeviceStorage.get('darkTheme') : ColdDeviceStorage.get('lightTheme'));
-}, { immediate: miLocalStorage.getItem('theme') == null });
-
-const darkTheme = computed(ColdDeviceStorage.makeGetterSetter('darkTheme'));
-const lightTheme = computed(ColdDeviceStorage.makeGetterSetter('lightTheme'));
-
-watch(darkTheme, (theme) => {
-	if (defaultStore.state.darkMode) {
-		applyTheme(theme);
-	}
-});
-
-watch(lightTheme, (theme) => {
-	if (!defaultStore.state.darkMode) {
-		applyTheme(theme);
-	}
-});
-
-//#region Sync dark mode
-if (ColdDeviceStorage.get('syncDeviceDarkMode')) {
-	defaultStore.set('darkMode', isDeviceDarkmode());
-}
-
-window.matchMedia('(prefers-color-scheme: dark)').addListener(mql => {
-	if (ColdDeviceStorage.get('syncDeviceDarkMode')) {
-		defaultStore.set('darkMode', mql.matches);
-	}
-});
-//#endregion
-
-fetchInstanceMetaPromise.then(() => {
-	if (defaultStore.state.themeInitial) {
-		if (instance.defaultLightTheme != null) ColdDeviceStorage.set('lightTheme', JSON5.parse(instance.defaultLightTheme));
-		if (instance.defaultDarkTheme != null) ColdDeviceStorage.set('darkTheme', JSON5.parse(instance.defaultDarkTheme));
-		defaultStore.set('themeInitial', false);
-	}
-});
-
-watch(defaultStore.reactiveState.useBlurEffectForModal, v => {
-	document.documentElement.style.setProperty('--modalBgFilter', v ? 'blur(4px)' : 'none');
-}, { immediate: true });
-
-watch(defaultStore.reactiveState.useBlurEffect, v => {
-	if (v) {
-		document.documentElement.style.removeProperty('--blur');
-	} else {
-		document.documentElement.style.setProperty('--blur', 'none');
-	}
-}, { immediate: true });
-
-let reloadDialogShowing = false;
-stream.on('_disconnected_', async () => {
-	if (defaultStore.state.serverDisconnectedBehavior === 'reload') {
-		location.reload();
-	} else if (defaultStore.state.serverDisconnectedBehavior === 'dialog') {
-		if (reloadDialogShowing) return;
-		reloadDialogShowing = true;
-		const { canceled } = await confirm({
-			type: 'warning',
-			title: i18n.ts.disconnectedFromServer,
-			text: i18n.ts.reloadConfirm,
-		});
-		reloadDialogShowing = false;
-		if (!canceled) {
-			location.reload();
-		}
-	}
-});
-
-for (const plugin of ColdDeviceStorage.get('plugins').filter(p => p.active)) {
-	import('./plugin').then(async ({ install }) => {
-		// Workaround for https://bugs.webkit.org/show_bug.cgi?id=242740
-		await new Promise(r => setTimeout(r, 0));
-		install(plugin);
-	});
-}
-
-const hotkeys = {
-	'd': (): void => {
-		defaultStore.set('darkMode', !defaultStore.state.darkMode);
-	},
-	's': (): void => {
-		mainRouter.push('/search');
-	},
-};
-
-if ($i) {
-	// only add post shortcuts if logged in
-	hotkeys['p|n'] = post;
-
-	if (defaultStore.state.accountSetupWizard !== -1) {
-		// このウィザードが実装される前に登録したユーザーには表示させないため
-		// TODO: そのうち消す
-		if (Date.now() - new Date($i.createdAt).getTime() < 1000 * 60 * 60 * 24) {
-			popup(defineAsyncComponent(() => import('@/components/MkUserSetupDialog.vue')), {}, {}, 'closed');
-		} else {
-			defaultStore.set('accountSetupWizard', -1);
-		}
-	}
-
-	if ($i.isDeleted) {
-		alert({
-			type: 'warning',
-			text: i18n.ts.accountDeletionInProgress,
-		});
-	}
-
-	const now = new Date();
-	const m = now.getMonth() + 1;
-	const d = now.getDate();
-	
-	if ($i.birthday) {
-		const bm = parseInt($i.birthday.split('-')[1]);
-		const bd = parseInt($i.birthday.split('-')[2]);
-		if (m === bm && d === bd) {
-			claimAchievement('loggedInOnBirthday');
-		}
-	}
-
-	if (m === 1 && d === 1) {
-		claimAchievement('loggedInOnNewYearsDay');
-	}
-
-	if ($i.loggedInDays >= 3) claimAchievement('login3');
-	if ($i.loggedInDays >= 7) claimAchievement('login7');
-	if ($i.loggedInDays >= 15) claimAchievement('login15');
-	if ($i.loggedInDays >= 30) claimAchievement('login30');
-	if ($i.loggedInDays >= 60) claimAchievement('login60');
-	if ($i.loggedInDays >= 100) claimAchievement('login100');
-	if ($i.loggedInDays >= 200) claimAchievement('login200');
-	if ($i.loggedInDays >= 300) claimAchievement('login300');
-	if ($i.loggedInDays >= 400) claimAchievement('login400');
-	if ($i.loggedInDays >= 500) claimAchievement('login500');
-	if ($i.loggedInDays >= 600) claimAchievement('login600');
-	if ($i.loggedInDays >= 700) claimAchievement('login700');
-	if ($i.loggedInDays >= 800) claimAchievement('login800');
-	if ($i.loggedInDays >= 900) claimAchievement('login900');
-	if ($i.loggedInDays >= 1000) claimAchievement('login1000');
-
-	if ($i.notesCount > 0) claimAchievement('notes1');
-	if ($i.notesCount >= 10) claimAchievement('notes10');
-	if ($i.notesCount >= 100) claimAchievement('notes100');
-	if ($i.notesCount >= 500) claimAchievement('notes500');
-	if ($i.notesCount >= 1000) claimAchievement('notes1000');
-	if ($i.notesCount >= 5000) claimAchievement('notes5000');
-	if ($i.notesCount >= 10000) claimAchievement('notes10000');
-	if ($i.notesCount >= 20000) claimAchievement('notes20000');
-	if ($i.notesCount >= 30000) claimAchievement('notes30000');
-	if ($i.notesCount >= 40000) claimAchievement('notes40000');
-	if ($i.notesCount >= 50000) claimAchievement('notes50000');
-	if ($i.notesCount >= 60000) claimAchievement('notes60000');
-	if ($i.notesCount >= 70000) claimAchievement('notes70000');
-	if ($i.notesCount >= 80000) claimAchievement('notes80000');
-	if ($i.notesCount >= 90000) claimAchievement('notes90000');
-	if ($i.notesCount >= 100000) claimAchievement('notes100000');
-
-	if ($i.followersCount > 0) claimAchievement('followers1');
-	if ($i.followersCount >= 10) claimAchievement('followers10');
-	if ($i.followersCount >= 50) claimAchievement('followers50');
-	if ($i.followersCount >= 100) claimAchievement('followers100');
-	if ($i.followersCount >= 300) claimAchievement('followers300');
-	if ($i.followersCount >= 500) claimAchievement('followers500');
-	if ($i.followersCount >= 1000) claimAchievement('followers1000');
-
-	if (Date.now() - new Date($i.createdAt).getTime() > 1000 * 60 * 60 * 24 * 365) {
-		claimAchievement('passedSinceAccountCreated1');
-	}
-	if (Date.now() - new Date($i.createdAt).getTime() > 1000 * 60 * 60 * 24 * 365 * 2) {
-		claimAchievement('passedSinceAccountCreated2');
-	}
-	if (Date.now() - new Date($i.createdAt).getTime() > 1000 * 60 * 60 * 24 * 365 * 3) {
-		claimAchievement('passedSinceAccountCreated3');
-	}
-
-	if (claimedAchievements.length >= 30) {
-		claimAchievement('collectAchievements30');
-	}
-
-	window.setInterval(() => {
-		if (Math.floor(Math.random() * 20000) === 0) {
-			claimAchievement('justPlainLucky');
-		}
-	}, 1000 * 10);
-
-	window.setTimeout(() => {
-		claimAchievement('client30min');
-	}, 1000 * 60 * 30);
-
-	window.setTimeout(() => {
-		claimAchievement('client60min');
-	}, 1000 * 60 * 60);
-
-	const lastUsed = miLocalStorage.getItem('lastUsed');
-	if (lastUsed) {
-		const lastUsedDate = parseInt(lastUsed, 10);
-		// 二時間以上前なら
-		if (Date.now() - lastUsedDate > 1000 * 60 * 60 * 2) {
-			toast(i18n.t('welcomeBackWithName', {
-				name: $i.name || $i.username,
-			}));
-		}
-	}
-	miLocalStorage.setItem('lastUsed', Date.now().toString());
-
-	const latestDonationInfoShownAt = miLocalStorage.getItem('latestDonationInfoShownAt');
-	const neverShowDonationInfo = miLocalStorage.getItem('neverShowDonationInfo');
-	if (neverShowDonationInfo !== 'true' && (new Date($i.createdAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 3))) && !location.pathname.startsWith('/miauth')) {
-		if (latestDonationInfoShownAt == null || (new Date(latestDonationInfoShownAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 30)))) {
-			popup(defineAsyncComponent(() => import('@/components/MkDonation.vue')), {}, {}, 'closed');
-		}
-	}
-
-	if ('Notification' in window) {
-		// 許可を得ていなかったらリクエスト
-		if (Notification.permission === 'default') {
-			Notification.requestPermission();
-		}
-	}
-
-	const main = markRaw(stream.useChannel('main', null, 'System'));
-
-	// 自分の情報が更新されたとき
-	main.on('meUpdated', i => {
-		updateAccount(i);
-	});
-
-	main.on('readAllNotifications', () => {
-		updateAccount({ hasUnreadNotification: false });
-	});
-
-	main.on('unreadNotification', () => {
-		updateAccount({ hasUnreadNotification: true });
-	});
-
-	main.on('unreadMention', () => {
-		updateAccount({ hasUnreadMentions: true });
-	});
-
-	main.on('readAllUnreadMentions', () => {
-		updateAccount({ hasUnreadMentions: false });
-	});
-
-	main.on('unreadSpecifiedNote', () => {
-		updateAccount({ hasUnreadSpecifiedNotes: true });
-	});
-
-	main.on('readAllUnreadSpecifiedNotes', () => {
-		updateAccount({ hasUnreadSpecifiedNotes: false });
-	});
-
-	main.on('readAllAntennas', () => {
-		updateAccount({ hasUnreadAntenna: false });
-	});
-
-	main.on('unreadAntenna', () => {
-		updateAccount({ hasUnreadAntenna: true });
-		sound.play('antenna');
-	});
-
-	main.on('readAllAnnouncements', () => {
-		updateAccount({ hasUnreadAnnouncement: false });
-	});
-
-	// トークンが再生成されたとき
-	// このままではMisskeyが利用できないので強制的にサインアウトさせる
-	main.on('myTokenRegenerated', () => {
-		signout();
-	});
-}
-
-// shortcut
-document.addEventListener('keydown', makeHotkey(hotkeys));
diff --git a/packages/frontend/src/local-storage.ts b/packages/frontend/src/local-storage.ts
index 441a35747..ca4f21f79 100644
--- a/packages/frontend/src/local-storage.ts
+++ b/packages/frontend/src/local-storage.ts
@@ -13,7 +13,7 @@ type Keys =
 	'hashtags' |
 	'wallpaper' |
 	'theme' |
-	'colorSchema' |
+	'colorScheme' |
 	'useSystemFont' | 
 	'fontSize' |
 	'ui' |
diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts
index c4f9d47d7..c44d34804 100644
--- a/packages/frontend/src/os.ts
+++ b/packages/frontend/src/os.ts
@@ -172,12 +172,6 @@ export function pageWindow(path: string) {
 	}, {}, 'closed');
 }
 
-export function modalPageWindow(path: string) {
-	popup(defineAsyncComponent(() => import('@/components/MkModalPageWindow.vue')), {
-		initialPath: path,
-	}, {}, 'closed');
-}
-
 export function toast(message: string) {
 	popup(MkToast, {
 		message,
diff --git a/packages/frontend/src/pages/_error_.vue b/packages/frontend/src/pages/_error_.vue
index f53fec7d9..f27d2df33 100644
--- a/packages/frontend/src/pages/_error_.vue
+++ b/packages/frontend/src/pages/_error_.vue
@@ -1,18 +1,20 @@
 <template>
 <MkLoading v-if="!loaded"/>
 <Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" appear>
-	<div v-show="loaded" class="mjndxjch">
-		<img src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/>
-		<p><b><i class="ti ti-alert-triangle"></i> {{ i18n.ts.pageLoadError }}</b></p>
-		<p v-if="meta && (version === meta.version)">{{ i18n.ts.pageLoadErrorDescription }}</p>
-		<p v-else-if="serverIsDead">{{ i18n.ts.serverIsDead }}</p>
-		<template v-else>
-			<p>{{ i18n.ts.newVersionOfClientAvailable }}</p>
-			<p>{{ i18n.ts.youShouldUpgradeClient }}</p>
-			<MkButton class="button primary" @click="reload">{{ i18n.ts.reload }}</MkButton>
-		</template>
-		<p><MkA to="/docs/general/troubleshooting" class="_link">{{ i18n.ts.troubleshooting }}</MkA></p>
-		<p v-if="error" class="error">ERROR: {{ error }}</p>
+	<div v-show="loaded" :class="$style.root">
+		<img src="https://xn--931a.moe/assets/error.jpg" class="_ghost" :class="$style.img"/>
+		<div class="_gaps">
+			<p><b><i class="ti ti-alert-triangle"></i> {{ i18n.ts.pageLoadError }}</b></p>
+			<p v-if="meta && (version === meta.version)">{{ i18n.ts.pageLoadErrorDescription }}</p>
+			<p v-else-if="serverIsDead">{{ i18n.ts.serverIsDead }}</p>
+			<template v-else>
+				<p>{{ i18n.ts.newVersionOfClientAvailable }}</p>
+				<p>{{ i18n.ts.youShouldUpgradeClient }}</p>
+				<MkButton style="margin: 8px auto;" @click="reload">{{ i18n.ts.reload }}</MkButton>
+			</template>
+			<p><MkA to="/docs/general/troubleshooting" class="_link">{{ i18n.ts.troubleshooting }}</MkA></p>
+			<p v-if="error" style="opacity: 0.7;">ERROR: {{ error }}</p>
+		</div>
 	</div>
 </Transition>
 </template>
@@ -64,28 +66,16 @@ definePageMetadata({
 });
 </script>
 
-<style lang="scss" scoped>
-.mjndxjch {
+<style lang="scss" module>
+.root {
 	padding: 32px;
 	text-align: center;
+}
 
-	> p {
-		margin: 0 0 12px 0;
-	}
-
-	> .button {
-		margin: 8px auto;
-	}
-
-	> img {
-		vertical-align: bottom;
-		height: 128px;
-		margin-bottom: 24px;
-		border-radius: 16px;
-	}
-
-	> .error {
-		opacity: 0.7;
-	}
+.img {
+	vertical-align: bottom;
+	height: 128px;
+	margin-bottom: 24px;
+	border-radius: 16px;
 }
 </style>
diff --git a/packages/frontend/src/pages/about-misskey.vue b/packages/frontend/src/pages/about-misskey.vue
index 9e0594db3..0017145fa 100644
--- a/packages/frontend/src/pages/about-misskey.vue
+++ b/packages/frontend/src/pages/about-misskey.vue
@@ -2,7 +2,7 @@
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
 	<div style="overflow: clip;">
-		<MkSpacer :content-max="600" :margin-min="20">
+		<MkSpacer :contentMax="600" :marginMin="20">
 			<div class="_gaps_m znqjceqz">
 				<div v-panel class="about">
 					<div ref="containerEl" class="container" :class="{ playing: easterEggEngine != null }">
@@ -10,8 +10,8 @@
 						<div class="misskey">Misskey</div>
 						<div class="version">v{{ version }}</div>
 						<span v-for="emoji in easterEggEmojis" :key="emoji.id" class="emoji" :data-physics-x="emoji.left" :data-physics-y="emoji.top" :class="{ _physics_circle_: !emoji.emoji.startsWith(':') }">
-							<MkCustomEmoji v-if="emoji.emoji[0] === ':'" class="emoji" :name="emoji.emoji" :normal="true" :no-style="true"/>
-							<MkEmoji v-else class="emoji" :emoji="emoji.emoji" :normal="true" :no-style="true"/>
+							<MkCustomEmoji v-if="emoji.emoji[0] === ':'" class="emoji" :name="emoji.emoji" :normal="true" :noStyle="true"/>
+							<MkEmoji v-else class="emoji" :emoji="emoji.emoji" :normal="true" :noStyle="true"/>
 						</span>
 					</div>
 					<button v-if="thereIsTreasure" class="_button treasure" @click="getTreasure"><img src="/fluent-emoji/1f3c6.png" class="treasureImg"></button>
@@ -86,8 +86,13 @@
 				</FormSection>
 				<FormSection>
 					<template #label>Special thanks</template>
-					<div style="text-align: center;">
-						<a style="display: inline-block;" class="dcadvirth" title="DC Advirth" href="https://www.dotchain.ltd/advirth" target="_blank"><img width="200" src="https://misskey-hub.net/sponsors/dcadvirth.png" alt="DC Advirth"></a>
+					<div class="_gaps" style="text-align: center;">
+						<div>
+							<a style="display: inline-block;" class="masknetwork" title="Mask Network" href="https://mask.io/" target="_blank"><img width="200" src="https://misskey-hub.net/sponsors/masknetwork.png" alt="Mask Network"></a>
+						</div>
+						<div>
+							<a style="display: inline-block;" class="dcadvirth" title="DC Advirth" href="https://www.dotchain.ltd/advirth" target="_blank"><img width="200" src="https://misskey-hub.net/sponsors/dcadvirth.png" alt="DC Advirth"></a>
+						</div>
 					</div>
 				</FormSection>
 			</div>
@@ -144,6 +149,12 @@ const patronsWithIcon = [{
 }, {
 	name: 'かみらえっと',
 	icon: 'https://misskey-hub.net/patrons/be1326bda7d940a482f3758ffd9ffaf6.jpg',
+}, {
+	name: 'へてて',
+	icon: 'https://misskey-hub.net/patrons/0431eacd7c6843d09de8ea9984307e86.jpg',
+}, {
+	name: 'spinlock',
+	icon: 'https://misskey-hub.net/patrons/6a1cebc819d540a78bf20e9e3115baa8.jpg',
 }];
 
 const patrons = [
diff --git a/packages/frontend/src/pages/about.emojis.vue b/packages/frontend/src/pages/about.emojis.vue
index 2d82fcf27..3744bed10 100644
--- a/packages/frontend/src/pages/about.emojis.vue
+++ b/packages/frontend/src/pages/about.emojis.vue
@@ -1,5 +1,5 @@
 <template>
-<div class="driuhtrh _gaps">
+<div class="_gaps">
 	<MkButton v-if="$i && ($i.isModerator || $i.policies.canManageCustomEmojis)" primary link to="/custom-emojis-manager">{{ i18n.ts.manageCustomEmojis }}</MkButton>
 
 	<div class="query">
@@ -14,17 +14,17 @@
 		-->
 	</div>
 
-	<MkFoldableSection v-if="searchEmojis" class="emojis">
+	<MkFoldableSection v-if="searchEmojis">
 		<template #header>{{ i18n.ts.searchResult }}</template>
-		<div class="zuvgdzyt">
-			<XEmoji v-for="emoji in searchEmojis" :key="emoji.name" class="emoji" :emoji="emoji"/>
+		<div :class="$style.emojis">
+			<XEmoji v-for="emoji in searchEmojis" :key="emoji.name" :emoji="emoji"/>
 		</div>
 	</MkFoldableSection>
 	
-	<MkFoldableSection v-for="category in customEmojiCategories" v-once :key="category" class="emojis">
+	<MkFoldableSection v-for="category in customEmojiCategories" v-once :key="category">
 		<template #header>{{ category || i18n.ts.other }}</template>
-		<div class="zuvgdzyt">
-			<XEmoji v-for="emoji in customEmojis.filter(e => e.category === category)" :key="emoji.name" class="emoji" :emoji="emoji"/>
+		<div :class="$style.emojis">
+			<XEmoji v-for="emoji in customEmojis.filter(e => e.category === category)" :key="emoji.name" :emoji="emoji"/>
 		</div>
 	</MkFoldableSection>
 </div>
@@ -57,7 +57,7 @@ function search() {
 
 		if (queryarry) {
 			searchEmojis = customEmojis.value.filter(emoji => 
-				queryarry.includes(`:${emoji.name}:`)
+				queryarry.includes(`:${emoji.name}:`),
 			);
 		} else {
 			searchEmojis = customEmojis.value.filter(emoji => emoji.name.includes(q) || emoji.aliases.includes(q));
@@ -84,36 +84,10 @@ watch($$(selectedTags), () => {
 }, { deep: true });
 </script>
 
-<style lang="scss" scoped>
-.driuhtrh {
-	background: var(--bg);
-
-	> .query {
-		background: var(--bg);
-
-		> .tags {
-			> .tag {
-				display: inline-block;
-				margin: 8px 8px 0 0;
-				padding: 4px 8px;
-				font-size: 0.9em;
-				background: var(--accentedBg);
-				border-radius: 5px;
-
-				&.active {
-					background: var(--accent);
-					color: var(--fgOnAccent);
-				}
-			}
-		}
-	}
-
-	> .emojis {
-		.zuvgdzyt {
-			display: grid;
-			grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
-			grid-gap: 12px;
-		}
-	}
+<style lang="scss" module>
+.emojis {
+	display: grid;
+	grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
+	grid-gap: 12px;
 }
 </style>
diff --git a/packages/frontend/src/pages/about.federation.vue b/packages/frontend/src/pages/about.federation.vue
index 8fe613a9a..a8c6c05d8 100644
--- a/packages/frontend/src/pages/about.federation.vue
+++ b/packages/frontend/src/pages/about.federation.vue
@@ -1,6 +1,6 @@
 <template>
-<div class="taeiyria">
-	<div class="query">
+<div>
+	<div>
 		<MkInput v-model="host" :debounce="true" class="">
 			<template #prefix><i class="ti ti-search"></i></template>
 			<template #label>{{ i18n.ts.host }}</template>
@@ -35,8 +35,8 @@
 	</div>
 
 	<MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination">
-		<div class="dqokceoi">
-			<MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" class="instance" :to="`/instance-info/${instance.host}`">
+		<div :class="$style.items">
+			<MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" :class="$style.item" :to="`/instance-info/${instance.host}`">
 				<MkInstanceCardMini :instance="instance"/>
 			</MkA>
 		</div>
@@ -82,21 +82,14 @@ function getStatus(instance) {
 }
 </script>
 
-<style lang="scss" scoped>
-.taeiyria {
-	> .query {
-		background: var(--bg);
-		margin-bottom: 16px;
-	}
-}
-
-.dqokceoi {
+<style lang="scss" module>
+.items {
 	display: grid;
 	grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
 	grid-gap: 12px;
+}
 
-	> .instance:hover {
-		text-decoration: none;
-	}
+.item:hover {
+	text-decoration: none;
 }
 </style>
diff --git a/packages/frontend/src/pages/about.vue b/packages/frontend/src/pages/about.vue
index 8e2999042..693d369b8 100644
--- a/packages/frontend/src/pages/about.vue
+++ b/packages/frontend/src/pages/about.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer v-if="tab === 'overview'" :content-max="600" :margin-min="20">
+	<MkSpacer v-if="tab === 'overview'" :contentMax="600" :marginMin="20">
 		<div class="_gaps_m">
 			<div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }">
 				<div style="overflow: clip;">
@@ -80,13 +80,13 @@
 			</FormSection>
 		</div>
 	</MkSpacer>
-	<MkSpacer v-else-if="tab === 'emojis'" :content-max="1000" :margin-min="20">
+	<MkSpacer v-else-if="tab === 'emojis'" :contentMax="1000" :marginMin="20">
 		<XEmojis/>
 	</MkSpacer>
-	<MkSpacer v-else-if="tab === 'federation'" :content-max="1000" :margin-min="20">
+	<MkSpacer v-else-if="tab === 'federation'" :contentMax="1000" :marginMin="20">
 		<XFederation/>
 	</MkSpacer>
-	<MkSpacer v-else-if="tab === 'charts'" :content-max="1000" :margin-min="20">
+	<MkSpacer v-else-if="tab === 'charts'" :contentMax="1000" :marginMin="20">
 		<MkInstanceStats/>
 	</MkSpacer>
 </MkStickyContainer>
diff --git a/packages/frontend/src/pages/achievements.vue b/packages/frontend/src/pages/achievements.vue
index 1eef7a53f..dc47d8dde 100644
--- a/packages/frontend/src/pages/achievements.vue
+++ b/packages/frontend/src/pages/achievements.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader/></template>
-	<MkSpacer :content-max="1200">
+	<MkSpacer :contentMax="1200">
 		<MkAchievements :user="$i"/>
 	</MkSpacer>
 </MkStickyContainer>
diff --git a/packages/frontend/src/pages/admin-file.vue b/packages/frontend/src/pages/admin-file.vue
index 1d309a737..24c863ba6 100644
--- a/packages/frontend/src/pages/admin-file.vue
+++ b/packages/frontend/src/pages/admin-file.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer v-if="file" :content-max="600" :margin-min="16" :margin-max="32">
+	<MkSpacer v-if="file" :contentMax="600" :marginMin="16" :marginMax="32">
 		<div v-if="tab === 'overview'" class="cxqhhsmd _gaps_m">
 			<a class="thumbnail" :href="file.url" target="_blank">
 				<MkDriveFileThumbnail class="thumbnail" :file="file" fit="contain"/>
@@ -32,7 +32,7 @@
 				<MkUserCardMini :user="file.user"/>
 			</MkA>
 			<div>
-				<MkSwitch v-model="isSensitive" @update:model-value="toggleIsSensitive">NSFW</MkSwitch>
+				<MkSwitch v-model="isSensitive" @update:modelValue="toggleIsSensitive">NSFW</MkSwitch>
 			</div>
 
 			<div>
diff --git a/packages/frontend/src/pages/admin/RolesEditorFormula.vue b/packages/frontend/src/pages/admin/RolesEditorFormula.vue
index 343d2c4c5..9530b27ba 100644
--- a/packages/frontend/src/pages/admin/RolesEditorFormula.vue
+++ b/packages/frontend/src/pages/admin/RolesEditorFormula.vue
@@ -1,5 +1,5 @@
 <template>
-<div :class="$style.root" class="_gaps">
+<div class="_gaps">
 	<div :class="$style.header">
 		<MkSelect v-model="type" :class="$style.typeSelect">
 			<option value="isLocal">{{ i18n.ts._role._condition.isLocal }}</option>
@@ -24,12 +24,12 @@
 		</button>
 	</div>
 
-	<div v-if="type === 'and' || type === 'or'" :class="$style.values" class="_gaps">
-		<Sortable v-model="v.values" tag="div" class="_gaps" item-key="id" handle=".drag-handle" :group="{ name: 'roleFormula' }" :animation="150" :swap-threshold="0.5">
+	<div v-if="type === 'and' || type === 'or'" class="_gaps">
+		<Sortable v-model="v.values" tag="div" class="_gaps" itemKey="id" handle=".drag-handle" :group="{ name: 'roleFormula' }" :animation="150" :swapThreshold="0.5">
 			<template #item="{element}">
 				<div :class="$style.item">
 					<!-- divが無いとエラーになる https://github.com/SortableJS/vue.draggable.next/issues/189 -->
-					<RolesEditorFormula :model-value="element" draggable @update:model-value="updated => valuesItemUpdated(updated)" @remove="removeItem(element)"/>
+					<RolesEditorFormula :modelValue="element" draggable @update:modelValue="updated => valuesItemUpdated(updated)" @remove="removeItem(element)"/>
 				</div>
 			</template>
 		</Sortable>
@@ -118,10 +118,6 @@ function removeSelf() {
 </script>
 
 <style lang="scss" module>
-.root {
-
-}
-
 .header {
 	display: flex;
 }
@@ -148,8 +144,4 @@ function removeSelf() {
 		border-color: var(--accent);
 	}
 }
-
-.values {
-
-}
 </style>
diff --git a/packages/frontend/src/pages/admin/abuses.vue b/packages/frontend/src/pages/admin/abuses.vue
index 9e8af4302..3bc5ee972 100644
--- a/packages/frontend/src/pages/admin/abuses.vue
+++ b/packages/frontend/src/pages/admin/abuses.vue
@@ -1,8 +1,8 @@
 <template>
 <MkStickyContainer>
 	<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="900">
-		<div class="lcixvhis">
+	<MkSpacer :contentMax="900">
+		<div>
 			<div class="reports">
 				<div class="">
 					<div class="inputs" style="display: flex;">
@@ -87,9 +87,3 @@ definePageMetadata({
 	icon: 'ti ti-exclamation-circle',
 });
 </script>
-
-<style lang="scss" scoped>
-.lcixvhis {
-	margin: var(--margin);
-}
-</style>
diff --git a/packages/frontend/src/pages/admin/ads.vue b/packages/frontend/src/pages/admin/ads.vue
index 803e8cb7b..2c9e18b0b 100644
--- a/packages/frontend/src/pages/admin/ads.vue
+++ b/packages/frontend/src/pages/admin/ads.vue
@@ -1,9 +1,9 @@
 <template>
 <MkStickyContainer>
 	<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="900">
-		<div class="uqshojas">
-			<div v-for="ad in ads" class="_panel _gaps_m ad">
+	<MkSpacer :contentMax="900">
+		<div>
+			<div v-for="ad in ads" class="_panel _gaps_m" :class="$style.ad">
 				<MkAd v-if="ad.url" :specify="ad"/>
 				<MkInput v-model="ad.url" type="url">
 					<template #label>URL</template>
@@ -196,14 +196,12 @@ definePageMetadata({
 });
 </script>
 
-<style lang="scss" scoped>
-.uqshojas {
-	> .ad {
-		padding: 32px;
+<style lang="scss" module>
+.ad {
+	padding: 32px;
 
-		&:not(:last-child) {
-			margin-bottom: var(--margin);
-		}
+	&:not(:last-child) {
+		margin-bottom: var(--margin);
 	}
 }
 </style>
diff --git a/packages/frontend/src/pages/admin/announcements.vue b/packages/frontend/src/pages/admin/announcements.vue
index b76e4b911..3cb32c1d9 100644
--- a/packages/frontend/src/pages/admin/announcements.vue
+++ b/packages/frontend/src/pages/admin/announcements.vue
@@ -1,8 +1,8 @@
 <template>
 <MkStickyContainer>
 	<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="900">
-		<div class="ztgjmzrw _gaps_m">
+	<MkSpacer :contentMax="900">
+		<div class="_gaps_m">
 			<section v-for="announcement in announcements" class="">
 				<div class="_panel _gaps_m" style="padding: 24px;">
 					<MkInput v-model="announcement.title">
@@ -113,9 +113,3 @@ definePageMetadata({
 	icon: 'ti ti-speakerphone',
 });
 </script>
-
-<style lang="scss" scoped>
-.ztgjmzrw {
-	margin: var(--margin);
-}
-</style>
diff --git a/packages/frontend/src/pages/admin/database.vue b/packages/frontend/src/pages/admin/database.vue
index 5a0d3d5e5..131e586af 100644
--- a/packages/frontend/src/pages/admin/database.vue
+++ b/packages/frontend/src/pages/admin/database.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="800" :margin-min="16" :margin-max="32">
+	<MkSpacer :contentMax="800" :marginMin="16" :marginMax="32">
 		<FormSuspense v-slot="{ result: database }" :p="databasePromiseFactory">
 			<MkKeyValue v-for="table in database" :key="table[0]" oneline style="margin: 1em 0;">
 				<template #key>{{ table[0] }}</template>
diff --git a/packages/frontend/src/pages/admin/email-settings.vue b/packages/frontend/src/pages/admin/email-settings.vue
index d51bf6230..4f5bb379a 100644
--- a/packages/frontend/src/pages/admin/email-settings.vue
+++ b/packages/frontend/src/pages/admin/email-settings.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><XHeader :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
+	<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
 		<FormSuspense :p="init">
 			<div class="_gaps_m">
 				<MkSwitch v-model="enableEmail">
@@ -18,7 +18,7 @@
 						<template #label>{{ i18n.ts.smtpConfig }}</template>
 
 						<div class="_gaps_m">
-							<FormSplit :min-width="280">
+							<FormSplit :minWidth="280">
 								<MkInput v-model="smtpHost">
 									<template #label>{{ i18n.ts.smtpHost }}</template>
 								</MkInput>
@@ -26,7 +26,7 @@
 									<template #label>{{ i18n.ts.smtpPort }}</template>
 								</MkInput>
 							</FormSplit>
-							<FormSplit :min-width="280">
+							<FormSplit :minWidth="280">
 								<MkInput v-model="smtpUser">
 									<template #label>{{ i18n.ts.smtpUser }}</template>
 								</MkInput>
@@ -47,7 +47,7 @@
 	</MkSpacer>
 	<template #footer>
 		<div :class="$style.footer">
-			<MkSpacer :content-max="700" :margin-min="16" :margin-max="16">
+			<MkSpacer :contentMax="700" :marginMin="16" :marginMax="16">
 				<div class="_buttons">
 					<MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
 					<MkButton rounded @click="testEmail"><i class="ti ti-send"></i> {{ i18n.ts.testEmail }}</MkButton>
diff --git a/packages/frontend/src/pages/admin/federation.vue b/packages/frontend/src/pages/admin/federation.vue
index 6af161043..a8d6dcf3d 100644
--- a/packages/frontend/src/pages/admin/federation.vue
+++ b/packages/frontend/src/pages/admin/federation.vue
@@ -2,9 +2,9 @@
 <div>
 	<MkStickyContainer>
 		<template #header><XHeader :actions="headerActions"/></template>
-		<MkSpacer :content-max="900">
-			<div class="taeiyrib">
-				<div class="query">
+		<MkSpacer :contentMax="900">
+			<div class="_gaps">
+				<div>
 					<MkInput v-model="host" :debounce="true" class="">
 						<template #prefix><i class="ti ti-search"></i></template>
 						<template #label>{{ i18n.ts.host }}</template>
@@ -39,8 +39,8 @@
 				</div>
 
 				<MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination">
-					<div class="dqokceoj">
-						<MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" class="instance" :to="`/instance-info/${instance.host}`">
+					<div :class="$style.instances">
+						<MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" :class="$style.instance" :to="`/instance-info/${instance.host}`">
 							<MkInstanceCardMini :instance="instance"/>
 						</MkA>
 					</div>
@@ -100,21 +100,14 @@ definePageMetadata(computed(() => ({
 })));
 </script>
 
-<style lang="scss" scoped>
-.taeiyrib {
-	> .query {
-		background: var(--bg);
-		margin-bottom: 16px;
-	}
-}
-
-.dqokceoj {
+<style lang="scss" module>
+.instances {
 	display: grid;
 	grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
 	grid-gap: 12px;
+}
 
-	> .instance:hover {
-		text-decoration: none;
-	}
+.instance:hover {
+	text-decoration: none;
 }
 </style>
diff --git a/packages/frontend/src/pages/admin/files.vue b/packages/frontend/src/pages/admin/files.vue
index c18943724..b204a1a64 100644
--- a/packages/frontend/src/pages/admin/files.vue
+++ b/packages/frontend/src/pages/admin/files.vue
@@ -2,30 +2,28 @@
 <div>
 	<MkStickyContainer>
 		<template #header><XHeader :actions="headerActions"/></template>
-		<MkSpacer :content-max="900">
-			<div class="xrmjdkdw">
-				<div>
-					<div class="inputs" style="display: flex; gap: var(--margin); flex-wrap: wrap;">
-						<MkSelect v-model="origin" style="margin: 0; flex: 1;">
-							<template #label>{{ i18n.ts.instance }}</template>
-							<option value="combined">{{ i18n.ts.all }}</option>
-							<option value="local">{{ i18n.ts.local }}</option>
-							<option value="remote">{{ i18n.ts.remote }}</option>
-						</MkSelect>
-						<MkInput v-model="searchHost" :debounce="true" type="search" style="margin: 0; flex: 1;" :disabled="pagination.params.origin === 'local'">
-							<template #label>{{ i18n.ts.host }}</template>
-						</MkInput>
-					</div>
-					<div class="inputs" style="display: flex; gap: var(--margin); flex-wrap: wrap; padding-top: 1.2em;">
-						<MkInput v-model="userId" :debounce="true" type="search" style="margin: 0; flex: 1;">
-							<template #label>User ID</template>
-						</MkInput>
-						<MkInput v-model="type" :debounce="true" type="search" style="margin: 0; flex: 1;">
-							<template #label>MIME type</template>
-						</MkInput>
-					</div>
-					<MkFileListForAdmin :pagination="pagination" :view-mode="viewMode"/>
+		<MkSpacer :contentMax="900">
+			<div class="_gaps">
+				<div class="inputs" style="display: flex; gap: var(--margin); flex-wrap: wrap;">
+					<MkSelect v-model="origin" style="margin: 0; flex: 1;">
+						<template #label>{{ i18n.ts.instance }}</template>
+						<option value="combined">{{ i18n.ts.all }}</option>
+						<option value="local">{{ i18n.ts.local }}</option>
+						<option value="remote">{{ i18n.ts.remote }}</option>
+					</MkSelect>
+					<MkInput v-model="searchHost" :debounce="true" type="search" style="margin: 0; flex: 1;" :disabled="pagination.params.origin === 'local'">
+						<template #label>{{ i18n.ts.host }}</template>
+					</MkInput>
 				</div>
+				<div class="inputs" style="display: flex; gap: var(--margin); flex-wrap: wrap;">
+					<MkInput v-model="userId" :debounce="true" type="search" style="margin: 0; flex: 1;">
+						<template #label>User ID</template>
+					</MkInput>
+					<MkInput v-model="type" :debounce="true" type="search" style="margin: 0; flex: 1;">
+						<template #label>MIME type</template>
+					</MkInput>
+				</div>
+				<MkFileListForAdmin :pagination="pagination" :viewMode="viewMode"/>
 			</div>
 		</MkSpacer>
 	</MkStickyContainer>
@@ -109,9 +107,3 @@ definePageMetadata(computed(() => ({
 	icon: 'ti ti-cloud',
 })));
 </script>
-
-<style lang="scss" scoped>
-.xrmjdkdw {
-	margin: var(--margin);
-}
-</style>
diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue
index 963393d7e..5cbbcaa44 100644
--- a/packages/frontend/src/pages/admin/index.vue
+++ b/packages/frontend/src/pages/admin/index.vue
@@ -1,7 +1,7 @@
 <template>
 <div ref="el" class="hiyeyicy" :class="{ wide: !narrow }">
 	<div v-if="!narrow || currentPage?.route.name == null" class="nav">	
-		<MkSpacer :content-max="700" :margin-min="16">
+		<MkSpacer :contentMax="700" :marginMin="16">
 			<div class="lxpfedzu">
 				<div class="banner">
 					<img :src="instance.iconUrl || '/favicon.ico'" alt="" class="icon"/>
diff --git a/packages/frontend/src/pages/admin/instance-block.vue b/packages/frontend/src/pages/admin/instance-block.vue
index 7a4937093..e5f3816c8 100644
--- a/packages/frontend/src/pages/admin/instance-block.vue
+++ b/packages/frontend/src/pages/admin/instance-block.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
+	<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
 		<FormSuspense :p="init">
 			<MkTextarea v-model="blockedHosts">
 				<span>{{ i18n.ts.blockedInstances }}</span>
diff --git a/packages/frontend/src/pages/admin/moderation.vue b/packages/frontend/src/pages/admin/moderation.vue
index bf788e360..e36c9ac91 100644
--- a/packages/frontend/src/pages/admin/moderation.vue
+++ b/packages/frontend/src/pages/admin/moderation.vue
@@ -2,7 +2,7 @@
 <div>
 	<MkStickyContainer>
 		<template #header><XHeader :tabs="headerTabs"/></template>
-		<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
+		<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
 			<FormSuspense :p="init">
 				<div class="_gaps_m">
 					<MkSwitch v-model="enableRegistration">
@@ -34,7 +34,7 @@
 		</MkSpacer>
 		<template #footer>
 			<div :class="$style.footer">
-				<MkSpacer :content-max="700" :margin-min="16" :margin-max="16">
+				<MkSpacer :contentMax="700" :marginMin="16" :marginMax="16">
 					<MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
 				</MkSpacer>
 			</div>
diff --git a/packages/frontend/src/pages/admin/object-storage.vue b/packages/frontend/src/pages/admin/object-storage.vue
index 704b27c17..e569aad1b 100644
--- a/packages/frontend/src/pages/admin/object-storage.vue
+++ b/packages/frontend/src/pages/admin/object-storage.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><XHeader :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
+	<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
 		<FormSuspense :p="init">
 			<div class="_gaps_m">
 				<MkSwitch v-model="useObjectStorage">{{ i18n.ts.useObjectStorage }}</MkSwitch>
@@ -33,7 +33,7 @@
 						<template #caption>{{ i18n.ts.objectStorageRegionDesc }}</template>
 					</MkInput>
 
-					<FormSplit :min-width="280">
+					<FormSplit :minWidth="280">
 						<MkInput v-model="objectStorageAccessKey">
 							<template #prefix><i class="ti ti-key"></i></template>
 							<template #label>Access key</template>
@@ -69,7 +69,7 @@
 	</MkSpacer>
 	<template #footer>
 		<div :class="$style.footer">
-			<MkSpacer :content-max="700" :margin-min="16" :margin-max="16">
+			<MkSpacer :contentMax="700" :marginMin="16" :marginMax="16">
 				<MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
 			</MkSpacer>
 		</div>
diff --git a/packages/frontend/src/pages/admin/other-settings.vue b/packages/frontend/src/pages/admin/other-settings.vue
index 62dff6ce7..15d720a07 100644
--- a/packages/frontend/src/pages/admin/other-settings.vue
+++ b/packages/frontend/src/pages/admin/other-settings.vue
@@ -1,9 +1,17 @@
 <template>
 <MkStickyContainer>
 	<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
+	<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
 		<FormSuspense :p="init">
-			none
+			<div class="_gaps_s">
+				<MkSwitch v-model="enableChartsForRemoteUser">
+					<template #label>{{ i18n.ts.enableChartsForRemoteUser }}</template>
+				</MkSwitch>
+
+				<MkSwitch v-model="enableChartsForFederatedInstances">
+					<template #label>{{ i18n.ts.enableChartsForFederatedInstances }}</template>
+				</MkSwitch>
+			</div>
 		</FormSuspense>
 	</MkSpacer>
 </MkStickyContainer>
@@ -17,13 +25,22 @@ import * as os from '@/os';
 import { fetchInstance } from '@/instance';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
+import MkSwitch from '@/components/MkSwitch.vue';
+
+let enableChartsForRemoteUser: boolean = $ref(false);
+let enableChartsForFederatedInstances: boolean = $ref(false);
 
 async function init() {
-	await os.api('admin/meta');
+	const meta = await os.api('admin/meta');
+	enableChartsForRemoteUser = meta.enableChartsForRemoteUser;
+	enableChartsForFederatedInstances = meta.enableChartsForFederatedInstances;
 }
 
 function save() {
-	os.apiWithDialog('admin/update-meta').then(() => {
+	os.apiWithDialog('admin/update-meta', {
+		enableChartsForRemoteUser,
+		enableChartsForFederatedInstances,
+	}).then(() => {
 		fetchInstance();
 	});
 }
diff --git a/packages/frontend/src/pages/admin/overview.instances.vue b/packages/frontend/src/pages/admin/overview.instances.vue
index 6c2ffd474..d349b3232 100644
--- a/packages/frontend/src/pages/admin/overview.instances.vue
+++ b/packages/frontend/src/pages/admin/overview.instances.vue
@@ -1,9 +1,9 @@
 <template>
-<div class="wbrkwale">
+<div>
 	<Transition :name="defaultStore.state.animation ? '_transition_zoom' : ''" mode="out-in">
 		<MkLoading v-if="fetching"/>
-		<div v-else class="instances">
-			<MkA v-for="(instance, i) in instances" :key="instance.id" v-tooltip.mfm.noDelay="`${instance.name}\n${instance.host}\n${instance.softwareName} ${instance.softwareVersion}`" :to="`/instance-info/${instance.host}`" class="instance">
+		<div v-else :class="$style.instances">
+			<MkA v-for="(instance, i) in instances" :key="instance.id" v-tooltip.mfm.noDelay="`${instance.name}\n${instance.host}\n${instance.softwareName} ${instance.softwareVersion}`" :to="`/instance-info/${instance.host}`" :class="$style.instance">
 				<MkInstanceCardMini :instance="instance"/>
 			</MkA>
 		</div>
@@ -36,16 +36,14 @@ useInterval(fetch, 1000 * 60, {
 });
 </script>
 
-<style lang="scss" scoped>
-.wbrkwale {
-	> .instances {
-		display: grid;
-		grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
-		grid-gap: 12px;
+<style lang="scss" module>
+.instances {
+	display: grid;
+	grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
+	grid-gap: 12px;
+}
 
-		> .instance:hover {
-			text-decoration: none;
-		}
-	}
+.instance:hover {
+	text-decoration: none;
 }
 </style>
diff --git a/packages/frontend/src/pages/admin/overview.pie.vue b/packages/frontend/src/pages/admin/overview.pie.vue
index 08a29bf55..af7bc7055 100644
--- a/packages/frontend/src/pages/admin/overview.pie.vue
+++ b/packages/frontend/src/pages/admin/overview.pie.vue
@@ -67,7 +67,3 @@ onMounted(() => {
 	});
 });
 </script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/packages/frontend/src/pages/admin/overview.queue.chart.vue b/packages/frontend/src/pages/admin/overview.queue.chart.vue
index 6a11e8b76..a3c8659ce 100644
--- a/packages/frontend/src/pages/admin/overview.queue.chart.vue
+++ b/packages/frontend/src/pages/admin/overview.queue.chart.vue
@@ -132,7 +132,3 @@ defineExpose({
 	pushData,
 });
 </script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/packages/frontend/src/pages/admin/overview.queue.vue b/packages/frontend/src/pages/admin/overview.queue.vue
index 1f56a2826..69ca89e22 100644
--- a/packages/frontend/src/pages/admin/overview.queue.vue
+++ b/packages/frontend/src/pages/admin/overview.queue.vue
@@ -33,9 +33,9 @@
 import { markRaw, onMounted, onUnmounted, ref } from 'vue';
 import XChart from './overview.queue.chart.vue';
 import number from '@/filters/number';
-import { stream } from '@/stream';
+import { useStream } from '@/stream';
 
-const connection = markRaw(stream.useChannel('queueStats'));
+const connection = markRaw(useStream().useChannel('queueStats'));
 
 const activeSincePrevTick = ref(0);
 const active = ref(0);
diff --git a/packages/frontend/src/pages/admin/overview.vue b/packages/frontend/src/pages/admin/overview.vue
index 5c96c07bf..e8295c81b 100644
--- a/packages/frontend/src/pages/admin/overview.vue
+++ b/packages/frontend/src/pages/admin/overview.vue
@@ -1,6 +1,6 @@
 <template>
-<MkSpacer :content-max="1000">
-	<div ref="rootEl" class="edbbcaef">
+<MkSpacer :contentMax="1000">
+	<div ref="rootEl" :class="$style.root">
 		<MkFoldableSection class="item">
 			<template #header>Stats</template>
 			<XStats/>
@@ -72,7 +72,7 @@ import XRetention from './overview.retention.vue';
 import XModerators from './overview.moderators.vue';
 import XHeatmap from './overview.heatmap.vue';
 import * as os from '@/os';
-import { stream } from '@/stream';
+import { useStream } from '@/stream';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
 import MkFoldableSection from '@/components/MkFoldableSection.vue';
@@ -87,7 +87,7 @@ let federationSubActive = $ref<number | null>(null);
 let federationSubActiveDiff = $ref<number | null>(null);
 let newUsers = $ref(null);
 let activeInstances = $shallowRef(null);
-const queueStatsConnection = markRaw(stream.useChannel('queueStats'));
+const queueStatsConnection = markRaw(useStream().useChannel('queueStats'));
 const now = new Date();
 const filesPagination = {
 	endpoint: 'admin/drive/files' as const,
@@ -176,8 +176,8 @@ definePageMetadata({
 });
 </script>
 
-<style lang="scss" scoped>
-.edbbcaef {
+<style lang="scss" module>
+.root {
 	display: grid;
 	grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
 	grid-gap: 16px;
diff --git a/packages/frontend/src/pages/admin/proxy-account.vue b/packages/frontend/src/pages/admin/proxy-account.vue
index 6ad566187..c81f50a0d 100644
--- a/packages/frontend/src/pages/admin/proxy-account.vue
+++ b/packages/frontend/src/pages/admin/proxy-account.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
+	<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
 		<FormSuspense :p="init">
 			<MkInfo>{{ i18n.ts.proxyAccountDescription }}</MkInfo>
 			<MkKeyValue>
diff --git a/packages/frontend/src/pages/admin/queue.chart.chart.vue b/packages/frontend/src/pages/admin/queue.chart.chart.vue
index 1a1f6a9db..9bc0eee21 100644
--- a/packages/frontend/src/pages/admin/queue.chart.chart.vue
+++ b/packages/frontend/src/pages/admin/queue.chart.chart.vue
@@ -132,7 +132,3 @@ defineExpose({
 	pushData,
 });
 </script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/packages/frontend/src/pages/admin/queue.chart.vue b/packages/frontend/src/pages/admin/queue.chart.vue
index 100d1ea54..8e6856fdd 100644
--- a/packages/frontend/src/pages/admin/queue.chart.vue
+++ b/packages/frontend/src/pages/admin/queue.chart.vue
@@ -1,35 +1,35 @@
 <template>
-<div class="pumxzjhg _gaps">
+<div class="_gaps">
 	<div :class="$style.status">
-		<div class="item _panel"><div class="label">Process</div>{{ number(activeSincePrevTick) }}</div>
-		<div class="item _panel"><div class="label">Active</div>{{ number(active) }}</div>
-		<div class="item _panel"><div class="label">Waiting</div>{{ number(waiting) }}</div>
-		<div class="item _panel"><div class="label">Delayed</div>{{ number(delayed) }}</div>
+		<div :class="$style.statusItem" class="_panel"><div :class="$style.statusLabel">Process</div>{{ number(activeSincePrevTick) }}</div>
+		<div :class="$style.statusItem" class="_panel"><div :class="$style.statusLabel">Active</div>{{ number(active) }}</div>
+		<div :class="$style.statusItem" class="_panel"><div :class="$style.statusLabel">Waiting</div>{{ number(waiting) }}</div>
+		<div :class="$style.statusItem" class="_panel"><div :class="$style.statusLabel">Delayed</div>{{ number(delayed) }}</div>
 	</div>
-	<div class="charts">
-		<div class="chart">
-			<div class="title">Process</div>
+	<div :class="$style.charts">
+		<div :class="$style.chart">
+			<div :class="$style.chartTitle">Process</div>
 			<XChart ref="chartProcess" type="process"/>
 		</div>
-		<div class="chart">
-			<div class="title">Active</div>
+		<div :class="$style.chart">
+			<div :class="$style.chartTitle">Active</div>
 			<XChart ref="chartActive" type="active"/>
 		</div>
-		<div class="chart">
-			<div class="title">Delayed</div>
+		<div :class="$style.chart">
+			<div :class="$style.chartTitle">Delayed</div>
 			<XChart ref="chartDelayed" type="delayed"/>
 		</div>
-		<div class="chart">
-			<div class="title">Waiting</div>
+		<div :class="$style.chart">
+			<div :class="$style.chartTitle">Waiting</div>
 			<XChart ref="chartWaiting" type="waiting"/>
 		</div>
 	</div>
-	<MkFolder :default-open="true" :max-height="250">
+	<MkFolder :defaultOpen="true" :max-height="250">
 		<template #icon><i class="ti ti-alert-triangle"></i></template>
 		<template #label>Errored instances</template>
 		<template #suffix>({{ number(jobs.reduce((a, b) => a + b[1], 0)) }} jobs)</template>
-		
-		<div :class="$style.jobs">
+
+		<div>
 			<div v-if="jobs.length > 0">
 				<div v-for="job in jobs" :key="job[0]">
 					<MkA :to="`/instance-info/${job[0]}`" behavior="window">{{ job[0] }}</MkA>
@@ -47,11 +47,11 @@ import { markRaw, onMounted, onUnmounted, ref } from 'vue';
 import XChart from './queue.chart.chart.vue';
 import number from '@/filters/number';
 import * as os from '@/os';
-import { stream } from '@/stream';
+import { useStream } from '@/stream';
 import { i18n } from '@/i18n';
 import MkFolder from '@/components/MkFolder.vue';
 
-const connection = markRaw(stream.useChannel('queueStats'));
+const connection = markRaw(useStream().useChannel('queueStats'));
 
 const activeSincePrevTick = ref(0);
 const active = ref(0);
@@ -118,45 +118,36 @@ onUnmounted(() => {
 });
 </script>
 
-<style lang="scss" scoped>
-.pumxzjhg {
-	> .charts {
-		display: grid;
-		grid-template-columns: 1fr 1fr;
-		gap: 10px;
-
-		> .chart {
-			min-width: 0;
-			padding: 16px;
-			background: var(--panel);
-			border-radius: var(--radius);
-
-			> .title {
-				margin-bottom: 8px;
-			}
-		}
-	}
-}
-</style>
-
 <style lang="scss" module>
+.charts {
+	display: grid;
+	grid-template-columns: 1fr 1fr;
+	gap: 10px;
+}
+
+.chart {
+	min-width: 0;
+	padding: 16px;
+	background: var(--panel);
+	border-radius: var(--radius);
+}
+
+.chartTitle {
+	margin-bottom: 8px;
+}
+
 .status {
 	display: grid;
 	grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
 	grid-gap: 10px;
-
-	&:global {
-		> .item {
-			padding: 12px 16px;
-
-			> .label {
-				font-size: 80%;
-				opacity: 0.6;
-			}
-		}
-	}
 }
 
-.jobs {
+.statusItem {
+	padding: 12px 16px;
+}
+
+.statusLabel {
+	font-size: 80%;
+	opacity: 0.6;
 }
 </style>
diff --git a/packages/frontend/src/pages/admin/queue.vue b/packages/frontend/src/pages/admin/queue.vue
index 509d329eb..1282a4b49 100644
--- a/packages/frontend/src/pages/admin/queue.vue
+++ b/packages/frontend/src/pages/admin/queue.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><XHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="800">
+	<MkSpacer :contentMax="800">
 		<XQueue v-if="tab === 'deliver'" domain="deliver"/>
 		<XQueue v-else-if="tab === 'inbox'" domain="inbox"/>
 		<br>
diff --git a/packages/frontend/src/pages/admin/relays.vue b/packages/frontend/src/pages/admin/relays.vue
index 7ebcdfc58..119439c95 100644
--- a/packages/frontend/src/pages/admin/relays.vue
+++ b/packages/frontend/src/pages/admin/relays.vue
@@ -1,14 +1,14 @@
 <template>
 <MkStickyContainer>
 	<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="800">
+	<MkSpacer :contentMax="800">
 		<div class="_gaps">
 			<div v-for="relay in relays" :key="relay.inbox" class="relaycxt _panel" style="padding: 16px;">
 				<div>{{ relay.inbox }}</div>
-				<div class="status">
-					<i v-if="relay.status === 'accepted'" class="ti ti-check icon accepted"></i>
-					<i v-else-if="relay.status === 'rejected'" class="ti ti-ban icon rejected"></i>
-					<i v-else class="ti ti-clock icon requesting"></i>
+				<div style="margin: 8px 0;">
+					<i v-if="relay.status === 'accepted'" class="ti ti-check" :class="$style.icon" style="color: var(--success);"></i>
+					<i v-else-if="relay.status === 'rejected'" class="ti ti-ban" :class="$style.icon" style="color: var(--error);"></i>
+					<i v-else class="ti ti-clock" :class="$style.icon"></i>
 					<span>{{ i18n.t(`_relayStatus.${relay.status}`) }}</span>
 				</div>
 				<MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton>
@@ -83,23 +83,9 @@ definePageMetadata({
 });
 </script>
 
-<style lang="scss" scoped>
-.relaycxt {
-	> .status {
-		margin: 8px 0;
-
-		> .icon {
-			width: 1em;
-			margin-right: 0.75em;
-
-			&.accepted {
-				color: var(--success);
-			}
-
-			&.rejected {
-				color: var(--error);
-			}
-		}
-	}
+<style lang="scss" module>
+.icon {
+	width: 1em;
+	margin-right: 0.75em;
 }
 </style>
diff --git a/packages/frontend/src/pages/admin/roles.edit.vue b/packages/frontend/src/pages/admin/roles.edit.vue
index c211ef2f0..c7a34ac77 100644
--- a/packages/frontend/src/pages/admin/roles.edit.vue
+++ b/packages/frontend/src/pages/admin/roles.edit.vue
@@ -2,12 +2,12 @@
 <div>
 	<MkStickyContainer>
 		<template #header><XHeader :tabs="headerTabs"/></template>
-		<MkSpacer :content-max="600" :margin-min="16" :margin-max="32">
+		<MkSpacer :contentMax="600" :marginMin="16" :marginMax="32">
 			<XEditor v-if="data" v-model="data"/>
 		</MkSpacer>
 		<template #footer>
 			<div :class="$style.footer">
-				<MkSpacer :content-max="600" :margin-min="16" :margin-max="16">
+				<MkSpacer :contentMax="600" :marginMin="16" :marginMax="16">
 					<MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
 				</MkSpacer>
 			</div>
diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue
index 49942c87c..a1fa9d293 100644
--- a/packages/frontend/src/pages/admin/roles.editor.vue
+++ b/packages/frontend/src/pages/admin/roles.editor.vue
@@ -36,7 +36,7 @@
 		<option value="conditional">{{ i18n.ts._role.conditional }}</option>
 	</MkSelect>
 
-	<MkFolder v-if="role.target === 'conditional'" default-open>
+	<MkFolder v-if="role.target === 'conditional'" defaultOpen>
 		<template #label>{{ i18n.ts._role.condition }}</template>
 		<div class="_gaps">
 			<RolesEditorFormula v-model="role.condFormula"/>
@@ -81,11 +81,11 @@
 					<MkSwitch v-model="role.policies.rateLimitFactor.useDefault" :readonly="readonly">
 						<template #label>{{ i18n.ts._role.useBaseValue }}</template>
 					</MkSwitch>
-					<MkRange :model-value="role.policies.rateLimitFactor.value * 100" :min="0" :max="400" :step="10" :text-converter="(v) => `${v}%`" @update:model-value="v => role.policies.rateLimitFactor.value = (v / 100)">
+					<MkRange :modelValue="role.policies.rateLimitFactor.value * 100" :min="0" :max="400" :step="10" :textConverter="(v) => `${v}%`" @update:modelValue="v => role.policies.rateLimitFactor.value = (v / 100)">
 						<template #label>{{ i18n.ts._role._options.rateLimitFactor }}</template>
 						<template #caption>{{ i18n.ts._role._options.descriptionOfRateLimitFactor }}</template>
 					</MkRange>
-					<MkRange v-model="role.policies.rateLimitFactor.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
+					<MkRange v-model="role.policies.rateLimitFactor.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>
@@ -105,7 +105,7 @@
 					<MkSwitch v-model="role.policies.gtlAvailable.value" :disabled="role.policies.gtlAvailable.useDefault" :readonly="readonly">
 						<template #label>{{ i18n.ts.enable }}</template>
 					</MkSwitch>
-					<MkRange v-model="role.policies.gtlAvailable.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
+					<MkRange v-model="role.policies.gtlAvailable.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>
@@ -125,7 +125,7 @@
 					<MkSwitch v-model="role.policies.ltlAvailable.value" :disabled="role.policies.ltlAvailable.useDefault" :readonly="readonly">
 						<template #label>{{ i18n.ts.enable }}</template>
 					</MkSwitch>
-					<MkRange v-model="role.policies.ltlAvailable.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
+					<MkRange v-model="role.policies.ltlAvailable.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>
@@ -145,7 +145,7 @@
 					<MkSwitch v-model="role.policies.canPublicNote.value" :disabled="role.policies.canPublicNote.useDefault" :readonly="readonly">
 						<template #label>{{ i18n.ts.enable }}</template>
 					</MkSwitch>
-					<MkRange v-model="role.policies.canPublicNote.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
+					<MkRange v-model="role.policies.canPublicNote.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>
@@ -165,7 +165,7 @@
 					<MkSwitch v-model="role.policies.canInvite.value" :disabled="role.policies.canInvite.useDefault" :readonly="readonly">
 						<template #label>{{ i18n.ts.enable }}</template>
 					</MkSwitch>
-					<MkRange v-model="role.policies.canInvite.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
+					<MkRange v-model="role.policies.canInvite.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>
@@ -185,7 +185,7 @@
 					<MkSwitch v-model="role.policies.canManageCustomEmojis.value" :disabled="role.policies.canManageCustomEmojis.useDefault" :readonly="readonly">
 						<template #label>{{ i18n.ts.enable }}</template>
 					</MkSwitch>
-					<MkRange v-model="role.policies.canManageCustomEmojis.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
+					<MkRange v-model="role.policies.canManageCustomEmojis.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>
@@ -205,7 +205,7 @@
 					<MkSwitch v-model="role.policies.canSearchNotes.value" :disabled="role.policies.canSearchNotes.useDefault" :readonly="readonly">
 						<template #label>{{ i18n.ts.enable }}</template>
 					</MkSwitch>
-					<MkRange v-model="role.policies.canSearchNotes.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
+					<MkRange v-model="role.policies.canSearchNotes.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>
@@ -225,7 +225,7 @@
 					<MkInput v-model="role.policies.driveCapacityMb.value" :disabled="role.policies.driveCapacityMb.useDefault" type="number" :readonly="readonly">
 						<template #suffix>MB</template>
 					</MkInput>
-					<MkRange v-model="role.policies.driveCapacityMb.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
+					<MkRange v-model="role.policies.driveCapacityMb.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>
@@ -245,7 +245,7 @@
 					<MkSwitch v-model="role.policies.alwaysMarkNsfw.value" :disabled="role.policies.alwaysMarkNsfw.useDefault" :readonly="readonly">
 						<template #label>{{ i18n.ts.enable }}</template>
 					</MkSwitch>
-					<MkRange v-model="role.policies.alwaysMarkNsfw.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
+					<MkRange v-model="role.policies.alwaysMarkNsfw.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>
@@ -264,7 +264,7 @@
 					</MkSwitch>
 					<MkInput v-model="role.policies.pinLimit.value" :disabled="role.policies.pinLimit.useDefault" type="number" :readonly="readonly">
 					</MkInput>
-					<MkRange v-model="role.policies.pinLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
+					<MkRange v-model="role.policies.pinLimit.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>
@@ -283,7 +283,7 @@
 					</MkSwitch>
 					<MkInput v-model="role.policies.antennaLimit.value" :disabled="role.policies.antennaLimit.useDefault" type="number" :readonly="readonly">
 					</MkInput>
-					<MkRange v-model="role.policies.antennaLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
+					<MkRange v-model="role.policies.antennaLimit.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>
@@ -303,7 +303,7 @@
 					<MkInput v-model="role.policies.wordMuteLimit.value" :disabled="role.policies.wordMuteLimit.useDefault" type="number" :readonly="readonly">
 						<template #suffix>chars</template>
 					</MkInput>
-					<MkRange v-model="role.policies.wordMuteLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
+					<MkRange v-model="role.policies.wordMuteLimit.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>
@@ -322,7 +322,7 @@
 					</MkSwitch>
 					<MkInput v-model="role.policies.webhookLimit.value" :disabled="role.policies.webhookLimit.useDefault" type="number" :readonly="readonly">
 					</MkInput>
-					<MkRange v-model="role.policies.webhookLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
+					<MkRange v-model="role.policies.webhookLimit.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>
@@ -341,7 +341,7 @@
 					</MkSwitch>
 					<MkInput v-model="role.policies.clipLimit.value" :disabled="role.policies.clipLimit.useDefault" type="number" :readonly="readonly">
 					</MkInput>
-					<MkRange v-model="role.policies.clipLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
+					<MkRange v-model="role.policies.clipLimit.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>
@@ -360,7 +360,7 @@
 					</MkSwitch>
 					<MkInput v-model="role.policies.noteEachClipsLimit.value" :disabled="role.policies.noteEachClipsLimit.useDefault" type="number" :readonly="readonly">
 					</MkInput>
-					<MkRange v-model="role.policies.noteEachClipsLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
+					<MkRange v-model="role.policies.noteEachClipsLimit.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>
@@ -379,7 +379,7 @@
 					</MkSwitch>
 					<MkInput v-model="role.policies.userListLimit.value" :disabled="role.policies.userListLimit.useDefault" type="number" :readonly="readonly">
 					</MkInput>
-					<MkRange v-model="role.policies.userListLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
+					<MkRange v-model="role.policies.userListLimit.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>
@@ -398,7 +398,7 @@
 					</MkSwitch>
 					<MkInput v-model="role.policies.userEachUserListsLimit.value" :disabled="role.policies.userEachUserListsLimit.useDefault" type="number" :readonly="readonly">
 					</MkInput>
-					<MkRange v-model="role.policies.userEachUserListsLimit.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
+					<MkRange v-model="role.policies.userEachUserListsLimit.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>
@@ -418,7 +418,7 @@
 					<MkSwitch v-model="role.policies.canHideAds.value" :disabled="role.policies.canHideAds.useDefault" :readonly="readonly">
 						<template #label>{{ i18n.ts.enable }}</template>
 					</MkSwitch>
-					<MkRange v-model="role.policies.canHideAds.priority" :min="0" :max="2" :step="1" easing :text-converter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
+					<MkRange v-model="role.policies.canHideAds.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>
diff --git a/packages/frontend/src/pages/admin/roles.role.vue b/packages/frontend/src/pages/admin/roles.role.vue
index 6eac90257..4ed6abf20 100644
--- a/packages/frontend/src/pages/admin/roles.role.vue
+++ b/packages/frontend/src/pages/admin/roles.role.vue
@@ -2,7 +2,7 @@
 <div>
 	<MkStickyContainer>
 		<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
-		<MkSpacer :content-max="700">
+		<MkSpacer :contentMax="700">
 			<div class="_gaps">
 				<div class="_buttons">
 					<MkButton primary rounded @click="edit"><i class="ti ti-pencil"></i> {{ i18n.ts.edit }}</MkButton>
@@ -11,9 +11,9 @@
 				<MkFolder>
 					<template #icon><i class="ti ti-info-circle"></i></template>
 					<template #label>{{ i18n.ts.info }}</template>
-					<XEditor :model-value="role" readonly/>
+					<XEditor :modelValue="role" readonly/>
 				</MkFolder>
-				<MkFolder v-if="role.target === 'manual'" default-open>
+				<MkFolder v-if="role.target === 'manual'" defaultOpen>
 					<template #icon><i class="ti ti-users"></i></template>
 					<template #label>{{ i18n.ts.users }}</template>
 					<template #suffix>{{ role.usersCount }}</template>
diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue
index e8dbe1c5f..6634d9cba 100644
--- a/packages/frontend/src/pages/admin/roles.vue
+++ b/packages/frontend/src/pages/admin/roles.vue
@@ -2,7 +2,7 @@
 <div>
 	<MkStickyContainer>
 		<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
-		<MkSpacer :content-max="700">
+		<MkSpacer :contentMax="700">
 			<div class="_gaps">
 				<MkFolder>
 					<template #label>{{ i18n.ts._role.baseRole }}</template>
@@ -14,7 +14,7 @@
 						<MkFolder v-if="matchQuery([i18n.ts._role._options.rateLimitFactor, 'rateLimitFactor'])">
 							<template #label>{{ i18n.ts._role._options.rateLimitFactor }}</template>
 							<template #suffix>{{ Math.floor(policies.rateLimitFactor * 100) }}%</template>
-							<MkRange :model-value="policies.rateLimitFactor * 100" :min="30" :max="300" :step="10" :text-converter="(v) => `${v}%`" @update:model-value="v => policies.rateLimitFactor = (v / 100)">
+							<MkRange :modelValue="policies.rateLimitFactor * 100" :min="30" :max="300" :step="10" :textConverter="(v) => `${v}%`" @update:modelValue="v => policies.rateLimitFactor = (v / 100)">
 								<template #caption>{{ i18n.ts._role._options.descriptionOfRateLimitFactor }}</template>
 							</MkRange>
 						</MkFolder>
@@ -156,13 +156,13 @@
 					<MkFoldableSection>
 						<template #header>Manual roles</template>
 						<div class="_gaps_s">
-							<MkRolePreview v-for="role in roles.filter(x => x.target === 'manual')" :key="role.id" :role="role" :for-moderation="true"/>
+							<MkRolePreview v-for="role in roles.filter(x => x.target === 'manual')" :key="role.id" :role="role" :forModeration="true"/>
 						</div>
 					</MkFoldableSection>
 					<MkFoldableSection>
 						<template #header>Conditional roles</template>
 						<div class="_gaps_s">
-							<MkRolePreview v-for="role in roles.filter(x => x.target === 'conditional')" :key="role.id" :role="role" :for-moderation="true"/>
+							<MkRolePreview v-for="role in roles.filter(x => x.target === 'conditional')" :key="role.id" :role="role" :forModeration="true"/>
 						</div>
 					</MkFoldableSection>
 				</div>
diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue
index cd8ef9e68..efb9f81f2 100644
--- a/packages/frontend/src/pages/admin/security.vue
+++ b/packages/frontend/src/pages/admin/security.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
+	<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
 		<FormSuspense :p="init">
 			<div class="_gaps_m">
 				<MkFolder>
@@ -33,7 +33,7 @@
 							<option value="remote">{{ i18n.ts.remoteOnly }}</option>
 						</MkRadios>
 
-						<MkRange v-model="sensitiveMediaDetectionSensitivity" :min="0" :max="4" :step="1" :text-converter="(v) => `${v + 1}`">
+						<MkRange v-model="sensitiveMediaDetectionSensitivity" :min="0" :max="4" :step="1" :textConverter="(v) => `${v + 1}`">
 							<template #label>{{ i18n.ts._sensitiveMediaDetection.sensitivity }}</template>
 							<template #caption>{{ i18n.ts._sensitiveMediaDetection.sensitivityDescription }}</template>
 						</MkRange>
@@ -65,7 +65,7 @@
 
 					<div class="_gaps_m">
 						<span>{{ i18n.ts.activeEmailValidationDescription }}</span>
-						<MkSwitch v-model="enableActiveEmailValidation" @update:model-value="save">
+						<MkSwitch v-model="enableActiveEmailValidation" @update:modelValue="save">
 							<template #label>Enable</template>
 						</MkSwitch>
 					</div>
@@ -77,7 +77,7 @@
 					<template v-else #suffix>Disabled</template>
 
 					<div class="_gaps_m">
-						<MkSwitch v-model="enableIpLogging" @update:model-value="save">
+						<MkSwitch v-model="enableIpLogging" @update:modelValue="save">
 							<template #label>Enable</template>
 						</MkSwitch>
 					</div>
diff --git a/packages/frontend/src/pages/admin/server-rules.vue b/packages/frontend/src/pages/admin/server-rules.vue
index 85781c0bd..fdba4f464 100644
--- a/packages/frontend/src/pages/admin/server-rules.vue
+++ b/packages/frontend/src/pages/admin/server-rules.vue
@@ -2,13 +2,13 @@
 <div>
 	<MkStickyContainer>
 		<template #header><XHeader :tabs="headerTabs"/></template>
-		<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
+		<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
 			<div class="_gaps_m">
 				<div>{{ i18n.ts._serverRules.description }}</div>
 				<Sortable
 					v-model="serverRules"
 					class="_gaps_m"
-					:item-key="(_, i) => i"
+					:itemKey="(_, i) => i"
 					:animation="150"
 					:handle="'.' + $style.itemHandle"
 					@start="e => e.item.classList.add('active')"
@@ -27,7 +27,7 @@
 				</Sortable>
 				<div :class="$style.commands">
 					<MkButton rounded @click="serverRules.push('')"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
-					<MkButton primary rounded :class="$style.buttonSave" @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
+					<MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
 				</div>
 			</div>
 		</MkSpacer>
diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue
index 7ec3c381f..39d5ae860 100644
--- a/packages/frontend/src/pages/admin/settings.vue
+++ b/packages/frontend/src/pages/admin/settings.vue
@@ -2,7 +2,7 @@
 <div>
 	<MkStickyContainer>
 		<template #header><XHeader :tabs="headerTabs"/></template>
-		<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
+		<MkSpacer :contentMax="700" :marginMin="16" :marginMax="32">
 			<FormSuspense :p="init">
 				<div class="_gaps_m">
 					<MkInput v-model="name">
@@ -13,7 +13,7 @@
 						<template #label>{{ i18n.ts.instanceDescription }}</template>
 					</MkTextarea>
 
-					<FormSplit :min-width="300">
+					<FormSplit :minWidth="300">
 						<MkInput v-model="maintainerName">
 							<template #label>{{ i18n.ts.maintainerName }}</template>
 						</MkInput>
@@ -29,18 +29,6 @@
 						<template #caption>{{ i18n.ts.pinnedUsersDescription }}</template>
 					</MkTextarea>
 
-					<FormSection>
-						<div class="_gaps_s">
-							<MkSwitch v-model="enableChartsForRemoteUser">
-								<template #label>{{ i18n.ts.enableChartsForRemoteUser }}</template>
-							</MkSwitch>
-
-							<MkSwitch v-model="enableChartsForFederatedInstances">
-								<template #label>{{ i18n.ts.enableChartsForFederatedInstances }}</template>
-							</MkSwitch>
-						</div>
-					</FormSection>
-
 					<FormSection>
 						<template #label>{{ i18n.ts.theme }}</template>
 
@@ -128,7 +116,7 @@
 		</MkSpacer>
 		<template #footer>
 			<div :class="$style.footer">
-				<MkSpacer :content-max="700" :margin-min="16" :margin-max="16">
+				<MkSpacer :contentMax="700" :marginMin="16" :marginMax="16">
 					<MkButton primary rounded @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
 				</MkSpacer>
 			</div>
@@ -166,8 +154,6 @@ let defaultDarkTheme: any = $ref(null);
 let pinnedUsers: string = $ref('');
 let cacheRemoteFiles: boolean = $ref(false);
 let enableServiceWorker: boolean = $ref(false);
-let enableChartsForRemoteUser: boolean = $ref(false);
-let enableChartsForFederatedInstances: boolean = $ref(false);
 let swPublicKey: any = $ref(null);
 let swPrivateKey: any = $ref(null);
 let deeplAuthKey: string = $ref('');
@@ -188,8 +174,6 @@ async function init() {
 	pinnedUsers = meta.pinnedUsers.join('\n');
 	cacheRemoteFiles = meta.cacheRemoteFiles;
 	enableServiceWorker = meta.enableServiceWorker;
-	enableChartsForRemoteUser = meta.enableChartsForRemoteUser;
-	enableChartsForFederatedInstances = meta.enableChartsForFederatedInstances;
 	swPublicKey = meta.swPublickey;
 	swPrivateKey = meta.swPrivateKey;
 	deeplAuthKey = meta.deeplAuthKey;
@@ -211,8 +195,6 @@ function save() {
 		pinnedUsers: pinnedUsers.split('\n'),
 		cacheRemoteFiles,
 		enableServiceWorker,
-		enableChartsForRemoteUser,
-		enableChartsForFederatedInstances,
 		swPublicKey,
 		swPrivateKey,
 		deeplAuthKey,
diff --git a/packages/frontend/src/pages/admin/users.vue b/packages/frontend/src/pages/admin/users.vue
index 819ced826..1af661a47 100644
--- a/packages/frontend/src/pages/admin/users.vue
+++ b/packages/frontend/src/pages/admin/users.vue
@@ -2,7 +2,7 @@
 <div>
 	<MkStickyContainer>
 		<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
-		<MkSpacer :content-max="900">
+		<MkSpacer :contentMax="900">
 			<div class="_gaps">
 				<div :class="$style.inputs">
 					<MkSelect v-model="sort" style="flex: 1;">
@@ -28,11 +28,11 @@
 					</MkSelect>
 				</div>
 				<div :class="$style.inputs">
-					<MkInput v-model="searchUsername" style="flex: 1;" type="text" :spellcheck="false" @update:model-value="$refs.users.reload()">
+					<MkInput v-model="searchUsername" style="flex: 1;" type="text" :spellcheck="false" @update:modelValue="$refs.users.reload()">
 						<template #prefix>@</template>
 						<template #label>{{ i18n.ts.username }}</template>
 					</MkInput>
-					<MkInput v-model="searchHost" style="flex: 1;" type="text" :spellcheck="false" :disabled="pagination.params.origin === 'local'" @update:model-value="$refs.users.reload()">
+					<MkInput v-model="searchHost" style="flex: 1;" type="text" :spellcheck="false" :disabled="pagination.params.origin === 'local'" @update:modelValue="$refs.users.reload()">
 						<template #prefix>@</template>
 						<template #label>{{ i18n.ts.host }}</template>
 					</MkInput>
diff --git a/packages/frontend/src/pages/ads.vue b/packages/frontend/src/pages/ads.vue
index 728ef3c0b..4cf2e4b2e 100644
--- a/packages/frontend/src/pages/ads.vue
+++ b/packages/frontend/src/pages/ads.vue
@@ -2,7 +2,7 @@
 <MkStickyContainer>
 	<template #header><MkPageHeader/></template>
 
-	<MkSpacer :content-max="500">
+	<MkSpacer :contentMax="500">
 		<div class="_gaps">
 			<MkAd v-for="ad in instance.ads" :key="ad.id" :specify="ad"/>
 		</div>
diff --git a/packages/frontend/src/pages/announcements.vue b/packages/frontend/src/pages/announcements.vue
index 16a0ee837..3dfb9e555 100644
--- a/packages/frontend/src/pages/announcements.vue
+++ b/packages/frontend/src/pages/announcements.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="800">
+	<MkSpacer :contentMax="800">
 		<MkPagination v-slot="{items}" :pagination="pagination" class="ruryvtyk _gaps_m">
 			<section v-for="(announcement, i) in items" :key="announcement.id" class="announcement _panel">
 				<div class="header"><span v-if="$i && !announcement.isRead">🆕 </span>{{ announcement.title }}</div>
diff --git a/packages/frontend/src/pages/antenna-timeline.vue b/packages/frontend/src/pages/antenna-timeline.vue
index 62e8178af..a22714791 100644
--- a/packages/frontend/src/pages/antenna-timeline.vue
+++ b/packages/frontend/src/pages/antenna-timeline.vue
@@ -1,19 +1,20 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<div ref="rootEl" v-hotkey.global="keymap" class="tqmomfks">
-		<div v-if="queue > 0" class="new"><button class="_buttonPrimary" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
-		<div class="tl">
-			<MkTimeline
-				ref="tlEl" :key="antennaId"
-				class="tl"
-				src="antenna"
-				:antenna="antennaId"
-				:sound="true"
-				@queue="queueUpdated"
-			/>
+	<MkSpacer :contentMax="800">
+		<div ref="rootEl" v-hotkey.global="keymap">
+			<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
+			<div :class="$style.tl">
+				<MkTimeline
+					ref="tlEl" :key="antennaId"
+					src="antenna"
+					:antenna="antennaId"
+					:sound="true"
+					@queue="queueUpdated"
+				/>
+			</div>
 		</div>
-	</div>
+	</MkSpacer>
 </MkStickyContainer>
 </template>
 
@@ -89,36 +90,29 @@ definePageMetadata(computed(() => antenna ? {
 } : null));
 </script>
 
-<style lang="scss" scoped>
-.tqmomfks {
-	padding: var(--margin);
+<style lang="scss" module>
+.new {
+	position: sticky;
+	top: calc(var(--stickyTop, 0px) + 16px);
+	z-index: 1000;
+	width: 100%;
+	margin: calc(-0.675em - 8px) 0;
 
-	> .new {
-		position: sticky;
-		top: calc(var(--stickyTop, 0px) + 16px);
-		z-index: 1000;
-		width: 100%;
-		margin: calc(-0.675em - 8px - var(--margin)) 0 calc(-0.675em - 8px);
-
-		> button {
-			display: block;
-			margin: var(--margin) auto 0 auto;
-			padding: 8px 16px;
-			border-radius: 32px;
-		}
-	}
-
-	> .tl {
-		background: var(--bg);
-		border-radius: var(--radius);
-		overflow: clip;
+	&:first-child {
+		margin-top: calc(-0.675em - 8px - var(--margin));
 	}
 }
 
-@container (min-width: 800px) {
-	.tqmomfks {
-		max-width: 800px;
-		margin: 0 auto;
-	}
+.newButton {
+	display: block;
+	margin: var(--margin) auto 0 auto;
+	padding: 8px 16px;
+	border-radius: 32px;
+}
+
+.tl {
+	background: var(--bg);
+	border-radius: var(--radius);
+	overflow: clip;
 }
 </style>
diff --git a/packages/frontend/src/pages/api-console.vue b/packages/frontend/src/pages/api-console.vue
index 7d2828e91..3a3cb3d7d 100644
--- a/packages/frontend/src/pages/api-console.vue
+++ b/packages/frontend/src/pages/api-console.vue
@@ -1,10 +1,10 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="700">
+	<MkSpacer :contentMax="700">
 		<div class="_gaps_m">
 			<div class="_gaps_m">
-				<MkInput v-model="endpoint" :datalist="endpoints" @update:model-value="onEndpointChange()">
+				<MkInput v-model="endpoint" :datalist="endpoints" @update:modelValue="onEndpointChange()">
 					<template #label>Endpoint</template>
 				</MkInput>
 				<MkTextarea v-model="body" code>
diff --git a/packages/frontend/src/pages/auth.vue b/packages/frontend/src/pages/auth.vue
index 2f40e7ded..54e76805b 100644
--- a/packages/frontend/src/pages/auth.vue
+++ b/packages/frontend/src/pages/auth.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="500">
+	<MkSpacer :contentMax="500">
 		<div v-if="state == 'fetch-session-error'">
 			<p>{{ i18n.ts.somethingHappened }}</p>
 		</div>
diff --git a/packages/frontend/src/pages/channel-editor.vue b/packages/frontend/src/pages/channel-editor.vue
index a74ab4047..0a358a141 100644
--- a/packages/frontend/src/pages/channel-editor.vue
+++ b/packages/frontend/src/pages/channel-editor.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="700">
+	<MkSpacer :contentMax="700">
 		<div v-if="channelId == null || channel != null" class="_gaps_m">
 			<MkInput v-model="name">
 				<template #label>{{ i18n.ts.name }}</template>
@@ -23,7 +23,7 @@
 				</div>
 			</div>
 
-			<MkFolder :default-open="true">
+			<MkFolder :defaultOpen="true">
 				<template #label>{{ i18n.ts.pinnedNotes }}</template>
 				
 				<div class="_gaps">
@@ -31,7 +31,7 @@
 
 					<Sortable 
 						v-model="pinnedNotes"
-						item-key="id"
+						itemKey="id"
 						:handle="'.' + $style.pinnedNoteHandle"
 						:animation="150"
 					>
diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue
index 9aa564a7d..bcc0fc686 100644
--- a/packages/frontend/src/pages/channel.vue
+++ b/packages/frontend/src/pages/channel.vue
@@ -1,10 +1,12 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="700" :class="$style.main">
+	<MkSpacer :contentMax="700" :class="$style.main">
 		<div v-if="channel && tab === 'overview'" class="_gaps">
 			<div class="_panel" :class="$style.bannerContainer">
 				<XChannelFollowButton :channel="channel" :full="true" :class="$style.subscribe"/>
+				<MkButton v-if="favorited" v-tooltip="i18n.ts.unfavorite" asLike class="button" rounded primary :class="$style.favorite" @click="unfavorite()"><i class="ti ti-star"></i></MkButton>
+				<MkButton v-else v-tooltip="i18n.ts.favorite" asLike class="button" rounded :class="$style.favorite" @click="favorite()"><i class="ti ti-star"></i></MkButton>
 				<div :style="{ backgroundImage: channel.bannerUrl ? `url(${channel.bannerUrl})` : null }" :class="$style.banner">
 					<div :class="$style.bannerStatus">
 						<div><i class="ti ti-users ti-fw"></i><I18n :src="i18n.ts._channel.usersCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.usersCount }}</b></template></I18n></div>
@@ -13,13 +15,10 @@
 					<div :class="$style.bannerFade"></div>
 				</div>
 				<div v-if="channel.description" :class="$style.description">
-					<Mfm :text="channel.description" :is-note="false" :i="$i"/>
+					<Mfm :text="channel.description" :isNote="false" :i="$i"/>
 				</div>
 			</div>
 
-			<MkButton v-if="favorited" v-tooltip="i18n.ts.unfavorite" as-like class="button" rounded primary @click="unfavorite()"><i class="ti ti-star"></i></MkButton>
-			<MkButton v-else v-tooltip="i18n.ts.favorite" as-like class="button" rounded @click="favorite()"><i class="ti ti-star"></i></MkButton>
-
 			<MkFoldableSection>
 				<template #header><i class="ti ti-pin ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.pinnedNotes }}</template>
 				<div v-if="channel.pinnedNotes.length > 0" class="_gaps">
@@ -52,7 +51,7 @@
 	</MkSpacer>
 	<template #footer>
 		<div :class="$style.footer">
-			<MkSpacer :content-max="700" :margin-min="16" :margin-max="16">
+			<MkSpacer :contentMax="700" :marginMin="16" :marginMax="16">
 				<div class="_buttonsCenter">
 					<MkButton inline rounded primary gradate @click="openPostForm()"><i class="ti ti-pencil"></i> {{ i18n.ts.postToTheChannel }}</MkButton>
 				</div>
@@ -229,6 +228,13 @@ definePageMetadata(computed(() => channel ? {
 	left: 16px;
 }
 
+.favorite {
+	position: absolute;
+	z-index: 1;
+	top: 16px;
+	right: 16px;
+}
+
 .banner {
 	position: relative;
 	height: 200px;
diff --git a/packages/frontend/src/pages/channels.vue b/packages/frontend/src/pages/channels.vue
index e670cdd86..0c4ccc1bc 100644
--- a/packages/frontend/src/pages/channels.vue
+++ b/packages/frontend/src/pages/channels.vue
@@ -1,13 +1,13 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="700">
+	<MkSpacer :contentMax="700">
 		<div v-if="tab === 'search'">
 			<div class="_gaps">
 				<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search">
 					<template #prefix><i class="ti ti-search"></i></template>
 				</MkInput>
-				<MkRadios v-model="searchType" @update:model-value="search()">
+				<MkRadios v-model="searchType" @update:modelValue="search()">
 					<option value="nameAndDescription">{{ i18n.ts._channel.nameAndDescription }}</option>
 					<option value="nameOnly">{{ i18n.ts._channel.nameOnly }}</option>
 				</MkRadios>
diff --git a/packages/frontend/src/pages/clicker.vue b/packages/frontend/src/pages/clicker.vue
index 24eae32e1..69ecc9e77 100644
--- a/packages/frontend/src/pages/clicker.vue
+++ b/packages/frontend/src/pages/clicker.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader/></template>
-	<MkSpacer :content-max="800">
+	<MkSpacer :contentMax="800">
 		<MkClickerGame/>
 	</MkSpacer>
 </MkStickyContainer>
diff --git a/packages/frontend/src/pages/clip.vue b/packages/frontend/src/pages/clip.vue
index e3ac3f4c9..d5313099d 100644
--- a/packages/frontend/src/pages/clip.vue
+++ b/packages/frontend/src/pages/clip.vue
@@ -1,16 +1,16 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions"/></template>
-	<MkSpacer :content-max="800">
-		<div v-if="clip">
-			<div class="okzinsic _panel">
-				<div v-if="clip.description" class="description">
-					<Mfm :text="clip.description" :is-note="false" :i="$i"/>
+	<MkSpacer :contentMax="800">
+		<div v-if="clip" class="_gaps">
+			<div class="_panel">
+				<div v-if="clip.description" :class="$style.description">
+					<Mfm :text="clip.description" :isNote="false" :i="$i"/>
 				</div>
-				<MkButton v-if="favorited" v-tooltip="i18n.ts.unfavorite" as-like class="button" rounded primary @click="unfavorite()"><i class="ti ti-heart"></i><span v-if="clip.favoritedCount > 0" style="margin-left: 6px;">{{ clip.favoritedCount }}</span></MkButton>
-				<MkButton v-else v-tooltip="i18n.ts.favorite" as-like class="button" rounded @click="favorite()"><i class="ti ti-heart"></i><span v-if="clip.favoritedCount > 0" style="margin-left: 6px;">{{ clip.favoritedCount }}</span></MkButton>
-				<div class="user">
-					<MkAvatar :user="clip.user" class="avatar" indicator link preview/> <MkUserName :user="clip.user" :nowrap="false"/>
+				<MkButton v-if="favorited" v-tooltip="i18n.ts.unfavorite" asLike rounded primary @click="unfavorite()"><i class="ti ti-heart"></i><span v-if="clip.favoritedCount > 0" style="margin-left: 6px;">{{ clip.favoritedCount }}</span></MkButton>
+				<MkButton v-else v-tooltip="i18n.ts.favorite" asLike rounded @click="favorite()"><i class="ti ti-heart"></i><span v-if="clip.favoritedCount > 0" style="margin-left: 6px;">{{ clip.favoritedCount }}</span></MkButton>
+				<div :class="$style.user">
+					<MkAvatar :user="clip.user" :class="$style.avatar" indicator link preview/> <MkUserName :user="clip.user" :nowrap="false"/>
 				</div>
 			</div>
 
@@ -147,25 +147,20 @@ definePageMetadata(computed(() => clip ? {
 } : null));
 </script>
 
-<style lang="scss" scoped>
-.okzinsic {
-	position: relative;
-	margin-bottom: var(--margin);
+<style lang="scss" module>
+.description {
+	padding: 16px;
+}
 
-	> .description {
-		padding: 16px;
-	}
+.user {
+	--height: 32px;
+	padding: 16px;
+	border-top: solid 0.5px var(--divider);
+	line-height: var(--height);
+}
 
-	> .user {
-		$height: 32px;
-		padding: 16px;
-		border-top: solid 0.5px var(--divider);
-		line-height: $height;
-
-		> .avatar {
-			width: $height;
-			height: $height;
-		}
-	}
+.avatar {
+	width: var(--height);
+	height: var(--height);
 }
 </style>
diff --git a/packages/frontend/src/pages/custom-emojis-manager.vue b/packages/frontend/src/pages/custom-emojis-manager.vue
index 3f13f0787..3da6a0d9c 100644
--- a/packages/frontend/src/pages/custom-emojis-manager.vue
+++ b/packages/frontend/src/pages/custom-emojis-manager.vue
@@ -2,7 +2,7 @@
 <div>
 	<MkStickyContainer>
 		<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
-		<MkSpacer :content-max="900">
+		<MkSpacer :contentMax="900">
 			<div class="ogwlenmc">
 				<div v-if="tab === 'local'" class="local">
 					<MkInput v-model="query" :debounce="true" type="search">
@@ -123,15 +123,14 @@ const toggleSelect = (emoji) => {
 };
 
 const add = async (ev: MouseEvent) => {
-	const files = await selectFiles(ev.currentTarget ?? ev.target, null);
-
-	const promise = Promise.all(files.map(file => os.api('admin/emoji/add', {
-		fileId: file.id,
-	})));
-	promise.then(() => {
-		emojisPaginationComponent.value.reload();
-	});
-	os.promiseDialog(promise);
+	os.popup(defineAsyncComponent(() => import('./emoji-edit-dialog.vue')), {
+	}, {
+		done: result => {
+			if (result.created) {
+				emojisPaginationComponent.value.prepend(result.created);
+			}
+		},
+	}, 'closed');
 };
 
 const edit = (emoji) => {
diff --git a/packages/frontend/src/pages/emoji-edit-dialog.vue b/packages/frontend/src/pages/emoji-edit-dialog.vue
index 84bc153b7..3208c9273 100644
--- a/packages/frontend/src/pages/emoji-edit-dialog.vue
+++ b/packages/frontend/src/pages/emoji-edit-dialog.vue
@@ -1,84 +1,171 @@
 <template>
 <MkModalWindow
 	ref="dialog"
-	:width="370"
-	:with-ok-button="true"
-	@close="$refs.dialog.close()"
+	:width="400"
+	@close="dialog.close()"
 	@closed="$emit('closed')"
-	@ok="ok()"
 >
-	<template #header>:{{ emoji.name }}:</template>
+	<template v-if="emoji" #header>:{{ emoji.name }}:</template>
+	<template v-else #header>New emoji</template>
 
-	<MkSpacer :margin-min="20" :margin-max="28">
-		<div class="yigymqpb _gaps_m">
-			<img :src="`/emoji/${emoji.name}.webp`" class="img"/>
-			<MkInput v-model="name">
-				<template #label>{{ i18n.ts.name }}</template>
-			</MkInput>
-			<MkInput v-model="category" :datalist="customEmojiCategories">
-				<template #label>{{ i18n.ts.category }}</template>
-			</MkInput>
-			<MkInput v-model="aliases">
-				<template #label>{{ i18n.ts.tags }}</template>
-				<template #caption>{{ i18n.ts.setMultipleBySeparatingWithSpace }}</template>
-			</MkInput>
-			<MkInput v-model="license">
-				<template #label>{{ i18n.ts.license }}</template>
-			</MkInput>
-			<MkButton danger @click="del()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
+	<div>
+		<MkSpacer :marginMin="20" :marginMax="28">
+			<div class="_gaps_m">
+				<div v-if="imgUrl != null" :class="$style.imgs">
+					<div style="background: #000;" :class="$style.imgContainer">
+						<img :src="imgUrl" :class="$style.img"/>
+					</div>
+					<div style="background: #222;" :class="$style.imgContainer">
+						<img :src="imgUrl" :class="$style.img"/>
+					</div>
+					<div style="background: #ddd;" :class="$style.imgContainer">
+						<img :src="imgUrl" :class="$style.img"/>
+					</div>
+					<div style="background: #fff;" :class="$style.imgContainer">
+						<img :src="imgUrl" :class="$style.img"/>
+					</div>
+				</div>
+				<MkButton rounded style="margin: 0 auto;" @click="changeImage">{{ i18n.ts.selectFile }}</MkButton>
+				<MkInput v-model="name">
+					<template #label>{{ i18n.ts.name }}</template>
+				</MkInput>
+				<MkInput v-model="category" :datalist="customEmojiCategories">
+					<template #label>{{ i18n.ts.category }}</template>
+				</MkInput>
+				<MkInput v-model="aliases">
+					<template #label>{{ i18n.ts.tags }}</template>
+					<template #caption>{{ i18n.ts.setMultipleBySeparatingWithSpace }}</template>
+				</MkInput>
+				<MkInput v-model="license">
+					<template #label>{{ i18n.ts.license }}</template>
+				</MkInput>
+				<MkFolder>
+					<template #label>{{ i18n.ts.rolesThatCanBeUsedThisEmojiAsReaction }}</template>
+					<template #suffix>{{ rolesThatCanBeUsedThisEmojiAsReaction.length === 0 ? i18n.ts.all : rolesThatCanBeUsedThisEmojiAsReaction.length }}</template>
+
+					<div class="_gaps">
+						<MkButton rounded @click="addRole"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
+
+						<div v-for="role in rolesThatCanBeUsedThisEmojiAsReaction" :key="role.id" :class="$style.roleItem">
+							<MkRolePreview :class="$style.role" :role="role" :forModeration="true" :detailed="false" style="pointer-events: none;"/>
+							<button v-if="role.target === 'manual'" class="_button" :class="$style.roleUnassign" @click="removeRole(role, $event)"><i class="ti ti-x"></i></button>
+							<button v-else class="_button" :class="$style.roleUnassign" disabled><i class="ti ti-ban"></i></button>
+						</div>
+
+						<MkInfo>{{ i18n.ts.rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription }}</MkInfo>
+						<MkInfo warn>{{ i18n.ts.rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn }}</MkInfo>
+					</div>
+				</MkFolder>
+				<MkSwitch v-model="isSensitive">isSensitive</MkSwitch>
+				<MkSwitch v-model="localOnly">{{ i18n.ts.localOnly }}</MkSwitch>
+				<MkButton danger @click="del()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
+			</div>
+		</MkSpacer>
+		<div :class="$style.footer">
+			<MkButton primary rounded style="margin: 0 auto;" @click="done"><i class="ti ti-check"></i> {{ props.emoji ? i18n.ts.update : i18n.ts.create }}</MkButton>
 		</div>
-	</MkSpacer>
+	</div>
 </MkModalWindow>
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
+import { computed, watch } from 'vue';
 import MkModalWindow from '@/components/MkModalWindow.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
+import MkInfo from '@/components/MkInfo.vue';
+import MkFolder from '@/components/MkFolder.vue';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
 import { customEmojiCategories } from '@/custom-emojis';
+import MkSwitch from '@/components/MkSwitch.vue';
+import { selectFile, selectFiles } from '@/scripts/select-file';
+import MkRolePreview from '@/components/MkRolePreview.vue';
 
 const props = defineProps<{
-	emoji: any,
+	emoji?: any,
 }>();
 
 let dialog = $ref(null);
-let name: string = $ref(props.emoji.name);
-let category: string = $ref(props.emoji.category);
-let aliases: string = $ref(props.emoji.aliases.join(' '));
-let license: string = $ref(props.emoji.license ?? '');
+let name: string = $ref(props.emoji ? props.emoji.name : '');
+let category: string = $ref(props.emoji ? props.emoji.category : '');
+let aliases: string = $ref(props.emoji ? props.emoji.aliases.join(' ') : '');
+let license: string = $ref(props.emoji ? (props.emoji.license ?? '') : '');
+let isSensitive = $ref(props.emoji ? props.emoji.isSensitive : false);
+let localOnly = $ref(props.emoji ? props.emoji.localOnly : false);
+let roleIdsThatCanBeUsedThisEmojiAsReaction = $ref(props.emoji ? props.emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : []);
+let rolesThatCanBeUsedThisEmojiAsReaction = $ref([]);
+let file = $ref();
+
+watch($$(roleIdsThatCanBeUsedThisEmojiAsReaction), async () => {
+	rolesThatCanBeUsedThisEmojiAsReaction = (await Promise.all(roleIdsThatCanBeUsedThisEmojiAsReaction.map((id) => os.api('admin/roles/show', { roleId: id }).catch(() => null)))).filter(x => x != null);
+}, { immediate: true });
+
+const imgUrl = computed(() => file ? file.url : props.emoji ? `/emoji/${props.emoji.name}.webp` : null);
 
 const emit = defineEmits<{
-	(ev: 'done', v: { deleted?: boolean, updated?: any }): void,
+	(ev: 'done', v: { deleted?: boolean; updated?: any; created?: any }): void,
 	(ev: 'closed'): void
 }>();
 
-function ok() {
-	update();
+async function changeImage(ev) {
+	file = await selectFile(ev.currentTarget ?? ev.target, null);
 }
 
-async function update() {
-	await os.apiWithDialog('admin/emoji/update', {
-		id: props.emoji.id,
+async function addRole() {
+	const roles = await os.api('admin/roles/list');
+	const currentRoleIds = rolesThatCanBeUsedThisEmojiAsReaction.map(x => x.id);
+
+	const { canceled, result: role } = await os.select({
+		items: roles.filter(r => r.isPublic).filter(r => !currentRoleIds.includes(r.id)).map(r => ({ text: r.name, value: r })),
+	});
+	if (canceled) return;
+
+	rolesThatCanBeUsedThisEmojiAsReaction.push(role);
+}
+
+async function removeRole(role, ev) {
+	rolesThatCanBeUsedThisEmojiAsReaction = rolesThatCanBeUsedThisEmojiAsReaction.filter(x => x.id !== role.id);
+}
+
+async function done() {
+	const params = {
 		name,
-		category,
-		aliases: aliases.split(' '),
+		category: category === '' ? null : category,
+		aliases: aliases.split(' ').filter(x => x !== ''),
 		license: license === '' ? null : license,
-	});
+		isSensitive,
+		localOnly,
+		roleIdsThatCanBeUsedThisEmojiAsReaction: rolesThatCanBeUsedThisEmojiAsReaction.map(x => x.id),
+	};
 
-	emit('done', {
-		updated: {
+	if (file) {
+		params.fileId = file.id;
+	}
+
+	if (props.emoji) {
+		await os.apiWithDialog('admin/emoji/update', {
 			id: props.emoji.id,
-			name,
-			category,
-			aliases: aliases.split(' '),
-			license: license === '' ? null : license,
-		},
-	});
+			...params,
+		});
 
-	dialog.close();
+		emit('done', {
+			updated: {
+				id: props.emoji.id,
+				...params,
+			},
+		});
+
+		dialog.close();
+	} else {
+		const created = await os.apiWithDialog('admin/emoji/add', params);
+
+		emit('done', {
+			created: created,
+		});
+
+		dialog.close();
+	}
 }
 
 async function del() {
@@ -99,12 +186,48 @@ async function del() {
 }
 </script>
 
-<style lang="scss" scoped>
-.yigymqpb {
-	> .img {
-		display: block;
-		height: 64px;
-		margin: 0 auto;
-	}
+<style lang="scss" module>
+.imgs {
+	display: flex;
+	gap: 8px;
+	flex-wrap: wrap;
+	justify-content: center;
+}
+
+.imgContainer {
+	padding: 8px;
+	border-radius: 6px;
+}
+
+.img {
+	display: block;
+	height: 64px;
+	width: 64px;
+	object-fit: contain;
+}
+
+.roleItem {
+	display: flex;
+}
+
+.role {
+	flex: 1;
+}
+
+.roleUnassign {
+	width: 32px;
+	height: 32px;
+	margin-left: 8px;
+	align-self: center;
+}
+
+.footer {
+	position: sticky;
+	bottom: 0;
+	left: 0;
+	padding: 12px;
+	border-top: solid 0.5px var(--divider);
+	-webkit-backdrop-filter: var(--blur, blur(15px));
+	backdrop-filter: var(--blur, blur(15px));
 }
 </style>
diff --git a/packages/frontend/src/pages/emojis.emoji.vue b/packages/frontend/src/pages/emojis.emoji.vue
index bdd21b29e..e9fab6a31 100644
--- a/packages/frontend/src/pages/emojis.emoji.vue
+++ b/packages/frontend/src/pages/emojis.emoji.vue
@@ -1,9 +1,9 @@
 <template>
-<button class="zuvgdzyu _button" @click="menu">
-	<img :src="emoji.url" class="img" loading="lazy"/>
-	<div class="body">
-		<div class="name _monospace">{{ emoji.name }}</div>
-		<div class="info">{{ emoji.aliases.join(' ') }}</div>
+<button class="_button" :class="$style.root" @click="menu">
+	<img :src="emoji.url" :class="$style.img" loading="lazy"/>
+	<div :class="$style.body">
+		<div :class="$style.name" class="_monospace">{{ emoji.name }}</div>
+		<div :class="$style.info">{{ emoji.aliases.join(' ') }}</div>
 	</div>
 </button>
 </template>
@@ -49,8 +49,8 @@ function menu(ev) {
 }
 </script>
 
-<style lang="scss" scoped>
-.zuvgdzyu {
+<style lang="scss" module>
+.root {
 	display: flex;
 	align-items: center;
 	padding: 12px;
@@ -61,29 +61,29 @@ function menu(ev) {
 	&:hover {
 		border-color: var(--accent);
 	}
+}
 
-	> .img {
-		width: 42px;
-		height: 42px;
-		object-fit: contain;
-	}
+.img {
+	width: 42px;
+	height: 42px;
+	object-fit: contain;
+}
 
-	> .body {
-		padding: 0 0 0 8px;
-		white-space: nowrap;
-		overflow: hidden;
+.body {
+	padding: 0 0 0 8px;
+	white-space: nowrap;
+	overflow: hidden;
+}
 
-		> .name {
-			text-overflow: ellipsis;
-			overflow: hidden;
-		}
+.name {
+	text-overflow: ellipsis;
+	overflow: hidden;
+}
 
-		> .info {
-			opacity: 0.5;
-			font-size: 0.9em;
-			text-overflow: ellipsis;
-			overflow: hidden;
-		}
-	}
+.info {
+	opacity: 0.5;
+	font-size: 0.9em;
+	text-overflow: ellipsis;
+	overflow: hidden;
 }
 </style>
diff --git a/packages/frontend/src/pages/explore.featured.vue b/packages/frontend/src/pages/explore.featured.vue
index a972ae04e..5c7131373 100644
--- a/packages/frontend/src/pages/explore.featured.vue
+++ b/packages/frontend/src/pages/explore.featured.vue
@@ -1,5 +1,5 @@
 <template>
-<MkSpacer :content-max="800">
+<MkSpacer :contentMax="800">
 	<MkTab v-model="tab" style="margin-bottom: var(--margin);">
 		<option value="notes">{{ i18n.ts.notes }}</option>
 		<option value="polls">{{ i18n.ts.poll }}</option>
diff --git a/packages/frontend/src/pages/explore.roles.vue b/packages/frontend/src/pages/explore.roles.vue
index 6ac469f7b..c855d79f4 100644
--- a/packages/frontend/src/pages/explore.roles.vue
+++ b/packages/frontend/src/pages/explore.roles.vue
@@ -1,7 +1,7 @@
 <template>
-<MkSpacer :content-max="700">
+<MkSpacer :contentMax="700">
 	<div class="_gaps_s">
-		<MkRolePreview v-for="role in roles" :key="role.id" :role="role" :for-moderation="false"/>
+		<MkRolePreview v-for="role in roles" :key="role.id" :role="role" :forModeration="false"/>
 	</div>
 </MkSpacer>
 </template>
diff --git a/packages/frontend/src/pages/explore.users.vue b/packages/frontend/src/pages/explore.users.vue
index f9c833dd2..785dbaa34 100644
--- a/packages/frontend/src/pages/explore.users.vue
+++ b/packages/frontend/src/pages/explore.users.vue
@@ -1,24 +1,24 @@
 <template>
-<MkSpacer :content-max="1200">
+<MkSpacer :contentMax="1200">
 	<MkTab v-model="origin" style="margin-bottom: var(--margin);">
 		<option value="local">{{ i18n.ts.local }}</option>
 		<option value="remote">{{ i18n.ts.remote }}</option>
 	</MkTab>
 	<div v-if="origin === 'local'">
 		<template v-if="tag == null">
-			<MkFoldableSection class="_margin" persist-key="explore-pinned-users">
+			<MkFoldableSection class="_margin" persistKey="explore-pinned-users">
 				<template #header><i class="ti ti-bookmark ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.pinnedUsers }}</template>
 				<MkUserList :pagination="pinnedUsers"/>
 			</MkFoldableSection>
-			<MkFoldableSection class="_margin" persist-key="explore-popular-users">
+			<MkFoldableSection class="_margin" persistKey="explore-popular-users">
 				<template #header><i class="ti ti-chart-line ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.popularUsers }}</template>
 				<MkUserList :pagination="popularUsers"/>
 			</MkFoldableSection>
-			<MkFoldableSection class="_margin" persist-key="explore-recently-updated-users">
+			<MkFoldableSection class="_margin" persistKey="explore-recently-updated-users">
 				<template #header><i class="ti ti-message ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyUpdatedUsers }}</template>
 				<MkUserList :pagination="recentlyUpdatedUsers"/>
 			</MkFoldableSection>
-			<MkFoldableSection class="_margin" persist-key="explore-recently-registered-users">
+			<MkFoldableSection class="_margin" persistKey="explore-recently-registered-users">
 				<template #header><i class="ti ti-plus ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyRegisteredUsers }}</template>
 				<MkUserList :pagination="recentlyRegisteredUsers"/>
 			</MkFoldableSection>
diff --git a/packages/frontend/src/pages/favorites.vue b/packages/frontend/src/pages/favorites.vue
index 0dc9b9dc8..460bf65d1 100644
--- a/packages/frontend/src/pages/favorites.vue
+++ b/packages/frontend/src/pages/favorites.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader/></template>
-	<MkSpacer :content-max="800">
+	<MkSpacer :contentMax="800">
 		<MkPagination :pagination="pagination">
 			<template #empty>
 				<div class="_fullinfo">
@@ -11,7 +11,7 @@
 			</template>
 
 			<template #default="{ items }">
-				<MkDateSeparatedList v-slot="{ item }" :items="items" :direction="'down'" :no-gap="false" :ad="false">
+				<MkDateSeparatedList v-slot="{ item }" :items="items" :direction="'down'" :noGap="false" :ad="false">
 					<MkNote :key="item.id" :note="item.note" :class="$style.note"/>
 				</MkDateSeparatedList>
 			</template>
diff --git a/packages/frontend/src/pages/flash/flash-edit.vue b/packages/frontend/src/pages/flash/flash-edit.vue
index 816825e5b..6a16cd1c4 100644
--- a/packages/frontend/src/pages/flash/flash-edit.vue
+++ b/packages/frontend/src/pages/flash/flash-edit.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="700">
+	<MkSpacer :contentMax="700">
 		<div class="_gaps">
 			<MkInput v-model="title">
 				<template #label>{{ i18n.ts._play.title }}</template>
@@ -33,7 +33,7 @@ import MkTextarea from '@/components/MkTextarea.vue';
 import MkInput from '@/components/MkInput.vue';
 import { useRouter } from '@/router';
 
-const PRESET_DEFAULT = `/// @ 0.13.2
+const PRESET_DEFAULT = `/// @ 0.13.3
 
 var name = ""
 
@@ -51,7 +51,7 @@ Ui:render([
 ])
 `;
 
-const PRESET_OMIKUJI = `/// @ 0.13.2
+const PRESET_OMIKUJI = `/// @ 0.13.3
 // ユーザーごとに日替わりのおみくじのプリセット
 
 // 選択肢
@@ -94,7 +94,7 @@ Ui:render([
 ])
 `;
 
-const PRESET_SHUFFLE = `/// @ 0.13.2
+const PRESET_SHUFFLE = `/// @ 0.13.3
 // 巻き戻し可能な文字シャッフルのプリセット
 
 let string = "ペペロンチーノ"
@@ -173,7 +173,7 @@ var cursor = 0
 do()
 `;
 
-const PRESET_QUIZ = `/// @ 0.13.2
+const PRESET_QUIZ = `/// @ 0.13.3
 let title = '地理クイズ'
 
 let qas = [{
@@ -286,7 +286,7 @@ qaEls.push(Ui:C:container({
 Ui:render(qaEls)
 `;
 
-const PRESET_TIMELINE = `/// @ 0.13.2
+const PRESET_TIMELINE = `/// @ 0.13.3
 // APIリクエストを行いローカルタイムラインを表示するプリセット
 
 @fetch() {
@@ -442,7 +442,3 @@ definePageMetadata(computed(() => flash ? {
 	title: i18n.ts._play.new,
 }));
 </script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/packages/frontend/src/pages/flash/flash-index.vue b/packages/frontend/src/pages/flash/flash-index.vue
index f1dca5f24..1f933c234 100644
--- a/packages/frontend/src/pages/flash/flash-index.vue
+++ b/packages/frontend/src/pages/flash/flash-index.vue
@@ -1,30 +1,30 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="700">
-		<div v-if="tab === 'featured'" class="">
+	<MkSpacer :contentMax="700">
+		<div v-if="tab === 'featured'">
 			<MkPagination v-slot="{items}" :pagination="featuredFlashsPagination">
 				<div class="_gaps_s">
-					<MkFlashPreview v-for="flash in items" :key="flash.id" class="" :flash="flash"/>
+					<MkFlashPreview v-for="flash in items" :key="flash.id" :flash="flash"/>
 				</div>
 			</MkPagination>
 		</div>
 
-		<div v-else-if="tab === 'my'" class="my">
+		<div v-else-if="tab === 'my'">
 			<div class="_gaps">
-				<MkButton class="new" gradate rounded style="margin: 0 auto;" @click="create()"><i class="ti ti-plus"></i></MkButton>
+				<MkButton gradate rounded style="margin: 0 auto;" @click="create()"><i class="ti ti-plus"></i></MkButton>
 				<MkPagination v-slot="{items}" :pagination="myFlashsPagination">
 					<div class="_gaps_s">
-						<MkFlashPreview v-for="flash in items" :key="flash.id" class="" :flash="flash"/>
+						<MkFlashPreview v-for="flash in items" :key="flash.id" :flash="flash"/>
 					</div>
 				</MkPagination>
 			</div>
 		</div>
 
-		<div v-else-if="tab === 'liked'" class="">
+		<div v-else-if="tab === 'liked'">
 			<MkPagination v-slot="{items}" :pagination="likedFlashsPagination">
 				<div class="_gaps_s">
-					<MkFlashPreview v-for="like in items" :key="like.flash.id" class="" :flash="like.flash"/>
+					<MkFlashPreview v-for="like in items" :key="like.flash.id" :flash="like.flash"/>
 				</div>
 			</MkPagination>
 		</div>
@@ -87,21 +87,3 @@ definePageMetadata(computed(() => ({
 	icon: 'ti ti-player-play',
 })));
 </script>
-
-<style lang="scss" scoped>
-.rknalgpo {
-	&.my .ckltabjg:first-child {
-		margin-top: 16px;
-	}
-
-	.ckltabjg:not(:last-child) {
-		margin-bottom: 8px;
-	}
-
-	@media (min-width: 500px) {
-		.ckltabjg:not(:last-child) {
-			margin-bottom: 16px;
-		}
-	}
-}
-</style>
diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue
index 961ef4b75..2e1532b9f 100644
--- a/packages/frontend/src/pages/flash/flash.vue
+++ b/packages/frontend/src/pages/flash/flash.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="700">
+	<MkSpacer :contentMax="700">
 		<Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in">
 			<div v-if="flash" :key="flash.id">
 				<Transition :name="defaultStore.state.animation ? 'zoom' : ''" mode="out-in">
@@ -10,8 +10,8 @@
 							<MkAsUi v-if="root" :component="root" :components="components"/>
 						</div>
 						<div class="actions _panel">
-							<MkButton v-if="flash.isLiked" v-tooltip="i18n.ts.unlike" as-like class="button" rounded primary @click="unlike()"><i class="ti ti-heart"></i><span v-if="flash.likedCount > 0" style="margin-left: 6px;">{{ flash.likedCount }}</span></MkButton>
-							<MkButton v-else v-tooltip="i18n.ts.like" as-like class="button" rounded @click="like()"><i class="ti ti-heart"></i><span v-if="flash.likedCount > 0" style="margin-left: 6px;">{{ flash.likedCount }}</span></MkButton>
+							<MkButton v-if="flash.isLiked" v-tooltip="i18n.ts.unlike" asLike class="button" rounded primary @click="unlike()"><i class="ti ti-heart"></i><span v-if="flash.likedCount > 0" style="margin-left: 6px;">{{ flash.likedCount }}</span></MkButton>
+							<MkButton v-else v-tooltip="i18n.ts.like" asLike class="button" rounded @click="like()"><i class="ti ti-heart"></i><span v-if="flash.likedCount > 0" style="margin-left: 6px;">{{ flash.likedCount }}</span></MkButton>
 							<MkButton v-tooltip="i18n.ts.shareWithNote" class="button" rounded @click="shareWithNote"><i class="ti ti-repeat ti-fw"></i></MkButton>
 							<MkButton v-tooltip="i18n.ts.share" class="button" rounded @click="share"><i class="ti ti-share ti-fw"></i></MkButton>
 						</div>
@@ -27,7 +27,7 @@
 						</div>
 					</div>
 				</Transition>
-				<MkFolder :default-open="false" :max-height="280" class="_margin">
+				<MkFolder :defaultOpen="false" :max-height="280" class="_margin">
 					<template #icon><i class="ti ti-code"></i></template>
 					<template #label>{{ i18n.ts._play.viewSource }}</template>
 
diff --git a/packages/frontend/src/pages/follow-requests.vue b/packages/frontend/src/pages/follow-requests.vue
index a51d1c78a..1452942a1 100644
--- a/packages/frontend/src/pages/follow-requests.vue
+++ b/packages/frontend/src/pages/follow-requests.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader/></template>
-	<MkSpacer :content-max="800">
+	<MkSpacer :contentMax="800">
 		<MkPagination ref="paginationComponent" :pagination="pagination">
 			<template #empty>
 				<div class="_fullinfo">
diff --git a/packages/frontend/src/pages/follow.vue b/packages/frontend/src/pages/follow.vue
index 828246d67..d14b66336 100644
--- a/packages/frontend/src/pages/follow.vue
+++ b/packages/frontend/src/pages/follow.vue
@@ -1,5 +1,5 @@
 <template>
-<div class="mk-follow-page">
+<div>
 </div>
 </template>
 
diff --git a/packages/frontend/src/pages/gallery/edit.vue b/packages/frontend/src/pages/gallery/edit.vue
index cafcee0c3..f381636a7 100644
--- a/packages/frontend/src/pages/gallery/edit.vue
+++ b/packages/frontend/src/pages/gallery/edit.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="800" :margin-min="16" :margin-max="32">
+	<MkSpacer :contentMax="800" :marginMin="16" :marginMax="32">
 		<FormSuspense :p="init" class="_gaps">
 			<MkInput v-model="title">
 				<template #label>{{ i18n.ts.title }}</template>
diff --git a/packages/frontend/src/pages/gallery/index.vue b/packages/frontend/src/pages/gallery/index.vue
index fc9cc7ae9..3c9c21a2f 100644
--- a/packages/frontend/src/pages/gallery/index.vue
+++ b/packages/frontend/src/pages/gallery/index.vue
@@ -1,21 +1,21 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="1400">
+	<MkSpacer :contentMax="1400">
 		<div class="_root">
 			<div v-if="tab === 'explore'">
 				<MkFoldableSection class="_margin">
 					<template #header><i class="ti ti-clock"></i>{{ i18n.ts.recentPosts }}</template>
-					<MkPagination v-slot="{items}" :pagination="recentPostsPagination" :disable-auto-load="true">
-						<div class="vfpdbgtk">
+					<MkPagination v-slot="{items}" :pagination="recentPostsPagination" :disableAutoLoad="true">
+						<div :class="$style.items">
 							<MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/>
 						</div>
 					</MkPagination>
 				</MkFoldableSection>
 				<MkFoldableSection class="_margin">
 					<template #header><i class="ti ti-comet"></i>{{ i18n.ts.popularPosts }}</template>
-					<MkPagination v-slot="{items}" :pagination="popularPostsPagination" :disable-auto-load="true">
-						<div class="vfpdbgtk">
+					<MkPagination v-slot="{items}" :pagination="popularPostsPagination" :disableAutoLoad="true">
+						<div :class="$style.items">
 							<MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/>
 						</div>
 					</MkPagination>
@@ -23,7 +23,7 @@
 			</div>
 			<div v-else-if="tab === 'liked'">
 				<MkPagination v-slot="{items}" :pagination="likedPostsPagination">
-					<div class="vfpdbgtk">
+					<div :class="$style.items">
 						<MkGalleryPostPreview v-for="like in items" :key="like.id" :post="like.post" class="post"/>
 					</div>
 				</MkPagination>
@@ -31,7 +31,7 @@
 			<div v-else-if="tab === 'my'">
 				<MkA to="/gallery/new" class="_link" style="margin: 16px;"><i class="ti ti-plus"></i> {{ i18n.ts.postToGallery }}</MkA>
 				<MkPagination v-slot="{items}" :pagination="myPostsPagination">
-					<div class="vfpdbgtk">
+					<div :class="$style.items">
 						<MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/>
 					</div>
 				</MkPagination>
@@ -119,15 +119,11 @@ definePageMetadata({
 });
 </script>
 
-<style lang="scss" scoped>
-.vfpdbgtk {
+<style lang="scss" module>
+.items {
 	display: grid;
 	grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
 	grid-gap: 12px;
 	margin: 0 var(--margin);
-
-	> .post {
-
-	}
 }
 </style>
diff --git a/packages/frontend/src/pages/gallery/post.vue b/packages/frontend/src/pages/gallery/post.vue
index e0f3c105e..dfa6c0bac 100644
--- a/packages/frontend/src/pages/gallery/post.vue
+++ b/packages/frontend/src/pages/gallery/post.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="1000" :margin-min="16" :margin-max="32">
+	<MkSpacer :contentMax="1000" :marginMin="16" :marginMax="32">
 		<div class="_root">
 			<Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in">
 				<div v-if="post" class="rkxwuolj">
diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue
index ba5fda137..83997b255 100644
--- a/packages/frontend/src/pages/instance-info.vue
+++ b/packages/frontend/src/pages/instance-info.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer v-if="instance" :content-max="600" :margin-min="16" :margin-max="32">
+	<MkSpacer v-if="instance" :contentMax="600" :marginMin="16" :marginMax="32">
 		<div v-if="tab === 'overview'" class="_gaps_m">
 			<div class="fnfelxur">
 				<img :src="faviconUrl" alt="" class="icon"/>
@@ -29,8 +29,8 @@
 			<FormSection v-if="iAmModerator">
 				<template #label>Moderation</template>
 				<div class="_gaps_s">
-					<MkSwitch v-model="suspended" @update:model-value="toggleSuspend">{{ i18n.ts.stopActivityDelivery }}</MkSwitch>
-					<MkSwitch v-model="isBlocked" @update:model-value="toggleBlock">{{ i18n.ts.blockThisInstance }}</MkSwitch>
+					<MkSwitch v-model="suspended" @update:modelValue="toggleSuspend">{{ i18n.ts.stopActivityDelivery }}</MkSwitch>
+					<MkSwitch v-model="isBlocked" @update:modelValue="toggleBlock">{{ i18n.ts.blockThisInstance }}</MkSwitch>
 					<MkButton @click="refreshMetadata"><i class="ti ti-refresh"></i> Refresh metadata</MkButton>
 				</div>
 			</FormSection>
diff --git a/packages/frontend/src/pages/list.vue b/packages/frontend/src/pages/list.vue
new file mode 100644
index 000000000..f92c06d1c
--- /dev/null
+++ b/packages/frontend/src/pages/list.vue
@@ -0,0 +1,148 @@
+<template>
+<MkStickyContainer>
+	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
+	<MKSpacer v-if="!(typeof error === 'undefined')" :contentMax="1200">
+		<div :class="$style.root">
+			<img :class="$style.img" src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/>
+			<p :class="$style.text">
+				<i class="ti ti-alert-triangle"></i>
+				{{ i18n.ts.nothing }}
+			</p>
+		</div>
+	</MKSpacer>
+	<MkSpacer v-else-if="list" :contentMax="700" :class="$style.main">
+		<div v-if="list" class="members _margin">
+			<div :class="$style.member_text">{{ i18n.ts.members }}</div>
+			<div class="_gaps_s">
+				<div v-for="user in users" :key="user.id" :class="$style.userItem">
+					<MkA :class="$style.userItemBody" :to="`${userPage(user)}`">
+						<MkUserCardMini :user="user"/>
+					</MkA>
+				</div>
+			</div>
+		</div>
+		<MkButton v-if="list.isLiked" v-tooltip="i18n.ts.unlike" inline :class="$style.button" asLike primary @click="unlike()"><i class="ti ti-heart-off"></i><span v-if="list.likedCount > 0" class="count">{{ list.likedCount }}</span></MkButton>
+		<MkButton v-if="!list.isLiked" v-tooltip="i18n.ts.like" inline :class="$style.button" asLike @click="like()"><i class="ti ti-heart"></i><span v-if="1 > 0" class="count">{{ list.likedCount }}</span></MkButton>
+		<MkButton inline @click="create()"><i class="ti ti-download" :class="$style.import"></i>{{ i18n.ts.import }}</MkButton>
+	</MkSpacer>
+</MkStickyContainer>
+</template>
+
+<script lang="ts" setup>
+import { watch, computed } from 'vue';
+import * as os from '@/os';
+import { userPage } from '@/filters/user';
+import { i18n } from '@/i18n';
+import MkUserCardMini from '@/components/MkUserCardMini.vue';
+import MkButton from '@/components/MkButton.vue';
+import { definePageMetadata } from '@/scripts/page-metadata';
+
+const props = defineProps<{
+	listId: string;
+}>();
+
+let list = $ref(null);
+let error = $ref();
+let users = $ref([]);
+
+function fetchList(): void {
+	os.api('users/lists/show', {
+		listId: props.listId,
+		forPublic: true,
+	}).then(_list => {
+		list = _list;
+		os.api('users/show', {
+			userIds: list.userIds,
+		}).then(_users => {
+			users = _users;
+		});
+	}).catch(err => {
+		error = err;
+	});
+}
+
+function like() {
+	os.apiWithDialog('users/lists/favorite', {
+		listId: list.id,
+	}).then(() => {
+		list.isLiked = true;
+		list.likedCount++;
+	});
+}
+
+function unlike() {
+	os.apiWithDialog('users/lists/unfavorite', {
+		listId: list.id,
+	}).then(() => {
+		list.isLiked = false;
+		list.likedCount--;
+	});
+}
+
+async function create() {
+	const { canceled, result: name } = await os.inputText({
+		title: i18n.ts.enterListName,
+	});
+	if (canceled) return;
+	await os.apiWithDialog('users/lists/create-from-public', { name: name, listId: list.id });
+}
+
+watch(() => props.listId, fetchList, { immediate: true });
+
+const headerActions = $computed(() => []);
+
+const headerTabs = $computed(() => []);
+
+definePageMetadata(computed(() => list ? {
+	title: list.name,
+	icon: 'ti ti-list',
+} : null));
+</script>
+<style lang="scss" module>
+.main {
+	min-height: calc(100cqh - (var(--stickyTop, 0px) + var(--stickyBottom, 0px)));
+}
+
+.userItem {
+	display: flex;
+}
+
+.userItemBody {
+	flex: 1;
+	min-width: 0;
+	margin-right: 8px;
+	
+	&:hover {
+		text-decoration: none;
+	}
+}
+.member_text {
+	margin: 5px;
+}
+
+.root {
+	padding: 32px;
+	text-align: center;
+  align-items: center;
+}
+
+.text {
+	margin: 0 0 8px 0;
+}
+
+.img {
+	vertical-align: bottom;
+  width: 128px;
+	height: 128px;
+	margin-bottom: 16px;
+	border-radius: 16px;
+}
+
+.button {
+	margin-right: 10px;
+}
+
+.import {
+	margin-right: 4px;
+}
+</style>
diff --git a/packages/frontend/src/pages/miauth.vue b/packages/frontend/src/pages/miauth.vue
index 8e0624f55..553946cd9 100644
--- a/packages/frontend/src/pages/miauth.vue
+++ b/packages/frontend/src/pages/miauth.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="800">
+	<MkSpacer :contentMax="800">
 		<div v-if="$i">
 			<div v-if="state == 'waiting'">
 				<MkLoading/>
diff --git a/packages/frontend/src/pages/my-antennas/create.vue b/packages/frontend/src/pages/my-antennas/create.vue
index c35af3e22..355d18fdb 100644
--- a/packages/frontend/src/pages/my-antennas/create.vue
+++ b/packages/frontend/src/pages/my-antennas/create.vue
@@ -1,5 +1,5 @@
 <template>
-<div class="geegznzt">
+<div>
 	<XAntenna :antenna="draft" @created="onAntennaCreated"/>
 </div>
 </template>
@@ -38,7 +38,3 @@ definePageMetadata({
 	icon: 'ti ti-antenna',
 });
 </script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/packages/frontend/src/pages/my-antennas/edit.vue b/packages/frontend/src/pages/my-antennas/edit.vue
index 913fbde8e..da9b2de48 100644
--- a/packages/frontend/src/pages/my-antennas/edit.vue
+++ b/packages/frontend/src/pages/my-antennas/edit.vue
@@ -36,7 +36,3 @@ definePageMetadata({
 	icon: 'ti ti-antenna',
 });
 </script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/packages/frontend/src/pages/my-antennas/editor.vue b/packages/frontend/src/pages/my-antennas/editor.vue
index 26b7bcc71..ed92208c4 100644
--- a/packages/frontend/src/pages/my-antennas/editor.vue
+++ b/packages/frontend/src/pages/my-antennas/editor.vue
@@ -1,6 +1,6 @@
 <template>
-<MkSpacer :content-max="700">
-	<div class="shaynizk">
+<MkSpacer :contentMax="700">
+	<div>
 		<div class="_gaps_m">
 			<MkInput v-model="name">
 				<template #label>{{ i18n.ts.name }}</template>
@@ -33,7 +33,7 @@
 			<MkSwitch v-model="withFile">{{ i18n.ts.withFileAntenna }}</MkSwitch>
 			<MkSwitch v-model="notify">{{ i18n.ts.notifyAntenna }}</MkSwitch>
 		</div>
-		<div class="actions">
+		<div :class="$style.actions">
 			<MkButton inline primary @click="saveAntenna()"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
 			<MkButton v-if="antenna.id != null" inline danger @click="deleteAntenna()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
 		</div>
@@ -128,12 +128,10 @@ function addUser() {
 }
 </script>
 
-<style lang="scss" scoped>
-.shaynizk {
-	> .actions {
-		margin-top: 16px;
-		padding: 24px 0;
-		border-top: solid 0.5px var(--divider);
-	}
+<style lang="scss" module>
+.actions {
+	margin-top: 16px;
+	padding: 24px 0;
+	border-top: solid 0.5px var(--divider);
 }
 </style>
diff --git a/packages/frontend/src/pages/my-antennas/index.vue b/packages/frontend/src/pages/my-antennas/index.vue
index f1764b1aa..2ca026b9a 100644
--- a/packages/frontend/src/pages/my-antennas/index.vue
+++ b/packages/frontend/src/pages/my-antennas/index.vue
@@ -1,18 +1,20 @@
-<template><MkStickyContainer>
+<template>
+<MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-		<MkSpacer :content-max="700">
-	<div class="ieepwinx">
-		<MkButton :link="true" to="/my/antennas/create" primary class="add"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
+	<MkSpacer :contentMax="700">
+		<div class="ieepwinx">
+			<MkButton :link="true" to="/my/antennas/create" primary class="add"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
 
-		<div class="">
-			<MkPagination v-slot="{items}" ref="list" :pagination="pagination">
-				<MkA v-for="antenna in items" :key="antenna.id" class="ljoevbzj" :to="`/my/antennas/${antenna.id}`">
-					<div class="name">{{ antenna.name }}</div>
-				</MkA>
-			</MkPagination>
+			<div class="">
+				<MkPagination v-slot="{items}" ref="list" :pagination="pagination">
+					<MkA v-for="antenna in items" :key="antenna.id" class="ljoevbzj" :to="`/my/antennas/${antenna.id}`">
+						<div class="name">{{ antenna.name }}</div>
+					</MkA>
+				</MkPagination>
+			</div>
 		</div>
-	</div>
-</MkSpacer></MkStickyContainer>
+	</MkSpacer>
+</MkStickyContainer>
 </template>
 
 <script lang="ts" setup>
diff --git a/packages/frontend/src/pages/my-clips/index.vue b/packages/frontend/src/pages/my-clips/index.vue
index ccffa7b56..a769f8ee9 100644
--- a/packages/frontend/src/pages/my-clips/index.vue
+++ b/packages/frontend/src/pages/my-clips/index.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="700">
+	<MkSpacer :contentMax="700">
 		<div v-if="tab === 'my'" class="_gaps">
 			<MkButton primary rounded class="add" @click="create"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
 
diff --git a/packages/frontend/src/pages/my-lists/index.vue b/packages/frontend/src/pages/my-lists/index.vue
index 47437f3e5..cee241c48 100644
--- a/packages/frontend/src/pages/my-lists/index.vue
+++ b/packages/frontend/src/pages/my-lists/index.vue
@@ -1,15 +1,17 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="700">
-		<div class="qkcjvfiv">
-			<MkButton primary class="add" @click="create"><i class="ti ti-plus"></i> {{ i18n.ts.createList }}</MkButton>
+	<MkSpacer :contentMax="700">
+		<div class="_gaps">
+			<MkButton primary rounded style="margin: 0 auto;" @click="create"><i class="ti ti-plus"></i> {{ i18n.ts.createList }}</MkButton>
 
-			<MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination" class="lists">
-				<MkA v-for="list in items" :key="list.id" class="list _panel" :to="`/my/lists/${ list.id }`">
-					<div class="name">{{ list.name }}</div>
-					<MkAvatars :user-ids="list.userIds"/>
-				</MkA>
+			<MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination">
+				<div class="_gaps">
+					<MkA v-for="list in items" :key="list.id" class="_panel" :class="$style.list" :to="`/my/lists/${ list.id }`">
+						<div style="margin-bottom: 4px;">{{ list.name }}</div>
+						<MkAvatars :userIds="list.userIds"/>
+					</MkA>
+				</div>
 			</MkPagination>
 		</div>
 	</MkSpacer>
@@ -58,28 +60,17 @@ definePageMetadata({
 });
 </script>
 
-<style lang="scss" scoped>
-.qkcjvfiv {
-	> .add {
-		margin: 0 auto var(--margin) auto;
-	}
+<style lang="scss" module>
+.list {
+	display: block;
+	padding: 16px;
+	border: solid 1px var(--divider);
+	border-radius: 6px;
+	margin-bottom: 8px;
 
-	> .lists {
-		> .list {
-			display: block;
-			padding: 16px;
-			border: solid 1px var(--divider);
-			border-radius: 6px;
-
-			&:hover {
-				border: solid 1px var(--accent);
-				text-decoration: none;
-			}
-
-			> .name {
-				margin-bottom: 4px;
-			}
-		}
+	&:hover {
+		border: solid 1px var(--accent);
+		text-decoration: none;
 	}
 }
 </style>
diff --git a/packages/frontend/src/pages/my-lists/list.vue b/packages/frontend/src/pages/my-lists/list.vue
index 86201e8e0..dd431e8dc 100644
--- a/packages/frontend/src/pages/my-lists/list.vue
+++ b/packages/frontend/src/pages/my-lists/list.vue
@@ -1,35 +1,43 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="700" :class="$style.main">
-		<div v-if="list" class="members _margin">
-			<div class="">{{ i18n.ts.members }}</div>
-			<div class="_gaps_s">
-				<div v-for="user in users" :key="user.id" :class="$style.userItem">
-					<MkA :class="$style.userItemBody" :to="`${userPage(user)}`">
-						<MkUserCardMini :user="user"/>
-					</MkA>
-					<button class="_button" :class="$style.remove" @click="removeUser(user, $event)"><i class="ti ti-x"></i></button>
+	<MkSpacer :contentMax="700" :class="$style.main">
+		<div v-if="list" class="_gaps">
+			<MkFolder>
+				<template #label>{{ i18n.ts.settings }}</template>
+
+				<div class="_gaps">
+					<MkInput v-model="name">
+						<template #label>{{ i18n.ts.name }}</template>
+					</MkInput>
+					<MkSwitch v-model="isPublic">{{ i18n.ts.public }}</MkSwitch>
+					<div class="_buttons">
+						<MkButton rounded primary @click="updateSettings">{{ i18n.ts.save }}</MkButton>
+						<MkButton rounded danger @click="deleteList()">{{ i18n.ts.delete }}</MkButton>
+					</div>
 				</div>
-			</div>
+			</MkFolder>
+
+			<MkFolder defaultOpen>
+				<template #label>{{ i18n.ts.members }}</template>
+
+				<div class="_gaps_s">
+					<MkButton rounded primary style="margin: 0 auto;" @click="addUser()">{{ i18n.ts.addUser }}</MkButton>
+					<div v-for="user in users" :key="user.id" :class="$style.userItem">
+						<MkA :class="$style.userItemBody" :to="`${userPage(user)}`">
+							<MkUserCardMini :user="user"/>
+						</MkA>
+						<button class="_button" :class="$style.remove" @click="removeUser(user, $event)"><i class="ti ti-x"></i></button>
+					</div>
+				</div>
+			</MkFolder>
 		</div>
 	</MkSpacer>
-	<template #footer>
-		<div :class="$style.footer">
-			<MkSpacer :content-max="700" :margin-min="16" :margin-max="16">
-				<div class="_buttons">
-					<MkButton inline rounded primary @click="addUser()">{{ i18n.ts.addUser }}</MkButton>
-					<MkButton inline rounded @click="renameList()">{{ i18n.ts.rename }}</MkButton>
-					<MkButton inline rounded danger @click="deleteList()">{{ i18n.ts.delete }}</MkButton>
-				</div>
-			</MkSpacer>
-		</div>
-	</template>
 </MkStickyContainer>
 </template>
 
 <script lang="ts" setup>
-import { computed, watch } from 'vue';
+import { computed, ref, watch } from 'vue';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os';
 import { mainRouter } from '@/router';
@@ -37,6 +45,9 @@ import { definePageMetadata } from '@/scripts/page-metadata';
 import { i18n } from '@/i18n';
 import { userPage } from '@/filters/user';
 import MkUserCardMini from '@/components/MkUserCardMini.vue';
+import MkSwitch from '@/components/MkSwitch.vue';
+import MkFolder from '@/components/MkFolder.vue';
+import MkInput from '@/components/MkInput.vue';
 import { userListsCache } from '@/cache';
 
 const props = defineProps<{
@@ -45,12 +56,17 @@ const props = defineProps<{
 
 let list = $ref(null);
 let users = $ref([]);
+const isPublic = ref(false);
+const name = ref('');
 
 function fetchList() {
 	os.api('users/lists/show', {
 		listId: props.listId,
 	}).then(_list => {
 		list = _list;
+		name.value = list.name;
+		isPublic.value = list.isPublic;
+
 		os.api('users/show', {
 			userIds: list.userIds,
 		}).then(_users => {
@@ -86,23 +102,6 @@ async function removeUser(user, ev) {
 	}], ev.currentTarget ?? ev.target);
 }
 
-async function renameList() {
-	const { canceled, result: name } = await os.inputText({
-		title: i18n.ts.enterListName,
-		default: list.name,
-	});
-	if (canceled) return;
-
-	await os.api('users/lists/update', {
-		listId: list.id,
-		name: name,
-	});
-
-	userListsCache.delete();
-
-	list.name = name;
-}
-
 async function deleteList() {
 	const { canceled } = await os.confirm({
 		type: 'warning',
@@ -117,6 +116,19 @@ async function deleteList() {
 	mainRouter.push('/my/lists');
 }
 
+async function updateSettings() {
+	await os.apiWithDialog('users/lists/update', {
+		listId: list.id,
+		name: name.value,
+		isPublic: isPublic.value,
+	});
+
+	userListsCache.delete();
+
+	list.name = name.value;
+	list.isPublic = isPublic.value;
+}
+
 watch(() => props.listId, fetchList, { immediate: true });
 
 const headerActions = $computed(() => []);
diff --git a/packages/frontend/src/pages/not-found.vue b/packages/frontend/src/pages/not-found.vue
index e58e44ef7..2c9d94901 100644
--- a/packages/frontend/src/pages/not-found.vue
+++ b/packages/frontend/src/pages/not-found.vue
@@ -1,5 +1,5 @@
 <template>
-<div class="ipledcug">
+<div>
 	<div class="_fullinfo">
 		<img src="https://xn--931a.moe/assets/not-found.jpg" class="_ghost"/>
 		<div>{{ i18n.ts.notFoundDescription }}</div>
diff --git a/packages/frontend/src/pages/note.vue b/packages/frontend/src/pages/note.vue
index d9baa1096..c519cefba 100644
--- a/packages/frontend/src/pages/note.vue
+++ b/packages/frontend/src/pages/note.vue
@@ -1,33 +1,33 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="800">
-		<div class="fcuexfpr">
+	<MkSpacer :contentMax="800">
+		<div>
 			<Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in">
-				<div v-if="note" class="note">
+				<div v-if="note">
 					<div v-if="showNext" class="_margin">
-						<MkNotes class="" :pagination="nextPagination" :no-gap="true"/>
+						<MkNotes class="" :pagination="nextPagination" :noGap="true"/>
 					</div>
 
-					<div class="main _margin">
-						<MkButton v-if="!showNext && hasNext" class="load next" @click="showNext = true"><i class="ti ti-chevron-up"></i></MkButton>
-						<div class="note _margin _gaps_s">
+					<div class="_margin">
+						<MkButton v-if="!showNext && hasNext" :class="$style.loadNext" @click="showNext = true"><i class="ti ti-chevron-up"></i></MkButton>
+						<div class="_margin _gaps_s">
 							<MkRemoteCaution v-if="note.user.host != null" :href="note.url ?? note.uri"/>
-							<MkNoteDetailed :key="note.id" v-model:note="note" class="note"/>
+							<MkNoteDetailed :key="note.id" v-model:note="note" :class="$style.note"/>
 						</div>
-						<div v-if="clips && clips.length > 0" class="clips _margin">
-							<div class="title">{{ i18n.ts.clip }}</div>
+						<div v-if="clips && clips.length > 0" class="_margin">
+							<div style="font-weight: bold; padding: 12px;">{{ i18n.ts.clip }}</div>
 							<div class="_gaps">
 								<MkA v-for="item in clips" :key="item.id" :to="`/clips/${item.id}`">
 									<MkClipPreview :clip="item"/>
 								</MkA>
 							</div>
 						</div>
-						<MkButton v-if="!showPrev && hasPrev" class="load prev" @click="showPrev = true"><i class="ti ti-chevron-down"></i></MkButton>
+						<MkButton v-if="!showPrev && hasPrev" :class="$style.loadPrev" @click="showPrev = true"><i class="ti ti-chevron-down"></i></MkButton>
 					</div>
 
 					<div v-if="showPrev" class="_margin">
-						<MkNotes class="" :pagination="prevPagination" :no-gap="true"/>
+						<MkNotes class="" :pagination="prevPagination" :noGap="true"/>
 					</div>
 				</div>
 				<MkError v-else-if="error" @retry="fetchNote()"/>
@@ -137,7 +137,7 @@ definePageMetadata(computed(() => note ? {
 } : null));
 </script>
 
-<style lang="scss" scoped>
+<style lang="scss" module>
 .fade-enter-active,
 .fade-leave-active {
 	transition: opacity 0.125s ease;
@@ -147,39 +147,23 @@ definePageMetadata(computed(() => note ? {
 	opacity: 0;
 }
 
-.fcuexfpr {
-	background: var(--bg);
+.loadNext,
+.loadPrev {
+	min-width: 0;
+	margin: 0 auto;
+	border-radius: 999px;
+}
 
-	> .note {
-		> .main {
-			> .load {
-				min-width: 0;
-				margin: 0 auto;
-				border-radius: 999px;
+.loadNext {
+	margin-bottom: var(--margin);
+}
 
-				&.next {
-					margin-bottom: var(--margin);
-				}
+.loadPrev {
+	margin-top: var(--margin);
+}
 
-				&.prev {
-					margin-top: var(--margin);
-				}
-			}
-
-			> .note {
-				> .note {
-					border-radius: var(--radius);
-					background: var(--panel);
-				}
-			}
-
-			> .clips {
-				> .title {
-					font-weight: bold;
-					padding: 12px;
-				}
-			}
-		}
-	}
+.note {
+	border-radius: var(--radius);
+	background: var(--panel);
 }
 </style>
diff --git a/packages/frontend/src/pages/notifications.vue b/packages/frontend/src/pages/notifications.vue
index 1789606cd..8196f9186 100644
--- a/packages/frontend/src/pages/notifications.vue
+++ b/packages/frontend/src/pages/notifications.vue
@@ -1,9 +1,9 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="800">
+	<MkSpacer :contentMax="800">
 		<div v-if="tab === 'all'">
-			<XNotifications class="notifications" :include-types="includeTypes"/>
+			<XNotifications class="notifications" :includeTypes="includeTypes"/>
 		</div>
 		<div v-else-if="tab === 'mentions'">
 			<MkNotes :pagination="mentionsPagination"/>
diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue
index 1b292e8f3..eca3feda6 100644
--- a/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue
+++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue
@@ -8,8 +8,8 @@
 		</button>
 	</template>
 
-	<section class="oyyftmcf">
-		<MkDriveFileThumbnail v-if="file" class="preview" :file="file" fit="contain" @click="choose()"/>
+	<section>
+		<MkDriveFileThumbnail v-if="file" style="height: 150px;" :file="file" fit="contain" @click="choose()"/>
 	</section>
 </XContainer>
 </template>
@@ -54,11 +54,3 @@ onMounted(async () => {
 	}
 });
 </script>
-
-<style lang="scss" scoped>
-.oyyftmcf {
-	> .preview {
-		height: 150px;
-	}
-}
-</style>
diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue
index bf21ae3c6..3b15c1774 100644
--- a/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue
+++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue
@@ -3,8 +3,8 @@
 <XContainer :draggable="true" @remove="() => $emit('remove')">
 	<template #header><i class="ti ti-align-left"></i> {{ i18n.ts._pages.blocks.text }}</template>
 
-	<section class="vckmsadr">
-		<textarea v-model="text"></textarea>
+	<section>
+		<textarea v-model="text" :class="$style.textarea"></textarea>
 	</section>
 </XContainer>
 </template>
@@ -33,23 +33,21 @@ watch($$(text), () => {
 });
 </script>
 
-<style lang="scss" scoped>
-.vckmsadr {
-	> textarea {
-		display: block;
-		-webkit-appearance: none;
-		-moz-appearance: none;
-		appearance: none;
-		width: 100%;
-		min-width: 100%;
-		min-height: 150px;
-		border: none;
-		box-shadow: none;
-		padding: 16px;
-		background: transparent;
-		color: var(--fg);
-		font-size: 14px;
-		box-sizing: border-box;
-	}
+<style lang="scss" module>
+.textarea {
+	display: block;
+	-webkit-appearance: none;
+	-moz-appearance: none;
+	appearance: none;
+	width: 100%;
+	min-width: 100%;
+	min-height: 150px;
+	border: none;
+	box-shadow: none;
+	padding: 16px;
+	background: transparent;
+	color: var(--fg);
+	font-size: 14px;
+	box-sizing: border-box;
 }
 </style>
diff --git a/packages/frontend/src/pages/page-editor/page-editor.blocks.vue b/packages/frontend/src/pages/page-editor/page-editor.blocks.vue
index 97bdcfe80..fc945b3d6 100644
--- a/packages/frontend/src/pages/page-editor/page-editor.blocks.vue
+++ b/packages/frontend/src/pages/page-editor/page-editor.blocks.vue
@@ -1,57 +1,59 @@
 <template>
-<Sortable :model-value="modelValue" tag="div" item-key="id" handle=".drag-handle" :group="{ name: 'blocks' }" :animation="150" :swap-threshold="0.5" @update:model-value="v => $emit('update:modelValue', v)">
+<Sortable :modelValue="modelValue" tag="div" itemKey="id" handle=".drag-handle" :group="{ name: 'blocks' }" :animation="150" :swapThreshold="0.5" @update:modelValue="v => $emit('update:modelValue', v)">
 	<template #item="{element}">
 		<div :class="$style.item">
 			<!-- divが無いとエラーになる https://github.com/SortableJS/vue.draggable.next/issues/189 -->
-			<component :is="'x-' + element.type" :model-value="element" @update:model-value="updateItem" @remove="() => removeItem(element)"/>
+			<component :is="getComponent(element.type)" :modelValue="element" @update:modelValue="updateItem" @remove="() => removeItem(element)"/>
 		</div>
 	</template>
 </Sortable>
 </template>
 
-<script lang="ts">
-import { defineComponent, defineAsyncComponent } from 'vue';
+<script lang="ts" setup>
+import { defineAsyncComponent } from 'vue';
 import XSection from './els/page-editor.el.section.vue';
 import XText from './els/page-editor.el.text.vue';
 import XImage from './els/page-editor.el.image.vue';
 import XNote from './els/page-editor.el.note.vue';
 
-export default defineComponent({
-	components: {
-		Sortable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)),
-		XSection, XText, XImage, XNote,
-	},
+function getComponent(type: string) {
+	switch (type) {
+		case 'section': return XSection;
+		case 'text': return XText;
+		case 'image': return XImage;
+		case 'note': return XNote;
+		default: return null;
+	}
+}
 
-	props: {
-		modelValue: {
-			type: Array,
-			required: true,
-		},
-	},
+const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
 
-	emits: ['update:modelValue'],
+const props = defineProps<{
+	modelValue: any[];
+}>();
 
-	methods: {
-		updateItem(v) {
-			const i = this.modelValue.findIndex(x => x.id === v.id);
-			const newValue = [
-				...this.modelValue.slice(0, i),
-				v,
-				...this.modelValue.slice(i + 1),
-			];
-			this.$emit('update:modelValue', newValue);
-		},
+const emit = defineEmits<{
+	(ev: 'update:modelValue', value: any[]): void;
+}>();
 
-		removeItem(el) {
-			const i = this.modelValue.findIndex(x => x.id === el.id);
-			const newValue = [
-				...this.modelValue.slice(0, i),
-				...this.modelValue.slice(i + 1),
-			];
-			this.$emit('update:modelValue', newValue);
-		},
-	},
-});
+function updateItem(v) {
+	const i = props.modelValue.findIndex(x => x.id === v.id);
+	const newValue = [
+		...props.modelValue.slice(0, i),
+		v,
+		...props.modelValue.slice(i + 1),
+	];
+	emit('update:modelValue', newValue);
+}
+
+function removeItem(el) {
+	const i = props.modelValue.findIndex(x => x.id === el.id);
+	const newValue = [
+		...props.modelValue.slice(0, i),
+		...props.modelValue.slice(i + 1),
+	];
+	emit('update:modelValue', newValue);
+}
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/page-editor/page-editor.container.vue b/packages/frontend/src/pages/page-editor/page-editor.container.vue
index dd733403a..0842b4fd2 100644
--- a/packages/frontend/src/pages/page-editor/page-editor.container.vue
+++ b/packages/frontend/src/pages/page-editor/page-editor.container.vue
@@ -1,5 +1,5 @@
 <template>
-<div class="cpjygsrt" :class="{ error: error != null, warn: warn != null }">
+<div class="cpjygsrt">
 	<header>
 		<div class="title"><slot name="header"></slot></div>
 		<div class="buttons">
@@ -16,58 +16,40 @@
 			</button>
 		</div>
 	</header>
-	<p v-show="showBody" v-if="error != null" class="error">{{ i18n.t('_pages.script.typeError', { slot: error.arg + 1, expect: i18n.t(`script.types.${error.expect}`), actual: i18n.t(`script.types.${error.actual}`) }) }}</p>
-	<p v-show="showBody" v-if="warn != null" class="warn">{{ i18n.t('_pages.script.thereIsEmptySlot', { slot: warn.slot + 1 }) }}</p>
 	<div v-show="showBody" class="body">
 		<slot></slot>
 	</div>
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { ref } from 'vue';
 import { i18n } from '@/i18n';
 
-export default defineComponent({
-	props: {
-		expanded: {
-			type: Boolean,
-			default: true,
-		},
-		removable: {
-			type: Boolean,
-			default: true,
-		},
-		draggable: {
-			type: Boolean,
-			default: false,
-		},
-		error: {
-			required: false,
-			default: null,
-		},
-		warn: {
-			required: false,
-			default: null,
-		},
-	},
-	emits: ['toggle', 'remove'],
-	data() {
-		return {
-			showBody: this.expanded,
-			i18n,
-		};
-	},
-	methods: {
-		toggleContent(show: boolean) {
-			this.showBody = show;
-			this.$emit('toggle', show);
-		},
-		remove() {
-			this.$emit('remove');
-		},
-	},
+const props = withDefaults(defineProps<{
+	expanded?: boolean;
+	removable?: boolean;
+	draggable?: boolean;
+}>(), {
+	expanded: true,
+	removable: true,
 });
+
+const emit = defineEmits<{
+	(ev: 'toggle', show: boolean): void;
+	(ev: 'remove'): void;
+}>();
+
+const showBody = ref(props.expanded);
+
+function toggleContent(show: boolean) {
+	showBody.value = show;
+	emit('toggle', show);
+}
+
+function remove() {
+	emit('remove');
+}
 </script>
 
 <style lang="scss" scoped>
@@ -128,20 +110,6 @@ export default defineComponent({
 		}
 	}
 
-	> .warn {
-		color: #b19e49;
-		margin: 0;
-		padding: 16px 16px 0 16px;
-		font-size: 14px;
-	}
-
-	> .error {
-		color: #f00;
-		margin: 0;
-		padding: 16px 16px 0 16px;
-		font-size: 14px;
-	}
-
 	> .body {
 		::v-deep(.juejbjww), ::v-deep(.eiipwacr) {
 			&:not(.inline):first-child {
diff --git a/packages/frontend/src/pages/page-editor/page-editor.vue b/packages/frontend/src/pages/page-editor/page-editor.vue
index bcf30e23a..bd54699dc 100644
--- a/packages/frontend/src/pages/page-editor/page-editor.vue
+++ b/packages/frontend/src/pages/page-editor/page-editor.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="700">
+	<MkSpacer :contentMax="700">
 		<div class="jqqmcavi">
 			<MkButton v-if="pageId" class="button" inline link :to="`/@${ author.username }/pages/${ currentName }`"><i class="ti ti-external-link"></i> {{ i18n.ts._pages.viewPage }}</MkButton>
 			<MkButton v-if="!readonly" inline primary class="button" @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
diff --git a/packages/frontend/src/pages/page.vue b/packages/frontend/src/pages/page.vue
index 5a0f58c8d..27a4cd059 100644
--- a/packages/frontend/src/pages/page.vue
+++ b/packages/frontend/src/pages/page.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="700">
+	<MkSpacer :contentMax="700">
 		<Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in">
 			<div v-if="page" :key="page.id" class="xcukqgmh">
 				<div class="main">
@@ -18,8 +18,8 @@
 					</div>
 					<div class="actions">
 						<div class="like">
-							<MkButton v-if="page.isLiked" v-tooltip="i18n.ts._pages.unlike" class="button" as-like primary @click="unlike()"><i class="ti ti-heart-off"></i><span v-if="page.likedCount > 0" class="count">{{ page.likedCount }}</span></MkButton>
-							<MkButton v-else v-tooltip="i18n.ts._pages.like" class="button" as-like @click="like()"><i class="ti ti-heart"></i><span v-if="page.likedCount > 0" class="count">{{ page.likedCount }}</span></MkButton>
+							<MkButton v-if="page.isLiked" v-tooltip="i18n.ts._pages.unlike" class="button" asLike primary @click="unlike()"><i class="ti ti-heart-off"></i><span v-if="page.likedCount > 0" class="count">{{ page.likedCount }}</span></MkButton>
+							<MkButton v-else v-tooltip="i18n.ts._pages.like" class="button" asLike @click="like()"><i class="ti ti-heart"></i><span v-if="page.likedCount > 0" class="count">{{ page.likedCount }}</span></MkButton>
 						</div>
 						<div class="other">
 							<button v-tooltip="i18n.ts.shareWithNote" v-click-anime class="_button" @click="shareWithNote"><i class="ti ti-repeat ti-fw"></i></button>
diff --git a/packages/frontend/src/pages/pages.vue b/packages/frontend/src/pages/pages.vue
index 0427332ab..4f67bda11 100644
--- a/packages/frontend/src/pages/pages.vue
+++ b/packages/frontend/src/pages/pages.vue
@@ -1,23 +1,29 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="700">
-		<div v-if="tab === 'featured'" class="rknalgpo">
+	<MkSpacer :contentMax="700">
+		<div v-if="tab === 'featured'">
 			<MkPagination v-slot="{items}" :pagination="featuredPagesPagination">
-				<MkPagePreview v-for="page in items" :key="page.id" class="ckltabjg" :page="page"/>
+				<div class="_gaps">
+					<MkPagePreview v-for="page in items" :key="page.id" :page="page"/>
+				</div>
 			</MkPagination>
 		</div>
 
-		<div v-else-if="tab === 'my'" class="rknalgpo my">
+		<div v-else-if="tab === 'my'" class="_gaps">
 			<MkButton class="new" @click="create()"><i class="ti ti-plus"></i></MkButton>
 			<MkPagination v-slot="{items}" :pagination="myPagesPagination">
-				<MkPagePreview v-for="page in items" :key="page.id" class="ckltabjg" :page="page"/>
+				<div class="_gaps">
+					<MkPagePreview v-for="page in items" :key="page.id" :page="page"/>
+				</div>
 			</MkPagination>
 		</div>
 
-		<div v-else-if="tab === 'liked'" class="rknalgpo">
+		<div v-else-if="tab === 'liked'">
 			<MkPagination v-slot="{items}" :pagination="likedPagesPagination">
-				<MkPagePreview v-for="like in items" :key="like.page.id" class="ckltabjg" :page="like.page"/>
+				<div class="_gaps">
+					<MkPagePreview v-for="like in items" :key="like.page.id" :page="like.page"/>
+				</div>
 			</MkPagination>
 		</div>
 	</MkSpacer>
@@ -79,21 +85,3 @@ definePageMetadata(computed(() => ({
 	icon: 'ti ti-note',
 })));
 </script>
-
-<style lang="scss" scoped>
-.rknalgpo {
-	&.my .ckltabjg:first-child {
-		margin-top: 16px;
-	}
-
-	.ckltabjg:not(:last-child) {
-		margin-bottom: 8px;
-	}
-
-	@media (min-width: 500px) {
-		.ckltabjg:not(:last-child) {
-			margin-bottom: 16px;
-		}
-	}
-}
-</style>
diff --git a/packages/frontend/src/pages/preview.vue b/packages/frontend/src/pages/preview.vue
deleted file mode 100644
index 354f686e4..000000000
--- a/packages/frontend/src/pages/preview.vue
+++ /dev/null
@@ -1,27 +0,0 @@
-<template>
-<div class="graojtoi">
-	<MkSample/>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { computed } from 'vue';
-import MkSample from '@/components/MkSample.vue';
-import { i18n } from '@/i18n';
-import { definePageMetadata } from '@/scripts/page-metadata';
-
-const headerActions = $computed(() => []);
-
-const headerTabs = $computed(() => []);
-
-definePageMetadata(computed(() => ({
-	title: i18n.ts.preview,
-	icon: 'ti ti-eye',
-})));
-</script>
-
-<style lang="scss" scoped>
-.graojtoi {
-	padding: var(--margin);
-}
-</style>
diff --git a/packages/frontend/src/pages/registry.keys.vue b/packages/frontend/src/pages/registry.keys.vue
index c687b89ea..b1d41fe2c 100644
--- a/packages/frontend/src/pages/registry.keys.vue
+++ b/packages/frontend/src/pages/registry.keys.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="600" :margin-min="16">
+	<MkSpacer :contentMax="600" :marginMin="16">
 		<div class="_gaps_m">
 			<FormSplit>
 				<MkKeyValue>
@@ -93,6 +93,3 @@ definePageMetadata({
 	icon: 'ti ti-adjustments',
 });
 </script>
-
-<style lang="scss" scoped>
-</style>
diff --git a/packages/frontend/src/pages/registry.value.vue b/packages/frontend/src/pages/registry.value.vue
index 00e2ca5e0..513a2f8fe 100644
--- a/packages/frontend/src/pages/registry.value.vue
+++ b/packages/frontend/src/pages/registry.value.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="600" :margin-min="16">
+	<MkSpacer :contentMax="600" :marginMin="16">
 		<div class="_gaps_m">
 			<FormInfo warn>{{ i18n.ts.editTheseSettingsMayBreakAccount }}</FormInfo>
 
@@ -118,6 +118,3 @@ definePageMetadata({
 	icon: 'ti ti-adjustments',
 });
 </script>
-
-<style lang="scss" scoped>
-</style>
diff --git a/packages/frontend/src/pages/registry.vue b/packages/frontend/src/pages/registry.vue
index 5a029cb0c..6bfb9bce5 100644
--- a/packages/frontend/src/pages/registry.vue
+++ b/packages/frontend/src/pages/registry.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="600" :margin-min="16">
+	<MkSpacer :contentMax="600" :marginMin="16">
 		<MkButton primary @click="createKey">{{ i18n.ts._registry.createKey }}</MkButton>
 
 		<FormSection v-if="scopes">
@@ -68,6 +68,3 @@ definePageMetadata({
 	icon: 'ti ti-adjustments',
 });
 </script>
-
-<style lang="scss" scoped>
-</style>
diff --git a/packages/frontend/src/pages/reset-password.vue b/packages/frontend/src/pages/reset-password.vue
index 38c88cc65..9d5730731 100644
--- a/packages/frontend/src/pages/reset-password.vue
+++ b/packages/frontend/src/pages/reset-password.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer v-if="token" :content-max="700" :margin-min="16" :margin-max="32">
+	<MkSpacer v-if="token" :contentMax="700" :marginMin="16" :marginMax="32">
 		<div class="_gaps_m">
 			<MkInput v-model="password" type="password">
 				<template #prefix><i class="ti ti-lock"></i></template>
@@ -53,7 +53,3 @@ definePageMetadata({
 	icon: 'ti ti-lock',
 });
 </script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/packages/frontend/src/pages/role.vue b/packages/frontend/src/pages/role.vue
index fe39c594b..e85ab0917 100644
--- a/packages/frontend/src/pages/role.vue
+++ b/packages/frontend/src/pages/role.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :tabs="headerTabs"/></template>
-	<MKSpacer v-if="!(typeof error === 'undefined')" :content-max="1200">
+	<MKSpacer v-if="!(typeof error === 'undefined')" :contentMax="1200">
 		<div :class="$style.root">
 			<img :class="$style.img" src="https://xn--931a.moe/assets/error.jpg" class="_ghost"/>
 			<p :class="$style.text">
@@ -10,17 +10,18 @@
 			</p>
 		</div>
 	</MKSpacer>
-	<MkSpacer v-else-if="tab === 'users'" :content-max="1200">
+	<MkSpacer v-else-if="tab === 'users'" :contentMax="1200">
 		<div class="_gaps_s">
 			<div v-if="role">{{ role.description }}</div>
 			<MkUserList :pagination="users" :extractor="(item) => item.user"/>
 		</div>
 	</MkSpacer>
-	<MkSpacer v-else-if="tab === 'timeline'" :content-max="700">
+	<MkSpacer v-else-if="tab === 'timeline'" :contentMax="700">
 		<MkTimeline ref="timeline" src="role" :role="props.role"/>
 	</MkSpacer>
 </MkStickyContainer>
 </template>
+
 <script lang="ts" setup>
 import { computed, watch } from 'vue';
 import * as os from '@/os';
@@ -80,6 +81,7 @@ definePageMetadata(computed(() => ({
 	icon: 'ti ti-badge',
 })));
 </script>
+
 <style lang="scss" module>
 .root {
 	padding: 32px;
diff --git a/packages/frontend/src/pages/scratchpad.vue b/packages/frontend/src/pages/scratchpad.vue
index fb78546cb..22eb00dad 100644
--- a/packages/frontend/src/pages/scratchpad.vue
+++ b/packages/frontend/src/pages/scratchpad.vue
@@ -1,8 +1,8 @@
 <template>
-<MkSpacer :content-max="800">
+<MkSpacer :contentMax="800">
 	<div :class="$style.root">
 		<div :class="$style.editor" class="_panel">
-			<PrismEditor v-model="code" class="_code code" :highlight="highlighter" :line-numbers="false"/>
+			<PrismEditor v-model="code" class="_code code" :highlight="highlighter" :lineNumbers="false"/>
 			<MkButton style="position: absolute; top: 8px; right: 8px;" primary @click="run()"><i class="ti ti-player-play"></i></MkButton>
 		</div>
 
diff --git a/packages/frontend/src/pages/search.user.vue b/packages/frontend/src/pages/search.user.vue
index 23a8978fd..bd1389ffe 100644
--- a/packages/frontend/src/pages/search.user.vue
+++ b/packages/frontend/src/pages/search.user.vue
@@ -4,7 +4,7 @@
 		<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search">
 			<template #prefix><i class="ti ti-search"></i></template>
 		</MkInput>
-		<MkRadios v-model="searchOrigin" @update:model-value="search()">
+		<MkRadios v-model="searchOrigin" @update:modelValue="search()">
 			<option value="combined">{{ i18n.ts.all }}</option>
 			<option value="local">{{ i18n.ts.local }}</option>
 			<option value="remote">{{ i18n.ts.remote }}</option>
diff --git a/packages/frontend/src/pages/search.vue b/packages/frontend/src/pages/search.vue
index 9f3d8da56..dcaf42e64 100644
--- a/packages/frontend/src/pages/search.vue
+++ b/packages/frontend/src/pages/search.vue
@@ -2,7 +2,7 @@
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
 
-	<MkSpacer v-if="tab === 'note'" :content-max="800">
+	<MkSpacer v-if="tab === 'note'" :contentMax="800">
 		<div v-if="notesSearchAvailable">
 			<XNote/>
 		</div>
@@ -11,7 +11,7 @@
 		</div>
 	</MkSpacer>
 
-	<MkSpacer v-else-if="tab === 'user'" :content-max="800">
+	<MkSpacer v-else-if="tab === 'user'" :contentMax="800">
 		<XUser/>
 	</MkSpacer>
 </MkStickyContainer>
diff --git a/packages/frontend/src/pages/settings/2fa.qrdialog.vue b/packages/frontend/src/pages/settings/2fa.qrdialog.vue
index 1d836db5f..6a798b562 100644
--- a/packages/frontend/src/pages/settings/2fa.qrdialog.vue
+++ b/packages/frontend/src/pages/settings/2fa.qrdialog.vue
@@ -1,8 +1,8 @@
 <template>
 <MkModal
 	ref="dialogEl"
-	:prefer-type="'dialog'"
-	:z-priority="'low'"
+	:preferType="'dialog'"
+	:zPriority="'low'"
 	@click="cancel"
 	@close="cancel"
 	@closed="emit('closed')"
diff --git a/packages/frontend/src/pages/settings/2fa.vue b/packages/frontend/src/pages/settings/2fa.vue
index 891934d70..aff7765ed 100644
--- a/packages/frontend/src/pages/settings/2fa.vue
+++ b/packages/frontend/src/pages/settings/2fa.vue
@@ -51,7 +51,7 @@
 			</div>
 		</MkFolder>
 
-		<MkSwitch :disabled="!$i.twoFactorEnabled || $i.securityKeysList.length === 0" :model-value="usePasswordLessLogin" @update:model-value="v => updatePasswordLessLogin(v)">
+		<MkSwitch :disabled="!$i.twoFactorEnabled || $i.securityKeysList.length === 0" :modelValue="usePasswordLessLogin" @update:modelValue="v => updatePasswordLessLogin(v)">
 			<template #label>{{ i18n.ts.passwordLessLogin }}</template>
 			<template #caption>{{ i18n.ts.passwordLessLoginDescription }}</template>
 		</MkSwitch>
diff --git a/packages/frontend/src/pages/settings/accounts.vue b/packages/frontend/src/pages/settings/accounts.vue
index a58e74fe6..78479be97 100644
--- a/packages/frontend/src/pages/settings/accounts.vue
+++ b/packages/frontend/src/pages/settings/accounts.vue
@@ -15,13 +15,13 @@
 
 <script lang="ts" setup>
 import { defineAsyncComponent, ref } from 'vue';
+import type * as Misskey from 'misskey-js';
 import FormSuspense from '@/components/form/suspense.vue';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os';
 import { getAccounts, addAccount as addAccounts, removeAccount as _removeAccount, login, $i } from '@/account';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
-import type * as Misskey from 'misskey-js';
 import MkUserCardMini from '@/components/MkUserCardMini.vue';
 
 const storedAccounts = ref<any>(null);
diff --git a/packages/frontend/src/pages/settings/apps.vue b/packages/frontend/src/pages/settings/apps.vue
index 599d6329e..fbb78200d 100644
--- a/packages/frontend/src/pages/settings/apps.vue
+++ b/packages/frontend/src/pages/settings/apps.vue
@@ -9,11 +9,11 @@
 		</template>
 		<template #default="{items}">
 			<div class="_gaps">
-				<div v-for="token in items" :key="token.id" class="_panel bfomjevm">
-					<img v-if="token.iconUrl" class="icon" :src="token.iconUrl" alt=""/>
-					<div class="body">
-						<div class="name">{{ token.name }}</div>
-						<div class="description">{{ token.description }}</div>
+				<div v-for="token in items" :key="token.id" class="_panel" :class="$style.app">
+					<img v-if="token.iconUrl" :class="$style.appIcon" :src="token.iconUrl" alt=""/>
+					<div :class="$style.appBody">
+						<div :class="$style.appName">{{ token.name }}</div>
+						<div>{{ token.description }}</div>
 						<MkKeyValue oneline>
 							<template #key>{{ i18n.ts.installedDate }}</template>
 							<template #value><MkTime :time="token.createdAt"/></template>
@@ -28,7 +28,7 @@
 								<li v-for="p in token.permission" :key="p">{{ i18n.t(`_permissions.${p}`) }}</li>
 							</ul>
 						</details>
-						<div class="actions">
+						<div>
 							<MkButton inline danger @click="revoke(token)"><i class="ti ti-trash"></i></MkButton>
 						</div>
 					</div>
@@ -75,27 +75,27 @@ definePageMetadata({
 });
 </script>
 
-<style lang="scss" scoped>
-.bfomjevm {
+<style lang="scss" module>
+.app {
 	display: flex;
 	padding: 16px;
+}
 
-	> .icon {
-		display: block;
-		flex-shrink: 0;
-		margin: 0 12px 0 0;
-		width: 50px;
-		height: 50px;
-		border-radius: 8px;
-	}
+.appIcon {
+	display: block;
+	flex-shrink: 0;
+	margin: 0 12px 0 0;
+	width: 50px;
+	height: 50px;
+	border-radius: 8px;
+}
 
-	> .body {
-		width: calc(100% - 62px);
-		position: relative;
+.appBody {
+	width: calc(100% - 62px);
+	position: relative;
+}
 
-		> .name {
-			font-weight: bold;
-		}
-	}
+.appName {
+	font-weight: bold;
 }
 </style>
diff --git a/packages/frontend/src/pages/settings/custom-css.vue b/packages/frontend/src/pages/settings/custom-css.vue
index 456c3742c..970d5689b 100644
--- a/packages/frontend/src/pages/settings/custom-css.vue
+++ b/packages/frontend/src/pages/settings/custom-css.vue
@@ -2,7 +2,7 @@
 <div class="_gaps_m">
 	<FormInfo warn>{{ i18n.ts.customCssWarn }}</FormInfo>
 
-	<MkTextarea v-model="localCustomCss" manual-save tall class="_monospace" style="tab-size: 2;">
+	<MkTextarea v-model="localCustomCss" manualSave tall class="_monospace" style="tab-size: 2;">
 		<template #label>CSS</template>
 	</MkTextarea>
 </div>
diff --git a/packages/frontend/src/pages/settings/drive.vue b/packages/frontend/src/pages/settings/drive.vue
index 73c2b2e60..8d7b30dc6 100644
--- a/packages/frontend/src/pages/settings/drive.vue
+++ b/packages/frontend/src/pages/settings/drive.vue
@@ -4,8 +4,8 @@
 		<template #label>{{ i18n.ts.usageAmount }}</template>
 
 		<div class="_gaps_m">
-			<div class="uawsfosz">
-				<div class="meter"><div :style="meterStyle"></div></div>
+			<div>
+				<div :class="$style.meter"><div :class="$style.meterValue" :style="meterStyle"></div></div>
 			</div>
 			<FormSplit>
 				<MkKeyValue>
@@ -22,7 +22,7 @@
 
 	<FormSection>
 		<template #label>{{ i18n.ts.statistics }}</template>
-		<MkChart src="per-user-drive" :args="{ user: $i }" span="day" :limit="7 * 5" :bar="true" :stacked="true" :detailed="false" :aspect-ratio="6"/>
+		<MkChart src="per-user-drive" :args="{ user: $i }" span="day" :limit="7 * 5" :bar="true" :stacked="true" :detailed="false" :aspectRatio="6"/>
 	</FormSection>
 
 	<FormSection>
@@ -39,10 +39,10 @@
 				<template #label>{{ i18n.ts.keepOriginalUploading }}</template>
 				<template #caption>{{ i18n.ts.keepOriginalUploadingDescription }}</template>
 			</MkSwitch>
-			<MkSwitch v-model="alwaysMarkNsfw" @update:model-value="saveProfile()">
+			<MkSwitch v-model="alwaysMarkNsfw" @update:modelValue="saveProfile()">
 				<template #label>{{ i18n.ts.alwaysMarkSensitive }}</template>
 			</MkSwitch>
-			<MkSwitch v-model="autoSensitive" @update:model-value="saveProfile()">
+			<MkSwitch v-model="autoSensitive" @update:modelValue="saveProfile()">
 				<template #label>{{ i18n.ts.enableAutoSensitive }}<span class="_beta">{{ i18n.ts.beta }}</span></template>
 				<template #caption>{{ i18n.ts.enableAutoSensitiveDescription }}</template>
 			</MkSwitch>
@@ -139,22 +139,16 @@ definePageMetadata({
 });
 </script>
 
-<style lang="scss" scoped>
+<style lang="scss" module>
+.meter {
+	height: 10px;
+	background: rgba(0, 0, 0, 0.1);
+	border-radius: 999px;
+	overflow: clip;
+}
 
-@use "sass:math";
-
-.uawsfosz {
-
-	> .meter {
-		$size: 12px;
-		background: rgba(0, 0, 0, 0.1);
-		border-radius: math.div($size, 2);
-		overflow: hidden;
-
-		> div {
-			height: $size;
-			border-radius: math.div($size, 2);
-		}
-	}
+.meterValue {
+	height: 100%;
+	border-radius: 999px;
 }
 </style>
diff --git a/packages/frontend/src/pages/settings/email.vue b/packages/frontend/src/pages/settings/email.vue
index b1e6f223b..d015cec15 100644
--- a/packages/frontend/src/pages/settings/email.vue
+++ b/packages/frontend/src/pages/settings/email.vue
@@ -2,7 +2,7 @@
 <div v-if="instance.enableEmail" class="_gaps_m">
 	<FormSection first>
 		<template #label>{{ i18n.ts.emailAddress }}</template>
-		<MkInput v-model="emailAddress" type="email" manual-save>
+		<MkInput v-model="emailAddress" type="email" manualSave>
 			<template #prefix><i class="ti ti-mail"></i></template>
 			<template v-if="$i.email && !$i.emailVerified" #caption>{{ i18n.ts.verificationEmailSent }}</template>
 			<template v-else-if="emailAddress === $i.email && $i.emailVerified" #caption><i class="ti ti-check" style="color: var(--success);"></i> {{ i18n.ts.emailVerified }}</template>
@@ -10,7 +10,7 @@
 	</FormSection>
 
 	<FormSection>
-		<MkSwitch :model-value="$i.receiveAnnouncementEmail" @update:model-value="onChangeReceiveAnnouncementEmail">
+		<MkSwitch :modelValue="$i.receiveAnnouncementEmail" @update:modelValue="onChangeReceiveAnnouncementEmail">
 			{{ i18n.ts.receiveAnnouncementFromInstance }}
 		</MkSwitch>
 	</FormSection>
diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue
index ba0f3274f..20b36f0fc 100644
--- a/packages/frontend/src/pages/settings/general.vue
+++ b/packages/frontend/src/pages/settings/general.vue
@@ -24,6 +24,7 @@
 		<div class="_gaps_s">
 			<MkSwitch v-model="showFixedPostForm">{{ i18n.ts.showFixedPostForm }}</MkSwitch>
 			<MkSwitch v-model="showFixedPostFormInChannel">{{ i18n.ts.showFixedPostFormInChannel }}</MkSwitch>
+			<MkSwitch v-model="showTimelineReplies">{{ i18n.ts.flagShowTimelineReplies }}<template #caption>{{ i18n.ts.flagShowTimelineRepliesDescription }} {{ i18n.ts.reflectMayTakeTime }}</template></MkSwitch>
 		</div>
 	</FormSection>
 
@@ -56,7 +57,7 @@
 				<option value="ignore">{{ i18n.ts._nsfw.ignore }}</option>
 				<option value="force">{{ i18n.ts._nsfw.force }}</option>
 			</MkSelect>
-		<!--
+
 			<MkRadios v-model="mediaListWithOneImageAppearance">
 				<template #label>{{ i18n.ts.mediaListWithOneImageAppearance }}</template>
 				<option value="expand">{{ i18n.ts.default }}</option>
@@ -64,7 +65,6 @@
 				<option value="1_1">{{ i18n.t('limitTo', { x: '1:1' }) }}</option>
 				<option value="2_3">{{ i18n.t('limitTo', { x: '2:3' }) }}</option>
 			</MkRadios>
-		-->
 		</div>
 	</FormSection>
 
@@ -145,12 +145,20 @@
 	</FormSection>
 
 	<FormSection>
-		<MkSwitch v-model="aiChanMode">{{ i18n.ts.aiChanMode }}</MkSwitch>
+		<template #label>{{ i18n.ts.other }}</template>
+
+		<div class="_gaps">
+			<MkFolder>
+				<template #label>{{ i18n.ts.additionalEmojiDictionary }}</template>
+				<div v-for="lang in emojiIndexLangs" class="_buttons">
+					<MkButton @click="downloadEmojiIndex(lang)"><i class="ti ti-download"></i> {{ lang }}{{ defaultStore.reactiveState.additionalUnicodeEmojiIndexes.value[lang] ? ` (${ i18n.ts.installed })` : '' }}</MkButton>
+					<MkButton v-if="defaultStore.reactiveState.additionalUnicodeEmojiIndexes.value[lang]" danger @click="removeEmojiIndex(lang)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton>
+				</div>
+			</MkFolder>
+			<FormLink to="/settings/deck">{{ i18n.ts.deck }}</FormLink>
+			<FormLink to="/settings/custom-css"><template #icon><i class="ti ti-code"></i></template>{{ i18n.ts.customCss }}</FormLink>
+		</div>
 	</FormSection>
-
-	<FormLink to="/settings/deck">{{ i18n.ts.deck }}</FormLink>
-
-	<FormLink to="/settings/custom-css"><template #icon><i class="ti ti-code"></i></template>{{ i18n.ts.customCss }}</FormLink>
 </div>
 </template>
 
@@ -160,6 +168,8 @@ import MkSwitch from '@/components/MkSwitch.vue';
 import MkSelect from '@/components/MkSelect.vue';
 import MkRadios from '@/components/MkRadios.vue';
 import MkRange from '@/components/MkRange.vue';
+import MkFolder from '@/components/MkFolder.vue';
+import MkButton from '@/components/MkButton.vue';
 import FormSection from '@/components/form/section.vue';
 import FormLink from '@/components/form/link.vue';
 import MkLink from '@/components/MkLink.vue';
@@ -212,10 +222,10 @@ const instanceTicker = computed(defaultStore.makeGetterSetter('instanceTicker'))
 const enableInfiniteScroll = computed(defaultStore.makeGetterSetter('enableInfiniteScroll'));
 const useReactionPickerForContextMenu = computed(defaultStore.makeGetterSetter('useReactionPickerForContextMenu'));
 const squareAvatars = computed(defaultStore.makeGetterSetter('squareAvatars'));
-const aiChanMode = computed(defaultStore.makeGetterSetter('aiChanMode'));
 const mediaListWithOneImageAppearance = computed(defaultStore.makeGetterSetter('mediaListWithOneImageAppearance'));
 const notificationPosition = computed(defaultStore.makeGetterSetter('notificationPosition'));
 const notificationStackAxis = computed(defaultStore.makeGetterSetter('notificationStackAxis'));
+const showTimelineReplies = computed(defaultStore.makeGetterSetter('showTimelineReplies'));
 
 watch(lang, () => {
 	miLocalStorage.setItem('lang', lang.value as string);
@@ -244,7 +254,6 @@ watch([
 	useSystemFont,
 	enableInfiniteScroll,
 	squareAvatars,
-	aiChanMode,
 	showNoteActionsOnlyHover,
 	showGapBetweenNotesInTimeline,
 	instanceTicker,
@@ -253,6 +262,34 @@ watch([
 	await reloadAsk();
 });
 
+const emojiIndexLangs = ['en-US'];
+
+function downloadEmojiIndex(lang: string) {
+	async function main() {
+		const currentIndexes = defaultStore.state.additionalUnicodeEmojiIndexes;
+		function download() {
+			switch (lang) {
+				case 'en-US': return import('../../unicode-emoji-indexes/en-US.json').then(x => x.default);
+				default: throw new Error('unrecognized lang: ' + lang);
+			}
+		}
+		currentIndexes[lang] = await download();
+		await defaultStore.set('additionalUnicodeEmojiIndexes', currentIndexes);
+	}
+
+	os.promiseDialog(main());
+}
+
+function removeEmojiIndex(lang: string) {
+	async function main() {
+		const currentIndexes = defaultStore.state.additionalUnicodeEmojiIndexes;
+		delete currentIndexes[lang];
+		await defaultStore.set('additionalUnicodeEmojiIndexes', currentIndexes);
+	}
+
+	os.promiseDialog(main());
+}
+
 const headerActions = $computed(() => []);
 
 const headerTabs = $computed(() => []);
diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue
index 34a962ef4..b4f056d8a 100644
--- a/packages/frontend/src/pages/settings/index.vue
+++ b/packages/frontend/src/pages/settings/index.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="900" :margin-min="20" :margin-max="32">
+	<MkSpacer :contentMax="900" :marginMin="20" :marginMax="32">
 		<div ref="el" class="vvcocwet" :class="{ wide: !narrow }">
 			<div class="body">
 				<div v-if="!narrow || currentPage?.route.name == null" class="nav">
diff --git a/packages/frontend/src/pages/settings/migration.vue b/packages/frontend/src/pages/settings/migration.vue
index 541992875..102bc6852 100644
--- a/packages/frontend/src/pages/settings/migration.vue
+++ b/packages/frontend/src/pages/settings/migration.vue
@@ -3,7 +3,7 @@
 	<FormInfo warn>
 		{{ i18n.ts.thisIsExperimentalFeature }}
 	</FormInfo>
-	<MkFolder :default-open="true">
+	<MkFolder :defaultOpen="true">
 		<template #icon><i class="ti ti-plane-arrival"></i></template>
 		<template #label>{{ i18n.ts._accountMigration.moveFrom }}</template>
 		<template #caption>{{ i18n.ts._accountMigration.moveFromSub }}</template>
@@ -25,7 +25,7 @@
 		</div>
 	</MkFolder>
 
-	<MkFolder :default-open="!!$i?.movedTo">
+	<MkFolder :defaultOpen="!!$i?.movedTo">
 		<template #icon><i class="ti ti-plane-departure"></i></template>
 		<template #label>{{ i18n.ts._accountMigration.moveTo }}</template>
 
@@ -48,7 +48,7 @@
 				<FormInfo>{{ i18n.ts._accountMigration.postMigrationNote }}</FormInfo>
 				<FormInfo warn>{{ i18n.ts._accountMigration.movedAndCannotBeUndone }}</FormInfo>
 				<div>{{ i18n.ts._accountMigration.movedTo }}</div>
-				<MkUserInfo v-if="movedTo" :user="movedTo" class="_panel _shadow" />
+				<MkUserInfo v-if="movedTo" :user="movedTo" class="_panel _shadow"/>
 			</template>
 		</div>
 	</MkFolder>
@@ -57,6 +57,8 @@
 
 <script lang="ts" setup>
 import { ref } from 'vue';
+import { toString } from 'misskey-js/built/acct';
+import { UserDetailed } from 'misskey-js/built/entities';
 import FormInfo from '@/components/MkInfo.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkButton from '@/components/MkButton.vue';
@@ -66,8 +68,6 @@ import * as os from '@/os';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
 import { $i } from '@/account';
-import { toString } from 'misskey-js/built/acct';
-import { UserDetailed } from 'misskey-js/built/entities';
 import { unisonReload } from '@/scripts/unison-reload';
 
 const moveToAccount = ref('');
diff --git a/packages/frontend/src/pages/settings/navbar.vue b/packages/frontend/src/pages/settings/navbar.vue
index b3b33b802..8780bfbc1 100644
--- a/packages/frontend/src/pages/settings/navbar.vue
+++ b/packages/frontend/src/pages/settings/navbar.vue
@@ -2,10 +2,10 @@
 <div class="_gaps_m">
 	<FormSlot>
 		<template #label>{{ i18n.ts.navbar }}</template>
-		<MkContainer :show-header="false">
+		<MkContainer :showHeader="false">
 			<Sortable 
 				v-model="items"
-				item-key="id"
+				itemKey="id"
 				:animation="150"
 				:handle="'.' + $style.itemHandle"
 				@start="e => e.item.classList.add('active')"
diff --git a/packages/frontend/src/pages/settings/notifications.vue b/packages/frontend/src/pages/settings/notifications.vue
index 2cf2f6d7f..2552db419 100644
--- a/packages/frontend/src/pages/settings/notifications.vue
+++ b/packages/frontend/src/pages/settings/notifications.vue
@@ -12,7 +12,7 @@
 
 		<div class="_gaps_m">
 			<MkPushNotificationAllowButton ref="allowButton"/>
-			<MkSwitch :disabled="!pushRegistrationInServer" :model-value="sendReadMessage" @update:model-value="onChangeSendReadMessage">
+			<MkSwitch :disabled="!pushRegistrationInServer" :modelValue="sendReadMessage" @update:modelValue="onChangeSendReadMessage">
 				<template #label>{{ i18n.ts.sendPushNotificationReadMessage }}</template>
 				<template #caption>
 					<I18n :src="i18n.ts.sendPushNotificationReadMessageCaption">
diff --git a/packages/frontend/src/pages/settings/other.vue b/packages/frontend/src/pages/settings/other.vue
index 776305d72..0b73780a8 100644
--- a/packages/frontend/src/pages/settings/other.vue
+++ b/packages/frontend/src/pages/settings/other.vue
@@ -53,6 +53,17 @@
 					</MkSwitch>
 				</div>
 			</MkFolder>
+
+			<MkFolder>
+				<template #icon><i class="ti ti-code"></i></template>
+				<template #label>{{ i18n.ts.developer }}</template>
+
+				<div class="_gaps_m">
+					<MkSwitch v-model="devMode">
+						<template #label>{{ i18n.ts.devMode }}</template>
+					</MkSwitch>
+				</div>
+			</MkFolder>
 		</div>
 	</FormSection>
 
@@ -80,6 +91,7 @@ import FormSection from '@/components/form/section.vue';
 
 const reportError = computed(defaultStore.makeGetterSetter('reportError'));
 const enableCondensedLineForAcct = computed(defaultStore.makeGetterSetter('enableCondensedLineForAcct'));
+const devMode = computed(defaultStore.makeGetterSetter('devMode'));
 
 function onChangeInjectFeaturedNote(v) {
 	os.api('i/update', {
diff --git a/packages/frontend/src/pages/settings/plugin.vue b/packages/frontend/src/pages/settings/plugin.vue
index 8b57dceef..75fae014f 100644
--- a/packages/frontend/src/pages/settings/plugin.vue
+++ b/packages/frontend/src/pages/settings/plugin.vue
@@ -8,7 +8,7 @@
 			<div v-for="plugin in plugins" :key="plugin.id" class="_panel _gaps_s" style="padding: 20px;">
 				<span style="display: flex;"><b>{{ plugin.name }}</b><span style="margin-left: auto;">v{{ plugin.version }}</span></span>
 
-				<MkSwitch :model-value="plugin.active" @update:model-value="changeActive(plugin, $event)">{{ i18n.ts.makeActive }}</MkSwitch>
+				<MkSwitch :modelValue="plugin.active" @update:modelValue="changeActive(plugin, $event)">{{ i18n.ts.makeActive }}</MkSwitch>
 
 				<MkKeyValue>
 					<template #key>{{ i18n.ts.author }}</template>
@@ -94,7 +94,3 @@ definePageMetadata({
 	icon: 'ti ti-plug',
 });
 </script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/packages/frontend/src/pages/settings/preferences-backups.vue b/packages/frontend/src/pages/settings/preferences-backups.vue
index 6613ce4c1..e34901cd1 100644
--- a/packages/frontend/src/pages/settings/preferences-backups.vue
+++ b/packages/frontend/src/pages/settings/preferences-backups.vue
@@ -32,7 +32,7 @@
 </template>
 
 <script lang="ts" setup>
-import { computed, onMounted, onUnmounted, useCssModule } from 'vue';
+import { computed, onMounted, onUnmounted } from 'vue';
 import { v4 as uuid } from 'uuid';
 import FormSection from '@/components/form/section.vue';
 import MkButton from '@/components/MkButton.vue';
@@ -40,7 +40,7 @@ import MkInfo from '@/components/MkInfo.vue';
 import * as os from '@/os';
 import { ColdDeviceStorage, defaultStore } from '@/store';
 import { unisonReload } from '@/scripts/unison-reload';
-import { stream } from '@/stream';
+import { useStream } from '@/stream';
 import { $i } from '@/account';
 import { i18n } from '@/i18n';
 import { version, host } from '@/config';
@@ -48,8 +48,6 @@ import { definePageMetadata } from '@/scripts/page-metadata';
 import { miLocalStorage } from '@/local-storage';
 const { t, ts } = i18n;
 
-useCssModule();
-
 const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
 	'menu',
 	'visibility',
@@ -125,7 +123,7 @@ type Profile = {
 	};
 };
 
-const connection = $i && stream.useChannel('main');
+const connection = $i && useStream().useChannel('main');
 
 let profiles = $ref<Record<string, Profile> | null>(null);
 
diff --git a/packages/frontend/src/pages/settings/privacy.vue b/packages/frontend/src/pages/settings/privacy.vue
index a1af0ba80..7fd4d6d34 100644
--- a/packages/frontend/src/pages/settings/privacy.vue
+++ b/packages/frontend/src/pages/settings/privacy.vue
@@ -1,14 +1,14 @@
 <template>
 <div class="_gaps_m">
-	<MkSwitch v-model="isLocked" @update:model-value="save()">{{ i18n.ts.makeFollowManuallyApprove }}<template #caption>{{ i18n.ts.lockedAccountInfo }}</template></MkSwitch>
-	<MkSwitch v-if="isLocked" v-model="autoAcceptFollowed" @update:model-value="save()">{{ i18n.ts.autoAcceptFollowed }}</MkSwitch>
+	<MkSwitch v-model="isLocked" @update:modelValue="save()">{{ i18n.ts.makeFollowManuallyApprove }}<template #caption>{{ i18n.ts.lockedAccountInfo }}</template></MkSwitch>
+	<MkSwitch v-if="isLocked" v-model="autoAcceptFollowed" @update:modelValue="save()">{{ i18n.ts.autoAcceptFollowed }}</MkSwitch>
 
-	<MkSwitch v-model="publicReactions" @update:model-value="save()">
+	<MkSwitch v-model="publicReactions" @update:modelValue="save()">
 		{{ i18n.ts.makeReactionsPublic }}
 		<template #caption>{{ i18n.ts.makeReactionsPublicDescription }}</template>
 	</MkSwitch>
 		
-	<MkSelect v-model="ffVisibility" @update:model-value="save()">
+	<MkSelect v-model="ffVisibility" @update:modelValue="save()">
 		<template #label>{{ i18n.ts.ffVisibility }}</template>
 		<option value="public">{{ i18n.ts._ffVisibility.public }}</option>
 		<option value="followers">{{ i18n.ts._ffVisibility.followers }}</option>
@@ -16,26 +16,26 @@
 		<template #caption>{{ i18n.ts.ffVisibilityDescription }}</template>
 	</MkSelect>
 		
-	<MkSwitch v-model="hideOnlineStatus" @update:model-value="save()">
+	<MkSwitch v-model="hideOnlineStatus" @update:modelValue="save()">
 		{{ i18n.ts.hideOnlineStatus }}
 		<template #caption>{{ i18n.ts.hideOnlineStatusDescription }}</template>
 	</MkSwitch>
-	<MkSwitch v-model="noCrawle" @update:model-value="save()">
+	<MkSwitch v-model="noCrawle" @update:modelValue="save()">
 		{{ i18n.ts.noCrawle }}
 		<template #caption>{{ i18n.ts.noCrawleDescription }}</template>
 	</MkSwitch>
-	<MkSwitch v-model="preventAiLearning" @update:model-value="save()">
+	<MkSwitch v-model="preventAiLearning" @update:modelValue="save()">
 		{{ i18n.ts.preventAiLearning }}<span class="_beta">{{ i18n.ts.beta }}</span>
 		<template #caption>{{ i18n.ts.preventAiLearningDescription }}</template>
 	</MkSwitch>
-	<MkSwitch v-model="isExplorable" @update:model-value="save()">
+	<MkSwitch v-model="isExplorable" @update:modelValue="save()">
 		{{ i18n.ts.makeExplorable }}
 		<template #caption>{{ i18n.ts.makeExplorableDescription }}</template>
 	</MkSwitch>
 
 	<FormSection>
 		<div class="_gaps_m">
-			<MkSwitch v-model="rememberNoteVisibility" @update:model-value="save()">{{ i18n.ts.rememberNoteVisibility }}</MkSwitch>
+			<MkSwitch v-model="rememberNoteVisibility" @update:modelValue="save()">{{ i18n.ts.rememberNoteVisibility }}</MkSwitch>
 			<MkFolder v-if="!rememberNoteVisibility">
 				<template #label>{{ i18n.ts.defaultNoteVisibility }}</template>
 				<template v-if="defaultNoteVisibility === 'public'" #suffix>{{ i18n.ts._visibility.public }}</template>
@@ -56,7 +56,7 @@
 		</div>
 	</FormSection>
 
-	<MkSwitch v-model="keepCw" @update:model-value="save()">{{ i18n.ts.keepCw }}</MkSwitch>
+	<MkSwitch v-model="keepCw" @update:modelValue="save()">{{ i18n.ts.keepCw }}</MkSwitch>
 </div>
 </template>
 
diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue
index 6ffd68261..58217d047 100644
--- a/packages/frontend/src/pages/settings/profile.vue
+++ b/packages/frontend/src/pages/settings/profile.vue
@@ -1,28 +1,28 @@
 <template>
 <div class="_gaps_m">
-	<div class="llvierxe" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }">
-		<div class="avatar">
-			<MkAvatar class="avatar" :user="$i" @click="changeAvatar"/>
-			<MkButton primary rounded class="avatarEdit" @click="changeAvatar">{{ i18n.ts._profile.changeAvatar }}</MkButton>
+	<div :class="$style.avatarAndBanner" :style="{ backgroundImage: $i.bannerUrl ? `url(${ $i.bannerUrl })` : null }">
+		<div :class="$style.avatarContainer">
+			<MkAvatar :class="$style.avatar" :user="$i" @click="changeAvatar"/>
+			<MkButton primary rounded @click="changeAvatar">{{ i18n.ts._profile.changeAvatar }}</MkButton>
 		</div>
-		<MkButton primary rounded class="bannerEdit" @click="changeBanner">{{ i18n.ts._profile.changeBanner }}</MkButton>
+		<MkButton primary rounded :class="$style.bannerEdit" @click="changeBanner">{{ i18n.ts._profile.changeBanner }}</MkButton>
 	</div>
 
-	<MkInput v-model="profile.name" :max="30" manual-save>
+	<MkInput v-model="profile.name" :max="30" manualSave>
 		<template #label>{{ i18n.ts._profile.name }}</template>
 	</MkInput>
 
-	<MkTextarea v-model="profile.description" :max="500" tall manual-save>
+	<MkTextarea v-model="profile.description" :max="500" tall manualSave>
 		<template #label>{{ i18n.ts._profile.description }}</template>
 		<template #caption>{{ i18n.ts._profile.youCanIncludeHashtags }}</template>
 	</MkTextarea>
 
-	<MkInput v-model="profile.location" manual-save>
+	<MkInput v-model="profile.location" manualSave>
 		<template #label>{{ i18n.ts.location }}</template>
 		<template #prefix><i class="ti ti-map-pin"></i></template>
 	</MkInput>
 
-	<MkInput v-model="profile.birthday" type="date" manual-save>
+	<MkInput v-model="profile.birthday" type="date" manualSave>
 		<template #label>{{ i18n.ts.birthday }}</template>
 		<template #prefix><i class="ti ti-cake"></i></template>
 	</MkInput>
@@ -48,7 +48,7 @@
 				<Sortable
 					v-model="fields"
 					class="_gaps_s"
-					item-key="id"
+					itemKey="id"
 					:animation="150"
 					:handle="'.' + $style.dragItemHandle"
 					@start="e => e.item.classList.add('active')"
@@ -59,7 +59,7 @@
 							<button v-if="!fieldEditMode" class="_button" :class="$style.dragItemHandle" tabindex="-1"><i class="ti ti-menu"></i></button>
 							<button v-if="fieldEditMode" :disabled="fields.length <= 1" class="_button" :class="$style.dragItemRemove" @click="deleteField(index)"><i class="ti ti-x"></i></button>
 							<div :class="$style.dragItemForm">
-								<FormSplit :min-width="200">
+								<FormSplit :minWidth="200">
 									<MkInput v-model="element.name" small>
 										<template #label>{{ i18n.ts._profile.metadataLabel }}</template>
 									</MkInput>
@@ -88,11 +88,11 @@
 	<MkSelect v-model="reactionAcceptance">
 		<template #label>{{ i18n.ts.reactionAcceptance }}</template>
 		<option :value="null">{{ i18n.ts.all }}</option>
-		<option value="likeOnly">{{ i18n.ts.likeOnly }}</option>
 		<option value="likeOnlyForRemote">{{ i18n.ts.likeOnlyForRemote }}</option>
+		<option value="nonSensitiveOnly">{{ i18n.ts.nonSensitiveOnly }}</option>
+		<option value="nonSensitiveOnlyForLocalLikeOnlyForRemote">{{ i18n.ts.nonSensitiveOnlyForLocalLikeOnlyForRemote }}</option>
+		<option value="likeOnly">{{ i18n.ts.likeOnly }}</option>
 	</MkSelect>
-
-	<MkSwitch v-model="profile.showTimelineReplies">{{ i18n.ts.flagShowTimelineReplies }}<template #caption>{{ i18n.ts.flagShowTimelineRepliesDescription }} {{ i18n.ts.reflectMayTakeTime }}</template></MkSwitch>
 </div>
 </template>
 
@@ -248,36 +248,35 @@ definePageMetadata({
 });
 </script>
 
-<style lang="scss" scoped>
-.llvierxe {
+<style lang="scss" module>
+.avatarAndBanner {
 	position: relative;
 	background-size: cover;
 	background-position: center;
 	border: solid 1px var(--divider);
 	border-radius: 10px;
 	overflow: clip;
-
-	> .avatar {
-		display: inline-block;
-		text-align: center;
-		padding: 16px;
-
-		> .avatar {
-			display: inline-block;
-			width: 72px;
-			height: 72px;
-			margin: 0 auto 16px auto;
-		}
-	}
-
-	> .bannerEdit {
-		position: absolute;
-		top: 16px;
-		right: 16px;
-	}
 }
-</style>
-<style lang="scss" module>
+
+.avatarContainer {
+	display: inline-block;
+	text-align: center;
+	padding: 16px;
+}
+
+.avatar {
+	display: inline-block;
+	width: 72px;
+	height: 72px;
+	margin: 0 auto 16px auto;
+}
+
+.bannerEdit {
+	position: absolute;
+	top: 16px;
+	right: 16px;
+}
+
 .metadataRoot {
 	container-type: inline-size;
 }
diff --git a/packages/frontend/src/pages/settings/reaction.vue b/packages/frontend/src/pages/settings/reaction.vue
index ed913731d..cb483e34b 100644
--- a/packages/frontend/src/pages/settings/reaction.vue
+++ b/packages/frontend/src/pages/settings/reaction.vue
@@ -3,15 +3,15 @@
 	<FromSlot>
 		<template #label>{{ i18n.ts.reactionSettingDescription }}</template>
 		<div v-panel style="border-radius: 6px;">
-			<Sortable v-model="reactions" class="zoaiodol" :item-key="item => item" :animation="150" :delay="100" :delay-on-touch-only="true">
+			<Sortable v-model="reactions" :class="$style.reactions" :itemKey="item => item" :animation="150" :delay="100" :delayOnTouchOnly="true">
 				<template #item="{element}">
-					<button class="_button item" @click="remove(element, $event)">
+					<button class="_button" :class="$style.reactionsItem" @click="remove(element, $event)">
 						<MkCustomEmoji v-if="element[0] === ':'" :name="element" :normal="true"/>
 						<MkEmoji v-else :emoji="element" :normal="true"/>
 					</button>
 				</template>
 				<template #footer>
-					<button class="_button add" @click="chooseEmoji"><i class="ti ti-plus"></i></button>
+					<button class="_button" :class="$style.reactionsAdd" @click="chooseEmoji"><i class="ti ti-plus"></i></button>
 				</template>
 			</Sortable>
 		</div>
@@ -135,20 +135,20 @@ definePageMetadata({
 });
 </script>
 
-<style lang="scss" scoped>
-.zoaiodol {
+<style lang="scss" module>
+.reactions {
 	padding: 12px;
 	font-size: 1.1em;
+}
 
-	> .item {
-		display: inline-block;
-		padding: 8px;
-		cursor: move;
-	}
+.reactionsItem {
+	display: inline-block;
+	padding: 8px;
+	cursor: move;
+}
 
-	> .add {
-		display: inline-block;
-		padding: 8px;
-	}
+.reactionsAdd {
+	display: inline-block;
+	padding: 8px;
 }
 </style>
diff --git a/packages/frontend/src/pages/settings/roles.vue b/packages/frontend/src/pages/settings/roles.vue
index ba510dced..05753c9b6 100644
--- a/packages/frontend/src/pages/settings/roles.vue
+++ b/packages/frontend/src/pages/settings/roles.vue
@@ -3,7 +3,7 @@
 	<FormSection first>
 		<template #label>{{ i18n.ts.rolesAssignedToMe }}</template>
 		<div class="_gaps_s">
-			<MkRolePreview v-for="role in $i.roles" :key="role.id" :role="role" :for-moderation="false"/>
+			<MkRolePreview v-for="role in $i.roles" :key="role.id" :role="role" :forModeration="false"/>
 		</div>
 	</FormSection>
 	<FormSection>
diff --git a/packages/frontend/src/pages/settings/security.vue b/packages/frontend/src/pages/settings/security.vue
index 0cc2df09c..2da84763a 100644
--- a/packages/frontend/src/pages/settings/security.vue
+++ b/packages/frontend/src/pages/settings/security.vue
@@ -9,7 +9,7 @@
 
 	<FormSection>
 		<template #label>{{ i18n.ts.signinHistory }}</template>
-		<MkPagination :pagination="pagination" disable-auto-load>
+		<MkPagination :pagination="pagination" disableAutoLoad>
 			<template #default="{items}">
 				<div>
 					<div v-for="item in items" :key="item.id" v-panel class="timnmucd">
diff --git a/packages/frontend/src/pages/settings/sounds.sound.vue b/packages/frontend/src/pages/settings/sounds.sound.vue
index aa9f52800..c1a333548 100644
--- a/packages/frontend/src/pages/settings/sounds.sound.vue
+++ b/packages/frontend/src/pages/settings/sounds.sound.vue
@@ -4,7 +4,7 @@
 		<template #label>{{ i18n.ts.sound }}</template>
 		<option v-for="x in soundsTypes" :key="x" :value="x">{{ x == null ? i18n.ts.none : x }}</option>
 	</MkSelect>
-	<MkRange v-model="volume" :min="0" :max="1" :step="0.05" :text-converter="(v) => `${Math.floor(v * 100)}%`">
+	<MkRange v-model="volume" :min="0" :max="1" :step="0.05" :textConverter="(v) => `${Math.floor(v * 100)}%`">
 		<template #label>{{ i18n.ts.volume }}</template>
 	</MkRange>
 
diff --git a/packages/frontend/src/pages/settings/sounds.vue b/packages/frontend/src/pages/settings/sounds.vue
index 724fe4347..c2bf3f8cd 100644
--- a/packages/frontend/src/pages/settings/sounds.vue
+++ b/packages/frontend/src/pages/settings/sounds.vue
@@ -1,6 +1,6 @@
 <template>
 <div class="_gaps_m">
-	<MkRange v-model="masterVolume" :min="0" :max="1" :step="0.05" :text-converter="(v) => `${Math.floor(v * 100)}%`">
+	<MkRange v-model="masterVolume" :min="0" :max="1" :step="0.05" :textConverter="(v) => `${Math.floor(v * 100)}%`">
 		<template #label>{{ i18n.ts.masterVolume }}</template>
 	</MkRange>
 
diff --git a/packages/frontend/src/pages/settings/statusbar.statusbar.vue b/packages/frontend/src/pages/settings/statusbar.statusbar.vue
index 81ff873e9..c73ff7c07 100644
--- a/packages/frontend/src/pages/settings/statusbar.statusbar.vue
+++ b/packages/frontend/src/pages/settings/statusbar.statusbar.vue
@@ -7,7 +7,7 @@
 		<option value="userList">User list timeline</option>
 	</MkSelect>
 
-	<MkInput v-model="statusbar.name" manual-save>
+	<MkInput v-model="statusbar.name" manualSave>
 		<template #label>{{ i18n.ts.label }}</template>
 	</MkInput>
 
@@ -25,13 +25,13 @@
 	</MkRadios>
 
 	<template v-if="statusbar.type === 'rss'">
-		<MkInput v-model="statusbar.props.url" manual-save type="url">
+		<MkInput v-model="statusbar.props.url" manualSave type="url">
 			<template #label>URL</template>
 		</MkInput>
 		<MkSwitch v-model="statusbar.props.shuffle">
 			<template #label>{{ i18n.ts.shuffle }}</template>
 		</MkSwitch>
-		<MkInput v-model="statusbar.props.refreshIntervalSec" manual-save type="number">
+		<MkInput v-model="statusbar.props.refreshIntervalSec" manualSave type="number">
 			<template #label>{{ i18n.ts.refreshInterval }}</template>
 		</MkInput>
 		<MkRange v-model="statusbar.props.marqueeDuration" :min="5" :max="150" :step="1">
@@ -43,7 +43,7 @@
 		</MkSwitch>
 	</template>
 	<template v-else-if="statusbar.type === 'federation'">
-		<MkInput v-model="statusbar.props.refreshIntervalSec" manual-save type="number">
+		<MkInput v-model="statusbar.props.refreshIntervalSec" manualSave type="number">
 			<template #label>{{ i18n.ts.refreshInterval }}</template>
 		</MkInput>
 		<MkRange v-model="statusbar.props.marqueeDuration" :min="5" :max="150" :step="1">
@@ -62,7 +62,7 @@
 			<template #label>{{ i18n.ts.userList }}</template>
 			<option v-for="list in userLists" :value="list.id">{{ list.name }}</option>
 		</MkSelect>
-		<MkInput v-model="statusbar.props.refreshIntervalSec" manual-save type="number">
+		<MkInput v-model="statusbar.props.refreshIntervalSec" manualSave type="number">
 			<template #label>{{ i18n.ts.refreshInterval }}</template>
 		</MkInput>
 		<MkRange v-model="statusbar.props.marqueeDuration" :min="5" :max="150" :step="1">
diff --git a/packages/frontend/src/pages/settings/statusbar.vue b/packages/frontend/src/pages/settings/statusbar.vue
index f5a090a63..bfb69936e 100644
--- a/packages/frontend/src/pages/settings/statusbar.vue
+++ b/packages/frontend/src/pages/settings/statusbar.vue
@@ -3,7 +3,7 @@
 	<MkFolder v-for="x in statusbars" :key="x.id">
 		<template #label>{{ x.type ?? i18n.ts.notSet }}</template>
 		<template #suffix>{{ x.name }}</template>
-		<XStatusbar :_id="x.id" :user-lists="userLists"/>
+		<XStatusbar :_id="x.id" :userLists="userLists"/>
 	</MkFolder>
 	<MkButton primary @click="add">{{ i18n.ts.add }}</MkButton>
 </div>
diff --git a/packages/frontend/src/pages/settings/theme.manage.vue b/packages/frontend/src/pages/settings/theme.manage.vue
index d1821a00d..025543511 100644
--- a/packages/frontend/src/pages/settings/theme.manage.vue
+++ b/packages/frontend/src/pages/settings/theme.manage.vue
@@ -10,13 +10,13 @@
 		</optgroup>
 	</MkSelect>
 	<template v-if="selectedTheme">
-		<MkInput readonly :model-value="selectedTheme.author">
+		<MkInput readonly :modelValue="selectedTheme.author">
 			<template #label>{{ i18n.ts.author }}</template>
 		</MkInput>
-		<MkTextarea v-if="selectedTheme.desc" readonly :model-value="selectedTheme.desc">
+		<MkTextarea v-if="selectedTheme.desc" readonly :modelValue="selectedTheme.desc">
 			<template #label>{{ i18n.ts._theme.description }}</template>
 		</MkTextarea>
-		<MkTextarea readonly tall :model-value="selectedThemeCode">
+		<MkTextarea readonly tall :modelValue="selectedThemeCode">
 			<template #label>{{ i18n.ts._theme.code }}</template>
 			<template #caption><button class="_textButton" @click="copyThemeCode()">{{ i18n.ts.copy }}</button></template>
 		</MkTextarea>
diff --git a/packages/frontend/src/pages/share.vue b/packages/frontend/src/pages/share.vue
index 78e071016..e0ac89923 100644
--- a/packages/frontend/src/pages/share.vue
+++ b/packages/frontend/src/pages/share.vue
@@ -1,22 +1,25 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="800">
+	<MkSpacer :contentMax="800">
 		<MkPostForm
 			v-if="state === 'writing'"
 			fixed
 			:instant="true"
-			:initial-text="initialText"
-			:initial-visibility="visibility"
-			:initial-files="files"
-			:initial-local-only="localOnly"
+			:initialText="initialText"
+			:initialVisibility="visibility"
+			:initialFiles="files"
+			:initialLocalOnly="localOnly"
 			:reply="reply"
 			:renote="renote"
-			:initial-visible-users="visibleUsers"
+			:initialVisibleUsers="visibleUsers"
 			class="_panel"
 			@posted="state = 'posted'"
 		/>
-		<MkButton v-else-if="state === 'posted'" primary class="close" @click="close()">{{ i18n.ts.close }}</MkButton>
+		<div v-else-if="state === 'posted'" class="_buttonsCenter">
+			<MkButton primary @click="close">{{ i18n.ts.close }}</MkButton>
+			<MkButton @click="goToMisskey">{{ i18n.ts.goToMisskey }}</MkButton>
+		</div>
 	</MkSpacer>
 </MkStickyContainer>
 </template>
@@ -148,10 +151,14 @@ function close(): void {
 
 	// 閉じなければ100ms後タイムラインに
 	window.setTimeout(() => {
-		mainRouter.push('/');
+		location.href = '/';
 	}, 100);
 }
 
+function goToMisskey(): void {
+	location.href = '/';
+}
+
 const headerActions = $computed(() => []);
 
 const headerTabs = $computed(() => []);
@@ -161,9 +168,3 @@ definePageMetadata({
 	icon: 'ti ti-share',
 });
 </script>
-
-<style lang="scss" scoped>
-.close {
-	margin: 16px auto;
-}
-</style>
diff --git a/packages/frontend/src/pages/signup-complete.vue b/packages/frontend/src/pages/signup-complete.vue
index 545953231..61d7eb24f 100644
--- a/packages/frontend/src/pages/signup-complete.vue
+++ b/packages/frontend/src/pages/signup-complete.vue
@@ -1,41 +1,80 @@
 <template>
 <div>
-	{{ i18n.ts.processing }}
+	<MkAnimBg style="position: fixed; top: 0;"/>
+	<div :class="$style.formContainer">
+		<form :class="$style.form" class="_panel" @submit.prevent="submit()">
+			<div :class="$style.banner">
+				<i class="ti ti-user-check"></i>
+			</div>
+			<div class="_gaps_m" style="padding: 32px;">
+				<div>{{ i18n.t('clickToFinishEmailVerification', { ok: i18n.ts.gotIt }) }}</div>
+				<div>
+					<MkButton gradate large rounded type="submit" :disabled="submitting" data-cy-admin-ok style="margin: 0 auto;">
+						{{ submitting ? i18n.ts.processing : i18n.ts.gotIt }}<MkEllipsis v-if="submitting"/>
+					</MkButton>
+				</div>
+			</div>
+		</form>
+	</div>
 </div>
 </template>
 
 <script lang="ts" setup>
-import { onMounted } from 'vue';
-import * as os from '@/os';
+import { } from 'vue';
+import MkButton from '@/components/MkButton.vue';
+import MkAnimBg from '@/components/MkAnimBg.vue';
 import { login } from '@/account';
 import { i18n } from '@/i18n';
-import { definePageMetadata } from '@/scripts/page-metadata';
+import * as os from '@/os';
+
+let submitting = $ref(false);
 
 const props = defineProps<{
 	code: string;
 }>();
 
-onMounted(async () => {
-	await os.alert({
-		type: 'info',
-		text: i18n.t('clickToFinishEmailVerification', { ok: i18n.ts.gotIt }),
-	});
-	const res = await os.apiWithDialog('signup-pending', {
+function submit() {
+	if (submitting) return;
+	submitting = true;
+
+	os.api('signup-pending', {
 		code: props.code,
+	}).then(res => {
+		return login(res.i, '/');
+	}).catch(() => {
+		submitting = false;
+
+		os.alert({
+			type: 'error',
+			text: i18n.ts.somethingHappened,
+		});
 	});
-	login(res.i, '/');
-});
-
-const headerActions = $computed(() => []);
-
-const headerTabs = $computed(() => []);
-
-definePageMetadata({
-	title: i18n.ts.signup,
-	icon: 'ti ti-user',
-});
+}
 </script>
 
-<style lang="scss" scoped>
+<style lang="scss" module>
+.formContainer {
+	min-height: 100svh;
+	padding: 32px 32px 64px 32px;
+	box-sizing: border-box;
+display: grid;
+place-content: center;
+}
 
+.form {
+	position: relative;
+	z-index: 10;
+	border-radius: var(--radius);
+	box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
+	overflow: clip;
+	max-width: 500px;
+}
+
+.banner {
+	padding: 16px;
+	text-align: center;
+	font-size: 26px;
+	background-color: var(--accentedBg);
+	color: var(--accent);
+}
 </style>
diff --git a/packages/frontend/src/pages/tag.vue b/packages/frontend/src/pages/tag.vue
index 511052c42..104e73886 100644
--- a/packages/frontend/src/pages/tag.vue
+++ b/packages/frontend/src/pages/tag.vue
@@ -1,16 +1,28 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="800">
-		<MkNotes class="" :pagination="pagination"/>
+	<MkSpacer :contentMax="800">
+		<MkNotes ref="notes" class="" :pagination="pagination"/>
 	</MkSpacer>
+	<template v-if="$i" #footer>
+		<div :class="$style.footer">
+			<MkSpacer :contentMax="800" :marginMin="16" :marginMax="16">
+				<MkButton rounded primary :class="$style.button" @click="post()"><i class="ti ti-pencil"></i>{{ i18n.ts.postToHashtag }}</MkButton>
+			</MkSpacer>
+		</div>
+	</template>
 </MkStickyContainer>
 </template>
 
 <script lang="ts" setup>
-import { computed } from 'vue';
+import { computed, ref } from 'vue';
 import MkNotes from '@/components/MkNotes.vue';
+import MkButton from '@/components/MkButton.vue';
 import { definePageMetadata } from '@/scripts/page-metadata';
+import { i18n } from '@/i18n';
+import { $i } from '@/account';
+import { defaultStore } from '@/store';
+import * as os from '@/os';
 
 const props = defineProps<{
 	tag: string;
@@ -23,6 +35,16 @@ const pagination = {
 		tag: props.tag,
 	})),
 };
+const notes = ref<InstanceType<typeof MkNotes>>();
+
+async function post() {
+	defaultStore.set('postFormHashtags', props.tag);
+	defaultStore.set('postFormWithHashtags', true);
+	await os.post();
+	defaultStore.set('postFormHashtags', '');
+	defaultStore.set('postFormWithHashtags', false);
+	notes.value?.pagingComponent?.reload();
+}
 
 const headerActions = $computed(() => []);
 
@@ -33,3 +55,16 @@ definePageMetadata(computed(() => ({
 	icon: 'ti ti-hash',
 })));
 </script>
+
+<style lang="scss" module>
+.footer {
+	-webkit-backdrop-filter: var(--blur, blur(15px));
+	backdrop-filter: var(--blur, blur(15px));
+	border-top: solid 0.5px var(--divider);
+	display: flex;
+}
+
+.button {
+		margin: 0 auto var(--margin) auto;
+}
+</style>
diff --git a/packages/frontend/src/pages/theme-editor.vue b/packages/frontend/src/pages/theme-editor.vue
index 56fdfdf78..f942b5005 100644
--- a/packages/frontend/src/pages/theme-editor.vue
+++ b/packages/frontend/src/pages/theme-editor.vue
@@ -1,9 +1,9 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="800" :margin-min="16" :margin-max="32">
+	<MkSpacer :contentMax="800" :marginMin="16" :marginMax="32">
 		<div class="cwepdizn _gaps_m">
-			<MkFolder :default-open="true">
+			<MkFolder :defaultOpen="true">
 				<template #label>{{ i18n.ts.backgroundColor }}</template>
 				<div class="cwepdizn-colors">
 					<div class="row">
@@ -19,7 +19,7 @@
 				</div>
 			</MkFolder>
 
-			<MkFolder :default-open="true">
+			<MkFolder :defaultOpen="true">
 				<template #label>{{ i18n.ts.accentColor }}</template>
 				<div class="cwepdizn-colors">
 					<div class="row">
@@ -30,7 +30,7 @@
 				</div>
 			</MkFolder>
 
-			<MkFolder :default-open="true">
+			<MkFolder :defaultOpen="true">
 				<template #label>{{ i18n.ts.textColor }}</template>
 				<div class="cwepdizn-colors">
 					<div class="row">
@@ -41,7 +41,7 @@
 				</div>
 			</MkFolder>
 
-			<MkFolder :default-open="false">
+			<MkFolder :defaultOpen="false">
 				<template #icon><i class="ti ti-code"></i></template>
 				<template #label>{{ i18n.ts.editCode }}</template>
 
@@ -53,7 +53,7 @@
 				</div>
 			</MkFolder>
 
-			<MkFolder :default-open="false">
+			<MkFolder :defaultOpen="false">
 				<template #label>{{ i18n.ts.addDescription }}</template>
 
 				<div class="_gaps_m">
diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue
index 1bf4cdc99..a441c6f72 100644
--- a/packages/frontend/src/pages/timeline.vue
+++ b/packages/frontend/src/pages/timeline.vue
@@ -1,12 +1,12 @@
 <template>
 <MkStickyContainer>
-	<template #header><MkPageHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :display-my-avatar="true"/></template>
-	<MkSpacer :content-max="800">
+	<template #header><MkPageHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :displayMyAvatar="true"/></template>
+	<MkSpacer :contentMax="800">
 		<div ref="rootEl" v-hotkey.global="keymap">
 			<XTutorial v-if="$i && defaultStore.reactiveState.timelineTutorial.value != -1" class="_panel" style="margin-bottom: var(--margin);"/>
 			<MkPostForm v-if="defaultStore.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--margin);"/>
 
-			<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
+			<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
 			<div :class="$style.tl">
 				<MkTimeline
 					ref="tlComponent"
@@ -187,13 +187,13 @@ definePageMetadata(computed(() => ({
 	&:first-child {
 		margin-top: calc(-0.675em - 8px - var(--margin));
 	}
+}
 
-	> button {
-		display: block;
-		margin: var(--margin) auto 0 auto;
-		padding: 8px 16px;
-		border-radius: 32px;
-	}
+.newButton {
+	display: block;
+	margin: var(--margin) auto 0 auto;
+	padding: 8px 16px;
+	border-radius: 32px;
 }
 
 .postForm {
diff --git a/packages/frontend/src/pages/user-info.vue b/packages/frontend/src/pages/user-info.vue
index 94718d153..56e8737e1 100644
--- a/packages/frontend/src/pages/user-info.vue
+++ b/packages/frontend/src/pages/user-info.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="600" :margin-min="16" :margin-max="32">
+	<MkSpacer :contentMax="600" :marginMin="16" :marginMax="32">
 		<FormSuspense :p="init">
 			<div v-if="tab === 'overview'" class="_gaps_m">
 				<div class="aeakzknw">
@@ -88,7 +88,7 @@
 			</div>
 
 			<div v-else-if="tab === 'moderation'" class="_gaps_m">
-				<MkSwitch v-model="suspended" @update:model-value="toggleSuspend">{{ i18n.ts.suspend }}</MkSwitch>
+				<MkSwitch v-model="suspended" @update:modelValue="toggleSuspend">{{ i18n.ts.suspend }}</MkSwitch>
 
 				<div>
 					<MkButton v-if="user.host == null && iAmModerator" inline style="margin-right: 8px;" @click="resetPassword"><i class="ti ti-key"></i> {{ i18n.ts.resetPassword }}</MkButton>
@@ -112,7 +112,7 @@
 						<MkButton v-if="user.host == null && iAmModerator" primary rounded @click="assignRole"><i class="ti ti-plus"></i> {{ i18n.ts.assign }}</MkButton>
 
 						<div v-for="role in info.roles" :key="role.id" :class="$style.roleItem">
-							<MkRolePreview :class="$style.role" :role="role" :for-moderation="true"/>
+							<MkRolePreview :class="$style.role" :role="role" :forModeration="true"/>
 							<button v-if="role.target === 'manual'" class="_button" :class="$style.roleUnassign" @click="unassignRole(role, $event)"><i class="ti ti-x"></i></button>
 							<button v-else class="_button" :class="$style.roleUnassign" disabled><i class="ti ti-ban"></i></button>
 						</div>
@@ -135,10 +135,10 @@
 				<MkFolder>
 					<template #icon><i class="ti ti-cloud"></i></template>
 					<template #label>{{ i18n.ts.files }}</template>
-					<MkFileListForAdmin :pagination="filesPagination" view-mode="grid"/>
+					<MkFileListForAdmin :pagination="filesPagination" viewMode="grid"/>
 				</MkFolder>
 
-				<MkTextarea v-model="moderationNote" manual-save>
+				<MkTextarea v-model="moderationNote" manualSave>
 					<template #label>Moderation note</template>
 				</MkTextarea>
 			</div>
diff --git a/packages/frontend/src/pages/user-list-timeline.vue b/packages/frontend/src/pages/user-list-timeline.vue
index acf7ea9b2..f66670e1f 100644
--- a/packages/frontend/src/pages/user-list-timeline.vue
+++ b/packages/frontend/src/pages/user-list-timeline.vue
@@ -1,19 +1,20 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<div ref="rootEl" class="eqqrhokj">
-		<div v-if="queue > 0" class="new"><button class="_buttonPrimary" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
-		<div class="tl">
-			<MkTimeline
-				ref="tlEl" :key="listId"
-				class="tl"
-				src="list"
-				:list="listId"
-				:sound="true"
-				@queue="queueUpdated"
-			/>
+	<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>
+			<div :class="$style.tl">
+				<MkTimeline
+					ref="tlEl" :key="listId"
+					src="list"
+					:list="listId"
+					:sound="true"
+					@queue="queueUpdated"
+				/>
+			</div>
 		</div>
-	</div>
+	</MkSpacer>
 </MkStickyContainer>
 </template>
 
@@ -82,36 +83,29 @@ definePageMetadata(computed(() => list ? {
 } : null));
 </script>
 
-<style lang="scss" scoped>
-.eqqrhokj {
-	padding: var(--margin);
+<style lang="scss" module>
+.new {
+	position: sticky;
+	top: calc(var(--stickyTop, 0px) + 16px);
+	z-index: 1000;
+	width: 100%;
+	margin: calc(-0.675em - 8px) 0;
 
-	> .new {
-		position: sticky;
-		top: calc(var(--stickyTop, 0px) + 16px);
-		z-index: 1000;
-		width: 100%;
-		margin: calc(-0.675em - 8px - var(--margin)) 0 calc(-0.675em - 8px);
-
-		> button {
-			display: block;
-			margin: var(--margin) auto 0 auto;
-			padding: 8px 16px;
-			border-radius: 32px;
-		}
-	}
-
-	> .tl {
-		background: var(--bg);
-		border-radius: var(--radius);
-		overflow: clip;
+	&:first-child {
+		margin-top: calc(-0.675em - 8px - var(--margin));
 	}
 }
 
-@container (min-width: 800px) {
-	.eqqrhokj {
-		max-width: 800px;
-		margin: 0 auto;
-	}
+.newButton {
+	display: block;
+	margin: var(--margin) auto 0 auto;
+	padding: 8px 16px;
+	border-radius: 32px;
+}
+
+.tl {
+	background: var(--bg);
+	border-radius: var(--radius);
+	overflow: clip;
 }
 </style>
diff --git a/packages/frontend/src/pages/user-tag.vue b/packages/frontend/src/pages/user-tag.vue
index fac7593e9..01ef1126c 100644
--- a/packages/frontend/src/pages/user-tag.vue
+++ b/packages/frontend/src/pages/user-tag.vue
@@ -2,7 +2,7 @@
 <MkStickyContainer>
 	<template #header><MkPageHeader/></template>
 
-	<MkSpacer :content-max="1200">
+	<MkSpacer :contentMax="1200">
 		<div class="_gaps_s">
 			<MkUserList :pagination="tagUsers"/>
 		</div>
diff --git a/packages/frontend/src/pages/user/achievements.vue b/packages/frontend/src/pages/user/achievements.vue
index 1b3a6e24b..7d5993c26 100644
--- a/packages/frontend/src/pages/user/achievements.vue
+++ b/packages/frontend/src/pages/user/achievements.vue
@@ -1,6 +1,6 @@
 <template>
-<MkSpacer :content-max="1200">
-	<MkAchievements :user="user" :with-locked="false" :with-description="$i != null && (props.user.id === $i.id)"/>
+<MkSpacer :contentMax="1200">
+	<MkAchievements :user="user" :withLocked="false" :withDescription="$i != null && (props.user.id === $i.id)"/>
 </MkSpacer>
 </template>
 
diff --git a/packages/frontend/src/pages/user/activity.vue b/packages/frontend/src/pages/user/activity.vue
index cd538ad61..655371ac1 100644
--- a/packages/frontend/src/pages/user/activity.vue
+++ b/packages/frontend/src/pages/user/activity.vue
@@ -1,5 +1,5 @@
 <template>
-<MkSpacer :content-max="700">
+<MkSpacer :contentMax="700">
 	<div class="_gaps">
 		<MkFoldableSection class="item">
 			<template #header><i class="ti ti-activity"></i> Heatmap</template>
@@ -34,7 +34,3 @@ const props = defineProps<{
 }>();
 
 </script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/packages/frontend/src/pages/user/clips.vue b/packages/frontend/src/pages/user/clips.vue
index 95f8cbc29..08b7b9a71 100644
--- a/packages/frontend/src/pages/user/clips.vue
+++ b/packages/frontend/src/pages/user/clips.vue
@@ -1,10 +1,10 @@
 <template>
-<MkSpacer :content-max="700">
-	<div class="pages-user-clips">
-		<MkPagination v-slot="{items}" ref="list" :pagination="pagination" class="list">
-			<MkA v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _margin">
+<MkSpacer :contentMax="700">
+	<div>
+		<MkPagination v-slot="{items}" ref="list" :pagination="pagination">
+			<MkA v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" :class="$style.item" class="_panel _margin">
 				<b>{{ item.name }}</b>
-				<div v-if="item.description" class="description">{{ item.description }}</div>
+				<div v-if="item.description" :class="$style.description">{{ item.description }}</div>
 			</MkA>
 		</MkPagination>
 	</div>
@@ -29,19 +29,15 @@ const pagination = {
 };
 </script>
 
-<style lang="scss" scoped>
-.pages-user-clips {
-	> .list {
-		> .item {
-			display: block;
-			padding: 16px;
+<style lang="scss" module>
+.item {
+	display: block;
+	padding: 16px;
+}
 
-			> .description {
-				margin-top: 8px;
-				padding-top: 8px;
-				border-top: solid 0.5px var(--divider);
-			}
-		}
-	}
+.description {
+	margin-top: 8px;
+	padding-top: 8px;
+	border-top: solid 0.5px var(--divider);
 }
 </style>
diff --git a/packages/frontend/src/pages/user/follow-list.vue b/packages/frontend/src/pages/user/follow-list.vue
index d42acd838..4e76ddfe7 100644
--- a/packages/frontend/src/pages/user/follow-list.vue
+++ b/packages/frontend/src/pages/user/follow-list.vue
@@ -1,8 +1,8 @@
 <template>
 <div>
-	<MkPagination v-slot="{items}" ref="list" :pagination="type === 'following' ? followingPagination : followersPagination" class="mk-following-or-followers">
-		<div class="users">
-			<MkUserInfo v-for="user in items.map(x => type === 'following' ? x.followee : x.follower)" :key="user.id" class="user" :user="user"/>
+	<MkPagination v-slot="{items}" ref="list" :pagination="type === 'following' ? followingPagination : followersPagination">
+		<div :class="$style.users">
+			<MkUserInfo v-for="user in items.map(x => type === 'following' ? x.followee : x.follower)" :key="user.id" :user="user"/>
 		</div>
 	</MkPagination>
 </div>
@@ -36,12 +36,10 @@ const followersPagination = {
 };
 </script>
 
-<style lang="scss" scoped>
-.mk-following-or-followers {
-	> .users {
-		display: grid;
-		grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
-		grid-gap: var(--margin);
-	}
+<style lang="scss" module>
+.users {
+	display: grid;
+	grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
+	grid-gap: var(--margin);
 }
 </style>
diff --git a/packages/frontend/src/pages/user/followers.vue b/packages/frontend/src/pages/user/followers.vue
index 20573e67e..b330f7863 100644
--- a/packages/frontend/src/pages/user/followers.vue
+++ b/packages/frontend/src/pages/user/followers.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="1000">
+	<MkSpacer :contentMax="1000">
 		<Transition name="fade" mode="out-in">
 			<div v-if="user">
 				<XFollowList :user="user" type="followers"/>
@@ -56,6 +56,3 @@ definePageMetadata(computed(() => user ? {
 	avatar: user,
 } : null));
 </script>
-
-<style lang="scss" scoped>
-</style>
diff --git a/packages/frontend/src/pages/user/following.vue b/packages/frontend/src/pages/user/following.vue
index 3825f138c..9544cf76c 100644
--- a/packages/frontend/src/pages/user/following.vue
+++ b/packages/frontend/src/pages/user/following.vue
@@ -1,7 +1,7 @@
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer :content-max="1000">
+	<MkSpacer :contentMax="1000">
 		<Transition name="fade" mode="out-in">
 			<div v-if="user">
 				<XFollowList :user="user" type="following"/>
@@ -56,6 +56,3 @@ definePageMetadata(computed(() => user ? {
 	avatar: user,
 } : null));
 </script>
-
-<style lang="scss" scoped>
-</style>
diff --git a/packages/frontend/src/pages/user/gallery.vue b/packages/frontend/src/pages/user/gallery.vue
index b80e83fb1..b4bbab16f 100644
--- a/packages/frontend/src/pages/user/gallery.vue
+++ b/packages/frontend/src/pages/user/gallery.vue
@@ -1,7 +1,7 @@
 <template>
-<MkSpacer :content-max="700">
+<MkSpacer :contentMax="700">
 	<MkPagination v-slot="{items}" :pagination="pagination">
-		<div class="jrnovfpt">
+		<div :class="$style.root">
 			<MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/>
 		</div>
 	</MkPagination>
@@ -28,8 +28,8 @@ const pagination = {
 };
 </script>
 
-<style lang="scss" scoped>
-.jrnovfpt {
+<style lang="scss" module>
+.root {
 	display: grid;
 	grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
 	grid-gap: 12px;
diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue
index 9c133346d..2e69eb367 100644
--- a/packages/frontend/src/pages/user/home.vue
+++ b/packages/frontend/src/pages/user/home.vue
@@ -1,5 +1,5 @@
 <template>
-<MkSpacer :content-max="narrow ? 800 : 1100">
+<MkSpacer :contentMax="narrow ? 800 : 1100">
 	<div ref="rootEl" class="ftskorzw" :class="{ wide: !narrow }" style="container-type: inline-size;">
 		<div class="main _gaps">
 			<!-- TODO -->
@@ -7,7 +7,7 @@
 			<!-- <div class="punished" v-if="user.isSilenced"><i class="ti ti-alert-triangle" style="margin-right: 8px;"></i> {{ i18n.ts.userSilenced }}</div> -->
 
 			<div class="profile _gaps">
-				<MkAccountMoved v-if="user.movedTo" :moved-to="user.movedTo"/>
+				<MkAccountMoved v-if="user.movedTo" :movedTo="user.movedTo"/>
 				<MkRemoteCaution v-if="user.host != null" :href="user.url ?? user.uri!" class="warn"/>
 
 				<div :key="user.id" class="main _panel">
@@ -49,7 +49,7 @@
 						</span>
 					</div>
 					<div v-if="iAmModerator" class="moderationNote">
-						<MkTextarea v-if="editModerationNote || (moderationNote != null && moderationNote !== '')" v-model="moderationNote" manual-save>
+						<MkTextarea v-if="editModerationNote || (moderationNote != null && moderationNote !== '')" v-model="moderationNote" manualSave>
 							<template #label>Moderation note</template>
 						</MkTextarea>
 						<div v-else>
@@ -69,7 +69,7 @@
 					</div>
 					<div class="description">
 						<MkOmit>
-							<Mfm v-if="user.description" :text="user.description" :is-note="false" :author="user" :i="$i"/>
+							<Mfm v-if="user.description" :text="user.description" :isNote="false" :author="user" :i="$i"/>
 							<p v-else class="empty">{{ i18n.ts.noAccountDescription }}</p>
 						</MkOmit>
 					</div>
@@ -123,7 +123,7 @@
 					<XPhotos :key="user.id" :user="user"/>
 					<XActivity :key="user.id" :user="user"/>
 				</template>
-				<MkNotes v-if="!disableNotes" :class="$style.tl" :no-gap="true" :pagination="pagination"/>
+				<MkNotes v-if="!disableNotes" :class="$style.tl" :noGap="true" :pagination="pagination"/>
 			</div>
 		</div>
 		<div v-if="!narrow" class="sub _gaps" style="container-type: inline-size;">
diff --git a/packages/frontend/src/pages/user/index.activity.vue b/packages/frontend/src/pages/user/index.activity.vue
index 2d9ee85bc..64d36307e 100644
--- a/packages/frontend/src/pages/user/index.activity.vue
+++ b/packages/frontend/src/pages/user/index.activity.vue
@@ -9,7 +9,7 @@
 	</template>
 
 	<div style="padding: 8px;">
-		<MkChart :src="chartSrc" :args="{ user, withoutAll: true }" span="day" :limit="limit" :bar="true" :stacked="true" :detailed="false" :aspect-ratio="5"/>
+		<MkChart :src="chartSrc" :args="{ user, withoutAll: true }" span="day" :limit="limit" :bar="true" :stacked="true" :detailed="false" :aspectRatio="5"/>
 	</div>
 </MkContainer>
 </template>
diff --git a/packages/frontend/src/pages/user/index.timeline.vue b/packages/frontend/src/pages/user/index.timeline.vue
index d8fc25391..91c580ce9 100644
--- a/packages/frontend/src/pages/user/index.timeline.vue
+++ b/packages/frontend/src/pages/user/index.timeline.vue
@@ -1,5 +1,5 @@
 <template>
-<MkSpacer :content-max="800" style="padding-top: 0">
+<MkSpacer :contentMax="800" style="padding-top: 0">
 	<MkStickyContainer>
 		<template #header>
 			<MkTab v-model="include" :class="$style.tab">
@@ -8,7 +8,7 @@
 				<option value="files">{{ i18n.ts.withFiles }}</option>
 			</MkTab>
 		</template>
-		<MkNotes :no-gap="true" :pagination="pagination" :class="$style.tl"/>
+		<MkNotes :noGap="true" :pagination="pagination" :class="$style.tl"/>
 	</MkStickyContainer>
 </MkSpacer>
 </template>
diff --git a/packages/frontend/src/pages/user/index.vue b/packages/frontend/src/pages/user/index.vue
index 03a226cc0..6aba815e9 100644
--- a/packages/frontend/src/pages/user/index.vue
+++ b/packages/frontend/src/pages/user/index.vue
@@ -2,20 +2,19 @@
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
 	<div>
-		<Transition name="fade" mode="out-in">
-			<div v-if="user">
-				<XHome v-if="tab === 'home'" :user="user"/>
-				<XTimeline v-else-if="tab === 'notes'" :user="user" />
-				<XActivity v-else-if="tab === 'activity'" :user="user"/>
-				<XAchievements v-else-if="tab === 'achievements'" :user="user"/>
-				<XReactions v-else-if="tab === 'reactions'" :user="user"/>
-				<XClips v-else-if="tab === 'clips'" :user="user"/>
-				<XPages v-else-if="tab === 'pages'" :user="user"/>
-				<XGallery v-else-if="tab === 'gallery'" :user="user"/>
-			</div>
-			<MkError v-else-if="error" @retry="fetchUser()"/>
-			<MkLoading v-else/>
-		</Transition>
+		<div v-if="user">
+			<XHome v-if="tab === 'home'" :user="user"/>
+			<XTimeline v-else-if="tab === 'notes'" :user="user"/>
+			<XActivity v-else-if="tab === 'activity'" :user="user"/>
+			<XAchievements v-else-if="tab === 'achievements'" :user="user"/>
+			<XReactions v-else-if="tab === 'reactions'" :user="user"/>
+			<XClips v-else-if="tab === 'clips'" :user="user"/>
+			<XLists v-else-if="tab === 'lists'" :user="user"/>
+			<XPages v-else-if="tab === 'pages'" :user="user"/>
+			<XGallery v-else-if="tab === 'gallery'" :user="user"/>
+		</div>
+		<MkError v-else-if="error" @retry="fetchUser()"/>
+		<MkLoading v-else/>
 	</div>
 </MkStickyContainer>
 </template>
@@ -36,6 +35,7 @@ const XActivity = defineAsyncComponent(() => import('./activity.vue'));
 const XAchievements = defineAsyncComponent(() => import('./achievements.vue'));
 const XReactions = defineAsyncComponent(() => import('./reactions.vue'));
 const XClips = defineAsyncComponent(() => import('./clips.vue'));
+const XLists = defineAsyncComponent(() => import('./lists.vue'));
 const XPages = defineAsyncComponent(() => import('./pages.vue'));
 const XGallery = defineAsyncComponent(() => import('./gallery.vue'));
 
@@ -90,6 +90,10 @@ const headerTabs = $computed(() => user ? [{
 	key: 'clips',
 	title: i18n.ts.clips,
 	icon: 'ti ti-paperclip',
+}, {
+	key: 'lists',
+	title: i18n.ts.lists,
+	icon: 'ti ti-list',
 }, {
 	key: 'pages',
 	title: i18n.ts.pages,
@@ -112,14 +116,3 @@ definePageMetadata(computed(() => user ? {
 	},
 } : null));
 </script>
-
-<style lang="scss" scoped>
-.fade-enter-active,
-.fade-leave-active {
-	transition: opacity 0.125s ease;
-}
-.fade-enter-from,
-.fade-leave-to {
-	opacity: 0;
-}
-</style>
diff --git a/packages/frontend/src/pages/user/lists.vue b/packages/frontend/src/pages/user/lists.vue
new file mode 100644
index 000000000..78f03d2b3
--- /dev/null
+++ b/packages/frontend/src/pages/user/lists.vue
@@ -0,0 +1,51 @@
+<template>
+<MkStickyContainer>
+	<MkSpacer :contentMax="700">
+		<div>
+			<MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination" class="lists">
+				<MkA v-for="list in items" :key="list.id" class="_panel" :class="$style.list" :to="`/list/${ list.id }`">
+					<div>{{ list.name }}</div>
+					<MkAvatars :userIds="list.userIds"/>
+				</MkA>
+			</MkPagination>
+		</div>
+	</MkSpacer>
+</MkStickyContainer>
+</template>
+
+<script lang="ts" setup>
+import {} from 'vue';
+import * as misskey from 'misskey-js';
+import MkPagination from '@/components/MkPagination.vue';
+import MkStickyContainer from '@/components/global/MkStickyContainer.vue';
+import MkSpacer from '@/components/global/MkSpacer.vue';
+import MkAvatars from '@/components/MkAvatars.vue';
+
+const props = defineProps<{
+	user: misskey.entities.UserDetailed;
+}>();
+
+const pagination = {
+	endpoint: 'users/lists/list' as const,
+	noPaging: true,
+	limit: 10,
+	params: {
+		userId: props.user.id,
+	},
+};
+</script>
+
+<style lang="scss" module>
+.list {
+	display: block;
+	padding: 16px;
+	border: solid 1px var(--divider);
+	border-radius: 6px;
+	margin-bottom: 8px;
+
+	&:hover {
+		border: solid 1px var(--accent);
+		text-decoration: none;
+	}
+}
+</style>
diff --git a/packages/frontend/src/pages/user/pages.vue b/packages/frontend/src/pages/user/pages.vue
index 7ea1d75f4..a2975c707 100644
--- a/packages/frontend/src/pages/user/pages.vue
+++ b/packages/frontend/src/pages/user/pages.vue
@@ -1,5 +1,5 @@
 <template>
-<MkSpacer :content-max="700">
+<MkSpacer :contentMax="700">
 	<MkPagination v-slot="{items}" ref="list" :pagination="pagination">
 		<MkPagePreview v-for="page in items" :key="page.id" :page="page" class="_margin"/>
 	</MkPagination>
@@ -24,7 +24,3 @@ const pagination = {
 	})),
 };
 </script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/packages/frontend/src/pages/user/reactions.vue b/packages/frontend/src/pages/user/reactions.vue
index 24129ec02..228160339 100644
--- a/packages/frontend/src/pages/user/reactions.vue
+++ b/packages/frontend/src/pages/user/reactions.vue
@@ -1,11 +1,11 @@
 <template>
-<MkSpacer :content-max="700">
+<MkSpacer :contentMax="700">
 	<MkPagination v-slot="{items}" ref="list" :pagination="pagination">
-		<div v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" class="item _panel _margin afdcfbfb">
-			<div class="header">
-				<MkAvatar class="avatar" :user="user"/>
-				<MkReactionIcon class="reaction" :reaction="item.type" :no-style="true"/>
-				<MkTime :time="item.createdAt" class="createdAt"/>
+		<div v-for="item in items" :key="item.id" :to="`/clips/${item.id}`" class="_panel _margin">
+			<div :class="$style.header">
+				<MkAvatar :class="$style.avatar" :user="user"/>
+				<MkReactionIcon :class="$style.reaction" :reaction="item.type" :noStyle="true"/>
+				<MkTime :time="item.createdAt" :class="$style.createdAt"/>
 			</div>
 			<MkNote :key="item.id" :note="item.note"/>
 		</div>
@@ -33,29 +33,27 @@ const pagination = {
 };
 </script>
 
-<style lang="scss" scoped>
-.afdcfbfb {
-	> .header {
-		display: flex;
-		align-items: center;
-		padding: 8px 16px;
-		margin-bottom: 8px;
-		border-bottom: solid 2px var(--divider);
+<style lang="scss" module>
+.header {
+	display: flex;
+	align-items: center;
+	padding: 8px 16px;
+	margin-bottom: 8px;
+	border-bottom: solid 2px var(--divider);
+}
 
-		> .avatar {
-			width: 24px;
-			height: 24px;
-			margin-right: 8px;
-		}
+.avatar {
+	width: 24px;
+	height: 24px;
+	margin-right: 8px;
+}
 
-		> .reaction {
-			width: 32px;
-			height: 32px;
-		}
+.reaction {
+	width: 32px;
+	height: 32px;
+}
 
-		> .createdAt {
-			margin-left: auto;
-		}
-	}
+.createdAt {
+	margin-left: auto;
 }
 </style>
diff --git a/packages/frontend/src/pages/welcome.entrance.a.vue b/packages/frontend/src/pages/welcome.entrance.a.vue
index 929152bd5..f082b4b3c 100644
--- a/packages/frontend/src/pages/welcome.entrance.a.vue
+++ b/packages/frontend/src/pages/welcome.entrance.a.vue
@@ -6,11 +6,11 @@
 	<div class="shape2"></div>
 	<img src="/client-assets/misskey.svg" class="misskey"/>
 	<div class="emojis">
-		<MkEmoji :normal="true" :no-style="true" emoji="👍"/>
-		<MkEmoji :normal="true" :no-style="true" emoji="❤"/>
-		<MkEmoji :normal="true" :no-style="true" emoji="😆"/>
-		<MkEmoji :normal="true" :no-style="true" emoji="🎉"/>
-		<MkEmoji :normal="true" :no-style="true" emoji="🍮"/>
+		<MkEmoji :normal="true" :noStyle="true" emoji="👍"/>
+		<MkEmoji :normal="true" :noStyle="true" emoji="❤"/>
+		<MkEmoji :normal="true" :noStyle="true" emoji="😆"/>
+		<MkEmoji :normal="true" :noStyle="true" emoji="🎉"/>
+		<MkEmoji :normal="true" :noStyle="true" emoji="🍮"/>
 	</div>
 	<div class="contents">
 		<MkVisitorDashboard/>
diff --git a/packages/frontend/src/pages/welcome.setup.vue b/packages/frontend/src/pages/welcome.setup.vue
index 7728d97a6..2081cb9c9 100644
--- a/packages/frontend/src/pages/welcome.setup.vue
+++ b/packages/frontend/src/pages/welcome.setup.vue
@@ -1,27 +1,32 @@
 <template>
-<form :class="$style.root" class="_panel" @submit.prevent="submit()">
-	<div :class="$style.title">
-		<div>Welcome to Misskey!</div>
-		<div :class="$style.version">v{{ version }}</div>
+<div>
+	<MkAnimBg style="position: fixed; top: 0;"/>
+	<div :class="$style.formContainer">
+		<form :class="$style.form" class="_panel" @submit.prevent="submit()">
+			<div :class="$style.title">
+				<div>Welcome to Misskey!</div>
+				<div :class="$style.version">v{{ version }}</div>
+			</div>
+			<div class="_gaps_m" style="padding: 32px;">
+				<div>{{ i18n.ts.intro }}</div>
+				<MkInput v-model="username" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-admin-username>
+					<template #label>{{ i18n.ts.username }}</template>
+					<template #prefix>@</template>
+					<template #suffix>@{{ host }}</template>
+				</MkInput>
+				<MkInput v-model="password" type="password" data-cy-admin-password>
+					<template #label>{{ i18n.ts.password }}</template>
+					<template #prefix><i class="ti ti-lock"></i></template>
+				</MkInput>
+				<div>
+					<MkButton gradate large rounded type="submit" :disabled="submitting" data-cy-admin-ok style="margin: 0 auto;">
+						{{ submitting ? i18n.ts.processing : i18n.ts.done }}<MkEllipsis v-if="submitting"/>
+					</MkButton>
+				</div>
+			</div>
+		</form>
 	</div>
-	<div class="_gaps_m" style="padding: 32px;">
-		<div>{{ i18n.ts.intro }}</div>
-		<MkInput v-model="username" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-admin-username>
-			<template #label>{{ i18n.ts.username }}</template>
-			<template #prefix>@</template>
-			<template #suffix>@{{ host }}</template>
-		</MkInput>
-		<MkInput v-model="password" type="password" data-cy-admin-password>
-			<template #label>{{ i18n.ts.password }}</template>
-			<template #prefix><i class="ti ti-lock"></i></template>
-		</MkInput>
-		<div>
-			<MkButton gradate large rounded type="submit" :disabled="submitting" data-cy-admin-ok style="margin: 0 auto;">
-				{{ submitting ? i18n.ts.processing : i18n.ts.done }}<MkEllipsis v-if="submitting"/>
-			</MkButton>
-		</div>
-	</div>
-</form>
+</div>
 </template>
 
 <script lang="ts" setup>
@@ -32,6 +37,7 @@ import { host, version } from '@/config';
 import * as os from '@/os';
 import { login } from '@/account';
 import { i18n } from '@/i18n';
+import MkAnimBg from '@/components/MkAnimBg.vue';
 
 let username = $ref('');
 let password = $ref('');
@@ -58,12 +64,21 @@ function submit() {
 </script>
 
 <style lang="scss" module>
-.root {
+.formContainer {
+	min-height: 100svh;
+	padding: 32px 32px 64px 32px;
+	box-sizing: border-box;
+display: grid;
+place-content: center;
+}
+
+.form {
+	position: relative;
+	z-index: 10;
 	border-radius: var(--radius);
 	box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1);
-	overflow: hidden;
+	overflow: clip;
 	max-width: 500px;
-	margin: 32px auto;
 }
 
 .title {
diff --git a/packages/frontend/src/pages/welcome.timeline.vue b/packages/frontend/src/pages/welcome.timeline.vue
index 6ec6e3f86..a93d103d4 100644
--- a/packages/frontend/src/pages/welcome.timeline.vue
+++ b/packages/frontend/src/pages/welcome.timeline.vue
@@ -3,16 +3,16 @@
 	<div ref="scrollEl" :class="[$style.scrollbox, { [$style.scroll]: isScrolling }]">
 		<div v-for="note in notes" :key="note.id" :class="$style.note">
 			<div class="_panel" :class="$style.content">
-				<div :class="$style.body">
+				<div>
 					<MkA v-if="note.replyId" class="reply" :to="`/notes/${note.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
 					<Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i"/>
 					<MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA>
 				</div>
 				<div v-if="note.files.length > 0" :class="$style.richcontent">
-					<MkMediaList :media-list="note.files"/>
+					<MkMediaList :mediaList="note.files"/>
 				</div>
 				<div v-if="note.poll">
-					<MkPoll :note="note" :read-only="true"/>
+					<MkPoll :note="note" :readOnly="true"/>
 				</div>
 			</div>
 			<MkReactionsViewer ref="reactionsViewer" :note="note"/>
diff --git a/packages/frontend/src/pizzax.ts b/packages/frontend/src/pizzax.ts
index 2616a8a1d..d97bd4be6 100644
--- a/packages/frontend/src/pizzax.ts
+++ b/packages/frontend/src/pizzax.ts
@@ -6,7 +6,7 @@ import { $i } from './account';
 import { api } from './os';
 import { get, set } from './scripts/idb-proxy';
 import { defaultStore } from './store';
-import { stream } from './stream';
+import { useStream } from './stream';
 import { deepClone } from './scripts/clone';
 
 type StateDef = Record<string, {
@@ -26,8 +26,6 @@ type PizzaxChannelMessage<T extends StateDef> = {
 	userId?: string;
 };
 
-const connection = $i && stream.useChannel('main');
-
 export class Storage<T extends StateDef> {
 	public readonly ready: Promise<void>;
 	public readonly loaded: Promise<void>;
@@ -105,8 +103,10 @@ export class Storage<T extends StateDef> {
 		});
 
 		if ($i) {
+			const connection = useStream().useChannel('main');
+
 			// streamingのuser storage updateイベントを監視して更新
-			connection?.on('registryUpdated', ({ scope, key, value }: { scope?: string[], key: keyof T, value: T[typeof key]['default'] }) => {
+			connection.on('registryUpdated', ({ scope, key, value }: { scope?: string[], key: keyof T, value: T[typeof key]['default'] }) => {
 				if (!scope || scope.length !== 2 || scope[0] !== 'client' || scope[1] !== this.key || this.state[key] === value) return;
 
 				this.reactiveState[key].value = this.state[key] = value;
diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts
index e46c1eeb7..6b11137d7 100644
--- a/packages/frontend/src/router.ts
+++ b/packages/frontend/src/router.ts
@@ -30,6 +30,10 @@ export const routes = [{
 	name: 'note',
 	path: '/notes/:noteId',
 	component: page(() => import('./pages/note.vue')),
+}, {
+	name: 'list',
+	path: '/list/:listId',
+	component: page(() => import('./pages/list.vue')),
 }, {
 	path: '/clips/:clipId',
 	component: page(() => import('./pages/clip.vue')),
@@ -242,9 +246,6 @@ export const routes = [{
 }, {
 	path: '/scratchpad',
 	component: page(() => import('./pages/scratchpad.vue')),
-}, {
-	path: '/preview',
-	component: page(() => import('./pages/preview.vue')),
 }, {
 	path: '/auth/:token',
 	component: page(() => import('./pages/auth.vue')),
diff --git a/packages/frontend/src/scripts/emojilist.ts b/packages/frontend/src/scripts/emojilist.ts
index 2e853b58b..79661b7ce 100644
--- a/packages/frontend/src/scripts/emojilist.ts
+++ b/packages/frontend/src/scripts/emojilist.ts
@@ -2,7 +2,6 @@ export const unicodeEmojiCategories = ['face', 'people', 'animals_and_nature', '
 
 export type UnicodeEmojiDef = {
 	name: string;
-	keywords: string[];
 	char: string;
 	category: typeof unicodeEmojiCategories[number];
 }
@@ -10,11 +9,16 @@ export type UnicodeEmojiDef = {
 // initial converted from https://github.com/muan/emojilib/commit/242fe68be86ed6536843b83f7e32f376468b38fb
 import _emojilist from '../emojilist.json';
 
-export const emojilist = _emojilist as UnicodeEmojiDef[];
+export const emojilist: UnicodeEmojiDef[] = _emojilist.map(x => ({
+	name: x[1] as string,
+	char: x[0] as string,
+	category: unicodeEmojiCategories[x[2]],
+}));
 
 const _indexByChar = new Map<string, number>();
 const _charGroupByCategory = new Map<string, string[]>();
-emojilist.forEach((emo, i) => {
+for (let i = 0; i < emojilist.length; i++) {
+	const emo = emojilist[i];
 	_indexByChar.set(emo.char, i);
 
 	if (_charGroupByCategory.has(emo.category)) {
@@ -22,14 +26,14 @@ emojilist.forEach((emo, i) => {
 	} else {
 		_charGroupByCategory.set(emo.category, [emo.char]);
 	}
-});
+}
 
 export const emojiCharByCategory = _charGroupByCategory;
 
-export function getEmojiName(char: string): string | undefined {
+export function getEmojiName(char: string): string | null {
 	const idx = _indexByChar.get(char);
-	if (typeof idx === 'undefined') {
-		return undefined;
+	if (idx == null) {
+		return null;
 	} else {
 		return emojilist[idx].name;
 	}
diff --git a/packages/frontend/src/scripts/get-drive-file-menu.ts b/packages/frontend/src/scripts/get-drive-file-menu.ts
index ed01b4905..060c8a1a1 100644
--- a/packages/frontend/src/scripts/get-drive-file-menu.ts
+++ b/packages/frontend/src/scripts/get-drive-file-menu.ts
@@ -73,7 +73,7 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile) {
 		action: () => rename(file),
 	}, {
 		text: file.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive,
-		icon: file.isSensitive ? 'ti ti-eye' : 'ti ti-eye-off',
+		icon: file.isSensitive ? 'ti ti-eye' : 'ti ti-eye-exclamation',
 		action: () => toggleSensitive(file),
 	}, {
 		text: i18n.ts.describeFile,
diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts
index c8a610025..960f26ca6 100644
--- a/packages/frontend/src/scripts/get-note-menu.ts
+++ b/packages/frontend/src/scripts/get-note-menu.ts
@@ -7,7 +7,7 @@ import { instance } from '@/instance';
 import * as os from '@/os';
 import copyToClipboard from '@/scripts/copy-to-clipboard';
 import { url } from '@/config';
-import { noteActions } from '@/store';
+import { defaultStore, noteActions } from '@/store';
 import { miLocalStorage } from '@/local-storage';
 import { getUserMenu } from '@/scripts/get-user-menu';
 import { clipsCache } from '@/cache';
@@ -396,5 +396,15 @@ export function getNoteMenu(props: {
 		}))]);
 	}
 
+	if (defaultStore.state.devMode) {
+		menu = menu.concat([null, {
+			icon: 'ti ti-id',
+			text: i18n.ts.copyNoteId,
+			action: () => {
+				copyToClipboard(appearNote.id);
+			},
+		}]);
+	}
+
 	return menu;
 }
diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts
index 6ff9fb63f..b055d2647 100644
--- a/packages/frontend/src/scripts/get-user-menu.ts
+++ b/packages/frontend/src/scripts/get-user-menu.ts
@@ -4,7 +4,7 @@ import { i18n } from '@/i18n';
 import copyToClipboard from '@/scripts/copy-to-clipboard';
 import { host } from '@/config';
 import * as os from '@/os';
-import { userActions } from '@/store';
+import { defaultStore, userActions } from '@/store';
 import { $i, iAmModerator } from '@/account';
 import { mainRouter } from '@/router';
 import { Router } from '@/nirax';
@@ -240,6 +240,16 @@ export function getUserMenu(user: misskey.entities.UserDetailed, router: Router
 		}]);
 	}
 
+	if (defaultStore.state.devMode) {
+		menu = menu.concat([null, {
+			icon: 'ti ti-id',
+			text: i18n.ts.copyUserId,
+			action: () => {
+				copyToClipboard(user.id);
+			},
+		}]);
+	}
+
 	if ($i && meId === user.id) {
 		menu = menu.concat([null, {
 			icon: 'ti ti-pencil',
diff --git a/packages/frontend/src/scripts/hpml/block.ts b/packages/frontend/src/scripts/hpml/block.ts
deleted file mode 100644
index 804c5c112..000000000
--- a/packages/frontend/src/scripts/hpml/block.ts
+++ /dev/null
@@ -1,109 +0,0 @@
-// blocks
-
-export type BlockBase = {
-	id: string;
-	type: string;
-};
-
-export type TextBlock = BlockBase & {
-	type: 'text';
-	text: string;
-};
-
-export type SectionBlock = BlockBase & {
-	type: 'section';
-	title: string;
-	children: (Block | VarBlock)[];
-};
-
-export type ImageBlock = BlockBase & {
-	type: 'image';
-	fileId: string | null;
-};
-
-export type ButtonBlock = BlockBase & {
-	type: 'button';
-	text: any;
-	primary: boolean;
-	action: string;
-	content: string;
-	event: string;
-	message: string;
-	var: string;
-	fn: string;
-};
-
-export type IfBlock = BlockBase & {
-	type: 'if';
-	var: string;
-	children: Block[];
-};
-
-export type TextareaBlock = BlockBase & {
-	type: 'textarea';
-	text: string;
-};
-
-export type PostBlock = BlockBase & {
-	type: 'post';
-	text: string;
-	attachCanvasImage: boolean;
-	canvasId: string;
-};
-
-export type CanvasBlock = BlockBase & {
-	type: 'canvas';
-	name: string; // canvas id
-	width: number;
-	height: number;
-};
-
-export type NoteBlock = BlockBase & {
-	type: 'note';
-	detailed: boolean;
-	note: string | null;
-};
-
-export type Block =
-	TextBlock | SectionBlock | ImageBlock | ButtonBlock | IfBlock | TextareaBlock | PostBlock | CanvasBlock | NoteBlock | VarBlock;
-
-// variable blocks
-
-export type VarBlockBase = BlockBase & {
-	name: string;
-};
-
-export type NumberInputVarBlock = VarBlockBase & {
-	type: 'numberInput';
-	text: string;
-};
-
-export type TextInputVarBlock = VarBlockBase & {
-	type: 'textInput';
-	text: string;
-};
-
-export type SwitchVarBlock = VarBlockBase & {
-	type: 'switch';
-	text: string;
-};
-
-export type RadioButtonVarBlock = VarBlockBase & {
-	type: 'radioButton';
-	title: string;
-	values: string[];
-};
-
-export type CounterVarBlock = VarBlockBase & {
-	type: 'counter';
-	text: string;
-	inc: number;
-};
-
-export type VarBlock =
-	NumberInputVarBlock | TextInputVarBlock | SwitchVarBlock | RadioButtonVarBlock | CounterVarBlock;
-
-const varBlock = ['numberInput', 'textInput', 'switch', 'radioButton', 'counter'];
-export function isVarBlock(block: Block): block is VarBlock {
-	return varBlock.includes(block.type);
-}
diff --git a/packages/frontend/src/scripts/hpml/evaluator.ts b/packages/frontend/src/scripts/hpml/evaluator.ts
deleted file mode 100644
index 9adfba7f2..000000000
--- a/packages/frontend/src/scripts/hpml/evaluator.ts
+++ /dev/null
@@ -1,171 +0,0 @@
-import { ref, Ref, unref } from 'vue';
-import { collectPageVars } from '../collect-page-vars';
-import { initHpmlLib } from './lib';
-import { Expr, isLiteralValue, Variable } from './expr';
-import { PageVar, envVarsDef, Fn, HpmlScope, HpmlError } from '.';
-import { version } from '@/config';
-
-/**
- * Hpml evaluator
- */
-export class Hpml {
-	private variables: Variable[];
-	private pageVars: PageVar[];
-	private envVars: Record<keyof typeof envVarsDef, any>;
-	public pageVarUpdatedCallback?: values.VFn;
-	public canvases: Record<string, HTMLCanvasElement> = {};
-	public vars: Ref<Record<string, any>> = ref({});
-	public page: Record<string, any>;
-
-	private opts: {
-		randomSeed: string; visitor?: any; url?: string;
-	};
-
-	constructor(page: Hpml['page'], opts: Hpml['opts']) {
-		this.page = page;
-		this.variables = this.page.variables;
-		this.pageVars = collectPageVars(this.page.content);
-		this.opts = opts;
-
-		const date = new Date();
-
-		this.envVars = {
-			AI: 'kawaii',
-			VERSION: version,
-			URL: this.page ? `${opts.url}/@${this.page.user.username}/pages/${this.page.name}` : '',
-			LOGIN: opts.visitor != null,
-			NAME: opts.visitor ? opts.visitor.name || opts.visitor.username : '',
-			USERNAME: opts.visitor ? opts.visitor.username : '',
-			USERID: opts.visitor ? opts.visitor.id : '',
-			NOTES_COUNT: opts.visitor ? opts.visitor.notesCount : 0,
-			FOLLOWERS_COUNT: opts.visitor ? opts.visitor.followersCount : 0,
-			FOLLOWING_COUNT: opts.visitor ? opts.visitor.followingCount : 0,
-			IS_CAT: opts.visitor ? opts.visitor.isCat : false,
-			SEED: opts.randomSeed ? opts.randomSeed : '',
-			YMD: `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`,
-			AISCRIPT_DISABLED: true,
-			NULL: null,
-		};
-
-		this.eval();
-	}
-
-	public eval() {
-		try {
-			this.vars.value = this.evaluateVars();
-		} catch (err) {
-			//this.onError(e);
-		}
-	}
-
-	public interpolate(str: string) {
-		if (str == null) return null;
-		return str.replace(/{(.+?)}/g, match => {
-			const v = unref(this.vars)[match.slice(1, -1).trim()];
-			return v == null ? 'NULL' : v.toString();
-		});
-	}
-
-	public registerCanvas(id: string, canvas: any) {
-		this.canvases[id] = canvas;
-	}
-
-	public updatePageVar(name: string, value: any) {
-		const pageVar = this.pageVars.find(v => v.name === name);
-		if (pageVar !== undefined) {
-			pageVar.value = value;
-		} else {
-			throw new HpmlError(`No such page var '${name}'`);
-		}
-	}
-
-	public updateRandomSeed(seed: string) {
-		this.opts.randomSeed = seed;
-		this.envVars.SEED = seed;
-	}
-
-	private _interpolateScope(str: string, scope: HpmlScope) {
-		return str.replace(/{(.+?)}/g, match => {
-			const v = scope.getState(match.slice(1, -1).trim());
-			return v == null ? 'NULL' : v.toString();
-		});
-	}
-
-	public evaluateVars(): Record<string, any> {
-		const values: Record<string, any> = {};
-
-		for (const [k, v] of Object.entries(this.envVars)) {
-			values[k] = v;
-		}
-
-		for (const v of this.pageVars) {
-			values[v.name] = v.value;
-		}
-
-		for (const v of this.variables) {
-			values[v.name] = this.evaluate(v, new HpmlScope([values]));
-		}
-
-		return values;
-	}
-
-	private evaluate(expr: Expr, scope: HpmlScope): any {
-		if (isLiteralValue(expr)) {
-			if (expr.type === null) {
-				return null;
-			}
-
-			if (expr.type === 'number') {
-				return parseInt((expr.value as any), 10);
-			}
-
-			if (expr.type === 'text' || expr.type === 'multiLineText') {
-				return this._interpolateScope(expr.value || '', scope);
-			}
-
-			if (expr.type === 'textList') {
-				return this._interpolateScope(expr.value || '', scope).trim().split('\n');
-			}
-
-			if (expr.type === 'ref') {
-				return scope.getState(expr.value);
-			}
-
-			// Define user function
-			if (expr.type === 'fn') {
-				return {
-					slots: expr.value.slots.map(x => x.name),
-					exec: (slotArg: Record<string, any>) => {
-						return this.evaluate(expr.value.expression, scope.createChildScope(slotArg, expr.id));
-					},
-				} as Fn;
-			}
-			return;
-		}
-
-		// Call user function
-		if (expr.type.startsWith('fn:')) {
-			const fnName = expr.type.split(':')[1];
-			const fn = scope.getState(fnName);
-			const args = {} as Record<string, any>;
-			for (let i = 0; i < fn.slots.length; i++) {
-				const name = fn.slots[i];
-				args[name] = this.evaluate(expr.args[i], scope);
-			}
-			return fn.exec(args);
-		}
-
-		if (expr.args === undefined) return null;
-
-		const funcs = initHpmlLib(expr, scope, this.opts.randomSeed, this.opts.visitor);
-
-		// Call function
-		const fnName = expr.type;
-		const fn = (funcs as any)[fnName];
-		if (fn == null) {
-			throw new HpmlError(`No such function '${fnName}'`);
-		} else {
-			return fn(...expr.args.map(x => this.evaluate(x, scope)));
-		}
-	}
-}
diff --git a/packages/frontend/src/scripts/hpml/expr.ts b/packages/frontend/src/scripts/hpml/expr.ts
deleted file mode 100644
index 18c7c2a14..000000000
--- a/packages/frontend/src/scripts/hpml/expr.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import { literalDefs, Type } from '.';
-
-export type ExprBase = {
-	id: string;
-};
-
-// value
-
-export type EmptyValue = ExprBase & {
-	type: null;
-	value: null;
-};
-
-export type TextValue = ExprBase & {
-	type: 'text';
-	value: string;
-};
-
-export type MultiLineTextValue = ExprBase & {
-	type: 'multiLineText';
-	value: string;
-};
-
-export type TextListValue = ExprBase & {
-	type: 'textList';
-	value: string;
-};
-
-export type NumberValue = ExprBase & {
-	type: 'number';
-	value: number;
-};
-
-export type RefValue = ExprBase & {
-	type: 'ref';
-	value: string; // value is variable name
-};
-
-export type AiScriptRefValue = ExprBase & {
-	type: 'aiScriptVar';
-	value: string; // value is variable name
-};
-
-export type UserFnValue = ExprBase & {
-	type: 'fn';
-	value: UserFnInnerValue;
-};
-type UserFnInnerValue = {
-	slots: {
-		name: string;
-		type: Type;
-	}[];
-	expression: Expr;
-};
-
-export type Value =
-	EmptyValue | TextValue | MultiLineTextValue | TextListValue | NumberValue | RefValue | AiScriptRefValue | UserFnValue;
-
-export function isLiteralValue(expr: Expr): expr is Value {
-	if (expr.type == null) return true;
-	if (literalDefs[expr.type]) return true;
-	return false;
-}
-
-// call function
-
-export type CallFn = ExprBase & { // "fn:hoge" or string
-	type: string;
-	args: Expr[];
-	value: null;
-};
-
-// variable
-export type Variable = (Value | CallFn) & {
-	name: string;
-};
-
-// expression
-export type Expr = Variable | Value | CallFn;
diff --git a/packages/frontend/src/scripts/hpml/index.ts b/packages/frontend/src/scripts/hpml/index.ts
deleted file mode 100644
index 994f286b9..000000000
--- a/packages/frontend/src/scripts/hpml/index.ts
+++ /dev/null
@@ -1,100 +0,0 @@
-/**
- * Hpml
- */
-
-import { Hpml } from './evaluator';
-import { funcDefs } from './lib';
-
-export type Fn = {
-	slots: string[];
-	exec: (args: Record<string, any>) => ReturnType<Hpml['evaluate']>;
-};
-
-export type Type = 'string' | 'number' | 'boolean' | 'stringArray' | null;
-
-export const literalDefs: Record<string, { out: any; category: string; icon: any; }> = {
-	text: { out: 'string', category: 'value', icon: 'ti ti-quote' },
-	multiLineText: { out: 'string', category: 'value', icon: 'ti ti-align-left' },
-	textList: { out: 'stringArray', category: 'value', icon: 'ti ti-list' },
-	number: { out: 'number', category: 'value', icon: 'ti ti-list-numbers' },
-	ref: { out: null, category: 'value', icon: 'ti ti-wand' },
-	aiScriptVar: { out: null, category: 'value', icon: 'ti ti-wand' },
-	fn: { out: 'function', category: 'value', icon: 'ti ti-math-function' },
-};
-
-export const blockDefs = [
-	...Object.entries(literalDefs).map(([k, v]) => ({
-		type: k, out: v.out, category: v.category, icon: v.icon,
-	})),
-	...Object.entries(funcDefs).map(([k, v]) => ({
-		type: k, out: v.out, category: v.category, icon: v.icon,
-	})),
-];
-
-export type PageVar = { name: string; value: any; type: Type; };
-
-export const envVarsDef: Record<string, Type> = {
-	AI: 'string',
-	URL: 'string',
-	VERSION: 'string',
-	LOGIN: 'boolean',
-	NAME: 'string',
-	USERNAME: 'string',
-	USERID: 'string',
-	NOTES_COUNT: 'number',
-	FOLLOWERS_COUNT: 'number',
-	FOLLOWING_COUNT: 'number',
-	IS_CAT: 'boolean',
-	SEED: null,
-	YMD: 'string',
-	AISCRIPT_DISABLED: 'boolean',
-	NULL: null,
-};
-
-export class HpmlScope {
-	private layerdStates: Record<string, any>[];
-	public name: string;
-
-	constructor(layerdStates: HpmlScope['layerdStates'], name?: HpmlScope['name']) {
-		this.layerdStates = layerdStates;
-		this.name = name ?? 'anonymous';
-	}
-
-	public createChildScope(states: Record<string, any>, name?: HpmlScope['name']): HpmlScope {
-		const layer = [states, ...this.layerdStates];
-		return new HpmlScope(layer, name);
-	}
-
-	/**
-	 * 指定した名前の変数の値を取得します
-	 * @param name 変数名
-	 */
-	public getState(name: string): any {
-		for (const later of this.layerdStates) {
-			const state = later[name];
-			if (state !== undefined) {
-				return state;
-			}
-		}
-
-		throw new HpmlError(
-			`No such variable '${name}' in scope '${this.name}'`, {
-				scope: this.layerdStates,
-			});
-	}
-}
-
-export class HpmlError extends Error {
-	public info?: any;
-
-	constructor(message: string, info?: any) {
-		super(message);
-
-		this.info = info;
-
-		// Maintains proper stack trace for where our error was thrown (only available on V8)
-		if (Error.captureStackTrace) {
-			Error.captureStackTrace(this, HpmlError);
-		}
-	}
-}
diff --git a/packages/frontend/src/scripts/hpml/lib.ts b/packages/frontend/src/scripts/hpml/lib.ts
deleted file mode 100644
index 88db82dd2..000000000
--- a/packages/frontend/src/scripts/hpml/lib.ts
+++ /dev/null
@@ -1,245 +0,0 @@
-import seedrandom from 'seedrandom';
-import { Hpml } from './evaluator';
-import { Expr } from './expr';
-import { Fn, HpmlScope } from '.';
-
-/* TODO: https://www.chartjs.org/docs/latest/configuration/canvas-background.html#color
-// https://stackoverflow.com/questions/38493564/chart-area-background-color-chartjs
-Chart.pluginService.register({
-	beforeDraw: (chart, easing) => {
-		if (chart.config.options.chartArea && chart.config.options.chartArea.backgroundColor) {
-			const ctx = chart.chart.ctx;
-			ctx.save();
-			ctx.fillStyle = chart.config.options.chartArea.backgroundColor;
-			ctx.fillRect(0, 0, chart.chart.width, chart.chart.height);
-			ctx.restore();
-		}
-	}
-});
-*/
-
-export function initAiLib(hpml: Hpml) {
-	return {
-		'MkPages:updated': values.FN_NATIVE(([callback]) => {
-			hpml.pageVarUpdatedCallback = (callback as values.VFn);
-		}),
-		'MkPages:get_canvas': values.FN_NATIVE(([id]) => {
-			utils.assertString(id);
-			const canvas = hpml.canvases[id.value];
-			const ctx = canvas.getContext('2d');
-			return values.OBJ(new Map([
-				['clear_rect', values.FN_NATIVE(([x, y, width, height]) => { ctx.clearRect(x.value, y.value, width.value, height.value); })],
-				['fill_rect', values.FN_NATIVE(([x, y, width, height]) => { ctx.fillRect(x.value, y.value, width.value, height.value); })],
-				['stroke_rect', values.FN_NATIVE(([x, y, width, height]) => { ctx.strokeRect(x.value, y.value, width.value, height.value); })],
-				['fill_text', values.FN_NATIVE(([text, x, y, width]) => { ctx.fillText(text.value, x.value, y.value, width ? width.value : undefined); })],
-				['stroke_text', values.FN_NATIVE(([text, x, y, width]) => { ctx.strokeText(text.value, x.value, y.value, width ? width.value : undefined); })],
-				['set_line_width', values.FN_NATIVE(([width]) => { ctx.lineWidth = width.value; })],
-				['set_font', values.FN_NATIVE(([font]) => { ctx.font = font.value; })],
-				['set_fill_style', values.FN_NATIVE(([style]) => { ctx.fillStyle = style.value; })],
-				['set_stroke_style', values.FN_NATIVE(([style]) => { ctx.strokeStyle = style.value; })],
-				['begin_path', values.FN_NATIVE(() => { ctx.beginPath(); })],
-				['close_path', values.FN_NATIVE(() => { ctx.closePath(); })],
-				['move_to', values.FN_NATIVE(([x, y]) => { ctx.moveTo(x.value, y.value); })],
-				['line_to', values.FN_NATIVE(([x, y]) => { ctx.lineTo(x.value, y.value); })],
-				['arc', values.FN_NATIVE(([x, y, radius, startAngle, endAngle]) => { ctx.arc(x.value, y.value, radius.value, startAngle.value, endAngle.value); })],
-				['rect', values.FN_NATIVE(([x, y, width, height]) => { ctx.rect(x.value, y.value, width.value, height.value); })],
-				['fill', values.FN_NATIVE(() => { ctx.fill(); })],
-				['stroke', values.FN_NATIVE(() => { ctx.stroke(); })],
-			]));
-		}),
-		'MkPages:chart': values.FN_NATIVE(([id, opts]) => {
-			/* TODO
-			utils.assertString(id);
-			utils.assertObject(opts);
-			const canvas = hpml.canvases[id.value];
-			const color = getComputedStyle(document.documentElement).getPropertyValue('--accent');
-			Chart.defaults.color = '#555';
-			const chart = new Chart(canvas, {
-				type: opts.value.get('type').value,
-				data: {
-					labels: opts.value.get('labels').value.map(x => x.value),
-					datasets: opts.value.get('datasets').value.map(x => ({
-						label: x.value.has('label') ? x.value.get('label').value : '',
-						data: x.value.get('data').value.map(x => x.value),
-						pointRadius: 0,
-						lineTension: 0,
-						borderWidth: 2,
-						borderColor: x.value.has('color') ? x.value.get('color') : color,
-						backgroundColor: tinycolor(x.value.has('color') ? x.value.get('color') : color).setAlpha(0.1).toRgbString(),
-					}))
-				},
-				options: {
-					responsive: false,
-					devicePixelRatio: 1.5,
-					title: {
-						display: opts.value.has('title'),
-						text: opts.value.has('title') ? opts.value.get('title').value : '',
-						fontSize: 14,
-					},
-					layout: {
-						padding: {
-							left: 32,
-							right: 32,
-							top: opts.value.has('title') ? 16 : 32,
-							bottom: 16
-						}
-					},
-					legend: {
-						display: opts.value.get('datasets').value.filter(x => x.value.has('label') && x.value.get('label').value).length === 0 ? false : true,
-						position: 'bottom',
-						labels: {
-							boxWidth: 16,
-						}
-					},
-					tooltips: {
-						enabled: false,
-					},
-					chartArea: {
-						backgroundColor: '#fff'
-					},
-					...(opts.value.get('type').value === 'radar' ? {
-						scale: {
-							ticks: {
-								display: opts.value.has('show_tick_label') ? opts.value.get('show_tick_label').value : false,
-								min: opts.value.has('min') ? opts.value.get('min').value : undefined,
-								max: opts.value.has('max') ? opts.value.get('max').value : undefined,
-								maxTicksLimit: 8,
-							},
-							pointLabels: {
-								fontSize: 12
-							}
-						}
-					} : {
-						scales: {
-							yAxes: [{
-								ticks: {
-									display: opts.value.has('show_tick_label') ? opts.value.get('show_tick_label').value : true,
-									min: opts.value.has('min') ? opts.value.get('min').value : undefined,
-									max: opts.value.has('max') ? opts.value.get('max').value : undefined,
-								}
-							}]
-						}
-					})
-				}
-			});
-			*/
-		}),
-	};
-}
-
-export const funcDefs: Record<string, { in: any[]; out: any; category: string; icon: any; }> = {
-	if: { in: ['boolean', 0, 0], out: 0, category: 'flow', icon: 'ti ti-share' },
-	for: { in: ['number', 'function'], out: null, category: 'flow', icon: 'ti ti-recycle' },
-	not: { in: ['boolean'], out: 'boolean', category: 'logical', icon: 'ti ti-flag' },
-	or: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'ti ti-flag' },
-	and: { in: ['boolean', 'boolean'], out: 'boolean', category: 'logical', icon: 'ti ti-flag' },
-	add: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'ti ti-plus' },
-	subtract: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'ti ti-minus' },
-	multiply: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'ti ti-x' },
-	divide: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'ti ti-divide' },
-	mod: { in: ['number', 'number'], out: 'number', category: 'operation', icon: 'ti ti-divide' },
-	round: { in: ['number'], out: 'number', category: 'operation', icon: 'ti ti-calculator' },
-	eq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'ti ti-equal' },
-	notEq: { in: [0, 0], out: 'boolean', category: 'comparison', icon: 'ti ti-equal-not' },
-	gt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'ti ti-math-greater' },
-	lt: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'ti ti-math-lower' },
-	gtEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'ti ti-math-equal-greater' },
-	ltEq: { in: ['number', 'number'], out: 'boolean', category: 'comparison', icon: 'ti ti-math-equal-lower' },
-	strLen: { in: ['string'], out: 'number', category: 'text', icon: 'ti ti-quote' },
-	strPick: { in: ['string', 'number'], out: 'string', category: 'text', icon: 'ti ti-quote' },
-	strReplace: { in: ['string', 'string', 'string'], out: 'string', category: 'text', icon: 'ti ti-quote' },
-	strReverse: { in: ['string'], out: 'string', category: 'text', icon: 'ti ti-quote' },
-	join: { in: ['stringArray', 'string'], out: 'string', category: 'text', icon: 'ti ti-quote' },
-	stringToNumber: { in: ['string'], out: 'number', category: 'convert', icon: 'ti ti-arrows-right-left' },
-	numberToString: { in: ['number'], out: 'string', category: 'convert', icon: 'ti ti-arrows-right-left' },
-	splitStrByLine: { in: ['string'], out: 'stringArray', category: 'convert', icon: 'ti ti-arrows-right-left' },
-	pick: { in: [null, 'number'], out: null, category: 'list', icon: 'ti ti-indent-increase' },
-	listLen: { in: [null], out: 'number', category: 'list', icon: 'ti ti-indent-increase' },
-	rannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'ti ti-dice' },
-	dailyRannum: { in: ['number', 'number'], out: 'number', category: 'random', icon: 'ti ti-dice' },
-	seedRannum: { in: [null, 'number', 'number'], out: 'number', category: 'random', icon: 'ti ti-dice' },
-	random: { in: ['number'], out: 'boolean', category: 'random', icon: 'ti ti-dice' },
-	dailyRandom: { in: ['number'], out: 'boolean', category: 'random', icon: 'ti ti-dice' },
-	seedRandom: { in: [null, 'number'], out: 'boolean', category: 'random', icon: 'ti ti-dice' },
-	randomPick: { in: [0], out: 0, category: 'random', icon: 'ti ti-dice' },
-	dailyRandomPick: { in: [0], out: 0, category: 'random', icon: 'ti ti-dice' },
-	seedRandomPick: { in: [null, 0], out: 0, category: 'random', icon: 'ti ti-dice' },
-	DRPWPM: { in: ['stringArray'], out: 'string', category: 'random', icon: 'ti ti-dice' }, // dailyRandomPickWithProbabilityMapping
-};
-
-export function initHpmlLib(expr: Expr, scope: HpmlScope, randomSeed: string, visitor?: any) {
-	const date = new Date();
-	const day = `${visitor ? visitor.id : ''} ${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`;
-
-	// SHOULD be fine to ignore since it's intended + function shape isn't defined
-	// eslint-disable-next-line @typescript-eslint/ban-types
-	const funcs: Record<string, Function> = {
-		not: (a: boolean) => !a,
-		or: (a: boolean, b: boolean) => a || b,
-		and: (a: boolean, b: boolean) => a && b,
-		eq: (a: any, b: any) => a === b,
-		notEq: (a: any, b: any) => a !== b,
-		gt: (a: number, b: number) => a > b,
-		lt: (a: number, b: number) => a < b,
-		gtEq: (a: number, b: number) => a >= b,
-		ltEq: (a: number, b: number) => a <= b,
-		if: (bool: boolean, a: any, b: any) => bool ? a : b,
-		for: (times: number, fn: Fn) => {
-			const result: any[] = [];
-			for (let i = 0; i < times; i++) {
-				result.push(fn.exec({
-					[fn.slots[0]]: i + 1,
-				}));
-			}
-			return result;
-		},
-		add: (a: number, b: number) => a + b,
-		subtract: (a: number, b: number) => a - b,
-		multiply: (a: number, b: number) => a * b,
-		divide: (a: number, b: number) => a / b,
-		mod: (a: number, b: number) => a % b,
-		round: (a: number) => Math.round(a),
-		strLen: (a: string) => a.length,
-		strPick: (a: string, b: number) => a[b - 1],
-		strReplace: (a: string, b: string, c: string) => a.split(b).join(c),
-		strReverse: (a: string) => a.split('').reverse().join(''),
-		join: (texts: string[], separator: string) => texts.join(separator || ''),
-		stringToNumber: (a: string) => parseInt(a),
-		numberToString: (a: number) => a.toString(),
-		splitStrByLine: (a: string) => a.split('\n'),
-		pick: (list: any[], i: number) => list[i - 1],
-		listLen: (list: any[]) => list.length,
-		random: (probability: number) => Math.floor(seedrandom(`${randomSeed}:${expr.id}`)() * 100) < probability,
-		rannum: (min: number, max: number) => min + Math.floor(seedrandom(`${randomSeed}:${expr.id}`)() * (max - min + 1)),
-		randomPick: (list: any[]) => list[Math.floor(seedrandom(`${randomSeed}:${expr.id}`)() * list.length)],
-		dailyRandom: (probability: number) => Math.floor(seedrandom(`${day}:${expr.id}`)() * 100) < probability,
-		dailyRannum: (min: number, max: number) => min + Math.floor(seedrandom(`${day}:${expr.id}`)() * (max - min + 1)),
-		dailyRandomPick: (list: any[]) => list[Math.floor(seedrandom(`${day}:${expr.id}`)() * list.length)],
-		seedRandom: (seed: any, probability: number) => Math.floor(seedrandom(seed)() * 100) < probability,
-		seedRannum: (seed: any, min: number, max: number) => min + Math.floor(seedrandom(seed)() * (max - min + 1)),
-		seedRandomPick: (seed: any, list: any[]) => list[Math.floor(seedrandom(seed)() * list.length)],
-		DRPWPM: (list: string[]) => {
-			const xs: any[] = [];
-			let totalFactor = 0;
-			for (const x of list) {
-				const parts = x.split(' ');
-				const factor = parseInt(parts.pop()!, 10);
-				const text = parts.join(' ');
-				totalFactor += factor;
-				xs.push({ factor, text });
-			}
-			const r = seedrandom(`${day}:${expr.id}`)() * totalFactor;
-			let stackedFactor = 0;
-			for (const x of xs) {
-				if (r >= stackedFactor && r <= stackedFactor + x.factor) {
-					return x.text;
-				} else {
-					stackedFactor += x.factor;
-				}
-			}
-			return xs[0].text;
-		},
-	};
-
-	return funcs;
-}
diff --git a/packages/frontend/src/scripts/hpml/type-checker.ts b/packages/frontend/src/scripts/hpml/type-checker.ts
deleted file mode 100644
index ea8133f29..000000000
--- a/packages/frontend/src/scripts/hpml/type-checker.ts
+++ /dev/null
@@ -1,182 +0,0 @@
-import { isLiteralValue } from './expr';
-import { funcDefs } from './lib';
-import { envVarsDef } from '.';
-import type { Type, PageVar } from '.';
-import type { Expr, Variable } from './expr';
-
-type TypeError = {
-	arg: number;
-	expect: Type;
-	actual: Type;
-};
-
-/**
- * Hpml type checker
- */
-export class HpmlTypeChecker {
-	public variables: Variable[];
-	public pageVars: PageVar[];
-
-	constructor(variables: HpmlTypeChecker['variables'] = [], pageVars: HpmlTypeChecker['pageVars'] = []) {
-		this.variables = variables;
-		this.pageVars = pageVars;
-	}
-
-	public typeCheck(v: Expr): TypeError | null {
-		if (isLiteralValue(v)) return null;
-
-		const def = funcDefs[v.type || ''];
-		if (def == null) {
-			throw new Error('Unknown type: ' + v.type);
-		}
-
-		const generic: Type[] = [];
-
-		for (let i = 0; i < def.in.length; i++) {
-			const arg = def.in[i];
-			const type = this.infer(v.args[i]);
-			if (type === null) continue;
-
-			if (typeof arg === 'number') {
-				if (generic[arg] === undefined) {
-					generic[arg] = type;
-				} else if (type !== generic[arg]) {
-					return {
-						arg: i,
-						expect: generic[arg],
-						actual: type,
-					};
-				}
-			} else if (type !== arg) {
-				return {
-					arg: i,
-					expect: arg,
-					actual: type,
-				};
-			}
-		}
-
-		return null;
-	}
-
-	public getExpectedType(v: Expr, slot: number): Type {
-		const def = funcDefs[v.type ?? ''];
-		if (def == null) {
-			throw new Error('Unknown type: ' + v.type);
-		}
-
-		const generic: Type[] = [];
-
-		for (let i = 0; i < def.in.length; i++) {
-			const arg = def.in[i];
-			const type = this.infer(v.args[i]);
-			if (type === null) continue;
-
-			if (typeof arg === 'number') {
-				if (generic[arg] === undefined) {
-					generic[arg] = type;
-				}
-			}
-		}
-
-		if (typeof def.in[slot] === 'number') {
-			return generic[def.in[slot]] ?? null;
-		} else {
-			return def.in[slot];
-		}
-	}
-
-	public infer(v: Expr): Type {
-		if (v.type === null) return null;
-		if (v.type === 'text') return 'string';
-		if (v.type === 'multiLineText') return 'string';
-		if (v.type === 'textList') return 'stringArray';
-		if (v.type === 'number') return 'number';
-		if (v.type === 'ref') {
-			const variable = this.variables.find(va => va.name === v.value);
-			if (variable) {
-				return this.infer(variable);
-			}
-
-			const pageVar = this.pageVars.find(va => va.name === v.value);
-			if (pageVar) {
-				return pageVar.type;
-			}
-
-			const envVar = envVarsDef[v.value ?? ''];
-			if (envVar !== undefined) {
-				return envVar;
-			}
-
-			return null;
-		}
-		if (v.type === 'aiScriptVar') return null;
-		if (v.type === 'fn') return null; // todo
-		if (v.type.startsWith('fn:')) return null; // todo
-
-		const generic: Type[] = [];
-
-		const def = funcDefs[v.type];
-
-		for (let i = 0; i < def.in.length; i++) {
-			const arg = def.in[i];
-			if (typeof arg === 'number') {
-				const type = this.infer(v.args[i]);
-
-				if (generic[arg] === undefined) {
-					generic[arg] = type;
-				} else {
-					if (type !== generic[arg]) {
-						generic[arg] = null;
-					}
-				}
-			}
-		}
-
-		if (typeof def.out === 'number') {
-			return generic[def.out];
-		} else {
-			return def.out;
-		}
-	}
-
-	public getVarByName(name: string): Variable {
-		const v = this.variables.find(x => x.name === name);
-		if (v !== undefined) {
-			return v;
-		} else {
-			throw new Error(`No such variable '${name}'`);
-		}
-	}
-
-	public getVarsByType(type: Type): Variable[] {
-		if (type == null) return this.variables;
-		return this.variables.filter(x => (this.infer(x) === null) || (this.infer(x) === type));
-	}
-
-	public getEnvVarsByType(type: Type): string[] {
-		if (type == null) return Object.keys(envVarsDef);
-		return Object.entries(envVarsDef).filter(([k, v]) => v === null || type === v).map(([k, v]) => k);
-	}
-
-	public getPageVarsByType(type: Type): string[] {
-		if (type == null) return this.pageVars.map(v => v.name);
-		return this.pageVars.filter(v => type === v.type).map(v => v.name);
-	}
-
-	public isUsedName(name: string) {
-		if (this.variables.some(v => v.name === name)) {
-			return true;
-		}
-
-		if (this.pageVars.some(v => v.name === name)) {
-			return true;
-		}
-
-		if (envVarsDef[name]) {
-			return true;
-		}
-
-		return false;
-	}
-}
diff --git a/packages/frontend/src/scripts/idle-render.ts b/packages/frontend/src/scripts/idle-render.ts
new file mode 100644
index 000000000..ccce8b02b
--- /dev/null
+++ b/packages/frontend/src/scripts/idle-render.ts
@@ -0,0 +1,38 @@
+class IdlingRenderScheduler {
+	#renderers: Set<FrameRequestCallback>;
+	#rafId: number;
+	#ricId: number;
+
+	constructor() {
+		this.#renderers = new Set();
+		this.#rafId = 0;
+		this.#ricId = requestIdleCallback((deadline) => this.#schedule(deadline));
+	}
+
+	#schedule(deadline: IdleDeadline): void {
+		if (deadline.timeRemaining()) {
+			this.#rafId = requestAnimationFrame((time) => {
+				for (const renderer of this.#renderers) {
+					renderer(time);
+				}
+			});
+		}
+		this.#ricId = requestIdleCallback((arg) => this.#schedule(arg));
+	}
+
+	add(renderer: FrameRequestCallback): void {
+		this.#renderers.add(renderer);
+	}
+
+	delete(renderer: FrameRequestCallback): void {
+		this.#renderers.delete(renderer);
+	}
+
+	dispose(): void {
+		this.#renderers.clear();
+		cancelAnimationFrame(this.#rafId);
+		cancelIdleCallback(this.#ricId);
+	}
+}
+
+export const defaultIdlingRenderScheduler = new IdlingRenderScheduler();
diff --git a/packages/frontend/src/scripts/select-file.ts b/packages/frontend/src/scripts/select-file.ts
index fe9f0a244..44a58d6c7 100644
--- a/packages/frontend/src/scripts/select-file.ts
+++ b/packages/frontend/src/scripts/select-file.ts
@@ -1,7 +1,7 @@
 import { ref } from 'vue';
 import { DriveFile } from 'misskey-js/built/entities';
 import * as os from '@/os';
-import { stream } from '@/stream';
+import { useStream } from '@/stream';
 import { i18n } from '@/i18n';
 import { defaultStore } from '@/store';
 import { uploadFile } from '@/scripts/upload';
@@ -51,7 +51,7 @@ export function chooseFileFromUrl(): Promise<DriveFile> {
 
 			const marker = Math.random().toString(); // TODO: UUIDとか使う
 
-			const connection = stream.useChannel('main');
+			const connection = useStream().useChannel('main');
 			connection.on('urlUploadFinished', urlResponse => {
 				if (urlResponse.marker === marker) {
 					res(urlResponse.file);
diff --git a/packages/frontend/src/scripts/theme.ts b/packages/frontend/src/scripts/theme.ts
index 28284c7bc..f2e825356 100644
--- a/packages/frontend/src/scripts/theme.ts
+++ b/packages/frontend/src/scripts/theme.ts
@@ -60,7 +60,7 @@ export function applyTheme(theme: Theme, persist = true) {
 		document.documentElement.classList.remove('_themeChanging_');
 	}, 1000);
 
-	const colorSchema = theme.base === 'dark' ? 'dark' : 'light';
+	const colorScheme = theme.base === 'dark' ? 'dark' : 'light';
 
 	// Deep copy
 	const _theme = deepClone(theme);
@@ -83,11 +83,11 @@ export function applyTheme(theme: Theme, persist = true) {
 		document.documentElement.style.setProperty(`--${k}`, v.toString());
 	}
 
-	document.documentElement.style.setProperty('color-schema', colorSchema);
+	document.documentElement.style.setProperty('color-scheme', colorScheme);
 
 	if (persist) {
 		miLocalStorage.setItem('theme', JSON.stringify(props));
-		miLocalStorage.setItem('colorSchema', colorSchema);
+		miLocalStorage.setItem('colorScheme', colorScheme);
 	}
 
 	// 色計算など再度行えるようにクライアント全体に通知
diff --git a/packages/frontend/src/scripts/time.ts b/packages/frontend/src/scripts/time.ts
index 34e8b6b17..b21978b18 100644
--- a/packages/frontend/src/scripts/time.ts
+++ b/packages/frontend/src/scripts/time.ts
@@ -5,15 +5,16 @@ const dateTimeIntervals = {
 };
 
 export function dateUTC(time: number[]): Date {
-	const d = time.length === 2 ? Date.UTC(time[0], time[1])
-					: time.length === 3 ? Date.UTC(time[0], time[1], time[2])
-					: time.length === 4 ? Date.UTC(time[0], time[1], time[2], time[3])
-					: time.length === 5 ? Date.UTC(time[0], time[1], time[2], time[3], time[4])
-					: time.length === 6 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5])
-					: time.length === 7 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5], time[6])
-					: null;
+	const d =
+		time.length === 2 ? Date.UTC(time[0], time[1])
+		: time.length === 3 ? Date.UTC(time[0], time[1], time[2])
+		: time.length === 4 ? Date.UTC(time[0], time[1], time[2], time[3])
+		: time.length === 5 ? Date.UTC(time[0], time[1], time[2], time[3], time[4])
+		: time.length === 6 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5])
+		: time.length === 7 ? Date.UTC(time[0], time[1], time[2], time[3], time[4], time[5], time[6])
+		: null;
 
-	if (!d) throw 'wrong number of arguments';
+	if (!d) throw new Error('wrong number of arguments');
 
 	return new Date(d);
 }
diff --git a/packages/frontend/src/scripts/use-note-capture.ts b/packages/frontend/src/scripts/use-note-capture.ts
index ffe33cccc..22a01e066 100644
--- a/packages/frontend/src/scripts/use-note-capture.ts
+++ b/packages/frontend/src/scripts/use-note-capture.ts
@@ -1,6 +1,6 @@
 import { onUnmounted, Ref } from 'vue';
 import * as misskey from 'misskey-js';
-import { stream } from '@/stream';
+import { useStream } from '@/stream';
 import { $i } from '@/account';
 
 export function useNoteCapture(props: {
@@ -9,7 +9,7 @@ export function useNoteCapture(props: {
 	isDeletedRef: Ref<boolean>;
 }) {
 	const note = props.note;
-	const connection = $i ? stream : null;
+	const connection = $i ? useStream() : null;
 
 	function onStreamNoteUpdated(noteData): void {
 		const { type, id, body } = noteData;
diff --git a/packages/frontend/src/scripts/worker-multi-dispatch.ts b/packages/frontend/src/scripts/worker-multi-dispatch.ts
new file mode 100644
index 000000000..1847a8ccf
--- /dev/null
+++ b/packages/frontend/src/scripts/worker-multi-dispatch.ts
@@ -0,0 +1,75 @@
+function defaultUseWorkerNumber(prev: number, totalWorkers: number) {
+    return prev + 1;
+}
+
+export class WorkerMultiDispatch<POST = any, RETURN = any> {
+    private symbol = Symbol('WorkerMultiDispatch');
+    private workers: Worker[] = [];
+    private terminated = false;
+    private prevWorkerNumber = 0;
+    private getUseWorkerNumber = defaultUseWorkerNumber;
+    private finalizationRegistry: FinalizationRegistry<symbol>;
+
+    constructor(workerConstructor: () => Worker, concurrency: number, getUseWorkerNumber = defaultUseWorkerNumber) {
+        this.getUseWorkerNumber = getUseWorkerNumber;
+        for (let i = 0; i < concurrency; i++) {
+            this.workers.push(workerConstructor());
+        }
+
+        this.finalizationRegistry = new FinalizationRegistry(() => {
+            this.terminate();
+        });
+        this.finalizationRegistry.register(this, this.symbol);
+
+        if (_DEV_) console.log('WorkerMultiDispatch: Created', this);
+    }
+
+    public postMessage(message: POST, options?: Transferable[] | StructuredSerializeOptions, useWorkerNumber: typeof defaultUseWorkerNumber = 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);
+        this.prevWorkerNumber = workerNumber;
+
+        // 不毛だがunionをoverloadに突っ込めない
+        // https://stackoverflow.com/questions/66507585/overload-signatures-union-types-and-no-overload-matches-this-call-error
+        // https://github.com/microsoft/TypeScript/issues/14107
+        if (Array.isArray(options)) {
+            this.workers[workerNumber].postMessage(message, options);
+        } else {
+            this.workers[workerNumber].postMessage(message, options);
+        }
+        return workerNumber;
+    }
+
+    public addListener(callback: (this: Worker, ev: MessageEvent<RETURN>) => any, options?: boolean | AddEventListenerOptions) {
+        this.workers.forEach(worker => {
+            worker.addEventListener('message', callback, options);
+        });
+    }
+
+    public removeListener(callback: (this: Worker, ev: MessageEvent<RETURN>) => any, options?: boolean | AddEventListenerOptions) {
+        this.workers.forEach(worker => {
+            worker.removeEventListener('message', callback, options);
+        });
+    }
+
+    public terminate() {
+        this.terminated = true;
+        if (_DEV_) console.log('WorkerMultiDispatch: Terminating', this);
+        this.workers.forEach(worker => {
+            worker.terminate();
+        });
+        this.workers = [];
+        this.finalizationRegistry.unregister(this);
+    }
+
+    public isTerminated() {
+        return this.terminated;
+    }
+    public getWorkers() {
+        return this.workers;
+    }
+    public getSymbol() {
+        return this.symbol;
+    }
+}
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index 245bcbefe..6ba05c36a 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -92,7 +92,7 @@ export const defaultStore = markRaw(new Storage('base', {
 	},
 	reactionAcceptance: {
 		where: 'account',
-		default: null as 'likeOnly' | 'likeOnlyForRemote' | null,
+		default: 'nonSensitiveOnly' as 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote' | null,
 	},
 	mutedWords: {
 		where: 'account',
@@ -102,6 +102,10 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'account',
 		default: [] as string[],
 	},
+	showTimelineReplies: {
+		where: 'account',
+		default: false,
+	},
 
 	menu: {
 		where: 'deviceAccount',
@@ -314,6 +318,10 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'device',
 		default: false,
 	},
+	devMode: {
+		where: 'device',
+		default: false,
+	},
 	mediaListWithOneImageAppearance: {
 		where: 'device',
 		default: 'expand' as 'expand' | '16_9' | '1_1' | '2_3',
@@ -328,7 +336,11 @@ export const defaultStore = markRaw(new Storage('base', {
 	},
 	enableCondensedLineForAcct: {
 		where: 'device',
-		default: true,
+		default: false,
+	},
+	additionalUnicodeEmojiIndexes: {
+		where: 'device',
+		default: {} as Record<string, Record<string, string[]>>,
 	},
 }));
 
diff --git a/packages/frontend/src/stream.ts b/packages/frontend/src/stream.ts
index dea3459b8..9cae58a26 100644
--- a/packages/frontend/src/stream.ts
+++ b/packages/frontend/src/stream.ts
@@ -3,6 +3,14 @@ import { markRaw } from 'vue';
 import { $i } from '@/account';
 import { url } from '@/config';
 
-export const stream = markRaw(new Misskey.Stream(url, $i ? {
-	token: $i.token,
-} : null));
+let stream: Misskey.Stream | null = null;
+
+export function useStream(): Misskey.Stream {
+	if (stream) return stream;
+
+	stream = markRaw(new Misskey.Stream(url, $i ? {
+		token: $i.token,
+	} : null));
+
+	return stream;
+}
diff --git a/packages/frontend/src/style.scss b/packages/frontend/src/style.scss
index 20254d335..b376e4c42 100644
--- a/packages/frontend/src/style.scss
+++ b/packages/frontend/src/style.scss
@@ -22,11 +22,7 @@
 } 
 
 html {
-	touch-action: manipulation;
 	background-color: var(--bg);
-	background-attachment: fixed;
-	background-size: cover;
-	background-position: center;
 	color: var(--fg);
 	accent-color: var(--accent);
 	overflow: auto;
@@ -38,7 +34,7 @@ html {
 	tab-size: 2;
 
 	&, * {
-		scrollbar-color: var(--scrollbarHandle) inherit;
+		scrollbar-color: var(--scrollbarHandle) transparent;
 		scrollbar-width: thin;
 
 		&::-webkit-scrollbar {
@@ -87,6 +83,7 @@ html._themeChanging_ {
 }
 
 html, body {
+	touch-action: manipulation;
 	margin: 0;
 	padding: 0;
 	scroll-behavior: smooth;
@@ -483,3 +480,140 @@ hr {
     transform:  scaleX(1.00) scaleY(1.00) ;
   }
 }
+
+// MFM -----------------------------
+
+._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%;
+		}
+	}
+}
+
+@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%); }
+}
diff --git a/packages/frontend/src/themes/_dark.json5 b/packages/frontend/src/themes/_dark.json5
index a23d25e86..5ef6adb08 100644
--- a/packages/frontend/src/themes/_dark.json5
+++ b/packages/frontend/src/themes/_dark.json5
@@ -21,6 +21,7 @@
 		fgTransparent: ':alpha<0.5<@fg',
 		fgHighlighted: ':lighten<3<@fg',
 		fgOnAccent: '#fff',
+		fgOnWhite: '#333',
 		divider: 'rgba(255, 255, 255, 0.1)',
 		indicator: '@accent',
 		panel: ':lighten<3<@bg',
@@ -77,7 +78,7 @@
 		codeString: '#ffb675',
 		codeNumber: '#cfff9e',
 		codeBoolean: '#c59eff',
-		deckDivider: '#000',
+		deckBg: '#000',
 		htmlThemeColor: '@bg',
 		X2: ':darken<2<@panel',
 		X3: 'rgba(255, 255, 255, 0.05)',
diff --git a/packages/frontend/src/themes/_light.json5 b/packages/frontend/src/themes/_light.json5
index 713756221..32f3c7490 100644
--- a/packages/frontend/src/themes/_light.json5
+++ b/packages/frontend/src/themes/_light.json5
@@ -21,6 +21,7 @@
 		fgTransparent: ':alpha<0.5<@fg',
 		fgHighlighted: ':darken<3<@fg',
 		fgOnAccent: '#fff',
+		fgOnWhite: '#333',
 		divider: 'rgba(0, 0, 0, 0.1)',
 		indicator: '@accent',
 		panel: ':lighten<3<@bg',
@@ -77,7 +78,7 @@
 		codeString: '#b98710',
 		codeNumber: '#0fbbbb',
 		codeBoolean: '#62b70c',
-		deckDivider: ':darken<3<@bg',
+		deckBg: ':darken<3<@bg',
 		htmlThemeColor: '@bg',
 		X2: ':darken<2<@panel',
 		X3: 'rgba(0, 0, 0, 0.05)',
diff --git a/packages/frontend/src/themes/d-astro.json5 b/packages/frontend/src/themes/d-astro.json5
index c6a927ec3..09a9ead1a 100644
--- a/packages/frontend/src/themes/d-astro.json5
+++ b/packages/frontend/src/themes/d-astro.json5
@@ -53,6 +53,7 @@
 		panelHeaderBg: ':lighten<3<@panel',
 		panelHeaderFg: '@fg',
 		htmlThemeColor: '@bg',
+		fgOnWhite: '@accent',
 		panelHighlight: ':lighten<3<@panel',
 		listItemHoverBg: 'rgba(255, 255, 255, 0.03)',
 		scrollbarHandle: 'rgba(255, 255, 255, 0.2)',
diff --git a/packages/frontend/src/themes/d-botanical.json5 b/packages/frontend/src/themes/d-botanical.json5
index 33cf7aa81..62208d237 100644
--- a/packages/frontend/src/themes/d-botanical.json5
+++ b/packages/frontend/src/themes/d-botanical.json5
@@ -11,6 +11,7 @@
 		bg: 'rgb(37, 38, 36)',
 		fg: 'rgb(216, 212, 199)',
 		fgHighlighted: '#fff',
+		fgOnWhite: '@accent',
 		divider: 'rgba(255, 255, 255, 0.14)',
 		panel: 'rgb(47, 47, 44)',
 		panelHeaderDivider: 'rgba(0, 0, 0, 0)',
diff --git a/packages/frontend/src/themes/d-cherry.json5 b/packages/frontend/src/themes/d-cherry.json5
index a7e1ad1c8..f9638124c 100644
--- a/packages/frontend/src/themes/d-cherry.json5
+++ b/packages/frontend/src/themes/d-cherry.json5
@@ -10,6 +10,7 @@
 		accent: 'rgb(255, 89, 117)',
 		bg: 'rgb(28, 28, 37)',
 		fg: 'rgb(236, 239, 244)',
+		fgOnWhite: '@accent',
 		panel: 'rgb(35, 35, 47)',
 		renote: '@accent',
 		link: '@accent',
diff --git a/packages/frontend/src/themes/d-dark.json5 b/packages/frontend/src/themes/d-dark.json5
index 63144e88e..ae4f7d53f 100644
--- a/packages/frontend/src/themes/d-dark.json5
+++ b/packages/frontend/src/themes/d-dark.json5
@@ -11,6 +11,7 @@
 		bg: '#232323',
 		fg: 'rgb(199, 209, 216)',
 		fgHighlighted: '#fff',
+		fgOnWhite: '@accent',
 		divider: 'rgba(255, 255, 255, 0.14)',
 		panel: '#2d2d2d',
 		panelHeaderDivider: 'rgba(0, 0, 0, 0)',
diff --git a/packages/frontend/src/themes/d-future.json5 b/packages/frontend/src/themes/d-future.json5
index 0962a1241..f2c1f3eb8 100644
--- a/packages/frontend/src/themes/d-future.json5
+++ b/packages/frontend/src/themes/d-future.json5
@@ -12,6 +12,7 @@
 		fg: '#D5D5D6',
 		fgHighlighted: '#fff',
 		fgOnAccent: '#000',
+		fgOnWhite: '@accent',
 		divider: 'rgba(255, 255, 255, 0.1)',
 		panel: '#18181c',
 		panelHeaderDivider: 'rgba(0, 0, 0, 0)',
diff --git a/packages/frontend/src/themes/d-green-lime.json5 b/packages/frontend/src/themes/d-green-lime.json5
index 9522f534a..ca4e688fd 100644
--- a/packages/frontend/src/themes/d-green-lime.json5
+++ b/packages/frontend/src/themes/d-green-lime.json5
@@ -12,6 +12,7 @@
 		fg: '#dee7e4',
 		fgHighlighted: '#fff',
 		fgOnAccent: '#192320',
+		fgOnWhite: '@accent',
 		divider: '#e7fffb24',
 		panel: '#192320',
 		panelHeaderDivider: 'rgba(0, 0, 0, 0)',
diff --git a/packages/frontend/src/themes/d-green-orange.json5 b/packages/frontend/src/themes/d-green-orange.json5
index e542782c6..c2539816e 100644
--- a/packages/frontend/src/themes/d-green-orange.json5
+++ b/packages/frontend/src/themes/d-green-orange.json5
@@ -12,6 +12,7 @@
 		fg: '#dee7e4',
 		fgHighlighted: '#fff',
 		fgOnAccent: '#192320',
+		fgOnWhite: '@accent',
 		divider: '#e7fffb24',
 		panel: '#192320',
 		panelHeaderDivider: 'rgba(0, 0, 0, 0)',
diff --git a/packages/frontend/src/themes/d-ice.json5 b/packages/frontend/src/themes/d-ice.json5
index 179b060dc..b4abc0cac 100644
--- a/packages/frontend/src/themes/d-ice.json5
+++ b/packages/frontend/src/themes/d-ice.json5
@@ -8,6 +8,7 @@
 
 	props: {
 		accent: '#47BFE8',
+		fgOnWhite: '@accent',
 		bg: '#212526',
 	},
 }
diff --git a/packages/frontend/src/themes/d-persimmon.json5 b/packages/frontend/src/themes/d-persimmon.json5
index e36265ff1..0ab6523dd 100644
--- a/packages/frontend/src/themes/d-persimmon.json5
+++ b/packages/frontend/src/themes/d-persimmon.json5
@@ -11,6 +11,7 @@
 		bg: 'rgb(31, 33, 31)',
 		fg: '#cdd8c7',
 		fgHighlighted: '#fff',
+		fgOnWhite: '@accent',
 		divider: 'rgba(255, 255, 255, 0.14)',
 		panel: 'rgb(41, 43, 41)',
 		infoFg: '@fg',
diff --git a/packages/frontend/src/themes/d-u0.json5 b/packages/frontend/src/themes/d-u0.json5
index b270f809a..ed776746a 100644
--- a/packages/frontend/src/themes/d-u0.json5
+++ b/packages/frontend/src/themes/d-u0.json5
@@ -55,6 +55,7 @@
 		codeNumber: '#cfff9e',
 		codeString: '#ffb675',
 		fgOnAccent: '#fff',
+		fgOnWhite: '@accent',
 		infoWarnBg: '#42321c',
 		infoWarnFg: '#ffbd3e',
 		navHoverFg: ':lighten<17<@fg',
@@ -83,6 +84,6 @@
 		fgTransparentWeak: ':alpha<0.75<@fg',
 		panelHeaderDivider: 'rgba(0, 0, 0, 0)',
 		scrollbarHandleHover: 'rgba(255, 255, 255, 0.4)',
-		deckDivider: '#142022',
+		deckBg: '#142022',
 	},
 }
diff --git a/packages/frontend/src/themes/l-apricot.json5 b/packages/frontend/src/themes/l-apricot.json5
index 1ed552557..fe1f9f892 100644
--- a/packages/frontend/src/themes/l-apricot.json5
+++ b/packages/frontend/src/themes/l-apricot.json5
@@ -10,6 +10,7 @@
 		accent: 'rgb(234, 154, 82)',
 		bg: '#e6e5e2',
 		fg: 'rgb(149, 143, 139)',
+		fgOnWhite: '@accent',
 		panel: '#EEECE8',
 		renote: '@accent',
 		link: '@accent',
diff --git a/packages/frontend/src/themes/l-botanical.json5 b/packages/frontend/src/themes/l-botanical.json5
index 2ea9a7d6c..5c9892789 100644
--- a/packages/frontend/src/themes/l-botanical.json5
+++ b/packages/frontend/src/themes/l-botanical.json5
@@ -11,6 +11,7 @@
 		bg: 'e2deda',
 		fg: '#3d3d3d',
 		fgHighlighted: '#6bc9a0',
+		fgOnWhite: '@accent',
 		divider: '#cfcfcf',
 		panel: '@X14',
 		panelHeaderBg: '@panel',
diff --git a/packages/frontend/src/themes/l-cherry.json5 b/packages/frontend/src/themes/l-cherry.json5
index 5ad240241..1189a28fe 100644
--- a/packages/frontend/src/themes/l-cherry.json5
+++ b/packages/frontend/src/themes/l-cherry.json5
@@ -10,6 +10,7 @@
 		accent: 'rgb(219, 96, 114)',
 		bg: 'rgb(254, 248, 249)',
 		fg: 'rgb(152, 13, 26)',
+		fgOnWhite: '@accent',
 		panel: 'rgb(255, 255, 255)',
 		renote: '@accent',
 		link: 'rgb(156, 187, 5)',
diff --git a/packages/frontend/src/themes/l-coffee.json5 b/packages/frontend/src/themes/l-coffee.json5
index fbcd4fa9e..b64cc7358 100644
--- a/packages/frontend/src/themes/l-coffee.json5
+++ b/packages/frontend/src/themes/l-coffee.json5
@@ -10,6 +10,7 @@
 		accent: '#9f8989',
 		bg: '#f5f3f3',
 		fg: '#7f6666',
+		fgOnWhite: '@accent',
 		panel: '#fff',
 		divider: 'rgba(87, 68, 68, 0.1)',
 		renote: 'rgb(160, 172, 125)',
diff --git a/packages/frontend/src/themes/l-light.json5 b/packages/frontend/src/themes/l-light.json5
index 248355c94..63c2e6d27 100644
--- a/packages/frontend/src/themes/l-light.json5
+++ b/packages/frontend/src/themes/l-light.json5
@@ -10,6 +10,7 @@
 	props: {
 		bg: '#f9f9f9',
 		fg: '#676767',
+		fgOnWhite: '@accent',
 		divider: '#e8e8e8',
 		header: ':alpha<0.7<@panel',
 		navBg: '#fff',
diff --git a/packages/frontend/src/themes/l-rainy.json5 b/packages/frontend/src/themes/l-rainy.json5
index 283dd74c6..e7d1d5af0 100644
--- a/packages/frontend/src/themes/l-rainy.json5
+++ b/packages/frontend/src/themes/l-rainy.json5
@@ -10,6 +10,7 @@
 		accent: '#5db0da',
 		bg: 'rgb(246 248 249)',
 		fg: '#636b71',
+		fgOnWhite: '@accent',
 		panel: '#fff',
 		divider: 'rgb(230 233 234)',
 		panelHeaderDivider: '@divider',
diff --git a/packages/frontend/src/themes/l-sushi.json5 b/packages/frontend/src/themes/l-sushi.json5
index 5846927d6..e787d6373 100644
--- a/packages/frontend/src/themes/l-sushi.json5
+++ b/packages/frontend/src/themes/l-sushi.json5
@@ -10,6 +10,7 @@
 		accent: '#e36749',
 		bg: '#f0eee9',
 		fg: '#5f5f5f',
+		fgOnWhite: '@accent',
 		renote: '@accent',
 		link: '@accent',
 		mention: '@accent',
diff --git a/packages/frontend/src/themes/l-u0.json5 b/packages/frontend/src/themes/l-u0.json5
index 03b114ba3..b77b15e3f 100644
--- a/packages/frontend/src/themes/l-u0.json5
+++ b/packages/frontend/src/themes/l-u0.json5
@@ -55,6 +55,7 @@
 		codeNumber: '#cfff9e',
 		codeString: '#ffb675',
 		fgOnAccent: '#fff',
+		fgOnWhite: '@accent',
 		infoWarnBg: '#42321c',
 		infoWarnFg: '#ffbd3e',
 		navHoverFg: ':lighten<17<@fg',
diff --git a/packages/frontend/src/themes/l-vivid.json5 b/packages/frontend/src/themes/l-vivid.json5
index b3c08f38a..822ef948d 100644
--- a/packages/frontend/src/themes/l-vivid.json5
+++ b/packages/frontend/src/themes/l-vivid.json5
@@ -52,6 +52,7 @@
 		driveFolderBg: ':alpha<0.3<@accent',
 		fgHighlighted: ':darken<3<@fg',
 		fgTransparent: ':alpha<0.5<@fg',
+		fgOnWhite: '@accent',
 		panelHeaderBg: ':lighten<3<@panel',
 		panelHeaderFg: '@fg',
 		htmlThemeColor: '@bg',
diff --git a/packages/frontend/src/ui/_common_/common.vue b/packages/frontend/src/ui/_common_/common.vue
index 71a4285e9..3b970eefb 100644
--- a/packages/frontend/src/ui/_common_/common.vue
+++ b/packages/frontend/src/ui/_common_/common.vue
@@ -10,12 +10,20 @@
 <XUpload v-if="uploads.length > 0"/>
 
 <TransitionGroup
-	tag="div" :class="[$style.notifications, $style[`notificationsPosition-${defaultStore.state.notificationPosition}`], $style[`notificationsStackAxis-${defaultStore.state.notificationStackAxis}`]]"
-	:move-class="defaultStore.state.animation ? $style.transition_notification_move : ''"
-	:enter-active-class="defaultStore.state.animation ? $style.transition_notification_enterActive : ''"
-	:leave-active-class="defaultStore.state.animation ? $style.transition_notification_leaveActive : ''"
-	:enter-from-class="defaultStore.state.animation ? $style.transition_notification_enterFrom : ''"
-	:leave-to-class="defaultStore.state.animation ? $style.transition_notification_leaveTo : ''"
+	tag="div"
+	:class="[$style.notifications, {
+		[$style.notificationsPosition_leftTop]: defaultStore.state.notificationPosition === 'leftTop',
+		[$style.notificationsPosition_leftBottom]: defaultStore.state.notificationPosition === 'leftBottom',
+		[$style.notificationsPosition_rightTop]: defaultStore.state.notificationPosition === 'rightTop',
+		[$style.notificationsPosition_rightBottom]: defaultStore.state.notificationPosition === 'rightBottom',
+		[$style.notificationsStackAxis_vertical]: defaultStore.state.notificationStackAxis === 'vertical',
+		[$style.notificationsStackAxis_horizontal]: defaultStore.state.notificationStackAxis === 'horizontal',
+	}]"
+	:moveClass="defaultStore.state.animation ? $style.transition_notification_move : ''"
+	:enterActiveClass="defaultStore.state.animation ? $style.transition_notification_enterActive : ''"
+	:leaveActiveClass="defaultStore.state.animation ? $style.transition_notification_leaveActive : ''"
+	: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">
 		<XNotification :notification="notification"/>
@@ -40,7 +48,7 @@ import { popups, pendingApiRequestsCount } from '@/os';
 import { uploads } from '@/scripts/upload';
 import * as sound from '@/scripts/sound';
 import { $i } from '@/account';
-import { stream } from '@/stream';
+import { useStream } from '@/stream';
 import { i18n } from '@/i18n';
 import { defaultStore } from '@/store';
 
@@ -55,7 +63,7 @@ function onNotification(notification) {
 	if ($i.mutingNotificationTypes.includes(notification.type)) return;
 
 	if (document.visibilityState === 'visible') {
-		stream.send('readNotification');
+		useStream().send('readNotification');
 
 		notifications.unshift(notification);
 		window.setTimeout(() => {
@@ -71,7 +79,7 @@ function onNotification(notification) {
 }
 
 if ($i) {
-	const connection = stream.useChannel('main', null, 'UI');
+	const connection = useStream().useChannel('main', null, 'UI');
 	connection.on('notification', onNotification);
 
 	//#region Listen message from SW
@@ -103,31 +111,31 @@ if ($i) {
 	pointer-events: none;
 	display: flex;
 
-	&.notificationsPosition-leftTop {
+	&.notificationsPosition_leftTop {
 		top: var(--margin);
 		left: 0;
 	}
 
-	&.notificationsPosition-rightTop {
+	&.notificationsPosition_rightTop {
 		top: var(--margin);
 		right: 0;
 	}
 
-	&.notificationsPosition-leftBottom {
+	&.notificationsPosition_leftBottom {
 		bottom: calc(var(--minBottomSpacing) + var(--margin));
 		left: 0;
 	}
 
-	&.notificationsPosition-rightBottom {
+	&.notificationsPosition_rightBottom {
 		bottom: calc(var(--minBottomSpacing) + var(--margin));
 		right: 0;
 	}
 
-	&.notificationsStackAxis-vertical {
+	&.notificationsStackAxis_vertical {
 		width: 250px;
 
-		&.notificationsPosition-leftTop,
-		&.notificationsPosition-rightTop {
+		&.notificationsPosition_leftTop,
+		&.notificationsPosition_rightTop {
 			flex-direction: column;
 
 			.notification {
@@ -137,8 +145,8 @@ if ($i) {
 			}
 		}
 
-		&.notificationsPosition-leftBottom,
-		&.notificationsPosition-rightBottom {
+		&.notificationsPosition_leftBottom,
+		&.notificationsPosition_rightBottom {
 			flex-direction: column-reverse;
 
 			.notification {
@@ -149,11 +157,11 @@ if ($i) {
 		}
 	}
 
-	&.notificationsStackAxis-horizontal {
+	&.notificationsStackAxis_horizontal {
 		width: 100%;
 
-		&.notificationsPosition-leftTop,
-		&.notificationsPosition-leftBottom {
+		&.notificationsPosition_leftTop,
+		&.notificationsPosition_leftBottom {
 			flex-direction: row;
 
 			.notification {
@@ -163,8 +171,8 @@ if ($i) {
 			}
 		}
 
-		&.notificationsPosition-rightTop,
-		&.notificationsPosition-rightBottom {
+		&.notificationsPosition_rightTop,
+		&.notificationsPosition_rightBottom {
 			flex-direction: row-reverse;
 
 			.notification {
diff --git a/packages/frontend/src/ui/_common_/navbar-for-mobile.vue b/packages/frontend/src/ui/_common_/navbar-for-mobile.vue
index 7a94a0c3e..365486a0a 100644
--- a/packages/frontend/src/ui/_common_/navbar-for-mobile.vue
+++ b/packages/frontend/src/ui/_common_/navbar-for-mobile.vue
@@ -1,43 +1,41 @@
 <template>
-<div class="kmwsukvl">
-	<div class="body">
-		<div class="top">
-			<div class="banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }"></div>
-			<button v-click-anime class="item _button instance" @click="openInstanceMenu">
-				<img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" class="icon"/>
-			</button>
-		</div>
-		<div class="middle">
-			<MkA v-click-anime class="item index" active-class="active" to="/" exact>
-				<i class="icon ti ti-home ti-fw"></i><span class="text">{{ i18n.ts.timeline }}</span>
-			</MkA>
-			<template v-for="item in menu">
-				<div v-if="item === '-'" class="divider"></div>
-				<component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime class="item _button" :class="[item, { active: navbarItemDef[item].active }]" active-class="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}">
-					<i class="icon ti-fw" :class="navbarItemDef[item].icon"></i><span class="text">{{ navbarItemDef[item].title }}</span>
-					<span v-if="navbarItemDef[item].indicated" class="indicator"><i class="icon _indicatorCircle"></i></span>
-				</component>
-			</template>
-			<div class="divider"></div>
-			<MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime class="item" active-class="active" to="/admin">
-				<i class="icon ti ti-dashboard ti-fw"></i><span class="text">{{ i18n.ts.controlPanel }}</span>
-			</MkA>
-			<button v-click-anime class="item _button" @click="more">
-				<i class="icon ti ti-grid-dots ti-fw"></i><span class="text">{{ i18n.ts.more }}</span>
-				<span v-if="otherMenuItemIndicated" class="indicator"><i class="icon _indicatorCircle"></i></span>
-			</button>
-			<MkA v-click-anime class="item" active-class="active" to="/settings">
-				<i class="icon ti ti-settings ti-fw"></i><span class="text">{{ i18n.ts.settings }}</span>
-			</MkA>
-		</div>
-		<div class="bottom">
-			<button class="item _button post" data-cy-open-post-form @click="os.post">
-				<i class="icon ti ti-pencil ti-fw"></i><span class="text">{{ i18n.ts.note }}</span>
-			</button>
-			<button v-click-anime class="item _button account" @click="openAccountMenu">
-				<MkAvatar :user="$i" class="avatar"/><MkAcct class="text _nowrap" :user="$i"/>
-			</button>
-		</div>
+<div :class="$style.root">
+	<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"/>
+		</button>
+	</div>
+	<div :class="$style.middle">
+		<MkA :class="$style.item" :activeClass="$style.active" to="/" exact>
+			<i :class="$style.itemIcon" class="ti ti-home ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.timeline }}</span>
+		</MkA>
+		<template v-for="item in menu">
+			<div v-if="item === '-'" :class="$style.divider"></div>
+			<component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" class="_button" :class="[$style.item, { [$style.active]: navbarItemDef[item].active }]" :activeClass="$style.active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}">
+				<i class="ti-fw" :class="[$style.itemIcon, navbarItemDef[item].icon]"></i><span :class="$style.itemText">{{ navbarItemDef[item].title }}</span>
+				<span v-if="navbarItemDef[item].indicated" :class="$style.itemIndicator"><i class="_indicatorCircle"></i></span>
+			</component>
+		</template>
+		<div :class="$style.divider"></div>
+		<MkA v-if="$i.isAdmin || $i.isModerator" :class="$style.item" :activeClass="$style.active" to="/admin">
+			<i :class="$style.itemIcon" class="ti ti-dashboard ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.controlPanel }}</span>
+		</MkA>
+		<button :class="$style.item" class="_button" @click="more">
+			<i :class="$style.itemIcon" class="ti ti-grid-dots ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.more }}</span>
+			<span v-if="otherMenuItemIndicated" :class="$style.itemIndicator"><i class="_indicatorCircle"></i></span>
+		</button>
+		<MkA :class="$style.item" :activeClass="$style.active" to="/settings">
+			<i :class="$style.itemIcon" class="ti ti-settings ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.settings }}</span>
+		</MkA>
+	</div>
+	<div :class="$style.bottom">
+		<button class="_button" :class="$style.post" data-cy-open-post-form @click="os.post">
+			<i :class="$style.postIcon" class="ti ti-pencil ti-fw"></i><span style="position: relative;">{{ i18n.ts.note }}</span>
+		</button>
+		<button class="_button" :class="$style.account" @click="openAccountMenu">
+			<MkAvatar :user="$i" :class="$style.avatar"/><MkAcct :class="$style.acct" class="_nowrap" :user="$i"/>
+		</button>
 	</div>
 </div>
 </template>
@@ -73,192 +71,186 @@ function more() {
 }
 </script>
 
-<style lang="scss" scoped>
-.kmwsukvl {
-	> .body {
-		display: flex;
-		flex-direction: column;
+<style lang="scss" module>
+.root {
+	display: flex;
+	flex-direction: column;
+}
 
-		> .top {
-			position: sticky;
-			top: 0;
-			z-index: 1;
-			padding: 20px 0;
-			background: var(--X14);
-			-webkit-backdrop-filter: var(--blur, blur(8px));
-			backdrop-filter: var(--blur, blur(8px));
+.top {
+	position: sticky;
+	top: 0;
+	z-index: 1;
+	padding: 20px 0;
+	background: var(--X14);
+	-webkit-backdrop-filter: var(--blur, blur(8px));
+	backdrop-filter: var(--blur, blur(8px));
+}
 
-			> .banner {
-				position: absolute;
-				top: 0;
-				left: 0;
-				width: 100%;
-				height: 100%;
-				background-size: cover;
-				background-position: center center;
-				-webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%);
-				mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%);
-			}
+.banner {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	background-size: cover;
+	background-position: center center;
+	-webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%);
+	mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%);
+}
 
-			> .instance {
-				position: relative;
-				display: block;
-				text-align: center;
-				width: 100%;
+.instance {
+	position: relative;
+	display: block;
+	text-align: center;
+	width: 100%;
+}
 
-				> .icon {
-					display: inline-block;
-					width: 38px;
-					aspect-ratio: 1;
-				}
-			}
-		}
+.instanceIcon {
+	display: inline-block;
+	width: 38px;
+	aspect-ratio: 1;
+}
 
-		> .bottom {
-			position: sticky;
-			bottom: 0;
-			padding: 20px 0;
-			background: var(--X14);
-			-webkit-backdrop-filter: var(--blur, blur(8px));
-			backdrop-filter: var(--blur, blur(8px));
+.bottom {
+	position: sticky;
+	bottom: 0;
+	padding: 20px 0;
+	background: var(--X14);
+	-webkit-backdrop-filter: var(--blur, blur(8px));
+	backdrop-filter: var(--blur, blur(8px));
+}
 
-			> .post {
-				position: relative;
-				display: block;
-				width: 100%;
-				height: 40px;
-				color: var(--fgOnAccent);
-				font-weight: bold;
-				text-align: left;
+.post {
+	position: relative;
+	display: block;
+	width: 100%;
+	height: 40px;
+	color: var(--fgOnAccent);
+	font-weight: bold;
+	text-align: left;
 
-				&:before {
-					content: "";
-					display: block;
-					width: calc(100% - 38px);
-					height: 100%;
-					margin: auto;
-					position: absolute;
-					top: 0;
-					left: 0;
-					right: 0;
-					bottom: 0;
-					border-radius: 999px;
-					background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
-				}
+	&:before {
+		content: "";
+		display: block;
+		width: calc(100% - 38px);
+		height: 100%;
+		margin: auto;
+		position: absolute;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		border-radius: 999px;
+		background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
+	}
 
-				&:hover, &.active {
-					&:before {
-						background: var(--accentLighten);
-					}
-				}
-
-				> .icon {
-					position: relative;
-					margin-left: 30px;
-					margin-right: 8px;
-					width: 32px;
-				}
-
-				> .text {
-					position: relative;
-				}
-			}
-
-			> .account {
-				position: relative;
-				display: flex;
-				align-items: center;
-				padding-left: 30px;
-				width: 100%;
-				text-align: left;
-				box-sizing: border-box;
-				margin-top: 16px;
-
-				> .avatar {
-					display: block;
-					flex-shrink: 0;
-					position: relative;
-					width: 32px;
-					aspect-ratio: 1;
-					margin-right: 8px;
-				}
-
-				> .text {
-					display: block;
-					flex-shrink: 1;
-					padding-right: 8px;
-				}
-			}
-		}
-
-		> .middle {
-			flex: 1;
-
-			> .divider {
-				margin: 16px 16px;
-				border-top: solid 0.5px var(--divider);
-			}
-
-			> .item {
-				position: relative;
-				display: block;
-				padding-left: 24px;
-				line-height: 2.85rem;
-				text-overflow: ellipsis;
-				overflow: hidden;
-				white-space: nowrap;
-				width: 100%;
-				text-align: left;
-				box-sizing: border-box;
-				color: var(--navFg);
-
-				> .icon {
-					position: relative;
-					width: 32px;
-					margin-right: 8px;
-				}
-
-				> .indicator {
-					position: absolute;
-					top: 0;
-					left: 20px;
-					color: var(--navIndicator);
-					font-size: 8px;
-					animation: blink 1s infinite;
-				}
-
-				> .text {
-					position: relative;
-					font-size: 0.9em;
-				}
-
-				&:hover {
-					text-decoration: none;
-					color: var(--navHoverFg);
-				}
-
-				&.active {
-					color: var(--navActive);
-				}
-
-				&:hover, &.active {
-					&:before {
-						content: "";
-						display: block;
-						width: calc(100% - 24px);
-						height: 100%;
-						margin: auto;
-						position: absolute;
-						top: 0;
-						left: 0;
-						right: 0;
-						bottom: 0;
-						border-radius: 999px;
-						background: var(--accentedBg);
-					}
-				}
-			}
+	&:hover, &.active {
+		&:before {
+			background: var(--accentLighten);
 		}
 	}
 }
+
+.postIcon {
+	position: relative;
+	margin-left: 30px;
+	margin-right: 8px;
+	width: 32px;
+}
+
+.account {
+	position: relative;
+	display: flex;
+	align-items: center;
+	padding-left: 30px;
+	width: 100%;
+	text-align: left;
+	box-sizing: border-box;
+	margin-top: 16px;
+}
+
+.avatar {
+	display: block;
+	flex-shrink: 0;
+	position: relative;
+	width: 32px;
+	aspect-ratio: 1;
+	margin-right: 8px;
+}
+
+.acct {
+	display: block;
+	flex-shrink: 1;
+	padding-right: 8px;
+}
+
+.middle {
+	flex: 1;
+}
+
+.divider {
+	margin: 16px 16px;
+	border-top: solid 0.5px var(--divider);
+}
+
+.item {
+	position: relative;
+	display: block;
+	padding-left: 24px;
+	line-height: 2.85rem;
+	text-overflow: ellipsis;
+	overflow: hidden;
+	white-space: nowrap;
+	width: 100%;
+	text-align: left;
+	box-sizing: border-box;
+	color: var(--navFg);
+
+	&:hover {
+		text-decoration: none;
+		color: var(--navHoverFg);
+	}
+
+	&.active {
+		color: var(--navActive);
+	}
+
+	&:hover, &.active {
+		&:before {
+			content: "";
+			display: block;
+			width: calc(100% - 24px);
+			height: 100%;
+			margin: auto;
+			position: absolute;
+			top: 0;
+			left: 0;
+			right: 0;
+			bottom: 0;
+			border-radius: 999px;
+			background: var(--accentedBg);
+		}
+	}
+}
+
+.itemIcon {
+	position: relative;
+	width: 32px;
+	margin-right: 8px;
+}
+
+.itemIndicator {
+	position: absolute;
+	top: 0;
+	left: 20px;
+	color: var(--navIndicator);
+	font-size: 8px;
+	animation: blink 1s infinite;
+}
+
+.itemText {
+	position: relative;
+	font-size: 0.9em;
+}
 </style>
diff --git a/packages/frontend/src/ui/_common_/navbar.vue b/packages/frontend/src/ui/_common_/navbar.vue
index 3b4b16142..a184f1d2f 100644
--- a/packages/frontend/src/ui/_common_/navbar.vue
+++ b/packages/frontend/src/ui/_common_/navbar.vue
@@ -1,51 +1,50 @@
 <template>
-<div class="mvcprjjd" :class="{ iconOnly }">
-	<div class="body">
-		<div class="top">
-			<div class="banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }"></div>
-			<button v-click-anime v-tooltip.noDelay.right="instance.name ?? i18n.ts.instance" class="item _button instance" @click="openInstanceMenu">
-				<img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" class="icon"/>
+<div :class="[$style.root, { [$style.iconOnly]: iconOnly }]">
+	<div :class="$style.body">
+		<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"/>
 			</button>
 		</div>
-		<div class="middle">
-			<MkA v-click-anime v-tooltip.noDelay.right="i18n.ts.timeline" class="item index" active-class="active" to="/" exact>
-				<i class="icon ti ti-home ti-fw"></i><span class="text">{{ i18n.ts.timeline }}</span>
+		<div :class="$style.middle">
+			<MkA v-tooltip.noDelay.right="i18n.ts.timeline" :class="$style.item" :activeClass="$style.active" to="/" exact>
+				<i :class="$style.itemIcon" class="ti ti-home ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.timeline }}</span>
 			</MkA>
 			<template v-for="item in menu">
-				<div v-if="item === '-'" class="divider"></div>
+				<div v-if="item === '-'" :class="$style.divider"></div>
 				<component
 					:is="navbarItemDef[item].to ? 'MkA' : 'button'"
 					v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)"
-					v-click-anime
 					v-tooltip.noDelay.right="navbarItemDef[item].title"
-					class="item _button"
-					:class="[item, { active: navbarItemDef[item].active }]"
-					active-class="active"
+					class="_button"
+					:class="[$style.item, { [$style.active]: navbarItemDef[item].active }]"
+					:activeClass="$style.active"
 					:to="navbarItemDef[item].to"
 					v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}"
 				>
-					<i class="icon ti-fw" :class="navbarItemDef[item].icon"></i><span class="text">{{ navbarItemDef[item].title }}</span>
-					<span v-if="navbarItemDef[item].indicated" class="indicator"><i class="icon _indicatorCircle"></i></span>
+					<i class="ti-fw" :class="[$style.itemIcon, navbarItemDef[item].icon]"></i><span :class="$style.itemText">{{ navbarItemDef[item].title }}</span>
+					<span v-if="navbarItemDef[item].indicated" :class="$style.itemIndicator"><i class="_indicatorCircle"></i></span>
 				</component>
 			</template>
-			<div class="divider"></div>
-			<MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime v-tooltip.noDelay.right="i18n.ts.controlPanel" class="item" active-class="active" to="/admin">
-				<i class="icon ti ti-dashboard ti-fw"></i><span class="text">{{ i18n.ts.controlPanel }}</span>
+			<div :class="$style.divider"></div>
+			<MkA v-if="$i.isAdmin || $i.isModerator" v-tooltip.noDelay.right="i18n.ts.controlPanel" :class="$style.item" :activeClass="$style.active" to="/admin">
+				<i :class="$style.itemIcon" class="ti ti-dashboard ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.controlPanel }}</span>
 			</MkA>
-			<button v-click-anime class="item _button" @click="more">
-				<i class="icon ti ti-grid-dots ti-fw"></i><span class="text">{{ i18n.ts.more }}</span>
-				<span v-if="otherMenuItemIndicated" class="indicator"><i class="icon _indicatorCircle"></i></span>
+			<button class="_button" :class="$style.item" @click="more">
+				<i :class="$style.itemIcon" class="ti ti-grid-dots ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.more }}</span>
+				<span v-if="otherMenuItemIndicated" :class="$style.itemIndicator"><i class="_indicatorCircle"></i></span>
 			</button>
-			<MkA v-click-anime v-tooltip.noDelay.right="i18n.ts.settings" class="item" active-class="active" to="/settings">
-				<i class="icon ti ti-settings ti-fw"></i><span class="text">{{ i18n.ts.settings }}</span>
+			<MkA v-tooltip.noDelay.right="i18n.ts.settings" :class="$style.item" :activeClass="$style.active" to="/settings">
+				<i :class="$style.itemIcon" class="ti ti-settings ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.settings }}</span>
 			</MkA>
 		</div>
-		<div class="bottom">
-			<button v-tooltip.noDelay.right="i18n.ts.note" class="item _button post" data-cy-open-post-form @click="os.post">
-				<i class="icon ti ti-pencil ti-fw"></i><span class="text">{{ i18n.ts.note }}</span>
+		<div :class="$style.bottom">
+			<button v-tooltip.noDelay.right="i18n.ts.note" class="_button" :class="[$style.post]" data-cy-open-post-form @click="os.post">
+				<i class="ti ti-pencil ti-fw" :class="$style.postIcon"></i><span :class="$style.postText">{{ i18n.ts.note }}</span>
 			</button>
-			<button v-click-anime v-tooltip.noDelay.right="`${i18n.ts.account}: @${$i.username}`" class="item _button account" @click="openAccountMenu">
-				<MkAvatar :user="$i" class="avatar"/><MkAcct class="text _nowrap" :user="$i"/>
+			<button v-tooltip.noDelay.right="`${i18n.ts.account}: @${$i.username}`" class="_button" :class="[$style.account]" @click="openAccountMenu">
+				<MkAvatar :user="$i" :class="$style.avatar"/><MkAcct class="_nowrap" :class="$style.acct" :user="$i"/>
 			</button>
 		</div>
 	</div>
@@ -99,374 +98,376 @@ function more(ev: MouseEvent) {
 }
 </script>
 
-<style lang="scss" scoped>
-.mvcprjjd {
-	$nav-width: 250px;
-	$nav-icon-only-width: 80px;
+<style lang="scss" module>
+.root {
+	--nav-width: 250px;
+	--nav-icon-only-width: 72px;
 
-	flex: 0 0 $nav-width;
-	width: $nav-width;
+	flex: 0 0 var(--nav-width);
+	width: var(--nav-width);
 	box-sizing: border-box;
+}
 
-	> .body {
-		position: fixed;
+.body {
+	position: fixed;
+	top: 0;
+	left: 0;
+	z-index: 1001;
+	width: var(--nav-icon-only-width);
+	height: 100dvh;
+	box-sizing: border-box;
+	overflow: auto;
+	overflow-x: clip;
+	overscroll-behavior: contain;
+	background: var(--navBg);
+	contain: strict;
+	display: flex;
+	flex-direction: column;
+}
+
+.root:not(.iconOnly) {
+	.body {
+		width: var(--nav-width);
+	}
+
+	.top {
+		position: sticky;
+		top: 0;
+		z-index: 1;
+		padding: 20px 0;
+		background: var(--X14);
+		-webkit-backdrop-filter: var(--blur, blur(8px));
+		backdrop-filter: var(--blur, blur(8px));
+	}
+
+	.banner {
+		position: absolute;
 		top: 0;
 		left: 0;
-		z-index: 1001;
-		width: $nav-icon-only-width;
-		height: 100dvh;
-		box-sizing: border-box;
-		overflow: auto;
-		overflow-x: clip;
-		background: var(--navBg);
-		contain: strict;
+		width: 100%;
+		height: 100%;
+		background-size: cover;
+		background-position: center center;
+		-webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%);
+		mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%);
+	}
+
+	.instance {
+		position: relative;
+		display: block;
+		text-align: center;
+		width: 100%;
+	}
+
+	.instanceIcon {
+		display: inline-block;
+		width: 38px;
+		aspect-ratio: 1;
+	}
+
+	.bottom {
+		position: sticky;
+		bottom: 0;
+		padding: 20px 0;
+		background: var(--X14);
+		-webkit-backdrop-filter: var(--blur, blur(8px));
+		backdrop-filter: var(--blur, blur(8px));
+	}
+
+	.post {
+		position: relative;
+		display: block;
+		width: 100%;
+		height: 40px;
+		color: var(--fgOnAccent);
+		font-weight: bold;
+		text-align: left;
+
+		&:before {
+			content: "";
+			display: block;
+			width: calc(100% - 38px);
+			height: 100%;
+			margin: auto;
+			position: absolute;
+			top: 0;
+			left: 0;
+			right: 0;
+			bottom: 0;
+			border-radius: 999px;
+			background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
+		}
+
+		&:hover, &.active {
+			&:before {
+				background: var(--accentLighten);
+			}
+		}
+	}
+
+	.postIcon {
+		position: relative;
+		margin-left: 30px;
+		margin-right: 8px;
+		width: 32px;
+	}
+
+	.postText {
+		position: relative;
+	}
+
+	.account {
+		position: relative;
 		display: flex;
-		flex-direction: column;
+		align-items: center;
+		padding-left: 30px;
+		width: 100%;
+		text-align: left;
+		box-sizing: border-box;
+		margin-top: 16px;
 	}
 
-	&:not(.iconOnly) {
-		> .body {
-			width: $nav-width;
+	.avatar {
+		display: block;
+		flex-shrink: 0;
+		position: relative;
+		width: 32px;
+		aspect-ratio: 1;
+		margin-right: 8px;
+	}
 
-			> .top {
-				position: sticky;
+	.acct {
+		display: block;
+		flex-shrink: 1;
+		padding-right: 8px;
+	}
+
+	.middle {
+		flex: 1;
+	}
+
+	.divider {
+		margin: 16px 16px;
+		border-top: solid 0.5px var(--divider);
+	}
+
+	.item {
+		position: relative;
+		display: block;
+		padding-left: 30px;
+		line-height: 2.85rem;
+		text-overflow: ellipsis;
+		overflow: hidden;
+		white-space: nowrap;
+		width: 100%;
+		text-align: left;
+		box-sizing: border-box;
+		color: var(--navFg);
+
+		&:hover {
+			text-decoration: none;
+			color: var(--navHoverFg);
+		}
+
+		&.active {
+			color: var(--navActive);
+		}
+
+		&:hover, &.active {
+			color: var(--accent);
+
+			&:before {
+				content: "";
+				display: block;
+				width: calc(100% - 34px);
+				height: 100%;
+				margin: auto;
+				position: absolute;
 				top: 0;
-				z-index: 1;
-				padding: 20px 0;
-				background: var(--X14);
-				-webkit-backdrop-filter: var(--blur, blur(8px));
-				backdrop-filter: var(--blur, blur(8px));
-
-				> .banner {
-					position: absolute;
-					top: 0;
-					left: 0;
-					width: 100%;
-					height: 100%;
-					background-size: cover;
-					background-position: center center;
-					-webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%);
-					mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%);
-				}
-
-				> .instance {
-					position: relative;
-					display: block;
-					text-align: center;
-					width: 100%;
-
-					> .icon {
-						display: inline-block;
-						width: 38px;
-						aspect-ratio: 1;
-					}
-				}
-			}
-
-			> .bottom {
-				position: sticky;
+				left: 0;
+				right: 0;
 				bottom: 0;
-				padding: 20px 0;
-				background: var(--X14);
-				-webkit-backdrop-filter: var(--blur, blur(8px));
-				backdrop-filter: var(--blur, blur(8px));
-
-				> .post {
-					position: relative;
-					display: block;
-					width: 100%;
-					height: 40px;
-					color: var(--fgOnAccent);
-					font-weight: bold;
-					text-align: left;
-
-					&:before {
-						content: "";
-						display: block;
-						width: calc(100% - 38px);
-						height: 100%;
-						margin: auto;
-						position: absolute;
-						top: 0;
-						left: 0;
-						right: 0;
-						bottom: 0;
-						border-radius: 999px;
-						background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
-					}
-
-					&:hover, &.active {
-						&:before {
-							background: var(--accentLighten);
-						}
-					}
-
-					> .icon {
-						position: relative;
-						margin-left: 30px;
-						margin-right: 8px;
-						width: 32px;
-					}
-
-					> .text {
-						position: relative;
-					}
-				}
-
-				> .account {
-					position: relative;
-					display: flex;
-					align-items: center;
-					padding-left: 30px;
-					width: 100%;
-					text-align: left;
-					box-sizing: border-box;
-					margin-top: 16px;
-
-					> .avatar {
-						display: block;
-						flex-shrink: 0;
-						position: relative;
-						width: 32px;
-						aspect-ratio: 1;
-						margin-right: 8px;
-					}
-
-					> .text {
-						display: block;
-						flex-shrink: 1;
-						padding-right: 8px;
-					}
-				}
-			}
-
-			> .middle {
-				flex: 1;
-
-				> .divider {
-					margin: 16px 16px;
-					border-top: solid 0.5px var(--divider);
-				}
-
-				> .item {
-					position: relative;
-					display: block;
-					padding-left: 30px;
-					line-height: 2.85rem;
-					text-overflow: ellipsis;
-					overflow: hidden;
-					white-space: nowrap;
-					width: 100%;
-					text-align: left;
-					box-sizing: border-box;
-					color: var(--navFg);
-
-					> .icon {
-						position: relative;
-						width: 32px;
-						margin-right: 8px;
-					}
-
-					> .indicator {
-						position: absolute;
-						top: 0;
-						left: 20px;
-						color: var(--navIndicator);
-						font-size: 8px;
-						animation: blink 1s infinite;
-					}
-
-					> .text {
-						position: relative;
-						font-size: 0.9em;
-					}
-
-					&:hover {
-						text-decoration: none;
-						color: var(--navHoverFg);
-					}
-
-					&.active {
-						color: var(--navActive);
-					}
-
-					&:hover, &.active {
-						color: var(--accent);
-
-						&:before {
-							content: "";
-							display: block;
-							width: calc(100% - 34px);
-							height: 100%;
-							margin: auto;
-							position: absolute;
-							top: 0;
-							left: 0;
-							right: 0;
-							bottom: 0;
-							border-radius: 999px;
-							background: var(--accentedBg);
-						}
-					}
-				}
+				border-radius: 999px;
+				background: var(--accentedBg);
 			}
 		}
 	}
 
-	&.iconOnly {
-		flex: 0 0 $nav-icon-only-width;
-		width: $nav-icon-only-width;
+	.itemIcon {
+		position: relative;
+		width: 32px;
+		margin-right: 8px;
+	}
 
-		> .body {
-			width: $nav-icon-only-width;
+	.itemIndicator {
+		position: absolute;
+		top: 0;
+		left: 20px;
+		color: var(--navIndicator);
+		font-size: 8px;
+		animation: blink 1s infinite;
+	}
 
-			> .top {
-				position: sticky;
-				top: 0;
-				z-index: 1;
-				padding: 20px 0;
-				background: var(--X14);
-				-webkit-backdrop-filter: var(--blur, blur(8px));
-				backdrop-filter: var(--blur, blur(8px));
+	.itemText {
+		position: relative;
+		font-size: 0.9em;
+	}
+}
 
-				> .instance {
-					display: block;
-					text-align: center;
-					width: 100%;
+.root.iconOnly {
+	flex: 0 0 var(--nav-icon-only-width);
+	width: var(--nav-icon-only-width);
 
-					> .icon {
-						display: inline-block;
-						width: 30px;
-						aspect-ratio: 1;
-					}
-				}
-			}
+	.body {
+		width: var(--nav-icon-only-width);
+	}
 
-			> .bottom {
-				position: sticky;
-				bottom: 0;
-				padding: 20px 0;
-				background: var(--X14);
-				-webkit-backdrop-filter: var(--blur, blur(8px));
-				backdrop-filter: var(--blur, blur(8px));
+	.top {
+		position: sticky;
+		top: 0;
+		z-index: 1;
+		padding: 20px 0;
+		background: var(--X14);
+		-webkit-backdrop-filter: var(--blur, blur(8px));
+		backdrop-filter: var(--blur, blur(8px));
+	}
 
-				> .post {
-					display: block;
-					position: relative;
-					width: 100%;
-					height: 52px;
-					margin-bottom: 16px;
-					text-align: center;
+	.instance {
+		display: block;
+		text-align: center;
+		width: 100%;
+	}
 
-					&:before {
-						content: "";
-						display: block;
-						position: absolute;
-						top: 0;
-						left: 0;
-						right: 0;
-						bottom: 0;
-						margin: auto;
-						width: 52px;
-						aspect-ratio: 1/1;
-						border-radius: 100%;
-						background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
-					}
+	.instanceIcon {
+		display: inline-block;
+		width: 30px;
+		aspect-ratio: 1;
+	}
 
-					&:hover, &.active {
-						&:before {
-							background: var(--accentLighten);
-						}
-					}
+	.bottom {
+		position: sticky;
+		bottom: 0;
+		padding: 20px 0;
+		background: var(--X14);
+		-webkit-backdrop-filter: var(--blur, blur(8px));
+		backdrop-filter: var(--blur, blur(8px));
+	}
 
-					> .icon {
-						position: relative;
-						color: var(--fgOnAccent);
-					}
+	.post {
+		display: block;
+		position: relative;
+		width: 100%;
+		height: 52px;
+		margin-bottom: 16px;
+		text-align: center;
 
-					> .text {
-						display: none;
-					}
-				}
+		&:before {
+			content: "";
+			display: block;
+			position: absolute;
+			top: 0;
+			left: 0;
+			right: 0;
+			bottom: 0;
+			margin: auto;
+			width: 52px;
+			aspect-ratio: 1/1;
+			border-radius: 100%;
+			background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
+		}
 
-				> .account {
-					display: block;
-					text-align: center;
-					width: 100%;
-
-					> .avatar {
-						display: inline-block;
-						width: 38px;
-						aspect-ratio: 1;
-					}
-
-					> .text {
-						display: none;
-					}
-				}
-			}
-
-			> .middle {
-				flex: 1;
-
-				> .divider {
-					margin: 8px auto;
-					width: calc(100% - 32px);
-					border-top: solid 0.5px var(--divider);
-				}
-
-				> .item {
-					display: block;
-					position: relative;
-					padding: 18px 0;
-					width: 100%;
-					text-align: center;
-
-					> .icon {
-						display: block;
-						margin: 0 auto;
-						opacity: 0.7;
-					}
-
-					> .text {
-						display: none;
-					}
-
-					> .indicator {
-						position: absolute;
-						top: 6px;
-						left: 24px;
-						color: var(--navIndicator);
-						font-size: 8px;
-						animation: blink 1s infinite;
-					}
-
-					&:hover, &.active {
-						text-decoration: none;
-						color: var(--accent);
-
-						&:before {
-							content: "";
-							display: block;
-							height: 100%;
-							aspect-ratio: 1;
-							margin: auto;
-							position: absolute;
-							top: 0;
-							left: 0;
-							right: 0;
-							bottom: 0;
-							border-radius: 999px;
-							background: var(--accentedBg);
-						}
-
-						> .icon, > .text {
-							opacity: 1;
-						}
-					}
-				}
+		&:hover, &.active {
+			&:before {
+				background: var(--accentLighten);
 			}
 		}
 	}
+
+	.postIcon {
+		position: relative;
+		color: var(--fgOnAccent);
+	}
+
+	.postText {
+		display: none;
+	}
+
+	.account {
+		display: block;
+		text-align: center;
+		width: 100%;
+	}
+
+	.avatar {
+		display: inline-block;
+		width: 38px;
+		aspect-ratio: 1;
+	}
+
+	.acct {
+		display: none;
+	}
+
+	.middle {
+		flex: 1;
+	}
+
+	.divider {
+		margin: 8px auto;
+		width: calc(100% - 32px);
+		border-top: solid 0.5px var(--divider);
+	}
+
+	.item {
+		display: block;
+		position: relative;
+		padding: 18px 0;
+		width: 100%;
+		text-align: center;
+
+		&:hover, &.active {
+			text-decoration: none;
+			color: var(--accent);
+
+			&:before {
+				content: "";
+				display: block;
+				height: 100%;
+				aspect-ratio: 1;
+				margin: auto;
+				position: absolute;
+				top: 0;
+				left: 0;
+				right: 0;
+				bottom: 0;
+				border-radius: 999px;
+				background: var(--accentedBg);
+			}
+
+			> .icon,
+			> .text {
+				opacity: 1;
+			}
+		}
+	}
+
+	.itemIcon {
+		display: block;
+		margin: 0 auto;
+		opacity: 0.7;
+	}
+
+	.itemText {
+		display: none;
+	}
+
+	.itemIndicator {
+		position: absolute;
+		top: 6px;
+		left: 24px;
+		color: var(--navIndicator);
+		font-size: 8px;
+		animation: blink 1s infinite;
+	}
 }
 </style>
diff --git a/packages/frontend/src/ui/_common_/statusbar-federation.vue b/packages/frontend/src/ui/_common_/statusbar-federation.vue
index fe95460ba..6f2e4bc9a 100644
--- a/packages/frontend/src/ui/_common_/statusbar-federation.vue
+++ b/packages/frontend/src/ui/_common_/statusbar-federation.vue
@@ -1,14 +1,20 @@
 <template>
-<span v-if="!fetching" class="nmidsaqw">
+<span v-if="!fetching" :class="$style.root">
 	<template v-if="display === 'marquee'">
-		<Transition name="change" mode="default">
+		<Transition
+			:enterActiveClass="$style.transition_change_enterActive"
+			:leaveActiveClass="$style.transition_change_leaveActive"
+			:enterFromClass="$style.transition_change_enterFrom"
+			:leaveToClass="$style.transition_change_leaveTo"
+			mode="default"
+		>
 			<MarqueeText :key="key" :duration="marqueeDuration" :reverse="marqueeReverse">
-				<span v-for="instance in instances" :key="instance.id" class="item" :class="{ colored }" :style="{ background: colored ? instance.themeColor : null }">
-					<img class="icon" :src="getInstanceIcon(instance)" alt=""/>
-					<MkA :to="`/instance-info/${instance.host}`" class="host _monospace">
+				<span v-for="instance in instances" :key="instance.id" :class="[$style.item, { [$style.colored]: colored }]" :style="{ background: colored ? instance.themeColor : null }">
+					<img :class="$style.icon" :src="getInstanceIcon(instance)" alt=""/>
+					<MkA :to="`/instance-info/${instance.host}`" :class="$style.host" class="_monospace">
 						{{ instance.host }}
 					</MkA>
-					<span class="divider"></span>
+					<span></span>
 				</span>
 			</MarqueeText>
 		</Transition>
@@ -61,46 +67,47 @@ function getInstanceIcon(instance): string {
 }
 </script>
 
-<style lang="scss" scoped>
-.change-enter-active, .change-leave-active {
+<style lang="scss" module>
+.transition_change_enterActive,
+.transition_change_leaveActive {
 	position: absolute;
 	top: 0;
   transition: all 1s ease;
 }
-.change-enter-from {
-  opacity: 0;
+.transition_change_enterFrom {
+	opacity: 0;
 	transform: translateY(-100%);
 }
-.change-leave-to {
-  opacity: 0;
+.transition_change_leaveTo {
+	opacity: 0;
 	transform: translateY(100%);
 }
 
-.nmidsaqw {
+.root {
 	display: inline-block;
 	position: relative;
+}
 
-	::v-deep(.item) {
-		display: inline-block;
-		vertical-align: bottom;
-		margin-right: 5em;
+.item {
+	display: inline-block;
+	vertical-align: bottom;
+	margin-right: 5em;
 
-		> .icon {
-			display: inline-block;
-			height: var(--height);
-			aspect-ratio: 1;
-			vertical-align: bottom;
-			margin-right: 1em;
-		}
-
-		> .host {
-			vertical-align: bottom;
-		}
-
-		&.colored {
-			padding-right: 1em;
-			color: #fff;
-		}
+	&.colored {
+		padding-right: 1em;
+		color: #fff;
 	}
 }
+
+.icon {
+	display: inline-block;
+	height: var(--height);
+	aspect-ratio: 1;
+	vertical-align: bottom;
+	margin-right: 1em;
+}
+
+.host {
+	vertical-align: bottom;
+}
 </style>
diff --git a/packages/frontend/src/ui/_common_/statusbar-rss.vue b/packages/frontend/src/ui/_common_/statusbar-rss.vue
index 44b6b278e..82473b609 100644
--- a/packages/frontend/src/ui/_common_/statusbar-rss.vue
+++ b/packages/frontend/src/ui/_common_/statusbar-rss.vue
@@ -1,10 +1,16 @@
 <template>
-<span v-if="!fetching" class="xbhtxfms">
+<span v-if="!fetching" :class="$style.root">
 	<template v-if="display === 'marquee'">
-		<Transition name="change" mode="default">
+		<Transition
+			:enterActiveClass="$style.transition_change_enterActive"
+			:leaveActiveClass="$style.transition_change_leaveActive"
+			:enterFromClass="$style.transition_change_enterFrom"
+			:leaveToClass="$style.transition_change_leaveTo"
+			mode="default"
+		>
 			<MarqueeText :key="key" :duration="marqueeDuration" :reverse="marqueeReverse">
-				<span v-for="item in items" class="item">
-					<a class="link" :href="item.link" rel="nofollow noopener" target="_blank" :title="item.title">{{ item.title }}</a><span class="divider"></span>
+				<span v-for="item in items" :class="$style.item">
+					<a :href="item.link" rel="nofollow noopener" target="_blank" :title="item.title">{{ item.title }}</a><span :class="$style.divider"></span>
 				</span>
 			</MarqueeText>
 		</Transition>
@@ -54,39 +60,40 @@ useInterval(tick, Math.max(5000, props.refreshIntervalSec * 1000), {
 });
 </script>
 
-<style lang="scss" scoped>
-.change-enter-active, .change-leave-active {
+<style lang="scss" module>
+.transition_change_enterActive,
+.transition_change_leaveActive {
 	position: absolute;
 	top: 0;
   transition: all 1s ease;
 }
-.change-enter-from {
-  opacity: 0;
+.transition_change_enterFrom {
+	opacity: 0;
 	transform: translateY(-100%);
 }
-.change-leave-to {
-  opacity: 0;
+.transition_change_leaveTo {
+	opacity: 0;
 	transform: translateY(100%);
 }
 
-.xbhtxfms {
+.root {
 	display: inline-block;
 	position: relative;
+}
 
-	::v-deep(.item) {
-		display: inline-flex;
-		align-items: center;
-		vertical-align: bottom;
-		margin: 0;
+.item {
+	display: inline-flex;
+	align-items: center;
+	vertical-align: bottom;
+	margin: 0;
+}
 
-		> .divider {
-			display: inline-block;
-			width: 0.5px;
-			height: var(--height);
-			margin: 0 3em;
-			background: currentColor;
-			opacity: 0.3;
-		}
-	}
+.divider {
+	display: inline-block;
+	width: 0.5px;
+	height: var(--height);
+	margin: 0 3em;
+	background: currentColor;
+	opacity: 0.3;
 }
 </style>
diff --git a/packages/frontend/src/ui/_common_/statusbar-user-list.vue b/packages/frontend/src/ui/_common_/statusbar-user-list.vue
index 16df69d96..9ac744943 100644
--- a/packages/frontend/src/ui/_common_/statusbar-user-list.vue
+++ b/packages/frontend/src/ui/_common_/statusbar-user-list.vue
@@ -1,14 +1,20 @@
 <template>
-<span v-if="!fetching" class="osdsvwzy">
+<span v-if="!fetching" :class="$style.root">
 	<template v-if="display === 'marquee'">
-		<Transition name="change" mode="default">
+		<Transition
+			:enterActiveClass="$style.transition_change_enterActive"
+			:leaveActiveClass="$style.transition_change_leaveActive"
+			:enterFromClass="$style.transition_change_enterFrom"
+			:leaveToClass="$style.transition_change_leaveTo"
+			mode="default"
+		>
 			<MarqueeText :key="key" :duration="marqueeDuration" :reverse="marqueeReverse">
-				<span v-for="note in notes" :key="note.id" class="item">
-					<img class="avatar" :src="note.user.avatarUrl" decoding="async"/>
-					<MkA class="text" :to="notePage(note)">
-						<Mfm class="text" :text="getNoteSummary(note)" :plain="true" :nowrap="true"/>
+				<span v-for="note in notes" :key="note.id" :class="$style.item">
+					<img :class="$style.avatar" :src="note.user.avatarUrl" decoding="async"/>
+					<MkA :class="$style.text" :to="notePage(note)">
+						<Mfm :text="getNoteSummary(note)" :plain="true" :nowrap="true"/>
 					</MkA>
-					<span class="divider"></span>
+					<span :class="$style.divider"></span>
 				</span>
 			</MarqueeText>
 		</Transition>
@@ -60,54 +66,53 @@ useInterval(tick, Math.max(5000, props.refreshIntervalSec * 1000), {
 });
 </script>
 
-<style lang="scss" scoped>
-.change-enter-active, .change-leave-active {
+<style lang="scss" module>
+.transition_change_enterActive,
+.transition_change_leaveActive {
 	position: absolute;
 	top: 0;
   transition: all 1s ease;
 }
-.change-enter-from {
-  opacity: 0;
+.transition_change_enterFrom {
+	opacity: 0;
 	transform: translateY(-100%);
 }
-.change-leave-to {
-  opacity: 0;
+.transition_change_leaveTo {
+	opacity: 0;
 	transform: translateY(100%);
 }
 
-.osdsvwzy {
+.root {
 	display: inline-block;
 	position: relative;
+}
 
-	::v-deep(.item) {
-		display: inline-flex;
-		align-items: center;
-		vertical-align: bottom;
-		margin: 0;
+.item {
+	display: inline-flex;
+	align-items: center;
+	vertical-align: bottom;
+	margin: 0;
+}
 
-		> .avatar {
-			display: inline-block;
-			height: var(--height);
-			aspect-ratio: 1;
-			vertical-align: bottom;
-			margin-right: 8px;
-		}
+.avatar {
+	display: inline-block;
+	height: var(--height);
+	aspect-ratio: 1;
+	vertical-align: bottom;
+	margin-right: 8px;
+}
 
-		> .text {
-			> .text {
-				display: inline-block;
-				vertical-align: bottom;
-			}
-		}
+.text {
+	display: inline-block;
+	vertical-align: bottom;
+}
 
-		> .divider {
-			display: inline-block;
-			width: 0.5px;
-			height: 16px;
-			margin: 0 3em;
-			background: currentColor;
-			opacity: 0;
-		}
-	}
+.divider {
+	display: inline-block;
+	width: 0.5px;
+	height: 16px;
+	margin: 0 3em;
+	background: currentColor;
+	opacity: 0;
 }
 </style>
diff --git a/packages/frontend/src/ui/_common_/statusbars.vue b/packages/frontend/src/ui/_common_/statusbars.vue
index f84695c15..3533972cd 100644
--- a/packages/frontend/src/ui/_common_/statusbars.vue
+++ b/packages/frontend/src/ui/_common_/statusbars.vue
@@ -1,18 +1,17 @@
 <template>
-<div class="dlrsnxqu">
+<div :class="$style.root">
 	<div
-		v-for="x in defaultStore.reactiveState.statusbars.value" :key="x.id" class="item" :class="[{ black: x.black }, {
-			verySmall: x.size === 'verySmall',
-			small: x.size === 'small',
-			medium: x.size === 'medium',
-			large: x.size === 'large',
-			veryLarge: x.size === 'veryLarge',
+		v-for="x in defaultStore.reactiveState.statusbars.value" :key="x.id" :class="[$style.item, { [$style.black]: x.black,
+			[$style.verySmall]: x.size === 'verySmall',
+			[$style.small]: x.size === 'small',
+			[$style.large]: x.size === 'large',
+			[$style.veryLarge]: x.size === 'veryLarge',
 		}]"
 	>
-		<span class="name">{{ x.name }}</span>
-		<XRss v-if="x.type === 'rss'" class="body" :refresh-interval-sec="x.props.refreshIntervalSec" :marquee-duration="x.props.marqueeDuration" :marquee-reverse="x.props.marqueeReverse" :display="x.props.display" :url="x.props.url" :shuffle="x.props.shuffle"/>
-		<XFederation v-else-if="x.type === 'federation'" class="body" :refresh-interval-sec="x.props.refreshIntervalSec" :marquee-duration="x.props.marqueeDuration" :marquee-reverse="x.props.marqueeReverse" :display="x.props.display" :colored="x.props.colored"/>
-		<XUserList v-else-if="x.type === 'userList'" class="body" :refresh-interval-sec="x.props.refreshIntervalSec" :marquee-duration="x.props.marqueeDuration" :marquee-reverse="x.props.marqueeReverse" :display="x.props.display" :user-list-id="x.props.userListId"/>
+		<span :class="$style.name">{{ x.name }}</span>
+		<XRss v-if="x.type === 'rss'" :class="$style.body" :refreshIntervalSec="x.props.refreshIntervalSec" :marqueeDuration="x.props.marqueeDuration" :marqueeReverse="x.props.marqueeReverse" :display="x.props.display" :url="x.props.url" :shuffle="x.props.shuffle"/>
+		<XFederation v-else-if="x.type === 'federation'" :class="$style.body" :refreshIntervalSec="x.props.refreshIntervalSec" :marqueeDuration="x.props.marqueeDuration" :marqueeReverse="x.props.marqueeReverse" :display="x.props.display" :colored="x.props.colored"/>
+		<XUserList v-else-if="x.type === 'userList'" :class="$style.body" :refreshIntervalSec="x.props.refreshIntervalSec" :marqueeDuration="x.props.marqueeDuration" :marqueeReverse="x.props.marqueeReverse" :display="x.props.display" :userListId="x.props.userListId"/>
 	</div>
 </div>
 </template>
@@ -25,67 +24,67 @@ const XFederation = defineAsyncComponent(() => import('./statusbar-federation.vu
 const XUserList = defineAsyncComponent(() => import('./statusbar-user-list.vue'));
 </script>
 
-<style lang="scss" scoped>
-.dlrsnxqu {
+<style lang="scss" module>
+.root {
 	font-size: 15px;
 	background: var(--panel);
+}
 
-	> .item {
-		--height: 24px;
-		--nameMargin: 10px;
-		font-size: 0.85em;
+.item {
+	--height: 24px;
+	--nameMargin: 10px;
+	font-size: 0.85em;
 
-		&.verySmall {
-			--nameMargin: 7px;
-			--height: 16px;
-			font-size: 0.75em;
-		}
+	&.verySmall {
+		--nameMargin: 7px;
+		--height: 16px;
+		font-size: 0.75em;
+	}
 
-		&.small {
-			--nameMargin: 8px;
-			--height: 20px;
-			font-size: 0.8em;
-		}
+	&.small {
+		--nameMargin: 8px;
+		--height: 20px;
+		font-size: 0.8em;
+	}
 
-		&.large {
-			--nameMargin: 12px;
-			--height: 26px;
-			font-size: 0.875em;
-		}
+	&.large {
+		--nameMargin: 12px;
+		--height: 26px;
+		font-size: 0.875em;
+	}
 
-		&.veryLarge {
-			--nameMargin: 14px;
-			--height: 30px;
-			font-size: 0.9em;
-		}
+	&.veryLarge {
+		--nameMargin: 14px;
+		--height: 30px;
+		font-size: 0.9em;
+	}
 
-		display: flex;
-		vertical-align: bottom;
-		width: 100%;
-		line-height: var(--height);
-		height: var(--height);
-		overflow: clip;
-		contain: strict;
+	display: flex;
+	vertical-align: bottom;
+	width: 100%;
+	line-height: var(--height);
+	height: var(--height);
+	overflow: clip;
+	contain: strict;
 
-		> .name {
-			padding: 0 var(--nameMargin);
-			font-weight: bold;
-			color: var(--accent);
-
-			&:empty {
-				display: none;
-			}
-		}
-
-		> .body {
-			min-width: 0;
-			flex: 1;
-		}
-
-		&.black {
-			background: #000;
-			color: #fff;
-		}
+	&.black {
+		background: #000;
+		color: #fff;
 	}
 }
+
+.name {
+	padding: 0 var(--nameMargin);
+	font-weight: bold;
+	color: var(--accent);
+
+	&:empty {
+		display: none;
+	}
+}
+
+.body {
+	min-width: 0;
+	flex: 1;
+}
 </style>
diff --git a/packages/frontend/src/ui/_common_/stream-indicator.vue b/packages/frontend/src/ui/_common_/stream-indicator.vue
index 2a856e2a4..74c475fc7 100644
--- a/packages/frontend/src/ui/_common_/stream-indicator.vue
+++ b/packages/frontend/src/ui/_common_/stream-indicator.vue
@@ -2,15 +2,15 @@
 <div v-if="hasDisconnected && defaultStore.state.serverDisconnectedBehavior === 'quiet'" :class="$style.root" class="_panel _shadow" @click="resetDisconnected">
 	<div><i class="ti ti-alert-triangle"></i> {{ i18n.ts.disconnectedFromServer }}</div>
 	<div :class="$style.command" class="_buttons">
-		<MkButton :class="$style.commandButton" small primary @click="reload">{{ i18n.ts.reload }}</MkButton>
-		<MkButton :class="$style.commandButton" small>{{ i18n.ts.doNothing }}</MkButton>
+		<MkButton small primary @click="reload">{{ i18n.ts.reload }}</MkButton>
+		<MkButton small>{{ i18n.ts.doNothing }}</MkButton>
 	</div>
 </div>
 </template>
 
 <script lang="ts" setup>
 import { onUnmounted } from 'vue';
-import { stream } from '@/stream';
+import { useStream } from '@/stream';
 import { i18n } from '@/i18n';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os';
@@ -32,10 +32,10 @@ function reload() {
 	location.reload();
 }
 
-stream.on('_disconnected_', onDisconnected);
+useStream().on('_disconnected_', onDisconnected);
 
 onUnmounted(() => {
-	stream.off('_disconnected_', onDisconnected);
+	useStream().off('_disconnected_', onDisconnected);
 });
 </script>
 
@@ -54,7 +54,4 @@ onUnmounted(() => {
 .command {
 	margin-top: 8px;
 }
-
-.commandButton {
-}
 </style>
diff --git a/packages/frontend/src/ui/classic.header.vue b/packages/frontend/src/ui/classic.header.vue
index daea77555..747d4edcb 100644
--- a/packages/frontend/src/ui/classic.header.vue
+++ b/packages/frontend/src/ui/classic.header.vue
@@ -5,18 +5,18 @@
 			<button v-click-anime class="item _button instance" @click="openInstanceMenu">
 				<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" class="_ghost"/>
 			</button>
-			<MkA v-click-anime v-tooltip="i18n.ts.timeline" class="item index" active-class="active" to="/" exact>
+			<MkA v-click-anime v-tooltip="i18n.ts.timeline" class="item index" activeClass="active" to="/" exact>
 				<i class="ti ti-home ti-fw"></i>
 			</MkA>
 			<template v-for="item in menu">
 				<div v-if="item === '-'" class="divider"></div>
-				<component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime v-tooltip="navbarItemDef[item].title" class="item _button" :class="item" active-class="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}">
+				<component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime v-tooltip="navbarItemDef[item].title" class="item _button" :class="item" activeClass="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}">
 					<i class="ti-fw" :class="navbarItemDef[item].icon"></i>
 					<span v-if="navbarItemDef[item].indicated" class="indicator"><i class="_indicatorCircle"></i></span>
 				</component>
 			</template>
 			<div class="divider"></div>
-			<MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime v-tooltip="i18n.ts.controlPanel" class="item" active-class="active" to="/admin" :behavior="settingsWindowed ? 'modalWindow' : null">
+			<MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime v-tooltip="i18n.ts.controlPanel" class="item" activeClass="active" to="/admin" :behavior="settingsWindowed ? 'window' : null">
 				<i class="ti ti-dashboard ti-fw"></i>
 			</MkA>
 			<button v-click-anime class="item _button" @click="more">
@@ -25,7 +25,7 @@
 			</button>
 		</div>
 		<div class="right">
-			<MkA v-click-anime v-tooltip="i18n.ts.settings" class="item" active-class="active" to="/settings" :behavior="settingsWindowed ? 'modalWindow' : null">
+			<MkA v-click-anime v-tooltip="i18n.ts.settings" class="item" activeClass="active" to="/settings" :behavior="settingsWindowed ? 'window' : null">
 				<i class="ti ti-settings ti-fw"></i>
 			</MkA>
 			<button v-click-anime class="item _button account" @click="openAccountMenu">
diff --git a/packages/frontend/src/ui/classic.sidebar.vue b/packages/frontend/src/ui/classic.sidebar.vue
index 73db14c65..cb264fc3b 100644
--- a/packages/frontend/src/ui/classic.sidebar.vue
+++ b/packages/frontend/src/ui/classic.sidebar.vue
@@ -9,25 +9,25 @@
 		</MkButton>
 	</div>
 	<div class="divider"></div>
-	<MkA v-click-anime class="item index" active-class="active" to="/" exact>
+	<MkA v-click-anime class="item index" activeClass="active" to="/" exact>
 		<i class="ti ti-home ti-fw"></i><span class="text">{{ i18n.ts.timeline }}</span>
 	</MkA>
 	<template v-for="item in menu">
 		<div v-if="item === '-'" class="divider"></div>
-		<component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime class="item _button" :class="item" active-class="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}">
+		<component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime class="item _button" :class="item" activeClass="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}">
 			<i class="ti-fw" :class="navbarItemDef[item].icon"></i><span class="text">{{ navbarItemDef[item].title }}</span>
 			<span v-if="navbarItemDef[item].indicated" class="indicator"><i class="_indicatorCircle"></i></span>
 		</component>
 	</template>
 	<div class="divider"></div>
-	<MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime class="item" active-class="active" to="/admin" :behavior="settingsWindowed ? 'modalWindow' : null">
+	<MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime class="item" activeClass="active" to="/admin" :behavior="settingsWindowed ? 'window' : null">
 		<i class="ti ti-dashboard ti-fw"></i><span class="text">{{ i18n.ts.controlPanel }}</span>
 	</MkA>
 	<button v-click-anime class="item _button" @click="more">
 		<i class="ti ti-dots ti-fw"></i><span class="text">{{ i18n.ts.more }}</span>
 		<span v-if="otherNavItemIndicated" class="indicator"><i class="_indicatorCircle"></i></span>
 	</button>
-	<MkA v-click-anime class="item" active-class="active" to="/settings" :behavior="settingsWindowed ? 'modalWindow' : null">
+	<MkA v-click-anime class="item" activeClass="active" to="/settings" :behavior="settingsWindowed ? 'window' : null">
 		<i class="ti ti-settings ti-fw"></i><span class="text">{{ i18n.ts.settings }}</span>
 	</MkA>
 	<div class="divider"></div>
diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue
index 792c1ccc5..d50f2b045 100644
--- a/packages/frontend/src/ui/classic.vue
+++ b/packages/frontend/src/ui/classic.vue
@@ -7,17 +7,17 @@
 			<XSidebar/>
 		</div>
 		<div v-else ref="widgetsLeft" class="widgets left">
-			<XWidgets place="left" :margin-top="'var(--margin)'" @mounted="attachSticky(widgetsLeft)"/>
+			<XWidgets place="left" :marginTop="'var(--margin)'" @mounted="attachSticky(widgetsLeft)"/>
 		</div>
 
-		<main class="main" :style="{ background: pageMetadata?.value?.bg }" @contextmenu.stop="onContextmenu">
+		<main class="main" @contextmenu.stop="onContextmenu">
 			<div class="content" style="container-type: inline-size;">
 				<RouterView/>
 			</div>
 		</main>
 
 		<div v-if="isDesktop" ref="widgetsRight" class="widgets right">
-			<XWidgets :place="showMenuOnTop ? 'right' : null" :margin-top="showMenuOnTop ? '0' : 'var(--margin)'" @mounted="attachSticky(widgetsRight)"/>
+			<XWidgets :place="showMenuOnTop ? 'right' : null" :marginTop="showMenuOnTop ? '0' : 'var(--margin)'" @mounted="attachSticky(widgetsRight)"/>
 		</div>
 	</div>
 
diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue
index 33e752513..c82873177 100644
--- a/packages/frontend/src/ui/deck.vue
+++ b/packages/frontend/src/ui/deck.vue
@@ -4,27 +4,23 @@
 
 	<div :class="$style.main">
 		<XStatusBars/>
-		<div ref="columnsEl" :class="[$style.columns, deckStore.reactiveState.columnAlign.value, { [$style.snapScroll]: snapScroll }]" @contextmenu.self.prevent="onContextmenu">
-			<template v-for="ids in layout">
-				<!-- sectionを利用しているのは、deck.vue側でcolumnに対してfirst-of-typeを効かせるため -->
-				<section
-					v-if="ids.length > 1"
-					:class="$style.folder"
-					:style="columns.filter(c => ids.includes(c.id)).some(c => c.flexible) ? { flex: 1, minWidth: '350px' } : { width: Math.max(...columns.filter(c => ids.includes(c.id)).map(c => c.width)) + 'px' }"
-				>
-					<DeckColumnCore v-for="id in ids" :ref="id" :key="id" :column="columns.find(c => c.id === id)" :is-stacked="true" @parent-focus="moveFocus(id, $event)"/>
-				</section>
-				<DeckColumnCore
-					v-else
-					:ref="ids[0]"
-					:key="ids[0]"
+		<div ref="columnsEl" :class="[$style.sections, { [$style.center]: deckStore.reactiveState.columnAlign.value === 'center', [$style.snapScroll]: snapScroll }]" @contextmenu.self.prevent="onContextmenu">
+			<!-- sectionを利用しているのは、deck.vue側でcolumnに対してfirst-of-typeを効かせるため -->
+			<section
+				v-for="ids in layout"
+				:class="$style.section"
+				:style="columns.filter(c => ids.includes(c.id)).some(c => c.flexible) ? { flex: 1, minWidth: '350px' } : { width: Math.max(...columns.filter(c => ids.includes(c.id)).map(c => c.width)) + 'px' }"
+			>
+				<component
+					:is="columnComponents[columns.find(c => c.id === id)!.type] ?? XTlColumn"
+					v-for="id in ids"
+					:ref="id"
+					:key="id"
 					:class="$style.column"
-					:column="columns.find(c => c.id === ids[0])"
-					:is-stacked="false"
-					:style="columns.find(c => c.id === ids[0])!.flexible ? { flex: 1, minWidth: '350px' } : { width: columns.find(c => c.id === ids[0])!.width + 'px' }"
-					@parent-focus="moveFocus(ids[0], $event)"
+					:column="columns.find(c => c.id === id)"
+					:isStacked="ids.length > 1"
 				/>
-			</template>
+			</section>
 			<div v-if="layout.length === 0" class="_panel" :class="$style.onboarding">
 				<div>{{ i18n.ts._deck.introduction }}</div>
 				<MkButton primary style="margin: 1em auto;" @click="addColumn">{{ i18n.ts._deck.addColumn }}</MkButton>
@@ -53,10 +49,10 @@
 	</div>
 
 	<Transition
-		:enter-active-class="defaultStore.state.animation ? $style.transition_menuDrawerBg_enterActive : ''"
-		:leave-active-class="defaultStore.state.animation ? $style.transition_menuDrawerBg_leaveActive : ''"
-		:enter-from-class="defaultStore.state.animation ? $style.transition_menuDrawerBg_enterFrom : ''"
-		:leave-to-class="defaultStore.state.animation ? $style.transition_menuDrawerBg_leaveTo : ''"
+		:enterActiveClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_enterActive : ''"
+		:leaveActiveClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_leaveActive : ''"
+		:enterFromClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_enterFrom : ''"
+		:leaveToClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_leaveTo : ''"
 	>
 		<div
 			v-if="drawerMenuShowing"
@@ -68,10 +64,10 @@
 	</Transition>
 
 	<Transition
-		:enter-active-class="defaultStore.state.animation ? $style.transition_menuDrawer_enterActive : ''"
-		:leave-active-class="defaultStore.state.animation ? $style.transition_menuDrawer_leaveActive : ''"
-		:enter-from-class="defaultStore.state.animation ? $style.transition_menuDrawer_enterFrom : ''"
-		:leave-to-class="defaultStore.state.animation ? $style.transition_menuDrawer_leaveTo : ''"
+		:enterActiveClass="defaultStore.state.animation ? $style.transition_menuDrawer_enterActive : ''"
+		:leaveActiveClass="defaultStore.state.animation ? $style.transition_menuDrawer_leaveActive : ''"
+		:enterFromClass="defaultStore.state.animation ? $style.transition_menuDrawer_enterFrom : ''"
+		:leaveToClass="defaultStore.state.animation ? $style.transition_menuDrawer_leaveTo : ''"
 	>
 		<div v-if="drawerMenuShowing" :class="$style.menu">
 			<XDrawerMenu/>
@@ -87,7 +83,6 @@ import { computed, defineAsyncComponent, ref, watch } from 'vue';
 import { v4 as uuid } from 'uuid';
 import XCommon from './_common_/common.vue';
 import { deckStore, addColumn as addColumnToStore, loadDeck, getProfiles, deleteProfile as deleteProfile_ } from './deck/deck-store';
-import DeckColumnCore from '@/ui/deck/column-core.vue';
 import XSidebar from '@/ui/_common_/navbar.vue';
 import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue';
 import MkButton from '@/components/MkButton.vue';
@@ -100,8 +95,31 @@ import { mainRouter } from '@/router';
 import { unisonReload } from '@/scripts/unison-reload';
 import { deviceKind } from '@/scripts/device-kind';
 import { defaultStore } from '@/store';
+import XMainColumn from '@/ui/deck/main-column.vue';
+import XTlColumn from '@/ui/deck/tl-column.vue';
+import XAntennaColumn from '@/ui/deck/antenna-column.vue';
+import XListColumn from '@/ui/deck/list-column.vue';
+import XChannelColumn from '@/ui/deck/channel-column.vue';
+import XNotificationsColumn from '@/ui/deck/notifications-column.vue';
+import XWidgetsColumn from '@/ui/deck/widgets-column.vue';
+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';
 const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue'));
 
+const columnComponents = {
+	main: XMainColumn,
+	widgets: XWidgetsColumn,
+	notifications: XNotificationsColumn,
+	tl: XTlColumn,
+	list: XListColumn,
+	channel: XChannelColumn,
+	antenna: XAntennaColumn,
+	mentions: XMentionsColumn,
+	direct: XDirectColumn,
+	roleTimeline: XRoleTimelineColumn,
+};
+
 mainRouter.navHook = (path, flag): boolean => {
 	if (flag === 'forcePage') return false;
 	const noMainColumn = !deckStore.state.columns.some(x => x.type === 'main');
@@ -187,11 +205,8 @@ window.addEventListener('wheel', (ev) => {
 		columnsEl.scrollLeft += ev.deltaY;
 	}
 });
-loadDeck();
 
-function moveFocus(id: string, direction: 'up' | 'down' | 'left' | 'right') {
-	// TODO??
-}
+loadDeck();
 
 function changeProfile(ev: MouseEvent) {
 	const items = ref([{
@@ -267,7 +282,7 @@ async function deleteProfile() {
 
 	--margin: var(--marginHalf);
 
-	--deckDividerThickness: 5px;
+	--columnGap: 6px;
 
 	display: flex;
 	height: 100dvh;
@@ -286,19 +301,21 @@ async function deleteProfile() {
 	flex-direction: column;
 }
 
-.columns {
+.sections {
 	flex: 1;
 	display: flex;
 	overflow-x: auto;
 	overflow-y: clip;
+	overscroll-behavior: contain;
+	background: var(--deckBg);
 
 	&.center {
-		> .column:first-of-type {
-			margin-left: auto;
+		> .section:first-of-type {
+			margin-left: auto !important;
 		}
 
-		> .column:last-of-type {
-			margin-right: auto;
+		> .section:last-of-type {
+			margin-right: auto !important;
 		}
 	}
 
@@ -307,23 +324,17 @@ async function deleteProfile() {
 	}
 }
 
-.column {
-	scroll-snap-align: start;
-	flex-shrink: 0;
-	border-right: solid var(--deckDividerThickness) var(--deckDivider);
-
-	&:first-of-type {
-		border-left: solid var(--deckDividerThickness) var(--deckDivider);
-	}
-}
-
-.folder {
-	composes: column;
+.section {
 	display: flex;
 	flex-direction: column;
+	scroll-snap-align: start;
+	flex-shrink: 0;
+	padding-top: var(--columnGap);
+	padding-bottom: var(--columnGap);
+	padding-left: var(--columnGap);
 
-	> *:not(:last-of-type) {
-		border-bottom: solid var(--deckDividerThickness) var(--deckDivider);
+	> .column:not(:last-of-type) {
+		margin-bottom: var(--columnGap);
 	}
 }
 
diff --git a/packages/frontend/src/ui/deck/antenna-column.vue b/packages/frontend/src/ui/deck/antenna-column.vue
index 76a8b6e76..d21a9cc58 100644
--- a/packages/frontend/src/ui/deck/antenna-column.vue
+++ b/packages/frontend/src/ui/deck/antenna-column.vue
@@ -1,10 +1,10 @@
 <template>
-<XColumn :menu="menu" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
+<XColumn :menu="menu" :column="column" :isStacked="isStacked">
 	<template #header>
 		<i class="ti ti-antenna"></i><span style="margin-left: 8px;">{{ column.name }}</span>
 	</template>
 
-	<MkTimeline v-if="column.antennaId" ref="timeline" src="antenna" :antenna="column.antennaId" @after="() => emit('loaded')"/>
+	<MkTimeline v-if="column.antennaId" ref="timeline" src="antenna" :antenna="column.antennaId"/>
 </XColumn>
 </template>
 
@@ -21,11 +21,6 @@ const props = defineProps<{
 	isStacked: boolean;
 }>();
 
-const emit = defineEmits<{
-	(ev: 'loaded'): void;
-	(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
-}>();
-
 let timeline = $shallowRef<InstanceType<typeof MkTimeline>>();
 
 onMounted(() => {
diff --git a/packages/frontend/src/ui/deck/channel-column.vue b/packages/frontend/src/ui/deck/channel-column.vue
index 9605d1b22..8b05ecc0b 100644
--- a/packages/frontend/src/ui/deck/channel-column.vue
+++ b/packages/frontend/src/ui/deck/channel-column.vue
@@ -1,5 +1,5 @@
 <template>
-<XColumn :menu="menu" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
+<XColumn :menu="menu" :column="column" :isStacked="isStacked">
 	<template #header>
 		<i class="ti ti-device-tv"></i><span style="margin-left: 8px;">{{ column.name }}</span>
 	</template>
@@ -8,30 +8,25 @@
 		<div style="padding: 8px; text-align: center;">
 			<MkButton primary gradate rounded inline @click="post"><i class="ti ti-pencil"></i></MkButton>
 		</div>
-		<MkTimeline ref="timeline" src="channel" :channel="column.channelId" @after="() => emit('loaded')"/>
+		<MkTimeline ref="timeline" src="channel" :channel="column.channelId"/>
 	</template>
 </XColumn>
 </template>
 
 <script lang="ts" setup>
+import * as misskey from 'misskey-js';
 import XColumn from './column.vue';
 import { updateColumn, Column } from './deck-store';
 import MkTimeline from '@/components/MkTimeline.vue';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
-import * as misskey from 'misskey-js';
 
 const props = defineProps<{
 	column: Column;
 	isStacked: boolean;
 }>();
 
-const emit = defineEmits<{
-	(ev: 'loaded'): void;
-	(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
-}>();
-
 let timeline = $shallowRef<InstanceType<typeof MkTimeline>>();
 let channel = $shallowRef<misskey.entities.Channel>();
 
diff --git a/packages/frontend/src/ui/deck/column-core.vue b/packages/frontend/src/ui/deck/column-core.vue
deleted file mode 100644
index 8e7addf35..000000000
--- a/packages/frontend/src/ui/deck/column-core.vue
+++ /dev/null
@@ -1,38 +0,0 @@
-<template>
-<!-- TODO: リファクタの余地がありそう -->
-<div v-if="!column">たぶん見えちゃいけないやつ</div>
-<XMainColumn v-else-if="column.type === 'main'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
-<XWidgetsColumn v-else-if="column.type === 'widgets'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
-<XNotificationsColumn v-else-if="column.type === 'notifications'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
-<XTlColumn v-else-if="column.type === 'tl'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
-<XListColumn v-else-if="column.type === 'list'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
-<XChannelColumn v-else-if="column.type === 'channel'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
-<XAntennaColumn v-else-if="column.type === 'antenna'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
-<XMentionsColumn v-else-if="column.type === 'mentions'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
-<XDirectColumn v-else-if="column.type === 'direct'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
-<XRoleTimelineColumn v-else-if="column.type === 'roleTimeline'" :column="column" :is-stacked="isStacked" @parent-focus="emit('parent-focus', $event)"/>
-</template>
-
-<script lang="ts" setup>
-import { } from 'vue';
-import XMainColumn from './main-column.vue';
-import XTlColumn from './tl-column.vue';
-import XAntennaColumn from './antenna-column.vue';
-import XListColumn from './list-column.vue';
-import XChannelColumn from './channel-column.vue';
-import XNotificationsColumn from './notifications-column.vue';
-import XWidgetsColumn from './widgets-column.vue';
-import XMentionsColumn from './mentions-column.vue';
-import XDirectColumn from './direct-column.vue';
-import XRoleTimelineColumn from './role-timeline-column.vue';
-import { Column } from './deck-store';
-
-defineProps<{
-	column?: Column;
-	isStacked: boolean;
-}>();
-
-const emit = defineEmits<{
-	(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
-}>();
-</script>
diff --git a/packages/frontend/src/ui/deck/column.vue b/packages/frontend/src/ui/deck/column.vue
index 402bbe035..c376eb2b4 100644
--- a/packages/frontend/src/ui/deck/column.vue
+++ b/packages/frontend/src/ui/deck/column.vue
@@ -1,8 +1,6 @@
 <template>
-<!-- sectionを利用しているのは、deck.vue側でcolumnに対してfirst-of-typeを効かせるため -->
-<section
-	v-hotkey="keymap"
-	:class="[$style.root, { [$style.paged]: isMainColumn, [$style.naked]: naked, [$style.active]: active, [$style.isStacked]: isStacked, [$style.draghover]: draghover, [$style.dragging]: dragging, [$style.dropready]: dropready }]"
+<div
+	:class="[$style.root, { [$style.paged]: isMainColumn, [$style.naked]: naked, [$style.active]: active, [$style.draghover]: draghover, [$style.dragging]: dragging, [$style.dropready]: dropready }]"
 	@dragover.prevent.stop="onDragover"
 	@dragleave="onDragleave"
 	@drop.prevent.stop="onDrop"
@@ -15,17 +13,26 @@
 		@dragend="onDragend"
 		@contextmenu.prevent.stop="onContextmenu"
 	>
+		<svg viewBox="0 0 256 128" :class="$style.tabShape">
+			<g transform="matrix(6.2431,0,0,6.2431,-677.417,-29.3839)">
+				<path d="M149.512,4.707L108.507,4.707C116.252,4.719 118.758,14.958 118.758,14.958C118.758,14.958 121.381,25.283 129.009,25.209L149.512,25.209L149.512,4.707Z" style="fill:var(--deckBg);"/>
+			</g>
+		</svg>
+		<div :class="$style.color"></div>
 		<button v-if="isStacked && !isMainColumn" :class="$style.toggleActive" class="_button" @click="toggleActive">
 			<template v-if="active"><i class="ti ti-chevron-up"></i></template>
 			<template v-else><i class="ti ti-chevron-down"></i></template>
 		</button>
 		<span :class="$style.title"><slot name="header"></slot></span>
+		<svg viewBox="0 0 16 16" version="1.1" :class="$style.grabber">
+			<path fill="currentColor" d="M10 13a1 1 0 1 1 0-2 1 1 0 0 1 0 2Zm0-4a1 1 0 1 1 0-2 1 1 0 0 1 0 2Zm-4 4a1 1 0 1 1 0-2 1 1 0 0 1 0 2Zm5-9a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM7 8a1 1 0 1 1-2 0 1 1 0 0 1 2 0ZM6 5a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z"></path>
+		</svg>
 		<button v-tooltip="i18n.ts.settings" :class="$style.menu" class="_button" @click.stop="showSettingsMenu"><i class="ti ti-dots"></i></button>
 	</header>
-	<div v-show="active" ref="body" :class="$style.body">
+	<div v-if="active" ref="body" :class="$style.body">
 		<slot></slot>
 	</div>
-</section>
+</div>
 </template>
 
 <script lang="ts" setup>
@@ -49,12 +56,7 @@ const props = withDefaults(defineProps<{
 	naked: false,
 });
 
-const emit = defineEmits<{
-	(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
-	(ev: 'change-active-state', v: boolean): void;
-}>();
-
-let body = $shallowRef<HTMLDivElement>();
+let body = $shallowRef<HTMLDivElement | null>();
 
 let dragging = $ref(false);
 watch($$(dragging), v => os.deckGlobalEvents.emit(v ? 'column.dragStart' : 'column.dragEnd'));
@@ -64,14 +66,6 @@ let dropready = $ref(false);
 
 const isMainColumn = $computed(() => props.column.type === 'main');
 const active = $computed(() => props.column.active !== false);
-watch($$(active), v => emit('change-active-state', v));
-
-const keymap = $computed(() => ({
-	'shift+up': () => emit('parent-focus', 'up'),
-	'shift+down': () => emit('parent-focus', 'down'),
-	'shift+left': () => emit('parent-focus', 'left'),
-	'shift+right': () => emit('parent-focus', 'right'),
-}));
 
 onMounted(() => {
 	os.deckGlobalEvents.on('column.dragStart', onOtherDragStart);
@@ -190,10 +184,12 @@ function onContextmenu(ev: MouseEvent) {
 }
 
 function goTop() {
-	body.scrollTo({
-		top: 0,
-		behavior: 'smooth',
-	});
+	if (body) {
+		body.scrollTo({
+			top: 0,
+			behavior: 'smooth',
+		});
+	}
 }
 
 function onDragstart(ev) {
@@ -248,6 +244,7 @@ function onDrop(ev) {
 	height: 100%;
 	overflow: clip;
 	contain: strict;
+	border-radius: 10px;
 
 	&.draghover {
 		&:after {
@@ -287,6 +284,7 @@ function onDrop(ev) {
 	&:not(.active) {
 		flex-basis: var(--deckColumnHeaderHeight);
 		min-height: var(--deckColumnHeaderHeight);
+		border-bottom-right-radius: 0;
 	}
 
 	&.naked {
@@ -299,10 +297,28 @@ function onDrop(ev) {
 			box-shadow: none;
 			color: var(--fg);
 		}
+
+		> .body {
+			background: transparent !important;
+
+			&::-webkit-scrollbar-track {
+				background: transparent;
+			}
+			scrollbar-color: var(--scrollbarHandle) transparent;
+		}
 	}
 
 	&.paged {
 		background: var(--bg) !important;
+
+		> .body {
+			background: var(--bg) !important;
+
+			&::-webkit-scrollbar-track {
+				background: inherit;
+			}
+			scrollbar-color: var(--scrollbarHandle) transparent;
+		}
 	}
 }
 
@@ -312,7 +328,7 @@ function onDrop(ev) {
 	z-index: 2;
 	line-height: var(--deckColumnHeaderHeight);
 	height: var(--deckColumnHeaderHeight);
-	padding: 0 16px;
+	padding: 0 16px 0 30px;
 	font-size: 0.9em;
 	color: var(--panelHeaderFg);
 	background: var(--panelHeaderBg);
@@ -321,6 +337,24 @@ function onDrop(ev) {
 	user-select: none;
 }
 
+.color {
+	position: absolute;
+	top: 12px;
+	left: 12px;
+	width: 3px;
+	height: calc(100% - 24px);
+	background: var(--accent);
+	border-radius: 999px;
+}
+
+.tabShape {
+	position: absolute;
+	top: 0;
+	right: -8px;
+	width: auto;
+	height: calc(100% - 6px);
+}
+
 .title {
 	display: inline-block;
 	align-items: center;
@@ -335,34 +369,39 @@ function onDrop(ev) {
 	z-index: 1;
 	width: var(--deckColumnHeaderHeight);
 	line-height: var(--deckColumnHeaderHeight);
-	color: var(--faceTextButton);
-
-	&:hover {
-		color: var(--faceTextButtonHover);
-	}
-
-	&:active {
-		color: var(--faceTextButtonActive);
-	}
 }
 
 .toggleActive {
 	margin-left: -16px;
 }
 
-.menu {
+.grabber {
 	margin-left: auto;
+	margin-right: 10px;
+	padding: 8px 8px;
+	box-sizing: border-box;
+	height: var(--deckColumnHeaderHeight);
+	cursor: move;
+	user-select: none;
+	opacity: 0.5;
+}
+
+.menu {
 	margin-right: -16px;
 }
 
 .body {
 	height: calc(100% - var(--deckColumnHeaderHeight));
 	overflow-y: auto;
-	overflow-x: hidden; // Safari does not supports clip
 	overflow-x: clip;
-	-webkit-overflow-scrolling: touch;
+	overscroll-behavior-y: contain;
 	box-sizing: border-box;
 	container-type: size;
 	background-color: var(--bg);
+
+	&::-webkit-scrollbar-track {
+		background: var(--panel);
+	}
+	scrollbar-color: var(--scrollbarHandle) var(--panel);
 }
 </style>
diff --git a/packages/frontend/src/ui/deck/direct-column.vue b/packages/frontend/src/ui/deck/direct-column.vue
index 15b76c4d9..dc3f58e6a 100644
--- a/packages/frontend/src/ui/deck/direct-column.vue
+++ b/packages/frontend/src/ui/deck/direct-column.vue
@@ -1,5 +1,5 @@
 <template>
-<XColumn :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
+<XColumn :column="column" :isStacked="isStacked">
 	<template #header><i class="ti ti-mail" style="margin-right: 8px;"></i>{{ column.name }}</template>
 
 	<MkNotes :pagination="pagination"/>
@@ -17,10 +17,6 @@ defineProps<{
 	isStacked: boolean;
 }>();
 
-const emit = defineEmits<{
-	(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
-}>();
-
 const pagination = {
 	endpoint: 'notes/mentions' as const,
 	limit: 10,
diff --git a/packages/frontend/src/ui/deck/list-column.vue b/packages/frontend/src/ui/deck/list-column.vue
index 352c1d246..f36dc6151 100644
--- a/packages/frontend/src/ui/deck/list-column.vue
+++ b/packages/frontend/src/ui/deck/list-column.vue
@@ -1,10 +1,10 @@
 <template>
-<XColumn :menu="menu" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
+<XColumn :menu="menu" :column="column" :isStacked="isStacked">
 	<template #header>
 		<i class="ti ti-list"></i><span style="margin-left: 8px;">{{ column.name }}</span>
 	</template>
 
-	<MkTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId" @after="() => emit('loaded')"/>
+	<MkTimeline v-if="column.listId" ref="timeline" src="list" :list="column.listId"/>
 </XColumn>
 </template>
 
@@ -21,11 +21,6 @@ const props = defineProps<{
 	isStacked: boolean;
 }>();
 
-const emit = defineEmits<{
-	(ev: 'loaded'): void;
-	(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
-}>();
-
 let timeline = $shallowRef<InstanceType<typeof MkTimeline>>();
 
 if (props.column.listId == null) {
diff --git a/packages/frontend/src/ui/deck/main-column.vue b/packages/frontend/src/ui/deck/main-column.vue
index f3826a8d3..169fac70a 100644
--- a/packages/frontend/src/ui/deck/main-column.vue
+++ b/packages/frontend/src/ui/deck/main-column.vue
@@ -1,5 +1,5 @@
 <template>
-<XColumn v-if="deckStore.state.alwaysShowMainColumn || mainRouter.currentRoute.value.name !== 'index'" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
+<XColumn v-if="deckStore.state.alwaysShowMainColumn || mainRouter.currentRoute.value.name !== 'index'" :column="column" :isStacked="isStacked">
 	<template #header>
 		<template v-if="pageMetadata?.value">
 			<i :class="pageMetadata?.value.icon"></i>
@@ -25,10 +25,6 @@ defineProps<{
 	isStacked: boolean;
 }>();
 
-const emit = defineEmits<{
-	(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
-}>();
-
 let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
 
 provide('router', mainRouter);
diff --git a/packages/frontend/src/ui/deck/mentions-column.vue b/packages/frontend/src/ui/deck/mentions-column.vue
index 852d7a8f7..98cf89874 100644
--- a/packages/frontend/src/ui/deck/mentions-column.vue
+++ b/packages/frontend/src/ui/deck/mentions-column.vue
@@ -1,5 +1,5 @@
 <template>
-<XColumn :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
+<XColumn :column="column" :isStacked="isStacked">
 	<template #header><i class="ti ti-at" style="margin-right: 8px;"></i>{{ column.name }}</template>
 
 	<MkNotes :pagination="pagination"/>
@@ -17,10 +17,6 @@ defineProps<{
 	isStacked: boolean;
 }>();
 
-const emit = defineEmits<{
-	(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
-}>();
-
 const pagination = {
 	endpoint: 'notes/mentions' as const,
 	limit: 10,
diff --git a/packages/frontend/src/ui/deck/notifications-column.vue b/packages/frontend/src/ui/deck/notifications-column.vue
index 9d133035f..8cf6ec1f6 100644
--- a/packages/frontend/src/ui/deck/notifications-column.vue
+++ b/packages/frontend/src/ui/deck/notifications-column.vue
@@ -1,8 +1,8 @@
 <template>
-<XColumn :column="column" :is-stacked="isStacked" :menu="menu" @parent-focus="$event => emit('parent-focus', $event)">
+<XColumn :column="column" :isStacked="isStacked" :menu="menu">
 	<template #header><i class="ti ti-bell" style="margin-right: 8px;"></i>{{ column.name }}</template>
 
-	<XNotifications :include-types="column.includingTypes"/>
+	<XNotifications :includeTypes="column.includingTypes"/>
 </XColumn>
 </template>
 
@@ -19,10 +19,6 @@ const props = defineProps<{
 	isStacked: boolean;
 }>();
 
-const emit = defineEmits<{
-	(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
-}>();
-
 function func() {
 	os.popup(defineAsyncComponent(() => import('@/components/MkNotificationSettingWindow.vue')), {
 		includingTypes: props.column.includingTypes,
diff --git a/packages/frontend/src/ui/deck/role-timeline-column.vue b/packages/frontend/src/ui/deck/role-timeline-column.vue
index 5783b3f07..a0b7f1c67 100644
--- a/packages/frontend/src/ui/deck/role-timeline-column.vue
+++ b/packages/frontend/src/ui/deck/role-timeline-column.vue
@@ -1,10 +1,10 @@
 <template>
-<XColumn :menu="menu" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
+<XColumn :menu="menu" :column="column" :isStacked="isStacked">
 	<template #header>
 		<i class="ti ti-badge"></i><span style="margin-left: 8px;">{{ column.name }}</span>
 	</template>
 
-	<MkTimeline v-if="column.roleId" ref="timeline" src="role" :role="column.roleId" @after="() => emit('loaded')"/>
+	<MkTimeline v-if="column.roleId" ref="timeline" src="role" :role="column.roleId"/>
 </XColumn>
 </template>
 
@@ -21,11 +21,6 @@ const props = defineProps<{
 	isStacked: boolean;
 }>();
 
-const emit = defineEmits<{
-	(ev: 'loaded'): void;
-	(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
-}>();
-
 let timeline = $shallowRef<InstanceType<typeof MkTimeline>>();
 
 onMounted(() => {
@@ -35,7 +30,7 @@ onMounted(() => {
 });
 
 async function setRole() {
-	const roles = await os.api('roles/list');
+	const roles = (await os.api('roles/list')).filter(x => x.isExplorable);
 	const { canceled, result: role } = await os.select({
 		title: i18n.ts.role,
 		items: roles.map(x => ({
diff --git a/packages/frontend/src/ui/deck/tl-column.vue b/packages/frontend/src/ui/deck/tl-column.vue
index c23943d4d..4844ad11f 100644
--- a/packages/frontend/src/ui/deck/tl-column.vue
+++ b/packages/frontend/src/ui/deck/tl-column.vue
@@ -1,5 +1,5 @@
 <template>
-<XColumn :menu="menu" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
+<XColumn :menu="menu" :column="column" :isStacked="isStacked">
 	<template #header>
 		<i v-if="column.tl === 'home'" class="ti ti-home"></i>
 		<i v-else-if="column.tl === 'local'" class="ti ti-planet"></i>
@@ -15,7 +15,7 @@
 		</p>
 		<p :class="$style.disabledDescription">{{ i18n.ts._disabledTimeline.description }}</p>
 	</div>
-	<MkTimeline v-else-if="column.tl" ref="timeline" :key="column.tl" :src="column.tl" @after="() => emit('loaded')"/>
+	<MkTimeline v-else-if="column.tl" ref="timeline" :key="column.tl" :src="column.tl"/>
 </XColumn>
 </template>
 
@@ -34,11 +34,6 @@ const props = defineProps<{
 	isStacked: boolean;
 }>();
 
-const emit = defineEmits<{
-	(ev: 'loaded'): void;
-	(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
-}>();
-
 let disabled = $ref(false);
 
 const isLocalTimelineAvailable = (($i == null && instance.policies.ltlAvailable) || ($i != null && $i.policies.ltlAvailable));
diff --git a/packages/frontend/src/ui/deck/widgets-column.vue b/packages/frontend/src/ui/deck/widgets-column.vue
index 3b5b72799..da14e54f7 100644
--- a/packages/frontend/src/ui/deck/widgets-column.vue
+++ b/packages/frontend/src/ui/deck/widgets-column.vue
@@ -1,10 +1,10 @@
 <template>
-<XColumn :menu="menu" :naked="true" :column="column" :is-stacked="isStacked" @parent-focus="$event => emit('parent-focus', $event)">
+<XColumn :menu="menu" :naked="true" :column="column" :isStacked="isStacked">
 	<template #header><i class="ti ti-apps" style="margin-right: 8px;"></i>{{ column.name }}</template>
 
 	<div :class="$style.root">
 		<div v-if="!(column.widgets && column.widgets.length > 0) && !edit" :class="$style.intro">{{ i18n.ts._deck.widgetsIntroduction }}</div>
-		<XWidgets :edit="edit" :widgets="column.widgets ?? []" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="edit = false"/>
+		<XWidgets :edit="edit" :widgets="column.widgets ?? []" @addWidget="addWidget" @removeWidget="removeWidget" @updateWidget="updateWidget" @updateWidgets="updateWidgets" @exit="edit = false"/>
 	</div>
 </XColumn>
 </template>
@@ -21,10 +21,6 @@ const props = defineProps<{
 	isStacked: boolean;
 }>();
 
-const emit = defineEmits<{
-	(ev: 'parent-focus', direction: 'up' | 'down' | 'left' | 'right'): void;
-}>();
-
 let edit = $ref(false);
 
 function addWidget(widget) {
diff --git a/packages/frontend/src/ui/minimum.vue b/packages/frontend/src/ui/minimum.vue
new file mode 100644
index 000000000..e656f00bb
--- /dev/null
+++ b/packages/frontend/src/ui/minimum.vue
@@ -0,0 +1,34 @@
+<template>
+<div :class="$style.root" style="container-type: inline-size;">
+	<RouterView/>
+
+	<XCommon/>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { provide, ComputedRef } from 'vue';
+import XCommon from './_common_/common.vue';
+import { mainRouter } from '@/router';
+import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata';
+import { instanceName } from '@/config';
+
+let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
+
+provide('router', mainRouter);
+provideMetadataReceiver((info) => {
+	pageMetadata = info;
+	if (pageMetadata.value) {
+		document.title = `${pageMetadata.value.title} | ${instanceName}`;
+	}
+});
+
+document.documentElement.style.overflowY = 'scroll';
+</script>
+
+<style lang="scss" module>
+.root {
+	min-height: 100dvh;
+	box-sizing: border-box;
+}
+</style>
diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue
index 27d0c26ac..c0da59a57 100644
--- a/packages/frontend/src/ui/universal.vue
+++ b/packages/frontend/src/ui/universal.vue
@@ -1,19 +1,15 @@
 <template>
-<div :class="[$style.root, { [$style.withWallpaper]: wallpaper }]">
+<div :class="$style.root">
 	<XSidebar v-if="!isMobile" :class="$style.sidebar"/>
 
-	<MkStickyContainer :class="$style.contents">
+	<MkStickyContainer ref="contents" :class="$style.contents" style="container-type: inline-size;" @contextmenu.stop="onContextmenu">
 		<template #header><XStatusBars :class="$style.statusbars"/></template>
-		<main style="min-width: 0;" :style="{ background: pageMetadata?.value?.bg }" @contextmenu.stop="onContextmenu">
-			<div :class="$style.content" style="container-type: inline-size;">
-				<RouterView/>
-			</div>
-			<div :class="$style.spacer"></div>
-		</main>
+		<RouterView/>
+		<div :class="$style.spacer"></div>
 	</MkStickyContainer>
 
-	<div v-if="isDesktop" ref="widgetsEl" :class="$style.widgets">
-		<XWidgets :margin-top="'var(--margin)'" @mounted="attachSticky"/>
+	<div v-if="isDesktop" :class="$style.widgets">
+		<XWidgets/>
 	</div>
 
 	<button v-if="!isDesktop && !isMobile" :class="$style.widgetButton" class="_button" @click="widgetsShowing = true"><i class="ti ti-apps"></i></button>
@@ -27,10 +23,10 @@
 	</div>
 
 	<Transition
-		:enter-active-class="defaultStore.state.animation ? $style.transition_menuDrawerBg_enterActive : ''"
-		:leave-active-class="defaultStore.state.animation ? $style.transition_menuDrawerBg_leaveActive : ''"
-		:enter-from-class="defaultStore.state.animation ? $style.transition_menuDrawerBg_enterFrom : ''"
-		:leave-to-class="defaultStore.state.animation ? $style.transition_menuDrawerBg_leaveTo : ''"
+		:enterActiveClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_enterActive : ''"
+		:leaveActiveClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_leaveActive : ''"
+		:enterFromClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_enterFrom : ''"
+		:leaveToClass="defaultStore.state.animation ? $style.transition_menuDrawerBg_leaveTo : ''"
 	>
 		<div
 			v-if="drawerMenuShowing"
@@ -42,10 +38,10 @@
 	</Transition>
 
 	<Transition
-		:enter-active-class="defaultStore.state.animation ? $style.transition_menuDrawer_enterActive : ''"
-		:leave-active-class="defaultStore.state.animation ? $style.transition_menuDrawer_leaveActive : ''"
-		:enter-from-class="defaultStore.state.animation ? $style.transition_menuDrawer_enterFrom : ''"
-		:leave-to-class="defaultStore.state.animation ? $style.transition_menuDrawer_leaveTo : ''"
+		:enterActiveClass="defaultStore.state.animation ? $style.transition_menuDrawer_enterActive : ''"
+		:leaveActiveClass="defaultStore.state.animation ? $style.transition_menuDrawer_leaveActive : ''"
+		:enterFromClass="defaultStore.state.animation ? $style.transition_menuDrawer_enterFrom : ''"
+		:leaveToClass="defaultStore.state.animation ? $style.transition_menuDrawer_leaveTo : ''"
 	>
 		<div v-if="drawerMenuShowing" :class="$style.menuDrawer">
 			<XDrawerMenu/>
@@ -53,10 +49,10 @@
 	</Transition>
 
 	<Transition
-		:enter-active-class="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_enterActive : ''"
-		:leave-active-class="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_leaveActive : ''"
-		:enter-from-class="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_enterFrom : ''"
-		:leave-to-class="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_leaveTo : ''"
+		:enterActiveClass="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_enterActive : ''"
+		:leaveActiveClass="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_leaveActive : ''"
+		:enterFromClass="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_enterFrom : ''"
+		:leaveToClass="defaultStore.state.animation ? $style.transition_widgetsDrawerBg_leaveTo : ''"
 	>
 		<div
 			v-if="widgetsShowing"
@@ -68,10 +64,10 @@
 	</Transition>
 
 	<Transition
-		:enter-active-class="defaultStore.state.animation ? $style.transition_widgetsDrawer_enterActive : ''"
-		:leave-active-class="defaultStore.state.animation ? $style.transition_widgetsDrawer_leaveActive : ''"
-		:enter-from-class="defaultStore.state.animation ? $style.transition_widgetsDrawer_enterFrom : ''"
-		:leave-to-class="defaultStore.state.animation ? $style.transition_widgetsDrawer_leaveTo : ''"
+		:enterActiveClass="defaultStore.state.animation ? $style.transition_widgetsDrawer_enterActive : ''"
+		:leaveActiveClass="defaultStore.state.animation ? $style.transition_widgetsDrawer_leaveActive : ''"
+		:enterFromClass="defaultStore.state.animation ? $style.transition_widgetsDrawer_enterFrom : ''"
+		:leaveToClass="defaultStore.state.animation ? $style.transition_widgetsDrawer_leaveTo : ''"
 	>
 		<div v-if="widgetsShowing" :class="$style.widgetsDrawer">
 			<button class="_button" :class="$style.widgetsCloseButton" @click="widgetsShowing = false"><i class="ti ti-x"></i></button>
@@ -84,10 +80,10 @@
 </template>
 
 <script lang="ts" setup>
-import { defineAsyncComponent, provide, onMounted, computed, ref, ComputedRef, watch, inject, Ref } from 'vue';
+import { defineAsyncComponent, provide, onMounted, computed, ref, ComputedRef, watch, shallowRef, Ref } from 'vue';
 import XCommon from './_common_/common.vue';
+import type MkStickyContainer from '@/components/global/MkStickyContainer.vue';
 import { instanceName } from '@/config';
-import { StickySidebar } from '@/scripts/sticky-sidebar';
 import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue';
 import * as os from '@/os';
 import { defaultStore } from '@/store';
@@ -99,6 +95,7 @@ import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata';
 import { deviceKind } from '@/scripts/device-kind';
 import { miLocalStorage } from '@/local-storage';
 import { CURRENT_STICKY_BOTTOM } from '@/const';
+
 const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue'));
 const XSidebar = defineAsyncComponent(() => import('@/ui/_common_/navbar.vue'));
 const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue'));
@@ -114,9 +111,9 @@ window.addEventListener('resize', () => {
 });
 
 let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
-const widgetsEl = $shallowRef<HTMLElement>();
 const widgetsShowing = $ref(false);
 const navFooter = $shallowRef<HTMLElement>();
+const contents = shallowRef<InstanceType<typeof MkStickyContainer>>();
 
 provide('router', mainRouter);
 provideMetadataReceiver((info) => {
@@ -140,8 +137,6 @@ mainRouter.on('change', () => {
 	drawerMenuShowing.value = false;
 });
 
-document.documentElement.style.overflowY = 'scroll';
-
 if (window.innerWidth > 1024) {
 	const tempUI = miLocalStorage.getItem('ui_temp');
 	if (tempUI) {
@@ -197,19 +192,13 @@ const onContextmenu = (ev) => {
 	}], ev);
 };
 
-const attachSticky = (el) => {
-	const sticky = new StickySidebar(widgetsEl);
-	window.addEventListener('scroll', () => {
-		sticky.calc(window.scrollY);
-	}, { passive: true });
-};
-
 function top() {
-	window.scroll({ top: 0, behavior: 'smooth' });
+	contents.value.rootEl.scrollTo({
+		top: 0,
+		behavior: 'smooth',
+	});
 }
 
-const wallpaper = miLocalStorage.getItem('wallpaper') != null;
-
 let navFooterHeight = $ref(0);
 provide<Ref<number>>(CURRENT_STICKY_BOTTOM, $$(navFooterHeight));
 
@@ -275,28 +264,33 @@ $widgets-hide-threshold: 1090px;
 }
 
 .root {
-	min-height: 100dvh;
+	height: 100dvh;
+	overflow: clip;
+	contain: strict;
 	box-sizing: border-box;
 	display: flex;
 }
 
-.withWallpaper {
-	background: var(--wallpaperOverlay);
-	//backdrop-filter: var(--blur, blur(4px));
-}
-
 .sidebar {
 	border-right: solid 0.5px var(--divider);
 }
 
 .contents {
-	width: 100%;
+	flex: 1;
+	height: 100%;
 	min-width: 0;
+	overflow: auto;
+	overflow-y: scroll;
+	overscroll-behavior: contain;
 	background: var(--bg);
 }
 
 .widgets {
-	padding: 0 var(--margin) calc(var(--margin) + env(safe-area-inset-bottom, 0px));
+	width: 350px;
+	height: 100%;
+	box-sizing: border-box;
+	overflow: auto;
+	padding: var(--margin) var(--margin) calc(var(--margin) + env(safe-area-inset-bottom, 0px));
 	border-left: solid 0.5px var(--divider);
 	background: var(--bg);
 
@@ -328,6 +322,7 @@ $widgets-hide-threshold: 1090px;
 	top: 0;
 	right: 0;
 	z-index: 1001;
+	width: 310px;
 	height: 100dvh;
 	padding: var(--margin) var(--margin) calc(var(--margin) + env(safe-area-inset-bottom, 0px)) !important;
 	box-sizing: border-box;
diff --git a/packages/frontend/src/ui/universal.widgets.vue b/packages/frontend/src/ui/universal.widgets.vue
index 3e0c38bb8..ec5e8bb03 100644
--- a/packages/frontend/src/ui/universal.widgets.vue
+++ b/packages/frontend/src/ui/universal.widgets.vue
@@ -1,6 +1,6 @@
 <template>
-<div :class="$style.root" :style="{ paddingTop: marginTop }">
-	<XWidgets :class="$style.widgets" :edit="editMode" :widgets="widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/>
+<div>
+	<XWidgets :edit="editMode" :widgets="widgets" @addWidget="addWidget" @removeWidget="removeWidget" @updateWidget="updateWidget" @updateWidgets="updateWidgets" @exit="editMode = false"/>
 
 	<button v-if="editMode" class="_textButton" style="font-size: 0.9em;" @click="editMode = false"><i class="ti ti-check"></i> {{ i18n.ts.editWidgetsExit }}</button>
 	<button v-else class="_textButton" data-cy-widget-edit :class="$style.edit" style="font-size: 0.9em;" @click="editMode = true"><i class="ti ti-pencil"></i> {{ i18n.ts.editWidgets }}</button>
@@ -11,7 +11,7 @@
 let editMode = $ref(false);
 </script>
 <script lang="ts" setup>
-import { onMounted } from 'vue';
+import { } from 'vue';
 import XWidgets from '@/components/MkWidgets.vue';
 import { i18n } from '@/i18n';
 import { defaultStore } from '@/store';
@@ -21,28 +21,16 @@ const props = withDefaults(defineProps<{
 	// left = place: leftだけを表示
 	// right = rightとnullを表示
 	place?: 'left' | null | 'right';
-	marginTop?: string;
 }>(), {
 	place: null,
-	marginTop: '0',
 });
 
-const emit = defineEmits<{
-	(ev: 'mounted', el?: Element): void;
-}>();
-
-let rootEl = $shallowRef<HTMLDivElement>();
-
 const widgets = $computed(() => {
 	if (props.place === null) return defaultStore.reactiveState.widgets.value;
 	if (props.place === 'left') return defaultStore.reactiveState.widgets.value.filter(w => w.place === 'left');
 	return defaultStore.reactiveState.widgets.value.filter(w => w.place !== 'left');
 });
 
-onMounted(() => {
-	emit('mounted', rootEl);
-});
-
 function addWidget(widget) {
 	defaultStore.set('widgets', [{
 		...widget,
@@ -82,16 +70,6 @@ function updateWidgets(thisWidgets) {
 </script>
 
 <style lang="scss" module>
-.root {
-	position: sticky;
-	height: min-content;
-	box-sizing: border-box;
-}
-
-.widgets {
-	width: 300px;
-}
-
 .edit {
 	width: 100%;
 }
diff --git a/packages/frontend/src/ui/visitor.vue b/packages/frontend/src/ui/visitor.vue
index 623abbda3..d6de145ed 100644
--- a/packages/frontend/src/ui/visitor.vue
+++ b/packages/frontend/src/ui/visitor.vue
@@ -12,10 +12,10 @@
 	<div class="main">
 		<div v-if="!root" class="header">
 			<div v-if="narrow === false" class="wide">
-				<MkA to="/" class="link" active-class="active"><i class="ti ti-home icon"></i> {{ i18n.ts.home }}</MkA>
-				<MkA v-if="isTimelineAvailable" to="/timeline" class="link" active-class="active"><i class="ti ti-message icon"></i> {{ i18n.ts.timeline }}</MkA>
-				<MkA to="/explore" class="link" active-class="active"><i class="ti ti-hash icon"></i> {{ i18n.ts.explore }}</MkA>
-				<MkA to="/channels" class="link" active-class="active"><i class="ti ti-device-tv icon"></i> {{ i18n.ts.channel }}</MkA>
+				<MkA to="/" class="link" activeClass="active"><i class="ti ti-home icon"></i> {{ i18n.ts.home }}</MkA>
+				<MkA v-if="isTimelineAvailable" to="/timeline" class="link" activeClass="active"><i class="ti ti-message icon"></i> {{ i18n.ts.timeline }}</MkA>
+				<MkA to="/explore" class="link" activeClass="active"><i class="ti ti-hash icon"></i> {{ i18n.ts.explore }}</MkA>
+				<MkA to="/channels" class="link" activeClass="active"><i class="ti ti-device-tv icon"></i> {{ i18n.ts.channel }}</MkA>
 			</div>
 			<div v-else-if="narrow === true" class="narrow">
 				<button class="menu _button" @click="showMenu = true">
@@ -44,15 +44,15 @@
 
 	<Transition :name="'tray'">
 		<div v-if="showMenu" class="menu">
-			<MkA to="/" class="link" active-class="active"><i class="ti ti-home icon"></i>{{ i18n.ts.home }}</MkA>
-			<MkA v-if="isTimelineAvailable" to="/timeline" class="link" active-class="active"><i class="ti ti-message icon"></i>{{ i18n.ts.timeline }}</MkA>
-			<MkA to="/explore" class="link" active-class="active"><i class="ti ti-hash icon"></i>{{ i18n.ts.explore }}</MkA>
-			<MkA to="/announcements" class="link" active-class="active"><i class="ti ti-speakerphone icon"></i>{{ i18n.ts.announcements }}</MkA>
-			<MkA to="/channels" class="link" active-class="active"><i class="ti ti-device-tv icon"></i>{{ i18n.ts.channel }}</MkA>
+			<MkA to="/" class="link" activeClass="active"><i class="ti ti-home icon"></i>{{ i18n.ts.home }}</MkA>
+			<MkA v-if="isTimelineAvailable" to="/timeline" class="link" activeClass="active"><i class="ti ti-message icon"></i>{{ i18n.ts.timeline }}</MkA>
+			<MkA to="/explore" class="link" activeClass="active"><i class="ti ti-hash icon"></i>{{ i18n.ts.explore }}</MkA>
+			<MkA to="/announcements" class="link" activeClass="active"><i class="ti ti-speakerphone icon"></i>{{ i18n.ts.announcements }}</MkA>
+			<MkA to="/channels" class="link" activeClass="active"><i class="ti ti-device-tv icon"></i>{{ i18n.ts.channel }}</MkA>
 			<div class="divider"></div>
-			<MkA to="/pages" class="link" active-class="active"><i class="ti ti-news icon"></i>{{ i18n.ts.pages }}</MkA>
-			<MkA to="/play" class="link" active-class="active"><i class="ti ti-player-play icon"></i>Play</MkA>
-			<MkA to="/gallery" class="link" active-class="active"><i class="ti ti-icons icon"></i>{{ i18n.ts.gallery }}</MkA>
+			<MkA to="/pages" class="link" activeClass="active"><i class="ti ti-news icon"></i>{{ i18n.ts.pages }}</MkA>
+			<MkA to="/play" class="link" activeClass="active"><i class="ti ti-player-play icon"></i>Play</MkA>
+			<MkA to="/gallery" class="link" activeClass="active"><i class="ti ti-icons icon"></i>{{ i18n.ts.gallery }}</MkA>
 			<div class="action">
 				<button class="_buttonPrimary" @click="signup()">{{ i18n.ts.signup }}</button>
 				<button class="_button" @click="signin()">{{ i18n.ts.login }}</button>
diff --git a/packages/frontend/src/ui/zen.vue b/packages/frontend/src/ui/zen.vue
index 628390e3f..d516a5df7 100644
--- a/packages/frontend/src/ui/zen.vue
+++ b/packages/frontend/src/ui/zen.vue
@@ -1,9 +1,17 @@
 <template>
-<div class="mk-app" style="container-type: inline-size;">
+<div :class="showBottom ? $style.rootWithBottom : $style.root" style="container-type: inline-size;">
 	<RouterView/>
 
 	<XCommon/>
 </div>
+
+<!--
+	デッキUIが設定されている場合はデッキUIに戻れるようにする (ただし?zenが明示された場合は表示しない)
+	See https://github.com/misskey-dev/misskey/issues/10905
+-->
+<div v-if="showBottom" :class="$style.bottom">
+	<button v-tooltip="i18n.ts.goToMisskey" :class="['_button', '_shadow', $style.button]" @click="goToMisskey"><i class="ti ti-home"></i></button>
+</div>
 </template>
 
 <script lang="ts" setup>
@@ -11,10 +19,13 @@ import { provide, ComputedRef } from 'vue';
 import XCommon from './_common_/common.vue';
 import { mainRouter } from '@/router';
 import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata';
-import { instanceName } from '@/config';
+import { instanceName, ui } from '@/config';
+import { i18n } from '@/i18n';
 
 let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
 
+const showBottom = !(new URLSearchParams(location.search)).has('zen') && ui === 'deck';
+
 provide('router', mainRouter);
 provideMetadataReceiver((info) => {
 	pageMetadata = info;
@@ -23,12 +34,41 @@ provideMetadataReceiver((info) => {
 	}
 });
 
+function goToMisskey() {
+	window.location.href = '/';
+}
+
 document.documentElement.style.overflowY = 'scroll';
 </script>
 
-<style lang="scss" scoped>
-.mk-app {
+<style lang="scss" module>
+.root {
 	min-height: 100dvh;
 	box-sizing: border-box;
 }
+
+.rootWithBottom {
+	min-height: calc(100dvh - (60px + (var(--margin) * 2) + env(safe-area-inset-bottom, 0px)));
+	box-sizing: border-box;
+}
+
+.bottom {
+	height: calc(60px + (var(--margin) * 2) + env(safe-area-inset-bottom, 0px));
+	width: 100%;
+	margin-top: auto;
+}
+
+.button {
+	position: fixed !important;
+	padding: 0;
+	aspect-ratio: 1;
+	width: 100%;
+	max-width: 60px;
+	margin: auto;
+	border-radius: 100%;
+	background: var(--panel);
+	color: var(--fg);
+	right: var(--margin);
+	bottom: calc(var(--margin) + env(safe-area-inset-bottom, 0px));
+}
 </style>
diff --git a/packages/frontend/src/unicode-emoji-indexes/en-US.json b/packages/frontend/src/unicode-emoji-indexes/en-US.json
new file mode 100644
index 000000000..c5544418d
--- /dev/null
+++ b/packages/frontend/src/unicode-emoji-indexes/en-US.json
@@ -0,0 +1,1784 @@
+{
+	"😀": ["face", "smile", "happy", "joy", ": D", "grin"],
+	"😬": ["face", "grimace", "teeth"],
+	"😁": ["face", "happy", "smile", "joy", "kawaii"],
+	"😂": ["face", "cry", "tears", "weep", "happy", "happytears", "haha"],
+	"🤣": ["face", "rolling", "floor", "laughing", "lol", "haha"],
+	"🥳": ["face", "celebration", "woohoo"],
+	"😃": ["face", "happy", "joy", "haha", ": D", ": )", "smile", "funny"],
+	"😄": ["face", "happy", "joy", "funny", "haha", "laugh", "like", ": D", ": )"],
+	"😅": ["face", "hot", "happy", "laugh", "sweat", "smile", "relief"],
+	"🥲": ["face"],
+	"😆": ["happy", "joy", "lol", "satisfied", "haha", "face", "glad", "XD", "laugh"],
+	"😇": ["face", "angel", "heaven", "halo"],
+	"😉": ["face", "happy", "mischievous", "secret", ";)", "smile", "eye"],
+	"😊": ["face", "smile", "happy", "flushed", "crush", "embarrassed", "shy", "joy"],
+	"🙂": ["face", "smile"],
+	"🙃": ["face", "flipped", "silly", "smile"],
+	"☺️": ["face", "blush", "massage", "happiness"],
+	"😋": ["happy", "joy", "tongue", "smile", "face", "silly", "yummy", "nom", "delicious", "savouring"],
+	"😌": ["face", "relaxed", "phew", "massage", "happiness"],
+	"😍": ["face", "love", "like", "affection", "valentines", "infatuation", "crush", "heart"],
+	"🥰": ["face", "love", "like", "affection", "valentines", "infatuation", "crush", "hearts", "adore"],
+	"😘": ["face", "love", "like", "affection", "valentines", "infatuation", "kiss"],
+	"😗": ["love", "like", "face", "3", "valentines", "infatuation", "kiss"],
+	"😙": ["face", "affection", "valentines", "infatuation", "kiss"],
+	"😚": ["face", "love", "like", "affection", "valentines", "infatuation", "kiss"],
+	"😜": ["face", "prank", "childish", "playful", "mischievous", "smile", "wink", "tongue"],
+	"🤪": ["face", "goofy", "crazy"],
+	"🤨": ["face", "distrust", "scepticism", "disapproval", "disbelief", "surprise"],
+	"🧐": ["face", "stuffy", "wealthy"],
+	"😝": ["face", "prank", "playful", "mischievous", "smile", "tongue"],
+	"😛": ["face", "prank", "childish", "playful", "mischievous", "smile", "tongue"],
+	"🤑": ["face", "rich", "dollar", "money"],
+	"🤓": ["face", "nerdy", "geek", "dork"],
+	"🥸": ["face", "nose", "glasses", "incognito"],
+	"😎": ["face", "cool", "smile", "summer", "beach", "sunglass"],
+	"🤩": ["face", "smile", "starry", "eyes", "grinning"],
+	"🤡": ["face"],
+	"🤠": ["face", "cowgirl", "hat"],
+	"🤗": ["face", "smile", "hug"],
+	"😏": ["face", "smile", "mean", "prank", "smug", "sarcasm"],
+	"😶": ["face", "hellokitty"],
+	"😐": ["indifference", "meh", ": |", "neutral"],
+	"😑": ["face", "indifferent", "-_-", "meh", "deadpan"],
+	"😒": ["indifference", "bored", "straight face", "serious", "sarcasm", "unimpressed", "skeptical", "dubious", "side_eye"],
+	"🙄": ["face", "eyeroll", "frustrated"],
+	"🤔": ["face", "hmmm", "think", "consider"],
+	"🤥": ["face", "lie", "pinocchio"],
+	"🤭": ["face", "whoops", "shock", "surprise"],
+	"🤫": ["face", "quiet", "shhh"],
+	"🤬": ["face", "swearing", "cursing", "cussing", "profanity", "expletive"],
+	"🤯": ["face", "shocked", "mind", "blown"],
+	"😳": ["face", "blush", "shy", "flattered"],
+	"😞": ["face", "sad", "upset", "depressed", ": ("],
+	"😟": ["face", "concern", "nervous", ": ("],
+	"😠": ["mad", "face", "annoyed", "frustrated"],
+	"😡": ["angry", "mad", "hate", "despise"],
+	"😔": ["face", "sad", "depressed", "upset"],
+	"😕": ["face", "indifference", "huh", "weird", "hmmm", ": /"],
+	"🙁": ["face", "frowning", "disappointed", "sad", "upset"],
+	"☹": ["face", "sad", "upset", "frown"],
+	"😣": ["face", "sick", "no", "upset", "oops"],
+	"😖": ["face", "confused", "sick", "unwell", "oops", ": S"],
+	"😫": ["sick", "whine", "upset", "frustrated"],
+	"😩": ["face", "tired", "sleepy", "sad", "frustrated", "upset"],
+	"🥺": ["face", "begging", "mercy"],
+	"😤": ["face", "gas", "phew", "proud", "pride"],
+	"😮": ["face", "surprise", "impressed", "wow", "whoa", ": O"],
+	"😱": ["face", "munch", "scared", "omg"],
+	"😨": ["face", "scared", "terrified", "nervous", "oops", "huh"],
+	"😰": ["face", "nervous", "sweat"],
+	"😯": ["face", "woo", "shh"],
+	"😦": ["face", "aw", "what"],
+	"😧": ["face", "stunned", "nervous"],
+	"😢": ["face", "tears", "sad", "depressed", "upset", ": '("],
+	"😥": ["face", "phew", "sweat", "nervous"],
+	"🤤": ["face"],
+	"😪": ["face", "tired", "rest", "nap"],
+	"😓": ["face", "hot", "sad", "tired", "exercise"],
+	"🥵": ["face", "feverish", "heat", "red", "sweating"],
+	"🥶": ["face", "blue", "freezing", "frozen", "frostbite", "icicles"],
+	"😭": ["face", "cry", "tears", "sad", "upset", "depressed"],
+	"😵": ["spent", "unconscious", "xox", "dizzy"],
+	"😲": ["face", "xox", "surprised", "poisoned"],
+	"🤐": ["face", "sealed", "zipper", "secret"],
+	"🤢": ["face", "vomit", "gross", "green", "sick", "throw up", "ill"],
+	"🤧": ["face", "gesundheit", "sneeze", "sick", "allergy"],
+	"🤮": ["face", "sick"],
+	"😷": ["face", "sick", "ill", "disease"],
+	"🤒": ["sick", "temperature", "thermometer", "cold", "fever"],
+	"🤕": ["injured", "clumsy", "bandage", "hurt"],
+	"🥴": ["face", "dizzy", "intoxicated", "tipsy", "wavy"],
+	"🥱": ["face", "tired", "yawning"],
+	"😴": ["face", "tired", "sleepy", "night", "zzz"],
+	"💤": ["sleepy", "tired", "dream"],
+	"😶‍🌫️": [],
+	"😮‍💨": [],
+	"😵‍💫": [],
+	"🫠": ["disappear", "dissolve", "liquid", "melt", "toketa"],
+	"🫢": ["amazement", "awe", "disbelief", "embarrass", "scared", "surprise", "ohoho"],
+	"🫣": ["captivated", "peep", "stare", "chunibyo"],
+	"🫡": ["ok", "salute", "sunny", "troops", "yes", "raja"],
+	"🫥": ["depressed", "disappear", "hide", "introvert", "invisible", "tensen"],
+	"🫤": ["disappointed", "meh", "skeptical", "unsure"],
+	"🥹": ["angry", "cry", "proud", "resist", "sad"],
+	"💩": ["hankey", "shitface", "fail", "turd", "shit"],
+	"😈": ["devil", "horns"],
+	"👿": ["devil", "angry", "horns"],
+	"👹": ["monster", "red", "mask", "halloween", "scary", "creepy", "devil", "demon", "japanese", "ogre"],
+	"👺": ["red", "evil", "mask", "monster", "scary", "creepy", "japanese", "goblin"],
+	"💀": ["dead", "skeleton", "creepy", "death"],
+	"👻": ["halloween", "spooky", "scary"],
+	"👽": ["UFO", "paul", "weird", "outer_space"],
+	"🤖": ["computer", "machine", "bot"],
+	"😺": ["animal", "cats", "happy", "smile"],
+	"😸": ["animal", "cats", "smile"],
+	"😹": ["animal", "cats", "haha", "happy", "tears"],
+	"😻": ["animal", "love", "like", "affection", "cats", "valentines", "heart"],
+	"😼": ["animal", "cats", "smirk"],
+	"😽": ["animal", "cats", "kiss"],
+	"🙀": ["animal", "cats", "munch", "scared", "scream"],
+	"😿": ["animal", "tears", "weep", "sad", "cats", "upset", "cry"],
+	"😾": ["animal", "cats"],
+	"🤲": ["hands", "gesture", "cupped", "prayer"],
+	"🙌": ["gesture", "hooray", "yea", "celebration", "hands"],
+	"👏": ["hands", "praise", "applause", "congrats", "yay"],
+	"👋": ["hands", "gesture", "goodbye", "solong", "farewell", "hello", "hi", "palm"],
+	"🤙": ["hands", "gesture"],
+	"👍": ["thumbsup", "yes", "awesome", "good", "agree", "accept", "cool", "hand", "like"],
+	"👎": ["thumbsdown", "no", "dislike", "hand"],
+	"👊": ["angry", "violence", "fist", "hit", "attack", "hand"],
+	"✊": ["fingers", "hand", "grasp"],
+	"🤛": ["hand", "fistbump"],
+	"🤜": ["hand", "fistbump"],
+	"✌": ["fingers", "ohyeah", "hand", "peace", "victory", "two"],
+	"👌": ["fingers", "limbs", "perfect", "ok", "okay"],
+	"✋": ["fingers", "stop", "highfive", "palm", "ban"],
+	"🤚": ["fingers", "raised", "backhand"],
+	"👐": ["fingers", "butterfly", "hands", "open"],
+	"💪": ["arm", "flex", "hand", "summer", "strong", "biceps"],
+	"🦾": ["flex", "hand", "strong", "biceps"],
+	"🙏": ["please", "hope", "wish", "namaste", "highfive"],
+	"🦶": ["kick", "stomp"],
+	"🦵": ["kick", "limb"],
+	"🦿": ["kick", "limb"],
+	"🤝": ["agreement", "shake"],
+	"☝": ["hand", "fingers", "direction", "up"],
+	"👆": ["fingers", "hand", "direction", "up"],
+	"👇": ["fingers", "hand", "direction", "down"],
+	"👈": ["direction", "fingers", "hand", "left"],
+	"👉": ["fingers", "hand", "direction", "right"],
+	"🖕": ["hand", "fingers", "rude", "middle", "flipping"],
+	"🖐": ["hand", "fingers", "palm"],
+	"🤟": ["hand", "fingers", "gesture"],
+	"🤘": ["hand", "fingers", "evil_eye", "sign_of_horns", "rock_on"],
+	"🤞": ["good", "lucky"],
+	"🖖": ["hand", "fingers", "spock", "star trek"],
+	"✍": ["lower_left_ballpoint_pen", "stationery", "write", "compose"],
+	"🫰": [],
+	"🫱": [],
+	"🫲": [],
+	"🫳": [],
+	"🫴": [],
+	"🫵": [],
+	"🫶": ["moemoekyun"],
+	"🤏": ["hand", "fingers"],
+	"🤌": ["hand", "fingers"],
+	"🤳": ["camera", "phone"],
+	"💅": ["beauty", "manicure", "finger", "fashion", "nail"],
+	"👄": ["mouth", "kiss"],
+	"🫦": [],
+	"🦷": ["teeth", "dentist"],
+	"👅": ["mouth", "playful"],
+	"👂": ["face", "hear", "sound", "listen"],
+	"🦻": ["face", "hear", "sound", "listen"],
+	"👃": ["smell", "sniff"],
+	"👁": ["face", "look", "see", "watch", "stare"],
+	"👀": ["look", "watch", "stalk", "peek", "see"],
+	"🧠": ["smart", "intelligent"],
+	"🫀": [],
+	"🫁": [],
+	"👤": ["user", "person", "human"],
+	"👥": ["user", "person", "human", "group", "team"],
+	"🗣": ["user", "person", "human", "sing", "say", "talk"],
+	"👶": ["child", "boy", "girl", "toddler"],
+	"🧒": ["gender-neutral", "young"],
+	"👦": ["man", "male", "guy", "teenager"],
+	"👧": ["female", "woman", "teenager"],
+	"🧑": ["gender-neutral", "person"],
+	"👨": ["mustache", "father", "dad", "guy", "classy", "sir", "moustache"],
+	"👩": ["female", "girls", "lady"],
+	"🧑‍🦱": ["curly", "afro", "braids", "ringlets"],
+	"👩‍🦱": ["woman", "female", "girl", "curly", "afro", "braids", "ringlets"],
+	"👨‍🦱": ["man", "male", "boy", "guy", "curly", "afro", "braids", "ringlets"],
+	"🧑‍🦰": ["redhead"],
+	"👩‍🦰": ["woman", "female", "girl", "ginger", "redhead"],
+	"👨‍🦰": ["man", "male", "boy", "guy", "ginger", "redhead"],
+	"👱‍♀️": ["woman", "female", "girl", "blonde", "person"],
+	"👱": ["man", "male", "boy", "blonde", "guy", "person"],
+	"🧑‍🦳": ["gray", "old", "white"],
+	"👩‍🦳": ["woman", "female", "girl", "gray", "old", "white"],
+	"👨‍🦳": ["man", "male", "boy", "guy", "gray", "old", "white"],
+	"🧑‍🦲": ["bald", "chemotherapy", "hairless", "shaven"],
+	"👩‍🦲": ["woman", "female", "girl", "bald", "chemotherapy", "hairless", "shaven"],
+	"👨‍🦲": ["man", "male", "boy", "guy", "bald", "chemotherapy", "hairless", "shaven"],
+	"🧔": ["person", "bewhiskered"],
+	"🧓": ["human", "elder", "senior", "gender-neutral"],
+	"👴": ["human", "male", "men", "old", "elder", "senior"],
+	"👵": ["human", "female", "women", "lady", "old", "elder", "senior"],
+	"👲": ["male", "boy", "chinese"],
+	"🧕": ["female", "hijab", "mantilla", "tichel"],
+	"👳‍♀️": ["female", "indian", "hinduism", "arabs", "woman"],
+	"👳": ["male", "indian", "hinduism", "arabs"],
+	"👮‍♀️": ["woman", "police", "law", "legal", "enforcement", "arrest", "911", "female"],
+	"👮": ["man", "police", "law", "legal", "enforcement", "arrest", "911"],
+	"👷‍♀️": ["female", "human", "wip", "build", "construction", "worker", "labor", "woman"],
+	"👷": ["male", "human", "wip", "guy", "build", "construction", "worker", "labor"],
+	"💂‍♀️": ["uk", "gb", "british", "female", "royal", "woman"],
+	"💂": ["uk", "gb", "british", "male", "guy", "royal"],
+	"🕵️‍♀️": ["human", "spy", "detective", "female", "woman"],
+	"🕵": ["human", "spy", "detective"],
+	"🧑‍⚕️": ["doctor", "nurse", "therapist", "healthcare", "human"],
+	"👩‍⚕️": ["doctor", "nurse", "therapist", "healthcare", "woman", "human"],
+	"👨‍⚕️": ["doctor", "nurse", "therapist", "healthcare", "man", "human"],
+	"🧑‍🌾": ["rancher", "gardener", "human"],
+	"👩‍🌾": ["rancher", "gardener", "woman", "human"],
+	"👨‍🌾": ["rancher", "gardener", "man", "human"],
+	"🧑‍🍳": ["chef", "human"],
+	"👩‍🍳": ["chef", "woman", "human"],
+	"👨‍🍳": ["chef", "man", "human"],
+	"🧑‍🎓": ["graduate", "human"],
+	"👩‍🎓": ["graduate", "woman", "human"],
+	"👨‍🎓": ["graduate", "man", "human"],
+	"🧑‍🎤": ["rockstar", "entertainer", "human"],
+	"👩‍🎤": ["rockstar", "entertainer", "woman", "human"],
+	"👨‍🎤": ["rockstar", "entertainer", "man", "human"],
+	"🧑‍🏫": ["instructor", "professor", "human"],
+	"👩‍🏫": ["instructor", "professor", "woman", "human"],
+	"👨‍🏫": ["instructor", "professor", "man", "human"],
+	"🧑‍🏭": ["assembly", "industrial", "human"],
+	"👩‍🏭": ["assembly", "industrial", "woman", "human"],
+	"👨‍🏭": ["assembly", "industrial", "man", "human"],
+	"🧑‍💻": ["coder", "developer", "engineer", "programmer", "software", "human", "laptop", "computer"],
+	"👩‍💻": ["coder", "developer", "engineer", "programmer", "software", "woman", "human", "laptop", "computer"],
+	"👨‍💻": ["coder", "developer", "engineer", "programmer", "software", "man", "human", "laptop", "computer"],
+	"🧑‍💼": ["business", "manager", "human"],
+	"👩‍💼": ["business", "manager", "woman", "human"],
+	"👨‍💼": ["business", "manager", "man", "human"],
+	"🧑‍🔧": ["plumber", "human", "wrench"],
+	"👩‍🔧": ["plumber", "woman", "human", "wrench"],
+	"👨‍🔧": ["plumber", "man", "human", "wrench"],
+	"🧑‍🔬": ["biologist", "chemist", "engineer", "physicist", "human"],
+	"👩‍🔬": ["biologist", "chemist", "engineer", "physicist", "woman", "human"],
+	"👨‍🔬": ["biologist", "chemist", "engineer", "physicist", "man", "human"],
+	"🧑‍🎨": ["painter", "human"],
+	"👩‍🎨": ["painter", "woman", "human"],
+	"👨‍🎨": ["painter", "man", "human"],
+	"🧑‍🚒": ["fireman", "human"],
+	"👩‍🚒": ["fireman", "woman", "human"],
+	"👨‍🚒": ["fireman", "man", "human"],
+	"🧑‍✈️": ["aviator", "plane", "human"],
+	"👩‍✈️": ["aviator", "plane", "woman", "human"],
+	"👨‍✈️": ["aviator", "plane", "man", "human"],
+	"🧑‍🚀": ["space", "rocket", "human"],
+	"👩‍🚀": ["space", "rocket", "woman", "human"],
+	"👨‍🚀": ["space", "rocket", "man", "human"],
+	"🧑‍⚖️": ["justice", "court", "human"],
+	"👩‍⚖️": ["justice", "court", "woman", "human"],
+	"👨‍⚖️": ["justice", "court", "man", "human"],
+	"🦸‍♀️": ["woman", "female", "good", "heroine", "superpowers"],
+	"🦸‍♂️": ["man", "male", "good", "hero", "superpowers"],
+	"🦹‍♀️": ["woman", "female", "evil", "bad", "criminal", "heroine", "superpowers"],
+	"🦹‍♂️": ["man", "male", "evil", "bad", "criminal", "hero", "superpowers"],
+	"🤶": ["woman", "female", "xmas", "mother christmas"],
+	"🧑‍🎄": ["xmas", "christmas"],
+	"🎅": ["festival", "man", "male", "xmas", "father christmas"],
+	"🥷": [],
+	"🧙‍♀️": ["woman", "female", "mage", "witch"],
+	"🧙‍♂️": ["man", "male", "mage", "sorcerer"],
+	"🧝‍♀️": ["woman", "female"],
+	"🧝‍♂️": ["man", "male"],
+	"🧛‍♀️": ["woman", "female"],
+	"🧛‍♂️": ["man", "male", "dracula"],
+	"🧟‍♀️": ["woman", "female", "undead", "walking dead"],
+	"🧟‍♂️": ["man", "male", "dracula", "undead", "walking dead"],
+	"🧞‍♀️": ["woman", "female"],
+	"🧞‍♂️": ["man", "male"],
+	"🧜‍♀️": ["woman", "female", "merwoman", "ariel"],
+	"🧜‍♂️": ["man", "male", "triton"],
+	"🧚‍♀️": ["woman", "female"],
+	"🧚‍♂️": ["man", "male"],
+	"👼": ["heaven", "wings", "halo"],
+	"🧌": [],
+	"🤰": ["baby"],
+	"🫃": [],
+	"🫄": [],
+	"🫅": [],
+	"🤱": ["nursing", "baby"],
+	"👩‍🍼": [],
+	"👨‍🍼": [],
+	"🧑‍🍼": [],
+	"👸": ["girl", "woman", "female", "blond", "crown", "royal", "queen"],
+	"🤴": ["boy", "man", "male", "crown", "royal", "king"],
+	"👰": ["couple", "marriage", "wedding", "woman", "bride"],
+	"👰": ["couple", "marriage", "wedding", "woman", "bride"],
+	"🤵": ["couple", "marriage", "wedding", "groom"],
+	"🤵": ["couple", "marriage", "wedding", "groom"],
+	"🏃‍♀️": ["woman", "walking", "exercise", "race", "running", "female"],
+	"🏃": ["man", "walking", "exercise", "race", "running"],
+	"🚶‍♀️": ["human", "feet", "steps", "woman", "female"],
+	"🚶": ["human", "feet", "steps"],
+	"💃": ["female", "girl", "woman", "fun"],
+	"🕺": ["male", "boy", "fun", "dancer"],
+	"👯": ["female", "bunny", "women", "girls"],
+	"👯‍♂️": ["male", "bunny", "men", "boys"],
+	"👫": ["pair", "people", "human", "love", "date", "dating", "like", "affection", "valentines", "marriage"],
+	"🧑‍🤝‍🧑": ["pair", "couple", "love", "like", "bromance", "friendship", "people", "human"],
+	"👬": ["pair", "couple", "love", "like", "bromance", "friendship", "people", "man", "human"],
+	"👭": ["pair", "couple", "love", "like", "bromance", "friendship", "people", "female", "human"],
+	"🫂": [],
+	"🙇‍♀️": ["woman", "female", "girl"],
+	"🙇": ["man", "male", "boy"],
+	"🤦‍♂️": ["man", "male", "boy", "disbelief"],
+	"🤦‍♀️": ["woman", "female", "girl", "disbelief"],
+	"🤷": ["woman", "female", "girl", "confused", "indifferent", "doubt"],
+	"🤷‍♂️": ["man", "male", "boy", "confused", "indifferent", "doubt"],
+	"💁": ["female", "girl", "woman", "human", "information"],
+	"💁‍♂️": ["male", "boy", "man", "human", "information"],
+	"🙅": ["female", "girl", "woman", "nope"],
+	"🙅‍♂️": ["male", "boy", "man", "nope"],
+	"🙆": ["women", "girl", "female", "pink", "human", "woman"],
+	"🙆‍♂️": ["men", "boy", "male", "blue", "human", "man"],
+	"🙋": ["female", "girl", "woman"],
+	"🙋‍♂️": ["male", "boy", "man"],
+	"🙎": ["female", "girl", "woman"],
+	"🙎‍♂️": ["male", "boy", "man"],
+	"🙍": ["female", "girl", "woman", "sad", "depressed", "discouraged", "unhappy"],
+	"🙍‍♂️": ["male", "boy", "man", "sad", "depressed", "discouraged", "unhappy"],
+	"💇": ["female", "girl", "woman"],
+	"💇‍♂️": ["male", "boy", "man"],
+	"💆": ["female", "girl", "woman", "head"],
+	"💆‍♂️": ["male", "boy", "man", "head"],
+	"🧖‍♀️": ["female", "woman", "spa", "steamroom", "sauna"],
+	"🧖‍♂️": ["male", "man", "spa", "steamroom", "sauna"],
+	"🧏‍♀️": ["woman", "female"],
+	"🧏‍♂️": ["man", "male"],
+	"🧍‍♀️": ["woman", "female"],
+	"🧍‍♂️": ["man", "male"],
+	"🧎‍♀️": ["woman", "female"],
+	"🧎‍♂️": ["man", "male"],
+	"🧑‍🦯": ["accessibility", "blind"],
+	"👩‍🦯": ["woman", "female", "accessibility", "blind"],
+	"👨‍🦯": ["man", "male", "accessibility", "blind"],
+	"🧑‍🦼": ["accessibility"],
+	"👩‍🦼": ["woman", "female", "accessibility"],
+	"👨‍🦼": ["man", "male", "accessibility"],
+	"🧑‍🦽": ["accessibility"],
+	"👩‍🦽": ["woman", "female", "accessibility"],
+	"👨‍🦽": ["man", "male", "accessibility"],
+	"💑": ["pair", "love", "like", "affection", "human", "dating", "valentines", "marriage"],
+	"👩‍❤️‍👩": ["pair", "love", "like", "affection", "human", "dating", "valentines", "marriage"],
+	"👨‍❤️‍👨": ["pair", "love", "like", "affection", "human", "dating", "valentines", "marriage"],
+	"💏": ["pair", "valentines", "love", "like", "dating", "marriage"],
+	"👩‍❤️‍💋‍👩": ["pair", "valentines", "love", "like", "dating", "marriage"],
+	"👨‍❤️‍💋‍👨": ["pair", "valentines", "love", "like", "dating", "marriage"],
+	"👪": ["home", "parents", "child", "mom", "dad", "father", "mother", "people", "human"],
+	"👨‍👩‍👧": ["home", "parents", "people", "human", "child"],
+	"👨‍👩‍👧‍👦": ["home", "parents", "people", "human", "children"],
+	"👨‍👩‍👦‍👦": ["home", "parents", "people", "human", "children"],
+	"👨‍👩‍👧‍👧": ["home", "parents", "people", "human", "children"],
+	"👩‍👩‍👦": ["home", "parents", "people", "human", "children"],
+	"👩‍👩‍👧": ["home", "parents", "people", "human", "children"],
+	"👩‍👩‍👧‍👦": ["home", "parents", "people", "human", "children"],
+	"👩‍👩‍👦‍👦": ["home", "parents", "people", "human", "children"],
+	"👩‍👩‍👧‍👧": ["home", "parents", "people", "human", "children"],
+	"👨‍👨‍👦": ["home", "parents", "people", "human", "children"],
+	"👨‍👨‍👧": ["home", "parents", "people", "human", "children"],
+	"👨‍👨‍👧‍👦": ["home", "parents", "people", "human", "children"],
+	"👨‍👨‍👦‍👦": ["home", "parents", "people", "human", "children"],
+	"👨‍👨‍👧‍👧": ["home", "parents", "people", "human", "children"],
+	"👩‍👦": ["home", "parent", "people", "human", "child"],
+	"👩‍👧": ["home", "parent", "people", "human", "child"],
+	"👩‍👧‍👦": ["home", "parent", "people", "human", "children"],
+	"👩‍👦‍👦": ["home", "parent", "people", "human", "children"],
+	"👩‍👧‍👧": ["home", "parent", "people", "human", "children"],
+	"👨‍👦": ["home", "parent", "people", "human", "child"],
+	"👨‍👧": ["home", "parent", "people", "human", "child"],
+	"👨‍👧‍👦": ["home", "parent", "people", "human", "children"],
+	"👨‍👦‍👦": ["home", "parent", "people", "human", "children"],
+	"👨‍👧‍👧": ["home", "parent", "people", "human", "children"],
+	"🧶": ["ball", "crochet", "knit"],
+	"🧵": ["needle", "sewing", "spool", "string"],
+	"🧥": ["jacket"],
+	"🥼": ["doctor", "experiment", "scientist", "chemist"],
+	"👚": ["fashion", "shopping_bags", "female"],
+	"👕": ["fashion", "cloth", "casual", "shirt", "tee"],
+	"👖": ["fashion", "shopping"],
+	"👔": ["shirt", "suitup", "formal", "fashion", "cloth", "business"],
+	"👗": ["clothes", "fashion", "shopping"],
+	"👙": ["swimming", "female", "woman", "girl", "fashion", "beach", "summer"],
+	"🩱": ["swimming", "female", "woman", "girl", "fashion", "beach", "summer"],
+	"👘": ["dress", "fashion", "women", "female", "japanese"],
+	"🥻": ["dress", "fashion", "women", "female"],
+	"🩲": ["dress", "fashion"],
+	"🩳": ["dress", "fashion"],
+	"💄": ["female", "girl", "fashion", "woman"],
+	"💋": ["face", "lips", "love", "like", "affection", "valentines"],
+	"👣": ["feet", "tracking", "walking", "beach"],
+	"🥿": ["ballet", "slip-on", "slipper"],
+	"👠": ["fashion", "shoes", "female", "pumps", "stiletto"],
+	"👡": ["shoes", "fashion", "flip flops"],
+	"👢": ["shoes", "fashion"],
+	"👞": ["fashion", "male"],
+	"👟": ["shoes", "sports", "sneakers"],
+	"🩴": [],
+	"🩰": ["shoes", "sports"],
+	"🧦": ["stockings", "clothes"],
+	"🧤": ["hands", "winter", "clothes"],
+	"🧣": ["neck", "winter", "clothes"],
+	"👒": ["fashion", "accessories", "female", "lady", "spring"],
+	"🎩": ["magic", "gentleman", "classy", "circus"],
+	"🧢": ["cap", "baseball"],
+	"⛑": ["construction", "build"],
+	"🪖": [],
+	"🎓": ["school", "college", "degree", "university", "graduation", "cap", "hat", "legal", "learn", "education"],
+	"👑": ["king", "kod", "leader", "royalty", "lord"],
+	"🎒": ["student", "education", "bag", "backpack"],
+	"🧳": ["packing", "travel"],
+	"👝": ["bag", "accessories", "shopping"],
+	"👛": ["fashion", "accessories", "money", "sales", "shopping"],
+	"👜": ["fashion", "accessory", "accessories", "shopping"],
+	"💼": ["business", "documents", "work", "law", "legal", "job", "career"],
+	"👓": ["fashion", "accessories", "eyesight", "nerdy", "dork", "geek"],
+	"🕶": ["face", "cool", "accessories"],
+	"🥽": ["eyes", "protection", "safety"],
+	"💍": ["wedding", "propose", "marriage", "valentines", "diamond", "fashion", "jewelry", "gem", "engagement"],
+	"🌂": ["weather", "rain", "drizzle"],
+	"🐶": ["animal", "friend", "nature", "woof", "puppy", "pet", "faithful"],
+	"🐱": ["animal", "meow", "nature", "pet", "kitten"],
+	"🐈‍⬛": ["animal", "meow", "nature", "pet", "kitten"],
+	"🐭": ["animal", "nature", "cheese_wedge", "rodent"],
+	"🐹": ["animal", "nature"],
+	"🐰": ["animal", "nature", "pet", "spring", "magic", "bunny"],
+	"🦊": ["animal", "nature", "face"],
+	"🐻": ["animal", "nature", "wild"],
+	"🐼": ["animal", "nature", "panda"],
+	"🐨": ["animal", "nature"],
+	"🐯": ["animal", "cat", "danger", "wild", "nature", "roar"],
+	"🦁": ["animal", "nature"],
+	"🐮": ["beef", "ox", "animal", "nature", "moo", "milk"],
+	"🐷": ["animal", "oink", "nature"],
+	"🐽": ["animal", "oink"],
+	"🐸": ["animal", "nature", "croak", "toad"],
+	"🦑": ["animal", "nature", "ocean", "sea"],
+	"🐙": ["animal", "creature", "ocean", "sea", "nature", "beach"],
+	"🦐": ["animal", "ocean", "nature", "seafood"],
+	"🐵": ["animal", "nature", "circus"],
+	"🦍": ["animal", "nature", "circus"],
+	"🙈": ["monkey", "animal", "nature", "haha"],
+	"🙉": ["animal", "monkey", "nature"],
+	"🙊": ["monkey", "animal", "nature", "omg"],
+	"🐒": ["animal", "nature", "banana", "circus"],
+	"🐔": ["animal", "cluck", "nature", "bird"],
+	"🐧": ["animal", "nature"],
+	"🐦": ["animal", "nature", "fly", "tweet", "spring"],
+	"🐤": ["animal", "chicken", "bird"],
+	"🐣": ["animal", "chicken", "egg", "born", "baby", "bird"],
+	"🐥": ["animal", "chicken", "baby", "bird"],
+	"🦆": ["animal", "nature", "bird", "mallard"],
+	"🦅": ["animal", "nature", "bird"],
+	"🦉": ["animal", "nature", "bird", "hoot"],
+	"🦇": ["animal", "nature", "blind", "vampire"],
+	"🐺": ["animal", "nature", "wild"],
+	"🐗": ["animal", "nature"],
+	"🐴": ["animal", "brown", "nature"],
+	"🦄": ["animal", "nature", "mystical"],
+	"🐝": ["animal", "insect", "nature", "bug", "spring", "honey"],
+	"🐛": ["animal", "insect", "nature", "worm"],
+	"🦋": ["animal", "insect", "nature", "caterpillar"],
+	"🐌": ["slow", "animal", "shell"],
+	"🐞": ["animal", "insect", "nature", "ladybug"],
+	"🐜": ["animal", "insect", "nature", "bug"],
+	"🦗": ["animal", "cricket", "chirp"],
+	"🕷": ["animal", "arachnid"],
+	"🪲": ["animal"],
+	"🪳": ["animal"],
+	"🪰": ["animal"],
+	"🪱": ["animal"],
+	"🦂": ["animal", "arachnid"],
+	"🦀": ["animal", "crustacean"],
+	"🐍": ["animal", "evil", "nature", "hiss", "python"],
+	"🦎": ["animal", "nature", "reptile"],
+	"🦖": ["animal", "nature", "dinosaur", "tyrannosaurus", "extinct"],
+	"🦕": ["animal", "nature", "dinosaur", "brachiosaurus", "brontosaurus", "diplodocus", "extinct"],
+	"🐢": ["animal", "slow", "nature", "tortoise"],
+	"🐠": ["animal", "swim", "ocean", "beach", "nemo"],
+	"🐟": ["animal", "food", "nature"],
+	"🐡": ["animal", "nature", "food", "sea", "ocean"],
+	"🐬": ["animal", "nature", "fish", "sea", "ocean", "flipper", "fins", "beach"],
+	"🦈": ["animal", "nature", "fish", "sea", "ocean", "jaws", "fins", "beach"],
+	"🐳": ["animal", "nature", "sea", "ocean"],
+	"🐋": ["animal", "nature", "sea", "ocean"],
+	"🐊": ["animal", "nature", "reptile", "lizard", "alligator"],
+	"🐆": ["animal", "nature"],
+	"🦓": ["animal", "nature", "stripes", "safari"],
+	"🐅": ["animal", "nature", "roar"],
+	"🐃": ["animal", "nature", "ox", "cow"],
+	"🐂": ["animal", "cow", "beef"],
+	"🐄": ["beef", "ox", "animal", "nature", "moo", "milk"],
+	"🦌": ["animal", "nature", "horns", "venison"],
+	"🐪": ["animal", "hot", "desert", "hump"],
+	"🐫": ["animal", "nature", "hot", "desert", "hump"],
+	"🦒": ["animal", "nature", "spots", "safari"],
+	"🐘": ["animal", "nature", "nose", "th", "circus"],
+	"🦏": ["animal", "nature", "horn"],
+	"🐐": ["animal", "nature"],
+	"🐏": ["animal", "sheep", "nature"],
+	"🐑": ["animal", "nature", "wool", "shipit"],
+	"🐎": ["animal", "gamble", "luck"],
+	"🐖": ["animal", "nature"],
+	"🐀": ["animal", "mouse", "rodent"],
+	"🐁": ["animal", "nature", "rodent"],
+	"🐓": ["animal", "nature", "chicken"],
+	"🦃": ["animal", "bird"],
+	"🕊": ["animal", "bird"],
+	"🐕": ["animal", "nature", "friend", "doge", "pet", "faithful"],
+	"🐩": ["dog", "animal", "101", "nature", "pet"],
+	"🐈": ["animal", "meow", "pet", "cats"],
+	"🐇": ["animal", "nature", "pet", "magic", "spring"],
+	"🐿": ["animal", "nature", "rodent", "squirrel"],
+	"🦔": ["animal", "nature", "spiny"],
+	"🦝": ["animal", "nature"],
+	"🦙": ["animal", "nature", "alpaca"],
+	"🦛": ["animal", "nature"],
+	"🦘": ["animal", "nature", "australia", "joey", "hop", "marsupial"],
+	"🦡": ["animal", "nature", "honey"],
+	"🦢": ["animal", "nature", "bird"],
+	"🦚": ["animal", "nature", "peahen", "bird"],
+	"🦜": ["animal", "nature", "bird", "pirate", "talk"],
+	"🦞": ["animal", "nature", "bisque", "claws", "seafood"],
+	"🦠": ["amoeba", "bacteria", "germs"],
+	"🦟": ["animal", "nature", "insect", "malaria"],
+	"🦬": ["animal", "nature"],
+	"🦣": ["animal", "nature"],
+	"🦫": ["animal", "nature"],
+	"🐻‍❄️": ["animal", "nature"],
+	"🦤": ["animal", "nature"],
+	"🪶": ["animal", "nature"],
+	"🦭": ["animal", "nature"],
+	"🐾": ["animal", "tracking", "footprints", "dog", "cat", "pet", "feet"],
+	"🐉": ["animal", "myth", "nature", "chinese", "green"],
+	"🐲": ["animal", "myth", "nature", "chinese", "green"],
+	"🦧": ["animal", "nature"],
+	"🦮": ["animal", "nature"],
+	"🐕‍🦺": ["animal", "nature"],
+	"🦥": ["animal", "nature"],
+	"🦦": ["animal", "nature"],
+	"🦨": ["animal", "nature"],
+	"🦩": ["animal", "nature"],
+	"🌵": ["vegetable", "plant", "nature"],
+	"🎄": ["festival", "vacation", "december", "xmas", "celebration"],
+	"🌲": ["plant", "nature"],
+	"🌳": ["plant", "nature"],
+	"🌴": ["plant", "vegetable", "nature", "summer", "beach", "mojito", "tropical"],
+	"🌱": ["plant", "nature", "grass", "lawn", "spring"],
+	"🌿": ["vegetable", "plant", "medicine", "weed", "grass", "lawn"],
+	"☘": ["vegetable", "plant", "nature", "irish", "clover"],
+	"🍀": ["vegetable", "plant", "nature", "lucky", "irish"],
+	"🎍": ["plant", "nature", "vegetable", "panda", "pine_decoration"],
+	"🎋": ["plant", "nature", "branch", "summer"],
+	"🍃": ["nature", "plant", "tree", "vegetable", "grass", "lawn", "spring"],
+	"🍂": ["nature", "plant", "vegetable", "leaves"],
+	"🍁": ["nature", "plant", "vegetable", "ca", "fall"],
+	"🌾": ["nature", "plant"],
+	"🌺": ["plant", "vegetable", "flowers", "beach"],
+	"🌻": ["nature", "plant", "fall"],
+	"🌹": ["flowers", "valentines", "love", "spring"],
+	"🥀": ["plant", "nature", "flower"],
+	"🌷": ["flowers", "plant", "nature", "summer", "spring"],
+	"🌼": ["nature", "flowers", "yellow"],
+	"🌸": ["nature", "plant", "spring", "flower"],
+	"💐": ["flowers", "nature", "spring"],
+	"🍄": ["plant", "vegetable"],
+	"🪴": ["plant"],
+	"🌰": ["food", "squirrel"],
+	"🎃": ["halloween", "light", "pumpkin", "creepy", "fall"],
+	"🐚": ["nature", "sea", "beach"],
+	"🕸": ["animal", "insect", "arachnid", "silk"],
+	"🌎": ["globe", "world", "USA", "international"],
+	"🌍": ["globe", "world", "international"],
+	"🌏": ["globe", "world", "east", "international"],
+	"🪐": ["saturn"],
+	"🌕": ["nature", "yellow", "twilight", "planet", "space", "night", "evening", "sleep"],
+	"🌖": ["nature", "twilight", "planet", "space", "night", "evening", "sleep", "waxing_gibbous_moon"],
+	"🌗": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"],
+	"🌘": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"],
+	"🌑": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"],
+	"🌒": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"],
+	"🌓": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"],
+	"🌔": ["nature", "night", "sky", "gray", "twilight", "planet", "space", "evening", "sleep"],
+	"🌚": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"],
+	"🌝": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"],
+	"🌛": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"],
+	"🌜": ["nature", "twilight", "planet", "space", "night", "evening", "sleep"],
+	"🌞": ["nature", "morning", "sky"],
+	"🌙": ["night", "sleep", "sky", "evening", "magic"],
+	"⭐": ["night", "yellow"],
+	"🌟": ["night", "sparkle", "awesome", "good", "magic"],
+	"💫": ["star", "sparkle", "shoot", "magic"],
+	"✨": ["stars", "shine", "shiny", "cool", "awesome", "good", "magic"],
+	"☄": ["space"],
+	"☀️": ["weather", "nature", "brightness", "summer", "beach", "spring"],
+	"🌤": ["weather"],
+	"⛅": ["weather", "nature", "cloudy", "morning", "fall", "spring"],
+	"🌥": ["weather"],
+	"🌦": ["weather"],
+	"☁️": ["weather", "sky"],
+	"🌧": ["weather"],
+	"⛈": ["weather", "lightning"],
+	"🌩": ["weather", "thunder"],
+	"⚡": ["thunder", "weather", "lightning bolt", "fast"],
+	"🔥": ["hot", "cook", "flame"],
+	"💥": ["bomb", "explode", "explosion", "collision", "blown"],
+	"❄️": ["winter", "season", "cold", "weather", "christmas", "xmas"],
+	"🌨": ["weather"],
+	"⛄": ["winter", "season", "cold", "weather", "christmas", "xmas", "frozen", "without_snow"],
+	"☃": ["winter", "season", "cold", "weather", "christmas", "xmas", "frozen"],
+	"🌬": ["gust", "air"],
+	"💨": ["wind", "air", "fast", "shoo", "fart", "smoke", "puff"],
+	"🌪": ["weather", "cyclone", "twister"],
+	"🌫": ["weather"],
+	"☂": ["weather", "spring"],
+	"☔": ["rainy", "weather", "spring"],
+	"💧": ["water", "drip", "faucet", "spring"],
+	"💦": ["water", "drip", "oops"],
+	"🌊": ["sea", "water", "wave", "nature", "tsunami", "disaster"],
+	"🪷": [],
+	"🪸": [],
+	"🪹": [],
+	"🪺": [],
+	"🍏": ["fruit", "nature"],
+	"🍎": ["fruit", "mac", "school"],
+	"🍐": ["fruit", "nature", "food"],
+	"🍊": ["food", "fruit", "nature", "orange"],
+	"🍋": ["fruit", "nature"],
+	"🍌": ["fruit", "food", "monkey"],
+	"🍉": ["fruit", "food", "picnic", "summer"],
+	"🍇": ["fruit", "food", "wine"],
+	"🍓": ["fruit", "food", "nature"],
+	"🍈": ["fruit", "nature", "food"],
+	"🍒": ["food", "fruit"],
+	"🍑": ["fruit", "nature", "food"],
+	"🍍": ["fruit", "nature", "food"],
+	"🥥": ["fruit", "nature", "food", "palm"],
+	"🥝": ["fruit", "food"],
+	"🥭": ["fruit", "food", "tropical"],
+	"🥑": ["fruit", "food"],
+	"🥦": ["fruit", "food", "vegetable"],
+	"🍅": ["fruit", "vegetable", "nature", "food"],
+	"🍆": ["vegetable", "nature", "food", "aubergine"],
+	"🥒": ["fruit", "food", "pickle"],
+	"🫐": ["fruit", "food"],
+	"🫒": ["fruit", "food"],
+	"🫑": ["fruit", "food"],
+	"🥕": ["vegetable", "food", "orange"],
+	"🌶": ["food", "spicy", "chilli", "chili"],
+	"🥔": ["food", "tuber", "vegatable", "starch"],
+	"🌽": ["food", "vegetable", "plant"],
+	"🥬": ["food", "vegetable", "plant", "bok choy", "cabbage", "kale", "lettuce"],
+	"🍠": ["food", "nature"],
+	"🥜": ["food", "nut"],
+	"🧄": ["food"],
+	"🧅": ["food"],
+	"🍯": ["bees", "sweet", "kitchen"],
+	"🥐": ["food", "bread", "french"],
+	"🍞": ["food", "wheat", "breakfast", "toast"],
+	"🥖": ["food", "bread", "french"],
+	"🥯": ["food", "bread", "bakery", "schmear"],
+	"🥨": ["food", "bread", "twisted"],
+	"🧀": ["food", "chadder"],
+	"🥚": ["food", "chicken", "breakfast"],
+	"🥓": ["food", "breakfast", "pork", "pig", "meat"],
+	"🥩": ["food", "cow", "meat", "cut", "chop", "lambchop", "porkchop"],
+	"🥞": ["food", "breakfast", "flapjacks", "hotcakes"],
+	"🍗": ["food", "meat", "drumstick", "bird", "chicken", "turkey"],
+	"🍖": ["good", "food", "drumstick"],
+	"🦴": ["skeleton"],
+	"🍤": ["food", "animal", "appetizer", "summer"],
+	"🍳": ["food", "breakfast", "kitchen", "egg"],
+	"🍔": ["meat", "fast food", "beef", "cheeseburger", "mcdonalds", "burger king"],
+	"🍟": ["chips", "snack", "fast food"],
+	"🥙": ["food", "flatbread", "stuffed", "gyro"],
+	"🌭": ["food", "frankfurter"],
+	"🍕": ["food", "party"],
+	"🥪": ["food", "lunch", "bread"],
+	"🥫": ["food", "soup"],
+	"🍝": ["food", "italian", "noodle"],
+	"🌮": ["food", "mexican"],
+	"🌯": ["food", "mexican"],
+	"🥗": ["food", "healthy", "lettuce"],
+	"🥘": ["food", "cooking", "casserole", "paella"],
+	"🍜": ["food", "japanese", "noodle", "chopsticks"],
+	"🍲": ["food", "meat", "soup"],
+	"🍥": ["food", "japan", "sea", "beach", "narutomaki", "pink", "swirl", "kamaboko", "surimi", "ramen"],
+	"🥠": ["food", "prophecy"],
+	"🍣": ["food", "fish", "japanese", "rice"],
+	"🍱": ["food", "japanese", "box"],
+	"🍛": ["food", "spicy", "hot", "indian"],
+	"🍙": ["food", "japanese"],
+	"🍚": ["food", "china", "asian"],
+	"🍘": ["food", "japanese"],
+	"🍢": ["food", "japanese"],
+	"🍡": ["food", "dessert", "sweet", "japanese", "barbecue", "meat"],
+	"🍧": ["hot", "dessert", "summer"],
+	"🍨": ["food", "hot", "dessert"],
+	"🍦": ["food", "hot", "dessert", "summer"],
+	"🥧": ["food", "dessert", "pastry"],
+	"🍰": ["food", "dessert"],
+	"🧁": ["food", "dessert", "bakery", "sweet"],
+	"🥮": ["food", "autumn"],
+	"🎂": ["food", "dessert", "cake"],
+	"🍮": ["dessert", "food"],
+	"🍬": ["snack", "dessert", "sweet", "lolly"],
+	"🍭": ["food", "snack", "candy", "sweet"],
+	"🍫": ["food", "snack", "dessert", "sweet"],
+	"🍿": ["food", "movie theater", "films", "snack"],
+	"🥟": ["food", "empanada", "pierogi", "potsticker"],
+	"🍩": ["food", "dessert", "snack", "sweet", "donut"],
+	"🍪": ["food", "snack", "oreo", "chocolate", "sweet", "dessert"],
+	"🧇": ["food"],
+	"🧆": ["food"],
+	"🧈": ["food"],
+	"🦪": ["food"],
+	"🫓": ["food"],
+	"🫔": ["food"],
+	"🫕": ["food"],
+	"🥛": ["beverage", "drink", "cow"],
+	"🍺": ["relax", "beverage", "drink", "drunk", "party", "pub", "summer", "alcohol", "booze"],
+	"🍻": ["relax", "beverage", "drink", "drunk", "party", "pub", "summer", "alcohol", "booze"],
+	"🥂": ["beverage", "drink", "party", "alcohol", "celebrate", "cheers", "wine", "champagne", "toast"],
+	"🍷": ["drink", "beverage", "drunk", "alcohol", "booze"],
+	"🥃": ["drink", "beverage", "drunk", "alcohol", "liquor", "booze", "bourbon", "scotch", "whisky", "glass", "shot"],
+	"🍸": ["drink", "drunk", "alcohol", "beverage", "booze", "mojito"],
+	"🍹": ["beverage", "cocktail", "summer", "beach", "alcohol", "booze", "mojito"],
+	"🍾": ["drink", "wine", "bottle", "celebration"],
+	"🍶": ["wine", "drink", "drunk", "beverage", "japanese", "alcohol", "booze"],
+	"🍵": ["drink", "bowl", "breakfast", "green", "british"],
+	"🥤": ["drink", "soda"],
+	"☕": ["beverage", "caffeine", "latte", "espresso"],
+	"🫖": [],
+	"🧋": ["tapioca"],
+	"🍼": ["food", "container", "milk"],
+	"🧃": ["food", "drink"],
+	"🧉": ["food", "drink"],
+	"🧊": ["food"],
+	"🧂": ["condiment", "shaker"],
+	"🥄": ["cutlery", "kitchen", "tableware"],
+	"🍴": ["cutlery", "kitchen"],
+	"🍽": ["food", "eat", "meal", "lunch", "dinner", "restaurant"],
+	"🥣": ["food", "breakfast", "cereal", "oatmeal", "porridge"],
+	"🥡": ["food", "leftovers"],
+	"🥢": ["food"],
+	"🫗": [],
+	"🫘": [],
+	"🫙": [],
+	"⚽": ["sports", "football"],
+	"🏀": ["sports", "balls", "NBA"],
+	"🏈": ["sports", "balls", "NFL"],
+	"⚾": ["sports", "balls"],
+	"🥎": ["sports", "balls"],
+	"🎾": ["sports", "balls", "green"],
+	"🏐": ["sports", "balls"],
+	"🏉": ["sports", "team"],
+	"🥏": ["sports", "frisbee", "ultimate"],
+	"🎱": ["pool", "hobby", "game", "luck", "magic"],
+	"⛳": ["sports", "business", "flag", "hole", "summer"],
+	"🏌️‍♀️": ["sports", "business", "woman", "female"],
+	"🏌": ["sports", "business"],
+	"🏓": ["sports", "pingpong"],
+	"🏸": ["sports"],
+	"🥅": ["sports"],
+	"🏒": ["sports"],
+	"🏑": ["sports"],
+	"🥍": ["sports", "ball", "stick"],
+	"🏏": ["sports"],
+	"🎿": ["sports", "winter", "cold", "snow"],
+	"⛷": ["sports", "winter", "snow"],
+	"🏂": ["sports", "winter"],
+	"🤺": ["sports", "fencing", "sword"],
+	"🤼‍♀️": ["sports", "wrestlers"],
+	"🤼‍♂️": ["sports", "wrestlers"],
+	"🤸‍♀️": ["gymnastics"],
+	"🤸‍♂️": ["gymnastics"],
+	"🤾‍♀️": ["sports"],
+	"🤾‍♂️": ["sports"],
+	"⛸": ["sports"],
+	"🥌": ["sports"],
+	"🛹": ["board"],
+	"🛷": ["sleigh", "luge", "toboggan"],
+	"🏹": ["sports"],
+	"🎣": ["food", "hobby", "summer"],
+	"🥊": ["sports", "fighting"],
+	"🥋": ["judo", "karate", "taekwondo"],
+	"🚣‍♀️": ["sports", "hobby", "water", "ship", "woman", "female"],
+	"🚣": ["sports", "hobby", "water", "ship"],
+	"🧗‍♀️": ["sports", "hobby", "woman", "female", "rock"],
+	"🧗‍♂️": ["sports", "hobby", "man", "male", "rock"],
+	"🏊‍♀️": ["sports", "exercise", "human", "athlete", "water", "summer", "woman", "female"],
+	"🏊": ["sports", "exercise", "human", "athlete", "water", "summer"],
+	"🤽‍♀️": ["sports", "pool"],
+	"🤽‍♂️": ["sports", "pool"],
+	"🧘‍♀️": ["woman", "female", "meditation", "yoga", "serenity", "zen", "mindfulness"],
+	"🧘‍♂️": ["man", "male", "meditation", "yoga", "serenity", "zen", "mindfulness"],
+	"🏄‍♀️": ["sports", "ocean", "sea", "summer", "beach", "woman", "female"],
+	"🏄": ["sports", "ocean", "sea", "summer", "beach"],
+	"🛀": ["clean", "shower", "bathroom"],
+	"⛹️‍♀️": ["sports", "human", "woman", "female"],
+	"⛹": ["sports", "human"],
+	"🏋️‍♀️": ["sports", "training", "exercise", "woman", "female"],
+	"🏋": ["sports", "training", "exercise"],
+	"🚴‍♀️": ["sports", "bike", "exercise", "hipster", "woman", "female"],
+	"🚴": ["sports", "bike", "exercise", "hipster"],
+	"🚵‍♀️": ["transportation", "sports", "human", "race", "bike", "woman", "female"],
+	"🚵": ["transportation", "sports", "human", "race", "bike"],
+	"🏇": ["animal", "betting", "competition", "gambling", "luck"],
+	"🤿": ["sports"],
+	"🪀": ["sports"],
+	"🪁": ["sports"],
+	"🦺": ["sports"],
+	"🪡": [],
+	"🪢": [],
+	"🕴": ["suit", "business", "levitate", "hover", "jump"],
+	"🏆": ["win", "award", "contest", "place", "ftw", "ceremony"],
+	"🎽": ["play", "pageant"],
+	"🏅": ["award", "winning"],
+	"🎖": ["award", "winning", "army"],
+	"🥇": ["award", "winning", "first"],
+	"🥈": ["award", "second"],
+	"🥉": ["award", "third"],
+	"🎗": ["sports", "cause", "support", "awareness"],
+	"🏵": ["flower", "decoration", "military"],
+	"🎫": ["event", "concert", "pass"],
+	"🎟": ["sports", "concert", "entrance"],
+	"🎭": ["acting", "theater", "drama"],
+	"🎨": ["design", "paint", "draw", "colors"],
+	"🎪": ["festival", "carnival", "party"],
+	"🤹‍♀️": ["juggle", "balance", "skill", "multitask"],
+	"🤹‍♂️": ["juggle", "balance", "skill", "multitask"],
+	"🎤": ["sound", "music", "PA", "sing", "talkshow"],
+	"🎧": ["music", "score", "gadgets"],
+	"🎼": ["treble", "clef", "compose"],
+	"🎹": ["piano", "instrument", "compose"],
+	"🥁": ["music", "instrument", "drumsticks", "snare"],
+	"🎷": ["music", "instrument", "jazz", "blues"],
+	"🎺": ["music", "brass"],
+	"🎸": ["music", "instrument"],
+	"🎻": ["music", "instrument", "orchestra", "symphony"],
+	"🪕": ["music", "instrument"],
+	"🪗": ["music", "instrument"],
+	"🪘": ["music", "instrument"],
+	"🎬": ["movie", "film", "record"],
+	"🎮": ["play", "console", "PS4", "controller"],
+	"👾": ["game", "arcade", "play"],
+	"🎯": ["game", "play", "bar", "target", "bullseye"],
+	"🎲": ["dice", "random", "tabletop", "play", "luck"],
+	"♟️": ["expendable"],
+	"🎰": ["bet", "gamble", "vegas", "fruit machine", "luck", "casino"],
+	"🧩": ["interlocking", "puzzle", "piece"],
+	"🎳": ["sports", "fun", "play"],
+	"🪄": [],
+	"🪅": [],
+	"🪆": [],
+	"🪬": [],
+	"🪩": [],
+	"🚗": ["red", "transportation", "vehicle"],
+	"🚕": ["uber", "vehicle", "cars", "transportation"],
+	"🚙": ["transportation", "vehicle"],
+	"🚌": ["car", "vehicle", "transportation"],
+	"🚎": ["bart", "transportation", "vehicle"],
+	"🏎": ["sports", "race", "fast", "formula", "f1"],
+	"🚓": ["vehicle", "cars", "transportation", "law", "legal", "enforcement"],
+	"🚑": ["health", "911", "hospital"],
+	"🚒": ["transportation", "cars", "vehicle"],
+	"🚐": ["vehicle", "car", "transportation"],
+	"🚚": ["cars", "transportation"],
+	"🚛": ["vehicle", "cars", "transportation", "express"],
+	"🚜": ["vehicle", "car", "farming", "agriculture"],
+	"🛴": ["vehicle", "kick", "razor"],
+	"🏍": ["race", "sports", "fast"],
+	"🚲": ["sports", "bicycle", "exercise", "hipster"],
+	"🛵": ["vehicle", "vespa", "sasha"],
+	"🦽": ["vehicle"],
+	"🦼": ["vehicle"],
+	"🛺": ["vehicle"],
+	"🪂": ["vehicle"],
+	"🚨": ["police", "ambulance", "911", "emergency", "alert", "error", "pinged", "law", "legal"],
+	"🚔": ["vehicle", "law", "legal", "enforcement", "911"],
+	"🚍": ["vehicle", "transportation"],
+	"🚘": ["car", "vehicle", "transportation"],
+	"🚖": ["vehicle", "cars", "uber"],
+	"🚡": ["transportation", "vehicle", "ski"],
+	"🚠": ["transportation", "vehicle", "ski"],
+	"🚟": ["vehicle", "transportation"],
+	"🚃": ["transportation", "vehicle", "train"],
+	"🚋": ["transportation", "vehicle", "carriage", "public", "travel"],
+	"🚝": ["transportation", "vehicle"],
+	"🚄": ["transportation", "vehicle"],
+	"🚅": ["transportation", "vehicle", "speed", "fast", "public", "travel"],
+	"🚈": ["transportation", "vehicle"],
+	"🚞": ["transportation", "vehicle"],
+	"🚂": ["transportation", "vehicle", "train"],
+	"🚆": ["transportation", "vehicle"],
+	"🚇": ["transportation", "blue-square", "mrt", "underground", "tube"],
+	"🚊": ["transportation", "vehicle"],
+	"🚉": ["transportation", "vehicle", "public"],
+	"🛸": ["transportation", "vehicle", "ufo"],
+	"🚁": ["transportation", "vehicle", "fly"],
+	"🛩": ["flight", "transportation", "fly", "vehicle"],
+	"✈️": ["vehicle", "transportation", "flight", "fly"],
+	"🛫": ["airport", "flight", "landing"],
+	"🛬": ["airport", "flight", "boarding"],
+	"⛵": ["ship", "summer", "transportation", "water", "sailing"],
+	"🛥": ["ship"],
+	"🚤": ["ship", "transportation", "vehicle", "summer"],
+	"⛴": ["boat", "ship", "yacht"],
+	"🛳": ["yacht", "cruise", "ferry"],
+	"🚀": ["launch", "ship", "staffmode", "NASA", "outer space", "outer_space", "fly"],
+	"🛰": ["communication", "gps", "orbit", "spaceflight", "NASA", "ISS"],
+	"🛻": ["car"],
+	"🛼": [],
+	"💺": ["sit", "airplane", "transport", "bus", "flight", "fly"],
+	"🛶": ["boat", "paddle", "water", "ship"],
+	"⚓": ["ship", "ferry", "sea", "boat"],
+	"🚧": ["wip", "progress", "caution", "warning"],
+	"⛽": ["gas station", "petroleum"],
+	"🚏": ["transportation", "wait"],
+	"🚦": ["transportation", "driving"],
+	"🚥": ["transportation", "signal"],
+	"🏁": ["contest", "finishline", "race", "gokart"],
+	"🚢": ["transportation", "titanic", "deploy"],
+	"🎡": ["photo", "carnival", "londoneye"],
+	"🎢": ["carnival", "playground", "photo", "fun"],
+	"🎠": ["photo", "carnival"],
+	"🏗": ["wip", "working", "progress"],
+	"🌁": ["photo", "mountain"],
+	"🏭": ["building", "industry", "pollution", "smoke"],
+	"⛲": ["photo", "summer", "water", "fresh"],
+	"🎑": ["photo", "japan", "asia", "tsukimi"],
+	"⛰": ["photo", "nature", "environment"],
+	"🏔": ["photo", "nature", "environment", "winter", "cold"],
+	"🗻": ["photo", "mountain", "nature", "japanese"],
+	"🌋": ["photo", "nature", "disaster"],
+	"🗾": ["nation", "country", "japanese", "asia"],
+	"🏕": ["photo", "outdoors", "tent"],
+	"⛺": ["photo", "camping", "outdoors"],
+	"🏞": ["photo", "environment", "nature"],
+	"🛣": ["road", "cupertino", "interstate", "highway"],
+	"🛤": ["train", "transportation"],
+	"🌅": ["morning", "view", "vacation", "photo"],
+	"🌄": ["view", "vacation", "photo"],
+	"🏜": ["photo", "warm", "saharah"],
+	"🏖": ["weather", "summer", "sunny", "sand", "mojito"],
+	"🏝": ["photo", "tropical", "mojito"],
+	"🌇": ["photo", "good morning", "dawn"],
+	"🌆": ["photo", "evening", "sky", "buildings"],
+	"🏙": ["photo", "night life", "urban"],
+	"🌃": ["evening", "city", "downtown"],
+	"🌉": ["photo", "sanfrancisco"],
+	"🌌": ["photo", "space", "stars"],
+	"🌠": ["night", "photo"],
+	"🎇": ["stars", "night", "shine"],
+	"🎆": ["photo", "festival", "carnival", "congratulations"],
+	"🌈": ["nature", "happy", "unicorn_face", "photo", "sky", "spring"],
+	"🏘": ["buildings", "photo"],
+	"🏰": ["building", "royalty", "history"],
+	"🏯": ["photo", "building"],
+	"🗼": ["photo", "japanese"],
+	"": ["photo", "japanese"],
+	"🏟": ["photo", "place", "sports", "concert", "venue"],
+	"🗽": ["american", "newyork"],
+	"🏠": ["building", "home"],
+	"🏡": ["home", "plant", "nature"],
+	"🏚": ["abandon", "evict", "broken", "building"],
+	"🏢": ["building", "bureau", "work"],
+	"🏬": ["building", "shopping", "mall"],
+	"🏣": ["building", "envelope", "communication"],
+	"🏤": ["building", "email"],
+	"🏥": ["building", "health", "surgery", "doctor"],
+	"🏦": ["building", "money", "sales", "cash", "business", "enterprise"],
+	"🏨": ["building", "accomodation", "checkin"],
+	"🏪": ["building", "shopping", "groceries"],
+	"🏫": ["building", "student", "education", "learn", "teach"],
+	"🏩": ["like", "affection", "dating"],
+	"💒": ["love", "like", "affection", "couple", "marriage", "bride", "groom"],
+	"🏛": ["art", "culture", "history"],
+	"⛪": ["building", "religion", "christ"],
+	"🕌": ["islam", "worship", "minaret"],
+	"🕍": ["judaism", "worship", "temple", "jewish"],
+	"🕋": ["mecca", "mosque", "islam"],
+	"⛩": ["temple", "japan", "kyoto"],
+	"🛕": ["temple"],
+	"🪨": [],
+	"🪵": [],
+	"🛖": [],
+	"🛝": [],
+	"🛞": [],
+	"🛟": [],
+	"⌚": ["time", "accessories"],
+	"📱": ["technology", "apple", "gadgets", "dial"],
+	"📲": ["iphone", "incoming"],
+	"💻": ["technology", "laptop", "screen", "display", "monitor"],
+	"⌨": ["technology", "computer", "type", "input", "text"],
+	"🖥": ["technology", "computing", "screen"],
+	"🖨": ["paper", "ink"],
+	"🖱": ["click"],
+	"🖲": ["technology", "trackpad"],
+	"🕹": ["game", "play"],
+	"🗜": ["tool"],
+	"💽": ["technology", "record", "data", "disk", "90s"],
+	"💾": ["oldschool", "technology", "save", "90s", "80s"],
+	"💿": ["technology", "dvd", "disk", "disc", "90s"],
+	"📀": ["cd", "disk", "disc"],
+	"📼": ["record", "video", "oldschool", "90s", "80s"],
+	"📷": ["gadgets", "photography"],
+	"📸": ["photography", "gadgets"],
+	"📹": ["film", "record"],
+	"🎥": ["film", "record"],
+	"📽": ["video", "tape", "record", "movie"],
+	"🎞": ["movie"],
+	"📞": ["technology", "communication", "dial"],
+	"☎️": ["technology", "communication", "dial", "telephone"],
+	"📟": ["bbcall", "oldschool", "90s"],
+	"📠": ["communication", "technology"],
+	"📺": ["technology", "program", "oldschool", "show", "television"],
+	"📻": ["communication", "music", "podcast", "program"],
+	"🎙": ["sing", "recording", "artist", "talkshow"],
+	"🎚": ["scale"],
+	"🎛": ["dial"],
+	"🧭": ["magnetic", "navigation", "orienteering"],
+	"⏱": ["time", "deadline"],
+	"⏲": ["alarm"],
+	"⏰": ["time", "wake"],
+	"🕰": ["time"],
+	"⏳": ["oldschool", "time", "countdown"],
+	"⌛": ["time", "clock", "oldschool", "limit", "exam", "quiz", "test"],
+	"📡": ["communication", "future", "radio", "space"],
+	"🔋": ["power", "energy", "sustain"],
+	"🪫": [],
+	"🔌": ["charger", "power"],
+	"💡": ["light", "electricity", "idea"],
+	"🔦": ["dark", "camping", "sight", "night"],
+	"🕯": ["fire", "wax"],
+	"🧯": ["quench"],
+	"🗑": ["bin", "trash", "rubbish", "garbage", "toss"],
+	"🛢": ["barrell"],
+	"💸": ["dollar", "bills", "payment", "sale"],
+	"💵": ["money", "sales", "bill", "currency"],
+	"💴": ["money", "sales", "japanese", "dollar", "currency"],
+	"💶": ["money", "sales", "dollar", "currency"],
+	"💷": ["british", "sterling", "money", "sales", "bills", "uk", "england", "currency"],
+	"💰": ["dollar", "payment", "coins", "sale"],
+	"🪙": ["dollar", "payment", "coins", "sale"],
+	"💳": ["money", "sales", "dollar", "bill", "payment", "shopping"],
+	"🪫": [],
+	"💎": ["blue", "ruby", "diamond", "jewelry"],
+	"⚖": ["law", "fairness", "weight"],
+	"🧰": ["tools", "diy", "fix", "maintainer", "mechanic"],
+	"🔧": ["tools", "diy", "ikea", "fix", "maintainer"],
+	"🔨": ["tools", "build", "create"],
+	"⚒": ["tools", "build", "create"],
+	"🛠": ["tools", "build", "create"],
+	"⛏": ["tools", "dig"],
+	"🪓": ["tools"],
+	"🦯": ["tools"],
+	"🔩": ["handy", "tools", "fix"],
+	"⚙": ["cog"],
+	"🪃": ["tool"],
+	"🪚": ["tool"],
+	"🪛": ["tool"],
+	"🪝": ["tool"],
+	"🪜": ["tool"],
+	"🧱": ["bricks"],
+	"⛓": ["lock", "arrest"],
+	"🧲": ["attraction", "magnetic"],
+	"🔫": ["violence", "weapon", "pistol", "revolver"],
+	"💣": ["boom", "explode", "explosion", "terrorism"],
+	"🧨": ["dynamite", "boom", "explode", "explosion", "explosive"],
+	"🔪": ["knife", "blade", "cutlery", "kitchen", "weapon"],
+	"🗡": ["weapon"],
+	"⚔": ["weapon"],
+	"🛡": ["protection", "security"],
+	"🚬": ["kills", "tobacco", "cigarette", "joint", "smoke"],
+	"☠": ["poison", "danger", "deadly", "scary", "death", "pirate", "evil"],
+	"⚰": ["vampire", "dead", "die", "death", "rip", "graveyard", "cemetery", "casket", "funeral", "box"],
+	"⚱": ["dead", "die", "death", "rip", "ashes"],
+	"🏺": ["vase", "jar"],
+	"🔮": ["disco", "party", "magic", "circus", "fortune_teller"],
+	"📿": ["dhikr", "religious"],
+	"🧿": ["bead", "charm"],
+	"💈": ["hair", "salon", "style"],
+	"⚗": ["distilling", "science", "experiment", "chemistry"],
+	"🔭": ["stars", "space", "zoom", "science", "astronomy"],
+	"🔬": ["laboratory", "experiment", "zoomin", "science", "study"],
+	"🕳": ["embarrassing"],
+	"💊": ["health", "medicine", "doctor", "pharmacy", "drug"],
+	"💉": ["health", "hospital", "drugs", "blood", "medicine", "needle", "doctor", "nurse"],
+	"🩸": ["health", "hospital", "medicine", "needle", "doctor", "nurse"],
+	"🩹": ["health", "hospital", "medicine", "needle", "doctor", "nurse"],
+	"🩺": ["health", "hospital", "medicine", "needle", "doctor", "nurse"],
+	"🪒": ["health"],
+	"🩻": [],
+	"🩼": [],
+	"🧬": ["biologist", "genetics", "life"],
+	"🧫": ["bacteria", "biology", "culture", "lab"],
+	"🧪": ["chemistry", "experiment", "lab", "science"],
+	"🌡": ["weather", "temperature", "hot", "cold"],
+	"🧹": ["cleaning", "sweeping", "witch"],
+	"🧺": ["laundry"],
+	"🧻": ["roll"],
+	"🏷": ["sale", "tag"],
+	"🔖": ["favorite", "label", "save"],
+	"🚽": ["restroom", "wc", "washroom", "bathroom", "potty"],
+	"🚿": ["clean", "water", "bathroom"],
+	"🛁": ["clean", "shower", "bathroom"],
+	"🧼": ["bar", "bathing", "cleaning", "lather"],
+	"🧽": ["absorbing", "cleaning", "porous"],
+	"🧴": ["moisturizer", "sunscreen"],
+	"🔑": ["lock", "door", "password"],
+	"🗝": ["lock", "door", "password"],
+	"🛋": ["read", "chill"],
+	"🪔": ["light", "oil"],
+	"🛌": ["bed", "rest"],
+	"🛏": ["sleep", "rest"],
+	"🚪": ["house", "entry", "exit"],
+	"🪑": ["house", "desk"],
+	"🛎": ["service"],
+	"🧸": ["plush", "stuffed"],
+	"🖼": ["photography"],
+	"🗺": ["location", "direction"],
+	"🛗": ["household"],
+	"🪞": ["household"],
+	"🪟": ["household"],
+	"🪠": ["household"],
+	"🪤": ["household"],
+	"🪣": ["household"],
+	"🪥": ["household"],
+	"🫧": [],
+	"⛱": ["weather", "summer"],
+	"🗿": ["rock", "easter island", "moai"],
+	"🛍": ["mall", "buy", "purchase"],
+	"🛒": ["trolley"],
+	"🎈": ["party", "celebration", "birthday", "circus"],
+	"🎏": ["fish", "japanese", "koinobori", "carp", "banner"],
+	"🎀": ["decoration", "pink", "girl", "bowtie"],
+	"🎁": ["present", "birthday", "christmas", "xmas"],
+	"🎊": ["festival", "party", "birthday", "circus"],
+	"🎉": ["party", "congratulations", "birthday", "magic", "circus", "celebration"],
+	"🎎": ["japanese", "toy", "kimono"],
+	"🎐": ["nature", "ding", "spring", "bell"],
+	"🎌": ["japanese", "nation", "country", "border"],
+	"🏮": ["light", "paper", "halloween", "spooky"],
+	"🧧": ["gift"],
+	"✉️": ["letter", "postal", "inbox", "communication"],
+	"📩": ["email", "communication"],
+	"📨": ["email", "inbox"],
+	"📧": ["communication", "inbox"],
+	"💌": ["email", "like", "affection", "envelope", "valentines"],
+	"📮": ["email", "letter", "envelope"],
+	"📪": ["email", "communication", "inbox"],
+	"📫": ["email", "inbox", "communication"],
+	"📬": ["email", "inbox", "communication"],
+	"📭": ["email", "inbox"],
+	"📦": ["mail", "gift", "cardboard", "box", "moving"],
+	"📯": ["instrument", "music"],
+	"📥": ["email", "documents"],
+	"📤": ["inbox", "email"],
+	"📜": ["documents", "ancient", "history", "paper"],
+	"📃": ["documents", "office", "paper"],
+	"📑": ["favorite", "save", "order", "tidy"],
+	"🧾": ["accounting", "expenses"],
+	"📊": ["graph", "presentation", "stats"],
+	"📈": ["graph", "presentation", "stats", "recovery", "business", "economics", "money", "sales", "good", "success"],
+	"📉": ["graph", "presentation", "stats", "recession", "business", "economics", "money", "sales", "bad", "failure"],
+	"📄": ["documents", "office", "paper", "information"],
+	"📅": ["calendar", "schedule"],
+	"📆": ["schedule", "date", "planning"],
+	"🗓": ["date", "schedule", "planning"],
+	"📇": ["business", "stationery"],
+	"🗃": ["business", "stationery"],
+	"🗳": ["election", "vote"],
+	"🗄": ["filing", "organizing"],
+	"📋": ["stationery", "documents"],
+	"🗒": ["memo", "stationery"],
+	"📁": ["documents", "business", "office"],
+	"📂": ["documents", "load"],
+	"🗂": ["organizing", "business", "stationery"],
+	"🗞": ["press", "headline"],
+	"📰": ["press", "headline"],
+	"📓": ["stationery", "record", "notes", "paper", "study"],
+	"📕": ["read", "library", "knowledge", "textbook", "learn"],
+	"📗": ["read", "library", "knowledge", "study"],
+	"📘": ["read", "library", "knowledge", "learn", "study"],
+	"📙": ["read", "library", "knowledge", "textbook", "study"],
+	"📔": ["classroom", "notes", "record", "paper", "study"],
+	"📒": ["notes", "paper"],
+	"📚": ["literature", "library", "study"],
+	"📖": ["book", "read", "library", "knowledge", "literature", "learn", "study"],
+	"🧷": ["diaper"],
+	"🔗": ["rings", "url"],
+	"📎": ["documents", "stationery"],
+	"🖇": ["documents", "stationery"],
+	"✂️": ["stationery", "cut"],
+	"📐": ["stationery", "math", "architect", "sketch"],
+	"📏": ["stationery", "calculate", "length", "math", "school", "drawing", "architect", "sketch"],
+	"🧮": ["calculation"],
+	"📌": ["stationery", "mark", "here"],
+	"📍": ["stationery", "location", "map", "here"],
+	"🚩": ["mark", "milestone", "place"],
+	"🏳": ["losing", "loser", "lost", "surrender", "give up", "fail"],
+	"🏴": ["pirate"],
+	"🏳️‍🌈": ["flag", "rainbow", "pride", "gay", "lgbt", "glbt", "queer", "homosexual", "lesbian", "bisexual", "transgender"],
+	"🏳️‍⚧️": ["flag", "transgender"],
+	"🔐": ["security", "privacy"],
+	"🔒": ["security", "password", "padlock"],
+	"🔓": ["privacy", "security"],
+	"🔏": ["security", "secret"],
+	"🖊": ["stationery", "writing", "write"],
+	"🖋": ["stationery", "writing", "write"],
+	"✒️": ["pen", "stationery", "writing", "write"],
+	"📝": ["write", "documents", "stationery", "pencil", "paper", "writing", "legal", "exam", "quiz", "test", "study", "compose"],
+	"✏️": ["stationery", "write", "paper", "writing", "school", "study"],
+	"🖍": ["drawing", "creativity"],
+	"🖌": ["drawing", "creativity", "art"],
+	"🔍": ["search", "zoom", "find", "detective"],
+	"🔎": ["search", "zoom", "find", "detective"],
+	"🪦": [],
+	"🪧": [],
+	"💯": ["score", "perfect", "numbers", "century", "exam", "quiz", "test", "pass", "hundred"],
+	"🔢": ["numbers", "blue-square"],
+	"❤️": ["love", "like", "affection", "valentines"],
+	"🧡": ["love", "like", "affection", "valentines"],
+	"💛": ["love", "like", "affection", "valentines"],
+	"💚": ["love", "like", "affection", "valentines"],
+	"💙": ["love", "like", "affection", "valentines"],
+	"💜": ["love", "like", "affection", "valentines"],
+	"🤎": ["love", "like", "affection", "valentines"],
+	"🖤": ["love", "like", "affection", "valentines"],
+	"🤍": ["love", "like", "affection", "valentines"],
+	"💔": ["sad", "sorry", "break", "heart", "heartbreak"],
+	"❣": ["decoration", "love"],
+	"💕": ["love", "like", "affection", "valentines", "heart"],
+	"💞": ["love", "like", "affection", "valentines"],
+	"💓": ["love", "like", "affection", "valentines", "pink", "heart"],
+	"💗": ["like", "love", "affection", "valentines", "pink"],
+	"💖": ["love", "like", "affection", "valentines"],
+	"💘": ["love", "like", "heart", "affection", "valentines"],
+	"💝": ["love", "valentines"],
+	"💟": ["purple-square", "love", "like"],
+	"❤️‍🔥": [],
+	"❤️‍🩹": [],
+	"☮": ["hippie"],
+	"✝": ["christianity"],
+	"☪": ["islam"],
+	"🕉": ["hinduism", "buddhism", "sikhism", "jainism"],
+	"☸": ["hinduism", "buddhism", "sikhism", "jainism"],
+	"✡": ["judaism"],
+	"🔯": ["purple-square", "religion", "jewish", "hexagram"],
+	"🕎": ["hanukkah", "candles", "jewish"],
+	"☯": ["balance"],
+	"☦": ["suppedaneum", "religion"],
+	"🛐": ["religion", "church", "temple", "prayer"],
+	"⛎": ["sign", "purple-square", "constellation", "astrology"],
+	"♈": ["sign", "purple-square", "zodiac", "astrology"],
+	"♉": ["purple-square", "sign", "zodiac", "astrology"],
+	"♊": ["sign", "zodiac", "purple-square", "astrology"],
+	"♋": ["sign", "zodiac", "purple-square", "astrology"],
+	"♌": ["sign", "purple-square", "zodiac", "astrology"],
+	"♍": ["sign", "zodiac", "purple-square", "astrology"],
+	"♎": ["sign", "purple-square", "zodiac", "astrology"],
+	"♏": ["sign", "zodiac", "purple-square", "astrology", "scorpio"],
+	"♐": ["sign", "zodiac", "purple-square", "astrology"],
+	"♑": ["sign", "zodiac", "purple-square", "astrology"],
+	"♒": ["sign", "purple-square", "zodiac", "astrology"],
+	"♓": ["purple-square", "sign", "zodiac", "astrology"],
+	"🆔": ["purple-square", "words"],
+	"⚛": ["science", "physics", "chemistry"],
+	"⚧️": ["purple-square", "woman", "female", "toilet", "loo", "restroom", "gender"],
+	"🈳": ["kanji", "japanese", "chinese", "empty", "sky", "blue-square", "aki"],
+	"🈹": ["cut", "divide", "chinese", "kanji", "pink-square", "waribiki"],
+	"☢": ["nuclear", "danger"],
+	"☣": ["danger"],
+	"📴": ["mute", "orange-square", "silence", "quiet"],
+	"📳": ["orange-square", "phone"],
+	"🈶": ["orange-square", "chinese", "have", "kanji", "ari"],
+	"🈚": ["nothing", "chinese", "kanji", "japanese", "orange-square", "nashi"],
+	"🈸": ["chinese", "japanese", "kanji", "orange-square", "moushikomi"],
+	"🈺": ["japanese", "opening hours", "orange-square", "eigyo"],
+	"🈷️": ["chinese", "month", "moon", "japanese", "orange-square", "kanji", "tsuki", "tsukigime", "getsugaku"],
+	"✴️": ["orange-square", "shape", "polygon"],
+	"🆚": ["words", "orange-square"],
+	"🉑": ["ok", "good", "chinese", "kanji", "agree", "yes", "orange-circle"],
+	"💮": ["japanese", "spring"],
+	"🉐": ["chinese", "kanji", "obtain", "get", "circle"],
+	"㊙️": ["privacy", "chinese", "sshh", "kanji", "red-circle"],
+	"㊗️": ["chinese", "kanji", "japanese", "red-circle"],
+	"🈴": ["japanese", "chinese", "join", "kanji", "red-square", "goukaku", "pass"],
+	"🈵": ["full", "chinese", "japanese", "red-square", "kanji", "man"],
+	"🈲": ["kanji", "japanese", "chinese", "forbidden", "limit", "restricted", "red-square", "kinshi"],
+	"🅰️": ["red-square", "alphabet", "letter"],
+	"🅱️": ["red-square", "alphabet", "letter"],
+	"🆎": ["red-square", "alphabet"],
+	"🆑": ["alphabet", "words", "red-square"],
+	"🅾️": ["alphabet", "red-square", "letter"],
+	"🆘": ["help", "red-square", "words", "emergency", "911"],
+	"⛔": ["limit", "security", "privacy", "bad", "denied", "stop", "circle"],
+	"📛": ["fire", "forbid"],
+	"🚫": ["forbid", "stop", "limit", "denied", "disallow", "circle"],
+	"❌": ["no", "delete", "remove", "cancel", "red"],
+	"⭕": ["circle", "round"],
+	"🛑": ["stop"],
+	"💢": ["angry", "mad"],
+	"♨️": ["bath", "warm", "relax"],
+	"🚷": ["rules", "crossing", "walking", "circle"],
+	"🚯": ["trash", "bin", "garbage", "circle"],
+	"🚳": ["cyclist", "prohibited", "circle"],
+	"🚱": ["drink", "faucet", "tap", "circle"],
+	"🔞": ["18", "drink", "pub", "night", "minor", "circle"],
+	"📵": ["iphone", "mute", "circle"],
+	"❗": ["heavy_exclamation_mark", "danger", "surprise", "punctuation", "wow", "warning"],
+	"❕": ["surprise", "punctuation", "gray", "wow", "warning"],
+	"❓": ["doubt", "confused"],
+	"❔": ["doubts", "gray", "huh", "confused"],
+	"‼️": ["exclamation", "surprise"],
+	"⁉️": ["wat", "punctuation", "surprise"],
+	"🔅": ["sun", "afternoon", "warm", "summer"],
+	"🔆": ["sun", "light"],
+	"🔱": ["weapon", "spear"],
+	"⚜": ["decorative", "scout"],
+	"〽️": ["graph", "presentation", "stats", "business", "economics", "bad"],
+	"⚠️": ["exclamation", "wip", "alert", "error", "problem", "issue"],
+	"🚸": ["school", "warning", "danger", "sign", "driving", "yellow-diamond"],
+	"🔰": ["badge", "shield"],
+	"♻️": ["arrow", "environment", "garbage", "trash"],
+	"🈯": ["chinese", "point", "green-square", "kanji", "reserved", "shiteiseki"],
+	"💹": ["green-square", "graph", "presentation", "stats"],
+	"❇️": ["stars", "green-square", "awesome", "good", "fireworks"],
+	"✳️": ["star", "sparkle", "green-square"],
+	"❎": ["x", "green-square", "no", "deny"],
+	"✅": ["green-square", "ok", "agree", "vote", "election", "answer", "tick"],
+	"💠": ["jewel", "blue", "gem", "crystal", "fancy"],
+	"🌀": ["weather", "swirl", "blue", "cloud", "vortex", "spiral", "whirlpool", "spin", "tornado", "hurricane", "typhoon"],
+	"➿": ["tape", "cassette"],
+	"🌐": ["earth", "international", "world", "internet", "interweb", "i18n"],
+	"Ⓜ️": ["alphabet", "blue-circle", "letter"],
+	"🏧": ["money", "sales", "cash", "blue-square", "payment", "bank"],
+	"🈂️": ["japanese", "blue-square", "katakana"],
+	"🛂": ["custom", "blue-square"],
+	"🛃": ["passport", "border", "blue-square"],
+	"🛄": ["blue-square", "airport", "transport"],
+	"🛅": ["blue-square", "travel"],
+	"♿": ["blue-square", "disabled", "a11y", "accessibility"],
+	"🚭": ["cigarette", "blue-square", "smell", "smoke"],
+	"🚾": ["toilet", "restroom", "blue-square"],
+	"🅿️": ["cars", "blue-square", "alphabet", "letter"],
+	"🚰": ["blue-square", "liquid", "restroom", "cleaning", "faucet"],
+	"🚹": ["toilet", "restroom", "wc", "blue-square", "gender", "male"],
+	"🚺": ["purple-square", "woman", "female", "toilet", "loo", "restroom", "gender"],
+	"🚼": ["orange-square", "child"],
+	"🚻": ["blue-square", "toilet", "refresh", "wc", "gender"],
+	"🚮": ["blue-square", "sign", "human", "info"],
+	"🎦": ["blue-square", "record", "film", "movie", "curtain", "stage", "theater"],
+	"📶": ["blue-square", "reception", "phone", "internet", "connection", "wifi", "bluetooth", "bars"],
+	"🈁": ["blue-square", "here", "katakana", "japanese", "destination"],
+	"🆖": ["blue-square", "words", "shape", "icon"],
+	"🆗": ["good", "agree", "yes", "blue-square"],
+	"🆙": ["blue-square", "above", "high"],
+	"🆒": ["words", "blue-square"],
+	"🆕": ["blue-square", "words", "start"],
+	"🆓": ["blue-square", "words"],
+	"0️⃣": ["0", "numbers", "blue-square", "null"],
+	"1️⃣": ["blue-square", "numbers", "1"],
+	"2️⃣": ["numbers", "2", "prime", "blue-square"],
+	"3️⃣": ["3", "numbers", "prime", "blue-square"],
+	"4️⃣": ["4", "numbers", "blue-square"],
+	"5️⃣": ["5", "numbers", "blue-square", "prime"],
+	"6️⃣": ["6", "numbers", "blue-square"],
+	"7️⃣": ["7", "numbers", "blue-square", "prime"],
+	"8️⃣": ["8", "blue-square", "numbers"],
+	"9️⃣": ["blue-square", "numbers", "9"],
+	"🔟": ["numbers", "10", "blue-square"],
+	"*⃣": ["star", "keycap"],
+	"⏏️": ["blue-square"],
+	"▶️": ["blue-square", "right", "direction", "play"],
+	"⏸": ["pause", "blue-square"],
+	"⏭": ["forward", "next", "blue-square"],
+	"⏹": ["blue-square"],
+	"⏺": ["blue-square"],
+	"⏯": ["blue-square", "play", "pause"],
+	"⏮": ["backward"],
+	"⏩": ["blue-square", "play", "speed", "continue"],
+	"⏪": ["play", "blue-square"],
+	"🔀": ["blue-square", "shuffle", "music", "random"],
+	"🔁": ["loop", "record"],
+	"🔂": ["blue-square", "loop"],
+	"◀️": ["blue-square", "left", "direction"],
+	"🔼": ["blue-square", "triangle", "direction", "point", "forward", "top"],
+	"🔽": ["blue-square", "direction", "bottom"],
+	"⏫": ["blue-square", "direction", "top"],
+	"⏬": ["blue-square", "direction", "bottom"],
+	"➡️": ["blue-square", "next"],
+	"⬅️": ["blue-square", "previous", "back"],
+	"⬆️": ["blue-square", "continue", "top", "direction"],
+	"⬇️": ["blue-square", "direction", "bottom"],
+	"↗️": ["blue-square", "point", "direction", "diagonal", "northeast"],
+	"↘️": ["blue-square", "direction", "diagonal", "southeast"],
+	"↙️": ["blue-square", "direction", "diagonal", "southwest"],
+	"↖️": ["blue-square", "point", "direction", "diagonal", "northwest"],
+	"↕️": ["blue-square", "direction", "way", "vertical"],
+	"↔️": ["shape", "direction", "horizontal", "sideways"],
+	"🔄": ["blue-square", "sync", "cycle"],
+	"↪️": ["blue-square", "return", "rotate", "direction"],
+	"↩️": ["back", "return", "blue-square", "undo", "enter"],
+	"⤴️": ["blue-square", "direction", "top"],
+	"⤵️": ["blue-square", "direction", "bottom"],
+	"#️⃣": ["symbol", "blue-square", "twitter"],
+	"ℹ️": ["blue-square", "alphabet", "letter"],
+	"🔤": ["blue-square", "alphabet"],
+	"🔡": ["blue-square", "alphabet"],
+	"🔠": ["alphabet", "words", "blue-square"],
+	"🔣": ["blue-square", "music", "note", "ampersand", "percent", "glyphs", "characters"],
+	"🎵": ["score", "tone", "sound"],
+	"🎶": ["music", "score"],
+	"〰️": ["draw", "line", "moustache", "mustache", "squiggle", "scribble"],
+	"➰": ["scribble", "draw", "shape", "squiggle"],
+	"✔️": ["ok", "nike", "answer", "yes", "tick"],
+	"🔃": ["sync", "cycle", "round", "repeat"],
+	"➕": ["math", "calculation", "addition", "more", "increase"],
+	"➖": ["math", "calculation", "subtract", "less"],
+	"➗": ["divide", "math", "calculation"],
+	"✖️": ["math", "calculation"],
+	"🟰": [],
+	"♾": ["forever"],
+	"💲": ["money", "sales", "payment", "currency", "buck"],
+	"💱": ["money", "sales", "dollar", "travel"],
+	"©️": ["ip", "license", "circle", "law", "legal"],
+	"®️": ["alphabet", "circle"],
+	"™️": ["trademark", "brand", "law", "legal"],
+	"🔚": ["words", "arrow"],
+	"🔙": ["arrow", "words", "return"],
+	"🔛": ["arrow", "words"],
+	"🔝": ["words", "blue-square"],
+	"🔜": ["arrow", "words"],
+	"☑️": ["ok", "agree", "confirm", "black-square", "vote", "election", "yes", "tick"],
+	"🔘": ["input", "old", "music", "circle"],
+	"⚫": ["shape", "button", "round"],
+	"⚪": ["shape", "round"],
+	"🔴": ["shape", "error", "danger"],
+	"🟠": ["shape"],
+	"🟡": ["shape"],
+	"🟢": ["shape"],
+	"🔵": ["shape", "icon", "button"],
+	"🟣": ["shape"],
+	"🟤": ["shape"],
+	"🔸": ["shape", "jewel", "gem"],
+	"🔹": ["shape", "jewel", "gem"],
+	"🔶": ["shape", "jewel", "gem"],
+	"🔷": ["shape", "jewel", "gem"],
+	"🔺": ["shape", "direction", "up", "top"],
+	"▪️": ["shape", "icon"],
+	"▫️": ["shape", "icon"],
+	"⬛": ["shape", "icon", "button"],
+	"⬜": ["shape", "icon", "stone", "button"],
+	"🟥": ["shape"],
+	"🟧": ["shape"],
+	"🟨": ["shape"],
+	"🟩": ["shape"],
+	"🟦": ["shape"],
+	"🟪": ["shape"],
+	"🟫": ["shape"],
+	"🔻": ["shape", "direction", "bottom"],
+	"◼️": ["shape", "button", "icon"],
+	"◻️": ["shape", "stone", "icon"],
+	"◾": ["icon", "shape", "button"],
+	"◽": ["shape", "stone", "icon", "button"],
+	"🔲": ["shape", "input", "frame"],
+	"🔳": ["shape", "input"],
+	"🔈": ["sound", "volume", "silence", "broadcast"],
+	"🔉": ["volume", "speaker", "broadcast"],
+	"🔊": ["volume", "noise", "noisy", "speaker", "broadcast"],
+	"🔇": ["sound", "volume", "silence", "quiet"],
+	"📣": ["sound", "speaker", "volume"],
+	"📢": ["volume", "sound"],
+	"🔔": ["sound", "notification", "christmas", "xmas", "chime"],
+	"🔕": ["sound", "volume", "mute", "quiet", "silent"],
+	"🃏": ["poker", "cards", "game", "play", "magic"],
+	"🀄": ["game", "play", "chinese", "kanji"],
+	"♠️": ["poker", "cards", "suits", "magic"],
+	"♣️": ["poker", "cards", "magic", "suits"],
+	"♥️": ["poker", "cards", "magic", "suits"],
+	"♦️": ["poker", "cards", "magic", "suits"],
+	"🎴": ["game", "sunset", "red"],
+	"💭": ["bubble", "cloud", "speech", "thinking", "dream"],
+	"🗯": ["caption", "speech", "thinking", "mad"],
+	"💬": ["bubble", "words", "message", "talk", "chatting"],
+	"🗨": ["words", "message", "talk", "chatting"],
+	"🕐": ["time", "late", "early", "schedule"],
+	"🕑": ["time", "late", "early", "schedule"],
+	"🕒": ["time", "late", "early", "schedule"],
+	"🕓": ["time", "late", "early", "schedule"],
+	"🕔": ["time", "late", "early", "schedule"],
+	"🕕": ["time", "late", "early", "schedule", "dawn", "dusk"],
+	"🕖": ["time", "late", "early", "schedule"],
+	"🕗": ["time", "late", "early", "schedule"],
+	"🕘": ["time", "late", "early", "schedule"],
+	"🕙": ["time", "late", "early", "schedule"],
+	"🕚": ["time", "late", "early", "schedule"],
+	"🕛": ["time", "noon", "midnight", "midday", "late", "early", "schedule"],
+	"🕜": ["time", "late", "early", "schedule"],
+	"🕝": ["time", "late", "early", "schedule"],
+	"🕞": ["time", "late", "early", "schedule"],
+	"🕟": ["time", "late", "early", "schedule"],
+	"🕠": ["time", "late", "early", "schedule"],
+	"🕡": ["time", "late", "early", "schedule"],
+	"🕢": ["time", "late", "early", "schedule"],
+	"🕣": ["time", "late", "early", "schedule"],
+	"🕤": ["time", "late", "early", "schedule"],
+	"🕥": ["time", "late", "early", "schedule"],
+	"🕦": ["time", "late", "early", "schedule"],
+	"🕧": ["time", "late", "early", "schedule"],
+	"🇦🇫": ["af", "flag", "nation", "country", "banner"],
+	"🇦🇽": ["Åland", "islands", "flag", "nation", "country", "banner"],
+	"🇦🇱": ["al", "flag", "nation", "country", "banner"],
+	"🇩🇿": ["dz", "flag", "nation", "country", "banner"],
+	"🇦🇸": ["american", "ws", "flag", "nation", "country", "banner"],
+	"🇦🇩": ["ad", "flag", "nation", "country", "banner"],
+	"🇦🇴": ["ao", "flag", "nation", "country", "banner"],
+	"🇦🇮": ["ai", "flag", "nation", "country", "banner"],
+	"🇦🇶": ["aq", "flag", "nation", "country", "banner"],
+	"🇦🇬": ["antigua", "barbuda", "flag", "nation", "country", "banner"],
+	"🇦🇷": ["ar", "flag", "nation", "country", "banner"],
+	"🇦🇲": ["am", "flag", "nation", "country", "banner"],
+	"🇦🇼": ["aw", "flag", "nation", "country", "banner"],
+	"🇦🇨": ["flag", "nation", "country", "banner"],
+	"🇦🇺": ["au", "flag", "nation", "country", "banner"],
+	"🇦🇹": ["at", "flag", "nation", "country", "banner"],
+	"🇦🇿": ["az", "flag", "nation", "country", "banner"],
+	"🇧🇸": ["bs", "flag", "nation", "country", "banner"],
+	"🇧🇭": ["bh", "flag", "nation", "country", "banner"],
+	"🇧🇩": ["bd", "flag", "nation", "country", "banner"],
+	"🇧🇧": ["bb", "flag", "nation", "country", "banner"],
+	"🇧🇾": ["by", "flag", "nation", "country", "banner"],
+	"🇧🇪": ["be", "flag", "nation", "country", "banner"],
+	"🇧🇿": ["bz", "flag", "nation", "country", "banner"],
+	"🇧🇯": ["bj", "flag", "nation", "country", "banner"],
+	"🇧🇲": ["bm", "flag", "nation", "country", "banner"],
+	"🇧🇹": ["bt", "flag", "nation", "country", "banner"],
+	"🇧🇴": ["bo", "flag", "nation", "country", "banner"],
+	"🇧🇶": ["bonaire", "flag", "nation", "country", "banner"],
+	"🇧🇦": ["bosnia", "herzegovina", "flag", "nation", "country", "banner"],
+	"🇧🇼": ["bw", "flag", "nation", "country", "banner"],
+	"🇧🇷": ["br", "flag", "nation", "country", "banner"],
+	"🇮🇴": ["british", "indian", "ocean", "territory", "flag", "nation", "country", "banner"],
+	"🇻🇬": ["british", "virgin", "islands", "bvi", "flag", "nation", "country", "banner"],
+	"🇧🇳": ["bn", "darussalam", "flag", "nation", "country", "banner"],
+	"🇧🇬": ["bg", "flag", "nation", "country", "banner"],
+	"🇧🇫": ["burkina", "faso", "flag", "nation", "country", "banner"],
+	"🇧🇮": ["bi", "flag", "nation", "country", "banner"],
+	"🇨🇻": ["cabo", "verde", "flag", "nation", "country", "banner"],
+	"🇰🇭": ["kh", "flag", "nation", "country", "banner"],
+	"🇨🇲": ["cm", "flag", "nation", "country", "banner"],
+	"🇨🇦": ["ca", "flag", "nation", "country", "banner"],
+	"🇮🇨": ["canary", "islands", "flag", "nation", "country", "banner"],
+	"🇰🇾": ["cayman", "islands", "flag", "nation", "country", "banner"],
+	"🇨🇫": ["central", "african", "republic", "flag", "nation", "country", "banner"],
+	"🇹🇩": ["td", "flag", "nation", "country", "banner"],
+	"🇨🇱": ["flag", "nation", "country", "banner"],
+	"🇨🇳": ["china", "chinese", "prc", "flag", "country", "nation", "banner"],
+	"🇨🇽": ["christmas", "island", "flag", "nation", "country", "banner"],
+	"🇨🇨": ["cocos", "keeling", "islands", "flag", "nation", "country", "banner"],
+	"🇨🇴": ["co", "flag", "nation", "country", "banner"],
+	"🇰🇲": ["km", "flag", "nation", "country", "banner"],
+	"🇨🇬": ["congo", "flag", "nation", "country", "banner"],
+	"🇨🇩": ["congo", "democratic", "republic", "flag", "nation", "country", "banner"],
+	"🇨🇰": ["cook", "islands", "flag", "nation", "country", "banner"],
+	"🇨🇷": ["costa", "rica", "flag", "nation", "country", "banner"],
+	"🇭🇷": ["hr", "flag", "nation", "country", "banner"],
+	"🇨🇺": ["cu", "flag", "nation", "country", "banner"],
+	"🇨🇼": ["curaçao", "flag", "nation", "country", "banner"],
+	"🇨🇾": ["cy", "flag", "nation", "country", "banner"],
+	"🇨🇿": ["cz", "flag", "nation", "country", "banner"],
+	"🇩🇰": ["dk", "flag", "nation", "country", "banner"],
+	"🇩🇯": ["dj", "flag", "nation", "country", "banner"],
+	"🇩🇲": ["dm", "flag", "nation", "country", "banner"],
+	"🇩🇴": ["dominican", "republic", "flag", "nation", "country", "banner"],
+	"🇪🇨": ["ec", "flag", "nation", "country", "banner"],
+	"🇪🇬": ["eg", "flag", "nation", "country", "banner"],
+	"🇸🇻": ["el", "salvador", "flag", "nation", "country", "banner"],
+	"🇬🇶": ["equatorial", "gn", "flag", "nation", "country", "banner"],
+	"🇪🇷": ["er", "flag", "nation", "country", "banner"],
+	"🇪🇪": ["ee", "flag", "nation", "country", "banner"],
+	"🇪🇹": ["et", "flag", "nation", "country", "banner"],
+	"🇪🇺": ["european", "union", "flag", "banner"],
+	"🇫🇰": ["falkland", "islands", "malvinas", "flag", "nation", "country", "banner"],
+	"🇫🇴": ["faroe", "islands", "flag", "nation", "country", "banner"],
+	"🇫🇯": ["fj", "flag", "nation", "country", "banner"],
+	"🇫🇮": ["fi", "flag", "nation", "country", "banner"],
+	"🇫🇷": ["banner", "flag", "nation", "france", "french", "country"],
+	"🇬🇫": ["french", "guiana", "flag", "nation", "country", "banner"],
+	"🇵🇫": ["french", "polynesia", "flag", "nation", "country", "banner"],
+	"🇹🇫": ["french", "southern", "territories", "flag", "nation", "country", "banner"],
+	"🇬🇦": ["ga", "flag", "nation", "country", "banner"],
+	"🇬🇲": ["gm", "flag", "nation", "country", "banner"],
+	"🇬🇪": ["ge", "flag", "nation", "country", "banner"],
+	"🇩🇪": ["german", "nation", "flag", "country", "banner"],
+	"🇬🇭": ["gh", "flag", "nation", "country", "banner"],
+	"🇬🇮": ["gi", "flag", "nation", "country", "banner"],
+	"🇬🇷": ["gr", "flag", "nation", "country", "banner"],
+	"🇬🇱": ["gl", "flag", "nation", "country", "banner"],
+	"🇬🇩": ["gd", "flag", "nation", "country", "banner"],
+	"🇬🇵": ["gp", "flag", "nation", "country", "banner"],
+	"🇬🇺": ["gu", "flag", "nation", "country", "banner"],
+	"🇬🇹": ["gt", "flag", "nation", "country", "banner"],
+	"🇬🇬": ["gg", "flag", "nation", "country", "banner"],
+	"🇬🇳": ["gn", "flag", "nation", "country", "banner"],
+	"🇬🇼": ["gw", "bissau", "flag", "nation", "country", "banner"],
+	"🇬🇾": ["gy", "flag", "nation", "country", "banner"],
+	"🇭🇹": ["ht", "flag", "nation", "country", "banner"],
+	"🇭🇳": ["hn", "flag", "nation", "country", "banner"],
+	"🇭🇰": ["hong", "kong", "flag", "nation", "country", "banner"],
+	"🇭🇺": ["hu", "flag", "nation", "country", "banner"],
+	"🇮🇸": ["is", "flag", "nation", "country", "banner"],
+	"🇮🇳": ["in", "flag", "nation", "country", "banner"],
+	"🇮🇩": ["flag", "nation", "country", "banner"],
+	"🇮🇷": ["iran, ", "islamic", "republic", "flag", "nation", "country", "banner"],
+	"🇮🇶": ["iq", "flag", "nation", "country", "banner"],
+	"🇮🇪": ["ie", "flag", "nation", "country", "banner"],
+	"🇮🇲": ["isle", "man", "flag", "nation", "country", "banner"],
+	"🇮🇱": ["il", "flag", "nation", "country", "banner"],
+	"🇮🇹": ["italy", "flag", "nation", "country", "banner"],
+	"🇨🇮": ["ivory", "coast", "flag", "nation", "country", "banner"],
+	"🇯🇲": ["jm", "flag", "nation", "country", "banner"],
+	"🇯🇵": ["japanese", "nation", "flag", "country", "banner"],
+	"🇯🇪": ["je", "flag", "nation", "country", "banner"],
+	"🇯🇴": ["jo", "flag", "nation", "country", "banner"],
+	"🇰🇿": ["kz", "flag", "nation", "country", "banner"],
+	"🇰🇪": ["ke", "flag", "nation", "country", "banner"],
+	"🇰🇮": ["ki", "flag", "nation", "country", "banner"],
+	"🇽🇰": ["xk", "flag", "nation", "country", "banner"],
+	"🇰🇼": ["kw", "flag", "nation", "country", "banner"],
+	"🇰🇬": ["kg", "flag", "nation", "country", "banner"],
+	"🇱🇦": ["lao", "democratic", "republic", "flag", "nation", "country", "banner"],
+	"🇱🇻": ["lv", "flag", "nation", "country", "banner"],
+	"🇱🇧": ["lb", "flag", "nation", "country", "banner"],
+	"🇱🇸": ["ls", "flag", "nation", "country", "banner"],
+	"🇱🇷": ["lr", "flag", "nation", "country", "banner"],
+	"🇱🇾": ["ly", "flag", "nation", "country", "banner"],
+	"🇱🇮": ["li", "flag", "nation", "country", "banner"],
+	"🇱🇹": ["lt", "flag", "nation", "country", "banner"],
+	"🇱🇺": ["lu", "flag", "nation", "country", "banner"],
+	"🇲🇴": ["macao", "flag", "nation", "country", "banner"],
+	"🇲🇰": ["macedonia, ", "flag", "nation", "country", "banner"],
+	"🇲🇬": ["mg", "flag", "nation", "country", "banner"],
+	"🇲🇼": ["mw", "flag", "nation", "country", "banner"],
+	"🇲🇾": ["my", "flag", "nation", "country", "banner"],
+	"🇲🇻": ["mv", "flag", "nation", "country", "banner"],
+	"🇲🇱": ["ml", "flag", "nation", "country", "banner"],
+	"🇲🇹": ["mt", "flag", "nation", "country", "banner"],
+	"🇲🇭": ["marshall", "islands", "flag", "nation", "country", "banner"],
+	"🇲🇶": ["mq", "flag", "nation", "country", "banner"],
+	"🇲🇷": ["mr", "flag", "nation", "country", "banner"],
+	"🇲🇺": ["mu", "flag", "nation", "country", "banner"],
+	"🇾🇹": ["yt", "flag", "nation", "country", "banner"],
+	"🇲🇽": ["mx", "flag", "nation", "country", "banner"],
+	"🇫🇲": ["micronesia, ", "federated", "states", "flag", "nation", "country", "banner"],
+	"🇲🇩": ["moldova, ", "republic", "flag", "nation", "country", "banner"],
+	"🇲🇨": ["mc", "flag", "nation", "country", "banner"],
+	"🇲🇳": ["mn", "flag", "nation", "country", "banner"],
+	"🇲🇪": ["me", "flag", "nation", "country", "banner"],
+	"🇲🇸": ["ms", "flag", "nation", "country", "banner"],
+	"🇲🇦": ["ma", "flag", "nation", "country", "banner"],
+	"🇲🇿": ["mz", "flag", "nation", "country", "banner"],
+	"🇲🇲": ["mm", "flag", "nation", "country", "banner"],
+	"🇳🇦": ["na", "flag", "nation", "country", "banner"],
+	"🇳🇷": ["nr", "flag", "nation", "country", "banner"],
+	"🇳🇵": ["np", "flag", "nation", "country", "banner"],
+	"🇳🇱": ["nl", "flag", "nation", "country", "banner"],
+	"🇳🇨": ["new", "caledonia", "flag", "nation", "country", "banner"],
+	"🇳🇿": ["new", "zealand", "flag", "nation", "country", "banner"],
+	"🇳🇮": ["ni", "flag", "nation", "country", "banner"],
+	"🇳🇪": ["ne", "flag", "nation", "country", "banner"],
+	"🇳🇬": ["flag", "nation", "country", "banner"],
+	"🇳🇺": ["nu", "flag", "nation", "country", "banner"],
+	"🇳🇫": ["norfolk", "island", "flag", "nation", "country", "banner"],
+	"🇲🇵": ["northern", "mariana", "islands", "flag", "nation", "country", "banner"],
+	"🇰🇵": ["north", "korea", "nation", "flag", "country", "banner"],
+	"🇳🇴": ["no", "flag", "nation", "country", "banner"],
+	"🇴🇲": ["om_symbol", "flag", "nation", "country", "banner"],
+	"🇵🇰": ["pk", "flag", "nation", "country", "banner"],
+	"🇵🇼": ["pw", "flag", "nation", "country", "banner"],
+	"🇵🇸": ["palestine", "palestinian", "territories", "flag", "nation", "country", "banner"],
+	"🇵🇦": ["pa", "flag", "nation", "country", "banner"],
+	"🇵🇬": ["papua", "new", "guinea", "flag", "nation", "country", "banner"],
+	"🇵🇾": ["py", "flag", "nation", "country", "banner"],
+	"🇵🇪": ["pe", "flag", "nation", "country", "banner"],
+	"🇵🇭": ["ph", "flag", "nation", "country", "banner"],
+	"🇵🇳": ["pitcairn", "flag", "nation", "country", "banner"],
+	"🇵🇱": ["pl", "flag", "nation", "country", "banner"],
+	"🇵🇹": ["pt", "flag", "nation", "country", "banner"],
+	"🇵🇷": ["puerto", "rico", "flag", "nation", "country", "banner"],
+	"🇶🇦": ["qa", "flag", "nation", "country", "banner"],
+	"🇷🇪": ["réunion", "flag", "nation", "country", "banner"],
+	"🇷🇴": ["ro", "flag", "nation", "country", "banner"],
+	"🇷🇺": ["russian", "federation", "flag", "nation", "country", "banner"],
+	"🇷🇼": ["rw", "flag", "nation", "country", "banner"],
+	"🇧🇱": ["saint", "barthélemy", "flag", "nation", "country", "banner"],
+	"🇸🇭": ["saint", "helena", "ascension", "tristan", "cunha", "flag", "nation", "country", "banner"],
+	"🇰🇳": ["saint", "kitts", "nevis", "flag", "nation", "country", "banner"],
+	"🇱🇨": ["saint", "lucia", "flag", "nation", "country", "banner"],
+	"🇵🇲": ["saint", "pierre", "miquelon", "flag", "nation", "country", "banner"],
+	"🇻🇨": ["saint", "vincent", "grenadines", "flag", "nation", "country", "banner"],
+	"🇼🇸": ["ws", "flag", "nation", "country", "banner"],
+	"🇸🇲": ["san", "marino", "flag", "nation", "country", "banner"],
+	"🇸🇹": ["sao", "tome", "principe", "flag", "nation", "country", "banner"],
+	"🇸🇦": ["flag", "nation", "country", "banner"],
+	"🇸🇳": ["sn", "flag", "nation", "country", "banner"],
+	"🇷🇸": ["rs", "flag", "nation", "country", "banner"],
+	"🇸🇨": ["sc", "flag", "nation", "country", "banner"],
+	"🇸🇱": ["sierra", "leone", "flag", "nation", "country", "banner"],
+	"🇸🇬": ["sg", "flag", "nation", "country", "banner"],
+	"🇸🇽": ["sint", "maarten", "dutch", "flag", "nation", "country", "banner"],
+	"🇸🇰": ["sk", "flag", "nation", "country", "banner"],
+	"🇸🇮": ["si", "flag", "nation", "country", "banner"],
+	"🇸🇧": ["solomon", "islands", "flag", "nation", "country", "banner"],
+	"🇸🇴": ["so", "flag", "nation", "country", "banner"],
+	"🇿🇦": ["south", "africa", "flag", "nation", "country", "banner"],
+	"🇬🇸": ["south", "georgia", "sandwich", "islands", "flag", "nation", "country", "banner"],
+	"🇰🇷": ["south", "korea", "nation", "flag", "country", "banner"],
+	"🇸🇸": ["south", "sd", "flag", "nation", "country", "banner"],
+	"🇪🇸": ["spain", "flag", "nation", "country", "banner"],
+	"🇱🇰": ["sri", "lanka", "flag", "nation", "country", "banner"],
+	"🇸🇩": ["sd", "flag", "nation", "country", "banner"],
+	"🇸🇷": ["sr", "flag", "nation", "country", "banner"],
+	"🇸🇿": ["sz", "flag", "nation", "country", "banner"],
+	"🇸🇪": ["se", "flag", "nation", "country", "banner"],
+	"🇨🇭": ["ch", "flag", "nation", "country", "banner"],
+	"🇸🇾": ["syrian", "arab", "republic", "flag", "nation", "country", "banner"],
+	"🇹🇼": ["tw", "flag", "nation", "country", "banner"],
+	"🇹🇯": ["tj", "flag", "nation", "country", "banner"],
+	"🇹🇿": ["tanzania, ", "united", "republic", "flag", "nation", "country", "banner"],
+	"🇹🇭": ["th", "flag", "nation", "country", "banner"],
+	"🇹🇱": ["timor", "leste", "flag", "nation", "country", "banner"],
+	"🇹🇬": ["tg", "flag", "nation", "country", "banner"],
+	"🇹🇰": ["tk", "flag", "nation", "country", "banner"],
+	"🇹🇴": ["to", "flag", "nation", "country", "banner"],
+	"🇹🇹": ["trinidad", "tobago", "flag", "nation", "country", "banner"],
+	"🇹🇦": ["flag", "nation", "country", "banner"],
+	"🇹🇳": ["tn", "flag", "nation", "country", "banner"],
+	"🇹🇷": ["turkey", "flag", "nation", "country", "banner"],
+	"🇹🇲": ["flag", "nation", "country", "banner"],
+	"🇹🇨": ["turks", "caicos", "islands", "flag", "nation", "country", "banner"],
+	"🇹🇻": ["flag", "nation", "country", "banner"],
+	"🇺🇬": ["ug", "flag", "nation", "country", "banner"],
+	"🇺🇦": ["ua", "flag", "nation", "country", "banner"],
+	"🇦🇪": ["united", "arab", "emirates", "flag", "nation", "country", "banner"],
+	"🇬🇧": ["united", "kingdom", "great", "britain", "northern", "ireland", "flag", "nation", "country", "banner", "british", "UK", "english", "england", "union jack"],
+	"🏴󠁧󠁢󠁥󠁮󠁧󠁿": ["flag", "english"],
+	"🏴󠁧󠁢󠁳󠁣󠁴󠁿": ["flag", "scottish"],
+	"🏴󠁧󠁢󠁷󠁬󠁳󠁿": ["flag", "welsh"],
+	"🇺🇸": ["united", "states", "america", "flag", "nation", "country", "banner"],
+	"🇻🇮": ["virgin", "islands", "us", "flag", "nation", "country", "banner"],
+	"🇺🇾": ["uy", "flag", "nation", "country", "banner"],
+	"🇺🇿": ["uz", "flag", "nation", "country", "banner"],
+	"🇻🇺": ["vu", "flag", "nation", "country", "banner"],
+	"🇻🇦": ["vatican", "city", "flag", "nation", "country", "banner"],
+	"🇻🇪": ["ve", "bolivarian", "republic", "flag", "nation", "country", "banner"],
+	"🇻🇳": ["viet", "nam", "flag", "nation", "country", "banner"],
+	"🇼🇫": ["wallis", "futuna", "flag", "nation", "country", "banner"],
+	"🇪🇭": ["western", "sahara", "flag", "nation", "country", "banner"],
+	"🇾🇪": ["ye", "flag", "nation", "country", "banner"],
+	"🇿🇲": ["zm", "flag", "nation", "country", "banner"],
+	"🇿🇼": ["zw", "flag", "nation", "country", "banner"],
+	"🇺🇳": ["un", "flag", "banner"],
+	"🏴‍☠️": ["skull", "crossbones", "flag", "banner"]
+}
diff --git a/packages/frontend/src/widgets/WidgetActivity.calendar.vue b/packages/frontend/src/widgets/WidgetActivity.calendar.vue
index 84f6af1c1..110f1d32e 100644
--- a/packages/frontend/src/widgets/WidgetActivity.calendar.vue
+++ b/packages/frontend/src/widgets/WidgetActivity.calendar.vue
@@ -1,25 +1,31 @@
 <template>
 <svg viewBox="0 0 21 7">
-	<rect v-for="record in activity" class="day"
+	<rect
+		v-for="record in activity" class="day"
 		width="1" height="1"
 		:x="record.x" :y="record.date.weekday"
 		rx="1" ry="1"
-		fill="transparent">
+		fill="transparent"
+	>
 		<title>{{ record.date.year }}/{{ record.date.month + 1 }}/{{ record.date.day }}</title>
 	</rect>
-	<rect v-for="record in activity" class="day"
+	<rect
+		v-for="record in activity" class="day"
 		:width="record.v" :height="record.v"
 		:x="record.x + ((1 - record.v) / 2)" :y="record.date.weekday + ((1 - record.v) / 2)"
 		rx="1" ry="1"
 		:fill="record.color"
-		style="pointer-events: none;"/>
-	<rect class="today"
+		style="pointer-events: none;"
+	/>
+	<rect
+		class="today"
 		width="1" height="1"
 		:x="activity[0].x" :y="activity[0].date.weekday"
 		rx="1" ry="1"
 		fill="none"
 		stroke-width="0.1"
-		stroke="#f73520"/>
+		stroke="#f73520"
+	/>
 </svg>
 </template>
 
diff --git a/packages/frontend/src/widgets/WidgetActivity.chart.vue b/packages/frontend/src/widgets/WidgetActivity.chart.vue
index b61e419f9..cc4df65dd 100644
--- a/packages/frontend/src/widgets/WidgetActivity.chart.vue
+++ b/packages/frontend/src/widgets/WidgetActivity.chart.vue
@@ -1,26 +1,30 @@
 <template>
-<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`" @mousedown.prevent="onMousedown">
+<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`" :class="$style.root" @mousedown.prevent="onMousedown">
 	<polyline
 		:points="pointsNote"
 		fill="none"
 		stroke-width="1"
-		stroke="#41ddde"/>
+		stroke="#41ddde"
+	/>
 	<polyline
 		:points="pointsReply"
 		fill="none"
 		stroke-width="1"
-		stroke="#f7796c"/>
+		stroke="#f7796c"
+	/>
 	<polyline
 		:points="pointsRenote"
 		fill="none"
 		stroke-width="1"
-		stroke="#a1de41"/>
+		stroke="#a1de41"
+	/>
 	<polyline
 		:points="pointsTotal"
 		fill="none"
 		stroke-width="1"
 		stroke="#555"
-		stroke-dasharray="2 2"/>
+		stroke-dasharray="2 2"
+	/>
 </svg>
 </template>
 
@@ -81,8 +85,8 @@ function render() {
 }
 </script>
 
-<style lang="scss" scoped>
-svg {
+<style lang="scss" module>
+.root {
 	display: block;
 	padding: 16px;
 	width: 100%;
diff --git a/packages/frontend/src/widgets/WidgetActivity.vue b/packages/frontend/src/widgets/WidgetActivity.vue
index e7f8819ab..892b24f69 100644
--- a/packages/frontend/src/widgets/WidgetActivity.vue
+++ b/packages/frontend/src/widgets/WidgetActivity.vue
@@ -1,5 +1,5 @@
 <template>
-<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent" data-cy-mkw-activity class="mkw-activity">
+<MkContainer :showHeader="widgetProps.showHeader" :naked="widgetProps.transparent" data-cy-mkw-activity class="mkw-activity">
 	<template #icon><i class="ti ti-chart-line"></i></template>
 	<template #header>{{ i18n.ts._widgets.activity }}</template>
 	<template #func="{ buttonStyleClass }"><button class="_button" :class="buttonStyleClass" @click="toggleView()"><i class="ti ti-selector"></i></button></template>
@@ -16,7 +16,7 @@
 
 <script lang="ts" setup>
 import { ref } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import XCalendar from './WidgetActivity.calendar.vue';
 import XChart from './WidgetActivity.chart.vue';
 import { GetFormResultType } from '@/scripts/form';
@@ -45,11 +45,8 @@ const widgetPropsDef = {
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 
 const { widgetProps, configure, save } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/frontend/src/widgets/WidgetAichan.vue b/packages/frontend/src/widgets/WidgetAichan.vue
index 37326ee98..797dd9c09 100644
--- a/packages/frontend/src/widgets/WidgetAichan.vue
+++ b/packages/frontend/src/widgets/WidgetAichan.vue
@@ -1,12 +1,12 @@
 <template>
-<MkContainer :naked="widgetProps.transparent" :show-header="false" data-cy-mkw-aichan class="mkw-aichan">
-	<iframe ref="live2d" class="dedjhjmo" src="https://misskey-dev.github.io/mascot-web/?scale=1.5&y=1.1&eyeY=100" @click="touched"></iframe>
+<MkContainer :naked="widgetProps.transparent" :showHeader="false" data-cy-mkw-aichan class="mkw-aichan">
+	<iframe ref="live2d" :class="$style.root" src="https://misskey-dev.github.io/mascot-web/?scale=1.5&y=1.1&eyeY=100" @click="touched"></iframe>
 </MkContainer>
 </template>
 
 <script lang="ts" setup>
 import { onMounted, onUnmounted, shallowRef } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
 
 const name = 'ai';
@@ -20,11 +20,8 @@ const widgetPropsDef = {
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
@@ -64,8 +61,8 @@ defineExpose<WidgetComponentExpose>({
 });
 </script>
 
-<style lang="scss" scoped>
-.dedjhjmo {
+<style lang="scss" module>
+.root {
 	width: 100%;
 	height: 350px;
 	border: none;
diff --git a/packages/frontend/src/widgets/WidgetAiscript.vue b/packages/frontend/src/widgets/WidgetAiscript.vue
index 947dbe5e7..d6c94cd56 100644
--- a/packages/frontend/src/widgets/WidgetAiscript.vue
+++ b/packages/frontend/src/widgets/WidgetAiscript.vue
@@ -1,5 +1,5 @@
 <template>
-<MkContainer :show-header="widgetProps.showHeader" data-cy-mkw-aiscript class="mkw-aiscript">
+<MkContainer :showHeader="widgetProps.showHeader" data-cy-mkw-aiscript class="mkw-aiscript">
 	<template #icon><i class="ti ti-terminal-2"></i></template>
 	<template #header>{{ i18n.ts._widgets.aiscript }}</template>
 
@@ -16,7 +16,7 @@
 <script lang="ts" setup>
 import { ref } from 'vue';
 import { Interpreter, Parser, utils } from '@syuilo/aiscript';
-import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
 import * as os from '@/os';
 import MkContainer from '@/components/MkContainer.vue';
@@ -41,11 +41,8 @@ const widgetPropsDef = {
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/frontend/src/widgets/WidgetAiscriptApp.vue b/packages/frontend/src/widgets/WidgetAiscriptApp.vue
index 455a6e6ea..3b67972e4 100644
--- a/packages/frontend/src/widgets/WidgetAiscriptApp.vue
+++ b/packages/frontend/src/widgets/WidgetAiscriptApp.vue
@@ -1,5 +1,5 @@
 <template>
-<MkContainer :show-header="widgetProps.showHeader" class="mkw-aiscriptApp">
+<MkContainer :showHeader="widgetProps.showHeader" class="mkw-aiscriptApp">
 	<template #header>App</template>
 	<div :class="$style.root">
 		<MkAsUi v-if="root" :component="root" :components="components" size="small"/>
@@ -10,7 +10,7 @@
 <script lang="ts" setup>
 import { onMounted, Ref, ref, watch } from 'vue';
 import { Interpreter, Parser } from '@syuilo/aiscript';
-import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
 import * as os from '@/os';
 import { createAiScriptEnv } from '@/scripts/aiscript/api';
@@ -35,12 +35,9 @@ const widgetPropsDef = {
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
-
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
+	
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
 	props,
diff --git a/packages/frontend/src/widgets/WidgetButton.vue b/packages/frontend/src/widgets/WidgetButton.vue
index 9eee9680d..bcb380f84 100644
--- a/packages/frontend/src/widgets/WidgetButton.vue
+++ b/packages/frontend/src/widgets/WidgetButton.vue
@@ -8,7 +8,7 @@
 
 <script lang="ts" setup>
 import { Interpreter, Parser } from '@syuilo/aiscript';
-import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
 import * as os from '@/os';
 import { createAiScriptEnv } from '@/scripts/aiscript/api';
@@ -35,11 +35,8 @@ const widgetPropsDef = {
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
@@ -101,8 +98,3 @@ defineExpose<WidgetComponentExpose>({
 	id: props.widget ? props.widget.id : null,
 });
 </script>
-
-<style lang="scss" scoped>
-.mkw-button {
-}
-</style>
diff --git a/packages/frontend/src/widgets/WidgetCalendar.vue b/packages/frontend/src/widgets/WidgetCalendar.vue
index 58d073226..447525837 100644
--- a/packages/frontend/src/widgets/WidgetCalendar.vue
+++ b/packages/frontend/src/widgets/WidgetCalendar.vue
@@ -34,7 +34,7 @@
 
 <script lang="ts" setup>
 import { ref } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
 import { i18n } from '@/i18n';
 import { useInterval } from '@/scripts/use-interval';
@@ -50,11 +50,8 @@ const widgetPropsDef = {
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/frontend/src/widgets/WidgetClicker.vue b/packages/frontend/src/widgets/WidgetClicker.vue
index 981788a3c..b7be2e8c8 100644
--- a/packages/frontend/src/widgets/WidgetClicker.vue
+++ b/packages/frontend/src/widgets/WidgetClicker.vue
@@ -1,5 +1,5 @@
 <template>
-<MkContainer :show-header="widgetProps.showHeader" class="mkw-clicker">
+<MkContainer :showHeader="widgetProps.showHeader" class="mkw-clicker">
 	<template #icon><i class="ti ti-cookie"></i></template>
 	<template #header>Clicker</template>
 	<MkClickerGame/>
@@ -7,7 +7,7 @@
 </template>
 
 <script lang="ts" setup>
-import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
 import MkContainer from '@/components/MkContainer.vue';
 import MkClickerGame from '@/components/MkClickerGame.vue';
@@ -23,12 +23,9 @@ const widgetPropsDef = {
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
-
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
+	
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
 	props,
diff --git a/packages/frontend/src/widgets/WidgetClock.vue b/packages/frontend/src/widgets/WidgetClock.vue
index ebd73cb9f..aee5026db 100644
--- a/packages/frontend/src/widgets/WidgetClock.vue
+++ b/packages/frontend/src/widgets/WidgetClock.vue
@@ -1,25 +1,31 @@
 <template>
-<MkContainer :naked="widgetProps.transparent" :show-header="false" data-cy-mkw-clock class="mkw-clock">
-	<div class="vubelbmv" :class="widgetProps.size">
-		<div v-if="widgetProps.label === 'tz' || widgetProps.label === 'timeAndTz'" class="_monospace label a abbrev">{{ tzAbbrev }}</div>
+<MkContainer :naked="widgetProps.transparent" :showHeader="false" data-cy-mkw-clock>
+	<div
+		:class="[$style.root, {
+			[$style.small]: widgetProps.size === 'small',
+			[$style.medium]: widgetProps.size === 'medium',
+			[$style.large]: widgetProps.size === 'large',
+		}]"
+	>
+		<div v-if="widgetProps.label === 'tz' || widgetProps.label === 'timeAndTz'" class="_monospace" :class="[$style.label, $style.a]">{{ tzAbbrev }}</div>
 		<MkAnalogClock
-			class="clock"
+			:class="$style.clock"
 			:thickness="widgetProps.thickness"
 			:offset="tzOffset"
 			:graduations="widgetProps.graduations"
-			:fade-graduations="widgetProps.fadeGraduations"
+			:fadeGraduations="widgetProps.fadeGraduations"
 			:twentyfour="widgetProps.twentyFour"
-			:s-animation="widgetProps.sAnimation"
+			:sAnimation="widgetProps.sAnimation"
 		/>
-		<MkDigitalClock v-if="widgetProps.label === 'time' || widgetProps.label === 'timeAndTz'" class="_monospace label c time" :show-s="false" :offset="tzOffset"/>
-		<div v-if="widgetProps.label === 'tz' || widgetProps.label === 'timeAndTz'" class="_monospace label d offset">{{ tzOffsetLabel }}</div>
+		<MkDigitalClock v-if="widgetProps.label === 'time' || widgetProps.label === 'timeAndTz'" :class="[$style.label, $style.c]" class="_monospace" :showS="false" :offset="tzOffset"/>
+		<div v-if="widgetProps.label === 'tz' || widgetProps.label === 'timeAndTz'" class="_monospace" :class="[$style.label, $style.d]">{{ tzOffsetLabel }}</div>
 	</div>
 </MkContainer>
 </template>
 
 <script lang="ts" setup>
 import { } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
 import MkContainer from '@/components/MkContainer.vue';
 import MkAnalogClock from '@/components/MkAnalogClock.vue';
@@ -114,11 +120,8 @@ const widgetPropsDef = {
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
@@ -143,39 +146,10 @@ defineExpose<WidgetComponentExpose>({
 });
 </script>
 
-<style lang="scss" scoped>
-.vubelbmv {
+<style lang="scss" module>
+.root {
 	position: relative;
 
-	> .label {
-		position: absolute;
-		opacity: 0.7;
-
-		&.a {
-			top: 14px;
-			left: 14px;
-		}
-
-		&.b {
-			top: 14px;
-			right: 14px;
-		}
-
-		&.c {
-			bottom: 14px;
-			left: 14px;
-		}
-
-		&.d {
-			bottom: 14px;
-			right: 14px;
-		}
-	}
-
-	> .clock {
-		margin: auto;
-	}
-
 	&.small {
 		padding: 12px;
 
@@ -200,4 +174,33 @@ defineExpose<WidgetComponentExpose>({
 		}
 	}
 }
+
+.label {
+	position: absolute;
+	opacity: 0.7;
+
+	&.a {
+		top: 14px;
+		left: 14px;
+	}
+
+	&.b {
+		top: 14px;
+		right: 14px;
+	}
+
+	&.c {
+		bottom: 14px;
+		left: 14px;
+	}
+
+	&.d {
+		bottom: 14px;
+		right: 14px;
+	}
+}
+
+.clock {
+	margin: auto;
+}
 </style>
diff --git a/packages/frontend/src/widgets/WidgetDigitalClock.vue b/packages/frontend/src/widgets/WidgetDigitalClock.vue
index cdd9c3a40..6148177d9 100644
--- a/packages/frontend/src/widgets/WidgetDigitalClock.vue
+++ b/packages/frontend/src/widgets/WidgetDigitalClock.vue
@@ -2,14 +2,14 @@
 <div data-cy-mkw-digitalClock class="_monospace" :class="[$style.root, { _panel: !widgetProps.transparent }]" :style="{ fontSize: `${widgetProps.fontSize}em` }">
 	<div v-if="widgetProps.showLabel" :class="$style.label">{{ tzAbbrev }}</div>
 	<div>
-		<MkDigitalClock :show-ms="widgetProps.showMs" :offset="tzOffset"/>
+		<MkDigitalClock :showMs="widgetProps.showMs" :offset="tzOffset"/>
 	</div>
 	<div v-if="widgetProps.showLabel" :class="$style.label">{{ tzOffsetLabel }}</div>
 </div>
 </template>
 
 <script lang="ts" setup>
-import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
 import { timezones } from '@/scripts/timezones';
 import MkDigitalClock from '@/components/MkDigitalClock.vue';
@@ -49,11 +49,8 @@ const widgetPropsDef = {
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/frontend/src/widgets/WidgetFederation.vue b/packages/frontend/src/widgets/WidgetFederation.vue
index 2033b074e..951c4aaa6 100644
--- a/packages/frontend/src/widgets/WidgetFederation.vue
+++ b/packages/frontend/src/widgets/WidgetFederation.vue
@@ -1,5 +1,5 @@
 <template>
-<MkContainer :show-header="widgetProps.showHeader" :foldable="foldable" :scrollable="scrollable" data-cy-mkw-federation class="mkw-federation">
+<MkContainer :showHeader="widgetProps.showHeader" :foldable="foldable" :scrollable="scrollable" data-cy-mkw-federation class="mkw-federation">
 	<template #icon><i class="ti ti-whirl"></i></template>
 	<template #header>{{ i18n.ts._widgets.federation }}</template>
 
@@ -21,7 +21,7 @@
 
 <script lang="ts" setup>
 import { ref } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
 import MkContainer from '@/components/MkContainer.vue';
 import MkMiniChart from '@/components/MkMiniChart.vue';
@@ -42,11 +42,8 @@ const widgetPropsDef = {
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps> & { foldable?: boolean; scrollable?: boolean; }>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; foldable?: boolean; scrollable?: boolean; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/frontend/src/widgets/WidgetInstanceCloud.vue b/packages/frontend/src/widgets/WidgetInstanceCloud.vue
index b15780765..f8b811e6b 100644
--- a/packages/frontend/src/widgets/WidgetInstanceCloud.vue
+++ b/packages/frontend/src/widgets/WidgetInstanceCloud.vue
@@ -1,5 +1,5 @@
 <template>
-<MkContainer :naked="widgetProps.transparent" :show-header="false" class="mkw-instance-cloud">
+<MkContainer :naked="widgetProps.transparent" :showHeader="false" class="mkw-instance-cloud">
 	<div class="">
 		<MkTagCloud v-if="activeInstances">
 			<li v-for="instance in activeInstances" :key="instance.id">
@@ -14,7 +14,7 @@
 
 <script lang="ts" setup>
 import { } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
 import MkContainer from '@/components/MkContainer.vue';
 import MkTagCloud from '@/components/MkTagCloud.vue';
@@ -33,11 +33,8 @@ const widgetPropsDef = {
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
@@ -75,7 +72,3 @@ defineExpose<WidgetComponentExpose>({
 	id: props.widget ? props.widget.id : null,
 });
 </script>
-
-<style lang="scss" scoped>
-
-</style>
diff --git a/packages/frontend/src/widgets/WidgetInstanceInfo.vue b/packages/frontend/src/widgets/WidgetInstanceInfo.vue
index d702fd2cb..c77b98f8f 100644
--- a/packages/frontend/src/widgets/WidgetInstanceInfo.vue
+++ b/packages/frontend/src/widgets/WidgetInstanceInfo.vue
@@ -15,7 +15,7 @@
 </template>
 
 <script lang="ts" setup>
-import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
 import { host } from '@/config';
 import { instance } from '@/instance';
@@ -27,11 +27,8 @@ const widgetPropsDef = {
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/frontend/src/widgets/WidgetJobQueue.vue b/packages/frontend/src/widgets/WidgetJobQueue.vue
index 84043cf13..3c8ffdb55 100644
--- a/packages/frontend/src/widgets/WidgetJobQueue.vue
+++ b/packages/frontend/src/widgets/WidgetJobQueue.vue
@@ -47,9 +47,9 @@
 
 <script lang="ts" setup>
 import { onUnmounted, reactive } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
-import { stream } from '@/stream';
+import { useStream } from '@/stream';
 import number from '@/filters/number';
 import * as sound from '@/scripts/sound';
 import { deepClone } from '@/scripts/clone';
@@ -69,11 +69,8 @@ const widgetPropsDef = {
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
@@ -81,7 +78,7 @@ const { widgetProps, configure } = useWidgetPropsManager(name,
 	emit,
 );
 
-const connection = stream.useChannel('queueStats');
+const connection = useStream().useChannel('queueStats');
 const current = reactive({
 	inbox: {
 		activeSincePrevTick: 0,
diff --git a/packages/frontend/src/widgets/WidgetMemo.vue b/packages/frontend/src/widgets/WidgetMemo.vue
index 959cf776a..78d27a31b 100644
--- a/packages/frontend/src/widgets/WidgetMemo.vue
+++ b/packages/frontend/src/widgets/WidgetMemo.vue
@@ -1,5 +1,5 @@
 <template>
-<MkContainer :show-header="widgetProps.showHeader" data-cy-mkw-memo class="mkw-memo">
+<MkContainer :showHeader="widgetProps.showHeader" data-cy-mkw-memo class="mkw-memo">
 	<template #icon><i class="ti ti-note"></i></template>
 	<template #header>{{ i18n.ts._widgets.memo }}</template>
 
@@ -12,7 +12,7 @@
 
 <script lang="ts" setup>
 import { ref, watch } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
 import MkContainer from '@/components/MkContainer.vue';
 import { defaultStore } from '@/store';
@@ -33,11 +33,8 @@ const widgetPropsDef = {
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/frontend/src/widgets/WidgetNotifications.vue b/packages/frontend/src/widgets/WidgetNotifications.vue
index 661f68b27..a24aa9b2e 100644
--- a/packages/frontend/src/widgets/WidgetNotifications.vue
+++ b/packages/frontend/src/widgets/WidgetNotifications.vue
@@ -1,18 +1,18 @@
 <template>
-<MkContainer :style="`height: ${widgetProps.height}px;`" :show-header="widgetProps.showHeader" :scrollable="true" data-cy-mkw-notifications class="mkw-notifications">
+<MkContainer :style="`height: ${widgetProps.height}px;`" :showHeader="widgetProps.showHeader" :scrollable="true" data-cy-mkw-notifications class="mkw-notifications">
 	<template #icon><i class="ti ti-bell"></i></template>
 	<template #header>{{ i18n.ts.notifications }}</template>
 	<template #func="{ buttonStyleClass }"><button class="_button" :class="buttonStyleClass" @click="configureNotification()"><i class="ti ti-settings"></i></button></template>
 
 	<div>
-		<XNotifications :include-types="widgetProps.includingTypes"/>
+		<XNotifications :includeTypes="widgetProps.includingTypes"/>
 	</div>
 </MkContainer>
 </template>
 
 <script lang="ts" setup>
 import { defineAsyncComponent } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
 import MkContainer from '@/components/MkContainer.vue';
 import XNotifications from '@/components/MkNotifications.vue';
@@ -39,12 +39,9 @@ const widgetPropsDef = {
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
-
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
+	
 const { widgetProps, configure, save } = useWidgetPropsManager(name,
 	widgetPropsDef,
 	props,
diff --git a/packages/frontend/src/widgets/WidgetOnlineUsers.vue b/packages/frontend/src/widgets/WidgetOnlineUsers.vue
index 44e073545..c920c3ca5 100644
--- a/packages/frontend/src/widgets/WidgetOnlineUsers.vue
+++ b/packages/frontend/src/widgets/WidgetOnlineUsers.vue
@@ -1,14 +1,16 @@
 <template>
-<div data-cy-mkw-onlineUsers class="mkw-onlineUsers" :class="{ _panel: !widgetProps.transparent, pad: !widgetProps.transparent }">
-	<I18n v-if="onlineUsersCount" :src="i18n.ts.onlineUsersCount" text-tag="span" class="text">
-		<template #n><b>{{ number(onlineUsersCount) }}</b></template>
-	</I18n>
+<div data-cy-mkw-onlineUsers :class="[$style.root, { _panel: !widgetProps.transparent, [$style.pad]: !widgetProps.transparent }]">
+	<span :class="$style.text">
+		<I18n v-if="onlineUsersCount" :src="i18n.ts.onlineUsersCount" textTag="span">
+			<template #n><b style="color: #41b781;">{{ number(onlineUsersCount) }}</b></template>
+		</I18n>
+	</span>
 </div>
 </template>
 
 <script lang="ts" setup>
 import { ref } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
 import * as os from '@/os';
 import { useInterval } from '@/scripts/use-interval';
@@ -26,11 +28,8 @@ const widgetPropsDef = {
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
@@ -58,22 +57,16 @@ defineExpose<WidgetComponentExpose>({
 });
 </script>
 
-<style lang="scss" scoped>
-.mkw-onlineUsers {
+<style lang="scss" module>
+.root {
 	text-align: center;
 
 	&.pad {
 		padding: 16px 0;
 	}
+}
 
-	> .text {
-		::v-deep(b) {
-			color: #41b781;
-		}
-
-		::v-deep(span) {
-			opacity: 0.7;
-		}
-	}
+.text {
+	color: var(--fgTransparentWeak);
 }
 </style>
diff --git a/packages/frontend/src/widgets/WidgetPhotos.vue b/packages/frontend/src/widgets/WidgetPhotos.vue
index 716bbb427..5c6a8cbf8 100644
--- a/packages/frontend/src/widgets/WidgetPhotos.vue
+++ b/packages/frontend/src/widgets/WidgetPhotos.vue
@@ -1,5 +1,5 @@
 <template>
-<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent" :class="$style.root" :data-transparent="widgetProps.transparent ? true : null" data-cy-mkw-photos class="mkw-photos">
+<MkContainer :showHeader="widgetProps.showHeader" :naked="widgetProps.transparent" :class="$style.root" :data-transparent="widgetProps.transparent ? true : null" data-cy-mkw-photos class="mkw-photos">
 	<template #icon><i class="ti ti-camera"></i></template>
 	<template #header>{{ i18n.ts._widgets.photos }}</template>
 
@@ -18,9 +18,9 @@
 
 <script lang="ts" setup>
 import { onUnmounted, ref } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
-import { stream } from '@/stream';
+import { useStream } from '@/stream';
 import { getStaticImageUrl } from '@/scripts/media-proxy';
 import * as os from '@/os';
 import MkContainer from '@/components/MkContainer.vue';
@@ -42,11 +42,8 @@ const widgetPropsDef = {
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
@@ -54,7 +51,7 @@ const { widgetProps, configure } = useWidgetPropsManager(name,
 	emit,
 );
 
-const connection = stream.useChannel('main');
+const connection = useStream().useChannel('main');
 const images = ref([]);
 const fetching = ref(true);
 
diff --git a/packages/frontend/src/widgets/WidgetPostForm.vue b/packages/frontend/src/widgets/WidgetPostForm.vue
index 7a96b0021..bc63f0282 100644
--- a/packages/frontend/src/widgets/WidgetPostForm.vue
+++ b/packages/frontend/src/widgets/WidgetPostForm.vue
@@ -4,7 +4,7 @@
 
 <script lang="ts" setup>
 import { } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
 import MkPostForm from '@/components/MkPostForm.vue';
 
@@ -15,11 +15,8 @@ const widgetPropsDef = {
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/frontend/src/widgets/WidgetProfile.vue b/packages/frontend/src/widgets/WidgetProfile.vue
index 819663a36..72e229ef8 100644
--- a/packages/frontend/src/widgets/WidgetProfile.vue
+++ b/packages/frontend/src/widgets/WidgetProfile.vue
@@ -17,7 +17,7 @@
 </template>
 
 <script lang="ts" setup>
-import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
 import { $i } from '@/account';
 import { userPage } from '@/filters/user';
@@ -29,11 +29,8 @@ const widgetPropsDef = {
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/frontend/src/widgets/WidgetRss.vue b/packages/frontend/src/widgets/WidgetRss.vue
index 18fa2e2c2..1be882c66 100644
--- a/packages/frontend/src/widgets/WidgetRss.vue
+++ b/packages/frontend/src/widgets/WidgetRss.vue
@@ -1,5 +1,5 @@
 <template>
-<MkContainer :show-header="widgetProps.showHeader" data-cy-mkw-rss class="mkw-rss">
+<MkContainer :showHeader="widgetProps.showHeader" data-cy-mkw-rss class="mkw-rss">
 	<template #icon><i class="ti ti-rss"></i></template>
 	<template #header>RSS</template>
 	<template #func="{ buttonStyleClass }"><button class="_button" :class="buttonStyleClass" @click="configure"><i class="ti ti-settings"></i></button></template>
@@ -19,7 +19,7 @@
 
 <script lang="ts" setup>
 import { ref, watch, computed } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
 import MkContainer from '@/components/MkContainer.vue';
 import { url as base } from '@/config';
@@ -49,11 +49,8 @@ const widgetPropsDef = {
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/frontend/src/widgets/WidgetRssTicker.vue b/packages/frontend/src/widgets/WidgetRssTicker.vue
index b0408f0d7..6b346c059 100644
--- a/packages/frontend/src/widgets/WidgetRssTicker.vue
+++ b/packages/frontend/src/widgets/WidgetRssTicker.vue
@@ -1,5 +1,5 @@
 <template>
-<MkContainer :naked="widgetProps.transparent" :show-header="widgetProps.showHeader" class="mkw-rss-ticker">
+<MkContainer :naked="widgetProps.transparent" :showHeader="widgetProps.showHeader" class="mkw-rss-ticker">
 	<template #icon><i class="ti ti-rss"></i></template>
 	<template #header>RSS</template>
 	<template #func="{ buttonStyleClass }"><button class="_button" :class="buttonStyleClass" @click="configure"><i class="ti ti-settings"></i></button></template>
@@ -12,7 +12,7 @@
 			<Transition :name="$style.change" mode="default" appear>
 				<MarqueeText :key="key" :duration="widgetProps.duration" :reverse="widgetProps.reverse">
 					<span v-for="item in items" :key="item.link" :class="$style.item">
-						<a :class="$style.link" :href="item.link" rel="nofollow noopener" target="_blank" :title="item.title">{{ item.title }}</a><span :class="$style.divider"></span>
+						<a :href="item.link" rel="nofollow noopener" target="_blank" :title="item.title">{{ item.title }}</a><span :class="$style.divider"></span>
 					</span>
 				</MarqueeText>
 			</Transition>
@@ -23,7 +23,7 @@
 
 <script lang="ts" setup>
 import { ref, watch, computed } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import MarqueeText from '@/components/MkMarquee.vue';
 import { GetFormResultType } from '@/scripts/form';
 import MkContainer from '@/components/MkContainer.vue';
@@ -73,11 +73,8 @@ const widgetPropsDef = {
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/frontend/src/widgets/WidgetSlideshow.vue b/packages/frontend/src/widgets/WidgetSlideshow.vue
index 915e7aaaf..d4ede5792 100644
--- a/packages/frontend/src/widgets/WidgetSlideshow.vue
+++ b/packages/frontend/src/widgets/WidgetSlideshow.vue
@@ -13,7 +13,7 @@
 
 <script lang="ts" setup>
 import { onMounted, ref, shallowRef } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
 import * as os from '@/os';
 import { useInterval } from '@/scripts/use-interval';
@@ -35,11 +35,8 @@ const widgetPropsDef = {
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 
 const { widgetProps, configure, save } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/frontend/src/widgets/WidgetTimeline.vue b/packages/frontend/src/widgets/WidgetTimeline.vue
index 71ee75f6c..3d497c2e2 100644
--- a/packages/frontend/src/widgets/WidgetTimeline.vue
+++ b/packages/frontend/src/widgets/WidgetTimeline.vue
@@ -1,5 +1,5 @@
 <template>
-<MkContainer :show-header="widgetProps.showHeader" :style="`height: ${widgetProps.height}px;`" :scrollable="true" data-cy-mkw-timeline class="mkw-timeline">
+<MkContainer :showHeader="widgetProps.showHeader" :style="`height: ${widgetProps.height}px;`" :scrollable="true" data-cy-mkw-timeline class="mkw-timeline">
 	<template #icon>
 		<i v-if="widgetProps.src === 'home'" class="ti ti-home"></i>
 		<i v-else-if="widgetProps.src === 'local'" class="ti ti-planet"></i>
@@ -30,7 +30,7 @@
 
 <script lang="ts" setup>
 import { ref } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
 import * as os from '@/os';
 import MkContainer from '@/components/MkContainer.vue';
@@ -71,11 +71,8 @@ const widgetPropsDef = {
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 
 const { widgetProps, configure, save } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/frontend/src/widgets/WidgetTrends.vue b/packages/frontend/src/widgets/WidgetTrends.vue
index 01450a7ab..36f908d5e 100644
--- a/packages/frontend/src/widgets/WidgetTrends.vue
+++ b/packages/frontend/src/widgets/WidgetTrends.vue
@@ -1,5 +1,5 @@
 <template>
-<MkContainer :show-header="widgetProps.showHeader" data-cy-mkw-trends class="mkw-trends">
+<MkContainer :showHeader="widgetProps.showHeader" data-cy-mkw-trends class="mkw-trends">
 	<template #icon><i class="ti ti-hash"></i></template>
 	<template #header>{{ i18n.ts._widgets.trends }}</template>
 
@@ -20,7 +20,7 @@
 
 <script lang="ts" setup>
 import { ref } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
 import MkContainer from '@/components/MkContainer.vue';
 import MkMiniChart from '@/components/MkMiniChart.vue';
@@ -40,11 +40,8 @@ const widgetPropsDef = {
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/frontend/src/widgets/WidgetUnixClock.vue b/packages/frontend/src/widgets/WidgetUnixClock.vue
index 22162d2b2..f1af71add 100644
--- a/packages/frontend/src/widgets/WidgetUnixClock.vue
+++ b/packages/frontend/src/widgets/WidgetUnixClock.vue
@@ -12,7 +12,7 @@
 
 <script lang="ts" setup>
 import { onUnmounted, ref, watch } from 'vue';
-import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
 
 const name = 'unixClock';
@@ -39,11 +39,8 @@ const widgetPropsDef = {
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 
 const { widgetProps, configure } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/frontend/src/widgets/WidgetUserList.vue b/packages/frontend/src/widgets/WidgetUserList.vue
index b8811d2fe..4380fdb62 100644
--- a/packages/frontend/src/widgets/WidgetUserList.vue
+++ b/packages/frontend/src/widgets/WidgetUserList.vue
@@ -1,5 +1,5 @@
 <template>
-<MkContainer :show-header="widgetProps.showHeader" class="mkw-userList">
+<MkContainer :showHeader="widgetProps.showHeader" class="mkw-userList">
 	<template #icon><i class="ti ti-users"></i></template>
 	<template #header>{{ list ? list.name : i18n.ts._widgets.userList }}</template>
 	<template #func="{ buttonStyleClass }"><button class="_button" :class="buttonStyleClass" @click="configure()"><i class="ti ti-settings"></i></button></template>
@@ -19,7 +19,7 @@
 </template>
 
 <script lang="ts" setup>
-import { useWidgetPropsManager, Widget, WidgetComponentExpose } from './widget';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
 import { GetFormResultType } from '@/scripts/form';
 import MkContainer from '@/components/MkContainer.vue';
 import * as os from '@/os';
@@ -43,11 +43,8 @@ const widgetPropsDef = {
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 
 const { widgetProps, configure, save } = useWidgetPropsManager(name,
 	widgetPropsDef,
diff --git a/packages/frontend/src/widgets/server-metric/index.vue b/packages/frontend/src/widgets/server-metric/index.vue
index 357d0ab78..e019ff540 100644
--- a/packages/frontend/src/widgets/server-metric/index.vue
+++ b/packages/frontend/src/widgets/server-metric/index.vue
@@ -1,5 +1,5 @@
 <template>
-<MkContainer :show-header="widgetProps.showHeader" :naked="widgetProps.transparent">
+<MkContainer :showHeader="widgetProps.showHeader" :naked="widgetProps.transparent">
 	<template #icon><i class="ti ti-server"></i></template>
 	<template #header>{{ i18n.ts._widgets.serverMetric }}</template>
 	<template #func="{ buttonStyleClass }"><button class="_button" :class="buttonStyleClass" @click="toggleView()"><i class="ti ti-selector"></i></button></template>
@@ -25,7 +25,7 @@ import XDisk from './disk.vue';
 import MkContainer from '@/components/MkContainer.vue';
 import { GetFormResultType } from '@/scripts/form';
 import * as os from '@/os';
-import { stream } from '@/stream';
+import { useStream } from '@/stream';
 import { i18n } from '@/i18n';
 
 const name = 'serverMetric';
@@ -75,7 +75,7 @@ const toggleView = () => {
 	save();
 };
 
-const connection = stream.useChannel('serverStats');
+const connection = useStream().useChannel('serverStats');
 onUnmounted(() => {
 	connection.dispose();
 });
diff --git a/packages/frontend/src/widgets/server-metric/pie.vue b/packages/frontend/src/widgets/server-metric/pie.vue
index 868dbc048..398815a6a 100644
--- a/packages/frontend/src/widgets/server-metric/pie.vue
+++ b/packages/frontend/src/widgets/server-metric/pie.vue
@@ -1,11 +1,12 @@
 <template>
-<svg class="hsalcinq" viewBox="0 0 1 1" preserveAspectRatio="none">
+<svg :class="$style.root" viewBox="0 0 1 1" preserveAspectRatio="none">
 	<circle
 		:r="r"
 		cx="50%" cy="50%"
 		fill="none"
 		stroke-width="0.1"
 		stroke="rgba(0, 0, 0, 0.05)"
+		:class="$style.circle"
 	/>
 	<circle
 		:r="r"
@@ -16,7 +17,7 @@
 		stroke-width="0.1"
 		:stroke="color"
 	/>
-	<text x="50%" y="50%" dy="0.05" text-anchor="middle">{{ (value * 100).toFixed(0) }}%</text>
+	<text x="50%" y="50%" dy="0.05" text-anchor="middle" :class="$style.text">{{ (value * 100).toFixed(0) }}%</text>
 </svg>
 </template>
 
@@ -33,20 +34,20 @@ const color = $computed(() => `hsl(${180 - (props.value * 180)}, 80%, 70%)`);
 const strokeDashoffset = $computed(() => (1 - props.value) * (Math.PI * (r * 2)));
 </script>
 
-<style lang="scss" scoped>
-.hsalcinq {
+<style lang="scss" module>
+.root {
 	display: block;
 	height: 100%;
+}
 
-	> circle {
-		transform-origin: center;
-		transform: rotate(-90deg);
-		transition: stroke-dashoffset 0.5s ease;
-	}
+.circle {
+	transform-origin: center;
+	transform: rotate(-90deg);
+	transition: stroke-dashoffset 0.5s ease;
+}
 
-	> text {
-		font-size: 0.15px;
-		fill: currentColor;
-	}
+.text {
+	font-size: 0.15px;
+	fill: currentColor;
 }
 </style>
diff --git a/packages/frontend/src/workers/draw-blurhash.ts b/packages/frontend/src/workers/draw-blurhash.ts
new file mode 100644
index 000000000..5f2168a44
--- /dev/null
+++ b/packages/frontend/src/workers/draw-blurhash.ts
@@ -0,0 +1,15 @@
+import { render } from 'buraha';
+
+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;
+    }
+    const work = new OffscreenCanvas(event.data.width ?? 64, event.data.height ?? 64);
+    render(event.data.hash, work);
+    const bitmap = work.transferToImageBitmap();
+    postMessage({ id: event.data.id, bitmap });
+};
diff --git a/packages/frontend/src/workers/test-webgl2.ts b/packages/frontend/src/workers/test-webgl2.ts
new file mode 100644
index 000000000..4769524d9
--- /dev/null
+++ b/packages/frontend/src/workers/test-webgl2.ts
@@ -0,0 +1,7 @@
+const canvas = new OffscreenCanvas(1, 1);
+const gl = canvas.getContext('webgl2');
+if (gl) {
+    postMessage({ result: true });
+} else {
+    postMessage({ result: false });
+}
diff --git a/packages/frontend/src/workers/tsconfig.json b/packages/frontend/src/workers/tsconfig.json
new file mode 100644
index 000000000..8ee893046
--- /dev/null
+++ b/packages/frontend/src/workers/tsconfig.json
@@ -0,0 +1,5 @@
+{
+	"compilerOptions": {
+		"lib": ["esnext", "webworker"],
+	}
+}
diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts
index fad0dd017..531dd0b48 100644
--- a/packages/frontend/vite.config.ts
+++ b/packages/frontend/vite.config.ts
@@ -6,7 +6,9 @@ import { type UserConfig, defineConfig } from 'vite';
 import ReactivityTransform from '@vue-macros/reactivity-transform/vite';
 
 import locales from '../../locales';
+import generateDTS from '../../locales/generateDTS';
 import meta from '../../package.json';
+import pluginUnwindCssModuleClassName from './lib/rollup-plugin-unwind-css-module-class-name';
 import pluginJson5 from './vite.json5';
 
 const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.json', '.json5', '.svg', '.sass', '.scss', '.css', '.vue'];
@@ -53,6 +55,7 @@ export function getConfig(): UserConfig {
 				reactivityTransform: true,
 			}),
 			ReactivityTransform(),
+			pluginUnwindCssModuleClassName(),
 			pluginJson5(),
 			...process.env.NODE_ENV === 'production'
 				? [
@@ -64,6 +67,10 @@ export function getConfig(): UserConfig {
 					}),
 				]
 				: [],
+			{
+				name: 'locale:generateDTS',
+				buildStart: generateDTS,
+			},
 		],
 
 		resolve: {
@@ -117,13 +124,15 @@ export function getConfig(): UserConfig {
 			manifest: 'manifest.json',
 			rollupOptions: {
 				input: {
-					app: './src/init.ts',
+					app: './src/_boot_.ts',
 				},
 				output: {
 					manualChunks: {
 						vue: ['vue'],
 						photoswipe: ['photoswipe', 'photoswipe/lightbox', 'photoswipe/style.css'],
 					},
+					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]',
 				},
 			},
 			cssCodeSplit: true,
@@ -139,6 +148,10 @@ export function getConfig(): UserConfig {
 			},
 		},
 
+		worker: {
+			format: 'es',
+		},
+
 		test: {
 			environment: 'happy-dom',
 			deps: {
diff --git a/packages/shared/.eslintrc.js b/packages/shared/.eslintrc.js
index 7c979a93d..a53ad1789 100644
--- a/packages/shared/.eslintrc.js
+++ b/packages/shared/.eslintrc.js
@@ -49,7 +49,7 @@ module.exports = {
 		'no-multi-spaces': ['error'],
 		'no-var': ['error'],
 		'prefer-arrow-callback': ['error'],
-		'no-throw-literal': ['warn'],
+		'no-throw-literal': ['error'],
 		'no-param-reassign': ['warn'],
 		'no-constant-condition': ['warn'],
 		'no-empty-pattern': ['warn'],
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 74a7e8985..f48fac3f4 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,4 +1,8 @@
-lockfileVersion: '6.0'
+lockfileVersion: '6.1'
+
+settings:
+  autoInstallPeers: true
+  excludeLinksFromLockfile: false
 
 overrides:
   chokidar: 3.5.3
@@ -30,8 +34,8 @@ importers:
         specifier: 4.1.0
         version: 4.1.0
       typescript:
-        specifier: 5.0.4
-        version: 5.0.4
+        specifier: 5.1.3
+        version: 5.1.3
     optionalDependencies:
       '@tensorflow/tfjs-core':
         specifier: 4.4.0
@@ -44,20 +48,20 @@ importers:
         specifier: 2.0.1
         version: 2.0.1
       '@typescript-eslint/eslint-plugin':
-        specifier: 5.59.5
-        version: 5.59.5(@typescript-eslint/parser@5.59.5)(eslint@8.40.0)(typescript@5.0.4)
+        specifier: 5.59.8
+        version: 5.59.8(@typescript-eslint/parser@5.59.8)(eslint@8.41.0)(typescript@5.1.3)
       '@typescript-eslint/parser':
-        specifier: 5.59.5
-        version: 5.59.5(eslint@8.40.0)(typescript@5.0.4)
+        specifier: 5.59.8
+        version: 5.59.8(eslint@8.41.0)(typescript@5.1.3)
       cross-env:
         specifier: 7.0.3
         version: 7.0.3
       cypress:
-        specifier: 12.12.0
-        version: 12.12.0
+        specifier: 12.13.0
+        version: 12.13.0
       eslint:
-        specifier: 8.40.0
-        version: 8.40.0
+        specifier: 8.41.0
+        version: 8.41.0
       start-server-and-test:
         specifier: 2.0.0
         version: 2.0.0
@@ -74,14 +78,14 @@ importers:
         specifier: 3.321.1
         version: 3.321.1
       '@bull-board/api':
-        specifier: 5.1.2
-        version: 5.1.2(@bull-board/ui@5.1.2)
+        specifier: 5.2.0
+        version: 5.2.0(@bull-board/ui@5.2.0)
       '@bull-board/fastify':
-        specifier: 5.1.2
-        version: 5.1.2
+        specifier: 5.2.0
+        version: 5.2.0
       '@bull-board/ui':
-        specifier: 5.1.2
-        version: 5.1.2
+        specifier: 5.2.0
+        version: 5.2.0
       '@discordapp/twemoji':
         specifier: 14.1.2
         version: 14.1.2
@@ -92,41 +96,41 @@ importers:
         specifier: 8.3.0
         version: 8.3.0
       '@fastify/cors':
-        specifier: 8.2.1
-        version: 8.2.1
+        specifier: 8.3.0
+        version: 8.3.0
       '@fastify/http-proxy':
         specifier: 9.1.0
-        version: 9.1.0
+        version: 9.1.0(bufferutil@4.0.7)(utf-8-validate@6.0.3)
       '@fastify/multipart':
         specifier: 7.6.0
         version: 7.6.0
       '@fastify/static':
-        specifier: 6.10.1
-        version: 6.10.1
+        specifier: 6.10.2
+        version: 6.10.2
       '@fastify/view':
         specifier: 7.4.1
         version: 7.4.1
       '@nestjs/common':
-        specifier: 9.4.0
-        version: 9.4.0(reflect-metadata@0.1.13)(rxjs@7.8.1)
+        specifier: 9.4.2
+        version: 9.4.2(reflect-metadata@0.1.13)(rxjs@7.8.1)
       '@nestjs/core':
-        specifier: 9.4.0
-        version: 9.4.0(@nestjs/common@9.4.0)(reflect-metadata@0.1.13)(rxjs@7.8.1)
+        specifier: 9.4.2
+        version: 9.4.2(@nestjs/common@9.4.2)(reflect-metadata@0.1.13)(rxjs@7.8.1)
       '@nestjs/testing':
-        specifier: 9.4.0
-        version: 9.4.0(@nestjs/common@9.4.0)(@nestjs/core@9.4.0)
+        specifier: 9.4.2
+        version: 9.4.2(@nestjs/common@9.4.2)(@nestjs/core@9.4.2)
       '@peertube/http-signature':
         specifier: 1.7.0
         version: 1.7.0
       '@sinonjs/fake-timers':
-        specifier: 10.0.2
-        version: 10.0.2
+        specifier: 10.2.0
+        version: 10.2.0
       '@swc/cli':
         specifier: 0.1.62
-        version: 0.1.62(@swc/core@1.3.56)(chokidar@3.5.3)
+        version: 0.1.62(@swc/core@1.3.61)(chokidar@3.5.3)
       '@swc/core':
-        specifier: 1.3.56
-        version: 1.3.56
+        specifier: 1.3.61
+        version: 1.3.61
       accepts:
         specifier: 1.3.8
         version: 1.3.8
@@ -145,15 +149,15 @@ importers:
       blurhash:
         specifier: 2.0.5
         version: 2.0.5
-      bull:
-        specifier: 4.10.4
-        version: 4.10.4
+      bullmq:
+        specifier: 3.15.0
+        version: 3.15.0
       cacheable-lookup:
         specifier: 6.1.0
         version: 6.1.0
       cbor:
-        specifier: 8.1.0
-        version: 8.1.0
+        specifier: 9.0.0
+        version: 9.0.0
       chalk:
         specifier: 5.2.0
         version: 5.2.0
@@ -200,8 +204,8 @@ importers:
         specifier: 12.6.0
         version: 12.6.0
       happy-dom:
-        specifier: 9.16.0
-        version: 9.16.0
+        specifier: 9.20.3
+        version: 9.20.3
       hpagent:
         specifier: 1.2.0
         version: 1.2.0
@@ -218,20 +222,20 @@ importers:
         specifier: 4.1.0
         version: 4.1.0
       jsdom:
-        specifier: 21.1.1
-        version: 21.1.1
+        specifier: 22.1.0
+        version: 22.1.0(bufferutil@4.0.7)(utf-8-validate@6.0.3)
       json5:
         specifier: 2.2.3
         version: 2.2.3
       jsonld:
-        specifier: 8.1.1
-        version: 8.1.1
+        specifier: 8.2.0
+        version: 8.2.0
       jsrsasign:
         specifier: 10.8.6
         version: 10.8.6
       meilisearch:
-        specifier: 0.32.3
-        version: 0.32.3
+        specifier: 0.32.5
+        version: 0.32.5
       mfm-js:
         specifier: 0.23.3
         version: 0.23.3
@@ -251,8 +255,8 @@ importers:
         specifier: 3.3.1
         version: 3.3.1
       nodemailer:
-        specifier: 6.9.2
-        version: 6.9.2
+        specifier: 6.9.3
+        version: 6.9.3
       nsfwjs:
         specifier: 2.4.2
         version: 2.4.2(@tensorflow/tfjs@4.4.0)
@@ -269,8 +273,8 @@ importers:
         specifier: 7.1.2
         version: 7.1.2
       pg:
-        specifier: 8.10.0
-        version: 8.10.0
+        specifier: 8.11.0
+        version: 8.11.0
       private-ip:
         specifier: 3.0.0
         version: 3.0.0
@@ -299,8 +303,8 @@ importers:
         specifier: 3.4.1
         version: 3.4.1
       re2:
-        specifier: 1.18.0
-        version: 1.18.0
+        specifier: 1.19.0
+        version: 1.19.0
       redis-lock:
         specifier: 0.1.4
         version: 0.1.4
@@ -329,8 +333,8 @@ importers:
         specifier: 3.0.5
         version: 3.0.5
       semver:
-        specifier: 7.5.0
-        version: 7.5.0
+        specifier: 7.5.1
+        version: 7.5.1
       sharp:
         specifier: 0.32.1
         version: 0.32.1
@@ -338,8 +342,8 @@ importers:
         specifier: github:misskey-dev/sharp-read-bmp
         version: github.com/misskey-dev/sharp-read-bmp/02d9dc189fa7df0c4bea09330be26741772dac01
       slacc:
-        specifier: 0.0.7
-        version: 0.0.7
+        specifier: 0.0.9
+        version: 0.0.9
       strict-event-emitter-types:
         specifier: 2.0.0
         version: 2.0.0
@@ -350,8 +354,8 @@ importers:
         specifier: github:misskey-dev/summaly
         version: github.com/misskey-dev/summaly/77dd5654bb82280b38c1f50e51a771c33f3df503
       systeminformation:
-        specifier: 5.17.12
-        version: 5.17.12
+        specifier: 5.17.16
+        version: 5.17.16
       tinycolor2:
         specifier: 1.6.0
         version: 1.6.0
@@ -369,16 +373,16 @@ importers:
         version: 14.0.0
       typeorm:
         specifier: 0.3.16
-        version: 0.3.16(ioredis@5.3.2)(pg@8.10.0)
+        version: 0.3.16(ioredis@5.3.2)(pg@8.11.0)
       typescript:
-        specifier: 5.0.4
-        version: 5.0.4
+        specifier: 5.1.3
+        version: 5.1.3
       ulid:
         specifier: 2.3.0
         version: 2.3.0
       unzipper:
-        specifier: 0.10.11
-        version: 0.10.11
+        specifier: 0.10.14
+        version: 0.10.14
       uuid:
         specifier: 9.0.0
         version: 9.0.0
@@ -388,12 +392,9 @@ importers:
       web-push:
         specifier: 3.6.1
         version: 3.6.1
-      websocket:
-        specifier: 1.0.34
-        version: 1.0.34
       ws:
         specifier: 8.13.0
-        version: 8.13.0
+        version: 8.13.0(bufferutil@4.0.7)(utf-8-validate@6.0.3)
       xev:
         specifier: 3.0.2
         version: 3.0.2
@@ -437,46 +438,55 @@ importers:
       '@tensorflow/tfjs-node':
         specifier: 4.4.0
         version: 4.4.0(seedrandom@3.0.5)
+      bufferutil:
+        specifier: ^4.0.7
+        version: 4.0.7
       slacc-android-arm-eabi:
-        specifier: 0.0.7
-        version: 0.0.7
+        specifier: 0.0.9
+        version: 0.0.9
       slacc-android-arm64:
-        specifier: 0.0.7
-        version: 0.0.7
+        specifier: 0.0.9
+        version: 0.0.9
       slacc-darwin-arm64:
-        specifier: 0.0.7
-        version: 0.0.7
+        specifier: 0.0.9
+        version: 0.0.9
       slacc-darwin-universal:
-        specifier: 0.0.7
-        version: 0.0.7
+        specifier: 0.0.9
+        version: 0.0.9
       slacc-darwin-x64:
-        specifier: 0.0.7
-        version: 0.0.7
+        specifier: 0.0.9
+        version: 0.0.9
+      slacc-freebsd-x64:
+        specifier: 0.0.9
+        version: 0.0.9
       slacc-linux-arm-gnueabihf:
-        specifier: 0.0.7
-        version: 0.0.7
+        specifier: 0.0.9
+        version: 0.0.9
       slacc-linux-arm64-gnu:
-        specifier: 0.0.7
-        version: 0.0.7
+        specifier: 0.0.9
+        version: 0.0.9
       slacc-linux-arm64-musl:
-        specifier: 0.0.7
-        version: 0.0.7
+        specifier: 0.0.9
+        version: 0.0.9
       slacc-linux-x64-gnu:
-        specifier: 0.0.7
-        version: 0.0.7
+        specifier: 0.0.9
+        version: 0.0.9
       slacc-win32-arm64-msvc:
-        specifier: 0.0.7
-        version: 0.0.7
+        specifier: 0.0.9
+        version: 0.0.9
       slacc-win32-x64-msvc:
-        specifier: 0.0.7
-        version: 0.0.7
+        specifier: 0.0.9
+        version: 0.0.9
+      utf-8-validate:
+        specifier: ^6.0.3
+        version: 6.0.3
     devDependencies:
       '@jest/globals':
         specifier: 29.5.0
         version: 29.5.0
       '@swc/jest':
         specifier: 0.2.26
-        version: 0.2.26(@swc/core@1.3.56)
+        version: 0.2.26(@swc/core@1.3.61)
       '@types/accepts':
         specifier: 1.3.5
         version: 1.3.5
@@ -486,9 +496,6 @@ importers:
       '@types/bcryptjs':
         specifier: 2.4.2
         version: 2.4.2
-      '@types/bull':
-        specifier: 4.10.0
-        version: 4.10.0
       '@types/cbor':
         specifier: 6.0.0
         version: 6.0.0
@@ -505,8 +512,8 @@ importers:
         specifier: 2.1.21
         version: 2.1.21
       '@types/jest':
-        specifier: 29.5.1
-        version: 29.5.1
+        specifier: 29.5.2
+        version: 29.5.2
       '@types/js-yaml':
         specifier: 4.0.5
         version: 4.0.5
@@ -523,20 +530,20 @@ importers:
         specifier: 2.1.1
         version: 2.1.1
       '@types/node':
-        specifier: 20.1.3
-        version: 20.1.3
+        specifier: 20.2.5
+        version: 20.2.5
       '@types/node-fetch':
         specifier: 3.0.3
         version: 3.0.3
       '@types/nodemailer':
-        specifier: 6.4.7
-        version: 6.4.7
+        specifier: 6.4.8
+        version: 6.4.8
       '@types/oauth':
         specifier: 0.9.1
         version: 0.9.1
       '@types/pg':
-        specifier: 8.6.6
-        version: 8.6.6
+        specifier: 8.10.1
+        version: 8.10.1
       '@types/pug':
         specifier: 2.0.6
         version: 2.0.6
@@ -577,8 +584,8 @@ importers:
         specifier: 0.2.3
         version: 0.2.3
       '@types/unzipper':
-        specifier: 0.10.5
-        version: 0.10.5
+        specifier: 0.10.6
+        version: 0.10.6
       '@types/uuid':
         specifier: 9.0.1
         version: 9.0.1
@@ -595,11 +602,11 @@ importers:
         specifier: 8.5.4
         version: 8.5.4
       '@typescript-eslint/eslint-plugin':
-        specifier: 5.59.5
-        version: 5.59.5(@typescript-eslint/parser@5.59.5)(eslint@8.40.0)(typescript@5.0.4)
+        specifier: 5.59.8
+        version: 5.59.8(@typescript-eslint/parser@5.59.8)(eslint@8.41.0)(typescript@5.1.3)
       '@typescript-eslint/parser':
-        specifier: 5.59.5
-        version: 5.59.5(eslint@8.40.0)(typescript@5.0.4)
+        specifier: 5.59.8
+        version: 5.59.8(eslint@8.41.0)(typescript@5.1.3)
       aws-sdk-client-mock:
         specifier: 2.1.1
         version: 2.1.1
@@ -607,17 +614,17 @@ importers:
         specifier: 7.0.3
         version: 7.0.3
       eslint:
-        specifier: 8.40.0
-        version: 8.40.0
+        specifier: 8.41.0
+        version: 8.41.0
       eslint-plugin-import:
         specifier: 2.27.5
-        version: 2.27.5(@typescript-eslint/parser@5.59.5)(eslint@8.40.0)
+        version: 2.27.5(@typescript-eslint/parser@5.59.8)(eslint@8.41.0)
       execa:
         specifier: 6.1.0
         version: 6.1.0
       jest:
         specifier: 29.5.0
-        version: 29.5.0(@types/node@20.1.3)
+        version: 29.5.0(@types/node@20.2.5)
       jest-mock:
         specifier: 29.5.0
         version: 29.5.0
@@ -629,43 +636,46 @@ importers:
         version: 14.1.2
       '@rollup/plugin-alias':
         specifier: 5.0.0
-        version: 5.0.0(rollup@3.21.6)
+        version: 5.0.0(rollup@3.23.0)
       '@rollup/plugin-json':
         specifier: 6.0.0
-        version: 6.0.0(rollup@3.21.6)
+        version: 6.0.0(rollup@3.23.0)
       '@rollup/plugin-replace':
         specifier: 5.0.2
-        version: 5.0.2(rollup@3.21.6)
+        version: 5.0.2(rollup@3.23.0)
       '@rollup/pluginutils':
         specifier: 5.0.2
-        version: 5.0.2(rollup@3.21.6)
+        version: 5.0.2(rollup@3.23.0)
       '@syuilo/aiscript':
-        specifier: 0.13.2
-        version: 0.13.2
+        specifier: 0.13.3
+        version: 0.13.3
       '@tabler/icons-webfont':
-        specifier: 2.17.0
-        version: 2.17.0
+        specifier: 2.21.0
+        version: 2.21.0
       '@vitejs/plugin-vue':
-        specifier: 4.2.2
-        version: 4.2.2(vite@4.3.5)(vue@3.3.1)
+        specifier: 4.2.3
+        version: 4.2.3(vite@4.3.9)(vue@3.3.4)
       '@vue-macros/reactivity-transform':
-        specifier: 0.3.6
-        version: 0.3.6(rollup@3.21.6)(vue@3.3.1)
+        specifier: 0.3.9
+        version: 0.3.9(rollup@3.23.0)(vue@3.3.4)
       '@vue/compiler-sfc':
-        specifier: 3.3.1
-        version: 3.3.1
+        specifier: 3.3.4
+        version: 3.3.4
+      astring:
+        specifier: 1.8.6
+        version: 1.8.6
       autosize:
-        specifier: 5.0.2
-        version: 5.0.2
-      blurhash:
-        specifier: 2.0.5
-        version: 2.0.5
+        specifier: 6.0.1
+        version: 6.0.1
       broadcast-channel:
-        specifier: 4.20.2
-        version: 4.20.2
+        specifier: 5.1.0
+        version: 5.1.0
       browser-image-resizer:
         specifier: github:misskey-dev/browser-image-resizer#v2.2.1-misskey.3
         version: github.com/misskey-dev/browser-image-resizer/0227e860621e55cbed0aabe6dc601096a7748c4a
+      buraha:
+        specifier: github:misskey-dev/buraha
+        version: github.com/misskey-dev/buraha/92b20c1ab15c5cb5a224cf3b1ecd4f6baca12b7c
       canvas-confetti:
         specifier: 1.6.0
         version: 1.6.0
@@ -685,8 +695,8 @@ importers:
         specifier: 2.0.1
         version: 2.0.1(chart.js@4.3.0)
       chromatic:
-        specifier: 6.17.4
-        version: 6.17.4
+        specifier: 6.18.0
+        version: 6.18.0
       compare-versions:
         specifier: 5.0.3
         version: 5.0.3
@@ -699,6 +709,9 @@ importers:
       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
@@ -742,8 +755,8 @@ importers:
         specifier: 1.0.0
         version: 1.0.0
       rollup:
-        specifier: 3.21.6
-        version: 3.21.6
+        specifier: 3.23.0
+        version: 3.23.0
       s-age:
         specifier: 1.1.2
         version: 1.1.2
@@ -766,8 +779,8 @@ importers:
         specifier: 3.1.0
         version: 3.1.0
       three:
-        specifier: 0.151.3
-        version: 0.151.3
+        specifier: 0.153.0
+        version: 0.153.0
       throttle-debounce:
         specifier: 5.0.0
         version: 5.0.0
@@ -784,8 +797,8 @@ importers:
         specifier: 14.0.0
         version: 14.0.0
       typescript:
-        specifier: 5.0.4
-        version: 5.0.4
+        specifier: 5.1.3
+        version: 5.1.3
       uuid:
         specifier: 9.0.0
         version: 9.0.0
@@ -793,81 +806,78 @@ importers:
         specifier: 1.8.0
         version: 1.8.0
       vite:
-        specifier: 4.3.5
-        version: 4.3.5(@types/node@20.1.3)(sass@1.62.1)
+        specifier: 4.3.9
+        version: 4.3.9(@types/node@20.2.5)(sass@1.62.1)
       vue:
-        specifier: 3.3.1
-        version: 3.3.1
-      vue-plyr:
-        specifier: 7.0.0
-        version: 7.0.0
+        specifier: 3.3.4
+        version: 3.3.4
       vue-prism-editor:
         specifier: 2.0.0-alpha.2
-        version: 2.0.0-alpha.2(vue@3.3.1)
+        version: 2.0.0-alpha.2(vue@3.3.4)
       vuedraggable:
         specifier: next
-        version: 4.1.0(vue@3.3.1)
+        version: 4.1.0(vue@3.3.4)
     devDependencies:
       '@storybook/addon-actions':
-        specifier: 7.0.10
-        version: 7.0.10(react-dom@18.2.0)(react@18.2.0)
+        specifier: 7.0.18
+        version: 7.0.18(react-dom@18.2.0)(react@18.2.0)
       '@storybook/addon-essentials':
-        specifier: 7.0.10
-        version: 7.0.10(react-dom@18.2.0)(react@18.2.0)
+        specifier: 7.0.18
+        version: 7.0.18(react-dom@18.2.0)(react@18.2.0)
       '@storybook/addon-interactions':
-        specifier: 7.0.10
-        version: 7.0.10(react-dom@18.2.0)(react@18.2.0)
+        specifier: 7.0.18
+        version: 7.0.18(react-dom@18.2.0)(react@18.2.0)
       '@storybook/addon-links':
-        specifier: 7.0.10
-        version: 7.0.10(react-dom@18.2.0)(react@18.2.0)
+        specifier: 7.0.18
+        version: 7.0.18(react-dom@18.2.0)(react@18.2.0)
       '@storybook/addon-storysource':
-        specifier: 7.0.10
-        version: 7.0.10(react-dom@18.2.0)(react@18.2.0)
+        specifier: 7.0.18
+        version: 7.0.18(react-dom@18.2.0)(react@18.2.0)
       '@storybook/addons':
-        specifier: 7.0.10
-        version: 7.0.10(react-dom@18.2.0)(react@18.2.0)
+        specifier: 7.0.18
+        version: 7.0.18(react-dom@18.2.0)(react@18.2.0)
       '@storybook/blocks':
-        specifier: 7.0.10
-        version: 7.0.10(react-dom@18.2.0)(react@18.2.0)
+        specifier: 7.0.18
+        version: 7.0.18(react-dom@18.2.0)(react@18.2.0)
       '@storybook/core-events':
-        specifier: 7.0.10
-        version: 7.0.10
+        specifier: 7.0.18
+        version: 7.0.18
       '@storybook/jest':
         specifier: 0.1.0
         version: 0.1.0
       '@storybook/manager-api':
-        specifier: 7.0.10
-        version: 7.0.10(react-dom@18.2.0)(react@18.2.0)
+        specifier: 7.0.18
+        version: 7.0.18(react-dom@18.2.0)(react@18.2.0)
       '@storybook/preview-api':
-        specifier: 7.0.10
-        version: 7.0.10
+        specifier: 7.0.18
+        version: 7.0.18
       '@storybook/react':
-        specifier: 7.0.10
-        version: 7.0.10(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4)
+        specifier: 7.0.18
+        version: 7.0.18(react-dom@18.2.0)(react@18.2.0)(typescript@5.1.3)
       '@storybook/react-vite':
-        specifier: 7.0.10
-        version: 7.0.10(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4)(vite@4.3.5)
+        specifier: 7.0.18
+        version: 7.0.18(react-dom@18.2.0)(react@18.2.0)(typescript@5.1.3)(vite@4.3.9)
       '@storybook/testing-library':
         specifier: 0.1.0
         version: 0.1.0
       '@storybook/theming':
-        specifier: 7.0.10
-        version: 7.0.10(react-dom@18.2.0)(react@18.2.0)
+        specifier: 7.0.18
+        version: 7.0.18(react-dom@18.2.0)(react@18.2.0)
       '@storybook/types':
-        specifier: 7.0.10
-        version: 7.0.10
+        specifier: 7.0.18
+        version: 7.0.18
       '@storybook/vue3':
-        specifier: 7.0.10
-        version: 7.0.10(vue@3.3.1)
+        specifier: 7.0.18
+        version: 7.0.18(vue@3.3.4)
       '@storybook/vue3-vite':
-        specifier: 7.0.10
-        version: 7.0.10(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4)(vite@4.3.5)(vue@3.3.1)
+        specifier: 7.0.18
+        version: 7.0.18(react-dom@18.2.0)(react@18.2.0)(typescript@5.1.3)(vite@4.3.9)(vue@3.3.4)
       '@testing-library/jest-dom':
         specifier: 5.16.5
         version: 5.16.5
       '@testing-library/vue':
         specifier: 7.0.0
-        version: 7.0.0(@vue/compiler-sfc@3.3.1)(vue@3.3.1)
+        version: 7.0.0(@vue/compiler-sfc@3.3.4)(vue@3.3.4)
       '@types/escape-regexp':
         specifier: 0.0.1
         version: 0.0.1
@@ -881,14 +891,14 @@ importers:
         specifier: 2.0.2
         version: 2.0.2
       '@types/matter-js':
-        specifier: 0.18.3
-        version: 0.18.3
+        specifier: 0.18.5
+        version: 0.18.5
       '@types/micromatch':
         specifier: 4.0.2
         version: 4.0.2
       '@types/node':
-        specifier: 20.1.3
-        version: 20.1.3
+        specifier: 20.2.5
+        version: 20.2.5
       '@types/punycode':
         specifier: 2.1.0
         version: 2.1.0
@@ -899,8 +909,8 @@ importers:
         specifier: 3.0.5
         version: 3.0.5
       '@types/testing-library__jest-dom':
-        specifier: ^5.14.5
-        version: 5.14.5
+        specifier: ^5.14.6
+        version: 5.14.6
       '@types/throttle-debounce':
         specifier: 5.0.0
         version: 5.0.0
@@ -917,20 +927,20 @@ importers:
         specifier: 8.5.4
         version: 8.5.4
       '@typescript-eslint/eslint-plugin':
-        specifier: 5.59.5
-        version: 5.59.5(@typescript-eslint/parser@5.59.5)(eslint@8.40.0)(typescript@5.0.4)
+        specifier: 5.59.8
+        version: 5.59.8(@typescript-eslint/parser@5.59.8)(eslint@8.41.0)(typescript@5.1.3)
       '@typescript-eslint/parser':
-        specifier: 5.59.5
-        version: 5.59.5(eslint@8.40.0)(typescript@5.0.4)
+        specifier: 5.59.8
+        version: 5.59.8(eslint@8.41.0)(typescript@5.1.3)
       '@vitest/coverage-c8':
-        specifier: 0.31.0
-        version: 0.31.0(vitest@0.31.0)
+        specifier: 0.31.4
+        version: 0.31.4(vitest@0.31.4)
       '@vue/runtime-core':
-        specifier: 3.3.1
-        version: 3.3.1
-      astring:
-        specifier: 1.8.4
-        version: 1.8.4
+        specifier: 3.3.4
+        version: 3.3.4
+      acorn:
+        specifier: ^8.8.2
+        version: 8.8.2
       chokidar-cli:
         specifier: 3.0.0
         version: 3.0.0
@@ -938,29 +948,29 @@ importers:
         specifier: 7.0.3
         version: 7.0.3
       cypress:
-        specifier: 12.12.0
-        version: 12.12.0
+        specifier: 12.13.0
+        version: 12.13.0
       eslint:
-        specifier: 8.40.0
-        version: 8.40.0
+        specifier: 8.41.0
+        version: 8.41.0
       eslint-plugin-import:
         specifier: 2.27.5
-        version: 2.27.5(@typescript-eslint/parser@5.59.5)(eslint@8.40.0)
+        version: 2.27.5(@typescript-eslint/parser@5.59.8)(eslint@8.41.0)
       eslint-plugin-vue:
-        specifier: 9.12.0
-        version: 9.12.0(eslint@8.40.0)
+        specifier: 9.14.1
+        version: 9.14.1(eslint@8.41.0)
       fast-glob:
         specifier: 3.2.12
         version: 3.2.12
       happy-dom:
-        specifier: 9.16.0
-        version: 9.16.0
+        specifier: 9.20.3
+        version: 9.20.3
       micromatch:
         specifier: 3.1.10
         version: 3.1.10
       msw:
         specifier: 1.2.1
-        version: 1.2.1(typescript@5.0.4)
+        version: 1.2.1(typescript@5.1.3)
       msw-storybook-addon:
         specifier: 1.8.0
         version: 1.8.0(msw@1.2.1)
@@ -977,11 +987,11 @@ importers:
         specifier: 2.0.0
         version: 2.0.0
       storybook:
-        specifier: 7.0.10
-        version: 7.0.10
+        specifier: 7.0.18
+        version: 7.0.18
       storybook-addon-misskey-theme:
         specifier: github:misskey-dev/storybook-addon-misskey-theme
-        version: github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.0.10)(@storybook/components@7.0.10)(@storybook/core-events@7.0.10)(@storybook/manager-api@7.0.10)(@storybook/preview-api@7.0.10)(@storybook/theming@7.0.10)(@storybook/types@7.0.10)(react-dom@18.2.0)(react@18.2.0)
+        version: github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.0.18)(@storybook/components@7.0.18)(@storybook/core-events@7.0.18)(@storybook/manager-api@7.0.18)(@storybook/preview-api@7.0.18)(@storybook/theming@7.0.18)(@storybook/types@7.0.18)(react-dom@18.2.0)(react@18.2.0)
       summaly:
         specifier: github:misskey-dev/summaly
         version: github.com/misskey-dev/summaly/77dd5654bb82280b38c1f50e51a771c33f3df503
@@ -989,23 +999,23 @@ importers:
         specifier: 1.0.2
         version: 1.0.2
       vitest:
-        specifier: 0.31.0
-        version: 0.31.0(happy-dom@9.16.0)(sass@1.62.1)
+        specifier: 0.31.4
+        version: 0.31.4(happy-dom@9.20.3)(sass@1.62.1)
       vitest-fetch-mock:
         specifier: 0.2.2
-        version: 0.2.2(vitest@0.31.0)
+        version: 0.2.2(vitest@0.31.4)
       vue-eslint-parser:
-        specifier: 9.2.1
-        version: 9.2.1(eslint@8.40.0)
+        specifier: 9.3.0
+        version: 9.3.0(eslint@8.41.0)
       vue-tsc:
-        specifier: 1.6.4
-        version: 1.6.4(typescript@5.0.4)
+        specifier: 1.6.5
+        version: 1.6.5(typescript@5.1.3)
 
   packages/misskey-js:
     dependencies:
       '@swc/cli':
         specifier: 0.1.62
-        version: 0.1.62(@swc/core@1.3.56)(chokidar@3.5.3)
+        version: 0.1.62(@swc/core@1.3.61)(chokidar@3.5.3)
       '@swc/core':
         specifier: 1.3.56
         version: 1.3.56
@@ -1090,11 +1100,11 @@ packages:
     resolution: {integrity: sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA==}
     dev: true
 
-  /@ampproject/remapping@2.2.0:
-    resolution: {integrity: sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==}
+  /@ampproject/remapping@2.2.1:
+    resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==}
     engines: {node: '>=6.0.0'}
     dependencies:
-      '@jridgewell/gen-mapping': 0.1.1
+      '@jridgewell/gen-mapping': 0.3.2
       '@jridgewell/trace-mapping': 0.3.17
     dev: true
 
@@ -1185,13 +1195,13 @@ packages:
     engines: {node: '>=14.0.0'}
     dependencies:
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/chunked-blob-reader@3.310.0:
     resolution: {integrity: sha512-CrJS3exo4mWaLnWxfCH+w88Ou0IcAZSIkk4QbmxiHl/5Dq705OLoxf4385MVyExpqpeVJYOYQ2WaD8i/pQZ2fg==}
     dependencies:
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/client-s3@3.321.1:
@@ -1292,7 +1302,7 @@ packages:
       '@aws-sdk/util-user-agent-browser': 3.310.0
       '@aws-sdk/util-user-agent-node': 3.310.0
       '@aws-sdk/util-utf8': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     transitivePeerDependencies:
       - aws-crt
     dev: false
@@ -1332,7 +1342,7 @@ packages:
       '@aws-sdk/util-user-agent-browser': 3.310.0
       '@aws-sdk/util-user-agent-node': 3.310.0
       '@aws-sdk/util-utf8': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     transitivePeerDependencies:
       - aws-crt
     dev: false
@@ -1376,7 +1386,7 @@ packages:
       '@aws-sdk/util-user-agent-node': 3.310.0
       '@aws-sdk/util-utf8': 3.310.0
       fast-xml-parser: 4.1.2
-      tslib: 2.5.0
+      tslib: 2.5.2
     transitivePeerDependencies:
       - aws-crt
     dev: false
@@ -1388,7 +1398,7 @@ packages:
       '@aws-sdk/types': 3.310.0
       '@aws-sdk/util-config-provider': 3.310.0
       '@aws-sdk/util-middleware': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/credential-provider-env@3.310.0:
@@ -1397,7 +1407,7 @@ packages:
     dependencies:
       '@aws-sdk/property-provider': 3.310.0
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/credential-provider-imds@3.310.0:
@@ -1408,7 +1418,7 @@ packages:
       '@aws-sdk/property-provider': 3.310.0
       '@aws-sdk/types': 3.310.0
       '@aws-sdk/url-parser': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/credential-provider-ini@3.321.1:
@@ -1423,7 +1433,7 @@ packages:
       '@aws-sdk/property-provider': 3.310.0
       '@aws-sdk/shared-ini-file-loader': 3.310.0
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     transitivePeerDependencies:
       - aws-crt
     dev: false
@@ -1441,7 +1451,7 @@ packages:
       '@aws-sdk/property-provider': 3.310.0
       '@aws-sdk/shared-ini-file-loader': 3.310.0
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     transitivePeerDependencies:
       - aws-crt
     dev: false
@@ -1453,7 +1463,7 @@ packages:
       '@aws-sdk/property-provider': 3.310.0
       '@aws-sdk/shared-ini-file-loader': 3.310.0
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/credential-provider-sso@3.321.1:
@@ -1465,7 +1475,7 @@ packages:
       '@aws-sdk/shared-ini-file-loader': 3.310.0
       '@aws-sdk/token-providers': 3.321.1
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     transitivePeerDependencies:
       - aws-crt
     dev: false
@@ -1476,7 +1486,7 @@ packages:
     dependencies:
       '@aws-sdk/property-provider': 3.310.0
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/eventstream-codec@3.310.0:
@@ -1485,7 +1495,7 @@ packages:
       '@aws-crypto/crc32': 3.0.0
       '@aws-sdk/types': 3.310.0
       '@aws-sdk/util-hex-encoding': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/eventstream-serde-browser@3.310.0:
@@ -1494,7 +1504,7 @@ packages:
     dependencies:
       '@aws-sdk/eventstream-serde-universal': 3.310.0
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/eventstream-serde-config-resolver@3.310.0:
@@ -1502,7 +1512,7 @@ packages:
     engines: {node: '>=14.0.0'}
     dependencies:
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/eventstream-serde-node@3.310.0:
@@ -1511,7 +1521,7 @@ packages:
     dependencies:
       '@aws-sdk/eventstream-serde-universal': 3.310.0
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/eventstream-serde-universal@3.310.0:
@@ -1520,7 +1530,7 @@ packages:
     dependencies:
       '@aws-sdk/eventstream-codec': 3.310.0
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/fetch-http-handler@3.310.0:
@@ -1530,7 +1540,7 @@ packages:
       '@aws-sdk/querystring-builder': 3.310.0
       '@aws-sdk/types': 3.310.0
       '@aws-sdk/util-base64': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/hash-blob-browser@3.310.0:
@@ -1538,7 +1548,7 @@ packages:
     dependencies:
       '@aws-sdk/chunked-blob-reader': 3.310.0
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/hash-node@3.310.0:
@@ -1548,7 +1558,7 @@ packages:
       '@aws-sdk/types': 3.310.0
       '@aws-sdk/util-buffer-from': 3.310.0
       '@aws-sdk/util-utf8': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/hash-stream-node@3.310.0:
@@ -1557,21 +1567,21 @@ packages:
     dependencies:
       '@aws-sdk/types': 3.310.0
       '@aws-sdk/util-utf8': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/invalid-dependency@3.310.0:
     resolution: {integrity: sha512-1s5RG5rSPXoa/aZ/Kqr5U/7lqpx+Ry81GprQ2bxWqJvWQIJ0IRUwo5pk8XFxbKVr/2a+4lZT/c3OGoBOM1yRRA==}
     dependencies:
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/is-array-buffer@3.310.0:
     resolution: {integrity: sha512-urnbcCR+h9NWUnmOtet/s4ghvzsidFmspfhYaHAmSRdy9yDjdjBJMFjjsn85A1ODUktztm+cVncXjQ38WCMjMQ==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/lib-storage@3.321.1(@aws-sdk/abort-controller@3.310.0)(@aws-sdk/client-s3@3.321.1):
@@ -1596,7 +1606,7 @@ packages:
     dependencies:
       '@aws-sdk/types': 3.310.0
       '@aws-sdk/util-utf8': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/middleware-bucket-endpoint@3.310.0:
@@ -1607,7 +1617,7 @@ packages:
       '@aws-sdk/types': 3.310.0
       '@aws-sdk/util-arn-parser': 3.310.0
       '@aws-sdk/util-config-provider': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/middleware-content-length@3.310.0:
@@ -1616,7 +1626,7 @@ packages:
     dependencies:
       '@aws-sdk/protocol-http': 3.310.0
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/middleware-endpoint@3.310.0:
@@ -1627,7 +1637,7 @@ packages:
       '@aws-sdk/types': 3.310.0
       '@aws-sdk/url-parser': 3.310.0
       '@aws-sdk/util-middleware': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/middleware-expect-continue@3.310.0:
@@ -1636,7 +1646,7 @@ packages:
     dependencies:
       '@aws-sdk/protocol-http': 3.310.0
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/middleware-flexible-checksums@3.310.0:
@@ -1649,7 +1659,7 @@ packages:
       '@aws-sdk/protocol-http': 3.310.0
       '@aws-sdk/types': 3.310.0
       '@aws-sdk/util-utf8': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/middleware-host-header@3.310.0:
@@ -1658,7 +1668,7 @@ packages:
     dependencies:
       '@aws-sdk/protocol-http': 3.310.0
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/middleware-location-constraint@3.310.0:
@@ -1666,7 +1676,7 @@ packages:
     engines: {node: '>=14.0.0'}
     dependencies:
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/middleware-logger@3.310.0:
@@ -1674,7 +1684,7 @@ packages:
     engines: {node: '>=14.0.0'}
     dependencies:
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/middleware-recursion-detection@3.310.0:
@@ -1683,7 +1693,7 @@ packages:
     dependencies:
       '@aws-sdk/protocol-http': 3.310.0
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/middleware-retry@3.310.0:
@@ -1695,7 +1705,7 @@ packages:
       '@aws-sdk/types': 3.310.0
       '@aws-sdk/util-middleware': 3.310.0
       '@aws-sdk/util-retry': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
       uuid: 8.3.2
     dev: false
 
@@ -1706,7 +1716,7 @@ packages:
       '@aws-sdk/protocol-http': 3.310.0
       '@aws-sdk/types': 3.310.0
       '@aws-sdk/util-arn-parser': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/middleware-sdk-sts@3.310.0:
@@ -1715,7 +1725,7 @@ packages:
     dependencies:
       '@aws-sdk/middleware-signing': 3.310.0
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/middleware-serde@3.310.0:
@@ -1723,7 +1733,7 @@ packages:
     engines: {node: '>=14.0.0'}
     dependencies:
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/middleware-signing@3.310.0:
@@ -1735,7 +1745,7 @@ packages:
       '@aws-sdk/signature-v4': 3.310.0
       '@aws-sdk/types': 3.310.0
       '@aws-sdk/util-middleware': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/middleware-ssec@3.310.0:
@@ -1743,14 +1753,14 @@ packages:
     engines: {node: '>=14.0.0'}
     dependencies:
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/middleware-stack@3.310.0:
     resolution: {integrity: sha512-010O1PD+UAcZVKRvqEusE1KJqN96wwrf6QsqbRM0ywsKQ21NDweaHvEDlds2VHpgmofxkRLRu/IDrlPkKRQrRg==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/middleware-user-agent@3.319.0:
@@ -1760,7 +1770,7 @@ packages:
       '@aws-sdk/protocol-http': 3.310.0
       '@aws-sdk/types': 3.310.0
       '@aws-sdk/util-endpoints': 3.319.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/node-config-provider@3.310.0:
@@ -1770,7 +1780,7 @@ packages:
       '@aws-sdk/property-provider': 3.310.0
       '@aws-sdk/shared-ini-file-loader': 3.310.0
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/node-http-handler@3.321.1:
@@ -1789,7 +1799,7 @@ packages:
     engines: {node: '>=14.0.0'}
     dependencies:
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/protocol-http@3.310.0:
@@ -1797,7 +1807,7 @@ packages:
     engines: {node: '>=14.0.0'}
     dependencies:
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/querystring-builder@3.310.0:
@@ -1806,7 +1816,7 @@ packages:
     dependencies:
       '@aws-sdk/types': 3.310.0
       '@aws-sdk/util-uri-escape': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/querystring-parser@3.310.0:
@@ -1814,7 +1824,7 @@ packages:
     engines: {node: '>=14.0.0'}
     dependencies:
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/service-error-classification@3.310.0:
@@ -1827,7 +1837,7 @@ packages:
     engines: {node: '>=14.0.0'}
     dependencies:
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/signature-v4-multi-region@3.310.0:
@@ -1842,7 +1852,7 @@ packages:
       '@aws-sdk/protocol-http': 3.310.0
       '@aws-sdk/signature-v4': 3.310.0
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/signature-v4@3.310.0:
@@ -1855,7 +1865,7 @@ packages:
       '@aws-sdk/util-middleware': 3.310.0
       '@aws-sdk/util-uri-escape': 3.310.0
       '@aws-sdk/util-utf8': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/smithy-client@3.316.0:
@@ -1864,7 +1874,7 @@ packages:
     dependencies:
       '@aws-sdk/middleware-stack': 3.310.0
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/token-providers@3.321.1:
@@ -1875,7 +1885,7 @@ packages:
       '@aws-sdk/property-provider': 3.310.0
       '@aws-sdk/shared-ini-file-loader': 3.310.0
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     transitivePeerDependencies:
       - aws-crt
     dev: false
@@ -1884,7 +1894,7 @@ packages:
     resolution: {integrity: sha512-j8eamQJ7YcIhw7fneUfs8LYl3t01k4uHi4ZDmNRgtbmbmTTG3FZc2MotStZnp3nZB6vLiPF1o5aoJxWVvkzS6A==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/url-parser@3.310.0:
@@ -1892,14 +1902,14 @@ packages:
     dependencies:
       '@aws-sdk/querystring-parser': 3.310.0
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/util-arn-parser@3.310.0:
     resolution: {integrity: sha512-jL8509owp/xB9+Or0pvn3Fe+b94qfklc2yPowZZIFAkFcCSIdkIglz18cPDWnYAcy9JGewpMS1COXKIUhZkJsA==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/util-base64@3.310.0:
@@ -1907,20 +1917,20 @@ packages:
     engines: {node: '>=14.0.0'}
     dependencies:
       '@aws-sdk/util-buffer-from': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/util-body-length-browser@3.310.0:
     resolution: {integrity: sha512-sxsC3lPBGfpHtNTUoGXMQXLwjmR0zVpx0rSvzTPAuoVILVsp5AU/w5FphNPxD5OVIjNbZv9KsKTuvNTiZjDp9g==}
     dependencies:
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/util-body-length-node@3.310.0:
     resolution: {integrity: sha512-2tqGXdyKhyA6w4zz7UPoS8Ip+7sayOg9BwHNidiGm2ikbDxm1YrCfYXvCBdwaJxa4hJfRVz+aL9e+d3GqPI9pQ==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/util-buffer-from@3.310.0:
@@ -1928,14 +1938,14 @@ packages:
     engines: {node: '>=14.0.0'}
     dependencies:
       '@aws-sdk/is-array-buffer': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/util-config-provider@3.310.0:
     resolution: {integrity: sha512-xIBaYo8dwiojCw8vnUcIL4Z5tyfb1v3yjqyJKJWV/dqKUFOOS0U591plmXbM+M/QkXyML3ypon1f8+BoaDExrg==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/util-defaults-mode-browser@3.316.0:
@@ -1945,7 +1955,7 @@ packages:
       '@aws-sdk/property-provider': 3.310.0
       '@aws-sdk/types': 3.310.0
       bowser: 2.11.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/util-defaults-mode-node@3.316.0:
@@ -1957,7 +1967,7 @@ packages:
       '@aws-sdk/node-config-provider': 3.310.0
       '@aws-sdk/property-provider': 3.310.0
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/util-endpoints@3.319.0:
@@ -1965,28 +1975,28 @@ packages:
     engines: {node: '>=14.0.0'}
     dependencies:
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/util-hex-encoding@3.310.0:
     resolution: {integrity: sha512-sVN7mcCCDSJ67pI1ZMtk84SKGqyix6/0A1Ab163YKn+lFBQRMKexleZzpYzNGxYzmQS6VanP/cfU7NiLQOaSfA==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/util-locate-window@3.208.0:
     resolution: {integrity: sha512-iua1A2+P7JJEDHVgvXrRJSvsnzG7stYSGQnBVphIUlemwl6nN5D+QrgbjECtrbxRz8asYFHSzhdhECqN+tFiBg==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/util-middleware@3.310.0:
     resolution: {integrity: sha512-FTSUKL/eRb9X6uEZClrTe27QFXUNNp7fxYrPndZwk1hlaOP5ix+MIHBcI7pIiiY/JPfOUmPyZOu+HetlFXjWog==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/util-retry@3.310.0:
@@ -1994,7 +2004,7 @@ packages:
     engines: {node: '>= 14.0.0'}
     dependencies:
       '@aws-sdk/service-error-classification': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/util-stream-browser@3.310.0:
@@ -2005,7 +2015,7 @@ packages:
       '@aws-sdk/util-base64': 3.310.0
       '@aws-sdk/util-hex-encoding': 3.310.0
       '@aws-sdk/util-utf8': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/util-stream-node@3.321.1:
@@ -2015,14 +2025,14 @@ packages:
       '@aws-sdk/node-http-handler': 3.321.1
       '@aws-sdk/types': 3.310.0
       '@aws-sdk/util-buffer-from': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/util-uri-escape@3.310.0:
     resolution: {integrity: sha512-drzt+aB2qo2LgtDoiy/3sVG8w63cgLkqFIa2NFlGpUgHFWTXkqtbgf4L5QdjRGKWhmZsnqkbtL7vkSWEcYDJ4Q==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/util-user-agent-browser@3.310.0:
@@ -2030,7 +2040,7 @@ packages:
     dependencies:
       '@aws-sdk/types': 3.310.0
       bowser: 2.11.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/util-user-agent-node@3.310.0:
@@ -2044,13 +2054,13 @@ packages:
     dependencies:
       '@aws-sdk/node-config-provider': 3.310.0
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/util-utf8-browser@3.259.0:
     resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==}
     dependencies:
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/util-utf8@3.310.0:
@@ -2058,7 +2068,7 @@ packages:
     engines: {node: '>=14.0.0'}
     dependencies:
       '@aws-sdk/util-buffer-from': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/util-waiter@3.310.0:
@@ -2067,25 +2077,24 @@ packages:
     dependencies:
       '@aws-sdk/abort-controller': 3.310.0
       '@aws-sdk/types': 3.310.0
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /@aws-sdk/xml-builder@3.310.0:
     resolution: {integrity: sha512-TqELu4mOuSIKQCqj63fGVs86Yh+vBx5nHRpWKNUNhB2nPTpfbziTs5c1X358be3peVWA4wPxW7Nt53KIg1tnNw==}
     engines: {node: '>=14.0.0'}
     dependencies:
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
-  /@babel/code-frame@7.18.6:
-    resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==}
+  /@babel/code-frame@7.21.4:
+    resolution: {integrity: sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==}
     engines: {node: '>=6.9.0'}
     dependencies:
       '@babel/highlight': 7.18.6
-    dev: true
 
-  /@babel/compat-data@7.21.4:
-    resolution: {integrity: sha512-/DYyDpeCfaVinT40FPGdkkb+lYSKvsVuMjDAG7jPOWWiM1ibOaB9CXJAlc4d1QpP/U2q2P9jbrSlClKSErd55g==}
+  /@babel/compat-data@7.22.3:
+    resolution: {integrity: sha512-aNtko9OPOwVESUFp3MZfD8Uzxl7JzSeJpd7npIoxCasU37PFbAQRpKglkaKwlHOyeJdrREpo8TW8ldrkYWwvIQ==}
     engines: {node: '>=6.9.0'}
     dev: true
 
@@ -2093,16 +2102,39 @@ packages:
     resolution: {integrity: sha512-qIJONzoa/qiHghnm0l1n4i/6IIziDpzqc36FBs4pzMhDUraHqponwJLiAKm1hGLP3OSB/TVNz6rMwVGpwxxySw==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@ampproject/remapping': 2.2.0
-      '@babel/code-frame': 7.18.6
-      '@babel/generator': 7.21.3
-      '@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.3)
-      '@babel/helper-module-transforms': 7.21.2
-      '@babel/helpers': 7.21.0
-      '@babel/parser': 7.21.8
-      '@babel/template': 7.20.7
-      '@babel/traverse': 7.21.3
-      '@babel/types': 7.21.5
+      '@ampproject/remapping': 2.2.1
+      '@babel/code-frame': 7.21.4
+      '@babel/generator': 7.22.3
+      '@babel/helper-compilation-targets': 7.22.1(@babel/core@7.21.3)
+      '@babel/helper-module-transforms': 7.22.1
+      '@babel/helpers': 7.22.3
+      '@babel/parser': 7.22.4
+      '@babel/template': 7.21.9
+      '@babel/traverse': 7.22.4
+      '@babel/types': 7.22.4
+      convert-source-map: 1.9.0
+      debug: 4.3.4(supports-color@8.1.1)
+      gensync: 1.0.0-beta.2
+      json5: 2.2.3
+      semver: 6.3.0
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@babel/core@7.22.1:
+    resolution: {integrity: sha512-Hkqu7J4ynysSXxmAahpN1jjRwVJ+NdpraFLIWflgjpVob3KNyK3/tIUc7Q7szed8WMp0JNa7Qtd1E9Oo22F9gA==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@ampproject/remapping': 2.2.1
+      '@babel/code-frame': 7.21.4
+      '@babel/generator': 7.22.3
+      '@babel/helper-compilation-targets': 7.22.1(@babel/core@7.22.1)
+      '@babel/helper-module-transforms': 7.22.1
+      '@babel/helpers': 7.22.3
+      '@babel/parser': 7.22.4
+      '@babel/template': 7.21.9
+      '@babel/traverse': 7.22.4
+      '@babel/types': 7.22.4
       convert-source-map: 1.9.0
       debug: 4.3.4(supports-color@8.1.1)
       gensync: 1.0.0-beta.2
@@ -2116,17 +2148,25 @@ packages:
     resolution: {integrity: sha512-QS3iR1GYC/YGUnW7IdggFeN5c1poPUurnGttOV/bZgPGV+izC/D8HnD6DLwod0fsatNyVn1G3EVWMYIF0nHbeA==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.21.5
+      '@babel/types': 7.22.4
+      '@jridgewell/gen-mapping': 0.3.2
+      '@jridgewell/trace-mapping': 0.3.17
+      jsesc: 2.5.2
+
+  /@babel/generator@7.22.3:
+    resolution: {integrity: sha512-C17MW4wlk//ES/CJDL51kPNwl+qiBQyN7b9SKyVp11BLGFeSPoVaHrv+MNt8jwQFhQWowW88z1eeBx3pFz9v8A==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/types': 7.22.4
       '@jridgewell/gen-mapping': 0.3.2
       '@jridgewell/trace-mapping': 0.3.17
       jsesc: 2.5.2
-    dev: true
 
   /@babel/helper-annotate-as-pure@7.18.6:
     resolution: {integrity: sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.21.5
+      '@babel/types': 7.22.4
     dev: true
 
   /@babel/helper-builder-binary-assignment-operator-visitor@7.18.9:
@@ -2134,16 +2174,16 @@ packages:
     engines: {node: '>=6.9.0'}
     dependencies:
       '@babel/helper-explode-assignable-expression': 7.18.6
-      '@babel/types': 7.21.5
+      '@babel/types': 7.22.4
     dev: true
 
-  /@babel/helper-compilation-targets@7.21.4(@babel/core@7.21.3):
-    resolution: {integrity: sha512-Fa0tTuOXZ1iL8IeDFUWCzjZcn+sJGd9RZdH9esYVjEejGmzf+FFYQpMi/kZUk2kPy/q1H3/GPw7np8qar/stfg==}
+  /@babel/helper-compilation-targets@7.22.1(@babel/core@7.21.3):
+    resolution: {integrity: sha512-Rqx13UM3yVB5q0D/KwQ8+SPfX/+Rnsy1Lw1k/UwOC4KC6qrzIQoY3lYnBu5EHKBlEHHcj0M0W8ltPSkD8rqfsQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0
     dependencies:
-      '@babel/compat-data': 7.21.4
+      '@babel/compat-data': 7.22.3
       '@babel/core': 7.21.3
       '@babel/helper-validator-option': 7.21.0
       browserslist: 4.21.5
@@ -2151,6 +2191,20 @@ packages:
       semver: 6.3.0
     dev: true
 
+  /@babel/helper-compilation-targets@7.22.1(@babel/core@7.22.1):
+    resolution: {integrity: sha512-Rqx13UM3yVB5q0D/KwQ8+SPfX/+Rnsy1Lw1k/UwOC4KC6qrzIQoY3lYnBu5EHKBlEHHcj0M0W8ltPSkD8rqfsQ==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0
+    dependencies:
+      '@babel/compat-data': 7.22.3
+      '@babel/core': 7.22.1
+      '@babel/helper-validator-option': 7.21.0
+      browserslist: 4.21.5
+      lru-cache: 5.1.1
+      semver: 6.3.0
+    dev: true
+
   /@babel/helper-create-class-features-plugin@7.21.0(@babel/core@7.21.3):
     resolution: {integrity: sha512-Q8wNiMIdwsv5la5SPxNYzzkPnjgC0Sy0i7jLkVOCdllu/xcVNkr3TeZzbHBJrj+XXRqzX5uCyCoV9eu6xUG7KQ==}
     engines: {node: '>=6.9.0'}
@@ -2159,7 +2213,26 @@ packages:
     dependencies:
       '@babel/core': 7.21.3
       '@babel/helper-annotate-as-pure': 7.18.6
-      '@babel/helper-environment-visitor': 7.18.9
+      '@babel/helper-environment-visitor': 7.22.1
+      '@babel/helper-function-name': 7.21.0
+      '@babel/helper-member-expression-to-functions': 7.21.0
+      '@babel/helper-optimise-call-expression': 7.18.6
+      '@babel/helper-replace-supers': 7.20.7
+      '@babel/helper-skip-transparent-expression-wrappers': 7.20.0
+      '@babel/helper-split-export-declaration': 7.18.6
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@babel/helper-create-class-features-plugin@7.21.0(@babel/core@7.22.1):
+    resolution: {integrity: sha512-Q8wNiMIdwsv5la5SPxNYzzkPnjgC0Sy0i7jLkVOCdllu/xcVNkr3TeZzbHBJrj+XXRqzX5uCyCoV9eu6xUG7KQ==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-annotate-as-pure': 7.18.6
+      '@babel/helper-environment-visitor': 7.22.1
       '@babel/helper-function-name': 7.21.0
       '@babel/helper-member-expression-to-functions': 7.21.0
       '@babel/helper-optimise-call-expression': 7.18.6
@@ -2181,13 +2254,24 @@ packages:
       regexpu-core: 5.3.2
     dev: true
 
+  /@babel/helper-create-regexp-features-plugin@7.21.0(@babel/core@7.22.1):
+    resolution: {integrity: sha512-N+LaFW/auRSWdx7SHD/HiARwXQju1vXTW4fKr4u5SgBUTm51OKEjKgj+cs00ggW3kEvNqwErnlwuq7Y3xBe4eg==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-annotate-as-pure': 7.18.6
+      regexpu-core: 5.3.2
+    dev: true
+
   /@babel/helper-define-polyfill-provider@0.3.3(@babel/core@7.21.3):
     resolution: {integrity: sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==}
     peerDependencies:
       '@babel/core': ^7.4.0-0
     dependencies:
       '@babel/core': 7.21.3
-      '@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.3)
+      '@babel/helper-compilation-targets': 7.22.1(@babel/core@7.21.3)
       '@babel/helper-plugin-utils': 7.20.2
       debug: 4.3.4(supports-color@8.1.1)
       lodash.debounce: 4.0.8
@@ -2197,59 +2281,72 @@ packages:
       - supports-color
     dev: true
 
-  /@babel/helper-environment-visitor@7.18.9:
-    resolution: {integrity: sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==}
-    engines: {node: '>=6.9.0'}
+  /@babel/helper-define-polyfill-provider@0.3.3(@babel/core@7.22.1):
+    resolution: {integrity: sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==}
+    peerDependencies:
+      '@babel/core': ^7.4.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-compilation-targets': 7.22.1(@babel/core@7.22.1)
+      '@babel/helper-plugin-utils': 7.20.2
+      debug: 4.3.4(supports-color@8.1.1)
+      lodash.debounce: 4.0.8
+      resolve: 1.22.1
+      semver: 6.3.0
+    transitivePeerDependencies:
+      - supports-color
     dev: true
 
+  /@babel/helper-environment-visitor@7.22.1:
+    resolution: {integrity: sha512-Z2tgopurB/kTbidvzeBrc2To3PUP/9i5MUe+fU6QJCQDyPwSH2oRapkLw3KGECDYSjhQZCNxEvNvZlLw8JjGwA==}
+    engines: {node: '>=6.9.0'}
+
   /@babel/helper-explode-assignable-expression@7.18.6:
     resolution: {integrity: sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.21.5
+      '@babel/types': 7.22.4
     dev: true
 
   /@babel/helper-function-name@7.21.0:
     resolution: {integrity: sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/template': 7.20.7
-      '@babel/types': 7.21.5
-    dev: true
+      '@babel/template': 7.21.9
+      '@babel/types': 7.22.4
 
   /@babel/helper-hoist-variables@7.18.6:
     resolution: {integrity: sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.21.5
-    dev: true
+      '@babel/types': 7.22.4
 
   /@babel/helper-member-expression-to-functions@7.21.0:
     resolution: {integrity: sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.21.5
+      '@babel/types': 7.22.4
     dev: true
 
-  /@babel/helper-module-imports@7.18.6:
-    resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==}
+  /@babel/helper-module-imports@7.21.4:
+    resolution: {integrity: sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.21.5
+      '@babel/types': 7.22.4
     dev: true
 
-  /@babel/helper-module-transforms@7.21.2:
-    resolution: {integrity: sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==}
+  /@babel/helper-module-transforms@7.22.1:
+    resolution: {integrity: sha512-dxAe9E7ySDGbQdCVOY/4+UcD8M9ZFqZcZhSPsPacvCG4M+9lwtDDQfI2EoaSvmf7W/8yCBkGU0m7Pvt1ru3UZw==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/helper-environment-visitor': 7.18.9
-      '@babel/helper-module-imports': 7.18.6
-      '@babel/helper-simple-access': 7.20.2
+      '@babel/helper-environment-visitor': 7.22.1
+      '@babel/helper-module-imports': 7.21.4
+      '@babel/helper-simple-access': 7.21.5
       '@babel/helper-split-export-declaration': 7.18.6
       '@babel/helper-validator-identifier': 7.19.1
-      '@babel/template': 7.20.7
-      '@babel/traverse': 7.21.3
-      '@babel/types': 7.21.5
+      '@babel/template': 7.21.9
+      '@babel/traverse': 7.22.4
+      '@babel/types': 7.22.4
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -2258,7 +2355,7 @@ packages:
     resolution: {integrity: sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.21.5
+      '@babel/types': 7.22.4
     dev: true
 
   /@babel/helper-plugin-utils@7.20.2:
@@ -2274,9 +2371,24 @@ packages:
     dependencies:
       '@babel/core': 7.21.3
       '@babel/helper-annotate-as-pure': 7.18.6
-      '@babel/helper-environment-visitor': 7.18.9
+      '@babel/helper-environment-visitor': 7.22.1
       '@babel/helper-wrap-function': 7.20.5
-      '@babel/types': 7.21.5
+      '@babel/types': 7.22.4
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@babel/helper-remap-async-to-generator@7.18.9(@babel/core@7.22.1):
+    resolution: {integrity: sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-annotate-as-pure': 7.18.6
+      '@babel/helper-environment-visitor': 7.22.1
+      '@babel/helper-wrap-function': 7.20.5
+      '@babel/types': 7.22.4
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -2285,40 +2397,35 @@ packages:
     resolution: {integrity: sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/helper-environment-visitor': 7.18.9
+      '@babel/helper-environment-visitor': 7.22.1
       '@babel/helper-member-expression-to-functions': 7.21.0
       '@babel/helper-optimise-call-expression': 7.18.6
-      '@babel/template': 7.20.7
-      '@babel/traverse': 7.21.3
-      '@babel/types': 7.21.5
+      '@babel/template': 7.21.9
+      '@babel/traverse': 7.22.4
+      '@babel/types': 7.22.4
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@babel/helper-simple-access@7.20.2:
-    resolution: {integrity: sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==}
+  /@babel/helper-simple-access@7.21.5:
+    resolution: {integrity: sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.21.5
+      '@babel/types': 7.22.4
     dev: true
 
   /@babel/helper-skip-transparent-expression-wrappers@7.20.0:
     resolution: {integrity: sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.21.5
+      '@babel/types': 7.22.4
     dev: true
 
   /@babel/helper-split-export-declaration@7.18.6:
     resolution: {integrity: sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.21.5
-    dev: true
-
-  /@babel/helper-string-parser@7.19.4:
-    resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==}
-    engines: {node: '>=6.9.0'}
+      '@babel/types': 7.22.4
 
   /@babel/helper-string-parser@7.21.5:
     resolution: {integrity: sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==}
@@ -2338,20 +2445,20 @@ packages:
     engines: {node: '>=6.9.0'}
     dependencies:
       '@babel/helper-function-name': 7.21.0
-      '@babel/template': 7.20.7
-      '@babel/traverse': 7.21.3
-      '@babel/types': 7.21.5
+      '@babel/template': 7.21.9
+      '@babel/traverse': 7.22.4
+      '@babel/types': 7.22.4
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@babel/helpers@7.21.0:
-    resolution: {integrity: sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==}
+  /@babel/helpers@7.22.3:
+    resolution: {integrity: sha512-jBJ7jWblbgr7r6wYZHMdIqKc73ycaTcCaWRq4/2LpuPHcx7xMlZvpGQkOYc9HeSjn6rcx15CPlgVcBtZ4WZJ2w==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/template': 7.20.7
-      '@babel/traverse': 7.21.3
-      '@babel/types': 7.21.5
+      '@babel/template': 7.21.9
+      '@babel/traverse': 7.22.4
+      '@babel/types': 7.22.4
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -2363,21 +2470,27 @@ packages:
       '@babel/helper-validator-identifier': 7.19.1
       chalk: 2.4.2
       js-tokens: 4.0.0
-    dev: true
-
-  /@babel/parser@7.21.4:
-    resolution: {integrity: sha512-alVJj7k7zIxqBZ7BTRhz0IqJFxW1VJbm6N8JbcYhQ186df9ZBPbZBmWSqAMXwHGsCJdYks7z/voa3ibiS5bCIw==}
-    engines: {node: '>=6.0.0'}
-    hasBin: true
-    dependencies:
-      '@babel/types': 7.21.4
 
   /@babel/parser@7.21.8:
     resolution: {integrity: sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==}
     engines: {node: '>=6.0.0'}
     hasBin: true
     dependencies:
-      '@babel/types': 7.21.4
+      '@babel/types': 7.22.4
+
+  /@babel/parser@7.21.9:
+    resolution: {integrity: sha512-q5PNg/Bi1OpGgx5jYlvWZwAorZepEudDMCLtj967aeS7WMont7dUZI46M2XwcIQqvUlMxWfdLFu4S/qSxeUu5g==}
+    engines: {node: '>=6.0.0'}
+    hasBin: true
+    dependencies:
+      '@babel/types': 7.22.4
+
+  /@babel/parser@7.22.4:
+    resolution: {integrity: sha512-VLLsx06XkEYqBtE5YGPwfSGwfrjnyPP5oiGty3S8pQLFDFLaS8VwWSIxkTXpcvr5zeYLE6+MBNl2npl/YnfofA==}
+    engines: {node: '>=6.0.0'}
+    hasBin: true
+    dependencies:
+      '@babel/types': 7.22.4
 
   /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.18.6(@babel/core@7.21.3):
     resolution: {integrity: sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==}
@@ -2389,6 +2502,16 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.18.6(@babel/core@7.22.1):
+    resolution: {integrity: sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.20.7(@babel/core@7.21.3):
     resolution: {integrity: sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==}
     engines: {node: '>=6.9.0'}
@@ -2401,6 +2524,18 @@ packages:
       '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.21.3)
     dev: true
 
+  /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.20.7(@babel/core@7.22.1):
+    resolution: {integrity: sha512-sbr9+wNE5aXMBBFBICk01tt7sBf2Oc9ikRFEcem/ZORup9IMUdNhW7/wVLEbbtlWOsEubJet46mHAL2C8+2jKQ==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.13.0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+      '@babel/helper-skip-transparent-expression-wrappers': 7.20.0
+      '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.22.1)
+    dev: true
+
   /@babel/plugin-proposal-async-generator-functions@7.20.7(@babel/core@7.21.3):
     resolution: {integrity: sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==}
     engines: {node: '>=6.9.0'}
@@ -2408,7 +2543,7 @@ packages:
       '@babel/core': ^7.0.0-0
     dependencies:
       '@babel/core': 7.21.3
-      '@babel/helper-environment-visitor': 7.18.9
+      '@babel/helper-environment-visitor': 7.22.1
       '@babel/helper-plugin-utils': 7.20.2
       '@babel/helper-remap-async-to-generator': 7.18.9(@babel/core@7.21.3)
       '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.21.3)
@@ -2416,6 +2551,21 @@ packages:
       - supports-color
     dev: true
 
+  /@babel/plugin-proposal-async-generator-functions@7.20.7(@babel/core@7.22.1):
+    resolution: {integrity: sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-environment-visitor': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+      '@babel/helper-remap-async-to-generator': 7.18.9(@babel/core@7.22.1)
+      '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.22.1)
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.21.3):
     resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==}
     engines: {node: '>=6.9.0'}
@@ -2429,6 +2579,19 @@ packages:
       - supports-color
     dev: true
 
+  /@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.22.1):
+    resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-create-class-features-plugin': 7.21.0(@babel/core@7.22.1)
+      '@babel/helper-plugin-utils': 7.20.2
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /@babel/plugin-proposal-class-static-block@7.21.0(@babel/core@7.21.3):
     resolution: {integrity: sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==}
     engines: {node: '>=6.9.0'}
@@ -2443,6 +2606,20 @@ packages:
       - supports-color
     dev: true
 
+  /@babel/plugin-proposal-class-static-block@7.21.0(@babel/core@7.22.1):
+    resolution: {integrity: sha512-XP5G9MWNUskFuP30IfFSEFB0Z6HzLIUcjYM4bYOPHXl7eiJ9HFv8tWj6TXTN5QODiEhDZAeI4hLok2iHFFV4hw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.12.0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-create-class-features-plugin': 7.21.0(@babel/core@7.22.1)
+      '@babel/helper-plugin-utils': 7.20.2
+      '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.22.1)
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /@babel/plugin-proposal-dynamic-import@7.18.6(@babel/core@7.21.3):
     resolution: {integrity: sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==}
     engines: {node: '>=6.9.0'}
@@ -2454,6 +2631,17 @@ packages:
       '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.21.3)
     dev: true
 
+  /@babel/plugin-proposal-dynamic-import@7.18.6(@babel/core@7.22.1):
+    resolution: {integrity: sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+      '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.22.1)
+    dev: true
+
   /@babel/plugin-proposal-export-namespace-from@7.18.9(@babel/core@7.21.3):
     resolution: {integrity: sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==}
     engines: {node: '>=6.9.0'}
@@ -2465,6 +2653,17 @@ packages:
       '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.21.3)
     dev: true
 
+  /@babel/plugin-proposal-export-namespace-from@7.18.9(@babel/core@7.22.1):
+    resolution: {integrity: sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+      '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.22.1)
+    dev: true
+
   /@babel/plugin-proposal-json-strings@7.18.6(@babel/core@7.21.3):
     resolution: {integrity: sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==}
     engines: {node: '>=6.9.0'}
@@ -2476,6 +2675,17 @@ packages:
       '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.21.3)
     dev: true
 
+  /@babel/plugin-proposal-json-strings@7.18.6(@babel/core@7.22.1):
+    resolution: {integrity: sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+      '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.22.1)
+    dev: true
+
   /@babel/plugin-proposal-logical-assignment-operators@7.20.7(@babel/core@7.21.3):
     resolution: {integrity: sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==}
     engines: {node: '>=6.9.0'}
@@ -2487,6 +2697,17 @@ packages:
       '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.21.3)
     dev: true
 
+  /@babel/plugin-proposal-logical-assignment-operators@7.20.7(@babel/core@7.22.1):
+    resolution: {integrity: sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+      '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.22.1)
+    dev: true
+
   /@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.21.3):
     resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==}
     engines: {node: '>=6.9.0'}
@@ -2498,6 +2719,17 @@ packages:
       '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.21.3)
     dev: true
 
+  /@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.22.1):
+    resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+      '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.22.1)
+    dev: true
+
   /@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.21.3):
     resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==}
     engines: {node: '>=6.9.0'}
@@ -2509,20 +2741,45 @@ packages:
       '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.21.3)
     dev: true
 
+  /@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.22.1):
+    resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+      '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.22.1)
+    dev: true
+
   /@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.21.3):
     resolution: {integrity: sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/compat-data': 7.21.4
+      '@babel/compat-data': 7.22.3
       '@babel/core': 7.21.3
-      '@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.3)
+      '@babel/helper-compilation-targets': 7.22.1(@babel/core@7.21.3)
       '@babel/helper-plugin-utils': 7.20.2
       '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.21.3)
       '@babel/plugin-transform-parameters': 7.21.3(@babel/core@7.21.3)
     dev: true
 
+  /@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.22.1):
+    resolution: {integrity: sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/compat-data': 7.22.3
+      '@babel/core': 7.22.1
+      '@babel/helper-compilation-targets': 7.22.1(@babel/core@7.22.1)
+      '@babel/helper-plugin-utils': 7.20.2
+      '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.22.1)
+      '@babel/plugin-transform-parameters': 7.21.3(@babel/core@7.22.1)
+    dev: true
+
   /@babel/plugin-proposal-optional-catch-binding@7.18.6(@babel/core@7.21.3):
     resolution: {integrity: sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==}
     engines: {node: '>=6.9.0'}
@@ -2534,6 +2791,17 @@ packages:
       '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.21.3)
     dev: true
 
+  /@babel/plugin-proposal-optional-catch-binding@7.18.6(@babel/core@7.22.1):
+    resolution: {integrity: sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+      '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.22.1)
+    dev: true
+
   /@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.21.3):
     resolution: {integrity: sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==}
     engines: {node: '>=6.9.0'}
@@ -2546,6 +2814,18 @@ packages:
       '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.21.3)
     dev: true
 
+  /@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.22.1):
+    resolution: {integrity: sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+      '@babel/helper-skip-transparent-expression-wrappers': 7.20.0
+      '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.22.1)
+    dev: true
+
   /@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.21.3):
     resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==}
     engines: {node: '>=6.9.0'}
@@ -2559,6 +2839,19 @@ packages:
       - supports-color
     dev: true
 
+  /@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.22.1):
+    resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-create-class-features-plugin': 7.21.0(@babel/core@7.22.1)
+      '@babel/helper-plugin-utils': 7.20.2
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /@babel/plugin-proposal-private-property-in-object@7.21.0(@babel/core@7.21.3):
     resolution: {integrity: sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw==}
     engines: {node: '>=6.9.0'}
@@ -2574,6 +2867,21 @@ packages:
       - supports-color
     dev: true
 
+  /@babel/plugin-proposal-private-property-in-object@7.21.0(@babel/core@7.22.1):
+    resolution: {integrity: sha512-ha4zfehbJjc5MmXBlHec1igel5TJXXLDDRbuJ4+XT2TJcyD9/V1919BA8gMvsdHcNMBy4WBUBiRb3nw/EQUtBw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-annotate-as-pure': 7.18.6
+      '@babel/helper-create-class-features-plugin': 7.21.0(@babel/core@7.22.1)
+      '@babel/helper-plugin-utils': 7.20.2
+      '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.22.1)
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /@babel/plugin-proposal-unicode-property-regex@7.18.6(@babel/core@7.21.3):
     resolution: {integrity: sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==}
     engines: {node: '>=4'}
@@ -2585,6 +2893,17 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-proposal-unicode-property-regex@7.18.6(@babel/core@7.22.1):
+    resolution: {integrity: sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==}
+    engines: {node: '>=4'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-create-regexp-features-plugin': 7.21.0(@babel/core@7.22.1)
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.21.3):
     resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==}
     peerDependencies:
@@ -2594,6 +2913,15 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.22.1):
+    resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.21.3):
     resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==}
     peerDependencies:
@@ -2603,6 +2931,15 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.22.1):
+    resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.21.3):
     resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==}
     peerDependencies:
@@ -2612,6 +2949,15 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.22.1):
+    resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.21.3):
     resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==}
     engines: {node: '>=6.9.0'}
@@ -2622,6 +2968,16 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.22.1):
+    resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.21.3):
     resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==}
     peerDependencies:
@@ -2631,6 +2987,15 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.22.1):
+    resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.21.3):
     resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==}
     peerDependencies:
@@ -2640,13 +3005,22 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
-  /@babel/plugin-syntax-flow@7.18.6(@babel/core@7.21.3):
+  /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.22.1):
+    resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
+  /@babel/plugin-syntax-flow@7.18.6(@babel/core@7.22.1):
     resolution: {integrity: sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.21.3
+      '@babel/core': 7.22.1
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
@@ -2660,6 +3034,16 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-syntax-import-assertions@7.20.0(@babel/core@7.22.1):
+    resolution: {integrity: sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.21.3):
     resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==}
     peerDependencies:
@@ -2669,6 +3053,15 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.22.1):
+    resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.21.3):
     resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==}
     peerDependencies:
@@ -2678,13 +3071,22 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
-  /@babel/plugin-syntax-jsx@7.18.6(@babel/core@7.21.3):
+  /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.22.1):
+    resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
+  /@babel/plugin-syntax-jsx@7.18.6(@babel/core@7.22.1):
     resolution: {integrity: sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.21.3
+      '@babel/core': 7.22.1
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
@@ -2697,6 +3099,15 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.22.1):
+    resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.21.3):
     resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==}
     peerDependencies:
@@ -2706,6 +3117,15 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.22.1):
+    resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.21.3):
     resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==}
     peerDependencies:
@@ -2715,6 +3135,15 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.22.1):
+    resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.21.3):
     resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==}
     peerDependencies:
@@ -2724,6 +3153,15 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.22.1):
+    resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.21.3):
     resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==}
     peerDependencies:
@@ -2733,6 +3171,15 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.22.1):
+    resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.21.3):
     resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==}
     peerDependencies:
@@ -2742,6 +3189,15 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.22.1):
+    resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.21.3):
     resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==}
     engines: {node: '>=6.9.0'}
@@ -2752,6 +3208,16 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.22.1):
+    resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.21.3):
     resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==}
     engines: {node: '>=6.9.0'}
@@ -2762,13 +3228,23 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
-  /@babel/plugin-syntax-typescript@7.20.0(@babel/core@7.21.3):
+  /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.22.1):
+    resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
+  /@babel/plugin-syntax-typescript@7.20.0(@babel/core@7.22.1):
     resolution: {integrity: sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.21.3
+      '@babel/core': 7.22.1
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
@@ -2782,6 +3258,16 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-transform-arrow-functions@7.20.7(@babel/core@7.22.1):
+    resolution: {integrity: sha512-3poA5E7dzDomxj9WXWwuD6A5F3kc7VXwIJO+E+J8qtDtS+pXPAhrgEyh+9GBwBgPq1Z+bB+/JD60lp5jsN7JPQ==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-transform-async-to-generator@7.20.7(@babel/core@7.21.3):
     resolution: {integrity: sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==}
     engines: {node: '>=6.9.0'}
@@ -2789,13 +3275,27 @@ packages:
       '@babel/core': ^7.0.0-0
     dependencies:
       '@babel/core': 7.21.3
-      '@babel/helper-module-imports': 7.18.6
+      '@babel/helper-module-imports': 7.21.4
       '@babel/helper-plugin-utils': 7.20.2
       '@babel/helper-remap-async-to-generator': 7.18.9(@babel/core@7.21.3)
     transitivePeerDependencies:
       - supports-color
     dev: true
 
+  /@babel/plugin-transform-async-to-generator@7.20.7(@babel/core@7.22.1):
+    resolution: {integrity: sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-module-imports': 7.21.4
+      '@babel/helper-plugin-utils': 7.20.2
+      '@babel/helper-remap-async-to-generator': 7.18.9(@babel/core@7.22.1)
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /@babel/plugin-transform-block-scoped-functions@7.18.6(@babel/core@7.21.3):
     resolution: {integrity: sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==}
     engines: {node: '>=6.9.0'}
@@ -2806,6 +3306,16 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-transform-block-scoped-functions@7.18.6(@babel/core@7.22.1):
+    resolution: {integrity: sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-transform-block-scoping@7.21.0(@babel/core@7.21.3):
     resolution: {integrity: sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ==}
     engines: {node: '>=6.9.0'}
@@ -2816,6 +3326,16 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-transform-block-scoping@7.21.0(@babel/core@7.22.1):
+    resolution: {integrity: sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-transform-classes@7.21.0(@babel/core@7.21.3):
     resolution: {integrity: sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ==}
     engines: {node: '>=6.9.0'}
@@ -2824,8 +3344,28 @@ packages:
     dependencies:
       '@babel/core': 7.21.3
       '@babel/helper-annotate-as-pure': 7.18.6
-      '@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.3)
-      '@babel/helper-environment-visitor': 7.18.9
+      '@babel/helper-compilation-targets': 7.22.1(@babel/core@7.21.3)
+      '@babel/helper-environment-visitor': 7.22.1
+      '@babel/helper-function-name': 7.21.0
+      '@babel/helper-optimise-call-expression': 7.18.6
+      '@babel/helper-plugin-utils': 7.20.2
+      '@babel/helper-replace-supers': 7.20.7
+      '@babel/helper-split-export-declaration': 7.18.6
+      globals: 11.12.0
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@babel/plugin-transform-classes@7.21.0(@babel/core@7.22.1):
+    resolution: {integrity: sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-annotate-as-pure': 7.18.6
+      '@babel/helper-compilation-targets': 7.22.1(@babel/core@7.22.1)
+      '@babel/helper-environment-visitor': 7.22.1
       '@babel/helper-function-name': 7.21.0
       '@babel/helper-optimise-call-expression': 7.18.6
       '@babel/helper-plugin-utils': 7.20.2
@@ -2844,7 +3384,18 @@ packages:
     dependencies:
       '@babel/core': 7.21.3
       '@babel/helper-plugin-utils': 7.20.2
-      '@babel/template': 7.20.7
+      '@babel/template': 7.21.9
+    dev: true
+
+  /@babel/plugin-transform-computed-properties@7.20.7(@babel/core@7.22.1):
+    resolution: {integrity: sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+      '@babel/template': 7.21.9
     dev: true
 
   /@babel/plugin-transform-destructuring@7.21.3(@babel/core@7.21.3):
@@ -2857,6 +3408,16 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-transform-destructuring@7.21.3(@babel/core@7.22.1):
+    resolution: {integrity: sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-transform-dotall-regex@7.18.6(@babel/core@7.21.3):
     resolution: {integrity: sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==}
     engines: {node: '>=6.9.0'}
@@ -2868,6 +3429,17 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-transform-dotall-regex@7.18.6(@babel/core@7.22.1):
+    resolution: {integrity: sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-create-regexp-features-plugin': 7.21.0(@babel/core@7.22.1)
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-transform-duplicate-keys@7.18.9(@babel/core@7.21.3):
     resolution: {integrity: sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==}
     engines: {node: '>=6.9.0'}
@@ -2878,6 +3450,16 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-transform-duplicate-keys@7.18.9(@babel/core@7.22.1):
+    resolution: {integrity: sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-transform-exponentiation-operator@7.18.6(@babel/core@7.21.3):
     resolution: {integrity: sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==}
     engines: {node: '>=6.9.0'}
@@ -2889,15 +3471,26 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
-  /@babel/plugin-transform-flow-strip-types@7.21.0(@babel/core@7.21.3):
+  /@babel/plugin-transform-exponentiation-operator@7.18.6(@babel/core@7.22.1):
+    resolution: {integrity: sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-builder-binary-assignment-operator-visitor': 7.18.9
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
+  /@babel/plugin-transform-flow-strip-types@7.21.0(@babel/core@7.22.1):
     resolution: {integrity: sha512-FlFA2Mj87a6sDkW4gfGrQQqwY/dLlBAyJa2dJEZ+FHXUVHBflO2wyKvg+OOEzXfrKYIa4HWl0mgmbCzt0cMb7w==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.21.3
+      '@babel/core': 7.22.1
       '@babel/helper-plugin-utils': 7.20.2
-      '@babel/plugin-syntax-flow': 7.18.6(@babel/core@7.21.3)
+      '@babel/plugin-syntax-flow': 7.18.6(@babel/core@7.22.1)
     dev: true
 
   /@babel/plugin-transform-for-of@7.21.0(@babel/core@7.21.3):
@@ -2910,6 +3503,16 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-transform-for-of@7.21.0(@babel/core@7.22.1):
+    resolution: {integrity: sha512-LlUYlydgDkKpIY7mcBWvyPPmMcOphEyYA27Ef4xpbh1IiDNLr0kZsos2nf92vz3IccvJI25QUwp86Eo5s6HmBQ==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-transform-function-name@7.18.9(@babel/core@7.21.3):
     resolution: {integrity: sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==}
     engines: {node: '>=6.9.0'}
@@ -2917,7 +3520,19 @@ packages:
       '@babel/core': ^7.0.0-0
     dependencies:
       '@babel/core': 7.21.3
-      '@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.3)
+      '@babel/helper-compilation-targets': 7.22.1(@babel/core@7.21.3)
+      '@babel/helper-function-name': 7.21.0
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
+  /@babel/plugin-transform-function-name@7.18.9(@babel/core@7.22.1):
+    resolution: {integrity: sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-compilation-targets': 7.22.1(@babel/core@7.22.1)
       '@babel/helper-function-name': 7.21.0
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
@@ -2932,6 +3547,16 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-transform-literals@7.18.9(@babel/core@7.22.1):
+    resolution: {integrity: sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-transform-member-expression-literals@7.18.6(@babel/core@7.21.3):
     resolution: {integrity: sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==}
     engines: {node: '>=6.9.0'}
@@ -2942,6 +3567,16 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-transform-member-expression-literals@7.18.6(@babel/core@7.22.1):
+    resolution: {integrity: sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-transform-modules-amd@7.20.11(@babel/core@7.21.3):
     resolution: {integrity: sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==}
     engines: {node: '>=6.9.0'}
@@ -2949,7 +3584,20 @@ packages:
       '@babel/core': ^7.0.0-0
     dependencies:
       '@babel/core': 7.21.3
-      '@babel/helper-module-transforms': 7.21.2
+      '@babel/helper-module-transforms': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@babel/plugin-transform-modules-amd@7.20.11(@babel/core@7.22.1):
+    resolution: {integrity: sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-module-transforms': 7.22.1
       '@babel/helper-plugin-utils': 7.20.2
     transitivePeerDependencies:
       - supports-color
@@ -2962,9 +3610,23 @@ packages:
       '@babel/core': ^7.0.0-0
     dependencies:
       '@babel/core': 7.21.3
-      '@babel/helper-module-transforms': 7.21.2
+      '@babel/helper-module-transforms': 7.22.1
       '@babel/helper-plugin-utils': 7.20.2
-      '@babel/helper-simple-access': 7.20.2
+      '@babel/helper-simple-access': 7.21.5
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@babel/plugin-transform-modules-commonjs@7.21.2(@babel/core@7.22.1):
+    resolution: {integrity: sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-module-transforms': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+      '@babel/helper-simple-access': 7.21.5
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -2977,7 +3639,22 @@ packages:
     dependencies:
       '@babel/core': 7.21.3
       '@babel/helper-hoist-variables': 7.18.6
-      '@babel/helper-module-transforms': 7.21.2
+      '@babel/helper-module-transforms': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+      '@babel/helper-validator-identifier': 7.19.1
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@babel/plugin-transform-modules-systemjs@7.20.11(@babel/core@7.22.1):
+    resolution: {integrity: sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-hoist-variables': 7.18.6
+      '@babel/helper-module-transforms': 7.22.1
       '@babel/helper-plugin-utils': 7.20.2
       '@babel/helper-validator-identifier': 7.19.1
     transitivePeerDependencies:
@@ -2991,7 +3668,20 @@ packages:
       '@babel/core': ^7.0.0-0
     dependencies:
       '@babel/core': 7.21.3
-      '@babel/helper-module-transforms': 7.21.2
+      '@babel/helper-module-transforms': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@babel/plugin-transform-modules-umd@7.18.6(@babel/core@7.22.1):
+    resolution: {integrity: sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-module-transforms': 7.22.1
       '@babel/helper-plugin-utils': 7.20.2
     transitivePeerDependencies:
       - supports-color
@@ -3008,6 +3698,17 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-transform-named-capturing-groups-regex@7.20.5(@babel/core@7.22.1):
+    resolution: {integrity: sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-create-regexp-features-plugin': 7.21.0(@babel/core@7.22.1)
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-transform-new-target@7.18.6(@babel/core@7.21.3):
     resolution: {integrity: sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==}
     engines: {node: '>=6.9.0'}
@@ -3018,6 +3719,16 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-transform-new-target@7.18.6(@babel/core@7.22.1):
+    resolution: {integrity: sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-transform-object-super@7.18.6(@babel/core@7.21.3):
     resolution: {integrity: sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==}
     engines: {node: '>=6.9.0'}
@@ -3031,6 +3742,19 @@ packages:
       - supports-color
     dev: true
 
+  /@babel/plugin-transform-object-super@7.18.6(@babel/core@7.22.1):
+    resolution: {integrity: sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+      '@babel/helper-replace-supers': 7.20.7
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /@babel/plugin-transform-parameters@7.21.3(@babel/core@7.21.3):
     resolution: {integrity: sha512-Wxc+TvppQG9xWFYatvCGPvZ6+SIUxQ2ZdiBP+PHYMIjnPXD+uThCshaz4NZOnODAtBjjcVQQ/3OKs9LW28purQ==}
     engines: {node: '>=6.9.0'}
@@ -3041,6 +3765,16 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-transform-parameters@7.21.3(@babel/core@7.22.1):
+    resolution: {integrity: sha512-Wxc+TvppQG9xWFYatvCGPvZ6+SIUxQ2ZdiBP+PHYMIjnPXD+uThCshaz4NZOnODAtBjjcVQQ/3OKs9LW28purQ==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-transform-property-literals@7.18.6(@babel/core@7.21.3):
     resolution: {integrity: sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==}
     engines: {node: '>=6.9.0'}
@@ -3051,38 +3785,48 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
-  /@babel/plugin-transform-react-jsx-self@7.21.0(@babel/core@7.21.3):
+  /@babel/plugin-transform-property-literals@7.18.6(@babel/core@7.22.1):
+    resolution: {integrity: sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
+  /@babel/plugin-transform-react-jsx-self@7.21.0(@babel/core@7.22.1):
     resolution: {integrity: sha512-f/Eq+79JEu+KUANFks9UZCcvydOOGMgF7jBrcwjHa5jTZD8JivnhCJYvmlhR/WTXBWonDExPoW0eO/CR4QJirA==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.21.3
+      '@babel/core': 7.22.1
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
-  /@babel/plugin-transform-react-jsx-source@7.19.6(@babel/core@7.21.3):
+  /@babel/plugin-transform-react-jsx-source@7.19.6(@babel/core@7.22.1):
     resolution: {integrity: sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.21.3
+      '@babel/core': 7.22.1
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
-  /@babel/plugin-transform-react-jsx@7.21.0(@babel/core@7.21.3):
+  /@babel/plugin-transform-react-jsx@7.21.0(@babel/core@7.22.1):
     resolution: {integrity: sha512-6OAWljMvQrZjR2DaNhVfRz6dkCAVV+ymcLUmaf8bccGOHn2v5rHJK3tTpij0BuhdYWP4LLaqj5lwcdlpAAPuvg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.21.3
+      '@babel/core': 7.22.1
       '@babel/helper-annotate-as-pure': 7.18.6
-      '@babel/helper-module-imports': 7.18.6
+      '@babel/helper-module-imports': 7.21.4
       '@babel/helper-plugin-utils': 7.20.2
-      '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.21.3)
-      '@babel/types': 7.21.5
+      '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.22.1)
+      '@babel/types': 7.22.4
     dev: true
 
   /@babel/plugin-transform-regenerator@7.20.5(@babel/core@7.21.3):
@@ -3096,6 +3840,17 @@ packages:
       regenerator-transform: 0.15.1
     dev: true
 
+  /@babel/plugin-transform-regenerator@7.20.5(@babel/core@7.22.1):
+    resolution: {integrity: sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+      regenerator-transform: 0.15.1
+    dev: true
+
   /@babel/plugin-transform-reserved-words@7.18.6(@babel/core@7.21.3):
     resolution: {integrity: sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==}
     engines: {node: '>=6.9.0'}
@@ -3106,6 +3861,16 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-transform-reserved-words@7.18.6(@babel/core@7.22.1):
+    resolution: {integrity: sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-transform-shorthand-properties@7.18.6(@babel/core@7.21.3):
     resolution: {integrity: sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==}
     engines: {node: '>=6.9.0'}
@@ -3116,6 +3881,16 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-transform-shorthand-properties@7.18.6(@babel/core@7.22.1):
+    resolution: {integrity: sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-transform-spread@7.20.7(@babel/core@7.21.3):
     resolution: {integrity: sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==}
     engines: {node: '>=6.9.0'}
@@ -3127,6 +3902,17 @@ packages:
       '@babel/helper-skip-transparent-expression-wrappers': 7.20.0
     dev: true
 
+  /@babel/plugin-transform-spread@7.20.7(@babel/core@7.22.1):
+    resolution: {integrity: sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+      '@babel/helper-skip-transparent-expression-wrappers': 7.20.0
+    dev: true
+
   /@babel/plugin-transform-sticky-regex@7.18.6(@babel/core@7.21.3):
     resolution: {integrity: sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==}
     engines: {node: '>=6.9.0'}
@@ -3137,6 +3923,16 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-transform-sticky-regex@7.18.6(@babel/core@7.22.1):
+    resolution: {integrity: sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-transform-template-literals@7.18.9(@babel/core@7.21.3):
     resolution: {integrity: sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==}
     engines: {node: '>=6.9.0'}
@@ -3147,6 +3943,16 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-transform-template-literals@7.18.9(@babel/core@7.22.1):
+    resolution: {integrity: sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-transform-typeof-symbol@7.18.9(@babel/core@7.21.3):
     resolution: {integrity: sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==}
     engines: {node: '>=6.9.0'}
@@ -3157,17 +3963,27 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
-  /@babel/plugin-transform-typescript@7.21.3(@babel/core@7.21.3):
+  /@babel/plugin-transform-typeof-symbol@7.18.9(@babel/core@7.22.1):
+    resolution: {integrity: sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
+  /@babel/plugin-transform-typescript@7.21.3(@babel/core@7.22.1):
     resolution: {integrity: sha512-RQxPz6Iqt8T0uw/WsJNReuBpWpBqs/n7mNo18sKLoTbMp+UrEekhH+pKSVC7gWz+DNjo9gryfV8YzCiT45RgMw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.21.3
+      '@babel/core': 7.22.1
       '@babel/helper-annotate-as-pure': 7.18.6
-      '@babel/helper-create-class-features-plugin': 7.21.0(@babel/core@7.21.3)
+      '@babel/helper-create-class-features-plugin': 7.21.0(@babel/core@7.22.1)
       '@babel/helper-plugin-utils': 7.20.2
-      '@babel/plugin-syntax-typescript': 7.20.0(@babel/core@7.21.3)
+      '@babel/plugin-syntax-typescript': 7.20.0(@babel/core@7.22.1)
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -3182,6 +3998,16 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-transform-unicode-escapes@7.18.10(@babel/core@7.22.1):
+    resolution: {integrity: sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/plugin-transform-unicode-regex@7.18.6(@babel/core@7.21.3):
     resolution: {integrity: sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==}
     engines: {node: '>=6.9.0'}
@@ -3193,15 +4019,26 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
+  /@babel/plugin-transform-unicode-regex@7.18.6(@babel/core@7.22.1):
+    resolution: {integrity: sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-create-regexp-features-plugin': 7.21.0(@babel/core@7.22.1)
+      '@babel/helper-plugin-utils': 7.20.2
+    dev: true
+
   /@babel/preset-env@7.21.4(@babel/core@7.21.3):
     resolution: {integrity: sha512-2W57zHs2yDLm6GD5ZpvNn71lZ0B/iypSdIeq25OurDKji6AdzV07qp4s3n1/x5BqtiGaTrPN3nerlSCaC5qNTw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/compat-data': 7.21.4
+      '@babel/compat-data': 7.22.3
       '@babel/core': 7.21.3
-      '@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.3)
+      '@babel/helper-compilation-targets': 7.22.1(@babel/core@7.21.3)
       '@babel/helper-plugin-utils': 7.20.2
       '@babel/helper-validator-option': 7.21.0
       '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.18.6(@babel/core@7.21.3)
@@ -3269,7 +4106,7 @@ packages:
       '@babel/plugin-transform-unicode-escapes': 7.18.10(@babel/core@7.21.3)
       '@babel/plugin-transform-unicode-regex': 7.18.6(@babel/core@7.21.3)
       '@babel/preset-modules': 0.1.5(@babel/core@7.21.3)
-      '@babel/types': 7.21.5
+      '@babel/types': 7.22.4
       babel-plugin-polyfill-corejs2: 0.3.3(@babel/core@7.21.3)
       babel-plugin-polyfill-corejs3: 0.6.0(@babel/core@7.21.3)
       babel-plugin-polyfill-regenerator: 0.4.1(@babel/core@7.21.3)
@@ -3279,16 +4116,102 @@ packages:
       - supports-color
     dev: true
 
-  /@babel/preset-flow@7.18.6(@babel/core@7.21.3):
+  /@babel/preset-env@7.21.4(@babel/core@7.22.1):
+    resolution: {integrity: sha512-2W57zHs2yDLm6GD5ZpvNn71lZ0B/iypSdIeq25OurDKji6AdzV07qp4s3n1/x5BqtiGaTrPN3nerlSCaC5qNTw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/compat-data': 7.22.3
+      '@babel/core': 7.22.1
+      '@babel/helper-compilation-targets': 7.22.1(@babel/core@7.22.1)
+      '@babel/helper-plugin-utils': 7.20.2
+      '@babel/helper-validator-option': 7.21.0
+      '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.18.6(@babel/core@7.22.1)
+      '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.20.7(@babel/core@7.22.1)
+      '@babel/plugin-proposal-async-generator-functions': 7.20.7(@babel/core@7.22.1)
+      '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.22.1)
+      '@babel/plugin-proposal-class-static-block': 7.21.0(@babel/core@7.22.1)
+      '@babel/plugin-proposal-dynamic-import': 7.18.6(@babel/core@7.22.1)
+      '@babel/plugin-proposal-export-namespace-from': 7.18.9(@babel/core@7.22.1)
+      '@babel/plugin-proposal-json-strings': 7.18.6(@babel/core@7.22.1)
+      '@babel/plugin-proposal-logical-assignment-operators': 7.20.7(@babel/core@7.22.1)
+      '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.22.1)
+      '@babel/plugin-proposal-numeric-separator': 7.18.6(@babel/core@7.22.1)
+      '@babel/plugin-proposal-object-rest-spread': 7.20.7(@babel/core@7.22.1)
+      '@babel/plugin-proposal-optional-catch-binding': 7.18.6(@babel/core@7.22.1)
+      '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.22.1)
+      '@babel/plugin-proposal-private-methods': 7.18.6(@babel/core@7.22.1)
+      '@babel/plugin-proposal-private-property-in-object': 7.21.0(@babel/core@7.22.1)
+      '@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.22.1)
+      '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.22.1)
+      '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.22.1)
+      '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.22.1)
+      '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.22.1)
+      '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.22.1)
+      '@babel/plugin-syntax-import-assertions': 7.20.0(@babel/core@7.22.1)
+      '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.22.1)
+      '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.22.1)
+      '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.22.1)
+      '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.22.1)
+      '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.22.1)
+      '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.22.1)
+      '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.22.1)
+      '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.22.1)
+      '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.22.1)
+      '@babel/plugin-transform-arrow-functions': 7.20.7(@babel/core@7.22.1)
+      '@babel/plugin-transform-async-to-generator': 7.20.7(@babel/core@7.22.1)
+      '@babel/plugin-transform-block-scoped-functions': 7.18.6(@babel/core@7.22.1)
+      '@babel/plugin-transform-block-scoping': 7.21.0(@babel/core@7.22.1)
+      '@babel/plugin-transform-classes': 7.21.0(@babel/core@7.22.1)
+      '@babel/plugin-transform-computed-properties': 7.20.7(@babel/core@7.22.1)
+      '@babel/plugin-transform-destructuring': 7.21.3(@babel/core@7.22.1)
+      '@babel/plugin-transform-dotall-regex': 7.18.6(@babel/core@7.22.1)
+      '@babel/plugin-transform-duplicate-keys': 7.18.9(@babel/core@7.22.1)
+      '@babel/plugin-transform-exponentiation-operator': 7.18.6(@babel/core@7.22.1)
+      '@babel/plugin-transform-for-of': 7.21.0(@babel/core@7.22.1)
+      '@babel/plugin-transform-function-name': 7.18.9(@babel/core@7.22.1)
+      '@babel/plugin-transform-literals': 7.18.9(@babel/core@7.22.1)
+      '@babel/plugin-transform-member-expression-literals': 7.18.6(@babel/core@7.22.1)
+      '@babel/plugin-transform-modules-amd': 7.20.11(@babel/core@7.22.1)
+      '@babel/plugin-transform-modules-commonjs': 7.21.2(@babel/core@7.22.1)
+      '@babel/plugin-transform-modules-systemjs': 7.20.11(@babel/core@7.22.1)
+      '@babel/plugin-transform-modules-umd': 7.18.6(@babel/core@7.22.1)
+      '@babel/plugin-transform-named-capturing-groups-regex': 7.20.5(@babel/core@7.22.1)
+      '@babel/plugin-transform-new-target': 7.18.6(@babel/core@7.22.1)
+      '@babel/plugin-transform-object-super': 7.18.6(@babel/core@7.22.1)
+      '@babel/plugin-transform-parameters': 7.21.3(@babel/core@7.22.1)
+      '@babel/plugin-transform-property-literals': 7.18.6(@babel/core@7.22.1)
+      '@babel/plugin-transform-regenerator': 7.20.5(@babel/core@7.22.1)
+      '@babel/plugin-transform-reserved-words': 7.18.6(@babel/core@7.22.1)
+      '@babel/plugin-transform-shorthand-properties': 7.18.6(@babel/core@7.22.1)
+      '@babel/plugin-transform-spread': 7.20.7(@babel/core@7.22.1)
+      '@babel/plugin-transform-sticky-regex': 7.18.6(@babel/core@7.22.1)
+      '@babel/plugin-transform-template-literals': 7.18.9(@babel/core@7.22.1)
+      '@babel/plugin-transform-typeof-symbol': 7.18.9(@babel/core@7.22.1)
+      '@babel/plugin-transform-unicode-escapes': 7.18.10(@babel/core@7.22.1)
+      '@babel/plugin-transform-unicode-regex': 7.18.6(@babel/core@7.22.1)
+      '@babel/preset-modules': 0.1.5(@babel/core@7.22.1)
+      '@babel/types': 7.22.4
+      babel-plugin-polyfill-corejs2: 0.3.3(@babel/core@7.22.1)
+      babel-plugin-polyfill-corejs3: 0.6.0(@babel/core@7.22.1)
+      babel-plugin-polyfill-regenerator: 0.4.1(@babel/core@7.22.1)
+      core-js-compat: 3.29.1
+      semver: 6.3.0
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@babel/preset-flow@7.18.6(@babel/core@7.22.1):
     resolution: {integrity: sha512-E7BDhL64W6OUqpuyHnSroLnqyRTcG6ZdOBl1OKI/QK/HJfplqK/S3sq1Cckx7oTodJ5yOXyfw7rEADJ6UjoQDQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.21.3
+      '@babel/core': 7.22.1
       '@babel/helper-plugin-utils': 7.20.2
       '@babel/helper-validator-option': 7.21.0
-      '@babel/plugin-transform-flow-strip-types': 7.21.0(@babel/core@7.21.3)
+      '@babel/plugin-transform-flow-strip-types': 7.21.0(@babel/core@7.22.1)
     dev: true
 
   /@babel/preset-modules@0.1.5(@babel/core@7.21.3):
@@ -3300,31 +4223,44 @@ packages:
       '@babel/helper-plugin-utils': 7.20.2
       '@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.21.3)
       '@babel/plugin-transform-dotall-regex': 7.18.6(@babel/core@7.21.3)
-      '@babel/types': 7.21.5
+      '@babel/types': 7.22.4
       esutils: 2.0.3
     dev: true
 
-  /@babel/preset-typescript@7.21.0(@babel/core@7.21.3):
+  /@babel/preset-modules@0.1.5(@babel/core@7.22.1):
+    resolution: {integrity: sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-plugin-utils': 7.20.2
+      '@babel/plugin-proposal-unicode-property-regex': 7.18.6(@babel/core@7.22.1)
+      '@babel/plugin-transform-dotall-regex': 7.18.6(@babel/core@7.22.1)
+      '@babel/types': 7.22.4
+      esutils: 2.0.3
+    dev: true
+
+  /@babel/preset-typescript@7.21.0(@babel/core@7.22.1):
     resolution: {integrity: sha512-myc9mpoVA5m1rF8K8DgLEatOYFDpwC+RkMkjZ0Du6uI62YvDe8uxIEYVs/VCdSJ097nlALiU/yBC7//3nI+hNg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.21.3
+      '@babel/core': 7.22.1
       '@babel/helper-plugin-utils': 7.20.2
       '@babel/helper-validator-option': 7.21.0
-      '@babel/plugin-transform-typescript': 7.21.3(@babel/core@7.21.3)
+      '@babel/plugin-transform-typescript': 7.21.3(@babel/core@7.22.1)
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@babel/register@7.21.0(@babel/core@7.21.3):
+  /@babel/register@7.21.0(@babel/core@7.22.1):
     resolution: {integrity: sha512-9nKsPmYDi5DidAqJaQooxIhsLJiNMkGr8ypQ8Uic7cIox7UCDsM7HuUGxdGT7mSDTYbqzIdsOWzfBton/YJrMw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.21.3
+      '@babel/core': 7.22.1
       clone-deep: 4.0.1
       find-cache-dir: 2.1.0
       make-dir: 2.1.0
@@ -3341,6 +4277,7 @@ packages:
     engines: {node: '>=6.9.0'}
     dependencies:
       regenerator-runtime: 0.13.11
+    dev: true
 
   /@babel/runtime@7.21.0:
     resolution: {integrity: sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==}
@@ -3348,43 +4285,59 @@ packages:
     dependencies:
       regenerator-runtime: 0.13.11
 
-  /@babel/template@7.20.7:
-    resolution: {integrity: sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==}
+  /@babel/template@7.21.9:
+    resolution: {integrity: sha512-MK0X5k8NKOuWRamiEfc3KEJiHMTkGZNUjzMipqCGDDc6ijRl/B7RGSKVGncu4Ro/HdyzzY6cmoXuKI2Gffk7vQ==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/code-frame': 7.18.6
-      '@babel/parser': 7.21.8
-      '@babel/types': 7.21.5
-    dev: true
+      '@babel/code-frame': 7.21.4
+      '@babel/parser': 7.22.4
+      '@babel/types': 7.22.4
 
   /@babel/traverse@7.21.3:
     resolution: {integrity: sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/code-frame': 7.18.6
-      '@babel/generator': 7.21.3
-      '@babel/helper-environment-visitor': 7.18.9
+      '@babel/code-frame': 7.21.4
+      '@babel/generator': 7.22.3
+      '@babel/helper-environment-visitor': 7.22.1
       '@babel/helper-function-name': 7.21.0
       '@babel/helper-hoist-variables': 7.18.6
       '@babel/helper-split-export-declaration': 7.18.6
-      '@babel/parser': 7.21.8
-      '@babel/types': 7.21.5
+      '@babel/parser': 7.22.4
+      '@babel/types': 7.22.4
+      debug: 4.3.4(supports-color@8.1.1)
+      globals: 11.12.0
+    transitivePeerDependencies:
+      - supports-color
+
+  /@babel/traverse@7.22.4:
+    resolution: {integrity: sha512-Tn1pDsjIcI+JcLKq1AVlZEr4226gpuAQTsLMorsYg9tuS/kG7nuwwJ4AB8jfQuEgb/COBwR/DqJxmoiYFu5/rQ==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/code-frame': 7.21.4
+      '@babel/generator': 7.22.3
+      '@babel/helper-environment-visitor': 7.22.1
+      '@babel/helper-function-name': 7.21.0
+      '@babel/helper-hoist-variables': 7.18.6
+      '@babel/helper-split-export-declaration': 7.18.6
+      '@babel/parser': 7.22.4
+      '@babel/types': 7.22.4
       debug: 4.3.4(supports-color@8.1.1)
       globals: 11.12.0
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@babel/types@7.21.4:
-    resolution: {integrity: sha512-rU2oY501qDxE8Pyo7i/Orqma4ziCOrby0/9mvbDUGEfvZjb279Nk9k19e2fiCxHbRRpY2ZyrgW1eq22mvmOIzA==}
+  /@babel/types@7.21.5:
+    resolution: {integrity: sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/helper-string-parser': 7.19.4
+      '@babel/helper-string-parser': 7.21.5
       '@babel/helper-validator-identifier': 7.19.1
       to-fast-properties: 2.0.0
 
-  /@babel/types@7.21.5:
-    resolution: {integrity: sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==}
+  /@babel/types@7.22.4:
+    resolution: {integrity: sha512-Tx9x3UBHTTsMSW85WB2kphxYQVvrZ/t1FxD88IpSgIjiUJlCm9z+xWIDwyo1vffTwSqteqyznB8ZE9vYYk16zA==}
     engines: {node: '>=6.9.0'}
     dependencies:
       '@babel/helper-string-parser': 7.21.5
@@ -3399,29 +4352,29 @@ packages:
     resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
     dev: true
 
-  /@bull-board/api@5.1.2(@bull-board/ui@5.1.2):
-    resolution: {integrity: sha512-NLV88eDnOMd0XuYUNBcaYL6UdxbGYY91kP+P5Jled6CGcYmXLXv/mzAopxmgctqmP07cjC2EXN/4Oc7dyalDqA==}
+  /@bull-board/api@5.2.0(@bull-board/ui@5.2.0):
+    resolution: {integrity: sha512-1HGF2EF/4zI3+Cj414nQzwFprLXOJTlVdqXUf5UEBS4HtYafWv93mGIwkrD8S4Bpz4VSvM87adF6tQPJ7Ewt+w==}
     peerDependencies:
-      '@bull-board/ui': 5.1.2
+      '@bull-board/ui': 5.2.0
     dependencies:
-      '@bull-board/ui': 5.1.2
+      '@bull-board/ui': 5.2.0
       redis-info: 3.1.0
     dev: false
 
-  /@bull-board/fastify@5.1.2:
-    resolution: {integrity: sha512-qiURhcqMfER5hp4RtgepMDbPj5H4ZKNOgK+7RIG3bd3d0tBoLjXzooXFryxzd6w130pXU9/crUMtcMP+Ulaj6g==}
+  /@bull-board/fastify@5.2.0:
+    resolution: {integrity: sha512-tvvgCAxFoiogqmAhxUiAOV/rXBVXlmg7JO3jkePA778O/YSiE7nrwwjiiLbLNuIYLZfdoYnRK4bIDmLeg1nK2A==}
     dependencies:
-      '@bull-board/api': 5.1.2(@bull-board/ui@5.1.2)
-      '@bull-board/ui': 5.1.2
-      '@fastify/static': 6.10.1
+      '@bull-board/api': 5.2.0(@bull-board/ui@5.2.0)
+      '@bull-board/ui': 5.2.0
+      '@fastify/static': 6.10.2
       '@fastify/view': 7.4.1
       ejs: 3.1.8
     dev: false
 
-  /@bull-board/ui@5.1.2:
-    resolution: {integrity: sha512-DXXbKA4NLo5D19Vssrg4pPFaFjXVzjFN0ht4GVuoJQejy7t/RVrWzZCjdyVuSiOFTlG3SyB39zW5a95Q5EXUTg==}
+  /@bull-board/ui@5.2.0:
+    resolution: {integrity: sha512-f2sgs7AjOVch7tFhbmlVCkhZjJWboxwNxWEfAsIUd1WidUC+Ef5J02tpQvu7apzRtu5zcn8IiJtI5HFO6oKaCA==}
     dependencies:
-      '@bull-board/api': 5.1.2(@bull-board/ui@5.1.2)
+      '@bull-board/api': 5.2.0(@bull-board/ui@5.2.0)
     dev: false
 
   /@canvas/image-data@1.0.0:
@@ -3560,13 +4513,13 @@ packages:
       - supports-color
     dev: true
 
-  /@digitalbazaar/http-client@3.2.0:
-    resolution: {integrity: sha512-NhYXcWE/JDE7AnJikNX7q0S6zNuUPA2NuIoRdUpmvHlarjmRqyr6hIO3Awu2FxlUzbdiI1uzuWrZyB9mD1tTvw==}
+  /@digitalbazaar/http-client@3.4.1:
+    resolution: {integrity: sha512-Ahk1N+s7urkgj7WvvUND5f8GiWEPfUw0D41hdElaqLgu8wZScI8gdI0q+qWw5N1d35x7GCRH2uk9mi+Uzo9M3g==}
     engines: {node: '>=14.0'}
     dependencies:
-      ky: 0.30.0
-      ky-universal: 0.10.1(ky@0.30.0)
-      undici: 5.16.0
+      ky: 0.33.3
+      ky-universal: 0.11.0(ky@0.33.3)
+      undici: 5.22.1
     transitivePeerDependencies:
       - web-streams-polyfill
     dev: false
@@ -3778,6 +4731,16 @@ packages:
       eslint-visitor-keys: 3.4.1
     dev: true
 
+  /@eslint-community/eslint-utils@4.4.0(eslint@8.41.0):
+    resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+    dependencies:
+      eslint: 8.41.0
+      eslint-visitor-keys: 3.4.1
+    dev: true
+
   /@eslint-community/regexpp@4.5.0:
     resolution: {integrity: sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ==}
     engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
@@ -3805,6 +4768,11 @@ packages:
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dev: true
 
+  /@eslint/js@8.41.0:
+    resolution: {integrity: sha512-LxcyMGxwmTh2lY9FwHPGWOHmYFCZvbrFCBZL4FzSSsxsRPuhrYUg/49/0KDfW8tnIEaEHtfmn6+NPN+1DqaNmA==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    dev: true
+
   /@fal-works/esbuild-plugin-global-externals@2.1.2:
     resolution: {integrity: sha512-cEee/Z+I12mZcFJshKcCqC8tuX5hG3s+d+9nZ3LabqKF1vKdF41B92pJVCBggjAGORAeOzyyDDKrZwIkLffeOQ==}
     dev: true
@@ -3843,8 +4811,8 @@ packages:
       fastify-plugin: 4.5.0
     dev: false
 
-  /@fastify/cors@8.2.1:
-    resolution: {integrity: sha512-2H2MrDD3ea7g707g1CNNLWb9/tYbmw7HS+MK2SDcgjxwzbOFR93JortelTIO8DBFsZqFtEpKNxiZfSyrGgYcbw==}
+  /@fastify/cors@8.3.0:
+    resolution: {integrity: sha512-oj9xkka2Tg0MrwuKhsSUumcAkfp2YCnKxmFEusi01pjk1YrdDsuSYTHXEelWNW+ilSy/ApZq0c2SvhKrLX0H1g==}
     dependencies:
       fastify-plugin: 4.5.0
       mnemonist: 0.39.5
@@ -3864,12 +4832,12 @@ packages:
       fast-json-stringify: 5.7.0
     dev: false
 
-  /@fastify/http-proxy@9.1.0:
+  /@fastify/http-proxy@9.1.0(bufferutil@4.0.7)(utf-8-validate@6.0.3):
     resolution: {integrity: sha512-vgHCTDKOqLB437zQJiLWFFnsrYfFZ6Lfwu/xXQoKqRUKIPDt+xG6LBRtf8s5MNqfFVoTE7kw1U/0qdRGDsMp4Q==}
     dependencies:
       '@fastify/reply-from': 9.0.1
       fastify-plugin: 4.5.0
-      ws: 8.13.0
+      ws: 8.13.0(bufferutil@4.0.7)(utf-8-validate@6.0.3)
     transitivePeerDependencies:
       - bufferutil
       - utf-8-validate
@@ -3910,8 +4878,8 @@ packages:
       mime: 3.0.0
     dev: false
 
-  /@fastify/static@6.10.1:
-    resolution: {integrity: sha512-DNnG+5QenQcTQw37qk0/191STThnN6SbU+2XMpWtpYR3gQUfUvMax14jTT/jqNINNbCkQJaKMnPtpFPKo4/68g==}
+  /@fastify/static@6.10.2:
+    resolution: {integrity: sha512-UoaMvIHSBLCZBYOVZwFRYqX2ufUhd7FFMYGDeSf0Z+D8jhYtwljjmuQGuanUP8kS4y/ZEV1a8mfLha3zNwsnnQ==}
     dependencies:
       '@fastify/accept-negotiator': 1.0.0
       '@fastify/send': 2.0.1
@@ -3965,6 +4933,7 @@ packages:
 
   /@ioredis/commands@1.2.0:
     resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==}
+    dev: false
 
   /@istanbuljs/load-nyc-config@1.1.0:
     resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==}
@@ -3987,7 +4956,7 @@ packages:
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
       '@jest/types': 29.5.0
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
       chalk: 4.1.2
       jest-message-util: 29.5.0
       jest-util: 29.5.0
@@ -4008,14 +4977,14 @@ packages:
       '@jest/test-result': 29.5.0
       '@jest/transform': 29.5.0
       '@jest/types': 29.5.0
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
       ansi-escapes: 4.3.2
       chalk: 4.1.2
       ci-info: 3.7.1
       exit: 0.1.2
       graceful-fs: 4.2.11
       jest-changed-files: 29.5.0
-      jest-config: 29.5.0(@types/node@20.1.3)
+      jest-config: 29.5.0(@types/node@20.2.5)
       jest-haste-map: 29.5.0
       jest-message-util: 29.5.0
       jest-regex-util: 29.4.3
@@ -4049,7 +5018,7 @@ packages:
     dependencies:
       '@jest/fake-timers': 29.5.0
       '@jest/types': 29.5.0
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
       jest-mock: 29.5.0
     dev: true
 
@@ -4075,8 +5044,8 @@ packages:
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
       '@jest/types': 29.5.0
-      '@sinonjs/fake-timers': 10.0.2
-      '@types/node': 20.1.3
+      '@sinonjs/fake-timers': 10.2.0
+      '@types/node': 20.2.5
       jest-message-util: 29.5.0
       jest-mock: 29.5.0
       jest-util: 29.5.0
@@ -4109,7 +5078,7 @@ packages:
       '@jest/transform': 29.5.0
       '@jest/types': 29.5.0
       '@jridgewell/trace-mapping': 0.3.17
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
       chalk: 4.1.2
       collect-v8-coverage: 1.0.1
       exit: 0.1.2
@@ -4178,7 +5147,7 @@ packages:
     resolution: {integrity: sha512-8vbeZWqLJOvHaDfeMuoHITGKSz5qWc9u04lnWrQE3VyuSw604PzQM824ZeX9XSjUCeDiE3GuxZe5UKa8J61NQw==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
-      '@babel/core': 7.21.3
+      '@babel/core': 7.22.1
       '@jest/types': 29.5.0
       '@jridgewell/trace-mapping': 0.3.17
       babel-plugin-istanbul: 6.1.1
@@ -4203,7 +5172,7 @@ packages:
     dependencies:
       '@types/istanbul-lib-coverage': 2.0.4
       '@types/istanbul-reports': 3.0.1
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
       '@types/yargs': 16.0.5
       chalk: 4.1.2
     dev: true
@@ -4215,12 +5184,12 @@ packages:
       '@jest/schemas': 29.4.3
       '@types/istanbul-lib-coverage': 2.0.4
       '@types/istanbul-reports': 3.0.1
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
       '@types/yargs': 17.0.19
       chalk: 4.1.2
     dev: true
 
-  /@joshwooding/vite-plugin-react-docgen-typescript@0.2.1(typescript@5.0.4)(vite@4.3.5):
+  /@joshwooding/vite-plugin-react-docgen-typescript@0.2.1(typescript@5.1.3)(vite@4.3.9):
     resolution: {integrity: sha512-ou4ZJSXMMWHqGS4g8uNRbC5TiTWxAgQZiVucoUrOCWuPrTbkpJbmVyIi9jU72SBry7gQtuMEDp4YR8EEXAg7VQ==}
     peerDependencies:
       typescript: '>= 4.3.x'
@@ -4232,17 +5201,9 @@ packages:
       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.0.4)
-      typescript: 5.0.4
-      vite: 4.3.5(@types/node@20.1.3)(sass@1.62.1)
-    dev: true
-
-  /@jridgewell/gen-mapping@0.1.1:
-    resolution: {integrity: sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==}
-    engines: {node: '>=6.0.0'}
-    dependencies:
-      '@jridgewell/set-array': 1.1.2
-      '@jridgewell/sourcemap-codec': 1.4.14
+      react-docgen-typescript: 2.2.2(typescript@5.1.3)
+      typescript: 5.1.3
+      vite: 4.3.9(@types/node@20.2.5)(sass@1.62.1)
     dev: true
 
   /@jridgewell/gen-mapping@0.3.2:
@@ -4306,7 +5267,7 @@ packages:
       nopt: 5.0.0
       npmlog: 5.0.1
       rimraf: 3.0.2
-      semver: 7.5.0
+      semver: 7.5.1
       tar: 6.1.13
     transitivePeerDependencies:
       - encoding
@@ -4381,46 +5342,52 @@ packages:
       os-filter-obj: 2.0.0
     dev: false
 
-  /@msgpackr-extract/msgpackr-extract-darwin-arm64@2.2.0:
-    resolution: {integrity: sha512-Z9LFPzfoJi4mflGWV+rv7o7ZbMU5oAU9VmzCgL240KnqDW65Y2HFCT3MW06/ITJSnbVLacmcEJA8phywK7JinQ==}
+  /@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.2:
+    resolution: {integrity: sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ==}
     cpu: [arm64]
     os: [darwin]
     requiresBuild: true
+    dev: false
     optional: true
 
-  /@msgpackr-extract/msgpackr-extract-darwin-x64@2.2.0:
-    resolution: {integrity: sha512-vq0tT8sjZsy4JdSqmadWVw6f66UXqUCabLmUVHZwUFzMgtgoIIQjT4VVRHKvlof3P/dMCkbMJ5hB1oJ9OWHaaw==}
+  /@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.2:
+    resolution: {integrity: sha512-lwriRAHm1Yg4iDf23Oxm9n/t5Zpw1lVnxYU3HnJPTi2lJRkKTrps1KVgvL6m7WvmhYVt/FIsssWay+k45QHeuw==}
     cpu: [x64]
     os: [darwin]
     requiresBuild: true
+    dev: false
     optional: true
 
-  /@msgpackr-extract/msgpackr-extract-linux-arm64@2.2.0:
-    resolution: {integrity: sha512-hlxxLdRmPyq16QCutUtP8Tm6RDWcyaLsRssaHROatgnkOxdleMTgetf9JsdncL8vLh7FVy/RN9i3XR5dnb9cRA==}
+  /@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.2:
+    resolution: {integrity: sha512-FU20Bo66/f7He9Fp9sP2zaJ1Q8L9uLPZQDub/WlUip78JlPeMbVL8546HbZfcW9LNciEXc8d+tThSJjSC+tmsg==}
     cpu: [arm64]
     os: [linux]
     requiresBuild: true
+    dev: false
     optional: true
 
-  /@msgpackr-extract/msgpackr-extract-linux-arm@2.2.0:
-    resolution: {integrity: sha512-SaJ3Qq4lX9Syd2xEo9u3qPxi/OB+5JO/ngJKK97XDpa1C587H9EWYO6KD8995DAjSinWvdHKRrCOXVUC5fvGOg==}
+  /@msgpackr-extract/msgpackr-extract-linux-arm@3.0.2:
+    resolution: {integrity: sha512-MOI9Dlfrpi2Cuc7i5dXdxPbFIgbDBGgKR5F2yWEa6FVEtSWncfVNKW5AKjImAQ6CZlBK9tympdsZJ2xThBiWWA==}
     cpu: [arm]
     os: [linux]
     requiresBuild: true
+    dev: false
     optional: true
 
-  /@msgpackr-extract/msgpackr-extract-linux-x64@2.2.0:
-    resolution: {integrity: sha512-94y5PJrSOqUNcFKmOl7z319FelCLAE0rz/jPCWS+UtdMZvpa4jrQd+cJPQCLp2Fes1yAW/YUQj/Di6YVT3c3Iw==}
+  /@msgpackr-extract/msgpackr-extract-linux-x64@3.0.2:
+    resolution: {integrity: sha512-gsWNDCklNy7Ajk0vBBf9jEx04RUxuDQfBse918Ww+Qb9HCPoGzS+XJTLe96iN3BVK7grnLiYghP/M4L8VsaHeA==}
     cpu: [x64]
     os: [linux]
     requiresBuild: true
+    dev: false
     optional: true
 
-  /@msgpackr-extract/msgpackr-extract-win32-x64@2.2.0:
-    resolution: {integrity: sha512-XrC0JzsqQSvOyM3t04FMLO6z5gCuhPE6k4FXuLK5xf52ZbdvcFe1yBmo7meCew9B8G2f0T9iu9t3kfTYRYROgA==}
+  /@msgpackr-extract/msgpackr-extract-win32-x64@3.0.2:
+    resolution: {integrity: sha512-O+6Gs8UeDbyFpbSh2CPEz/UOrrdWPTBYNblZK5CxxLisYt4kGX3Sc+czffFonyjiGSq3jWLwJS/CCJc7tBr4sQ==}
     cpu: [x64]
     os: [win32]
     requiresBuild: true
+    dev: false
     optional: true
 
   /@mswjs/cookies@0.2.2:
@@ -4455,8 +5422,8 @@ packages:
       tar-fs: 2.1.1
     dev: true
 
-  /@nestjs/common@9.4.0(reflect-metadata@0.1.13)(rxjs@7.8.1):
-    resolution: {integrity: sha512-RUcVAQsEF4WPrmzFXEOUfZnPwrLTe1UVlzXTlSyfqfqbdWDPKDGlIPVelBLfc5/+RRUQ0I5iE4+CQvpCmkqldw==}
+  /@nestjs/common@9.4.2(reflect-metadata@0.1.13)(rxjs@7.8.1):
+    resolution: {integrity: sha512-sea+qZnbD5x3YWZDVQT/wbVJ2NiABaM1tyZTLuW9hpkcM2KFA96xKtK3VaCxyz49zoXIgSOefsyK7HuUMCe27Q==}
     peerDependencies:
       cache-manager: <=5
       class-transformer: '*'
@@ -4474,12 +5441,12 @@ packages:
       iterare: 1.2.1
       reflect-metadata: 0.1.13
       rxjs: 7.8.1
-      tslib: 2.5.0
+      tslib: 2.5.2
       uid: 2.0.2
     dev: false
 
-  /@nestjs/core@9.4.0(@nestjs/common@9.4.0)(reflect-metadata@0.1.13)(rxjs@7.8.1):
-    resolution: {integrity: sha512-yTLryCgFD0462wPe4HIzhyTcDgibt8Stfwb5YzcX7Ma0NM4m8uBIpcPG109KBubp8ZmV85e5mw4rl20qLQQVsQ==}
+  /@nestjs/core@9.4.2(@nestjs/common@9.4.2)(reflect-metadata@0.1.13)(rxjs@7.8.1):
+    resolution: {integrity: sha512-S5K9GTpjBqEJtu5VxRsVaaGEBZ1bkY+Ht4+2hqZSKsI+rzcEB5hcvR+5KiMsMY1VGYvlZ99lxYz72p4h8B0mKw==}
     requiresBuild: true
     peerDependencies:
       '@nestjs/common': ^9.0.0
@@ -4496,21 +5463,21 @@ packages:
       '@nestjs/websockets':
         optional: true
     dependencies:
-      '@nestjs/common': 9.4.0(reflect-metadata@0.1.13)(rxjs@7.8.1)
+      '@nestjs/common': 9.4.2(reflect-metadata@0.1.13)(rxjs@7.8.1)
       '@nuxtjs/opencollective': 0.3.2
       fast-safe-stringify: 2.1.1
       iterare: 1.2.1
       path-to-regexp: 3.2.0
       reflect-metadata: 0.1.13
       rxjs: 7.8.1
-      tslib: 2.5.0
+      tslib: 2.5.2
       uid: 2.0.2
     transitivePeerDependencies:
       - encoding
     dev: false
 
-  /@nestjs/testing@9.4.0(@nestjs/common@9.4.0)(@nestjs/core@9.4.0):
-    resolution: {integrity: sha512-xZWp363P4otcebg++gSjUcdCfTK0RorORzyFq3aLaSAQOlq8kxfFDRIKzEATR4aOUfqTMMsAA8lhnMJWf35N6A==}
+  /@nestjs/testing@9.4.2(@nestjs/common@9.4.2)(@nestjs/core@9.4.2):
+    resolution: {integrity: sha512-4WZPJz85zLVZkhmWYq+Unr43MixISelg/TyuX1YFZYOeukIN+O6fRtAAPIKLqRQsiY0rE/h8FAEbYGWhNrRfSA==}
     peerDependencies:
       '@nestjs/common': ^9.0.0
       '@nestjs/core': ^9.0.0
@@ -4522,9 +5489,9 @@ packages:
       '@nestjs/platform-express':
         optional: true
     dependencies:
-      '@nestjs/common': 9.4.0(reflect-metadata@0.1.13)(rxjs@7.8.1)
-      '@nestjs/core': 9.4.0(@nestjs/common@9.4.0)(reflect-metadata@0.1.13)(rxjs@7.8.1)
-      tslib: 2.5.0
+      '@nestjs/common': 9.4.2(reflect-metadata@0.1.13)(rxjs@7.8.1)
+      '@nestjs/core': 9.4.2(@nestjs/common@9.4.2)(reflect-metadata@0.1.13)(rxjs@7.8.1)
+      tslib: 2.5.2
     dev: false
 
   /@nodelib/fs.scandir@2.1.5:
@@ -4550,12 +5517,13 @@ packages:
     engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
     dependencies:
       '@gar/promisify': 1.1.3
-      semver: 7.5.0
+      semver: 7.5.1
     dev: false
 
   /@npmcli/move-file@2.0.1:
     resolution: {integrity: sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==}
     engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
+    deprecated: This functionality has been moved to @npmcli/fs
     dependencies:
       mkdirp: 1.0.4
       rimraf: 3.0.2
@@ -4653,7 +5621,7 @@ packages:
       '@redis/client': 1.4.2
     dev: true
 
-  /@rollup/plugin-alias@5.0.0(rollup@3.21.6):
+  /@rollup/plugin-alias@5.0.0(rollup@3.23.0):
     resolution: {integrity: sha512-l9hY5chSCjuFRPsnRm16twWBiSApl2uYFLsepQYwtBuAxNMQ/1dJqADld40P0Jkqm65GRTLy/AC6hnpVebtLsA==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
@@ -4662,11 +5630,11 @@ packages:
       rollup:
         optional: true
     dependencies:
-      rollup: 3.21.6
+      rollup: 3.23.0
       slash: 4.0.0
     dev: false
 
-  /@rollup/plugin-json@6.0.0(rollup@3.21.6):
+  /@rollup/plugin-json@6.0.0(rollup@3.23.0):
     resolution: {integrity: sha512-i/4C5Jrdr1XUarRhVu27EEwjt4GObltD7c+MkCIpO2QIbojw8MUs+CCTqOphQi3Qtg1FLmYt+l+6YeoIf51J7w==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
@@ -4675,11 +5643,11 @@ packages:
       rollup:
         optional: true
     dependencies:
-      '@rollup/pluginutils': 5.0.2(rollup@3.21.6)
-      rollup: 3.21.6
+      '@rollup/pluginutils': 5.0.2(rollup@3.23.0)
+      rollup: 3.23.0
     dev: false
 
-  /@rollup/plugin-replace@5.0.2(rollup@3.21.6):
+  /@rollup/plugin-replace@5.0.2(rollup@3.23.0):
     resolution: {integrity: sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
@@ -4688,9 +5656,9 @@ packages:
       rollup:
         optional: true
     dependencies:
-      '@rollup/pluginutils': 5.0.2(rollup@3.21.6)
+      '@rollup/pluginutils': 5.0.2(rollup@3.23.0)
       magic-string: 0.27.0
-      rollup: 3.21.6
+      rollup: 3.23.0
     dev: false
 
   /@rollup/pluginutils@4.2.1:
@@ -4701,7 +5669,7 @@ packages:
       picomatch: 2.3.1
     dev: true
 
-  /@rollup/pluginutils@5.0.2(rollup@3.21.6):
+  /@rollup/pluginutils@5.0.2(rollup@3.23.0):
     resolution: {integrity: sha512-pTd9rIsP92h+B6wWwFbW8RkZv4hiR/xKsqre4SIuAOaOEQRxi0lqLke9k2/7WegC85GgUs9pjmOjCUi3In4vwA==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
@@ -4713,7 +5681,7 @@ packages:
       '@types/estree': 1.0.1
       estree-walker: 2.0.2
       picomatch: 2.3.1
-      rollup: 3.21.6
+      rollup: 3.23.0
     dev: false
 
   /@rushstack/node-core-library@3.58.0(@types/node@18.16.3):
@@ -4791,11 +5759,17 @@ packages:
     resolution: {integrity: sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==}
     dependencies:
       type-detect: 4.0.8
+    dev: true
 
-  /@sinonjs/fake-timers@10.0.2:
-    resolution: {integrity: sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==}
+  /@sinonjs/commons@3.0.0:
+    resolution: {integrity: sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==}
     dependencies:
-      '@sinonjs/commons': 2.0.0
+      type-detect: 4.0.8
+
+  /@sinonjs/fake-timers@10.2.0:
+    resolution: {integrity: sha512-OPwQlEdg40HAj5KNF8WW6q2KG4Z+cBCZb3m4ninfTZKaBmbIJodviQsDBoYMPHkOyJJMHnOJo5j2+LKDOhOACg==}
+    dependencies:
+      '@sinonjs/commons': 3.0.0
 
   /@sinonjs/fake-timers@9.1.2:
     resolution: {integrity: sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==}
@@ -4836,8 +5810,8 @@ packages:
       lodash.values: 4.3.0
       object-hash: 3.0.0
       packageurl-js: 1.0.1
-      semver: 7.5.0
-      tslib: 2.5.0
+      semver: 7.5.1
+      tslib: 2.5.2
     dev: false
 
   /@snyk/graphlib@2.1.9-patch.3:
@@ -4864,8 +5838,8 @@ packages:
     resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==}
     dev: false
 
-  /@storybook/addon-actions@7.0.10(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-U8c7n918/mOjXnc1Iu/sglbK+ryC4xoyjWE5SG/68h0+sHb1rioNq7leAi24mCP6jNwNI5Q7TWtuvflOGxQDKQ==}
+  /@storybook/addon-actions@7.0.18(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-3M5AU/ZD79YP88vKlFezIJbIoG/II7wCixUBTmwiC3BeQZDuVsqPNl8eiP6MGT70xwyx7a993lSM5f5N5W93vg==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -4875,14 +5849,14 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.0.10
-      '@storybook/components': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-events': 7.0.10
+      '@storybook/client-logger': 7.0.18
+      '@storybook/components': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-events': 7.0.18
       '@storybook/global': 5.0.0
-      '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.0.10
-      '@storybook/theming': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.0.10
+      '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.0.18
+      '@storybook/theming': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.0.18
       dequal: 2.0.3
       lodash: 4.17.21
       polished: 4.2.2
@@ -4895,8 +5869,8 @@ packages:
       uuid: 9.0.0
     dev: true
 
-  /@storybook/addon-backgrounds@7.0.10(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-QtOxXO9hKtwBjjdLXWYKp4HpcpNOrLfc71dn78XbMKyCkQRlYtVe8GNk/++70UQtFfKCEJIB0hTHrPmSjDJE5A==}
+  /@storybook/addon-backgrounds@7.0.18(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-cPQy1Ot7Urf4hQz+xnF1YKrqSyR0DRwozBmF+sGzceACWmueFl0CifYZC8RSmaiIyVh0RyWPxZ9F/eT67NX2lA==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -4906,22 +5880,22 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.0.10
-      '@storybook/components': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-events': 7.0.10
+      '@storybook/client-logger': 7.0.18
+      '@storybook/components': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-events': 7.0.18
       '@storybook/global': 5.0.0
-      '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.0.10
-      '@storybook/theming': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.0.10
+      '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.0.18
+      '@storybook/theming': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.0.18
       memoizerific: 1.11.3
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
       ts-dedent: 2.2.0
     dev: true
 
-  /@storybook/addon-controls@7.0.10(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-j5UiPH8ZJx0ieUoIeV3iENlsIRDuQCeg3gTlLD668sebx8KHOCSJygh0Zvg1sTUUGSIbenhWaPlqfaW6ShKFWQ==}
+  /@storybook/addon-controls@7.0.18(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-mD6DE52CCMKugXk2Uab0QxwgfE76kFJroxASmnePnXUNWfP9EZJpJXYE3cyyBbmZuxa46VHDGGEGXQWRl4+Eog==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -4931,15 +5905,15 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/blocks': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/client-logger': 7.0.10
-      '@storybook/components': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-common': 7.0.10
-      '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/node-logger': 7.0.10
-      '@storybook/preview-api': 7.0.10
-      '@storybook/theming': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.0.10
+      '@storybook/blocks': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/client-logger': 7.0.18
+      '@storybook/components': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-common': 7.0.18
+      '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/node-logger': 7.0.18
+      '@storybook/preview-api': 7.0.18
+      '@storybook/theming': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.0.18
       lodash: 4.17.21
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
@@ -4948,29 +5922,29 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/addon-docs@7.0.10(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-1tUsJ+fuBqk4oTOBLabyPQeQYiRKs9I6+soY7dG8jN15Bxe/Ey2giNpqUkA3xAIuqS75ydRVKmsfQvILu2nLjg==}
+  /@storybook/addon-docs@7.0.18(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-oq+ZN5809gIRdTZQIpeK1F8BJtL1/VWo9rWvl6ymVOL/Xzdgd7AOfKf9Y99X35RcxAGysRIHLGJjF4bgLoY1Aw==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
     dependencies:
-      '@babel/core': 7.21.3
-      '@babel/plugin-transform-react-jsx': 7.21.0(@babel/core@7.21.3)
+      '@babel/core': 7.22.1
+      '@babel/plugin-transform-react-jsx': 7.21.0(@babel/core@7.22.1)
       '@jest/transform': 29.5.0
       '@mdx-js/react': 2.3.0(react@18.2.0)
-      '@storybook/blocks': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/client-logger': 7.0.10
-      '@storybook/components': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/csf-plugin': 7.0.10
-      '@storybook/csf-tools': 7.0.10
+      '@storybook/blocks': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/client-logger': 7.0.18
+      '@storybook/components': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/csf-plugin': 7.0.18
+      '@storybook/csf-tools': 7.0.18
       '@storybook/global': 5.0.0
       '@storybook/mdx2-csf': 1.0.0
-      '@storybook/node-logger': 7.0.10
-      '@storybook/postinstall': 7.0.10
-      '@storybook/preview-api': 7.0.10
-      '@storybook/react-dom-shim': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/theming': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.0.10
+      '@storybook/node-logger': 7.0.18
+      '@storybook/postinstall': 7.0.18
+      '@storybook/preview-api': 7.0.18
+      '@storybook/react-dom-shim': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/theming': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.0.18
       fs-extra: 11.1.0
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
@@ -4981,25 +5955,25 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/addon-essentials@7.0.10(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-nOeUtNbfLXOlgGqqqlsYC9gcYSrAxABBo8jHYiZg3xaEB9+cnKjCKK8VxrqJiR002AG5JZvi+uHeAauM94fkkQ==}
+  /@storybook/addon-essentials@7.0.18(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-0XXu7xhtRefA1WxxorKk6BWeeB+7gQ+r2+bG1zQEfBgDYPR06YbPw4H79IZ8JiR97aJRsZBK5UUhOZMDrc5zcQ==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
     dependencies:
-      '@storybook/addon-actions': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/addon-backgrounds': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/addon-controls': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/addon-docs': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/addon-highlight': 7.0.10
-      '@storybook/addon-measure': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/addon-outline': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/addon-toolbars': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/addon-viewport': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-common': 7.0.10
-      '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/node-logger': 7.0.10
-      '@storybook/preview-api': 7.0.10
+      '@storybook/addon-actions': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/addon-backgrounds': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/addon-controls': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/addon-docs': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/addon-highlight': 7.0.18
+      '@storybook/addon-measure': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/addon-outline': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/addon-toolbars': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/addon-viewport': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-common': 7.0.18
+      '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/node-logger': 7.0.18
+      '@storybook/preview-api': 7.0.18
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
       ts-dedent: 2.2.0
@@ -5007,16 +5981,16 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/addon-highlight@7.0.10:
-    resolution: {integrity: sha512-TohDxElSu7JrSvhLRZAwtNk/7Ot626wvlODwklocE4kbtn1fulFoUlRta7NImBGX554LITDFRy0m4R1rRQ9OfQ==}
+  /@storybook/addon-highlight@7.0.18:
+    resolution: {integrity: sha512-a3nfUhbu6whoDclIZSV/fzLj132tNNjV05ENTpuN3JpLoMd3+obDUWzeQUs9TetK4RBRN3ewM7sIMEI4oBpgmg==}
     dependencies:
-      '@storybook/core-events': 7.0.10
+      '@storybook/core-events': 7.0.18
       '@storybook/global': 5.0.0
-      '@storybook/preview-api': 7.0.10
+      '@storybook/preview-api': 7.0.18
     dev: true
 
-  /@storybook/addon-interactions@7.0.10(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-7hdFgoetQblwysYwRlmC5fbMVDb6lIM6le1pVEmRci6X44Gr2Xe5w2s6h5bTp4tMpNS1CFKjru9kF/TqfK46wA==}
+  /@storybook/addon-interactions@7.0.18(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-V3OD5lSj6Te6Kzc//2k2S79dLPk6Zu1pAbqWAN4RrdXyKj6YCiZ666GmVdiaG+24Qp5UuMeAkd1D05osJlOteA==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -5026,16 +6000,16 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.0.10
-      '@storybook/components': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-common': 7.0.10
-      '@storybook/core-events': 7.0.10
+      '@storybook/client-logger': 7.0.18
+      '@storybook/components': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-common': 7.0.18
+      '@storybook/core-events': 7.0.18
       '@storybook/global': 5.0.0
-      '@storybook/instrumenter': 7.0.10
-      '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.0.10
-      '@storybook/theming': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.0.10
+      '@storybook/instrumenter': 7.0.18
+      '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.0.18
+      '@storybook/theming': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.0.18
       jest-mock: 27.5.1
       polished: 4.2.2
       react: 18.2.0
@@ -5045,8 +6019,8 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/addon-links@7.0.10(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-Odhe0eICqW9X2yyIjtOVb23cKXJ2WRxPHBm5oYf6hBBoXXK7EJicwyQSJLxJyHK7r1PeAnFxSGlNrO3w7JULjg==}
+  /@storybook/addon-links@7.0.18(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-xEwflt7bp9FRoZVeqPGb6d3s2Gh+/jaSmnyIxMxrBy2oovKIqu9ptolqz1AhjFOXfaLs9c2RAmJUuFZJtETLxA==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -5056,22 +6030,22 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.0.10
-      '@storybook/core-events': 7.0.10
+      '@storybook/client-logger': 7.0.18
+      '@storybook/core-events': 7.0.18
       '@storybook/csf': 0.1.0
       '@storybook/global': 5.0.0
-      '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.0.10
-      '@storybook/router': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.0.10
+      '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.0.18
+      '@storybook/router': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.0.18
       prop-types: 15.8.1
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
       ts-dedent: 2.2.0
     dev: true
 
-  /@storybook/addon-measure@7.0.10(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-70BQT8PM6r3qjXDgXuN5mx9CBq9dYTdEgR1tlZ8FbMi8B8tB1oZJD0o6tfGM3r8WjdI0sTwX70ic5pv9Ma/MiA==}
+  /@storybook/addon-measure@7.0.18(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-iu8vQpGOA+CFYbWR6QNshj20o33OQ/xcTbp5P4U6xGYDUliUBbwJ2KLxcKlmIeBanBrBdz0jPFtHwY4dM1ZdKw==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -5081,19 +6055,19 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.0.10
-      '@storybook/components': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-events': 7.0.10
+      '@storybook/client-logger': 7.0.18
+      '@storybook/components': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-events': 7.0.18
       '@storybook/global': 5.0.0
-      '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.0.10
-      '@storybook/types': 7.0.10
+      '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.0.18
+      '@storybook/types': 7.0.18
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
     dev: true
 
-  /@storybook/addon-outline@7.0.10(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-Aakoc+II7orfgUDmjgMbnSp5HZS/47z0NeRAfh+FP4fxL0lFd9vmaeIXWYo1DjJqdEFfvlSLd8aS9Ltb+souMw==}
+  /@storybook/addon-outline@7.0.18(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-3vNWO7ezo6GIvidbz8JxFrKtfVEoTQN7tnZx+wpqmCF8ihBORewkpeMUnvgb9ZKjD0X7gE8eQvvG8KKWcyHDBQ==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -5103,20 +6077,20 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.0.10
-      '@storybook/components': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-events': 7.0.10
+      '@storybook/client-logger': 7.0.18
+      '@storybook/components': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-events': 7.0.18
       '@storybook/global': 5.0.0
-      '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.0.10
-      '@storybook/types': 7.0.10
+      '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.0.18
+      '@storybook/types': 7.0.18
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
       ts-dedent: 2.2.0
     dev: true
 
-  /@storybook/addon-storysource@7.0.10(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-5anwqnBOcHDI/EB3F2q3Vs/JN+vCBRr8UVqnKS8NqN3BrpJ4q7jUeQ2cA0Q2/aAmdHJn9FLh/Cgx7aTO+6iC2w==}
+  /@storybook/addon-storysource@7.0.18(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-ejOO9d9Aa63DCXCoXtCsOJLefdbrsvSAEV9wU2HfT+EOIS1dq/SV+ZtIMAvdAf4whB42K+pEzB5hLE2+zCK9PQ==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -5126,13 +6100,13 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.0.10
-      '@storybook/components': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.0.10
-      '@storybook/router': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/source-loader': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/theming': 7.0.10(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/client-logger': 7.0.18
+      '@storybook/components': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.0.18
+      '@storybook/router': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/source-loader': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/theming': 7.0.18(react-dom@18.2.0)(react@18.2.0)
       estraverse: 5.3.0
       prop-types: 15.8.1
       react: 18.2.0
@@ -5140,8 +6114,8 @@ packages:
       react-syntax-highlighter: 15.5.0(react@18.2.0)
     dev: true
 
-  /@storybook/addon-toolbars@7.0.10(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-U4a45CDw4SZzrgboYVMgxyiD7Ejud1kSz2lyS+J3fGTZGXq2+tmJS/2oNrLJlSH7v8629lVUbKnFxsP0HbfShg==}
+  /@storybook/addon-toolbars@7.0.18(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-mwhq962o0WloHAeFjJ6BXO2nzdTo5KE2fqawPpqcB2lwXP6tvaA2tDWwgntjPCHejqWTS+ZTdO4/1xrMhWYt/g==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -5151,17 +6125,17 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.0.10
-      '@storybook/components': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.0.10
-      '@storybook/theming': 7.0.10(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/client-logger': 7.0.18
+      '@storybook/components': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.0.18
+      '@storybook/theming': 7.0.18(react-dom@18.2.0)(react@18.2.0)
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
     dev: true
 
-  /@storybook/addon-viewport@7.0.10(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-Ck9sdCg3T8ChXoxYL5IEi+ZUOwdH6Je5XeK4kRVq+Ar+Ytm5CFTGJCCZjI6biroTnuJCUefaV2K5NUZoHkZI+A==}
+  /@storybook/addon-viewport@7.0.18(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-aVVLBsWXfGDX3z1pc93LWWdG5RUoJbGL/JJPMZGwXdwWpP8V3OBl8D8bgPymyg+MgwhSRZZDDGgnJaVGGwZ6bQ==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -5171,49 +6145,49 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.0.10
-      '@storybook/components': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-events': 7.0.10
+      '@storybook/client-logger': 7.0.18
+      '@storybook/components': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-events': 7.0.18
       '@storybook/global': 5.0.0
-      '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.0.10
-      '@storybook/theming': 7.0.10(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.0.18
+      '@storybook/theming': 7.0.18(react-dom@18.2.0)(react@18.2.0)
       memoizerific: 1.11.3
       prop-types: 15.8.1
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
     dev: true
 
-  /@storybook/addons@7.0.10(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-RRtozbB0JovZdLgTgC03kOjNh/5sAN77VHZFC5aK/Y9Hz2A0C6V4w/SqTt0382skSllcGMcrHjB1k06BlxlZ8A==}
+  /@storybook/addons@7.0.18(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-+j9ItxWoVzarbllaV4WRaJpDM3P2aC5O6F3cPn4YkG/unb6HOs11WLAqFbzZnLYZNAFvWS8PYEAtqs1BxG66YQ==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
     dependencies:
-      '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.0.10
-      '@storybook/types': 7.0.10
+      '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.0.18
+      '@storybook/types': 7.0.18
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
     dev: true
 
-  /@storybook/blocks@7.0.10(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-OqXuN1x2TjXgrOqGSaD0Vz8iCqmLjiPkrQpWMD7bToFpHH0dpmcrzzRhLhxgJLN2CAzyr98IYIkUgXX9Da1neA==}
+  /@storybook/blocks@7.0.18(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-HLsuzmUdVIeFXEP5v5vyjnEePRNYjzltwTjCKQhHAlt8/aQZmREiIMOfoMoAa1Rd+On8Ib2DUd2cN10VS18H8A==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
     dependencies:
-      '@storybook/channels': 7.0.10
-      '@storybook/client-logger': 7.0.10
-      '@storybook/components': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-events': 7.0.10
+      '@storybook/channels': 7.0.18
+      '@storybook/client-logger': 7.0.18
+      '@storybook/components': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-events': 7.0.18
       '@storybook/csf': 0.1.0
-      '@storybook/docs-tools': 7.0.10
+      '@storybook/docs-tools': 7.0.18
       '@storybook/global': 5.0.0
-      '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.0.10
-      '@storybook/theming': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.0.10
+      '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.0.18
+      '@storybook/theming': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.0.18
       '@types/lodash': 4.14.191
       color-convert: 2.0.1
       dequal: 2.0.3
@@ -5231,13 +6205,13 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/builder-manager@7.0.10:
-    resolution: {integrity: sha512-izCVE4JEbDVN5DPkX/Ym1PifAJKlheBvXKmGXGklnJQ2l+TEuvesPbOmVFNuu7ptJAFw4JO5n2KAo9+a5FRwiw==}
+  /@storybook/builder-manager@7.0.18:
+    resolution: {integrity: sha512-yFMm3xuYkyg2hS1uz3CkvyvLzK7qJsDPVEh7lew8GiJK1Xx8cc+FnAOlRTjWNxvhfiT296wAMCTPWv7LeoSgqQ==}
     dependencies:
       '@fal-works/esbuild-plugin-global-externals': 2.1.2
-      '@storybook/core-common': 7.0.10
-      '@storybook/manager': 7.0.10
-      '@storybook/node-logger': 7.0.10
+      '@storybook/core-common': 7.0.18
+      '@storybook/manager': 7.0.18
+      '@storybook/node-logger': 7.0.18
       '@types/ejs': 3.1.2
       '@types/find-cache-dir': 3.2.1
       '@yarnpkg/esbuild-plugin-pnp': 3.0.0-rc.15(esbuild@0.17.18)
@@ -5254,8 +6228,8 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/builder-vite@7.0.10(typescript@5.0.4)(vite@4.3.5):
-    resolution: {integrity: sha512-tKY2QnHni10TE3+Sy2wfR7h4FuribR849VBpDI/LcwtRkCgoOBfMCdEnAKMWyU6qAlY+9KDSOQq9SDTu3WZGOg==}
+  /@storybook/builder-vite@7.0.18(typescript@5.1.3)(vite@4.3.9):
+    resolution: {integrity: sha512-Qze6/PwUJq+z776dBoG5uinAEVZyPalZIaU/VOWpTrN8L9FQbL0+HDrZU2E/BMNW+ZfnSjF3V2USLyiutsC1Tw==}
     peerDependencies:
       '@preact/preset-vite': '*'
       typescript: '>= 4.3.x'
@@ -5269,16 +6243,16 @@ packages:
       vite-plugin-glimmerx:
         optional: true
     dependencies:
-      '@storybook/channel-postmessage': 7.0.10
-      '@storybook/channel-websocket': 7.0.10
-      '@storybook/client-logger': 7.0.10
-      '@storybook/core-common': 7.0.10
-      '@storybook/csf-plugin': 7.0.10
+      '@storybook/channel-postmessage': 7.0.18
+      '@storybook/channel-websocket': 7.0.18
+      '@storybook/client-logger': 7.0.18
+      '@storybook/core-common': 7.0.18
+      '@storybook/csf-plugin': 7.0.18
       '@storybook/mdx2-csf': 1.0.0
-      '@storybook/node-logger': 7.0.10
-      '@storybook/preview': 7.0.10
-      '@storybook/preview-api': 7.0.10
-      '@storybook/types': 7.0.10
+      '@storybook/node-logger': 7.0.18
+      '@storybook/preview': 7.0.18
+      '@storybook/preview-api': 7.0.18
+      '@storybook/types': 7.0.18
       browser-assert: 1.2.1
       es-module-lexer: 0.9.3
       express: 4.18.2
@@ -5288,19 +6262,19 @@ packages:
       magic-string: 0.27.0
       remark-external-links: 8.0.0
       remark-slug: 6.1.0
-      rollup: 3.21.6
-      typescript: 5.0.4
-      vite: 4.3.5(@types/node@20.1.3)(sass@1.62.1)
+      rollup: 3.23.0
+      typescript: 5.1.3
+      vite: 4.3.9(@types/node@20.2.5)(sass@1.62.1)
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@storybook/channel-postmessage@7.0.10:
-    resolution: {integrity: sha512-Y5ZSp9WYH3HznQ2rrGN78Y5fYM16Bfvyn3iKy5QD38gsQk1gTrra1osIZ0X+lk3sep14D4oW4QMW3DCSrn0Big==}
+  /@storybook/channel-postmessage@7.0.18:
+    resolution: {integrity: sha512-rpwBH5ANdPnugS6+7xG9qHSoS+aPSEnBxDKsONWFubfMTTXQuFkf/793rBbxGkoINdqh8kSdKOM2rIty6e9cmQ==}
     dependencies:
-      '@storybook/channels': 7.0.10
-      '@storybook/client-logger': 7.0.10
-      '@storybook/core-events': 7.0.10
+      '@storybook/channels': 7.0.18
+      '@storybook/client-logger': 7.0.18
+      '@storybook/core-events': 7.0.18
       '@storybook/global': 5.0.0
       qs: 6.11.1
       telejson: 7.0.4
@@ -5328,18 +6302,17 @@ packages:
       telejson: 7.0.4
     dev: true
 
-  /@storybook/channel-websocket@7.0.10:
-    resolution: {integrity: sha512-WXueykS71YxEqKlsIbbmmA6QSChEePffzqs7QASUpHhi21IDqmtq2HhAqYWlQptyqSWYdv6wxrOqwe6z4zakLA==}
+  /@storybook/channel-websocket@7.0.18:
+    resolution: {integrity: sha512-QYsZIfe23NN4i+oIdPKHaYBehk3a/HYk57a+M2oR3Frmv8IOqc/e31uH+xx5NxnjHrTJj7Y80ZJw6EKB682S6w==}
     dependencies:
-      '@storybook/channels': 7.0.10
-      '@storybook/client-logger': 7.0.10
+      '@storybook/channels': 7.0.18
+      '@storybook/client-logger': 7.0.18
       '@storybook/global': 5.0.0
       telejson: 7.0.4
     dev: true
 
-  /@storybook/channels@7.0.10:
-    resolution: {integrity: sha512-hdPaGV3W7s6MkVcg33S5hmkVgqXC16AA7H0ID9MROiU1lQzolKhSqCs2iVfGPQfmWzEJeqWQoEXU7dmCclRM0w==}
-    dev: true
+  /@storybook/channels@7.0.18:
+    resolution: {integrity: sha512-rkA7ea0M3+dWS+71iHJdiZ5R2QuIdiVg0CgyLJHDagc1qej7pEVNhMWtppeq+X5Pwp9nkz8ZTQ7aCjTf6th0/A==}
 
   /@storybook/channels@7.0.2:
     resolution: {integrity: sha512-qkI8mFy9c8mxN2f01etayKhCaauL6RAsxRzbX1/pKj6UqhHWqqUbtHwymrv4hG5qDYjV1e9pd7ae5eNF8Kui0g==}
@@ -5349,20 +6322,20 @@ packages:
     resolution: {integrity: sha512-+34cVmrXZ3lb1s5tDK+OWd5HLtEPSUMas0VKFJ0k9LBpFlVl9aiCZBJRvSYmWL7beauUfa+HSmJgjlD6228ChQ==}
     dev: true
 
-  /@storybook/cli@7.0.10:
-    resolution: {integrity: sha512-FhtE6Yrk7MMa9AgLb3MTmqiQ3IlWHjjrj7Vcj2QM6BcP342xSe7C1d+V6+tYX/oPOEB3Upz+PKNrju1iHxurQQ==}
+  /@storybook/cli@7.0.18:
+    resolution: {integrity: sha512-9n4J4thiCUsGSXiRc6ZysqYUaCMCrpu0/qgC+5ngfFRuMmZgUV0y5+0fmaOhT2XjsonTTgucizO82i7+ottCVg==}
     hasBin: true
     dependencies:
-      '@babel/core': 7.21.3
-      '@babel/preset-env': 7.21.4(@babel/core@7.21.3)
+      '@babel/core': 7.22.1
+      '@babel/preset-env': 7.21.4(@babel/core@7.22.1)
       '@ndelangen/get-tarball': 3.0.7
-      '@storybook/codemod': 7.0.10
-      '@storybook/core-common': 7.0.10
-      '@storybook/core-server': 7.0.10
-      '@storybook/csf-tools': 7.0.10
-      '@storybook/node-logger': 7.0.10
-      '@storybook/telemetry': 7.0.10
-      '@storybook/types': 7.0.10
+      '@storybook/codemod': 7.0.18
+      '@storybook/core-common': 7.0.18
+      '@storybook/core-server': 7.0.18
+      '@storybook/csf-tools': 7.0.18
+      '@storybook/node-logger': 7.0.18
+      '@storybook/telemetry': 7.0.18
+      '@storybook/types': 7.0.18
       '@types/semver': 7.5.0
       boxen: 5.1.2
       chalk: 4.1.2
@@ -5380,11 +6353,12 @@ packages:
       globby: 11.1.0
       jscodeshift: 0.14.0(@babel/preset-env@7.21.4)
       leven: 3.1.0
+      ora: 5.4.1
       prettier: 2.8.8
       prompts: 2.4.2
       puppeteer-core: 2.1.1
       read-pkg-up: 7.0.1
-      semver: 7.5.0
+      semver: 7.5.1
       shelljs: 0.8.5
       simple-update-notifier: 1.1.0
       strip-json-comments: 3.1.1
@@ -5398,8 +6372,8 @@ packages:
       - utf-8-validate
     dev: true
 
-  /@storybook/client-logger@7.0.10:
-    resolution: {integrity: sha512-hb8tO+w28ErzjEw69ERMtZT81Xyg835FQjH6Y42ejoGcBA9Z0W6RZmx4RgkcIUOlYXkU6lSnNVne9gXodV4/Hw==}
+  /@storybook/client-logger@7.0.18:
+    resolution: {integrity: sha512-uKgFdVedYoRDZBVrE1IBdWNHDFln1IxWEeI+7ZiNSQwREG9swHpU5Fa8DceclM/oLjJRuzG1jFzv+XZY8894+Q==}
     dependencies:
       '@storybook/global': 5.0.0
     dev: true
@@ -5416,16 +6390,16 @@ packages:
       '@storybook/global': 5.0.0
     dev: true
 
-  /@storybook/codemod@7.0.10:
-    resolution: {integrity: sha512-BnPknLV3wnaSk0azjFBAWLVfwgUHtFvVk9I6y1idIaQhc0nnegKoa0jTxWigthftZK/Pv9yG3gxG7o7O4KcChQ==}
+  /@storybook/codemod@7.0.18:
+    resolution: {integrity: sha512-+9XFns29e8FpPLsqA8ZCQ3mNnIIKD3QnqGYkbkCVKi/G1fomvVQsIfsnkrYv5SobTbz29B4aNWxAaeSnO7/OGg==}
     dependencies:
       '@babel/core': 7.21.3
       '@babel/preset-env': 7.21.4(@babel/core@7.21.3)
       '@babel/types': 7.21.5
       '@storybook/csf': 0.1.0
-      '@storybook/csf-tools': 7.0.10
-      '@storybook/node-logger': 7.0.10
-      '@storybook/types': 7.0.10
+      '@storybook/csf-tools': 7.0.18
+      '@storybook/node-logger': 7.0.18
+      '@storybook/types': 7.0.18
       cross-spawn: 7.0.3
       globby: 11.1.0
       jscodeshift: 0.14.0(@babel/preset-env@7.21.4)
@@ -5436,17 +6410,17 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/components@7.0.10(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-jdGiVP+a3XqoGpKkDFGt4g2cgb23aLfMS/RhnuhT7FK6hGh7WFfuuqx4QqQHx4VZCdXIWVIzszaCdGCs7AsW2w==}
+  /@storybook/components@7.0.18(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-Jn1CbF9UAKt8BVaZtuhmthpcZ02VMaCFXR0ISfDXCpiMKnylmpP0+WfXcoKLzz6yS+EW8EW5S9+Qq8xgQY8H7A==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
     dependencies:
-      '@storybook/client-logger': 7.0.10
+      '@storybook/client-logger': 7.0.18
       '@storybook/csf': 0.1.0
       '@storybook/global': 5.0.0
-      '@storybook/theming': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.0.10
+      '@storybook/theming': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.0.18
       memoizerific: 1.11.3
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
@@ -5454,18 +6428,18 @@ packages:
       util-deprecate: 1.0.2
     dev: true
 
-  /@storybook/core-client@7.0.10:
-    resolution: {integrity: sha512-sN/TKB7QHWP6josdjyNtoqDXihROPtgvzo5+akfW6+S7hhfsQ4BJd09nkBqEX9E7z81blmFFDUOU3a8bQbPdKQ==}
+  /@storybook/core-client@7.0.18:
+    resolution: {integrity: sha512-ueExRZx6fd9LRssgdhDJ0bL4Ir2RrbXzJz/kjIT2KgYY3l7jkhe0dpT3bOgGKjQt0f7XMFU24t/r7aDLGMB+2Q==}
     dependencies:
-      '@storybook/client-logger': 7.0.10
-      '@storybook/preview-api': 7.0.10
+      '@storybook/client-logger': 7.0.18
+      '@storybook/preview-api': 7.0.18
     dev: true
 
-  /@storybook/core-common@7.0.10:
-    resolution: {integrity: sha512-AAYXixukGlpMy8XoSM8cTfcyQ6ijBq5q50xNTj/ssTbGnGSk6POgtoJZf6g8XtS0OxsFXBSxuBuMBBBbKtoztw==}
+  /@storybook/core-common@7.0.18:
+    resolution: {integrity: sha512-HZAB1NIK/Yv0x9poyzqYcue2tx39+MAF1mbHgGy+JJZRerO2fRShgo8f8VPH9ChbFCoJ7isL5wNhgGdg9kp2kA==}
     dependencies:
-      '@storybook/node-logger': 7.0.10
-      '@storybook/types': 7.0.10
+      '@storybook/node-logger': 7.0.18
+      '@storybook/types': 7.0.18
       '@types/node': 16.18.16
       '@types/pretty-hrtime': 1.0.1
       chalk: 4.1.2
@@ -5487,8 +6461,8 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/core-events@7.0.10:
-    resolution: {integrity: sha512-OyBqhxVQOdI78Vgv6nKwXOdIVNChyfktpdxQZP1rz9MpO6MrqMaGAUL7k8xQMQAVx0VY+dAMYZB3bnyN2IC8FA==}
+  /@storybook/core-events@7.0.18:
+    resolution: {integrity: sha512-7gxHBQDezdKOeq/u1LL80Bwjfcwsv7XOS3yWQElcgqp+gLaYB6OwwgtkCB2yV6a6l4nep9IdPWE8G3TxIzn9xw==}
     dev: true
 
   /@storybook/core-events@7.0.2:
@@ -5499,23 +6473,23 @@ packages:
     resolution: {integrity: sha512-kGrtjlYtjd4iTVk+Phb4CymZaVkB+MGscKAgcO8gfgJ/Q/gq8HQLVZSIzeoCDcDSHOGlBzbg2WVtdHIHhCKlOQ==}
     dev: true
 
-  /@storybook/core-server@7.0.10:
-    resolution: {integrity: sha512-KFCc3turPed8tiC5IUKTV7oObVmFckMP1XqO7zec2g2NlGQsN83DRso+BA1wpV/bb8AD1NJDU6LJnyN3KKdi1Q==}
+  /@storybook/core-server@7.0.18:
+    resolution: {integrity: sha512-zGSGYSoCaSXM28OYKW7zsmpo8VU1icubXLRgdF21fbMhFN1WVS+bPA5+gSkAMf8acq5RNM8uSKskh7E2YDVEqA==}
     dependencies:
       '@aw-web-design/x-default-browser': 1.4.88
       '@discoveryjs/json-ext': 0.5.7
-      '@storybook/builder-manager': 7.0.10
-      '@storybook/core-common': 7.0.10
-      '@storybook/core-events': 7.0.10
+      '@storybook/builder-manager': 7.0.18
+      '@storybook/core-common': 7.0.18
+      '@storybook/core-events': 7.0.18
       '@storybook/csf': 0.1.0
-      '@storybook/csf-tools': 7.0.10
+      '@storybook/csf-tools': 7.0.18
       '@storybook/docs-mdx': 0.1.0
       '@storybook/global': 5.0.0
-      '@storybook/manager': 7.0.10
-      '@storybook/node-logger': 7.0.10
-      '@storybook/preview-api': 7.0.10
-      '@storybook/telemetry': 7.0.10
-      '@storybook/types': 7.0.10
+      '@storybook/manager': 7.0.18
+      '@storybook/node-logger': 7.0.18
+      '@storybook/preview-api': 7.0.18
+      '@storybook/telemetry': 7.0.18
+      '@storybook/types': 7.0.18
       '@types/detect-port': 1.3.2
       '@types/node': 16.18.16
       '@types/node-fetch': 2.6.2
@@ -5532,18 +6506,18 @@ packages:
       globby: 11.1.0
       ip: 2.0.0
       lodash: 4.17.21
-      node-fetch: 2.6.7
+      node-fetch: 2.6.11
       open: 8.4.2
       pretty-hrtime: 1.0.3
       prompts: 2.4.2
       read-pkg-up: 7.0.1
-      semver: 7.5.0
+      semver: 7.5.1
       serve-favicon: 2.5.0
       telejson: 7.0.4
       ts-dedent: 2.2.0
       util-deprecate: 1.0.2
       watchpack: 2.4.0
-      ws: 8.13.0
+      ws: 8.13.0(bufferutil@4.0.7)(utf-8-validate@6.0.3)
     transitivePeerDependencies:
       - bufferutil
       - encoding
@@ -5551,48 +6525,46 @@ packages:
       - utf-8-validate
     dev: true
 
-  /@storybook/csf-plugin@7.0.10:
-    resolution: {integrity: sha512-uUty5rLs6O32tJaXIne2/42UxFL3eaRCDgtAoAkGxbUPa/FLYpO0rYtqF2OG9MagwXU7+As5RlLkDLeYAvUzlQ==}
+  /@storybook/csf-plugin@7.0.18:
+    resolution: {integrity: sha512-Cr/Qr4/H4JIYgbbmDjQIYuqjp6nOaZga73R3KZcuClk27B90sI2ADegMYvORgbFgSkwweNQjgak6hLoOyogAhw==}
     dependencies:
-      '@storybook/csf-tools': 7.0.10
+      '@storybook/csf-tools': 7.0.18
       unplugin: 0.10.2
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@storybook/csf-tools@7.0.10:
-    resolution: {integrity: sha512-sl/995jq03HD7/Q9cb54h0glgt7JLGTkfikSlB35NGMEkgEXEswDmpQHA/TbzUYylIxuAwTKghwMqL3IwSSHwA==}
+  /@storybook/csf-tools@7.0.18:
+    resolution: {integrity: sha512-0IJ2qdrxleTl67FUzsEvGcy96CY0OKyERE33tAsLNbvWcabdJKpLHP+rJwbsCw4z6IlS+kkmEffeFf5qRPTwkQ==}
     dependencies:
       '@babel/generator': 7.21.3
-      '@babel/parser': 7.21.8
+      '@babel/parser': 7.21.9
       '@babel/traverse': 7.21.3
       '@babel/types': 7.21.5
       '@storybook/csf': 0.1.0
-      '@storybook/types': 7.0.10
+      '@storybook/types': 7.0.18
       fs-extra: 11.1.0
       recast: 0.23.1
       ts-dedent: 2.2.0
     transitivePeerDependencies:
       - supports-color
-    dev: true
 
   /@storybook/csf@0.1.0:
     resolution: {integrity: sha512-uk+jMXCZ8t38jSTHk2o5btI+aV2Ksbvl6DoOv3r6VaCM1KZqeuMwtwywIQdflkA8/6q/dKT8z8L+g8hC4GC3VQ==}
     dependencies:
       type-fest: 2.19.0
-    dev: true
 
   /@storybook/docs-mdx@0.1.0:
     resolution: {integrity: sha512-JDaBR9lwVY4eSH5W8EGHrhODjygPd6QImRbwjAuJNEnY0Vw4ie3bPkeGfnacB3OBW6u/agqPv2aRlR46JcAQLg==}
     dev: true
 
-  /@storybook/docs-tools@7.0.10:
-    resolution: {integrity: sha512-w3m7+LlQGI50i07XjiOzIfoap8rnmsrs8hXGUTodbs9vvLt8HBdUaapOGnYr/1BzA0YQJ7Nz2z1nTirQEphmsQ==}
+  /@storybook/docs-tools@7.0.18:
+    resolution: {integrity: sha512-H95dW2DquGQ75ZVrFjvznPdCxT0eW6esDnemzLJB61KitcYZrWRavfrZzFtUcpzIa84OgY5pllFYt636v11LHQ==}
     dependencies:
-      '@babel/core': 7.21.3
-      '@storybook/core-common': 7.0.10
-      '@storybook/preview-api': 7.0.10
-      '@storybook/types': 7.0.10
+      '@babel/core': 7.22.1
+      '@storybook/core-common': 7.0.18
+      '@storybook/preview-api': 7.0.18
+      '@storybook/types': 7.0.18
       '@types/doctrine': 0.0.3
       doctrine: 3.0.0
       lodash: 4.17.21
@@ -5603,21 +6575,21 @@ packages:
   /@storybook/expect@27.5.2-0:
     resolution: {integrity: sha512-cP99mhWN/JeCp7VSIiymvj5tmuMY050iFohvp8Zq+kewKsBSZ6/qpTJAGCCZk6pneTcp4S0Fm5BSqyxzbyJ3gw==}
     dependencies:
-      '@types/jest': 29.5.1
+      '@types/jest': 29.5.2
     dev: true
 
   /@storybook/global@5.0.0:
     resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==}
     dev: true
 
-  /@storybook/instrumenter@7.0.10:
-    resolution: {integrity: sha512-Z+kIidnxaq3tneUnIKB2d0DCqb4lwUdOS/AC43LNvd9C6BWYgj89cIPdLDTNhOWa0ZiEju7wTS+K/3uMvcHZ4w==}
+  /@storybook/instrumenter@7.0.18:
+    resolution: {integrity: sha512-fyQxeuVC0H+w3oyTuByE95xnAQ+l/WhUBVkHV2X+PWjg9vg9Y9JmrbNWynlvz5HLFlsY3qAWJh+ciVRVSvY5Jw==}
     dependencies:
-      '@storybook/channels': 7.0.10
-      '@storybook/client-logger': 7.0.10
-      '@storybook/core-events': 7.0.10
+      '@storybook/channels': 7.0.18
+      '@storybook/client-logger': 7.0.18
+      '@storybook/core-events': 7.0.18
       '@storybook/global': 5.0.0
-      '@storybook/preview-api': 7.0.10
+      '@storybook/preview-api': 7.0.18
     dev: true
 
   /@storybook/instrumenter@7.0.2:
@@ -5649,41 +6621,41 @@ packages:
       jest-mock: 27.5.1
     dev: true
 
-  /@storybook/manager-api@7.0.10(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-Dik73GKUX9QCFOvukTXjZoZX0G6n/LrRMkwLggb28E9m8iFt2ivWvF9MVvyRoDffR9VP5t53+nV5fqxqpXWoQw==}
+  /@storybook/manager-api@7.0.18(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-anQkm09twL96YkKGXHa+LI0+yMaY6Jxs1lRaetHdMlIqN4VHBHhizHaMgtGfH6xCTuO3WdrKTN7cZii5RH7PBQ==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
     dependencies:
-      '@storybook/channels': 7.0.10
-      '@storybook/client-logger': 7.0.10
-      '@storybook/core-events': 7.0.10
+      '@storybook/channels': 7.0.18
+      '@storybook/client-logger': 7.0.18
+      '@storybook/core-events': 7.0.18
       '@storybook/csf': 0.1.0
       '@storybook/global': 5.0.0
-      '@storybook/router': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/theming': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.0.10
+      '@storybook/router': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/theming': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.0.18
       dequal: 2.0.3
       lodash: 4.17.21
       memoizerific: 1.11.3
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
-      semver: 7.5.0
+      semver: 7.5.1
       store2: 2.14.2
       telejson: 7.0.4
       ts-dedent: 2.2.0
     dev: true
 
-  /@storybook/manager@7.0.10:
-    resolution: {integrity: sha512-cFMOOXmcRx1tN50TqC2huOsF91fAvNM82wTDnAbT2FtA+ZHFHNyE1PgWgiKDDepzOpKaG+FfT4bJcQAaAfYOBg==}
+  /@storybook/manager@7.0.18:
+    resolution: {integrity: sha512-hasb8XDmkT9lyX2cwb3Xg0ngcNQ1QCNHKurl2YJtXowb1CvawGKokhnVUTso15NCnurolDyw/Wqka1sagfm+Mg==}
     dev: true
 
   /@storybook/mdx2-csf@1.0.0:
     resolution: {integrity: sha512-dBAnEL4HfxxJmv7LdEYUoZlQbWj9APZNIbOaq0tgF8XkxiIbzqvgB0jhL/9UOrysSDbQWBiCRTu2wOVxedGfmw==}
     dev: true
 
-  /@storybook/node-logger@7.0.10:
-    resolution: {integrity: sha512-btCCreucTApi7EP84jbfqlFQZDD4Kz9lFLftalZA7nskDZW6i8reNNykTU2Y22TQvlbpqs5kL1N1cEsbG3vepw==}
+  /@storybook/node-logger@7.0.18:
+    resolution: {integrity: sha512-cIeKEBvELtoVP/5UeQ01GJWZ7wM69/9Q+R5uOtNQBlwWFcCD6AVFWMRqq7ObMvdJG/okhXSF+sDetb+BF3zvdw==}
     dependencies:
       '@types/npmlog': 4.1.4
       chalk: 4.1.2
@@ -5691,20 +6663,20 @@ packages:
       pretty-hrtime: 1.0.3
     dev: true
 
-  /@storybook/postinstall@7.0.10:
-    resolution: {integrity: sha512-SVPKGuuvfn1MceLWzYHGbpP77+waLKXglAH4Gkdoa2mKdk3XO45Zn8OhwwNzHuP698boMNaGaB/utBLBpkXMMg==}
+  /@storybook/postinstall@7.0.18:
+    resolution: {integrity: sha512-ObIwAK2UiYhXN/7UifISQgBoH5jnyxh6T8kvCw83YhC78SDOPNgIGjToJECizJ7iubtqAWtCfCT5TrGEpyLGbg==}
     dev: true
 
-  /@storybook/preview-api@7.0.10:
-    resolution: {integrity: sha512-URj2YJKbs8hc6JZQ3aA+MmjB4hTSzGZAVFVs3kLUEuaQPDbU1RT5GKxedwF5zlMnkZQPNoaUtopN3z7aF+SKFQ==}
+  /@storybook/preview-api@7.0.18:
+    resolution: {integrity: sha512-xxtC0gPGMn/DbwvS4ZuJaBwfFNsjUCf0yLYHFrNe6fxncbvcLZ550RuyUwYuIRfsiKrlgfa3QmmCa4JM/JesHQ==}
     dependencies:
-      '@storybook/channel-postmessage': 7.0.10
-      '@storybook/channels': 7.0.10
-      '@storybook/client-logger': 7.0.10
-      '@storybook/core-events': 7.0.10
+      '@storybook/channel-postmessage': 7.0.18
+      '@storybook/channels': 7.0.18
+      '@storybook/client-logger': 7.0.18
+      '@storybook/core-events': 7.0.18
       '@storybook/csf': 0.1.0
       '@storybook/global': 5.0.0
-      '@storybook/types': 7.0.10
+      '@storybook/types': 7.0.18
       '@types/qs': 6.9.7
       dequal: 2.0.3
       lodash: 4.17.21
@@ -5755,12 +6727,12 @@ packages:
       util-deprecate: 1.0.2
     dev: true
 
-  /@storybook/preview@7.0.10:
-    resolution: {integrity: sha512-IQX8v7OpKeo2Oqeyxo6/uSRys+dJ7zms12jViJWGzx9fg6IchV/iNtf4TBrF3Z2JBNKovk03kICAMHTpZuz9Qg==}
+  /@storybook/preview@7.0.18:
+    resolution: {integrity: sha512-L53p2eo8G12U6tp7hD3mk5tdWFXLvdEyV9e7a1x9bw1LfH15K/bp8lO6U/W1kkpse7+rqWBqoTjJC1Ktm5Sxog==}
     dev: true
 
-  /@storybook/react-dom-shim@7.0.10(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-NLuE2Be/BGmXHufwLp1Gje+IsTb0HWvwzHlci2U430WgwGU8fsTPNgALMrwCpqN9o1KnrRGpysQEoyIYStQBdg==}
+  /@storybook/react-dom-shim@7.0.18(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-O1FRypR8q1katjbznnxI+NtALd2gaWa7KnTwbIDf+ddZltXHMZ8xMiEGEtAMrfXlIuqIr9UvmLRfKZC/ysuA+g==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -5769,25 +6741,25 @@ packages:
       react-dom: 18.2.0(react@18.2.0)
     dev: true
 
-  /@storybook/react-vite@7.0.10(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4)(vite@4.3.5):
-    resolution: {integrity: sha512-ZEwRpMEJAQtMruG/XGha52XHkb3extXudWT5SoXOcfiRy9eK7Y3oJwHR8KHNH3LE+LrRh7c+D53k7eMudRtsNA==}
+  /@storybook/react-vite@7.0.18(react-dom@18.2.0)(react@18.2.0)(typescript@5.1.3)(vite@4.3.9):
+    resolution: {integrity: sha512-rxJwp/b0dPazn15xLIeRgwrdZGWmoqoLhU7Mm+AXKToXvbe77i2bjHhkFbz34dpKFtD0i/ajcZSpmsxpxfB0HA==}
     engines: {node: '>=16'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
       vite: ^3.0.0 || ^4.0.0
     dependencies:
-      '@joshwooding/vite-plugin-react-docgen-typescript': 0.2.1(typescript@5.0.4)(vite@4.3.5)
+      '@joshwooding/vite-plugin-react-docgen-typescript': 0.2.1(typescript@5.1.3)(vite@4.3.9)
       '@rollup/pluginutils': 4.2.1
-      '@storybook/builder-vite': 7.0.10(typescript@5.0.4)(vite@4.3.5)
-      '@storybook/react': 7.0.10(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4)
-      '@vitejs/plugin-react': 3.1.0(vite@4.3.5)
+      '@storybook/builder-vite': 7.0.18(typescript@5.1.3)(vite@4.3.9)
+      '@storybook/react': 7.0.18(react-dom@18.2.0)(react@18.2.0)(typescript@5.1.3)
+      '@vitejs/plugin-react': 3.1.0(vite@4.3.9)
       ast-types: 0.14.2
       magic-string: 0.27.0
       react: 18.2.0
       react-docgen: 6.0.0-alpha.3
       react-dom: 18.2.0(react@18.2.0)
-      vite: 4.3.5(@types/node@20.1.3)(sass@1.62.1)
+      vite: 4.3.9(@types/node@20.2.5)(sass@1.62.1)
     transitivePeerDependencies:
       - '@preact/preset-vite'
       - supports-color
@@ -5795,8 +6767,8 @@ packages:
       - vite-plugin-glimmerx
     dev: true
 
-  /@storybook/react@7.0.10(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4):
-    resolution: {integrity: sha512-/DDUGFz0bk5c/HCfSr7fL74rQc+3s317TDDKY6ZrgUzdIkze4D/TlAbWV78XV/ceeFNi1fLAUzGjFzuDwmVkJw==}
+  /@storybook/react@7.0.18(react-dom@18.2.0)(react@18.2.0)(typescript@5.1.3):
+    resolution: {integrity: sha512-lumUbHYeuL3qa4SZR9K2YC4UIt1hwW19GuI/6f2HEV5gR9QHHSJHg9HD9pjcxv4fQaiG81ACZ0Sg6lyUkcJvuQ==}
     engines: {node: '>=16.0.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -5806,13 +6778,13 @@ packages:
       typescript:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.0.10
-      '@storybook/core-client': 7.0.10
-      '@storybook/docs-tools': 7.0.10
+      '@storybook/client-logger': 7.0.18
+      '@storybook/core-client': 7.0.18
+      '@storybook/docs-tools': 7.0.18
       '@storybook/global': 5.0.0
-      '@storybook/preview-api': 7.0.10
-      '@storybook/react-dom-shim': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.0.10
+      '@storybook/preview-api': 7.0.18
+      '@storybook/react-dom-shim': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.0.18
       '@types/escodegen': 0.0.6
       '@types/estree': 0.0.51
       '@types/node': 16.18.16
@@ -5828,33 +6800,33 @@ packages:
       react-element-to-jsx-string: 15.0.0(react-dom@18.2.0)(react@18.2.0)
       ts-dedent: 2.2.0
       type-fest: 2.19.0
-      typescript: 5.0.4
+      typescript: 5.1.3
       util-deprecate: 1.0.2
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@storybook/router@7.0.10(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-Vq3nuyrGsvbPYhsaVu0TwtzX8Yb5TZYg7v5gY/uk1brSIk7Mvw64E8WF4TKNhPcWnlxNrfP9S96IZgT9iuuCpw==}
+  /@storybook/router@7.0.18(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-Mue4s/BnKgdYcsiW9yuvW3qL9k3AgYn5HIhnkBExAteyiUGdAca4IJFhArmGgFktgeLc4ecBQ7sgaCljApnbgg==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
     dependencies:
-      '@storybook/client-logger': 7.0.10
+      '@storybook/client-logger': 7.0.18
       memoizerific: 1.11.3
       qs: 6.11.1
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
     dev: true
 
-  /@storybook/source-loader@7.0.10(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-DtdYllq0piU6vgoVjsuPsWaGlhSOJgJr/kRovu5zltaZzdEOyQZ7e0zQmA4Py0h9jnGbg2fQG9zccofY3jUdJw==}
+  /@storybook/source-loader@7.0.18(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-n910+/rNJ3tCRUx3JJm/5ehjp5CK2WZg+KPRtG5a4AeVhQBdxsxw2D2pDYBWY1aFhJ+S4AZJOLIk9cdOMneA9g==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
     dependencies:
       '@storybook/csf': 0.1.0
-      '@storybook/types': 7.0.10
+      '@storybook/types': 7.0.18
       estraverse: 5.3.0
       lodash: 4.17.21
       prettier: 2.8.8
@@ -5862,11 +6834,11 @@ packages:
       react-dom: 18.2.0(react@18.2.0)
     dev: true
 
-  /@storybook/telemetry@7.0.10:
-    resolution: {integrity: sha512-0xlMECcSU2UnmpDTxKE/+pKpcW88fhxEZxh54yoA6NPpq6SGUN1r5ybUMffJCZ0JgaQ8HOc3Vxd13T3VXAMLXA==}
+  /@storybook/telemetry@7.0.18:
+    resolution: {integrity: sha512-JP5Z7lGU+oKjNmz2cZW5J7EerwyWBBPOU+NvvooZsymIx02ZvJ4ClmFtolJnBM7m4KoAy50JxV5NQWi+q8PicQ==}
     dependencies:
-      '@storybook/client-logger': 7.0.10
-      '@storybook/core-common': 7.0.10
+      '@storybook/client-logger': 7.0.18
+      '@storybook/core-common': 7.0.18
       chalk: 4.1.2
       detect-package-manager: 2.0.1
       fetch-retry: 5.0.4
@@ -5889,28 +6861,27 @@ packages:
       ts-dedent: 2.2.0
     dev: true
 
-  /@storybook/theming@7.0.10(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-kKxIMElOUAyIAJOlhU6NS6/F6KpZLWvfGnUYC5V4f5Rsu+lKnbWI/TJ1rCIooz2wZBQ6dv+fjA3sOh5K+LRh2w==}
+  /@storybook/theming@7.0.18(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-P1gMKa/mKQHIMq0sxBIwTzAcF6v/6hrc62YmkuV62vXu+8zNV2YWbRwywqm3Q6faZEadmb/bL9+z8whaKhCL/g==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
     dependencies:
       '@emotion/use-insertion-effect-with-fallbacks': 1.0.0(react@18.2.0)
-      '@storybook/client-logger': 7.0.10
+      '@storybook/client-logger': 7.0.18
       '@storybook/global': 5.0.0
       memoizerific: 1.11.3
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
     dev: true
 
-  /@storybook/types@7.0.10:
-    resolution: {integrity: sha512-mFktvN8PjjDFJSjck4spikmjtr0AwfOhcEtIf4UCmUD5JHgGppkQmvO6483nGcprSFcWOvD2uYGs8Wp32wG/MQ==}
+  /@storybook/types@7.0.18:
+    resolution: {integrity: sha512-qPop2CbvmX42/BX29YT9jIzW2TlMcMjAE+KCpcKLBiD1oT5DJ1fhMzpe6RW9HkMegkBxjWx54iamN4oHM/pwcQ==}
     dependencies:
-      '@storybook/channels': 7.0.10
+      '@storybook/channels': 7.0.18
       '@types/babel__core': 7.20.0
       '@types/express': 4.17.17
       file-system-cache: 2.0.2
-    dev: true
 
   /@storybook/types@7.0.2:
     resolution: {integrity: sha512-0OCt/kAexa8MCcljxA+yZxGMn0n2U2Ync0KxotItqNbKBKVkaLQUls0+IXTWSCpC/QJvNZ049jxUHHanNi/96w==}
@@ -5930,23 +6901,23 @@ packages:
       file-system-cache: 2.0.2
     dev: true
 
-  /@storybook/vue3-vite@7.0.10(react-dom@18.2.0)(react@18.2.0)(typescript@5.0.4)(vite@4.3.5)(vue@3.3.1):
-    resolution: {integrity: sha512-BbA6uLlNFIpSBW9UAJ9e96yCGGoMho0pogEbkzoRLdw/0OoqDqnRMue78CwW5eiIWXYjNZb3UwAyh9VgYqKk5g==}
+  /@storybook/vue3-vite@7.0.18(react-dom@18.2.0)(react@18.2.0)(typescript@5.1.3)(vite@4.3.9)(vue@3.3.4):
+    resolution: {integrity: sha512-dwkwBQRDUSvf44Z4ZDftusP6obuczPkApxALxsTczkbpOxK/13SXArlrKgyUaFrcqto9i2e8HbAYb7y1ymO3ig==}
     engines: {node: ^14.18 || >=16}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
       vite: ^3.0.0 || ^4.0.0
     dependencies:
-      '@storybook/builder-vite': 7.0.10(typescript@5.0.4)(vite@4.3.5)
-      '@storybook/core-server': 7.0.10
-      '@storybook/vue3': 7.0.10(vue@3.3.1)
-      '@vitejs/plugin-vue': 4.2.2(vite@4.3.5)(vue@3.3.1)
+      '@storybook/builder-vite': 7.0.18(typescript@5.1.3)(vite@4.3.9)
+      '@storybook/core-server': 7.0.18
+      '@storybook/vue3': 7.0.18(vue@3.3.4)
+      '@vitejs/plugin-vue': 4.2.3(vite@4.3.9)(vue@3.3.4)
       magic-string: 0.27.0
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
-      vite: 4.3.5(@types/node@20.1.3)(sass@1.62.1)
-      vue-docgen-api: 4.64.1(vue@3.3.1)
+      vite: 4.3.9(@types/node@20.2.5)(sass@1.62.1)
+      vue-docgen-api: 4.64.1(vue@3.3.4)
     transitivePeerDependencies:
       - '@preact/preset-vite'
       - bufferutil
@@ -5958,25 +6929,26 @@ packages:
       - vue
     dev: true
 
-  /@storybook/vue3@7.0.10(vue@3.3.1):
-    resolution: {integrity: sha512-B4DW/lR9Am06RJM3TGrIgIYzurG6tsgUX9EQ6rQRDFd4EWw1bskcG8MrNwFBBiDBnXe1frL4AdDidF47CFStNg==}
+  /@storybook/vue3@7.0.18(vue@3.3.4):
+    resolution: {integrity: sha512-++oC4Ee74ln9jPJSUnA6RWLxk5PNBGSP7lu71bA0b98MYsQ4GKliNEQf8lZmelSQy6nWoVHO0iyOhsKey7K3Ow==}
     engines: {node: '>=16.0.0'}
     peerDependencies:
       vue: ^3.0.0
     dependencies:
-      '@storybook/core-client': 7.0.10
-      '@storybook/docs-tools': 7.0.10
+      '@storybook/core-client': 7.0.18
+      '@storybook/docs-tools': 7.0.18
       '@storybook/global': 5.0.0
-      '@storybook/preview-api': 7.0.10
-      '@storybook/types': 7.0.10
+      '@storybook/preview-api': 7.0.18
+      '@storybook/types': 7.0.18
       ts-dedent: 2.2.0
       type-fest: 2.19.0
-      vue: 3.3.1
+      vue: 3.3.4
+      vue-component-type-helpers: 1.6.5
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@swc/cli@0.1.62(@swc/core@1.3.56)(chokidar@3.5.3):
+  /@swc/cli@0.1.62(@swc/core@1.3.61)(chokidar@3.5.3):
     resolution: {integrity: sha512-kOFLjKY3XH1DWLfXL1/B5MizeNorHR8wHKEi92S/Zi9Md/AK17KSqR8MgyRJ6C1fhKHvbBCl8wboyKAFXStkYw==}
     engines: {node: '>= 12.13'}
     hasBin: true
@@ -5988,11 +6960,11 @@ packages:
         optional: true
     dependencies:
       '@mole-inc/bin-wrapper': 8.0.1
-      '@swc/core': 1.3.56
+      '@swc/core': 1.3.61
       chokidar: 3.5.3
       commander: 7.2.0
       fast-glob: 3.2.12
-      semver: 7.5.0
+      semver: 7.5.1
       slash: 3.0.0
       source-map: 0.7.4
     dev: false
@@ -6016,6 +6988,14 @@ packages:
     requiresBuild: true
     optional: true
 
+  /@swc/core-darwin-arm64@1.3.61:
+    resolution: {integrity: sha512-Ra1CZIYYyIp/Y64VcKyaLjIPUwT83JmGduvHu8vhUZOvWV4dWL4s5DrcxQVaQJjjb7Z2N/IUYYS55US1TGnxZw==}
+    engines: {node: '>=10'}
+    cpu: [arm64]
+    os: [darwin]
+    requiresBuild: true
+    optional: true
+
   /@swc/core-darwin-x64@1.3.56:
     resolution: {integrity: sha512-VH5saqYFasdRXJy6RAT+MXm0+IjkMZvOkohJwUei+oA65cKJofQwrJ1jZro8yOJFYvUSI3jgNRGsdBkmo/4hMw==}
     engines: {node: '>=10'}
@@ -6024,6 +7004,14 @@ packages:
     requiresBuild: true
     optional: true
 
+  /@swc/core-darwin-x64@1.3.61:
+    resolution: {integrity: sha512-LUia75UByUFkYH1Ddw7IE0X9usNVGJ7aL6+cgOTju7P0dsU0f8h/OGc/GDfp1E4qnKxDCJE+GwDRLoi4SjIxpg==}
+    engines: {node: '>=10'}
+    cpu: [x64]
+    os: [darwin]
+    requiresBuild: true
+    optional: true
+
   /@swc/core-linux-arm-gnueabihf@1.3.56:
     resolution: {integrity: sha512-LWwPo6NnJkH01+ukqvkoNIOpMdw+Zundm4vBeicwyVrkP+mC3kwVfi03TUFpQUz3kRKdw/QEnxGTj+MouCPbtw==}
     engines: {node: '>=10'}
@@ -6032,6 +7020,14 @@ packages:
     requiresBuild: true
     optional: true
 
+  /@swc/core-linux-arm-gnueabihf@1.3.61:
+    resolution: {integrity: sha512-aalPlicYxHAn2PxNlo3JFEZkMXzCtUwjP27AgMqnfV4cSz7Omo56OtC+413e/kGyCH86Er9gJRQQsxNKP8Qbsg==}
+    engines: {node: '>=10'}
+    cpu: [arm]
+    os: [linux]
+    requiresBuild: true
+    optional: true
+
   /@swc/core-linux-arm64-gnu@1.3.56:
     resolution: {integrity: sha512-GzsUy/4egJ4cMlxbM+Ub7AMi5CKAc+pxBxrh8MUPQbyStW8jGgnQsJouTnGy0LHawtdEnsCOl6PcO6OgvktXuQ==}
     engines: {node: '>=10'}
@@ -6040,6 +7036,14 @@ packages:
     requiresBuild: true
     optional: true
 
+  /@swc/core-linux-arm64-gnu@1.3.61:
+    resolution: {integrity: sha512-9hGdsbQrYNPo1c7YzWF57yl17bsIuuEQi3I1fOFSv3puL3l5M/C/oCD0Bz6IdKh6mEDC5UNJE4LWtV1gFA995A==}
+    engines: {node: '>=10'}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    optional: true
+
   /@swc/core-linux-arm64-musl@1.3.56:
     resolution: {integrity: sha512-9gxL09BIiAv8zY0DjfnFf19bo8+P4T9tdhzPwcm+1yPJcY5yr1+YFWLNFzz01agtOj6VlZ2/wUJTaOfdjjtc+A==}
     engines: {node: '>=10'}
@@ -6048,6 +7052,14 @@ packages:
     requiresBuild: true
     optional: true
 
+  /@swc/core-linux-arm64-musl@1.3.61:
+    resolution: {integrity: sha512-mVmcNfFQRP4SYbGC08IPB3B9Xox+VpGIQqA3Qg7LMCcejLAQLi4Lfe8CDvvBPlQzXHso0Cv+BicJnQVKs8JLOA==}
+    engines: {node: '>=10'}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    optional: true
+
   /@swc/core-linux-x64-gnu@1.3.56:
     resolution: {integrity: sha512-n0ORNknl50vMRkll3BDO1E4WOqY6iISlPV1ZQCRLWQ6YQ2q8/WAryBxc2OAybcGHBUFkxyACpJukeU1QZ/9tNw==}
     engines: {node: '>=10'}
@@ -6056,6 +7068,14 @@ packages:
     requiresBuild: true
     optional: true
 
+  /@swc/core-linux-x64-gnu@1.3.61:
+    resolution: {integrity: sha512-ZkRHs7GEikN6JiVL1/stvq9BVHKrSKoRn9ulVK2hMr+mAGNOKm3Y06NSzOO+BWwMaFOgnO2dWlszCUICsQ0kpg==}
+    engines: {node: '>=10'}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    optional: true
+
   /@swc/core-linux-x64-musl@1.3.56:
     resolution: {integrity: sha512-r+D34WLAOAlJtfw1gaVWpHRwCncU9nzW9i7w9kSw4HpWYnHJOz54jLGSEmNsrhdTCz1VK2ar+V2ktFUsrlGlDA==}
     engines: {node: '>=10'}
@@ -6064,6 +7084,14 @@ packages:
     requiresBuild: true
     optional: true
 
+  /@swc/core-linux-x64-musl@1.3.61:
+    resolution: {integrity: sha512-zK7VqQ5JlK20+7fxI4AgvIUckeZyX0XIbliGXNMR3i+39SJq1vs9scYEmq8VnAfvNdMU5BG+DewbFJlMfCtkxQ==}
+    engines: {node: '>=10'}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    optional: true
+
   /@swc/core-win32-arm64-msvc@1.3.56:
     resolution: {integrity: sha512-29Yt75Is6X24z3x8h/xZC1HnDPkPpyLH9mDQiM6Cuc0I9mVr1XSriPEUB2N/awf5IE4SA8c+3IVq1DtKWbkJIw==}
     engines: {node: '>=10'}
@@ -6072,6 +7100,14 @@ packages:
     requiresBuild: true
     optional: true
 
+  /@swc/core-win32-arm64-msvc@1.3.61:
+    resolution: {integrity: sha512-e9kVVPk5iVNhO41TvLvcExDHn5iATQ5/M4U7/CdcC7s0fK19TKSEUqkdoTLIJvHBFhgR7w3JJSErfnauO0xXoA==}
+    engines: {node: '>=10'}
+    cpu: [arm64]
+    os: [win32]
+    requiresBuild: true
+    optional: true
+
   /@swc/core-win32-ia32-msvc@1.3.56:
     resolution: {integrity: sha512-mplp0zbYDrcHtfvkniXlXdB04e2qIjz2Gq/XHKr4Rnc6xVORJjjXF91IemXKpavx2oZYJws+LNJL7UFQ8jyCdQ==}
     engines: {node: '>=10'}
@@ -6080,6 +7116,14 @@ packages:
     requiresBuild: true
     optional: true
 
+  /@swc/core-win32-ia32-msvc@1.3.61:
+    resolution: {integrity: sha512-7cJULfa6HvKqvFh6M/f7mKiNRhE2AjgFUCZfdOuy5r8vbtpk+qBK94TXwaDjJYDUGKzDVZw/tJ1eN4Y9n9Ls/Q==}
+    engines: {node: '>=10'}
+    cpu: [ia32]
+    os: [win32]
+    requiresBuild: true
+    optional: true
+
   /@swc/core-win32-x64-msvc@1.3.56:
     resolution: {integrity: sha512-zp8MBnrw/bjdLenO/ifYzHrImSjKunqL0C2IF4LXYNRfcbYFh2NwobsVQMZ20IT0474lKRdlP8Oxdt+bHuXrzA==}
     engines: {node: '>=10'}
@@ -6088,6 +7132,14 @@ packages:
     requiresBuild: true
     optional: true
 
+  /@swc/core-win32-x64-msvc@1.3.61:
+    resolution: {integrity: sha512-Jx8S+21WcKF/wlhW+sYpystWUyymDTEsbBpOgBRpXZelakVcNBCIIYSZOKW/A9PwWTpu6S8yvbs9nUOzKiVPqA==}
+    engines: {node: '>=10'}
+    cpu: [x64]
+    os: [win32]
+    requiresBuild: true
+    optional: true
+
   /@swc/core@1.3.56:
     resolution: {integrity: sha512-yz/EeXT+PMZucUNrYceRUaTfuNS4IIu5EDZSOlvCEvm4jAmZi7CYH1B/kvzEzoAOzr7zkQiDPNJftcQXLkjbjA==}
     engines: {node: '>=10'}
@@ -6109,6 +7161,27 @@ packages:
       '@swc/core-win32-ia32-msvc': 1.3.56
       '@swc/core-win32-x64-msvc': 1.3.56
 
+  /@swc/core@1.3.61:
+    resolution: {integrity: sha512-p58Ltdjo7Yy8CU3zK0cp4/eAgy5qkHs35znGedqVGPiA67cuYZM63DuTfmyrOntMRwQnaFkMLklDAPCizDdDng==}
+    engines: {node: '>=10'}
+    requiresBuild: true
+    peerDependencies:
+      '@swc/helpers': ^0.5.0
+    peerDependenciesMeta:
+      '@swc/helpers':
+        optional: true
+    optionalDependencies:
+      '@swc/core-darwin-arm64': 1.3.61
+      '@swc/core-darwin-x64': 1.3.61
+      '@swc/core-linux-arm-gnueabihf': 1.3.61
+      '@swc/core-linux-arm64-gnu': 1.3.61
+      '@swc/core-linux-arm64-musl': 1.3.61
+      '@swc/core-linux-x64-gnu': 1.3.61
+      '@swc/core-linux-x64-musl': 1.3.61
+      '@swc/core-win32-arm64-msvc': 1.3.61
+      '@swc/core-win32-ia32-msvc': 1.3.61
+      '@swc/core-win32-x64-msvc': 1.3.61
+
   /@swc/jest@0.2.26(@swc/core@1.3.56):
     resolution: {integrity: sha512-7lAi7q7ShTO3E5Gt1Xqf3pIhRbERxR1DUxvtVa9WKzIB+HGQ7wZP5sYx86zqnaEoKKGhmOoZ7gyW0IRu8Br5+A==}
     engines: {npm: '>= 7.0.0'}
@@ -6120,14 +7193,25 @@ packages:
       jsonc-parser: 3.2.0
     dev: true
 
+  /@swc/jest@0.2.26(@swc/core@1.3.61):
+    resolution: {integrity: sha512-7lAi7q7ShTO3E5Gt1Xqf3pIhRbERxR1DUxvtVa9WKzIB+HGQ7wZP5sYx86zqnaEoKKGhmOoZ7gyW0IRu8Br5+A==}
+    engines: {npm: '>= 7.0.0'}
+    peerDependencies:
+      '@swc/core': '*'
+    dependencies:
+      '@jest/create-cache-key-function': 27.5.1
+      '@swc/core': 1.3.61
+      jsonc-parser: 3.2.0
+    dev: true
+
   /@swc/wasm@1.2.130:
     resolution: {integrity: sha512-rNcJsBxS70+pv8YUWwf5fRlWX6JoY/HJc25HD/F8m6Kv7XhJdqPPMhyX6TKkUBPAG7TWlZYoxa+rHAjPy4Cj3Q==}
     requiresBuild: true
     dev: false
     optional: true
 
-  /@syuilo/aiscript@0.13.2:
-    resolution: {integrity: sha512-1aqQSH6U+vV01UDUotXUEjIwJKcZZPASJyIJ9msxXRpSInPGJJ/q1kGkZMgSpVhzYFT7/QBxo0UC1ZVEOsrDTw==}
+  /@syuilo/aiscript@0.13.3:
+    resolution: {integrity: sha512-0YFlWA+7YhyRRsp+9Nl72SoSUg5ghskthjCdLvj4qdGyLedeyanKZWJlH2A9d47Nes03UYY8CRDsMHHv64IWcg==}
     dependencies:
       autobind-decorator: 2.4.0
       seedrandom: 3.0.5
@@ -6148,14 +7232,14 @@ packages:
     dependencies:
       defer-to-connect: 2.0.1
 
-  /@tabler/icons-webfont@2.17.0:
-    resolution: {integrity: sha512-jgRZWiWCaG++jFTIU/dbOT+JmSgoFlALwBUUS31mt1b5py7B0YWelnfxf5s3ctE+0dlnoIS+r7rDOeDSAWx8SA==}
+  /@tabler/icons-webfont@2.21.0:
+    resolution: {integrity: sha512-WCa57zYBjD9NF3/g96WKePgKUkKKD95Y+mo27/fzXOGxuoP9lGRjd01UCeLTGVxdEPErwlCjHXSi8HoDX2jevg==}
     dependencies:
-      '@tabler/icons': 2.17.0
+      '@tabler/icons': 2.21.0
     dev: false
 
-  /@tabler/icons@2.17.0:
-    resolution: {integrity: sha512-UeJaylOGNRhQKyDlgZfrQ3UPSGlfVQuXcmCsTYeXioKKepibW6VZ3H36Lo1jvBTBkQD2e9m+k2NxwkztOTXwrA==}
+  /@tabler/icons@2.21.0:
+    resolution: {integrity: sha512-XKrTEHMX6XzCOwcOU8ZNA+Xqm51sI+0abn2jk1fyQUpWeFnGsOEiC+fpQ4EISc+v+U9jqgTSbh8bZ6JBuKU5sw==}
     dev: false
 
   /@tensorflow/tfjs-backend-cpu@4.4.0(@tensorflow/tfjs-core@4.4.0):
@@ -6215,7 +7299,7 @@ packages:
     dependencies:
       '@tensorflow/tfjs-core': 4.4.0
       '@types/node-fetch': 2.6.2
-      node-fetch: 2.6.7
+      node-fetch: 2.6.11
       seedrandom: 3.0.5
       string_decoder: 1.3.0
     transitivePeerDependencies:
@@ -6274,7 +7358,7 @@ packages:
     resolution: {integrity: sha512-d9ULIT+a4EXLX3UU8FBjauG9NnsZHkHztXoIcTsOKoOw030fyjheN9svkTULjJxtYag9DZz5Jz5qkWZDPxTFwA==}
     engines: {node: '>=12'}
     dependencies:
-      '@babel/code-frame': 7.18.6
+      '@babel/code-frame': 7.21.4
       '@babel/runtime': 7.21.0
       '@types/aria-query': 5.0.1
       aria-query: 5.1.3
@@ -6288,7 +7372,7 @@ packages:
     resolution: {integrity: sha512-xTEnpUKiV/bMyEsE5bT4oYA0x0Z/colMtxzUY8bKyPXBNLn/e0V4ZjBZkEhms0xE4pv9QsPfSRu9AWS4y5wGvA==}
     engines: {node: '>=14'}
     dependencies:
-      '@babel/code-frame': 7.18.6
+      '@babel/code-frame': 7.21.4
       '@babel/runtime': 7.21.0
       '@types/aria-query': 5.0.1
       aria-query: 5.1.3
@@ -6304,7 +7388,7 @@ packages:
     dependencies:
       '@adobe/css-tools': 4.2.0
       '@babel/runtime': 7.20.7
-      '@types/testing-library__jest-dom': 5.14.5
+      '@types/testing-library__jest-dom': 5.14.6
       aria-query: 5.1.3
       chalk: 3.0.0
       css.escape: 1.5.1
@@ -6323,7 +7407,7 @@ packages:
       '@testing-library/dom': 8.20.0
     dev: true
 
-  /@testing-library/vue@7.0.0(@vue/compiler-sfc@3.3.1)(vue@3.3.1):
+  /@testing-library/vue@7.0.0(@vue/compiler-sfc@3.3.4)(vue@3.3.4):
     resolution: {integrity: sha512-JU/q93HGo2qdm1dCgWymkeQlfpC0/0/DBZ2nAHgEAsVZxX11xVIxT7gbXdI7HACQpUbsUWt1zABGU075Fzt9XQ==}
     engines: {node: '>=14'}
     peerDependencies:
@@ -6332,9 +7416,9 @@ packages:
     dependencies:
       '@babel/runtime': 7.21.0
       '@testing-library/dom': 9.2.0
-      '@vue/compiler-sfc': 3.3.1
-      '@vue/test-utils': 2.3.2(vue@3.3.1)
-      vue: 3.3.1
+      '@vue/compiler-sfc': 3.3.4
+      '@vue/test-utils': 2.3.2(vue@3.3.4)
+      vue: 3.3.4
     dev: true
 
   /@tokenizer/token@0.3.0:
@@ -6353,7 +7437,7 @@ packages:
   /@types/accepts@1.3.5:
     resolution: {integrity: sha512-jOdnI/3qTpHABjM5cx1Hc0sKsPoYCp+DP/GJRGtDlPd7fiV9oXGGIcjW/ZOxLIvjGz8MA+uMZI9metHlgqbgwQ==}
     dependencies:
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
     dev: true
 
   /@types/archiver@5.3.2:
@@ -6373,31 +7457,27 @@ packages:
   /@types/babel__core@7.20.0:
     resolution: {integrity: sha512-+n8dL/9GWblDO0iU6eZAwEIJVr5DWigtle+Q6HLOrh/pdbXOhOtqzq8VPPE2zvNJzSKY4vH/z3iT3tn0A3ypiQ==}
     dependencies:
-      '@babel/parser': 7.21.4
-      '@babel/types': 7.21.4
+      '@babel/parser': 7.22.4
+      '@babel/types': 7.22.4
       '@types/babel__generator': 7.6.4
       '@types/babel__template': 7.4.1
-      '@types/babel__traverse': 7.18.3
-    dev: true
+      '@types/babel__traverse': 7.20.0
 
   /@types/babel__generator@7.6.4:
     resolution: {integrity: sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==}
     dependencies:
-      '@babel/types': 7.21.5
-    dev: true
+      '@babel/types': 7.22.4
 
   /@types/babel__template@7.4.1:
     resolution: {integrity: sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==}
     dependencies:
-      '@babel/parser': 7.21.8
-      '@babel/types': 7.21.5
-    dev: true
+      '@babel/parser': 7.22.4
+      '@babel/types': 7.22.4
 
-  /@types/babel__traverse@7.18.3:
-    resolution: {integrity: sha512-1kbcJ40lLB7MHsj39U4Sh1uTd2E7rLEa79kmDpI6cy+XiXsteB3POdQomoq4FxszMrO3ZYchkhYJw7A2862b3w==}
+  /@types/babel__traverse@7.20.0:
+    resolution: {integrity: sha512-TBOjqAGf0hmaqRwpii5LLkJLg7c6OMm4nHLmpsUxwk9bBHtoTC6dAHdVWdGv4TBxj2CZOZY8Xfq8WmfoVi7n4Q==}
     dependencies:
-      '@babel/types': 7.21.5
-    dev: true
+      '@babel/types': 7.22.4
 
   /@types/bcryptjs@2.4.2:
     resolution: {integrity: sha512-LiMQ6EOPob/4yUL66SZzu6Yh77cbzJFYll+ZfaPiPPFswtIlA/Fs1MzdKYA7JApHU49zQTbJGX3PDmCpIdDBRQ==}
@@ -6407,44 +7487,35 @@ packages:
     resolution: {integrity: sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==}
     dependencies:
       '@types/connect': 3.4.35
-      '@types/node': 20.1.3
-    dev: true
+      '@types/node': 20.2.5
 
   /@types/braces@3.0.1:
     resolution: {integrity: sha512-+euflG6ygo4bn0JHtn4pYqcXwRtLvElQ7/nnjDu7iYG56H0+OhCd7d6Ug0IE3WcFpZozBKW2+80FUbv5QGk5AQ==}
     dev: true
 
-  /@types/bull@4.10.0:
-    resolution: {integrity: sha512-RkYW8K2H3J76HT6twmHYbzJ0GtLDDotpLP9ah9gtiA7zfF6peBH1l5fEiK0oeIZ3/642M7Jcb9sPmor8Vf4w6g==}
-    dependencies:
-      bull: 4.10.4
-    transitivePeerDependencies:
-      - supports-color
-    dev: true
-
   /@types/cacheable-request@6.0.3:
     resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==}
     dependencies:
       '@types/http-cache-semantics': 4.0.1
       '@types/keyv': 3.1.4
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
       '@types/responselike': 1.0.0
     dev: false
 
   /@types/cbor@6.0.0:
     resolution: {integrity: sha512-mGQ1lbYOwVti5Xlarn1bTeBZqgY0kstsdjnkoEovgohYKdBjGejHyNGXHdMBeqyQazIv32Jjp33+5pBEaSRy2w==}
     dependencies:
-      cbor: 8.1.0
+      cbor: 9.0.0
     dev: true
 
   /@types/chai-subset@1.3.3:
     resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==}
     dependencies:
-      '@types/chai': 4.3.4
+      '@types/chai': 4.3.5
     dev: true
 
-  /@types/chai@4.3.4:
-    resolution: {integrity: sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==}
+  /@types/chai@4.3.5:
+    resolution: {integrity: sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==}
     dev: true
 
   /@types/color-convert@2.0.0:
@@ -6460,8 +7531,7 @@ packages:
   /@types/connect@3.4.35:
     resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==}
     dependencies:
-      '@types/node': 20.1.3
-    dev: true
+      '@types/node': 20.2.5
 
   /@types/content-disposition@0.5.5:
     resolution: {integrity: sha512-v6LCdKfK6BwcqMo+wYW05rLS12S0ZO0Fl4w1h4aaZMD7bqT3gVUns6FvLJKGZHQmYn3SX55JWGpziwJRwVgutA==}
@@ -6525,10 +7595,9 @@ packages:
   /@types/express-serve-static-core@4.17.33:
     resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==}
     dependencies:
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
       '@types/qs': 6.9.7
       '@types/range-parser': 1.2.4
-    dev: true
 
   /@types/express@4.17.17:
     resolution: {integrity: sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==}
@@ -6537,7 +7606,6 @@ packages:
       '@types/express-serve-static-core': 4.17.33
       '@types/qs': 6.9.7
       '@types/serve-static': 1.15.1
-    dev: true
 
   /@types/find-cache-dir@3.2.1:
     resolution: {integrity: sha512-frsJrz2t/CeGifcu/6uRo4b+SzAwT4NYCVPu1GN8IB9XTzrpPkGuV0tmh9mN+/L0PklAlsC3u5Fxt0ju00LXIw==}
@@ -6546,34 +7614,34 @@ packages:
   /@types/fluent-ffmpeg@2.1.21:
     resolution: {integrity: sha512-+n3dy/Tegt6n+YwGZUiGq6i8Jrnt8+MoyPiW1L6J5EWUl7GSt18a/VyReecfCsvTTNBXNMIKOMHDstiQM8nJLA==}
     dependencies:
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
     dev: true
 
   /@types/glob-stream@6.1.1:
     resolution: {integrity: sha512-AGOUTsTdbPkRS0qDeyeS+6KypmfVpbT5j23SN8UPG63qjKXNKjXn6V9wZUr8Fin0m9l8oGYaPK8b2WUMF8xI1A==}
     dependencies:
       '@types/glob': 8.1.0
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
     dev: true
 
   /@types/glob@7.2.0:
     resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
     dependencies:
       '@types/minimatch': 5.1.2
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
     dev: true
 
   /@types/glob@8.1.0:
     resolution: {integrity: sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==}
     dependencies:
       '@types/minimatch': 5.1.2
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
     dev: true
 
   /@types/graceful-fs@4.1.6:
     resolution: {integrity: sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==}
     dependencies:
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
     dev: true
 
   /@types/gulp-rename@2.0.1:
@@ -6586,7 +7654,7 @@ packages:
   /@types/gulp-rename@2.0.2:
     resolution: {integrity: sha512-CQsXqTVtAXqrPd4IbrrlJEEzRkUR3RXsyZbrVoOVqjlchDDmnyRDatAUisjpQjjCg/wjJrSiNg8T1uAbJ/7Qqg==}
     dependencies:
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
       '@types/vinyl': 2.0.7
     dev: true
 
@@ -6630,6 +7698,13 @@ packages:
       pretty-format: 29.5.0
     dev: true
 
+  /@types/jest@29.5.2:
+    resolution: {integrity: sha512-mSoZVJF5YzGVCk+FsDxzDuH7s+SCkzrgKZzf0Z0T2WudhBUPoF6ktoTPC4R0ZoCPCV5xUvuU6ias5NvxcBcMMg==}
+    dependencies:
+      expect: 29.5.0
+      pretty-format: 29.5.0
+    dev: true
+
   /@types/js-levenshtein@1.1.1:
     resolution: {integrity: sha512-qC4bCqYGy1y/NP7dDVr7KJarn+PbX1nSpwA7JXdu0HxT3QYjO8MJ+cntENtHFVy2dRAyBV23OZ6MxsW1AM1L8g==}
     dev: true
@@ -6641,7 +7716,7 @@ packages:
   /@types/jsdom@21.1.1:
     resolution: {integrity: sha512-cZFuoVLtzKP3gmq9eNosUL1R50U+USkbLtUQ1bYVgl/lKp0FZM7Cq4aIHAL8oIvQ17uSHi7jXPtfDOdjPwBE7A==}
     dependencies:
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
       '@types/tough-cookie': 4.0.2
       parse5: 7.1.2
     dev: true
@@ -6665,7 +7740,7 @@ packages:
   /@types/keyv@3.1.4:
     resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
     dependencies:
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
     dev: false
 
   /@types/lodash@4.14.191:
@@ -6676,8 +7751,8 @@ packages:
     resolution: {integrity: sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==}
     dev: false
 
-  /@types/matter-js@0.18.3:
-    resolution: {integrity: sha512-7DYI52ebEl6AD9+RV2jO/XHdlFlpozYbkURtYKKJ2tO34q49Y15cfl+JSJpoMglQCAL/PxBSHKVv3wkvfZZD7g==}
+  /@types/matter-js@0.18.5:
+    resolution: {integrity: sha512-CV8m/FUmjmFNFcI7fUnsKcCLeqbf0kzWdKOTLGrpfKwWwrF6ggLaQlHNsg8267TkkiUAPoXY/7q6H9qwmR5TZg==}
     dev: true
 
   /@types/mdx@2.0.3:
@@ -6696,7 +7771,6 @@ packages:
 
   /@types/mime@3.0.1:
     resolution: {integrity: sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==}
-    dev: true
 
   /@types/minimatch@5.1.2:
     resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==}
@@ -6713,7 +7787,7 @@ packages:
   /@types/node-fetch@2.6.2:
     resolution: {integrity: sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==}
     dependencies:
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
       form-data: 3.0.1
 
   /@types/node-fetch@3.0.3:
@@ -6741,13 +7815,13 @@ packages:
     resolution: {integrity: sha512-OPs5WnnT1xkCBiuQrZA4+YAV4HEJejmHneyraIaxsbev5yCEr6KMwINNFP9wQeFIw8FWcoTqF3vQsa5CDaI+8Q==}
     dev: true
 
-  /@types/node@20.1.3:
-    resolution: {integrity: sha512-NP2yfZpgmf2eDRPmgGq+fjGjSwFgYbihA8/gK+ey23qT9RkxsgNTZvGOEpXgzIGqesTYkElELLgtKoMQTys5vA==}
+  /@types/node@20.2.5:
+    resolution: {integrity: sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==}
 
-  /@types/nodemailer@6.4.7:
-    resolution: {integrity: sha512-f5qCBGAn/f0qtRcd4SEn88c8Fp3Swct1731X4ryPKqS61/A3LmmzN8zaEz7hneJvpjFbUUgY7lru/B/7ODTazg==}
+  /@types/nodemailer@6.4.8:
+    resolution: {integrity: sha512-oVsJSCkqViCn8/pEu2hfjwVO+Gb3e+eTWjg3PcjeFKRItfKpKwHphQqbYmPQrlMk+op7pNNWPbsJIEthpFN/OQ==}
     dependencies:
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
     dev: true
 
   /@types/normalize-package-data@2.4.1:
@@ -6761,7 +7835,7 @@ packages:
   /@types/oauth@0.9.1:
     resolution: {integrity: sha512-a1iY62/a3yhZ7qH7cNUsxoI3U/0Fe9+RnuFrpTKr+0WVOzbKlSLojShCKe20aOD1Sppv+i8Zlq0pLDuTJnwS4A==}
     dependencies:
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
     dev: true
 
   /@types/offscreencanvas@2019.3.0:
@@ -6772,12 +7846,12 @@ packages:
     resolution: {integrity: sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg==}
     dev: false
 
-  /@types/pg@8.6.6:
-    resolution: {integrity: sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==}
+  /@types/pg@8.10.1:
+    resolution: {integrity: sha512-AmEHA/XxMxemQom5iDwP62FYNkv+gDDnetRG7v2N2dPtju7UKI7FknUimcZo7SodKTHtckYPzaTqUEvUKbVJEA==}
     dependencies:
-      '@types/node': 20.1.3
-      pg-protocol: 1.5.0
-      pg-types: 2.2.0
+      '@types/node': 20.2.5
+      pg-protocol: 1.6.0
+      pg-types: 4.0.1
     dev: true
 
   /@types/prettier@2.7.2:
@@ -6803,12 +7877,11 @@ packages:
   /@types/qrcode@1.5.0:
     resolution: {integrity: sha512-x5ilHXRxUPIMfjtM+1vf/GPTRWZ81nqscursm5gMznJeK9M0YnZ1c3bEvRLQ0zSSgedLx1J6MGL231ObQGGhaA==}
     dependencies:
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
     dev: true
 
   /@types/qs@6.9.7:
     resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==}
-    dev: true
 
   /@types/random-seed@0.3.3:
     resolution: {integrity: sha512-kHsCbIRHNXJo6EN5W8EA5b4i1hdT6jaZke5crBPLUcLqaLdZ0QBq8QVMbafHzhjFF83Cl9qlee2dChD18d/kPg==}
@@ -6816,7 +7889,6 @@ packages:
 
   /@types/range-parser@1.2.4:
     resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==}
-    dev: true
 
   /@types/ratelimiter@3.4.4:
     resolution: {integrity: sha512-GSMb93iSA8KKFDgVL2Wzs/kqrHMJcU8xhLdwI5omoACcj7K18SacklLtY1C4G02HC5drd6GygtsIaGbfxJSe0g==}
@@ -6833,7 +7905,7 @@ packages:
   /@types/readdir-glob@1.1.1:
     resolution: {integrity: sha512-ImM6TmoF8bgOwvehGviEj3tRdRBbQujr1N+0ypaln/GWjaerOB26jb93vsRHmdMtvVQZQebOlqt2HROark87mQ==}
     dependencies:
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
     dev: true
 
   /@types/redis@4.0.11:
@@ -6849,7 +7921,7 @@ packages:
   /@types/responselike@1.0.0:
     resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==}
     dependencies:
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
     dev: false
 
   /@types/sanitize-html@2.9.0:
@@ -6877,8 +7949,7 @@ packages:
     resolution: {integrity: sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==}
     dependencies:
       '@types/mime': 3.0.1
-      '@types/node': 20.1.3
-    dev: true
+      '@types/node': 20.2.5
 
   /@types/serviceworker@0.0.67:
     resolution: {integrity: sha512-7TCH7iNsCSNb+aUD9M/36TekrWFSLCjNK8zw/3n5kOtRjbLtDfGYMXTrDnGhSfqXNwpqmt9Vd90w5C/ad1tX6Q==}
@@ -6887,7 +7958,7 @@ packages:
   /@types/set-cookie-parser@2.4.2:
     resolution: {integrity: sha512-fBZgytwhYAUkj/jC/FAV4RQ5EerRup1YQsXQCh8rZfiHkc4UahC192oH0smGwsXol3cL3A5oETuAHeQHmhXM4w==}
     dependencies:
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
     dev: true
 
   /@types/sharp@0.32.0:
@@ -6919,10 +7990,10 @@ packages:
     resolution: {integrity: sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==}
     dev: true
 
-  /@types/testing-library__jest-dom@5.14.5:
-    resolution: {integrity: sha512-SBwbxYoyPIvxHbeHxTZX2Pe/74F/tX2/D3mMvzabdeJ25bBojfW0TyB8BHrbq/9zaaKICJZjLP+8r6AeZMFCuQ==}
+  /@types/testing-library__jest-dom@5.14.6:
+    resolution: {integrity: sha512-FkHXCb+ikSoUP4Y4rOslzTdX5sqYwMxfefKh1GmZ8ce1GOkEHntSp6b5cGadmNfp5e4BMEWOMx+WSKd5/MqlDA==}
     dependencies:
-      '@types/jest': 29.5.1
+      '@types/jest': 29.5.2
     dev: true
 
   /@types/throttle-debounce@5.0.0:
@@ -6952,7 +8023,7 @@ packages:
   /@types/undertaker@1.2.8:
     resolution: {integrity: sha512-gW3PRqCHYpo45XFQHJBhch7L6hytPsIe0QeLujlnFsjHPnXLhJcPdN6a9368d7aIQgH2I/dUTPFBlGeSNA3qOg==}
     dependencies:
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
       '@types/undertaker-registry': 1.0.1
       async-done: 1.3.2
     dev: true
@@ -6961,10 +8032,10 @@ packages:
     resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
     dev: true
 
-  /@types/unzipper@0.10.5:
-    resolution: {integrity: sha512-NrLJb29AdnBARpg9S/4ktfPEisbJ0AvaaAr3j7Q1tg8AgcEUsq2HqbNzvgLRoWyRtjzeLEv7vuL39u1mrNIyNA==}
+  /@types/unzipper@0.10.6:
+    resolution: {integrity: sha512-zcBj329AHgKLQyz209N/S9R0GZqXSkUQO4tJSYE3x02qg4JuDFpgKMj50r82Erk1natCWQDIvSccDddt7jPzjA==}
     dependencies:
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
     dev: true
 
   /@types/uuid@9.0.1:
@@ -6974,14 +8045,14 @@ packages:
   /@types/vary@1.1.0:
     resolution: {integrity: sha512-LQWqrIa0dvEOOH37lGksMEXbypRLUFqu6Gx0pmX7zIUisD2I/qaVgEX/vJ/PSVSW0Hk6yz1BNkFpqg6dZm3Wug==}
     dependencies:
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
     dev: true
 
   /@types/vinyl-fs@2.4.12:
     resolution: {integrity: sha512-LgBpYIWuuGsihnlF+OOWWz4ovwCYlT03gd3DuLwex50cYZLmX3yrW+sFF9ndtmh7zcZpS6Ri47PrIu+fV+sbXw==}
     dependencies:
       '@types/glob-stream': 6.1.1
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
       '@types/vinyl': 2.0.7
     dev: true
 
@@ -6989,12 +8060,12 @@ packages:
     resolution: {integrity: sha512-4UqPv+2567NhMQuMLdKAyK4yzrfCqwaTt6bLhHEs8PFcxbHILsrxaY63n4wgE/BRLDWDQeI+WcTmkXKExh9hQg==}
     dependencies:
       '@types/expect': 1.20.4
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
 
   /@types/web-push@3.3.2:
     resolution: {integrity: sha512-JxWGVL/m7mWTIg4mRYO+A6s0jPmBkr4iJr39DqJpRJAc+jrPiEe1/asmkwerzRon8ZZDxaZJpsxpv0Z18Wo9gw==}
     dependencies:
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
     dev: true
 
   /@types/webgl-ext@0.0.30:
@@ -7008,13 +8079,13 @@ packages:
   /@types/websocket@1.0.5:
     resolution: {integrity: sha512-NbsqiNX9CnEfC1Z0Vf4mE1SgAJ07JnRYcNex7AJ9zAVzmiGHmjKFEk7O4TJIsgv2B1sLEb6owKFZrACwdYngsQ==}
     dependencies:
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
     dev: true
 
   /@types/ws@8.5.4:
     resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==}
     dependencies:
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
     dev: true
 
   /@types/yargs-parser@21.0.0:
@@ -7037,7 +8108,7 @@ packages:
     resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==}
     requiresBuild: true
     dependencies:
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
     dev: true
     optional: true
 
@@ -7069,6 +8140,34 @@ packages:
       - supports-color
     dev: true
 
+  /@typescript-eslint/eslint-plugin@5.59.8(@typescript-eslint/parser@5.59.8)(eslint@8.41.0)(typescript@5.1.3):
+    resolution: {integrity: sha512-JDMOmhXteJ4WVKOiHXGCoB96ADWg9q7efPWHRViT/f09bA8XOMLAVHHju3l0MkZnG1izaWXYmgvQcUjTRcpShQ==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      '@typescript-eslint/parser': ^5.0.0
+      eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@eslint-community/regexpp': 4.5.0
+      '@typescript-eslint/parser': 5.59.8(eslint@8.41.0)(typescript@5.1.3)
+      '@typescript-eslint/scope-manager': 5.59.8
+      '@typescript-eslint/type-utils': 5.59.8(eslint@8.41.0)(typescript@5.1.3)
+      '@typescript-eslint/utils': 5.59.8(eslint@8.41.0)(typescript@5.1.3)
+      debug: 4.3.4(supports-color@8.1.1)
+      eslint: 8.41.0
+      grapheme-splitter: 1.0.4
+      ignore: 5.2.4
+      natural-compare-lite: 1.4.0
+      semver: 7.5.1
+      tsutils: 3.21.0(typescript@5.1.3)
+      typescript: 5.1.3
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /@typescript-eslint/parser@5.59.5(eslint@8.40.0)(typescript@5.0.4):
     resolution: {integrity: sha512-NJXQC4MRnF9N9yWqQE2/KLRSOLvrrlZb48NGVfBa+RuPMN6B7ZcK5jZOvhuygv4D64fRKnZI4L4p8+M+rfeQuw==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -7089,6 +8188,26 @@ packages:
       - supports-color
     dev: true
 
+  /@typescript-eslint/parser@5.59.8(eslint@8.41.0)(typescript@5.1.3):
+    resolution: {integrity: sha512-AnR19RjJcpjoeGojmwZtCwBX/RidqDZtzcbG3xHrmz0aHHoOcbWnpDllenRDmDvsV0RQ6+tbb09/kyc+UT9Orw==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@typescript-eslint/scope-manager': 5.59.8
+      '@typescript-eslint/types': 5.59.8
+      '@typescript-eslint/typescript-estree': 5.59.8(typescript@5.1.3)
+      debug: 4.3.4(supports-color@8.1.1)
+      eslint: 8.41.0
+      typescript: 5.1.3
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /@typescript-eslint/scope-manager@5.59.5:
     resolution: {integrity: sha512-jVecWwnkX6ZgutF+DovbBJirZcAxgxC0EOHYt/niMROf8p4PwxxG32Qdhj/iIQQIuOflLjNkxoXyArkcIP7C3A==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -7097,6 +8216,14 @@ packages:
       '@typescript-eslint/visitor-keys': 5.59.5
     dev: true
 
+  /@typescript-eslint/scope-manager@5.59.8:
+    resolution: {integrity: sha512-/w08ndCYI8gxGf+9zKf1vtx/16y8MHrZs5/tnjHhMLNSixuNcJavSX4wAiPf4aS5x41Es9YPCn44MIe4cxIlig==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    dependencies:
+      '@typescript-eslint/types': 5.59.8
+      '@typescript-eslint/visitor-keys': 5.59.8
+    dev: true
+
   /@typescript-eslint/type-utils@5.59.5(eslint@8.40.0)(typescript@5.0.4):
     resolution: {integrity: sha512-4eyhS7oGym67/pSxA2mmNq7X164oqDYNnZCUayBwJZIRVvKpBCMBzFnFxjeoDeShjtO6RQBHBuwybuX3POnDqg==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -7117,11 +8244,36 @@ packages:
       - supports-color
     dev: true
 
+  /@typescript-eslint/type-utils@5.59.8(eslint@8.41.0)(typescript@5.1.3):
+    resolution: {integrity: sha512-+5M518uEIHFBy3FnyqZUF3BMP+AXnYn4oyH8RF012+e7/msMY98FhGL5SrN29NQ9xDgvqCgYnsOiKp1VjZ/fpA==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      eslint: '*'
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@typescript-eslint/typescript-estree': 5.59.8(typescript@5.1.3)
+      '@typescript-eslint/utils': 5.59.8(eslint@8.41.0)(typescript@5.1.3)
+      debug: 4.3.4(supports-color@8.1.1)
+      eslint: 8.41.0
+      tsutils: 3.21.0(typescript@5.1.3)
+      typescript: 5.1.3
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /@typescript-eslint/types@5.59.5:
     resolution: {integrity: sha512-xkfRPHbqSH4Ggx4eHRIO/eGL8XL4Ysb4woL8c87YuAo8Md7AUjyWKa9YMwTL519SyDPrfEgKdewjkxNCVeJW7w==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dev: true
 
+  /@typescript-eslint/types@5.59.8:
+    resolution: {integrity: sha512-+uWuOhBTj/L6awoWIg0BlWy0u9TyFpCHrAuQ5bNfxDaZ1Ppb3mx6tUigc74LHcbHpOHuOTOJrBoAnhdHdaea1w==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    dev: true
+
   /@typescript-eslint/typescript-estree@5.59.5(typescript@5.0.4):
     resolution: {integrity: sha512-+XXdLN2CZLZcD/mO7mQtJMvCkzRfmODbeSKuMY/yXbGkzvA9rJyDY5qDYNoiz2kP/dmyAxXquL2BvLQLJFPQIg==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -7136,13 +8288,34 @@ packages:
       debug: 4.3.4(supports-color@8.1.1)
       globby: 11.1.0
       is-glob: 4.0.3
-      semver: 7.5.0
+      semver: 7.5.1
       tsutils: 3.21.0(typescript@5.0.4)
       typescript: 5.0.4
     transitivePeerDependencies:
       - supports-color
     dev: true
 
+  /@typescript-eslint/typescript-estree@5.59.8(typescript@5.1.3):
+    resolution: {integrity: sha512-Jy/lPSDJGNow14vYu6IrW790p7HIf/SOV1Bb6lZ7NUkLc2iB2Z9elESmsaUtLw8kVqogSbtLH9tut5GCX1RLDg==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@typescript-eslint/types': 5.59.8
+      '@typescript-eslint/visitor-keys': 5.59.8
+      debug: 4.3.4(supports-color@8.1.1)
+      globby: 11.1.0
+      is-glob: 4.0.3
+      semver: 7.5.1
+      tsutils: 3.21.0(typescript@5.1.3)
+      typescript: 5.1.3
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /@typescript-eslint/utils@5.59.5(eslint@8.40.0)(typescript@5.0.4):
     resolution: {integrity: sha512-sCEHOiw+RbyTii9c3/qN74hYDPNORb8yWCoPLmB7BIflhplJ65u2PBpdRla12e3SSTJ2erRkPjz7ngLHhUegxA==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -7157,7 +8330,27 @@ packages:
       '@typescript-eslint/typescript-estree': 5.59.5(typescript@5.0.4)
       eslint: 8.40.0
       eslint-scope: 5.1.1
-      semver: 7.5.0
+      semver: 7.5.1
+    transitivePeerDependencies:
+      - supports-color
+      - typescript
+    dev: true
+
+  /@typescript-eslint/utils@5.59.8(eslint@8.41.0)(typescript@5.1.3):
+    resolution: {integrity: sha512-Tr65630KysnNn9f9G7ROF3w1b5/7f6QVCJ+WK9nhIocWmx9F+TmCAcglF26Vm7z8KCTwoKcNEBZrhlklla3CKg==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    peerDependencies:
+      eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
+    dependencies:
+      '@eslint-community/eslint-utils': 4.4.0(eslint@8.41.0)
+      '@types/json-schema': 7.0.11
+      '@types/semver': 7.5.0
+      '@typescript-eslint/scope-manager': 5.59.8
+      '@typescript-eslint/types': 5.59.8
+      '@typescript-eslint/typescript-estree': 5.59.8(typescript@5.1.3)
+      eslint: 8.41.0
+      eslint-scope: 5.1.1
+      semver: 7.5.1
     transitivePeerDependencies:
       - supports-color
       - typescript
@@ -7171,78 +8364,86 @@ packages:
       eslint-visitor-keys: 3.4.1
     dev: true
 
-  /@vitejs/plugin-react@3.1.0(vite@4.3.5):
+  /@typescript-eslint/visitor-keys@5.59.8:
+    resolution: {integrity: sha512-pJhi2ms0x0xgloT7xYabil3SGGlojNNKjK/q6dB3Ey0uJLMjK2UDGJvHieiyJVW/7C3KI+Z4Q3pEHkm4ejA+xQ==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    dependencies:
+      '@typescript-eslint/types': 5.59.8
+      eslint-visitor-keys: 3.4.1
+    dev: true
+
+  /@vitejs/plugin-react@3.1.0(vite@4.3.9):
     resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==}
     engines: {node: ^14.18.0 || >=16.0.0}
     peerDependencies:
       vite: ^4.1.0-beta.0
     dependencies:
-      '@babel/core': 7.21.3
-      '@babel/plugin-transform-react-jsx-self': 7.21.0(@babel/core@7.21.3)
-      '@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.21.3)
+      '@babel/core': 7.22.1
+      '@babel/plugin-transform-react-jsx-self': 7.21.0(@babel/core@7.22.1)
+      '@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.22.1)
       magic-string: 0.27.0
       react-refresh: 0.14.0
-      vite: 4.3.5(@types/node@20.1.3)(sass@1.62.1)
+      vite: 4.3.9(@types/node@20.2.5)(sass@1.62.1)
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@vitejs/plugin-vue@4.2.2(vite@4.3.5)(vue@3.3.1):
-    resolution: {integrity: sha512-kNH4wMAqs13UiZe/2If1ioO0Mjz71rr2oALTl2c5ajBIox9Vz/UGW/wGkr7GA3SC6Eb29c1HtzAtxdGfbXAkfQ==}
+  /@vitejs/plugin-vue@4.2.3(vite@4.3.9)(vue@3.3.4):
+    resolution: {integrity: sha512-R6JDUfiZbJA9cMiguQ7jxALsgiprjBeHL5ikpXfJCH62pPHtI+JdJ5xWj6Ev73yXSlYl86+blXn1kZHQ7uElxw==}
     engines: {node: ^14.18.0 || >=16.0.0}
     peerDependencies:
       vite: ^4.0.0
       vue: ^3.2.25
     dependencies:
-      vite: 4.3.5(@types/node@20.1.3)(sass@1.62.1)
-      vue: 3.3.1
+      vite: 4.3.9(@types/node@20.2.5)(sass@1.62.1)
+      vue: 3.3.4
 
-  /@vitest/coverage-c8@0.31.0(vitest@0.31.0):
-    resolution: {integrity: sha512-h72qN1D962AO7UefQVulm9JFP5ACS7OfhCdBHioXU8f7ohH/+NTZCgAqmgcfRNHHO/8wLFxx+93YVxhodkEJVA==}
+  /@vitest/coverage-c8@0.31.4(vitest@0.31.4):
+    resolution: {integrity: sha512-VPx368m4DTcpA/P0v3YdVxl4QOSh1DbUcXURLRvDShrIB5KxOgfzw4Bn2R8AhAe/GyiWW/FIsJ/OJdYXCCiC1w==}
     peerDependencies:
       vitest: '>=0.30.0 <1'
     dependencies:
-      '@ampproject/remapping': 2.2.0
+      '@ampproject/remapping': 2.2.1
       c8: 7.13.0
       magic-string: 0.30.0
       picocolors: 1.0.0
       std-env: 3.3.2
-      vitest: 0.31.0(happy-dom@9.16.0)(sass@1.62.1)
+      vitest: 0.31.4(happy-dom@9.20.3)(sass@1.62.1)
     dev: true
 
-  /@vitest/expect@0.31.0:
-    resolution: {integrity: sha512-Jlm8ZTyp6vMY9iz9Ny9a0BHnCG4fqBa8neCF6Pk/c/6vkUk49Ls6UBlgGAU82QnzzoaUs9E/mUhq/eq9uMOv/g==}
+  /@vitest/expect@0.31.4:
+    resolution: {integrity: sha512-tibyx8o7GUyGHZGyPgzwiaPaLDQ9MMuCOrc03BYT0nryUuhLbL7NV2r/q98iv5STlwMgaKuFJkgBW/8iPKwlSg==}
     dependencies:
-      '@vitest/spy': 0.31.0
-      '@vitest/utils': 0.31.0
+      '@vitest/spy': 0.31.4
+      '@vitest/utils': 0.31.4
       chai: 4.3.7
     dev: true
 
-  /@vitest/runner@0.31.0:
-    resolution: {integrity: sha512-H1OE+Ly7JFeBwnpHTrKyCNm/oZgr+16N4qIlzzqSG/YRQDATBYmJb/KUn3GrZaiQQyL7GwpNHVZxSQd6juLCgw==}
+  /@vitest/runner@0.31.4:
+    resolution: {integrity: sha512-Wgm6UER+gwq6zkyrm5/wbpXGF+g+UBB78asJlFkIOwyse0pz8lZoiC6SW5i4gPnls/zUcPLWS7Zog0LVepXnpg==}
     dependencies:
-      '@vitest/utils': 0.31.0
+      '@vitest/utils': 0.31.4
       concordance: 5.0.4
       p-limit: 4.0.0
       pathe: 1.1.0
     dev: true
 
-  /@vitest/snapshot@0.31.0:
-    resolution: {integrity: sha512-5dTXhbHnyUMTMOujZPB0wjFjQ6q5x9c8TvAsSPUNKjp1tVU7i9pbqcKPqntyu2oXtmVxKbuHCqrOd+Ft60r4tg==}
+  /@vitest/snapshot@0.31.4:
+    resolution: {integrity: sha512-LemvNumL3NdWSmfVAMpXILGyaXPkZbG5tyl6+RQSdcHnTj6hvA49UAI8jzez9oQyE/FWLKRSNqTGzsHuk89LRA==}
     dependencies:
       magic-string: 0.30.0
       pathe: 1.1.0
       pretty-format: 27.5.1
     dev: true
 
-  /@vitest/spy@0.31.0:
-    resolution: {integrity: sha512-IzCEQ85RN26GqjQNkYahgVLLkULOxOm5H/t364LG0JYb3Apg0PsYCHLBYGA006+SVRMWhQvHlBBCyuByAMFmkg==}
+  /@vitest/spy@0.31.4:
+    resolution: {integrity: sha512-3ei5ZH1s3aqbEyftPAzSuunGICRuhE+IXOmpURFdkm5ybUADk+viyQfejNk6q8M5QGX8/EVKw+QWMEP3DTJDag==}
     dependencies:
       tinyspy: 2.1.0
     dev: true
 
-  /@vitest/utils@0.31.0:
-    resolution: {integrity: sha512-kahaRyLX7GS1urekRXN2752X4gIgOGVX4Wo8eDUGUkTWlGpXzf5ZS6N9RUUS+Re3XEE8nVGqNyxkSxF5HXlGhQ==}
+  /@vitest/utils@0.31.4:
+    resolution: {integrity: sha512-DobZbHacWznoGUfYU8XDPY78UubJxXfMNY1+SUdOp1NsI34eopSA6aZMeaGu10waSOeYwE8lxrd/pLfT0RMxjQ==}
     dependencies:
       concordance: 5.0.4
       loupe: 2.3.6
@@ -7261,166 +8462,158 @@ packages:
       muggle-string: 0.2.2
     dev: true
 
-  /@volar/typescript@1.4.1(typescript@5.0.4):
-    resolution: {integrity: sha512-phTy6p9yG6bgMIKQWEeDOi/aeT0njZsb1a/G1mrEuDsLmAn24Le4gDwSsGNhea6Uhu+3gdpUZn2PmZXa+WG2iQ==}
+  /@volar/typescript@1.4.1-patch.2(typescript@5.1.3):
+    resolution: {integrity: sha512-lPFYaGt8OdMEzNGJJChF40uYqMO4Z/7Q9fHPQC/NRVtht43KotSXLrkPandVVMf9aPbiJ059eAT+fwHGX16k4w==}
     peerDependencies:
       typescript: '*'
     dependencies:
       '@volar/language-core': 1.4.1
-      typescript: 5.0.4
+      typescript: 5.1.3
     dev: true
 
-  /@volar/vue-language-core@1.6.4:
-    resolution: {integrity: sha512-1o+cAtN2DIDNAX/HS8rkjZc8wTMTK+zCab/qtYbvEVlmokhZiDrQeoD9/l0Ug7YCNg+mVuMNHKNBY7pX8U2/Jw==}
+  /@volar/vue-language-core@1.6.5:
+    resolution: {integrity: sha512-IF2b6hW4QAxfsLd5mePmLgtkXzNi+YnH6ltCd80gb7+cbdpFMjM1I+w+nSg2kfBTyfu+W8useCZvW89kPTBpzg==}
     dependencies:
       '@volar/language-core': 1.4.1
       '@volar/source-map': 1.4.1
-      '@vue/compiler-dom': 3.3.1
-      '@vue/compiler-sfc': 3.3.1
-      '@vue/reactivity': 3.3.1
-      '@vue/shared': 3.3.1
+      '@vue/compiler-dom': 3.3.4
+      '@vue/compiler-sfc': 3.3.4
+      '@vue/reactivity': 3.3.4
+      '@vue/shared': 3.3.4
       minimatch: 9.0.0
       muggle-string: 0.2.2
       vue-template-compiler: 2.7.14
     dev: true
 
-  /@volar/vue-typescript@1.6.4(typescript@5.0.4):
-    resolution: {integrity: sha512-qKwgP0KVQR/aaH/SN3AP7RB8NnXPWDn3tjyXP6IT6etxkDeZLBLsXWUD9KMak/RvV1DgbXDuz4F9yuZlbt29rA==}
+  /@volar/vue-typescript@1.6.5(typescript@5.1.3):
+    resolution: {integrity: sha512-er9rVClS4PHztMUmtPMDTl+7c7JyrxweKSAEe/o/Noeq2bQx6v3/jZHVHBe8ZNUti5ubJL/+Tg8L3bzmlalV8A==}
     peerDependencies:
       typescript: '*'
     dependencies:
-      '@volar/typescript': 1.4.1(typescript@5.0.4)
-      '@volar/vue-language-core': 1.6.4
-      typescript: 5.0.4
+      '@volar/typescript': 1.4.1-patch.2(typescript@5.1.3)
+      '@volar/vue-language-core': 1.6.5
+      typescript: 5.1.3
     dev: true
 
-  /@vue-macros/common@1.3.1(rollup@3.21.6)(vue@3.3.1):
-    resolution: {integrity: sha512-Lc5aP/8HNJD1XrnvpeNuWcCf82bZdR3auN/chA1b/1rKZgSnmQkH9f33tKO9qLwXSy+u4hpCi8Rw+oUuF1KCeg==}
-    engines: {node: '>=14.19.0'}
+  /@vue-macros/common@1.3.3(rollup@3.23.0)(vue@3.3.4):
+    resolution: {integrity: sha512-bjHomaf3mu+ARMD4DX22C/lLVVocbmwgcLH7bg1rK4kB5ghesgShZTQIrNR6ZjifQmdGc/2jjZ/25kSb364uEA==}
+    engines: {node: '>=16.14.0'}
     peerDependencies:
       vue: ^2.7.0 || ^3.2.25
     peerDependenciesMeta:
       vue:
         optional: true
     dependencies:
-      '@babel/types': 7.21.5
-      '@rollup/pluginutils': 5.0.2(rollup@3.21.6)
-      '@vue/compiler-sfc': 3.3.1
+      '@babel/types': 7.22.4
+      '@rollup/pluginutils': 5.0.2(rollup@3.23.0)
+      '@vue/compiler-sfc': 3.3.4
       local-pkg: 0.4.3
       magic-string-ast: 0.1.2
-      vue: 3.3.1
+      vue: 3.3.4
     transitivePeerDependencies:
       - rollup
     dev: false
 
-  /@vue-macros/reactivity-transform@0.3.6(rollup@3.21.6)(vue@3.3.1):
-    resolution: {integrity: sha512-PFJRXHEdIP03LeNnfcwjUk8pKWjvyeOZjCNwbLgfqunI9tknG4IQMfl86qswK83f+DoOTMCoeMFoUnmlbr+yUw==}
-    engines: {node: '>=14.19.0'}
+  /@vue-macros/reactivity-transform@0.3.9(rollup@3.23.0)(vue@3.3.4):
+    resolution: {integrity: sha512-lzzH2qzIxc1LWRrSR+ax0TVeBTgwTpG9qTZOo4Au+ODgJyXpIWHGCnc9rjcxGfu6LitjZ75NmyjbEnaEkomefw==}
+    engines: {node: '>=16.14.0'}
     peerDependencies:
       vue: ^2.7.0 || ^3.2.25
     dependencies:
-      '@babel/parser': 7.21.8
-      '@vue-macros/common': 1.3.1(rollup@3.21.6)(vue@3.3.1)
-      '@vue/compiler-core': 3.3.1
-      '@vue/shared': 3.3.1
+      '@babel/parser': 7.22.4
+      '@vue-macros/common': 1.3.3(rollup@3.23.0)(vue@3.3.4)
+      '@vue/compiler-core': 3.3.4
+      '@vue/shared': 3.3.4
       magic-string: 0.30.0
       unplugin: 1.3.1
-      vue: 3.3.1
+      vue: 3.3.4
     transitivePeerDependencies:
       - rollup
     dev: false
 
-  /@vue/compiler-core@3.3.1:
-    resolution: {integrity: sha512-5le1qYSBgLWg2jdLrbydlhnPJkkzMw46UrRUvTnOKlfg6pThtm9ohhqBhNPHbr0RcM1MCbK5WZe/3Ghz0SZjpQ==}
+  /@vue/compiler-core@3.3.4:
+    resolution: {integrity: sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==}
     dependencies:
-      '@babel/parser': 7.21.8
-      '@vue/shared': 3.3.1
+      '@babel/parser': 7.22.4
+      '@vue/shared': 3.3.4
       estree-walker: 2.0.2
       source-map-js: 1.0.2
 
-  /@vue/compiler-dom@3.3.1:
-    resolution: {integrity: sha512-VmgIsoLivCft3+oNc5KM7b9wd0nZxP/g2qilMwi1hJyGA624KWnNKHn4hzBQs4FpzydUVpNy+TWVT8KiRCh3MQ==}
+  /@vue/compiler-dom@3.3.4:
+    resolution: {integrity: sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==}
     dependencies:
-      '@vue/compiler-core': 3.3.1
-      '@vue/shared': 3.3.1
+      '@vue/compiler-core': 3.3.4
+      '@vue/shared': 3.3.4
 
-  /@vue/compiler-sfc@2.7.14:
-    resolution: {integrity: sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==}
+  /@vue/compiler-sfc@3.3.4:
+    resolution: {integrity: sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==}
     dependencies:
       '@babel/parser': 7.21.8
-      postcss: 8.4.23
-      source-map: 0.6.1
-    dev: false
-
-  /@vue/compiler-sfc@3.3.1:
-    resolution: {integrity: sha512-G+FPwBbXSLaA4+Ry5/bdD9Oda+sRslQcE9o6JSZaougRiT4OjVL0vtkbQHPrGRTULZV28OcrAjRfSZOSB0OTXQ==}
-    dependencies:
-      '@babel/parser': 7.21.4
-      '@vue/compiler-core': 3.3.1
-      '@vue/compiler-dom': 3.3.1
-      '@vue/compiler-ssr': 3.3.1
-      '@vue/reactivity-transform': 3.3.1
-      '@vue/shared': 3.3.1
+      '@vue/compiler-core': 3.3.4
+      '@vue/compiler-dom': 3.3.4
+      '@vue/compiler-ssr': 3.3.4
+      '@vue/reactivity-transform': 3.3.4
+      '@vue/shared': 3.3.4
       estree-walker: 2.0.2
       magic-string: 0.30.0
       postcss: 8.4.23
       source-map-js: 1.0.2
 
-  /@vue/compiler-ssr@3.3.1:
-    resolution: {integrity: sha512-QOQWGNCWuSeyKx4KvWSJlnIMGg+/2oCHgkFUYo7aJ+9Uaaz45yRgKQ+FNigy50NYBQIhpXn2e4OSR8GXh4knrQ==}
+  /@vue/compiler-ssr@3.3.4:
+    resolution: {integrity: sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==}
     dependencies:
-      '@vue/compiler-dom': 3.3.1
-      '@vue/shared': 3.3.1
+      '@vue/compiler-dom': 3.3.4
+      '@vue/shared': 3.3.4
 
-  /@vue/reactivity-transform@3.3.1:
-    resolution: {integrity: sha512-MkOrJauAGH4MNdxGW/PmrDegMyOGX0wGIdKUZJRBXOTpotDONg7/TPJe2QeGeBCow/5v9iOqZOWCfvmOWIaDMg==}
+  /@vue/reactivity-transform@3.3.4:
+    resolution: {integrity: sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==}
     dependencies:
-      '@babel/parser': 7.21.8
-      '@vue/compiler-core': 3.3.1
-      '@vue/shared': 3.3.1
+      '@babel/parser': 7.22.4
+      '@vue/compiler-core': 3.3.4
+      '@vue/shared': 3.3.4
       estree-walker: 2.0.2
       magic-string: 0.30.0
 
-  /@vue/reactivity@3.3.1:
-    resolution: {integrity: sha512-zCfmazOtyUdC1NS/EPiSYJ4RqojqmTAviJyBbyVvY8zAv5NhK44Yfw0E1tt+m5vz0ZO1ptI9jDKBr3MWIEkpgw==}
+  /@vue/reactivity@3.3.4:
+    resolution: {integrity: sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==}
     dependencies:
-      '@vue/shared': 3.3.1
+      '@vue/shared': 3.3.4
 
-  /@vue/runtime-core@3.3.1:
-    resolution: {integrity: sha512-Ljb37LYafhQqKIasc0r32Cva8gIh6VeSMjlwO6V03tCjHd18gmjP0F4UD+8/a59sGTysAgA8Rb9lIC2DVxRz2Q==}
+  /@vue/runtime-core@3.3.4:
+    resolution: {integrity: sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==}
     dependencies:
-      '@vue/reactivity': 3.3.1
-      '@vue/shared': 3.3.1
+      '@vue/reactivity': 3.3.4
+      '@vue/shared': 3.3.4
 
-  /@vue/runtime-dom@3.3.1:
-    resolution: {integrity: sha512-NBjYbQPtMklb7lsJsM2Juv5Ygry6mvZP7PdH1GZqrzfLkvlplQT3qCtQMd/sib6yiy8t9m/Y4hVU7X9nzb9Oeg==}
+  /@vue/runtime-dom@3.3.4:
+    resolution: {integrity: sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==}
     dependencies:
-      '@vue/runtime-core': 3.3.1
-      '@vue/shared': 3.3.1
+      '@vue/runtime-core': 3.3.4
+      '@vue/shared': 3.3.4
       csstype: 3.1.1
 
-  /@vue/server-renderer@3.3.1(vue@3.3.1):
-    resolution: {integrity: sha512-sod8ggOwbkQXw3lBjfzrbdxRS9lw/lNHoMaXghHawNYowf+4WoaLWD5ouz6fPZadUqNKAsqK95p8DYb1vcVfPA==}
+  /@vue/server-renderer@3.3.4(vue@3.3.4):
+    resolution: {integrity: sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==}
     peerDependencies:
-      vue: 3.3.1
+      vue: 3.3.4
     dependencies:
-      '@vue/compiler-ssr': 3.3.1
-      '@vue/shared': 3.3.1
-      vue: 3.3.1
+      '@vue/compiler-ssr': 3.3.4
+      '@vue/shared': 3.3.4
+      vue: 3.3.4
 
-  /@vue/shared@3.3.1:
-    resolution: {integrity: sha512-ybDBtQ+479HL/bkeIOIAwgpeAEACzztkvulJLbK3JMFuTOv4qDivmV3AIsR8RHYJ+RD9tQxcHWBsX4GqEcYrfw==}
+  /@vue/shared@3.3.4:
+    resolution: {integrity: sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==}
 
-  /@vue/test-utils@2.3.2(vue@3.3.1):
+  /@vue/test-utils@2.3.2(vue@3.3.4):
     resolution: {integrity: sha512-hJnVaYhbrIm0yBS0+e1Y0Sj85cMyAi+PAbK4JHqMRUZ6S622Goa+G7QzkRSyvCteG8wop7tipuEbHoZo26wsSA==}
     peerDependencies:
       vue: ^3.0.1
     dependencies:
       js-beautify: 1.14.6
-      vue: 3.3.1
+      vue: 3.3.4
     optionalDependencies:
-      '@vue/compiler-dom': 3.3.1
-      '@vue/server-renderer': 3.3.1(vue@3.3.1)
+      '@vue/compiler-dom': 3.3.4
+      '@vue/server-renderer': 3.3.4(vue@3.3.4)
     dev: true
 
   /@webgpu/types@0.1.30:
@@ -7461,7 +8654,7 @@ packages:
       p-limit: 2.3.0
       pluralize: 7.0.0
       pretty-bytes: 5.6.0
-      semver: 7.5.0
+      semver: 7.5.1
       stream-to-promise: 2.2.0
       tar-stream: 2.2.0
       treeify: 1.1.0
@@ -7476,7 +8669,7 @@ packages:
       esbuild: '>=0.10.0'
     dependencies:
       esbuild: 0.17.18
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: true
 
   /@yarnpkg/fslib@2.10.2:
@@ -7570,13 +8763,6 @@ packages:
       mime-types: 2.1.35
       negotiator: 0.6.3
 
-  /acorn-globals@7.0.1:
-    resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==}
-    dependencies:
-      acorn: 8.8.2
-      acorn-walk: 8.2.0
-    dev: false
-
   /acorn-jsx@5.3.2(acorn@7.4.1):
     resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
     peerDependencies:
@@ -7601,6 +8787,7 @@ packages:
   /acorn-walk@8.2.0:
     resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
     engines: {node: '>=0.4.0'}
+    dev: true
 
   /acorn@7.4.1:
     resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==}
@@ -7750,7 +8937,6 @@ packages:
     engines: {node: '>=4'}
     dependencies:
       color-convert: 1.9.3
-    dev: true
 
   /ansi-styles@4.3.0:
     resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
@@ -8015,7 +9201,6 @@ packages:
       is-nan: 1.3.2
       object-is: 1.1.5
       util: 0.12.5
-    dev: true
 
   /assertion-error@1.1.0:
     resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
@@ -8029,32 +9214,31 @@ packages:
     resolution: {integrity: sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==}
     engines: {node: '>=4'}
     dependencies:
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: true
 
   /ast-types@0.15.2:
     resolution: {integrity: sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==}
     engines: {node: '>=4'}
     dependencies:
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: true
 
   /ast-types@0.16.1:
     resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==}
     engines: {node: '>=4'}
     dependencies:
-      tslib: 2.5.0
-    dev: true
+      tslib: 2.5.2
 
   /astral-regex@2.0.0:
     resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}
     engines: {node: '>=8'}
     dev: true
 
-  /astring@1.8.4:
-    resolution: {integrity: sha512-97a+l2LBU3Op3bBQEff79i/E4jMD2ZLFD8rHx9B6mXyB2uQwhJQYfiDqUwtfjF4QA1F2qs//N6Cw8LetMbQjcw==}
+  /astring@1.8.6:
+    resolution: {integrity: sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg==}
     hasBin: true
-    dev: true
+    dev: false
 
   /async-done@1.3.2:
     resolution: {integrity: sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==}
@@ -8113,8 +9297,8 @@ packages:
       postcss-value-parser: 3.3.1
     dev: false
 
-  /autosize@5.0.2:
-    resolution: {integrity: sha512-FPVt5ynkqUAA9gcMZnJHka1XfQgr1WNd/yRfIjmj5WGmjua+u5Hl9hn8M2nU5CNy2bEIcj1ZUwXq7IOHsfZG9w==}
+  /autosize@6.0.1:
+    resolution: {integrity: sha512-f86EjiUKE6Xvczc4ioP1JBlWG7FKrE13qe/DxBCpe8GCipCq2nFw73aO8QEBKHfSbYGDN5eB9jXWKen7tspDqQ==}
     dev: false
 
   /autwh@0.1.0:
@@ -8126,7 +9310,6 @@ packages:
   /available-typed-arrays@1.0.5:
     resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==}
     engines: {node: '>= 0.4'}
-    dev: true
 
   /avvio@8.2.0:
     resolution: {integrity: sha512-bbCQdg7bpEv6kGH41RO/3B2/GMMmJSo2iBK+X8AWN9mujtfUipMDfIjsgHCfpnKqoGEQrrmCDKSa5OQ19+fDmg==}
@@ -8169,12 +9352,12 @@ packages:
       - debug
     dev: true
 
-  /babel-core@7.0.0-bridge.0(@babel/core@7.21.3):
+  /babel-core@7.0.0-bridge.0(@babel/core@7.22.1):
     resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.21.3
+      '@babel/core': 7.22.1
     dev: true
 
   /babel-jest@29.5.0(@babel/core@7.21.3):
@@ -8212,10 +9395,10 @@ packages:
     resolution: {integrity: sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
-      '@babel/template': 7.20.7
-      '@babel/types': 7.21.5
+      '@babel/template': 7.21.9
+      '@babel/types': 7.22.4
       '@types/babel__core': 7.20.0
-      '@types/babel__traverse': 7.18.3
+      '@types/babel__traverse': 7.20.0
     dev: true
 
   /babel-plugin-polyfill-corejs2@0.3.3(@babel/core@7.21.3):
@@ -8223,7 +9406,7 @@ packages:
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/compat-data': 7.21.4
+      '@babel/compat-data': 7.22.3
       '@babel/core': 7.21.3
       '@babel/helper-define-polyfill-provider': 0.3.3(@babel/core@7.21.3)
       semver: 6.3.0
@@ -8231,6 +9414,19 @@ packages:
       - supports-color
     dev: true
 
+  /babel-plugin-polyfill-corejs2@0.3.3(@babel/core@7.22.1):
+    resolution: {integrity: sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/compat-data': 7.22.3
+      '@babel/core': 7.22.1
+      '@babel/helper-define-polyfill-provider': 0.3.3(@babel/core@7.22.1)
+      semver: 6.3.0
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /babel-plugin-polyfill-corejs3@0.6.0(@babel/core@7.21.3):
     resolution: {integrity: sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==}
     peerDependencies:
@@ -8243,6 +9439,18 @@ packages:
       - supports-color
     dev: true
 
+  /babel-plugin-polyfill-corejs3@0.6.0(@babel/core@7.22.1):
+    resolution: {integrity: sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-define-polyfill-provider': 0.3.3(@babel/core@7.22.1)
+      core-js-compat: 3.29.1
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /babel-plugin-polyfill-regenerator@0.4.1(@babel/core@7.21.3):
     resolution: {integrity: sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==}
     peerDependencies:
@@ -8254,6 +9462,17 @@ packages:
       - supports-color
     dev: true
 
+  /babel-plugin-polyfill-regenerator@0.4.1(@babel/core@7.22.1):
+    resolution: {integrity: sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/helper-define-polyfill-provider': 0.3.3(@babel/core@7.22.1)
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /babel-preset-current-node-syntax@1.0.1(@babel/core@7.21.3):
     resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==}
     peerDependencies:
@@ -8274,6 +9493,26 @@ packages:
       '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.21.3)
     dev: true
 
+  /babel-preset-current-node-syntax@1.0.1(@babel/core@7.22.1):
+    resolution: {integrity: sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==}
+    peerDependencies:
+      '@babel/core': ^7.0.0
+    dependencies:
+      '@babel/core': 7.22.1
+      '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.22.1)
+      '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.22.1)
+      '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.22.1)
+      '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.22.1)
+      '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.22.1)
+      '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.22.1)
+      '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.22.1)
+      '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.22.1)
+      '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.22.1)
+      '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.22.1)
+      '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.22.1)
+      '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.22.1)
+    dev: true
+
   /babel-preset-jest@29.5.0(@babel/core@7.21.3):
     resolution: {integrity: sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -8289,7 +9528,7 @@ packages:
     resolution: {integrity: sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==}
     engines: {node: '>= 10.0.0'}
     dependencies:
-      '@babel/types': 7.21.4
+      '@babel/types': 7.22.4
 
   /bach@1.2.0:
     resolution: {integrity: sha512-bZOOfCb3gXBXbTFXq3OZtGR88LwGeJvzu6szttaIzymOTS4ZttBNOWSv7aLZja2EMycKtRYV0Oa8SNKH/zkxvg==}
@@ -8361,7 +9600,7 @@ packages:
     engines: {node: '>=12'}
     dependencies:
       bin-version: 6.0.0
-      semver: 7.5.0
+      semver: 7.5.1
       semver-truncate: 2.0.0
     dev: false
 
@@ -8507,10 +9746,10 @@ packages:
     dependencies:
       fill-range: 7.0.1
 
-  /broadcast-channel@4.20.2:
-    resolution: {integrity: sha512-v0lJgMzC+MX4e2KCFWYXChZ2mKTqm5mnJGId6tqJp3NfylggbNd8c2uKeP4MQxD2ucKOesY68aN98zwl9d6Tvg==}
+  /broadcast-channel@5.1.0:
+    resolution: {integrity: sha512-wAbP+mtQ28N+iX3scX6Q97UN39ER5jRWOtM3r1BNPLWFOMt3AGmwN9kS3fqwgaUW0tbWHRSfTpsT+pAvrzQz0Q==}
     dependencies:
-      '@babel/runtime': 7.20.7
+      '@babel/runtime': 7.21.0
       oblivious-set: 1.1.1
       p-queue: 6.6.2
       rimraf: 3.0.2
@@ -8609,22 +9848,21 @@ packages:
     requiresBuild: true
     dependencies:
       node-gyp-build: 4.6.0
-    dev: false
 
-  /bull@4.10.4:
-    resolution: {integrity: sha512-o9m/7HjS/Or3vqRd59evBlWCXd9Lp+ALppKseoSKHaykK46SmRjAilX98PgmOz1yeVaurt8D5UtvEt4bUjM3eA==}
-    engines: {node: '>=12'}
+  /bullmq@3.15.0:
+    resolution: {integrity: sha512-U0LSRjuoyIBpnE62T4maCWMYEt3qdBCa1lnlPxYKQmRF/Y+FQ9W6iW5JvNNN+NA5Jet7k0uX71a93EX1zGnrhw==}
     dependencies:
-      cron-parser: 4.7.1
-      debuglog: 1.0.1
-      get-port: 5.1.1
+      cron-parser: 4.8.1
+      glob: 8.1.0
       ioredis: 5.3.2
       lodash: 4.17.21
-      msgpackr: 1.8.1
-      semver: 7.5.0
-      uuid: 8.3.2
+      msgpackr: 1.9.2
+      semver: 7.5.1
+      tslib: 2.5.2
+      uuid: 9.0.0
     transitivePeerDependencies:
       - supports-color
+    dev: false
 
   /busboy@1.6.0:
     resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
@@ -8813,9 +10051,9 @@ packages:
   /caseless@0.12.0:
     resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==}
 
-  /cbor@8.1.0:
-    resolution: {integrity: sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==}
-    engines: {node: '>=12.19'}
+  /cbor@9.0.0:
+    resolution: {integrity: sha512-87cFgOKxjUOnGpNeQMBVER4Mc/rZAk9xC+Ygfx5FLCAUt/tpVHphuZC5fJmp/KSDsEsBEDIPtEt0YbD/GFQw8Q==}
+    engines: {node: '>=16'}
     dependencies:
       nofilter: 3.1.0
 
@@ -8863,7 +10101,6 @@ packages:
       ansi-styles: 3.2.1
       escape-string-regexp: 1.0.5
       supports-color: 5.5.0
-    dev: true
 
   /chalk@3.0.0:
     resolution: {integrity: sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==}
@@ -9022,11 +10259,12 @@ packages:
     resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
     engines: {node: '>=10'}
 
-  /chromatic@6.17.4:
-    resolution: {integrity: sha512-vnlvsv2lkp8BVtTn1OumJzqkDk2qB3pcGxEDIfZtVboKtzIPjnIlGa+c1fVKQe8NvHDU8R39k8klqgKHIXUVJw==}
+  /chromatic@6.18.0:
+    resolution: {integrity: sha512-Sj7xMFGQ6jSTBrsdgMMjSQAP2OMNogg4GXV4djf4kAp6Dp+pY4FwByIagvbtQRjC33kQVi592FS52vMBOBMEzw==}
     hasBin: true
     dependencies:
       '@discoveryjs/json-ext': 0.5.7
+      '@storybook/csf-tools': 7.0.18
       '@types/webpack-env': 1.18.0
       snyk-nodejs-lockfile-parser: 1.49.0
     transitivePeerDependencies:
@@ -9209,6 +10447,7 @@ packages:
   /cluster-key-slot@1.1.2:
     resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
     engines: {node: '>=0.10.0'}
+    dev: false
 
   /co@4.6.0:
     resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==}
@@ -9414,7 +10653,7 @@ packages:
       js-string-escape: 1.0.1
       lodash: 4.17.21
       md5-hex: 3.0.1
-      semver: 7.5.0
+      semver: 7.5.1
       well-known-symbols: 2.0.0
     dev: true
 
@@ -9435,8 +10674,8 @@ packages:
   /constantinople@4.0.1:
     resolution: {integrity: sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==}
     dependencies:
-      '@babel/parser': 7.21.4
-      '@babel/types': 7.21.4
+      '@babel/parser': 7.22.4
+      '@babel/types': 7.22.4
 
   /content-disposition@0.5.4:
     resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
@@ -9510,11 +10749,12 @@ packages:
       readable-stream: 3.6.0
     dev: false
 
-  /cron-parser@4.7.1:
-    resolution: {integrity: sha512-WguFaoQ0hQ61SgsCZLHUcNbAvlK0lypKXu62ARguefYmjzaOXIVRNrAmyXzabTwUn4sQvQLkk6bjH+ipGfw8bA==}
+  /cron-parser@4.8.1:
+    resolution: {integrity: sha512-jbokKWGcyU4gl6jAfX97E1gDpY12DJ1cLJZmoDzaAln/shZ+S3KBFBuA2Q6WeUN4gJf/8klnV1EfvhA2lK5IRQ==}
     engines: {node: '>=12.0.0'}
     dependencies:
-      luxon: 3.2.1
+      luxon: 3.3.0
+    dev: false
 
   /cropperjs@2.0.0-beta.2:
     resolution: {integrity: sha512-jDRSODDGKmi9vp3p/+WXkxMqV/AE+GpSld1U3cHZDRdLy9UykRzurSe8k1dR0TExn45ygCMrv31qkg+K3EeXXw==}
@@ -9536,6 +10776,15 @@ packages:
       node-fetch: 2.6.7
     transitivePeerDependencies:
       - encoding
+    dev: true
+
+  /cross-fetch@3.1.6:
+    resolution: {integrity: sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==}
+    dependencies:
+      node-fetch: 2.6.11
+    transitivePeerDependencies:
+      - encoding
+    dev: false
 
   /cross-spawn@5.1.0:
     resolution: {integrity: sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==}
@@ -9639,18 +10888,14 @@ packages:
   /csstype@3.1.1:
     resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==}
 
-  /custom-event-polyfill@1.0.7:
-    resolution: {integrity: sha512-TDDkd5DkaZxZFM8p+1I3yAlvM3rSr1wbrOliG4yJiwinMZN8z/iGL7BTlDkrJcYTmgUSb4ywVCc3ZaUtOtC76w==}
-    dev: false
-
   /cwise-compiler@1.1.3:
     resolution: {integrity: sha512-WXlK/m+Di8DMMcCjcWr4i+XzcQra9eCdXIJrgh4TUgh0pIS/yJduLxS9JgefsHJ/YVLdgPtXm9r62W92MvanEQ==}
     dependencies:
       uniq: 1.0.1
     dev: false
 
-  /cypress@12.12.0:
-    resolution: {integrity: sha512-UU5wFQ7SMVCR/hyKok/KmzG6fpZgBHHfrXcHzDmPHWrT+UUetxFzQgt7cxCszlwfozckzwkd22dxMwl/vNkWRw==}
+  /cypress@12.13.0:
+    resolution: {integrity: sha512-QJlSmdPk+53Zhy69woJMySZQJoWfEWun3X5OOenGsXjRPVfByVTHorxNehbzhZrEzH9RDUDqVcck0ahtlS+N/Q==}
     engines: {node: ^14.0.0 || ^16.0.0 || >=18.0.0}
     hasBin: true
     requiresBuild: true
@@ -9692,7 +10937,7 @@ packages:
       pretty-bytes: 5.6.0
       proxy-from-env: 1.0.0
       request-progress: 3.0.0
-      semver: 7.5.0
+      semver: 7.5.1
       supports-color: 8.1.1
       tmp: 0.2.1
       untildify: 4.0.0
@@ -9784,9 +11029,6 @@ packages:
       ms: 2.1.2
       supports-color: 8.1.1
 
-  /debuglog@1.0.1:
-    resolution: {integrity: sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw==}
-
   /decamelize-keys@1.1.1:
     resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==}
     engines: {node: '>=0.10.0'}
@@ -9880,6 +11122,7 @@ packages:
 
   /deep-is@0.1.4:
     resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+    dev: true
 
   /deepmerge@4.2.2:
     resolution: {integrity: sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==}
@@ -9978,6 +11221,7 @@ packages:
   /denque@2.1.0:
     resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
     engines: {node: '>=0.10'}
+    dev: false
 
   /depd@1.1.2:
     resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==}
@@ -10240,10 +11484,6 @@ packages:
     resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}
     dev: false
 
-  /entities@4.4.0:
-    resolution: {integrity: sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==}
-    engines: {node: '>=0.12'}
-
   /entities@4.5.0:
     resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
     engines: {node: '>=0.12'}
@@ -10351,7 +11591,6 @@ packages:
 
   /es6-object-assign@1.1.0:
     resolution: {integrity: sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==}
-    dev: true
 
   /es6-promise@4.2.8:
     resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==}
@@ -10464,6 +11703,7 @@ packages:
       optionator: 0.8.3
     optionalDependencies:
       source-map: 0.6.1
+    dev: true
 
   /eslint-formatter-pretty@4.1.0:
     resolution: {integrity: sha512-IsUTtGxF1hrH6lMWiSl1WbGaiP01eT6kzywdY1U+zLc0MP+nwEnUiS9UI8IaOTUhTeQJLlCEWIbXINBH4YJbBQ==}
@@ -10518,6 +11758,35 @@ packages:
       - supports-color
     dev: true
 
+  /eslint-module-utils@2.7.4(@typescript-eslint/parser@5.59.8)(eslint-import-resolver-node@0.3.7)(eslint@8.41.0):
+    resolution: {integrity: sha512-j4GT+rqzCoRKHwURX7pddtIPGySnX9Si/cgMI5ztrcqOPtk5dDEeZ34CQVPphnqkJytlc97Vuk05Um2mJ3gEQA==}
+    engines: {node: '>=4'}
+    peerDependencies:
+      '@typescript-eslint/parser': '*'
+      eslint: '*'
+      eslint-import-resolver-node: '*'
+      eslint-import-resolver-typescript: '*'
+      eslint-import-resolver-webpack: '*'
+    peerDependenciesMeta:
+      '@typescript-eslint/parser':
+        optional: true
+      eslint:
+        optional: true
+      eslint-import-resolver-node:
+        optional: true
+      eslint-import-resolver-typescript:
+        optional: true
+      eslint-import-resolver-webpack:
+        optional: true
+    dependencies:
+      '@typescript-eslint/parser': 5.59.8(eslint@8.41.0)(typescript@5.1.3)
+      debug: 3.2.7(supports-color@8.1.1)
+      eslint: 8.41.0
+      eslint-import-resolver-node: 0.3.7
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.59.5)(eslint@8.40.0):
     resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==}
     engines: {node: '>=4'}
@@ -10551,19 +11820,52 @@ packages:
       - supports-color
     dev: true
 
-  /eslint-plugin-vue@9.12.0(eslint@8.40.0):
-    resolution: {integrity: sha512-xH8PgpDW2WwmFSmRfs/3iWogef1CJzQqX264I65zz77jDuxF2yLy7+GA2diUM8ZNATuSl1+UehMQkb5YEyau5w==}
+  /eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.59.8)(eslint@8.41.0):
+    resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==}
+    engines: {node: '>=4'}
+    peerDependencies:
+      '@typescript-eslint/parser': '*'
+      eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
+    peerDependenciesMeta:
+      '@typescript-eslint/parser':
+        optional: true
+    dependencies:
+      '@typescript-eslint/parser': 5.59.8(eslint@8.41.0)(typescript@5.1.3)
+      array-includes: 3.1.6
+      array.prototype.flat: 1.3.1
+      array.prototype.flatmap: 1.3.1
+      debug: 3.2.7(supports-color@8.1.1)
+      doctrine: 2.1.0
+      eslint: 8.41.0
+      eslint-import-resolver-node: 0.3.7
+      eslint-module-utils: 2.7.4(@typescript-eslint/parser@5.59.8)(eslint-import-resolver-node@0.3.7)(eslint@8.41.0)
+      has: 1.0.3
+      is-core-module: 2.11.0
+      is-glob: 4.0.3
+      minimatch: 3.1.2
+      object.values: 1.1.6
+      resolve: 1.22.1
+      semver: 6.3.0
+      tsconfig-paths: 3.14.1
+    transitivePeerDependencies:
+      - eslint-import-resolver-typescript
+      - eslint-import-resolver-webpack
+      - supports-color
+    dev: true
+
+  /eslint-plugin-vue@9.14.1(eslint@8.41.0):
+    resolution: {integrity: sha512-LQazDB1qkNEKejLe/b5a9VfEbtbczcOaui5lQ4Qw0tbRBbQYREyxxOV5BQgNDTqGPs9pxqiEpbMi9ywuIaF7vw==}
     engines: {node: ^14.17.0 || >=16.0.0}
     peerDependencies:
       eslint: ^6.2.0 || ^7.0.0 || ^8.0.0
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.0(eslint@8.40.0)
-      eslint: 8.40.0
+      '@eslint-community/eslint-utils': 4.4.0(eslint@8.41.0)
+      eslint: 8.41.0
       natural-compare: 1.4.0
       nth-check: 2.1.1
       postcss-selector-parser: 6.0.11
-      semver: 7.5.0
-      vue-eslint-parser: 9.2.1(eslint@8.40.0)
+      semver: 7.5.1
+      vue-eslint-parser: 9.3.0(eslint@8.41.0)
       xml-name-validator: 4.0.0
     transitivePeerDependencies:
       - supports-color
@@ -10643,6 +11945,54 @@ packages:
       - supports-color
     dev: true
 
+  /eslint@8.41.0:
+    resolution: {integrity: sha512-WQDQpzGBOP5IrXPo4Hc0814r4/v2rrIsB0rhT7jtunIalgg6gYXWhRMOejVO8yH21T/FGaxjmFjBMNqcIlmH1Q==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    hasBin: true
+    dependencies:
+      '@eslint-community/eslint-utils': 4.4.0(eslint@8.41.0)
+      '@eslint-community/regexpp': 4.5.0
+      '@eslint/eslintrc': 2.0.3
+      '@eslint/js': 8.41.0
+      '@humanwhocodes/config-array': 0.11.8
+      '@humanwhocodes/module-importer': 1.0.1
+      '@nodelib/fs.walk': 1.2.8
+      ajv: 6.12.6
+      chalk: 4.1.2
+      cross-spawn: 7.0.3
+      debug: 4.3.4(supports-color@8.1.1)
+      doctrine: 3.0.0
+      escape-string-regexp: 4.0.0
+      eslint-scope: 7.2.0
+      eslint-visitor-keys: 3.4.1
+      espree: 9.5.2
+      esquery: 1.4.2
+      esutils: 2.0.3
+      fast-deep-equal: 3.1.3
+      file-entry-cache: 6.0.1
+      find-up: 5.0.0
+      glob-parent: 6.0.2
+      globals: 13.19.0
+      graphemer: 1.4.0
+      ignore: 5.2.4
+      import-fresh: 3.3.0
+      imurmurhash: 0.1.4
+      is-glob: 4.0.3
+      is-path-inside: 3.0.3
+      js-yaml: 4.1.0
+      json-stable-stringify-without-jsonify: 1.0.1
+      levn: 0.4.1
+      lodash.merge: 4.6.2
+      minimatch: 3.1.2
+      natural-compare: 1.4.0
+      optionator: 0.9.1
+      strip-ansi: 6.0.1
+      strip-json-comments: 3.1.1
+      text-table: 0.2.0
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /espree@9.5.2:
     resolution: {integrity: sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -10685,13 +12035,14 @@ packages:
   /estraverse@5.3.0:
     resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
     engines: {node: '>=4.0'}
+    dev: true
 
   /estree-to-babel@3.2.1:
     resolution: {integrity: sha512-YNF+mZ/Wu2FU/gvmzuWtYc8rloubL7wfXCTgouFrnjGVXPA/EeYYA7pupXWrb3Iv1cTBeSSxxJIbK23l4MRNqg==}
     engines: {node: '>=8.3.0'}
     dependencies:
-      '@babel/traverse': 7.21.3
-      '@babel/types': 7.21.5
+      '@babel/traverse': 7.22.4
+      '@babel/types': 7.22.4
       c8: 7.13.0
     transitivePeerDependencies:
       - supports-color
@@ -10700,9 +12051,16 @@ packages:
   /estree-walker@2.0.2:
     resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
 
+  /estree-walker@3.0.3:
+    resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
+    dependencies:
+      '@types/estree': 1.0.1
+    dev: false
+
   /esutils@2.0.3:
     resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
     engines: {node: '>=0.10.0'}
+    dev: true
 
   /etag@1.8.1:
     resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
@@ -10712,7 +12070,7 @@ packages:
   /event-loop-spinner@2.2.0:
     resolution: {integrity: sha512-KB44sV4Mv7uLIkJHJ5qhiZe5um6th2g57nHQL/uqnPHKP2IswoTRWUteEXTJQL4gW++1zqWUni+H2hGkP51c9w==}
     dependencies:
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: false
 
   /event-stream@3.3.4:
@@ -11037,6 +12395,7 @@ packages:
 
   /fast-levenshtein@2.0.6:
     resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+    dev: true
 
   /fast-querystring@1.1.0:
     resolution: {integrity: sha512-LWkjBCZlxjnSanuPpZ6mHswjy8hQv3VcPJsQB3ltUF2zjvrycr0leP3TSTEEfvQ1WEMSRl5YNsGqaft9bjLqEw==}
@@ -11091,7 +12450,7 @@ packages:
       proxy-addr: 2.0.7
       rfdc: 1.3.0
       secure-json-parse: 2.7.0
-      semver: 7.5.0
+      semver: 7.5.1
       tiny-lru: 11.0.1
     transitivePeerDependencies:
       - supports-color
@@ -11157,7 +12516,6 @@ packages:
     dependencies:
       fs-extra: 11.1.0
       ramda: 0.28.0
-    dev: true
 
   /file-type@17.1.6:
     resolution: {integrity: sha512-hlDw5Ev+9e883s0pwUsuuYNu4tD7GgpUnOvykjv1Gya0ZIjuKumthDRua90VUn6/nlRKAjcxLUnHNTIUWwWIiw==}
@@ -11381,7 +12739,6 @@ packages:
     resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
     dependencies:
       is-callable: 1.2.7
-    dev: true
 
   /for-in@1.0.2:
     resolution: {integrity: sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==}
@@ -11473,7 +12830,6 @@ packages:
       graceful-fs: 4.2.11
       jsonfile: 6.1.0
       universalify: 2.0.0
-    dev: true
 
   /fs-extra@7.0.1:
     resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==}
@@ -11647,6 +13003,7 @@ packages:
   /get-port@5.1.1:
     resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==}
     engines: {node: '>=8'}
+    dev: true
 
   /get-stream@3.0.0:
     resolution: {integrity: sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==}
@@ -11840,7 +13197,6 @@ packages:
   /globals@11.12.0:
     resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
     engines: {node: '>=4'}
-    dev: true
 
   /globals@13.19.0:
     resolution: {integrity: sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==}
@@ -11876,7 +13232,6 @@ packages:
     resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==}
     dependencies:
       get-intrinsic: 1.2.0
-    dev: true
 
   /got@11.8.5:
     resolution: {integrity: sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==}
@@ -11911,16 +13266,16 @@ packages:
       p-cancelable: 3.0.0
       responselike: 3.0.0
 
-  /graceful-fs@4.2.10:
-    resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
-    dev: false
-
   /graceful-fs@4.2.11:
     resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
 
   /grapheme-splitter@1.0.4:
     resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
 
+  /graphemer@1.4.0:
+    resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
+    dev: true
+
   /graphql@16.6.0:
     resolution: {integrity: sha512-KPIBPDlW7NxrbT/eh4qPXz5FiFdL5UbaA0XUNz2Rp3Z3hqBSkbj0GVjwFDztsWVauZUWsbKHgMg++sk8UX0bkw==}
     engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0}
@@ -12041,8 +13396,8 @@ packages:
       uglify-js: 3.17.4
     dev: true
 
-  /happy-dom@9.16.0:
-    resolution: {integrity: sha512-goq7grRjIiV2Svb251LWQOo/xm04za2mJ9+assbZJx1KnaVOX1gZBBp4MHbiFNkR6JW7UL81iCtZxCVu+qU5ng==}
+  /happy-dom@9.20.3:
+    resolution: {integrity: sha512-eBsgauT435fXFvQDNcmm5QbGtYzxEzOaX35Ia+h6yP/wwa4xSWZh1CfP+mGby8Hk6Xu59mTkpyf72rUXHNxY7A==}
     dependencies:
       css.escape: 1.5.1
       entities: 4.5.0
@@ -12088,7 +13443,6 @@ packages:
   /has-flag@3.0.0:
     resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
     engines: {node: '>=4'}
-    dev: true
 
   /has-flag@4.0.0:
     resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
@@ -12463,8 +13817,9 @@ packages:
     resolution: {integrity: sha512-/nPtyeX9xPUvxZf+r0518B7uqNKlP+LqNJqSiXFEaa2T71rWIwTVXGH7hB9xO/EVdwa5/pWlFCPwShOW81XIxQ==}
     dev: false
 
-  /install-artifact-from-github@1.3.2:
-    resolution: {integrity: sha512-yCFcLvqk0yQdxx0uJz4t9Z3adDMLAYrcGYv546uRXCSvxE+GqNYhhz/KmrGcUKGI/gVLR9n/e/zM9jX/+ASMJQ==}
+  /install-artifact-from-github@1.3.3:
+    resolution: {integrity: sha512-x79SL0d8WOi1ZjXSTUqqs0GPQZ92YArJAN9O46wgU9wdH2U9ecyyhB9YGDbPe2OLV4ptmt6AZYRQZ2GydQZosQ==}
+    hasBin: true
     dev: false
 
   /internal-slot@1.0.5:
@@ -12500,6 +13855,7 @@ packages:
       standard-as-callback: 2.1.0
     transitivePeerDependencies:
       - supports-color
+    dev: false
 
   /iota-array@1.0.0:
     resolution: {integrity: sha512-pZ2xT+LOHckCatGQ3DcG/a+QuEqvoxqkiL7tvE8nn3uuu+f6i1TtpB5/FtWFbxUuVr5PZCx8KskuGatbJDXOWA==}
@@ -12593,7 +13949,6 @@ packages:
     dependencies:
       call-bind: 1.0.2
       has-tostringtag: 1.0.0
-    dev: true
 
   /is-array-buffer@3.0.2:
     resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==}
@@ -12747,7 +14102,6 @@ packages:
     engines: {node: '>= 0.4'}
     dependencies:
       has-tostringtag: 1.0.0
-    dev: true
 
   /is-glob@3.1.0:
     resolution: {integrity: sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==}
@@ -12804,7 +14158,6 @@ packages:
     dependencies:
       call-bind: 1.0.2
       define-properties: 1.1.4
-    dev: true
 
   /is-negated-glob@1.0.0:
     resolution: {integrity: sha512-czXVVn/QEmgvej1f50BZ648vUI+em0xqMq2Sn+QncCLN4zj1UAxlT+kw/6ggQTOaZPd1HqKQGEqbpQVtJucWug==}
@@ -12952,7 +14305,6 @@ packages:
       for-each: 0.3.3
       gopd: 1.0.1
       has-tostringtag: 1.0.0
-    dev: true
 
   /is-typedarray@1.0.0:
     resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==}
@@ -13036,7 +14388,7 @@ packages:
   /isomorphic-unfetch@3.1.0:
     resolution: {integrity: sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==}
     dependencies:
-      node-fetch: 2.6.7
+      node-fetch: 2.6.11
       unfetch: 4.2.0
     transitivePeerDependencies:
       - encoding
@@ -13054,8 +14406,8 @@ packages:
     resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==}
     engines: {node: '>=8'}
     dependencies:
-      '@babel/core': 7.21.3
-      '@babel/parser': 7.21.8
+      '@babel/core': 7.22.1
+      '@babel/parser': 7.22.4
       '@istanbuljs/schema': 0.1.3
       istanbul-lib-coverage: 3.2.0
       semver: 6.3.0
@@ -13130,7 +14482,7 @@ packages:
       '@jest/expect': 29.5.0
       '@jest/test-result': 29.5.0
       '@jest/types': 29.5.0
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
       chalk: 4.1.2
       co: 4.6.0
       dedent: 0.7.0
@@ -13178,7 +14530,7 @@ packages:
       - ts-node
     dev: true
 
-  /jest-cli@29.5.0(@types/node@20.1.3):
+  /jest-cli@29.5.0(@types/node@20.2.5):
     resolution: {integrity: sha512-L1KcP1l4HtfwdxXNFCL5bmUbLQiKrakMUriBEcc1Vfz6gx31ORKdreuWvmQVBit+1ss9NNR3yxjwfwzZNdQXJw==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     hasBin: true
@@ -13195,7 +14547,7 @@ packages:
       exit: 0.1.2
       graceful-fs: 4.2.11
       import-local: 3.1.0
-      jest-config: 29.5.0(@types/node@20.1.3)
+      jest-config: 29.5.0(@types/node@20.2.5)
       jest-util: 29.5.0
       jest-validate: 29.5.0
       prompts: 2.4.2
@@ -13245,7 +14597,7 @@ packages:
       - supports-color
     dev: true
 
-  /jest-config@29.5.0(@types/node@20.1.3):
+  /jest-config@29.5.0(@types/node@20.2.5):
     resolution: {integrity: sha512-kvDUKBnNJPNBmFFOhDbm59iu1Fii1Q6SxyhXfvylq3UTHbg6o7j/g8k2dZyXWLvfdKB1vAPxNZnMgtKJcmu3kA==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     peerDependencies:
@@ -13260,7 +14612,7 @@ packages:
       '@babel/core': 7.21.3
       '@jest/test-sequencer': 29.5.0
       '@jest/types': 29.5.0
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
       babel-jest: 29.5.0(@babel/core@7.21.3)
       chalk: 4.1.2
       ci-info: 3.7.1
@@ -13329,7 +14681,7 @@ packages:
       '@jest/environment': 29.5.0
       '@jest/fake-timers': 29.5.0
       '@jest/types': 29.5.0
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
       jest-mock: 29.5.0
       jest-util: 29.5.0
     dev: true
@@ -13359,7 +14711,7 @@ packages:
     dependencies:
       '@jest/types': 29.5.0
       '@types/graceful-fs': 4.1.6
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
       anymatch: 3.1.3
       fb-watchman: 2.0.2
       graceful-fs: 4.2.11
@@ -13394,7 +14746,7 @@ packages:
     resolution: {integrity: sha512-Kijeg9Dag6CKtIDA7O21zNTACqD5MD/8HfIV8pdD94vFyFuer52SigdC3IQMhab3vACxXMiFk+yMHNdbqtyTGA==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
-      '@babel/code-frame': 7.18.6
+      '@babel/code-frame': 7.21.4
       '@jest/types': 29.5.0
       '@types/stack-utils': 2.0.1
       chalk: 4.1.2
@@ -13410,7 +14762,7 @@ packages:
     engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
     dependencies:
       '@jest/types': 27.5.1
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
     dev: true
 
   /jest-mock@29.5.0:
@@ -13418,7 +14770,7 @@ packages:
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
       '@jest/types': 29.5.0
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
       jest-util: 29.5.0
     dev: true
 
@@ -13473,7 +14825,7 @@ packages:
       '@jest/test-result': 29.5.0
       '@jest/transform': 29.5.0
       '@jest/types': 29.5.0
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
       chalk: 4.1.2
       emittery: 0.13.1
       graceful-fs: 4.2.11
@@ -13504,7 +14856,7 @@ packages:
       '@jest/test-result': 29.5.0
       '@jest/transform': 29.5.0
       '@jest/types': 29.5.0
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
       chalk: 4.1.2
       cjs-module-lexer: 1.2.2
       collect-v8-coverage: 1.0.1
@@ -13527,18 +14879,18 @@ packages:
     resolution: {integrity: sha512-x7Wolra5V0tt3wRs3/ts3S6ciSQVypgGQlJpz2rsdQYoUKxMxPNaoHMGJN6qAuPJqS+2iQ1ZUn5kl7HCyls84g==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
-      '@babel/core': 7.21.3
-      '@babel/generator': 7.21.3
-      '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.21.3)
-      '@babel/plugin-syntax-typescript': 7.20.0(@babel/core@7.21.3)
-      '@babel/traverse': 7.21.3
-      '@babel/types': 7.21.4
+      '@babel/core': 7.22.1
+      '@babel/generator': 7.22.3
+      '@babel/plugin-syntax-jsx': 7.18.6(@babel/core@7.22.1)
+      '@babel/plugin-syntax-typescript': 7.20.0(@babel/core@7.22.1)
+      '@babel/traverse': 7.22.4
+      '@babel/types': 7.22.4
       '@jest/expect-utils': 29.5.0
       '@jest/transform': 29.5.0
       '@jest/types': 29.5.0
-      '@types/babel__traverse': 7.18.3
+      '@types/babel__traverse': 7.20.0
       '@types/prettier': 2.7.2
-      babel-preset-current-node-syntax: 1.0.1(@babel/core@7.21.3)
+      babel-preset-current-node-syntax: 1.0.1(@babel/core@7.22.1)
       chalk: 4.1.2
       expect: 29.5.0
       graceful-fs: 4.2.11
@@ -13549,7 +14901,7 @@ packages:
       jest-util: 29.5.0
       natural-compare: 1.4.0
       pretty-format: 29.5.0
-      semver: 7.5.0
+      semver: 7.5.1
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -13559,7 +14911,7 @@ packages:
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
       '@jest/types': 29.5.0
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
       chalk: 4.1.2
       ci-info: 3.7.1
       graceful-fs: 4.2.11
@@ -13584,7 +14936,7 @@ packages:
     dependencies:
       '@jest/test-result': 29.5.0
       '@jest/types': 29.5.0
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
       ansi-escapes: 4.3.2
       chalk: 4.1.2
       emittery: 0.13.1
@@ -13603,7 +14955,7 @@ packages:
     resolution: {integrity: sha512-NcrQnevGoSp4b5kg+akIpthoAFHxPBcb5P6mYPY0fUNT+sSvmtu6jlkEle3anczUKIKEbMxFimk9oTP/tpIPgA==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
       jest-util: 29.5.0
       merge-stream: 2.0.0
       supports-color: 8.1.1
@@ -13629,7 +14981,7 @@ packages:
       - ts-node
     dev: true
 
-  /jest@29.5.0(@types/node@20.1.3):
+  /jest@29.5.0(@types/node@20.2.5):
     resolution: {integrity: sha512-juMg3he2uru1QoXX078zTa7pO85QyB9xajZc6bU+d9yEGwrKX6+vGmJQ3UdVZsvTEUARIdObzH68QItim6OSSQ==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     hasBin: true
@@ -13642,7 +14994,7 @@ packages:
       '@jest/core': 29.5.0
       '@jest/types': 29.5.0
       import-local: 3.1.0
-      jest-cli: 29.5.0(@types/node@20.1.3)
+      jest-cli: 29.5.0(@types/node@20.2.5)
     transitivePeerDependencies:
       - '@types/node'
       - supports-color
@@ -13705,7 +15057,6 @@ packages:
 
   /js-tokens@4.0.0:
     resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
-    dev: true
 
   /js-yaml@3.14.1:
     resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==}
@@ -13744,17 +15095,17 @@ packages:
     peerDependencies:
       '@babel/preset-env': ^7.1.6
     dependencies:
-      '@babel/core': 7.21.3
-      '@babel/parser': 7.21.8
-      '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.21.3)
-      '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.21.3)
-      '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.21.3)
-      '@babel/plugin-transform-modules-commonjs': 7.21.2(@babel/core@7.21.3)
-      '@babel/preset-env': 7.21.4(@babel/core@7.21.3)
-      '@babel/preset-flow': 7.18.6(@babel/core@7.21.3)
-      '@babel/preset-typescript': 7.21.0(@babel/core@7.21.3)
-      '@babel/register': 7.21.0(@babel/core@7.21.3)
-      babel-core: 7.0.0-bridge.0(@babel/core@7.21.3)
+      '@babel/core': 7.22.1
+      '@babel/parser': 7.22.4
+      '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.22.1)
+      '@babel/plugin-proposal-nullish-coalescing-operator': 7.18.6(@babel/core@7.22.1)
+      '@babel/plugin-proposal-optional-chaining': 7.21.0(@babel/core@7.22.1)
+      '@babel/plugin-transform-modules-commonjs': 7.21.2(@babel/core@7.22.1)
+      '@babel/preset-env': 7.21.4(@babel/core@7.22.1)
+      '@babel/preset-flow': 7.18.6(@babel/core@7.22.1)
+      '@babel/preset-typescript': 7.21.0(@babel/core@7.22.1)
+      '@babel/register': 7.21.0(@babel/core@7.22.1)
+      babel-core: 7.0.0-bridge.0(@babel/core@7.22.1)
       chalk: 4.1.2
       flow-parser: 0.202.0
       graceful-fs: 4.2.11
@@ -13768,9 +15119,9 @@ packages:
       - supports-color
     dev: true
 
-  /jsdom@21.1.1:
-    resolution: {integrity: sha512-Jjgdmw48RKcdAIQyUD1UdBh2ecH7VqwaXPN3ehoZN6MqgVbMn+lRm1aAT1AsdJRAJpwfa4IpwgzySn61h2qu3w==}
-    engines: {node: '>=14'}
+  /jsdom@22.1.0(bufferutil@4.0.7)(utf-8-validate@6.0.3):
+    resolution: {integrity: sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==}
+    engines: {node: '>=16'}
     peerDependencies:
       canvas: ^2.5.0
     peerDependenciesMeta:
@@ -13778,19 +15129,16 @@ packages:
         optional: true
     dependencies:
       abab: 2.0.6
-      acorn: 8.8.2
-      acorn-globals: 7.0.1
       cssstyle: 3.0.0
       data-urls: 4.0.0
       decimal.js: 10.4.3
       domexception: 4.0.0
-      escodegen: 2.0.0
       form-data: 4.0.0
       html-encoding-sniffer: 3.0.0
       http-proxy-agent: 5.0.0
       https-proxy-agent: 5.0.1
       is-potential-custom-element-name: 1.0.1
-      nwsapi: 2.2.2
+      nwsapi: 2.2.5
       parse5: 7.1.2
       rrweb-cssom: 0.6.0
       saxes: 6.0.0
@@ -13801,7 +15149,7 @@ packages:
       whatwg-encoding: 2.0.0
       whatwg-mimetype: 3.0.0
       whatwg-url: 12.0.1
-      ws: 8.13.0
+      ws: 8.13.0(bufferutil@4.0.7)(utf-8-validate@6.0.3)
       xml-name-validator: 4.0.0
     transitivePeerDependencies:
       - bufferutil
@@ -13818,7 +15166,6 @@ packages:
     resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
     engines: {node: '>=4'}
     hasBin: true
-    dev: true
 
   /json-buffer@3.0.1:
     resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
@@ -13888,16 +15235,15 @@ packages:
       universalify: 2.0.0
     optionalDependencies:
       graceful-fs: 4.2.11
-    dev: true
 
-  /jsonld@8.1.1:
-    resolution: {integrity: sha512-TbtV1hlnoDYxbscazbxcS7seDGV+pc0yktxpMySh0OBFvnLw/TIth0jiQtP/9r+ywuCbtj10XjDNBIkRgiyeUg==}
+  /jsonld@8.2.0:
+    resolution: {integrity: sha512-qHUa9pn3/cdAZw26HY1Jmy9+sHOxaLrveTRWUcrSDx5apTa20bBTe+X4nzI7dlqc+M5GkwQW6RgRdqO6LF5nkw==}
     engines: {node: '>=14'}
     dependencies:
-      '@digitalbazaar/http-client': 3.2.0
+      '@digitalbazaar/http-client': 3.4.1
       canonicalize: 1.0.8
       lru-cache: 6.0.0
-      rdf-canonize: 3.3.0
+      rdf-canonize: 3.4.0
     transitivePeerDependencies:
       - web-streams-polyfill
     dev: false
@@ -13989,24 +15335,24 @@ packages:
     engines: {node: '>=6'}
     dev: true
 
-  /ky-universal@0.10.1(ky@0.30.0):
-    resolution: {integrity: sha512-r8909k+ELKZAxhVA5c440x22hqw5XcMRwLRbgpPQk4JHy3/ddJnvzcnSo5Ww3HdKdNeS3Y8dBgcIYyVahMa46g==}
-    engines: {node: '>=14'}
+  /ky-universal@0.11.0(ky@0.33.3):
+    resolution: {integrity: sha512-65KyweaWvk+uKKkCrfAf+xqN2/epw1IJDtlyCPxYffFCMR8u1sp2U65NtWpnozYfZxQ6IUzIlvUcw+hQ82U2Xw==}
+    engines: {node: '>=14.16'}
     peerDependencies:
-      ky: '>=0.26.0'
-      web-streams-polyfill: '>=3.0.1'
+      ky: '>=0.31.4'
+      web-streams-polyfill: '>=3.2.1'
     peerDependenciesMeta:
       web-streams-polyfill:
         optional: true
     dependencies:
       abort-controller: 3.0.0
-      ky: 0.30.0
+      ky: 0.33.3
       node-fetch: 3.3.1
     dev: false
 
-  /ky@0.30.0:
-    resolution: {integrity: sha512-X/u76z4JtDVq10u1JA5UQfatPxgPaVDMYTrgHyiTpGN2z4TMEJkIHsoSBBSg9SWZEIXTKsi9kHgiQ9o3Y/4yog==}
-    engines: {node: '>=12'}
+  /ky@0.33.3:
+    resolution: {integrity: sha512-CasD9OCEQSFIam2U8efFK81Yeg8vNMTBUqtMOHlrcWQHqUX3HeCl9Dr31u4toV7emlH8Mymk5+9p0lL6mKb/Xw==}
+    engines: {node: '>=14.16'}
     dev: false
 
   /last-run@1.1.1:
@@ -14063,6 +15409,7 @@ packages:
     dependencies:
       prelude-ls: 1.1.2
       type-check: 0.3.2
+    dev: true
 
   /levn@0.4.1:
     resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
@@ -14135,10 +15482,6 @@ packages:
       strip-bom: 2.0.0
     dev: false
 
-  /loadjs@4.2.0:
-    resolution: {integrity: sha512-AgQGZisAlTPbTEzrHPb6q+NYBMD+DP9uvGSIjSUM5uG+0jG15cb8axWpxuOIqrmQjn6scaaH8JwloiP27b2KXA==}
-    dev: false
-
   /local-pkg@0.4.3:
     resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==}
     engines: {node: '>=14'}
@@ -14182,6 +15525,7 @@ packages:
 
   /lodash.defaults@4.2.0:
     resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
+    dev: false
 
   /lodash.difference@4.5.0:
     resolution: {integrity: sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==}
@@ -14213,6 +15557,7 @@ packages:
 
   /lodash.isarguments@3.1.0:
     resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}
+    dev: false
 
   /lodash.isempty@4.4.0:
     resolution: {integrity: sha512-oKMuF3xEeqDltrGMfDxAPGIVMSSRv8tbRSODbrs4KGsRRLEhrW8N8Rd4DRgB2+621hY8A8XwwrTVhXWpxFvMzg==}
@@ -14366,9 +15711,10 @@ packages:
     engines: {node: '>=16.14'}
     dev: true
 
-  /luxon@3.2.1:
-    resolution: {integrity: sha512-QrwPArQCNLAKGO/C+ZIilgIuDnEnKx5QYODdDtbFaxzsbZcc/a7WFq7MhsVYgRlwawLtvOUESTlfJ+hc/USqPg==}
+  /luxon@3.3.0:
+    resolution: {integrity: sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==}
     engines: {node: '>=12'}
+    dev: false
 
   /lz-string@1.5.0:
     resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==}
@@ -14529,10 +15875,10 @@ packages:
     engines: {node: '>= 0.6'}
     dev: true
 
-  /meilisearch@0.32.3:
-    resolution: {integrity: sha512-EOgfBuRE5SiIPIpEDYe2HO0D7a4z5bexIgaAdJFma/dH5hx1kwO+u/qb2g3qKyjG+iA3l8MlmTj/Xd72uahaAw==}
+  /meilisearch@0.32.5:
+    resolution: {integrity: sha512-pVccjGAGP1IDSLg3lx9VhyQjUo7kN8x/HVjSurtb8U24V5/pALpf5H2hj6f60QhJd0Ea4tnGRv8fGr2YqWMo9A==}
     dependencies:
-      cross-fetch: 3.1.5
+      cross-fetch: 3.1.6
     transitivePeerDependencies:
       - encoding
     dev: false
@@ -14844,24 +16190,27 @@ packages:
     engines: {node: '>=12.13'}
     dev: false
 
-  /msgpackr-extract@2.2.0:
-    resolution: {integrity: sha512-0YcvWSv7ZOGl9Od6Y5iJ3XnPww8O7WLcpYMDwX+PAA/uXLDtyw94PJv9GLQV/nnp3cWlDhMoyKZIQLrx33sWog==}
+  /msgpackr-extract@3.0.2:
+    resolution: {integrity: sha512-SdzXp4kD/Qf8agZ9+iTu6eql0m3kWm1A2y1hkpTeVNENutaB0BwHlSvAIaMxwntmRUAUjon2V4L8Z/njd0Ct8A==}
+    hasBin: true
     requiresBuild: true
     dependencies:
-      node-gyp-build-optional-packages: 5.0.3
+      node-gyp-build-optional-packages: 5.0.7
     optionalDependencies:
-      '@msgpackr-extract/msgpackr-extract-darwin-arm64': 2.2.0
-      '@msgpackr-extract/msgpackr-extract-darwin-x64': 2.2.0
-      '@msgpackr-extract/msgpackr-extract-linux-arm': 2.2.0
-      '@msgpackr-extract/msgpackr-extract-linux-arm64': 2.2.0
-      '@msgpackr-extract/msgpackr-extract-linux-x64': 2.2.0
-      '@msgpackr-extract/msgpackr-extract-win32-x64': 2.2.0
+      '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.2
+      '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.2
+      '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.2
+      '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.2
+      '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.2
+      '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.2
+    dev: false
     optional: true
 
-  /msgpackr@1.8.1:
-    resolution: {integrity: sha512-05fT4J8ZqjYlR4QcRDIhLCYKUOHXk7C/xa62GzMKj74l3up9k2QZ3LgFc6qWdsPHl91QA2WLWqWc8b8t7GLNNw==}
+  /msgpackr@1.9.2:
+    resolution: {integrity: sha512-xtDgI3Xv0AAiZWLRGDchyzBwU6aq0rwJ+W+5Y4CZhEWtkl/hJtFFLc+3JtGTw7nz1yquxs7nL8q/yA2aqpflIQ==}
     optionalDependencies:
-      msgpackr-extract: 2.2.0
+      msgpackr-extract: 3.0.2
+    dev: false
 
   /msw-storybook-addon@1.8.0(msw@1.2.1):
     resolution: {integrity: sha512-dw3vZwqjixmiur0vouRSOax7wPSu9Og2Hspy9JZFHf49bZRjwDiLF0Pfn2NXEkGviYJOJiGxS1ejoTiUwoSg4A==}
@@ -14869,10 +16218,10 @@ packages:
       msw: '>=0.35.0 <2.0.0'
     dependencies:
       is-node-process: 1.0.1
-      msw: 1.2.1(typescript@5.0.4)
+      msw: 1.2.1(typescript@5.1.3)
     dev: true
 
-  /msw@1.2.1(typescript@5.0.4):
+  /msw@1.2.1(typescript@5.1.3):
     resolution: {integrity: sha512-bF7qWJQSmKn6bwGYVPXOxhexTCGD5oJSZg8yt8IBClxvo3Dx/1W0zqE1nX9BSWmzRsCKWfeGWcB/vpqV6aclpw==}
     engines: {node: '>=14'}
     hasBin: true
@@ -14901,7 +16250,7 @@ packages:
       path-to-regexp: 6.2.1
       strict-event-emitter: 0.4.6
       type-fest: 2.19.0
-      typescript: 5.0.4
+      typescript: 5.1.3
       yargs: 17.6.2
     transitivePeerDependencies:
       - encoding
@@ -15044,7 +16393,7 @@ packages:
     resolution: {integrity: sha512-8+Ib8rRJ4L0o3kfmyVCL7gzrohyDe0cMFTBa2d364yIrEGMEoetznKJx899YxjybU6bL9SQkYPSBBs1gyYs8Xg==}
     dependencies:
       '@sinonjs/commons': 2.0.0
-      '@sinonjs/fake-timers': 10.0.2
+      '@sinonjs/fake-timers': 10.2.0
       '@sinonjs/text-encoding': 0.7.2
       just-extend: 4.2.1
       path-to-regexp: 1.8.0
@@ -15054,7 +16403,7 @@ packages:
     resolution: {integrity: sha512-eSKV6s+APenqVh8ubJyiu/YhZgxQpGP66ntzUb3lY1xB9ukSRaGnx0AIxI+IM+1+IVYC1oWobgG5L3Lt9ARykQ==}
     engines: {node: '>=10'}
     dependencies:
-      semver: 7.5.0
+      semver: 7.5.1
 
   /node-addon-api@5.0.0:
     resolution: {integrity: sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA==}
@@ -15083,6 +16432,17 @@ packages:
     resolution: {integrity: sha512-KIkvH1jl6b3O7es/0ShyCgWLcfXxlBrLBbP3rOr23WArC66IMcU4DeZEeYEOwnopYhawLTn7/y+YtmASe8DFVQ==}
     dev: true
 
+  /node-fetch@2.6.11:
+    resolution: {integrity: sha512-4I6pdBY1EthSqDmJkiNk3JIT8cswwR9nfeW/cPdUagJYEQG7R95WRH74wpz7ma8Gh/9dI9FP+OU+0E4FvtA55w==}
+    engines: {node: 4.x || >=6.0.0}
+    peerDependencies:
+      encoding: ^0.1.0
+    peerDependenciesMeta:
+      encoding:
+        optional: true
+    dependencies:
+      whatwg-url: 5.0.0
+
   /node-fetch@2.6.7:
     resolution: {integrity: sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==}
     engines: {node: 4.x || >=6.0.0}
@@ -15102,17 +16462,20 @@ packages:
       fetch-blob: 3.2.0
       formdata-polyfill: 4.0.10
 
-  /node-gyp-build-optional-packages@5.0.3:
-    resolution: {integrity: sha512-k75jcVzk5wnnc/FMxsf4udAoTEUv2jY3ycfdSd3yWu6Cnd1oee6/CfZJApyscA4FJOmdoixWwiwOyf16RzD5JA==}
+  /node-gyp-build-optional-packages@5.0.7:
+    resolution: {integrity: sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w==}
+    hasBin: true
+    dev: false
     optional: true
 
   /node-gyp-build@4.6.0:
     resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==}
-    dev: false
+    hasBin: true
 
   /node-gyp@9.3.1:
     resolution: {integrity: sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg==}
     engines: {node: ^12.13 || ^14.13 || >=16}
+    hasBin: true
     dependencies:
       env-paths: 2.2.1
       glob: 7.2.3
@@ -15121,7 +16484,7 @@ packages:
       nopt: 6.0.0
       npmlog: 6.0.2
       rimraf: 3.0.2
-      semver: 7.5.0
+      semver: 7.5.1
       tar: 6.1.13
       which: 2.0.2
     transitivePeerDependencies:
@@ -15145,8 +16508,8 @@ packages:
       is: 3.3.0
     dev: false
 
-  /nodemailer@6.9.2:
-    resolution: {integrity: sha512-4+TYaa/e1nIxQfyw/WzNPYTEZ5OvHIDEnmjs4LPmIfccPQN+2CYKmGHjWixn/chzD3bmUTu5FMfpltizMxqzdg==}
+  /nodemailer@6.9.3:
+    resolution: {integrity: sha512-fy9v3NgTzBngrMFkDsKEj0r02U7jm6XfC3b52eoNV+GCrGj+s8pt5OqhiJdWKuw51zCTdiNR/IUD1z33LIIGpg==}
     engines: {node: '>=6.0.0'}
     dev: false
 
@@ -15184,7 +16547,7 @@ packages:
     dependencies:
       hosted-git-info: 4.1.0
       is-core-module: 2.11.0
-      semver: 7.5.0
+      semver: 7.5.1
       validate-npm-package-license: 3.0.4
     dev: true
 
@@ -15291,8 +16654,8 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: false
 
-  /nwsapi@2.2.2:
-    resolution: {integrity: sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==}
+  /nwsapi@2.2.5:
+    resolution: {integrity: sha512-6xpotnECFy/og7tKSBVmUNft7J3jyXAka4XvG6AUhFWRz+Q/Ljus7znJAA3bxColfQLdS+XsjoodtJfCgeTEFQ==}
     dev: false
 
   /oauth-sign@0.9.0:
@@ -15334,7 +16697,6 @@ packages:
     dependencies:
       call-bind: 1.0.2
       define-properties: 1.1.4
-    dev: true
 
   /object-keys@1.1.1:
     resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
@@ -15404,6 +16766,10 @@ packages:
     resolution: {integrity: sha512-Oh+8fK09mgGmAshFdH6hSVco6KZmd1tTwNFWj35OvzdmJTMZtAkbn05zar2iG3v6sDs1JLEtOiBGNb6BHwkb2w==}
     dev: false
 
+  /obuf@1.1.2:
+    resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==}
+    dev: true
+
   /omggif@1.0.10:
     resolution: {integrity: sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==}
     dev: false
@@ -15479,6 +16845,7 @@ packages:
       prelude-ls: 1.1.2
       type-check: 0.3.2
       word-wrap: 1.2.3
+    dev: true
 
   /optionator@0.9.1:
     resolution: {integrity: sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==}
@@ -15684,7 +17051,7 @@ packages:
     resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
     engines: {node: '>=8'}
     dependencies:
-      '@babel/code-frame': 7.18.6
+      '@babel/code-frame': 7.21.4
       error-ex: 1.3.2
       json-parse-even-better-errors: 2.3.1
       lines-and-columns: 1.2.4
@@ -15727,7 +17094,7 @@ packages:
   /parse5@7.1.2:
     resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
     dependencies:
-      entities: 4.4.0
+      entities: 4.5.0
 
   /parseurl@1.3.3:
     resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
@@ -15856,29 +17223,35 @@ packages:
   /performance-now@2.1.0:
     resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
 
-  /pg-connection-string@2.5.0:
-    resolution: {integrity: sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==}
+  /pg-cloudflare@1.1.0:
+    resolution: {integrity: sha512-tGM8/s6frwuAIyRcJ6nWcIvd3+3NmUKIs6OjviIm1HPPFEt5MzQDOTBQyhPWg/m0kCl95M6gA1JaIXtS8KovOA==}
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /pg-connection-string@2.6.0:
+    resolution: {integrity: sha512-x14ibktcwlHKoHxx9X3uTVW9zIGR41ZB6QNhHb21OPNdCCO3NaRnpJuwKIQSR4u+Yqjx4HCvy7Hh7VSy1U4dGg==}
     dev: false
 
   /pg-int8@1.0.1:
     resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
     engines: {node: '>=4.0.0'}
 
-  /pg-pool@3.6.0(pg@8.10.0):
+  /pg-numeric@1.0.2:
+    resolution: {integrity: sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==}
+    engines: {node: '>=4'}
+    dev: true
+
+  /pg-pool@3.6.0(pg@8.11.0):
     resolution: {integrity: sha512-clFRf2ksqd+F497kWFyM21tMjeikn60oGDmqMT8UBrynEwVEX/5R5xd2sdvdo1cZCFlguORNpVuqxIj+aK4cfQ==}
     peerDependencies:
       pg: '>=8.0'
     dependencies:
-      pg: 8.10.0
+      pg: 8.11.0
     dev: false
 
-  /pg-protocol@1.5.0:
-    resolution: {integrity: sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==}
-    dev: true
-
   /pg-protocol@1.6.0:
     resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==}
-    dev: false
 
   /pg-types@2.2.0:
     resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==}
@@ -15889,9 +17262,23 @@ packages:
       postgres-bytea: 1.0.0
       postgres-date: 1.0.7
       postgres-interval: 1.2.0
+    dev: false
 
-  /pg@8.10.0:
-    resolution: {integrity: sha512-ke7o7qSTMb47iwzOSaZMfeR7xToFdkE71ifIipOAAaLIM0DYzfOAXlgFFmYUIE2BcJtvnVlGCID84ZzCegE8CQ==}
+  /pg-types@4.0.1:
+    resolution: {integrity: sha512-hRCSDuLII9/LE3smys1hRHcu5QGcLs9ggT7I/TCs0IE+2Eesxi9+9RWAAwZ0yaGjxoWICF/YHLOEjydGujoJ+g==}
+    engines: {node: '>=10'}
+    dependencies:
+      pg-int8: 1.0.1
+      pg-numeric: 1.0.2
+      postgres-array: 3.0.2
+      postgres-bytea: 3.0.0
+      postgres-date: 2.0.1
+      postgres-interval: 3.0.0
+      postgres-range: 1.1.3
+    dev: true
+
+  /pg@8.11.0:
+    resolution: {integrity: sha512-meLUVPn2TWgJyLmy7el3fQQVwft4gU5NGyvV0XbD41iU9Jbg8lCH4zexhIkihDzVHJStlt6r088G6/fWeNjhXA==}
     engines: {node: '>= 8.0.0'}
     peerDependencies:
       pg-native: '>=3.0.1'
@@ -15901,11 +17288,13 @@ packages:
     dependencies:
       buffer-writer: 2.0.0
       packet-reader: 1.0.0
-      pg-connection-string: 2.5.0
-      pg-pool: 3.6.0(pg@8.10.0)
+      pg-connection-string: 2.6.0
+      pg-pool: 3.6.0(pg@8.11.0)
       pg-protocol: 1.6.0
       pg-types: 2.2.0
       pgpass: 1.0.5
+    optionalDependencies:
+      pg-cloudflare: 1.1.0
     dev: false
 
   /pgpass@1.0.5:
@@ -16302,20 +17691,50 @@ packages:
   /postgres-array@2.0.0:
     resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
     engines: {node: '>=4'}
+    dev: false
+
+  /postgres-array@3.0.2:
+    resolution: {integrity: sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog==}
+    engines: {node: '>=12'}
+    dev: true
 
   /postgres-bytea@1.0.0:
     resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==}
     engines: {node: '>=0.10.0'}
+    dev: false
+
+  /postgres-bytea@3.0.0:
+    resolution: {integrity: sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==}
+    engines: {node: '>= 6'}
+    dependencies:
+      obuf: 1.1.2
+    dev: true
 
   /postgres-date@1.0.7:
     resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==}
     engines: {node: '>=0.10.0'}
+    dev: false
+
+  /postgres-date@2.0.1:
+    resolution: {integrity: sha512-YtMKdsDt5Ojv1wQRvUhnyDJNSr2dGIC96mQVKz7xufp07nfuFONzdaowrMHjlAzY6GDLd4f+LUHHAAM1h4MdUw==}
+    engines: {node: '>=12'}
+    dev: true
 
   /postgres-interval@1.2.0:
     resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
     engines: {node: '>=0.10.0'}
     dependencies:
       xtend: 4.0.2
+    dev: false
+
+  /postgres-interval@3.0.0:
+    resolution: {integrity: sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==}
+    engines: {node: '>=12'}
+    dev: true
+
+  /postgres-range@1.1.3:
+    resolution: {integrity: sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g==}
+    dev: true
 
   /prebuild-install@7.1.1:
     resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==}
@@ -16338,6 +17757,7 @@ packages:
   /prelude-ls@1.1.2:
     resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==}
     engines: {node: '>= 0.8.0'}
+    dev: true
 
   /prelude-ls@1.2.1:
     resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
@@ -16755,7 +18175,6 @@ packages:
 
   /ramda@0.28.0:
     resolution: {integrity: sha512-9QnLuG/kPVgWvMQ4aODhsBUFKOUmnbUnsSXACv+NCQZcHbeb+v8Lodp8OVxtRULN1/xOyYLLaL6npE6dMq5QTA==}
-    dev: true
 
   /random-seed@0.3.0:
     resolution: {integrity: sha512-y13xtn3kcTlLub3HKWXxJNeC2qK4mB59evwZ5EkeRlolx+Bp2ztF7LbcZmyCnOqlHQrLnfuNbi1sVmm9lPDlDA==}
@@ -16773,10 +18192,6 @@ packages:
     resolution: {integrity: sha512-9CRCUX/w4+fNMzlYgA8GeJz7BZwBPwaGm3FhAm9Hi50k8wNy2CyiJQa8awygWJay87uVVCV0/FwbLcD6+/A9KQ==}
     dev: false
 
-  /rangetouch@2.0.1:
-    resolution: {integrity: sha512-sln+pNSc8NGaHoLzwNBssFSf/rSYkqeBXzX1AtJlkJiUaVSJSbRAWJk+4omsXkN+EJalzkZhWQ3th1m0FpR5xA==}
-    dev: false
-
   /ratelimiter@3.4.1:
     resolution: {integrity: sha512-5FJbRW/Jkkdk29ksedAfWFkQkhbUrMx3QJGwMKAypeIiQf4yrLW+gtPKZiaWt4zPrtw1uGufOjGO7UGM6VllsQ==}
     dev: false
@@ -16800,18 +18215,18 @@ packages:
       minimist: 1.2.8
       strip-json-comments: 2.0.1
 
-  /rdf-canonize@3.3.0:
-    resolution: {integrity: sha512-gfSNkMua/VWC1eYbSkVaL/9LQhFeOh0QULwv7Or0f+po8pMgQ1blYQFe1r9Mv2GJZXw88Cz/drnAnB9UlNnHfQ==}
+  /rdf-canonize@3.4.0:
+    resolution: {integrity: sha512-fUeWjrkOO0t1rg7B2fdyDTvngj+9RlUyL92vOdiB7c0FPguWVsniIMjEtHH+meLBO9rzkUlUzBVXgWrjI8P9LA==}
     engines: {node: '>=12'}
     dependencies:
       setimmediate: 1.0.5
     dev: false
 
-  /re2@1.18.0:
-    resolution: {integrity: sha512-MoCYZlJ9YUgksND9asyNF2/x532daXU/ARp1UeJbQ5flMY6ryKNEhrWt85aw3YluzOJlC3vXpGgK2a1jb0b4GA==}
+  /re2@1.19.0:
+    resolution: {integrity: sha512-y0LcLZgBF3L7mDtNfbghb7dCmChYQO2QsUGklNueAJUH+HAZO8UZUubgNsf6OxRTAQpeE4KMPR7vcpK3+Q+GiA==}
     requiresBuild: true
     dependencies:
-      install-artifact-from-github: 1.3.2
+      install-artifact-from-github: 1.3.3
       nan: 2.17.0
       node-gyp: 9.3.1
     transitivePeerDependencies:
@@ -16829,12 +18244,12 @@ packages:
       react-dom: 18.2.0(react@18.2.0)
     dev: true
 
-  /react-docgen-typescript@2.2.2(typescript@5.0.4):
+  /react-docgen-typescript@2.2.2(typescript@5.1.3):
     resolution: {integrity: sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==}
     peerDependencies:
       typescript: '>= 4.3.x'
     dependencies:
-      typescript: 5.0.4
+      typescript: 5.1.3
     dev: true
 
   /react-docgen@6.0.0-alpha.3:
@@ -16842,8 +18257,8 @@ packages:
     engines: {node: '>=12.0.0'}
     hasBin: true
     dependencies:
-      '@babel/core': 7.21.3
-      '@babel/generator': 7.21.3
+      '@babel/core': 7.22.1
+      '@babel/generator': 7.22.3
       ast-types: 0.14.2
       commander: 2.20.3
       doctrine: 3.0.0
@@ -17033,7 +18448,7 @@ packages:
       ast-types: 0.15.2
       esprima: 4.0.1
       source-map: 0.6.1
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: true
 
   /recast@0.22.0:
@@ -17044,7 +18459,7 @@ packages:
       ast-types: 0.15.2
       esprima: 4.0.1
       source-map: 0.6.1
-      tslib: 2.5.0
+      tslib: 2.5.2
     dev: true
 
   /recast@0.23.1:
@@ -17055,8 +18470,7 @@ packages:
       ast-types: 0.16.1
       esprima: 4.0.1
       source-map: 0.6.1
-      tslib: 2.5.0
-    dev: true
+      tslib: 2.5.2
 
   /rechoir@0.6.2:
     resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==}
@@ -17079,6 +18493,7 @@ packages:
   /redis-errors@1.2.0:
     resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}
     engines: {node: '>=4'}
+    dev: false
 
   /redis-info@3.1.0:
     resolution: {integrity: sha512-ER4L9Sh/vm63DkIE0bkSjxluQlioBiBgf5w1UuldaW/3vPcecdljVDisZhmnCMvsxHNiARTTDDHGg9cGwTfrKg==}
@@ -17096,6 +18511,7 @@ packages:
     engines: {node: '>=4'}
     dependencies:
       redis-errors: 1.2.0
+    dev: false
 
   /redis@4.5.1:
     resolution: {integrity: sha512-oxXSoIqMJCQVBTfxP6BNTCtDMyh9G6Vi5wjdPdV/sRKkufyZslDqCScSGcOr6XGR/reAWZefz7E4leM31RgdBA==}
@@ -17444,8 +18860,8 @@ packages:
       seedrandom: 2.4.2
     dev: false
 
-  /rollup@3.21.6:
-    resolution: {integrity: sha512-SXIICxvxQxR3D4dp/3LDHZIJPC8a4anKMHd4E3Jiz2/JnY+2bEjqrOokAauc5ShGVNFHlEFjBXAXlaxkJqIqSg==}
+  /rollup@3.23.0:
+    resolution: {integrity: sha512-h31UlwEi7FHihLe1zbk+3Q7z1k/84rb9BSwmBSr/XjOCEaBJ2YyedQDuM0t/kfOS0IxM+vk1/zI9XxYj9V+NJQ==}
     engines: {node: '>=14.18.0', npm: '>=8.0.0'}
     hasBin: true
     optionalDependencies:
@@ -17617,6 +19033,14 @@ packages:
     hasBin: true
     dependencies:
       lru-cache: 6.0.0
+    dev: true
+
+  /semver@7.5.1:
+    resolution: {integrity: sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==}
+    engines: {node: '>=10'}
+    hasBin: true
+    dependencies:
+      lru-cache: 6.0.0
 
   /send@0.18.0:
     resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
@@ -17708,7 +19132,7 @@ packages:
       detect-libc: 2.0.1
       node-addon-api: 5.0.0
       prebuild-install: 7.1.1
-      semver: 7.5.0
+      semver: 7.5.1
       simple-get: 4.0.1
       tar-fs: 2.1.1
       tunnel-agent: 0.6.0
@@ -17723,7 +19147,7 @@ packages:
       detect-libc: 2.0.1
       node-addon-api: 6.1.0
       prebuild-install: 7.1.1
-      semver: 7.5.0
+      semver: 7.5.1
       simple-get: 4.0.1
       tar-fs: 2.1.1
       tunnel-agent: 0.6.0
@@ -17816,8 +19240,8 @@ packages:
     resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
     dev: true
 
-  /slacc-android-arm-eabi@0.0.7:
-    resolution: {integrity: sha512-6TikZlR1jsQscxwphhrf0U4xbsRy6zKJ0zmEULopTzbohgo5OLdZ7L3tQazkYlaaFe3YjGnVLW3FfGhhrajVog==}
+  /slacc-android-arm-eabi@0.0.9:
+    resolution: {integrity: sha512-T5P5kJ5UwW3UMoPXqqHh9TpCnuCJDCoivoiuONDXrYPYKF8sKDPMVVg1x/KU9/m7e562x9vAMBrIyqFFbEW0Jw==}
     engines: {node: '>= 10'}
     cpu: [arm]
     os: [android]
@@ -17825,8 +19249,8 @@ packages:
     dev: false
     optional: true
 
-  /slacc-android-arm64@0.0.7:
-    resolution: {integrity: sha512-aol/9Rg0Hfqu81hpK+HXcx9sGYu4qqYU+djBCgLtb8I6ZMdWUdE0dp8ACBoTOmYn34hYGcUu4FlJUZ8r7Utucg==}
+  /slacc-android-arm64@0.0.9:
+    resolution: {integrity: sha512-bcKB3ukcI5wWJa2clK/5cy6a4TKp51DRkdRuFgKLG05gBj1jbH+7+8iBPojljeY28LC2frmwVHGj3vDmkFUeYg==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [android]
@@ -17834,8 +19258,8 @@ packages:
     dev: false
     optional: true
 
-  /slacc-darwin-arm64@0.0.7:
-    resolution: {integrity: sha512-PkV7rO/c9AImNYDacP+kxtOjVuxjy06IIOAxbWerIWvoeqsCNRtiF/dh+OqIACRFBuHIDe0oAyUCEMGUTnzjyQ==}
+  /slacc-darwin-arm64@0.0.9:
+    resolution: {integrity: sha512-EspX0Hj6t0Afxbsyc6rY9mTOUQQrPVtWPwwNRaljGRorPyRDDefrU1OnJXRcwcIp0oCZrRrivRYlO7lai63EMw==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [darwin]
@@ -17843,16 +19267,16 @@ packages:
     dev: false
     optional: true
 
-  /slacc-darwin-universal@0.0.7:
-    resolution: {integrity: sha512-Y9zXpL40m4Yq3dE5vdnAgfmn0Fxc0Bf0ixC9TSl96gKeIZEd6drkjfpHFdsIDNImzOksIAUo0HHiDdbEfE7zdQ==}
+  /slacc-darwin-universal@0.0.9:
+    resolution: {integrity: sha512-oQySg+9MPyKI9rwwwhmSZQkPks2/rq3k1P5HKwUgnaFZDvDtS/hpDycB3BxSDqWdD5kVA8PLCVa8pt9T5KyKfg==}
     engines: {node: '>= 10'}
     os: [darwin]
     requiresBuild: true
     dev: false
     optional: true
 
-  /slacc-darwin-x64@0.0.7:
-    resolution: {integrity: sha512-yKaGjX2YJl1QHe4NgqQVsY83jees3hjFxEUPoKpuZEQzWbMNn0XSyceFRGXIk1oDqiKU40UcsdcCedjYjSEd0Q==}
+  /slacc-darwin-x64@0.0.9:
+    resolution: {integrity: sha512-9Xp7mVKKF2QvDiIZOBgwsDdL/+95KBiFTdbo+XtH6YKoh6zNw0aPpkA3JojsdSMYdGHUrxl8b7avhzI0USqeEg==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [darwin]
@@ -17860,8 +19284,17 @@ packages:
     dev: false
     optional: true
 
-  /slacc-linux-arm-gnueabihf@0.0.7:
-    resolution: {integrity: sha512-pdWMdQeX6uA9JfSoWo9EHH0yRiwXKMbaKoS9gflDSyt/hjeR3Qx/KK7Wihd7HeXx7njlNdpr9ycTRmm5NgapQQ==}
+  /slacc-freebsd-x64@0.0.9:
+    resolution: {integrity: sha512-jRd8WmXZLU2mcxV7SN8CzZzGiwbpxtaTjLwrYMTryQZ2TFr1xd1r5mQfTN5sBiwu3tnyK5dmHnRAPy+215mOkQ==}
+    engines: {node: '>= 10'}
+    cpu: [x64]
+    os: [freebsd]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /slacc-linux-arm-gnueabihf@0.0.9:
+    resolution: {integrity: sha512-nhP6+jgd30sq+zFxFW7fGhnPwCfCCU0l1JKk3ORGFMl7wH7ippTDd1xGapKq7N+zgdgURbyj83P3wWb2gcRZ1w==}
     engines: {node: '>= 10'}
     cpu: [arm]
     os: [linux]
@@ -17869,8 +19302,8 @@ packages:
     dev: false
     optional: true
 
-  /slacc-linux-arm64-gnu@0.0.7:
-    resolution: {integrity: sha512-hz9TK/w6fxeNZXyFzuLq5cJD/XRyJbo6BaIdW+VrKKnb9nkLnWlqDQtdtJk7Fw7zHjdY3Uqufjwm0iT6qBVpUQ==}
+  /slacc-linux-arm64-gnu@0.0.9:
+    resolution: {integrity: sha512-x7v0rDe0KNVe1Hl6/XCtkCpqdT283pyVaUmk+af0AnoesNRjYEK8DBc8i53N4nhotionHzPIZfu5gPAFkf6RhA==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [linux]
@@ -17878,8 +19311,8 @@ packages:
     dev: false
     optional: true
 
-  /slacc-linux-arm64-musl@0.0.7:
-    resolution: {integrity: sha512-wCDAYL7e+lh3XL7g87Ui/Bb2Ap9GcBqeJuj2yHIx6MYC8ontwFSXhqRTmd2zmPLmZA5Nc11aKGN11YNu0Pnwlw==}
+  /slacc-linux-arm64-musl@0.0.9:
+    resolution: {integrity: sha512-jyq/ylITHIXTQX5ZqAbi7Mn5SdRgYJi+uEoUCi5UhoXb9LjpNzhkFuY29Je3IkVIIV7AEcAxIlvjdymXdzcF5w==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [linux]
@@ -17887,8 +19320,8 @@ packages:
     dev: false
     optional: true
 
-  /slacc-linux-x64-gnu@0.0.7:
-    resolution: {integrity: sha512-E5+2cveizpfHXCk/Hu5VfslWFeDVw47nywODiJ8CsofT2l5ITfYPMFEBXm9ORY25mGBTgsO6lJYiF9Hz4FlS9Q==}
+  /slacc-linux-x64-gnu@0.0.9:
+    resolution: {integrity: sha512-Xs/F81H7cKhlIBigFID6CJlgjy0NeDUGV1CI1MI5mSVHsVI8dUO8zXWETjo6o8krJPgfjT5Jd4tAgvUFct5hng==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [linux]
@@ -17896,8 +19329,8 @@ packages:
     dev: false
     optional: true
 
-  /slacc-win32-arm64-msvc@0.0.7:
-    resolution: {integrity: sha512-3a+qnkZbP+Pr5RZuzd0Vi1uCal137QiJajRAWT4r7qwu+Zidd50x2oikQ4rAegqZVTm8qTwVmWA+WmH8WHI7iw==}
+  /slacc-win32-arm64-msvc@0.0.9:
+    resolution: {integrity: sha512-C+H0VkKbEEnRbcXRIG5rIaXlg7IZw3o1BbvqA71B8ouQRCu/dNRuH9EQsOYXWltndY42zZi8IupNIwydTUg+Mg==}
     engines: {node: '>= 10'}
     cpu: [arm64]
     os: [win32]
@@ -17905,8 +19338,8 @@ packages:
     dev: false
     optional: true
 
-  /slacc-win32-x64-msvc@0.0.7:
-    resolution: {integrity: sha512-ydFdZ7wEXQPsw2Tg+yG9uJdCGTehyPtrWBVUMa7fojr3j1gbtThXS2l9Ad/6fYYi2VwdaYPLWbwV3GYElPGL8g==}
+  /slacc-win32-x64-msvc@0.0.9:
+    resolution: {integrity: sha512-bElMnBbeMatCtVp2/+hBS6Z+846nQImEul9nBEr4gfezHotOM6MqR6PI7UQQzGhozpgwiDg2l1ub1MdOIgYizg==}
     engines: {node: '>= 10'}
     cpu: [x64]
     os: [win32]
@@ -17914,21 +19347,22 @@ packages:
     dev: false
     optional: true
 
-  /slacc@0.0.7:
-    resolution: {integrity: sha512-rwi2F3oJaGPST9JdCoUd5fnSZaoZFgTL00GFKhKufT48uwtUEAHlOL0t8gEVmon71X+53f9nEdsGWhwtOutJTQ==}
+  /slacc@0.0.9:
+    resolution: {integrity: sha512-BwhjD3daQB3VIx7GxkComMYrnkWuMt4YmDAueMMchblfUUBbP8EcuonJ1Bz9nqtRn1mAH2YPrrRDP95akM+ZuQ==}
     engines: {node: '>= 10'}
     optionalDependencies:
-      slacc-android-arm-eabi: 0.0.7
-      slacc-android-arm64: 0.0.7
-      slacc-darwin-arm64: 0.0.7
-      slacc-darwin-universal: 0.0.7
-      slacc-darwin-x64: 0.0.7
-      slacc-linux-arm-gnueabihf: 0.0.7
-      slacc-linux-arm64-gnu: 0.0.7
-      slacc-linux-arm64-musl: 0.0.7
-      slacc-linux-x64-gnu: 0.0.7
-      slacc-win32-arm64-msvc: 0.0.7
-      slacc-win32-x64-msvc: 0.0.7
+      slacc-android-arm-eabi: 0.0.9
+      slacc-android-arm64: 0.0.9
+      slacc-darwin-arm64: 0.0.9
+      slacc-darwin-universal: 0.0.9
+      slacc-darwin-x64: 0.0.9
+      slacc-freebsd-x64: 0.0.9
+      slacc-linux-arm-gnueabihf: 0.0.9
+      slacc-linux-arm64-gnu: 0.0.9
+      slacc-linux-arm64-musl: 0.0.9
+      slacc-linux-x64-gnu: 0.0.9
+      slacc-win32-arm64-msvc: 0.0.9
+      slacc-win32-x64-msvc: 0.0.9
     dev: false
 
   /slash@3.0.0:
@@ -18020,7 +19454,7 @@ packages:
       lodash.topairs: 4.3.0
       micromatch: 4.0.5
       p-map: 4.0.0
-      semver: 7.5.0
+      semver: 7.5.1
       snyk-config: 5.1.0
       tslib: 1.14.1
       uuid: 8.3.2
@@ -18204,6 +19638,7 @@ packages:
 
   /standard-as-callback@2.1.0:
     resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==}
+    dev: false
 
   /start-server-and-test@2.0.0:
     resolution: {integrity: sha512-UqKLw0mJbfrsG1jcRLTUlvuRi9sjNuUiDOLI42r7R5fA9dsFoywAy9DoLXNYys9B886E4RCKb+qM1Gzu96h7DQ==}
@@ -18248,11 +19683,11 @@ packages:
     resolution: {integrity: sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==}
     dev: true
 
-  /storybook@7.0.10:
-    resolution: {integrity: sha512-L36+Um+Ra8AKTvv84ODFJfuthmWnR1Lc6pjslcb8LYO+PVlqEOeqSknmTcKntDYwgvKx5lg62urtJxzGdwO0yw==}
+  /storybook@7.0.18:
+    resolution: {integrity: sha512-FXMmTiomSlLPTHty7vGLr0prPf6pCV07EwAmNOYYYTskitEYV0R7hlhawByd7HuobjIhHvSTKesa1Whl86zLNA==}
     hasBin: true
     dependencies:
-      '@storybook/cli': 7.0.10
+      '@storybook/cli': 7.0.18
     transitivePeerDependencies:
       - bufferutil
       - encoding
@@ -18516,7 +19951,6 @@ packages:
     engines: {node: '>=4'}
     dependencies:
       has-flag: 3.0.0
-    dev: true
 
   /supports-color@7.2.0:
     resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
@@ -18570,8 +20004,8 @@ packages:
     resolution: {integrity: sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==}
     dev: true
 
-  /systeminformation@5.17.12:
-    resolution: {integrity: sha512-I3pfMW2vue53u+X08BNxaJieaHkRoMMKjWetY9lbYJeWFaeWPO6P4FkNc4XOCX8F9vbQ0HqQ25RJoz3U/B7liw==}
+  /systeminformation@5.17.16:
+    resolution: {integrity: sha512-dl2QLa7yp9QbBl9um+51CAr3p/40tbz+f34X1lUXkk1SnDcNeJR2iWu/8HD7GM2yRukmy3RCRXFYcPZs0lCs0Q==}
     engines: {node: '>=8.0.0'}
     os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android]
     hasBin: true
@@ -18708,8 +20142,8 @@ packages:
       real-require: 0.2.0
     dev: false
 
-  /three@0.151.3:
-    resolution: {integrity: sha512-+vbuqxFy8kzLeO5MgpBHUvP/EAiecaDwDuOPPDe6SbrZr96kccF0ktLngXc7xA7bzyd3N0t2f6mw3Z9y6JCojQ==}
+  /three@0.153.0:
+    resolution: {integrity: sha512-OCP2/uQR6GcDpSLnJt/3a4mdS0kNWcbfUXIwLoEMgLzEUIVIYsSDwskpmOii/AkDM+BBwrl6+CKgrjX9+E2aWg==}
     dev: false
 
   /throttle-debounce@5.0.0:
@@ -18767,8 +20201,8 @@ packages:
     engines: {node: '>=12'}
     dev: false
 
-  /tinybench@2.4.0:
-    resolution: {integrity: sha512-iyziEiyFxX4kyxSp+MtY1oCH/lvjH3PxFN8PGCDeqcZWAJ/i+9y+nL85w99PxVzrIvew/GSkSbDYtiGVa85Afg==}
+  /tinybench@2.5.0:
+    resolution: {integrity: sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA==}
     dev: true
 
   /tinycolor2@1.6.0:
@@ -18922,7 +20356,6 @@ packages:
   /ts-dedent@2.2.0:
     resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==}
     engines: {node: '>=6.10'}
-    dev: true
 
   /ts-map@1.0.3:
     resolution: {integrity: sha512-vDWbsl26LIcPGmDpoVzjEP6+hvHZkBkLW7JpvwbCv/5IYPJlsbzCVXY3wsCeAxAUeTclNOUZxnLdGh3VBD/J6w==}
@@ -18978,6 +20411,9 @@ packages:
   /tslib@2.5.0:
     resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==}
 
+  /tslib@2.5.2:
+    resolution: {integrity: sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==}
+
   /tsutils@3.21.0(typescript@5.0.4):
     resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
     engines: {node: '>= 6'}
@@ -18988,6 +20424,16 @@ packages:
       typescript: 5.0.4
     dev: true
 
+  /tsutils@3.21.0(typescript@5.1.3):
+    resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
+    engines: {node: '>= 6'}
+    peerDependencies:
+      typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta'
+    dependencies:
+      tslib: 1.14.1
+      typescript: 5.1.3
+    dev: true
+
   /tunnel-agent@0.6.0:
     resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
     dependencies:
@@ -19010,6 +20456,7 @@ packages:
     engines: {node: '>= 0.8.0'}
     dependencies:
       prelude-ls: 1.1.2
+    dev: true
 
   /type-check@0.4.0:
     resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
@@ -19055,7 +20502,6 @@ packages:
   /type-fest@2.19.0:
     resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==}
     engines: {node: '>=12.20'}
-    dev: true
 
   /type-is@1.6.18:
     resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
@@ -19073,16 +20519,10 @@ packages:
     resolution: {integrity: sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==}
     dev: false
 
-  /typedarray-to-buffer@3.1.5:
-    resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==}
-    dependencies:
-      is-typedarray: 1.0.0
-    dev: false
-
   /typedarray@0.0.6:
     resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==}
 
-  /typeorm@0.3.16(ioredis@5.3.2)(pg@8.10.0):
+  /typeorm@0.3.16(ioredis@5.3.2)(pg@8.11.0):
     resolution: {integrity: sha512-wJ4Qy1oqRKNDdZiBTTaVMqwo/XxC52Q7uNPTjltPgLhvIW173bL6Iad0lhptMOsFlpixFPaUu3PNziaRBwX2Zw==}
     engines: {node: '>= 12.9.0'}
     hasBin: true
@@ -19151,7 +20591,7 @@ packages:
       glob: 8.1.0
       ioredis: 5.3.2
       mkdirp: 2.1.6
-      pg: 8.10.0
+      pg: 8.11.0
       reflect-metadata: 0.1.13
       sha.js: 2.4.11
       tslib: 2.5.0
@@ -19171,6 +20611,12 @@ packages:
     resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==}
     engines: {node: '>=12.20'}
     hasBin: true
+    dev: true
+
+  /typescript@5.1.3:
+    resolution: {integrity: sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==}
+    engines: {node: '>=14.17'}
+    hasBin: true
 
   /ufo@1.1.1:
     resolution: {integrity: sha512-MvlCc4GHrmZdAllBc0iUDowff36Q9Ndw/UzqmEKyrfSzokTd9ZCy1i+IIk5hrYKkjoYVQyNbrw7/F8XJ2rEwTg==}
@@ -19230,16 +20676,16 @@ packages:
       undertaker-registry: 1.0.1
     dev: false
 
-  /undici@5.16.0:
-    resolution: {integrity: sha512-KWBOXNv6VX+oJQhchXieUznEmnJMqgXMbs0xxH2t8q/FUAWSJvOSr/rMaZKnX5RIVq7JDn0JbP4BOnKG2SGXLQ==}
+  /undici@5.21.0:
+    resolution: {integrity: sha512-HOjK8l6a57b2ZGXOcUsI5NLfoTrfmbOl90ixJDl0AEFG4wgHNDQxtZy15/ZQp7HhjkpaGlp/eneMgtsu1dIlUA==}
     engines: {node: '>=12.18'}
     dependencies:
       busboy: 1.6.0
     dev: false
 
-  /undici@5.21.0:
-    resolution: {integrity: sha512-HOjK8l6a57b2ZGXOcUsI5NLfoTrfmbOl90ixJDl0AEFG4wgHNDQxtZy15/ZQp7HhjkpaGlp/eneMgtsu1dIlUA==}
-    engines: {node: '>=12.18'}
+  /undici@5.22.1:
+    resolution: {integrity: sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==}
+    engines: {node: '>=14.0'}
     dependencies:
       busboy: 1.6.0
     dev: false
@@ -19347,7 +20793,6 @@ packages:
   /universalify@2.0.0:
     resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==}
     engines: {node: '>= 10.0.0'}
-    dev: true
 
   /unload@2.4.1:
     resolution: {integrity: sha512-IViSAm8Z3sRBYA+9wc0fLQmU9Nrxb16rcDmIiR6Y9LJSZzI7QY5QsDhqPpKOjAn0O9/kfK1TfNEMMAGPTIraPw==}
@@ -19388,8 +20833,8 @@ packages:
     engines: {node: '>=8'}
     dev: true
 
-  /unzipper@0.10.11:
-    resolution: {integrity: sha512-+BrAq2oFqWod5IESRjL3S8baohbevGcVA+teAIOYWM3pDVdseogqbzhhvvmiyQrUNKFUnDMtELW3X8ykbyDCJw==}
+  /unzipper@0.10.14:
+    resolution: {integrity: sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g==}
     dependencies:
       big-integer: 1.6.51
       binary: 0.3.0
@@ -19397,7 +20842,7 @@ packages:
       buffer-indexof-polyfill: 1.0.2
       duplexer2: 0.1.4
       fstream: 1.0.12
-      graceful-fs: 4.2.10
+      graceful-fs: 4.2.11
       listenercount: 1.0.1
       readable-stream: 2.3.7
       setimmediate: 1.0.5
@@ -19430,10 +20875,6 @@ packages:
       requires-port: 1.0.0
     dev: false
 
-  /url-polyfill@1.1.12:
-    resolution: {integrity: sha512-mYFmBHCapZjtcNHW0MDq9967t+z4Dmg5CJ0KqysK3+ZbyoNOWQHksGCTWwDhxGXllkWlOc10Xfko6v4a3ucM6A==}
-    dev: false
-
   /urlsafe-base64@1.0.0:
     resolution: {integrity: sha512-RtuPeMy7c1UrHwproMZN9gN6kiZ0SvJwRaEzwZY0j9MypEkFqyBaKv176jvlPtg58Zh36bOkS0NFABXMHvvGCA==}
     dev: false
@@ -19453,13 +20894,12 @@ packages:
     resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==}
     engines: {node: '>=0.10.0'}
 
-  /utf-8-validate@5.0.10:
-    resolution: {integrity: sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==}
+  /utf-8-validate@6.0.3:
+    resolution: {integrity: sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==}
     engines: {node: '>=6.14.2'}
     requiresBuild: true
     dependencies:
       node-gyp-build: 4.6.0
-    dev: false
 
   /util-deprecate@1.0.2:
     resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
@@ -19472,7 +20912,6 @@ packages:
       is-generator-function: 1.0.10
       is-typed-array: 1.1.10
       which-typed-array: 1.1.9
-    dev: true
 
   /utils-merge@1.0.1:
     resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==}
@@ -19598,8 +21037,8 @@ packages:
       replace-ext: 1.0.1
     dev: false
 
-  /vite-node@0.31.0(@types/node@20.1.3)(sass@1.62.1):
-    resolution: {integrity: sha512-8x1x1LNuPvE2vIvkSB7c1mApX5oqlgsxzHQesYF7l5n1gKrEmrClIiZuOFbFDQcjLsmcWSwwmrWrcGWm9Fxc/g==}
+  /vite-node@0.31.4(@types/node@20.2.5)(sass@1.62.1):
+    resolution: {integrity: sha512-uzL377GjJtTbuc5KQxVbDu2xfU/x0wVjUtXQR2ihS21q/NK6ROr4oG0rsSkBBddZUVCwzfx22in76/0ZZHXgkQ==}
     engines: {node: '>=v14.18.0'}
     hasBin: true
     dependencies:
@@ -19608,7 +21047,7 @@ packages:
       mlly: 1.2.0
       pathe: 1.1.0
       picocolors: 1.0.0
-      vite: 4.3.5(@types/node@20.1.3)(sass@1.62.1)
+      vite: 4.3.9(@types/node@20.2.5)(sass@1.62.1)
     transitivePeerDependencies:
       - '@types/node'
       - less
@@ -19623,8 +21062,8 @@ packages:
     resolution: {integrity: sha512-irjKcKXRn7v5bPAg4mAbsS6DgibpP1VUFL9tlgxU6lloK6V9yw9qCZkS+s2PtbkZpWNzr3TN3zVJAc6J7gJZmA==}
     dev: true
 
-  /vite@4.3.5(@types/node@20.1.3)(sass@1.62.1):
-    resolution: {integrity: sha512-0gEnL9wiRFxgz40o/i/eTBwm+NEbpUeTWhzKrZDSdKm6nplj+z4lKz8ANDgildxHm47Vg8EUia0aicKbawUVVA==}
+  /vite@4.3.9(@types/node@20.2.5)(sass@1.62.1):
+    resolution: {integrity: sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==}
     engines: {node: ^14.18.0 || >=16.0.0}
     hasBin: true
     peerDependencies:
@@ -19648,28 +21087,28 @@ packages:
       terser:
         optional: true
     dependencies:
-      '@types/node': 20.1.3
+      '@types/node': 20.2.5
       esbuild: 0.17.18
       postcss: 8.4.23
-      rollup: 3.21.6
+      rollup: 3.23.0
       sass: 1.62.1
     optionalDependencies:
       fsevents: 2.3.2
 
-  /vitest-fetch-mock@0.2.2(vitest@0.31.0):
+  /vitest-fetch-mock@0.2.2(vitest@0.31.4):
     resolution: {integrity: sha512-XmH6QgTSjCWrqXoPREIdbj40T7i1xnGmAsTAgfckoO75W1IEHKR8hcPCQ7SO16RsdW1t85oUm6pcQRLeBgjVYQ==}
     engines: {node: '>=14.14.0'}
     peerDependencies:
       vitest: '>=0.16.0'
     dependencies:
       cross-fetch: 3.1.5
-      vitest: 0.31.0(happy-dom@9.16.0)(sass@1.62.1)
+      vitest: 0.31.4(happy-dom@9.20.3)(sass@1.62.1)
     transitivePeerDependencies:
       - encoding
     dev: true
 
-  /vitest@0.31.0(happy-dom@9.16.0)(sass@1.62.1):
-    resolution: {integrity: sha512-JwWJS9p3GU9GxkG7eBSmr4Q4x4bvVBSswaCFf1PBNHiPx00obfhHRJfgHcnI0ffn+NMlIh9QGvG75FlaIBdKGA==}
+  /vitest@0.31.4(happy-dom@9.20.3)(sass@1.62.1):
+    resolution: {integrity: sha512-GoV0VQPmWrUFOZSg3RpQAPN+LPmHg2/gxlMNJlyxJihkz6qReHDV6b0pPDcqFLNEPya4tWJ1pgwUNP9MLmUfvQ==}
     engines: {node: '>=v14.18.0'}
     hasBin: true
     peerDependencies:
@@ -19699,31 +21138,31 @@ packages:
       webdriverio:
         optional: true
     dependencies:
-      '@types/chai': 4.3.4
+      '@types/chai': 4.3.5
       '@types/chai-subset': 1.3.3
-      '@types/node': 20.1.3
-      '@vitest/expect': 0.31.0
-      '@vitest/runner': 0.31.0
-      '@vitest/snapshot': 0.31.0
-      '@vitest/spy': 0.31.0
-      '@vitest/utils': 0.31.0
+      '@types/node': 20.2.5
+      '@vitest/expect': 0.31.4
+      '@vitest/runner': 0.31.4
+      '@vitest/snapshot': 0.31.4
+      '@vitest/spy': 0.31.4
+      '@vitest/utils': 0.31.4
       acorn: 8.8.2
       acorn-walk: 8.2.0
       cac: 6.7.14
       chai: 4.3.7
       concordance: 5.0.4
       debug: 4.3.4(supports-color@8.1.1)
-      happy-dom: 9.16.0
+      happy-dom: 9.20.3
       local-pkg: 0.4.3
       magic-string: 0.30.0
       pathe: 1.1.0
       picocolors: 1.0.0
       std-env: 3.3.2
       strip-literal: 1.0.1
-      tinybench: 2.4.0
+      tinybench: 2.5.0
       tinypool: 0.5.0
-      vite: 4.3.5(@types/node@20.1.3)(sass@1.62.1)
-      vite-node: 0.31.0(@types/node@20.1.3)(sass@1.62.1)
+      vite: 4.3.9(@types/node@20.2.5)(sass@1.62.1)
+      vite-node: 0.31.4(@types/node@20.2.5)(sass@1.62.1)
       why-is-node-running: 2.2.2
     transitivePeerDependencies:
       - less
@@ -19738,64 +21177,61 @@ packages:
     resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
     engines: {node: '>=0.10.0'}
 
-  /vue-docgen-api@4.64.1(vue@3.3.1):
+  /vue-component-type-helpers@1.6.5:
+    resolution: {integrity: sha512-iGdlqtajmiqed8ptURKPJ/Olz0/mwripVZszg6tygfZSIL9kYFPJTNY6+Q6OjWGznl2L06vxG5HvNvAnWrnzbg==}
+    dev: true
+
+  /vue-docgen-api@4.64.1(vue@3.3.4):
     resolution: {integrity: sha512-jbOf7ByE3Zvtuk+429Jorl+eIeh2aB2Fx1GUo3xJd1aByJWE8KDlSEa6b11PB1ze8f0sRUBraRDinICCk0KY7g==}
     dependencies:
-      '@babel/parser': 7.21.8
-      '@babel/types': 7.21.4
-      '@vue/compiler-dom': 3.3.1
-      '@vue/compiler-sfc': 3.3.1
+      '@babel/parser': 7.22.4
+      '@babel/types': 7.22.4
+      '@vue/compiler-dom': 3.3.4
+      '@vue/compiler-sfc': 3.3.4
       ast-types: 0.14.2
       hash-sum: 2.0.0
       lru-cache: 8.0.4
       pug: 3.0.2
       recast: 0.22.0
       ts-map: 1.0.3
-      vue-inbrowser-compiler-independent-utils: 4.64.1(vue@3.3.1)
+      vue-inbrowser-compiler-independent-utils: 4.64.1(vue@3.3.4)
     transitivePeerDependencies:
       - vue
     dev: true
 
-  /vue-eslint-parser@9.2.1(eslint@8.40.0):
-    resolution: {integrity: sha512-tPOex4n6jit4E7h68auOEbDMwE58XiP4dylfaVTCOVCouR45g+QFDBjgIdEU52EXJxKyjgh91dLfN2rxUcV0bQ==}
+  /vue-eslint-parser@9.3.0(eslint@8.41.0):
+    resolution: {integrity: sha512-48IxT9d0+wArT1+3wNIy0tascRoywqSUe2E1YalIC1L8jsUGe5aJQItWfRok7DVFGz3UYvzEI7n5wiTXsCMAcQ==}
     engines: {node: ^14.17.0 || >=16.0.0}
     peerDependencies:
       eslint: '>=6.0.0'
     dependencies:
       debug: 4.3.4(supports-color@8.1.1)
-      eslint: 8.40.0
+      eslint: 8.41.0
       eslint-scope: 7.2.0
       eslint-visitor-keys: 3.4.1
       espree: 9.5.2
       esquery: 1.4.2
       lodash: 4.17.21
-      semver: 7.5.0
+      semver: 7.5.1
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /vue-inbrowser-compiler-independent-utils@4.64.1(vue@3.3.1):
+  /vue-inbrowser-compiler-independent-utils@4.64.1(vue@3.3.4):
     resolution: {integrity: sha512-Hn32n07XZ8j9W8+fmOXPQL+i+W2e/8i6mkH4Ju3H6nR0+cfvmWM95GhczYi5B27+Y8JlCKgAo04IUiYce4mKAw==}
     peerDependencies:
       vue: '>=2'
     dependencies:
-      vue: 3.3.1
+      vue: 3.3.4
     dev: true
 
-  /vue-plyr@7.0.0:
-    resolution: {integrity: sha512-NvbO/ZzV1IxlBQQbQlon5Sk8hKuGAj3k4k0XVdi7gM4oSqu8mZMhJ3WM3FfAtNfV790jbLnb8P3dHYqaBqIv6g==}
-    dependencies:
-      plyr: github.com/sampotts/plyr/d434c9af16e641400aaee93188594208d88f2658
-      vue: 2.7.14
-    dev: false
-
-  /vue-prism-editor@2.0.0-alpha.2(vue@3.3.1):
+  /vue-prism-editor@2.0.0-alpha.2(vue@3.3.4):
     resolution: {integrity: sha512-Gu42ba9nosrE+gJpnAEuEkDMqG9zSUysIR8SdXUw8MQKDjBnnNR9lHC18uOr/ICz7yrA/5c7jHJr9lpElODC7w==}
     engines: {node: '>=10'}
     peerDependencies:
       vue: ^3.0.0
     dependencies:
-      vue: 3.3.1
+      vue: 3.3.4
     dev: false
 
   /vue-template-compiler@2.7.14:
@@ -19805,41 +21241,34 @@ packages:
       he: 1.2.0
     dev: true
 
-  /vue-tsc@1.6.4(typescript@5.0.4):
-    resolution: {integrity: sha512-8rg8S1AhRJ6/WriENQEhyqH5wsxSxuD5iaD+QnkZn2ArZ6evlhqfBAIcVN8mfSyCV9DeLkQXkOSv/MaeJiJPAQ==}
+  /vue-tsc@1.6.5(typescript@5.1.3):
+    resolution: {integrity: sha512-Wtw3J7CC+JM2OR56huRd5iKlvFWpvDiU+fO1+rqyu4V2nMTotShz4zbOZpW5g9fUOcjnyZYfBo5q5q+D/q27JA==}
     hasBin: true
     peerDependencies:
       typescript: '*'
     dependencies:
-      '@volar/vue-language-core': 1.6.4
-      '@volar/vue-typescript': 1.6.4(typescript@5.0.4)
-      semver: 7.5.0
-      typescript: 5.0.4
+      '@volar/vue-language-core': 1.6.5
+      '@volar/vue-typescript': 1.6.5(typescript@5.1.3)
+      semver: 7.5.1
+      typescript: 5.1.3
     dev: true
 
-  /vue@2.7.14:
-    resolution: {integrity: sha512-b2qkFyOM0kwqWFuQmgd4o+uHGU7T+2z3T+WQp8UBjADfEv2n4FEMffzBmCKNP0IGzOEEfYjvtcC62xaSKeQDrQ==}
+  /vue@3.3.4:
+    resolution: {integrity: sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==}
     dependencies:
-      '@vue/compiler-sfc': 2.7.14
-      csstype: 3.1.1
-    dev: false
+      '@vue/compiler-dom': 3.3.4
+      '@vue/compiler-sfc': 3.3.4
+      '@vue/runtime-dom': 3.3.4
+      '@vue/server-renderer': 3.3.4(vue@3.3.4)
+      '@vue/shared': 3.3.4
 
-  /vue@3.3.1:
-    resolution: {integrity: sha512-3Rwy4I5idbPVSDZu6I+fFh6tdDSZbauImCTqLxE7y0LpHtiDvPeY01OI7RkFPbva1nk4hoO0sv/NzosH2h60sg==}
-    dependencies:
-      '@vue/compiler-dom': 3.3.1
-      '@vue/compiler-sfc': 3.3.1
-      '@vue/runtime-dom': 3.3.1
-      '@vue/server-renderer': 3.3.1(vue@3.3.1)
-      '@vue/shared': 3.3.1
-
-  /vuedraggable@4.1.0(vue@3.3.1):
+  /vuedraggable@4.1.0(vue@3.3.4):
     resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==}
     peerDependencies:
       vue: ^3.0.1
     dependencies:
       sortablejs: 1.14.0
-      vue: 3.3.1
+      vue: 3.3.4
     dev: false
 
   /w3c-xmlserializer@4.0.0:
@@ -19929,20 +21358,6 @@ packages:
     resolution: {integrity: sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw==}
     dev: false
 
-  /websocket@1.0.34:
-    resolution: {integrity: sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==}
-    engines: {node: '>=4.0.0'}
-    dependencies:
-      bufferutil: 4.0.7
-      debug: 2.6.9
-      es5-ext: 0.10.62
-      typedarray-to-buffer: 3.1.5
-      utf-8-validate: 5.0.10
-      yaeti: 0.0.6
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
-
   /well-known-symbols@2.0.0:
     resolution: {integrity: sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q==}
     engines: {node: '>=6'}
@@ -20013,7 +21428,6 @@ packages:
       gopd: 1.0.1
       has-tostringtag: 1.0.0
       is-typed-array: 1.1.10
-    dev: true
 
   /which@1.3.1:
     resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
@@ -20054,14 +21468,15 @@ packages:
     resolution: {integrity: sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==}
     engines: {node: '>= 10.0.0'}
     dependencies:
-      '@babel/parser': 7.21.4
-      '@babel/types': 7.21.4
+      '@babel/parser': 7.22.4
+      '@babel/types': 7.22.4
       assert-never: 1.2.1
       babel-walk: 3.0.0-canary-5
 
   /word-wrap@1.2.3:
     resolution: {integrity: sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==}
     engines: {node: '>=0.10.0'}
+    dev: true
 
   /wordwrap@1.0.0:
     resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}
@@ -20133,7 +21548,7 @@ packages:
       async-limiter: 1.0.1
     dev: true
 
-  /ws@8.13.0:
+  /ws@8.13.0(bufferutil@4.0.7)(utf-8-validate@6.0.3):
     resolution: {integrity: sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==}
     engines: {node: '>=10.0.0'}
     peerDependencies:
@@ -20144,6 +21559,9 @@ packages:
         optional: true
       utf-8-validate:
         optional: true
+    dependencies:
+      bufferutil: 4.0.7
+      utf-8-validate: 6.0.3
 
   /xev@3.0.2:
     resolution: {integrity: sha512-8kxuH95iMXzHZj+fwqfA4UrPcYOy6bGIgfWzo9Ji23JoEc30ge/Z++Ubkiuy8c0+M64nXmmxrmJ7C8wnuBhluw==}
@@ -20191,11 +21609,6 @@ packages:
     resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
     engines: {node: '>=10'}
 
-  /yaeti@0.0.6:
-    resolution: {integrity: sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==}
-    engines: {node: '>=0.10.32'}
-    dev: false
-
   /yallist@2.1.2:
     resolution: {integrity: sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==}
 
@@ -20352,6 +21765,12 @@ packages:
     version: 2.2.1-misskey.3
     dev: false
 
+  github.com/misskey-dev/buraha/92b20c1ab15c5cb5a224cf3b1ecd4f6baca12b7c:
+    resolution: {tarball: https://codeload.github.com/misskey-dev/buraha/tar.gz/92b20c1ab15c5cb5a224cf3b1ecd4f6baca12b7c}
+    name: buraha
+    version: 0.0.0
+    dev: false
+
   github.com/misskey-dev/sharp-read-bmp/02d9dc189fa7df0c4bea09330be26741772dac01:
     resolution: {tarball: https://codeload.github.com/misskey-dev/sharp-read-bmp/tar.gz/02d9dc189fa7df0c4bea09330be26741772dac01}
     name: sharp-read-bmp
@@ -20362,7 +21781,7 @@ packages:
       sharp: 0.31.3
     dev: false
 
-  github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.0.10)(@storybook/components@7.0.10)(@storybook/core-events@7.0.10)(@storybook/manager-api@7.0.10)(@storybook/preview-api@7.0.10)(@storybook/theming@7.0.10)(@storybook/types@7.0.10)(react-dom@18.2.0)(react@18.2.0):
+  github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.0.18)(@storybook/components@7.0.18)(@storybook/core-events@7.0.18)(@storybook/manager-api@7.0.18)(@storybook/preview-api@7.0.18)(@storybook/theming@7.0.18)(@storybook/types@7.0.18)(react-dom@18.2.0)(react@18.2.0):
     resolution: {tarball: https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640}
     id: github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640
     name: storybook-addon-misskey-theme
@@ -20383,13 +21802,13 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/blocks': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/components': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-events': 7.0.10
-      '@storybook/manager-api': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.0.10
-      '@storybook/theming': 7.0.10(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.0.10
+      '@storybook/blocks': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/components': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-events': 7.0.18
+      '@storybook/manager-api': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 7.0.18
+      '@storybook/theming': 7.0.18(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 7.0.18
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
     dev: true
@@ -20407,15 +21826,3 @@ packages:
       jschardet: 3.0.0
       private-ip: 2.3.3
       trace-redirect: 1.0.6
-
-  github.com/sampotts/plyr/d434c9af16e641400aaee93188594208d88f2658:
-    resolution: {tarball: https://codeload.github.com/sampotts/plyr/tar.gz/d434c9af16e641400aaee93188594208d88f2658}
-    name: plyr
-    version: 3.7.0
-    dependencies:
-      core-js: 3.29.1
-      custom-event-polyfill: 1.0.7
-      loadjs: 4.2.0
-      rangetouch: 2.0.1
-      url-polyfill: 1.1.12
-    dev: false
diff --git a/scripts/dev.js b/scripts/dev.js
index db7bc11fe..2f20d8f07 100644
--- a/scripts/dev.js
+++ b/scripts/dev.js
@@ -44,11 +44,17 @@ const fs = require('fs');
 			if (!stat) throw new Error('not exist yet');
 			if (stat.size === 0) throw new Error('not built yet');
 
-			await execa('pnpm', ['start'], {
+			const subprocess = await execa('pnpm', ['start'], {
 				cwd: __dirname + '/../',
 				stdout: process.stdout,
 				stderr: process.stderr,
 			});
+
+			// なぜかworkerだけが終了してmasterが残るのでその対策
+			process.on('SIGINT', () => {
+				subprocess.kill('SIGINT');
+				process.exit(0);
+			});
 		} catch (e) {
 			await new Promise(resolve => setTimeout(resolve, 3000));
 			start();