diff --git a/CHANGELOG.md b/CHANGELOG.md
index e15c6d646..e363abe2b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,18 @@
 
 -->
 
+## 12.101.0 (2021/12/29)
+
+### Improvements
+- クライアント: ノートプレビューの精度を改善
+- クライアント: MFM sparkleエフェクトの改善
+- クライアント: デザインの調整
+- セキュリティの向上
+
+### Bugfixes
+- クライアント: 一部のコンポーネントが裏に隠れるのを修正
+- fix html blockquote conversion
+
 ## 12.100.2 (2021/12/18)
 
 ### Bugfixes
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index fe3df853b..633995c94 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -87,7 +87,7 @@ Configuration files are located in [`/.github/workflows`](/.github/workflows).
 
 ## Vue
 Misskey uses Vue(v3) as its front-end framework.
-**When creating a new component, please use the Composition API instead of the Options API.**
+**When creating a new component, please use the Composition API (and [setup sugar](https://v3.vuejs.org/api/sfc-script-setup.html)) instead of the Options API.**
 Some of the existing components are implemented in the Options API, but it is an old implementation. Refactors that migrate those components to the Composition API are also welcome.
 
 ## Adding MisskeyRoom items
diff --git a/cypress/support/index.js b/cypress/support/index.js
index d68db96df..a9ac34476 100644
--- a/cypress/support/index.js
+++ b/cypress/support/index.js
@@ -18,3 +18,9 @@ import './commands'
 
 // Alternatively you can use CommonJS syntax:
 // require('./commands')
+
+Cypress.on('uncaught:exception', (err, runnable) => {
+  if (err.message.includes('ResizeObserver loop limit exceeded')) {
+    return false
+  }
+});
diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml
index 95592d4f3..8942f9a55 100644
--- a/locales/ar-SA.yml
+++ b/locales/ar-SA.yml
@@ -580,7 +580,6 @@ regenerateLoginToken: "أعد توليد الرمز"
 regenerateLoginTokenDescription: "ينشئ رمز استيثاق جديد في العادة هذا ليس ضروريًا ؛ عند إنشاء رمز جديد ستُخرج جميع الأجهزة."
 setMultipleBySeparatingWithSpace: "يمكنك ادخال أكثر من مدخل واحد وذلك بفصلها بمسافات."
 fileIdOrUrl: "معرف الملف أو رابط"
-chatOpenBehavior: "سلوك نفاذة المحادثة عند فتحها"
 behavior: "السلوك"
 sample: "مثال"
 abuseReports: "البلاغات"
diff --git a/locales/de-DE.yml b/locales/de-DE.yml
index 1e655f2a1..240a67778 100644
--- a/locales/de-DE.yml
+++ b/locales/de-DE.yml
@@ -448,6 +448,7 @@ uiLanguage: "Sprache der Benutzeroberfläche"
 groupInvited: "Du wurdest in eine Gruppe eingeladen"
 aboutX: "Über {x}"
 useOsNativeEmojis: "Eingebaute Emojis des Betriebssystems benutzen"
+disableDrawer: "Keine ausfahrbaren Menüs verwenden"
 youHaveNoGroups: "Keine Gruppen vorhanden"
 joinOrCreateGroup: "Lass dich zu einer Gruppe einladen oder erstelle deine eigene."
 noHistory: "Kein Verlauf"
@@ -613,7 +614,6 @@ regenerateLoginToken: "Anmeldungstoken regenerieren"
 regenerateLoginTokenDescription: "Den zur Anmeldung intern verwendeten Token regenerieren. Normalerweise wird dies nicht benötigt. Bei Regeneration werden alle Geräte ausgeloggt."
 setMultipleBySeparatingWithSpace: "Trenne Elemente durch ein Leerzeichen um mehrere Einstellungen zu kofigurieren."
 fileIdOrUrl: "Datei-ID oder URL"
-chatOpenBehavior: "Verhalten des Chatfensters bei Öffnung"
 behavior: "Verhalten"
 sample: "Beispiel"
 abuseReports: "Meldungen"
@@ -818,6 +818,7 @@ leaveGroup: "Gruppe verlassen"
 leaveGroupConfirm: "Möchtest du \"{name}\" wirklich verlassen?"
 useDrawerReactionPickerForMobile: "Auf mobilen Geräten ausfahrbare Reaktionsauswahl anzeigen"
 welcomeBackWithName: "Willkommen zurück, {name}"
+clickToFinishEmailVerification: "Drücke bitte auf [{ok}], um die Email-Bestätigung abzuschließen."
 _emailUnavailable:
   used: "Diese Email-Adresse wird bereits verwendet"
   format: "Das Format dieser Email-Adresse ist ungültig"
diff --git a/locales/en-US.yml b/locales/en-US.yml
index 8ae1671cf..9286e78cc 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -448,6 +448,7 @@ uiLanguage: "User interface language"
 groupInvited: "You've been invited to a group"
 aboutX: "About {x}"
 useOsNativeEmojis: "Use OS native Emoji"
+disableDrawer: "Don't use drawer-style menus"
 youHaveNoGroups: "You have no groups"
 joinOrCreateGroup: "Get invited to a group or create your own."
 noHistory: "No history available"
@@ -613,7 +614,6 @@ regenerateLoginToken: "Regenerate login token"
 regenerateLoginTokenDescription: "Regenerate the token used internally during login. Normally this action is not necessary. If regenerated, all devices will be logged out."
 setMultipleBySeparatingWithSpace: "Separate multiple entries with spaces."
 fileIdOrUrl: "File-ID or URL"
-chatOpenBehavior: "Behavior of the chat window when opened"
 behavior: "Behavior"
 sample: "Sample"
 abuseReports: "Reports"
@@ -817,6 +817,7 @@ leaveGroup: "Leave Group"
 leaveGroupConfirm: "Are you sure you want to leave \"{name}\"?"
 useDrawerReactionPickerForMobile: "Display reaction picker as drawer on mobile"
 welcomeBackWithName: "Welcome back, {name}"
+clickToFinishEmailVerification: "Please click [{ok}] to complete email verification."
 _emailUnavailable:
   used: "This email address is already being used"
   format: "The format of this email address is invalid"
diff --git a/locales/eo-UY.yml b/locales/eo-UY.yml
index 38e07ba1f..15a8440a2 100644
--- a/locales/eo-UY.yml
+++ b/locales/eo-UY.yml
@@ -23,7 +23,7 @@ otherSettings: "Aliaj agordoj"
 openInWindow: "Malfermi en nova fenestro"
 profile: "Profilo"
 timeline: "Templinio"
-noAccountDescription: "Neniu priskribo"
+noAccountDescription: "La uzanto ankoraŭ ne skribis la sinprezenton en sia profilo."
 login: "Saluti"
 loggingIn: "Salutado…"
 logout: "Adiaŭi"
@@ -101,7 +101,8 @@ clickToShow: "Klaku por malkaŝu"
 sensitive: "Enhavo ne estas deca por laborejo (NSFW)"
 add: "Aldoni"
 reaction: "Reagoj"
-rememberNoteVisibility: "Rememori la agordon de videbleco de la laste sendita"
+reactionSetting: "Reagoj aperontaj en la elektilo de reagoj"
+rememberNoteVisibility: "Rememori la agordon de videbleco de la lasta afiŝado"
 attachCancel: "Deigi aldonaĵon"
 markAsSensitive: "Troviĝi NSFW"
 unmarkAsSensitive: "Ne troviĝi NSFW"
@@ -121,6 +122,7 @@ selectAntenna: "Elekti antenon"
 selectWidget: "Elekti enestraĵon"
 editWidgets: "Redakti fenestraĵon"
 editWidgetsExit: "Fini la redaktadon"
+customEmojis: "Federaj emoĵioj"
 emoji: "Emoĵio"
 emojis: "Emoĵio"
 emojiName: "Nomo de la emoĵio"
@@ -128,11 +130,12 @@ emojiUrl: "URL de la emoĵio"
 addEmoji: "Aldoni emoĵion"
 settingGuide: "Agordaj rekomendoj"
 cacheRemoteFiles: "Stapli forajn dosierojn"
-flagAsBot: "Marki kiel uzata de roboto"
-flagAsCat: "Agordi kiel kat-iĝa"
-flagAsCatDescription: "Se vi estas kato, ebligu la agordon."
+flagAsBot: "Marki kiel esti uzanto de roboto"
+flagAsCat: "Marki kiel esti kato"
+flagAsCatDescription: "Se vi estas kato, faru ĉi tiun flagon"
 autoAcceptFollowed: "Aŭtomate akcepti la peton de sekvado far uzantoj kiujn vi sekvas"
 addAccount: "Aldoni konton"
+loginFailed: "Saluto malsukcesis"
 showOnRemote: "Vidi ĉe la surloka nodo"
 general: "Ĝenerala"
 wallpaper: "Ekranfonoj"
@@ -148,6 +151,7 @@ recipient: "Ricevonton"
 annotation: "Komentarioj"
 federation: "Federaĵo"
 instances: "Nodoj"
+registeredAt: "Registrita je"
 latestRequestSentAt: "La laste sendita peto"
 latestRequestReceivedAt: "La laste ricevita peto "
 latestStatus: "Laŭstato"
@@ -319,8 +323,11 @@ antennas: "Antenoj"
 manageAntennas: "Administri antenojn"
 name: "Nomo"
 antennaSource: "Fonto de la anteno"
+antennaKeywords: "Ricevi per ĉefterminoj"
+antennaExcludeKeywords: "Krom ĉefterminoj"
 notifyAntenna: "Oni sciigos novajn notojn"
 withFileAntenna: "Nur kun aldonaĵo"
+enableServiceworker: "Aktivigi ServiceWorker"
 withReplies: "Inkluzive respondoj"
 connectedTo: "Sekva konto estas konektita"
 notesAndReplies: "Kun respondoj"
@@ -452,9 +459,9 @@ nothing: "Neniu"
 installedDate: "Dato de instalado"
 lastUsedDate: "Lastfoje uzita je"
 state: "Stato"
-sort: "Ordigado"
-ascendingOrder: "Kreski"
-descendingOrder: "Malkreski"
+sort: "Ordigi laŭ"
+ascendingOrder: "Kreska ordo"
+descendingOrder: "Malkreska ordo"
 scratchpad: "Malneta redaktilo"
 output: "Elmeto"
 script: "Skripto"
@@ -506,7 +513,7 @@ smtpPort: "Pordo"
 smtpUser: "Uzantnomo"
 smtpPass: "Pasvorto"
 wordMute: "Silentigi specifajn vortojn"
-userSaysSomething: "{name} parolis ion"
+userSaysSomething: "{name} diras ion"
 makeActive: "Aktivigi"
 display: "Vidi"
 copy: "Kopii"
@@ -522,7 +529,6 @@ useGlobalSetting: "Oni uzas malloka agordo"
 other: "Aliaj"
 regenerateLoginToken: "Regeneri la aŭtentikigan pecon"
 fileIdOrUrl: "Dosiera identigilo aŭ URL"
-chatOpenBehavior: "Konduto por malfermi la fenestron de babilejo"
 behavior: "Konduto"
 sample: "Ekzemplo"
 abuseReports: "Signaloj"
@@ -569,6 +575,7 @@ left: "Maldekstra"
 center: "Centra"
 wide: "Vasta"
 narrow: "Malvasta"
+needReloadToApply: "Tiu agordo estos aplikita nur poste reŝargi."
 showTitlebar: "Videbligi titolan stangon"
 clearCache: "Malplenigi staplon"
 onlineUsersCount: "{n} uzantoj estas surlineaj"
@@ -633,12 +640,15 @@ unread: "Nelegita"
 controlPanel: "Ŝaltpodio"
 manageAccounts: "Bonteni la kontojn"
 classic: "Klasika"
-ffVisibility: "Videbleco pri viaj sekvataro/sekvantaro\n"
-ffVisibilityDescription: "Agordi la videblecon kiu povas vidi tiujn kiujn vi sekvas kaj tiujn kiuj sekvas vin."
-continueThread: "Vidi pli mesaĝarojn"
+muteThread: "Silentigi la mesaĝaron"
+unmuteThread: "Malsilentigi la mesaĝaron"
+ffVisibility: "Videbleco de la sekvadoj pri vi"
+ffVisibilityDescription: "Tie ĉi vi povas agordi la videblecon pri kiuj povas vidi tiujn, kiujn vi sekvas kaj kiuj sekvas vin."
+continueThread: "Pli vidi la mesaĝaron"
 incorrectPassword: "Nevalida pasvorto"
 leaveGroup: "Eliĝi el la grupo"
 leaveGroupConfirm: "Ĉu vi certas ke vi volas eliĝi el la grupo {name}?"
+welcomeBackWithName: "Alvenbenon! {name}"
 _emailUnavailable:
   used: "La retpoŝto jam estas uzita."
   format: "Nevalida formato."
@@ -646,7 +656,7 @@ _emailUnavailable:
   smtp: "Tiu retpoŝta servilo ne respondas"
 _ffVisibility:
   public: "Publika"
-  followers: "Afiŝi nur al sekvantoj"
+  followers: "Nur al sekvantoj"
   private: "Malpublikigita"
 _signup:
   emailAddressInfo: "Entajpu vian retpoŝton"
@@ -693,6 +703,7 @@ _mfm:
   inlineMath: "Formulo (en linio)"
   blockMath: "Formulo (bloko)"
   quote: "Citi"
+  emoji: "Federaj emoĵioj"
   search: "Serĉi"
   flip: "Inversa"
   x2: "Granda"
@@ -717,6 +728,8 @@ _channel:
   following: "Sekvante"
   usersCount: "{n} partoprenantoj"
 _menuDisplay:
+  sideFull: "Flanko"
+  sideIcon: "Flanko (bildsimbolo)"
   top: "Supro"
   hide: "Kaŝi"
 _wordMute:
@@ -730,6 +743,7 @@ _theme:
   description: "Priskribo"
   defaultValue: "Implicitaĵa valoro"
   color: "Koloro"
+  func: "Funkcio"
   darken: "Malbrileco"
   lighten: "Brileco"
   keys:
@@ -782,8 +796,8 @@ _permissions:
   "write:drive": "Ĉia operacio por skribi, forviŝi, aŭ alimaniere ŝanĝi la informon de dosiero en via disko de Misskey"
   "read:favorites": "Vidi vian liston de preferaĵoj"
   "write:favorites": "Redakti vian liston de preferaĵoj"
-  "read:following": "Vidi la infomaciojn pri tio, kion vi sekvas"
-  "write:following": "Sekvi aŭ malsekvi alian uzanton"
+  "read:following": "Vidi la infomojn pri la sekvadoj pri vi"
+  "write:following": "Sekvi/Malsekvi alian uzanton"
   "read:messaging": "Vidi viajn retbabiladojn"
   "write:messaging": "Administri viajn retbabiladojn"
   "read:mutes": "Vidi vian liston de silentigitoj"
@@ -798,7 +812,7 @@ _permissions:
   "read:channels": "Vidi kanalojn"
 _antennaSources:
   all: "Ĉiuj notoj"
-  homeTimeline: "Notoj far uzantoj kiujn vi sekvas"
+  homeTimeline: "Notoj de uzantoj kiujn vi sekvas"
 _weekday:
   sunday: "Dimanĉo"
   monday: "Lundo"
@@ -830,19 +844,25 @@ _poll:
   closed: "Oni jam balotis ĝin"
 _visibility:
   public: "Publika"
-  publicDescription: "Afiŝi al ĉiuj en la Fediverso"
+  publicDescription: "Publikigi al ĉiuj en la Fediverso"
   home: "Hejma"
   homeDescription: "Dissendi nur sur hejma templinio"
   followers: "Nur al sekvantoj"
-  followersDescription: "Afiŝi nur al sekvantoj"
-  specified: "Rekte"
-  specifiedDescription: "Afiŝi nur al specifaj uzantoj"
+  followersDescription: "Videbligi nur al sekvantoj"
+  specified: "Rekte montrita"
+  specifiedDescription: "Montri nur al specifaj uzantoj"
   localOnly: "Nur loka"
-  localOnlyDescription: "Ne afiŝi al foraj uzantoj"
+  localOnlyDescription: "Ne videbligi al foraj uzantoj"
 _postForm:
   replyPlaceholder: "Respondi la noton…"
   quotePlaceholder: "Citi la noton…"
   channelPlaceholder: "Mencii en la kanalo…"
+  _placeholders:
+    a: "Kiel vi fartas?"
+    b: "Kio okazis ĉirkaŭ vi?"
+    c: "Kion vi pensas?"
+    d: "Kion vi parolos?"
+    e: "Komencu skribi…"
 _profile:
   name: "Nomo"
   username: "Uzantnomo"
@@ -951,6 +971,9 @@ _pages:
       default: "Implicitaĵa valoro"
   script:
     categories:
+      random: "Hazardo"
+      value: "Valoro"
+      fn: "Funkcio"
       text: "Manipulo de teksto"
       list: "Listoj"
     blocks:
@@ -968,6 +991,7 @@ _pages:
       _join:
         arg1: "Listoj"
         arg2: "apartigilo"
+      random: "Hazardo"
       _randomPick:
         arg1: "Listoj"
       _dailyRandomPick:
@@ -985,6 +1009,7 @@ _pages:
         arg1: "Teksto"
       _splitStrByLine:
         arg1: "Teksto"
+      fn: "Funkcio"
       _fn:
         slots: "Juntoj"
         arg1: "Elmeto"
@@ -1008,7 +1033,7 @@ _notification:
   youGotPoll: "{name} balotis"
   youGotMessagingMessageFromUser: "{name} sendis al vi mesaĝon"
   youGotMessagingMessageFromGroup: "Oni sendis al la grupo {name} mesaĝon"
-  youWereFollowed: "eksekvis vin"
+  youWereFollowed: "Eksekvis vin"
   youReceivedFollowRequest: "Vi ricevis peton de sekvado"
   yourFollowRequestAccepted: "Via peto de sekvado estis akceptita."
   youWereInvitedToGroup: "Invitita al grupo"
@@ -1021,7 +1046,7 @@ _notification:
     quote: "Citi"
     reaction: "Reagoj"
     receiveFollowRequest: "Ricevi peton de sekvado"
-    followRequestAccepted: "Akceptita peto por sekvado"
+    followRequestAccepted: "Akceptita peto de sekvado"
     groupInvited: "Invitita al grupo"
 _deck:
   profile: "Agordaro"
diff --git a/locales/es-ES.yml b/locales/es-ES.yml
index 84b24603f..a8b2f5b72 100644
--- a/locales/es-ES.yml
+++ b/locales/es-ES.yml
@@ -609,7 +609,6 @@ regenerateLoginToken: "Regenerar token de login"
 regenerateLoginTokenDescription: "Regenerar el token usado internamente durante el login. No siempre es necesario hacerlo. Al hacerlo de nuevo, se deslogueará en todos los dispositivos."
 setMultipleBySeparatingWithSpace: "Puedes añadir mas de uno, separado por espacios."
 fileIdOrUrl: "Id del archivo o URL"
-chatOpenBehavior: "Comportamiento al abrir el chat"
 behavior: "Comportamiento"
 sample: "Muestra"
 abuseReports: "Reportes"
diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml
index fd89ecb17..d7e436492 100644
--- a/locales/fr-FR.yml
+++ b/locales/fr-FR.yml
@@ -611,7 +611,6 @@ regenerateLoginToken: "Régénérer le jeton de connexion"
 regenerateLoginTokenDescription: "Générer un nouveau jeton d'authentification. Cette opération ne devrait pas être nécessaire ; lors de la génération d'un nouveau jeton, tous les appareils seront déconnectés. "
 setMultipleBySeparatingWithSpace: "Vous pouvez en définir plusieurs, en les séparant par des espaces."
 fileIdOrUrl: "ID du fichier ou URL"
-chatOpenBehavior: "Comportement de la fenêtre de discussion lors de son ouverture"
 behavior: "Comportement"
 sample: "Exemple"
 abuseReports: "Signalements"
diff --git a/locales/id-ID.yml b/locales/id-ID.yml
index e083c27b2..f4f0ec8ce 100644
--- a/locales/id-ID.yml
+++ b/locales/id-ID.yml
@@ -611,7 +611,6 @@ regenerateLoginToken: "Perbarui token login"
 regenerateLoginTokenDescription: "Perbarui token yang digunakan secara internal saat login. Normalnya aksi ini tidak diperlukan. Jika diperbarui, semua perangkat akan dilogout."
 setMultipleBySeparatingWithSpace: "Kamu dapat menyetel banyak dengan memisahkannya menggunakan spasi."
 fileIdOrUrl: "File-ID atau URL"
-chatOpenBehavior: "Perilaku jendelan obrolan ketika dibuka"
 behavior: "Perilaku"
 sample: "Contoh"
 abuseReports: "Laporkan"
diff --git a/locales/it-IT.yml b/locales/it-IT.yml
index 236568e2f..ff5b01a58 100644
--- a/locales/it-IT.yml
+++ b/locales/it-IT.yml
@@ -590,7 +590,6 @@ other: "Avanzate"
 regenerateLoginToken: "Genera di nuovo un token di connessione"
 regenerateLoginTokenDescription: "Genera un nuovo token di autenticazione. Solitamente questa operazione non è necessaria: quando si genera un nuovo token, tutti i dispositivi vanno disconnessi."
 fileIdOrUrl: "ID o URL del file"
-chatOpenBehavior: "Comportamento della finestra di chat quando viene aperta"
 behavior: "Comportamento"
 abuseReports: "Segnalazioni"
 reportAbuse: "Segnalazioni"
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 72c20819e..62aade568 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -448,6 +448,7 @@ uiLanguage: "UIの表示言語"
 groupInvited: "グループに招待されました"
 aboutX: "{x}について"
 useOsNativeEmojis: "OSネイティブの絵文字を使用"
+disableDrawer: "メニューをドロワーで表示しない"
 youHaveNoGroups: "グループがありません"
 joinOrCreateGroup: "既存のグループに招待してもらうか、新しくグループを作成してください。"
 noHistory: "履歴はありません"
@@ -613,7 +614,6 @@ regenerateLoginToken: "ログイントークンを再生成"
 regenerateLoginTokenDescription: "ログインに使用される内部トークンを再生成します。通常この操作を行う必要はありません。再生成すると、全てのデバイスでログアウトされます。"
 setMultipleBySeparatingWithSpace: "スペースで区切って複数設定できます。"
 fileIdOrUrl: "ファイルIDまたはURL"
-chatOpenBehavior: "チャットを開くときの動作"
 behavior: "動作"
 sample: "サンプル"
 abuseReports: "通報"
@@ -818,6 +818,7 @@ leaveGroup: "グループから抜ける"
 leaveGroupConfirm: "「{name}」から抜けますか?"
 useDrawerReactionPickerForMobile: "モバイルデバイスのときドロワーで表示"
 welcomeBackWithName: "おかえりなさい、{name}さん"
+clickToFinishEmailVerification: "[{ok}]を押して、メールアドレスの確認を完了してください。"
 
 _emailUnavailable:
   used: "既に使用されています"
diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml
index 8b7513ffa..1fee5f572 100644
--- a/locales/ko-KR.yml
+++ b/locales/ko-KR.yml
@@ -612,7 +612,6 @@ regenerateLoginToken: "로그인 토큰을 재생성"
 regenerateLoginTokenDescription: "로그인할 때 사용되는 내부 토큰을 재생성합니다. 일반적으로 이 작업을 실행할 필요는 없습니다. 이 기능을 사용하면 이 계정으로 로그인한 모든 기기에서 로그아웃됩니다."
 setMultipleBySeparatingWithSpace: "공백으로 구분하여 여러 개 설정할 수 있습니다."
 fileIdOrUrl: "파일 ID 또는 URL"
-chatOpenBehavior: "대화를 열 때의 동작"
 behavior: "동작"
 sample: "예시"
 abuseReports: "신고"
diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml
index ca2af6647..a4f8f3efe 100644
--- a/locales/pl-PL.yml
+++ b/locales/pl-PL.yml
@@ -81,6 +81,8 @@ somethingHappened: "Coś poszło nie tak"
 retry: "Spróbuj ponownie"
 pageLoadError: "Nie udało się załadować strony"
 pageLoadErrorDescription: "Zwykle jest to spowodowane problemem z siecią lub cache przeglądarki. Spróbuj wyczyścić cache i sprawdź jeszcze raz za chwilę."
+serverIsDead: "Serwer nie odpowiada. Zaczekaj chwilę i spróbuj ponownie."
+youShouldUpgradeClient: "Odśwież stronę, by zaaktualizować klienta."
 enterListName: "Nazwa listy"
 privacy: "Prywatność"
 makeFollowManuallyApprove: "Prośby o możliwość obserwacji wymagają zatwierdzenia"
@@ -104,6 +106,7 @@ clickToShow: "Kliknij, aby wyświetlić"
 sensitive: "NSFW"
 add: "Dodaj"
 reaction: "Reakcja"
+reactionSetting: "Reakcje do pokazania w wyborniku reakcji"
 reactionSettingDescription2: "Przeciągnij aby zmienić kolejność, naciśnij aby usunąć, naciśnij „+” aby dodać"
 rememberNoteVisibility: "Zapamiętuj ustawienia widoczności wpisu"
 attachCancel: "Usuń załącznik"
@@ -180,6 +183,7 @@ instanceInfo: "Informacje o instancji"
 statistics: "Statystyki"
 clearQueue: "Wyczyść kolejkę"
 clearQueueConfirmTitle: "Czy na pewno chcesz wyczyścić kolejkę?"
+clearQueueConfirmText: "Wszystkie niewysłane wpisy z kolejki nie zostaną wysłane. Zwykle to nie jest konieczne."
 clearCachedFiles: "Wyczyść pamięć podręczną"
 clearCachedFilesConfirm: "Czy na pewno chcesz usunąć wszystkie zdalne pliki z pamięci podręcznej?"
 blockedInstances: "Zablokowane instancje"
@@ -219,6 +223,7 @@ more: "Więcej!"
 featured: "Wyróżnione"
 usernameOrUserId: "Nazwa lub id użytkownika"
 noSuchUser: "Nie znaleziono użytkownika"
+lookup: "Zapytania"
 announcements: "Ogłoszenia"
 imageUrl: "Adres URL obrazka"
 remove: "Usuń"
@@ -308,22 +313,28 @@ monthX: "{month}"
 yearX: "{year}"
 pages: "Strony"
 integration: "Integracja"
+connectService: "Połącz"
+disconnectService: "Rozłącz"
 enableLocalTimeline: "Włącz lokalną oś czasu"
 enableGlobalTimeline: "Włącz globalną oś czasu"
 disablingTimelinesInfo: "Administratorzy i moderatorzy będą zawsze mieć dostęp do wszystkich osi czasu, nawet gdy są one wyłączone."
 registration: "Zarejestruj się"
 enableRegistration: "Włącz rejestrację nowych użytkowników"
 invite: "Zaproś"
+proxyRemoteFiles: "Przekierowuj pliki obcych instancji przez proxy"
+proxyRemoteFilesDescription: "Gdy ta opcja jest włączona, zdalne pliki które nie są przechowywane lokalnie, lub zostały usunięte z powodu przekroczenia limitu miejsca będą kierowane przez proxy, razem z generowaniem miniatur. Nie ma to żadnego wpływu na przestrzeń dyskową serwera."
 driveCapacityPerLocalAccount: "Powierzchnia dyskowa na lokalnego użytkownika"
 driveCapacityPerRemoteAccount: "Powierzchnia dyskowa na zdalnego użytkownika"
 inMb: "W megabajtach"
 iconUrl: "Adres URL ikony"
 bannerUrl: "Adres URL banera"
+backgroundImageUrl: "Adres URL tła"
 basicInfo: "Podstawowe informacje"
 pinnedUsers: "Przypięty użytkownik"
 pinnedUsersDescription: "Wypisz po jednej nazwie użytkownika w wierszu. Podani użytkownicy zostaną przypięci pod kartą „Eksploruj”."
 pinnedPages: "Przypięte strony"
 pinnedPagesDescription: "Wprowadź ścieżki stron które chcesz przypiąć na głównej stronie instancji, oddzielone znakiem nowego wiersza."
+pinnedClipId: "ID przypiętego klipu"
 pinnedNotes: "Przypięty wpis"
 hcaptcha: "hCaptcha"
 enableHcaptcha: "Włącz hCaptcha"
@@ -338,7 +349,9 @@ antennas: "Anteny"
 manageAntennas: "Zarządzaj Antenami"
 name: "Nazwa"
 antennaSource: "Źródło Anteny"
+antennaKeywords: "Słowa kluczowe do obserwacji"
 antennaExcludeKeywords: "Wykluczone słowa kluczowe"
+antennaKeywordsDescription: "Oddziel spacjami dla warunku AND, albo wymuś koniec linii dla warunku OR"
 notifyAntenna: "Powiadamiaj o nowych wpisach"
 withFileAntenna: "Filtruj tylko wpisy z załączonym plikiem"
 enableServiceworker: "Włącz ServiceWorker"
@@ -586,8 +599,9 @@ useGlobalSetting: "Użyj globalnych ustawień"
 useGlobalSettingDesc: "Jeżeli włączone, zostaną wykorzystane ustawienia powiadomień Twojego konta. Jeżeli wyłączone, mogą zostać wykonane oddzielne konfiguracje."
 other: "Inne"
 regenerateLoginToken: "Generuj token logowania ponownie"
+regenerateLoginTokenDescription: "Regeneruje token używany wewnętrznie podczas logowania. Zazwyczaj nie jest to konieczne. Po regeneracji wszystkie urządzenia zostaną wylogowane."
 setMultipleBySeparatingWithSpace: "Możesz ustawić wiele, oddzielając je spacjami."
-chatOpenBehavior: "Zachowanie okna czatu po otwarciu"
+fileIdOrUrl: "ID pliku albo URL"
 behavior: "Zachowanie"
 sample: "Przykład"
 abuseReports: "Zgłoszenia"
@@ -595,6 +609,8 @@ reportAbuse: "Zgłoś"
 reportAbuseOf: "Zgłoś {name}"
 fillAbuseReportDescription: "Wypełnij szczegóły zgłoszenia. Jeżeli dotyczy ono określonego wpisu, uwzględnij jego adres URL."
 abuseReported: "Twoje zgłoszenie zostało wysłane. Dziękujemy."
+reporteeOrigin: "Pochodzenie zgłoszonego"
+reporterOrigin: "Pochodzenie zgłaszającego"
 send: "Wyślij"
 abuseMarkAsResolved: "Oznacz zgłoszenie jako rozwiązane"
 openInNewTab: "Otwórz w nowej karcie"
@@ -607,7 +623,7 @@ random: "Losowe"
 system: "System"
 switchUi: "Przełącz interfejs użytkownika"
 desktop: "Pulpit"
-createNew: "Utworzy nowy"
+createNew: "Utwórz nowy"
 optional: "Nieobowiązkowe"
 public: "Publiczny"
 i18nInfo: "Misskey jest tłumaczone na wiele języków przez wolontariuszy. Możesz pomóc na {link}."
@@ -630,6 +646,7 @@ driveFilesCount: "Liczba plików na dysku"
 driveUsage: "Użycie przestrzeni dyskowej"
 noCrawle: "Odrzuć indeksowanie przez crawlery"
 noCrawleDescription: "Proś wyszukiwarki internetowe, aby nie indeksowały Twojego profilu, wpisów, stron itd."
+lockedAccountInfo: "Dopóki nie ustawisz widoczności wpisu na \"Obserwujący\", twoje wpisy będą mogli widzieć wszyscy, nawet jeśli ustawisz manualne zatwierdzanie obserwujących."
 alwaysMarkSensitive: "Oznacz domyślnie jako NSFW"
 loadRawImages: "Wyświetlaj zdjęcia w załącznikach w całości zamiast miniatur"
 disableShowingAnimatedImages: "Nie odtwarzaj animowanych obrazów"
@@ -653,6 +670,7 @@ center: "Wyśsrodkuj"
 wide: "Szerokie"
 narrow: "Wąskie"
 reloadToApplySetting: "To ustawienie zostanie zastosowane po odświeżeniu strony. Chcesz odświeżyć?"
+needReloadToApply: "To ustawienie zostanie zastosowane po odświeżeniu strony"
 showTitlebar: "Pokazuj pasek tytułowy"
 clearCache: "Wyczyść pamięć podręczną"
 onlineUsersCount: "{n} osób jest online"
@@ -697,6 +715,8 @@ unlikeConfirm: "Na pewno chcesz usunąć polubienie?"
 fullView: "Pełny widok"
 quitFullView: "Opuść pełny widok"
 addDescription: "Dodaj opis"
+userPagePinTip: "Możesz wyświetlać wpisy w tym miejscu po wybraniu \"Przypnij do profilu\" z menu pojedyńczego wpisu"
+notSpecifiedMentionWarning: "Ten wpis zawiera wzmianki o użytkownikach niezawartych jako odbiorcy"
 info: "Informacje"
 userInfo: "Informacje o użykowniku"
 unknown: "Nieznane"
@@ -734,9 +754,15 @@ middle: "Średnie"
 low: "Niski"
 emailNotConfiguredWarning: "Nie podano adresu e-mail"
 ratio: "Stosunek"
+previewNoteText: "Pokaż podgląd"
+customCss: "Własny CSS"
+customCssWarn: "Używaj tego ustawienia tylko wtedy, gdy wiesz co ono robi. Nieprawidłowe wpisy mogą spowodować, że klient przestanie działać poprawnie."
 global: "Globalna"
+squareAvatars: "Wyświetlaj kwadratowe awatary"
 sent: "Wyślij"
+received: "Otrzymane"
 hashtags: "Hashtag"
+pubSub: "Konta Pub/Sub"
 hide: "Ukryj"
 _ffVisibility:
   public: "Publikuj"
@@ -977,6 +1003,7 @@ _tutorial:
   step1_3: "Twoja oś czasu jest jeszcze pusta, ponieważ nie opublikowałeś(-aś) jeszcze żadnych wpisów i nie obserwujesz jeszcze nikogo."
   step2_1: "Ukończmy konfigurację profilu zanim utworzymy wpis lub zaczniemy kogoś obserwować."
   step3_1: "Zakończyłeś(-aś) konfigurację profilu?"
+  step3_3: "Wypełnij pole i kliknij przycisk w prawym górnym rogu by wysłać post."
 _2fa:
   registerDevice: "Zarejestruj nowe urządzenie"
   step1: "Najpierw, zainstaluj aplikację uwierzytelniającą (taką jak {a} lub {b}) na swoim urządzeniu."
@@ -1048,7 +1075,7 @@ _poll:
   deadlineDate: "Data zakończenia"
   deadlineTime: "godz."
   duration: "Czas trwania"
-  votesCount: "{} głosów"
+  votesCount: "{n} głosów"
   totalVotes: "Łącznie {n} głosów"
   vote: "Głosowanie w ankiecie"
   showResult: "Pokaż wyniki"
diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml
index 19fe6a689..588fb91c4 100644
--- a/locales/ru-RU.yml
+++ b/locales/ru-RU.yml
@@ -611,7 +611,6 @@ regenerateLoginToken: "Создать новый токен для входа"
 regenerateLoginTokenDescription: "Создаёт новый токен, используемый внутри программы во время входа. Обычно в этом нет необходимости. При создании все устройства будут отключены."
 setMultipleBySeparatingWithSpace: "Можно написать несколько через пробел"
 fileIdOrUrl: "Идентификатор файла или ссылка"
-chatOpenBehavior: "Поведение окна чата при открытии"
 behavior: "Поведение"
 sample: "Пример"
 abuseReports: "Жалобы"
@@ -683,6 +682,7 @@ center: "По центру"
 wide: "Толстый"
 narrow: "Тонкий"
 reloadToApplySetting: "Это настройка вступает в силу при загрузке страницы. Перезагрузить сейчас?"
+needReloadToApply: "Чтобы это вступило в силу, требуется перезагрузка."
 showTitlebar: "Показать заголовок"
 clearCache: "Очистить кэш"
 onlineUsersCount: "Пользователей сейчас в сети: {n}"
@@ -801,11 +801,16 @@ makeReactionsPublicDescription: "Список сделанных вами реа
 classic: "Классика"
 unmuteThread: "Отключить звук"
 ffVisibilityDescription: "Вы можете установить объем вашей следующей/последней информации."
+voteConfirm: "Вы бы проголосовали за \"{choice}\"?"
 hide: "Спрятать"
+leaveGroup: "Покинуть группу"
 leaveGroupConfirm: "Вы хотите оставить \"{name}\"?"
+welcomeBackWithName: "С возвращением, {name}!"
+clickToFinishEmailVerification: "Пожалуйста, нажмите [{ok}], чтобы завершить подтверждение адреса электронной почты."
 _emailUnavailable:
   used: "Уже используется"
   format: "Неправильный формат"
+  mx: "Это неправильный почтовый сервер!"
   smtp: "Почтовый сервер не отвечает"
 _ffVisibility:
   public: "Опубликовать"
@@ -1263,6 +1268,7 @@ _exportOrImport:
   muteList: "Скрытые"
   blockingList: "Заблокированные"
   userLists: "Списки"
+  excludeMutingUsers: "Исключение отключенных пользователей"
 _charts:
   federationInstancesIncDec: "Изменение внешних связей"
   federationInstancesTotal: "Количество внешних связей"
diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml
index 029f5ef3c..8cb0e2e2a 100644
--- a/locales/uk-UA.yml
+++ b/locales/uk-UA.yml
@@ -592,7 +592,6 @@ regenerateLoginToken: "Оновити Login Token"
 regenerateLoginTokenDescription: "Регенерувати внутрішній ключ використовуваний під час входу. Зазвичай цього не потрібно робити. При регенерації всі пристрої вийдуть з системи."
 setMultipleBySeparatingWithSpace: "Можна вказати кілька значень, відділивши їх пробілом."
 fileIdOrUrl: "Ідентифікатор файлу або посилання"
-chatOpenBehavior: "Поводження вікна переписки під час відкриття"
 behavior: "Поведінка"
 sample: "Приклад"
 abuseReports: "Скарги"
diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml
index dd69a3c7c..c842b065a 100644
--- a/locales/zh-CN.yml
+++ b/locales/zh-CN.yml
@@ -448,6 +448,7 @@ uiLanguage: "显示语言"
 groupInvited: "您有新的群组邀请"
 aboutX: "关于 {x}"
 useOsNativeEmojis: "使用系统的原生表情符号"
+disableDrawer: "不显示抽屉菜单"
 youHaveNoGroups: "没有群组"
 joinOrCreateGroup: "请加入一个现有的群组,或者创建新群组。"
 noHistory: "没有历史记录"
@@ -613,7 +614,6 @@ regenerateLoginToken: "重新生成登录令牌"
 regenerateLoginTokenDescription: "重新生成用于登录的内部令牌。通常您不需要这样做。重新生成后,您将在所有设备上登出。"
 setMultipleBySeparatingWithSpace: "您可以使用空格分隔多个项目。"
 fileIdOrUrl: "文件ID或者URL"
-chatOpenBehavior: "聊天窗口打开时的行为"
 behavior: "行为"
 sample: "示例"
 abuseReports: "举报"
@@ -685,6 +685,7 @@ center: "中央"
 wide: "宽"
 narrow: "窄"
 reloadToApplySetting: "页面刷新后设置才会生效。是否现在刷新页面?"
+needReloadToApply: "重启后应用才会生效。"
 showTitlebar: "显示标题栏"
 clearCache: "清除缓存"
 onlineUsersCount: "{n}人在线"
@@ -816,6 +817,8 @@ hide: "隐藏"
 leaveGroup: "离开群组"
 leaveGroupConfirm: "确定离开「{name}」?"
 useDrawerReactionPickerForMobile: "在移动设备上使用抽屉显示"
+welcomeBackWithName: "欢迎回来,{name}"
+clickToFinishEmailVerification: "点击 [{ok}] 完成电子邮件地址认证。"
 _emailUnavailable:
   used: "已经被使用过"
   format: "无效的格式"
diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml
index 364ef3dd9..ad264822f 100644
--- a/locales/zh-TW.yml
+++ b/locales/zh-TW.yml
@@ -597,7 +597,6 @@ regenerateLoginToken: "重新產生登入權杖"
 regenerateLoginTokenDescription: "重新產生用於登入的內部權杖。一般情況下是不需要這樣做的。一旦重產,所有裝置將會被登出。"
 setMultipleBySeparatingWithSpace: "您可以使用空格分隔多個項目。"
 fileIdOrUrl: "檔案ID或URL"
-chatOpenBehavior: "開啟聊天窗口時的行為"
 behavior: "行為"
 sample: "範例"
 abuseReports: "檢舉"
diff --git a/package.json b/package.json
index 58df68ca2..0c5becded 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "misskey",
-	"version": "12.100.2",
+	"version": "12.101.0",
 	"codename": "indigo",
 	"repository": {
 		"type": "git",
diff --git a/packages/backend/src/const.ts b/packages/backend/src/const.ts
index 43f59f1e4..b00bd8165 100644
--- a/packages/backend/src/const.ts
+++ b/packages/backend/src/const.ts
@@ -1,2 +1,47 @@
 export const USER_ONLINE_THRESHOLD = 1000 * 60 * 10; // 10min
 export const USER_ACTIVE_THRESHOLD = 1000 * 60 * 60 * 24 * 3; // 3days
+
+// ブラウザで直接表示することを許可するファイルの種類のリスト
+// ここに含まれないものは application/octet-stream としてレスポンスされる
+// SVGはXSSを生むので許可しない
+export const FILE_TYPE_BROWSERSAFE = [
+	// Images
+	'image/png',
+	'image/gif',
+	'image/jpeg',
+	'image/webp',
+	'image/apng',
+	'image/bmp',
+	'image/tiff',
+	'image/x-icon',
+
+	// OggS
+	'audio/opus',
+	'video/ogg',
+	'audio/ogg',
+	'application/ogg',
+
+	// ISO/IEC base media file format
+	'video/quicktime',
+	'video/mp4',
+	'audio/mp4',
+	'video/x-m4v',
+	'audio/x-m4a',
+	'video/3gpp',
+	'video/3gpp2',
+
+	'video/mpeg',
+	'audio/mpeg',
+
+	'video/webm',
+	'audio/webm',
+
+	'audio/aac',
+	'audio/x-flac',
+	'audio/vnd.wave',
+];
+/*
+https://github.com/sindresorhus/file-type/blob/main/supported.js
+https://github.com/sindresorhus/file-type/blob/main/core.js
+https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Containers
+*/
diff --git a/packages/backend/src/mfm/from-html.ts b/packages/backend/src/mfm/from-html.ts
index 43e16d80c..fc6d3b306 100644
--- a/packages/backend/src/mfm/from-html.ts
+++ b/packages/backend/src/mfm/from-html.ts
@@ -168,7 +168,7 @@ export function fromHtml(html: string, hashtagNames?: string[]): string | null {
 			case 'blockquote': {
 				const t = getText(node);
 				if (t) {
-					text += '> ';
+					text += '\n> ';
 					text += t.split('\n').join(`\n> `);
 				}
 				break;
diff --git a/packages/backend/src/misc/get-file-info.ts b/packages/backend/src/misc/get-file-info.ts
index f36aa8f96..361cdd295 100644
--- a/packages/backend/src/misc/get-file-info.ts
+++ b/packages/backend/src/misc/get-file-info.ts
@@ -99,7 +99,10 @@ export async function getFileInfo(path: string): Promise<FileInfo> {
 /**
  * Detect MIME Type and extension
  */
-export async function detectType(path: string) {
+export async function detectType(path: string): Promise<{
+	mime: string;
+	ext: string | null;
+}> {
 	// Check 0 byte
 	const fileSize = await getFileSize(path);
 	if (fileSize === 0) {
diff --git a/packages/backend/src/server/file/send-drive-file.ts b/packages/backend/src/server/file/send-drive-file.ts
index 8bb5655b4..7bfc36e25 100644
--- a/packages/backend/src/server/file/send-drive-file.ts
+++ b/packages/backend/src/server/file/send-drive-file.ts
@@ -14,6 +14,7 @@ import { detectType } from '@/misc/get-file-info';
 import { convertToJpeg, convertToPngOrJpeg } from '@/services/drive/image-processor';
 import { GenerateVideoThumbnail } from '@/services/drive/generate-video-thumbnail';
 import { StatusError } from '@/misc/fetch';
+import { FILE_TYPE_BROWSERSAFE } from '@/const';
 
 //const _filename = fileURLToPath(import.meta.url);
 const _filename = __filename;
@@ -27,6 +28,7 @@ const commonReadableHandlerGenerator = (ctx: Koa.Context) => (e: Error): void =>
 	ctx.set('Cache-Control', 'max-age=300');
 };
 
+// eslint-disable-next-line import/no-default-export
 export default async function(ctx: Koa.Context) {
 	const key = ctx.params.key;
 
@@ -81,7 +83,7 @@ export default async function(ctx: Koa.Context) {
 
 				const image = await convertFile();
 				ctx.body = image.data;
-				ctx.set('Content-Type', image.type);
+				ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(image.type) ? image.type : 'application/octet-stream');
 				ctx.set('Cache-Control', 'max-age=31536000, immutable');
 			} catch (e) {
 				serverLogger.error(`${e}`);
@@ -112,14 +114,14 @@ export default async function(ctx: Koa.Context) {
 		}).toString();
 
 		ctx.body = InternalStorage.read(key);
-		ctx.set('Content-Type', mime);
+		ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(mime) ? mime : 'application/octet-stream');
 		ctx.set('Cache-Control', 'max-age=31536000, immutable');
 		ctx.set('Content-Disposition', contentDisposition('inline', filename));
 	} else {
 		const readable = InternalStorage.read(file.accessKey!);
 		readable.on('error', commonReadableHandlerGenerator(ctx));
 		ctx.body = readable;
-		ctx.set('Content-Type', file.type);
+		ctx.set('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.type) ? file.type : 'application/octet-stream');
 		ctx.set('Cache-Control', 'max-age=31536000, immutable');
 		ctx.set('Content-Disposition', contentDisposition('inline', file.name));
 	}
diff --git a/packages/backend/src/server/proxy/proxy-media.ts b/packages/backend/src/server/proxy/proxy-media.ts
index 9e13c0877..b116b4b96 100644
--- a/packages/backend/src/server/proxy/proxy-media.ts
+++ b/packages/backend/src/server/proxy/proxy-media.ts
@@ -6,6 +6,7 @@ import { createTemp } from '@/misc/create-temp';
 import { downloadUrl } from '@/misc/download-url';
 import { detectType } from '@/misc/get-file-info';
 import { StatusError } from '@/misc/fetch';
+import { FILE_TYPE_BROWSERSAFE } from '@/const';
 
 export async function proxyMedia(ctx: Koa.Context) {
 	const url = 'url' in ctx.query ? ctx.query.url : 'https://' + ctx.params.url;
@@ -18,7 +19,7 @@ export async function proxyMedia(ctx: Koa.Context) {
 
 		const { mime, ext } = await detectType(path);
 
-		if (!mime.startsWith('image/')) throw 403;
+		if (!FILE_TYPE_BROWSERSAFE.includes(mime)) throw 403;
 
 		let image: IImage;
 
diff --git a/packages/backend/src/server/web/views/note.pug b/packages/backend/src/server/web/views/note.pug
index 703093697..fce91bdab 100644
--- a/packages/backend/src/server/web/views/note.pug
+++ b/packages/backend/src/server/web/views/note.pug
@@ -4,6 +4,7 @@ block vars
 	- const user = note.user;
 	- 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;
 
 block title
 	= `${title} | ${instanceName}`
@@ -19,7 +20,7 @@ block og
 	meta(property='og:image'       content= user.avatarUrl)
 
 block meta
-	if user.host || profile.noCrawle
+	if user.host || isRenote || profile.noCrawle
 		meta(name='robots' content='noindex')
 
 	meta(name='misskey:user-username' content=user.username)
diff --git a/packages/backend/src/services/drive/add-file.ts b/packages/backend/src/services/drive/add-file.ts
index ee4d51a96..a59c9501b 100644
--- a/packages/backend/src/services/drive/add-file.ts
+++ b/packages/backend/src/services/drive/add-file.ts
@@ -20,6 +20,7 @@ import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error';
 import * as S3 from 'aws-sdk/clients/s3';
 import { getS3 } from './s3';
 import * as sharp from 'sharp';
+import { FILE_TYPE_BROWSERSAFE } from '@/const';
 
 const logger = driveLogger.createSubLogger('register', 'yellow');
 
@@ -241,6 +242,7 @@ export async function generateAlts(path: string, type: string, generateWeb: bool
  */
 async function upload(key: string, stream: fs.ReadStream | Buffer, type: string, filename?: string) {
 	if (type === 'image/apng') type = 'image/png';
+	if (!FILE_TYPE_BROWSERSAFE.includes(type)) type = 'application/octet-stream';
 
 	const meta = await fetchMeta();
 
diff --git a/packages/backend/src/services/push-notification.ts b/packages/backend/src/services/push-notification.ts
index 616bc7441..2133768a9 100644
--- a/packages/backend/src/services/push-notification.ts
+++ b/packages/backend/src/services/push-notification.ts
@@ -3,10 +3,33 @@ import config from '@/config/index';
 import { SwSubscriptions } from '@/models/index';
 import { fetchMeta } from '@/misc/fetch-meta';
 import { Packed } from '@/misc/schema';
+import { getNoteSummary } from '@/misc/get-note-summary';
 
 type notificationType = 'notification' | 'unreadMessagingMessage';
 type notificationBody = Packed<'Notification'> | Packed<'MessagingMessage'>;
 
+// プッシュメッセージサーバーには文字数制限があるため、内容を削減します
+function truncateNotification(notification: Packed<'Notification'>): any {
+	if (notification.note) {
+		return {
+			...notification,
+			note: {
+				...notification.note,
+				// textをgetNoteSummaryしたものに置き換える
+				text: getNoteSummary(notification.type === 'renote' ? notification.note.renote as Packed<'Note'> : notification.note),
+				...{
+					cw: undefined,
+					reply: undefined,
+					renote: undefined,
+					user: undefined as any, // 通知を受け取ったユーザーである場合が多いのでこれも捨てる
+				}
+			}
+		};
+	}
+
+	return notification;
+}
+
 export default async function(userId: string, type: notificationType, body: notificationBody) {
 	const meta = await fetchMeta();
 
@@ -32,7 +55,9 @@ export default async function(userId: string, type: notificationType, body: noti
 		};
 
 		push.sendNotification(pushSubscription, JSON.stringify({
-			type, body,
+			type,
+			body: type === 'notification' ? truncateNotification(body as Packed<'Notification'>) : body,
+			userId,
 		}), {
 			proxy: config.proxy,
 		}).catch((err: any) => {
diff --git a/packages/client/assets/sparkle-spritesheet.png b/packages/client/assets/sparkle-spritesheet.png
deleted file mode 100644
index c32fbf2a1..000000000
--- a/packages/client/assets/sparkle-spritesheet.png
+++ /dev/null
@@ -1,3 +0,0 @@
-version https://git-lfs.github.com/spec/v1
-oid sha256:fab6b43b46b196d8193062b37640dfa04e7bff87511f25667890309a7cbb2e7c
-size 185
diff --git a/packages/client/package.json b/packages/client/package.json
index 681e22208..198bcbcef 100644
--- a/packages/client/package.json
+++ b/packages/client/package.json
@@ -27,39 +27,39 @@
 		"@types/oauth": "0.9.1",
 		"@types/parse5": "6.0.3",
 		"@types/punycode": "2.1.0",
-		"@types/qrcode": "1.4.1",
+		"@types/qrcode": "1.4.2",
 		"@types/random-seed": "0.3.3",
 		"@types/request-stats": "3.0.0",
 		"@types/seedrandom": "2.4.28",
 		"@types/throttle-debounce": "2.1.0",
 		"@types/tinycolor2": "1.4.3",
-		"@types/tmp": "0.2.2",
+		"@types/tmp": "0.2.3",
 		"@types/uuid": "8.3.3",
 		"@types/web-push": "3.3.2",
 		"@types/webpack": "5.28.0",
 		"@types/webpack-stream": "3.2.12",
 		"@types/websocket": "1.0.4",
 		"@types/ws": "8.2.2",
-		"@typescript-eslint/parser": "5.6.0",
-		"@vue/compiler-sfc": "3.2.24",
+		"@typescript-eslint/parser": "5.8.1",
+		"@vue/compiler-sfc": "3.2.26",
 		"abort-controller": "3.0.0",
 		"autobind-decorator": "2.4.0",
 		"autosize": "4.0.4",
 		"autwh": "0.1.0",
 		"blurhash": "1.1.4",
-		"broadcast-channel": "4.7.0",
-		"chart.js": "3.6.2",
+		"broadcast-channel": "4.9.0",
+		"chart.js": "3.7.0",
 		"chartjs-adapter-date-fns": "2.0.0",
 		"chartjs-plugin-zoom": "1.2.0",
 		"compare-versions": "3.6.0",
-		"content-disposition": "0.5.3",
+		"content-disposition": "0.5.4",
 		"crc-32": "1.2.0",
 		"css-loader": "6.5.1",
-		"cssnano": "5.0.12",
-		"date-fns": "2.27.0",
+		"cssnano": "5.0.14",
+		"date-fns": "2.28.0",
 		"dateformat": "4.5.1",
 		"escape-regexp": "0.0.1",
-		"eslint": "8.4.1",
+		"eslint": "8.5.0",
 		"eslint-plugin-vue": "8.2.0",
 		"eventemitter3": "4.0.7",
 		"feed": "4.2.2",
@@ -80,7 +80,7 @@
 		"parse5": "6.0.1",
 		"photoswipe": "git://github.com/dimsemenov/photoswipe#v5-beta",
 		"portscanner": "2.2.0",
-		"postcss": "8.4.4",
+		"postcss": "8.4.5",
 		"postcss-loader": "6.2.1",
 		"prismjs": "1.25.0",
 		"private-ip": "2.3.3",
@@ -94,7 +94,7 @@
 		"request-stats": "3.0.0",
 		"rndstr": "1.0.0",
 		"s-age": "1.1.2",
-		"sass": "1.44.0",
+		"sass": "1.45.1",
 		"sass-loader": "12.4.0",
 		"seedrandom": "3.0.5",
 		"strict-event-emitter-types": "2.0.0",
@@ -111,11 +111,11 @@
 		"tsc-alias": "1.4.2",
 		"tsconfig-paths": "3.12.0",
 		"twemoji-parser": "13.1.0",
-		"typescript": "4.5.2",
+		"typescript": "4.5.4",
 		"uuid": "8.3.2",
 		"v-debounce": "0.1.2",
 		"vanilla-tilt": "1.7.2",
-		"vue": "3.2.24",
+		"vue": "3.2.26",
 		"vue-loader": "16.8.3",
 		"vue-prism-editor": "2.0.0-alpha.2",
 		"vue-router": "4.0.5",
@@ -126,14 +126,14 @@
 		"webpack": "5.65.0",
 		"webpack-cli": "4.9.1",
 		"websocket": "1.0.34",
-		"ws": "8.3.0"
+		"ws": "8.4.0"
 	},
 	"devDependencies": {
 		"@redocly/openapi-core": "1.0.0-beta.54",
 		"@types/fluent-ffmpeg": "2.1.17",
-		"@typescript-eslint/eslint-plugin": "5.4.0",
+		"@typescript-eslint/eslint-plugin": "5.8.1",
 		"cross-env": "7.0.3",
-		"cypress": "8.5.0",
+		"cypress": "9.2.0",
 		"eslint-plugin-import": "2.25.3",
 		"start-server-and-test": "1.14.0"
 	}
diff --git a/packages/client/src/components/emoji-picker.vue b/packages/client/src/components/emoji-picker.vue
index ff450246f..a8eed1ca2 100644
--- a/packages/client/src/components/emoji-picker.vue
+++ b/packages/client/src/components/emoji-picker.vue
@@ -77,7 +77,7 @@
 import { defineComponent, markRaw } from 'vue';
 import { emojilist } from '@/scripts/emojilist';
 import { getStaticImageUrl } from '@/scripts/get-static-image-url';
-import Particle from '@/components/particle.vue';
+import Ripple from '@/components/ripple.vue';
 import * as os from '@/os';
 import { isTouchUsing } from '@/scripts/touch';
 import { isMobile } from '@/scripts/is-mobile';
@@ -296,9 +296,9 @@ export default defineComponent({
 			if (ev) {
 				const el = ev.currentTarget || ev.target;
 				const rect = el.getBoundingClientRect();
-				const x = rect.left + (el.clientWidth / 2);
-				const y = rect.top + (el.clientHeight / 2);
-				os.popup(Particle, { x, y }, {}, 'end');
+				const x = rect.left + (el.offsetWidth / 2);
+				const y = rect.top + (el.offsetHeight / 2);
+				os.popup(Ripple, { x, y }, {}, 'end');
 			}
 
 			const key = this.getKey(emoji);
diff --git a/packages/client/src/components/form/input.vue b/packages/client/src/components/form/input.vue
index c990b693f..3533f4f27 100644
--- a/packages/client/src/components/form/input.vue
+++ b/packages/client/src/components/form/input.vue
@@ -5,7 +5,7 @@
 		<div ref="prefixEl" class="prefix"><slot name="prefix"></slot></div>
 		<input ref="inputEl"
 			v-model="v"
-			v-panel
+			v-adaptive-border
 			:type="type"
 			:disabled="disabled"
 			:required="required"
@@ -243,7 +243,8 @@ export default defineComponent({
 			font-weight: normal;
 			font-size: 1em;
 			color: var(--fg);
-			border: solid 0.5px var(--panel);
+			background: var(--panel);
+			border: solid 1px var(--panel);
 			border-radius: 6px;
 			outline: none;
 			box-shadow: none;
@@ -251,7 +252,7 @@ export default defineComponent({
 			transition: border-color 0.1s ease-out;
 
 			&:hover {
-				border-color: var(--inputBorderHover);
+				border-color: var(--inputBorderHover) !important;
 			}
 		}
 
@@ -298,7 +299,7 @@ export default defineComponent({
 
 		&.focused {
 			> input {
-				border-color: var(--accent);
+				border-color: var(--accent) !important;
 				//box-shadow: 0 0 0 4px var(--focus);
 			}
 		}
diff --git a/packages/client/src/components/form/select.vue b/packages/client/src/components/form/select.vue
index 9ecff1aa6..afc53ca9c 100644
--- a/packages/client/src/components/form/select.vue
+++ b/packages/client/src/components/form/select.vue
@@ -3,7 +3,9 @@
 	<div class="label" @click="focus"><slot name="label"></slot></div>
 	<div ref="container" class="input" :class="{ inline, disabled, focused }" @click.prevent="onClick">
 		<div ref="prefixEl" class="prefix"><slot name="prefix"></slot></div>
-		<select ref="inputEl" v-model="v" v-panel
+		<select ref="inputEl"
+			v-model="v"
+			v-adaptive-border
 			class="select"
 			:disabled="disabled"
 			:required="required"
@@ -226,7 +228,7 @@ export default defineComponent({
 
 		&:hover {
 			> .select {
-				border-color: var(--inputBorderHover);
+				border-color: var(--inputBorderHover) !important;
 			}
 		}
 
@@ -242,6 +244,7 @@ export default defineComponent({
 			font-weight: normal;
 			font-size: 1em;
 			color: var(--fg);
+			background: var(--panel);
 			border: solid 1px var(--panel);
 			border-radius: 6px;
 			outline: none;
@@ -295,7 +298,7 @@ export default defineComponent({
 
 		&.focused {
 			> select {
-				border-color: var(--accent);
+				border-color: var(--accent) !important;
 			}
 		}
 
diff --git a/packages/client/src/components/form/switch.vue b/packages/client/src/components/form/switch.vue
index 239303a55..aa9b09215 100644
--- a/packages/client/src/components/form/switch.vue
+++ b/packages/client/src/components/form/switch.vue
@@ -2,10 +2,6 @@
 <div
 	class="ziffeoms"
 	:class="{ disabled, checked }"
-	role="switch"
-	:aria-checked="checked"
-	:aria-disabled="disabled"
-	@click.prevent="toggle"
 >
 	<input
 		ref="input"
@@ -13,18 +9,20 @@
 		:disabled="disabled"
 		@keydown.enter="toggle"
 	>
-	<span v-tooltip="checked ? $ts.itsOn : $ts.itsOff" class="button">
-		<span class="handle"></span>
+	<span ref="button" v-adaptive-border v-tooltip="checked ? $ts.itsOn : $ts.itsOff" class="button" @click.prevent="toggle">
+		<i class="check fas fa-check"></i>
 	</span>
 	<span class="label">
-		<span><slot></slot></span>
+		<span @click="toggle"><slot></slot></span>
 		<p class="caption"><slot name="caption"></slot></p>
 	</span>
 </div>
 </template>
 
 <script lang="ts">
-import { defineComponent } from 'vue';
+import { defineComponent, ref, toRefs } from 'vue';
+import * as os from '@/os';
+import Ripple from '@/components/ripple.vue';
 
 export default defineComponent({
 	props: {
@@ -37,17 +35,28 @@ export default defineComponent({
 			default: false
 		}
 	},
-	computed: {
-		checked(): boolean {
-			return this.modelValue;
-		}
+
+	setup(props, context) {
+		const button = ref<HTMLElement>();
+		const checked = toRefs(props).modelValue;
+		const toggle = () => {
+			if (props.disabled) return;
+			context.emit('update:modelValue', !checked.value);
+
+			if (!checked.value) {
+				const rect = button.value.getBoundingClientRect();
+				const x = rect.left + (button.value.offsetWidth / 2);
+				const y = rect.top + (button.value.offsetHeight / 2);
+				os.popup(Ripple, { x, y, particle: false }, {}, 'end');
+			}
+		};
+
+		return {
+			button,
+			checked,
+			toggle,
+		};
 	},
-	methods: {
-		toggle() {
-			if (this.disabled) return;
-			this.$emit('update:modelValue', !this.checked);
-		}
-	}
 });
 </script>
 
@@ -55,16 +64,7 @@ export default defineComponent({
 .ziffeoms {
 	position: relative;
 	display: flex;
-	cursor: pointer;
-	transition: all 0.3s;
-
-	&:first-child {
-		margin-top: 0;
-	}
-
-	&:last-child {
-		margin-bottom: 0;
-	}
+	transition: all 0.2s ease;
 
 	> * {
 		user-select: none;
@@ -80,27 +80,32 @@ export default defineComponent({
 
 	> .button {
 		position: relative;
-		display: inline-block;
+		display: inline-flex;
 		flex-shrink: 0;
 		margin: 0;
-		width: 36px;
-		height: 26px;
-		background: var(--switchBg);
+		box-sizing: border-box;
+		width: 23px;
+		height: 23px;
 		outline: none;
-		border-radius: 999px;
+		background: var(--panel);
+		border: solid 1px var(--panel);
+		border-radius: 4px;
+		cursor: pointer;
 		transition: inherit;
 
-		> .handle {
-			position: absolute;
-			top: 0;
-			bottom: 0;
-			left: 5px;
-			margin: auto 0;
-			border-radius: 100%;
-			transition: background-color 0.3s, transform 0.3s;
-			width: 16px;
-			height: 16px;
-			background-color: #fff;
+		> .check {
+			margin: auto;
+			opacity: 0;
+			color: var(--fgOnAccent);
+			font-size: 13px;
+			transform: scale(0.5);
+			transition: all 0.2s ease;
+		}
+	}
+
+	&:hover {
+		> .button {
+			border-color: var(--inputBorderHover) !important;
 		}
 	}
 
@@ -108,13 +113,13 @@ export default defineComponent({
 		margin-left: 16px;
 		margin-top: 2px;
 		display: block;
-		cursor: pointer;
 		transition: inherit;
 		color: var(--fg);
 
 		> span {
 			display: block;
 			line-height: 20px;
+			cursor: pointer;
 			transition: inherit;
 		}
 
@@ -129,12 +134,6 @@ export default defineComponent({
 		}
 	}
 
-	&:hover {
-		> .button {
-			background-color: var(--accentedBg);
-		}
-	}
-
 	&.disabled {
 		opacity: 0.6;
 		cursor: not-allowed;
@@ -142,11 +141,12 @@ export default defineComponent({
 
 	&.checked {
 		> .button {
-			background-color: var(--accent);
-			border-color: var(--accent);
+			background-color: var(--accent) !important;
+			border-color: var(--accent) !important;
 
-			> .handle {
-				transform: translateX(10px);
+			> .check {
+				opacity: 1;
+				transform: scale(1);
 			}
 		}
 	}
diff --git a/packages/client/src/components/form/textarea.vue b/packages/client/src/components/form/textarea.vue
index 98fd0da94..c9ba9b97a 100644
--- a/packages/client/src/components/form/textarea.vue
+++ b/packages/client/src/components/form/textarea.vue
@@ -4,7 +4,7 @@
 	<div class="input" :class="{ disabled, focused, tall, pre }">
 		<textarea ref="inputEl"
 			v-model="v"
-			v-panel
+			v-adaptive-border
 			:class="{ code, _monospace: code }"
 			:disabled="disabled"
 			:required="required"
@@ -210,7 +210,8 @@ export default defineComponent({
 			font-weight: normal;
 			font-size: 1em;
 			color: var(--fg);
-			border: solid 0.5px var(--panel);
+			background: var(--panel);
+			border: solid 1px var(--panel);
 			border-radius: 6px;
 			outline: none;
 			box-shadow: none;
@@ -218,13 +219,13 @@ export default defineComponent({
 			transition: border-color 0.1s ease-out;
 
 			&:hover {
-				border-color: var(--inputBorderHover);
+				border-color: var(--inputBorderHover) !important;
 			}
 		}
 
 		&.focused {
 			> textarea {
-				border-color: var(--accent);
+				border-color: var(--accent) !important;
 			}
 		}
 
diff --git a/packages/client/src/components/global/a.vue b/packages/client/src/components/global/a.vue
index 5db61203c..77ee7525a 100644
--- a/packages/client/src/components/global/a.vue
+++ b/packages/client/src/components/global/a.vue
@@ -106,11 +106,6 @@ export default defineComponent({
 				return;
 			}
 
-			if (this.to.startsWith('/my/messaging')) {
-				if (ColdDeviceStorage.get('chatOpenBehavior') === 'window') return this.window();
-				if (ColdDeviceStorage.get('chatOpenBehavior') === 'popout') return this.popout();
-			}
-
 			if (this.behavior) {
 				if (this.behavior === 'window') {
 					return this.window();
diff --git a/packages/client/src/components/global/header.vue b/packages/client/src/components/global/header.vue
index 2e03d783a..a241ece40 100644
--- a/packages/client/src/components/global/header.vue
+++ b/packages/client/src/components/global/header.vue
@@ -6,7 +6,7 @@
 			<i v-else-if="info.icon" class="icon" :class="info.icon"></i>
 
 			<div class="title">
-				<MkUserName v-if="info.userName" :user="info.userName" :nowrap="false" class="title"/>
+				<MkUserName v-if="info.userName" :user="info.userName" :nowrap="true" class="title"/>
 				<div v-else-if="info.title" class="title">{{ info.title }}</div>
 				<div v-if="!narrow && info.subtitle" class="subtitle">
 					{{ info.subtitle }}
@@ -268,6 +268,7 @@ export default defineComponent({
 	> .titleContainer {
 		display: flex;
 		align-items: center;
+		max-width: 400px;
 		overflow: auto;
 		white-space: nowrap;
 		text-align: left;
diff --git a/packages/client/src/components/global/url.vue b/packages/client/src/components/global/url.vue
index 097fcddef..56a8c3453 100644
--- a/packages/client/src/components/global/url.vue
+++ b/packages/client/src/components/global/url.vue
@@ -1,7 +1,5 @@
 <template>
-<component :is="self ? 'MkA' : 'a'" class="ieqqeuvs _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target"
-	@mouseover="onMouseover"
-	@mouseleave="onMouseleave"
+<component :is="self ? 'MkA' : 'a'" ref="el" class="ieqqeuvs _link" :[attr]="self ? url.substr(local.length) : url" :rel="rel" :target="target"
 	@contextmenu.stop="() => {}"
 >
 	<template v-if="!self">
@@ -20,11 +18,11 @@
 </template>
 
 <script lang="ts">
-import { defineComponent } from 'vue';
+import { defineComponent, ref } from 'vue';
 import { toUnicode as decodePunycode } from 'punycode/';
 import { url as local } from '@/config';
-import { isTouchUsing } from '@/scripts/touch';
 import * as os from '@/os';
+import { useTooltip } from '@/scripts/use-tooltip';
 
 export default defineComponent({
 	props: {
@@ -35,74 +33,36 @@ export default defineComponent({
 		rel: {
 			type: String,
 			required: false,
+			default: null,
 		}
 	},
-	data() {
-		const self = this.url.startsWith(local);
+	setup(props) {
+		const self = props.url.startsWith(local);
+		const url = new URL(props.url);
+		const el = ref();
+		
+		useTooltip(el, (showing) => {
+			os.popup(import('@/components/url-preview-popup.vue'), {
+				showing,
+				url: props.url,
+				source: el.value,
+			}, {}, 'closed');
+		});
+
 		return {
 			local,
-			schema: null as string | null,
-			hostname: null as string | null,
-			port: null as string | null,
-			pathname: null as string | null,
-			query: null as string | null,
-			hash: null as string | null,
+			schema: url.protocol,
+			hostname: decodePunycode(url.hostname),
+			port: url.port,
+			pathname: decodeURIComponent(url.pathname),
+			query: decodeURIComponent(url.search),
+			hash: decodeURIComponent(url.hash),
 			self: self,
 			attr: self ? 'to' : 'href',
 			target: self ? null : '_blank',
-			showTimer: null,
-			hideTimer: null,
-			checkTimer: null,
-			close: null,
+			el,
 		};
 	},
-	created() {
-		const url = new URL(this.url);
-		this.schema = url.protocol;
-		this.hostname = decodePunycode(url.hostname);
-		this.port = url.port;
-		this.pathname = decodeURIComponent(url.pathname);
-		this.query = decodeURIComponent(url.search);
-		this.hash = decodeURIComponent(url.hash);
-	},
-	methods: {
-		async showPreview() {
-			if (!document.body.contains(this.$el)) return;
-			if (this.close) return;
-
-			const { dispose } = await os.popup(import('@/components/url-preview-popup.vue'), {
-				url: this.url,
-				source: this.$el
-			});
-
-			this.close = () => {
-				dispose();
-			};
-
-			this.checkTimer = setInterval(() => {
-				if (!document.body.contains(this.$el)) this.closePreview();
-			}, 1000);
-		},
-		closePreview() {
-			if (this.close) {
-				clearInterval(this.checkTimer);
-				this.close();
-				this.close = null;
-			}
-		},
-		onMouseover() {
-			if (isTouchUsing) return;
-			clearTimeout(this.showTimer);
-			clearTimeout(this.hideTimer);
-			this.showTimer = setTimeout(this.showPreview, 500);
-		},
-		onMouseleave() {
-			if (isTouchUsing) return;
-			clearTimeout(this.showTimer);
-			clearTimeout(this.hideTimer);
-			this.hideTimer = setTimeout(this.closePreview, 500);
-		}
-	}
 });
 </script>
 
diff --git a/packages/client/src/components/media-list.vue b/packages/client/src/components/media-list.vue
index c987ff5ff..2970d06c9 100644
--- a/packages/client/src/components/media-list.vue
+++ b/packages/client/src/components/media-list.vue
@@ -105,6 +105,7 @@ export default defineComponent({
 		return {
 			previewable,
 			gallery,
+			pswpZIndex: os.claimZIndex('middle'),
 		};
 	},
 });
@@ -188,3 +189,11 @@ export default defineComponent({
 	}
 }
 </style>
+
+<style lang="scss">
+.pswp {
+	// なぜか機能しない
+  //z-index: v-bind(pswpZIndex);
+	z-index: 2000000;
+}
+</style>
diff --git a/packages/client/src/components/mfm.ts b/packages/client/src/components/mfm.ts
index d1da365d9..2e6d26476 100644
--- a/packages/client/src/components/mfm.ts
+++ b/packages/client/src/components/mfm.ts
@@ -175,14 +175,7 @@ export default defineComponent({
 							if (!this.$store.state.animatedMfm) {
 								return genEl(token.children);
 							}
-							let count = token.props.args.count ? parseInt(token.props.args.count) : 10;
-							if (count > 100) {
-								count = 100;
-							}
-							const speed = token.props.args.speed ? parseFloat(token.props.args.speed) : 1;
-							return h(MkSparkle, {
-								count, speed,
-							}, genEl(token.children));
+							return h(MkSparkle, {}, genEl(token.children));
 						}
 						case 'rotate': {
 							const degrees = parseInt(token.props.args.deg) || '90';
diff --git a/packages/client/src/components/note-preview.vue b/packages/client/src/components/note-preview.vue
index 6e3eba930..bdcb8d5ee 100644
--- a/packages/client/src/components/note-preview.vue
+++ b/packages/client/src/components/note-preview.vue
@@ -7,7 +7,7 @@
 		</div>
 		<div class="body">
 			<div class="content">
-				<Mfm :text="text" :author="$i" :i="$i"/>
+				<Mfm :text="text.trim()" :author="$i" :i="$i"/>
 			</div>
 		</div>
 	</div>
@@ -61,6 +61,7 @@ export default defineComponent({
 		width: 40px;
 		height: 40px;
 		border-radius: 8px;
+		pointer-events: none;
 	}
 
 	> .main {
@@ -69,6 +70,7 @@ export default defineComponent({
 
 		> .header {
 			margin-bottom: 2px;
+			font-weight: bold;
 		}
 
 		> .body {
diff --git a/packages/client/src/components/page-window.vue b/packages/client/src/components/page-window.vue
index 39c185b3e..ec7451d5a 100644
--- a/packages/client/src/components/page-window.vue
+++ b/packages/client/src/components/page-window.vue
@@ -16,7 +16,13 @@
 	<template #headerLeft>
 		<button v-if="history.length > 0" v-tooltip="$ts.goBack" class="_button" @click="back()"><i class="fas fa-arrow-left"></i></button>
 	</template>
-	<div class="yrolvcoq">
+	<template #headerRight>
+		<button v-tooltip="$ts.showInPage" class="_button" @click="expand()"><i class="fas fa-expand-alt"></i></button>
+		<button v-tooltip="$ts.popout" class="_button" @click="popout()"><i class="fas fa-external-link-alt"></i></button>
+		<button class="_button" @click="menu"><i class="fas fa-ellipsis-h"></i></button>
+	</template>
+
+	<div class="yrolvcoq" :style="{ background: pageInfo?.bg }">
 		<MkStickyContainer>
 			<template #header><MkHeader v-if="pageInfo && !pageInfo.hideHeader" :info="pageInfo"/></template>
 			<component :is="component" v-bind="props" :ref="changePage"/>
@@ -33,6 +39,7 @@ import copyToClipboard from '@/scripts/copy-to-clipboard';
 import { resolve } from '@/router';
 import { url } from '@/config';
 import * as symbols from '@/symbols';
+import * as os from '@/os';
 
 export default defineComponent({
 	components: {
@@ -139,6 +146,23 @@ export default defineComponent({
 			this.props = props;
 		},
 
+		menu(ev) {
+			os.popupMenu([{
+				icon: 'fas fa-external-link-alt',
+				text: this.$ts.openInNewTab,
+				action: () => {
+					window.open(this.url, '_blank');
+					this.$refs.window.close();
+				}
+			}, {
+				icon: 'fas fa-link',
+				text: this.$ts.copyLink,
+				action: () => {
+					copyToClipboard(this.url);
+				}
+			}], ev.currentTarget || ev.target);
+		},
+
 		back() {
 			this.navigate(this.history.pop(), false);
 		},
diff --git a/packages/client/src/components/reactions-viewer.reaction.vue b/packages/client/src/components/reactions-viewer.reaction.vue
index a1de99f01..bbf518549 100644
--- a/packages/client/src/components/reactions-viewer.reaction.vue
+++ b/packages/client/src/components/reactions-viewer.reaction.vue
@@ -2,7 +2,7 @@
 <button
 	v-if="count > 0"
 	ref="buttonRef"
-	v-particle="canToggle"
+	v-ripple="canToggle"
 	class="hkzvhatu _button"
 	:class="{ reacted: note.myReaction == reaction, canToggle }"
 	@click="toggleReaction()"
diff --git a/packages/client/src/components/particle.vue b/packages/client/src/components/ripple.vue
similarity index 65%
rename from packages/client/src/components/particle.vue
rename to packages/client/src/components/ripple.vue
index d82705c1e..272eacbc6 100644
--- a/packages/client/src/components/particle.vue
+++ b/packages/client/src/components/ripple.vue
@@ -1,5 +1,5 @@
 <template>
-<div class="vswabwbm" :style="{ top: `${y - 64}px`, left: `${x - 64}px` }" :class="{ active }">
+<div class="vswabwbm" :style="{ zIndex, top: `${y - 64}px`, left: `${x - 64}px` }" :class="{ active }">
 	<svg width="128" height="128" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg">
 		<circle fill="none" cx="64" cy="64">
 			<animate attributeName="r"
@@ -52,7 +52,8 @@
 </template>
 
 <script lang="ts">
-import { defineComponent } from 'vue';
+import { defineComponent, onMounted } from 'vue';
+import * as os from '@/os';
 
 export default defineComponent({
 	props: {
@@ -63,37 +64,46 @@ export default defineComponent({
 		y: {
 			type: Number,
 			required: true
+		},
+		particle: {
+			type: Boolean,
+			required: false,
+			default: true,
 		}
 	},
 	emits: ['end'],
-	data() {
+	setup(props, context) {
 		const particles = [];
 		const origin = 64;
 		const colors = ['#FF1493', '#00FFFF', '#FFE202'];
 
-		for (let i = 0; i < 12; i++) {
-			const angle = Math.random() * (Math.PI * 2);
-			const pos = Math.random() * 16;
-			const velocity = 16 + (Math.random() * 48);
-			particles.push({
-				size: 4 + (Math.random() * 8),
-				xA: origin + (Math.sin(angle) * pos),
-				yA: origin + (Math.cos(angle) * pos),
-				xB: origin + (Math.sin(angle) * (pos + velocity)),
-				yB: origin + (Math.cos(angle) * (pos + velocity)),
-				color: colors[Math.floor(Math.random() * colors.length)]
-			});
+		if (props.particle) {
+			for (let i = 0; i < 12; i++) {
+				const angle = Math.random() * (Math.PI * 2);
+				const pos = Math.random() * 16;
+				const velocity = 16 + (Math.random() * 48);
+				particles.push({
+					size: 4 + (Math.random() * 8),
+					xA: origin + (Math.sin(angle) * pos),
+					yA: origin + (Math.cos(angle) * pos),
+					xB: origin + (Math.sin(angle) * (pos + velocity)),
+					yB: origin + (Math.cos(angle) * (pos + velocity)),
+					color: colors[Math.floor(Math.random() * colors.length)]
+				});
+			}
 		}
 
+		onMounted(() => {
+			setTimeout(() => {
+				context.emit('end');
+			}, 1100);
+		});
+
 		return {
-			particles
+			particles,
+			zIndex: os.claimZIndex('high'),
 		};
 	},
-	mounted() {
-		setTimeout(() => {
-			this.$emit('end');
-		}, 1100);
-	}
 });
 </script>
 
@@ -101,7 +111,6 @@ export default defineComponent({
 .vswabwbm {
 	pointer-events: none;
 	position: fixed;
-	z-index: 1000000;
 	width: 128px;
 	height: 128px;
 
diff --git a/packages/client/src/components/signup.vue b/packages/client/src/components/signup.vue
index 8668d1d07..38a9fd55f 100644
--- a/packages/client/src/components/signup.vue
+++ b/packages/client/src/components/signup.vue
@@ -51,14 +51,13 @@
 				<span v-if="passwordRetypeState == 'not-match'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.passwordNotMatched }}</span>
 			</template>
 		</MkInput>
-		<label v-if="meta.tosUrl" class="_formBlock tou">
-			<input v-model="ToSAgreement" type="checkbox">
+		<MkSwitch v-if="meta.tosUrl" v-model="ToSAgreement" class="_formBlock tou">
 			<I18n :src="$ts.agreeTo">
 				<template #0>
 					<a :href="meta.tosUrl" class="_link" target="_blank">{{ $ts.tos }}</a>
 				</template>
 			</I18n>
-		</label>
+		</MkSwitch>
 		<captcha v-if="meta.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" class="_formBlock captcha" provider="hcaptcha" :sitekey="meta.hcaptchaSiteKey"/>
 		<captcha v-if="meta.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" class="_formBlock captcha" provider="recaptcha" :sitekey="meta.recaptchaSiteKey"/>
 		<MkButton class="_formBlock" type="submit" :disabled="shouldDisableSubmitting" gradate data-cy-signup-submit>{{ $ts.start }}</MkButton>
@@ -258,11 +257,5 @@ export default defineComponent({
 	.captcha {
 		margin: 16px 0;
 	}
-
-	> .tou {
-		display: block;
-		margin: 16px 0;
-		cursor: pointer;
-	}
 }
 </style>
diff --git a/packages/client/src/components/sparkle.vue b/packages/client/src/components/sparkle.vue
index 21b57f158..f52e5a3f9 100644
--- a/packages/client/src/components/sparkle.vue
+++ b/packages/client/src/components/sparkle.vue
@@ -1,161 +1,121 @@
 <template>
 <span class="mk-sparkle">
-	<span ref="content">
+	<span ref="el">
 		<slot></slot>
 	</span>
-	<canvas ref="canvas"></canvas>
+	<!-- なぜか path に対する key が機能しないため
+	<svg :width="width" :height="height" :viewBox="`0 0 ${width} ${height}`" xmlns="http://www.w3.org/2000/svg">
+		<path v-for="particle in particles" :key="particle.id" style="transform-origin: center; transform-box: fill-box;"
+			:transform="`translate(${particle.x} ${particle.y})`"
+			:fill="particle.color"
+			d="M29.427,2.011C29.721,0.83 30.782,0 32,0C33.218,0 34.279,0.83 34.573,2.011L39.455,21.646C39.629,22.347 39.991,22.987 40.502,23.498C41.013,24.009 41.653,24.371 42.354,24.545L61.989,29.427C63.17,29.721 64,30.782 64,32C64,33.218 63.17,34.279 61.989,34.573L42.354,39.455C41.653,39.629 41.013,39.991 40.502,40.502C39.991,41.013 39.629,41.653 39.455,42.354L34.573,61.989C34.279,63.17 33.218,64 32,64C30.782,64 29.721,63.17 29.427,61.989L24.545,42.354C24.371,41.653 24.009,41.013 23.498,40.502C22.987,39.991 22.347,39.629 21.646,39.455L2.011,34.573C0.83,34.279 0,33.218 0,32C0,30.782 0.83,29.721 2.011,29.427L21.646,24.545C22.347,24.371 22.987,24.009 23.498,23.498C24.009,22.987 24.371,22.347 24.545,21.646L29.427,2.011Z"
+		>
+			<animateTransform
+				attributeName="transform"
+				attributeType="XML"
+				type="rotate"
+				from="0 0 0"
+				to="360 0 0"
+				:dur="`${particle.dur}ms`"
+				repeatCount="indefinite"
+				additive="sum"
+			/>
+			<animateTransform
+				attributeName="transform"
+				attributeType="XML"
+				type="scale"
+				:values="`0; ${particle.size}; 0`"
+				:dur="`${particle.dur}ms`"
+				repeatCount="indefinite"
+				additive="sum"
+			/>
+		</path>
+	</svg>
+	-->
+	<svg v-for="particle in particles" :key="particle.id" :width="width" :height="height" :viewBox="`0 0 ${width} ${height}`" xmlns="http://www.w3.org/2000/svg">
+		<path style="transform-origin: center; transform-box: fill-box;"
+			:transform="`translate(${particle.x} ${particle.y})`"
+			:fill="particle.color"
+			d="M29.427,2.011C29.721,0.83 30.782,0 32,0C33.218,0 34.279,0.83 34.573,2.011L39.455,21.646C39.629,22.347 39.991,22.987 40.502,23.498C41.013,24.009 41.653,24.371 42.354,24.545L61.989,29.427C63.17,29.721 64,30.782 64,32C64,33.218 63.17,34.279 61.989,34.573L42.354,39.455C41.653,39.629 41.013,39.991 40.502,40.502C39.991,41.013 39.629,41.653 39.455,42.354L34.573,61.989C34.279,63.17 33.218,64 32,64C30.782,64 29.721,63.17 29.427,61.989L24.545,42.354C24.371,41.653 24.009,41.013 23.498,40.502C22.987,39.991 22.347,39.629 21.646,39.455L2.011,34.573C0.83,34.279 0,33.218 0,32C0,30.782 0.83,29.721 2.011,29.427L21.646,24.545C22.347,24.371 22.987,24.009 23.498,23.498C24.009,22.987 24.371,22.347 24.545,21.646L29.427,2.011Z"
+		>
+			<animateTransform
+				attributeName="transform"
+				attributeType="XML"
+				type="rotate"
+				from="0 0 0"
+				to="360 0 0"
+				:dur="`${particle.dur}ms`"
+				repeatCount="1"
+				additive="sum"
+			/>
+			<animateTransform
+				attributeName="transform"
+				attributeType="XML"
+				type="scale"
+				:values="`0; ${particle.size}; 0`"
+				:dur="`${particle.dur}ms`"
+				repeatCount="1"
+				additive="sum"
+			/>
+		</path>
+	</svg>
 </span>
 </template>
 
 <script lang="ts">
-import { defineComponent } from 'vue';
+import { defineComponent, onMounted, onUnmounted, ref } from 'vue';
 import * as os from '@/os';
 
-const sprite = new Image();
-sprite.src = '/client-assets/sparkle-spritesheet.png';
-
 export default defineComponent({
-	props: {
-		count: {
-			type: Number,
-			required: true,
-		},
-		speed: {
-			type: Number,
-			required: true,
-		},
-	},
-	data() {
-		return {
-			sprites: [0,6,13,20],
-			particles: [],
-			anim: null,
-			ctx: null,
-		};
-	},
-	unmounted() {
-		window.cancelAnimationFrame(this.anim);
-	},
-	mounted() {
-		this.ctx = this.$refs.canvas.getContext('2d');
+	setup() {
+		const particles = ref([]);
+		const el = ref<HTMLElement>();
+		const width = ref(0);
+		const height = ref(0);
+		const colors = ['#FF1493', '#00FFFF', '#FFE202', '#FFE202', '#FFE202'];
 
-		new ResizeObserver(this.resize).observe(this.$refs.content);
-
-		this.resize();
-		this.tick();
-	},
-	updated() {
-		this.resize();
-	},
-	methods: {
-		createSparkles(w, h, count) {
-			const holder = [];
-
-			for (let i = 0; i < count; i++) {
-
-				const color = '#' + ('000000' + Math.floor(Math.random() * 16777215).toString(16)).slice(-6);
-
-				holder[i] = {
-					position: {
-						x: Math.floor(Math.random() * w),
-						y: Math.floor(Math.random() * h)
-					},
-					style: this.sprites[ Math.floor(Math.random() * 4) ],
-					delta: {
-						x: Math.floor(Math.random() * 1000) - 500,
-						y: Math.floor(Math.random() * 1000) - 500
-					},
-					color: color,
-					opacity: Math.random(),
+		onMounted(() => {
+			const ro = new ResizeObserver((entries, observer) => {
+				width.value = el.value?.offsetWidth + 64;
+				height.value = el.value?.offsetHeight + 64;
+			});
+			ro.observe(el.value);
+			let stop = false;
+			const add = () => {
+				if (stop) return;
+				const x = (Math.random() * (width.value - 64));
+				const y = (Math.random() * (height.value - 64));
+				const sizeFactor = Math.random();
+				const particle = {
+					id: Math.random().toString(),
+					x,
+					y,
+					size: 0.2 + ((sizeFactor / 10) * 3),
+					dur: 1000 + (sizeFactor * 1000),
+					color: colors[Math.floor(Math.random() * colors.length)],
 				};
+				particles.value.push(particle);
+				window.setTimeout(() => {
+					particles.value = particles.value.filter(x => x.id !== particle.id);
+				}, particle.dur - 100);
 
-			}
-
-			return holder;
-		},
-		draw(time) {
-			this.ctx.clearRect(0, 0, this.$refs.canvas.width, this.$refs.canvas.height);
-			this.ctx.beginPath();
-
-			const particleSize = Math.floor(this.fontSize / 2);
-			this.particles.forEach((particle) => {
-				var modulus = Math.floor(Math.random()*7);
-
-				if (Math.floor(time) % modulus === 0) {
-					particle.style = this.sprites[ Math.floor(Math.random()*4) ];
-				}
-
-				this.ctx.save();
-				this.ctx.globalAlpha = particle.opacity;
-				this.ctx.drawImage(sprite, particle.style, 0, 7, 7, particle.position.x, particle.position.y, particleSize, particleSize);
-
-				this.ctx.globalCompositeOperation = "source-atop";
-				this.ctx.globalAlpha = 0.5;
-				this.ctx.fillStyle = particle.color;
-				this.ctx.fillRect(particle.position.x, particle.position.y, particleSize, particleSize);
-
-				this.ctx.restore();
+				window.setTimeout(() => {
+					add();
+				}, 500 + (Math.random() * 500));
+			};
+			add();
+			onUnmounted(() => {
+				ro.disconnect();
+				stop = true;
 			});
-			this.ctx.stroke();
-		},
-		tick() {
-			this.anim = window.requestAnimationFrame((time) => {
-				if (!this.$refs.canvas) {
-					return;
-				}
-				this.particles.forEach((particle) => {
-					if (!particle) {
-						return;
-					}
-					var randX = Math.random() > Math.random() * 2;
-					var randY = Math.random() > Math.random() * 3;
+		});
 
-					if (randX) {
-						particle.position.x += (particle.delta.x * this.speed) / 1500;
-					}
-
-					if (!randY) {
-						particle.position.y -= (particle.delta.y * this.speed) / 800;
-					}
-
-					if( particle.position.x > this.$refs.canvas.width ) {
-						particle.position.x = -7;
-					} else if (particle.position.x < -7) {
-						particle.position.x = this.$refs.canvas.width;
-					}
-
-					if (particle.position.y > this.$refs.canvas.height) {
-						particle.position.y = -7;
-						particle.position.x = Math.floor(Math.random() * this.$refs.canvas.width);
-					} else if (particle.position.y < -7) {
-						particle.position.y = this.$refs.canvas.height;
-						particle.position.x = Math.floor(Math.random() * this.$refs.canvas.width);
-					}
-
-					particle.opacity -= 0.005;
-
-					if (particle.opacity <= 0) {
-						particle.opacity = 1;
-					}
-				});
-
-				this.draw(time);
-
-				this.tick();
-			});
-		},
-		resize() {
-			if (this.$refs.content) {
-				const contentRect = this.$refs.content.getBoundingClientRect();
-				this.fontSize = parseFloat(getComputedStyle(this.$refs.content).fontSize);
-				const padding = this.fontSize * 0.2;
-
-				this.$refs.canvas.width = parseInt(contentRect.width + padding);
-				this.$refs.canvas.height = parseInt(contentRect.height + padding);
-
-				this.particles = this.createSparkles(this.$refs.canvas.width, this.$refs.canvas.height, this.count);
-			}
-		},
+		return {
+			el,
+			width,
+			height,
+			particles,
+		};
 	},
 });
 </script>
@@ -169,10 +129,10 @@ export default defineComponent({
 		display: inline-block;
 	}
 
-	> canvas {
+	> svg {
 		position: absolute;
-		top: -0.1em;
-		left: -0.1em;
+		top: -32px;
+		left: -32px;
 		pointer-events: none;
 	}
 }
diff --git a/packages/client/src/components/ui/menu.vue b/packages/client/src/components/ui/menu.vue
index 869709cf2..6f3f277b1 100644
--- a/packages/client/src/components/ui/menu.vue
+++ b/packages/client/src/components/ui/menu.vue
@@ -284,7 +284,7 @@ export default defineComponent({
 	}
 
 	&.asDrawer {
-		padding: 12px 0;
+		padding: 12px 0 calc(env(safe-area-inset-bottom, 0px) + 12px) 0;
 		width: 100%;
 
 		> .item {
diff --git a/packages/client/src/components/ui/modal.vue b/packages/client/src/components/ui/modal.vue
index b09d04c45..3e2e59b27 100644
--- a/packages/client/src/components/ui/modal.vue
+++ b/packages/client/src/components/ui/modal.vue
@@ -13,6 +13,7 @@
 import { defineComponent, nextTick, onMounted, computed, PropType, ref, watch } from 'vue';
 import * as os from '@/os';
 import { isTouchUsing } from '@/scripts/touch';
+import { defaultStore } from '@/store';
 
 function getFixedContainer(el: Element | null): Element | null {
 	if (el == null || el.tagName === 'BODY') return null;
@@ -77,7 +78,7 @@ export default defineComponent({
 		const zIndex = os.claimZIndex(props.zPriority);
 		const type = computed(() => {
 			if (props.preferType === 'auto') {
-				if (isTouchUsing && window.innerWidth < 500 && window.innerHeight < 1000) {
+				if (!defaultStore.state.disableDrawer && isTouchUsing && window.innerWidth < 500 && window.innerHeight < 1000) {
 					return 'drawer';
 				} else {
 					return props.src != null ? 'popup' : 'dialog';
diff --git a/packages/client/src/components/ui/pagination.vue b/packages/client/src/components/ui/pagination.vue
index 00200efd3..64af4a54f 100644
--- a/packages/client/src/components/ui/pagination.vue
+++ b/packages/client/src/components/ui/pagination.vue
@@ -5,7 +5,12 @@
 	<MkError v-else-if="error" @retry="init()"/>
 
 	<div v-else-if="empty" key="_empty_" class="empty">
-		<slot name="empty"></slot>
+		<slot name="empty">
+			<div class="_fullinfo">
+				<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
+				<div>{{ $ts.nothing }}</div>
+			</div>
+		</slot>
 	</div>
 
 	<div v-else class="cxiknjgy">
diff --git a/packages/client/src/components/ui/window.vue b/packages/client/src/components/ui/window.vue
index d01498d8d..bd33289cc 100644
--- a/packages/client/src/components/ui/window.vue
+++ b/packages/client/src/components/ui/window.vue
@@ -414,6 +414,10 @@ export default defineComponent({
 				}
 			}
 
+			> .left {
+				min-width: 16px;
+			}
+
 			> .title {
 				flex: 1;
 				position: relative;
@@ -421,7 +425,6 @@ export default defineComponent({
 				white-space: nowrap;
 				overflow: hidden;
 				text-overflow: ellipsis;
-				text-align: center;
 				cursor: move;
 			}
 		}
diff --git a/packages/client/src/components/updated.vue b/packages/client/src/components/updated.vue
index 74f54524b..375ac0dbb 100644
--- a/packages/client/src/components/updated.vue
+++ b/packages/client/src/components/updated.vue
@@ -1,7 +1,7 @@
 <template>
 <MkModal ref="modal" :z-priority="'middle'" @click="$refs.modal.close()" @closed="$emit('closed')">
 	<div class="ewlycnyt">
-		<div class="title">{{ $ts.misskeyUpdated }}</div>
+		<div class="title"><MkSparkle>{{ $ts.misskeyUpdated }}</MkSparkle></div>
 		<div class="version">✨{{ version }}🚀</div>
 		<MkButton full @click="whatIsNew">{{ $ts.whatIsNew }}</MkButton>
 		<MkButton class="gotIt" primary full @click="$refs.modal.close()">{{ $ts.gotIt }}</MkButton>
@@ -9,31 +9,19 @@
 </MkModal>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { ref } from 'vue';
 import MkModal from '@/components/ui/modal.vue';
 import MkButton from '@/components/ui/button.vue';
+import MkSparkle from '@/components/sparkle.vue';
 import { version } from '@/config';
 
-export default defineComponent({
-	components: {
-		MkModal,
-		MkButton,
-	},
+const modal = ref();
 
-	data() {
-		return {
-			version: version,
-		};
-	},
-
-	methods: {
-		whatIsNew() {
-			this.$refs.modal.close();
-			window.open(`https://misskey-hub.net/docs/releases.html#_${version.replace(/\./g, '-')}`, '_blank');
-		}
-	}
-});
+const whatIsNew = () => {
+	modal.value.close();
+	window.open(`https://misskey-hub.net/docs/releases.html#_${version.replace(/\./g, '-')}`, '_blank');
+};
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/client/src/directives/adaptive-border.ts b/packages/client/src/directives/adaptive-border.ts
new file mode 100644
index 000000000..fc426ca2c
--- /dev/null
+++ b/packages/client/src/directives/adaptive-border.ts
@@ -0,0 +1,24 @@
+import { Directive } from 'vue';
+
+export default {
+	mounted(src, binding, vn) {
+		const getBgColor = (el: HTMLElement) => {
+			const style = window.getComputedStyle(el);
+			if (style.backgroundColor && !['rgba(0, 0, 0, 0)', 'rgba(0,0,0,0)', 'transparent'].includes(style.backgroundColor)) {
+				return style.backgroundColor;
+			} else {
+				return el.parentElement ? getBgColor(el.parentElement) : 'transparent';
+			}
+		}
+	
+		const parentBg = getBgColor(src.parentElement);
+
+		const myBg = window.getComputedStyle(src).backgroundColor;
+
+		if (parentBg === myBg) {
+			src.style.borderColor = 'var(--divider)';
+		} else {
+			src.style.borderColor = myBg;
+		}
+	},
+} as Directive;
diff --git a/packages/client/src/directives/index.ts b/packages/client/src/directives/index.ts
index 4b11bb621..fc9b6f86d 100644
--- a/packages/client/src/directives/index.ts
+++ b/packages/client/src/directives/index.ts
@@ -3,7 +3,7 @@ import { App } from 'vue';
 import userPreview from './user-preview';
 import size from './size';
 import getSize from './get-size';
-import particle from './particle';
+import ripple from './ripple';
 import tooltip from './tooltip';
 import hotkey from './hotkey';
 import appear from './appear';
@@ -11,13 +11,14 @@ import anim from './anim';
 import stickyContainer from './sticky-container';
 import clickAnime from './click-anime';
 import panel from './panel';
+import adaptiveBorder from './adaptive-border';
 
 export default function(app: App) {
 	app.directive('userPreview', userPreview);
 	app.directive('user-preview', userPreview);
 	app.directive('size', size);
 	app.directive('get-size', getSize);
-	app.directive('particle', particle);
+	app.directive('ripple', ripple);
 	app.directive('tooltip', tooltip);
 	app.directive('hotkey', hotkey);
 	app.directive('appear', appear);
@@ -25,4 +26,5 @@ export default function(app: App) {
 	app.directive('click-anime', clickAnime);
 	app.directive('sticky-container', stickyContainer);
 	app.directive('panel', panel);
+	app.directive('adaptive-border', adaptiveBorder);
 }
diff --git a/packages/client/src/directives/panel.ts b/packages/client/src/directives/panel.ts
index 86ec2a911..5f9158db2 100644
--- a/packages/client/src/directives/panel.ts
+++ b/packages/client/src/directives/panel.ts
@@ -7,7 +7,7 @@ export default {
 			if (style.backgroundColor && !['rgba(0, 0, 0, 0)', 'rgba(0,0,0,0)', 'transparent'].includes(style.backgroundColor)) {
 				return style.backgroundColor;
 			} else {
-				return getBgColor(el.parentElement);
+				return el.parentElement ? getBgColor(el.parentElement) : 'transparent';
 			}
 		}
 	
diff --git a/packages/client/src/directives/particle.ts b/packages/client/src/directives/ripple.ts
similarity index 59%
rename from packages/client/src/directives/particle.ts
rename to packages/client/src/directives/ripple.ts
index c90df89a5..f1d41ddb0 100644
--- a/packages/client/src/directives/particle.ts
+++ b/packages/client/src/directives/ripple.ts
@@ -1,4 +1,4 @@
-import Particle from '@/components/particle.vue';
+import Ripple from '@/components/ripple.vue';
 import { popup } from '@/os';
 
 export default {
@@ -9,10 +9,10 @@ export default {
 		el.addEventListener('click', () => {
 			const rect = el.getBoundingClientRect();
 
-			const x = rect.left + (el.clientWidth / 2);
-			const y = rect.top + (el.clientHeight / 2);
+			const x = rect.left + (el.offsetWidth / 2);
+			const y = rect.top + (el.offsetHeight / 2);
 
-			popup(Particle, { x, y }, {}, 'end');
+			popup(Ripple, { x, y }, {}, 'end');
 		});
 	}
 };
diff --git a/packages/client/src/pages/admin/file-dialog.vue b/packages/client/src/pages/admin/file-dialog.vue
index f0774b294..4c33f6239 100644
--- a/packages/client/src/pages/admin/file-dialog.vue
+++ b/packages/client/src/pages/admin/file-dialog.vue
@@ -40,7 +40,6 @@ import MkButton from '@/components/ui/button.vue';
 import MkSwitch from '@/components/form/switch.vue';
 import XModalWindow from '@/components/ui/modal-window.vue';
 import MkDriveFileThumbnail from '@/components/drive-file-thumbnail.vue';
-import Progress from '@/scripts/loading';
 import bytes from '@/filters/bytes';
 import * as os from '@/os';
 
@@ -74,11 +73,9 @@ export default defineComponent({
 
 	methods: {
 		async fetch() {
-			Progress.start();
 			this.file = await os.api('drive/files/show', { fileId: this.fileId });
 			this.info = await os.api('admin/drive/show-file', { fileId: this.fileId });
 			this.isSensitive = this.file.isSensitive;
-			Progress.done();
 		},
 
 		showUser() {
diff --git a/packages/client/src/pages/antenna-timeline.vue b/packages/client/src/pages/antenna-timeline.vue
index 2738208c9..c38f28572 100644
--- a/packages/client/src/pages/antenna-timeline.vue
+++ b/packages/client/src/pages/antenna-timeline.vue
@@ -7,8 +7,6 @@
 			src="antenna"
 			:antenna="antennaId"
 			:sound="true"
-			@before="before()"
-			@after="after()"
 			@queue="queueUpdated"
 		/>
 	</div>
@@ -17,7 +15,6 @@
 
 <script lang="ts">
 import { defineComponent, defineAsyncComponent, computed } from 'vue';
-import Progress from '@/scripts/loading';
 import XTimeline from '@/components/timeline.vue';
 import { scroll } from '@/scripts/scroll';
 import * as os from '@/os';
@@ -76,14 +73,6 @@ export default defineComponent({
 	},
 
 	methods: {
-		before() {
-			Progress.start();
-		},
-
-		after() {
-			Progress.done();
-		},
-
 		queueUpdated(q) {
 			this.queue = q;
 		},
diff --git a/packages/client/src/pages/channel-editor.vue b/packages/client/src/pages/channel-editor.vue
index 67b839bc3..58c644be6 100644
--- a/packages/client/src/pages/channel-editor.vue
+++ b/packages/client/src/pages/channel-editor.vue
@@ -1,28 +1,26 @@
 <template>
-<div>
-	<div class="_section">
-		<div class="_content">
-			<MkInput v-model="name">
-				<template #label>{{ $ts.name }}</template>
-			</MkInput>
+<MkSpacer :content-max="700">
+	<div class="_formRoot">
+		<MkInput v-model="name" class="_formBlock">
+			<template #label>{{ $ts.name }}</template>
+		</MkInput>
 
-			<MkTextarea v-model="description">
-				<template #label>{{ $ts.description }}</template>
-			</MkTextarea>
+		<MkTextarea v-model="description" class="_formBlock">
+			<template #label>{{ $ts.description }}</template>
+		</MkTextarea>
 
-			<div class="banner">
-				<MkButton v-if="bannerId == null" @click="setBannerImage"><i class="fas fa-plus"></i> {{ $ts._channel.setBanner }}</MkButton>
-				<div v-else-if="bannerUrl">
-					<img :src="bannerUrl" style="width: 100%;"/>
-					<MkButton @click="removeBannerImage()"><i class="fas fa-trash-alt"></i> {{ $ts._channel.removeBanner }}</MkButton>
-				</div>
+		<div class="banner">
+			<MkButton v-if="bannerId == null" @click="setBannerImage"><i class="fas fa-plus"></i> {{ $ts._channel.setBanner }}</MkButton>
+			<div v-else-if="bannerUrl">
+				<img :src="bannerUrl" style="width: 100%;"/>
+				<MkButton @click="removeBannerImage()"><i class="fas fa-trash-alt"></i> {{ $ts._channel.removeBanner }}</MkButton>
 			</div>
 		</div>
-		<div class="_footer">
+		<div class="_formBlock">
 			<MkButton primary @click="save()"><i class="fas fa-save"></i> {{ channelId ? $ts.save : $ts.create }}</MkButton>
 		</div>
 	</div>
-</div>
+</MkSpacer>
 </template>
 
 <script lang="ts">
@@ -51,9 +49,11 @@ export default defineComponent({
 			[symbols.PAGE_INFO]: computed(() => this.channelId ? {
 				title: this.$ts._channel.edit,
 				icon: 'fas fa-satellite-dish',
+				bg: 'var(--bg)',
 			} : {
 				title: this.$ts._channel.create,
 				icon: 'fas fa-satellite-dish',
+				bg: 'var(--bg)',
 			}),
 			channel: null,
 			name: null,
diff --git a/packages/client/src/pages/channel.vue b/packages/client/src/pages/channel.vue
index a328eacb0..67ab2d898 100644
--- a/packages/client/src/pages/channel.vue
+++ b/packages/client/src/pages/channel.vue
@@ -1,29 +1,31 @@
 <template>
-<div v-if="channel" class="_section">
-	<div class="wpgynlbz _content _panel _gap" :class="{ hide: !showBanner }">
-		<XChannelFollowButton :channel="channel" :full="true" class="subscribe"/>
-		<button class="_button toggle" @click="() => showBanner = !showBanner">
-			<template v-if="showBanner"><i class="fas fa-angle-up"></i></template>
-			<template v-else><i class="fas fa-angle-down"></i></template>
-		</button>
-		<div v-if="!showBanner" class="hideOverlay">
-		</div>
-		<div :style="{ backgroundImage: channel.bannerUrl ? `url(${channel.bannerUrl})` : null }" class="banner">
-			<div class="status">
-				<div><i class="fas fa-users fa-fw"></i><I18n :src="$ts._channel.usersCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.usersCount }}</b></template></I18n></div>
-				<div><i class="fas fa-pencil-alt fa-fw"></i><I18n :src="$ts._channel.notesCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.notesCount }}</b></template></I18n></div>
+<MkSpacer :content-max="700">
+	<div v-if="channel">
+		<div class="wpgynlbz _panel _gap" :class="{ hide: !showBanner }">
+			<XChannelFollowButton :channel="channel" :full="true" class="subscribe"/>
+			<button class="_button toggle" @click="() => showBanner = !showBanner">
+				<template v-if="showBanner"><i class="fas fa-angle-up"></i></template>
+				<template v-else><i class="fas fa-angle-down"></i></template>
+			</button>
+			<div v-if="!showBanner" class="hideOverlay">
+			</div>
+			<div :style="{ backgroundImage: channel.bannerUrl ? `url(${channel.bannerUrl})` : null }" class="banner">
+				<div class="status">
+					<div><i class="fas fa-users fa-fw"></i><I18n :src="$ts._channel.usersCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.usersCount }}</b></template></I18n></div>
+					<div><i class="fas fa-pencil-alt fa-fw"></i><I18n :src="$ts._channel.notesCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.notesCount }}</b></template></I18n></div>
+				</div>
+				<div class="fade"></div>
+			</div>
+			<div v-if="channel.description" class="description">
+				<Mfm :text="channel.description" :is-note="false" :i="$i"/>
 			</div>
-			<div class="fade"></div>
-		</div>
-		<div v-if="channel.description" class="description">
-			<Mfm :text="channel.description" :is-note="false" :i="$i"/>
 		</div>
+
+		<XPostForm v-if="$i" :channel="channel" class="post-form _panel _gap" fixed/>
+
+		<XTimeline :key="channelId" class="_gap" src="channel" :channel="channelId" @before="before" @after="after"/>
 	</div>
-
-	<XPostForm v-if="$i" :channel="channel" class="post-form _content _panel _gap" fixed/>
-
-	<XTimeline :key="channelId" class="_content _gap" src="channel" :channel="channelId" @before="before" @after="after"/>
-</div>
+</MkSpacer>
 </template>
 
 <script lang="ts">
@@ -55,6 +57,12 @@ export default defineComponent({
 			[symbols.PAGE_INFO]: computed(() => this.channel ? {
 				title: this.channel.name,
 				icon: 'fas fa-satellite-dish',
+				bg: 'var(--bg)',
+				actions: [...(this.$i && this.$i.id === this.channel.userId ? [{
+					icon: 'fas fa-cog',
+					text: this.$ts.edit,
+					handler: this.edit,
+				}] : [])],
 			} : null),
 			channel: null,
 			showBanner: true,
@@ -79,8 +87,10 @@ export default defineComponent({
 		}
 	},
 
-	created() {
-
+	methods: {
+		edit() {
+			this.$router.push(`/channels/${this.channel.id}/edit`);
+		}
 	},
 });
 </script>
diff --git a/packages/client/src/pages/channels.vue b/packages/client/src/pages/channels.vue
index a08c27327..48877ab3e 100644
--- a/packages/client/src/pages/channels.vue
+++ b/packages/client/src/pages/channels.vue
@@ -1,58 +1,63 @@
 <template>
-<div>
-	<div v-if="$i" class="_section" style="padding: 0;">
-		<MkTab v-model="tab" class="_content">
-			<option value="featured"><i class="fas fa-fire-alt"></i> {{ $ts._channel.featured }}</option>
-			<option value="following"><i class="fas fa-heart"></i> {{ $ts._channel.following }}</option>
-			<option value="owned"><i class="fas fa-edit"></i> {{ $ts._channel.owned }}</option>
-		</MkTab>
+<MkSpacer :content-max="700">
+	<div v-if="tab === 'featured'" class="_content grwlizim featured">
+		<MkPagination v-slot="{items}" :pagination="featuredPagination">
+			<MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/>
+		</MkPagination>
 	</div>
-
-	<div class="_section">
-		<div v-if="tab === 'featured'" class="_content grwlizim featured">
-			<MkPagination v-slot="{items}" :pagination="featuredPagination">
-				<MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/>
-			</MkPagination>
-		</div>
-
-		<div v-if="tab === 'following'" class="_content grwlizim following">
-			<MkPagination v-slot="{items}" :pagination="followingPagination">
-				<MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/>
-			</MkPagination>
-		</div>
-
-		<div v-if="tab === 'owned'" class="_content grwlizim owned">
-			<MkButton class="new" @click="create()"><i class="fas fa-plus"></i></MkButton>
-			<MkPagination v-slot="{items}" :pagination="ownedPagination">
-				<MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/>
-			</MkPagination>
-		</div>
+	<div v-else-if="tab === 'following'" class="_content grwlizim following">
+		<MkPagination v-slot="{items}" :pagination="followingPagination">
+			<MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/>
+		</MkPagination>
 	</div>
-</div>
+	<div v-else-if="tab === 'owned'" class="_content grwlizim owned">
+		<MkButton class="new" @click="create()"><i class="fas fa-plus"></i></MkButton>
+		<MkPagination v-slot="{items}" :pagination="ownedPagination">
+			<MkChannelPreview v-for="channel in items" :key="channel.id" class="_gap" :channel="channel"/>
+		</MkPagination>
+	</div>
+</MkSpacer>
 </template>
 
 <script lang="ts">
-import { defineComponent } from 'vue';
+import { computed, defineComponent } from 'vue';
 import MkChannelPreview from '@/components/channel-preview.vue';
 import MkPagination from '@/components/ui/pagination.vue';
 import MkButton from '@/components/ui/button.vue';
-import MkTab from '@/components/tab.vue';
 import * as symbols from '@/symbols';
 
 export default defineComponent({
 	components: {
-		MkChannelPreview, MkPagination, MkButton, MkTab
+		MkChannelPreview, MkPagination, MkButton,
 	},
 	data() {
 		return {
-			[symbols.PAGE_INFO]: {
+			[symbols.PAGE_INFO]: computed(() => ({
 				title: this.$ts.channel,
 				icon: 'fas fa-satellite-dish',
-				action: {
+				bg: 'var(--bg)',
+				actions: [{
 					icon: 'fas fa-plus',
-					handler: this.create
-				}
-			},
+					text: this.$ts.create,
+					handler: this.create,
+				}],
+				tabs: [{
+					active: this.tab === 'featured',
+					title: this.$ts._channel.featured,
+					icon: 'fas fa-fire-alt',
+					onClick: () => { this.tab = 'featured'; },
+				}, {
+					active: this.tab === 'following',
+					title: this.$ts._channel.following,
+					icon: 'fas fa-heart',
+					onClick: () => { this.tab = 'following'; },
+				}, {
+					active: this.tab === 'owned',
+					title: this.$ts._channel.owned,
+					icon: 'fas fa-edit',
+					onClick: () => { this.tab = 'owned'; },
+				},]
+			})),
 			tab: 'featured',
 			featuredPagination: {
 				endpoint: 'channels/featured',
diff --git a/packages/client/src/pages/favorites.vue b/packages/client/src/pages/favorites.vue
index 980d59835..faab86474 100644
--- a/packages/client/src/pages/favorites.vue
+++ b/packages/client/src/pages/favorites.vue
@@ -1,14 +1,13 @@
 <template>
 <div class="jmelgwjh">
 	<div class="body">
-		<XNotes class="notes" :pagination="pagination" :detail="true" :prop="'note'" @before="before()" @after="after()"/>
+		<XNotes class="notes" :pagination="pagination" :detail="true" :prop="'note'"/>
 	</div>
 </div>
 </template>
 
 <script lang="ts">
 import { defineComponent } from 'vue';
-import Progress from '@/scripts/loading';
 import XNotes from '@/components/notes.vue';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
@@ -33,16 +32,6 @@ export default defineComponent({
 			},
 		};
 	},
-
-	methods: {
-		before() {
-			Progress.start();
-		},
-
-		after() {
-			Progress.done();
-		}
-	}
 });
 </script>
 
diff --git a/packages/client/src/pages/featured.vue b/packages/client/src/pages/featured.vue
index f5edf2559..0844c0952 100644
--- a/packages/client/src/pages/featured.vue
+++ b/packages/client/src/pages/featured.vue
@@ -1,12 +1,11 @@
 <template>
 <MkSpacer :content-max="800">
-	<XNotes ref="notes" :pagination="pagination" @before="before" @after="after"/>
+	<XNotes ref="notes" :pagination="pagination"/>
 </MkSpacer>
 </template>
 
 <script lang="ts">
 import { defineComponent } from 'vue';
-import Progress from '@/scripts/loading';
 import XNotes from '@/components/notes.vue';
 import * as symbols from '@/symbols';
 
@@ -29,15 +28,5 @@ export default defineComponent({
 			},
 		};
 	},
-
-	methods: {
-		before() {
-			Progress.start();
-		},
-
-		after() {
-			Progress.done();
-		}
-	}
 });
 </script>
diff --git a/packages/client/src/pages/mentions.vue b/packages/client/src/pages/mentions.vue
index cd9c6a8fd..691d3bd9a 100644
--- a/packages/client/src/pages/mentions.vue
+++ b/packages/client/src/pages/mentions.vue
@@ -1,12 +1,11 @@
 <template>
 <MkSpacer :content-max="800">
-	<XNotes :pagination="pagination" @before="before()" @after="after()"/>
+	<XNotes :pagination="pagination"/>
 </MkSpacer>
 </template>
 
 <script lang="ts">
 import { defineComponent } from 'vue';
-import Progress from '@/scripts/loading';
 import XNotes from '@/components/notes.vue';
 import * as symbols from '@/symbols';
 
@@ -28,15 +27,5 @@ export default defineComponent({
 			},
 		};
 	},
-
-	methods: {
-		before() {
-			Progress.start();
-		},
-
-		after() {
-			Progress.done();
-		}
-	}
 });
 </script>
diff --git a/packages/client/src/pages/messages.vue b/packages/client/src/pages/messages.vue
index 9fde0bc7d..9085af948 100644
--- a/packages/client/src/pages/messages.vue
+++ b/packages/client/src/pages/messages.vue
@@ -1,12 +1,11 @@
 <template>
 <MkSpacer :content-max="800">
-	<XNotes :pagination="pagination" @before="before()" @after="after()"/>
+	<XNotes :pagination="pagination"/>
 </MkSpacer>
 </template>
 
 <script lang="ts">
 import { defineComponent } from 'vue';
-import Progress from '@/scripts/loading';
 import XNotes from '@/components/notes.vue';
 import * as symbols from '@/symbols';
 
@@ -31,15 +30,5 @@ export default defineComponent({
 			},
 		};
 	},
-
-	methods: {
-		before() {
-			Progress.start();
-		},
-
-		after() {
-			Progress.done();
-		}
-	}
 });
 </script>
diff --git a/packages/client/src/pages/my-groups/group.vue b/packages/client/src/pages/my-groups/group.vue
index 89c8659b4..c307f037a 100644
--- a/packages/client/src/pages/my-groups/group.vue
+++ b/packages/client/src/pages/my-groups/group.vue
@@ -35,7 +35,6 @@
 
 <script lang="ts">
 import { computed, defineComponent } from 'vue';
-import Progress from '@/scripts/loading';
 import MkButton from '@/components/ui/button.vue';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
@@ -73,7 +72,6 @@ export default defineComponent({
 
 	methods: {
 		fetch() {
-			Progress.start();
 			os.api('users/groups/show', {
 				groupId: this.groupId
 			}).then(group => {
@@ -82,7 +80,6 @@ export default defineComponent({
 					userIds: this.group.userIds
 				}).then(users => {
 					this.users = users;
-					Progress.done();
 				});
 			});
 		},
diff --git a/packages/client/src/pages/my-lists/list.vue b/packages/client/src/pages/my-lists/list.vue
index 0bfa20514..a25522f93 100644
--- a/packages/client/src/pages/my-lists/list.vue
+++ b/packages/client/src/pages/my-lists/list.vue
@@ -36,7 +36,6 @@
 
 <script lang="ts">
 import { computed, defineComponent } from 'vue';
-import Progress from '@/scripts/loading';
 import MkButton from '@/components/ui/button.vue';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
@@ -68,7 +67,6 @@ export default defineComponent({
 
 	methods: {
 		fetch() {
-			Progress.start();
 			os.api('users/lists/show', {
 				listId: this.$route.params.list
 			}).then(list => {
@@ -77,7 +75,6 @@ export default defineComponent({
 					userIds: this.list.userIds
 				}).then(users => {
 					this.users = users;
-					Progress.done();
 				});
 			});
 		},
diff --git a/packages/client/src/pages/notifications.vue b/packages/client/src/pages/notifications.vue
index cf8e934b5..695c54a53 100644
--- a/packages/client/src/pages/notifications.vue
+++ b/packages/client/src/pages/notifications.vue
@@ -1,14 +1,13 @@
 <template>
 <MkSpacer :content-max="800">
 	<div class="clupoqwt">
-		<XNotifications class="notifications" :include-types="includeTypes" :unread-only="tab === 'unread'" @before="before" @after="after"/>
+		<XNotifications class="notifications" :include-types="includeTypes" :unread-only="tab === 'unread'"/>
 	</div>
 </MkSpacer>
 </template>
 
 <script lang="ts">
 import { computed, defineComponent } from 'vue';
-import Progress from '@/scripts/loading';
 import XNotifications from '@/components/notifications.vue';
 import * as os from '@/os';
 import * as symbols from '@/symbols';
@@ -53,14 +52,6 @@ export default defineComponent({
 	},
 
 	methods: {
-		before() {
-			Progress.start();
-		},
-
-		after() {
-			Progress.done();
-		},
-
 		setFilter(ev) {
 			const typeItems = notificationTypes.map(t => ({
 				text: this.$t(`_notification._types.${t}`),
diff --git a/packages/client/src/pages/search.vue b/packages/client/src/pages/search.vue
index c7da3fe1c..85d19bb25 100644
--- a/packages/client/src/pages/search.vue
+++ b/packages/client/src/pages/search.vue
@@ -1,14 +1,13 @@
 <template>
 <div class="_section">
 	<div class="_content">
-		<XNotes ref="notes" :pagination="pagination" @before="before" @after="after"/>
+		<XNotes ref="notes" :pagination="pagination"/>
 	</div>
 </div>
 </template>
 
 <script lang="ts">
 import { computed, defineComponent } from 'vue';
-import Progress from '@/scripts/loading';
 import XNotes from '@/components/notes.vue';
 import * as symbols from '@/symbols';
 
@@ -39,15 +38,5 @@ export default defineComponent({
 			(this.$refs.notes as any).reload();
 		}
 	},
-
-	methods: {
-		before() {
-			Progress.start();
-		},
-
-		after() {
-			Progress.done();
-		}
-	}
 });
 </script>
diff --git a/packages/client/src/pages/settings/general.vue b/packages/client/src/pages/settings/general.vue
index 57a4b2557..734bc7844 100644
--- a/packages/client/src/pages/settings/general.vue
+++ b/packages/client/src/pages/settings/general.vue
@@ -43,6 +43,7 @@
 		<FormSwitch v-model="useOsNativeEmojis" class="_formBlock">{{ $ts.useOsNativeEmojis }}
 			<div><Mfm :key="useOsNativeEmojis" text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></div>
 		</FormSwitch>
+		<FormSwitch v-model="disableDrawer" class="_formBlock">{{ $ts.disableDrawer }}</FormSwitch>
 
 		<FormRadios v-model="fontSize" class="_formBlock">
 			<template #label>{{ $ts.fontSize }}</template>
@@ -76,13 +77,6 @@
 		<FormSwitch v-model="defaultSideView">{{ $ts.openInSideView }}</FormSwitch>
 	</FormGroup>
 
-	<FormSelect v-model="chatOpenBehavior" class="_formBlock">
-		<template #label>{{ $ts.chatOpenBehavior }}</template>
-		<option value="page">{{ $ts.showInPage }}</option>
-		<option value="window">{{ $ts.openInWindow }}</option>
-		<option value="popout">{{ $ts.popout }}</option>
-	</FormSelect>
-
 	<FormLink to="/settings/deck" class="_formBlock">{{ $ts.deck }}</FormLink>
 
 	<FormLink to="/settings/custom-css" class="_formBlock"><template #icon><i class="fas fa-code"></i></template>{{ $ts.customCss }}</FormLink>
@@ -140,6 +134,7 @@ export default defineComponent({
 		showGapBetweenNotesInTimeline: defaultStore.makeGetterSetter('showGapBetweenNotesInTimeline'),
 		disableAnimatedMfm: defaultStore.makeGetterSetter('animatedMfm', v => !v, v => !v),
 		useOsNativeEmojis: defaultStore.makeGetterSetter('useOsNativeEmojis'),
+		disableDrawer: defaultStore.makeGetterSetter('disableDrawer'),
 		disableShowingAnimatedImages: defaultStore.makeGetterSetter('disableShowingAnimatedImages'),
 		loadRawImages: defaultStore.makeGetterSetter('loadRawImages'),
 		imageNewTab: defaultStore.makeGetterSetter('imageNewTab'),
@@ -147,7 +142,6 @@ export default defineComponent({
 		disablePagesScript: defaultStore.makeGetterSetter('disablePagesScript'),
 		showFixedPostForm: defaultStore.makeGetterSetter('showFixedPostForm'),
 		defaultSideView: defaultStore.makeGetterSetter('defaultSideView'),
-		chatOpenBehavior: ColdDeviceStorage.makeGetterSetter('chatOpenBehavior'),
 		instanceTicker: defaultStore.makeGetterSetter('instanceTicker'),
 		enableInfiniteScroll: defaultStore.makeGetterSetter('enableInfiniteScroll'),
 		useReactionPickerForContextMenu: defaultStore.makeGetterSetter('useReactionPickerForContextMenu'),
diff --git a/packages/client/src/pages/settings/theme.vue b/packages/client/src/pages/settings/theme.vue
index 357b2ab83..6c88b6569 100644
--- a/packages/client/src/pages/settings/theme.vue
+++ b/packages/client/src/pages/settings/theme.vue
@@ -1,6 +1,6 @@
 <template>
 <div class="_formRoot">
-	<div v-panel class="rfqxtzch _formBlock">
+	<div v-adaptive-border class="rfqxtzch _panel _formBlock">
 		<div class="toggle">
 			<div class="toggleWrapper">
 				<input id="dn" v-model="darkMode" type="checkbox" class="dn"/>
diff --git a/packages/client/src/pages/signup-complete.vue b/packages/client/src/pages/signup-complete.vue
index 3bbc9938d..89375e05d 100644
--- a/packages/client/src/pages/signup-complete.vue
+++ b/packages/client/src/pages/signup-complete.vue
@@ -31,12 +31,15 @@ export default defineComponent({
 		}
 	},
 
-	mounted() {
-		os.apiWithDialog('signup-pending', {
-			code: this.code,
-		}).then(res => {
-			login(res.i, '/');
+	async mounted() {
+		await os.alert({
+			type: 'info',
+			text: this.$t('clickToFinishEmailVerification', { ok: this.$ts.gotIt }),
 		});
+		const res = await os.apiWithDialog('signup-pending', {
+			code: this.code,
+		});
+		login(res.i, '/');
 	},
 
 	methods: {
diff --git a/packages/client/src/pages/tag.vue b/packages/client/src/pages/tag.vue
index f4709659e..a0c836784 100644
--- a/packages/client/src/pages/tag.vue
+++ b/packages/client/src/pages/tag.vue
@@ -1,12 +1,11 @@
 <template>
 <div class="_section">
-	<XNotes ref="notes" class="_content" :pagination="pagination" @before="before" @after="after"/>
+	<XNotes ref="notes" class="_content" :pagination="pagination"/>
 </div>
 </template>
 
 <script lang="ts">
 import { defineComponent } from 'vue';
-import Progress from '@/scripts/loading';
 import XNotes from '@/components/notes.vue';
 import * as symbols from '@/symbols';
 
@@ -43,15 +42,5 @@ export default defineComponent({
 			(this.$refs.notes as any).reload();
 		}
 	},
-
-	methods: {
-		before() {
-			Progress.start();
-		},
-
-		after() {
-			Progress.done();
-		}
-	}
 });
 </script>
diff --git a/packages/client/src/pages/timeline.vue b/packages/client/src/pages/timeline.vue
index 494932c60..216b3c34e 100644
--- a/packages/client/src/pages/timeline.vue
+++ b/packages/client/src/pages/timeline.vue
@@ -10,8 +10,6 @@
 				class="tl"
 				:src="src"
 				:sound="true"
-				@before="before()"
-				@after="after()"
 				@queue="queueUpdated"
 			/>
 		</div>
@@ -21,7 +19,6 @@
 
 <script lang="ts">
 import { defineComponent, defineAsyncComponent, computed } from 'vue';
-import Progress from '@/scripts/loading';
 import XTimeline from '@/components/timeline.vue';
 import XPostForm from '@/components/post-form.vue';
 import { scroll } from '@/scripts/scroll';
@@ -118,14 +115,6 @@ export default defineComponent({
 	},
 
 	methods: {
-		before() {
-			Progress.start();
-		},
-
-		after() {
-			Progress.done();
-		},
-
 		queueUpdated(q) {
 			this.queue = q;
 		},
diff --git a/packages/client/src/pages/user-list-timeline.vue b/packages/client/src/pages/user-list-timeline.vue
index c2970c8d0..4476567cf 100644
--- a/packages/client/src/pages/user-list-timeline.vue
+++ b/packages/client/src/pages/user-list-timeline.vue
@@ -7,8 +7,6 @@
 			src="list"
 			:list="listId"
 			:sound="true"
-			@before="before()"
-			@after="after()"
 			@queue="queueUpdated"
 		/>
 	</div>
@@ -17,7 +15,6 @@
 
 <script lang="ts">
 import { defineComponent, defineAsyncComponent, computed } from 'vue';
-import Progress from '@/scripts/loading';
 import XTimeline from '@/components/timeline.vue';
 import { scroll } from '@/scripts/scroll';
 import * as os from '@/os';
@@ -76,14 +73,6 @@ export default defineComponent({
 	},
 
 	methods: {
-		before() {
-			Progress.start();
-		},
-
-		after() {
-			Progress.done();
-		},
-
 		queueUpdated(q) {
 			this.queue = q;
 		},
diff --git a/packages/client/src/pages/user/index.vue b/packages/client/src/pages/user/index.vue
index 5a529ba8c..0b9636858 100644
--- a/packages/client/src/pages/user/index.vue
+++ b/packages/client/src/pages/user/index.vue
@@ -205,7 +205,6 @@ import MkFolder from '@/components/ui/folder.vue';
 import MkRemoteCaution from '@/components/remote-caution.vue';
 import MkTab from '@/components/tab.vue';
 import MkInfo from '@/components/ui/info.vue';
-import Progress from '@/scripts/loading';
 import * as Acct from 'misskey-js/built/acct';
 import { getScrollPosition } from '@/scripts/scroll';
 import { getUserMenu } from '@/scripts/get-user-menu';
@@ -328,13 +327,10 @@ export default defineComponent({
 		fetch() {
 			if (this.acct == null) return;
 			this.user = null;
-			Progress.start();
 			os.api('users/show', Acct.parse(this.acct)).then(user => {
 				this.user = user;
 			}).catch(e => {
 				this.error = e;
-			}).finally(() => {
-				Progress.done();
 			});
 		},
 
diff --git a/packages/client/src/scripts/loading.ts b/packages/client/src/scripts/loading.ts
deleted file mode 100644
index 4b0a560e3..000000000
--- a/packages/client/src/scripts/loading.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-export default {
-	start: () => {
-		// TODO
-	},
-	done: () => {
-		// TODO
-	},
-	set: val => {
-		// TODO
-	}
-};
diff --git a/packages/client/src/scripts/use-tooltip.ts b/packages/client/src/scripts/use-tooltip.ts
index 0df4baca7..bc8f27a03 100644
--- a/packages/client/src/scripts/use-tooltip.ts
+++ b/packages/client/src/scripts/use-tooltip.ts
@@ -1,4 +1,4 @@
-import { Ref, ref, watch } from 'vue';
+import { Ref, ref, watch, onUnmounted } from 'vue';
 
 export function useTooltip(
 	elRef: Ref<HTMLElement | { $el: HTMLElement } | null | undefined>,
@@ -18,6 +18,9 @@ export function useTooltip(
 	const open = () => {
 		close();
 		if (!isHovering) return;
+		if (elRef.value == null) return;
+		const el = elRef.value instanceof Element ? elRef.value : elRef.value.$el;
+		if (!document.body.contains(el)) return; // openしようとしたときに既に元要素がDOMから消えている場合があるため
 
 		const showing = ref(true);
 		onShow(showing);
@@ -69,9 +72,14 @@ export function useTooltip(
 			el.addEventListener('mouseleave', onMouseleave, { passive: true });
 			el.addEventListener('touchstart', onTouchstart, { passive: true });
 			el.addEventListener('touchend', onTouchend, { passive: true });
+			el.addEventListener('click', close, { passive: true });
 		}
 	}, {
 		immediate: true,
 		flush: 'post',
 	});
+
+	onUnmounted(() => {
+		close();
+	});
 }
diff --git a/packages/client/src/store.ts b/packages/client/src/store.ts
index 2290a2177..dc9c3b7b9 100644
--- a/packages/client/src/store.ts
+++ b/packages/client/src/store.ts
@@ -138,6 +138,10 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'device',
 		default: false
 	},
+	disableDrawer: {
+		where: 'device',
+		default: false
+	},
 	useBlurEffectForModal: {
 		where: 'device',
 		default: true
@@ -241,7 +245,6 @@ export class ColdDeviceStorage {
 		lightTheme: require('@/themes/l-light.json5') as Theme,
 		darkTheme: require('@/themes/d-dark.json5') as Theme,
 		syncDeviceDarkMode: true,
-		chatOpenBehavior: 'page' as 'page' | 'window' | 'popout',
 		plugins: [] as Plugin[],
 		mediaVolume: 0.5,
 		sound_masterVolume: 0.3,
diff --git a/packages/client/src/sw/compose-notification.ts b/packages/client/src/sw/compose-notification.ts
index 0aed9610e..10bd27c9e 100644
--- a/packages/client/src/sw/compose-notification.ts
+++ b/packages/client/src/sw/compose-notification.ts
@@ -3,7 +3,6 @@
  */
 declare var self: ServiceWorkerGlobalScope;
 
-import { getNoteSummary } from '@/scripts/get-note-summary';
 import * as misskey from 'misskey-js';
 
 function getUserName(user: misskey.entities.User): string {
@@ -26,37 +25,37 @@ export default async function(type, data, i18n): Promise<[string, NotificationOp
 			switch (data.type) {
 				case 'mention':
 					return [i18n.t('_notification.youGotMention', { name: getUserName(data.user) }), {
-						body: getNoteSummary(data.note, i18n.locale),
+						body: data.note.text,
 						icon: data.user.avatarUrl
 					}];
 
 				case 'reply':
 					return [i18n.t('_notification.youGotReply', { name: getUserName(data.user) }), {
-						body: getNoteSummary(data.note, i18n.locale),
+						body: data.note.text,
 						icon: data.user.avatarUrl
 					}];
 
 				case 'renote':
 					return [i18n.t('_notification.youRenoted', { name: getUserName(data.user) }), {
-						body: getNoteSummary(data.note, i18n.locale),
+						body: data.note.text,
 						icon: data.user.avatarUrl
 					}];
 
 				case 'quote':
 					return [i18n.t('_notification.youGotQuote', { name: getUserName(data.user) }), {
-						body: getNoteSummary(data.note, i18n.locale),
+						body: data.note.text,
 						icon: data.user.avatarUrl
 					}];
 
 				case 'reaction':
 					return [`${data.reaction} ${getUserName(data.user)}`, {
-						body: getNoteSummary(data.note, i18n.locale),
+						body: data.note.text,
 						icon: data.user.avatarUrl
 					}];
 
 				case 'pollVote':
 					return [i18n.t('_notification.youGotPoll', { name: getUserName(data.user) }), {
-						body: getNoteSummary(data.note, i18n.locale),
+						body: data.note.text,
 						icon: data.user.avatarUrl
 					}];
 
diff --git a/packages/client/src/ui/_common_/sidebar.vue b/packages/client/src/ui/_common_/sidebar.vue
index e363c3abd..fa712ba45 100644
--- a/packages/client/src/ui/_common_/sidebar.vue
+++ b/packages/client/src/ui/_common_/sidebar.vue
@@ -170,6 +170,8 @@ export default defineComponent({
 			}
 
 			&:hover, &.active {
+				color: var(--accent);
+
 				&:before {
 					content: "";
 					display: block;
@@ -283,8 +285,10 @@ export default defineComponent({
 				}
 
 				&:before {
-					width: 100%;
-					border-radius: 0;
+					width: min-content;
+					height: 100%;
+					aspect-ratio: 1/1;
+					border-radius: 8px;
 				}
 
 				&.post {
@@ -296,8 +300,9 @@ export default defineComponent({
 				}
 
 				&.post:before {
-					width: calc(100% - 32px);
-					height: calc(100% - 32px);
+					width: calc(100% - 28px);
+					height: min-content;
+					aspect-ratio: 1/1;
 					border-radius: 100%;
 				}
 			}
diff --git a/packages/client/src/ui/deck/direct-column.vue b/packages/client/src/ui/deck/direct-column.vue
index a11b2e82e..6ef733dfd 100644
--- a/packages/client/src/ui/deck/direct-column.vue
+++ b/packages/client/src/ui/deck/direct-column.vue
@@ -8,7 +8,6 @@
 
 <script lang="ts">
 import { defineComponent } from 'vue';
-import Progress from '@/scripts/loading';
 import XColumn from './column.vue';
 import XNotes from '@/components/notes.vue';
 import * as os from '@/os';
@@ -41,15 +40,5 @@ export default defineComponent({
 			},
 		}
 	},
-
-	methods: {
-		before() {
-			Progress.start();
-		},
-
-		after() {
-			Progress.done();
-		}
-	}
 });
 </script>
diff --git a/packages/client/src/ui/deck/mentions-column.vue b/packages/client/src/ui/deck/mentions-column.vue
index 7dd06989c..4b8dc0c4e 100644
--- a/packages/client/src/ui/deck/mentions-column.vue
+++ b/packages/client/src/ui/deck/mentions-column.vue
@@ -8,7 +8,6 @@
 
 <script lang="ts">
 import { defineComponent } from 'vue';
-import Progress from '@/scripts/loading';
 import XColumn from './column.vue';
 import XNotes from '@/components/notes.vue';
 import * as os from '@/os';
@@ -38,15 +37,5 @@ export default defineComponent({
 			},
 		}
 	},
-
-	methods: {
-		before() {
-			Progress.start();
-		},
-
-		after() {
-			Progress.done();
-		}
-	}
 });
 </script>
diff --git a/packages/client/src/widgets/calendar.vue b/packages/client/src/widgets/calendar.vue
index 545072e87..c8b52d7af 100644
--- a/packages/client/src/widgets/calendar.vue
+++ b/packages/client/src/widgets/calendar.vue
@@ -5,7 +5,8 @@
 			<span class="year">{{ $t('yearX', { year }) }}</span>
 			<span class="month">{{ $t('monthX', { month }) }}</span>
 		</p>
-		<p class="day">{{ $t('dayX', { day }) }}</p>
+		<p v-if="month === 1 && day === 1" class="day">🎉{{ $t('dayX', { day }) }}<span style="display: inline-block; transform: scaleX(-1);">🎉</span></p>
+		<p v-else class="day">{{ $t('dayX', { day }) }}</p>
 		<p class="week-day">{{ weekDay }}</p>
 	</div>
 	<div class="info">
@@ -34,7 +35,6 @@
 <script lang="ts">
 import { defineComponent } from 'vue';
 import define from './define';
-import * as os from '@/os';
 
 const widget = define({
 	name: 'calendar',
@@ -127,12 +127,12 @@ export default defineComponent({
 			}
 		}
 
-		> p {
+		> .month-and-year, > .week-day {
 			margin: 0;
 			line-height: 18px;
 			font-size: 0.9em;
 
-			> span {
+			> .year, > .month {
 				margin: 0 4px;
 			}
 		}
diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock
index 949c8bab4..4f666c252 100644
--- a/packages/client/yarn.lock
+++ b/packages/client/yarn.lock
@@ -23,10 +23,10 @@
     chalk "^2.0.0"
     js-tokens "^4.0.0"
 
-"@babel/parser@^7.15.0":
-  version "7.15.6"
-  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.15.6.tgz#043b9aa3c303c0722e5377fef9197f4cf1796549"
-  integrity sha512-S/TSCcsRuCkmpUuoWijua0Snt+f3ewU/8spLo+4AXJCZfT0bVCzLD5MuOKdrx0mlAptbKzn5AdgEIIKXxXkz9Q==
+"@babel/parser@^7.16.4":
+  version "7.16.6"
+  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.6.tgz#8f194828193e8fa79166f34a4b4e52f3e769a314"
+  integrity sha512-Gr86ujcNuPDnNOY8mi383Hvi8IYrJVJYuf3XcuBM/Dgd+bINn/7tHqsj+tKkoreMbmGsFLsltI/JJd8fOFWGDQ==
 
 "@babel/parser@^7.6.0", "@babel/parser@^7.9.6":
   version "7.13.9"
@@ -68,10 +68,10 @@
   dependencies:
     "@cspotcode/source-map-consumer" "0.8.0"
 
-"@cypress/request@^2.88.6":
-  version "2.88.6"
-  resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.6.tgz#a970dd675befc6bdf8a8921576c01f51cc5798e9"
-  integrity sha512-z0UxBE/+qaESAHY9p9sM2h8Y4XqtsbDCt0/DPOrqA/RZgKi4PkxdpXyK4wCCnSk1xHqWHZZAE+gV6aDAR6+caQ==
+"@cypress/request@^2.88.10":
+  version "2.88.10"
+  resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.10.tgz#b66d76b07f860d3a4b8d7a0604d020c662752cce"
+  integrity sha512-Zp7F+R93N0yZyG34GutyTNr+okam7s/Fzc1+i3kcqOP8vk6OuajuE9qZJ6Rs+10/1JFtXFYMdyarnU1rZuJesg==
   dependencies:
     aws-sign2 "~0.7.0"
     aws4 "^1.8.0"
@@ -80,8 +80,7 @@
     extend "~3.0.2"
     forever-agent "~0.6.1"
     form-data "~2.3.2"
-    har-validator "~5.1.3"
-    http-signature "~1.2.0"
+    http-signature "~1.3.6"
     is-typedarray "~1.0.0"
     isstream "~0.1.2"
     json-stringify-safe "~5.0.1"
@@ -516,10 +515,10 @@
   resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8"
   integrity sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==
 
-"@types/qrcode@1.4.1":
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.4.1.tgz#0689f400c3a95d2db040c99c99834faa09ee9dc1"
-  integrity sha512-vxMyr7JM7tYPxu8vUE83NiosWX5DZieCyYeJRoOIg0pAkyofCBzknJ2ycUZkPGDFis2RS8GN/BeJLnRnAPxeCA==
+"@types/qrcode@1.4.2":
+  version "1.4.2"
+  resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.4.2.tgz#7d7142d6fa9921f195db342ed08b539181546c74"
+  integrity sha512-7uNT9L4WQTNJejHTSTdaJhfBSCN73xtXaHFyBJ8TSwiLhe4PRuTue7Iph0s2nG9R/ifUaSnGhLUOZavlBEqDWQ==
   dependencies:
     "@types/node" "*"
 
@@ -570,10 +569,10 @@
   resolved "https://registry.yarnpkg.com/@types/tinycolor2/-/tinycolor2-1.4.3.tgz#ed4a0901f954b126e6a914b4839c77462d56e706"
   integrity sha512-Kf1w9NE5HEgGxCRyIcRXR/ZYtDv0V8FVPtYHwLxl0O+maGX0erE77pQlD0gpP+/KByMZ87mOA79SjifhSB3PjQ==
 
-"@types/tmp@0.2.2":
-  version "0.2.2"
-  resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.2.tgz#424537a3b91828cb26aaf697f21ae3cd1b69f7e7"
-  integrity sha512-MhSa0yylXtVMsyT8qFpHA1DLHj4DvQGH5ntxrhHSh8PxUVNi35Wk+P5hVgqbO2qZqOotqr9jaoPRL+iRjWYm/A==
+"@types/tmp@0.2.3":
+  version "0.2.3"
+  resolved "https://registry.yarnpkg.com/@types/tmp/-/tmp-0.2.3.tgz#908bfb113419fd6a42273674c00994d40902c165"
+  integrity sha512-dDZH/tXzwjutnuk4UacGgFRwV+JSLaXL1ikvidfJprkb7L9Nx1njcRHHmi3Dsvt7pgqqTEeucQuOrWHPFgzVHA==
 
 "@types/uglify-js@*":
   version "3.9.0"
@@ -682,13 +681,13 @@
   dependencies:
     "@types/node" "*"
 
-"@typescript-eslint/eslint-plugin@5.4.0":
-  version "5.4.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.4.0.tgz#05e711a2e7b68342661fde61bccbd1531c19521a"
-  integrity sha512-9/yPSBlwzsetCsGEn9j24D8vGQgJkOTr4oMLas/w886ZtzKIs1iyoqFrwsX2fqYEeUwsdBpC21gcjRGo57u0eg==
+"@typescript-eslint/eslint-plugin@5.8.1":
+  version "5.8.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.8.1.tgz#97dfaa39f38e99f86801fdf34f9f1bed66704258"
+  integrity sha512-wTZ5oEKrKj/8/366qTM366zqhIKAp6NCMweoRONtfuC07OAU9nVI2GZZdqQ1qD30WAAtcPdkH+npDwtRFdp4Rw==
   dependencies:
-    "@typescript-eslint/experimental-utils" "5.4.0"
-    "@typescript-eslint/scope-manager" "5.4.0"
+    "@typescript-eslint/experimental-utils" "5.8.1"
+    "@typescript-eslint/scope-manager" "5.8.1"
     debug "^4.3.2"
     functional-red-black-tree "^1.0.1"
     ignore "^5.1.8"
@@ -696,94 +695,60 @@
     semver "^7.3.5"
     tsutils "^3.21.0"
 
-"@typescript-eslint/experimental-utils@5.4.0":
-  version "5.4.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.4.0.tgz#238a7418d2da3b24874ba35385eb21cc61d2a65e"
-  integrity sha512-Nz2JDIQUdmIGd6p33A+naQmwfkU5KVTLb/5lTk+tLVTDacZKoGQisj8UCxk7onJcrgjIvr8xWqkYI+DbI3TfXg==
+"@typescript-eslint/experimental-utils@5.8.1":
+  version "5.8.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-5.8.1.tgz#01861eb2f0749f07d02db342b794145a66ed346f"
+  integrity sha512-fbodVnjIDU4JpeXWRDsG5IfIjYBxEvs8EBO8W1+YVdtrc2B9ppfof5sZhVEDOtgTfFHnYQJDI8+qdqLYO4ceww==
   dependencies:
     "@types/json-schema" "^7.0.9"
-    "@typescript-eslint/scope-manager" "5.4.0"
-    "@typescript-eslint/types" "5.4.0"
-    "@typescript-eslint/typescript-estree" "5.4.0"
+    "@typescript-eslint/scope-manager" "5.8.1"
+    "@typescript-eslint/types" "5.8.1"
+    "@typescript-eslint/typescript-estree" "5.8.1"
     eslint-scope "^5.1.1"
     eslint-utils "^3.0.0"
 
-"@typescript-eslint/parser@5.6.0":
-  version "5.6.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.6.0.tgz#11677324659641400d653253c03dcfbed468d199"
-  integrity sha512-YVK49NgdUPQ8SpCZaOpiq1kLkYRPMv9U5gcMrywzI8brtwZjr/tG3sZpuHyODt76W/A0SufNjYt9ZOgrC4tLIQ==
+"@typescript-eslint/parser@5.8.1":
+  version "5.8.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.8.1.tgz#380f5f1e596b540059998aa3fc80d78f0f9b0d0a"
+  integrity sha512-K1giKHAjHuyB421SoXMXFHHVI4NdNY603uKw92++D3qyxSeYvC10CBJ/GE5Thpo4WTUvu1mmJI2/FFkz38F2Gw==
   dependencies:
-    "@typescript-eslint/scope-manager" "5.6.0"
-    "@typescript-eslint/types" "5.6.0"
-    "@typescript-eslint/typescript-estree" "5.6.0"
+    "@typescript-eslint/scope-manager" "5.8.1"
+    "@typescript-eslint/types" "5.8.1"
+    "@typescript-eslint/typescript-estree" "5.8.1"
     debug "^4.3.2"
 
-"@typescript-eslint/scope-manager@5.4.0":
-  version "5.4.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.4.0.tgz#aaab08415f4a9cf32b870c7750ae8ba4607126a1"
-  integrity sha512-pRxFjYwoi8R+n+sibjgF9iUiAELU9ihPBtHzocyW8v8D8G8KeQvXTsW7+CBYIyTYsmhtNk50QPGLE3vrvhM5KA==
+"@typescript-eslint/scope-manager@5.8.1":
+  version "5.8.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.8.1.tgz#7fc0604f7ade8833e4d42cebaa1e2debf8b932e4"
+  integrity sha512-DGxJkNyYruFH3NIZc3PwrzwOQAg7vvgsHsHCILOLvUpupgkwDZdNq/cXU3BjF4LNrCsVg0qxEyWasys5AiJ85Q==
   dependencies:
-    "@typescript-eslint/types" "5.4.0"
-    "@typescript-eslint/visitor-keys" "5.4.0"
+    "@typescript-eslint/types" "5.8.1"
+    "@typescript-eslint/visitor-keys" "5.8.1"
 
-"@typescript-eslint/scope-manager@5.6.0":
-  version "5.6.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.6.0.tgz#9dd7f007dc8f3a34cdff6f79f5eaab27ae05157e"
-  integrity sha512-1U1G77Hw2jsGWVsO2w6eVCbOg0HZ5WxL/cozVSTfqnL/eB9muhb8THsP0G3w+BB5xAHv9KptwdfYFAUfzcIh4A==
+"@typescript-eslint/types@5.8.1":
+  version "5.8.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.8.1.tgz#04c6b49ebc8c99238238a6b8b43f2fc613983b5a"
+  integrity sha512-L/FlWCCgnjKOLefdok90/pqInkomLnAcF9UAzNr+DSqMC3IffzumHTQTrINXhP1gVp9zlHiYYjvozVZDPleLcA==
+
+"@typescript-eslint/typescript-estree@5.8.1":
+  version "5.8.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.8.1.tgz#a592855be688e7b729a1e9411d7d74ec992ed6ef"
+  integrity sha512-26lQ8l8tTbG7ri7xEcCFT9ijU5Fk+sx/KRRyyzCv7MQ+rZZlqiDPtMKWLC8P7o+dtCnby4c+OlxuX1tp8WfafQ==
   dependencies:
-    "@typescript-eslint/types" "5.6.0"
-    "@typescript-eslint/visitor-keys" "5.6.0"
-
-"@typescript-eslint/types@5.4.0":
-  version "5.4.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.4.0.tgz#b1c130f4b381b77bec19696c6e3366f9781ce8f2"
-  integrity sha512-GjXNpmn+n1LvnttarX+sPD6+S7giO+9LxDIGlRl4wK3a7qMWALOHYuVSZpPTfEIklYjaWuMtfKdeByx0AcaThA==
-
-"@typescript-eslint/types@5.6.0":
-  version "5.6.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.6.0.tgz#745cb1b59daadcc1f32f7be95f0f68accf38afdd"
-  integrity sha512-OIZffked7mXv4mXzWU5MgAEbCf9ecNJBKi+Si6/I9PpTaj+cf2x58h2oHW5/P/yTnPkKaayfjhLvx+crnl5ubA==
-
-"@typescript-eslint/typescript-estree@5.4.0":
-  version "5.4.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.4.0.tgz#fe524fb308973c68ebeb7428f3b64499a6ba5fc0"
-  integrity sha512-nhlNoBdhKuwiLMx6GrybPT3SFILm5Gij2YBdPEPFlYNFAXUJWX6QRgvi/lwVoadaQEFsizohs6aFRMqsXI2ewA==
-  dependencies:
-    "@typescript-eslint/types" "5.4.0"
-    "@typescript-eslint/visitor-keys" "5.4.0"
+    "@typescript-eslint/types" "5.8.1"
+    "@typescript-eslint/visitor-keys" "5.8.1"
     debug "^4.3.2"
     globby "^11.0.4"
     is-glob "^4.0.3"
     semver "^7.3.5"
     tsutils "^3.21.0"
 
-"@typescript-eslint/typescript-estree@5.6.0":
-  version "5.6.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.6.0.tgz#dfbb19c9307fdd81bd9c650c67e8397821d7faf0"
-  integrity sha512-92vK5tQaE81rK7fOmuWMrSQtK1IMonESR+RJR2Tlc7w4o0MeEdjgidY/uO2Gobh7z4Q1hhS94Cr7r021fMVEeA==
+"@typescript-eslint/visitor-keys@5.8.1":
+  version "5.8.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.8.1.tgz#58a2c566265d5511224bc316149890451c1bbab0"
+  integrity sha512-SWgiWIwocK6NralrJarPZlWdr0hZnj5GXHIgfdm8hNkyKvpeQuFyLP6YjSIe9kf3YBIfU6OHSZLYkQ+smZwtNg==
   dependencies:
-    "@typescript-eslint/types" "5.6.0"
-    "@typescript-eslint/visitor-keys" "5.6.0"
-    debug "^4.3.2"
-    globby "^11.0.4"
-    is-glob "^4.0.3"
-    semver "^7.3.5"
-    tsutils "^3.21.0"
-
-"@typescript-eslint/visitor-keys@5.4.0":
-  version "5.4.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.4.0.tgz#09bc28efd3621f292fe88c86eef3bf4893364c8c"
-  integrity sha512-PVbax7MeE7tdLfW5SA0fs8NGVVr+buMPrcj+CWYWPXsZCH8qZ1THufDzbXm1xrZ2b2PA1iENJ0sRq5fuUtvsJg==
-  dependencies:
-    "@typescript-eslint/types" "5.4.0"
-    eslint-visitor-keys "^3.0.0"
-
-"@typescript-eslint/visitor-keys@5.6.0":
-  version "5.6.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.6.0.tgz#3e36509e103fe9713d8f035ac977235fd63cb6e6"
-  integrity sha512-1p7hDp5cpRFUyE3+lvA74egs+RWSgumrBpzBCDzfTFv0aQ7lIeay80yU0hIxgAhwQ6PcasW35kaOCyDOv6O/Ng==
-  dependencies:
-    "@typescript-eslint/types" "5.6.0"
+    "@typescript-eslint/types" "5.8.1"
     eslint-visitor-keys "^3.0.0"
 
 "@ungap/promise-all-settled@1.1.2":
@@ -791,95 +756,95 @@
   resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44"
   integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==
 
-"@vue/compiler-core@3.2.24":
-  version "3.2.24"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.24.tgz#cadcda0e026e7f1cd453ce87160be51a5f313fe0"
-  integrity sha512-A0SxB2HAggKzP57LDin5gfgWOTwFyGCtQ5MTMNBADnfQYALWnYuC8kMI0DhRSplGTWRvn9Z2DAnG8f35BnojuA==
+"@vue/compiler-core@3.2.26":
+  version "3.2.26"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-core/-/compiler-core-3.2.26.tgz#9ab92ae624da51f7b6064f4679c2d4564f437cc8"
+  integrity sha512-N5XNBobZbaASdzY9Lga2D9Lul5vdCIOXvUMd6ThcN8zgqQhPKfCV+wfAJNNJKQkSHudnYRO2gEB+lp0iN3g2Tw==
   dependencies:
-    "@babel/parser" "^7.15.0"
-    "@vue/shared" "3.2.24"
+    "@babel/parser" "^7.16.4"
+    "@vue/shared" "3.2.26"
     estree-walker "^2.0.2"
     source-map "^0.6.1"
 
-"@vue/compiler-dom@3.2.24":
-  version "3.2.24"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.24.tgz#32235cb444660245be5cc58f4beb76747400505c"
-  integrity sha512-KQEm8r0JFsrNNIfbD28pcwMvHpcJcwjVR1XWFcD0yyQ8eREd7IXhT7J6j7iNCSE/TIo78NOvkwbyX+lnIm836w==
+"@vue/compiler-dom@3.2.26":
+  version "3.2.26"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.2.26.tgz#c7a7b55d50a7b7981dd44fc28211df1450482667"
+  integrity sha512-smBfaOW6mQDxcT3p9TKT6mE22vjxjJL50GFVJiI0chXYGU/xzC05QRGrW3HHVuJrmLTLx5zBhsZ2dIATERbarg==
   dependencies:
-    "@vue/compiler-core" "3.2.24"
-    "@vue/shared" "3.2.24"
+    "@vue/compiler-core" "3.2.26"
+    "@vue/shared" "3.2.26"
 
-"@vue/compiler-sfc@3.2.24":
-  version "3.2.24"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.24.tgz#803a309e1935fc85981218d88fd4fb5db05afdb1"
-  integrity sha512-YGPcIvVJp2qTPkuT6kT43Eo1xjstyY4bmuiSV31my4bQMBFVR26ANmifUSt759Blok71gK0WzfIZHbcOKYOeKA==
+"@vue/compiler-sfc@3.2.26":
+  version "3.2.26"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.2.26.tgz#3ce76677e4aa58311655a3bea9eb1cb804d2273f"
+  integrity sha512-ePpnfktV90UcLdsDQUh2JdiTuhV0Skv2iYXxfNMOK/F3Q+2BO0AulcVcfoksOpTJGmhhfosWfMyEaEf0UaWpIw==
   dependencies:
-    "@babel/parser" "^7.15.0"
-    "@vue/compiler-core" "3.2.24"
-    "@vue/compiler-dom" "3.2.24"
-    "@vue/compiler-ssr" "3.2.24"
-    "@vue/ref-transform" "3.2.24"
-    "@vue/shared" "3.2.24"
+    "@babel/parser" "^7.16.4"
+    "@vue/compiler-core" "3.2.26"
+    "@vue/compiler-dom" "3.2.26"
+    "@vue/compiler-ssr" "3.2.26"
+    "@vue/reactivity-transform" "3.2.26"
+    "@vue/shared" "3.2.26"
     estree-walker "^2.0.2"
     magic-string "^0.25.7"
     postcss "^8.1.10"
     source-map "^0.6.1"
 
-"@vue/compiler-ssr@3.2.24":
-  version "3.2.24"
-  resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.24.tgz#edd0b7e7cd28d6c2578bb770ce0128bfdef9c948"
-  integrity sha512-E1HHShNsGVWXxs68LDOUuI+Bzak9W/Ier/366aKDBFuwvfwgruwq6abhMfj6pSDZpwZ/PXnfliyl/m7qBSq6gw==
+"@vue/compiler-ssr@3.2.26":
+  version "3.2.26"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-ssr/-/compiler-ssr-3.2.26.tgz#fd049523341fbf4ab5e88e25eef566d862894ba7"
+  integrity sha512-2mywLX0ODc4Zn8qBoA2PDCsLEZfpUGZcyoFRLSOjyGGK6wDy2/5kyDOWtf0S0UvtoyVq95OTSGIALjZ4k2q/ag==
   dependencies:
-    "@vue/compiler-dom" "3.2.24"
-    "@vue/shared" "3.2.24"
+    "@vue/compiler-dom" "3.2.26"
+    "@vue/shared" "3.2.26"
 
-"@vue/reactivity@3.2.24":
-  version "3.2.24"
-  resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.24.tgz#4b24301ff7af0607c49a1e6976a22b4f62eab1a4"
-  integrity sha512-5eVsO9wfQ5erCMSRBjpqLkkI+LglJS7E0oLZJs2gsChpvOjH2Uwt3Hk1nVv0ywStnWg71Ykn3SyQwtnl7PknOQ==
+"@vue/reactivity-transform@3.2.26":
+  version "3.2.26"
+  resolved "https://registry.yarnpkg.com/@vue/reactivity-transform/-/reactivity-transform-3.2.26.tgz#6d8f20a4aa2d19728f25de99962addbe7c4d03e9"
+  integrity sha512-XKMyuCmzNA7nvFlYhdKwD78rcnmPb7q46uoR00zkX6yZrUmcCQ5OikiwUEVbvNhL5hBJuvbSO95jB5zkUon+eQ==
   dependencies:
-    "@vue/shared" "3.2.24"
-
-"@vue/ref-transform@3.2.24":
-  version "3.2.24"
-  resolved "https://registry.yarnpkg.com/@vue/ref-transform/-/ref-transform-3.2.24.tgz#335bf06e0b25be16e7056cc4117c51a79f0c9122"
-  integrity sha512-j6oNbsGLvea2rF8GQB9w6q7UFL1So7J+t6ducaMeWPSyjYZ+slWpwPVK6mmyghg5oGqC41R+HC5BV036Y0KhXQ==
-  dependencies:
-    "@babel/parser" "^7.15.0"
-    "@vue/compiler-core" "3.2.24"
-    "@vue/shared" "3.2.24"
+    "@babel/parser" "^7.16.4"
+    "@vue/compiler-core" "3.2.26"
+    "@vue/shared" "3.2.26"
     estree-walker "^2.0.2"
     magic-string "^0.25.7"
 
-"@vue/runtime-core@3.2.24":
-  version "3.2.24"
-  resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.24.tgz#b3e433587442910d90f78f61ce3c5faf6fb3f355"
-  integrity sha512-ReI06vGgYuW0G8FlOcAOzMklVDJSxKuRhYzT8j+a8BTfs1945kxo1Th28BPvasyYx8J+LMeZ0HqpPH9yGXvWvg==
+"@vue/reactivity@3.2.26":
+  version "3.2.26"
+  resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.2.26.tgz#d529191e581521c3c12e29ef986d4c8a933a0f83"
+  integrity sha512-h38bxCZLW6oFJVDlCcAiUKFnXI8xP8d+eO0pcDxx+7dQfSPje2AO6M9S9QO6MrxQB7fGP0DH0dYQ8ksf6hrXKQ==
   dependencies:
-    "@vue/reactivity" "3.2.24"
-    "@vue/shared" "3.2.24"
+    "@vue/shared" "3.2.26"
 
-"@vue/runtime-dom@3.2.24":
-  version "3.2.24"
-  resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.24.tgz#14b462514cdb01d9de6c09ecf1d5d5d55a58e1e3"
-  integrity sha512-piqsabtIEUKkMGSJlOyKUonZEDtdwOpR6teQ8EKbH8PX9sxfAt9snLnFJldUhhyYrLIyDtnjwajfJ7/XtpD4JA==
+"@vue/runtime-core@3.2.26":
+  version "3.2.26"
+  resolved "https://registry.yarnpkg.com/@vue/runtime-core/-/runtime-core-3.2.26.tgz#5c59cc440ed7a39b6dbd4c02e2d21c8d1988f0de"
+  integrity sha512-BcYi7qZ9Nn+CJDJrHQ6Zsmxei2hDW0L6AB4vPvUQGBm2fZyC0GXd/4nVbyA2ubmuhctD5RbYY8L+5GUJszv9mQ==
   dependencies:
-    "@vue/runtime-core" "3.2.24"
-    "@vue/shared" "3.2.24"
+    "@vue/reactivity" "3.2.26"
+    "@vue/shared" "3.2.26"
+
+"@vue/runtime-dom@3.2.26":
+  version "3.2.26"
+  resolved "https://registry.yarnpkg.com/@vue/runtime-dom/-/runtime-dom-3.2.26.tgz#84d3ae2584488747717c2e072d5d9112c0d2e6c2"
+  integrity sha512-dY56UIiZI+gjc4e8JQBwAifljyexfVCkIAu/WX8snh8vSOt/gMSEGwPRcl2UpYpBYeyExV8WCbgvwWRNt9cHhQ==
+  dependencies:
+    "@vue/runtime-core" "3.2.26"
+    "@vue/shared" "3.2.26"
     csstype "^2.6.8"
 
-"@vue/server-renderer@3.2.24":
-  version "3.2.24"
-  resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.24.tgz#8009b1e52293fd4efc71b90452baf502ecb9dc03"
-  integrity sha512-DqiCRDxTbv67Hw5ImiqnLIQbPGtIwWLLfEcVHoEnu1f21EMTB6LfoS69EQddd8VyfN5kfX3Fmz27/hrFPpRaMQ==
+"@vue/server-renderer@3.2.26":
+  version "3.2.26"
+  resolved "https://registry.yarnpkg.com/@vue/server-renderer/-/server-renderer-3.2.26.tgz#f16a4b9fbcc917417b4cea70c99afce2701341cf"
+  integrity sha512-Jp5SggDUvvUYSBIvYEhy76t4nr1vapY/FIFloWmQzn7UxqaHrrBpbxrqPcTrSgGrcaglj0VBp22BKJNre4aA1w==
   dependencies:
-    "@vue/compiler-ssr" "3.2.24"
-    "@vue/shared" "3.2.24"
+    "@vue/compiler-ssr" "3.2.26"
+    "@vue/shared" "3.2.26"
 
-"@vue/shared@3.2.24":
-  version "3.2.24"
-  resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.24.tgz#d74615e856013b17fb60b19b09d712729ad5e090"
-  integrity sha512-BUgRiZCkCrqDps5aQ9av05xcge3rn092ztKIh17tHkeEFgP4zfXMQWBA2zfdoCdCEdBL26xtOv+FZYiOp9RUDA==
+"@vue/shared@3.2.26":
+  version "3.2.26"
+  resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.2.26.tgz#7acd1621783571b9a82eca1f041b4a0a983481d9"
+  integrity sha512-vPV6Cq+NIWbH5pZu+V+2QHE9y1qfuTq49uNWw4f7FDEeZaDU2H2cx5jcUZOAKW7qTrUS4k6qZPbMy1x4N96nbA==
 
 "@webassemblyjs/ast@1.11.0":
   version "1.11.0"
@@ -1217,7 +1182,7 @@ ajv-keywords@^3.5.2:
   resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
   integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
 
-ajv@^6.10.0, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.5.5:
+ajv@^6.10.0, ajv@^6.12.4, ajv@^6.12.5:
   version "6.12.5"
   resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.5.tgz#19b0e8bae8f476e5ba666300387775fb1a00a4da"
   integrity sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==
@@ -1461,7 +1426,7 @@ blob-util@^2.0.2:
   resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb"
   integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==
 
-bluebird@3.7.2, bluebird@^3.7.2:
+bluebird@3.7.2:
   version "3.7.2"
   resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
   integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
@@ -1496,10 +1461,10 @@ braces@^3.0.1, braces@~3.0.2:
   dependencies:
     fill-range "^7.0.1"
 
-broadcast-channel@4.7.0:
-  version "4.7.0"
-  resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.7.0.tgz#4f5c31982f627eae4ffe463623ba36a9e7da1992"
-  integrity sha512-1C7wDPqeiKkwpScqFP044MsPAtxxDNKZzOnJmkHaTuOlUdaMLo11op56NrCOMiRh8dzktstcNsiHELGeTMKnNQ==
+broadcast-channel@4.9.0:
+  version "4.9.0"
+  resolved "https://registry.yarnpkg.com/broadcast-channel/-/broadcast-channel-4.9.0.tgz#8af337d4ea19aeb6b819ec2eb3dda942b28c724c"
+  integrity sha512-xWzFb3wrOZGJF2kOSs2D3KvHXdLDMVb+WypEIoNvwblcHgUBydVy65pDJ9RS4WN9Kyvs0UVQuCCzfKme0G6Qjw==
   dependencies:
     "@babel/runtime" "^7.16.0"
     detect-node "^2.1.0"
@@ -1655,10 +1620,10 @@ character-parser@^2.2.0:
   dependencies:
     is-regex "^1.0.3"
 
-chart.js@3.6.2:
-  version "3.6.2"
-  resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.6.2.tgz#47342c551f688ffdda2cd53b534cb7e461ecec33"
-  integrity sha512-Xz7f/fgtVltfQYWq0zL1Xbv7N2inpG+B54p3D5FSvpCdy3sM+oZhbqa42eNuYXltaVvajgX5UpKCU2GeeJIgxg==
+chart.js@3.7.0:
+  version "3.7.0"
+  resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.7.0.tgz#7a19c93035341df801d613993c2170a1fcf1d882"
+  integrity sha512-31gVuqqKp3lDIFmzpKIrBeum4OpZsQjSIAqlOpgjosHDJZlULtvwLEZKtEhIAZc7JMPaHlYMys40Qy9Mf+1AAg==
 
 chartjs-adapter-date-fns@2.0.0:
   version "2.0.0"
@@ -1869,12 +1834,12 @@ constantinople@^4.0.1:
     "@babel/parser" "^7.6.0"
     "@babel/types" "^7.6.1"
 
-content-disposition@0.5.3:
-  version "0.5.3"
-  resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
-  integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==
+content-disposition@0.5.4:
+  version "0.5.4"
+  resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe"
+  integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==
   dependencies:
-    safe-buffer "5.1.2"
+    safe-buffer "5.2.1"
 
 core-util-is@1.0.2:
   version "1.0.2"
@@ -2007,15 +1972,15 @@ cssesc@^3.0.0:
   resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
   integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
 
-cssnano-preset-default@^5.1.8:
-  version "5.1.8"
-  resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.1.8.tgz#7525feb1b72f7b06e57f55064cbdae341d79dea2"
-  integrity sha512-zWMlP0+AMPBVE852SqTrP0DnhTcTA2C1wAF92TKZ3Va+aUVqLIhkqKlnJIXXdqXD7RN+S1ujuWmNpvrJBiM/vg==
+cssnano-preset-default@^5.1.9:
+  version "5.1.9"
+  resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-5.1.9.tgz#79628ac48eccbdad570f70b4018cc38d43d1b7df"
+  integrity sha512-RhkEucqlQ+OxEi14K1p8gdXcMQy1mSpo7P1oC44oRls7BYIj8p+cht4IFBFV3W4iOjTP8EUB33XV1fX9KhDzyA==
   dependencies:
     css-declaration-sorter "^6.0.3"
     cssnano-utils "^2.0.1"
     postcss-calc "^8.0.0"
-    postcss-colormin "^5.2.1"
+    postcss-colormin "^5.2.2"
     postcss-convert-values "^5.0.2"
     postcss-discard-comments "^5.0.1"
     postcss-discard-duplicates "^5.0.1"
@@ -2034,7 +1999,7 @@ cssnano-preset-default@^5.1.8:
     postcss-normalize-string "^5.0.1"
     postcss-normalize-timing-functions "^5.0.1"
     postcss-normalize-unicode "^5.0.1"
-    postcss-normalize-url "^5.0.3"
+    postcss-normalize-url "^5.0.4"
     postcss-normalize-whitespace "^5.0.1"
     postcss-ordered-values "^5.0.2"
     postcss-reduce-initial "^5.0.2"
@@ -2047,13 +2012,12 @@ cssnano-utils@^2.0.1:
   resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-2.0.1.tgz#8660aa2b37ed869d2e2f22918196a9a8b6498ce2"
   integrity sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ==
 
-cssnano@5.0.12:
-  version "5.0.12"
-  resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.0.12.tgz#2c083a1c786fc9dc2d5522bd3c0e331b7cd302ab"
-  integrity sha512-U38V4x2iJ3ijPdeWqUrEr4eKBB5PbEKsNP5T8xcik2Au3LeMtiMHX0i2Hu9k51FcKofNZumbrcdC6+a521IUHg==
+cssnano@5.0.14:
+  version "5.0.14"
+  resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-5.0.14.tgz#99bc550f663b48c38e9b8e0ae795697c9de84b47"
+  integrity sha512-qzhRkFvBhv08tbyKCIfWbxBXmkIpLl1uNblt8SpTHkgLfON5OCPX/CCnkdNmEosvo8bANQYmTTMEgcVBlisHaw==
   dependencies:
-    cssnano-preset-default "^5.1.8"
-    is-resolvable "^1.1.0"
+    cssnano-preset-default "^5.1.9"
     lilconfig "^2.0.3"
     yaml "^1.10.2"
 
@@ -2076,19 +2040,19 @@ csstype@^2.6.8:
   resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.13.tgz#a6893015b90e84dd6e85d0e3b442a1e84f2dbe0f"
   integrity sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A==
 
-cypress@8.5.0:
-  version "8.5.0"
-  resolved "https://registry.yarnpkg.com/cypress/-/cypress-8.5.0.tgz#5712ca170913f8344bf167301205c4217c1eb9bd"
-  integrity sha512-MMkXIS+Ro2KETn4gAlG3tIc/7FiljuuCZP0zpd9QsRG6MZSyZW/l1J3D4iQM6WHsVxuX4rFChn5jPFlC2tNSvQ==
+cypress@9.2.0:
+  version "9.2.0"
+  resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.2.0.tgz#727c20b4662167890db81d5f6ba615231835b17d"
+  integrity sha512-Jn26Tprhfzh/a66Sdj9SoaYlnNX6Mjfmj5PHu2a7l3YHXhrgmavM368wjCmgrxC6KHTOv9SpMQGhAJn+upDViA==
   dependencies:
-    "@cypress/request" "^2.88.6"
+    "@cypress/request" "^2.88.10"
     "@cypress/xvfb" "^1.2.4"
     "@types/node" "^14.14.31"
     "@types/sinonjs__fake-timers" "^6.0.2"
     "@types/sizzle" "^2.3.2"
     arch "^2.2.0"
     blob-util "^2.0.2"
-    bluebird "^3.7.2"
+    bluebird "3.7.2"
     cachedir "^2.3.0"
     chalk "^4.1.0"
     check-more-types "^2.24.0"
@@ -2116,7 +2080,6 @@ cypress@8.5.0:
     ospath "^1.2.2"
     pretty-bytes "^5.6.0"
     proxy-from-env "1.0.0"
-    ramda "~0.27.1"
     request-progress "^3.0.0"
     supports-color "^8.1.1"
     tmp "~0.2.1"
@@ -2139,10 +2102,10 @@ dashdash@^1.12.0:
   dependencies:
     assert-plus "^1.0.0"
 
-date-fns@2.27.0:
-  version "2.27.0"
-  resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.27.0.tgz#e1ff3c3ddbbab8a2eaadbb6106be2929a5a2d92b"
-  integrity sha512-sj+J0Mo2p2X1e306MHq282WS4/A8Pz/95GIFcsPNMPMZVI3EUrAdSv90al1k+p74WGLCruMXk23bfEDZa71X9Q==
+date-fns@2.28.0:
+  version "2.28.0"
+  resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2"
+  integrity sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==
 
 dateformat@4.5.1:
   version "4.5.1"
@@ -2628,10 +2591,10 @@ eslint-visitor-keys@^3.1.0:
   resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.1.0.tgz#eee4acea891814cda67a7d8812d9647dd0179af2"
   integrity sha512-yWJFpu4DtjsWKkt5GeNBBuZMlNcYVs6vRCLoCVEJrTjaSB6LC98gFipNK/erM2Heg/E8mIK+hXG/pJMLK+eRZA==
 
-eslint@8.4.1:
-  version "8.4.1"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.4.1.tgz#d6531bbf3e598dffd7c0c7d35ec52a0b30fdfa2d"
-  integrity sha512-TxU/p7LB1KxQ6+7aztTnO7K0i+h0tDi81YRY9VzB6Id71kNz+fFYnf5HD5UOQmxkzcoa0TlVZf9dpMtUv0GpWg==
+eslint@8.5.0:
+  version "8.5.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.5.0.tgz#ddd2c1afd8f412036f87ae2a063d2aa296d3175f"
+  integrity sha512-tVGSkgNbOfiHyVte8bCM8OmX+xG9PzVG/B4UCF60zx7j61WIVY/AqJECDgpLD4DbbESD0e174gOg3ZlrX15GDg==
   dependencies:
     "@eslint/eslintrc" "^1.0.5"
     "@humanwhocodes/config-array" "^0.9.2"
@@ -3229,19 +3192,6 @@ hammerjs@^2.0.8:
   resolved "https://registry.yarnpkg.com/hammerjs/-/hammerjs-2.0.8.tgz#04ef77862cff2bb79d30f7692095930222bf60f1"
   integrity sha1-BO93hiz/K7edMPdpIJWTAiK/YPE=
 
-har-schema@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
-  integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
-
-har-validator@~5.1.3:
-  version "5.1.3"
-  resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
-  integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==
-  dependencies:
-    ajv "^6.5.5"
-    har-schema "^2.0.0"
-
 has-bigints@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113"
@@ -3310,14 +3260,14 @@ http-headers@^3.0.1:
   dependencies:
     next-line "^1.1.0"
 
-http-signature@~1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
-  integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=
+http-signature@~1.3.6:
+  version "1.3.6"
+  resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.3.6.tgz#cb6fbfdf86d1c974f343be94e87f7fc128662cf9"
+  integrity sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==
   dependencies:
     assert-plus "^1.0.0"
-    jsprim "^1.2.2"
-    sshpk "^1.7.0"
+    jsprim "^2.0.2"
+    sshpk "^1.14.1"
 
 http_ece@1.1.0:
   version "1.1.0"
@@ -3475,11 +3425,6 @@ ipaddr.js@^2.0.1:
   resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0"
   integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==
 
-is-absolute-url@^3.0.3:
-  version "3.0.3"
-  resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698"
-  integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==
-
 is-arrayish@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
@@ -3663,11 +3608,6 @@ is-regex@^1.1.4:
     call-bind "^1.0.2"
     has-tostringtag "^1.0.0"
 
-is-resolvable@^1.1.0:
-  version "1.1.0"
-  resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88"
-  integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==
-
 is-shared-array-buffer@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz#97b0c85fbdacb59c9c446fe653b82cf2b5b7cfe6"
@@ -3832,10 +3772,10 @@ json-schema-traverse@^1.0.0:
   resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
   integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
 
-json-schema@0.2.3:
-  version "0.2.3"
-  resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
-  integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=
+json-schema@0.4.0:
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.4.0.tgz#f7de4cf6efab838ebaeb3236474cbba5a1930ab5"
+  integrity sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==
 
 json-stable-stringify-without-jsonify@^1.0.1:
   version "1.0.1"
@@ -3902,14 +3842,14 @@ jsonfile@^6.0.1:
   optionalDependencies:
     graceful-fs "^4.1.6"
 
-jsprim@^1.2.2:
-  version "1.4.1"
-  resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
-  integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=
+jsprim@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-2.0.2.tgz#77ca23dbcd4135cd364800d22ff82c2185803d4d"
+  integrity sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ==
   dependencies:
     assert-plus "1.0.0"
     extsprintf "1.3.0"
-    json-schema "0.2.3"
+    json-schema "0.4.0"
     verror "1.10.0"
 
 jstransformer@1.0.0:
@@ -4705,15 +4645,15 @@ postcss-calc@^8.0.0:
     postcss-selector-parser "^6.0.2"
     postcss-value-parser "^4.0.2"
 
-postcss-colormin@^5.2.1:
-  version "5.2.1"
-  resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.2.1.tgz#6e444a806fd3c578827dbad022762df19334414d"
-  integrity sha512-VVwMrEYLcHYePUYV99Ymuoi7WhKrMGy/V9/kTS0DkCoJYmmjdOMneyhzYUxcNgteKDVbrewOkSM7Wje/MFwxzA==
+postcss-colormin@^5.2.2:
+  version "5.2.2"
+  resolved "https://registry.yarnpkg.com/postcss-colormin/-/postcss-colormin-5.2.2.tgz#019cd6912bef9e7e0924462c5e4ffae241e2f437"
+  integrity sha512-tSEe3NpqWARUTidDlF0LntPkdlhXqfDFuA1yslqpvvGAfpZ7oBaw+/QXd935NKm2U9p4PED0HDZlzmMk7fVC6g==
   dependencies:
     browserslist "^4.16.6"
     caniuse-api "^3.0.0"
     colord "^2.9.1"
-    postcss-value-parser "^4.1.0"
+    postcss-value-parser "^4.2.0"
 
 postcss-convert-values@^5.0.2:
   version "5.0.2"
@@ -4882,14 +4822,13 @@ postcss-normalize-unicode@^5.0.1:
     browserslist "^4.16.0"
     postcss-value-parser "^4.1.0"
 
-postcss-normalize-url@^5.0.3:
-  version "5.0.3"
-  resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.0.3.tgz#42eca6ede57fe69075fab0f88ac8e48916ef931c"
-  integrity sha512-qWiUMbvkRx3kc1Dp5opzUwc7MBWZcSDK2yofCmdvFBCpx+zFPkxBC1FASQ59Pt+flYfj/nTZSkmF56+XG5elSg==
+postcss-normalize-url@^5.0.4:
+  version "5.0.4"
+  resolved "https://registry.yarnpkg.com/postcss-normalize-url/-/postcss-normalize-url-5.0.4.tgz#3b0322c425e31dd275174d0d5db0e466f50810fb"
+  integrity sha512-cNj3RzK2pgQQyNp7dzq0dqpUpQ/wYtdDZM3DepPmFjCmYIfceuD9VIAcOdvrNetjIU65g1B4uwdP/Krf6AFdXg==
   dependencies:
-    is-absolute-url "^3.0.3"
     normalize-url "^6.0.1"
-    postcss-value-parser "^4.1.0"
+    postcss-value-parser "^4.2.0"
 
 postcss-normalize-whitespace@^5.0.1:
   version "5.0.1"
@@ -4961,10 +4900,15 @@ postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0:
   resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb"
   integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==
 
-postcss@8.4.4:
-  version "8.4.4"
-  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.4.tgz#d53d4ec6a75fd62557a66bb41978bf47ff0c2869"
-  integrity sha512-joU6fBsN6EIer28Lj6GDFoC/5yOZzLCfn0zHAn/MYXI7aPt4m4hK5KC5ovEZXy+lnCjmYIbQWngvju2ddyEr8Q==
+postcss-value-parser@^4.2.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
+  integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
+
+postcss@8.4.5:
+  version "8.4.5"
+  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.5.tgz#bae665764dfd4c6fcc24dc0fdf7e7aa00cc77f95"
+  integrity sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==
   dependencies:
     nanoid "^3.1.30"
     picocolors "^1.0.0"
@@ -5203,11 +5147,6 @@ querystring@0.2.1:
   resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.1.tgz#40d77615bb09d16902a85c3e38aa8b5ed761c2dd"
   integrity sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==
 
-ramda@~0.27.1:
-  version "0.27.1"
-  resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9"
-  integrity sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw==
-
 random-seed@0.3.0:
   version "0.3.0"
   resolved "https://registry.yarnpkg.com/random-seed/-/random-seed-0.3.0.tgz#d945f2e1f38f49e8d58913431b8bf6bb937556cd"
@@ -5389,10 +5328,10 @@ safari-14-idb-fix@^1.0.4:
   resolved "https://registry.yarnpkg.com/safari-14-idb-fix/-/safari-14-idb-fix-1.0.4.tgz#5c68ba63e2a8ae0d89a0aa1e13fe89e3aef7da19"
   integrity sha512-4+Y2baQdgJpzu84d0QjySl70Kyygzf0pepVg8NVg4NnQEPpfC91fAn0baNvtStlCjUUxxiu0BOMiafa98fRRuA==
 
-safe-buffer@5.1.2:
-  version "5.1.2"
-  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
-  integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+safe-buffer@5.2.1:
+  version "5.2.1"
+  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
+  integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
 
 safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2:
   version "5.2.0"
@@ -5412,13 +5351,14 @@ sass-loader@12.4.0:
     klona "^2.0.4"
     neo-async "^2.6.2"
 
-sass@1.44.0:
-  version "1.44.0"
-  resolved "https://registry.yarnpkg.com/sass/-/sass-1.44.0.tgz#619aa0a2275c097f9af5e6b8fe8a95e3056430fb"
-  integrity sha512-0hLREbHFXGQqls/K8X+koeP+ogFRPF4ZqetVB19b7Cst9Er8cOR0rc6RU7MaI4W1JmUShd1BPgPoeqmmgMMYFw==
+sass@1.45.1:
+  version "1.45.1"
+  resolved "https://registry.yarnpkg.com/sass/-/sass-1.45.1.tgz#fa03951f924d1ba5762949567eaf660e608a1ab0"
+  integrity sha512-pwPRiq29UR0o4X3fiQyCtrESldXvUQAAE0QmcJTpsI4kuHHcLzZ54M1oNBVIXybQv8QF2zfkpFcTxp8ta97dUA==
   dependencies:
     chokidar ">=3.0.0 <4.0.0"
     immutable "^4.0.0"
+    source-map-js ">=0.6.2 <2.0.0"
 
 sax@^1.2.4, sax@~1.2.4:
   version "1.2.4"
@@ -5552,16 +5492,16 @@ source-list-map@^2.0.1:
   resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
   integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==
 
+"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.1.tgz#a1741c131e3c77d048252adfa24e23b908670caf"
+  integrity sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==
+
 source-map-js@^0.6.2:
   version "0.6.2"
   resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e"
   integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==
 
-source-map-js@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.1.tgz#a1741c131e3c77d048252adfa24e23b908670caf"
-  integrity sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==
-
 source-map-support@~0.5.19:
   version "0.5.19"
   resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
@@ -5602,7 +5542,7 @@ sprintf-js@~1.0.2:
   resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
   integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=
 
-sshpk@^1.7.0:
+sshpk@^1.14.1:
   version "1.16.1"
   resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877"
   integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==
@@ -6080,10 +6020,10 @@ typedarray-to-buffer@^3.1.5:
   dependencies:
     is-typedarray "^1.0.0"
 
-typescript@4.5.2:
-  version "4.5.2"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.2.tgz#8ac1fba9f52256fdb06fb89e4122fa6a346c2998"
-  integrity sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==
+typescript@4.5.4:
+  version "4.5.4"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.5.4.tgz#a17d3a0263bf5c8723b9c52f43c5084edf13c2e8"
+  integrity sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==
 
 unbox-primitive@^1.0.1:
   version "1.0.1"
@@ -6258,16 +6198,16 @@ vue-svg-loader@0.17.0-beta.2:
     semver "^7.3.2"
     svgo "^1.3.2"
 
-vue@3.2.24:
-  version "3.2.24"
-  resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.24.tgz#6de7b01e55740dc37c0a5dbd45e70eed49f95189"
-  integrity sha512-PvCklXNfcUMyeP/a9nME27C32IipwUDoS45rDyKn5+RQrWyjL+0JAJtf98HL6y9bfqQRTlYjSowWEB1nXxvG5Q==
+vue@3.2.26:
+  version "3.2.26"
+  resolved "https://registry.yarnpkg.com/vue/-/vue-3.2.26.tgz#5db575583ecae495c7caa5c12fd590dffcbb763e"
+  integrity sha512-KD4lULmskL5cCsEkfhERVRIOEDrfEL9CwAsLYpzptOGjaGFNWo3BQ9g8MAb7RaIO71rmVOziZ/uEN/rHwcUIhg==
   dependencies:
-    "@vue/compiler-dom" "3.2.24"
-    "@vue/compiler-sfc" "3.2.24"
-    "@vue/runtime-dom" "3.2.24"
-    "@vue/server-renderer" "3.2.24"
-    "@vue/shared" "3.2.24"
+    "@vue/compiler-dom" "3.2.26"
+    "@vue/compiler-sfc" "3.2.26"
+    "@vue/runtime-dom" "3.2.26"
+    "@vue/server-renderer" "3.2.26"
+    "@vue/shared" "3.2.26"
 
 vuedraggable@4.0.1:
   version "4.0.1"
@@ -6510,10 +6450,10 @@ wrappy@1:
   resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
   integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
 
-ws@8.3.0:
-  version "8.3.0"
-  resolved "https://registry.yarnpkg.com/ws/-/ws-8.3.0.tgz#7185e252c8973a60d57170175ff55fdbd116070d"
-  integrity sha512-Gs5EZtpqZzLvmIM59w4igITU57lrtYVFneaa434VROv4thzJyV6UjIL3D42lslWlI+D4KzLYnxSwtfuiO79sNw==
+ws@8.4.0:
+  version "8.4.0"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-8.4.0.tgz#f05e982a0a88c604080e8581576e2a063802bed6"
+  integrity sha512-IHVsKe2pjajSUIl4KYMQOdlyliovpEPquKkqbwswulszzI7r0SfQrxnXdWAEqOlDCLrVSJzo+O1hAwdog2sKSQ==
 
 xml-js@^1.6.11:
   version "1.6.11"
diff --git a/packages/shared/.eslintrc.js b/packages/shared/.eslintrc.js
index dbdd148ac..9d7d4159e 100644
--- a/packages/shared/.eslintrc.js
+++ b/packages/shared/.eslintrc.js
@@ -56,6 +56,7 @@ module.exports = {
 		'object-curly-spacing': ['error', 'always'],
 		'space-infix-ops': ['error'],
 		'space-before-blocks': ['error', 'always'],
+		'@typescript-eslint/no-unnecessary-condition': ['error'],
 		'@typescript-eslint/no-var-requires': ['warn'],
 		'@typescript-eslint/no-inferrable-types': ['warn'],
 		'@typescript-eslint/no-empty-function': ['off'],