diff --git a/.github/workflows/storybook.yml b/.github/workflows/storybook.yml
index b04f4260c3bb87a985fb44cc330c085c0b39e879..6cb1b34997ed39b72eacf8889552650366453344 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 8594c42d10569531d1a12b1faa15ad760b301b9f..a0630065669ae1a98a12ad45d96892ffa402aac4 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 9185be344cb77afd2a68d407ca7a34cc9bf742a7..827a326d1855cebffa3e86dc15d9fb29d07352d4 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 9da5c1a94e4bb7b4072918e2293bc8c776a389d5..0000000000000000000000000000000000000000
--- 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 bfca086f5ce6058fcd58b27c1baeac448e19d65f..5d0fd201b301f74ce3f3bc6ad3b7931edda48f19 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 843470cf46eb61a19a2c4393658b258f9a8cc439..c4c12cb1aa8cc4c5ab8c097859426da743ce1213 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 3ea2313b2f4e26ad975359f0f0fa4e05fc247aa8..0f1c7c89fee6c9bfa3a4ff4d89173d83e4f1602a 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 b043ecf3cf5c7d4421d89142831d864669a79bc0..a5dd18e3fc6e4cc8039acd5ac2f7fd7f2e34c5af 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 d8ac41c925ba191a3691a4727fcfe475897aede5..173380805c34fe79c97e66743021764a13599be6 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 0000000000000000000000000000000000000000..5949aee7cd3bb37bef67199e8fe486886e54753b
--- /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 df42697ccbc86bd98398c81b7e6bd11683e3ee6f..70217caa27bdab42494b5a776a76faac1918ccae 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 fe3edb445b8c7889ef50eba66c0304342beba5dd..7047f42eff4983c2b7ddee224368490920081fa6 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 0b7108fe6d820f185737ff257c600ccaeb41cca2..fcba3fb8223f5a5a909bd60e26a972b48cbb6f01 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 d09f75155dd90ac1fec4458e4fcaf4699ce3c7d1..652814ca9861d69bc16c4a6ec0ebe6b0298f94b9 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 39574d3321b9d83846430a556b69b32895bbc74e..fd46eef1ff4a5f908d5f479c5ac4430690e3b938 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 36c29295f63be196cd82bb3d81e9d75cac8b9fda..ec2900527b6540e567f09c1be3d6c98cf926ad66 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 a123ad726655487713de65fe02a0af3fa4df61bc..e92449fdb9aa015f43bdaea80ee601f0b5830269 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 22f110ebadf6bb295750c0dcebf48ee0ede8974a..d8e68202d71badf76cb3f5dd930cfa0ba04e1b5e 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 638cc1cf8a9242244657cbd613cc4276fb552c53..9c278ea751af9e0adac24cbf26310123a2cfecdb 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 d2b42313a780ce9b2a6f164680ea3748a714226f..ef0baeef500e80f7c3fc363aea60e8683a8396b3 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 a21d7ab9e3c909688128ec74b283971bbe152194..8cf7d37f639f24fde39e49c663389ceeb12e1e1f 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 0000000000000000000000000000000000000000..b50a50eed8818ddfeabd6f48e6a6057ceda6993a
--- /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 0000000000000000000000000000000000000000..ac9c4c42b9a2f84ed8d938d00c10ddabf22af4a6
--- /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 0000000000000000000000000000000000000000..690653bd7cb3afb72cdb23ffdbb9e889ab44c168
--- /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 0000000000000000000000000000000000000000..40b0a2bc5ec55c86a783b3e8d0c9e9ef10bb1e03
--- /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 4bab4a7341855e5ebd27cd65ea00335b1b3ac3a2..56ecbc2eaf004277e14647682a67adc99e2be83e 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 5fb4e8ef3cf9880a2d078cda438d5b81372016f5..406e3192bb623d2eaa4bb5d598056fb23b03ef99 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 c6e107538952df6e2b15b9ae32a7bc717f6899a2..9d1945e4d41b8ce2890fb94e2b6191e492f06e77 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 2d4226a32de374dd34d94f431e8995da4c55d296..d8df371916bdab8d75a7e1f41ed4935a0d3ca2aa 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 cf1e81ffc821648f3dd4b9ddd75bb152fb94a43c..de33e4c243de2357cc239393cc10762cc95390fa 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 7aaa1b833fda5b20b1f4ea231982e5716d55a3eb..1a52a229c57f44609c9a928a25501eeffbc476b5 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 93557ce617998ffc0b79bd35588fcb4eadba0b59..3499df38b763521cce5c7085888ed98d4ea9e7a1 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 8103d5afe9725aefa9a26f8f625831803f39e715..9de633350bef4ce440fbf85bba413f3493e7d34f 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 0b861be8d0475b55a8725ba7cb76d796f7491b5f..5acc9ad9adbd3d850db90ed90fb1550c4dee71ea 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 9b2d5dc0ff1ec7e3e364193709a6806c4f45dc39..dffee16e08ca28ceb4bddba1c61e4745cf199079 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 977c9052c0ac5b2155235eb722f2eb2450cacac9..1c8491bf573b11078ea5f8c54d8adc8254edc589 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 1129bd159cf934c2afa8667c147cd5c6475f4b58..e57e57d310168836043fb069ae6f8181a4c0f424 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 a245908c981cddeb371e9f902da5fcea60198fe8..ed47165f7b4c3c090b3eb3dab2f9ba66fcae00d0 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 0cee2076bf8d3d09ff175603f311f7194c8f601d..bf50a1cdedb6ecb4fd782e403d563b534a207b8b 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 1d7394777629e218c4526c7ec496c00bff07998c..3384ca457785c0805025ade46fdb706b156baa54 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 b4ffffecc0a65371b057fdc568d97011138ecf06..2ae8a2b7548fbbea13cf069de1ddbdf258d143d1 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 a274b19e4b4b10095c23dcb8017312fa1bc26021..4b01b6af7e9b7e74b4a441ae5c62576bd7f0d182 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 68087ccc3b34248fa38f626ade439747d5551cb3..40ae106662710270e54dc23ea8360368deac3270 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 3ee79906431f5d9aa9b05ba560d6ed435de25664..f58a6a10fcd8910a87271b18355a5554344dece8 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 57baade77707a60defcbdcb23a14e5326801761a..467755a0728c72fdf257afbec17bf615c2863d01 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 60e19bfca52f46eb194f15775f71067180077e83..d8b95ca4d1131e871f6498f53448789b2f8a7f17 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 2dc1a410ac2f766996018cdcf5c1959ccd79cf36..20fe2a0a770b383055b534bf97423776d4f325df 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 87a9db405f9d0fcc56deb27b915e3b8062bdae8a..76757f530a6d1dac75fc45a9cd90b9ac02a1d6e2 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 eea1d1b8482825e01de66924013f1de9a3c99559..f52ebed107537bda65d3b90d29c6a2053452e9b5 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 03e361265804d6a701b85b21aa0dcea167ce5a59..b0e9e534df5a3523f1badebae5e140321c90604f 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 3bad048bc095659fea0d7e6465d992116c1035be..4a18cd1b3b7e7fe8bbcc7f24fe6aea15ab0dd021 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 7f61e1d6f323d16df042d4f894bb898d0ef0f7ad..bfd506ea868c938ea42a63d65e9581d093b5b25f 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 2461cb2c122004fe4273f3d20213fbaa36d3c094..8628819278fe8919199db0042d73eae33acc677c 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 8cdfb703f1c3f15d1fb42cbf4e6ab19fa28c3b8d..f826d506254e2bf8a31bd702c84378c28aec7eb4 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 b717434e09c65ee2665b314fdb0c94eb2d333d9a..53a0d14cd78d70ad44a8f062e10345f58f0a5f92 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 bb190cf60fa581d7240adb1f9e931bbbb42dba79..6cd71c0e2ae3901c44bfc2a8a443d8ba3580a190 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 c06c7a7159c2114d5718dce8986b895d66f00284..4a073f102fc88f09dfc6a83a9becda13eab50992 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 9e206ee98f7ae8d6a8c8c8624277200016e31684..f0cbc9900de0abe64d03425b9ce60125ceebaf44 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 34e8b6b17c56ef714a7ec50d29a863f498c1fe3d..b21978b186893c8adef27aabf771916801f8a7df 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;
-
-	if (!d) throw 'wrong number of arguments';
+	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 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 588c98b58dd8a3baddeb88532cb531ffe4879fc6..4231acc0465baaa8933d6180a621bccb2db03fa0 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 dbb437d439a3a86e040485a5dd637ce4f229bc3c..8fd3e65f5e5dd14c23d113aacf5125198c2ef9de 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 df508b4dcaa635c5e53690259f0432346f152237..4f49a0595061e34e9d6d76cf156aeab8c9a2b974 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 8e10f999b617e665a536a9b3688d808dd44bbdcf..6669890cf6775aab758ef7b3b6a69ea4167440e4 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 b8a4b54d4c37bd284aa69d89728cdb3b3ad61025..94f3dc3cb320a9074985b42165417f168224cf63 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 0000000000000000000000000000000000000000..e57abb460a463f01b04dc41027e35915e9774dd2
--- /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 b8ba28db9b58fc2a69c550957e681a1b407e9a63..4b230ab742161e8b19671d3a9331a05de9d0f15a 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 db4fd62cf6112b47cd4c8a6edc36ec4e566e0f0b..63f56e77cb83b648eae8436c82f723851ed3e447 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 3ba5dc4a8a92e685cae4f6c99f550e4e7bd85123..1e620516e4b507fdce6917ec967940ad5d7f8670 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 f3d404e6c9b4f4197b57ef72b1d701deb34fb92f..488979c409db2e814670dc5d254ea44bafef2e59 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 dc025f988987ace5a3dd038f0e69af94c0deae4d..42f9c1af7d7a72a911f7b14a948723a954016ef3 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 {
 			}
 		}
 
+		//#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,
+		});
+
 		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');
 
-		this.queueService.systemQueue
-			.on('waiting', (jobId) => systemLogger.debug(`waiting id=${jobId}`))
+		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
+
+		//#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,
+		});
+
+		const dbLogger = this.logger.createSubLogger('db');
+
+		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 ? 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.deliverQueue
-			.on('waiting', (jobId) => deliverLogger.debug(`waiting id=${jobId}`))
+		//#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,
+			},
+		});
+
+		const deliverLogger = this.logger.createSubLogger('deliver');
+
+		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.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}`));
+			.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,
+			},
+		});
 
-		this.queueService.inboxQueue
-			.on('waiting', (jobId) => inboxLogger.debug(`waiting id=${jobId}`))
+		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.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'}`));
+			.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
 
-		this.queueService.dbQueue
-			.on('waiting', (jobId) => dbLogger.debug(`waiting id=${jobId}`))
-			.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}`));
-
-		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 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,
+			},
+		});
 
-		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 webhookLogger = this.logger.createSubLogger('webhook');
 
-		this.queueService.webhookDeliverQueue
-			.on('waiting', (jobId) => webhookLogger.debug(`waiting id=${jobId}`))
+		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,
-		});
+		const relationshipLogger = this.logger.createSubLogger('relationship');
+	
+		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.add('cleanCharts', {
+		//#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`);
+			}
 		}, {
-			repeat: { cron: '0 0 * * *' },
-			removeOnComplete: true,
+			...baseQueueOptions(this.config, QUEUE.OBJECT_STORAGE),
+			autorun: false,
+			concurrency: 16,
 		});
 
-		this.queueService.systemQueue.add('aggregateRetention', {
-		}, {
-			repeat: { cron: '0 0 * * *' },
-			removeOnComplete: true,
-		});
+		const objectStorageLogger = this.logger.createSubLogger('objectStorage');
 
-		this.queueService.systemQueue.add('clean', {
-		}, {
-			repeat: { cron: '0 0 * * *' },
-			removeOnComplete: true,
-		});
+		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
 
-		this.queueService.systemQueue.add('checkExpiredMutings', {
-		}, {
-			repeat: { cron: '*/5 * * * *' },
-			removeOnComplete: true,
+		//#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
+	}
 
-		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 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));
-		}
+	@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(),
+		]);
+	}
 
-		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));
+	@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 0000000000000000000000000000000000000000..d240fe70e0c55d6eeed07910c1808c50c7e962d6
--- /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 d33e349c363ca4cd560e86ed903015ec0b6d8411..0000000000000000000000000000000000000000
--- 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 e2720b4fe01e44699714462305f4b89e434b7d78..600ce0828f916c7933bcbe842e1569a8c3e884de 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 2476d71a5ebc8dd4aeef033a700f816791376a1b..c4ee212bab739fc3260ae6a045a6351aaf711a7e 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 b458167042070c520dd2e4abf0f312d27845900a..22d7c1b4fb2d00cc622ebeacfbd6a930cd46256d 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 1936e8df23eec5a76c598bae0e912731e9797b17..cefa6da5e946a2f029dfe659fa80e35efd046f9e 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 5a33c271889c2ef98168ef7f99e974d4fe44c54a..c54bf59ae4c2443c8121c06aa20d9f73ff8942cc 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 e36a78de6aa2b77cc073650e14c7a1e63e47337d..39dd801af017e84b4c08e79f94e2a9fcc9fe238e 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 { bindThis } from '@/decorators.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
+import type * as Bull from 'bullmq';
 import type { DbUserDeleteJobData } from '../types.js';
-import { bindThis } from '@/decorators.js';
 
 @Injectable()
 export class DeleteAccountProcessorService {
diff --git a/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts b/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts
index 604497cf5425de9b1412af7cee6baa7fd1010875..6772c5dc7662545878594ef9f6899644ab707107 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 { 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';
-import { bindThis } from '@/decorators.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 2fb2f56f8def61f599605c89e743fc8f6bb4cb93..edf87bd921743dac725a4c1acbd21dbb9cdbc7f0 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 { bindThis } from '@/decorators.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
+import type * as Bull from 'bullmq';
 import type { ObjectStorageFileJobData } from '../types.js';
-import { bindThis } from '@/decorators.js';
 
 @Injectable()
 export class DeleteFileProcessorService {
diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts
index f293bd4d7e016c5d33e4719f851dd27fc4a35730..406e9df850d8d3253483c58be608806b93810b02 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 501ed4090a7311e2023ed68535e27748139530fb..21501592f2cdc7f2db0ce545cb19ececf12b9b49 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 894903e79b1073f351731652c512df3d218b3ac4..ac52325c8d998368d778a8b86057c8fef9725138 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 c7b54070d6a786244e7401d81592b8244b75b7a9..eb758e162dfcaf0198a0e9c0f2d55e2635eb071a 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 { 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';
-import { bindThis } from '@/decorators.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 b50f373ef820d7a14f810bbfd94d4e0477c7e812..3203d9f3e5c9b888a5e6b677484b9f522e752ff7 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 f2f2383a8837d8c036fbca4a41629e6881ac7497..76c38a6b8648417a72460b75773ad3a15bd63ed3 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 fa9c1ac1ea94b846fed0b205fae19d6a35bac0a4..8726cb14027832f5b29511789513305e1bf574b0 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 { bindThis } from '@/decorators.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
-import type Bull from 'bull';
+import type * as Bull from 'bullmq';
 import type { DbExportFollowingData } from '../types.js';
-import { bindThis } from '@/decorators.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 b14bf5f5b18af052e973a23f11e1a8d9beddb0bf..0f11a9e8435f7f3639082eee31ac778edc962325 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 { 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';
-import { bindThis } from '@/decorators.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 e4f12ad101054ba6bc27df9e3911aad63937c591..24fb33188373b6975d6f964b0f558c2f1d26eefd 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 54bde44044de147a1b6198e9a4efbf61437a296a..ec633580535a90333f1f086b61e80c8f8cc38df8 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 { 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';
-import { bindThis } from '@/decorators.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 d06131b8c81b80169f9413556b8beb8de5f2a01c..575cad69d5c49a7494200f777a2dc4a511eb2d00 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 3f075b02d20edb4f2995fa80644da89d30158566..2f1a9e5b03a10c9ba96b0458dafbddaa637f6370 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 cf78d8330c961cbcc44c7ee2fbd46b76c009c834..d862567871570f06f15e51e469fcc9c3a81fbc4c 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 aa5cf12c509bb5f2cf42302624f2782258b56ae4..15bee9672eac441c3a2fcafe2ef47b9cc8b69c8c 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 379994ee791b1907930daf8d12354515f434019b..723935cd311343e341c146e48fd26f0ca6f2b893 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 { 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';
-import { bindThis } from '@/decorators.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 c423863410febc3f28c2ffd332f8c1fc05325a70..824ee8157ab16fcbd9182fd1e0a72b2906233097 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 ab8b1e9e22556d683f9e3b30c926858db22afc57..ce1d7aaa1bfcd9fcfd867f2f8b07db6fb5bc0bcd 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 ff454df455508464eb5b424c90593fe1edb7b35c..722260d94821b9facb85daf477d8dfc54eaea75d 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 e5840f3da8c84bc5a9f962bce18c9aa59bd10703..eab8e1e68d70da96dd89cbeb926a59c038e1c981 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 7ff84c15a5c07640a5e2b25c69c53bf24705d4dd..f1696bf5677554223943b4873d54f834acf41c6d 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 84a5c21c4910fbccb8abdf42610da98b5166cf84..8b40c167496d1b52ab7e934477522558a6f7168f 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 e675d9cf1baf751e85e58f7aa082bbebce5d9f73..455acd1e47f4296312ce09141cb77a8e2bfe22f9 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 9257fee13e95c616bb0ab0935ae11af3801f6390..c3d45e4ad6932487af51f41e82e5a0a63bb0ec96 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 e3483c82c67d31fa59c02639927883c7eca56e3b..dad1a4132a955a609ecb1c206e9f56e67831397e 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 6548c475b2dbfacf033c8060c3d2105225a7b76f..e23591d8765dfb0e9dd8c3720b5f97a3f2cfd4ec 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 ee1aae5b6c787fead3660845c5aad2e8f5e20506..1e32e9988dfb269316556234547412ee33613c94 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 258e8de034195bb5aa00d02f4bc588ac841a5215..893dfe956e5057917f7d8c258552e1a9fb649f79 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;
+		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;
 
-			// TODO: トークンが間違ってるなどしてauthenticateに失敗したら
-			// コネクション切断するなりエラーメッセージ返すなりする
-			// (現状はエラーがキャッチされておらずサーバーのログに流れて邪魔なので)
-			const [user, miapp] = await this.authenticateService.authenticate(q.i as string);
+			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) {
-				request.reject(400);
+				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 09bd7cbff455ab12df430e88cc1ed61bd616a66e..7e678a64040728ea8eb09f4dc2fd3579fb1d5425 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 2393c2441c3b7b5fcf434431e4b0122c5c6758b7..12db1f78fb689fca3f87aeb82fb4cc2d0f45a1db 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 2fb3e489e72f367084546fd84edb097a42de12df..509224e9c3f6cb131f12ab471587d46c2991516b 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 f63348b60b79d9bdc79454460b2462121653e1e1..fb22bdc477e0a75894b9c3085fbbeb2087943feb 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 f12738bd3a2d17f722dd6f5d8cd2027baf775283..f2d4aa8996ac09013f2d884059f732caf57b2a92 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 dca0f443b7975ebec6420e7b5f2aa24c7d2dea16..e756a9b510c48759c67f91f841916b247056914c 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 cb2e661bfb2e3b6b3e32c2d21b6c10c499d98f4a..05842460cfb49b5f8ae9aa86ef38244135ca4836 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 ad33398da60fc8331b902ccd053d82e0bb254a32..e8985a9cd8343ffcfb780a77516d3e6d02948032 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 3361e5a4d3f54c79a14d5f8e4352f4db85222550..48fb03a8af122605a4e1c794aaac9f32e85fb2de 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 e141be764a795b483f00dd4f50a00a39f83c2b8d..f5662f4a0e819f876ca8620d9b84a45bbe32b3a2 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 74be00a8b8319a3b70f67a92bc0839eadc3e6404..8f5e6177c28f7b2e9909de055747735c6cd31044 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 584ea07c3bdb008e6e3542076f38c2033ebfefc9..53d724a9ddc073fa8305a27340e64d4b5e7b3a3c 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 3f7f2cdecef3d4b2a76d4dd81be4271e52b2c056..96be5ed844dfe9c70277e7d5922244e5e7c8bb76 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 c11c1eac407ba2f6f50bcfdcbe88803d0d91b464..88c1ca7f58c4e299afae82ffc4826f9d64b867ef 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 89abd91c7e0576c4f610f1cfa32133a762238592..7a3581e6e441bdb57006315ccc155befe8a0e31a 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 afdafc7c55024aa79275bf87b6177fb55625c7ea..2ee549232c5395efe7097d94eb6969fb3b7cbb67 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 2956bf1cbd52b48ff428103c020d61b25e2c123b..742df0ca952e777a3bbfbecabb373767f3b2a5e1 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 c6ee1e5c2bceef6e027e29b9b033cea79af244f1..e1f286439bca1b2650b7568ab5634e423f6b4fa9 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 4ced6d3ff175ac1f6325e3127644e712a6ba2c1a..1d4825f812eb0526e0864937c0fe48f0fcf2fdfb 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 6202c740f1a3e3099b3a6dcbbe693dd2b4625e58..42e36cb04a7e12bc5d7c3cc9bb25f01a923436d7 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 0000000000000000000000000000000000000000..8591e4ab965b20ddce2a2f453f89f98651449884
--- /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 0000000000000000000000000000000000000000..263852fde1ffe440596fb2ac6d3cff5c4d544abd
--- /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 2104c4377dd13dd2c11892e3f2dfccf0e1921e31..eab29944b2f31c3aacd21c41f9fc0497d72ff0c0 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 77f9cba808f949df5c3572db5b04fc24381af1fe..8077841c8ce15d6f9afbbfc1a1771e652cb6955a 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 0000000000000000000000000000000000000000..be8e317816c4062a54407740121c379e4ada6d5d
--- /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 6453d7d9808ce2539ff4551e68db354b5cfa38dd..b0a95a2f2864326a6178764ed606f708bb48f80c 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 5454836fe116511a53ff2e9acbccca9c2fe21433..d3339072c1efce1b9c561fca1197996dbf987a2c 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 ee874ad81e19aa10f926e44dc3734674e20edf51..1755aa94cf13545673d6f75ff58f79aa572b24f9 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 4f7b4e78b695bc984b43f56f6aa5f678208cfca4..5a33e13cf55b012579e165e9a55f6a7396246a0c 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 09b0005ac11421b748612ec4b1f2cd6c955ca54b..9ca4db8ced92bc0542530e89b6fa58d4da50dd40 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 9d106c8b2f9839024a5c35beec4a72d0b8f771c0..ab9c1aa0b52aad31406ff82ab399d97a67d3739e 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 a6f9145952db2bebe49a81e7bf76dd3c7c49ab79..8b1c2c09c974181fae099690e4a4c798bc9b0806 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 fd7f54da54cc554ad666473500718568c22a0f80..38ae8ad2e5b850d8ab8aea6d5d2ca9f12310e782 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 cb5d05a403910528b38b53b111f34a2d12e023af..69b3f68e0524b34ead1077a00050d3921e126e0d 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 486f0ecc47bc2cc1b5ec963b2dd627dcc1da62f0..c514025e0b40fc934c5a82c4b019bad02126e34e 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 74dc62f1e7a191437f7fc4ca507f14bc9f11c2df..5a0018803a631383c28afa0ffe1b667d6eeaf625 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 5594fcdfbfdff95a898a11f3039acb3d5e9f6a01..1549aa7906109aa2e0a97378a32208896f47eedb 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 10f2d269bcdb720c1779711481a99c424d70841d..a458d7f8c7d6a2a82e6329e716c6717e77298d6c 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 badfcccd61291efea7ad64c07316afc2701b0a68..874c48c60285c760f9c674af8232916e7f682b2d 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 ddffc361c888816ede27b99ffae0435aa5086d43..08bb08ffe7e12c419189b22783e36660193c5cfa 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 f4c83aa89d386e1bd6528876dc6fac1a1875d15a..83d57349a64dc79a326c1efa1705b2f944d6fa14 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 0addb430c970f3194284680af2379df87d10ed1b..5da997f28bb9ce8a7e3266ba233620d05ad0daec 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 0000000000000000000000000000000000000000..dd3b09f85ae2aba387a872f52fdcd8aaa08e63a4
--- /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 a7f8210c8e2dbe84a72c2ae4f7d34385d3979790..02684c93b888dd4e7bb0f4e93eb7e6617faddfbd 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 6b31e68616afb861204bcdade8ff2a9485014149..a7bcd859aef1a5c37a9ba3eecff40b5c1e8e6f90 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 38db081ac089ca359bc82199d2daba590895eaef..aa68f4117d93c2f32dfefe81ee4aa68f0624f181 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 809ed2c66cc15ca09d3a5e7b7d066b642b092118..22f7d81e4e08d8a4060cdbe6ad019484006080e1 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 e8e0e57d2abb7978827e9ab78a8f8a1d11935206..24c3ad4b83746335e45dc07e91cb6ef75257fb4a 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 7c51d4c00ce432313fd90182624d1426321a46fb..f4424221090fd4e88d33c58f8c2fbbf092a22311 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 ab694f64fb9876c7aeb014b7717bc4ef948fe11c..f6a9a4875de3ba8b476ba5909498dc73f8de76a1 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 0000000000000000000000000000000000000000..3929bf06083a440c508ba5d0349d86a7434a8d64
--- /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 0000000000000000000000000000000000000000..a18f0d90494d432facd03362e53100d57ef9085d
--- /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 5b4004d8e3aab9893af96fafc3bf1ae39f459651..506d1879010134dbf79f99575ccc417e8ca75351 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 0000000000000000000000000000000000000000..921c161765fd1cfcb59fbffe6e5dd2f2a2f83ae4
--- /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 9b104391d7f940729dbc16782d98b3d20e676523..4770f616acb472e347ac90748672aef6505065e8 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),
 						});
 					}
+
+					// rejectかつ理由がtrueの場合、削除対象であることを示す
+					fail(true);
 				} else {
-					await alert({
-						type: 'error',
-						title: i18n.ts.failedToFetchAccountInformation,
-						text: JSON.stringify(res.error),
-					});
+					(res as Account).token = token;
+					done(res as Account);
 				}
-
-				// rejectかつ理由がtrueの場合、削除対象であることを示す
-				fail(true);
-			} else {
-				(res as Account).token = token;
-				done(res as Account);
-			}
-		})
-		.catch(fail);
+			})
+			.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 0000000000000000000000000000000000000000..e1b12fe7d6aa0f65ac6419d7f1bb4019526a9e92
--- /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 0000000000000000000000000000000000000000..76e8c5072481e1ab1984fb80385efc63c8917932
--- /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 0000000000000000000000000000000000000000..c2664f6c1d3e0e2ea9fa8e47b0c4392a5533a3a2
--- /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 9f2bf993389f89c3e0b55fb5d1ca1805c7609135..48236782d98cd6450022e4bd429f8379023d3e36 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 b02bfdc2b82daa3e4a8973cfaeeab3c43d7b5f80..bc07b9ba5f356f70d8999c06e73fc875fa6847dd 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 d30037dcf9657b91ce449a3bf5db6c9a1ab20633..3fdb261dac8275d149b5269eebd3e9feff454f47 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 e7fbb472846e28a1bb79355cf56f34b673b51010..0aebdccf4ff5060b5f261d2faecc9071518aab61 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 f12020f81047e0a391ec342fd10310e01e2e44ff..05caffe7d005ee63c32aae254527ec07a3fbcfa1 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 0000000000000000000000000000000000000000..575ea7c5e34968ed8e31145f6156c0b0800041cb
--- /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 6ade5316c6812d4d8e5ca1495df23f41f0e7d9e2..8bfcfa6aa65bcaf6bdcd07a66fa0859247c77155 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 663c57623d4ab421cd5107322a17f7dc766d78eb..fd892d8174e84215146de9c848935b93ba90eefb 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 995a72e511735c78f69710a00aa51cd922ef8e13..630620fc08730981d124fe20ca667adcdff21fa1 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 0ddee34f0a7748ad44ba17bf6c26e5f7568c74e9..16e44ec6186b5310b54fdd5f00570e78aed6a154 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 9e275d61723f09a3d59357ac12b1f0b3d5d9c894..7b7bef47873d41dd812223d172242414b0964b18 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 408eab7399e6a56603a418cd1b4f06659c6f9dc6..4050520eb9bedd7c542c8be4768df20a7d61eb4f 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 06d5b9949ab06478a9ebf1fa03690dd4f6474174..00ff98774bce28d4e916bfae89a5d6019ee0c0ec 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 7cfe535edd1957453558c8694762d27cd1e4d272..fe5b78754daa9330b8e970866bba16f9993c3c0b 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 da6439fd2cb64e91118e606404f86ae28f2527dd..a6ab5aded4272fd9d4f351549c97f5224abb43b0 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 d03331a6ebc14c9875dab31920b3ea431152436b..af1c57b349b7ffc19b9751f1defd5148c626d8f7 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 b81c806b0c1deca849848cbac340633d8d8d67f3..fb11834f4d737e4c4548a4ff2d1e75e9ebb0d8e6 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 043a614e461e0fbff34fad2eae13d94528607699..82363499b7a27bde308e84f0d24b9b473abe8c10 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 d6303f967592d40ef74bbda54cdb62a69dbc24b6..6942a0e6c3f5ee7958052dcd65d7725fddc1a997 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 9f5404ce15587921f34aed1c5ddaf1440b73941d..4d5df0bba4e9a3e2b85a72049ff05003dd3374f4 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 0000000000000000000000000000000000000000..344f6de47c3050cd241d5a5497956fd8b9e99a2d
--- /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 278dc8a5e7ebbedb784bf52b430177d8071d159c..aea20f24890de1e7d70e15ca8dc8eced22d87ccf 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 ab408b500867d06fa9e175a9f138045ef84a9a85..f0641161bebd5d01167da0958fa2e492c9fd5cae 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 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="$style.thumbnail" :file="file" fit="contain"/>
+
+		<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 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>
-
-	<MkDriveFileThumbnail class="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>
 </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 {
+	position: absolute;
+	top: 0;
+	left: 0;
+	pointer-events: none;
 
-	> .label {
+	&: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;
-		pointer-events: none;
+		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;
-			}
+			background: #c12113;
 		}
+	}
+}
 
-		> img {
-			position: absolute;
-			z-index: 2;
-			top: 0;
-			left: 0;
-		}
+.labelImg {
+	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);
-		}
-	}
+.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;
-	}
+.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;
-		}
-	}
+.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 156013b9aa8401bba59151dc72f19789d96dcf56..1969342402627708dd3c677f4554493278fd8f94 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);
-
-		> i {
-			margin-right: 4px;
-			margin-left: 2px;
-			text-align: left;
-		}
+.checkbox {
+	position: absolute;
+	bottom: 8px;
+	right: 8px;
+	width: 16px;
+	height: 16px;
+	background: #fff;
+	border: solid 1px #000;
+
+	&.checked {
+		background: var(--accent);
 	}
+}
 
-	> .upload {
-		margin: 4px 4px;
-		font-size: 0.8em;
-		text-align: right;
-		color: var(--desktopDriveFolderFg);
-	}
+.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 dbbfef5f0561b59c939034a7fd43f23a14a17a7b..3349603d3b98eafc32fe67cfa57eb4b0f16593cf 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 bfec57d6a04ea4e146b1537f105cbef166bb5a30..52aef450d92b38d9bc9208cf11017ddd344bb7f1 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);
-
-		&, * {
-			user-select: none;
-		}
+.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;
+}
+
+.navPath {
+	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;
+
+	&:hover {
+		text-decoration: underline;
+	}
 
-		> .path {
-			display: inline-block;
-			vertical-align: bottom;
-			line-height: 42px;
-			white-space: nowrap;
-
-			> * {
-				display: inline-block;
-				margin: 0;
-				padding: 0 8px;
-				line-height: 42px;
-				cursor: pointer;
-
-				* {
-					pointer-events: none;
-				}
-
-				&: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;
-					}
-				}
-			}
-		}
+	&.navCurrent {
+		font-weight: bold;
+		cursor: default;
 
-		> .menu {
-			margin-left: auto;
-			padding: 0 12px;
+		&:hover {
+			text-decoration: none;
 		}
 	}
 
-	> .main {
-		flex: 1;
-		overflow: auto;
-		padding: var(--margin);
-
-		&, * {
-			user-select: none;
-		}
+	&.navSeparator {
+		margin: 0;
+		padding: 0;
+		opacity: 0.5;
+		cursor: default;
+	}
+}
 
-		&.fetching {
-			cursor: wait !important;
+.navMenu {
+	margin-left: auto;
+	padding: 0 12px;
+}
 
-			* {
-				pointer-events: none;
-			}
+.main {
+	flex: 1;
+	overflow: auto;
+	padding: var(--margin);
+	user-select: none;
 
-			> .contents {
-				opacity: 0.5;
-			}
-		}
+	&.fetching {
+		cursor: wait !important;
+		opacity: 0.5;
+		pointer-events: none;
+	}
 
-		&.uploading {
-			height: calc(100% - 38px - 100px);
-		}
+	&.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;
-				}
-			}
+.folders,
+.files {
+	display: flex;
+	flex-wrap: wrap;
+}
 
-			> .empty {
-				padding: 16px;
-				text-align: center;
-				pointer-events: none;
-				opacity: 0.5;
+.folder,
+.file {
+	flex-grow: 1;
+	width: 128px;
+	margin: 4px;
+	box-sizing: border-box;
+}
 
-				> p {
-					margin: 0;
-				}
-			}
-		}
-	}
+.padding {
+	flex-grow: 1;
+	pointer-events: none;
+	width: 128px + 8px;
+}
 
-	> .dropzone {
-		position: absolute;
-		left: 0;
-		top: 38px;
-		width: 100%;
-		height: calc(100% - 38px);
-		border: dashed 2px var(--focus);
-		pointer-events: none;
-	}
+.empty {
+	padding: 16px;
+	text-align: center;
+	pointer-events: none;
+	opacity: 0.5;
+}
 
-	> input {
-		display: none;
-	}
+.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 33379ed5ca70f413aef77fc76ae6d9280c6c7c51..490aed6e0462abfab1f0e38050ab494b61946773 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 8d2b19c013aa0e5be86ccdafc0122cfdf3ca58ac..da873cb90bebf7fb9936bcc8c74b99e8a55fd2b7 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 8b2abc15a3ea329edd25313f8933291fdeebc1e9..64ccbec9c3bad0d8aeaa24f03af6427537fbaf3f 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 9eaf16374b7476c634b442c9092498c6d003a264..cf856fd31f0a7a63234ff0de493934fd7222b338 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 c568d4ed5cf06fb4313aac73a546aaf0e99c9d81..cfb65e3b63c613e6b5416e5dd65cbb9ec4584539 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 84970410e92b4e5538e48c2fa946a74b911c344f..9fecfd608223408bac5e8158b510c5e803a10f15 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 95eef45df054412073387f776a04587859a62c46..61b87bda78fb3fc5e0f819c138b6ab2294ed1b61 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 475e01c8d4270663e283f5650efe1fb134e095db..5dd07fc7da3201ec5c307ea1bc1fda36518ffb18 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 props = withDefaults(defineProps<{
+	expanded?: boolean;
+	persistKey?: string;
+}>(), {
+	expanded: true,
+});
+
+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(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;
-		},
-
-		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 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));
-
-		> .title {
-			display: grid;
-			place-content: center;
-			margin: 0;
-			padding: 12px 16px 12px 0;
-		}
+.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));
+}
 
-		> .divider {
-			flex: 1;
-			margin: auto;
-			height: 1px;
-			background: var(--divider);
-		}
+.title {
+	display: grid;
+	place-content: center;
+	margin: 0;
+	padding: 12px 16px 12px 0;
+}
 
-		> button {
-			padding: 12px 0 12px 16px;
-		}
-	}
+.divider {
+	flex: 1;
+	margin: auto;
+	height: 1px;
+	background: var(--divider);
+}
+
+.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 10eee6aab106c5bd03d2ed26d8774041a0f83832..70f0cc5cda62146b60b0d8ee1365b058a609ec8f 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 beee21c647c3f83f55450b6bc02e9a3d2b7ed9e1..b732fbb2b9baa2d7a8afcf689d60cbcd9b1be94d 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 0befa7e3ae78ec20e1d43b85df2e2e9f7a6e385f..1264c42331deebca55924df5fcea9796209cc021 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>
-		</div>
-		<div class="sub">
-			<MkA to="/about" class="_link">{{ i18n.ts._forgotPassword.ifNoEmail }}</MkA>
+				<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>
-	</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 979df2e7c1acb1292e56f484e653c9c657a4c3a7..6d2b391e6d0e1aba4c7815d622061eced1363981 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 57b3e755132f9274d8fbc4cf1a4ee1fdfa6c04e1..72ac0a58f94abb37460d2e00e434f10ef46e4f07 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 4f8f7b945ac65ecd6e1fd64b04cc00dc9f0c324c..3a39ad963ba1b3f2ac15ef07dae457629629ea5c 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 a90e27e502063472451c06f70b49ff1b13f0cb43..0000000000000000000000000000000000000000
--- 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 6406a350601716d30d156bbb9a8fdbea0e457f8b..672a28f6d0cca1c82c2be238f5c1c350d1db5cea 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">
+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 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 { onMounted, shallowRef, useCssModule, watch } from 'vue';
-import { decode } from 'blurhash';
+import { computed, nextTick, onMounted, onUnmounted, shallowRef, watch } from 'vue';
+import { v4 as uuid } from 'uuid';
+import { render } from 'buraha';
 import { defaultStore } from '@/store';
 
-const $style = useCssModule();
-
 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);
-
-function onLoad() {
-	loaded = true;
+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 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');
+	if (!ctx) return;
+	ctx.drawImage(bitmap, 0, 0, canvasWidth, canvasHeight);
+}
+
+async function draw() {
+	if (!canvas.value || props.hash == null) return;
+
 	const ctx = canvas.value.getContext('2d');
-	const imageData = ctx!.createImageData(width, height);
-	imageData.data.set(pixels);
-	ctx!.putImageData(imageData, 0, 0);
+	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);
 }
 
-watch([() => props.hash, canvas], () => {
+workerPromise.then(worker => {
+	if (worker) {
+		worker.addListener(workerOnMessage);
+	}
+
 	draw();
 });
 
-onMounted(() => {
+watch(() => props.src, () => {
+	waitForDecode();
+});
+
+watch(() => props.hash, () => {
 	draw();
 });
-</script>
 
-<style lang="scss" module>
-.transition_toggle_enterActive,
-.transition_toggle_leaveActive {
-	position: absolute;
-	top: 0;
-	left: 0;
-}
+onMounted(() => {
+	// drawImageがmountedより先に呼ばれている場合はここで描画する
+	if (bitmapTmp) {
+		drawImage(bitmapTmp);
+	}
+	waitForDecode();
+});
 
-.transition_toggle_enterTo,
-.transition_toggle_leaveFrom {
-	opacity: 0;
-}
+onUnmounted(() => {
+	workerPromise.then(worker => {
+		worker?.removeListener(workerOnMessage);
+	});
+});
+</script>
 
-.loader {
+<style lang="scss" module>
+.transition_leaveActive {
 	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 ff69c7964124d04df1ff28b9bebc9068379a69f9..4b6a77563569cbdf24ac361177b1d0acf571bf89 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 80e5cc82709ef9383ee7e0fcc019f114619a8d8c..9262778612529431f99e3760a71fe0d68f38cd34 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 5ca4c5051849e7fcdb57b9e4d858e8e85a2480c1..5902d6fd25fda0fab716e79fd50e6b71c4f0bade 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;
-
-	--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;
-
-		> * {
-			display: block;
-		}
-
-		> b {
-			overflow: hidden;
-			text-overflow: ellipsis;
-		}
-
-		> *:not(:last-child) {
-			margin-right: .2em;
-		}
+	overflow: clip;
+}
 
-		> .icon {
-			font-size: 1.6em;
-		}
-	}
+.download,
+.sensitive {
+	display: flex;
+	align-items: center;
+	font-size: 12px;
+	padding: 8px 12px;
+	white-space: nowrap;
+}
 
-	> .download {
-		background: var(--noteAttachedFile);
-	}
+.download {
+	background: var(--noteAttachedFile);
+}
 
-	> .sensitive {
-		background: #111;
-		color: #fff;
-	}
+.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 42dc9e79ff55c154d0eb9d6408d0d3712f4b082f..b29871c3635e1552bc8b1240bc26a8a5aeb0fdca 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 e456ff3eec76c395ee4833bd0d3e01a4d2c16900..a0a2450054b1188a9ed52f1ac905e73ee736cf4b 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 a4b76300e6ad71274380aa63159dba087a148ae4..40bae90b5e49f2202f5f3562a5841cc137e8d45a 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;
+.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;
+}
 
-		font-size: 3.5em;
-		overflow: hidden;
-		background-position: center;
-		background-size: cover;
-		width: 100%;
-		height: 100%;
-	}
+.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 {
+.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 481c3710ca714dcc3d5c8e6ddd0e1742b4200ea2..bb256c394b7fcd4e9c11743f63ebc85bdbcabecc 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 e0935efbe70277f38638f029c50d226cb1e04d99..4fedfe7014f911bf19dd91cc2013cc23a8be1183 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 e513a65a32ad5ddb0dd017607ca31fb35353e2a5..7dd6a8c88f0613bda654cbb7719b26b2e850ad69 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 99df9e8150863f65a8f488ded028df5493e1814b..bb5c6c7aab7e08288ce060ea1ed27d854885b8d6 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 b38865f52510ca620bdaac9648b006844c31b157..0000000000000000000000000000000000000000
--- 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 63c55b904af23e51ec400bfc7a9d4fdbcb23f7e0..08569b4d6ed5684f8f7fd77bc9018286ba510ce8 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 d95f8de311a9568adb1904c79f96d9f43312b296..7c9ddadbf8e3a3722945b34941ab02a59c27fd14 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 0d6d329d9813087523b6c4427e33a33d2ce72b56..a65039277b8ee62d9b02a8f6914a8b6fad229584 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>
-			<footer class="footer">
-				<div class="info">
-					<MkA class="created-at" :to="notePage(appearNote)">
-						<MkTime :time="appearNote.createdAt" mode="detail"/>
-					</MkA>
+				<div v-if="appearNote.files.length > 0">
+					<MkMediaList :mediaList="appearNote.files"/>
 				</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>
+				<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>
+			<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;
-
-		&: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;
-		}
-	}
+.replyTo {
+	opacity: 0.7;
+	padding-bottom: 0;
+}
 
-	&:hover > .article > .main > .footer > .button {
-		opacity: 1;
-	}
+.replyToMore {
+	opacity: 0.7;
+}
 
-	> .reply-to {
-		opacity: 0.7;
-		padding-bottom: 0;
-	}
+.renote {
+	display: flex;
+	align-items: center;
+	padding: 16px 32px 8px 32px;
+	line-height: 28px;
+	white-space: pre;
+	color: var(--renote);
+}
 
-	> .reply-to-more {
-		opacity: 0.7;
-	}
+.renoteAvatar {
+	flex-shrink: 0;
+	display: inline-block;
+	width: 28px;
+	height: 28px;
+	margin: 0 8px 0 0;
+	border-radius: 6px;
+}
 
-	> .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;
-		}
+.renoteText {
+	overflow: hidden;
+	flex-shrink: 1;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+}
 
-		> i {
-			margin-right: 4px;
-		}
+.renoteName {
+	font-weight: bold;
+}
 
-		> span {
-			overflow: hidden;
-			flex-shrink: 1;
-			text-overflow: ellipsis;
-			white-space: nowrap;
+.renoteInfo {
+	margin-left: auto;
+	font-size: 0.9em;
+}
 
-			> .name {
-				font-weight: bold;
-			}
-		}
+.renoteTime {
+	flex-shrink: 0;
+	color: inherit;
+}
 
-		> .info {
-			margin-left: auto;
-			font-size: 0.9em;
+.renote + .note {
+	padding-top: 8px;
+}
 
-			> .time {
-				flex-shrink: 0;
-				color: inherit;
+.note {
+	padding: 32px;
+	font-size: 1.2em;
 
-				> .dropdownIcon {
-					margin-right: 4px;
-				}
-			}
-		}
+	&:hover > .main > .footer > .button {
+		opacity: 1;
 	}
+}
 
-	> .renote + .article {
-		padding-top: 8px;
-	}
+.noteHeader {
+	display: flex;
+	position: relative;
+	margin-bottom: 16px;
+	align-items: center;
+}
 
-	> .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;
-			}
+.noteHeaderAvatar {
+	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;
-					}
-				}
+.noteHeaderBody {
+	flex: 1;
+	display: flex;
+	flex-direction: column;
+	justify-content: center;
+	padding-left: 16px;
+	font-size: 0.95em;
+}
 
-				> .username {
-					margin-bottom: 2px;
-					line-height: 1.3;
-					word-wrap: anywhere;
-				}
-			}
-		}
+.noteHeaderName {
+	font-weight: bold;
+	line-height: 1.3;
+}
 
-		> .main {
-			> .body {
-				container-type: inline-size;
+.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;
+}
 
-				> .cw {
-					cursor: default;
-					display: block;
-					margin: 0;
-					padding: 0;
-					overflow-wrap: break-word;
+.noteHeaderInfo {
+	float: right;
+}
 
-					> .text {
-						margin-right: 8px;
-					}
-				}
+.noteHeaderUsername {
+	margin-bottom: 2px;
+	line-height: 1.3;
+	word-wrap: anywhere;
+}
 
-				> .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;
-						}
-					}
-				}
+.noteContent {
+	container-type: inline-size;
+	overflow-wrap: break-word;
+}
 
-				> .channel {
-					opacity: 0.7;
-					font-size: 80%;
-				}
-			}
+.cw {
+	cursor: default;
+	display: block;
+	margin: 0;
+	padding: 0;
+	overflow-wrap: break-word;
+}
 
-			> .footer {
-				> .info {
-					margin: 16px 0;
-					opacity: 0.7;
-					font-size: 0.9em;
-				}
+.noteReplyTarget {
+	color: var(--accent);
+	margin-right: 0.5em;
+}
+
+.rn {
+	margin-left: 4px;
+	font-style: oblique;
+	color: var(--renote);
+}
 
-				> .button {
-					margin: 0;
-					padding: 8px;
-					opacity: 0.7;
+.translation {
+	border: solid 0.5px var(--divider);
+	border-radius: var(--radius);
+	padding: 12px;
+	margin-top: 8px;
+}
 
-					&:not(:last-child) {
-						margin-right: 28px;
-					}
+.poll {
+	font-size: 80%;
+}
 
-					&:hover {
-						color: var(--fgHighlighted);
-					}
+.quote {
+	padding: 8px 0;
+}
 
-					> .count {
-						display: inline;
-						margin: 0 0 0 8px;
-						opacity: 0.7;
-					}
+.quoteNote {
+	padding: 16px;
+	border: dashed 1px var(--renote);
+	border-radius: 8px;
+}
 
-					&.reacted {
-						color: var(--accent);
-					}
-				}
-			}
-		}
+.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;
 	}
 
-	> .reply {
-		border-top: solid 0.5px var(--divider);
+	&: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 6b55c27869673a0a95196e1b3eedd4f546000fd1..6786f8b2569dd63bca4b17138943e43d92904823 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 bd27a43b61e0a69ee8c639fe230d2a975aff1ad2..21be1454a7a0c51f1a462c4e9a85696a732b5b94 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 a4e949c898581a6b66f0b9bd91a5c36298608e07..9cc2b7a967a32b5a5cb68bd84b5a2758d5e15516 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 efae687e667919c0641c891be75c84fa9ab7aa6a..d25332b10f0f44cc3e86bc6a06fad5594f7766eb 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 f6d0e5681ddf10aaeed7ab9493722dac0fd94c7b..598d3a05510e603bead1f46b4f043ed811f8c4c2 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 1aea95fe0e0d7177ed70cc7409857a877635e988..70224bffa130d50c50e7be91fcbf9e965f38b300 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 e7fc73bce362eb4a965338b54531cf670b53a4b6..d48e7886eb045a4871db11c0d4afa7a68e95d8c4 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]);
-			}
-		}
-
-		function isObject(v): boolean {
-			return typeof v === 'object' && !Array.isArray(v) && v !== null;
-		}
+if (isObject(props.value)) {
+	for (const key in props.value) {
+		collapsed[key] = collapsable(props.value[key]);
+	}
+}
 
-		function isArray(v): boolean {
-			return Array.isArray(v);
-		}
+function isObject(v): boolean {
+	return typeof v === 'object' && !Array.isArray(v) && v !== null;
+}
 
-		function isEmpty(v): boolean {
-			return (isArray(v) && v.length === 0) || (isObject(v) && Object.keys(v).length === 0);
-		}
+function isArray(v): boolean {
+	return Array.isArray(v);
+}
 
-		function collapsable(v): boolean {
-			return (isObject(v) || isArray(v)) && !isEmpty(v);
-		}
+function isEmpty(v): boolean {
+	return (isArray(v) && v.length === 0) || (isObject(v) && Object.keys(v).length === 0);
+}
 
-		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 55578a37f6b0c799bd7e3231b70f70a0a275df94..8b1ed74142b78a7e587a422583ba32fcb4640507 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 e2d68d12c348fa0f1f7717d957318c784b87be2e..668f9ff5af88d6da089964203781ef3c5032e765 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 02ce58451db56bb42b8e90a2e391d1f7b45680f1..709b5a52dfe5f147c624bba639888e119ffdc672 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 cd8af560e43cead1e4a1e6df14ab24efdd64d1e9..740094b1130e0944e646b11d9ed7b126e9fac961 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 0810061ff93642a28f647530d08ed6ea40ccf4a6..464e340116ba79b46b664c897efd873d43502a87 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;
-
-		> 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;
-
-			> .backdrop {
-				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;
-
-				> i {
-					margin-right: 4px;
-					color: var(--accent);
-				}
-
-				> .votes {
-					margin-left: 4px;
-					opacity: 0.7;
-				}
-			}
-		}
-	}
+<style lang="scss" module>
+.choices {
+	display: block;
+	margin: 0;
+	padding: 0;
+	list-style: none;
+}
 
-	> p {
-		color: var(--fg);
+.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;
+}
 
-		a {
-			color: inherit;
-		}
-	}
+.bg {
+	position: absolute;
+	top: 0;
+	left: 0;
+	height: 100%;
+	background: var(--accent);
+	background: linear-gradient(90deg,var(--buttonGradateA),var(--buttonGradateB));
+	transition: width 1s ease;
+}
 
-	&.done {
-		> ul > li {
-			cursor: default;
-		}
+.fg {
+	position: relative;
+	display: inline-block;
+	padding: 3px 5px;
+	background: var(--panel);
+	border-radius: 3px;
+}
+
+.info {
+	color: var(--fg);
+}
+
+.done {
+	.choice {
+		cursor: default;
 	}
 }
 </style>
diff --git a/packages/frontend/src/components/MkPollEditor.vue b/packages/frontend/src/components/MkPollEditor.vue
index 471ec391698ee22ceae66fbd65f1bfce701ba8b3..2da9339944caac0b4e87b574bfb3066cb5fb3c6f 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 93b9eb401d7c248a8dd6b96837ad5890ad9ee6c7..30af36566947c8f2329bf808f80598fe9710f165 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 c65cb7d6e5d247c0ad61e1459bb8c96ae411b1a5..5c65569683041ea64d94bfb32d40cf97cc10f065 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 760c6e5d08966cbc7a9edc8aad7699afffcd2936..18fa142ebce45fff4257e3a942af6c4a41e8a04f 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;
-
-		> .file {
-			position: relative;
-			width: 64px;
-			height: 64px;
-			margin-right: 4px;
-			border-radius: 4px;
-			overflow: hidden;
-			cursor: move;
-
-			&:hover > .remove {
-				display: block;
-			}
+.files {
+	display: flex;
+	flex-wrap: wrap;
+}
 
-			> .thumbnail {
-				width: 100%;
-				height: 100%;
-				z-index: 1;
-				color: var(--fg);
-			}
+.file {
+	position: relative;
+	width: 64px;
+	height: 64px;
+	margin-right: 4px;
+	border-radius: 4px;
+	overflow: hidden;
+	cursor: move;
+}
 
-			> .sensitive {
-				display: flex;
-				position: absolute;
-				width: 64px;
-				height: 64px;
-				top: 0;
-				left: 0;
-				z-index: 2;
-				background: rgba(17, 17, 17, .7);
-				color: #fff;
+.thumbnail {
+	width: 100%;
+	height: 100%;
+	z-index: 1;
+	color: var(--fg);
+}
 
-				> .icon {
-					margin: auto;
-				}
-			}
-		}
-	}
+.sensitive {
+	display: flex;
+	position: absolute;
+	width: 64px;
+	height: 64px;
+	top: 0;
+	left: 0;
+	z-index: 2;
+	background: rgba(17, 17, 17, .7);
+	color: #fff;
+}
 
-	> .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 6326c498d7e197d366d674707176e3ff4a7bf9fa..98af92c6f844cc4739211ecd8937e80a23837b9c 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 b98c814f24b2b270ddd9364534cc2c491849af5f..448084d9ba9527344b80dee6f962d1f23eeaeb02 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;
-
-		// Register
-		pushRegistrationInServer = await api('sw/register', {
-			endpoint: subscription.endpoint,
-			auth: encode(subscription.getKey('auth')),
-			publickey: encode(subscription.getKey('p256dh')),
-		});
-	}, async err => { // When subscribe failed
+		.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
 		// 通知が許可されていなかったとき
-		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 e2240fb4e13500bf02bf73b16801e03ba2ba3fe0..84be10078a610c58dfe200a195027c1172721176 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 0c0cc36692ead8642e3757216721e0812e432845..cd2a359d5c14afa35c03ba8be08d11309c328f06 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 29b3f9b85b451b0aedc195066f2e18a7843b9633..dfb06f63c44ea0791ff150e20d1d82809cee9955 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 4d67dc3da24a018bbe445a8846ec19479da34725..34afa72232b7fe072360e726694b464fc8073686 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 f5e611c62a08db711dde8c3863fa70ee8d61c734..99960f5d250aa294bf143c89841016f90bf93373 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 9480af51020cd1f421ebc6cf9e6f7e21602f5aaf..aabebb3abf759395ff2f89be908122435fac1f51 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 3219c8a92c15ae6c80c68e37925ae699b68e56c3..ce146463eceaa5bb0f3c65e06c306033bf1739e7 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 56025535f184ed989884e452f4c5b0452df923d2..814a68d4daf0e3b52711c35aeaa47c0ba9cea843 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 8bd0279806208bf9424600196e14abe2061d6e19..9f56189f3efc2cd837f0293e598f288a22217b99 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 9d93211d5f8aace5d3825ec1c0cdd9b4f1e72674..60c3a47385a592f2b95add8ba970a49912f12908 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 2f5866f3401e8169fd0569a02500769cee7777be..9fbe1ec993f43e3afbecc2300a46e263fa1d9c79 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 922b862b47020b7ed2e07884204e21759d450302..0000000000000000000000000000000000000000
--- 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 ffc5e82b56198de5a2c3f3acaa0cf2186d95b756..b1a509b9e678a29684a3747cca86a5f0183bd1f5 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 08e41d6ae56214dd52bdceaf0a25864443889cd1..eb5876e584367775a15ef61eb17b876756e05d98 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 0e8bdb321e243aa093c3df0109ee47de4a413939..472269abaf5a6fe371309e16cb1d0eeb34c7978c 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 6da81c3bcb081f65b6af6fb0bf4a1776c443efa7..b6ffba6cc7080d9f1aaec25ae0b1d12d81bc39ad 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 17f8b864254a7e7782ee32953153fabf90376bba..d8d002fdb6d947ebbc7cc98be2cabed5378f2c27 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 1ac7107aa784f26189aceee13215ba0c61fb30b0..3a050889c8d685ec69faeaed8d8b9b659837d377 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 2a8e43c570b498f335d918f0d7b7f2176240f7b6..72b70416d9b9636f525055d06e494df7ff5972af 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';
-
-export default defineComponent({
-	props: {
-		def: {
-			type: Array,
-			required: true,
-		},
-		grid: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-	},
-});
+<script lang="ts" setup>
+import { } from 'vue';
+
+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 6f819bbbd741d116384b2edd04ae08eb991b4f62..7274f9b310d2e0821e1fb4be6c9b4dc612b845fa 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 4e8d5bab7fd0753799187a0e50f6a66dba65b8f9..6e4e054aad1c3e6d99af972b7135643bf41add16 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 82b631edda3ef4a5bcb1d7dcebf46870e91c7c2f..83b2ed24447a2818a30f3336ffa64a82006e027b 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,
-	},
-
-	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,
-		},
-	},
-
-	emits: ['change', 'keydown', 'enter', 'update:modelValue'],
-
-	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;
-			context.emit('change', ev);
-		};
-		const onKeydown = (ev: KeyboardEvent) => {
-			if (ev.isComposing || ev.key === 'Process' || ev.keyCode === 229) return;
-
-			context.emit('keydown', ev);
-
-			if (ev.code === 'Enter') {
-				context.emit('enter');
-			}
-		};
-
-		const updated = () => {
-			changed.value = false;
-			context.emit('update:modelValue', v.value);
-		};
+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;
+}>();
+
+const emit = defineEmits<{
+	(ev: 'change', _ev: KeyboardEvent): void;
+	(ev: 'keydown', _ev: KeyboardEvent): void;
+	(ev: 'enter'): void;
+	(ev: 'update:modelValue', value: string): void;
+}>();
+
+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>();
+
+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;
+
+	emit('keydown', ev);
+
+	if (ev.code === 'Enter') {
+		emit('enter');
+	}
+};
 
-		const debouncedUpdated = debounce(1000, updated);
+const updated = () => {
+	changed.value = false;
+	emit('update:modelValue', v.value ?? '');
+};
 
-		watch(modelValue, newValue => {
-			v.value = newValue;
-		});
+const debouncedUpdated = debounce(1000, updated);
 
-		watch(v, newValue => {
-			if (!props.manualSave) {
-				if (props.debounce) {
-					debouncedUpdated();
-				} else {
-					updated();
-				}
-			}
+watch(modelValue, newValue => {
+	v.value = newValue;
+});
 
-			invalid.value = inputEl.value.validity.badInput;
-		});
+watch(v, newValue => {
+	if (!props.manualSave) {
+		if (props.debounce) {
+			debouncedUpdated();
+		} else {
+			updated();
+		}
+	}
 
-		onMounted(() => {
-			nextTick(() => {
-				if (autofocus.value) {
-					focus();
-				}
-			});
-		});
+	invalid.value = inputEl.value.validity.badInput;
+});
 
-		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;
-		}
+	&: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;
 	}
+}
 
-	> .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;
-			}
-		}
+.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;
-			}
-		}
+.focused {
+	> .textarea {
+		border-color: var(--accent) !important;
+	}
+}
 
-		&.disabled {
-			opacity: 0.7;
+.disabled {
+	opacity: 0.7;
+	cursor: not-allowed !important;
 
-			&, * {
-				cursor: not-allowed !important;
-			}
-		}
-
-		&.tall {
-			> textarea {
-				min-height: 200px;
-			}
-		}
+	> .textarea {
+		cursor: not-allowed !important;
+	}
+}
 
-		&.pre {
-			> textarea {
-				white-space: pre;
-			}
-		}
+.tall {
+	> .textarea {
+		min-height: 200px;
 	}
+}
 
-	> .save {
-		margin: 8px 0 0 0;
+.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 fb0a3a4b67d823449f683ef54847f7db642e653a..2595ebc45d1b8cdcef1e37d869ab003e8f6f181f 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 ad53c7f28918fbb2120b1419e0d2b780b2b5c466..e135f564727cf388efd12b5fe9f60e42efcb3923 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 56be0444052ece4ee3ab518d7643cae9984ab7c9..3ddd81aaee19720be3afd0ccc88bc0f2f477d8a8 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 2d34b090ed6aaf10de3c6175dbf90df4caae72c5..91c9b70a5ae7c7b916f1a09467d7f017b3597dd0 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 eed7fa71f6c33f1ba7eaf4e183c7e8e57f942388..3a0b2abb4e053e61b72108cf6b69b83af05fcaaa 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 9c5622b1c586482c8c02314cc0adedc6c6d6f683..fcad5b8064f8c46ee98bc83af1fe795830a79e03 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 e244be3e963b3d3e5bd50690486f4c5e8a574847..36a9e2f73fbe7b6049d1f02c6d235a391be790e6 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 f560ebcd8a3389cc305996005e72e6df01bbabdf..172b517511158a52ccc0bf37074ffad95015b281 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 251ab5d79ae2533eee3ec4f09fc7d652d6bb9279..a2c2b53b08dc1117af5a126a962b4a176cb6a734 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 8ca035544801cc9ad46f67c9b99bd611be89edde..c3b777a12ecd8571ef6d10c9c88cd5950242231d 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 dc78bbf42d3faa1c2db5e88fb76346bf6afc9565..792ff7afd7bca59f2b9337f649fa5bca2851ae4c 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 a2a195cb0994720685cb2ed3ad8476a4cb4256f9..789f88a8fe53fb6eb99853d41e28090a45927002 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 e9f4f68df89a9f51f4c524f3340924b8b82e42c1..5cea67ccf51150f323a38412d63b35d92a53b81c 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 f26ea112142523c7176f4c468613e60939527ead..3107209b97f0081544883d09cae0f2a37970b8a2 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 4e80a5c0fbe6f7489502c83dc47d095436a12d2b..566441213e976067fa69a8629e74ed93ef50c89a 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 d0f95fceda4bf05cc22389735ace02354655c3de..0b80c2edc78f27cef0c77abb56d148b103b5ca26 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 c181d84bc0f2ec5ac05c3b56ed9ea158f05b4698..c8dbe90944b2cb67da9e861671907b59756f44b5 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 62267681273b140c08ca00bb7a1a2c5afe8449b0..9566cc651f0a1efe99cca1502f2892136c53d1c3 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 da98da29d0a33e48f1fb81afa0aae1d126fba962..1b6ab1f13aac3a7bd342e6c316e89f3d4917576c 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 ad1c02a488d85191161bf2e0d5ed7ed397ea6bf3..30547c7444062546432d725d96829bf703821ba7 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 b662479b2a28ba032a2656f9890807fa6ce4d6de..dafabf2ba8582c52f9283ee655d727fb84c8d0fb 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 4d765fe2f7cc09c4f9cd1bc2d40699a9f3a63df9..0edfd98efcf0aac09e84ecb1d5bf80020f6f0957 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 a1775c0bdb125d64887a71df970adb92fffdce2f..22b5edc3c9fb2caed04a59ebf5921912eda306ee 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 {
+			padding-left: 4px;
 		}
+	}
+}
 
-		> .text {
-			flex-shrink: 1;
-			white-space: normal;
-			padding-right: 12px;
-			text-align: center;
-		}
+.text {
+	flex-shrink: 1;
+	white-space: normal;
+	padding-right: 12px;
+	text-align: center;
+}
 
-		> .right {
-			margin-left: auto;
-			opacity: 0.7;
-			white-space: nowrap;
+.suffix {
+	margin-left: auto;
+	opacity: 0.7;
+	white-space: nowrap;
 
-			> .text:not(:empty) {
-				margin-right: 0.75em;
-			}
-		}
+	> .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 79ce8fe51fc0d6a444108f77a9a1973a91349acb..809d80620fbab60e7eb6c6f07e9a66ef1646c099 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 3a44c3da3d17e7db36a67a802fdb14ae02787545..b3d8c22b27d30936e7ae47924489f2f4482bcace 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,
-	},
-
-	props: {
-		p: {
-			type: Function as PropType<() => Promise<any>>,
-			required: true,
-		},
-	},
+const props = defineProps<{
+	p: () => Promise<any>;
+}>();
 
-	setup(props, context) {
-		const pending = ref(true);
-		const resolved = ref(false);
-		const rejected = ref(false);
-		const result = ref(null);
+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;
-			});
-		};
-
-		watch(() => props.p, () => {
-			process();
-		}, {
-			immediate: true,
-		});
-
-		const retry = () => {
-			process();
-		};
+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;
+	});
+};
 
-		return {
-			pending,
-			resolved,
-			rejected,
-			result,
-			retry,
-			defaultStore,
-			i18n,
-		};
-	},
+watch(() => props.p, () => {
+	process();
+}, {
+	immediate: true,
 });
-</script>
 
-<style lang="scss" scoped>
-.fade-enter-active,
-.fade-leave-active {
-	transition: opacity 0.125s ease;
-}
-.fade-enter-from,
-.fade-leave-to {
-	opacity: 0;
-}
+const retry = () => {
+	process();
+};
+</script>
 
-.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 40d134dffb33a3cc65130f19cf4e6c8dd9048742..4e608c6efe995198038e5283a5685e54806ba1d2 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 59358aef70c0aa3dc9a70d9b5bc40c3f1ee77abe..f93659f5ed6f6c7b1ddcd4d81c802e6df00ac5c1 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 aa975600f041170a651d819e0cace1d52e3c8b0f..8b25ab1b6a637f3fbdecd9f792f8a691e470a9b0 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 42abdcbdccfc879c2a49410bcb60eb34e734ae15..422b35c9ddfe674cb78f780762a420d4982032fb 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 1d46ff1ec9f355e0a2110ade6ae2382fe3f5df4b..4b2e8e4750762c14712f7dc6065a3ffee2b89176 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 0cb31ffcba5ce5d47e7a1290963c3da166d00a30..e8a7f17cc6af0ed79a93d2fa688750ff97fae8e9 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 f6811b67475ece7404088ee8f08130b4811bcea9..685b3b8b8e6186ded648322d61dd94191880675a 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 0000000000000000000000000000000000000000..2a50a34390b243785fcc64247d17ea4a8843ba02
--- /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 28a0d1c9861c9c9b94b0320d3726114983dfa0e1..0000000000000000000000000000000000000000
--- 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 9e1da64e61bb7e0e13ce14224ff3e0e2f4739f07..d71343baf9f9a6ea499319954429c53ca39f6600 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 b91d378b17ee76d64c1b792d47fbc365bf018b05..0a21d39bcad9c95595fff6dce93f204218e6fa68 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 44c02088dab49351da3cd2bafa4d06c4e1b98212..e5dba54b4e747f04b5d0eaafcd94b4708049b4d8 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();
 });
-</script>
-
-<style lang="scss" module>
 
-</style>
+defineExpose({
+	rootEl: $$(rootEl),
+});
+</script>
diff --git a/packages/frontend/src/components/global/MkTime.vue b/packages/frontend/src/components/global/MkTime.vue
index 261cc0ee18ca2f93c7ac927d1aa2cb5f4af8d0ea..dfc3c89798b6b4837a35a06207b1c81ea7a9434f 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 2a9278030652b9b1ad78ff750341e0ee2ab65a27..c1efd9a06b8582995eb377cbe7eaa5ffa6dcb701 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 4186a4a4fb4c0b14fd024abe78f4dabbf3d8ad0f..c9e85c54600350b97ec659e49773c808bfc76459 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 1fd293ba10755e33ce42c92fd1272f69bebfec04..2708b759aa842277124fda3414db43695a2b5b41 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 4ef8111da910dea910c82d6ce5e9ebf31d143848..ee2a2bc7bd14b3310192bdc9b106d08c3e338740 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 c3c07b583416a3f1ea4d5cb7061cf2c04703bb40..0000000000000000000000000000000000000000
--- 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 0000000000000000000000000000000000000000..71249a8aff5ba9e686e6f6c85dd086e150f7ac38
--- /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 f3e7764604b99255320797853de3cae7a4cc8fc1..2bf3d12daafc3eb555d5b5f31192d112b63010e4 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 83931021d856376ee7f62d64dfe1622aa68659bd..0000000000000000000000000000000000000000
--- 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 82ff36ec36e0de9ababd950e6f219e57e88520f9..0000000000000000000000000000000000000000
--- 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 63fde6a1207f9bc5cf51a7f240a73924f95db9df..0000000000000000000000000000000000000000
--- 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 372a15f0c661346311db2da35d79700e98f4a99b..0000000000000000000000000000000000000000
--- 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 6ea81d257f086dbdfeebe38806f18d47977d669c..2edcfb8b1a30c72b3c7bd5ad8decd83dbe2786f4 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 8c65dabf08471966ecf0ec495cf26696ec627675..7133a7f5a15057b8baa11d08e35d151bd35457ea 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);
 
-		return {
-			note,
-		};
-	},
+onMounted(() => {
+	os.api('notes/show', { noteId: props.block.note })
+		.then(result => {
+			note.value = result;
+		});
 });
 </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 72c1b6deb091fbcd68329fe3381f3cd384cbb81c..0000000000000000000000000000000000000000
--- 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 55da610cb693b3e8d383b4641416451810c39ca3..0000000000000000000000000000000000000000
--- 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 ce8f252e440f633eb2d46a95adb56ae31c4b3221..0000000000000000000000000000000000000000
--- 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 50181b390568f7fd3446ab553c3534f2c64e3123..83a16ae0a5b7bc6ca4b29add241c581a067e09dd 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>
-
-	<div class="children">
-		<XBlock v-for="child in block.children" :key="child.id" :block="child" :hpml="hpml" :h="h + 1"/>
+<section>
+	<component
+		:is="'h' + h"
+		:class="{
+			'h2': h === 2,
+			'h3': h === 3,
+			'h4': h === 4,
+		}"
+	>
+		{{ block.title }}
+	</component>
+
+	<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';
-
-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,
-		},
-	},
-});
-</script>
+<script lang="ts" setup>
+import { defineAsyncComponent } from 'vue';
+import * as Misskey from 'misskey-js';
+import { SectionBlock } from './block.type';
 
-<style lang="scss" scoped>
-.sdgxphyu {
-	margin: 1.5em 0;
+const XBlock = defineAsyncComponent(() => import('./page.block.vue'));
 
-	> h2 {
-		font-size: 1.35em;
-		margin: 0 0 0.5em 0;
-	}
+defineProps<{
+	block: SectionBlock,
+	h: number,
+	page: Misskey.entities.Page,
+}>();
+</script>
 
-	> h3 {
-		font-size: 1em;
-		margin: 0 0 0.5em 0;
-	}
+<style lang="scss" module>
+.h2 {
+	font-size: 1.35em;
+	margin: 0 0 0.5em 0;
+}
 
-	> h4 {
-		font-size: 1em;
-		margin: 0 0 0.5em 0;
-	}
+.h3 {
+	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 b5f3464512babdeb315f4474b82251c44c5b56bf..0000000000000000000000000000000000000000
--- 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 d020a99de88318fc7dfa8c521885f12a9377d0b4..0000000000000000000000000000000000000000
--- 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 e0e4959efa26019ee04ab8c544cc451454324112..48ce4b0e1e4d70764a076be1856d3e7cfb0694d6 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,
-		},
-	},
-});
-</script>
-
-<style lang="scss" scoped>
-.mrdgzndn {
-	&:not(:first-child) {
-		margin-top: 0.5em;
-	}
+const MkUrlPreview = defineAsyncComponent(() => import('@/components/MkUrlPreview.vue'));
 
-	&:not(:last-child) {
-		margin-bottom: 0.5em;
-	}
+const props = defineProps<{
+	block: TextBlock,
+	page: Misskey.entities.Page,
+}>();
 
-	> .url {
-		margin: 0.5em 0;
-	}
-}
-</style>
+const urls = props.block.text ? extractUrlFromMfm(mfm.parse(props.block.text)) : [];
+</script>
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 db3a96dd1b12387783ab514f09b69b8ada8b1c3f..0000000000000000000000000000000000000000
--- 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 9b82412e8aef5f59241c2f3699d48451a35759d9..0000000000000000000000000000000000000000
--- 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 5f1f62581ea481c29636992327b2717a3b431924..c2c2693224758826e158d9085e395f7873b9f5b8 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 a89a420d77b8026e192d26aa16ab3f51b502d40e..9b738b2fd46357e2d6f294f91934562c563d33f9 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 5d13497b5f569786f5ac1223656df3fe4d0d3743..373141fa359947b96ab2f127ade16a274fc0b531 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 402e82e33b87f8b1cb25886873fa9022f3416861..fde06a4aa0a5e05e52a5db80a142316f7f643a10 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 220c6210c067c7cca13149322ffc5d94efdf269f..30771ec1b37913d9adc4999959fef55fac3d3485 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 49e7bb4008545a4032f60b342f8096fe55151427..0000000000000000000000000000000000000000
--- 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 441a35747a3be50a9684c5547e6d234adc4542f2..ca4f21f79b0681a2933112cc1d760825218e4f3e 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 c4f9d47d7d35ba639516b877bd838e14e9888b6a..c44d3480462adb1b8de7861a428649de0c0df812 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 f53fec7d94a0649a15f9775aa668918561e57139..f27d2df336f88b4582866a6da7dbad4d46ce13e5 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 9e0594db3c4937663dfcda08a9b02a32f2f97d1b..0017145fa151797f28a8af83aae1503d59bf61fb 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 2d82fcf2771371242922f76672559cd1366f6f59..3744bed10ff639266784cb27245050ffafe515cc 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 8fe613a9a86751d7482c5581b4bb3d56945e8820..a8c6c05d8b74ffae8070b2a44ca914b0e60584ae 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 8e29990426fe3eaf0a136a15661418c49461c36e..693d369b891327e7611ef14d520bf76d4c02da5f 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 1eef7a53fe37a328b09a66389488ab90184357bf..dc47d8dde0ab342333cd9f56d9ca6cce80f4c2eb 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 1d309a7377880f65cbea59ecd270384bd0a04acd..24c863ba626e92a2b562df9d0777f5c69f161b3d 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 343d2c4c5cf7e0b6d8089d8506011ad844d6b9b2..9530b27bad0fdfdf7cc8feea6dd7300056e22988 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 9e8af43024e6eca006fe08db606a2ad5401bb7e8..3bc5ee97235849a705c46c1f34ea2130452099d3 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 803e8cb7b063f8e22436c57d880f092d0368c218..2c9e18b0bffb03312a47c44b75595687c8762968 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 b76e4b911491f854b29d5d9ceb5edb9259db46cc..3cb32c1d9daa316af6d940a5b17087c107af4727 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 5a0d3d5e51a182f26b8a5453dc55c5302018d89a..131e586afd0795a285a3d27763f7f512ec76fa31 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 d51bf6230a9a02d47332d04dd3eea943a92980a2..4f5bb379ad89164f86ffbd45118ae032fb212501 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 6af1610431b4c2018108cf67b48197e2f534a5a2..a8d6dcf3de6228420111088bed5e55c5a11fcfd1 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 c189437246abc1d6b005bdfd7cb44246d918e98f..b204a1a64a5faec670992512b7a54edb6e7bec0f 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 963393d7e535186247a7f54805c24c463300aa16..5cbbcaa44cdc19323a0d23a0e8ff9e41de90f64a 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 7a4937093ee14e781836a7c579ec9768cac0a04a..e5f3816c82d4d6c784559d067f3bf63538d5a3e1 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 bf788e3609876587ac4d5a4f465ee7a32405a911..e36c9ac91d64baf4ece3e8bd1ad40d0640ae1437 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 704b27c1745304500a401e2d35471f07c93c1708..e569aad1b88ce50fbe4e42217b0faf26bfdeda3d 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 62dff6ce7fb9424b063d6853a9aede73d7a75b4a..15d720a070367280a8daddc42646c280b127e5e3 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 6c2ffd474242517227d662ca5be333f7af13b711..d349b32322aa15cac3a47d8faf07909bef9ac902 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 08a29bf550373933f6c13cee800f352069c7d84e..af7bc70551fbb0c1ca1a26e0fbf0149b78a7b088 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 6a11e8b768fa622c0ab6232aeb9f508c92b7b387..a3c8659ce5e3cac71cc2138386642bdad7469625 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 1f56a2826aca0ac8ae6c14853b7f680f74b3a29e..69ca89e226a7f2ecadf2558c8a0e305ec1244dff 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 5c96c07bfbe2a5256f71a64c131d0c7a6ea0a0fb..e8295c81b5aa242f8ae4dcd50d08cf451349711e 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 6ad566187a4b9723b518ed816906a63ce65bd096..c81f50a0d238541a7afcc8fc4c31535d00746f5d 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 1a1f6a9db4dd434426d8a6ba50a5f0824547f348..9bc0eee2124fdce9806d66a7eed071eec5791800 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 100d1ea545892cf3eb25e1badd6992b1a681e14b..8e6856fdddeb9dda07c4f56e01ab875e1025a111 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 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;
 }
-</style>
 
-<style lang="scss" module>
 .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;
-			}
-		}
-	}
+.statusItem {
+	padding: 12px 16px;
 }
 
-.jobs {
+.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 509d329eb16ced178d5ef5e31e6293355f7c3ed3..1282a4b49f3f25eb4888131749fafa162e1d2da1 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 7ebcdfc58340b31a6617abe7c938763c8acee0f1..119439c958e3f3ba102622013e2f6a6549e79431 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 c211ef2f055f1f73f79c2c2596066c16121d1baf..c7a34ac77b8ad853e92586b338ba52480de3cde2 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 49942c87ce58aec4531441e5b90674f294768530..a1fa9d2932d6973a314041cb995c1257877c9a4c 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 6eac902577b2b244638dd7e1f3c7b3d625a4f50c..4ed6abf200a04c602d6b73ac9aaf1c95cb9b7396 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 e8dbe1c5f0ed6c16fb3d557266c899d227c2fd91..6634d9cba94caa0c517f5ff2132e59e1d40e3f57 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 cd8ef9e68ba383d3e0cf11e7d6d1a924b1fa9097..efb9f81f25964626781a3d6b68fa80305309a5dc 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 85781c0bd00cd128a503f2b0d922dd7d964fa398..fdba4f464e4c40e808ca71afc8d40f053c18e2ea 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 7ec3c381f3a33815a24a4330b0d0c8d2db093c6f..39d5ae8607546420c08350f4ebb1415deb90a144 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 819ced826d4ad87fc0c4aa646b71d3d0493c3ae6..1af661a475bad934cd6f7243cb9b6453c8c01939 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 728ef3c0b120a66f7ca4fd7c3550b6aacd07931d..4cf2e4b2e5f918b759a2dace40be216dfae2bae3 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 16a0ee837346f1af886c1f0a32545eaceb8dd35b..3dfb9e5554ce23eb16933464eb5b0dd60be817ee 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 62e8178af1d8e6cf14d080a9542dd02fc8cde6eb..a22714791fd000926d241d2d877b3c25741e5e0a 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);
-
-	> .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;
-		}
-	}
+<style lang="scss" module>
+.new {
+	position: sticky;
+	top: calc(var(--stickyTop, 0px) + 16px);
+	z-index: 1000;
+	width: 100%;
+	margin: calc(-0.675em - 8px) 0;
 
-	> .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 7d2828e91dc9775c77f72cf878667d4299b7dfdc..3a3cb3d7d37e9e3c4ca07d2e275a4dce1f720b99 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 2f40e7ded68a22abe60495afafb85ad37cce3f31..54e76805bfd33b04565e7aba70cc4114495aafc0 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 a74ab40473a5b6e05be491db1a85fbcf0c28ef8d..0a358a141b620ffe88837a75b739b72dee3e0f8f 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 9aa564a7dac7ad66cfa66f76b2412098dd85edb7..bcc0fc6860c07447628922a08954568e0e2682dd 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 e670cdd864787e89bffa4121d0e1b42c62e7a77f..0c4ccc1bcdbaeb35767ae5650a30aa1409934092 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 24eae32e1310729b8597070a8c768122c89d2b72..69ecc9e7721e5a4d81543b59fb3d791e5d662067 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 e3ac3f4c9bb722cfe38210308d67c71c4d790cd0..d5313099da8d8eb714e16443e784a02c457ac549 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);
-
-	> .description {
-		padding: 16px;
-	}
-
-	> .user {
-		$height: 32px;
-		padding: 16px;
-		border-top: solid 0.5px var(--divider);
-		line-height: $height;
-
-		> .avatar {
-			width: $height;
-			height: $height;
-		}
-	}
+<style lang="scss" module>
+.description {
+	padding: 16px;
+}
+
+.user {
+	--height: 32px;
+	padding: 16px;
+	border-top: solid 0.5px var(--divider);
+	line-height: var(--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 3f13f0787d6a0d17ee8b2bea9aff448b156eb277..3da6a0d9cb1ac38915d2dc56f3117f870fec085e 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 84bc153b71d4d75fcbb7a669655f4bd3ff68ce4f..3208c92738d8fd8efdedaf7d23fadc991d3387df 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>
-
-	<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>
+	<template v-if="emoji" #header>:{{ emoji.name }}:</template>
+	<template v-else #header>New emoji</template>
+
+	<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 bdd21b29eeda3930a9c62b05ddbf074a2d576dc0..e9fab6a313fefeb4504e54d5e7709b17f60bf773 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 a972ae04ec32926332dbd4e62eed330eda9925eb..5c713137384636bbf7f0ea0a47789cbbacafd1bf 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 6ac469f7bae3b6ef9762c05bc44604dd80f947e4..c855d79f45e5a84b6f544603bd82ba8c6af2fc93 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 f9c833dd29fc2792aaaaaf40eac859a6af2786d0..785dbaa343af96265e901dfc4d3161b9e3027f50 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 0dc9b9dc8f68a69a492bc079be310e0c28671ed7..460bf65d1ea522b965cb22d255898548ccfc0c8b 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 816825e5b6e5649d7062cb20e2939196664a8b87..6a16cd1c4afc739df2e122e046840402212a5753 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 f1dca5f240726ea1ba59c71c382cfffe23407fba..1f933c23466fafdbfb9fadd5e8f263b35d02ce98 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 961ef4b751288b39f235c54c9908fe8d7e3cddf4..2e1532b9f3e64e4a0fc419ec0aec21c21512fdac 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 a51d1c78a4f722d359ffe73302afa857be8b8f32..1452942a1e549d85d45064c33d590cdfa8081efd 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 828246d67862e5899882af5191bb64128cdf6488..d14b66336476298be0619d5e7afbb4f23288ea81 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 cafcee0c33981e76337557dab26c8a8f45b760d8..f381636a785e205328d5ede367a893c1b6b78e4d 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 fc9cc7ae9e997f4345783a510bbe293aee69bdd6..3c9c21a2ffdc65043b22c1e1ec5a59552f3a70ce 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 e0f3c105e159392d4884affc44bb171cea537bb7..dfa6c0bac0e5a29fe8b15630483e627a10747e13 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 ba5fda137a4c978e490e6eff08db6e20d3e1bfe3..83997b255549825d86b0e1e7de49b6092b162da1 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 0000000000000000000000000000000000000000..f92c06d1c53b4e6f72aeedbe3677244c5572b3e7
--- /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 8e0624f555b957a3914d2a12bfb118d7566a4fa2..553946cd9e67d171a895d85b2c1bf5c58958f98f 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 c35af3e22a8c04ea84bfd090e9485d9ee2f75fe7..355d18fdb527d804a4cd42ce6299afa48cf7fd49 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 913fbde8e95620fe1b1c51f60d899b1c9c964712..da9b2de48f1d67b54ecbeb34c715d6194bc15e29 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 26b7bcc71b6614e017a510cc6b075b588fcd392f..ed92208c42f584d14d8ade3694842ef27ec34ef5 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 f1764b1aadf15382aa0b41a244371204661d7751..2ca026b9a15ab75ec36db921b2d089be2711f5ca 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 ccffa7b5631c47307078884d20f738865fc58b8c..a769f8ee9761a28d47ff3d77c9d63362dc68f2d6 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 47437f3e57a73428e1b22df06d2fa637e7b07f75..cee241c489632c54bbb5a81124827172d21a204d 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;
-	}
-
-	> .lists {
-		> .list {
-			display: block;
-			padding: 16px;
-			border: solid 1px var(--divider);
-			border-radius: 6px;
-
-			&:hover {
-				border: solid 1px var(--accent);
-				text-decoration: none;
-			}
+<style lang="scss" module>
+.list {
+	display: block;
+	padding: 16px;
+	border: solid 1px var(--divider);
+	border-radius: 6px;
+	margin-bottom: 8px;
 
-			> .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 86201e8e0c87748b8e97c0d69fdeac9f7375c90b..dd431e8dc0426fc30fa2b131f3ea4c91e97b1756 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>
-		</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>
+			</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>
-			</MkSpacer>
+			</MkFolder>
 		</div>
-	</template>
+	</MkSpacer>
 </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 e58e44ef79abc27ae601fafde2d751a6b7b771cb..2c9d949017ede433fda55cb6719a37f08fbaff8e 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 d9baa1096a27b21e13938ffde31f658e987801b4..c519cefbaf44d1b49623bc1ce4bd7452510f796d 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);
-
-	> .note {
-		> .main {
-			> .load {
-				min-width: 0;
-				margin: 0 auto;
-				border-radius: 999px;
-
-				&.next {
-					margin-bottom: var(--margin);
-				}
-
-				&.prev {
-					margin-top: var(--margin);
-				}
-			}
-
-			> .note {
-				> .note {
-					border-radius: var(--radius);
-					background: var(--panel);
-				}
-			}
-
-			> .clips {
-				> .title {
-					font-weight: bold;
-					padding: 12px;
-				}
-			}
-		}
-	}
+.loadNext,
+.loadPrev {
+	min-width: 0;
+	margin: 0 auto;
+	border-radius: 999px;
+}
+
+.loadNext {
+	margin-bottom: var(--margin);
+}
+
+.loadPrev {
+	margin-top: var(--margin);
+}
+
+.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 1789606cd83106d6f3f1112b7f21c1106ae09bbc..8196f918684129e39057c81b29c5e11fb738d5b5 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 1b292e8f3c8e8394772638ffeaf53f526d23c687..eca3feda62f02bf306681660514b7be94f71bc89 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 bf21ae3c676e64577a4a8872d6bfd172356d62e5..3b15c17747a48d6913bb8eb8897ff9706d763c7f 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 97bdcfe80f1ee6aabe48b328dad9a21f01490580..fc945b3d63c46967b2d83098883670e4be0f65f0 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,
-	},
-
-	props: {
-		modelValue: {
-			type: Array,
-			required: true,
-		},
-	},
-
-	emits: ['update:modelValue'],
-
-	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);
-		},
-
-		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 getComponent(type: string) {
+	switch (type) {
+		case 'section': return XSection;
+		case 'text': return XText;
+		case 'image': return XImage;
+		case 'note': return XNote;
+		default: return null;
+	}
+}
+
+const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
+
+const props = defineProps<{
+	modelValue: any[];
+}>();
+
+const emit = defineEmits<{
+	(ev: 'update:modelValue', value: any[]): void;
+}>();
+
+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 dd733403af750d34a5fdb65a84c85bfbeaa713d2..0842b4fd26097dd060f68711774579e6278d2282 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 bcf30e23a7b6070d0b3f9c8233c14f365ebeecfc..bd54699dc44a876da2ee927f97f8e0c271d71e1d 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 5a0f58c8df8b3b58f2a92a20d66476b495cd24f3..27a4cd059550dabedcc0253af0fba36c3c4df326 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 0427332ab274d47e4ed5f9d84e50419caa944157..4f67bda11fc6c9e9b111a522459ec9bb608a4315 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 354f686e467c70138ce7f39d190686fae0b16ae4..0000000000000000000000000000000000000000
--- 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 c687b89eab09c2f947b118c7ad2be84e50ba8ecc..b1d41fe2c7790157be018bebd9470cc2f4fef2d1 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 00e2ca5e036dbf130ae2cf02ff2ce43358d12a34..513a2f8febc9bd26214cc3650bf3e59c6c49b563 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 5a029cb0c7e7462035e63bc55b347b8ba3edadcf..6bfb9bce58c78a5b8454f7a9288cd7b827408a69 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 38c88cc650ea676863dfc76b3ff822ba56b51289..9d5730731433b38d0e3d4181b1feb36dd278d70d 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 fe39c594ba868b10ac348e49607a70718fa4f2e4..e85ab0917a8434bd90136ea77e92ca9aa38d4d2d 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 fb78546cb1086eeb120910e6a8761d290956e70b..22eb00dad4ad33eda18b339f450cdcde9b1d3058 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 23a8978fd15eaefbf96cd486b480b52fb67545fa..bd1389ffef97e9268f6d3c52ac4ec6eec6584610 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 9f3d8da5602aeebb27cdee0d840f19548420131e..dcaf42e6488d33bbd938712f086a79d4537bc7e6 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 1d836db5f5d78da89329c8b5f5925f8343438bb1..6a798b562671639734838f5ae20266bab88acfe2 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 891934d706a83c6efa1b8c956b5f48184064a200..aff7765ed50953d6c5391a7d27b8b3b7adc925d8 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 a58e74fe69eab15f765f150a92404573c877300a..78479be973eb5aaded5bfebd88487f952e549e8b 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 599d6329e2c62a62ebb200fbae45960700cd387a..fbb78200d46238a523644006878c39be78b8fff1 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 456c3742c5e480d43c671e32f12d4ae98622eb27..970d5689b4bd3d101b4b920337eba60cd2bbd99e 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 73c2b2e6040e44c6b2c7a6418f47e0539131bd33..8d7b30dc6eeb14925bbf3ecbd6aa4ad5a1cd7e02 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>
-
-@use "sass:math";
-
-.uawsfosz {
-
-	> .meter {
-		$size: 12px;
-		background: rgba(0, 0, 0, 0.1);
-		border-radius: math.div($size, 2);
-		overflow: hidden;
+<style lang="scss" module>
+.meter {
+	height: 10px;
+	background: rgba(0, 0, 0, 0.1);
+	border-radius: 999px;
+	overflow: clip;
+}
 
-		> 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 b1e6f223b6417dcde2dbe0297d6931556d757c5f..d015cec1541ec1140c99ff3e8ac50e52c875f379 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 ba0f3274fc827826b7efac411bb189c1134d715a..20b36f0fcb549dedb5c356af82056fe8e9139ff6 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>
-	</FormSection>
+		<template #label>{{ i18n.ts.other }}</template>
 
-	<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 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>
 </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 34a962ef4cf86849170c2a56ffa67065df8ba1f5..b4f056d8a6880e1872e0b515304726137898f13c 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 541992875e5bd009cb753d2fb415aa6f0ffb0af1..102bc68523a8d95dd982e09f8366ea19173a6576 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 b3b33b8026d1667a90f7f0f0a32dc428751b2114..8780bfbc1e4ecd4de037ef0f333a63bc665decc8 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 2cf2f6d7f65080a38c403f8b5fdc5ba1a70e40ce..2552db4198999f8ee3c9204e64fa8152f310de93 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 776305d723c087986b38d0f644384847e48e138d..0b73780a8bd7ec05e9a07278a85d5ef8899cbc6a 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 8b57dceefbff3f6d3b3e46ee625f98381e3e6f0a..75fae014f85598721a11894c0cbe33d5e9220566 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 6613ce4c1d8639758668f1276e4d48c1c11c001c..e34901cd11fe89540a22a64f7b2f716e948e7b8a 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 a1af0ba80bbb200bc22a7b846c36d27fd49d16c8..7fd4d6d34e1e94dd5eec06c6993325c60fd87724 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 6ffd6826108839dbe650f317d9f0f62c96ba0d2b..58217d047546dc8c71536c17442bfb1a67d51efb 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;
+.avatarContainer {
+	display: inline-block;
+	text-align: center;
+	padding: 16px;
+}
 
-		> .avatar {
-			display: inline-block;
-			width: 72px;
-			height: 72px;
-			margin: 0 auto 16px auto;
-		}
-	}
+.avatar {
+	display: inline-block;
+	width: 72px;
+	height: 72px;
+	margin: 0 auto 16px auto;
+}
 
-	> .bannerEdit {
-		position: absolute;
-		top: 16px;
-		right: 16px;
-	}
+.bannerEdit {
+	position: absolute;
+	top: 16px;
+	right: 16px;
 }
-</style>
-<style lang="scss" module>
+
 .metadataRoot {
 	container-type: inline-size;
 }
diff --git a/packages/frontend/src/pages/settings/reaction.vue b/packages/frontend/src/pages/settings/reaction.vue
index ed913731d36b41afe81c4076eb8c50e5bfa15a62..cb483e34b540cc0054093867484216dd5f8271a5 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 ba510dced3eb8df4ede8c65a9bf391bbeedd234e..05753c9b60d7341f64576506f63ae07f9a7a54eb 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 0cc2df09c5e6c0e8524c3b579c7b7e08a7ebf5b8..2da84763a3d68f7175a5a7df557cf9abe8255c4c 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 aa9f52800608e64acc2be0ade2b5baf721cc8079..c1a333548de9e907c9abfe0c2050b36dd8257064 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 724fe4347857098647de6cbdc278b861952ef2fc..c2bf3f8cd57a2802672397a82c336309bcee7ca5 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 81ff873e9e55f50c7265f97669e203c9290d0252..c73ff7c0751d9e532536df82cc6ff230840142f4 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 f5a090a63b1ca251bb3865f5e326620ff69fad31..bfb69936e13423a311bab3321deadc1db3a05f30 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 d1821a00d47019497c57e020312fe20b07cf9200..0255435112be234c7c62650d2273d3bef533905f 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 78e0710162aff4edd01393002ea79b4cba1ea9d8..e0ac899230f9db90f8782f33f5d953ee5c42dbf8 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 545953231032da2655ec717c9906fee373311714..61d7eb24fd2fbe9aff5fbd3eb7eaf75b607af56c 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', {
-		code: props.code,
-	});
-	login(res.i, '/');
-});
-
-const headerActions = $computed(() => []);
+function submit() {
+	if (submitting) return;
+	submitting = true;
 
-const headerTabs = $computed(() => []);
+	os.api('signup-pending', {
+		code: props.code,
+	}).then(res => {
+		return login(res.i, '/');
+	}).catch(() => {
+		submitting = false;
 
-definePageMetadata({
-	title: i18n.ts.signup,
-	icon: 'ti ti-user',
-});
+		os.alert({
+			type: 'error',
+			text: i18n.ts.somethingHappened,
+		});
+	});
+}
 </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 511052c4242395afc593e62303860e992bed3190..104e73886655f364f80eb93a715b9a8a92742466 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 56fdfdf7820ca5ca0a62938ec6f9ca3c37eecbf8..f942b5005b5a160a44e3464671a1867407116fb1 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 1bf4cdc99ae4ee1ae6222fff1b65133a3c8f5b48..a441c6f728666e97cb304c9e4678e4da29e562e6 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 94718d1533324a783f3c7bc27dfa60608e8ddc5a..56e8737e1caa7f01203eac55b118e5efabfcd70c 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 acf7ea9b2c2abf70ee60726ef8beb62b609a7c9b..f66670e1f6fd7230cb6ec018c7da9a131fa0d31b 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);
-
-	> .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;
-		}
-	}
+<style lang="scss" module>
+.new {
+	position: sticky;
+	top: calc(var(--stickyTop, 0px) + 16px);
+	z-index: 1000;
+	width: 100%;
+	margin: calc(-0.675em - 8px) 0;
 
-	> .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 fac7593e9cac082e76a60e99968028b959f979b2..01ef1126cb62ff77ca67a02dde727a03554904df 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 1b3a6e24b3cf1d2d760c47db114b5952ab0116a2..7d5993c265230937babf7952d9a18253a45c4bdc 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 cd538ad61fdd6679fea36f5840e75348b4a2a81a..655371ac1d554d824d51e3c24474b464633de2e9 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 95f8cbc296b4f02c39475df339bc3675590018f9..08b7b9a71f13bff7983aa7b2e0d15870c0c8b328 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 d42acd838f854afc2dfe4810717cf2630564c0de..4e76ddfe795b9baefb6b9525b8e23afb07388409 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 20573e67e946514d93a3d059977c04139524012a..b330f7863774cf7450bd636409c52ae95be0f2c9 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 3825f138cfcf9819194073c36a87e4cb68cd2661..9544cf76ca29d1b562d43c1b637b0ab19170c280 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 b80e83fb11f29fd68b5c00b54c2d7baa4cc1a401..b4bbab16fd35e41b61144e96db58be2b48bb0cf7 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 9c133346d5440ce0d2077b02e44fe2999b5600a1..2e69eb367ba9c06c066a55cbe410e2ff4e945090 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 2d9ee85bc489dd42ce8e6cea13a35c10d3c6494d..64d36307e9957ae74c67d8f5a0f2452cb80df00b 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 d8fc253910b350f87864c8d1c939dd84793c547d..91c580ce96862e79300135fa8b01201c213a6880 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 03a226cc095c5565e1a677f715357edebcd23da5..6aba815e9d74d68da4a322031f59c58d30af9c42 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 0000000000000000000000000000000000000000..78f03d2b38ba0d8739fd06eb7061b082cf18d52e
--- /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 7ea1d75f4304424318557482d025729d2ca8770d..a2975c707910bfdb64cf19d86e4bbd1864299ed5 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 24129ec024670cba3e0e5207a606da8b4155549a..228160339472f516302833b1d5408200d655c241 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 929152bd5abcf86f8e0f960c03f6003864b96ea4..f082b4b3c79cecf1a759404bfd3f4036f563c82e 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 7728d97a6554f348bf763994b521ceaeb0d429a6..2081cb9c93b2fdc8c7a337c65858f6e0bd49e2ea 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 6ec6e3f8633ef977c137b7fcb0c5ecb35aec95bd..a93d103d4feb6c5936279f5984867ed515a6a677 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 2616a8a1d5f1d26206eb90f176c9bf789e135e5f..d97bd4be62e9a996c077b06e150b1d393faa5e1b 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 e46c1eeb77ebd70fdedc64cc68a463cfbfb6df22..6b11137d799158792be486f02a08ad087168ab92 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 2e853b58b50947540e30d155b00d52d9a5ba1619..79661b7ce9472f60410fa717093e4bb2c8c2b8a5 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 ed01b490547c186e44abfabf7cabaee1107d3d32..060c8a1a110915ffd35de4369f30fda24f75e518 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 c8a610025385a3cebb940abbeefc9a07885a5962..960f26ca675b501ce168ad495f280434a2cd158a 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 6ff9fb63f1d92ab1717da4461be98aaeca9f3bc2..b055d264737b3fc58f4f365528b82410349cfe2a 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 804c5c1124c036e476403650a9ee8a4e1a64dc51..0000000000000000000000000000000000000000
--- 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 9adfba7f27b84065f68c81a54692bf6e10122a10..0000000000000000000000000000000000000000
--- 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 18c7c2a14b6da8704b252f6455cdf145c3d1be7c..0000000000000000000000000000000000000000
--- 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 994f286b9ff690e0c85caf65c252bea99668b9a0..0000000000000000000000000000000000000000
--- 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 88db82dd272946371fb5be80d77bfea4d3b42d96..0000000000000000000000000000000000000000
--- 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 ea8133f297619d2ca684fd0606e0100f66e8379d..0000000000000000000000000000000000000000
--- 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 0000000000000000000000000000000000000000..ccce8b02bfc47afb3ab75670a3fbe984f1d6cfb6
--- /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 fe9f0a2447ae22626a81b72c4f9fd18b50d1beb2..44a58d6c7d61f36fa1e8093206e298130374ddc5 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 28284c7bcf31d185ddf11e089d4bfdbc11333543..f2e825356511360b1d1f74fcbbb1fbfee20c5f7f 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 34e8b6b17c56ef714a7ec50d29a863f498c1fe3d..b21978b186893c8adef27aabf771916801f8a7df 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;
-
-	if (!d) throw 'wrong number of arguments';
+	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 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 ffe33cccc09c1def1789ea04efd98b8494ac6c54..22a01e066a347e92cce8e3e5eb7ed7e0e008bf4f 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 0000000000000000000000000000000000000000..1847a8ccff10e57430df72355723526f4fa912e1
--- /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 245bcbefe11f40277bd9afae24ebb84be74481f2..6ba05c36abbd53c592bfebd09d152d5923117b48 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 dea3459b862334a79a0664cb565f74be943ff113..9cae58a26a42d8241e8f2f5c19284a31cf3e633c 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 20254d335ea491d5aba6866430d75f0c7a5e4476..b376e4c42dd09ae2241aa0120d4d5659c4913bfd 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 a23d25e866b2bf1e7eaad63e596a82f5d658cfc4..5ef6adb085e53dbac4178fcab63898c2ecfd9868 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 713756221aaa1f6afeb95878d52a5027e91b0e40..32f3c7490917f59df4783c8c6397e5d438fb365f 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 c6a927ec3ae7e49d4286774f050be97a09285242..09a9ead1a2e3398fc367e2dc615ece71c80cdf80 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 33cf7aa8174502b1226b9645d036bb56b2db6fca..62208d23781e4dd02bcaf7f4f8990f59d24e312c 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 a7e1ad1c809a90a589607f4cef5fe77d418dc4bf..f9638124c2eb1544b9d0ebde86506ac54f5ee6c5 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 63144e88ea26f36cd9b551acda87b6e2ef292df6..ae4f7d53f5dbe9638d3e805b4a823ebd4bae09b0 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 0962a124110a4f655b7e1a312ea0ddd87e91f72a..f2c1f3eb86dd0db52f999603e22f150beda4f913 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 9522f534a41b41fc4ff8f8db6009dc1dcef32726..ca4e688fdb1ad3541dd744d914b973f85b734251 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 e542782c6651ac5870c7e7b6d9c91f5eb88a726e..c2539816e254ebfae74336ea10dcf1afbb4488e5 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 179b060dcf62b15d61367c8dfc0f2462632ab53a..b4abc0cacbe390712b87d3c7f3ea13418dbe4c38 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 e36265ff10ceaf2af5060bf094e1f81b0b1cafa1..0ab6523dd7cb46ca5b22c558846d89ec0c4069ee 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 b270f809ac1718851f99ab0718448eb15ca13c12..ed776746a88a7238259386d38d3cfd6f9d644ed8 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 1ed552557585358df555d9df7159ca157fb590a3..fe1f9f892753f2ed021115dfe6acdf2fb2ab4e57 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 2ea9a7d6c9776a464f3d07d804bcc76d3c179ca0..5c9892789641d8d03f260bd9f6d3c112aaa47b7d 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 5ad240241ed93402f63dec0f96450db90ee94f81..1189a28fe62cb5011c1898b766ecf336b39a23b3 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 fbcd4fa9efb3e373ee42fcbe58a03c160cc3eb17..b64cc735836baa0e038044dc65752461615b3800 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 248355c9459d32588dad2cba8e829ec503bd78be..63c2e6d278b9afaa8559d53273b7965fb73da7d3 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 283dd74c6cfe6ef3938e68acbace9c194b7227bd..e7d1d5af009516d544dccef2cbcdfbfbb0f01c2f 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 5846927d6535e23693bae3cbe5aa52636e2e8e7b..e787d63734b5e25f7ef9df2d2450bc626bebbd86 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 03b114ba39bc1f2e077bc3cbfc850d281614a96e..b77b15e3f0ad1c9d3fbb64d6976fd9d7a35acf51 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 b3c08f38ae14d09640e31c667ac44c4b40fa8801..822ef948dd8c912b1119f178d693d6f16c4929e9 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 71a4285e9db12c95c580258c0ad4bb3849b6981b..3b970eefbec75cc99a582f0b91cfad0f24440e87 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 7a94a0c3eedfa570afca95353c4a9e1d574d20dc..365486a0aea6d6287f991e235df0343a996fb155 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;
-
-		> .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));
+<style lang="scss" module>
+.root {
+	display: flex;
+	flex-direction: column;
+}
 
-			> .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%);
-			}
+.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));
+}
 
-			> .instance {
-				position: relative;
-				display: block;
-				text-align: center;
-				width: 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%);
+}
 
-				> .icon {
-					display: inline-block;
-					width: 38px;
-					aspect-ratio: 1;
-				}
-			}
-		}
+.instance {
+	position: relative;
+	display: block;
+	text-align: center;
+	width: 100%;
+}
 
-		> .bottom {
-			position: sticky;
-			bottom: 0;
-			padding: 20px 0;
-			background: var(--X14);
-			-webkit-backdrop-filter: var(--blur, blur(8px));
-			backdrop-filter: var(--blur, blur(8px));
+.instanceIcon {
+	display: inline-block;
+	width: 38px;
+	aspect-ratio: 1;
+}
 
-			> .post {
-				position: relative;
-				display: block;
-				width: 100%;
-				height: 40px;
-				color: var(--fgOnAccent);
-				font-weight: bold;
-				text-align: left;
+.bottom {
+	position: sticky;
+	bottom: 0;
+	padding: 20px 0;
+	background: var(--X14);
+	-webkit-backdrop-filter: var(--blur, blur(8px));
+	backdrop-filter: var(--blur, blur(8px));
+}
 
-				&: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));
-				}
+.post {
+	position: relative;
+	display: block;
+	width: 100%;
+	height: 40px;
+	color: var(--fgOnAccent);
+	font-weight: bold;
+	text-align: left;
 
-				&:hover, &.active {
-					&:before {
-						background: var(--accentLighten);
-					}
-				}
+	&: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));
+	}
 
-				> .icon {
-					position: relative;
-					margin-left: 30px;
-					margin-right: 8px;
-					width: 32px;
-				}
+	&:hover, &.active {
+		&:before {
+			background: var(--accentLighten);
+		}
+	}
+}
 
-				> .text {
-					position: relative;
-				}
-			}
+.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;
+.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;
-				}
+.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;
-				}
-			}
-		}
+.acct {
+	display: block;
+	flex-shrink: 1;
+	padding-right: 8px;
+}
 
-		> .middle {
-			flex: 1;
+.middle {
+	flex: 1;
+}
 
-			> .divider {
-				margin: 16px 16px;
-				border-top: solid 0.5px var(--divider);
-			}
+.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);
+.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;
-				}
+	&:hover {
+		text-decoration: none;
+		color: var(--navHoverFg);
+	}
 
-				> .indicator {
-					position: absolute;
-					top: 0;
-					left: 20px;
-					color: var(--navIndicator);
-					font-size: 8px;
-					animation: blink 1s infinite;
-				}
+	&.active {
+		color: var(--navActive);
+	}
 
-				> .text {
-					position: relative;
-					font-size: 0.9em;
-				}
+	&: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 {
-					text-decoration: none;
-					color: var(--navHoverFg);
-				}
+.itemIcon {
+	position: relative;
+	width: 32px;
+	margin-right: 8px;
+}
 
-				&.active {
-					color: var(--navActive);
-				}
+.itemIndicator {
+	position: absolute;
+	top: 0;
+	left: 20px;
+	color: var(--navIndicator);
+	font-size: 8px;
+	animation: blink 1s infinite;
+}
 
-				&: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);
-					}
-				}
-			}
-		}
-	}
+.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 3b4b16142262fb1feca175cd27284486c349b043..a184f1d2f0076796dd6f3197dadaa2946cabfdb2 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;
+	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);
+	}
 
-	> .body {
-		position: fixed;
+	.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;
-		display: flex;
-		flex-direction: column;
+		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%);
 	}
 
-	&:not(.iconOnly) {
-		> .body {
-			width: $nav-width;
+	.instance {
+		position: relative;
+		display: block;
+		text-align: center;
+		width: 100%;
+	}
 
-			> .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%);
-				}
-
-				> .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));
+	}
+
+	.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;
+	}
 
-			> .bottom {
-				position: sticky;
+	.postText {
+		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;
+	}
+
+	.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;
+				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;
-					}
-				}
+				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;
+	}
+}
+
+.root.iconOnly {
+	flex: 0 0 var(--nav-icon-only-width);
+	width: var(--nav-icon-only-width);
+
+	.body {
+		width: var(--nav-icon-only-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));
+	}
+
+	.instance {
+		display: block;
+		text-align: center;
+		width: 100%;
+	}
+
+	.instanceIcon {
+		display: inline-block;
+		width: 30px;
+		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));
+	}
 
-			> .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);
-						}
-					}
-				}
+	.post {
+		display: block;
+		position: relative;
+		width: 100%;
+		height: 52px;
+		margin-bottom: 16px;
+		text-align: center;
+
+		&: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));
+		}
+
+		&:hover, &.active {
+			&:before {
+				background: var(--accentLighten);
 			}
 		}
 	}
 
-	&.iconOnly {
-		flex: 0 0 $nav-icon-only-width;
-		width: $nav-icon-only-width;
+	.postIcon {
+		position: relative;
+		color: var(--fgOnAccent);
+	}
 
-		> .body {
-			width: $nav-icon-only-width;
+	.postText {
+		display: none;
+	}
 
-			> .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));
-
-				> .instance {
-					display: block;
-					text-align: center;
-					width: 100%;
-
-					> .icon {
-						display: inline-block;
-						width: 30px;
-						aspect-ratio: 1;
-					}
-				}
-			}
+	.account {
+		display: block;
+		text-align: center;
+		width: 100%;
+	}
+
+	.avatar {
+		display: inline-block;
+		width: 38px;
+		aspect-ratio: 1;
+	}
+
+	.acct {
+		display: none;
+	}
 
-			> .bottom {
-				position: sticky;
+	.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;
-				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;
-
-					&: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));
-					}
-
-					&:hover, &.active {
-						&:before {
-							background: var(--accentLighten);
-						}
-					}
-
-					> .icon {
-						position: relative;
-						color: var(--fgOnAccent);
-					}
-
-					> .text {
-						display: none;
-					}
-				}
-
-				> .account {
-					display: block;
-					text-align: center;
-					width: 100%;
-
-					> .avatar {
-						display: inline-block;
-						width: 38px;
-						aspect-ratio: 1;
-					}
-
-					> .text {
-						display: none;
-					}
-				}
+				border-radius: 999px;
+				background: var(--accentedBg);
 			}
 
-			> .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;
-						}
-					}
-				}
+			> .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 fe95460ba4b894be41b61065a3c67e544d1c753d..6f2e4bc9a76ea91e1cb7870d165a383867f5e81a 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;
-		}
+	&.colored {
+		padding-right: 1em;
+		color: #fff;
+	}
+}
 
-		> .host {
-			vertical-align: bottom;
-		}
+.icon {
+	display: inline-block;
+	height: var(--height);
+	aspect-ratio: 1;
+	vertical-align: bottom;
+	margin-right: 1em;
+}
 
-		&.colored {
-			padding-right: 1em;
-			color: #fff;
-		}
-	}
+.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 44b6b278eac309855d069477652415521f005232..82473b609fe50e379ac5f92b6e60268837884b56 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 16df69d968de38d13e4833edadfa1a7549efe458..9ac744943d32b92ba9a9a19199411c4784a0aecb 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 f84695c15f8562ed804b752487476a47e1ac9dd5..3533972cdf94cc1bd0d25fbec2175146587efae7 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;
-
-		&.verySmall {
-			--nameMargin: 7px;
-			--height: 16px;
-			font-size: 0.75em;
-		}
+.item {
+	--height: 24px;
+	--nameMargin: 10px;
+	font-size: 0.85em;
 
-		&.small {
-			--nameMargin: 8px;
-			--height: 20px;
-			font-size: 0.8em;
-		}
+	&.verySmall {
+		--nameMargin: 7px;
+		--height: 16px;
+		font-size: 0.75em;
+	}
 
-		&.large {
-			--nameMargin: 12px;
-			--height: 26px;
-			font-size: 0.875em;
-		}
+	&.small {
+		--nameMargin: 8px;
+		--height: 20px;
+		font-size: 0.8em;
+	}
 
-		&.veryLarge {
-			--nameMargin: 14px;
-			--height: 30px;
-			font-size: 0.9em;
-		}
+	&.large {
+		--nameMargin: 12px;
+		--height: 26px;
+		font-size: 0.875em;
+	}
 
-		display: flex;
-		vertical-align: bottom;
-		width: 100%;
-		line-height: var(--height);
-		height: var(--height);
-		overflow: clip;
-		contain: strict;
+	&.veryLarge {
+		--nameMargin: 14px;
+		--height: 30px;
+		font-size: 0.9em;
+	}
 
-		> .name {
-			padding: 0 var(--nameMargin);
-			font-weight: bold;
-			color: var(--accent);
+	display: flex;
+	vertical-align: bottom;
+	width: 100%;
+	line-height: var(--height);
+	height: var(--height);
+	overflow: clip;
+	contain: strict;
 
-			&:empty {
-				display: none;
-			}
-		}
+	&.black {
+		background: #000;
+		color: #fff;
+	}
+}
 
-		> .body {
-			min-width: 0;
-			flex: 1;
-		}
+.name {
+	padding: 0 var(--nameMargin);
+	font-weight: bold;
+	color: var(--accent);
 
-		&.black {
-			background: #000;
-			color: #fff;
-		}
+	&: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 2a856e2a4542d2f9494b279605fc8e1be94538ad..74c475fc7de614631501d2df0b506c2b03256b5d 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 daea775552528478098a7b2a6ca16a8836999c7d..747d4edcb4495fe1dd5d310b76a3cc6ad0456aac 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 73db14c65e5929e9c75860a1cc9c8786860875a2..cb264fc3ba70e7c83ccc64c3cf7264ffdff5f338 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 792c1ccc5e44b668ad55e8a9b3bdf31bf56ad70c..d50f2b0454b8358ec577dbbd868e144cd00d4d7c 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 33e752513b9dd6d1c6aab11b2b207c85e4b71789..c82873177334c738439c3086abc7d5583206489c 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 76a8b6e760b98e42da80b1b513ad06361b73be4d..d21a9cc5809cc3fe0e8a78f7a1593232f3056567 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 9605d1b22ee6e029efb5d0f9c2302402210d1e87..8b05ecc0bbfb2d8887760daa724465277832f7d9 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 8e7addf3596aceb7935482ca4e6aa8ce9242520c..0000000000000000000000000000000000000000
--- 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 402bbe035260e6a2e6fded26c9300310a6e5167f..c376eb2b471d382f374b8cb54c0c0d2044f6f1e2 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 15b76c4d92bcf2f4f5bd7e9632c5f02959d0f6bb..dc3f58e6a4a65a877d51c1254d204258aa63bfc4 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 352c1d246a4a3e54b0de58f1d7efef8da41528a6..f36dc6151c56b43fa8de8fdc6e6baa4fc2c13e7e 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 f3826a8d31eb09b23cf70a6e482b2ff1409b8ed3..169fac70a2145154bf3f291c33068b907ed7ff0b 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 852d7a8f7e2f1f932585cfcf4a30374f700ac8c0..98cf898749150c5fc6fdd00fa777f0417261b2d6 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 9d133035fe5a3a73ba10b3b28e8d035731f27b8d..8cf6ec1f65823bf266dc81c933260331b67edf28 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 5783b3f071522cd5ae3b034b85a7fe0d7c4b0434..a0b7f1c67595ee01882e5b5c0c3c88926ccbaa2f 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 c23943d4db8ae39e4e624d3048ec3fe7eaae1482..4844ad11ff181ffdfd410b0a17321b14f77ba3e5 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 3b5b72799174bb5edc8a650d726b56a2db3f1d19..da14e54f749bdeb0b6a5dffeebc889602e6a27d4 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 0000000000000000000000000000000000000000..e656f00bb209fbf70b0e4eac4261c659c2f9baa7
--- /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 27d0c26ac4a1147ab338f54a8fd91e4a33fd9c1b..c0da59a57d9d9be90e80e8419f6565fe89e15127 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 3e0c38bb831bd624cd14402b676346c467cc4b45..ec5e8bb03fbcc28eed09cb6088c19937e8ed0a2a 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 623abbda39dbdea2a11f5990ace54fc6689a2e14..d6de145edb00356d19bd53bce901053c4d55fc7f 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 628390e3f7bd2750c9d144a63bcdc243595c8aa8..d516a5df758e5143e89e5c98f846083558c9d517 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 0000000000000000000000000000000000000000..c5544418db326acfe2d91ca74c495a4661652aec
--- /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 84f6af1c134986278daa8139cde1aa4a418bcf7c..110f1d32eb04539b358cdb58d84ce5069703856b 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 b61e419f9466a76f776924b6276e4922cd31ccb9..cc4df65dd2f1c9484f06dc790eb256812e40e0da 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 e7f8819abd18f0f591c6602896d9ca8dd8aa7f46..892b24f69d8fa0abee07963801b4667c230e88e3 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 37326ee981f3aa9b839be0bff1b0f4b4880e4ce2..797dd9c09ffff65014d18e0e3e0ca85e6825ba3f 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 947dbe5e77f573489d7c50b204a942b88d4d9531..d6c94cd56a6dcf0cddbb2d2253886153519b11a6 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 455a6e6ea56444f50e5261fb3a60e8013202a56e..3b67972e400f7769b68aa9c1d3caaab4e1c520cc 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 9eee9680dbf6dd1a3dcaabd7e4cc5f6aec1b286c..bcb380f849d155801cb8afca302be433cd41a1af 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 58d073226369e780872f434d810a74b2e6ff86dd..447525837c020bff219a0fec6a323fca3d6472cd 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 981788a3c53161f2a7889f6addc9df4fc8712eac..b7be2e8c83a6a9de9ec79dbe99cb81ab22ba5c1c 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 ebd73cb9f551e998d9d578c6a2ade6c25db2823a..aee5026db48ccfe1850ea031c9afc982d120bce8 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 cdd9c3a4016ebcd2429615c2ee741258b140d496..6148177d9a144e197a22e6b1399a836949804ccb 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 2033b074e021cc0c80bfa9f2af6e6abdc59cc1fe..951c4aaa6da92ba57b6206ac2bc799f5f8474885 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 b1578076559cbd8def3c84dc6606df7aa509d2e9..f8b811e6baddd02f2ec24c33449874a71047ea6e 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 d702fd2cb02ac6e5f9bc04101c9873ae7afb03fc..c77b98f8f469a884467e01bbc835ad626aefcac5 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 84043cf13fb24f9932e2669215c52a1df1bab4b8..3c8ffdb55a30798e663fd73a11529ad319c3c01e 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 959cf776ad9f4bd0ab91c68f243a4396301dbbc0..78d27a31b94d1ae8cec921b76ff0c23cf322591b 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 661f68b278eeacf72c797e5cdcc9b7624344d574..a24aa9b2e9a985a04ace759085e20196923efea3 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 44e073545d91e4e295b56496e9cff84a9da4a09c..c920c3ca5372055b0a4783db9fabd3ffdc470e97 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 716bbb42749139c475bd9996a8384c91db0276e2..5c6a8cbf83741b867e7e6adb9d39c746a7c3c336 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 7a96b00217415c17ade33bc8e25f35aecc1cb93e..bc63f028214fdb732800783a382d68619206bb6c 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 819663a3662e36a7fddd4bcd29a435dc88765841..72e229ef8f5cf18b732ab45e2a3c72c79f8c5789 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 18fa2e2c22b9d67127ad3419e653b291f2b9ec02..1be882c66dfac5a6573a288a1b79f65811bab0cc 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 b0408f0d7f76365a00c7564d2f5a40c5067ebaaf..6b346c05981eb7e58ce0f68b6e12d97ba42f8784 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 915e7aaaf436131c2cef470ea8545da6bfd76f7d..d4ede57926d162f0295425eab57ada68ab9607d2 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 71ee75f6cb6507aae29157db9b1f3fa859324b5c..3d497c2e237773126fc3e7647bee536571694b44 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 01450a7ab5bc3a4a4f26ece4782c925c95490f52..36f908d5ea280323879ad60b63a284e2869f20c5 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 22162d2b2c920b139cb9a4632ee8082560a86f90..f1af71adda49010471a06940dcaa5f75481032ce 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 b8811d2fed4202ed57fd012def800e5e6776ea12..4380fdb62fd7d663052402f8ab82f51efbb750ab 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 357d0ab78b3757e5d11d8c068a41c12fa07ace51..e019ff540b8ecf4912c1ac19ef478ac8b32d6e1e 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 868dbc0484307dea66ba11c86cfc540619d0d9a6..398815a6ae3ac8e9e34d75b68840907748ab1453 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 0000000000000000000000000000000000000000..5f2168a44a30fbc711e569acc571b184c99f0b03
--- /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 0000000000000000000000000000000000000000..4769524d9c81184b73de71e14b6891d20417fbe2
--- /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 0000000000000000000000000000000000000000..8ee893046590f2c9aba8e4cbb8a51a722bdfa6b6
--- /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 fad0dd0177a48088b8ec1562cd8afe636a751edb..531dd0b488c516c4b9802deac8d9e2758d411856 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 7c979a93dcd3b4988fdffb78540e942fe24dd4ff..a53ad178943dcc6b90b17a71454c626b7db499ed 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 74a7e89857cb9c0e2313e7fed132363677b3400b..f48fac3f479cd362866b09b72ee01b5d7eae2885 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==}
+  /@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.8:
-    resolution: {integrity: sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA==}
+  /@babel/parser@7.21.9:
+    resolution: {integrity: sha512-q5PNg/Bi1OpGgx5jYlvWZwAorZepEudDMCLtj967aeS7WMont7dUZI46M2XwcIQqvUlMxWfdLFu4S/qSxeUu5g==}
     engines: {node: '>=6.0.0'}
     hasBin: true
     dependencies:
-      '@babel/types': 7.21.4
+      '@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,87 +3275,270 @@ 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'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.21.3
+      '@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'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.21.3
+      '@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'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.21.3
+      '@babel/helper-annotate-as-pure': 7.18.6
+      '@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
+      '@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-computed-properties@7.20.7(@babel/core@7.21.3):
+    resolution: {integrity: sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.21.3
+      '@babel/helper-plugin-utils': 7.20.2
+      '@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):
+    resolution: {integrity: sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.21.3
+      '@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'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.21.3
+      '@babel/helper-create-regexp-features-plugin': 7.21.0(@babel/core@7.21.3)
+      '@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'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.21.3
+      '@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'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.21.3
+      '@babel/helper-builder-binary-assignment-operator-visitor': 7.18.9
       '@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-block-scoped-functions@7.18.6(@babel/core@7.21.3):
-    resolution: {integrity: sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==}
+  /@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.21.3
+      '@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-block-scoping@7.21.0(@babel/core@7.21.3):
-    resolution: {integrity: sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ==}
+  /@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.22.1)
     dev: true
 
-  /@babel/plugin-transform-classes@7.21.0(@babel/core@7.21.3):
-    resolution: {integrity: sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ==}
+  /@babel/plugin-transform-for-of@7.21.0(@babel/core@7.21.3):
+    resolution: {integrity: sha512-LlUYlydgDkKpIY7mcBWvyPPmMcOphEyYA27Ef4xpbh1IiDNLr0kZsos2nf92vz3IccvJI25QUwp86Eo5s6HmBQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     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-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-computed-properties@7.20.7(@babel/core@7.21.3):
-    resolution: {integrity: sha512-Lz7MvBK6DTjElHAmfu6bfANzKcxpyNPeYBGEafyA6E5HtRpjpZwU+u7Qrgz/2OR0z+5TvKYbPdphfSaAcZBrYQ==}
+  /@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.21.3
+      '@babel/core': 7.22.1
       '@babel/helper-plugin-utils': 7.20.2
-      '@babel/template': 7.20.7
     dev: true
 
-  /@babel/plugin-transform-destructuring@7.21.3(@babel/core@7.21.3):
-    resolution: {integrity: sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA==}
+  /@babel/plugin-transform-function-name@7.18.9(@babel/core@7.21.3):
+    resolution: {integrity: sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
       '@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-dotall-regex@7.18.6(@babel/core@7.21.3):
-    resolution: {integrity: sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==}
+  /@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.21.3
-      '@babel/helper-create-regexp-features-plugin': 7.21.0(@babel/core@7.21.3)
+      '@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
 
-  /@babel/plugin-transform-duplicate-keys@7.18.9(@babel/core@7.21.3):
-    resolution: {integrity: sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==}
+  /@babel/plugin-transform-literals@7.18.9(@babel/core@7.21.3):
+    resolution: {integrity: sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
@@ -2878,106 +3547,114 @@ packages:
       '@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==}
+  /@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.21.3
-      '@babel/helper-builder-binary-assignment-operator-visitor': 7.18.9
+      '@babel/core': 7.22.1
       '@babel/helper-plugin-utils': 7.20.2
     dev: true
 
-  /@babel/plugin-transform-flow-strip-types@7.21.0(@babel/core@7.21.3):
-    resolution: {integrity: sha512-FlFA2Mj87a6sDkW4gfGrQQqwY/dLlBAyJa2dJEZ+FHXUVHBflO2wyKvg+OOEzXfrKYIa4HWl0mgmbCzt0cMb7w==}
+  /@babel/plugin-transform-member-expression-literals@7.18.6(@babel/core@7.21.3):
+    resolution: {integrity: sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
       '@babel/core': 7.21.3
       '@babel/helper-plugin-utils': 7.20.2
-      '@babel/plugin-syntax-flow': 7.18.6(@babel/core@7.21.3)
     dev: true
 
-  /@babel/plugin-transform-for-of@7.21.0(@babel/core@7.21.3):
-    resolution: {integrity: sha512-LlUYlydgDkKpIY7mcBWvyPPmMcOphEyYA27Ef4xpbh1IiDNLr0kZsos2nf92vz3IccvJI25QUwp86Eo5s6HmBQ==}
+  /@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.21.3
+      '@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==}
+  /@babel/plugin-transform-modules-amd@7.20.11(@babel/core@7.21.3):
+    resolution: {integrity: sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@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-function-name': 7.21.0
+      '@babel/helper-module-transforms': 7.22.1
       '@babel/helper-plugin-utils': 7.20.2
+    transitivePeerDependencies:
+      - supports-color
     dev: true
 
-  /@babel/plugin-transform-literals@7.18.9(@babel/core@7.21.3):
-    resolution: {integrity: sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==}
+  /@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.21.3
+      '@babel/core': 7.22.1
+      '@babel/helper-module-transforms': 7.22.1
       '@babel/helper-plugin-utils': 7.20.2
+    transitivePeerDependencies:
+      - supports-color
     dev: true
 
-  /@babel/plugin-transform-member-expression-literals@7.18.6(@babel/core@7.21.3):
-    resolution: {integrity: sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==}
+  /@babel/plugin-transform-modules-commonjs@7.21.2(@babel/core@7.21.3):
+    resolution: {integrity: sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
       '@babel/core': 7.21.3
+      '@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
 
-  /@babel/plugin-transform-modules-amd@7.20.11(@babel/core@7.21.3):
-    resolution: {integrity: sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==}
+  /@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.21.3
-      '@babel/helper-module-transforms': 7.21.2
+      '@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
 
-  /@babel/plugin-transform-modules-commonjs@7.21.2(@babel/core@7.21.3):
-    resolution: {integrity: sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA==}
+  /@babel/plugin-transform-modules-systemjs@7.20.11(@babel/core@7.21.3):
+    resolution: {integrity: sha512-vVu5g9BPQKSFEmvt2TA4Da5N+QVS66EX21d8uoOihC+OCpUoGvzVsXeqFdtAEfVa5BILAeFt+U7yVmLbQnAJmw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
       '@babel/core': 7.21.3
-      '@babel/helper-module-transforms': 7.21.2
+      '@babel/helper-hoist-variables': 7.18.6
+      '@babel/helper-module-transforms': 7.22.1
       '@babel/helper-plugin-utils': 7.20.2
-      '@babel/helper-simple-access': 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.21.3):
+  /@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.21.3
+      '@babel/core': 7.22.1
       '@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:
@@ -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-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.21.3):
+  /@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.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
 
-  /@babel/traverse@7.21.3:
-    resolution: {integrity: sha512-XLyopNeaTancVitYZe2MlUEvgKb6YVVPXzofHgqHijCImG33b/uTurMS488ht/Hbsb2XK3U2BnSTxKVNGV3nGQ==}
+  /@babel/traverse@7.22.4:
+    resolution: {integrity: sha512-Tn1pDsjIcI+JcLKq1AVlZEr4226gpuAQTsLMorsYg9tuS/kG7nuwwJ4AB8jfQuEgb/COBwR/DqJxmoiYFu5/rQ==}
     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
     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,13 +8244,59 @@ packages:
       - 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/typescript-estree@5.59.5(typescript@5.0.4):
-    resolution: {integrity: sha512-+XXdLN2CZLZcD/mO7mQtJMvCkzRfmODbeSKuMY/yXbGkzvA9rJyDY5qDYNoiz2kP/dmyAxXquL2BvLQLJFPQIg==}
+  /@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}
+    peerDependencies:
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@typescript-eslint/types': 5.59.5
+      '@typescript-eslint/visitor-keys': 5.59.5
+      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.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: '*'
@@ -7131,14 +8304,14 @@ packages:
       typescript:
         optional: true
     dependencies:
-      '@typescript-eslint/types': 5.59.5
-      '@typescript-eslint/visitor-keys': 5.59.5
+      '@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.0
-      tsutils: 3.21.0(typescript@5.0.4)
-      typescript: 5.0.4
+      semver: 7.5.1
+      tsutils: 3.21.0(typescript@5.1.3)
+      typescript: 5.1.3
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -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,58 +11820,140 @@ 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
     dev: true
 
-  /eslint-rule-docs@1.1.235:
-    resolution: {integrity: sha512-+TQ+x4JdTnDoFEXXb3fDvfGOwnyNV7duH8fXWTPD1ieaBmB8omj7Gw/pMBBu4uI2uJCCU8APDaQJzWuXnTsH4A==}
-    dev: true
-
-  /eslint-scope@5.1.1:
-    resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
-    engines: {node: '>=8.0.0'}
-    dependencies:
-      esrecurse: 4.3.0
-      estraverse: 4.3.0
-    dev: true
-
-  /eslint-scope@7.2.0:
-    resolution: {integrity: sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
-    dependencies:
-      esrecurse: 4.3.0
-      estraverse: 5.3.0
-    dev: true
-
-  /eslint-visitor-keys@3.4.1:
-    resolution: {integrity: sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==}
-    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
-    dev: true
-
-  /eslint@8.40.0:
-    resolution: {integrity: sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ==}
+  /eslint-rule-docs@1.1.235:
+    resolution: {integrity: sha512-+TQ+x4JdTnDoFEXXb3fDvfGOwnyNV7duH8fXWTPD1ieaBmB8omj7Gw/pMBBu4uI2uJCCU8APDaQJzWuXnTsH4A==}
+    dev: true
+
+  /eslint-scope@5.1.1:
+    resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
+    engines: {node: '>=8.0.0'}
+    dependencies:
+      esrecurse: 4.3.0
+      estraverse: 4.3.0
+    dev: true
+
+  /eslint-scope@7.2.0:
+    resolution: {integrity: sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    dependencies:
+      esrecurse: 4.3.0
+      estraverse: 5.3.0
+    dev: true
+
+  /eslint-visitor-keys@3.4.1:
+    resolution: {integrity: sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    dev: true
+
+  /eslint@8.40.0:
+    resolution: {integrity: sha512-bvR+TsP9EHL3TqNtj9sCNJVAFK3fBN8Q7g5waghxyRsPLIMwL73XSKnZFK0hk/O2ANC+iAoq6PWMQ+IfBAJIiQ==}
+    engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+    hasBin: true
+    dependencies:
+      '@eslint-community/eslint-utils': 4.4.0(eslint@8.40.0)
+      '@eslint-community/regexpp': 4.5.0
+      '@eslint/eslintrc': 2.0.3
+      '@eslint/js': 8.40.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
+      grapheme-splitter: 1.0.4
+      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-sdsl: 4.2.0
+      js-yaml: 4.1.0
+      json-stable-stringify-without-jsonify: 1.0.1
+      levn: 0.4.1
+      lodash.merge: 4.6.2
+      minimatch: 3.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
+
+  /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.40.0)
+      '@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.40.0
+      '@eslint/js': 8.41.0
       '@humanwhocodes/config-array': 0.11.8
       '@humanwhocodes/module-importer': 1.0.1
       '@nodelib/fs.walk': 1.2.8
@@ -10622,13 +11973,12 @@ packages:
       find-up: 5.0.0
       glob-parent: 6.0.2
       globals: 13.19.0
-      grapheme-splitter: 1.0.4
+      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-sdsl: 4.2.0
       js-yaml: 4.1.0
       json-stable-stringify-without-jsonify: 1.0.1
       levn: 0.4.1
@@ -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-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.10.0:
-    resolution: {integrity: sha512-ke7o7qSTMb47iwzOSaZMfeR7xToFdkE71ifIipOAAaLIM0DYzfOAXlgFFmYUIE2BcJtvnVlGCID84ZzCegE8CQ==}
+  /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==}
-    dependencies:
-      '@vue/compiler-sfc': 2.7.14
-      csstype: 3.1.1
-    dev: false
-
-  /vue@3.3.1:
-    resolution: {integrity: sha512-3Rwy4I5idbPVSDZu6I+fFh6tdDSZbauImCTqLxE7y0LpHtiDvPeY01OI7RkFPbva1nk4hoO0sv/NzosH2h60sg==}
+  /vue@3.3.4:
+    resolution: {integrity: sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==}
     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
+      '@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
 
-  /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 db7bc11febd14cb336053e283b2cb7dddbc0d6e0..2f20d8f07c6415ca9193cc1e17a90119885366fc 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();