diff --git a/.config/docker_example.env b/.config/docker_example.env
index 7a0261524b..4fe8e76b78 100644
--- a/.config/docker_example.env
+++ b/.config/docker_example.env
@@ -2,3 +2,4 @@
 POSTGRES_PASSWORD=example-misskey-pass
 POSTGRES_USER=example-misskey-user
 POSTGRES_DB=misskey
+DATABASE_URL="postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}"
diff --git a/.config/docker_example.yml b/.config/docker_example.yml
index c6c83a98bf..296237c95d 100644
--- a/.config/docker_example.yml
+++ b/.config/docker_example.yml
@@ -2,6 +2,63 @@
 # Misskey configuration
 #━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
+#   ┌──────────────────────────────┐
+#───┘ a boring but important thing └────────────────────────────
+
+#
+# First of all, let me tell you a story that may possibly be
+# boring to you and possibly important to you.
+#
+# Misskey is licensed under the AGPLv3 license. This license is
+# known to be often misunderstood.  Please read the following
+# instructions carefully and select the appropriate option so
+# that you do not negligently cause a license violation.
+#
+
+# --------
+# Option 1: If you host Misskey AS-IS (without any changes to
+#           the source code. forks are not included).
+#
+# Step 1: Congratulations! You don't need to do anything.
+
+# --------
+# Option 2: If you have made changes to the source code (forks
+#           are included) and publish a Git repository of source
+#           code.  There should be no access restrictions on
+#           this repository.  Strictly speaking, it doesn't have
+#           to be a Git repository, but you'll probably use Git!
+#
+# Step 1: Build and run the Misskey server first.
+# Step 2: Open <https://your.misskey.example/admin/settings> in
+#         your browser with the administrator account.
+# Step 3: Enter the URL of your Git repository in the
+#         "Repository URL" field.
+
+# --------
+# Option 3: If neither of the above applies to you.
+#           (In this case, the source code should be published
+#           on the Misskey interface.  IT IS NOT ENOUGH TO
+#           DISCLOSE THE SOURCE CODE WEHN A USER REQUESTS IT BY
+#           E-MAIL OR OTHER MEANS.  If you are not satisfied
+#           with this, it is recommended that you read the
+#           license again carefully.  Anyway, enabling this
+#           option will automatically generate and publish a
+#           tarball at build time, protecting you from
+#           inadvertent license violations. (There is no legal
+#           guarantee, of course.)  The tarball will generated
+#           from the root directory of your codebase.  So it is
+#           also recommended to check <built/tarball> directory
+#           once after building and before activating the server
+#           to avoid ACCIDENTAL LEAKING OF SENSITIVE INFORMATION.
+#           To prevent certain files from being included in the
+#           tarball, add a glob pattern after line 15 in
+#           <scripts/tarball.mjs>.  DO NOT FORGET TO BUILD AFTER
+#           ENABLING THIS OPTION!)
+#
+# Step 1: Uncomment the following line.
+#
+# publishTarballInsteadOfProvideRepositoryUrl: true
+
 #   ┌─────┐
 #───┘ URL └─────────────────────────────────────────────────────
 
@@ -148,14 +205,14 @@ id: 'aidx'
 # Job concurrency per worker
 # deliverJobConcurrency: 128
 # inboxJobConcurrency: 16
-# relashionshipJobConcurrency: 16
-#  What's relashionshipJob?:
+# relationshipJobConcurrency: 16
+#  What's relationshipJob?:
 #   Follow, unfollow, block and unblock(ings) while following-imports, etc. or account migrations.
 
 # Job rate limiter
 # deliverJobPerSec: 128
 # inboxJobPerSec: 32
-# relashionshipJobPerSec: 64
+# relationshipJobPerSec: 64
 
 # Job attempts
 # deliverJobMaxAttempts: 12
diff --git a/.config/example.yml b/.config/example.yml
index 4aa7757c61..c037a280b6 100644
--- a/.config/example.yml
+++ b/.config/example.yml
@@ -2,6 +2,63 @@
 # Misskey configuration
 #━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 
+#   ┌──────────────────────────────┐
+#───┘ a boring but important thing └────────────────────────────
+
+#
+# First of all, let me tell you a story that may possibly be
+# boring to you and possibly important to you.
+#
+# Misskey is licensed under the AGPLv3 license. This license is
+# known to be often misunderstood.  Please read the following
+# instructions carefully and select the appropriate option so
+# that you do not negligently cause a license violation.
+#
+
+# --------
+# Option 1: If you host Misskey AS-IS (without any changes to
+#           the source code. forks are not included).
+#
+# Step 1: Congratulations! You don't need to do anything.
+
+# --------
+# Option 2: If you have made changes to the source code (forks
+#           are included) and publish a Git repository of source
+#           code.  There should be no access restrictions on
+#           this repository.  Strictly speaking, it doesn't have
+#           to be a Git repository, but you'll probably use Git!
+#
+# Step 1: Build and run the Misskey server first.
+# Step 2: Open <https://your.misskey.example/admin/settings> in
+#         your browser with the administrator account.
+# Step 3: Enter the URL of your Git repository in the
+#         "Repository URL" field.
+
+# --------
+# Option 3: If neither of the above applies to you.
+#           (In this case, the source code should be published
+#           on the Misskey interface.  IT IS NOT ENOUGH TO
+#           DISCLOSE THE SOURCE CODE WEHN A USER REQUESTS IT BY
+#           E-MAIL OR OTHER MEANS.  If you are not satisfied
+#           with this, it is recommended that you read the
+#           license again carefully.  Anyway, enabling this
+#           option will automatically generate and publish a
+#           tarball at build time, protecting you from
+#           inadvertent license violations. (There is no legal
+#           guarantee, of course.)  The tarball will generated
+#           from the root directory of your codebase.  So it is
+#           also recommended to check <built/tarball> directory
+#           once after building and before activating the server
+#           to avoid ACCIDENTAL LEAKING OF SENSITIVE INFORMATION.
+#           To prevent certain files from being included in the
+#           tarball, add a glob pattern after line 15 in
+#           <scripts/tarball.mjs>.  DO NOT FORGET TO BUILD AFTER
+#           ENABLING THIS OPTION!)
+#
+# Step 1: Uncomment the following line.
+#
+# publishTarballInsteadOfProvideRepositoryUrl: true
+
 #   ┌─────┐
 #───┘ URL └─────────────────────────────────────────────────────
 
@@ -118,7 +175,7 @@ redis:
 #   ┌───────────────────────────┐
 #───┘ MeiliSearch configuration └─────────────────────────────
 
-# You can set scope to local (default value) or global 
+# You can set scope to local (default value) or global
 # (include notes from remote).
 
 #meilisearch:
@@ -160,14 +217,14 @@ id: 'aidx'
 # Job concurrency per worker
 #deliverJobConcurrency: 128
 #inboxJobConcurrency: 16
-#relashionshipJobConcurrency: 16
-# What's relashionshipJob?:
+#relationshipJobConcurrency: 16
+# What's relationshipJob?:
 #  Follow, unfollow, block and unblock(ings) while following-imports, etc. or account migrations.
 
 # Job rate limiter
 #deliverJobPerSec: 128
 #inboxJobPerSec: 32
-#relashionshipJobPerSec: 64
+#relationshipJobPerSec: 64
 
 # Job attempts
 #deliverJobMaxAttempts: 12
@@ -219,7 +276,7 @@ signToActivityPubGet: true
 checkActivityPubGetSignature: false
 
 # For security reasons, uploading attachments from the intranet is prohibited,
-# but exceptions can be made from the following settings. Default value is "undefined". 
+# but exceptions can be made from the following settings. Default value is "undefined".
 # Read changelog to learn more (Improvements of 12.90.0 (2021/09/04)).
 #allowedPrivateNetworks: [
 #  '127.0.0.1/32'
diff --git a/.forgejo/workflows/docker-develop.yml b/.forgejo/workflows/docker-develop.yml
deleted file mode 100644
index 0c8338c4df..0000000000
--- a/.forgejo/workflows/docker-develop.yml
+++ /dev/null
@@ -1,58 +0,0 @@
-name: Publish Docker image (develop)
-
-on:
-  push:
-    branches:
-      - develop
-    paths:
-      - packages/**
-      - locales/**
-  workflow_dispatch:
-
-env:
-  REGISTRY: git.joinsharkey.org
-
-jobs:
-  push_to_registry:
-    name: Push Docker image to GHCR
-    runs-on: docker
-    steps:
-      - name: install packages
-        run: apt-get update && apt-get install -y wget git curl
-      - uses: https://code.forgejo.org/actions/setup-node@v3
-        with:
-          node-version: 20
-      - name: Install docker
-        run: |
-          echo deb http://deb.debian.org/debian bullseye-backports main | tee /etc/apt/sources.list.d/backports.list && apt-get -qq update
-          DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -qq -y -t bullseye-backports docker.io
-      - name: Check out the repo
-        uses: actions/checkout@v4.1.1
-      - name: Set up Docker Buildx
-        id: buildx
-        uses: https://github.com/docker/setup-buildx-action@v3.0.0
-        with:
-          platforms: linux/amd64,linux/arm64
-      - name: Docker meta
-        id: meta
-        uses: https://github.com/docker/metadata-action@v5
-        with:
-          images: ${{ env.REGISTRY }}/sharkey/sharkey
-      - name: Log in to GHCR
-        uses: https://github.com/docker/login-action@v3
-        with:
-          registry: ${{ env.REGISTRY }}
-          username: Marie
-          password: ${{ secrets.TOKEN }}
-      - name: Build and Push to GHCR
-        id: build
-        uses: https://github.com/docker/build-push-action@v5
-        with:
-          builder: ${{ steps.buildx.outputs.name }}
-          context: .
-          push: true
-          platforms: ${{ steps.buildx.outputs.platforms }}
-          provenance: false
-          tags:  ${{ env.REGISTRY }}/sharkey/sharkey:develop
-          labels: develop
-          build-args: NODE_ENV=development
\ No newline at end of file
diff --git a/.forgejo/workflows/docker.yml b/.forgejo/workflows/docker.yml
deleted file mode 100644
index 155c5f7deb..0000000000
--- a/.forgejo/workflows/docker.yml
+++ /dev/null
@@ -1,62 +0,0 @@
-name: Publish Docker image
-
-on:
-  release:
-    types: [published]
-  workflow_dispatch:
-
-env:
-  REGISTRY: git.joinsharkey.org
-
-jobs:
-  push_to_registry:
-    name: Push Docker image to GHCR
-    runs-on: docker
-
-    steps:
-      - name: install packages
-        run: apt-get update && apt-get install -y wget git curl
-      - uses: https://code.forgejo.org/actions/setup-node@v3
-        with:
-          node-version: 20
-      - name: Install docker
-        run: |
-          echo deb http://deb.debian.org/debian bullseye-backports main | tee /etc/apt/sources.list.d/backports.list && apt-get -qq update
-          DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -qq -y -t bullseye-backports docker.io
-      - name: Check out the repo
-        uses: actions/checkout@v4.1.1
-      - name: Set up Docker Buildx
-        id: buildx
-        uses: https://github.com/docker/setup-buildx-action@v3.0.0
-        with:
-          platforms: linux/amd64,linux/arm64
-      - name: Docker meta
-        id: meta
-        uses: https://github.com/docker/metadata-action@v5
-        with:
-          images: ${{ env.REGISTRY }}/sharkey/sharkey
-          tags: |
-            type=edge
-            type=ref,event=pr
-            type=ref,event=branch
-            type=semver,pattern={{version}}
-            type=semver,pattern={{major}}.{{minor}}
-            type=semver,pattern={{major}}
-            type=raw,value=stable
-      - name: Log in to GHCR
-        uses: https://github.com/docker/login-action@v3
-        with:
-          registry: ${{ env.REGISTRY }}
-          username: Marie
-          password: ${{ secrets.TOKEN }}
-      - name: Build and Push to GHCR
-        id: build
-        uses: https://github.com/docker/build-push-action@v5
-        with:
-          builder: ${{ steps.buildx.outputs.name }}
-          context: .
-          push: true
-          platforms: ${{ steps.buildx.outputs.platforms }}
-          provenance: false
-          tags: ${{ steps.meta.outputs.tags }}
-          labels: ${{ steps.meta.outputs.labels }}
\ No newline at end of file
diff --git a/.gitea/ISSUE_TEMPLATE/01_bug-report.yml b/.gitea/ISSUE_TEMPLATE/01_bug-report.yml
deleted file mode 100644
index 6282cc43f9..0000000000
--- a/.gitea/ISSUE_TEMPLATE/01_bug-report.yml
+++ /dev/null
@@ -1,97 +0,0 @@
-name: 🐛 Bug Report
-description: Create a report to help us improve
-title: 'bug: '
-
-body:
-  - type: markdown
-    attributes:
-      value: |
-        Thanks for reporting!
-        First, in order to avoid duplicate Issues, please search to see if the problem you found has already been reported.
-        Also, If you are NOT owner/admin of server, PLEASE DONT REPORT SERVER SPECIFIC ISSUES TO HERE! (e.g. feature XXX is not working in misskey.example) Please try with another misskey servers, and if your issue is only reproducible with specific server, contact your server's owner/admin first.
-
-  - type: textarea
-    attributes:
-      label: 💡 Summary
-      description: Tell us what the bug is
-    validations:
-      required: true
-
-  - type: textarea
-    attributes:
-      label: 🥰 Expected Behavior
-      description: Tell us what should happen
-    validations:
-      required: true
-
-  - type: textarea
-    attributes:
-      label: 🤬 Actual Behavior
-      description: |
-        Tell us what happens instead of the expected behavior.
-        Please include errors from the developer console and/or server log files if you have access to them.
-    validations:
-      required: true
-
-  - type: textarea
-    attributes:
-      label: 📝 Steps to Reproduce
-      placeholder: |
-        1.
-        2.
-        3.
-    validations:
-      required: false
-
-  - type: textarea
-    attributes:
-      label: 💻 Frontend Environment
-      description: |
-        Tell us where on the platform it happens
-        DO NOT WRITE "latest". Please provide the specific version.
-
-        Examples:
-          * Model and OS of the device(s): MacBook Pro (14inch, 2021), macOS Ventura 13.4
-          * Browser: Chrome 113.0.5672.126
-          * Server URL: misskey.io
-          * Misskey: 13.x.x
-      value: |
-        * Model and OS of the device(s):
-        * Browser:
-        * Server URL:
-        * Misskey:
-      render: markdown
-    validations:
-      required: false
-
-  - type: textarea
-    attributes:
-      label: 🛰 Backend Environment (for server admin)
-      description: |
-        Tell us where on the platform it happens
-        DO NOT WRITE "latest". Please provide the specific version.
-        If you are using a managed service, put that after the version.
-
-        Examples:
-          * Installation Method or Hosting Service: docker compose, k8s/docker, systemd, "Misskey install shell script", development environment
-          * Misskey: 13.x.x
-          * Node: 20.x.x
-          * PostgreSQL: 15.x.x
-          * Redis: 7.x.x
-          * OS and Architecture: Ubuntu 22.04.2 LTS aarch64
-      value: |
-        * Installation Method or Hosting Service:
-        * Misskey:
-        * Node:
-        * PostgreSQL:
-        * Redis:
-        * OS and Architecture:
-      render: markdown
-    validations:
-      required: false
-
-  - type: checkboxes
-    attributes:
-      label: Do you want to address this bug yourself?
-      options:
-        - label: Yes, I will patch the bug myself and send a pull request
diff --git a/.gitea/ISSUE_TEMPLATE/02_feature-request.yml b/.gitea/ISSUE_TEMPLATE/02_feature-request.yml
deleted file mode 100644
index d3bf64d869..0000000000
--- a/.gitea/ISSUE_TEMPLATE/02_feature-request.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-name: ✨ Feature Request
-description: Suggest an idea for this project
-title: 'feat: '
-
-body:
-  - type: textarea
-    attributes:
-      label: Summary
-      description: Tell us what the suggestion is
-    validations:
-      required: true
-  - type: textarea
-    attributes:
-      label: Purpose
-      description: Describe the specific problem or need you think this feature will solve, and who it will help.
-    validations:
-      required: true
-  - type: checkboxes
-    attributes:
-      label: Do you want to implement this feature yourself?
-      options:
-        - label: Yes, I will implement this by myself and send a pull request
\ No newline at end of file
diff --git a/.gitea/ISSUE_TEMPLATE/config.yml b/.gitea/ISSUE_TEMPLATE/config.yml
deleted file mode 100644
index b845c1c9ac..0000000000
--- a/.gitea/ISSUE_TEMPLATE/config.yml
+++ /dev/null
@@ -1,4 +0,0 @@
-contact_links:
-  - name: 💬 Transfem.org Discord
-    url: https://discord.gg/HJcAanTR6H
-    about: Chat freely about Sharkey
diff --git a/.gitea/pull_request_template.md b/.gitea/pull_request_template.md
deleted file mode 100644
index 63eb2ab623..0000000000
--- a/.gitea/pull_request_template.md
+++ /dev/null
@@ -1,24 +0,0 @@
-<!-- ℹ お読みください / README
-PRありがとうございます! PRを作成する前に、コントリビューションガイドをご確認ください:
-Thank you for your PR! Before creating a PR, please check the contribution guide:
-https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md
--->
-	
-## What
-<!-- このPRで何をしたのか? どう変わるのか? -->
-<!-- What did you do with this PR? How will it change things? -->
-	
-## Why
-<!-- なぜそうするのか? どういう意図なのか? 何が困っているのか? -->
-<!-- Why do you do it? What are your intentions? What is the problem? -->
-	
-## Additional info (optional)
-<!-- テスト観点など -->
-<!-- Test perspective, etc -->
-	
-## Checklist
-- [ ] Read the [contribution guide](https://github.com/misskey-dev/misskey/blob/develop/CONTRIBUTING.md)
-- [ ] Test working in a local environment
-- [ ] (If needed) Add story of storybook
-- [ ] (If needed) Update CHANGELOG.md
-- [ ] (If possible) Add tests
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 11e69b2621..2b6a5c1ebf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,6 +41,7 @@ docker-compose.yml
 # misskey
 /build
 built
+built-test
 /data
 /.cache-loader
 /db
@@ -57,6 +58,7 @@ api-docs.json
 ormconfig.json
 temp
 /packages/frontend/src/**/*.stories.ts
+tsdoc-metadata.json
 
 # Sharkey
 /packages/megalodon/lib
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index aea1307ca2..be00aa7097 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -11,7 +11,7 @@ testCommit:
   variables:
     POSTGRES_PASSWORD: ci
   script:
-    - apt-get update && apt-get install -y git wget curl build-essential python3 
+    - apt-get update && apt-get install -y git wget curl build-essential python3
     - cp .config/ci.yml .config/default.yml
     - corepack enable
     - corepack prepare pnpm@latest --activate
@@ -28,6 +28,7 @@ testCommit:
      - packages/*/node_modules/
   only:
     - develop
+    - merge_requests
     - stable
 
 getImageTag:
@@ -55,6 +56,7 @@ getImageTag:
     - stable
     - develop
     - tags
+
 buildDocker:
   stage: deploy
   needs:
@@ -79,6 +81,7 @@ buildDocker:
     - stable
     - develop
     - tags
+
 mergeManifests:
   stage: deploy
   needs:
diff --git a/.gitlab/issue_templates/bug.md b/.gitlab/issue_templates/bug.md
new file mode 100644
index 0000000000..6914647570
--- /dev/null
+++ b/.gitlab/issue_templates/bug.md
@@ -0,0 +1,29 @@
+<!-- 💖 Thanks for taking the time to fill out this bug report!
+💁 Having trouble with deployment? [Ask the support chat.](https://discord.gg/4qUhaeeHmm)
+🔒 Found a security vulnerability? [Please disclose it responsibly.](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/SECURITY.md)
+🤝 By submitting this feature request, you agree to follow our [Contribution Guidelines.](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/CONTRIBUTING.md) -->
+
+**What happened?** _(Please give us a brief description of what happened.)_
+
+**What did you expect to happen?** _(Please give us a brief description of what you expected to happen.)_
+
+**Version** _(What version of Sharkey is your instance running? You can find this by clicking your instance's logo at the top left and then clicking instance information.)_
+
+**Instance** _(What instance of Sharkey are you using?)_
+
+**What type of issue is this?** _(If this happens on your device and has to do with the user interface, it's client-side. If this happens on either with the API or the backend, or you got a server-side error in the client, it's server-side.)_
+
+**What browser are you using? (Client-side issues only)**
+
+**What operating system are you using? (Client-side issues only)**
+
+**How do you deploy Sharkey on your server? (Server-side issues only)**
+
+**What operating system are you using? (Server-side issues only)**
+
+**Relevant log output** _(Please copy and paste any relevant log output. You can find your log by inspecting the page, and going to the "console" tab. This will be automatically formatted into code, so no need for backticks.)_
+
+**Contribution Guidelines**
+By submitting this issue, you agree to follow our [Contribution Guidelines](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/CONTRIBUTING.md)
+- [ ] I agree to follow this project's Contribution Guidelines
+- [ ] I have searched the issue tracker for similar issues, and this is not a duplicate.
diff --git a/.gitlab/issue_templates/feature.md b/.gitlab/issue_templates/feature.md
new file mode 100644
index 0000000000..d4235eb5a3
--- /dev/null
+++ b/.gitlab/issue_templates/feature.md
@@ -0,0 +1,17 @@
+<!-- 💖 Thanks for taking the time to fill out this bug report!
+💁 Having trouble with deployment? [Ask the support chat.](https://discord.gg/4qUhaeeHmm)
+🔒 Found a security vulnerability? [Please disclose it responsibly.](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/SECURITY.md)
+🤝 By submitting this feature request, you agree to follow our [Contribution Guidelines.](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/CONTRIBUTING.md) -->
+
+**What feature would you like implemented?** _(Please give us a brief description of what you'd like.)_
+
+**Why should we add this feature?** _(Please give us a brief description of why your feature is important.)_
+
+**Version** _(What version of Sharkey is your instance running? You can find this by clicking your instance's logo at the top left and then clicking instance information.)_
+
+**Instance** _(What instance of Sharkey are you using?)_
+
+**Contribution Guidelines**
+By submitting this issue, you agree to follow our [Contribution Guidelines](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/CONTRIBUTING.md)
+- [ ] I agree to follow this project's Contribution Guidelines
+- [ ] I have searched the issue tracker for similar requests, and this is not a duplicate.
diff --git a/.gitlab/merge_request_templates/default.md b/.gitlab/merge_request_templates/default.md
new file mode 100644
index 0000000000..18bffa5419
--- /dev/null
+++ b/.gitlab/merge_request_templates/default.md
@@ -0,0 +1,11 @@
+<!-- Thanks for taking the time to make Sharkey better!  -->
+
+**What does this PR do?** _(Please give us a brief description of what this PR does.)_
+
+**Contribution Guidelines**
+By submitting this merge request, you agree to follow our [Contribution Guidelines](https://activitypub.software/TransFem-org/Sharkey/-/blob/develop/CONTRIBUTING.md)
+- [ ] I agree to follow this project's Contribution Guidelines
+- [ ] I have made sure to test this pull request
+
+<!-- Uncomment if your merge request has multiple authors -->
+<!-- Co-authored-by: Name <email@email.com> -->
diff --git a/.gitmodules b/.gitmodules
index 225a69a652..a3ca76cc96 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,3 +4,6 @@
 [submodule "fluent-emojis"]
 	path = fluent-emojis
 	url = https://github.com/misskey-dev/emojis.git
+[submodule "tossface-emojis"]
+	path = tossface-emojis
+	url = https://activitypub.software/TransFem-org/tossface-emojis.git
diff --git a/.npmrc b/.npmrc
index da702c3af5..b949431c77 100644
--- a/.npmrc
+++ b/.npmrc
@@ -1,2 +1,2 @@
-@sharkey:registry=https://git.joinsharkey.org/api/packages/Sharkey/npm/
+@transfem-org:registry=https://activitypub.software/api/v4/packages/npm/
 engine-strict = true
diff --git a/.forgejo/workflows/lint.yml b/.old/workflows/lint.yml
similarity index 84%
rename from .forgejo/workflows/lint.yml
rename to .old/workflows/lint.yml
index 0a773d5fb0..8ad19b45c8 100644
--- a/.forgejo/workflows/lint.yml
+++ b/.old/workflows/lint.yml
@@ -8,6 +8,12 @@ on:
     paths:
       - packages/**
   pull_request:
+    paths:
+      - packages/backend/**
+      - packages/frontend/**
+      - packages/sw/**
+      - packages/misskey-js/**
+      - packages/shared/.eslintrc.js
 
 jobs:
   pnpm_install:
@@ -82,4 +88,8 @@ jobs:
     - run: pnpm i --frozen-lockfile
     - run: pnpm --filter misskey-js run build
       if: ${{ matrix.workspace == 'backend' }}
+    - run: pnpm --filter misskey-reversi run build:tsc
+      if: ${{ matrix.workspace == 'backend' }}
+    - run: pnpm --filter misskey-bubble-game run build
+      if: ${{ matrix.workspace == 'backend' }}
     - run: pnpm --filter ${{ matrix.workspace }} run typecheck
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 30e2e57b7d..bafee277d2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,5 @@
 <!--
-## 2023.x.x (unreleased)
+## 202x.x.x (unreleased)
 
 ### General
 -
@@ -12,6 +12,146 @@
 
 -->
 
+## 2024.3.1
+
+### General
+-
+
+### Client
+- Fix: 絵文字関係の不具合を修正 (#13485)
+  - 履歴に残っている or ピン留めされた絵文字がコントロールパネルより削除されていた際にリアクションデッキが表示できなくなる
+  - Unicode絵文字が履歴に残っている or ピン留めされているとリアクションデッキが表示できなくなる
+- Fix: カスタム絵文字の画像読み込みに失敗した際はテキストではなくダミー画像を表示 #13487
+
+### Server
+-
+
+## 2024.3.0
+
+### General
+- Enhance: 投稿者のロールに応じて、一つのノートに含むことのできるメンションとダイレクト投稿の宛先の人数に上限を設定できるように
+  * デフォルトのメンション上限は20アカウントに設定されます。(管理者はベースロールの設定で変更可能です。)
+  * 連合の問い合わせに応答しないサーバーのリモートユーザーへのメンションは、上限の人数に含めない実装になっています。
+- Enhance: 通知がミュート、凍結を考慮するようになりました
+- Enhance: サーバーごとにモデレーションノートを残せるように
+- Enhance: コンディショナルロールの条件に「マニュアルロールへのアサイン」を追加
+- Enhance: 通知の受信設定に「フォロー中またはフォロワー」を追加
+- Enhance: 通知の履歴をリセットできるように
+- Fix: ダイレクトなノートに対してはダイレクトでしか返信できないように
+
+### Client
+- Enhance: ノート作成画面のファイル添付メニューの区切り線の位置を調整
+- Fix: syuilo/misskeyの時代からあるインスタンスが改変されたバージョンであると誤認識される問題
+- Fix: MFMのオートコンプリートが出るべき状況で出ないことがある問題を修正
+- Fix: チャートのラベルが消えている問題を修正
+- Fix: 画面表示後最初の音声再生が爆音になることがある問題を修正
+- Fix: 設定のバックアップ作成時に名前を入力しなかった場合、ローカライゼーションがおかしくなる問題を修正
+- Fix: ページ`/admin/emojis`の絵文字編集ダイアログで「リアクションとして使えるロール」を追加する際に何も選択せずOKを押下すると画面が固まる問題を修正
+- Fix: 絵文字サジェストの順位で、絵文字自体の名前が同じものよりもタグで一致しているものが優先されてしまう問題を修正
+- Fix: ユーザの情報のポップアップが消えなくなることがある問題を修正
+
+### Server
+- Enhance: エンドポイント`flash/update`の`flashId`以外のパラメータは必須ではなくなりました
+- Fix: nodeinfoにenableMcaptchaとenableTurnstileが無いのを修正
+- Fix: 破損した通知をクライアントに送信しないように
+	* 通知欄が無限にリロードされる問題が改善する可能性があります
+- Fix: 禁止キーワードを含むノートがDelayed Queueに追加されて再処理される問題を修正
+- Fix: 自分がフォローしていないアカウントのフォロワー限定ノートが閲覧できることがある問題を修正
+- Fix: タイムラインのオプションで「リノートを表示」を無効にしている際、投票のみの引用リノートが流れてこない問題を修正
+- Fix: エンドポイント`admin/emoji/update`の各種修正
+  - 必須パラメータを`id`または`name`のいずれかのみに
+  - `id`の代わりに`name`で絵文字を指定可能に(`id`・`name`両指定時は従来通り`name`を変更する挙動)
+  - `category`および`licence`が指定なしの時勝手にnullに上書きされる挙動を修正
+- Fix: 通知の受信設定で「相互フォロー」が正しく動作しない問題を修正
+
+## 2024.2.0
+
+### Note
+- 外部サイトからプラグインをインストールする場合のパスが`/install-extentions`から`/install-extensions`に変わります。以前のパスからは自動でリダイレクトされるようになっていますが、新しいパスに変更することをお勧めします。
+
+### General
+- Feat: [mCaptcha](https://github.com/mCaptcha/mCaptcha)のサポートを追加
+- Feat: Add support for TrueMail
+- Feat: AGPLv3ライセンスに誤って違反するのを防止する機能を追加
+	- 管理者がrepositoryUrlを変更したり、またはソースコードを直接頒布することを選択できるようになります
+	- 本体のソースコードに改変を加えた際に、ライセンスに基づく適切な案内を表示します
+- Enhance: モデレーターはすべてのユーザーのリアクション一覧を見られるように
+- Fix: リストライムラインの「リノートを表示」が正しく機能しない問題を修正
+- Fix: リモートユーザーのリアクション一覧がすべて見えてしまうのを修正
+  * すべてのリモートユーザーのリアクション一覧を見えないようにします
+- Fix: 特定のキーワード及び正規表現にマッチする文字列を含むノートが投稿された際、エラーに出来るような設定項目を追加 #13207
+  * デフォルトは空欄なので適用前と同等の動作になります
+
+### Client
+- Feat: 新しいゲームを追加
+- Feat: 音声・映像プレイヤーを追加
+- Feat: 絵文字の詳細ダイアログを追加
+- Feat: 枠線をつけるMFM`$[border.width=1,style=solid,color=fff,radius=0 ...]`を追加
+  - デフォルトで枠線からはみ出る部分が隠されるようにしました。初期と同じ挙動にするには`$[border.noclip`が必要です
+- Feat: スワイプでタブを切り替えられるように
+- Enhance: MFM等のコードブロックに全文コピー用のボタンを追加
+- Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように
+- Enhance: チャンネルノートのピン留めをノートのメニューからできるように
+- Enhance: 管理者の場合はAPI tokenの発行画面で管理機能に関する権限を付与できるように
+- Enhance: AiScriptを0.17.0に更新 [CHANGELOG](https://github.com/aiscript-dev/aiscript/blob/bb89d132b633a622d3cb0eff0d0cc7e476c0cfdd/CHANGELOG.md)
+  - 配列の範囲外・非整数のインデックスへの代入が完全禁止になるので注意
+- Enhance: 絵文字ピッカー・オートコンプリートで、完全一致した絵文字を優先的に表示するように
+- Enhance: Playの説明欄にMFMを使えるように
+- Enhance: チャンネルノートの場合は詳細ページからその前後のノートを見れるように
+- Enhance: 季節に応じた画面の演出を南半球でも利用できるように
+- Enhance: タイムラインフィルターの設定をすべて保持できるように
+	- 今までの「TLに他の人への返信を含める」設定は一旦リセットされます
+- Enhance: タイムラインフィルターに「センシティブなファイルを含むノートを表示」を追加
+- Enhance: ノート作成画面のファイル添付メニューから直接ファイルを削除できるように
+- Enhance: MFMの属性でオートコンプリートが使用できるように #12735
+- Enhance: 絵文字編集ダイアログをモーダルではなくウィンドウで表示するように
+- Enhance: リモートのユーザーはメニューから直接リモートで表示できるように
+- Enhance: リモートへの引用リノートと同一のリンクにはリンクプレビューを表示しないように
+- Enhance: コードのシンタックスハイライトにテーマを適用できるように
+- Enhance: リアクション権限がない場合、ハートにフォールバックするのではなくリアクションピッカーなどから打てないように
+  - リモートのユーザーにローカルのみのカスタム絵文字をリアクションしようとした場合
+  - センシティブなリアクションを認めていないユーザーにセンシティブなカスタム絵文字をリアクションしようとした場合
+  - ロールが必要な絵文字をリアクションしようとした場合
+- Enhance: ページ遷移時にPlayerを閉じるように
+- Enhance: 通報ページのユーザをクリックした際にユーザをウィンドウで開くように
+- Enhance: ノートの通報時にリモートのノートであっても自インスタンスにおけるノートのリンクを含むように
+- Enhance: オフライン表示のデザインを改善・多言語対応
+- Fix: ネイティブモードの絵文字がモノクロにならないように
+- Fix: v2023.12.0で追加された「モデレーターがユーザーのアイコンもしくはバナー画像を未設定状態にできる機能」が管理画面上で正しく表示されていない問題を修正
+- Fix: AiScriptの`readline`関数が不正な値を返すことがある問題のv2023.12.0時点での修正がPlay以外に適用されていないのを修正
+- Fix: v2023.12.1で追加された`$[clickable ...]`および`onClickEv`が正しく機能していないのを修正
+- Fix: Renoteのキーボードショートカットが機能していなかった問題を修正
+- Fix: 投稿フォームでアンケートの日時指定をした状態で再読み込みをすると期日が復元されない問題を修正
+- Fix: アンケートを設定したノートを「削除して編集」をするとアンケートの期日が引き継がれず、リセットされてしまう問題を修正
+- Fix: デッキのプロファイル作成時に名前を空にできる問題を修正
+- Fix: テーマ作成時に名称が空欄でも作成できてしまう問題を修正
+- Fix: プラグインで`Plugin:register_note_post_interruptor`を使用すると、ノートが投稿できなくなる問題を修正
+- Fix: iOSで大きな画像を変換してアップロードできない問題を修正
+- Fix: 「アニメーション画像を再生しない」もしくは「データセーバー(アイコン)」を有効にしていても、アイコンデコレーションのアニメーションが停止されない問題を修正
+- Fix: 画像をクロップするとクロップ後の解像度が異様に低くなる問題の修正
+- Fix: 画像をクロップ時、正常に完了できない問題の修正
+- Fix: キャプションが空の画像をクロップするとキャプションにnullという文字列が入ってしまう問題の修正
+- Fix: プロフィールを編集してもリロードするまで反映されない問題を修正
+- Fix: エラー画像URLを設定した後解除すると,デフォルトの画像が表示されない問題の修正
+- Fix: MkCodeEditorで行がずれていってしまう問題の修正
+- Fix: Summaly proxy利用時にプレイヤーが動作しないことがあるのを修正 #13196
+
+### Server
+- Enhance: 連合先のレートリミットを超過した際にリトライするようになりました
+- Enhance: ActivityPub Deliver queueでBodyを事前処理するように (#12916)
+- Enhance: クリップをエクスポートできるように
+- Enhance: `/files`のファイルに対してHTTP Rangeリクエストを行えるように
+- Enhance: `api.json`のOpenAPI Specificationを3.1.0に更新
+- Enhance: 連合向けのノート配信を軽量化 #13192
+- Fix: `drive/files/update`でファイル名のバリデーションが機能していない問題を修正
+- Fix: `notes/create`で、`text`が空白文字のみで構成されているか`null`であって、かつ`text`だけであるリクエストに対するレスポンスが400になるように変更
+- Fix: `notes/create`で、`text`が空白文字のみで構成されていてかつリノート、ファイルまたは投票を含んでいるリクエストに対するレスポンスの`text`が`""`から`null`になるように変更
+- Fix: ipv4とipv6の両方が利用可能な環境でallowedPrivateNetworksが設定されていた場合プライベートipの検証ができていなかった問題を修正
+- Fix: properly handle cc followers
+- Fix: ジョブに関する設定の名前を修正 relashionshipJobPerSec -> relationshipJobPerSec
+- Fix: コントロールパネル->モデレーション->「誰でも新規登録できるようにする」の初期値をONからOFFに変更 #13122
+- Fix: リモートユーザーが復活してもキャッシュにより該当ユーザーのActivityが受け入れられないのを修正 #13273
+
 ## 2023.12.2
 
 ### General
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7f6c1f4f82..a3263bf6aa 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -122,6 +122,19 @@ command.
 If you have not changed it from the default, it will be "http://localhost:3000".
 If "port" in .config/default.yml is set to something other than 3000, you need to change the proxy settings in packages/frontend/vite.config.local-dev.ts.
 
+### `MK_DEV_PREFER=backend pnpm dev`
+pnpm dev has another mode with `MK_DEV_PREFER=backend`.
+
+```
+MK_DEV_PREFER=backend pnpm dev
+```
+
+- This mode is closer to the production environment than the default mode.
+- Vite runs behind the backend (the backend will proxy Vite at /vite).
+- You can see Misskey by accessing `http://localhost:3000` (Replace `3000` with the port configured with `port` in .config/default.yml).
+- To change the port of Vite, specify with `VITE_PORT` environment variable.
+- HMR may not work in some environments such as Windows.
+
 ### Dev Container
 Instead of running `pnpm` locally, you can use Dev Container to set up your development environment.
 To use Dev Container, open the project directory on VSCode with Dev Containers installed.  
@@ -286,18 +299,17 @@ export const argTypes = {
 			min: 1,
 			max: 4,
 		},
+	},
 };
 ```
 
 Also, you can use msw to mock API requests in the storybook. Creating a `MyComponent.stories.msw.ts` file to define the mock handlers.
 
 ```ts
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
 export const handlers = [
-	rest.post('/api/notes/timeline', (req, res, ctx) => {
-		return res(
-			ctx.json([]),
-		);
+	http.post('/api/notes/timeline', ({ request }) => {
+		return HttpResponse.json([]);
 	}),
 ];
 ```
diff --git a/COPYING b/COPYING
index c218443d42..6a5f3ca1d5 100644
--- a/COPYING
+++ b/COPYING
@@ -1,5 +1,5 @@
 Unless otherwise stated this repository is
-Copyright © 2014-2023 syuilo and contributers
+Copyright © 2014-2024 syuilo and contributors
 
 And is distributed under The GNU Affero General Public License Version 3, you should have received a copy of the license file as LICENSE.
 
diff --git a/Dockerfile b/Dockerfile
index 440c04e2df..8ad4bbbfb1 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,6 +1,6 @@
 # syntax = docker/dockerfile:1.4
 
-ARG NODE_VERSION=21.4.0-alpine3.18
+ARG NODE_VERSION=20.10.0-alpine3.18
 
 FROM node:${NODE_VERSION} as build
 
@@ -43,7 +43,12 @@ COPY --from=build /sharkey/packages/megalodon/lib ./packages/megalodon/lib
 COPY --from=build /sharkey/packages/megalodon/node_modules ./packages/megalodon/node_modules
 COPY --from=build /sharkey/packages/misskey-js/built ./packages/misskey-js/built
 COPY --from=build /sharkey/packages/misskey-js/node_modules ./packages/misskey-js/node_modules
+COPY --from=build /sharkey/packages/misskey-reversi/built ./packages/misskey-reversi/built
+COPY --from=build /sharkey/packages/misskey-reversi/node_modules ./packages/misskey-reversi/node_modules
+COPY --from=build /sharkey/packages/misskey-bubble-game/built ./packages/misskey-bubble-game/built
+COPY --from=build /sharkey/packages/misskey-bubble-game/node_modules ./packages/misskey-bubble-game/node_modules
 COPY --from=build /sharkey/fluent-emojis ./fluent-emojis
+COPY --from=build /sharkey/tossface-emojis/dist ./tossface-emojis/dist
 COPY --from=build /sharkey/sharkey-assets ./packages/frontend/assets
 
 COPY package.json ./package.json
@@ -55,6 +60,8 @@ COPY packages/backend/migration ./packages/backend/migration
 COPY packages/backend/assets ./packages/backend/assets
 COPY packages/megalodon/package.json ./packages/megalodon/package.json
 COPY packages/misskey-js/package.json ./packages/misskey-js/package.json
+COPY packages/misskey-reversi/package.json ./packages/misskey-reversi/package.json
+COPY packages/misskey-bubble-game/package.json ./packages/misskey-bubble-game/package.json
 
 ENV NODE_ENV=production
 RUN corepack enable
diff --git a/README.md b/README.md
index d3ba23c4e1..c002acbff9 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,13 @@
 <div align="center">
 <a href="https://joinsharkey.org/">
-	<img src="https://raw.githubusercontent.com/transfem-org/Sharkey/5180b4093f30e3bf3ff8d6b16751b69ebed9fb12/packages/frontend/assets/sharkey.svg" alt="Sharkey logo" style="border-radius:50%" width="400"/>
+	<img src="https://activitypub.software/TransFem-org/Sharkey/-/raw/develop/packages/frontend/assets/sharkey.svg" alt="Sharkey logo" style="border-radius:50%" width="300"/>
 </a>
 
 **🌎 **[Sharkey](https://joinsharkey.org/)** is an open source, decentralized social media platform that's free forever! 🚀**
 
 ---
 
-<a href="https://joinsharkey.org">
+<a href="https://fedidb.org/software/sharkey">
 		<img src="https://custom-icon-badges.herokuapp.com/badge/find_an-instance-acea31?logoColor=acea31&style=for-the-badge&logo=sharkey&labelColor=363B40" alt="find an instance"/></a>
 
 <a href="https://docs.joinsharkey.org/docs/install/fresh/">
@@ -21,8 +21,6 @@
 
 <a href="https://ko-fi.com/transfem">
 		<img src="https://custom-icon-badges.herokuapp.com/badge/donate-F96854?logoColor=F96854&style=for-the-badge&logo=kofi&labelColor=363B40" alt="donate"/></a>
-<a href="https://hosted.weblate.org/projects/sharkey/">
-		<img src="https://custom-icon-badges.herokuapp.com/badge/translate-sharkey-124437?logoColor=acea31&style=for-the-badge&logo=translate-sharkey&labelColor=363B40" alt="Translate Sharkey"/></a>
 
 ---
 
@@ -30,7 +28,7 @@
 
 <div>
 
-<a href="https://joinsharkey.org/"><img src="https://cdn.transfem.social/files/b2721164-e015-463e-b851-3e953dd0d9f9.webp" align="right" height="520px"/></a>
+<a href="https://joinsharkey.org/"><img src="https://cdn.shonk.social/files/b671c81c-58cf-4f13-bc96-af0b0c96c667.webp" align="right" height="520px"/></a>
 
 ## ✨ Features
 - **ActivityPub support**\
@@ -44,9 +42,9 @@ Sharkey makes some UI/UX improvements to make it easier to navigate
 - **Sign-Up Approval**\
 With Sharkey, you can enable sign-ups, subject to manual moderator approval and mandatory user-provided reasons for joining.
 - **Rich Web UI**\
-	Sharkey has a rich and easy to use Web UI!
-	It is highly customizable, from changing the layout and adding widgets to making custom themes.
-	Furthermore, plugins can be created using AiScript, an original programming language.
+       Sharkey has a rich and easy to use Web UI!
+       It is highly customizable, from changing the layout and adding widgets to making custom themes.
+       Furthermore, plugins can be created using AiScript, an original programming language.
 - And much more...
 
 </div>
diff --git a/ROADMAP.md b/ROADMAP.md
index 3077c41e73..509ecb9fe7 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -6,6 +6,7 @@ Also, the later tasks are more indefinite and are subject to change as developme
 This is the phase we are at now. We need to make a high-maintenance environment that can withstand future development.
 
 - ~~Make the number of type errors zero (backend)~~ → Done ✔️
+- Make the number of type errors zero (frontend)
 - Improve CI
 	- ~~Fix tests~~ → Done ✔️
 	- Fix random test failures - https://github.com/misskey-dev/misskey/issues/7985 and https://github.com/misskey-dev/misskey/issues/7986
diff --git a/SECURITY.md b/SECURITY.md
index fa00b700e9..cfc0614dd6 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -4,6 +4,6 @@ If you discover a security issue in Sharkey, please report it by sending an
 email to [admin@transfem.org](mailto:admin@transfem.org).
 
 This will allow us to assess the risk, and make a fix available before we add a
-bug report to the GitHub repository.
+bug report to the GitLab repository.
 
 Thanks for helping make Sharkey safe for everyone.
diff --git a/cypress/e2e/basic.cy.js b/cypress/e2e/basic.cy.js
index 5ab07c7480..604241d13c 100644
--- a/cypress/e2e/basic.cy.js
+++ b/cypress/e2e/basic.cy.js
@@ -161,11 +161,13 @@ describe('After user signed in', () => {
 	});
 
   it('successfully loads', () => {
-		cy.get('[data-cy-user-setup-continue]').should('be.visible');
+		// 表示に時間がかかるのでデフォルト秒数だとタイムアウトする
+		cy.get('[data-cy-user-setup-continue]', { timeout: 30000 }).should('be.visible');
   });
 
 	it('account setup wizard', () => {
-		cy.get('[data-cy-user-setup-continue]').click();
+		// 表示に時間がかかるのでデフォルト秒数だとタイムアウトする
+		cy.get('[data-cy-user-setup-continue]', { timeout: 30000 }).click();
 
 		cy.get('[data-cy-user-setup-user-name] input').type('ありす');
 		cy.get('[data-cy-user-setup-user-description] textarea').type('ほげ');
@@ -202,7 +204,8 @@ describe('After user setup', () => {
 		cy.login('alice', 'alice1234');
 
 		// アカウント初期設定ウィザード
-		cy.get('[data-cy-user-setup] [data-cy-modal-window-close]').click();
+		// 表示に時間がかかるのでデフォルト秒数だとタイムアウトする
+		cy.get('[data-cy-user-setup] [data-cy-modal-window-close]', { timeout: 30000 }).click();
 		cy.get('[data-cy-modal-dialog-ok]').click();
 	});
 
diff --git a/cypress/e2e/router.cy.js b/cypress/e2e/router.cy.js
new file mode 100644
index 0000000000..6de27be5f4
--- /dev/null
+++ b/cypress/e2e/router.cy.js
@@ -0,0 +1,30 @@
+describe('Router transition', () => {
+	describe('Redirect', () => {
+		// サーバの初期化。ルートのテストに関しては各describeごとに1度だけ実行で十分だと思う(使いまわした方が早い)
+		before(() => {
+			cy.resetState();
+
+			// インスタンス初期セットアップ
+			cy.registerUser('admin', 'pass', true);
+
+			// ユーザー作成
+			cy.registerUser('alice', 'alice1234');
+
+			cy.login('alice', 'alice1234');
+
+			// アカウント初期設定ウィザード
+			// 表示に時間がかかるのでデフォルト秒数だとタイムアウトする
+			cy.get('[data-cy-user-setup] [data-cy-modal-window-close]', { timeout: 30000 }).click();
+			cy.wait(500);
+			cy.get('[data-cy-modal-dialog-ok]').click();
+		});
+
+		it('redirect to user profile', () => {
+			// テストのためだけに用意されたリダイレクト用ルートに飛ぶ
+			cy.visit('/redirect-test');
+
+			// プロフィールページのURLであることを確認する
+			cy.url().should('include', '/@alice')
+		});
+	});
+});
diff --git a/docker-compose_example.yml b/docker-compose_example.yml
index c967f331a4..647f6f0c77 100644
--- a/docker-compose_example.yml
+++ b/docker-compose_example.yml
@@ -8,6 +8,7 @@ services:
     links:
       - db
       - redis
+#     - mcaptcha
 #     - meilisearch
     depends_on:
       db:
@@ -48,12 +49,43 @@ services:
       interval: 5s
       retries: 20
 
+#  mcaptcha:
+#    restart: always
+#    image: mcaptcha/mcaptcha:latest
+#    networks:
+#      internal_network:
+#      external_network:
+#        aliases:
+#          - localhost
+#    ports:
+#      - 7493:7493
+#    env_file:
+#      - .config/docker.env
+#    environment:
+#      PORT: 7493
+#      MCAPTCHA_redis_URL: "redis://mcaptcha_redis/"
+#    depends_on:
+#      db:
+#        condition: service_healthy
+#      mcaptcha_redis:
+#        condition: service_healthy
+#
+#  mcaptcha_redis:
+#    image: mcaptcha/cache:latest
+#    networks:
+#      - internal_network
+#    healthcheck:
+#      test: "redis-cli ping"
+#      interval: 5s
+#      retries: 20
+
 #  meilisearch:
 #    restart: always
 #    image: getmeili/meilisearch:v1.3.4
 #    environment:
 #      - MEILI_NO_ANALYTICS=true
 #      - MEILI_ENV=production
+#      - MEILI_MASTER_KEY=ChangeThis
 #    networks:
 #      - shonk
 #    volumes:
diff --git a/healthcheck.sh b/healthcheck.sh
index 16119fc49a..02f13576e9 100644
--- a/healthcheck.sh
+++ b/healthcheck.sh
@@ -1,6 +1,6 @@
 #!/bin/bash
 
-# SPDX-FileCopyrightText: syuilo and other misskey contributors
+# SPDX-FileCopyrightText: syuilo and misskey-project
 # SPDX-License-Identifier: AGPL-3.0-only
 
 PORT=$(grep '^port:' /sharkey/.config/default.yml | awk 'NR==1{print $2; exit}')
diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml
index 0a7d86cc89..17c8f24fa5 100644
--- a/locales/ar-SA.yml
+++ b/locales/ar-SA.yml
@@ -360,6 +360,8 @@ hcaptcha: "hCaptcha"
 enableHcaptcha: "فعّل hCaptcha"
 hcaptchaSiteKey: "مفتاح الموقع"
 hcaptchaSecretKey: "المفتاح السري"
+mcaptchaSiteKey: "مفتاح الموقع"
+mcaptchaSecretKey: "المفتاح السري"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "تمكين reCAPTCHA"
 recaptchaSiteKey: "مفتاح الموقع"
@@ -1009,7 +1011,10 @@ expired: "منتهية صلاحيته"
 icon: "الصورة الرمزية"
 replies: "رد"
 renotes: "أعد النشر"
+sourceCode: "الشفرة المصدرية"
 flip: "اقلب"
+lastNDays: "آخر {n} أيام"
+surrender: "ألغِ"
 _initialAccountSetting:
   accountCreated: "نجح إنشاء حسابك!"
   letsStartAccountSetup: "إذا كنت جديدًا لنعدّ حسابك الشخصي."
@@ -1414,6 +1419,7 @@ _profile:
 _exportOrImport:
   allNotes: "كل الملاحظات"
   favoritedNotes: " الملاحظات المفضلة"
+  clips: "مِشبك"
   followingList: "المتابَعون"
   muteList: "المستخدمون المكتومون"
   blockingList: "المستخدمون المحجوبون"
@@ -1561,3 +1567,6 @@ _moderationLogTypes:
   suspend: "علِق"
   resetPassword: "أعد تعيين كلمتك السرية"
   createInvitation: "ولِّد دعوة"
+_reversi:
+  total: "المجموع"
+
diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml
index 77ba3f0306..2a23cda06b 100644
--- a/locales/bn-BD.yml
+++ b/locales/bn-BD.yml
@@ -357,6 +357,8 @@ hcaptcha: "hCaptcha"
 enableHcaptcha: "hCaptcha চালু করুন"
 hcaptchaSiteKey: "সাইট কী"
 hcaptchaSecretKey: "সিক্রেট কী"
+mcaptchaSiteKey: "সাইট কী"
+mcaptchaSecretKey: "সিক্রেট কী"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "reCAPTCHA চালু করুন"
 recaptchaSiteKey: "সাইট কী"
@@ -853,6 +855,7 @@ youFollowing: "অনুসরণ করা হচ্ছে"
 icon: "প্রোফাইল ছবি"
 replies: "জবাব"
 renotes: "রিনোট"
+sourceCode: "সোর্স কোড"
 flip: "উল্টান"
 _role:
   priority: "অগ্রাধিকার"
@@ -1190,6 +1193,7 @@ _profile:
   changeBanner: "ব্যানার পরিবর্তন করুন"
 _exportOrImport:
   allNotes: "সকল নোট"
+  clips: "ক্লিপ"
   followingList: "অনুসরণ করা হচ্ছে"
   muteList: "মিউট"
   blockingList: "ব্লক"
@@ -1341,3 +1345,6 @@ _webhookSettings:
 _moderationLogTypes:
   suspend: "স্থগিত করা"
   resetPassword: "পাসওয়ার্ড রিসেট করুন"
+_reversi:
+  total: "মোট"
+
diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml
index ba1e44256e..ffdcc9787f 100644
--- a/locales/ca-ES.yml
+++ b/locales/ca-ES.yml
@@ -130,6 +130,7 @@ overwriteFromPinnedEmojis: "Sobreescriu des dels emojis fixats"
 reactionSettingDescription2: "Arrossega per reordenar, fes clic per suprimir, prem \"+\" per afegir."
 rememberNoteVisibility: "Recorda la configuració de visibilitat de les notes"
 attachCancel: "Eliminar el fitxer adjunt"
+deleteFile: "Esborrar l'arxiu "
 markAsSensitive: "Marcar com a NSFW"
 unmarkAsSensitive: "Deixar de marcar com a sensible"
 enterFileName: "Defineix nom del fitxer"
@@ -336,8 +337,12 @@ whenServerDisconnected: "Quan es perdi la connexió al servidor"
 disconnectedFromServer: "Desconnectat pel servidor"
 reload: "Actualitza"
 doNothing: "Ignora"
-accept: "Accepta"
-normal: "Nomal"
+reloadConfirm: "Vols recarregar?"
+watch: "Veure"
+unwatch: "Deixar de veure"
+accept: "Acceptar"
+reject: "Denegar"
+normal: "Normal"
 instanceName: "Nom del servidor"
 instanceDescription: "Descripció del servidor"
 maintainerName: "Nom de l'administrador"
@@ -355,25 +360,56 @@ connectService: "Connecta"
 disconnectService: "Desconnecta"
 enableLocalTimeline: "Activa la línia de temps local"
 enableGlobalTimeline: "Activa la línia de temps global"
+disablingTimelinesInfo: "Fins i tot si aquestes línies de temps són desactivades, els administradors i els moderadors poden continuar visualitzant per conveniència."
 registration: "Registre"
+enableRegistration: "Permet els registres d'usuaris"
 invite: "Convida"
+driveCapacityPerLocalAccount: "Capacitat del disc per usuaris locals"
+driveCapacityPerRemoteAccount: "Capacitat del disc per usuaris remots"
+inMb: "En megabytes"
+bannerUrl: "Adreça URL del bàner"
+backgroundImageUrl: "Adreça URL de la imatge de fons"
 basicInfo: "Informació bàsica"
 pinnedUsers: "Usuaris fixats"
+pinnedUsersDescription: "Llista d'usuaris, separats per salts de línia, que seran fixats a la pestanya \"Explorar\"."
+pinnedPages: "Pàgines fixades"
+pinnedPagesDescription: "Escriu els camins de les pàgines que vols fixar a la pàgina d'inici d'aquesta instància. Separades per salts de línia."
+pinnedClipId: "ID del retall fixat"
 pinnedNotes: "Nota fixada"
+hcaptcha: "hCaptcha"
+enableHcaptcha: "Activar hCaptcha"
+hcaptchaSiteKey: "Clau del lloc"
+hcaptchaSecretKey: "Clau secreta"
+mcaptcha: "mCaptcha"
+enableMcaptcha: "Activar mCaptcha"
+mcaptchaSiteKey: "Clau del lloc"
+mcaptchaSecretKey: "Clau secreta"
+mcaptchaInstanceUrl: "Adreça URL del servidor mCaptcha"
+recaptcha: "reCAPTCHA"
+enableRecaptcha: "Activar reCAPTCHA"
+recaptchaSiteKey: "Clau del lloc"
+recaptchaSecretKey: "Clau secreta"
 turnstile: "Turnstile"
 enableTurnstile: "Activar Turnstile"
 turnstileSiteKey: "Clau del lloc"
 turnstileSecretKey: "Clau secreta"
+avoidMultiCaptchaConfirm: "Fer servir diferents sistemes de Captcha a la vegada pot causar problemes entre ells. Vols desactivar els altres sistemes de Captcha activats? Si els vols mantenir actius fes clic a cancel·lar."
 antennas: "Antena"
 manageAntennas: "Gestiona les antenes"
+name: "Nom"
 antennaSource: "Font de l'antena"
 antennaKeywords: "Paraules clau a seguir"
 antennaExcludeKeywords: "Paraules clau a excloure"
 antennaKeywordsDescription: "Separar amb espais per la condició AND o amb salts de línia per la condició OR."
 notifyAntenna: "Notifica'm les publicacions noves"
 withFileAntenna: "Només les publicacions amb fitxers"
+enableServiceworker: "Activar les notificacions al navegador"
 antennaUsersDescription: "Llistar un nom d'usuari per línia"
+caseSensitive: "Sensible a majúscules i minúscules "
+withReplies: "Inclou respostes"
+connectedTo: "Aquests comptes hi són connectats"
 notesAndReplies: "Amb respostes"
+withFiles: "Incloure arxius"
 silence: "Silencia"
 silenceConfirm: "Segur que vols silenciar aquest usuari?"
 unsilence: "Deixa de silenciar"
@@ -389,20 +425,40 @@ userList: "Llistes"
 about: "Informació"
 aboutMisskey: "Quant a Misskey"
 administrator: "Administrador/a"
+token: "Codi de verificació"
+2fa: "Autenticació de doble factor"
+setupOf2fa: "Configurar l'autenticació de doble factor"
+totp: "Aplicació d'autenticació"
+totpDescription: "Escriu una contrasenya d'un sol us fent servir l'aplicació d'autenticació"
 moderator: "Moderador/a"
 moderation: "Moderació"
+moderationNote: "Nota de moderació "
+addModerationNote: "Afegir una nota de moderació "
+moderationLogs: "Registre de moderació "
 nUsersMentioned: "{n} usuaris mencionats"
+securityKeyAndPasskey: "Clau de seguretat / Clau de pas"
 securityKey: "Clau de seguretat"
+lastUsed: "Fet servir per última vegada"
+lastUsedAt: "Fet servir per última vegada: {t}"
 unregister: "Cancel·la el registre"
 passwordLessLogin: "Inici de sessió sense contrasenya"
+passwordLessLoginDescription: "Permet l'inici de sessió sense contrasenya fent servir només una Clau de seguretat/Clau de pas"
 resetPassword: "Restableix la contrasenya"
 newPasswordIs: "La contrasenya nova és «{password}»"
 reduceUiAnimation: "Redueix les animacions de la interfície"
 share: "Comparteix"
 notFound: "No s'ha trobat"
+notFoundDescription: "No es troba cap pàgina que correspongui a aquesta adreça"
+uploadFolder: "Carpeta per defecte per pujades"
+markAsReadAllNotifications: "Marca totes les notificacions com a llegides"
 markAsReadAllUnreadNotes: "Marca-ho tot com a llegit"
+markAsReadAllTalkMessages: "Marcar tots els missatges com llegits"
 help: "Ajuda"
+inputMessageHere: "Escriu aquí el teu missatge "
+close: "Tancar"
 invites: "Convida"
+members: "Membres"
+transfer: "Transferir"
 title: "Títol"
 text: "Text"
 enable: "Habilita"
@@ -472,12 +528,62 @@ objectStorage: "Emmagatzematge d'objectes\n"
 useObjectStorage: "Utilitzar l'emmagatzematge d'objectes"
 objectStorageBaseUrl: "Base d'enllaç"
 objectStorageBaseUrlDesc: "Prefix d'enllaç utilitzat per a fer referencia als fitxers. Especifica l'enllaç del teu CDN o Proxy si n'estàs utilitzant qualsevol, en cas contrari, especifica l'enllaç al que es pot accedir públicament segons la guia de servei que vosté utilitza.\nPer l'ús d'S3 utilitza 'https://<bucket>.s3.amazonaws.com' I per a GCS o serveis equivalents utilitza 'https://storage.googleapis.com/<bucket>'."
+objectStorageBucket: "Dipòsit "
+objectStorageBucketDesc: "Escriu el nom del dipòsit que fas servir al teu proveïdor d'emmagatzematge "
+objectStoragePrefix: "Prefix"
+objectStoragePrefixDesc: "Els fitxers es deixaren a directoris amb aquest prefix"
+objectStorageEndpoint: "Endpoint"
+objectStorageEndpointDesc: "Deixa'l buit si fas servir AWS S3, si no és així específica un punt d'entrada com '<host>' o '<host>:<port>', depenent del servei que facis servir."
+objectStorageRegion: "Regió "
+objectStorageRegionDesc: "Especifica una regió com 'xx-east-1'. Si el teu servei no diferència regions has de posar 'us-east-1'. Deixa'l buit si fas servir variables d'entorn o un arxiu de configuració d'AWS."
+objectStorageUseSSL: "Fes servir SSL"
+objectStorageUseSSLDesc: "Desactiva'l si no tens pensat fer servir HTTPS per les connexions de l'API"
+objectStorageUseProxy: "Connectar-se  mitjançant un Proxy"
+objectStorageUseProxyDesc: "Desactiva'l si no faràs servir un Proxy per les connexions de l'API"
+objectStorageSetPublicRead: "Configurar les pujades com públiques "
+s3ForcePathStyleDesc: "Si s3ForcePathStyle es troba activat el nom del dipòsit s'ha d'incloure a l'adreça URL en comtes del nom del host. Potser que necessitis activar-ho quan facis servir, per exemple, Minio a un servidor propi."
+serverLogs: "Registres del servidor"
+deleteAll: "Esborrar tot"
+showFixedPostForm: "Mostrar el formulari per escriure a l'inici de la línia de temps"
+showFixedPostFormInChannel: "Mostrar el formulari d'escriptura al principi de la línia de temps (Canals)"
+withRepliesByDefaultForNewlyFollowed: "Inclou les respostes d'usuaris nous seguits a la línia de temps per defecte."
 newNoteRecived: "Hi ha publicacions noves"
+sounds: "Sons"
+sound: "So"
+listen: "Escoltar"
+none: "Res"
+showInPage: "Mostrar a la pàgina "
+popout: "Finestra emergent"
+volume: "Volum"
+masterVolume: "Volum principal"
+notUseSound: "Sense so"
+useSoundOnlyWhenActive: "Reproduir sons només quan Misskey estigui actiu"
+details: "Detalls"
+chooseEmoji: "Tria un emoji"
+unableToProcess: "L'operació no pot ser completada "
+recentUsed: "Utilitzat recentment"
+install: "Instal·lació "
+uninstall: "Desinstal·lar "
+installedApps: "Aplicacions autoritzades "
+nothing: "No hi ha res per veure aquí "
 installedDate: "Data d'instal·lació"
+lastUsedDate: "Utilitzat per última vegada"
 state: "Estat"
 sort: "Ordena"
 ascendingOrder: "Ascendent"
 descendingOrder: "Descendent"
+scratchpad: "Bloc de proves"
+scratchpadDescription: "El bloc de proves proporciona un entorn experimental per AiScript. Pot escriure i verificar els resultats que interactuen amb Misskey."
+output: "Sortida"
+script: "Script"
+disablePagesScript: "Desactivar AiScript a les pàgines "
+updateRemoteUser: "Actualitzar la informació de l'usuari remot"
+unsetUserAvatar: "Desactivar l'avatar "
+unsetUserAvatarConfirm: "Segur que vols desactivar l'avatar?"
+unsetUserBanner: "Desactivar el bàner "
+unsetUserBannerConfirm: "Segur que vols desactivar el bàner?"
+deleteAllFiles: "Esborrar tots els arxius"
+deleteAllFilesConfirm: "Segur que vols esborrar tots els arxius?"
 removeAllFollowing: "Deixar de seguir tots els usuaris seguits"
 removeAllFollowingDescription: "El fet d'executar això, et farà deixar de seguir a tots els usuaris de {host}. Si us plau, executa això si l'amfitrió, per exemple, ja no existeix."
 userSuspended: "Aquest usuari ha sigut suspès"
@@ -526,48 +632,1387 @@ medium: "Mitjà"
 small: "Petit"
 generateAccessToken: "Genera codi d'accés"
 permission: "Permisos"
+adminPermission: "Permisos d'administrador "
 enableAll: "Habilita tot"
 disableAll: "Deshabilita tot"
 tokenRequested: "Donar accés al compte"
+pluginTokenRequestedDescription: "Aquest connector podrà fer servir tots els permisos configurats aquí."
+notificationType: "Tipus de notificació "
+edit: "Editar"
+emailServer: "Servidor de correu electrònic "
+enableEmail: "Activar l'enviament de correus electrònics "
+emailConfigInfo: "Es fa servir per confirmar el teu correu quan et registres o oblides la contrasenya "
+email: "Correu electrònic"
+emailAddress: "Adreça de correu electrònic"
+smtpConfig: "Configuració del servidor SMTP"
 smtpHost: "Amfitrió"
+smtpPort: "Port"
 smtpUser: "Nom d'usuari"
 smtpPass: "Contrasenya"
+emptyToDisableSmtpAuth: "No omplis el nom d'usuari i la contrasenya si vols deshabilitar l'autenticació SMTP"
+smtpSecure: "Fes servir SSL/TLS per connexions SMTP"
+smtpSecureInfo: "Desactiva això quan facis servir connexions STARTTLS"
+testEmail: "Prova l'enviament de correu "
+wordMute: "Silenciar paraules "
+hardWordMute: "Silenciar paraules fortes"
+regexpError: "Error de l'expressió regular "
+regexpErrorDescription: "S'ha produït un error a l'expressió regular a la línia {line} de les paraules silenciades {tab}:"
+instanceMute: "Silenciar servidor"
+userSaysSomething: "{name} n'ha dit alguna cosa"
+makeActive: "Activar"
+display: "Veure"
+copy: "Copiar"
+metrics: "Mètriques"
+overview: "Visió General"
+logs: "Registres"
+delayed: "Endarrerits "
+database: "Bases de dades"
+channel: "Canals"
+create: "Crear"
+notificationSetting: "Paràmetres de notificacions"
+notificationSettingDesc: "Selecciona els tipus de notificacions que es mostraran"
+useGlobalSetting: "Fer servir la configuració global"
+useGlobalSettingDesc: "Si s'activa, es farà servir la configuració de notificacions del teu comte. Si no s'activa es poden fer configuracions individuals."
+other: "Altre"
+regenerateLoginToken: "Regenerar clau de seguretat d'inici de sessió"
+regenerateLoginTokenDescription: "Regenera la clau de seguretat que es fa servir internament durant l'inici de sessió. Normalment aquesta acció no és necessària. Si es regenera es tancarà la sessió a tots els dispositius amb una sessió activa."
+theKeywordWhenSearchingForCustomEmoji: "Cercar un emoji personalitzat "
+setMultipleBySeparatingWithSpace: "Separa múltiples entrades amb un espai"
+fileIdOrUrl: "ID de l'arxiu o URL"
+behavior: "Comportament"
+sample: "Mostrar"
+abuseReports: "Denúncies "
+reportAbuse: "Denuncia un abús "
+reportAbuseRenote: "Denuncia una renota"
+reportAbuseOf: "Denuncia a {name}"
+fillAbuseReportDescription: "Omple els detalls sobre aquesta denúncia. Si la denúncia és sobre una nota en concret inclou l'adreça URL."
+abuseReported: "La teva denúncia s'ha enviat. Moltes gràcies."
+reporter: "Denunciant "
+reporteeOrigin: "Origen de la denúncia "
+reporterOrigin: "Origen del denunciant"
+forwardReport: "Transferir la denúncia a una instància remota"
+forwardReportIsAnonymous: "En comptes del teu compte, es farà servir un compte anònim com a denunciat a la instància remota."
+send: "Enviar"
+abuseMarkAsResolved: "Marcar la denúncia com a resolta"
+openInNewTab: "Obre a una pestanya nova"
+openInSideView: "Obre a una vista lateral"
+defaultNavigationBehaviour: "Navegació per defecte"
+editTheseSettingsMayBreakAccount: "Editar aquestes opcions pot deixar inoperatiu el teu compte"
+instanceTicker: "Informació de notes de la instància "
+waitingFor: "Esperant {x}"
+random: "Aleatori "
+system: "Sistema"
+switchUi: "Canviar interfície d'usuari "
+desktop: "Escriptori"
+clip: "Retalls"
+createNew: "Crear"
+optional: "Opcional"
+createNewClip: "Crear un nou Retall"
+unclip: "Treure Retall"
+confirmToUnclipAlreadyClippedNote: "Aquesta nota ja és inclosa al Retall \"{name}\". Vols treure-la d'aquest retall?"
+public: "Públic "
+private: "Privat"
+i18nInfo: "Misskey està sent traduït a diferents idiomes per voluntaris. Pots ajudar aquí {link}."
+manageAccessTokens: "Administrar claus de seguretat d'accés "
+accountInfo: "Informació del compte"
+notesCount: "Comptador de notes"
+repliesCount: "Nombre de respostes"
 renotesCount: "Impulsos fets"
+repliedCount: "Nombre de respostes rebudes"
 renotedCount: "Impulsos rebuts"
+followingCount: "Nombre de comptes seguits"
+followersCount: "Nombre de seguidors"
+sentReactionsCount: "Nombre de reaccions enviades"
+receivedReactionsCount: "Nombre de reaccions rebudes"
+pollVotesCount: "Nombre de vots enviats a enquestes"
+pollVotedCount: "Nombre de vots rebuts a les enquestes"
+yes: "Sí "
+no: "No"
+driveFilesCount: "Nombre de fitxers al Disc"
+driveUsage: "Utilització de l'espai del Disc"
+noCrawle: "Rebutjar la indexació dels buscadors"
+noCrawleDescription: "No permetis que els buscadors indexin el teu perfil, notes, pàgines, etc."
+lockedAccountInfo: "Tret que establiu la visibilitat de la nota a \"Només seguidors\", les vostres notes seran visibles per qualsevol persona, fins i tot si heu d'aprovar els seguidors manualment"
+alwaysMarkSensitive: "Marcar com a sensible per defecte"
+loadRawImages: "Carregar les imatges originals en comptes de miniatures "
+disableShowingAnimatedImages: "No reproduir imatges animades"
+highlightSensitiveMedia: "Ressalta els medis marcats com a sensibles"
+verificationEmailSent: "S'ha enviat un correu electrònic de verificació. Fes clic a l'enllaç per completar la verificació."
+notSet: "Sense definir"
+emailVerified: "El correu electrònic s'ha verificat"
+noteFavoritesCount: "Nombre de notes favorites "
+pageLikesCount: "Nombre de Pàgines que t'agraden "
+pageLikedCount: "Nombre d'agraïments rebuts a les Pàgines "
+contact: "Contacte"
+useSystemFont: "Fes servir la font per defecte del sistema"
+clips: "Retalls"
+experimentalFeatures: "Característiques experimentals"
+experimental: "Experimental"
+thisIsExperimentalFeature: "Aquesta és una característica experimental. La seva funcionalitat pot canviar, i pot ser que no funcioni degudament."
+developer: "Programador"
+makeExplorable: "Fes que el compte sigui visible a la secció \"Explorar\""
+makeExplorableDescription: "Si desactives aquesta opció, el teu compte no sortirà a la secció \"Explorar\""
+showGapBetweenNotesInTimeline: "Mostra una separació entre els articles a la línia de temps"
+duplicate: "Duplicat"
+left: "Esquerra"
+center: "Centre"
+wide: "Gran"
+narrow: "Estret"
+reloadToApplySetting: "Aquest ajust només s'aplicarà després de recarregar la pàgina. Vols fer-ho ara?"
+needReloadToApply: "Es requereix recarregar per reflectir aquesta opció "
+showTitlebar: "Mostra la barra del títol "
 clearCache: "Esborra la memòria cau"
+onlineUsersCount: "{n} Usuaris es troben en línia "
+nUsers: "{n} Usuaris"
+nNotes: "{n} Notes"
+sendErrorReports: "Enviar informes d'error "
+sendErrorReportsDescription: "Quan s'activa, es compartirà amb Misskey informació detallada de l'error quan es trobi un problema això farà pujar la qualitat de Misskey.\nAixò inclourà informació com la versió del SO que fas servir, el navegador web que fas servir, la teva activitat a Misskey, etc."
+myTheme: "El meu tema"
+backgroundColor: "Color de fons"
+accentColor: "Color principal"
+textColor: "Color del text"
+saveAs: "Desar com..."
+advanced: "Avançat"
+advancedSettings: "Configuració avançada"
+value: "Valor"
+createdAt: "Creat el"
+updatedAt: "Actualitzat el"
+saveConfirm: "Desar canvis?"
+deleteConfirm: "Segur que vols esborrar?"
+invalidValue: "Valor invàlid."
+registry: "Registre "
+closeAccount: "Tancar el compte"
+currentVersion: "Versió actual"
+latestVersion: "Versió nova"
+youAreRunningUpToDateClient: "Ja estàs fent servir la versió més recent del client."
+newVersionOfClientAvailable: "Tens disponible una versió del client més recent."
+usageAmount: "Ús "
+capacity: "Capacitat"
+inUse: "Fet servir"
+editCode: "Editar el codi"
+apply: "Aplicar"
+receiveAnnouncementFromInstance: "Rep notificacions d'aquesta instància "
+emailNotification: "Notificacions per correu electrònic "
+publish: "Publicar"
+inChannelSearch: "Cerca al canal"
+useReactionPickerForContextMenu: "Fes clic al botó dret del ratolí per obrir el menú de reaccions"
+typingUsers: "{users} està/estàn Escrivint "
+jumpToSpecifiedDate: "Ves a una data concreta"
 showingPastTimeline: "Estàs veient una línia de temps antiga"
+clear: "Tornar"
+markAllAsRead: "Marcar tot com llegit"
+goBack: "Tornar"
+unlikeConfirm: "Vols esborrar el teu m'agrada?"
+fullView: "Vista completa."
+quitFullView: "Sortir de la vista completa"
+addDescription: "Afegeix una descripció "
+userPagePinTip: "Podeu seleccionar \"Fixar al perfil\" del menú de notes individuals per mostrar les notes aquí."
+notSpecifiedMentionWarning: "Aquesta nota esmenta usuaris que no es troben com a destinataris"
 info: "Informació"
+userInfo: "Informació de l'usuari"
+unknown: "Desconegut"
+onlineStatus: "Connectat"
+hideOnlineStatus: "Ocultar l'estat de connexió"
+hideOnlineStatusDescription: "Ocultant el teu estat de connexió redueix les funcionalitats d'algunes funcions com la cerca."
+online: "Connectat"
+active: "Actiu"
+offline: "Desconnectat"
+notRecommended: "No recomanat"
+botProtection: "Protecció contra bots"
+instanceBlocking: "Instàncies blocades/silenciades"
+selectAccount: "Seleccionar un compte"
+switchAccount: "Canviar de compte"
+enabled: "Activat"
+disabled: "Desactivat"
+quickAction: "Accions ràpides"
 user: "Usuaris"
 administration: "Administració"
+accounts: "Comptes"
+switch: "Canvia"
+noMaintainerInformationWarning: "La informació de l'administrador no s'ha configurat"
+noBotProtectionWarning: "La protecció contra bots no s'ha configurat."
+configure: "Configurar"
+postToGallery: "Crear una nova publicació a la galeria"
+postToHashtag: "Pública a aquesta etiqueta"
+gallery: "Galeria"
+recentPosts: "Articles recents"
+popularPosts: "Articles populars"
+shareWithNote: "Comparteix amb una nota"
+ads: "Anuncis"
+expiration: ""
+startingperiod: "Inici"
+memo: "Recordatori"
+priority: "Prioritat"
+high: "Alta"
 middle: "Mitjà"
+low: "Baixa"
+emailNotConfiguredWarning: "Adreça de correu electrònic"
+ratio: "Proporció"
+previewNoteText: "Mostrar vista prèvia"
+customCss: "CSS personalitzat"
+customCssWarn: "Aquesta configuració només hauries de configurar-la si saps que fas. Si poses valors inadequats pots fer que el client deixi de funcionar correctament."
 global: "Global"
+squareAvatars: "Mostrar avatars quadrats"
+sent: "Enviar"
+received: "Rebut"
+searchResult: "Resultats de la cerca"
+hashtags: "Etiquetes"
+troubleshooting: "Solucionar problemes"
+useBlurEffect: "Fes servir efectes de desenfocament a la interfície"
+learnMore: "Saber més "
+misskeyUpdated: "Misskey s'ha actualitzat "
+whatIsNew: "Mostra canvis"
+translate: "Traduir "
+translatedFrom: "Traduït del {x}"
+accountDeletionInProgress: "S'està produint l'eliminació del compte"
+usernameInfo: "Un nom que identifiqui el teu compte d'altres en aquest servidor. Pots fer servir lletres (a~z, A~Z), números (0~9) i guions baixos (_). Els noms d'usuari no es poden canviar després."
+aiChanMode: "Mode IA"
+devMode: "Mode desenvolupador"
+keepCw: "Mantenir els avisos de contingut"
+pubSub: "Comptes Pub/Sub"
+lastCommunication: "Última comunicació "
+resolved: "Resolt"
+unresolved: "Sense resoldre"
+breakFollow: "Deixar de seguir"
+breakFollowConfirm: "Vols deixar de seguir?"
+itsOn: "Activat"
+itsOff: "Desactivat"
+on: "Activar"
+off: "Desactivar"
+emailRequiredForSignup: "Demanar correu electrònic per registrar-se "
+unread: "Sense llegir"
+filter: "Filtrar"
+controlPanel: "Panel de control"
+manageAccounts: "Gestionar comptes"
+makeReactionsPublic: "Reaccions públiques "
+makeReactionsPublicDescription: "Això fa que totes les teves reaccions siguin visibles públicament "
+classic: "Clàssic "
+muteThread: "Silenciar el fil"
+unmuteThread: "Deixar de silenciar el fil"
+followingVisibility: "Visibilitat dels seguiments"
+followersVisibility: "Visibilitat dels seguidors"
+continueThread: "Veure la continuació del fil"
+deleteAccountConfirm: "Això eliminarà el teu compte irreversiblement. Procedir?"
+incorrectPassword: "Contrasenya incorrecta."
+voteConfirm: "Confirma el teu vot \"{choice}\""
+hide: "Amagar"
+useDrawerReactionPickerForMobile: "Mostrar el selector de reaccions com un calaix al mòbil "
+welcomeBackWithName: "Benvingut de nou, {name}"
+clickToFinishEmailVerification: "Si us plau, fes clic a [{ok}] per completar la verificació per correu electrònic "
+overridedDeviceKind: "Tipus de dispositiu"
+smartphone: "Telèfon intel·ligent"
+tablet: "Tauleta"
+auto: "Automàtic "
+themeColor: "Color del tema"
+size: "Mida"
+numberOfColumn: "Nombre de columnes"
 searchByGoogle: "Cercar"
+instanceDefaultLightTheme: "Tema clar per defecte de tota la instància "
+instanceDefaultDarkTheme: "Tema fosc per defecte de tota la instància "
+instanceDefaultThemeDescription: "Introdueix el codi del tema en format d'objecte"
+mutePeriod: "Duració del silenci"
+period: "Límit de temps"
+indefinitely: "Permanent"
+tenMinutes: "10 minuts"
+oneHour: "1 hora"
+oneDay: "Un dia"
+oneWeek: "Una setmana"
+oneMonth: "Un mes"
+reflectMayTakeTime: "Això pot trigar una estona a tenir efecte"
+failedToFetchAccountInformation: "No es pot obtenir la informació del compte"
+rateLimitExceeded: "S'ha arribat al màxim de peticions"
+cropImage: "Retalla la imatge"
+cropImageAsk: "Vols retallar la imatge?"
+cropYes: "Retallar"
+cropNo: "Fer servir tal qual"
 file: "Fitxers"
+recentNHours: "Últimes {n} hores"
+recentNDays: "Últims {n} dies"
+noEmailServerWarning: "Correu electrònic del servidor sense configurar"
+thereIsUnresolvedAbuseReportWarning: "Hi ha informes sense solucionar."
+recommended: "Recomanat"
+check: "Verificar"
+driveCapOverrideLabel: "Canvia la capacitat del Disc per aquest usuari"
+driveCapOverrideCaption: "Restableix la mida original posant un valor de 0 o menys."
+requireAdminForView: "Has de ser administrador per poder veure això."
+isSystemAccount: "Un compte creat i operat automàticament pel sistema."
+typeToConfirm: "Si us plau, escriu {x} per confirmar"
+deleteAccount: "Esborrar el compte"
+document: "Documentació"
+numberOfPageCache: "Nombre de pàgines a la memòria cau"
+numberOfPageCacheDescription: "Incrementant aquest nombre farà que millori l'experiència de l'usuari, però es farà servir més memòria al dispositiu de l'usuari."
+logoutConfirm: "Vols sortir?"
+lastActiveDate: "Fet servir per última vegada"
+statusbar: "Barra d'estat"
+pleaseSelect: "Selecciona una opció"
+reverse: "Invertir"
+colored: "Colorit"
+refreshInterval: "Interval d'actualització "
+label: "Etiqueta"
+type: "Tipus"
+speed: "Velocitat"
+slow: "Lent"
+fast: "Ràpid "
+sensitiveMediaDetection: "Detecció de contingut sensible"
+localOnly: "Només local"
+remoteOnly: "Només remot"
+failedToUpload: "Ha fallat la pujada"
+cannotUploadBecauseInappropriate: "Aquest fitxer no es pot pujar perquè s'ha trobat que algunes parts són inapropiades."
+cannotUploadBecauseNoFreeSpace: "Ha fallat la pujada del fitxer perquè no hi ha capacitat al Disc."
+cannotUploadBecauseExceedsFileSizeLimit: "Aquest fitxer no es pot pujar perquè supera la mida permesa."
+beta: "Proves"
+enableAutoSensitive: "Marcar com a sensible automàticament "
+enableAutoSensitiveDescription: "Permet la detecció i el marcat automàtic dels mitjans sensibles fent servir aprenentatge automàtic quan sigui possible. Si aquesta opció es troba desactivada potser que estigui activada per a tota la instància. "
+activeEmailValidationDescription: "Activa la validació estricta de comptes de correu electrònic, inclou la validació d'adreces d'un sol ús i si es possible comunicar-se amb aquestes. Quan es troba desactivada només es vàlida el format del correu electrònic."
+navbar: "Barra de navegació "
+shuffle: "Aleatori"
+account: "Compte"
+move: "Mou"
+pushNotification: "Enviament de notificacions"
+subscribePushNotification: "Activar l'enviament de notificacions"
+unsubscribePushNotification: "Desactivar l'enviament de notificacions"
+pushNotificationAlreadySubscribed: "L'enviament de notificacions ja és activat"
+pushNotificationNotSupported: "El teu navegador o la teva instància no suporta l'enviament de notificacions "
+sendPushNotificationReadMessage: "Esborrar les notificacions enviades quan s'hagin llegit"
+sendPushNotificationReadMessageCaption: "Això pot fer que el teu dispositiu consumeixi més bateria"
+windowMaximize: "Maximitzar "
+windowMinimize: "Minimitzar"
+windowRestore: "Restaurar"
+caption: "Llegenda"
+loggedInAsBot: "Identificat com a bot"
+tools: "Eines"
+cannotLoad: "No es pot carregar"
+numberOfProfileView: "Visualitzacions del perfil"
+like: "M'agrada "
+unlike: "Treure m'agrada "
+numberOfLikes: "M'agraden "
+show: "Veure"
+neverShow: "No mostrar més "
+remindMeLater: "Recorda-m'ho més tard"
+didYouLikeMisskey: "T'està agradant Misskey?"
+pleaseDonate: "A {host} fem servir el software lliure Misskey. Considera fer un donatiu a Misskey perquè pugui continuar el seu desenvolupament!"
+roles: "Rols"
+role: "Rols"
+noRole: "No s'han trobat rols"
+normalUser: "Usuari normal"
+undefined: "Sense definir"
+assign: "Assignar "
+unassign: "Treure"
+color: "Color"
+manageCustomEmojis: "Gestiona els emojis personalitzats"
+manageAvatarDecorations: "Gestiona les decoracions dels avatars "
+youCannotCreateAnymore: "Has arribat al màxim de creacions"
+cannotPerformTemporary: "Temporalment no disponible"
+cannotPerformTemporaryDescription: "Aquesta acció no es pot dur a terme temporalment per arribar al seu límit d'execució. Pots esperar una mica i tornar-ho a intentar."
+invalidParamError: "Paràmetres incorrectes "
+invalidParamErrorDescription: "Els paràmetres demanats no són correctes. Normalment això es deu a un error, però també pot ser a alguna entrada excedint els límits o similar."
+permissionDeniedError: "Operació no permesa "
+permissionDeniedErrorDescription: "Aquest compte no té suficients permisos per dur a terme aquesta acció "
+preset: "Predefinit"
+selectFromPresets: "Escull des dels predefinits"
+achievements: "Assoliments"
+gotInvalidResponseError: "Resposta del servidor invàlida "
+gotInvalidResponseErrorDescription: "No es pot contactar amb el servidor o potser es troba fora de línia per manteniment. Provar-ho de nou més tard."
+thisPostMayBeAnnoying: "Aquesta nota pot ser molesta per algú."
+thisPostMayBeAnnoyingHome: "Publicar a la línia de temps d'Inici"
+thisPostMayBeAnnoyingCancel: "Cancel·lar "
+thisPostMayBeAnnoyingIgnore: "Publicar de totes maneres"
+collapseRenotes: "Col·lapsar les renotes que ja has vist"
+internalServerError: "Error intern del servidor"
+internalServerErrorDescription: "El servidor ha fallat de manera inexplicable."
+copyErrorInfo: "Copiar la informació de l'error "
+joinThisServer: "Registra't en aquesta instància "
+exploreOtherServers: "Cerca una altra instància "
+letsLookAtTimeline: "Dona una ullada a la línia de temps"
+disableFederationConfirm: "Vols treure la federació?"
+disableFederationConfirmWarn: "Fins i tot traient la federació, les publicacions continuaren sent públiques, a no ser que es digui el contrari. Normalment no has de tocar això."
+disableFederationOk: "Desactivar"
+invitationRequiredToRegister: "Aquesta instància només permet el registre per invitació. Per registrar-te has d'introduir el codi d'invitació."
+emailNotSupported: "Aquesta instància no suporta l'enviament de correus electrònics "
+postToTheChannel: "Publicar a un Canal"
+cannotBeChangedLater: "Això ja no es podrà canviar."
+reactionAcceptance: "Acceptació de reaccions "
+likeOnly: "Només m'agraden "
+likeOnlyForRemote: "Tot (només m'agraden d'instàncies remotes)"
+nonSensitiveOnly: "Només sense contingut sensible"
+nonSensitiveOnlyForLocalLikeOnlyForRemote: "Només contingut no sensible (Només m'agraden d'instàncies remotes)"
+rolesAssignedToMe: "Rols assignats "
+resetPasswordConfirm: "Vols canviar la teva contrasenya?"
+sensitiveWords: "Paraules sensibles"
+sensitiveWordsDescription: "La visibilitat de totes les notes que continguin qualsevol de les paraules configurades seran, automàticament, afegides a \"Inici\". Pots llistar diferents paraules separant les per línies noves."
+sensitiveWordsDescription2: "Fent servir espais crearà expressions AND si l'expressió s'envolta amb barres inclinades es converteix en una expressió regular."
+prohibitedWords: "Paraules prohibides"
+prohibitedWordsDescription: "Quan intenteu publicar una Nota que conté una paraula prohibida, feu que es converteixi en un error. Es poden dividir i establir múltiples línies."
+prohibitedWordsDescription2: "Fent servir espais crearà expressions AND si l'expressió s'envolta amb barres inclinades es converteix en una expressió regular."
+hiddenTags: "Etiquetes ocultes"
+hiddenTagsDescription: "La visibilitat de totes les notes que continguin qualsevol de les paraules configurades seran, automàticament, afegides a \"Inici\". Pots llistar diferents paraules separant les per línies noves."
+notesSearchNotAvailable: "La cerca de notes no es troba disponible."
+license: "Llicència"
+unfavoriteConfirm: "Esborrar dels favorits?"
+myClips: "Els meus retalls"
+drivecleaner: "Netejador de Disc"
+retryAllQueuesNow: "Prova de nou d'executar totes les cues"
+retryAllQueuesConfirmTitle: "Tornar a intentar-ho tot?"
+retryAllQueuesConfirmText: "Això farà que la càrrega del servidor augmenti temporalment."
+enableChartsForRemoteUser: "Generar gràfiques d'usuaris remots"
+enableChartsForFederatedInstances: "Generar gràfiques d'instàncies remotes"
+showClipButtonInNoteFooter: "Afegir \"Retall\" al menú d'acció de la nota"
+reactionsDisplaySize: "Mida de les reaccions"
+limitWidthOfReaction: "Limitar l'amplada màxima de la reacció i mostrar-les en una mida reduïda "
+noteIdOrUrl: "ID o URL de la nota"
+video: "Vídeo"
+videos: "Vídeos "
+audio: "So"
+audioFiles: "So"
+dataSaver: "Economitzador de dades"
+accountMigration: "Migració del compte"
+accountMoved: "Aquest usuari té un compte nou:"
+accountMovedShort: "Aquest compte ha sigut migrat"
+operationForbidden: "Operació no permesa "
+forceShowAds: "Mostra els anuncis sempre "
+addMemo: "Afegir recordatori"
+editMemo: "Editar recordatori"
+reactionsList: "Reaccions"
+renotesList: "Impulsos"
+notificationDisplay: "Notificacions"
+leftTop: "Dalt a l'esquerra "
+rightTop: "Dalt a la dreta "
+leftBottom: "A baix a l'esquerra"
+rightBottom: "A baix a la dreta"
+stackAxis: "Apilar en direcció "
+vertical: "Vertical"
+horizontal: "Horitzontal "
+position: "Posició "
+serverRules: "Regles del servidor"
+pleaseConfirmBelowBeforeSignup: "Per obrir un compte en aquest servidor, has de llegir i acceptar el següent."
+pleaseAgreeAllToContinue: "Has d'acceptar tots els camps de dalt per poder continuar."
+continue: "Continuar"
+preservedUsernames: "Noms d'usuaris reservats"
+preservedUsernamesDescription: "Llistat de noms d'usuaris que no es poden fer servir separats per salts de linia. Aquests noms d'usuaris no estaran disponibles quan es creï un compte d'usuari normal, però els administradors els poden fer servir per crear comptes manualment. Per altre banda els comptes ja creats amb aquests noms d'usuari no es veure'n afectats."
+createNoteFromTheFile: "Compon una nota des d'aquest fitxer"
+archive: "Arxiu"
+channelArchiveConfirmTitle: "Vols arxivar {name}?"
+channelArchiveConfirmDescription: "Un Canal arxivat no apareixerà a la llista de canals o als resultats de cerca. Tampoc es poden afegir noves entrades."
+thisChannelArchived: "Aquest Canal ha sigut arxivat."
+displayOfNote: "Mostrar notes"
+initialAccountSetting: "Configuració del perfil"
+youFollowing: "Seguit"
+preventAiLearning: "Descartar l'ús d'aprenentatge automàtic (IA Generativa)"
+preventAiLearningDescription: "Demanar els indexadors no fer servir els texts, imatges, etc. en cap conjunt de dades per alimentar l'aprenentatge automàtic (IA Predictiva/ Generativa). Això s'aconsegueix afegint la etiqueta \"noai\" com a resposta HTML al contingut corresponent. Prevenir aquest ús totalment pot ser que no sigui aconseguit, ja que molts indexadors poden obviar aquesta etiqueta."
+options: "Opcions"
+specifyUser: "Especificar usuari"
+failedToPreviewUrl: "Vista prèvia no disponible"
+update: "Actualitzar"
+rolesThatCanBeUsedThisEmojiAsReaction: "Rols que poden fer servir aquest emoji com a reacció "
+rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "Si cap rol es especificat tothom ho pot fer servir"
+rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "Aquests rols han de ser públics "
+cancelReactionConfirm: "Vols esborrar la teva reacció?"
+changeReactionConfirm: "Vols canviar la teva reacció?"
+later: "Més tard"
+goToMisskey: "Ves a Misskey"
+additionalEmojiDictionary: "Diccionari d'emojis adicionals"
+installed: "Instal·lats "
+branding: "Marca"
+enableServerMachineStats: "Publicar estadístiques del maquinari del servidor"
+enableIdenticonGeneration: "Activar la generació d'icones d'identificació "
+turnOffToImprovePerformance: "Desactivant aquesta opció es pot millorar el rendiment."
+createInviteCode: "Crear codi d'invitació "
+createWithOptions: "Crear invitació amb opcions"
+createCount: "Comptador d'invitacions "
+inviteCodeCreated: "Invitació creada"
+inviteLimitExceeded: "Has sobrepassat el límit d'invitacions que pots crear."
+createLimitRemaining: "Et queden {limit} invitacions restants"
+inviteLimitResetCycle: "Cada {time} {limit} invitacions."
+expirationDate: "Data de venciment"
+noExpirationDate: "Sense data de venciment"
+inviteCodeUsedAt: "Codi d'invitació fet servir el"
+registeredUserUsingInviteCode: "Codi d'invitació fet servir per l'usuari "
+waitingForMailAuth: "Esperant la verificació per correu electrònic "
+inviteCodeCreator: "Invitació creada per"
+usedAt: "Utilitzada el"
+unused: "Sense utilitzar"
+used: "Utilitzada"
+expired: "Caducat"
+doYouAgree: "Estàs d'acord?"
+beSureToReadThisAsItIsImportant: "Llegeix això perquè és molt important."
+iHaveReadXCarefullyAndAgree: "He llegit {x} i estic d'acord."
+dialog: "Diàleg "
 icon: "Icona"
+forYou: "Per a tu"
+currentAnnouncements: "Informes actuals"
+pastAnnouncements: "Informes passats"
+youHaveUnreadAnnouncements: "Tens informes per llegir."
+useSecurityKey: "Segueix les instruccions del teu navegador O dispositiu per fer servir el teu passkey."
 replies: "Respostes"
 renotes: "Impulsa"
+loadReplies: "Mostrar les respostes"
+loadConversation: "Mostrar la conversació "
+pinnedList: "Llista fixada"
+keepScreenOn: "Mantenir la pantalla encesa"
+verifiedLink: "La propietat de l'enllaç ha sigut verificada"
+notifyNotes: "Notificar quan hi hagi notes noves"
+unnotifyNotes: "Deixar de notificar quan hi hagi notes noves"
+authentication: "Autenticació "
+authenticationRequiredToContinue: "Si us plau autentificat per continuar"
+dateAndTime: "Data i hora"
+showRenotes: "Mostrar impulsos"
+edited: "Editat"
+notificationRecieveConfig: "Paràmetres de notificacions"
+mutualFollow: "Seguidor mutu"
+fileAttachedOnly: "Només notes amb adjunts"
+showRepliesToOthersInTimeline: "Mostrar les respostes a altres a la línia de temps"
+hideRepliesToOthersInTimeline: "Amagar les respostes a altres a la línia de temps"
+showRepliesToOthersInTimelineAll: "Mostrar les respostes a altres a usuaris que segueixes a la línia de temps"
+hideRepliesToOthersInTimelineAll: "Ocultar les teves respostes a tots els usuaris que segueixes a la línia de temps"
+confirmShowRepliesAll: "Aquesta opció no té marxa enrere. Vols mostrar les teves respostes a tots els que segueixes a la teva línia de temps?"
+confirmHideRepliesAll: "Aquesta opció no té marxa enrere. Vols ocultar les teves respostes a tots els usuaris que segueixes a la línia de temps?"
+externalServices: "Serveis externs"
+sourceCode: "Codi font"
+impressum: "Impressum"
+impressumUrl: "Adreça URL impressum"
+impressumDescription: "A països, com Alemanya, la inclusió de la informació de contacte de l'operador (un Impressum) és requereix de manera legal per llocs comercials."
+privacyPolicy: "Política de privacitat"
+privacyPolicyUrl: "Adreça URL de la política de privacitat"
+tosAndPrivacyPolicy: "Termes d'ús i política de privacitat"
+avatarDecorations: "Decoracions dels avatars"
+attach: "Adjuntar"
+detach: "Eliminar"
+detachAll: "Treure tot"
+angle: "Angle"
+flip: "Girar"
+showAvatarDecorations: "Mostrar les decoracions dels avatars"
+releaseToRefresh: "Deixar anar per actualitzar"
+refreshing: "Recarregant..."
+pullDownToRefresh: "Llisca cap a baix per recarregar"
+disableStreamingTimeline: "Desactivar l'actualització en temps real de les línies de temps"
+useGroupedNotifications: "Mostrar les notificacions agrupades "
+signupPendingError: "Hi ha hagut un problema verificant l'adreça de correu electrònic. L'enllaç pot haver caducat."
+cwNotationRequired: "Si està activat \"Amagar contingut\" s'ha d'escriure una descripció "
+doReaction: "Afegeix una reacció "
+code: "Codi"
+reloadRequiredToApplySettings: "És necessari recarregar la pàgina per aplicar els canvis."
+remainingN: "Queden: {n}"
+overwriteContentConfirm: "Vols substituir el contingut actual?"
+seasonalScreenEffect: "Efectes de pantalla segons les estacions"
+decorate: "Decorar"
+addMfmFunction: "Afegeix funcions MFM"
+enableQuickAddMfmFunction: "Activar accés ràpid per afegir funcions MFM"
+bubbleGame: "Bubble Game"
+sfx: "Efectes de so"
+soundWillBePlayed: "Es reproduiran efectes de so"
+showReplay: "Veure reproducció"
+replay: "Reproduir"
+replaying: "Reproduint"
+ranking: "Classificació"
+lastNDays: "Últims {n} dies"
+backToTitle: "Torna al títol"
+hemisphere: "Geolocalització"
+withSensitive: "Incloure notes amb fitxers sensibles"
+userSaysSomethingSensitive: "La publicació de {name} conte material sensible"
+enableHorizontalSwipe: "Lliscar per canviar de pestanya"
+surrender: "Cancel·lar "
+_bubbleGame:
+  howToPlay: "Com es juga"
+  _howToPlay:
+    section1: "Ajusta la posició i deixa caure l'objecte dintre la caixa."
+    section2: "Quan dos objectes del mateix tipus es toquen, canviaran en un objecte diferent i guanyares punts."
+    section3: "El joc s'acabarà quan els objectes sobresurtin de la caixa. Intenta aconseguir la puntuació més gran possible fusionant objectes mentre impedeixes que sobresurtin de la caixa!"
+_announcement:
+  forExistingUsers: "Anunci per usuaris registrats"
+  forExistingUsersDescription: "Aquest avís només es mostrarà als usuaris existents fins al moment de la publicació. Si no també es mostrarà als usuaris que es registrin després de la publicació."
+  needConfirmationToRead: "Es necessita confirmació de lectura de la notificació "
+  needConfirmationToReadDescription: "Si s'activa es mostrarà un diàleg per confirmar la lectura d'aquesta notificació. A més aquesta notificació serà exclosa de qualsevol funcionalitat com \"Marcar tot com a llegit\"."
+  end: "Final de la notificació "
+  tooManyActiveAnnouncementDescription: "Tenir massa notificacions actives pot empitjorar l'experiència de l'usuari. Considera finalitzar els anuncis que siguin antics."
+  readConfirmTitle: "Marcar com llegida?"
+  readConfirmText: "Això marcarà el contingut de \"{title}\" com llegit."
+  shouldNotBeUsedToPresentPermanentInfo: "Ja que l'ús de notificacions pot impactar l'experiència dels nous usuaris, és recomanable fer servir les notificacions amb el flux d'informació en comptes de fer-les servir en un únic bloc."
+  dialogAnnouncementUxWarn: "Tenir dues o més notificacions amb l'estil de finestres pot impactar l'experiència de l'usuari, és per això que és recomana fer-lo servir amb cura."
+  silence: "Sense notificacions"
+  silenceDescription: "Activant aquesta opció la notificació no es mostrarà ni l'usuari l'haurà de llegir."
+_initialAccountSetting:
+  accountCreated: "S'ha completat la creació del compte!"
+  letsStartAccountSetup: "Posem ràpidament la configuració inicial del compte."
+  letsFillYourProfile: "Comencem establint el teu perfil."
+  profileSetting: "Configuració del perfil"
+  privacySetting: "Configuració de seguretat"
+  theseSettingsCanEditLater: "Aquests ajustos es poden canviar més tard."
+  youCanEditMoreSettingsInSettingsPageLater: "A més d'això, es poden fer diferents configuracions a través de la pàgina de configuració. Assegureu-vos de comprovar-ho més tard."
+  followUsers: "Prova de seguir usuaris que t'interessin per construir la teva línia de temps."
+  pushNotificationDescription: "Activant les notificacions emergents et permetrà rebre notificacions de {name} directament al teu dispositiu."
+  initialAccountSettingCompleted: "Configuració del perfil completada!"
+  haveFun: "Disfruta {name}!"
+  youCanContinueTutorial: "Pots continuar amb un tutorial per aprendre a Fer servir {name} (MissKey) o tu pots estalviar i començar a fer-lo servir ja."
+  startTutorial: "Començar el tutorial"
+  skipAreYouSure: "Et vols saltar la configuració del perfil?"
+  laterAreYouSure: "Vols continuar la configuració del perfil més tard?"
+_initialTutorial:
+  launchTutorial: "Començar tutorial"
+  title: "Tutorial"
+  wellDone: "Ben fet!"
+  skipAreYouSure: "Sortir del tutorial?"
+  _landing:
+    title: "Benvingut al tutorial"
+    description: "Aquí aprendràs el bàsic per poder fer servir Misskey i les seves característiques."
+  _note:
+    title: "Què és una Nota?"
+    description: "Les publicacions a Misskey es diuen 'Notes'. Les Notes s'ordenen cronològicament a la línia de temps i s'actualitzen de forma automàtica."
+    reply: "Fes clic en aquest botó per contestar a un missatge. També és possible contestar a una contestació, continuant la conversació en forma de fil."
+    renote: "Pots compartir una Nota a la teva pròpia línia de temps. Inclús pots citar-les amb els teus comentaris."
+    reaction: "Pots afegir reaccions a les Notes. Entrarem més en detall a la pròxima pàgina."
+    menu: "Pots veure els detalls de les Notes, copiar enllaços i fer diferents accions."
+  _reaction:
+    title: "Què són les Reaccions?"
+    description: "Es poden reaccionar a les Notes amb diferents emoticones. Les reaccions et permeten expressar matisos que hi són més enllà d'un simple m'agrada."
+    letsTryReacting: "Es poden afegir reaccions fent clic al botó '+'. Prova reaccionant a aquesta nota!"
+    reactToContinue: "Afegeix una reacció per continuar."
+    reactNotification: "Rebràs notificacions en temps real quan un usuari reaccioni a les teves notes."
+    reactDone: "Pots desfer una reacció fent clic al botó '-'."
+  _timeline:
+    title: "El concepte de les línies de temps"
+    description1: "Misskey mostra diferents línies de temps basades en l'ús (algunes poden no estar disponibles depenent de la política del servidor)"
+    home: "Pots veure notes dels comptes que segueixes"
+    local: "Pots veure les notes dels usuaris del servidor."
+    social: "Es mostren les notes de les línies de temps d'Inici i Local."
+    global: "Pots veure les notes de tots els servidors connectats."
+    description2: "Pots canviar la línia de temps en qualsevol moment fent servir la barra de la pantalla superior."
+    description3: "A més hi ha línies de temps per llistes i per canals. Si vols saber més {link}."
+  _postNote:
+    title: "Configuració de la publicació de les notes"
+    description1: "Quan públiques una nota a Misskey hi ha diferents opcions disponibles. El formulari de publicació es veu així"
+    _visibility:
+      description: "Pots limitar qui pot veure les teves notes."
+      public: "La teva nota serà visible per a tots els usuaris."
+      home: "Publicar només a línia de temps d'Inici. La gent que visiti el teu perfil o mitjançant les remotes també la podran veure."
+      followers: "Només visible per a seguidors. Només els teus seguidors la podran veure i ningú més. Ningú més podrà fer renotes."
+      direct: "Només visible per a alguns seguidors, el destinatari rebre una notificació. Es pot fer servir com una alternativa als missatges directes."
+      doNotSendConfidencialOnDirect1: "Tingues cura quan enviïs informació sensible."
+      doNotSendConfidencialOnDirect2: "Els administradors del servidor poden veure tot el que escrius. Ves compte quan enviïs informació sensible en enviar notes directes a altres usuaris en servidors de poca confiança."
+      localOnly: "Publicar amb aquesta opció activada farà que la nota no federi amb altres servidors. Els usuaris d'altres servidors no podran veure la nota directament, sense importar les opcions de visualització."
+    _cw:
+      title: "Avís de Contingut (CW)"
+      description: "En comptes del cos de la nota es mostrarà el que s'escrigui al camp de 'comentaris'. Fent clic a 'Llegir més' es mostrarà el cos."
+      _exampleNote:
+        cw: "Això et farà venir gana!"
+        note: "Acabo de menjar-me un donut de xocolata 🍩😋"
+      useCases: "Això es fa servir per seguir normes del servidor sobre certes notes o per ocultar contingut sensible O revelador."
+  _howToMakeAttachmentsSensitive:
+    title: "Com marcar adjunts com a contingut sensible?"
+    description: "Per adjunts que sigui requerit per les normes del servidor o que puguin contenir material sensible, s'ha d'afegir l'opció 'sensible'."
+    tryThisFile: "Prova de marcar la imatge adjunta en aquest formulari com a sensible!"
+    _exampleNote:
+      note: "Oops! L'he fet bona en obrir la tapa de Nocilla..."
+    method: "Per marcar un adjunt com a sensible, fes clic a la miniatura de l'adjunt, obre el menú i fes clic a 'Marcar com a sensible'."
+    sensitiveSucceeded: "Quan adjuntis fitxers si us plau marca la sensibilitat seguint les normes del servidor."
+    doItToContinue: "Marca el fitxer adjunt com a sensible per poder continuar."
+  _done:
+    title: "Has completat el tutorial 🎉"
+    description: "Les funcions explicades aquí és una petita mostra. Per una explicació més detallada de com fer servir MissKey consulta {link}."
+_timelineDescription:
+  home: "A la línia de temps d'Inici pots veure les notes dels usuaris que segueixes."
+  local: "A la línia de temps Local pots veure les notes de tots els usuaris d'aquest servidor."
+  social: "La línia de temps Social mostren les notes de les línies de temps d'Inici i Local."
+  global: "A la línia de temps Global pots veure les notes de tots els servidors connectats."
+_serverRules:
+  description: "Un conjunt de regles que seran mostrades abans de registrar-se. Es recomanable configurar un resum dels termes d'ús."
+_serverSettings:
+  iconUrl: "URL de la icona"
+  appIconDescription: "Especifica la icona que es mostrarà quan el {host} es mostri en una aplicació."
+  appIconUsageExample: "Per exemple com a PWA, o quan es mostri com un favorit a la pàgina d'inici del telèfon mòbil"
+  appIconStyleRecommendation: "Com la icona pot ser retallada com un cercle o un quadrat, es recomana fer servir una icona amb un marge acolorit que l'envolti."
+  appIconResolutionMustBe: "La resolució mínima és {resolution}."
+  manifestJsonOverride: "Sobreescriure manifest.json"
+  shortName: "Nom curt"
+  shortNameDescription: "Una abreviatura del nom de la instància que es poguí mostrar en cas que el nom oficial sigui massa llarg"
+  fanoutTimelineDescription: "Quan es troba activat millora bastant el rendiment quan es recuperen les línies de temps i redueix la carrega de la base de dades. Com a contrapunt, l'ús de memòria de Redis es veurà incrementada. Considera d'estabilitat aquesta opció en cas de tenir un servidor amb poca memòria o si tens problemes de inestabilitat."
+  fanoutTimelineDbFallback: "Carregar de la base de dades"
+  fanoutTimelineDbFallbackDescription: "Quan s'activa, la línia de temps fa servir la base de dades per consultes adicionals si la línia de temps no es troba a la memòria cau. Si és desactiva la càrrega del servidor és veure reduïda, però també és reduirà el nombre de línies de temps que és poden obtenir."
+_accountMigration:
+  moveFrom: "Migrar un altre compte a aquest"
+  moveFromSub: "Crear un àlies per un altre compte"
+  moveFromLabel: "Compte original #{n}"
+  moveFromDescription: "Has de crear un àlies del compte que vols migrar en aquest compte.\nFes servir aquest format per posar el compte que vols migrar: @nomusuari@servidor.exemple.com\nPer esborrar l'àlies deixa el camp en blanc (no és recomanable de fer)"
+  moveTo: "Migrar aquest compte a un altre"
+  moveToLabel: "Compte al qual es vol migrar:"
+  moveCannotBeUndone: "Les migracions dels comptes no es poden desfer."
+  moveAccountDescription: "Això migrarà la teva compte a un altre diferent.\n ・Els seguidors d'aquest compte és passaran al compte nou de forma automàtica\n ・Es deixaran de seguir a tots els usuaris que es segueixen actualment en aquest compte\n ・No es poden crear notes noves, etc. en aquest compte\n\nSi bé la migració de seguidors es automàtica, has de preparar alguns pasos manualment per migrar la llista d'usuaris que segueixes. Per fer això has d'exportar els seguidors que després importaraes al compte nou mitjançant el menú de configuració. El mateix procediment s'ha de seguir per less teves llistes i els teus usuaris silenciats i bloquejats.\n\n(Aquesta explicació s'aplica a Misskey v13.12.0 i posteriors. Altres aplicacions, com Mastodon, poden funcionar diferent.)"
+  moveAccountHowTo: "Per fer la migració, primer has de crear un àlies per aquest compte al compte al qual vols migrar.\nDesprés de crear l'àlies, introdueix el compte al qual vols migrar amb el format següent: @nomusuari@servidor.exemple.com"
+  startMigration: "Migrar"
+  migrationConfirm: "Vols migrar aquest compte a {account}? Una vegada comenci la migració no es podrà parar O fer marxa enrere i no podràs tornar a fer servir aquest compte mai més."
+  movedAndCannotBeUndone: "Aquest compte ha migrat.\nLes migracions no es poden desfer."
+  postMigrationNote: "Aquest compte deixarà de seguir tots els comptes que segueix 24 hores després de germinar la migració.\nEl nombre de seguidors i seguits passarà a ser de zero. Per evitar que els teus seguidors no puguin veure les publicacions marcades com a només seguidors continuaren seguint aquest compte."
+  movedTo: "Nou compte:"
+_achievements:
+  earnedAt: "Desbloquejat el"
+  _types:
+    _notes1:
+      title: "Aquí, configurant el meu msky"
+      description: "Publica la teva primera Nota"
+      flavor: "Passa-t'ho bé fent servir Miskey!"
+    _notes10:
+      title: "Algunes notes"
+      description: "Publica 10 notes"
+    _notes100:
+      title: "Un piló de notes"
+      description: "Publica 100 notes"
+    _notes500:
+      title: "Cobert de notes"
+      description: "Publica 500 notes"
+    _notes1000:
+      title: "Un piló de notes"
+      description: "1 000 notes publicades"
+    _notes5000:
+      title: "Desbordament de notes"
+      description: "5 000 notes publicades"
+    _notes10000:
+      title: "Supernota"
+      description: "10 000 notes publicades"
+    _notes20000:
+      title: "Necessito... Més... Notes!"
+      description: "20 000 notes publicades"
+    _notes30000:
+      title: "Notes notes notes!"
+      description: "30 000 notes publicades"
+    _notes40000:
+      title: "Fàbrica de notes"
+      description: "40 000 notes publicades"
+    _notes50000:
+      title: "Planeta de notes"
+      description: "50 000 notes publicades"
+    _notes60000:
+      title: "Quàsar de notes"
+      description: "60 000 notes publicades"
+    _notes70000:
+      title: "Forat negre de notes"
+      description: "70 000 notes publicades"
+    _notes80000:
+      title: "Galàxia de notes"
+      description: "80 000 notes publicades"
+    _notes90000:
+      title: "Univers de notes"
+      description: "90 000 notes publicades"
+    _notes100000:
+      title: "ALL YOUR NOTE ARE BELONG TO US"
+      description: "100 000 notes publicades"
+      flavor: "Segur que tens moltes coses a dir?"
+    _login3:
+      title: "Principiant I"
+      description: "Vas iniciar sessió fa tres dies"
+      flavor: "Des d'avui diguem Misskist"
+    _login7:
+      title: "Principiant II"
+      description: "Vas iniciar sessió fa set dies"
+      flavor: "Ja saps com va funcionant tot?"
+    _login15:
+      title: "Principiant III"
+      description: "Vas iniciar sessió fa quinze dies"
+    _login30:
+      title: "Misskist I"
+      description: "Vas iniciar sessió fa trenta dies"
+    _login60:
+      title: "Misskist II"
+      description: "Vas iniciar sessió fa seixanta dies"
+    _login100:
+      title: "Misskist III"
+      description: "Vas iniciar sessió fa cent dies"
+      flavor: "Misskist violent"
+    _login200:
+      title: "Regular I"
+      description: "Vas iniciar sessió fa dos-cents dies"
+    _login300:
+      title: "Regular II"
+      description: "Vas iniciar sessió fa tres-cents dies"
+    _login400:
+      title: "Regular III"
+      description: "Vas iniciar sessió fa quatre-cents dies"
+    _login500:
+      title: "Expert I"
+      description: "Vas iniciar sessió fa cinc-cents dies"
+      flavor: "Amics, he dit massa vegades que soc un amant de les notes"
+    _login600:
+      title: "Expert II"
+      description: "Vas iniciar sessió fa sis-cents dies"
+    _login700:
+      title: "Expert III"
+      description: "Vas iniciar sessió fa set-cents dies"
+    _login800:
+      title: "Mestre de les Notes I"
+      description: "Vas iniciar sessió fa vuit-cents dies "
+    _login900:
+      title: "Mestre de les Notes II"
+      description: "Vas iniciar sessió fa nou-cents dies"
+    _login1000:
+      title: "Mestre de les Notes III"
+      description: "Vas iniciar sessió fa mil dies"
+      flavor: "Gràcies per fer servir MissKey!"
+    _noteClipped1:
+      title: "He de retallar-te!"
+      description: "Retalla la teva primera nota"
+    _noteFavorited1:
+      title: "Quan miro les estrelles"
+      description: "La primera vegada que vaig registrar el meu favorit"
+    _myNoteFavorited1:
+      title: "Vull una estrella"
+      description: "La meva nota va ser registrada com favorita per una de les altres persones"
+    _profileFilled:
+      title: "Estic a punt"
+      description: "Vaig fer la configuració de perfil"
+    _markedAsCat:
+      title: "Soc un gat"
+      description: "He establert el meu compte com si fos un Gat"
+      flavor: "Encara no tinc nom"
+    _following1:
+      title: "És el meu primer seguiment"
+      description: "És la primera vegada que et segueixo"
+    _following10:
+      title: "Segueix-me... Segueix-me..."
+      description: "Seguir 10 usuaris"
+    _following50:
+      title: "Molts amics"
+      description: "Seguir 50 comptes"
+    _following100:
+      title: "100 amics"
+      description: "Segueixes 100 comptes"
+    _following300:
+      title: "Sobrecàrrega d'amics"
+      description: "Segueixes 300 comptes"
+    _followers1:
+      title: "Primer seguidor"
+      description: "1 seguidor guanyat"
+    _followers10:
+      title: "Segueix-me!"
+      description: "10 seguidors guanyats"
+    _followers50:
+      title: "Venen en manada"
+      description: "50 seguidors guanyats"
+    _followers100:
+      title: "Popular"
+      description: "100 seguidors guanyats"
+    _followers300:
+      title: "Si us plau, d'un en un!"
+      description: "300 seguidors guanyats"
+    _followers500:
+      title: "Torre de ràdio"
+      description: "500 seguidors guanyats"
+    _followers1000:
+      title: "Influenciador"
+      description: "1 000 seguidors guanyats"
+    _collectAchievements30:
+      title: "Col·leccionista d'èxits "
+      description: "Desbloqueja 30 assoliments"
+    _viewAchievements3min:
+      title: "M'agraden els èxits "
+      description: "Mira la teva llista d'assoliments durant més de 3 minuts"
+    _iLoveMisskey:
+      title: "Estimo Misskey"
+      description: "Publica \"I ❤ #Misskey\""
+      flavor: "L'equip de desenvolupament de Misskey agraeix el vostre suport!"
+    _foundTreasure:
+      title: "A la Recerca del Tresor"
+      description: "Has trobat el tresor amagat"
+    _client30min:
+      title: "Parem una estona"
+      description: "Mantingues obert Misskey per 30 minuts"
+    _client60min:
+      title: "A totes amb Misskey"
+      description: "Mantingues Misskey obert per 60 minuts"
+    _noteDeletedWithin1min:
+      title: "No et preocupis"
+      description: "Esborra una nota al minut de publicar-la"
+    _postedAtLateNight:
+      title: "Nocturn"
+      description: "Publica una nota a altes hores de la nit "
+      flavor: "És hora d'anar a dormir."
+    _postedAt0min0sec:
+      title: "Rellotge xerraire"
+      description: "Publica una nota a les 0:00"
+      flavor: "Tic tac, tic tac, tic tac, DING!"
+    _selfQuote:
+      title: "Autoreferència "
+      description: "Cita una nota teva"
+    _htl20npm:
+      title: "Línia de temps fluida"
+      description: "La teva línia de temps va a més de 20npm (notes per minut)"
+    _viewInstanceChart:
+      title: "Analista "
+      description: "Mira els gràfics de la teva instància "
+    _outputHelloWorldOnScratchpad:
+      title: "Hola, món!"
+      description: "Escriu \"hola, món\" al bloc de notes"
+    _open3windows:
+      title: "Multi finestres"
+      description: "I va obrir més de tres finestres"
+    _driveFolderCircularReference:
+      title: "Consulteu la secció de bucle"
+      description: "Intenta crear carpetes recursives al Disc"
+    _reactWithoutRead:
+      title: "De veritat has llegit això?"
+      description: "Reaccions a una nota de més de 100 caràcters publicada fa menys de 3 segons "
+    _clickedClickHere:
+      title: "Fer clic"
+      description: "Has fet clic aquí "
+    _justPlainLucky:
+      title: "Ha sigut sort"
+      description: "Oportunitat de guanyar-lo amb una probabilitat d'un 0.005% cada 10 segons"
+    _setNameToSyuilo:
+      title: "soc millor"
+      description: "Posat \"siuylo\" com a nom"
+    _passedSinceAccountCreated1:
+      title: "Primer aniversari"
+      description: "Ja ha passat un any d'ençà que vas crear el teu compte"
+    _passedSinceAccountCreated2:
+      title: "Segon aniversari"
+      description: "Ja han passat dos anys d'ençà que vas crear el teu compte"
+    _passedSinceAccountCreated3:
+      title: "Tres anys"
+      description: "Ja han passat tres anys d'ençà que vas crear el teu compte"
+    _loggedInOnBirthday:
+      title: "Felicitats!"
+      description: "T'has identificat el dia del teu aniversari"
+    _loggedInOnNewYearsDay:
+      title: "Bon any nou!"
+      description: "T'has identificat el primer dia de l'any "
+      flavor: "A per un altre any memorable a la teva instància   "
+    _cookieClicked:
+      title: "Un joc en què fas clic a les galetes"
+      description: "Pica galetes"
+      flavor: "Espera, ets al lloc web correcte?"
+    _brainDiver:
+      title: "Busseja Ments"
+      description: "Publica un enllaç al Busseja Ments"
+      flavor: "Misskey-Misskey La-Tu-Ma"
+    _smashTestNotificationButton:
+      title: "Sobrecàrrega de proves"
+      description: "Envia moltes notificacions de prova en un període de temps molt curt"
+    _tutorialCompleted:
+      title: "Diploma del Curs Elemental de Misskey"
+      description: "Has completat el tutorial"
+    _bubbleGameExplodingHead:
+      title: "🤯"
+      description: "L'objecte més gran del joc de la bombolla "
+    _bubbleGameDoubleExplodingHead:
+      title: "Doble 🤯"
+      description: "Dos dels objectes més grans del joc de la bombolla al mateix temps"
+      flavor: "Pots emplenar una carmanyola com aquesta 🤯🤯 una mica"
 _role:
+  new: "Nou rol"
+  edit: "Editar el rol"
+  name: "Nom del rol"
+  description: "Descripció del rol"
+  permission: "Permisos de rol"
+  descriptionOfPermission: "Els <b>Moderadors</b> poden fer operacions bàsiques de moderació.\nEls <b>Administradors</b> poden canviar tots els ajustos del servidor."
+  assignTarget: "Assignar "
+  descriptionOfAssignTarget: "<b>Manual</b> per canviar manualment qui és part d'aquest rol i qui no.\n<b>Condicional</b> per afegir o eliminar de manera automàtica els usuaris d'aquest rol basat en una determinada condició."
+  manual: "Manual"
+  manualRoles: "Rols manuals"
+  conditional: "Condicional"
+  conditionalRoles: "Rols condicionals"
+  condition: "Condició"
+  isConditionalRole: "Aquest és un rol condicional"
+  isPublic: "Rol públic"
+  descriptionOfIsPublic: "Aquest rol es mostrarà al perfil dels usuaris al que se'ls assigni."
+  options: "Opcions"
+  policies: "Polítiques"
+  baseRole: "Plantilla de rols"
+  useBaseValue: "Fer servir els valors de la plantilla de rols"
+  chooseRoleToAssign: "Selecciona els rols a assignar"
+  iconUrl: "URL de la icona "
+  asBadge: "Mostrar com a insígnia "
+  descriptionOfAsBadge: "La icona d'aquest rol es mostrarà al costat dels noms d'usuaris que tinguin assignats aquest rol."
+  isExplorable: "Fer el rol explorable"
+  descriptionOfIsExplorable: "La línia de temps d'aquest rol i la llista d'usuaris seran públics si s'activa."
+  displayOrder: "Posició "
+  descriptionOfDisplayOrder: "Com més gran és el número, més dalt la seva posició a la interfície."
+  canEditMembersByModerator: "Permetre que els moderadors editin la llista d'usuaris en aquest rol"
+  descriptionOfCanEditMembersByModerator: "Quan s'activa, els moderadors, així com els administradors, podran afegir i treure usuaris d'aquest rol. Si es troba desactivat, només els administradors poden assignar usuaris."
+  priority: "Prioritat"
   _priority:
+    low: "Baixa"
     middle: "Mitjà"
+    high: "Alta"
   _options:
+    gtlAvailable: "Pot veure la línia de temps global"
+    ltlAvailable: "Pot veure la línia de temps local"
+    canPublicNote: "Pot enviar notes públiques"
+    canInvite: "Pot crear invitacions a la instància "
+    inviteLimit: "Límit d'invitacions "
+    inviteLimitCycle: "Temps de refresc de les invitacions"
+    inviteExpirationTime: "Interval de caducitat de les invitacions"
+    canManageCustomEmojis: "Gestiona els emojis personalitzats"
+    canManageAvatarDecorations: "Gestiona les decoracions dels avatars "
+    driveCapacity: "Capacitat del disc"
+    alwaysMarkNsfw: "Marca sempre els fitxers com a sensibles"
+    pinMax: "Nombre màxim de notes fixades"
     antennaMax: "Nombre màxim d'antenes"
+    wordMuteMax: "Nombre màxim de caràcters permesos a les paraules silenciades"
+    webhookMax: "Nombre màxim de Webhooks"
+    clipMax: "Nombre màxim de clips"
+    noteEachClipsMax: "Nombre màxim de notes dintre d'un clip"
+    userListMax: "Nombre màxim de llistes d'usuaris "
+    userEachUserListsMax: "Nombre màxim d'usuaris dintre d'una llista d'usuaris "
+    rateLimitFactor: "Limitador"
+    descriptionOfRateLimitFactor: "Límits baixos són menys restrictius, límits alts són més restrictius."
+    canHideAds: "Pot amagar els anuncis"
+    canSearchNotes: "Pot cercar notes"
+    canUseTranslator: "Pot fer servir el traductor"
+    avatarDecorationLimit: "Nombre màxim de decoracions que es poden aplicar els avatars"
+  _condition:
+    isLocal: "Usuari local"
+    isRemote: "Usuari remot"
+    createdLessThan: "Han passat menys de X a passat des de la creació del compte"
+    createdMoreThan: "Han passat més de X des de la creació del compte"
+    followersLessThanOrEq: "Té menys de X seguidors"
+    followersMoreThanOrEq: "Té X o més seguidors"
+    followingLessThanOrEq: "Segueix X o menys comptes"
+    followingMoreThanOrEq: "Segueix a X o més comptes"
+    notesLessThanOrEq: "Les publicacions són menys o igual a "
+    notesMoreThanOrEq: "Les publicacions són més o igual a "
+    and: "AND condicional "
+    or: "OR condicional"
+    not: "NOT condicional"
+_sensitiveMediaDetection:
+  description: "Redueix els esforços de moderació gràcies al reconeixement automàtic dels fitxers amb contingut sensible mitjançant Machine Learing. Això augmentarà la càrrega del servidor."
+  sensitivity: "Sensibilitat de la detecció "
+  sensitivityDescription: "Reduint la sensibilitat provocarà menys falsos positius. D'altra banda incrementant-ho generarà més falsos negatius."
+  setSensitiveFlagAutomatically: "Marcar com a sensible"
+  setSensitiveFlagAutomaticallyDescription: "Els resultats de la detecció interna seran desats, inclòs si aquesta opció es troba desactivada."
+  analyzeVideos: "Activar anàlisis de vídeos "
+  analyzeVideosDescription: "Analitzar els vídeos a més de les imatges. Això incrementarà lleugerament la càrrega del servidor."
+_emailUnavailable:
+  used: "Aquest correu electrònic ja s'està fent servir"
+  format: "El format del correu electrònic és invàlid "
+  disposable: "No es poden fer servir adreces de correu electrònic d'un sol ús "
+  mx: "Aquest servidor de correu electrònic no és vàlid "
+  smtp: "Aquest servidor de correu electrònic no respon"
+  banned: "No pots registrar-te amb aquesta adreça de correu electrònic "
+_ffVisibility:
+  public: "Publicar"
+  followers: "Visible només per a seguidors "
+  private: "Privat"
+_signup:
+  almostThere: "Ja quasi estem"
+  emailAddressInfo: "Si us plau, escriu la teva adreça de correu electrònic. No es farà pública."
+  emailSent: "S'ha enviat un correu de confirmació a ({email}). Si us plau, fes clic a l'enllaç per completar el registre."
+_accountDelete:
+  accountDelete: "Eliminar el compte"
+  mayTakeTime: "Com l'eliminació d'un compte consumeix bastants recursos, pot trigar un temps perquè es completi l'esborrat, depenent si tens molt contingut i la quantitat de fitxer que hagis pujat."
+  sendEmail: "Una vegada hagi finalitzat l'esborrat del compte rebràs un correu electrònic a l'adreça que tinguis registrada en aquest compte."
+  requestAccountDelete: "Demanar l'eliminació del compte"
+  started: "Ha començat l'esborrat del compte."
+  inProgress: "L'esborrat es troba en procés "
+_ad:
+  back: "Tornar"
+  reduceFrequencyOfThisAd: "Mostrar menys aquest anunci"
+  hide: "No mostrar mai"
+  timezoneinfo: "El dia de la setmana ve determinat del fus horari del servidor."
+  adsSettings: "Configuració d'anuncis "
+  notesPerOneAd: "Interval d'emplaçament d'anuncis en temps real (Notes per anuncis)"
+  setZeroToDisable: "Ajusta aquest valor a 0 per deshabilitar l'actualització d'anuncis en temps real"
+  adsTooClose: "L'interval actual pot fer que l'experiència de l'usuari sigui dolenta perquè l'interval és molt baix."
+_forgotPassword:
+  enterEmail: "Escriu l'adreça de correu electrònic amb la que et vas registrar. S'enviarà un correu electrònic amb un enllaç perquè puguis canviar-la."
+  ifNoEmail: "Si no vas fer servir una adreça de correu electrònic per registrar-te, si us plau posa't en contacte amb l'administrador."
+  contactAdmin: "Aquesta instància no suporta registrar-se amb correu electrònic. Si us plau, contacta amb l'administrador del servidor."
+_gallery:
+  my: "La meva Galeria "
+  liked: "Publicacions que t'han agradat"
+  like: "M'agrada "
+  unlike: "Ja no m'agrada"
 _email:
   _follow:
     title: "t'ha seguit"
+  _receiveFollowRequest:
+    title: "Has rebut una sol·licitud  de seguiment"
+_plugin:
+  install: "Instal·lar un afegit "
+  installWarn: "Si us plau, no instal·lis afegits que no siguin de confiança."
+  manage: "Gestionar els afegits"
+  viewSource: "Veure l'origen "
+_preferencesBackups:
+  list: "Llista de còpies de seguretat"
+  saveNew: "Fer una còpia de seguretat nova"
+  loadFile: "Carregar des d'un fitxer"
+  apply: "Aplicar en aquest dispositiu"
+  save: "Desar els canvis"
+  inputName: "Escriu un nom per aquesta còpia de seguretat"
+  cannotSave: "No s'ha pogut desar"
+  nameAlreadyExists: "Ja existeix una còpia de seguretat anomenada \"{name}\". Escriu un nom diferent."
+  applyConfirm: "Vols aplicar la còpia de seguretat \"{name}\" a aquest dispositiu? La configuració actual del dispositiu serà esborrada."
+  saveConfirm: "Desar còpia de seguretat com {name}?"
+  deleteConfirm: "Esborrar la còpia de seguretat {name}?"
+  renameConfirm: "Vols canvia el nom de la còpia de seguretat de \"{old}\" a \"{new}\"?"
+  noBackups: "No hi ha còpies de seguretat. Pots fer una còpia de seguretat de la configuració d'aquest dispositiu al servidor fent servir \"Crear nova còpia de seguretat\""
+  createdAt: "Creat el: {date} {time}"
+  updatedAt: "Actualitzat el: {date} {time}"
+  cannotLoad: "Hi ha hagut un error al carregar"
+  invalidFile: "Format del fitxer no vàlid "
+_registry:
+  scope: "Àmbit "
+  key: "Clau"
+  keys: "Claus"
+  domain: "Domini"
+  createKey: "Crear una clau"
+_aboutMisskey:
+  about: "Misskey és un programa de codi obert desenvolupar per syuilo des de 2014"
+  contributors: "Col·laboradors principals"
+  allContributors: "Tots els col·laboradors "
+  source: "Codi font"
+  translation: "Tradueix Misskey"
+  donate: "Fes un donatiu a Misskey"
+  morePatrons: "També agraïm el suport d'altres col·laboradors que no surten en aquesta llista. Gràcies! 🥰"
+  patrons: "Patrocinadors"
+  projectMembers: "Membres del projecte"
+_displayOfSensitiveMedia:
+  respect: "Ocultar imatges o vídeos marcats com a sensibles"
+  ignore: "Mostrar imatges o vídeos marcats com a sensibles"
+  force: "Ocultar totes les imatges o vídeos "
+_instanceTicker:
+  none: "No mostrar mai"
+  remote: "Mostrar per usuaris remots"
+  always: "Mostrar sempre"
+_serverDisconnectedBehavior:
+  reload: "Recarregar automàticament "
+  dialog: "Mostrar finestres de confirmació "
+  quiet: "Mostrar un avís que no molesti"
+_channel:
+  create: "Crear un canal"
+  edit: "Editar canal"
+  setBanner: "Estableix el bàner "
+  removeBanner: "Eliminar el.bàner"
+  featured: "Popular"
+  owned: "Propietat"
+  following: "Seguin"
+  usersCount: "{n} Participants"
+  notesCount: "{n} Notes"
+  nameAndDescription: "Nom i descripció "
+  nameOnly: "Nom només "
+  allowRenoteToExternal: "Permet la citació i l'impuls fora del canal"
+_menuDisplay:
+  sideFull: "Horitzontal "
+  sideIcon: "Horitzontal (icones)"
+  top: "A dalt"
+  hide: "Amagar"
+_wordMute:
+  muteWords: "Paraules silenciades"
+  muteWordsDescription: "Separar amb espais per la condició AND o amb salts de línia per la condició OR."
+  muteWordsDescription2: "Envolta les paraules amb barres per fer servir expressions regulars."
 _instanceMute:
   instanceMuteDescription: "Silencia tots els impulsos dels servidors seleccionats, també els usuaris que responen a altres d'un servidor silenciat."
+  instanceMuteDescription2: "Separar amb salts de línia"
+  title: "Ocultar notes de les instàncies en la llista."
+  heading: "Llista d'instàncies a silenciar"
 _theme:
+  explore: "Explorar els temes "
+  install: "Instal·lar un tema"
+  manage: "Gestionar els temes "
+  code: "Codi del tema"
   description: "Descripció"
+  installed: "{name} Instal·lat "
+  installedThemes: "Temes instal·lats "
+  builtinThemes: "Temes integrats"
+  alreadyInstalled: "Aquest tema ja es troba instal·lat "
+  invalid: "El format d'aquest tema no és correcte"
+  make: "Crear un tema"
+  base: "Base"
+  addConstant: "Afegir constant "
+  constant: "Constant"
+  defaultValue: "Valor per defecte"
+  color: "Color"
+  refProp: "Referència a una propietat"
+  refConst: "Referència a una constant "
+  key: "Clau"
+  func: "Funcions"
+  funcKind: "Tipus de funció "
+  argument: "Argument"
+  basedProp: "Propietat referenciada"
+  alpha: "Opacitat"
+  darken: "Enfosquir "
+  lighten: "Brillantor"
+  inputConstantName: "Escriu un nom per aquesta constant"
+  importInfo: "Si escrius el codi del tema aquí, el podràs importar a l'editor del tema"
+  deleteConstantConfirm: "Vols esborrar la constant {const}?"
   keys:
+    accent: "Accent"
+    bg: "Fons"
+    fg: "Text"
+    focus: "Enfocament"
+    indicator: "Indicador"
+    panel: "Taulell "
+    shadow: "Ombra"
+    header: "Capçalera"
+    navBg: "Fons de la barra lateral"
+    navFg: "Text de la barra lateral"
+    navHoverFg: "Text barra lateral (en passar per sobre)"
+    navActive: "Text barra lateral (actiu)"
+    navIndicator: "Indicador barra lateral"
+    link: "Enllaç"
+    hashtag: "Etiqueta"
     mention: "Menció"
+    mentionMe: "Mencions (jo)"
     renote: "Renotar"
+    modalBg: "Fons del modal"
     divider: "Divisor"
+    scrollbarHandle: "Maneta de la barra de desplaçament"
+    scrollbarHandleHover: "Maneta de la barra de desplaçament (en passar-hi per sobre)"
+    dateLabelFg: "Text de l'etiqueta de la data"
+    infoBg: "Fons d'informació "
+    infoFg: "Text d'informació "
+    infoWarnBg: "Fons avís "
+    infoWarnFg: "Text avís "
+    toastBg: "Fons notificació "
+    toastFg: "Text notificació "
+    buttonBg: "Fons botó "
+    buttonHoverBg: "Fons botó (en passar-hi per sobre)"
+    inputBorder: "Contorn del cap d'introducció "
+    listItemHoverBg: "Fons dels elements d'una llista"
+    driveFolderBg: "Fons de la carpeta Disc"
+    wallpaperOverlay: "Superposició del fons de pantalla "
+    badge: "Insígnia "
+    messageBg: "Fons del xat"
+    accentDarken: "Accent (fosc)"
+    accentLighten: "Accent (clar)"
+    fgHighlighted: "Text ressaltat"
 _sfx:
   note: "Notes"
+  noteMy: "Nota (per mi)"
   notification: "Notificacions"
   antenna: "Antenes"
+  channel: "Notificacions dels canals"
+  reaction: "Quan se selecciona una reacció "
+_soundSettings:
+  driveFile: "Fer servir un fitxer d'àudio del disc"
+  driveFileWarn: "Seleccionar un fitxer d'àudio del disc"
+  driveFileTypeWarn: "Fitxer no suportat "
+  driveFileTypeWarnDescription: "Seleccionar un fitxer d'àudio "
+  driveFileDurationWarn: "L'àudio és massa llarg"
+  driveFileDurationWarnDescription: "Els àudios molt llargs pot interrompre l'ús de Misskey. Vols continuar?"
+_ago:
+  future: "Futur "
+  justNow: "Ara mateix"
+  secondsAgo: "Fa {n} segons"
+  minutesAgo: "Fa {n} minuts"
+  hoursAgo: "Fa {n} hores"
+  daysAgo: "Fa {n} dies"
+  weeksAgo: "Fa {n} setmanes"
+  monthsAgo: "Fa {n} mesos"
+  yearsAgo: "Fa {n} anys"
+  invalid: "Res"
+_timeIn:
+  seconds: "En {n} segons"
+  minutes: "En {n} minuts"
+  hours: "En {n} hores"
+  days: "En {n} dies"
+  weeks: "En {n} setmanes"
+  months: "En {n} mesos"
+  years: "En {n} anys"
+_time:
+  second: "Segon(s)"
+  minute: "Minut(s)"
+  hour: "Hor(a)(es)"
+  day: "Di(a)(es)"
 _2fa:
+  alreadyRegistered: "J has registrat un dispositiu d'autenticació de doble factor."
+  registerTOTP: "Registrar una aplicació autenticadora"
+  step1: "Primer instal·la una aplicació autenticadora (com {a} o {b}) al teu dispositiu."
+  step2: "Després escaneja el codi QR que es mostra en aquesta pantalla."
+  step2Click: "Fent clic en aquest codi QR et permetrà registrar l'autenticació de doble factor a la teva clau de seguretat o en l'aplicació d'autenticació del teu dispositiu."
+  step2Uri: "Escriu la següent URI si estàs fent servir una aplicació d'escriptori "
+  step3Title: "Escriu un codi d'autenticació"
+  step3: "Escriu el codi d'autenticació (token) que es mostra a la teva aplicació per finalitzar la configuració."
+  setupCompleted: "Configuració terminada"
+  step4: "D'ara endavant quan accedeixis se't demanarà el token que has introduït."
+  securityKeyNotSupported: "El teu navegador no suporta claus de seguretat"
+  registerTOTPBeforeKey: "Configura una aplicació d'autenticació per registrar una clau de seguretat o una clau de pas."
+  securityKeyInfo: "A més de l'empremta digital o PIN per autenticar-te, pots configurar autenticació mitjançant maquinari que suporti claus de seguretat FIDO2, per protegir encara més el teu compte."
+  registerSecurityKey: "Registrar una clau de seguretat o clau de pas"
+  securityKeyName: "Escriu un nom per la clau"
+  tapSecurityKey: "Seguiu les instruccions del navegador i registrar les claus de seguretat o la clau de pas"
+  removeKey: "Esborrar la clau de seguretat"
+  removeKeyConfirm: "Esborrar la còpia de seguretat {name}?"
+  whyTOTPOnlyRenew: "L'aplicació d'autenticació no es pot eliminar mentre hi hagi una clau de seguretat registrada."
+  renewTOTP: "Reconfigurar l'aplicació d'autenticació "
+  renewTOTPConfirm: "Això farà que els codis de validació de l'antiga aplicació deixin de funcionar"
+  renewTOTPOk: "Reconfigurar"
   renewTOTPCancel: "No, gràcies"
+  checkBackupCodesBeforeCloseThisWizard: "Abans de tancar aquesta finestra, comprova el següent codi de seguretat."
+  backupCodes: "Codi de seguretat."
+  backupCodesDescription: "Si l'aplicació d'autenticació no es pot utilitzar, es pot accedir al compte utilitzant els següents codis de còpia de seguretat. Assegura't de mantenir aquests codis en un lloc segur. Cada codi es pot utilitzar només una vegada."
+  backupCodeUsedWarning: "Es va utilitzar un codi de còpia de seguretat. Si l'aplicació de certificació està disponible, reconfigura l'aplicació d'autenticació tan aviat com sigui possible."
+  backupCodesExhaustedWarning: "Es van utilitzar tots els codis de còpia de seguretat. Si no es pot utilitzar l'aplicació d'autenticació, ja no es pot accedir al compte. Torna a registrar l'aplicació d'autenticació."
+_permissions:
+  "read:account": "Veure la informació del compte."
+  "write:account": "Editar la informació del compte."
+  "read:blocks": "Veure la llista d'usuaris bloquejats"
+  "write:blocks": "Editar la llista d'usuaris blocats"
+  "read:drive": "Accedeix als teus fitxers i carpetes del Disc"
+  "write:drive": "Editar o eliminar els teus fitxers i carpetes al Disc"
+  "read:favorites": "Veure la teva llista de favorits"
+  "write:favorites": "Editar la teva llista de favorits"
+  "read:following": "Veure informació de qui segueixes"
+  "write:following": "Segueix o deixa de seguir altres comptes"
+  "read:messaging": "Veure els teus xats"
+  "write:messaging": "Crear o esborrar missatges de xat"
+  "read:mutes": "Veure la teva llista d'usuaris silenciats"
+  "write:mutes": "Editar la teva llista d'usuaris silenciats"
+  "write:notes": "Crear o esborrar notes"
+  "read:notifications": "Veure les teves notificacions"
+  "write:notifications": "Gestionar les teves notificacions"
+  "read:reactions": "Veure les teves reaccions"
+  "write:reactions": "Editar les teves reaccions"
+  "write:votes": "Votar en una enquesta"
+  "read:pages": "Veure les teves pàgines "
+  "write:pages": "Editar o esborrar les teves pàgines "
+  "read:page-likes": "Veure la llista de les pàgines que t'han agradat"
+  "write:page-likes": "Editar la llista de les pàgines que t'han agradat"
+  "read:user-groups": "Veure els teus grups d'usuaris "
+  "write:user-groups": "Editar o esborrar els teus grups d'usuaris "
+  "read:channels": "Veure els teus canals"
+  "write:channels": "Editar els teus canals"
+  "read:gallery": "Veure la teva galeria "
+  "write:gallery": "Editar la teva galeria"
+  "read:gallery-likes": "Veure la llista de publicacions de galeries que t'han agradat"
+  "write:gallery-likes": "Editar la llista de publicacions de galeries que t'han agradat"
+  "read:flash": "Veure reproduccions"
+  "write:flash": "Editar reproduccions"
+  "read:flash-likes": "Veure la llista de reproduccions que t'han agradat"
+  "write:flash-likes": "Editar la llista de reproduccions que t'han agradat"
+  "read:admin:abuse-user-reports": "Veure informes d'usuaris "
+  "write:admin:delete-account": "Esborrar compte d'usuari "
+  "write:admin:delete-all-files-of-a-user": "Esborrar tots els fitxers d'un usuari"
+  "read:admin:index-stats": "Veure l'índex de la base de dades"
+  "read:admin:table-stats": "Veure la informació de les taules a la base de dades"
+  "read:admin:user-ips": "Veure adreça IP de l'usuari "
+  "read:admin:meta": "Veure meta-informació del servidor"
+  "write:admin:reset-password": "Reiniciar contrasenya d'usuari "
+  "write:admin:resolve-abuse-user-report": "Resoldre informes d'usuaris "
+  "write:admin:send-email": "Enviar correu electrònic "
+  "read:admin:server-info": "Veure informació del servidor"
+  "read:admin:show-moderation-log": "Veure registre de moderació "
+  "read:admin:show-user": "Veure informació privada de l'usuari "
+  "read:admin:show-users": "Veure informació privada de l'usuari "
+  "write:admin:suspend-user": "Suspendre usuari"
+  "write:admin:unset-user-avatar": "Esborrar avatar d'usuari "
+  "write:admin:unset-user-banner": "Esborrar bàner de l'usuari "
+  "write:admin:unsuspend-user": "Treure la suspensió d'un usuari"
+  "write:admin:meta": "Gestionar les metadades de la instància"
+  "write:admin:user-note": "Gestionar les notes de moderació "
+  "write:admin:roles": "Gestionar rols"
+  "read:admin:roles": "Veure rols"
+  "write:admin:relays": "Gestionar relé"
+  "read:admin:relays": "Veure relés"
+  "write:admin:invite-codes": "Gestionar codis d'invitació "
+  "read:admin:invite-codes": "Veure codis d'invitació "
+  "write:admin:announcements": "Gestionar anuncis"
+  "read:admin:announcements": "Veure anuncis"
+  "write:admin:avatar-decorations": "Gestionar la decoració dels avatars"
+  "read:admin:avatar-decorations": "Veure les decoracions dels avatars"
+  "write:admin:federation": "Gestionar la federació d'instàncies "
+  "write:admin:account": "Gestionar els comptes d'usuaris "
+  "read:admin:account": "Veure els comptes d'usuaris "
+  "write:admin:emoji": "Edició d'emojis"
+  "read:admin:emoji": "Veure emojis"
+  "write:admin:queue": "Gestionar la cua de feines"
+  "read:admin:queue": "Veure la cua de feines"
 _antennaSources:
   all: "Totes les publicacions"
   homeTimeline: "Publicacions dels usuaris seguits"
@@ -580,18 +2025,76 @@ _widgets:
   timeline: "Línia de temps"
   activity: "Activitat"
   federation: "Federació"
+  button: "Botó "
   jobQueue: "Cua de tasques"
   _userList:
     chooseList: "Tria una llista"
 _cw:
+  hide: "Amagar"
   show: "Carregar més"
+  chars: "{count} caràcters "
+  files: "{count} fitxer(s)"
+_poll:
+  noOnlyOneChoice: "Es necessita escollir dues opcions com a mínim "
+  choiceN: "Opció {n}"
+  noMore: "No pots afegir més opcions"
+  canMultipleVote: "Permetre escollir diferents opcions"
+  expiration: "Finalitza el"
+  infinite: "Mai"
+  at: "Finalitza en..."
+  after: "Finalitza després..."
+  deadlineDate: "Data de finalització "
+  deadlineTime: "Hor(a)(es)"
+  duration: "Duració "
+  votesCount: "{n} vots"
+  totalVotes: "{n} vots en total"
+  vote: "Votar en una enquesta"
+  showResult: "Veure resultats"
+  voted: "Has votat"
+  closed: "Finalitzada"
+  remainingDays: "Queden {d} dies i {h} hores per finalitzar"
+  remainingHours: "Queden {h} hores i {m} minuts"
+  remainingMinutes: "Queden {m} minuts i {s} segons"
+  remainingSeconds: "Queden {s} segons"
 _visibility:
+  public: "Públic "
+  publicDescription: "La teva nota la podrà veure tothom "
   home: "Inici"
+  homeDescription: "Publicar només a la línia de temps d'Inici "
   followers: "Seguidors"
+  followersDescription: "Fes només visible per als teus seguidors"
+  specified: "Directe"
+  specifiedDescription: "Fer visible només per alguns usuaris"
+  disableFederation: "Sense federar"
+  disableFederationDescription: "No enviar a altres servidors"
+_postForm:
+  replyPlaceholder: "Contestar..."
+  quotePlaceholder: "Citar..."
+  channelPlaceholder: "Publicar a un canal..."
+  _placeholders:
+    a: "Que vols dir?..."
+    b: "Alguna cosa interessant al teu voltant?..."
+    c: "Què et passa pel cap?..."
+    d: "Què vols dir?..."
+    e: "Escriu alguna cosa..."
+    f: "Esperant que escriguis qualsevol cosa..."
 _profile:
+  name: "Nom"
   username: "Nom d'usuari"
+  description: "Biografia "
+  youCanIncludeHashtags: "Pots posar etiquetes a la teva biografia "
+  metadata: "Informació adicional "
+  metadataEdit: "Editar la informació adicional "
+  metadataDescription: "Amb això podràs mostrar camps d'informació adicional al teu perfil."
+  metadataLabel: "Etiqueta "
+  metadataContent: "Contingut"
+  changeAvatar: "Canviar l'avatar "
+  changeBanner: "Canviar el bàner "
+  verifiedLinkDescription: "Escrivint una adreça URL que enllaci a aquest perfil, una icona de propietat verificada es mostrarà al costat del camp."
+  avatarDecorationMax: "Pot afegir un màxim de {max} decoracions."
 _exportOrImport:
   allNotes: "Totes les publicacions"
+  clips: "Retalls"
   followingList: "Seguint"
   muteList: "Silencia"
   blockingList: "Bloqueja"
@@ -604,18 +2107,74 @@ _timelines:
   social: "Social"
   global: "Global"
 _play:
+  viewSource: "Veure l'origen "
+  featured: "Popular"
+  title: "Títol "
+  script: "Script"
   summary: "Descripció"
 _pages:
+  viewSource: "Veure l'origen "
+  viewPage: "Veure les teves pàgines "
+  like: "M'agrada "
+  unlike: "Treure m'agrada "
+  my: "Les meves pàgines "
+  liked: "Pàgines que m'agraden "
+  featured: "Popular"
+  inspector: "Inspeccionar"
   contents: "Contingut"
+  content: "Bloquejar la pàgina "
+  variables: "Variables"
+  title: "Títol "
+  url: "URL de la pàgina "
+  summary: "Resum de la pàgina "
+  alignCenter: "Centrar elements"
+  hideTitleWhenPinned: "Amagar el títol de la pàgina quan estigui fixada al perfil"
+  font: "Lletra tipogràfica"
+  fontSerif: "Serif"
+  fontSansSerif: "Sans Serif"
+  eyeCatchingImageSet: "Escull una miniatura"
+  eyeCatchingImageRemove: "Esborrar la miniatura"
+  chooseBlock: "Afegeix un bloc"
+  selectType: "Seleccionar tipus"
+  contentBlocks: "Contingut"
+  inputBlocks: "Entrada "
+  specialBlocks: "Especial"
   blocks:
+    text: "Text"
+    textarea: "Àrea de text"
+    section: "Secció "
     image: "Imatges"
+    button: "Botó "
+    note: "Incorporar una Nota"
     _note:
       id: "ID de la publicació"
+      idDescription: "Alternativament pots enganxar l'adreça URL de la nota aquí."
       detailed: "Mostra els detalls"
+_relayStatus:
+  requesting: "Pendent"
+  accepted: "Acceptat"
+  rejected: "Rebutjat"
 _notification:
+  fileUploaded: "Fitxer pujat sense cap problema"
+  youGotMention: "{name} t'ha mencionat"
+  youGotReply: "{name} t'ha contestat"
+  youGotQuote: "{name} t'ha citat"
   youRenoted: "Impulsat per {name}"
   youWereFollowed: "t'ha seguit"
+  youReceivedFollowRequest: "Has rebut una petició de seguiment"
+  yourFollowRequestAccepted: "La teva petició de seguiment ha sigut acceptada"
+  pollEnded: "Ja pots veure els resultats de l'enquesta "
+  newNote: "Nota nova"
   unreadAntennaNote: "Antena {name}"
+  roleAssigned: "Rol assignat "
+  emptyPushNotificationMessage: "Les notificacions han sigut actualitzades"
+  achievementEarned: "Aconseguiment desblocat"
+  testNotification: "Notificació de prova"
+  checkNotificationBehavior: "Comprova el comportament de la notificació "
+  sendTestNotification: "Enviar notificació de prova"
+  notificationWillBeDisplayedLikeThis: "Les notificacions és veure'n així "
+  reactedBySomeUsers: "Han reaccionat {n} usuaris"
+  renotedBySomeUsers: "L'han impulsat {n} usuaris"
   _types:
     all: "Tots"
     follow: "Seguint"
@@ -645,8 +2204,55 @@ _deck:
     tl: "Línia de temps"
     antenna: "Antena"
     list: "Llistes"
+    channel: "Canals"
     mentions: "Mencions"
     direct: "Publicacions directes"
+_webhookSettings:
+  name: "Nom"
+  active: "Activat"
 _moderationLogTypes:
   suspend: "Suspèn"
   resetPassword: "Restableix la contrasenya"
+  suspendRemoteInstance: "Servidor remot suspès "
+  unsuspendRemoteInstance: "S'ha tret la suspensió del servidor remot"
+  markSensitiveDriveFile: "Fitxer marcat com a sensible"
+  unmarkSensitiveDriveFile: "S'ha tret la marca de sensible del fitxer"
+  resolveAbuseReport: "Informe resolt"
+  createInvitation: "Crear codi d'invitació "
+  createAd: "Anunci creat"
+  deleteAd: "Anunci esborrat"
+  updateAd: "Anunci actualitzat"
+  createAvatarDecoration: "Decoració de l'avatar creada"
+  updateAvatarDecoration: "S'ha actualitzat la decoració de l'avatar "
+  deleteAvatarDecoration: "S'ha esborrat la decoració de l'avatar "
+  unsetUserAvatar: "Esborrar l'avatar d'aquest usuari"
+  unsetUserBanner: "Esborrar el bàner d'aquest usuari"
+_fileViewer:
+  title: "Detall del fitxer"
+  type: "Tipus de fitxer"
+  size: "Mida"
+  url: "URL"
+  uploadedAt: "Pujat el"
+  attachedNotes: "Notes amb aquest fitxer"
+  thisPageCanBeSeenFromTheAuthor: "Aquesta pàgina només la pot veure l'usuari que ha pujat aquest fitxer."
+_externalResourceInstaller:
+  title: "Instal·lar des d'un lloc extern"
+  checkVendorBeforeInstall: "Assegura't que qui distribueix aquest recurs és fiable abans d'instal·lar-ho."
+  _plugin:
+    title: "Vols instal·lar aquest afegit?"
+    metaTitle: "Informació de l'afegit "
+  _theme:
+    title: "Vols instal·lar aquest tema?"
+    metaTitle: "Informació del tema"
+  _meta:
+    base: "Paleta de colors base"
+  _vendorInfo:
+    title: "Informació del distribuïdor "
+    endpoint: "Punt final referenciat"
+    hashVerify: "Verificació d'integritat "
+  _errors:
+    _invalidParams:
+      title: "Paràmetres no vàlids "
+_reversi:
+  total: "Total"
+
diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml
index afe613677f..6dad336b7f 100644
--- a/locales/cs-CZ.yml
+++ b/locales/cs-CZ.yml
@@ -366,6 +366,8 @@ hcaptcha: "hCaptcha"
 enableHcaptcha: "Aktivovat hCaptchu"
 hcaptchaSiteKey: "Klíč stránky"
 hcaptchaSecretKey: "Tajný Klíč (Secret Key)"
+mcaptchaSiteKey: "Klíč stránky"
+mcaptchaSecretKey: "Tajný Klíč (Secret Key)"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "Zapnout ReCAPTCHu"
 recaptchaSiteKey: "Klíč stránky"
@@ -1003,6 +1005,7 @@ resetPasswordConfirm: "Opravdu chcete resetovat heslo?"
 sensitiveWords: "Citlivá slova"
 sensitiveWordsDescription: "Viditelnost všech poznámek obsahujících některé z nakonfigurovaných slov bude automaticky nastavena na \"Domů\". Můžete jich uvést více tak, že je oddělíte pomocí řádků."
 sensitiveWordsDescription2: "Použití mezer vytvoří výrazy AND a obklopení klíčových slov lomítky je změní na regulární výraz."
+prohibitedWordsDescription2: "Použití mezer vytvoří výrazy AND a obklopení klíčových slov lomítky je změní na regulární výraz."
 notesSearchNotAvailable: "Vyhledávání poznámek je nedostupné."
 license: "Licence"
 unfavoriteConfirm: "Opravdu chcete odstranit z oblíbených?"
@@ -1092,7 +1095,10 @@ iHaveReadXCarefullyAndAgree: "Přečetl jsem si text \"{x}\" a souhlasím s ním
 icon: "Avatar"
 replies: "Odpovědi"
 renotes: "Přeposlat"
+sourceCode: "Zdrojový kód"
 flip: "Otočit"
+lastNDays: "Posledních {n} dnů"
+surrender: "Zrušit"
 _initialAccountSetting:
   accountCreated: "Váš účet byl úspěšně vytvořen!"
   letsStartAccountSetup: "Pro začátek si nastavte svůj profil."
@@ -1825,6 +1831,7 @@ _profile:
 _exportOrImport:
   allNotes: "Všechny poznámky"
   favoritedNotes: "Oblíbené poznámky"
+  clips: "Oříznout"
   followingList: "Sledovaní"
   muteList: "Ztlumit"
   blockingList: "Zablokovat"
@@ -2016,3 +2023,6 @@ _moderationLogTypes:
   suspend: "Zmrazit"
   resetPassword: "Resetovat heslo"
   createInvitation: "Vygenerovat pozvánku"
+_reversi:
+  total: "Celkem"
+
diff --git a/locales/da-DK.yml b/locales/da-DK.yml
index 08c15ed092..d1fbec9f67 100644
--- a/locales/da-DK.yml
+++ b/locales/da-DK.yml
@@ -1,2 +1,3 @@
 ---
 _lang_: "Dansk"
+
diff --git a/locales/de-DE.yml b/locales/de-DE.yml
index 83b254b2d5..7cb451e233 100644
--- a/locales/de-DE.yml
+++ b/locales/de-DE.yml
@@ -121,9 +121,15 @@ sensitive: "Sensibel"
 add: "Hinzufügen"
 reaction: "Reaktionen"
 reactions: "Reaktionen"
+emojiPicker: "Emoji auswählen"
+pinnedEmojisForReactionSettingDescription: "Lege Emojis fest, die angepinnt werden sollen, um sie beim Reagieren als Erstes anzuzeigen."
+pinnedEmojisSettingDescription: "Lege Emojis fest, die angepinnt werden sollen, um sie in der Emoji-Auswahl als Erstes anzuzeigen"
+overwriteFromPinnedEmojisForReaction: "Überschreiben mit den Reaktions-Einstellungen"
+overwriteFromPinnedEmojis: "Überschreiben mit den allgemeinen Einstellungen"
 reactionSettingDescription2: "Ziehe um Anzuordnen, klicke um zu löschen, drücke „+“ um hinzuzufügen"
 rememberNoteVisibility: "Notizsichtbarkeit merken"
 attachCancel: "Anhang entfernen"
+deleteFile: "Datei gelöscht"
 markAsSensitive: "Als sensibel markieren"
 unmarkAsSensitive: "Als nicht sensibel markieren"
 enterFileName: "Dateinamen eingeben"
@@ -178,7 +184,7 @@ searchWith: "Suchen: {q}"
 youHaveNoLists: "Du hast keine Listen"
 followConfirm: "Möchtest du {name} wirklich folgen?"
 proxyAccount: "Proxy-Benutzerkonto"
-proxyAccountDescription: "Ein Proxy-Benutzerkonto ist ein Benutzerkonto, das sich für Nutzer unter bestimmten Konditionen wie ein Follower aus einer fremden Instanz verhält. Zum Beispiel wird die Aktivität eines Nutzers aus einer fremden Instanz nicht an diese Instanz übermittelt, falls es keinen Benutzer dieser Instanz gibt, der diesem Nutzer aus fremder Instanz folgt. In diesem Fall folgt stattdessen das Proxy-Benutzerkonto."
+proxyAccountDescription: "Ein Proxy-Konto ist ein Benutzerkonto, das unter bestimmten Bedingungen als Follower für Benutzer fremder Instanzen fungiert. Wenn zum Beispiel ein Benutzer einen Benutzer einer fremden Instanz zu einer Liste hinzufügt, werden die Aktivitäten des entfernten Benutzers nicht an die Instanz übermittelt, wenn kein lokaler Benutzer diesem Benutzer folgt; stattdessen folgt das Proxy-Konto."
 host: "Hostname"
 selectUser: "Benutzer auswählen"
 recipient: "Empfänger"
@@ -260,6 +266,7 @@ removed: "Erfolgreich gelöscht"
 removeAreYouSure: "Möchtest du „{x}“ wirklich entfernen?"
 deleteAreYouSure: "Möchtest du „{x}“ wirklich löschen?"
 resetAreYouSure: "Wirklich zurücksetzen?"
+areYouSure: "Bist du sicher?"
 saved: "Erfolgreich gespeichert"
 messaging: "Chat"
 upload: "Hochladen"
@@ -354,7 +361,7 @@ enableLocalTimeline: "Lokale Chronik aktivieren"
 enableGlobalTimeline: "Globale Chronik aktivieren"
 disablingTimelinesInfo: "Administratoren und Moderatoren haben immer Zugriff auf alle Chroniken, auch wenn diese deaktiviert sind."
 registration: "Registrieren"
-enableRegistration: "Registration neuer Benutzer erlauben"
+enableRegistration: "Registrierung neuer Benutzer erlauben"
 invite: "Einladen"
 driveCapacityPerLocalAccount: "Drive-Kapazität pro lokalem Benutzerkonto"
 driveCapacityPerRemoteAccount: "Drive-Kapazität pro Benutzer fremder Instanzen"
@@ -372,6 +379,11 @@ hcaptcha: "hCaptcha"
 enableHcaptcha: "hCaptcha aktivieren"
 hcaptchaSiteKey: "Site key"
 hcaptchaSecretKey: "Secret key"
+mcaptcha: "mCaptcha"
+enableMcaptcha: "mCaptcha aktivieren"
+mcaptchaSiteKey: "Site key"
+mcaptchaSecretKey: "Secret key"
+mcaptchaInstanceUrl: "mCaptcha Instanz-URL"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "reCAPTCHA aktivieren"
 recaptchaSiteKey: "Site key"
@@ -429,7 +441,7 @@ lastUsed: "Zuletzt benutzt"
 lastUsedAt: "Zuletzt verwendet: {t}"
 unregister: "Deaktivieren"
 passwordLessLogin: "Passwortloses Anmelden"
-passwordLessLoginDescription: "Ermöglicht passwortfreies Einloggen, nur via Security-Token oder Passkey"
+passwordLessLoginDescription: "Ermöglicht passwortloses Einloggen mit einem Security-Token oder Passkey"
 resetPassword: "Passwort zurücksetzen"
 newPasswordIs: "Das neue Passwort ist „{password}“"
 reduceUiAnimation: "Animationen der Benutzeroberfläche reduzieren"
@@ -619,6 +631,7 @@ medium: "Mittel"
 small: "Klein"
 generateAccessToken: "Zugriffstoken generieren"
 permission: "Berechtigungen"
+adminPermission: "Administratorberechtigung"
 enableAll: "Alle aktivieren"
 disableAll: "Alle deaktivieren"
 tokenRequested: "Zugriff zum Benutzerkonto gewähren"
@@ -973,6 +986,7 @@ neverShow: "Nicht wieder anzeigen"
 remindMeLater: "Vielleicht später"
 didYouLikeMisskey: "Gefällt dir Sharkey?"
 pleaseDonate: "Sharkey ist die kostenlose Software, die von {host} verwendet wird. Wir würden uns über Spenden freuen, damit dessen Entwicklung weitergeführt werden kann!"
+pleaseDonateInstance: "Du kannst {host} auch direkt unterstützen, indem du an deine Instanz Administration spendest."
 roles: "Rollen"
 role: "Rolle"
 noRole: "Rolle nicht gefunden"
@@ -1023,6 +1037,7 @@ resetPasswordConfirm: "Wirklich Passwort zurücksetzen?"
 sensitiveWords: "Sensible Wörter"
 sensitiveWordsDescription: "Die Notizsichtbarkeit aller Notizen, die diese Wörter enthalten, wird automatisch auf \"Startseite\" gesetzt. Durch Zeilenumbrüche können mehrere konfiguriert werden."
 sensitiveWordsDescription2: "Durch die Verwendung von Leerzeichen können AND-Verknüpfungen angegeben werden und durch das Umgeben von Schrägstrichen können reguläre Ausdrücke verwendet werden."
+prohibitedWordsDescription2: "Durch die Verwendung von Leerzeichen können AND-Verknüpfungen angegeben werden und durch das Umgeben von Schrägstrichen können reguläre Ausdrücke verwendet werden."
 hiddenTags: "Ausgeblendete Hashtags"
 hiddenTagsDescription: "Die hier eingestellten Tags werden nicht mehr in den Trends angezeigt. Mit der Umschalttaste können mehrere ausgewählt werden."
 notesSearchNotAvailable: "Die Notizsuche ist nicht verfügbar."
@@ -1144,12 +1159,15 @@ hideRepliesToOthersInTimelineAll: "Antworten von allen momentan gefolgten Benutz
 confirmShowRepliesAll: "Dies ist eine unwiderrufliche Aktion. Wirklich Antworten von allen momentan gefolgten Benutzern in der Chronik anzeigen?"
 confirmHideRepliesAll: "Dies ist eine unwiderrufliche Aktion. Wirklich Antworten von allen momentan gefolgten Benutzern nicht in der Chronik anzeigen?"
 externalServices: "Externe Dienste"
+sourceCode: "Quellcode"
 impressum: "Impressum"
 impressumUrl: "Impressums-URL"
 impressumDescription: "In manchen Ländern, wie Deutschland und dessen Umgebung, ist die Angabe von Betreiberinformationen (ein Impressum) bei kommerziellem Betrieb zwingend."
 privacyPolicy: "Datenschutzerklärung"
 privacyPolicyUrl: "Datenschutzerklärungs-URL"
 tosAndPrivacyPolicy: "Nutzungsbedingungen und Datenschutzerklärung"
+donation: "Spenden"
+donationUrl: "Spenden-URL"
 avatarDecorations: "Profilbilddekoration"
 attach: "Anbringen"
 detach: "Entfernen"
@@ -1165,6 +1183,11 @@ signupPendingError: "Beim Überprüfen der Mailadresse ist etwas schiefgelaufen.
 cwNotationRequired: "Ist \"Inhaltswarnung verwenden\" aktiviert, muss eine Beschreibung gegeben werden."
 doReaction: "Reagieren"
 code: "Code"
+decorate: "Dekorieren"
+addMfmFunction: "MFM hinzufügen"
+sfx: "Soundeffekte"
+lastNDays: "Letzten {n} Tage"
+surrender: "Abbrechen"
 _announcement:
   forExistingUsers: "Nur für existierende Nutzer"
   forExistingUsersDescription: "Ist diese Option aktiviert, wird diese Ankündigung nur Nutzern angezeigt, die zum Zeitpunkt der Ankündigung bereits registriert sind. Ist sie deaktiviert, wird sie auch Nutzern, die sich nach dessen Veröffentlichung registrieren, angezeigt."
@@ -1174,6 +1197,7 @@ _announcement:
   tooManyActiveAnnouncementDescription: "Zu viele aktive Ankündigungen können die Benutzerfreundlichkeit verschlechtern. Es wird empfohlen, veraltete Ankündigungen zu archivieren."
   readConfirmTitle: "Als gelesen markieren?"
   readConfirmText: "Dies markiert den Inhalt von \"{title}\" als gelesen."
+  shouldNotBeUsedToPresentPermanentInfo: "Es wird empfohlen, Ankündigungen für aktuelle und zeitlich begrenzte Neuigkeiten zu nutzen, statt für Informationen, die langfristig relevant sind."
   dialogAnnouncementUxWarn: "Bei der Verwendung von mehr als zwei Meldungen im Dialog-Format wird um Vorsicht geboten, da dies negative Auswirkungen auf die UX haben kann."
   silence: "Keine Benachrichtigung"
   silenceDescription: "Wenn aktiviert, gibt diese Meldung keine Nachricht aus und muss nicht als \"gelesen\" markiert werden."
@@ -1203,6 +1227,24 @@ _initialTutorial:
     description: "Hier kannst du sehen, wie Misskey funktioniert"
   _note:
     title: "Was sind Notizen?"
+    description: "Beiträge auf Misskey heißen \"Notizen\". Notizen werden chronologisch in der Chronik angeordnet und in Echtzeit aktualisiert."
+    reply: "Klicke auf diesen Button, um auf eine Nachricht zu antworten. Es ist auch möglich, auf Antworten zu antworten und die Unterhaltung wie einen Thread fortzusetzen."
+  _reaction:
+    title: "Was sind Reaktionen?"
+    reactToContinue: "Füge eine Reaktion hinzu, um fortzufahren."
+    reactNotification: "Du erhältst Echtzeit-Benachrichtigungen, wenn jemand auf deine Notiz reagiert."
+  _postNote:
+    _visibility:
+      description: "Du kannst einschränken, wer deine Notiz sehen kann."
+      public: "Deine Notiz wird für alle Nutzer sichtbar sein."
+      doNotSendConfidencialOnDirect1: "Sei vorsichtig, wenn du sensible Informationen verschickst!"
+    _cw:
+      title: "Inhaltswarnung"
+  _done:
+    title: "Du hast das Tutorial abgeschlossen! 🎉"
+_timelineDescription:
+  local: "In der lokalen Chronik siehst du Notizen von allen Benutzern auf diesem Server."
+  global: "In der globalen Chronik siehst du Notizen von allen föderierten Servern."
 _serverRules:
   description: "Eine Reihe von Regeln, die vor der Registrierung angezeigt werden. Eine Zusammenfassung der Nutzungsbedingungen anzuzeigen ist empfohlen."
 _serverSettings:
@@ -1215,6 +1257,8 @@ _serverSettings:
   shortName: "Abkürzung"
   shortNameDescription: "Ein Kürzel für den Namen der Instanz, der angezeigt werden kann, falls der volle Instanzname lang ist."
   fanoutTimelineDescription: "Ist diese Option aktiviert, kann eine erhebliche Verbesserung im Abrufen von Chroniken und eine Reduzierung der Datenbankbelastung erzielt werden, im Gegenzug zu einer Steigerung in der Speichernutzung von Redis. Bei geringem Serverspeicher oder Serverinstabilität kann diese Option deaktiviert werden."
+  fanoutTimelineDbFallback: "Auf die Datenbank zurückfallen"
+  fanoutTimelineDbFallbackDescription: "Ist diese Option aktiviert, wird die Chronik auf zusätzliche Abfragen in der Datenbank zurückgreifen, wenn sich die Chronik nicht im Cache befindet. Eine Deaktivierung führt zu geringerer Serverlast, aber schränkt den Zeitraum der abrufbaren Chronik ein. "
 _accountMigration:
   moveFrom: "Von einem anderen Konto zu diesem migrieren"
   moveFromSub: "Alias für ein anderes Konto erstellen"
@@ -1472,6 +1516,8 @@ _achievements:
     _smashTestNotificationButton:
       title: "Testüberfluss"
       description: "Betätige den Benachrichtigungstest mehrfach innerhalb einer extrem kurzen Zeitspanne"
+    _tutorialCompleted:
+      description: "Tutorial abgeschlossen"
 _role:
   new: "Rolle erstellen"
   edit: "Rolle bearbeiten"
@@ -1482,7 +1528,9 @@ _role:
   assignTarget: "Zuweisungsart"
   descriptionOfAssignTarget: "<b>Manuell</b> bedeutet, dass die Liste der Benutzer einer Rolle manuell verwaltet wird.\n<b>Konditional</b> bedeutet, dass die Liste der Benutzer einer Rolle durch eine Bedingung automatisch verwaltet wird."
   manual: "Manuell"
+  manualRoles: "Manuelle Rollen"
   conditional: "Konditional"
+  conditionalRoles: "Bedingte Rolle"
   condition: "Bedingung"
   isConditionalRole: "Dies ist eine konditionale Rolle."
   isPublic: "Öffentliche Rolle"
@@ -1524,13 +1572,14 @@ _role:
     webhookMax: "Maximale Anzahl an Webhooks"
     clipMax: "Maximale Anzahl an Clips"
     noteEachClipsMax: "Maximale Anzahl an Notizen innerhalb eines Clips"
-    userListMax: "Maximale Anzahl an Benutzern in einer Benutzerliste"
-    userEachUserListsMax: "Maximale Anzahl an Benutzerlisten"
+    userListMax: "Maximale Anzahl an Benutzerlisten"
+    userEachUserListsMax: "Maximale Anzahl an Benutzern in einer Benutzerliste"
     rateLimitFactor: "Versuchsanzahl"
     descriptionOfRateLimitFactor: "Je niedriger desto weniger restriktiv, je höher destro restriktiver."
     canHideAds: "Kann Werbung ausblenden"
     canSearchNotes: "Nutzung der Notizsuchfunktion"
     canUseTranslator: "Verwendung des Übersetzers"
+    avatarDecorationLimit: "Maximale Anzahl an Profilbilddekorationen, die angebracht werden können"
   _condition:
     isLocal: "Lokaler Benutzer"
     isRemote: "Benutzer fremder Instanz"
@@ -1559,6 +1608,7 @@ _emailUnavailable:
   disposable: "Wegwerf-Email-Adressen können nicht verwendet werden"
   mx: "Dieser Email-Server ist ungültig"
   smtp: "Dieser Email-Server antwortet nicht"
+  banned: "Du kannst dich mit dieser E-Mail-Adresse nicht registrieren"
 _ffVisibility:
   public: "Öffentlich"
   followers: "Nur für Follower sichtbar"
@@ -1887,6 +1937,7 @@ _widgets:
   _userList:
     chooseList: "Liste auswählen"
   clicker: "Klickzähler"
+  birthdayFollowings: "Nutzer, die heute Geburtstag haben"
 _cw:
   hide: "Inhalt verbergen"
   show: "Inhalt anzeigen"
@@ -1952,6 +2003,7 @@ _profile:
 _exportOrImport:
   allNotes: "Alle Notizen"
   favoritedNotes: "Als Favorit markierte Notizen"
+  clips: "Clip erstellen"
   followingList: "Gefolgte Benutzer"
   muteList: "Stummschaltungen"
   blockingList: "Blockierungen"
@@ -2234,3 +2286,10 @@ _externalResourceInstaller:
     _themeInstallFailed:
       title: "Das Farbschema konnte nicht installiert werden"
       description: "Während der Installation des Farbschemas ist ein Problem aufgetreten. Bitte versuche es erneut. Detaillierte Fehlerinformationen können über die Javascript-Konsole abgerufen werden."
+_reversi:
+  blackOrWhite: "Schwarz/Weiß"
+  rules: "Regeln"
+  black: "Schwarz"
+  white: "Weiß"
+  total: "Gesamt"
+
diff --git a/locales/el-GR.yml b/locales/el-GR.yml
index 30a52b726e..bb5639a741 100644
--- a/locales/el-GR.yml
+++ b/locales/el-GR.yml
@@ -356,6 +356,7 @@ _profile:
   username: "Όνομα μέλους"
 _exportOrImport:
   allNotes: "Όλα τα σημειώματα"
+  clips: "Κλιπ"
   followingList: "Ακολουθεί"
   muteList: "Μέλη σε σίγαση"
   blockingList: "Μπλοκαρισμένα μέλη"
@@ -395,3 +396,6 @@ _webhookSettings:
   name: "Όνομα"
 _moderationLogTypes:
   suspend: "Αποβολή"
+_reversi:
+  total: "Σύνολο"
+
diff --git a/locales/en-US.yml b/locales/en-US.yml
index 64f5d568eb..a1abba47e6 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -127,13 +127,14 @@ reaction: "Reactions"
 reactions: "Reactions"
 emojiPicker: "Emoji picker"
 pinnedEmojisForReactionSettingDescription: "Set the emojis which should be pinned and displayed immediately when reacting."
-pinnedEmojisSettingDescription: "Set the emojis to be pinned and displayed when entering emojis"
+pinnedEmojisSettingDescription: "Set the emojis to be pinned and displayed when viewing emoji picker"
 emojiPickerDisplay: "Emoji picker display"
 overwriteFromPinnedEmojisForReaction: "Override from reaction settings"
 overwriteFromPinnedEmojis: "Override from general settings"
 reactionSettingDescription2: "Drag to reorder, click to delete, press \"+\" to add."
 rememberNoteVisibility: "Remember note visibility settings"
 attachCancel: "Remove attachment"
+deleteFile: "File deleted"
 markAsSensitive: "Mark as sensitive"
 unmarkAsSensitive: "Unmark as sensitive"
 enterFileName: "Enter filename"
@@ -390,6 +391,11 @@ hcaptcha: "hCaptcha"
 enableHcaptcha: "Enable hCaptcha"
 hcaptchaSiteKey: "Site key"
 hcaptchaSecretKey: "Secret key"
+mcaptcha: "mCaptcha"
+enableMcaptcha: "Enable mCaptcha"
+mcaptchaSiteKey: "Site key"
+mcaptchaSecretKey: "Secret key"
+mcaptchaInstanceUrl: "mCaptcha instance URL"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "Enable reCAPTCHA"
 recaptchaSiteKey: "Site key"
@@ -518,6 +524,7 @@ mediaListWithOneImageAppearance: "Height of media lists with one image only"
 limitTo: "Limit to {x}"
 noFollowRequests: "You don't have any pending follow requests"
 openImageInNewTab: "Open images in new tab"
+warnForMissingAltText: "Warn you when you forget to put alt text"
 dashboard: "Dashboard"
 local: "Local"
 remote: "Remote"
@@ -550,6 +557,8 @@ objectStorageUseProxy: "Connect over Proxy"
 objectStorageUseProxyDesc: "Turn this off if you are not going to use a Proxy for API connections"
 objectStorageSetPublicRead: "Set \"public-read\" on upload"
 s3ForcePathStyleDesc: "If s3ForcePathStyle is enabled, the bucket name has to included in the path of the URL as opposed to the hostname of the URL. You may need to enable this setting when using services such as a self-hosted Minio instance."
+deeplFreeMode: "Use DeepLX-JS (No Auth Key)"
+deeplFreeModeDescription: "Need Help? Check our documentation to know how to setup DeepLX-JS."
 serverLogs: "Server logs"
 deleteAll: "Delete all"
 showFixedPostForm: "Display the posting form at the top of the timeline"
@@ -640,6 +649,7 @@ medium: "Medium"
 small: "Small"
 generateAccessToken: "Generate access token"
 permission: "Permissions"
+adminPermission: "Admin Permissions"
 enableAll: "Enable all"
 disableAll: "Disable all"
 tokenRequested: "Grant access to account"
@@ -656,7 +666,7 @@ smtpHost: "Host"
 smtpPort: "Port"
 smtpUser: "Username"
 smtpPass: "Password"
-emptyToDisableSmtpAuth: "Leave username and password empty to disable SMTP verification"
+emptyToDisableSmtpAuth: "Leave username and password empty to disable SMTP authentication"
 smtpSecure: "Use implicit SSL/TLS for SMTP connections"
 smtpSecureInfo: "Turn this off when using STARTTLS"
 testEmail: "Test email delivery"
@@ -683,6 +693,7 @@ useGlobalSettingDesc: "If turned on, your account's notification settings will b
 other: "Other"
 regenerateLoginToken: "Regenerate login token"
 regenerateLoginTokenDescription: "Regenerates the token used internally during login. Normally this action is not necessary. If regenerated, all devices will be logged out."
+theKeywordWhenSearchingForCustomEmoji: "This is the keyword when searching for custom emojis."
 setMultipleBySeparatingWithSpace: "Separate multiple entries with spaces."
 fileIdOrUrl: "File ID or URL"
 behavior: "Behavior"
@@ -959,6 +970,10 @@ numberOfPageCache: "Number of cached pages"
 numberOfPageCacheDescription: "Increasing this number will improve convenience for but cause more load as more memory usage on the user's device."
 numberOfReplies: "Number of replies in a thread"
 numberOfRepliesDescription: "Increasing this number will display more replies. Setting this too high can cause replies to be cramped and unreadable."
+boostSettings: "Boost Settings"
+showVisibilitySelectorOnBoost: "Show Visibility Selector"
+showVisibilitySelectorOnBoostDescription: "Shows the visiblity selector if enabled when clicking boost, if disabled it will use the default visiblity defined below and the selector will not show up."
+visibilityOnBoost: "Default boost visibility"
 logoutConfirm: "Really log out?"
 lastActiveDate: "Last used at"
 statusbar: "Status bar"
@@ -1010,6 +1025,8 @@ neverShow: "Don't show again"
 remindMeLater: "Maybe later"
 didYouLikeMisskey: "Have you taken a liking to Sharkey?"
 pleaseDonate: "{host} uses the free software, Sharkey. We would highly appreciate your donations so development of Sharkey can continue!"
+pleaseDonateInstance: "You can also support {host} directly by donating to your instance administration."
+correspondingSourceIsAvailable: "The corresponding source code is available at {anchor}"
 roles: "Roles"
 role: "Role"
 noRole: "Role not found"
@@ -1036,6 +1053,9 @@ thisPostMayBeAnnoying: "This note may annoy others."
 thisPostMayBeAnnoyingHome: "Post to home timeline"
 thisPostMayBeAnnoyingCancel: "Cancel"
 thisPostMayBeAnnoyingIgnore: "Post anyway"
+thisPostIsMissingAltTextCancel: "Cancel"
+thisPostIsMissingAltTextIgnore: "Post anyway"
+thisPostIsMissingAltText: "One of the files attached to this post is missing alt text. Please ensure all the attachments have alt text."
 collapseRenotes: "Collapse boosts you've already seen"
 collapseFiles: "Collapse files"
 autoloadConversation: "Load conversation on replies"
@@ -1063,6 +1083,9 @@ resetPasswordConfirm: "Really reset your password?"
 sensitiveWords: "Sensitive words"
 sensitiveWordsDescription: "The visibility of all notes containing any of the configured words will be set to \"Home\" automatically. You can list multiple by separating them via line breaks."
 sensitiveWordsDescription2: "Using spaces will create AND expressions and surrounding keywords with slashes will turn them into a regular expression."
+prohibitedWords: "Prohibited words"
+prohibitedWordsDescription: "Enables an error when attempting to post a note containing the set word(s). Multiple words can be set, separated by a new line."
+prohibitedWordsDescription2: "Using spaces will create AND expressions and surrounding keywords with slashes will turn them into a regular expression."
 hiddenTags: "Hidden hashtags"
 hiddenTagsDescription: "Select tags which will not shown on trend list.\nMultiple tags could be registered by lines."
 notesSearchNotAvailable: "Note search is unavailable."
@@ -1081,12 +1104,15 @@ limitWidthOfReaction: "Limits the maximum width of reactions and display them in
 noteIdOrUrl: "Note ID or URL"
 video: "Video"
 videos: "Videos"
+audio: "Audio"
+audioFiles: "Audio"
 dataSaver: "Data Saver"
 accountMigration: "Account Migration"
 accountMoved: "This user has moved to a new account:"
 accountMovedShort: "This account has been migrated."
 operationForbidden: "Operation forbidden"
 forceShowAds: "Always show ads"
+oneko: "Cat friend :3"
 addMemo: "Add memo"
 editMemo: "Edit memo"
 reactionsList: "Reactions"
@@ -1182,6 +1208,7 @@ showRenotes: "Show boosts"
 edited: "Edited"
 notificationRecieveConfig: "Notification Settings"
 mutualFollow: "Mutual follow"
+followingOrFollower: "Following or follower"
 fileAttachedOnly: "Only notes with files"
 showRepliesToOthersInTimeline: "Show replies to others in timeline"
 hideRepliesToOthersInTimeline: "Hide replies to others from timeline"
@@ -1190,16 +1217,25 @@ hideRepliesToOthersInTimelineAll: "Hide replies to others from everyone you foll
 confirmShowRepliesAll: "This operation is irreversible. Would you really like to show replies to others from everyone you follow in your timeline?"
 confirmHideRepliesAll: "This operation is irreversible. Would you really like to hide replies to others from everyone you follow in your timeline?"
 externalServices: "External Services"
+sourceCode: "Source code"
+sourceCodeIsNotYetProvided: "The source code is not yet available. Please contact your administrator to fix this problem."
+repositoryUrl: "Repository URL"
+repositoryUrlDescription: "If there is a repository where the source code is publicly available, enter its URL. If you are using Sharkey as-is (without any changes to the source code), enter https://activitypub.software/TransFem-org/Sharkey/."
+repositoryUrlOrTarballRequired: "If you have not published a repository, you must provide a tarball instead. See .config/example.yml for more information."
+feedback: "Feedback"
+feedbackUrl: "Feedback URL"
 impressum: "Impressum"
 impressumUrl: "Impressum URL"
 impressumDescription: "In some countries, like germany, the inclusion of operator contact information (an Impressum) is legally required for commercial websites."
 privacyPolicy: "Privacy Policy"
 privacyPolicyUrl: "Privacy Policy URL"
 tosAndPrivacyPolicy: "Terms of Service and Privacy Policy"
+donation: "Donate"
+donationUrl: "Donation URL"
 avatarDecorations: "Avatar decorations"
 attach: "Attach"
 detach: "Remove"
-detachAll: "Remove all"
+detachAll: "Remove All"
 angle: "Angle"
 flip: "Flip"
 showAvatarDecorations: "Show avatar decorations"
@@ -1219,6 +1255,39 @@ seasonalScreenEffect: "Seasonal screen effects"
 decorate: "Decorate"
 addMfmFunction: "Add MFM"
 enableQuickAddMfmFunction: "Show advanced MFM picker"
+bubbleGame: "Bubble Game"
+sfx: "Sound Effects"
+soundWillBePlayed: "Sound will be played"
+showReplay: "View Replay"
+replay: "Replay"
+replaying: "Showing replay"
+endReplay: "Exit Replay"
+copyReplayData: "Copy replay data"
+ranking: "Ranking"
+lastNDays: "Last {n} days"
+backToTitle: "Go back to title"
+hemisphere: "Where are you located"
+withSensitive: "Include notes with sensitive files"
+userSaysSomethingSensitive: "Post by {name} contains sensitive content"
+enableHorizontalSwipe: "Swipe to switch tabs"
+loading: "Loading"
+surrender: "Cancel"
+gameRetry: "Retry"
+_bubbleGame:
+  howToPlay: "How to play"
+  hold: "Hold"
+  _score:
+    score: "Score"
+    scoreYen: "Amount of money earned"
+    highScore: "High score"
+    maxChain: "Maximum number of chains"
+    yen: "{yen} Yen"
+    estimatedQty: "{qty} Pieces"
+    scoreSweets: "{onigiriQtyWithUnit} Onigiri"
+  _howToPlay:
+    section1: "Adjust the position and drop the object into the box."
+    section2: "When two objects of the same type touch each other, they will change into a different object and you score points."
+    section3: "The game is over when objects overflow from the box. Aim for a high score by fusing objects together while you avoid overflowing the box!"
 _announcement:
   forExistingUsers: "Existing users only"
   forExistingUsersDescription: "This announcement will only be shown to users existing at the point of publishment if enabled. If disabled, those newly signing up after it has been posted will also see it."
@@ -1228,7 +1297,7 @@ _announcement:
   tooManyActiveAnnouncementDescription: "Having too many active announcements may worsen the user experience. Please consider archiving announcements that have become obsolete."
   readConfirmTitle: "Mark as read?"
   readConfirmText: "This will mark the contents of \"{title}\" as read."
-  shouldNotBeUsedToPresentPermanentInfo: "As it may significantly impact the user experience for new users, it is recommended to use notifications in the flow information rather than stock information."
+  shouldNotBeUsedToPresentPermanentInfo: "It's best to use announcements to publish fresh and time-bound information, not for information that will be relevant in the long term."
   dialogAnnouncementUxWarn: "Having two or more dialog-style notifications simultaneously can significantly impact the user experience, so please use them carefully."
   silence: "No notification"
   silenceDescription: "Turning this on will skip the notification of this announcement and the user won't need to read it."
@@ -1589,6 +1658,13 @@ _achievements:
     _tutorialCompleted:
       title: "Sharkey Elementary Course Diploma"
       description: "Tutorial completed"
+    _bubbleGameExplodingHead:
+      title: "🤯"
+      description: "The biggest object in the bubble game"
+    _bubbleGameDoubleExplodingHead:
+      title: "Double🤯"
+      description: "Two of the biggest objects in the bubble game at the same time"
+      flavor: "You can fill a lunch box like this 🤯 🤯 a bit."
 _role:
   new: "New role"
   edit: "Edit role"
@@ -1631,6 +1707,7 @@ _role:
     ltlAvailable: "Can view the local timeline"
     canPublicNote: "Can send public notes"
     canImportNotes: "Can import notes"
+    mentionMax: "Maximum number of mentions in a note"
     canInvite: "Can create instance invite codes"
     inviteLimit: "Invite limit"
     inviteLimitCycle: "Invite limit cooldown"
@@ -1654,6 +1731,7 @@ _role:
     canUseTranslator: "Translator usage"
     avatarDecorationLimit: "Maximum number of avatar decorations that can be applied"
   _condition:
+    roleAssignedTo: "Assigned to manual roles"
     isLocal: "Local user"
     isRemote: "Remote user"
     createdLessThan: "Less than X has passed since account creation"
@@ -1756,8 +1834,12 @@ _aboutMisskey:
   contributors: "Main contributors"
   allContributors: "All contributors"
   source: "Source code"
+  original: "Misskey original"
+  original_sharkey: "Sharkey original"
+  thisIsModifiedVersion: "{name} uses a modified version of the original Sharkey."
   translation: "Translate Sharkey"
-  donate: "Donate to Sharkey"
+  donate: "Donate to Misskey"
+  donate_sharkey: "Donate to Sharkey"
   morePatrons: "We also appreciate the support of many other helpers not listed here. Thank you! 🥰"
   patrons: "Patrons"
   projectMembers: "Project members"
@@ -1977,54 +2059,54 @@ _permissions:
   "read:flash-likes": "View list of liked Plays"
   "write:flash-likes": "Edit list of liked Plays"
   "read:admin:abuse-user-reports": "View user reports"
-  "write:admin:delete-account": "Delete account"
+  "write:admin:delete-account": "Delete user account"
   "write:admin:delete-all-files-of-a-user": "Delete all files of a user"
-  "read:admin:index-stats": "View information about database indexes"
-  "read:admin:table-stats": "View information about database tables"
-  "read:admin:user-ips": "View user IP address"
+  "read:admin:index-stats": "View database index stats"
+  "read:admin:table-stats": "View database table stats"
+  "read:admin:user-ips": "View user IP addresses"
   "read:admin:meta": "View instance metadata"
-  "write:admin:reset-password": "Reset user passwords"
-  "write:admin:resolve-abuse-user-report": "Resolve user reports"
-  "write:admin:send-email": "Send Email"
+  "write:admin:reset-password": "Reset user password"
+  "write:admin:resolve-abuse-user-report": "Resolve user report"
+  "write:admin:send-email": "Send email"
   "read:admin:server-info": "View server info"
   "read:admin:show-moderation-log": "View moderation log"
-  "read:admin:show-user": "View user information"
-  "read:admin:show-users": "View users"
+  "read:admin:show-user": "View private user info"
+  "read:admin:show-users": "View private user info"
   "write:admin:suspend-user": "Suspend user"
-  "write:admin:unset-user-avatar": "Remove avatar from user"
-  "write:admin:unset-user-banner": "Remove banner from user"
+  "write:admin:unset-user-avatar": "Remove user avatar"
+  "write:admin:unset-user-banner": "Remove user banner"
   "write:admin:unsuspend-user": "Unsuspend user"
-  "write:admin:meta": "Edit instance metadata"
-  "write:admin:user-note": "Edit user note"
-  "write:admin:roles": "Edit roles"
+  "write:admin:meta": "Manage instance metadata"
+  "write:admin:user-note": "Manage moderation note"
+  "write:admin:roles": "Manage roles"
   "read:admin:roles": "View roles"
-  "write:admin:relays": "Edit relays"
+  "write:admin:relays": "Manage relays"
   "read:admin:relays": "View relays"
-  "write:admin:invite-codes": "Edit invite codes"
+  "write:admin:invite-codes": "Manage invite codes"
   "read:admin:invite-codes": "View invite codes"
-  "write:admin:announcements": "Edit announcements"
+  "write:admin:announcements": "Manage announcements"
   "read:admin:announcements": "View announcements"
-  "write:admin:avatar-decorations": "Edit avatar decorations"
+  "write:admin:avatar-decorations": "Manage avatar decorations"
   "read:admin:avatar-decorations": "View avatar decorations"
-  "write:admin:federation": "Edit remote instance information"
-  "write:admin:account": "Edit users"
-  "read:admin:account": "View information about user"
-  "write:admin:emoji": "Edit emojis"
-  "read:admin:emoji": "View emojis"
-  "write:admin:queue": "Edit queue"
-  "read:admin:queue": "View queue"
-  "write:admin:promo": "Edit promo"
-  "write:admin:drive": "Edit user drive"
-  "read:admin:drive": "View user drive"
-  "read:admin:stream": "Using the Websocket API for Admin"
-  "write:admin:ad": "Edit ads"
+  "write:admin:federation": "Manage federation data"
+  "write:admin:account": "Manage user account"
+  "read:admin:account": "View user account"
+  "write:admin:emoji": "Manage emoji"
+  "read:admin:emoji": "View emoji"
+  "write:admin:queue": "Manage job queue"
+  "read:admin:queue": "View job queue info"
+  "write:admin:promo": "Manage promotion notes"
+  "write:admin:drive": "Manage user drive"
+  "read:admin:drive": "View user drive info"
+  "read:admin:stream": "Use WebSocket API for Admin"
+  "write:admin:ad": "Manage ads"
   "read:admin:ad": "View ads"
-  "write:invite-codes": "Create Invitation Code"
-  "read:invite-codes": "View Invitation Code"
-  "write:clip-favorite": "Edit clips and likes"
-  "read:clip-favorite": "View clips and likes"
-  "read:federation": "View information about remote instance"
-  "write:report-abuse": "Report abuse"
+  "write:invite-codes": "Create invite codes"
+  "read:invite-codes": "Get invite codes"
+  "write:clip-favorite": "Manage favorited clips"
+  "read:clip-favorite": "View favorited clips"
+  "read:federation": "Get federation data"
+  "write:report-abuse": "Report violation"
 _auth:
   shareAccessTitle: "Granting application permissions"
   shareAccess: "Would you like to authorize \"{name}\" to access this account?"
@@ -2143,12 +2225,17 @@ _profile:
   metadataContent: "Content"
   changeAvatar: "Change avatar"
   changeBanner: "Change banner"
+  updateBanner: "Update banner"
+  removeBanner: "Remove banner"
   changeBackground: "Change background"
+  updateBackground: "Update background"
+  removeBackground: "Remove background"
   verifiedLinkDescription: "By entering an URL that contains a link to your profile here, an ownership verification icon can be displayed next to the field."
   avatarDecorationMax: "You can add up to {max} decorations."
 _exportOrImport:
   allNotes: "All notes"
   favoritedNotes: "Favorite notes"
+  clips: "Clip"
   followingList: "Followed users"
   muteList: "Muted users"
   blockingList: "Blocked users"
@@ -2277,6 +2364,8 @@ _notification:
   reactedBySomeUsers: "{n} users reacted"
   renotedBySomeUsers: "Boosted by {n} users"
   followedBySomeUsers: "Followed by {n} users"
+  flushNotification: "Clear notifications"
+  edited: "Note got edited"
   _types:
     all: "All"
     note: "New notes"
@@ -2375,6 +2464,7 @@ _moderationLogTypes:
   resetPassword: "Password reset"
   suspendRemoteInstance: "Remote instance suspended"
   unsuspendRemoteInstance: "Remote instance unsuspended"
+  updateRemoteInstanceNote: "Moderation note updated for remote instance."
   markSensitiveDriveFile: "File marked as sensitive"
   unmarkSensitiveDriveFile: "File unmarked as sensitive"
   resolveAbuseReport: "Report resolved"
@@ -2536,3 +2626,54 @@ _dataSaver:
   _code:
     title: "Code highlighting"
     description: "If code highlighting notations are used in MFM, etc., they will not load until tapped. Syntax highlighting requires downloading the highlight definition files for each programming language. Therefore, disabling the automatic loading of these files is expected to reduce the amount of communication data."
+_hemisphere:
+  N: "Northern Hemisphere"
+  S: "Southern Hemisphere"
+  caption: "Used in some client settings to determine season."
+_reversi:
+  reversi: "Reversi"
+  gameSettings: "Game settings"
+  chooseBoard: "Choose a board"
+  blackOrWhite: "Black/White"
+  blackIs: "{name} is playing Black"
+  rules: "Rules"
+  thisGameIsStartedSoon: "The game will begin shortly"
+  waitingForOther: "Waiting for opponent's turn"
+  waitingForMe: "Waiting for your turn"
+  waitingBoth: "Get ready"
+  ready: "Ready"
+  cancelReady: "Not ready"
+  opponentTurn: "Opponent's turn"
+  myTurn: "Your turn"
+  turnOf: "It's {name}'s turn"
+  pastTurnOf: "{name}'s turn"
+  surrender: "Surrender"
+  surrendered: "Surrendered"
+  timeout: "Out of time"
+  drawn: "Draw"
+  won: "{name} wins"
+  black: "Black"
+  white: "White"
+  total: "Total"
+  turnCount: "Turn {count}"
+  myGames: "My rounds"
+  allGames: "All rounds"
+  ended: "Ended"
+  playing: "Currently playing"
+  isLlotheo: "The one with fewer stones wins (Llotheo)"
+  loopedMap: "Looping map"
+  canPutEverywhere: "Tiles are placeable everywhere"
+  timeLimitForEachTurn: "Time limit for turn"
+  freeMatch: "Free Match"
+  lookingForPlayer: "Finding opponent..."
+  gameCanceled: "The game has been cancelled."
+  shareToTlTheGameWhenStart: "Share Game to timeline when started"
+  iStartedAGame: "The game has begun! #MisskeyReversi"
+  opponentHasSettingsChanged: "The opponent has changed their settings."
+  allowIrregularRules: "Irregular rules (completely free)"
+  disallowIrregularRules: "No irregular rules"
+  showBoardLabels: "Display row and column numbering on the board"
+  useAvatarAsStone: "Turn stones into user avatars"
+_offlineScreen:
+  title: "Offline - cannot connect to the server"
+  header: "Unable to connect to the server"
diff --git a/locales/es-ES.yml b/locales/es-ES.yml
index c269cc4d75..2288038e64 100644
--- a/locales/es-ES.yml
+++ b/locales/es-ES.yml
@@ -11,7 +11,7 @@ password: "Contraseña"
 forgotPassword: "Olvidé mi contraseña"
 fetchingAsApObject: "Buscando en el fediverso"
 ok: "OK"
-gotIt: "Entendido"
+gotIt: "¡Lo tengo!"
 cancel: "Cancelar"
 noThankYou: "No gracias"
 enterUsername: "Introduce el nombre de usuario"
@@ -130,6 +130,7 @@ overwriteFromPinnedEmojis: "Sobreescribir los emojis fijados"
 reactionSettingDescription2: "Arrastre para reordenar, click para borrar, apriete la tecla + para añadir."
 rememberNoteVisibility: "Recordar visibilidad"
 attachCancel: "Quitar adjunto"
+deleteFile: "Archivo eliminado"
 markAsSensitive: "Marcar como sensible"
 unmarkAsSensitive: "Desmarcar como sensible"
 enterFileName: "Ingrese el nombre del archivo"
@@ -379,6 +380,11 @@ hcaptcha: "hCaptcha"
 enableHcaptcha: "Habilitar hCaptcha"
 hcaptchaSiteKey: "Clave del sitio"
 hcaptchaSecretKey: "Clave secreta"
+mcaptcha: "mCaptcha"
+enableMcaptcha: "Activar mCaptcha"
+mcaptchaSiteKey: "Clave del sitio"
+mcaptchaSecretKey: "Clave secreta"
+mcaptchaInstanceUrl: "URL del servidor mCaptcha"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "activar reCAPTCHA"
 recaptchaSiteKey: "Clave del sitio"
@@ -626,6 +632,7 @@ medium: "Mediano"
 small: "Pequeño"
 generateAccessToken: "Generar token de acceso"
 permission: "Permisos"
+adminPermission: "Permiso de administrador"
 enableAll: "Activar todo"
 disableAll: "Desactivar todo"
 tokenRequested: "Permiso de acceso a la cuenta"
@@ -669,6 +676,7 @@ useGlobalSettingDesc: "Al activarse, se usará la configuración de notificacion
 other: "Otro"
 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."
+theKeywordWhenSearchingForCustomEmoji: "Palabra clave para buscar el emoji personalizado."
 setMultipleBySeparatingWithSpace: "Puedes añadir mas de uno, separado por espacios."
 fileIdOrUrl: "Id del archivo o URL"
 behavior: "Comportamiento"
@@ -1033,6 +1041,8 @@ resetPasswordConfirm: "¿Realmente quieres cambiar la contraseña?"
 sensitiveWords: "Palabras sensibles"
 sensitiveWordsDescription: "La visibilidad de todas las notas que contienen cualquiera de las palabras configuradas serán puestas en \"Inicio\" automáticamente. Puedes enumerás varias separándolas con saltos de línea"
 sensitiveWordsDescription2: "Si se usan espacios se crearán expresiones AND y las palabras subsecuentes con barras inclinadas se convertirán en expresiones regulares."
+prohibitedWords: "Palabras explícitas"
+prohibitedWordsDescription2: "Si se usan espacios se crearán expresiones AND y las palabras subsecuentes con barras inclinadas se convertirán en expresiones regulares."
 hiddenTags: "Hashtags ocultos"
 hiddenTagsDescription: "Selecciona las etiquetas que no se mostrarán en tendencias. Una etiqueta por línea."
 notesSearchNotAvailable: "No se puede buscar una nota"
@@ -1051,6 +1061,8 @@ limitWidthOfReaction: "Limitar ancho de las reacciones"
 noteIdOrUrl: "ID o URL de la nota"
 video: "Video"
 videos: "Video"
+audio: "Sonido"
+audioFiles: "Sonido"
 dataSaver: "Ahorro de datos"
 accountMigration: "Migración de cuenta"
 accountMoved: "Este usuario se movió a una nueva cuenta:"
@@ -1154,6 +1166,7 @@ hideRepliesToOthersInTimelineAll: "Ocultar tus respuestas a otros usuarios que s
 confirmShowRepliesAll: "Esta operación es irreversible. ¿Confirmas que quieres mostrar tus respuestas a otros usuarios que sigues en tu línea de tiempo?"
 confirmHideRepliesAll: "Esta operación es irreversible. ¿Confirmas que quieres ocultar tus respuestas a otros usuarios que sigues en tu línea de tiempo?"
 externalServices: "Servicios Externos"
+sourceCode: "Código fuente"
 impressum: "Impressum"
 impressumUrl: "Impressum URL"
 impressumDescription: "En algunos países, como Alemania, la inclusión del operador de datos (el Impressum) es requerido legalmente para sitios web comerciales."
@@ -1181,6 +1194,28 @@ remainingN: "Faltan: {n}"
 overwriteContentConfirm: "¿Quieres sustituir todo el contenido actual?"
 seasonalScreenEffect: "Efectos de pantalla asociados a estaciones"
 decorate: "Decorar"
+addMfmFunction: "Añadir función MFM"
+enableQuickAddMfmFunction: "Activar acceso rápido para añadir funciones MFM"
+bubbleGame: "Bubble Game"
+sfx: "Efectos de sonido"
+soundWillBePlayed: "Se reproducirán efectos sonoros"
+showReplay: "Ver reproducción"
+replay: "Reproducir"
+replaying: "Reproduciendo"
+ranking: "Clasificación"
+lastNDays: "Últimos {n} días"
+backToTitle: "Regresar al inicio"
+hemisphere: "Región"
+withSensitive: "Mostrar notas que contengan material sensible"
+userSaysSomethingSensitive: "La publicación de {name} contiene material sensible"
+enableHorizontalSwipe: "Deslice para cambiar de pestaña"
+surrender: "detener"
+_bubbleGame:
+  howToPlay: "Cómo jugar"
+  _howToPlay:
+    section1: "Ajuste la posición y deje caer el objeto en la caja"
+    section2: "Cuando dos objetos del mismo tipo se tocan, cambian a otro tipo y consigues puntos"
+    section3: "El juego termina cuando la caja se desborda de objetos. ¡Intenta conseguir una puntuación alta al juntar objetos mientras evitas desbordar la caja!"
 _announcement:
   forExistingUsers: "Solo para usuarios registrados"
   forExistingUsersDescription: "Este anuncio solo se mostrará a aquellos usuarios registrados en el momento de su publicación. Si se deshabilita esta opción, aquellos usuarios que se registren tras su publicación también lo verán."
@@ -1551,6 +1586,10 @@ _achievements:
     _tutorialCompleted:
       title: "Diploma del Curso Básico de Misskey"
       description: "Tutorial completado"
+    _bubbleGameExplodingHead:
+      title: "🤯"
+    _bubbleGameDoubleExplodingHead:
+      title: "Doble 🤯"
 _role:
   new: "Crear rol"
   edit: "Editar rol"
@@ -1933,6 +1972,54 @@ _permissions:
   "write:flash": "Editar Plays"
   "read:flash-likes": "Ver los Play que me gustan"
   "write:flash-likes": "Editar lista de Play que me gustan"
+  "read:admin:abuse-user-reports": "Ver reportes de usuarios"
+  "write:admin:delete-account": "Eliminar cuentas de usuario"
+  "write:admin:delete-all-files-of-a-user": "Eliminar todos los archivos de un usuario"
+  "read:admin:index-stats": "Ver datos indexados"
+  "read:admin:user-ips": "Ver dirección IP de usuario"
+  "read:admin:meta": "Ver metadatos de la instancia"
+  "write:admin:reset-password": "Restablecer contraseñas de usuario"
+  "write:admin:resolve-abuse-user-report": "Resolución de reportes de usuario"
+  "write:admin:send-email": "Enviar email"
+  "read:admin:server-info": "Ver información del servidor"
+  "read:admin:show-moderation-log": "Ver log de moderación"
+  "read:admin:show-user": "Ver información privada de usuario"
+  "read:admin:show-users": "Ver información privada de usuario"
+  "write:admin:suspend-user": "Suspender cuentas de usuario"
+  "write:admin:unset-user-avatar": "Quitar avatares de usuario"
+  "write:admin:unset-user-banner": "Quitar banner de usuarios"
+  "write:admin:unsuspend-user": "Quitar suspensión de cuentas de usuario"
+  "write:admin:meta": "Edición de metadatos de la instancia"
+  "write:admin:user-note": "Moderación de notas"
+  "write:admin:roles": "Edición de roles de usuario"
+  "read:admin:roles": "Ver roles de usuario"
+  "write:admin:relays": "Edición de relays"
+  "read:admin:relays": "Ver relays"
+  "write:admin:invite-codes": "Edición de códigos de invitación"
+  "read:admin:invite-codes": "Ver códigos de invitación"
+  "write:admin:announcements": "Edición de anuncios"
+  "read:admin:announcements": "Ver anuncios"
+  "write:admin:avatar-decorations": "Edición de decoración de avatares"
+  "read:admin:avatar-decorations": "Ver decoraciones de avatar"
+  "write:admin:federation": "Edición de federación de instancias"
+  "write:admin:account": "Edición de cuentas de usuario"
+  "read:admin:account": "Ver cuentas de usuario"
+  "write:admin:emoji": "Edición de emojis"
+  "read:admin:emoji": "Ver emojis"
+  "write:admin:queue": "Edición de cola de tareas"
+  "read:admin:queue": "Ver cola de tareas"
+  "write:admin:promo": "Edición de promociones"
+  "write:admin:drive": "Edición de Drive de usuarios"
+  "read:admin:drive": "Ver Drive de usuarios"
+  "read:admin:stream": "Usar la API de Websocket para administradores"
+  "write:admin:ad": "Edición de anuncios"
+  "read:admin:ad": "Ver anuncios"
+  "write:invite-codes": "Crear códigos de invitación"
+  "read:invite-codes": "Ver códigos de invitación"
+  "write:clip-favorite": "Marcar me gusta en clips"
+  "read:clip-favorite": "Ver los clips que me gustan"
+  "read:federation": "Ver instancias federadas"
+  "write:report-abuse": "Crear reportes de usuario"
 _auth:
   shareAccessTitle: "Permisos de la aplicación"
   shareAccess: "¿Desea permitir el acceso a la cuenta \"{name}\"?"
@@ -2055,6 +2142,7 @@ _profile:
 _exportOrImport:
   allNotes: "Todas las notas"
   favoritedNotes: "Notas favoritas"
+  clips: "Clip"
   followingList: "Siguiendo"
   muteList: "Silenciados"
   blockingList: "Bloqueados"
@@ -2354,3 +2442,11 @@ _dataSaver:
   _code:
     title: "Resaltar código"
     description: "Si se usa resaltado de código en MFM, etc., no se cargará hasta pulsar en ello. El resaltado de sintaxis requiere la descarga de archivos de definición para cada lenguaje de programación. Debido a esto, al deshabilitar la carga automática de estos archivos reducirás el consumo de datos."
+_hemisphere:
+  N: "Hemisferio norte"
+  S: "Hemisferio sur"
+_reversi:
+  reversi: "Reversi"
+  won: "{name} ha ganado"
+  total: "Total"
+
diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml
index ac9e94a01a..24243c1f18 100644
--- a/locales/fr-FR.yml
+++ b/locales/fr-FR.yml
@@ -2,7 +2,7 @@
 _lang_: "Français"
 headlineMisskey: "Réseau relié par des notes"
 introMisskey: "Bienvenue ! Misskey est un service de microblogage décentralisé, libre et ouvert.\nÉcrivez des « notes » et partagez ce qui se passe à l’instant présent, autour de vous avec les autres 📡\nLa fonction « réactions », vous permet également d’ajouter une réaction rapide aux notes des autres utilisateur·rice·s 👍\nExplorons un nouveau monde 🚀"
-poweredByMisskeyDescription: "{nom} est l'un des services propulsés par la plateforme ouverte <b>Misskey</b> (appelée \"instance Misskey\")."
+poweredByMisskeyDescription: "{name} est l'un des services propulsés par la plateforme ouverte <b>Misskey</b> (appelée \"instance Misskey\")."
 monthAndDay: "{day}/{month}"
 search: "Rechercher"
 notifications: "Notifications"
@@ -130,6 +130,7 @@ overwriteFromPinnedEmojis: "Remplacer par les émojis épinglés globalement"
 reactionSettingDescription2: "Déplacer pour réorganiser, cliquer pour effacer, utiliser « + » pour ajouter."
 rememberNoteVisibility: "Se souvenir de la visibilité des notes"
 attachCancel: "Supprimer le fichier attaché"
+deleteFile: "Fichier supprimé"
 markAsSensitive: "Marquer comme sensible"
 unmarkAsSensitive: "Supprimer le marquage comme sensible"
 enterFileName: "Entrer le nom du fichier"
@@ -168,7 +169,7 @@ cacheRemoteSensitiveFilesDescription: "Si vous désactivez ce paramètre, les fi
 flagAsBot: "Ce compte est un robot"
 flagAsBotDescription: "Si ce compte est géré de manière automatisée, choisissez cette option. Si elle est activée, elle agira comme un marqueur pour les autres développeurs afin d'éviter des chaînes d'interaction sans fin avec d'autres robots et d'ajuster les systèmes internes de Misskey pour traiter ce compte comme un robot."
 flagAsCat: "Ce compte est un chat"
-flagAsCatDescription: "Activer l'option \" Je suis un chat \" pour ce compte."
+flagAsCatDescription: "Miaou miaou miaou ?"
 flagShowTimelineReplies: "Afficher les réponses dans le fil"
 flagShowTimelineRepliesDescription: "Affiche les réponses des utilisateurs aux notes des autres utilisateurs dans la timeline si cette option est activée."
 autoAcceptFollowed: "Accepter automatiquement les demandes d’abonnement venant d’utilisateur·rice·s que vous suivez"
@@ -379,6 +380,11 @@ hcaptcha: "hCaptcha"
 enableHcaptcha: "Activer hCaptcha"
 hcaptchaSiteKey: "Clé du site"
 hcaptchaSecretKey: "Clé secrète"
+mcaptcha: "mCaptcha"
+enableMcaptcha: "Activer mCaptcha"
+mcaptchaSiteKey: "Clé du site"
+mcaptchaSecretKey: "Clé secrète"
+mcaptchaInstanceUrl: "URL de l'instance de mCaptcha"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "Activer reCAPTCHA"
 recaptchaSiteKey: "Clé du site"
@@ -396,7 +402,7 @@ antennaKeywords: "Mots clés à recevoir"
 antennaExcludeKeywords: "Mots clés à exclure"
 antennaKeywordsDescription: "Séparer avec des espaces pour la condition AND. Séparer avec un saut de ligne pour une condition OR."
 notifyAntenna: "Me notifier pour les nouvelles notes"
-withFileAntenna: "Notes ayant des attachements uniquement"
+withFileAntenna: "Notes ayant des fichiers joints uniquement"
 enableServiceworker: "Activer ServiceWorker"
 antennaUsersDescription: "Saisissez un seul nom d’utilisateur·rice par ligne"
 caseSensitive: "Sensible à la casse"
@@ -520,7 +526,7 @@ hideThisNote: "Masquer cette note"
 showFeaturedNotesInTimeline: "Afficher les notes des Tendances dans le fil d'actualité"
 objectStorage: "Stockage d'objets"
 useObjectStorage: "Utiliser le stockage d'objets"
-objectStorageBaseUrl: "Base URL"
+objectStorageBaseUrl: "URL de base"
 objectStorageBaseUrlDesc: "Préfixe d’URL utilisé pour construire l’URL vers le référencement d’objet (média). Spécifiez son URL si vous utilisez un CDN ou un proxy, sinon spécifiez l’adresse accessible au public selon le guide de service que vous allez utiliser. P.ex. 'https://<bucket>.s3.amazonaws.com' pour AWS S3 et 'https://storage.googleapis.com/<bucket>' pour GCS."
 objectStorageBucket: "Bucket"
 objectStorageBucketDesc: "Veuillez spécifier le nom du compartiment utilisé sur le service configuré."
@@ -625,6 +631,7 @@ medium: "Moyen"
 small: "Petit"
 generateAccessToken: "Générer un jeton d'accès"
 permission: "Autorisations "
+adminPermission: "Droits de l'administrateur"
 enableAll: "Tout activer"
 disableAll: "Tout désactiver"
 tokenRequested: "Autoriser l'accès au compte"
@@ -696,7 +703,7 @@ system: "Système"
 switchUi: "Modifier l'interface utilisateur"
 desktop: "Bureau"
 clip: "Clip"
-createNew: "Créer nouveau"
+createNew: "Créer"
 optional: "Facultatif"
 createNewClip: "Créer un nouveau clip"
 unclip: "Supprimer le clip"
@@ -1028,12 +1035,18 @@ nonSensitiveOnlyForLocalLikeOnlyForRemote: "Non sensibles seulement (mentions j'
 rolesAssignedToMe: "Rôles attribués à moi"
 resetPasswordConfirm: "Souhaitez-vous réinitialiser votre mot de passe ?"
 sensitiveWords: "Mots sensibles"
+sensitiveWordsDescription2: "Séparer par une espace pour créer une expression AND ; entourer de barres obliques pour créer une expression régulière."
+prohibitedWords: "Mots interdits"
+prohibitedWordsDescription2: "Séparer par une espace pour créer une expression AND ; entourer de barres obliques pour créer une expression régulière."
 hiddenTags: "Hashtags cachés"
 hiddenTagsDescription: "Les hashtags définis ne s'afficheront pas dans les tendances. Vous pouvez définir plusieurs hashtags en faisant un saut de ligne."
 notesSearchNotAvailable: "La recherche de notes n'est pas disponible."
 license: "Licence"
+unfavoriteConfirm: "Vraiment supprimer des favoris ?"
 myClips: "Mes clips"
 drivecleaner: "Nettoyeur du Disque"
+retryAllQueuesNow: "Réessayer tous les fils d'attente immédiatement"
+retryAllQueuesConfirmTitle: "Vraiment réessayer ?"
 retryAllQueuesConfirmText: "Cela peut augmenter temporairement la charge du serveur."
 enableChartsForRemoteUser: "Générer les graphiques pour les utilisateurs distants"
 enableChartsForFederatedInstances: "Générer les graphiques pour les instances distantes"
@@ -1043,6 +1056,8 @@ limitWidthOfReaction: "Limiter la largeur maximale des réactions et les affiche
 noteIdOrUrl: "Identifiant de la note ou URL"
 video: "Vidéo"
 videos: "Vidéos"
+audio: "Audio"
+audioFiles: "Fichiers audio"
 dataSaver: "Économiseur de données"
 accountMigration: "Migration de compte"
 accountMoved: "Cet·te utilisateur·rice a migré son compte vers :"
@@ -1081,12 +1096,27 @@ specifyUser: "Spécifier l'utilisateur·rice"
 failedToPreviewUrl: "Aperçu d'URL échoué"
 update: "Mettre à jour"
 rolesThatCanBeUsedThisEmojiAsReaction: "Rôles qui peuvent utiliser cet émoji comme réaction"
+rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "Si aucun rôle n'est spécifié, tout le monde peut utiliser cet émoji comme réaction."
+rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "Il faut un rôle public."
+cancelReactionConfirm: "Supprimez la réaction ?"
+changeReactionConfirm: "Changer la réaction ?"
 later: "Plus tard"
 goToMisskey: "Retour vers Misskey"
 additionalEmojiDictionary: "Dictionnaires d'émojis additionnels"
 installed: "Installé"
 branding: "Image de marque"
+enableServerMachineStats: "Publier les statistiques du matériel du serveur"
+enableIdenticonGeneration: "Générer les identicons des utilisateurs"
+turnOffToImprovePerformance: "Désactiver peut améliorer la performance."
+createInviteCode: "Créer un code d'invitation"
+createWithOptions: "Options"
+createCount: "Quantité à créer"
+inviteCodeCreated: "Code d'invitation créé"
+inviteLimitExceeded: "Vous avez atteint la limite de codes d'invitation que vous pouvez générer."
 expirationDate: "Date d’expiration"
+noExpirationDate: "Ne pas expirer"
+inviteCodeUsedAt: "Code d'invitation utilisé à"
+registeredUserUsingInviteCode: "Code d'invitation utilisé par"
 waitingForMailAuth: "En attente de la vérification de l'adresse courriel"
 inviteCodeCreator: "Créateur·rice de ce code d'invitation"
 usedAt: "Utilisé le"
@@ -1095,17 +1125,20 @@ used: "Utilisé"
 expired: "Expiré"
 doYouAgree: "Êtes-vous d’accord ?"
 beSureToReadThisAsItIsImportant: "Assurez-vous de le lire ; c'est important."
+iHaveReadXCarefullyAndAgree: "J'ai lu le contenu de « {x} » et donne mon accord."
 dialog: "Dialogue"
 icon: "Avatar"
 forYou: "Pour vous"
 currentAnnouncements: "Annonces actuelles"
 pastAnnouncements: "Annonces passées"
+youHaveUnreadAnnouncements: "Il y a des annonces non lues."
 replies: "Réponses"
 renotes: "Renotes"
 loadReplies: "Inclure les réponses"
 loadConversation: "Afficher la conversation"
 pinnedList: "Liste épinglée"
 notifyNotes: "Notifier à propos des nouvelles notes"
+unnotifyNotes: "Ne pas notifier pour la publication des notes"
 authentication: "Authentification"
 authenticationRequiredToContinue: "Veuillez vous authentifier pour continuer"
 dateAndTime: "Date et heure"
@@ -1113,6 +1146,7 @@ showRenotes: "Afficher les renotes"
 edited: "Modifié"
 notificationRecieveConfig: "Paramètres des notifications"
 mutualFollow: "Abonnement mutuel"
+fileAttachedOnly: "Avec fichiers joints seulement"
 showRepliesToOthersInTimeline: "Afficher les réponses aux autres dans le fil"
 hideRepliesToOthersInTimeline: "Masquer les réponses aux autres dans le fil"
 showRepliesToOthersInTimelineAll: "Afficher les réponses de toutes les personnes que vous suivez dans le fil"
@@ -1120,6 +1154,12 @@ hideRepliesToOthersInTimelineAll: "Masquer les réponses de toutes les personnes
 confirmShowRepliesAll: "Cette opération est irréversible. Voulez-vous vraiment afficher les réponses de toutes les personnes que vous suivez dans le fil ?"
 confirmHideRepliesAll: "Cette opération est irréversible. Voulez-vous vraiment masquer les réponses de toutes les personnes que vous suivez dans le fil ?"
 externalServices: "Services externes"
+sourceCode: "Code source"
+sourceCodeIsNotYetProvided: "Le code source n'est pas encore disponible. Veuillez signaler ce problème aux administrateurs."
+repositoryUrl: "URL du dépôt"
+repositoryUrlDescription: "Entrez l'URL du dépôt où se trouve le code source ici. Si vous utilisez Misskey tel quel (sans changer le code source), entrez https://github.com/misskey-dev/misskey"
+feedback: "Commentaires"
+feedbackUrl: "URL pour les commentaires"
 impressum: "Impressum"
 impressumUrl: "URL de l'impressum"
 impressumDescription: "Dans certains pays comme l'Allemagne, il est obligatoire d'afficher les informations sur l'opérateur d'un site (un impressum)."
@@ -1147,7 +1187,34 @@ remainingN: "Restants : {n}"
 overwriteContentConfirm: "Voulez-vous remplacer le contenu actuel ?"
 seasonalScreenEffect: "Effet d'écran saisonnier"
 decorate: "Décorer"
+addMfmFunction: "Insérer MFM"
+enableQuickAddMfmFunction: "Afficher le sélecteur de MFM avancé"
+bubbleGame: "Jeu de bulles"
+sfx: "Effets sonores"
+soundWillBePlayed: "Le son sera joué"
+showReplay: "Voir le replay"
+replay: "Rediffusion"
+replaying: "En cours de rediffusion"
+endReplay: "Arrêter la rediffusion"
+copyReplayData: "Copier les données de la rediffusion"
+ranking: "Classement"
+lastNDays: "Derniers {n} jours"
+backToTitle: "Retourner au titre"
+hemisphere: "Votre région"
+enableHorizontalSwipe: "Glisser pour changer d'onglet"
+loading: "Chargement en cours"
+surrender: "Annuler"
+gameRetry: "Réessayer"
+_bubbleGame:
+  howToPlay: "Comment jouer"
+  hold: "Réserver"
+  _score:
+    score: "Score"
+    scoreYen: "Montant gagné"
+    highScore: "Meilleur score"
+    yen: "{yen} yens"
 _announcement:
+  forExistingUsers: "Pour les utilisateurs existants seulement"
   readConfirmTitle: "Marquer comme lu ?"
   shouldNotBeUsedToPresentPermanentInfo: "Puisque cela pourrait nuire considérablement à l'expérience utilisateur pour les nouveaux utilisateurs, il est recommandé d'utiliser les annonces pour afficher des informations temporaires plutôt que des informations persistantes."
   dialogAnnouncementUxWarn: "Avoir deux ou plus annonces de style dialogue en même temps pourrait nuire considérablement à l'expérience utilisateur. Veuillez les utiliser avec caution."
@@ -1157,7 +1224,7 @@ _initialAccountSetting:
   profileSetting: "Paramètres du profil"
   privacySetting: "Paramètres de confidentialité"
   initialAccountSettingCompleted: "Configuration du profil terminée avec succès !"
-  youCanContinueTutorial: "Vous pouvez procéder au tutoriel sur l'utilisation de {nom}(Misskey) ou vous arrêter ici et commencer à l'utiliser immédiatement."
+  youCanContinueTutorial: "Vous pouvez procéder au tutoriel sur l'utilisation de {name}(Misskey) ou vous arrêter ici et commencer à l'utiliser immédiatement."
   startTutorial: "Démarrer le tutoriel"
   skipAreYouSure: "Désirez-vous ignorer la configuration du profil ?"
 _initialTutorial:
@@ -1242,6 +1309,7 @@ _accountMigration:
   startMigration: "Migrer"
   movedTo: "Compte vers lequel vous migrez :"
 _achievements:
+  earnedAt: "Date d'obtention"
   _types:
     _notes1:
       title: "Je viens tout juste de configurer mon shonk"
@@ -1282,10 +1350,13 @@ _achievements:
       title: "Régulier III"
       description: "Se connecter pour un total de 400 jours"
     _login500:
+      title: "Expert I"
       description: "Se connecter pour un total de 500 jours"
     _login600:
+      title: "Expert II"
       description: "Se connecter pour un total de 600 jours"
     _login700:
+      title: "Expert III"
       description: "Se connecter pour un total de 700 jours"
     _login800:
       description: "Se connecter pour un total de 800 jours"
@@ -1380,9 +1451,12 @@ _role:
   description: "Description du rôle"
   permission: "Rôle et autorisations"
   assignTarget: "Attribuer"
+  manual: "Manuel"
   manualRoles: "Rôles manuels"
+  conditional: "Conditionnel"
   conditionalRoles: "Rôles conditionnels"
   condition: "Condition"
+  isConditionalRole: "Ceci est un rôle conditionnel."
   isPublic: "Rôle public"
   options: "Options"
   policies: "Stratégies"
@@ -1799,6 +1873,7 @@ _profile:
   avatarDecorationMax: "Vous pouvez mettre au plus {max} décorations d'avatar."
 _exportOrImport:
   allNotes: "Toutes les notes"
+  clips: "Clip"
   followingList: "Abonnements"
   muteList: "Comptes masqués"
   blockingList: "Comptes bloqués"
@@ -2063,3 +2138,6 @@ _dataSaver:
   _code:
     title: "Mise en évidence du code"
     description: "Si la notation de mise en évidence du code est utilisée, par exemple dans la MFM, elle ne sera pas chargée tant qu'elle n'aura pas été tapée. La mise en évidence du code nécessite le chargement du fichier de définition de chaque langue à mettre en évidence, mais comme ces fichiers ne sont plus chargés automatiquement, on peut s'attendre à une réduction du trafic de données."
+_reversi:
+  total: "Total"
+
diff --git a/locales/generateDTS.js b/locales/generateDTS.js
index d3afdd6e15..49807144ec 100644
--- a/locales/generateDTS.js
+++ b/locales/generateDTS.js
@@ -6,54 +6,176 @@ import ts from 'typescript';
 
 const __filename = fileURLToPath(import.meta.url);
 const __dirname = dirname(__filename);
+const parameterRegExp = /\{(\w+)\}/g;
+
+function createMemberType(item) {
+	if (typeof item !== 'string') {
+		return ts.factory.createTypeLiteralNode(createMembers(item));
+	}
+	const parameters = Array.from(
+		item.matchAll(parameterRegExp),
+		([, parameter]) => parameter,
+	);
+	return parameters.length
+		? ts.factory.createTypeReferenceNode(
+				ts.factory.createIdentifier('ParameterizedString'),
+				[
+					ts.factory.createUnionTypeNode(
+						parameters.map((parameter) =>
+							ts.factory.createStringLiteral(parameter),
+						),
+					),
+				],
+			)
+		: ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword);
+}
 
 function createMembers(record) {
-	return Object.entries(record)
-		.map(([k, v]) => ts.factory.createPropertySignature(
+	return Object.entries(record).map(([k, v]) => {
+		const node = ts.factory.createPropertySignature(
 			undefined,
 			ts.factory.createStringLiteral(k),
 			undefined,
-			typeof v === 'string'
-				? ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword)
-				: ts.factory.createTypeLiteralNode(createMembers(v)),
-		));
+			createMemberType(v),
+		);
+		if (typeof v === 'string') {
+			ts.addSyntheticLeadingComment(
+				node,
+				ts.SyntaxKind.MultiLineCommentTrivia,
+				`*
+ * ${v.replace(/\n/g, '\n * ')}
+ `,
+				true,
+			);
+		}
+		return node;
+	});
 }
 
 export default function generateDTS() {
 	const locale = yaml.load(fs.readFileSync(`${__dirname}/ja-JP.yml`, 'utf-8'));
 	const members = createMembers(locale);
 	const elements = [
+		ts.factory.createVariableStatement(
+			[ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)],
+			ts.factory.createVariableDeclarationList(
+				[
+					ts.factory.createVariableDeclaration(
+						ts.factory.createIdentifier('kParameters'),
+						undefined,
+						ts.factory.createTypeOperatorNode(
+							ts.SyntaxKind.UniqueKeyword,
+							ts.factory.createKeywordTypeNode(ts.SyntaxKind.SymbolKeyword),
+						),
+						undefined,
+					),
+				],
+				ts.NodeFlags.Const,
+			),
+		),
+		ts.factory.createInterfaceDeclaration(
+			[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
+			ts.factory.createIdentifier('ParameterizedString'),
+			[
+				ts.factory.createTypeParameterDeclaration(
+					undefined,
+					ts.factory.createIdentifier('T'),
+					ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
+					ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
+				),
+			],
+			undefined,
+			[
+				ts.factory.createPropertySignature(
+					undefined,
+					ts.factory.createComputedPropertyName(
+						ts.factory.createIdentifier('kParameters'),
+					),
+					undefined,
+					ts.factory.createTypeReferenceNode(
+						ts.factory.createIdentifier('T'),
+						undefined,
+					),
+				),
+			],
+		),
+		ts.factory.createInterfaceDeclaration(
+			[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
+			ts.factory.createIdentifier('ILocale'),
+			undefined,
+			undefined,
+			[
+				ts.factory.createIndexSignature(
+					undefined,
+					[
+						ts.factory.createParameterDeclaration(
+							undefined,
+							undefined,
+							ts.factory.createIdentifier('_'),
+							undefined,
+							ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
+							undefined,
+						),
+					],
+					ts.factory.createUnionTypeNode([
+						ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
+						ts.factory.createTypeReferenceNode(
+							ts.factory.createIdentifier('ParameterizedString'),
+						),
+						ts.factory.createTypeReferenceNode(
+							ts.factory.createIdentifier('ILocale'),
+							undefined,
+						),
+					]),
+				),
+			],
+		),
 		ts.factory.createInterfaceDeclaration(
 			[ts.factory.createToken(ts.SyntaxKind.ExportKeyword)],
 			ts.factory.createIdentifier('Locale'),
 			undefined,
-			undefined,
+			[
+				ts.factory.createHeritageClause(ts.SyntaxKind.ExtendsKeyword, [
+					ts.factory.createExpressionWithTypeArguments(
+						ts.factory.createIdentifier('ILocale'),
+						undefined,
+					),
+				]),
+			],
 			members,
 		),
 		ts.factory.createVariableStatement(
 			[ts.factory.createToken(ts.SyntaxKind.DeclareKeyword)],
 			ts.factory.createVariableDeclarationList(
-				[ts.factory.createVariableDeclaration(
-					ts.factory.createIdentifier('locales'),
-					undefined,
-					ts.factory.createTypeLiteralNode([ts.factory.createIndexSignature(
+				[
+					ts.factory.createVariableDeclaration(
+						ts.factory.createIdentifier('locales'),
 						undefined,
-						[ts.factory.createParameterDeclaration(
-							undefined,
-							undefined,
-							ts.factory.createIdentifier('lang'),
-							undefined,
-							ts.factory.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
-							undefined,
-						)],
-						ts.factory.createTypeReferenceNode(
-							ts.factory.createIdentifier('Locale'),
-							undefined,
-						),
-					)]),
-					undefined,
-				)],
-				ts.NodeFlags.Const | ts.NodeFlags.Ambient | ts.NodeFlags.ContextFlags,
+						ts.factory.createTypeLiteralNode([
+							ts.factory.createIndexSignature(
+								undefined,
+								[
+									ts.factory.createParameterDeclaration(
+										undefined,
+										undefined,
+										ts.factory.createIdentifier('lang'),
+										undefined,
+										ts.factory.createKeywordTypeNode(
+											ts.SyntaxKind.StringKeyword,
+										),
+										undefined,
+									),
+								],
+								ts.factory.createTypeReferenceNode(
+									ts.factory.createIdentifier('Locale'),
+									undefined,
+								),
+							),
+						]),
+						undefined,
+					),
+				],
+				ts.NodeFlags.Const,
 			),
 		),
 		ts.factory.createFunctionDeclaration(
@@ -70,16 +192,39 @@ export default function generateDTS() {
 		),
 		ts.factory.createExportDefault(ts.factory.createIdentifier('locales')),
 	];
-	const printed = ts.createPrinter({
-		newLine: ts.NewLineKind.LineFeed,
-	}).printList(
-		ts.ListFormat.MultiLine,
-		ts.factory.createNodeArray(elements),
-		ts.createSourceFile('index.d.ts', '', ts.ScriptTarget.ESNext, true, ts.ScriptKind.TS),
+	ts.addSyntheticLeadingComment(
+		elements[0],
+		ts.SyntaxKind.MultiLineCommentTrivia,
+		' eslint-disable ',
+		true,
 	);
+	ts.addSyntheticLeadingComment(
+		elements[0],
+		ts.SyntaxKind.SingleLineCommentTrivia,
+		' This file is generated by locales/generateDTS.js',
+		true,
+	);
+	ts.addSyntheticLeadingComment(
+		elements[0],
+		ts.SyntaxKind.SingleLineCommentTrivia,
+		' Do not edit this file directly.',
+		true,
+	);
+	const printed = ts
+		.createPrinter({
+			newLine: ts.NewLineKind.LineFeed,
+		})
+		.printList(
+			ts.ListFormat.MultiLine,
+			ts.factory.createNodeArray(elements),
+			ts.createSourceFile(
+				'index.d.ts',
+				'',
+				ts.ScriptTarget.ESNext,
+				true,
+				ts.ScriptKind.TS,
+			),
+		);
 
-	fs.writeFileSync(`${__dirname}/index.d.ts`, `/* eslint-disable */
-// This file is generated by locales/generateDTS.js
-// Do not edit this file directly.
-${printed}`, 'utf-8');
+	fs.writeFileSync(`${__dirname}/index.d.ts`, printed, 'utf-8');
 }
diff --git a/locales/hr-HR.yml b/locales/hr-HR.yml
index 9cfebdd01a..881aa8464e 100644
--- a/locales/hr-HR.yml
+++ b/locales/hr-HR.yml
@@ -3,3 +3,4 @@ _lang_: "japanski"
 ok: "OK"
 gotIt: "Razumijem"
 cancel: "otkazati"
+
diff --git a/locales/ht-HT.yml b/locales/ht-HT.yml
index e3595c79b6..1698c9f280 100644
--- a/locales/ht-HT.yml
+++ b/locales/ht-HT.yml
@@ -16,3 +16,4 @@ _2fa:
   renewTOTPCancel: "Sispann"
 _widgets:
   profile: "pwofil"
+
diff --git a/locales/hu-HU.yml b/locales/hu-HU.yml
index 023a91494d..2f7006484a 100644
--- a/locales/hu-HU.yml
+++ b/locales/hu-HU.yml
@@ -102,3 +102,4 @@ _deck:
   _columns:
     notifications: "Értesítések"
     tl: "Idővonal"
+
diff --git a/locales/id-ID.yml b/locales/id-ID.yml
index 00844550fd..b638d7991f 100644
--- a/locales/id-ID.yml
+++ b/locales/id-ID.yml
@@ -81,7 +81,7 @@ exportRequested: "Kamu telah meminta ekspor. Ini akan memakan waktu sesaat. Sete
 importRequested: "Kamu telah meminta impor. Ini akan memakan waktu sesaat."
 lists: "Daftar"
 noLists: "Kamu tidak memiliki daftar apapun"
-note: "Catat"
+note: "Catatan"
 notes: "Catatan"
 following: "Ikuti"
 followers: "Pengikut"
@@ -125,9 +125,12 @@ emojiPicker: "Emoji Picker"
 pinnedEmojisForReactionSettingDescription: "Atur sematan emoji pada reaksi"
 pinnedEmojisSettingDescription: "Atur sematan emoji pada masukan emoji"
 emojiPickerDisplay: "Tampilan Emoji Picker"
+overwriteFromPinnedEmojisForReaction: "Timpa dari pengaturan reaksi"
+overwriteFromPinnedEmojis: "Timpa dari pengaturan umum"
 reactionSettingDescription2: "Geser untuk memindah urutan emoji, klik untuk menghapus, tekan \"+\" untuk menambahkan"
 rememberNoteVisibility: "Ingat pengaturan visibilitas catatan"
 attachCancel: "Hapus lampiran"
+deleteFile: "Berkas dihapus"
 markAsSensitive: "Tandai sebagai konten sensitif"
 unmarkAsSensitive: "Hapus tanda konten sensitif"
 enterFileName: "Masukkan nama berkas"
@@ -377,6 +380,11 @@ hcaptcha: "hCaptcha"
 enableHcaptcha: "Nyalakan hCaptcha"
 hcaptchaSiteKey: "Site Key"
 hcaptchaSecretKey: "Secret Key"
+mcaptcha: "mCaptcha"
+enableMcaptcha: "Nyalakan mCaptcha"
+mcaptchaSiteKey: "Site key"
+mcaptchaSecretKey: "Secret Key"
+mcaptchaInstanceUrl: "URL instansi mCaptcha"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "Nyalakan reCAPTCHA"
 recaptchaSiteKey: "Site key"
@@ -624,6 +632,7 @@ medium: "Sedang"
 small: "Kecil"
 generateAccessToken: "Buat token akses"
 permission: "Izin"
+adminPermission: "Wewenang Izin Admin"
 enableAll: "Aktifkan semua"
 disableAll: "Nonaktifkan semua"
 tokenRequested: "Berikan ijin akses ke akun"
@@ -667,6 +676,7 @@ useGlobalSettingDesc: "Jika dinyalakan, setelan notifikasi akun kamu akan diguna
 other: "Lainnya"
 regenerateLoginToken: "Perbarui token login"
 regenerateLoginTokenDescription: "Perbarui token yang digunakan secara internal saat login. Normalnya aksi ini tidak diperlukan. Jika diperbarui, semua perangkat akan dilogout."
+theKeywordWhenSearchingForCustomEmoji: "Kata kunci ini digunakan untuk mencari emoji kustom yang dicari."
 setMultipleBySeparatingWithSpace: "Kamu dapat menyetel banyak dengan memisahkannya menggunakan spasi."
 fileIdOrUrl: "File-ID atau URL"
 behavior: "Perilaku"
@@ -879,6 +889,8 @@ makeReactionsPublicDescription: "Pengaturan ini akan membuat daftar dari semua r
 classic: "Klasik"
 muteThread: "Bisukan thread"
 unmuteThread: "Suarakan thread"
+followingVisibility: "Visibilitas mengikuti"
+followersVisibility: "Visibilitas pengikut"
 continueThread: "Lihat lanjutan thread"
 deleteAccountConfirm: "Akun akan dihapus. Apakah kamu yakin?"
 incorrectPassword: "Kata sandi salah."
@@ -1029,6 +1041,8 @@ resetPasswordConfirm: "Yakin untuk mereset kata sandimu?"
 sensitiveWords: "Kata sensitif"
 sensitiveWordsDescription: "Visibilitas dari semua catatan mengandung kata yang telah diatur akan dijadikan \"Beranda\" secara otomatis. Kamu dapat mendaftarkan kata tersebut lebih dari satu dengan menuliskannya di baris baru."
 sensitiveWordsDescription2: "Menggunakan spasi akan membuat ekspresi AND dan kata kunci disekitarnya dengan garis miring akan mengubahnya menjadi ekspresi reguler."
+prohibitedWords: "Kata yang dilarang"
+prohibitedWordsDescription2: "Menggunakan spasi akan membuat ekspresi AND dan kata kunci disekitarnya dengan garis miring akan mengubahnya menjadi ekspresi reguler."
 hiddenTags: "Tagar tersembunyi"
 hiddenTagsDescription: "Pilih tanda yang mana akan tidak diperlihatkan dalam daftar tren.\nTanda lebih dari satu dapat didaftarkan dengan tiap baris."
 notesSearchNotAvailable: "Pencarian catatan tidak tersedia."
@@ -1047,6 +1061,8 @@ limitWidthOfReaction: "Batasi lebar maksimum reaksi dan tampilkan dalam ukuran t
 noteIdOrUrl: "ID catatan atau URL"
 video: "Video"
 videos: "Video"
+audio: "Suara"
+audioFiles: "Berkas Suara"
 dataSaver: "Penghemat data"
 accountMigration: "Pemindahan akun"
 accountMoved: "Pengguna ini telah berpindah ke akun baru:"
@@ -1150,6 +1166,7 @@ hideRepliesToOthersInTimelineAll: "Sembuyikan balasan ke lainnya dari semua oran
 confirmShowRepliesAll: "Operasi ini tidak dapat diubah. Apakah kamu yakin untuk menampilkan balasan ke lainnya dari semua orang yang kamu ikuti di lini masa?"
 confirmHideRepliesAll: "Operasi ini tidak dapat diubah. Apakah kamu yakin untuk menyembunyikan balasan ke lainnya dari semua orang yang kamu ikuti di lini masa?"
 externalServices: "Layanan eksternal"
+sourceCode: "Sumber kode"
 impressum: "Impressum"
 impressumUrl: "Tautan Impressum"
 impressumDescription: "Pada beberapa negara seperti Jerman, inklusi dari informasi kontak operator (sebuah Impressum) diperlukan secara legal untuk situs web komersil."
@@ -1174,7 +1191,29 @@ doReaction: "Tambahkan reaksi"
 code: "Kode"
 reloadRequiredToApplySettings: "Muat ulang diperlukan untuk menerapkan pengaturan."
 remainingN: "Sisa : {n}"
+overwriteContentConfirm: "Apakah kamu yakin untuk menimpa konten saat ini?"
+seasonalScreenEffect: "Efek layar musiman"
 decorate: "Dekor"
+addMfmFunction: "Tambahkan dekorasi"
+enableQuickAddMfmFunction: "Tampilkan pemilih MFM tingkat lanjut"
+bubbleGame: "Bubble Game"
+sfx: "Efek Suara"
+soundWillBePlayed: "Suara yang akan dimainkan"
+showReplay: "Lihat tayangan ulang"
+replay: "Tayangan ulang"
+replaying: "Menayangkan Ulang"
+ranking: "Peringkat"
+lastNDays: "{n} hari terakhir"
+backToTitle: "Ke Judul"
+hemisphere: "Letak kamu tinggal"
+withSensitive: "Lampirkan catatan dengan berkas sensitif"
+userSaysSomethingSensitive: "Postingan oleh {name} mengandung konten sensitif"
+enableHorizontalSwipe: "Geser untuk mengganti tab"
+surrender: "Batalkan"
+_bubbleGame:
+  howToPlay: "Cara bermain"
+  _howToPlay:
+    section1: "Atur posisi dan jatuhkan obyek ke dalam kotak."
 _announcement:
   forExistingUsers: "Hanya pengguna yang telah ada"
   forExistingUsersDescription: "Pengumuman ini akan dimunculkan ke pengguna yang sudah ada dari titik waktu publikasi jika dinyalakan. Apabila dimatikan, mereka yang baru mendaftar setelah publikasi ini akan juga melihatnya."
@@ -1184,7 +1223,10 @@ _announcement:
   tooManyActiveAnnouncementDescription: "Terlalu banyak pengumuman dapat memperburuk pengalaman pengguna. Mohon pertimbangkan untuk mengarsipkan pengumuman yang sudah usang/tidak relevan."
   readConfirmTitle: "Tandai telah dibaca?"
   readConfirmText: "Aksi ini akan menandai konten dari \"{title}\" telah dibaca."
+  shouldNotBeUsedToPresentPermanentInfo: "Karena dapat berdampak pada pengalaman pengguna untuk pengguna baru, sangat direkomendasikan untuk menggunakan notifikasi secara mengalir daripada tetap."
+  dialogAnnouncementUxWarn: "Memiliki dua atau lebih gaya dialog notifikasi secara bersamaan dapat berdampak signifikan pada pengalaman pengguna, mohon untuk menggunakannya dengan hati-hati."
   silence: "Tiada notifikasi"
+  silenceDescription: "Apabila diaktifkan, notifikasi dari pengumuman ini akan dilewatkan dan pengguna tidak perlu membacanya."
 _initialAccountSetting:
   accountCreated: "Akun kamu telah sukses dibuat!"
   letsStartAccountSetup: "Untuk pemula, ayo atur profilmu dulu."
@@ -1197,6 +1239,7 @@ _initialAccountSetting:
   pushNotificationDescription: "Menyalakan notifikasi dorong akan membuatmu menerima notifikasi dari {name} secara langsung ke perangkatmu."
   initialAccountSettingCompleted: "Pengaturan profil selesai!"
   haveFun: "Selamat menikmati, {name}!"
+  youCanContinueTutorial: "Kamu dapat menjutkan ke tutorial dalam bagaimana menggunakan {name} (Misskey) atau kamu dapat keluar dari pemasangan ini dan langsung menggunakannya segera."
   startTutorial: "Mulai Tutorial"
   skipAreYouSure: "Yakin melewati atur profil?"
   laterAreYouSure: "Yakin banget untuk atur profil nanti?"
@@ -1210,6 +1253,10 @@ _initialTutorial:
     description: "Di sini kamu dapat mempelajari dasar-dasar dari penggunaan Misskey dan fitur-fiturnya."
   _note:
     title: "Apa itu Catatan?"
+    description: "Postingan di Misskey disebut sebagai 'Catatan'. Catatan ditampilkan secara kronologis pada lini masa dan dimutakhirkan secara real-time."
+    reply: "Klik pada tombol ini untuk membalas ke sebuah pesan. Bisa juga untuk membalas ke sebuah balasan dan melanjutkannya seperti percakapan selayaknya utas."
+    renote: "Kamu dapat membagikan catatan ke lini masa milikmu. Kamu juga dapat mengutipnya dengan komentarmu."
+    reaction: "Kamu dapat menambahkan reaksi ke Catatan. Detil lebih lanjut akan dijelaskan di halaman berikutnya."
   _reaction:
     title: "Apa itu Reaksi?"
   _timeline:
@@ -1228,6 +1275,8 @@ _initialTutorial:
         note: "Baru aja makan donat berlapis coklat 🍩😋"
   _howToMakeAttachmentsSensitive:
     title: "Bagaimana menandai lampiran sebagai sensitif?"
+  _done:
+    title: "Kamu telah menyelesaikan tutorial! 🎉"
 _serverRules:
   description: "Daftar peraturan akan ditampilkan sebelum pendaftaran. Mengatur ringkasan dari Syarat dan Ketentuan sangat direkomendasikan."
 _serverSettings:
@@ -1774,6 +1823,14 @@ _sfx:
   notification: "Notifikasi"
   antenna: "Penerimaan Antenna"
   channel: "Notifikasi Kanal"
+  reaction: "Ketika memilih reaksi"
+_soundSettings:
+  driveFile: "Menggunakan berkas audio dalam Drive"
+  driveFileWarn: "Pilih berkas audio dari Drive"
+  driveFileTypeWarn: "Berkas ini tidak didukung"
+  driveFileTypeWarnDescription: "Pilih berkas audio"
+  driveFileDurationWarn: "Audio ini terlalu panjang"
+  driveFileDurationWarnDescription: "Audio panjang dapat mengganggu penggunaan Misskey. Masih ingin melanjutkan?"
 _ago:
   future: "Masa depan"
   justNow: "Baru saja"
@@ -1785,6 +1842,14 @@ _ago:
   monthsAgo: "{n} bulan lalu"
   yearsAgo: "{n} tahun lalu"
   invalid: "Tidak ada sama sekali disini"
+_timeIn:
+  seconds: "dalam {n} detik"
+  minutes: "dalam {n} menit"
+  hours: "dalam {n} jam"
+  days: "dalam {n} hari"
+  weeks: "dalam {n} minggu"
+  months: "dalam {n} bulan"
+  years: "dalam {n} tahun"
 _time:
   second: "detik"
   minute: "menit"
@@ -1856,6 +1921,55 @@ _permissions:
   "write:flash": "Sunting Play"
   "read:flash-likes": "Lihat daftar Play yang disukai"
   "write:flash-likes": "Sunting daftar Play yang disukai"
+  "read:admin:abuse-user-reports": "Lihat laporan pengguna"
+  "write:admin:delete-account": "Hapus akun pengguna"
+  "write:admin:delete-all-files-of-a-user": "Hapus semua berkas dari seorang pengguna"
+  "read:admin:index-stats": "Lihat statistik indeks basis data"
+  "read:admin:table-stats": "Lihat statistik tabel basis data"
+  "read:admin:user-ips": "Lihat alamat IP pengguna"
+  "read:admin:meta": "Lihat metadata instansi"
+  "write:admin:reset-password": "Atur ulang kata sandi pengguna"
+  "write:admin:resolve-abuse-user-report": "Selesaikan laporan pengguna"
+  "write:admin:send-email": "Mengirim surel"
+  "read:admin:server-info": "Lihat informasi peladen"
+  "read:admin:show-moderation-log": "Lihat log moderasi"
+  "read:admin:show-user": "Lihat informasi pengguna privat"
+  "read:admin:show-users": "Lihat informasi pengguna privat"
+  "write:admin:suspend-user": "Tangguhkan pengguna"
+  "write:admin:unset-user-avatar": "Hapus avatar pengguna"
+  "write:admin:unset-user-banner": "Hapus banner pengguna"
+  "write:admin:unsuspend-user": "Batalkan penangguhan pengguna"
+  "write:admin:meta": "Kelola metadata instansi"
+  "write:admin:user-note": "Kelola moderasi catatan"
+  "write:admin:roles": "Kelola peran"
+  "read:admin:roles": "Lihat peran"
+  "write:admin:relays": "Kelola relay"
+  "read:admin:relays": "Lihat relay"
+  "write:admin:invite-codes": "Kelola kode undangan"
+  "read:admin:invite-codes": "Lihat kode undangan"
+  "write:admin:announcements": "Kelola pengumuman"
+  "read:admin:announcements": "Lihat Pengumuman"
+  "write:admin:avatar-decorations": "Kelola dekorasi avatar"
+  "read:admin:avatar-decorations": "Lihat dekorasi avatar"
+  "write:admin:federation": "Kelola data federasi"
+  "write:admin:account": "Kelola akun pengguna"
+  "read:admin:account": "Lihat akun pengguna"
+  "write:admin:emoji": "Kelola emoji"
+  "read:admin:emoji": "Lihat emoji"
+  "write:admin:queue": "Kelola antrian kerja"
+  "read:admin:queue": "Lihat informasi antrian kerja"
+  "write:admin:promo": "Kelola catatan promosi"
+  "write:admin:drive": "Kelola drive pengguna"
+  "read:admin:drive": "Kelola informasi drive pengguna"
+  "read:admin:stream": "Gunakan API WebSocket untuk Admin"
+  "write:admin:ad": "Kelola iklan"
+  "read:admin:ad": "Lihat iklan"
+  "write:invite-codes": "Membuat kode undangan"
+  "read:invite-codes": "Mendapatkan kode undangan"
+  "write:clip-favorite": "Kelola klip yang difavoritkan"
+  "read:clip-favorite": "Lihat klip yang difavoritkan"
+  "read:federation": "Mendapatkan data federasi"
+  "write:report-abuse": "Melaporkan pelanggaran"
 _auth:
   shareAccessTitle: "Mendapatkan ijin akses aplikasi"
   shareAccess: "Apakah kamu ingin mengijinkan \"{name}\" untuk mengakses akun ini?"
@@ -1910,6 +2024,7 @@ _widgets:
   _userList:
     chooseList: "Pilih daftar"
   clicker: "Pengeklik"
+  birthdayFollowings: "Pengguna yang merayakan hari ulang tahunnya hari ini"
 _cw:
   hide: "Sembunyikan"
   show: "Lihat konten"
@@ -1972,9 +2087,11 @@ _profile:
   changeAvatar: "Ubah avatar"
   changeBanner: "Ubah header"
   verifiedLinkDescription: "Dengan memasukkan URL yang mengandung tautan ke profil kamu di sini, ikon verifikasi kepemilikan dapat ditampilkan di sebelah kolom ini."
+  avatarDecorationMax: "Dapat ditambahkan hingga {max} dekorasi."
 _exportOrImport:
   allNotes: "Semua catatan"
   favoritedNotes: "Catatan favorit"
+  clips: "Klip"
   followingList: "Ikuti"
   muteList: "Bisukan"
   blockingList: "Blokir"
@@ -2093,12 +2210,16 @@ _notification:
   pollEnded: "Hasil Kuesioner telah keluar"
   newNote: "Catatan baru"
   unreadAntennaNote: "Antena {name}"
+  roleAssigned: "Peran Diberikan"
   emptyPushNotificationMessage: "Pembaruan notifikasi dorong"
   achievementEarned: "Pencapaian didapatkan"
   testNotification: "Tes notifikasi"
   checkNotificationBehavior: "Cek tampilan notifikasi"
   sendTestNotification: "Kirim tes notifikasi"
   notificationWillBeDisplayedLikeThis: "Notifikasi akan terlihat seperti ini"
+  reactedBySomeUsers: "{n} orang memberikan reaksi"
+  renotedBySomeUsers: "{n} orang telah merenote"
+  followedBySomeUsers: "{n} orang telah mengikuti"
   _types:
     all: "Semua"
     note: "Catatan baru"
@@ -2111,6 +2232,7 @@ _notification:
     pollEnded: "Jajak pendapat berakhir"
     receiveFollowRequest: "Permintaan mengikuti diterima"
     followRequestAccepted: "Permintaan mengikuti disetujui"
+    roleAssigned: "Peran Diberikan"
     achievementEarned: "Pencapaian didapatkan"
     app: "Notifikasi dari aplikasi tertaut"
   _actions:
@@ -2202,6 +2324,11 @@ _moderationLogTypes:
   createAd: "Iklan telah dibuat"
   deleteAd: "Iklan telah dihapus"
   updateAd: "Iklan telah diperbaharui"
+  createAvatarDecoration: "Buat dekorasi avatar"
+  updateAvatarDecoration: "Perbarui dekorasi avatar"
+  deleteAvatarDecoration: "Hapus dekorasi avatar"
+  unsetUserAvatar: "Hapus avatar pengguna"
+  unsetUserBanner: "Hapus banner pengguna"
 _fileViewer:
   title: "Rincian berkas"
   type: "Jenis berkas"
@@ -2210,3 +2337,95 @@ _fileViewer:
   uploadedAt: "Diunggah pada"
   attachedNotes: "Catatan yang dilampirkan"
   thisPageCanBeSeenFromTheAuthor: "Halaman ini hanya dapat dilihat oleh pengguna yang mengunggah bekas ini."
+_externalResourceInstaller:
+  title: "Pasang dari situs eksternal"
+  checkVendorBeforeInstall: "Pastikan sumber dari sumber daya ini terpercaya sebelum melakukan pemasangan."
+  _plugin:
+    title: "Apakah kamu ingin memasang plugin ini?"
+    metaTitle: "Informasi plugin"
+  _theme:
+    title: "Apakah kamu ingin memasang tema ini?"
+    metaTitle: "Informasi tema"
+  _meta:
+    base: "Skema warna dasar"
+  _vendorInfo:
+    title: "Informasi sumber"
+    endpoint: "Referensi Endpoint"
+    hashVerify: "Verifikasi hash"
+  _errors:
+    _invalidParams:
+      title: "Parameter tidak valid"
+      description: "Tidak cukup informasi untuk memuat data dari situs eksternal. Mohon konfirmasi kembali URL yang dimasukkan."
+    _resourceTypeNotSupported:
+      title: "Sumber daya eksternal ini tidak didukung"
+      description: "Tipe sumber daya eksternal ini tidak didukung. Mohon kontak administrator dari situs tersebut."
+    _failedToFetch:
+      title: "Gagal memuat data"
+      fetchErrorDescription: "Kesalahan terjadi ketika menghubungkan dengan situs eksternal. Jika percobaan kembali tidak dapat memperbaiki masalah ini, mohon hubungi administrator dari situs tersebut."
+      parseErrorDescription: "Kesalahan terjadi dalam memproses data yang dimuat dari situs eksternal. Mohon hubungi administrator dari situs tersebut."
+    _hashUnmatched:
+      title: "Verifikasi data gagal"
+      description: "Kesalahan terjadi dalam memverifikasi integritas data yang diambil. Sebagai pencegahan keamanan, pemasangan tidak dapat dilanjutkan. Mohon hubungi administrator dari situs tersebut."
+    _pluginParseFailed:
+      title: "Kesalahan AiScript"
+      description: "Data yang diminta telah diambil dengan sukses, namun kesalahan terjadi ketika AiScript melakukan parsing. Mohon hubungi pembuat plugin. Detil kesalahan dapat dilihat pada konsol Javascript."
+    _pluginInstallFailed:
+      title: "Pemasangan plugin gagal"
+      description: "Kesalahan terjadi ketika pemasangan plugin. Mohon coba lagi. Detil kesalahan dapat dilihat pada konsol Javascript."
+    _themeParseFailed:
+      title: "Parsing tema gagal"
+      description: "Data yang diminta telah diambil dengan sukses, namun kesalahan terjadi ketika tema melakukan parsing. Mohon hubungi pembuat tema. Detil kesalahan dapat dilihat pada konsol Javascript."
+    _themeInstallFailed:
+      title: "Pemasangan tema gagal"
+      description: "Kesalahan terjadi ketika pemasangan tema. Mohon coba lagi. Detil kesalahan dapat dilihat pada konsol Javascript."
+_dataSaver:
+  _media:
+    title: "Memuat media"
+    description: "Mencegah gambar/video dimuat secara otomatis. Menyembunyikan gambar/video dan akan dimuat ketika diketuk."
+  _avatar:
+    title: "Gambar avatar"
+    description: "Hentikan animasi gambar avatar. Gambar animasi dapat berukuran lebih besar dari gambar biasa, berpotensi pada pengurangan lalu lintas data lebih jauh."
+  _urlPreview:
+    title: "Gambar kecil URL pratinjau"
+    description: "Gambar kecil URL pratinjau tidak akan dimuat lagi."
+  _code:
+    title: "Penyorotan kode"
+    description: "Jika notasi penyorotan kode digunakan di MFM, dll. Fungsi tersebut tidak akan dimuat apabila tidak diketuk. Penyorotan sintaks membutuhkan pengunduhan berkas definisi penyorotan untuk setiap bahasa pemrograman. Oleh sebab itu, menonaktifkan pemuatan otomatis dari berkas ini dilakukan untuk mengurangi jumlah komunikasi data."
+_hemisphere:
+  N: "Bumi belahan utara"
+  S: "Bumi belahan selatan"
+  caption: "Digunakan dalam beberapa pengaturan klien untuk menentukan musim."
+_reversi:
+  reversi: "Reversi"
+  gameSettings: "Pengaturan permainan"
+  chooseBoard: "Pilih papan"
+  blackOrWhite: "Hitam/Putih"
+  blackIs: "{name} bermain sebagai Hitam"
+  rules: "Aturan"
+  thisGameIsStartedSoon: "Permainan akan segera dimulai"
+  waitingForOther: "Menunggu langkah giliran dari lawan"
+  waitingForMe: "Menungguh langkah giliran dari kamu"
+  waitingBoth: "Bersiap"
+  ready: "Siap"
+  cancelReady: "Belum siap"
+  opponentTurn: "Giliran lawan"
+  myTurn: "Giliran kamu"
+  turnOf: "Giliran {name}"
+  pastTurnOf: "Giliran {name}"
+  surrender: "Menyerah"
+  surrendered: "Telah menyerah"
+  timeout: "Waktu habis"
+  drawn: "Seri"
+  won: "{name} menang"
+  black: "Hitam"
+  white: "Putih"
+  total: "Jumlah"
+  turnCount: "Langkah ke {count}"
+  myGames: "Rondeku"
+  allGames: "Semua ronde"
+  ended: "Selesai"
+  playing: "Sedang bermain"
+  isLlotheo: "Pemain dengan batu yang sedikit menang (Llotheo)"
+  loopedMap: "Peta melingkar"
+  canPutEverywhere: "Keping dapat ditaruh dimana saja"
+
diff --git a/locales/index.d.ts b/locales/index.d.ts
index dd2f34a69a..e407d2119b 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -1,2653 +1,10052 @@
 /* eslint-disable */
 // This file is generated by locales/generateDTS.js
 // Do not edit this file directly.
-export interface Locale {
+declare const kParameters: unique symbol;
+export interface ParameterizedString<T extends string = string> {
+    [kParameters]: T;
+}
+export interface ILocale {
+    [_: string]: string | ParameterizedString | ILocale;
+}
+export interface Locale extends ILocale {
+    /**
+     * 日本語
+     */
     "_lang_": string;
+    /**
+     * ノートでつながるネットワーク
+     */
     "headlineMisskey": string;
+    /**
+     * ようこそ!Sharkeyは、オープンソースの分散型マイクロブログサービスです。
+     * 「ノート」を作成して、いま起こっていることを共有したり、あなたについて皆に発信しよう📡
+     * 「リアクション」機能で、皆のノートに素早く反応を追加することもできます👍
+     * 新しい世界を探検しよう🚀
+     */
     "introMisskey": string;
-    "poweredByMisskeyDescription": string;
-    "monthAndDay": string;
+    /**
+     * {name}は、オープンソースのプラットフォーム<b>Sharkey</b>のサーバーのひとつです。
+     */
+    "poweredByMisskeyDescription": ParameterizedString<"name">;
+    /**
+     * {month}月 {day}日
+     */
+    "monthAndDay": ParameterizedString<"month" | "day">;
+    /**
+     * 検索
+     */
     "search": string;
+    /**
+     * 通知
+     */
     "notifications": string;
+    /**
+     * ユーザー名
+     */
     "username": string;
+    /**
+     * パスワード
+     */
     "password": string;
+    /**
+     * パスワードを忘れた
+     */
     "forgotPassword": string;
+    /**
+     * 連合に照会中
+     */
     "fetchingAsApObject": string;
+    /**
+     * OK
+     */
     "ok": string;
+    /**
+     * わかった
+     */
     "gotIt": string;
+    /**
+     * キャンセル
+     */
     "cancel": string;
+    /**
+     * やめておく
+     */
     "noThankYou": string;
+    /**
+     * ユーザー名を入力
+     */
     "enterUsername": string;
-    "renotedBy": string;
+    /**
+     * {user}がブースト
+     */
+    "renotedBy": ParameterizedString<"user">;
+    /**
+     * ノートはありません
+     */
     "noNotes": string;
+    /**
+     * 通知はありません
+     */
     "noNotifications": string;
+    /**
+     * サーバー
+     */
     "instance": string;
+    /**
+     * 設定
+     */
     "settings": string;
+    /**
+     * 通知の設定
+     */
     "notificationSettings": string;
+    /**
+     * 基本設定
+     */
     "basicSettings": string;
+    /**
+     * その他の設定
+     */
     "otherSettings": string;
+    /**
+     * ウィンドウで開く
+     */
     "openInWindow": string;
+    /**
+     * プロフィール
+     */
     "profile": string;
+    /**
+     * タイムライン
+     */
     "timeline": string;
+    /**
+     * 自己紹介はありません
+     */
     "noAccountDescription": string;
+    /**
+     * ログイン
+     */
     "login": string;
+    /**
+     * ログイン中
+     */
     "loggingIn": string;
+    /**
+     * ログアウト
+     */
     "logout": string;
+    /**
+     * 新規登録
+     */
     "signup": string;
+    /**
+     * アップロード中
+     */
     "uploading": string;
+    /**
+     * 保存
+     */
     "save": string;
+    /**
+     * ユーザー
+     */
     "users": string;
+    /**
+     * 承認
+     */
     "approvals": string;
+    /**
+     * ユーザーを追加
+     */
     "addUser": string;
+    /**
+     * お気に入り
+     */
     "favorite": string;
+    /**
+     * お気に入り
+     */
     "favorites": string;
+    /**
+     * お気に入り解除
+     */
     "unfavorite": string;
+    /**
+     * お気に入りに登録しました。
+     */
     "favorited": string;
+    /**
+     * 既にお気に入りに登録されています。
+     */
     "alreadyFavorited": string;
+    /**
+     * お気に入りに登録できませんでした。
+     */
     "cantFavorite": string;
+    /**
+     * ピン留め
+     */
     "pin": string;
+    /**
+     * ピン留め解除
+     */
     "unpin": string;
+    /**
+     * 内容をコピー
+     */
     "copyContent": string;
+    /**
+     * リンクをコピー
+     */
     "copyLink": string;
+    /**
+     * ブーストのリンクをコピー
+     */
     "copyLinkRenote": string;
+    /**
+     * 削除
+     */
     "delete": string;
+    /**
+     * 削除して編集
+     */
     "deleteAndEdit": string;
+    /**
+     * このノートを削除してもう一度編集しますか?このノートへのリアクション、ブースト、返信も全て削除されます。
+     */
     "deleteAndEditConfirm": string;
+    /**
+     * リストに追加
+     */
     "addToList": string;
+    /**
+     * アンテナに追加
+     */
     "addToAntenna": string;
+    /**
+     * メッセージを送信
+     */
     "sendMessage": string;
+    /**
+     * RSSをコピー
+     */
     "copyRSS": string;
+    /**
+     * ユーザー名をコピー
+     */
     "copyUsername": string;
+    /**
+     * リモートプロフィールを開く
+     */
     "openRemoteProfile": string;
+    /**
+     * ユーザーIDをコピー
+     */
     "copyUserId": string;
+    /**
+     * ノートIDをコピー
+     */
     "copyNoteId": string;
+    /**
+     * ファイルIDをコピー
+     */
     "copyFileId": string;
+    /**
+     * フォルダーIDをコピー
+     */
     "copyFolderId": string;
+    /**
+     * プロフィールURLをコピー
+     */
     "copyProfileUrl": string;
+    /**
+     * ユーザーを検索
+     */
     "searchUser": string;
+    /**
+     * 返信
+     */
     "reply": string;
+    /**
+     * もっと見る
+     */
     "loadMore": string;
+    /**
+     * もっと見る
+     */
     "showMore": string;
+    /**
+     * 閉じる
+     */
     "showLess": string;
+    /**
+     * フォローされました
+     */
     "youGotNewFollower": string;
+    /**
+     * フォローリクエストされました
+     */
     "receiveFollowRequest": string;
+    /**
+     * フォローが承認されました
+     */
     "followRequestAccepted": string;
+    /**
+     * メンション
+     */
     "mention": string;
+    /**
+     * あなた宛て
+     */
     "mentions": string;
+    /**
+     * ダイレクト投稿
+     */
     "directNotes": string;
+    /**
+     * インポートとエクスポート
+     */
     "importAndExport": string;
+    /**
+     * インポート
+     */
     "import": string;
+    /**
+     * エクスポート
+     */
     "export": string;
+    /**
+     * ファイル
+     */
     "files": string;
+    /**
+     * ダウンロード
+     */
     "download": string;
-    "driveFileDeleteConfirm": string;
-    "unfollowConfirm": string;
+    /**
+     * ファイル「{name}」を削除しますか?このファイルを使用した一部のコンテンツも削除されます。
+     */
+    "driveFileDeleteConfirm": ParameterizedString<"name">;
+    /**
+     * {name}のフォローを解除しますか?
+     */
+    "unfollowConfirm": ParameterizedString<"name">;
+    /**
+     * エクスポートをリクエストしました。これには時間がかかる場合があります。エクスポートが終わると、「ドライブ」に追加されます。
+     */
     "exportRequested": string;
+    /**
+     * インポートをリクエストしました。これには時間がかかる場合があります。
+     */
     "importRequested": string;
+    /**
+     * リスト
+     */
     "lists": string;
+    /**
+     * リストはありません
+     */
     "noLists": string;
+    /**
+     * ノート
+     */
     "note": string;
+    /**
+     * ノート
+     */
     "notes": string;
+    /**
+     * フォロー
+     */
     "following": string;
+    /**
+     * フォロワー
+     */
     "followers": string;
+    /**
+     * フォローされています
+     */
     "followsYou": string;
+    /**
+     * リスト作成
+     */
     "createList": string;
+    /**
+     * リストの管理
+     */
     "manageLists": string;
+    /**
+     * エラー
+     */
     "error": string;
+    /**
+     * 問題が発生しました
+     */
     "somethingHappened": string;
+    /**
+     * 再試行
+     */
     "retry": string;
+    /**
+     * ページの読み込みに失敗しました。
+     */
     "pageLoadError": string;
+    /**
+     * これは通常、ネットワークまたはブラウザキャッシュが原因です。キャッシュをクリアするか、しばらく待ってから再度試してください。
+     */
     "pageLoadErrorDescription": string;
+    /**
+     * サーバーの応答がありません。しばらく待ってから再度試してください。
+     */
     "serverIsDead": string;
+    /**
+     * このページを表示するためには、リロードして新しいバージョンのクライアントをご利用ください。
+     */
     "youShouldUpgradeClient": string;
+    /**
+     * リスト名を入力
+     */
     "enterListName": string;
+    /**
+     * プライバシー
+     */
     "privacy": string;
+    /**
+     * フォローを承認制にする
+     */
     "makeFollowManuallyApprove": string;
+    /**
+     * デフォルトの公開範囲
+     */
     "defaultNoteVisibility": string;
+    /**
+     * フォロー
+     */
     "follow": string;
+    /**
+     * フォロー申請
+     */
     "followRequest": string;
+    /**
+     * フォロー申請
+     */
     "followRequests": string;
+    /**
+     * フォロー解除
+     */
     "unfollow": string;
+    /**
+     * フォロー許可待ち
+     */
     "followRequestPending": string;
+    /**
+     * 絵文字を入力
+     */
     "enterEmoji": string;
+    /**
+     * ブースト
+     */
     "renote": string;
+    /**
+     * ブースト解除
+     */
     "unrenote": string;
+    /**
+     * ブーストしました。
+     */
     "renoted": string;
+    /**
+     * 引用。
+     */
     "quoted": string;
+    /**
+     * ブースト解除しました。
+     */
     "rmboost": string;
+    /**
+     * この投稿はブーストできません。
+     */
     "cantRenote": string;
+    /**
+     * ブーストをブーストすることはできません。
+     */
     "cantReRenote": string;
+    /**
+     * 引用
+     */
     "quote": string;
+    /**
+     * チャンネル内ブースト
+     */
     "inChannelRenote": string;
+    /**
+     * チャンネル内引用
+     */
     "inChannelQuote": string;
+    /**
+     * ピン留めされたノート
+     */
     "pinnedNote": string;
+    /**
+     * ピン留め
+     */
     "pinned": string;
+    /**
+     * あなた
+     */
     "you": string;
+    /**
+     * クリックして表示
+     */
     "clickToShow": string;
+    /**
+     * センシティブ
+     */
     "sensitive": string;
+    /**
+     * 追加
+     */
     "add": string;
+    /**
+     * リアクション
+     */
     "reaction": string;
+    /**
+     * リアクション
+     */
     "reactions": string;
+    /**
+     * 絵文字ピッカー
+     */
     "emojiPicker": string;
+    /**
+     * リアクション時にピン留め表示する絵文字を設定できます
+     */
     "pinnedEmojisForReactionSettingDescription": string;
+    /**
+     * 絵文字入力時にピン留め表示する絵文字を設定できます
+     */
     "pinnedEmojisSettingDescription": string;
+    /**
+     * ピッカーの表示
+     */
     "emojiPickerDisplay": string;
+    /**
+     * リアクション設定から上書きする
+     */
     "overwriteFromPinnedEmojisForReaction": string;
+    /**
+     * 全般設定から上書きする
+     */
     "overwriteFromPinnedEmojis": string;
+    /**
+     * ドラッグして並び替え、クリックして削除、+を押して追加します。
+     */
     "reactionSettingDescription2": string;
+    /**
+     * 公開範囲を記憶する
+     */
     "rememberNoteVisibility": string;
+    /**
+     * 添付取り消し
+     */
     "attachCancel": string;
+    /**
+     * ファイルを削除
+     */
+    "deleteFile": string;
+    /**
+     * センシティブとして設定
+     */
     "markAsSensitive": string;
+    /**
+     * センシティブを解除する
+     */
     "unmarkAsSensitive": string;
+    /**
+     * ファイル名を入力
+     */
     "enterFileName": string;
+    /**
+     * ミュート
+     */
     "mute": string;
+    /**
+     * ミュート解除
+     */
     "unmute": string;
+    /**
+     * ブーストをミュート
+     */
     "renoteMute": string;
+    /**
+     * ブーストのミュートを解除
+     */
     "renoteUnmute": string;
+    /**
+     * ブロック
+     */
     "block": string;
+    /**
+     * ブロック解除
+     */
     "unblock": string;
+    /**
+     * ユーザーのすべてのメディアをNSFWとしてマークする
+     */
     "markAsNSFW": string;
+    /**
+     * 凍結
+     */
     "suspend": string;
+    /**
+     * 解凍
+     */
     "unsuspend": string;
+    /**
+     * ブロックしますか?
+     */
     "blockConfirm": string;
+    /**
+     * ブロック解除しますか?
+     */
     "unblockConfirm": string;
+    /**
+     * このアカウントからのすべてのメディアをNSFWとしてマークしてもよろしいですか?
+     */
     "nsfwConfirm": string;
+    /**
+     * このアカウントのすべてのメディアをNSFWとしてマーク解除してもよろしいですか?
+     */
     "unNsfwConfirm": string;
+    /**
+     * 凍結しますか?
+     */
     "suspendConfirm": string;
+    /**
+     * このアカウントを承認してもよろしいですか?
+     */
     "approveConfirm": string;
+    /**
+     * 解凍しますか?
+     */
     "unsuspendConfirm": string;
+    /**
+     * リストを選択
+     */
     "selectList": string;
+    /**
+     * リストを編集
+     */
     "editList": string;
+    /**
+     * チャンネルを選択
+     */
     "selectChannel": string;
+    /**
+     * アンテナを選択
+     */
     "selectAntenna": string;
+    /**
+     * アンテナを編集
+     */
     "editAntenna": string;
+    /**
+     * ウィジェットを選択
+     */
     "selectWidget": string;
+    /**
+     * ウィジェットを編集
+     */
     "editWidgets": string;
+    /**
+     * 編集を終了
+     */
     "editWidgetsExit": string;
+    /**
+     * カスタム絵文字
+     */
     "customEmojis": string;
+    /**
+     * 絵文字
+     */
     "emoji": string;
+    /**
+     * 絵文字
+     */
     "emojis": string;
+    /**
+     * 絵文字名
+     */
     "emojiName": string;
+    /**
+     * 絵文字画像URL
+     */
     "emojiUrl": string;
+    /**
+     * 絵文字を追加
+     */
     "addEmoji": string;
+    /**
+     * おすすめ設定
+     */
     "settingGuide": string;
+    /**
+     * リモートのファイルをキャッシュする
+     */
     "cacheRemoteFiles": string;
+    /**
+     * この設定を有効にすると、リモートファイルをこのサーバーのストレージにキャッシュするようになります。画像の表示が高速になりますが、サーバーのストレージを多く消費します。リモートユーザーがどれほどキャッシュを保持するかは、ロールによるドライブ容量制限によって決定されます。この制限を超えた場合、古いファイルからキャッシュが削除されリンクになります。この設定が無効の場合、リモートのファイルを最初からリンクとして保持しますが、画像のサムネイル生成やユーザーのプライバシー保護のために、default.ymlでproxyRemoteFilesをtrueにすることをお勧めします。
+     */
     "cacheRemoteFilesDescription": string;
+    /**
+     * ファイル管理の🗑️ボタンで全てのキャッシュを削除できます。
+     */
     "youCanCleanRemoteFilesCache": string;
+    /**
+     * リモートのセンシティブなファイルをキャッシュする
+     */
     "cacheRemoteSensitiveFiles": string;
+    /**
+     * この設定を無効にすると、リモートのセンシティブなファイルはキャッシュせず直リンクするようになります。
+     */
     "cacheRemoteSensitiveFilesDescription": string;
+    /**
+     * Botとして設定
+     */
     "flagAsBot": string;
+    /**
+     * このアカウントがプログラムによって運用される場合は、このフラグをオンにします。オンにすると、反応の連鎖を防ぐためのフラグとして他の開発者に役立ったり、Sharkeyのシステム上での扱いがBotに合ったものになります。
+     */
     "flagAsBotDescription": string;
+    /**
+     * にゃああああああああああああああ!!!!!!!!!!!!
+     */
     "flagAsCat": string;
+    /**
+     * にゃにゃにゃ??
+     */
     "flagAsCatDescription": string;
+    /**
+     * 猫語で話す
+     */
     "flagSpeakAsCat": string;
+    /**
+     * 有効にすると、あなたの投稿の 「な」を「にゃ」にします。
+     */
     "flagSpeakAsCatDescription": string;
+    /**
+     * タイムラインにノートへの返信を表示する
+     */
     "flagShowTimelineReplies": string;
+    /**
+     * オンにすると、タイムラインにユーザーのノート以外にもそのユーザーの他のノートへの返信を表示します。
+     */
     "flagShowTimelineRepliesDescription": string;
+    /**
+     * フォロー中ユーザーからのフォロリクを自動承認
+     */
     "autoAcceptFollowed": string;
+    /**
+     * アカウントを追加
+     */
     "addAccount": string;
+    /**
+     * アカウントリストの情報を更新
+     */
     "reloadAccountsList": string;
+    /**
+     * ログインに失敗しました
+     */
     "loginFailed": string;
+    /**
+     * リモートで表示
+     */
     "showOnRemote": string;
+    /**
+     * 全般
+     */
     "general": string;
+    /**
+     * 壁紙
+     */
     "wallpaper": string;
+    /**
+     * 壁紙を設定
+     */
     "setWallpaper": string;
+    /**
+     * 壁紙を削除
+     */
     "removeWallpaper": string;
-    "searchWith": string;
+    /**
+     * 検索: {q}
+     */
+    "searchWith": ParameterizedString<"q">;
+    /**
+     * リストがありません
+     */
     "youHaveNoLists": string;
-    "followConfirm": string;
+    /**
+     * {name}をフォローしますか?
+     */
+    "followConfirm": ParameterizedString<"name">;
+    /**
+     * プロキシアカウント
+     */
     "proxyAccount": string;
+    /**
+     * プロキシアカウントは、特定の条件下でユーザーのリモートフォローを代行するアカウントです。例えば、ユーザーがリモートユーザーをリストに入れたとき、リストに入れられたユーザーを誰もフォローしていないとアクティビティがサーバーに配達されないため、代わりにプロキシアカウントがフォローするようにします。
+     */
     "proxyAccountDescription": string;
+    /**
+     * ホスト
+     */
     "host": string;
+    /**
+     * ユーザーを選択
+     */
     "selectUser": string;
+    /**
+     * 宛先
+     */
     "recipient": string;
+    /**
+     * 注釈
+     */
     "annotation": string;
+    /**
+     * 連合
+     */
     "federation": string;
+    /**
+     * サーバー
+     */
     "instances": string;
+    /**
+     * 初観測
+     */
     "registeredAt": string;
+    /**
+     * 直近のリクエスト受信
+     */
     "latestRequestReceivedAt": string;
+    /**
+     * 直近のステータス
+     */
     "latestStatus": string;
+    /**
+     * ストレージ使用量
+     */
     "storageUsage": string;
+    /**
+     * チャート
+     */
     "charts": string;
+    /**
+     * 1時間ごと
+     */
     "perHour": string;
+    /**
+     * 1日ごと
+     */
     "perDay": string;
+    /**
+     * アクティビティの配送を停止
+     */
     "stopActivityDelivery": string;
+    /**
+     * このインスタンスをブロック
+     */
     "blockThisInstance": string;
+    /**
+     * インスタンスをサイレンス
+     */
     "silenceThisInstance": string;
+    /**
+     * 操作
+     */
     "operations": string;
+    /**
+     * ソフトウェア
+     */
     "software": string;
+    /**
+     * バージョン
+     */
     "version": string;
+    /**
+     * メタデータ
+     */
     "metadata": string;
-    "withNFiles": string;
+    /**
+     * {n}つのファイル
+     */
+    "withNFiles": ParameterizedString<"n">;
+    /**
+     * モニター
+     */
     "monitor": string;
+    /**
+     * ジョブキュー
+     */
     "jobQueue": string;
+    /**
+     * CPUとメモリ
+     */
     "cpuAndMemory": string;
+    /**
+     * ネットワーク
+     */
     "network": string;
+    /**
+     * ディスク
+     */
     "disk": string;
+    /**
+     * サーバー情報
+     */
     "instanceInfo": string;
+    /**
+     * 統計
+     */
     "statistics": string;
+    /**
+     * キューをクリア
+     */
     "clearQueue": string;
+    /**
+     * キューをクリアしますか?
+     */
     "clearQueueConfirmTitle": string;
+    /**
+     * 未配達の投稿は配送されなくなります。通常この操作を行う必要はありません。
+     */
     "clearQueueConfirmText": string;
+    /**
+     * キャッシュをクリア
+     */
     "clearCachedFiles": string;
+    /**
+     * キャッシュされたリモートファイルをすべて削除しますか?
+     */
     "clearCachedFilesConfirm": string;
+    /**
+     * ブロックしたサーバー
+     */
     "blockedInstances": string;
+    /**
+     * ブロックしたいサーバーのホストを改行で区切って設定します。ブロックされたサーバーは、このインスタンスとやり取りできなくなります。
+     */
     "blockedInstancesDescription": string;
+    /**
+     * サイレンスしたサーバー
+     */
     "silencedInstances": string;
+    /**
+     * サイレンスしたいインスタンスのホストを改行で区切って設定します。サイレンスされたインスタンスに所属するアカウントはすべて「サイレンス」として扱われ、フォローがすべてリクエストになり、フォロワーでないローカルアカウントにはメンションできなくなります。ブロックしたインスタンスには影響しません。
+     */
     "silencedInstancesDescription": string;
+    /**
+     * ミュートとブロック
+     */
     "muteAndBlock": string;
+    /**
+     * ミュートしたユーザー
+     */
     "mutedUsers": string;
+    /**
+     * ブロックしたユーザー
+     */
     "blockedUsers": string;
+    /**
+     * ユーザーはいません
+     */
     "noUsers": string;
+    /**
+     * プロフィールを編集
+     */
     "editProfile": string;
+    /**
+     * このノートを削除しますか?
+     */
     "noteDeleteConfirm": string;
+    /**
+     * これ以上ピン留めできません
+     */
     "pinLimitExceeded": string;
+    /**
+     * Sharkeyのインストールが完了しました!管理者アカウントを作成しましょう。
+     */
     "intro": string;
+    /**
+     * 完了
+     */
     "done": string;
+    /**
+     * 処理中
+     */
     "processing": string;
+    /**
+     * プレビュー
+     */
     "preview": string;
+    /**
+     * デフォルト
+     */
     "default": string;
-    "defaultValueIs": string;
+    /**
+     * デフォルト: {value}
+     */
+    "defaultValueIs": ParameterizedString<"value">;
+    /**
+     * 絵文字はありません
+     */
     "noCustomEmojis": string;
+    /**
+     * ジョブはありません
+     */
     "noJobs": string;
+    /**
+     * 連合中
+     */
     "federating": string;
+    /**
+     * ブロック中
+     */
     "blocked": string;
+    /**
+     * 配信停止
+     */
     "suspended": string;
+    /**
+     * 全て
+     */
     "all": string;
+    /**
+     * 購読中
+     */
     "subscribing": string;
+    /**
+     * 配信中
+     */
     "publishing": string;
+    /**
+     * 応答なし
+     */
     "notResponding": string;
+    /**
+     * サーバーのフォロー
+     */
     "instanceFollowing": string;
+    /**
+     * サーバーのフォロワー
+     */
     "instanceFollowers": string;
+    /**
+     * サーバーのユーザー
+     */
     "instanceUsers": string;
+    /**
+     * パスワードを変更
+     */
     "changePassword": string;
+    /**
+     * セキュリティ
+     */
     "security": string;
+    /**
+     * 入力が一致しません。
+     */
     "retypedNotMatch": string;
+    /**
+     * 現在のパスワード
+     */
     "currentPassword": string;
+    /**
+     * 新しいパスワード
+     */
     "newPassword": string;
+    /**
+     * 新しいパスワード(再入力)
+     */
     "newPasswordRetype": string;
+    /**
+     * ファイルを添付
+     */
     "attachFile": string;
+    /**
+     * もっと!
+     */
     "more": string;
+    /**
+     * ハイライト
+     */
     "featured": string;
+    /**
+     * ユーザー名かユーザーID
+     */
     "usernameOrUserId": string;
+    /**
+     * ユーザーが見つかりません
+     */
     "noSuchUser": string;
+    /**
+     * 照会
+     */
     "lookup": string;
+    /**
+     * お知らせ
+     */
     "announcements": string;
+    /**
+     * 画像URL
+     */
     "imageUrl": string;
+    /**
+     * 削除
+     */
     "remove": string;
+    /**
+     * 削除しました
+     */
     "removed": string;
-    "removeAreYouSure": string;
-    "deleteAreYouSure": string;
+    /**
+     * 「{x}」を削除しますか?
+     */
+    "removeAreYouSure": ParameterizedString<"x">;
+    /**
+     * 「{x}」を削除しますか?
+     */
+    "deleteAreYouSure": ParameterizedString<"x">;
+    /**
+     * リセットしますか?
+     */
     "resetAreYouSure": string;
+    /**
+     * よろしいですか?
+     */
     "areYouSure": string;
+    /**
+     * 保存しました
+     */
     "saved": string;
+    /**
+     * チャット
+     */
     "messaging": string;
+    /**
+     * アップロード
+     */
     "upload": string;
+    /**
+     * オリジナル画像を保持
+     */
     "keepOriginalUploading": string;
+    /**
+     * 画像をアップロードする時にオリジナル版を保持します。オフにするとアップロード時にブラウザでWeb公開用画像を生成します。
+     */
     "keepOriginalUploadingDescription": string;
+    /**
+     * ドライブから
+     */
     "fromDrive": string;
+    /**
+     * URLから
+     */
     "fromUrl": string;
+    /**
+     * URLアップロード
+     */
     "uploadFromUrl": string;
+    /**
+     * アップロードしたいファイルのURL
+     */
     "uploadFromUrlDescription": string;
+    /**
+     * アップロードをリクエストしました
+     */
     "uploadFromUrlRequested": string;
+    /**
+     * アップロードが完了するまで時間がかかる場合があります。
+     */
     "uploadFromUrlMayTakeTime": string;
+    /**
+     * みつける
+     */
     "explore": string;
+    /**
+     * 既読
+     */
     "messageRead": string;
+    /**
+     * これより過去の履歴はありません
+     */
     "noMoreHistory": string;
+    /**
+     * チャットを開始
+     */
     "startMessaging": string;
-    "nUsersRead": string;
-    "agreeTo": string;
+    /**
+     * {n}人が読みました
+     */
+    "nUsersRead": ParameterizedString<"n">;
+    /**
+     * {0}に同意
+     */
+    "agreeTo": ParameterizedString<"0">;
+    /**
+     * 同意する
+     */
     "agree": string;
+    /**
+     * 下記に同意する
+     */
     "agreeBelow": string;
+    /**
+     * 基本的な注意事項
+     */
     "basicNotesBeforeCreateAccount": string;
+    /**
+     * 利用規約
+     */
     "termsOfService": string;
+    /**
+     * 始める
+     */
     "start": string;
+    /**
+     * ホーム
+     */
     "home": string;
+    /**
+     * リモートユーザーのため、情報が不完全です。
+     */
     "remoteUserCaution": string;
+    /**
+     * アクティビティ
+     */
     "activity": string;
+    /**
+     * 画像
+     */
     "images": string;
+    /**
+     * 画像
+     */
     "image": string;
+    /**
+     * 誕生日
+     */
     "birthday": string;
-    "yearsOld": string;
+    /**
+     * {age}歳
+     */
+    "yearsOld": ParameterizedString<"age">;
+    /**
+     * 登録日
+     */
     "registeredDate": string;
+    /**
+     * 場所
+     */
     "location": string;
+    /**
+     * テーマ
+     */
     "theme": string;
+    /**
+     * ライトモードで使うテーマ
+     */
     "themeForLightMode": string;
+    /**
+     * ダークモードで使うテーマ
+     */
     "themeForDarkMode": string;
+    /**
+     * ライト
+     */
     "light": string;
+    /**
+     * ダーク
+     */
     "dark": string;
+    /**
+     * 明るいテーマ
+     */
     "lightThemes": string;
+    /**
+     * 暗いテーマ
+     */
     "darkThemes": string;
+    /**
+     * デバイスのダークモードと同期する
+     */
     "syncDeviceDarkMode": string;
+    /**
+     * ドライブ
+     */
     "drive": string;
+    /**
+     * ファイル名
+     */
     "fileName": string;
+    /**
+     * ファイルを選択
+     */
     "selectFile": string;
+    /**
+     * ファイルを選択
+     */
     "selectFiles": string;
+    /**
+     * フォルダーを選択
+     */
     "selectFolder": string;
+    /**
+     * フォルダーを選択
+     */
     "selectFolders": string;
+    /**
+     * ファイル名を変更
+     */
     "renameFile": string;
+    /**
+     * フォルダー名
+     */
     "folderName": string;
+    /**
+     * フォルダーを作成
+     */
     "createFolder": string;
+    /**
+     * フォルダー名を変更
+     */
     "renameFolder": string;
+    /**
+     * フォルダーを削除
+     */
     "deleteFolder": string;
+    /**
+     * フォルダー
+     */
     "folder": string;
+    /**
+     * ファイルを追加
+     */
     "addFile": string;
+    /**
+     * ドライブは空です
+     */
     "emptyDrive": string;
+    /**
+     * フォルダーは空です
+     */
     "emptyFolder": string;
+    /**
+     * 削除できません
+     */
     "unableToDelete": string;
+    /**
+     * 新しいファイル名を入力してください
+     */
     "inputNewFileName": string;
+    /**
+     * 新しいキャプションを入力してください
+     */
     "inputNewDescription": string;
+    /**
+     * 新しいフォルダ名を入力してください
+     */
     "inputNewFolderName": string;
+    /**
+     * 移動先のフォルダーは、移動するフォルダーのサブフォルダーです。
+     */
     "circularReferenceFolder": string;
+    /**
+     * このフォルダは空でないため、削除できません。
+     */
     "hasChildFilesOrFolders": string;
+    /**
+     * URLをコピー
+     */
     "copyUrl": string;
+    /**
+     * 名前を変更
+     */
     "rename": string;
+    /**
+     * アイコン
+     */
     "avatar": string;
+    /**
+     * バナー
+     */
     "banner": string;
+    /**
+     * 背景
+     */
     "background": string;
+    /**
+     * センシティブなメディアの表示
+     */
     "displayOfSensitiveMedia": string;
+    /**
+     * サーバーとの接続が失われたとき
+     */
     "whenServerDisconnected": string;
+    /**
+     * サーバーから切断されました
+     */
     "disconnectedFromServer": string;
+    /**
+     * リロード
+     */
     "reload": string;
+    /**
+     * なにもしない
+     */
     "doNothing": string;
+    /**
+     * リロードしますか?
+     */
     "reloadConfirm": string;
+    /**
+     * ウォッチ
+     */
     "watch": string;
+    /**
+     * ウォッチ解除
+     */
     "unwatch": string;
+    /**
+     * 許可
+     */
     "accept": string;
+    /**
+     * 拒否
+     */
     "reject": string;
+    /**
+     * 通常
+     */
     "normal": string;
+    /**
+     * サーバー名
+     */
     "instanceName": string;
+    /**
+     * サーバーの紹介
+     */
     "instanceDescription": string;
+    /**
+     * 管理者の名前
+     */
     "maintainerName": string;
+    /**
+     * 管理者のメールアドレス
+     */
     "maintainerEmail": string;
+    /**
+     * 利用規約URL
+     */
     "tosUrl": string;
+    /**
+     * 今年
+     */
     "thisYear": string;
+    /**
+     * 今月
+     */
     "thisMonth": string;
+    /**
+     * 今日
+     */
     "today": string;
-    "dayX": string;
-    "monthX": string;
-    "yearX": string;
+    /**
+     * {day}日
+     */
+    "dayX": ParameterizedString<"day">;
+    /**
+     * {month}月
+     */
+    "monthX": ParameterizedString<"month">;
+    /**
+     * {year}年
+     */
+    "yearX": ParameterizedString<"year">;
+    /**
+     * ページ
+     */
     "pages": string;
+    /**
+     * 連携
+     */
     "integration": string;
+    /**
+     * 接続する
+     */
     "connectService": string;
+    /**
+     * 切断する
+     */
     "disconnectService": string;
+    /**
+     * ローカルタイムラインを有効にする
+     */
     "enableLocalTimeline": string;
+    /**
+     * グローバルタイムラインを有効にする
+     */
     "enableGlobalTimeline": string;
+    /**
+     * これらのタイムラインを無効化しても、利便性のため管理者およびモデレーターは引き続き利用することができます。
+     */
     "disablingTimelinesInfo": string;
+    /**
+     * 登録
+     */
     "registration": string;
+    /**
+     * 誰でも新規登録できるようにする
+     */
     "enableRegistration": string;
+    /**
+     * 招待
+     */
     "invite": string;
+    /**
+     * ローカルユーザーひとりあたりのドライブ容量
+     */
     "driveCapacityPerLocalAccount": string;
+    /**
+     * リモートユーザーひとりあたりのドライブ容量
+     */
     "driveCapacityPerRemoteAccount": string;
+    /**
+     * メガバイト単位
+     */
     "inMb": string;
+    /**
+     * バナー画像のURL
+     */
     "bannerUrl": string;
+    /**
+     * 背景画像のURL
+     */
     "backgroundImageUrl": string;
+    /**
+     * 基本情報
+     */
     "basicInfo": string;
+    /**
+     * ピン留めユーザー
+     */
     "pinnedUsers": string;
+    /**
+     * 「みつける」ページなどにピン留めしたいユーザーを改行で区切って記述します。
+     */
     "pinnedUsersDescription": string;
+    /**
+     * ピン留めページ
+     */
     "pinnedPages": string;
+    /**
+     * サーバーのトップページにピン留めしたいページのパスを改行で区切って記述します。
+     */
     "pinnedPagesDescription": string;
+    /**
+     * ピン留めするクリップのID
+     */
     "pinnedClipId": string;
+    /**
+     * ピン留めされたノート
+     */
     "pinnedNotes": string;
+    /**
+     * hCaptcha
+     */
     "hcaptcha": string;
+    /**
+     * hCaptchaを有効にする
+     */
     "enableHcaptcha": string;
+    /**
+     * サイトキー
+     */
     "hcaptchaSiteKey": string;
+    /**
+     * シークレットキー
+     */
     "hcaptchaSecretKey": string;
+    /**
+     * mCaptcha
+     */
+    "mcaptcha": string;
+    /**
+     * mCaptchaを有効にする
+     */
+    "enableMcaptcha": string;
+    /**
+     * サイトキー
+     */
+    "mcaptchaSiteKey": string;
+    /**
+     * シークレットキー
+     */
+    "mcaptchaSecretKey": string;
+    /**
+     * mCaptchaのインスタンスのURL
+     */
+    "mcaptchaInstanceUrl": string;
+    /**
+     * reCAPTCHA
+     */
     "recaptcha": string;
+    /**
+     * reCAPTCHAを有効にする
+     */
     "enableRecaptcha": string;
+    /**
+     * サイトキー
+     */
     "recaptchaSiteKey": string;
+    /**
+     * シークレットキー
+     */
     "recaptchaSecretKey": string;
+    /**
+     * Turnstile
+     */
     "turnstile": string;
+    /**
+     * Turnstileを有効にする
+     */
     "enableTurnstile": string;
+    /**
+     * サイトキー
+     */
     "turnstileSiteKey": string;
+    /**
+     * シークレットキー
+     */
     "turnstileSecretKey": string;
+    /**
+     * 複数のCaptchaを使用すると干渉を起こす可能性があります。他のCaptchaを無効にしますか?キャンセルして複数のCaptchaを有効化したままにすることも可能です。
+     */
     "avoidMultiCaptchaConfirm": string;
+    /**
+     * アンテナ
+     */
     "antennas": string;
+    /**
+     * アンテナの管理
+     */
     "manageAntennas": string;
+    /**
+     * 名前
+     */
     "name": string;
+    /**
+     * 受信ソース
+     */
     "antennaSource": string;
+    /**
+     * 受信キーワード
+     */
     "antennaKeywords": string;
+    /**
+     * 除外キーワード
+     */
     "antennaExcludeKeywords": string;
+    /**
+     * スペースで区切るとAND指定になり、改行で区切るとOR指定になります
+     */
     "antennaKeywordsDescription": string;
+    /**
+     * 新しいノートを通知する
+     */
     "notifyAntenna": string;
+    /**
+     * ファイルが添付されたノートのみ
+     */
     "withFileAntenna": string;
+    /**
+     * ブラウザへのプッシュ通知を有効にする
+     */
     "enableServiceworker": string;
+    /**
+     * ユーザー名を改行で区切って指定します
+     */
     "antennaUsersDescription": string;
+    /**
+     * 大文字小文字を区別する
+     */
     "caseSensitive": string;
+    /**
+     * 返信を含む
+     */
     "withReplies": string;
+    /**
+     * 次のアカウントに接続されています
+     */
     "connectedTo": string;
+    /**
+     * 投稿と返信
+     */
     "notesAndReplies": string;
+    /**
+     * ファイル付き
+     */
     "withFiles": string;
+    /**
+     * サイレンス
+     */
     "silence": string;
+    /**
+     * サイレンスしますか?
+     */
     "silenceConfirm": string;
+    /**
+     * サイレンス解除
+     */
     "unsilence": string;
+    /**
+     * サイレンス解除しますか?
+     */
     "unsilenceConfirm": string;
+    /**
+     * 人気のユーザー
+     */
     "popularUsers": string;
+    /**
+     * 最近投稿したユーザー
+     */
     "recentlyUpdatedUsers": string;
+    /**
+     * 最近登録したユーザー
+     */
     "recentlyRegisteredUsers": string;
+    /**
+     * 最近発見されたユーザー
+     */
     "recentlyDiscoveredUsers": string;
-    "exploreUsersCount": string;
+    /**
+     * {count}のユーザーがいます
+     */
+    "exploreUsersCount": ParameterizedString<"count">;
+    /**
+     * Fediverseを探索
+     */
     "exploreFediverse": string;
+    /**
+     * 人気のタグ
+     */
     "popularTags": string;
+    /**
+     * リスト
+     */
     "userList": string;
+    /**
+     * 情報
+     */
     "about": string;
+    /**
+     * Sharkeyについて
+     */
     "aboutMisskey": string;
+    /**
+     * 管理者
+     */
     "administrator": string;
+    /**
+     * 確認コード
+     */
     "token": string;
+    /**
+     * 二要素認証
+     */
     "2fa": string;
+    /**
+     * 二要素認証のセットアップ
+     */
     "setupOf2fa": string;
+    /**
+     * 認証アプリ
+     */
     "totp": string;
+    /**
+     * 認証アプリを使ってワンタイムパスワードを入力
+     */
     "totpDescription": string;
+    /**
+     * モデレーター
+     */
     "moderator": string;
+    /**
+     * モデレーション
+     */
     "moderation": string;
+    /**
+     * モデレーションノート
+     */
     "moderationNote": string;
+    /**
+     * モデレーションノートを追加する
+     */
     "addModerationNote": string;
+    /**
+     * モデログ
+     */
     "moderationLogs": string;
-    "nUsersMentioned": string;
+    /**
+     * {n}人が投稿
+     */
+    "nUsersMentioned": ParameterizedString<"n">;
+    /**
+     * セキュリティキー・パスキー
+     */
     "securityKeyAndPasskey": string;
+    /**
+     * セキュリティキー
+     */
     "securityKey": string;
+    /**
+     * 最後の使用
+     */
     "lastUsed": string;
-    "lastUsedAt": string;
+    /**
+     * 最後の使用: {t}
+     */
+    "lastUsedAt": ParameterizedString<"t">;
+    /**
+     * 登録を解除
+     */
     "unregister": string;
+    /**
+     * パスワードレスログイン
+     */
     "passwordLessLogin": string;
+    /**
+     * パスワードを使用せず、セキュリティキーやパスキーなどのみでログインします
+     */
     "passwordLessLoginDescription": string;
+    /**
+     * パスワードをリセット
+     */
     "resetPassword": string;
-    "newPasswordIs": string;
+    /**
+     * 新しいパスワードは「{password}」です
+     */
+    "newPasswordIs": ParameterizedString<"password">;
+    /**
+     * UIのアニメーションを減らす
+     */
     "reduceUiAnimation": string;
+    /**
+     * 共有
+     */
     "share": string;
+    /**
+     * 見つかりません
+     */
     "notFound": string;
+    /**
+     * 指定されたURLに該当するページはありませんでした。
+     */
     "notFoundDescription": string;
+    /**
+     * 既定アップロード先
+     */
     "uploadFolder": string;
+    /**
+     * すべての通知を既読にする
+     */
     "markAsReadAllNotifications": string;
+    /**
+     * すべての投稿を既読にする
+     */
     "markAsReadAllUnreadNotes": string;
+    /**
+     * すべてのチャットを既読にする
+     */
     "markAsReadAllTalkMessages": string;
+    /**
+     * ヘルプ
+     */
     "help": string;
+    /**
+     * ここにメッセージを入力
+     */
     "inputMessageHere": string;
+    /**
+     * 閉じる
+     */
     "close": string;
+    /**
+     * 招待
+     */
     "invites": string;
+    /**
+     * メンバー
+     */
     "members": string;
+    /**
+     * 譲渡
+     */
     "transfer": string;
+    /**
+     * タイトル
+     */
     "title": string;
+    /**
+     * テキスト
+     */
     "text": string;
+    /**
+     * 有効にする
+     */
     "enable": string;
+    /**
+     * 次
+     */
     "next": string;
+    /**
+     * 再入力
+     */
     "retype": string;
-    "noteOf": string;
+    /**
+     * {user}のノート
+     */
+    "noteOf": ParameterizedString<"user">;
+    /**
+     * すべての返信の内容を表示する
+     */
     "expandAllCws": string;
+    /**
+     * すべての返信の内容を隠す
+     */
     "collapseAllCws": string;
+    /**
+     * 引用付き
+     */
     "quoteAttached": string;
+    /**
+     * 引用として添付しますか?
+     */
     "quoteQuestion": string;
+    /**
+     * まだチャットはありません
+     */
     "noMessagesYet": string;
+    /**
+     * 新しいメッセージがあります
+     */
     "newMessageExists": string;
+    /**
+     * メッセージに添付できるファイルはひとつです
+     */
     "onlyOneFileCanBeAttached": string;
+    /**
+     * 続行する前に、サインアップまたはサインインが必要です
+     */
     "signinRequired": string;
+    /**
+     * 招待
+     */
     "invitations": string;
+    /**
+     * 招待コード
+     */
     "invitationCode": string;
+    /**
+     * 確認しています
+     */
     "checking": string;
+    /**
+     * 利用できます
+     */
     "available": string;
+    /**
+     * 利用できません
+     */
     "unavailable": string;
+    /**
+     * a~z、A~Z、0~9、_が使えます
+     */
     "usernameInvalidFormat": string;
+    /**
+     * 短すぎます
+     */
     "tooShort": string;
+    /**
+     * 長すぎます
+     */
     "tooLong": string;
+    /**
+     * 弱いパスワード
+     */
     "weakPassword": string;
+    /**
+     * 普通のパスワード
+     */
     "normalPassword": string;
+    /**
+     * 強いパスワード
+     */
     "strongPassword": string;
+    /**
+     * 一致しました
+     */
     "passwordMatched": string;
+    /**
+     * 一致していません
+     */
     "passwordNotMatched": string;
-    "signinWith": string;
+    /**
+     * {x}でログイン
+     */
+    "signinWith": ParameterizedString<"x">;
+    /**
+     * ログインできませんでした。ユーザー名とパスワードを確認してください。
+     */
     "signinFailed": string;
+    /**
+     * もしくは
+     */
     "or": string;
+    /**
+     * 言語
+     */
     "language": string;
+    /**
+     * UIの表示言語
+     */
     "uiLanguage": string;
-    "aboutX": string;
+    /**
+     * {x}について
+     */
+    "aboutX": ParameterizedString<"x">;
+    /**
+     * 絵文字のスタイル
+     */
     "emojiStyle": string;
+    /**
+     * ネイティブ
+     */
     "native": string;
+    /**
+     * メニューをドロワーで表示しない
+     */
     "disableDrawer": string;
+    /**
+     * ノートのアクションをホバー時のみ表示する
+     */
     "showNoteActionsOnlyHover": string;
+    /**
+     * 履歴はありません
+     */
     "noHistory": string;
+    /**
+     * ログイン履歴
+     */
     "signinHistory": string;
+    /**
+     * 高度なMFMを有効にする
+     */
     "enableAdvancedMfm": string;
+    /**
+     * 動きのあるMFMを有効にする
+     */
     "enableAnimatedMfm": string;
+    /**
+     * やっています
+     */
     "doing": string;
+    /**
+     * カテゴリ
+     */
     "category": string;
+    /**
+     * タグ
+     */
     "tags": string;
+    /**
+     * このドキュメントのソース
+     */
     "docSource": string;
+    /**
+     * アカウントを作成
+     */
     "createAccount": string;
+    /**
+     * 既存のアカウント
+     */
     "existingAccount": string;
+    /**
+     * 再生成
+     */
     "regenerate": string;
+    /**
+     * フォントサイズ
+     */
     "fontSize": string;
+    /**
+     * コーナーの丸み
+     */
     "cornerRadius": string;
+    /**
+     * 画像が1枚のみのメディアリストの高さ
+     */
     "mediaListWithOneImageAppearance": string;
-    "limitTo": string;
+    /**
+     * {x}を上限に
+     */
+    "limitTo": ParameterizedString<"x">;
+    /**
+     * フォロー申請はありません
+     */
     "noFollowRequests": string;
+    /**
+     * 画像を新しいタブで開く
+     */
     "openImageInNewTab": string;
+    /**
+     * ダッシュボード
+     */
     "dashboard": string;
+    /**
+     * ローカル
+     */
     "local": string;
+    /**
+     * リモート
+     */
     "remote": string;
+    /**
+     * 合計
+     */
     "total": string;
+    /**
+     * 前週比
+     */
     "weekOverWeekChanges": string;
+    /**
+     * 前日比
+     */
     "dayOverDayChanges": string;
+    /**
+     * アピアランス
+     */
     "appearance": string;
+    /**
+     * クライアント設定
+     */
     "clientSettings": string;
+    /**
+     * アカウント設定
+     */
     "accountSettings": string;
+    /**
+     * プロモーション
+     */
     "promotion": string;
+    /**
+     * プロモート
+     */
     "promote": string;
+    /**
+     * 日数
+     */
     "numberOfDays": string;
+    /**
+     * このノートを非表示
+     */
     "hideThisNote": string;
+    /**
+     * タイムラインにおすすめのノートを表示する
+     */
     "showFeaturedNotesInTimeline": string;
+    /**
+     * オブジェクトストレージ
+     */
     "objectStorage": string;
+    /**
+     * オブジェクトストレージを使用
+     */
     "useObjectStorage": string;
+    /**
+     * Base URL
+     */
     "objectStorageBaseUrl": string;
+    /**
+     * 参照に使用するURL。CDNやProxyを使用している場合はそのURL、S3: 'https://<bucket>.s3.amazonaws.com'、GCS等: 'https://storage.googleapis.com/<bucket>'。
+     */
     "objectStorageBaseUrlDesc": string;
+    /**
+     * Bucket
+     */
     "objectStorageBucket": string;
+    /**
+     * 使用サービスのbucket名を指定してください。
+     */
     "objectStorageBucketDesc": string;
+    /**
+     * Prefix
+     */
     "objectStoragePrefix": string;
+    /**
+     * このprefixのディレクトリ下に格納されます。
+     */
     "objectStoragePrefixDesc": string;
+    /**
+     * Endpoint
+     */
     "objectStorageEndpoint": string;
+    /**
+     * S3の場合は空、それ以外の場合は各サービスのendpointを指定してください。'<host>'または'<host>:<port>'のように指定します。
+     */
     "objectStorageEndpointDesc": string;
+    /**
+     * Region
+     */
     "objectStorageRegion": string;
+    /**
+     * 'xx-east-1'のようなregionを指定してください。使用サービスにregionの概念がない場合は'us-east-1'にしてください。AWS設定ファイルまたは環境変数を参照する場合は空にしてください。
+     */
     "objectStorageRegionDesc": string;
+    /**
+     * SSLを使用する
+     */
     "objectStorageUseSSL": string;
+    /**
+     * API接続にhttpsを使用しない場合はオフにしてください
+     */
     "objectStorageUseSSLDesc": string;
+    /**
+     * Proxyを利用する
+     */
     "objectStorageUseProxy": string;
+    /**
+     * API接続にproxyを利用しない場合はオフにしてください
+     */
     "objectStorageUseProxyDesc": string;
+    /**
+     * アップロード時に'public-read'を設定する
+     */
     "objectStorageSetPublicRead": string;
+    /**
+     * s3ForcePathStyleを有効にすると、バケット名をURLのホスト名ではなくパスの一部として指定することを強制します。セルフホストされたMinioなどの使用時に有効にする必要がある場合があります。
+     */
     "s3ForcePathStyleDesc": string;
+    /**
+     * DeepLX-JS を使用する (認証キーなし)
+     */
+    "deeplFreeMode": string;
+    /**
+     * ヘルプが必要ですか? DeepLX-JSのセットアップ方法については、ドキュメントを参照してください。
+     */
+    "deeplFreeModeDescription": string;
+    /**
+     * サーバーログ
+     */
     "serverLogs": string;
+    /**
+     * 全て削除
+     */
     "deleteAll": string;
+    /**
+     * タイムライン上部に投稿フォームを表示する
+     */
     "showFixedPostForm": string;
+    /**
+     * タイムライン上部に投稿フォームを表示する(チャンネル)
+     */
     "showFixedPostFormInChannel": string;
+    /**
+     * フォローする際、デフォルトで返信をTLに含むようにする
+     */
     "withRepliesByDefaultForNewlyFollowed": string;
+    /**
+     * 新しいノートがあります
+     */
     "newNoteRecived": string;
+    /**
+     * サウンド
+     */
     "sounds": string;
+    /**
+     * サウンド
+     */
     "sound": string;
+    /**
+     * 聴く
+     */
     "listen": string;
+    /**
+     * なし
+     */
     "none": string;
+    /**
+     * ページで表示
+     */
     "showInPage": string;
+    /**
+     * ポップアウト
+     */
     "popout": string;
+    /**
+     * 音量
+     */
     "volume": string;
+    /**
+     * マスター音量
+     */
     "masterVolume": string;
+    /**
+     * サウンドを出力しない
+     */
     "notUseSound": string;
+    /**
+     * Misskeyがアクティブな時のみサウンドを出力する
+     */
     "useSoundOnlyWhenActive": string;
+    /**
+     * 詳細
+     */
     "details": string;
+    /**
+     * 絵文字を選択
+     */
     "chooseEmoji": string;
+    /**
+     * 操作を完了できません
+     */
     "unableToProcess": string;
+    /**
+     * 最近使用
+     */
     "recentUsed": string;
+    /**
+     * インストール
+     */
     "install": string;
+    /**
+     * アンインストール
+     */
     "uninstall": string;
+    /**
+     * インストールされたアプリ
+     */
     "installedApps": string;
+    /**
+     * ありません
+     */
     "nothing": string;
+    /**
+     * インストール日時
+     */
     "installedDate": string;
+    /**
+     * 最終使用日時
+     */
     "lastUsedDate": string;
+    /**
+     * 状態
+     */
     "state": string;
+    /**
+     * ソート
+     */
     "sort": string;
+    /**
+     * 昇順
+     */
     "ascendingOrder": string;
+    /**
+     * 降順
+     */
     "descendingOrder": string;
+    /**
+     * スクラッチパッド
+     */
     "scratchpad": string;
+    /**
+     * スクラッチパッドは、AiScriptの実験環境を提供します。Sharkeyと対話するコードの記述、実行、結果の確認ができます。
+     */
     "scratchpadDescription": string;
+    /**
+     * 出力
+     */
     "output": string;
+    /**
+     * スクリプト
+     */
     "script": string;
+    /**
+     * Pagesのスクリプトを無効にする
+     */
     "disablePagesScript": string;
+    /**
+     * リモートユーザー情報の更新
+     */
     "updateRemoteUser": string;
+    /**
+     * アイコンを解除
+     */
     "unsetUserAvatar": string;
+    /**
+     * アイコンを解除しますか?
+     */
     "unsetUserAvatarConfirm": string;
+    /**
+     * バナーを解除
+     */
     "unsetUserBanner": string;
+    /**
+     * バナーを解除しますか?
+     */
     "unsetUserBannerConfirm": string;
+    /**
+     * すべてのファイルを削除
+     */
     "deleteAllFiles": string;
+    /**
+     * すべてのファイルを削除しますか?
+     */
     "deleteAllFilesConfirm": string;
+    /**
+     * フォローを全解除
+     */
     "removeAllFollowing": string;
-    "removeAllFollowingDescription": string;
+    /**
+     * {host}からのフォローをすべて解除します。そのサーバーがもう存在しなくなった場合などに実行してください。
+     */
+    "removeAllFollowingDescription": ParameterizedString<"host">;
+    /**
+     * このユーザーは凍結されています。
+     */
     "userSuspended": string;
+    /**
+     * このユーザーはサイレンスされています。
+     */
     "userSilenced": string;
+    /**
+     * アカウントが凍結されています
+     */
     "yourAccountSuspendedTitle": string;
+    /**
+     * このアカウントは、サーバーの利用規約に違反したなどの理由により、凍結されています。詳細については管理者までお問い合わせください。新しいアカウントを作らないでください。
+     */
     "yourAccountSuspendedDescription": string;
+    /**
+     * トークンが無効です
+     */
     "tokenRevoked": string;
+    /**
+     * ログイントークンが失効しています。ログインし直してください。
+     */
     "tokenRevokedDescription": string;
+    /**
+     * アカウントは削除されています
+     */
     "accountDeleted": string;
+    /**
+     * このアカウントは削除されています。
+     */
     "accountDeletedDescription": string;
+    /**
+     * メニュー
+     */
     "menu": string;
+    /**
+     * 分割線
+     */
     "divider": string;
+    /**
+     * 項目を追加
+     */
     "addItem": string;
+    /**
+     * 並び替え
+     */
     "rearrange": string;
+    /**
+     * リレー
+     */
     "relays": string;
+    /**
+     * リレーの追加
+     */
     "addRelay": string;
+    /**
+     * inboxのURL
+     */
     "inboxUrl": string;
+    /**
+     * 追加済みのリレー
+     */
     "addedRelays": string;
+    /**
+     * プッシュ通知を行うには有効にする必要があります。
+     */
     "serviceworkerInfo": string;
+    /**
+     * 削除された投稿
+     */
     "deletedNote": string;
+    /**
+     * 非公開の投稿
+     */
     "invisibleNote": string;
+    /**
+     * 自動でもっと見る
+     */
     "enableInfiniteScroll": string;
+    /**
+     * 公開範囲
+     */
     "visibility": string;
+    /**
+     * アンケート
+     */
     "poll": string;
+    /**
+     * 内容を隠す
+     */
     "useCw": string;
+    /**
+     * プレイヤーを開く
+     */
     "enablePlayer": string;
+    /**
+     * プレイヤーを閉じる
+     */
     "disablePlayer": string;
+    /**
+     * ポストを展開する
+     */
     "expandTweet": string;
+    /**
+     * テーマエディター
+     */
     "themeEditor": string;
+    /**
+     * 説明
+     */
     "description": string;
+    /**
+     * キャプションを付ける
+     */
     "describeFile": string;
+    /**
+     * キャプションを入力
+     */
     "enterFileDescription": string;
+    /**
+     * 作者
+     */
     "author": string;
+    /**
+     * 未保存の変更があります。破棄しますか?
+     */
     "leaveConfirm": string;
+    /**
+     * 管理
+     */
     "manage": string;
+    /**
+     * プラグイン
+     */
     "plugins": string;
+    /**
+     * 設定のバックアップ
+     */
     "preferencesBackups": string;
+    /**
+     * デッキ
+     */
     "deck": string;
+    /**
+     * デッキ解除
+     */
     "undeck": string;
+    /**
+     * モーダルにぼかし効果を使用
+     */
     "useBlurEffectForModal": string;
+    /**
+     * フル機能リアクションピッカーを使用
+     */
     "useFullReactionPicker": string;
+    /**
+     * 幅
+     */
     "width": string;
+    /**
+     * 高さ
+     */
     "height": string;
+    /**
+     * 大
+     */
     "large": string;
+    /**
+     * 中
+     */
     "medium": string;
+    /**
+     * 小
+     */
     "small": string;
+    /**
+     * アクセストークンの発行
+     */
     "generateAccessToken": string;
+    /**
+     * 権限
+     */
     "permission": string;
+    /**
+     * 管理者権限
+     */
+    "adminPermission": string;
+    /**
+     * 全て有効にする
+     */
     "enableAll": string;
+    /**
+     * 全て無効にする
+     */
     "disableAll": string;
+    /**
+     * アカウントへのアクセス許可
+     */
     "tokenRequested": string;
+    /**
+     * このプラグインはここで設定した権限を行使できるようになります。
+     */
     "pluginTokenRequestedDescription": string;
+    /**
+     * 通知の種類
+     */
     "notificationType": string;
+    /**
+     * 編集
+     */
     "edit": string;
+    /**
+     * メールサーバー
+     */
     "emailServer": string;
+    /**
+     * メール配信機能を有効化する
+     */
     "enableEmail": string;
+    /**
+     * メールアドレスの確認やパスワードリセットの際に使います
+     */
     "emailConfigInfo": string;
+    /**
+     * メール
+     */
     "email": string;
+    /**
+     * メールアドレス
+     */
     "emailAddress": string;
+    /**
+     * SMTP サーバーの設定
+     */
     "smtpConfig": string;
+    /**
+     * ホスト
+     */
     "smtpHost": string;
+    /**
+     * ポート
+     */
     "smtpPort": string;
+    /**
+     * ユーザー名
+     */
     "smtpUser": string;
+    /**
+     * パスワード
+     */
     "smtpPass": string;
+    /**
+     * ユーザー名とパスワードを空欄にすることで、SMTP認証を無効化出来ます
+     */
     "emptyToDisableSmtpAuth": string;
+    /**
+     * SMTP 接続に暗黙的なSSL/TLSを使用する
+     */
     "smtpSecure": string;
+    /**
+     * STARTTLS使用時はオフにします。
+     */
     "smtpSecureInfo": string;
+    /**
+     * 配信テスト
+     */
     "testEmail": string;
+    /**
+     * ワードミュート
+     */
     "wordMute": string;
+    /**
+     * ハードワードミュート
+     */
     "hardWordMute": string;
+    /**
+     * 正規表現エラー
+     */
     "regexpError": string;
-    "regexpErrorDescription": string;
+    /**
+     * {tab}ワードミュートの{line}行目の正規表現にエラーが発生しました:
+     */
+    "regexpErrorDescription": ParameterizedString<"tab" | "line">;
+    /**
+     * サーバーミュート
+     */
     "instanceMute": string;
-    "userSaysSomething": string;
+    /**
+     * {name}が何かを言いました
+     */
+    "userSaysSomething": ParameterizedString<"name">;
+    /**
+     * アクティブにする
+     */
     "makeActive": string;
+    /**
+     * 表示
+     */
     "display": string;
+    /**
+     * コピー
+     */
     "copy": string;
+    /**
+     * メトリクス
+     */
     "metrics": string;
+    /**
+     * 概要
+     */
     "overview": string;
+    /**
+     * ログ
+     */
     "logs": string;
+    /**
+     * 遅延
+     */
     "delayed": string;
+    /**
+     * データベース
+     */
     "database": string;
+    /**
+     * チャンネル
+     */
     "channel": string;
+    /**
+     * 作成
+     */
     "create": string;
+    /**
+     * 通知設定
+     */
     "notificationSetting": string;
+    /**
+     * 表示する通知の種別を選択してください。
+     */
     "notificationSettingDesc": string;
+    /**
+     * グローバル設定を使う
+     */
     "useGlobalSetting": string;
+    /**
+     * オンにすると、アカウントの通知設定が使用されます。オフにすると、個別に設定できるようになります。
+     */
     "useGlobalSettingDesc": string;
+    /**
+     * その他
+     */
     "other": string;
+    /**
+     * ログイントークンを再生成
+     */
     "regenerateLoginToken": string;
+    /**
+     * ログインに使用される内部トークンを再生成します。通常この操作を行う必要はありません。再生成すると、全てのデバイスでログアウトされます。
+     */
     "regenerateLoginTokenDescription": string;
+    /**
+     * カスタム絵文字を検索する時のキーワードになります。
+     */
+    "theKeywordWhenSearchingForCustomEmoji": string;
+    /**
+     * スペースで区切って複数設定できます。
+     */
     "setMultipleBySeparatingWithSpace": string;
+    /**
+     * ファイルIDまたはURL
+     */
     "fileIdOrUrl": string;
+    /**
+     * 動作
+     */
     "behavior": string;
+    /**
+     * サンプル
+     */
     "sample": string;
+    /**
+     * 通報
+     */
     "abuseReports": string;
+    /**
+     * 通報
+     */
     "reportAbuse": string;
+    /**
+     * ブーストを通報
+     */
     "reportAbuseRenote": string;
-    "reportAbuseOf": string;
+    /**
+     * {name}を通報する
+     */
+    "reportAbuseOf": ParameterizedString<"name">;
+    /**
+     * 通報理由の詳細を記入してください。対象のノートがある場合はそのURLも記入してください。
+     */
     "fillAbuseReportDescription": string;
+    /**
+     * 内容が送信されました。ご報告ありがとうございました。
+     */
     "abuseReported": string;
+    /**
+     * 通報者
+     */
     "reporter": string;
+    /**
+     * 通報先
+     */
     "reporteeOrigin": string;
+    /**
+     * 通報元
+     */
     "reporterOrigin": string;
+    /**
+     * リモートサーバーに通報を転送する
+     */
     "forwardReport": string;
+    /**
+     * リモートサーバーからはあなたの情報は見れず、匿名のシステムアカウントとして表示されます。
+     */
     "forwardReportIsAnonymous": string;
+    /**
+     * 送信
+     */
     "send": string;
+    /**
+     * 対応済みにする
+     */
     "abuseMarkAsResolved": string;
+    /**
+     * 新しいタブで開く
+     */
     "openInNewTab": string;
+    /**
+     * サイドビューで開く
+     */
     "openInSideView": string;
+    /**
+     * デフォルトのナビゲーション
+     */
     "defaultNavigationBehaviour": string;
+    /**
+     * これらの設定を編集するとアカウントが破損する可能性があります。
+     */
     "editTheseSettingsMayBreakAccount": string;
+    /**
+     * ノートのサーバー情報
+     */
     "instanceTicker": string;
-    "waitingFor": string;
+    /**
+     * {x}を待っています
+     */
+    "waitingFor": ParameterizedString<"x">;
+    /**
+     * ランダム
+     */
     "random": string;
+    /**
+     * システム
+     */
     "system": string;
+    /**
+     * UI切り替え
+     */
     "switchUi": string;
+    /**
+     * デスクトップ
+     */
     "desktop": string;
+    /**
+     * クリップ
+     */
     "clip": string;
+    /**
+     * 新規作成
+     */
     "createNew": string;
+    /**
+     * 任意
+     */
     "optional": string;
+    /**
+     * 新しいクリップを作成
+     */
     "createNewClip": string;
+    /**
+     * クリップ解除
+     */
     "unclip": string;
-    "confirmToUnclipAlreadyClippedNote": string;
+    /**
+     * このノートはすでにクリップ「{name}」に含まれています。ノートをこのクリップから除外しますか?
+     */
+    "confirmToUnclipAlreadyClippedNote": ParameterizedString<"name">;
+    /**
+     * パブリック
+     */
     "public": string;
+    /**
+     * 非公開
+     */
     "private": string;
-    "i18nInfo": string;
+    /**
+     * Sharkeyは有志によって様々な言語に翻訳されています。{link}で翻訳に協力できます。
+     */
+    "i18nInfo": ParameterizedString<"link">;
+    /**
+     * アクセストークンの管理
+     */
     "manageAccessTokens": string;
+    /**
+     * アカウント情報
+     */
     "accountInfo": string;
+    /**
+     * ノートの数
+     */
     "notesCount": string;
+    /**
+     * 返信した数
+     */
     "repliesCount": string;
+    /**
+     * ブーストした数
+     */
     "renotesCount": string;
+    /**
+     * 返信された数
+     */
     "repliedCount": string;
+    /**
+     * ブーストされた数
+     */
     "renotedCount": string;
+    /**
+     * フォロー数
+     */
     "followingCount": string;
+    /**
+     * フォロワー数
+     */
     "followersCount": string;
+    /**
+     * リアクションした数
+     */
     "sentReactionsCount": string;
+    /**
+     * リアクションされた数
+     */
     "receivedReactionsCount": string;
+    /**
+     * アンケートに投票した数
+     */
     "pollVotesCount": string;
+    /**
+     * アンケートに投票された数
+     */
     "pollVotedCount": string;
+    /**
+     * はい
+     */
     "yes": string;
+    /**
+     * いいえ
+     */
     "no": string;
+    /**
+     * ドライブのファイル数
+     */
     "driveFilesCount": string;
+    /**
+     * ドライブ使用量
+     */
     "driveUsage": string;
+    /**
+     * クローラーによるインデックスを拒否
+     */
     "noCrawle": string;
+    /**
+     * 外部の検索エンジンにあなたのユーザーページ、ノート、Pagesなどのコンテンツを登録(インデックス)しないよう要求します。
+     */
     "noCrawleDescription": string;
+    /**
+     * フォローを承認制にしても、ノートの公開範囲を「フォロワー」にしない限り、誰でもあなたのノートを見ることができます。
+     */
     "lockedAccountInfo": string;
+    /**
+     * デフォルトでメディアをセンシティブ設定にする
+     */
     "alwaysMarkSensitive": string;
+    /**
+     * 添付画像のサムネイルをオリジナル画質にする
+     */
     "loadRawImages": string;
+    /**
+     * アニメーション画像を再生しない
+     */
     "disableShowingAnimatedImages": string;
+    /**
+     * メディアがセンシティブであることを分かりやすく表示
+     */
     "highlightSensitiveMedia": string;
+    /**
+     * 確認のメールを送信しました。メールに記載されたリンクにアクセスして、設定を完了してください。
+     */
     "verificationEmailSent": string;
+    /**
+     * 未設定
+     */
     "notSet": string;
+    /**
+     * メールアドレスが確認されました
+     */
     "emailVerified": string;
+    /**
+     * お気に入りノートの数
+     */
     "noteFavoritesCount": string;
+    /**
+     * Pageにいいねした数
+     */
     "pageLikesCount": string;
+    /**
+     * Pageにいいねされた数
+     */
     "pageLikedCount": string;
+    /**
+     * 連絡先
+     */
     "contact": string;
+    /**
+     * システムのデフォルトのフォントを使う
+     */
     "useSystemFont": string;
+    /**
+     * クリップ
+     */
     "clips": string;
+    /**
+     * 実験的機能
+     */
     "experimentalFeatures": string;
+    /**
+     * 実験的
+     */
     "experimental": string;
+    /**
+     * これは実験的な機能です。仕様が変更されたり、正常に動作しなかったりする可能性があります。
+     */
     "thisIsExperimentalFeature": string;
+    /**
+     * 開発者
+     */
     "developer": string;
+    /**
+     * アカウントを見つけやすくする
+     */
     "makeExplorable": string;
+    /**
+     * オフにすると、「みつける」にアカウントが載らなくなります。
+     */
     "makeExplorableDescription": string;
+    /**
+     * 公開ノートをインデックス不可にする
+     */
     "makeIndexable": string;
+    /**
+     * ノート検索があなたの公開ノートをインデックス化しないようにします。
+     */
     "makeIndexableDescription": string;
+    /**
+     * タイムラインのノートを離して表示
+     */
     "showGapBetweenNotesInTimeline": string;
+    /**
+     * 複製
+     */
     "duplicate": string;
+    /**
+     * 左
+     */
     "left": string;
+    /**
+     * 中央
+     */
     "center": string;
+    /**
+     * 広い
+     */
     "wide": string;
+    /**
+     * 狭い
+     */
     "narrow": string;
+    /**
+     * 設定はページリロード後に反映されます。今すぐリロードしますか?
+     */
     "reloadToApplySetting": string;
+    /**
+     * 反映には再起動が必要です。
+     */
     "needReloadToApply": string;
+    /**
+     * タイトルバーを表示する
+     */
     "showTitlebar": string;
+    /**
+     * キャッシュをクリア
+     */
     "clearCache": string;
-    "onlineUsersCount": string;
-    "nUsers": string;
-    "nNotes": string;
+    /**
+     * {n}人がオンライン
+     */
+    "onlineUsersCount": ParameterizedString<"n">;
+    /**
+     * {n}ユーザー
+     */
+    "nUsers": ParameterizedString<"n">;
+    /**
+     * {n}ノート
+     */
+    "nNotes": ParameterizedString<"n">;
+    /**
+     * エラーリポートを送信
+     */
     "sendErrorReports": string;
+    /**
+     * オンにすると、問題が発生したときにエラーの詳細情報がSharkeyに共有され、ソフトウェアの品質向上に役立てることができます。エラー情報には、OSのバージョン、ブラウザの種類、行動履歴などが含まれます。
+     */
     "sendErrorReportsDescription": string;
+    /**
+     * マイテーマ
+     */
     "myTheme": string;
+    /**
+     * 背景
+     */
     "backgroundColor": string;
+    /**
+     * アクセント
+     */
     "accentColor": string;
+    /**
+     * 文字
+     */
     "textColor": string;
+    /**
+     * 名前を付けて保存
+     */
     "saveAs": string;
+    /**
+     * 高度
+     */
     "advanced": string;
+    /**
+     * 高度な設定
+     */
     "advancedSettings": string;
+    /**
+     * 値
+     */
     "value": string;
+    /**
+     * 作成日時
+     */
     "createdAt": string;
+    /**
+     * 更新日時
+     */
     "updatedAt": string;
+    /**
+     * 保存しますか?
+     */
     "saveConfirm": string;
+    /**
+     * 削除しますか?
+     */
     "deleteConfirm": string;
+    /**
+     * 有効な値ではありません。
+     */
     "invalidValue": string;
+    /**
+     * レジストリ
+     */
     "registry": string;
+    /**
+     * アカウントを閉鎖する
+     */
     "closeAccount": string;
+    /**
+     * 現在のバージョン
+     */
     "currentVersion": string;
+    /**
+     * 最新のバージョン
+     */
     "latestVersion": string;
+    /**
+     * お使いのクライアントは最新です。
+     */
     "youAreRunningUpToDateClient": string;
+    /**
+     * 新しいバージョンのクライアントが利用可能です。
+     */
     "newVersionOfClientAvailable": string;
+    /**
+     * 使用量
+     */
     "usageAmount": string;
+    /**
+     * 容量
+     */
     "capacity": string;
+    /**
+     * 使用中
+     */
     "inUse": string;
+    /**
+     * コードを編集
+     */
     "editCode": string;
+    /**
+     * 適用
+     */
     "apply": string;
+    /**
+     * サーバーからのお知らせを受け取る
+     */
     "receiveAnnouncementFromInstance": string;
+    /**
+     * メール通知
+     */
     "emailNotification": string;
+    /**
+     * 公開
+     */
     "publish": string;
+    /**
+     * チャンネル内検索
+     */
     "inChannelSearch": string;
+    /**
+     * 右クリックでリアクションピッカーを開く
+     */
     "useReactionPickerForContextMenu": string;
-    "typingUsers": string;
+    /**
+     * {users}が入力中
+     */
+    "typingUsers": ParameterizedString<"users">;
+    /**
+     * 特定の日付にジャンプ
+     */
     "jumpToSpecifiedDate": string;
+    /**
+     * 過去のタイムラインを表示しています
+     */
     "showingPastTimeline": string;
+    /**
+     * クリア
+     */
     "clear": string;
+    /**
+     * 全て既読にする
+     */
     "markAllAsRead": string;
+    /**
+     * 戻る
+     */
     "goBack": string;
+    /**
+     * いいね解除しますか?
+     */
     "unlikeConfirm": string;
+    /**
+     * フルビュー
+     */
     "fullView": string;
+    /**
+     * フルビュー解除
+     */
     "quitFullView": string;
+    /**
+     * 説明を追加
+     */
     "addDescription": string;
+    /**
+     * 個々のノートのメニューから「ピン留め」を選択することで、ここにノートを表示しておくことができます。
+     */
     "userPagePinTip": string;
+    /**
+     * 宛先に含まれていないメンションがあります
+     */
     "notSpecifiedMentionWarning": string;
+    /**
+     * 情報
+     */
     "info": string;
+    /**
+     * ユーザー情報
+     */
     "userInfo": string;
+    /**
+     * 不明
+     */
     "unknown": string;
+    /**
+     * オンライン状態
+     */
     "onlineStatus": string;
+    /**
+     * オンライン状態を隠す
+     */
     "hideOnlineStatus": string;
+    /**
+     * オンライン状態を隠すと、検索などの一部機能において利便性が低下することがあります。
+     */
     "hideOnlineStatusDescription": string;
+    /**
+     * オンライン
+     */
     "online": string;
+    /**
+     * アクティブ
+     */
     "active": string;
+    /**
+     * オフライン
+     */
     "offline": string;
+    /**
+     * 非推奨
+     */
     "notRecommended": string;
+    /**
+     * Botプロテクション
+     */
     "botProtection": string;
+    /**
+     * サーバーブロック・サイレンス
+     */
     "instanceBlocking": string;
+    /**
+     * アカウントを選択
+     */
     "selectAccount": string;
+    /**
+     * アカウントを切り替え
+     */
     "switchAccount": string;
+    /**
+     * 有効
+     */
     "enabled": string;
+    /**
+     * 無効
+     */
     "disabled": string;
+    /**
+     * クイックアクション
+     */
     "quickAction": string;
+    /**
+     * ユーザー
+     */
     "user": string;
+    /**
+     * 管理
+     */
     "administration": string;
+    /**
+     * アカウント
+     */
     "accounts": string;
+    /**
+     * 切り替え
+     */
     "switch": string;
+    /**
+     * 管理者情報が設定されていません。
+     */
     "noMaintainerInformationWarning": string;
+    /**
+     * Botプロテクションが設定されていません。
+     */
     "noBotProtectionWarning": string;
+    /**
+     * 設定する
+     */
     "configure": string;
+    /**
+     * ギャラリーへ投稿
+     */
     "postToGallery": string;
+    /**
+     * このハッシュタグで投稿
+     */
     "postToHashtag": string;
+    /**
+     * ギャラリー
+     */
     "gallery": string;
+    /**
+     * 最近の投稿
+     */
     "recentPosts": string;
+    /**
+     * 人気の投稿
+     */
     "popularPosts": string;
+    /**
+     * ノートで共有
+     */
     "shareWithNote": string;
+    /**
+     * 広告
+     */
     "ads": string;
+    /**
+     * 期限
+     */
     "expiration": string;
+    /**
+     * 開始期間
+     */
     "startingperiod": string;
+    /**
+     * メモ
+     */
     "memo": string;
+    /**
+     * 優先度
+     */
     "priority": string;
+    /**
+     * 高
+     */
     "high": string;
+    /**
+     * 中
+     */
     "middle": string;
+    /**
+     * 低
+     */
     "low": string;
+    /**
+     * メールアドレスの設定がされていません。
+     */
     "emailNotConfiguredWarning": string;
+    /**
+     * 比率
+     */
     "ratio": string;
+    /**
+     * 本文をプレビュー
+     */
     "previewNoteText": string;
+    /**
+     * カスタムCSS
+     */
     "customCss": string;
+    /**
+     * この設定は必ず知識のある方が行ってください。不適切な設定を行うとクライアントが正常に使用できなくなる恐れがあります。
+     */
     "customCssWarn": string;
+    /**
+     * グローバル
+     */
     "global": string;
+    /**
+     * アイコンを四角形で表示
+     */
     "squareAvatars": string;
+    /**
+     * 送信
+     */
     "sent": string;
+    /**
+     * 受信
+     */
     "received": string;
+    /**
+     * 検索結果
+     */
     "searchResult": string;
+    /**
+     * ハッシュタグ
+     */
     "hashtags": string;
+    /**
+     * トラブルシューティング
+     */
     "troubleshooting": string;
+    /**
+     * UIにぼかし効果を使用
+     */
     "useBlurEffect": string;
+    /**
+     * 詳しく
+     */
     "learnMore": string;
+    /**
+     * Sharkeyが更新されました!
+     */
     "misskeyUpdated": string;
+    /**
+     * 更新情報を見る
+     */
     "whatIsNew": string;
+    /**
+     * 翻訳
+     */
     "translate": string;
-    "translatedFrom": string;
+    /**
+     * {x}から翻訳
+     */
+    "translatedFrom": ParameterizedString<"x">;
+    /**
+     * アカウントの削除が進行中です
+     */
     "accountDeletionInProgress": string;
+    /**
+     * サーバー上であなたのアカウントを一意に識別するための名前。アルファベット(a~z, A~Z)、数字(0~9)、およびアンダーバー(_)が使用できます。ユーザー名は後から変更することは出来ません。
+     */
     "usernameInfo": string;
+    /**
+     * 藍モード
+     */
     "aiChanMode": string;
+    /**
+     * 開発者モード
+     */
     "devMode": string;
+    /**
+     * CWを維持する
+     */
     "keepCw": string;
+    /**
+     * Pub/Subのアカウント
+     */
     "pubSub": string;
+    /**
+     * 直近の通信
+     */
     "lastCommunication": string;
+    /**
+     * 解決済み
+     */
     "resolved": string;
+    /**
+     * 未解決
+     */
     "unresolved": string;
+    /**
+     * フォロワーを解除
+     */
     "breakFollow": string;
+    /**
+     * フォロワー解除しますか?
+     */
     "breakFollowConfirm": string;
+    /**
+     * オンになっています
+     */
     "itsOn": string;
+    /**
+     * オフになっています
+     */
     "itsOff": string;
+    /**
+     * オン
+     */
     "on": string;
+    /**
+     * オフ
+     */
     "off": string;
+    /**
+     * アカウント登録にメールアドレスを必須にする
+     */
     "emailRequiredForSignup": string;
+    /**
+     * 新規ユーザーの承認が必要
+     */
     "approvalRequiredForSignup": string;
+    /**
+     * 未読
+     */
     "unread": string;
+    /**
+     * フィルタ
+     */
     "filter": string;
+    /**
+     * コントロールパネル
+     */
     "controlPanel": string;
+    /**
+     * アカウントを管理
+     */
     "manageAccounts": string;
+    /**
+     * リアクション一覧を公開する
+     */
     "makeReactionsPublic": string;
+    /**
+     * あなたがしたリアクション一覧を誰でも見れるようにします。
+     */
     "makeReactionsPublicDescription": string;
+    /**
+     * クラシック
+     */
     "classic": string;
+    /**
+     * スレッドをミュート
+     */
     "muteThread": string;
+    /**
+     * スレッドのミュートを解除
+     */
     "unmuteThread": string;
+    /**
+     * フォローの公開範囲
+     */
     "followingVisibility": string;
+    /**
+     * フォロワーの公開範囲
+     */
     "followersVisibility": string;
+    /**
+     * さらにスレッドを見る
+     */
     "continueThread": string;
+    /**
+     * アカウントが削除されます。よろしいですか?
+     */
     "deleteAccountConfirm": string;
+    /**
+     * パスワードが間違っています。
+     */
     "incorrectPassword": string;
-    "voteConfirm": string;
-    "voteConfirmMulti": string;
+    /**
+     * 「{choice}」に投票しますか?
+     */
+    "voteConfirm": ParameterizedString<"choice">;
+    /**
+     * 「{choice}」に投票しますか?
+     *  確認後、選択肢を増やすことができます。
+     */
+    "voteConfirmMulti": ParameterizedString<"choice">;
+    /**
+     * 隠す
+     */
     "hide": string;
+    /**
+     * モバイルデバイスのときドロワーで表示
+     */
     "useDrawerReactionPickerForMobile": string;
-    "welcomeBackWithName": string;
-    "clickToFinishEmailVerification": string;
+    /**
+     * おかえりなさい、{name}さん
+     */
+    "welcomeBackWithName": ParameterizedString<"name">;
+    /**
+     * [{ok}]を押して、メールアドレスの確認を完了してください。
+     */
+    "clickToFinishEmailVerification": ParameterizedString<"ok">;
+    /**
+     * デバイスタイプ
+     */
     "overridedDeviceKind": string;
+    /**
+     * スマートフォン
+     */
     "smartphone": string;
+    /**
+     * タブレット
+     */
     "tablet": string;
+    /**
+     * 自動
+     */
     "auto": string;
+    /**
+     * テーマカラー
+     */
     "themeColor": string;
+    /**
+     * サイズ
+     */
     "size": string;
+    /**
+     * 列の数
+     */
     "numberOfColumn": string;
+    /**
+     * 検索
+     */
     "searchByGoogle": string;
+    /**
+     * サーバーデフォルトのライトテーマ
+     */
     "instanceDefaultLightTheme": string;
+    /**
+     * サーバーデフォルトのダークテーマ
+     */
     "instanceDefaultDarkTheme": string;
+    /**
+     * オブジェクト形式のテーマコードを記入します。
+     */
     "instanceDefaultThemeDescription": string;
+    /**
+     * ミュートする期限
+     */
     "mutePeriod": string;
+    /**
+     * 期限
+     */
     "period": string;
+    /**
+     * 無期限
+     */
     "indefinitely": string;
+    /**
+     * 10分
+     */
     "tenMinutes": string;
+    /**
+     * 1時間
+     */
     "oneHour": string;
+    /**
+     * 1日
+     */
     "oneDay": string;
+    /**
+     * 1週間
+     */
     "oneWeek": string;
+    /**
+     * 1ヶ月
+     */
     "oneMonth": string;
+    /**
+     * 反映されるまで時間がかかる場合があります。
+     */
     "reflectMayTakeTime": string;
+    /**
+     * アカウント情報の取得に失敗しました
+     */
     "failedToFetchAccountInformation": string;
+    /**
+     * レート制限を超えました
+     */
     "rateLimitExceeded": string;
+    /**
+     * 画像のクロップ
+     */
     "cropImage": string;
+    /**
+     * 画像をクロップしますか?
+     */
     "cropImageAsk": string;
+    /**
+     * クロップする
+     */
     "cropYes": string;
+    /**
+     * そのまま使う
+     */
     "cropNo": string;
+    /**
+     * ファイル
+     */
     "file": string;
-    "recentNHours": string;
-    "recentNDays": string;
+    /**
+     * 直近{n}時間
+     */
+    "recentNHours": ParameterizedString<"n">;
+    /**
+     * 直近{n}日
+     */
+    "recentNDays": ParameterizedString<"n">;
+    /**
+     * メールサーバーの設定がされていません。
+     */
     "noEmailServerWarning": string;
+    /**
+     * 未対応の通報があります。
+     */
     "thereIsUnresolvedAbuseReportWarning": string;
+    /**
+     * 承認待ちのユーザーがいる。
+     */
     "pendingUserApprovals": string;
+    /**
+     * 推奨
+     */
     "recommended": string;
+    /**
+     * チェック
+     */
     "check": string;
+    /**
+     * このユーザーのドライブ容量上限を変更
+     */
     "driveCapOverrideLabel": string;
+    /**
+     * 0以下を指定すると解除されます。
+     */
     "driveCapOverrideCaption": string;
+    /**
+     * 閲覧するには管理者アカウントでログインしている必要があります。
+     */
     "requireAdminForView": string;
+    /**
+     * システムにより自動で作成・管理されているアカウントです。
+     */
     "isSystemAccount": string;
-    "typeToConfirm": string;
+    /**
+     * この操作を行うには {x} と入力してください
+     */
+    "typeToConfirm": ParameterizedString<"x">;
+    /**
+     * アカウント削除
+     */
     "deleteAccount": string;
+    /**
+     * 承認する
+     */
     "approveAccount": string;
+    /**
+     * 拒否と削除
+     */
     "denyAccount": string;
+    /**
+     * 承認済み
+     */
     "approved": string;
+    /**
+     * 承認されていない
+     */
     "notApproved": string;
+    /**
+     * 承認状況
+     */
     "approvalStatus": string;
+    /**
+     * ドキュメント
+     */
     "document": string;
+    /**
+     * ページキャッシュ数
+     */
     "numberOfPageCache": string;
+    /**
+     * 多くすると利便性が向上しますが、負荷とメモリ使用量が増えます。
+     */
     "numberOfPageCacheDescription": string;
+    /**
+     * スレッド内の返信数
+     */
     "numberOfReplies": string;
+    /**
+     * この数値を大きくすると、より多くの返信が表示されます。この値を大きくしすぎると、返信が窮屈になり、読めなくなることがあります。
+     */
     "numberOfRepliesDescription": string;
+    /**
+     * ブースト設定
+     */
+    "boostSettings": string;
+    /**
+     * 可視性セレクタを表示
+     */
+    "showVisibilitySelectorOnBoost": string;
+    /**
+     * 無効の場合、以下で定義されるデフォルトの可視性が使用され、セレクタは表示されません。
+     */
+    "showVisibilitySelectorOnBoostDescription": string;
+    /**
+     * デフォルトのブースト可視性の設定
+     */
+    "visibilityOnBoost": string;
+    /**
+     * ログアウトしますか?
+     */
     "logoutConfirm": string;
+    /**
+     * 最終利用日時
+     */
     "lastActiveDate": string;
+    /**
+     * ステータスバー
+     */
     "statusbar": string;
+    /**
+     * 選択してください
+     */
     "pleaseSelect": string;
+    /**
+     * 反転
+     */
     "reverse": string;
+    /**
+     * 色付き
+     */
     "colored": string;
+    /**
+     * 更新間隔
+     */
     "refreshInterval": string;
+    /**
+     * ラベル
+     */
     "label": string;
+    /**
+     * タイプ
+     */
     "type": string;
+    /**
+     * 速度
+     */
     "speed": string;
+    /**
+     * 遅い
+     */
     "slow": string;
+    /**
+     * 速い
+     */
     "fast": string;
+    /**
+     * センシティブなメディアの検出
+     */
     "sensitiveMediaDetection": string;
+    /**
+     * ローカルのみ
+     */
     "localOnly": string;
+    /**
+     * リモートのみ
+     */
     "remoteOnly": string;
+    /**
+     * アップロード失敗
+     */
     "failedToUpload": string;
+    /**
+     * 不適切な内容を含む可能性があると判定されたためアップロードできません。
+     */
     "cannotUploadBecauseInappropriate": string;
+    /**
+     * ドライブの空き容量が無いためアップロードできません。
+     */
     "cannotUploadBecauseNoFreeSpace": string;
+    /**
+     * ファイルサイズの制限を超えているためアップロードできません。
+     */
     "cannotUploadBecauseExceedsFileSizeLimit": string;
+    /**
+     * ベータ
+     */
     "beta": string;
+    /**
+     * 自動センシティブ判定
+     */
     "enableAutoSensitive": string;
+    /**
+     * 利用可能な場合は、機械学習を利用して自動でメディアにセンシティブフラグを設定します。この機能をオフにしても、サーバーによっては自動で設定されることがあります。
+     */
     "enableAutoSensitiveDescription": string;
+    /**
+     * ユーザーのメールアドレスのバリデーションを、捨てアドかどうかや実際に通信可能かどうかなどを判定しより積極的に行います。オフにすると単に文字列として正しいかどうかのみチェックされます。
+     */
     "activeEmailValidationDescription": string;
+    /**
+     * ナビゲーションバー
+     */
     "navbar": string;
+    /**
+     * シャッフル
+     */
     "shuffle": string;
+    /**
+     * アカウント
+     */
     "account": string;
+    /**
+     * 移動
+     */
     "move": string;
+    /**
+     * プッシュ通知
+     */
     "pushNotification": string;
+    /**
+     * プッシュ通知を有効化
+     */
     "subscribePushNotification": string;
+    /**
+     * プッシュ通知を停止する
+     */
     "unsubscribePushNotification": string;
+    /**
+     * プッシュ通知は有効です
+     */
     "pushNotificationAlreadySubscribed": string;
+    /**
+     * ブラウザかサーバーがプッシュ通知に非対応
+     */
     "pushNotificationNotSupported": string;
+    /**
+     * 通知が既読になったらプッシュ通知を削除する
+     */
     "sendPushNotificationReadMessage": string;
+    /**
+     * 端末の電池消費量が増加する可能性があります。
+     */
     "sendPushNotificationReadMessageCaption": string;
+    /**
+     * 最大化
+     */
     "windowMaximize": string;
+    /**
+     * 最小化
+     */
     "windowMinimize": string;
+    /**
+     * 元に戻す
+     */
     "windowRestore": string;
+    /**
+     * キャプション
+     */
     "caption": string;
+    /**
+     * Botアカウントでログイン中
+     */
     "loggedInAsBot": string;
+    /**
+     * ツール
+     */
     "tools": string;
+    /**
+     * 読み込めません
+     */
     "cannotLoad": string;
+    /**
+     * プロフィール表示回数
+     */
     "numberOfProfileView": string;
+    /**
+     * いいね!
+     */
     "like": string;
+    /**
+     * いいねを解除
+     */
     "unlike": string;
+    /**
+     * 絵文字のようなデフォルト
+     */
     "defaultLike": string;
+    /**
+     * いいね数
+     */
     "numberOfLikes": string;
+    /**
+     * 表示
+     */
     "show": string;
+    /**
+     * 今後表示しない
+     */
     "neverShow": string;
+    /**
+     * また後で
+     */
     "remindMeLater": string;
+    /**
+     * Sharkeyを気に入っていただけましたか?
+     */
     "didYouLikeMisskey": string;
-    "pleaseDonate": string;
+    /**
+     * Sharkeyは{host}が使用している無料のソフトウェアです。これからも開発を続けられるように、ぜひ寄付をお願いします!
+     */
+    "pleaseDonate": ParameterizedString<"host">;
+    /**
+     * インスタンス管理者への寄付によって{host}を直接サポートすることもできます。
+     */
+    "pleaseDonateInstance": ParameterizedString<"host">;
+    /**
+     * 対応するソースコードは{anchor}から利用可能です。
+     */
+    "correspondingSourceIsAvailable": ParameterizedString<"anchor">;
+    /**
+     * ロール
+     */
     "roles": string;
+    /**
+     * ロール
+     */
     "role": string;
+    /**
+     * ロールはありません
+     */
     "noRole": string;
+    /**
+     * 一般ユーザー
+     */
     "normalUser": string;
+    /**
+     * 未定義
+     */
     "undefined": string;
+    /**
+     * アサイン
+     */
     "assign": string;
+    /**
+     * アサインを解除
+     */
     "unassign": string;
+    /**
+     * 色
+     */
     "color": string;
+    /**
+     * カスタム絵文字の管理
+     */
     "manageCustomEmojis": string;
+    /**
+     * アバターデコレーションの管理
+     */
     "manageAvatarDecorations": string;
+    /**
+     * これ以上作成することはできません。
+     */
     "youCannotCreateAnymore": string;
+    /**
+     * 一時的に利用できません
+     */
     "cannotPerformTemporary": string;
+    /**
+     * 操作回数が制限を超過するため一時的に利用できません。しばらく時間を置いてから再度お試しください。
+     */
     "cannotPerformTemporaryDescription": string;
+    /**
+     * パラメータエラー
+     */
     "invalidParamError": string;
+    /**
+     * リクエストパラメータに問題があります。通常これはバグですが、入力した文字数が多すぎる等の可能性もあります。
+     */
     "invalidParamErrorDescription": string;
+    /**
+     * 操作が拒否されました
+     */
     "permissionDeniedError": string;
+    /**
+     * このアカウントにはこの操作を行うための権限がありません。
+     */
     "permissionDeniedErrorDescription": string;
+    /**
+     * プリセット
+     */
     "preset": string;
+    /**
+     * プリセットから選択
+     */
     "selectFromPresets": string;
+    /**
+     * 実績
+     */
     "achievements": string;
+    /**
+     * サーバーの応答が無効です
+     */
     "gotInvalidResponseError": string;
+    /**
+     * サーバーがダウンまたはメンテナンスしている可能性があります。しばらくしてから再度お試しください。
+     */
     "gotInvalidResponseErrorDescription": string;
+    /**
+     * この投稿は迷惑になる可能性があります。
+     */
     "thisPostMayBeAnnoying": string;
+    /**
+     * ホームに投稿
+     */
     "thisPostMayBeAnnoyingHome": string;
+    /**
+     * やめる
+     */
     "thisPostMayBeAnnoyingCancel": string;
+    /**
+     * このまま投稿
+     */
     "thisPostMayBeAnnoyingIgnore": string;
+    /**
+     * やめる
+     */
+    "thisPostIsMissingAltTextCancel": string;
+    /**
+     * このまま投稿
+     */
+    "thisPostIsMissingAltTextIgnore": string;
+    /**
+     * この投稿に添付されたファイルの 1 つに代替テキストがありません。すべての添付ファイルに代替テキストが含まれていることを確認してください。
+     */
+    "thisPostIsMissingAltText": string;
+    /**
+     * 見たことのあるブーストを省略して表示
+     */
     "collapseRenotes": string;
+    /**
+     * ファイルを折りたたむ
+     */
     "collapseFiles": string;
+    /**
+     * 返信に会話を読み込む
+     */
     "autoloadConversation": string;
+    /**
+     * サーバー内部エラー
+     */
     "internalServerError": string;
+    /**
+     * サーバー内部で予期しないエラーが発生しました。
+     */
     "internalServerErrorDescription": string;
+    /**
+     * エラー情報をコピー
+     */
     "copyErrorInfo": string;
+    /**
+     * このサーバーに登録する
+     */
     "joinThisServer": string;
+    /**
+     * 他のサーバーを探す
+     */
     "exploreOtherServers": string;
+    /**
+     * タイムラインを見てみる
+     */
     "letsLookAtTimeline": string;
+    /**
+     * 連合なしにしますか?
+     */
     "disableFederationConfirm": string;
+    /**
+     * 連合なしにしても投稿は非公開になりません。ほとんどの場合、連合なしにする必要はありません。
+     */
     "disableFederationConfirmWarn": string;
+    /**
+     * 連合なしにする
+     */
     "disableFederationOk": string;
+    /**
+     * 現在このサーバーは招待制です。招待コードをお持ちの方のみ登録できます。
+     */
     "invitationRequiredToRegister": string;
+    /**
+     * このインスタンスは、登録理由を指定したユーザーのみを受け入れています。
+     */
     "approvalRequiredToRegister": string;
+    /**
+     * このサーバーではメール配信はサポートされていません
+     */
     "emailNotSupported": string;
+    /**
+     * チャンネルに投稿
+     */
     "postToTheChannel": string;
+    /**
+     * 後から変更できません。
+     */
     "cannotBeChangedLater": string;
+    /**
+     * リアクションの受け入れ
+     */
     "reactionAcceptance": string;
+    /**
+     * いいねのみ
+     */
     "likeOnly": string;
+    /**
+     * 全て (リモートはいいねのみ)
+     */
     "likeOnlyForRemote": string;
+    /**
+     * 非センシティブのみ
+     */
     "nonSensitiveOnly": string;
+    /**
+     * 非センシティブのみ (リモートはいいねのみ)
+     */
     "nonSensitiveOnlyForLocalLikeOnlyForRemote": string;
+    /**
+     * 自分に割り当てられたロール
+     */
     "rolesAssignedToMe": string;
+    /**
+     * パスワードリセットしますか?
+     */
     "resetPasswordConfirm": string;
+    /**
+     * センシティブワード
+     */
     "sensitiveWords": string;
+    /**
+     * 設定したワードが含まれるノートの公開範囲をホームにします。改行で区切って複数設定できます。
+     */
     "sensitiveWordsDescription": string;
+    /**
+     * スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。
+     */
     "sensitiveWordsDescription2": string;
+    /**
+     * 禁止ワード
+     */
+    "prohibitedWords": string;
+    /**
+     * 設定したワードが含まれるノートを投稿しようとした際、エラーとなるようにします。改行で区切って複数設定できます。
+     */
+    "prohibitedWordsDescription": string;
+    /**
+     * スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。
+     */
+    "prohibitedWordsDescription2": string;
+    /**
+     * 非表示ハッシュタグ
+     */
     "hiddenTags": string;
+    /**
+     * 設定したタグをトレンドに表示させないようにします。改行で区切って複数設定できます。
+     */
     "hiddenTagsDescription": string;
+    /**
+     * ノート検索は利用できません。
+     */
     "notesSearchNotAvailable": string;
+    /**
+     * ライセンス
+     */
     "license": string;
+    /**
+     * お気に入り解除しますか?
+     */
     "unfavoriteConfirm": string;
+    /**
+     * 自分のクリップ
+     */
     "myClips": string;
+    /**
+     * ドライブクリーナー
+     */
     "drivecleaner": string;
+    /**
+     * すべてのキューを今すぐ再試行
+     */
     "retryAllQueuesNow": string;
+    /**
+     * 今すぐ再試行しますか?
+     */
     "retryAllQueuesConfirmTitle": string;
+    /**
+     * 一時的にサーバーの負荷が増大することがあります。
+     */
     "retryAllQueuesConfirmText": string;
+    /**
+     * リモートユーザーのチャートを生成
+     */
     "enableChartsForRemoteUser": string;
+    /**
+     * リモートサーバーのチャートを生成
+     */
     "enableChartsForFederatedInstances": string;
+    /**
+     * ノートのアクションにクリップを追加
+     */
     "showClipButtonInNoteFooter": string;
+    /**
+     * リアクションの表示サイズ
+     */
     "reactionsDisplaySize": string;
+    /**
+     * リアクションの最大横幅を制限し、縮小して表示する
+     */
     "limitWidthOfReaction": string;
+    /**
+     * ノートIDまたはURL
+     */
     "noteIdOrUrl": string;
+    /**
+     * 動画
+     */
     "video": string;
+    /**
+     * 動画
+     */
     "videos": string;
+    /**
+     * 音声
+     */
+    "audio": string;
+    /**
+     * 音声
+     */
+    "audioFiles": string;
+    /**
+     * データセーバー
+     */
     "dataSaver": string;
+    /**
+     * アカウントの移行
+     */
     "accountMigration": string;
+    /**
+     * このユーザーは新しいアカウントに移行しました:
+     */
     "accountMoved": string;
+    /**
+     * このアカウントは移行されています
+     */
     "accountMovedShort": string;
+    /**
+     * この操作はできません
+     */
     "operationForbidden": string;
+    /**
+     * 常に広告を表示する
+     */
     "forceShowAds": string;
+    /**
+     * 猫友達 :3
+     */
+    "oneko": string;
+    /**
+     * メモを追加
+     */
     "addMemo": string;
+    /**
+     * メモを編集
+     */
     "editMemo": string;
+    /**
+     * リアクション一覧
+     */
     "reactionsList": string;
+    /**
+     * ブースト一覧
+     */
     "renotesList": string;
+    /**
+     * 通知の表示
+     */
     "notificationDisplay": string;
+    /**
+     * 左上
+     */
     "leftTop": string;
+    /**
+     * 右上
+     */
     "rightTop": string;
+    /**
+     * 左下
+     */
     "leftBottom": string;
+    /**
+     * 右下
+     */
     "rightBottom": string;
+    /**
+     * スタック方向
+     */
     "stackAxis": string;
+    /**
+     * 縦
+     */
     "vertical": string;
+    /**
+     * 横
+     */
     "horizontal": string;
+    /**
+     * 位置
+     */
     "position": string;
+    /**
+     * サーバールール
+     */
     "serverRules": string;
+    /**
+     * このサーバーに登録するには、以下の内容を確認し同意する必要があります。
+     */
     "pleaseConfirmBelowBeforeSignup": string;
+    /**
+     * 続けるには、全ての「同意する」にチェックが入っている必要があります。
+     */
     "pleaseAgreeAllToContinue": string;
+    /**
+     * 続ける
+     */
     "continue": string;
+    /**
+     * 予約ユーザー名
+     */
     "preservedUsernames": string;
+    /**
+     * 予約するユーザー名を改行で列挙します。ここで指定されたユーザー名はアカウント作成時に使えなくなりますが、管理者によるアカウント作成時はこの制限を受けません。また、既に存在するアカウントも影響を受けません。
+     */
     "preservedUsernamesDescription": string;
+    /**
+     * このファイルからノートを作成
+     */
     "createNoteFromTheFile": string;
+    /**
+     * アーカイブ
+     */
     "archive": string;
-    "channelArchiveConfirmTitle": string;
+    /**
+     * {name}をアーカイブしますか?
+     */
+    "channelArchiveConfirmTitle": ParameterizedString<"name">;
+    /**
+     * アーカイブすると、チャンネル一覧や検索結果に表示されなくなり、新たな書き込みもできなくなります。
+     */
     "channelArchiveConfirmDescription": string;
+    /**
+     * このチャンネルはアーカイブされています。
+     */
     "thisChannelArchived": string;
+    /**
+     * ノートの表示
+     */
     "displayOfNote": string;
+    /**
+     * 初期設定
+     */
     "initialAccountSetting": string;
+    /**
+     * フォロー中
+     */
     "youFollowing": string;
+    /**
+     * 生成AIによる学習を拒否
+     */
     "preventAiLearning": string;
+    /**
+     * 外部の文章生成AIや画像生成AIに対して、投稿したノートや画像などのコンテンツを学習の対象にしないように要求します。これはnoaiフラグをHTMLレスポンスに含めることによって実現されますが、この要求に従うかはそのAI次第であるため、学習を完全に防止するものではありません。
+     */
     "preventAiLearningDescription": string;
+    /**
+     * オプション
+     */
     "options": string;
+    /**
+     * ユーザー指定
+     */
     "specifyUser": string;
+    /**
+     * プレビューできません
+     */
     "failedToPreviewUrl": string;
+    /**
+     * 更新
+     */
     "update": string;
+    /**
+     * リアクションとして使えるロール
+     */
     "rolesThatCanBeUsedThisEmojiAsReaction": string;
+    /**
+     * ロールの指定が一つもない場合、誰でもリアクションとして使えます。
+     */
     "rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription": string;
+    /**
+     * ロールは公開ロールである必要があります。
+     */
     "rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn": string;
+    /**
+     * リアクションを取り消しますか?
+     */
     "cancelReactionConfirm": string;
+    /**
+     * リアクションを変更しますか?
+     */
     "changeReactionConfirm": string;
+    /**
+     * あとで
+     */
     "later": string;
+    /**
+     * Sharkeyへ
+     */
     "goToMisskey": string;
+    /**
+     * 絵文字の追加辞書
+     */
     "additionalEmojiDictionary": string;
+    /**
+     * インストール済み
+     */
     "installed": string;
+    /**
+     * ブランディング
+     */
     "branding": string;
+    /**
+     * サーバーのマシン情報を公開する
+     */
     "enableServerMachineStats": string;
+    /**
+     * 実績を有効にする
+     */
     "enableAchievements": string;
+    /**
+     * オフにすると実績システムは無効になります。
+     */
     "turnOffAchievements": string;
+    /**
+     * botのハッシュタグ追加を許可する
+     */
     "enableBotTrending": string;
+    /**
+     * オフにするとボットがハッシュタグを入力しなくなります。
+     */
     "turnOffBotTrending": string;
+    /**
+     * ユーザーごとのIdenticon生成を有効にする
+     */
     "enableIdenticonGeneration": string;
+    /**
+     * オフにするとパフォーマンスが向上します。
+     */
     "turnOffToImprovePerformance": string;
+    /**
+     * 招待コードを作成
+     */
     "createInviteCode": string;
+    /**
+     * オプションを指定して作成
+     */
     "createWithOptions": string;
+    /**
+     * 作成数
+     */
     "createCount": string;
+    /**
+     * 招待コードを作成しました
+     */
     "inviteCodeCreated": string;
+    /**
+     * 作成できる招待コードの数が上限に達しています。
+     */
     "inviteLimitExceeded": string;
-    "createLimitRemaining": string;
-    "inviteLimitResetCycle": string;
+    /**
+     * 作成できる招待コード: 残り {limit} 個
+     */
+    "createLimitRemaining": ParameterizedString<"limit">;
+    /**
+     * {time}で最大 {limit} 個の招待コードを作成できます。
+     */
+    "inviteLimitResetCycle": ParameterizedString<"time" | "limit">;
+    /**
+     * 有効期限
+     */
     "expirationDate": string;
+    /**
+     * 有効期限を設けない
+     */
     "noExpirationDate": string;
+    /**
+     * 招待コードが使用された日時
+     */
     "inviteCodeUsedAt": string;
+    /**
+     * 招待コードを使用したユーザー
+     */
     "registeredUserUsingInviteCode": string;
+    /**
+     * メール認証待ち
+     */
     "waitingForMailAuth": string;
+    /**
+     * 招待コードを作成したユーザー
+     */
     "inviteCodeCreator": string;
+    /**
+     * 使用日時
+     */
     "usedAt": string;
+    /**
+     * 未使用
+     */
     "unused": string;
+    /**
+     * 使用済み
+     */
     "used": string;
+    /**
+     * 期限切れ
+     */
     "expired": string;
+    /**
+     * 同意しますか?
+     */
     "doYouAgree": string;
+    /**
+     * 重要ですので必ずお読みください。
+     */
     "beSureToReadThisAsItIsImportant": string;
-    "iHaveReadXCarefullyAndAgree": string;
+    /**
+     * 「{x}」の内容をよく読み、同意します。
+     */
+    "iHaveReadXCarefullyAndAgree": ParameterizedString<"x">;
+    /**
+     * ダイアログ
+     */
     "dialog": string;
+    /**
+     * アイコン
+     */
     "icon": string;
+    /**
+     * あなたへ
+     */
     "forYou": string;
+    /**
+     * 現在のお知らせ
+     */
     "currentAnnouncements": string;
+    /**
+     * 過去のお知らせ
+     */
     "pastAnnouncements": string;
+    /**
+     * 未読のお知らせがあります。
+     */
     "youHaveUnreadAnnouncements": string;
+    /**
+     * ブラウザまたはデバイスの指示に従って、セキュリティキーまたはパスキーを使用してください。
+     */
     "useSecurityKey": string;
+    /**
+     * 返信
+     */
     "replies": string;
+    /**
+     * ブースト
+     */
     "renotes": string;
+    /**
+     * 返信を見る
+     */
     "loadReplies": string;
+    /**
+     * 会話を見る
+     */
     "loadConversation": string;
+    /**
+     * ピン留めされたリスト
+     */
     "pinnedList": string;
+    /**
+     * デバイスの画面を常にオンにする
+     */
     "keepScreenOn": string;
+    /**
+     * クリックしてノートを開く
+     */
     "clickToOpen": string;
+    /**
+     * ボットをタイムラインに表示
+     */
     "showBots": string;
+    /**
+     * このリンク先の所有者であることが確認されました
+     */
     "verifiedLink": string;
+    /**
+     * 投稿を通知
+     */
     "notifyNotes": string;
+    /**
+     * 投稿の通知を解除
+     */
     "unnotifyNotes": string;
+    /**
+     * 認証
+     */
     "authentication": string;
+    /**
+     * 続けるには認証を行ってください
+     */
     "authenticationRequiredToContinue": string;
+    /**
+     * 日時
+     */
     "dateAndTime": string;
+    /**
+     * ブーストを表示
+     */
     "showRenotes": string;
+    /**
+     * 編集済み
+     */
     "edited": string;
+    /**
+     * 通知の受信設定
+     */
     "notificationRecieveConfig": string;
+    /**
+     * 相互フォロー
+     */
     "mutualFollow": string;
+    /**
+     * フォロー中またはフォロワー
+     */
+    "followingOrFollower": string;
+    /**
+     * ファイル付きのみ
+     */
     "fileAttachedOnly": string;
+    /**
+     * TLに他の人への返信を含める
+     */
     "showRepliesToOthersInTimeline": string;
+    /**
+     * TLに他の人への返信を含めない
+     */
     "hideRepliesToOthersInTimeline": string;
+    /**
+     * TLに現在フォロー中の人全員の返信を含めるようにする
+     */
     "showRepliesToOthersInTimelineAll": string;
+    /**
+     * TLに現在フォロー中の人全員の返信を含めないようにする
+     */
     "hideRepliesToOthersInTimelineAll": string;
+    /**
+     * この操作は元に戻せません。本当にTLに現在フォロー中の人全員の返信を含めるようにしますか?
+     */
     "confirmShowRepliesAll": string;
+    /**
+     * この操作は元に戻せません。本当にTLに現在フォロー中の人全員の返信を含めないようにしますか?
+     */
     "confirmHideRepliesAll": string;
+    /**
+     * 外部サービス
+     */
     "externalServices": string;
+    /**
+     * ソースコード
+     */
+    "sourceCode": string;
+    /**
+     * ソースコードはまだ提供されていません。この問題の修正について管理者に問い合わせてください。
+     */
+    "sourceCodeIsNotYetProvided": string;
+    /**
+     * リポジトリURL
+     */
+    "repositoryUrl": string;
+    /**
+     * ソースコードが公開されているリポジトリがある場合、そのURLを記入します。Misskeyを現状のまま(ソースコードにいかなる変更も加えずに)使用している場合は https://github.com/misskey-dev/misskey と記入します。
+     */
+    "repositoryUrlDescription": string;
+    /**
+     * リポジトリを公開していない場合、代わりにtarballを提供する必要があります。詳細は.config/example.ymlを参照してください。
+     */
+    "repositoryUrlOrTarballRequired": string;
+    /**
+     * フィードバック
+     */
+    "feedback": string;
+    /**
+     * フィードバックURL
+     */
+    "feedbackUrl": string;
+    /**
+     * 運営者情報
+     */
     "impressum": string;
+    /**
+     * 運営者情報URL
+     */
     "impressumUrl": string;
+    /**
+     * ドイツなどの一部の国と地域では表示が義務付けられています(Impressum)。
+     */
     "impressumDescription": string;
+    /**
+     * プライバシーポリシー
+     */
     "privacyPolicy": string;
+    /**
+     * プライバシーポリシーURL
+     */
     "privacyPolicyUrl": string;
+    /**
+     * 利用規約・プライバシーポリシー
+     */
     "tosAndPrivacyPolicy": string;
+    /**
+     * 寄付する
+     */
+    "donation": string;
+    /**
+     * 寄付URL
+     */
+    "donationUrl": string;
+    /**
+     * アイコンデコレーション
+     */
     "avatarDecorations": string;
+    /**
+     * 付ける
+     */
     "attach": string;
+    /**
+     * 外す
+     */
     "detach": string;
+    /**
+     * 全て外す
+     */
     "detachAll": string;
+    /**
+     * 角度
+     */
     "angle": string;
+    /**
+     * 反転
+     */
     "flip": string;
+    /**
+     * アイコンのデコレーションを表示
+     */
     "showAvatarDecorations": string;
+    /**
+     * 離してリロード
+     */
     "releaseToRefresh": string;
+    /**
+     * リロード中
+     */
     "refreshing": string;
+    /**
+     * 引っ張ってリロード
+     */
     "pullDownToRefresh": string;
+    /**
+     * タイムラインのリアルタイム更新を無効にする
+     */
     "disableStreamingTimeline": string;
+    /**
+     * 通知をグルーピングして表示する
+     */
     "useGroupedNotifications": string;
+    /**
+     * メールアドレスの確認中に問題が発生しました。リンクの有効期限が切れている可能性があります。
+     */
     "signupPendingError": string;
+    /**
+     * 「内容を隠す」がオンの場合は注釈の記述が必要です。
+     */
     "cwNotationRequired": string;
+    /**
+     * リアクションする
+     */
     "doReaction": string;
+    /**
+     * コード
+     */
     "code": string;
+    /**
+     * 設定の反映にはリロードが必要です。
+     */
     "reloadRequiredToApplySettings": string;
-    "remainingN": string;
+    /**
+     * 残り: {n}
+     */
+    "remainingN": ParameterizedString<"n">;
+    /**
+     * 現在の内容に上書きされますがよろしいですか?
+     */
     "overwriteContentConfirm": string;
+    /**
+     * 季節に応じた画面の演出
+     */
     "seasonalScreenEffect": string;
+    /**
+     * デコる
+     */
     "decorate": string;
+    /**
+     * 装飾を追加
+     */
     "addMfmFunction": string;
+    /**
+     * 高度なMFMのピッカーを表示する
+     */
     "enableQuickAddMfmFunction": string;
+    /**
+     * バブルゲーム
+     */
+    "bubbleGame": string;
+    /**
+     * 効果音
+     */
+    "sfx": string;
+    /**
+     * サウンドが再生されます
+     */
+    "soundWillBePlayed": string;
+    /**
+     * リプレイを見る
+     */
+    "showReplay": string;
+    /**
+     * リプレイ
+     */
+    "replay": string;
+    /**
+     * リプレイ中
+     */
+    "replaying": string;
+    /**
+     * リプレイを終了
+     */
+    "endReplay": string;
+    /**
+     * リプレイデータをコピー
+     */
+    "copyReplayData": string;
+    /**
+     * ランキング
+     */
+    "ranking": string;
+    /**
+     * 直近{n}日
+     */
+    "lastNDays": ParameterizedString<"n">;
+    /**
+     * タイトルへ
+     */
+    "backToTitle": string;
+    /**
+     * お住まいの地域
+     */
+    "hemisphere": string;
+    /**
+     * センシティブなファイルを含むノートを表示
+     */
+    "withSensitive": string;
+    /**
+     * {name}のセンシティブなファイルを含む投稿
+     */
+    "userSaysSomethingSensitive": ParameterizedString<"name">;
+    /**
+     * スワイプしてタブを切り替える
+     */
+    "enableHorizontalSwipe": string;
+    /**
+     * 読み込み中
+     */
+    "loading": string;
+    /**
+     * やめる
+     */
+    "surrender": string;
+    /**
+     * リトライ
+     */
+    "gameRetry": string;
+    "_bubbleGame": {
+        /**
+         * 遊び方
+         */
+        "howToPlay": string;
+        /**
+         * ホールド
+         */
+        "hold": string;
+        "_score": {
+            /**
+             * スコア
+             */
+            "score": string;
+            /**
+             * 稼いだ金額
+             */
+            "scoreYen": string;
+            /**
+             * ハイスコア
+             */
+            "highScore": string;
+            /**
+             * 最大チェーン数
+             */
+            "maxChain": string;
+            /**
+             * {yen}円
+             */
+            "yen": ParameterizedString<"yen">;
+            /**
+             * {qty}個分
+             */
+            "estimatedQty": ParameterizedString<"qty">;
+            /**
+             * おにぎり {onigiriQtyWithUnit}
+             */
+            "scoreSweets": ParameterizedString<"onigiriQtyWithUnit">;
+        };
+        "_howToPlay": {
+            /**
+             * 位置を調整してハコにモノを落とします。
+             */
+            "section1": string;
+            /**
+             * 同じ種類のモノがくっつくと別のモノに変化して、スコアが得られます。
+             */
+            "section2": string;
+            /**
+             * モノがハコからあふれるとゲームオーバーです。ハコからあふれないようにしつつモノを融合させてハイスコアを目指そう!
+             */
+            "section3": string;
+        };
+    };
     "_announcement": {
+        /**
+         * 既存ユーザーのみ
+         */
         "forExistingUsers": string;
+        /**
+         * 有効にすると、このお知らせ作成時点で存在するユーザーにのみお知らせが表示されます。無効にすると、このお知らせ作成後にアカウントを作成したユーザーにもお知らせが表示されます。
+         */
         "forExistingUsersDescription": string;
+        /**
+         * 既読にするのに確認が必要
+         */
         "needConfirmationToRead": string;
+        /**
+         * 有効にすると、このお知らせを既読にする際に確認ダイアログが表示されます。また、一括既読操作の対象になりません。
+         */
         "needConfirmationToReadDescription": string;
+        /**
+         * お知らせを終了
+         */
         "end": string;
+        /**
+         * アクティブなお知らせが多いため、UXが低下する可能性があります。終了したお知らせはアーカイブすることを検討してください。
+         */
         "tooManyActiveAnnouncementDescription": string;
+        /**
+         * 既読にしますか?
+         */
         "readConfirmTitle": string;
-        "readConfirmText": string;
+        /**
+         * 「{title}」の内容を読み、既読にします。
+         */
+        "readConfirmText": ParameterizedString<"title">;
+        /**
+         * 特に新規ユーザーのUXを損ねる可能性が高いため、常時掲示するための情報ではなく、即時性が求められる情報の掲示のためにお知らせを使用することを推奨します。
+         */
         "shouldNotBeUsedToPresentPermanentInfo": string;
+        /**
+         * ダイアログ形式のお知らせが同時に2つ以上ある場合、UXに悪影響を及ぼす可能性が非常に高いため、使用は慎重に行うことを推奨します。
+         */
         "dialogAnnouncementUxWarn": string;
+        /**
+         * 非通知
+         */
         "silence": string;
+        /**
+         * オンにすると、このお知らせは通知されず、既読にする必要もなくなります。
+         */
         "silenceDescription": string;
     };
     "_initialAccountSetting": {
+        /**
+         * アカウントの作成が完了しました!
+         */
         "accountCreated": string;
+        /**
+         * さっそくアカウントの初期設定を行いましょう。
+         */
         "letsStartAccountSetup": string;
+        /**
+         * まずはあなたのプロフィールを設定しましょう。
+         */
         "letsFillYourProfile": string;
+        /**
+         * プロフィール設定
+         */
         "profileSetting": string;
+        /**
+         * プライバシー設定
+         */
         "privacySetting": string;
+        /**
+         * これらの設定は後から変更できます。
+         */
         "theseSettingsCanEditLater": string;
+        /**
+         * この他にも様々な設定を「設定」ページから行えます。ぜひ後で確認してみてください。
+         */
         "youCanEditMoreSettingsInSettingsPageLater": string;
+        /**
+         * タイムラインを構築するため、気になるユーザーをフォローしてみましょう。
+         */
         "followUsers": string;
-        "pushNotificationDescription": string;
+        /**
+         * プッシュ通知を有効にすると{name}の通知をお使いのデバイスで受け取ることができます。
+         */
+        "pushNotificationDescription": ParameterizedString<"name">;
+        /**
+         * 初期設定が完了しました!
+         */
         "initialAccountSettingCompleted": string;
-        "haveFun": string;
-        "youCanContinueTutorial": string;
+        /**
+         * {name}をお楽しみください!
+         */
+        "haveFun": ParameterizedString<"name">;
+        /**
+         * このまま{name}(Sharkey)の使い方についてのチュートリアルに進むこともできますが、ここで中断してすぐに使い始めることもできます。
+         */
+        "youCanContinueTutorial": ParameterizedString<"name">;
+        /**
+         * チュートリアルを開始
+         */
         "startTutorial": string;
+        /**
+         * 初期設定をスキップしますか?
+         */
         "skipAreYouSure": string;
+        /**
+         * 初期設定をあとでやり直しますか?
+         */
         "laterAreYouSure": string;
     };
     "_initialTutorial": {
+        /**
+         * チュートリアルを見る
+         */
         "launchTutorial": string;
+        /**
+         * チュートリアル
+         */
         "title": string;
+        /**
+         * よくできました
+         */
         "wellDone": string;
+        /**
+         * チュートリアルを終了しますか?
+         */
         "skipAreYouSure": string;
         "_landing": {
+            /**
+             * チュートリアルへようこそ
+             */
             "title": string;
+            /**
+             * ここでは、Sharkeyの基本的な使い方や機能を確認できます。
+             */
             "description": string;
         };
         "_note": {
+            /**
+             * ノートって何?
+             */
             "title": string;
+            /**
+             * Sharkeyでの投稿は「ノート」と呼びます。ノートはタイムラインに時系列で並んでいて、リアルタイムで更新されていきます。
+             */
             "description": string;
+            /**
+             * 返信することができます。返信に対しての返信も可能で、スレッドのように会話を続けることもできます。
+             */
             "reply": string;
+            /**
+             * そのノートを自分のタイムラインに流して共有することができます。テキストを追加して引用することも可能です。
+             */
             "renote": string;
+            /**
+             * リアクションをつけることができます。詳しくは次のページで解説します。
+             */
             "reaction": string;
+            /**
+             * ノートの詳細を表示したり、リンクをコピーしたりなどの様々な操作が行えます。
+             */
             "menu": string;
         };
         "_reaction": {
+            /**
+             * リアクションって何?
+             */
             "title": string;
+            /**
+             * ノートには「リアクション」をつけることができます。「いいね」では伝わらないニュアンスも、リアクションで簡単・気軽に表現できます。
+             */
             "description": string;
+            /**
+             * リアクションは、ノートの「+」ボタンをクリックするとつけられます。試しにこのサンプルのノートにリアクションをつけてみてください!
+             */
             "letsTryReacting": string;
+            /**
+             * リアクションをつけると先に進めるようになります。
+             */
             "reactToContinue": string;
+            /**
+             * あなたのノートが誰かにリアクションされると、リアルタイムで通知を受け取ります。
+             */
             "reactNotification": string;
+            /**
+             * 「ー」ボタンを押すとリアクションを取り消すことができます。
+             */
             "reactDone": string;
         };
         "_timeline": {
+            /**
+             * タイムラインのしくみ
+             */
             "title": string;
+            /**
+             * Sharkeyには、使い方に応じて複数のタイムラインが用意されています(サーバーによってはいずれかが無効になっていることがあります)。
+             */
             "description1": string;
+            /**
+             * あなたがフォローしているアカウントの投稿を見られます。
+             */
             "home": string;
+            /**
+             * このサーバーにいるユーザー全員の投稿を見られます。
+             */
             "local": string;
+            /**
+             * ホームタイムラインとローカルタイムラインの投稿が両方表示されます。
+             */
             "social": string;
+            /**
+             * 接続している他のすべてのサーバーからの投稿を見られます。
+             */
             "global": string;
+            /**
+             * それぞれのタイムラインは、画面上部でいつでも切り替えられます。
+             */
             "description2": string;
-            "description3": string;
+            /**
+             * その他にも、リストタイムラインやチャンネルタイムラインなどがあります。詳しくは{link}をご覧ください。
+             */
+            "description3": ParameterizedString<"link">;
         };
         "_postNote": {
+            /**
+             * ノートの投稿設定
+             */
             "title": string;
+            /**
+             * Sharkeyにノートを投稿する際には、様々なオプションの設定が可能です。投稿フォームはこのようになっています。
+             */
             "description1": string;
             "_visibility": {
+                /**
+                 * ノートを表示できる相手を制限できます。
+                 */
                 "description": string;
+                /**
+                 * すべてのユーザーに公開。
+                 */
                 "public": string;
+                /**
+                 * ホームタイムラインのみに公開。フォロワー・プロフィールを見に来た人・ブーストから、他のユーザーも見ることができます。
+                 */
                 "home": string;
+                /**
+                 * フォロワーにのみ公開。本人以外がブーストすることはできず、またフォロワー以外は閲覧できません。
+                 */
                 "followers": string;
+                /**
+                 * 指定したユーザーにのみ公開され、また相手に通知が入ります。ダイレクトメッセージのかわりにお使いいただけます。
+                 */
                 "direct": string;
+                /**
+                 * 機密情報は送信する際は注意してください。
+                 */
                 "doNotSendConfidencialOnDirect1": string;
+                /**
+                 * 送信先のサーバーの管理者は投稿内容を見ることが可能なので、信頼できないサーバーのユーザーにダイレクト投稿を送信する場合は、機密情報の扱いに注意が必要です。
+                 */
                 "doNotSendConfidencialOnDirect2": string;
+                /**
+                 * 他のサーバーに投稿を連合しません。上記の公開範囲に関わらず、他のサーバーのユーザーは、この設定がついたノートを直接閲覧することができなくなります。
+                 */
                 "localOnly": string;
             };
             "_cw": {
+                /**
+                 * 内容を隠す(CW)
+                 */
                 "title": string;
+                /**
+                 * 本文のかわりに「注釈」に書いた内容が表示されます。「もっと見る」を押すと本文が表示されます。
+                 */
                 "description": string;
                 "_exampleNote": {
+                    /**
+                     * 飯テロ注意
+                     */
                     "cw": string;
+                    /**
+                     * チョコのかかったドーナツを食べました🍩😋
+                     */
                     "note": string;
                 };
+                /**
+                 * サーバーのガイドラインにより必要とされるノートに指定したり、ネタバレ投稿やセンシティブな文章を自主規制したりするときに使います。
+                 */
                 "useCases": string;
             };
         };
         "_howToMakeAttachmentsSensitive": {
+            /**
+             * 添付ファイルをセンシティブにするには?
+             */
             "title": string;
+            /**
+             * サーバーのガイドラインにより必要とされる際や、そのまま見れる状態にしておくべきではない添付ファイルには、「センシティブ」設定を付けます。
+             */
             "description": string;
+            /**
+             * 試しに、このフォームに添付された画像をセンシティブにしてみてください!
+             */
             "tryThisFile": string;
             "_exampleNote": {
+                /**
+                 * 納豆のフタ開けるのミスったわね…
+                 */
                 "note": string;
             };
+            /**
+             * 添付ファイルをセンシティブにする際は、そのファイルをクリックしてメニューを開き、「センシティブとして設定」をクリックします。
+             */
             "method": string;
+            /**
+             * ファイルを添付する際は、サーバーのガイドラインに従ってセンシティブを適切に設定してください。
+             */
             "sensitiveSucceeded": string;
+            /**
+             * 画像をセンシティブに設定すると先に進めるようになります。
+             */
             "doItToContinue": string;
         };
         "_done": {
+            /**
+             * チュートリアルは終了です🎉
+             */
             "title": string;
-            "description": string;
+            /**
+             * ここで紹介した機能はほんの一部にすぎません。Sharkeyの使い方をより詳しく知るには、{link}をご覧ください。
+             */
+            "description": ParameterizedString<"link">;
         };
     };
     "_timelineDescription": {
+        /**
+         * ホームタイムラインでは、あなたがフォローしているアカウントの投稿を見られます。
+         */
         "home": string;
+        /**
+         * ローカルタイムラインでは、このサーバーにいるユーザー全員の投稿を見られます。
+         */
         "local": string;
+        /**
+         * ソーシャルタイムラインには、ホームタイムラインとローカルタイムラインの投稿が両方表示されます。
+         */
         "social": string;
+        /**
+         * グローバルタイムラインでは、接続している他のすべてのサーバーからの投稿を見られます。
+         */
         "global": string;
     };
     "_serverRules": {
+        /**
+         * 新規登録前に表示する、サーバーの簡潔なルールを設定します。内容は利用規約の要約とすることを推奨します。
+         */
         "description": string;
     };
     "_serverSettings": {
+        /**
+         * アイコン画像のURL
+         */
         "iconUrl": string;
-        "appIconDescription": string;
+        /**
+         * {host}がアプリとして表示される際のアイコンを指定します。
+         */
+        "appIconDescription": ParameterizedString<"host">;
+        /**
+         * 例: PWAや、スマートフォンのホーム画面にブックマークとして追加された時など
+         */
         "appIconUsageExample": string;
+        /**
+         * 円形もしくは角丸にクロップされる場合があるため、塗り潰された余白のある背景を持つことが推奨されます。
+         */
         "appIconStyleRecommendation": string;
-        "appIconResolutionMustBe": string;
+        /**
+         * 解像度は必ず{resolution}である必要があります。
+         */
+        "appIconResolutionMustBe": ParameterizedString<"resolution">;
+        /**
+         * manifest.jsonのオーバーライド
+         */
         "manifestJsonOverride": string;
+        /**
+         * 略称
+         */
         "shortName": string;
+        /**
+         * サーバーの正式名称が長い場合に、代わりに表示することのできる略称や通称。
+         */
         "shortNameDescription": string;
+        /**
+         * 有効にすると、各種タイムラインを取得する際のパフォーマンスが大幅に向上し、データベースへの負荷を軽減することが可能です。ただし、Redisのメモリ使用量は増加します。サーバーのメモリ容量が少ない場合、または動作が不安定な場合は無効にすることができます。
+         */
         "fanoutTimelineDescription": string;
+        /**
+         * データベースへのフォールバック
+         */
         "fanoutTimelineDbFallback": string;
+        /**
+         * 有効にすると、タイムラインがキャッシュされていない場合にDBへ追加で問い合わせを行うフォールバック処理を行います。無効にすると、フォールバック処理を行わないことでさらにサーバーの負荷を軽減することができますが、タイムラインが取得できる範囲に制限が生じます。
+         */
         "fanoutTimelineDbFallbackDescription": string;
     };
     "_accountMigration": {
+        /**
+         * 別のアカウントからこのアカウントに移行
+         */
         "moveFrom": string;
+        /**
+         * 別のアカウントへエイリアスを作成
+         */
         "moveFromSub": string;
-        "moveFromLabel": string;
+        /**
+         * 移行元のアカウント #{n}
+         */
+        "moveFromLabel": ParameterizedString<"n">;
+        /**
+         * 別のアカウントからこのアカウントに移行したい場合、ここでエイリアスを作成しておく必要があります。
+         * 移行元のアカウントをこのように入力してください: @username@server.example.com
+         * 削除するには、入力欄を空にして保存します(非推奨)。
+         */
         "moveFromDescription": string;
+        /**
+         * このアカウントを新しいアカウントへ移行
+         */
         "moveTo": string;
+        /**
+         * 移行先のアカウント:
+         */
         "moveToLabel": string;
+        /**
+         * アカウントを移行すると、取り消すことはできません。
+         */
         "moveCannotBeUndone": string;
+        /**
+         * 新しいアカウントへ移行します。
+         *  ・フォロワーが新しいアカウントを自動でフォローします
+         *  ・このアカウントからのフォローは全て解除されます
+         *  ・このアカウントではノートの作成などができなくなります
+         *
+         * フォロワーの移行は自動ですが、フォローの移行は手動で行う必要があります。移行前にこのアカウントでフォローエクスポートし、移行後すぐに移行先アカウントでインポートを行なってください。
+         * リスト・ミュート・ブロックについても同様ですので、手動で移行する必要があります。
+         *
+         * (この説明はこのサーバー(Sharkey v13.12.0以降)の仕様です。Mastodonなどの他のActivityPubソフトウェアでは挙動が異なる場合があります。)
+         */
         "moveAccountDescription": string;
+        /**
+         * アカウントの移行には、まずは移行先のアカウントでこのアカウントに対しエイリアスを作成します。
+         * エイリアス作成後、移行先のアカウントを次のように入力してください: @username@server.example.com
+         */
         "moveAccountHowTo": string;
+        /**
+         * 移行する
+         */
         "startMigration": string;
-        "migrationConfirm": string;
+        /**
+         * 本当にこのアカウントを {account} に移行しますか?一度移行すると取り消せず、二度とこのアカウントを元の状態で使用できなくなります。
+         */
+        "migrationConfirm": ParameterizedString<"account">;
+        /**
+         *
+         * アカウントは移行されています。
+         * 移行を取り消すことはできません。
+         */
         "movedAndCannotBeUndone": string;
+        /**
+         * このアカウントからのフォロー解除は移行操作から24時間後に実行されます。
+         * このアカウントのフォロー・フォロワー数は0になっています。フォロワーの解除はされないため、あなたのフォロワーはこのアカウントのフォロワー向け投稿を引き続き閲覧できます。
+         */
         "postMigrationNote": string;
+        /**
+         * 移行先のアカウント:
+         */
         "movedTo": string;
     };
     "_achievements": {
+        /**
+         * 獲得日時
+         */
         "earnedAt": string;
         "_types": {
             "_notes1": {
+                /**
+                 * just setting up my shonk
+                 */
                 "title": string;
+                /**
+                 * 初めてノートを投稿した
+                 */
                 "description": string;
+                /**
+                 * 良いSharkeyライフを!
+                 */
                 "flavor": string;
             };
             "_notes10": {
+                /**
+                 * いくつかのノート
+                 */
                 "title": string;
+                /**
+                 * ノートを10回投稿した
+                 */
                 "description": string;
             };
             "_notes100": {
+                /**
+                 * たくさんのノート
+                 */
                 "title": string;
+                /**
+                 * ノートを100回投稿した
+                 */
                 "description": string;
             };
             "_notes500": {
+                /**
+                 * ノートまみれ
+                 */
                 "title": string;
+                /**
+                 * ノートを500回投稿した
+                 */
                 "description": string;
             };
             "_notes1000": {
+                /**
+                 * ノートの山
+                 */
                 "title": string;
+                /**
+                 * ノートを1,000回投稿した
+                 */
                 "description": string;
             };
             "_notes5000": {
+                /**
+                 * 湧き出るノート
+                 */
                 "title": string;
+                /**
+                 * ノートを5,000回投稿した
+                 */
                 "description": string;
             };
             "_notes10000": {
+                /**
+                 * スーパーノート
+                 */
                 "title": string;
+                /**
+                 * ノートを10,000回投稿した
+                 */
                 "description": string;
             };
             "_notes20000": {
+                /**
+                 * ニードモアノート
+                 */
                 "title": string;
+                /**
+                 * ノートを20,000回投稿した
+                 */
                 "description": string;
             };
             "_notes30000": {
+                /**
+                 * ノートノートノート
+                 */
                 "title": string;
+                /**
+                 * ノートを30,000回投稿した
+                 */
                 "description": string;
             };
             "_notes40000": {
+                /**
+                 * ノート工場
+                 */
                 "title": string;
+                /**
+                 * ノートを40,000回投稿した
+                 */
                 "description": string;
             };
             "_notes50000": {
+                /**
+                 * ノートの惑星
+                 */
                 "title": string;
+                /**
+                 * ノートを50,000回投稿した
+                 */
                 "description": string;
             };
             "_notes60000": {
+                /**
+                 * ノートクエーサー
+                 */
                 "title": string;
+                /**
+                 * ノートを60,000回投稿した
+                 */
                 "description": string;
             };
             "_notes70000": {
+                /**
+                 * ブラックノートホール
+                 */
                 "title": string;
+                /**
+                 * ノートを70,000回投稿した
+                 */
                 "description": string;
             };
             "_notes80000": {
+                /**
+                 * ノートギャラクシー
+                 */
                 "title": string;
+                /**
+                 * ノートを80,000回投稿した
+                 */
                 "description": string;
             };
             "_notes90000": {
+                /**
+                 * ノートバース
+                 */
                 "title": string;
+                /**
+                 * ノートを90,000回投稿した
+                 */
                 "description": string;
             };
             "_notes100000": {
+                /**
+                 * ALL YOUR NOTE ARE BELONG TO US
+                 */
                 "title": string;
+                /**
+                 * ノートを100,000回投稿した
+                 */
                 "description": string;
+                /**
+                 * そんなに書くことある?
+                 */
                 "flavor": string;
             };
             "_login3": {
+                /**
+                 * ビギナーⅠ
+                 */
                 "title": string;
+                /**
+                 * 通算ログイン日数が3日
+                 */
                 "description": string;
+                /**
+                 * 今日からね僕は ミスキストってことで
+                 */
                 "flavor": string;
             };
             "_login7": {
+                /**
+                 * ビギナーⅡ
+                 */
                 "title": string;
+                /**
+                 * 通算ログイン日数が7日
+                 */
                 "description": string;
+                /**
+                 * 慣れてきましたか?
+                 */
                 "flavor": string;
             };
             "_login15": {
+                /**
+                 * ビギナーⅢ
+                 */
                 "title": string;
+                /**
+                 * 通算ログイン日数が15日
+                 */
                 "description": string;
             };
             "_login30": {
+                /**
+                 * ミスキストⅠ
+                 */
                 "title": string;
+                /**
+                 * 通算ログイン日数が30日
+                 */
                 "description": string;
             };
             "_login60": {
+                /**
+                 * ミスキストⅡ
+                 */
                 "title": string;
+                /**
+                 * 通算ログイン日数が60日
+                 */
                 "description": string;
             };
             "_login100": {
+                /**
+                 * ミスキストⅢ
+                 */
                 "title": string;
+                /**
+                 * 通算ログイン日数が100日
+                 */
                 "description": string;
+                /**
+                 * そのユーザー、ミスキストにつき
+                 */
                 "flavor": string;
             };
             "_login200": {
+                /**
+                 * 常連Ⅰ
+                 */
                 "title": string;
+                /**
+                 * 通算ログイン日数が200日
+                 */
                 "description": string;
             };
             "_login300": {
+                /**
+                 * 常連Ⅱ
+                 */
                 "title": string;
+                /**
+                 * 通算ログイン日数が300日
+                 */
                 "description": string;
             };
             "_login400": {
+                /**
+                 * 常連Ⅲ
+                 */
                 "title": string;
+                /**
+                 * 通算ログイン日数が400日
+                 */
                 "description": string;
             };
             "_login500": {
+                /**
+                 * ベテランⅠ
+                 */
                 "title": string;
+                /**
+                 * 通算ログイン日数が500日
+                 */
                 "description": string;
+                /**
+                 * 諸君、私はノートが好きだ
+                 */
                 "flavor": string;
             };
             "_login600": {
+                /**
+                 * ベテランⅡ
+                 */
                 "title": string;
+                /**
+                 * 通算ログイン日数が600日
+                 */
                 "description": string;
             };
             "_login700": {
+                /**
+                 * ベテランⅢ
+                 */
                 "title": string;
+                /**
+                 * 通算ログイン日数が700日
+                 */
                 "description": string;
             };
             "_login800": {
+                /**
+                 * ノートマスターⅠ
+                 */
                 "title": string;
+                /**
+                 * 通算ログイン日数が800日
+                 */
                 "description": string;
             };
             "_login900": {
+                /**
+                 * ノートマスターⅡ
+                 */
                 "title": string;
+                /**
+                 * 通算ログイン日数が900日
+                 */
                 "description": string;
             };
             "_login1000": {
+                /**
+                 * ノートマスターⅢ
+                 */
                 "title": string;
+                /**
+                 * 通算ログイン日数が1,000日
+                 */
                 "description": string;
+                /**
+                 * Sharkeyを使ってくれてありがとう!
+                 */
                 "flavor": string;
             };
             "_noteClipped1": {
+                /**
+                 * クリップせずにはいられないな
+                 */
                 "title": string;
+                /**
+                 * 初めてノートをクリップした
+                 */
                 "description": string;
             };
             "_noteFavorited1": {
+                /**
+                 * 星をみるひと
+                 */
                 "title": string;
+                /**
+                 * 初めてノートをお気に入りに登録した
+                 */
                 "description": string;
             };
             "_myNoteFavorited1": {
+                /**
+                 * 星が欲しい
+                 */
                 "title": string;
+                /**
+                 * 自分のノートが他の人からお気に入りに登録された
+                 */
                 "description": string;
             };
             "_profileFilled": {
+                /**
+                 * 準備万端
+                 */
                 "title": string;
+                /**
+                 * プロフィール設定を行った
+                 */
                 "description": string;
             };
             "_markedAsCat": {
+                /**
+                 * 吾輩は猫である
+                 */
                 "title": string;
+                /**
+                 * アカウントをCatとして設定した
+                 */
                 "description": string;
+                /**
+                 * 名前はまだない。
+                 */
                 "flavor": string;
             };
             "_following1": {
+                /**
+                 * はじめてのフォロー
+                 */
                 "title": string;
+                /**
+                 * 初めてフォローした
+                 */
                 "description": string;
             };
             "_following10": {
+                /**
+                 * ついてく、ついてく
+                 */
                 "title": string;
+                /**
+                 * フォローが10人を超した
+                 */
                 "description": string;
             };
             "_following50": {
+                /**
+                 * 友達たくさん
+                 */
                 "title": string;
+                /**
+                 * フォローが50人を超した
+                 */
                 "description": string;
             };
             "_following100": {
+                /**
+                 * 友達100人
+                 */
                 "title": string;
+                /**
+                 * フォローが100人を超した
+                 */
                 "description": string;
             };
             "_following300": {
+                /**
+                 * 友達過多
+                 */
                 "title": string;
+                /**
+                 * フォローが300人を超した
+                 */
                 "description": string;
             };
             "_followers1": {
+                /**
+                 * はじめてのフォロワー
+                 */
                 "title": string;
+                /**
+                 * 初めてフォローされた
+                 */
                 "description": string;
             };
             "_followers10": {
+                /**
+                 * フォローミー!
+                 */
                 "title": string;
+                /**
+                 * フォロワーが10人を超した
+                 */
                 "description": string;
             };
             "_followers50": {
+                /**
+                 * ぞろぞろ
+                 */
                 "title": string;
+                /**
+                 * フォロワーが50人を超した
+                 */
                 "description": string;
             };
             "_followers100": {
+                /**
+                 * 人気者
+                 */
                 "title": string;
+                /**
+                 * フォロワーが100人を超した
+                 */
                 "description": string;
             };
             "_followers300": {
+                /**
+                 * 一列でお並びください
+                 */
                 "title": string;
+                /**
+                 * フォロワーが300人を超した
+                 */
                 "description": string;
             };
             "_followers500": {
+                /**
+                 * 基地局
+                 */
                 "title": string;
+                /**
+                 * フォロワーが500人を超した
+                 */
                 "description": string;
             };
             "_followers1000": {
+                /**
+                 * インフルエンサー
+                 */
                 "title": string;
+                /**
+                 * フォロワーが1,000人を超した
+                 */
                 "description": string;
             };
             "_collectAchievements30": {
+                /**
+                 * 実績コレクター
+                 */
                 "title": string;
+                /**
+                 * 実績を30個以上獲得した
+                 */
                 "description": string;
             };
             "_viewAchievements3min": {
+                /**
+                 * 実績好き
+                 */
                 "title": string;
+                /**
+                 * 実績一覧を3分以上眺め続けた
+                 */
                 "description": string;
             };
             "_iLoveMisskey": {
+                /**
+                 * I Love Sharkey
+                 */
                 "title": string;
+                /**
+                 * "I ❤ #Sharkey"を投稿した
+                 */
                 "description": string;
+                /**
+                 * Sharkeyを使ってくださりありがとうございます! by 開発チーム
+                 */
                 "flavor": string;
             };
             "_foundTreasure": {
+                /**
+                 * 宝探し
+                 */
                 "title": string;
+                /**
+                 * 隠されたお宝を発見した
+                 */
                 "description": string;
             };
             "_client30min": {
+                /**
+                 * ひとやすみ
+                 */
                 "title": string;
+                /**
+                 * クライアントを起動してから30分以上経過した
+                 */
                 "description": string;
             };
             "_client60min": {
+                /**
+                 * Sharkeyの見すぎ
+                 */
                 "title": string;
+                /**
+                 * クライアントを起動してから60分以上経過した
+                 */
                 "description": string;
             };
             "_noteDeletedWithin1min": {
+                /**
+                 * いまのなし
+                 */
                 "title": string;
+                /**
+                 * 投稿してから1分以内にその投稿を削除した
+                 */
                 "description": string;
             };
             "_postedAtLateNight": {
+                /**
+                 * 夜行性
+                 */
                 "title": string;
+                /**
+                 * 深夜にノートを投稿した
+                 */
                 "description": string;
+                /**
+                 * そろそろ寝よう。
+                 */
                 "flavor": string;
             };
             "_postedAt0min0sec": {
+                /**
+                 * 時報
+                 */
                 "title": string;
+                /**
+                 * 0分0秒にノートを投稿した
+                 */
                 "description": string;
+                /**
+                 * ポッ ポッ ポッ ピーン
+                 */
                 "flavor": string;
             };
             "_selfQuote": {
+                /**
+                 * 自己言及
+                 */
                 "title": string;
+                /**
+                 * 自分のノートを引用した
+                 */
                 "description": string;
             };
             "_htl20npm": {
+                /**
+                 * 流れるTL
+                 */
                 "title": string;
+                /**
+                 * ホームタイムラインの流速が20npmを越す
+                 */
                 "description": string;
             };
             "_viewInstanceChart": {
+                /**
+                 * アナリスト
+                 */
                 "title": string;
+                /**
+                 * サーバーのチャートを表示した
+                 */
                 "description": string;
             };
             "_outputHelloWorldOnScratchpad": {
+                /**
+                 * Hello, world!
+                 */
                 "title": string;
+                /**
+                 * スクラッチパッドで hello world を出力した
+                 */
                 "description": string;
             };
             "_open3windows": {
+                /**
+                 * マルチウィンドウ
+                 */
                 "title": string;
+                /**
+                 * ウィンドウを3つ以上開いた状態にした
+                 */
                 "description": string;
             };
             "_driveFolderCircularReference": {
+                /**
+                 * 循環参照
+                 */
                 "title": string;
+                /**
+                 * ドライブのフォルダを再帰的な入れ子にしようとした
+                 */
                 "description": string;
             };
             "_reactWithoutRead": {
+                /**
+                 * ちゃんと読んだ?
+                 */
                 "title": string;
+                /**
+                 * 100文字以上のテキストを含むノートに投稿されてから3秒以内にリアクションした
+                 */
                 "description": string;
             };
             "_clickedClickHere": {
+                /**
+                 * ここをクリック
+                 */
                 "title": string;
+                /**
+                 * ここをクリックした
+                 */
                 "description": string;
             };
             "_justPlainLucky": {
+                /**
+                 * 単なるラッキー
+                 */
                 "title": string;
+                /**
+                 * 10秒ごとに0.005%の確率で獲得
+                 */
                 "description": string;
             };
             "_setNameToSyuilo": {
+                /**
+                 * 神様コンプレックス
+                 */
                 "title": string;
+                /**
+                 * 名前を syuilo に設定した
+                 */
                 "description": string;
             };
             "_passedSinceAccountCreated1": {
+                /**
+                 * 一周年
+                 */
                 "title": string;
+                /**
+                 * アカウント作成から1年経過した
+                 */
                 "description": string;
             };
             "_passedSinceAccountCreated2": {
+                /**
+                 * 二周年
+                 */
                 "title": string;
+                /**
+                 * アカウント作成から2年経過した
+                 */
                 "description": string;
             };
             "_passedSinceAccountCreated3": {
+                /**
+                 * 三周年
+                 */
                 "title": string;
+                /**
+                 * アカウント作成から3年経過した
+                 */
                 "description": string;
             };
             "_loggedInOnBirthday": {
+                /**
+                 * ハッピーバースデー
+                 */
                 "title": string;
+                /**
+                 * 誕生日にログインした
+                 */
                 "description": string;
             };
             "_loggedInOnNewYearsDay": {
+                /**
+                 * あけましておめでとうございます
+                 */
                 "title": string;
+                /**
+                 * 元日にログインした
+                 */
                 "description": string;
+                /**
+                 * 今年も弊サーバーをよろしくお願いします
+                 */
                 "flavor": string;
             };
             "_cookieClicked": {
+                /**
+                 * クッキーをクリックするゲーム
+                 */
                 "title": string;
+                /**
+                 * クッキーをクリックした
+                 */
                 "description": string;
+                /**
+                 * ソフト間違ってない?
+                 */
                 "flavor": string;
             };
             "_brainDiver": {
+                /**
+                 * Brain Diver
+                 */
                 "title": string;
+                /**
+                 * Brain Diverへのリンクを投稿した
+                 */
                 "description": string;
+                /**
+                 * Misskey-Misskey La-Tu-Ma
+                 */
                 "flavor": string;
             };
             "_smashTestNotificationButton": {
+                /**
+                 * テスト過剰
+                 */
                 "title": string;
+                /**
+                 * 通知のテストをごく短時間のうちに連続して行った
+                 */
                 "description": string;
             };
             "_tutorialCompleted": {
+                /**
+                 * Sharkey初心者講座 修了証
+                 */
                 "title": string;
+                /**
+                 * チュートリアルを完了した
+                 */
                 "description": string;
             };
+            "_bubbleGameExplodingHead": {
+                /**
+                 * 🤯
+                 */
+                "title": string;
+                /**
+                 * バブルゲームで最も大きいモノを出した
+                 */
+                "description": string;
+            };
+            "_bubbleGameDoubleExplodingHead": {
+                /**
+                 * ダブル🤯
+                 */
+                "title": string;
+                /**
+                 * バブルゲームで最も大きいモノを2つ同時に出した
+                 */
+                "description": string;
+                /**
+                 * これくらいの おべんとばこに 🤯 🤯 ちょっとつめて
+                 */
+                "flavor": string;
+            };
         };
     };
     "_role": {
+        /**
+         * ロールの作成
+         */
         "new": string;
+        /**
+         * ロールの編集
+         */
         "edit": string;
+        /**
+         * ロール名
+         */
         "name": string;
+        /**
+         * ロールの説明
+         */
         "description": string;
+        /**
+         * ロールの権限
+         */
         "permission": string;
+        /**
+         * <b>モデレーター</b>は基本的なモデレーションに関する操作を行えます。
+         * <b>管理者</b>はサーバーの全ての設定を変更できます。
+         */
         "descriptionOfPermission": string;
+        /**
+         * アサイン
+         */
         "assignTarget": string;
+        /**
+         * <b>マニュアル</b>は誰がこのロールに含まれるかを手動で管理します。
+         * <b>コンディショナル</b>は条件を設定し、それに合致するユーザーが自動で含まれるようになります。
+         */
         "descriptionOfAssignTarget": string;
+        /**
+         * マニュアル
+         */
         "manual": string;
+        /**
+         * マニュアルロール
+         */
         "manualRoles": string;
+        /**
+         * コンディショナル
+         */
         "conditional": string;
+        /**
+         * コンディショナルロール
+         */
         "conditionalRoles": string;
+        /**
+         * 条件
+         */
         "condition": string;
+        /**
+         * これはコンディショナルロールです。
+         */
         "isConditionalRole": string;
+        /**
+         * 公開ロール
+         */
         "isPublic": string;
+        /**
+         * ユーザーのプロフィールでこのロールが表示されます。
+         */
         "descriptionOfIsPublic": string;
+        /**
+         * オプション
+         */
         "options": string;
+        /**
+         * ポリシー
+         */
         "policies": string;
+        /**
+         * ベースロール
+         */
         "baseRole": string;
+        /**
+         * ベースロールの値を使用
+         */
         "useBaseValue": string;
+        /**
+         * アサインするロールを選択
+         */
         "chooseRoleToAssign": string;
+        /**
+         * アイコン画像のURL
+         */
         "iconUrl": string;
+        /**
+         * バッジとして表示
+         */
         "asBadge": string;
+        /**
+         * オンにすると、ユーザー名の横にロールのアイコンが表示されます。
+         */
         "descriptionOfAsBadge": string;
+        /**
+         * ユーザーを見つけやすくする
+         */
         "isExplorable": string;
+        /**
+         * オンにすると、「みつける」でメンバー一覧が公開されるほか、ロールのタイムラインが利用可能になります。
+         */
         "descriptionOfIsExplorable": string;
+        /**
+         * 表示順
+         */
         "displayOrder": string;
+        /**
+         * 数値が大きいほどUI上で先頭に表示されます。
+         */
         "descriptionOfDisplayOrder": string;
+        /**
+         * モデレーターのメンバー編集を許可
+         */
         "canEditMembersByModerator": string;
+        /**
+         * オンにすると、管理者に加えてモデレーターもこのロールへユーザーをアサイン/アサイン解除できるようになります。オフにすると管理者のみが行えます。
+         */
         "descriptionOfCanEditMembersByModerator": string;
+        /**
+         * 優先度
+         */
         "priority": string;
         "_priority": {
+            /**
+             * 低
+             */
             "low": string;
+            /**
+             * 中
+             */
             "middle": string;
+            /**
+             * 高
+             */
             "high": string;
         };
         "_options": {
+            /**
+             * グローバルタイムラインの閲覧
+             */
             "gtlAvailable": string;
+            /**
+             * バブルタイムラインの閲覧
+             */
             "btlAvailable": string;
+            /**
+             * ローカルタイムラインの閲覧
+             */
             "ltlAvailable": string;
+            /**
+             * パブリック投稿の許可
+             */
             "canPublicNote": string;
+            /**
+             * ノートのインポートが可能
+             */
             "canImportNotes": string;
+            /**
+             * ノート内の最大メンション数
+             */
+            "mentionMax": string;
+            /**
+             * サーバー招待コードの発行
+             */
             "canInvite": string;
+            /**
+             * 招待コードの作成可能数
+             */
             "inviteLimit": string;
+            /**
+             * 招待コードの発行間隔
+             */
             "inviteLimitCycle": string;
+            /**
+             * 招待コードの有効期限
+             */
             "inviteExpirationTime": string;
+            /**
+             * カスタム絵文字の管理
+             */
             "canManageCustomEmojis": string;
+            /**
+             * アバターデコレーションの管理
+             */
             "canManageAvatarDecorations": string;
+            /**
+             * ドライブ容量
+             */
             "driveCapacity": string;
+            /**
+             * ファイルにNSFWを常に付与
+             */
             "alwaysMarkNsfw": string;
+            /**
+             * ノートのピン留めの最大数
+             */
             "pinMax": string;
+            /**
+             * アンテナの作成可能数
+             */
             "antennaMax": string;
+            /**
+             * ワードミュートの最大文字数
+             */
             "wordMuteMax": string;
+            /**
+             * Webhookの作成可能数
+             */
             "webhookMax": string;
+            /**
+             * クリップの作成可能数
+             */
             "clipMax": string;
+            /**
+             * クリップ内のノートの最大数
+             */
             "noteEachClipsMax": string;
+            /**
+             * ユーザーリストの作成可能数
+             */
             "userListMax": string;
+            /**
+             * ユーザーリスト内のユーザーの最大数
+             */
             "userEachUserListsMax": string;
+            /**
+             * レートリミット
+             */
             "rateLimitFactor": string;
+            /**
+             * 小さいほど制限が緩和され、大きいほど制限が強化されます。
+             */
             "descriptionOfRateLimitFactor": string;
+            /**
+             * 広告の非表示
+             */
             "canHideAds": string;
+            /**
+             * ノート検索の利用
+             */
             "canSearchNotes": string;
+            /**
+             * 翻訳機能の利用
+             */
             "canUseTranslator": string;
+            /**
+             * アイコンデコレーションの最大取付個数
+             */
             "avatarDecorationLimit": string;
         };
         "_condition": {
+            /**
+             * マニュアルロールにアサイン済み
+             */
+            "roleAssignedTo": string;
+            /**
+             * ローカルユーザー
+             */
             "isLocal": string;
+            /**
+             * リモートユーザー
+             */
             "isRemote": string;
+            /**
+             * アカウント作成から~以内
+             */
             "createdLessThan": string;
+            /**
+             * アカウント作成から~経過
+             */
             "createdMoreThan": string;
+            /**
+             * フォロワー数が~以下
+             */
             "followersLessThanOrEq": string;
+            /**
+             * フォロワー数が~以上
+             */
             "followersMoreThanOrEq": string;
+            /**
+             * フォロー数が~以下
+             */
             "followingLessThanOrEq": string;
+            /**
+             * フォロー数が~以上
+             */
             "followingMoreThanOrEq": string;
+            /**
+             * 投稿数が~以下
+             */
             "notesLessThanOrEq": string;
+            /**
+             * 投稿数が~以上
+             */
             "notesMoreThanOrEq": string;
+            /**
+             * ~かつ~
+             */
             "and": string;
+            /**
+             * ~または~
+             */
             "or": string;
+            /**
+             * ~ではない
+             */
             "not": string;
         };
     };
     "_sensitiveMediaDetection": {
+        /**
+         * 機械学習を使って自動でセンシティブなメディアを検出し、モデレーションに役立てることができます。サーバーの負荷が少し増えます。
+         */
         "description": string;
+        /**
+         * 検出感度
+         */
         "sensitivity": string;
+        /**
+         * 感度を低くすると、誤検知(偽陽性)が減ります。感度を高くすると、検知漏れ(偽陰性)が減ります。
+         */
         "sensitivityDescription": string;
+        /**
+         * センシティブフラグを設定する
+         */
         "setSensitiveFlagAutomatically": string;
+        /**
+         * この設定をオフにしても内部的に判定結果は保持されます。
+         */
         "setSensitiveFlagAutomaticallyDescription": string;
+        /**
+         * 動画の解析を有効化
+         */
         "analyzeVideos": string;
+        /**
+         * 静止画に加えて動画も解析するようにします。サーバーの負荷が少し増えます。
+         */
         "analyzeVideosDescription": string;
     };
     "_emailUnavailable": {
+        /**
+         * 既に使用されています
+         */
         "used": string;
+        /**
+         * 形式が正しくありません
+         */
         "format": string;
+        /**
+         * 恒久的に使用可能なアドレスではありません
+         */
         "disposable": string;
+        /**
+         * 正しいメールサーバーではありません
+         */
         "mx": string;
+        /**
+         * メールサーバーが応答しません
+         */
         "smtp": string;
+        /**
+         * このメールアドレスでは登録できません
+         */
         "banned": string;
     };
     "_ffVisibility": {
+        /**
+         * 公開
+         */
         "public": string;
+        /**
+         * フォロワーだけに公開
+         */
         "followers": string;
+        /**
+         * 非公開
+         */
         "private": string;
     };
     "_signup": {
+        /**
+         * ほとんど完了です
+         */
         "almostThere": string;
+        /**
+         * あなたが使っているメールアドレスを入力してください。メールアドレスが公開されることはありません。
+         */
         "emailAddressInfo": string;
-        "emailSent": string;
+        /**
+         * 入力されたメールアドレス({email})宛に確認のメールが送信されました。メールに記載されたリンクにアクセスすると、アカウントの作成が完了します。メールに記載されているリンクの有効期限は30分です。
+         */
+        "emailSent": ParameterizedString<"email">;
+        /**
+         * アカウントが作成され、承認待ちの状態です。
+         */
         "approvalPending": string;
+        /**
+         * インスタンスに参加したい理由を入力してください。
+         */
         "reasonInfo": string;
     };
     "_accountDelete": {
+        /**
+         * アカウントの削除
+         */
         "accountDelete": string;
+        /**
+         * アカウントの削除は負荷のかかる処理であるため、作成したコンテンツの数やアップロードしたファイルの数が多いと完了までに時間がかかることがあります。
+         */
         "mayTakeTime": string;
+        /**
+         * アカウントの削除が完了する際は、登録してあったメールアドレス宛に通知を送信します。
+         */
         "sendEmail": string;
+        /**
+         * アカウント削除をリクエスト
+         */
         "requestAccountDelete": string;
+        /**
+         * 削除処理が開始されました。
+         */
         "started": string;
+        /**
+         * 削除が進行中
+         */
         "inProgress": string;
     };
     "_ad": {
+        /**
+         * 戻る
+         */
         "back": string;
+        /**
+         * この広告の表示頻度を下げる
+         */
         "reduceFrequencyOfThisAd": string;
+        /**
+         * 表示しない
+         */
         "hide": string;
+        /**
+         * 曜日はサーバーのタイムゾーンを元に指定されます。
+         */
         "timezoneinfo": string;
+        /**
+         * 広告配信設定
+         */
         "adsSettings": string;
+        /**
+         * リアルタイム更新中に広告を配信する間隔(ノートの個数)
+         */
         "notesPerOneAd": string;
+        /**
+         * 0でリアルタイム更新時の広告配信を無効
+         */
         "setZeroToDisable": string;
+        /**
+         * 広告の配信間隔が極めて短いため、ユーザー体験が著しく損われる可能性があります。
+         */
         "adsTooClose": string;
     };
     "_forgotPassword": {
+        /**
+         * アカウントに登録したメールアドレスを入力してください。そのアドレス宛てに、パスワードリセット用のリンクが送信されます。
+         */
         "enterEmail": string;
+        /**
+         * メールアドレスを登録していない場合は、管理者までお問い合わせください。
+         */
         "ifNoEmail": string;
+        /**
+         * このサーバーではメールがサポートされていないため、パスワードリセットを行う場合は管理者までお問い合わせください。
+         */
         "contactAdmin": string;
     };
     "_gallery": {
+        /**
+         * 自分の投稿
+         */
         "my": string;
+        /**
+         * いいねした投稿
+         */
         "liked": string;
+        /**
+         * いいね!
+         */
         "like": string;
+        /**
+         * いいね解除
+         */
         "unlike": string;
     };
     "_email": {
         "_follow": {
+            /**
+             * フォローされました
+             */
             "title": string;
         };
         "_receiveFollowRequest": {
+            /**
+             * フォローリクエストを受け取りました
+             */
             "title": string;
         };
     };
     "_plugin": {
+        /**
+         * プラグインのインストール
+         */
         "install": string;
+        /**
+         * 信頼できないプラグインはインストールしないでください。
+         */
         "installWarn": string;
+        /**
+         * プラグインの管理
+         */
         "manage": string;
+        /**
+         * ソースを表示
+         */
         "viewSource": string;
     };
     "_preferencesBackups": {
+        /**
+         * 作成したバックアップ
+         */
         "list": string;
+        /**
+         * 新規保存
+         */
         "saveNew": string;
+        /**
+         * ファイルを読み込み
+         */
         "loadFile": string;
+        /**
+         * このデバイスに適用
+         */
         "apply": string;
+        /**
+         * 上書き保存
+         */
         "save": string;
+        /**
+         * バックアップ名を入力
+         */
         "inputName": string;
+        /**
+         * 保存できません
+         */
         "cannotSave": string;
-        "nameAlreadyExists": string;
-        "applyConfirm": string;
-        "saveConfirm": string;
-        "deleteConfirm": string;
-        "renameConfirm": string;
+        /**
+         * バックアップ名「{name}」は既に存在します。違う名前を指定してください。
+         */
+        "nameAlreadyExists": ParameterizedString<"name">;
+        /**
+         * バックアップ「{name}」を現在のデバイスに適用しますか?現在のデバイス設定は失われます。
+         */
+        "applyConfirm": ParameterizedString<"name">;
+        /**
+         * {name}に上書き保存しますか?
+         */
+        "saveConfirm": ParameterizedString<"name">;
+        /**
+         * {name}を削除しますか?
+         */
+        "deleteConfirm": ParameterizedString<"name">;
+        /**
+         * 「{old}」を「{new}」に変更しますか?
+         */
+        "renameConfirm": ParameterizedString<"old" | "new">;
+        /**
+         * バックアップはありません。「新規保存」で現在のクライアント設定をサーバーに保存できます。
+         */
         "noBackups": string;
-        "createdAt": string;
-        "updatedAt": string;
+        /**
+         * 作成日時: {date} {time}
+         */
+        "createdAt": ParameterizedString<"date" | "time">;
+        /**
+         * 更新日時: {date} {time}
+         */
+        "updatedAt": ParameterizedString<"date" | "time">;
+        /**
+         * 読み込みできません
+         */
         "cannotLoad": string;
+        /**
+         * ファイル形式が違います。
+         */
         "invalidFile": string;
     };
     "_registry": {
+        /**
+         * スコープ
+         */
         "scope": string;
+        /**
+         * キー
+         */
         "key": string;
+        /**
+         * キー
+         */
         "keys": string;
+        /**
+         * ドメイン
+         */
         "domain": string;
+        /**
+         * キーを作成
+         */
         "createKey": string;
     };
     "_aboutMisskey": {
+        /**
+         * Sharkeyは、Misskeyをベースにしたオープンソースのソフトウェアです。
+         */
         "about": string;
+        /**
+         * 主なコントリビューター
+         */
         "contributors": string;
+        /**
+         * 全てのコントリビューター
+         */
         "allContributors": string;
+        /**
+         * ソースコード
+         */
         "source": string;
+        /**
+         * Misskey オリジナル
+         */
+        "original": string;
+        /**
+         * Sharkey オリジナル
+         */
+        "original_sharkey": string;
+        /**
+         * {name}はオリジナルのSharkeyを改変したバージョンを使用しています。
+         */
+        "thisIsModifiedVersion": ParameterizedString<"name">;
+        /**
+         * Sharkeyを翻訳
+         */
         "translation": string;
+        /**
+         * Misskeyに寄付
+         */
         "donate": string;
+        /**
+         * Sharkeyに寄付
+         */
+        "donate_sharkey": string;
+        /**
+         * 他にも多くの方が支援してくれています。ありがとうございます🥰
+         */
         "morePatrons": string;
+        /**
+         * 支援者
+         */
         "patrons": string;
+        /**
+         * プロジェクトメンバー
+         */
         "projectMembers": string;
     };
     "_displayOfSensitiveMedia": {
+        /**
+         * センシティブ設定されたメディアを隠す
+         */
         "respect": string;
+        /**
+         * センシティブ設定されたメディアを隠さない
+         */
         "ignore": string;
+        /**
+         * 常にメディアを隠す
+         */
         "force": string;
     };
     "_instanceTicker": {
+        /**
+         * 表示しない
+         */
         "none": string;
+        /**
+         * リモートユーザーに表示
+         */
         "remote": string;
+        /**
+         * 常に表示
+         */
         "always": string;
     };
     "_serverDisconnectedBehavior": {
+        /**
+         * 自動でリロード
+         */
         "reload": string;
+        /**
+         * ダイアログで警告
+         */
         "dialog": string;
+        /**
+         * 控えめに警告
+         */
         "quiet": string;
+        /**
+         * 警告を無効にする
+         */
         "disabled": string;
     };
     "_channel": {
+        /**
+         * チャンネルを作成
+         */
         "create": string;
+        /**
+         * チャンネルを編集
+         */
         "edit": string;
+        /**
+         * バナーを設定
+         */
         "setBanner": string;
+        /**
+         * バナーを削除
+         */
         "removeBanner": string;
+        /**
+         * トレンド
+         */
         "featured": string;
+        /**
+         * 管理中
+         */
         "owned": string;
+        /**
+         * フォロー中
+         */
         "following": string;
-        "usersCount": string;
-        "notesCount": string;
+        /**
+         * {n}人が参加中
+         */
+        "usersCount": ParameterizedString<"n">;
+        /**
+         * {n}投稿があります
+         */
+        "notesCount": ParameterizedString<"n">;
+        /**
+         * 名前と説明
+         */
         "nameAndDescription": string;
+        /**
+         * 名前のみ
+         */
         "nameOnly": string;
+        /**
+         * チャンネル外へのブーストと引用ブーストを許可する
+         */
         "allowRenoteToExternal": string;
     };
     "_menuDisplay": {
+        /**
+         * 横
+         */
         "sideFull": string;
+        /**
+         * 横(アイコン)
+         */
         "sideIcon": string;
+        /**
+         * 上部
+         */
         "top": string;
+        /**
+         * 隠す
+         */
         "hide": string;
     };
     "_wordMute": {
+        /**
+         * ミュートするワード
+         */
         "muteWords": string;
+        /**
+         * スペースで区切るとAND指定になり、改行で区切るとOR指定になります。
+         */
         "muteWordsDescription": string;
+        /**
+         * キーワードをスラッシュで囲むと正規表現になります。
+         */
         "muteWordsDescription2": string;
     };
     "_instanceMute": {
+        /**
+         * ミュートしたサーバーのユーザーへの返信を含めて、設定したサーバーの全てのノートとブーストをミュートします。
+         */
         "instanceMuteDescription": string;
+        /**
+         * 改行で区切って設定します
+         */
         "instanceMuteDescription2": string;
+        /**
+         * 設定したサーバーのノートを隠します。
+         */
         "title": string;
+        /**
+         * ミュートするサーバー
+         */
         "heading": string;
     };
     "_theme": {
+        /**
+         * テーマを探す
+         */
         "explore": string;
+        /**
+         * テーマのインストール
+         */
         "install": string;
+        /**
+         * テーマの管理
+         */
         "manage": string;
+        /**
+         * テーマコード
+         */
         "code": string;
+        /**
+         * 説明
+         */
         "description": string;
-        "installed": string;
+        /**
+         * {name}をインストールしました
+         */
+        "installed": ParameterizedString<"name">;
+        /**
+         * インストールされたテーマ
+         */
         "installedThemes": string;
+        /**
+         * 標準のテーマ
+         */
         "builtinThemes": string;
+        /**
+         * そのテーマは既にインストールされています
+         */
         "alreadyInstalled": string;
+        /**
+         * テーマの形式が間違っています
+         */
         "invalid": string;
+        /**
+         * テーマを作る
+         */
         "make": string;
+        /**
+         * ベース
+         */
         "base": string;
+        /**
+         * 定数を追加
+         */
         "addConstant": string;
+        /**
+         * 定数
+         */
         "constant": string;
+        /**
+         * デフォルト値
+         */
         "defaultValue": string;
+        /**
+         * 色
+         */
         "color": string;
+        /**
+         * プロパティを参照
+         */
         "refProp": string;
+        /**
+         * 定数を参照
+         */
         "refConst": string;
+        /**
+         * キー
+         */
         "key": string;
+        /**
+         * 関数
+         */
         "func": string;
+        /**
+         * 関数の種類
+         */
         "funcKind": string;
+        /**
+         * 引数
+         */
         "argument": string;
+        /**
+         * 元にするプロパティの名前
+         */
         "basedProp": string;
+        /**
+         * 不透明度
+         */
         "alpha": string;
+        /**
+         * 暗さ
+         */
         "darken": string;
+        /**
+         * 明るさ
+         */
         "lighten": string;
+        /**
+         * 定数名を入力してください
+         */
         "inputConstantName": string;
+        /**
+         * ここにテーマコードを貼り付けて、エディターにインポートできます
+         */
         "importInfo": string;
-        "deleteConstantConfirm": string;
+        /**
+         * 定数 {const} を削除しても良いですか?
+         */
+        "deleteConstantConfirm": ParameterizedString<"const">;
         "keys": {
+            /**
+             * アクセント
+             */
             "accent": string;
+            /**
+             * 背景
+             */
             "bg": string;
+            /**
+             * 文字
+             */
             "fg": string;
+            /**
+             * フォーカス
+             */
             "focus": string;
+            /**
+             * インジケーター
+             */
             "indicator": string;
+            /**
+             * パネル
+             */
             "panel": string;
+            /**
+             * 影
+             */
             "shadow": string;
+            /**
+             * ヘッダー
+             */
             "header": string;
+            /**
+             * サイドバーの背景
+             */
             "navBg": string;
+            /**
+             * サイドバーの文字
+             */
             "navFg": string;
+            /**
+             * サイドバー文字(ホバー)
+             */
             "navHoverFg": string;
+            /**
+             * サイドバー文字(アクティブ)
+             */
             "navActive": string;
+            /**
+             * サイドバーのインジケーター
+             */
             "navIndicator": string;
+            /**
+             * リンク
+             */
             "link": string;
+            /**
+             * ハッシュタグ
+             */
             "hashtag": string;
+            /**
+             * メンション
+             */
             "mention": string;
+            /**
+             * あなた宛てメンション
+             */
             "mentionMe": string;
+            /**
+             * Boost
+             */
             "renote": string;
+            /**
+             * モーダルの背景
+             */
             "modalBg": string;
+            /**
+             * 分割線
+             */
             "divider": string;
+            /**
+             * スクロールバーの取っ手
+             */
             "scrollbarHandle": string;
+            /**
+             * スクロールバーの取っ手(ホバー)
+             */
             "scrollbarHandleHover": string;
+            /**
+             * 日付ラベルの文字
+             */
             "dateLabelFg": string;
+            /**
+             * 情報の背景
+             */
             "infoBg": string;
+            /**
+             * 情報の文字
+             */
             "infoFg": string;
+            /**
+             * 警告の背景
+             */
             "infoWarnBg": string;
+            /**
+             * 警告の文字
+             */
             "infoWarnFg": string;
+            /**
+             * 通知トーストの背景
+             */
             "toastBg": string;
+            /**
+             * 通知トーストの文字
+             */
             "toastFg": string;
+            /**
+             * ボタンの背景
+             */
             "buttonBg": string;
+            /**
+             * ボタンの背景 (ホバー)
+             */
             "buttonHoverBg": string;
+            /**
+             * 入力ボックスの縁取り
+             */
             "inputBorder": string;
+            /**
+             * リスト項目の背景 (ホバー)
+             */
             "listItemHoverBg": string;
+            /**
+             * ドライブフォルダーの背景
+             */
             "driveFolderBg": string;
+            /**
+             * 壁紙のオーバーレイ
+             */
             "wallpaperOverlay": string;
+            /**
+             * バッジ
+             */
             "badge": string;
+            /**
+             * チャットの背景
+             */
             "messageBg": string;
+            /**
+             * アクセント (暗め)
+             */
             "accentDarken": string;
+            /**
+             * アクセント (明るめ)
+             */
             "accentLighten": string;
+            /**
+             * 強調された文字
+             */
             "fgHighlighted": string;
         };
     };
     "_sfx": {
+        /**
+         * ノート
+         */
         "note": string;
+        /**
+         * ノート(自分)
+         */
         "noteMy": string;
+        /**
+         * 通知
+         */
         "notification": string;
+        /**
+         * アンテナ受信
+         */
         "antenna": string;
+        /**
+         * チャンネル通知
+         */
         "channel": string;
+        /**
+         * リアクション選択時
+         */
         "reaction": string;
     };
     "_soundSettings": {
+        /**
+         * ドライブの音声を使用
+         */
         "driveFile": string;
+        /**
+         * ドライブのファイルを選択してください
+         */
         "driveFileWarn": string;
+        /**
+         * このファイルは対応していません
+         */
         "driveFileTypeWarn": string;
+        /**
+         * 音声ファイルを選択してください
+         */
         "driveFileTypeWarnDescription": string;
+        /**
+         * 音声が長すぎます
+         */
         "driveFileDurationWarn": string;
+        /**
+         * 長い音声を使用するとMisskeyの使用に支障をきたす可能性があります。それでも続行しますか?
+         */
         "driveFileDurationWarnDescription": string;
     };
     "_ago": {
+        /**
+         * 未来
+         */
         "future": string;
+        /**
+         * たった今
+         */
         "justNow": string;
-        "secondsAgo": string;
-        "minutesAgo": string;
-        "hoursAgo": string;
-        "daysAgo": string;
-        "weeksAgo": string;
-        "monthsAgo": string;
-        "yearsAgo": string;
+        /**
+         * {n}秒前
+         */
+        "secondsAgo": ParameterizedString<"n">;
+        /**
+         * {n}分前
+         */
+        "minutesAgo": ParameterizedString<"n">;
+        /**
+         * {n}時間前
+         */
+        "hoursAgo": ParameterizedString<"n">;
+        /**
+         * {n}日前
+         */
+        "daysAgo": ParameterizedString<"n">;
+        /**
+         * {n}週間前
+         */
+        "weeksAgo": ParameterizedString<"n">;
+        /**
+         * {n}ヶ月前
+         */
+        "monthsAgo": ParameterizedString<"n">;
+        /**
+         * {n}年前
+         */
+        "yearsAgo": ParameterizedString<"n">;
+        /**
+         * 日時の解析に失敗
+         */
         "invalid": string;
     };
     "_timeIn": {
-        "seconds": string;
-        "minutes": string;
-        "hours": string;
-        "days": string;
-        "weeks": string;
-        "months": string;
-        "years": string;
+        /**
+         * {n}秒後
+         */
+        "seconds": ParameterizedString<"n">;
+        /**
+         * {n}分後
+         */
+        "minutes": ParameterizedString<"n">;
+        /**
+         * {n}時間後
+         */
+        "hours": ParameterizedString<"n">;
+        /**
+         * {n}日後
+         */
+        "days": ParameterizedString<"n">;
+        /**
+         * {n}週間後
+         */
+        "weeks": ParameterizedString<"n">;
+        /**
+         * {n}ヶ月後
+         */
+        "months": ParameterizedString<"n">;
+        /**
+         * {n}年後
+         */
+        "years": ParameterizedString<"n">;
     };
     "_time": {
+        /**
+         * 秒
+         */
         "second": string;
+        /**
+         * 分
+         */
         "minute": string;
+        /**
+         * 時間
+         */
         "hour": string;
+        /**
+         * 日
+         */
         "day": string;
     };
     "_2fa": {
+        /**
+         * 既に設定は完了しています。
+         */
         "alreadyRegistered": string;
+        /**
+         * 認証アプリの設定を開始
+         */
         "registerTOTP": string;
-        "step1": string;
+        /**
+         * まず、{a}や{b}などの認証アプリをお使いのデバイスにインストールします。
+         */
+        "step1": ParameterizedString<"a" | "b">;
+        /**
+         * 次に、表示されているQRコードをアプリでスキャンします。
+         */
         "step2": string;
+        /**
+         * QRコードをクリックすると、お使いの端末にインストールされている認証アプリやキーリングに登録できます。
+         */
         "step2Click": string;
+        /**
+         * デスクトップアプリを使用する場合は次のURIを入力します
+         */
         "step2Uri": string;
+        /**
+         * 確認コードを入力
+         */
         "step3Title": string;
+        /**
+         * アプリに表示されている確認コード(トークン)を入力します。
+         */
         "step3": string;
+        /**
+         * 設定が完了しました
+         */
         "setupCompleted": string;
+        /**
+         * これからログインするときも、同じようにコードを入力します。
+         */
         "step4": string;
+        /**
+         * お使いのブラウザはセキュリティキーに対応していません。
+         */
         "securityKeyNotSupported": string;
+        /**
+         * セキュリティキー・パスキーを登録するには、まず認証アプリの設定を行なってください。
+         */
         "registerTOTPBeforeKey": string;
+        /**
+         * FIDO2をサポートするハードウェアセキュリティキー、端末の生体認証やPINロック、パスキーといった、WebAuthn由来の鍵を登録します。
+         */
         "securityKeyInfo": string;
+        /**
+         * セキュリティキー・パスキーを登録する
+         */
         "registerSecurityKey": string;
+        /**
+         * キーの名前を入力
+         */
         "securityKeyName": string;
+        /**
+         * ブラウザの指示に従い、セキュリティキーやパスキーを登録してください
+         */
         "tapSecurityKey": string;
+        /**
+         * セキュリティキーを削除
+         */
         "removeKey": string;
-        "removeKeyConfirm": string;
+        /**
+         * {name}を削除しますか?
+         */
+        "removeKeyConfirm": ParameterizedString<"name">;
+        /**
+         * セキュリティキーが登録されている場合、認証アプリの設定は解除できません。
+         */
         "whyTOTPOnlyRenew": string;
+        /**
+         * 認証アプリを再設定
+         */
         "renewTOTP": string;
+        /**
+         * 今までの認証アプリの確認コードおよびバックアップコードは使用できなくなります
+         */
         "renewTOTPConfirm": string;
+        /**
+         * 再設定する
+         */
         "renewTOTPOk": string;
+        /**
+         * やめておく
+         */
         "renewTOTPCancel": string;
+        /**
+         * このウィザードを閉じる前に、以下のバックアップコードを確認してください。
+         */
         "checkBackupCodesBeforeCloseThisWizard": string;
+        /**
+         * バックアップコード
+         */
         "backupCodes": string;
+        /**
+         * 認証アプリが使用できなくなった場合、以下のバックアップコードを使ってアカウントにアクセスできます。これらのコードは必ず安全な場所に保管してください。各コードは一回だけ使用できます。
+         */
         "backupCodesDescription": string;
+        /**
+         * バックアップコードが使用されました。認証アプリが使えなくなっている場合、なるべく早く認証アプリを再設定してください。
+         */
         "backupCodeUsedWarning": string;
+        /**
+         * バックアップコードが全て使用されました。認証アプリを利用できない場合、これ以上アカウントにアクセスできなくなります。認証アプリを再登録してください。
+         */
         "backupCodesExhaustedWarning": string;
     };
     "_permissions": {
+        /**
+         * アカウントの情報を見る
+         */
         "read:account": string;
+        /**
+         * アカウントの情報を変更する
+         */
         "write:account": string;
+        /**
+         * ブロックを見る
+         */
         "read:blocks": string;
+        /**
+         * ブロックを操作する
+         */
         "write:blocks": string;
+        /**
+         * ドライブを見る
+         */
         "read:drive": string;
+        /**
+         * ドライブを操作する
+         */
         "write:drive": string;
+        /**
+         * お気に入りを見る
+         */
         "read:favorites": string;
+        /**
+         * お気に入りを操作する
+         */
         "write:favorites": string;
+        /**
+         * フォローの情報を見る
+         */
         "read:following": string;
+        /**
+         * フォロー・フォロー解除する
+         */
         "write:following": string;
+        /**
+         * チャットを見る
+         */
         "read:messaging": string;
+        /**
+         * チャットを操作する
+         */
         "write:messaging": string;
+        /**
+         * ミュートを見る
+         */
         "read:mutes": string;
+        /**
+         * ミュートを操作する
+         */
         "write:mutes": string;
+        /**
+         * ノートを作成・削除する
+         */
         "write:notes": string;
+        /**
+         * 通知を見る
+         */
         "read:notifications": string;
+        /**
+         * 通知を操作する
+         */
         "write:notifications": string;
+        /**
+         * リアクションを見る
+         */
         "read:reactions": string;
+        /**
+         * リアクションを操作する
+         */
         "write:reactions": string;
+        /**
+         * 投票する
+         */
         "write:votes": string;
+        /**
+         * ページを見る
+         */
         "read:pages": string;
+        /**
+         * ページを操作する
+         */
         "write:pages": string;
+        /**
+         * ページのいいねを見る
+         */
         "read:page-likes": string;
+        /**
+         * ページのいいねを操作する
+         */
         "write:page-likes": string;
+        /**
+         * ユーザーグループを見る
+         */
         "read:user-groups": string;
+        /**
+         * ユーザーグループを操作する
+         */
         "write:user-groups": string;
+        /**
+         * チャンネルを見る
+         */
         "read:channels": string;
+        /**
+         * チャンネルを操作する
+         */
         "write:channels": string;
+        /**
+         * ギャラリーを見る
+         */
         "read:gallery": string;
+        /**
+         * ギャラリーを操作する
+         */
         "write:gallery": string;
+        /**
+         * ギャラリーのいいねを見る
+         */
         "read:gallery-likes": string;
+        /**
+         * ギャラリーのいいねを操作する
+         */
         "write:gallery-likes": string;
+        /**
+         * Playを見る
+         */
         "read:flash": string;
+        /**
+         * Playを操作する
+         */
         "write:flash": string;
+        /**
+         * Playのいいねを見る
+         */
         "read:flash-likes": string;
+        /**
+         * Playのいいねを操作する
+         */
         "write:flash-likes": string;
+        /**
+         * ユーザーからの通報を見る
+         */
         "read:admin:abuse-user-reports": string;
+        /**
+         * ユーザーアカウントを削除する
+         */
         "write:admin:delete-account": string;
+        /**
+         * ユーザーのすべてのファイルを削除する
+         */
         "write:admin:delete-all-files-of-a-user": string;
+        /**
+         * データベースインデックスに関する情報を見る
+         */
         "read:admin:index-stats": string;
+        /**
+         * データベーステーブルに関する情報を見る
+         */
         "read:admin:table-stats": string;
+        /**
+         * ユーザーのIPアドレスを見る
+         */
         "read:admin:user-ips": string;
+        /**
+         * インスタンスのメタデータを見る
+         */
         "read:admin:meta": string;
+        /**
+         * ユーザーのパスワードをリセットする
+         */
         "write:admin:reset-password": string;
+        /**
+         * ユーザーからの通報を解決する
+         */
         "write:admin:resolve-abuse-user-report": string;
+        /**
+         * メールを送る
+         */
         "write:admin:send-email": string;
+        /**
+         * サーバーの情報を見る
+         */
         "read:admin:server-info": string;
+        /**
+         * モデレーションログを見る
+         */
         "read:admin:show-moderation-log": string;
+        /**
+         * ユーザーのプライベートな情報を見る
+         */
         "read:admin:show-user": string;
+        /**
+         * ユーザーのプライベートな情報を見る
+         */
         "read:admin:show-users": string;
+        /**
+         * ユーザーを凍結する
+         */
         "write:admin:suspend-user": string;
+        /**
+         * ユーザーのアバターを削除する
+         */
         "write:admin:unset-user-avatar": string;
+        /**
+         * ユーザーのバーナーを削除する
+         */
         "write:admin:unset-user-banner": string;
+        /**
+         * ユーザーの凍結を解除する
+         */
         "write:admin:unsuspend-user": string;
+        /**
+         * インスタンスのメタデータを操作する
+         */
         "write:admin:meta": string;
+        /**
+         * モデレーションノートを操作する
+         */
         "write:admin:user-note": string;
+        /**
+         * ロールを操作する
+         */
         "write:admin:roles": string;
+        /**
+         * ロールを見る
+         */
         "read:admin:roles": string;
+        /**
+         * リレーを操作する
+         */
         "write:admin:relays": string;
+        /**
+         * リレーを見る
+         */
         "read:admin:relays": string;
+        /**
+         * 招待コードを操作する
+         */
         "write:admin:invite-codes": string;
+        /**
+         * 招待コードを見る
+         */
         "read:admin:invite-codes": string;
+        /**
+         * お知らせを操作する
+         */
         "write:admin:announcements": string;
+        /**
+         * お知らせを見る
+         */
         "read:admin:announcements": string;
+        /**
+         * アバターデコレーションを操作する
+         */
         "write:admin:avatar-decorations": string;
+        /**
+         * アバターデコレーションを見る
+         */
         "read:admin:avatar-decorations": string;
+        /**
+         * 連合に関する情報を操作する
+         */
         "write:admin:federation": string;
+        /**
+         * ユーザーアカウントを操作する
+         */
         "write:admin:account": string;
+        /**
+         * ユーザーに関する情報を見る
+         */
         "read:admin:account": string;
+        /**
+         * 絵文字を操作する
+         */
         "write:admin:emoji": string;
+        /**
+         * 絵文字を見る
+         */
         "read:admin:emoji": string;
+        /**
+         * ジョブキューを操作する
+         */
         "write:admin:queue": string;
+        /**
+         * ジョブキューに関する情報を見る
+         */
         "read:admin:queue": string;
+        /**
+         * プロモーションノートを操作する
+         */
         "write:admin:promo": string;
+        /**
+         * ユーザーのドライブを操作する
+         */
         "write:admin:drive": string;
+        /**
+         * ユーザーのドライブの関する情報を見る
+         */
         "read:admin:drive": string;
+        /**
+         * 管理者用のWebsocket APIを使う
+         */
         "read:admin:stream": string;
+        /**
+         * 広告を操作する
+         */
         "write:admin:ad": string;
+        /**
+         * 広告を見る
+         */
         "read:admin:ad": string;
+        /**
+         * 招待コードを作成する
+         */
         "write:invite-codes": string;
+        /**
+         * 招待コードを取得する
+         */
         "read:invite-codes": string;
+        /**
+         * クリップのいいねを操作する
+         */
         "write:clip-favorite": string;
+        /**
+         * クリップのいいねを見る
+         */
         "read:clip-favorite": string;
+        /**
+         * 連合に関する情報を取得する
+         */
         "read:federation": string;
+        /**
+         * 違反を報告する
+         */
         "write:report-abuse": string;
     };
     "_auth": {
+        /**
+         * アプリへのアクセス許可
+         */
         "shareAccessTitle": string;
-        "shareAccess": string;
+        /**
+         * 「{name}」がアカウントにアクセスすることを許可しますか?
+         */
+        "shareAccess": ParameterizedString<"name">;
+        /**
+         * アカウントへのアクセスを許可しますか?
+         */
         "shareAccessAsk": string;
-        "permission": string;
+        /**
+         * {name}は次の権限を要求しています
+         */
+        "permission": ParameterizedString<"name">;
+        /**
+         * このアプリは次の権限を要求しています
+         */
         "permissionAsk": string;
+        /**
+         * アプリケーションに戻ってやっていってください
+         */
         "pleaseGoBack": string;
+        /**
+         * アプリケーションに戻っています
+         */
         "callback": string;
+        /**
+         * アクセスを拒否しました
+         */
         "denied": string;
+        /**
+         * アプリケーションにアクセス許可を与えるには、ログインが必要です。
+         */
         "pleaseLogin": string;
     };
     "_antennaSources": {
+        /**
+         * 全てのノート
+         */
         "all": string;
+        /**
+         * フォローしているユーザーのノート
+         */
         "homeTimeline": string;
+        /**
+         * 指定した一人または複数のユーザーのノート
+         */
         "users": string;
+        /**
+         * 指定したリストのユーザーのノート
+         */
         "userList": string;
+        /**
+         * 指定した一人または複数のユーザーを除いた全てのノート
+         */
         "userBlacklist": string;
     };
     "_weekday": {
+        /**
+         * 日曜日
+         */
         "sunday": string;
+        /**
+         * 月曜日
+         */
         "monday": string;
+        /**
+         * 火曜日
+         */
         "tuesday": string;
+        /**
+         * 水曜日
+         */
         "wednesday": string;
+        /**
+         * 木曜日
+         */
         "thursday": string;
+        /**
+         * 金曜日
+         */
         "friday": string;
+        /**
+         * 土曜日
+         */
         "saturday": string;
     };
     "_widgets": {
+        /**
+         * プロフィール
+         */
         "profile": string;
+        /**
+         * サーバー情報
+         */
         "instanceInfo": string;
+        /**
+         * 付箋
+         */
         "memo": string;
+        /**
+         * 通知
+         */
         "notifications": string;
+        /**
+         * タイムライン
+         */
         "timeline": string;
+        /**
+         * カレンダー
+         */
         "calendar": string;
+        /**
+         * トレンド
+         */
         "trends": string;
+        /**
+         * 時計
+         */
         "clock": string;
+        /**
+         * RSSリーダー
+         */
         "rss": string;
+        /**
+         * RSSティッカー
+         */
         "rssTicker": string;
+        /**
+         * アクティビティ
+         */
         "activity": string;
+        /**
+         * フォト
+         */
         "photos": string;
+        /**
+         * デジタル時計
+         */
         "digitalClock": string;
+        /**
+         * UNIX時計
+         */
         "unixClock": string;
+        /**
+         * 連合
+         */
         "federation": string;
+        /**
+         * サーバークラウド
+         */
         "instanceCloud": string;
+        /**
+         * 投稿フォーム
+         */
         "postForm": string;
+        /**
+         * スライドショー
+         */
         "slideshow": string;
+        /**
+         * ボタン
+         */
         "button": string;
+        /**
+         * オンラインユーザー
+         */
         "onlineUsers": string;
+        /**
+         * ジョブキュー
+         */
         "jobQueue": string;
+        /**
+         * サーバーメトリクス
+         */
         "serverMetric": string;
+        /**
+         * AiScriptコンソール
+         */
         "aiscript": string;
+        /**
+         * AiScript App
+         */
         "aiscriptApp": string;
+        /**
+         * 藍
+         */
         "aichan": string;
+        /**
+         * ユーザーリスト
+         */
         "userList": string;
         "_userList": {
+            /**
+             * リストを選択
+             */
             "chooseList": string;
         };
+        /**
+         * クリッカー
+         */
         "clicker": string;
+        /**
+         * 検索
+         */
         "search": string;
+        /**
+         * 今日誕生日のユーザー
+         */
         "birthdayFollowings": string;
     };
     "_cw": {
+        /**
+         * 隠す
+         */
         "hide": string;
+        /**
+         * もっと見る
+         */
         "show": string;
-        "chars": string;
-        "files": string;
+        /**
+         * {count}文字
+         */
+        "chars": ParameterizedString<"count">;
+        /**
+         * {count}ファイル
+         */
+        "files": ParameterizedString<"count">;
     };
     "_poll": {
+        /**
+         * 選択肢は最低2つ必要です
+         */
         "noOnlyOneChoice": string;
-        "choiceN": string;
+        /**
+         * 選択肢{n}
+         */
+        "choiceN": ParameterizedString<"n">;
+        /**
+         * これ以上追加できません
+         */
         "noMore": string;
+        /**
+         * 複数回答可
+         */
         "canMultipleVote": string;
+        /**
+         * 期限
+         */
         "expiration": string;
+        /**
+         * 無期限
+         */
         "infinite": string;
+        /**
+         * 日時指定
+         */
         "at": string;
+        /**
+         * 経過指定
+         */
         "after": string;
+        /**
+         * 期日
+         */
         "deadlineDate": string;
+        /**
+         * 時間
+         */
         "deadlineTime": string;
+        /**
+         * 期間
+         */
         "duration": string;
-        "votesCount": string;
-        "totalVotes": string;
+        /**
+         * {n}票
+         */
+        "votesCount": ParameterizedString<"n">;
+        /**
+         * 計{n}票
+         */
+        "totalVotes": ParameterizedString<"n">;
+        /**
+         * 投票する
+         */
         "vote": string;
+        /**
+         * 結果を見る
+         */
         "showResult": string;
+        /**
+         * 投票済み
+         */
         "voted": string;
+        /**
+         * 終了済み
+         */
         "closed": string;
-        "remainingDays": string;
-        "remainingHours": string;
-        "remainingMinutes": string;
-        "remainingSeconds": string;
+        /**
+         * 終了まであと{d}日{h}時間
+         */
+        "remainingDays": ParameterizedString<"d" | "h">;
+        /**
+         * 終了まであと{h}時間{m}分
+         */
+        "remainingHours": ParameterizedString<"h" | "m">;
+        /**
+         * 終了まであと{m}分{s}秒
+         */
+        "remainingMinutes": ParameterizedString<"m" | "s">;
+        /**
+         * 終了まであと{s}秒
+         */
+        "remainingSeconds": ParameterizedString<"s">;
+        /**
+         * 複数の選択肢
+         */
         "multiple": string;
     };
     "_visibility": {
+        /**
+         * パブリック
+         */
         "public": string;
+        /**
+         * 全てのユーザーに公開
+         */
         "publicDescription": string;
+        /**
+         * ホーム
+         */
         "home": string;
+        /**
+         * ホームタイムラインのみに公開
+         */
         "homeDescription": string;
+        /**
+         * フォロワー
+         */
         "followers": string;
+        /**
+         * 自分のフォロワーのみに公開
+         */
         "followersDescription": string;
+        /**
+         * ダイレクト
+         */
         "specified": string;
+        /**
+         * 指定したユーザーのみに公開
+         */
         "specifiedDescription": string;
+        /**
+         * 連合なし
+         */
         "disableFederation": string;
+        /**
+         * 他サーバーへの配信を行いません
+         */
         "disableFederationDescription": string;
     };
     "_postForm": {
+        /**
+         * このノートに返信...
+         */
         "replyPlaceholder": string;
+        /**
+         * このノートを引用...
+         */
         "quotePlaceholder": string;
+        /**
+         * チャンネルに投稿...
+         */
         "channelPlaceholder": string;
         "_placeholders": {
+            /**
+             * いまどうしてる?
+             */
             "a": string;
+            /**
+             * 何かありましたか?
+             */
             "b": string;
+            /**
+             * 何をお考えですか?
+             */
             "c": string;
+            /**
+             * 言いたいことは?
+             */
             "d": string;
+            /**
+             * ここに書いてください
+             */
             "e": string;
+            /**
+             * あなたが書くのを待っています...
+             */
             "f": string;
         };
     };
     "_profile": {
+        /**
+         * 名前
+         */
         "name": string;
+        /**
+         * ユーザー名
+         */
         "username": string;
+        /**
+         * 自己紹介
+         */
         "description": string;
+        /**
+         * ハッシュタグを含めることができます。
+         */
         "youCanIncludeHashtags": string;
+        /**
+         * 追加情報
+         */
         "metadata": string;
+        /**
+         * 追加情報を編集
+         */
         "metadataEdit": string;
+        /**
+         * プロフィールに表として追加情報を表示することができます。
+         */
         "metadataDescription": string;
+        /**
+         * ラベル
+         */
         "metadataLabel": string;
+        /**
+         * 内容
+         */
         "metadataContent": string;
+        /**
+         * アイコン画像を変更
+         */
         "changeAvatar": string;
+        /**
+         * バナー画像を変更
+         */
         "changeBanner": string;
+        /**
+         * 更新バナー
+         */
+        "updateBanner": string;
+        /**
+         * バナーを削除
+         */
+        "removeBanner": string;
+        /**
+         * 背景を変更する
+         */
         "changeBackground": string;
+        /**
+         * 背景を更新する
+         */
+        "updateBackground": string;
+        /**
+         * 背景を削除する
+         */
+        "removeBackground": string;
+        /**
+         * 内容にURLを設定すると、リンク先のWebサイトに自分のプロフィールへのリンクが含まれている場合に所有者確認済みアイコンを表示させることができます。
+         */
         "verifiedLinkDescription": string;
-        "avatarDecorationMax": string;
+        /**
+         * 最大{max}つまでデコレーションを付けられます。
+         */
+        "avatarDecorationMax": ParameterizedString<"max">;
     };
     "_exportOrImport": {
+        /**
+         * 全てのノート
+         */
         "allNotes": string;
+        /**
+         * お気に入りにしたノート
+         */
         "favoritedNotes": string;
+        /**
+         * クリップ
+         */
+        "clips": string;
+        /**
+         * フォロー
+         */
         "followingList": string;
+        /**
+         * ミュート
+         */
         "muteList": string;
+        /**
+         * ブロック
+         */
         "blockingList": string;
+        /**
+         * リスト
+         */
         "userLists": string;
+        /**
+         * ミュートしているユーザーを除外
+         */
         "excludeMutingUsers": string;
+        /**
+         * 使われていないアカウントを除外
+         */
         "excludeInactiveUsers": string;
+        /**
+         * インポートした人による返信をTLに含むようにする
+         */
         "withReplies": string;
     };
     "_charts": {
+        /**
+         * 連合
+         */
         "federation": string;
+        /**
+         * リクエスト
+         */
         "apRequest": string;
+        /**
+         * ユーザーの増減
+         */
         "usersIncDec": string;
+        /**
+         * ユーザーの合計
+         */
         "usersTotal": string;
+        /**
+         * アクティブユーザー数
+         */
         "activeUsers": string;
+        /**
+         * ノートの増減
+         */
         "notesIncDec": string;
+        /**
+         * ローカルのノートの増減
+         */
         "localNotesIncDec": string;
+        /**
+         * リモートのノートの増減
+         */
         "remoteNotesIncDec": string;
+        /**
+         * ノートの合計
+         */
         "notesTotal": string;
+        /**
+         * ファイルの増減
+         */
         "filesIncDec": string;
+        /**
+         * ファイルの合計
+         */
         "filesTotal": string;
+        /**
+         * ストレージ使用量の増減
+         */
         "storageUsageIncDec": string;
+        /**
+         * ストレージ使用量の合計
+         */
         "storageUsageTotal": string;
     };
     "_instanceCharts": {
+        /**
+         * リクエスト
+         */
         "requests": string;
+        /**
+         * ユーザーの増減
+         */
         "users": string;
+        /**
+         * ユーザーの累積
+         */
         "usersTotal": string;
+        /**
+         * ノートの増減
+         */
         "notes": string;
+        /**
+         * ノートの累積
+         */
         "notesTotal": string;
+        /**
+         * フォロー/フォロワーの増減
+         */
         "ff": string;
+        /**
+         * フォロー/フォロワーの累積
+         */
         "ffTotal": string;
+        /**
+         * キャッシュサイズの増減
+         */
         "cacheSize": string;
+        /**
+         * キャッシュサイズの累積
+         */
         "cacheSizeTotal": string;
+        /**
+         * ファイル数の増減
+         */
         "files": string;
+        /**
+         * ファイル数の累積
+         */
         "filesTotal": string;
     };
     "_timelines": {
+        /**
+         * ホーム
+         */
         "home": string;
+        /**
+         * ローカル
+         */
         "local": string;
+        /**
+         * ソーシャル
+         */
         "social": string;
+        /**
+         * グローバル
+         */
         "global": string;
     };
     "_play": {
+        /**
+         * Playの作成
+         */
         "new": string;
+        /**
+         * Playの編集
+         */
         "edit": string;
+        /**
+         * Playを作成しました
+         */
         "created": string;
+        /**
+         * Playを更新しました
+         */
         "updated": string;
+        /**
+         * Playを削除しました
+         */
         "deleted": string;
+        /**
+         * Play設定
+         */
         "pageSetting": string;
+        /**
+         * このPlayを編集
+         */
         "editThisPage": string;
+        /**
+         * ソースを表示
+         */
         "viewSource": string;
+        /**
+         * 自分のPlay
+         */
         "my": string;
+        /**
+         * いいねしたPlay
+         */
         "liked": string;
+        /**
+         * 人気
+         */
         "featured": string;
+        /**
+         * タイトル
+         */
         "title": string;
+        /**
+         * スクリプト
+         */
         "script": string;
+        /**
+         * 説明
+         */
         "summary": string;
     };
     "_pages": {
+        /**
+         * ページの作成
+         */
         "newPage": string;
+        /**
+         * ページの編集
+         */
         "editPage": string;
+        /**
+         * ソースを表示中
+         */
         "readPage": string;
+        /**
+         * ページを作成しました
+         */
         "created": string;
+        /**
+         * ページを更新しました
+         */
         "updated": string;
+        /**
+         * ページを削除しました
+         */
         "deleted": string;
+        /**
+         * ページ設定
+         */
         "pageSetting": string;
+        /**
+         * 指定されたページURLは既に存在しています
+         */
         "nameAlreadyExists": string;
+        /**
+         * 不正なページURLです
+         */
         "invalidNameTitle": string;
+        /**
+         * 空白でないか確認してください
+         */
         "invalidNameText": string;
+        /**
+         * このページを編集
+         */
         "editThisPage": string;
+        /**
+         * ソースを表示
+         */
         "viewSource": string;
+        /**
+         * ページを見る
+         */
         "viewPage": string;
+        /**
+         * いいね
+         */
         "like": string;
+        /**
+         * いいね解除
+         */
         "unlike": string;
+        /**
+         * 自分のページ
+         */
         "my": string;
+        /**
+         * いいねしたページ
+         */
         "liked": string;
+        /**
+         * 人気
+         */
         "featured": string;
+        /**
+         * インスペクター
+         */
         "inspector": string;
+        /**
+         * コンテンツ
+         */
         "contents": string;
+        /**
+         * ページブロック
+         */
         "content": string;
+        /**
+         * 変数
+         */
         "variables": string;
+        /**
+         * タイトル
+         */
         "title": string;
+        /**
+         * ページURL
+         */
         "url": string;
+        /**
+         * ページの要約
+         */
         "summary": string;
+        /**
+         * 中央寄せ
+         */
         "alignCenter": string;
+        /**
+         * ピン留めされているときにタイトルを非表示
+         */
         "hideTitleWhenPinned": string;
+        /**
+         * フォント
+         */
         "font": string;
+        /**
+         * セリフ
+         */
         "fontSerif": string;
+        /**
+         * サンセリフ
+         */
         "fontSansSerif": string;
+        /**
+         * アイキャッチ画像を設定
+         */
         "eyeCatchingImageSet": string;
+        /**
+         * アイキャッチ画像を削除
+         */
         "eyeCatchingImageRemove": string;
+        /**
+         * ブロックを追加
+         */
         "chooseBlock": string;
+        /**
+         * 種類を選択
+         */
         "selectType": string;
+        /**
+         * コンテンツ
+         */
         "contentBlocks": string;
+        /**
+         * 入力
+         */
         "inputBlocks": string;
+        /**
+         * 特殊
+         */
         "specialBlocks": string;
         "blocks": {
+            /**
+             * テキスト
+             */
             "text": string;
+            /**
+             * テキストエリア
+             */
             "textarea": string;
+            /**
+             * セクション
+             */
             "section": string;
+            /**
+             * 画像
+             */
             "image": string;
+            /**
+             * ボタン
+             */
             "button": string;
+            /**
+             * ノート埋め込み
+             */
             "note": string;
             "_note": {
+                /**
+                 * ノートID
+                 */
                 "id": string;
+                /**
+                 * ノートURLをペーストして設定することもできます。
+                 */
                 "idDescription": string;
+                /**
+                 * 詳細な表示
+                 */
                 "detailed": string;
             };
         };
     };
     "_relayStatus": {
+        /**
+         * 承認待ち
+         */
         "requesting": string;
+        /**
+         * 承認済み
+         */
         "accepted": string;
+        /**
+         * 拒否済み
+         */
         "rejected": string;
     };
     "_notification": {
+        /**
+         * ファイルがアップロードされました
+         */
         "fileUploaded": string;
-        "youGotMention": string;
-        "youGotReply": string;
-        "youGotQuote": string;
-        "youRenoted": string;
+        /**
+         * {name}からのメンション
+         */
+        "youGotMention": ParameterizedString<"name">;
+        /**
+         * {name}からのリプライ
+         */
+        "youGotReply": ParameterizedString<"name">;
+        /**
+         * {name}による引用
+         */
+        "youGotQuote": ParameterizedString<"name">;
+        /**
+         * {name}がBoostしました
+         */
+        "youRenoted": ParameterizedString<"name">;
+        /**
+         * フォローされました
+         */
         "youWereFollowed": string;
+        /**
+         * フォローリクエストが来ました
+         */
         "youReceivedFollowRequest": string;
+        /**
+         * フォローリクエストが承認されました
+         */
         "yourFollowRequestAccepted": string;
+        /**
+         * アンケートの結果が出ました
+         */
         "pollEnded": string;
+        /**
+         * 投稿が編集されました
+         */
+        "edited": string;
+        /**
+         * 新しい投稿
+         */
         "newNote": string;
-        "unreadAntennaNote": string;
+        /**
+         * アンテナ {name}
+         */
+        "unreadAntennaNote": ParameterizedString<"name">;
+        /**
+         * ロールが付与されました
+         */
         "roleAssigned": string;
+        /**
+         * プッシュ通知の更新をしました
+         */
         "emptyPushNotificationMessage": string;
+        /**
+         * 実績を獲得
+         */
         "achievementEarned": string;
+        /**
+         * 通知テスト
+         */
         "testNotification": string;
+        /**
+         * 通知の表示を確かめる
+         */
         "checkNotificationBehavior": string;
+        /**
+         * テスト通知を送信する
+         */
         "sendTestNotification": string;
+        /**
+         * 通知はこのように表示されます
+         */
         "notificationWillBeDisplayedLikeThis": string;
-        "reactedBySomeUsers": string;
-        "renotedBySomeUsers": string;
-        "followedBySomeUsers": string;
+        /**
+         * {n}人がリアクションしました
+         */
+        "reactedBySomeUsers": ParameterizedString<"n">;
+        /**
+         * {n}人がブーストしました
+         */
+        "renotedBySomeUsers": ParameterizedString<"n">;
+        /**
+         * {n}人にフォローされました
+         */
+        "followedBySomeUsers": ParameterizedString<"n">;
+        /**
+         * 通知の履歴をリセットする
+         */
+        "flushNotification": string;
         "_types": {
+            /**
+             * すべて
+             */
             "all": string;
+            /**
+             * ユーザーの新規投稿
+             */
             "note": string;
+            /**
+             * フォロー
+             */
             "follow": string;
+            /**
+             * メンション
+             */
             "mention": string;
+            /**
+             * リプライ
+             */
             "reply": string;
+            /**
+             * Boost
+             */
             "renote": string;
+            /**
+             * 引用
+             */
             "quote": string;
+            /**
+             * リアクション
+             */
             "reaction": string;
+            /**
+             * アンケートが終了
+             */
             "pollEnded": string;
+            /**
+             * フォロー申請を受け取った
+             */
             "receiveFollowRequest": string;
+            /**
+             * フォローが受理された
+             */
             "followRequestAccepted": string;
+            /**
+             * ロールが付与された
+             */
             "roleAssigned": string;
+            /**
+             * 実績の獲得
+             */
             "achievementEarned": string;
+            /**
+             * 連携アプリからの通知
+             */
             "app": string;
         };
         "_actions": {
+            /**
+             * フォローバック
+             */
             "followBack": string;
+            /**
+             * 返信
+             */
             "reply": string;
+            /**
+             * Boost
+             */
             "renote": string;
         };
     };
     "_deck": {
+        /**
+         * 常にメインカラムを表示
+         */
         "alwaysShowMainColumn": string;
+        /**
+         * カラムの寄せ
+         */
         "columnAlign": string;
+        /**
+         * カラムを追加
+         */
         "addColumn": string;
+        /**
+         * カラムの設定
+         */
         "configureColumn": string;
+        /**
+         * 左に移動
+         */
         "swapLeft": string;
+        /**
+         * 右に移動
+         */
         "swapRight": string;
+        /**
+         * 上に移動
+         */
         "swapUp": string;
+        /**
+         * 下に移動
+         */
         "swapDown": string;
+        /**
+         * 左にスタック
+         */
         "stackLeft": string;
+        /**
+         * 右に出す
+         */
         "popRight": string;
+        /**
+         * プロファイル
+         */
         "profile": string;
+        /**
+         * 新規プロファイル
+         */
         "newProfile": string;
+        /**
+         * プロファイルを削除
+         */
         "deleteProfile": string;
+        /**
+         * カラムを組み合わせて自分だけのインターフェイスを作りましょう!
+         */
         "introduction": string;
+        /**
+         * 画面の右にある + を押して、いつでもカラムを追加できます。
+         */
         "introduction2": string;
+        /**
+         * カラムのメニューから、「ウィジェットの編集」を選択してウィジェットを追加してください
+         */
         "widgetsIntroduction": string;
+        /**
+         * 非ルートページは簡易UIで表示
+         */
         "useSimpleUiForNonRootPages": string;
+        /**
+         * 「幅を自動調整」が有効の場合、これが幅の最小値となります
+         */
         "usedAsMinWidthWhenFlexible": string;
+        /**
+         * 幅を自動調整
+         */
         "flexible": string;
         "_columns": {
+            /**
+             * メイン
+             */
             "main": string;
+            /**
+             * ウィジェット
+             */
             "widgets": string;
+            /**
+             * 通知
+             */
             "notifications": string;
+            /**
+             * タイムライン
+             */
             "tl": string;
+            /**
+             * アンテナ
+             */
             "antenna": string;
+            /**
+             * リスト
+             */
             "list": string;
+            /**
+             * チャンネル
+             */
             "channel": string;
+            /**
+             * あなた宛て
+             */
             "mentions": string;
+            /**
+             * ダイレクト
+             */
             "direct": string;
+            /**
+             * ロールタイムライン
+             */
             "roleTimeline": string;
         };
     };
     "_dialog": {
-        "charactersExceeded": string;
-        "charactersBelow": string;
+        /**
+         * 最大文字数を超えています! 現在 {current} / 制限 {max}
+         */
+        "charactersExceeded": ParameterizedString<"current" | "max">;
+        /**
+         * 最小文字数を下回っています! 現在 {current} / 制限 {min}
+         */
+        "charactersBelow": ParameterizedString<"current" | "min">;
     };
     "_disabledTimeline": {
+        /**
+         * 無効化されたタイムライン
+         */
         "title": string;
+        /**
+         * 現在のロールでは、このタイムラインを使用することはできません。
+         */
         "description": string;
     };
     "_drivecleaner": {
+        /**
+         * サイズが大きい順
+         */
         "orderBySizeDesc": string;
+        /**
+         * 追加日が古い順
+         */
         "orderByCreatedAtAsc": string;
     };
     "_webhookSettings": {
+        /**
+         * Webhookを作成
+         */
         "createWebhook": string;
+        /**
+         * 名前
+         */
         "name": string;
+        /**
+         * シークレット
+         */
         "secret": string;
+        /**
+         * Webhookを実行するタイミング
+         */
         "events": string;
+        /**
+         * 有効
+         */
         "active": string;
         "_events": {
+            /**
+             * フォローしたとき
+             */
             "follow": string;
+            /**
+             * フォローされたとき
+             */
             "followed": string;
+            /**
+             * ノートを投稿したとき
+             */
             "note": string;
+            /**
+             * 返信されたとき
+             */
             "reply": string;
+            /**
+             * Boostされたとき
+             */
             "renote": string;
+            /**
+             * リアクションがあったとき
+             */
             "reaction": string;
+            /**
+             * メンションされたとき
+             */
             "mention": string;
         };
     };
     "_moderationLogTypes": {
+        /**
+         * ロールを作成
+         */
         "createRole": string;
+        /**
+         * ロールを削除
+         */
         "deleteRole": string;
+        /**
+         * ロールを更新
+         */
         "updateRole": string;
+        /**
+         * ロールへアサイン
+         */
         "assignRole": string;
+        /**
+         * ロールのアサイン解除
+         */
         "unassignRole": string;
+        /**
+         * 承認済み
+         */
         "approve": string;
+        /**
+         * 凍結
+         */
         "suspend": string;
+        /**
+         * 凍結解除
+         */
         "unsuspend": string;
+        /**
+         * カスタム絵文字追加
+         */
         "addCustomEmoji": string;
+        /**
+         * カスタム絵文字更新
+         */
         "updateCustomEmoji": string;
+        /**
+         * カスタム絵文字削除
+         */
         "deleteCustomEmoji": string;
+        /**
+         * サーバー設定更新
+         */
         "updateServerSettings": string;
+        /**
+         * ユーザーのモデレーションノート更新
+         */
         "updateUserNote": string;
+        /**
+         * ファイルを削除
+         */
         "deleteDriveFile": string;
+        /**
+         * ノートを削除
+         */
         "deleteNote": string;
+        /**
+         * 全体のお知らせを作成
+         */
         "createGlobalAnnouncement": string;
+        /**
+         * ユーザーへお知らせを作成
+         */
         "createUserAnnouncement": string;
+        /**
+         * 全体のお知らせを更新
+         */
         "updateGlobalAnnouncement": string;
+        /**
+         * ユーザーのお知らせを更新
+         */
         "updateUserAnnouncement": string;
+        /**
+         * 全体のお知らせを削除
+         */
         "deleteGlobalAnnouncement": string;
+        /**
+         * ユーザーのお知らせを削除
+         */
         "deleteUserAnnouncement": string;
+        /**
+         * パスワードをリセット
+         */
         "resetPassword": string;
+        /**
+         * リモートサーバーを停止
+         */
         "suspendRemoteInstance": string;
+        /**
+         * リモートサーバーを再開
+         */
         "unsuspendRemoteInstance": string;
+        /**
+         * リモートサーバーのモデレーションノート更新
+         */
+        "updateRemoteInstanceNote": string;
+        /**
+         * ファイルをセンシティブ付与
+         */
         "markSensitiveDriveFile": string;
+        /**
+         * ファイルをセンシティブ解除
+         */
         "unmarkSensitiveDriveFile": string;
+        /**
+         * 通報を解決
+         */
         "resolveAbuseReport": string;
+        /**
+         * 招待コードを作成
+         */
         "createInvitation": string;
+        /**
+         * 広告を作成
+         */
         "createAd": string;
+        /**
+         * 広告を削除
+         */
         "deleteAd": string;
+        /**
+         * 広告を更新
+         */
         "updateAd": string;
+        /**
+         * アイコンデコレーションを作成
+         */
         "createAvatarDecoration": string;
+        /**
+         * アイコンデコレーションを更新
+         */
         "updateAvatarDecoration": string;
+        /**
+         * アイコンデコレーションを削除
+         */
         "deleteAvatarDecoration": string;
+        /**
+         * ユーザーのアイコンを解除
+         */
         "unsetUserAvatar": string;
+        /**
+         * ユーザーのバナーを解除
+         */
         "unsetUserBanner": string;
     };
     "_fileViewer": {
+        /**
+         * ファイルの詳細
+         */
         "title": string;
+        /**
+         * ファイルタイプ
+         */
         "type": string;
+        /**
+         * ファイルサイズ
+         */
         "size": string;
+        /**
+         * URL
+         */
         "url": string;
+        /**
+         * 追加日
+         */
         "uploadedAt": string;
+        /**
+         * 添付されているノート
+         */
         "attachedNotes": string;
+        /**
+         * このページは、このファイルをアップロードしたユーザーしか閲覧できません。
+         */
         "thisPageCanBeSeenFromTheAuthor": string;
     };
     "_externalResourceInstaller": {
+        /**
+         * 外部サイトからインストール
+         */
         "title": string;
+        /**
+         * 配布元が信頼できるかを確認した上でインストールしてください。
+         */
         "checkVendorBeforeInstall": string;
         "_plugin": {
+            /**
+             * このプラグインをインストールしますか?
+             */
             "title": string;
+            /**
+             * プラグイン情報
+             */
             "metaTitle": string;
         };
         "_theme": {
+            /**
+             * このテーマをインストールしますか?
+             */
             "title": string;
+            /**
+             * テーマ情報
+             */
             "metaTitle": string;
         };
         "_meta": {
+            /**
+             * 基本のカラースキーム
+             */
             "base": string;
         };
         "_vendorInfo": {
+            /**
+             * 配布元情報
+             */
             "title": string;
+            /**
+             * 参照したエンドポイント
+             */
             "endpoint": string;
+            /**
+             * ファイル整合性の確認
+             */
             "hashVerify": string;
         };
         "_errors": {
             "_invalidParams": {
+                /**
+                 * パラメータが不足しています
+                 */
                 "title": string;
+                /**
+                 * 外部サイトからデータを取得するために必要な情報が不足しています。URLをお確かめください。
+                 */
                 "description": string;
             };
             "_resourceTypeNotSupported": {
+                /**
+                 * この外部リソースには対応していません
+                 */
                 "title": string;
+                /**
+                 * この外部サイトから取得したリソースの種別には対応していません。サイト管理者にお問い合わせください。
+                 */
                 "description": string;
             };
             "_failedToFetch": {
+                /**
+                 * データの取得に失敗しました
+                 */
                 "title": string;
+                /**
+                 * 外部サイトとの通信に失敗しました。もう一度試しても改善しない場合、サイト管理者にお問い合わせください。
+                 */
                 "fetchErrorDescription": string;
+                /**
+                 * 外部サイトから取得したデータが読み取れませんでした。サイト管理者にお問い合わせください。
+                 */
                 "parseErrorDescription": string;
             };
             "_hashUnmatched": {
+                /**
+                 * 正しいデータが取得できませんでした
+                 */
                 "title": string;
+                /**
+                 * 提供されたデータの整合性の確認に失敗しました。セキュリティ上、インストールは続行できません。サイト管理者にお問い合わせください。
+                 */
                 "description": string;
             };
             "_pluginParseFailed": {
+                /**
+                 * AiScript エラー
+                 */
                 "title": string;
+                /**
+                 * データは取得できたものの、AiScriptの解析時にエラーがあったため読み込めませんでした。プラグインの作者にお問い合わせください。エラーの詳細はJavascriptコンソールをご確認ください。
+                 */
                 "description": string;
             };
             "_pluginInstallFailed": {
+                /**
+                 * プラグインのインストールに失敗しました
+                 */
                 "title": string;
+                /**
+                 * プラグインのインストール中に問題が発生しました。もう一度お試しください。エラーの詳細はJavascriptコンソールをご覧ください。
+                 */
                 "description": string;
             };
             "_themeParseFailed": {
+                /**
+                 * テーマ解析エラー
+                 */
                 "title": string;
+                /**
+                 * データは取得できたものの、テーマファイルの解析時にエラーがあったため読み込めませんでした。テーマの作者にお問い合わせください。エラーの詳細はJavascriptコンソールをご確認ください。
+                 */
                 "description": string;
             };
             "_themeInstallFailed": {
+                /**
+                 * テーマのインストールに失敗しました
+                 */
                 "title": string;
+                /**
+                 * テーマのインストール中に問題が発生しました。もう一度お試しください。エラーの詳細はJavascriptコンソールをご覧ください。
+                 */
                 "description": string;
             };
         };
     };
     "_animatedMFM": {
+        /**
+         * MFMアニメーションを再生
+         */
         "play": string;
+        /**
+         * MFMアニメーション停止
+         */
         "stop": string;
         "_alert": {
+            /**
+             * MFMアニメーションには、点滅するライトや高速で動くテキスト/絵文字を含まれる場合があります。
+             */
             "text": string;
+            /**
+             * 再生する
+             */
             "confirm": string;
         };
     };
     "_dataRequest": {
+        /**
+         * データリクエスト
+         */
         "title": string;
+        /**
+         * データリクエストは3日ごとに可能です。
+         */
         "warn": string;
+        /**
+         * データの保存が完了すると、このアカウントに登録されているEメールアドレスにメールが送信されます。
+         */
         "text": string;
+        /**
+         * リクエスト
+         */
         "button": string;
     };
     "_dataSaver": {
         "_media": {
+            /**
+             * メディアの読み込み
+             */
             "title": string;
+            /**
+             * 画像・動画が自動で読み込まれるのを防止します。隠れている画像・動画はタップすると読み込まれます。
+             */
             "description": string;
         };
         "_avatar": {
+            /**
+             * アイコン画像
+             */
             "title": string;
+            /**
+             * アイコン画像のアニメーションが停止します。アニメーション画像は通常の画像よりファイルサイズが大きいことがあるので、データ通信量をさらに削減できます。
+             */
             "description": string;
         };
         "_urlPreview": {
+            /**
+             * URLプレビューのサムネイル
+             */
             "title": string;
+            /**
+             * URLプレビューのサムネイル画像が読み込まれなくなります。
+             */
             "description": string;
         };
         "_code": {
+            /**
+             * コードハイライト
+             */
             "title": string;
+            /**
+             * MFMなどでコードハイライト記法が使われている場合、タップするまで読み込まれなくなります。コードハイライトではハイライトする言語ごとにその定義ファイルを読み込む必要がありますが、それらが自動で読み込まれなくなるため、通信量の削減が見込めます。
+             */
             "description": string;
         };
     };
+    "_hemisphere": {
+        /**
+         * 北半球
+         */
+        "N": string;
+        /**
+         * 南半球
+         */
+        "S": string;
+        /**
+         * 一部のクライアント設定で、季節を判定するために使用します。
+         */
+        "caption": string;
+    };
+    "_reversi": {
+        /**
+         * リバーシ
+         */
+        "reversi": string;
+        /**
+         * 対局の設定
+         */
+        "gameSettings": string;
+        /**
+         * ボードを選択
+         */
+        "chooseBoard": string;
+        /**
+         * 先行/後攻
+         */
+        "blackOrWhite": string;
+        /**
+         * {name}が黒(先行)
+         */
+        "blackIs": ParameterizedString<"name">;
+        /**
+         * ルール
+         */
+        "rules": string;
+        /**
+         * 対局はまもなく開始されます
+         */
+        "thisGameIsStartedSoon": string;
+        /**
+         * 相手の準備が完了するのを待っています
+         */
+        "waitingForOther": string;
+        /**
+         * あなたの準備が完了するのを待っています
+         */
+        "waitingForMe": string;
+        /**
+         * 準備してください
+         */
+        "waitingBoth": string;
+        /**
+         * 準備完了
+         */
+        "ready": string;
+        /**
+         * 準備を再開
+         */
+        "cancelReady": string;
+        /**
+         * 相手のターンです
+         */
+        "opponentTurn": string;
+        /**
+         * あなたのターンです
+         */
+        "myTurn": string;
+        /**
+         * {name}のターンです
+         */
+        "turnOf": ParameterizedString<"name">;
+        /**
+         * {name}のターン
+         */
+        "pastTurnOf": ParameterizedString<"name">;
+        /**
+         * 投了
+         */
+        "surrender": string;
+        /**
+         * 投了により
+         */
+        "surrendered": string;
+        /**
+         * 時間切れ
+         */
+        "timeout": string;
+        /**
+         * 引き分け
+         */
+        "drawn": string;
+        /**
+         * {name}の勝ち
+         */
+        "won": ParameterizedString<"name">;
+        /**
+         * 黒
+         */
+        "black": string;
+        /**
+         * 白
+         */
+        "white": string;
+        /**
+         * 合計
+         */
+        "total": string;
+        /**
+         * {count}ターン目
+         */
+        "turnCount": ParameterizedString<"count">;
+        /**
+         * 自分の対局
+         */
+        "myGames": string;
+        /**
+         * みんなの対局
+         */
+        "allGames": string;
+        /**
+         * 終了
+         */
+        "ended": string;
+        /**
+         * 対局中
+         */
+        "playing": string;
+        /**
+         * 石の少ない方が勝ち(ロセオ)
+         */
+        "isLlotheo": string;
+        /**
+         * ループマップ
+         */
+        "loopedMap": string;
+        /**
+         * どこでも置けるモード
+         */
+        "canPutEverywhere": string;
+        /**
+         * 1ターンの時間制限
+         */
+        "timeLimitForEachTurn": string;
+        /**
+         * フリーマッチ
+         */
+        "freeMatch": string;
+        /**
+         * 対戦相手を探しています
+         */
+        "lookingForPlayer": string;
+        /**
+         * 対局がキャンセルされました
+         */
+        "gameCanceled": string;
+        /**
+         * 開始時に対局をタイムラインに投稿
+         */
+        "shareToTlTheGameWhenStart": string;
+        /**
+         * 対局を開始しました! #MisskeyReversi
+         */
+        "iStartedAGame": string;
+        /**
+         * 相手が設定を変更しました
+         */
+        "opponentHasSettingsChanged": string;
+        /**
+         * 変則許可 (完全フリー)
+         */
+        "allowIrregularRules": string;
+        /**
+         * 変則なし
+         */
+        "disallowIrregularRules": string;
+        /**
+         * 盤面に行・列番号を表示
+         */
+        "showBoardLabels": string;
+        /**
+         * 石をアイコンにする
+         */
+        "useAvatarAsStone": string;
+    };
+    "_offlineScreen": {
+        /**
+         * オフライン - サーバーに接続できません
+         */
+        "title": string;
+        /**
+         * サーバーに接続できません
+         */
+        "header": string;
+    };
 }
 declare const locales: {
     [lang: string]: Locale;
diff --git a/locales/it-IT.yml b/locales/it-IT.yml
index 33686ddc3d..7414e083d0 100644
--- a/locales/it-IT.yml
+++ b/locales/it-IT.yml
@@ -103,7 +103,7 @@ defaultNoteVisibility: "Privacy predefinita delle note"
 follow: "Segui"
 followRequest: "Richiesta di follow"
 followRequests: "Richieste di follow"
-unfollow: "Interrompi following"
+unfollow: "Smetti di seguire"
 followRequestPending: "Richiesta in approvazione"
 enterEmoji: "Inserisci emoji"
 renote: "Rinota"
@@ -131,6 +131,7 @@ overwriteFromPinnedEmojis: "Sovrascrivi con le impostazioni globali"
 reactionSettingDescription2: "Trascina per riorganizzare, clicca per cancellare, usa il pulsante \"+\" per aggiungere."
 rememberNoteVisibility: "Ricordare le impostazioni di visibilità delle note"
 attachCancel: "Rimuovi allegato"
+deleteFile: "File da Drive eliminato"
 markAsSensitive: "Segna come esplicito"
 unmarkAsSensitive: "Non segnare come esplicito "
 enterFileName: "Nome del file"
@@ -380,6 +381,11 @@ hcaptcha: "hCaptcha"
 enableHcaptcha: "Abilita hCaptcha"
 hcaptchaSiteKey: "Chiave del sito"
 hcaptchaSecretKey: "Chiave segreta"
+mcaptcha: "mCaptcha"
+enableMcaptcha: "Abilita hCaptcha"
+mcaptchaSiteKey: "Chiave del sito"
+mcaptchaSecretKey: "Chiave segreta"
+mcaptchaInstanceUrl: "URL della istanza mCaptcha"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "Abilita reCAPTCHA"
 recaptchaSiteKey: "Chiave del sito"
@@ -430,7 +436,7 @@ moderation: "moderazione"
 moderationNote: "Promemoria di moderazione"
 addModerationNote: "Aggiungi promemoria di moderazione"
 moderationLogs: "Cronologia di moderazione"
-nUsersMentioned: "{n} profili menzionati"
+nUsersMentioned: "{n} profili ne parlano"
 securityKeyAndPasskey: "Chiave di sicurezza e accesso"
 securityKey: "Chiave di sicurezza"
 lastUsed: "Ultima attività"
@@ -627,6 +633,7 @@ medium: "Medio"
 small: "Piccolo"
 generateAccessToken: "Genera token di accesso"
 permission: "Autorizzazioni "
+adminPermission: "Privilegi amministrativi"
 enableAll: "Abilita tutto"
 disableAll: "Disabilita tutto"
 tokenRequested: "Autorizza accesso al profilo"
@@ -652,7 +659,7 @@ hardWordMute: "Filtro parole forte"
 regexpError: "errore regex"
 regexpErrorDescription: "Si è verificato un errore nell'espressione regolare alla riga {line} della parola muta {tab}:"
 instanceMute: "Silenzia l'istanza"
-userSaysSomething: "{name} ha detto qualcosa"
+userSaysSomething: "{name} ha parlato"
 makeActive: "Attiva"
 display: "Visualizza"
 copy: "Copia"
@@ -670,6 +677,7 @@ useGlobalSettingDesc: "Quando attiva, verranno utilizzate le impostazioni notifi
 other: "Ulteriori"
 regenerateLoginToken: "Genera di nuovo un token di connessione"
 regenerateLoginTokenDescription: "Genera un nuovo token di autenticazione. Solitamente questa operazione non è necessaria: quando si genera un nuovo token, tutti i dispositivi vanno disconnessi."
+theKeywordWhenSearchingForCustomEmoji: "Questa sarà la parola chiave durante la ricerca di emoji personalizzate"
 setMultipleBySeparatingWithSpace: "È possibile creare multiple voci separate da spazi."
 fileIdOrUrl: "ID o URL del file"
 behavior: "Comportamento"
@@ -757,7 +765,7 @@ reloadToApplySetting: "Le tue preferenze verranno impostate dopo il ricaricament
 needReloadToApply: "È necessario riavviare per rendere effettive le modifiche."
 showTitlebar: "Visualizza la barra del titolo"
 clearCache: "Svuota la cache"
-onlineUsersCount: "{n} persone online"
+onlineUsersCount: "{n} persone attive adesso"
 nUsers: "{n} profili"
 nNotes: "{n}Note"
 sendErrorReports: "Invia segnalazioni di errori"
@@ -868,7 +876,7 @@ pubSub: "Publish/Subscribe del profilo"
 lastCommunication: "La comunicazione più recente"
 resolved: "Risolto"
 unresolved: "Non risolto"
-breakFollow: "Interrompi follow"
+breakFollow: "Impedire di seguirmi"
 breakFollowConfirm: "Vuoi davvero che questo profilo smetta di seguirti?"
 itsOn: "Abilitato"
 itsOff: "Disabilitato"
@@ -884,6 +892,8 @@ makeReactionsPublicDescription: "La lista delle reazioni che avete fatto è a di
 classic: "Classico"
 muteThread: "Silenzia conversazione"
 unmuteThread: "Riattiva la conversazione"
+followingVisibility: "Visibilità dei profili seguiti"
+followersVisibility: "Visibilità dei profili che ti seguono"
 continueThread: "Altre conversazioni"
 deleteAccountConfirm: "Così verrà eliminato il profilo. Vuoi procedere?"
 incorrectPassword: "La password è errata."
@@ -985,6 +995,7 @@ neverShow: "Non mostrare più"
 remindMeLater: "Rimanda"
 didYouLikeMisskey: "Ti piace Misskey?"
 pleaseDonate: "Misskey è il software libero utilizzato su {host}. Offrendo una donazione è più facile continuare a svilupparlo!"
+correspondingSourceIsAvailable: ""
 roles: "Ruoli"
 role: "Ruolo"
 noRole: "Ruolo non trovato"
@@ -1035,6 +1046,9 @@ resetPasswordConfirm: "Vuoi davvero ripristinare la password?"
 sensitiveWords: "Parole esplicite"
 sensitiveWordsDescription: "Imposta automaticamente \"Home\" alla visibilità delle Note che contengono una qualsiasi parola tra queste configurate. Puoi separarle per riga."
 sensitiveWordsDescription2: "Gli spazi creano la relazione \"E\" tra parole (questo E quello). Racchiudere una parola nelle slash \"/\" la trasforma in Espressione Regolare."
+prohibitedWords: "Parole proibite"
+prohibitedWordsDescription: "Verrà impedito di pubblicare Note che abbiano le parole indicate. Puoi impostare più parole, separatamente, su ogni riga."
+prohibitedWordsDescription2: "Gli spazi creano la relazione \"E\" tra parole (questo E quello). Racchiudere una parola nelle slash \"/\" la trasforma in Espressione Regolare."
 hiddenTags: "Hashtag nascosti"
 hiddenTagsDescription: "Impedire la visualizzazione del tag impostato nei trend. Puoi impostare più valori, uno per riga."
 notesSearchNotAvailable: "Non è possibile cercare tra le Note."
@@ -1053,6 +1067,8 @@ limitWidthOfReaction: "Limita la larghezza delle reazioni e ridimensionale"
 noteIdOrUrl: "ID della Nota o URL"
 video: "Video"
 videos: "Video"
+audio: "Audio"
+audioFiles: "Audio"
 dataSaver: "Risparmia dati"
 accountMigration: "Migrazione del profilo"
 accountMoved: "Questo profilo ha migrato altrove:"
@@ -1156,6 +1172,13 @@ hideRepliesToOthersInTimelineAll: "Nascondi le risposte dei tuoi follow nella TL
 confirmShowRepliesAll: "Questa è una attività irreversibile. Vuoi davvero includere tutte le risposte dei following in TL?"
 confirmHideRepliesAll: "Questa è una attività irreversibile. Vuoi davvero escludere tutte le risposte dei following in TL?"
 externalServices: "Servizi esterni"
+sourceCode: "Codice sorgente"
+sourceCodeIsNotYetProvided: ""
+repositoryUrl: "URL della repository"
+repositoryUrlDescription: "Se esiste un repository il cui il codice sorgente è disponibile pubblicamente, inserisci il suo URL. Se stai utilizzando Misskey così com'è (senza alcuna modifica al codice sorgente), inserisci https://github.com/misskey-dev/misskey."
+repositoryUrlOrTarballRequired: "Se non disponi di un repository pubblico, dovrai fornire un file tarball (tar). Vedere .config/example.yml per i dettagli."
+feedback: "Feedback"
+feedbackUrl: "URL di feedback"
 impressum: "Dichiarazione di proprietà"
 impressumUrl: "URL della dichiarazione di proprietà"
 impressumDescription: "La dichiarazione di proprietà, è obbligatoria in alcuni paesi come la Germania (Impressum)."
@@ -1183,6 +1206,28 @@ remainingN: "Rimangono: {n}"
 overwriteContentConfirm: "Vuoi davvero sostituire l'attuale contenuto?"
 seasonalScreenEffect: "Schermate in base alla stagione"
 decorate: "Decora"
+addMfmFunction: "Aggiungi decorazioni"
+enableQuickAddMfmFunction: "Attiva il selettore di funzioni MFM"
+bubbleGame: "Bubble Game"
+sfx: "Effetti sonori"
+soundWillBePlayed: "Con musica ed effetti sonori"
+showReplay: "Vedi i replay"
+replay: "Replay"
+replaying: "Replay in corso"
+ranking: "Classifica"
+lastNDays: "Ultimi {n} giorni"
+backToTitle: "Torna al titolo"
+hemisphere: "Geolocalizzazione"
+withSensitive: "Mostra le Note con allegati espliciti"
+userSaysSomethingSensitive: "Note da {name} con allegati espliciti"
+enableHorizontalSwipe: "Trascina per invertire i tab"
+surrender: "Annulla"
+_bubbleGame:
+  howToPlay: "Come giocare"
+  _howToPlay:
+    section1: "Scegli la posizione e rilascia l'oggetto nel contenitore."
+    section2: "Se due oggetti dello stesso tipo si toccano, si trasformano in un oggetto diverso, aumentando il punteggio."
+    section3: "Se gli oggetti escono dal limite superiore del contenitore, il gioco finisce. Cerca di ottenere un punteggio elevato fondendo gli oggetti, evitando che escano dal contenitore!"
 _announcement:
   forExistingUsers: "Solo ai profili attuali"
   forExistingUsersDescription: "L'annuncio sarà visibile solo ai profili esistenti in questo momento. Se disabilitato, sarà visibile anche ai profili che verranno creati dopo la pubblicazione di questo annuncio."
@@ -1553,6 +1598,13 @@ _achievements:
     _tutorialCompleted:
       title: "Attestato di partecipazione al corso per principianti di Misskey"
       description: "Ha completato il tutorial"
+    _bubbleGameExplodingHead:
+      title: "🤯"
+      description: "Estrai l'oggetto più grande dal Bubble Game"
+    _bubbleGameDoubleExplodingHead:
+      title: "Doppio 🤯"
+      description: "Due oggetti più grossi contemporaneamente nel Bubble Game"
+      flavor: "Ha le dimensioni di una bento-box 🤯 🤯"
 _role:
   new: "Nuovo ruolo"
   edit: "Modifica ruolo"
@@ -1643,6 +1695,7 @@ _emailUnavailable:
   disposable: "Indirizzo email non utilizzabile"
   mx: "Server email non corretto"
   smtp: "Il server email non risponde"
+  banned: "Non puoi registrarti con questo indirizzo email"
 _ffVisibility:
   public: "Pubblica"
   followers: "Mostra solo ai follower"
@@ -1715,6 +1768,8 @@ _aboutMisskey:
   contributors: "Principali sostenitori"
   allContributors: "Tutti i sostenitori"
   source: "Codice sorgente"
+  original: "Originale"
+  thisIsModifiedVersion: "{name} sta usando una versione modificata diversa da Misskey originale."
   translation: "Tradurre Misskey"
   donate: "Sostieni Misskey"
   morePatrons: "Apprezziamo sinceramente il supporto di tante altre persone. Grazie mille! 🥰"
@@ -1935,6 +1990,55 @@ _permissions:
   "write:flash": "Modifica Play"
   "read:flash-likes": "Visualizza lista di Play piaciuti"
   "write:flash-likes": "Modifica lista di Play piaciuti"
+  "read:admin:abuse-user-reports": "Mostra i report dai profili utente"
+  "write:admin:delete-account": "Elimina l'account utente"
+  "write:admin:delete-all-files-of-a-user": "Elimina i file dell'account utente"
+  "read:admin:index-stats": "Visualizza informazioni sugli indici del database"
+  "read:admin:table-stats": "Visualizza informazioni sulle tabelle del database"
+  "read:admin:user-ips": "Visualizza indirizzi IP degli account"
+  "read:admin:meta": "Visualizza i metadati dell'istanza"
+  "write:admin:reset-password": "Ripristina la password dell'account utente"
+  "write:admin:resolve-abuse-user-report": "Risolvere le segnalazioni dagli account utente"
+  "write:admin:send-email": "Spedire email"
+  "read:admin:server-info": "Vedere le informazioni sul server"
+  "read:admin:show-moderation-log": "Vedere lo storico di moderazione"
+  "read:admin:show-user": "Vedere le informazioni private degli account utente"
+  "read:admin:show-users": "Vedere le informazioni private degli account utente"
+  "write:admin:suspend-user": "Sospendere i profili"
+  "write:admin:unset-user-avatar": "Rimuovere la foto profilo dai profili"
+  "write:admin:unset-user-banner": "Rimuovere l'immagine testata dai profili"
+  "write:admin:unsuspend-user": "Togliere la sospensione ai profili"
+  "write:admin:meta": "Modificare i metadati dell'istanza"
+  "write:admin:user-note": "Scrivere annotazioni di moderazione"
+  "write:admin:roles": "Gestire i ruoli"
+  "read:admin:roles": "Vedere i ruoli"
+  "write:admin:relays": "Gestire i Relay"
+  "read:admin:relays": "Vedere i Relay"
+  "write:admin:invite-codes": "Gestire codici di invito"
+  "read:admin:invite-codes": "Vedere codici di invito"
+  "write:admin:announcements": "Gestire gli annunci"
+  "read:admin:announcements": "Leggere gli annunci"
+  "write:admin:avatar-decorations": "Gestire le decorazioni"
+  "read:admin:avatar-decorations": "Vedere le decorazioni"
+  "write:admin:federation": "Gestire la federazione"
+  "write:admin:account": "Vedere la federazione"
+  "read:admin:account": "Vedere le utenze"
+  "write:admin:emoji": "Gestire le emoji personalizzate"
+  "read:admin:emoji": "Vedere le emoji personalizzate"
+  "write:admin:queue": "Gestire la coda di attività"
+  "read:admin:queue": "Vedere la coda di attività"
+  "write:admin:promo": "Gestire le promozioni"
+  "write:admin:drive": "Gestire il Drive degli account"
+  "read:admin:drive": "Vedere il Drive degli account"
+  "read:admin:stream": "Usare le API Websocket"
+  "write:admin:ad": "Gestire i banner pubblicitari"
+  "read:admin:ad": "Vedere i banner pubblicitari"
+  "write:invite-codes": "Creare codici di invito"
+  "read:invite-codes": "Vedere i codici di invito"
+  "write:clip-favorite": "Impostare Clip preferite"
+  "read:clip-favorite": "Vedere Clip preferite"
+  "read:federation": "Vedere la federazione"
+  "write:report-abuse": "Inviare segnalazioni"
 _auth:
   shareAccessTitle: "Permessi dell'applicazione"
   shareAccess: "Vuoi autorizzare {name} ad accedere al tuo profilo?"
@@ -1979,7 +2083,7 @@ _widgets:
   postForm: "Finestra di pubblicazione"
   slideshow: "Diapositive"
   button: "Pulsante"
-  onlineUsers: "Persone online"
+  onlineUsers: "Persone attive adesso"
   jobQueue: "Coda di lavoro"
   serverMetric: "Statistiche server"
   aiscript: "Console AiScript"
@@ -2056,6 +2160,7 @@ _profile:
 _exportOrImport:
   allNotes: "Tutte le note"
   favoritedNotes: "Note preferite"
+  clips: "Clip"
   followingList: "Follow"
   muteList: "Elenco profili silenziati"
   blockingList: "Elenco profili bloccati"
@@ -2174,6 +2279,7 @@ _notification:
   pollEnded: "Risultati del sondaggio."
   newNote: "Nuove Note"
   unreadAntennaNote: "Antenna {name}"
+  roleAssigned: "Ruolo assegnato"
   emptyPushNotificationMessage: "Le notifiche push sono state aggiornate."
   achievementEarned: "Obiettivo raggiunto"
   testNotification: "Prova la notifica"
@@ -2195,6 +2301,7 @@ _notification:
     pollEnded: "Sondaggio chiuso."
     receiveFollowRequest: "Richiesta di follow ricevuta"
     followRequestAccepted: "Richiesta di follow accettata"
+    roleAssigned: "Ruolo concesso"
     achievementEarned: "Risultato raggiunto"
     app: "Notifiche da applicazioni"
   _actions:
@@ -2353,3 +2460,53 @@ _dataSaver:
   _code:
     title: "Codice evidenziato"
     description: "Impedire che il codice sorgente sia automaticamente evidenziato. Evidenziare il codice richiede il caricamento di un file per ogni linguaggio. Puoi evidenziare soltanto il codice che intendi leggere e ridurre il traffico inutilizzato."
+_hemisphere:
+  N: "Emisfero boreale"
+  S: "Emisfero australe"
+  caption: "Utile per alcune impostazioni del client, per determinare la stagione."
+_reversi:
+  reversi: "Reversi"
+  gameSettings: "Impostazioni di gioco"
+  chooseBoard: "Segli la tavola"
+  blackOrWhite: "Neri / Bianchi"
+  blackIs: "{name} muove i Neri"
+  rules: "Regole del gioco"
+  thisGameIsStartedSoon: "Il gioco sta per iniziare"
+  waitingForOther: "Attendere l'avversario"
+  waitingForMe: "Ti stanno aspettando"
+  waitingBoth: "Preparatevi"
+  ready: "Pronti"
+  cancelReady: "Riprendere la preparazione"
+  opponentTurn: "Turno avversario"
+  myTurn: "Tocca a te"
+  turnOf: "Tocca a {name}"
+  pastTurnOf: "Turno di {name}"
+  surrender: "Mi arrendo"
+  surrendered: "Ha ceduto"
+  timeout: "Tempo scaduto"
+  drawn: "Pareggio"
+  won: "Ha vinto {name}"
+  black: "Neri"
+  white: "Bianchi"
+  total: "Totale"
+  turnCount: "Turno N. {count}"
+  myGames: "Le mie sfide"
+  allGames: "Tutte le sfide"
+  ended: "Conclusione"
+  playing: "In gioco"
+  isLlotheo: "Vince chi ha meno pietre (Roseo)"
+  loopedMap: "Mappa ricorsiva"
+  canPutEverywhere: "Modalità che può essere posizionata ovunque"
+  timeLimitForEachTurn: "Tempo limite per turno"
+  freeMatch: "Sfida libera"
+  lookingForPlayer: "Alla ricerca di un avversario"
+  gameCanceled: "Sfida cancellata"
+  shareToTlTheGameWhenStart: "Pubblica l'inizio della partita sulla tua Timeline"
+  iStartedAGame: "Inizia la sfida! #MisskeyReversi"
+  opponentHasSettingsChanged: "L'avversario ha cambiato configurazione"
+  allowIrregularRules: "Regole inconsuete (completamente libere)"
+  disallowIrregularRules: "Impedire le regole inconsuete"
+_offlineScreen:
+  title: "Scollegato. Impossibile connettersi al server"
+  header: "Impossibile connettersi al server"
+
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index b632fbad63..57f52c64b2 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -15,7 +15,7 @@ gotIt: "わかった"
 cancel: "キャンセル"
 noThankYou: "やめておく"
 enterUsername: "ユーザー名を入力"
-renotedBy: "{user}がリノート"
+renotedBy: "{user}がブースト"
 noNotes: "ノートはありません"
 noNotifications: "通知はありません"
 instance: "サーバー"
@@ -46,16 +46,16 @@ pin: "ピン留め"
 unpin: "ピン留め解除"
 copyContent: "内容をコピー"
 copyLink: "リンクをコピー"
-copyLinkRenote: "リノートのリンクをコピー"
+copyLinkRenote: "ブーストのリンクをコピー"
 delete: "削除"
 deleteAndEdit: "削除して編集"
-deleteAndEditConfirm: "このノートを削除してもう一度編集しますか?このノートへのリアクション、リノート、返信も全て削除されます。"
+deleteAndEditConfirm: "このノートを削除してもう一度編集しますか?このノートへのリアクション、ブースト、返信も全て削除されます。"
 addToList: "リストに追加"
 addToAntenna: "アンテナに追加"
 sendMessage: "メッセージを送信"
 copyRSS: "RSSをコピー"
 copyUsername: "ユーザー名をコピー"
-openRemoteProfile: "リモートプロファイルを開く"
+openRemoteProfile: "リモートプロフィールを開く"
 copyUserId: "ユーザーIDをコピー"
 copyNoteId: "ノートIDをコピー"
 copyFileId: "ファイルIDをコピー"
@@ -107,15 +107,15 @@ followRequests: "フォロー申請"
 unfollow: "フォロー解除"
 followRequestPending: "フォロー許可待ち"
 enterEmoji: "絵文字を入力"
-renote: "リノート"
-unrenote: "リノート解除"
-renoted: "ブースト。"
+renote: "ブースト"
+unrenote: "ブースト解除"
+renoted: "ブーストしました。"
 quoted: "引用。"
-rmboost: "アンブースト。"
-cantRenote: "この投稿はリノートできません。"
-cantReRenote: "リノートをリノートすることはできません。"
+rmboost: "ブースト解除しました。"
+cantRenote: "この投稿はブーストできません。"
+cantReRenote: "ブーストをブーストすることはできません。"
 quote: "引用"
-inChannelRenote: "チャンネル内リノート"
+inChannelRenote: "チャンネル内ブースト"
 inChannelQuote: "チャンネル内引用"
 pinnedNote: "ピン留めされたノート"
 pinned: "ピン留め"
@@ -134,13 +134,14 @@ overwriteFromPinnedEmojis: "全般設定から上書きする"
 reactionSettingDescription2: "ドラッグして並び替え、クリックして削除、+を押して追加します。"
 rememberNoteVisibility: "公開範囲を記憶する"
 attachCancel: "添付取り消し"
+deleteFile: "ファイルを削除"
 markAsSensitive: "センシティブとして設定"
 unmarkAsSensitive: "センシティブを解除する"
 enterFileName: "ファイル名を入力"
 mute: "ミュート"
 unmute: "ミュート解除"
-renoteMute: "リノートをミュート"
-renoteUnmute: "リノートのミュートを解除"
+renoteMute: "ブーストをミュート"
+renoteUnmute: "ブーストのミュートを解除"
 block: "ブロック"
 unblock: "ブロック解除"
 markAsNSFW: "ユーザーのすべてのメディアをNSFWとしてマークする"
@@ -209,8 +210,8 @@ charts: "チャート"
 perHour: "1時間ごと"
 perDay: "1日ごと"
 stopActivityDelivery: "アクティビティの配送を停止"
-blockThisInstance: "このサーバーをブロック"
-silenceThisInstance: "サーバーをサイレンス"
+blockThisInstance: "このインスタンスをブロック"
+silenceThisInstance: "インスタンスをサイレンス"
 operations: "操作"
 software: "ソフトウェア"
 version: "バージョン"
@@ -231,7 +232,7 @@ clearCachedFilesConfirm: "キャッシュされたリモートファイルをす
 blockedInstances: "ブロックしたサーバー"
 blockedInstancesDescription: "ブロックしたいサーバーのホストを改行で区切って設定します。ブロックされたサーバーは、このインスタンスとやり取りできなくなります。"
 silencedInstances: "サイレンスしたサーバー"
-silencedInstancesDescription: "サイレンスしたいサーバーのホストを改行で区切って設定します。サイレンスされたサーバーに所属するアカウントはすべて「サイレンス」として扱われ、フォローがすべてリクエストになり、フォロワーでないローカルアカウントにはメンションできなくなります。ブロックしたインスタンスには影響しません。"
+silencedInstancesDescription: "サイレンスしたいインスタンスのホストを改行で区切って設定します。サイレンスされたインスタンスに所属するアカウントはすべて「サイレンス」として扱われ、フォローがすべてリクエストになり、フォロワーでないローカルアカウントにはメンションできなくなります。ブロックしたインスタンスには影響しません。"
 muteAndBlock: "ミュートとブロック"
 mutedUsers: "ミュートしたユーザー"
 blockedUsers: "ブロックしたユーザー"
@@ -390,6 +391,11 @@ hcaptcha: "hCaptcha"
 enableHcaptcha: "hCaptchaを有効にする"
 hcaptchaSiteKey: "サイトキー"
 hcaptchaSecretKey: "シークレットキー"
+mcaptcha: "mCaptcha"
+enableMcaptcha: "mCaptchaを有効にする"
+mcaptchaSiteKey: "サイトキー"
+mcaptchaSecretKey: "シークレットキー"
+mcaptchaInstanceUrl: "mCaptchaのインスタンスのURL"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "reCAPTCHAを有効にする"
 recaptchaSiteKey: "サイトキー"
@@ -550,6 +556,8 @@ objectStorageUseProxy: "Proxyを利用する"
 objectStorageUseProxyDesc: "API接続にproxyを利用しない場合はオフにしてください"
 objectStorageSetPublicRead: "アップロード時に'public-read'を設定する"
 s3ForcePathStyleDesc: "s3ForcePathStyleを有効にすると、バケット名をURLのホスト名ではなくパスの一部として指定することを強制します。セルフホストされたMinioなどの使用時に有効にする必要がある場合があります。"
+deeplFreeMode: "DeepLX-JS を使用する (認証キーなし)"
+deeplFreeModeDescription: "ヘルプが必要ですか? DeepLX-JSのセットアップ方法については、ドキュメントを参照してください。"
 serverLogs: "サーバーログ"
 deleteAll: "全て削除"
 showFixedPostForm: "タイムライン上部に投稿フォームを表示する"
@@ -640,6 +648,7 @@ medium: "中"
 small: "小"
 generateAccessToken: "アクセストークンの発行"
 permission: "権限"
+adminPermission: "管理者権限"
 enableAll: "全て有効にする"
 disableAll: "全て無効にする"
 tokenRequested: "アカウントへのアクセス許可"
@@ -683,13 +692,14 @@ useGlobalSettingDesc: "オンにすると、アカウントの通知設定が使
 other: "その他"
 regenerateLoginToken: "ログイントークンを再生成"
 regenerateLoginTokenDescription: "ログインに使用される内部トークンを再生成します。通常この操作を行う必要はありません。再生成すると、全てのデバイスでログアウトされます。"
+theKeywordWhenSearchingForCustomEmoji: "カスタム絵文字を検索する時のキーワードになります。"
 setMultipleBySeparatingWithSpace: "スペースで区切って複数設定できます。"
 fileIdOrUrl: "ファイルIDまたはURL"
 behavior: "動作"
 sample: "サンプル"
 abuseReports: "通報"
 reportAbuse: "通報"
-reportAbuseRenote: "リノートを通報"
+reportAbuseRenote: "ブーストを通報"
 reportAbuseOf: "{name}を通報する"
 fillAbuseReportDescription: "通報理由の詳細を記入してください。対象のノートがある場合はそのURLも記入してください。"
 abuseReported: "内容が送信されました。ご報告ありがとうございました。"
@@ -723,9 +733,9 @@ manageAccessTokens: "アクセストークンの管理"
 accountInfo: "アカウント情報"
 notesCount: "ノートの数"
 repliesCount: "返信した数"
-renotesCount: "リノートした数"
+renotesCount: "ブーストした数"
 repliedCount: "返信された数"
-renotedCount: "リノートされた数"
+renotedCount: "ブーストされた数"
 followingCount: "フォロー数"
 followersCount: "フォロワー数"
 sentReactionsCount: "リアクションした数"
@@ -959,6 +969,10 @@ numberOfPageCache: "ページキャッシュ数"
 numberOfPageCacheDescription: "多くすると利便性が向上しますが、負荷とメモリ使用量が増えます。"
 numberOfReplies: "スレッド内の返信数"
 numberOfRepliesDescription: "この数値を大きくすると、より多くの返信が表示されます。この値を大きくしすぎると、返信が窮屈になり、読めなくなることがあります。"
+boostSettings: "ブースト設定"
+showVisibilitySelectorOnBoost: "可視性セレクタを表示"
+showVisibilitySelectorOnBoostDescription: "無効の場合、以下で定義されるデフォルトの可視性が使用され、セレクタは表示されません。"
+visibilityOnBoost: "デフォルトのブースト可視性の設定"
 logoutConfirm: "ログアウトしますか?"
 lastActiveDate: "最終利用日時"
 statusbar: "ステータスバー"
@@ -1010,6 +1024,8 @@ neverShow: "今後表示しない"
 remindMeLater: "また後で"
 didYouLikeMisskey: "Sharkeyを気に入っていただけましたか?"
 pleaseDonate: "Sharkeyは{host}が使用している無料のソフトウェアです。これからも開発を続けられるように、ぜひ寄付をお願いします!"
+pleaseDonateInstance: "インスタンス管理者への寄付によって{host}を直接サポートすることもできます。"
+correspondingSourceIsAvailable: "対応するソースコードは{anchor}から利用可能です。"
 roles: "ロール"
 role: "ロール"
 noRole: "ロールはありません"
@@ -1036,7 +1052,10 @@ thisPostMayBeAnnoying: "この投稿は迷惑になる可能性があります
 thisPostMayBeAnnoyingHome: "ホームに投稿"
 thisPostMayBeAnnoyingCancel: "やめる"
 thisPostMayBeAnnoyingIgnore: "このまま投稿"
-collapseRenotes: "見たことのあるリノートを省略して表示"
+thisPostIsMissingAltTextCancel: "やめる"
+thisPostIsMissingAltTextIgnore: "このまま投稿"
+thisPostIsMissingAltText: "この投稿に添付されたファイルの 1 つに代替テキストがありません。すべての添付ファイルに代替テキストが含まれていることを確認してください。"
+collapseRenotes: "見たことのあるブーストを省略して表示"
 collapseFiles: "ファイルを折りたたむ"
 autoloadConversation: "返信に会話を読み込む"
 internalServerError: "サーバー内部エラー"
@@ -1063,6 +1082,9 @@ resetPasswordConfirm: "パスワードリセットしますか?"
 sensitiveWords: "センシティブワード"
 sensitiveWordsDescription: "設定したワードが含まれるノートの公開範囲をホームにします。改行で区切って複数設定できます。"
 sensitiveWordsDescription2: "スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。"
+prohibitedWords: "禁止ワード"
+prohibitedWordsDescription: "設定したワードが含まれるノートを投稿しようとした際、エラーとなるようにします。改行で区切って複数設定できます。"
+prohibitedWordsDescription2: "スペースで区切るとAND指定になり、キーワードをスラッシュで囲むと正規表現になります。"
 hiddenTags: "非表示ハッシュタグ"
 hiddenTagsDescription: "設定したタグをトレンドに表示させないようにします。改行で区切って複数設定できます。"
 notesSearchNotAvailable: "ノート検索は利用できません。"
@@ -1081,16 +1103,19 @@ limitWidthOfReaction: "リアクションの最大横幅を制限し、縮小し
 noteIdOrUrl: "ノートIDまたはURL"
 video: "動画"
 videos: "動画"
+audio: "音声"
+audioFiles: "音声"
 dataSaver: "データセーバー"
 accountMigration: "アカウントの移行"
 accountMoved: "このユーザーは新しいアカウントに移行しました:"
 accountMovedShort: "このアカウントは移行されています"
 operationForbidden: "この操作はできません"
 forceShowAds: "常に広告を表示する"
+oneko: "猫友達 :3"
 addMemo: "メモを追加"
 editMemo: "メモを編集"
 reactionsList: "リアクション一覧"
-renotesList: "リノート一覧"
+renotesList: "ブースト一覧"
 notificationDisplay: "通知の表示"
 leftTop: "左上"
 rightTop: "右上"
@@ -1132,9 +1157,9 @@ installed: "インストール済み"
 branding: "ブランディング"
 enableServerMachineStats: "サーバーのマシン情報を公開する"
 enableAchievements: "実績を有効にする"
-turnOffAchievements: "これをオフにすると、達成システムは無効になります。"
-enableBotTrending: "ハッシュタグにボットを追加する"
-turnOffBotTrending: "これをオフにすると、ボットがハッシュタグを入力しなくなります。"
+turnOffAchievements: "オフにすると実績システムは無効になります。"
+enableBotTrending: "botのハッシュタグ追加を許可する"
+turnOffBotTrending: "オフにするとボットがハッシュタグを入力しなくなります。"
 enableIdenticonGeneration: "ユーザーごとのIdenticon生成を有効にする"
 turnOffToImprovePerformance: "オフにするとパフォーマンスが向上します。"
 createInviteCode: "招待コードを作成"
@@ -1165,7 +1190,7 @@ pastAnnouncements: "過去のお知らせ"
 youHaveUnreadAnnouncements: "未読のお知らせがあります。"
 useSecurityKey: "ブラウザまたはデバイスの指示に従って、セキュリティキーまたはパスキーを使用してください。"
 replies: "返信"
-renotes: "リノート"
+renotes: "ブースト"
 loadReplies: "返信を見る"
 loadConversation: "会話を見る"
 pinnedList: "ピン留めされたリスト"
@@ -1178,10 +1203,11 @@ unnotifyNotes: "投稿の通知を解除"
 authentication: "認証"
 authenticationRequiredToContinue: "続けるには認証を行ってください"
 dateAndTime: "日時"
-showRenotes: "リノートを表示"
+showRenotes: "ブーストを表示"
 edited: "編集済み"
 notificationRecieveConfig: "通知の受信設定"
 mutualFollow: "相互フォロー"
+followingOrFollower: "フォロー中またはフォロワー"
 fileAttachedOnly: "ファイル付きのみ"
 showRepliesToOthersInTimeline: "TLに他の人への返信を含める"
 hideRepliesToOthersInTimeline: "TLに他の人への返信を含めない"
@@ -1190,12 +1216,21 @@ hideRepliesToOthersInTimelineAll: "TLに現在フォロー中の人全員の返
 confirmShowRepliesAll: "この操作は元に戻せません。本当にTLに現在フォロー中の人全員の返信を含めるようにしますか?"
 confirmHideRepliesAll: "この操作は元に戻せません。本当にTLに現在フォロー中の人全員の返信を含めないようにしますか?"
 externalServices: "外部サービス"
+sourceCode: "ソースコード"
+sourceCodeIsNotYetProvided: "ソースコードはまだ提供されていません。この問題の修正について管理者に問い合わせてください。"
+repositoryUrl: "リポジトリURL"
+repositoryUrlDescription: "ソースコードが公開されているリポジトリがある場合、そのURLを記入します。Misskeyを現状のまま(ソースコードにいかなる変更も加えずに)使用している場合は https://github.com/misskey-dev/misskey と記入します。"
+repositoryUrlOrTarballRequired: "リポジトリを公開していない場合、代わりにtarballを提供する必要があります。詳細は.config/example.ymlを参照してください。"
+feedback: "フィードバック"
+feedbackUrl: "フィードバックURL"
 impressum: "運営者情報"
 impressumUrl: "運営者情報URL"
 impressumDescription: "ドイツなどの一部の国と地域では表示が義務付けられています(Impressum)。"
 privacyPolicy: "プライバシーポリシー"
 privacyPolicyUrl: "プライバシーポリシーURL"
 tosAndPrivacyPolicy: "利用規約・プライバシーポリシー"
+donation: "寄付する"
+donationUrl: "寄付URL"
 avatarDecorations: "アイコンデコレーション"
 attach: "付ける"
 detach: "外す"
@@ -1219,6 +1254,40 @@ seasonalScreenEffect: "季節に応じた画面の演出"
 decorate: "デコる"
 addMfmFunction: "装飾を追加"
 enableQuickAddMfmFunction: "高度なMFMのピッカーを表示する"
+bubbleGame: "バブルゲーム"
+sfx: "効果音"
+soundWillBePlayed: "サウンドが再生されます"
+showReplay: "リプレイを見る"
+replay: "リプレイ"
+replaying: "リプレイ中"
+endReplay: "リプレイを終了"
+copyReplayData: "リプレイデータをコピー"
+ranking: "ランキング"
+lastNDays: "直近{n}日"
+backToTitle: "タイトルへ"
+hemisphere: "お住まいの地域"
+withSensitive: "センシティブなファイルを含むノートを表示"
+userSaysSomethingSensitive: "{name}のセンシティブなファイルを含む投稿"
+enableHorizontalSwipe: "スワイプしてタブを切り替える"
+loading: "読み込み中"
+surrender: "やめる"
+gameRetry: "リトライ"
+
+_bubbleGame:
+  howToPlay: "遊び方"
+  hold: "ホールド"
+  _score:
+    score: "スコア"
+    scoreYen: "稼いだ金額"
+    highScore: "ハイスコア"
+    maxChain: "最大チェーン数"
+    yen: "{yen}円"
+    estimatedQty: "{qty}個分"
+    scoreSweets: "おにぎり {onigiriQtyWithUnit}"
+  _howToPlay:
+    section1: "位置を調整してハコにモノを落とします。"
+    section2: "同じ種類のモノがくっつくと別のモノに変化して、スコアが得られます。"
+    section3: "モノがハコからあふれるとゲームオーバーです。ハコからあふれないようにしつつモノを融合させてハイスコアを目指そう!"
 
 _announcement:
   forExistingUsers: "既存ユーザーのみ"
@@ -1229,7 +1298,7 @@ _announcement:
   tooManyActiveAnnouncementDescription: "アクティブなお知らせが多いため、UXが低下する可能性があります。終了したお知らせはアーカイブすることを検討してください。"
   readConfirmTitle: "既読にしますか?"
   readConfirmText: "「{title}」の内容を読み、既読にします。"
-  shouldNotBeUsedToPresentPermanentInfo: "特に新規ユーザーのUXを損ねる可能性が高いため、ストック情報ではなくフロー情報の掲示にお知らせを使用することを推奨します。"
+  shouldNotBeUsedToPresentPermanentInfo: "特に新規ユーザーのUXを損ねる可能性が高いため、常時掲示するための情報ではなく、即時性が求められる情報の掲示のためにお知らせを使用することを推奨します。"
   dialogAnnouncementUxWarn: "ダイアログ形式のお知らせが同時に2つ以上ある場合、UXに悪影響を及ぼす可能性が非常に高いため、使用は慎重に行うことを推奨します。"
   silence: "非通知"
   silenceDescription: "オンにすると、このお知らせは通知されず、既読にする必要もなくなります。"
@@ -1288,8 +1357,8 @@ _initialTutorial:
     _visibility:
       description: "ノートを表示できる相手を制限できます。"
       public: "すべてのユーザーに公開。"
-      home: "ホームタイムラインのみに公開。フォロワー・プロフィールを見に来た人・リノートから、他のユーザーも見ることができます。"
-      followers: "フォロワーにのみ公開。本人以外がリノートすることはできず、またフォロワー以外は閲覧できません。"
+      home: "ホームタイムラインのみに公開。フォロワー・プロフィールを見に来た人・ブーストから、他のユーザーも見ることができます。"
+      followers: "フォロワーにのみ公開。本人以外がブーストすることはできず、またフォロワー以外は閲覧できません。"
       direct: "指定したユーザーにのみ公開され、また相手に通知が入ります。ダイレクトメッセージのかわりにお使いいただけます。"
       doNotSendConfidencialOnDirect1: "機密情報は送信する際は注意してください。"
       doNotSendConfidencialOnDirect2: "送信先のサーバーの管理者は投稿内容を見ることが可能なので、信頼できないサーバーのユーザーにダイレクト投稿を送信する場合は、機密情報の扱いに注意が必要です。"
@@ -1597,6 +1666,13 @@ _achievements:
     _tutorialCompleted:
       title: "Sharkey初心者講座 修了証"
       description: "チュートリアルを完了した"
+    _bubbleGameExplodingHead:
+      title: "🤯"
+      description: "バブルゲームで最も大きいモノを出した"
+    _bubbleGameDoubleExplodingHead:
+      title: "ダブル🤯"
+      description: "バブルゲームで最も大きいモノを2つ同時に出した"
+      flavor: "これくらいの おべんとばこに 🤯 🤯 ちょっとつめて"
 
 _role:
   new: "ロールの作成"
@@ -1636,10 +1712,11 @@ _role:
     high: "高"
   _options:
     gtlAvailable: "グローバルタイムラインの閲覧"
-    btlAvailable: "バブルのタイムラインを見ることができる"
+    btlAvailable: "バブルタイムラインの閲覧"
     ltlAvailable: "ローカルタイムラインの閲覧"
     canPublicNote: "パブリック投稿の許可"
     canImportNotes: "ノートのインポートが可能"
+    mentionMax: "ノート内の最大メンション数"
     canInvite: "サーバー招待コードの発行"
     inviteLimit: "招待コードの作成可能数"
     inviteLimitCycle: "招待コードの発行間隔"
@@ -1663,6 +1740,7 @@ _role:
     canUseTranslator: "翻訳機能の利用"
     avatarDecorationLimit: "アイコンデコレーションの最大取付個数"
   _condition:
+    roleAssignedTo: "マニュアルロールにアサイン済み"
     isLocal: "ローカルユーザー"
     isRemote: "リモートユーザー"
     createdLessThan: "アカウント作成から~以内"
@@ -1774,12 +1852,16 @@ _registry:
   createKey: "キーを作成"
 
 _aboutMisskey:
-  about: "Sharkeyは、2014年からsyuiloによって開発されているMisskeyをベースにしたオープンソースのソフトウェアです。"
+  about: "Sharkeyは、Misskeyをベースにしたオープンソースのソフトウェアです。"
   contributors: "主なコントリビューター"
   allContributors: "全てのコントリビューター"
   source: "ソースコード"
+  original: "Misskey オリジナル"
+  original_sharkey: "Sharkey オリジナル"
+  thisIsModifiedVersion: "{name}はオリジナルのSharkeyを改変したバージョンを使用しています。"
   translation: "Sharkeyを翻訳"
-  donate: "Sharkeyに寄付"
+  donate: "Misskeyに寄付"
+  donate_sharkey: "Sharkeyに寄付"
   morePatrons: "他にも多くの方が支援してくれています。ありがとうございます🥰"
   patrons: "支援者"
   projectMembers: "プロジェクトメンバー"
@@ -1812,7 +1894,7 @@ _channel:
   notesCount: "{n}投稿があります"
   nameAndDescription: "名前と説明"
   nameOnly: "名前のみ"
-  allowRenoteToExternal: "チャンネル外へのリノートと引用リノートを許可する"
+  allowRenoteToExternal: "チャンネル外へのブーストと引用ブーストを許可する"
 
 _menuDisplay:
   sideFull: "横"
@@ -1826,7 +1908,7 @@ _wordMute:
   muteWordsDescription2: "キーワードをスラッシュで囲むと正規表現になります。"
 
 _instanceMute:
-  instanceMuteDescription: "ミュートしたサーバーのユーザーへの返信を含めて、設定したサーバーの全てのノートとRenoteをミュートします。"
+  instanceMuteDescription: "ミュートしたサーバーのユーザーへの返信を含めて、設定したサーバーの全てのノートとブーストをミュートします。"
   instanceMuteDescription2: "改行で区切って設定します"
   title: "設定したサーバーのノートを隠します。"
   heading: "ミュートするサーバー"
@@ -1880,7 +1962,7 @@ _theme:
     hashtag: "ハッシュタグ"
     mention: "メンション"
     mentionMe: "あなた宛てメンション"
-    renote: "Renote"
+    renote: "Boost"
     modalBg: "モーダルの背景"
     divider: "分割線"
     scrollbarHandle: "スクロールバーの取っ手"
@@ -2190,13 +2272,18 @@ _profile:
   metadataContent: "内容"
   changeAvatar: "アイコン画像を変更"
   changeBanner: "バナー画像を変更"
+  updateBanner: "更新バナー"
+  removeBanner: "バナーを削除"
   changeBackground: "背景を変更する"
+  updateBackground: "背景を更新する"
+  removeBackground: "背景を削除する"
   verifiedLinkDescription: "内容にURLを設定すると、リンク先のWebサイトに自分のプロフィールへのリンクが含まれている場合に所有者確認済みアイコンを表示させることができます。"
   avatarDecorationMax: "最大{max}つまでデコレーションを付けられます。"
 
 _exportOrImport:
   allNotes: "全てのノート"
   favoritedNotes: "お気に入りにしたノート"
+  clips: "クリップ"
   followingList: "フォロー"
   muteList: "ミュート"
   blockingList: "ブロック"
@@ -2316,11 +2403,12 @@ _notification:
   youGotMention: "{name}からのメンション"
   youGotReply: "{name}からのリプライ"
   youGotQuote: "{name}による引用"
-  youRenoted: "{name}がRenoteしました"
+  youRenoted: "{name}がBoostしました"
   youWereFollowed: "フォローされました"
   youReceivedFollowRequest: "フォローリクエストが来ました"
   yourFollowRequestAccepted: "フォローリクエストが承認されました"
   pollEnded: "アンケートの結果が出ました"
+  edited: "投稿が編集されました"
   newNote: "新しい投稿"
   unreadAntennaNote: "アンテナ {name}"
   roleAssigned: "ロールが付与されました"
@@ -2331,8 +2419,9 @@ _notification:
   sendTestNotification: "テスト通知を送信する"
   notificationWillBeDisplayedLikeThis: "通知はこのように表示されます"
   reactedBySomeUsers: "{n}人がリアクションしました"
-  renotedBySomeUsers: "{n}人がリノートしました"
+  renotedBySomeUsers: "{n}人がブーストしました"
   followedBySomeUsers: "{n}人にフォローされました"
+  flushNotification: "通知の履歴をリセットする"
 
   _types:
     all: "すべて"
@@ -2340,7 +2429,7 @@ _notification:
     follow: "フォロー"
     mention: "メンション"
     reply: "リプライ"
-    renote: "Renote"
+    renote: "Boost"
     quote: "引用"
     reaction: "リアクション"
     pollEnded: "アンケートが終了"
@@ -2353,7 +2442,7 @@ _notification:
   _actions:
     followBack: "フォローバック"
     reply: "返信"
-    renote: "Renote"
+    renote: "Boost"
 
 _deck:
   alwaysShowMainColumn: "常にメインカラムを表示"
@@ -2411,7 +2500,7 @@ _webhookSettings:
     followed: "フォローされたとき"
     note: "ノートを投稿したとき"
     reply: "返信されたとき"
-    renote: "Renoteされたとき"
+    renote: "Boostされたとき"
     reaction: "リアクションがあったとき"
     mention: "メンションされたとき"
 
@@ -2428,7 +2517,7 @@ _moderationLogTypes:
   updateCustomEmoji: "カスタム絵文字更新"
   deleteCustomEmoji: "カスタム絵文字削除"
   updateServerSettings: "サーバー設定更新"
-  updateUserNote: "モデレーションノート更新"
+  updateUserNote: "ユーザーのモデレーションノート更新"
   deleteDriveFile: "ファイルを削除"
   deleteNote: "ノートを削除"
   createGlobalAnnouncement: "全体のお知らせを作成"
@@ -2440,6 +2529,7 @@ _moderationLogTypes:
   resetPassword: "パスワードをリセット"
   suspendRemoteInstance: "リモートサーバーを停止"
   unsuspendRemoteInstance: "リモートサーバーを再開"
+  updateRemoteInstanceNote: "リモートサーバーのモデレーションノート更新"
   markSensitiveDriveFile: "ファイルをセンシティブ付与"
   unmarkSensitiveDriveFile: "ファイルをセンシティブ解除"
   resolveAbuseReport: "通報を解決"
@@ -2508,15 +2598,15 @@ _animatedMFM:
   play: "MFMアニメーションを再生"
   stop: "MFMアニメーション停止"
   _alert:
-    text: "アニメーションMFMには、点滅するライトや高速で動くテキスト/絵文字を含めることができる。"
-    confirm: "アニメイト"
+    text: "MFMアニメーションには、点滅するライトや高速で動くテキスト/絵文字を含まれる場合があります。"
+    confirm: "再生する"
 
 _dataRequest:
-  title: "リクエストデータ"
-  warn: "データのリクエストは3日ごとにしかできない。"
-  text: "データのダウンロードが完了すると、このアカウントに登録されているEメールアドレスにEメールが送信されます。"
+  title: "データリクエスト"
+  warn: "データリクエストは3日ごとに可能です。"
+  text: "データの保存が完了すると、このアカウントに登録されているEメールアドレスにメールが送信されます。"
   button: "リクエスト"
-  
+
 _dataSaver:
   _media:
     title: "メディアの読み込み"
@@ -2530,3 +2620,57 @@ _dataSaver:
   _code:
     title: "コードハイライト"
     description: "MFMなどでコードハイライト記法が使われている場合、タップするまで読み込まれなくなります。コードハイライトではハイライトする言語ごとにその定義ファイルを読み込む必要がありますが、それらが自動で読み込まれなくなるため、通信量の削減が見込めます。"
+
+_hemisphere:
+  N: "北半球"
+  S: "南半球"
+  caption: "一部のクライアント設定で、季節を判定するために使用します。"
+
+_reversi:
+  reversi: "リバーシ"
+  gameSettings: "対局の設定"
+  chooseBoard: "ボードを選択"
+  blackOrWhite: "先行/後攻"
+  blackIs: "{name}が黒(先行)"
+  rules: "ルール"
+  thisGameIsStartedSoon: "対局はまもなく開始されます"
+  waitingForOther: "相手の準備が完了するのを待っています"
+  waitingForMe: "あなたの準備が完了するのを待っています"
+  waitingBoth: "準備してください"
+  ready: "準備完了"
+  cancelReady: "準備を再開"
+  opponentTurn: "相手のターンです"
+  myTurn: "あなたのターンです"
+  turnOf: "{name}のターンです"
+  pastTurnOf: "{name}のターン"
+  surrender: "投了"
+  surrendered: "投了により"
+  timeout: "時間切れ"
+  drawn: "引き分け"
+  won: "{name}の勝ち"
+  black: "黒"
+  white: "白"
+  total: "合計"
+  turnCount: "{count}ターン目"
+  myGames: "自分の対局"
+  allGames: "みんなの対局"
+  ended: "終了"
+  playing: "対局中"
+  isLlotheo: "石の少ない方が勝ち(ロセオ)"
+  loopedMap: "ループマップ"
+  canPutEverywhere: "どこでも置けるモード"
+  timeLimitForEachTurn: "1ターンの時間制限"
+  freeMatch: "フリーマッチ"
+  lookingForPlayer: "対戦相手を探しています"
+  gameCanceled: "対局がキャンセルされました"
+  shareToTlTheGameWhenStart: "開始時に対局をタイムラインに投稿"
+  iStartedAGame: "対局を開始しました! #MisskeyReversi"
+  opponentHasSettingsChanged: "相手が設定を変更しました"
+  allowIrregularRules: "変則許可 (完全フリー)"
+  disallowIrregularRules: "変則なし"
+  showBoardLabels: "盤面に行・列番号を表示"
+  useAvatarAsStone: "石をアイコンにする"
+
+_offlineScreen:
+  title: "オフライン - サーバーに接続できません"
+  header: "サーバーに接続できません"
diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml
index 1a78c1ec46..d4c7eb0918 100644
--- a/locales/ja-KS.yml
+++ b/locales/ja-KS.yml
@@ -15,7 +15,7 @@ gotIt: "ほい"
 cancel: "やめとく"
 noThankYou: "やめとく"
 enterUsername: "ユーザー名を入れてや"
-renotedBy: "{user}がリノートしたで"
+renotedBy: "{user}がブーストしたで"
 noNotes: "ノートはあらへん"
 noNotifications: "通知はあらへん"
 instance: "サーバー"
@@ -45,10 +45,10 @@ pin: "ピン留めしとく"
 unpin: "やっぱピン留めせん"
 copyContent: "内容をコピー"
 copyLink: "リンクをコピー"
-copyLinkRenote: "リノートのリンクをコピーするで?"
+copyLinkRenote: "ブーストのリンクをコピーするで?"
 delete: "ほかす"
 deleteAndEdit: "ほかして直す"
-deleteAndEditConfirm: "このノートをほかしてもっかい直す?このノートへのツッコミ、リノート、返信も全部消えるんやけどそれでもええん?"
+deleteAndEditConfirm: "このノートをほかしてもっかい直す?このノートへのツッコミ、ブースト、返信も全部消えるんやけどそれでもええん?"
 addToList: "リストに入れたる"
 addToAntenna: "アンテナに入れる"
 sendMessage: "メッセージを送る"
@@ -105,13 +105,13 @@ followRequests: "フォロー申請"
 unfollow: "フォローやめる"
 followRequestPending: "フォロー許してくれるん待っとる"
 enterEmoji: "絵文字を入れてや"
-renote: "リノート"
-unrenote: "リノートやめる"
-renoted: "リノートしたで。"
-cantRenote: "この投稿はリノートできへんっぽい。"
-cantReRenote: "リノート自体はリノートできへんで。"
+renote: "ブースト"
+unrenote: "ブーストやめる"
+renoted: "ブーストしたで。"
+cantRenote: "この投稿はブーストできへんっぽい。"
+cantReRenote: "ブースト自体はブーストできへんで。"
 quote: "引用"
-inChannelRenote: "チャンネルの中でリノート"
+inChannelRenote: "チャンネルの中でブースト"
 inChannelQuote: "チャンネル内引用"
 pinnedNote: "ピン留めされとるノート"
 pinned: "ピン留めしとく"
@@ -130,13 +130,14 @@ overwriteFromPinnedEmojis: "全般設定から上書きする"
 reactionSettingDescription2: "ドラッグで並び替え、クリックで削除、+を押して追加やで。"
 rememberNoteVisibility: "公開範囲覚えといて"
 attachCancel: "のっけるのやめる"
+deleteFile: "ファイルをほかす"
 markAsSensitive: "ちょっとこれはアカン"
 unmarkAsSensitive: "そこまでアカンことないやろ"
 enterFileName: "ファイル名を入れてや"
 mute: "ミュート"
 unmute: "ミュートやめたる"
-renoteMute: "リノートは見いひん"
-renoteUnmute: "リノートもやっぱ見るわ"
+renoteMute: "ブーストは見いひん"
+renoteUnmute: "ブーストもやっぱ見るわ"
 block: "ブロック"
 unblock: "ブロックやめたる"
 suspend: "凍結"
@@ -381,6 +382,11 @@ hcaptcha: "hCaptcha(キャプチャ)"
 enableHcaptcha: "hCaptcha(キャプチャ)をつけとく"
 hcaptchaSiteKey: "サイトキー"
 hcaptchaSecretKey: "シークレットキー"
+mcaptcha: "mCaptcha"
+enableMcaptcha: "hCaptcha(キャプチャ)をつけとく"
+mcaptchaSiteKey: "サイトキー"
+mcaptchaSecretKey: "シークレットキー"
+mcaptchaInstanceUrl: "mCaptchaのインスタンスのURL"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "reCAPTCHA(リキャプチャ)を有効にする"
 recaptchaSiteKey: "サイトキー"
@@ -628,6 +634,7 @@ medium: "中"
 small: "小"
 generateAccessToken: "アクセストークンの発行"
 permission: "権限"
+adminPermission: "管理者権限"
 enableAll: "全部使えるようにする"
 disableAll: "全部使えへんようにする"
 tokenRequested: "アカウントへのアクセス許してやったらどうや"
@@ -671,13 +678,14 @@ useGlobalSettingDesc: "オンにすると、アカウントの通知設定が使
 other: "その他"
 regenerateLoginToken: "ログイントークンを再生成"
 regenerateLoginTokenDescription: "ログインに使われる内部トークンをもっかい作るで。いつもならこれをやる必要はないで。もっかい作ると、全部のデバイスでログアウトされるで気ぃつけてなー。"
+theKeywordWhenSearchingForCustomEmoji: "カスタム絵文字を探すときのキーワードになるで。"
 setMultipleBySeparatingWithSpace: "スペースで区切って何個でも設定できるで。"
 fileIdOrUrl: "ファイルIDかURL"
 behavior: "動作"
 sample: "サンプル"
 abuseReports: "通報"
 reportAbuse: "通報"
-reportAbuseRenote: "リノート苦情だすで?"
+reportAbuseRenote: "ブースト苦情だすで?"
 reportAbuseOf: "{name}を通報する"
 fillAbuseReportDescription: "細かい通報理由を書いてなー。対象ノートがある時はそのURLも書いといてなー。"
 abuseReported: "無事内容が送信されたみたいやで。おおきに〜。"
@@ -711,9 +719,9 @@ manageAccessTokens: "アクセストークンの管理"
 accountInfo: "アカウント情報"
 notesCount: "ノートの数やで"
 repliesCount: "返信した数やで"
-renotesCount: "リノートした数やで"
+renotesCount: "ブーストした数やで"
 repliedCount: "返信された数やで"
-renotedCount: "リノートされた数やで"
+renotedCount: "ブーストされた数やで"
 followingCount: "フォロー数やで"
 followersCount: "フォロワー数やで"
 sentReactionsCount: "ツッコんだ数"
@@ -883,6 +891,8 @@ makeReactionsPublicDescription: "あんたがしたツッコミ一覧を誰で
 classic: "クラシック"
 muteThread: "スレッドをミュート"
 unmuteThread: "スレッドのミュートを解除"
+followingVisibility: "フォローの公開範囲"
+followersVisibility: "フォロワーの公開範囲"
 continueThread: "さらにスレッドを見るで"
 deleteAccountConfirm: "アカウントを消すで?ええんか?"
 incorrectPassword: "パスワードがちゃうわ。"
@@ -983,6 +993,7 @@ neverShow: "今後表示しない"
 remindMeLater: "また後で"
 didYouLikeMisskey: "Sharkey気に入ってくれた?"
 pleaseDonate: "Sharkeyは{host}が使うとる無料のソフトウェアやで。これからも開発を続けれるように、寄付したってな~。"
+correspondingSourceIsAvailable: "{anchor}"
 roles: "ロール"
 role: "ロール"
 noRole: "ロールはありまへん"
@@ -1009,7 +1020,7 @@ thisPostMayBeAnnoying: "この投稿は迷惑かもしらんで。"
 thisPostMayBeAnnoyingHome: "ホームに投稿"
 thisPostMayBeAnnoyingCancel: "やめとく"
 thisPostMayBeAnnoyingIgnore: "このまま投稿"
-collapseRenotes: "見たことあるリノートは飛ばして表示するで"
+collapseRenotes: "見たことあるブーストは飛ばして表示するで"
 internalServerError: "サーバー内部エラー"
 internalServerErrorDescription: "サーバーでなんか変なこと起こっとるわ。"
 copyErrorInfo: "エラー情報をコピるで"
@@ -1033,6 +1044,7 @@ resetPasswordConfirm: "パスワード作り直すんでええな?"
 sensitiveWords: "けったいな単語"
 sensitiveWordsDescription: "設定した単語が入っとるノートの公開範囲をホームにしたるわ。改行で区切ったら複数設定できるで。"
 sensitiveWordsDescription2: "スペースで区切るとAND指定、キーワードをスラッシュで囲んだら正規表現や。"
+prohibitedWordsDescription2: "スペースで区切るとAND指定、キーワードをスラッシュで囲んだら正規表現や。"
 hiddenTags: "見えてへんハッシュタグ"
 hiddenTagsDescription: "設定したタグを最近流行りのとこに見えんようにすんで。複数設定するときは改行で区切ってな。"
 notesSearchNotAvailable: "なんかノート探せへん。"
@@ -1051,6 +1063,8 @@ limitWidthOfReaction: "ツッコミの最大横幅を制限して、ちっさく
 noteIdOrUrl: "ノートIDかURL"
 video: "動画"
 videos: "動画"
+audio: "音声"
+audioFiles: "音声"
 dataSaver: "データケチケチ"
 accountMigration: "アカウントのお引っ越し"
 accountMoved: "このユーザーはさらのアカウントに引っ越したで:"
@@ -1060,7 +1074,7 @@ forceShowAds: "いっつも広告を映す"
 addMemo: "メモを足す"
 editMemo: "メモをいらう"
 reactionsList: "ツッコミ一覧"
-renotesList: "リノート一覧"
+renotesList: "ブースト一覧"
 notificationDisplay: "通知見せる"
 leftTop: "左上"
 rightTop: "右上"
@@ -1131,7 +1145,7 @@ pastAnnouncements: "過去のお知らせやで"
 youHaveUnreadAnnouncements: "あんたまだこのお知らせ読んどらんやろ。"
 useSecurityKey: "ブラウザまたはデバイスの言う通りに、セキュリティキーまたはパスキーを使ってや。"
 replies: "返事"
-renotes: "リノート"
+renotes: "ブースト"
 loadReplies: "返信を見るで"
 loadConversation: "会話を見るで"
 pinnedList: "ピン留めしはったリスト"
@@ -1142,7 +1156,7 @@ unnotifyNotes: "投稿の通知やめる"
 authentication: "認証"
 authenticationRequiredToContinue: "続けるんなら認証してや。"
 dateAndTime: "日時"
-showRenotes: "リノート出す"
+showRenotes: "ブースト出す"
 edited: "いじったやつ"
 notificationRecieveConfig: "通知もらうかの設定"
 mutualFollow: "お互いフォローしてんで"
@@ -1154,6 +1168,7 @@ hideRepliesToOthersInTimelineAll: "タイムラインに今フォローしとる
 confirmShowRepliesAll: "これは元に戻せへんから慎重に決めてや。本当にタイムラインに今フォローしとる全員の返信を入れるか?"
 confirmHideRepliesAll: "これは元に戻せへんから慎重に決めてや。本当にタイムラインに今フォローしとる全員の返信を入れへんのか?"
 externalServices: "他のサイトのサービス"
+sourceCode: "ソースコード"
 impressum: "運営者の情報"
 impressumUrl: "運営者の情報URL"
 impressumDescription: "ドイツとかの一部んところではな、表示が義務付けられてんねん(Impressum)。"
@@ -1181,6 +1196,28 @@ remainingN: "残り:{n}"
 overwriteContentConfirm: "今の内容に上書きされるけどいい?"
 seasonalScreenEffect: "季節にあった画面の動き"
 decorate: "デコる"
+addMfmFunction: "装飾つける"
+enableQuickAddMfmFunction: "ややこしいMFMのピッカーを出す"
+bubbleGame: "バブルゲーム"
+sfx: "効果音"
+soundWillBePlayed: "サウンドが再生されるで"
+showReplay: "リプレイ見る"
+replay: "リプレイ"
+replaying: "リプレイ中"
+ranking: "ランキング"
+lastNDays: "直近{n}日"
+backToTitle: "タイトルへ"
+hemisphere: "住んでる地域"
+withSensitive: "センシティブなファイルを含むノートを表示"
+userSaysSomethingSensitive: "{name}のセンシティブなファイルを含む投稿"
+enableHorizontalSwipe: "スワイプしてタブを切り替える"
+surrender: "やめとく"
+_bubbleGame:
+  howToPlay: "遊び方"
+  _howToPlay:
+    section1: "位置を調整してハコにモノを落とすで。"
+    section2: "同じもんがくっついたら別のやつになって、スコアがもらえるで。"
+    section3: "モノがハコからあふれたらゲームオーバーや。ハコからあふれんようにしながらモノを融合させてハイスコアを目指しいや!"
 _announcement:
   forExistingUsers: "もうおるユーザーのみ"
   forExistingUsersDescription: "オンにしたらこのお知らせができた時点でおる人らにだけお知らせが行くで。切ったらこの知らせが行ったあとにアカウント作った人にもちゃんとお知らせが行くで。"
@@ -1247,8 +1284,8 @@ _initialTutorial:
     _visibility:
       description: "ノートを見れる相手を制限できるわ。"
       public: "みんなに見せるで。"
-      home: "ホームタイムラインにだけ見せるで。フォロワーとか、プロフィールを見に来た人、リノートからも見れるから、実質は全員見れるけどな。あんまし広がりにくいってことや。"
-      followers: "フォロワーにだけ見せるで。自分以外はリノートできへんし、フォロワー以外は絶対に見れへん。"
+      home: "ホームタイムラインにだけ見せるで。フォロワーとか、プロフィールを見に来た人、ブーストからも見れるから、実質は全員見れるけどな。あんまし広がりにくいってことや。"
+      followers: "フォロワーにだけ見せるで。自分以外はブーストできへんし、フォロワー以外は絶対に見れへん。"
       direct: "指定した人にだけ公開されて、ついでに通知も送るで。ダイレクトメールの代わりとして使ってな。"
       doNotSendConfidencialOnDirect1: "機密情報を送るときは十分注意せえよ。"
       doNotSendConfidencialOnDirect2: "送信先のサーバーの管理者は投稿内容が見れるから、信用できへんサーバーのひとにダイレクト投稿するときには、めっちゃ用心しとくんやで。"
@@ -1551,6 +1588,13 @@ _achievements:
     _tutorialCompleted:
       title: "Sharkeyひよっこ講座 修了証"
       description: "チュートリアル全部やった"
+    _bubbleGameExplodingHead:
+      title: "🤯"
+      description: "バブルゲームで最も大きいモノを出した"
+    _bubbleGameDoubleExplodingHead:
+      title: "ダブル🤯"
+      description: "バブルゲームで最も大きいモノを2つ同時に出した"
+      flavor: "これくらいの おべんとばこに 🤯 🤯 ちょっとつめて"
 _role:
   new: "ロールの作成"
   edit: "ロールの編集"
@@ -1641,6 +1685,7 @@ _emailUnavailable:
   disposable: "ずーっと使えるアドレスじゃないみたいや"
   mx: "正しいメールサーバーじゃないっぽいわ"
   smtp: "メールサーバーがうんともすんとも言わへん"
+  banned: "このメールアドレスはあかん"
 _ffVisibility:
   public: "公開"
   followers: "フォロワーだけに公開"
@@ -1709,7 +1754,7 @@ _registry:
   domain: "ドメイン"
   createKey: "キーを作る"
 _aboutMisskey:
-  about: "Sharkeyは、syuiloが2014年からずっと作ってはる、Misskeyをベースにしたオープンソースなソフトウェアや。"
+  about: "Sharkeyは、Misskeyをベースにしたオープンソースなソフトウェアや。"
   contributors: "主な貢献者"
   allContributors: "全ての貢献者"
   source: "ソースコード"
@@ -1742,7 +1787,7 @@ _channel:
   notesCount: "{n}こ投稿があるで"
   nameAndDescription: "名前と説明"
   nameOnly: "名前だけ"
-  allowRenoteToExternal: "チャンネルの外にリノートできるようにする"
+  allowRenoteToExternal: "チャンネルの外にブーストできるようにする"
 _menuDisplay:
   sideFull: "横"
   sideIcon: "横(アイコン)"
@@ -1932,6 +1977,55 @@ _permissions:
   "write:flash": "Playを操作する"
   "read:flash-likes": "Playのええやん!を見る"
   "write:flash-likes": "Playのええやん!を見る"
+  "read:admin:abuse-user-reports": "ユーザーからの通報を見る"
+  "write:admin:delete-account": "ユーザーアカウント消す"
+  "write:admin:delete-all-files-of-a-user": "ユーザーのファイル全部ほかす"
+  "read:admin:index-stats": "データベースインデックスの情報見る"
+  "read:admin:table-stats": "データベーステーブルの情報見る"
+  "read:admin:user-ips": "ユーザーのIPアドレスを見る"
+  "read:admin:meta": "インスタンスのメタデータ見る"
+  "write:admin:reset-password": "ユーザーのパスワードをリセット"
+  "write:admin:resolve-abuse-user-report": "ユーザーからの通報を解決する"
+  "write:admin:send-email": "メール送る"
+  "read:admin:server-info": "サーバーの情報見る"
+  "read:admin:show-moderation-log": "モデレーションログ見る"
+  "read:admin:show-user": "ユーザーのプライベートな情報見る"
+  "read:admin:show-users": "ユーザーのプライベートな情報見る"
+  "write:admin:suspend-user": "ユーザーを凍結"
+  "write:admin:unset-user-avatar": "ユーザーのアバターを削除"
+  "write:admin:unset-user-banner": "ユーザーのバナーを削除"
+  "write:admin:unsuspend-user": "ユーザーの凍結解除"
+  "write:admin:meta": "インスタンスのメタデータいじる"
+  "write:admin:user-note": "モデレーションノートいじる"
+  "write:admin:roles": "ロールをいじる"
+  "read:admin:roles": "ロール見る"
+  "write:admin:relays": "リレーいじる"
+  "read:admin:relays": "リレー見る"
+  "write:admin:invite-codes": "招待コードいじる"
+  "read:admin:invite-codes": "招待コード見る"
+  "write:admin:announcements": "お知らせいじる"
+  "read:admin:announcements": "お知らせ見る"
+  "write:admin:avatar-decorations": "アバターデコレーションをいじる"
+  "read:admin:avatar-decorations": "アバターデコレーション見る"
+  "write:admin:federation": "連合の情報いじる"
+  "write:admin:account": "ユーザーアカウントいじる"
+  "read:admin:account": "ユーザーの情報見る"
+  "write:admin:emoji": "絵文字いじる"
+  "read:admin:emoji": "絵文字見る"
+  "write:admin:queue": "ジョブキューいじる"
+  "read:admin:queue": "ジョブキューの情報見る"
+  "write:admin:promo": "プロモーションノートいじる"
+  "write:admin:drive": "ユーザーのドライブいじる"
+  "read:admin:drive": "ユーザーのドライブの情報見る"
+  "read:admin:stream": "管理者用のWebsocket API使う"
+  "write:admin:ad": "広告いじる"
+  "read:admin:ad": "広告見る"
+  "write:invite-codes": "招待コード作る"
+  "read:invite-codes": "招待コード取得"
+  "write:clip-favorite": "クリップのいいねいじる"
+  "read:clip-favorite": "クリップのいいね見る"
+  "read:federation": "連合の情報取得"
+  "write:report-abuse": "違反報告"
 _auth:
   shareAccessTitle: "アプリへのアクセス許してやったらどうや"
   shareAccess: "「{name}」がアカウントにアクセスすることを許可してええか?"
@@ -1945,9 +2039,9 @@ _auth:
 _antennaSources:
   all: "みんなのノート"
   homeTimeline: "フォローしとるユーザーのノート"
-  users: "選らんだ一人か複数のユーザーのノート"
+  users: "選んだ一人か複数のユーザーのノート"
   userList: "選んだリストのユーザーのノート"
-  userBlacklist: "選んだ1人か複数のユーザーのノート"
+  userBlacklist: "選んだ一人か複数のユーザーを除いた全てのノート"
 _weekday:
   sunday: "日曜日"
   monday: "月曜日"
@@ -2053,6 +2147,7 @@ _profile:
 _exportOrImport:
   allNotes: "全てのノート"
   favoritedNotes: "お気に入りにしたノート"
+  clips: "クリップ"
   followingList: "フォロー"
   muteList: "ミュート"
   blockingList: "ブロック"
@@ -2164,13 +2259,14 @@ _notification:
   youGotMention: "{name}からのメンション"
   youGotReply: "{name}からのリプライ"
   youGotQuote: "{name}による引用"
-  youRenoted: "{name}がリノートしたみたいやで"
+  youRenoted: "{name}がブーストしたみたいやで"
   youWereFollowed: "フォローされたで"
   youReceivedFollowRequest: "フォロー許可してほしいみたいやな"
   yourFollowRequestAccepted: "フォローさせてもろたで"
   pollEnded: "アンケートの結果が出たみたいや"
   newNote: "さらの投稿"
   unreadAntennaNote: "アンテナ {name}"
+  roleAssigned: "ロールが付与されたで"
   emptyPushNotificationMessage: "プッシュ通知の更新をしといたで"
   achievementEarned: "実績を獲得しとるで"
   testNotification: "通知テスト"
@@ -2178,7 +2274,7 @@ _notification:
   sendTestNotification: "テスト通知を送信するで"
   notificationWillBeDisplayedLikeThis: "通知はこのように表示されるで"
   reactedBySomeUsers: "{n}人がツッコんだで"
-  renotedBySomeUsers: "{n}人がリノートしたで"
+  renotedBySomeUsers: "{n}人がブーストしたで"
   followedBySomeUsers: "{n}人にフォローされたで"
   _types:
     all: "すべて"
@@ -2192,6 +2288,7 @@ _notification:
     pollEnded: "アンケートが終了したで"
     receiveFollowRequest: "フォロー許可してほしいみたいやで"
     followRequestAccepted: "フォローが受理されたで"
+    roleAssigned: "ロールが付与された"
     achievementEarned: "実績の獲得"
     app: "連携アプリからの通知や"
   _actions:
@@ -2249,7 +2346,7 @@ _webhookSettings:
     followed: "フォローもらったとき~!"
     note: "ノートを投稿したとき~!"
     reply: "返信があるとき~!"
-    renote: "リノートされるとき~!"
+    renote: "ブーストされるとき~!"
     reaction: "ツッコまれたとき~!"
     mention: "メンションがあるとき~!"
 _moderationLogTypes:
@@ -2350,3 +2447,52 @@ _dataSaver:
   _code:
     title: "コードハイライト"
     description: "MFMとかでコードハイライト記法が使われてるとき、タップするまで読み込まれへんくなるで。コードハイライトではハイライトする言語ごとにその決めてるファイルを読む必要はあんねんな。けどな、それは自動で読み込まれなくなるから、通信量を少なくできることができるねん。"
+_hemisphere:
+  N: "北半球"
+  S: "南半球"
+  caption: "一部のクライアント設定で、季節を判定するのに使用するで。"
+_reversi:
+  reversi: "リバーシ"
+  gameSettings: "対局の設定"
+  chooseBoard: "ボードを選択"
+  blackOrWhite: "先行/後攻"
+  blackIs: "{name}が黒(先行)"
+  rules: "ルール"
+  thisGameIsStartedSoon: "対局、そろそろ開始されるで。"
+  waitingForOther: "相手の準備が完了するのを待ってんで。"
+  waitingForMe: "あんさんの準備が完了すんのを待ってんで"
+  waitingBoth: "準備してなー"
+  ready: "準備完了"
+  cancelReady: "準備を再開"
+  opponentTurn: "相手のターンやで"
+  myTurn: "あんさんのターンや"
+  turnOf: "{name}のターンやで"
+  pastTurnOf: "{name}のターン"
+  surrender: "投了"
+  surrendered: "投了により"
+  timeout: "時間切れ"
+  drawn: "引き分け"
+  won: "{name}の勝ち"
+  black: "黒"
+  white: "白"
+  total: "合計"
+  turnCount: "{count}ターン目"
+  myGames: "自分の対局"
+  allGames: "みんなの対局"
+  ended: "終了"
+  playing: "対局中"
+  isLlotheo: "石の少ない方が勝ち(ロセオ)"
+  loopedMap: "ループマップ"
+  canPutEverywhere: "どこでも置けるモード"
+  timeLimitForEachTurn: "1ターンの時間制限"
+  freeMatch: "フリーマッチ"
+  lookingForPlayer: "対戦相手を探してるで"
+  gameCanceled: "対局がキャンセルされたわ"
+  shareToTlTheGameWhenStart: "初めの時に対局をタイムラインに投稿するで"
+  iStartedAGame: "対局し始めたで! #MisskeyReversi"
+  opponentHasSettingsChanged: "相手が設定変えたで"
+  allowIrregularRules: "変則許可 (完全フリー)"
+  disallowIrregularRules: "変則なし"
+_offlineScreen:
+  title: "オフライン - サーバーに接続できひんで"
+  header: "サーバーに接続できへんわ"
diff --git a/locales/jbo-EN.yml b/locales/jbo-EN.yml
index d4fea291d7..297ca53dd7 100644
--- a/locales/jbo-EN.yml
+++ b/locales/jbo-EN.yml
@@ -1,3 +1,4 @@
 ---
 _lang_: "la .lojban."
 headlineMisskey: "lo se tcana noi jorne fi loi notci"
+
diff --git a/locales/kab-KAB.yml b/locales/kab-KAB.yml
index 22e24d3baa..b976f028f0 100644
--- a/locales/kab-KAB.yml
+++ b/locales/kab-KAB.yml
@@ -104,3 +104,4 @@ _deck:
   _columns:
     notifications: "Ilɣuyen"
     list: "Tibdarin"
+
diff --git a/locales/kn-IN.yml b/locales/kn-IN.yml
index b3ad46f2b1..bb6d1ee242 100644
--- a/locales/kn-IN.yml
+++ b/locales/kn-IN.yml
@@ -84,3 +84,4 @@ _deck:
     notifications: "ಅಧಿಸೂಚನೆಗಳು"
     tl: "ಸಮಯಸಾಲು"
     mentions: "ಹೆಸರಿಸಿದ"
+
diff --git a/locales/ko-GS.yml b/locales/ko-GS.yml
index 566667ba79..39492d902f 100644
--- a/locales/ko-GS.yml
+++ b/locales/ko-GS.yml
@@ -40,7 +40,7 @@ favorites: "질겨찾기"
 unfavorite: "질겨찾기서 어ᇝ애기"
 favorited: "질겨찾기에 담앗십니다."
 alreadyFavorited: "벌시로 질겨찾기에 담기 잇십니다."
-cantFavorite: "질겨찾기에 몬 담았십니다."
+cantFavorite: "질겨찾기에 몬 담앗십니다."
 pin: "프로필에 붙이기"
 unpin: "프로필서 띠기"
 copyContent: "내용 복사하기"
@@ -124,6 +124,7 @@ reactions: "반엉"
 reactionSettingDescription2: "꺼시서 두고, 누질라서 뭉캐고,  ‘+’럴 누질라서 옇십니다."
 rememberNoteVisibility: "공개 범위럴 기억하기"
 attachCancel: "붙임 빼기"
+deleteFile: "파일 뭉캐기"
 markAsSensitive: "수ᇚ힘 설정"
 unmarkAsSensitive: "수ᇚ힘 무루기"
 enterFileName: "파일 이럼 서기"
@@ -373,6 +374,8 @@ hcaptcha: "에이치캡차"
 enableHcaptcha: "에이치캡차 키기"
 hcaptchaSiteKey: "사이트키"
 hcaptchaSecretKey: "시크릿키"
+mcaptchaSiteKey: "사이트키"
+mcaptchaSecretKey: "시크릿키"
 recaptcha: "리캡차"
 enableRecaptcha: "리캡차 키기"
 recaptchaSiteKey: "사이트키"
@@ -461,6 +464,8 @@ onlyOneFileCanBeAttached: "메시지엔 파일 하나까제밖에 몬 넣십니
 invitations: "초대하기"
 invitationCode: "초대장"
 checking: "학인하고 잇십니다"
+tooShort: "억수로 짜립니다"
+tooLong: "억수로 집니다"
 passwordMatched: "맞십니다"
 passwordNotMatched: "안 맞십니다"
 signinFailed: "로그인 몬 했십니다. 고 이름이랑 비밀번호 제대로 썼는가 확인해 주이소."
@@ -513,7 +518,7 @@ objectStoragePrefixDesc: "요 Prefix 디렉토리 안에다가 파일이 들어
 objectStorageEndpoint: "Endpoint"
 objectStorageEndpointDesc: "AWS S3을 쓸라멘 요는 비워두고, 아이멘은 그 서비스 가이드에 맞게 endpoint를 넣어 주이소. '<host>' 내지 '<host>:<port>'처럼 넣십니다."
 objectStorageRegion: "Region"
-objectStorageRegionDesc: "'xx-east-1' 같은 region 이름을 옇어 주이소. 써먹을 서비스에 region 개념 같은 게 읎다! 카면은 대신에 'us-east-1'을 옇어 놓으이소. AWS 설정 파일이나 환경 변수를 갖다 끌어다 쓸 거면은 요는 비워 두이소."
+objectStorageRegionDesc: "'xx-east-1' 같은 region 이름을 옇어 주이소. 만약에 내 서비스엔 region 같은 개념이 읎다, 카면은 대신에 'us-east-1'라고 해 두이소. AWS 설정 파일이나 환경 변수를 끌어다 쓰겠다믄 요는 비워 두이소."
 objectStorageUseSSL: "SSL 쓰기"
 objectStorageUseSSLDesc: "API 호출할 때 HTTPS 안 쓸거면은 꺼 두이소"
 objectStorageUseProxy: "연결에 프락시 사용"
@@ -536,7 +541,7 @@ volume: "음량"
 masterVolume: "대빵 음량"
 notUseSound: "음소거하기"
 useSoundOnlyWhenActive: "Misskey가 활성화되어 있을 때만 소리 내기"
-details: "좀 더"
+details: "자세히"
 chooseEmoji: "이모지 선택"
 unableToProcess: "작업 다 몬 했십니다"
 recentUsed: "최근 쓴 놈"
@@ -569,7 +574,11 @@ userSilenced: "요 게정은... 수ᇚ혀 있십니다."
 relays: "릴레이"
 addRelay: "릴레이 옇기"
 addedRelays: "옇은 릴레이"
+deletedNote: "뭉캔 걸"
 enableInfiniteScroll: "알아서 더 보기"
+useCw: "내용 수ᇚ후기"
+description: "설멩"
+describeFile: "캡션 옇기"
 author: "맨던 사람"
 manage: "간리"
 emailServer: "전자우펜 서버"
@@ -598,6 +607,7 @@ renotesCount: "리노트한 수"
 renotedCount: "리노트덴 수"
 followingCount: "팔로우 수"
 followersCount: "팔로워 수"
+noteFavoritesCount: "질겨찾기한 노트 수"
 clips: "클립 맨걸기"
 clearCache: "캐시 비우기"
 unlikeConfirm: "좋네예럴 무룹니꺼?"
@@ -606,6 +616,7 @@ user: "사용자"
 administration: "간리"
 on: "킴"
 off: "껌"
+hide: "수ᇚ후기"
 clickToFinishEmailVerification: "[{ok}]럴 누질라서 전자우펜 정멩얼 껕내이소."
 searchByGoogle: "찾기"
 tenMinutes: "십 분"
@@ -624,9 +635,12 @@ role: "옉할"
 noRole: "옉할이 없십니다"
 thisPostMayBeAnnoyingCancel: "아이예"
 likeOnly: "좋네예마"
+myClips: "내 클립"
 icon: "아바타"
 replies: "답하기"
 renotes: "리노트"
+attach: "옇기"
+surrender: "아이예"
 _initialAccountSetting:
   startTutorial: "길라잡이 하기"
 _initialTutorial:
@@ -639,9 +653,52 @@ _initialTutorial:
     title: "길라잡이가 껕낫십니다!🎉"
 _achievements:
   _types:
+    _notes1:
+      description: "첫 노트럴 섯어예"
+    _notes10:
+      description: "노트럴 10번 섰어예"
+    _notes100:
+      description: "노트럴 100번 섰어예"
+    _notes500:
+      description: "노트럴 500번 섰어예"
+    _notes1000:
+      description: "노트럴 1,000번 섰어예"
+    _notes5000:
+      description: "노트럴 5,000번 섰어예"
+    _notes10000:
+      description: "노트럴 10,000번 섰어예"
+    _notes20000:
+      description: "노트럴 20,000번 섰어예"
+    _notes30000:
+      description: "노트럴 30,000번 섰어예"
+    _notes40000:
+      description: "노트럴 40,000번 섰어예"
+    _notes50000:
+      description: "노트럴 50,000번 섰어예"
+    _notes60000:
+      description: "노트럴 60,000번 섰어예"
+    _notes70000:
+      description: "노트럴 70,000번 섰어예"
+    _notes80000:
+      description: "노트럴 80,000번 섰어예"
+    _notes90000:
+      description: "노트럴 90,000번 섰어예"
+    _notes100000:
+      description: "노트럴 100,000번 섰어예"
+    _noteClipped1:
+      description: "첫 노트럴 클립햇어예"
+    _noteFavorited1:
+      description: "첫 노트럴 질겨찾기에 담앗어예"
+    _myNoteFavorited1:
+      description: "다런 사람이 내 노트럴 질겨찾기에 담앗십니다"
+    _iLoveMisskey:
+      description: "“I ❤ #Misskey”럴 섰어예"
+    _postedAt0min0sec:
+      description: "0분 0초에 노트를 섰어예"
     _tutorialCompleted:
       description: "길라잡이럴 껕냇십니다"
 _gallery:
+  my: "내 걸"
   liked: "좋네예한 걸"
   like: "좋네예!"
   unlike: "좋네예 무루기"
@@ -652,7 +709,12 @@ _serverDisconnectedBehavior:
   reload: "알아서 새로곤침"
 _channel:
   removeBanner: "배너 뭉캐기"
+  usersCount: "{n}명 참여"
+  notesCount: "노트 {n}개"
+_menuDisplay:
+  hide: "수ᇚ후기"
 _theme:
+  description: "설멩"
   keys:
     mention: "멘션"
 _sfx:
@@ -661,6 +723,9 @@ _sfx:
 _2fa:
   step3Title: "학인 기호럴 서기"
   renewTOTPCancel: "뎃어예"
+_permissions:
+  "read:favorites": "질겨찾기 보기"
+  "write:favorites": "질겨찾기 곤치기"
 _widgets:
   profile: "프로필"
   instanceInfo: "서버 정보"
@@ -672,7 +737,10 @@ _widgets:
   _userList:
     chooseList: "리스트 개리기"
 _cw:
+  hide: "수ᇚ후기"
   show: "더 볼래예"
+  chars: "걸자 {count}개"
+  files: "파일 {count}개"
 _visibility:
   home: "덜머리"
   followers: "팔로워"
@@ -680,6 +748,8 @@ _profile:
   name: "이럼"
   username: "사용자 이럼"
 _exportOrImport:
+  favoritedNotes: "질겨찾기한 노트"
+  clips: "클립 맨걸기"
   followingList: "팔로잉"
   muteList: "수ᇚ후기"
   blockingList: "차단하기"
@@ -689,16 +759,20 @@ _charts:
 _timelines:
   home: "덜머리"
 _play:
+  my: "내 플레이"
   script: "스크립트"
+  summary: "설멩"
 _pages:
   like: "좋네예"
   unlike: "좋네예 무루기"
+  my: "내 페이지"
   blocks:
     image: "이미지"
     _note:
       id: "노트 아이디"
 _notification:
   youWereFollowed: "새 팔로워가 잇십니다"
+  newNote: "새 걸"
   _types:
     follow: "팔로잉"
     mention: "멘션"
@@ -721,3 +795,6 @@ _moderationLogTypes:
   deleteUserAnnouncement: "사용자 공지 걸 뭉캐기"
   resetPassword: "비밀번호 재설정"
   resolveAbuseReport: "신고 해겔하기"
+_reversi:
+  total: "합계"
+
diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml
index 4a13012eed..877ae6b217 100644
--- a/locales/ko-KR.yml
+++ b/locales/ko-KR.yml
@@ -130,6 +130,7 @@ overwriteFromPinnedEmojis: "일반 설정을 덮어쓰기"
 reactionSettingDescription2: "끌어서 순서 변경, 클릭해서 삭제, +를 눌러서 추가할 수 있습니다."
 rememberNoteVisibility: "공개 범위를 기억하기"
 attachCancel: "첨부 취소"
+deleteFile: "파일 삭제"
 markAsSensitive: "열람주의로 설정"
 unmarkAsSensitive: "열람주의 해제"
 enterFileName: "파일명을 입력"
@@ -278,7 +279,7 @@ uploadFromUrl: "URL 업로드"
 uploadFromUrlDescription: "업로드하려는 파일의 URL"
 uploadFromUrlRequested: "업로드를 요청했습니다"
 uploadFromUrlMayTakeTime: "업로드가 완료될 때까지 시간이 소요될 수 있습니다."
-explore: "발견하기"
+explore: "둘러보기"
 messageRead: "읽음"
 noMoreHistory: "이것보다 과거의 기록이 없습니다"
 startMessaging: "대화 시작하기"
@@ -379,6 +380,11 @@ hcaptcha: "hCaptcha"
 enableHcaptcha: "hCaptcha 활성화"
 hcaptchaSiteKey: "사이트 키"
 hcaptchaSecretKey: "시크릿 키"
+mcaptcha: "mCaptcha"
+enableMcaptcha: "mCaptcha 활성화"
+mcaptchaSiteKey: "사이트 키"
+mcaptchaSecretKey: "시크릿 키"
+mcaptchaInstanceUrl: "mCaptcha 인스턴스 URL"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "reCAPTCHA 활성화"
 recaptchaSiteKey: "사이트 키"
@@ -626,6 +632,7 @@ medium: "보통"
 small: "작게"
 generateAccessToken: "액세스 토큰 생성"
 permission: "권한"
+adminPermission: "관리자 권한"
 enableAll: "전체 선택"
 disableAll: "전체 해제"
 tokenRequested: "계정 접근 허용"
@@ -669,6 +676,7 @@ useGlobalSettingDesc: "활성화하면 계정의 알림 설정이 적용됩니
 other: "기타"
 regenerateLoginToken: "로그인 토큰을 재생성"
 regenerateLoginTokenDescription: "로그인할 때 사용되는 내부 토큰을 재생성합니다. 일반적으로 이 작업을 실행할 필요는 없습니다. 이 기능을 사용하면 이 계정으로 로그인한 모든 기기에서 로그아웃됩니다."
+theKeywordWhenSearchingForCustomEmoji: "맞춤 이모티콘을 검색할 때 키워드가 됩니다."
 setMultipleBySeparatingWithSpace: "공백으로 구분하여 여러 개 설정할 수 있습니다."
 fileIdOrUrl: "파일 ID 또는 URL"
 behavior: "동작"
@@ -983,6 +991,7 @@ neverShow: "다시 보지 않기"
 remindMeLater: "나중에 알림"
 didYouLikeMisskey: "Misskey가 마음에 드시나요?"
 pleaseDonate: "Misskey는 {host} 서버의 무료 소프트웨어입니다. 앞으로도 개발을 이어 나가려면 후원이 절실히 필요합니다!"
+correspondingSourceIsAvailable: "소스 코드는 {anchor}에서 받아보실 수 있습니다."
 roles: "역할"
 role: "역할"
 noRole: "역할이 없습니다"
@@ -1014,7 +1023,7 @@ internalServerError: "내부 서버 오류"
 internalServerErrorDescription: "내부 서버에서 예기치 않은 오류가 발생했습니다."
 copyErrorInfo: "오류 정보 복사"
 joinThisServer: "이 서버에 가입"
-exploreOtherServers: "다른 서버 둘러보기"
+exploreOtherServers: "다른 서버 찾기"
 letsLookAtTimeline: "타임라인 구경하기"
 disableFederationConfirm: "정말로 연합을 끄시겠습니까?"
 disableFederationConfirmWarn: "연합을 끄더라도 게시물이 비공개로 전환되는 것은 아닙니다. 대부분의 경우 연합을 비활성화할 필요가 없습니다."
@@ -1033,6 +1042,9 @@ resetPasswordConfirm: "비밀번호를 재설정하시겠습니까?"
 sensitiveWords: "민감한 단어"
 sensitiveWordsDescription: "설정한 단어가 포함된 노트의 공개 범위를 '홈'으로 강제합니다. 개행으로 구분하여 여러 개를 지정할 수 있습니다."
 sensitiveWordsDescription2: "공백으로 구분하면 AND 지정이 되며, 키워드를 슬래시로 둘러싸면 정규 표현식이 됩니다."
+prohibitedWords: "금지 워드"
+prohibitedWordsDescription: "설정된 워드가 포함되는 노트를 작성하려고 하면, 에러가 발생하도록 합니다. 줄바꿈으로 구분지어 복수 설정할 수 있습니다."
+prohibitedWordsDescription2: "공백으로 구분하면 AND 지정이 되며, 키워드를 슬래시로 둘러싸면 정규 표현식이 됩니다."
 hiddenTags: "숨긴 해시태그"
 hiddenTagsDescription: "설정한 태그를 트렌드에 표시하지 않도록 합니다. 줄 바꿈으로 하나씩 나눠서 설정할 수 있습니다."
 notesSearchNotAvailable: "노트 검색을 이용하실 수 없습니다."
@@ -1051,6 +1063,8 @@ limitWidthOfReaction: "리액션의 최대 폭을 제한하고 작게 표시하
 noteIdOrUrl: "노트 ID 및 URL"
 video: "동영상"
 videos: "동영상"
+audio: "소리"
+audioFiles: "소리"
 dataSaver: "데이터 절약 모드"
 accountMigration: "계정 이동"
 accountMoved: "이 사용자는 다음 계정으로 이사했습니다:"
@@ -1151,9 +1165,16 @@ showRepliesToOthersInTimeline: "타임라인에 다른 사람에게 보내는 
 hideRepliesToOthersInTimeline: "타임라인에 다른 사람에게 보내는 답글을 포함하지 않음"
 showRepliesToOthersInTimelineAll: "타임라인에 현재 팔로우 중인 사람 전원의 답글을 포함하게 하기"
 hideRepliesToOthersInTimelineAll: "타임라인에 현재 팔로우 중인 사람 전원의 답글이 나오지 않게 하기"
-confirmShowRepliesAll: "이 조작은 되돌릴 수 없습니다. 정말로 타임라인에 현재 팔로우 중인 사람 전원의 답글이 나오지 않게 하시겠습니까?"
+confirmShowRepliesAll: "이 조작은 되돌릴 수 없습니다. 정말로 타임라인에 현재 팔로우 중인 사람 전원의 답글이 나오게 하시겠습니까?"
 confirmHideRepliesAll: "이 조작은 되돌릴 수 없습니다. 정말로 타임라인에 현재 팔로우 중인 사람 전원의 답글이 나오지 않게 하시겠습니까?"
 externalServices: "외부 서비스"
+sourceCode: "소스 코드"
+sourceCodeIsNotYetProvided: "소스 코드를 아직 제공하지 않습니다. 이 문제를 해결하려면 관리자에게 문의해 주세요."
+repositoryUrl: "저장소 URL"
+repositoryUrlDescription: "소스 코드를 공개한 저장소가 있는 경우, 그 URL을 적습니다. Misskey를 원본 그대로 (소스 코드를 어떤 식으로도 변경하지 않고) 쓰고 있는 경우 https://github.com/misskey-dev/misskey 라고 적습니다."
+repositoryUrlOrTarballRequired: "저장소를 공개하지 않은 경우 대신 tarball을 제공할 필요가 있습니다. 세부사항은 .config/example.yml을 참조해 주세요."
+feedback: "피드백"
+feedbackUrl: "피드백 URL"
 impressum: "운영자 정보"
 impressumUrl: "운영자 정보 URL"
 impressumDescription: "독일 등의 일부 나라와 지역에서는 꼭 표시해야 합니다(Impressum)."
@@ -1183,6 +1204,26 @@ seasonalScreenEffect: "계절에 따른 효과 보이기"
 decorate: "장식하기"
 addMfmFunction: "장식 추가하기"
 enableQuickAddMfmFunction: "상급자용 MFM 선택기 표시하기"
+bubbleGame: "버블 게임"
+sfx: "효과음"
+soundWillBePlayed: "소리가 재생됩니다"
+showReplay: "리플레이 보기"
+replay: "리플레이"
+replaying: "리플레이 중"
+ranking: "랭킹"
+lastNDays: "최근 {n}일"
+backToTitle: "타이틀로 가기"
+hemisphere: "거주 지역"
+withSensitive: "민감한 파일이 포함된 노트 보기"
+userSaysSomethingSensitive: "{name}의 민감한 파일이 포함된 게시물"
+enableHorizontalSwipe: "스와이프하여 탭 전환"
+surrender: "그만두기"
+_bubbleGame:
+  howToPlay: "설명"
+  _howToPlay:
+    section1: "위치를 조정하여 상자에 물건을 떨어뜨립니다."
+    section2: "같은 종류의 물건이 붙으면 다른 물건으로 바뀌면서 점수를 얻게 됩니다."
+    section3: "상자에서 물건이 넘치면 게임 오버입니다. 상자에서 물건이 넘치지 않도록 하면서 물건을 융합하여 높은 점수를 획득하세요!"
 _announcement:
   forExistingUsers: "기존 유저에게만 알림"
   forExistingUsersDescription: "활성화하면 이 공지사항을 게시한 시점에서 이미 가입한 유저에게만 표시합니다. 비활성화하면 게시 후에 가입한 유저에게도 표시합니다."
@@ -1553,6 +1594,13 @@ _achievements:
     _tutorialCompleted:
       title: "Misskey 입문자 과정 수료증"
       description: "튜토리얼을 완료했습니다"
+    _bubbleGameExplodingHead:
+      title: "🤯"
+      description: "버블 게임에서 가장 큰 물건을 내놓았다"
+    _bubbleGameDoubleExplodingHead:
+      title: "더블 🤯"
+      description: "버블게임에서 가장 큰 물건 2개를 동시에 내놓았다."
+      flavor: "이 정도만 도시락통에 🤯 🤯 조금만 더"
 _role:
   new: "새 역할 생성"
   edit: "역할 수정"
@@ -1716,6 +1764,8 @@ _aboutMisskey:
   contributors: "주요 기여자"
   allContributors: "모든 기여자"
   source: "소스 코드"
+  original: "원본"
+  thisIsModifiedVersion: "{name}에서는 원본 미스키를 수정한 버전을 사용하고 있습니다."
   translation: "Misskey를 번역하기"
   donate: "Misskey에 기부하기"
   morePatrons: "이 외에도 다른 많은 분들이 도움을 주시고 계십니다. 감사합니다🥰"
@@ -1761,7 +1811,7 @@ _instanceMute:
   title: "지정한 서버의 노트를 숨깁니다."
   heading: "뮤트할 서버"
 _theme:
-  explore: "테마 찾아보기"
+  explore: "테마 둘러보기"
   install: "테마 설치"
   manage: "테마 관리"
   code: "테마 코드"
@@ -1986,7 +2036,7 @@ _permissions:
   "write:report-abuse": "위반 내용 신고하기"
 _auth:
   shareAccessTitle: "어플리케이션의 접근 허가"
-  shareAccess: "\"{name}\" 이 계정에 접근하는 것을 허용하시겠습니까?"
+  shareAccess: "‘{name}’에서 계정에 접근하는 것을 허용하시겠습니까?"
   shareAccessAsk: "이 애플리케이션이 계정에 접근하는 것을 허용하시겠습니까?"
   permission: "{name}에서 다음 권한을 요청하였습니다"
   permissionAsk: "이 앱은 다음의 권한을 요청합니다"
@@ -2105,6 +2155,7 @@ _profile:
 _exportOrImport:
   allNotes: "모든 노트"
   favoritedNotes: "즐겨찾기한 노트"
+  clips: "클립"
   followingList: "팔로잉"
   muteList: "뮤트"
   blockingList: "차단"
@@ -2330,6 +2381,7 @@ _moderationLogTypes:
   resetPassword: "비밀번호 재설정"
   suspendRemoteInstance: "리모트 서버를 정지"
   unsuspendRemoteInstance: "리모트 서버의 정지를 해제"
+  updateRemoteInstanceNote: "리모트 서버의 조정 기록 갱신"
   markSensitiveDriveFile: "파일에 열람주의를 설정"
   unmarkSensitiveDriveFile: "파일에 열람주의를 해제"
   resolveAbuseReport: "신고 처리"
@@ -2404,3 +2456,53 @@ _dataSaver:
   _code:
     title: "문자열 강조"
     description: "MFM 등으로 문자열 강조 기법을 사용할 때 누르기 전에는 불러오지 않습니다. 문자열 강조에서는 강조할 언어마다 그 정의 파일을 불러와야 하지만 이를 자동으로 불러오지 않으므로 데이터 사용량을 줄일 수 있습니다."
+_hemisphere:
+  N: "북반구"
+  S: "남반구"
+  caption: "일부 클라이언트 설정에서 계절을 판단하기 위해 사용합니다."
+_reversi:
+  reversi: "리버시"
+  gameSettings: "대국 설정"
+  chooseBoard: "보드 선택"
+  blackOrWhite: "선공/후공"
+  blackIs: "{name}님이 흑(선공)"
+  rules: "규칙"
+  thisGameIsStartedSoon: "대국이 곧 시작됩니다"
+  waitingForOther: "상대방의 준비가 완료되기를 기다리고 있습니다."
+  waitingForMe: "당신의 준비가 완료되기를 기다리고 있습니다."
+  waitingBoth: "준비하세요"
+  ready: "준비 완료"
+  cancelReady: "준비 다시 시작"
+  opponentTurn: "상대의 차례입니다"
+  myTurn: "당신의 차례입니다"
+  turnOf: "{name}의 차례입니다"
+  pastTurnOf: "{name}의 차례"
+  surrender: "기권"
+  surrendered: "기권에 의해"
+  timeout: "시간 초과"
+  drawn: "무승부"
+  won: "{name}의 승리"
+  black: "흑"
+  white: "백"
+  total: "합계"
+  turnCount: "{count}턴 째"
+  myGames: "내 대국"
+  allGames: "모두의 대국"
+  ended: "종료"
+  playing: "대국 중"
+  isLlotheo: "돌이 적은 사람이 승리 (로세오)"
+  loopedMap: "루프 지도"
+  canPutEverywhere: "어디에도 둘 수 있는 모드"
+  timeLimitForEachTurn: "1턴의 시간 제한"
+  freeMatch: "프리매치"
+  lookingForPlayer: "상대를 찾고 있습니다"
+  gameCanceled: "대국이 취소되었습니다"
+  shareToTlTheGameWhenStart: "대국 시작 시 타임라인에 대국을 게시"
+  iStartedAGame: "대국이 시작되었습니다! #MisskeyReversi"
+  opponentHasSettingsChanged: "상대방이 설정을 변경했습니다"
+  allowIrregularRules: "규칙변경 허가 (완전 자유)"
+  disallowIrregularRules: "규칙변경 없음"
+_offlineScreen:
+  title: "오프라인 - 서버에 접속할 수 없습니다"
+  header: "서버에 접속할 수 없습니다"
+
diff --git a/locales/lo-LA.yml b/locales/lo-LA.yml
index c9e5aea1ed..6f03c914fd 100644
--- a/locales/lo-LA.yml
+++ b/locales/lo-LA.yml
@@ -1,9 +1,9 @@
 ---
 _lang_: "ພາສາລາວ"
-headlineMisskey: "ເຊື່ອມຕໍ່ເຄືອຂ່າຍໂດຍຫມາຍເຫດ"
-introMisskey: "ຍິນດີຕ້ອນຮັບ! Misskey ເປັນແຫຼ່ງເປີດ, ການບໍລິການ microblogging ກະຈາຍ\nສ້າງ \"ບັນທຶກ\" ເພື່ອແບ່ງປັນຄວາມຄິດຂອງທ່ານກັບທຸກໆຄົນທີ່ຢູ່ອ້ອມຮອບທ່ານ 📡\nດ້ວຍ \"ປະຕິກິລິຍາ\", ທ່ານຍັງສາມາດສະແດງຄວາມຮູ້ສຶກຂອງທ່ານຢ່າງໄວວາກ່ຽວກັບບັນທຶກຂອງທຸກໆຄົນ 👍\nມາສຳຫຼວດໂລກໃໝ່! 🚀"
+headlineMisskey: "ເຊື່ອມຕໍ່ເຄືອຂ່າຍໂດຍ note"
+introMisskey: "ຍິນດີຕ້ອນຮັບ! Misskey ເປັນຊອຟແວopensource, ສຳລັບບໍລິການ microblogging ແບບ decentralized\nສ້າງ “note” ເພື່ອແບ່ງປັນຄວາມຄິດຂອງທ່ານກັບທຸກໆ ຄົນທີ່ຢູ່ອ້ອມຮອບທ່ານ 📡\nຢ່າລືມ “reaction” ໂນຕຂອງລາວເພື່ອສະແດງຄວາມຮູ້ສຶກ 👍\nມາສຳຫຼວດໂລກໃໝ່ແນ! 🚀"
 poweredByMisskeyDescription: "{name} ແມ່ນສ່ວນໜຶ່ງຂອງການບໍລິການທີ່ຂັບເຄື່ອນໂດຍແພລດຟອມ open source. <b>Misskey</b> (ເອີ້ນວ່າ \"Misskey instance\")"
-monthAndDay: "{ເດືອນ}/{ມື້}"
+monthAndDay: "ເດືອນ{month} / ວັນ{day}"
 search: "ຄົ້ນຫາ"
 notifications: "ການແຈ້ງເຕືອນ"
 username: "ຊື່ຜູ້ໃຊ້"
@@ -15,25 +15,25 @@ gotIt: "ເຂົ້າໃຈແລ້ວ!"
 cancel: "ຍົກເລີກ"
 noThankYou: "ບໍ່​ແມ່ນ​ຕອນ​ນີ້"
 enterUsername: "ປ້ອນຊື່ຜູ້ໃຊ້"
-renotedBy: "Renoted ໂດຍ {ຜູ້ໃຊ້}"
-noNotes: "ບໍ່ມີຫມາຍເຫດ"
+renotedBy: "Renoted ໂດຍ {user}"
+noNotes: "ບໍ່ມີ note"
 noNotifications: "ບໍ່ມີການແຈ້ງເຕືອນ"
 instance: "ອີນສະແຕນ"
 settings: "ກຳນົດຄ່າ"
 notificationSettings: "ຕັ້ງຄ່າການແຈ້ງເຕືອນ"
 basicSettings: "ການຕັ້ງຄ່າພື້ນຖານ"
 otherSettings: "ການຕັ້ງຄ່າອື່ນໆ"
-openInWindow: "ເປີດຢູ່ໃນປ່ອງຢ້ຽມ"
+openInWindow: "ເປີດໃນປ່ອງຢ້ຽມ"
 profile: "ໂພຼຟາຍ"
-timeline: "​ເສັ້ນກຳ​ນົດ​ເວ​ລາ​"
+timeline: "ໄທມ໌ໄລນ໌"
 noAccountDescription: "ຜູ້ໃຊ້ນີ້ຍັງບໍ່ໄດ້ຂຽນໃນຊີວະປະຫວັດຂອງເຂົາເຈົ້າເທື່ອ"
 login: "ເຂົ້າ​ສູ່​ລະ​ບົບ"
 loggingIn: "ກຳລັງເຂົ້າສູ່ລະບົບ..."
 logout: "ອອກ​ຈາກ​ລະ​ບົບ"
 signup: "ລົງ​ທະ​ບຽນ"
-uploading: "ການອັບໂຫຼດ..."
+uploading: "ກຳລັງອັບໂຫຼດ..."
 save: "ບັນທຶກ"
-users: "ຜູ້ໃຊ້ຕ່າງໆ"
+users: "ຜູ້ໃຊ້"
 addUser: "ເພີ່ມຜູ້ໃຊ້"
 favorite: "ເພີ່ມໃສ່ລາຍການທີ່ມັກ"
 favorites: "ລາຍການທີ່ມັກ"
@@ -41,13 +41,14 @@ unfavorite: "ລຶບອອກຈາກລາຍການທີ່ມັກ"
 favorited: "ເພີ່ມໃສ່ລາຍການທີ່ມັກແລ້ວ"
 alreadyFavorited: "ເພີ່ມເຂົ້າໃນລາຍການທີ່ມັກແລ້ວ."
 cantFavorite: "ບໍ່ສາມາດເພີ່ມໃສ່ລາຍການທີ່ມັກໄດ້."
-pin: "ປັກໝຸດໄປຫາໂປຣໄຟລ໌"
-unpin: "ຖອດປັກໝຸດອອກຈາກໂປຣໄຟລ໌"
+pin: "ປັກໝຸດ"
+unpin: "ຖອດປັກໝຸດອອກ"
 copyContent: "ຄັດລອກເນື້ອຫາ"
-copyLink: "ສຳເນົາລິ້ງ"
+copyLink: "ຄັດລອກລິ້ງ"
+copyLinkRenote: "ຄັດລອກລິ້ງຂອງ renote"
 delete: "ລຶບ"
-deleteAndEdit: "ລົບ​ແລະ​ແກ້​ໄຂ​"
-deleteAndEditConfirm: "ເຈົ້າ​ແນ່​ໃຈ​ບໍ່? ທີ່ທ່ານຕ້ອງການທີ່ຈະລຶບບັນທຶກນີ້ແລະແກ້ໄຂມັນ ທ່ານອາດຈະສູນເສຍການໂຕ້ຕອບ, ບັນທຶກ, ແລະການຕອບກັບທັງໝົດ"
+deleteAndEdit: "ລຶບ​ແລະ​ແກ້​ໄຂ​"
+deleteAndEditConfirm: "ເຈົ້າ​ແນ່​ໃຈ​ບໍ່? ທີ່ທ່ານຕ້ອງການທີ່ຈະລຶບ note ນີ້ ແລະແກ້ໄຂມັນ ທ່ານອາດຈະສູນເສຍ reaction, renote, ແລະການຕອບກັບທັງໝົດ"
 addToList: "ເພີ່ມໃສ່ລາຍຊື່"
 addToAntenna: "ເພີ່ມໃສ່ເສົາອາກາດ"
 sendMessage: "ສົ່ງຂໍ້ຄວາມ"
@@ -66,15 +67,15 @@ showLess: "ປິດ"
 youGotNewFollower: "ໄດ້ຕິດຕາມທ່ານ"
 receiveFollowRequest: "ປະຕິບັດຕາມຄໍາຮ້ອງຂໍທີ່ໄດ້ຮັບ"
 followRequestAccepted: "ຜູ້ຕິດຕາມໄດ້ຍອມຮັບຄໍາຮ້ອງຂໍຂອງທ່ານ"
-mention: "ໄດ້ກ່າວມາ"
+mention: "ກ່າວຖືງ"
 mentions: "ກ່າວເຖິງ"
-directNotes: "ໂດຍກົງຫມາຍເຫດ"
+directNotes: "ໂພສ Direct note"
 importAndExport: "ນໍາເຂົ້າ / ສົ່ງອອກ"
 import: "ນຳເຂົ້າ"
-export: "ນຳອອກ"
+export: "ສົ່ງອອກ"
 files: "ໄຟລ໌"
 download: "ດາວໂຫລດ"
-driveFileDeleteConfirm: "ທ່ານແນ່ໃຈບໍ່ວ່າຕ້ອງການລຶບໄຟລ໌ \"{name}\"? ບັນທຶກທີ່ມີໄຟລ໌ແນບນີ້ຈະຖືກລຶບຖິ້ມ"
+driveFileDeleteConfirm: "ທ່ານແນ່ໃຈບໍ່ວ່າຕ້ອງການລຶບໄຟລ໌ \"{name}\"? note ທີ່ມີໄຟລ໌ແນບນີ້ຈະຖືກລຶບຖິ້ມ"
 unfollowConfirm: "ທ່ານແນ່ໃຈບໍ່ວ່າຕ້ອງການເຊົາຕິດຕາມ {name}?"
 exportRequested: "ໃນເວລາທີ່ທ່ານໄດ້ຮ້ອງຂໍການສົ່ງອອກ ມັນອາດຈະໃຊ້ເວລາບາງເວລາ ແລະມັນຈະຖືກເພີ່ມໃສ່ drive ຂອງທ່ານເມື່ອມັນສຳເລັດແລ້ວ"
 importRequested: "ໃນເວລາທີ່ທ່ານໄດ້ຮ້ອງຂໍການນໍາເຂົ້າ ມັນອາດຈະໃຊ້ເວລາບາງເວລາ"
@@ -86,7 +87,7 @@ following: "ກຳລັງຕິດຕາມ"
 followers: "ຜູ້ຕິດຕາມ"
 followsYou: "ຕິດ​ຕາມ​ເຈົ້າ"
 createList: "ສ້າງລາຍຊື່"
-manageLists: "ການບໍລິຫານບັນຊີລາຍການ"
+manageLists: "ຈັດການລາຍຊື່"
 error: "ຂໍ້ຜິດພາດ"
 somethingHappened: "​ອຸຍ, ມີ​ບາງ​ຢ່າງ​ຜິ​ດ​ພາດ"
 retry: "ລອງໃຫມ່"
@@ -96,30 +97,30 @@ serverIsDead: "ເຊີບເວີນີ້ບໍ່ຕອບສະໜອງ 
 youShouldUpgradeClient: "ເພື່ອເບິ່ງໜ້ານີ້, ກະລຸນາໂຫຼດຂໍ້ມູນຄືນໃໝ່ເພື່ອອັບເດດລູກຄ້າຂອງທ່ານ"
 enterListName: "ໃສ່ຊື່ສຳລັບລາຍຊື່"
 privacy: "ຄວາມເປັນສ່ວນຕົວ"
-makeFollowManuallyApprove: "ປະຕິບັດຕາມການຮ້ອງຂໍຮຽກຮ້ອງໃຫ້ມີການອະນຸມັດ"
-defaultNoteVisibility: "ເປັນຄ່າເລີ່ມຕົ້ນ"
+makeFollowManuallyApprove: "ຕິດຕາມຄຳຂໍທີ່ຕ້ອງໄດ້ຮັບການອະນຸມັດ"
+defaultNoteVisibility: "ການເບິ່ງເຫັນທີ່ເປັນຄ່າເລີ່ມຕົ້ນ"
 follow: "ກຳລັງຕິດຕາມ"
-followRequest: "ສົ່ງ​ການ​ຮ້ອງ​ຂໍ​ປະ​ຕິ​ບ​ຕາມ​"
-followRequests: "ປະຕິບັດຕາມຄໍາຮ້ອງຂໍ"
+followRequest: "ສົ່ງ​ຄຳຂໍ​ຕິ​ດ​ຕາມ​"
+followRequests: "ສົ່ງ​ຄຳຂໍ​ຕິ​ດ​ຕາມ​"
 unfollow: "ເຊົາຕິດຕາມ"
-followRequestPending: "ປະຕິບັດຕາມຄໍາຮ້ອງຂໍທີ່ລໍຖ້າຢູ່"
-enterEmoji: "ປ້ອນອີໂມຈິ"
+followRequestPending: "ລໍຖ້າການອະນຸມັດໃຫ້ຕິດຕາມ"
+enterEmoji: "ປ້ອນເອໂມຈິ"
 renote: "Renote"
 unrenote: "ເລີກ Renote"
-renoted: "ເກັບບັນທຶກໄວ້"
-cantRenote: "ໂພສນີ້ບໍ່ສາມາດຖືກບັນທຶກໄວ້ຄືນໃໝ່ໄດ້"
+renoted: "renote ແລ້ວ"
+cantRenote: "ໂພສນີ້ບໍ່ສາມາດ renote ໃໝ່ໄດ້"
 cantReRenote: "ບໍ່ສາມາດບັນທຶກຄືນໃໝ່ໄດ້"
-quote: "ລວມຂໍ້ຄວາມອ້າງອີງ"
-inChannelRenote: "ຊ່ອງພຽງແຕ່ Renote"
-inChannelQuote: "ຊ່ອງເທົ່ານັ້ນ Quote"
-pinnedNote: "ບັນທຶກທີ່ປັກໝຸດໄວ້"
-pinned: "ປັກໝຸດໄປຫາໂປຣໄຟລ໌"
+quote: "ອ້າງອີງ"
+inChannelRenote: "Renote ໃນ channel ເທົ່ານັ້ນ"
+inChannelQuote: "ອ້າງອິງໃນ channel ເທົ່ານັ້ນ"
+pinnedNote: "note ທີ່ປັກໝຸດໄວ້"
+pinned: "ປັກໝຸດ"
 you: "ເຈົ້າ"
 clickToShow: "ກົດເພື່ອສະແດງໃຫ້ເຫັນ"
 sensitive: "NSFW"
 add: "ເພີ່ມ"
-reaction: "ປະຕິກິລິຍາ"
-reactions: "ປະຕິກິລິຍາ"
+reaction: "reaction"
+reactions: "reaction"
 attachCancel: "ເອົາໄຟລ໌ແນບ"
 mute: "ປີດສຽງ"
 unmute: "ເປີດສຽງ"
@@ -306,6 +307,8 @@ basicInfo: "ຂໍ້ມຸນເບື້ອງຕົ້ນ"
 pinnedNotes: "ບັນທຶກທີ່ປັກໝຸດໄວ້"
 hcaptchaSiteKey: "ກະແຈໄຊທ໌"
 hcaptchaSecretKey: "ກະແຈລັບ"
+mcaptchaSiteKey: "ກະແຈໄຊທ໌"
+mcaptchaSecretKey: "ກະແຈລັບ"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "ເປີດໃຊ້ງານລີແຄ໋ບຈາ"
 recaptchaSiteKey: "ກະແຈໄຊທ໌"
@@ -463,3 +466,4 @@ _webhookSettings:
   name: "ຊື່"
 _moderationLogTypes:
   suspend: "ລະງັບ"
+
diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml
index 1e96a1aa98..42fbf183be 100644
--- a/locales/nl-NL.yml
+++ b/locales/nl-NL.yml
@@ -348,6 +348,8 @@ hcaptcha: "hCaptcha"
 enableHcaptcha: "Inschakelen hCaptcha"
 hcaptchaSiteKey: "Site sleutel"
 hcaptchaSecretKey: "Geheime sleutel"
+mcaptchaSiteKey: "Site sleutel"
+mcaptchaSecretKey: "Geheime sleutel"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "Inschakelen reCAPTCHA"
 recaptchaSiteKey: "Site sleutel"
@@ -495,3 +497,4 @@ _webhookSettings:
 _moderationLogTypes:
   suspend: "Opschorten"
   resetPassword: "Wachtwoord terugzetten"
+
diff --git a/locales/no-NO.yml b/locales/no-NO.yml
index 195b1d0717..098faa8add 100644
--- a/locales/no-NO.yml
+++ b/locales/no-NO.yml
@@ -463,6 +463,7 @@ options: "Alternativ"
 icon: "Avatar"
 replies: "Svar"
 renotes: "Renote"
+surrender: "Avbryt"
 _initialAccountSetting:
   theseSettingsCanEditLater: "Du kan endre disse innstillingene senere."
 _achievements:
@@ -720,3 +721,4 @@ _webhookSettings:
   name: "Navn"
 _moderationLogTypes:
   suspend: "Suspender"
+
diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml
index b0f9f4923d..3f9fa5f5f1 100644
--- a/locales/pl-PL.yml
+++ b/locales/pl-PL.yml
@@ -345,6 +345,8 @@ hcaptcha: "hCaptcha"
 enableHcaptcha: "Włącz hCaptcha"
 hcaptchaSiteKey: "Klucz strony"
 hcaptchaSecretKey: "Tajny klucz"
+mcaptchaSiteKey: "Klucz strony"
+mcaptchaSecretKey: "Tajny klucz"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "Włącz reCAPTCHA"
 recaptchaSiteKey: "Klucz strony"
@@ -869,6 +871,7 @@ youFollowing: "Śledzeni"
 icon: "Awatar"
 replies: "Odpowiedzi"
 renotes: "Udostępnień"
+sourceCode: "Kod źródłowy"
 flip: "Odwróć"
 _role:
   priority: "Priorytet"
@@ -1232,6 +1235,7 @@ _profile:
 _exportOrImport:
   allNotes: "Wszystkie wpisy"
   favoritedNotes: "Ulubione wpisy"
+  clips: "Klip"
   followingList: "Obserwowani"
   muteList: "Wycisz"
   blockingList: "Zablokuj"
@@ -1394,3 +1398,6 @@ _webhookSettings:
 _moderationLogTypes:
   suspend: "Zawieś"
   resetPassword: "Zresetuj hasło"
+_reversi:
+  total: "Łącznie"
+
diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml
index 1fd2fd57e1..26657e7b52 100644
--- a/locales/pt-PT.yml
+++ b/locales/pt-PT.yml
@@ -368,6 +368,8 @@ hcaptcha: "hCaptcha"
 enableHcaptcha: "Ativar hCaptcha"
 hcaptchaSiteKey: "Chave do sítio ‘web’"
 hcaptchaSecretKey: "Chave secreta"
+mcaptchaSiteKey: "Chave do sítio ‘web’"
+mcaptchaSecretKey: "Chave secreta"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "Habilitar reCAPTCHA"
 recaptchaSiteKey: "Chave do sítio ‘web’"
@@ -1008,6 +1010,8 @@ replies: "Respostas"
 renotes: "Repostagens"
 keepScreenOn: "Manter a tela do dispositivo sempre ligada"
 flip: "Inversão"
+lastNDays: "Últimos {n} dias"
+surrender: "Cancelar"
 _initialAccountSetting:
   followUsers: "Siga usuários que lhe interessam para criar a sua linha do tempo."
 _serverSettings:
@@ -1400,6 +1404,7 @@ _profile:
   username: "Nome de usuário"
 _exportOrImport:
   favoritedNotes: "Notas nos favoritos"
+  clips: "Clipe"
   followingList: "Seguindo"
   muteList: "Silenciar"
   blockingList: "Bloquear"
@@ -1494,3 +1499,6 @@ _webhookSettings:
 _moderationLogTypes:
   suspend: "Suspender"
   resetPassword: "Redefinir senha"
+_reversi:
+  total: "Total"
+
diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml
index bf8787413b..e45b8b75ec 100644
--- a/locales/ro-RO.yml
+++ b/locales/ro-RO.yml
@@ -359,6 +359,8 @@ hcaptcha: "hCaptcha"
 enableHcaptcha: "Activează hCaptcha"
 hcaptchaSiteKey: "Site key"
 hcaptchaSecretKey: "Secret key"
+mcaptchaSiteKey: "Site key"
+mcaptchaSecretKey: "Secret key"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "Activează reCAPTCHA"
 recaptchaSiteKey: "Site key"
@@ -725,3 +727,6 @@ _webhookSettings:
 _moderationLogTypes:
   suspend: "Suspendă"
   resetPassword: "Resetează parola"
+_reversi:
+  total: "Total"
+
diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml
index 25f409df92..d666b69490 100644
--- a/locales/ru-RU.yml
+++ b/locales/ru-RU.yml
@@ -53,8 +53,8 @@ addToAntenna: "Добавить к антенне"
 sendMessage: "Отправить сообщение"
 copyRSS: "Скопировать RSS"
 copyUsername: "Скопировать имя пользователя"
-copyUserId: "Скопировать ID пользователя"
-copyNoteId: "Скопировать ID заметки"
+copyUserId: "Скопировать идентификатор пользователя"
+copyNoteId: "Скопировать идентификатор заметки"
 copyFileId: "Скопировать ID файла"
 copyFolderId: "Скопировать ID папки"
 copyProfileUrl: "Скопировать URL профиля "
@@ -134,8 +134,8 @@ unmarkAsSensitive: "Снять отметку «не для всех»"
 enterFileName: "Введите имя файла"
 mute: "Скрыть"
 unmute: "Отменить скрытие"
-renoteMute: "Заглушить репосты"
-renoteUnmute: "Включить репосты"
+renoteMute: "Скрыть репосты"
+renoteUnmute: "Открыть репосты"
 block: "Заблокировать"
 unblock: "Разблокировать"
 suspend: "Заморозить"
@@ -161,8 +161,8 @@ addEmoji: "Добавить эмодзи"
 settingGuide: "Рекомендуемые настройки"
 cacheRemoteFiles: "Кешировать внешние файлы"
 cacheRemoteFilesDescription: "Когда эта настройка отключена, файлы с других сайтов будут загружаться прямо оттуда. Это сэкономит место на сервере, но увеличит трафик, так как не будут создаваться эскизы."
-cacheRemoteSensitiveFiles: "Кешировать внешние файлы"
-cacheRemoteSensitiveFilesDescription: "Описание удаленных внешних файлов в кэше"
+cacheRemoteSensitiveFiles: "Кэшировать внешние файлы «не для всех»"
+cacheRemoteSensitiveFilesDescription: "Если отключено, файлы «не для всех» загружаются непосредственно с удалённых серверов, не кэшируясь."
 flagAsBot: "Аккаунт бота"
 flagAsBotDescription: "Включите, если этот аккаунт управляется программой. Это позволит системе Misskey учитывать это, а также поможет разработчикам других ботов предотвратить бесконечные циклы взаимодействия."
 flagAsCat: "Аккаунт кота"
@@ -261,6 +261,7 @@ removed: "Удалено"
 removeAreYouSure: "Хотите удалить «{x}»?"
 deleteAreYouSure: "Хотите удалить «{x}»?"
 resetAreYouSure: "На самом деле сбросить?"
+areYouSure: "Вы уверены?"
 saved: "Сохранено"
 messaging: "Сообщения"
 upload: "Загрузить"
@@ -278,7 +279,7 @@ noMoreHistory: "История закончилась"
 startMessaging: "Начать общение"
 nUsersRead: "Прочитали {n}"
 agreeTo: "Я соглашаюсь с {0}"
-agree: "Согласиться"
+agree: "Согласен"
 agreeBelow: "Согласен со следующими"
 basicNotesBeforeCreateAccount: "Записи, перед созданием аккаунта"
 termsOfService: "Условия использования"
@@ -324,7 +325,7 @@ copyUrl: "Копировать ссылку"
 rename: "Переименовать"
 avatar: "Аватар"
 banner: "Шапка"
-displayOfSensitiveMedia: "Определение деликатного контента"
+displayOfSensitiveMedia: "Отображение содержимого не для всех"
 whenServerDisconnected: "Когда соединение с сервером потеряно"
 disconnectedFromServer: "Разорвано соединение с сервером"
 reload: "Перезагрузить"
@@ -372,6 +373,8 @@ hcaptcha: "hCaptcha"
 enableHcaptcha: "Включить hCaptcha"
 hcaptchaSiteKey: "Ключ сайта"
 hcaptchaSecretKey: "Секретный ключ"
+mcaptchaSiteKey: "Ключ сайта"
+mcaptchaSecretKey: "Секретный ключ"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "Включить reCAPTCHA"
 recaptchaSiteKey: "Ключ сайта"
@@ -413,7 +416,7 @@ about: "Описание"
 aboutMisskey: "О Misskey"
 administrator: "Администратор"
 token: "Токен"
-2fa: "2-х факторная аутентификация"
+2fa: "Двухфакторная аутентификация"
 setupOf2fa: "Настроить двухфакторную аутентификацию"
 totp: "Приложение-аутентификатор"
 totpDescription: "Описание приложения-аутентификатора"
@@ -477,7 +480,7 @@ aboutX: "Описание {x}"
 emojiStyle: "Стиль эмодзи"
 native: "Системные"
 disableDrawer: "Не использовать выдвижные меню"
-showNoteActionsOnlyHover: "Показывать кнопки управления заметкой только при наведении"
+showNoteActionsOnlyHover: "Показывать кнопки у заметок только при наведении"
 noHistory: "История пока пуста"
 signinHistory: "Журнал посещений"
 enableAdvancedMfm: "Включить расширенный MFM"
@@ -490,8 +493,8 @@ createAccount: "Новая учётная запись"
 existingAccount: "Существующая учётная запись"
 regenerate: "Создать повторно"
 fontSize: "Размер шрифта"
-mediaListWithOneImageAppearance: "Показывать список медиа только одним изображением"
-limitTo: "Обрезать до {x}"
+mediaListWithOneImageAppearance: "Вид изображения, если оно единственное в списке"
+limitTo: "Ограничить до {x}"
 noFollowRequests: "Нерассмотренные запросы на подписку отсутствуют"
 openImageInNewTab: "Открыть изображение в новой вкладке"
 dashboard: "Панель управления"
@@ -525,7 +528,7 @@ objectStorageUseSSLDesc: "Отключите, если не собираетес
 objectStorageUseProxy: "Использовать прокси"
 objectStorageUseProxyDesc: "Отключите, если не будете испоьзовать прокси для соединений по протоколу ObjectStorage."
 objectStorageSetPublicRead: "Устанавливать public-read при загрузке на сервер"
-s3ForcePathStyleDesc: "Включение s3ForcePathStyle принудительно указывает имя корзины как часть пути в URL-адресе вместо имени хоста. Может потребоваться активация при использовании таких вещей, как локальный Minio."
+s3ForcePathStyleDesc: "Включение s3ForcePathStyle приводит к тому, что имя корзины указывается как часть пути в URL, а не в имени хоста. Может потребоваться включить при использовании локального Minio или чего-то подобного."
 serverLogs: "Журнал сервера"
 deleteAll: "Удалить всё"
 showFixedPostForm: "Показывать поле для ввода новой заметки наверху ленты"
@@ -569,7 +572,7 @@ yourAccountSuspendedTitle: "Эта учетная запись заблокир
 yourAccountSuspendedDescription: "Эта учетная запись была заблокирована из-за нарушения условий предоставления услуг сервера. Свяжитесь с администратором, если вы хотите узнать более подробную причину. Пожалуйста, не создавайте новую учетную запись."
 tokenRevoked: "Токен недействителен"
 tokenRevokedDescription: "Срок действия вашего токена входа истек. Пожалуйста, войдите снова."
-accountDeleted: "Эта учетная запись удалена"
+accountDeleted: "Учетная запись удалена"
 accountDeletedDescription: "Эта учетная запись удалена"
 menu: "Меню"
 divider: "Линия-разделитель"
@@ -651,6 +654,7 @@ useGlobalSettingDesc: "Если включено, будут использов
 other: "Другие"
 regenerateLoginToken: "Создать новый токен для входа"
 regenerateLoginTokenDescription: "Создаёт новый токен, используемый внутри программы во время входа. Обычно в этом нет необходимости. При создании все устройства будут отключены."
+theKeywordWhenSearchingForCustomEmoji: "Это ключевое слово будет использовано при поиске эмодзи."
 setMultipleBySeparatingWithSpace: "Можно написать несколько через пробел"
 fileIdOrUrl: "Идентификатор файла или ссылка"
 behavior: "Поведение"
@@ -721,7 +725,7 @@ useSystemFont: "Использовать шрифт, предлагаемый с
 clips: "Подборки"
 experimentalFeatures: "Экспериментальные функции"
 experimental: "Экспериментальные"
-thisIsExperimentalFeature: "Это экспериментальная функция. Технические характеристики могут измениться или он может работать неправильно."
+thisIsExperimentalFeature: "Это экспериментальная функция. Её поведение, вероятно, поменяется в следующей версии, а ещё она может работать не так, как задумано."
 developer: "Разработчик"
 makeExplorable: "Опубликовать профиль в «Обзоре»."
 makeExplorableDescription: "Если выключить, ваш профиль не будет показан в разделе «Обзор»."
@@ -806,7 +810,7 @@ noMaintainerInformationWarning: "Не заполнены сведения об 
 noBotProtectionWarning: "Ботозащита не настроена"
 configure: "Настроить"
 postToGallery: "Опубликовать в галерею"
-postToHashtag: "Опубликовать пост с этим хештегом"
+postToHashtag: "Написать заметку с этим хэштегом"
 gallery: "Галерея"
 recentPosts: "Недавние публикации"
 popularPosts: "Популярные публикации"
@@ -835,7 +839,7 @@ useBlurEffect: "Размытие в интерфейсе"
 learnMore: "Подробнее"
 misskeyUpdated: "Misskey обновился!"
 whatIsNew: "Что новенького?"
-translate: "Перевод"
+translate: "Перевести"
 translatedFrom: "Перевод. Язык оригинала — {x}"
 accountDeletionInProgress: "В настоящее время выполняется удаление учетной записи"
 usernameInfo: "Имя, которое отличает вашу учетную запись от других на этом сервере. Вы можете использовать алфавит (a~z, A~Z), цифры (0~9) или символы подчеркивания (_). Имена пользователей не могут быть изменены позже."
@@ -847,11 +851,11 @@ lastCommunication: "Последнее сообщение"
 resolved: "Решено"
 unresolved: "Без решения"
 breakFollow: "Отписка"
-breakFollowConfirm: "Удалить из подписок пользователя ?"
+breakFollowConfirm: "Действительно удалить этого подписчика?"
 itsOn: "Включено"
 itsOff: "Выключено"
-on: "Вкл"
-off: "Выкл"
+on: "Вкл."
+off: "Выкл."
 emailRequiredForSignup: "Для регистрации учётной записи нужен адрес электронной почты"
 unread: "Непрочитанное"
 filter: "Фильтры"
@@ -880,7 +884,7 @@ numberOfColumn: "Количество столбцов"
 searchByGoogle: "Поиск"
 instanceDefaultLightTheme: "Светлая тема по умолчанию"
 instanceDefaultDarkTheme: "Темная тема по умолчанию"
-instanceDefaultThemeDescription: "Описание темы по умолчанию для инстанса"
+instanceDefaultThemeDescription: "Введите код темы в формате объекта."
 mutePeriod: "Продолжительность скрытия"
 period: "Опрос длится"
 indefinitely: "вечно"
@@ -904,7 +908,7 @@ thereIsUnresolvedAbuseReportWarning: "Остались нерешённые жа
 recommended: "Рекомендуем"
 check: "Проверить"
 driveCapOverrideLabel: "Изменение лимита дискового пространства для этого пользователя"
-driveCapOverrideCaption: "Укажите меньше или равное нулю для отмены"
+driveCapOverrideCaption: "Введите нуль или меньше, чтобы использовать значение по умолчанию."
 requireAdminForView: "Для просмотра необходимо иметь аккаунт администратора"
 isSystemAccount: "Данная учётная запись создана автоматически и управляется системой"
 typeToConfirm: "Введите {x} для продолжения"
@@ -924,7 +928,7 @@ type: "Тип"
 speed: "Скорость"
 slow: "Медленная"
 fast: "Быстрая"
-sensitiveMediaDetection: "Определение содержимого деликатного характера"
+sensitiveMediaDetection: "Распознание содержимого не для всех"
 localOnly: "Локально"
 remoteOnly: "Только удалённо"
 failedToUpload: "Сбой выгрузки"
@@ -1001,15 +1005,17 @@ invitationRequiredToRegister: "Этот сервер в настоящее вр
 emailNotSupported: "Доставка почты не поддерживается на этом сервере"
 postToTheChannel: "Отправить в канал"
 cannotBeChangedLater: "Это нельзя изменить позже"
-reactionAcceptance: "Принятие реакций"
-likeOnly: "Только лайки"
-likeOnlyForRemote: "Только лайки с удалённых серверов"
-nonSensitiveOnly: "Безопасный серфинг"
+reactionAcceptance: "Допустимые реакции"
+likeOnly: "Только «нравится!»"
+likeOnlyForRemote: "Всё (с других серверов только «нравится!»)"
+nonSensitiveOnly: "Только безопасные"
+nonSensitiveOnlyForLocalLikeOnlyForRemote: "Только безопасные (с других серверов только «нравится!»)"
 rolesAssignedToMe: "Мои роли"
 resetPasswordConfirm: "Сбросить пароль?"
 sensitiveWords: "Чувствительные слова"
 sensitiveWordsDescription: "Установите общедоступный диапазон заметки, содержащей заданное слово, на домашний. Можно сделать несколько настроек, разделив их переносами строк."
 sensitiveWordsDescription2: "Разделение пробелом создаёт спецификацию AND, а разделение косой чертой создаёт регулярное выражение."
+prohibitedWordsDescription2: "Разделение пробелом создаёт спецификацию AND, а разделение косой чертой создаёт регулярное выражение."
 notesSearchNotAvailable: "Поиск заметок недоступен"
 license: "Лицензия"
 unfavoriteConfirm: "Удалить избранное?"
@@ -1024,20 +1030,20 @@ noteIdOrUrl: "ID или ссылка на заметку"
 video: "Видео"
 videos: "Видео"
 dataSaver: "Экономия трафика"
-accountMigration: "Перенести учётную запись"
-accountMoved: "Учетная запись перенесена"
+accountMigration: "Перенос учётной записи"
+accountMoved: "Учётная запись перенесена"
 accountMovedShort: "Эта учётная запись перемещена"
-operationForbidden: "Эта операция невозможна."
+operationForbidden: "Это действие запрещено"
 forceShowAds: "Всегда отображать рекламу"
-addMemo: "Добавить заметку"
-editMemo: "Редактировать заметку"
-reactionsList: "Реакции"
+addMemo: "Добавить памятку"
+editMemo: "Изменить памятку"
+reactionsList: "Список реакций"
 renotesList: "Репосты"
-notificationDisplay: "Отображение уведомления"
-leftTop: "Верхний левый угол"
-rightTop: "Сверху справа"
-leftBottom: "Снизу слева"
-rightBottom: "Снизу справа"
+notificationDisplay: "Отображение уведомлений"
+leftTop: "Влево вверх"
+rightTop: "Вправо вверх"
+leftBottom: "Влево вниз"
+rightBottom: "Вправо вниз"
 vertical: "Вертикальная"
 horizontal: "Сбоку"
 position: "Позиция"
@@ -1076,7 +1082,10 @@ icon: "Аватар"
 replies: "Ответы"
 renotes: "Репост"
 loadReplies: "Показать ответы"
+sourceCode: "Исходный код"
 flip: "Переворот"
+lastNDays: "Последние {n} сут"
+surrender: "Этот пост не может быть отменен."
 _initialAccountSetting:
   accountCreated: "Аккаунт успешно создан!"
   letsStartAccountSetup: "Давайте настроим вашу учётную запись."
@@ -1693,7 +1702,7 @@ _weekday:
 _widgets:
   profile: "Профиль"
   instanceInfo: "Информация об инстансе"
-  memo: "Напоминания"
+  memo: "Памятки"
   notifications: "Уведомления"
   timeline: "Лента"
   calendar: "Календарь"
@@ -1784,6 +1793,7 @@ _profile:
 _exportOrImport:
   allNotes: "Все заметки\n"
   favoritedNotes: "Избранное"
+  clips: "Подборка"
   followingList: "Подписки"
   muteList: "Скрытые"
   blockingList: "Заблокированные"
@@ -1959,4 +1969,10 @@ _webhookSettings:
   active: "Вкл."
 _moderationLogTypes:
   suspend: "Заморозить"
+  addCustomEmoji: "Добавлено эмодзи"
+  updateCustomEmoji: "Изменено эмодзи"
+  deleteCustomEmoji: "Удалено эмодзи"
   resetPassword: "Сброс пароля:"
+_reversi:
+  total: "Всего"
+
diff --git a/locales/si-LK.yml b/locales/si-LK.yml
index ed97d539c0..cd21505a47 100644
--- a/locales/si-LK.yml
+++ b/locales/si-LK.yml
@@ -1 +1,2 @@
 ---
+
diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml
index ccd9767695..f280f91270 100644
--- a/locales/sk-SK.yml
+++ b/locales/sk-SK.yml
@@ -349,6 +349,8 @@ hcaptcha: "hCaptcha"
 enableHcaptcha: "Zapnúť hCaptchu"
 hcaptchaSiteKey: "Site key"
 hcaptchaSecretKey: "Secret key"
+mcaptchaSiteKey: "Site key"
+mcaptchaSecretKey: "Secret key"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "Zapnúť ReCAPTCHA"
 recaptchaSiteKey: "Site key"
@@ -917,7 +919,9 @@ youFollowing: "Sledované"
 icon: "Avatar"
 replies: "Odpovede"
 renotes: "Preposlať"
+sourceCode: "Zdrojový kód"
 flip: "Preklopiť"
+lastNDays: "Posledných {n} dní"
 _role:
   priority: "Priorita"
   _priority:
@@ -1284,6 +1288,7 @@ _profile:
   changeBanner: "Zmeniť banner"
 _exportOrImport:
   allNotes: "Všetky poznámky"
+  clips: "Klip"
   followingList: "Sledujete"
   muteList: "Vypnúť zvuk"
   blockingList: "Zablokovať"
@@ -1441,3 +1446,6 @@ _webhookSettings:
 _moderationLogTypes:
   suspend: "Zmraziť"
   resetPassword: "Resetovať heslo"
+_reversi:
+  total: "Celkom"
+
diff --git a/locales/sv-SE.yml b/locales/sv-SE.yml
index 4defa3b11f..76b9bc90b7 100644
--- a/locales/sv-SE.yml
+++ b/locales/sv-SE.yml
@@ -345,6 +345,8 @@ hcaptcha: "hCaptcha"
 enableHcaptcha: "Aktivera hCaptcha"
 hcaptchaSiteKey: "Webbplatsnyckel"
 hcaptchaSecretKey: "Hemlig nyckel"
+mcaptchaSiteKey: "Webbplatsnyckel"
+mcaptchaSecretKey: "Hemlig nyckel"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "Aktivera reCAPTCHA"
 recaptchaSiteKey: "Webbplatsnyckel"
@@ -574,3 +576,4 @@ _webhookSettings:
 _moderationLogTypes:
   suspend: "Suspendera"
   resetPassword: "Återställ Lösenord"
+
diff --git a/locales/th-TH.yml b/locales/th-TH.yml
index d94cfbfc51..60c34d84b9 100644
--- a/locales/th-TH.yml
+++ b/locales/th-TH.yml
@@ -1,20 +1,20 @@
 ---
 _lang_: "ภาษาไทย"
-headlineMisskey: "เชื่อมต่อระบบ Network ด้วย Note"
-introMisskey: "ยินดีต้อนรับทุกคนจ้า! Misskey คือ บริการไมโครบล็อกกิ้ง (MicroBlogging) แบบกระจายศูนย์อำนาจ (Decentralized) \n\nเขียน \"โน้ต (Note)\" เพื่อส่งต่อเรื่องราวของคุณให้ทั้งโลกได้รับรู้📡\nและอย่าลืมที่จะ \"React\" กับเรื่องราวของคนอื่น ๆ ด้วย! 👍\n\nมุ่งสู่โลกใบใหม่กันเถอะ🚀"
+headlineMisskey: "เชื่อมต่อเครือข่ายโดยโน้ต"
+introMisskey: "ยินดีต้อนรับทุกคนจ้า! Misskey คือ ซอฟต์แวร์โอเพนซอร์สสำหรับบริการไมโครบล็อกกิ้ง (MicroBlogging) แบบกระจายศูนย์อำนาจ (Decentralized) \n\nเขียน “โน้ต (Note)” เพื่อส่งต่อเรื่องราวของคุณให้ทั้งโลกได้รับรู้📡\nและอย่าลืมที่จะ “รีแอคชั่น” กับเรื่องราวของคนอื่น ๆ ด้วยนะ! 👍\n\nท่องสำรวจโลกใบใหม่กันเถอะ🚀"
 poweredByMisskeyDescription: "{name} เป็นส่วนหนึ่งในบริการที่ถูกขับเคลื่อนโดยแพลตฟอร์มโอเพ่นซอร์ส <b>Misskey</b> (เรียกว่า \"อินสแตนซ์ Misskey\")"
 monthAndDay: "{month}/{day}"
 search: "ค้นหา"
 notifications: "การเเจ้งเตือน"
 username: "ชื่อผู้ใช้"
 password: "รหัสผ่าน"
-forgotPassword: "ลืมรหัสผ่านใช่ไหม"
-fetchingAsApObject: "กำลังดึงข้อมูล จาก เฟดิเวิร์ส..."
-ok: "โอเค"
+forgotPassword: "ลืมรหัสผ่าน"
+fetchingAsApObject: "กำลังดึงข้อมูลจากสหพันธ์..."
+ok: "ตกลง"
 gotIt: "เข้าใจแล้ว !"
 cancel: "ยกเลิก"
-noThankYou: "ไม่เป็นไร"
-enterUsername: "ใส่ชื่อผู้ใช้"
+noThankYou: "ไม่เอาดีกว่า"
+enterUsername: "กรอกชื่อผู้ใช้"
 renotedBy: "รีโน้ตโดย {user}"
 noNotes: "ไม่มีโน้ต"
 noNotifications: "ไม่มีการแจ้งเตือน"
@@ -26,30 +26,30 @@ otherSettings: "การตั้งค่าอื่นๆ"
 openInWindow: "เปิดในหน้าต่าง"
 profile: "โปรไฟล์"
 timeline: "ไทม์ไลน์"
-noAccountDescription: "ผู้ใช้รายนี้ยังไม่ได้เขียนลงประวัติของพวกเขา"
+noAccountDescription: "ผู้ใช้รายนี้ยังไม่ได้เขียนคำแนะนำตัว"
 login: "เข้าสู่ระบบ"
 loggingIn: "กำลังเข้าสู่ระบบ"
 logout: "ออกจากระบบ"
 signup: "สร้างบัญชีผู้ใช้"
-uploading: "กำลังอัพโหลด..."
+uploading: "กำลังอัปโหลด"
 save: "บันทึก"
 users: "ผู้ใช้งาน"
 addUser: "เพิ่มผู้ใช้"
 favorite: "รายการโปรด"
 favorites: "รายการโปรด"
 unfavorite: "ลบออกจากรายการโปรด"
-favorited: "เพิ่มแล้วในรายการโปรด"
-alreadyFavorited: "เพิ่มในรายการโปรดอยู่แล้ว"
-cantFavorite: "ไม่สามารถเพิ่มในรายการโปรดได้"
-pin: "ปักหมุดไปยังโปรไฟล์"
-unpin: "เลิกปักหมุดจากโปรไฟล์"
+favorited: "เพิ่มลงรายการโปรดแล้ว"
+alreadyFavorited: "เพิ่มลงรายการโปรดอยู่แล้ว"
+cantFavorite: "ไม่สามารถเพิ่มลงรายการโปรดได้"
+pin: "ปักหมุด"
+unpin: "เลิกปักหมุด"
 copyContent: "คัดลอกเนื้อหา"
 copyLink: "คัดลอกลิงก์"
 copyLinkRenote: "คัดลอกลิงก์รีโน้ต"
 delete: "ลบ"
 deleteAndEdit: "ลบและแก้ไข"
-deleteAndEditConfirm: "นายแน่ใจแล้วเหรอ? ว่าต้องการลบโน้ตนี้และแก้ไข คุณอาจจะสูญเสียการโต้ตอบ, โน้ต, และการตอบกลับทั้งหมดได้นะ"
-addToList: "เพิ่มในลิสต์"
+deleteAndEditConfirm: "คุณต้องการลบโน้ตนี้และแก้ไขใหม่ใช่ไหม? รีแอคชั่น รีโน้ต และการตอบกลับต่อโน้ตนี้ทั้งหมดจะถูกลบออกด้วย"
+addToList: "เพิ่มลงรายชื่อ"
 addToAntenna: "เพิ่มไปยังเสาอากาศ"
 sendMessage: "ส่งข้อความ"
 copyRSS: "คัดลอก RSS"
@@ -59,35 +59,35 @@ copyNoteId: "คัดลอก ID โน้ต "
 copyFileId: "คัดลอกไฟล์ ID"
 copyFolderId: "คัดลอกโฟลเดอร์ ID"
 copyProfileUrl: "คัดลอกโปรไฟล์ URL"
-searchUser: "ค้นหาผู้ใช้งาน"
+searchUser: "ค้นหาผู้ใช้"
 reply: "ตอบกลับ"
-loadMore: "โหลดเพิ่มเติม"
+loadMore: "แสดงเพิ่มเติม"
 showMore: "แสดงเพิ่มเติม"
 showLess: "ปิด"
 youGotNewFollower: "ได้ติดตามคุณ"
-receiveFollowRequest: "คำขอผู้ติดตามที่ได้รับ"
-followRequestAccepted: "ผู้ติดตามได้ตอบรับคำขอร้องของคุณแล้ว"
+receiveFollowRequest: "มีคำขอติดตามส่งมาหา"
+followRequestAccepted: "การติดตามได้รับการอนุมัติแล้ว"
 mention: "กล่าวถึง"
 mentions: "พูดถึง"
-directNotes: "ไดเร็คโน้ต"
+directNotes: "โพสต์แบบไดเร็กต์"
 importAndExport: "นำเข้า / ส่งออก"
 import: "นำเข้า"
-export: "นำออก"
+export: "ส่งออก"
 files: "ไฟล์"
 download: "ดาวน์โหลด"
-driveFileDeleteConfirm: "คุณต้องการลบไฟล์ \"{name}\" ใช่หรือไม่? โน้ตย่อที่แนบมากับไฟล์นี้ก็จะถูกลบไปด้วย"
-unfollowConfirm: "นายแน่ใจแล้วหรอว่าต้องการเลิกติดตาม {name}?"
-exportRequested: "เมื่อคุณได้ร้องขอการส่งออก อาจจะต้องใช้เวลาสักครู่ และจะถูกเพิ่มในไดรฟ์ของคุณเมื่อเสร็จสิ้นแล้ว"
-importRequested: "เมื่อคุณได้ร้องขอการนำเข้า อาจจะต้องใช้เวลาสักครู่นะ"
-lists: "รายการ"
-noLists: "คุณไม่มีลิสต์ใด ๆ"
+driveFileDeleteConfirm: "ต้องการลบไฟล์ “{name}” ใช่ไหม? โน้ตที่แนบมากับไฟล์นี้ก็จะถูกลบไปด้วย"
+unfollowConfirm: "ต้องการเลิกติดตาม {name} ใช่ไหม?"
+exportRequested: "คุณได้ร้องขอการส่งออก อาจใช้เวลาสักครู่ และจะถูกเพิ่มในไดรฟ์ของคุณเมื่อเสร็จสิ้นแล้ว"
+importRequested: "คุณได้ร้องขอการนำเข้า การดำเนินการนี้อาจใช้เวลาสักครู่"
+lists: "รายชื่อ"
+noLists: "คุณไม่มีรายชื่อใดๆ"
 note: " โน้ต"
-notes: "ตัวโน้ต"
+notes: " โน้ต"
 following: "กำลังติดตาม"
 followers: "ผู้ติดตาม"
 followsYou: "ติดตามคุณ"
-createList: "สร้างลิสต์"
-manageLists: "จัดการลิสต์"
+createList: "สร้างรายชื่อ"
+manageLists: "จัดการรายชื่อ"
 error: "ผิดพลาด!"
 somethingHappened: "อุ๊ย ! มีอะไรบางอย่างผิดพลาด"
 retry: "ลองใหม่อีกครั้ง"
@@ -95,74 +95,81 @@ pageLoadError: "เกิดข้อผิดพลาดในการโห
 pageLoadErrorDescription: "โดยปกติแล้วมักจะเกิดจากข้อผิดพลาดของเครือข่ายหรือแคชของเบราว์เซอร์ ลองล้างแคชแล้วลองใหม่อีกครั้งหลังจากรอสักครู่ "
 serverIsDead: "เซิร์ฟเวอร์นี้ไม่มีการตอบสนอง โปรดกรุณารอสักครู่แล้วลองใหม่อีกครั้ง"
 youShouldUpgradeClient: "หากต้องการดูหน้านี้ กรุณาโหลดหน้าใหม่เพื่ออัปเดตไคลเอ็นต์ของคุณ"
-enterListName: "ใส่ชื่อสำหรับรายการลิสต์"
+enterListName: "ป้อนนามเรียกของรายชื่อชุดนี้"
 privacy: "ความเป็นส่วนตัว"
-makeFollowManuallyApprove: "ติดตามคำขอที่ต้องได้รับการอนุมัติ"
+makeFollowManuallyApprove: "อนุมัติคำขอติดตามด้วยตนเอง"
 defaultNoteVisibility: "การมองเห็นที่เป็นค่าเริ่มต้น"
 follow: "ติดตาม"
 followRequest: "ส่งคำขอติดตาม"
 followRequests: "ส่งคำขอติดตาม"
 unfollow: "เลิกติดตาม"
-followRequestPending: "กำลังรอดำเนินการร้องขอติดตาม"
-enterEmoji: "ใส่อีโมจิ"
+followRequestPending: "รออนุมัติคำขอติดตาม"
+enterEmoji: "พิมพ์เอโมจิ"
 renote: "รีโน้ต"
 unrenote: "เลิกรีโน้ต"
 renoted: "รีโน้ตแล้ว"
-cantRenote: "โพสต์นี้ไม่สามารถรีโน้ตไว้ใหม่ได้นะ"
-cantReRenote: "ไม่สามารถรีโน้ตเอาไว้ใหม่ได้นะ"
+cantRenote: "โพสต์นี้ไม่สามารถรีโน้ตใหม่ได้"
+cantReRenote: "รีโน้ตไม่สามารถรีโน้ตซ้ำได้"
 quote: "อ้างอิง"
-inChannelRenote: "รีโน้ตช่องแชลแนลเท่านั้น"
-inChannelQuote: "อ้างช่องเท่านั้น"
-pinnedNote: "โน้ตที่ปักหมุดเอาไว้"
-pinned: "ปักหมุดไปยังโปรไฟล์"
+inChannelRenote: "รีโน้ตในช่องเท่านั้น"
+inChannelQuote: "อ้างอิงในช่องเท่านั้น"
+pinnedNote: "โน้ตที่ปักหมุดไว้"
+pinned: "ปักหมุด"
 you: "คุณ"
 clickToShow: "คลิกเพื่อแสดง"
-sensitive: "เนื้อหาที่ละเอียดอ่อน NSFW"
+sensitive: "เนื้อหาที่ละเอียดอ่อน"
 add: "เพิ่ม"
 reaction: "รีแอคชั่น"
 reactions: "รีแอคชั่น"
-reactionSettingDescription2: "กดลากเพื่อจัดลำดับใหม่ กดคลิกเพื่อลบ กด \"+\" เพื่อเพิ่ม"
-rememberNoteVisibility: "จดจำการตั้งค่าการมองเห็นตัวโน้ต"
-attachCancel: "ลบไฟล์ออกที่แนบมา"
-markAsSensitive: "ทำเครื่องหมายว่าละเอียดอ่อน"
-unmarkAsSensitive: "ยกเลิกทำเครื่องหมายเป็น NSFW"
+emojiPicker: "ตัวจิ้มเอโมจิ"
+pinnedEmojisForReactionSettingDescription: "ตรึงเอโมจิไว้ด้านบนสำหรับรีแอคชั่นอย่างเร่งด่วน"
+pinnedEmojisSettingDescription: "ตรึงเอโมจิไว้ด้านบนสำหรับพิมพ์เอโมจิอย่างเร่งด่วน"
+emojiPickerDisplay: "แสดงตัวจิ้มเอโมจิ"
+overwriteFromPinnedEmojisForReaction: "เขียนทับการตั้งค่ารีแอคชั่น"
+overwriteFromPinnedEmojis: "เขียนทับการตั้งค่าทั่วไป"
+reactionSettingDescription2: "ลากเพื่อจัดลำดับใหม่ คลิกที่เอโมจินั้นเพื่อลบ กด “+” เพื่อเพิ่ม"
+rememberNoteVisibility: "จำการตั้งค่าการมองเห็นโน้ต"
+attachCancel: "ยกเลิกแนบไฟล์"
+deleteFile: "ลบไฟล์ออก"
+markAsSensitive: "ทำเครื่องหมายว่ามีเนื้อหาละเอียดอ่อน"
+unmarkAsSensitive: "ยกเลิกทำเครื่องหมายว่ามีเนื้อหาละเอียดอ่อน"
 enterFileName: "พิมพ์ชื่อไฟล์"
 mute: "ปิดเสียง"
 unmute: "ยกเลิกการปิดเสียง"
 renoteMute: "ปิดเสียงรีโน้ต"
 renoteUnmute: "เปิดเสียง รีโน้ต"
-block: "บล็อค"
-unblock: "เลิกปิดกั้น"
-suspend: "ถูกระงับ"
-unsuspend: "ยกเลิกระงับ"
-blockConfirm: "คุณแน่ใจแล้วเหรอ? ว่าต้องการบล็อกบัญชีนี้"
-unblockConfirm: "คุณแน่ใจแล้วเหรอ? ว่าต้องการปลดบล็อคบัญชีนี้"
-suspendConfirm: "แน่ใจว่าคุณต้องการระงับบัญชีนี้?"
-unsuspendConfirm: "นายแน่ใจแล้วหรอ? ว่าต้องการยกเลิกการระงับบัญชีนี้"
-selectList: "เลือกรายการ"
-editList: "แก้ไขรายการ"
-selectChannel: "เลือกแชนแนล"
+block: "บล็อก"
+unblock: "เลิกบล็อก"
+suspend: "ระงับ"
+unsuspend: "เลิกระงับ"
+blockConfirm: "ต้องการบล็อกบัญชีนี้ใช่ไหม?"
+unblockConfirm: "ต้องการเลิกบล็อกบัญชีนี้ใช่ไหม?"
+suspendConfirm: "ต้องการระงับบัญชีนี้ใช่ไหม?"
+unsuspendConfirm: "ต้องการยกเลิกการระงับบัญชีนี้ใช่ไหม?"
+selectList: "เลือกรายชื่อ"
+editList: "แก้ไขรายชื่อ"
+selectChannel: "เลือกช่อง"
 selectAntenna: "เลือกเสาอากาศ"
 editAntenna: "แก้ไขเสาอากาศ"
 selectWidget: "เลือกวิดเจ็ต"
 editWidgets: "แก้ไขวิดเจ็ต"
 editWidgetsExit: "เรียบร้อย"
-customEmojis: "กำหนดอีโมจิเอง"
-emoji: "อีโมจิ"
+customEmojis: "เอโมจิที่กำหนดเอง"
+emoji: "เอโมจิ"
 emojis: "อีโมจิ"
-emojiName: "ชื่ออิโมจิ"
-emojiUrl: "อิโมจิ URL"
-addEmoji: "แทรกอีโมจิ"
+emojiName: "ชื่อเอโมจิ"
+emojiUrl: "URL ของเอโมจิ"
+addEmoji: "แทรกเอโมจิ"
 settingGuide: "การตั้งค่าที่แนะนำ"
 cacheRemoteFiles: "แคชไฟล์ระยะไกล"
-cacheRemoteFilesDescription: "เมื่อปิดใช้งานการตั้งค่านี้ ไฟล์ระยะไกลนั้นจะถูกโหลดโดยตรงจากอินสแตนซ์ระยะไกล แต่กรณีการปิดใช้งานนี้จะช่วยลดปริมาณการใช้พื้นที่จัดเก็บข้อมูล แต่เพิ่มปริมาณการใช้งาน เพราะเนื่องจากจะไม่มีการสร้างภาพขนาดย่อ"
+cacheRemoteFilesDescription: "หากเปิดใช้งาน ไฟล์ระยะไกลจะถูกแคชไว้ ทำให้แสดงภาพเร็วขึ้น แต่ก็ใช้พื้นที่เก็บข้อมูลของเซิร์ฟเวอร์มากขึ้นเช่นกัน สำหรับขีดจำกัดที่ผู้ใช้ระยะไกลถูกแคชไว้จะขึ้นอยู่กับความจุไดรฟ์ตามบทบาทของเขา เมื่อเกินแล้วไฟล์เก่าจะถูกลบออกและเก็บเป็นลิงก์แทน หากปิดใช้งาน ไฟล์ระยะไกลจะถูกเก็บเป็นลิงก์ตั้งแต่ต้น เราแนะนำให้ตั้งค่า proxyRemoteFiles ใน default.yml เป็น true เพื่อสร้างธัมบ์เนลและปกป้องความเป็นส่วนตัวของผู้ใช้"
 youCanCleanRemoteFilesCache: "คุณสามารถล้างแคชได้โดยคลิกที่ปุ่ม 🗑️ ในมุมมองการจัดการไฟล์"
-cacheRemoteSensitiveFiles: "ไฟล์ระยะไกลที่มีความละเอียดอ่อนแคช"
-cacheRemoteSensitiveFilesDescription: "เมื่อปิดการใช้งานแล้วการตั้งค่านี้ ไฟล์รีโมตที่มีความละเอียดอ่อนนั้นจะถูกโหลดโดยตรงจากอินสแตนซ์ระยะไกลโดยที่ไม่มีการแคช"
-flagAsBot: "ทำเครื่องหมายบอกว่าบัญชีนี้เป็นบอท"
+cacheRemoteSensitiveFiles: "แคชไฟล์ระยะไกลที่มีเนื้อหาละเอียดอ่อน"
+cacheRemoteSensitiveFilesDescription: "เมื่อปิดการใช้งานการตั้งค่านี้ ไฟล์ระยะไกลที่มีเครื่องหมายว่ามีเนื้อหาละเอียดอ่อนนั้นจะถูกโหลดโดยตรงจากอินสแตนซ์ระยะไกลโดยที่ไม่มีการแคช"
+flagAsBot: "ทำเครื่องหมายบอกว่าบัญชีนี้เป็นบอต"
 flagAsBotDescription: "การเปิดใช้งานตัวเลือกนี้หากบัญชีนี้ถูกควบคุมโดยนักเขียนโปรแกรม หรือ ถ้าหากเปิดใช้งาน มันจะทำหน้าที่เป็นแฟล็กสำหรับนักพัฒนารายอื่นๆ และเพื่อป้องกันการโต้ตอบแบบไม่มีที่สิ้นสุดกับบอทตัวอื่นๆ และยังสามารถปรับเปลี่ยนระบบภายในของ Misskey เพื่อปฏิบัติต่อบัญชีนี้เป็นบอท"
-flagAsCat: "ทำเครื่องหมายบอกว่าบัญชีนี้เป็นแมว"
-flagAsCatDescription: "การเปิดใช้งานตัวเลือกนี้เพื่อทำเครื่องหมายบอกว่าบัญชีนี้เป็นแมว"
+flagAsCat: "เมี้ยววววววววววววววว!!!!!!!!!!!"
+flagAsCatDescription: "เหมียวเหมียวเมี้ยว??"
 flagShowTimelineReplies: "แสดงตอบกลับ ในไทม์ไลน์"
 flagShowTimelineRepliesDescription: "แสดงการตอบกลับของผู้ใช้งานไปยังโน้ตของผู้ใช้งานรายอื่นๆในไทม์ไลน์หากได้เปิดเอาไว้"
 autoAcceptFollowed: "อนุมัติคำขอติดตามโดยอัตโนมัติทันที จากผู้ใช้งานที่คุณกำลังติดตาม"
@@ -171,22 +178,22 @@ reloadAccountsList: "รีโหลดรายการบัญชีให
 loginFailed: "การเข้าสู่ระบบไม่สำเร็จ"
 showOnRemote: "ดูบนอินสแตนซ์ระยะไกล"
 general: "ทั่วไป"
-wallpaper: "วอลล์เปเปอร์"
-setWallpaper: "ตั้งวอลเปเปอร์"
-removeWallpaper: "นำวอลเปเปอร์ออก"
+wallpaper: "ภาพพื้นหลัง"
+setWallpaper: "ตั้งค่าภาพพื้นหลัง"
+removeWallpaper: "นำภาพพื้นหลังออก"
 searchWith: "ค้นหา: {q}"
-youHaveNoLists: "คุณไม่มีลิสต์ใด ๆ "
-followConfirm: "คุณแน่ใจแล้วหรอว่าต้องการที่จะติดตาม {name}?"
-proxyAccount: "บัญชี พร็อกซี่"
+youHaveNoLists: "คุณไม่มีรายชื่อใดๆ "
+followConfirm: "ต้องการติดตาม {name} ใช่ไหม?"
+proxyAccount: "บัญชีพร็อกซี่"
 proxyAccountDescription: "บัญชีพร็อกซี่ คือ บัญชีที่จะทำหน้าที่เป็นผู้ติดตามระยะไกลสำหรับผู้ใช้งานที่อยู่ภายใต้ด้วยเงื่อนไขบางอย่าง ยกตัวอย่าง เช่น เมื่อมีผู้ใช้งานนั้นได้เพิ่มผู้ใช้งานจากระยะไกลลงในรายการ แต่กิจกรรมของผู้ใช้ในระยะไกลนั้นจะไม่ถูกส่งไปยังอินสแตนซ์หากไม่มีผู้ใช้งานในพื้นที่ติดตามผู้ใช้รายนั้น ดังนั้นบัญชีพร็อกซีนี้จะติดตามแทน"
 host: "โฮสต์"
 selectUser: "เลือกผู้ใช้งาน"
 recipient: "ผู้รับ"
-annotation: "ความคิดเห็น"
-federation: "เฟดิเวิร์ส"
-instances: "Server"
-registeredAt: "จดทะเบียนที่"
-latestRequestReceivedAt: "ได้รับคำขอล่าสุดไปแล้ว"
+annotation: "หมายเหตุประกอบ"
+federation: "สหพันธ์"
+instances: "อินสแตนซ์"
+registeredAt: "วันที่ลงทะเบียน"
+latestRequestReceivedAt: "คำขอล่าสุดที่ได้รับ"
 latestStatus: "สถานะล่าสุด"
 storageUsage: "พื้นที่จัดเก็บข้อมูลที่ใช้ไป"
 charts: "โดดเด่น"
@@ -194,33 +201,34 @@ perHour: "ทุกชั่วโมง"
 perDay: "ต่อวัน"
 stopActivityDelivery: "หยุดส่งกิจกรรม"
 blockThisInstance: "บล็อกอินสแตนซ์นี้"
-silenceThisInstance: "ปกปิดอินสแตนซ์นี้"
+silenceThisInstance: "ปิดปากอินสแตนซ์นี้"
 operations: "ดำเนินการ"
 software: "ซอฟต์แวร์"
 version: "เวอร์ชั่น"
 metadata: "Metadata"
-withNFiles: "{n} ไฟล์(s)"
+withNFiles: "{n} ไฟล์"
 monitor: "มอนิเตอร์"
 jobQueue: "คิวงาน"
 cpuAndMemory: "ซีพียู และ หน่วยความจำ"
-network: "เน็ตเวิร์ก"
+network: "เครือข่าย"
 disk: "ดิสก์"
-instanceInfo: "ข้อมูล อินสแตนซ์"
+instanceInfo: "ข้อมูลอินสแตนซ์"
 statistics: "สถิติการใช้งาน"
 clearQueue: "ล้างคิว"
-clearQueueConfirmTitle: "คุณแน่ใจแล้วหรอว่าต้องการที่จะล้างคิว?"
-clearQueueConfirmText: "บันทึกย่อที่ยังไม่ได้ส่งที่เหลืออยู่ในคิวนั้นมักจะ ไม่ถูกรวมเข้าด้วยกัน โดยปกติแล้วไม่จำเป็นต้องดำเนินการนี้"
+clearQueueConfirmTitle: "ต้องการล้างคิวใช่ไหม?"
+clearQueueConfirmText: "โพสต์ที่ยังค้างในคิวจะไม่ถูกจัดส่งอีกต่อไป โดยปกติแล้วการดำเนินการนี้ไม่จำเป็น"
 clearCachedFiles: "ล้างแคช"
-clearCachedFilesConfirm: "นายแน่ใจแล้วหรอว่าต้องการที่จะลบไฟล์ระยะไกลที่แคชไว้ทั้งหมด?"
-blockedInstances: "อินสแตนซ์ที่ ถูกบล็อก"
+clearCachedFilesConfirm: "ต้องการลบไฟล์ระยะไกลที่แคชไว้ทั้งหมดใช่ไหม?"
+blockedInstances: "อินสแตนซ์ที่ถูกบล็อก"
 blockedInstancesDescription: "ระบุชื่อโฮสต์ของอินสแตนซ์ที่คุณต้องการบล็อก อินสแตนซ์ที่อยู่ในรายการนั้นจะไม่สามารถพูดคุยกับอินสแตนซ์นี้ได้อีกต่อไป"
-silencedInstances: "ปกปิดอินสแตนซ์นี้แล้ว"
+silencedInstances: "ปิดปากอินสแตนซ์นี้แล้ว"
+silencedInstancesDescription: "ตั้งค่ารายชื่อโฮสต์ของอินสแตนซ์ที่คุณต้องการปิดปาก บัญชีทั้งหมดของอินสแตนซ์ที่อยู่ในรายชื่อนั้นๆ จะถือว่าถูกปิดปากเช่นกัน ทำได้เฉพาะคำขอติดตามเท่านั้น และไม่สามารถกล่าวถึงบัญชีในพื้นที่ได้หากไม่ได้ติดตาม | สิ่งนี้ไม่มีผลต่ออินสแตนซ์ที่ถูกบล็อก"
 muteAndBlock: "ปิดเสียงและบล็อก"
 mutedUsers: "ผู้ใช้ที่ถูกปิดเสียง"
 blockedUsers: "ผู้ใช้ที่ถูกบล็อก"
 noUsers: "ไม่พบผู้ใช้งาน"
 editProfile: "แก้ไขโปรไฟล์"
-noteDeleteConfirm: "นายแน่ใจแล้วหรอว่าต้องการลบโน้ตนี้นะ?"
+noteDeleteConfirm: "ต้องการลบโน้ตนี้ใช่ไหม?"
 pinLimitExceeded: "คุณไม่สามารถปักหมุดโน้ตเพิ่มเติมใดๆได้อีก"
 intro: "การติดตั้ง Misskey เสร็จสิ้นแล้วนะ! โปรดสร้างผู้ใช้งานที่เป็นผู้ดูแลระบบ"
 done: "เสร็จสิ้น"
@@ -228,8 +236,8 @@ processing: "กำลังประมวลผล..."
 preview: "แสดงตัวอย่าง"
 default: "ค่าเริ่มต้น"
 defaultValueIs: "ค่าเริ่มต้น: {value}"
-noCustomEmojis: "ไม่มีอีโมจิ"
-noJobs: "ไม่มีชิ้นงาน"
+noCustomEmojis: "ไม่มีเอโมจิ"
+noJobs: "ไม่มีงาน"
 federating: "สหพันธ์"
 blocked: "ถูกบล็อก"
 suspended: "ถูกระงับ"
@@ -237,42 +245,43 @@ all: "ทั้งหมด"
 subscribing: "สมัครแล้ว"
 publishing: "กำลังเผยแพร่"
 notResponding: "ไม่มีการตอบสนอง"
-instanceFollowing: "กำลังติดตาม บน อินสแตนซ์"
+instanceFollowing: "กำลังติดตามบนอินสแตนซ์"
 instanceFollowers: "ผู้ติดตามของอินสแตนซ์"
 instanceUsers: "ผู้ใช้งานของอินสแตนซ์นี้"
 changePassword: "เปลี่ยนรหัสผ่าน"
 security: "ความปลอดภัย"
-retypedNotMatch: "อินพุตไม่ตรงกันนะ"
+retypedNotMatch: "ทั้งสองป้อนข้อมูลไม่สอดคล้องกัน"
 currentPassword: "รหัสผ่านปัจจุบัน"
 newPassword: "รหัสผ่านใหม่"
 newPasswordRetype: "ใส่รหัสผ่านใหม่อีกครั้ง"
 attachFile: "แนบไฟล์"
-more: "เพิ่มเติม"
+more: "เพิ่มเติม!"
 featured: "ไฮไลท์"
 usernameOrUserId: "ชื่อผู้ใช้หรือรหัสผู้ใช้งาน"
 noSuchUser: "ไม่พบผู้ใช้"
 lookup: "การค้นหา"
 announcements: "ประกาศ"
-imageUrl: "url รูปภาพ"
+imageUrl: "URL รูปภาพ"
 remove: "ลบ"
 removed: "ถูกลบไปแล้ว"
-removeAreYouSure: "นายแน่ใจจริงหรอว่าต้องการที่จะลบออก \"{x}\""
-deleteAreYouSure: "ต้องการลบ {x} หรือไม่คะ?"
-resetAreYouSure: "รีเซ็ตเลยไหม"
+removeAreYouSure: "ต้องการลบ “{x}” ใช่ไหม?"
+deleteAreYouSure: "ต้องการลบ “{x}” ใช่ไหม?"
+resetAreYouSure: "รีเซ็ตเลยไหม?"
+areYouSure: "แน่ใจแล้วใช่ไหมคะ?"
 saved: "บันทึกแล้ว"
 messaging: "แชท"
-upload: "อัพโหลด"
+upload: "อัปโหลด"
 keepOriginalUploading: "เก็บภาพต้นฉบับ"
-keepOriginalUploadingDescription: "บันทึกรูปภาพที่อัพโหลดต้นฉบับตามที่เป็นอยู่ ถ้าหากปิดอยู่ ระบบจะสร้างเวอร์ชั่นที่จะแสดงบนเว็บเมื่ออัพโหลดนะ"
+keepOriginalUploadingDescription: "เก็บภาพต้นฉบับไว้เมื่ออัปโหลดภาพ หากปิด รูปภาพสำหรับการเผยแพร่ทางเว็บจะถูกสร้างขึ้นในเบราว์เซอร์เมื่อทำการอัปโหลด"
 fromDrive: "จากไดรฟ์"
 fromUrl: "จาก URL"
-uploadFromUrl: "อัพโหลดจาก URL"
+uploadFromUrl: "อัปโหลดจาก URL"
 uploadFromUrlDescription: "URL ของไฟล์ที่คุณต้องการอัปโหลด"
-uploadFromUrlRequested: "อัพโหลดที่ร้องขอ"
-uploadFromUrlMayTakeTime: "มันอาจจะต้องใช้เวลาสักครู่จนกว่าการอัพโหลดจะเสร็จสมบูรณ์นะ"
+uploadFromUrlRequested: "ร้องขอการอัปโหลดแล้ว"
+uploadFromUrlMayTakeTime: "การอัปโหลดอาจใช้เวลาสักครู่จึงจะเสร็จสมบูรณ์"
 explore: "สำรวจ"
 messageRead: "อ่านแล้ว"
-noMoreHistory: "ในนั้นไม่มีประวัติอีกต่อไปแล้วนะ"
+noMoreHistory: "ไม่มีประวัติเพิ่มเติม"
 startMessaging: "เริ่มการสนทนา"
 nUsersRead: "อ่านโดย {n}"
 agreeTo: "ฉันยอมรับที่จะ {0}"
@@ -280,9 +289,9 @@ agree: "ยอมรับ"
 agreeBelow: "ฉันยอมรับถึงด้านล่าง"
 basicNotesBeforeCreateAccount: "หมายเหตุสำคัญ"
 termsOfService: "เงื่อนไขการให้บริการ"
-start: "เริ่มต้น​ใช้งาน​"
+start: "เริ่ม"
 home: "หน้าแรก"
-remoteUserCaution: "เนื่องจากผู้ใช้งานรายนี้นั้น มาจากอินสแตนซ์ระยะไกล ข้อมูลที่แสดงดังกล่าวนั้นอาจจะไม่สมบูรณ์ก็ได้นะ"
+remoteUserCaution: "ข้อมูลอาจไม่สมบูรณ์เนื่องจากผู้ใช้รายนี้มาจากอินสแตนซ์ระยะไกล"
 activity: "กิจกรรม"
 images: "รูปภาพ"
 image: "รูปภาพ"
@@ -291,13 +300,13 @@ yearsOld: "{age} ปี"
 registeredDate: "วันที่สมัครสมาชิก"
 location: "ตำแหน่งที่ตั้ง"
 theme: "ธีม"
-themeForLightMode: "ธีมที่จะใช้ในโหมดแสง"
+themeForLightMode: "ธีมที่จะใช้ในโหมดสว่าง"
 themeForDarkMode: "ธีมที่จะใช้ในโหมดมืด"
 light: "สว่าง"
 dark: "มืด"
 lightThemes: "ธีมสว่าง"
 darkThemes: "ธีมมืด"
-syncDeviceDarkMode: "ซิงค์โหมดมืดด้วยการตั้งค่ากับอุปกรณ์"
+syncDeviceDarkMode: "ซิงค์โหมดมืดกับการตั้งค่าอุปกรณ์ของคุณ"
 drive: "ไดรฟ์"
 fileName: "ชื่อไฟล์"
 selectFile: "เลือกไฟล์"
@@ -305,46 +314,47 @@ selectFiles: "เลือกไฟล์"
 selectFolder: "เลือกโฟลเดอร์"
 selectFolders: "เลือกโฟลเดอร์"
 renameFile: "เปลี่ยนชื่อไฟล์"
-folderName: "ชื่อแฟ้ม"
+folderName: "ชื่อโฟลเดอร์"
 createFolder: "สร้างโฟลเดอร์"
 renameFolder: "เปลี่ยนชื่อโฟลเดอร์"
 deleteFolder: "ลบโฟลเดอร์"
+folder: "โฟลเดอร์"
 addFile: "เพิ่มไฟล์"
 emptyDrive: "ไดรฟ์ของคุณว่างเปล่านะ"
 emptyFolder: "โฟลเดอร์นี้ว่างเปล่า"
 unableToDelete: "ไม่สามารถลบออกได้"
-inputNewFileName: "ป้อนชื่อไฟล์ใหม่นะ"
+inputNewFileName: "ป้อนชื่อไฟล์ใหม่"
 inputNewDescription: "กรุณาใส่แคปชั่นใหม่"
-inputNewFolderName: "กรุณาใส่ชื่อโฟลเดอร์ใหม่นะ\n"
-circularReferenceFolder: "โฟลเดอร์ปลายทาง คือ โฟลเดอร์ย่อยของโฟลเดอร์ที่คุณต้องการที่จะย้ายล่ะนะ"
-hasChildFilesOrFolders: "เนื่องจากโฟลเดอร์นี้ไม่ว่างเปล่า จึงไม่สามารถลบได้นะ"
+inputNewFolderName: "กรุณาใส่ชื่อโฟลเดอร์ใหม่"
+circularReferenceFolder: "โฟลเดอร์ปลายทางคือโฟลเดอร์ย่อยของโฟลเดอร์ที่คุณกำลังย้าย"
+hasChildFilesOrFolders: "เนื่องจากโฟลเดอร์นี้ไม่ว่างเปล่า จึงไม่สามารถลบ"
 copyUrl: "คัดลอก URL"
 rename: "เปลี่ยนชื่อ"
 avatar: "ไอคอน"
 banner: "แบนเนอร์"
-displayOfSensitiveMedia: "แสดงผลสื่อละเอียดอ่อน"
-whenServerDisconnected: "สูญเสียการเชื่อมต่อกับเซิร์ฟเวอร์"
-disconnectedFromServer: "ถูกตัดการเชื่อมต่อออกจากเซิร์ฟเวอร์"
+displayOfSensitiveMedia: "แสดงสื่อที่มีเนื้อหาละเอียดอ่อน"
+whenServerDisconnected: "เมื่อสูญเสียการเชื่อมต่อกับเซิร์ฟเวอร์"
+disconnectedFromServer: "การเชื่อมต่อเซิร์ฟเวอร์ถูกตัด"
 reload: "รีโหลด"
 doNothing: "เมิน"
-reloadConfirm: "นายต้องการรีเฟรชไทม์ไลน์หรือป่าว?"
+reloadConfirm: "รีโหลดเลยไหม?"
 watch: "ดู"
 unwatch: "หยุดดู"
 accept: "ยอมรับ"
 reject: "ปฏิเสธ"
 normal: "ปกติ"
-instanceName: "ชื่อ อินสแตนซ์"
+instanceName: "ชื่ออินสแตนซ์"
 instanceDescription: "คำอธิบายอินสแตนซ์"
 maintainerName: "ผู้ดูแล"
-maintainerEmail: "อีเมล์แอดมิน"
-tosUrl: "เงื่อนไขการให้บริการ URL"
+maintainerEmail: "อีเมลผู้ดูแลระบบ"
+tosUrl: "URL เงื่อนไขการให้บริการ"
 thisYear: "ปีนี้"
 thisMonth: "เดือนนี้"
 today: "วันนี้"
 dayX: "{day}"
 monthX: "เดือน {month}"
 yearX: "{year}"
-pages: "หน้า"
+pages: "หน้าเพจ"
 integration: "รวบรวม"
 connectService: "เชื่อมต่อ"
 disconnectService: "ตัดการเชื่อมต่อ"
@@ -353,23 +363,28 @@ enableGlobalTimeline: "เปิดใช้งานไทม์ไลน์ท
 disablingTimelinesInfo: "ผู้ดูแลระบบและผู้ควบคุมจะสามารถเข้าถึงไทม์ไลน์ทั้งหมด ถึงแม้ว่าจะไม่ได้เปิดใช้งานก็ตาม"
 registration: "ลงทะเบียน"
 enableRegistration: "เปิดใช้งานการลงทะเบียนผู้ใช้ใหม่"
-invite: "เชิญชวน"
+invite: "คำเชิญ"
 driveCapacityPerLocalAccount: "ความจุของไดรฟ์ต่อผู้ใช้ภายในเครื่อง"
 driveCapacityPerRemoteAccount: "ความจุของไดรฟ์ต่อผู้ใช้ระยะไกล"
 inMb: "เป็นเมกะไบต์"
 bannerUrl: "URL รูปภาพแบนเนอร์"
 backgroundImageUrl: "URL ภาพพื้นหลัง"
 basicInfo: "ข้อมูลเบื้องต้น"
-pinnedUsers: "ผู้ใช้งานที่ได้รับการปักหมุด"
-pinnedUsersDescription: "ลิสต์ชื่อผู้ใช้โดยคั่นด้วยการขึ้นบรรทัดใหม่เพื่อปักหมุดในแท็บ \"สำรวจ\""
-pinnedPages: "หน้าที่ปักหมุด"
-pinnedPagesDescription: "ป้อนเส้นทางของหน้าที่คุณต้องการตรึงไว้ที่หน้าแรกของอินสแตนซ์นี้ โดยคั่นด้วยตัวแบ่งบรรทัด"
+pinnedUsers: "ผู้ใช้ที่ถูกปักหมุด"
+pinnedUsersDescription: "ป้อนชื่อผู้ใช้ที่คุณต้องการปักหมุดในหน้า “ค้นพบ” ฯลฯ คั่นด้วยการขึ้นบรรทัดใหม่"
+pinnedPages: "หน้าเพจที่ปักหมุด"
+pinnedPagesDescription: "ป้อนเส้นทางของหน้าเพจที่คุณต้องการปักหมุดไว้ที่หน้าแรกของอินสแตนซ์นี้ คั่นด้วยขึ้นบรรทัดใหม่"
 pinnedClipId: "ID ของคลิปที่จะปักหมุด"
 pinnedNotes: "โน้ตที่ปักหมุดไว้"
 hcaptcha: "hCaptcha"
 enableHcaptcha: "เปิดใช้ hCaptcha"
 hcaptchaSiteKey: "คีย์ไซต์"
 hcaptchaSecretKey: "คีย์ลับ"
+mcaptcha: "mCaptcha"
+enableMcaptcha: "เปิดใช้ mCaptcha"
+mcaptchaSiteKey: "คีย์ไซต์"
+mcaptchaSecretKey: "คีย์ลับ"
+mcaptchaInstanceUrl: "URL ของอินสแตนซ์ของ mCaptcha"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "เปิดใช้ reCAPTCHA"
 recaptchaSiteKey: "คีย์ไซต์"
@@ -385,26 +400,26 @@ name: "ชื่อ"
 antennaSource: "แหล่งเสาอากาศ"
 antennaKeywords: "คีย์เวิร์ดที่ควรฟัง"
 antennaExcludeKeywords: "คีย์เวิร์ดที่จะยกเว้น"
-antennaKeywordsDescription: "คั่นด้วยช่องว่างสำหรับเงื่อนไข AND หรือด้วยการขึ้นบรรทัดใหม่สำหรับเงื่อนไข OR นะ"
+antennaKeywordsDescription: "คั่นด้วยช่องว่างสำหรับเงื่อนไข AND หรือด้วยการขึ้นบรรทัดใหม่สำหรับเงื่อนไข OR"
 notifyAntenna: "แจ้งเตือนเกี่ยวกับโน้ตใหม่"
 withFileAntenna: "เฉพาะโน้ตที่มีไฟล์"
-enableServiceworker: "เปิดใช้งาน การแจ้งเตือนแบบพุชสำหรับเบราว์เซอร์ของคุณ"
+enableServiceworker: "เปิดใช้งานการแจ้งเตือนแบบพุชไปยังเบราว์เซอร์ของคุณ"
 antennaUsersDescription: "ระบุหนึ่งชื่อผู้ใช้ต่อบรรทัด"
-caseSensitive: "กรณีที่สำคัญ"
+caseSensitive: "อักษรพิมพ์ใหญ่-พิมพ์เล็กความหมายต่างกัน"
 withReplies: "รวมตอบกลับ"
 connectedTo: "บัญชีดังต่อไปนี้มีการเชื่อมต่อกัน"
 notesAndReplies: "โพสต์และการตอบกลับ"
-withFiles: "รวบรวมไฟล์"
+withFiles: "มีไฟล์"
 silence: "ถูกปิดปาก"
-silenceConfirm: "นายแน่ใจแล้วหรอว่าต้องการที่จะ ปิดปาก ผู้ใช้งานรายนี้?"
+silenceConfirm: "ต้องการปิดปากผู้ใช้รายนี้ใช่ไหม?"
 unsilence: "ยกเลิกการปิดปาก"
-unsilenceConfirm: "นายแน่ใจแล้วหรอว่าต้องการที่จะยกเลิกปิดปากผู้ใช้งานรายนี้?"
+unsilenceConfirm: "ต้องการเลิกปิดปากผู้ใช้รายนี้ใช่ไหม?"
 popularUsers: "ผู้ใช้ที่เป็นที่นิยม"
 recentlyUpdatedUsers: "ผู้ใช้ที่เพิ่งใช้งานล่าสุด"
 recentlyRegisteredUsers: "ผู้ใช้ที่เข้าร่วมใหม่"
 recentlyDiscoveredUsers: "ผู้ใช้ที่เพิ่งค้นพบใหม่"
-exploreUsersCount: "มีผู้ใช้ {จำนวน} ราย"
-exploreFediverse: "สำรวจเฟดดิเวิร์ส"
+exploreUsersCount: "มีผู้ใช้ {count} ราย"
+exploreFediverse: "สำรวจสหพันธ์"
 popularTags: "แท็กยอดนิยม"
 userList: "ลิสต์"
 about: "เกี่ยวกับ"
@@ -419,8 +434,8 @@ moderator: "ผู้ควบคุม"
 moderation: "การกลั่นกรอง"
 moderationNote: "โน้ตการกลั่นกรอง"
 addModerationNote: "เพิ่มโน้ตการกลั่นกรอง"
-moderationLogs: "บันทึกการกลั่นกรอง"
-nUsersMentioned: "กล่าวถึงโดยผู้ใช้ {n} รายนี้"
+moderationLogs: "ปูมการแก้ไข"
+nUsersMentioned: "กล่าวถึงโดยผู้ใช้ {n} ราย"
 securityKeyAndPasskey: "ความปลอดภัยและรหัสผ่าน"
 securityKey: "กุญแจความปลอดภัย"
 lastUsed: "ใช้ล่าสุด"
@@ -429,19 +444,19 @@ unregister: "เลิกติดตาม"
 passwordLessLogin: "เข้าสู่ระบบแบบไม่ใช้รหัสผ่าน"
 passwordLessLoginDescription: "อนุญาตให้เข้าสู่ระบบโดยไม่ต้องใช้รหัสผ่านโดยใช้รหัสรักษาความปลอดภัยหรือรหัสผ่านเท่านั้น"
 resetPassword: "รีเซ็ตรหัสผ่าน"
-newPasswordIs: "รหัสผ่านใหม่คือ \"{password}\""
+newPasswordIs: "รหัสผ่านใหม่คือ “{password}”"
 reduceUiAnimation: "ลดภาพเคลื่อนไหว UI"
-share: "แชร์"
+share: "แบ่งปัน"
 notFound: "ไม่พบหน้าที่ต้องการ"
-notFoundDescription: "ไม่พบหน้าที่สอดคล้องตรงกันกับ URL นี้นะ"
-uploadFolder: "โฟลเดอร์เริ่มต้นสำหรับอัพโหลด"
+notFoundDescription: "ไม่พบหน้าตาม URL ที่ระบุ"
+uploadFolder: "โฟลเดอร์เริ่มต้นสำหรับอัปโหลด"
 markAsReadAllNotifications: "ทำเครื่องหมายการแจ้งเตือนทั้งหมดว่าอ่านแล้ว"
 markAsReadAllUnreadNotes: "ทำเครื่องหมายโน้ตทั้งหมดว่าอ่านแล้ว"
 markAsReadAllTalkMessages: "ทำเครื่องหมายข้อความทั้งหมดว่าอ่านแล้ว"
 help: "ช่วยเหลือ"
 inputMessageHere: "พิมพ์ข้อความที่นี่"
 close: "ปิด"
-invites: "เชิญชวน"
+invites: "คำเชิญ"
 members: "สมาชิก"
 transfer: "ถ่ายโอน"
 title: "หัวข้อ"
@@ -449,15 +464,15 @@ text: "ข้อความ"
 enable: "เปิดใช้งาน"
 next: "ถัด​ไป"
 retype: "พิมพ์รหัสอีกครั้ง"
-noteOf: "โน้ต โดย {user}"
+noteOf: "โน้ตของ {user}"
 quoteAttached: "อ้างอิง"
-quoteQuestion: "นายต้องการที่จะอ้างอิงหรอ?"
-noMessagesYet: "ยังไม่มีข้อความนะ"
+quoteQuestion: "ต้องการที่จะแนบมันเพื่ออ้างอิงใช่ไหม?"
+noMessagesYet: "ยังไม่มีข้อความ"
 newMessageExists: "คุณมีข้อความใหม่"
-onlyOneFileCanBeAttached: "คุณสามารถแนบไฟล์กับข้อความได้เพียงไฟล์เดียวเท่านั้นนะ"
-signinRequired: "กรุณาลงทะเบียนหรือลงชื่อเข้าใช้ก่อนดำเนินการต่อนะ"
-invitations: "เชิญชวน"
-invitationCode: "รหัสคำเชิญ"
+onlyOneFileCanBeAttached: "สามารถแนบไฟล์ได้เพียงไฟล์เดียวต่อ 1 ข้อความ"
+signinRequired: "กรุณาลงทะเบียนหรือลงชื่อเข้าใช้ก่อนดำเนินการต่อ"
+invitations: "คำเชิญ"
+invitationCode: "รหัสเชิญ"
 checking: "Checking"
 available: "พร้อมใช้งาน"
 unavailable: "ไม่พร้อมใช้"
@@ -475,11 +490,11 @@ or: "หรือ"
 language: "ภาษา"
 uiLanguage: "ภาษาอินเทอร์เฟซผู้ใช้งาน"
 aboutX: "เกี่ยวกับ {x}"
-emojiStyle: "สไตล์อิโมจิ"
+emojiStyle: "สไตล์เอโมจิ"
 native: "ภาษาแม่"
 disableDrawer: "อย่าใช้ลิ้นชักสไตล์เมนู"
 showNoteActionsOnlyHover: "แสดงการดำเนินการเฉพาะโน้ตเมื่อโฮเวอร์"
-noHistory: "ไม่มีรายการ"
+noHistory: "ไม่มีประวัติ"
 signinHistory: "ประวัติการเข้าสู่ระบบ"
 enableAdvancedMfm: "เปิดใช้งาน MFM ขั้นสูง"
 enableAnimatedMfm: "เปิดการใช้งาน MFM ด้วยแอนิเมชั่น"
@@ -491,7 +506,7 @@ createAccount: "สร้างบัญชี"
 existingAccount: "บัญชีที่มีอยู่"
 regenerate: "สร้างอีกครั้ง"
 fontSize: "ขนาดตัวอักษร"
-mediaListWithOneImageAppearance: "ความสูงของลิสต์สื่อจะต้องมีรูปภาพเดียวเท่านั้น"
+mediaListWithOneImageAppearance: "ความสูงของรายการสื่อที่มีเพียงรูปเดียว"
 limitTo: "จำกัดไว้ที่ {x}"
 noFollowRequests: "คุณไม่มีคำขอติดตามที่รอดำเนินการ"
 openImageInNewTab: "เปิดรูปภาพในแท็บใหม่"
@@ -509,14 +524,14 @@ promote: "โปรโมท"
 numberOfDays: "จำนวนวัน"
 hideThisNote: "ซ่อนโน้ตนี้"
 showFeaturedNotesInTimeline: "แสดงโน้ตเด่นในไทม์ไลน์"
-objectStorage: "อ็อบเจ็กต์ ที่จัดเก็บ"
-useObjectStorage: "ใช้ อ็อบเจ็กต์ ที่จัดเก็บ"
-objectStorageBaseUrl: "URL ฐาน"
+objectStorage: "การจัดเก็บในรูปแบบอ็อบเจกต์"
+useObjectStorage: "ใช้การจัดเก็บในรูปแบบอ็อบเจกต์"
+objectStorageBaseUrl: "Base URL"
 objectStorageBaseUrlDesc: "URL ที่ใช้เป็นข้อมูลอ้างอิง ระบุ URL ของ CDN หรือ Proxy ถ้าหากคุณใช้อย่างใดอย่างหนึ่ง\n สำหรับการใช้งาน S3 'https://<bucket>.s3.amazonaws.com' และสำหรับ GCS หรือบริการที่เทียบเท่าใช้ 'https://storage.googleapis.com/<bucket>', เป็นต้น"
 objectStorageBucket: "Bucket"
 objectStorageBucketDesc: "โปรดระบุชื่อที่เก็บข้อมูลที่ใช้กับผู้ให้บริการของคุณ"
 objectStoragePrefix: "คำนำหน้า"
-objectStoragePrefixDesc: "ไฟล์ทั้งหมดจะถูกเก็บไว้ภายใต้ไดเร็กทอรีที่มีคำนำหน้านี้นะ"
+objectStoragePrefixDesc: "ไฟล์ทั้งหมดจะถูกเก็บไว้ภายใต้ไดเร็กทอรีที่มีคำนำหน้านี้"
 objectStorageEndpoint: "ปลายทาง"
 objectStorageEndpointDesc: "เว้นว่างไว้หากคุณใช้ AWS S3 หรือระบุปลายทางเป็น '<host>' หรือ '<host>:<port>' ทั้งนี้ขึ้นอยู่กับผู้ให้บริการที่คุณใช้อยู่ด้วย"
 objectStorageRegion: "ภูมิภาค"
@@ -525,12 +540,13 @@ objectStorageUseSSL: "ใช้ SSL"
 objectStorageUseSSLDesc: "ปิดการทำงานนี้ไว้ ถ้าหากคุณจะไม่ใช้ HTTPS สำหรับการเชื่อมต่อ API"
 objectStorageUseProxy: "เชื่อมต่อผ่านพร็อกซี"
 objectStorageUseProxyDesc: "ปิดสิ่งนี้ไว้ถ้าหากคุณจะไม่ใช้ Proxy สำหรับการเชื่อมต่อ API"
-objectStorageSetPublicRead: "ตั้งค่า \"public-read\" ในการอัปโหลด"
+objectStorageSetPublicRead: "ตั้งค่าเป็น “public-read” เมื่ออัปโหลด"
 s3ForcePathStyleDesc: "ถ้าหากเปิดใช้งาน s3ForcePathStyle ชื่อบัคเก็ตนั้นอาจจะต้องรวมอยู่ในเส้นทางของ URL ซึ่งตรงข้ามกับชื่อโฮสต์ของ URL คุณอาจจะต้องเปิดใช้งานการตั้งค่านี้เมื่อใช้บริการต่างๆ เช่น อินสแตนซ์ Minio ที่โฮสต์เองนะ"
-serverLogs: "บันทึกของเซิร์ฟเวอร์"
+serverLogs: "ปูมของเซิร์ฟเวอร์"
 deleteAll: "ลบทั้งหมด"
 showFixedPostForm: "แสดงแบบฟอร์มการโพสต์ที่ด้านบนสุดของไทม์ไลน์"
-showFixedPostFormInChannel: "แสดงแบบฟอร์มกำลังโพสต์ที่ด้านบนของไทม์ไลน์ (แชนแนล)"
+showFixedPostFormInChannel: "แสดงแบบฟอร์มการโพสต์ที่ด้านบนของไทม์ไลน์ (ช่อง)"
+withRepliesByDefaultForNewlyFollowed: "แสดงการตอบกลับจากผู้ใช้ที่คุณเพิ่งติดตามลงไทม์ไลน์ตามค่าเริ่มต้น"
 newNoteRecived: "มีโน้ตใหม่"
 sounds: "เสียง"
 sound: "เสียง"
@@ -538,10 +554,12 @@ listen: "ฟัง"
 none: "ไม่มี"
 showInPage: "แสดงในเพจ"
 popout: "ป๊อปเอาต์"
-volume: "ความดัง"
-masterVolume: "มาสเตอร์วอลุ่ม"
+volume: "ระดับเสียง"
+masterVolume: "ระดับเสียงหลัก"
+notUseSound: "ไม่ใช้เสียง"
+useSoundOnlyWhenActive: "มีเสียงออกเฉพาะตอนกำลังใช้ Misskey อยู่เท่านั้น"
 details: "รายละเอียด"
-chooseEmoji: "เลือกโมจิของเธอ"
+chooseEmoji: "เลือกเอโมจิ"
 unableToProcess: "ไม่สามารถดำเนินการให้เสร็จสิ้นได้"
 recentUsed: "ใช้ล่าสุด"
 install: "ติดตั้ง"
@@ -552,24 +570,24 @@ installedDate: "วันที่ติดตั้ง"
 lastUsedDate: "ใช้งานครั้งล่าสุด"
 state: "สถานะ"
 sort: "เรียงลำดับ"
-ascendingOrder: "เรียงจากน้อยไปมาก"
-descendingOrder: "เรียงจากมากไปน้อย"
-scratchpad: "กระดานทดลอง"
+ascendingOrder: "เรียงลำดับขึ้น"
+descendingOrder: "เรียงลำดับลง"
+scratchpad: "Scratchpad"
 scratchpadDescription: "Scratchpad เป็นการจัดเตรียมสภาพแวดล้อมสำหรับการทดลอง AiScript แต่คุณสามารถเขียน ดำเนินการ และตรวจสอบผลลัพธ์ของการโต้ตอบกับ Misskey มันได้ด้วยนะ"
 output: "เอาท์พุต"
 script: "สคริปต์"
 disablePagesScript: "ปิดการใช้งาน AiScript บนเพจ"
 updateRemoteUser: "อัปเดตข้อมูลผู้ใช้งานระยะไกล"
 unsetUserAvatar: "เลิกตั้งอวตาร"
-unsetUserAvatarConfirm: "คุณแน่ใจหรือไม่ว่าต้องการเลิกตั้งอวตาร?"
+unsetUserAvatarConfirm: "ต้องการเลิกตั้งอวตารใข่ไหม?"
 unsetUserBanner: "เลิกตั้งแบนเนอร์"
-unsetUserBannerConfirm: "คุณแน่ใจหรือไม่ว่าต้องการเลิกตั้งแบนเนอร์เลยมั้ย?"
+unsetUserBannerConfirm: "ต้องการเลิกตั้งแบนเนอร์?"
 deleteAllFiles: "ลบไฟล์ทั้งหมด"
-deleteAllFilesConfirm: "นายแน่ใจแล้วหรอว่าต้องการที่จะลบไฟล์ทั้งหมด?"
+deleteAllFilesConfirm: "ต้องการลบไฟล์ทั้งหมดใช่ไหม?"
 removeAllFollowing: "เลิกติดตามผู้ใช้ที่ติดตามทั้งหมด"
-removeAllFollowingDescription: "การที่คุณดำเนินการนี้จะเลิกติดตามบัญชีทั้งหมดจาก {host} โปรดเรียกใช้คำสั่งสิ่งนี้หากต้องการยกเลิกอินสแตนซ์ เช่น ไม่มีอยู่แล้ว"
+removeAllFollowingDescription: "เลิกติดตามทั้งหมดจาก {host} โปรดเรียกใช้สิ่งนี้เมื่ออินสแตนซ์ดังกล่าวได้สูญหายตายจากไปแล้ว"
 userSuspended: "ผู้ใช้รายนี้ถูกระงับการใช้งาน"
-userSilenced: "ผู้ใช้รายนี้กำลังถูกปิดกั้น"
+userSilenced: "ผู้ใช้รายนี้ถูกปิดปากอยู่"
 yourAccountSuspendedTitle: "บัญชีนี้นั้นถูกระงับ"
 yourAccountSuspendedDescription: "บัญชีนี้ถูกระงับ เนื่องจากละเมิดข้อกำหนดในการให้บริการของเซิร์ฟเวอร์หรืออาจจะละเมิดหลักเกณฑ์ชุมชน หรือ อาจจะโดนร้องเรียนเรื่องการละเมิดลิขสิทธิ์และอื่นๆอย่างต่อเนื่องซ้ำๆ หากคุณคิดว่าไม่ได้ทำผิดจริงๆหรือตัดสินผิดพลาด ได้โปรดกรุณาติดต่อผู้ดูแลระบบหากคุณต้องการทราบเหตุผลโดยละเอียดเพิ่มเติม และขอความกรุณาอย่าสร้างบัญชีใหม่"
 tokenRevoked: "โทเค็นไม่ถูกต้อง"
@@ -582,7 +600,7 @@ addItem: "เพิ่มรายการ"
 rearrange: "จัดใหม่"
 relays: "รีเลย์"
 addRelay: "เพิ่มรีเลย์"
-inboxUrl: "อินบ็อกซ์ URL"
+inboxUrl: "URL ของอินบ็อกซ์"
 addedRelays: "เพิ่มรีเลย์แล้ว"
 serviceworkerInfo: "ต้องเปิดใช้งานสำหรับการแจ้งเตือนแบบพุช"
 deletedNote: "โน้ตที่ถูกลบ"
@@ -599,14 +617,14 @@ description: "รายละเอียด"
 describeFile: "เพิ่มแคปชั่น"
 enterFileDescription: "ใส่แคปชั่น"
 author: "ผู้เขียน"
-leaveConfirm: "คุณมีการเปลี่ยนแปลงที่ไม่ได้บันทึกนะ นายต้องการทิ้งการเปลี่ยนแปลงเหล่านั้นหรอ?"
+leaveConfirm: "มีการเปลี่ยนแปลงที่ยังไม่ได้บันทึก ต้องการละทิ้งมันใช่ไหม?"
 manage: "การจัดการ"
 plugins: "ปลั๊กอิน"
 preferencesBackups: "ตั้งค่าการสำรองข้อมูล"
 deck: "เด็ค"
 undeck: "ออกจากเด็ค"
 useBlurEffectForModal: "ใช้เอฟเฟกต์เบลอสำหรับโมดอล"
-useFullReactionPicker: "ใช้เครื่องมือเลือกปฏิกิริยาขนาดเต็ม"
+useFullReactionPicker: "ใช้ตัวจิ้มรีแอคชั่นอย่างเต็มรูปแบบ"
 width: "ความกว้าง"
 height: "ความสูง"
 large: "ใหญ่"
@@ -614,17 +632,18 @@ medium: "ปานกลาง"
 small: "เล็ก"
 generateAccessToken: "สร้างการเข้าถึงโทเค็น"
 permission: "การอนุญาต"
+adminPermission: "สิทธิ์ของผู้ดูแลระบบ"
 enableAll: "เปิดใช้งานทั้งหมด"
 disableAll: "ปิดการใช้งานทั้งหมด"
 tokenRequested: "ให้สิทธิ์การเข้าถึงบัญชี"
 pluginTokenRequestedDescription: "ปลั๊กอินนี้จะสามารถใช้การอนุญาตที่ตั้งค่าไว้ที่นี่นะ"
 notificationType: "ประเภทการแจ้งเตือน"
 edit: "แก้ไข"
-emailServer: "อีเมล์เซิร์ฟเวอร์"
+emailServer: "อีเมลเซิร์ฟเวอร์"
 enableEmail: "เปิดใช้งานการกระจายอีเมล"
 emailConfigInfo: "ใช้เพื่อยืนยันอีเมลของคุณระหว่างการสมัครหรือถ้าหากคุณลืมรหัสผ่าน"
-email: "อีเมล์"
-emailAddress: "ที่อยู่อีเมล์"
+email: "อีเมล"
+emailAddress: "ที่อยู่อีเมล"
 smtpConfig: "กำหนดค่าเซิร์ฟเวอร์ SMTP"
 smtpHost: "โฮสต์"
 smtpPort: "พอร์ต"
@@ -645,41 +664,42 @@ display: "แสดงผล"
 copy: "คัดลอก"
 metrics: "เมตริก"
 overview: "ภาพรวม"
-logs: "บันทึกข้อมูลระบบ"
+logs: "ปูม"
 delayed: "ดีเลย์"
 database: "ฐานข้อมูล"
-channel: "แชนแนล"
+channel: "ช่อง"
 create: "สร้าง"
 notificationSetting: "ตั้งค่าการแจ้งเตือน"
 notificationSettingDesc: "เลือกประเภทการแจ้งเตือนที่ต้องการจะแสดง"
 useGlobalSetting: "ใช้การตั้งค่าส่วนกลาง"
-useGlobalSettingDesc: "หากเปิดไว้ ระบบจะใช้การตั้งค่าการแจ้งเตือนของบัญชีของคุณ หากปิดอยู่ สามารถทำการกำหนดค่าแต่ละรายการได้นะ"
+useGlobalSettingDesc: "เมื่อเปิดใช้งาน ใช้การตั้งค่าการแจ้งเตือนจากบัญชีคุณ เมื่อปิดใช้งาน สามารถตั้งค่าได้อย่างอิสระ"
 other: "อื่น ๆ"
 regenerateLoginToken: "สร้างโทเค็นการเข้าสู่ระบบอีกครั้ง"
 regenerateLoginTokenDescription: "สร้างโทเค็นใหม่ที่ใช้ภายในระหว่างการเข้าสู่ระบบ โดยตามหลักปกติแล้วการดำเนินการนี้ไม่จำเป็น หากสร้างใหม่ อุปกรณ์ทั้งหมดจะถูกออกจากระบบนะ"
+theKeywordWhenSearchingForCustomEmoji: "คีย์เวิร์ดสำหรับใช้ค้นหาเอโมจิที่กำหนดเอง"
 setMultipleBySeparatingWithSpace: "คั่นหลายรายการด้วยช่องว่าง"
-fileIdOrUrl: "ไฟล์ ID หรือ URL"
+fileIdOrUrl: "ID ของไฟล์ หรือ URL"
 behavior: "พฤติกรรม"
 sample: "ตัวอย่าง"
 abuseReports: "รายงาน"
 reportAbuse: "รายงาน"
 reportAbuseRenote: "รายงานรีโน้ต"
-reportAbuseOf: "รายงาน {ชื่อ}"
+reportAbuseOf: "รายงาน {name}"
 fillAbuseReportDescription: "กรุณากรอกรายละเอียดเกี่ยวกับรายงานนี้ หากเป็นเรื่องเกี่ยวกับโน้ตโดยเฉพาะ ได้โปรดระบุ URL"
 abuseReported: "เราได้ส่งรายงานของคุณไปแล้ว ขอบคุณมากๆนะ"
-reporter: "นักข่าว"
+reporter: "ผู้รายงาน"
 reporteeOrigin: "รายงานต้นทาง"
-reporterOrigin: "นักข่าวต้นทาง"
+reporterOrigin: "แหล่งผู้รายงาน"
 forwardReport: "ส่งต่อรายงานไปยังอินสแตนซ์ระยะไกล"
-forwardReportIsAnonymous: "แทนที่จะเป็นบัญชีของคุณ บัญชีระบบที่ไม่ระบุตัวตนจะแสดงเป็นนักข่าวที่อินสแตนซ์ระยะไกล"
+forwardReportIsAnonymous: "ข้อมูลของคุณจะไม่ปรากฏบนอินสแตนซ์ระยะไกลและปรากฏเป็นบัญชีระบบที่ไม่ระบุชื่อ"
 send: "ส่ง"
 abuseMarkAsResolved: "ทำเครื่องหมายรายงานว่าแก้ไขแล้ว"
 openInNewTab: "เปิดในแท็บใหม่"
 openInSideView: "เปิดในมุมมองด้านข้าง"
 defaultNavigationBehaviour: "พฤติกรรมการนำทางที่เป็นค่าเริ่มต้น"
 editTheseSettingsMayBreakAccount: "การแก้ไขการตั้งค่าเหล่านี้อาจทำให้บัญชีของคุณเสียหายนะ"
-instanceTicker: "ข้อมูลอินสแตนซ์ของบันทึกย่อ"
-waitingFor: "กำลังรอคอย {x}"
+instanceTicker: "ข้อมูลอินสแตนซ์ของโน้ต"
+waitingFor: "กำลังรอ {x}"
 random: "สุ่มค่า"
 system: "ระบบ"
 switchUi: "สลับ UI"
@@ -689,7 +709,7 @@ createNew: "สร้างใหม่"
 optional: "ไม่บังคับ"
 createNewClip: "สร้างคลิปใหม่"
 unclip: "ลบคลิป"
-confirmToUnclipAlreadyClippedNote: "โน้ตนี้เป็นส่วนหนึ่งของคลิป \"{name}\" แล้ว คุณต้องการลบออกจากคลิปนี้แทนอย่างงั้นหรอ?"
+confirmToUnclipAlreadyClippedNote: "โน้ตนี้เป็นส่วนหนึ่งของคลิป “{name}” อยู่แล้ว ต้องการนำมันออกจากคลิปใช่ไหม?"
 public: "สาธารณะ"
 private: "ส่วนตัว"
 i18nInfo: "Misskey กำลังได้รับการแปลเป็นภาษาต่างๆ โดยอาสาสมัคร คุณสามารถช่วยเหลือได้ที่ {link}"
@@ -702,8 +722,8 @@ repliedCount: "จำนวนของการตอบกลับที่
 renotedCount: "จำนวนรีโน้ตที่ได้รับแล้ว"
 followingCount: "จำนวนบัญชีที่ติดตาม"
 followersCount: "จำนวนผู้ติดตาม"
-sentReactionsCount: "จำนวนปฏิกิริยาที่ส่ง"
-receivedReactionsCount: "จำนวนปฏิกิริยาที่ได้รับ"
+sentReactionsCount: "จำนวนรีแอคชั่นที่ส่ง"
+receivedReactionsCount: "จำนวนรีแอคชั่นที่ได้รับ"
 pollVotesCount: "จำนวนโหวตที่ส่งไป"
 pollVotedCount: "จำนวนโหวตที่ได้รับ"
 yes: "ใช่"
@@ -711,17 +731,17 @@ no: "ไม่"
 driveFilesCount: "จำนวนไฟล์ไดรฟ์"
 driveUsage: "การใช้พื้นที่ไดรฟ์"
 noCrawle: "ปฏิเสธการจัดทำดัชนีของโปรแกรมรวบรวมข้อมูล"
-noCrawleDescription: "ขอให้เครื่องมือค้นหาไม่จัดทำดัชนีหน้าโปรไฟล์ บันทึกย่อ หน้า ฯลฯ"
-lockedAccountInfo: "เว้นแต่ว่าคุณจะต้องตั้งค่าการเปิดเผยโน้ตเป็น \"ผู้ติดตามเท่านั้น\" โน้ตย่อของคุณจะปรากฏแก่ทุกคน ถึงแม้ว่าคุณจะเป็นกำหนดให้ผู้ติดตามต้องได้รับการอนุมัติด้วยตนเองก็ตาม"
-alwaysMarkSensitive: "ทำเครื่องหมายเป็น NSFW เป็นค่าเริ่มต้น"
+noCrawleDescription: "ขอให้เครื่องมือค้นหาไม่จัดทำดัชนีหน้าโปรไฟล์ โน้ต หน้าเพจ ฯลฯ"
+lockedAccountInfo: "แม้ว่าการอนุมัติการติดตามถูกเปิดใช้งานอยู่ทุกคนก็ยังคงสามารถเห็นโน้ตของคุณได้ เว้นแต่ว่าคุณจะเปลี่ยนการเปิดเผยโน้ตของคุณเป็น  “เฉพาะผู้ติดตาม”"
+alwaysMarkSensitive: "ทำเครื่องหมายว่ามีเนื้อหาละเอียดอ่อนเป็นค่าเริ่มต้น"
 loadRawImages: "โหลดภาพต้นฉบับแทนการแสดงภาพขนาดย่อ"
 disableShowingAnimatedImages: "ไม่ต้องเล่นภาพเคลื่อนไหว"
-highlightSensitiveMedia: "ไฮไลท์สื่อที่ละเอียดอ่อน"
+highlightSensitiveMedia: "ไฮไลท์สื่อที่มีเนื้อหาละเอียดอ่อน"
 verificationEmailSent: "ส่งอีเมลยืนยันแล้วนะ ได้โปรดกรุณาไปที่ลิงก์ที่รวมไว้เพื่อทำการตรวจสอบให้เสร็จสิ้น"
 notSet: "ไม่ได้ตั้งค่า"
 emailVerified: "อีเมลได้รับการยืนยันแล้ว"
 noteFavoritesCount: "จำนวนโน้ตที่ชื่นชอบ"
-pageLikesCount: "จำนวนเพจที่ชอบ"
+pageLikesCount: "จำนวนเพจที่ถูกใจ"
 pageLikedCount: "จำนวนการกดถูกใจเพจที่ได้รับแล้ว"
 contact: "ติดต่อ"
 useSystemFont: "ใช้ฟอนต์เริ่มต้นของระบบ"
@@ -730,15 +750,15 @@ experimentalFeatures: "ฟังก์ชั่นทดสอบ"
 experimental: "ทดลอง"
 thisIsExperimentalFeature: "นี่คือฟีเจอร์ทดลองนะค่ะ ฟังก์ชันการทำงานบางอย่างอาจเปลี่ยนแปลงได้ และอาจไม่ทำงานหรือไม่เสถียรตามที่ตั้งใจไว้นะ"
 developer: "สำหรับนักพัฒนา"
-makeExplorable: "ทำให้บัญชีมองเห็นใน \"สำรวจ\""
-makeExplorableDescription: "ถ้าหากคุณปิดการทำงานนี้ บัญชีของคุณนั้นจะไม่แสดงในส่วน \"สำรวจ\" นะ"
+makeExplorable: "ทำให้บัญชีมองเห็นใน “สำรวจ”"
+makeExplorableDescription: "ถ้าหากคุณปิดการทำงานนี้ บัญชีของคุณนั้นจะไม่แสดงในส่วน “สำรวจ”"
 showGapBetweenNotesInTimeline: "แสดงช่องว่างระหว่างโพสต์บนไทม์ไลน์"
 duplicate: "ทำซ้ำ"
 left: "ซ้าย"
-center: "ศูนย์กลาง"
+center: "กึ่งกลาง"
 wide: "กว้าง"
 narrow: "ชิด"
-reloadToApplySetting: "การตั้งค่านี้จะมีผลหลังจากโหลดหน้าซ้ำเท่านั้น ต้องการที่จะโหลดใหม่เลยมั้ย"
+reloadToApplySetting: "การตั้งค่านี้จะมีผลหลังจากโหลดหน้าซ้ำเท่านั้น ต้องการที่จะโหลดใหม่เลยไหม?"
 needReloadToApply: "จำเป็นต้องโหลดซ้ำถึงจะมีผลนะ"
 showTitlebar: "แสดงแถบชื่อ"
 clearCache: "ล้างแคช"
@@ -748,57 +768,57 @@ nNotes: "{n} โน้ต"
 sendErrorReports: "ส่งรายงานว่าข้อผิดพลาด"
 sendErrorReportsDescription: "เมื่อเปิดใช้งาน ข้อมูลข้อผิดพลาดโดยรายละเอียดนั้นจะถูกแชร์ให้กับ Misskey เมื่อเกิดปัญหา ซึ่งช่วยปรับปรุงคุณภาพของ Misskey\nซึ่งจะรวมถึงข้อมูล เช่น เวอร์ชั่นของระบบปฏิบัติการ เบราว์เซอร์ที่คุณใช้ กิจกรรมของคุณใน Misskey เป็นต้น"
 myTheme: "ธีมของฉัน"
-backgroundColor: "ภาพพื้นหลัง"
-accentColor: "รูปแบบสี"
+backgroundColor: "สีพื้นหลัง"
+accentColor: "สีหลัก"
 textColor: "สีข้อความ"
 saveAs: "บันทึกเป็น..."
 advanced: "ขั้นสูง"
 advancedSettings: "การตั้งค่าขั้นสูง"
 value: "ค่า"
 createdAt: "สร้างเมื่อ"
-updatedAt: "อัพเดทล่าสุด"
+updatedAt: "อัปเดตล่าสุด"
 saveConfirm: "บันทึกเปลี่ยนแปลงมั้ย?"
 deleteConfirm: "ลบจริงๆเหรอ?"
 invalidValue: "ค่านี้ไม่ถูกต้อง"
 registry: "ทะเบียน"
 closeAccount: "ปิด บัญชี"
 currentVersion: "เวอร์ชั่นปัจจุบัน"
-latestVersion: "รุ่นปัจจุบัน"
+latestVersion: "เวอร์ชั่นล่าสุด"
 youAreRunningUpToDateClient: "คุณกำลังใช้ไคลเอ็นต์เวอร์ชันใหม่ล่าสุดนะ"
 newVersionOfClientAvailable: "มีไคลเอ็นต์เวอร์ชันใหม่กว่าของคุณพร้อมใช้งานนะ"
 usageAmount: "การใช้งาน"
 capacity: "ความจุ"
 inUse: "ใช้แล้ว"
 editCode: "แก้ไขโค้ด"
-apply: "ตกลง"
+apply: "นำไปใช้"
 receiveAnnouncementFromInstance: "รับการแจ้งเตือนจากอินสแตนซ์นี้"
-emailNotification: "การแจ้งเตือนทางอีเมล์"
+emailNotification: "การแจ้งเตือนทางอีเมล"
 publish: "เผยแพร่"
 inChannelSearch: "ค้นหาในช่อง"
-useReactionPickerForContextMenu: "เปิดตัวเลือกปฏิกิริยาเมื่อคลิกขวา"
-typingUsers: "{users} กำลัง/กำลังพิมพ์..."
+useReactionPickerForContextMenu: "คลิกขวาเพื่อเปิดตัวจิ้มรีแอคชั่น"
+typingUsers: "{users} กำลังพิมพ์..."
 jumpToSpecifiedDate: "ข้ามไปยังวันที่เฉพาะเจาะจง"
 showingPastTimeline: "กำลังแสดงผลไทม์ไลน์เก่า"
 clear: "ล้าง"
 markAllAsRead: "ทำเครื่องหมายทั้งหมดว่าอ่านแล้ว"
 goBack: "ย้อนกลับ"
-unlikeConfirm: "ลบไลค์ของคุณออกจริงๆหรอ"
+unlikeConfirm: "ต้องการเลิกถูกใจใช่ไหม?"
 fullView: "มุมมองแบบเต็ม"
 quitFullView: "ออกจากมุมมองแบบเต็ม"
 addDescription: "เพิ่มคำอธิบาย"
-userPagePinTip: "คุณสามารถแสดงผลโน้ตย่อได้ที่นี่โดยเลือก \"ปักหมุดที่โปรไฟล์\" จากเมนูของโน้ตย่อแต่ละรายการนะ"
+userPagePinTip: "ปักหมุดโน้ตให้แสดงที่นี่ได้โดยเลือกเมนู “ปักหมุด” ของโน้ตนั้นๆ"
 notSpecifiedMentionWarning: "โน้ตนี้มีการกล่าวถึงผู้ใช้งานที่ไม่รวมอยู่ในผู้รับ"
 info: "เกี่ยวกับ"
 userInfo: "ข้อมูลผู้ใช้"
 unknown: "ไม่ทราบสถานะ"
 onlineStatus: "สถานะออนไลน์"
 hideOnlineStatus: "ซ่อนสถานะออนไลน์"
-hideOnlineStatusDescription: "การซ่อนสถานะออนไลน์ของคุณช่วยลดความสะดวกของคุณสมบัติบางอย่าง เช่น การค้นหา อ่ะนะ"
+hideOnlineStatusDescription: "การซ่อนสถานะออนไลน์อาจทำให้ฟังก์ชันบางอย่าง เช่น การค้นหา สะดวกน้อยลง"
 online: "ออนไลน์"
 active: "ใช้งานอยู่"
 offline: "ออฟไลน์"
-notRecommended: "ไม่ใช้งาน"
-botProtection: "การป้องกัน Bot (or AI)"
+notRecommended: "ไม่แนะนำ"
+botProtection: "การป้องกัน Bot"
 instanceBlocking: "อินสแตนซ์ที่ถูกบล็อก"
 selectAccount: "เลือกบัญชี"
 switchAccount: "สลับบัญชีผู้ใช้"
@@ -820,19 +840,19 @@ popularPosts: "โพสต์ติดอันดับ"
 shareWithNote: "แบ่งปันด้วยโน้ต"
 ads: "โฆษณา"
 expiration: "กำหนดเวลา"
-startingperiod: "เริ่ม"
-memo: "ข้อควรจำ"
+startingperiod: "เริ่มเมื่อ"
+memo: "เมโม"
 priority: "ลำดับความสำคัญ"
 high: "สูง"
 middle: "ปานกลาง"
 low: "ต่ำ"
-emailNotConfiguredWarning: "ไม่ได้ตั้งค่าที่อยู่อีเมลนะ"
+emailNotConfiguredWarning: "ยังไม่ได้ตั้งค่าที่อยู่อีเมล"
 ratio: "อัตราส่วน"
 previewNoteText: "แสดงตัวอย่าง"
 customCss: "CSS ที่กำหนดเอง"
-customCssWarn: "ควรใช้การตั้งค่านี้เฉพาะต่อเมื่อคุณรู้ว่าการตั้งค่านี้ใช้ทำอะไร การป้อนค่าที่ไม่เหมาะสมอาจทำให้ไคลเอ็นต์หยุดทำงานตามปกติได้นะ"
+customCssWarn: "ควรใช้การตั้งค่านี้เฉพาะต่อเมื่อคุณรู้มันใช้ทำอะไร การตั้งค่าที่ไม่เหมาะสมอาจทำให้ไคลเอ็นต์ไม่สามารถใช้งานได้อย่างถูกต้อง"
 global: "ทั่วโลก"
-squareAvatars: "แสดงผลอวตารสี่เหลี่ยม"
+squareAvatars: "แสดงผลอวตารเป็นสี่เหลี่ยม"
 sent: "ส่ง"
 received: "ได้รับแล้ว"
 searchResult: "ผลการค้นหา"
@@ -849,10 +869,10 @@ usernameInfo: "ชื่อที่ระบุบัญชีของคุ
 aiChanMode: "โหมด Ai "
 devMode: "โหมดนักพัฒนา"
 keepCw: "เก็บคำเตือนเนื้อหา"
-pubSub: "บัญชีผับ/ย่อย"
+pubSub: "บัญชี Pub/Sub"
 lastCommunication: "การสื่อสารครั้งสุดท้ายล่าสุด"
 resolved: "คลี่คลายแล้ว"
-unresolved: "รอการเฉลย"
+unresolved: "ยังไม่ได้รับการแก้ไข"
 breakFollow: "ลบผู้ติดตาม"
 breakFollowConfirm: "ลบผู้ติดตามนี้ออกจริงหรอ?"
 itsOn: "เปิดใช้งาน"
@@ -860,36 +880,38 @@ itsOff: "ปิดใช้งาน"
 on: "เปิด"
 off: "ปิด"
 emailRequiredForSignup: "จำเป็นต้องการใช้ที่อยู่อีเมลสำหรับการสมัคร"
-unread: "ไม่ได้อ่าน"
+unread: "ยังไม่ได้อ่าน"
 filter: "กรอง"
 controlPanel: "แผงควบคุม"
 manageAccounts: "จัดการบัญชี"
-makeReactionsPublic: "ตั้งค่าประวัติปฏิกิริยาต่อสาธารณะ"
-makeReactionsPublicDescription: "การทำเช่นนี้จะทำให้รายการปฏิกิริยาที่ผ่านมาของคุณจะปรากฏต่อสาธารณะนะ"
+makeReactionsPublic: "ตั้งค่าประวัติการรีแอคชั่นเป็นสาธารณะ"
+makeReactionsPublicDescription: "การทำเช่นนี้จะทำให้รายการรีแอคชั่นของคุณที่ผ่านมาทั้งหมดปรากฏต่อสาธารณะ"
 classic: "คลาสสิค"
 muteThread: "ปิดเสียงเธรด"
-unmuteThread: "เปิดเสียงเธรด"
+unmuteThread: "เลิกปิดเสียงเธรด"
+followingVisibility: "การมองเห็นที่เรากำลังติดตาม"
+followersVisibility: "การมองเห็นผู้ที่กำลังติดตามเรา"
 continueThread: "ดูความต่อเนื่องเธรด"
 deleteAccountConfirm: "การดำเนินการนี้จะลบบัญชีของคุณอย่างถาวรเลยนะ แน่ใจหรอดำเนินการ?"
 incorrectPassword: "รหัสผ่านไม่ถูกต้อง"
-voteConfirm: "ยืนยันการโหวต \"{choice}\" มั้ย?"
+voteConfirm: "ต้องการโหวต “{choice}” ใช่ไหม?"
 hide: "ซ่อน"
-useDrawerReactionPickerForMobile: "แสดงผล ตัวเลือกปฏิกิริยาเป็นลิ้นชักบนมือถือ"
-welcomeBackWithName: "ยินดีต้อนรับการกลับมานะคะ, {name}"
-clickToFinishEmailVerification: "กรุณาคลิก [{ok}] เพื่อดำเนินการยืนยันอีเมลให้เสร็จสมบูรณ์นะ"
+useDrawerReactionPickerForMobile: "แสดง ตัวจิ้มรีแอคชั่น เป็นแบบลิ้นชัก เมื่อใช้บนมือถือ"
+welcomeBackWithName: "ยินดีต้อนรับการกลับมานะคะ, คุณ{name}"
+clickToFinishEmailVerification: "กรุณาคลิก [{ok}] เพื่อดำเนินการยืนยันอีเมลให้เสร็จสมบูรณ์"
 overridedDeviceKind: "ประเภทอุปกรณ์"
 smartphone: "สมาร์ทโฟน"
 tablet: "แท็บเล็ต"
 auto: "อัตโนมัติ"
-themeColor: "อินสแตนซ์ Ticker Color"
+themeColor: "สีธีม"
 size: "ขนาด"
 numberOfColumn: "จำนวนคอลัมน์"
 searchByGoogle: "ค้นหา"
-instanceDefaultLightTheme: "ธีมสว่างค่าเริ่มต้นสำหรับอินสแตนซ์"
-instanceDefaultDarkTheme: "ธีมมืดค่าเริ่มต้นอินสแตนซ์"
+instanceDefaultLightTheme: "ธีมสว่างตามค่าเริ่มต้นของอินสแตนซ์"
+instanceDefaultDarkTheme: "ธีมมืดตามค่าเริ่มต้นของอินสแตนซ์"
 instanceDefaultThemeDescription: "ป้อนรหัสธีมในรูปแบบออบเจ็กต์"
 mutePeriod: "ระยะเวลาปิดเสียง"
-period: "สิ้นสุดการสำรวจความคิดเห็น"
+period: "ระยะเวลา"
 indefinitely: "ตลอดไป"
 tenMinutes: "10 นาที"
 oneHour: "1 ชั่วโมง"
@@ -919,28 +941,28 @@ deleteAccount: "ลบบัญชี"
 document: "เอกสาร"
 numberOfPageCache: "จำนวนหน้าเพจที่แคช"
 numberOfPageCacheDescription: "การเพิ่มจำนวนนี้จะช่วยเพิ่มความสะดวกให้กับผู้ใช้งาน แต่จะทำให้เซิร์ฟเวอร์โหลดมากขึ้นและต้องใช้หน่วยความจำมากขึ้นอีกด้วย"
-logoutConfirm: "คุณแน่ใจว่าต้องการออกจากระบบ?"
-lastActiveDate: "ใช้งานล่าสุดที่"
-statusbar: "ไอคอนบนแถบสถานะ"
+logoutConfirm: "ต้องการออกจากระบบใช่ไหม?"
+lastActiveDate: "ใช้งานล่าสุดเมื่อ"
+statusbar: "แถบสถานะ"
 pleaseSelect: "ตัวเลือก"
-reverse: "ย้อนกลับ"
+reverse: "พลิก"
 colored: "สี"
-refreshInterval: "รอบการอัพเดต"
+refreshInterval: "ความถี่ในการอัปเดต"
 label: "ป้ายชื่อ"
 type: "รูปแบบ"
 speed: "ความเร็ว"
 slow: "ช้า"
 fast: "เร็ว"
-sensitiveMediaDetection: "การตรวจจับของสื่อ NSFW"
+sensitiveMediaDetection: "การตรวจจับสื่อที่มีเนื้อหาละเอียดอ่อน"
 localOnly: "เฉพาะท้องถิ่น"
-remoteOnly: "รีโมทเท่านั้น"
+remoteOnly: "ระยะไกลเท่านั้น"
 failedToUpload: "การอัปโหลดล้มเหลว"
 cannotUploadBecauseInappropriate: "ไม่สามารถอัปโหลดไฟล์นี้ได้เนื่องจากระบบตรวจพบบางส่วนของไฟล์ว่านี้อาจจะเป็น NSFW"
-cannotUploadBecauseNoFreeSpace: "การอัปโหลดนั้นล้มเหลวเนื่องจากไม่มีความจุของไดรฟ์"
+cannotUploadBecauseNoFreeSpace: "ไม่สามารถอัปโหลดได้เนื่องจากไม่มีพื้นที่ว่างในไดรฟ์เหลือแล้ว"
 cannotUploadBecauseExceedsFileSizeLimit: "ไม่สามารถอัปโหลดไฟล์นี้ได้แล้วเนื่องจากเกินขีดจำกัดของขนาดไฟล์แล้ว"
 beta: "เบต้า"
-enableAutoSensitive: "ทำเครื่องหมาย NSFW อัตโนมัติ"
-enableAutoSensitiveDescription: "อนุญาตให้ตรวจหาและทำเครื่องหมายสื่อ NSFW โดยอัตโนมัติผ่านการเรียนรู้ของเครื่องหากเป็นไปได้ แม้ว่าตัวเลือกนี้จะถูกปิดใช้งาน แต่ก็สามารถเปิดใช้งานได้ทั้งอินสแตนซ์นี้"
+enableAutoSensitive: "ทำเครื่องหมายว่ามีเนื้อหาที่ละเอียดอ่อนโดยอัตโนมัติ"
+enableAutoSensitiveDescription: "อนุญาตให้ตรวจหาและทำเครื่องหมายสื่อว่ามีเนื้อหาโดยละเอียดอ่อนโดยอัตโนมัติ ผ่าน Machine Learning หากเป็นไปได้ แม้ว่าคุณจะปิดคุณสมบัตินี้ ก็อาจถูกตั้งค่าโดยอัตโนมัติ ทั้งนี้ขึ้นอยู่กับเซิร์ฟเวอร์"
 activeEmailValidationDescription: "เปิดใช้งานการตรวจสอบที่อยู่อีเมลให้มีความเข้มงวดยิ่งขึ้น ซึ่งอาจจะรวมไปถึงการตรวจสอบที่อยู่อีเมล์ที่ใช้แล้วทิ้งและโดยให้พิจารณาว่าสามารถสื่อสารด้วยได้หรือไม่ เมื่อไม่เลือกระบบจะตรวจสอบเฉพาะรูปแบบของอีเมลเท่านั้น"
 navbar: "แถบนำทาง"
 shuffle: "สลับ"
@@ -952,32 +974,33 @@ unsubscribePushNotification: "ปิดการแจ้งเตือนแ
 pushNotificationAlreadySubscribed: "การแจ้งเตือนแบบพุชได้เปิดใช้งานแล้ว"
 pushNotificationNotSupported: "เบราว์เซอร์หรืออินสแตนซ์ของคุณนั้นไม่รองรับการแจ้งเตือนแบบพุช"
 sendPushNotificationReadMessage: "ลบการแจ้งเตือนแบบพุชเมื่ออ่านการแจ้งเตือนหรือข้อความที่เกี่ยวข้องแล้ว"
-sendPushNotificationReadMessageCaption: "การแจ้งเตือนที่มีข้อความ \"{emptyPushNotificationMessage}\" จะแสดงขึ้นมาในช่วงระยะเวลาสั้นๆ การดำเนินการนี้อาจทำให้เพิ่มการใช้งานแบตเตอรี่ของอุปกรณ์ถ้าหากมีนะ"
-windowMaximize: "ขยายใหญ่สุดแล้ว"
+sendPushNotificationReadMessageCaption: "อาจทำให้อุปกรณ์ของคุณใช้พลังงานมากขึ้น"
+windowMaximize: "ขยายใหญ่สุด"
 windowMinimize: "ย่อเล็กที่สุด"
 windowRestore: "เลิกทำ"
-caption: "รายละเอียด"
+caption: "คำอธิบาย"
 loggedInAsBot: "ล็อกอินเป็นบอตอยู่ในขณะนี้"
 tools: "เครื่องมือ"
 cannotLoad: "ไม่สามารถโหลดได้"
 numberOfProfileView: "มุมมองโปรไฟล์"
-like: "ชื่นชอบ"
-unlike: "ไม่ชอบ"
-numberOfLikes: "จำนวนไลค์"
+like: "ถูกใจ!"
+unlike: "เลิกถูกใจ"
+numberOfLikes: "จำนวนยอดถูกใจ"
 show: "แสดงผล"
 neverShow: "ไม่ต้องแสดงข้อความนี้อีก"
 remindMeLater: "ไว้ครั้งหน้าแล้วกัน"
-didYouLikeMisskey: "คุณเคยชอบ Misskey ไหม?"
+didYouLikeMisskey: "คุณชอบ Misskey ไหม?"
 pleaseDonate: "Misskey เป็นซอฟต์แวร์ฟรีที่ใช้งานโดย {host} เราขอขอบคุณการสนับสนุนของคุณอย่างสูงเพื่อให้การพัฒนา Misskey สามารถดำเนินต่อไปได้!"
+correspondingSourceIsAvailable: "ซอร์สโค้ดที่เกี่ยวข้องมีอยู่ที่ {anchor}"
 roles: "บทบาท"
 role: "บทบาท"
 noRole: "ไม่พบบทบาท"
 normalUser: "ผู้ใช้มาตรฐาน"
 undefined: "ไม่ได้กำหนด"
-assign: "กำหนด"
-unassign: "ยังไม่มอบหมาย"
+assign: "มอบหมาย"
+unassign: "เลิกมอบหมาย"
 color: "สี"
-manageCustomEmojis: "จัดการอีโมจิแบบกำหนดเอง"
+manageCustomEmojis: "จัดการเอโมจิที่กำหนดเอง"
 manageAvatarDecorations: "จัดการตกแต่งอวตาร"
 youCannotCreateAnymore: "คุณถึงขีดจํากัดการสร้างแล้วนะ"
 cannotPerformTemporary: "ไม่สามารถใช้การได้ชั่วคราว"
@@ -992,33 +1015,38 @@ achievements: "ความสำเร็จ"
 gotInvalidResponseError: "การตอบสนองเซิร์ฟเวอร์ไม่ถูกต้อง"
 gotInvalidResponseErrorDescription: "เซิร์ฟเวอร์อาจไม่สามารถเข้าถึงได้หรืออาจจะกำลังอยู่ในระหว่างปรับปรุง กรุณาลองใหม่อีกครั้งในภายหลังนะคะ"
 thisPostMayBeAnnoying: "โน้ตนี้อาจจะเป็นการรบกวนผู้อื่นนะคะ"
-thisPostMayBeAnnoyingHome: "โพสต์ไปยังบ้านไทม์ไลน์"
+thisPostMayBeAnnoyingHome: "โพสต์ไปยังไทม์ไลน์หน้าแรก"
 thisPostMayBeAnnoyingCancel: "เลิก"
 thisPostMayBeAnnoyingIgnore: "โพสต์ยังไงก็แล้วแต่"
-collapseRenotes: "ยุบ renotes ที่คุณได้เห็นแล้ว"
+collapseRenotes: "ยุบรีโน้ตที่คุณเคยเห็นแล้ว"
 internalServerError: "เซิร์ฟเวอร์ภายในเกิดข้อผิดพลาด"
 internalServerErrorDescription: "เซิร์ฟเวอร์รันค้นพบข้อผิดพลาดที่ไม่คาดคิด"
 copyErrorInfo: "คัดลอกรายละเอียดข้อผิดพลาด"
 joinThisServer: "ลงชื่อสมัครใช้ในอินสแตนซ์นี้"
 exploreOtherServers: "มองหาอินสแตนซ์อื่น"
-letsLookAtTimeline: "ลองดูที่ไทม์ไลน์"
+letsLookAtTimeline: "มาดูไทม์ไลน์กัน"
 disableFederationConfirm: "ปิดใช้งานสหพันธ์จริงๆหรอแน่ใจแล้วนะ?"
 disableFederationConfirmWarn: "โพสต์จะยังคงเป็นสาธารณะต่อไป เว้นแต่จะตั้งค่าเป็นอย่างอื่น"
 disableFederationOk: "ปิดการใช้งาน"
-invitationRequiredToRegister: "อินสแตนซ์นี้เป็นแบบรับเชิญเท่านั้น คุณต้องป้อนรหัสเชิญ เพื่องลงทะเบียนเข้าใช้งาน"
+invitationRequiredToRegister: "อินสแตนซ์นี้เป็นแบบรับเชิญ เฉพาะผู้ที่มีรหัสเชิญเท่านั้นที่สามารถลงทะเบียนได้"
 emailNotSupported: "อินสแตนซ์นี้ไม่รองรับการส่งอีเมล"
 postToTheChannel: "โพสต์ลงช่อง"
 cannotBeChangedLater: "สิ่งนี้ไม่สามารถเปลี่ยนแปลงได้ในภายหลังนะ"
 reactionAcceptance: "การยอมรับรีแอคชั่น"
-likeOnly: "ที่ชอบเท่านั้น"
-likeOnlyForRemote: "ไลค์สำหรับอินสแตนซ์ระยะไกลเท่านั้น"
-nonSensitiveOnly: "ไม่มีความอ่อนไหวเท่านั้น"
-nonSensitiveOnlyForLocalLikeOnlyForRemote: "ไม่มีความอ่อนไหวเท่านั้น (เฉพาะไลค์จากระยะไกลเท่านั้น)"
+likeOnly: "ที่ถูกใจเท่านั้น"
+likeOnlyForRemote: "ทั้งหมด (เฉพาะการถูกใจจากอินสแตนซ์ระยะไกล)"
+nonSensitiveOnly: "เฉพาะไม่มีเนื้อหาละเอียดอ่อน"
+nonSensitiveOnlyForLocalLikeOnlyForRemote: "เฉพาะไม่มีเนื้อหาละเอียดอ่อน (เฉพาะการถูกใจจากระยะไกลเท่านั้น)"
 rolesAssignedToMe: "บทบาทที่ได้รับมอบหมายให้ฉัน"
 resetPasswordConfirm: "รีเซ็ตรหัสผ่านของคุณจริงๆหรอ?"
-sensitiveWords: "คำที่ละเอียดอ่อน"
+sensitiveWords: "คำที่มีเนื้อหาละเอียดอ่อน"
 sensitiveWordsDescription: "การเปิดเผยโน้ตทั้งหมดที่มีคำที่กำหนดค่าไว้จะถูกตั้งค่าเป็น \"หน้าแรก\" โดยอัตโนมัติ คุณยังสามารถแสดงหลายรายการได้โดยแยกรายการโดยใช้ตัวแบ่งบรรทัดได้นะ"
 sensitiveWordsDescription2: "การใช้ช่องว่างนั้นอาจจะสร้างนิพจน์ AND และคำหลักที่มีเครื่องหมายทับล้อมรอบจะเปลี่ยนเป็นนิพจน์ทั่วไปนะ"
+prohibitedWords: "คำต้องห้าม"
+prohibitedWordsDescription: "จะแจ้งเตือนว่าเกิดข้อผิดพลาดเมื่อพยายามโพสต์โน้ตที่มีคำที่กำหนดไว้ สามารถตั้งได้หลายคำด้วยการขึ้นบรรทัดใหม่"
+prohibitedWordsDescription2: "การใช้ช่องว่างนั้นอาจจะสร้างนิพจน์ AND และคำหลักที่มีเครื่องหมายทับล้อมรอบจะเปลี่ยนเป็นนิพจน์ทั่วไปนะ"
+hiddenTags: "แฮชแท็กที่ซ่อนอยู่"
+hiddenTagsDescription: "เลือกแท็กที่จะไม่แสดงในรายการเทรนด์ สามารถลงทะเบียนหลายแท็กได้โดยขึ้นบรรทัดใหม่"
 notesSearchNotAvailable: "การค้นหาโน้ตไม่พร้อมใช้งาน"
 license: "ใบอนุญาต"
 unfavoriteConfirm: "ลบออกจากรายการโปรดแน่ใจหรอ?"
@@ -1029,21 +1057,24 @@ retryAllQueuesConfirmTitle: "ลองใหม่ทั้งหมดจริ
 retryAllQueuesConfirmText: "สิ่งนี้จะเพิ่มการโหลดเซิร์ฟเวอร์ชั่วคราวนะ"
 enableChartsForRemoteUser: "สร้างแผนภูมิข้อมูลผู้ใช้ระยะไกล"
 enableChartsForFederatedInstances: "สร้างแผนภูมิข้อมูลอินสแตนซ์ระยะไกล"
-showClipButtonInNoteFooter: "เพิ่ม \"คลิป\" เพื่อบันทึกเมนูการทำงาน"
-reactionsDisplaySize: "รีแอคชั่นแสดงผลขนาด"
-noteIdOrUrl: "โน้ต ID หรือ URL"
+showClipButtonInNoteFooter: "เพิ่ม “คลิป” ไปยังเมนูสั่งการของโน้ต"
+reactionsDisplaySize: "ขนาดของรีแอคชั่น"
+limitWidthOfReaction: "จำกัดความกว้างสูงสุดของรีแอคชั่นและแสดงให้เล็กลง"
+noteIdOrUrl: "ID ของโน้ต หรือ URL"
 video: "วีดีโอ"
 videos: "วีดีโอ"
+audio: "เสียง"
+audioFiles: "เสียง"
 dataSaver: "ประหยัดข้อมูล"
-accountMigration: "การโยกย้ายบัญชี"
+accountMigration: "โยกย้ายบัญชี"
 accountMoved: "ผู้ใช้รายนี้ได้ย้ายไปยังบัญชีใหม่แล้ว:"
 accountMovedShort: "บัญชีนี้ถูกโอนย้ายไปแล้วค่ะ"
-operationForbidden: "ห้ามดำเนินการ"
+operationForbidden: "การดำเนินการถูกห้าม"
 forceShowAds: "แสดงโฆษณาเสมอ"
-addMemo: "เพิ่มมีโม"
-editMemo: "แก้ไขมีโม"
-reactionsList: "ปฏิกิริยา"
-renotesList: "Renotes รีโน้ต"
+addMemo: "เพิ่มเมโม"
+editMemo: "แก้ไขเมโม"
+reactionsList: "รายการรีแอคชั่น"
+renotesList: "รายการรีโน้ต"
 notificationDisplay: "การแจ้งเตือน"
 leftTop: "บนซ้าย"
 rightTop: "บนขวา"
@@ -1051,96 +1082,112 @@ leftBottom: "ล่างซ้าย"
 rightBottom: "ล่างขวา"
 stackAxis: "ทิศทางการซ้อน"
 vertical: "แนวตั้ง"
-horizontal: "ด้านข้าง"
+horizontal: "แนวนอน"
 position: "ตำแหน่ง"
-serverRules: "กฎของเซิฟเวอร์"
+serverRules: "กฎของเซิร์ฟเวอร์"
 pleaseConfirmBelowBeforeSignup: "โปรดยืนยันที่ด้านล่างก่อนสมัครใช้งาน"
 pleaseAgreeAllToContinue: "คุณต้องยอมรับทุกช่องตรงด้านบนเพื่อดำเนินการต่อค่ะ"
 continue: "ดำเนินการต่อ"
 preservedUsernames: "ชื่อผู้ใช้ที่สงวนไว้"
-preservedUsernamesDescription: "ลิสต์ชื่อผู้ใช้ที่จะสำรองโดยคั่นด้วยการแบ่งบรรทัดนั้น เพราะสิ่งเหล่านี้จะไม่สามารถทำได้ในระหว่างการสร้างบัญชีตามปกติ บัญชีที่มีอยู่แล้วนั้นโดยใช้ชื่อผู้ใช้เหล่านี้จะไม่ได้รับผลกระทบอะไร"
+preservedUsernamesDescription: "ระบุชื่อผู้ใช้ที่จะสงวนชื่อไว้ คั่นด้วยการขึ้นบรรทัดใหม่ ชื่อผู้ใช้ที่ระบุที่นี่จะไม่สามารถใช้งานได้อีกต่อไปเมื่อสร้างบัญชีใหม่ ยกเว้นเมื่อผู้ดูแลระบบสร้างบัญชี นอกจากนี้ บัญชีที่มีอยู่แล้วจะไม่ได้รับผลกระทบ"
 createNoteFromTheFile: "เรียบเรียงโน้ตจากไฟล์นี้"
 archive: "เก็บถาวร"
-channelArchiveConfirmTitle: "เก็บถาวรจริงๆ {name} มั้ย?"
-channelArchiveConfirmDescription: "ช่องที่ถูกเก็บถาวรแล้วนั้นจะไม่ปรากฏในรายการช่องหรือผลการค้นหานั้นอีกต่อไปไม่สามารถเพิ่มโพสต์ใหม่ได้อีกต่อไปนะ"
+channelArchiveConfirmTitle: "ต้องการเก็บถาวรเจ้า {name} ใช่ไหม?"
+channelArchiveConfirmDescription: "เมื่อเก็บถาวรแล้ว จะไม่ปรากฏในรายการช่องหรือผลการค้นหาอีกต่อไป และจะไม่สามารถโพสต์ใหม่ได้อีกต่อไป"
 thisChannelArchived: "ช่องนี้ถูกเก็บถาวรแล้วนะ"
 displayOfNote: "การแสดงโน้ต"
 initialAccountSetting: "ตั้งค่าโปรไฟล์"
 youFollowing: "ติดตามแล้ว"
-preventAiLearning: "ปฏิเสธการใช้งาน ในการเรียนรู้ของเครื่อง (Generative AI)"
-preventAiLearningDescription: "การส่งคำร้องขอโปรแกรมรวบรวมข้อมูลไม่ให้ใช้ข้อความที่โพสต์หรือรูปภาพ ฯลฯ ในชุดข้อมูลแมชชีนเลิร์นนิง (Predictive / Generative AI) สิ่งนี้นั้นทำได้โดยการเพิ่มแฟล็กการตอบสนอง \"noai\" HTML ให้กับเนื้อหาที่เกี่ยวข้อง แต่อย่างไรก็ตามแล้ว การป้องกันโดยสมบูรณ์นั้นไม่สามารถทำได้ผ่านแฟล็กนี้เนื่องจากอาจจะทำให้ถูกเพิกเฉยได้"
+preventAiLearning: "ปฏิเสธการเรียนรู้ด้วย generative AI"
+preventAiLearningDescription: "ส่งคำร้องขอไม่ให้ใช้ ข้อความในโน้ตที่โพสต์, หรือเนื้อหารูปภาพ ฯลฯ ในการเรียนรู้ของเครื่อง(machine learning) / Predictive AI / Generative AI โดยการเพิ่มแฟล็ก “noai” ลง HTML-Response ให้กับเนื้อหาที่เกี่ยวข้อง แต่ทั้งนี้ ไม่ได้ป้องกัน AI จากการเรียนรู้ได้อย่างสมบูรณ์ เนื่องจากมี AI บางตัวเท่านั้นที่จะเคารพคำขอดังกล่าว"
 options: "ตัวเลือกบทบาท"
 specifyUser: "ผู้ใช้เฉพาะ"
 failedToPreviewUrl: "ไม่สามารถดูตัวอย่างได้"
 update: "อัปเดต"
-rolesThatCanBeUsedThisEmojiAsReaction: "บทบาทที่สามารถใช้อิโมจินี้เป็นรีแอคชั่นได้"
-rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "ถ้าหากไม่ได้ระบุบทบาท ทุกคนนั้นก็สามารถใช้อิโมจินี้เป็นการแสดงความรู้สึกได้นะ"
+rolesThatCanBeUsedThisEmojiAsReaction: "บทบาทที่สามารถใช้เอโมจินี้เป็นรีแอคชั่นได้"
+rolesThatCanBeUsedThisEmojiAsReactionEmptyDescription: "ถ้าหากไม่ได้ระบุบทบาท ใคร ๆ ก็สามารถใช้เอโมจินี้เพื่อรีแอคชั่นได้"
 rolesThatCanBeUsedThisEmojiAsReactionPublicRoleWarn: "บทบาทเหล่านี้ต้องเป็นสาธารณะ"
-cancelReactionConfirm: "ต้องการลบรีแอคชั่นของคุณจริงๆหรอ?"
-changeReactionConfirm: "ต้องการเปลี่ยนรีแอคชั่นของคุณจริงๆหรอ?"
+cancelReactionConfirm: "ต้องการลบรีแอคชั่นใช่ไหม?"
+changeReactionConfirm: "ต้องการเปลี่ยนรีแอคชั่นใช่ไหม?"
 later: "ไว้ทีหลัง"
 goToMisskey: "ถึง Misskey"
-additionalEmojiDictionary: "พจนานุกรมอีโมจิเพิ่มเติม"
+additionalEmojiDictionary: "พจนานุกรมเอโมจิเพิ่มเติม"
 installed: "ติดตั้งแล้ว"
 branding: "แบรนดิ้ง"
 enableServerMachineStats: "เผยแพร่สถานะฮาร์ดแวร์ของเซิร์ฟเวอร์"
 enableIdenticonGeneration: "เปิดใช้งานผู้ใช้สร้างตัวระบุ"
 turnOffToImprovePerformance: "การปิดส่วนนี้สามารถเพิ่มประสิทธิภาพได้"
-createInviteCode: "สร้างคำเชิญ"
+createInviteCode: "สร้างรหัสเชิญ"
 createWithOptions: "สร้างด้วยตัวเลือก"
-createCount: "จำนวนการเชิญ"
-inviteCodeCreated: "สร้างคำเชิญแล้ว"
-inviteLimitExceeded: "คุณสร้างคำเชิญเกินถึงขีดจำกัดแล้วนะ"
-createLimitRemaining: "ขีดจำกัดการเชิญ: {limit} ที่เหลืออยู่"
-inviteLimitResetCycle: "ขีดจำกัดนี้จะถูกรีเซ็ตเป็น {limit} ที่ {time}."
+createCount: "จำนวนรหัสเชิญ"
+inviteCodeCreated: "สร้างรหัสเชิญแล้ว"
+inviteLimitExceeded: "จำนวนรหัสเชิญที่สามารถสร้างได้ถึงขีดจำกัดแล้ว"
+createLimitRemaining: "รหัสเชิญที่สามารถสร้างได้: เหลืออยู่ {limit} รหัส"
+inviteLimitResetCycle: "สามารถสร้างรหัสเชิญได้อีกสูงสุด {limit} รหัส ภายใน {time}"
 expirationDate: "วันที่หมดอายุ"
 noExpirationDate: "ไม่มีหมดอายุ"
-inviteCodeUsedAt: "รหัสคำเชิญใช้แล้วที่"
-registeredUserUsingInviteCode: "ใช้คำเชิญแล้วโดย"
+inviteCodeUsedAt: "วันเวลาที่ใช้รหัสเชิญ"
+registeredUserUsingInviteCode: "ผู้ใช้ที่ใช้รหัสเชิญ"
 waitingForMailAuth: "กำลังรอการยืนยันอีเมล"
-inviteCodeCreator: "สร้างการเชิญแล้วโดย"
-usedAt: "ใช้แล้วที่"
-unused: "ไม่ใช้แล้ว"
-used: "ใช้แล้ว"
+inviteCodeCreator: "ผู้ใช้ที่สร้างรหัสเชิญ"
+usedAt: "วันเวลาที่ถูกใช้"
+unused: "ยังไม่ได้ใช้"
+used: "ถูกใช้แล้ว"
 expired: "หมดอายุแล้ว"
-doYouAgree: "ยอมรับมั้ย?"
+doYouAgree: "ยอมรับไหม?"
 beSureToReadThisAsItIsImportant: "กรุณาอ่านข้อมูลที่สำคัญอันนี้"
-iHaveReadXCarefullyAndAgree: "ฉันได้อ่านข้อความ \"{x}\" และยินยอม"
+iHaveReadXCarefullyAndAgree: "ฉันได้อ่านและยินยอมเนื้อหาของ “{x}”"
 dialog: "ไดอะล็อก"
 icon: "ไอคอน"
 forYou: "สำหรับคุณ"
 currentAnnouncements: "ประกาศในปัจจุบัน"
 pastAnnouncements: "ประกาศที่ผ่านมา"
 youHaveUnreadAnnouncements: "มีการประกาศที่ยังไม่ได้อ่าน"
+useSecurityKey: "โปรดปฏิบัติตามคำแนะนำของเบราว์เซอร์หรืออุปกรณ์ของคุณเพื่อใช้ security key หรือ passkey"
 replies: "ตอบกลับ"
 renotes: "รีโน้ต"
 loadReplies: "แสดงการตอบกลับ"
 loadConversation: "แสดงบทสนทนา"
-pinnedList: "รายการที่ปักหมุดไว้แล้ว"
-keepScreenOn: "เปิดหน้าจอไว้"
+pinnedList: "รายชื่อที่ปักหมุดไว้"
+keepScreenOn: "เปิดหน้าจออุปกรณ์ค้างไว้"
+verifiedLink: "ความเป็นเจ้าของลิงก์ได้รับการยืนยันแล้ว"
 notifyNotes: "แจ้งเตือนเกี่ยวกับโพสต์ใหม่"
 unnotifyNotes: "หยุดการแจ้งเตือนเกี่ยวกับโน้ตใหม่"
 authentication: "การตรวจสอบสิทธิ์"
-authenticationRequiredToContinue: "กรุณาตรวจสอบการรับรองความถูกต้องเพื่อดำเนินการต่อ"
+authenticationRequiredToContinue: "กรุณายืนยันตัวตนทางอิเล็กทรอนิกส์เพื่อดำเนินการต่อ"
 dateAndTime: "เวลาประทับ"
 showRenotes: "แสดงรีโน้ต"
 edited: "แก้ไขแล้ว"
 notificationRecieveConfig: "การตั้งค่าการแจ้งเตือน"
 mutualFollow: "ติดตามซึ่งกันและกัน"
+followingOrFollower: "กำลังติดตามหรือผู้ติดตาม"
 fileAttachedOnly: "เฉพาะโน้ตที่มีไฟล์เท่านั้น"
-showRepliesToOthersInTimeline: "แสดงการตอบกลับไปยังอื่นๆในไทม์ไลน์"
-hideRepliesToOthersInTimeline: "ซ่อนการตอบกลับไปยังอื่นๆจากไทม์ไลน์"
+showRepliesToOthersInTimeline: "แสดงการตอบกลับผู้อื่นลงในไทม์ไลน์"
+hideRepliesToOthersInTimeline: "ไม่แสดงการตอบกลับผู้อื่นลงในไทม์ไลน์"
+showRepliesToOthersInTimelineAll: "รวมตอบกลับจากทุกคนที่คุณติดตามไว้ในไทม์ไลน์ของคุณ"
+hideRepliesToOthersInTimelineAll: "ซ่อนตอบกลับจากทุกคนที่คุณติดตามไปจากไทม์ไลน์ของคุณ"
+confirmShowRepliesAll: "การดำเนินการนี้ไม่สามารถย้อนกลับได้ คุณต้องการแสดงการตอบกลับผู้อื่นจากผู้ใช้ทุกคนที่คุณติดตามอยู่ในไทม์ไลน์ของคุณหรือไม่?"
+confirmHideRepliesAll: "การดำเนินการนี้ไม่สามารถย้อนกลับได้ คุณต้องการซ่อนการตอบกลับผู้อื่นจากผู้ใช้ทุกคนที่คุณติดตามอยู่ในไทม์ไลน์ของคุณหรือไม่?"
 externalServices: "บริการภายนอก"
+sourceCode: "ซอร์สโค้ด"
+sourceCodeIsNotYetProvided: "ซอร์สโค้ดยังไม่พร้อมใช้งาน โปรดติดต่อผู้ดูแลระบบของคุณเพื่อแก้ไขปัญหานี้"
+repositoryUrl: "URL ของ repository"
+repositoryUrlDescription: "หากมีที่เก็บซอร์สโค้ดที่เปิดเผยต่อสาธารณะ ให้ป้อน URL ที่เก็บซอร์สโค้ดนั้น แต่หากคุณใช้ Misskey ตามต้นฉบับ (ไม่มีการเปลี่ยนแปลงซอร์สโค้ด) ให้ป้อน https://github.com/misskey-dev/misskey"
+repositoryUrlOrTarballRequired: "หากคุณไม่มี repository สาธารณะ คุณจะต้องจัดเตรียม tarball แทน ดู .config/example.yml สำหรับรายละเอียด"
+feedback: "ฟีดแบ็ก"
+feedbackUrl: "URLของฟีดแบ็ก"
 impressum: "อิมเพรสชั่น"
 impressumUrl: "URL อิมเพรสชั่น"
+impressumDescription: "การติดป้ายกำกับ (Impressum) มีผลบังคับใช้ในบางประเทศและภูมิภาค เช่น ประเทศเยอรมนี"
 privacyPolicy: "นโยบายความเป็นส่วนตัว"
 privacyPolicyUrl: "URL นโยบายความเป็นส่วนตัว"
 tosAndPrivacyPolicy: "เงื่อนไขในการให้บริการและนโยบายความเป็นส่วนตัว"
 avatarDecorations: "การตกแต่งอวตาร"
 attach: "แนบ"
 detach: "นำออก"
+detachAll: "เอาออกทั้งหมด"
 angle: "แองเกิล"
-flip: "ย้อนกลับ"
+flip: "พลิก"
 showAvatarDecorations: "แสดงตกแต่งอวตาร"
 releaseToRefresh: "ปล่อยเพื่อรีเฟรช"
 refreshing: "กำลังรีเฟรช..."
@@ -1148,17 +1195,62 @@ pullDownToRefresh: "ดึงลงเพื่อรีเฟรช"
 disableStreamingTimeline: "ปิดใช้งานอัปเดตไทม์ไลน์แบบเรียลไทม์"
 useGroupedNotifications: "แสดงผลการแจ้งเตือนแบบกลุ่มแล้ว"
 signupPendingError: "มีปัญหาในการตรวจสอบที่อยู่อีเมลลิงก์อาจหมดอายุแล้ว"
+cwNotationRequired: "หากเปิดใช้งาน “ซ่อนเนื้อหา” จะต้องระบุคำอธิบาย"
 doReaction: "เพิ่มรีแอคชั่น"
+code: "โค้ด"
+reloadRequiredToApplySettings: "จำเป็นต้องมีการโหลดซ้ำเพื่อให้การตั้งค่ามีผล"
+remainingN: "เหลือ : {n}"
+overwriteContentConfirm: "แน่ใจหรือไม่ว่าต้องการเขียนทับเนื้อหาปัจจุบัน?"
+seasonalScreenEffect: "เอฟเฟกต์หน้าจอตามฤดูกาล"
+decorate: "ตกแต่ง"
+addMfmFunction: "เพิ่มการตกแต่ง"
+enableQuickAddMfmFunction: "แสดงตัวจิ้มเลือก MFM ขั้นสูง"
+bubbleGame: "เกมบับเบิ้ล"
+sfx: "เสียงเอฟเฟ็กต์"
+soundWillBePlayed: "จะมีการเล่นเอฟเฟกต์เสียง"
+showReplay: "ดูรีเพลย์"
+replay: "รีเพลย์"
+replaying: "กำลังรีเพลย์"
+endReplay: "ออกจากรีเพลย์"
+copyReplayData: "คัดลอกข้อมูลรีเพลย์"
+ranking: "อันดับ"
+lastNDays: "ล่าสุด {n} วันที่แล้ว"
+backToTitle: "กลับไปหน้าไตเติ้ล"
+hemisphere: "พื้นที่ที่อาศัยอยู่"
+withSensitive: "แสดงโน้ตที่มีไฟล์เนื้อหาละเอียดอ่อน"
+userSaysSomethingSensitive: "โพสต์ที่มีไฟล์เนื้อหาละเอียดอ่อนของ {name}"
+enableHorizontalSwipe: "ปัดเพื่อสลับแท็บ"
+loading: "กำลังโหลด"
+surrender: "ยอมแพ้"
+gameRetry: "เริ่มเกมใหม่"
+_bubbleGame:
+  howToPlay: "วิธีเล่น"
+  hold: "หยุดชั่วคราว"
+  _score:
+    score: "คะแนน"
+    scoreYen: "จำนวนเงินที่ได้รับ"
+    highScore: "คะแนนสูงสุด"
+    maxChain: "จำนวน chain สูงสุด"
+    yen: "{yen} เยน"
+    estimatedQty: "{qty} อัน"
+    scoreSweets: "โอนิงิริ {onigiriQtyWithUnit}"
+  _howToPlay:
+    section1: "ขยับตำแหน่งและวางวัตถุลงในกล่อง"
+    section2: "เมื่อวัตถุประเภทเดียวกันมารวมกัน พวกมันจะกลายเป็นวัตถุใหม่และคุณจะได้รับคะแนน"
+    section3: "หากวัตถุล้นออกมาจากกล่อง เกมก็จะจบลง ตั้งเป้าทำคะแนนให้สูงด้วยการหลอมวัตถุต่าง ๆ โดยไม่ทำให้ล้นกล่อง!"
 _announcement:
   forExistingUsers: "ผู้ใช้งานที่มีอยู่เท่านั้น"
   forExistingUsersDescription: "การประกาศนี้จะแสดงต่อผู้ใช้ที่มีอยู่ ณ จุดที่เผยแพร่นั้นๆถ้าหากเปิดใช้งาน ถ้าหากปิดใช้งานผู้ที่กำลังสมัครใหม่หลังจากโพสต์แล้วนั้นก็จะเห็นเช่นกัน"
-  needConfirmationToRead: "จำเป็นต้องยืนยันเพื่อทำเครื่องหมายบอกว่าอ่านแล้ว"
-  needConfirmationToReadDescription: "ข้อความแจ้งแยก ถ้าหากต้องการเพื่อยืนยันว่ากำลังทำเครื่องหมายประกาศนี้ว่าอ่านแล้วจะแสดงขึ้นถ้าหากเปิดใช้งาน การประกาศนั้นจะไม่รวมอยู่ในฟังก์ชั่นว่า \"ทำเครื่องหมายทั้งหมดว่าอ่านแล้ว\""
-  end: "ประกาศเก็บถาวร"
+  needConfirmationToRead: "จำเป็นต้องยืนยันว่าอ่านแล้ว"
+  needConfirmationToReadDescription: "กล่องโต้ตอบการยืนยันจะปรากฏขึ้นเมื่อจะทำเครื่องหมายว่าอ่านแล้ว นอกจากนี้ยังทำให้ประกาศนี้ยังไม่ถูกอ่านเมื่อใช้ฟังก์ชั่น “ทำเครื่องหมายฯ ทั้งหมดว่าอ่านแล้ว”"
+  end: "เก็บประกาศ"
   tooManyActiveAnnouncementDescription: "การมีประกาศที่ใช้งานมากเกินไปนั้นอาจจะทำให้ประสบการณ์ของผู้ใช้งานนั้นดูแย่ลง โปรดกรุณาพิจารณาการเก็บประกาศที่ล้าสมัยด้วยนะค่ะ"
-  readConfirmTitle: "ทำเครื่องหมายบอกว่าอ่านแล้วเลยมั้ย?"
-  readConfirmText: "การดำเนินการนี้จะทำเครื่องหมายเนื้อหาของ \"{title}\" บอกว่าอ่านแล้วนะ"
+  readConfirmTitle: "ทำเครื่องหมายว่าอ่านแล้วเลยไหม?"
+  readConfirmText: "จะทำเครื่องหมายใส่ “{title}” ว่าอ่านแล้ว"
+  shouldNotBeUsedToPresentPermanentInfo: "เราขอแนะนำให้ใช้ประกาศเพื่อโพสต์ข้อมูลแบบ flow มากกว่าข้อมูลแบบ stock เนื่องจากมีแนวโน้มที่จะส่งผลเสียต่อ UX โดยเฉพาะสำหรับผู้ใช้ใหม่"
+  dialogAnnouncementUxWarn: "เราขอแนะนำให้ใช้ด้วยความระมัดระวัง เนื่องจากการแจ้งเตือนแบบกล่องโต้ตอบตั้งแต่ 2 รายการขึ้นไปพร้อมกันอาจส่งผลเสียต่อ UX ได้อย่างมาก"
   silence: "ไม่มีการแจ้งเตือน"
+  silenceDescription: "หากเปิดใช้งาน จะไม่มีการแจ้งเตือนประกาศนี้ และผู้ใช้จะไม่จำเป็นต้องทำเครื่องหมายว่าอ่านแล้ว"
 _initialAccountSetting:
   accountCreated: "คุณได้สร้างบัญชีของคุณสำเร็จเรียบร้อยแล้ว!"
   letsStartAccountSetup: "สำหรับผู้เริ่มต้นมาตั้งค่าโปรไฟล์ของคุณกันเถอะ"
@@ -1170,7 +1262,8 @@ _initialAccountSetting:
   followUsers: "ลองติดตามผู้ใช้บางคนที่คุณอาจจะสนใจเพื่อสร้างไทม์ไลน์ของคุณสิ !"
   pushNotificationDescription: "กำลังเปิดใช้งานการแจ้งเตือนแบบพุชจะช่วยให้คุณได้รับการแจ้งเตือนจาก {name} โดยตรงบนอุปกรณ์ของคุณนะ"
   initialAccountSettingCompleted: "ตั้งค่าโปรไฟล์เสร็จสมบูรณ์แล้ว!"
-  haveFun: "ขอให้สนุก {name}!"
+  haveFun: "ขอให้สนุกกับ {name}!"
+  youCanContinueTutorial: "คุณสามารถดำเนินการต่อด้วยบทช่วยสอนเกี่ยวกับวิธีใช้ {name} (Misskey) หรือออกจากบทช่วยสอนแล้วเริ่มใช้งานได้ทันที"
   startTutorial: "เริ่มการฝึกสอน"
   skipAreYouSure: "ต้องการข้ามการตั้งค่าโปรไฟล์จริงๆแบบนั้นหรอ?"
   laterAreYouSure: "ต้องการตั้งค่าโปรไฟล์ในภายหลังจริงๆอย่างงั้นหรอ?"
@@ -1181,29 +1274,80 @@ _initialTutorial:
   skipAreYouSure: "ต้องการออกจากบทช่วยสอนใช่ไหม?"
   _landing:
     title: "ยินดีต้อนรับสู่บทช่วยสอน"
+    description: "คุณสามารถตรวจสอบการใช้งานและฟังก์ชั่นพื้นฐานของ Misskey ได้ที่นี่"
   _note:
     title: "โน้ตคืออะไร?"
+    description: "โพสต์ใน Misskey เรียกว่า “โน้ต” ซึ่งจะจัดเรียงตามลำดับเวลาบนไทม์ไลน์และอัปเดตแบบเรียลไทม์"
+    reply: "คุณสามารถตอบกลับได้ และคุณยังสามารถตอบกลับใส่การตอบกลับเพื่อสนทนาต่อได้เสมือนดั่งเธรด"
+    renote: "คุณสามารถแชร์โน้ตไปยังไทม์ไลน์ของคุณเอง คุณยังสามารถเพิ่มข้อความและเครื่องหมายคำพูดได้"
+    reaction: "คุณสามารถเพิ่มรีแอคชั่นได้ รายละเอียดจะอธิบายอยู่ในหน้าถัดไป"
+    menu: "คุณสามารถดูรายละเอียดโน้ต คัดลอกลิงก์ และดำเนินการอื่นๆ ได้"
   _reaction:
     title: "รีแอคชั่นคืออะไร?"
+    description: "โน้ตสามารถ“รีแอคชั่น”ด้วยเอโมจิต่างๆ ซึ่งทำให้สามารถแสดงความแตกต่างเล็กๆ น้อยๆ ที่อาจไม่สามารถสื่อออกมาได้ด้วยการแค่การกด “ถูกใจ”"
+    letsTryReacting: "คุณสามารถเพิ่มรีแอคชั่นได้ด้วยการคลิกปุ่ม “+” บนโน้ต ลองรีแอคชั่นโน้ตตัวอย่างนี้ดูสิ!"
+    reactToContinue: "เพิ่มรีแอคชั่นเพื่อดำเนินการต่อ"
+    reactNotification: "คุณจะได้รับการแจ้งเตือนแบบเรียลไทม์เมื่อมีคนตอบรีแอคชั่นโน้ตของคุณ"
+    reactDone: "คุณสามารถยกเลิกรีแอคชั่นได้โดยการกดปุ่ม “-”"
   _timeline:
     title: "แนวคิดเรื่องของไทม์ไลน์"
+    description1: "Misskey มีหลายไทม์ไลน์ขึ้นอยู่กับวิธีการใช้งานของคุณ (บางไทม์ไลน์อาจไม่สามารถใช้ได้ขึ้นอยู่กับนโยบายของเซิร์ฟเวอร์)"
+    home: "คุณสามารถดูโพสต์จากบัญชีที่คุณติดตามได้"
+    local: "คุณสามารถดูโพสต์จากผู้ใช้ทั้งหมดบนเซิร์ฟเวอร์นี้"
+    social: "โพสต์จากทั้งไทม์ไลน์หน้าแรกและไทม์ไลน์ในพื้นที่ของคุณจะปรากฏขึ้น"
+    global: "คุณสามารถดูโพสต์จากเซิร์ฟเวอร์ที่เชื่อมต่ออื่นๆ ทั้งหมดได้"
+    description2: "คุณสามารถสลับระหว่างแต่ละไทม์ไลน์ได้ตลอดเวลาได้ที่บริเวณด้านบนของหน้าจอ"
+    description3: "นอกจากนี้ยังมีรายการไทม์ไลน์ ไทม์ไลน์ของช่อง ฯลฯ โปรดดู {link} สำหรับรายละเอียดเพิ่มเติม"
   _postNote:
-    title: "ตั้งค่ากำลังโพสต์โน้ต"
+    title: "ตั้งค่าการโพสต์โน้ต"
+    description1: "เมื่อโพสต์โน้ตบน Misskey คุณสามารถตั้งค่าตัวเลือกต่างๆ ได้ แบบฟอร์มการส่งมีลักษณะดังนี้"
     _visibility:
       description: "คุณสามารถจำกัดผู้ที่สามารถดูโน้ตของคุณได้นะ"
       public: "โน้ตของคุณนั้นจะปรากฏแก่ผู้ใช้งานทุกคน"
+      home: "เผยแพร่บนไทม์ไลน์หน้าแรกเท่านั้น ผู้คนที่เข้าชมโปรไฟล์ของคุณ ผ่านผู้ติดตาม และผ่านการรีโน้ตสามารถเห็นได้"
+      followers: "มองเห็นได้เฉพาะผู้ติดตามเท่านั้น ไม่มีใครอื่นนอกจากตัวคุณเองที่สามารถรีโน้ตได้ และมีเพียงผู้ติดตามของคุณเท่านั้นที่สามารถดูได้"
+      direct: "เปิดให้เห็นเฉพาะผู้ใช้ที่ระบุเท่านั้น และพวกเขาจะได้รับแจ้งเตือนด้วย คุณสามารถใช้มันแทนข้อความโดยตรง (dm)"
+      doNotSendConfidencialOnDirect1: "โปรดใช้ความระมัดระวังในการส่งข้อมูลที่ละเอียดอ่อน"
+      doNotSendConfidencialOnDirect2: "ผู้ดูแลระบบเซิร์ฟเวอร์ปลายทางสามารถดูเนื้อหาที่โพสต์ได้ ดังนั้นหากคุณส่งโพสต์โดยตรงไปยังผู้ใช้บนเซิร์ฟเวอร์ที่ไม่น่าเชื่อถือ คุณจะต้องใช้ความระมัดระวังในการจัดการข้อมูลที่เป็นความลับ"
+      localOnly: "การโพสต์ด้วย flag นี้จะไม่รวมโน้ตไปยังเซิร์ฟเวอร์อื่น ผู้ใช้บนเซิร์ฟเวอร์อื่นจะไม่สามารถดูโน้ตเหล่านี้ได้โดยตรง โดยไม่คำนึงถึงการตั้งค่าการแสดงผลข้างต้น"
     _cw:
       title: "คำเตือนเกี่ยวกับเนื้อหา"
+      description: "เนื้อหาที่เขียนด้วย “คำอธิบายประกอบ” จะแสดงแทนข้อความหลัก คลิก “ดูเพิ่มเติม” เพื่อแสดงข้อความเต็ม"
       _exampleNote:
         cw: "นี่อาจจะทำให้คุณหิวอย่างแน่นอน!"
+        note: "เพิ่งไปกินโดนัทเคลือบช็อคโกแลตมา 🍩😋"
+      useCases: "ใช้สิ่งนี้เพื่อระบุโน้ตที่ต้องตามแนวทางปฏิบัติของเซิร์ฟเวอร์ หรือเพื่อควบคุมการสปอยล์และข้อความที่ละเอียดอ่อนด้วยตนเอง"
+  _howToMakeAttachmentsSensitive:
+    title: "จะทำเครื่องหมายไฟล์แนบว่ามีเนื้อหาละเอียดอ่อนได้อย่างไร?"
+    description: "ทำเครื่องหมายไฟล์แนบว่า “มีเนื้อหาละเอียดอ่อน” เมื่อจำเป็นตามแนวทางของเซิร์ฟเวอร์ หรือเมื่อไฟล์แนบไม่ควรปรากฏให้เห็น"
+    tryThisFile: "ลองทำให้รูปภาพที่แนบมากับแบบฟอร์มนี้มีเนื้อหาละเอียดอ่อน!"
+    _exampleNote:
+      note: "อุ้ย นัตโตะ ฝาเปิดเละเทะ..."
+    method: "หากต้องการทำให้ไฟล์แนบมีเนื้อหาละเอียดอ่อน ให้คลิกไฟล์เพื่อเปิดเมนูแล้วคลิก “ทำเครื่องหมายว่ามีเนื้อหาละเอียดอ่อน”"
+    sensitiveSucceeded: "เมื่อแนบไฟล์ โปรดตั้งค่าเครื่องหมายว่ามีเนื้อหาละเอียดอ่อนตามแนวทางของเซิร์ฟเวอร์"
+    doItToContinue: "ทำเครื่องหมายกับรูปภาพว่ามีเนื้อหาละเอียดอ่อน เพื่อดำเนินการต่อ"
+  _done:
+    title: "บทเรียนจบลงแล้วจ้า เย่เย่เย่  🎉"
+    description: "คุณสมบัติที่แนะนำในที่นี่เป็นเพียงบางส่วนเท่านั้น หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับวิธีใช้ Misskey โปรดไปที่ {link}"
+_timelineDescription:
+  home: "บนไทม์ไลน์หน้าแรก คุณสามารถดูโพสต์จากบัญชีที่คุณติดตามได้"
+  local: "ไทม์ไลน์ในพื้นที่ช่วยให้คุณเห็นโพสต์จากผู้ใช้ทั้งหมดบนเซิร์ฟเวอร์นี้"
+  social: "ไทม์ไลน์โซเชียลจะแสดงโพสต์จากทั้งไทม์ไลน์หน้าแรกและไทม์ไลน์ในพื้นที่"
+  global: "ในไทม์ไลน์ทั่วโลก คุณสามารถดูโน้ตจากเซิร์ฟเวอร์ที่เชื่อมต่อทั้งหมดได้"
 _serverRules:
   description: "ชุดของกฎที่จะแสดงก่อนการลงทะเบียนเราขอแนะนำให้ตั้งค่าสรุปข้อกำหนดในการให้บริการ"
 _serverSettings:
-  iconUrl: "ไอคอน URL"
+  iconUrl: "URL ไอคอน"
+  appIconDescription: "ระบุไอคอนที่จะใช้เมื่อ {host} แสดงเป็นแอป"
   appIconUsageExample: "E.g. เป็น PWA หรือเมื่อแสดงผลเป็นบุ๊กมาร์กหน้าจอหลักบนโทรศัพท์"
+  appIconStyleRecommendation: "เนื่องจากไอคอนอาจถูกครอบตัดเป็นสี่เหลี่ยมจัตุรัสหรือวงกลม จึงแนะนำให้ใช้ไอคอนที่มีขอบสีรอบๆ เนื้อหา"
   appIconResolutionMustBe: "ความละเอียดขั้นต่ำไว้คือ {resolution}."
-  manifestJsonOverride: "manifest.json โอเวอร์ลาย"
+  manifestJsonOverride: "เขียนทับ manifest.json"
   shortName: "ชื่อย่อ"
+  shortNameDescription: "ตัวย่อหรือชื่อทั่วไปที่สามารถแสดงแทนชื่ออย่างเป็นทางการแบบยาวของเซิร์ฟเวอร์"
+  fanoutTimelineDescription: "เพิ่มประสิทธิภาพการดึงข้อมูลไทม์ไลน์อย่างมาก และลดภาระในฐานข้อมูลเมื่อเปิดใช้งาน ในทางกลับกัน การใช้หน่วยความจำของ Redis จะเพิ่มขึ้น ลองปิดการใช้งานนี้ในกรณีที่หน่วยความจำเซิร์ฟเวอร์เหลือน้อยหรือเซิร์ฟเวอร์ไม่เสถียร"
+  fanoutTimelineDbFallback: "ฟอลแบ๊กกลับฐานข้อมูล"
+  fanoutTimelineDbFallbackDescription: "เมื่อเปิดใช้งาน หากไม่ได้แคชไทม์ไลน์ ไทม์ไลน์จะฟอลแบ๊กไปยังฐานข้อมูลสำหรับการ query เพิ่มเติม การปิดใช้งานจะช่วยลดภาระของเซิร์ฟเวอร์ด้วยการกำจัดกระบวนฟอลแบ๊ก แต่มันก็จะจำกัดช่วงเวลาไทม์ไลน์ที่สามารถดึงข้อมูลได้"
 _accountMigration:
   moveFrom: "ย้ายข้อมูลบัญชีอื่นไปยังอีกบัญชีนี้หนึ่ง"
   moveFromSub: "สร้างนามแฝงไปยังบัญชีอื่น"
@@ -1212,7 +1356,7 @@ _accountMigration:
   moveTo: "ย้ายข้อมูลบัญชีนี้ไปยังบัญชีอีกหนึ่ง"
   moveToLabel: "บัญชีที่จะย้ายไปที่:"
   moveCannotBeUndone: "ไม่สามารถยกเลิกการโอนย้ายบัญชีได้"
-  moveAccountDescription: "การกระทำนี้ไม่สามารถย้อนกลับได้นะ ขั้นตอนแรก ต้องสร้างนามแฝงสำหรับบัญชีนี้ในบัญชีที่คุณต้องการย้ายไป หลังจากนั้นแล้ว ป้อนบัญชีที่จะย้ายไปในรูปแบบดังต่อไปนี้: @person@instance.com"
+  moveAccountDescription: "การดำเนินการนี้จะย้ายบัญชีของคุณไปยังบัญชีอื่น\n・ผู้ที่กำลังติดตามคุณจากบัญชีนี้จะถูกย้ายไปยังบัญชีใหม่โดยอัตโนมัติ\n・บัญชีนี้จะเลิกติดตามผู้ใช้ทั้งหมดที่กำลังติดตามอยู่\n・คุณจะไม่สามารถสร้างโน้ต ฯลฯ ในบัญชีนี้ได้\n\nแม้ว่าการย้ายผู้ที่ติดตามคุณจะเป็นไปโดยอัตโนมัติ แต่คุณต้องเตรียมขั้นตอนบางอย่างด้วยตนเอง เพื่อย้ายรายชื่อผู้ใช้ที่คุณกำลังติดตาม โดยดำเนินการส่งออกรายชื่อแล้วค่อยนำเข้ามาภายหลังในเมนูการตั้งค่าของบัญชีใหม่ ใช้ขั้นตอนเดียวกันนี้ใช้รายชื่อผู้ใช้ที่ถูกปิดเสียงและถูกบล็อก\n\n(คำอธิบายนี้ใช้กับ Misskey v13.12.0 ขึ้นไป, ซอฟต์แวร์ ActivityPub อื่นๆ เช่น Mastodon อาจทำงานแตกต่างออกไป)"
   moveAccountHowTo: "หากต้องการย้ายข้อมูลก่อนอื่นให้สร้างชื่อแทนสำหรับบัญชีนี้ ในบัญชีที่จะต้องการย้ายไป\nหลังจากที่คุณสร้างนามแฝงนั้นแล้ว ให้ป้อนบัญชีที่ต้องการจะย้ายไปในรูปแบบดังต่อไปนี้: @username@server.example.com"
   startMigration: "โอนย้าย"
   migrationConfirm: "ยืนยันการย้ายข้อมูลบัญชีนี้ไปที่ {account} เมื่อเริ่มแล้วจะไม่สามารถหยุดหรือนำกลับคืนมาได้ และคุณจะไม่สามารถใช้บัญชีนี้ในสถานะดั้งเดิมได้อีกต่อไป\n\nนอกจากนี้ คุณจำเป็นต้องสร้างบัญชีสำรองสำหรับการย้ายบัญชี"
@@ -1227,31 +1371,31 @@ _achievements:
       description: "โพสต์โน้ตแรกของคุณ"
       flavor: "ขอให้มีช่วงเวลาที่ดีกับ Misskey นะคะ!"
     _notes10:
-      title: "โน้ตบางอย่าง"
+      title: "โน้ตไม่กี่ชิ้น"
       description: "โพสต์ 10 โน้ต"
     _notes100:
-      title: "โน้ตจำนวนมาก"
+      title: "โน้ตเยอะอยู่"
       description: "โพสต์ 100 โน้ต"
     _notes500:
-      title: "ครอบคลุมในโน้ต"
+      title: "จมคากองโน้ต"
       description: "โพสต์ 500 โน้ต"
     _notes1000:
       title: "ภูเขาแห่งโน้ต"
       description: "โพสต์ 1,000 โน้ต"
     _notes5000:
-      title: "โน้ตล้น"
+      title: "โน้ตล้นไปแล้ว"
       description: "โพสต์ 5,000 โน้ต"
     _notes10000:
       title: "ซุปเปอร์โน้ต"
       description: "โพสต์ 10,000 โน้ต"
     _notes20000:
-      title: "ต้องการ... เพิ่มเติม... โน้ต..."
+      title: "ต้ อ ง ก า ร โ น้ ต เ พิ่ ม อี ก !"
       description: "โพสต์ 20,000 โน้ต"
     _notes30000:
       title: "โน้ต โน้ต โน้ต!"
       description: "โพสต์ 30,000 โน้ต"
     _notes40000:
-      title: "โน้ตโรงงาน"
+      title: "โรงงานผลิตโน้ต"
       description: "โพสต์ 40,000 โน้ต"
     _notes50000:
       title: "ดาวเคราะห์แห่งโน้ต"
@@ -1260,26 +1404,26 @@ _achievements:
       title: "โน้ตควอซาร์"
       description: "โพสต์ 60,000 โน้ต"
     _notes70000:
-      title: "โน้ตหลุมดำ"
+      title: "หลุม-โน้ต-ดำ"
       description: "โพสต์ 70,000 โน้ต"
     _notes80000:
-      title: "โน้ต กาแล็กซี่"
+      title: "ดาราจักรโน้ต"
       description: "โพสต์ 80,000 โน้ต"
     _notes90000:
-      title: "โน้ต จักรวาล"
+      title: "จักรวาลโน้ต"
       description: "โพสต์ 90,000 โน้ต"
     _notes100000:
       title: "ALL YOUR NOTE ARE BELONG TO US"
       description: "โพสต์ 100,000 โน้ต"
-      flavor: "นายแน่ใจล่ะก็ มีอะไรพูดมาได้นะ"
+      flavor: "มีเรื่องจะเขียนมากขนาดนั้นเลยเหรอนั่น?"
     _login3:
       title: "มือใหม่ I"
       description: "เข้าสู่ระบบเป็นเวลารวม 3 วัน"
-      flavor: "เริ่มตั้งแต่วันนี้ เรียกฉันว่ามิสคิสต์"
+      flavor: "ตั้งแต่วันนี้เป็นต้นไป ฉันคือมิสคิสต์"
     _login7:
       title: "มือใหม่ II"
       description: "เข้าสู่ระบบเป็นเวลารวม 7 วัน"
-      flavor: "รู้สึกเหมือนคุณได้แขวนของสิ่งต่างๆ หรือยังคะ?"
+      flavor: "ชินกับมันแล้วหรือยัง?"
     _login15:
       title: "มือใหม่ III"
       description: "เข้าสู่ระบบเป็นเวลารวม 15 วัน"
@@ -1292,7 +1436,7 @@ _achievements:
     _login100:
       title: "มิสคิสท์ III"
       description: "เข้าสู่ระบบเป็นเวลารวม 100 วัน"
-      flavor: "ความรุนแรง Misskist"
+      flavor: "มิสคิสต์หัวรุนแรง"
     _login200:
       title: "ลูกค้าประจำ I"
       description: "เข้าสู่ระบบเป็นเวลารวม 200 วัน"
@@ -1305,7 +1449,7 @@ _achievements:
     _login500:
       title: "ผู้เชี่ยวชาญ I"
       description: "เข้าสู่ระบบเป็นเวลารวม 500 วัน"
-      flavor: "เพื่อนของผมนะมักจะกล่าวว่าผมนะชอบจดโน้ต"
+      flavor: "ทุกท่าน ผมชอบโน้ต (กล่าวโดย เดอะ เ_เ_อร์)"
     _login600:
       title: "ผู้เชี่ยวชาญ II"
       description: "เข้าสู่ระบบเป็นเวลารวม 600 วัน"
@@ -1323,7 +1467,7 @@ _achievements:
       description: "เข้าสู่ระบบเป็นเวลารวม 1,000 วัน"
       flavor: "ขอบคุณที่ใช้ Misskey นะ !"
     _noteClipped1:
-      title: "จะต้อง... คลิป..."
+      title: "อดไม่ได้ที่จะต้องคลิปมันเอาไว้"
       description: "คลิปโน้ตตัวแรกของคุณ"
     _noteFavorited1:
       title: "สตาร์เกเซอร์"
@@ -1332,15 +1476,15 @@ _achievements:
       title: "แสวงหาดวงดาว"
       description: "มีคนอื่นๆที่ชื่นชอบหนึ่งในโน้ตของคุณ"
     _profileFilled:
-      title: "เตรียมไว้อย่างดี"
+      title: "เตรียมตัวอย่างดี"
       description: "ตั้งค่าโปรไฟล์ของคุณ"
     _markedAsCat:
       title: "ฉันเป็นแมว"
       description: "ทำเครื่องหมายบัญชีของคุณว่าเป็นแมว"
-      flavor: "ฉันจะให้ชื่อคุณภายหลังนะ"
+      flavor: "แมวน้อยไร้ชื่อ"
     _following1:
-      title: "กำลังติดตามผู้ใช้คนแรกของคุณ"
-      description: "ติดตามผู้ใช้"
+      title: "ก้าวแรกสู่...กดติดตาม"
+      description: "กดติดตามชาวบ้านครั้งแรก"
     _following10:
       title: "ทำต่อไป... ทำต่อไป..."
       description: "ติดตาม 10 บัญชีผู้ใช้"
@@ -1351,7 +1495,7 @@ _achievements:
       title: "เพื่อน 100 คน"
       description: "ติดตาม 100 บัญชี"
     _following300:
-      title: "เพื่อนโอเวอร์โหลด"
+      title: "มีเพื่อนมากเกินไปละ"
       description: "ติดตาม 300 บัญชี"
     _followers1:
       title: "ผู้ติดตามคนแรก"
@@ -1378,12 +1522,12 @@ _achievements:
       title: "นักสะสมความสำเร็จ"
       description: "ได้รับความสำเร็จ 30 ครั้ง"
     _viewAchievements3min:
-      title: "ชอบบรรลุผลสําเร็จ"
+      title: "ชอบบรรลุความสําเร็จ"
       description: "มองดูรายการความสำเร็จของคุณเป็นเวลาอย่างน้อย 3 นาที"
     _iLoveMisskey:
       title: "ฉันรัก Misskey"
-      description: "โพสต์ \"I ❤ #Misskey\""
-      flavor: "ขอบคุณที่ใช้ Misskey! by ทีมผู้พัฒนา"
+      description: "โพสต์ “I ❤ #Misskey”"
+      flavor: "ขอบคุณพระคุณเป็นอย่างสูงที่ท่านใช้ Misskey นะคะ ! by ทีมผู้พัฒนา"
     _foundTreasure:
       title: "ล่าสมบัติ"
       description: "คุณพบสมบัติที่ซ่อนอยู่"
@@ -1391,25 +1535,25 @@ _achievements:
       title: "พักผ่อนสักหน่อย"
       description: "ใช้เวลา 30 นาทีบน Misskey"
     _client60min:
-      title: "ไม่มี \"Miss\" ใน Misskey "
+      title: "Misskey ต้องไม่มีสิ่งใด “Miss”"
       description: "เปิด Misskey ค้างไว้แล้วอย่างน้อย 60 นาที"
     _noteDeletedWithin1min:
       title: "ไม่เป็นไร"
       description: "ลบโน้ตภายในหนึ่งนาทีหลังจากที่โพสต์"
     _postedAtLateNight:
-      title: "กลางคืน"
+      title: "ออกหากินยามดึกดื่น"
       description: "โพสต์โน้ตตอนดึกๆ"
       flavor: "ได้เวลาเข้านอนแล้วนะ"
     _postedAt0min0sec:
-      title: "นาฬิกาพูดได้"
-      description: "โพสต์บนโน้ตเมื่อเวลา 00:00 น."
-      flavor: "คลิก คลิก คลิก แกล๊งๆ"
+      title: "นาฬิกาเทียบเวลา"
+      description: "โพสต์โน้ตเมื่อเวลา 00:00 น."
+      flavor: "โป๊ะ โป๊ะ โป๊ะ ปิ้งงงงง"
     _selfQuote:
       title: "อ้างอิงตนเอง"
-      description: "อ้างโน้ตย่อของคุณเอง"
+      description: "อ้างโน้ตของคุณเอง"
     _htl20npm:
       title: "ไทม์ไลน์ไหล"
-      description: "มีการทำความเร็วของไทม์ไลน์ที่บ้านของคุณเกิน 20 npm (โน้ตต่อนาที)"
+      description: "มีการทำความเร็วของไทม์ไลน์หน้าแรกเกิน 20 npm (โน้ตต่อนาที)"
     _viewInstanceChart:
       title: "วิเคราะห์"
       description: "ดูแผนภูมิอินสแตนซ์ของคุณ"
@@ -1426,14 +1570,14 @@ _achievements:
       title: "คุณอ่านมันจริงๆหรือเปล่า?"
       description: "มีการโต้ตอบกับโน้ตที่มีความยาวมากกว่า 100 ตัวอักษรภายใน 3 วินาทีหลังจากที่โพสต์"
     _clickedClickHere:
-      title: "คลิ๊กที่นี่"
+      title: "คลิกที่นี่"
       description: "คุณได้คลิกที่นี่"
     _justPlainLucky:
       title: "แค่ลัคกี้ธรรมดา"
       description: "มีโอกาสที่จะได้รับด้วยความน่าจะเป็นไปได้ 0.005% ทุก ๆ 10 วินาที"
     _setNameToSyuilo:
-      title: "พระเจ้าคอมเพล็กซ์"
-      description: "ตั้งชื่อของคุณเป็น \"syuilo\""
+      title: "คอมเพล็กซ์ของพระเจ้า"
+      description: "ตั้งชื่อของคุณเป็น “syuilo”"
     _passedSinceAccountCreated1:
       title: "ครบรอบหนึ่งปี"
       description: "ผ่านไปหนึ่งปีแล้วนะตั้งแต่บัญชีของคุณถูกสร้างขึ้นมาน่ะ"
@@ -1453,7 +1597,7 @@ _achievements:
     _cookieClicked:
       title: "เกมที่คุณคลิกที่คุกกี้"
       description: "คลิกคุกกี้"
-      flavor: "เดี๋ยวก่อนนะ คุณอยู่ในเว็บไซต์ที่ถูกต้องแน่อย่างงั้นเหรอ?"
+      flavor: "ใช่หรอ? แน่ใจว่าซอฟต์แวร์ทำงานถูกต้องนะ?"
     _brainDiver:
       title: "Brain Diver"
       description: "โพสต์ลิงก์ไปยัง Brain Diver"
@@ -1461,35 +1605,47 @@ _achievements:
     _smashTestNotificationButton:
       title: "ทดสอบโอเวอร์โฟลว์"
       description: "ทดสอบการแจ้งเตือนทริกเกอร์ซ้ำๆ ภายในระยะเวลาอันสั้นๆ"
+    _tutorialCompleted:
+      title: "ใบรับรองการสำเร็จหลักสูตร Misskey มือใหม่"
+      description: "เสร็จสิ้นการสอนแล้ว"
+    _bubbleGameExplodingHead:
+      title: "🤯"
+      description: "สร้างวัตถุที่ใหญ่ที่สุดในเกมบับเบิ้ล"
+    _bubbleGameDoubleExplodingHead:
+      title: "ดับเบิ้ล"
+      description: "สร้างวัตถุที่ใหญ่ที่สุดในเกมบับเบิ้ลสองชิ้นในเวลาเดียวกัน"
+      flavor: "ปิ่นโตขนาดนี้ น่าจะเพิ่ม 🤯 🤯 เข้าไปนิดหน่อย"
 _role:
   new: "บทบาทใหม่"
   edit: "แก้ไขบทบาท"
   name: "ชื่อบทบาท"
   description: "คำอธิบายบทบาท"
   permission: "สิทธิ์ตามบทบาท"
-  descriptionOfPermission: "<b>ผู้ดูแลกลั่นกรองเนื้อหา</b> สามารถดำเนินการดูแลขั้นพื้นฐานได้นะ\n<b>ผู้ดูแลระบบ</b> สามารถเปลี่ยนการตั้งค่าทั้งหมดของอินสแตนซ์ได้นะ"
+  descriptionOfPermission: "<b>ผู้ควบคุม</b> สามารถดำเนินการดูแลขั้นพื้นฐานได้\n<b>ผู้ดูแลระบบ</b> สามารถเปลี่ยนการตั้งค่าทั้งหมดของอินสแตนซ์ได้"
   assignTarget: "มอบหมาย"
-  descriptionOfAssignTarget: "<b>แมนนวล</b> เพื่อเปลี่ยนผู้ที่เป็นส่วนหนึ่งของบทบาทนี้และใครที่ไม่ใช่ด้วยตนเอง\n<b>เงื่อนไข</b> เพื่อให้ผู้ใช้ได้รับการกำหนดและนำออกจากบทบาทนี้โดยอัตโนมัติตามเงื่อนไขชุดหนึ่ง"
+  descriptionOfAssignTarget: "แบบ<b>ปรับเอง</b> เพิ่มถอนบทบาทนี้แก่ผู้ใช้ด้วยตัวเอง\nแบบ<b>มีเงื่อนไข</b> เพิ่มถอนบทบาทนี้แก่ผู้ใช้โดยอัตโนมัติหากเข้าเงื่อนไขใดต่อไปนี้"
   manual: "ปรับเอง"
+  manualRoles: "บทบาทแบบทำมือ"
   conditional: "มีเงื่อนไข"
+  conditionalRoles: "บทบาทแบบมีเงื่อนไข"
   condition: "เงื่อนไข"
   isConditionalRole: "นี่คือบทบาทที่มีเงื่อนไข"
-  isPublic: "บทบาทสาธารณะ"
-  descriptionOfIsPublic: "ทุกคนสามารถดูได้ว่าผู้ใช้งานนั้นได้รับมอบหมายบทบาทด้วยหรือไม่ \n\nบทบาทจะแสดงในโปรไฟล์ของผู้ใช้ด้วย"
+  isPublic: "ทำให้บทบาทเปิดเผยต่อสาธารณะ"
+  descriptionOfIsPublic: "บทบาทจะปรากฏบนโปรไฟล์ของผู้ใช้และเปิดเผยต่อสาธารณะ (ทุกคนสามารถเห็นได้ว่าผู้ใช้รายนี้มีบทบาทนี้)"
   options: "ตัวเลือกบทบาท"
   policies: "นโยบาย"
-  baseRole: "บทบาทพื้นฐาน"
-  useBaseValue: "ใช้บทบาทพื้นฐานเริ่มต้น"
+  baseRole: "เทมเพลตบทบาท"
+  useBaseValue: "ใช้ตามเทมเพลตบทบาท"
   chooseRoleToAssign: "เลือกบทบาทที่ต้องการกำหนด"
-  iconUrl: "ไอคอน URL"
+  iconUrl: "URL ไอคอน"
   asBadge: "แสดงเป็นตรา"
-  descriptionOfAsBadge: "ไอคอนของบทบาทนี้จะปรากฏถัดจากชื่อผู้ใช้ของผู้ใช้งานด้วยบทบาทนี้ถ้าหากเปิดใช้งาน"
-  isExplorable: "บทบาทไทม์ไลน์เป็นแบบสาธารณะ"
-  descriptionOfIsExplorable: "ไทม์ไลน์ของบทบาทนี้จะสามารถเข้าถึงได้แบบสาธารณะถ้าหากเปิดใช้งาน เส้นเวลาของบทบาทนั้นจะไม่ถูกเปิดเผยต่อสาธารณะ ถึงแม้ว่าจะไม่เปิดเผยต่อสาธารณะแม้แต่ว่า...จะตั้งค่าไว้ยังไงก็ตาม"
-  displayOrder: "ตำแหน่ง"
-  descriptionOfDisplayOrder: "ยิ่งตัวเลขสูง ตำแหน่ง UI ก็ยิ่งสูงขึ้นนะ"
-  canEditMembersByModerator: "อนุญาตให้ผู้ดูแลแก้ไขสมาชิก"
-  descriptionOfCanEditMembersByModerator: "เมื่อเปิดใช้ ผู้ดูแลนอกเหนือจากผู้ดูแลระบบแล้ว จะสามารถกำหนดและยกเลิกการมอบหมายบทบาทนี้ให้กับผู้ใช้ได้ เมื่อปิด เฉพาะผู้ดูแลระบบเท่านั้นที่จะสามารถกำหนดผู้ใช้ได้นะ"
+  descriptionOfAsBadge: "เมื่อเปิดใช้งาน ไอคอนบทบาทจะปรากฏถัดจากชื่อผู้ใช้"
+  isExplorable: "ค้นหาผู้ใช้ได้ง่ายขึ้นโดยดูจากบทบาท"
+  descriptionOfIsExplorable: "เมื่อเปิดใช้งาน ไทมไลน์บทบาทนี้และสมาชิกที่มีบทบาทนี้จะเปิดเผยเป็นสาธารณะ"
+  displayOrder: "ลำดับการแสดงผล"
+  descriptionOfDisplayOrder: "เลขที่สูงกว่าจะแสดงบน UI ก่อน"
+  canEditMembersByModerator: "อนุญาตให้ผู้ควบคุมแก้ไขสมาชิก"
+  descriptionOfCanEditMembersByModerator: "เมื่อเปิดใช้ นอกเหนือจากผู้ควบคุมและผู้ดูแลระบบแล้ว จะสามารถเพิ่มถอนบทบาทนี้แก่ผู้ใช้ได้ แต่เมื่อปิดใช้ จะมีเฉพาะผู้ดูแลระบบเท่านั้นที่จะสามารถดำเนินการได้"
   priority: "ลำดับความสำคัญ"
   _priority:
     low: "ต่ำ"
@@ -1498,12 +1654,13 @@ _role:
   _options:
     gtlAvailable: "การดูไทม์ไลน์ทั่วโลก"
     ltlAvailable: "การดูไทม์ไลน์ในท้องถิ่น"
-    canPublicNote: "สามารถส่งโน้ตสาธารณะ"
+    canPublicNote: "สามารถโพสต์แบบสาธารณะ"
+    mentionMax: "จำนวนการกล่าวถึงสูงสุดต่อโน้ต"
     canInvite: "สร้างรหัสเชิญอินสแตนซ์"
     inviteLimit: "จำกัดการเชิญ"
-    inviteLimitCycle: "จำกัดการเชิญไว้คูลดาวน์"
+    inviteLimitCycle: "คูลดาวน์ในการเชิญ"
     inviteExpirationTime: "วันหมดอายุของรหัสการเชิญ"
-    canManageCustomEmojis: "จัดการอีโมจิแบบกำหนดเอง"
+    canManageCustomEmojis: "จัดการเอโมจิที่กำหนดเอง"
     canManageAvatarDecorations: "จัดการตกแต่งอวตาร"
     driveCapacity: "ความจุของไดรฟ์"
     alwaysMarkNsfw: "ทำเครื่องหมายไฟล์ว่าเป็น NSFW เสมอ"
@@ -1515,13 +1672,15 @@ _role:
     noteEachClipsMax: "จำนวนโน้ตสูงสุดภายในคลิป"
     userListMax: "จำนวนรายชื่อผู้ใช้สูงสุด"
     userEachUserListsMax: "จำนวนผู้ใช้สูงสุดภายในรายการผู้ใช้"
-    rateLimitFactor: "ขีดจำกัดอัตรา"
-    descriptionOfRateLimitFactor: "ขีดจํากัดอัตราที่ต่ำกว่ามีข้อจํากัดน้อยกว่าข้อจํากัดที่สูงกว่า"
+    rateLimitFactor: "อัตราการจำกัด"
+    descriptionOfRateLimitFactor: "ยิ่งตัวเลขน้อยก็ยิ่งจำกัดน้อย ยิ่งมากก็ยิ่งเข้มงวดมากขึ้น"
     canHideAds: "ซ่อนโฆษณา"
     canSearchNotes: "การใช้การค้นหาโน้ต"
     canUseTranslator: "การใช้งานแปล"
+    avatarDecorationLimit: "จำนวนการตกแต่งไอคอนสูงสุดที่สามารถติดตั้งได้"
   _condition:
-    isLocal: "ผู้ใช้ภายใน"
+    roleAssignedTo: "มอบหมายให้มีบทบาทแบบทำมือ"
+    isLocal: "ผู้ใช้ในพื้นที่"
     isRemote: "ผู้ใช้ระยะไกล"
     createdLessThan: "สร้างน้อยกว่า"
     createdMoreThan: "สร้างมากกว่า"
@@ -1535,10 +1694,10 @@ _role:
     or: "หรือ"
     not: "ไม่"
 _sensitiveMediaDetection:
-  description: "ลดความพยายามในการดูแลเซิร์ฟเวอร์ผ่านการจดจำสื่อ NSFW โดยอัตโนมัติผ่านการเรียนรู้ของเครื่อง การทำสิ่งนี้อาจจะเพิ่มภาระบนเซิร์ฟเวอร์เล็กน้อย"
-  sensitivity: "การตรวจจับความไว"
-  sensitivityDescription: "การลดความไวนั้นจะนำไปสู่การตรวจจับที่ผิดพลาดน้อยลง (ผลบวกที่ผิดพลาด) แต่ในขณะที่การเพิ่มนั้นจะนำไปสู่การตรวจหาที่พลาดน้อยลง (ผลลบเท็จ)"
-  setSensitiveFlagAutomatically: "ทำเครื่องหมายว่าเป็น NSFW"
+  description: "ใช้ Machine Learning เพื่อตรวจจับสื่อที่มีเนื้อหาละเอียดอ่อนโดยอัตโนมัติและใช้เพื่อการกลั่นกรอง ภาระของเซิร์ฟเวอร์จะเพิ่มขึ้นเล็กน้อย"
+  sensitivity: "ความไวในการตรวจจับ"
+  sensitivityDescription: "เมื่อความไวต่ำ Misdetection (ผลบวกลวง) จะลดลง, เมื่อความไวสูง Missed detection (ผลลบลวง) จะลดลง"
+  setSensitiveFlagAutomatically: "ทำเครื่องหมายว่ามีเนื้อหาละเอียดอ่อน"
   setSensitiveFlagAutomaticallyDescription: "ผลลัพธ์ของการตรวจจับภายในนั้นจะยังคงอยู่ ถึงแม้ว่าจะปิดตัวเลือกนี้"
   analyzeVideos: "เปิดใช้งานวิเคราะห์ของวิดีโอ"
   analyzeVideosDescription: "การวิเคราะห์วิดีโอนอกเหนือจากรูปภาพนั้น การทำสิ่งนี้จะทำให้เพิ่มภาระบนเซิร์ฟเวอร์เล็กน้อย"
@@ -1548,18 +1707,19 @@ _emailUnavailable:
   disposable: "ไม่สามารถใช้อีเมลชั่วคราวได้"
   mx: "เซิร์ฟเวอร์อีเมลนี้ไม่ถูกต้อง"
   smtp: "เซิร์ฟเวอร์อีเมลนี้ไม่มีการตอบสนอง"
+  banned: "คุณไม่สามารถลงทะเบียนด้วยที่อยู่อีเมลนี้ได้"
 _ffVisibility:
-  public: "เผยแพร่"
+  public: "สาธารณะ"
   followers: "ปรากฏให้แก่ผู้ติดตามเท่านั้น"
   private: "ส่วนตัว"
 _signup:
-  almostThere: "เกือบจะมี"
-  emailAddressInfo: "โปรดกรอกอีเมลของคุณ มันจะไม่เปิดเผยต่อสาธารณะ"
-  emailSent: "เราได้ส่งอีเมลยืนยันไปยังที่อยู่อีเมลของคุณแล้วนะ ({email}) โปรดคลิกลิงก์ที่รวมไว้เพื่อสร้างบัญชีให้เสร็จสิ้น"
+  almostThere: "เกือบจะเสร็จแล้ว"
+  emailAddressInfo: "กรุณากรอกที่อยู่อีเมลที่คุณใช้ ที่อยู่อีเมลของคุณจะไม่ถูกเผยแพร่สู่สาธารณชน"
+  emailSent: "อีเมลยืนยันได้ถูกส่งไปยังที่อยู่อีเมลที่คุณป้อน ({email}) แล้ว กรุณาติดตามลิงก์ในอีเมลเพื่อสร้างบัญชีให้เสร็จสมบูรณ์ ลิงก์ที่ให้ไว้จะหมดอายุใน 30 นาที"
 _accountDelete:
   accountDelete: "ลบบัญชีผู้ใช้"
   mayTakeTime: "เนื่องจากการลบบัญชีนี้จะเป็นกระบวนการที่ต้องใช้ทรัพยากรมาก จึงอาจจะต้องใช้เวลาสักครู่ถึงจะเสร็จสมบูรณ์ ทั้งนี้ขึ้นอยู่กับจำนวนเนื้อหาที่คุณสร้างและจำนวนไฟล์ที่คุณอัปโหลดนะ"
-  sendEmail: "เมื่อการลบบัญชีนี้เสร็จสิ้น เราอาจจะส่งอีเมลไปยังที่อยู่อีเมลของคุณที่เคยลงทะเบียนไว้กับบัญชีนี้นะ"
+  sendEmail: "เมื่อการลบบัญชีเสร็จสิ้น การแจ้งเตือนจะถูกส่งไปยังที่อยู่อีเมลที่ลงทะเบียนไว้"
   requestAccountDelete: "ร้องขอให้ลบบัญชี"
   started: "การลบได้เริ่มต้นขึ้น"
   inProgress: "ปัจจุบันกำลังดำเนินการลบอยู่"
@@ -1569,7 +1729,9 @@ _ad:
   hide: "ไม่ต้องแสดง"
   timezoneinfo: "วันในสัปดาห์นี้จะถูกกำหนดจากโซนเวลาของเซิร์ฟเวอร์"
   adsSettings: "ตั้งค่าการโฆษณา"
+  notesPerOneAd: "อัปเดตช่วงเวลาตำแหน่งโฆษณาแบบเรียลไทม์ (จำนวนโน้ตต่อโฆษณา)"
   setZeroToDisable: "ตั้งค่านี้ให้เป็น 0 เพื่อปิดใช้งานโฆษณาอัปเดตแบบเรียลไทม์"
+  adsTooClose: "เนื่องจากช่วงเวลาการแสดงโฆษณาสั้นมาก ประสบการณ์ผู้ใช้จึงอาจลดลงอย่างมาก"
 _forgotPassword:
   enterEmail: "ป้อนที่อยู่อีเมลที่คุณเคยใช้ในการลงทะเบียนไว้ ลิงก์ที่คุณสามารถรีเซ็ตรหัสผ่านได้นั้นจะถูกส่งไปนะ"
   ifNoEmail: "ถ้าหากคุณไม่ได้ใช้อีเมลระหว่างการลงทะเบียน กรุณาติดต่อผู้ดูแลระบบอินสแตนซ์แทนนะ"
@@ -1577,8 +1739,8 @@ _forgotPassword:
 _gallery:
   my: "แกลลอรี่ของฉัน"
   liked: "โพสต์ที่ถูกใจ"
-  like: "ชื่นชอบ"
-  unlike: "ลบไลค์"
+  like: "ถูกใจ!"
+  unlike: "เลิกถูกใจ"
 _email:
   _follow:
     title: "ได้ติดตามคุณ"
@@ -1591,7 +1753,7 @@ _plugin:
   viewSource: "ดูต้นฉบับ"
 _preferencesBackups:
   list: "สร้างการสำรองข้อมูล"
-  saveNew: "บันทึกใหม่"
+  saveNew: "บันทึกข้อมูลสำรองใหม่"
   loadFile: "โหลดจากไฟล์"
   apply: "นำไปใช้กับอุปกรณ์นี้"
   save: "บันทึก"
@@ -1601,8 +1763,8 @@ _preferencesBackups:
   applyConfirm: "คุณต้องการใช้ข้อมูลสำรอง \"{name}\" กับอุปกรณ์นี้อย่างงั้นจริงหรอ การตั้งค่าที่มีอยู่ของอุปกรณ์นี้จะถูกเขียนทับนะ"
   saveConfirm: "บันทึกข้อมูลสำรองเป็น {name} มั้ย?"
   deleteConfirm: "ลบข้อมูลสำรอง {name} มั้ย?"
-  renameConfirm: "เปลี่ยนชื่อข้อมูลสำรองนี้จาก \"{old}\" เป็น \"{new}\" หรือป่าว"
-  noBackups: "ไม่มีข้อมูลสำรองนะ คุณสามารถสำรองข้อมูลการตั้งค่าไคลเอนต์ของคุณบนเซิร์ฟเวอร์นี้โดยใช้ \"สร้างการสำรองข้อมูลใหม่\"ได้นะ"
+  renameConfirm: "ต้องการเปลี่ยนชื่อข้อมูลสำรองจาก “{old}” เป็น “{new}” ใช่ไหม?"
+  noBackups: "ไม่มีข้อมูลสำรอง สามารถบันทึกการตั้งค่าไคลเอนต์ปัจจุบันไปยังเซิร์ฟเวอร์ด้วย “บันทึกข้อมูลสำรองใหม่”"
   createdAt: "สร้างเมื่อ: {date} {time}"
   updatedAt: "อัปเดตเมื่อ: {date} {time}"
   cannotLoad: "การโหลดล้มเหลว"
@@ -1618,13 +1780,16 @@ _aboutMisskey:
   contributors: "ผู้สนับสนุนหลัก"
   allContributors: "ผู้มีส่วนร่วมทั้งหมด"
   source: "ซอร์สโค้ด"
+  original: "ต้นฉบับ"
+  thisIsModifiedVersion: "{name} ใช้ Misskey เวอร์ชันดัดแปลง"
   translation: "แปลภาษา Misskey"
   donate: "บริจาคให้กับ Misskey"
-  morePatrons: " ขอบคุณทุกท่านที่ร่วมกันช่วยเหลือตลอดมานะคะ 🥰"
-  patrons: "สมาชิกพันธมิตร"
+  morePatrons: "และอีกหลายท่านที่ไม่ได้เอ่ยนาม ขอบคุณที่ร่วมช่วยเหลือตลอดมานะคะ 🥰"
+  patrons: "ผู้อุปถัมภ์"
+  projectMembers: "สมาชิกในโครงการ"
 _displayOfSensitiveMedia:
-  respect: "ซ่อนสื่อทำเครื่องหมายบอกว่าละเอียดอ่อน"
-  ignore: "แสดงผลสื่อทำเครื่องหมายบอกว่าละเอียดอ่อน"
+  respect: "ซ่อนสื่อที่มีเนื้อหาละเอียดอ่อน"
+  ignore: "แสดงสื่อที่มีเนื้อหาละเอียดอ่อน"
   force: "ซ่อนสื่อทั้งหมด"
 _instanceTicker:
   none: "ไม่ต้องแสดง"
@@ -1635,17 +1800,18 @@ _serverDisconnectedBehavior:
   dialog: "แสดงกล่องโต้ตอบคำเตือน"
   quiet: "แสดงคำเตือนที่ไม่เป็นการรบกวน"
 _channel:
-  create: "สร้างแชนแนลใหม่"
-  edit: "แก้ไขแชนแนล"
+  create: "สร้างช่องใหม่"
+  edit: "แก้ไขช่อง"
   setBanner: "เซตแบนเนอร์"
   removeBanner: "ลบแบนเนอร์"
   featured: "เทรนด์"
   owned: "เจ้าของ"
   following: "ติดตามแล้ว"
   usersCount: "{n} ผู้เข้าร่วม"
-  notesCount: "{n} โน้ต"
+  notesCount: "มี {n} โน้ต"
   nameAndDescription: "ชื่อและคำอธิบาย"
   nameOnly: "ชื่อเท่านั้น"
+  allowRenoteToExternal: "อนุญาตให้รีโน้ตและอ้างอิงนอกช่องได้"
 _menuDisplay:
   sideFull: "ด้านข้าง"
   sideIcon: "ด้านข้าง (ไอคอน)"
@@ -1658,7 +1824,7 @@ _wordMute:
 _instanceMute:
   instanceMuteDescription: "การดำเนินการนี้จะปิดเสียง\"โน้ต/รีโน้ต\"จากอินสแตนซ์ที่อยู่ในรายการ รวมถึงบันทึกของผู้ใช้ที่ตอบกลับผู้ใช้จากอินสแตนซ์ที่ปิดเสียง"
   instanceMuteDescription2: "คั่นด้วยการขึ้นบรรทัดใหม่"
-  title: "ซ่อนโน้ตจากอินสแตนซ์ที่มีอยู่ในรายการ"
+  title: "ซ่อนโน้ตจากอินสแตนซ์ที่มีอยู่ในรายชื่อ"
   heading: "รายชื่ออินสแตนซ์ที่ถูกปิดเสียง"
 _theme:
   explore: "สำรวจธีม"
@@ -1691,8 +1857,8 @@ _theme:
   importInfo: "ถ้าหากต้องการป้อนโค้ดที่นี่ คุณยังสามารถนำเข้าไปยังโปรแกรมแก้ไขธีมได้"
   deleteConstantConfirm: "คุณต้องการลบค่าคงที่ {const} หรือป่าว?"
   keys:
-    accent: "เน้น"
-    bg: "ภาพพื้นหลัง"
+    accent: "สีหลัก"
+    bg: "พื้นหลัง"
     fg: "ข้อความ"
     focus: "โฟกัส"
     indicator: "ตัวบ่งชี้"
@@ -1728,15 +1894,23 @@ _theme:
     wallpaperOverlay: "วอลล์เปเปอร์ซ้อนทับ"
     badge: "ตรา"
     messageBg: "พื้นหลังแชท"
-    accentDarken: "เน้น (มืด)"
-    accentLighten: "เน้น (สว่าง)"
+    accentDarken: "สีหลัก (มืด)"
+    accentLighten: "สีหลัก (สว่าง)"
     fgHighlighted: "ข้อความที่ไฮไลต์"
 _sfx:
-  note: "หมายเหตุ"
+  note: "โน้ต"
   noteMy: "โน้ตของตัวเอง"
   notification: "การเเจ้งเตือน"
   antenna: "เสาอากาศ"
   channel: "การแจ้งเตือนช่อง"
+  reaction: "เมื่อเลือกรีแอคชั่น"
+_soundSettings:
+  driveFile: "ใช้เสียงจากไดรฟ์"
+  driveFileWarn: "เลือกไฟล์ในไดรฟ์ของคุณ"
+  driveFileTypeWarn: "ไม่รองรับไฟล์นี้"
+  driveFileTypeWarnDescription: "กรุณาเลือกไฟล์เสียง"
+  driveFileDurationWarn: "เสียงยาวเกินไป"
+  driveFileDurationWarnDescription: "การใช้เสียงที่ยาวอาจรบกวนการใช้งาน Misskey, ต้องการดำเนินการต่อหรือไม่?"
 _ago:
   future: "อนาคต"
   justNow: "เมื่อกี๊นี้"
@@ -1748,6 +1922,14 @@ _ago:
   monthsAgo: "{n} เดือนที่แล้ว"
   yearsAgo: "{n} ปีที่ผ่านมา"
   invalid: "ไม่พบผลลัพธ์"
+_timeIn:
+  seconds: "ใน {n} วินาที"
+  minutes: "ใน {n} นาที"
+  hours: "ใน {n} ชั่วโมง"
+  days: "ใน {n} วัน"
+  weeks: "ใน {n} สัปดาห์"
+  months: "ใน {n} เดือน"
+  years: "ใน {n} ปี"
 _time:
   second: "วินาที"
   minute: "นาที"
@@ -1777,7 +1959,9 @@ _2fa:
   renewTOTPConfirm: "วิธีการแบบนี้จะทําให้รหัสยืนยันจากแอพก่อนหน้าของคุณหยุดทํางานเลยนะ"
   renewTOTPOk: "ตั้งค่าคอนฟิกใหม่"
   renewTOTPCancel: "ไม่เป็นไร"
+  checkBackupCodesBeforeCloseThisWizard: "โปรดตรวจสอบรหัสสำรองด้านล่างก่อนที่จะปิดวิซาร์ดนี้"
   backupCodes: "รหัสสำรองข้อมูล"
+  backupCodesDescription: "หากแอปยืนยันตัวตนของคุณไม่พร้อมใช้งาน คุณสามารถใช้รหัสสำรองด้านล่างเพื่อเข้าถึงบัญชีของคุณได้ อย่าลืมเก็บรหัสเหล่านี้ไว้ในที่ปลอดภัย แต่ละรหัสสามารถใช้ได้เพียงครั้งเดียวเท่านั้น"
   backupCodeUsedWarning: "มีการใช้รหัสสำรองแล้ว โปรดกรุณากำหนดค่าการตรวจสอบสิทธิ์แบบสองปัจจัยโดยเร็วที่สุดถ้าหากคุณยังไม่สามารถใช้งานได้อีก"
   backupCodesExhaustedWarning: "รหัสสำรองทั้งหมดถูกใช้แล้ว ถ้าหากคุณยังสูญเสียการเข้าถึงแอปการตรวจสอบสิทธิ์แบบสองปัจจัยคุณจะยังไม่สามารถเข้าถึงบัญชีนี้ได้ กรุณากำหนดค่าการรับรองความถูกต้องด้วยการยืนยันสองชั้น"
 _permissions:
@@ -1798,29 +1982,78 @@ _permissions:
   "write:notes": "เขียนหรือลบโน้ต"
   "read:notifications": "ดูการแจ้งเตือนของคุณ"
   "write:notifications": "จัดการแจ้งเตือนของคุณ"
-  "read:reactions": "ดูปฏิกิริยาของคุณ"
-  "write:reactions": "แก้ไขปฏิกิริยาของคุณ"
+  "read:reactions": "ดูรีแอคชั่นของคุณ"
+  "write:reactions": "แก้ไขรีแอคชั่นของคุณ"
   "write:votes": "โหวตบนสำรวจความคิดเห็น"
-  "read:pages": "ดูหน้า"
+  "read:pages": "ดูหน้าเพจ"
   "write:pages": "แก้ไขหรือลบเพจของคุณ"
-  "read:page-likes": "ดูไลค์ของคุณบนเพจ"
-  "write:page-likes": "แก้ไขการถูกใจของคุณบนเพจ"
+  "read:page-likes": "ดูรายการเพจที่ถูกใจไว้"
+  "write:page-likes": "แก้ไขรายการเพจที่ถูกใจ"
   "read:user-groups": "ดูกลุ่มผู้ใช้ของคุณ"
   "write:user-groups": "แก้ไขหรือลบกลุ่มผู้ใช้ของคุณ"
   "read:channels": "ดูแชนแนลของคุณ"
   "write:channels": "แก้ไขแชนแนลของคุณ"
   "read:gallery": "ดูแกลเลอรี่"
   "write:gallery": "แก้ไขแกลเลอรี่ของคุณ"
-  "read:gallery-likes": "ดูรายการโพสต์ในแกลเลอรีที่ชอบของคุณ"
-  "write:gallery-likes": "แก้ไขรายการโพสต์ในแกลเลอรีที่ชอบของคุณ"
-  "read:flash": "วิว เพลย์"
-  "write:flash": "แก้ไขเพลย์"
-  "read:flash-likes": "ดูรายชื่อของไลค์ เพลย์"
-  "write:flash-likes": "แก้ไขรายชื่อของไลค์ เพลย์"
+  "read:gallery-likes": "ดูรายการโพสต์แกลเลอรีที่ถูกใจไว้"
+  "write:gallery-likes": "แก้ไขรายการโพสต์แกลเลอรีที่ถูกใจไว้"
+  "read:flash": "ดู Play"
+  "write:flash": "แก้ไข Play"
+  "read:flash-likes": "ดูรายการ  play ที่ถูกใจไว้"
+  "write:flash-likes": "แก้ไขรายการ play ที่ถูกใจไว้"
+  "read:admin:abuse-user-reports": "ดูรายงานจากผู้ใช้"
+  "write:admin:delete-account": "ลบบัญชีผู้ใช้"
+  "write:admin:delete-all-files-of-a-user": "ลบไฟล์ทั้งหมดของผู้ใช้"
+  "read:admin:index-stats": "ดูข้อมูลเกี่ยวกับดัชนีฐานข้อมูล"
+  "read:admin:table-stats": "ดูข้อมูลเกี่ยวกับตารางฐานข้อมูล"
+  "read:admin:user-ips": "ดูที่อยู่ IP ของผู้ใช้"
+  "read:admin:meta": "ดูข้อมูลเมตาของอินสแตนซ์"
+  "write:admin:reset-password": "รีเซ็ตรหัสผ่านของผู้ใช้"
+  "write:admin:resolve-abuse-user-report": "แก้ไขรายงานจากผู้ใช้"
+  "write:admin:send-email": "ส่งอีเมล"
+  "read:admin:server-info": "ดูข้อมูลเซิร์ฟเวอร์"
+  "read:admin:show-moderation-log": "ดูปูมการแก้ไข"
+  "read:admin:show-user": "ดูข้อมูลส่วนตัวของผู้ใช้"
+  "read:admin:show-users": "ดูข้อมูลส่วนตัวของผู้ใช้"
+  "write:admin:suspend-user": "ระงับผู้ใช้"
+  "write:admin:unset-user-avatar": "ลบอวตารผู้ใช้"
+  "write:admin:unset-user-banner": "ลบแบนเนอร์ผู้ใช้"
+  "write:admin:unsuspend-user": "ยกเลิกการระงับผู้ใช้"
+  "write:admin:meta": "จัดการข้อมูลเมตาของอินสแตนซ์"
+  "write:admin:user-note": "จัดการโน้ตการกลั่นกรอง"
+  "write:admin:roles": "จัดการบทบาท"
+  "read:admin:roles": "ดูบทบาท"
+  "write:admin:relays": "จัดการรีเลย์"
+  "read:admin:relays": "ดูรีเลย์"
+  "write:admin:invite-codes": "จัดการรหัสเชิญ"
+  "read:admin:invite-codes": "ดูรหัสเชิญ"
+  "write:admin:announcements": "จัดการประกาศ"
+  "read:admin:announcements": "ดูประกาศ"
+  "write:admin:avatar-decorations": "จัดการการตกแต่งอวตาร"
+  "read:admin:avatar-decorations": "ดูการตกแต่งอวตาร"
+  "write:admin:federation": "จัดการข้อมูลเกี่ยวกับสหพันธ์"
+  "write:admin:account": "จัดการบัญชีผู้ใช้"
+  "read:admin:account": "ดูข้อมูลเกี่ยวกับผู้ใช้"
+  "write:admin:emoji": "จัดการเอโมจิ"
+  "read:admin:emoji": "ดูเอโมจิ"
+  "write:admin:queue": "จัดการคิวงาน"
+  "read:admin:queue": "ดูข้อมูลเกี่ยวกับคิวงาน"
+  "write:admin:promo": "จัดการโน้ตโปรโมชั่น"
+  "write:admin:drive": "จัดการไดรฟ์ของผู้ใช้"
+  "read:admin:drive": "ดูข้อมูลเกี่ยวกับไดรฟ์ของผู้ใช้"
+  "read:admin:stream": "ใช้ Websocket API สำหรับผู้ดูแลระบบ"
+  "write:admin:ad": "จัดการโฆษณา"
+  "read:admin:ad": "ดูโฆษณา"
+  "write:invite-codes": "สร้างรหัสเชิญ"
+  "read:invite-codes": "รับรหัสเชิญ"
+  "write:clip-favorite": "ควบคุมการถูกใจของคลิป"
+  "read:clip-favorite": "ดูการถูกใจของคลิป"
+  "read:federation": "รับข้อมูลเกี่ยวกับสหพันธ์"
+  "write:report-abuse": "รายงานการละเมิด"
 _auth:
   shareAccessTitle: "การให้สิทธิ์แอปพลิเคชัน"
   shareAccess: "คุณต้องการอนุญาตให้ \"{name}\" เข้าถึงบัญชีนี้เลยมั้ย?"
-  shareAccessAsk: "คุณแน่ใจแล้วจริงๆหรอว่าต้องการอนุญาตให้แอปพลิเคชันนี้เข้าถึงบัญชีของคุณแน่ใจแล้วหรอ?"
+  shareAccessAsk: "ต้องการอนุญาตให้แอปพลิเคชันนี้เข้าถึงบัญชีของคุณหรือไม่?"
   permission: "{name} ได้ขอสิทธิ์การเข้าถึงดังต่อไปนี้"
   permissionAsk: "แอปพลิเคชันนี้ขอสิทธิ์ดังต่อไปนี้"
   pleaseGoBack: "กรุณากลับไปที่แอปพลิเคชัน"
@@ -1856,7 +2089,7 @@ _widgets:
   photos: "รูปภาพ"
   digitalClock: "นาฬิกาดิจิตอล"
   unixClock: "นาฬิกา UNIX"
-  federation: "Fediration"
+  federation: "สหพันธ์"
   instanceCloud: "อินสแตนซ์คลาวด์"
   postForm: "แบบฟอร์มการโพสต์"
   slideshow: "แสดงภาพนิ่ง"
@@ -1864,13 +2097,14 @@ _widgets:
   onlineUsers: "ผู้ใช้ที่ออนไลน์"
   jobQueue: "คิวงาน"
   serverMetric: "ตัวชี้วัดเซิร์ฟเวอร์"
-  aiscript: "AiScript คอนโซล"
-  aiscriptApp: "AiScript แอพ"
+  aiscript: " คอนโซล AiScript"
+  aiscriptApp: "แอป AiScript"
   aichan: "ไอ"
   userList: "รายชื่อผู้ใช้"
   _userList:
-    chooseList: "เลือกรายการ"
+    chooseList: "เลือกรายชื่อ"
   clicker: "คลิกเกอร์"
+  birthdayFollowings: "วันเกิดผู้ใช้ในวันนี้"
 _cw:
   hide: "ซ่อน"
   show: "โหลดเพิ่มเติม"
@@ -1878,15 +2112,15 @@ _cw:
   files: "{count} ไฟล์"
 _poll:
   noOnlyOneChoice: "จำเป็นต้องมีอย่างน้อยสองตัวเลือก"
-  choiceN: "ตัวเลือก {n}"
-  noMore: "คุณไม่สามารถเพิ่มตัวเลือกอื่นได้"
+  choiceN: "ตัวเลือกที่ {n}"
+  noMore: "เพิ่มตัวเลือกอีกไม่ได้แล้ว"
   canMultipleVote: "สามารถตอบได้หลายคำตอบ"
-  expiration: "สิ้นสุดการสำรวจความคิดเห็น"
-  infinite: "ไม่ต้องเลย"
-  at: "จบที่..."
-  after: "สิ้นสุดหลัง..."
+  expiration: "สิ้นสุดโพล"
+  infinite: "ไม่กำหนดระยะเวลา"
+  at: "ระบุวันเวลา"
+  after: "ระบุระยะเวลา"
   deadlineDate: "วันสิ้นสุด"
-  deadlineTime: "ชั่วโมง"
+  deadlineTime: "เวลา"
   duration: "ระยะเวลา"
   votesCount: "{n} คะแนนเสียง"
   totalVotes: "{n} คะแนนเสียงทั้งหมด"
@@ -1894,37 +2128,37 @@ _poll:
   showResult: "ดูผลลัพธ์"
   voted: "โหวตแล้ว"
   closed: "สิ้นสุดแล้ว"
-  remainingDays: "{d} วัน(s) {h} ชั่วโมง(s) ที่เหลืออยู่"
-  remainingHours: "{h} ชั่วโมง(s) {m} นาที(s) ที่เหลืออยู่"
-  remainingMinutes: "{m} นาที(s) {s} วินาที(s) ที่เหลืออยู่"
-  remainingSeconds: "{s} นาที(s) ที่เหลืออยู่"
+  remainingDays: "เหลืออีก {d} วัน {h} ชั่วโมง"
+  remainingHours: "เหลืออีก {h} ชั่วโมง {m} นาที"
+  remainingMinutes: "เหลืออีก {m} นาที {s} วินาที"
+  remainingSeconds: "เหลืออีก {s} วินาที"
 _visibility:
   public: "สาธารณะ"
   publicDescription: "โน้ตของคุณจะปรากฏแก่ผู้ใช้ทุกคน"
   home: "หน้าแรก"
   homeDescription: "โพสลงไทม์ไลน์ที่บ้านเท่านั้น"
   followers: "ผู้ติดตาม"
-  followersDescription: "ทำให้ผู้ติดตามนั้นมองเห็นแค่คุณเท่านั้น"
+  followersDescription: "เฉพาะผู้ติดตามเท่านั้นที่มองเห็นได้"
   specified: "ไดเร็ค"
   specifiedDescription: "ทำให้มองเห็นได้เฉพาะผู้ใช้ที่ระบุเท่านั้น"
-  disableFederation: "ไม่มีสหภาพ"
+  disableFederation: "ไม่มีสหพันธ์"
   disableFederationDescription: "อย่าส่งไปยังอินสแตนซ์อื่น"
 _postForm:
   replyPlaceholder: "ตอบกลับโน้ตนี้..."
   quotePlaceholder: "อ้างโน้ตนี้..."
   channelPlaceholder: "โพสต์ลงช่อง..."
   _placeholders:
-    a: "คุณเป็นอะไรไปหรอ?"
-    b: "เกิดอะไรขึ้นรอบตัวคุณ?"
-    c: "คุณกำลังคิดอะไรอยู่?"
-    d: "คุณต้องการจะพูดอะไร?"
-    e: "เริ่มเขียน..."
+    a: "ตอนนี้เป็นยังไงบ้าง?"
+    b: "มีอะไรเกิดขึ้นหรือเปล่า?"
+    c: "กำลังคิดอะไรอยู่?"
+    d: "ต้องการจะพูดอะไรไหม?"
+    e: "มาเขียนกันเถอะ"
     f: "กำลังรอให้คุณเขียน..."
 _profile:
   name: "ชื่อ"
   username: "ชื่อผู้ใช้"
-  description: "ประวัติ"
-  youCanIncludeHashtags: "คุณยังสามารถใส่แฮชแท็กในประวัติของคุณได้นะ"
+  description: "แนะนำตัว"
+  youCanIncludeHashtags: "คุณสามารถใส่แฮชแท็กในส่วนแนะนำตัวของคุณได้"
   metadata: "ข้อมูลเพิ่มเติม"
   metadataEdit: "แก้ไขข้อมูลเพิ่มเติม"
   metadataDescription: "ใช้สิ่งเหล่านี้ คุณสามารถแสดงฟิลด์ข้อมูลเพิ่มเติมในโปรไฟล์ของคุณ"
@@ -1932,14 +2166,16 @@ _profile:
   metadataContent: "เนื้อหา"
   changeAvatar: "เปลี่ยนอวาตาร์"
   changeBanner: "เปลี่ยนแบนเนอร์"
-  verifiedLinkDescription: "โดยการป้อน URL ที่มีลิงก์ไปยังโปรไฟล์ของคุณตรงนี้ ส่วนไอคอนการยืนยันความเป็นเจ้าของนั้นก็สามารถแสดงถัดจากฟิลด์ได้นะ"
+  verifiedLinkDescription: "หากป้อน URL ที่มีลิงก์ไปยังโปรไฟล์ของคุณ ไอคอนการยืนยันความเป็นเจ้าของจะแสดงถัดจากฟิลด์นั้น ๆ"
+  avatarDecorationMax: "คุณสามารถเพิ่มการตกแต่งได้สูงสุด {max}"
 _exportOrImport:
   allNotes: "โน้ตทั้งหมด"
-  favoritedNotes: "บันทึกที่ชื่นชอบ"
+  favoritedNotes: "โน้ตที่ถูกใจไว้"
+  clips: "คลิป"
   followingList: "กำลังติดตาม"
   muteList: "ปิดเสียง"
   blockingList: "บล็อค"
-  userLists: "รายการ"
+  userLists: "รายชื่อ"
   excludeMutingUsers: "ยกเว้นผู้ใช้ที่ปิดเสียง"
   excludeInactiveUsers: "ยกเว้นผู้ใช้ที่ไม่ได้ใช้งาน"
   withReplies: "รวมการตอบกลับจากผู้ใช้ที่นำเข้าไว้ในไทม์ไลน์"
@@ -1975,16 +2211,16 @@ _timelines:
   social: "โซเชี่ยล"
   global: "ทั่วโลก"
 _play:
-  new: "สร้างการเล่น"
-  edit: "แก้ไขเล่น"
-  created: "สร้างการเล่นแล้ว"
-  updated: "แก้ไขการเล่นแล้ว"
-  deleted: "ลบการเล่นแล้ว"
-  pageSetting: "ตั้งค่าการเล่น"
+  new: "สร้าง Play"
+  edit: "แก้ไข Play"
+  created: "สร้าง Play แล้ว"
+  updated: "แก้ไข Play แล้ว"
+  deleted: "ลบ Play แล้ว"
+  pageSetting: "ตั้งค่า Play"
   editThisPage: "แก้ไข Play นี้"
   viewSource: "ดูต้นฉบับ"
-  my: "มาย เพลย์"
-  liked: "ไลค์ เพลย์"
+  my: "Play ของฉัน"
+  liked: "Play ที่ถูกใจไว้"
   featured: "เป็นที่นิยม"
   title: "หัวข้อ"
   script: "สคริปต์"
@@ -1996,15 +2232,15 @@ _pages:
   created: "สร้างหน้าเพจสำเร็จเรียบร้อยแล้ว"
   updated: "แก้ไขหน้าเพจสำเร็จเรียบร้อยแล้ว"
   deleted: "ลบหน้าเพจสำเร็จเรียบร้อยแล้ว"
-  pageSetting: "การตั้งค่าหน้า"
+  pageSetting: "การตั้งค่าหน้าเพจ"
   nameAlreadyExists: "URL ของหน้าที่ระบุนั้นมีอยู่แล้ว"
   invalidNameTitle: "URL ของหน้าที่ระบุนั้นไม่ถูกต้อง"
   invalidNameText: "ตรวจสอบให้แน่ใจนะว่าชื่อหน้าไม่ว่างเปล่า"
   editThisPage: "แก้ไขเพจนี้"
   viewSource: "ดูต้นฉบับ"
-  viewPage: "ดูหน้า"
+  viewPage: "ดูหน้าเพจ"
   like: "ถูกใจ"
-  unlike: "ลบไลค์"
+  unlike: "เลิกถูกใจ"
   my: "หน้าเพจของฉัน"
   liked: "หน้าเพจที่ถูกใจ"
   featured: "เป็นที่นิยม"
@@ -2017,7 +2253,7 @@ _pages:
   summary: "สรุปเพจ"
   alignCenter: "เซ็นเตอร์"
   hideTitleWhenPinned: "ซ่อนชื่อหน้าเพจเมื่อปักหมุดไว้ที่โปรไฟล์"
-  font: "ตัวอักษร"
+  font: "แบบอักษร"
   fontSerif: "Serif"
   fontSansSerif: "Sans Serif"
   eyeCatchingImageSet: "ตั้งค่าภาพขนาดย่อ"
@@ -2025,7 +2261,7 @@ _pages:
   chooseBlock: "เพิ่มบล็อค"
   selectType: "เลือกชนิด"
   contentBlocks: "เนื้อหา"
-  inputBlocks: "อินพุต"
+  inputBlocks: "ป้อนข้อมูล"
   specialBlocks: "พิเศษ"
   blocks:
     text: "ข้อความ"
@@ -2043,23 +2279,28 @@ _relayStatus:
   accepted: "ได้รับการอนุมัติ"
   rejected: "ถูกปฏิเสธ"
 _notification:
-  fileUploaded: "ไฟล์ถูกอัพโหลดแล้วน่ะ"
+  fileUploaded: "ไฟล์ถูกอัปโหลดแล้ว"
   youGotMention: "{name} กล่าวถึงคุณ"
   youGotReply: "{name} ตอบกลับถึงคุณ"
-  youGotQuote: "{name} อ้างถึงคุณ"
+  youGotQuote: "{name} อ้างอิงคุณ"
   youRenoted: "รีโน้ตจาก {name}"
   youWereFollowed: "ได้ติดตามคุณ"
-  youReceivedFollowRequest: "คุณมีคำขอติดตามใหม่น่ะ"
-  yourFollowRequestAccepted: "คำขอติดตามของคุณได้รับการยอมรับแล้วน่ะ"
-  pollEnded: "โพลสำรวจความคิดเห็นผลลัพธ์มีพร้อมใช้งาน"
+  youReceivedFollowRequest: "ได้รับคำขอติดตาม"
+  yourFollowRequestAccepted: "คำขอติดตามได้รับการอนุมัติแล้ว"
+  pollEnded: "ผลโพลออกมาแล้ว"
   newNote: "โพสต์ใหม่"
   unreadAntennaNote: "เสาอากาศ {name}"
-  emptyPushNotificationMessage: "การแจ้งเตือนแบบพุชได้รับการอัพเดทแล้ว"
+  roleAssigned: "ได้รับบทบาท"
+  emptyPushNotificationMessage: "อัปเดตการแจ้งเตือนแบบพุชแล้ว"
   achievementEarned: "รับความสำเร็จ"
   testNotification: "ทดสอบการแจ้งเตือน"
-  checkNotificationBehavior: "ตรวจสอบลักษณะที่ปรากฏการแจ้งเตือน"
+  checkNotificationBehavior: "กดเพื่อดูลักษณะการแจ้งเตือน"
   sendTestNotification: "ส่งทดสอบการแจ้งเตือน"
   notificationWillBeDisplayedLikeThis: "การแจ้งเตือนมีลักษณะแบบนี้"
+  reactedBySomeUsers: "ถูกรีแอคชั่นโดยผู้ใช้ {n} ราย"
+  renotedBySomeUsers: "รีโน้ตจากผู้ใช้ {n} ราย"
+  followedBySomeUsers: "มีผู้ติดตาม {n} ราย"
+  flushNotification: "ล้างประวัติการแจ้งเตือน"
   _types:
     all: "ทั้งหมด"
     note: "โน้ตใหม่"
@@ -2069,9 +2310,10 @@ _notification:
     renote: "รีโน้ต"
     quote: "อ้างคำพูด"
     reaction: "รีแอคชั่น"
-    pollEnded: "โพลนี้สิ้นสุดลงแล้ว"
-    receiveFollowRequest: "ได้รับคำขอติดตาม\n"
-    followRequestAccepted: "ยอมรับคำขอติดตาม"
+    pollEnded: "โพลสิ้นสุดแล้ว"
+    receiveFollowRequest: "ได้รับคำร้องขอติดตาม"
+    followRequestAccepted: "อนุมัติให้ติดตามแล้ว"
+    roleAssigned: "ให้บทบาท"
     achievementEarned: "ปลดล็อกความสำเร็จแล้ว"
     app: "การแจ้งเตือนจากแอปที่มีลิงก์"
   _actions:
@@ -2105,9 +2347,9 @@ _deck:
     tl: "ไทม์ไลน์"
     antenna: "เสาอากาศ"
     list: "รายการ"
-    channel: "แชนแนล"
+    channel: "ช่อง"
     mentions: "พูดถึง"
-    direct: "ไดเร็ค"
+    direct: "ไดเร็กต์"
     roleTimeline: "บทบาทไทม์ไลน์"
 _dialog:
   charactersExceeded: "คุณกำลังมีตัวอักขระเกินขีดจำกัดสูงสุดแล้วนะ! ปัจจุบันอยู่ที่ {current} จาก {max}"
@@ -2138,11 +2380,11 @@ _moderationLogTypes:
   updateRole: "อัปเดตบทบาทแล้ว"
   assignRole: "ได้รับมอบหมายบทบาท"
   unassignRole: "ถอดออกจากบทบาทแล้ว"
-  suspend: "ถูกระงับ"
-  unsuspend: "เลิกถูกระงับ"
-  addCustomEmoji: "เพิ่มอีโมจิที่กำหนดเองแล้ว"
-  updateCustomEmoji: "อัปเดตอีโมจิที่กำหนดเองแล้ว"
-  deleteCustomEmoji: "ลบอีโมจิที่กำหนดเองออกแล้ว"
+  suspend: "ระงับ"
+  unsuspend: "เลิกระงับ"
+  addCustomEmoji: "เพิ่มเอโมจิที่กำหนดเองแล้ว"
+  updateCustomEmoji: "อัปเดตเอโมจิที่กำหนดเองแล้ว"
+  deleteCustomEmoji: "ลบเอโมจิที่กำหนดเองออกแล้ว"
   updateServerSettings: "อัปเดตการตั้งค่าเซิร์ฟเวอร์แล้ว"
   updateUserNote: "อัปเดตโน้ตการกลั่นกรองแล้ว"
   deleteDriveFile: "ลบไฟล์ออกแล้ว"
@@ -2154,15 +2396,21 @@ _moderationLogTypes:
   deleteGlobalAnnouncement: "ลบประกาศทั่วโลกออกแล้ว"
   deleteUserAnnouncement: "ลบประกาศผู้ใช้ออกแล้ว"
   resetPassword: "รีเซ็ตรหัสผ่าน"
-  suspendRemoteInstance: "อินสแตนซ์ระยะไกลถูกระงับ"
-  unsuspendRemoteInstance: "อินสแตนซ์ระยะไกลเลิกการระงับ"
-  markSensitiveDriveFile: "ทำเครื่องหมายไฟล์บอกว่าละเอียดอ่อน"
-  unmarkSensitiveDriveFile: "ยกเลิกทำเครื่องหมายไฟล์ว่าละเอียดอ่อน"
+  suspendRemoteInstance: "ระงับอินสแตนซ์ระยะไกล"
+  unsuspendRemoteInstance: "เลิกระงับอินสแตนซ์ระยะไกล"
+  updateRemoteInstanceNote: "อัปเดตโน้ตการกลั่นกรองของอินสแตนซ์ระยะไกลแล้ว"
+  markSensitiveDriveFile: "ทำเครื่องหมายไฟล์ว่ามีเนื้อหาละเอียดอ่อน"
+  unmarkSensitiveDriveFile: "ยกเลิกทำเครื่องหมายไฟล์ว่ามีเนื้อหาละเอียดอ่อน"
   resolveAbuseReport: "รายงานได้รับการแก้ไขแล้ว"
-  createInvitation: "สร้างคำเชิญ"
+  createInvitation: "สร้างรหัสเชิญ"
   createAd: "สร้างโฆษณาแล้ว"
   deleteAd: "ลบโฆษณาออกแล้ว"
   updateAd: "อัปเดตโฆษณาแล้ว"
+  createAvatarDecoration: "สร้างการตกแต่งไอคอนแล้ว"
+  updateAvatarDecoration: "อัปเดตการตกแต่งไอคอนแล้ว"
+  deleteAvatarDecoration: "ลบการตกแต่งไอคอนแล้ว"
+  unsetUserAvatar: "ลบไอคอนผู้ใช้"
+  unsetUserBanner: "ลบแบนเนอร์ผู้ใช้"
 _fileViewer:
   title: "รายละเอียดไฟล์"
   type: "ประเภทไฟล์"
@@ -2172,14 +2420,108 @@ _fileViewer:
   attachedNotes: "โน้ตที่แนบมาด้วย"
   thisPageCanBeSeenFromTheAuthor: "หน้าเพจนี้จะสามารถปรากฏได้โดยผู้ใช้ที่อัปโหลดไฟล์นี้เท่านั้น"
 _externalResourceInstaller:
+  title: "ติดตั้งจากไซต์ภายนอก"
+  checkVendorBeforeInstall: "โปรดตรวจสอบให้แน่ใจว่าแหล่งแจกหน่ายมีความน่าเชื่อถือก่อนทำการติดตั้ง"
   _plugin:
+    title: "ต้องการติดตั้งปลั๊กอินนี้หรือไม่?"
     metaTitle: "ข้อมูลส่วนเสริม"
   _theme:
+    title: "ต้องการติดตั้งธีมนี้หรือไม่?"
     metaTitle: "ข้อมูลธีม"
+  _meta:
+    base: "โทนสีพื้นฐาน"
   _vendorInfo:
     title: "ข้อมูลผู้จัดจำหน่าย"
+    endpoint: "จุดอ้างอิงปลายทาง (Referenced endpoint)"
+    hashVerify: "การตรวจสอบแฮช (ความสมบูรณ์ของไฟล์)"
   _errors:
+    _invalidParams:
+      title: "พารามิเตอร์ไม่ถูกต้อง"
+      description: "มีสารสนเทศไม่เพียงพอที่จะโหลดข้อมูลจากไซต์ภายนอก โปรดยืนยัน URL ที่ป้อน"
+    _resourceTypeNotSupported:
+      title: "ไม่รองรับทรัพยากรภายนอกนี้"
+      description: "ไม่รองรับประเภทของทรัพยากรภายนอกนี้ โปรดติดต่อผู้ดูแลเว็บไซต์"
+    _failedToFetch:
+      title: "รับข้อมูลล้มเหลว"
+      fetchErrorDescription: "เกิดข้อผิดพลาดในการสื่อสารกับไซต์ภายนอก หากการลองอีกครั้งไม่สามารถแก้ไขปัญหานี้ได้ โปรดติดต่อผู้ดูแลไซต์"
+      parseErrorDescription: "เกิดข้อผิดพลาดในการประมวลผลข้อมูลที่โหลดจากไซต์ภายนอก โปรดติดต่อผู้ดูแลเว็บไซต์"
+    _hashUnmatched:
+      title: "การยืนยัน/ตรวจสอบข้อมูลล้มเหลว"
+      description: "เกิดข้อผิดพลาดในการตรวจสอบความสมบูรณ์ของข้อมูลที่ดึงมา เพื่อเป็นมาตรการรักษาความปลอดภัย การติดตั้งไม่สามารถดำเนินการต่อได้ โปรดติดต่อผู้ดูแลเว็บไซต์"
     _pluginParseFailed:
       title: "ข้อผิดพลาด AiScript"
+      description: "ดึงข้อมูลที่ร้องขอสำเร็จแล้ว แต่มีข้อผิดพลาดเกิดขึ้นระหว่างการแยกวิเคราะห์ AiScript โปรดติดต่อผู้เขียนปลั๊กอิน รายละเอียดข้อผิดพลาดสามารถดูได้ในคอนโซล Javascript"
+    _pluginInstallFailed:
+      title: "ติดตั้งปลั๊กอินล้มเหลว"
+      description: "เกิดปัญหาขณะติดตั้งปลั๊กอิน กรุณาลองอีกครั้ง. โปรดดูคอนโซล Javascript สำหรับรายละเอียดข้อผิดพลาด"
     _themeParseFailed:
       title: "การแยกวิเคราะห์ธีมล้มเหลว"
+      description: "ดึงข้อมูลที่ร้องขอสำเร็จแล้ว แต่มีข้อผิดพลาดเกิดขึ้นระหว่างการแยกวิเคราะห์ธีม โปรดติดต่อผู้เขียนธีม รายละเอียดข้อผิดพลาดสามารถดูได้ในคอนโซล Javascript"
+    _themeInstallFailed:
+      title: "ติดตั้งธีมล้มเหลว"
+      description: "เกิดปัญหาระหว่างการติดตั้งธีม กรุณาลองอีกครั้ง. รายละเอียดข้อผิดพลาดสามารถดูได้ในคอนโซล Javascript"
+_dataSaver:
+  _media:
+    title: "โหลดมีเดีย"
+    description: "กันไม่ให้ภาพและวิดีโอโหลดโดยอัตโนมัติ แตะรูปภาพ/วิดีโอที่ซ่อนอยู่เพื่อโหลด"
+  _avatar:
+    title: "รูปไอคอน"
+    description: "ระงับการเคลื่อนไหวของภาพไอคอน ภาพเคลื่อนไหวอาจมีขนาดไฟล์ใหญ่กว่าภาพปกติ ดังนั้นจึงสามารถช่วยในการลดการใช้ข้อมูล"
+  _urlPreview:
+    title: "ธัมบ์เนลแสดงตัวอย่าง URL"
+    description: "ธัมบ์เนลแสดงตัวอย่าง URL จะไม่โหลดโดยอัตโนมัติ"
+  _code:
+    title: "ไฮไลต์โค้ด"
+    description: "หากใช้สัญลักษณ์ไฮไลต์โค้ดใน MFM ฯลฯ สัญลักษณ์เหล่านั้นจะไม่โหลดจนกว่าจะแตะ การไฮไลต์ไวยากรณ์(syntax)จำเป็นต้องดาวน์โหลดไฟล์คำจำกัดความของไฮไลต์สำหรับแต่ละภาษา ดังนั้นการปิดใช้งานการโหลดไฟล์เหล่านี้โดยอัตโนมัติจึงคาดว่าจะช่วยลดปริมาณข้อมูลการสื่อสารได้"
+_hemisphere:
+  N: "ซีกโลกเหนือ"
+  S: "ซีกโลกใต้"
+  caption: "ใช้เพื่อกำหนดฤดูกาลของไคลเอ็นต์"
+_reversi:
+  reversi: "รีเวอร์ซี"
+  gameSettings: "ตั้งค่าการเล่น"
+  chooseBoard: "เลือกกระดาน"
+  blackOrWhite: "ดำ/ขาว"
+  blackIs: "{name}เป็นสีดำ"
+  rules: "กฎ"
+  thisGameIsStartedSoon: "การเล่นจะเริ่มแล้ว"
+  waitingForOther: "กำลังรออีกฝ่ายเตรียมตัวให้เสร็จ"
+  waitingForMe: "กำลังรอฝ่ายคุณเตรียมตัวให้เสร็จ"
+  waitingBoth: "กรุณาเตรียมตัว"
+  ready: "เตรียมตัวพร้อมแล้ว"
+  cancelReady: "ยกเลิกการเตรียมตัวพร้อม"
+  opponentTurn: "ตาอีกฝ่าย"
+  myTurn: "ตาของคุณ"
+  turnOf: "ตาของ{name}"
+  pastTurnOf: "ตาของ{name}"
+  surrender: "ยอมแพ้"
+  surrendered: "ยอมแพ้แล้ว"
+  timeout: "หมดเวลาแล้ว"
+  drawn: "เสมอ"
+  won: "{name}ชนะ"
+  black: "ดำ"
+  white: "ขาว"
+  total: "รวมทั้งหมด"
+  turnCount: "ตาที่{count}"
+  myGames: "การเล่นของตัวเอง"
+  allGames: "การเล่นของทุกคน"
+  ended: "จบ"
+  playing: "กำลังเล่น"
+  isLlotheo: "คนที่มีตัวหมากน้อยกว่าชนะ (Roseo)"
+  loopedMap: "ลูปแมป"
+  canPutEverywhere: "โหมดที่สามารถวางได้ทุกที่"
+  timeLimitForEachTurn: "จำกัดเวลาต่อแต่ละตา"
+  freeMatch: "ฟรีแมตช์"
+  lookingForPlayer: "กำลังมองหาคู่ต่อสู้อยู่"
+  gameCanceled: "ยกเลิกการเล่นแล้ว"
+  shareToTlTheGameWhenStart: "โพสต์ลงไทม์ไลน์เมื่อเริ่มการเล่น"
+  iStartedAGame: "เริ่มเล่นหมากรีเวอร์ซีแล้ว! #MisskeyReversi"
+  opponentHasSettingsChanged: "อีกฝ่ายเปลี่ยนการตั้งค่า"
+  allowIrregularRules: "อนุญาตกฎที่ไม่ปรกติ (โหมดฟรีทุกอย่าง)"
+  disallowIrregularRules: "ไม่อนุญาตกฎที่ไม่ปรกติ"
+  showBoardLabels: "แสดงหมายเลขแถว/คอลัมน์บนกระดาน"
+  useAvatarAsStone: "ใช้รูปอวตารเป็นหมาก"
+_offlineScreen:
+  title: "ออฟไลน์ - ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ได้"
+  header: "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ได้"
+
diff --git a/locales/tr-TR.yml b/locales/tr-TR.yml
index 0793592d34..e93a6e43e1 100644
--- a/locales/tr-TR.yml
+++ b/locales/tr-TR.yml
@@ -455,3 +455,4 @@ _deck:
 _moderationLogTypes:
   suspend: "askıya al"
   resetPassword: "Şifre sıfırlama"
+
diff --git a/locales/ug-CN.yml b/locales/ug-CN.yml
index e48f64511c..e06cee11a2 100644
--- a/locales/ug-CN.yml
+++ b/locales/ug-CN.yml
@@ -17,3 +17,4 @@ _2fa:
   renewTOTPCancel: "ئۇنى توختىتىڭ"
 _widgets:
   profile: "profile"
+
diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml
index 9b609edebb..df36f43c06 100644
--- a/locales/uk-UA.yml
+++ b/locales/uk-UA.yml
@@ -352,6 +352,8 @@ hcaptcha: "hCaptcha"
 enableHcaptcha: "Увімкнути hCaptcha"
 hcaptchaSiteKey: "Ключ сайту"
 hcaptchaSecretKey: "Секретний ключ"
+mcaptchaSiteKey: "Ключ сайту"
+mcaptchaSecretKey: "Секретний ключ"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "Увімкнути reCAPTCHA"
 recaptchaSiteKey: "Ключ сайту"
@@ -909,7 +911,9 @@ youFollowing: "Підписки"
 icon: "Аватар"
 replies: "Відповісти"
 renotes: "Поширити"
+sourceCode: "Вихідний код"
 flip: "Перевернути"
+lastNDays: "Останні {n} днів"
 _achievements:
   earnedAt: "Відкрито"
   _types:
@@ -1468,6 +1472,7 @@ _profile:
   changeBanner: "Змінити банер"
 _exportOrImport:
   allNotes: "Всі нотатки"
+  clips: "Добірка"
   followingList: "Підписки"
   muteList: "Ігнорувати"
   blockingList: "Заблокувати"
@@ -1616,3 +1621,6 @@ _webhookSettings:
 _moderationLogTypes:
   suspend: "Призупинити"
   resetPassword: "Скинути пароль"
+_reversi:
+  total: "Всього"
+
diff --git a/locales/uz-UZ.yml b/locales/uz-UZ.yml
index 3d00e47394..a79a76066a 100644
--- a/locales/uz-UZ.yml
+++ b/locales/uz-UZ.yml
@@ -366,6 +366,8 @@ hcaptcha: "hCaptcha"
 enableHcaptcha: "hCaptchani yoqish"
 hcaptchaSiteKey: "Sayt kaliti"
 hcaptchaSecretKey: "Mahfiy kalit"
+mcaptchaSiteKey: "Sayt kaliti"
+mcaptchaSecretKey: "Maxfiy kalit"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "reCAPTCHA ni yoqish"
 recaptchaSiteKey: "Sayt kaliti"
@@ -973,6 +975,7 @@ _profile:
   changeBanner: "Bannerni o'zgartirish"
 _exportOrImport:
   allNotes: "Barcha qaydlar"
+  clips: "Klip"
   followingList: "Obuna bo‘lish"
   muteList: "Ovozni o‘chirish"
   blockingList: "Bloklangan foydalanuvchilar"
@@ -1085,3 +1088,6 @@ _webhookSettings:
 _moderationLogTypes:
   suspend: "To'xtatish"
   resetPassword: "Parolni tiklash"
+_reversi:
+  total: "Jami"
+
diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml
index e4f7d27423..15530a5cd3 100644
--- a/locales/vi-VN.yml
+++ b/locales/vi-VN.yml
@@ -368,6 +368,8 @@ hcaptcha: "hCaptcha"
 enableHcaptcha: "Bật hCaptcha"
 hcaptchaSiteKey: "Khóa của trang"
 hcaptchaSecretKey: "Khóa bí mật"
+mcaptchaSiteKey: "Khóa của trang"
+mcaptchaSecretKey: "Khóa bí mật"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "Bật reCAPTCHA"
 recaptchaSiteKey: "Khóa của trang"
@@ -1043,7 +1045,10 @@ loadReplies: "Hiển thị các trả lời"
 pinnedList: "Các mục đã được ghim"
 keepScreenOn: "Giữ màn hình luôn bật"
 verifiedLink: "Chúng tôi đã xác nhận bạn là chủ sở hữu của đường dẫn này"
+sourceCode: "Mã nguồn"
 flip: "Lật"
+lastNDays: "{n} ngày trước"
+surrender: "Từ chối"
 _announcement:
   forExistingUsers: "Chỉ những người dùng đã tồn tại"
   forExistingUsersDescription: "Nếu được bật, thông báo này sẽ chỉ hiển thị với những người dùng đã tồn tại vào lúc thông báo được tạo. Nếu tắt đi, những tài khoản mới đăng ký sau khi thông báo được đăng lên cũng sẽ thấy nó."
@@ -1669,6 +1674,7 @@ _profile:
 _exportOrImport:
   allNotes: "Toàn bộ tút"
   favoritedNotes: "Bài viết đã thích"
+  clips: "Lưu bài viết"
   followingList: "Đang theo dõi"
   muteList: "Ẩn"
   blockingList: "Chặn"
@@ -1846,3 +1852,6 @@ _webhookSettings:
 _moderationLogTypes:
   suspend: "Vô hiệu hóa"
   resetPassword: "Đặt lại mật khẩu"
+_reversi:
+  total: "Tổng cộng"
+
diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml
index bfacc03e0a..17ad6e7150 100644
--- a/locales/zh-CN.yml
+++ b/locales/zh-CN.yml
@@ -11,7 +11,7 @@ password: "密码"
 forgotPassword: "忘记密码"
 fetchingAsApObject: "在联邦宇宙查询中..."
 ok: "OK"
-gotIt: "我明白了"
+gotIt: "好"
 cancel: "取消"
 noThankYou: "不用,谢谢"
 enterUsername: "输入用户名"
@@ -121,14 +121,21 @@ sensitive: "敏感内容"
 add: "添加"
 reaction: "回应"
 reactions: "回应"
+emojiPicker: "表情符号选择器"
+pinnedEmojisForReactionSettingDescription: "可以设置发表回应时置顶显示的表情符号"
+pinnedEmojisSettingDescription: "可以设置输入表情符号时置顶显示的表情符号"
+emojiPickerDisplay: "选择器显示设置"
+overwriteFromPinnedEmojisForReaction: "从「置顶(回应)」设置覆盖"
+overwriteFromPinnedEmojis: "从全局设置覆盖"
 reactionSettingDescription2: "拖动重新排序,单击删除,点击 + 添加。"
 rememberNoteVisibility: "保存上次设置的可见性"
 attachCancel: "删除附件"
+deleteFile: "删除文件"
 markAsSensitive: "标记为敏感内容"
 unmarkAsSensitive: "取消标记为敏感内容"
 enterFileName: "输入文件名"
 mute: "屏蔽"
-unmute: "解除屏蔽"
+unmute: "解除静音"
 renoteMute: "屏蔽转帖"
 renoteUnmute: "解除屏蔽转帖"
 block: "拉黑"
@@ -209,15 +216,15 @@ instanceInfo: "服务器信息"
 statistics: "统计"
 clearQueue: "清除队列"
 clearQueueConfirmTitle: "确定清除队列?"
-clearQueueConfirmText: "未送达的帖子将不会投递。 通常,您不需要这样做。"
+clearQueueConfirmText: "未送达的帖子将不会被投递。 通常无需执行此操作。"
 clearCachedFiles: "清除缓存"
-clearCachedFilesConfirm: "确定要清除缓存文件?"
+clearCachedFilesConfirm: "确定要清除所有缓存的远程文件?"
 blockedInstances: "被封锁的服务器"
 blockedInstancesDescription: "设定要封锁的服务器,以换行来进行分割。被封锁的服务器将无法与本服务器进行交换通讯。子域名也同样会被封锁。"
-silencedInstances: "沉默的服务器"
-silencedInstancesDescription: "设置要静音的服务器的主机,以换行符分隔。属于静默服务器的所有帐户都将被视为“静默”,所有关注都将成为请求,并且您将无法提及非关注者的本地帐户。被阻止的实例不受影响。"
-muteAndBlock: "屏蔽/拉黑"
-mutedUsers: "已屏蔽用户"
+silencedInstances: "被静音的服务器"
+silencedInstancesDescription: "设置要静音的服务器,以换行符分隔。被静音的服务器内所有的账户将默认处于「静音」状态,仅能发送关注请求,并且在未关注状态下无法提及本地账户。被阻止的实例不受影响。"
+muteAndBlock: "静音/拉黑"
+mutedUsers: "已静音用户"
 blockedUsers: "已拉黑的用户"
 noUsers: "无用户"
 editProfile: "编辑资料"
@@ -260,6 +267,7 @@ removed: "已删除"
 removeAreYouSure: "要删掉「{x}」吗?"
 deleteAreYouSure: "要删掉「{x}」吗?"
 resetAreYouSure: "恢复默认设置?"
+areYouSure: "你确定吗?"
 saved: "已保存"
 messaging: "聊天"
 upload: "本地上传"
@@ -328,7 +336,7 @@ displayOfSensitiveMedia: "显示敏感媒体"
 whenServerDisconnected: "与服务器连接中断时"
 disconnectedFromServer: "已和服务器断开连接"
 reload: "重新加载"
-doNothing: "关闭弹窗"
+doNothing: "关闭"
 reloadConfirm: "确定要重新加载吗?"
 watch: "关注"
 unwatch: "取消关注"
@@ -352,7 +360,7 @@ connectService: "连接"
 disconnectService: "断开连接"
 enableLocalTimeline: "启用本地时间线"
 enableGlobalTimeline: "启用全局时间线"
-disablingTimelinesInfo: "即使时间线功能被禁用,出于方便,管理员和协作者也可以继续使用。"
+disablingTimelinesInfo: "即使时间线功能被禁用,出于方便,管理员和监察员也可以继续使用。"
 registration: "注册"
 enableRegistration: "允许任何人注册"
 invite: "邀请"
@@ -372,15 +380,20 @@ hcaptcha: "hCaptcha"
 enableHcaptcha: "启用 hCaptcha"
 hcaptchaSiteKey: "网站密钥"
 hcaptchaSecretKey: "hCaptcha 密钥(SecretKey)"
+mcaptcha: "mCaptcha"
+enableMcaptcha: "启用 mCaptcha"
+mcaptchaSiteKey: "网站密钥"
+mcaptchaSecretKey: "mCaptcha 密钥(SecretKey)"
+mcaptchaInstanceUrl: "mCaptcha 实例地址"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "启用 reCAPTCHA\n(请注意, 此功能在中国大陆不可用. 如果启用, 可能导致无法正常使用登录或注册等功能)"
 recaptchaSiteKey: "网站密钥"
-recaptchaSecretKey: "reCAPTCHA 密钥(SecretKey)"
+recaptchaSecretKey: "mCaptcha 密钥(SecretKey)"
 turnstile: "Turnstile"
 enableTurnstile: "启用 Turnstile"
 turnstileSiteKey: "网站密钥"
 turnstileSecretKey: "Turnstile 密钥(SecretKey)"
-avoidMultiCaptchaConfirm: "使用多种验证方式可能会造成干扰,您要禁用其他验证方式吗?您可以按“取消”按钮,继续保持启用多种验证方式。"
+avoidMultiCaptchaConfirm: "使用多个 Captcha 可能会互相干扰,您要禁用其它 Captcha 吗?您可以按“取消”按钮,继续保持启用多种验证方式。"
 antennas: "天线"
 manageAntennas: "天线管理"
 name: "名称"
@@ -477,7 +490,7 @@ or: "或者"
 language: "语言"
 uiLanguage: "显示语言"
 aboutX: "关于 {x}"
-emojiStyle: "emoji 的样式"
+emojiStyle: "表情符号的样式"
 native: "原生"
 disableDrawer: "不显示抽屉菜单"
 showNoteActionsOnlyHover: "仅在悬停时显示帖子操作"
@@ -514,7 +527,7 @@ showFeaturedNotesInTimeline: "在时间线上显示热门推荐"
 objectStorage: "对象存储"
 useObjectStorage: "使用对象存储"
 objectStorageBaseUrl: "Base URL"
-objectStorageBaseUrlDesc: "这里是用于参考的 URL,如果您正在使用 CDN 或反向代理,请指定其 URL,例如 S3:“https://<bucket>.s3.amazonaws.com”,GCS:“https://storage.googleapis.com/<bucket>”"
+objectStorageBaseUrlDesc: "用于参考的 URL,如果您正在使用 CDN 或 Proxy,请填入服务商提供的 URL;S3:“https://<bucket>.s3.amazonaws.com”;GCS:“https://storage.googleapis.com/<bucket>”"
 objectStorageBucket: "存储桶"
 objectStorageBucketDesc: "请指定使用的对象存储服务的存储桶名称。"
 objectStoragePrefix: "前缀"
@@ -533,6 +546,7 @@ serverLogs: "服务器日志"
 deleteAll: "全部删除"
 showFixedPostForm: "在时间线顶部显示发帖框"
 showFixedPostFormInChannel: "在时间线顶部显示发帖对话框(频道)"
+withRepliesByDefaultForNewlyFollowed: "在时间线中默认包含新关注用户的回复"
 newNoteRecived: "有新的帖子"
 sounds: "提示音"
 sound: "提示音"
@@ -542,6 +556,8 @@ showInPage: "在页面中显示"
 popout: "弹窗"
 volume: "音量"
 masterVolume: "主音量"
+notUseSound: "静音"
+useSoundOnlyWhenActive: "仅在 Misskey 活跃时输出声音"
 details: "详情"
 chooseEmoji: "选择表情符号"
 unableToProcess: "操作无法完成"
@@ -562,10 +578,14 @@ output: "输出"
 script: "脚本"
 disablePagesScript: "禁用页面脚本"
 updateRemoteUser: "更新远程用户信息"
+unsetUserAvatar: "清除头像"
+unsetUserAvatarConfirm: "要清除头像吗?"
+unsetUserBanner: "清除横幅"
+unsetUserBannerConfirm: "要清除横幅吗?"
 deleteAllFiles: "删除所有文件"
 deleteAllFilesConfirm: "要删除所有文件吗?"
 removeAllFollowing: "取消所有关注"
-removeAllFollowingDescription: "取消 {host} 的所有关注者。当服务器不再存在时执行。"
+removeAllFollowingDescription: "取消来自 {host} 的所有关注者。当服务器不再存在时执行。"
 userSuspended: "该用户已被冻结。"
 userSilenced: "该用户已被禁言。"
 yourAccountSuspendedTitle: "账户已被冻结"
@@ -612,6 +632,7 @@ medium: "中"
 small: "小"
 generateAccessToken: "生成访问令牌"
 permission: "权限"
+adminPermission: "管理员权限"
 enableAll: "启用全部"
 disableAll: "禁用全部"
 tokenRequested: "允许访问账户"
@@ -633,6 +654,7 @@ smtpSecure: "在 SMTP 连接中使用隐式 SSL / TLS"
 smtpSecureInfo: "使用 STARTTLS 时关闭。"
 testEmail: "邮件发送测试"
 wordMute: "文字屏蔽"
+hardWordMute: "屏蔽关键词"
 regexpError: "正则表达式错误"
 regexpErrorDescription: "{tab} 屏蔽文字的第 {line} 行的正则表达式有错误:"
 instanceMute: "被屏蔽的服务器"
@@ -654,6 +676,7 @@ useGlobalSettingDesc: "启用时,将使用账户通知设置。关闭时,则
 other: "其他"
 regenerateLoginToken: "重新生成登录令牌"
 regenerateLoginTokenDescription: "重新生成用于登录的内部令牌。通常您不需要这样做。重新生成后,您将在所有设备上登出。"
+theKeywordWhenSearchingForCustomEmoji: "这将是搜素自定义表情符号时的关键词。"
 setMultipleBySeparatingWithSpace: "您可以使用空格分隔多个项目。"
 fileIdOrUrl: "文件 ID 或者 URL"
 behavior: "行为"
@@ -866,6 +889,8 @@ makeReactionsPublicDescription: "将您发表过的回应设置成公开可见
 classic: "经典"
 muteThread: "屏蔽帖子列表"
 unmuteThread: "取消屏蔽帖子列表"
+followingVisibility: "关注的人的公开范围"
+followersVisibility: "关注者的公开范围"
 continueThread: "查看更多帖子"
 deleteAccountConfirm: "将要删除账户。是否确认?"
 incorrectPassword: "密码错误"
@@ -949,7 +974,7 @@ unsubscribePushNotification: "停用推送通知消息"
 pushNotificationAlreadySubscribed: "推送通知消息已启用"
 pushNotificationNotSupported: "浏览器或服务器不支持推送通知消息"
 sendPushNotificationReadMessage: "删除已读推送通知消息"
-sendPushNotificationReadMessageCaption: "“{emptyPushNotificationMessage}”的通知消息将会显示。您终端设备的电池消耗可能会增加。"
+sendPushNotificationReadMessageCaption: "您终端设备的电池消耗可能会增加。"
 windowMaximize: "最大化"
 windowMinimize: "最小化"
 windowRestore: "还原"
@@ -966,6 +991,7 @@ neverShow: "不再显示"
 remindMeLater: "稍后提醒我"
 didYouLikeMisskey: "您喜欢 Misskey 吗?"
 pleaseDonate: "Misskey 是 {host} 所使用的免费软件。为了今后也能够维持 Misskey 的开发,请在有余力的情况下进行捐助!"
+correspondingSourceIsAvailable: "对应的源代码可在{anchor}找到"
 roles: "角色"
 role: "角色"
 noRole: "角色不存在"
@@ -975,6 +1001,7 @@ assign: "分配"
 unassign: "取消分配"
 color: "颜色"
 manageCustomEmojis: "管理自定义表情符号"
+manageAvatarDecorations: "管理头像挂件"
 youCannotCreateAnymore: "抱歉,您无法再创建更多了。"
 cannotPerformTemporary: "暂时不可用"
 cannotPerformTemporaryDescription: "因操作过于频繁,暂时不可用,请稍后再试。"
@@ -1015,6 +1042,11 @@ resetPasswordConfirm: "确定重置密码?"
 sensitiveWords: "敏感词"
 sensitiveWordsDescription: "将包含设置词的帖子的可见范围设置为首页。可以通过用换行符分隔来设置多个。"
 sensitiveWordsDescription2: "AND 条件用空格分隔,正则表达式用斜线包裹。"
+prohibitedWords: "禁用词"
+prohibitedWordsDescription: "发布包含设定词汇的帖子时将出错。可用换行设定多个关键字"
+prohibitedWordsDescription2: "AND 条件用空格分隔,正则表达式用斜线包裹。"
+hiddenTags: "隐藏标签"
+hiddenTagsDescription: "设定的标签将不会在时间线上显示。可使用换行来设置多个标签。"
 notesSearchNotAvailable: "帖子检索不可用"
 license: "许可信息"
 unfavoriteConfirm: "确定要取消收藏吗?"
@@ -1027,9 +1059,12 @@ enableChartsForRemoteUser: "生成远程用户的图表"
 enableChartsForFederatedInstances: "生成远程服务器的图表"
 showClipButtonInNoteFooter: "在贴文下方显示便签按钮"
 reactionsDisplaySize: "回应显示大小"
+limitWidthOfReaction: "限制回应的最大宽度,并将其缩小显示"
 noteIdOrUrl: "帖子 ID 或 URL"
 video: "视频"
 videos: "视频"
+audio: "音频"
+audioFiles: "音频"
 dataSaver: "省流量模式"
 accountMigration: "账户迁移"
 accountMoved: "此用户已迁移账户"
@@ -1082,7 +1117,7 @@ branding: "品牌"
 enableServerMachineStats: "公开服务器硬件统计信息"
 enableIdenticonGeneration: "启用生成用户 Identicon"
 turnOffToImprovePerformance: "关闭该选项可以提高性能。"
-createInviteCode: "发行邀请码"
+createInviteCode: "生成邀请码"
 createWithOptions: "使用选项来创建"
 createCount: "发行数"
 inviteCodeCreated: "已创建邀请码"
@@ -1094,7 +1129,7 @@ noExpirationDate: "不设置有效日期"
 inviteCodeUsedAt: "邀请码被使用的日期和时间"
 registeredUserUsingInviteCode: "使用了邀请码的用户"
 waitingForMailAuth: "等待验证电子邮件"
-inviteCodeCreator: "发行邀请码的用户"
+inviteCodeCreator: "生成邀请码的用户"
 usedAt: "使用时间"
 unused: "未使用"
 used: "已使用"
@@ -1125,20 +1160,96 @@ showRenotes: "显示转帖"
 edited: "已编辑"
 notificationRecieveConfig: "通知接收设置"
 mutualFollow: "互相关注"
+followingOrFollower: "关注中或关注者"
 fileAttachedOnly: "仅限媒体"
-showRepliesToOthersInTimeline: "在时间线上显示给其他人的回复"
-hideRepliesToOthersInTimeline: "在时间线上隐藏给其他人的回复"
+showRepliesToOthersInTimeline: "在时间线中包含给别人的回复"
+hideRepliesToOthersInTimeline: "在时间线中隐藏给别人的回复"
+showRepliesToOthersInTimelineAll: "在时间线中包含现在关注的所有人的回复"
+hideRepliesToOthersInTimelineAll: "在时间线中隐藏现在关注的所有人的回复"
+confirmShowRepliesAll: "此操作不可撤销。确认要在时间线中包含现在关注的所有人的回复吗?"
+confirmHideRepliesAll: "此操作不可撤销。确认要在时间线中隐藏现在关注的所有人的回复吗?"
+externalServices: "外部服务"
+sourceCode: "源代码"
+sourceCodeIsNotYetProvided: "还未提供源代码。要解决此问题请联系管理员。"
+repositoryUrl: "仓库地址"
+repositoryUrlDescription: "若源代码所在的仓库是公开的,请填入对应的 URL。若是按原样使用 Misskey(并未追加或者修改代码)的情况请填入 https://github.com/misskey-dev/misskey。"
+repositoryUrlOrTarballRequired: "若仓库并未公开,则需要提供 tarball 作为替代。详情请看 .config/example.yml。"
+feedback: "反馈"
+feedbackUrl: "反馈地址"
+impressum: "运营商信息"
+impressumUrl: "运营商信息地址"
+impressumDescription: "德国等国家和地区有义务展示此类信息(Impressum)。"
+privacyPolicy: "隐私政策"
+privacyPolicyUrl: "隐私政策地址"
+tosAndPrivacyPolicy: "服务条款及隐私政策"
 avatarDecorations: "头像挂件"
+attach: "佩戴"
+detach: "卸下"
+detachAll: "全部卸下"
+angle: "角度"
 flip: "翻转"
+showAvatarDecorations: "显示头像挂件"
+releaseToRefresh: "松开以刷新"
+refreshing: "刷新中"
+pullDownToRefresh: "下拉以刷新"
+disableStreamingTimeline: "禁止实时更新时间线"
+useGroupedNotifications: "分组显示通知"
+signupPendingError: "确认电子邮件时出现错误。链接可能已过期。"
+cwNotationRequired: "在启用「隐藏内容」时必须输入注释"
+doReaction: "回应"
+code: "代码"
+reloadRequiredToApplySettings: "需要重新载入来使设置生效"
+remainingN: "剩余:{n}"
+overwriteContentConfirm: "将覆盖现有内容。确定吗?"
+seasonalScreenEffect: "应景的画面效果"
+decorate: "装饰"
+addMfmFunction: "添加装饰"
+enableQuickAddMfmFunction: "显示高级 MFM 选择器"
+bubbleGame: "泡泡游戏"
+sfx: "音效"
+soundWillBePlayed: "声音将会播放"
+showReplay: "观看回放"
+replay: "重播"
+replaying: "重播中"
+endReplay: "结束回放"
+copyReplayData: "复制回放数据"
+ranking: "排行榜"
+lastNDays: "最近 {n} 天"
+backToTitle: "返回标题"
+hemisphere: "居住地区"
+withSensitive: "显示包含敏感媒体的帖子"
+userSaysSomethingSensitive: "含 {name} 敏感文件的帖子"
+enableHorizontalSwipe: "滑动切换标签页"
+loading: "读取中"
+surrender: "取消"
+gameRetry: "重试"
+_bubbleGame:
+  howToPlay: "游戏说明"
+  hold: "抓住"
+  _score:
+    score: "得分"
+    scoreYen: "赚到的钱"
+    highScore: "最高分"
+    maxChain: "最高连击数"
+    yen: "{yen} 日元"
+    estimatedQty: "约 {qty} 个"
+  _howToPlay:
+    section1: "对准位置将Emoji投入盒子。"
+    section2: "相同的Emoji相互接触合成后会得到新的Emoji,以此获得分数。"
+    section3: "如果Emoji从箱子中溢出游戏将会结束。在防止Emoji溢出的同时,不断合成新的Emoji,来获取更高的分数吧!"
 _announcement:
   forExistingUsers: "仅限现有用户"
   forExistingUsersDescription: "若启用,该公告将仅对创建此公告时存在的用户可见。 如果禁用,则在创建此公告后注册的用户也可以看到该公告。"
   needConfirmationToRead: "需要确认才能标记为已读"
   needConfirmationToReadDescription: "若启用,则会在标记已读时会显示确认对话框。此外,它也会不受批量已读操作的影响。"
   end: "结束公告"
-  tooManyActiveAnnouncementDescription: "若有大量活动公告,可能会造成用户体验可能下降。请考虑归档已完成的公告。"
+  tooManyActiveAnnouncementDescription: "若有大量活动公告,可能会造成用户体验下降。请考虑归档已完成的公告。"
   readConfirmTitle: "标记为已读?"
   readConfirmText: "阅读“{title}”的内容并将其标记为已读。"
+  shouldNotBeUsedToPresentPermanentInfo: "我们建议使用公告来发布临时性的流动信息而不是固定的常规信息,因为这可能损害用户体验,尤其是对于新用户而言。"
+  dialogAnnouncementUxWarn: "同时存在 2 个或以上的对话框公告极有可能对用户体验产生负面的影响,建议谨慎使用。"
+  silence: "不发送通知"
+  silenceDescription: "开启后,此条公告将不会发送通知,也不强制用户阅读。"
 _initialAccountSetting:
   accountCreated: "账户创建完成了!"
   letsStartAccountSetup: "来进行帐户的初始设置吧。"
@@ -1151,19 +1262,91 @@ _initialAccountSetting:
   pushNotificationDescription: "启用推送通知的话,就可以在设备上接收来自 {name} 的通知了。"
   initialAccountSettingCompleted: "初始设定已经完成了!"
   haveFun: "希望 {name} 在这里玩得开心!"
+  youCanContinueTutorial: "您可以继续了解 {name}(Misskey) 的使用教程,也可以在此停止教程并立即开始使用它。\n"
+  startTutorial: "开始教学"
   skipAreYouSure: "要跳过初始设置吗?"
   laterAreYouSure: "要稍后再进行初始设定吗?"
+_initialTutorial:
+  launchTutorial: "观看教学"
+  title: "教学"
+  wellDone: "做得好"
+  skipAreYouSure: "是否退出教学?"
+  _landing:
+    title: "欢迎来到教学"
+    description: "在这里,您可以查看 Misskey 的基本使用方法和功能。"
+  _note:
+    title: "什么是帖子?"
+    description: "在 Misskey 上发表的文章称为「帖子」。帖子在时间线上按照时间顺序排列,并实时更新。"
+    reply: "用来回复帖子。可以对回复进行回复,从而形成一串对话。"
+    renote: "用来将帖子共享到自己的时间线上。也可以加上自己的文字然后引用它。"
+    reaction: "用来添加回应。详细信息将在下一页进行说明。"
+    menu: "用来进行例如显示帖子详情、复制链接等各种各样的操作。"
+  _reaction:
+    title: "什么是回应?"
+    description: "您可以在帖子中添加“回应”。 您可以使用反应轻松地表达点“赞”所无法传达的细微差别。"
+    letsTryReacting: "回应可以通过点击帖子中的「+」按钮来添加。试着给这个示例帖子添加一个回应!"
+    reactToContinue: "添加一个回应来继续"
+    reactNotification: "当您的帖子被某人添加了回应时,将实时收到通知。"
+    reactDone: "通过按下「ー」按钮,可以取消已经添加的回应"
+  _timeline:
+    title: "时间线的运作方式"
+    description1: "Misskey 根据使用方式提供了多个时间线(根据服务器的设定,可能有一些被禁用)。"
+    home: "可以查看您关注的账户的帖子。"
+    local: "可以查看这个服务器上所有用户发表的帖子。"
+    social: "将同时显示首页时间线和本地时间线的内容。"
+    global: "可以查看所有已联合的服务器上的帖子。"
+    description2: "可以随时在屏幕顶部在每个时间线之间切换。"
+    description3: "另外,还有列表时间线和频道时间线。请参阅{link}了解更多详细信息。"
+  _postNote:
+    title: "帖子发布设置"
+    description1: "在 Misskey 发布帖子时,您可以设置各种选项。发帖窗口看起来是这样的。\n"
+    _visibility:
+      description: "您可以限制谁可以看到您的帖子。"
+      public: "向所有用户公开。\n"
+      home: "仅在首页时间线上发布。 关注者、从个人资料页查看过来的用户、以及通过转帖也能被别的用户看见。"
+      followers: "仅对关注者可见。 除了您自己之外,没有人可以转贴,并且只有您的关注者可以查看它。\n"
+      direct: "它将仅向指定用户公开,并且他们也会收到通知。 您可以使用它来代替私信。\n"
+      doNotSendConfidencialOnDirect1: "发送敏感信息时请注意。\n"
+      doNotSendConfidencialOnDirect2: "目标服务器的管理员可以看到发布的内容,因此如果您向不受信任的服务器上的用户发送私信,则在处理敏感信息时需要小心。"
+      localOnly: "不将帖子推送到其它服务器。 无论上述公开范围如何,其它服务器的用户将无法看到附加了此设定的帖子。\n"
+    _cw:
+      title: "隐藏内容 (CW)\n"
+      description: "显示「注解」里的内容而不是正文。点击「查看更多」将会把正文显示出来。"
+      _exampleNote:
+        cw: "深夜报复社会"
+        note: "茨了带巧克力的甜甜圈🍩😋"
+      useCases: "用于服务器条款所规定的帖子,或对剧透内容和敏感内容进行自主规制。"
+  _howToMakeAttachmentsSensitive:
+    title: "如何将附件标注为敏感内容?"
+    description: "对于服务器方针所要求要求的,又或者不适合直接展示的附件,请添加「敏感」标记。\n"
+    tryThisFile: "试试看,将附加到此窗口的图像标注为敏感!"
+    _exampleNote:
+      note: "拆纳豆包装时出错了…"
+    method: "要标注附件为敏感内容,请单击该文件以打开菜单,然后单击“标记为敏感内容”。"
+    sensitiveSucceeded: "附加文件时,请遵循服务器的条款来设置正确敏感设定。\n"
+    doItToContinue: "将图像标记为敏感后才能够继续"
+  _done:
+    title: "恭喜您,已经完成了教程🎉\n"
+    description: "这里介绍的只是其中一小部分的功能。 要了解更多有关如何使用 Misskey 的更多信息,请访问 {link}。"
+_timelineDescription:
+  home: "首页时间线可以查看您关注的账户的帖子。"
+  local: "本地时间线可以查看这个服务器上所有用户发表的帖子。"
+  social: "社交时间线将同时显示首页时间线和本地时间线的内容。"
+  global: "全局时间线可以查看所有已联合的服务器上的帖子。"
 _serverRules:
   description: "在新用户注册前显示服务器的简单规则。推荐显示服务条款的主要内容。"
 _serverSettings:
   iconUrl: "图标 URL"
   appIconDescription: "指定当 {host} 显示为 app 时的图标。"
-  appIconUsageExample: "例如:作为书签添加到 PWA 或手机主屏幕的时候"
+  appIconUsageExample: "如作为书签添加到 PWA 或手机主屏幕时"
   appIconStyleRecommendation: "因为有可能会被裁切为圆形或者圆角矩形,建议使用边缘带有留白背景的图标。"
   appIconResolutionMustBe: "分辨率必须为 {resolution}。"
   manifestJsonOverride: "覆盖 manifest.json"
   shortName: "简称"
   shortNameDescription: "如果服务器的正式名称很长,可以用简称或者別名来替代。"
+  fanoutTimelineDescription: "当启用时,可显著提高获取各种时间线时的性能,并减轻数据库的负荷。但是相对的 Redis 的内存使用量将会增加。如果服务器的内存不是很大,又或者运行不稳定的话可以把它关掉。"
+  fanoutTimelineDbFallback: "回退到数据库"
+  fanoutTimelineDbFallbackDescription: "当启用时,若时间线未被缓存,则将额外查询数据库。禁用该功能可通过不执行回退处理进一步减少服务器负载,但会限制可检索的时间线范围。"
 _accountMigration:
   moveFrom: "从别的账号迁移到此账户"
   moveFromSub: "为另一个账户建立别名"
@@ -1390,7 +1573,7 @@ _achievements:
       description: "点了这里"
     _justPlainLucky:
       title: "超高校级的幸运"
-      description: "每 10 秒有 0.01 的概率自动获得"
+      description: "每 10 秒有 0.005% 的概率自动获得"
     _setNameToSyuilo:
       title: "像神一样呐"
       description: "将名称设定为 syuilo"
@@ -1421,6 +1604,15 @@ _achievements:
     _smashTestNotificationButton:
       title: "过度测试"
       description: "短时间内连续测试通知"
+    _tutorialCompleted:
+      title: "Misskey 初学者课程 结业证书"
+      description: "完成了教学"
+    _bubbleGameExplodingHead:
+      title: "🤯"
+      description: "你合成出了游戏里最大的Emoji"
+    _bubbleGameDoubleExplodingHead:
+      title: "两个🤯"
+      description: "你合成出了2个游戏里最大的Emoji"
 _role:
   new: "创建角色"
   edit: "编辑角色"
@@ -1431,7 +1623,9 @@ _role:
   assignTarget: "授权对象"
   descriptionOfAssignTarget: "<b>手动</b>指手动选择谁被包括在这个角色中。\n<b>符合条件</b>指设置条件以自动包括符合条件的用户。"
   manual: "手动"
+  manualRoles: "手动角色"
   conditional: "符合条件"
+  conditionalRoles: "条件角色"
   condition: "条件"
   isConditionalRole: "这是一个条件控制的角色。"
   isPublic: "角色公开"
@@ -1459,11 +1653,13 @@ _role:
     gtlAvailable: "查看全局时间线"
     ltlAvailable: "查看本地时间线"
     canPublicNote: "允许公开发帖"
+    mentionMax: "帖子内最多提及数"
     canInvite: "发放服务器邀请码"
-    inviteLimit: "可发行邀请码的数量"
+    inviteLimit: "可生成邀请码的数量"
     inviteLimitCycle: "邀请码的发行间隔"
     inviteExpirationTime: "邀请码的有效日期"
     canManageCustomEmojis: "管理自定义表情符号"
+    canManageAvatarDecorations: "管理头像挂件"
     driveCapacity: "网盘容量"
     alwaysMarkNsfw: "总是将文件标记为 NSFW"
     pinMax: "帖子置顶数量限制"
@@ -1478,7 +1674,10 @@ _role:
     descriptionOfRateLimitFactor: "值越小限制越少,值越大限制越多。"
     canHideAds: "可以隐藏广告"
     canSearchNotes: "是否可以搜索帖子"
+    canUseTranslator: "使用翻译功能"
+    avatarDecorationLimit: "可添加头像挂件的最大个数"
   _condition:
+    roleAssignedTo: "已分配给手动角色"
     isLocal: "是本地用户"
     isRemote: "是远程用户"
     createdLessThan: "账户创建时间少于"
@@ -1506,6 +1705,7 @@ _emailUnavailable:
   disposable: "不是永久可用的地址"
   mx: "邮件服务器不正确"
   smtp: "邮件服务器没有响应"
+  banned: "无法使用此邮件地址注册"
 _ffVisibility:
   public: "公开"
   followers: "只有关注你的用户能看到"
@@ -1526,6 +1726,10 @@ _ad:
   reduceFrequencyOfThisAd: "减少此广告的频率"
   hide: "不显示"
   timezoneinfo: "星期几是由服务器的时区所指定的。"
+  adsSettings: "广告设置"
+  notesPerOneAd: "在实时更新时间线中插入广告的间隔(帖子个数)"
+  setZeroToDisable: "设为 0 将不在实时更新时间线中投放广告"
+  adsTooClose: "广告投放时间间隔过短将可能显著损害用户体验。"
 _forgotPassword:
   enterEmail: "请输入您设置的电子邮箱地址,密码重置链接将发送至该邮箱上。"
   ifNoEmail: "如果您没有设置电子邮件地址,请联系管理员。"
@@ -1565,8 +1769,8 @@ _preferencesBackups:
   invalidFile: "无效的的文件格式。"
 _registry:
   scope: "范围"
-  key: "主要"
-  keys: "主要"
+  key: "键"
+  keys: "键"
   domain: "域"
   createKey: "创建键"
 _aboutMisskey:
@@ -1574,10 +1778,13 @@ _aboutMisskey:
   contributors: "主要贡献者"
   allContributors: "全体贡献者"
   source: "源代码"
+  original: "原版"
+  thisIsModifiedVersion: "{name}正在使用修改后的 Misskey。"
   translation: "翻译 Misskey"
   donate: "赞助 Misskey"
   morePatrons: "还有很多其它的人也在支持我们,非常感谢🥰"
   patrons: "支持者"
+  projectMembers: "项目成员"
 _displayOfSensitiveMedia:
   respect: "隐藏敏感媒体"
   ignore: "显示敏感媒体"
@@ -1602,6 +1809,7 @@ _channel:
   notesCount: "有 {n} 个帖子"
   nameAndDescription: "名称与描述"
   nameOnly: "仅名称"
+  allowRenoteToExternal: "允许在频道外转帖及引用"
 _menuDisplay:
   sideFull: "横向"
   sideIcon: "横向(图标)"
@@ -1693,6 +1901,14 @@ _sfx:
   notification: "通知"
   antenna: "天线接收"
   channel: "频道通知"
+  reaction: "选择回应时"
+_soundSettings:
+  driveFile: "使用网盘内的音频"
+  driveFileWarn: "选择网盘上的文件"
+  driveFileTypeWarn: "不支持此文件"
+  driveFileTypeWarnDescription: "请选择音频文件"
+  driveFileDurationWarn: "音频过长"
+  driveFileDurationWarnDescription: "使用长音频可能会影响 Misskey 的使用。即使这样也要继续吗?"
 _ago:
   future: "未来"
   justNow: "最近"
@@ -1706,7 +1922,12 @@ _ago:
   invalid: "没有"
 _timeIn:
   seconds: "{n}秒后"
+  minutes: "{n} 分后"
+  hours: "{n} 小时后"
   days: "{n}天后"
+  weeks: "{n} 周后"
+  months: "{n} 月后"
+  years: "{n} 年后"
 _time:
   second: "秒"
   minute: "分"
@@ -1778,6 +1999,55 @@ _permissions:
   "write:flash": "编辑 Play"
   "read:flash-likes": "查看 Play 的点赞"
   "write:flash-likes": "编辑 Play 的点赞列表"
+  "read:admin:abuse-user-reports": "查看来自用户的举报"
+  "write:admin:delete-account": "删除用户账户"
+  "write:admin:delete-all-files-of-a-user": "删除用户所有的文件"
+  "read:admin:index-stats": "查看数据库索引相关的信息"
+  "read:admin:table-stats": "查看数据库表相关的信息"
+  "read:admin:user-ips": "查看用户 IP 地址"
+  "read:admin:meta": "查看实例的元数据"
+  "write:admin:reset-password": "重置用户密码"
+  "write:admin:resolve-abuse-user-report": "将来自用户的报告标记为「已解决」"
+  "write:admin:send-email": "发送邮件"
+  "read:admin:server-info": "查看服务器信息"
+  "read:admin:show-moderation-log": "查看管理日志"
+  "read:admin:show-user": "查看用户的非公开信息"
+  "read:admin:show-users": "查看用户的非公开信息"
+  "write:admin:suspend-user": "冻结用户"
+  "write:admin:unset-user-avatar": "删除用户头像"
+  "write:admin:unset-user-banner": "删除用户横幅"
+  "write:admin:unsuspend-user": "解除用户冻结"
+  "write:admin:meta": "编辑实例元数据"
+  "write:admin:user-note": "编辑管理笔记"
+  "write:admin:roles": "编辑角色"
+  "read:admin:roles": "查看角色"
+  "write:admin:relays": "编辑中继"
+  "read:admin:relays": "查看中继"
+  "write:admin:invite-codes": "编辑邀请码"
+  "read:admin:invite-codes": "查看邀请码"
+  "write:admin:announcements": "编辑公告"
+  "read:admin:announcements": "查看公告"
+  "write:admin:avatar-decorations": "编辑头像挂件"
+  "read:admin:avatar-decorations": "查看头像挂件"
+  "write:admin:federation": "编辑联合相关信息"
+  "write:admin:account": "编辑用户账户"
+  "read:admin:account": "查看用户相关情报"
+  "write:admin:emoji": "编辑表情文字"
+  "read:admin:emoji": "查看表情文字"
+  "write:admin:queue": "编辑作业队列"
+  "read:admin:queue": "查看作业队列相关情报"
+  "write:admin:promo": "运营推广说明"
+  "write:admin:drive": "编辑用户网盘"
+  "read:admin:drive": "查看用户网盘相关情报"
+  "read:admin:stream": "使用管理员用的 Websocket API"
+  "write:admin:ad": "编辑广告"
+  "read:admin:ad": "查看广告"
+  "write:invite-codes": "生成邀请码"
+  "read:invite-codes": "获取已发行的邀请码"
+  "write:clip-favorite": "编辑便签的点赞"
+  "read:clip-favorite": "查看便签的点赞"
+  "read:federation": "查看联合相关信息"
+  "write:report-abuse": "举报用户"
 _auth:
   shareAccessTitle: "应用程序授权许可"
   shareAccess: "您要授权允许 “{name}” 访问您的帐户吗?"
@@ -1832,6 +2102,7 @@ _widgets:
   _userList:
     chooseList: "选择列表"
   clicker: "点击器"
+  birthdayFollowings: "今天是他们的生日"
 _cw:
   hide: "隐藏"
   show: "查看更多"
@@ -1894,15 +2165,18 @@ _profile:
   changeAvatar: "修改头像"
   changeBanner: "修改横幅"
   verifiedLinkDescription: "如果将内容设置为 URL,当链接所指向的网页内包含自己的个人资料链接时,可以显示一个已验证图标。"
+  avatarDecorationMax: "最多可添加 {max} 个挂件"
 _exportOrImport:
   allNotes: "所有帖子"
   favoritedNotes: "收藏的帖子"
+  clips: "便签"
   followingList: "关注中"
   muteList: "屏蔽"
   blockingList: "拉黑"
   userLists: "列表"
   excludeMutingUsers: "排除屏蔽用户"
   excludeInactiveUsers: "排除不活跃用户"
+  withReplies: "在时间线中包含导入用户的回复"
 _charts:
   federation: "联合"
   apRequest: "请求"
@@ -2014,12 +2288,17 @@ _notification:
   pollEnded: "问卷调查结果已生成。"
   newNote: "新的帖子"
   unreadAntennaNote: "天线 {name}"
+  roleAssigned: "授予的角色"
   emptyPushNotificationMessage: "推送通知已更新"
   achievementEarned: "获得成就"
   testNotification: "测试通知"
   checkNotificationBehavior: "检查通知显示"
   sendTestNotification: "发送测试通知"
   notificationWillBeDisplayedLikeThis: "通知将会这样表示"
+  reactedBySomeUsers: "{n} 人回应了"
+  renotedBySomeUsers: "{n} 人转发了"
+  followedBySomeUsers: "被 {n} 人关注"
+  flushNotification: "重置通知历史"
   _types:
     all: "全部"
     note: "用户的新帖子"
@@ -2032,6 +2311,7 @@ _notification:
     pollEnded: "问卷调查结束"
     receiveFollowRequest: "收到关注请求"
     followRequestAccepted: "关注请求已通过"
+    roleAssigned: "授予的角色"
     achievementEarned: "取得的成就"
     app: "关联应用的通知"
   _actions:
@@ -2114,17 +2394,129 @@ _moderationLogTypes:
   deleteGlobalAnnouncement: "删除全体通知"
   deleteUserAnnouncement: "删除用户通知"
   resetPassword: "重置密码"
+  suspendRemoteInstance: "停止远程服务器"
+  unsuspendRemoteInstance: "恢复远程服务器"
+  updateRemoteInstanceNote: "更新远程服务器的管理笔记"
   markSensitiveDriveFile: "标记网盘文件为敏感媒体"
   unmarkSensitiveDriveFile: "取消标记网盘文件为敏感媒体"
   resolveAbuseReport: "处理举报"
-  createInvitation: "发行邀请码"
+  createInvitation: "生成邀请码"
   createAd: "创建了广告"
   deleteAd: "删除了广告"
   updateAd: "更新了广告"
+  createAvatarDecoration: "新建头像挂件"
+  updateAvatarDecoration: "更新头像挂件"
+  deleteAvatarDecoration: "删除头像挂件"
+  unsetUserAvatar: "清除用户头像"
+  unsetUserBanner: "清除用户横幅"
 _fileViewer:
+  title: "文件信息"
+  type: "文件类型"
+  size: "文件大小"
   url: "URL"
   uploadedAt: "添加日期"
+  attachedNotes: "附加到的帖子"
+  thisPageCanBeSeenFromTheAuthor: "此页只能被该文件的上传者查看。"
 _externalResourceInstaller:
+  title: "从外部站点安装"
+  checkVendorBeforeInstall: "请在安装前确保来源可靠"
+  _plugin:
+    title: "要安装此插件吗?"
+    metaTitle: "插件信息"
+  _theme:
+    title: "要安装此主题吗?"
+    metaTitle: "主题信息"
+  _meta:
+    base: "基本配色方案"
+  _vendorInfo:
+    title: "来源信息"
+    endpoint: "参考端点"
+    hashVerify: "确认文件完整性"
   _errors:
+    _invalidParams:
+      title: "缺少参数"
+      description: "缺少从外部站点获取数据所需的信息。请检查 URL。"
+    _resourceTypeNotSupported:
+      title: "不支持此外部资源"
+      description: "不支持从此外部站点获取的资源类型。请联系站点管理员。"
+    _failedToFetch:
+      title: "获取数据失败"
+      fetchErrorDescription: "与外部站点的通信失败。 如果重试后问题仍然存在,请联系站点管理员。"
+      parseErrorDescription: "无法读取从外部站点取得的数据。请联系站点管理员。"
+    _hashUnmatched:
+      title: "无法获取正确数据"
+      description: "无法验证数据的完整性。安全起见,无法继续安装。请联系站点管理员。"
     _pluginParseFailed:
       title: "AiScript 错误"
+      description: "虽然取得了数据,但是由于 AiScript 解析时出现错误,无法读取数据。请联系插件的作者。可在 Javascript 控制台查看错误详情。"
+    _pluginInstallFailed:
+      title: "插件安装失败"
+      description: "安装插件时出现错误。请再试一次。可在 Javascript 控制台查看错误详情。"
+    _themeParseFailed:
+      title: "主题解析错误"
+      description: "虽然取得了主题文件,但是由于解析时出现错误,无法加载主题。请联系主题的作者。可在 Javascript 控制台查看错误详情。"
+    _themeInstallFailed:
+      title: "安装主题失败"
+      description: "安装主题时出错。请再试一次。可在 Javascript 控制台查看错误详情。"
+_dataSaver:
+  _media:
+    title: "加载媒体"
+    description: "防止自动加载图像和视频。 点击隐藏的图像/视频即可加载它们。\n"
+  _avatar:
+    title: "头像"
+    description: "停止播放头像的动画。 由于动画图片的文件大小可能比普通图像大,这可以进一步减少数据流量。"
+  _urlPreview:
+    title: "URL预览缩略图\n"
+    description: "将不再加载 URL 预览缩略图。"
+  _code:
+    title: "代码高亮"
+    description: "如果使用了代码高亮标记,例如在 MFM 中,则在点击之前不会加载。 代码高亮要求加载每种高亮语言的定义文件,由于这些文件不再自动加载,因此有望减少数据传输量。"
+_hemisphere:
+  N: "北半球"
+  S: "南半球"
+  caption: "在某些客户端设置中用来确定季节"
+_reversi:
+  reversi: "黑白棋"
+  gameSettings: "对局设置"
+  blackOrWhite: "先手/后手"
+  blackIs: "{name}执黑(先手)"
+  rules: "规则"
+  thisGameIsStartedSoon: "对局即将开始"
+  waitingForOther: "等待对手准备"
+  waitingForMe: "等待你的准备"
+  waitingBoth: "请准备"
+  ready: "准备就绪"
+  cancelReady: "重新准备"
+  opponentTurn: "对手的回合"
+  myTurn: "你的回合"
+  turnOf: "{name}的回合"
+  pastTurnOf: "{name}的回合"
+  surrender: "认输"
+  surrendered: "已认输"
+  timeout: "超时"
+  drawn: "平局"
+  won: "{name}获胜"
+  black: "黑"
+  white: "白"
+  total: "总计"
+  turnCount: "第{count}回合"
+  myGames: "我的对局"
+  allGames: "所有对局"
+  ended: "结束"
+  playing: "对局中"
+  canPutEverywhere: "无限制放置模式"
+  timeLimitForEachTurn: "1回合的时间限制"
+  freeMatch: "自由匹配"
+  lookingForPlayer: "正在寻找对手"
+  gameCanceled: "对局被取消了"
+  shareToTlTheGameWhenStart: "开始时在时间线发布对局"
+  iStartedAGame: "对局开始!#MisskeyReversi"
+  opponentHasSettingsChanged: "对手更改了设定"
+  allowIrregularRules: "允许非常规规则(完全自由)"
+  disallowIrregularRules: "禁止非常规规则"
+  showBoardLabels: "显示行号和列号"
+  useAvatarAsStone: "用头像作为棋子"
+_offlineScreen:
+  title: "离线——无法连接到服务器"
+  header: "无法连接到服务器"
+
diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml
index 36b6e77e9b..5cdecc10ac 100644
--- a/locales/zh-TW.yml
+++ b/locales/zh-TW.yml
@@ -66,7 +66,7 @@ showMore: "載入更多"
 showLess: "關閉"
 youGotNewFollower: "您有新的追隨者"
 receiveFollowRequest: "您有新的追隨請求"
-followRequestAccepted: "追隨請求已接受"
+followRequestAccepted: "追隨請求已被接受"
 mention: "提及"
 mentions: "提及"
 directNotes: "私訊"
@@ -91,7 +91,7 @@ manageLists: "管理清單"
 error: "錯誤"
 somethingHappened: "發生錯誤"
 retry: "重試"
-pageLoadError: "載入頁面失敗"
+pageLoadError: "無法載入頁面。"
 pageLoadErrorDescription: "這通常是網路錯誤或瀏覽器快取殘留而引起的。請先清除瀏覽器快取,稍後再重試。"
 serverIsDead: "伺服器沒有回應。請稍等片刻再試。"
 youShouldUpgradeClient: "請重新載入以使用新版客戶端顯示此頁面。"
@@ -130,6 +130,7 @@ overwriteFromPinnedEmojis: "從一般複寫設定"
 reactionSettingDescription2: "拖動以交換,點擊以刪除,按下「+」以新增。"
 rememberNoteVisibility: "記住貼文可見性"
 attachCancel: "移除附件"
+deleteFile: "刪除檔案"
 markAsSensitive: "標記為敏感內容"
 unmarkAsSensitive: "取消標記為敏感內容"
 enterFileName: "請輸入檔案名稱"
@@ -379,6 +380,11 @@ hcaptcha: "hCaptcha"
 enableHcaptcha: "啟用 hCaptcha"
 hcaptchaSiteKey: "網站金鑰"
 hcaptchaSecretKey: "金鑰"
+mcaptcha: "mCaptcha"
+enableMcaptcha: "啟用 mCaptcha"
+mcaptchaSiteKey: "網站金鑰"
+mcaptchaSecretKey: "金鑰"
+mcaptchaInstanceUrl: "mCaptcha 的實例網址"
 recaptcha: "reCAPTCHA"
 enableRecaptcha: "啟用 reCAPTCHA"
 recaptchaSiteKey: "網站金鑰"
@@ -592,13 +598,13 @@ menu: "選單"
 divider: "分隔線"
 addItem: "新增項目"
 rearrange: "排序方式"
-relays: "中繼"
-addRelay: "新增中繼"
+relays: "中繼器"
+addRelay: "新增中繼器"
 inboxUrl: "收件夾URL"
-addedRelays: "已加入的中繼"
+addedRelays: "已加入的中繼器"
 serviceworkerInfo: "如要使用推播通知,需要啟用此選項並設定金鑰。"
 deletedNote: "已刪除的貼文"
-invisibleNote: "私密的貼文"
+invisibleNote: "私人貼文"
 enableInfiniteScroll: "啟用自動滾動頁面模式"
 visibility: "可見性"
 poll: "票選活動"
@@ -626,6 +632,7 @@ medium: "中"
 small: "小"
 generateAccessToken: "發行存取權杖"
 permission: "權限"
+adminPermission: "管理員權限"
 enableAll: "啟用全部"
 disableAll: "停用全部"
 tokenRequested: "允許存取帳戶"
@@ -669,6 +676,7 @@ useGlobalSettingDesc: "啟用時,將使用帳戶通知設定。停用時,則
 other: "其他"
 regenerateLoginToken: "重新產生登入權杖"
 regenerateLoginTokenDescription: "重新產生用於登入的內部權杖。一般情況下是不需要這樣做的。重新產生後,所有裝置將會被登出。"
+theKeywordWhenSearchingForCustomEmoji: "這是搜尋自訂表情符號時的關鍵字"
 setMultipleBySeparatingWithSpace: "您可以使用空格分隔多個項目。"
 fileIdOrUrl: "檔案 ID 或 URL"
 behavior: "行為"
@@ -682,7 +690,7 @@ abuseReported: "檢舉完成。感謝您的報告。"
 reporter: "檢舉者"
 reporteeOrigin: "檢舉來源"
 reporterOrigin: "檢舉者來源"
-forwardReport: "將報告轉送給遠端實例"
+forwardReport: "將報告轉送給遠端伺服器"
 forwardReportIsAnonymous: "在遠端實例上看不到您的資訊,顯示的報告者是匿名的系统帳戶。"
 send: "發送"
 abuseMarkAsResolved: "處理完畢"
@@ -690,7 +698,7 @@ openInNewTab: "在新分頁中開啟"
 openInSideView: "在側欄中開啟"
 defaultNavigationBehaviour: "預設導航"
 editTheseSettingsMayBreakAccount: "修改這些設定可能會毀損您的帳戶"
-instanceTicker: "貼文的實例來源"
+instanceTicker: "貼文的伺服器資訊"
 waitingFor: "等待{x}"
 random: "隨機"
 system: "系統"
@@ -811,7 +819,7 @@ active: "最近活躍"
 offline: "離線"
 notRecommended: "不推薦"
 botProtection: "Bot 防護"
-instanceBlocking: "已封鎖的實例"
+instanceBlocking: "已封鎖或禁言的伺服器"
 selectAccount: "選擇帳戶"
 switchAccount: "切換帳戶"
 enabled: "已啟用"
@@ -954,7 +962,7 @@ cannotUploadBecauseNoFreeSpace: "由於雲端硬碟沒有可用空間,因此
 cannotUploadBecauseExceedsFileSizeLimit: "由於超過了檔案大小的限制,無法上傳。"
 beta: "測試版"
 enableAutoSensitive: "自動 NSFW 判定"
-enableAutoSensitiveDescription: "如果可用,它將使用機器學習技術判斷檔案是否需要標記為敏感。即使關閉此功能,也可能會依實例規則而自動啟用。"
+enableAutoSensitiveDescription: "如果可行,它將使用機器學習技術判斷檔案是否需要標記為敏感。即使關閉此功能,也可能會依伺服器規則而自動啟用。"
 activeEmailValidationDescription: "主動地驗證使用者的電子郵件地址,以確定是否是一次性地址以及是否可以真正與其進行通訊。關閉時,僅檢查格式是否正確。"
 navbar: "導覽列"
 shuffle: "隨機"
@@ -964,7 +972,7 @@ pushNotification: "推播通知"
 subscribePushNotification: "啟用推播通知"
 unsubscribePushNotification: "停用推播通知"
 pushNotificationAlreadySubscribed: "推播通知啟用中"
-pushNotificationNotSupported: "瀏覽器或實例不支援推播通知"
+pushNotificationNotSupported: "瀏覽器或伺服器不支援推播通知"
 sendPushNotificationReadMessage: "如果已閱讀通知與訊息,就刪除推播通知"
 sendPushNotificationReadMessageCaption: "「{emptyPushNotificationMessage}」通知將立刻顯示。可能會更消耗裝置電池。"
 windowMaximize: "最大化"
@@ -983,6 +991,7 @@ neverShow: "不再顯示"
 remindMeLater: "以後再說"
 didYouLikeMisskey: "您喜歡 Misskey 嗎?"
 pleaseDonate: "Misskey 是由 {host} 使用的免費軟體。請贊助我們,讓開發得以持續!"
+correspondingSourceIsAvailable: "對應的原始碼可以在 {anchor} 處找到。"
 roles: "角色"
 role: "角色"
 noRole: "沒有角色"
@@ -1033,6 +1042,9 @@ resetPasswordConfirm: "重設密碼?"
 sensitiveWords: "敏感詞"
 sensitiveWordsDescription: "將含有設定詞彙的貼文可見性設為發送至首頁。可以用換行來進行複數的設定。"
 sensitiveWordsDescription2: "空格代表「以及」(AND),斜線包圍關鍵字代表使用正規表達式。"
+prohibitedWords: "禁語"
+prohibitedWordsDescription: "當要發布包含禁語的貼文時,會出現錯誤。可以用換行分隔來設定多個禁語。"
+prohibitedWordsDescription2: "空格代表「以及」(AND),斜線包圍關鍵字代表使用正規表達式。"
 hiddenTags: "隱藏標籤"
 hiddenTagsDescription: "設定的標籤不會在趨勢中顯示,換行可以設定多個標籤。"
 notesSearchNotAvailable: "無法使用搜尋貼文功能。"
@@ -1051,6 +1063,8 @@ limitWidthOfReaction: "限制反應的最大寬度,並縮小顯示尺寸。"
 noteIdOrUrl: "貼文ID或URL"
 video: "影片"
 videos: "影片"
+audio: "音效"
+audioFiles: "音效檔案"
 dataSaver: "數據節省模式"
 accountMigration: "遷移帳戶"
 accountMoved: "這個使用者已遷移至新的帳戶:"
@@ -1146,6 +1160,7 @@ showRenotes: "顯示其他人的轉發貼文"
 edited: "已編輯"
 notificationRecieveConfig: "接受通知的設定"
 mutualFollow: "互相追隨"
+followingOrFollower: "追隨中或追隨者"
 fileAttachedOnly: "顯示包含附件的貼文"
 showRepliesToOthersInTimeline: "顯示給其他人的回覆"
 hideRepliesToOthersInTimeline: "在時間軸上隱藏給其他人的回覆"
@@ -1154,6 +1169,13 @@ hideRepliesToOthersInTimelineAll: "在時間軸不包含追隨中所有人的回
 confirmShowRepliesAll: "進行此操作後無法復原。您真的希望時間軸「包含」您目前追隨的所有人的回覆嗎?"
 confirmHideRepliesAll: "進行此操作後無法復原。您真的希望時間軸「不包含」您目前追隨的所有人的回覆嗎?"
 externalServices: "外部服務"
+sourceCode: "原始碼"
+sourceCodeIsNotYetProvided: "尚未提供原始碼,請洽詢管理員解決這個問題。"
+repositoryUrl: "儲存庫 URL"
+repositoryUrlDescription: "如果存在可公開取得原始碼的儲存庫,請輸入其 URL。 如果您按原樣使用 Misskey(不對原始碼進行任何更改),請輸入 https://github.com/misskey-dev/misskey。"
+repositoryUrlOrTarballRequired: "如果儲存庫不是公開的,則必須提供 tarball。 詳細資訊請參閱 .config/example.yml。"
+feedback: "意見回饋"
+feedbackUrl: "意見回饋 URL"
 impressum: "營運者資訊"
 impressumUrl: "營運者資訊網址"
 impressumDescription: "在德國與部份地區必須要明確顯示營運者資訊。"
@@ -1182,7 +1204,40 @@ overwriteContentConfirm: "確定要覆蓋目前的內容嗎?"
 seasonalScreenEffect: "隨季節變換畫面的呈現"
 decorate: "設置頭像裝飾"
 addMfmFunction: "插入MFM功能語法"
-enableQuickAddMfmFunction: "顯示高級MFM選擇器"
+enableQuickAddMfmFunction: "顯示高級 MFM 選擇器"
+bubbleGame: "氣泡遊戲"
+sfx: "音效"
+soundWillBePlayed: "將播放音效"
+showReplay: "觀看重播"
+replay: "重播"
+replaying: "重播中"
+endReplay: "退出重播"
+copyReplayData: "複製重播資料"
+ranking: "排行榜"
+lastNDays: "過去 {n} 天"
+backToTitle: "回到遊戲標題頁"
+hemisphere: "您居住的地區"
+withSensitive: "顯示包含敏感檔案的貼文"
+userSaysSomethingSensitive: "包含 {name} 敏感檔案的貼文"
+enableHorizontalSwipe: "滑動切換時間軸"
+loading: "載入中"
+surrender: "退出"
+gameRetry: "再試一次"
+_bubbleGame:
+  howToPlay: "玩法說明"
+  hold: "保留"
+  _score:
+    score: "分數"
+    scoreYen: "賺取的金額"
+    highScore: "最高分"
+    maxChain: "最大結合數"
+    yen: "{yen} 日圓"
+    estimatedQty: "{qty}個"
+    scoreSweets: "飯糰 {onigiriQtyWithUnit}"
+  _howToPlay:
+    section1: "調整位置並將物體放入盒子中。"
+    section2: "當相同類型的物體黏在一起時,它們會變成不同的物體,您就會得到分數。"
+    section3: "如果物體從盒子裡溢出,遊戲就結束了。透過融合物體而不溢出盒子來獲得高分!"
 _announcement:
   forExistingUsers: "僅限既有的使用者"
   forExistingUsersDescription: "啟用代表僅向現存使用者顯示;停用代表張貼後註冊的新使用者也會看到。"
@@ -1192,7 +1247,7 @@ _announcement:
   tooManyActiveAnnouncementDescription: "有過多公告可能會影響使用者體驗。請考慮歸檔已結束的公告。"
   readConfirmTitle: "標記為已讀嗎?"
   readConfirmText: "閱讀「{title}」的內容並標記為已讀。"
-  shouldNotBeUsedToPresentPermanentInfo: "由於可能會破壞使用者體驗,尤其是對於新使用者而言,我們建議使用公告來發布有時效性的資訊而不是固定不變的資訊。"
+  shouldNotBeUsedToPresentPermanentInfo: "為了避免損害新用戶的使用體驗,建議使用公告來發布即時性的訊息,而不是用於固定不變的資訊。"
   dialogAnnouncementUxWarn: "如果同時有 2 個以上對話方塊形式的公告存在,對於使用者體驗很可能會有不良的影響,因此建議謹慎使用。"
   silence: "不發送通知"
   silenceDescription: "啟用此選項後,將不會發送此公告的通知,並且無需將其標記為已讀。"
@@ -1501,7 +1556,7 @@ _achievements:
       description: "首頁時間軸在一分鐘內出現超過二十篇貼文"
     _viewInstanceChart:
       title: "分析師"
-      description: "顯示了實例的圖表"
+      description: "顯示了伺服器的圖表"
     _outputHelloWorldOnScratchpad:
       title: "Hello, world!"
       description: "在 AiScript 控制臺輸出了「hello world」"
@@ -1553,13 +1608,20 @@ _achievements:
     _tutorialCompleted:
       title: "Misskey新手講座 結業證書"
       description: "已完成教學課程"
+    _bubbleGameExplodingHead:
+      title: "🤯"
+      description: "氣泡遊戲中最大的物體出現了"
+    _bubbleGameDoubleExplodingHead:
+      title: "雙重🤯"
+      description: "氣泡遊戲中最大的物體同時出現了兩個"
+      flavor: "這樣大小的便當盒,用 🤯 🤯 稍微裝滿一些吧"
 _role:
   new: "建立角色"
   edit: "編輯角色"
   name: "角色名稱"
   description: "角色描述 "
   permission: "角色的權限"
-  descriptionOfPermission: "<b>審查員</b>執行與審查相關的基本操作。\n<b>管理員</b>能變更實例的全部設定"
+  descriptionOfPermission: "<b>審查員</b>執行與審查相關的基本操作。\n<b>管理員</b>能變更伺服器的全部設定。"
   assignTarget: "指派目標"
   descriptionOfAssignTarget: "<b>手動</b>是以手動管理這個角色包含的人員。\n<b>符合條件</b>是設定條件以自動包含符合條件的使用者。"
   manual: "手動"
@@ -1575,7 +1637,7 @@ _role:
   baseRole: "基本角色"
   useBaseValue: "使用基本角色的值"
   chooseRoleToAssign: "選擇要指派的角色"
-  iconUrl: "圖示的URL"
+  iconUrl: "圖示的 URL"
   asBadge: "顯示為徽章"
   descriptionOfAsBadge: "開啟的話,角色圖示會顯示在使用者名稱旁邊。"
   isExplorable: "讓使用者更容易找到您"
@@ -1593,7 +1655,8 @@ _role:
     gtlAvailable: "瀏覽全域時間軸"
     ltlAvailable: "瀏覽本地時間軸"
     canPublicNote: "允許公開貼文"
-    canInvite: "發行實例邀請碼"
+    mentionMax: "貼文內的最大提及數"
+    canInvite: "發行伺服器邀請碼"
     inviteLimit: "可建立邀請碼的數量"
     inviteLimitCycle: "邀請碼的發放間隔"
     inviteExpirationTime: "邀請碼的有效日期"
@@ -1616,6 +1679,7 @@ _role:
     canUseTranslator: "使用翻譯功能"
     avatarDecorationLimit: "頭像裝飾的最大設置量"
   _condition:
+    roleAssignedTo: "手動指派角色完成"
     isLocal: "本地使用者"
     isRemote: "遠端使用者"
     createdLessThan: "帳戶加入時間不超過"
@@ -1671,7 +1735,7 @@ _ad:
 _forgotPassword:
   enterEmail: "請輸入您的帳戶註冊的電子郵件地址。 密碼重置連結將被發送到該電子郵件地址。"
   ifNoEmail: "如果您還沒有註冊您的電子郵件地址,請聯繫管理員。 "
-  contactAdmin: "此實例不支持電子郵件,請聯繫您的管理員重置您的密碼。 "
+  contactAdmin: "本伺服器不支援電子郵件,請聯繫您的管理員重置您的密碼。 "
 _gallery:
   my: "我的貼文"
   liked: "喜歡的貼文"
@@ -1716,6 +1780,8 @@ _aboutMisskey:
   contributors: "主要貢獻者"
   allContributors: "全體貢獻人員"
   source: "原始碼"
+  original: "原始"
+  thisIsModifiedVersion: "{name} 使用原始 Misskey 的修改版本。"
   translation: "翻譯 Misskey"
   donate: "贊助 Misskey"
   morePatrons: "還有許許多多幫助我們的其他人,非常感謝你們。 🥰"
@@ -1758,8 +1824,8 @@ _wordMute:
 _instanceMute:
   instanceMuteDescription: "包括對被靜音伺服器上的使用者的回覆,被設定的伺服器上所有貼文及轉發都會被靜音。"
   instanceMuteDescription2: "設定時以換行進行分隔"
-  title: "將隱藏被設定的實例貼文。"
-  heading: "將實例靜音"
+  title: "將隱藏被設定的伺服器貼文。"
+  heading: "將伺服器靜音"
 _theme:
   explore: "探索佈景主題"
   install: "安裝佈景主題"
@@ -1927,14 +1993,63 @@ _permissions:
   "write:user-groups": "編輯使用者群組"
   "read:channels": "已查看的頻道"
   "write:channels": "編輯頻道"
-  "read:gallery": "瀏覽圖庫"
-  "write:gallery": "操作圖庫"
-  "read:gallery-likes": "讀取喜歡的圖片"
-  "write:gallery-likes": "操作喜歡的圖片"
+  "read:gallery": "瀏覽相簿"
+  "write:gallery": "編輯相簿"
+  "read:gallery-likes": "瀏覽相簿的讚"
+  "write:gallery-likes": "編輯相簿的讚"
   "read:flash": "檢視 Play"
   "write:flash": "編輯 Play"
   "read:flash-likes": "檢視 Play 的讚"
   "write:flash-likes": "編輯 Play 的讚"
+  "read:admin:abuse-user-reports": "查看來自使用者的檢舉"
+  "write:admin:delete-account": "刪除使用者帳戶"
+  "write:admin:delete-all-files-of-a-user": "刪除使用者的所有檔案"
+  "read:admin:index-stats": "查看資料庫索引的相關資訊"
+  "read:admin:table-stats": "查看資料庫表格的相關資訊"
+  "read:admin:user-ips": "查看使用者的 IP 位址"
+  "read:admin:meta": "查看實例的元資料"
+  "write:admin:reset-password": "重設使用者的密碼"
+  "write:admin:resolve-abuse-user-report": "解決來自使用者的檢舉"
+  "write:admin:send-email": "發送郵件"
+  "read:admin:server-info": "查看伺服器的資訊"
+  "read:admin:show-moderation-log": "查看審查紀錄"
+  "read:admin:show-user": "查看使用者的私密資訊"
+  "read:admin:show-users": "查看使用者的私密資訊"
+  "write:admin:suspend-user": "凍結使用者"
+  "write:admin:unset-user-avatar": "刪除使用者的頭像"
+  "write:admin:unset-user-banner": "刪除使用者的橫幅"
+  "write:admin:unsuspend-user": "解除凍結使用者"
+  "write:admin:meta": "編輯實例的元資料"
+  "write:admin:user-note": "編輯審查筆記"
+  "write:admin:roles": "編輯角色"
+  "read:admin:roles": "查看角色"
+  "write:admin:relays": "編輯中繼器"
+  "read:admin:relays": "查看中繼器"
+  "write:admin:invite-codes": "編輯邀請碼"
+  "read:admin:invite-codes": "查看邀請碼"
+  "write:admin:announcements": "編輯公告"
+  "read:admin:announcements": "查看公告"
+  "write:admin:avatar-decorations": "編輯頭像裝飾"
+  "read:admin:avatar-decorations": "查看頭像裝飾"
+  "write:admin:federation": "編輯站台聯邦的相關資訊"
+  "write:admin:account": "編輯使用者帳戶"
+  "read:admin:account": "查看使用者的相關資訊"
+  "write:admin:emoji": "編輯表情符號"
+  "read:admin:emoji": "查看表情符號"
+  "write:admin:queue": "編輯工作佇列"
+  "read:admin:queue": "查看工作佇列的相關資訊"
+  "write:admin:promo": "編輯推廣貼文"
+  "write:admin:drive": "編輯使用者的雲端硬碟"
+  "read:admin:drive": "查看使用者雲端硬碟的相關資訊"
+  "read:admin:stream": "使用管理員的 Websocket API"
+  "write:admin:ad": "編輯廣告"
+  "read:admin:ad": "查看廣告"
+  "write:invite-codes": "建立邀請碼"
+  "read:invite-codes": "取得邀請碼"
+  "write:clip-favorite": "編輯摘錄的讚"
+  "read:clip-favorite": "查看摘錄的讚"
+  "read:federation": "查看站台聯邦的相關資訊"
+  "write:report-abuse": "檢舉違規行為"
 _auth:
   shareAccessTitle: "應用程式的存取權限"
   shareAccess: "要授權「“{name}”」存取您的帳戶嗎?"
@@ -1961,7 +2076,7 @@ _weekday:
   saturday: "週六"
 _widgets:
   profile: "個人檔案"
-  instanceInfo: "實例資訊"
+  instanceInfo: "伺服器資訊"
   memo: "備忘錄"
   notifications: "通知"
   timeline: "時間軸"
@@ -1975,7 +2090,7 @@ _widgets:
   digitalClock: "電子時鐘"
   unixClock: "UNIX 時間"
   federation: "聯邦宇宙"
-  instanceCloud: "實例雲"
+  instanceCloud: "伺服器雲"
   postForm: "發文視窗"
   slideshow: "幻燈片"
   button: "按鈕"
@@ -2008,7 +2123,7 @@ _poll:
   deadlineTime: "小時"
   duration: "時長"
   votesCount: "{n} 票"
-  totalVotes: "一共{n}票"
+  totalVotes: "合計 {n} 票"
   vote: "投票"
   showResult: "顯示結果"
   voted: "已投票"
@@ -2027,7 +2142,7 @@ _visibility:
   specified: "指定使用者"
   specifiedDescription: "僅發布至指定使用者"
   disableFederation: "停用聯邦"
-  disableFederationDescription: "不要傳遞給其他實例"
+  disableFederationDescription: "不發送到其他伺服器"
 _postForm:
   replyPlaceholder: "回覆此貼文..."
   quotePlaceholder: "引用此貼文..."
@@ -2056,6 +2171,7 @@ _profile:
 _exportOrImport:
   allNotes: "所有貼文"
   favoritedNotes: "「我的最愛」貼文"
+  clips: "摘錄"
   followingList: "追隨中"
   muteList: "靜音"
   blockingList: "封鎖"
@@ -2184,6 +2300,7 @@ _notification:
   reactedBySomeUsers: "{n}人做出了反應"
   renotedBySomeUsers: "{n}人做了轉發"
   followedBySomeUsers: "被{n}人追隨了"
+  flushNotification: "重置通知歷史紀錄"
   _types:
     all: "全部 "
     note: "使用者的最新貼文"
@@ -2269,7 +2386,7 @@ _moderationLogTypes:
   updateCustomEmoji: "更新自訂表情符號"
   deleteCustomEmoji: "刪除自訂表情符號"
   updateServerSettings: "更新伺服器設定"
-  updateUserNote: "更新管理筆記"
+  updateUserNote: "更新了使用者的管理筆記"
   deleteDriveFile: "刪除檔案"
   deleteNote: "刪除貼文"
   createGlobalAnnouncement: "建立全網通知"
@@ -2281,6 +2398,7 @@ _moderationLogTypes:
   resetPassword: "重設密碼"
   suspendRemoteInstance: "封鎖遠端伺服器"
   unsuspendRemoteInstance: "解除封鎖遠端伺服器"
+  updateRemoteInstanceNote: "更新了遠端伺服器的管理筆記"
   markSensitiveDriveFile: "標記為敏感檔案"
   unmarkSensitiveDriveFile: "撤銷標記為敏感檔案"
   resolveAbuseReport: "解決檢舉"
@@ -2355,3 +2473,55 @@ _dataSaver:
   _code:
     title: "程式碼突出顯示"
     description: "如果使用了 MFM 的程式碼突顯標記,則在點擊之前不會載入。程式碼突顯要求加載每種程式語言的突顯定義檔案,但由於這些檔案不再自動載入,因此有望減少資料流量。"
+_hemisphere:
+  N: "北半球"
+  S: "南半球"
+  caption: "在某些客戶端的設定中,用於判斷季節。"
+_reversi:
+  reversi: "黑白棋"
+  gameSettings: "對弈設定"
+  chooseBoard: "選擇棋盤"
+  blackOrWhite: "先手/後手"
+  blackIs: "{name} 為黑棋(先攻)"
+  rules: "規則"
+  thisGameIsStartedSoon: "對弈即將開始"
+  waitingForOther: "等待對手準備就緒"
+  waitingForMe: "等待您準備就緒"
+  waitingBoth: "請準備"
+  ready: "準備就緒"
+  cancelReady: "重新準備"
+  opponentTurn: "對手的回合"
+  myTurn: "您的回合"
+  turnOf: "{name} 的回合"
+  pastTurnOf: "{name} 的回合"
+  surrender: "認輸"
+  surrendered: "對手認輸"
+  timeout: "時間到"
+  drawn: "平手"
+  won: "{name} 獲勝"
+  black: "黑"
+  white: "白"
+  total: "合計"
+  turnCount: "{count} 回合"
+  myGames: "我的對弈"
+  allGames: "所有對弈"
+  ended: "已結束"
+  playing: "正在對弈"
+  isLlotheo: "子較少的一方為勝(顛倒規則)"
+  loopedMap: "循環棋盤"
+  canPutEverywhere: "隨意置放模式"
+  timeLimitForEachTurn: "每回合的時間限制"
+  freeMatch: "自由對戰"
+  lookingForPlayer: "正在搜尋對手"
+  gameCanceled: "對弈已被取消"
+  shareToTlTheGameWhenStart: "在遊戲開始時將對弈資訊發布到時間軸"
+  iStartedAGame: "對弈開始了! #MisskeyReversi"
+  opponentHasSettingsChanged: "對手更改了設定"
+  allowIrregularRules: "允許異常規則(完全自由)"
+  disallowIrregularRules: "不允許異常規則"
+  showBoardLabels: "在棋盤上顯示行、列號"
+  useAvatarAsStone: "用大頭貼當作棋子"
+_offlineScreen:
+  title: "離線-無法連接伺服器"
+  header: "無法連接伺服器"
+
diff --git a/package.json b/package.json
index a7b105b310..5e89cba218 100644
--- a/package.json
+++ b/package.json
@@ -1,16 +1,19 @@
 {
 	"name": "sharkey",
-	"version": "2023.12.1",
+	"version": "2024.3.1",
 	"codename": "shonk",
 	"repository": {
 		"type": "git",
-		"url": "https://git.joinsharkey.org/Sharkey/Sharkey.git"
+		"url": "https://activitypub.software/TransFem-org/Sharkey.git"
 	},
-	"packageManager": "pnpm@8.12.1",
+	"packageManager": "pnpm@8.15.4",
 	"workspaces": [
 		"packages/frontend",
 		"packages/backend",
-		"packages/sw"
+		"packages/sw",
+		"packages/misskey-js",
+		"packages/misskey-reversi",
+		"packages/misskey-bubble-game"
 	],
 	"private": true,
 	"scripts": {
@@ -18,7 +21,7 @@
 		"build-assets": "node ./scripts/build-assets.mjs",
 		"build": "pnpm build-pre && pnpm -r build && pnpm build-assets",
 		"build-storybook": "pnpm --filter frontend build-storybook",
-		"build-misskey-js-with-types": "pnpm --filter backend build && pnpm --filter backend generate-api-json && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api",
+		"build-misskey-js-with-types": "pnpm build-pre && pnpm --filter backend... --filter=!misskey-js build && pnpm --filter backend generate-api-json && ncp packages/backend/built/api.json packages/misskey-js/generator/api.json && pnpm --filter misskey-js update-autogen-code && pnpm --filter misskey-js build && pnpm --filter misskey-js api",
 		"start": "pnpm check:connect && cd packages/backend && node ./built/boot/entry.js",
 		"start:test": "cd packages/backend && cross-env NODE_ENV=test node ./built/boot/entry.js",
 		"init": "pnpm migrate",
@@ -45,20 +48,23 @@
 		"lodash": "4.17.21"
 	},
 	"dependencies": {
+		"cssnano": "6.0.5",
 		"execa": "8.0.1",
-		"cssnano": "6.0.2",
+		"fast-glob": "3.3.2",
+		"ignore-walk": "6.0.4",
 		"js-yaml": "4.1.0",
-		"postcss": "8.4.32",
-		"terser": "5.26.0",
+		"postcss": "8.4.35",
+		"tar": "6.2.0",
+		"terser": "5.28.1",
 		"typescript": "5.3.3"
 	},
 	"devDependencies": {
-		"@typescript-eslint/eslint-plugin": "6.14.0",
-		"@typescript-eslint/parser": "6.14.0",
+		"@typescript-eslint/eslint-plugin": "7.1.0",
+		"@typescript-eslint/parser": "7.1.0",
 		"cross-env": "7.0.3",
-		"cypress": "13.6.1",
-		"eslint": "8.56.0",
-		"start-server-and-test": "2.0.3",
-		"ncp": "2.0.0"
+		"cypress": "13.6.6",
+		"eslint": "8.57.0",
+		"ncp": "2.0.0",
+		"start-server-and-test": "2.0.3"
 	}
 }
diff --git a/packages/backend/check_connect.js b/packages/backend/check_connect.js
index ea988a7f69..d88e649c09 100644
--- a/packages/backend/check_connect.js
+++ b/packages/backend/check_connect.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/generate_api_json.js b/packages/backend/generate_api_json.js
index 5819c60a5f..4079b3bb0a 100644
--- a/packages/backend/generate_api_json.js
+++ b/packages/backend/generate_api_json.js
@@ -3,6 +3,6 @@ import { genOpenapiSpec } from './built/server/api/openapi/gen-spec.js'
 import { writeFileSync } from "node:fs";
 
 const config = loadConfig();
-const spec = genOpenapiSpec(config);
+const spec = genOpenapiSpec(config, true);
 
 writeFileSync('./built/api.json', JSON.stringify(spec), 'utf-8');
\ No newline at end of file
diff --git a/packages/backend/jest.config.cjs b/packages/backend/jest.config.cjs
index 97d777c862..5a4aa4e15a 100644
--- a/packages/backend/jest.config.cjs
+++ b/packages/backend/jest.config.cjs
@@ -160,7 +160,6 @@ module.exports = {
 	testMatch: [
 		"<rootDir>/test/unit/**/*.ts",
 		"<rootDir>/src/**/*.test.ts",
-		"<rootDir>/test/e2e/**/*.ts",
 	],
 
 	// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
diff --git a/packages/backend/jest.config.e2e.cjs b/packages/backend/jest.config.e2e.cjs
new file mode 100644
index 0000000000..4502da47df
--- /dev/null
+++ b/packages/backend/jest.config.e2e.cjs
@@ -0,0 +1,15 @@
+/*
+* For a detailed explanation regarding each configuration property and type check, visit:
+* https://jestjs.io/docs/en/configuration.html
+*/
+
+const base = require('./jest.config.cjs')
+
+module.exports = {
+	...base,
+	globalSetup: "<rootDir>/built-test/entry.js",
+	setupFilesAfterEnv: ["<rootDir>/test/jest.setup.ts"],
+	testMatch: [
+		"<rootDir>/test/e2e/**/*.ts",
+	],
+};
diff --git a/packages/backend/jest.config.unit.cjs b/packages/backend/jest.config.unit.cjs
new file mode 100644
index 0000000000..aa5992936b
--- /dev/null
+++ b/packages/backend/jest.config.unit.cjs
@@ -0,0 +1,14 @@
+/*
+* For a detailed explanation regarding each configuration property and type check, visit:
+* https://jestjs.io/docs/en/configuration.html
+*/
+
+const base = require('./jest.config.cjs')
+
+module.exports = {
+	...base,
+	testMatch: [
+		"<rootDir>/test/unit/**/*.ts",
+		"<rootDir>/src/**/*.test.ts",
+	],
+};
diff --git a/packages/backend/migration/1000000000000-Init.js b/packages/backend/migration/1000000000000-Init.js
index 6f04b52ae1..c06885fd40 100644
--- a/packages/backend/migration/1000000000000-Init.js
+++ b/packages/backend/migration/1000000000000-Init.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1556348509290-Pages.js b/packages/backend/migration/1556348509290-Pages.js
index 05d801227b..c7542e808c 100644
--- a/packages/backend/migration/1556348509290-Pages.js
+++ b/packages/backend/migration/1556348509290-Pages.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1556746559567-UserProfile.js b/packages/backend/migration/1556746559567-UserProfile.js
index 7cc1ba0083..13ff6ce6bf 100644
--- a/packages/backend/migration/1556746559567-UserProfile.js
+++ b/packages/backend/migration/1556746559567-UserProfile.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1557476068003-PinnedUsers.js b/packages/backend/migration/1557476068003-PinnedUsers.js
index 12f0b8fc6a..f2f1deae2f 100644
--- a/packages/backend/migration/1557476068003-PinnedUsers.js
+++ b/packages/backend/migration/1557476068003-PinnedUsers.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1557761316509-AddSomeUrls.js b/packages/backend/migration/1557761316509-AddSomeUrls.js
index b83ce2ed5e..c73f30c49f 100644
--- a/packages/backend/migration/1557761316509-AddSomeUrls.js
+++ b/packages/backend/migration/1557761316509-AddSomeUrls.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1557932705754-ObjectStorageSetting.js b/packages/backend/migration/1557932705754-ObjectStorageSetting.js
index 736dcafaac..0e1ef321ab 100644
--- a/packages/backend/migration/1557932705754-ObjectStorageSetting.js
+++ b/packages/backend/migration/1557932705754-ObjectStorageSetting.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1558072954435-PageLike.js b/packages/backend/migration/1558072954435-PageLike.js
index d9502a6e03..a08f68a0e6 100644
--- a/packages/backend/migration/1558072954435-PageLike.js
+++ b/packages/backend/migration/1558072954435-PageLike.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1558103093633-UserGroup.js b/packages/backend/migration/1558103093633-UserGroup.js
index b3cc6eb949..f762dc2371 100644
--- a/packages/backend/migration/1558103093633-UserGroup.js
+++ b/packages/backend/migration/1558103093633-UserGroup.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1558257926829-UserGroupInvite.js b/packages/backend/migration/1558257926829-UserGroupInvite.js
index a87173cdfe..853b52d17d 100644
--- a/packages/backend/migration/1558257926829-UserGroupInvite.js
+++ b/packages/backend/migration/1558257926829-UserGroupInvite.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1558266512381-UserListJoining.js b/packages/backend/migration/1558266512381-UserListJoining.js
index bc94b7f425..e161d52f12 100644
--- a/packages/backend/migration/1558266512381-UserListJoining.js
+++ b/packages/backend/migration/1558266512381-UserListJoining.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1561706992953-webauthn.js b/packages/backend/migration/1561706992953-webauthn.js
index fa9b1188ca..4c81035ff1 100644
--- a/packages/backend/migration/1561706992953-webauthn.js
+++ b/packages/backend/migration/1561706992953-webauthn.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1561873850023-ChartIndexes.js b/packages/backend/migration/1561873850023-ChartIndexes.js
index c7e93ba7b7..3f190ce143 100644
--- a/packages/backend/migration/1561873850023-ChartIndexes.js
+++ b/packages/backend/migration/1561873850023-ChartIndexes.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1562422242907-PasswordLessLogin.js b/packages/backend/migration/1562422242907-PasswordLessLogin.js
index 3df3a6f5f5..4c0fbbbc9f 100644
--- a/packages/backend/migration/1562422242907-PasswordLessLogin.js
+++ b/packages/backend/migration/1562422242907-PasswordLessLogin.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1562444565093-PinnedPage.js b/packages/backend/migration/1562444565093-PinnedPage.js
index 329d49bbed..89639399f0 100644
--- a/packages/backend/migration/1562444565093-PinnedPage.js
+++ b/packages/backend/migration/1562444565093-PinnedPage.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1562448332510-PageTitleHideOption.js b/packages/backend/migration/1562448332510-PageTitleHideOption.js
index e41db08090..70d54aa777 100644
--- a/packages/backend/migration/1562448332510-PageTitleHideOption.js
+++ b/packages/backend/migration/1562448332510-PageTitleHideOption.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1562869971568-ModerationLog.js b/packages/backend/migration/1562869971568-ModerationLog.js
index 2eb3015d5c..3dd9b22edf 100644
--- a/packages/backend/migration/1562869971568-ModerationLog.js
+++ b/packages/backend/migration/1562869971568-ModerationLog.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1563757595828-UsedUsername.js b/packages/backend/migration/1563757595828-UsedUsername.js
index 91d9d36b9d..258e5abab2 100644
--- a/packages/backend/migration/1563757595828-UsedUsername.js
+++ b/packages/backend/migration/1563757595828-UsedUsername.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1565634203341-room.js b/packages/backend/migration/1565634203341-room.js
index c2e5fca863..04c9749c1b 100644
--- a/packages/backend/migration/1565634203341-room.js
+++ b/packages/backend/migration/1565634203341-room.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1571220798684-CustomEmojiCategory.js b/packages/backend/migration/1571220798684-CustomEmojiCategory.js
index f211af67be..1fc78a65ff 100644
--- a/packages/backend/migration/1571220798684-CustomEmojiCategory.js
+++ b/packages/backend/migration/1571220798684-CustomEmojiCategory.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1572760203493-nodeinfo.js b/packages/backend/migration/1572760203493-nodeinfo.js
index c281b0b2db..ea7a67bc3e 100644
--- a/packages/backend/migration/1572760203493-nodeinfo.js
+++ b/packages/backend/migration/1572760203493-nodeinfo.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1576269851876-TalkFederationId.js b/packages/backend/migration/1576269851876-TalkFederationId.js
index 045f9ddb04..c49c716e7a 100644
--- a/packages/backend/migration/1576269851876-TalkFederationId.js
+++ b/packages/backend/migration/1576269851876-TalkFederationId.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1576869585998-ProxyRemoteFiles.js b/packages/backend/migration/1576869585998-ProxyRemoteFiles.js
index 0dde1ae70c..192dbe3485 100644
--- a/packages/backend/migration/1576869585998-ProxyRemoteFiles.js
+++ b/packages/backend/migration/1576869585998-ProxyRemoteFiles.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1579267006611-v12.js b/packages/backend/migration/1579267006611-v12.js
index 86f9da7e7a..9267be5630 100644
--- a/packages/backend/migration/1579267006611-v12.js
+++ b/packages/backend/migration/1579267006611-v12.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1579270193251-v12-2.js b/packages/backend/migration/1579270193251-v12-2.js
index 2593aca573..e2ca9709ea 100644
--- a/packages/backend/migration/1579270193251-v12-2.js
+++ b/packages/backend/migration/1579270193251-v12-2.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1579282808087-v12-3.js b/packages/backend/migration/1579282808087-v12-3.js
index a816b2e82e..4098f041c8 100644
--- a/packages/backend/migration/1579282808087-v12-3.js
+++ b/packages/backend/migration/1579282808087-v12-3.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1579544426412-v12-4.js b/packages/backend/migration/1579544426412-v12-4.js
index 600dc270a5..1153993f35 100644
--- a/packages/backend/migration/1579544426412-v12-4.js
+++ b/packages/backend/migration/1579544426412-v12-4.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1579977526288-v12-5.js b/packages/backend/migration/1579977526288-v12-5.js
index 73f3343347..d9e1b48bb2 100644
--- a/packages/backend/migration/1579977526288-v12-5.js
+++ b/packages/backend/migration/1579977526288-v12-5.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1579993013959-v12-6.js b/packages/backend/migration/1579993013959-v12-6.js
index 5009e0aa88..9c249422a2 100644
--- a/packages/backend/migration/1579993013959-v12-6.js
+++ b/packages/backend/migration/1579993013959-v12-6.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1580069531114-v12-7.js b/packages/backend/migration/1580069531114-v12-7.js
index ff943ffa6b..ceee6b2031 100644
--- a/packages/backend/migration/1580069531114-v12-7.js
+++ b/packages/backend/migration/1580069531114-v12-7.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1580148575182-v12-8.js b/packages/backend/migration/1580148575182-v12-8.js
index 20b77b391f..6841dcc38f 100644
--- a/packages/backend/migration/1580148575182-v12-8.js
+++ b/packages/backend/migration/1580148575182-v12-8.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1580154400017-v12-9.js b/packages/backend/migration/1580154400017-v12-9.js
index f78dc47456..c01d8089d0 100644
--- a/packages/backend/migration/1580154400017-v12-9.js
+++ b/packages/backend/migration/1580154400017-v12-9.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1580276619901-v12-10.js b/packages/backend/migration/1580276619901-v12-10.js
index 09fa27ae83..be6e467fab 100644
--- a/packages/backend/migration/1580276619901-v12-10.js
+++ b/packages/backend/migration/1580276619901-v12-10.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1580331224276-v12-11.js b/packages/backend/migration/1580331224276-v12-11.js
index f118c34937..af817a8c8a 100644
--- a/packages/backend/migration/1580331224276-v12-11.js
+++ b/packages/backend/migration/1580331224276-v12-11.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1580508795118-v12-12.js b/packages/backend/migration/1580508795118-v12-12.js
index 4fba933a08..4bd855f7ab 100644
--- a/packages/backend/migration/1580508795118-v12-12.js
+++ b/packages/backend/migration/1580508795118-v12-12.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1580543501339-v12-13.js b/packages/backend/migration/1580543501339-v12-13.js
index 9344516309..be76c02163 100644
--- a/packages/backend/migration/1580543501339-v12-13.js
+++ b/packages/backend/migration/1580543501339-v12-13.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1580864313253-v12-14.js b/packages/backend/migration/1580864313253-v12-14.js
index 5034492a70..f8891a2b66 100644
--- a/packages/backend/migration/1580864313253-v12-14.js
+++ b/packages/backend/migration/1580864313253-v12-14.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1581526429287-user-group-invitation.js b/packages/backend/migration/1581526429287-user-group-invitation.js
index fc81813807..51703e2ba1 100644
--- a/packages/backend/migration/1581526429287-user-group-invitation.js
+++ b/packages/backend/migration/1581526429287-user-group-invitation.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1581695816408-user-group-antenna.js b/packages/backend/migration/1581695816408-user-group-antenna.js
index 8a212c092a..e6791ba1a4 100644
--- a/packages/backend/migration/1581695816408-user-group-antenna.js
+++ b/packages/backend/migration/1581695816408-user-group-antenna.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1581708415836-drive-user-folder-id-index.js b/packages/backend/migration/1581708415836-drive-user-folder-id-index.js
index 6594078db8..28ce4cc142 100644
--- a/packages/backend/migration/1581708415836-drive-user-folder-id-index.js
+++ b/packages/backend/migration/1581708415836-drive-user-folder-id-index.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1581979837262-promo.js b/packages/backend/migration/1581979837262-promo.js
index 585564a400..707c85fcb3 100644
--- a/packages/backend/migration/1581979837262-promo.js
+++ b/packages/backend/migration/1581979837262-promo.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1582019042083-featured-injecttion.js b/packages/backend/migration/1582019042083-featured-injecttion.js
index d270006277..f308f0a454 100644
--- a/packages/backend/migration/1582019042083-featured-injecttion.js
+++ b/packages/backend/migration/1582019042083-featured-injecttion.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1582210532752-antenna-exclude.js b/packages/backend/migration/1582210532752-antenna-exclude.js
index 12eee2364c..9b87e3ff39 100644
--- a/packages/backend/migration/1582210532752-antenna-exclude.js
+++ b/packages/backend/migration/1582210532752-antenna-exclude.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1582875306439-note-reaction-length.js b/packages/backend/migration/1582875306439-note-reaction-length.js
index a4413c9533..e801d1ac44 100644
--- a/packages/backend/migration/1582875306439-note-reaction-length.js
+++ b/packages/backend/migration/1582875306439-note-reaction-length.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1585361548360-miauth.js b/packages/backend/migration/1585361548360-miauth.js
index d073fa3d26..d5932c6083 100644
--- a/packages/backend/migration/1585361548360-miauth.js
+++ b/packages/backend/migration/1585361548360-miauth.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1585385921215-custom-notification.js b/packages/backend/migration/1585385921215-custom-notification.js
index a3336e0eca..35303b99e9 100644
--- a/packages/backend/migration/1585385921215-custom-notification.js
+++ b/packages/backend/migration/1585385921215-custom-notification.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1585772678853-ap-url.js b/packages/backend/migration/1585772678853-ap-url.js
index f67f5a4542..f978fc80b4 100644
--- a/packages/backend/migration/1585772678853-ap-url.js
+++ b/packages/backend/migration/1585772678853-ap-url.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1586624197029-AddObjectStorageUseProxy.js b/packages/backend/migration/1586624197029-AddObjectStorageUseProxy.js
index 16f7599b80..fde8629bba 100644
--- a/packages/backend/migration/1586624197029-AddObjectStorageUseProxy.js
+++ b/packages/backend/migration/1586624197029-AddObjectStorageUseProxy.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1586641139527-remote-reaction.js b/packages/backend/migration/1586641139527-remote-reaction.js
index 666bb42ca6..3e907af5f1 100644
--- a/packages/backend/migration/1586641139527-remote-reaction.js
+++ b/packages/backend/migration/1586641139527-remote-reaction.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1586708940386-pageAiScript.js b/packages/backend/migration/1586708940386-pageAiScript.js
index 3d0d0ab915..ce5007cea1 100644
--- a/packages/backend/migration/1586708940386-pageAiScript.js
+++ b/packages/backend/migration/1586708940386-pageAiScript.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1588044505511-hCaptcha.js b/packages/backend/migration/1588044505511-hCaptcha.js
index 22cc6672c5..aeacb653b3 100644
--- a/packages/backend/migration/1588044505511-hCaptcha.js
+++ b/packages/backend/migration/1588044505511-hCaptcha.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1589023282116-pubRelay.js b/packages/backend/migration/1589023282116-pubRelay.js
index ed010699e1..8739adb733 100644
--- a/packages/backend/migration/1589023282116-pubRelay.js
+++ b/packages/backend/migration/1589023282116-pubRelay.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1595075960584-blurhash.js b/packages/backend/migration/1595075960584-blurhash.js
index 967676531f..9752625cd2 100644
--- a/packages/backend/migration/1595075960584-blurhash.js
+++ b/packages/backend/migration/1595075960584-blurhash.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1595077605646-blurhash-for-avatar-banner.js b/packages/backend/migration/1595077605646-blurhash-for-avatar-banner.js
index 7df079ac05..fdff8c633a 100644
--- a/packages/backend/migration/1595077605646-blurhash-for-avatar-banner.js
+++ b/packages/backend/migration/1595077605646-blurhash-for-avatar-banner.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1595676934834-instance-icon-url.js b/packages/backend/migration/1595676934834-instance-icon-url.js
index 6bccff082b..5f834064c4 100644
--- a/packages/backend/migration/1595676934834-instance-icon-url.js
+++ b/packages/backend/migration/1595676934834-instance-icon-url.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1595771249699-word-mute.js b/packages/backend/migration/1595771249699-word-mute.js
index cfd0a5ccc1..f4fa1227e3 100644
--- a/packages/backend/migration/1595771249699-word-mute.js
+++ b/packages/backend/migration/1595771249699-word-mute.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1595782306083-word-mute2.js b/packages/backend/migration/1595782306083-word-mute2.js
index 64acf2b721..3c2062ec07 100644
--- a/packages/backend/migration/1595782306083-word-mute2.js
+++ b/packages/backend/migration/1595782306083-word-mute2.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1596548170836-channel.js b/packages/backend/migration/1596548170836-channel.js
index a26991d4d8..ee6753a476 100644
--- a/packages/backend/migration/1596548170836-channel.js
+++ b/packages/backend/migration/1596548170836-channel.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1596786425167-channel2.js b/packages/backend/migration/1596786425167-channel2.js
index 4e87b11bb5..9e6ead4378 100644
--- a/packages/backend/migration/1596786425167-channel2.js
+++ b/packages/backend/migration/1596786425167-channel2.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1597230137744-objectStorageSetPublicRead.js b/packages/backend/migration/1597230137744-objectStorageSetPublicRead.js
index 93e6f186d5..bc32d4a052 100644
--- a/packages/backend/migration/1597230137744-objectStorageSetPublicRead.js
+++ b/packages/backend/migration/1597230137744-objectStorageSetPublicRead.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1597236229720-IncludingNotificationTypes.js b/packages/backend/migration/1597236229720-IncludingNotificationTypes.js
index bda702d999..99686bd70e 100644
--- a/packages/backend/migration/1597236229720-IncludingNotificationTypes.js
+++ b/packages/backend/migration/1597236229720-IncludingNotificationTypes.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1597385880794-add-sensitive-index.js b/packages/backend/migration/1597385880794-add-sensitive-index.js
index ffb94895d7..a67810880b 100644
--- a/packages/backend/migration/1597385880794-add-sensitive-index.js
+++ b/packages/backend/migration/1597385880794-add-sensitive-index.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1597459042300-channel-unread.js b/packages/backend/migration/1597459042300-channel-unread.js
index 5b94d8296a..ced9b5265a 100644
--- a/packages/backend/migration/1597459042300-channel-unread.js
+++ b/packages/backend/migration/1597459042300-channel-unread.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1597893996136-ChannelNoteIdDescIndex.js b/packages/backend/migration/1597893996136-ChannelNoteIdDescIndex.js
index 543e511404..ca4eba385e 100644
--- a/packages/backend/migration/1597893996136-ChannelNoteIdDescIndex.js
+++ b/packages/backend/migration/1597893996136-ChannelNoteIdDescIndex.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1600353287890-mutingNotificationTypes.js b/packages/backend/migration/1600353287890-mutingNotificationTypes.js
index 4e0b8ad6eb..0996aa21f6 100644
--- a/packages/backend/migration/1600353287890-mutingNotificationTypes.js
+++ b/packages/backend/migration/1600353287890-mutingNotificationTypes.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1603094348345-refine-abuse-user-report.js b/packages/backend/migration/1603094348345-refine-abuse-user-report.js
index 4e052e07c2..354915b165 100644
--- a/packages/backend/migration/1603094348345-refine-abuse-user-report.js
+++ b/packages/backend/migration/1603094348345-refine-abuse-user-report.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1603095701770-refine-abuse-user-report2.js b/packages/backend/migration/1603095701770-refine-abuse-user-report2.js
index 2eb205c6e0..75dd3513b5 100644
--- a/packages/backend/migration/1603095701770-refine-abuse-user-report2.js
+++ b/packages/backend/migration/1603095701770-refine-abuse-user-report2.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1603776877564-instance-theme-color.js b/packages/backend/migration/1603776877564-instance-theme-color.js
index 5f83bc14e6..c8ab89ab56 100644
--- a/packages/backend/migration/1603776877564-instance-theme-color.js
+++ b/packages/backend/migration/1603776877564-instance-theme-color.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1603781553011-instance-favicon.js b/packages/backend/migration/1603781553011-instance-favicon.js
index 758b86408f..7d793d4f1f 100644
--- a/packages/backend/migration/1603781553011-instance-favicon.js
+++ b/packages/backend/migration/1603781553011-instance-favicon.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1604821689616-delete-auto-watch.js b/packages/backend/migration/1604821689616-delete-auto-watch.js
index 917ef5b10c..8160877038 100644
--- a/packages/backend/migration/1604821689616-delete-auto-watch.js
+++ b/packages/backend/migration/1604821689616-delete-auto-watch.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1605408848373-clip-description.js b/packages/backend/migration/1605408848373-clip-description.js
index fedc603b3c..77a218791c 100644
--- a/packages/backend/migration/1605408848373-clip-description.js
+++ b/packages/backend/migration/1605408848373-clip-description.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1605408971051-comments.js b/packages/backend/migration/1605408971051-comments.js
index 8ab16859d2..494bfb7950 100644
--- a/packages/backend/migration/1605408971051-comments.js
+++ b/packages/backend/migration/1605408971051-comments.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1605585339718-instance-pinned-pages.js b/packages/backend/migration/1605585339718-instance-pinned-pages.js
index 767139c9e5..8f4c806439 100644
--- a/packages/backend/migration/1605585339718-instance-pinned-pages.js
+++ b/packages/backend/migration/1605585339718-instance-pinned-pages.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1605965516823-instance-images.js b/packages/backend/migration/1605965516823-instance-images.js
index 848b53f1ba..9cc2eb4032 100644
--- a/packages/backend/migration/1605965516823-instance-images.js
+++ b/packages/backend/migration/1605965516823-instance-images.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1606191203881-no-crawle.js b/packages/backend/migration/1606191203881-no-crawle.js
index 5c878f5a24..af04566eaa 100644
--- a/packages/backend/migration/1606191203881-no-crawle.js
+++ b/packages/backend/migration/1606191203881-no-crawle.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1607151207216-instance-pinned-clip.js b/packages/backend/migration/1607151207216-instance-pinned-clip.js
index 67db39fede..f85c3d42d7 100644
--- a/packages/backend/migration/1607151207216-instance-pinned-clip.js
+++ b/packages/backend/migration/1607151207216-instance-pinned-clip.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1607353487793-isExplorable.js b/packages/backend/migration/1607353487793-isExplorable.js
index 95ee07e917..e07fe6c306 100644
--- a/packages/backend/migration/1607353487793-isExplorable.js
+++ b/packages/backend/migration/1607353487793-isExplorable.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1610277136869-registry.js b/packages/backend/migration/1610277136869-registry.js
index c5fe2c5a62..1a10f23590 100644
--- a/packages/backend/migration/1610277136869-registry.js
+++ b/packages/backend/migration/1610277136869-registry.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1610277585759-registry2.js b/packages/backend/migration/1610277585759-registry2.js
index f734a235b0..46e56279f4 100644
--- a/packages/backend/migration/1610277585759-registry2.js
+++ b/packages/backend/migration/1610277585759-registry2.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1610283021566-registry3.js b/packages/backend/migration/1610283021566-registry3.js
index c94546c732..402040f38b 100644
--- a/packages/backend/migration/1610283021566-registry3.js
+++ b/packages/backend/migration/1610283021566-registry3.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1611354329133-followersUri.js b/packages/backend/migration/1611354329133-followersUri.js
index 7e5f8c3093..15abb2a9d1 100644
--- a/packages/backend/migration/1611354329133-followersUri.js
+++ b/packages/backend/migration/1611354329133-followersUri.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1611397665007-gallery.js b/packages/backend/migration/1611397665007-gallery.js
index cd5c39cc10..cbd2b62c56 100644
--- a/packages/backend/migration/1611397665007-gallery.js
+++ b/packages/backend/migration/1611397665007-gallery.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1611547387175-objectStorageS3ForcePathStyle.js b/packages/backend/migration/1611547387175-objectStorageS3ForcePathStyle.js
index c0b1da1e53..c5440b7a48 100644
--- a/packages/backend/migration/1611547387175-objectStorageS3ForcePathStyle.js
+++ b/packages/backend/migration/1611547387175-objectStorageS3ForcePathStyle.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1612619156584-announcement-email.js b/packages/backend/migration/1612619156584-announcement-email.js
index f8277725f7..ddacab322b 100644
--- a/packages/backend/migration/1612619156584-announcement-email.js
+++ b/packages/backend/migration/1612619156584-announcement-email.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1613155914446-emailNotificationTypes.js b/packages/backend/migration/1613155914446-emailNotificationTypes.js
index 3afe491e48..d34ba7e826 100644
--- a/packages/backend/migration/1613155914446-emailNotificationTypes.js
+++ b/packages/backend/migration/1613155914446-emailNotificationTypes.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1613181457597-user-lang.js b/packages/backend/migration/1613181457597-user-lang.js
index 33e363477f..6ef5245953 100644
--- a/packages/backend/migration/1613181457597-user-lang.js
+++ b/packages/backend/migration/1613181457597-user-lang.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1613503367223-use-bigint-for-driveUsage.js b/packages/backend/migration/1613503367223-use-bigint-for-driveUsage.js
index 9c75c0ae54..8529ea3247 100644
--- a/packages/backend/migration/1613503367223-use-bigint-for-driveUsage.js
+++ b/packages/backend/migration/1613503367223-use-bigint-for-driveUsage.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1615965918224-chart-v2.js b/packages/backend/migration/1615965918224-chart-v2.js
index 2c0cacd1d9..deecde7227 100644
--- a/packages/backend/migration/1615965918224-chart-v2.js
+++ b/packages/backend/migration/1615965918224-chart-v2.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1615966519402-chart-v2-2.js b/packages/backend/migration/1615966519402-chart-v2-2.js
index 8d6ebf6a81..7842a27108 100644
--- a/packages/backend/migration/1615966519402-chart-v2-2.js
+++ b/packages/backend/migration/1615966519402-chart-v2-2.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1618637372000-user-last-active-date.js b/packages/backend/migration/1618637372000-user-last-active-date.js
index 8b4652898d..7caf179fa5 100644
--- a/packages/backend/migration/1618637372000-user-last-active-date.js
+++ b/packages/backend/migration/1618637372000-user-last-active-date.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1618639857000-user-hide-online-status.js b/packages/backend/migration/1618639857000-user-hide-online-status.js
index 1f19a7ebb4..2012962742 100644
--- a/packages/backend/migration/1618639857000-user-hide-online-status.js
+++ b/packages/backend/migration/1618639857000-user-hide-online-status.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1619942102890-password-reset.js b/packages/backend/migration/1619942102890-password-reset.js
index 9898011774..7784da2bce 100644
--- a/packages/backend/migration/1619942102890-password-reset.js
+++ b/packages/backend/migration/1619942102890-password-reset.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1620019354680-ad.js b/packages/backend/migration/1620019354680-ad.js
index 1ae66d71f4..7630ed01a1 100644
--- a/packages/backend/migration/1620019354680-ad.js
+++ b/packages/backend/migration/1620019354680-ad.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1620364649428-ad2.js b/packages/backend/migration/1620364649428-ad2.js
index b9b26be076..7959185685 100644
--- a/packages/backend/migration/1620364649428-ad2.js
+++ b/packages/backend/migration/1620364649428-ad2.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1621479946000-add-note-indexes.js b/packages/backend/migration/1621479946000-add-note-indexes.js
index 299c1f6c02..f72bf8211e 100644
--- a/packages/backend/migration/1621479946000-add-note-indexes.js
+++ b/packages/backend/migration/1621479946000-add-note-indexes.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1622679304522-user-profile-description-length.js b/packages/backend/migration/1622679304522-user-profile-description-length.js
index 988456fe7d..7324175b46 100644
--- a/packages/backend/migration/1622679304522-user-profile-description-length.js
+++ b/packages/backend/migration/1622679304522-user-profile-description-length.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1622681548499-log-message-length.js b/packages/backend/migration/1622681548499-log-message-length.js
index e1fa22c88b..b4d8d497e3 100644
--- a/packages/backend/migration/1622681548499-log-message-length.js
+++ b/packages/backend/migration/1622681548499-log-message-length.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1626509500668-fix-remote-file-proxy.js b/packages/backend/migration/1626509500668-fix-remote-file-proxy.js
index 906e49cabb..9145247ab1 100644
--- a/packages/backend/migration/1626509500668-fix-remote-file-proxy.js
+++ b/packages/backend/migration/1626509500668-fix-remote-file-proxy.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1629004542760-chart-reindex.js b/packages/backend/migration/1629004542760-chart-reindex.js
index f1d08ecfe4..072cdec3c1 100644
--- a/packages/backend/migration/1629004542760-chart-reindex.js
+++ b/packages/backend/migration/1629004542760-chart-reindex.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1629024377804-deepl-integration.js b/packages/backend/migration/1629024377804-deepl-integration.js
index 465f1bcca9..5889196f15 100644
--- a/packages/backend/migration/1629024377804-deepl-integration.js
+++ b/packages/backend/migration/1629024377804-deepl-integration.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1629288472000-fix-channel-userId.js b/packages/backend/migration/1629288472000-fix-channel-userId.js
index 9f946ad550..d7907d05bd 100644
--- a/packages/backend/migration/1629288472000-fix-channel-userId.js
+++ b/packages/backend/migration/1629288472000-fix-channel-userId.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1629512953000-user-is-deleted.js b/packages/backend/migration/1629512953000-user-is-deleted.js
index 78bbd8bbee..94165e466b 100644
--- a/packages/backend/migration/1629512953000-user-is-deleted.js
+++ b/packages/backend/migration/1629512953000-user-is-deleted.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1629778475000-deepl-integration2.js b/packages/backend/migration/1629778475000-deepl-integration2.js
index b719dcf57f..a54daf8fb3 100644
--- a/packages/backend/migration/1629778475000-deepl-integration2.js
+++ b/packages/backend/migration/1629778475000-deepl-integration2.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1629833361000-AddShowTLReplies.js b/packages/backend/migration/1629833361000-AddShowTLReplies.js
index 00aef6aeb8..b80e2ef67f 100644
--- a/packages/backend/migration/1629833361000-AddShowTLReplies.js
+++ b/packages/backend/migration/1629833361000-AddShowTLReplies.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1629968054000_userInstanceBlocks.js b/packages/backend/migration/1629968054000_userInstanceBlocks.js
index e8168e372e..e88fa8aece 100644
--- a/packages/backend/migration/1629968054000_userInstanceBlocks.js
+++ b/packages/backend/migration/1629968054000_userInstanceBlocks.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1633068642000-email-required-for-signup.js b/packages/backend/migration/1633068642000-email-required-for-signup.js
index 230227d364..d23db2052f 100644
--- a/packages/backend/migration/1633068642000-email-required-for-signup.js
+++ b/packages/backend/migration/1633068642000-email-required-for-signup.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1633071909016-user-pending.js b/packages/backend/migration/1633071909016-user-pending.js
index f0d037967f..db0f2fde1a 100644
--- a/packages/backend/migration/1633071909016-user-pending.js
+++ b/packages/backend/migration/1633071909016-user-pending.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1634486652000-user-public-reactions.js b/packages/backend/migration/1634486652000-user-public-reactions.js
index 09870c79c6..ce1818886a 100644
--- a/packages/backend/migration/1634486652000-user-public-reactions.js
+++ b/packages/backend/migration/1634486652000-user-public-reactions.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1634902659689-delete-log.js b/packages/backend/migration/1634902659689-delete-log.js
index e4e625536b..2e2267f9f4 100644
--- a/packages/backend/migration/1634902659689-delete-log.js
+++ b/packages/backend/migration/1634902659689-delete-log.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1635500777168-note-thread-mute.js b/packages/backend/migration/1635500777168-note-thread-mute.js
index 9f376c4795..d5fca59594 100644
--- a/packages/backend/migration/1635500777168-note-thread-mute.js
+++ b/packages/backend/migration/1635500777168-note-thread-mute.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1636197624383-ff-visibility.js b/packages/backend/migration/1636197624383-ff-visibility.js
index aa089d42ac..27faae1c92 100644
--- a/packages/backend/migration/1636197624383-ff-visibility.js
+++ b/packages/backend/migration/1636197624383-ff-visibility.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1636697408073-remove-via-mobile.js b/packages/backend/migration/1636697408073-remove-via-mobile.js
index c014ceb921..81f0b63443 100644
--- a/packages/backend/migration/1636697408073-remove-via-mobile.js
+++ b/packages/backend/migration/1636697408073-remove-via-mobile.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1637320813000-forwarded-report.js b/packages/backend/migration/1637320813000-forwarded-report.js
index 0d1f48beb4..8125468aae 100644
--- a/packages/backend/migration/1637320813000-forwarded-report.js
+++ b/packages/backend/migration/1637320813000-forwarded-report.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1639325650583-chart-v3.js b/packages/backend/migration/1639325650583-chart-v3.js
index e6209e2b70..2255476394 100644
--- a/packages/backend/migration/1639325650583-chart-v3.js
+++ b/packages/backend/migration/1639325650583-chart-v3.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1642611822809-emoji-url.js b/packages/backend/migration/1642611822809-emoji-url.js
index 212fc957ad..421614b408 100644
--- a/packages/backend/migration/1642611822809-emoji-url.js
+++ b/packages/backend/migration/1642611822809-emoji-url.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1642613870898-drive-file-webpublic-type.js b/packages/backend/migration/1642613870898-drive-file-webpublic-type.js
index e50770fff3..e61a3fc49e 100644
--- a/packages/backend/migration/1642613870898-drive-file-webpublic-type.js
+++ b/packages/backend/migration/1642613870898-drive-file-webpublic-type.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1643963705770-chart-v4.js b/packages/backend/migration/1643963705770-chart-v4.js
index af0bd18e58..77355cd7f3 100644
--- a/packages/backend/migration/1643963705770-chart-v4.js
+++ b/packages/backend/migration/1643963705770-chart-v4.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1643966656277-chart-v5.js b/packages/backend/migration/1643966656277-chart-v5.js
index b3389a6539..54e4705e56 100644
--- a/packages/backend/migration/1643966656277-chart-v5.js
+++ b/packages/backend/migration/1643966656277-chart-v5.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1643967331284-chart-v6.js b/packages/backend/migration/1643967331284-chart-v6.js
index 1197bdd717..aa64bc9faa 100644
--- a/packages/backend/migration/1643967331284-chart-v6.js
+++ b/packages/backend/migration/1643967331284-chart-v6.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1644010796173-convert-hard-mutes.js b/packages/backend/migration/1644010796173-convert-hard-mutes.js
index 1a5316ac05..9aec21b5ff 100644
--- a/packages/backend/migration/1644010796173-convert-hard-mutes.js
+++ b/packages/backend/migration/1644010796173-convert-hard-mutes.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1644058404077-chart-v7.js b/packages/backend/migration/1644058404077-chart-v7.js
index a850d5f48f..a09fff1bc7 100644
--- a/packages/backend/migration/1644058404077-chart-v7.js
+++ b/packages/backend/migration/1644058404077-chart-v7.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1644059847460-chart-v8.js b/packages/backend/migration/1644059847460-chart-v8.js
index 2e20159ba9..43b95926b6 100644
--- a/packages/backend/migration/1644059847460-chart-v8.js
+++ b/packages/backend/migration/1644059847460-chart-v8.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1644060125705-chart-v9.js b/packages/backend/migration/1644060125705-chart-v9.js
index d1d9469ea2..dc99f3c8f8 100644
--- a/packages/backend/migration/1644060125705-chart-v9.js
+++ b/packages/backend/migration/1644060125705-chart-v9.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1644073149413-chart-v10.js b/packages/backend/migration/1644073149413-chart-v10.js
index 466ae59837..4d36235729 100644
--- a/packages/backend/migration/1644073149413-chart-v10.js
+++ b/packages/backend/migration/1644073149413-chart-v10.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1644095659741-chart-v11.js b/packages/backend/migration/1644095659741-chart-v11.js
index 5c98e25d86..80bacbf710 100644
--- a/packages/backend/migration/1644095659741-chart-v11.js
+++ b/packages/backend/migration/1644095659741-chart-v11.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1644328606241-chart-v12.js b/packages/backend/migration/1644328606241-chart-v12.js
index 2a7272fd22..15c0dd9040 100644
--- a/packages/backend/migration/1644328606241-chart-v12.js
+++ b/packages/backend/migration/1644328606241-chart-v12.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1644331238153-chart-v13.js b/packages/backend/migration/1644331238153-chart-v13.js
index 7e33b0a8e9..0c2db66f27 100644
--- a/packages/backend/migration/1644331238153-chart-v13.js
+++ b/packages/backend/migration/1644331238153-chart-v13.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1644344266289-chart-v14.js b/packages/backend/migration/1644344266289-chart-v14.js
index 2050d54591..0f4688ab77 100644
--- a/packages/backend/migration/1644344266289-chart-v14.js
+++ b/packages/backend/migration/1644344266289-chart-v14.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1644395759931-instance-theme-color.js b/packages/backend/migration/1644395759931-instance-theme-color.js
index ac842e4fe5..fd7356e68a 100644
--- a/packages/backend/migration/1644395759931-instance-theme-color.js
+++ b/packages/backend/migration/1644395759931-instance-theme-color.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1644481657998-chart-v15.js b/packages/backend/migration/1644481657998-chart-v15.js
index ad5589df8b..964bea3d07 100644
--- a/packages/backend/migration/1644481657998-chart-v15.js
+++ b/packages/backend/migration/1644481657998-chart-v15.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1644551208096-following-indexes.js b/packages/backend/migration/1644551208096-following-indexes.js
index 795b8e900e..8d1d4890dc 100644
--- a/packages/backend/migration/1644551208096-following-indexes.js
+++ b/packages/backend/migration/1644551208096-following-indexes.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1645340161439-remove-max-note-text-length.js b/packages/backend/migration/1645340161439-remove-max-note-text-length.js
index 84eaeddfa4..1cf6b0801b 100644
--- a/packages/backend/migration/1645340161439-remove-max-note-text-length.js
+++ b/packages/backend/migration/1645340161439-remove-max-note-text-length.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1645599900873-federation-chart-pubsub.js b/packages/backend/migration/1645599900873-federation-chart-pubsub.js
index 4f9f501cca..3042c8ecd9 100644
--- a/packages/backend/migration/1645599900873-federation-chart-pubsub.js
+++ b/packages/backend/migration/1645599900873-federation-chart-pubsub.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1646143552768-instance-default-theme.js b/packages/backend/migration/1646143552768-instance-default-theme.js
index 3532916304..8f0755e3a2 100644
--- a/packages/backend/migration/1646143552768-instance-default-theme.js
+++ b/packages/backend/migration/1646143552768-instance-default-theme.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1646387162108-mute-expires-at.js b/packages/backend/migration/1646387162108-mute-expires-at.js
index 868f5c87ef..412db14881 100644
--- a/packages/backend/migration/1646387162108-mute-expires-at.js
+++ b/packages/backend/migration/1646387162108-mute-expires-at.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1646549089451-poll-ended-notification.js b/packages/backend/migration/1646549089451-poll-ended-notification.js
index fa7327ff9c..6c481c6ac6 100644
--- a/packages/backend/migration/1646549089451-poll-ended-notification.js
+++ b/packages/backend/migration/1646549089451-poll-ended-notification.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1646633030285-chart-federation-active.js b/packages/backend/migration/1646633030285-chart-federation-active.js
index b9863746ad..13d54c3180 100644
--- a/packages/backend/migration/1646633030285-chart-federation-active.js
+++ b/packages/backend/migration/1646633030285-chart-federation-active.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1646655454495-remove-instance-drive-columns.js b/packages/backend/migration/1646655454495-remove-instance-drive-columns.js
index 8fd96ed4c6..04d6fce887 100644
--- a/packages/backend/migration/1646655454495-remove-instance-drive-columns.js
+++ b/packages/backend/migration/1646655454495-remove-instance-drive-columns.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1646732390560-chart-federation-active-sub-pub.js b/packages/backend/migration/1646732390560-chart-federation-active-sub-pub.js
index 1b28d012ae..289b929ad9 100644
--- a/packages/backend/migration/1646732390560-chart-federation-active-sub-pub.js
+++ b/packages/backend/migration/1646732390560-chart-federation-active-sub-pub.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1648548247382-webhook.js b/packages/backend/migration/1648548247382-webhook.js
index fc2a691918..f31d3c5bb5 100644
--- a/packages/backend/migration/1648548247382-webhook.js
+++ b/packages/backend/migration/1648548247382-webhook.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1648816172177-webhook-2.js b/packages/backend/migration/1648816172177-webhook-2.js
index a7bccff82d..4d1b293b2c 100644
--- a/packages/backend/migration/1648816172177-webhook-2.js
+++ b/packages/backend/migration/1648816172177-webhook-2.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1651224615271-foreign-key.js b/packages/backend/migration/1651224615271-foreign-key.js
index 12e4646329..fa51bb5e31 100644
--- a/packages/backend/migration/1651224615271-foreign-key.js
+++ b/packages/backend/migration/1651224615271-foreign-key.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1652859567549-uniform-themecolor.js b/packages/backend/migration/1652859567549-uniform-themecolor.js
index 422e63dfec..754e089824 100644
--- a/packages/backend/migration/1652859567549-uniform-themecolor.js
+++ b/packages/backend/migration/1652859567549-uniform-themecolor.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1655368940105-nsfw-detection.js b/packages/backend/migration/1655368940105-nsfw-detection.js
index ad37ff6f83..d2d0d00117 100644
--- a/packages/backend/migration/1655368940105-nsfw-detection.js
+++ b/packages/backend/migration/1655368940105-nsfw-detection.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1655371960534-nsfw-detection-2.js b/packages/backend/migration/1655371960534-nsfw-detection-2.js
index e6cc266178..e5adbddca4 100644
--- a/packages/backend/migration/1655371960534-nsfw-detection-2.js
+++ b/packages/backend/migration/1655371960534-nsfw-detection-2.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1655388169582-nsfw-detection-3.js b/packages/backend/migration/1655388169582-nsfw-detection-3.js
index 40362cc20c..12fc281327 100644
--- a/packages/backend/migration/1655388169582-nsfw-detection-3.js
+++ b/packages/backend/migration/1655388169582-nsfw-detection-3.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1655393015659-nsfw-detection-4.js b/packages/backend/migration/1655393015659-nsfw-detection-4.js
index d74fe9c929..39fb175679 100644
--- a/packages/backend/migration/1655393015659-nsfw-detection-4.js
+++ b/packages/backend/migration/1655393015659-nsfw-detection-4.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1655813815729-driveCapacityOverrideMb.js b/packages/backend/migration/1655813815729-driveCapacityOverrideMb.js
index 7e97f9dc74..e64c8c1b82 100644
--- a/packages/backend/migration/1655813815729-driveCapacityOverrideMb.js
+++ b/packages/backend/migration/1655813815729-driveCapacityOverrideMb.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1655918165614-user-ip.js b/packages/backend/migration/1655918165614-user-ip.js
index ccb3ceb49d..668c6d909b 100644
--- a/packages/backend/migration/1655918165614-user-ip.js
+++ b/packages/backend/migration/1655918165614-user-ip.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1656122560740-file-ip.js b/packages/backend/migration/1656122560740-file-ip.js
index dc02df0e68..e5efaf3d9f 100644
--- a/packages/backend/migration/1656122560740-file-ip.js
+++ b/packages/backend/migration/1656122560740-file-ip.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1656251734807-nsfw-detection-5.js b/packages/backend/migration/1656251734807-nsfw-detection-5.js
index 06da9251b1..9b36bd76eb 100644
--- a/packages/backend/migration/1656251734807-nsfw-detection-5.js
+++ b/packages/backend/migration/1656251734807-nsfw-detection-5.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1656328812281-ip-2.js b/packages/backend/migration/1656328812281-ip-2.js
index 1b53e697de..39fcd1d83d 100644
--- a/packages/backend/migration/1656328812281-ip-2.js
+++ b/packages/backend/migration/1656328812281-ip-2.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1656408772602-nsfw-detection-6.js b/packages/backend/migration/1656408772602-nsfw-detection-6.js
index 0adc8bb793..efadd22e5d 100644
--- a/packages/backend/migration/1656408772602-nsfw-detection-6.js
+++ b/packages/backend/migration/1656408772602-nsfw-detection-6.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1656772790599-user-moderation-note.js b/packages/backend/migration/1656772790599-user-moderation-note.js
index 63a993851f..ef2f0f6522 100644
--- a/packages/backend/migration/1656772790599-user-moderation-note.js
+++ b/packages/backend/migration/1656772790599-user-moderation-note.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1657346559800-active-email-validation.js b/packages/backend/migration/1657346559800-active-email-validation.js
index 44b1f3f4fa..e8d5b29cdf 100644
--- a/packages/backend/migration/1657346559800-active-email-validation.js
+++ b/packages/backend/migration/1657346559800-active-email-validation.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1664694635394-turnstile.js b/packages/backend/migration/1664694635394-turnstile.js
index 3ec6da9136..a9baf4c657 100644
--- a/packages/backend/migration/1664694635394-turnstile.js
+++ b/packages/backend/migration/1664694635394-turnstile.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1665091090561-add-renote-muting.js b/packages/backend/migration/1665091090561-add-renote-muting.js
index a22d7037f3..5748572517 100644
--- a/packages/backend/migration/1665091090561-add-renote-muting.js
+++ b/packages/backend/migration/1665091090561-add-renote-muting.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1669138716634-whetherPushNotifyToSendReadMessage.js b/packages/backend/migration/1669138716634-whetherPushNotifyToSendReadMessage.js
index a317468ac9..431241897d 100644
--- a/packages/backend/migration/1669138716634-whetherPushNotifyToSendReadMessage.js
+++ b/packages/backend/migration/1669138716634-whetherPushNotifyToSendReadMessage.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1671924750884-RetentionAggregation.js b/packages/backend/migration/1671924750884-RetentionAggregation.js
index 5057bf1060..67079bb7a1 100644
--- a/packages/backend/migration/1671924750884-RetentionAggregation.js
+++ b/packages/backend/migration/1671924750884-RetentionAggregation.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1671926422832-RetentionAggregation2.js b/packages/backend/migration/1671926422832-RetentionAggregation2.js
index 665e24d721..f26e0f7d2e 100644
--- a/packages/backend/migration/1671926422832-RetentionAggregation2.js
+++ b/packages/backend/migration/1671926422832-RetentionAggregation2.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1672562400597-PerUserPvChart.js b/packages/backend/migration/1672562400597-PerUserPvChart.js
index 1fbe1eb14a..844f665a8b 100644
--- a/packages/backend/migration/1672562400597-PerUserPvChart.js
+++ b/packages/backend/migration/1672562400597-PerUserPvChart.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1672703171386-remove-latestRequestSentAt.js b/packages/backend/migration/1672703171386-remove-latestRequestSentAt.js
index f053e5c20c..fa73fc8977 100644
--- a/packages/backend/migration/1672703171386-remove-latestRequestSentAt.js
+++ b/packages/backend/migration/1672703171386-remove-latestRequestSentAt.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1672704017999-remove-lastCommunicatedAt.js b/packages/backend/migration/1672704017999-remove-lastCommunicatedAt.js
index b71f7e1306..abf209162b 100644
--- a/packages/backend/migration/1672704017999-remove-lastCommunicatedAt.js
+++ b/packages/backend/migration/1672704017999-remove-lastCommunicatedAt.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1672704136584-remove-latestStatus.js b/packages/backend/migration/1672704136584-remove-latestStatus.js
index f08ed96a45..d75344c053 100644
--- a/packages/backend/migration/1672704136584-remove-latestStatus.js
+++ b/packages/backend/migration/1672704136584-remove-latestStatus.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1672822262496-Flash.js b/packages/backend/migration/1672822262496-Flash.js
index e45055b3cc..fd3f77d893 100644
--- a/packages/backend/migration/1672822262496-Flash.js
+++ b/packages/backend/migration/1672822262496-Flash.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1673336077243-PollChoiceLength.js b/packages/backend/migration/1673336077243-PollChoiceLength.js
index 8c4a5007e4..7bd65149d6 100644
--- a/packages/backend/migration/1673336077243-PollChoiceLength.js
+++ b/packages/backend/migration/1673336077243-PollChoiceLength.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1673500412259-Role.js b/packages/backend/migration/1673500412259-Role.js
index 2bf6a7f4e8..6bfb31e08e 100644
--- a/packages/backend/migration/1673500412259-Role.js
+++ b/packages/backend/migration/1673500412259-Role.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1673515526953-RoleColor.js b/packages/backend/migration/1673515526953-RoleColor.js
index 693dcfb0b6..b856e4183b 100644
--- a/packages/backend/migration/1673515526953-RoleColor.js
+++ b/packages/backend/migration/1673515526953-RoleColor.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1673522856499-RoleIroiro.js b/packages/backend/migration/1673522856499-RoleIroiro.js
index 10a6eef162..40635e50d8 100644
--- a/packages/backend/migration/1673522856499-RoleIroiro.js
+++ b/packages/backend/migration/1673522856499-RoleIroiro.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1673524604156-RoleLastUsedAt.js b/packages/backend/migration/1673524604156-RoleLastUsedAt.js
index 5bbd0c39ac..3bbb8000d8 100644
--- a/packages/backend/migration/1673524604156-RoleLastUsedAt.js
+++ b/packages/backend/migration/1673524604156-RoleLastUsedAt.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1673570377815-RoleConditional.js b/packages/backend/migration/1673570377815-RoleConditional.js
index d2b25d121e..354fd6c66a 100644
--- a/packages/backend/migration/1673570377815-RoleConditional.js
+++ b/packages/backend/migration/1673570377815-RoleConditional.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1673575973645-MetaClean.js b/packages/backend/migration/1673575973645-MetaClean.js
index 7671785d94..684d62e8e9 100644
--- a/packages/backend/migration/1673575973645-MetaClean.js
+++ b/packages/backend/migration/1673575973645-MetaClean.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1673783015567-Policies.js b/packages/backend/migration/1673783015567-Policies.js
index 4f76752c9f..8674306620 100644
--- a/packages/backend/migration/1673783015567-Policies.js
+++ b/packages/backend/migration/1673783015567-Policies.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1673812883772-firstRetrievedAt.js b/packages/backend/migration/1673812883772-firstRetrievedAt.js
index 82990e30b6..4111cc4ad0 100644
--- a/packages/backend/migration/1673812883772-firstRetrievedAt.js
+++ b/packages/backend/migration/1673812883772-firstRetrievedAt.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1674086433654-flashScriptLength.js b/packages/backend/migration/1674086433654-flashScriptLength.js
index 996fe8c691..cdfb812ba0 100644
--- a/packages/backend/migration/1674086433654-flashScriptLength.js
+++ b/packages/backend/migration/1674086433654-flashScriptLength.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1674118260469-achievement.js b/packages/backend/migration/1674118260469-achievement.js
index 5d79dc669e..072cf81ec3 100644
--- a/packages/backend/migration/1674118260469-achievement.js
+++ b/packages/backend/migration/1674118260469-achievement.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1674255666603-loggedInDates.js b/packages/backend/migration/1674255666603-loggedInDates.js
index a6cf4b400f..a2a217da95 100644
--- a/packages/backend/migration/1674255666603-loggedInDates.js
+++ b/packages/backend/migration/1674255666603-loggedInDates.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1675053125067-fixforeignkeyreports.js b/packages/backend/migration/1675053125067-fixforeignkeyreports.js
index d24dc5ec5a..2ca383f563 100644
--- a/packages/backend/migration/1675053125067-fixforeignkeyreports.js
+++ b/packages/backend/migration/1675053125067-fixforeignkeyreports.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1675404035646-cleanup.js b/packages/backend/migration/1675404035646-cleanup.js
index c4e4332bbc..5cd5f5534a 100644
--- a/packages/backend/migration/1675404035646-cleanup.js
+++ b/packages/backend/migration/1675404035646-cleanup.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1675557528704-role-icon-badge.js b/packages/backend/migration/1675557528704-role-icon-badge.js
index ee39c07a51..48684075d1 100644
--- a/packages/backend/migration/1675557528704-role-icon-badge.js
+++ b/packages/backend/migration/1675557528704-role-icon-badge.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1676434944993-drop-group.js b/packages/backend/migration/1676434944993-drop-group.js
index 1db2d5818f..2df8a2d789 100644
--- a/packages/backend/migration/1676434944993-drop-group.js
+++ b/packages/backend/migration/1676434944993-drop-group.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1676438468213-ad3.js b/packages/backend/migration/1676438468213-ad3.js
index 8347f56b95..83ca5828e3 100644
--- a/packages/backend/migration/1676438468213-ad3.js
+++ b/packages/backend/migration/1676438468213-ad3.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1677054292210-ad4.js b/packages/backend/migration/1677054292210-ad4.js
index 037e21059c..11c42dd354 100644
--- a/packages/backend/migration/1677054292210-ad4.js
+++ b/packages/backend/migration/1677054292210-ad4.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1677570181236-role-assignment-expires-at.js b/packages/backend/migration/1677570181236-role-assignment-expires-at.js
index e44bca1d20..6fe32ffeb0 100644
--- a/packages/backend/migration/1677570181236-role-assignment-expires-at.js
+++ b/packages/backend/migration/1677570181236-role-assignment-expires-at.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1678164627293-per-note-reaction-acceptance.js b/packages/backend/migration/1678164627293-per-note-reaction-acceptance.js
index c85aafbd4c..44c807499c 100644
--- a/packages/backend/migration/1678164627293-per-note-reaction-acceptance.js
+++ b/packages/backend/migration/1678164627293-per-note-reaction-acceptance.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1678426061773-tweak-varchar-length.js b/packages/backend/migration/1678426061773-tweak-varchar-length.js
index 2541f99a19..74c4fd6715 100644
--- a/packages/backend/migration/1678426061773-tweak-varchar-length.js
+++ b/packages/backend/migration/1678426061773-tweak-varchar-length.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1678427401214-remove-unused.js b/packages/backend/migration/1678427401214-remove-unused.js
index e2947034ea..da9c252b19 100644
--- a/packages/backend/migration/1678427401214-remove-unused.js
+++ b/packages/backend/migration/1678427401214-remove-unused.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1678602320354-role-display-order.js b/packages/backend/migration/1678602320354-role-display-order.js
index 0ab7b0c3e2..d3cc9792ca 100644
--- a/packages/backend/migration/1678602320354-role-display-order.js
+++ b/packages/backend/migration/1678602320354-role-display-order.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1678694614599-sensitive-words.js b/packages/backend/migration/1678694614599-sensitive-words.js
index 5f69424eca..13361f597e 100644
--- a/packages/backend/migration/1678694614599-sensitive-words.js
+++ b/packages/backend/migration/1678694614599-sensitive-words.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1678869617549-retention-date-key.js b/packages/backend/migration/1678869617549-retention-date-key.js
index 55bf6248e6..1b995385b0 100644
--- a/packages/backend/migration/1678869617549-retention-date-key.js
+++ b/packages/backend/migration/1678869617549-retention-date-key.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1678945242650-add-props-for-custom-emoji.js b/packages/backend/migration/1678945242650-add-props-for-custom-emoji.js
index 0054e78f88..5d1218be12 100644
--- a/packages/backend/migration/1678945242650-add-props-for-custom-emoji.js
+++ b/packages/backend/migration/1678945242650-add-props-for-custom-emoji.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1678953978856-clip-favorite.js b/packages/backend/migration/1678953978856-clip-favorite.js
index 13145497bb..9d706c4dae 100644
--- a/packages/backend/migration/1678953978856-clip-favorite.js
+++ b/packages/backend/migration/1678953978856-clip-favorite.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1679309757174-antenna-active.js b/packages/backend/migration/1679309757174-antenna-active.js
index 0b2bcc69ff..dadea25a7c 100644
--- a/packages/backend/migration/1679309757174-antenna-active.js
+++ b/packages/backend/migration/1679309757174-antenna-active.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1679639483253-enableChartsForRemoteUser.js b/packages/backend/migration/1679639483253-enableChartsForRemoteUser.js
index 68576064f2..f2a13100e2 100644
--- a/packages/backend/migration/1679639483253-enableChartsForRemoteUser.js
+++ b/packages/backend/migration/1679639483253-enableChartsForRemoteUser.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1679651580149-cleanup.js b/packages/backend/migration/1679651580149-cleanup.js
index 7049891cf0..efee339c46 100644
--- a/packages/backend/migration/1679651580149-cleanup.js
+++ b/packages/backend/migration/1679651580149-cleanup.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1679652081809-enableChartsForFederatedInstances.js b/packages/backend/migration/1679652081809-enableChartsForFederatedInstances.js
index f3a07cbd1d..67be10e6fd 100644
--- a/packages/backend/migration/1679652081809-enableChartsForFederatedInstances.js
+++ b/packages/backend/migration/1679652081809-enableChartsForFederatedInstances.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1680228513388-channelFavorite.js b/packages/backend/migration/1680228513388-channelFavorite.js
index 58eb7359f2..866173305e 100644
--- a/packages/backend/migration/1680228513388-channelFavorite.js
+++ b/packages/backend/migration/1680228513388-channelFavorite.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1680238118084-channelNotePining.js b/packages/backend/migration/1680238118084-channelNotePining.js
index f1f192d7bb..78bafc0237 100644
--- a/packages/backend/migration/1680238118084-channelNotePining.js
+++ b/packages/backend/migration/1680238118084-channelNotePining.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1680491187535-cleanup.js b/packages/backend/migration/1680491187535-cleanup.js
index 006b403bd1..f0b1bccdab 100644
--- a/packages/backend/migration/1680491187535-cleanup.js
+++ b/packages/backend/migration/1680491187535-cleanup.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1680582195041-cleanup.js b/packages/backend/migration/1680582195041-cleanup.js
index 7d941be8cf..83d04b6186 100644
--- a/packages/backend/migration/1680582195041-cleanup.js
+++ b/packages/backend/migration/1680582195041-cleanup.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1680702787050-UserMemo.js b/packages/backend/migration/1680702787050-UserMemo.js
index 104d66ce24..3f7afe8657 100644
--- a/packages/backend/migration/1680702787050-UserMemo.js
+++ b/packages/backend/migration/1680702787050-UserMemo.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1680775031481-avatar-url-and-banner-url.js b/packages/backend/migration/1680775031481-avatar-url-and-banner-url.js
index c613ee511e..49295e70eb 100644
--- a/packages/backend/migration/1680775031481-avatar-url-and-banner-url.js
+++ b/packages/backend/migration/1680775031481-avatar-url-and-banner-url.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1680931179228-account-move.js b/packages/backend/migration/1680931179228-account-move.js
index 203d838f57..a8b5e4df68 100644
--- a/packages/backend/migration/1680931179228-account-move.js
+++ b/packages/backend/migration/1680931179228-account-move.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1681400427971-serverRules.js b/packages/backend/migration/1681400427971-serverRules.js
index 70a74ebfff..176783b50a 100644
--- a/packages/backend/migration/1681400427971-serverRules.js
+++ b/packages/backend/migration/1681400427971-serverRules.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1681870960239-RoleTLSetting.js b/packages/backend/migration/1681870960239-RoleTLSetting.js
index 07b9bc4e35..2999051a3b 100644
--- a/packages/backend/migration/1681870960239-RoleTLSetting.js
+++ b/packages/backend/migration/1681870960239-RoleTLSetting.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1682190963894-movedAt.js b/packages/backend/migration/1682190963894-movedAt.js
index cc33da8747..852cf58969 100644
--- a/packages/backend/migration/1682190963894-movedAt.js
+++ b/packages/backend/migration/1682190963894-movedAt.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1682754135458-preservedUsernames.js b/packages/backend/migration/1682754135458-preservedUsernames.js
index 61723e4abd..8aae3c2054 100644
--- a/packages/backend/migration/1682754135458-preservedUsernames.js
+++ b/packages/backend/migration/1682754135458-preservedUsernames.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1682985520254-channelColor.js b/packages/backend/migration/1682985520254-channelColor.js
index 43f1f48334..3c7f3101a5 100644
--- a/packages/backend/migration/1682985520254-channelColor.js
+++ b/packages/backend/migration/1682985520254-channelColor.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1683328299359-channelArchive.js b/packages/backend/migration/1683328299359-channelArchive.js
index 759dcbfdae..10a87246de 100644
--- a/packages/backend/migration/1683328299359-channelArchive.js
+++ b/packages/backend/migration/1683328299359-channelArchive.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1683682889948-prevent-ai-larning.js b/packages/backend/migration/1683682889948-prevent-ai-larning.js
index 1dc3eec21f..167c9f71d2 100644
--- a/packages/backend/migration/1683682889948-prevent-ai-larning.js
+++ b/packages/backend/migration/1683682889948-prevent-ai-larning.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1683683083083-public-reactions-default-true.js b/packages/backend/migration/1683683083083-public-reactions-default-true.js
index 32cbe33b2f..f416e5ffa7 100644
--- a/packages/backend/migration/1683683083083-public-reactions-default-true.js
+++ b/packages/backend/migration/1683683083083-public-reactions-default-true.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1683789676867-fix-typo.js b/packages/backend/migration/1683789676867-fix-typo.js
index 5cd686e2f1..d647d20e62 100644
--- a/packages/backend/migration/1683789676867-fix-typo.js
+++ b/packages/backend/migration/1683789676867-fix-typo.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1683847157541-UserList.js b/packages/backend/migration/1683847157541-UserList.js
index f9e79a43a1..14a52d64f8 100644
--- a/packages/backend/migration/1683847157541-UserList.js
+++ b/packages/backend/migration/1683847157541-UserList.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1683869758873-UserListFavorites.js b/packages/backend/migration/1683869758873-UserListFavorites.js
index aef4597a75..aae4056845 100644
--- a/packages/backend/migration/1683869758873-UserListFavorites.js
+++ b/packages/backend/migration/1683869758873-UserListFavorites.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1684206886988-remove-showTimelineReplies.js b/packages/backend/migration/1684206886988-remove-showTimelineReplies.js
index a0798f85c6..398f9f0803 100644
--- a/packages/backend/migration/1684206886988-remove-showTimelineReplies.js
+++ b/packages/backend/migration/1684206886988-remove-showTimelineReplies.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1684386446061-emoji-improve.js b/packages/backend/migration/1684386446061-emoji-improve.js
index 7bded84cc9..e7e94769b8 100644
--- a/packages/backend/migration/1684386446061-emoji-improve.js
+++ b/packages/backend/migration/1684386446061-emoji-improve.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1685973839966-errorImageUrl.js b/packages/backend/migration/1685973839966-errorImageUrl.js
index c4a1567b9b..ca685ef088 100644
--- a/packages/backend/migration/1685973839966-errorImageUrl.js
+++ b/packages/backend/migration/1685973839966-errorImageUrl.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1688280713783-add-meta-options.js b/packages/backend/migration/1688280713783-add-meta-options.js
index ade8378c00..77d1934925 100644
--- a/packages/backend/migration/1688280713783-add-meta-options.js
+++ b/packages/backend/migration/1688280713783-add-meta-options.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1688720440658-refactor-invite-system.js b/packages/backend/migration/1688720440658-refactor-invite-system.js
index 20f178612d..ea192a1950 100644
--- a/packages/backend/migration/1688720440658-refactor-invite-system.js
+++ b/packages/backend/migration/1688720440658-refactor-invite-system.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1688880985544-add-index-to-relations.js b/packages/backend/migration/1688880985544-add-index-to-relations.js
index 6daac20329..c18903641c 100644
--- a/packages/backend/migration/1688880985544-add-index-to-relations.js
+++ b/packages/backend/migration/1688880985544-add-index-to-relations.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1689102832143-nsfw-cache.js b/packages/backend/migration/1689102832143-nsfw-cache.js
index 419588296e..90d453418b 100644
--- a/packages/backend/migration/1689102832143-nsfw-cache.js
+++ b/packages/backend/migration/1689102832143-nsfw-cache.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1690782653311-SensitiveChannel.js b/packages/backend/migration/1690782653311-SensitiveChannel.js
index e76dda5180..afec1a2153 100644
--- a/packages/backend/migration/1690782653311-SensitiveChannel.js
+++ b/packages/backend/migration/1690782653311-SensitiveChannel.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1690796169261-play-visibility.js b/packages/backend/migration/1690796169261-play-visibility.js
index c57fa7a109..5e5843bfee 100644
--- a/packages/backend/migration/1690796169261-play-visibility.js
+++ b/packages/backend/migration/1690796169261-play-visibility.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1691959191872-passkey-support.js b/packages/backend/migration/1691959191872-passkey-support.js
index 55b571d60d..1da9bdb363 100644
--- a/packages/backend/migration/1691959191872-passkey-support.js
+++ b/packages/backend/migration/1691959191872-passkey-support.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1694850832075-server-icons-and-manifest.js b/packages/backend/migration/1694850832075-server-icons-and-manifest.js
index 1bd8979d9b..235bf05744 100644
--- a/packages/backend/migration/1694850832075-server-icons-and-manifest.js
+++ b/packages/backend/migration/1694850832075-server-icons-and-manifest.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1694915420864-clipped-count.js b/packages/backend/migration/1694915420864-clipped-count.js
index 1ad8e04ce0..6d70aaecf1 100644
--- a/packages/backend/migration/1694915420864-clipped-count.js
+++ b/packages/backend/migration/1694915420864-clipped-count.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1695944637565-notificationRecieveConfig.js b/packages/backend/migration/1695944637565-notificationRecieveConfig.js
index 42d3dce5d6..04a40993c0 100644
--- a/packages/backend/migration/1695944637565-notificationRecieveConfig.js
+++ b/packages/backend/migration/1695944637565-notificationRecieveConfig.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1696003580220-AddSomeUrls.js b/packages/backend/migration/1696003580220-AddSomeUrls.js
index 683aa5eeed..213e39e7af 100644
--- a/packages/backend/migration/1696003580220-AddSomeUrls.js
+++ b/packages/backend/migration/1696003580220-AddSomeUrls.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1696222183852-withReplies.js b/packages/backend/migration/1696222183852-withReplies.js
index 9f65d5f6a1..84a5511d17 100644
--- a/packages/backend/migration/1696222183852-withReplies.js
+++ b/packages/backend/migration/1696222183852-withReplies.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1696373953614-meta-cache-settings.js b/packages/backend/migration/1696373953614-meta-cache-settings.js
index 8e24a1c5c6..cef09b3eb7 100644
--- a/packages/backend/migration/1696373953614-meta-cache-settings.js
+++ b/packages/backend/migration/1696373953614-meta-cache-settings.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1696405744672-clean-up.js b/packages/backend/migration/1696405744672-clean-up.js
index 5ec89b08f4..4e1ee6cd61 100644
--- a/packages/backend/migration/1696405744672-clean-up.js
+++ b/packages/backend/migration/1696405744672-clean-up.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1696569742153-clean-up.js b/packages/backend/migration/1696569742153-clean-up.js
index de48fab5aa..b7c981bab2 100644
--- a/packages/backend/migration/1696569742153-clean-up.js
+++ b/packages/backend/migration/1696569742153-clean-up.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1696581429196-clean-up.js b/packages/backend/migration/1696581429196-clean-up.js
index da69b4e9de..b6723f3430 100644
--- a/packages/backend/migration/1696581429196-clean-up.js
+++ b/packages/backend/migration/1696581429196-clean-up.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1696743032098-AdsOnStream.js b/packages/backend/migration/1696743032098-AdsOnStream.js
index c86ee84883..43b9f83e66 100644
--- a/packages/backend/migration/1696743032098-AdsOnStream.js
+++ b/packages/backend/migration/1696743032098-AdsOnStream.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1696807733453-userListUserId.js b/packages/backend/migration/1696807733453-userListUserId.js
index ab2ba07fb5..8f0ae2cd87 100644
--- a/packages/backend/migration/1696807733453-userListUserId.js
+++ b/packages/backend/migration/1696807733453-userListUserId.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1696808725134-userListUserId-2.js b/packages/backend/migration/1696808725134-userListUserId-2.js
index 5bcb5aedc2..cc504e761c 100644
--- a/packages/backend/migration/1696808725134-userListUserId-2.js
+++ b/packages/backend/migration/1696808725134-userListUserId-2.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1697247230117-InstanceSilence.js b/packages/backend/migration/1697247230117-InstanceSilence.js
index 5fdbca3b27..309d817087 100644
--- a/packages/backend/migration/1697247230117-InstanceSilence.js
+++ b/packages/backend/migration/1697247230117-InstanceSilence.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1697420555911-deleteCreatedAt.js b/packages/backend/migration/1697420555911-deleteCreatedAt.js
index 958d61a348..407a5f449a 100644
--- a/packages/backend/migration/1697420555911-deleteCreatedAt.js
+++ b/packages/backend/migration/1697420555911-deleteCreatedAt.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1697436246389-antenna-localOnly.js b/packages/backend/migration/1697436246389-antenna-localOnly.js
index 0228673291..d7c0ca6510 100644
--- a/packages/backend/migration/1697436246389-antenna-localOnly.js
+++ b/packages/backend/migration/1697436246389-antenna-localOnly.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1697441463087-FollowRequestWithReplies.js b/packages/backend/migration/1697441463087-FollowRequestWithReplies.js
index 214c6f6680..58b61aff63 100644
--- a/packages/backend/migration/1697441463087-FollowRequestWithReplies.js
+++ b/packages/backend/migration/1697441463087-FollowRequestWithReplies.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1697673894459-note-reactionAndUserPairCache.js b/packages/backend/migration/1697673894459-note-reactionAndUserPairCache.js
index fe0ea282d2..fab07fd3f4 100644
--- a/packages/backend/migration/1697673894459-note-reactionAndUserPairCache.js
+++ b/packages/backend/migration/1697673894459-note-reactionAndUserPairCache.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1697847397844-avatar-decoration.js b/packages/backend/migration/1697847397844-avatar-decoration.js
index 1f22139746..32ee47e968 100644
--- a/packages/backend/migration/1697847397844-avatar-decoration.js
+++ b/packages/backend/migration/1697847397844-avatar-decoration.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1697941908548-avatar-decoration2.js b/packages/backend/migration/1697941908548-avatar-decoration2.js
index 9d15c1c3d0..58344e2bb6 100644
--- a/packages/backend/migration/1697941908548-avatar-decoration2.js
+++ b/packages/backend/migration/1697941908548-avatar-decoration2.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1698041201306-enable-ftt.js b/packages/backend/migration/1698041201306-enable-ftt.js
index 6769ed53b5..c67dda6f5f 100644
--- a/packages/backend/migration/1698041201306-enable-ftt.js
+++ b/packages/backend/migration/1698041201306-enable-ftt.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1698840138000-add-allow-renote-to-external.js b/packages/backend/migration/1698840138000-add-allow-renote-to-external.js
index 0edf298841..8ce35b0f69 100644
--- a/packages/backend/migration/1698840138000-add-allow-renote-to-external.js
+++ b/packages/backend/migration/1698840138000-add-allow-renote-to-external.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1699141698112-announcement-silence.js b/packages/backend/migration/1699141698112-announcement-silence.js
index eef9b076fc..f462d30b51 100644
--- a/packages/backend/migration/1699141698112-announcement-silence.js
+++ b/packages/backend/migration/1699141698112-announcement-silence.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1700096812223-enableFanoutTimelineDbFallback.js b/packages/backend/migration/1700096812223-enableFanoutTimelineDbFallback.js
index 94fa588985..2ab93624ce 100644
--- a/packages/backend/migration/1700096812223-enableFanoutTimelineDbFallback.js
+++ b/packages/backend/migration/1700096812223-enableFanoutTimelineDbFallback.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1700303245007-supportVerifyMailApi.js b/packages/backend/migration/1700303245007-supportVerifyMailApi.js
index 3ac59ec37a..58ff7a69c4 100644
--- a/packages/backend/migration/1700303245007-supportVerifyMailApi.js
+++ b/packages/backend/migration/1700303245007-supportVerifyMailApi.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1700902349231-add-bday-index.js b/packages/backend/migration/1700902349231-add-bday-index.js
index 251526fc26..c58165c70e 100644
--- a/packages/backend/migration/1700902349231-add-bday-index.js
+++ b/packages/backend/migration/1700902349231-add-bday-index.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1702718871541-ffVisibility.js b/packages/backend/migration/1702718871541-ffVisibility.js
index e9e820c897..164af00f25 100644
--- a/packages/backend/migration/1702718871541-ffVisibility.js
+++ b/packages/backend/migration/1702718871541-ffVisibility.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1703209889304-bannedEmailDomains.js b/packages/backend/migration/1703209889304-bannedEmailDomains.js
index 5dc99c138f..2fdd4e1183 100644
--- a/packages/backend/migration/1703209889304-bannedEmailDomains.js
+++ b/packages/backend/migration/1703209889304-bannedEmailDomains.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/migration/1703658526000-supportTrueMailApi.js b/packages/backend/migration/1703658526000-supportTrueMailApi.js
new file mode 100644
index 0000000000..fb62653e40
--- /dev/null
+++ b/packages/backend/migration/1703658526000-supportTrueMailApi.js
@@ -0,0 +1,20 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class SupportTrueMailApi1703658526000 {
+    name = 'SupportTrueMailApi1703658526000'
+
+    async up(queryRunner) {
+    	  await queryRunner.query(`ALTER TABLE "meta" ADD "truemailInstance" character varying(1024)`);
+        await queryRunner.query(`ALTER TABLE "meta" ADD "truemailAuthKey" character varying(1024)`);
+        await queryRunner.query(`ALTER TABLE "meta" ADD "enableTruemailApi" boolean NOT NULL DEFAULT false`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableTruemailApi"`);
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "truemailInstance"`);
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "truemailAuthKey"`);
+    }
+}
diff --git a/packages/backend/migration/1704373210054-support-mcaptcha.js b/packages/backend/migration/1704373210054-support-mcaptcha.js
new file mode 100644
index 0000000000..50b4801e14
--- /dev/null
+++ b/packages/backend/migration/1704373210054-support-mcaptcha.js
@@ -0,0 +1,22 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class SupportMcaptcha1704373210054 {
+    name = 'SupportMcaptcha1704373210054'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" ADD "enableMcaptcha" boolean NOT NULL DEFAULT false`);
+        await queryRunner.query(`ALTER TABLE "meta" ADD "mcaptchaSitekey" character varying(1024)`);
+        await queryRunner.query(`ALTER TABLE "meta" ADD "mcaptchaSecretKey" character varying(1024)`);
+        await queryRunner.query(`ALTER TABLE "meta" ADD "mcaptchaInstanceUrl" character varying(1024)`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "mcaptchaInstanceUrl"`);
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "mcaptchaSecretKey"`);
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "mcaptchaSitekey"`);
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableMcaptcha"`);
+    }
+}
diff --git a/packages/backend/migration/1704744370000-add-donation-url.js b/packages/backend/migration/1704744370000-add-donation-url.js
new file mode 100644
index 0000000000..c953b13cc9
--- /dev/null
+++ b/packages/backend/migration/1704744370000-add-donation-url.js
@@ -0,0 +1,10 @@
+export class AddDonationUrl1704744370000 {
+    name = 'AddDonationUrl1704744370000'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" ADD "donationUrl" character varying(1024)`);
+    }
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "donationUrl"`);
+    }
+}
diff --git a/packages/backend/migration/1704959805077-bubble-game-record.js b/packages/backend/migration/1704959805077-bubble-game-record.js
new file mode 100644
index 0000000000..6c4d7ab1a9
--- /dev/null
+++ b/packages/backend/migration/1704959805077-bubble-game-record.js
@@ -0,0 +1,24 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class BubbleGameRecord1704959805077 {
+    name = 'BubbleGameRecord1704959805077'
+
+    async up(queryRunner) {
+        await queryRunner.query(`CREATE TABLE "bubble_game_record" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "seededAt" TIMESTAMP WITH TIME ZONE NOT NULL, "seed" character varying(1024) NOT NULL, "gameVersion" integer NOT NULL, "gameMode" character varying(128) NOT NULL, "score" integer NOT NULL, "logs" jsonb NOT NULL DEFAULT '[]', "isVerified" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_a75395fe404b392e2893b50d7ea" PRIMARY KEY ("id"))`);
+        await queryRunner.query(`CREATE INDEX "IDX_75276757070d21fdfaf4c05290" ON "bubble_game_record" ("userId") `);
+        await queryRunner.query(`CREATE INDEX "IDX_4ae7053179014915d1432d3f40" ON "bubble_game_record" ("seededAt") `);
+        await queryRunner.query(`CREATE INDEX "IDX_26d4ee490b5a487142d35466ee" ON "bubble_game_record" ("score") `);
+        await queryRunner.query(`ALTER TABLE "bubble_game_record" ADD CONSTRAINT "FK_75276757070d21fdfaf4c052909" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "bubble_game_record" DROP CONSTRAINT "FK_75276757070d21fdfaf4c052909"`);
+        await queryRunner.query(`DROP INDEX "public"."IDX_26d4ee490b5a487142d35466ee"`);
+        await queryRunner.query(`DROP INDEX "public"."IDX_4ae7053179014915d1432d3f40"`);
+        await queryRunner.query(`DROP INDEX "public"."IDX_75276757070d21fdfaf4c05290"`);
+        await queryRunner.query(`DROP TABLE "bubble_game_record"`);
+    }
+}
diff --git a/packages/backend/migration/1705222772858-optimize-note-index-for-array-column.js b/packages/backend/migration/1705222772858-optimize-note-index-for-array-column.js
new file mode 100644
index 0000000000..fe0a5a2bcf
--- /dev/null
+++ b/packages/backend/migration/1705222772858-optimize-note-index-for-array-column.js
@@ -0,0 +1,24 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class OptimizeNoteIndexForArrayColumns1705222772858 {
+    name = 'OptimizeNoteIndexForArrayColumns1705222772858'
+
+    async up(queryRunner) {
+        await queryRunner.query(`DROP INDEX "public"."IDX_796a8c03959361f97dc2be1d5c"`);
+        await queryRunner.query(`DROP INDEX "public"."IDX_54ebcb6d27222913b908d56fd8"`);
+        await queryRunner.query(`DROP INDEX "public"."IDX_88937d94d7443d9a99a76fa5c0"`);
+        await queryRunner.query(`DROP INDEX "public"."IDX_51c063b6a133a9cb87145450f5"`);
+        await queryRunner.query(`CREATE INDEX "IDX_NOTE_FILE_IDS" ON "note" using gin ("fileIds")`)
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`DROP INDEX "IDX_NOTE_FILE_IDS"`)
+        await queryRunner.query(`CREATE INDEX "IDX_51c063b6a133a9cb87145450f5" ON "note" ("fileIds") `);
+        await queryRunner.query(`CREATE INDEX "IDX_88937d94d7443d9a99a76fa5c0" ON "note" ("tags") `);
+        await queryRunner.query(`CREATE INDEX "IDX_54ebcb6d27222913b908d56fd8" ON "note" ("mentions") `);
+        await queryRunner.query(`CREATE INDEX "IDX_796a8c03959361f97dc2be1d5c" ON "note" ("visibleUserIds") `);
+    }
+}
diff --git a/packages/backend/migration/1705475608437-reversi.js b/packages/backend/migration/1705475608437-reversi.js
new file mode 100644
index 0000000000..9921728457
--- /dev/null
+++ b/packages/backend/migration/1705475608437-reversi.js
@@ -0,0 +1,22 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class Reversi1705475608437 {
+    name = 'Reversi1705475608437'
+
+    async up(queryRunner) {
+        await queryRunner.query(`DROP INDEX "public"."IDX_b46ec40746efceac604142be1c"`);
+        await queryRunner.query(`DROP INDEX "public"."IDX_b604d92d6c7aec38627f6eaf16"`);
+        await queryRunner.query(`ALTER TABLE "reversi_game" DROP COLUMN "createdAt"`);
+        await queryRunner.query(`ALTER TABLE "reversi_matching" DROP COLUMN "createdAt"`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "reversi_matching" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`);
+        await queryRunner.query(`ALTER TABLE "reversi_game" ADD "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL`);
+        await queryRunner.query(`CREATE INDEX "IDX_b604d92d6c7aec38627f6eaf16" ON "reversi_matching" ("createdAt") `);
+        await queryRunner.query(`CREATE INDEX "IDX_b46ec40746efceac604142be1c" ON "reversi_game" ("createdAt") `);
+    }
+}
diff --git a/packages/backend/migration/1705654039457-reversi-2.js b/packages/backend/migration/1705654039457-reversi-2.js
new file mode 100644
index 0000000000..6685dca73b
--- /dev/null
+++ b/packages/backend/migration/1705654039457-reversi-2.js
@@ -0,0 +1,18 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class Reversi21705654039457 {
+    name = 'Reversi21705654039457'
+
+    async up(queryRunner) {
+			await queryRunner.query(`ALTER TABLE "reversi_game" RENAME COLUMN "user1Accepted" TO "user1Ready"`);
+			await queryRunner.query(`ALTER TABLE "reversi_game" RENAME COLUMN "user2Accepted" TO "user2Ready"`);
+    }
+
+    async down(queryRunner) {
+			await queryRunner.query(`ALTER TABLE "reversi_game" RENAME COLUMN "user1Ready" TO "user1Accepted"`);
+			await queryRunner.query(`ALTER TABLE "reversi_game" RENAME COLUMN "user2Ready" TO "user2Accepted"`);
+    }
+}
diff --git a/packages/backend/migration/1705793785675-reversi-3.js b/packages/backend/migration/1705793785675-reversi-3.js
new file mode 100644
index 0000000000..94b1e4fac9
--- /dev/null
+++ b/packages/backend/migration/1705793785675-reversi-3.js
@@ -0,0 +1,18 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class Reversi31705793785675 {
+    name = 'Reversi31705793785675'
+
+    async up(queryRunner) {
+			await queryRunner.query(`ALTER TABLE "reversi_game" RENAME COLUMN "surrendered" TO "surrenderedUserId"`);
+        await queryRunner.query(`ALTER TABLE "reversi_game" ADD "timeoutUserId" character varying(32)`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "reversi_game" DROP COLUMN "timeoutUserId"`);
+			await queryRunner.query(`ALTER TABLE "reversi_game" RENAME COLUMN "surrenderedUserId" TO "surrendered"`);
+    }
+}
diff --git a/packages/backend/migration/1705794768153-reversi-4.js b/packages/backend/migration/1705794768153-reversi-4.js
new file mode 100644
index 0000000000..95119cabba
--- /dev/null
+++ b/packages/backend/migration/1705794768153-reversi-4.js
@@ -0,0 +1,18 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class Reversi41705794768153 {
+    name = 'Reversi41705794768153'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "reversi_game" ADD "endedAt" TIMESTAMP WITH TIME ZONE`);
+        await queryRunner.query(`COMMENT ON COLUMN "reversi_game"."endedAt" IS 'The ended date of the ReversiGame.'`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`COMMENT ON COLUMN "reversi_game"."endedAt" IS 'The ended date of the ReversiGame.'`);
+        await queryRunner.query(`ALTER TABLE "reversi_game" DROP COLUMN "endedAt"`);
+    }
+}
diff --git a/packages/backend/migration/1705798904141-reversi-5.js b/packages/backend/migration/1705798904141-reversi-5.js
new file mode 100644
index 0000000000..f1a1a42d46
--- /dev/null
+++ b/packages/backend/migration/1705798904141-reversi-5.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class Reversi51705798904141 {
+    name = 'Reversi51705798904141'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "reversi_game" ADD "timeLimitForEachTurn" smallint NOT NULL DEFAULT '90'`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "reversi_game" DROP COLUMN "timeLimitForEachTurn"`);
+    }
+}
diff --git a/packages/backend/migration/1706081514499-reversi-6.js b/packages/backend/migration/1706081514499-reversi-6.js
new file mode 100644
index 0000000000..0d9e5cbbf2
--- /dev/null
+++ b/packages/backend/migration/1706081514499-reversi-6.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class Reversi61706081514499 {
+    name = 'Reversi61706081514499'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "reversi_game" ADD "noIrregularRules" boolean NOT NULL DEFAULT false`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "reversi_game" DROP COLUMN "noIrregularRules"`);
+    }
+}
diff --git a/packages/backend/migration/1706232992000-deeplx.js b/packages/backend/migration/1706232992000-deeplx.js
new file mode 100644
index 0000000000..5c763dbf8b
--- /dev/null
+++ b/packages/backend/migration/1706232992000-deeplx.js
@@ -0,0 +1,18 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class Deeplx1706232992000 {
+    name = 'Deeplx1706232992000';
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" ADD "deeplFreeMode" boolean NOT NULL DEFAULT false`);
+		await queryRunner.query(`ALTER TABLE "meta" ADD "deeplFreeInstance" character varying(1024)`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "deeplFreeMode"`);
+		await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "deeplFreeInstance"`);
+    }
+}
diff --git a/packages/backend/migration/1706791962000-fix-meta-disableRegistration.js b/packages/backend/migration/1706791962000-fix-meta-disableRegistration.js
new file mode 100644
index 0000000000..1c45f3756d
--- /dev/null
+++ b/packages/backend/migration/1706791962000-fix-meta-disableRegistration.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class FixMetaDisableRegistration1706791962000 {
+    name = 'FixMetaDisableRegistration1706791962000'
+
+    async up(queryRunner) {
+        await queryRunner.query(`alter table meta alter column "disableRegistration" set default true;`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`alter table meta alter column "disableRegistration" set default false;`);
+    }
+}
diff --git a/packages/backend/migration/1707429690000-prohibited-words.js b/packages/backend/migration/1707429690000-prohibited-words.js
new file mode 100644
index 0000000000..44e96cb160
--- /dev/null
+++ b/packages/backend/migration/1707429690000-prohibited-words.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class prohibitedWords1707429690000 {
+    name = 'prohibitedWords1707429690000'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" ADD "prohibitedWords" character varying(1024) array NOT NULL DEFAULT '{}'`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "prohibitedWords"`);
+    }
+}
diff --git a/packages/backend/migration/1707808106310-MakeRepositoryUrlNullable.js b/packages/backend/migration/1707808106310-MakeRepositoryUrlNullable.js
new file mode 100644
index 0000000000..335b14976c
--- /dev/null
+++ b/packages/backend/migration/1707808106310-MakeRepositoryUrlNullable.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class MakeRepositoryUrlNullable1707808106310 {
+    name = 'MakeRepositoryUrlNullable1707808106310'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "repositoryUrl" DROP NOT NULL`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "repositoryUrl" SET NOT NULL`);
+    }
+}
diff --git a/packages/backend/migration/1708266695091-repositoryUrl-from-syuilo-to-misskey-dev.js b/packages/backend/migration/1708266695091-repositoryUrl-from-syuilo-to-misskey-dev.js
new file mode 100644
index 0000000000..e4dbaa16d0
--- /dev/null
+++ b/packages/backend/migration/1708266695091-repositoryUrl-from-syuilo-to-misskey-dev.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class RepositoryUrlFromSyuiloToMisskeyDev1708266695091 {
+    name = 'RepositoryUrlFromSyuiloToMisskeyDev1708266695091'
+
+    async up(queryRunner) {
+        await queryRunner.query(`UPDATE "meta" SET "repositoryUrl" = 'https://github.com/misskey-dev/misskey' WHERE "repositoryUrl" = 'https://github.com/syuilo/misskey'`);
+    }
+
+    async down(queryRunner) {
+        // no valid down migration
+    }
+}
diff --git a/packages/backend/migration/1708342829000-SharkeyRepositoryUrl.js b/packages/backend/migration/1708342829000-SharkeyRepositoryUrl.js
new file mode 100644
index 0000000000..0853ed4edd
--- /dev/null
+++ b/packages/backend/migration/1708342829000-SharkeyRepositoryUrl.js
@@ -0,0 +1,22 @@
+/*
+ * SPDX-FileCopyrightText: dakkar and other Sharkey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class SharkeyRepositoryUrl1708342829000 {
+  name = 'SharkeyRepositoryUrl1708342829000'
+
+  async up(queryRunner) {
+    await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "repositoryUrl" SET DEFAULT 'https://activitypub.software/TransFem-org/Sharkey/'`);
+    await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "feedbackUrl" SET DEFAULT 'https://activitypub.software/TransFem-org/Sharkey/-/issues/new'`);
+    await queryRunner.query(`UPDATE "meta" SET "repositoryUrl"=DEFAULT WHERE "repositoryUrl" IN ('https://git.joinsharkey.org/Sharkey/Sharkey','https://github.com/transfem-org/sharkey','https://github.com/misskey-dev/misskey')`);
+    await queryRunner.query(`UPDATE "meta" SET "feedbackUrl"=DEFAULT WHERE "feedbackUrl" IN ('https://git.joinsharkey.org/Sharkey/Sharkey/issues/new/choose','https://github.com/transfem-org/sharkey/issues/new','https://github.com/misskey-dev/misskey/issues/new')`);
+  }
+
+  async down(queryRunner) {
+    await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "repositoryUrl" SET DEFAULT 'https://git.joinsharkey.org/Sharkey/Sharkey'`);
+    await queryRunner.query(`ALTER TABLE "meta" ALTER COLUMN "feedbackUrl" SET DEFAULT 'https://git.joinsharkey.org/Sharkey/Sharkey/issues/new/choose'`);
+    await queryRunner.query(`UPDATE "meta" SET "repositoryUrl"=DEFAULT WHERE "repositoryUrl" IN ('https://git.joinsharkey.org/Sharkey/Sharkey','https://github.com/transfem-org/sharkey','https://github.com/misskey-dev/misskey')`);
+    await queryRunner.query(`UPDATE "meta" SET "feedbackUrl"=DEFAULT WHERE "feedbackUrl" IN ('https://git.joinsharkey.org/Sharkey/Sharkey/issues/new/choose','https://github.com/transfem-org/sharkey/issues/new','https://github.com/misskey-dev/misskey/issues/new')`);
+  }
+}
diff --git a/packages/backend/migration/1708399372194-per-instance-mod-note.js b/packages/backend/migration/1708399372194-per-instance-mod-note.js
new file mode 100644
index 0000000000..339a4d7af9
--- /dev/null
+++ b/packages/backend/migration/1708399372194-per-instance-mod-note.js
@@ -0,0 +1,16 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export class PerInstanceModNote1708399372194 {
+    name = 'PerInstanceModNote1708399372194'
+
+    async up(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "instance" ADD "moderationNote" character varying(16384) NOT NULL DEFAULT ''`);
+    }
+
+    async down(queryRunner) {
+        await queryRunner.query(`ALTER TABLE "instance" DROP COLUMN "moderationNote"`);
+    }
+}
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 2aa10b1c96..2ddb067afe 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -8,11 +8,12 @@
 	},
 	"scripts": {
 		"start": "node ./built/boot/entry.js",
-		"start:test": "NODE_ENV=test node ./built/boot/entry.js",
+		"start:test": "cross-env NODE_ENV=test node ./built/boot/entry.js",
 		"migrate": "pnpm typeorm migration:run -d ormconfig.js",
 		"revert": "pnpm typeorm migration:revert -d ormconfig.js",
 		"check:connect": "node ./check_connect.js",
 		"build": "swc src -d built -D",
+		"build:test": "swc test-server -d built-test -D --config-file test-server/.swcrc",
 		"watch:swc": "swc src -d built -D -w",
 		"build:tsc": "tsc -p tsconfig.json && tsc-alias -p tsconfig.json",
 		"watch": "node watch.mjs",
@@ -21,12 +22,16 @@
 		"typecheck": "pnpm --filter megalodon build && tsc --noEmit",
 		"eslint": "eslint --quiet \"src/**/*.ts\"",
 		"lint": "pnpm typecheck && pnpm eslint",
-		"jest": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit",
-		"jest-and-coverage": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit",
+		"jest": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.unit.cjs",
+		"jest:e2e": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.e2e.cjs",
+		"jest-and-coverage": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --config jest.config.unit.cjs",
+		"jest-and-coverage:e2e": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --config jest.config.e2e.cjs",
 		"jest-clear": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --clearCache",
 		"test": "pnpm jest",
+		"test:e2e": "pnpm build && pnpm build:test && pnpm jest:e2e",
 		"test-and-coverage": "pnpm jest-and-coverage",
-		"generate-api-json": "node ./generate_api_json.js"
+		"test-and-coverage:e2e": "pnpm build && pnpm build:test && pnpm jest-and-coverage:e2e",
+		"generate-api-json": "pnpm build && node ./generate_api_json.js"
 	},
 	"optionalDependencies": {
 		"@swc/core-android-arm64": "1.3.11",
@@ -60,87 +65,91 @@
 	"dependencies": {
 		"@aws-sdk/client-s3": "3.412.0",
 		"@aws-sdk/lib-storage": "3.412.0",
-		"@bull-board/api": "5.10.2",
-		"@bull-board/fastify": "5.10.2",
-		"@bull-board/ui": "5.10.2",
+		"@bull-board/api": "5.14.2",
+		"@bull-board/fastify": "5.14.2",
+		"@bull-board/ui": "5.14.2",
 		"@discordapp/twemoji": "15.0.2",
 		"@fastify/accepts": "4.3.0",
-		"@fastify/cookie": "9.2.0",
+		"@fastify/cookie": "9.3.1",
 		"@fastify/cors": "8.5.0",
 		"@fastify/express": "2.3.0",
 		"@fastify/http-proxy": "9.3.0",
-		"@fastify/multipart": "8.0.0",
+		"@fastify/multipart": "8.1.0",
 		"@fastify/static": "6.12.0",
 		"@fastify/view": "8.2.0",
-		"@nestjs/common": "10.2.10",
-		"@nestjs/core": "10.2.10",
-		"@nestjs/testing": "10.2.10",
+		"@misskey-dev/sharp-read-bmp": "1.2.0",
+		"@misskey-dev/summaly": "5.0.3",
+		"@nestjs/common": "10.3.3",
+		"@nestjs/core": "10.3.3",
+		"@nestjs/testing": "10.3.3",
 		"@peertube/http-signature": "1.7.0",
-		"@sharkey/sfm-js": "0.24.3",
-		"@simplewebauthn/server": "8.3.5",
+		"@simplewebauthn/server": "9.0.3",
 		"@sinonjs/fake-timers": "11.2.2",
 		"@smithy/node-http-handler": "2.1.10",
 		"@swc/cli": "0.1.63",
-		"@swc/core": "1.3.100",
+		"@swc/core": "1.3.107",
+		"@transfem-org/sfm-js": "0.24.4",
 		"@twemoji/parser": "15.0.0",
 		"accepts": "1.3.8",
 		"ajv": "8.12.0",
 		"archiver": "6.0.1",
-		"argon2": "^0.31.1",
-		"async-mutex": "0.4.0",
+		"argon2": "^0.40.1",
+		"async-mutex": "0.4.1",
 		"bcryptjs": "2.4.3",
 		"blurhash": "2.0.5",
 		"body-parser": "1.20.2",
-		"bullmq": "4.15.4",
+		"bullmq": "5.4.0",
 		"cacheable-lookup": "7.0.0",
-		"cbor": "9.0.1",
+		"cbor": "9.0.2",
 		"chalk": "5.3.0",
 		"chalk-template": "1.1.0",
-		"chokidar": "3.5.3",
+		"chokidar": "3.6.0",
 		"cli-highlight": "2.1.11",
 		"color-convert": "2.0.1",
 		"content-disposition": "0.5.4",
 		"date-fns": "2.30.0",
 		"deep-email-validator": "0.1.21",
-		"fastify": "4.24.3",
+		"fastify": "4.25.2",
 		"fastify-multer": "^2.0.3",
 		"fastify-raw-body": "4.3.0",
 		"feed": "4.2.2",
-		"file-type": "18.7.0",
+		"file-type": "19.0.0",
 		"fluent-ffmpeg": "2.1.2",
 		"form-data": "4.0.0",
 		"glob": "10.3.10",
-		"got": "14.0.0",
+		"got": "14.2.0",
 		"happy-dom": "10.0.3",
 		"hpagent": "1.2.0",
-		"http-link-header": "1.1.1",
+		"htmlescape": "1.1.1",
+		"http-link-header": "1.1.2",
 		"ioredis": "5.3.2",
 		"ip-cidr": "3.1.0",
 		"ipaddr.js": "2.1.0",
 		"is-svg": "5.0.0",
 		"js-yaml": "4.1.0",
-		"jsdom": "23.0.1",
+		"jsdom": "23.2.0",
 		"json5": "2.2.3",
 		"jsonld": "8.3.2",
-		"jsrsasign": "10.9.0",
-		"meilisearch": "0.36.0",
+		"jsrsasign": "11.1.0",
 		"megalodon": "workspace:*",
+		"meilisearch": "0.37.0",
 		"microformats-parser": "2.0.2",
 		"mime-types": "2.1.35",
 		"misskey-js": "workspace:*",
+		"misskey-reversi": "workspace:*",
 		"ms": "3.0.0-canary.1",
-		"nanoid": "5.0.4",
+		"nanoid": "5.0.6",
 		"nested-property": "4.0.0",
 		"node-fetch": "3.3.2",
-		"nodemailer": "6.9.7",
+		"nodemailer": "6.9.10",
 		"oauth": "0.10.0",
 		"oauth2orize": "1.12.0",
 		"oauth2orize-pkce": "0.1.2",
 		"os-utils": "0.0.14",
-		"otpauth": "9.2.1",
+		"otpauth": "9.2.2",
 		"parse5": "7.1.2",
 		"pg": "8.11.3",
-		"pkce-challenge": "4.0.1",
+		"pkce-challenge": "4.1.0",
 		"probe-image-size": "7.2.3",
 		"promise-limit": "2.7.0",
 		"pug": "3.0.2",
@@ -151,44 +160,44 @@
 		"ratelimiter": "3.4.1",
 		"re2": "1.20.9",
 		"redis-lock": "0.1.4",
-		"reflect-metadata": "0.1.14",
+		"reflect-metadata": "0.2.1",
 		"rename": "1.0.4",
 		"rss-parser": "3.13.0",
 		"rxjs": "7.8.1",
-		"sanitize-html": "2.11.0",
+		"sanitize-html": "2.12.1",
 		"secure-json-parse": "2.7.0",
-		"sharp": "0.32.6",
-		"sharp-read-bmp": "github:misskey-dev/sharp-read-bmp",
+		"sharp": "0.33.2",
 		"slacc": "0.0.10",
 		"strict-event-emitter-types": "2.0.0",
 		"stringz": "2.1.0",
-		"summaly": "github:misskey-dev/summaly",
-		"systeminformation": "5.21.20",
+		"systeminformation": "5.22.0",
 		"tinycolor2": "1.6.0",
-		"tmp": "0.2.1",
+		"tmp": "0.2.2",
 		"tsc-alias": "1.8.8",
 		"tsconfig-paths": "4.2.0",
-		"typeorm": "0.3.17",
+		"typeorm": "0.3.20",
 		"typescript": "5.3.3",
 		"ulid": "2.3.0",
 		"uuid": "^9.0.1",
 		"vary": "1.1.2",
-		"web-push": "3.6.6",
-		"ws": "8.15.1",
+		"web-push": "3.6.7",
+		"ws": "8.16.0",
 		"xev": "3.0.2"
 	},
 	"devDependencies": {
 		"@jest/globals": "29.7.0",
-		"@simplewebauthn/typescript-types": "8.3.4",
-		"@swc/jest": "0.2.29",
+		"@misskey-dev/eslint-plugin": "1.0.0",
+		"@nestjs/platform-express": "10.3.3",
+		"@simplewebauthn/types": "9.0.1",
+		"@swc/jest": "0.2.31",
 		"@types/accepts": "1.3.7",
 		"@types/archiver": "6.0.2",
 		"@types/bcryptjs": "2.4.6",
 		"@types/body-parser": "1.19.5",
-		"@types/cbor": "6.0.0",
 		"@types/color-convert": "2.0.3",
 		"@types/content-disposition": "0.5.8",
 		"@types/fluent-ffmpeg": "2.1.24",
+		"@types/htmlescape": "^1.1.3",
 		"@types/http-link-header": "1.0.5",
 		"@types/jest": "29.5.11",
 		"@types/js-yaml": "4.0.9",
@@ -197,22 +206,21 @@
 		"@types/jsrsasign": "10.5.12",
 		"@types/mime-types": "2.1.4",
 		"@types/ms": "0.7.34",
-		"@types/node": "20.10.5",
+		"@types/node": "20.11.22",
 		"@types/node-fetch": "3.0.3",
 		"@types/nodemailer": "6.4.14",
 		"@types/oauth": "0.9.4",
 		"@types/oauth2orize": "1.11.3",
 		"@types/oauth2orize-pkce": "0.1.2",
-		"@types/pg": "8.10.9",
+		"@types/pg": "8.11.2",
 		"@types/pug": "2.0.10",
-		"@types/punycode": "2.1.3",
+		"@types/punycode": "2.1.4",
 		"@types/qrcode": "1.5.5",
 		"@types/random-seed": "0.3.5",
 		"@types/ratelimiter": "3.4.6",
 		"@types/rename": "1.0.7",
-		"@types/sanitize-html": "2.9.5",
-		"@types/semver": "7.5.6",
-		"@types/sharp": "0.32.0",
+		"@types/sanitize-html": "2.11.0",
+		"@types/semver": "7.5.8",
 		"@types/simple-oauth2": "5.0.7",
 		"@types/sinonjs__fake-timers": "8.1.5",
 		"@types/tinycolor2": "1.4.6",
@@ -221,16 +229,18 @@
 		"@types/vary": "1.1.3",
 		"@types/web-push": "3.6.3",
 		"@types/ws": "8.5.10",
-		"@typescript-eslint/eslint-plugin": "6.14.0",
-		"@typescript-eslint/parser": "6.14.0",
-		"aws-sdk-client-mock": "3.0.0",
+		"@typescript-eslint/eslint-plugin": "7.1.0",
+		"@typescript-eslint/parser": "7.1.0",
+		"aws-sdk-client-mock": "3.0.1",
 		"cross-env": "7.0.3",
-		"eslint": "8.56.0",
+		"eslint": "8.57.0",
 		"eslint-plugin-import": "2.29.1",
 		"execa": "8.0.1",
+		"fkill": "^9.0.0",
 		"jest": "29.7.0",
 		"jest-mock": "29.7.0",
-		"nodemon": "3.0.2",
+		"nodemon": "3.1.0",
+		"pid-port": "1.0.0",
 		"simple-oauth2": "5.0.0"
 	}
 }
diff --git a/packages/backend/src/@types/hcaptcha.d.ts b/packages/backend/src/@types/hcaptcha.d.ts
index 43e67dd340..e11dda4662 100644
--- a/packages/backend/src/@types/hcaptcha.d.ts
+++ b/packages/backend/src/@types/hcaptcha.d.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/@types/http-signature.d.ts b/packages/backend/src/@types/http-signature.d.ts
index 1f3b48aa54..75b62e55f0 100644
--- a/packages/backend/src/@types/http-signature.d.ts
+++ b/packages/backend/src/@types/http-signature.d.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/@types/os-utils.d.ts b/packages/backend/src/@types/os-utils.d.ts
index 8c44232c14..8943edddd1 100644
--- a/packages/backend/src/@types/os-utils.d.ts
+++ b/packages/backend/src/@types/os-utils.d.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/@types/package.json.d.ts b/packages/backend/src/@types/package.json.d.ts
index 197b4b6bf0..52a2b356db 100644
--- a/packages/backend/src/@types/package.json.d.ts
+++ b/packages/backend/src/@types/package.json.d.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/@types/probe-image-size.d.ts b/packages/backend/src/@types/probe-image-size.d.ts
index 4d312cba34..538836475c 100644
--- a/packages/backend/src/@types/probe-image-size.d.ts
+++ b/packages/backend/src/@types/probe-image-size.d.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/@types/redis-lock.d.ts b/packages/backend/src/@types/redis-lock.d.ts
index c607d600d8..b037cde5ee 100644
--- a/packages/backend/src/@types/redis-lock.d.ts
+++ b/packages/backend/src/@types/redis-lock.d.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/GlobalModule.ts b/packages/backend/src/GlobalModule.ts
index 3e9d19f825..09971e8ca0 100644
--- a/packages/backend/src/GlobalModule.ts
+++ b/packages/backend/src/GlobalModule.ts
@@ -1,9 +1,8 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { setTimeout } from 'node:timers/promises';
 import { Global, Inject, Module } from '@nestjs/common';
 import * as Redis from 'ioredis';
 import { DataSource } from 'typeorm';
@@ -12,6 +11,7 @@ import { DI } from './di-symbols.js';
 import { Config, loadConfig } from './config.js';
 import { createPostgresDataSource } from './postgres.js';
 import { RepositoryModule } from './models/RepositoryModule.js';
+import { allSettled } from './misc/promise-tracker.js';
 import type { Provider, OnApplicationShutdown } from '@nestjs/common';
 
 const $config: Provider = {
@@ -33,7 +33,7 @@ const $meilisearch: Provider = {
 	useFactory: (config: Config) => {
 		if (config.meilisearch) {
 			return new MeiliSearch({
-				host: `${config.meilisearch.ssl ? 'https' : 'http' }://${config.meilisearch.host}:${config.meilisearch.port}`,
+				host: `${config.meilisearch.ssl ? 'https' : 'http'}://${config.meilisearch.host}:${config.meilisearch.port}`,
 				apiKey: config.meilisearch.apiKey,
 			});
 		} else {
@@ -91,17 +91,12 @@ export class GlobalModule implements OnApplicationShutdown {
 		@Inject(DI.redisForPub) private redisForPub: Redis.Redis,
 		@Inject(DI.redisForSub) private redisForSub: Redis.Redis,
 		@Inject(DI.redisForTimelines) private redisForTimelines: Redis.Redis,
-	) {}
+	) { }
 
 	public async dispose(): Promise<void> {
-		if (process.env.NODE_ENV === 'test') {
-			// XXX:
-			// Shutting down the existing connections causes errors on Jest as
-			// Misskey has asynchronous postgres/redis connections that are not
-			// awaited.
-			// Let's wait for some random time for them to finish.
-			await setTimeout(5000);
-		}
+		// Wait for all potential DB queries
+		await allSettled();
+		// And then disconnect from DB
 		await Promise.all([
 			this.db.destroy(),
 			this.redisClient.disconnect(),
diff --git a/packages/backend/src/MainModule.ts b/packages/backend/src/MainModule.ts
index 90aba0cc91..f86a0be93c 100644
--- a/packages/backend/src/MainModule.ts
+++ b/packages/backend/src/MainModule.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/NestLogger.ts b/packages/backend/src/NestLogger.ts
index e18e9e88a7..80f1f7a024 100644
--- a/packages/backend/src/NestLogger.ts
+++ b/packages/backend/src/NestLogger.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/boot/common.ts b/packages/backend/src/boot/common.ts
index df10ab1e3d..268c07582d 100644
--- a/packages/backend/src/boot/common.ts
+++ b/packages/backend/src/boot/common.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/boot/entry.ts b/packages/backend/src/boot/entry.ts
index bd0ed29fc2..ae74a43c84 100644
--- a/packages/backend/src/boot/entry.ts
+++ b/packages/backend/src/boot/entry.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/boot/master.ts b/packages/backend/src/boot/master.ts
index a3f8694640..aafd43beea 100644
--- a/packages/backend/src/boot/master.ts
+++ b/packages/backend/src/boot/master.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/boot/worker.ts b/packages/backend/src/boot/worker.ts
index 0399c9fe5c..d4a7cd56e5 100644
--- a/packages/backend/src/boot/worker.ts
+++ b/packages/backend/src/boot/worker.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts
index a550fdc364..c99bc7ae03 100644
--- a/packages/backend/src/config.ts
+++ b/packages/backend/src/config.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -58,6 +58,8 @@ type Source = {
 		scope?: 'local' | 'global' | string[];
 	};
 
+	publishTarballInsteadOfProvideRepositoryUrl?: boolean;
+
 	proxy?: string;
 	proxySmtp?: string;
 	proxyBypassHosts?: string[];
@@ -76,10 +78,10 @@ type Source = {
 
 	deliverJobConcurrency?: number;
 	inboxJobConcurrency?: number;
-	relashionshipJobConcurrency?: number;
+	relationshipJobConcurrency?: number;
 	deliverJobPerSec?: number;
 	inboxJobPerSec?: number;
-	relashionshipJobPerSec?: number;
+	relationshipJobPerSec?: number;
 	deliverJobMaxAttempts?: number;
 	inboxJobMaxAttempts?: number;
 
@@ -141,18 +143,19 @@ export type Config = {
 	outgoingAddressFamily: 'ipv4' | 'ipv6' | 'dual' | undefined;
 	deliverJobConcurrency: number | undefined;
 	inboxJobConcurrency: number | undefined;
-	relashionshipJobConcurrency: number | undefined;
+	relationshipJobConcurrency: number | undefined;
 	deliverJobPerSec: number | undefined;
 	inboxJobPerSec: number | undefined;
-	relashionshipJobPerSec: number | undefined;
+	relationshipJobPerSec: number | undefined;
 	deliverJobMaxAttempts: number | undefined;
 	inboxJobMaxAttempts: number | undefined;
 	proxyRemoteFiles: boolean | undefined;
 	customMOTD: string[] | undefined;
-	signToActivityPubGet: boolean | undefined;
+	signToActivityPubGet: boolean;
 	checkActivityPubGetSignature: boolean | undefined;
 
 	version: string;
+	publishTarballInsteadOfProvideRepositoryUrl: boolean;
 	host: string;
 	hostname: string;
 	scheme: string;
@@ -224,6 +227,7 @@ export function loadConfig(): Config {
 
 	return {
 		version,
+		publishTarballInsteadOfProvideRepositoryUrl: !!config.publishTarballInsteadOfProvideRepositoryUrl,
 		url: url.origin,
 		port: config.port ?? parseInt(process.env.PORT ?? '', 10),
 		socket: config.socket,
@@ -257,15 +261,15 @@ export function loadConfig(): Config {
 		outgoingAddressFamily: config.outgoingAddressFamily,
 		deliverJobConcurrency: config.deliverJobConcurrency,
 		inboxJobConcurrency: config.inboxJobConcurrency,
-		relashionshipJobConcurrency: config.relashionshipJobConcurrency,
+		relationshipJobConcurrency: config.relationshipJobConcurrency,
 		deliverJobPerSec: config.deliverJobPerSec,
 		inboxJobPerSec: config.inboxJobPerSec,
-		relashionshipJobPerSec: config.relashionshipJobPerSec,
+		relationshipJobPerSec: config.relationshipJobPerSec,
 		deliverJobMaxAttempts: config.deliverJobMaxAttempts,
 		inboxJobMaxAttempts: config.inboxJobMaxAttempts,
 		proxyRemoteFiles: config.proxyRemoteFiles,
 		customMOTD: config.customMOTD,
-		signToActivityPubGet: config.signToActivityPubGet,
+		signToActivityPubGet: config.signToActivityPubGet ?? true,
 		checkActivityPubGetSignature: config.checkActivityPubGetSignature,
 		mediaProxy: externalMediaProxy ?? internalMediaProxy,
 		externalMediaProxyEnabled: externalMediaProxy !== null && externalMediaProxy !== internalMediaProxy,
diff --git a/packages/backend/src/const.ts b/packages/backend/src/const.ts
index 6abc11ac93..02c27779ca 100644
--- a/packages/backend/src/const.ts
+++ b/packages/backend/src/const.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/AccountMoveService.ts b/packages/backend/src/core/AccountMoveService.ts
index 350aa6ba24..5bd885df40 100644
--- a/packages/backend/src/core/AccountMoveService.ts
+++ b/packages/backend/src/core/AccountMoveService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -20,7 +20,6 @@ import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
 import { ApDeliverManagerService } from '@/core/activitypub/ApDeliverManagerService.js';
 import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
-import { CacheService } from '@/core/CacheService.js';
 import { ProxyAccountService } from '@/core/ProxyAccountService.js';
 import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
 import { MetaService } from '@/core/MetaService.js';
@@ -60,7 +59,6 @@ export class AccountMoveService {
 		private instanceChart: InstanceChart,
 		private metaService: MetaService,
 		private relayService: RelayService,
-		private cacheService: CacheService,
 		private queueService: QueueService,
 	) {
 	}
@@ -84,7 +82,7 @@ export class AccountMoveService {
 		Object.assign(src, update);
 
 		// Update cache
-		this.cacheService.uriPersonCache.set(srcUri, src);
+		this.globalEventService.publishInternalEvent('localUserUpdated', src);
 
 		const srcPerson = await this.apRendererService.renderPerson(src);
 		const updateAct = this.apRendererService.addContext(this.apRendererService.renderUpdate(srcPerson, src));
@@ -96,7 +94,7 @@ export class AccountMoveService {
 		await this.apDeliverManagerService.deliverToFollowers(src, moveAct);
 
 		// Publish meUpdated event
-		const iObj = await this.userEntityService.pack<true, true>(src.id, src, { detail: true, includeSecrets: true });
+		const iObj = await this.userEntityService.pack(src.id, src, { schema: 'MeDetailed', includeSecrets: true });
 		this.globalEventService.publishMainStream(src.id, 'meUpdated', iObj);
 
 		// Unfollow after 24 hours
diff --git a/packages/backend/src/core/AccountUpdateService.ts b/packages/backend/src/core/AccountUpdateService.ts
index 664700ea6b..69a57b4854 100644
--- a/packages/backend/src/core/AccountUpdateService.ts
+++ b/packages/backend/src/core/AccountUpdateService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/AchievementService.ts b/packages/backend/src/core/AchievementService.ts
index 88fc033859..4fc1193f32 100644
--- a/packages/backend/src/core/AchievementService.ts
+++ b/packages/backend/src/core/AchievementService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -87,6 +87,8 @@ export const ACHIEVEMENT_TYPES = [
 	'brainDiver',
 	'smashTestNotificationButton',
 	'tutorialCompleted',
+	'bubbleGameExplodingHead',
+	'bubbleGameDoubleExplodingHead',
 ] as const;
 
 @Injectable()
diff --git a/packages/backend/src/core/AnnouncementService.ts b/packages/backend/src/core/AnnouncementService.ts
index 8c348e595d..b298a70929 100644
--- a/packages/backend/src/core/AnnouncementService.ts
+++ b/packages/backend/src/core/AnnouncementService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts
index 2c27a02559..4f956a43ed 100644
--- a/packages/backend/src/core/AntennaService.ts
+++ b/packages/backend/src/core/AntennaService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -55,23 +55,29 @@ export class AntennaService implements OnApplicationShutdown {
 			const { type, body } = obj.message as GlobalEvents['internal']['payload'];
 			switch (type) {
 				case 'antennaCreated':
-					this.antennas.push({
+					this.antennas.push({ // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
 						...body,
 						lastUsedAt: new Date(body.lastUsedAt),
+						user: null, // joinなカラムは通常取ってこないので
+						userList: null, // joinなカラムは通常取ってこないので
 					});
 					break;
 				case 'antennaUpdated': {
 					const idx = this.antennas.findIndex(a => a.id === body.id);
 					if (idx >= 0) {
-						this.antennas[idx] = {
+						this.antennas[idx] = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
 							...body,
 							lastUsedAt: new Date(body.lastUsedAt),
+							user: null, // joinなカラムは通常取ってこないので
+							userList: null, // joinなカラムは通常取ってこないので
 						};
 					} else {
 						// サーバ起動時にactiveじゃなかった場合、リストに持っていないので追加する必要あり
-						this.antennas.push({
+						this.antennas.push({ // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
 							...body,
 							lastUsedAt: new Date(body.lastUsedAt),
+							user: null, // joinなカラムは通常取ってこないので
+							userList: null, // joinなカラムは通常取ってこないので
 						});
 					}
 				}
diff --git a/packages/backend/src/core/AppLockService.ts b/packages/backend/src/core/AppLockService.ts
index 7a1293a6de..bd2749cb87 100644
--- a/packages/backend/src/core/AppLockService.ts
+++ b/packages/backend/src/core/AppLockService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/AvatarDecorationService.ts b/packages/backend/src/core/AvatarDecorationService.ts
index e97946f9dc..21e31d79a4 100644
--- a/packages/backend/src/core/AvatarDecorationService.ts
+++ b/packages/backend/src/core/AvatarDecorationService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/CacheService.ts b/packages/backend/src/core/CacheService.ts
index e1413342b1..d008e7ec52 100644
--- a/packages/backend/src/core/CacheService.ts
+++ b/packages/backend/src/core/CacheService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -16,10 +16,10 @@ import type { OnApplicationShutdown } from '@nestjs/common';
 
 @Injectable()
 export class CacheService implements OnApplicationShutdown {
-	public userByIdCache: MemoryKVCache<MiUser, MiUser | string>;
-	public localUserByNativeTokenCache: MemoryKVCache<MiLocalUser | null, string | null>;
+	public userByIdCache: MemoryKVCache<MiUser>;
+	public localUserByNativeTokenCache: MemoryKVCache<MiLocalUser | null>;
 	public localUserByIdCache: MemoryKVCache<MiLocalUser>;
-	public uriPersonCache: MemoryKVCache<MiUser | null, string | null>;
+	public uriPersonCache: MemoryKVCache<MiUser | null>;
 	public userProfileCache: RedisKVCache<MiUserProfile>;
 	public userMutingsCache: RedisKVCache<Set<string>>;
 	public userBlockingCache: RedisKVCache<Set<string>>;
@@ -56,41 +56,10 @@ export class CacheService implements OnApplicationShutdown {
 	) {
 		//this.onMessage = this.onMessage.bind(this);
 
-		const localUserByIdCache = new MemoryKVCache<MiLocalUser>(1000 * 60 * 60 * 6 /* 6h */);
-		this.localUserByIdCache	= localUserByIdCache;
-
-		// ローカルユーザーならlocalUserByIdCacheにデータを追加し、こちらにはid(文字列)だけを追加する
-		const userByIdCache = new MemoryKVCache<MiUser, MiUser | string>(1000 * 60 * 60 * 6 /* 6h */, {
-			toMapConverter: user => {
-				if (user.host === null) {
-					localUserByIdCache.set(user.id, user as MiLocalUser);
-					return user.id;
-				}
-
-				return user;
-			},
-			fromMapConverter: userOrId => typeof userOrId === 'string' ? localUserByIdCache.get(userOrId) : userOrId,
-		});
-		this.userByIdCache = userByIdCache;
-
-		this.localUserByNativeTokenCache = new MemoryKVCache<MiLocalUser | null, string | null>(Infinity, {
-			toMapConverter: user => {
-				if (user === null) return null;
-
-				localUserByIdCache.set(user.id, user);
-				return user.id;
-			},
-			fromMapConverter: id => id === null ? null : localUserByIdCache.get(id),
-		});
-		this.uriPersonCache = new MemoryKVCache<MiUser | null, string | null>(Infinity, {
-			toMapConverter: user => {
-				if (user === null) return null;
-
-				userByIdCache.set(user.id, user);
-				return user.id;
-			},
-			fromMapConverter: id => id === null ? null : userByIdCache.get(id),
-		});
+		this.userByIdCache = new MemoryKVCache<MiUser>(Infinity);
+		this.localUserByNativeTokenCache = new MemoryKVCache<MiLocalUser | null>(Infinity);
+		this.localUserByIdCache = new MemoryKVCache<MiLocalUser>(Infinity);
+		this.uriPersonCache = new MemoryKVCache<MiUser | null>(Infinity);
 
 		this.userProfileCache = new RedisKVCache<MiUserProfile>(this.redisClient, 'userProfile', {
 			lifetime: 1000 * 60 * 30, // 30m
@@ -159,17 +128,29 @@ export class CacheService implements OnApplicationShutdown {
 			const { type, body } = obj.message as GlobalEvents['internal']['payload'];
 			switch (type) {
 				case 'userChangeSuspendedState':
-				case 'remoteUserUpdated': {
-					const user = await this.usersRepository.findOneByOrFail({ id: body.id });
-					this.userByIdCache.set(user.id, user);
-					for (const [k, v] of this.uriPersonCache.cache.entries()) {
-						if (v.value === user.id) {
-							this.uriPersonCache.set(k, user);
+				case 'userChangeDeletedState':
+				case 'remoteUserUpdated':
+				case 'localUserUpdated': {
+					const user = await this.usersRepository.findOneBy({ id: body.id });
+					if (user == null) {
+						this.userByIdCache.delete(body.id);
+						this.localUserByIdCache.delete(body.id);
+						for (const [k, v] of this.uriPersonCache.cache.entries()) {
+							if (v.value?.id === body.id) {
+								this.uriPersonCache.delete(k);
+							}
+						}
+					} else {
+						this.userByIdCache.set(user.id, user);
+						for (const [k, v] of this.uriPersonCache.cache.entries()) {
+							if (v.value?.id === user.id) {
+								this.uriPersonCache.set(k, user);
+							}
+						}
+						if (this.userEntityService.isLocalUser(user)) {
+							this.localUserByNativeTokenCache.set(user.token!, user);
+							this.localUserByIdCache.set(user.id, user);
 						}
-					}
-					if (this.userEntityService.isLocalUser(user)) {
-						this.localUserByNativeTokenCache.set(user.token!, user);
-						this.localUserByIdCache.set(user.id, user);
 					}
 					break;
 				}
diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts
index f64196f4fc..f6b7955cd2 100644
--- a/packages/backend/src/core/CaptchaService.ts
+++ b/packages/backend/src/core/CaptchaService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -73,6 +73,37 @@ export class CaptchaService {
 		}
 	}
 
+	// https://codeberg.org/Gusted/mCaptcha/src/branch/main/mcaptcha.go
+	@bindThis
+	public async verifyMcaptcha(secret: string, siteKey: string, instanceHost: string, response: string | null | undefined): Promise<void> {
+		if (response == null) {
+			throw new Error('mcaptcha-failed: no response provided');
+		}
+
+		const endpointUrl = new URL('/api/v1/pow/siteverify', instanceHost);
+		const result = await this.httpRequestService.send(endpointUrl.toString(), {
+			method: 'POST',
+			body: JSON.stringify({
+				key: siteKey,
+				secret: secret,
+				token: response,
+			}),
+			headers: {
+				'Content-Type': 'application/json',
+			},
+		});
+
+		if (result.status !== 200) {
+			throw new Error('mcaptcha-failed: mcaptcha didn\'t return 200 OK');
+		}
+
+		const resp = (await result.json()) as { valid: boolean };
+
+		if (!resp.valid) {
+			throw new Error('mcaptcha-request-failed');
+		}
+	}
+
 	@bindThis
 	public async verifyTurnstile(secret: string, response: string | null | undefined): Promise<void> {
 		if (response == null) {
diff --git a/packages/backend/src/core/ClipService.ts b/packages/backend/src/core/ClipService.ts
index e94f1eb531..bb8be26ce6 100644
--- a/packages/backend/src/core/ClipService.ts
+++ b/packages/backend/src/core/ClipService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts
index fa868ff8b0..48a2a2b80c 100644
--- a/packages/backend/src/core/CoreModule.ts
+++ b/packages/backend/src/core/CoreModule.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -66,6 +66,8 @@ import { FeaturedService } from './FeaturedService.js';
 import { FanoutTimelineService } from './FanoutTimelineService.js';
 import { ChannelFollowingService } from './ChannelFollowingService.js';
 import { RegistryApiService } from './RegistryApiService.js';
+import { ReversiService } from './ReversiService.js';
+
 import { ChartLoggerService } from './chart/ChartLoggerService.js';
 import FederationChart from './chart/charts/federation.js';
 import NotesChart from './chart/charts/notes.js';
@@ -80,6 +82,7 @@ import PerUserFollowingChart from './chart/charts/per-user-following.js';
 import PerUserDriveChart from './chart/charts/per-user-drive.js';
 import ApRequestChart from './chart/charts/ap-request.js';
 import { ChartManagementService } from './chart/ChartManagementService.js';
+
 import { AbuseUserReportEntityService } from './entities/AbuseUserReportEntityService.js';
 import { AntennaEntityService } from './entities/AntennaEntityService.js';
 import { AppEntityService } from './entities/AppEntityService.js';
@@ -112,6 +115,9 @@ import { UserListEntityService } from './entities/UserListEntityService.js';
 import { FlashEntityService } from './entities/FlashEntityService.js';
 import { FlashLikeEntityService } from './entities/FlashLikeEntityService.js';
 import { RoleEntityService } from './entities/RoleEntityService.js';
+import { ReversiGameEntityService } from './entities/ReversiGameEntityService.js';
+import { MetaEntityService } from './entities/MetaEntityService.js';
+
 import { ApAudienceService } from './activitypub/ApAudienceService.js';
 import { ApDbResolverService } from './activitypub/ApDbResolverService.js';
 import { ApDeliverManagerService } from './activitypub/ApDeliverManagerService.js';
@@ -199,6 +205,7 @@ const $FanoutTimelineService: Provider = { provide: 'FanoutTimelineService', use
 const $FanoutTimelineEndpointService: Provider = { provide: 'FanoutTimelineEndpointService', useExisting: FanoutTimelineEndpointService };
 const $ChannelFollowingService: Provider = { provide: 'ChannelFollowingService', useExisting: ChannelFollowingService };
 const $RegistryApiService: Provider = { provide: 'RegistryApiService', useExisting: RegistryApiService };
+const $ReversiService: Provider = { provide: 'ReversiService', useExisting: ReversiService };
 
 const $ChartLoggerService: Provider = { provide: 'ChartLoggerService', useExisting: ChartLoggerService };
 const $FederationChart: Provider = { provide: 'FederationChart', useExisting: FederationChart };
@@ -247,6 +254,8 @@ const $UserListEntityService: Provider = { provide: 'UserListEntityService', use
 const $FlashEntityService: Provider = { provide: 'FlashEntityService', useExisting: FlashEntityService };
 const $FlashLikeEntityService: Provider = { provide: 'FlashLikeEntityService', useExisting: FlashLikeEntityService };
 const $RoleEntityService: Provider = { provide: 'RoleEntityService', useExisting: RoleEntityService };
+const $ReversiGameEntityService: Provider = { provide: 'ReversiGameEntityService', useExisting: ReversiGameEntityService };
+const $MetaEntityService: Provider = { provide: 'MetaEntityService', useExisting: MetaEntityService };
 
 const $ApAudienceService: Provider = { provide: 'ApAudienceService', useExisting: ApAudienceService };
 const $ApDbResolverService: Provider = { provide: 'ApDbResolverService', useExisting: ApDbResolverService };
@@ -336,6 +345,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		FanoutTimelineEndpointService,
 		ChannelFollowingService,
 		RegistryApiService,
+		ReversiService,
+
 		ChartLoggerService,
 		FederationChart,
 		NotesChart,
@@ -350,6 +361,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		PerUserDriveChart,
 		ApRequestChart,
 		ChartManagementService,
+
 		AbuseUserReportEntityService,
 		AntennaEntityService,
 		AppEntityService,
@@ -382,6 +394,9 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		FlashEntityService,
 		FlashLikeEntityService,
 		RoleEntityService,
+		ReversiGameEntityService,
+		MetaEntityService,
+
 		ApAudienceService,
 		ApDbResolverService,
 		ApDeliverManagerService,
@@ -466,6 +481,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$FanoutTimelineEndpointService,
 		$ChannelFollowingService,
 		$RegistryApiService,
+		$ReversiService,
+
 		$ChartLoggerService,
 		$FederationChart,
 		$NotesChart,
@@ -480,6 +497,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$PerUserDriveChart,
 		$ApRequestChart,
 		$ChartManagementService,
+
 		$AbuseUserReportEntityService,
 		$AntennaEntityService,
 		$AppEntityService,
@@ -512,6 +530,9 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$FlashEntityService,
 		$FlashLikeEntityService,
 		$RoleEntityService,
+		$ReversiGameEntityService,
+		$MetaEntityService,
+
 		$ApAudienceService,
 		$ApDbResolverService,
 		$ApDeliverManagerService,
@@ -597,6 +618,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		FanoutTimelineEndpointService,
 		ChannelFollowingService,
 		RegistryApiService,
+		ReversiService,
+
 		FederationChart,
 		NotesChart,
 		UsersChart,
@@ -610,6 +633,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		PerUserDriveChart,
 		ApRequestChart,
 		ChartManagementService,
+
 		AbuseUserReportEntityService,
 		AntennaEntityService,
 		AppEntityService,
@@ -642,6 +666,9 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		FlashEntityService,
 		FlashLikeEntityService,
 		RoleEntityService,
+		ReversiGameEntityService,
+		MetaEntityService,
+
 		ApAudienceService,
 		ApDbResolverService,
 		ApDeliverManagerService,
@@ -726,6 +753,8 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$FanoutTimelineEndpointService,
 		$ChannelFollowingService,
 		$RegistryApiService,
+		$ReversiService,
+
 		$FederationChart,
 		$NotesChart,
 		$UsersChart,
@@ -739,6 +768,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$PerUserDriveChart,
 		$ApRequestChart,
 		$ChartManagementService,
+
 		$AbuseUserReportEntityService,
 		$AntennaEntityService,
 		$AppEntityService,
@@ -771,6 +801,9 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
 		$FlashEntityService,
 		$FlashLikeEntityService,
 		$RoleEntityService,
+		$ReversiGameEntityService,
+		$MetaEntityService,
+
 		$ApAudienceService,
 		$ApDbResolverService,
 		$ApDeliverManagerService,
diff --git a/packages/backend/src/core/CreateSystemUserService.ts b/packages/backend/src/core/CreateSystemUserService.ts
index 58853c8e23..14d814b0e6 100644
--- a/packages/backend/src/core/CreateSystemUserService.ts
+++ b/packages/backend/src/core/CreateSystemUserService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts
index 5a1fe3d089..acc1c604e6 100644
--- a/packages/backend/src/core/CustomEmojiService.ts
+++ b/packages/backend/src/core/CustomEmojiService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -408,7 +408,7 @@ export class CustomEmojiService implements OnApplicationShutdown {
 	 */
 	@bindThis
 	public checkDuplicate(name: string): Promise<boolean> {
-		return this.emojisRepository.exist({ where: { name, host: IsNull() } });
+		return this.emojisRepository.exists({ where: { name, host: IsNull() } });
 	}
 
 	@bindThis
@@ -416,6 +416,11 @@ export class CustomEmojiService implements OnApplicationShutdown {
 		return this.emojisRepository.findOneBy({ id });
 	}
 
+	@bindThis
+	public getEmojiByName(name: string): Promise<MiEmoji | null> {
+		return this.emojisRepository.findOneBy({ name, host: IsNull() });
+	}
+
 	@bindThis
 	public dispose(): void {
 		this.cache.dispose();
diff --git a/packages/backend/src/core/DeleteAccountService.ts b/packages/backend/src/core/DeleteAccountService.ts
index 570bd440e4..79b614edba 100644
--- a/packages/backend/src/core/DeleteAccountService.ts
+++ b/packages/backend/src/core/DeleteAccountService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -9,6 +9,7 @@ import { QueueService } from '@/core/QueueService.js';
 import { UserSuspendService } from '@/core/UserSuspendService.js';
 import { DI } from '@/di-symbols.js';
 import { bindThis } from '@/decorators.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
 
 @Injectable()
 export class DeleteAccountService {
@@ -18,6 +19,7 @@ export class DeleteAccountService {
 
 		private userSuspendService: UserSuspendService,
 		private queueService: QueueService,
+		private globalEventService: GlobalEventService,
 	) {
 	}
 
@@ -39,5 +41,7 @@ export class DeleteAccountService {
 		await this.usersRepository.update(user.id, {
 			isDeleted: true,
 		});
+
+		this.globalEventService.publishInternalEvent('userChangeDeletedState', { id: user.id, isDeleted: true });
 	}
 }
diff --git a/packages/backend/src/core/DownloadService.ts b/packages/backend/src/core/DownloadService.ts
index 5474272b00..21ae798f9f 100644
--- a/packages/backend/src/core/DownloadService.ts
+++ b/packages/backend/src/core/DownloadService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -145,7 +145,8 @@ export class DownloadService {
 		const parsedIp = ipaddr.parse(ip);
 
 		for (const net of this.config.allowedPrivateNetworks ?? []) {
-			if (parsedIp.match(ipaddr.parseCIDR(net))) {
+			const cidr = ipaddr.parseCIDR(net);
+			if (cidr[0].kind() === parsedIp.kind() && parsedIp.match(ipaddr.parseCIDR(net))) {
 				return false;
 			}
 		}
diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts
index 9b6187be4f..f64568ee9a 100644
--- a/packages/backend/src/core/DriveService.ts
+++ b/packages/backend/src/core/DriveService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -7,7 +7,7 @@ import { randomUUID } from 'node:crypto';
 import * as fs from 'node:fs';
 import { Inject, Injectable } from '@nestjs/common';
 import sharp from 'sharp';
-import { sharpBmp } from 'sharp-read-bmp';
+import { sharpBmp } from '@misskey-dev/sharp-read-bmp';
 import { IsNull } from 'typeorm';
 import { DeleteObjectCommandInput, PutObjectCommandInput, NoSuchKey } from '@aws-sdk/client-s3';
 import { DI } from '@/di-symbols.js';
@@ -634,7 +634,7 @@ export class DriveService {
 	public async updateFile(file: MiDriveFile, values: Partial<MiDriveFile>, updater: MiUser) {
 		const alwaysMarkNsfw = (await this.roleService.getUserPolicies(file.userId)).alwaysMarkNsfw;
 
-		if (values.name && !this.driveFileEntityService.validateFileName(file.name)) {
+		if (values.name != null && !this.driveFileEntityService.validateFileName(values.name)) {
 			throw new DriveService.InvalidFileNameError();
 		}
 
diff --git a/packages/backend/src/core/EmailService.ts b/packages/backend/src/core/EmailService.ts
index 7fc7800783..08f8f80a6e 100644
--- a/packages/backend/src/core/EmailService.ts
+++ b/packages/backend/src/core/EmailService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -40,6 +40,8 @@ export class EmailService {
 	public async sendEmail(to: string, subject: string, html: string, text: string) {
 		const meta = await this.metaService.fetch(true);
 
+		if (!meta.enableEmail) return;
+
 		const iconUrl = `${this.config.url}/static-assets/mi-white.png`;
 		const emailSettingUrl = `${this.config.url}/settings/email`;
 
@@ -156,7 +158,7 @@ export class EmailService {
 	@bindThis
 	public async validateEmailForAccount(emailAddress: string): Promise<{
 		available: boolean;
-		reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned';
+		reason: null | 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | 'banned' | 'network' | 'blacklist';
 	}> {
 		const meta = await this.metaService.fetch();
 
@@ -165,14 +167,23 @@ export class EmailService {
 			email: emailAddress,
 		});
 
+		if (exist !== 0) {
+			return {
+				available: false,
+				reason: 'used',
+			};
+		}
+
 		let validated: {
 			valid: boolean,
 			reason?: string | null,
-		};
+		} = { valid: true, reason: null };
 
 		if (meta.enableActiveEmailValidation) {
 			if (meta.enableVerifymailApi && meta.verifymailAuthKey != null) {
 				validated = await this.verifyMail(emailAddress, meta.verifymailAuthKey);
+			} else if (meta.enableTruemailApi && meta.truemailInstance && meta.truemailAuthKey != null) {
+				validated = await this.trueMail(meta.truemailInstance, emailAddress, meta.truemailAuthKey);
 			} else {
 				validated = await validateEmail({
 					email: emailAddress,
@@ -183,25 +194,37 @@ export class EmailService {
 					validateSMTP: false, // 日本だと25ポートが殆どのプロバイダーで塞がれていてタイムアウトになるので
 				});
 			}
-		} else {
-			validated = { valid: true, reason: null };
+		}
+
+		if (!validated.valid) {
+			const formatReason: Record<string, 'format' | 'disposable' | 'mx' | 'smtp' | 'network' | 'blacklist' | undefined> = {
+				regex: 'format',
+				disposable: 'disposable',
+				mx: 'mx',
+				smtp: 'smtp',
+				network: 'network',
+				blacklist: 'blacklist',
+			};
+
+			return {
+				available: false,
+				reason: validated.reason ? formatReason[validated.reason] ?? null : null,
+			};
 		}
 
 		const emailDomain: string = emailAddress.split('@')[1];
 		const isBanned = this.utilityService.isBlockedHost(meta.bannedEmailDomains, emailDomain);
 
-		const available = exist === 0 && validated.valid && !isBanned;
+		if (isBanned) {
+			return {
+				available: false,
+				reason: 'banned',
+			};
+		}
 
 		return {
-			available,
-			reason: available ? null :
-			exist !== 0 ? 'used' :
-			isBanned ? 'banned' :
-			validated.reason === 'regex' ? 'format' :
-			validated.reason === 'disposable' ? 'disposable' :
-			validated.reason === 'mx' ? 'mx' :
-			validated.reason === 'smtp' ? 'smtp' :
-			null,
+			available: true,
+			reason: null,
 		};
 	}
 
@@ -218,7 +241,8 @@ export class EmailService {
 			},
 		});
 
-		const json = (await res.json()) as {
+		const json = (await res.json()) as Partial<{
+			message: string;
 			block: boolean;
 			catch_all: boolean;
 			deliverable_email: boolean;
@@ -233,8 +257,15 @@ export class EmailService {
 			mx_priority: { [key: string]: number };
 			privacy: boolean;
 			related_domains: string[];
-		};
+		}>;
 
+		/* api error: when there is only one `message` attribute in the returned result */
+		if (Object.keys(json).length === 1 && Reflect.has(json, 'message')) {
+			return {
+				valid: false,
+				reason: null,
+			};
+		}
 		if (json.email_address === undefined) {
 			return {
 				valid: false,
@@ -265,4 +296,68 @@ export class EmailService {
 			reason: null,
 		};
 	}
+
+	private async trueMail<T>(truemailInstance: string, emailAddress: string, truemailAuthKey: string): Promise<{
+		valid: boolean;
+		reason: 'used' | 'format' | 'blacklist' | 'mx' | 'smtp' | 'network' | T | null;
+	}> {
+		const endpoint = truemailInstance + '?email=' + emailAddress;
+		try {
+			const res = await this.httpRequestService.send(endpoint, {
+				method: 'POST',
+				headers: {
+					'Content-Type': 'application/json',
+					Accept: 'application/json',
+					Authorization: truemailAuthKey,
+				},
+			});
+
+			const json = (await res.json()) as {
+				email: string;
+				success: boolean;
+				error?: string;
+				errors?: {
+					list_match?: string;
+					regex?: string;
+					mx?: string;
+					smtp?: string;
+				} | null;
+			};
+
+			if (json.email === undefined || json.errors?.regex) {
+				return {
+					valid: false,
+					reason: 'format',
+				};
+			}
+			if (json.errors?.smtp) {
+				return {
+					valid: false,
+					reason: 'smtp',
+				};
+			}
+			if (json.errors?.mx) {
+				return {
+					valid: false,
+					reason: 'mx',
+				};
+			}
+			if (!json.success) {
+				return {
+					valid: false,
+					reason: json.errors?.list_match as T || 'blacklist',
+				};
+			}
+
+			return {
+				valid: true,
+				reason: null,
+			};
+		} catch (error) {
+			return {
+				valid: false,
+				reason: 'network',
+			};
+		}
+	}
 }
diff --git a/packages/backend/src/core/FanoutTimelineEndpointService.ts b/packages/backend/src/core/FanoutTimelineEndpointService.ts
index 6d857d189e..6aa63d7d55 100644
--- a/packages/backend/src/core/FanoutTimelineEndpointService.ts
+++ b/packages/backend/src/core/FanoutTimelineEndpointService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -122,6 +122,8 @@ export class FanoutTimelineEndpointService {
 				filter = (note) => {
 					if (isUserRelated(note, userIdsWhoBlockingMe, ps.ignoreAuthorFromBlock)) return false;
 					if (isUserRelated(note, userIdsWhoMeMuting, ps.ignoreAuthorFromMute)) return false;
+					if (note.mentions.some(mention => userIdsWhoMeMuting.has(mention))) return false;
+					if (isPureRenote(note) && note.renote && note.renote.mentions.some(mention => userIdsWhoMeMuting.has(mention))) return false;
 					if (isPureRenote(note) && isUserRelated(note, userIdsWhoMeMutingRenotes, ps.ignoreAuthorFromMute)) return false;
 					if (isInstanceMuted(note, userMutedInstances)) return false;
 
diff --git a/packages/backend/src/core/FanoutTimelineService.ts b/packages/backend/src/core/FanoutTimelineService.ts
index 9b2678fbcd..f6dabfadcd 100644
--- a/packages/backend/src/core/FanoutTimelineService.ts
+++ b/packages/backend/src/core/FanoutTimelineService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/FeaturedService.ts b/packages/backend/src/core/FeaturedService.ts
index 595383c82c..b3335e38da 100644
--- a/packages/backend/src/core/FeaturedService.ts
+++ b/packages/backend/src/core/FeaturedService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/FederatedInstanceService.ts b/packages/backend/src/core/FederatedInstanceService.ts
index e41f010e48..66db2067d9 100644
--- a/packages/backend/src/core/FederatedInstanceService.ts
+++ b/packages/backend/src/core/FederatedInstanceService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts
index 682acef15b..bc270bd28f 100644
--- a/packages/backend/src/core/FetchInstanceMetadataService.ts
+++ b/packages/backend/src/core/FetchInstanceMetadataService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/FileInfoService.ts b/packages/backend/src/core/FileInfoService.ts
index 803f6908de..2d3312f247 100644
--- a/packages/backend/src/core/FileInfoService.ts
+++ b/packages/backend/src/core/FileInfoService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -11,6 +11,7 @@ import * as fileType from 'file-type';
 import isSvg from 'is-svg';
 import probeImageSize from 'probe-image-size';
 import sharp from 'sharp';
+import { sharpBmp } from '@misskey-dev/sharp-read-bmp';
 import { encode } from 'blurhash';
 import { bindThis } from '@/decorators.js';
 
@@ -110,7 +111,7 @@ export class FileInfoService {
 			'image/avif',
 			'image/svg+xml',
 		].includes(type.mime)) {
-			blurhash = await this.getBlurhash(path).catch(e => {
+			blurhash = await this.getBlurhash(path, type.mime).catch(e => {
 				warnings.push(`getBlurhash failed: ${e}`);
 				return undefined;
 			});
@@ -237,9 +238,9 @@ export class FileInfoService {
 	 * Calculate average color of image
 	 */
 	@bindThis
-	private getBlurhash(path: string): Promise<string> {
-		return new Promise((resolve, reject) => {
-			sharp(path)
+	private getBlurhash(path: string, type: string): Promise<string> {
+		return new Promise(async (resolve, reject) => {
+			(await sharpBmp(path, type))
 				.raw()
 				.ensureAlpha()
 				.resize(64, 64, { fit: 'inside' })
diff --git a/packages/backend/src/core/GlobalEventService.ts b/packages/backend/src/core/GlobalEventService.ts
index 95a4eba742..22871adb16 100644
--- a/packages/backend/src/core/GlobalEventService.ts
+++ b/packages/backend/src/core/GlobalEventService.ts
@@ -1,10 +1,11 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import { Inject, Injectable } from '@nestjs/common';
 import * as Redis from 'ioredis';
+import * as Reversi from 'misskey-reversi';
 import type { MiChannel } from '@/models/Channel.js';
 import type { MiUser } from '@/models/User.js';
 import type { MiUserProfile } from '@/models/UserProfile.js';
@@ -18,7 +19,7 @@ import type { MiSignin } from '@/models/Signin.js';
 import type { MiPage } from '@/models/Page.js';
 import type { MiWebhook } from '@/models/Webhook.js';
 import type { MiMeta } from '@/models/Meta.js';
-import { MiAvatarDecoration, MiRole, MiRoleAssignment } from '@/models/_.js';
+import { MiAvatarDecoration, MiReversiGame, MiRole, MiRoleAssignment } from '@/models/_.js';
 import type { Packed } from '@/misc/json-schema.js';
 import { DI } from '@/di-symbols.js';
 import type { Config } from '@/config.js';
@@ -53,21 +54,22 @@ export interface MainEventTypes {
 	reply: Packed<'Note'>;
 	renote: Packed<'Note'>;
 	follow: Packed<'UserDetailedNotMe'>;
-	followed: Packed<'User'>;
-	unfollow: Packed<'User'>;
-	meUpdated: Packed<'User'>;
+	followed: Packed<'UserLite'>;
+	unfollow: Packed<'UserDetailedNotMe'>;
+	meUpdated: Packed<'MeDetailed'>;
 	pageEvent: {
 		pageId: MiPage['id'];
 		event: string;
 		var: any;
 		userId: MiUser['id'];
-		user: Packed<'User'>;
+		user: Packed<'UserDetailed'>;
 	};
 	urlUploadFinished: {
 		marker?: string | null;
 		file: Packed<'DriveFile'>;
 	};
 	readAllNotifications: undefined;
+	notificationFlushed: undefined;
 	unreadNotification: Packed<'Notification'>;
 	unreadMention: MiNote['id'];
 	readAllUnreadMentions: undefined;
@@ -91,10 +93,11 @@ export interface MainEventTypes {
 	};
 	driveFileCreated: Packed<'DriveFile'>;
 	readAntenna: MiAntenna;
-	receiveFollowRequest: Packed<'User'>;
+	receiveFollowRequest: Packed<'UserLite'>;
 	announcementCreated: {
 		announcement: Packed<'Announcement'>;
 	};
+	edited: Packed<'Note'>;
 }
 
 export interface DriveEventTypes {
@@ -142,8 +145,8 @@ type NoteStreamEventTypes = {
 };
 
 export interface UserListEventTypes {
-	userAdded: Packed<'User'>;
-	userRemoved: Packed<'User'>;
+	userAdded: Packed<'UserLite'>;
+	userRemoved: Packed<'UserLite'>;
 }
 
 export interface AntennaEventTypes {
@@ -162,6 +165,38 @@ export interface AdminEventTypes {
 		comment: string;
 	};
 }
+
+export interface ReversiEventTypes {
+	matched: {
+		game: Packed<'ReversiGameDetailed'>;
+	};
+	invited: {
+		user: Packed<'User'>;
+	};
+}
+
+export interface ReversiGameEventTypes {
+	changeReadyStates: {
+		user1: boolean;
+		user2: boolean;
+	};
+	updateSettings: {
+		userId: MiUser['id'];
+		key: string;
+		value: any;
+	};
+	log: Reversi.Serializer.Log & { id: string | null };
+	started: {
+		game: Packed<'ReversiGameDetailed'>;
+	};
+	ended: {
+		winnerId: MiUser['id'] | null;
+		game: Packed<'ReversiGameDetailed'>;
+	};
+	canceled: {
+		userId: MiUser['id'];
+	};
+}
 //#endregion
 
 // 辞書(interface or type)から{ type, body }ユニオンを定義
@@ -179,8 +214,10 @@ type SerializedAll<T> = {
 
 export interface InternalEventTypes {
 	userChangeSuspendedState: { id: MiUser['id']; isSuspended: MiUser['isSuspended']; };
+	userChangeDeletedState: { id: MiUser['id']; isDeleted: MiUser['isDeleted']; };
 	userTokenRegenerated: { id: MiUser['id']; oldToken: string; newToken: string; };
 	remoteUserUpdated: { id: MiUser['id']; };
+	localUserUpdated: { id: MiUser['id']; };
 	follow: { followerId: MiUser['id']; followeeId: MiUser['id']; };
 	unfollow: { followerId: MiUser['id']; followeeId: MiUser['id']; };
 	blockingCreated: { blockerId: MiUser['id']; blockeeId: MiUser['id']; };
@@ -252,6 +289,14 @@ export type GlobalEvents = {
 		name: 'notesStream';
 		payload: Serialized<Packed<'Note'>>;
 	};
+	reversi: {
+		name: `reversiStream:${MiUser['id']}`;
+		payload: EventUnionFromDictionary<SerializedAll<ReversiEventTypes>>;
+	};
+	reversiGame: {
+		name: `reversiGameStream:${MiReversiGame['id']}`;
+		payload: EventUnionFromDictionary<SerializedAll<ReversiGameEventTypes>>;
+	};
 };
 
 // API event definitions
@@ -341,4 +386,14 @@ export class GlobalEventService {
 	public publishAdminStream<K extends keyof AdminEventTypes>(userId: MiUser['id'], type: K, value?: AdminEventTypes[K]): void {
 		this.publish(`adminStream:${userId}`, type, typeof value === 'undefined' ? null : value);
 	}
+
+	@bindThis
+	public publishReversiStream<K extends keyof ReversiEventTypes>(userId: MiUser['id'], type: K, value?: ReversiEventTypes[K]): void {
+		this.publish(`reversiStream:${userId}`, type, typeof value === 'undefined' ? null : value);
+	}
+
+	@bindThis
+	public publishReversiGameStream<K extends keyof ReversiGameEventTypes>(gameId: MiReversiGame['id'], type: K, value?: ReversiGameEventTypes[K]): void {
+		this.publish(`reversiGameStream:${gameId}`, type, typeof value === 'undefined' ? null : value);
+	}
 }
diff --git a/packages/backend/src/core/HashtagService.ts b/packages/backend/src/core/HashtagService.ts
index 5a2417c9cd..eb192ee6da 100644
--- a/packages/backend/src/core/HashtagService.ts
+++ b/packages/backend/src/core/HashtagService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -163,7 +163,7 @@ export class HashtagService {
 		const instance = await this.metaService.fetch();
 		const hiddenTags = instance.hiddenTags.map(t => normalizeForSearch(t));
 		if (hiddenTags.includes(hashtag)) return;
-		if (this.utilityService.isSensitiveWordIncluded(hashtag, instance.sensitiveWords)) return;
+		if (this.utilityService.isKeyWordIncluded(hashtag, instance.sensitiveWords)) return;
 
 		// YYYYMMDDHHmm (10分間隔)
 		const now = new Date();
diff --git a/packages/backend/src/core/HttpRequestService.ts b/packages/backend/src/core/HttpRequestService.ts
index 1352e137ce..7f3cac7c58 100644
--- a/packages/backend/src/core/HttpRequestService.ts
+++ b/packages/backend/src/core/HttpRequestService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/IdService.ts b/packages/backend/src/core/IdService.ts
index 43e72d2d7b..10df6ef266 100644
--- a/packages/backend/src/core/IdService.ts
+++ b/packages/backend/src/core/IdService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/ImageProcessingService.ts b/packages/backend/src/core/ImageProcessingService.ts
index 8e800eb8f5..6f978b34c8 100644
--- a/packages/backend/src/core/ImageProcessingService.ts
+++ b/packages/backend/src/core/ImageProcessingService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/InstanceActorService.ts b/packages/backend/src/core/InstanceActorService.ts
index b40fd46291..22c47297a3 100644
--- a/packages/backend/src/core/InstanceActorService.ts
+++ b/packages/backend/src/core/InstanceActorService.ts
@@ -1,10 +1,10 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import { Inject, Injectable } from '@nestjs/common';
-import { IsNull } from 'typeorm';
+import { IsNull, Not } from 'typeorm';
 import type { MiLocalUser } from '@/models/User.js';
 import type { UsersRepository } from '@/models/_.js';
 import { MemorySingleCache } from '@/misc/cache.js';
@@ -27,6 +27,14 @@ export class InstanceActorService {
 		this.cache = new MemorySingleCache<MiLocalUser>(Infinity);
 	}
 
+	@bindThis
+	public async realLocalUsersPresent(): Promise<boolean> {
+		return await this.usersRepository.existsBy({
+			host: IsNull(),
+			username: Not(ACTOR_USERNAME),
+		});
+	}
+
 	@bindThis
 	public async getInstanceActor(): Promise<MiLocalUser> {
 		const cached = this.cache.get();
diff --git a/packages/backend/src/core/InternalStorageService.ts b/packages/backend/src/core/InternalStorageService.ts
index 22129bb348..4fb8a93e49 100644
--- a/packages/backend/src/core/InternalStorageService.ts
+++ b/packages/backend/src/core/InternalStorageService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/LoggerService.ts b/packages/backend/src/core/LoggerService.ts
index 46b000ee63..96d9b09992 100644
--- a/packages/backend/src/core/LoggerService.ts
+++ b/packages/backend/src/core/LoggerService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/MetaService.ts b/packages/backend/src/core/MetaService.ts
index 80e8020961..ec630f804e 100644
--- a/packages/backend/src/core/MetaService.ts
+++ b/packages/backend/src/core/MetaService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -51,7 +51,10 @@ export class MetaService implements OnApplicationShutdown {
 			const { type, body } = obj.message as GlobalEvents['internal']['payload'];
 			switch (type) {
 				case 'metaUpdated': {
-					this.cache = body;
+					this.cache = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
+						...body,
+						proxyAccount: null, // joinなカラムは通常取ってこないので
+					};
 					break;
 				}
 				default:
diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts
index 60bf8b3c2a..b7c9064cef 100644
--- a/packages/backend/src/core/MfmService.ts
+++ b/packages/backend/src/core/MfmService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -13,7 +13,7 @@ import { intersperse } from '@/misc/prelude/array.js';
 import type { IMentionedRemoteUsers } from '@/models/Note.js';
 import { bindThis } from '@/decorators.js';
 import * as TreeAdapter from '../../node_modules/parse5/dist/tree-adapters/default.js';
-import type * as mfm from '@sharkey/sfm-js';
+import type * as mfm from '@transfem-org/sfm-js';
 
 const treeAdapter = TreeAdapter.defaultTreeAdapter;
 
@@ -419,6 +419,10 @@ export class MfmService {
 			},
 
 			text: (node) => {
+				if (!node.props.text.match(/[\r\n]/)) {
+					return doc.createTextNode(node.props.text);
+				}
+
 				const el = doc.createElement('span');
 				const nodes = node.props.text.split(/\r\n|\r|\n/).map(x => doc.createTextNode(x));
 
@@ -475,7 +479,7 @@ export class MfmService {
 
 		const handlers: {
             [K in mfm.MfmNode['type']]: (node: mfm.NodeType<K>) => any;
-        } = {
+    } = {
 			async bold(node) {
 				const el = doc.createElement('span');
 				el.textContent = '**';
@@ -643,8 +647,8 @@ export class MfmService {
 				await appendChildren(node.children, el);
 				return el;
 			},
-        };
-		
+		};
+
 		await appendChildren(nodes, doc.body);
 
 		if (quoteUri !== null) {
diff --git a/packages/backend/src/core/ModerationLogService.ts b/packages/backend/src/core/ModerationLogService.ts
index 8b78d02047..6c155c9a62 100644
--- a/packages/backend/src/core/ModerationLogService.ts
+++ b/packages/backend/src/core/ModerationLogService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 3bc4a29b99..b985846f1c 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -1,10 +1,10 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import { setImmediate } from 'node:timers/promises';
-import * as mfm from '@sharkey/sfm-js';
+import * as mfm from '@transfem-org/sfm-js';
 import { In, DataSource, IsNull, LessThan } from 'typeorm';
 import * as Redis from 'ioredis';
 import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
@@ -57,7 +57,12 @@ import { FeaturedService } from '@/core/FeaturedService.js';
 import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
 import { UtilityService } from '@/core/UtilityService.js';
 import { UserBlockingService } from '@/core/UserBlockingService.js';
+import { CacheService } from '@/core/CacheService.js';
 import { isReply } from '@/misc/is-reply.js';
+import { trackPromise } from '@/misc/promise-tracker.js';
+import { isUserRelated } from '@/misc/is-user-related.js';
+import { isNotNull } from '@/misc/is-not-null.js';
+import { IdentifiableError } from '@/misc/identifiable-error.js';
 
 type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
 
@@ -216,6 +221,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 		private instanceChart: InstanceChart,
 		private utilityService: UtilityService,
 		private userBlockingService: UserBlockingService,
+		private cacheService: CacheService,
 	) { }
 
 	@bindThis
@@ -253,7 +259,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 
 		if (data.visibility === 'public' && data.channel == null) {
 			const sensitiveWords = meta.sensitiveWords;
-			if (this.utilityService.isSensitiveWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
+			if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
 				data.visibility = 'home';
 			} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
 				data.visibility = 'home';
@@ -324,6 +330,9 @@ export class NoteCreateService implements OnApplicationShutdown {
 				data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH);
 			}
 			data.text = data.text.trim();
+			if (data.text === '') {
+				data.text = null;
+			}
 		} else {
 			data.text = null;
 		}
@@ -377,6 +386,10 @@ export class NoteCreateService implements OnApplicationShutdown {
 			});
 		}
 
+		if (mentionedUsers.length > 0 && mentionedUsers.length > (await this.roleService.getUserPolicies(user.id)).mentionLimit) {
+			throw new IdentifiableError('9f466dab-c856-48cd-9e65-ff90ff750580', 'Note contains too many mentions');
+		}
+
 		const note = await this.insertNote(user, data, tags, emojis, mentionedUsers);
 
 		setImmediate('post created', { signal: this.#shutdownController.signal }).then(
@@ -422,13 +435,23 @@ export class NoteCreateService implements OnApplicationShutdown {
 
 		if (data.visibility === 'public' && data.channel == null) {
 			const sensitiveWords = meta.sensitiveWords;
-			if (this.utilityService.isSensitiveWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
+			if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
 				data.visibility = 'home';
 			} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
 				data.visibility = 'home';
 			}
 		}
 
+		const hasProhibitedWords = await this.checkProhibitedWordsContain({
+			cw: data.cw,
+			text: data.text,
+			pollChoices: data.poll?.choices,
+		}, meta.prohibitedWords);
+
+		if (hasProhibitedWords) {
+			throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words');
+		}
+
 		const inSilencedInstance = this.utilityService.isSilencedHost(meta.silencedHosts, user.host);
 
 		if (data.visibility === 'public' && inSilencedInstance && user.host !== null) {
@@ -493,6 +516,9 @@ export class NoteCreateService implements OnApplicationShutdown {
 				data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH);
 			}
 			data.text = data.text.trim();
+			if (data.text === '') {
+				data.text = null;
+			}
 		} else {
 			data.text = null;
 		}
@@ -538,6 +564,10 @@ export class NoteCreateService implements OnApplicationShutdown {
 			}
 		}
 
+		if (mentionedUsers.length > 0 && mentionedUsers.length > (await this.roleService.getUserPolicies(user.id)).mentionLimit) {
+			throw new IdentifiableError('9f466dab-c856-48cd-9e65-ff90ff750580', 'Note contains too many mentions');
+		}
+
 		const note = await this.insertNote(user, data, tags, emojis, mentionedUsers);
 
 		setImmediate('post created', { signal: this.#shutdownController.signal }).then(
@@ -785,14 +815,22 @@ export class NoteCreateService implements OnApplicationShutdown {
 				});
 				// 通知
 				if (data.reply.userHost === null) {
-					const isThreadMuted = await this.noteThreadMutingsRepository.exist({
+					const isThreadMuted = await this.noteThreadMutingsRepository.exists({
 						where: {
 							userId: data.reply.userId,
 							threadId: data.reply.threadId ?? data.reply.id,
 						},
 					});
 
-					if (!isThreadMuted) {
+					const [
+						userIdsWhoMeMuting,
+					] = data.reply.userId ? await Promise.all([
+						this.cacheService.userMutingsCache.fetch(data.reply.userId),
+					]) : [new Set<string>()];
+
+					const muted = isUserRelated(note, userIdsWhoMeMuting);
+
+					if (!isThreadMuted && !muted) {
 						nm.push(data.reply.userId, 'reply');
 						this.globalEventService.publishMainStream(data.reply.userId, 'reply', noteObj);
 
@@ -812,7 +850,24 @@ export class NoteCreateService implements OnApplicationShutdown {
 
 				// Notify
 				if (data.renote.userHost === null) {
-					nm.push(data.renote.userId, type);
+					const isThreadMuted = await this.noteThreadMutingsRepository.exists({
+						where: {
+							userId: data.renote.userId,
+							threadId: data.renote.threadId ?? data.renote.id,
+						},
+					});
+
+					const [
+						userIdsWhoMeMuting,
+					] = data.renote.userId ? await Promise.all([
+						this.cacheService.userMutingsCache.fetch(data.renote.userId),
+					]) : [new Set<string>()];
+
+					const muted = isUserRelated(note, userIdsWhoMeMuting);
+
+					if (!isThreadMuted && !muted) {
+						nm.push(data.renote.userId, type);
+					}
 				}
 
 				// Publish event
@@ -862,7 +917,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 						this.relayService.deliverToRelays(user, noteActivity);
 					}
 
-					dm.execute();
+					trackPromise(dm.execute());
 				})();
 			}
 			//#endregion
@@ -1022,14 +1077,22 @@ export class NoteCreateService implements OnApplicationShutdown {
 	@bindThis
 	private async createMentionedEvents(mentionedUsers: MinimumUser[], note: MiNote, nm: NotificationManager) {
 		for (const u of mentionedUsers.filter(u => this.userEntityService.isLocalUser(u))) {
-			const isThreadMuted = await this.noteThreadMutingsRepository.exist({
+			const isThreadMuted = await this.noteThreadMutingsRepository.exists({
 				where: {
 					userId: u.id,
 					threadId: note.threadId ?? note.id,
 				},
 			});
 
-			if (isThreadMuted) {
+			const [
+				userIdsWhoMeMuting,
+			] = u.id ? await Promise.all([
+				this.cacheService.userMutingsCache.fetch(u.id),
+			]) : [new Set<string>()];
+
+			const muted = isUserRelated(note, userIdsWhoMeMuting);
+
+			if (isThreadMuted || muted) {
 				continue;
 			}
 
@@ -1092,7 +1155,7 @@ export class NoteCreateService implements OnApplicationShutdown {
 		const mentions = extractMentions(tokens);
 		let mentionedUsers = (await Promise.all(mentions.map(m =>
 			this.remoteUserResolveService.resolveUser(m.username, m.host ?? user.host).catch(() => null),
-		))).filter(x => x != null) as MiUser[];
+		))).filter(isNotNull);
 
 		// Drop duplicate users
 		mentionedUsers = mentionedUsers.filter((u, i, self) =>
@@ -1266,6 +1329,23 @@ export class NoteCreateService implements OnApplicationShutdown {
 		}
 	}
 
+	public async checkProhibitedWordsContain(content: Parameters<UtilityService['concatNoteContentsForKeyWordCheck']>[0], prohibitedWords?: string[]) {
+		if (prohibitedWords == null) {
+			prohibitedWords = (await this.metaService.fetch()).prohibitedWords;
+		}
+
+		if (
+			this.utilityService.isKeyWordIncluded(
+				this.utilityService.concatNoteContentsForKeyWordCheck(content),
+				prohibitedWords,
+			)
+		) {
+			return true;
+		}
+
+		return false;
+	}
+
 	@bindThis
 	public dispose(): void {
 		this.#shutdownController.abort();
diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts
index 99d9d9db7e..471ade92c7 100644
--- a/packages/backend/src/core/NoteDeleteService.ts
+++ b/packages/backend/src/core/NoteDeleteService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts
index dae3f485ff..72fc01ae3b 100644
--- a/packages/backend/src/core/NoteEditService.ts
+++ b/packages/backend/src/core/NoteEditService.ts
@@ -4,7 +4,7 @@
  */
 
 import { setImmediate } from 'node:timers/promises';
-import * as mfm from '@sharkey/sfm-js';
+import * as mfm from '@transfem-org/sfm-js';
 import { DataSource, In, IsNull, LessThan } from 'typeorm';
 import * as Redis from 'ioredis';
 import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
@@ -46,8 +46,15 @@ import { MetaService } from '@/core/MetaService.js';
 import { SearchService } from '@/core/SearchService.js';
 import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
 import { UtilityService } from '@/core/UtilityService.js';
+import { UserBlockingService } from '@/core/UserBlockingService.js';
+import { CacheService } from '@/core/CacheService.js';
+import { isReply } from '@/misc/is-reply.js';
+import { trackPromise } from '@/misc/promise-tracker.js';
+import { isUserRelated } from '@/misc/is-user-related.js';
+import { isNotNull } from '@/misc/is-not-null.js';
+import { IdentifiableError } from '@/misc/identifiable-error.js';
 
-type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
+type NotificationType = 'reply' | 'renote' | 'quote' | 'mention' | 'edited';
 
 class NotificationManager {
 	private notifier: { id: MiUser['id']; };
@@ -206,6 +213,8 @@ export class NoteEditService implements OnApplicationShutdown {
 		private activeUsersChart: ActiveUsersChart,
 		private instanceChart: InstanceChart,
 		private utilityService: UtilityService,
+		private userBlockingService: UserBlockingService,
+		private cacheService: CacheService,
 	) { }
 
 	@bindThis
@@ -222,7 +231,7 @@ export class NoteEditService implements OnApplicationShutdown {
 
 		const oldnote = await this.notesRepository.findOneBy({
 			id: editid,
-		});	
+		});
 
 		if (oldnote == null) {
 			throw new Error('no such note');
@@ -232,6 +241,13 @@ export class NoteEditService implements OnApplicationShutdown {
 			throw new Error('not the author');
 		}
 
+		// we never want to change the replyId, so fetch the original "parent"
+		if (oldnote.replyId) {
+			data.reply = await this.notesRepository.findOneBy({ id: oldnote.replyId });
+		} else {
+			data.reply = undefined;
+		}
+
 		// チャンネル外にリプライしたら対象のスコープに合わせる
 		// (クライアントサイドでやっても良い処理だと思うけどとりあえずサーバーサイドで)
 		if (data.reply && data.channel && data.reply.channelId !== data.channel.id) {
@@ -255,16 +271,28 @@ export class NoteEditService implements OnApplicationShutdown {
 		if (data.channel != null) data.localOnly = true;
 		if (data.updatedAt == null) data.updatedAt = new Date();
 
+		const meta = await this.metaService.fetch();
+
 		if (data.visibility === 'public' && data.channel == null) {
-			const sensitiveWords = (await this.metaService.fetch()).sensitiveWords;
-			if (this.isSensitive(data, sensitiveWords)) {
+			const sensitiveWords = meta.sensitiveWords;
+			if (this.utilityService.isKeyWordIncluded(data.cw ?? data.text ?? '', sensitiveWords)) {
 				data.visibility = 'home';
 			} else if ((await this.roleService.getUserPolicies(user.id)).canPublicNote === false) {
 				data.visibility = 'home';
 			}
 		}
 
-		const inSilencedInstance = this.utilityService.isSilencedHost((await this.metaService.fetch()).silencedHosts, user.host);
+		const hasProhibitedWords = await this.checkProhibitedWordsContain({
+			cw: data.cw,
+			text: data.text,
+			pollChoices: data.poll?.choices,
+		}, meta.prohibitedWords);
+
+		if (hasProhibitedWords) {
+			throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words');
+		}
+
+		const inSilencedInstance = this.utilityService.isSilencedHost((meta).silencedHosts, user.host);
 
 		if (data.visibility === 'public' && inSilencedInstance && user.host !== null) {
 			data.visibility = 'home';
@@ -296,6 +324,18 @@ export class NoteEditService implements OnApplicationShutdown {
 			}
 		}
 
+		// Check blocking
+		if (data.renote && !this.isQuote(data)) {
+			if (data.renote.userHost === null) {
+				if (data.renote.userId !== user.id) {
+					const blocked = await this.userBlockingService.checkBlocked(data.renote.userId, user.id);
+					if (blocked) {
+						throw new Error('blocked');
+					}
+				}
+			}
+		}
+
 		// 返信対象がpublicではないならhomeにする
 		if (data.reply && data.reply.visibility !== 'public' && data.visibility === 'public') {
 			data.visibility = 'home';
@@ -316,6 +356,9 @@ export class NoteEditService implements OnApplicationShutdown {
 				data.text = data.text.slice(0, DB_MAX_NOTE_TEXT_LENGTH);
 			}
 			data.text = data.text.trim();
+			if (data.text === '') {
+				data.text = null;
+			}
 		} else {
 			data.text = null;
 		}
@@ -361,6 +404,18 @@ export class NoteEditService implements OnApplicationShutdown {
 			}
 		}
 
+		if (user.host && !data.cw) {
+			await this.federatedInstanceService.fetch(user.host).then(async i => {
+				if (i.isNSFW) {
+					data.cw = 'Instance is marked as NSFW';
+				}
+			});
+		}
+
+		if (mentionedUsers.length > 0 && mentionedUsers.length > (await this.roleService.getUserPolicies(user.id)).mentionLimit) {
+			throw new IdentifiableError('9f466dab-c856-48cd-9e65-ff90ff750580', 'Note contains too many mentions');
+		}
+
 		const update: Partial<MiNote> = {};
 		if (data.text !== oldnote.text) {
 			update.text = data.text;
@@ -397,7 +452,7 @@ export class NoteEditService implements OnApplicationShutdown {
 				id: oldnote.id,
 				updatedAt: data.updatedAt ? data.updatedAt : new Date(),
 				fileIds: data.files ? data.files.map(file => file.id) : [],
-				replyId: data.reply ? data.reply.id : null,
+				replyId: oldnote.replyId,
 				renoteId: data.renote ? data.renote.id : null,
 				channelId: data.channel ? data.channel.id : null,
 				threadId: data.reply
@@ -548,7 +603,7 @@ export class NoteEditService implements OnApplicationShutdown {
 			}
 
 			// Pack the note
-			const noteObj = await this.noteEntityService.pack(note, null, { skipHide: true });
+			const noteObj = await this.noteEntityService.pack(note, null, { skipHide: true, withReactionAndUserPairCache: true });
 			if (data.poll != null) {
 				this.globalEventService.publishNoteStream(note.id, 'updated', {
 					cw: note.cw,
@@ -557,7 +612,7 @@ export class NoteEditService implements OnApplicationShutdown {
 			} else {
 				this.globalEventService.publishNoteStream(note.id, 'updated', {
 					cw: note.cw,
-					text: note.text!
+					text: note.text!,
 				});
 			}
 
@@ -574,26 +629,34 @@ export class NoteEditService implements OnApplicationShutdown {
 
 			const nm = new NotificationManager(this.mutingsRepository, this.notificationService, user, note);
 
-			await this.createMentionedEvents(mentionedUsers, note, nm);
+			//await this.createMentionedEvents(mentionedUsers, note, nm);
 
 			// If has in reply to note
 			if (data.reply) {
 				// 通知
 				if (data.reply.userHost === null) {
-					const isThreadMuted = await this.noteThreadMutingsRepository.exist({
+					const isThreadMuted = await this.noteThreadMutingsRepository.exists({
 						where: {
 							userId: data.reply.userId,
 							threadId: data.reply.threadId ?? data.reply.id,
 						},
 					});
 
-					if (!isThreadMuted) {
-						nm.push(data.reply.userId, 'reply');
-						this.globalEventService.publishMainStream(data.reply.userId, 'reply', noteObj);
+					const [
+						userIdsWhoMeMuting,
+					] = data.reply.userId ? await Promise.all([
+						this.cacheService.userMutingsCache.fetch(data.reply.userId),
+					]) : [new Set<string>()];
 
-						const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === data.reply!.userId && x.on.includes('reply'));
+					const muted = isUserRelated(note, userIdsWhoMeMuting);
+
+					if (!isThreadMuted && !muted) {
+						nm.push(data.reply.userId, 'edited');
+						this.globalEventService.publishMainStream(data.reply.userId, 'edited', noteObj);
+
+						const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === data.reply!.userId && x.on.includes('edited'));
 						for (const webhook of webhooks) {
-							this.queueService.webhookDeliver(webhook, 'reply', {
+							this.queueService.webhookDeliver(webhook, 'edited', {
 								note: noteObj,
 							});
 						}
@@ -601,28 +664,6 @@ export class NoteEditService implements OnApplicationShutdown {
 				}
 			}
 
-			// If it is renote
-			if (data.renote) {
-				const type = data.text ? 'quote' : 'renote';
-
-				// Notify
-				if (data.renote.userHost === null) {
-					nm.push(data.renote.userId, type);
-				}
-
-				// Publish event
-				if ((user.id !== data.renote.userId) && data.renote.userHost === null) {
-					this.globalEventService.publishMainStream(data.renote.userId, 'renote', noteObj);
-
-					const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === data.renote!.userId && x.on.includes('renote'));
-					for (const webhook of webhooks) {
-						this.queueService.webhookDeliver(webhook, 'renote', {
-							note: noteObj,
-						});
-					}
-				}
-			}
-
 			nm.notify();
 
 			//#region AP deliver
@@ -657,7 +698,7 @@ export class NoteEditService implements OnApplicationShutdown {
 						this.relayService.deliverToRelays(user, noteActivity);
 					}
 
-					dm.execute();
+					trackPromise(dm.execute());
 				})();
 			}
 			//#endregion
@@ -686,41 +727,30 @@ export class NoteEditService implements OnApplicationShutdown {
 	}
 
 	@bindThis
-	private isSensitive(note: Option, sensitiveWord: string[]): boolean {
-		if (sensitiveWord.length > 0) {
-			const text = note.cw ?? note.text ?? '';
-			if (text === '') return false;
-			const matched = sensitiveWord.some(filter => {
-				// represents RegExp
-				const regexp = filter.match(/^\/(.+)\/(.*)$/);
-				// This should never happen due to input sanitisation.
-				if (!regexp) {
-					const words = filter.split(' ');
-					return words.every(keyword => text.includes(keyword));
-				}
-				try {
-					return new RE2(regexp[1], regexp[2]).test(text);
-				} catch (err) {
-					// This should never happen due to input sanitisation.
-					return false;
-				}
-			});
-			if (matched) return true;
-		}
-		return false;
+	private isQuote(note: Option): note is Option & { renote: MiNote } {
+		// sync with misc/is-quote.ts
+		return !!note.renote && (!!note.text || !!note.cw || (!!note.files && !!note.files.length) || !!note.poll);
 	}
 
 	@bindThis
 	private async createMentionedEvents(mentionedUsers: MinimumUser[], note: MiNote, nm: NotificationManager) {
 		for (const u of mentionedUsers.filter(u => this.userEntityService.isLocalUser(u))) {
-			const isThreadMuted = await this.noteThreadMutingsRepository.exist({
+			const isThreadMuted = await this.noteThreadMutingsRepository.exists({
 				where: {
 					userId: u.id,
 					threadId: note.threadId ?? note.id,
 				},
 			});
 
-			if (isThreadMuted) {
+			const [
+				userIdsWhoMeMuting,
+			] = u.id ? await Promise.all([
+				this.cacheService.userMutingsCache.fetch(u.id),
+			]) : [new Set<string>()];
+
+			const muted = isUserRelated(note, userIdsWhoMeMuting);
+
+			if (isThreadMuted || muted) {
 				continue;
 			}
 
@@ -728,17 +758,17 @@ export class NoteEditService implements OnApplicationShutdown {
 				detail: true,
 			});
 
-			this.globalEventService.publishMainStream(u.id, 'mention', detailPackedNote);
+			this.globalEventService.publishMainStream(u.id, 'edited', detailPackedNote);
 
-			const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === u.id && x.on.includes('mention'));
+			const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === u.id && x.on.includes('edited'));
 			for (const webhook of webhooks) {
-				this.queueService.webhookDeliver(webhook, 'mention', {
+				this.queueService.webhookDeliver(webhook, 'edited', {
 					note: detailPackedNote,
 				});
 			}
 
 			// Create notification
-			nm.push(u.id, 'mention');
+			nm.push(u.id, 'edited');
 		}
 	}
 
@@ -748,7 +778,7 @@ export class NoteEditService implements OnApplicationShutdown {
 		const user = await this.usersRepository.findOneBy({ id: note.userId });
 		if (user == null) throw new Error('user not found');
 
-		const content = data.renote && data.text == null && data.poll == null && (data.files == null || data.files.length === 0)
+		const content = data.renote && !this.isQuote(data)
 			? this.apRendererService.renderAnnounce(data.renote.uri ? data.renote.uri : `${this.config.url}/notes/${data.renote.id}`, note)
 			: this.apRendererService.renderUpdate(await this.apRendererService.renderUpNote(note, false), user);
 
@@ -769,7 +799,7 @@ export class NoteEditService implements OnApplicationShutdown {
 		const mentions = extractMentions(tokens);
 		let mentionedUsers = (await Promise.all(mentions.map(m =>
 			this.remoteUserResolveService.resolveUser(m.username, m.host ?? user.host).catch(() => null),
-		))).filter(x => x != null) as MiUser[];
+		))).filter(isNotNull) as MiUser[];
 
 		// Drop duplicate users
 		mentionedUsers = mentionedUsers.filter((u, i, self) =>
@@ -782,6 +812,7 @@ export class NoteEditService implements OnApplicationShutdown {
 	@bindThis
 	private async pushToTl(note: MiNote, user: { id: MiUser['id']; host: MiUser['host']; }) {
 		const meta = await this.metaService.fetch();
+		if (!meta.enableFanoutTimeline) return;
 
 		const r = this.redisForTimelines.pipeline();
 
@@ -825,7 +856,7 @@ export class NoteEditService implements OnApplicationShutdown {
 
 			if (note.visibility === 'followers') {
 				// TODO: 重そうだから何とかしたい Set 使う?
-				userListMemberships = userListMemberships.filter(x => followings.some(f => f.followerId === x.userListUserId));
+				userListMemberships = userListMemberships.filter(x => x.userListUserId === user.id || followings.some(f => f.followerId === x.userListUserId));
 			}
 
 			// TODO: あまりにも数が多いと redisPipeline.exec に失敗する(理由は不明)ため、3万件程度を目安に分割して実行するようにする
@@ -834,7 +865,7 @@ export class NoteEditService implements OnApplicationShutdown {
 				if (note.visibility === 'specified' && !note.visibleUserIds.some(v => v === following.followerId)) continue;
 
 				// 「自分自身への返信 or そのフォロワーへの返信」のどちらでもない場合
-				if (note.replyId && !(note.replyUserId === note.userId || note.replyUserId === following.followerId)) {
+				if (isReply(note, following.followerId)) {
 					if (!following.withReplies) continue;
 				}
 
@@ -848,11 +879,12 @@ export class NoteEditService implements OnApplicationShutdown {
 				// ダイレクトのとき、そのリストが対象外のユーザーの場合
 				if (
 					note.visibility === 'specified' &&
+					note.userId !== userListMembership.userListUserId &&
 					!note.visibleUserIds.some(v => v === userListMembership.userListUserId)
 				) continue;
 
 				// 「自分自身への返信 or そのリストの作成者への返信」のどちらでもない場合
-				if (note.replyId && !(note.replyUserId === note.userId || note.replyUserId === userListMembership.userListUserId)) {
+				if (isReply(note, userListMembership.userListUserId)) {
 					if (!userListMembership.withReplies) continue;
 				}
 
@@ -870,11 +902,14 @@ export class NoteEditService implements OnApplicationShutdown {
 			}
 
 			// 自分自身以外への返信
-			if (note.replyId && note.replyUserId !== note.userId) {
+			if (isReply(note)) {
 				this.fanoutTimelineService.push(`userTimelineWithReplies:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
 
 				if (note.visibility === 'public' && note.userHost == null) {
 					this.fanoutTimelineService.push('localTimelineWithReplies', note.id, 300, r);
+					if (note.replyUserHost == null) {
+						this.fanoutTimelineService.push(`localTimelineWithReplyTo:${note.replyUserId}`, note.id, 300 / 10, r);
+					}
 				}
 			} else {
 				this.fanoutTimelineService.push(`userTimeline:${user.id}`, note.id, note.userHost == null ? meta.perLocalUserUserTimelineCacheMax : meta.perRemoteUserUserTimelineCacheMax, r);
@@ -938,6 +973,23 @@ export class NoteEditService implements OnApplicationShutdown {
 		}
 	}
 
+	public async checkProhibitedWordsContain(content: Parameters<UtilityService['concatNoteContentsForKeyWordCheck']>[0], prohibitedWords?: string[]) {
+		if (prohibitedWords == null) {
+			prohibitedWords = (await this.metaService.fetch()).prohibitedWords;
+		}
+
+		if (
+			this.utilityService.isKeyWordIncluded(
+				this.utilityService.concatNoteContentsForKeyWordCheck(content),
+				prohibitedWords,
+			)
+		) {
+			return true;
+		}
+
+		return false;
+	}
+
 	@bindThis
 	public dispose(): void {
 		this.#shutdownController.abort();
diff --git a/packages/backend/src/core/NotePiningService.ts b/packages/backend/src/core/NotePiningService.ts
index 74e53c5c46..d38b48b65d 100644
--- a/packages/backend/src/core/NotePiningService.ts
+++ b/packages/backend/src/core/NotePiningService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/NoteReadService.ts b/packages/backend/src/core/NoteReadService.ts
index 03c1735e04..320b23cc1a 100644
--- a/packages/backend/src/core/NoteReadService.ts
+++ b/packages/backend/src/core/NoteReadService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -14,6 +14,7 @@ import { IdService } from '@/core/IdService.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
 import type { NoteUnreadsRepository, MutingsRepository, NoteThreadMutingsRepository } from '@/models/_.js';
 import { bindThis } from '@/decorators.js';
+import { trackPromise } from '@/misc/promise-tracker.js';
 
 @Injectable()
 export class NoteReadService implements OnApplicationShutdown {
@@ -48,7 +49,7 @@ export class NoteReadService implements OnApplicationShutdown {
 		//#endregion
 
 		// スレッドミュート
-		const isThreadMuted = await this.noteThreadMutingsRepository.exist({
+		const isThreadMuted = await this.noteThreadMutingsRepository.exists({
 			where: {
 				userId: userId,
 				threadId: note.threadId ?? note.id,
@@ -65,11 +66,15 @@ export class NoteReadService implements OnApplicationShutdown {
 			noteUserId: note.userId,
 		};
 
-		await this.noteUnreadsRepository.insert(unread);
+		/* we may be called from NoteEditService, for a note that's
+			already present in the `note_unread` table: `upsert` makes sure
+			we don't throw a "duplicate key" error, while still updating
+			the other columns if they've changed */
+		await this.noteUnreadsRepository.upsert(unread, ['userId', 'noteId']);
 
 		// 2秒経っても既読にならなかったら「未読の投稿がありますよ」イベントを発行する
 		setTimeout(2000, 'unread note', { signal: this.#shutdownController.signal }).then(async () => {
-			const exist = await this.noteUnreadsRepository.exist({ where: { id: unread.id } });
+			const exist = await this.noteUnreadsRepository.exists({ where: { id: unread.id } });
 
 			if (!exist) return;
 
@@ -87,46 +92,47 @@ export class NoteReadService implements OnApplicationShutdown {
 		userId: MiUser['id'],
 		notes: (MiNote | Packed<'Note'>)[],
 	): Promise<void> {
-		const readMentions: (MiNote | Packed<'Note'>)[] = [];
-		const readSpecifiedNotes: (MiNote | Packed<'Note'>)[] = [];
+		if (notes.length === 0) return;
+
+		const noteIds = new Set<MiNote['id']>();
 
 		for (const note of notes) {
 			if (note.mentions && note.mentions.includes(userId)) {
-				readMentions.push(note);
+				noteIds.add(note.id);
 			} else if (note.visibleUserIds && note.visibleUserIds.includes(userId)) {
-				readSpecifiedNotes.push(note);
+				noteIds.add(note.id);
 			}
 		}
 
-		if ((readMentions.length > 0) || (readSpecifiedNotes.length > 0)) {
-			// Remove the record
-			await this.noteUnreadsRepository.delete({
-				userId: userId,
-				noteId: In([...readMentions.map(n => n.id), ...readSpecifiedNotes.map(n => n.id)]),
-			});
+		if (noteIds.size === 0) return;
 
-			// TODO: ↓まとめてクエリしたい
+		// Remove the record
+		await this.noteUnreadsRepository.delete({
+			userId: userId,
+			noteId: In(Array.from(noteIds)),
+		});
 
-			this.noteUnreadsRepository.countBy({
-				userId: userId,
-				isMentioned: true,
-			}).then(mentionsCount => {
-				if (mentionsCount === 0) {
-					// 全て既読になったイベントを発行
-					this.globalEventService.publishMainStream(userId, 'readAllUnreadMentions');
-				}
-			});
+		// TODO: ↓まとめてクエリしたい
 
-			this.noteUnreadsRepository.countBy({
-				userId: userId,
-				isSpecified: true,
-			}).then(specifiedCount => {
-				if (specifiedCount === 0) {
-					// 全て既読になったイベントを発行
-					this.globalEventService.publishMainStream(userId, 'readAllUnreadSpecifiedNotes');
-				}
-			});
-		}
+		trackPromise(this.noteUnreadsRepository.countBy({
+			userId: userId,
+			isMentioned: true,
+		}).then(mentionsCount => {
+			if (mentionsCount === 0) {
+				// 全て既読になったイベントを発行
+				this.globalEventService.publishMainStream(userId, 'readAllUnreadMentions');
+			}
+		}));
+
+		trackPromise(this.noteUnreadsRepository.countBy({
+			userId: userId,
+			isSpecified: true,
+		}).then(specifiedCount => {
+			if (specifiedCount === 0) {
+				// 全て既読になったイベントを発行
+				this.globalEventService.publishMainStream(userId, 'readAllUnreadSpecifiedNotes');
+			}
+		}));
 	}
 
 	@bindThis
diff --git a/packages/backend/src/core/NotificationService.ts b/packages/backend/src/core/NotificationService.ts
index ad7be83e5b..68ad92f396 100644
--- a/packages/backend/src/core/NotificationService.ts
+++ b/packages/backend/src/core/NotificationService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -20,6 +20,7 @@ import { CacheService } from '@/core/CacheService.js';
 import type { Config } from '@/config.js';
 import { UserListService } from '@/core/UserListService.js';
 import type { FilterUnionByProperty } from '@/types.js';
+import { trackPromise } from '@/misc/promise-tracker.js';
 
 @Injectable()
 export class NotificationService implements OnApplicationShutdown {
@@ -74,7 +75,18 @@ export class NotificationService implements OnApplicationShutdown {
 	}
 
 	@bindThis
-	public async createNotification<T extends MiNotification['type']>(
+	public createNotification<T extends MiNotification['type']>(
+		notifieeId: MiUser['id'],
+		type: T,
+		data: Omit<FilterUnionByProperty<MiNotification, 'type', T>, 'type' | 'id' | 'createdAt' | 'notifierId'>,
+		notifierId?: MiUser['id'] | null,
+	) {
+		trackPromise(
+			this.#createNotificationInternal(notifieeId, type, data, notifierId),
+		);
+	}
+
+	async #createNotificationInternal<T extends MiNotification['type']>(
 		notifieeId: MiUser['id'],
 		type: T,
 		data: Omit<FilterUnionByProperty<MiNotification, 'type', T>, 'type' | 'id' | 'createdAt' | 'notifierId'>,
@@ -110,6 +122,14 @@ export class NotificationService implements OnApplicationShutdown {
 					return null;
 				}
 			} else if (recieveConfig?.type === 'mutualFollow') {
+				const [isFollowing, isFollower] = await Promise.all([
+					this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => Object.hasOwn(followings, notifierId)),
+					this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => Object.hasOwn(followings, notifieeId)),
+				]);
+				if (!(isFollowing && isFollower)) {
+					return null;
+				}
+			} else if (recieveConfig?.type === 'followingOrFollower') {
 				const [isFollowing, isFollower] = await Promise.all([
 					this.cacheService.userFollowingsCache.fetch(notifieeId).then(followings => Object.hasOwn(followings, notifierId)),
 					this.cacheService.userFollowingsCache.fetch(notifierId).then(followings => Object.hasOwn(followings, notifieeId)),
@@ -143,6 +163,8 @@ export class NotificationService implements OnApplicationShutdown {
 
 		const packed = await this.notificationEntityService.pack(notification, notifieeId, {});
 
+		if (packed == null) return null;
+
 		// Publish notification event
 		this.globalEventService.publishMainStream(notifieeId, 'notification', packed);
 
@@ -192,6 +214,15 @@ export class NotificationService implements OnApplicationShutdown {
 		*/
 	}
 
+	@bindThis
+	public async flushAllNotifications(userId: MiUser['id']) {
+		await Promise.all([
+			this.redisClient.del(`notificationTimeline:${userId}`),
+			this.redisClient.del(`latestReadNotification:${userId}`),
+		]);
+		this.globalEventService.publishMainStream(userId, 'notificationFlushed');
+	}
+
 	@bindThis
 	public dispose(): void {
 		this.#shutdownController.abort();
diff --git a/packages/backend/src/core/PollService.ts b/packages/backend/src/core/PollService.ts
index 9e1b5ca78a..6c96ab16cf 100644
--- a/packages/backend/src/core/PollService.ts
+++ b/packages/backend/src/core/PollService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/ProxyAccountService.ts b/packages/backend/src/core/ProxyAccountService.ts
index b1bc60701b..71d663bf90 100644
--- a/packages/backend/src/core/ProxyAccountService.ts
+++ b/packages/backend/src/core/ProxyAccountService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/PushNotificationService.ts b/packages/backend/src/core/PushNotificationService.ts
index 40d1deceeb..3b706d9854 100644
--- a/packages/backend/src/core/PushNotificationService.ts
+++ b/packages/backend/src/core/PushNotificationService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -115,12 +115,19 @@ export class PushNotificationService implements OnApplicationShutdown {
 						endpoint: subscription.endpoint,
 						auth: subscription.auth,
 						publickey: subscription.publickey,
+					}).then(() => {
+						this.refreshCache(userId);
 					});
 				}
 			});
 		}
 	}
 
+	@bindThis
+	public refreshCache(userId: string): void {
+		this.subscriptionsCache.refresh(userId);
+	}
+
 	@bindThis
 	public dispose(): void {
 		this.subscriptionsCache.dispose();
diff --git a/packages/backend/src/core/QueryService.ts b/packages/backend/src/core/QueryService.ts
index f006ed4944..c4feeaf971 100644
--- a/packages/backend/src/core/QueryService.ts
+++ b/packages/backend/src/core/QueryService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -212,8 +212,8 @@ export class QueryService {
 				// または 自分自身
 					.orWhere('note.userId = :meId')
 				// または 自分宛て
-					.orWhere(':meId = ANY(note.visibleUserIds)')
-					.orWhere(':meId = ANY(note.mentions)')
+					.orWhere(':meIdAsList <@ note.visibleUserIds')
+					.orWhere(':meIdAsList <@ note.mentions')
 					.orWhere(new Brackets(qb => {
 						qb
 						// または フォロワー宛ての投稿であり、
@@ -228,7 +228,7 @@ export class QueryService {
 					}));
 			}));
 
-			q.setParameters({ meId: me.id });
+			q.setParameters({ meId: me.id, meIdAsList: [me.id] });
 		}
 	}
 
diff --git a/packages/backend/src/core/QueueModule.ts b/packages/backend/src/core/QueueModule.ts
index 4444dc9787..216734e9e5 100644
--- a/packages/backend/src/core/QueueModule.ts
+++ b/packages/backend/src/core/QueueModule.ts
@@ -1,14 +1,14 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { setTimeout } from 'node:timers/promises';
 import { Inject, Module, OnApplicationShutdown } from '@nestjs/common';
 import * as Bull from 'bullmq';
 import { DI } from '@/di-symbols.js';
 import type { Config } from '@/config.js';
 import { QUEUE, baseQueueOptions } from '@/queue/const.js';
+import { allSettled } from '@/misc/promise-tracker.js';
 import type { Provider } from '@nestjs/common';
 import type { DeliverJobData, InboxJobData, EndedPollNotificationJobData, WebhookDeliverJobData, RelationshipJobData } from '../queue/types.js';
 
@@ -106,14 +106,9 @@ export class QueueModule implements OnApplicationShutdown {
 	) {}
 
 	public async dispose(): Promise<void> {
-		if (process.env.NODE_ENV === 'test') {
-			// XXX:
-			// Shutting down the existing connections causes errors on Jest as
-			// Misskey has asynchronous postgres/redis connections that are not
-			// awaited.
-			// Let's wait for some random time for them to finish.
-			await setTimeout(5000);
-		}
+		// Wait for all potential queue jobs
+		await allSettled();
+		// And then close all queues
 		await Promise.all([
 			this.systemQueue.close(),
 			this.endedPollNotificationQueue.close(),
diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts
index 2ee61eb549..103813acf2 100644
--- a/packages/backend/src/core/QueueService.ts
+++ b/packages/backend/src/core/QueueService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -17,6 +17,7 @@ import type { DbJobData, DeliverJobData, RelationshipJobData, ThinUser } from '.
 import type httpSignature from '@peertube/http-signature';
 import type * as Bull from 'bullmq';
 import { MiNote } from '@/models/Note.js';
+import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js';
 
 @Injectable()
 export class QueueService {
@@ -75,11 +76,15 @@ export class QueueService {
 		if (content == null) return null;
 		if (to == null) return null;
 
+		const contentBody = JSON.stringify(content);
+		const digest = ApRequestCreator.createDigest(contentBody);
+
 		const data: DeliverJobData = {
 			user: {
 				id: user.id,
 			},
-			content,
+			content: contentBody,
+			digest,
 			to,
 			isSharedInbox,
 		};
@@ -104,6 +109,8 @@ export class QueueService {
 	@bindThis
 	public async deliverMany(user: ThinUser, content: IActivity | null, inboxes: Map<string, boolean>) {
 		if (content == null) return null;
+		const contentBody = JSON.stringify(content);
+		const digest = ApRequestCreator.createDigest(contentBody);
 
 		const opts = {
 			attempts: this.config.deliverJobMaxAttempts ?? 12,
@@ -118,7 +125,8 @@ export class QueueService {
 			name: d[0],
 			data: {
 				user,
-				content,
+				content: contentBody,
+				digest,
 				to: d[0],
 				isSharedInbox: d[1],
 			} as DeliverJobData,
@@ -185,6 +193,16 @@ export class QueueService {
 		});
 	}
 
+	@bindThis
+	public createExportClipsJob(user: ThinUser) {
+		return this.dbQueue.add('exportClips', {
+			user: { id: user.id },
+		}, {
+			removeOnComplete: true,
+			removeOnFail: true,
+		});
+	}
+
 	@bindThis
 	public createExportFavoritesJob(user: ThinUser) {
 		return this.dbQueue.add('exportFavorites', {
diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts
index 0daee34ce5..90586b500e 100644
--- a/packages/backend/src/core/ReactionService.ts
+++ b/packages/backend/src/core/ReactionService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -28,13 +28,14 @@ import { UserBlockingService } from '@/core/UserBlockingService.js';
 import { CustomEmojiService } from '@/core/CustomEmojiService.js';
 import { RoleService } from '@/core/RoleService.js';
 import { FeaturedService } from '@/core/FeaturedService.js';
+import { trackPromise } from '@/misc/promise-tracker.js';
 
-const FALLBACK = '❤';
+const FALLBACK = '\u2764';
 const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16;
 
 const legacies: Record<string, string> = {
 	'like': '👍',
-	'love': '❤', // ここに記述する場合は異体字セレクタを入れない
+	'love': '\u2764', // ハート、異体字セレクタを入れない
 	'laugh': '😆',
 	'hmm': '🤔',
 	'surprise': '😮',
@@ -122,7 +123,7 @@ export class ReactionService {
 		let reaction = _reaction ?? FALLBACK;
 
 		if (note.reactionAcceptance === 'likeOnly' || ((note.reactionAcceptance === 'likeOnlyForRemote' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote') && (user.host != null))) {
-			reaction = '❤️';
+			reaction = '\u2764';
 		} else if (_reaction) {
 			const custom = reaction.match(isCustomEmojiRegexp);
 			if (custom) {
@@ -247,7 +248,7 @@ export class ReactionService {
 
 		// リアクションされたユーザーがローカルユーザーなら通知を作成
 		if (note.userHost === null) {
-			const isThreadMuted = await this.noteThreadMutingsRepository.exist({
+			const isThreadMuted = await this.noteThreadMutingsRepository.exists({
 				where: {
 					userId: note.userId,
 					threadId: note.threadId ?? note.id,
@@ -280,7 +281,7 @@ export class ReactionService {
 				}
 			}
 
-			dm.execute();
+			trackPromise(dm.execute());
 		}
 		//#endregion
 	}
@@ -328,40 +329,41 @@ export class ReactionService {
 				dm.addDirectRecipe(reactee as MiRemoteUser);
 			}
 			dm.addFollowersRecipe();
-			dm.execute();
+			trackPromise(dm.execute());
 		}
 		//#endregion
 	}
 
+	/**
+	 * 文字列タイプのレガシーな形式のリアクションを現在の形式に変換しつつ、
+	 * データベース上には存在する「0個のリアクションがついている」という情報を削除する。
+	 */
 	@bindThis
-	public convertLegacyReactions(reactions: Record<string, number>) {
-		const _reactions = {} as Record<string, number>;
+	public convertLegacyReactions(reactions: MiNote['reactions']): MiNote['reactions'] {
+		return Object.entries(reactions)
+			.filter(([, count]) => {
+				// `ReactionService.prototype.delete`ではリアクション削除時に、
+				// `MiNote['reactions']`のエントリの値をデクリメントしているが、
+				// デクリメントしているだけなのでエントリ自体は0を値として持つ形で残り続ける。
+				// そのため、この処理がなければ、「0個のリアクションがついている」ということになってしまう。
+				return count > 0;
+			})
+			.map(([reaction, count]) => {
+				// unchecked indexed access
+				const convertedReaction = legacies[reaction] as string | undefined;
 
-		for (const reaction of Object.keys(reactions)) {
-			if (reactions[reaction] <= 0) continue;
+				const key = this.decodeReaction(convertedReaction ?? reaction).reaction;
 
-			if (Object.keys(legacies).includes(reaction)) {
-				if (_reactions[legacies[reaction]]) {
-					_reactions[legacies[reaction]] += reactions[reaction];
-				} else {
-					_reactions[legacies[reaction]] = reactions[reaction];
-				}
-			} else {
-				if (_reactions[reaction]) {
-					_reactions[reaction] += reactions[reaction];
-				} else {
-					_reactions[reaction] = reactions[reaction];
-				}
-			}
-		}
+				return [key, count] as const;
+			})
+			.reduce<MiNote['reactions']>((acc, [key, count]) => {
+				// unchecked indexed access
+				const prevCount = acc[key] as number | undefined;
 
-		const _reactions2 = {} as Record<string, number>;
+				acc[key] = (prevCount ?? 0) + count;
 
-		for (const reaction of Object.keys(_reactions)) {
-			_reactions2[this.decodeReaction(reaction).reaction] = _reactions[reaction];
-		}
-
-		return _reactions2;
+				return acc;
+			}, {});
 	}
 
 	@bindThis
diff --git a/packages/backend/src/core/RegistryApiService.ts b/packages/backend/src/core/RegistryApiService.ts
index d340c5e480..2c8877d8a8 100644
--- a/packages/backend/src/core/RegistryApiService.ts
+++ b/packages/backend/src/core/RegistryApiService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/RelayService.ts b/packages/backend/src/core/RelayService.ts
index d40cd080c7..e9dc9b57af 100644
--- a/packages/backend/src/core/RelayService.ts
+++ b/packages/backend/src/core/RelayService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/RemoteLoggerService.ts b/packages/backend/src/core/RemoteLoggerService.ts
index 5d13988ed7..413b03bb56 100644
--- a/packages/backend/src/core/RemoteLoggerService.ts
+++ b/packages/backend/src/core/RemoteLoggerService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/RemoteUserResolveService.ts b/packages/backend/src/core/RemoteUserResolveService.ts
index 75c5f14aa4..f5a55eb8bc 100644
--- a/packages/backend/src/core/RemoteUserResolveService.ts
+++ b/packages/backend/src/core/RemoteUserResolveService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/ReversiService.ts b/packages/backend/src/core/ReversiService.ts
new file mode 100644
index 0000000000..53a7234823
--- /dev/null
+++ b/packages/backend/src/core/ReversiService.ts
@@ -0,0 +1,615 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import * as Redis from 'ioredis';
+import { ModuleRef } from '@nestjs/core';
+import * as Reversi from 'misskey-reversi';
+import { IsNull, LessThan, MoreThan } from 'typeorm';
+import type {
+	MiReversiGame,
+	ReversiGamesRepository,
+} from '@/models/_.js';
+import type { MiUser } from '@/models/User.js';
+import { DI } from '@/di-symbols.js';
+import { bindThis } from '@/decorators.js';
+import { CacheService } from '@/core/CacheService.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { IdService } from '@/core/IdService.js';
+import { NotificationService } from '@/core/NotificationService.js';
+import { Serialized } from '@/types.js';
+import { ReversiGameEntityService } from './entities/ReversiGameEntityService.js';
+import type { OnApplicationShutdown, OnModuleInit } from '@nestjs/common';
+
+const INVITATION_TIMEOUT_MS = 1000 * 20; // 20sec
+
+@Injectable()
+export class ReversiService implements OnApplicationShutdown, OnModuleInit {
+	private notificationService: NotificationService;
+
+	constructor(
+		private moduleRef: ModuleRef,
+
+		@Inject(DI.redis)
+		private redisClient: Redis.Redis,
+
+		@Inject(DI.reversiGamesRepository)
+		private reversiGamesRepository: ReversiGamesRepository,
+
+		private cacheService: CacheService,
+		private userEntityService: UserEntityService,
+		private globalEventService: GlobalEventService,
+		private reversiGameEntityService: ReversiGameEntityService,
+		private idService: IdService,
+	) {
+	}
+
+	async onModuleInit() {
+		this.notificationService = this.moduleRef.get(NotificationService.name);
+	}
+
+	@bindThis
+	private async cacheGame(game: MiReversiGame) {
+		await this.redisClient.setex(`reversi:game:cache:${game.id}`, 60 * 60, JSON.stringify(game));
+	}
+
+	@bindThis
+	private async deleteGameCache(gameId: MiReversiGame['id']) {
+		await this.redisClient.del(`reversi:game:cache:${gameId}`);
+	}
+
+	@bindThis
+	private getBakeProps(game: MiReversiGame) {
+		return {
+			startedAt: game.startedAt,
+			endedAt: game.endedAt,
+			// ゲームの途中からユーザーが変わることは無いので
+			//user1Id: game.user1Id,
+			//user2Id: game.user2Id,
+			user1Ready: game.user1Ready,
+			user2Ready: game.user2Ready,
+			black: game.black,
+			isStarted: game.isStarted,
+			isEnded: game.isEnded,
+			winnerId: game.winnerId,
+			surrenderedUserId: game.surrenderedUserId,
+			timeoutUserId: game.timeoutUserId,
+			isLlotheo: game.isLlotheo,
+			canPutEverywhere: game.canPutEverywhere,
+			loopedBoard: game.loopedBoard,
+			timeLimitForEachTurn: game.timeLimitForEachTurn,
+			logs: game.logs,
+			map: game.map,
+			bw: game.bw,
+			crc32: game.crc32,
+			noIrregularRules: game.noIrregularRules,
+		} satisfies Partial<MiReversiGame>;
+	}
+
+	@bindThis
+	public async matchSpecificUser(me: MiUser, targetUser: MiUser, multiple = false): Promise<MiReversiGame | null> {
+		if (targetUser.id === me.id) {
+			throw new Error('You cannot match yourself.');
+		}
+
+		if (!multiple) {
+			// 既にマッチしている対局が無いか探す(3分以内)
+			const games = await this.reversiGamesRepository.find({
+				where: [
+					{ id: MoreThan(this.idService.gen(Date.now() - 1000 * 60 * 3)), user1Id: me.id, user2Id: targetUser.id, isStarted: false },
+					{ id: MoreThan(this.idService.gen(Date.now() - 1000 * 60 * 3)), user1Id: targetUser.id, user2Id: me.id, isStarted: false },
+				],
+				relations: ['user1', 'user2'],
+				order: { id: 'DESC' },
+			});
+			if (games.length > 0) {
+				return games[0];
+			}
+		}
+
+		//#region 相手から既に招待されてないか確認
+		const invitations = await this.redisClient.zrange(
+			`reversi:matchSpecific:${me.id}`,
+			Date.now() - INVITATION_TIMEOUT_MS,
+			'+inf',
+			'BYSCORE');
+
+		if (invitations.includes(targetUser.id)) {
+			await this.redisClient.zrem(`reversi:matchSpecific:${me.id}`, targetUser.id);
+
+			const game = await this.matched(targetUser.id, me.id, {
+				noIrregularRules: false,
+			});
+
+			return game;
+		}
+		//#endregion
+
+		const redisPipeline = this.redisClient.pipeline();
+		redisPipeline.zadd(`reversi:matchSpecific:${targetUser.id}`, Date.now(), me.id);
+		redisPipeline.expire(`reversi:matchSpecific:${targetUser.id}`, 120, 'NX');
+		await redisPipeline.exec();
+
+		this.globalEventService.publishReversiStream(targetUser.id, 'invited', {
+			user: await this.userEntityService.pack(me, targetUser),
+		});
+
+		return null;
+	}
+
+	@bindThis
+	public async matchAnyUser(me: MiUser, options: { noIrregularRules: boolean }, multiple = false): Promise<MiReversiGame | null> {
+		if (!multiple) {
+			// 既にマッチしている対局が無いか探す(3分以内)
+			const games = await this.reversiGamesRepository.find({
+				where: [
+					{ id: MoreThan(this.idService.gen(Date.now() - 1000 * 60 * 3)), user1Id: me.id, isStarted: false },
+					{ id: MoreThan(this.idService.gen(Date.now() - 1000 * 60 * 3)), user2Id: me.id, isStarted: false },
+				],
+				relations: ['user1', 'user2'],
+				order: { id: 'DESC' },
+			});
+			if (games.length > 0) {
+				return games[0];
+			}
+		}
+
+		//#region まず自分宛ての招待を探す
+		const invitations = await this.redisClient.zrange(
+			`reversi:matchSpecific:${me.id}`,
+			Date.now() - INVITATION_TIMEOUT_MS,
+			'+inf',
+			'BYSCORE');
+
+		if (invitations.length > 0) {
+			const invitorId = invitations[Math.floor(Math.random() * invitations.length)];
+			await this.redisClient.zrem(`reversi:matchSpecific:${me.id}`, invitorId);
+
+			const game = await this.matched(invitorId, me.id, {
+				noIrregularRules: false,
+			});
+
+			return game;
+		}
+		//#endregion
+
+		const matchings = await this.redisClient.zrange(
+			'reversi:matchAny',
+			0,
+			2, // 自分自身のIDが入っている場合もあるので2つ取得
+			'REV');
+
+		const items = matchings.filter(id => !id.startsWith(me.id));
+
+		if (items.length > 0) {
+			const [matchedUserId, option] = items[0].split(':');
+
+			await this.redisClient.zrem('reversi:matchAny',
+				me.id,
+				matchedUserId,
+				me.id + ':noIrregularRules',
+				matchedUserId + ':noIrregularRules');
+
+			const game = await this.matched(matchedUserId, me.id, {
+				noIrregularRules: options.noIrregularRules || option === 'noIrregularRules',
+			});
+
+			return game;
+		} else {
+			const redisPipeline = this.redisClient.pipeline();
+			if (options.noIrregularRules) {
+				redisPipeline.zadd('reversi:matchAny', Date.now(), me.id + ':noIrregularRules');
+			} else {
+				redisPipeline.zadd('reversi:matchAny', Date.now(), me.id);
+			}
+			redisPipeline.expire('reversi:matchAny', 15, 'NX');
+			await redisPipeline.exec();
+			return null;
+		}
+	}
+
+	@bindThis
+	public async matchSpecificUserCancel(user: MiUser, targetUserId: MiUser['id']) {
+		await this.redisClient.zrem(`reversi:matchSpecific:${targetUserId}`, user.id);
+	}
+
+	@bindThis
+	public async matchAnyUserCancel(user: MiUser) {
+		await this.redisClient.zrem('reversi:matchAny', user.id, user.id + ':noIrregularRules');
+	}
+
+	@bindThis
+	public async cleanOutdatedGames() {
+		await this.reversiGamesRepository.delete({
+			id: LessThan(this.idService.gen(Date.now() - 1000 * 60 * 10)),
+			isStarted: false,
+		});
+	}
+
+	@bindThis
+	public async gameReady(gameId: MiReversiGame['id'], user: MiUser, ready: boolean) {
+		const game = await this.get(gameId);
+		if (game == null) throw new Error('game not found');
+		if (game.isStarted) return;
+
+		let isBothReady = false;
+
+		if (game.user1Id === user.id) {
+			const updatedGame = {
+				...game,
+				user1Ready: ready,
+			};
+			this.cacheGame(updatedGame);
+
+			this.globalEventService.publishReversiGameStream(game.id, 'changeReadyStates', {
+				user1: ready,
+				user2: updatedGame.user2Ready,
+			});
+
+			if (ready && updatedGame.user2Ready) isBothReady = true;
+		} else if (game.user2Id === user.id) {
+			const updatedGame = {
+				...game,
+				user2Ready: ready,
+			};
+			this.cacheGame(updatedGame);
+
+			this.globalEventService.publishReversiGameStream(game.id, 'changeReadyStates', {
+				user1: updatedGame.user1Ready,
+				user2: ready,
+			});
+
+			if (ready && updatedGame.user1Ready) isBothReady = true;
+		} else {
+			return;
+		}
+
+		if (isBothReady) {
+			// 3秒後、両者readyならゲーム開始
+			setTimeout(async () => {
+				const freshGame = await this.get(game.id);
+				if (freshGame == null || freshGame.isStarted || freshGame.isEnded) return;
+				if (!freshGame.user1Ready || !freshGame.user2Ready) return;
+
+				this.startGame(freshGame);
+			}, 3000);
+		}
+	}
+
+	@bindThis
+	private async matched(parentId: MiUser['id'], childId: MiUser['id'], options: { noIrregularRules: boolean; }): Promise<MiReversiGame> {
+		const game = await this.reversiGamesRepository.insert({
+			id: this.idService.gen(),
+			user1Id: parentId,
+			user2Id: childId,
+			user1Ready: false,
+			user2Ready: false,
+			isStarted: false,
+			isEnded: false,
+			logs: [],
+			map: Reversi.maps.eighteight.data,
+			bw: 'random',
+			isLlotheo: false,
+			noIrregularRules: options.noIrregularRules,
+		}).then(x => this.reversiGamesRepository.findOneOrFail({
+			where: { id: x.identifiers[0].id },
+			relations: ['user1', 'user2'],
+		}));
+		this.cacheGame(game);
+
+		const packed = await this.reversiGameEntityService.packDetail(game);
+		this.globalEventService.publishReversiStream(parentId, 'matched', { game: packed });
+
+		return game;
+	}
+
+	@bindThis
+	private async startGame(game: MiReversiGame) {
+		let bw: number;
+		if (game.bw === 'random') {
+			bw = Math.random() > 0.5 ? 1 : 2;
+		} else {
+			bw = parseInt(game.bw, 10);
+		}
+
+		const engine = new Reversi.Game(game.map, {
+			isLlotheo: game.isLlotheo,
+			canPutEverywhere: game.canPutEverywhere,
+			loopedBoard: game.loopedBoard,
+		});
+
+		const crc32 = engine.calcCrc32().toString();
+
+		const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update()
+			.set({
+				...this.getBakeProps(game),
+				startedAt: new Date(),
+				isStarted: true,
+				black: bw,
+				map: game.map,
+				crc32,
+			})
+			.where('id = :id', { id: game.id })
+			.returning('*')
+			.execute()
+			.then((response) => response.raw[0]);
+		// キャッシュ効率化のためにユーザー情報は再利用
+		updatedGame.user1 = game.user1;
+		updatedGame.user2 = game.user2;
+		this.cacheGame(updatedGame);
+
+		//#region 盤面に最初から石がないなどして始まった瞬間に勝敗が決定する場合があるのでその処理
+		if (engine.isEnded) {
+			let winnerId;
+			if (engine.winner === true) {
+				winnerId = bw === 1 ? updatedGame.user1Id : updatedGame.user2Id;
+			} else if (engine.winner === false) {
+				winnerId = bw === 1 ? updatedGame.user2Id : updatedGame.user1Id;
+			} else {
+				winnerId = null;
+			}
+
+			await this.endGame(updatedGame, winnerId, null);
+
+			return;
+		}
+		//#endregion
+
+		this.redisClient.setex(`reversi:game:turnTimer:${game.id}:1`, updatedGame.timeLimitForEachTurn, '');
+
+		this.globalEventService.publishReversiGameStream(game.id, 'started', {
+			game: await this.reversiGameEntityService.packDetail(updatedGame),
+		});
+	}
+
+	@bindThis
+	private async endGame(game: MiReversiGame, winnerId: MiUser['id'] | null, reason: 'surrender' | 'timeout' | null) {
+		const updatedGame = await this.reversiGamesRepository.createQueryBuilder().update()
+			.set({
+				...this.getBakeProps(game),
+				isEnded: true,
+				endedAt: new Date(),
+				winnerId: winnerId,
+				surrenderedUserId: reason === 'surrender' ? (winnerId === game.user1Id ? game.user2Id : game.user1Id) : null,
+				timeoutUserId: reason === 'timeout' ? (winnerId === game.user1Id ? game.user2Id : game.user1Id) : null,
+			})
+			.where('id = :id', { id: game.id })
+			.returning('*')
+			.execute()
+			.then((response) => response.raw[0]);
+		// キャッシュ効率化のためにユーザー情報は再利用
+		updatedGame.user1 = game.user1;
+		updatedGame.user2 = game.user2;
+		this.cacheGame(updatedGame);
+
+		this.globalEventService.publishReversiGameStream(game.id, 'ended', {
+			winnerId: winnerId,
+			game: await this.reversiGameEntityService.packDetail(updatedGame),
+		});
+	}
+
+	@bindThis
+	public async getInvitations(user: MiUser): Promise<MiUser['id'][]> {
+		const invitations = await this.redisClient.zrange(
+			`reversi:matchSpecific:${user.id}`,
+			Date.now() - INVITATION_TIMEOUT_MS,
+			'+inf',
+			'BYSCORE');
+		return invitations;
+	}
+
+	@bindThis
+	public async updateSettings(gameId: MiReversiGame['id'], user: MiUser, key: string, value: any) {
+		const game = await this.get(gameId);
+		if (game == null) throw new Error('game not found');
+		if (game.isStarted) return;
+		if ((game.user1Id !== user.id) && (game.user2Id !== user.id)) return;
+		if ((game.user1Id === user.id) && game.user1Ready) return;
+		if ((game.user2Id === user.id) && game.user2Ready) return;
+
+		if (!['map', 'bw', 'isLlotheo', 'canPutEverywhere', 'loopedBoard', 'timeLimitForEachTurn'].includes(key)) return;
+
+		// TODO: より厳格なバリデーション
+
+		const updatedGame = {
+			...game,
+			[key]: value,
+		};
+		this.cacheGame(updatedGame);
+
+		this.globalEventService.publishReversiGameStream(game.id, 'updateSettings', {
+			userId: user.id,
+			key: key,
+			value: value,
+		});
+	}
+
+	@bindThis
+	public async putStoneToGame(gameId: MiReversiGame['id'], user: MiUser, pos: number, id?: string | null) {
+		const game = await this.get(gameId);
+		if (game == null) throw new Error('game not found');
+		if (!game.isStarted) return;
+		if (game.isEnded) return;
+		if ((game.user1Id !== user.id) && (game.user2Id !== user.id)) return;
+
+		const myColor =
+			((game.user1Id === user.id) && game.black === 1) || ((game.user2Id === user.id) && game.black === 2)
+				? true
+				: false;
+
+		const engine = Reversi.Serializer.restoreGame({
+			map: game.map,
+			isLlotheo: game.isLlotheo,
+			canPutEverywhere: game.canPutEverywhere,
+			loopedBoard: game.loopedBoard,
+			logs: game.logs,
+		});
+
+		if (engine.turn !== myColor) return;
+		if (!engine.canPut(myColor, pos)) return;
+
+		engine.putStone(pos);
+
+		const logs = Reversi.Serializer.deserializeLogs(game.logs);
+
+		const log = {
+			time: Date.now(),
+			player: myColor,
+			operation: 'put',
+			pos,
+		} as const;
+
+		logs.push(log);
+
+		const serializeLogs = Reversi.Serializer.serializeLogs(logs);
+
+		const crc32 = engine.calcCrc32().toString();
+
+		const updatedGame = {
+			...game,
+			crc32,
+			logs: serializeLogs,
+		};
+		this.cacheGame(updatedGame);
+
+		this.globalEventService.publishReversiGameStream(game.id, 'log', {
+			...log,
+			id: id ?? null,
+		});
+
+		if (engine.isEnded) {
+			let winnerId;
+			if (engine.winner === true) {
+				winnerId = game.black === 1 ? game.user1Id : game.user2Id;
+			} else if (engine.winner === false) {
+				winnerId = game.black === 1 ? game.user2Id : game.user1Id;
+			} else {
+				winnerId = null;
+			}
+
+			await this.endGame(updatedGame, winnerId, null);
+		} else {
+			this.redisClient.setex(`reversi:game:turnTimer:${game.id}:${engine.turn ? '1' : '0'}`, updatedGame.timeLimitForEachTurn, '');
+		}
+	}
+
+	@bindThis
+	public async surrender(gameId: MiReversiGame['id'], user: MiUser) {
+		const game = await this.get(gameId);
+		if (game == null) throw new Error('game not found');
+		if (game.isEnded) return;
+		if ((game.user1Id !== user.id) && (game.user2Id !== user.id)) return;
+
+		const winnerId = game.user1Id === user.id ? game.user2Id : game.user1Id;
+
+		await this.endGame(game, winnerId, 'surrender');
+	}
+
+	@bindThis
+	public async checkTimeout(gameId: MiReversiGame['id']) {
+		const game = await this.get(gameId);
+		if (game == null) throw new Error('game not found');
+		if (game.isEnded) return;
+
+		const engine = Reversi.Serializer.restoreGame({
+			map: game.map,
+			isLlotheo: game.isLlotheo,
+			canPutEverywhere: game.canPutEverywhere,
+			loopedBoard: game.loopedBoard,
+			logs: game.logs,
+		});
+
+		if (engine.turn == null) return;
+
+		const timer = await this.redisClient.exists(`reversi:game:turnTimer:${game.id}:${engine.turn ? '1' : '0'}`);
+
+		if (timer === 0) {
+			const winnerId = engine.turn ? (game.black === 1 ? game.user2Id : game.user1Id) : (game.black === 1 ? game.user1Id : game.user2Id);
+
+			await this.endGame(game, winnerId, 'timeout');
+		}
+	}
+
+	@bindThis
+	public async cancelGame(gameId: MiReversiGame['id'], user: MiUser) {
+		const game = await this.get(gameId);
+		if (game == null) throw new Error('game not found');
+		if (game.isStarted) return;
+		if ((game.user1Id !== user.id) && (game.user2Id !== user.id)) return;
+
+		await this.reversiGamesRepository.delete(game.id);
+		this.deleteGameCache(game.id);
+
+		this.globalEventService.publishReversiGameStream(game.id, 'canceled', {
+			userId: user.id,
+		});
+	}
+
+	@bindThis
+	public async get(id: MiReversiGame['id']): Promise<MiReversiGame | null> {
+		const cached = await this.redisClient.get(`reversi:game:cache:${id}`);
+		if (cached != null) {
+			// TODO: この辺りのデシリアライズ処理をどこか別のサービスに切り出したい
+			const parsed = JSON.parse(cached) as Serialized<MiReversiGame>;
+			return {
+				...parsed,
+				startedAt: parsed.startedAt != null ? new Date(parsed.startedAt) : null,
+				endedAt: parsed.endedAt != null ? new Date(parsed.endedAt) : null,
+				user1: parsed.user1 != null ? {
+					...parsed.user1,
+					avatar: null,
+					banner: null,
+					background: null,
+					updatedAt: parsed.user1.updatedAt != null ? new Date(parsed.user1.updatedAt) : null,
+					lastActiveDate: parsed.user1.lastActiveDate != null ? new Date(parsed.user1.lastActiveDate) : null,
+					lastFetchedAt: parsed.user1.lastFetchedAt != null ? new Date(parsed.user1.lastFetchedAt) : null,
+					movedAt: parsed.user1.movedAt != null ? new Date(parsed.user1.movedAt) : null,
+				} : null,
+				user2: parsed.user2 != null ? {
+					...parsed.user2,
+					avatar: null,
+					banner: null,
+					background: null,
+					updatedAt: parsed.user2.updatedAt != null ? new Date(parsed.user2.updatedAt) : null,
+					lastActiveDate: parsed.user2.lastActiveDate != null ? new Date(parsed.user2.lastActiveDate) : null,
+					lastFetchedAt: parsed.user2.lastFetchedAt != null ? new Date(parsed.user2.lastFetchedAt) : null,
+					movedAt: parsed.user2.movedAt != null ? new Date(parsed.user2.movedAt) : null,
+				} : null,
+			};
+		} else {
+			const game = await this.reversiGamesRepository.findOne({
+				where: { id },
+				relations: ['user1', 'user2'],
+			});
+			if (game == null) return null;
+
+			this.cacheGame(game);
+
+			return game;
+		}
+	}
+
+	@bindThis
+	public async checkCrc(gameId: MiReversiGame['id'], crc32: string | number) {
+		const game = await this.get(gameId);
+		if (game == null) throw new Error('game not found');
+
+		if (crc32.toString() !== game.crc32) {
+			return game;
+		} else {
+			return null;
+		}
+	}
+
+	@bindThis
+	public dispose(): void {
+	}
+
+	@bindThis
+	public onApplicationShutdown(signal?: string | undefined): void {
+		this.dispose();
+	}
+}
diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts
index dcd9d7399f..2059a6e784 100644
--- a/packages/backend/src/core/RoleService.ts
+++ b/packages/backend/src/core/RoleService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -36,6 +36,7 @@ export type RolePolicies = {
 	ltlAvailable: boolean;
 	btlAvailable: boolean;
 	canPublicNote: boolean;
+	mentionLimit: number;
 	canInvite: boolean;
 	inviteLimit: number;
 	inviteLimitCycle: number;
@@ -65,6 +66,7 @@ export const DEFAULT_POLICIES: RolePolicies = {
 	ltlAvailable: true,
 	btlAvailable: false,
 	canPublicNote: true,
+	mentionLimit: 20,
 	canInvite: false,
 	inviteLimit: 0,
 	inviteLimitCycle: 60 * 24 * 7,
@@ -181,9 +183,11 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
 				case 'userRoleAssigned': {
 					const cached = this.roleAssignmentByUserIdCache.get(body.userId);
 					if (cached) {
-						cached.push({
+						cached.push({ // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
 							...body,
 							expiresAt: body.expiresAt ? new Date(body.expiresAt) : null,
+							user: null, // joinなカラムは通常取ってこないので
+							role: null, // joinなカラムは通常取ってこないので
 						});
 					}
 					break;
@@ -202,17 +206,20 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
 	}
 
 	@bindThis
-	private evalCond(user: MiUser, value: RoleCondFormulaValue): boolean {
+	private evalCond(user: MiUser, roles: MiRole[], value: RoleCondFormulaValue): boolean {
 		try {
 			switch (value.type) {
 				case 'and': {
-					return value.values.every(v => this.evalCond(user, v));
+					return value.values.every(v => this.evalCond(user, roles, v));
 				}
 				case 'or': {
-					return value.values.some(v => this.evalCond(user, v));
+					return value.values.some(v => this.evalCond(user, roles, v));
 				}
 				case 'not': {
-					return !this.evalCond(user, value.value);
+					return !this.evalCond(user, roles, value.value);
+				}
+				case 'roleAssignedTo': {
+					return roles.some(r => r.id === value.roleId);
 				}
 				case 'isLocal': {
 					return this.userEntityService.isLocalUser(user);
@@ -274,7 +281,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
 		const assigns = await this.getUserAssigns(userId);
 		const assignedRoles = roles.filter(r => assigns.map(x => x.roleId).includes(r.id));
 		const user = roles.some(r => r.target === 'conditional') ? await this.cacheService.findUserById(userId) : null;
-		const matchedCondRoles = roles.filter(r => r.target === 'conditional' && this.evalCond(user!, r.condFormula));
+		const matchedCondRoles = roles.filter(r => r.target === 'conditional' && this.evalCond(user!, assignedRoles, r.condFormula));
 		return [...assignedRoles, ...matchedCondRoles];
 	}
 
@@ -287,13 +294,13 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
 		let assigns = await this.roleAssignmentByUserIdCache.fetch(userId, () => this.roleAssignmentsRepository.findBy({ userId }));
 		// 期限切れのロールを除外
 		assigns = assigns.filter(a => a.expiresAt == null || (a.expiresAt.getTime() > now));
-		const assignedRoleIds = assigns.map(x => x.roleId);
 		const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({}));
-		const assignedBadgeRoles = roles.filter(r => r.asBadge && assignedRoleIds.includes(r.id));
+		const assignedRoles = roles.filter(r => assigns.map(x => x.roleId).includes(r.id));
+		const assignedBadgeRoles = assignedRoles.filter(r => r.asBadge);
 		const badgeCondRoles = roles.filter(r => r.asBadge && (r.target === 'conditional'));
 		if (badgeCondRoles.length > 0) {
 			const user = roles.some(r => r.target === 'conditional') ? await this.cacheService.findUserById(userId) : null;
-			const matchedBadgeCondRoles = badgeCondRoles.filter(r => this.evalCond(user!, r.condFormula));
+			const matchedBadgeCondRoles = badgeCondRoles.filter(r => this.evalCond(user!, assignedRoles, r.condFormula));
 			return [...assignedBadgeRoles, ...matchedBadgeCondRoles];
 		} else {
 			return assignedBadgeRoles;
@@ -328,6 +335,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit {
 			btlAvailable: calc('btlAvailable', vs => vs.some(v => v === true)),
 			ltlAvailable: calc('ltlAvailable', vs => vs.some(v => v === true)),
 			canPublicNote: calc('canPublicNote', vs => vs.some(v => v === true)),
+			mentionLimit: calc('mentionLimit', vs => Math.max(...vs)),
 			canInvite: calc('canInvite', vs => vs.some(v => v === true)),
 			inviteLimit: calc('inviteLimit', vs => Math.max(...vs)),
 			inviteLimitCycle: calc('inviteLimitCycle', vs => Math.max(...vs)),
diff --git a/packages/backend/src/core/S3Service.ts b/packages/backend/src/core/S3Service.ts
index df0991539d..bb2a463354 100644
--- a/packages/backend/src/core/S3Service.ts
+++ b/packages/backend/src/core/S3Service.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/SearchService.ts b/packages/backend/src/core/SearchService.ts
index 57c7e4baba..6dc3e85fc8 100644
--- a/packages/backend/src/core/SearchService.ts
+++ b/packages/backend/src/core/SearchService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts
index 32e3dee937..e3d69e5e94 100644
--- a/packages/backend/src/core/SignupService.ts
+++ b/packages/backend/src/core/SignupService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -17,6 +17,7 @@ import { MiUserKeypair } from '@/models/UserKeypair.js';
 import { MiUsedUsername } from '@/models/UsedUsername.js';
 import generateUserToken from '@/misc/generate-native-user-token.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { InstanceActorService } from '@/core/InstanceActorService.js';
 import { bindThis } from '@/decorators.js';
 import UsersChart from '@/core/chart/charts/users.js';
 import { UtilityService } from '@/core/UtilityService.js';
@@ -38,6 +39,7 @@ export class SignupService {
 		private userEntityService: UserEntityService,
 		private idService: IdService,
 		private metaService: MetaService,
+		private instanceActorService: InstanceActorService,
 		private usersChart: UsersChart,
 	) {
 	}
@@ -75,16 +77,16 @@ export class SignupService {
 		const secret = generateUserToken();
 
 		// Check username duplication
-		if (await this.usersRepository.exist({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) {
+		if (await this.usersRepository.exists({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) {
 			throw new Error('DUPLICATED_USERNAME');
 		}
 
 		// Check deleted username duplication
-		if (await this.usedUsernamesRepository.exist({ where: { username: username.toLowerCase() } })) {
+		if (await this.usedUsernamesRepository.exists({ where: { username: username.toLowerCase() } })) {
 			throw new Error('USED_USERNAME');
 		}
 
-		const isTheFirstUser = (await this.usersRepository.countBy({ host: IsNull() })) === 0;
+		const isTheFirstUser = !await this.instanceActorService.realLocalUsersPresent();
 
 		if (!opts.ignorePreservedUsernames && !isTheFirstUser) {
 			const isPreserved = instance.preservedUsernames.map(x => x.toLowerCase()).includes(username.toLowerCase());
diff --git a/packages/backend/src/core/UserAuthService.ts b/packages/backend/src/core/UserAuthService.ts
index ccf4dfc6bd..bdc27cbe8e 100644
--- a/packages/backend/src/core/UserAuthService.ts
+++ b/packages/backend/src/core/UserAuthService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/UserBlockingService.ts b/packages/backend/src/core/UserBlockingService.ts
index 39b19325c3..96f389b54c 100644
--- a/packages/backend/src/core/UserBlockingService.ts
+++ b/packages/backend/src/core/UserBlockingService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -109,13 +109,13 @@ export class UserBlockingService implements OnModuleInit {
 
 		if (this.userEntityService.isLocalUser(followee)) {
 			this.userEntityService.pack(followee, followee, {
-				detail: true,
+				schema: 'MeDetailed',
 			}).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed));
 		}
 
 		if (this.userEntityService.isLocalUser(follower) && !silent) {
 			this.userEntityService.pack(followee, follower, {
-				detail: true,
+				schema: 'UserDetailedNotMe',
 			}).then(async packed => {
 				this.globalEventService.publishMainStream(follower.id, 'unfollow', packed);
 
diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts
index d600ffb9d9..deeecdeb1f 100644
--- a/packages/backend/src/core/UserFollowingService.ts
+++ b/packages/backend/src/core/UserFollowingService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -30,6 +30,7 @@ import type { Config } from '@/config.js';
 import { AccountMoveService } from '@/core/AccountMoveService.js';
 import { UtilityService } from '@/core/UtilityService.js';
 import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
+import type { ThinUser } from '@/queue/types.js';
 import Logger from '../logger.js';
 
 const logger = new Logger('following/create');
@@ -94,21 +95,35 @@ export class UserFollowingService implements OnModuleInit {
 		this.userBlockingService = this.moduleRef.get('UserBlockingService');
 	}
 
+	@bindThis
+	public async deliverAccept(follower: MiRemoteUser, followee: MiPartialLocalUser, requestId?: string) {
+		const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, requestId), followee));
+		this.queueService.deliver(followee, content, follower.inbox, false);
+	}
+
 	@bindThis
 	public async follow(
-		_follower: { id: MiUser['id'] },
-		_followee: { id: MiUser['id'] },
+		_follower: ThinUser,
+		_followee: ThinUser,
 		{ requestId, silent = false, withReplies }: {
 			requestId?: string,
 			silent?: boolean,
 			withReplies?: boolean,
 		} = {},
 	): Promise<void> {
+		/**
+		 * 必ず最新のユーザー情報を取得する
+		 */
 		const [follower, followee] = await Promise.all([
 			this.usersRepository.findOneByOrFail({ id: _follower.id }),
 			this.usersRepository.findOneByOrFail({ id: _followee.id }),
 		]) as [MiLocalUser | MiRemoteUser, MiLocalUser | MiRemoteUser];
 
+		if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isRemoteUser(followee)) {
+			// What?
+			throw new Error('Remote user cannot follow remote user.');
+		}
+
 		// check blocking
 		const [blocking, blocked] = await Promise.all([
 			this.userBlockingService.checkBlocked(follower.id, followee.id),
@@ -129,6 +144,24 @@ export class UserFollowingService implements OnModuleInit {
 			if (blocked) throw new IdentifiableError('3338392a-f764-498d-8855-db939dcf8c48', 'blocked');
 		}
 
+		if (await this.followingsRepository.exists({
+			where: {
+				followerId: follower.id,
+				followeeId: followee.id,
+			},
+		})) {
+			// すでにフォロー関係が存在している場合
+			if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
+				// リモート → ローカル: acceptを送り返しておしまい
+				this.deliverAccept(follower, followee, requestId);
+				return;
+			}
+			if (this.userEntityService.isLocalUser(follower)) {
+				// ローカル → リモート/ローカル: 例外
+				throw new IdentifiableError('ec3f65c0-a9d1-47d9-8791-b2e7b9dcdced', 'already following');
+			}
+		}
+
 		const followeeProfile = await this.userProfilesRepository.findOneByOrFail({ userId: followee.id });
 		// フォロー対象が鍵アカウントである or
 		// フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or
@@ -144,7 +177,7 @@ export class UserFollowingService implements OnModuleInit {
 			let autoAccept = false;
 
 			// 鍵アカウントであっても、既にフォローされていた場合はスルー
-			const isFollowing = await this.followingsRepository.exist({
+			const isFollowing = await this.followingsRepository.exists({
 				where: {
 					followerId: follower.id,
 					followeeId: followee.id,
@@ -156,7 +189,7 @@ export class UserFollowingService implements OnModuleInit {
 
 			// フォローしているユーザーは自動承認オプション
 			if (!autoAccept && (this.userEntityService.isLocalUser(followee) && followeeProfile.autoAcceptFollowed)) {
-				const isFollowed = await this.followingsRepository.exist({
+				const isFollowed = await this.followingsRepository.exists({
 					where: {
 						followerId: followee.id,
 						followeeId: follower.id,
@@ -170,7 +203,7 @@ export class UserFollowingService implements OnModuleInit {
 			if (followee.isLocked && !autoAccept) {
 				autoAccept = !!(await this.accountMoveService.validateAlsoKnownAs(
 					follower,
-					(oldSrc, newSrc) => this.followingsRepository.exist({
+					(oldSrc, newSrc) => this.followingsRepository.exists({
 						where: {
 							followeeId: followee.id,
 							followerId: newSrc.id,
@@ -189,8 +222,7 @@ export class UserFollowingService implements OnModuleInit {
 		await this.insertFollowingDoc(followee, follower, silent, withReplies);
 
 		if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
-			const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, requestId), followee));
-			this.queueService.deliver(followee, content, follower.inbox, false);
+			this.deliverAccept(follower, followee, requestId);
 		}
 	}
 
@@ -233,7 +265,7 @@ export class UserFollowingService implements OnModuleInit {
 
 		this.cacheService.userFollowingsCache.refresh(follower.id);
 
-		const requestExist = await this.followRequestsRepository.exist({
+		const requestExist = await this.followRequestsRepository.exists({
 			where: {
 				followeeId: followee.id,
 				followerId: follower.id,
@@ -293,9 +325,9 @@ export class UserFollowingService implements OnModuleInit {
 		if (this.userEntityService.isLocalUser(follower) && !silent) {
 			// Publish follow event
 			this.userEntityService.pack(followee.id, follower, {
-				detail: true,
+				schema: 'UserDetailedNotMe',
 			}).then(async packed => {
-				this.globalEventService.publishMainStream(follower.id, 'follow', packed as Packed<'UserDetailedNotMe'>);
+				this.globalEventService.publishMainStream(follower.id, 'follow', packed);
 
 				const webhooks = (await this.webhookService.getActiveWebhooks()).filter(x => x.userId === follower.id && x.on.includes('follow'));
 				for (const webhook of webhooks) {
@@ -360,7 +392,7 @@ export class UserFollowingService implements OnModuleInit {
 		if (!silent && this.userEntityService.isLocalUser(follower)) {
 			// Publish unfollow event
 			this.userEntityService.pack(followee.id, follower, {
-				detail: true,
+				schema: 'UserDetailedNotMe',
 			}).then(async packed => {
 				this.globalEventService.publishMainStream(follower.id, 'unfollow', packed);
 
@@ -479,6 +511,12 @@ export class UserFollowingService implements OnModuleInit {
 		if (blocking) throw new Error('blocking');
 		if (blocked) throw new Error('blocked');
 
+		// Remove old follow requests before creating a new one.
+		await this.followRequestsRepository.delete({
+			followeeId: followee.id,
+			followerId: follower.id,
+		});
+
 		const followRequest = await this.followRequestsRepository.insert({
 			id: this.idService.gen(),
 			followerId: follower.id,
@@ -500,7 +538,7 @@ export class UserFollowingService implements OnModuleInit {
 			this.userEntityService.pack(follower.id, followee).then(packed => this.globalEventService.publishMainStream(followee.id, 'receiveFollowRequest', packed));
 
 			this.userEntityService.pack(followee.id, followee, {
-				detail: true,
+				schema: 'MeDetailed',
 			}).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed));
 
 			// 通知を作成
@@ -531,7 +569,7 @@ export class UserFollowingService implements OnModuleInit {
 			}
 		}
 
-		const requestExist = await this.followRequestsRepository.exist({
+		const requestExist = await this.followRequestsRepository.exists({
 			where: {
 				followeeId: followee.id,
 				followerId: follower.id,
@@ -548,7 +586,7 @@ export class UserFollowingService implements OnModuleInit {
 		});
 
 		this.userEntityService.pack(followee.id, followee, {
-			detail: true,
+			schema: 'MeDetailed',
 		}).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed));
 	}
 
@@ -571,12 +609,11 @@ export class UserFollowingService implements OnModuleInit {
 		await this.insertFollowingDoc(followee, follower, false, request.withReplies);
 
 		if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
-			const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee as MiPartialLocalUser, request.requestId!), followee));
-			this.queueService.deliver(followee, content, follower.inbox, false);
+			this.deliverAccept(follower, followee as MiPartialLocalUser, request.requestId ?? undefined);
 		}
 
 		this.userEntityService.pack(followee.id, followee, {
-			detail: true,
+			schema: 'MeDetailed',
 		}).then(packed => this.globalEventService.publishMainStream(followee.id, 'meUpdated', packed));
 	}
 
@@ -696,7 +733,7 @@ export class UserFollowingService implements OnModuleInit {
 	@bindThis
 	private async publishUnfollow(followee: Both, follower: Local): Promise<void> {
 		const packedFollowee = await this.userEntityService.pack(followee.id, follower, {
-			detail: true,
+			schema: 'UserDetailedNotMe',
 		});
 
 		this.globalEventService.publishMainStream(follower.id, 'unfollow', packedFollowee);
diff --git a/packages/backend/src/core/UserKeypairService.ts b/packages/backend/src/core/UserKeypairService.ts
index 425a97f3f1..51ac99179a 100644
--- a/packages/backend/src/core/UserKeypairService.ts
+++ b/packages/backend/src/core/UserKeypairService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/UserListService.ts b/packages/backend/src/core/UserListService.ts
index b6e4e1e884..bbdcfed738 100644
--- a/packages/backend/src/core/UserListService.ts
+++ b/packages/backend/src/core/UserListService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/UserMutingService.ts b/packages/backend/src/core/UserMutingService.ts
index 397e6bdd5d..06643be5fb 100644
--- a/packages/backend/src/core/UserMutingService.ts
+++ b/packages/backend/src/core/UserMutingService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/UserService.ts b/packages/backend/src/core/UserService.ts
index d16e1be615..72fa4d928d 100644
--- a/packages/backend/src/core/UserService.ts
+++ b/packages/backend/src/core/UserService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/UserSuspendService.ts b/packages/backend/src/core/UserSuspendService.ts
index 8940a142d1..d594a223f4 100644
--- a/packages/backend/src/core/UserSuspendService.ts
+++ b/packages/backend/src/core/UserSuspendService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/UtilityService.ts b/packages/backend/src/core/UtilityService.ts
index 5dec36c89e..652e8f7449 100644
--- a/packages/backend/src/core/UtilityService.ts
+++ b/packages/backend/src/core/UtilityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -43,13 +43,27 @@ export class UtilityService {
 	}
 
 	@bindThis
-	public isSensitiveWordIncluded(text: string, sensitiveWords: string[]): boolean {
-		if (sensitiveWords.length === 0) return false;
+	public concatNoteContentsForKeyWordCheck(content: {
+		cw?: string | null;
+		text?: string | null;
+		pollChoices?: string[] | null;
+		others?: string[] | null;
+	}): string {
+		/**
+		 * ノートの内容を結合してキーワードチェック用の文字列を生成する
+		 * cwとtextは内容が繋がっているかもしれないので間に何も入れずにチェックする
+		 */
+		return `${content.cw ?? ''}${content.text ?? ''}\n${(content.pollChoices ?? []).join('\n')}\n${(content.others ?? []).join('\n')}`;
+	}
+
+	@bindThis
+	public isKeyWordIncluded(text: string, keyWords: string[]): boolean {
+		if (keyWords.length === 0) return false;
 		if (text === '') return false;
 
 		const regexpregexp = /^\/(.+)\/(.*)$/;
 
-		const matched = sensitiveWords.some(filter => {
+		const matched = keyWords.some(filter => {
 			// represents RegExp
 			const regexp = filter.match(regexpregexp);
 			// This should never happen due to input sanitisation.
diff --git a/packages/backend/src/core/VideoProcessingService.ts b/packages/backend/src/core/VideoProcessingService.ts
index ffb7573358..747fe4fc7e 100644
--- a/packages/backend/src/core/VideoProcessingService.ts
+++ b/packages/backend/src/core/VideoProcessingService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/WebAuthnService.ts b/packages/backend/src/core/WebAuthnService.ts
index 5945dc2919..42fbed2110 100644
--- a/packages/backend/src/core/WebAuthnService.ts
+++ b/packages/backend/src/core/WebAuthnService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -26,7 +26,7 @@ import type {
 	PublicKeyCredentialDescriptorFuture,
 	PublicKeyCredentialRequestOptionsJSON,
 	RegistrationResponseJSON,
-} from '@simplewebauthn/typescript-types';
+} from '@simplewebauthn/types';
 
 @Injectable()
 export class WebAuthnService {
@@ -191,7 +191,7 @@ export class WebAuthnService {
 			if (cert[0] === 0x04) { // 前の実装ではいつも 0x04 で始まっていた
 				const halfLength = (cert.length - 1) / 2;
 
-				const cborMap = new Map<number, number | ArrayBufferLike>();
+				const cborMap = new Map<number, number | Uint8Array>();
 				cborMap.set(1, 2); // kty, EC2
 				cborMap.set(3, -7); // alg, ES256
 				cborMap.set(-1, 1); // crv, P256
diff --git a/packages/backend/src/core/WebfingerService.ts b/packages/backend/src/core/WebfingerService.ts
index 3d5747aebd..374536a741 100644
--- a/packages/backend/src/core/WebfingerService.ts
+++ b/packages/backend/src/core/WebfingerService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/WebhookService.ts b/packages/backend/src/core/WebhookService.ts
index 930e6ef64a..6be34977b0 100644
--- a/packages/backend/src/core/WebhookService.ts
+++ b/packages/backend/src/core/WebhookService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -49,9 +49,10 @@ export class WebhookService implements OnApplicationShutdown {
 			switch (type) {
 				case 'webhookCreated':
 					if (body.active) {
-						this.webhooks.push({
+						this.webhooks.push({ // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
 							...body,
 							latestSentAt: body.latestSentAt ? new Date(body.latestSentAt) : null,
+							user: null, // joinなカラムは通常取ってこないので
 						});
 					}
 					break;
@@ -59,14 +60,16 @@ export class WebhookService implements OnApplicationShutdown {
 					if (body.active) {
 						const i = this.webhooks.findIndex(a => a.id === body.id);
 						if (i > -1) {
-							this.webhooks[i] = {
+							this.webhooks[i] = { // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
 								...body,
 								latestSentAt: body.latestSentAt ? new Date(body.latestSentAt) : null,
+								user: null, // joinなカラムは通常取ってこないので
 							};
 						} else {
-							this.webhooks.push({
+							this.webhooks.push({ // TODO: このあたりのデシリアライズ処理は各modelファイル内に関数としてexportしたい
 								...body,
 								latestSentAt: body.latestSentAt ? new Date(body.latestSentAt) : null,
+								user: null, // joinなカラムは通常取ってこないので
 							});
 						}
 					} else {
diff --git a/packages/backend/src/core/activitypub/ApAudienceService.ts b/packages/backend/src/core/activitypub/ApAudienceService.ts
index 440852bdf3..0fccc7b950 100644
--- a/packages/backend/src/core/activitypub/ApAudienceService.ts
+++ b/packages/backend/src/core/activitypub/ApAudienceService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -8,6 +8,7 @@ import promiseLimit from 'promise-limit';
 import type { MiRemoteUser, MiUser } from '@/models/User.js';
 import { concat, unique } from '@/misc/prelude/array.js';
 import { bindThis } from '@/decorators.js';
+import { isNotNull } from '@/misc/is-not-null.js';
 import { getApIds } from './type.js';
 import { ApPersonService } from './models/ApPersonService.js';
 import type { ApObject } from './type.js';
@@ -40,7 +41,7 @@ export class ApAudienceService {
 		const limit = promiseLimit<MiUser | null>(2);
 		const mentionedUsers = (await Promise.all(
 			others.map(id => limit(() => this.apPersonService.resolvePerson(id, resolver).catch(() => null))),
-		)).filter((x): x is MiUser => x != null);
+		)).filter(isNotNull);
 
 		if (toGroups.public.length > 0) {
 			return {
@@ -58,7 +59,7 @@ export class ApAudienceService {
 			};
 		}
 
-		if (toGroups.followers.length > 0) {
+		if (toGroups.followers.length > 0 || ccGroups.followers.length > 0) {
 			return {
 				visibility: 'followers',
 				mentionedUsers,
diff --git a/packages/backend/src/core/activitypub/ApDbResolverService.ts b/packages/backend/src/core/activitypub/ApDbResolverService.ts
index dd1687edeb..44680a2ed5 100644
--- a/packages/backend/src/core/activitypub/ApDbResolverService.ts
+++ b/packages/backend/src/core/activitypub/ApDbResolverService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -106,12 +106,12 @@ export class ApDbResolverService implements OnApplicationShutdown {
 
 			return await this.cacheService.userByIdCache.fetchMaybe(
 				parsed.id,
-				() => this.usersRepository.findOneBy({ id: parsed.id }).then(x => x ?? undefined),
+				() => this.usersRepository.findOneBy({ id: parsed.id, isDeleted: false }).then(x => x ?? undefined),
 			) as MiLocalUser | undefined ?? null;
 		} else {
 			return await this.cacheService.uriPersonCache.fetch(
 				parsed.uri,
-				() => this.usersRepository.findOneBy({ uri: parsed.uri }),
+				() => this.usersRepository.findOneBy({ uri: parsed.uri, isDeleted: false }),
 			) as MiRemoteUser | null;
 		}
 	}
@@ -136,8 +136,12 @@ export class ApDbResolverService implements OnApplicationShutdown {
 
 		if (key == null) return null;
 
+		const user = await this.cacheService.findUserById(key.userId).catch(() => null) as MiRemoteUser | null;
+		if (user == null) return null;
+		if (user.isDeleted) return null;
+
 		return {
-			user: await this.cacheService.findUserById(key.userId) as MiRemoteUser,
+			user,
 			key,
 		};
 	}
@@ -151,6 +155,7 @@ export class ApDbResolverService implements OnApplicationShutdown {
 		key: MiUserPublickey | null;
 	} | null> {
 		const user = await this.apPersonService.resolvePerson(uri) as MiRemoteUser;
+		if (user.isDeleted) return null;
 
 		const key = await this.publicKeyByUserIdCache.fetch(
 			user.id,
diff --git a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts
index 81003bcf1c..5d07cd8e8f 100644
--- a/packages/backend/src/core/activitypub/ApDeliverManagerService.ts
+++ b/packages/backend/src/core/activitypub/ApDeliverManagerService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -144,7 +144,7 @@ class DeliverManager {
 		}
 
 		// deliver
-		this.queueService.deliverMany(this.actor, this.activity, inboxes);
+		await this.queueService.deliverMany(this.actor, this.activity, inboxes);
 	}
 }
 
diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts
index d8616d293d..6ff03b22e1 100644
--- a/packages/backend/src/core/activitypub/ApInboxService.ts
+++ b/packages/backend/src/core/activitypub/ApInboxService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -27,6 +27,7 @@ import { QueueService } from '@/core/QueueService.js';
 import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/_.js';
 import { bindThis } from '@/decorators.js';
 import type { MiRemoteUser } from '@/models/User.js';
+import { isNotNull } from '@/misc/is-not-null.js';
 import { getApHrefNullable, getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isMove, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js';
 import { ApNoteService } from './models/ApNoteService.js';
 import { ApLoggerService } from './ApLoggerService.js';
@@ -35,6 +36,7 @@ import { ApResolverService } from './ApResolverService.js';
 import { ApAudienceService } from './ApAudienceService.js';
 import { ApPersonService } from './models/ApPersonService.js';
 import { ApQuestionService } from './models/ApQuestionService.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
 import type { Resolver } from './ApResolverService.js';
 import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IReject, IRemove, IUndo, IUpdate, IMove } from './type.js';
 
@@ -82,6 +84,7 @@ export class ApInboxService {
 		private apPersonService: ApPersonService,
 		private apQuestionService: ApQuestionService,
 		private queueService: QueueService,
+		private globalEventService: GlobalEventService,
 	) {
 		this.logger = this.apLoggerService.logger;
 	}
@@ -97,6 +100,8 @@ export class ApInboxService {
 				} catch (err) {
 					if (err instanceof Error || typeof err === 'string') {
 						this.logger.error(err);
+					} else {
+						throw err;
 					}
 				}
 			}
@@ -256,7 +261,7 @@ export class ApInboxService {
 
 		const targetUri = getApId(activity.object);
 
-		this.announceNote(actor, activity, targetUri);
+		await this.announceNote(actor, activity, targetUri);
 	}
 
 	@bindThis
@@ -288,7 +293,7 @@ export class ApInboxService {
 			} catch (err) {
 				// 対象が4xxならスキップ
 				if (err instanceof StatusError) {
-					if (err.isClientError) {
+					if (!err.isRetryable) {
 						this.logger.warn(`Ignored announce target ${targetUri} - ${err.statusCode}`);
 						return;
 					}
@@ -373,7 +378,7 @@ export class ApInboxService {
 		});
 
 		if (isPost(object)) {
-			this.createNote(resolver, actor, object, false, activity);
+			await this.createNote(resolver, actor, object, false, activity);
 		} else {
 			this.logger.warn(`Unknown type: ${getApType(object)}`);
 		}
@@ -404,7 +409,7 @@ export class ApInboxService {
 			await this.apNoteService.createNote(note, resolver, silent);
 			return 'ok';
 		} catch (err) {
-			if (err instanceof StatusError && err.isClientError) {
+			if (err instanceof StatusError && !err.isRetryable) {
 				return `skip ${err.statusCode}`;
 			} else {
 				throw err;
@@ -477,6 +482,8 @@ export class ApInboxService {
 			isDeleted: true,
 		});
 
+		this.globalEventService.publishInternalEvent('remoteUserUpdated', { id: actor.id });
+
 		return `ok: queued ${job.name} ${job.id}`;
 	}
 
@@ -513,7 +520,7 @@ export class ApInboxService {
 		const userIds = uris
 			.filter(uri => uri.startsWith(this.config.url + '/users/'))
 			.map(uri => uri.split('/').at(-1))
-			.filter((userId): userId is string => userId !== undefined);
+			.filter(isNotNull);
 		const users = await this.usersRepository.findBy({
 			id: In(userIds),
 		});
@@ -627,7 +634,7 @@ export class ApInboxService {
 			return 'skip: follower not found';
 		}
 
-		const isFollowing = await this.followingsRepository.exist({
+		const isFollowing = await this.followingsRepository.exists({
 			where: {
 				followerId: follower.id,
 				followeeId: actor.id,
@@ -684,14 +691,14 @@ export class ApInboxService {
 			return 'skip: フォロー解除しようとしているユーザーはローカルユーザーではありません';
 		}
 
-		const requestExist = await this.followRequestsRepository.exist({
+		const requestExist = await this.followRequestsRepository.exists({
 			where: {
 				followerId: actor.id,
 				followeeId: followee.id,
 			},
 		});
 
-		const isFollowing = await this.followingsRepository.exist({
+		const isFollowing = await this.followingsRepository.exists({
 			where: {
 				followerId: actor.id,
 				followeeId: followee.id,
diff --git a/packages/backend/src/core/activitypub/ApLoggerService.ts b/packages/backend/src/core/activitypub/ApLoggerService.ts
index cd9597e423..428d8061ce 100644
--- a/packages/backend/src/core/activitypub/ApLoggerService.ts
+++ b/packages/backend/src/core/activitypub/ApLoggerService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/activitypub/ApMfmService.ts b/packages/backend/src/core/activitypub/ApMfmService.ts
index c19eb310d2..6d53ce5147 100644
--- a/packages/backend/src/core/activitypub/ApMfmService.ts
+++ b/packages/backend/src/core/activitypub/ApMfmService.ts
@@ -1,10 +1,10 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import { Injectable } from '@nestjs/common';
-import * as mfm from '@sharkey/sfm-js';
+import * as mfm from '@transfem-org/sfm-js';
 import { MfmService } from '@/core/MfmService.js';
 import type { MiNote } from '@/models/Note.js';
 import { bindThis } from '@/decorators.js';
@@ -25,8 +25,21 @@ export class ApMfmService {
 	}
 
 	@bindThis
-	public getNoteHtml(note: MiNote): string | null {
-		if (!note.text) return '';
-		return this.mfmService.toHtml(mfm.parse(note.text), note.mentionedRemoteUsers ? JSON.parse(note.mentionedRemoteUsers) : []);
+	public getNoteHtml(note: MiNote, apAppend?: string) {
+		let noMisskeyContent = false;
+		const srcMfm = (note.text ?? '') + (apAppend ?? '');
+
+		const parsed = mfm.parse(srcMfm);
+
+		if (!apAppend && parsed?.every(n => ['text', 'unicodeEmoji', 'emojiCode', 'mention', 'hashtag', 'url'].includes(n.type))) {
+			noMisskeyContent = true;
+		}
+
+		const content = this.mfmService.toHtml(parsed, note.mentionedRemoteUsers ? JSON.parse(note.mentionedRemoteUsers) : []);
+
+		return {
+			content,
+			noMisskeyContent,
+		};
 	}
 }
diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts
index f4d39d2408..a84feffac6 100644
--- a/packages/backend/src/core/activitypub/ApRendererService.ts
+++ b/packages/backend/src/core/activitypub/ApRendererService.ts
@@ -1,12 +1,12 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import { createPublicKey, randomUUID } from 'node:crypto';
 import { Inject, Injectable } from '@nestjs/common';
 import { In } from 'typeorm';
-import * as mfm from '@sharkey/sfm-js';
+import * as mfm from '@transfem-org/sfm-js';
 import { DI } from '@/di-symbols.js';
 import type { Config } from '@/config.js';
 import type { MiPartialLocalUser, MiLocalUser, MiPartialRemoteUser, MiRemoteUser, MiUser } from '@/models/User.js';
@@ -28,10 +28,10 @@ import { bindThis } from '@/decorators.js';
 import { CustomEmojiService } from '@/core/CustomEmojiService.js';
 import { isNotNull } from '@/misc/is-not-null.js';
 import { IdService } from '@/core/IdService.js';
+import { MetaService } from '../MetaService.js';
 import { LdSignatureService } from './LdSignatureService.js';
 import { ApMfmService } from './ApMfmService.js';
 import type { IAccept, IActivity, IAdd, IAnnounce, IApDocument, IApEmoji, IApHashtag, IApImage, IApMention, IBlock, ICreate, IDelete, IFlag, IFollow, IKey, ILike, IMove, IObject, IPost, IQuestion, IReject, IRemove, ITombstone, IUndo, IUpdate } from './type.js';
-import { MetaService } from '../MetaService.js';
 
 @Injectable()
 export class ApRendererService {
@@ -335,7 +335,7 @@ export class ApRendererService {
 		const getPromisedFiles = async (ids: string[]): Promise<MiDriveFile[]> => {
 			if (ids.length === 0) return [];
 			const items = await this.driveFilesRepository.findBy({ id: In(ids) });
-			return ids.map(id => items.find(item => item.id === id)).filter((item): item is MiDriveFile => item != null);
+			return ids.map(id => items.find(item => item.id === id)).filter(isNotNull);
 		};
 
 		let inReplyTo;
@@ -345,7 +345,7 @@ export class ApRendererService {
 			inReplyToNote = await this.notesRepository.findOneBy({ id: note.replyId });
 
 			if (inReplyToNote != null) {
-				const inReplyToUserExist = await this.usersRepository.exist({ where: { id: inReplyToNote.userId } });
+				const inReplyToUserExist = await this.usersRepository.exists({ where: { id: inReplyToNote.userId } });
 
 				if (inReplyToUserExist) {
 					if (inReplyToNote.uri) {
@@ -409,17 +409,15 @@ export class ApRendererService {
 			poll = await this.pollsRepository.findOneBy({ noteId: note.id });
 		}
 
-		let apText = text;
+		let apAppend = '';
 
 		if (quote) {
-			apText += `\n\nRE: ${quote}`;
+			apAppend += `\n\nRE: ${quote}`;
 		}
 
 		const summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw;
 
-		const content = this.apMfmService.getNoteHtml(Object.assign({}, note, {
-			text: apText,
-		}));
+		const { content, noMisskeyContent } = this.apMfmService.getNoteHtml(note, apAppend);
 
 		const emojis = await this.getEmojis(note.emojis);
 		const apemojis = emojis.filter(emoji => !emoji.localOnly).map(emoji => this.renderEmoji(emoji));
@@ -432,9 +430,6 @@ export class ApRendererService {
 
 		const asPoll = poll ? {
 			type: 'Question',
-			content: this.apMfmService.getNoteHtml(Object.assign({}, note, {
-				text: text,
-			})),
 			[poll.expiresAt && poll.expiresAt < new Date() ? 'closed' : 'endTime']: poll.expiresAt,
 			[poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({
 				type: 'Note',
@@ -452,11 +447,13 @@ export class ApRendererService {
 			attributedTo,
 			summary: summary ?? undefined,
 			content: content ?? undefined,
-			_misskey_content: text,
-			source: {
-				content: text,
-				mediaType: 'text/x.misskeymarkdown',
-			},
+			...(noMisskeyContent ? {} : {
+				_misskey_content: text,
+				source: {
+					content: text,
+					mediaType: 'text/x.misskeymarkdown',
+				},
+			}),
 			_misskey_quote: quote,
 			quoteUrl: quote,
 			quoteUri: quote,
@@ -639,7 +636,7 @@ export class ApRendererService {
 			inReplyToNote = await this.notesRepository.findOneBy({ id: note.replyId });
 
 			if (inReplyToNote != null) {
-				const inReplyToUserExist = await this.usersRepository.exist({ where: { id: inReplyToNote.userId } });
+				const inReplyToUserExist = await this.usersRepository.exists({ where: { id: inReplyToNote.userId } });
 
 				if (inReplyToUserExist) {
 					if (inReplyToNote.uri) {
@@ -703,17 +700,15 @@ export class ApRendererService {
 			poll = await this.pollsRepository.findOneBy({ noteId: note.id });
 		}
 
-		let apText = text;
+		let apAppend = '';
 
 		if (quote) {
-			apText += `\n\nRE: ${quote}`;
+			apAppend += `\n\nRE: ${quote}`;
 		}
 
 		const summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw;
 
-		const content = this.apMfmService.getNoteHtml(Object.assign({}, note, {
-			text: apText,
-		}));
+		const { content, noMisskeyContent } = this.apMfmService.getNoteHtml(note, apAppend);
 
 		const emojis = await this.getEmojis(note.emojis);
 		const apemojis = emojis.filter(emoji => !emoji.localOnly).map(emoji => this.renderEmoji(emoji));
@@ -726,9 +721,6 @@ export class ApRendererService {
 
 		const asPoll = poll ? {
 			type: 'Question',
-			content: this.apMfmService.getNoteHtml(Object.assign({}, note, {
-				text: text,
-			})),
 			[poll.expiresAt && poll.expiresAt < new Date() ? 'closed' : 'endTime']: poll.expiresAt,
 			[poll.multiple ? 'anyOf' : 'oneOf']: poll.choices.map((text, i) => ({
 				type: 'Note',
@@ -747,11 +739,13 @@ export class ApRendererService {
 			summary: summary ?? undefined,
 			content: content ?? undefined,
 			updated: note.updatedAt?.toISOString(),
-			_misskey_content: text,
-			source: {
-				content: text,
-				mediaType: 'text/x.misskeymarkdown',
-			},
+			...(noMisskeyContent ? {} : {
+				_misskey_content: text,
+				source: {
+					content: text,
+					mediaType: 'text/x.misskeymarkdown',
+				},
+			}),
 			_misskey_quote: quote,
 			quoteUrl: quote,
 			quoteUri: quote,
@@ -796,6 +790,7 @@ export class ApRendererService {
 				'https://www.w3.org/ns/activitystreams',
 				'https://w3id.org/security/v1',
 				{
+					Key: 'sec:Key',
 					// as non-standards
 					manuallyApprovesFollowers: 'as:manuallyApprovesFollowers',
 					sensitive: 'as:sensitive',
@@ -821,12 +816,12 @@ export class ApRendererService {
 					'_misskey_summary': 'misskey:_misskey_summary',
 					'isCat': 'misskey:isCat',
 					// Firefish
-					firefish: "https://joinfirefish.org/ns#",
-					speakAsCat: "firefish:speakAsCat",
+					firefish: 'https://joinfirefish.org/ns#',
+					speakAsCat: 'firefish:speakAsCat',
 					// Sharkey
-					sharkey: "https://joinsharkey.org/ns#",
-					backgroundUrl: "sharkey:backgroundUrl",
-					listenbrainz: "sharkey:listenbrainz",
+					sharkey: 'https://joinsharkey.org/ns#',
+					backgroundUrl: 'sharkey:backgroundUrl',
+					listenbrainz: 'sharkey:listenbrainz',
 					// vcard
 					vcard: 'http://www.w3.org/2006/vcard/ns#',
 				},
diff --git a/packages/backend/src/core/activitypub/ApRequestService.ts b/packages/backend/src/core/activitypub/ApRequestService.ts
index bd7b9bdf09..93ac8ce9a7 100644
--- a/packages/backend/src/core/activitypub/ApRequestService.ts
+++ b/packages/backend/src/core/activitypub/ApRequestService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -35,9 +35,9 @@ type PrivateKey = {
 };
 
 export class ApRequestCreator {
-	static createSignedPost(args: { key: PrivateKey, url: string, body: string, additionalHeaders: Record<string, string> }): Signed {
+	static createSignedPost(args: { key: PrivateKey, url: string, body: string, digest?: string, additionalHeaders: Record<string, string> }): Signed {
 		const u = new URL(args.url);
-		const digestHeader = `SHA-256=${crypto.createHash('sha256').update(args.body).digest('base64')}`;
+		const digestHeader = args.digest ?? this.createDigest(args.body);
 
 		const request: Request = {
 			url: u.href,
@@ -60,6 +60,10 @@ export class ApRequestCreator {
 		};
 	}
 
+	static createDigest(body: string) {
+		return `SHA-256=${crypto.createHash('sha256').update(body).digest('base64')}`;
+	}
+
 	static createSignedGet(args: { key: PrivateKey, url: string, additionalHeaders: Record<string, string> }): Signed {
 		const u = new URL(args.url);
 
@@ -146,8 +150,8 @@ export class ApRequestService {
 	}
 
 	@bindThis
-	public async signedPost(user: { id: MiUser['id'] }, url: string, object: unknown): Promise<void> {
-		const body = JSON.stringify(object);
+	public async signedPost(user: { id: MiUser['id'] }, url: string, object: unknown, digest?: string): Promise<void> {
+		const body = typeof object === 'string' ? object : JSON.stringify(object);
 
 		const keypair = await this.userKeypairService.getUserKeypair(user.id);
 
@@ -158,6 +162,7 @@ export class ApRequestService {
 			},
 			url,
 			body,
+			digest,
 			additionalHeaders: {
 			},
 		});
diff --git a/packages/backend/src/core/activitypub/ApResolverService.ts b/packages/backend/src/core/activitypub/ApResolverService.ts
index 870cf6372a..bb3c40f093 100644
--- a/packages/backend/src/core/activitypub/ApResolverService.ts
+++ b/packages/backend/src/core/activitypub/ApResolverService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/activitypub/LdSignatureService.ts b/packages/backend/src/core/activitypub/LdSignatureService.ts
index d8464b3839..9de184336f 100644
--- a/packages/backend/src/core/activitypub/LdSignatureService.ts
+++ b/packages/backend/src/core/activitypub/LdSignatureService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/activitypub/misc/contexts.ts b/packages/backend/src/core/activitypub/misc/contexts.ts
index 71c440e5cc..88afdefcd3 100644
--- a/packages/backend/src/core/activitypub/misc/contexts.ts
+++ b/packages/backend/src/core/activitypub/misc/contexts.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/activitypub/models/ApImageService.ts b/packages/backend/src/core/activitypub/models/ApImageService.ts
index 2eff7c64e0..1017a5018a 100644
--- a/packages/backend/src/core/activitypub/models/ApImageService.ts
+++ b/packages/backend/src/core/activitypub/models/ApImageService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/activitypub/models/ApMentionService.ts b/packages/backend/src/core/activitypub/models/ApMentionService.ts
index 9aa8ba5ede..0ced7e88af 100644
--- a/packages/backend/src/core/activitypub/models/ApMentionService.ts
+++ b/packages/backend/src/core/activitypub/models/ApMentionService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -8,6 +8,7 @@ import promiseLimit from 'promise-limit';
 import type { MiUser } from '@/models/_.js';
 import { toArray, unique } from '@/misc/prelude/array.js';
 import { bindThis } from '@/decorators.js';
+import { isNotNull } from '@/misc/is-not-null.js';
 import { isMention } from '../type.js';
 import { Resolver } from '../ApResolverService.js';
 import { ApPersonService } from './ApPersonService.js';
@@ -27,7 +28,7 @@ export class ApMentionService {
 		const limit = promiseLimit<MiUser | null>(2);
 		const mentionedUsers = (await Promise.all(
 			hrefs.map(x => limit(() => this.apPersonService.resolvePerson(x, resolver).catch(() => null))),
-		)).filter((x): x is MiUser => x != null);
+		)).filter(isNotNull);
 
 		return mentionedUsers;
 	}
diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts
index 2595783e04..6d9dc86c16 100644
--- a/packages/backend/src/core/activitypub/models/ApNoteService.ts
+++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -25,6 +25,8 @@ import { StatusError } from '@/misc/status-error.js';
 import { UtilityService } from '@/core/UtilityService.js';
 import { bindThis } from '@/decorators.js';
 import { checkHttps } from '@/misc/check-https.js';
+import { IdentifiableError } from '@/misc/identifiable-error.js';
+import { isNotNull } from '@/misc/is-not-null.js';
 import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js';
 import { ApLoggerService } from '../ApLoggerService.js';
 import { ApMfmService } from '../ApMfmService.js';
@@ -156,11 +158,47 @@ export class ApNoteService {
 			throw new Error('invalid note.attributedTo: ' + note.attributedTo);
 		}
 
-		const actor = await this.apPersonService.resolvePerson(getOneApId(note.attributedTo), resolver) as MiRemoteUser;
+		const uri = getOneApId(note.attributedTo);
 
-		// 投稿者が凍結されていたらスキップ
+		// ローカルで投稿者を検索し、もし凍結されていたらスキップ
+		const cachedActor = await this.apPersonService.fetchPerson(uri) as MiRemoteUser;
+		if (cachedActor && cachedActor.isSuspended) {
+			throw new IdentifiableError('85ab9bd7-3a41-4530-959d-f07073900109', 'actor has been suspended');
+		}
+
+		const apMentions = await this.apMentionService.extractApMentions(note.tag, resolver);
+		const apHashtags = extractApHashtags(note.tag);
+
+		const cw = note.summary === '' ? null : note.summary;
+
+		// テキストのパース
+		let text: string | null = null;
+		if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') {
+			text = note.source.content;
+		} else if (typeof note._misskey_content !== 'undefined') {
+			text = note._misskey_content;
+		} else if (typeof note.content === 'string') {
+			text = this.apMfmService.htmlToMfm(note.content, note.tag);
+		}
+
+		const poll = await this.apQuestionService.extractPollFromQuestion(note, resolver).catch(() => undefined);
+
+		//#region Contents Check
+		// 添付ファイルとユーザーをこのサーバーで登録する前に内容をチェックする
+		/**
+		 * 禁止ワードチェック
+		 */
+		const hasProhibitedWords = await this.noteCreateService.checkProhibitedWordsContain({ cw, text, pollChoices: poll?.choices });
+		if (hasProhibitedWords) {
+			throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words');
+		}
+		//#endregion
+
+		const actor = cachedActor ?? await this.apPersonService.resolvePerson(uri, resolver) as MiRemoteUser;
+
+		// 解決した投稿者が凍結されていたらスキップ
 		if (actor.isSuspended) {
-			throw new Error('actor has been suspended');
+			throw new IdentifiableError('85ab9bd7-3a41-4530-959d-f07073900109', 'actor has been suspended');
 		}
 
 		const noteAudience = await this.apAudienceService.parseAudience(actor, note.to, note.cc, resolver);
@@ -175,9 +213,6 @@ export class ApNoteService {
 			}
 		}
 
-		const apMentions = await this.apMentionService.extractApMentions(note.tag, resolver);
-		const apHashtags = extractApHashtags(note.tag);
-
 		// 添付ファイル
 		// TODO: attachmentは必ずしもImageではない
 		// TODO: attachmentは必ずしも配列ではない
@@ -221,12 +256,12 @@ export class ApNoteService {
 					return { status: 'ok', res };
 				} catch (e) {
 					return {
-						status: (e instanceof StatusError && e.isClientError) ? 'permerror' : 'temperror',
+						status: (e instanceof StatusError && !e.isRetryable) ? 'permerror' : 'temperror',
 					};
 				}
 			};
 
-			const uris = unique([note._misskey_quote, note.quoteUrl, note.quoteUri].filter((x): x is string => typeof x === 'string'));
+			const uris = unique([note._misskey_quote, note.quoteUrl, note.quoteUri].filter(isNotNull));
 			const results = await Promise.all(uris.map(tryResolveNote));
 
 			quote = results.filter((x): x is { status: 'ok', res: MiNote } => x.status === 'ok').map(x => x.res).at(0);
@@ -237,18 +272,6 @@ export class ApNoteService {
 			}
 		}
 
-		const cw = note.summary === '' ? null : note.summary;
-
-		// テキストのパース
-		let text: string | null = null;
-		if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') {
-			text = note.source.content;
-		} else if (typeof note._misskey_content !== 'undefined') {
-			text = note._misskey_content;
-		} else if (typeof note.content === 'string') {
-			text = this.apMfmService.htmlToMfm(note.content, note.tag);
-		}
-
 		// vote
 		if (reply && reply.hasPoll) {
 			const poll = await this.pollsRepository.findOneByOrFail({ noteId: reply.id });
@@ -278,8 +301,6 @@ export class ApNoteService {
 
 		const apEmojis = emojis.map(emoji => emoji.name);
 
-		const poll = await this.apQuestionService.extractPollFromQuestion(note, resolver).catch(() => undefined);
-
 		try {
 			return await this.noteCreateService.create(actor, {
 				createdAt: note.published ? new Date(note.published) : null,
@@ -317,14 +338,14 @@ export class ApNoteService {
 	 */
 	@bindThis
 	public async updateNote(value: string | IObject, resolver?: Resolver, silent = false): Promise<MiNote | null> {
-		const uri = typeof value === 'string' ? value : value.id;
-		if (uri == null) throw new Error('uri is null');
+		const noteUri = typeof value === 'string' ? value : value.id;
+		if (noteUri == null) throw new Error('uri is null');
 
 		// URIがこのサーバーを指しているならスキップ
-		if (uri.startsWith(this.config.url + '/')) throw new Error('uri points local');
+		if (noteUri.startsWith(this.config.url + '/')) throw new Error('uri points local');
 
 		//#region このサーバーに既に登録されているか
-		const UpdatedNote = await this.notesRepository.findOneBy({ uri });
+		const UpdatedNote = await this.notesRepository.findOneBy({ uri: noteUri });
 		if (UpdatedNote == null) throw new Error('Note is not registered');
 
 		// eslint-disable-next-line no-param-reassign
@@ -364,11 +385,47 @@ export class ApNoteService {
 			throw new Error('invalid note.attributedTo: ' + note.attributedTo);
 		}
 
-		const actor = await this.apPersonService.resolvePerson(getOneApId(note.attributedTo), resolver) as MiRemoteUser;
+		const uri = getOneApId(note.attributedTo);
+
+		// ローカルで投稿者を検索し、もし凍結されていたらスキップ
+		const cachedActor = await this.apPersonService.fetchPerson(uri) as MiRemoteUser;
+		if (cachedActor && cachedActor.isSuspended) {
+			throw new IdentifiableError('85ab9bd7-3a41-4530-959d-f07073900109', 'actor has been suspended');
+		}
+
+		const apMentions = await this.apMentionService.extractApMentions(note.tag, resolver);
+		const apHashtags = extractApHashtags(note.tag);
+
+		const cw = note.summary === '' ? null : note.summary;
+
+		// テキストのパース
+		let text: string | null = null;
+		if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') {
+			text = note.source.content;
+		} else if (typeof note._misskey_content !== 'undefined') {
+			text = note._misskey_content;
+		} else if (typeof note.content === 'string') {
+			text = this.apMfmService.htmlToMfm(note.content, note.tag);
+		}
+
+		const poll = await this.apQuestionService.extractPollFromQuestion(note, resolver).catch(() => undefined);
+
+		//#region Contents Check
+		// 添付ファイルとユーザーをこのサーバーで登録する前に内容をチェックする
+		/**
+		 * 禁止ワードチェック
+		 */
+		const hasProhibitedWords = await this.noteCreateService.checkProhibitedWordsContain({ cw, text, pollChoices: poll?.choices });
+		if (hasProhibitedWords) {
+			throw new IdentifiableError('689ee33f-f97c-479a-ac49-1b9f8140af99', 'Note contains prohibited words');
+		}
+		//#endregion
+
+		const actor = cachedActor ?? await this.apPersonService.resolvePerson(uri, resolver) as MiRemoteUser;
 
 		// 投稿者が凍結されていたらスキップ
 		if (actor.isSuspended) {
-			throw new Error('actor has been suspended');
+			throw new IdentifiableError('85ab9bd7-3a41-4530-959d-f07073900109', 'actor has been suspended');
 		}
 
 		const noteAudience = await this.apAudienceService.parseAudience(actor, note.to, note.cc, resolver);
@@ -383,9 +440,6 @@ export class ApNoteService {
 			}
 		}
 
-		const apMentions = await this.apMentionService.extractApMentions(note.tag, resolver);
-		const apHashtags = extractApHashtags(note.tag);
-
 		// 添付ファイル
 		// TODO: attachmentは必ずしもImageではない
 		// TODO: attachmentは必ずしも配列ではない
@@ -445,24 +499,12 @@ export class ApNoteService {
 			}
 		}
 
-		const cw = note.summary === '' ? null : note.summary;
-
-		// テキストのパース
-		let text: string | null = null;
-		if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source.content === 'string') {
-			text = note.source.content;
-		} else if (typeof note._misskey_content !== 'undefined') {
-			text = note._misskey_content;
-		} else if (typeof note.content === 'string') {
-			text = this.apMfmService.htmlToMfm(note.content, note.tag);
-		}
-
 		// vote
 		if (reply && reply.hasPoll) {
-			const poll = await this.pollsRepository.findOneByOrFail({ noteId: reply.id });
+			const replyPoll = await this.pollsRepository.findOneByOrFail({ noteId: reply.id });
 
 			const tryCreateVote = async (name: string, index: number): Promise<null> => {
-				if (poll.expiresAt && Date.now() > new Date(poll.expiresAt).getTime()) {
+				if (replyPoll.expiresAt && Date.now() > new Date(replyPoll.expiresAt).getTime()) {
 					this.logger.warn(`vote to expired poll from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`);
 				} else if (index >= 0) {
 					this.logger.info(`vote from AP: actor=${actor.username}@${actor.host}, note=${note.id}, choice=${name}`);
@@ -475,7 +517,7 @@ export class ApNoteService {
 			};
 
 			if (note.name) {
-				return await tryCreateVote(note.name, poll.choices.findIndex(x => x === note.name));
+				return await tryCreateVote(note.name, replyPoll.choices.findIndex(x => x === note.name));
 			}
 		}
 
@@ -486,8 +528,6 @@ export class ApNoteService {
 
 		const apEmojis = emojis.map(emoji => emoji.name);
 
-		const poll = await this.apQuestionService.extractPollFromQuestion(note, resolver).catch(() => undefined);
-
 		try {
 			return await this.noteEditService.edit(actor, UpdatedNote.id, {
 				createdAt: note.published ? new Date(note.published) : null,
diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts
index 8507c6f949..c489d38d90 100644
--- a/packages/backend/src/core/activitypub/models/ApPersonService.ts
+++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -38,6 +38,7 @@ import { MetaService } from '@/core/MetaService.js';
 import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
 import type { AccountMoveService } from '@/core/AccountMoveService.js';
 import { checkHttps } from '@/misc/check-https.js';
+import { isNotNull } from '@/misc/is-not-null.js';
 import { getApId, getApType, getOneApHrefNullable, isActor, isCollection, isCollectionOrOrderedCollection, isPropertyValue } from '../type.js';
 import { extractApHashtags } from './tag.js';
 import type { OnModuleInit } from '@nestjs/common';
@@ -225,23 +226,42 @@ export class ApPersonService implements OnModuleInit {
 		return null;
 	}
 
-	private async resolveAvatarAndBanner(user: MiRemoteUser, icon: any, image: any, bgimg: any): Promise<Pick<MiRemoteUser, 'avatarId' | 'bannerId' | 'backgroundId' | 'avatarUrl' | 'bannerUrl' | 'backgroundUrl' | 'avatarBlurhash' | 'bannerBlurhash' | 'backgroundBlurhash'>> {
+	private async resolveAvatarAndBanner(user: MiRemoteUser, icon: any, image: any, bgimg: any): Promise<Partial<Pick<MiRemoteUser, 'avatarId' | 'bannerId' | 'backgroundId' | 'avatarUrl' | 'bannerUrl' | 'backgroundUrl' | 'avatarBlurhash' | 'bannerBlurhash' | 'backgroundBlurhash'>>> {
+		if (user == null) throw new Error('failed to create user: user is null');
+
 		const [avatar, banner, background] = await Promise.all([icon, image, bgimg].map(img => {
-			if (img == null) return null;
-			if (user == null) throw new Error('failed to create user: user is null');
+			// if we have an explicitly missing image, return an
+			// explicitly-null set of values
+			if ((img == null) || (typeof img === 'object' && img.url == null)) {
+				return { id: null, url: null, blurhash: null };
+			}
+
 			return this.apImageService.resolveImage(user, img).catch(() => null);
 		}));
 
+		/*
+			we don't want to return nulls on errors! if the database fields
+			are already null, nothing changes; if the database has old
+			values, we should keep those. The exception is if the remote has
+			actually removed the images: in that case, the block above
+			returns the special {id:null}&c value, and we return those
+		*/
 		return {
-			avatarId: avatar?.id ?? null,
-			bannerId: banner?.id ?? null,
-			backgroundId: background?.id ?? null,
-			avatarUrl: avatar ? this.driveFileEntityService.getPublicUrl(avatar, 'avatar') : null,
-			bannerUrl: banner ? this.driveFileEntityService.getPublicUrl(banner) : null,
-			backgroundUrl: background ? this.driveFileEntityService.getPublicUrl(background) : null,
-			avatarBlurhash: avatar?.blurhash ?? null,
-			bannerBlurhash: banner?.blurhash ?? null,
-			backgroundBlurhash: background?.blurhash ?? null
+			...( avatar ? {
+				avatarId: avatar.id,
+				avatarUrl: avatar.url ? this.driveFileEntityService.getPublicUrl(avatar, 'avatar') : null,
+				avatarBlurhash: avatar.blurhash,
+			} : {}),
+			...( banner ? {
+				bannerId: banner.id,
+				bannerUrl: banner.url ? this.driveFileEntityService.getPublicUrl(banner) : null,
+				bannerBlurhash: banner.blurhash,
+			} : {}),
+			...( background ? {
+				backgroundId: background.id,
+				backgroundUrl: background.url ? this.driveFileEntityService.getPublicUrl(background) : null,
+				backgroundBlurhash: background.blurhash,
+			} : {}),
 		};
 	}
 
@@ -640,7 +660,7 @@ export class ApPersonService implements OnModuleInit {
 
 			// とりあえずidを別の時間で生成して順番を維持
 			let td = 0;
-			for (const note of featuredNotes.filter((note): note is MiNote => note != null)) {
+			for (const note of featuredNotes.filter(isNotNull)) {
 				td -= 1000;
 				transactionalEntityManager.insert(MiUserNotePining, {
 					id: this.idService.gen(Date.now() + td),
diff --git a/packages/backend/src/core/activitypub/models/ApQuestionService.ts b/packages/backend/src/core/activitypub/models/ApQuestionService.ts
index 27bd62268b..d1936cfe1d 100644
--- a/packages/backend/src/core/activitypub/models/ApQuestionService.ts
+++ b/packages/backend/src/core/activitypub/models/ApQuestionService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -10,6 +10,7 @@ import type { Config } from '@/config.js';
 import type { IPoll } from '@/models/Poll.js';
 import type Logger from '@/logger.js';
 import { bindThis } from '@/decorators.js';
+import { isNotNull } from '@/misc/is-not-null.js';
 import { isQuestion } from '../type.js';
 import { ApLoggerService } from '../ApLoggerService.js';
 import { ApResolverService } from '../ApResolverService.js';
@@ -51,7 +52,7 @@ export class ApQuestionService {
 
 		const choices = question[multiple ? 'anyOf' : 'oneOf']
 			?.map((x) => x.name)
-			.filter((x): x is string => typeof x === 'string')
+			.filter(isNotNull)
 			?? [];
 
 		const votes = question[multiple ? 'anyOf' : 'oneOf']?.map((x) => x.replies?.totalItems ?? x._misskey_votes ?? 0);
diff --git a/packages/backend/src/core/activitypub/models/icon.ts b/packages/backend/src/core/activitypub/models/icon.ts
index 9fed78020d..5722507a3b 100644
--- a/packages/backend/src/core/activitypub/models/icon.ts
+++ b/packages/backend/src/core/activitypub/models/icon.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/activitypub/models/identifier.ts b/packages/backend/src/core/activitypub/models/identifier.ts
index 22a7b0a76e..dce4f410b4 100644
--- a/packages/backend/src/core/activitypub/models/identifier.ts
+++ b/packages/backend/src/core/activitypub/models/identifier.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/activitypub/models/tag.ts b/packages/backend/src/core/activitypub/models/tag.ts
index 772ea11864..e7ceec3262 100644
--- a/packages/backend/src/core/activitypub/models/tag.ts
+++ b/packages/backend/src/core/activitypub/models/tag.ts
@@ -1,9 +1,10 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import { toArray } from '@/misc/prelude/array.js';
+import { isNotNull } from '@/misc/is-not-null.js';
 import { isHashtag } from '../type.js';
 import type { IObject, IApHashtag } from '../type.js';
 
@@ -15,7 +16,7 @@ export function extractApHashtags(tags: IObject | IObject[] | null | undefined):
 	return hashtags.map(tag => {
 		const m = tag.name.match(/^#(.+)/);
 		return m ? m[1] : null;
-	}).filter((x): x is string => x != null);
+	}).filter(isNotNull);
 }
 
 export function extractApHashtagObjects(tags: IObject | IObject[] | null | undefined): IApHashtag[] {
diff --git a/packages/backend/src/core/activitypub/type.ts b/packages/backend/src/core/activitypub/type.ts
index ce3317ef72..716515840c 100644
--- a/packages/backend/src/core/activitypub/type.ts
+++ b/packages/backend/src/core/activitypub/type.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/ChartLoggerService.ts b/packages/backend/src/core/chart/ChartLoggerService.ts
index bd90efec64..afc728d564 100644
--- a/packages/backend/src/core/chart/ChartLoggerService.ts
+++ b/packages/backend/src/core/chart/ChartLoggerService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/ChartManagementService.ts b/packages/backend/src/core/chart/ChartManagementService.ts
index f751a68cb4..79681370a1 100644
--- a/packages/backend/src/core/chart/ChartManagementService.ts
+++ b/packages/backend/src/core/chart/ChartManagementService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/active-users.ts b/packages/backend/src/core/chart/charts/active-users.ts
index f0918e059c..05905f3782 100644
--- a/packages/backend/src/core/chart/charts/active-users.ts
+++ b/packages/backend/src/core/chart/charts/active-users.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/ap-request.ts b/packages/backend/src/core/chart/charts/ap-request.ts
index 03c9b42be1..04e771a95b 100644
--- a/packages/backend/src/core/chart/charts/ap-request.ts
+++ b/packages/backend/src/core/chart/charts/ap-request.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/drive.ts b/packages/backend/src/core/chart/charts/drive.ts
index bbcbf1a955..613e074a9f 100644
--- a/packages/backend/src/core/chart/charts/drive.ts
+++ b/packages/backend/src/core/chart/charts/drive.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/entities/active-users.ts b/packages/backend/src/core/chart/charts/entities/active-users.ts
index e68022ef29..fc2b88a2bb 100644
--- a/packages/backend/src/core/chart/charts/entities/active-users.ts
+++ b/packages/backend/src/core/chart/charts/entities/active-users.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/entities/ap-request.ts b/packages/backend/src/core/chart/charts/entities/ap-request.ts
index a824515255..93e47e081b 100644
--- a/packages/backend/src/core/chart/charts/entities/ap-request.ts
+++ b/packages/backend/src/core/chart/charts/entities/ap-request.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/entities/drive.ts b/packages/backend/src/core/chart/charts/entities/drive.ts
index 4a56bd45c5..4ea16da38c 100644
--- a/packages/backend/src/core/chart/charts/entities/drive.ts
+++ b/packages/backend/src/core/chart/charts/entities/drive.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/entities/federation.ts b/packages/backend/src/core/chart/charts/entities/federation.ts
index e067c71a7f..5ed7804343 100644
--- a/packages/backend/src/core/chart/charts/entities/federation.ts
+++ b/packages/backend/src/core/chart/charts/entities/federation.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/entities/instance.ts b/packages/backend/src/core/chart/charts/entities/instance.ts
index 4ea10d56d1..d0cac3e73f 100644
--- a/packages/backend/src/core/chart/charts/entities/instance.ts
+++ b/packages/backend/src/core/chart/charts/entities/instance.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/entities/notes.ts b/packages/backend/src/core/chart/charts/entities/notes.ts
index 26e2529b17..325236ab35 100644
--- a/packages/backend/src/core/chart/charts/entities/notes.ts
+++ b/packages/backend/src/core/chart/charts/entities/notes.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/entities/per-user-drive.ts b/packages/backend/src/core/chart/charts/entities/per-user-drive.ts
index aec3dd5140..25d4619dde 100644
--- a/packages/backend/src/core/chart/charts/entities/per-user-drive.ts
+++ b/packages/backend/src/core/chart/charts/entities/per-user-drive.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/entities/per-user-following.ts b/packages/backend/src/core/chart/charts/entities/per-user-following.ts
index afb5813058..1618bd22f3 100644
--- a/packages/backend/src/core/chart/charts/entities/per-user-following.ts
+++ b/packages/backend/src/core/chart/charts/entities/per-user-following.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/entities/per-user-notes.ts b/packages/backend/src/core/chart/charts/entities/per-user-notes.ts
index 60a0b01c8e..30404b2e48 100644
--- a/packages/backend/src/core/chart/charts/entities/per-user-notes.ts
+++ b/packages/backend/src/core/chart/charts/entities/per-user-notes.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/entities/per-user-pv.ts b/packages/backend/src/core/chart/charts/entities/per-user-pv.ts
index 78d4464d7e..7a903afad4 100644
--- a/packages/backend/src/core/chart/charts/entities/per-user-pv.ts
+++ b/packages/backend/src/core/chart/charts/entities/per-user-pv.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/entities/per-user-reactions.ts b/packages/backend/src/core/chart/charts/entities/per-user-reactions.ts
index 761101d479..bb62bb2386 100644
--- a/packages/backend/src/core/chart/charts/entities/per-user-reactions.ts
+++ b/packages/backend/src/core/chart/charts/entities/per-user-reactions.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/entities/test-grouped.ts b/packages/backend/src/core/chart/charts/entities/test-grouped.ts
index 15eb1fd1f8..599c1dc136 100644
--- a/packages/backend/src/core/chart/charts/entities/test-grouped.ts
+++ b/packages/backend/src/core/chart/charts/entities/test-grouped.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/entities/test-intersection.ts b/packages/backend/src/core/chart/charts/entities/test-intersection.ts
index 2ef63977a5..d29b39716c 100644
--- a/packages/backend/src/core/chart/charts/entities/test-intersection.ts
+++ b/packages/backend/src/core/chart/charts/entities/test-intersection.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/entities/test-unique.ts b/packages/backend/src/core/chart/charts/entities/test-unique.ts
index 56233585db..bdaa1716ed 100644
--- a/packages/backend/src/core/chart/charts/entities/test-unique.ts
+++ b/packages/backend/src/core/chart/charts/entities/test-unique.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/entities/test.ts b/packages/backend/src/core/chart/charts/entities/test.ts
index 163db4e79f..c80ff55c99 100644
--- a/packages/backend/src/core/chart/charts/entities/test.ts
+++ b/packages/backend/src/core/chart/charts/entities/test.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/entities/users.ts b/packages/backend/src/core/chart/charts/entities/users.ts
index c7bffd3fd4..f94a5029d7 100644
--- a/packages/backend/src/core/chart/charts/entities/users.ts
+++ b/packages/backend/src/core/chart/charts/entities/users.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/federation.ts b/packages/backend/src/core/chart/charts/federation.ts
index fc474b002b..5e4555ee96 100644
--- a/packages/backend/src/core/chart/charts/federation.ts
+++ b/packages/backend/src/core/chart/charts/federation.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/instance.ts b/packages/backend/src/core/chart/charts/instance.ts
index 9df0afb02e..97f3bc6f2b 100644
--- a/packages/backend/src/core/chart/charts/instance.ts
+++ b/packages/backend/src/core/chart/charts/instance.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/notes.ts b/packages/backend/src/core/chart/charts/notes.ts
index df3295dbac..f763b5fffa 100644
--- a/packages/backend/src/core/chart/charts/notes.ts
+++ b/packages/backend/src/core/chart/charts/notes.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/per-user-drive.ts b/packages/backend/src/core/chart/charts/per-user-drive.ts
index 18354359c8..404964d8b7 100644
--- a/packages/backend/src/core/chart/charts/per-user-drive.ts
+++ b/packages/backend/src/core/chart/charts/per-user-drive.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/per-user-following.ts b/packages/backend/src/core/chart/charts/per-user-following.ts
index 79bff2cb66..588ac638de 100644
--- a/packages/backend/src/core/chart/charts/per-user-following.ts
+++ b/packages/backend/src/core/chart/charts/per-user-following.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/per-user-notes.ts b/packages/backend/src/core/chart/charts/per-user-notes.ts
index 0db0e6f07f..e4900772bb 100644
--- a/packages/backend/src/core/chart/charts/per-user-notes.ts
+++ b/packages/backend/src/core/chart/charts/per-user-notes.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/per-user-pv.ts b/packages/backend/src/core/chart/charts/per-user-pv.ts
index cf1b4c71f6..31708fefa8 100644
--- a/packages/backend/src/core/chart/charts/per-user-pv.ts
+++ b/packages/backend/src/core/chart/charts/per-user-pv.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/per-user-reactions.ts b/packages/backend/src/core/chart/charts/per-user-reactions.ts
index 9f4f6e9651..c29c4d2870 100644
--- a/packages/backend/src/core/chart/charts/per-user-reactions.ts
+++ b/packages/backend/src/core/chart/charts/per-user-reactions.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/test-grouped.ts b/packages/backend/src/core/chart/charts/test-grouped.ts
index 00fb872237..7a2844f4ed 100644
--- a/packages/backend/src/core/chart/charts/test-grouped.ts
+++ b/packages/backend/src/core/chart/charts/test-grouped.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/test-intersection.ts b/packages/backend/src/core/chart/charts/test-intersection.ts
index 45a7e805c5..b8d0556c9f 100644
--- a/packages/backend/src/core/chart/charts/test-intersection.ts
+++ b/packages/backend/src/core/chart/charts/test-intersection.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/test-unique.ts b/packages/backend/src/core/chart/charts/test-unique.ts
index e9d38eaf13..f94e008059 100644
--- a/packages/backend/src/core/chart/charts/test-unique.ts
+++ b/packages/backend/src/core/chart/charts/test-unique.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/test.ts b/packages/backend/src/core/chart/charts/test.ts
index 4dd6063b5b..a90dc8f99b 100644
--- a/packages/backend/src/core/chart/charts/test.ts
+++ b/packages/backend/src/core/chart/charts/test.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/charts/users.ts b/packages/backend/src/core/chart/charts/users.ts
index c2026c2aea..d148fc629b 100644
--- a/packages/backend/src/core/chart/charts/users.ts
+++ b/packages/backend/src/core/chart/charts/users.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/chart/core.ts b/packages/backend/src/core/chart/core.ts
index 8d0a89f2d6..aa0cb9dc2b 100644
--- a/packages/backend/src/core/chart/core.ts
+++ b/packages/backend/src/core/chart/core.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -94,6 +94,29 @@ type ToJsonSchema<S> = {
 };
 
 export function getJsonSchema<S extends Schema>(schema: S): ToJsonSchema<Unflatten<ChartResult<S>>> {
+	const unflatten = (str: string, parent: Record<string, any>) => {
+		const keys = str.split('.');
+		const key = keys.shift();
+		const nextKey = keys[0];
+
+		if (key == null) return;
+
+		if (parent.properties[key] == null) {
+			parent.properties[key] = nextKey ? {
+				type: 'object',
+				properties: {},
+				required: [],
+			} : {
+				type: 'array',
+				items: {
+					type: 'number',
+				},
+			};
+		}
+
+		if (nextKey) unflatten(keys.join('.'), parent.properties[key] as Record<string, any>);
+	};
+
 	const jsonSchema = {
 		type: 'object',
 		properties: {} as Record<string, unknown>,
@@ -101,10 +124,7 @@ export function getJsonSchema<S extends Schema>(schema: S): ToJsonSchema<Unflatt
 	};
 
 	for (const k in schema) {
-		jsonSchema.properties[k] = {
-			type: 'array',
-			items: { type: 'number' },
-		};
+		unflatten(k, jsonSchema);
 	}
 
 	return jsonSchema as ToJsonSchema<Unflatten<ChartResult<S>>>;
diff --git a/packages/backend/src/core/chart/entities.ts b/packages/backend/src/core/chart/entities.ts
index b6a1299a2f..e424f2c8c5 100644
--- a/packages/backend/src/core/chart/entities.ts
+++ b/packages/backend/src/core/chart/entities.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts
index 97de891ece..49f256d870 100644
--- a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts
+++ b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -38,13 +38,13 @@ export class AbuseUserReportEntityService {
 			targetUserId: report.targetUserId,
 			assigneeId: report.assigneeId,
 			reporter: this.userEntityService.pack(report.reporter ?? report.reporterId, null, {
-				detail: true,
+				schema: 'UserDetailedNotMe',
 			}),
 			targetUser: this.userEntityService.pack(report.targetUser ?? report.targetUserId, null, {
-				detail: true,
+				schema: 'UserDetailedNotMe',
 			}),
 			assignee: report.assigneeId ? this.userEntityService.pack(report.assignee ?? report.assigneeId, null, {
-				detail: true,
+				schema: 'UserDetailedNotMe',
 			}) : null,
 			forwarded: report.forwarded,
 		});
diff --git a/packages/backend/src/core/entities/AntennaEntityService.ts b/packages/backend/src/core/entities/AntennaEntityService.ts
index 265a61e8ad..64d6a3c978 100644
--- a/packages/backend/src/core/entities/AntennaEntityService.ts
+++ b/packages/backend/src/core/entities/AntennaEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/entities/AppEntityService.ts b/packages/backend/src/core/entities/AppEntityService.ts
index 14a93cda5b..785b84689a 100644
--- a/packages/backend/src/core/entities/AppEntityService.ts
+++ b/packages/backend/src/core/entities/AppEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/entities/AuthSessionEntityService.ts b/packages/backend/src/core/entities/AuthSessionEntityService.ts
index fd356cc89d..72873680c9 100644
--- a/packages/backend/src/core/entities/AuthSessionEntityService.ts
+++ b/packages/backend/src/core/entities/AuthSessionEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/entities/BlockingEntityService.ts b/packages/backend/src/core/entities/BlockingEntityService.ts
index b4760346b7..c8c1520ceb 100644
--- a/packages/backend/src/core/entities/BlockingEntityService.ts
+++ b/packages/backend/src/core/entities/BlockingEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -37,7 +37,7 @@ export class BlockingEntityService {
 			createdAt: this.idService.parse(blocking.id).date.toISOString(),
 			blockeeId: blocking.blockeeId,
 			blockee: this.userEntityService.pack(blocking.blockeeId, me, {
-				detail: true,
+				schema: 'UserDetailedNotMe',
 			}),
 		});
 	}
diff --git a/packages/backend/src/core/entities/ChannelEntityService.ts b/packages/backend/src/core/entities/ChannelEntityService.ts
index 305946b8a6..1ba7ca8e57 100644
--- a/packages/backend/src/core/entities/ChannelEntityService.ts
+++ b/packages/backend/src/core/entities/ChannelEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -51,14 +51,14 @@ export class ChannelEntityService {
 
 		const banner = channel.bannerId ? await this.driveFilesRepository.findOneBy({ id: channel.bannerId }) : null;
 
-		const isFollowing = meId ? await this.channelFollowingsRepository.exist({
+		const isFollowing = meId ? await this.channelFollowingsRepository.exists({
 			where: {
 				followerId: meId,
 				followeeId: channel.id,
 			},
 		}) : false;
 
-		const isFavorited = meId ? await this.channelFavoritesRepository.exist({
+		const isFavorited = meId ? await this.channelFavoritesRepository.exists({
 			where: {
 				userId: meId,
 				channelId: channel.id,
diff --git a/packages/backend/src/core/entities/ClipEntityService.ts b/packages/backend/src/core/entities/ClipEntityService.ts
index 96422894fd..26fcd6714d 100644
--- a/packages/backend/src/core/entities/ClipEntityService.ts
+++ b/packages/backend/src/core/entities/ClipEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -46,7 +46,7 @@ export class ClipEntityService {
 			description: clip.description,
 			isPublic: clip.isPublic,
 			favoritedCount: await this.clipFavoritesRepository.countBy({ clipId: clip.id }),
-			isFavorited: meId ? await this.clipFavoritesRepository.exist({ where: { clipId: clip.id, userId: meId } }) : undefined,
+			isFavorited: meId ? await this.clipFavoritesRepository.exists({ where: { clipId: clip.id, userId: meId } }) : undefined,
 		});
 	}
 
diff --git a/packages/backend/src/core/entities/DriveFileEntityService.ts b/packages/backend/src/core/entities/DriveFileEntityService.ts
index 14be000367..8affe2b3bf 100644
--- a/packages/backend/src/core/entities/DriveFileEntityService.ts
+++ b/packages/backend/src/core/entities/DriveFileEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -259,7 +259,7 @@ export class DriveFileEntityService {
 		options?: PackOptions,
 	): Promise<Packed<'DriveFile'>[]> {
 		const items = await Promise.all(files.map(f => this.packNullable(f, options)));
-		return items.filter((x): x is Packed<'DriveFile'> => x != null);
+		return items.filter(isNotNull);
 	}
 
 	@bindThis
diff --git a/packages/backend/src/core/entities/DriveFolderEntityService.ts b/packages/backend/src/core/entities/DriveFolderEntityService.ts
index 8fa78154b9..299f23ad38 100644
--- a/packages/backend/src/core/entities/DriveFolderEntityService.ts
+++ b/packages/backend/src/core/entities/DriveFolderEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/entities/EmojiEntityService.ts b/packages/backend/src/core/entities/EmojiEntityService.ts
index 5b97cfad5e..841bd731c0 100644
--- a/packages/backend/src/core/entities/EmojiEntityService.ts
+++ b/packages/backend/src/core/entities/EmojiEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -31,6 +31,7 @@ export class EmojiEntityService {
 			category: emoji.category,
 			// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
 			url: emoji.publicUrl || emoji.originalUrl,
+			localOnly: emoji.localOnly ? true : undefined,
 			isSensitive: emoji.isSensitive ? true : undefined,
 			roleIdsThatCanBeUsedThisEmojiAsReaction: emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length > 0 ? emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : undefined,
 		};
diff --git a/packages/backend/src/core/entities/FlashEntityService.ts b/packages/backend/src/core/entities/FlashEntityService.ts
index dc335d9975..db4cf6d360 100644
--- a/packages/backend/src/core/entities/FlashEntityService.ts
+++ b/packages/backend/src/core/entities/FlashEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -42,12 +42,12 @@ export class FlashEntityService {
 			createdAt: this.idService.parse(flash.id).date.toISOString(),
 			updatedAt: flash.updatedAt.toISOString(),
 			userId: flash.userId,
-			user: this.userEntityService.pack(flash.user ?? flash.userId, me), // { detail: true } すると無限ループするので注意
+			user: this.userEntityService.pack(flash.user ?? flash.userId, me), // { schema: 'UserDetailed' } すると無限ループするので注意
 			title: flash.title,
 			summary: flash.summary,
 			script: flash.script,
 			likedCount: flash.likedCount,
-			isLiked: meId ? await this.flashLikesRepository.exist({ where: { flashId: flash.id, userId: meId } }) : undefined,
+			isLiked: meId ? await this.flashLikesRepository.exists({ where: { flashId: flash.id, userId: meId } }) : undefined,
 		});
 	}
 
diff --git a/packages/backend/src/core/entities/FlashLikeEntityService.ts b/packages/backend/src/core/entities/FlashLikeEntityService.ts
index 2eff86217a..6e0b9d6e11 100644
--- a/packages/backend/src/core/entities/FlashLikeEntityService.ts
+++ b/packages/backend/src/core/entities/FlashLikeEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/entities/FollowRequestEntityService.ts b/packages/backend/src/core/entities/FollowRequestEntityService.ts
index 0e0fec9f46..763b75101f 100644
--- a/packages/backend/src/core/entities/FollowRequestEntityService.ts
+++ b/packages/backend/src/core/entities/FollowRequestEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/entities/FollowingEntityService.ts b/packages/backend/src/core/entities/FollowingEntityService.ts
index 52aa979677..24cd33e3f7 100644
--- a/packages/backend/src/core/entities/FollowingEntityService.ts
+++ b/packages/backend/src/core/entities/FollowingEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -89,10 +89,10 @@ export class FollowingEntityService {
 			followeeId: following.followeeId,
 			followerId: following.followerId,
 			followee: opts.populateFollowee ? this.userEntityService.pack(following.followee ?? following.followeeId, me, {
-				detail: true,
+				schema: 'UserDetailedNotMe',
 			}) : undefined,
 			follower: opts.populateFollower ? this.userEntityService.pack(following.follower ?? following.followerId, me, {
-				detail: true,
+				schema: 'UserDetailedNotMe',
 			}) : undefined,
 		});
 	}
diff --git a/packages/backend/src/core/entities/GalleryLikeEntityService.ts b/packages/backend/src/core/entities/GalleryLikeEntityService.ts
index e740701888..f199a81b4d 100644
--- a/packages/backend/src/core/entities/GalleryLikeEntityService.ts
+++ b/packages/backend/src/core/entities/GalleryLikeEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/entities/GalleryPostEntityService.ts b/packages/backend/src/core/entities/GalleryPostEntityService.ts
index d7b960e0d9..101182a9e5 100644
--- a/packages/backend/src/core/entities/GalleryPostEntityService.ts
+++ b/packages/backend/src/core/entities/GalleryPostEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -53,7 +53,7 @@ export class GalleryPostEntityService {
 			tags: post.tags.length > 0 ? post.tags : undefined,
 			isSensitive: post.isSensitive,
 			likedCount: post.likedCount,
-			isLiked: meId ? await this.galleryLikesRepository.exist({ where: { postId: post.id, userId: meId } }) : undefined,
+			isLiked: meId ? await this.galleryLikesRepository.exists({ where: { postId: post.id, userId: meId } }) : undefined,
 		});
 	}
 
diff --git a/packages/backend/src/core/entities/HashtagEntityService.ts b/packages/backend/src/core/entities/HashtagEntityService.ts
index 006e267b12..d798b15807 100644
--- a/packages/backend/src/core/entities/HashtagEntityService.ts
+++ b/packages/backend/src/core/entities/HashtagEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/entities/InstanceEntityService.ts b/packages/backend/src/core/entities/InstanceEntityService.ts
index 515b356dee..b4a518a1c6 100644
--- a/packages/backend/src/core/entities/InstanceEntityService.ts
+++ b/packages/backend/src/core/entities/InstanceEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -8,12 +8,15 @@ import type { Packed } from '@/misc/json-schema.js';
 import type { MiInstance } from '@/models/Instance.js';
 import { MetaService } from '@/core/MetaService.js';
 import { bindThis } from '@/decorators.js';
-import { UtilityService } from '../UtilityService.js';
+import { UtilityService } from '@/core/UtilityService.js';
+import { RoleService } from '@/core/RoleService.js';
+import { MiUser } from '@/models/User.js';
 
 @Injectable()
 export class InstanceEntityService {
 	constructor(
 		private metaService: MetaService,
+		private roleService: RoleService,
 
 		private utilityService: UtilityService,
 	) {
@@ -22,8 +25,11 @@ export class InstanceEntityService {
 	@bindThis
 	public async pack(
 		instance: MiInstance,
+		me?: { id: MiUser['id']; } | null | undefined,
 	): Promise<Packed<'FederationInstance'>> {
 		const meta = await this.metaService.fetch();
+		const iAmModerator = me ? await this.roleService.isModerator(me as MiUser) : false;
+
 		return {
 			id: instance.id,
 			firstRetrievedAt: instance.firstRetrievedAt.toISOString(),
@@ -49,6 +55,7 @@ export class InstanceEntityService {
 			infoUpdatedAt: instance.infoUpdatedAt ? instance.infoUpdatedAt.toISOString() : null,
 			latestRequestReceivedAt: instance.latestRequestReceivedAt ? instance.latestRequestReceivedAt.toISOString() : null,
 			isNSFW: instance.isNSFW,
+			moderationNote: iAmModerator ? instance.moderationNote : null,
 		};
 	}
 
diff --git a/packages/backend/src/core/entities/InviteCodeEntityService.ts b/packages/backend/src/core/entities/InviteCodeEntityService.ts
index 0f15fb5ab2..891543bc0f 100644
--- a/packages/backend/src/core/entities/InviteCodeEntityService.ts
+++ b/packages/backend/src/core/entities/InviteCodeEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts
new file mode 100644
index 0000000000..7e3926c431
--- /dev/null
+++ b/packages/backend/src/core/entities/MetaEntityService.ts
@@ -0,0 +1,157 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Brackets } from 'typeorm';
+import { Inject, Injectable } from '@nestjs/common';
+import JSON5 from 'json5';
+import type { Packed } from '@/misc/json-schema.js';
+import type { MiMeta } from '@/models/Meta.js';
+import type { AdsRepository } from '@/models/_.js';
+import { MetaService } from '@/core/MetaService.js';
+import { bindThis } from '@/decorators.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { InstanceActorService } from '@/core/InstanceActorService.js';
+import type { Config } from '@/config.js';
+import { DI } from '@/di-symbols.js';
+import { DEFAULT_POLICIES } from '@/core/RoleService.js';
+
+@Injectable()
+export class MetaEntityService {
+	constructor(
+		@Inject(DI.config)
+		private config: Config,
+
+		@Inject(DI.adsRepository)
+		private adsRepository: AdsRepository,
+
+		private userEntityService: UserEntityService,
+		private metaService: MetaService,
+		private instanceActorService: InstanceActorService,
+	) { }
+
+	@bindThis
+	public async pack(meta?: MiMeta): Promise<Packed<'MetaLite'>> {
+		let instance = meta;
+
+		if (!instance) {
+			instance = await this.metaService.fetch();
+		}
+
+		const ads = await this.adsRepository.createQueryBuilder('ads')
+			.where('ads.expiresAt > :now', { now: new Date() })
+			.andWhere('ads.startsAt <= :now', { now: new Date() })
+			.andWhere(new Brackets(qb => {
+				// 曜日のビットフラグを確認する
+				qb.where('ads.dayOfWeek & :dayOfWeek > 0', { dayOfWeek: 1 << new Date().getDay() })
+					.orWhere('ads.dayOfWeek = 0');
+			}))
+			.getMany();
+
+		const packed: Packed<'MetaLite'> = {
+			maintainerName: instance.maintainerName,
+			maintainerEmail: instance.maintainerEmail,
+
+			version: this.config.version,
+			providesTarball: this.config.publishTarballInsteadOfProvideRepositoryUrl,
+
+			name: instance.name,
+			shortName: instance.shortName,
+			uri: this.config.url,
+			description: instance.description,
+			langs: instance.langs,
+			tosUrl: instance.termsOfServiceUrl,
+			repositoryUrl: instance.repositoryUrl,
+			feedbackUrl: instance.feedbackUrl,
+			impressumUrl: instance.impressumUrl,
+			donationUrl: instance.donationUrl,
+			privacyPolicyUrl: instance.privacyPolicyUrl,
+			disableRegistration: instance.disableRegistration,
+			emailRequiredForSignup: instance.emailRequiredForSignup,
+			approvalRequiredForSignup: instance.approvalRequiredForSignup,
+			enableHcaptcha: instance.enableHcaptcha,
+			hcaptchaSiteKey: instance.hcaptchaSiteKey,
+			enableMcaptcha: instance.enableMcaptcha,
+			mcaptchaSiteKey: instance.mcaptchaSitekey,
+			mcaptchaInstanceUrl: instance.mcaptchaInstanceUrl,
+			enableRecaptcha: instance.enableRecaptcha,
+			enableAchievements: instance.enableAchievements,
+			recaptchaSiteKey: instance.recaptchaSiteKey,
+			enableTurnstile: instance.enableTurnstile,
+			turnstileSiteKey: instance.turnstileSiteKey,
+			swPublickey: instance.swPublicKey,
+			themeColor: instance.themeColor,
+			mascotImageUrl: instance.mascotImageUrl ?? '/assets/ai.png',
+			bannerUrl: instance.bannerUrl,
+			infoImageUrl: instance.infoImageUrl,
+			serverErrorImageUrl: instance.serverErrorImageUrl,
+			notFoundImageUrl: instance.notFoundImageUrl,
+			iconUrl: instance.iconUrl,
+			backgroundImageUrl: instance.backgroundImageUrl,
+			logoImageUrl: instance.logoImageUrl,
+			maxNoteTextLength: this.config.maxNoteLength,
+			// クライアントの手間を減らすためあらかじめJSONに変換しておく
+			defaultLightTheme: instance.defaultLightTheme ? JSON.stringify(JSON5.parse(instance.defaultLightTheme)) : null,
+			defaultDarkTheme: instance.defaultDarkTheme ? JSON.stringify(JSON5.parse(instance.defaultDarkTheme)) : null,
+			defaultLike: instance.defaultLike,
+			ads: ads.map(ad => ({
+				id: ad.id,
+				url: ad.url,
+				place: ad.place,
+				ratio: ad.ratio,
+				imageUrl: ad.imageUrl,
+				dayOfWeek: ad.dayOfWeek,
+			})),
+			notesPerOneAd: instance.notesPerOneAd,
+			enableEmail: instance.enableEmail,
+			enableServiceWorker: instance.enableServiceWorker,
+
+			translatorAvailable: instance.deeplAuthKey != null || instance.deeplFreeMode && instance.deeplFreeInstance != null,
+
+			serverRules: instance.serverRules,
+
+			policies: { ...DEFAULT_POLICIES, ...instance.policies },
+
+			mediaProxy: this.config.mediaProxy,
+		};
+
+		return packed;
+	}
+
+	@bindThis
+	public async packDetailed(meta?: MiMeta): Promise<Packed<'MetaDetailed'>> {
+		let instance = meta;
+
+		if (!instance) {
+			instance = await this.metaService.fetch();
+		}
+
+		const packed = await this.pack(instance);
+
+		const proxyAccount = instance.proxyAccountId ? await this.userEntityService.pack(instance.proxyAccountId).catch(() => null) : null;
+
+		const packDetailed: Packed<'MetaDetailed'> = {
+			...packed,
+			cacheRemoteFiles: instance.cacheRemoteFiles,
+			cacheRemoteSensitiveFiles: instance.cacheRemoteSensitiveFiles,
+			requireSetup: !await this.instanceActorService.realLocalUsersPresent(),
+			proxyAccountName: proxyAccount ? proxyAccount.username : null,
+			features: {
+				localTimeline: instance.policies.ltlAvailable,
+				globalTimeline: instance.policies.gtlAvailable,
+				registration: !instance.disableRegistration,
+				emailRequiredForSignup: instance.emailRequiredForSignup,
+				hcaptcha: instance.enableHcaptcha,
+				recaptcha: instance.enableRecaptcha,
+				turnstile: instance.enableTurnstile,
+				objectStorage: instance.useObjectStorage,
+				serviceWorker: instance.enableServiceWorker,
+				miauth: true,
+			},
+		};
+
+		return packDetailed;
+	}
+}
+
diff --git a/packages/backend/src/core/entities/ModerationLogEntityService.ts b/packages/backend/src/core/entities/ModerationLogEntityService.ts
index 6729ca2671..205e147bd1 100644
--- a/packages/backend/src/core/entities/ModerationLogEntityService.ts
+++ b/packages/backend/src/core/entities/ModerationLogEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -37,7 +37,7 @@ export class ModerationLogEntityService {
 			info: log.info,
 			userId: log.userId,
 			user: this.userEntityService.pack(log.user ?? log.userId, null, {
-				detail: true,
+				schema: 'UserDetailedNotMe',
 			}),
 		});
 	}
diff --git a/packages/backend/src/core/entities/MutingEntityService.ts b/packages/backend/src/core/entities/MutingEntityService.ts
index 9d672169ba..0a52f429a2 100644
--- a/packages/backend/src/core/entities/MutingEntityService.ts
+++ b/packages/backend/src/core/entities/MutingEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -39,7 +39,7 @@ export class MutingEntityService {
 			expiresAt: muting.expiresAt ? muting.expiresAt.toISOString() : null,
 			muteeId: muting.muteeId,
 			mutee: this.userEntityService.pack(muting.muteeId, me, {
-				detail: true,
+				schema: 'UserDetailedNotMe',
 			}),
 		});
 	}
diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts
index fee96bb80d..86a8670f29 100644
--- a/packages/backend/src/core/entities/NoteEntityService.ts
+++ b/packages/backend/src/core/entities/NoteEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -114,7 +114,7 @@ export class NoteEntityService implements OnModuleInit {
 				hide = false;
 			} else {
 				if (packedNote.renote) {
-					const isFollowing = await this.followingsRepository.exist({
+					const isFollowing = await this.followingsRepository.exists({
 						where: {
 							followeeId: packedNote.renote.userId,
 							followerId: meId,
@@ -124,7 +124,7 @@ export class NoteEntityService implements OnModuleInit {
 					hide = !isFollowing;
 				} else {
 					// フォロワーかどうか
-					const isFollowing = await this.followingsRepository.exist({
+					const isFollowing = await this.followingsRepository.exists({
 						where: {
 							followeeId: packedNote.userId,
 							followerId: meId,
@@ -181,7 +181,7 @@ export class NoteEntityService implements OnModuleInit {
 
 		return {
 			multiple: poll.multiple,
-			expiresAt: poll.expiresAt,
+			expiresAt: poll.expiresAt?.toISOString() ?? null,
 			choices,
 		};
 	}
@@ -342,9 +342,7 @@ export class NoteEntityService implements OnModuleInit {
 			createdAt: this.idService.parse(note.id).date.toISOString(),
 			updatedAt: note.updatedAt ? note.updatedAt.toISOString() : undefined,
 			userId: note.userId,
-			user: this.userEntityService.pack(note.user ?? note.userId, me, {
-				detail: false,
-			}),
+			user: this.userEntityService.pack(note.user ?? note.userId, me),
 			text: text,
 			cw: note.cw,
 			visibility: note.visibility,
@@ -369,6 +367,7 @@ export class NoteEntityService implements OnModuleInit {
 				color: channel.color,
 				isSensitive: channel.isSensitive,
 				allowRenoteToExternal: channel.allowRenoteToExternal,
+				userId: channel.userId,
 			} : undefined,
 			mentions: note.mentions && note.mentions.length > 0 ? note.mentions : undefined,
 			uri: note.uri ?? undefined,
diff --git a/packages/backend/src/core/entities/NoteFavoriteEntityService.ts b/packages/backend/src/core/entities/NoteFavoriteEntityService.ts
index 1c9aed413f..3cdafe48ad 100644
--- a/packages/backend/src/core/entities/NoteFavoriteEntityService.ts
+++ b/packages/backend/src/core/entities/NoteFavoriteEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/entities/NoteReactionEntityService.ts b/packages/backend/src/core/entities/NoteReactionEntityService.ts
index f4aba3e543..3f4fa3cf96 100644
--- a/packages/backend/src/core/entities/NoteReactionEntityService.ts
+++ b/packages/backend/src/core/entities/NoteReactionEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -69,4 +69,19 @@ export class NoteReactionEntityService implements OnModuleInit {
 			} : {}),
 		};
 	}
+
+	@bindThis
+	public async packMany(
+		reactions: MiNoteReaction[],
+		me?: { id: MiUser['id'] } | null | undefined,
+		options?: {
+			withNote: boolean;
+		},
+	): Promise<Packed<'NoteReaction'>[]> {
+		const opts = Object.assign({
+			withNote: false,
+		}, options);
+
+		return Promise.all(reactions.map(reaction => this.pack(reaction, me, opts)));
+	}
 }
diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts
index 704081ed00..18b9d148c4 100644
--- a/packages/backend/src/core/entities/NotificationEntityService.ts
+++ b/packages/backend/src/core/entities/NotificationEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -14,14 +14,14 @@ import type { MiNote } from '@/models/Note.js';
 import type { Packed } from '@/misc/json-schema.js';
 import { bindThis } from '@/decorators.js';
 import { isNotNull } from '@/misc/is-not-null.js';
-import { FilterUnionByProperty, notificationTypes } from '@/types.js';
+import { FilterUnionByProperty, groupedNotificationTypes } from '@/types.js';
+import { CacheService } from '@/core/CacheService.js';
 import { RoleEntityService } from './RoleEntityService.js';
 import type { OnModuleInit } from '@nestjs/common';
 import type { UserEntityService } from './UserEntityService.js';
 import type { NoteEntityService } from './NoteEntityService.js';
 
-const NOTE_REQUIRED_NOTIFICATION_TYPES = new Set(['note', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollEnded'] as (typeof notificationTypes[number])[]);
-const NOTE_REQUIRED_GROUPED_NOTIFICATION_TYPES = new Set(['note', 'mention', 'reply', 'renote', 'renote:grouped', 'quote', 'reaction', 'reaction:grouped', 'pollEnded']);
+const NOTE_REQUIRED_NOTIFICATION_TYPES = new Set(['note', 'mention', 'reply', 'renote', 'renote:grouped', 'quote', 'reaction', 'reaction:grouped', 'pollEnded', 'edited'] as (typeof groupedNotificationTypes[number])[]);
 
 @Injectable()
 export class NotificationEntityService implements OnModuleInit {
@@ -41,6 +41,8 @@ export class NotificationEntityService implements OnModuleInit {
 		@Inject(DI.followRequestsRepository)
 		private followRequestsRepository: FollowRequestsRepository,
 
+		private cacheService: CacheService,
+
 		//private userEntityService: UserEntityService,
 		//private noteEntityService: NoteEntityService,
 	) {
@@ -52,146 +54,61 @@ export class NotificationEntityService implements OnModuleInit {
 		this.roleEntityService = this.moduleRef.get('RoleEntityService');
 	}
 
-	@bindThis
-	public async pack(
-		src: MiNotification,
+	/**
+	 * 通知をパックする共通処理
+	*/
+	async #packInternal <T extends MiNotification | MiGroupedNotification> (
+		src: T,
 		meId: MiUser['id'],
 		// eslint-disable-next-line @typescript-eslint/ban-types
 		options: {
-
+			checkValidNotifier?: boolean;
 		},
 		hint?: {
 			packedNotes: Map<MiNote['id'], Packed<'Note'>>;
-			packedUsers: Map<MiUser['id'], Packed<'User'>>;
+			packedUsers: Map<MiUser['id'], Packed<'UserLite'>>;
 		},
-	): Promise<Packed<'Notification'>> {
+	): Promise<Packed<'Notification'> | null> {
 		const notification = src;
-		const noteIfNeed = NOTE_REQUIRED_NOTIFICATION_TYPES.has(notification.type) && 'noteId' in notification ? (
+
+		if (options.checkValidNotifier !== false && !(await this.#isValidNotifier(notification, meId))) return null;
+
+		const needsNote = NOTE_REQUIRED_NOTIFICATION_TYPES.has(notification.type) && 'noteId' in notification;
+		const noteIfNeed = needsNote ? (
 			hint?.packedNotes != null
 				? hint.packedNotes.get(notification.noteId)
 				: this.noteEntityService.pack(notification.noteId, { id: meId }, {
 					detail: true,
 				})
 		) : undefined;
-		const userIfNeed = 'notifierId' in notification ? (
+		// if the note has been deleted, don't show this notification
+		if (needsNote && !noteIfNeed) return null;
+
+		const needsUser = 'notifierId' in notification;
+		const userIfNeed = needsUser ? (
 			hint?.packedUsers != null
 				? hint.packedUsers.get(notification.notifierId)
-				: this.userEntityService.pack(notification.notifierId, { id: meId }, {
-					detail: false,
-				})
-		) : undefined;
-		const role = notification.type === 'roleAssigned' ? await this.roleEntityService.pack(notification.roleId) : undefined;
-
-		return await awaitAll({
-			id: notification.id,
-			createdAt: new Date(notification.createdAt).toISOString(),
-			type: notification.type,
-			userId: 'notifierId' in notification ? notification.notifierId : undefined,
-			...(userIfNeed != null ? { user: userIfNeed } : {}),
-			...(noteIfNeed != null ? { note: noteIfNeed } : {}),
-			...(notification.type === 'reaction' ? {
-				reaction: notification.reaction,
-			} : {}),
-			...(notification.type === 'roleAssigned' ? {
-				role: role,
-			} : {}),
-			...(notification.type === 'achievementEarned' ? {
-				achievement: notification.achievement,
-			} : {}),
-			...(notification.type === 'app' ? {
-				body: notification.customBody,
-				header: notification.customHeader,
-				icon: notification.customIcon,
-			} : {}),
-		});
-	}
-
-	@bindThis
-	public async packMany(
-		notifications: MiNotification[],
-		meId: MiUser['id'],
-	) {
-		if (notifications.length === 0) return [];
-
-		let validNotifications = notifications;
-
-		const noteIds = validNotifications.map(x => 'noteId' in x ? x.noteId : null).filter(isNotNull);
-		const notes = noteIds.length > 0 ? await this.notesRepository.find({
-			where: { id: In(noteIds) },
-			relations: ['user', 'reply', 'reply.user', 'renote', 'renote.user'],
-		}) : [];
-		const packedNotesArray = await this.noteEntityService.packMany(notes, { id: meId }, {
-			detail: true,
-		});
-		const packedNotes = new Map(packedNotesArray.map(p => [p.id, p]));
-
-		validNotifications = validNotifications.filter(x => !('noteId' in x) || packedNotes.has(x.noteId));
-
-		const userIds = validNotifications.map(x => 'notifierId' in x ? x.notifierId : null).filter(isNotNull);
-		const users = userIds.length > 0 ? await this.usersRepository.find({
-			where: { id: In(userIds) },
-		}) : [];
-		const packedUsersArray = await this.userEntityService.packMany(users, { id: meId }, {
-			detail: false,
-		});
-		const packedUsers = new Map(packedUsersArray.map(p => [p.id, p]));
-
-		// 既に解決されたフォローリクエストの通知を除外
-		const followRequestNotifications = validNotifications.filter((x): x is FilterUnionByProperty<MiGroupedNotification, 'type', 'receiveFollowRequest'> => x.type === 'receiveFollowRequest');
-		if (followRequestNotifications.length > 0) {
-			const reqs = await this.followRequestsRepository.find({
-				where: { followerId: In(followRequestNotifications.map(x => x.notifierId)) },
-			});
-			validNotifications = validNotifications.filter(x => (x.type !== 'receiveFollowRequest') || reqs.some(r => r.followerId === x.notifierId));
-		}
-
-		return await Promise.all(validNotifications.map(x => this.pack(x, meId, {}, {
-			packedNotes,
-			packedUsers,
-		})));
-	}
-
-	@bindThis
-	public async packGrouped(
-		src: MiGroupedNotification,
-		meId: MiUser['id'],
-		// eslint-disable-next-line @typescript-eslint/ban-types
-		options: {
-
-		},
-		hint?: {
-			packedNotes: Map<MiNote['id'], Packed<'Note'>>;
-			packedUsers: Map<MiUser['id'], Packed<'User'>>;
-		},
-	): Promise<Packed<'Notification'>> {
-		const notification = src;
-		const noteIfNeed = NOTE_REQUIRED_GROUPED_NOTIFICATION_TYPES.has(notification.type) && 'noteId' in notification ? (
-			hint?.packedNotes != null
-				? hint.packedNotes.get(notification.noteId)
-				: this.noteEntityService.pack(notification.noteId, { id: meId }, {
-					detail: true,
-				})
-		) : undefined;
-		const userIfNeed = 'notifierId' in notification ? (
-			hint?.packedUsers != null
-				? hint.packedUsers.get(notification.notifierId)
-				: this.userEntityService.pack(notification.notifierId, { id: meId }, {
-					detail: false,
-				})
+				: this.userEntityService.pack(notification.notifierId, { id: meId })
 		) : undefined;
+		// if the user has been deleted, don't show this notification
+		if (needsUser && !userIfNeed) return null;
 
+		// #region Grouped notifications
 		if (notification.type === 'reaction:grouped') {
-			const reactions = await Promise.all(notification.reactions.map(async reaction => {
+			const reactions = (await Promise.all(notification.reactions.map(async reaction => {
 				const user = hint?.packedUsers != null
 					? hint.packedUsers.get(reaction.userId)!
-					: await this.userEntityService.pack(reaction.userId, { id: meId }, {
-						detail: false,
-					});
+					: await this.userEntityService.pack(reaction.userId, { id: meId });
 				return {
 					user,
 					reaction: reaction.reaction,
 				};
-			}));
+			}))).filter(r => isNotNull(r.user));
+			// if all users have been deleted, don't show this notification
+			if (reactions.length === 0) {
+				return null;
+			}
+
 			return await awaitAll({
 				id: notification.id,
 				createdAt: new Date(notification.createdAt).toISOString(),
@@ -200,16 +117,19 @@ export class NotificationEntityService implements OnModuleInit {
 				reactions,
 			});
 		} else if (notification.type === 'renote:grouped') {
-			const users = await Promise.all(notification.userIds.map(userId => {
+			const users = (await Promise.all(notification.userIds.map(userId => {
 				const packedUser = hint?.packedUsers != null ? hint.packedUsers.get(userId) : null;
 				if (packedUser) {
 					return packedUser;
 				}
 
-				return this.userEntityService.pack(userId, { id: meId }, {
-					detail: false,
-				});
-			}));
+				return this.userEntityService.pack(userId, { id: meId });
+			}))).filter(isNotNull);
+			// if all users have been deleted, don't show this notification
+			if (users.length === 0) {
+				return null;
+			}
+
 			return await awaitAll({
 				id: notification.id,
 				createdAt: new Date(notification.createdAt).toISOString(),
@@ -218,8 +138,14 @@ export class NotificationEntityService implements OnModuleInit {
 				users,
 			});
 		}
+		// #endregion
 
-		const role = notification.type === 'roleAssigned' ? await this.roleEntityService.pack(notification.roleId) : undefined;
+		const needsRole = notification.type === 'roleAssigned';
+		const role = needsRole ? await this.roleEntityService.pack(notification.roleId) : undefined;
+		// if the role has been deleted, don't show this notification
+		if (needsRole && !role) {
+			return null;
+		}
 
 		return await awaitAll({
 			id: notification.id,
@@ -245,15 +171,16 @@ export class NotificationEntityService implements OnModuleInit {
 		});
 	}
 
-	@bindThis
-	public async packGroupedMany(
-		notifications: MiGroupedNotification[],
+	async #packManyInternal <T extends MiNotification | MiGroupedNotification>	(
+		notifications: T[],
 		meId: MiUser['id'],
-	) {
+	): Promise<T[]> {
 		if (notifications.length === 0) return [];
 
 		let validNotifications = notifications;
 
+		validNotifications = await this.#filterValidNotifier(validNotifications, meId);
+
 		const noteIds = validNotifications.map(x => 'noteId' in x ? x.noteId : null).filter(isNotNull);
 		const notes = noteIds.length > 0 ? await this.notesRepository.find({
 			where: { id: In(noteIds) },
@@ -275,13 +202,11 @@ export class NotificationEntityService implements OnModuleInit {
 		const users = userIds.length > 0 ? await this.usersRepository.find({
 			where: { id: In(userIds) },
 		}) : [];
-		const packedUsersArray = await this.userEntityService.packMany(users, { id: meId }, {
-			detail: false,
-		});
+		const packedUsersArray = await this.userEntityService.packMany(users, { id: meId });
 		const packedUsers = new Map(packedUsersArray.map(p => [p.id, p]));
 
 		// 既に解決されたフォローリクエストの通知を除外
-		const followRequestNotifications = validNotifications.filter((x): x is FilterUnionByProperty<MiGroupedNotification, 'type', 'receiveFollowRequest'> => x.type === 'receiveFollowRequest');
+		const followRequestNotifications = validNotifications.filter((x): x is FilterUnionByProperty<T, 'type', 'receiveFollowRequest'> => x.type === 'receiveFollowRequest');
 		if (followRequestNotifications.length > 0) {
 			const reqs = await this.followRequestsRepository.find({
 				where: { followerId: In(followRequestNotifications.map(x => x.notifierId)) },
@@ -289,9 +214,107 @@ export class NotificationEntityService implements OnModuleInit {
 			validNotifications = validNotifications.filter(x => (x.type !== 'receiveFollowRequest') || reqs.some(r => r.followerId === x.notifierId));
 		}
 
-		return await Promise.all(validNotifications.map(x => this.packGrouped(x, meId, {}, {
-			packedNotes,
-			packedUsers,
-		})));
+		const packPromises = validNotifications.map(x => {
+			return this.pack(
+				x,
+				meId,
+				{ checkValidNotifier: false },
+				{ packedNotes, packedUsers },
+			);
+		});
+
+		return (await Promise.all(packPromises)).filter(isNotNull);
+	}
+
+	@bindThis
+	public async pack(
+		src: MiNotification | MiGroupedNotification,
+		meId: MiUser['id'],
+		// eslint-disable-next-line @typescript-eslint/ban-types
+		options: {
+			checkValidNotifier?: boolean;
+		},
+		hint?: {
+			packedNotes: Map<MiNote['id'], Packed<'Note'>>;
+			packedUsers: Map<MiUser['id'], Packed<'UserLite'>>;
+		},
+	): Promise<Packed<'Notification'> | null> {
+		return await this.#packInternal(src, meId, options, hint);
+	}
+
+	@bindThis
+	public async packMany(
+		notifications: MiNotification[],
+		meId: MiUser['id'],
+	): Promise<MiNotification[]> {
+		return await this.#packManyInternal(notifications, meId);
+	}
+
+	@bindThis
+	public async packGroupedMany(
+		notifications: MiGroupedNotification[],
+		meId: MiUser['id'],
+	): Promise<MiGroupedNotification[]> {
+		return await this.#packManyInternal(notifications, meId);
+	}
+
+	/**
+	 * notifierが存在するか、ミュートされていないか、サスペンドされていないかを確認するvalidator
+	 */
+	#validateNotifier <T extends MiNotification | MiGroupedNotification> (
+		notification: T,
+		userIdsWhoMeMuting: Set<MiUser['id']>,
+		userMutedInstances: Set<string>,
+		notifiers: MiUser[],
+	): boolean {
+		if (!('notifierId' in notification)) return true;
+		if (userIdsWhoMeMuting.has(notification.notifierId)) return false;
+
+		const notifier = notifiers.find(x => x.id === notification.notifierId) ?? null;
+
+		if (notifier == null) return false;
+		if (notifier.host && userMutedInstances.has(notifier.host)) return false;
+
+		if (notifier.isSuspended) return false;
+
+		return true;
+	}
+
+	/**
+	 * notifierが存在するか、ミュートされていないか、サスペンドされていないかを実際に確認する
+	 */
+	async #isValidNotifier(
+		notification: MiNotification | MiGroupedNotification,
+		meId: MiUser['id'],
+	): Promise<boolean> {
+		return (await this.#filterValidNotifier([notification], meId)).length === 1;
+	}
+
+	/**
+	 * notifierが存在するか、ミュートされていないか、サスペンドされていないかを実際に複数確認する
+	 */
+	async #filterValidNotifier <T extends MiNotification | MiGroupedNotification> (
+		notifications: T[],
+		meId: MiUser['id'],
+	): Promise<T[]> {
+		const [
+			userIdsWhoMeMuting,
+			userMutedInstances,
+		] = await Promise.all([
+			this.cacheService.userMutingsCache.fetch(meId),
+			this.cacheService.userProfileCache.fetch(meId).then(p => new Set(p.mutedInstances)),
+		]);
+
+		const notifierIds = notifications.map(notification => 'notifierId' in notification ? notification.notifierId : null).filter(isNotNull);
+		const notifiers = notifierIds.length > 0 ? await this.usersRepository.find({
+			where: { id: In(notifierIds) },
+		}) : [];
+
+		const filteredNotifications = ((await Promise.all(notifications.map(async (notification) => {
+			const isValid = this.#validateNotifier(notification, userIdsWhoMeMuting, userMutedInstances, notifiers);
+			return isValid ? notification : null;
+		}))) as [T | null] ).filter(isNotNull);
+
+		return filteredNotifications;
 	}
 }
diff --git a/packages/backend/src/core/entities/PageEntityService.ts b/packages/backend/src/core/entities/PageEntityService.ts
index f39ef949db..65c69a49a7 100644
--- a/packages/backend/src/core/entities/PageEntityService.ts
+++ b/packages/backend/src/core/entities/PageEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -14,6 +14,7 @@ import type { MiPage } from '@/models/Page.js';
 import type { MiDriveFile } from '@/models/DriveFile.js';
 import { bindThis } from '@/decorators.js';
 import { IdService } from '@/core/IdService.js';
+import { isNotNull } from '@/misc/is-not-null.js';
 import { UserEntityService } from './UserEntityService.js';
 import { DriveFileEntityService } from './DriveFileEntityService.js';
 
@@ -90,7 +91,7 @@ export class PageEntityService {
 			createdAt: this.idService.parse(page.id).date.toISOString(),
 			updatedAt: page.updatedAt.toISOString(),
 			userId: page.userId,
-			user: this.userEntityService.pack(page.user ?? page.userId, me), // { detail: true } すると無限ループするので注意
+			user: this.userEntityService.pack(page.user ?? page.userId, me), // { schema: 'UserDetailed' } すると無限ループするので注意
 			content: page.content,
 			variables: page.variables,
 			title: page.title,
@@ -102,9 +103,9 @@ export class PageEntityService {
 			script: page.script,
 			eyeCatchingImageId: page.eyeCatchingImageId,
 			eyeCatchingImage: page.eyeCatchingImageId ? await this.driveFileEntityService.pack(page.eyeCatchingImageId) : null,
-			attachedFiles: this.driveFileEntityService.packMany((await Promise.all(attachedFiles)).filter((x): x is MiDriveFile => x != null)),
+			attachedFiles: this.driveFileEntityService.packMany((await Promise.all(attachedFiles)).filter(isNotNull)),
 			likedCount: page.likedCount,
-			isLiked: meId ? await this.pageLikesRepository.exist({ where: { pageId: page.id, userId: meId } }) : undefined,
+			isLiked: meId ? await this.pageLikesRepository.exists({ where: { pageId: page.id, userId: meId } }) : undefined,
 		});
 	}
 
diff --git a/packages/backend/src/core/entities/PageLikeEntityService.ts b/packages/backend/src/core/entities/PageLikeEntityService.ts
index 4dc691ab93..cfccbcb660 100644
--- a/packages/backend/src/core/entities/PageLikeEntityService.ts
+++ b/packages/backend/src/core/entities/PageLikeEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/entities/RenoteMutingEntityService.ts b/packages/backend/src/core/entities/RenoteMutingEntityService.ts
index 3f9dc9180a..0b05a5db80 100644
--- a/packages/backend/src/core/entities/RenoteMutingEntityService.ts
+++ b/packages/backend/src/core/entities/RenoteMutingEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -38,7 +38,7 @@ export class RenoteMutingEntityService {
 			createdAt: this.idService.parse(muting.id).date.toISOString(),
 			muteeId: muting.muteeId,
 			mutee: this.userEntityService.pack(muting.muteeId, me, {
-				detail: true,
+				schema: 'UserDetailedNotMe',
 			}),
 		});
 	}
diff --git a/packages/backend/src/core/entities/ReversiGameEntityService.ts b/packages/backend/src/core/entities/ReversiGameEntityService.ts
new file mode 100644
index 0000000000..32cbe631e4
--- /dev/null
+++ b/packages/backend/src/core/entities/ReversiGameEntityService.ts
@@ -0,0 +1,120 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import { DI } from '@/di-symbols.js';
+import type { ReversiGamesRepository } from '@/models/_.js';
+import { awaitAll } from '@/misc/prelude/await-all.js';
+import type { Packed } from '@/misc/json-schema.js';
+import type { } from '@/models/Blocking.js';
+import type { MiReversiGame } from '@/models/ReversiGame.js';
+import { bindThis } from '@/decorators.js';
+import { IdService } from '@/core/IdService.js';
+import { UserEntityService } from './UserEntityService.js';
+
+@Injectable()
+export class ReversiGameEntityService {
+	constructor(
+		@Inject(DI.reversiGamesRepository)
+		private reversiGamesRepository: ReversiGamesRepository,
+
+		private userEntityService: UserEntityService,
+		private idService: IdService,
+	) {
+	}
+
+	@bindThis
+	public async packDetail(
+		src: MiReversiGame['id'] | MiReversiGame,
+	): Promise<Packed<'ReversiGameDetailed'>> {
+		const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
+
+		const users = await Promise.all([
+			this.userEntityService.pack(game.user1 ?? game.user1Id),
+			this.userEntityService.pack(game.user2 ?? game.user2Id),
+		]);
+
+		return await awaitAll({
+			id: game.id,
+			createdAt: this.idService.parse(game.id).date.toISOString(),
+			startedAt: game.startedAt && game.startedAt.toISOString(),
+			endedAt: game.endedAt && game.endedAt.toISOString(),
+			isStarted: game.isStarted,
+			isEnded: game.isEnded,
+			form1: game.form1,
+			form2: game.form2,
+			user1Ready: game.user1Ready,
+			user2Ready: game.user2Ready,
+			user1Id: game.user1Id,
+			user2Id: game.user2Id,
+			user1: users[0],
+			user2: users[1],
+			winnerId: game.winnerId,
+			winner: game.winnerId ? users.find(u => u.id === game.winnerId)! : null,
+			surrenderedUserId: game.surrenderedUserId,
+			timeoutUserId: game.timeoutUserId,
+			black: game.black,
+			bw: game.bw,
+			isLlotheo: game.isLlotheo,
+			canPutEverywhere: game.canPutEverywhere,
+			loopedBoard: game.loopedBoard,
+			timeLimitForEachTurn: game.timeLimitForEachTurn,
+			noIrregularRules: game.noIrregularRules,
+			logs: game.logs,
+			map: game.map,
+		});
+	}
+
+	@bindThis
+	public packDetailMany(
+		xs: MiReversiGame[],
+	) {
+		return Promise.all(xs.map(x => this.packDetail(x)));
+	}
+
+	@bindThis
+	public async packLite(
+		src: MiReversiGame['id'] | MiReversiGame,
+	): Promise<Packed<'ReversiGameLite'>> {
+		const game = typeof src === 'object' ? src : await this.reversiGamesRepository.findOneByOrFail({ id: src });
+
+		const users = await Promise.all([
+			this.userEntityService.pack(game.user1 ?? game.user1Id),
+			this.userEntityService.pack(game.user2 ?? game.user2Id),
+		]);
+
+		return await awaitAll({
+			id: game.id,
+			createdAt: this.idService.parse(game.id).date.toISOString(),
+			startedAt: game.startedAt && game.startedAt.toISOString(),
+			endedAt: game.endedAt && game.endedAt.toISOString(),
+			isStarted: game.isStarted,
+			isEnded: game.isEnded,
+			user1Id: game.user1Id,
+			user2Id: game.user2Id,
+			user1: users[0],
+			user2: users[1],
+			winnerId: game.winnerId,
+			winner: game.winnerId ? users.find(u => u.id === game.winnerId)! : null,
+			surrenderedUserId: game.surrenderedUserId,
+			timeoutUserId: game.timeoutUserId,
+			black: game.black,
+			bw: game.bw,
+			isLlotheo: game.isLlotheo,
+			canPutEverywhere: game.canPutEverywhere,
+			loopedBoard: game.loopedBoard,
+			timeLimitForEachTurn: game.timeLimitForEachTurn,
+			noIrregularRules: game.noIrregularRules,
+		});
+	}
+
+	@bindThis
+	public packLiteMany(
+		xs: MiReversiGame[],
+	) {
+		return Promise.all(xs.map(x => this.packLite(x)));
+	}
+}
+
diff --git a/packages/backend/src/core/entities/RoleEntityService.ts b/packages/backend/src/core/entities/RoleEntityService.ts
index 5563f9a1ac..2a7dc37bce 100644
--- a/packages/backend/src/core/entities/RoleEntityService.ts
+++ b/packages/backend/src/core/entities/RoleEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/entities/SigninEntityService.ts b/packages/backend/src/core/entities/SigninEntityService.ts
index 6bde3e589a..00b124d594 100644
--- a/packages/backend/src/core/entities/SigninEntityService.ts
+++ b/packages/backend/src/core/entities/SigninEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts
index adb7dfbf86..8f5d986fac 100644
--- a/packages/backend/src/core/entities/UserEntityService.ts
+++ b/packages/backend/src/core/entities/UserEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -25,19 +25,12 @@ import { IdService } from '@/core/IdService.js';
 import type { AnnouncementService } from '@/core/AnnouncementService.js';
 import type { CustomEmojiService } from '@/core/CustomEmojiService.js';
 import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
+import { isNotNull } from '@/misc/is-not-null.js';
 import type { OnModuleInit } from '@nestjs/common';
 import type { NoteEntityService } from './NoteEntityService.js';
 import type { DriveFileEntityService } from './DriveFileEntityService.js';
 import type { PageEntityService } from './PageEntityService.js';
 
-type IsUserDetailed<Detailed extends boolean> = Detailed extends true ? Packed<'UserDetailed'> : Packed<'UserLite'>;
-type IsMeAndIsUserDetailed<ExpectsMe extends boolean | null, Detailed extends boolean> =
-	Detailed extends true ?
-		ExpectsMe extends true ? Packed<'MeDetailed'> :
-		ExpectsMe extends false ? Packed<'UserDetailedNotMe'> :
-		Packed<'UserDetailed'> :
-	Packed<'UserLite'>;
-
 const Ajv = _Ajv.default;
 const ajv = new Ajv();
 
@@ -161,43 +154,43 @@ export class UserEntityService implements OnModuleInit {
 				followerId: me,
 				followeeId: target,
 			}),
-			this.followingsRepository.exist({
+			this.followingsRepository.exists({
 				where: {
 					followerId: target,
 					followeeId: me,
 				},
 			}),
-			this.followRequestsRepository.exist({
+			this.followRequestsRepository.exists({
 				where: {
 					followerId: me,
 					followeeId: target,
 				},
 			}),
-			this.followRequestsRepository.exist({
+			this.followRequestsRepository.exists({
 				where: {
 					followerId: target,
 					followeeId: me,
 				},
 			}),
-			this.blockingsRepository.exist({
+			this.blockingsRepository.exists({
 				where: {
 					blockerId: me,
 					blockeeId: target,
 				},
 			}),
-			this.blockingsRepository.exist({
+			this.blockingsRepository.exists({
 				where: {
 					blockerId: target,
 					blockeeId: me,
 				},
 			}),
-			this.mutingsRepository.exist({
+			this.mutingsRepository.exists({
 				where: {
 					muterId: me,
 					muteeId: target,
 				},
 			}),
-			this.renoteMutingsRepository.exist({
+			this.renoteMutingsRepository.exists({
 				where: {
 					muterId: me,
 					muteeId: target,
@@ -224,7 +217,7 @@ export class UserEntityService implements OnModuleInit {
 		/*
 		const myAntennas = (await this.antennaService.getAntennas()).filter(a => a.userId === userId);
 
-		const isUnread = (myAntennas.length > 0 ? await this.antennaNotesRepository.exist({
+		const isUnread = (myAntennas.length > 0 ? await this.antennaNotesRepository.exists({
 			where: {
 				antennaId: In(myAntennas.map(x => x.id)),
 				read: false,
@@ -304,17 +297,17 @@ export class UserEntityService implements OnModuleInit {
 		return `${this.config.url}/users/${userId}`;
 	}
 
-	public async pack<ExpectsMe extends boolean | null = null, D extends boolean = false>(
+	public async pack<S extends 'MeDetailed' | 'UserDetailedNotMe' | 'UserDetailed' | 'UserLite' = 'UserLite'>(
 		src: MiUser['id'] | MiUser,
 		me?: { id: MiUser['id']; } | null | undefined,
 		options?: {
-			detail?: D,
+			schema?: S,
 			includeSecrets?: boolean,
 			userProfile?: MiUserProfile,
 		},
-	): Promise<IsMeAndIsUserDetailed<ExpectsMe, D>> {
+	): Promise<Packed<S>> {
 		const opts = Object.assign({
-			detail: false,
+			schema: 'UserLite',
 			includeSecrets: false,
 		}, options);
 
@@ -346,19 +339,20 @@ export class UserEntityService implements OnModuleInit {
 			});
 		}
 
+		const isDetailed = opts.schema !== 'UserLite';
 		const meId = me ? me.id : null;
 		const isMe = meId === user.id;
 		const iAmModerator = me ? await this.roleService.isModerator(me as MiUser) : false;
 
-		const relation = meId && !isMe && opts.detail ? await this.getRelation(meId, user.id) : null;
-		const pins = opts.detail ? await this.userNotePiningsRepository.createQueryBuilder('pin')
+		const relation = meId && !isMe && isDetailed ? await this.getRelation(meId, user.id) : null;
+		const pins = isDetailed ? await this.userNotePiningsRepository.createQueryBuilder('pin')
 			.where('pin.userId = :userId', { userId: user.id })
 			.innerJoinAndSelect('pin.note', 'note')
 			.orderBy('pin.id', 'DESC')
 			.getMany() : [];
-		const profile = opts.detail ? (opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id })) : null;
+		const profile = isDetailed ? (opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id })) : null;
 
-		const mastoapi = !opts.detail ? opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id }) : null;
+		const mastoapi = !isDetailed ? opts.userProfile ?? await this.userProfilesRepository.findOneByOrFail({ userId: user.id }) : null;
 
 		const followingCount = profile == null ? null :
 			(profile.followingVisibility === 'public') || isMe ? user.followingCount :
@@ -370,17 +364,16 @@ export class UserEntityService implements OnModuleInit {
 			(profile.followersVisibility === 'followers') && (relation && relation.isFollowing) ? user.followersCount :
 			null;
 
-		const isModerator = isMe && opts.detail ? this.roleService.isModerator(user) : null;
-		const isAdmin = isMe && opts.detail ? this.roleService.isAdministrator(user) : null;
-		const unreadAnnouncements = isMe && opts.detail ?
+		const isModerator = isMe && isDetailed ? this.roleService.isModerator(user) : null;
+		const isAdmin = isMe && isDetailed ? this.roleService.isAdministrator(user) : null;
+		const unreadAnnouncements = isMe && isDetailed ?
 			(await this.announcementService.getUnreadAnnouncements(user)).map((announcement) => ({
 				createdAt: this.idService.parse(announcement.id).date.toISOString(),
 				...announcement,
 			})) : null;
 
 		const checkHost = user.host == null ? this.config.host : user.host;
-		
-		const notificationsInfo = isMe && opts.detail ? await this.getNotificationsInfo(user.id) : null;
+		const notificationsInfo = isMe && isDetailed ? await this.getNotificationsInfo(user.id) : null;
 
 		const packed = {
 			id: user.id,
@@ -425,13 +418,13 @@ export class UserEntityService implements OnModuleInit {
 				displayOrder: r.displayOrder,
 			}))) : undefined,
 
-			...(opts.detail ? {
+			...(isDetailed ? {
 				url: profile!.url,
 				uri: user.uri,
 				movedTo: user.movedToUri ? this.apPersonService.resolvePerson(user.movedToUri).then(user => user.id).catch(() => null) : null,
 				alsoKnownAs: user.alsoKnownAs
 					? Promise.all(user.alsoKnownAs.map(uri => this.apPersonService.fetchPerson(uri).then(user => user?.id).catch(() => null)))
-						.then(xs => xs.length === 0 ? null : xs.filter(x => x != null) as string[])
+						.then(xs => xs.length === 0 ? null : xs.filter(isNotNull))
 					: null,
 				updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null,
 				lastFetchedAt: user.lastFetchedAt ? user.lastFetchedAt.toISOString() : null,
@@ -453,7 +446,7 @@ export class UserEntityService implements OnModuleInit {
 				}),
 				pinnedPageId: profile!.pinnedPageId,
 				pinnedPage: profile!.pinnedPageId ? this.pageEntityService.pack(profile!.pinnedPageId, me) : null,
-				publicReactions: profile!.publicReactions,
+				publicReactions: this.isLocalUser(user) ? profile!.publicReactions : false, // https://github.com/misskey-dev/misskey/issues/12964
 				followersVisibility: profile!.followersVisibility,
 				followingVisibility: profile!.followingVisibility,
 				twoFactorEnabled: profile!.twoFactorEnabled,
@@ -480,7 +473,7 @@ export class UserEntityService implements OnModuleInit {
 				moderationNote: iAmModerator ? (profile!.moderationNote ?? '') : undefined,
 			} : {}),
 
-			...(opts.detail && isMe ? {
+			...(isDetailed && isMe ? {
 				avatarId: user.avatarId,
 				bannerId: user.bannerId,
 				backgroundId: user.backgroundId,
@@ -554,19 +547,19 @@ export class UserEntityService implements OnModuleInit {
 				notify: relation.following?.notify ?? 'none',
 				withReplies: relation.following?.withReplies ?? false,
 			} : {}),
-		} as Promiseable<Packed<'User'>> as Promiseable<IsMeAndIsUserDetailed<ExpectsMe, D>>;
+		} as Promiseable<Packed<S>>;
 
 		return await awaitAll(packed);
 	}
 
-	public packMany<D extends boolean = false>(
+	public packMany<S extends 'MeDetailed' | 'UserDetailedNotMe' | 'UserDetailed' | 'UserLite' = 'UserLite'>(
 		users: (MiUser['id'] | MiUser)[],
 		me?: { id: MiUser['id'] } | null | undefined,
 		options?: {
-			detail?: D,
+			schema?: S,
 			includeSecrets?: boolean,
 		},
-	): Promise<IsUserDetailed<D>[]> {
+	): Promise<Packed<S>[]> {
 		return Promise.all(users.map(u => this.pack(u, me, options)));
 	}
 }
diff --git a/packages/backend/src/core/entities/UserListEntityService.ts b/packages/backend/src/core/entities/UserListEntityService.ts
index 31ab7293da..09cab24521 100644
--- a/packages/backend/src/core/entities/UserListEntityService.ts
+++ b/packages/backend/src/core/entities/UserListEntityService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/daemons/DaemonModule.ts b/packages/backend/src/daemons/DaemonModule.ts
index 236985076c..a67907e6dd 100644
--- a/packages/backend/src/daemons/DaemonModule.ts
+++ b/packages/backend/src/daemons/DaemonModule.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/daemons/QueueStatsService.ts b/packages/backend/src/daemons/QueueStatsService.ts
index 5edc0f45ab..ede104b9fe 100644
--- a/packages/backend/src/daemons/QueueStatsService.ts
+++ b/packages/backend/src/daemons/QueueStatsService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/daemons/ServerStatsService.ts b/packages/backend/src/daemons/ServerStatsService.ts
index c5ef9b2fa3..2c70344c94 100644
--- a/packages/backend/src/daemons/ServerStatsService.ts
+++ b/packages/backend/src/daemons/ServerStatsService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -37,7 +37,7 @@ export class ServerStatsService implements OnApplicationShutdown {
 		const log = [] as any[];
 
 		ev.on('requestServerStatsLog', x => {
-			ev.emit(`serverStatsLog:${x.id}`, log.slice(0, x.length ?? 50));
+			ev.emit(`serverStatsLog:${x.id}`, log.slice(0, x.length));
 		});
 
 		const tick = async () => {
diff --git a/packages/backend/src/decorators.ts b/packages/backend/src/decorators.ts
index 6b439978db..21777657d1 100644
--- a/packages/backend/src/decorators.ts
+++ b/packages/backend/src/decorators.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/di-symbols.ts b/packages/backend/src/di-symbols.ts
index 0c5ac8f2d3..564a8db70a 100644
--- a/packages/backend/src/di-symbols.ts
+++ b/packages/backend/src/di-symbols.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -79,5 +79,7 @@ export const DI = {
 	flashLikesRepository: Symbol('flashLikesRepository'),
 	userMemosRepository: Symbol('userMemosRepository'),
 	noteEditRepository: Symbol('noteEditRepository'),
+	bubbleGameRecordsRepository: Symbol('bubbleGameRecordsRepository'),
+	reversiGamesRepository: Symbol('reversiGamesRepository'),
 	//#endregion
 };
diff --git a/packages/backend/src/env.ts b/packages/backend/src/env.ts
index af1c3bdd3c..ba44cfa2e6 100644
--- a/packages/backend/src/env.ts
+++ b/packages/backend/src/env.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/global.d.ts b/packages/backend/src/global.d.ts
index a9e6243cc4..2f19e85525 100644
--- a/packages/backend/src/global.d.ts
+++ b/packages/backend/src/global.d.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/logger.ts b/packages/backend/src/logger.ts
index 5c10559ec6..d4705af601 100644
--- a/packages/backend/src/logger.ts
+++ b/packages/backend/src/logger.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -71,8 +71,11 @@ export default class Logger {
 		let log = `${l} ${worker}\t[${contexts.join(' ')}]\t${m}`;
 		if (envOption.withLogTime) log = chalk.gray(time) + ' ' + log;
 
-		console.log(important ? chalk.bold(log) : log);
-		if (level === 'error' && data) console.log(data);
+		const args: unknown[] = [important ? chalk.bold(log) : log];
+		if (data != null) {
+			args.push(data);
+		}
+		console.log(...args);
 	}
 
 	@bindThis
diff --git a/packages/backend/src/misc/FileWriterStream.ts b/packages/backend/src/misc/FileWriterStream.ts
new file mode 100644
index 0000000000..367a8eb560
--- /dev/null
+++ b/packages/backend/src/misc/FileWriterStream.ts
@@ -0,0 +1,36 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import * as fs from 'node:fs/promises';
+import type { PathLike } from 'node:fs';
+
+/**
+ * `fs.createWriteStream()`相当のことを行う`WritableStream` (Web標準)
+ */
+export class FileWriterStream extends WritableStream<Uint8Array> {
+	constructor(path: PathLike) {
+		let file: fs.FileHandle | null = null;
+
+		super({
+			start: async () => {
+				file = await fs.open(path, 'a');
+			},
+			write: async (chunk, controller) => {
+				if (file === null) {
+					controller.error();
+					throw new Error();
+				}
+
+				await file.write(chunk);
+			},
+			close: async () => {
+				await file?.close();
+			},
+			abort: async () => {
+				await file?.close();
+			},
+		});
+	}
+}
diff --git a/packages/backend/src/misc/JsonArrayStream.ts b/packages/backend/src/misc/JsonArrayStream.ts
new file mode 100644
index 0000000000..754938989d
--- /dev/null
+++ b/packages/backend/src/misc/JsonArrayStream.ts
@@ -0,0 +1,35 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { TransformStream } from 'node:stream/web';
+
+/**
+ * ストリームに流れてきた各データについて`JSON.stringify()`した上で、それらを一つの配列にまとめる
+ */
+export class JsonArrayStream extends TransformStream<unknown, string> {
+	constructor() {
+		/** 最初の要素かどうかを変数に記録 */
+		let isFirst = true;
+
+		super({
+			start(controller) {
+				controller.enqueue('[');
+			},
+			flush(controller) {
+				controller.enqueue(']');
+			},
+			transform(chunk, controller) {
+				if (isFirst) {
+					isFirst = false;
+				} else {
+					// 妥当なJSON配列にするためには最初以外の要素の前に`,`を挿入しなければならない
+					controller.enqueue(',\n');
+				}
+
+				controller.enqueue(JSON.stringify(chunk));
+			},
+		});
+	}
+}
diff --git a/packages/backend/src/misc/acct.ts b/packages/backend/src/misc/acct.ts
index 5db72746c0..3d729b1151 100644
--- a/packages/backend/src/misc/acct.ts
+++ b/packages/backend/src/misc/acct.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/cache.ts b/packages/backend/src/misc/cache.ts
index c235871931..bba64a06ef 100644
--- a/packages/backend/src/misc/cache.ts
+++ b/packages/backend/src/misc/cache.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -186,28 +186,18 @@ export class RedisSingleCache<T> {
 
 // TODO: メモリ節約のためあまり参照されないキーを定期的に削除できるようにする?
 
-function nothingToDo<T, V = T>(value: T): V {
-	return value as unknown as V;
-}
-
-export class MemoryKVCache<T, V = T> {
-	public cache: Map<string, { date: number; value: V; }>;
+export class MemoryKVCache<T> {
+	/**
+	 * データを持つマップ
+	 * @deprecated これを直接操作するべきではない
+	 */
+	public cache: Map<string, { date: number; value: T; }>;
 	private lifetime: number;
 	private gcIntervalHandle: NodeJS.Timeout;
-	private toMapConverter: (value: T) => V;
-	private fromMapConverter: (cached: V) => T | undefined;
 
-	constructor(lifetime: MemoryKVCache<never>['lifetime'], options: {
-		toMapConverter: (value: T) => V;
-		fromMapConverter: (cached: V) => T | undefined;
-	} = {
-		toMapConverter: nothingToDo,
-		fromMapConverter: nothingToDo,
-	}) {
+	constructor(lifetime: MemoryKVCache<never>['lifetime']) {
 		this.cache = new Map();
 		this.lifetime = lifetime;
-		this.toMapConverter = options.toMapConverter;
-		this.fromMapConverter = options.fromMapConverter;
 
 		this.gcIntervalHandle = setInterval(() => {
 			this.gc();
@@ -215,10 +205,14 @@ export class MemoryKVCache<T, V = T> {
 	}
 
 	@bindThis
+	/**
+	 * Mapにキャッシュをセットします
+	 * @deprecated これを直接呼び出すべきではない。InternalEventなどで変更を全てのプロセス/マシンに通知するべき
+	 */
 	public set(key: string, value: T): void {
 		this.cache.set(key, {
 			date: Date.now(),
-			value: this.toMapConverter(value),
+			value,
 		});
 	}
 
@@ -230,7 +224,7 @@ export class MemoryKVCache<T, V = T> {
 			this.cache.delete(key);
 			return undefined;
 		}
-		return this.fromMapConverter(cached.value);
+		return cached.value;
 	}
 
 	@bindThis
@@ -241,10 +235,9 @@ export class MemoryKVCache<T, V = T> {
 	/**
 	 * キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します
 	 * optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
-	 * fetcherの引数はcacheに保存されている値があれば渡されます
 	 */
 	@bindThis
-	public async fetch(key: string, fetcher: (value: V | undefined) => Promise<T>, validator?: (cachedValue: T) => boolean): Promise<T> {
+	public async fetch(key: string, fetcher: () => Promise<T>, validator?: (cachedValue: T) => boolean): Promise<T> {
 		const cachedValue = this.get(key);
 		if (cachedValue !== undefined) {
 			if (validator) {
@@ -259,7 +252,7 @@ export class MemoryKVCache<T, V = T> {
 		}
 
 		// Cache MISS
-		const value = await fetcher(this.cache.get(key)?.value);
+		const value = await fetcher();
 		this.set(key, value);
 		return value;
 	}
@@ -267,10 +260,9 @@ export class MemoryKVCache<T, V = T> {
 	/**
 	 * キャッシュがあればそれを返し、無ければfetcherを呼び出して結果をキャッシュ&返します
 	 * optional: キャッシュが存在してもvalidatorでfalseを返すとキャッシュ無効扱いにします
-	 * fetcherの引数はcacheに保存されている値があれば渡されます
 	 */
 	@bindThis
-	public async fetchMaybe(key: string, fetcher: (value: V | undefined) => Promise<T | undefined>, validator?: (cachedValue: T) => boolean): Promise<T | undefined> {
+	public async fetchMaybe(key: string, fetcher: () => Promise<T | undefined>, validator?: (cachedValue: T) => boolean): Promise<T | undefined> {
 		const cachedValue = this.get(key);
 		if (cachedValue !== undefined) {
 			if (validator) {
@@ -285,7 +277,7 @@ export class MemoryKVCache<T, V = T> {
 		}
 
 		// Cache MISS
-		const value = await fetcher(this.cache.get(key)?.value);
+		const value = await fetcher();
 		if (value !== undefined) {
 			this.set(key, value);
 		}
diff --git a/packages/backend/src/misc/check-https.ts b/packages/backend/src/misc/check-https.ts
index 0b13ccabdd..15a54f6ce7 100644
--- a/packages/backend/src/misc/check-https.ts
+++ b/packages/backend/src/misc/check-https.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/check-word-mute.ts b/packages/backend/src/misc/check-word-mute.ts
index cef5595451..c50f2b723c 100644
--- a/packages/backend/src/misc/check-word-mute.ts
+++ b/packages/backend/src/misc/check-word-mute.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/clone.ts b/packages/backend/src/misc/clone.ts
index 9d20deac3b..ed05485649 100644
--- a/packages/backend/src/misc/clone.ts
+++ b/packages/backend/src/misc/clone.ts
@@ -1,12 +1,12 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 // structredCloneが遅いため
 // SEE: http://var.blog.jp/archives/86038606.html
 
-type Cloneable = string | number | boolean | null | { [key: string]: Cloneable } | Cloneable[];
+type Cloneable = string | number | boolean | null | undefined | { [key: string]: Cloneable } | Cloneable[];
 
 export function deepClone<T extends Cloneable>(x: T): T {
 	if (typeof x === 'object') {
@@ -14,7 +14,7 @@ export function deepClone<T extends Cloneable>(x: T): T {
 		if (Array.isArray(x)) return x.map(deepClone) as T;
 		const obj = {} as Record<string, Cloneable>;
 		for (const [k, v] of Object.entries(x)) {
-			obj[k] = deepClone(v);
+			obj[k] = v === undefined ? undefined : deepClone(v);
 		}
 		return obj as T;
 	} else {
diff --git a/packages/backend/src/misc/content-disposition.ts b/packages/backend/src/misc/content-disposition.ts
index 1ac8c88d21..467b5057d6 100644
--- a/packages/backend/src/misc/content-disposition.ts
+++ b/packages/backend/src/misc/content-disposition.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/correct-filename.ts b/packages/backend/src/misc/correct-filename.ts
index 9130af44c3..f7ee02781d 100644
--- a/packages/backend/src/misc/correct-filename.ts
+++ b/packages/backend/src/misc/correct-filename.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -21,7 +21,7 @@ const extRegExp = /\.[0-9a-zA-Z]+$/i;
 /**
  * 与えられた拡張子とファイル名が一致しているかどうかを確認し、
  * 一致していない場合は拡張子を付与して返す
- * 
+ *
  * extはfile-typeのextを想定
  */
 export function correctFilename(filename: string, ext: string | null) {
diff --git a/packages/backend/src/misc/create-temp.ts b/packages/backend/src/misc/create-temp.ts
index 5b4943b7a2..6cc896046f 100644
--- a/packages/backend/src/misc/create-temp.ts
+++ b/packages/backend/src/misc/create-temp.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/dev-null.ts b/packages/backend/src/misc/dev-null.ts
index f510177c0b..4d9806fbe8 100644
--- a/packages/backend/src/misc/dev-null.ts
+++ b/packages/backend/src/misc/dev-null.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/emoji-regex.ts b/packages/backend/src/misc/emoji-regex.ts
index 04c2f2e913..53e66298a6 100644
--- a/packages/backend/src/misc/emoji-regex.ts
+++ b/packages/backend/src/misc/emoji-regex.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/extract-custom-emojis-from-mfm.ts b/packages/backend/src/misc/extract-custom-emojis-from-mfm.ts
index 0e8dfd21f8..36a9b8e1f4 100644
--- a/packages/backend/src/misc/extract-custom-emojis-from-mfm.ts
+++ b/packages/backend/src/misc/extract-custom-emojis-from-mfm.ts
@@ -1,9 +1,9 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import * as mfm from '@sharkey/sfm-js';
+import * as mfm from '@transfem-org/sfm-js';
 import { unique } from '@/misc/prelude/array.js';
 
 export function extractCustomEmojisFromMfm(nodes: mfm.MfmNode[]): string[] {
diff --git a/packages/backend/src/misc/extract-hashtags.ts b/packages/backend/src/misc/extract-hashtags.ts
index 3598d90093..ed7606d995 100644
--- a/packages/backend/src/misc/extract-hashtags.ts
+++ b/packages/backend/src/misc/extract-hashtags.ts
@@ -1,9 +1,9 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import * as mfm from '@sharkey/sfm-js';
+import * as mfm from '@transfem-org/sfm-js';
 import { unique } from '@/misc/prelude/array.js';
 
 export function extractHashtags(nodes: mfm.MfmNode[]): string[] {
diff --git a/packages/backend/src/misc/extract-mentions.ts b/packages/backend/src/misc/extract-mentions.ts
index b0897b05a8..bb21c32ffb 100644
--- a/packages/backend/src/misc/extract-mentions.ts
+++ b/packages/backend/src/misc/extract-mentions.ts
@@ -1,11 +1,11 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 // test is located in test/extract-mentions
 
-import * as mfm from '@sharkey/sfm-js';
+import * as mfm from '@transfem-org/sfm-js';
 
 export function extractMentions(nodes: mfm.MfmNode[]): mfm.MfmMention['props'][] {
 	// TODO: 重複を削除
diff --git a/packages/backend/src/misc/fastify-hook-handlers.ts b/packages/backend/src/misc/fastify-hook-handlers.ts
new file mode 100644
index 0000000000..49a48f6a6b
--- /dev/null
+++ b/packages/backend/src/misc/fastify-hook-handlers.ts
@@ -0,0 +1,9 @@
+import type { onRequestHookHandler } from 'fastify';
+
+export const handleRequestRedirectToOmitSearch: onRequestHookHandler = (request, reply, done) => {
+	const index = request.url.indexOf('?');
+	if (~index) {
+		reply.redirect(301, request.url.slice(0, index));
+	}
+	done();
+};
diff --git a/packages/backend/src/misc/fastify-reply-error.ts b/packages/backend/src/misc/fastify-reply-error.ts
index 7c889bab7a..e6c4e78d2f 100644
--- a/packages/backend/src/misc/fastify-reply-error.ts
+++ b/packages/backend/src/misc/fastify-reply-error.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/gen-identicon.ts b/packages/backend/src/misc/gen-identicon.ts
index c36b00af63..62a8ab8ace 100644
--- a/packages/backend/src/misc/gen-identicon.ts
+++ b/packages/backend/src/misc/gen-identicon.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/gen-key-pair.ts b/packages/backend/src/misc/gen-key-pair.ts
index c0815613e7..02a303dc0a 100644
--- a/packages/backend/src/misc/gen-key-pair.ts
+++ b/packages/backend/src/misc/gen-key-pair.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/generate-invite-code.ts b/packages/backend/src/misc/generate-invite-code.ts
index 7c88561179..006920cf0e 100644
--- a/packages/backend/src/misc/generate-invite-code.ts
+++ b/packages/backend/src/misc/generate-invite-code.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/generate-native-user-token.ts b/packages/backend/src/misc/generate-native-user-token.ts
index 094c625120..85fb383ba2 100644
--- a/packages/backend/src/misc/generate-native-user-token.ts
+++ b/packages/backend/src/misc/generate-native-user-token.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/get-ip-hash.ts b/packages/backend/src/misc/get-ip-hash.ts
index 3a01e4f578..e132fa8f31 100644
--- a/packages/backend/src/misc/get-ip-hash.ts
+++ b/packages/backend/src/misc/get-ip-hash.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/get-note-summary.ts b/packages/backend/src/misc/get-note-summary.ts
index 1bda5cdcf7..1a07139a50 100644
--- a/packages/backend/src/misc/get-note-summary.ts
+++ b/packages/backend/src/misc/get-note-summary.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/get-reaction-emoji.ts b/packages/backend/src/misc/get-reaction-emoji.ts
index 80ef7ff7bc..3f975853ed 100644
--- a/packages/backend/src/misc/get-reaction-emoji.ts
+++ b/packages/backend/src/misc/get-reaction-emoji.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/i18n.ts b/packages/backend/src/misc/i18n.ts
index 4c9d1a08e3..6cbbdef74c 100644
--- a/packages/backend/src/misc/i18n.ts
+++ b/packages/backend/src/misc/i18n.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/id/aid.ts b/packages/backend/src/misc/id/aid.ts
index de03f6793f..60ba788e44 100644
--- a/packages/backend/src/misc/id/aid.ts
+++ b/packages/backend/src/misc/id/aid.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/id/aidx.ts b/packages/backend/src/misc/id/aidx.ts
index 9f457f6f0a..1b087e70af 100644
--- a/packages/backend/src/misc/id/aidx.ts
+++ b/packages/backend/src/misc/id/aidx.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/id/meid.ts b/packages/backend/src/misc/id/meid.ts
index 7646282edb..dfab48a369 100644
--- a/packages/backend/src/misc/id/meid.ts
+++ b/packages/backend/src/misc/id/meid.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/id/meidg.ts b/packages/backend/src/misc/id/meidg.ts
index f2a55443ef..b9c0cc3dda 100644
--- a/packages/backend/src/misc/id/meidg.ts
+++ b/packages/backend/src/misc/id/meidg.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/id/object-id.ts b/packages/backend/src/misc/id/object-id.ts
index f5c3619fdb..243f92bbac 100644
--- a/packages/backend/src/misc/id/object-id.ts
+++ b/packages/backend/src/misc/id/object-id.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/id/ulid.ts b/packages/backend/src/misc/id/ulid.ts
index 00dd67dafe..fc3654d6d2 100644
--- a/packages/backend/src/misc/id/ulid.ts
+++ b/packages/backend/src/misc/id/ulid.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/identifiable-error.ts b/packages/backend/src/misc/identifiable-error.ts
index 71a4773fac..13c41f1e3b 100644
--- a/packages/backend/src/misc/identifiable-error.ts
+++ b/packages/backend/src/misc/identifiable-error.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/is-duplicate-key-value-error.ts b/packages/backend/src/misc/is-duplicate-key-value-error.ts
index 91e0a6b93d..8da0280f60 100644
--- a/packages/backend/src/misc/is-duplicate-key-value-error.ts
+++ b/packages/backend/src/misc/is-duplicate-key-value-error.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/is-instance-muted.ts b/packages/backend/src/misc/is-instance-muted.ts
index 35fe11849d..096a8b39c7 100644
--- a/packages/backend/src/misc/is-instance-muted.ts
+++ b/packages/backend/src/misc/is-instance-muted.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/is-mime-image.ts b/packages/backend/src/misc/is-mime-image.ts
index 1a5a8cf0f4..8ffbc99230 100644
--- a/packages/backend/src/misc/is-mime-image.ts
+++ b/packages/backend/src/misc/is-mime-image.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/is-native-token.ts b/packages/backend/src/misc/is-native-token.ts
index 618e60b7d8..300c4c05b3 100644
--- a/packages/backend/src/misc/is-native-token.ts
+++ b/packages/backend/src/misc/is-native-token.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/is-not-null.ts b/packages/backend/src/misc/is-not-null.ts
index 153a9e51ef..8d9dc8bb39 100644
--- a/packages/backend/src/misc/is-not-null.ts
+++ b/packages/backend/src/misc/is-not-null.ts
@@ -1,10 +1,8 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-// we are using {} as "any non-nullish value" as expected
-// eslint-disable-next-line @typescript-eslint/ban-types
-export function isNotNull<T extends {}>(input: T | undefined | null): input is T {
+export function isNotNull<T extends NonNullable<unknown>>(input: T | undefined | null): input is T {
 	return input != null;
 }
diff --git a/packages/backend/src/misc/is-quote.ts b/packages/backend/src/misc/is-quote.ts
index db72d1d57a..75b29f63f4 100644
--- a/packages/backend/src/misc/is-quote.ts
+++ b/packages/backend/src/misc/is-quote.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/is-reply.ts b/packages/backend/src/misc/is-reply.ts
index 964c2aa153..980eae11c9 100644
--- a/packages/backend/src/misc/is-reply.ts
+++ b/packages/backend/src/misc/is-reply.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/is-user-related.ts b/packages/backend/src/misc/is-user-related.ts
index 6efb1194d3..93c9b2b814 100644
--- a/packages/backend/src/misc/is-user-related.ts
+++ b/packages/backend/src/misc/is-user-related.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts
index 176978d35f..46b0bb2fab 100644
--- a/packages/backend/src/misc/json-schema.ts
+++ b/packages/backend/src/misc/json-schema.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -25,7 +25,7 @@ import { packedBlockingSchema } from '@/models/json-schema/blocking.js';
 import { packedNoteReactionSchema } from '@/models/json-schema/note-reaction.js';
 import { packedHashtagSchema } from '@/models/json-schema/hashtag.js';
 import { packedInviteCodeSchema } from '@/models/json-schema/invite-code.js';
-import { packedPageSchema } from '@/models/json-schema/page.js';
+import { packedPageSchema, packedPageBlockSchema } from '@/models/json-schema/page.js';
 import { packedNoteFavoriteSchema } from '@/models/json-schema/note-favorite.js';
 import { packedChannelSchema } from '@/models/json-schema/channel.js';
 import { packedAntennaSchema } from '@/models/json-schema/antenna.js';
@@ -37,8 +37,25 @@ import { packedEmojiDetailedSchema, packedEmojiSimpleSchema } from '@/models/jso
 import { packedFlashSchema } from '@/models/json-schema/flash.js';
 import { packedAnnouncementSchema } from '@/models/json-schema/announcement.js';
 import { packedSigninSchema } from '@/models/json-schema/signin.js';
-import { packedRoleLiteSchema, packedRoleSchema } from '@/models/json-schema/role.js';
+import {
+	packedRoleLiteSchema,
+	packedRoleSchema,
+	packedRolePoliciesSchema,
+	packedRoleCondFormulaLogicsSchema,
+	packedRoleCondFormulaValueNot,
+	packedRoleCondFormulaValueIsLocalOrRemoteSchema,
+	packedRoleCondFormulaValueAssignedRoleSchema,
+	packedRoleCondFormulaValueCreatedSchema,
+	packedRoleCondFormulaFollowersOrFollowingOrNotesSchema,
+	packedRoleCondFormulaValueSchema,
+} from '@/models/json-schema/role.js';
 import { packedAdSchema } from '@/models/json-schema/ad.js';
+import { packedReversiGameLiteSchema, packedReversiGameDetailedSchema } from '@/models/json-schema/reversi-game.js';
+import {
+	packedMetaLiteSchema,
+	packedMetaDetailedOnlySchema,
+	packedMetaDetailedSchema,
+} from '@/models/json-schema/meta.js';
 
 export const refs = {
 	UserLite: packedUserLiteSchema,
@@ -66,6 +83,7 @@ export const refs = {
 	Hashtag: packedHashtagSchema,
 	InviteCode: packedInviteCodeSchema,
 	Page: packedPageSchema,
+	PageBlock: packedPageBlockSchema,
 	Channel: packedChannelSchema,
 	QueueCount: packedQueueCountSchema,
 	Antenna: packedAntennaSchema,
@@ -76,12 +94,28 @@ export const refs = {
 	EmojiDetailed: packedEmojiDetailedSchema,
 	Flash: packedFlashSchema,
 	Signin: packedSigninSchema,
+	RoleCondFormulaLogics: packedRoleCondFormulaLogicsSchema,
+	RoleCondFormulaValueNot: packedRoleCondFormulaValueNot,
+	RoleCondFormulaValueIsLocalOrRemote: packedRoleCondFormulaValueIsLocalOrRemoteSchema,
+	RoleCondFormulaValueAssignedRole: packedRoleCondFormulaValueAssignedRoleSchema,
+	RoleCondFormulaValueCreated: packedRoleCondFormulaValueCreatedSchema,
+	RoleCondFormulaFollowersOrFollowingOrNotes: packedRoleCondFormulaFollowersOrFollowingOrNotesSchema,
+	RoleCondFormulaValue: packedRoleCondFormulaValueSchema,
 	RoleLite: packedRoleLiteSchema,
 	Role: packedRoleSchema,
+	RolePolicies: packedRolePoliciesSchema,
+	ReversiGameLite: packedReversiGameLiteSchema,
+	ReversiGameDetailed: packedReversiGameDetailedSchema,
+	MetaLite: packedMetaLiteSchema,
+	MetaDetailedOnly: packedMetaDetailedOnlySchema,
+	MetaDetailed: packedMetaDetailedSchema,
 };
 
 export type Packed<x extends keyof typeof refs> = SchemaType<typeof refs[x]>;
 
+export type KeyOf<x extends keyof typeof refs> = PropertiesToUnion<typeof refs[x]>;
+type PropertiesToUnion<p extends Schema> = p['properties'] extends NonNullable<Obj> ? keyof p['properties'] : never;
+
 type TypeStringef = 'null' | 'boolean' | 'integer' | 'number' | 'string' | 'array' | 'object' | 'any';
 type StringDefToType<T extends TypeStringef> =
 	T extends 'null' ? null :
@@ -111,6 +145,7 @@ export interface Schema extends OfSchema {
 	readonly example?: any;
 	readonly format?: string;
 	readonly ref?: keyof typeof refs;
+	readonly selfRef?: boolean;
 	readonly enum?: ReadonlyArray<string | null>;
 	readonly default?: (this['type'] extends TypeStringef ? StringDefToType<this['type']> : any) | null;
 	readonly maxLength?: number;
diff --git a/packages/backend/src/misc/langmap.ts b/packages/backend/src/misc/langmap.ts
index 9e287677df..5ff9338651 100644
--- a/packages/backend/src/misc/langmap.ts
+++ b/packages/backend/src/misc/langmap.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/normalize-for-search.ts b/packages/backend/src/misc/normalize-for-search.ts
index 9d96f4169d..3f19617e14 100644
--- a/packages/backend/src/misc/normalize-for-search.ts
+++ b/packages/backend/src/misc/normalize-for-search.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/prelude/array.ts b/packages/backend/src/misc/prelude/array.ts
index 8438b64805..bd6c8ee8e3 100644
--- a/packages/backend/src/misc/prelude/array.ts
+++ b/packages/backend/src/misc/prelude/array.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/prelude/await-all.ts b/packages/backend/src/misc/prelude/await-all.ts
index 6b8a91f8a5..48249fe1ae 100644
--- a/packages/backend/src/misc/prelude/await-all.ts
+++ b/packages/backend/src/misc/prelude/await-all.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/prelude/math.ts b/packages/backend/src/misc/prelude/math.ts
index 87b5017d09..38556def2d 100644
--- a/packages/backend/src/misc/prelude/math.ts
+++ b/packages/backend/src/misc/prelude/math.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/prelude/maybe.ts b/packages/backend/src/misc/prelude/maybe.ts
index 17c100b80d..1c58ccb9c7 100644
--- a/packages/backend/src/misc/prelude/maybe.ts
+++ b/packages/backend/src/misc/prelude/maybe.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/prelude/relation.ts b/packages/backend/src/misc/prelude/relation.ts
index 3456c1a0bc..7dcd4c700a 100644
--- a/packages/backend/src/misc/prelude/relation.ts
+++ b/packages/backend/src/misc/prelude/relation.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/prelude/string.ts b/packages/backend/src/misc/prelude/string.ts
index a727ab7f1d..67ea529961 100644
--- a/packages/backend/src/misc/prelude/string.ts
+++ b/packages/backend/src/misc/prelude/string.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/prelude/symbol.ts b/packages/backend/src/misc/prelude/symbol.ts
index 91c058a845..7e8d39bdb6 100644
--- a/packages/backend/src/misc/prelude/symbol.ts
+++ b/packages/backend/src/misc/prelude/symbol.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/prelude/time.ts b/packages/backend/src/misc/prelude/time.ts
index 4479db1081..275b67ed00 100644
--- a/packages/backend/src/misc/prelude/time.ts
+++ b/packages/backend/src/misc/prelude/time.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/prelude/url.ts b/packages/backend/src/misc/prelude/url.ts
index 633eb98218..270a075075 100644
--- a/packages/backend/src/misc/prelude/url.ts
+++ b/packages/backend/src/misc/prelude/url.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/prelude/xml.ts b/packages/backend/src/misc/prelude/xml.ts
index bca116a7ec..61c166cee5 100644
--- a/packages/backend/src/misc/prelude/xml.ts
+++ b/packages/backend/src/misc/prelude/xml.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/promise-tracker.ts b/packages/backend/src/misc/promise-tracker.ts
new file mode 100644
index 0000000000..8a52ca703e
--- /dev/null
+++ b/packages/backend/src/misc/promise-tracker.ts
@@ -0,0 +1,23 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+const promiseRefs: Set<WeakRef<Promise<unknown>>> = new Set();
+
+/**
+ * This tracks promises that other modules decided not to wait for,
+ * and makes sure they are all settled before fully closing down the server.
+ */
+export function trackPromise(promise: Promise<unknown>) {
+	if (process.env.NODE_ENV !== 'test') {
+		return;
+	}
+	const ref = new WeakRef(promise);
+	promiseRefs.add(ref);
+	promise.finally(() => promiseRefs.delete(ref));
+}
+
+export async function allSettled(): Promise<void> {
+	await Promise.allSettled([...promiseRefs].map(r => r.deref()));
+}
diff --git a/packages/backend/src/misc/reset-db.ts b/packages/backend/src/misc/reset-db.ts
index a571460a59..75fb4c3e7b 100644
--- a/packages/backend/src/misc/reset-db.ts
+++ b/packages/backend/src/misc/reset-db.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/safe-for-sql.ts b/packages/backend/src/misc/safe-for-sql.ts
index d7bdd0a81c..ac4b8e2e2e 100644
--- a/packages/backend/src/misc/safe-for-sql.ts
+++ b/packages/backend/src/misc/safe-for-sql.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/secure-rndstr.ts b/packages/backend/src/misc/secure-rndstr.ts
index 01368d808a..7853100d89 100644
--- a/packages/backend/src/misc/secure-rndstr.ts
+++ b/packages/backend/src/misc/secure-rndstr.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/show-machine-info.ts b/packages/backend/src/misc/show-machine-info.ts
index ed0fa651f1..8ddec35f23 100644
--- a/packages/backend/src/misc/show-machine-info.ts
+++ b/packages/backend/src/misc/show-machine-info.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/sql-like-escape.ts b/packages/backend/src/misc/sql-like-escape.ts
index 85cc7405e1..0c05255674 100644
--- a/packages/backend/src/misc/sql-like-escape.ts
+++ b/packages/backend/src/misc/sql-like-escape.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/misc/status-error.ts b/packages/backend/src/misc/status-error.ts
index 4285685d24..c3533db607 100644
--- a/packages/backend/src/misc/status-error.ts
+++ b/packages/backend/src/misc/status-error.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -7,6 +7,7 @@ export class StatusError extends Error {
 	public statusCode: number;
 	public statusMessage?: string;
 	public isClientError: boolean;
+	public isRetryable: boolean;
 
 	constructor(message: string, statusCode: number, statusMessage?: string) {
 		super(message);
@@ -14,5 +15,6 @@ export class StatusError extends Error {
 		this.statusCode = statusCode;
 		this.statusMessage = statusMessage;
 		this.isClientError = typeof this.statusCode === 'number' && this.statusCode >= 400 && this.statusCode < 500;
+		this.isRetryable = !this.isClientError || this.statusCode === 429;
 	}
 }
diff --git a/packages/backend/src/misc/truncate.ts b/packages/backend/src/misc/truncate.ts
index b65202fbd4..1c8a274609 100644
--- a/packages/backend/src/misc/truncate.ts
+++ b/packages/backend/src/misc/truncate.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/AbuseUserReport.ts b/packages/backend/src/models/AbuseUserReport.ts
index 593c44f66b..0615fd7eb5 100644
--- a/packages/backend/src/models/AbuseUserReport.ts
+++ b/packages/backend/src/models/AbuseUserReport.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/AccessToken.ts b/packages/backend/src/models/AccessToken.ts
index 452711eb8c..6f98c14ec1 100644
--- a/packages/backend/src/models/AccessToken.ts
+++ b/packages/backend/src/models/AccessToken.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/Ad.ts b/packages/backend/src/models/Ad.ts
index b1d7d7d79e..108e991c70 100644
--- a/packages/backend/src/models/Ad.ts
+++ b/packages/backend/src/models/Ad.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/Announcement.ts b/packages/backend/src/models/Announcement.ts
index 8f8be88fed..d0c59fff50 100644
--- a/packages/backend/src/models/Announcement.ts
+++ b/packages/backend/src/models/Announcement.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -38,7 +38,7 @@ export class MiAnnouncement {
 		length: 256, nullable: false,
 		default: 'info',
 	})
-	public icon: string;
+	public icon: 'info' | 'warning' | 'error' | 'success';
 
 	// normal ... お知らせページ掲載
 	// banner ... お知らせページ掲載 + バナー表示
@@ -47,7 +47,7 @@ export class MiAnnouncement {
 		length: 256, nullable: false,
 		default: 'normal',
 	})
-	public display: string;
+	public display: 'normal' | 'banner' | 'dialog';
 
 	@Column('boolean', {
 		default: false,
diff --git a/packages/backend/src/models/AnnouncementRead.ts b/packages/backend/src/models/AnnouncementRead.ts
index db09e65f50..47de8dd180 100644
--- a/packages/backend/src/models/AnnouncementRead.ts
+++ b/packages/backend/src/models/AnnouncementRead.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/Antenna.ts b/packages/backend/src/models/Antenna.ts
index b74c61b728..332a899768 100644
--- a/packages/backend/src/models/Antenna.ts
+++ b/packages/backend/src/models/Antenna.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/App.ts b/packages/backend/src/models/App.ts
index 5c56a224a2..0185e2995c 100644
--- a/packages/backend/src/models/App.ts
+++ b/packages/backend/src/models/App.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/AuthSession.ts b/packages/backend/src/models/AuthSession.ts
index 81bed21211..03050ba955 100644
--- a/packages/backend/src/models/AuthSession.ts
+++ b/packages/backend/src/models/AuthSession.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/AvatarDecoration.ts b/packages/backend/src/models/AvatarDecoration.ts
index 08ebbdeac1..13f0b05667 100644
--- a/packages/backend/src/models/AvatarDecoration.ts
+++ b/packages/backend/src/models/AvatarDecoration.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/Blocking.ts b/packages/backend/src/models/Blocking.ts
index 9bf7a63b6e..34a6efe5a6 100644
--- a/packages/backend/src/models/Blocking.ts
+++ b/packages/backend/src/models/Blocking.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/BubbleGameRecord.ts b/packages/backend/src/models/BubbleGameRecord.ts
new file mode 100644
index 0000000000..686e39c118
--- /dev/null
+++ b/packages/backend/src/models/BubbleGameRecord.ts
@@ -0,0 +1,57 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
+import { id } from './util/id.js';
+import { MiUser } from './User.js';
+
+@Entity('bubble_game_record')
+export class MiBubbleGameRecord {
+	@PrimaryColumn(id())
+	public id: string;
+
+	@Index()
+	@Column({
+		...id(),
+	})
+	public userId: MiUser['id'];
+
+	@ManyToOne(type => MiUser, {
+		onDelete: 'CASCADE',
+	})
+	@JoinColumn()
+	public user: MiUser | null;
+
+	@Index()
+	@Column('timestamp with time zone')
+	public seededAt: Date;
+
+	@Column('varchar', {
+		length: 1024,
+	})
+	public seed: string;
+
+	@Column('integer')
+	public gameVersion: number;
+
+	@Column('varchar', {
+		length: 128,
+	})
+	public gameMode: string;
+
+	@Index()
+	@Column('integer')
+	public score: number;
+
+	@Column('jsonb', {
+		default: [],
+	})
+	public logs: number[][];
+
+	@Column('boolean', {
+		default: false,
+	})
+	public isVerified: boolean;
+}
diff --git a/packages/backend/src/models/Channel.ts b/packages/backend/src/models/Channel.ts
index a7f9e262b1..f5e9b17e3e 100644
--- a/packages/backend/src/models/Channel.ts
+++ b/packages/backend/src/models/Channel.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/ChannelFavorite.ts b/packages/backend/src/models/ChannelFavorite.ts
index fc25ffe260..167f41cf16 100644
--- a/packages/backend/src/models/ChannelFavorite.ts
+++ b/packages/backend/src/models/ChannelFavorite.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/ChannelFollowing.ts b/packages/backend/src/models/ChannelFollowing.ts
index 4dd391a082..c7afdd05b0 100644
--- a/packages/backend/src/models/ChannelFollowing.ts
+++ b/packages/backend/src/models/ChannelFollowing.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/Clip.ts b/packages/backend/src/models/Clip.ts
index 2483b0925a..6295a329fb 100644
--- a/packages/backend/src/models/Clip.ts
+++ b/packages/backend/src/models/Clip.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/ClipFavorite.ts b/packages/backend/src/models/ClipFavorite.ts
index aa949b3ea8..40bdb9f4aa 100644
--- a/packages/backend/src/models/ClipFavorite.ts
+++ b/packages/backend/src/models/ClipFavorite.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/ClipNote.ts b/packages/backend/src/models/ClipNote.ts
index b7cc5ee39b..6e1d2bec4c 100644
--- a/packages/backend/src/models/ClipNote.ts
+++ b/packages/backend/src/models/ClipNote.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/DriveFile.ts b/packages/backend/src/models/DriveFile.ts
index ca564f6f0c..efb639f075 100644
--- a/packages/backend/src/models/DriveFile.ts
+++ b/packages/backend/src/models/DriveFile.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/DriveFolder.ts b/packages/backend/src/models/DriveFolder.ts
index 18f6d17709..07046d6e11 100644
--- a/packages/backend/src/models/DriveFolder.ts
+++ b/packages/backend/src/models/DriveFolder.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/Emoji.ts b/packages/backend/src/models/Emoji.ts
index 563ac1d9d3..d62b6e9f6f 100644
--- a/packages/backend/src/models/Emoji.ts
+++ b/packages/backend/src/models/Emoji.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/Flash.ts b/packages/backend/src/models/Flash.ts
index ac880843b0..a1469a0d94 100644
--- a/packages/backend/src/models/Flash.ts
+++ b/packages/backend/src/models/Flash.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/FlashLike.ts b/packages/backend/src/models/FlashLike.ts
index ad7f4966b4..a9fb48123e 100644
--- a/packages/backend/src/models/FlashLike.ts
+++ b/packages/backend/src/models/FlashLike.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/FollowRequest.ts b/packages/backend/src/models/FollowRequest.ts
index 9899694dd6..3ff5e7a478 100644
--- a/packages/backend/src/models/FollowRequest.ts
+++ b/packages/backend/src/models/FollowRequest.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/Following.ts b/packages/backend/src/models/Following.ts
index e320911a1d..62cbc29f26 100644
--- a/packages/backend/src/models/Following.ts
+++ b/packages/backend/src/models/Following.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/GalleryLike.ts b/packages/backend/src/models/GalleryLike.ts
index 84d4ce9c3e..ed0963122d 100644
--- a/packages/backend/src/models/GalleryLike.ts
+++ b/packages/backend/src/models/GalleryLike.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/GalleryPost.ts b/packages/backend/src/models/GalleryPost.ts
index b72220caf9..04d8823e37 100644
--- a/packages/backend/src/models/GalleryPost.ts
+++ b/packages/backend/src/models/GalleryPost.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/Hashtag.ts b/packages/backend/src/models/Hashtag.ts
index 1493774752..3add06d0c3 100644
--- a/packages/backend/src/models/Hashtag.ts
+++ b/packages/backend/src/models/Hashtag.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/Instance.ts b/packages/backend/src/models/Instance.ts
index 4200b1b461..7dd4e5b10c 100644
--- a/packages/backend/src/models/Instance.ts
+++ b/packages/backend/src/models/Instance.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -149,4 +149,9 @@ export class MiInstance {
 		default: false,
 	})
 	public isNSFW: boolean;
+	
+	@Column('varchar', {
+		length: 16384, default: '',
+	})
+	public moderationNote: string;
 }
diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts
index 4bf856e619..dd2e78cde2 100644
--- a/packages/backend/src/models/Meta.ts
+++ b/packages/backend/src/models/Meta.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -76,6 +76,11 @@ export class MiMeta {
 	})
 	public sensitiveWords: string[];
 
+	@Column('varchar', {
+		length: 1024, array: true, default: '{}',
+	})
+	public prohibitedWords: string[];
+
 	@Column('varchar', {
 		length: 1024, array: true, default: '{}',
 	})
@@ -196,6 +201,29 @@ export class MiMeta {
 	})
 	public hcaptchaSecretKey: string | null;
 
+	@Column('boolean', {
+		default: false,
+	})
+	public enableMcaptcha: boolean;
+
+	@Column('varchar', {
+		length: 1024,
+		nullable: true,
+	})
+	public mcaptchaSitekey: string | null;
+
+	@Column('varchar', {
+		length: 1024,
+		nullable: true,
+	})
+	public mcaptchaSecretKey: string | null;
+
+	@Column('varchar', {
+		length: 1024,
+		nullable: true,
+	})
+	public mcaptchaInstanceUrl: string | null;
+
 	@Column('boolean', {
 		default: false,
 	})
@@ -230,6 +258,8 @@ export class MiMeta {
 	})
 	public turnstileSecretKey: string | null;
 
+	// chaptcha系を追加した際にはnodeinfoのレスポンスに追加するのを忘れないようにすること
+
 	@Column('enum', {
 		enum: ['none', 'all', 'local', 'remote'],
 		default: 'none',
@@ -330,6 +360,17 @@ export class MiMeta {
 	})
 	public deeplIsPro: boolean;
 
+	@Column('boolean', {
+		default: false,
+	})
+	public deeplFreeMode: boolean;
+
+	@Column('varchar', {
+		length: 1024,
+		nullable: true,
+	})
+	public deeplFreeInstance: string | null;
+
 	@Column('varchar', {
 		length: 1024,
 		nullable: true,
@@ -338,14 +379,14 @@ export class MiMeta {
 
 	@Column('varchar', {
 		length: 1024,
-		default: 'https://github.com/misskey-dev/misskey',
+		default: 'https://activitypub.software/TransFem-org/Sharkey/',
 		nullable: false,
 	})
-	public repositoryUrl: string;
+	public repositoryUrl: string | null;
 
 	@Column('varchar', {
 		length: 1024,
-		default: 'https://github.com/misskey-dev/misskey/issues/new',
+		default: 'https://activitypub.software/TransFem-org/Sharkey/-/issues/new',
 		nullable: true,
 	})
 	public feedbackUrl: string | null;
@@ -362,6 +403,12 @@ export class MiMeta {
 	})
 	public privacyPolicyUrl: string | null;
 
+	@Column('varchar', {
+		length: 1024,
+		nullable: true,
+	})
+	public donationUrl: string | null;
+
 	@Column('varchar', {
 		length: 8192,
 		nullable: true,
@@ -467,6 +514,23 @@ export class MiMeta {
 	})
 	public verifymailAuthKey: string | null;
 
+	@Column('boolean', {
+		default: false,
+	})
+	public enableTruemailApi: boolean;
+
+	@Column('varchar', {
+		length: 1024,
+		nullable: true,
+	})
+	public truemailInstance: string | null;
+
+	@Column('varchar', {
+		length: 1024,
+		nullable: true,
+	})
+	public truemailAuthKey: string | null;
+
 	@Column('boolean', {
 		default: true,
 	})
diff --git a/packages/backend/src/models/ModerationLog.ts b/packages/backend/src/models/ModerationLog.ts
index 71b33c3e47..edde315fdf 100644
--- a/packages/backend/src/models/ModerationLog.ts
+++ b/packages/backend/src/models/ModerationLog.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/Muting.ts b/packages/backend/src/models/Muting.ts
index a528e1e7d7..e1240b9c4e 100644
--- a/packages/backend/src/models/Muting.ts
+++ b/packages/backend/src/models/Muting.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/Note.ts b/packages/backend/src/models/Note.ts
index 2705282880..b11e2ec62b 100644
--- a/packages/backend/src/models/Note.ts
+++ b/packages/backend/src/models/Note.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -11,9 +11,6 @@ import { MiChannel } from './Channel.js';
 import type { MiDriveFile } from './DriveFile.js';
 
 @Entity('note')
-@Index('IDX_NOTE_TAGS', { synchronize: false })
-@Index('IDX_NOTE_MENTIONS', { synchronize: false })
-@Index('IDX_NOTE_VISIBLE_USER_IDS', { synchronize: false })
 export class MiNote {
 	@PrimaryColumn(id())
 	public id: string;
@@ -139,7 +136,7 @@ export class MiNote {
 	})
 	public url: string | null;
 
-	@Index()
+	@Index('IDX_NOTE_FILE_IDS', { synchronize: false })
 	@Column({
 		...id(),
 		array: true, default: '{}',
@@ -151,14 +148,14 @@ export class MiNote {
 	})
 	public attachedFileTypes: string[];
 
-	@Index()
+	@Index('IDX_NOTE_VISIBLE_USER_IDS', { synchronize: false })
 	@Column({
 		...id(),
 		array: true, default: '{}',
 	})
 	public visibleUserIds: MiUser['id'][];
 
-	@Index()
+	@Index('IDX_NOTE_MENTIONS', { synchronize: false })
 	@Column({
 		...id(),
 		array: true, default: '{}',
@@ -180,7 +177,7 @@ export class MiNote {
 	})
 	public emojis: string[];
 
-	@Index()
+	@Index('IDX_NOTE_TAGS', { synchronize: false })
 	@Column('varchar', {
 		length: 128, array: true, default: '{}',
 	})
diff --git a/packages/backend/src/models/NoteFavorite.ts b/packages/backend/src/models/NoteFavorite.ts
index 364eaabd98..cf76c767b0 100644
--- a/packages/backend/src/models/NoteFavorite.ts
+++ b/packages/backend/src/models/NoteFavorite.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/NoteReaction.ts b/packages/backend/src/models/NoteReaction.ts
index ee3a447464..42dfcaa9ad 100644
--- a/packages/backend/src/models/NoteReaction.ts
+++ b/packages/backend/src/models/NoteReaction.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/NoteThreadMuting.ts b/packages/backend/src/models/NoteThreadMuting.ts
index 00311aa570..e7bd39f348 100644
--- a/packages/backend/src/models/NoteThreadMuting.ts
+++ b/packages/backend/src/models/NoteThreadMuting.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/NoteUnread.ts b/packages/backend/src/models/NoteUnread.ts
index d86a474553..c759181117 100644
--- a/packages/backend/src/models/NoteUnread.ts
+++ b/packages/backend/src/models/NoteUnread.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/Notification.ts b/packages/backend/src/models/Notification.ts
index 3bc2edaa0d..4ed71a106c 100644
--- a/packages/backend/src/models/Notification.ts
+++ b/packages/backend/src/models/Notification.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -107,6 +107,12 @@ export type MiNotification = {
 	type: 'test';
 	id: string;
 	createdAt: string;
+} | {
+	type: 'edited';
+	id: string;
+	createdAt: string;
+	notifierId: MiUser['id'];
+	noteId: MiNote['id'];
 };
 
 export type MiGroupedNotification = MiNotification | {
diff --git a/packages/backend/src/models/Page.ts b/packages/backend/src/models/Page.ts
index 9cab875499..1695bf570e 100644
--- a/packages/backend/src/models/Page.ts
+++ b/packages/backend/src/models/Page.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/PageLike.ts b/packages/backend/src/models/PageLike.ts
index b845f58b7d..05ca22cf2c 100644
--- a/packages/backend/src/models/PageLike.ts
+++ b/packages/backend/src/models/PageLike.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/PasswordResetRequest.ts b/packages/backend/src/models/PasswordResetRequest.ts
index 5be439511f..fdaf21056b 100644
--- a/packages/backend/src/models/PasswordResetRequest.ts
+++ b/packages/backend/src/models/PasswordResetRequest.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/Poll.ts b/packages/backend/src/models/Poll.ts
index 5ce0b9a2fc..c2693dbb19 100644
--- a/packages/backend/src/models/Poll.ts
+++ b/packages/backend/src/models/Poll.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/PollVote.ts b/packages/backend/src/models/PollVote.ts
index 751be8a32b..b5c780293c 100644
--- a/packages/backend/src/models/PollVote.ts
+++ b/packages/backend/src/models/PollVote.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/PromoNote.ts b/packages/backend/src/models/PromoNote.ts
index f4425fe88b..ae27adec9e 100644
--- a/packages/backend/src/models/PromoNote.ts
+++ b/packages/backend/src/models/PromoNote.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/PromoRead.ts b/packages/backend/src/models/PromoRead.ts
index d9f3075416..b2a698cc7b 100644
--- a/packages/backend/src/models/PromoRead.ts
+++ b/packages/backend/src/models/PromoRead.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/RegistrationTicket.ts b/packages/backend/src/models/RegistrationTicket.ts
index 730cedffba..0a4e4b9189 100644
--- a/packages/backend/src/models/RegistrationTicket.ts
+++ b/packages/backend/src/models/RegistrationTicket.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/RegistryItem.ts b/packages/backend/src/models/RegistryItem.ts
index 60bdced957..335e8b9eab 100644
--- a/packages/backend/src/models/RegistryItem.ts
+++ b/packages/backend/src/models/RegistryItem.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/Relay.ts b/packages/backend/src/models/Relay.ts
index 293fccecfc..eca2916032 100644
--- a/packages/backend/src/models/Relay.ts
+++ b/packages/backend/src/models/Relay.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/RenoteMuting.ts b/packages/backend/src/models/RenoteMuting.ts
index 17df43ea31..448a0b7663 100644
--- a/packages/backend/src/models/RenoteMuting.ts
+++ b/packages/backend/src/models/RenoteMuting.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts
index 0b5d3b640f..053edd6094 100644
--- a/packages/backend/src/models/RepositoryModule.ts
+++ b/packages/backend/src/models/RepositoryModule.ts
@@ -1,11 +1,11 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import { Module } from '@nestjs/common';
 import { DI } from '@/di-symbols.js';
-import { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiAvatarDecoration, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListMembership, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook, NoteEdit } from './_.js';
+import { MiAbuseUserReport, MiAccessToken, MiAd, MiAnnouncement, MiAnnouncementRead, MiAntenna, MiApp, MiAuthSession, MiAvatarDecoration, MiBlocking, MiChannel, MiChannelFavorite, MiChannelFollowing, MiClip, MiClipFavorite, MiClipNote, MiDriveFile, MiDriveFolder, MiEmoji, MiFlash, MiFlashLike, MiFollowRequest, MiFollowing, MiGalleryLike, MiGalleryPost, MiHashtag, MiInstance, MiMeta, MiModerationLog, MiMuting, MiNote, MiNoteFavorite, MiNoteReaction, MiNoteThreadMuting, MiNoteUnread, MiPage, MiPageLike, MiPasswordResetRequest, MiPoll, MiPollVote, MiPromoNote, MiPromoRead, MiRegistrationTicket, MiRegistryItem, MiRelay, MiRenoteMuting, MiRetentionAggregation, MiRole, MiRoleAssignment, MiSignin, MiSwSubscription, MiUsedUsername, MiUser, MiUserIp, MiUserKeypair, MiUserList, MiUserListFavorite, MiUserListMembership, MiUserMemo, MiUserNotePining, MiUserPending, MiUserProfile, MiUserPublickey, MiUserSecurityKey, MiWebhook, NoteEdit, MiBubbleGameRecord, MiReversiGame } from './_.js';
 import type { DataSource } from 'typeorm';
 import type { Provider } from '@nestjs/common';
 
@@ -405,6 +405,18 @@ const $noteEditRepository: Provider = {
 	inject: [DI.db],
 };
 
+const $bubbleGameRecordsRepository: Provider = {
+	provide: DI.bubbleGameRecordsRepository,
+	useFactory: (db: DataSource) => db.getRepository(MiBubbleGameRecord),
+	inject: [DI.db],
+};
+
+const $reversiGamesRepository: Provider = {
+	provide: DI.reversiGamesRepository,
+	useFactory: (db: DataSource) => db.getRepository(MiReversiGame),
+	inject: [DI.db],
+};
+
 @Module({
 	imports: [
 	],
@@ -475,6 +487,8 @@ const $noteEditRepository: Provider = {
 		$flashLikesRepository,
 		$userMemosRepository,
 		$noteEditRepository,
+		$bubbleGameRecordsRepository,
+		$reversiGamesRepository,
 	],
 	exports: [
 		$usersRepository,
@@ -543,6 +557,8 @@ const $noteEditRepository: Provider = {
 		$flashLikesRepository,
 		$userMemosRepository,
 		$noteEditRepository,
+		$bubbleGameRecordsRepository,
+		$reversiGamesRepository,
 	],
 })
 export class RepositoryModule {}
diff --git a/packages/backend/src/models/RetentionAggregation.ts b/packages/backend/src/models/RetentionAggregation.ts
index 9da401597c..139f3e4dfd 100644
--- a/packages/backend/src/models/RetentionAggregation.ts
+++ b/packages/backend/src/models/RetentionAggregation.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/ReversiGame.ts b/packages/backend/src/models/ReversiGame.ts
new file mode 100644
index 0000000000..c03335dd63
--- /dev/null
+++ b/packages/backend/src/models/ReversiGame.ts
@@ -0,0 +1,143 @@
+import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
+import { id } from './util/id.js';
+import { MiUser } from './User.js';
+
+@Entity('reversi_game')
+export class MiReversiGame {
+	@PrimaryColumn(id())
+	public id: string;
+
+	@Column('timestamp with time zone', {
+		nullable: true,
+		comment: 'The started date of the ReversiGame.',
+	})
+	public startedAt: Date | null;
+
+	@Column('timestamp with time zone', {
+		nullable: true,
+		comment: 'The ended date of the ReversiGame.',
+	})
+	public endedAt: Date | null;
+
+	@Column(id())
+	public user1Id: MiUser['id'];
+
+	@ManyToOne(type => MiUser, {
+		onDelete: 'CASCADE',
+	})
+	@JoinColumn()
+	public user1: MiUser | null;
+
+	@Column(id())
+	public user2Id: MiUser['id'];
+
+	@ManyToOne(type => MiUser, {
+		onDelete: 'CASCADE',
+	})
+	@JoinColumn()
+	public user2: MiUser | null;
+
+	@Column('boolean', {
+		default: false,
+	})
+	public user1Ready: boolean;
+
+	@Column('boolean', {
+		default: false,
+	})
+	public user2Ready: boolean;
+
+	/**
+	 * どちらのプレイヤーが先行(黒)か
+	 * 1 ... user1
+	 * 2 ... user2
+	 */
+	@Column('integer', {
+		nullable: true,
+	})
+	public black: number | null;
+
+	@Column('boolean', {
+		default: false,
+	})
+	public isStarted: boolean;
+
+	@Column('boolean', {
+		default: false,
+	})
+	public isEnded: boolean;
+
+	@Column({
+		...id(),
+		nullable: true,
+	})
+	public winnerId: MiUser['id'] | null;
+
+	@Column({
+		...id(),
+		nullable: true,
+	})
+	public surrenderedUserId: MiUser['id'] | null;
+
+	@Column({
+		...id(),
+		nullable: true,
+	})
+	public timeoutUserId: MiUser['id'] | null;
+
+	// in sec
+	@Column('smallint', {
+		default: 90,
+	})
+	public timeLimitForEachTurn: number;
+
+	@Column('jsonb', {
+		default: [],
+	})
+	public logs: number[][];
+
+	@Column('varchar', {
+		array: true, length: 64,
+	})
+	public map: string[];
+
+	@Column('varchar', {
+		length: 32,
+	})
+	public bw: string;
+
+	@Column('boolean', {
+		default: false,
+	})
+	public noIrregularRules: boolean;
+
+	@Column('boolean', {
+		default: false,
+	})
+	public isLlotheo: boolean;
+
+	@Column('boolean', {
+		default: false,
+	})
+	public canPutEverywhere: boolean;
+
+	@Column('boolean', {
+		default: false,
+	})
+	public loopedBoard: boolean;
+
+	@Column('jsonb', {
+		nullable: true, default: null,
+	})
+	public form1: any | null;
+
+	@Column('jsonb', {
+		nullable: true, default: null,
+	})
+	public form2: any | null;
+
+	@Column('varchar', {
+		length: 32, nullable: true,
+	})
+	public crc32: string | null;
+}
diff --git a/packages/backend/src/models/Role.ts b/packages/backend/src/models/Role.ts
index 6976956e13..058abe3118 100644
--- a/packages/backend/src/models/Role.ts
+++ b/packages/backend/src/models/Role.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -29,6 +29,11 @@ type CondFormulaValueIsRemote = {
 	type: 'isRemote';
 };
 
+type CondFormulaValueRoleAssignedTo = {
+	type: 'roleAssignedTo';
+	roleId: string;
+};
+
 type CondFormulaValueCreatedLessThan = {
 	type: 'createdLessThan';
 	sec: number;
@@ -69,12 +74,13 @@ type CondFormulaValueNotesMoreThanOrEq = {
 	value: number;
 };
 
-export type RoleCondFormulaValue =
+export type RoleCondFormulaValue = { id: string } & (
 	CondFormulaValueAnd |
 	CondFormulaValueOr |
 	CondFormulaValueNot |
 	CondFormulaValueIsLocal |
 	CondFormulaValueIsRemote |
+	CondFormulaValueRoleAssignedTo |
 	CondFormulaValueCreatedLessThan |
 	CondFormulaValueCreatedMoreThan |
 	CondFormulaValueFollowersLessThanOrEq |
@@ -82,7 +88,8 @@ export type RoleCondFormulaValue =
 	CondFormulaValueFollowingLessThanOrEq |
 	CondFormulaValueFollowingMoreThanOrEq |
 	CondFormulaValueNotesLessThanOrEq |
-	CondFormulaValueNotesMoreThanOrEq;
+	CondFormulaValueNotesMoreThanOrEq
+);
 
 @Entity('role')
 export class MiRole {
diff --git a/packages/backend/src/models/RoleAssignment.ts b/packages/backend/src/models/RoleAssignment.ts
index 30c7e19f2a..37755d631b 100644
--- a/packages/backend/src/models/RoleAssignment.ts
+++ b/packages/backend/src/models/RoleAssignment.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/Signin.ts b/packages/backend/src/models/Signin.ts
index 656b44dfe0..f8ff9c57d7 100644
--- a/packages/backend/src/models/Signin.ts
+++ b/packages/backend/src/models/Signin.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/SwSubscription.ts b/packages/backend/src/models/SwSubscription.ts
index f685a8ff3e..0c531132b3 100644
--- a/packages/backend/src/models/SwSubscription.ts
+++ b/packages/backend/src/models/SwSubscription.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/UsedUsername.ts b/packages/backend/src/models/UsedUsername.ts
index c75bf424c1..fbfc126763 100644
--- a/packages/backend/src/models/UsedUsername.ts
+++ b/packages/backend/src/models/UsedUsername.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/User.ts b/packages/backend/src/models/User.ts
index 3db8b398fd..b0910133c9 100644
--- a/packages/backend/src/models/User.ts
+++ b/packages/backend/src/models/User.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/UserIp.ts b/packages/backend/src/models/UserIp.ts
index 60a7bc8b01..3e757fcf79 100644
--- a/packages/backend/src/models/UserIp.ts
+++ b/packages/backend/src/models/UserIp.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/UserKeypair.ts b/packages/backend/src/models/UserKeypair.ts
index a316dbaeb4..f5252d126c 100644
--- a/packages/backend/src/models/UserKeypair.ts
+++ b/packages/backend/src/models/UserKeypair.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/UserList.ts b/packages/backend/src/models/UserList.ts
index 7ad15419d7..5fb991a87d 100644
--- a/packages/backend/src/models/UserList.ts
+++ b/packages/backend/src/models/UserList.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/UserListFavorite.ts b/packages/backend/src/models/UserListFavorite.ts
index a18ed9253a..80b2d61eb7 100644
--- a/packages/backend/src/models/UserListFavorite.ts
+++ b/packages/backend/src/models/UserListFavorite.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/UserListMembership.ts b/packages/backend/src/models/UserListMembership.ts
index fa8287f17a..af659d071d 100644
--- a/packages/backend/src/models/UserListMembership.ts
+++ b/packages/backend/src/models/UserListMembership.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/UserMemo.ts b/packages/backend/src/models/UserMemo.ts
index ab5e812c44..29e28d290a 100644
--- a/packages/backend/src/models/UserMemo.ts
+++ b/packages/backend/src/models/UserMemo.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/UserNotePining.ts b/packages/backend/src/models/UserNotePining.ts
index ae5977aa56..92c5cd55d0 100644
--- a/packages/backend/src/models/UserNotePining.ts
+++ b/packages/backend/src/models/UserNotePining.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/UserPending.ts b/packages/backend/src/models/UserPending.ts
index 6b26bd228c..961ae344f1 100644
--- a/packages/backend/src/models/UserPending.ts
+++ b/packages/backend/src/models/UserPending.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/UserProfile.ts b/packages/backend/src/models/UserProfile.ts
index ae46fbc83c..40ea26f610 100644
--- a/packages/backend/src/models/UserProfile.ts
+++ b/packages/backend/src/models/UserProfile.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -256,6 +256,8 @@ export class MiUserProfile {
 			type: 'follower';
 		} | {
 			type: 'mutualFollow';
+		} | {
+			type: 'followingOrFollower';
 		} | {
 			type: 'list';
 			userListId: MiUserList['id'];
diff --git a/packages/backend/src/models/UserPublickey.ts b/packages/backend/src/models/UserPublickey.ts
index 33de73c636..6bcd785304 100644
--- a/packages/backend/src/models/UserPublickey.ts
+++ b/packages/backend/src/models/UserPublickey.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/UserSecurityKey.ts b/packages/backend/src/models/UserSecurityKey.ts
index 02c29bfbb5..0babbe1abe 100644
--- a/packages/backend/src/models/UserSecurityKey.ts
+++ b/packages/backend/src/models/UserSecurityKey.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/Webhook.ts b/packages/backend/src/models/Webhook.ts
index ec4e13cc76..2a727f86fd 100644
--- a/packages/backend/src/models/Webhook.ts
+++ b/packages/backend/src/models/Webhook.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -7,7 +7,7 @@ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typ
 import { id } from './util/id.js';
 import { MiUser } from './User.js';
 
-export const webhookEventTypes = ['mention', 'unfollow', 'follow', 'followed', 'note', 'reply', 'renote', 'reaction'] as const;
+export const webhookEventTypes = ['mention', 'unfollow', 'follow', 'followed', 'note', 'reply', 'renote', 'reaction', 'edited'] as const;
 
 @Entity('webhook')
 export class MiWebhook {
diff --git a/packages/backend/src/models/_.ts b/packages/backend/src/models/_.ts
index 2a7810235e..744a1dd4e7 100644
--- a/packages/backend/src/models/_.ts
+++ b/packages/backend/src/models/_.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -69,6 +69,9 @@ import { MiFlash } from '@/models/Flash.js';
 import { MiFlashLike } from '@/models/FlashLike.js';
 import { MiUserListFavorite } from '@/models/UserListFavorite.js';
 import { NoteEdit } from '@/models/NoteEdit.js';
+import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
+import { MiReversiGame } from '@/models/ReversiGame.js';
+
 import type { Repository } from 'typeorm';
 
 export {
@@ -138,6 +141,8 @@ export {
 	MiFlashLike,
 	MiUserMemo,
 	NoteEdit,
+	MiBubbleGameRecord,
+	MiReversiGame,
 };
 
 export type AbuseUserReportsRepository = Repository<MiAbuseUserReport>;
@@ -206,3 +211,5 @@ export type FlashsRepository = Repository<MiFlash>;
 export type FlashLikesRepository = Repository<MiFlashLike>;
 export type UserMemoRepository = Repository<MiUserMemo>;
 export type NoteEditRepository = Repository<NoteEdit>;
+export type BubbleGameRecordsRepository = Repository<MiBubbleGameRecord>;
+export type ReversiGamesRepository = Repository<MiReversiGame>;
diff --git a/packages/backend/src/models/json-schema/ad.ts b/packages/backend/src/models/json-schema/ad.ts
index 649ffcd4dc..b01b39a38b 100644
--- a/packages/backend/src/models/json-schema/ad.ts
+++ b/packages/backend/src/models/json-schema/ad.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/json-schema/announcement.ts b/packages/backend/src/models/json-schema/announcement.ts
index 78a98872b2..b9352bd31e 100644
--- a/packages/backend/src/models/json-schema/announcement.ts
+++ b/packages/backend/src/models/json-schema/announcement.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -37,10 +37,12 @@ export const packedAnnouncementSchema = {
 		icon: {
 			type: 'string',
 			optional: false, nullable: false,
+			enum: ['info', 'warning', 'error', 'success'],
 		},
 		display: {
 			type: 'string',
 			optional: false, nullable: false,
+			enum: ['dialog', 'normal', 'banner'],
 		},
 		needConfirmationToRead: {
 			type: 'boolean',
diff --git a/packages/backend/src/models/json-schema/antenna.ts b/packages/backend/src/models/json-schema/antenna.ts
index 4a9f0ed355..74622b6193 100644
--- a/packages/backend/src/models/json-schema/antenna.ts
+++ b/packages/backend/src/models/json-schema/antenna.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/json-schema/app.ts b/packages/backend/src/models/json-schema/app.ts
index 9e0916299c..6148232224 100644
--- a/packages/backend/src/models/json-schema/app.ts
+++ b/packages/backend/src/models/json-schema/app.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/json-schema/blocking.ts b/packages/backend/src/models/json-schema/blocking.ts
index 0b58f1f8d7..2d02ba6a70 100644
--- a/packages/backend/src/models/json-schema/blocking.ts
+++ b/packages/backend/src/models/json-schema/blocking.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -25,7 +25,7 @@ export const packedBlockingSchema = {
 		blockee: {
 			type: 'object',
 			optional: false, nullable: false,
-			ref: 'UserDetailed',
+			ref: 'UserDetailedNotMe',
 		},
 	},
 } as const;
diff --git a/packages/backend/src/models/json-schema/channel.ts b/packages/backend/src/models/json-schema/channel.ts
index 5b0fa0f15d..d233f7858d 100644
--- a/packages/backend/src/models/json-schema/channel.ts
+++ b/packages/backend/src/models/json-schema/channel.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/json-schema/clip.ts b/packages/backend/src/models/json-schema/clip.ts
index 1ab96c2b3b..ca4886c978 100644
--- a/packages/backend/src/models/json-schema/clip.ts
+++ b/packages/backend/src/models/json-schema/clip.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/json-schema/drive-file.ts b/packages/backend/src/models/json-schema/drive-file.ts
index 79f242a711..ca88cc0e39 100644
--- a/packages/backend/src/models/json-schema/drive-file.ts
+++ b/packages/backend/src/models/json-schema/drive-file.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/json-schema/drive-folder.ts b/packages/backend/src/models/json-schema/drive-folder.ts
index aaad301303..12012a7e12 100644
--- a/packages/backend/src/models/json-schema/drive-folder.ts
+++ b/packages/backend/src/models/json-schema/drive-folder.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/json-schema/emoji.ts b/packages/backend/src/models/json-schema/emoji.ts
index 99a58f8773..62686ad5ae 100644
--- a/packages/backend/src/models/json-schema/emoji.ts
+++ b/packages/backend/src/models/json-schema/emoji.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -27,6 +27,10 @@ export const packedEmojiSimpleSchema = {
 			type: 'string',
 			optional: false, nullable: false,
 		},
+		localOnly: {
+			type: 'boolean',
+			optional: true, nullable: false,
+		},
 		isSensitive: {
 			type: 'boolean',
 			optional: true, nullable: false,
diff --git a/packages/backend/src/models/json-schema/federation-instance.ts b/packages/backend/src/models/json-schema/federation-instance.ts
index 94873716bf..7b8ab22831 100644
--- a/packages/backend/src/models/json-schema/federation-instance.ts
+++ b/packages/backend/src/models/json-schema/federation-instance.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -112,5 +112,9 @@ export const packedFederationInstanceSchema = {
 			optional: false,
 			nullable: false,
 		},
+		moderationNote: {
+			type: 'string',
+			optional: true, nullable: true,
+		},
 	},
 } as const;
diff --git a/packages/backend/src/models/json-schema/flash.ts b/packages/backend/src/models/json-schema/flash.ts
index f08fa7a279..952df649ad 100644
--- a/packages/backend/src/models/json-schema/flash.ts
+++ b/packages/backend/src/models/json-schema/flash.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/json-schema/following.ts b/packages/backend/src/models/json-schema/following.ts
index e92cff20a1..c5295a5128 100644
--- a/packages/backend/src/models/json-schema/following.ts
+++ b/packages/backend/src/models/json-schema/following.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -30,12 +30,12 @@ export const packedFollowingSchema = {
 		followee: {
 			type: 'object',
 			optional: true, nullable: false,
-			ref: 'UserDetailed',
+			ref: 'UserDetailedNotMe',
 		},
 		follower: {
 			type: 'object',
 			optional: true, nullable: false,
-			ref: 'UserDetailed',
+			ref: 'UserDetailedNotMe',
 		},
 	},
 } as const;
diff --git a/packages/backend/src/models/json-schema/gallery-post.ts b/packages/backend/src/models/json-schema/gallery-post.ts
index df7038950c..a46d5115c2 100644
--- a/packages/backend/src/models/json-schema/gallery-post.ts
+++ b/packages/backend/src/models/json-schema/gallery-post.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/json-schema/hashtag.ts b/packages/backend/src/models/json-schema/hashtag.ts
index a48e972a5d..4fd136afed 100644
--- a/packages/backend/src/models/json-schema/hashtag.ts
+++ b/packages/backend/src/models/json-schema/hashtag.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/json-schema/invite-code.ts b/packages/backend/src/models/json-schema/invite-code.ts
index cd8bf98d90..08d1b8fd0c 100644
--- a/packages/backend/src/models/json-schema/invite-code.ts
+++ b/packages/backend/src/models/json-schema/invite-code.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts
new file mode 100644
index 0000000000..9db3f7f809
--- /dev/null
+++ b/packages/backend/src/models/json-schema/meta.ts
@@ -0,0 +1,344 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export const packedMetaLiteSchema = {
+	type: 'object',
+	optional: false, nullable: false,
+	properties: {
+		maintainerName: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		maintainerEmail: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		version: {
+			type: 'string',
+			optional: false, nullable: false,
+		},
+		providesTarball: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		name: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		shortName: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		uri: {
+			type: 'string',
+			optional: false, nullable: false,
+			format: 'url',
+			example: 'https://misskey.example.com',
+		},
+		description: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		langs: {
+			type: 'array',
+			optional: false, nullable: false,
+			items: {
+				type: 'string',
+				optional: false, nullable: false,
+			},
+		},
+		tosUrl: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		repositoryUrl: {
+			type: 'string',
+			optional: false, nullable: true,
+			default: 'https://github.com/misskey-dev/misskey',
+		},
+		feedbackUrl: {
+			type: 'string',
+			optional: false, nullable: true,
+			default: 'https://github.com/misskey-dev/misskey/issues/new',
+		},
+		donationUrl: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		defaultDarkTheme: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		defaultLightTheme: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		defaultLike: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		disableRegistration: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		emailRequiredForSignup: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		approvalRequiredForSignup: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		enableHcaptcha: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		hcaptchaSiteKey: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		enableMcaptcha: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		mcaptchaSiteKey: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		mcaptchaInstanceUrl: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		enableRecaptcha: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		recaptchaSiteKey: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		enableTurnstile: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		turnstileSiteKey: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		enableAchievements: {
+			type: 'boolean',
+			optional: false, nullable: true,
+		},
+		swPublickey: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		mascotImageUrl: {
+			type: 'string',
+			optional: false, nullable: false,
+			default: '/assets/ai.png',
+		},
+		bannerUrl: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		serverErrorImageUrl: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		infoImageUrl: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		notFoundImageUrl: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		iconUrl: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		maxNoteTextLength: {
+			type: 'number',
+			optional: false, nullable: false,
+		},
+		ads: {
+			type: 'array',
+			optional: false, nullable: false,
+			items: {
+				type: 'object',
+				optional: false, nullable: false,
+				properties: {
+					id: {
+						type: 'string',
+						optional: false, nullable: false,
+						format: 'id',
+						example: 'xxxxxxxxxx',
+					},
+					url: {
+						type: 'string',
+						optional: false, nullable: false,
+						format: 'url',
+					},
+					place: {
+						type: 'string',
+						optional: false, nullable: false,
+					},
+					ratio: {
+						type: 'number',
+						optional: false, nullable: false,
+					},
+					imageUrl: {
+						type: 'string',
+						optional: false, nullable: false,
+						format: 'url',
+					},
+					dayOfWeek: {
+						type: 'integer',
+						optional: false, nullable: false,
+					},
+				},
+			},
+		},
+		notesPerOneAd: {
+			type: 'number',
+			optional: false, nullable: false,
+			default: 0,
+		},
+		enableEmail: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		enableServiceWorker: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		translatorAvailable: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		mediaProxy: {
+			type: 'string',
+			optional: false, nullable: false,
+		},
+		backgroundImageUrl: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		impressumUrl: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		logoImageUrl: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		privacyPolicyUrl: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		serverRules: {
+			type: 'array',
+			optional: false, nullable: false,
+			items: {
+				type: 'string',
+			},
+		},
+		themeColor: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		policies: {
+			type: 'object',
+			optional: false, nullable: false,
+			ref: 'RolePolicies',
+		},
+	},
+} as const;
+
+export const packedMetaDetailedOnlySchema = {
+	type: 'object',
+	optional: false, nullable: false,
+	properties: {
+		features: {
+			type: 'object',
+			optional: true, nullable: false,
+			properties: {
+				registration: {
+					type: 'boolean',
+					optional: false, nullable: false,
+				},
+				emailRequiredForSignup: {
+					type: 'boolean',
+					optional: false, nullable: false,
+				},
+				localTimeline: {
+					type: 'boolean',
+					optional: false, nullable: false,
+				},
+				globalTimeline: {
+					type: 'boolean',
+					optional: false, nullable: false,
+				},
+				hcaptcha: {
+					type: 'boolean',
+					optional: false, nullable: false,
+				},
+				turnstile: {
+					type: 'boolean',
+					optional: false, nullable: false,
+				},
+				recaptcha: {
+					type: 'boolean',
+					optional: false, nullable: false,
+				},
+				objectStorage: {
+					type: 'boolean',
+					optional: false, nullable: false,
+				},
+				serviceWorker: {
+					type: 'boolean',
+					optional: false, nullable: false,
+				},
+				miauth: {
+					type: 'boolean',
+					optional: true, nullable: false,
+					default: true,
+				},
+			},
+		},
+		proxyAccountName: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		requireSetup: {
+			type: 'boolean',
+			optional: false, nullable: false,
+			example: false,
+		},
+		cacheRemoteFiles: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		cacheRemoteSensitiveFiles: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+	},
+} as const;
+
+export const packedMetaDetailedSchema = {
+	type: 'object',
+	allOf: [
+		{
+			type: 'object',
+			ref: 'MetaLite',
+		},
+		{
+			type: 'object',
+			ref: 'MetaDetailedOnly',
+		},
+	],
+} as const;
diff --git a/packages/backend/src/models/json-schema/muting.ts b/packages/backend/src/models/json-schema/muting.ts
index dde9dc0288..b5fab013ef 100644
--- a/packages/backend/src/models/json-schema/muting.ts
+++ b/packages/backend/src/models/json-schema/muting.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -30,7 +30,7 @@ export const packedMutingSchema = {
 		mutee: {
 			type: 'object',
 			optional: false, nullable: false,
-			ref: 'UserDetailed',
+			ref: 'UserDetailedNotMe',
 		},
 	},
 } as const;
diff --git a/packages/backend/src/models/json-schema/note-favorite.ts b/packages/backend/src/models/json-schema/note-favorite.ts
index 3f0007d917..d2a3745f4b 100644
--- a/packages/backend/src/models/json-schema/note-favorite.ts
+++ b/packages/backend/src/models/json-schema/note-favorite.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/json-schema/note-reaction.ts b/packages/backend/src/models/json-schema/note-reaction.ts
index e3335f426e..95658ace1f 100644
--- a/packages/backend/src/models/json-schema/note-reaction.ts
+++ b/packages/backend/src/models/json-schema/note-reaction.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/json-schema/note.ts b/packages/backend/src/models/json-schema/note.ts
index aa749943f0..bb4ccc7ee4 100644
--- a/packages/backend/src/models/json-schema/note.ts
+++ b/packages/backend/src/models/json-schema/note.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -69,6 +69,7 @@ export const packedNoteSchema = {
 		visibility: {
 			type: 'string',
 			optional: false, nullable: false,
+			enum: ['public', 'home', 'followers', 'specified'],
 		},
 		mentions: {
 			type: 'array',
@@ -117,6 +118,48 @@ export const packedNoteSchema = {
 		poll: {
 			type: 'object',
 			optional: true, nullable: true,
+			properties: {
+				expiresAt: {
+					type: 'string',
+					optional: true, nullable: true,
+					format: 'date-time',
+				},
+				multiple: {
+					type: 'boolean',
+					optional: false, nullable: false,
+				},
+				choices: {
+					type: 'array',
+					optional: false, nullable: false,
+					items: {
+						type: 'object',
+						optional: false, nullable: false,
+						properties: {
+							isVoted: {
+								type: 'boolean',
+								optional: false, nullable: false,
+							},
+							text: {
+								type: 'string',
+								optional: false, nullable: false,
+							},
+							votes: {
+								type: 'number',
+								optional: false, nullable: false,
+							},
+						},
+					},
+				},
+			},
+		},
+		emojis: {
+			type: 'object',
+			optional: true, nullable: false,
+			additionalProperties: {
+				anyOf: [{
+					type: 'string',
+				}],
+			},
 		},
 		channelId: {
 			type: 'string',
@@ -148,6 +191,10 @@ export const packedNoteSchema = {
 					type: 'boolean',
 					optional: false, nullable: false,
 				},
+				userId: {
+					type: 'string',
+					optional: false, nullable: true,
+				},
 			},
 		},
 		localOnly: {
@@ -158,9 +205,23 @@ export const packedNoteSchema = {
 			type: 'string',
 			optional: false, nullable: true,
 		},
+		reactionEmojis: {
+			type: 'object',
+			optional: false, nullable: false,
+			additionalProperties: {
+				anyOf: [{
+					type: 'string',
+				}],
+			},
+		},
 		reactions: {
 			type: 'object',
 			optional: false, nullable: false,
+			additionalProperties: {
+				anyOf: [{
+					type: 'number',
+				}],
+			},
 		},
 		renoteCount: {
 			type: 'number',
@@ -192,7 +253,7 @@ export const packedNoteSchema = {
 		},
 
 		myReaction: {
-			type: 'object',
+			type: 'string',
 			optional: true, nullable: true,
 		},
 	},
diff --git a/packages/backend/src/models/json-schema/notification.ts b/packages/backend/src/models/json-schema/notification.ts
index c6d6e84317..3f31cc47ee 100644
--- a/packages/backend/src/models/json-schema/notification.ts
+++ b/packages/backend/src/models/json-schema/notification.ts
@@ -1,11 +1,11 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import { notificationTypes } from '@/types.js';
 
-export const packedNotificationSchema = {
+const baseSchema = {
 	type: 'object',
 	properties: {
 		id: {
@@ -23,68 +23,393 @@ export const packedNotificationSchema = {
 			optional: false, nullable: false,
 			enum: [...notificationTypes, 'reaction:grouped', 'renote:grouped'],
 		},
-		user: {
-			type: 'object',
-			ref: 'UserLite',
-			optional: true, nullable: true,
-		},
-		userId: {
-			type: 'string',
-			optional: true, nullable: true,
-			format: 'id',
-		},
-		note: {
-			type: 'object',
-			ref: 'Note',
-			optional: true, nullable: true,
-		},
-		reaction: {
-			type: 'string',
-			optional: true, nullable: true,
-		},
-		achievement: {
-			type: 'string',
-			optional: true, nullable: false,
-		},
-		body: {
-			type: 'string',
-			optional: true, nullable: true,
-		},
-		header: {
-			type: 'string',
-			optional: true, nullable: true,
-		},
-		icon: {
-			type: 'string',
-			optional: true, nullable: true,
-		},
-		reactions: {
-			type: 'array',
-			optional: true, nullable: true,
-			items: {
-				type: 'object',
-				properties: {
-					user: {
-						type: 'object',
-						ref: 'UserLite',
-						optional: false, nullable: false,
-					},
-					reaction: {
-						type: 'string',
-						optional: false, nullable: false,
-					},
-				},
-				required: ['user', 'reaction'],
+	},
+} as const;
+
+export const packedNotificationSchema = {
+	type: 'object',
+	oneOf: [{
+		type: 'object',
+		properties: {
+			...baseSchema.properties,
+			type: {
+				type: 'string',
+				optional: false, nullable: false,
+				enum: ['note'],
 			},
-		},
-		users: {
-			type: 'array',
-			optional: true, nullable: true,
-			items: {
+			user: {
 				type: 'object',
 				ref: 'UserLite',
 				optional: false, nullable: false,
 			},
+			userId: {
+				type: 'string',
+				optional: false, nullable: false,
+				format: 'id',
+			},
+			note: {
+				type: 'object',
+				ref: 'Note',
+				optional: false, nullable: false,
+			},
 		},
-	},
+	}, {
+		type: 'object',
+		properties: {
+			...baseSchema.properties,
+			type: {
+				type: 'string',
+				optional: false, nullable: false,
+				enum: ['mention'],
+			},
+			user: {
+				type: 'object',
+				ref: 'UserLite',
+				optional: false, nullable: false,
+			},
+			userId: {
+				type: 'string',
+				optional: false, nullable: false,
+				format: 'id',
+			},
+			note: {
+				type: 'object',
+				ref: 'Note',
+				optional: false, nullable: false,
+			},
+		},
+	}, {
+		type: 'object',
+		properties: {
+			...baseSchema.properties,
+			type: {
+				type: 'string',
+				optional: false, nullable: false,
+				enum: ['reply'],
+			},
+			user: {
+				type: 'object',
+				ref: 'UserLite',
+				optional: false, nullable: false,
+			},
+			userId: {
+				type: 'string',
+				optional: false, nullable: false,
+				format: 'id',
+			},
+			note: {
+				type: 'object',
+				ref: 'Note',
+				optional: false, nullable: false,
+			},
+		},
+	}, {
+		type: 'object',
+		properties: {
+			...baseSchema.properties,
+			type: {
+				type: 'string',
+				optional: false, nullable: false,
+				enum: ['renote'],
+			},
+			user: {
+				type: 'object',
+				ref: 'UserLite',
+				optional: false, nullable: false,
+			},
+			userId: {
+				type: 'string',
+				optional: false, nullable: false,
+				format: 'id',
+			},
+			note: {
+				type: 'object',
+				ref: 'Note',
+				optional: false, nullable: false,
+			},
+		},
+	}, {
+		type: 'object',
+		properties: {
+			...baseSchema.properties,
+			type: {
+				type: 'string',
+				optional: false, nullable: false,
+				enum: ['quote'],
+			},
+			user: {
+				type: 'object',
+				ref: 'UserLite',
+				optional: false, nullable: false,
+			},
+			userId: {
+				type: 'string',
+				optional: false, nullable: false,
+				format: 'id',
+			},
+			note: {
+				type: 'object',
+				ref: 'Note',
+				optional: false, nullable: false,
+			},
+		},
+	}, {
+		type: 'object',
+		properties: {
+			...baseSchema.properties,
+			type: {
+				type: 'string',
+				optional: false, nullable: false,
+				enum: ['reaction'],
+			},
+			user: {
+				type: 'object',
+				ref: 'UserLite',
+				optional: false, nullable: false,
+			},
+			userId: {
+				type: 'string',
+				optional: false, nullable: false,
+				format: 'id',
+			},
+			note: {
+				type: 'object',
+				ref: 'Note',
+				optional: false, nullable: false,
+			},
+			reaction: {
+				type: 'string',
+				optional: false, nullable: false,
+			},
+		},
+	}, {
+		type: 'object',
+		properties: {
+			...baseSchema.properties,
+			type: {
+				type: 'string',
+				optional: false, nullable: false,
+				enum: ['pollEnded'],
+			},
+			user: {
+				type: 'object',
+				ref: 'UserLite',
+				optional: false, nullable: false,
+			},
+			userId: {
+				type: 'string',
+				optional: false, nullable: false,
+				format: 'id',
+			},
+			note: {
+				type: 'object',
+				ref: 'Note',
+				optional: false, nullable: false,
+			},
+		},
+	}, {
+		type: 'object',
+		properties: {
+			...baseSchema.properties,
+			type: {
+				type: 'string',
+				optional: false, nullable: false,
+				enum: ['follow'],
+			},
+			user: {
+				type: 'object',
+				ref: 'UserLite',
+				optional: false, nullable: false,
+			},
+			userId: {
+				type: 'string',
+				optional: false, nullable: false,
+				format: 'id',
+			},
+		},
+	}, {
+		type: 'object',
+		properties: {
+			...baseSchema.properties,
+			type: {
+				type: 'string',
+				optional: false, nullable: false,
+				enum: ['receiveFollowRequest'],
+			},
+			user: {
+				type: 'object',
+				ref: 'UserLite',
+				optional: false, nullable: false,
+			},
+			userId: {
+				type: 'string',
+				optional: false, nullable: false,
+				format: 'id',
+			},
+		},
+	}, {
+		type: 'object',
+		properties: {
+			...baseSchema.properties,
+			type: {
+				type: 'string',
+				optional: false, nullable: false,
+				enum: ['followRequestAccepted'],
+			},
+			user: {
+				type: 'object',
+				ref: 'UserLite',
+				optional: false, nullable: false,
+			},
+			userId: {
+				type: 'string',
+				optional: false, nullable: false,
+				format: 'id',
+			},
+		},
+	}, {
+		type: 'object',
+		properties: {
+			...baseSchema.properties,
+			type: {
+				type: 'string',
+				optional: false, nullable: false,
+				enum: ['roleAssigned'],
+			},
+			role: {
+				type: 'object',
+				ref: 'Role',
+				optional: false, nullable: false,
+			},
+		},
+	}, {
+		type: 'object',
+		properties: {
+			...baseSchema.properties,
+			type: {
+				type: 'string',
+				optional: false, nullable: false,
+				enum: ['achievementEarned'],
+			},
+			achievement: {
+				type: 'string',
+				optional: false, nullable: false,
+			},
+		},
+	}, {
+		type: 'object',
+		properties: {
+			...baseSchema.properties,
+			type: {
+				type: 'string',
+				optional: false, nullable: false,
+				enum: ['app'],
+			},
+			body: {
+				type: 'string',
+				optional: false, nullable: false,
+			},
+			header: {
+				type: 'string',
+				optional: false, nullable: false,
+			},
+			icon: {
+				type: 'string',
+				optional: false, nullable: false,
+			},
+		},
+	}, {
+		type: 'object',
+		properties: {
+			...baseSchema.properties,
+			type: {
+				type: 'string',
+				optional: false, nullable: false,
+				enum: ['edited'],
+			},
+			user: {
+				type: 'object',
+				ref: 'UserLite',
+				optional: false, nullable: false,
+			},
+			userId: {
+				type: 'string',
+				optional: false, nullable: false,
+				format: 'id',
+			},
+			note: {
+				type: 'object',
+				ref: 'Note',
+				optional: false, nullable: false,
+			},
+		},
+	}, {
+		type: 'object',
+		properties: {
+			...baseSchema.properties,
+			type: {
+				type: 'string',
+				optional: false, nullable: false,
+				enum: ['reaction:grouped'],
+			},
+			note: {
+				type: 'object',
+				ref: 'Note',
+				optional: false, nullable: false,
+			},
+			reactions: {
+				type: 'array',
+				optional: false, nullable: false,
+				items: {
+					type: 'object',
+					properties: {
+						user: {
+							type: 'object',
+							ref: 'UserLite',
+							optional: false, nullable: false,
+						},
+						reaction: {
+							type: 'string',
+							optional: false, nullable: false,
+						},
+					},
+					required: ['user', 'reaction'],
+				},
+			},
+		},
+	}, {
+		type: 'object',
+		properties: {
+			...baseSchema.properties,
+			type: {
+				type: 'string',
+				optional: false, nullable: false,
+				enum: ['renote:grouped'],
+			},
+			note: {
+				type: 'object',
+				ref: 'Note',
+				optional: false, nullable: false,
+			},
+			users: {
+				type: 'array',
+				optional: false, nullable: false,
+				items: {
+					type: 'object',
+					ref: 'UserLite',
+					optional: false, nullable: false,
+				},
+			},
+		},
+	}, {
+		type: 'object',
+		properties: {
+			...baseSchema.properties,
+			type: {
+				type: 'string',
+				optional: false, nullable: false,
+				enum: ['test'],
+			},
+		},
+	}],
 } as const;
diff --git a/packages/backend/src/models/json-schema/page.ts b/packages/backend/src/models/json-schema/page.ts
index 9baacd6884..748d6f1245 100644
--- a/packages/backend/src/models/json-schema/page.ts
+++ b/packages/backend/src/models/json-schema/page.ts
@@ -1,8 +1,110 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
+const blockBaseSchema = {
+	type: 'object',
+	properties: {
+		id: {
+			type: 'string',
+			optional: false, nullable: false,
+		},
+		type: {
+			type: 'string',
+			optional: false, nullable: false,
+		},
+	},
+} as const;
+
+const textBlockSchema = {
+	type: 'object',
+	properties: {
+		...blockBaseSchema.properties,
+		type: {
+			type: 'string',
+			optional: false, nullable: false,
+			enum: ['text'],
+		},
+		text: {
+			type: 'string',
+			optional: false, nullable: false,
+		},
+	},
+} as const;
+
+const sectionBlockSchema = {
+	type: 'object',
+	properties: {
+		...blockBaseSchema.properties,
+		type: {
+			type: 'string',
+			optional: false, nullable: false,
+			enum: ['section'],
+		},
+		title: {
+			type: 'string',
+			optional: false, nullable: false,
+		},
+		children: {
+			type: 'array',
+			optional: false, nullable: false,
+			items: {
+				type: 'object',
+				optional: false, nullable: false,
+				ref: 'PageBlock',
+				selfRef: true,
+			},
+		},
+	},
+} as const;
+
+const imageBlockSchema = {
+	type: 'object',
+	properties: {
+		...blockBaseSchema.properties,
+		type: {
+			type: 'string',
+			optional: false, nullable: false,
+			enum: ['image'],
+		},
+		fileId: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+	},
+} as const;
+
+const noteBlockSchema = {
+	type: 'object',
+	properties: {
+		...blockBaseSchema.properties,
+		type: {
+			type: 'string',
+			optional: false, nullable: false,
+			enum: ['note'],
+		},
+		detailed: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		note: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+	},
+} as const;
+
+export const packedPageBlockSchema = {
+	type: 'object',
+	oneOf: [
+		textBlockSchema,
+		sectionBlockSchema,
+		imageBlockSchema,
+		noteBlockSchema,
+	],
+} as const;
+
 export const packedPageSchema = {
 	type: 'object',
 	properties: {
@@ -38,6 +140,7 @@ export const packedPageSchema = {
 			items: {
 				type: 'object',
 				optional: false, nullable: false,
+				ref: 'PageBlock',
 			},
 		},
 		variables: {
diff --git a/packages/backend/src/models/json-schema/queue.ts b/packages/backend/src/models/json-schema/queue.ts
index 43da6e605d..2ecf5c831f 100644
--- a/packages/backend/src/models/json-schema/queue.ts
+++ b/packages/backend/src/models/json-schema/queue.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/json-schema/renote-muting.ts b/packages/backend/src/models/json-schema/renote-muting.ts
index feed1ceb09..344d6c7c00 100644
--- a/packages/backend/src/models/json-schema/renote-muting.ts
+++ b/packages/backend/src/models/json-schema/renote-muting.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -25,7 +25,7 @@ export const packedRenoteMutingSchema = {
 		mutee: {
 			type: 'object',
 			optional: false, nullable: false,
-			ref: 'UserDetailed',
+			ref: 'UserDetailedNotMe',
 		},
 	},
 } as const;
diff --git a/packages/backend/src/models/json-schema/reversi-game.ts b/packages/backend/src/models/json-schema/reversi-game.ts
new file mode 100644
index 0000000000..cb37200384
--- /dev/null
+++ b/packages/backend/src/models/json-schema/reversi-game.ts
@@ -0,0 +1,243 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export const packedReversiGameLiteSchema = {
+	type: 'object',
+	properties: {
+		id: {
+			type: 'string',
+			optional: false, nullable: false,
+			format: 'id',
+		},
+		createdAt: {
+			type: 'string',
+			optional: false, nullable: false,
+			format: 'date-time',
+		},
+		startedAt: {
+			type: 'string',
+			optional: false, nullable: true,
+			format: 'date-time',
+		},
+		endedAt: {
+			type: 'string',
+			optional: false, nullable: true,
+			format: 'date-time',
+		},
+		isStarted: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		isEnded: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		user1Id: {
+			type: 'string',
+			optional: false, nullable: false,
+			format: 'id',
+		},
+		user2Id: {
+			type: 'string',
+			optional: false, nullable: false,
+			format: 'id',
+		},
+		user1: {
+			type: 'object',
+			optional: false, nullable: false,
+			ref: 'UserLite',
+		},
+		user2: {
+			type: 'object',
+			optional: false, nullable: false,
+			ref: 'UserLite',
+		},
+		winnerId: {
+			type: 'string',
+			optional: false, nullable: true,
+			format: 'id',
+		},
+		winner: {
+			type: 'object',
+			optional: false, nullable: true,
+			ref: 'UserLite',
+		},
+		surrenderedUserId: {
+			type: 'string',
+			optional: false, nullable: true,
+			format: 'id',
+		},
+		timeoutUserId: {
+			type: 'string',
+			optional: false, nullable: true,
+			format: 'id',
+		},
+		black: {
+			type: 'number',
+			optional: false, nullable: true,
+		},
+		bw: {
+			type: 'string',
+			optional: false, nullable: false,
+		},
+		noIrregularRules: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		isLlotheo: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		canPutEverywhere: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		loopedBoard: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		timeLimitForEachTurn: {
+			type: 'number',
+			optional: false, nullable: false,
+		},
+	},
+} as const;
+
+export const packedReversiGameDetailedSchema = {
+	type: 'object',
+	properties: {
+		id: {
+			type: 'string',
+			optional: false, nullable: false,
+			format: 'id',
+		},
+		createdAt: {
+			type: 'string',
+			optional: false, nullable: false,
+			format: 'date-time',
+		},
+		startedAt: {
+			type: 'string',
+			optional: false, nullable: true,
+			format: 'date-time',
+		},
+		endedAt: {
+			type: 'string',
+			optional: false, nullable: true,
+			format: 'date-time',
+		},
+		isStarted: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		isEnded: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		form1: {
+			type: 'object',
+			optional: false, nullable: true,
+		},
+		form2: {
+			type: 'object',
+			optional: false, nullable: true,
+		},
+		user1Ready: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		user2Ready: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		user1Id: {
+			type: 'string',
+			optional: false, nullable: false,
+			format: 'id',
+		},
+		user2Id: {
+			type: 'string',
+			optional: false, nullable: false,
+			format: 'id',
+		},
+		user1: {
+			type: 'object',
+			optional: false, nullable: false,
+			ref: 'UserLite',
+		},
+		user2: {
+			type: 'object',
+			optional: false, nullable: false,
+			ref: 'UserLite',
+		},
+		winnerId: {
+			type: 'string',
+			optional: false, nullable: true,
+			format: 'id',
+		},
+		winner: {
+			type: 'object',
+			optional: false, nullable: true,
+			ref: 'UserLite',
+		},
+		surrenderedUserId: {
+			type: 'string',
+			optional: false, nullable: true,
+			format: 'id',
+		},
+		timeoutUserId: {
+			type: 'string',
+			optional: false, nullable: true,
+			format: 'id',
+		},
+		black: {
+			type: 'number',
+			optional: false, nullable: true,
+		},
+		bw: {
+			type: 'string',
+			optional: false, nullable: false,
+		},
+		noIrregularRules: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		isLlotheo: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		canPutEverywhere: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		loopedBoard: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		timeLimitForEachTurn: {
+			type: 'number',
+			optional: false, nullable: false,
+		},
+		logs: {
+			type: 'array',
+			optional: false, nullable: false,
+			items: {
+				type: 'array',
+				optional: false, nullable: false,
+				items: {
+					type: 'number',
+				},
+			},
+		},
+		map: {
+			type: 'array',
+			optional: false, nullable: false,
+			items: {
+				type: 'string',
+				optional: false, nullable: false,
+			},
+		},
+	},
+} as const;
diff --git a/packages/backend/src/models/json-schema/role.ts b/packages/backend/src/models/json-schema/role.ts
index b0c6804bb8..7eba1d5443 100644
--- a/packages/backend/src/models/json-schema/role.ts
+++ b/packages/backend/src/models/json-schema/role.ts
@@ -1,26 +1,260 @@
-const rolePolicyValue = {
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export const packedRoleCondFormulaLogicsSchema = {
 	type: 'object',
 	properties: {
+		id: {
+			type: 'string', optional: false,
+		},
+		type: {
+			type: 'string',
+			nullable: false, optional: false,
+			enum: ['and', 'or'],
+		},
+		values: {
+			type: 'array',
+			nullable: false, optional: false,
+			items: {
+				ref: 'RoleCondFormulaValue',
+			},
+		},
+	},
+} as const;
+
+export const packedRoleCondFormulaValueNot = {
+	type: 'object',
+	properties: {
+		id: {
+			type: 'string', optional: false,
+		},
+		type: {
+			type: 'string',
+			nullable: false, optional: false,
+			enum: ['not'],
+		},
 		value: {
-			oneOf: [
-				{
-					type: 'integer',
-					optional: false, nullable: false,
-				},
-				{
-					type: 'boolean',
-					optional: false, nullable: false,
-				},
+			type: 'object',
+			optional: false,
+			ref: 'RoleCondFormulaValue',
+		},
+	},
+} as const;
+
+export const packedRoleCondFormulaValueIsLocalOrRemoteSchema = {
+	type: 'object',
+	properties: {
+		id: {
+			type: 'string', optional: false,
+		},
+		type: {
+			type: 'string',
+			nullable: false, optional: false,
+			enum: ['isLocal', 'isRemote'],
+		},
+	},
+} as const;
+
+export const packedRoleCondFormulaValueAssignedRoleSchema = {
+	type: 'object',
+	properties: {
+		id: {
+			type: 'string', optional: false,
+		},
+		type: {
+			type: 'string',
+			nullable: false, optional: false,
+			enum: ['roleAssignedTo'],
+		},
+		roleId: {
+			type: 'string',
+			nullable: false, optional: false,
+			format: 'id',
+			example: 'xxxxxxxxxx',
+		},
+	},
+} as const;
+
+export const packedRoleCondFormulaValueCreatedSchema = {
+	type: 'object',
+	properties: {
+		id: {
+			type: 'string', optional: false,
+		},
+		type: {
+			type: 'string',
+			nullable: false, optional: false,
+			enum: [
+				'createdLessThan',
+				'createdMoreThan',
 			],
 		},
-		priority: {
+		sec: {
+			type: 'number',
+			nullable: false, optional: false,
+		},
+	},
+} as const;
+
+export const packedRoleCondFormulaFollowersOrFollowingOrNotesSchema = {
+	type: 'object',
+	properties: {
+		id: {
+			type: 'string', optional: false,
+		},
+		type: {
+			type: 'string',
+			nullable: false, optional: false,
+			enum: [
+				'followersLessThanOrEq',
+				'followersMoreThanOrEq',
+				'followingLessThanOrEq',
+				'followingMoreThanOrEq',
+				'notesLessThanOrEq',
+				'notesMoreThanOrEq',
+			],
+		},
+		value: {
+			type: 'number',
+			nullable: false, optional: false,
+		},
+	},
+} as const;
+
+export const packedRoleCondFormulaValueSchema = {
+	type: 'object',
+	oneOf: [
+		{
+			ref: 'RoleCondFormulaLogics',
+		},
+		{
+			ref: 'RoleCondFormulaValueNot',
+		},
+		{
+			ref: 'RoleCondFormulaValueIsLocalOrRemote',
+		},
+		{
+			ref: 'RoleCondFormulaValueAssignedRole',
+		},
+		{
+			ref: 'RoleCondFormulaValueCreated',
+		},
+		{
+			ref: 'RoleCondFormulaFollowersOrFollowingOrNotes',
+		},
+	],
+} as const;
+
+export const packedRolePoliciesSchema = {
+	type: 'object',
+	optional: false, nullable: false,
+	properties: {
+		gtlAvailable: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		ltlAvailable: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		btlAvailable: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		canPublicNote: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		mentionLimit: {
 			type: 'integer',
 			optional: false, nullable: false,
 		},
-		useDefault: {
+		canInvite: {
 			type: 'boolean',
 			optional: false, nullable: false,
 		},
+		inviteLimit: {
+			type: 'integer',
+			optional: false, nullable: false,
+		},
+		inviteLimitCycle: {
+			type: 'integer',
+			optional: false, nullable: false,
+		},
+		inviteExpirationTime: {
+			type: 'integer',
+			optional: false, nullable: false,
+		},
+		canManageCustomEmojis: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		canManageAvatarDecorations: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		canSearchNotes: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		canUseTranslator: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		canHideAds: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		driveCapacityMb: {
+			type: 'integer',
+			optional: false, nullable: false,
+		},
+		alwaysMarkNsfw: {
+			type: 'boolean',
+			optional: false, nullable: false,
+		},
+		pinLimit: {
+			type: 'integer',
+			optional: false, nullable: false,
+		},
+		antennaLimit: {
+			type: 'integer',
+			optional: false, nullable: false,
+		},
+		wordMuteLimit: {
+			type: 'integer',
+			optional: false, nullable: false,
+		},
+		webhookLimit: {
+			type: 'integer',
+			optional: false, nullable: false,
+		},
+		clipLimit: {
+			type: 'integer',
+			optional: false, nullable: false,
+		},
+		noteEachClipsLimit: {
+			type: 'integer',
+			optional: false, nullable: false,
+		},
+		userListLimit: {
+			type: 'integer',
+			optional: false, nullable: false,
+		},
+		userEachUserListsLimit: {
+			type: 'integer',
+			optional: false, nullable: false,
+		},
+		rateLimitFactor: {
+			type: 'integer',
+			optional: false, nullable: false,
+		},
+		avatarDecorationLimit: {
+			type: 'integer',
+			optional: false, nullable: false,
+		},
 	},
 } as const;
 
@@ -97,6 +331,7 @@ export const packedRoleSchema = {
 				condFormula: {
 					type: 'object',
 					optional: false, nullable: false,
+					ref: 'RoleCondFormulaValue',
 				},
 				isPublic: {
 					type: 'boolean',
@@ -121,31 +356,28 @@ export const packedRoleSchema = {
 				policies: {
 					type: 'object',
 					optional: false, nullable: false,
-					properties: {
-						pinLimit: rolePolicyValue,
-						canInvite: rolePolicyValue,
-						clipLimit: rolePolicyValue,
-						canHideAds: rolePolicyValue,
-						inviteLimit: rolePolicyValue,
-						antennaLimit: rolePolicyValue,
-						gtlAvailable: rolePolicyValue,
-						ltlAvailable: rolePolicyValue,
-						webhookLimit: rolePolicyValue,
-						canPublicNote: rolePolicyValue,
-						userListLimit: rolePolicyValue,
-						wordMuteLimit: rolePolicyValue,
-						alwaysMarkNsfw: rolePolicyValue,
-						canSearchNotes: rolePolicyValue,
-						driveCapacityMb: rolePolicyValue,
-						rateLimitFactor: rolePolicyValue,
-						inviteLimitCycle: rolePolicyValue,
-						noteEachClipsLimit: rolePolicyValue,
-						inviteExpirationTime: rolePolicyValue,
-						canManageCustomEmojis: rolePolicyValue,
-						userEachUserListsLimit: rolePolicyValue,
-						canManageAvatarDecorations: rolePolicyValue,
-						canUseTranslator: rolePolicyValue,
-						avatarDecorationLimit: rolePolicyValue,
+					additionalProperties: {
+						anyOf: [{
+							type: 'object',
+							properties: {
+								value: {
+									oneOf: [
+										{
+											type: 'integer',
+										},
+										{
+											type: 'boolean',
+										},
+									],
+								},
+								priority: {
+									type: 'integer',
+								},
+								useDefault: {
+									type: 'boolean',
+								},
+							},
+						}],
 					},
 				},
 				usersCount: {
diff --git a/packages/backend/src/models/json-schema/user-list.ts b/packages/backend/src/models/json-schema/user-list.ts
index e257d9984c..dc9af25602 100644
--- a/packages/backend/src/models/json-schema/user-list.ts
+++ b/packages/backend/src/models/json-schema/user-list.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts
index af67e62afa..33a3efd453 100644
--- a/packages/backend/src/models/json-schema/user.ts
+++ b/packages/backend/src/models/json-schema/user.ts
@@ -1,18 +1,40 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-const notificationRecieveConfig = {
+export const notificationRecieveConfig = {
 	type: 'object',
-	nullable: false, optional: true,
-	properties: {
-		type: {
-			type: 'string',
-			nullable: false, optional: false,
-			enum: ['all', 'following', 'follower', 'mutualFollow', 'list', 'never'],
+	oneOf: [
+		{
+			type: 'object',
+			nullable: false,
+			properties: {
+				type: {
+					type: 'string',
+					nullable: false,
+					enum: ['all', 'following', 'follower', 'mutualFollow', 'followingOrFollower', 'never'],
+				},
+			},
+			required: ['type'],
 		},
-	},
+		{
+			type: 'object',
+			nullable: false,
+			properties: {
+				type: {
+					type: 'string',
+					nullable: false,
+					enum: ['list'],
+				},
+				userListId: {
+					type: 'string',
+					format: 'misskey:id',
+				},
+			},
+			required: ['type', 'userListId'],
+		},
+	],
 } as const;
 
 export const packedUserLiteSchema = {
@@ -148,6 +170,9 @@ export const packedUserLiteSchema = {
 		emojis: {
 			type: 'object',
 			nullable: false, optional: false,
+			additionalProperties: {
+				type: 'string',
+			},
 		},
 		onlineStatus: {
 			type: 'string',
@@ -584,15 +609,20 @@ export const packedMeDetailedOnlySchema = {
 			type: 'object',
 			nullable: false, optional: false,
 			properties: {
-				app: notificationRecieveConfig,
-				quote: notificationRecieveConfig,
-				reply: notificationRecieveConfig,
-				follow: notificationRecieveConfig,
-				renote: notificationRecieveConfig,
-				mention: notificationRecieveConfig,
-				reaction: notificationRecieveConfig,
-				pollEnded: notificationRecieveConfig,
-				receiveFollowRequest: notificationRecieveConfig,
+				note: { optional: true, ...notificationRecieveConfig },
+				follow: { optional: true, ...notificationRecieveConfig },
+				mention: { optional: true, ...notificationRecieveConfig },
+				reply: { optional: true, ...notificationRecieveConfig },
+				renote: { optional: true, ...notificationRecieveConfig },
+				quote: { optional: true, ...notificationRecieveConfig },
+				reaction: { optional: true, ...notificationRecieveConfig },
+				pollEnded: { optional: true, ...notificationRecieveConfig },
+				receiveFollowRequest: { optional: true, ...notificationRecieveConfig },
+				followRequestAccepted: { optional: true, ...notificationRecieveConfig },
+				roleAssigned: { optional: true, ...notificationRecieveConfig },
+				achievementEarned: { optional: true, ...notificationRecieveConfig },
+				app: { optional: true, ...notificationRecieveConfig },
+				test: { optional: true, ...notificationRecieveConfig },
 			},
 		},
 		emailNotificationTypes: {
@@ -628,104 +658,7 @@ export const packedMeDetailedOnlySchema = {
 		policies: {
 			type: 'object',
 			nullable: false, optional: false,
-			properties: {
-				gtlAvailable: {
-					type: 'boolean',
-					nullable: false, optional: false,
-				},
-				ltlAvailable: {
-					type: 'boolean',
-					nullable: false, optional: false,
-				},
-				canPublicNote: {
-					type: 'boolean',
-					nullable: false, optional: false,
-				},
-				canInvite: {
-					type: 'boolean',
-					nullable: false, optional: false,
-				},
-				inviteLimit: {
-					type: 'number',
-					nullable: false, optional: false,
-				},
-				inviteLimitCycle: {
-					type: 'number',
-					nullable: false, optional: false,
-				},
-				inviteExpirationTime: {
-					type: 'number',
-					nullable: false, optional: false,
-				},
-				canManageCustomEmojis: {
-					type: 'boolean',
-					nullable: false, optional: false,
-				},
-				canManageAvatarDecorations: {
-					type: 'boolean',
-					nullable: false, optional: false,
-				},
-				canSearchNotes: {
-					type: 'boolean',
-					nullable: false, optional: false,
-				},
-				canUseTranslator: {
-					type: 'boolean',
-					nullable: false, optional: false,
-				},
-				canHideAds: {
-					type: 'boolean',
-					nullable: false, optional: false,
-				},
-				driveCapacityMb: {
-					type: 'number',
-					nullable: false, optional: false,
-				},
-				alwaysMarkNsfw: {
-					type: 'boolean',
-					nullable: false, optional: false,
-				},
-				pinLimit: {
-					type: 'number',
-					nullable: false, optional: false,
-				},
-				antennaLimit: {
-					type: 'number',
-					nullable: false, optional: false,
-				},
-				wordMuteLimit: {
-					type: 'number',
-					nullable: false, optional: false,
-				},
-				webhookLimit: {
-					type: 'number',
-					nullable: false, optional: false,
-				},
-				clipLimit: {
-					type: 'number',
-					nullable: false, optional: false,
-				},
-				noteEachClipsLimit: {
-					type: 'number',
-					nullable: false, optional: false,
-				},
-				userListLimit: {
-					type: 'number',
-					nullable: false, optional: false,
-				},
-				userEachUserListsLimit: {
-					type: 'number',
-					nullable: false, optional: false,
-				},
-				rateLimitFactor: {
-					type: 'number',
-					nullable: false, optional: false,
-				},
-				avatarDecorationLimit: {
-					type: 'number',
-					nullable: false, optional: false,
-				},
-			},
+			ref: 'RolePolicies',
 		},
 		//#region secrets
 		email: {
@@ -820,13 +753,5 @@ export const packedUserSchema = {
 			type: 'object',
 			ref: 'UserDetailed',
 		},
-		{
-			type: 'object',
-			ref: 'UserDetailedNotMe',
-		},
-		{
-			type: 'object',
-			ref: 'MeDetailed',
-		},
 	],
 } as const;
diff --git a/packages/backend/src/models/util/id.ts b/packages/backend/src/models/util/id.ts
index 81e83b8db9..2d742702c7 100644
--- a/packages/backend/src/models/util/id.ts
+++ b/packages/backend/src/models/util/id.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/postgres.ts b/packages/backend/src/postgres.ts
index 18773a1b66..4a1b42383f 100644
--- a/packages/backend/src/postgres.ts
+++ b/packages/backend/src/postgres.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -77,6 +77,8 @@ import { MiFlash } from '@/models/Flash.js';
 import { MiFlashLike } from '@/models/FlashLike.js';
 import { MiUserMemo } from '@/models/UserMemo.js';
 import { NoteEdit } from '@/models/NoteEdit.js';
+import { MiBubbleGameRecord } from '@/models/BubbleGameRecord.js';
+import { MiReversiGame } from '@/models/ReversiGame.js';
 
 import { Config } from '@/config.js';
 import MisskeyLogger from '@/logger.js';
@@ -192,6 +194,8 @@ export const entities = [
 	MiFlashLike,
 	MiUserMemo,
 	NoteEdit,
+	MiBubbleGameRecord,
+	MiReversiGame,
 	...charts,
 ];
 
@@ -209,22 +213,24 @@ export function createPostgresDataSource(config: Config) {
 			statement_timeout: 1000 * 10,
 			...config.db.extra,
 		},
-		replication: config.dbReplications ? {
-			master: {
-				host: config.db.host,
-				port: config.db.port,
-				username: config.db.user,
-				password: config.db.pass,
-				database: config.db.db,
+		...(config.dbReplications ? {
+			replication: {
+				master: {
+					host: config.db.host,
+					port: config.db.port,
+					username: config.db.user,
+					password: config.db.pass,
+					database: config.db.db,
+				},
+				slaves: config.dbSlaves!.map(rep => ({
+					host: rep.host,
+					port: rep.port,
+					username: rep.user,
+					password: rep.pass,
+					database: rep.db,
+				})),
 			},
-			slaves: config.dbSlaves!.map(rep => ({
-				host: rep.host,
-				port: rep.port,
-				username: rep.user,
-				password: rep.pass,
-				database: rep.db,
-			})),
-		} : undefined,
+		} : {}),
 		synchronize: process.env.NODE_ENV === 'test',
 		dropSchema: process.env.NODE_ENV === 'test',
 		cache: !config.db.disableCache && process.env.NODE_ENV !== 'test' ? { // dbをcloseしても何故かredisのコネクションが内部的に残り続けるようで、テストの際に支障が出るため無効にする(キャッシュも含めてテストしたいため本当は有効にしたいが...)
diff --git a/packages/backend/src/queue/QueueLoggerService.ts b/packages/backend/src/queue/QueueLoggerService.ts
index 618d1d5c2f..65869afd46 100644
--- a/packages/backend/src/queue/QueueLoggerService.ts
+++ b/packages/backend/src/queue/QueueLoggerService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/queue/QueueProcessorModule.ts b/packages/backend/src/queue/QueueProcessorModule.ts
index 29dc78605b..d7316e19e3 100644
--- a/packages/backend/src/queue/QueueProcessorModule.ts
+++ b/packages/backend/src/queue/QueueProcessorModule.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -25,6 +25,7 @@ import { ExportCustomEmojisProcessorService } from './processors/ExportCustomEmo
 import { ExportFollowingProcessorService } from './processors/ExportFollowingProcessorService.js';
 import { ExportMutingProcessorService } from './processors/ExportMutingProcessorService.js';
 import { ExportNotesProcessorService } from './processors/ExportNotesProcessorService.js';
+import { ExportClipsProcessorService } from './processors/ExportClipsProcessorService.js';
 import { ExportUserListsProcessorService } from './processors/ExportUserListsProcessorService.js';
 import { ExportAntennasProcessorService } from './processors/ExportAntennasProcessorService.js';
 import { ImportBlockingProcessorService } from './processors/ImportBlockingProcessorService.js';
@@ -56,6 +57,7 @@ import { RelationshipProcessorService } from './processors/RelationshipProcessor
 		ExportAccountDataProcessorService,
 		ExportCustomEmojisProcessorService,
 		ExportNotesProcessorService,
+		ExportClipsProcessorService,
 		ExportFavoritesProcessorService,
 		ExportFollowingProcessorService,
 		ExportMutingProcessorService,
diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts
index ea3ecd4ded..76b6d7fb05 100644
--- a/packages/backend/src/queue/QueueProcessorService.ts
+++ b/packages/backend/src/queue/QueueProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -17,6 +17,7 @@ import { DeleteDriveFilesProcessorService } from './processors/DeleteDriveFilesP
 import { ExportAccountDataProcessorService } from './processors/ExportAccountDataProcessorService.js';
 import { ExportCustomEmojisProcessorService } from './processors/ExportCustomEmojisProcessorService.js';
 import { ExportNotesProcessorService } from './processors/ExportNotesProcessorService.js';
+import { ExportClipsProcessorService } from './processors/ExportClipsProcessorService.js';
 import { ExportFollowingProcessorService } from './processors/ExportFollowingProcessorService.js';
 import { ExportMutingProcessorService } from './processors/ExportMutingProcessorService.js';
 import { ExportBlockingProcessorService } from './processors/ExportBlockingProcessorService.js';
@@ -94,6 +95,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
 		private exportAccountDataProcessorService: ExportAccountDataProcessorService,
 		private exportCustomEmojisProcessorService: ExportCustomEmojisProcessorService,
 		private exportNotesProcessorService: ExportNotesProcessorService,
+		private exportClipsProcessorService: ExportClipsProcessorService,
 		private exportFavoritesProcessorService: ExportFavoritesProcessorService,
 		private exportFollowingProcessorService: ExportFollowingProcessorService,
 		private exportMutingProcessorService: ExportMutingProcessorService,
@@ -169,6 +171,7 @@ export class QueueProcessorService implements OnApplicationShutdown {
 				case 'exportAccountData': return this.exportAccountDataProcessorService.process(job);
 				case 'exportCustomEmojis': return this.exportCustomEmojisProcessorService.process(job);
 				case 'exportNotes': return this.exportNotesProcessorService.process(job);
+				case 'exportClips': return this.exportClipsProcessorService.process(job);
 				case 'exportFavorites': return this.exportFavoritesProcessorService.process(job);
 				case 'exportFollowing': return this.exportFollowingProcessorService.process(job);
 				case 'exportMuting': return this.exportMutingProcessorService.process(job);
@@ -292,9 +295,9 @@ export class QueueProcessorService implements OnApplicationShutdown {
 		}, {
 			...baseQueueOptions(this.config, QUEUE.RELATIONSHIP),
 			autorun: false,
-			concurrency: this.config.relashionshipJobConcurrency ?? 16,
+			concurrency: this.config.relationshipJobConcurrency ?? 16,
 			limiter: {
-				max: this.config.relashionshipJobPerSec ?? 64,
+				max: this.config.relationshipJobPerSec ?? 64,
 				duration: 1000,
 			},
 		});
diff --git a/packages/backend/src/queue/const.ts b/packages/backend/src/queue/const.ts
index 87d075304d..132e916612 100644
--- a/packages/backend/src/queue/const.ts
+++ b/packages/backend/src/queue/const.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts b/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts
index 9f49d85c7f..4769cccabf 100644
--- a/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts
+++ b/packages/backend/src/queue/processors/AggregateRetentionProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts b/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts
index 9b07389dc3..448fc9c763 100644
--- a/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts
+++ b/packages/backend/src/queue/processors/CheckExpiredMutingsProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/queue/processors/CleanChartsProcessorService.ts b/packages/backend/src/queue/processors/CleanChartsProcessorService.ts
index 55c444eee6..110468801c 100644
--- a/packages/backend/src/queue/processors/CleanChartsProcessorService.ts
+++ b/packages/backend/src/queue/processors/CleanChartsProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/queue/processors/CleanProcessorService.ts b/packages/backend/src/queue/processors/CleanProcessorService.ts
index e252c5d8a1..a26b69cd2b 100644
--- a/packages/backend/src/queue/processors/CleanProcessorService.ts
+++ b/packages/backend/src/queue/processors/CleanProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -11,6 +11,7 @@ import type Logger from '@/logger.js';
 import { bindThis } from '@/decorators.js';
 import { IdService } from '@/core/IdService.js';
 import type { Config } from '@/config.js';
+import { ReversiService } from '@/core/ReversiService.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
 import type * as Bull from 'bullmq';
 
@@ -32,6 +33,7 @@ export class CleanProcessorService {
 		private roleAssignmentsRepository: RoleAssignmentsRepository,
 
 		private queueLoggerService: QueueLoggerService,
+		private reversiService: ReversiService,
 		private idService: IdService,
 	) {
 		this.logger = this.queueLoggerService.logger.createSubLogger('clean');
@@ -65,6 +67,8 @@ export class CleanProcessorService {
 			});
 		}
 
+		this.reversiService.cleanOutdatedGames();
+
 		this.logger.succ('Cleaned.');
 	}
 }
diff --git a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts
index b62cc8a8fd..917de8b72c 100644
--- a/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts
+++ b/packages/backend/src/queue/processors/CleanRemoteFilesProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts
index 56369f3a7a..0e604a0501 100644
--- a/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts
+++ b/packages/backend/src/queue/processors/DeleteAccountProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts b/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts
index 6d0a45bcc0..291fa4a6d8 100644
--- a/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts
+++ b/packages/backend/src/queue/processors/DeleteDriveFilesProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/queue/processors/DeleteFileProcessorService.ts b/packages/backend/src/queue/processors/DeleteFileProcessorService.ts
index a4638bfaaf..fc1dd93ce7 100644
--- a/packages/backend/src/queue/processors/DeleteFileProcessorService.ts
+++ b/packages/backend/src/queue/processors/DeleteFileProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts
index 4a1d9f28b4..5fed070929 100644
--- a/packages/backend/src/queue/processors/DeliverProcessorService.ts
+++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -72,7 +72,7 @@ export class DeliverProcessorService {
 		}
 
 		try {
-			await this.apRequestService.signedPost(job.data.user, job.data.to, job.data.content);
+			await this.apRequestService.signedPost(job.data.user, job.data.to, job.data.content, job.data.digest);
 
 			// Update stats
 			this.federatedInstanceService.fetch(host).then(i => {
@@ -111,7 +111,7 @@ export class DeliverProcessorService {
 
 			if (res instanceof StatusError) {
 				// 4xx
-				if (res.isClientError) {
+				if (!res.isRetryable) {
 					// 相手が閉鎖していることを明示しているため、配送停止する
 					if (job.data.isSharedInbox && res.statusCode === 410) {
 						this.federatedInstanceService.fetch(host).then(i => {
diff --git a/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts b/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts
index 4a48084436..29c1f27bb1 100644
--- a/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts
+++ b/packages/backend/src/queue/processors/EndedPollNotificationProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts
index d0968d2923..af48bad417 100644
--- a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts
index 0a37e3ca1e..6ec3c18786 100644
--- a/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportBlockingProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/queue/processors/ExportClipsProcessorService.ts b/packages/backend/src/queue/processors/ExportClipsProcessorService.ts
new file mode 100644
index 0000000000..01eab26e96
--- /dev/null
+++ b/packages/backend/src/queue/processors/ExportClipsProcessorService.ts
@@ -0,0 +1,206 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import * as fs from 'node:fs';
+import { Writable } from 'node:stream';
+import { Inject, Injectable, StreamableFile } from '@nestjs/common';
+import { MoreThan } from 'typeorm';
+import { format as dateFormat } from 'date-fns';
+import { DI } from '@/di-symbols.js';
+import type { ClipNotesRepository, ClipsRepository, MiClip, MiClipNote, MiUser, NotesRepository, PollsRepository, UsersRepository } from '@/models/_.js';
+import type Logger from '@/logger.js';
+import { DriveService } from '@/core/DriveService.js';
+import { createTemp } from '@/misc/create-temp.js';
+import type { MiPoll } from '@/models/Poll.js';
+import type { MiNote } from '@/models/Note.js';
+import { bindThis } from '@/decorators.js';
+import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
+import { Packed } from '@/misc/json-schema.js';
+import { IdService } from '@/core/IdService.js';
+import { QueueLoggerService } from '../QueueLoggerService.js';
+import type * as Bull from 'bullmq';
+import type { DbJobDataWithUser } from '../types.js';
+
+@Injectable()
+export class ExportClipsProcessorService {
+	private logger: Logger;
+
+	constructor(
+		@Inject(DI.usersRepository)
+		private usersRepository: UsersRepository,
+
+		@Inject(DI.pollsRepository)
+		private pollsRepository: PollsRepository,
+
+		@Inject(DI.clipsRepository)
+		private clipsRepository: ClipsRepository,
+
+		@Inject(DI.clipNotesRepository)
+		private clipNotesRepository: ClipNotesRepository,
+
+		private driveService: DriveService,
+		private queueLoggerService: QueueLoggerService,
+		private idService: IdService,
+	) {
+		this.logger = this.queueLoggerService.logger.createSubLogger('export-clips');
+	}
+
+	@bindThis
+	public async process(job: Bull.Job<DbJobDataWithUser>): Promise<void> {
+		this.logger.info(`Exporting clips of ${job.data.user.id} ...`);
+
+		const user = await this.usersRepository.findOneBy({ id: job.data.user.id });
+		if (user == null) {
+			return;
+		}
+
+		// Create temp file
+		const [path, cleanup] = await createTemp();
+
+		this.logger.info(`Temp file is ${path}`);
+
+		try {
+			const stream = Writable.toWeb(fs.createWriteStream(path, { flags: 'a' }));
+			const writer = stream.getWriter();
+			writer.closed.catch(this.logger.error);
+
+			await writer.write('[');
+
+			await this.processClips(writer, user, job);
+
+			await writer.write(']');
+			await writer.close();
+
+			this.logger.succ(`Exported to: ${path}`);
+
+			const fileName = 'clips-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json';
+			const driveFile = await this.driveService.addFile({ user, path, name: fileName, force: true, ext: 'json' });
+
+			this.logger.succ(`Exported to: ${driveFile.id}`);
+		} finally {
+			cleanup();
+		}
+	}
+
+	async processClips(writer: WritableStreamDefaultWriter, user: MiUser, job: Bull.Job<DbJobDataWithUser>) {
+		let exportedClipsCount = 0;
+		let cursor: MiClip['id'] | null = null;
+
+		while (true) {
+			const clips = await this.clipsRepository.find({
+				where: {
+					userId: user.id,
+					...(cursor ? { id: MoreThan(cursor) } : {}),
+				},
+				take: 100,
+				order: {
+					id: 1,
+				},
+			});
+
+			if (clips.length === 0) {
+				job.updateProgress(100);
+				break;
+			}
+
+			cursor = clips.at(-1)?.id ?? null;
+
+			for (const clip of clips) {
+				// Stringify but remove the last `]}`
+				const content = JSON.stringify(this.serializeClip(clip)).slice(0, -2);
+				const isFirst = exportedClipsCount === 0;
+				await writer.write(isFirst ? content : ',\n' + content);
+
+				await this.processClipNotes(writer, clip.id);
+
+				await writer.write(']}');
+				exportedClipsCount++;
+			}
+
+			const total = await this.clipsRepository.countBy({
+				userId: user.id,
+			});
+
+			job.updateProgress(exportedClipsCount / total);
+		}
+	}
+
+	async processClipNotes(writer: WritableStreamDefaultWriter, clipId: string): Promise<void> {
+		let exportedClipNotesCount = 0;
+		let cursor: MiClipNote['id'] | null = null;
+
+		while (true) {
+			const clipNotes = await this.clipNotesRepository.find({
+				where: {
+					clipId,
+					...(cursor ? { id: MoreThan(cursor) } : {}),
+				},
+				take: 100,
+				order: {
+					id: 1,
+				},
+				relations: ['note', 'note.user'],
+			}) as (MiClipNote & { note: MiNote & { user: MiUser } })[];
+
+			if (clipNotes.length === 0) {
+				break;
+			}
+
+			cursor = clipNotes.at(-1)?.id ?? null;
+
+			for (const clipNote of clipNotes) {
+				let poll: MiPoll | undefined;
+				if (clipNote.note.hasPoll) {
+					poll = await this.pollsRepository.findOneByOrFail({ noteId: clipNote.note.id });
+				}
+				const content = JSON.stringify(this.serializeClipNote(clipNote, poll));
+				const isFirst = exportedClipNotesCount === 0;
+				await writer.write(isFirst ? content : ',\n' + content);
+
+				exportedClipNotesCount++;
+			}
+		}
+	}
+
+	private serializeClip(clip: MiClip): Record<string, unknown> {
+		return {
+			id: clip.id,
+			name: clip.name,
+			description: clip.description,
+			lastClippedAt: clip.lastClippedAt?.toISOString(),
+			clipNotes: [],
+		};
+	}
+
+	private serializeClipNote(clip: MiClipNote & { note: MiNote & { user: MiUser } }, poll: MiPoll | undefined): Record<string, unknown> {
+		return {
+			id: clip.id,
+			createdAt: this.idService.parse(clip.id).date.toISOString(),
+			note: {
+				id: clip.note.id,
+				text: clip.note.text,
+				createdAt: this.idService.parse(clip.note.id).date.toISOString(),
+				fileIds: clip.note.fileIds,
+				replyId: clip.note.replyId,
+				renoteId: clip.note.renoteId,
+				poll: poll,
+				cw: clip.note.cw,
+				visibility: clip.note.visibility,
+				visibleUserIds: clip.note.visibleUserIds,
+				localOnly: clip.note.localOnly,
+				reactionAcceptance: clip.note.reactionAcceptance,
+				uri: clip.note.uri,
+				url: clip.note.url,
+				user: {
+					id: clip.note.user.id,
+					name: clip.note.user.name,
+					username: clip.note.user.username,
+					host: clip.note.user.host,
+					uri: clip.note.user.uri,
+				},
+			},
+		};
+	}
+}
diff --git a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts
index d5387fe42e..e4eb4791bd 100644
--- a/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportCustomEmojisProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts
index af2a3434a9..7bb626dd31 100644
--- a/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportFavoritesProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts
index c9739eb1cb..1cc80e66d7 100644
--- a/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportFollowingProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts
index c8425c1f2d..243b74f2c2 100644
--- a/packages/backend/src/queue/processors/ExportMutingProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportMutingProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts
index cd4ccb0b07..c7611012d7 100644
--- a/packages/backend/src/queue/processors/ExportNotesProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportNotesProcessorService.ts
@@ -1,9 +1,9 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import * as fs from 'node:fs';
+import { ReadableStream, TextEncoderStream } from 'node:stream/web';
 import { Inject, Injectable } from '@nestjs/common';
 import { MoreThan } from 'typeorm';
 import { format as dateFormat } from 'date-fns';
@@ -18,10 +18,82 @@ import { bindThis } from '@/decorators.js';
 import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
 import { Packed } from '@/misc/json-schema.js';
 import { IdService } from '@/core/IdService.js';
+import { JsonArrayStream } from '@/misc/JsonArrayStream.js';
+import { FileWriterStream } from '@/misc/FileWriterStream.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
 import type * as Bull from 'bullmq';
 import type { DbJobDataWithUser } from '../types.js';
 
+class NoteStream extends ReadableStream<Record<string, unknown>> {
+	constructor(
+		job: Bull.Job,
+		notesRepository: NotesRepository,
+		pollsRepository: PollsRepository,
+		driveFileEntityService: DriveFileEntityService,
+		idService: IdService,
+		userId: string,
+	) {
+		let exportedNotesCount = 0;
+		let cursor: MiNote['id'] | null = null;
+
+		const serialize = (
+			note: MiNote,
+			poll: MiPoll | null,
+			files: Packed<'DriveFile'>[],
+		): Record<string, unknown> => {
+			return {
+				id: note.id,
+				text: note.text,
+				createdAt: idService.parse(note.id).date.toISOString(),
+				fileIds: note.fileIds,
+				files: files,
+				replyId: note.replyId,
+				renoteId: note.renoteId,
+				poll: poll,
+				cw: note.cw,
+				visibility: note.visibility,
+				visibleUserIds: note.visibleUserIds,
+				localOnly: note.localOnly,
+				reactionAcceptance: note.reactionAcceptance,
+			};
+		};
+
+		super({
+			async pull(controller): Promise<void> {
+				const notes = await notesRepository.find({
+					where: {
+						userId,
+						...(cursor !== null ? { id: MoreThan(cursor) } : {}),
+					},
+					take: 100, // 100件ずつ取得
+					order: { id: 1 },
+				});
+
+				if (notes.length === 0) {
+					job.updateProgress(100);
+					controller.close();
+				}
+
+				cursor = notes.at(-1)?.id ?? null;
+
+				for (const note of notes) {
+					const poll = note.hasPoll
+						? await pollsRepository.findOneByOrFail({ noteId: note.id }) // N+1
+						: null;
+					const files = await driveFileEntityService.packManyByIds(note.fileIds); // N+1
+					const content = serialize(note, poll, files);
+
+					controller.enqueue(content);
+					exportedNotesCount++;
+				}
+
+				const total = await notesRepository.countBy({ userId });
+				job.updateProgress(exportedNotesCount / total);
+			},
+		});
+	}
+}
+
 @Injectable()
 export class ExportNotesProcessorService {
 	private logger: Logger;
@@ -59,67 +131,19 @@ export class ExportNotesProcessorService {
 		this.logger.info(`Temp file is ${path}`);
 
 		try {
-			const stream = fs.createWriteStream(path, { flags: 'a' });
+			// メモリが足りなくならないようにストリームで処理する
+			await new NoteStream(
+				job,
+				this.notesRepository,
+				this.pollsRepository,
+				this.driveFileEntityService,
+				this.idService,
+				user.id,
+			)
+				.pipeThrough(new JsonArrayStream())
+				.pipeThrough(new TextEncoderStream())
+				.pipeTo(new FileWriterStream(path));
 
-			const write = (text: string): Promise<void> => {
-				return new Promise<void>((res, rej) => {
-					stream.write(text, err => {
-						if (err) {
-							this.logger.error(err);
-							rej(err);
-						} else {
-							res();
-						}
-					});
-				});
-			};
-
-			await write('[');
-
-			let exportedNotesCount = 0;
-			let cursor: MiNote['id'] | null = null;
-
-			while (true) {
-				const notes = await this.notesRepository.find({
-					where: {
-						userId: user.id,
-						...(cursor ? { id: MoreThan(cursor) } : {}),
-					},
-					take: 100,
-					order: {
-						id: 1,
-					},
-				}) as MiNote[];
-
-				if (notes.length === 0) {
-					job.updateProgress(100);
-					break;
-				}
-
-				cursor = notes.at(-1)?.id ?? null;
-
-				for (const note of notes) {
-					let poll: MiPoll | undefined;
-					if (note.hasPoll) {
-						poll = await this.pollsRepository.findOneByOrFail({ noteId: note.id });
-					}
-					const files = await this.driveFileEntityService.packManyByIds(note.fileIds);
-					const content = JSON.stringify(this.serialize(note, poll, files));
-					const isFirst = exportedNotesCount === 0;
-					await write(isFirst ? content : ',\n' + content);
-					exportedNotesCount++;
-				}
-
-				const total = await this.notesRepository.countBy({
-					userId: user.id,
-				});
-
-				job.updateProgress(exportedNotesCount / total);
-			}
-
-			await write(']');
-
-			stream.end();
 			this.logger.succ(`Exported to: ${path}`);
 
 			const fileName = 'notes-' + dateFormat(new Date(), 'yyyy-MM-dd-HH-mm-ss') + '.json';
@@ -130,22 +154,4 @@ export class ExportNotesProcessorService {
 			cleanup();
 		}
 	}
-
-	private serialize(note: MiNote, poll: MiPoll | null = null, files: Packed<'DriveFile'>[]): Record<string, unknown> {
-		return {
-			id: note.id,
-			text: note.text,
-			createdAt: this.idService.parse(note.id).date.toISOString(),
-			fileIds: note.fileIds,
-			files: files,
-			replyId: note.replyId,
-			renoteId: note.renoteId,
-			poll: poll,
-			cw: note.cw,
-			visibility: note.visibility,
-			visibleUserIds: note.visibleUserIds,
-			localOnly: note.localOnly,
-			reactionAcceptance: note.reactionAcceptance,
-		};
-	}
 }
diff --git a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts
index a3f9441dc2..ee87cff5d3 100644
--- a/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts
+++ b/packages/backend/src/queue/processors/ExportUserListsProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts
index 291ea14b67..951b560597 100644
--- a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts
+++ b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts
index 64520b770b..b78229c648 100644
--- a/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts
+++ b/packages/backend/src/queue/processors/ImportBlockingProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts
index a52af54a39..171809d25c 100644
--- a/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts
+++ b/packages/backend/src/queue/processors/ImportCustomEmojisProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts
index e75499a56f..70c9f3a096 100644
--- a/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts
+++ b/packages/backend/src/queue/processors/ImportFollowingProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts
index 9db4e5d8e0..ec9d2b6c4c 100644
--- a/packages/backend/src/queue/processors/ImportMutingProcessorService.ts
+++ b/packages/backend/src/queue/processors/ImportMutingProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/queue/processors/ImportNotesProcessorService.ts b/packages/backend/src/queue/processors/ImportNotesProcessorService.ts
index d64a861b03..10cd90c4ad 100644
--- a/packages/backend/src/queue/processors/ImportNotesProcessorService.ts
+++ b/packages/backend/src/queue/processors/ImportNotesProcessorService.ts
@@ -1,5 +1,5 @@
 import * as fs from 'node:fs';
-import * as vm from 'node:vm';
+import * as fsp from 'node:fs/promises';
 import * as crypto from 'node:crypto';
 import { Inject, Injectable } from '@nestjs/common';
 import { ZipReader } from 'slacc';
@@ -51,7 +51,7 @@ export class ImportNotesProcessorService {
 
 	@bindThis
 	private async uploadFiles(dir: string, user: MiUser, folder?: MiDriveFolder['id']) {
-		const fileList = fs.readdirSync(dir);
+		const fileList = await fsp.readdir(dir);
 		for await (const file of fileList) {
 			const name = `${dir}/${file}`;
 			if (fs.statSync(name).isDirectory()) {
@@ -132,7 +132,7 @@ export class ImportNotesProcessorService {
 
 	private parseTwitterFile(str : string) : null | [{ tweet: any }] {
 		const removed = str.replace(new RegExp('window\\.YTD\\.tweets\\.part0 = ', 'g'), '');
-	
+
 		try {
 			return JSON.parse(removed);
 		} catch (error) {
@@ -141,6 +141,19 @@ export class ImportNotesProcessorService {
 		}
 	}
 
+	@bindThis
+	private parseTwitterFile(str : string) : { tweet: object }[] {
+		const jsonStr = str.replace(/^\s*window\.YTD\.tweets\.part0\s*=\s*/, '');
+
+		try {
+			return JSON.parse(jsonStr);
+		} catch (error) {
+			//The format is not what we expected. Either this file was tampered with or twitters exports changed
+			this.logger.warn('Failed to import twitter notes due to malformed file');
+			throw error;
+		}
+	}
+
 	@bindThis
 	public async process(job: Bull.Job<DbNoteImportJobData>): Promise<void> {
 		this.logger.info(`Starting note import of ${job.data.user.id} ...`);
@@ -173,7 +186,7 @@ export class ImportNotesProcessorService {
 			const destPath = path + '/twitter.zip';
 
 			try {
-				fs.writeFileSync(destPath, '', 'binary');
+				await fsp.writeFile(destPath, '', 'binary');
 				await this.downloadService.downloadUrl(file.url, destPath);
 			} catch (e) { // TODO: 何度か再試行
 				if (e instanceof Error || typeof e === 'string') {
@@ -185,21 +198,13 @@ export class ImportNotesProcessorService {
 			const outputPath = path + '/twitter';
 			try {
 				this.logger.succ(`Unzipping to ${outputPath}`);
-				ZipReader.withDestinationPath(outputPath).viaBuffer(await fs.promises.readFile(destPath));
+				ZipReader.withDestinationPath(outputPath).viaBuffer(await fsp.readFile(destPath));
 
-				const unprocessedTweetJson = this.parseTwitterFile(fs.readFileSync(outputPath + '/data/tweets.js', 'utf-8'));
+				const unprocessedTweets = this.parseTwitterFile(await fsp.readFile(outputPath + '/data/tweets.js', 'utf-8'));
 
-				//Make sure that it isnt null (because if something went wrong in parseTwitterFile it returns null)
-				if (unprocessedTweetJson) {
-					const tweets = Object.keys(unprocessedTweetJson).reduce((m, key, i, obj) => {
-						return m.concat(unprocessedTweetJson[i].tweet);
-					}, []);
-
-					const processedTweets = await this.recreateChain(['id_str'], ['in_reply_to_status_id_str'], tweets, false);
-					this.queueService.createImportTweetsToDbJob(job.data.user, processedTweets, null);
-				} else {
-					this.logger.warn('Failed to import twitter notes due to malformed file');
-				}
+				const tweets = unprocessedTweets.map(e => e.tweet);
+				const processedTweets = await this.recreateChain(['id_str'], ['in_reply_to_status_id_str'], tweets, false);
+				this.queueService.createImportTweetsToDbJob(job.data.user, processedTweets, null);
 			} finally {
 				cleanup();
 			}
@@ -211,7 +216,7 @@ export class ImportNotesProcessorService {
 			const destPath = path + '/facebook.zip';
 
 			try {
-				fs.writeFileSync(destPath, '', 'binary');
+				await fsp.writeFile(destPath, '', 'binary');
 				await this.downloadService.downloadUrl(file.url, destPath);
 			} catch (e) { // TODO: 何度か再試行
 				if (e instanceof Error || typeof e === 'string') {
@@ -223,8 +228,8 @@ export class ImportNotesProcessorService {
 			const outputPath = path + '/facebook';
 			try {
 				this.logger.succ(`Unzipping to ${outputPath}`);
-				ZipReader.withDestinationPath(outputPath).viaBuffer(await fs.promises.readFile(destPath));
-				const postsJson = fs.readFileSync(outputPath + '/your_activity_across_facebook/posts/your_posts__check_ins__photos_and_videos_1.json', 'utf-8');
+				ZipReader.withDestinationPath(outputPath).viaBuffer(await fsp.readFile(destPath));
+				const postsJson = await fsp.readFile(outputPath + '/your_activity_across_facebook/posts/your_posts__check_ins__photos_and_videos_1.json', 'utf-8');
 				const posts = JSON.parse(postsJson);
 				const facebookFolder = await this.driveFoldersRepository.findOneBy({ name: 'Facebook', userId: job.data.user.id, parentId: folder?.id });
 				if (facebookFolder == null && folder) {
@@ -244,7 +249,7 @@ export class ImportNotesProcessorService {
 			const destPath = path + '/unknown.zip';
 
 			try {
-				fs.writeFileSync(destPath, '', 'binary');
+				await fsp.writeFile(destPath, '', 'binary');
 				await this.downloadService.downloadUrl(file.url, destPath);
 			} catch (e) { // TODO: 何度か再試行
 				if (e instanceof Error || typeof e === 'string') {
@@ -256,11 +261,11 @@ export class ImportNotesProcessorService {
 			const outputPath = path + '/unknown';
 			try {
 				this.logger.succ(`Unzipping to ${outputPath}`);
-				ZipReader.withDestinationPath(outputPath).viaBuffer(await fs.promises.readFile(destPath));
+				ZipReader.withDestinationPath(outputPath).viaBuffer(await fsp.readFile(destPath));
 				const isInstagram = type === 'Instagram' || fs.existsSync(outputPath + '/instagram_live') || fs.existsSync(outputPath + '/instagram_ads_and_businesses');
 				const isOutbox = type === 'Mastodon' || fs.existsSync(outputPath + '/outbox.json');
 				if (isInstagram) {
-					const postsJson = fs.readFileSync(outputPath + '/content/posts_1.json', 'utf-8');
+					const postsJson = await fsp.readFile(outputPath + '/content/posts_1.json', 'utf-8');
 					const posts = JSON.parse(postsJson);
 					const igFolder = await this.driveFoldersRepository.findOneBy({ name: 'Instagram', userId: job.data.user.id, parentId: folder?.id });
 					if (igFolder == null && folder) {
@@ -270,16 +275,16 @@ export class ImportNotesProcessorService {
 					}
 					this.queueService.createImportIGToDbJob(job.data.user, posts);
 				} else if (isOutbox) {
-					const actorJson = fs.readFileSync(outputPath + '/actor.json', 'utf-8');
+					const actorJson = await fsp.readFile(outputPath + '/actor.json', 'utf-8');
 					const actor = JSON.parse(actorJson);
 					const isPleroma = actor['@context'].some((v: any) => typeof v === 'string' && v.match(/litepub(.*)/));
 					if (isPleroma) {
-						const outboxJson = fs.readFileSync(outputPath + '/outbox.json', 'utf-8');
+						const outboxJson = await fsp.readFile(outputPath + '/outbox.json', 'utf-8');
 						const outbox = JSON.parse(outboxJson);
 						const processedToots = await this.recreateChain(['object', 'id'], ['object', 'inReplyTo'], outbox.orderedItems.filter((x: any) => x.type === 'Create' && x.object.type === 'Note'), true);
 						this.queueService.createImportPleroToDbJob(job.data.user, processedToots, null);
 					} else {
-						const outboxJson = fs.readFileSync(outputPath + '/outbox.json', 'utf-8');
+						const outboxJson = await fsp.readFile(outputPath + '/outbox.json', 'utf-8');
 						const outbox = JSON.parse(outboxJson);
 						let mastoFolder = await this.driveFoldersRepository.findOneBy({ name: 'Mastodon', userId: job.data.user.id, parentId: folder?.id });
 						if (mastoFolder == null && folder) {
@@ -302,7 +307,7 @@ export class ImportNotesProcessorService {
 			this.logger.info(`Temp dir is ${path}`);
 
 			try {
-				fs.writeFileSync(path, '', 'utf-8');
+				await fsp.writeFile(path, '', 'utf-8');
 				await this.downloadService.downloadUrl(file.url, path);
 			} catch (e) { // TODO: 何度か再試行
 				if (e instanceof Error || typeof e === 'string') {
@@ -311,7 +316,7 @@ export class ImportNotesProcessorService {
 				throw e;
 			}
 
-			const notesJson = fs.readFileSync(path, 'utf-8');
+			const notesJson = await fsp.readFile(path, 'utf-8');
 			const notes = JSON.parse(notesJson);
 			const processedNotes = await this.recreateChain(['id'], ['replyId'], notes, false);
 			this.queueService.createImportKeyNotesToDbJob(job.data.user, processedNotes, null);
@@ -424,6 +429,10 @@ export class ImportNotesProcessorService {
 				const name = file.url.substring(slashdex + 1);
 				const exists = await this.driveFilesRepository.findOneBy({ name: name, userId: user.id });
 				if (exists) {
+					if (file.name) {
+						this.driveService.updateFile(exists, { comment: file.name }, user);
+					}
+
 					files.push(exists);
 				}
 			}
@@ -583,14 +592,15 @@ export class ImportNotesProcessorService {
 		async function replaceTwitterMentions(full_text: string, mentions: any) {
 			let full_textedit = full_text;
 			mentions.forEach((mention: any) => {
-				full_textedit = full_textedit.replaceAll(`@${mention.screen_name}`, `[@${mention.screen_name}](https://nitter.net/${mention.screen_name})`);
+				full_textedit = full_textedit.replaceAll(`@${mention.screen_name}`, `[@${mention.screen_name}](https://twitter.com/${mention.screen_name})`);
 			});
 			return full_textedit;
 		}
 
 		try {
 			const date = new Date(tweet.created_at);
-			const textReplaceURLs = tweet.entities.urls && tweet.entities.urls.length > 0 ? await replaceTwitterUrls(tweet.full_text, tweet.entities.urls) : tweet.full_text;
+			const decodedText = tweet.full_text.replaceAll('&gt;', '>').replaceAll('&lt;', '<').replaceAll('&amp;', '&');
+			const textReplaceURLs = tweet.entities.urls && tweet.entities.urls.length > 0 ? await replaceTwitterUrls(decodedText, tweet.entities.urls) : decodedText;
 			const text = tweet.entities.user_mentions && tweet.entities.user_mentions.length > 0 ? await replaceTwitterMentions(textReplaceURLs, tweet.entities.user_mentions) : textReplaceURLs;
 			const files: MiDriveFile[] = [];
 
diff --git a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts
index 5dd3fbe887..a5992c28c8 100644
--- a/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts
+++ b/packages/backend/src/queue/processors/ImportUserListsProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts
index f69634968d..ad1d9799a7 100644
--- a/packages/backend/src/queue/processors/InboxProcessorService.ts
+++ b/packages/backend/src/queue/processors/InboxProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -24,6 +24,7 @@ import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
 import { LdSignatureService } from '@/core/activitypub/LdSignatureService.js';
 import { ApInboxService } from '@/core/activitypub/ApInboxService.js';
 import { bindThis } from '@/decorators.js';
+import { IdentifiableError } from '@/misc/identifiable-error.js';
 import { QueueLoggerService } from '../QueueLoggerService.js';
 import type { InboxJobData } from '../types.js';
 
@@ -85,7 +86,7 @@ export class InboxProcessorService {
 			} catch (err) {
 				// 対象が4xxならスキップ
 				if (err instanceof StatusError) {
-					if (err.isClientError) {
+					if (!err.isRetryable) {
 						throw new Bull.UnrecoverableError(`skip: Ignored deleted actors on both ends ${activity.actor} - ${err.statusCode}`);
 					}
 					throw new Error(`Error in actor ${activity.actor} - ${err.statusCode}`);
@@ -191,7 +192,17 @@ export class InboxProcessorService {
 		});
 
 		// アクティビティを処理
-		await this.apInboxService.performActivity(authUser.user, activity);
+		try {
+			await this.apInboxService.performActivity(authUser.user, activity);
+		} catch (e) {
+			if (e instanceof IdentifiableError) {
+				if (e.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') {
+					return 'blocked notes with prohibited words';
+				}
+				if (e.id === '85ab9bd7-3a41-4530-959d-f07073900109') return 'actor has been suspended';
+			}
+			throw e;
+		}
 		return 'ok';
 	}
 }
diff --git a/packages/backend/src/queue/processors/RelationshipProcessorService.ts b/packages/backend/src/queue/processors/RelationshipProcessorService.ts
index b2d8e3631f..408b02fb38 100644
--- a/packages/backend/src/queue/processors/RelationshipProcessorService.ts
+++ b/packages/backend/src/queue/processors/RelationshipProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts b/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts
index b3b055ef8c..570cdf9a75 100644
--- a/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts
+++ b/packages/backend/src/queue/processors/ResyncChartsProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/queue/processors/TickChartsProcessorService.ts b/packages/backend/src/queue/processors/TickChartsProcessorService.ts
index 7b1efb71e0..93ec34162d 100644
--- a/packages/backend/src/queue/processors/TickChartsProcessorService.ts
+++ b/packages/backend/src/queue/processors/TickChartsProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts b/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts
index a41f5565c8..8c260c0137 100644
--- a/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts
+++ b/packages/backend/src/queue/processors/WebhookDeliverProcessorService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -71,7 +71,7 @@ export class WebhookDeliverProcessorService {
 
 			if (res instanceof StatusError) {
 				// 4xx
-				if (res.isClientError) {
+				if (!res.isRetryable) {
 					throw new Bull.UnrecoverableError(`${res.statusCode} ${res.statusMessage}`);
 				}
 
diff --git a/packages/backend/src/queue/types.ts b/packages/backend/src/queue/types.ts
index 432b3d364f..91718898b2 100644
--- a/packages/backend/src/queue/types.ts
+++ b/packages/backend/src/queue/types.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -15,7 +15,9 @@ export type DeliverJobData = {
 	/** Actor */
 	user: ThinUser;
 	/** Activity */
-	content: unknown;
+	content: string;
+	/** Digest header */
+	digest: string;
 	/** inbox URL to deliver */
 	to: string;
 	/** whether it is sharedInbox */
diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts
index 8fa8320c8c..64bce07a98 100644
--- a/packages/backend/src/server/ActivityPubServerService.ts
+++ b/packages/backend/src/server/ActivityPubServerService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -162,23 +162,25 @@ export class ActivityPubServerService {
 			return true;
 		}
 
+		const keyId = new URL(signature.keyId);
+		const keyHost = this.utilityService.toPuny(keyId.hostname);
+
+		const logPrefix = `${request.id} ${request.url} (by ${request.headers['user-agent']}) apparently from ${keyHost}:`;
+
 		if (signature.params.headers.indexOf('host') === -1
 			|| request.headers.host !== this.config.host) {
 			// no destination host, or not us: refuse
-			this.authlogger.warn(`${request.id} ${request.url} no destination host, or not us: refuse`);
+			this.authlogger.warn(`${logPrefix} no destination host, or not us: refuse`);
 			reply.code(401);
 			return true;
 		}
 
-		const keyId = new URL(signature.keyId);
-		const keyHost = this.utilityService.toPuny(keyId.hostname);
-
 		const meta = await this.metaService.fetch();
 		if (this.utilityService.isBlockedHost(meta.blockedHosts, keyHost)) {
 			/* blocked instance: refuse (we don't care if the signature is
 				 good, if they even pretend to be from a blocked instance,
 				 they're out) */
-			this.authlogger.warn(`${request.id} ${request.url} instance ${keyHost} is blocked: refuse`);
+			this.authlogger.warn(`${logPrefix} instance is blocked: refuse`);
 			reply.code(401);
 			return true;
 		}
@@ -193,13 +195,13 @@ export class ActivityPubServerService {
 			/* keyId is often in the shape `${user.uri}#${keyname}`, try
 				 fetching information about the remote user */
 			const candidate = formatURL(keyId, { fragment: false });
-			this.authlogger.info(`${request.id} ${request.url} we don't know the user for keyId ${keyId}, trying to fetch via ${candidate}`);
+			this.authlogger.info(`${logPrefix} we don't know the user for keyId ${keyId}, trying to fetch via ${candidate}`);
 			authUser = await this.apDbResolverService.getAuthUserFromApId(candidate);
 		}
 
 		if (authUser?.key == null) {
 			// we can't figure out who the signer is, or we can't get their key: refuse
-			this.authlogger.warn(`${request.id} ${request.url} we can't figure out who the signer is, or we can't get their key: refuse`);
+			this.authlogger.warn(`${logPrefix} we can't figure out who the signer is, or we can't get their key: refuse`);
 			reply.code(401);
 			return true;
 		}
@@ -207,20 +209,20 @@ export class ActivityPubServerService {
 		let httpSignatureValidated = httpSignature.verifySignature(signature, authUser.key.keyPem);
 
 		if (!httpSignatureValidated) {
-			this.authlogger.info(`${request.id} ${request.url} failed to validate signature, re-fetching the key for ${authUser.user.uri}`);
+			this.authlogger.info(`${logPrefix} failed to validate signature, re-fetching the key for ${authUser.user.uri}`);
 			// maybe they changed their key? refetch it
 			authUser.key = await this.apDbResolverService.refetchPublicKeyForApId(authUser.user);
 
 			if (authUser.key != null) {
 				httpSignatureValidated = httpSignature.verifySignature(signature, authUser.key.keyPem);
 			} else {
-				this.authlogger.warn(`${request.id} ${request.url} failed to re-fetch key for ${authUser.user}`);
+				this.authlogger.warn(`${logPrefix} failed to re-fetch key for ${authUser.user}`);
 			}
 		}
 
 		if (!httpSignatureValidated) {
 			// bad signature: refuse
-			this.authlogger.info(`${request.id} ${request.url} failed to validate signature: refuse`);
+			this.authlogger.info(`${logPrefix} failed to validate signature: refuse`);
 			reply.code(401);
 			return true;
 		}
@@ -793,6 +795,8 @@ export class ActivityPubServerService {
 
 		fastify.get<{ Params: { user: string; } }>('/users/:user', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
 			if (await this.shouldRefuseGetRequest(request, reply, request.params.user)) return;
+			
+			vary(reply.raw, 'Accept');
 
 			const userId = request.params.user;
 
@@ -807,6 +811,8 @@ export class ActivityPubServerService {
 
 		fastify.get<{ Params: { user: string; } }>('/@:user', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
 			if (await this.shouldRefuseGetRequest(request, reply, request.params.user)) return;
+			
+			vary(reply.raw, 'Accept');
 
 			const user = await this.usersRepository.findOneBy({
 				usernameLower: request.params.user.toLowerCase(),
diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts
index e82ef64dc4..6d24898acc 100644
--- a/packages/backend/src/server/FileServerService.ts
+++ b/packages/backend/src/server/FileServerService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -9,7 +9,7 @@ import { dirname } from 'node:path';
 import { Inject, Injectable } from '@nestjs/common';
 import rename from 'rename';
 import sharp from 'sharp';
-import { sharpBmp } from 'sharp-read-bmp';
+import { sharpBmp } from '@misskey-dev/sharp-read-bmp';
 import type { Config } from '@/config.js';
 import type { MiDriveFile, DriveFilesRepository } from '@/models/_.js';
 import { DI } from '@/di-symbols.js';
@@ -27,6 +27,7 @@ import { LoggerService } from '@/core/LoggerService.js';
 import { bindThis } from '@/decorators.js';
 import { isMimeImage } from '@/misc/is-mime-image.js';
 import { correctFilename } from '@/misc/correct-filename.js';
+import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js';
 import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions } from 'fastify';
 
 const _filename = fileURLToPath(import.meta.url);
@@ -65,20 +66,23 @@ export class FileServerService {
 			done();
 		});
 
-		fastify.get('/files/app-default.jpg', (request, reply) => {
-			const file = fs.createReadStream(`${_dirname}/assets/dummy.png`);
-			reply.header('Content-Type', 'image/jpeg');
-			reply.header('Cache-Control', 'max-age=31536000, immutable');
-			return reply.send(file);
-		});
+		fastify.register((fastify, options, done) => {
+			fastify.addHook('onRequest', handleRequestRedirectToOmitSearch);
+			fastify.get('/files/app-default.jpg', (request, reply) => {
+				const file = fs.createReadStream(`${_dirname}/assets/dummy.png`);
+				reply.header('Content-Type', 'image/jpeg');
+				reply.header('Cache-Control', 'max-age=31536000, immutable');
+				return reply.send(file);
+			});
 
-		fastify.get<{ Params: { key: string; } }>('/files/:key', async (request, reply) => {
-			return await this.sendDriveFile(request, reply)
-				.catch(err => this.errorHandler(request, reply, err));
-		});
-		fastify.get<{ Params: { key: string; } }>('/files/:key/*', async (request, reply) => {
-			return await this.sendDriveFile(request, reply)
-				.catch(err => this.errorHandler(request, reply, err));
+			fastify.get<{ Params: { key: string; } }>('/files/:key', async (request, reply) => {
+				return await this.sendDriveFile(request, reply)
+					.catch(err => this.errorHandler(request, reply, err));
+			});
+			fastify.get<{ Params: { key: string; } }>('/files/:key/*', async (request, reply) => {
+				return await reply.redirect(301, `${this.config.url}/files/${request.params.key}`);
+			});
+			done();
 		});
 
 		fastify.get<{
@@ -166,11 +170,35 @@ export class FileServerService {
 				}
 
 				if (!image) {
-					image = {
-						data: fs.createReadStream(file.path),
-						ext: file.ext,
-						type: file.mime,
-					};
+					if (request.headers.range && file.file.size > 0) {
+						const range = request.headers.range as string;
+						const parts = range.replace(/bytes=/, '').split('-');
+						const start = parseInt(parts[0], 10);
+						let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1;
+						if (end > file.file.size) {
+							end = file.file.size - 1;
+						}
+						const chunksize = end - start + 1;
+
+						image = {
+							data: fs.createReadStream(file.path, {
+								start,
+								end,
+							}),
+							ext: file.ext,
+							type: file.mime,
+						};
+
+						reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`);
+						reply.header('Accept-Ranges', 'bytes');
+						reply.header('Content-Length', chunksize);
+					} else {
+						image = {
+							data: fs.createReadStream(file.path),
+							ext: file.ext,
+							type: file.mime,
+						};
+					}
 				}
 
 				if ('pipe' in image.data && typeof image.data.pipe === 'function') {
@@ -201,11 +229,54 @@ export class FileServerService {
 				reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.mime) ? file.mime : 'application/octet-stream');
 				reply.header('Cache-Control', 'max-age=31536000, immutable');
 				reply.header('Content-Disposition', contentDisposition('inline', filename));
+
+				if (request.headers.range && file.file.size > 0) {
+					const range = request.headers.range as string;
+					const parts = range.replace(/bytes=/, '').split('-');
+					const start = parseInt(parts[0], 10);
+					let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1;
+					if (end > file.file.size) {
+						end = file.file.size - 1;
+					}
+					const chunksize = end - start + 1;
+					const fileStream = fs.createReadStream(file.path, {
+						start,
+						end,
+					});
+					reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`);
+					reply.header('Accept-Ranges', 'bytes');
+					reply.header('Content-Length', chunksize);
+					reply.code(206);
+					return fileStream;
+				}
+
 				return fs.createReadStream(file.path);
 			} else {
 				reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.file.type) ? file.file.type : 'application/octet-stream');
 				reply.header('Cache-Control', 'max-age=31536000, immutable');
 				reply.header('Content-Disposition', contentDisposition('inline', file.filename));
+
+				if (request.headers.range && file.file.size > 0) {
+					const range = request.headers.range as string;
+					const parts = range.replace(/bytes=/, '').split('-');
+					const start = parseInt(parts[0], 10);
+					let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1;
+					console.log(end);
+					if (end > file.file.size) {
+						end = file.file.size - 1;
+					}
+					const chunksize = end - start + 1;
+					const fileStream = fs.createReadStream(file.path, {
+						start,
+						end,
+					});
+					reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`);
+					reply.header('Accept-Ranges', 'bytes');
+					reply.header('Content-Length', chunksize);
+					reply.code(206);
+					return fileStream;
+				}
+
 				return fs.createReadStream(file.path);
 			}
 		} catch (e) {
@@ -338,11 +409,35 @@ export class FileServerService {
 			}
 
 			if (!image) {
-				image = {
-					data: fs.createReadStream(file.path),
-					ext: file.ext,
-					type: file.mime,
-				};
+				if (request.headers.range && file.file && file.file.size > 0) {
+					const range = request.headers.range as string;
+					const parts = range.replace(/bytes=/, '').split('-');
+					const start = parseInt(parts[0], 10);
+					let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1;
+					if (end > file.file.size) {
+						end = file.file.size - 1;
+					}
+					const chunksize = end - start + 1;
+
+					image = {
+						data: fs.createReadStream(file.path, {
+							start,
+							end,
+						}),
+						ext: file.ext,
+						type: file.mime,
+					};
+
+					reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`);
+					reply.header('Accept-Ranges', 'bytes');
+					reply.header('Content-Length', chunksize);
+				} else {
+					image = {
+						data: fs.createReadStream(file.path),
+						ext: file.ext,
+						type: file.mime,
+					};
+				}
 			}
 
 			if ('cleanup' in file) {
diff --git a/packages/backend/src/server/NodeinfoServerService.ts b/packages/backend/src/server/NodeinfoServerService.ts
index 31479269b9..d4f4c8b752 100644
--- a/packages/backend/src/server/NodeinfoServerService.ts
+++ b/packages/backend/src/server/NodeinfoServerService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -108,6 +108,7 @@ export class NodeinfoServerService {
 					tosUrl: meta.termsOfServiceUrl,
 					privacyPolicyUrl: meta.privacyPolicyUrl,
 					impressumUrl: meta.impressumUrl,
+					donationUrl: meta.donationUrl,
 					repositoryUrl: meta.repositoryUrl,
 					feedbackUrl: meta.feedbackUrl,
 					disableRegistration: meta.disableRegistration,
@@ -117,6 +118,8 @@ export class NodeinfoServerService {
 					emailRequiredForSignup: meta.emailRequiredForSignup,
 					enableHcaptcha: meta.enableHcaptcha,
 					enableRecaptcha: meta.enableRecaptcha,
+					enableMcaptcha: meta.enableMcaptcha,
+					enableTurnstile: meta.enableTurnstile,
 					maxNoteTextLength: this.config.maxNoteLength,
 					enableEmail: meta.enableEmail,
 					enableServiceWorker: meta.enableServiceWorker,
diff --git a/packages/backend/src/server/ServerModule.ts b/packages/backend/src/server/ServerModule.ts
index 52070b5157..e0c4768ffc 100644
--- a/packages/backend/src/server/ServerModule.ts
+++ b/packages/backend/src/server/ServerModule.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -22,10 +22,14 @@ import { SigninApiService } from './api/SigninApiService.js';
 import { SigninService } from './api/SigninService.js';
 import { SignupApiService } from './api/SignupApiService.js';
 import { StreamingApiServerService } from './api/StreamingApiServerService.js';
+import { OpenApiServerService } from './api/openapi/OpenApiServerService.js';
 import { ClientServerService } from './web/ClientServerService.js';
 import { MastoConverters } from './api/mastodon/converters.js';
 import { FeedService } from './web/FeedService.js';
 import { UrlPreviewService } from './web/UrlPreviewService.js';
+import { ClientLoggerService } from './web/ClientLoggerService.js';
+import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js';
+
 import { MainChannelService } from './api/stream/channels/main.js';
 import { AdminChannelService } from './api/stream/channels/admin.js';
 import { AntennaChannelService } from './api/stream/channels/antenna.js';
@@ -40,11 +44,10 @@ import { LocalTimelineChannelService } from './api/stream/channels/local-timelin
 import { QueueStatsChannelService } from './api/stream/channels/queue-stats.js';
 import { ServerStatsChannelService } from './api/stream/channels/server-stats.js';
 import { UserListChannelService } from './api/stream/channels/user-list.js';
-import { OpenApiServerService } from './api/openapi/OpenApiServerService.js';
 import { MastodonApiServerService } from './api/mastodon/MastodonApiServerService.js';
-import { ClientLoggerService } from './web/ClientLoggerService.js';
 import { RoleTimelineChannelService } from './api/stream/channels/role-timeline.js';
-import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js';
+import { ReversiChannelService } from './api/stream/channels/reversi.js';
+import { ReversiGameChannelService } from './api/stream/channels/reversi-game.js';
 
 @Module({
 	imports: [
@@ -81,6 +84,8 @@ import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js';
 		BubbleTimelineChannelService,
 		HashtagChannelService,
 		RoleTimelineChannelService,
+		ReversiChannelService,
+		ReversiGameChannelService,
 		HomeTimelineChannelService,
 		HybridTimelineChannelService,
 		LocalTimelineChannelService,
diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts
index 3b43b931ae..5a456e09ad 100644
--- a/packages/backend/src/server/ServerService.ts
+++ b/packages/backend/src/server/ServerService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -206,7 +206,7 @@ export class ServerService implements OnApplicationShutdown {
 				});
 
 				this.globalEventService.publishMainStream(profile.userId, 'meUpdated', await this.userEntityService.pack(profile.userId, { id: profile.userId }, {
-					detail: true,
+					schema: 'MeDetailed',
 					includeSecrets: true,
 				}));
 
diff --git a/packages/backend/src/server/WellKnownServerService.ts b/packages/backend/src/server/WellKnownServerService.ts
index c3eaf53a14..8e326da89a 100644
--- a/packages/backend/src/server/WellKnownServerService.ts
+++ b/packages/backend/src/server/WellKnownServerService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts
index 56f804dee8..9836689872 100644
--- a/packages/backend/src/server/api/ApiCallService.ts
+++ b/packages/backend/src/server/api/ApiCallService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/ApiLoggerService.ts b/packages/backend/src/server/api/ApiLoggerService.ts
index 2339366a5d..72b71c0b5c 100644
--- a/packages/backend/src/server/api/ApiLoggerService.ts
+++ b/packages/backend/src/server/api/ApiLoggerService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts
index 1758c03aca..e99244cdd0 100644
--- a/packages/backend/src/server/api/ApiServerService.ts
+++ b/packages/backend/src/server/api/ApiServerService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -157,7 +157,7 @@ export class ApiServerService {
 				return {
 					ok: true,
 					token: token.token,
-					user: await this.userEntityService.pack(token.userId, null, { detail: true }),
+					user: await this.userEntityService.pack(token.userId, null, { schema: 'UserDetailedNotMe' }),
 				};
 			} else {
 				return {
diff --git a/packages/backend/src/server/api/AuthenticateService.ts b/packages/backend/src/server/api/AuthenticateService.ts
index f075688194..ddef8db987 100644
--- a/packages/backend/src/server/api/AuthenticateService.ts
+++ b/packages/backend/src/server/api/AuthenticateService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts
index ed1b2d4377..f2945f477c 100644
--- a/packages/backend/src/server/api/EndpointsModule.ts
+++ b/packages/backend/src/server/api/EndpointsModule.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -214,6 +214,7 @@ import * as ep___i_exportBlocking from './endpoints/i/export-blocking.js';
 import * as ep___i_exportFollowing from './endpoints/i/export-following.js';
 import * as ep___i_exportMute from './endpoints/i/export-mute.js';
 import * as ep___i_exportNotes from './endpoints/i/export-notes.js';
+import * as ep___i_exportClips from './endpoints/i/export-clips.js';
 import * as ep___i_exportFavorites from './endpoints/i/export-favorites.js';
 import * as ep___i_exportUserLists from './endpoints/i/export-user-lists.js';
 import * as ep___i_exportAntennas from './endpoints/i/export-antennas.js';
@@ -304,6 +305,7 @@ import * as ep___notes_translate from './endpoints/notes/translate.js';
 import * as ep___notes_unrenote from './endpoints/notes/unrenote.js';
 import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js';
 import * as ep___notifications_create from './endpoints/notifications/create.js';
+import * as ep___notifications_flush from './endpoints/notifications/flush.js';
 import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js';
 import * as ep___notifications_testNotification from './endpoints/notifications/test-notification.js';
 import * as ep___pagePush from './endpoints/page-push.js';
@@ -376,6 +378,15 @@ import * as ep___fetchRss from './endpoints/fetch-rss.js';
 import * as ep___fetchExternalResources from './endpoints/fetch-external-resources.js';
 import * as ep___retention from './endpoints/retention.js';
 import * as ep___sponsors from './endpoints/sponsors.js';
+import * as ep___bubbleGame_register from './endpoints/bubble-game/register.js';
+import * as ep___bubbleGame_ranking from './endpoints/bubble-game/ranking.js';
+import * as ep___reversi_cancelMatch from './endpoints/reversi/cancel-match.js';
+import * as ep___reversi_games from './endpoints/reversi/games.js';
+import * as ep___reversi_match from './endpoints/reversi/match.js';
+import * as ep___reversi_invitations from './endpoints/reversi/invitations.js';
+import * as ep___reversi_showGame from './endpoints/reversi/show-game.js';
+import * as ep___reversi_surrender from './endpoints/reversi/surrender.js';
+import * as ep___reversi_verify from './endpoints/reversi/verify.js';
 import { GetterService } from './GetterService.js';
 import { ApiLoggerService } from './ApiLoggerService.js';
 import type { Provider } from '@nestjs/common';
@@ -588,6 +599,7 @@ const $i_exportBlocking: Provider = { provide: 'ep:i/export-blocking', useClass:
 const $i_exportFollowing: Provider = { provide: 'ep:i/export-following', useClass: ep___i_exportFollowing.default };
 const $i_exportMute: Provider = { provide: 'ep:i/export-mute', useClass: ep___i_exportMute.default };
 const $i_exportNotes: Provider = { provide: 'ep:i/export-notes', useClass: ep___i_exportNotes.default };
+const $i_exportClips: Provider = { provide: 'ep:i/export-clips', useClass: ep___i_exportClips.default };
 const $i_exportFavorites: Provider = { provide: 'ep:i/export-favorites', useClass: ep___i_exportFavorites.default };
 const $i_exportUserLists: Provider = { provide: 'ep:i/export-user-lists', useClass: ep___i_exportUserLists.default };
 const $i_exportAntennas: Provider = { provide: 'ep:i/export-antennas', useClass: ep___i_exportAntennas.default };
@@ -678,6 +690,7 @@ const $notes_userListTimeline: Provider = { provide: 'ep:notes/user-list-timelin
 const $notes_edit: Provider = { provide: 'ep:notes/edit', useClass: ep___notes_edit.default };
 const $notes_versions: Provider = { provide: 'ep:notes/versions', useClass: ep___notes_versions.default };
 const $notifications_create: Provider = { provide: 'ep:notifications/create', useClass: ep___notifications_create.default };
+const $notifications_flush: Provider = { provide: 'ep:notifications/flush', useClass: ep___notifications_flush.default };
 const $notifications_markAllAsRead: Provider = { provide: 'ep:notifications/mark-all-as-read', useClass: ep___notifications_markAllAsRead.default };
 const $notifications_testNotification: Provider = { provide: 'ep:notifications/test-notification', useClass: ep___notifications_testNotification.default };
 const $pagePush: Provider = { provide: 'ep:page-push', useClass: ep___pagePush.default };
@@ -750,6 +763,15 @@ const $fetchRss: Provider = { provide: 'ep:fetch-rss', useClass: ep___fetchRss.d
 const $fetchExternalResources: Provider = { provide: 'ep:fetch-external-resources', useClass: ep___fetchExternalResources.default };
 const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention.default };
 const $sponsors: Provider = { provide: 'ep:sponsors', useClass: ep___sponsors.default };
+const $bubbleGame_register: Provider = { provide: 'ep:bubble-game/register', useClass: ep___bubbleGame_register.default };
+const $bubbleGame_ranking: Provider = { provide: 'ep:bubble-game/ranking', useClass: ep___bubbleGame_ranking.default };
+const $reversi_cancelMatch: Provider = { provide: 'ep:reversi/cancel-match', useClass: ep___reversi_cancelMatch.default };
+const $reversi_games: Provider = { provide: 'ep:reversi/games', useClass: ep___reversi_games.default };
+const $reversi_match: Provider = { provide: 'ep:reversi/match', useClass: ep___reversi_match.default };
+const $reversi_invitations: Provider = { provide: 'ep:reversi/invitations', useClass: ep___reversi_invitations.default };
+const $reversi_showGame: Provider = { provide: 'ep:reversi/show-game', useClass: ep___reversi_showGame.default };
+const $reversi_surrender: Provider = { provide: 'ep:reversi/surrender', useClass: ep___reversi_surrender.default };
+const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep___reversi_verify.default };
 
 @Module({
 	imports: [
@@ -966,6 +988,7 @@ const $sponsors: Provider = { provide: 'ep:sponsors', useClass: ep___sponsors.de
 		$i_exportFollowing,
 		$i_exportMute,
 		$i_exportNotes,
+		$i_exportClips,
 		$i_exportFavorites,
 		$i_exportUserLists,
 		$i_exportAntennas,
@@ -1056,6 +1079,7 @@ const $sponsors: Provider = { provide: 'ep:sponsors', useClass: ep___sponsors.de
 		$notes_edit,
 		$notes_versions,
 		$notifications_create,
+		$notifications_flush,
 		$notifications_markAllAsRead,
 		$notifications_testNotification,
 		$pagePush,
@@ -1128,6 +1152,15 @@ const $sponsors: Provider = { provide: 'ep:sponsors', useClass: ep___sponsors.de
 		$fetchExternalResources,
 		$retention,
 		$sponsors,
+		$bubbleGame_register,
+		$bubbleGame_ranking,
+		$reversi_cancelMatch,
+		$reversi_games,
+		$reversi_match,
+		$reversi_invitations,
+		$reversi_showGame,
+		$reversi_surrender,
+		$reversi_verify,
 	],
 	exports: [
 		$admin_meta,
@@ -1338,6 +1371,7 @@ const $sponsors: Provider = { provide: 'ep:sponsors', useClass: ep___sponsors.de
 		$i_exportFollowing,
 		$i_exportMute,
 		$i_exportNotes,
+		$i_exportClips,
 		$i_exportFavorites,
 		$i_exportUserLists,
 		$i_exportAntennas,
@@ -1428,7 +1462,9 @@ const $sponsors: Provider = { provide: 'ep:sponsors', useClass: ep___sponsors.de
 		$notes_edit,
 		$notes_versions,
 		$notifications_create,
+		$notifications_flush,
 		$notifications_markAllAsRead,
+		$notifications_testNotification,
 		$pagePush,
 		$pages_create,
 		$pages_delete,
@@ -1497,6 +1533,15 @@ const $sponsors: Provider = { provide: 'ep:sponsors', useClass: ep___sponsors.de
 		$fetchExternalResources,
 		$retention,
 		$sponsors,
+		$bubbleGame_register,
+		$bubbleGame_ranking,
+		$reversi_cancelMatch,
+		$reversi_games,
+		$reversi_match,
+		$reversi_invitations,
+		$reversi_showGame,
+		$reversi_surrender,
+		$reversi_verify,
 	],
 })
 export class EndpointsModule {}
diff --git a/packages/backend/src/server/api/GetterService.ts b/packages/backend/src/server/api/GetterService.ts
index 2616cbb761..8643be0f30 100644
--- a/packages/backend/src/server/api/GetterService.ts
+++ b/packages/backend/src/server/api/GetterService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/RateLimiterService.ts b/packages/backend/src/server/api/RateLimiterService.ts
index 0e644aa091..0439cdfe5e 100644
--- a/packages/backend/src/server/api/RateLimiterService.ts
+++ b/packages/backend/src/server/api/RateLimiterService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts
index fd247df22a..6fbcacbc11 100644
--- a/packages/backend/src/server/api/SigninApiService.ts
+++ b/packages/backend/src/server/api/SigninApiService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -24,7 +24,7 @@ import { UserAuthService } from '@/core/UserAuthService.js';
 import { MetaService } from '@/core/MetaService.js';
 import { RateLimiterService } from './RateLimiterService.js';
 import { SigninService } from './SigninService.js';
-import type { AuthenticationResponseJSON } from '@simplewebauthn/typescript-types';
+import type { AuthenticationResponseJSON } from '@simplewebauthn/types';
 import type { FastifyReply, FastifyRequest } from 'fastify';
 
 @Injectable()
diff --git a/packages/backend/src/server/api/SigninService.ts b/packages/backend/src/server/api/SigninService.ts
index 98e9027006..714e56e8c3 100644
--- a/packages/backend/src/server/api/SigninService.ts
+++ b/packages/backend/src/server/api/SigninService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts
index 63379c8878..9c221314ac 100644
--- a/packages/backend/src/server/api/SignupApiService.ts
+++ b/packages/backend/src/server/api/SignupApiService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -70,6 +70,7 @@ export class SignupApiService {
 				'hcaptcha-response'?: string;
 				'g-recaptcha-response'?: string;
 				'turnstile-response'?: string;
+				'm-captcha-response'?: string;
 			}
 		}>,
 		reply: FastifyReply,
@@ -87,6 +88,12 @@ export class SignupApiService {
 				});
 			}
 
+			if (instance.enableMcaptcha && instance.mcaptchaSecretKey && instance.mcaptchaSitekey && instance.mcaptchaInstanceUrl) {
+				await this.captchaService.verifyMcaptcha(instance.mcaptchaSecretKey, instance.mcaptchaSitekey, instance.mcaptchaInstanceUrl, body['m-captcha-response']).catch(err => {
+					throw new FastifyReplyError(400, err);
+				});
+			}
+
 			if (instance.enableRecaptcha && instance.recaptchaSecretKey) {
 				await this.captchaService.verifyRecaptcha(instance.recaptchaSecretKey, body['g-recaptcha-response']).catch(err => {
 					throw new FastifyReplyError(400, err);
@@ -169,12 +176,12 @@ export class SignupApiService {
 		}
 
 		if (instance.emailRequiredForSignup) {
-			if (await this.usersRepository.exist({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) {
+			if (await this.usersRepository.exists({ where: { usernameLower: username.toLowerCase(), host: IsNull() } })) {
 				throw new FastifyReplyError(400, 'DUPLICATED_USERNAME');
 			}
 
 			// Check deleted username duplication
-			if (await this.usedUsernamesRepository.exist({ where: { username: username.toLowerCase() } })) {
+			if (await this.usedUsernamesRepository.exists({ where: { username: username.toLowerCase() } })) {
 				throw new FastifyReplyError(400, 'USED_USERNAME');
 			}
 
@@ -253,7 +260,7 @@ export class SignupApiService {
 				});
 
 				const res = await this.userEntityService.pack(account, account, {
-					detail: true,
+					schema: 'MeDetailed',
 					includeSecrets: true,
 				});
 
diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts
index 3b387d92ca..b8f448477b 100644
--- a/packages/backend/src/server/api/StreamingApiServerService.ts
+++ b/packages/backend/src/server/api/StreamingApiServerService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoint-base.ts b/packages/backend/src/server/api/endpoint-base.ts
index d5279faa1c..e061aa3a8e 100644
--- a/packages/backend/src/server/api/endpoint-base.ts
+++ b/packages/backend/src/server/api/endpoint-base.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index f82bf257fc..f83d2cacff 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -1,11 +1,10 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import type { Schema } from '@/misc/json-schema.js';
 import { permissions } from 'misskey-js';
-import { RolePolicies } from '@/core/RoleService.js';
+import type { KeyOf, Schema } from '@/misc/json-schema.js';
 
 import * as ep___admin_meta from './endpoints/admin/meta.js';
 import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js';
@@ -215,6 +214,7 @@ import * as ep___i_exportBlocking from './endpoints/i/export-blocking.js';
 import * as ep___i_exportFollowing from './endpoints/i/export-following.js';
 import * as ep___i_exportMute from './endpoints/i/export-mute.js';
 import * as ep___i_exportNotes from './endpoints/i/export-notes.js';
+import * as ep___i_exportClips from './endpoints/i/export-clips.js';
 import * as ep___i_exportFavorites from './endpoints/i/export-favorites.js';
 import * as ep___i_exportUserLists from './endpoints/i/export-user-lists.js';
 import * as ep___i_exportAntennas from './endpoints/i/export-antennas.js';
@@ -305,6 +305,7 @@ import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeli
 import * as ep___notes_edit from './endpoints/notes/edit.js';
 import * as ep___notes_versions from './endpoints/notes/versions.js';
 import * as ep___notifications_create from './endpoints/notifications/create.js';
+import * as ep___notifications_flush from './endpoints/notifications/flush.js';
 import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js';
 import * as ep___notifications_testNotification from './endpoints/notifications/test-notification.js';
 import * as ep___pagePush from './endpoints/page-push.js';
@@ -377,6 +378,15 @@ import * as ep___fetchRss from './endpoints/fetch-rss.js';
 import * as ep___fetchExternalResources from './endpoints/fetch-external-resources.js';
 import * as ep___retention from './endpoints/retention.js';
 import * as ep___sponsors from './endpoints/sponsors.js';
+import * as ep___bubbleGame_register from './endpoints/bubble-game/register.js';
+import * as ep___bubbleGame_ranking from './endpoints/bubble-game/ranking.js';
+import * as ep___reversi_cancelMatch from './endpoints/reversi/cancel-match.js';
+import * as ep___reversi_games from './endpoints/reversi/games.js';
+import * as ep___reversi_match from './endpoints/reversi/match.js';
+import * as ep___reversi_invitations from './endpoints/reversi/invitations.js';
+import * as ep___reversi_showGame from './endpoints/reversi/show-game.js';
+import * as ep___reversi_surrender from './endpoints/reversi/surrender.js';
+import * as ep___reversi_verify from './endpoints/reversi/verify.js';
 
 const eps = [
 	['admin/meta', ep___admin_meta],
@@ -587,6 +597,7 @@ const eps = [
 	['i/export-following', ep___i_exportFollowing],
 	['i/export-mute', ep___i_exportMute],
 	['i/export-notes', ep___i_exportNotes],
+	['i/export-clips', ep___i_exportClips],
 	['i/export-favorites', ep___i_exportFavorites],
 	['i/export-user-lists', ep___i_exportUserLists],
 	['i/export-antennas', ep___i_exportAntennas],
@@ -677,6 +688,7 @@ const eps = [
 	['notes/edit', ep___notes_edit],
 	['notes/versions', ep___notes_versions],
 	['notifications/create', ep___notifications_create],
+	['notifications/flush', ep___notifications_flush],
 	['notifications/mark-all-as-read', ep___notifications_markAllAsRead],
 	['notifications/test-notification', ep___notifications_testNotification],
 	['page-push', ep___pagePush],
@@ -749,6 +761,15 @@ const eps = [
 	['fetch-external-resources', ep___fetchExternalResources],
 	['retention', ep___retention],
 	['sponsors', ep___sponsors],
+	['bubble-game/register', ep___bubbleGame_register],
+	['bubble-game/ranking', ep___bubbleGame_ranking],
+	['reversi/cancel-match', ep___reversi_cancelMatch],
+	['reversi/games', ep___reversi_games],
+	['reversi/match', ep___reversi_match],
+	['reversi/invitations', ep___reversi_invitations],
+	['reversi/show-game', ep___reversi_showGame],
+	['reversi/surrender', ep___reversi_surrender],
+	['reversi/verify', ep___reversi_verify],
 ];
 
 interface IEndpointMetaBase {
@@ -782,7 +803,7 @@ interface IEndpointMetaBase {
 	 */
 	readonly requireAdmin?: boolean;
 
-	readonly requireRolePolicy?: keyof RolePolicies;
+	readonly requireRolePolicy?: KeyOf<'RolePolicies'>;
 
 	/**
 	 * 引っ越し済みのユーザーによるリクエストを禁止するか
diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts
index 3484d6707a..cf3f257ca6 100644
--- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts
+++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -62,17 +62,17 @@ export const meta = {
 				reporter: {
 					type: 'object',
 					nullable: false, optional: false,
-					ref: 'User',
+					ref: 'UserDetailedNotMe',
 				},
 				targetUser: {
 					type: 'object',
 					nullable: false, optional: false,
-					ref: 'User',
+					ref: 'UserDetailedNotMe',
 				},
 				assignee: {
 					type: 'object',
 					nullable: true, optional: true,
-					ref: 'User',
+					ref: 'UserDetailedNotMe',
 				},
 			},
 		},
diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
index f54d567fff..a7e8a3b018 100644
--- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -9,8 +9,10 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
 import type { UsersRepository } from '@/models/_.js';
 import { SignupService } from '@/core/SignupService.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { InstanceActorService } from '@/core/InstanceActorService.js';
 import { localUsernameSchema, passwordSchema } from '@/models/User.js';
 import { DI } from '@/di-symbols.js';
+import { Packed } from '@/misc/json-schema.js';
 
 export const meta = {
 	tags: ['admin'],
@@ -18,7 +20,7 @@ export const meta = {
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
-		ref: 'User',
+		ref: 'MeDetailed',
 		properties: {
 			token: {
 				type: 'string',
@@ -45,13 +47,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 		private userEntityService: UserEntityService,
 		private signupService: SignupService,
+		private instanceActorService: InstanceActorService,
 	) {
 		super(meta, paramDef, async (ps, _me, token) => {
 			const me = _me ? await this.usersRepository.findOneByOrFail({ id: _me.id }) : null;
-			const noUsers = (await this.usersRepository.countBy({
-				host: IsNull(),
-			})) === 0;
-			if ((!noUsers && !me?.isRoot) || token !== null) throw new Error('access denied');
+			const realUsers = await this.instanceActorService.realLocalUsersPresent();
+			if ((realUsers && !me?.isRoot) || token !== null) throw new Error('access denied');
 
 			const { account, secret } = await this.signupService.signup({
 				username: ps.username,
@@ -60,11 +61,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			});
 
 			const res = await this.userEntityService.pack(account, account, {
-				detail: true,
+				schema: 'MeDetailed',
 				includeSecrets: true,
-			});
+			}) as Packed<'MeDetailed'> & { token: string };
 
-			(res as any).token = secret;
+			res.token = secret;
 
 			return res;
 		});
diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts
index 52d8c8ce18..4074e416b8 100644
--- a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts
+++ b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/find-by-email.ts b/packages/backend/src/server/api/endpoints/admin/accounts/find-by-email.ts
index 93673453d6..12cd5cf295 100644
--- a/packages/backend/src/server/api/endpoints/admin/accounts/find-by-email.ts
+++ b/packages/backend/src/server/api/endpoints/admin/accounts/find-by-email.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -27,7 +27,7 @@ export const meta = {
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
-		ref: 'User',
+		ref: 'UserDetailedNotMe',
 	},
 } as const;
 
@@ -58,7 +58,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			}
 
 			const res = await this.userEntityService.pack(profile.user!, null, {
-				detail: true,
+				schema: 'UserDetailedNotMe',
 			});
 
 			return res;
diff --git a/packages/backend/src/server/api/endpoints/admin/ad/create.ts b/packages/backend/src/server/api/endpoints/admin/ad/create.ts
index 041b10f9f7..1e7a9fb3ec 100644
--- a/packages/backend/src/server/api/endpoints/admin/ad/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/ad/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/ad/delete.ts b/packages/backend/src/server/api/endpoints/admin/ad/delete.ts
index 5b18b347d3..501e13c6a7 100644
--- a/packages/backend/src/server/api/endpoints/admin/ad/delete.ts
+++ b/packages/backend/src/server/api/endpoints/admin/ad/delete.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/ad/list.ts b/packages/backend/src/server/api/endpoints/admin/ad/list.ts
index 586c1f44db..6406709cda 100644
--- a/packages/backend/src/server/api/endpoints/admin/ad/list.ts
+++ b/packages/backend/src/server/api/endpoints/admin/ad/list.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/ad/update.ts b/packages/backend/src/server/api/endpoints/admin/ad/update.ts
index bf96e44b0c..62358457ff 100644
--- a/packages/backend/src/server/api/endpoints/admin/ad/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/ad/update.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts
index c9df70c76b..2dae1df87d 100644
--- a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts
index 939333345e..6d1e1b0a10 100644
--- a/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts
+++ b/packages/backend/src/server/api/endpoints/admin/announcements/delete.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts
index 429b138599..87eaad31a3 100644
--- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts
+++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts
index db6db8356d..6fce6e4e0a 100644
--- a/packages/backend/src/server/api/endpoints/admin/announcements/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/announcements/update.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/create.ts b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/create.ts
index 4ac74253cc..fd21309818 100644
--- a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/delete.ts b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/delete.ts
index 88977f801a..3a5673d99d 100644
--- a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/delete.ts
+++ b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/delete.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/list.ts b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/list.ts
index 33122c3eef..aee90023e1 100644
--- a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/list.ts
+++ b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/list.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/update.ts b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/update.ts
index 6211345f96..34b3b5a11f 100644
--- a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/update.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/delete-account.ts b/packages/backend/src/server/api/endpoints/admin/delete-account.ts
index 2c82c2879d..b6f0f22d60 100644
--- a/packages/backend/src/server/api/endpoints/admin/delete-account.ts
+++ b/packages/backend/src/server/api/endpoints/admin/delete-account.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -15,9 +15,6 @@ export const meta = {
 	requireCredential: true,
 	requireAdmin: true,
 	kind: 'write:admin:delete-account',
-
-	res: {
-	},
 } as const;
 
 export const paramDef = {
diff --git a/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts b/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts
index 7d33065f2e..d8341b3ad7 100644
--- a/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/delete-all-files-of-a-user.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts b/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts
index af2bb6b1ca..d420a929bd 100644
--- a/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts
+++ b/packages/backend/src/server/api/endpoints/admin/drive/clean-remote-files.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts
index a3b221284b..d612572e2e 100644
--- a/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts
+++ b/packages/backend/src/server/api/endpoints/admin/drive/cleanup.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/drive/files.ts b/packages/backend/src/server/api/endpoints/admin/drive/files.ts
index 37fa439bcf..915d777e77 100644
--- a/packages/backend/src/server/api/endpoints/admin/drive/files.ts
+++ b/packages/backend/src/server/api/endpoints/admin/drive/files.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts
index 3aeb3e45e3..459d8880fa 100644
--- a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts
+++ b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -84,6 +84,24 @@ export const meta = {
 			properties: {
 				type: 'object',
 				optional: false, nullable: false,
+				properties: {
+					width: {
+						type: 'number',
+						optional: true, nullable: false,
+					},
+					height: {
+						type: 'number',
+						optional: true, nullable: false,
+					},
+					orientation: {
+						type: 'number',
+						optional: true, nullable: false,
+					},
+					avgColor: {
+						type: 'string',
+						optional: true, nullable: false,
+					},
+				},
 			},
 			storedInternal: {
 				type: 'boolean',
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts
index 1cd8125c52..a30a080e59 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
index 0868e24948..767e517b80 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -31,7 +31,10 @@ export const meta = {
 		},
 	},
 
-	ref: 'EmojiDetailed',
+	res: {
+		type: 'object',
+		ref: 'EmojiDetailed',
+	},
 } as const;
 
 export const paramDef = {
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
index 611b64be07..29af7598ed 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts
index 450695984a..cec9f700c3 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts
index e1e6e7c2c4..50c45b6ac5 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts
index 208616c0ac..8e5f69c894 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/import-zip.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts
index f3e0c1ef1f..e423f440d0 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -98,11 +98,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			}
 
 			if (ps.query) {
-				q.andWhere('emoji.name like :query', { query: '%' + sqlLikeEscape(ps.query) + '%' });
+				q.andWhere('emoji.name like :query', { query: '%' + sqlLikeEscape(ps.query) + '%' })
+					.orderBy('length(emoji.name)', 'ASC');
 			}
 
 			const emojis = await q
-				.orderBy('emoji.id', 'DESC')
+				.addOrderBy('emoji.id', 'DESC')
 				.limit(ps.limit)
 				.getMany();
 
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts
index 59e87253f6..53810d1d16 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -91,7 +91,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				//q.andWhere('emoji.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` });
 				//const emojis = await q.limit(ps.limit).getMany();
 
-				emojis = await q.getMany();
+				emojis = await q.orderBy('length(emoji.name)', 'ASC').getMany();
 				const queryarry = ps.query.match(/\:([a-z0-9_]*)\:/g);
 
 				if (queryarry) {
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts
index 26dd43e926..0fa119eabe 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts
index 18961976f9..d9ee18699c 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts
index c680f2e2d4..dc25df2767 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-license-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-license-bulk.ts
index 47c692b613..4ba99faab7 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/set-license-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-license-bulk.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
index 550bb0052b..22609a16a3 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -57,7 +57,10 @@ export const paramDef = {
 			type: 'string',
 		} },
 	},
-	required: ['id', 'name', 'aliases'],
+	anyOf: [
+		{ required: ['id'] },
+		{ required: ['name'] },
+	],
 } as const;
 
 @Injectable()
@@ -70,27 +73,33 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			let driveFile;
-
 			if (ps.fileId) {
 				driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
 				if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
 			}
-			const emoji = await this.customEmojiService.getEmojiById(ps.id);
-			if (emoji != null) {
-				if (ps.name !== emoji.name) {
+
+			let emojiId;
+			if (ps.id) {
+				emojiId = ps.id;
+				const emoji = await this.customEmojiService.getEmojiById(ps.id);
+				if (!emoji) throw new ApiError(meta.errors.noSuchEmoji);
+				if (ps.name && (ps.name !== emoji.name)) {
 					const isDuplicate = await this.customEmojiService.checkDuplicate(ps.name);
 					if (isDuplicate) throw new ApiError(meta.errors.sameNameEmojiExists);
 				}
 			} else {
-				throw new ApiError(meta.errors.noSuchEmoji);
+				if (!ps.name) throw new Error('Invalid Params unexpectedly passed. This is a BUG. Please report it to the development team.');
+				const emoji = await this.customEmojiService.getEmojiByName(ps.name);
+				if (!emoji) throw new ApiError(meta.errors.noSuchEmoji);
+				emojiId = emoji.id;
 			}
 
-			await this.customEmojiService.update(ps.id, {
+			await this.customEmojiService.update(emojiId, {
 				driveFile,
 				name: ps.name,
-				category: ps.category ?? null,
+				category: ps.category,
 				aliases: ps.aliases,
-				license: ps.license ?? null,
+				license: ps.license,
 				isSensitive: ps.isSensitive,
 				localOnly: ps.localOnly,
 				roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction,
diff --git a/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts b/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts
index 57612850b4..4a54c26009 100644
--- a/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts
+++ b/packages/backend/src/server/api/endpoints/admin/federation/delete-all-files.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts b/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts
index 0d061c685f..556e291025 100644
--- a/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts
+++ b/packages/backend/src/server/api/endpoints/admin/federation/refresh-remote-instance-metadata.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts
index c15fb83454..9e93310746 100644
--- a/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts
+++ b/packages/backend/src/server/api/endpoints/admin/federation/remove-all-following.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts
index 188ab69532..4ababae9f2 100644
--- a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts
+++ b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -25,6 +25,7 @@ export const paramDef = {
 		host: { type: 'string' },
 		isSuspended: { type: 'boolean' },
 		isNSFW: { type: 'boolean' },
+		moderationNote: { type: 'string' },
 	},
 	required: ['host'],
 } as const;
@@ -46,29 +47,32 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				throw new Error('instance not found');
 			}
 
-			if (ps.isSuspended != null) {
-				await this.federatedInstanceService.update(instance.id, {
-					isSuspended: ps.isSuspended,
-				});
+			await this.federatedInstanceService.update(instance.id, {
+				isSuspended: ps.isSuspended,
+				isNSFW: ps.isNSFW,
+				moderationNote: ps.moderationNote,
+			});
 
-				if (instance.isSuspended !== ps.isSuspended) {
-					if (ps.isSuspended) {
-						this.moderationLogService.log(me, 'suspendRemoteInstance', {
-							id: instance.id,
-							host: instance.host,
-						});
-					} else {
-						this.moderationLogService.log(me, 'unsuspendRemoteInstance', {
-							id: instance.id,
-							host: instance.host,
-						});
-					}
+			if (ps.isSuspended != null && instance.isSuspended !== ps.isSuspended) {
+				if (ps.isSuspended) {
+					this.moderationLogService.log(me, 'suspendRemoteInstance', {
+						id: instance.id,
+						host: instance.host,
+					});
+				} else {
+					this.moderationLogService.log(me, 'unsuspendRemoteInstance', {
+						id: instance.id,
+						host: instance.host,
+					});
 				}
 			}
 
-			if (ps.isNSFW != null) {
-				await this.federatedInstanceService.update(instance.id, {
-					isNSFW: ps.isNSFW,
+			if (ps.moderationNote != null && instance.moderationNote !== ps.moderationNote) {
+				this.moderationLogService.log(me, 'updateRemoteInstanceNote', {
+					id: instance.id,
+					host: instance.host,
+					before: instance.moderationNote,
+					after: ps.moderationNote,
 				});
 			}
 		});
diff --git a/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts b/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts
index 0b50212119..90a3fa0200 100644
--- a/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts
+++ b/packages/backend/src/server/api/endpoints/admin/get-index-stats.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts b/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts
index 0d44b288cb..eb85fca179 100644
--- a/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts
+++ b/packages/backend/src/server/api/endpoints/admin/get-table-stats.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -18,6 +18,18 @@ export const meta = {
 	res: {
 		type: 'object',
 		optional: false, nullable: false,
+		additionalProperties: {
+			type: 'object',
+			properties: {
+				count: {
+					type: 'number',
+				},
+				size: {
+					type: 'number',
+				},
+			},
+			required: ['count', 'size'],
+		},
 		example: {
 			migrations: {
 				count: 66,
diff --git a/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts b/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts
index 1b437f718b..b7781b8c99 100644
--- a/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts
+++ b/packages/backend/src/server/api/endpoints/admin/get-user-ips.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/invite/create.ts b/packages/backend/src/server/api/endpoints/admin/invite/create.ts
index 396b84623f..0f551e1ba2 100644
--- a/packages/backend/src/server/api/endpoints/admin/invite/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/invite/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/invite/list.ts b/packages/backend/src/server/api/endpoints/admin/invite/list.ts
index d293dcadc6..e33a9a1aec 100644
--- a/packages/backend/src/server/api/endpoints/admin/invite/list.ts
+++ b/packages/backend/src/server/api/endpoints/admin/invite/list.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts
index 4fd2a568ad..34454c276e 100644
--- a/packages/backend/src/server/api/endpoints/admin/meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/meta.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -45,6 +45,18 @@ export const meta = {
 				type: 'string',
 				optional: false, nullable: true,
 			},
+			enableMcaptcha: {
+				type: 'boolean',
+				optional: false, nullable: false,
+			},
+			mcaptchaSiteKey: {
+				type: 'string',
+				optional: false, nullable: true,
+			},
+			mcaptchaInstanceUrl: {
+				type: 'string',
+				optional: false, nullable: true,
+			},
 			enableRecaptcha: {
 				type: 'boolean',
 				optional: false, nullable: false,
@@ -148,6 +160,13 @@ export const meta = {
 					type: 'string',
 				},
 			},
+			prohibitedWords: {
+				type: 'array',
+				optional: false, nullable: false,
+				items: {
+					type: 'string',
+				},
+			},
 			bannedEmailDomains: {
 				type: 'array',
 				optional: true, nullable: false,
@@ -174,6 +193,10 @@ export const meta = {
 				type: 'string',
 				optional: false, nullable: true,
 			},
+			mcaptchaSecretKey: {
+				type: 'string',
+				optional: false, nullable: true,
+			},
 			recaptchaSecretKey: {
 				type: 'string',
 				optional: false, nullable: true,
@@ -299,6 +322,18 @@ export const meta = {
 				type: 'string',
 				optional: false, nullable: true,
 			},
+			enableTruemailApi: {
+				type: 'boolean',
+				optional: false, nullable: false,
+			},
+			truemailInstance: {
+				type: 'string',
+				optional: false, nullable: true,
+			},
+			truemailAuthKey: {
+				type: 'string',
+				optional: false, nullable: true,
+			},
 			enableChartsForRemoteUser: {
 				type: 'boolean',
 				optional: false, nullable: false,
@@ -367,6 +402,14 @@ export const meta = {
 				type: 'boolean',
 				optional: false, nullable: false,
 			},
+			deeplFreeMode: {
+				type: 'boolean',
+				optional: false, nullable: false,
+			},
+			deeplFreeInstance: {
+				type: 'string',
+				optional: false, nullable: true,
+			},
 			defaultDarkTheme: {
 				type: 'string',
 				optional: false, nullable: true,
@@ -387,6 +430,10 @@ export const meta = {
 				type: 'string',
 				optional: false, nullable: true,
 			},
+			donationUrl: {
+				type: 'string',
+				optional: false, nullable: true,
+			},
 			maintainerEmail: {
 				type: 'string',
 				optional: false, nullable: true,
@@ -413,7 +460,7 @@ export const meta = {
 			},
 			repositoryUrl: {
 				type: 'string',
-				optional: false, nullable: false,
+				optional: false, nullable: true,
 			},
 			summalyProxy: {
 				type: 'string',
@@ -470,12 +517,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				repositoryUrl: instance.repositoryUrl,
 				feedbackUrl: instance.feedbackUrl,
 				impressumUrl: instance.impressumUrl,
+				donationUrl: instance.donationUrl,
 				privacyPolicyUrl: instance.privacyPolicyUrl,
 				disableRegistration: instance.disableRegistration,
 				emailRequiredForSignup: instance.emailRequiredForSignup,
 				approvalRequiredForSignup: instance.approvalRequiredForSignup,
 				enableHcaptcha: instance.enableHcaptcha,
 				hcaptchaSiteKey: instance.hcaptchaSiteKey,
+				enableMcaptcha: instance.enableMcaptcha,
+				mcaptchaSiteKey: instance.mcaptchaSitekey,
+				mcaptchaInstanceUrl: instance.mcaptchaInstanceUrl,
 				enableRecaptcha: instance.enableRecaptcha,
 				recaptchaSiteKey: instance.recaptchaSiteKey,
 				enableTurnstile: instance.enableTurnstile,
@@ -505,9 +556,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				blockedHosts: instance.blockedHosts,
 				silencedHosts: instance.silencedHosts,
 				sensitiveWords: instance.sensitiveWords,
+				prohibitedWords: instance.prohibitedWords,
 				preservedUsernames: instance.preservedUsernames,
 				bubbleInstances: instance.bubbleInstances,
 				hcaptchaSecretKey: instance.hcaptchaSecretKey,
+				mcaptchaSecretKey: instance.mcaptchaSecretKey,
 				recaptchaSecretKey: instance.recaptchaSecretKey,
 				turnstileSecretKey: instance.turnstileSecretKey,
 				sensitiveMediaDetection: instance.sensitiveMediaDetection,
@@ -539,10 +592,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle,
 				deeplAuthKey: instance.deeplAuthKey,
 				deeplIsPro: instance.deeplIsPro,
+				deeplFreeMode: instance.deeplFreeMode,
+				deeplFreeInstance: instance.deeplFreeInstance,
 				enableIpLogging: instance.enableIpLogging,
 				enableActiveEmailValidation: instance.enableActiveEmailValidation,
 				enableVerifymailApi: instance.enableVerifymailApi,
 				verifymailAuthKey: instance.verifymailAuthKey,
+				enableTruemailApi: instance.enableTruemailApi,
+				truemailInstance: instance.truemailInstance,
+				truemailAuthKey: instance.truemailAuthKey,
 				enableChartsForRemoteUser: instance.enableChartsForRemoteUser,
 				enableChartsForFederatedInstances: instance.enableChartsForFederatedInstances,
 				enableServerMachineStats: instance.enableServerMachineStats,
diff --git a/packages/backend/src/server/api/endpoints/admin/promo/create.ts b/packages/backend/src/server/api/endpoints/admin/promo/create.ts
index ab69dfba96..1d32c6cc00 100644
--- a/packages/backend/src/server/api/endpoints/admin/promo/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/promo/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -55,7 +55,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				throw e;
 			});
 
-			const exist = await this.promoNotesRepository.exist({ where: { noteId: note.id } });
+			const exist = await this.promoNotesRepository.exists({ where: { noteId: note.id } });
 
 			if (exist) {
 				throw new ApiError(meta.errors.alreadyPromoted);
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/clear.ts b/packages/backend/src/server/api/endpoints/admin/queue/clear.ts
index 9912043c8b..3f7df0e63d 100644
--- a/packages/backend/src/server/api/endpoints/admin/queue/clear.ts
+++ b/packages/backend/src/server/api/endpoints/admin/queue/clear.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts
index 8473909103..7a3410ffa7 100644
--- a/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts
+++ b/packages/backend/src/server/api/endpoints/admin/queue/deliver-delayed.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts
index 19f7cb85c0..305ae1af1d 100644
--- a/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts
+++ b/packages/backend/src/server/api/endpoints/admin/queue/inbox-delayed.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/promote.ts b/packages/backend/src/server/api/endpoints/admin/queue/promote.ts
index d06780e044..7502d4e1f7 100644
--- a/packages/backend/src/server/api/endpoints/admin/queue/promote.ts
+++ b/packages/backend/src/server/api/endpoints/admin/queue/promote.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts
index 189690b703..9694b3fa40 100644
--- a/packages/backend/src/server/api/endpoints/admin/queue/stats.ts
+++ b/packages/backend/src/server/api/endpoints/admin/queue/stats.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/relays/add.ts b/packages/backend/src/server/api/endpoints/admin/relays/add.ts
index d55dff7b0c..3d7bc4567e 100644
--- a/packages/backend/src/server/api/endpoints/admin/relays/add.ts
+++ b/packages/backend/src/server/api/endpoints/admin/relays/add.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/relays/list.ts b/packages/backend/src/server/api/endpoints/admin/relays/list.ts
index 61ea287bff..587d5c3b03 100644
--- a/packages/backend/src/server/api/endpoints/admin/relays/list.ts
+++ b/packages/backend/src/server/api/endpoints/admin/relays/list.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/relays/remove.ts b/packages/backend/src/server/api/endpoints/admin/relays/remove.ts
index 8a6dd4e152..1f6e773cd4 100644
--- a/packages/backend/src/server/api/endpoints/admin/relays/remove.ts
+++ b/packages/backend/src/server/api/endpoints/admin/relays/remove.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/reset-password.ts b/packages/backend/src/server/api/endpoints/admin/reset-password.ts
index 506cd609ae..828dbae712 100644
--- a/packages/backend/src/server/api/endpoints/admin/reset-password.ts
+++ b/packages/backend/src/server/api/endpoints/admin/reset-password.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts
index 26c4038b98..8b0456068b 100644
--- a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts
+++ b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/roles/assign.ts b/packages/backend/src/server/api/endpoints/admin/roles/assign.ts
index 8eb3d2bf59..b6c7953781 100644
--- a/packages/backend/src/server/api/endpoints/admin/roles/assign.ts
+++ b/packages/backend/src/server/api/endpoints/admin/roles/assign.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/roles/create.ts b/packages/backend/src/server/api/endpoints/admin/roles/create.ts
index de23d2fb11..e0c02f7a5d 100644
--- a/packages/backend/src/server/api/endpoints/admin/roles/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/roles/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/roles/delete.ts b/packages/backend/src/server/api/endpoints/admin/roles/delete.ts
index 9e2968e317..638e2b15b9 100644
--- a/packages/backend/src/server/api/endpoints/admin/roles/delete.ts
+++ b/packages/backend/src/server/api/endpoints/admin/roles/delete.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/roles/list.ts b/packages/backend/src/server/api/endpoints/admin/roles/list.ts
index d3d1a10a69..333fac6aa6 100644
--- a/packages/backend/src/server/api/endpoints/admin/roles/list.ts
+++ b/packages/backend/src/server/api/endpoints/admin/roles/list.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/roles/show.ts b/packages/backend/src/server/api/endpoints/admin/roles/show.ts
index ad4345e5a5..13e5cbb995 100644
--- a/packages/backend/src/server/api/endpoints/admin/roles/show.ts
+++ b/packages/backend/src/server/api/endpoints/admin/roles/show.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/roles/unassign.ts b/packages/backend/src/server/api/endpoints/admin/roles/unassign.ts
index c11265252c..e7da3384b1 100644
--- a/packages/backend/src/server/api/endpoints/admin/roles/unassign.ts
+++ b/packages/backend/src/server/api/endpoints/admin/roles/unassign.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/roles/update-default-policies.ts b/packages/backend/src/server/api/endpoints/admin/roles/update-default-policies.ts
index 203f749a6e..d7209965db 100644
--- a/packages/backend/src/server/api/endpoints/admin/roles/update-default-policies.ts
+++ b/packages/backend/src/server/api/endpoints/admin/roles/update-default-policies.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/roles/update.ts b/packages/backend/src/server/api/endpoints/admin/roles/update.ts
index 74d5aae5d8..5242e0be2f 100644
--- a/packages/backend/src/server/api/endpoints/admin/roles/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/roles/update.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/roles/users.ts b/packages/backend/src/server/api/endpoints/admin/roles/users.ts
index 66f4d9d26b..45758d4f50 100644
--- a/packages/backend/src/server/api/endpoints/admin/roles/users.ts
+++ b/packages/backend/src/server/api/endpoints/admin/roles/users.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -17,7 +17,7 @@ export const meta = {
 	tags: ['admin', 'role', 'users'],
 
 	requireCredential: false,
-	requireAdmin: true,
+	requireModerator: true,
 	kind: 'read:admin:roles',
 
 	errors: {
@@ -40,7 +40,7 @@ export const meta = {
 			},
 			required: ['id', 'createdAt', 'user'],
 		},
-	}
+	},
 } as const;
 
 export const paramDef = {
@@ -92,7 +92,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			return await Promise.all(assigns.map(async assign => ({
 				id: assign.id,
 				createdAt: this.idService.parse(assign.id).date.toISOString(),
-				user: await this.userEntityService.pack(assign.user!, me, { detail: true }),
+				user: await this.userEntityService.pack(assign.user!, me, { schema: 'UserDetailed' }),
 				expiresAt: assign.expiresAt?.toISOString() ?? null,
 			})));
 		});
diff --git a/packages/backend/src/server/api/endpoints/admin/send-email.ts b/packages/backend/src/server/api/endpoints/admin/send-email.ts
index d20aee656c..f01a7778a8 100644
--- a/packages/backend/src/server/api/endpoints/admin/send-email.ts
+++ b/packages/backend/src/server/api/endpoints/admin/send-email.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/server-info.ts b/packages/backend/src/server/api/endpoints/admin/server-info.ts
index 374712f57d..80b6a4d32e 100644
--- a/packages/backend/src/server/api/endpoints/admin/server-info.ts
+++ b/packages/backend/src/server/api/endpoints/admin/server-info.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts
index f3601be9bb..58c5f1f60a 100644
--- a/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts
+++ b/packages/backend/src/server/api/endpoints/admin/show-moderation-logs.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -50,7 +50,7 @@ export const meta = {
 				user: {
 					type: 'object',
 					optional: false, nullable: false,
-					ref: 'UserDetailed',
+					ref: 'UserDetailedNotMe',
 				},
 			},
 		},
diff --git a/packages/backend/src/server/api/endpoints/admin/show-user.ts b/packages/backend/src/server/api/endpoints/admin/show-user.ts
index ea22f9eeb9..a7ca7f9547 100644
--- a/packages/backend/src/server/api/endpoints/admin/show-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/show-user.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -10,6 +10,7 @@ import { DI } from '@/di-symbols.js';
 import { RoleService } from '@/core/RoleService.js';
 import { RoleEntityService } from '@/core/entities/RoleEntityService.js';
 import { IdService } from '@/core/IdService.js';
+import { notificationRecieveConfig } from '@/models/json-schema/user.js';
 
 export const meta = {
 	tags: ['admin'],
@@ -21,6 +22,157 @@ export const meta = {
 	res: {
 		type: 'object',
 		nullable: false, optional: false,
+		properties: {
+			email: {
+				type: 'string',
+				optional: false, nullable: true,
+			},
+			emailVerified: {
+				type: 'boolean',
+				optional: false, nullable: false,
+			},
+			autoAcceptFollowed: {
+				type: 'boolean',
+				optional: false, nullable: false,
+			},
+			noCrawle: {
+				type: 'boolean',
+				optional: false, nullable: false,
+			},
+			preventAiLearning: {
+				type: 'boolean',
+				optional: false, nullable: false,
+			},
+			alwaysMarkNsfw: {
+				type: 'boolean',
+				optional: false, nullable: false,
+			},
+			autoSensitive: {
+				type: 'boolean',
+				optional: false, nullable: false,
+			},
+			carefulBot: {
+				type: 'boolean',
+				optional: false, nullable: false,
+			},
+			injectFeaturedNote: {
+				type: 'boolean',
+				optional: false, nullable: false,
+			},
+			receiveAnnouncementEmail: {
+				type: 'boolean',
+				optional: false, nullable: false,
+			},
+			mutedWords: {
+				type: 'array',
+				optional: false, nullable: false,
+				items: {
+					anyOf: [
+						{
+							type: 'string',
+						},
+						{
+							type: 'array',
+							items: {
+								type: 'string',
+							},
+						},
+					],
+				},
+			},
+			mutedInstances: {
+				type: 'array',
+				optional: false, nullable: false,
+				items: {
+					type: 'string',
+				},
+			},
+			notificationRecieveConfig: {
+				type: 'object',
+				optional: false, nullable: false,
+				properties: {
+					note: { optional: true, ...notificationRecieveConfig },
+					follow: { optional: true, ...notificationRecieveConfig },
+					mention: { optional: true, ...notificationRecieveConfig },
+					reply: { optional: true, ...notificationRecieveConfig },
+					renote: { optional: true, ...notificationRecieveConfig },
+					quote: { optional: true, ...notificationRecieveConfig },
+					reaction: { optional: true, ...notificationRecieveConfig },
+					pollEnded: { optional: true, ...notificationRecieveConfig },
+					receiveFollowRequest: { optional: true, ...notificationRecieveConfig },
+					followRequestAccepted: { optional: true, ...notificationRecieveConfig },
+					roleAssigned: { optional: true, ...notificationRecieveConfig },
+					achievementEarned: { optional: true, ...notificationRecieveConfig },
+					app: { optional: true, ...notificationRecieveConfig },
+					test: { optional: true, ...notificationRecieveConfig },
+				},
+			},
+			isModerator: {
+				type: 'boolean',
+				optional: false, nullable: false,
+			},
+			isSilenced: {
+				type: 'boolean',
+				optional: false, nullable: false,
+			},
+			isSuspended: {
+				type: 'boolean',
+				optional: false, nullable: false,
+			},
+			isHibernated: {
+				type: 'boolean',
+				optional: false, nullable: false,
+			},
+			lastActiveDate: {
+				type: 'string',
+				optional: false, nullable: true,
+			},
+			moderationNote: {
+				type: 'string',
+				optional: false, nullable: false,
+			},
+			signins: {
+				type: 'array',
+				optional: false, nullable: false,
+				items: {
+					ref: 'Signin',
+				},
+			},
+			policies: {
+				type: 'object',
+				optional: false, nullable: false,
+				ref: 'RolePolicies',
+			},
+			roles: {
+				type: 'array',
+				optional: false, nullable: false,
+				items: {
+					type: 'object',
+					ref: 'Role',
+				},
+			},
+			roleAssigns: {
+				type: 'array',
+				optional: false, nullable: false,
+				items: {
+					type: 'object',
+					properties: {
+						createdAt: {
+							type: 'string',
+							optional: false, nullable: false,
+						},
+						expiresAt: {
+							type: 'string',
+							optional: false, nullable: true,
+						},
+						roleId: {
+							type: 'string',
+							optional: false, nullable: false,
+						},
+					},
+				},
+			},
+		},
 	},
 } as const;
 
@@ -91,7 +243,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				isSilenced: isSilenced,
 				isSuspended: user.isSuspended,
 				isHibernated: user.isHibernated,
-				lastActiveDate: user.lastActiveDate,
+				lastActiveDate: user.lastActiveDate ? user.lastActiveDate.toISOString() : null,
 				moderationNote: profile.moderationNote ?? '',
 				signins,
 				policies: await this.roleService.getUserPolicies(user.id),
diff --git a/packages/backend/src/server/api/endpoints/admin/show-users.ts b/packages/backend/src/server/api/endpoints/admin/show-users.ts
index 6267fb97b2..685da928e3 100644
--- a/packages/backend/src/server/api/endpoints/admin/show-users.ts
+++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -115,7 +115,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			const users = await query.getMany();
 
-			return await this.userEntityService.packMany(users, me, { detail: true });
+			return await this.userEntityService.packMany(users, me, { schema: 'UserDetailed' });
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts
index a26fa81c13..8a946405cc 100644
--- a/packages/backend/src/server/api/endpoints/admin/suspend-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/suspend-user.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/unset-user-avatar.ts b/packages/backend/src/server/api/endpoints/admin/unset-user-avatar.ts
index 8b22fad1d4..ddab6f3a9d 100644
--- a/packages/backend/src/server/api/endpoints/admin/unset-user-avatar.ts
+++ b/packages/backend/src/server/api/endpoints/admin/unset-user-avatar.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/unset-user-banner.ts b/packages/backend/src/server/api/endpoints/admin/unset-user-banner.ts
index 5ec359c0ef..e16dad719c 100644
--- a/packages/backend/src/server/api/endpoints/admin/unset-user-banner.ts
+++ b/packages/backend/src/server/api/endpoints/admin/unset-user-banner.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts
index 9c896f0e64..2c2b1bf6f5 100644
--- a/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts
+++ b/packages/backend/src/server/api/endpoints/admin/unsuspend-user.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
index 5c916fe340..7fea7d969e 100644
--- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -41,6 +41,11 @@ export const paramDef = {
 				type: 'string',
 			},
 		},
+		prohibitedWords: {
+			type: 'array', nullable: true, items: {
+				type: 'string',
+			},
+		},
 		themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' },
 		mascotImageUrl: { type: 'string', nullable: true },
 		bannerUrl: { type: 'string', nullable: true },
@@ -65,6 +70,10 @@ export const paramDef = {
 		enableHcaptcha: { type: 'boolean' },
 		hcaptchaSiteKey: { type: 'string', nullable: true },
 		hcaptchaSecretKey: { type: 'string', nullable: true },
+		enableMcaptcha: { type: 'boolean' },
+		mcaptchaSiteKey: { type: 'string', nullable: true },
+		mcaptchaInstanceUrl: { type: 'string', nullable: true },
+		mcaptchaSecretKey: { type: 'string', nullable: true },
 		enableRecaptcha: { type: 'boolean' },
 		recaptchaSiteKey: { type: 'string', nullable: true },
 		recaptchaSecretKey: { type: 'string', nullable: true },
@@ -87,6 +96,8 @@ export const paramDef = {
 		summalyProxy: { type: 'string', nullable: true },
 		deeplAuthKey: { type: 'string', nullable: true },
 		deeplIsPro: { type: 'boolean' },
+		deeplFreeMode: { type: 'boolean' },
+		deeplFreeInstance: { type: 'string', nullable: true },
 		enableEmail: { type: 'boolean' },
 		email: { type: 'string', nullable: true },
 		smtpSecure: { type: 'boolean' },
@@ -98,9 +109,10 @@ export const paramDef = {
 		swPublicKey: { type: 'string', nullable: true },
 		swPrivateKey: { type: 'string', nullable: true },
 		tosUrl: { type: 'string', nullable: true },
-		repositoryUrl: { type: 'string' },
-		feedbackUrl: { type: 'string' },
+		repositoryUrl: { type: 'string', nullable: true },
+		feedbackUrl: { type: 'string', nullable: true },
 		impressumUrl: { type: 'string', nullable: true },
+		donationUrl: { type: 'string', nullable: true },
 		privacyPolicyUrl: { type: 'string', nullable: true },
 		useObjectStorage: { type: 'boolean' },
 		objectStorageBaseUrl: { type: 'string', nullable: true },
@@ -119,6 +131,9 @@ export const paramDef = {
 		enableActiveEmailValidation: { type: 'boolean' },
 		enableVerifymailApi: { type: 'boolean' },
 		verifymailAuthKey: { type: 'string', nullable: true },
+		enableTruemailApi: { type: 'boolean' },
+		truemailInstance: { type: 'string', nullable: true },
+		truemailAuthKey: { type: 'string', nullable: true },
 		enableChartsForRemoteUser: { type: 'boolean' },
 		enableChartsForFederatedInstances: { type: 'boolean' },
 		enableServerMachineStats: { type: 'boolean' },
@@ -175,6 +190,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			if (Array.isArray(ps.sensitiveWords)) {
 				set.sensitiveWords = ps.sensitiveWords.filter(Boolean);
 			}
+			if (Array.isArray(ps.prohibitedWords)) {
+				set.prohibitedWords = ps.prohibitedWords.filter(Boolean);
+			}
 			if (Array.isArray(ps.silencedHosts)) {
 				let lastValue = '';
 				set.silencedHosts = ps.silencedHosts.sort().filter((h) => {
@@ -279,6 +297,22 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				set.hcaptchaSecretKey = ps.hcaptchaSecretKey;
 			}
 
+			if (ps.enableMcaptcha !== undefined) {
+				set.enableMcaptcha = ps.enableMcaptcha;
+			}
+
+			if (ps.mcaptchaSiteKey !== undefined) {
+				set.mcaptchaSitekey = ps.mcaptchaSiteKey;
+			}
+
+			if (ps.mcaptchaInstanceUrl !== undefined) {
+				set.mcaptchaInstanceUrl = ps.mcaptchaInstanceUrl;
+			}
+
+			if (ps.mcaptchaSecretKey !== undefined) {
+				set.mcaptchaSecretKey = ps.mcaptchaSecretKey;
+			}
+
 			if (ps.enableRecaptcha !== undefined) {
 				set.enableRecaptcha = ps.enableRecaptcha;
 			}
@@ -372,7 +406,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			}
 
 			if (ps.repositoryUrl !== undefined) {
-				set.repositoryUrl = ps.repositoryUrl;
+				set.repositoryUrl = URL.canParse(ps.repositoryUrl!) ? ps.repositoryUrl : null;
 			}
 
 			if (ps.feedbackUrl !== undefined) {
@@ -383,6 +417,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				set.impressumUrl = ps.impressumUrl;
 			}
 
+			if (ps.donationUrl !== undefined) {
+				set.donationUrl = ps.donationUrl;
+			}
+
 			if (ps.privacyPolicyUrl !== undefined) {
 				set.privacyPolicyUrl = ps.privacyPolicyUrl;
 			}
@@ -451,6 +489,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				set.deeplIsPro = ps.deeplIsPro;
 			}
 
+			if (ps.deeplFreeMode !== undefined) {
+				set.deeplFreeMode = ps.deeplFreeMode;
+			}
+
+			if (ps.deeplFreeInstance !== undefined) {
+				if (ps.deeplFreeInstance === '') {
+					set.deeplFreeInstance = null;
+				} else {
+					set.deeplFreeInstance = ps.deeplFreeInstance;
+				}
+			}
+
 			if (ps.enableIpLogging !== undefined) {
 				set.enableIpLogging = ps.enableIpLogging;
 			}
@@ -471,6 +521,26 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				}
 			}
 
+			if (ps.enableTruemailApi !== undefined) {
+				set.enableTruemailApi = ps.enableTruemailApi;
+			}
+
+			if (ps.truemailInstance !== undefined) {
+				if (ps.truemailInstance === '') {
+					set.truemailInstance = null;
+				} else {
+					set.truemailInstance = ps.truemailInstance;
+				}
+			}
+
+			if (ps.truemailAuthKey !== undefined) {
+				if (ps.truemailAuthKey === '') {
+					set.truemailAuthKey = null;
+				} else {
+					set.truemailAuthKey = ps.truemailAuthKey;
+				}
+			}
+
 			if (ps.enableChartsForRemoteUser !== undefined) {
 				set.enableChartsForRemoteUser = ps.enableChartsForRemoteUser;
 			}
diff --git a/packages/backend/src/server/api/endpoints/admin/update-user-note.ts b/packages/backend/src/server/api/endpoints/admin/update-user-note.ts
index e582147e72..e9930422c0 100644
--- a/packages/backend/src/server/api/endpoints/admin/update-user-note.ts
+++ b/packages/backend/src/server/api/endpoints/admin/update-user-note.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/announcements.ts b/packages/backend/src/server/api/endpoints/announcements.ts
index 7c242dbcd5..3b12f5b62c 100644
--- a/packages/backend/src/server/api/endpoints/announcements.ts
+++ b/packages/backend/src/server/api/endpoints/announcements.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts
index b029493d3a..191de8f833 100644
--- a/packages/backend/src/server/api/endpoints/antennas/create.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/antennas/delete.ts b/packages/backend/src/server/api/endpoints/antennas/delete.ts
index e6240aec65..2258954b56 100644
--- a/packages/backend/src/server/api/endpoints/antennas/delete.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/delete.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/antennas/list.ts b/packages/backend/src/server/api/endpoints/antennas/list.ts
index 3a9f969d24..83d29f9c8c 100644
--- a/packages/backend/src/server/api/endpoints/antennas/list.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/list.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts
index 0bf2688b4a..f4dfe1ecc4 100644
--- a/packages/backend/src/server/api/endpoints/antennas/notes.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -14,6 +14,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import { IdService } from '@/core/IdService.js';
 import { FanoutTimelineService } from '@/core/FanoutTimelineService.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { trackPromise } from '@/misc/promise-tracker.js';
 import { ApiError } from '../../error.js';
 
 export const meta = {
@@ -92,7 +93,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			antenna.isActive = true;
 			antenna.lastUsedAt = new Date();
-			this.antennasRepository.update(antenna.id, antenna);
+			trackPromise(this.antennasRepository.update(antenna.id, antenna));
 
 			if (needPublishEvent) {
 				this.globalEventService.publishInternalEvent('antennaUpdated', antenna);
@@ -123,9 +124,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				notes.sort((a, b) => a.id > b.id ? -1 : 1);
 			}
 
-			if (notes.length > 0) {
-				this.noteReadService.read(me.id, notes);
-			}
+			this.noteReadService.read(me.id, notes);
 
 			return await this.noteEntityService.packMany(notes, me);
 		});
diff --git a/packages/backend/src/server/api/endpoints/antennas/show.ts b/packages/backend/src/server/api/endpoints/antennas/show.ts
index 77c9b31763..a40f187d0b 100644
--- a/packages/backend/src/server/api/endpoints/antennas/show.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/show.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts
index 3457bb6f66..459729f61f 100644
--- a/packages/backend/src/server/api/endpoints/antennas/update.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/update.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/ap/get.ts b/packages/backend/src/server/api/endpoints/ap/get.ts
index e0ef5d413a..d8c55de7ec 100644
--- a/packages/backend/src/server/api/endpoints/ap/get.ts
+++ b/packages/backend/src/server/api/endpoints/ap/get.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts
index 8ab16880fa..364a4826e3 100644
--- a/packages/backend/src/server/api/endpoints/ap/show.ts
+++ b/packages/backend/src/server/api/endpoints/ap/show.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -148,7 +148,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		if (user != null) {
 			return {
 				type: 'User',
-				object: await this.userEntityService.pack(user, me, { detail: true }),
+				object: await this.userEntityService.pack(user, me, { schema: 'UserDetailedNotMe' }),
 			};
 		} else if (note != null) {
 			try {
diff --git a/packages/backend/src/server/api/endpoints/app/create.ts b/packages/backend/src/server/api/endpoints/app/create.ts
index f89d9823ba..492705d6f9 100644
--- a/packages/backend/src/server/api/endpoints/app/create.ts
+++ b/packages/backend/src/server/api/endpoints/app/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/app/show.ts b/packages/backend/src/server/api/endpoints/app/show.ts
index cb968a1c65..3db9a0d0d4 100644
--- a/packages/backend/src/server/api/endpoints/app/show.ts
+++ b/packages/backend/src/server/api/endpoints/app/show.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/auth/accept.ts b/packages/backend/src/server/api/endpoints/auth/accept.ts
index e0baeb3565..2e62f04df0 100644
--- a/packages/backend/src/server/api/endpoints/auth/accept.ts
+++ b/packages/backend/src/server/api/endpoints/auth/accept.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -62,7 +62,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			const accessToken = secureRndstr(32);
 
 			// Fetch exist access token
-			const exist = await this.accessTokensRepository.exist({
+			const exist = await this.accessTokensRepository.exists({
 				where: {
 					appId: session.appId,
 					userId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/auth/session/generate.ts b/packages/backend/src/server/api/endpoints/auth/session/generate.ts
index 6e474c59e0..26dd893138 100644
--- a/packages/backend/src/server/api/endpoints/auth/session/generate.ts
+++ b/packages/backend/src/server/api/endpoints/auth/session/generate.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/auth/session/show.ts b/packages/backend/src/server/api/endpoints/auth/session/show.ts
index 0f5da0f252..13e02a2541 100644
--- a/packages/backend/src/server/api/endpoints/auth/session/show.ts
+++ b/packages/backend/src/server/api/endpoints/auth/session/show.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts
index ffddda090b..b490c5832d 100644
--- a/packages/backend/src/server/api/endpoints/auth/session/userkey.ts
+++ b/packages/backend/src/server/api/endpoints/auth/session/userkey.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -112,7 +112,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			return {
 				accessToken: accessToken.token,
 				user: await this.userEntityService.pack(session.userId, null, {
-					detail: true,
+					schema: 'UserDetailedNotMe',
 				}),
 			};
 		});
diff --git a/packages/backend/src/server/api/endpoints/blocking/create.ts b/packages/backend/src/server/api/endpoints/blocking/create.ts
index 3c7d7ac8cd..5066215749 100644
--- a/packages/backend/src/server/api/endpoints/blocking/create.ts
+++ b/packages/backend/src/server/api/endpoints/blocking/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -88,7 +88,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			});
 
 			// Check if already blocking
-			const exist = await this.blockingsRepository.exist({
+			const exist = await this.blockingsRepository.exists({
 				where: {
 					blockerId: blocker.id,
 					blockeeId: blockee.id,
@@ -102,7 +102,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			await this.userBlockingService.block(blocker, blockee);
 
 			return await this.userEntityService.pack(blockee.id, blocker, {
-				detail: true,
+				schema: 'UserDetailedNotMe',
 			});
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/blocking/delete.ts b/packages/backend/src/server/api/endpoints/blocking/delete.ts
index 0ce334d559..cebb307338 100644
--- a/packages/backend/src/server/api/endpoints/blocking/delete.ts
+++ b/packages/backend/src/server/api/endpoints/blocking/delete.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -88,7 +88,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			});
 
 			// Check not blocking
-			const exist = await this.blockingsRepository.exist({
+			const exist = await this.blockingsRepository.exists({
 				where: {
 					blockerId: blocker.id,
 					blockeeId: blockee.id,
@@ -103,7 +103,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			await this.userBlockingService.unblock(blocker, blockee);
 
 			return await this.userEntityService.pack(blockee.id, blocker, {
-				detail: true,
+				schema: 'UserDetailedNotMe',
 			});
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/blocking/list.ts b/packages/backend/src/server/api/endpoints/blocking/list.ts
index 58d24540d1..8431fa6b34 100644
--- a/packages/backend/src/server/api/endpoints/blocking/list.ts
+++ b/packages/backend/src/server/api/endpoints/blocking/list.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/bubble-game/ranking.ts b/packages/backend/src/server/api/endpoints/bubble-game/ranking.ts
new file mode 100644
index 0000000000..ab877bbe20
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/bubble-game/ranking.ts
@@ -0,0 +1,83 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import { MoreThan } from 'typeorm';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import type { BubbleGameRecordsRepository } from '@/models/_.js';
+import { DI } from '@/di-symbols.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+
+export const meta = {
+	allowGet: true,
+	cacheSec: 60,
+
+	errors: {
+	},
+
+	res: {
+		type: 'array',
+		optional: false, nullable: false,
+		items: {
+			type: 'object',
+			optional: false, nullable: false,
+			properties: {
+				id: {
+					type: 'string', format: 'misskey:id',
+					optional: false, nullable: false,
+				},
+				score: {
+					type: 'integer',
+					optional: false, nullable: false,
+				},
+				user: {
+					type: 'object',
+					optional: true, nullable: false,
+					ref: 'UserLite',
+				},
+			},
+		},
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		gameMode: { type: 'string' },
+	},
+	required: ['gameMode'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		@Inject(DI.bubbleGameRecordsRepository)
+		private bubbleGameRecordsRepository: BubbleGameRecordsRepository,
+
+		private userEntityService: UserEntityService,
+	) {
+		super(meta, paramDef, async (ps) => {
+			const records = await this.bubbleGameRecordsRepository.find({
+				where: {
+					gameMode: ps.gameMode,
+					seededAt: MoreThan(new Date(Date.now() - 1000 * 60 * 60 * 24 * 7)),
+				},
+				order: {
+					score: 'DESC',
+				},
+				take: 10,
+				relations: ['user'],
+			});
+
+			const users = await this.userEntityService.packMany(records.map(r => r.user!), null);
+
+			return records.map(r => ({
+				id: r.id,
+				score: r.score,
+				user: users.find(u => u.id === r.user!.id),
+			}));
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/bubble-game/register.ts b/packages/backend/src/server/api/endpoints/bubble-game/register.ts
new file mode 100644
index 0000000000..0a999e42cd
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/bubble-game/register.ts
@@ -0,0 +1,89 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import ms from 'ms';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { IdService } from '@/core/IdService.js';
+import type { BubbleGameRecordsRepository } from '@/models/_.js';
+import { DI } from '@/di-symbols.js';
+import { ApiError } from '../../error.js';
+
+export const meta = {
+	requireCredential: true,
+
+	kind: 'write:account',
+
+	limit: {
+		duration: ms('1hour'),
+		max: 120,
+		minInterval: ms('30sec'),
+	},
+
+	errors: {
+		invalidSeed: {
+			message: 'Provided seed is invalid.',
+			code: 'INVALID_SEED',
+			id: 'eb627bc7-574b-4a52-a860-3c3eae772b88',
+		},
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		score: { type: 'integer', minimum: 0 },
+		seed: { type: 'string', minLength: 1, maxLength: 1024 },
+		logs: {
+			type: 'array',
+			items: {
+				type: 'array',
+				items: {
+					type: 'number',
+				},
+			},
+		},
+		gameMode: { type: 'string' },
+		gameVersion: { type: 'integer' },
+	},
+	required: ['score', 'seed', 'logs', 'gameMode', 'gameVersion'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		@Inject(DI.bubbleGameRecordsRepository)
+		private bubbleGameRecordsRepository: BubbleGameRecordsRepository,
+
+		private idService: IdService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			const seedDate = new Date(parseInt(ps.seed, 10));
+			const now = new Date();
+
+			// シードが未来なのは通常のプレイではありえないので弾く
+			if (seedDate.getTime() > now.getTime()) {
+				throw new ApiError(meta.errors.invalidSeed);
+			}
+
+			// シードが古すぎる(5時間以上前)のも弾く
+			if (seedDate.getTime() < now.getTime() - 1000 * 60 * 60 * 5) {
+				throw new ApiError(meta.errors.invalidSeed);
+			}
+
+			await this.bubbleGameRecordsRepository.insert({
+				id: this.idService.gen(now.getTime()),
+				seed: ps.seed,
+				seededAt: seedDate,
+				userId: me.id,
+				score: ps.score,
+				logs: ps.logs,
+				gameMode: ps.gameMode,
+				gameVersion: ps.gameVersion,
+				isVerified: false,
+			});
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/channels/create.ts b/packages/backend/src/server/api/endpoints/channels/create.ts
index 3dd1eddd01..2866db5424 100644
--- a/packages/backend/src/server/api/endpoints/channels/create.ts
+++ b/packages/backend/src/server/api/endpoints/channels/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/channels/favorite.ts b/packages/backend/src/server/api/endpoints/channels/favorite.ts
index c175718919..a1ae9b80a7 100644
--- a/packages/backend/src/server/api/endpoints/channels/favorite.ts
+++ b/packages/backend/src/server/api/endpoints/channels/favorite.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/channels/featured.ts b/packages/backend/src/server/api/endpoints/channels/featured.ts
index 412ea1bb16..a9a79ba8fc 100644
--- a/packages/backend/src/server/api/endpoints/channels/featured.ts
+++ b/packages/backend/src/server/api/endpoints/channels/featured.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/channels/follow.ts b/packages/backend/src/server/api/endpoints/channels/follow.ts
index bb5a477eb8..1812820ba2 100644
--- a/packages/backend/src/server/api/endpoints/channels/follow.ts
+++ b/packages/backend/src/server/api/endpoints/channels/follow.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/channels/followed.ts b/packages/backend/src/server/api/endpoints/channels/followed.ts
index 6514f1ea3c..d2f36f251e 100644
--- a/packages/backend/src/server/api/endpoints/channels/followed.ts
+++ b/packages/backend/src/server/api/endpoints/channels/followed.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/channels/my-favorites.ts b/packages/backend/src/server/api/endpoints/channels/my-favorites.ts
index 057a438ac9..d96e6c3ad2 100644
--- a/packages/backend/src/server/api/endpoints/channels/my-favorites.ts
+++ b/packages/backend/src/server/api/endpoints/channels/my-favorites.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/channels/owned.ts b/packages/backend/src/server/api/endpoints/channels/owned.ts
index b1dd693537..daab685f1b 100644
--- a/packages/backend/src/server/api/endpoints/channels/owned.ts
+++ b/packages/backend/src/server/api/endpoints/channels/owned.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/channels/search.ts b/packages/backend/src/server/api/endpoints/channels/search.ts
index 9c78a94844..ae32203603 100644
--- a/packages/backend/src/server/api/endpoints/channels/search.ts
+++ b/packages/backend/src/server/api/endpoints/channels/search.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/channels/show.ts b/packages/backend/src/server/api/endpoints/channels/show.ts
index 3eaa83c7e8..332ce2c9dc 100644
--- a/packages/backend/src/server/api/endpoints/channels/show.ts
+++ b/packages/backend/src/server/api/endpoints/channels/show.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/channels/timeline.ts b/packages/backend/src/server/api/endpoints/channels/timeline.ts
index 006228ceee..8c55673590 100644
--- a/packages/backend/src/server/api/endpoints/channels/timeline.ts
+++ b/packages/backend/src/server/api/endpoints/channels/timeline.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/channels/unfavorite.ts b/packages/backend/src/server/api/endpoints/channels/unfavorite.ts
index b4c7af8154..fc6b75e295 100644
--- a/packages/backend/src/server/api/endpoints/channels/unfavorite.ts
+++ b/packages/backend/src/server/api/endpoints/channels/unfavorite.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/channels/unfollow.ts b/packages/backend/src/server/api/endpoints/channels/unfollow.ts
index c95332c7f8..48c5261135 100644
--- a/packages/backend/src/server/api/endpoints/channels/unfollow.ts
+++ b/packages/backend/src/server/api/endpoints/channels/unfollow.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/channels/update.ts b/packages/backend/src/server/api/endpoints/channels/update.ts
index 93d02e4a12..dba2938b39 100644
--- a/packages/backend/src/server/api/endpoints/channels/update.ts
+++ b/packages/backend/src/server/api/endpoints/channels/update.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/charts/active-users.ts b/packages/backend/src/server/api/endpoints/charts/active-users.ts
index e768923ce1..fd21e3d9fe 100644
--- a/packages/backend/src/server/api/endpoints/charts/active-users.ts
+++ b/packages/backend/src/server/api/endpoints/charts/active-users.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/charts/ap-request.ts b/packages/backend/src/server/api/endpoints/charts/ap-request.ts
index f518ae41ca..cbe792376b 100644
--- a/packages/backend/src/server/api/endpoints/charts/ap-request.ts
+++ b/packages/backend/src/server/api/endpoints/charts/ap-request.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/charts/drive.ts b/packages/backend/src/server/api/endpoints/charts/drive.ts
index 94afab113e..d32bc765a4 100644
--- a/packages/backend/src/server/api/endpoints/charts/drive.ts
+++ b/packages/backend/src/server/api/endpoints/charts/drive.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/charts/federation.ts b/packages/backend/src/server/api/endpoints/charts/federation.ts
index bc33930ca4..dad21e9e8e 100644
--- a/packages/backend/src/server/api/endpoints/charts/federation.ts
+++ b/packages/backend/src/server/api/endpoints/charts/federation.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/charts/instance.ts b/packages/backend/src/server/api/endpoints/charts/instance.ts
index a432845b38..68aa12ac0e 100644
--- a/packages/backend/src/server/api/endpoints/charts/instance.ts
+++ b/packages/backend/src/server/api/endpoints/charts/instance.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/charts/notes.ts b/packages/backend/src/server/api/endpoints/charts/notes.ts
index e1e9d06311..e1979cfe8b 100644
--- a/packages/backend/src/server/api/endpoints/charts/notes.ts
+++ b/packages/backend/src/server/api/endpoints/charts/notes.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/charts/user/drive.ts b/packages/backend/src/server/api/endpoints/charts/user/drive.ts
index b4a58c9872..dcb72084b7 100644
--- a/packages/backend/src/server/api/endpoints/charts/user/drive.ts
+++ b/packages/backend/src/server/api/endpoints/charts/user/drive.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/charts/user/following.ts b/packages/backend/src/server/api/endpoints/charts/user/following.ts
index c609c5a7fe..0a019ce4fb 100644
--- a/packages/backend/src/server/api/endpoints/charts/user/following.ts
+++ b/packages/backend/src/server/api/endpoints/charts/user/following.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/charts/user/notes.ts b/packages/backend/src/server/api/endpoints/charts/user/notes.ts
index ad6a342fb7..06b15bca18 100644
--- a/packages/backend/src/server/api/endpoints/charts/user/notes.ts
+++ b/packages/backend/src/server/api/endpoints/charts/user/notes.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/charts/user/pv.ts b/packages/backend/src/server/api/endpoints/charts/user/pv.ts
index 635a403d12..d359b491e2 100644
--- a/packages/backend/src/server/api/endpoints/charts/user/pv.ts
+++ b/packages/backend/src/server/api/endpoints/charts/user/pv.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/charts/user/reactions.ts b/packages/backend/src/server/api/endpoints/charts/user/reactions.ts
index 92bc7028ad..4355aa5348 100644
--- a/packages/backend/src/server/api/endpoints/charts/user/reactions.ts
+++ b/packages/backend/src/server/api/endpoints/charts/user/reactions.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/charts/users.ts b/packages/backend/src/server/api/endpoints/charts/users.ts
index 3be3721e3a..1f5f5fea54 100644
--- a/packages/backend/src/server/api/endpoints/charts/users.ts
+++ b/packages/backend/src/server/api/endpoints/charts/users.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/clips/add-note.ts b/packages/backend/src/server/api/endpoints/clips/add-note.ts
index 749593aa65..d7c9ea3964 100644
--- a/packages/backend/src/server/api/endpoints/clips/add-note.ts
+++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/clips/create.ts b/packages/backend/src/server/api/endpoints/clips/create.ts
index b4c7b52e72..ceebc8ba5e 100644
--- a/packages/backend/src/server/api/endpoints/clips/create.ts
+++ b/packages/backend/src/server/api/endpoints/clips/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/clips/delete.ts b/packages/backend/src/server/api/endpoints/clips/delete.ts
index 239945e8a4..ca8ff2e1f1 100644
--- a/packages/backend/src/server/api/endpoints/clips/delete.ts
+++ b/packages/backend/src/server/api/endpoints/clips/delete.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/clips/favorite.ts b/packages/backend/src/server/api/endpoints/clips/favorite.ts
index 015b2cfa85..11f8ec3e92 100644
--- a/packages/backend/src/server/api/endpoints/clips/favorite.ts
+++ b/packages/backend/src/server/api/endpoints/clips/favorite.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -62,7 +62,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				throw new ApiError(meta.errors.noSuchClip);
 			}
 
-			const exist = await this.clipFavoritesRepository.exist({
+			const exist = await this.clipFavoritesRepository.exists({
 				where: {
 					clipId: clip.id,
 					userId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/clips/list.ts b/packages/backend/src/server/api/endpoints/clips/list.ts
index c124762e33..2e4a3ff820 100644
--- a/packages/backend/src/server/api/endpoints/clips/list.ts
+++ b/packages/backend/src/server/api/endpoints/clips/list.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/clips/my-favorites.ts b/packages/backend/src/server/api/endpoints/clips/my-favorites.ts
index c58c16e25f..44719592d1 100644
--- a/packages/backend/src/server/api/endpoints/clips/my-favorites.ts
+++ b/packages/backend/src/server/api/endpoints/clips/my-favorites.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/clips/notes.ts b/packages/backend/src/server/api/endpoints/clips/notes.ts
index 1427d8d0a7..943c31c894 100644
--- a/packages/backend/src/server/api/endpoints/clips/notes.ts
+++ b/packages/backend/src/server/api/endpoints/clips/notes.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/clips/remove-note.ts b/packages/backend/src/server/api/endpoints/clips/remove-note.ts
index 7b153cb555..33f9ecd25b 100644
--- a/packages/backend/src/server/api/endpoints/clips/remove-note.ts
+++ b/packages/backend/src/server/api/endpoints/clips/remove-note.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/clips/show.ts b/packages/backend/src/server/api/endpoints/clips/show.ts
index 03b1e09dfb..1078a1b176 100644
--- a/packages/backend/src/server/api/endpoints/clips/show.ts
+++ b/packages/backend/src/server/api/endpoints/clips/show.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/clips/unfavorite.ts b/packages/backend/src/server/api/endpoints/clips/unfavorite.ts
index d1007f7a19..a458fda4a0 100644
--- a/packages/backend/src/server/api/endpoints/clips/unfavorite.ts
+++ b/packages/backend/src/server/api/endpoints/clips/unfavorite.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/clips/update.ts b/packages/backend/src/server/api/endpoints/clips/update.ts
index 0b9878578c..3b44ba81b3 100644
--- a/packages/backend/src/server/api/endpoints/clips/update.ts
+++ b/packages/backend/src/server/api/endpoints/clips/update.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/drive.ts b/packages/backend/src/server/api/endpoints/drive.ts
index 71d3ca5f14..7e9b0fa0e1 100644
--- a/packages/backend/src/server/api/endpoints/drive.ts
+++ b/packages/backend/src/server/api/endpoints/drive.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/drive/files.ts b/packages/backend/src/server/api/endpoints/drive/files.ts
index b7e9d12e94..10c521332d 100644
--- a/packages/backend/src/server/api/endpoints/drive/files.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -36,7 +36,7 @@ export const paramDef = {
 		untilId: { type: 'string', format: 'misskey:id' },
 		folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
 		type: { type: 'string', nullable: true, pattern: /^[a-zA-Z\/\-*]+$/.toString().slice(1, -1) },
-		sort: { type: 'string', nullable: true, enum: ['+createdAt', '-createdAt', '+name', '-name', '+size', '-size'] },
+		sort: { type: 'string', nullable: true, enum: ['+createdAt', '-createdAt', '+name', '-name', '+size', '-size', null] },
 	},
 	required: [],
 } as const;
diff --git a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts
index 14a13b09c9..4670392025 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -74,7 +74,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			}
 
 			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId);
-			query.andWhere(':file = ANY(note.fileIds)', { file: file.id });
+			query.andWhere(':file <@ note.fileIds', { file: [file.id] });
 
 			const notes = await query.limit(ps.limit).getMany();
 
diff --git a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts
index 85e6312b6a..cc7920505f 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -38,7 +38,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		private driveFilesRepository: DriveFilesRepository,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const exist = await this.driveFilesRepository.exist({
+			const exist = await this.driveFilesRepository.exists({
 				where: {
 					md5: ps.md5,
 					userId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts
index 5e97588c99..9c17f93ab2 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/create.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/drive/files/delete.ts b/packages/backend/src/server/api/endpoints/drive/files/delete.ts
index f46bf49965..fa6e11da49 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/delete.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/delete.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts
index 7b784f253e..090cff6875 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/drive/files/find.ts b/packages/backend/src/server/api/endpoints/drive/files/find.ts
index 0ceb31e58d..595a6957b2 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/find.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/find.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/drive/files/show.ts b/packages/backend/src/server/api/endpoints/drive/files/show.ts
index 474c7f02d3..e8f4539d61 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/show.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/show.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts
index c96e1a1aac..5541018126 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/update.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts
index 1e66035b5c..49d2e78d08 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/drive/folders.ts b/packages/backend/src/server/api/endpoints/drive/folders.ts
index 3a09266591..8c4848f8e1 100644
--- a/packages/backend/src/server/api/endpoints/drive/folders.ts
+++ b/packages/backend/src/server/api/endpoints/drive/folders.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/drive/folders/create.ts b/packages/backend/src/server/api/endpoints/drive/folders/create.ts
index d18199f19b..c94070d9ff 100644
--- a/packages/backend/src/server/api/endpoints/drive/folders/create.ts
+++ b/packages/backend/src/server/api/endpoints/drive/folders/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts
index 46a00ca3dc..85d63873a4 100644
--- a/packages/backend/src/server/api/endpoints/drive/folders/delete.ts
+++ b/packages/backend/src/server/api/endpoints/drive/folders/delete.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/drive/folders/find.ts b/packages/backend/src/server/api/endpoints/drive/folders/find.ts
index 2f5cdcc648..eb45a30bc0 100644
--- a/packages/backend/src/server/api/endpoints/drive/folders/find.ts
+++ b/packages/backend/src/server/api/endpoints/drive/folders/find.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/drive/folders/show.ts b/packages/backend/src/server/api/endpoints/drive/folders/show.ts
index dd44fc46c9..a1c0df6697 100644
--- a/packages/backend/src/server/api/endpoints/drive/folders/show.ts
+++ b/packages/backend/src/server/api/endpoints/drive/folders/show.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/drive/folders/update.ts b/packages/backend/src/server/api/endpoints/drive/folders/update.ts
index f8683132b2..52b8b335b5 100644
--- a/packages/backend/src/server/api/endpoints/drive/folders/update.ts
+++ b/packages/backend/src/server/api/endpoints/drive/folders/update.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/drive/stream.ts b/packages/backend/src/server/api/endpoints/drive/stream.ts
index 27e1656f82..f7c1ed39b5 100644
--- a/packages/backend/src/server/api/endpoints/drive/stream.ts
+++ b/packages/backend/src/server/api/endpoints/drive/stream.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/email-address/available.ts b/packages/backend/src/server/api/endpoints/email-address/available.ts
index 787009f13c..1d7dacd60e 100644
--- a/packages/backend/src/server/api/endpoints/email-address/available.ts
+++ b/packages/backend/src/server/api/endpoints/email-address/available.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/emoji.ts b/packages/backend/src/server/api/endpoints/emoji.ts
index ead8c9979e..ccfbda0d44 100644
--- a/packages/backend/src/server/api/endpoints/emoji.ts
+++ b/packages/backend/src/server/api/endpoints/emoji.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/emojis.ts b/packages/backend/src/server/api/endpoints/emojis.ts
index 2adf0a21b3..46ef4eca1b 100644
--- a/packages/backend/src/server/api/endpoints/emojis.ts
+++ b/packages/backend/src/server/api/endpoints/emojis.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/endpoint.ts b/packages/backend/src/server/api/endpoints/endpoint.ts
index 66ac8f664f..fe7e9c36f3 100644
--- a/packages/backend/src/server/api/endpoints/endpoint.ts
+++ b/packages/backend/src/server/api/endpoints/endpoint.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/endpoints.ts b/packages/backend/src/server/api/endpoints/endpoints.ts
index 86def04aca..4aedf62a84 100644
--- a/packages/backend/src/server/api/endpoints/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints/endpoints.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/export-custom-emojis.ts b/packages/backend/src/server/api/endpoints/export-custom-emojis.ts
index 7380c593e3..5ff099524d 100644
--- a/packages/backend/src/server/api/endpoints/export-custom-emojis.ts
+++ b/packages/backend/src/server/api/endpoints/export-custom-emojis.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/federation/followers.ts b/packages/backend/src/server/api/endpoints/federation/followers.ts
index a92cf6a9d8..ce4dd13067 100644
--- a/packages/backend/src/server/api/endpoints/federation/followers.ts
+++ b/packages/backend/src/server/api/endpoints/federation/followers.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/federation/following.ts b/packages/backend/src/server/api/endpoints/federation/following.ts
index d72ceeeea2..1a793889c7 100644
--- a/packages/backend/src/server/api/endpoints/federation/following.ts
+++ b/packages/backend/src/server/api/endpoints/federation/following.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts
index 617ca65733..4064a415a4 100644
--- a/packages/backend/src/server/api/endpoints/federation/instances.ts
+++ b/packages/backend/src/server/api/endpoints/federation/instances.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -41,6 +41,7 @@ export const paramDef = {
 		subscribing: { type: 'boolean', nullable: true },
 		publishing: { type: 'boolean', nullable: true },
 		nsfw: { type: 'boolean', nullable: true },
+		bubble: { type: 'boolean', nullable: true },
 		limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 },
 		offset: { type: 'integer', default: 0 },
 		sort: {
@@ -61,6 +62,7 @@ export const paramDef = {
 				'-firstRetrievedAt',
 				'+latestRequestReceivedAt',
 				'-latestRequestReceivedAt',
+				null,
 			],
 		},
 	},
@@ -98,6 +100,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				default: query.orderBy('instance.id', 'DESC'); break;
 			}
 
+			if (me == null) {
+				ps.blocked = false;
+				ps.suspended = false;
+				ps.silenced = false;
+			}
+
 			if (typeof ps.blocked === 'boolean') {
 				const meta = await this.metaService.fetch(true);
 				if (ps.blocked) {
@@ -148,6 +156,23 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				}
 			}
 
+			if (typeof ps.bubble === 'boolean') {
+				const meta = await this.metaService.fetch(true);
+
+				if (ps.bubble) {
+					if (meta.bubbleInstances.length === 0) {
+						return [];
+					}
+					query.andWhere('instance.host IN (:...bubble)', {
+						bubble: meta.bubbleInstances,
+					});
+				} else if (meta.bubbleInstances.length > 0) {
+					query.andWhere('instance.host NOT IN (:...bubble)', {
+						bubble: meta.bubbleInstances,
+					});
+				}
+			}
+
 			if (typeof ps.federating === 'boolean') {
 				if (ps.federating) {
 					query.andWhere('((instance.followingCount > 0) OR (instance.followersCount > 0))');
diff --git a/packages/backend/src/server/api/endpoints/federation/show-instance.ts b/packages/backend/src/server/api/endpoints/federation/show-instance.ts
index 781c15e742..2972861a4b 100644
--- a/packages/backend/src/server/api/endpoints/federation/show-instance.ts
+++ b/packages/backend/src/server/api/endpoints/federation/show-instance.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -43,7 +43,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			const instance = await this.instancesRepository
 				.findOneBy({ host: this.utilityService.toPuny(ps.host) });
 
-			return instance ? await this.instanceEntityService.pack(instance) : null;
+			return instance ? await this.instanceEntityService.pack(instance, me) : null;
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/federation/stats.ts b/packages/backend/src/server/api/endpoints/federation/stats.ts
index 262aa68776..bac54970ab 100644
--- a/packages/backend/src/server/api/endpoints/federation/stats.ts
+++ b/packages/backend/src/server/api/endpoints/federation/stats.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts
index e6198ff601..f8430ef431 100644
--- a/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts
+++ b/packages/backend/src/server/api/endpoints/federation/update-remote-user.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/federation/users.ts b/packages/backend/src/server/api/endpoints/federation/users.ts
index d97171865a..71b1aeb07b 100644
--- a/packages/backend/src/server/api/endpoints/federation/users.ts
+++ b/packages/backend/src/server/api/endpoints/federation/users.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -54,7 +54,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				.limit(ps.limit)
 				.getMany();
 
-			return await this.userEntityService.packMany(users, me, { detail: true });
+			return await this.userEntityService.packMany(users, me, { schema: 'UserDetailedNotMe' });
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/fetch-external-resources.ts b/packages/backend/src/server/api/endpoints/fetch-external-resources.ts
index cbe579eb6b..f36136d53b 100644
--- a/packages/backend/src/server/api/endpoints/fetch-external-resources.ts
+++ b/packages/backend/src/server/api/endpoints/fetch-external-resources.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/fetch-rss.ts b/packages/backend/src/server/api/endpoints/fetch-rss.ts
index b2dee83fe9..2085b06365 100644
--- a/packages/backend/src/server/api/endpoints/fetch-rss.ts
+++ b/packages/backend/src/server/api/endpoints/fetch-rss.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/flash/create.ts b/packages/backend/src/server/api/endpoints/flash/create.ts
index 674f323734..584d167a29 100644
--- a/packages/backend/src/server/api/endpoints/flash/create.ts
+++ b/packages/backend/src/server/api/endpoints/flash/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/flash/delete.ts b/packages/backend/src/server/api/endpoints/flash/delete.ts
index e5448c816a..d3d47e5deb 100644
--- a/packages/backend/src/server/api/endpoints/flash/delete.ts
+++ b/packages/backend/src/server/api/endpoints/flash/delete.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/flash/featured.ts b/packages/backend/src/server/api/endpoints/flash/featured.ts
index 1fa5612ac4..c2d6ab5085 100644
--- a/packages/backend/src/server/api/endpoints/flash/featured.ts
+++ b/packages/backend/src/server/api/endpoints/flash/featured.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/flash/like.ts b/packages/backend/src/server/api/endpoints/flash/like.ts
index 1003249c0c..e4dc5b61c5 100644
--- a/packages/backend/src/server/api/endpoints/flash/like.ts
+++ b/packages/backend/src/server/api/endpoints/flash/like.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -70,7 +70,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			}
 
 			// if already liked
-			const exist = await this.flashLikesRepository.exist({
+			const exist = await this.flashLikesRepository.exists({
 				where: {
 					flashId: flash.id,
 					userId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/flash/my-likes.ts b/packages/backend/src/server/api/endpoints/flash/my-likes.ts
index e328bdbee5..755cc5acfc 100644
--- a/packages/backend/src/server/api/endpoints/flash/my-likes.ts
+++ b/packages/backend/src/server/api/endpoints/flash/my-likes.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/flash/my.ts b/packages/backend/src/server/api/endpoints/flash/my.ts
index 442d8dcd75..5746096232 100644
--- a/packages/backend/src/server/api/endpoints/flash/my.ts
+++ b/packages/backend/src/server/api/endpoints/flash/my.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/flash/show.ts b/packages/backend/src/server/api/endpoints/flash/show.ts
index c41a27c925..a6fbd8e76e 100644
--- a/packages/backend/src/server/api/endpoints/flash/show.ts
+++ b/packages/backend/src/server/api/endpoints/flash/show.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/flash/unlike.ts b/packages/backend/src/server/api/endpoints/flash/unlike.ts
index d5c20a1167..7869bcdf52 100644
--- a/packages/backend/src/server/api/endpoints/flash/unlike.ts
+++ b/packages/backend/src/server/api/endpoints/flash/unlike.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/flash/update.ts b/packages/backend/src/server/api/endpoints/flash/update.ts
index 8b5e1f99e9..e378669f0a 100644
--- a/packages/backend/src/server/api/endpoints/flash/update.ts
+++ b/packages/backend/src/server/api/endpoints/flash/update.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -51,7 +51,7 @@ export const paramDef = {
 		} },
 		visibility: { type: 'string', enum: ['public', 'private'] },
 	},
-	required: ['flashId', 'title', 'summary', 'script', 'permissions'],
+	required: ['flashId'],
 } as const;
 
 @Injectable()
@@ -71,11 +71,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			await this.flashsRepository.update(flash.id, {
 				updatedAt: new Date(),
-				title: ps.title,
-				summary: ps.summary,
-				script: ps.script,
-				permissions: ps.permissions,
-				visibility: ps.visibility,
+				...Object.fromEntries(
+					Object.entries(ps).filter(
+						([key, val]) => (key !== 'flashId') && Object.hasOwn(paramDef.properties, key)
+					)
+				),
 			});
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts
index 9037944ef9..db320e7129 100644
--- a/packages/backend/src/server/api/endpoints/following/create.ts
+++ b/packages/backend/src/server/api/endpoints/following/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -71,7 +71,7 @@ export const paramDef = {
 	type: 'object',
 	properties: {
 		userId: { type: 'string', format: 'misskey:id' },
-		withReplies: { type: 'boolean' }
+		withReplies: { type: 'boolean' },
 	},
 	required: ['userId'],
 } as const;
@@ -100,22 +100,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				throw err;
 			});
 
-			// Check if already following
-			const exist = await this.followingsRepository.exist({
-				where: {
-					followerId: follower.id,
-					followeeId: followee.id,
-				},
-			});
-
-			if (exist) {
-				throw new ApiError(meta.errors.alreadyFollowing);
-			}
-
 			try {
 				await this.userFollowingService.follow(follower, followee, { withReplies: ps.withReplies });
 			} catch (e) {
 				if (e instanceof IdentifiableError) {
+					if (e.id === 'ec3f65c0-a9d1-47d9-8791-b2e7b9dcdced') throw new ApiError(meta.errors.alreadyFollowing);
 					if (e.id === '710e8fb0-b8c3-4922-be49-d5d93d8e6a6e') throw new ApiError(meta.errors.blocking);
 					if (e.id === '3338392a-f764-498d-8855-db939dcf8c48') throw new ApiError(meta.errors.blocked);
 				}
diff --git a/packages/backend/src/server/api/endpoints/following/delete.ts b/packages/backend/src/server/api/endpoints/following/delete.ts
index f44692ba6d..ba146b6703 100644
--- a/packages/backend/src/server/api/endpoints/following/delete.ts
+++ b/packages/backend/src/server/api/endpoints/following/delete.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -85,7 +85,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			});
 
 			// Check not following
-			const exist = await this.followingsRepository.exist({
+			const exist = await this.followingsRepository.exists({
 				where: {
 					followerId: follower.id,
 					followeeId: followee.id,
diff --git a/packages/backend/src/server/api/endpoints/following/invalidate.ts b/packages/backend/src/server/api/endpoints/following/invalidate.ts
index 53ef925b2f..8935c2c2da 100644
--- a/packages/backend/src/server/api/endpoints/following/invalidate.ts
+++ b/packages/backend/src/server/api/endpoints/following/invalidate.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/following/requests/accept.ts b/packages/backend/src/server/api/endpoints/following/requests/accept.ts
index 91fe922200..2d1446681c 100644
--- a/packages/backend/src/server/api/endpoints/following/requests/accept.ts
+++ b/packages/backend/src/server/api/endpoints/following/requests/accept.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/following/requests/cancel.ts b/packages/backend/src/server/api/endpoints/following/requests/cancel.ts
index d9d5c7041b..6d663d480c 100644
--- a/packages/backend/src/server/api/endpoints/following/requests/cancel.ts
+++ b/packages/backend/src/server/api/endpoints/following/requests/cancel.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/following/requests/list.ts b/packages/backend/src/server/api/endpoints/following/requests/list.ts
index c4faa88f65..88f559138b 100644
--- a/packages/backend/src/server/api/endpoints/following/requests/list.ts
+++ b/packages/backend/src/server/api/endpoints/following/requests/list.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/following/requests/reject.ts b/packages/backend/src/server/api/endpoints/following/requests/reject.ts
index 35f047bcef..4f78eae677 100644
--- a/packages/backend/src/server/api/endpoints/following/requests/reject.ts
+++ b/packages/backend/src/server/api/endpoints/following/requests/reject.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/following/update-all.ts b/packages/backend/src/server/api/endpoints/following/update-all.ts
index 28734cfdbd..c953feb393 100644
--- a/packages/backend/src/server/api/endpoints/following/update-all.ts
+++ b/packages/backend/src/server/api/endpoints/following/update-all.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/following/update.ts b/packages/backend/src/server/api/endpoints/following/update.ts
index db17d151df..d62cf210ed 100644
--- a/packages/backend/src/server/api/endpoints/following/update.ts
+++ b/packages/backend/src/server/api/endpoints/following/update.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/gallery/featured.ts b/packages/backend/src/server/api/endpoints/gallery/featured.ts
index cea4234065..7d2878e03f 100644
--- a/packages/backend/src/server/api/endpoints/gallery/featured.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/featured.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/gallery/popular.ts b/packages/backend/src/server/api/endpoints/gallery/popular.ts
index c5d06f67dd..4ee252104a 100644
--- a/packages/backend/src/server/api/endpoints/gallery/popular.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/popular.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts.ts b/packages/backend/src/server/api/endpoints/gallery/posts.ts
index 3ca5f4989a..d398418ab4 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts
index 71e0ad4141..b07cdf1ed9 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -12,6 +12,7 @@ import type { MiDriveFile } from '@/models/DriveFile.js';
 import { IdService } from '@/core/IdService.js';
 import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
 import { DI } from '@/di-symbols.js';
+import { isNotNull } from '@/misc/is-not-null.js';
 
 export const meta = {
 	tags: ['gallery'],
@@ -69,7 +70,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 					id: fileId,
 					userId: me.id,
 				}),
-			))).filter((file): file is MiDriveFile => file != null);
+			))).filter(isNotNull);
 
 			if (files.length === 0) {
 				throw new Error();
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts
index deef2912bb..527e3fb52d 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/delete.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts
index cc424261b4..91e49e6463 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/like.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/like.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -72,7 +72,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			}
 
 			// if already liked
-			const exist = await this.galleryLikesRepository.exist({
+			const exist = await this.galleryLikesRepository.exists({
 				where: {
 					postId: post.id,
 					userId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/show.ts b/packages/backend/src/server/api/endpoints/gallery/posts/show.ts
index b3eda1be52..bd69898229 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/show.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/show.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts
index caa4d45553..f44e2c7afc 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/unlike.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts
index 632214a0c2..8bd83ff5ba 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -10,6 +10,7 @@ import type { DriveFilesRepository, GalleryPostsRepository } from '@/models/_.js
 import type { MiDriveFile } from '@/models/DriveFile.js';
 import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
 import { DI } from '@/di-symbols.js';
+import { isNotNull } from '@/misc/is-not-null.js';
 
 export const meta = {
 	tags: ['gallery'],
@@ -67,7 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 					id: fileId,
 					userId: me.id,
 				}),
-			))).filter((file): file is MiDriveFile => file != null);
+			))).filter(isNotNull);
 
 			if (files.length === 0) {
 				throw new Error();
diff --git a/packages/backend/src/server/api/endpoints/get-avatar-decorations.ts b/packages/backend/src/server/api/endpoints/get-avatar-decorations.ts
index dbe1626149..52acee1cfb 100644
--- a/packages/backend/src/server/api/endpoints/get-avatar-decorations.ts
+++ b/packages/backend/src/server/api/endpoints/get-avatar-decorations.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/get-online-users-count.ts b/packages/backend/src/server/api/endpoints/get-online-users-count.ts
index 737d637b7e..a57774be73 100644
--- a/packages/backend/src/server/api/endpoints/get-online-users-count.ts
+++ b/packages/backend/src/server/api/endpoints/get-online-users-count.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/hashtags/list.ts b/packages/backend/src/server/api/endpoints/hashtags/list.ts
index 21d863107d..5cd3c6584d 100644
--- a/packages/backend/src/server/api/endpoints/hashtags/list.ts
+++ b/packages/backend/src/server/api/endpoints/hashtags/list.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/hashtags/search.ts b/packages/backend/src/server/api/endpoints/hashtags/search.ts
index acfef16b11..d4eb851054 100644
--- a/packages/backend/src/server/api/endpoints/hashtags/search.ts
+++ b/packages/backend/src/server/api/endpoints/hashtags/search.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -43,7 +43,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		super(meta, paramDef, async (ps, me) => {
 			const hashtags = await this.hashtagsRepository.createQueryBuilder('tag')
 				.where('tag.name like :q', { q: sqlLikeEscape(ps.query.toLowerCase()) + '%' })
-				.orderBy('tag.count', 'DESC')
+				.orderBy('tag.mentionedLocalUsersCount', 'DESC')
 				.groupBy('tag.id')
 				.limit(ps.limit)
 				.offset(ps.offset)
diff --git a/packages/backend/src/server/api/endpoints/hashtags/show.ts b/packages/backend/src/server/api/endpoints/hashtags/show.ts
index 3ba16fdc85..940e3bd69d 100644
--- a/packages/backend/src/server/api/endpoints/hashtags/show.ts
+++ b/packages/backend/src/server/api/endpoints/hashtags/show.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/hashtags/trend.ts b/packages/backend/src/server/api/endpoints/hashtags/trend.ts
index 8f382eb96b..cb8065e3a6 100644
--- a/packages/backend/src/server/api/endpoints/hashtags/trend.ts
+++ b/packages/backend/src/server/api/endpoints/hashtags/trend.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/hashtags/users.ts b/packages/backend/src/server/api/endpoints/hashtags/users.ts
index 50aea79943..30f0c1b0c8 100644
--- a/packages/backend/src/server/api/endpoints/hashtags/users.ts
+++ b/packages/backend/src/server/api/endpoints/hashtags/users.ts
@@ -1,11 +1,12 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import { Inject, Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import type { UsersRepository } from '@/models/_.js';
+import { safeForSql } from "@/misc/safe-for-sql.js";
 import { normalizeForSearch } from '@/misc/normalize-for-search.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { DI } from '@/di-symbols.js';
@@ -47,8 +48,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		private userEntityService: UserEntityService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
+			if (!safeForSql(normalizeForSearch(ps.tag))) throw new Error('Injection');
 			const query = this.usersRepository.createQueryBuilder('user')
-				.where(':tag = ANY(user.tags)', { tag: normalizeForSearch(ps.tag) })
+				.where(':tag <@ user.tags', { tag: [normalizeForSearch(ps.tag)] })
 				.andWhere('user.isSuspended = FALSE');
 
 			const recent = new Date(Date.now() - (1000 * 60 * 60 * 24 * 5));
@@ -74,7 +76,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			const users = await query.limit(ps.limit).getMany();
 
-			return await this.userEntityService.packMany(users, me, { detail: true });
+			return await this.userEntityService.packMany(users, me, { schema: 'UserDetailed' });
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/i.ts b/packages/backend/src/server/api/endpoints/i.ts
index c24e049180..d324e3e64a 100644
--- a/packages/backend/src/server/api/endpoints/i.ts
+++ b/packages/backend/src/server/api/endpoints/i.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -71,8 +71,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				userProfile.loggedInDates = [...userProfile.loggedInDates, today];
 			}
 
-			return await this.userEntityService.pack<true, true>(userProfile.user!, userProfile.user!, {
-				detail: true,
+			return await this.userEntityService.pack(userProfile.user!, userProfile.user!, {
+				schema: 'MeDetailed',
 				includeSecrets: isSecure,
 				userProfile,
 			});
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/done.ts b/packages/backend/src/server/api/endpoints/i/2fa/done.ts
index 9f8e2894b8..2a30e8b0c3 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/done.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/done.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -15,6 +15,19 @@ export const meta = {
 	requireCredential: true,
 
 	secure: true,
+
+	res: {
+		type: 'object',
+		properties: {
+			backupCodes: {
+				type: 'array',
+				optional: false,
+				items: {
+					type: 'string',
+				},
+			},
+		},
+	},
 } as const;
 
 export const paramDef = {
@@ -64,7 +77,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			// Publish meUpdated event
 			this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, {
-				detail: true,
+				schema: 'MeDetailed',
 				includeSecrets: true,
 			}));
 
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts
index 4161553d28..74ee90b3dd 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/key-done.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -112,7 +112,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 
 			// Publish meUpdated event
 			this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, {
-				detail: true,
+				schema: 'MeDetailed',
 				includeSecrets: true,
 			}));
 
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts
index 2ed701014d..bf039ccd16 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/password-less.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -74,7 +74,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			// Publish meUpdated event
 			this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, {
-				detail: true,
+				schema: 'MeDetailed',
 				includeSecrets: true,
 			}));
 		});
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts
index 325d54d196..cc6e9ee42d 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/register-key.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -48,7 +48,7 @@ export const meta = {
 				properties: {
 					id: {
 						type: 'string',
-						nullable: true,
+						optional: true,
 					},
 				},
 			},
@@ -104,13 +104,13 @@ export const meta = {
 							items: {
 								type: 'string',
 								enum: [
-									"ble",
-									"cable",
-									"hybrid",
-									"internal",
-									"nfc",
-									"smart-card",
-									"usb",
+									'ble',
+									'cable',
+									'hybrid',
+									'internal',
+									'nfc',
+									'smart-card',
+									'usb',
 								],
 							},
 						},
@@ -124,8 +124,8 @@ export const meta = {
 					authenticatorAttachment: {
 						type: 'string',
 						enum: [
-							"cross-platform",
-							"platform",
+							'cross-platform',
+							'platform',
 						],
 					},
 					requireResidentKey: {
@@ -134,9 +134,9 @@ export const meta = {
 					userVerification: {
 						type: 'string',
 						enum: [
-							"discouraged",
-							"preferred",
-							"required",
+							'discouraged',
+							'preferred',
+							'required',
 						],
 					},
 				},
@@ -145,10 +145,11 @@ export const meta = {
 				type: 'string',
 				nullable: true,
 				enum: [
-					"direct",
-					"enterprise",
-					"indirect",
-					"none",
+					'direct',
+					'enterprise',
+					'indirect',
+					'none',
+					null,
 				],
 			},
 			extensions: {
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/register.ts b/packages/backend/src/server/api/endpoints/i/2fa/register.ts
index 15e50c49f3..7283159f87 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/register.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/register.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts
index 21e848fb5c..098fd59303 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/remove-key.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -99,7 +99,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			// Publish meUpdated event
 			this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, {
-				detail: true,
+				schema: 'MeDetailed',
 				includeSecrets: true,
 			}));
 
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts
index 8dd880c9fa..8da331505b 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/unregister.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -77,7 +77,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			// Publish meUpdated event
 			this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, {
-				detail: true,
+				schema: 'MeDetailed',
 				includeSecrets: true,
 			}));
 		});
diff --git a/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts b/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts
index 7056ec5a58..deb56a3ac4 100644
--- a/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts
+++ b/packages/backend/src/server/api/endpoints/i/2fa/update-key.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -69,7 +69,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			// Publish meUpdated event
 			this.globalEventService.publishMainStream(me.id, 'meUpdated', await this.userEntityService.pack(me.id, me, {
-				detail: true,
+				schema: 'MeDetailed',
 				includeSecrets: true,
 			}));
 
diff --git a/packages/backend/src/server/api/endpoints/i/apps.ts b/packages/backend/src/server/api/endpoints/i/apps.ts
index ef89f93181..91c8597b1b 100644
--- a/packages/backend/src/server/api/endpoints/i/apps.ts
+++ b/packages/backend/src/server/api/endpoints/i/apps.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -21,26 +21,31 @@ export const meta = {
 			properties: {
 				id: {
 					type: 'string',
+					optional: false,
 					format: 'misskey:id',
 				},
 				name: {
 					type: 'string',
+					optional: true,
 				},
 				createdAt: {
 					type: 'string',
+					optional: false,
 					format: 'date-time',
 				},
 				lastUsedAt: {
 					type: 'string',
+					optional: true,
 					format: 'date-time',
 				},
 				permission: {
 					type: 'array',
+					optional: false,
 					uniqueItems: true,
 					items: {
-						type: 'string'
+						type: 'string',
 					},
-				}
+				},
 			},
 		},
 	},
diff --git a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts
index a0ed371fb8..0b4faf5ef8 100644
--- a/packages/backend/src/server/api/endpoints/i/authorized-apps.ts
+++ b/packages/backend/src/server/api/endpoints/i/authorized-apps.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -23,23 +23,27 @@ export const meta = {
 				id: {
 					type: 'string',
 					format: 'misskey:id',
+					optional: false,
 				},
 				name: {
 					type: 'string',
+					optional: false,
 				},
 				callbackUrl: {
 					type: 'string',
-					nullable: true,
+					optional: false, nullable: true,
 				},
 				permission: {
 					type: 'array',
+					optional: false,
 					uniqueItems: true,
 					items: {
-						type: 'string'
+						type: 'string',
 					},
 				},
 				isAuthorized: {
 					type: 'boolean',
+					optional: true,
 				},
 			},
 		},
diff --git a/packages/backend/src/server/api/endpoints/i/change-password.ts b/packages/backend/src/server/api/endpoints/i/change-password.ts
index fb0f2bc88e..6aedde717c 100644
--- a/packages/backend/src/server/api/endpoints/i/change-password.ts
+++ b/packages/backend/src/server/api/endpoints/i/change-password.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/claim-achievement.ts b/packages/backend/src/server/api/endpoints/i/claim-achievement.ts
index 3580d6ba1b..73231e8e09 100644
--- a/packages/backend/src/server/api/endpoints/i/claim-achievement.ts
+++ b/packages/backend/src/server/api/endpoints/i/claim-achievement.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/delete-account.ts b/packages/backend/src/server/api/endpoints/i/delete-account.ts
index e0b40db917..af4d601ad6 100644
--- a/packages/backend/src/server/api/endpoints/i/delete-account.ts
+++ b/packages/backend/src/server/api/endpoints/i/delete-account.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/export-antennas.ts b/packages/backend/src/server/api/endpoints/i/export-antennas.ts
index 23b2f6b4ce..77fb4a895f 100644
--- a/packages/backend/src/server/api/endpoints/i/export-antennas.ts
+++ b/packages/backend/src/server/api/endpoints/i/export-antennas.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/export-blocking.ts b/packages/backend/src/server/api/endpoints/i/export-blocking.ts
index 8068a3b305..7573018bec 100644
--- a/packages/backend/src/server/api/endpoints/i/export-blocking.ts
+++ b/packages/backend/src/server/api/endpoints/i/export-blocking.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/export-clips.ts b/packages/backend/src/server/api/endpoints/i/export-clips.ts
new file mode 100644
index 0000000000..10d1fdac73
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/i/export-clips.ts
@@ -0,0 +1,35 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import ms from 'ms';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { QueueService } from '@/core/QueueService.js';
+
+export const meta = {
+	secure: true,
+	requireCredential: true,
+	limit: {
+		duration: ms('1day'),
+		max: 1,
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		private queueService: QueueService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			this.queueService.createExportClipsJob(me);
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/i/export-favorites.ts b/packages/backend/src/server/api/endpoints/i/export-favorites.ts
index c22905bc67..5e03f70170 100644
--- a/packages/backend/src/server/api/endpoints/i/export-favorites.ts
+++ b/packages/backend/src/server/api/endpoints/i/export-favorites.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/export-following.ts b/packages/backend/src/server/api/endpoints/i/export-following.ts
index 880833ab76..2e5ba14737 100644
--- a/packages/backend/src/server/api/endpoints/i/export-following.ts
+++ b/packages/backend/src/server/api/endpoints/i/export-following.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/export-mute.ts b/packages/backend/src/server/api/endpoints/i/export-mute.ts
index 8eb70a387a..0384cf142b 100644
--- a/packages/backend/src/server/api/endpoints/i/export-mute.ts
+++ b/packages/backend/src/server/api/endpoints/i/export-mute.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/export-notes.ts b/packages/backend/src/server/api/endpoints/i/export-notes.ts
index 791f637790..db4e78f667 100644
--- a/packages/backend/src/server/api/endpoints/i/export-notes.ts
+++ b/packages/backend/src/server/api/endpoints/i/export-notes.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/export-user-lists.ts b/packages/backend/src/server/api/endpoints/i/export-user-lists.ts
index f387f6d016..6cd662102c 100644
--- a/packages/backend/src/server/api/endpoints/i/export-user-lists.ts
+++ b/packages/backend/src/server/api/endpoints/i/export-user-lists.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/favorites.ts b/packages/backend/src/server/api/endpoints/i/favorites.ts
index d6f13c535a..3558035eca 100644
--- a/packages/backend/src/server/api/endpoints/i/favorites.ts
+++ b/packages/backend/src/server/api/endpoints/i/favorites.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts
index 7e37adc4ac..d492585ffa 100644
--- a/packages/backend/src/server/api/endpoints/i/gallery/likes.ts
+++ b/packages/backend/src/server/api/endpoints/i/gallery/likes.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts
index 148d38aa54..73a6fcc98b 100644
--- a/packages/backend/src/server/api/endpoints/i/gallery/posts.ts
+++ b/packages/backend/src/server/api/endpoints/i/gallery/posts.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/import-antennas.ts b/packages/backend/src/server/api/endpoints/i/import-antennas.ts
index 71db8710af..b4661a93e2 100644
--- a/packages/backend/src/server/api/endpoints/i/import-antennas.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-antennas.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -71,7 +71,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		private downloadService: DownloadService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const userExist = await this.usersRepository.exist({ where: { id: me.id } });
+			const userExist = await this.usersRepository.exists({ where: { id: me.id } });
 			if (!userExist) throw new ApiError(meta.errors.noSuchUser);
 			const file = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
 			if (file === null) throw new ApiError(meta.errors.noSuchFile);
diff --git a/packages/backend/src/server/api/endpoints/i/import-blocking.ts b/packages/backend/src/server/api/endpoints/i/import-blocking.ts
index 965ad30547..8ddbe5663e 100644
--- a/packages/backend/src/server/api/endpoints/i/import-blocking.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-blocking.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/import-following.ts b/packages/backend/src/server/api/endpoints/i/import-following.ts
index e5fa2ac96a..390dd9cd71 100644
--- a/packages/backend/src/server/api/endpoints/i/import-following.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-following.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/import-muting.ts b/packages/backend/src/server/api/endpoints/i/import-muting.ts
index 926cf13d7f..51a9cdf5a5 100644
--- a/packages/backend/src/server/api/endpoints/i/import-muting.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-muting.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts
index 2167996435..a3b67301a7 100644
--- a/packages/backend/src/server/api/endpoints/i/import-user-lists.ts
+++ b/packages/backend/src/server/api/endpoints/i/import-user-lists.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/move.ts b/packages/backend/src/server/api/endpoints/i/move.ts
index f3ba720c2b..1bd641232c 100644
--- a/packages/backend/src/server/api/endpoints/i/move.ts
+++ b/packages/backend/src/server/api/endpoints/i/move.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/notifications-grouped.ts b/packages/backend/src/server/api/endpoints/i/notifications-grouped.ts
index 4ea94b07f6..ad4577be58 100644
--- a/packages/backend/src/server/api/endpoints/i/notifications-grouped.ts
+++ b/packages/backend/src/server/api/endpoints/i/notifications-grouped.ts
@@ -1,13 +1,13 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { Brackets, In } from 'typeorm';
+import { In } from 'typeorm';
 import * as Redis from 'ioredis';
 import { Inject, Injectable } from '@nestjs/common';
 import type { NotesRepository } from '@/models/_.js';
-import { obsoleteNotificationTypes, notificationTypes, FilterUnionByProperty } from '@/types.js';
+import { obsoleteNotificationTypes, groupedNotificationTypes, FilterUnionByProperty } from '@/types.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { NoteReadService } from '@/core/NoteReadService.js';
 import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js';
@@ -48,10 +48,10 @@ export const paramDef = {
 		markAsRead: { type: 'boolean', default: true },
 		// 後方互換のため、廃止された通知タイプも受け付ける
 		includeTypes: { type: 'array', items: {
-			type: 'string', enum: [...notificationTypes, ...obsoleteNotificationTypes],
+			type: 'string', enum: [...groupedNotificationTypes, ...obsoleteNotificationTypes],
 		} },
 		excludeTypes: { type: 'array', items: {
-			type: 'string', enum: [...notificationTypes, ...obsoleteNotificationTypes],
+			type: 'string', enum: [...groupedNotificationTypes, ...obsoleteNotificationTypes],
 		} },
 	},
 	required: [],
@@ -79,12 +79,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				return [];
 			}
 			// excludeTypes に全指定されている場合はクエリしない
-			if (notificationTypes.every(type => ps.excludeTypes?.includes(type))) {
+			if (groupedNotificationTypes.every(type => ps.excludeTypes?.includes(type))) {
 				return [];
 			}
 
-			const includeTypes = ps.includeTypes && ps.includeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof notificationTypes[number][];
-			const excludeTypes = ps.excludeTypes && ps.excludeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof notificationTypes[number][];
+			const includeTypes = ps.includeTypes && ps.includeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof groupedNotificationTypes[number][];
+			const excludeTypes = ps.excludeTypes && ps.excludeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof groupedNotificationTypes[number][];
 
 			const limit = (ps.limit + EXTRA_LIMIT) + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1
 			const notificationsRes = await this.redisClient.xrevrange(
@@ -162,9 +162,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			}
 
 			groupedNotifications = groupedNotifications.slice(0, ps.limit);
-
 			const noteIds = groupedNotifications
-				.filter((notification): notification is FilterUnionByProperty<MiNotification, 'type', 'mention' | 'reply' | 'quote'> => ['mention', 'reply', 'quote'].includes(notification.type))
+				.filter((notification): notification is FilterUnionByProperty<MiNotification, 'type', 'mention' | 'reply' | 'quote' | 'edited'> => ['mention', 'reply', 'quote', 'edited'].includes(notification.type))
 				.map(notification => notification.noteId!);
 
 			if (noteIds.length > 0) {
diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts
index 039fd9454c..594e8b95c8 100644
--- a/packages/backend/src/server/api/endpoints/i/notifications.ts
+++ b/packages/backend/src/server/api/endpoints/i/notifications.ts
@@ -1,9 +1,9 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { Brackets, In } from 'typeorm';
+import { In } from 'typeorm';
 import * as Redis from 'ioredis';
 import { Inject, Injectable } from '@nestjs/common';
 import type { NotesRepository } from '@/models/_.js';
@@ -113,7 +113,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			}
 
 			const noteIds = notifications
-				.filter((notification): notification is FilterUnionByProperty<MiNotification, 'type', 'mention' | 'reply' | 'quote'> => ['mention', 'reply', 'quote'].includes(notification.type))
+				.filter((notification): notification is FilterUnionByProperty<MiNotification, 'type', 'mention' | 'reply' | 'quote' | 'edited'> => ['mention', 'reply', 'quote', 'edited'].includes(notification.type))
 				.map(notification => notification.noteId);
 
 			if (noteIds.length > 0) {
diff --git a/packages/backend/src/server/api/endpoints/i/page-likes.ts b/packages/backend/src/server/api/endpoints/i/page-likes.ts
index 6bf7e6aa9b..d4c09426a7 100644
--- a/packages/backend/src/server/api/endpoints/i/page-likes.ts
+++ b/packages/backend/src/server/api/endpoints/i/page-likes.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/pages.ts b/packages/backend/src/server/api/endpoints/i/pages.ts
index b8082c018f..1b6359a633 100644
--- a/packages/backend/src/server/api/endpoints/i/pages.ts
+++ b/packages/backend/src/server/api/endpoints/i/pages.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/pin.ts b/packages/backend/src/server/api/endpoints/i/pin.ts
index c89cdfa3a4..b7cafd74df 100644
--- a/packages/backend/src/server/api/endpoints/i/pin.ts
+++ b/packages/backend/src/server/api/endpoints/i/pin.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -66,8 +66,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				throw err;
 			});
 
-			return await this.userEntityService.pack<true, true>(me.id, me, {
-				detail: true,
+			return await this.userEntityService.pack(me.id, me, {
+				schema: 'MeDetailed',
 			});
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts b/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts
index e43ab7c15e..d1a8eccb1d 100644
--- a/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts
+++ b/packages/backend/src/server/api/endpoints/i/read-all-unread-notes.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/read-announcement.ts b/packages/backend/src/server/api/endpoints/i/read-announcement.ts
index ba7859d0d4..4db1ca73c1 100644
--- a/packages/backend/src/server/api/endpoints/i/read-announcement.ts
+++ b/packages/backend/src/server/api/endpoints/i/read-announcement.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts
index 92295beeee..e1cdfdc185 100644
--- a/packages/backend/src/server/api/endpoints/i/regenerate-token.ts
+++ b/packages/backend/src/server/api/endpoints/i/regenerate-token.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-all.ts b/packages/backend/src/server/api/endpoints/i/registry/get-all.ts
index 79a81cb73f..f1797cfde7 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/get-all.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/get-all.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts
index d9b26cab2c..d53c390460 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/get-detail.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -22,7 +22,16 @@ export const meta = {
 
 	res: {
 		type: 'object',
-	}
+		properties: {
+			updatedAt: {
+				type: 'string',
+				optional: false,
+			},
+			value: {
+				optional: false,
+			},
+		},
+	},
 } as const;
 
 export const paramDef = {
@@ -50,7 +59,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			}
 
 			return {
-				updatedAt: item.updatedAt,
+				updatedAt: item.updatedAt.toISOString(),
 				value: item.value,
 			};
 		});
diff --git a/packages/backend/src/server/api/endpoints/i/registry/get.ts b/packages/backend/src/server/api/endpoints/i/registry/get.ts
index c373410256..d9a8fdd449 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/get.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/get.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts b/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts
index a91dcd9543..3fe339606d 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/keys-with-type.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -13,6 +13,9 @@ export const meta = {
 
 	res: {
 		type: 'object',
+		additionalProperties: {
+			type: 'string',
+		},
 	},
 } as const;
 
diff --git a/packages/backend/src/server/api/endpoints/i/registry/keys.ts b/packages/backend/src/server/api/endpoints/i/registry/keys.ts
index ad203d5203..28f158c62d 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/keys.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/keys.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -10,6 +10,13 @@ import { RegistryApiService } from '@/core/RegistryApiService.js';
 export const meta = {
 	requireCredential: true,
 	kind: 'read:account',
+
+	res: {
+		type: 'array',
+		items: {
+			type: 'string',
+		},
+	},
 } as const;
 
 export const paramDef = {
diff --git a/packages/backend/src/server/api/endpoints/i/registry/remove.ts b/packages/backend/src/server/api/endpoints/i/registry/remove.ts
index 9cbe271b91..cf965ba0cf 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/remove.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/remove.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/registry/scopes-with-domain.ts b/packages/backend/src/server/api/endpoints/i/registry/scopes-with-domain.ts
index 0aca2a26fe..67a99b028a 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/scopes-with-domain.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/scopes-with-domain.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/registry/set.ts b/packages/backend/src/server/api/endpoints/i/registry/set.ts
index c61d5b8727..8723035d84 100644
--- a/packages/backend/src/server/api/endpoints/i/registry/set.ts
+++ b/packages/backend/src/server/api/endpoints/i/registry/set.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/revoke-token.ts b/packages/backend/src/server/api/endpoints/i/revoke-token.ts
index 98d866f867..c05ee93c6f 100644
--- a/packages/backend/src/server/api/endpoints/i/revoke-token.ts
+++ b/packages/backend/src/server/api/endpoints/i/revoke-token.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -34,7 +34,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			if (ps.tokenId) {
-				const tokenExist = await this.accessTokensRepository.exist({ where: { id: ps.tokenId } });
+				const tokenExist = await this.accessTokensRepository.exists({ where: { id: ps.tokenId } });
 
 				if (tokenExist) {
 					await this.accessTokensRepository.delete({
@@ -43,7 +43,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 					});
 				}
 			} else if (ps.token) {
-				const tokenExist = await this.accessTokensRepository.exist({ where: { token: ps.token } });
+				const tokenExist = await this.accessTokensRepository.exists({ where: { token: ps.token } });
 
 				if (tokenExist) {
 					await this.accessTokensRepository.delete({
diff --git a/packages/backend/src/server/api/endpoints/i/signin-history.ts b/packages/backend/src/server/api/endpoints/i/signin-history.ts
index f82e3f9b28..76ad0bbe21 100644
--- a/packages/backend/src/server/api/endpoints/i/signin-history.ts
+++ b/packages/backend/src/server/api/endpoints/i/signin-history.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/unpin.ts b/packages/backend/src/server/api/endpoints/i/unpin.ts
index b59c0e954f..74825cf9f3 100644
--- a/packages/backend/src/server/api/endpoints/i/unpin.ts
+++ b/packages/backend/src/server/api/endpoints/i/unpin.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -51,8 +51,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				throw err;
 			});
 
-			return await this.userEntityService.pack<true, true>(me.id, me, {
-				detail: true,
+			return await this.userEntityService.pack(me.id, me, {
+				schema: 'MeDetailed',
 			});
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts
index 090b07be3c..08a8301bd1 100644
--- a/packages/backend/src/server/api/endpoints/i/update-email.ts
+++ b/packages/backend/src/server/api/endpoints/i/update-email.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -44,7 +44,7 @@ export const meta = {
 
 	res: {
 		type: 'object',
-		ref: 'UserDetailed',
+		ref: 'MeDetailed',
 	},
 } as const;
 
@@ -107,7 +107,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			});
 
 			const iObj = await this.userEntityService.pack(me.id, me, {
-				detail: true,
+				schema: 'MeDetailed',
 				includeSecrets: true,
 			});
 
diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts
index 22079de042..06edb28578 100644
--- a/packages/backend/src/server/api/endpoints/i/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/update.ts
@@ -1,10 +1,10 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import RE2 from 're2';
-import * as mfm from '@sharkey/sfm-js';
+import * as mfm from '@transfem-org/sfm-js';
 import { Inject, Injectable } from '@nestjs/common';
 import ms from 'ms';
 import { JSDOM } from 'jsdom';
@@ -33,6 +33,7 @@ import { HttpRequestService } from '@/core/HttpRequestService.js';
 import type { Config } from '@/config.js';
 import { safeForSql } from '@/misc/safe-for-sql.js';
 import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
+import { notificationRecieveConfig } from '@/models/json-schema/user.js';
 import { ApiLoggerService } from '../../ApiLoggerService.js';
 import { ApiError } from '../../error.js';
 
@@ -200,7 +201,26 @@ export const paramDef = {
 		mutedInstances: { type: 'array', items: {
 			type: 'string',
 		} },
-		notificationRecieveConfig: { type: 'object' },
+		notificationRecieveConfig: {
+			type: 'object',
+			nullable: false,
+			properties: {
+				note: notificationRecieveConfig,
+				follow: notificationRecieveConfig,
+				mention: notificationRecieveConfig,
+				reply: notificationRecieveConfig,
+				renote: notificationRecieveConfig,
+				quote: notificationRecieveConfig,
+				reaction: notificationRecieveConfig,
+				pollEnded: notificationRecieveConfig,
+				receiveFollowRequest: notificationRecieveConfig,
+				followRequestAccepted: notificationRecieveConfig,
+				roleAssigned: notificationRecieveConfig,
+				achievementEarned: notificationRecieveConfig,
+				app: notificationRecieveConfig,
+				test: notificationRecieveConfig,
+			},
+		},
 		emailNotificationTypes: { type: 'array', items: {
 			type: 'string',
 		} },
@@ -468,9 +488,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			this.hashtagService.updateUsertags(user, tags);
 			//#endregion
 
-			if (Object.keys(updates).length > 0) await this.usersRepository.update(user.id, updates);
-			if (Object.keys(updates).includes('alsoKnownAs')) {
-				this.cacheService.uriPersonCache.set(this.userEntityService.genLocalUserUri(user.id), { ...user, ...updates });
+			if (Object.keys(updates).length > 0) {
+				await this.usersRepository.update(user.id, updates);
+				this.globalEventService.publishInternalEvent('localUserUpdated', { id: user.id });
 			}
 
 			await this.userProfilesRepository.update(user.id, {
@@ -478,8 +498,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				verifiedLinks: [],
 			});
 
-			const iObj = await this.userEntityService.pack<true, true>(user.id, user, {
-				detail: true,
+			const iObj = await this.userEntityService.pack(user.id, user, {
+				schema: 'MeDetailed',
 				includeSecrets: isSecure,
 			});
 
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts
index bdc9f9ea8b..535a3ea308 100644
--- a/packages/backend/src/server/api/endpoints/i/webhooks/create.ts
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -33,7 +33,7 @@ export const meta = {
 		properties: {
 			id: {
 				type: 'string',
-				format: 'misskey:id'
+				format: 'misskey:id',
 			},
 			userId: {
 				type: 'string',
@@ -45,7 +45,7 @@ export const meta = {
 				items: {
 					type: 'string',
 					enum: webhookEventTypes,
-				}
+				},
 			},
 			url: { type: 'string' },
 			secret: { type: 'string' },
@@ -108,7 +108,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				url: webhook.url,
 				secret: webhook.secret,
 				active: webhook.active,
-				latestSentAt: webhook.latestSentAt?.toISOString(),
+				latestSentAt: webhook.latestSentAt ? webhook.latestSentAt.toISOString() : null,
 				latestStatus: webhook.latestStatus,
 			};
 		});
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts b/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts
index db7d0db13c..1b1ac00670 100644
--- a/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/delete.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts
index afb2d0509e..fe07afb2d0 100644
--- a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -23,7 +23,7 @@ export const meta = {
 			properties: {
 				id: {
 					type: 'string',
-					format: 'misskey:id'
+					format: 'misskey:id',
 				},
 				userId: {
 					type: 'string',
@@ -35,7 +35,7 @@ export const meta = {
 					items: {
 						type: 'string',
 						enum: webhookEventTypes,
-					}
+					},
 				},
 				url: { type: 'string' },
 				secret: { type: 'string' },
@@ -43,8 +43,8 @@ export const meta = {
 				latestSentAt: { type: 'string', format: 'date-time', nullable: true },
 				latestStatus: { type: 'integer', nullable: true },
 			},
-		}
-	}
+		},
+	},
 } as const;
 
 export const paramDef = {
@@ -73,7 +73,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 					url: webhook.url,
 					secret: webhook.secret,
 					active: webhook.active,
-					latestSentAt: webhook.latestSentAt?.toISOString(),
+					latestSentAt: webhook.latestSentAt ? webhook.latestSentAt.toISOString() : null,
 					latestStatus: webhook.latestStatus,
 				}
 			));
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts
index 5c6dd908b4..5ddb79caf2 100644
--- a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -30,7 +30,7 @@ export const meta = {
 		properties: {
 			id: {
 				type: 'string',
-				format: 'misskey:id'
+				format: 'misskey:id',
 			},
 			userId: {
 				type: 'string',
@@ -42,7 +42,7 @@ export const meta = {
 				items: {
 					type: 'string',
 					enum: webhookEventTypes,
-				}
+				},
 			},
 			url: { type: 'string' },
 			secret: { type: 'string' },
@@ -85,7 +85,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				url: webhook.url,
 				secret: webhook.secret,
 				active: webhook.active,
-				latestSentAt: webhook.latestSentAt?.toISOString(),
+				latestSentAt: webhook.latestSentAt ? webhook.latestSentAt.toISOString() : null,
 				latestStatus: webhook.latestStatus,
 			};
 		});
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/update.ts b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts
index b3e000524d..6e380d76f8 100644
--- a/packages/backend/src/server/api/endpoints/i/webhooks/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/update.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/invite/create.ts b/packages/backend/src/server/api/endpoints/invite/create.ts
index 4f37f2f4bb..0ff125ad9c 100644
--- a/packages/backend/src/server/api/endpoints/invite/create.ts
+++ b/packages/backend/src/server/api/endpoints/invite/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/invite/delete.ts b/packages/backend/src/server/api/endpoints/invite/delete.ts
index d84430a49f..e960ff9f4e 100644
--- a/packages/backend/src/server/api/endpoints/invite/delete.ts
+++ b/packages/backend/src/server/api/endpoints/invite/delete.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/invite/limit.ts b/packages/backend/src/server/api/endpoints/invite/limit.ts
index fc3bb9bdc2..2786bd98d5 100644
--- a/packages/backend/src/server/api/endpoints/invite/limit.ts
+++ b/packages/backend/src/server/api/endpoints/invite/limit.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/invite/list.ts b/packages/backend/src/server/api/endpoints/invite/list.ts
index 6734f27e14..23aefe83a2 100644
--- a/packages/backend/src/server/api/endpoints/invite/list.ts
+++ b/packages/backend/src/server/api/endpoints/invite/list.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts
index 1d0c102c9d..5460635e1d 100644
--- a/packages/backend/src/server/api/endpoints/meta.ts
+++ b/packages/backend/src/server/api/endpoints/meta.ts
@@ -1,18 +1,11 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { IsNull, LessThanOrEqual, MoreThan, Brackets } from 'typeorm';
-import { Inject, Injectable } from '@nestjs/common';
-import JSON5 from 'json5';
-import type { AdsRepository, UsersRepository } from '@/models/_.js';
+import { Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
-import { UserEntityService } from '@/core/entities/UserEntityService.js';
-import { MetaService } from '@/core/MetaService.js';
-import type { Config } from '@/config.js';
-import { DI } from '@/di-symbols.js';
-import { DEFAULT_POLICIES } from '@/core/RoleService.js';
+import { MetaEntityService } from '@/core/entities/MetaEntityService.js';
 
 export const meta = {
 	tags: ['meta'],
@@ -21,284 +14,10 @@ export const meta = {
 
 	res: {
 		type: 'object',
-		optional: false, nullable: false,
-		properties: {
-			maintainerName: {
-				type: 'string',
-				optional: false, nullable: true,
-			},
-			maintainerEmail: {
-				type: 'string',
-				optional: false, nullable: true,
-			},
-			version: {
-				type: 'string',
-				optional: false, nullable: false,
-			},
-			name: {
-				type: 'string',
-				optional: false, nullable: false,
-			},
-			shortName: {
-				type: 'string',
-				optional: false, nullable: true,
-			},
-			uri: {
-				type: 'string',
-				optional: false, nullable: false,
-				format: 'url',
-				example: 'https://misskey.example.com',
-			},
-			description: {
-				type: 'string',
-				optional: false, nullable: true,
-			},
-			langs: {
-				type: 'array',
-				optional: false, nullable: false,
-				items: {
-					type: 'string',
-					optional: false, nullable: false,
-				},
-			},
-			tosUrl: {
-				type: 'string',
-				optional: false, nullable: true,
-			},
-			repositoryUrl: {
-				type: 'string',
-				optional: false, nullable: false,
-				default: 'https://github.com/misskey-dev/misskey',
-			},
-			feedbackUrl: {
-				type: 'string',
-				optional: false, nullable: false,
-				default: 'https://github.com/misskey-dev/misskey/issues/new',
-			},
-			defaultDarkTheme: {
-				type: 'string',
-				optional: false, nullable: true,
-			},
-			defaultLightTheme: {
-				type: 'string',
-				optional: false, nullable: true,
-			},
-			defaultLike: {
-				type: 'string',
-				optional: false, nullable: true,
-			},
-			disableRegistration: {
-				type: 'boolean',
-				optional: false, nullable: false,
-			},
-			cacheRemoteFiles: {
-				type: 'boolean',
-				optional: false, nullable: false,
-			},
-			cacheRemoteSensitiveFiles: {
-				type: 'boolean',
-				optional: false, nullable: false,
-			},
-			emailRequiredForSignup: {
-				type: 'boolean',
-				optional: false, nullable: false,
-			},
-			approvalRequiredForSignup: {
-				type: 'boolean',
-				optional: false, nullable: false,
-			},
-			enableHcaptcha: {
-				type: 'boolean',
-				optional: false, nullable: false,
-			},
-			hcaptchaSiteKey: {
-				type: 'string',
-				optional: false, nullable: true,
-			},
-			enableRecaptcha: {
-				type: 'boolean',
-				optional: false, nullable: false,
-			},
-			recaptchaSiteKey: {
-				type: 'string',
-				optional: false, nullable: true,
-			},
-			enableTurnstile: {
-				type: 'boolean',
-				optional: false, nullable: false,
-			},
-			turnstileSiteKey: {
-				type: 'string',
-				optional: false, nullable: true,
-			},
-			swPublickey: {
-				type: 'string',
-				optional: false, nullable: true,
-			},
-			mascotImageUrl: {
-				type: 'string',
-				optional: false, nullable: false,
-				default: '/assets/ai.png',
-			},
-			bannerUrl: {
-				type: 'string',
-				optional: false, nullable: false,
-			},
-			serverErrorImageUrl: {
-				type: 'string',
-				optional: false, nullable: true,
-			},
-			infoImageUrl: {
-				type: 'string',
-				optional: false, nullable: true,
-			},
-			notFoundImageUrl: {
-				type: 'string',
-				optional: false, nullable: true,
-			},
-			iconUrl: {
-				type: 'string',
-				optional: false, nullable: true,
-			},
-			maxNoteTextLength: {
-				type: 'number',
-				optional: false, nullable: false,
-			},
-			ads: {
-				type: 'array',
-				optional: false, nullable: false,
-				items: {
-					type: 'object',
-					optional: false, nullable: false,
-					properties: {
-						id: {
-							type: 'string',
-							optional: false, nullable: false,
-							format: 'id',
-							example: 'xxxxxxxxxx',
-						},
-						url: {
-							type: 'string',
-							optional: false, nullable: false,
-							format: 'url',
-						},
-						place: {
-							type: 'string',
-							optional: false, nullable: false,
-						},
-						ratio: {
-							type: 'number',
-							optional: false, nullable: false,
-						},
-						imageUrl: {
-							type: 'string',
-							optional: false, nullable: false,
-							format: 'url',
-						},
-						dayOfWeek: {
-							type: 'integer',
-							optional: false, nullable: false,
-						},
-					},
-				},
-			},
-			notesPerOneAd: {
-				type: 'number',
-				optional: false, nullable: false,
-				default: 0,
-			},
-			requireSetup: {
-				type: 'boolean',
-				optional: false, nullable: false,
-				example: false,
-			},
-			enableEmail: {
-				type: 'boolean',
-				optional: false, nullable: false,
-			},
-			enableServiceWorker: {
-				type: 'boolean',
-				optional: false, nullable: false,
-			},
-			translatorAvailable: {
-				type: 'boolean',
-				optional: false, nullable: false,
-			},
-			proxyAccountName: {
-				type: 'string',
-				optional: false, nullable: true,
-			},
-			mediaProxy: {
-				type: 'string',
-				optional: false, nullable: false,
-			},
-			features: {
-				type: 'object',
-				optional: true, nullable: false,
-				properties: {
-					registration: {
-						type: 'boolean',
-						optional: false, nullable: false,
-					},
-					localTimeline: {
-						type: 'boolean',
-						optional: false, nullable: false,
-					},
-					globalTimeline: {
-						type: 'boolean',
-						optional: false, nullable: false,
-					},
-					hcaptcha: {
-						type: 'boolean',
-						optional: false, nullable: false,
-					},
-					recaptcha: {
-						type: 'boolean',
-						optional: false, nullable: false,
-					},
-					objectStorage: {
-						type: 'boolean',
-						optional: false, nullable: false,
-					},
-					serviceWorker: {
-						type: 'boolean',
-						optional: false, nullable: false,
-					},
-					miauth: {
-						type: 'boolean',
-						optional: true, nullable: false,
-						default: true,
-					},
-				},
-			},
-			backgroundImageUrl: {
-				type: 'string',
-				optional: false, nullable: true,
-			},
-			impressumUrl: {
-				type: 'string',
-				optional: false, nullable: true,
-			},
-			logoImageUrl: {
-				type: 'string',
-				optional: false, nullable: true,
-			},
-			privacyPolicyUrl: {
-				type: 'string',
-				optional: false, nullable: true,
-			},
-			serverRules: {
-				type: 'array',
-				optional: false, nullable: false,
-				items: {
-					type: 'string',
-				},
-			},
-			themeColor: {
-				type: 'string',
-				optional: false, nullable: true,
-			},
-		},
+		oneOf: [
+			{ type: 'object', ref: 'MetaLite' },
+			{ type: 'object', ref: 'MetaDetailed' },
+		],
 	},
 } as const;
 
@@ -313,118 +32,10 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
-		@Inject(DI.config)
-		private config: Config,
-
-		@Inject(DI.usersRepository)
-		private usersRepository: UsersRepository,
-
-		@Inject(DI.adsRepository)
-		private adsRepository: AdsRepository,
-
-		private userEntityService: UserEntityService,
-		private metaService: MetaService,
+		private metaEntityService: MetaEntityService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const instance = await this.metaService.fetch(true);
-
-			const ads = await this.adsRepository.createQueryBuilder('ads')
-				.where('ads.expiresAt > :now', { now: new Date() })
-				.andWhere('ads.startsAt <= :now', { now: new Date() })
-				.andWhere(new Brackets(qb => {
-					// 曜日のビットフラグを確認する
-					qb.where('ads.dayOfWeek & :dayOfWeek > 0', { dayOfWeek: 1 << new Date().getDay() })
-						.orWhere('ads.dayOfWeek = 0');
-				}))
-				.getMany();
-
-			const response: any = {
-				maintainerName: instance.maintainerName,
-				maintainerEmail: instance.maintainerEmail,
-
-				version: this.config.version,
-
-				name: instance.name,
-				shortName: instance.shortName,
-				uri: this.config.url,
-				description: instance.description,
-				langs: instance.langs,
-				tosUrl: instance.termsOfServiceUrl,
-				repositoryUrl: instance.repositoryUrl,
-				feedbackUrl: instance.feedbackUrl,
-				impressumUrl: instance.impressumUrl,
-				privacyPolicyUrl: instance.privacyPolicyUrl,
-				disableRegistration: instance.disableRegistration,
-				emailRequiredForSignup: instance.emailRequiredForSignup,
-				approvalRequiredForSignup: instance.approvalRequiredForSignup,
-				enableHcaptcha: instance.enableHcaptcha,
-				hcaptchaSiteKey: instance.hcaptchaSiteKey,
-				enableRecaptcha: instance.enableRecaptcha,
-				enableAchievements: instance.enableAchievements,
-				recaptchaSiteKey: instance.recaptchaSiteKey,
-				enableTurnstile: instance.enableTurnstile,
-				turnstileSiteKey: instance.turnstileSiteKey,
-				swPublickey: instance.swPublicKey,
-				themeColor: instance.themeColor,
-				mascotImageUrl: instance.mascotImageUrl,
-				bannerUrl: instance.bannerUrl,
-				infoImageUrl: instance.infoImageUrl,
-				serverErrorImageUrl: instance.serverErrorImageUrl,
-				notFoundImageUrl: instance.notFoundImageUrl,
-				iconUrl: instance.iconUrl,
-				backgroundImageUrl: instance.backgroundImageUrl,
-				logoImageUrl: instance.logoImageUrl,
-				maxNoteTextLength: this.config.maxNoteLength,
-				// クライアントの手間を減らすためあらかじめJSONに変換しておく
-				defaultLightTheme: instance.defaultLightTheme ? JSON.stringify(JSON5.parse(instance.defaultLightTheme)) : null,
-				defaultDarkTheme: instance.defaultDarkTheme ? JSON.stringify(JSON5.parse(instance.defaultDarkTheme)) : null,
-				defaultLike: instance.defaultLike,
-				ads: ads.map(ad => ({
-					id: ad.id,
-					url: ad.url,
-					place: ad.place,
-					ratio: ad.ratio,
-					imageUrl: ad.imageUrl,
-					dayOfWeek: ad.dayOfWeek,
-				})),
-				notesPerOneAd: instance.notesPerOneAd,
-				enableEmail: instance.enableEmail,
-				enableServiceWorker: instance.enableServiceWorker,
-
-				translatorAvailable: instance.deeplAuthKey != null,
-
-				serverRules: instance.serverRules,
-
-				policies: { ...DEFAULT_POLICIES, ...instance.policies },
-
-				mediaProxy: this.config.mediaProxy,
-
-				...(ps.detail ? {
-					cacheRemoteFiles: instance.cacheRemoteFiles,
-					cacheRemoteSensitiveFiles: instance.cacheRemoteSensitiveFiles,
-					requireSetup: (await this.usersRepository.countBy({
-						host: IsNull(),
-					})) === 0,
-				} : {}),
-			};
-
-			if (ps.detail) {
-				const proxyAccount = instance.proxyAccountId ? await this.userEntityService.pack(instance.proxyAccountId).catch(() => null) : null;
-
-				response.proxyAccountName = proxyAccount ? proxyAccount.username : null;
-				response.features = {
-					registration: !instance.disableRegistration,
-					emailRequiredForSignup: instance.emailRequiredForSignup,
-					hcaptcha: instance.enableHcaptcha,
-					recaptcha: instance.enableRecaptcha,
-					turnstile: instance.enableTurnstile,
-					objectStorage: instance.useObjectStorage,
-					serviceWorker: instance.enableServiceWorker,
-					miauth: true,
-				};
-			}
-
-			return response;
+			return ps.detail ? await this.metaEntityService.packDetailed() : await this.metaEntityService.pack();
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts
index cac8f41f8e..fc9a8f3ebe 100644
--- a/packages/backend/src/server/api/endpoints/miauth/gen-token.ts
+++ b/packages/backend/src/server/api/endpoints/miauth/gen-token.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/mute/create.ts b/packages/backend/src/server/api/endpoints/mute/create.ts
index 49c2b5707d..e39c133b43 100644
--- a/packages/backend/src/server/api/endpoints/mute/create.ts
+++ b/packages/backend/src/server/api/endpoints/mute/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -83,7 +83,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			});
 
 			// Check if already muting
-			const exist = await this.mutingsRepository.exist({
+			const exist = await this.mutingsRepository.exists({
 				where: {
 					muterId: muter.id,
 					muteeId: mutee.id,
diff --git a/packages/backend/src/server/api/endpoints/mute/delete.ts b/packages/backend/src/server/api/endpoints/mute/delete.ts
index a3fd2dd82f..d11832858e 100644
--- a/packages/backend/src/server/api/endpoints/mute/delete.ts
+++ b/packages/backend/src/server/api/endpoints/mute/delete.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/mute/list.ts b/packages/backend/src/server/api/endpoints/mute/list.ts
index 2a41182ebc..23204f2829 100644
--- a/packages/backend/src/server/api/endpoints/mute/list.ts
+++ b/packages/backend/src/server/api/endpoints/mute/list.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/my/apps.ts b/packages/backend/src/server/api/endpoints/my/apps.ts
index 1b70b85b07..c04a92626f 100644
--- a/packages/backend/src/server/api/endpoints/my/apps.ts
+++ b/packages/backend/src/server/api/endpoints/my/apps.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/notes.ts b/packages/backend/src/server/api/endpoints/notes.ts
index 95ba5e8b64..9938322a2a 100644
--- a/packages/backend/src/server/api/endpoints/notes.ts
+++ b/packages/backend/src/server/api/endpoints/notes.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/notes/children.ts b/packages/backend/src/server/api/endpoints/notes/children.ts
index a16740c816..2654e196b2 100644
--- a/packages/backend/src/server/api/endpoints/notes/children.ts
+++ b/packages/backend/src/server/api/endpoints/notes/children.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -74,7 +74,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			this.queryService.generateVisibilityQuery(query, me);
 			if (me) {
-				this.queryService.generateMutedUserQuery(query, me);
 				this.queryService.generateBlockedUserQuery(query, me);
 			}
 
diff --git a/packages/backend/src/server/api/endpoints/notes/clips.ts b/packages/backend/src/server/api/endpoints/notes/clips.ts
index 677c0ea307..29cab9f212 100644
--- a/packages/backend/src/server/api/endpoints/notes/clips.ts
+++ b/packages/backend/src/server/api/endpoints/notes/clips.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/notes/conversation.ts b/packages/backend/src/server/api/endpoints/notes/conversation.ts
index bb22ee4907..37bc5cc878 100644
--- a/packages/backend/src/server/api/endpoints/notes/conversation.ts
+++ b/packages/backend/src/server/api/endpoints/notes/conversation.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/notes/create.test.ts b/packages/backend/src/server/api/endpoints/notes/create.test.ts
index 0de5a14a93..f3d887bb20 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.test.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.test.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -34,19 +34,18 @@ describe('api:notes/create', () => {
 					.toBe(VALID);
 			});
 
-			// TODO
-			//test('null post', () => {
-			//	expect(v({ text: null }))
-			//		.toBe(INVALID);
-			//});
+			test('null post', () => {
+				expect(v({ text: null }))
+					.toBe(INVALID);
+			});
 
 			test('0 characters post', () => {
 				expect(v({ text: '' }))
 					.toBe(INVALID);
 			});
 
-			test('over 3000 characters post', async () => {
-				expect(v({ text: await tooLong }))
+			test('whitespace-only post', () => {
+				expect(v({ text: ' ' }))
 					.toBe(INVALID);
 			});
 		});
diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index ac0a7f3b51..95ebda2f21 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -17,6 +17,9 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import { NoteCreateService } from '@/core/NoteCreateService.js';
 import { DI } from '@/di-symbols.js';
 import { isPureRenote } from '@/misc/is-pure-renote.js';
+import { MetaService } from '@/core/MetaService.js';
+import { UtilityService } from '@/core/UtilityService.js';
+import { IdentifiableError } from '@/misc/identifiable-error.js';
 import { ApiError } from '../../error.js';
 
 export const meta = {
@@ -88,6 +91,12 @@ export const meta = {
 			id: '3ac74a84-8fd5-4bb0-870f-01804f82ce16',
 		},
 
+		cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility: {
+			message: 'You cannot reply to a specified visibility note with extended visibility.',
+			code: 'CANNOT_REPLY_TO_SPECIFIED_VISIBILITY_NOTE_WITH_EXTENDED_VISIBILITY',
+			id: 'ed940410-535c-4d5e-bfa3-af798671e93c',
+		},
+
 		cannotCreateAlreadyExpiredPoll: {
 			message: 'Poll is already expired.',
 			code: 'CANNOT_CREATE_ALREADY_EXPIRED_POLL',
@@ -117,6 +126,18 @@ export const meta = {
 			code: 'CANNOT_RENOTE_OUTSIDE_OF_CHANNEL',
 			id: '33510210-8452-094c-6227-4a6c05d99f00',
 		},
+
+		containsProhibitedWords: {
+			message: 'Cannot post because it contains prohibited words.',
+			code: 'CONTAINS_PROHIBITED_WORDS',
+			id: 'aa6e01d3-a85c-669d-758a-76aab43af334',
+		},
+
+		containsTooManyMentions: {
+			message: 'Cannot post because it exceeds the allowed number of mentions.',
+			code: 'CONTAINS_TOO_MANY_MENTIONS',
+			id: '4de0363a-3046-481b-9b0f-feff3e211025',
+		},
 	},
 } as const;
 
@@ -167,7 +188,7 @@ export const paramDef = {
 					uniqueItems: true,
 					minItems: 2,
 					maxItems: 10,
-					items: { type: 'string', minLength: 1, maxLength: 50 },
+					items: { type: 'string', minLength: 1, maxLength: 150 },
 				},
 				multiple: { type: 'boolean' },
 				expiresAt: { type: 'integer', nullable: true },
@@ -177,13 +198,32 @@ export const paramDef = {
 		},
 	},
 	// (re)note with text, files and poll are optional
-	anyOf: [
-		{ required: ['text'] },
-		{ required: ['renoteId'] },
-		{ required: ['fileIds'] },
-		{ required: ['mediaIds'] },
-		{ required: ['poll'] },
-	],
+	if: {
+		properties: {
+			renoteId: {
+				type: 'null',
+			},
+			fileIds: {
+				type: 'null',
+			},
+			mediaIds: {
+				type: 'null',
+			},
+			poll: {
+				type: 'null',
+			},
+		},
+	},
+	then: {
+		properties: {
+			text: {
+				type: 'string',
+				minLength: 1,
+				pattern: '[^\\s]+',
+			},
+		},
+		required: ['text'],
+	},
 } as const;
 
 @Injectable()
@@ -252,7 +292,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 				// Check blocking
 				if (renote.userId !== me.id) {
-					const blockExist = await this.blockingsRepository.exist({
+					const blockExist = await this.blockingsRepository.exists({
 						where: {
 							blockerId: renote.userId,
 							blockeeId: me.id,
@@ -295,12 +335,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				} else if (isPureRenote(reply)) {
 					throw new ApiError(meta.errors.cannotReplyToPureRenote);
 				} else if (!await this.noteEntityService.isVisibleForMe(reply, me.id)) {
-					throw new ApiError(meta.errors.noSuchReplyTarget);
+					throw new ApiError(meta.errors.cannotReplyToInvisibleNote);
+				} else if (reply.visibility === 'specified' && ps.visibility !== 'specified') {
+					throw new ApiError(meta.errors.cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility);
 				}
 
 				// Check blocking
 				if (reply.userId !== me.id) {
-					const blockExist = await this.blockingsRepository.exist({
+					const blockExist = await this.blockingsRepository.exists({
 						where: {
 							blockerId: reply.userId,
 							blockeeId: me.id,
@@ -332,31 +374,43 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			}
 
 			// 投稿を作成
-			const note = await this.noteCreateService.create(me, {
-				createdAt: new Date(),
-				files: files,
-				poll: ps.poll ? {
-					choices: ps.poll.choices,
-					multiple: ps.poll.multiple ?? false,
-					expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null,
-				} : undefined,
-				text: ps.text ?? undefined,
-				reply,
-				renote,
-				cw: ps.cw,
-				localOnly: ps.localOnly,
-				reactionAcceptance: ps.reactionAcceptance,
-				visibility: ps.visibility,
-				visibleUsers,
-				channel,
-				apMentions: ps.noExtractMentions ? [] : undefined,
-				apHashtags: ps.noExtractHashtags ? [] : undefined,
-				apEmojis: ps.noExtractEmojis ? [] : undefined,
-			});
+			try {
+				const note = await this.noteCreateService.create(me, {
+					createdAt: new Date(),
+					files: files,
+					poll: ps.poll ? {
+						choices: ps.poll.choices,
+						multiple: ps.poll.multiple ?? false,
+						expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null,
+					} : undefined,
+					text: ps.text ?? undefined,
+					reply,
+					renote,
+					cw: ps.cw,
+					localOnly: ps.localOnly,
+					reactionAcceptance: ps.reactionAcceptance,
+					visibility: ps.visibility,
+					visibleUsers,
+					channel,
+					apMentions: ps.noExtractMentions ? [] : undefined,
+					apHashtags: ps.noExtractHashtags ? [] : undefined,
+					apEmojis: ps.noExtractEmojis ? [] : undefined,
+				});
 
-			return {
-				createdNote: await this.noteEntityService.pack(note, me),
-			};
+				return {
+					createdNote: await this.noteEntityService.pack(note, me),
+				};
+			} catch (e) {
+				// TODO: 他のErrorもここでキャッチしてエラーメッセージを当てるようにしたい
+				if (e instanceof IdentifiableError) {
+					if (e.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') {
+						throw new ApiError(meta.errors.containsProhibitedWords);
+					} else if (e.id === '9f466dab-c856-48cd-9e65-ff90ff750580') {
+						throw new ApiError(meta.errors.containsTooManyMentions);
+					}
+				}
+				throw e;
+			}
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/notes/delete.ts b/packages/backend/src/server/api/endpoints/notes/delete.ts
index 55aaaf4f78..9d7c9a9081 100644
--- a/packages/backend/src/server/api/endpoints/notes/delete.ts
+++ b/packages/backend/src/server/api/endpoints/notes/delete.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/notes/edit.ts b/packages/backend/src/server/api/endpoints/notes/edit.ts
index 0c9c0d3baf..3caeda288b 100644
--- a/packages/backend/src/server/api/endpoints/notes/edit.ts
+++ b/packages/backend/src/server/api/endpoints/notes/edit.ts
@@ -11,6 +11,8 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import { NoteEditService } from '@/core/NoteEditService.js';
 import { DI } from '@/di-symbols.js';
+import { isPureRenote } from '@/misc/is-pure-renote.js';
+import { IdentifiableError } from '@/misc/identifiable-error.js';
 import { ApiError } from '../../error.js';
 
 export const meta = {
@@ -18,6 +20,8 @@ export const meta = {
 
 	requireCredential: true,
 
+	prohibitMoved: true,
+
 	limit: {
 		duration: ms('1hour'),
 		max: 300,
@@ -52,18 +56,42 @@ export const meta = {
 			id: 'fd4cc33e-2a37-48dd-99cc-9b806eb2031a',
 		},
 
+		cannotRenoteDueToVisibility: {
+			message: 'You can not Renote due to target visibility.',
+			code: 'CANNOT_RENOTE_DUE_TO_VISIBILITY',
+			id: 'be9529e9-fe72-4de0-ae43-0b363c4938af',
+		},
+
 		noSuchReplyTarget: {
 			message: 'No such reply target.',
 			code: 'NO_SUCH_REPLY_TARGET',
 			id: '749ee0f6-d3da-459a-bf02-282e2da4292c',
 		},
 
+		cannotReplyToInvisibleNote: {
+			message: 'You cannot reply to an invisible Note.',
+			code: 'CANNOT_REPLY_TO_AN_INVISIBLE_NOTE',
+			id: 'b98980fa-3780-406c-a935-b6d0eeee10d1',
+		},
+
 		cannotReplyToPureRenote: {
 			message: 'You can not reply to a pure Renote.',
 			code: 'CANNOT_REPLY_TO_A_PURE_RENOTE',
 			id: '3ac74a84-8fd5-4bb0-870f-01804f82ce15',
 		},
 
+		maxLength: {
+			message: 'You tried posting a note which is too long.',
+			code: 'MAX_LENGTH',
+			id: '3ac74a84-8fd5-4bb0-870f-01804f82ce16',
+		},
+
+		cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility: {
+			message: 'You cannot reply to a specified visibility note with extended visibility.',
+			code: 'CANNOT_REPLY_TO_SPECIFIED_VISIBILITY_NOTE_WITH_EXTENDED_VISIBILITY',
+			id: 'ed940410-535c-4d5e-bfa3-af798671e93c',
+		},
+
 		cannotCreateAlreadyExpiredPoll: {
 			message: 'Poll is already expired.',
 			code: 'CANNOT_CREATE_ALREADY_EXPIRED_POLL',
@@ -82,6 +110,12 @@ export const meta = {
 			id: 'b390d7e1-8a5e-46ed-b625-06271cafd3d3',
 		},
 
+		noSuchFile: {
+			message: 'Some files are not found.',
+			code: 'NO_SUCH_FILE',
+			id: 'b6992544-63e7-67f0-fa7f-32444b1b5306',
+		},
+
 		accountLocked: {
 			message: 'You migrated. Your account is now locked.',
 			code: 'ACCOUNT_LOCKED',
@@ -136,10 +170,16 @@ export const meta = {
 			id: '33510210-8452-094c-6227-4a6c05d99f02',
 		},
 
-		maxLength: {
-			message: 'You tried posting a note which is too long.',
-			code: 'MAX_LENGTH',
-			id: '3ac74a84-8fd5-4bb0-870f-01804f82ce16',
+		containsProhibitedWords: {
+			message: 'Cannot post because it contains prohibited words.',
+			code: 'CONTAINS_PROHIBITED_WORDS',
+			id: 'aa6e01d3-a85c-669d-758a-76aab43af334',
+		},
+
+		containsTooManyMentions: {
+			message: 'Cannot post because it exceeds the allowed number of mentions.',
+			code: 'CONTAINS_TOO_MANY_MENTIONS',
+			id: '4de0363a-3046-481b-9b0f-feff3e211025',
 		},
 	},
 } as const;
@@ -194,7 +234,7 @@ export const paramDef = {
 					uniqueItems: true,
 					minItems: 2,
 					maxItems: 10,
-					items: { type: 'string', minLength: 1, maxLength: 50 },
+					items: { type: 'string', minLength: 1, maxLength: 150 },
 				},
 				multiple: { type: 'boolean' },
 				expiresAt: { type: 'integer', nullable: true },
@@ -203,38 +243,33 @@ export const paramDef = {
 			required: ['choices'],
 		},
 	},
-	anyOf: [
-		{
-			// (re)note with text, files and poll are optional
-			properties: {
-				text: {
-					type: 'string',
-					minLength: 1,
-					nullable: false,
-				},
+	// (re)note with text, files and poll are optional
+	if: {
+		properties: {
+			renoteId: {
+				type: 'null',
 			},
-			required: ['text'],
-		},
-		{
-			// (re)note with files, text and poll are optional
-			required: ['fileIds'],
-		},
-		{
-			// (re)note with files, text and poll are optional
-			required: ['mediaIds'],
-		},
-		{
-			// (re)note with poll, text and files are optional
-			properties: {
-				poll: { type: 'object', nullable: false },
+			fileIds: {
+				type: 'null',
+			},
+			mediaIds: {
+				type: 'null',
+			},
+			poll: {
+				type: 'null',
 			},
-			required: ['poll'],
 		},
-		{
-			// pure renote
-			required: ['renoteId'],
+	},
+	then: {
+		properties: {
+			text: {
+				type: 'string',
+				minLength: 1,
+				pattern: '[^\\s]+',
+			},
 		},
-	],
+		required: ['text'],
+	},
 } as const;
 
 @Injectable()
@@ -285,7 +320,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 					.getMany();
 
 				if (files.length !== fileIds.length) {
-					throw new ApiError(meta.errors.noSuchNote);
+					throw new ApiError(meta.errors.noSuchFile);
 				}
 			}
 
@@ -294,14 +329,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			if (ps.renoteId === ps.editId) {
 				throw new ApiError(meta.errors.cannotQuoteCurrentPost);
 			}
-			
+
 			if (ps.renoteId != null) {
 				// Fetch renote to note
 				renote = await this.notesRepository.findOneBy({ id: ps.renoteId });
 
 				if (renote == null) {
 					throw new ApiError(meta.errors.noSuchRenoteTarget);
-				} else if (renote.renoteId && !renote.text && !renote.fileIds && !renote.hasPoll) {
+				} else if (isPureRenote(renote)) {
 					throw new ApiError(meta.errors.cannotReRenote);
 				}
 
@@ -311,7 +346,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 				// Check blocking
 				if (renote.userId !== me.id) {
-					const blockExist = await this.blockingsRepository.exist({
+					const blockExist = await this.blockingsRepository.exists({
 						where: {
 							blockerId: renote.userId,
 							blockeeId: me.id,
@@ -322,6 +357,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 					}
 				}
 
+				if (renote.visibility === 'followers' && renote.userId !== me.id) {
+					// 他人のfollowers noteはreject
+					throw new ApiError(meta.errors.cannotRenoteDueToVisibility);
+				} else if (renote.visibility === 'specified') {
+					// specified / direct noteはreject
+					throw new ApiError(meta.errors.cannotRenoteDueToVisibility);
+				}
+
 				if (renote.channelId && renote.channelId !== ps.channelId) {
 					// チャンネルのノートに対しリノート要求がきたとき、チャンネル外へのリノート可否をチェック
 					// リノートのユースケースのうち、チャンネル内→チャンネル外は少数だと考えられるため、JOINはせず必要な時に都度取得する
@@ -343,13 +386,17 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 				if (reply == null) {
 					throw new ApiError(meta.errors.noSuchReplyTarget);
-				} else if (reply.renoteId && !reply.text && !reply.fileIds && !reply.hasPoll) {
+				} else if (isPureRenote(reply)) {
 					throw new ApiError(meta.errors.cannotReplyToPureRenote);
+				} else if (!await this.noteEntityService.isVisibleForMe(reply, me.id)) {
+					throw new ApiError(meta.errors.cannotReplyToInvisibleNote);
+				} else if (reply.visibility === 'specified' && ps.visibility !== 'specified') {
+					throw new ApiError(meta.errors.cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility);
 				}
 
 				// Check blocking
 				if (reply.userId !== me.id) {
-					const blockExist = await this.blockingsRepository.exist({
+					const blockExist = await this.blockingsRepository.exists({
 						where: {
 							blockerId: reply.userId,
 							blockeeId: me.id,
@@ -379,32 +426,43 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 					throw new ApiError(meta.errors.noSuchChannel);
 				}
 			}
+			try {
+				// 投稿を作成
+				const note = await this.noteEditService.edit(me, ps.editId!, {
+					files: files,
+					poll: ps.poll ? {
+						choices: ps.poll.choices,
+						multiple: ps.poll.multiple ?? false,
+						expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null,
+					} : undefined,
+					text: ps.text ?? undefined,
+					reply,
+					renote,
+					cw: ps.cw,
+					localOnly: ps.localOnly,
+					reactionAcceptance: ps.reactionAcceptance,
+					visibility: ps.visibility,
+					visibleUsers,
+					channel,
+					apMentions: ps.noExtractMentions ? [] : undefined,
+					apHashtags: ps.noExtractHashtags ? [] : undefined,
+					apEmojis: ps.noExtractEmojis ? [] : undefined,
+				});
 
-			// 投稿を作成
-			const note = await this.noteEditService.edit(me, ps.editId!, {
-				files: files,
-				poll: ps.poll ? {
-					choices: ps.poll.choices,
-					multiple: ps.poll.multiple ?? false,
-					expiresAt: ps.poll.expiresAt ? new Date(ps.poll.expiresAt) : null,
-				} : undefined,
-				text: ps.text ?? undefined,
-				reply,
-				renote,
-				cw: ps.cw,
-				localOnly: ps.localOnly,
-				reactionAcceptance: ps.reactionAcceptance,
-				visibility: ps.visibility,
-				visibleUsers,
-				channel,
-				apMentions: ps.noExtractMentions ? [] : undefined,
-				apHashtags: ps.noExtractHashtags ? [] : undefined,
-				apEmojis: ps.noExtractEmojis ? [] : undefined,
-			});
-
-			return {
-				createdNote: await this.noteEntityService.pack(note, me),
-			};
+				return {
+					createdNote: await this.noteEntityService.pack(note, me),
+				};
+			} catch (e) {
+				// TODO: 他のErrorもここでキャッチしてエラーメッセージを当てるようにしたい
+				if (e instanceof IdentifiableError) {
+					if (e.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') {
+						throw new ApiError(meta.errors.containsProhibitedWords);
+					} else if (e.id === '9f466dab-c856-48cd-9e65-ff90ff750580') {
+						throw new ApiError(meta.errors.containsTooManyMentions);
+					}
+				}
+				throw e;
+			}
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts
index ed3dce7f35..804071b3d4 100644
--- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -67,7 +67,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			});
 
 			// if already favorited
-			const exist = await this.noteFavoritesRepository.exist({
+			const exist = await this.noteFavoritesRepository.exists({
 				where: {
 					noteId: note.id,
 					userId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts
index 8ab9775a2c..2036facdba 100644
--- a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts
+++ b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/notes/featured.ts b/packages/backend/src/server/api/endpoints/notes/featured.ts
index 31b8d1ad2d..dcd971360d 100644
--- a/packages/backend/src/server/api/endpoints/notes/featured.ts
+++ b/packages/backend/src/server/api/endpoints/notes/featured.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
index 844de80268..d660f3fb69 100644
--- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
index 13cfb31ad0..ce5ddadb9e 100644
--- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
index 8db7d25e03..5c3c7ae7d0 100644
--- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/notes/mentions.ts b/packages/backend/src/server/api/endpoints/notes/mentions.ts
index 2317f8f7b2..5558dd3a8b 100644
--- a/packages/backend/src/server/api/endpoints/notes/mentions.ts
+++ b/packages/backend/src/server/api/endpoints/notes/mentions.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -61,9 +61,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId)
 				.andWhere(new Brackets(qb => {
-					qb
-						.where(`'{"${me.id}"}' <@ note.mentions`)
-						.orWhere(`'{"${me.id}"}' <@ note.visibleUserIds`);
+					qb // このmeIdAsListパラメータはqueryServiceのgenerateVisibilityQueryでセットされる
+						.where(':meIdAsList <@ note.mentions')
+						.orWhere(':meIdAsList <@ note.visibleUserIds');
 				}))
 				// Avoid scanning primary key index
 				.orderBy('CONCAT(note.id)', 'DESC')
diff --git a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts
index 90af29a695..ba38573065 100644
--- a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts
+++ b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
index 734c3f0e63..a91c506afd 100644
--- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
+++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/notes/reactions.ts b/packages/backend/src/server/api/endpoints/notes/reactions.ts
index a2c1778199..3beb5064ae 100644
--- a/packages/backend/src/server/api/endpoints/notes/reactions.ts
+++ b/packages/backend/src/server/api/endpoints/notes/reactions.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -74,6 +74,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				query.andWhere('reaction.reaction = :type', { type });
 			}
 
+			if (me) this.queryService.generateMutedUserQuery(query, me);
+			if (me) this.queryService.generateBlockedUserQuery(query, me);
+
 			const reactions = await query.limit(ps.limit).getMany();
 
 			return await Promise.all(reactions.map(reaction => this.noteReactionEntityService.pack(reaction, me)));
diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts
index ff22ef1322..b9899608bf 100644
--- a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts
index b43ab044fa..600c1e0019 100644
--- a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts
+++ b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -19,8 +19,8 @@ export const meta = {
 
 	limit: {
 		duration: ms('1hour'),
-		max: 60,
-		minInterval: ms('3sec'),
+		max: 80,
+		minInterval: ms('1sec'),
 	},
 
 	errors: {
diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts
index 063650b3c7..a88c286f64 100644
--- a/packages/backend/src/server/api/endpoints/notes/renotes.ts
+++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/notes/replies.ts b/packages/backend/src/server/api/endpoints/notes/replies.ts
index 70142c9818..5f32332a6a 100644
--- a/packages/backend/src/server/api/endpoints/notes/replies.ts
+++ b/packages/backend/src/server/api/endpoints/notes/replies.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts
index 89e05fd57e..55ff6771b1 100644
--- a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts
+++ b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -104,14 +104,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			try {
 				if (ps.tag) {
 					if (!safeForSql(normalizeForSearch(ps.tag))) throw new Error('Injection');
-					query.andWhere(`'{"${normalizeForSearch(ps.tag)}"}' <@ note.tags`);
+					query.andWhere(':tag <@ note.tags', { tag: [normalizeForSearch(ps.tag)] });
 				} else {
 					query.andWhere(new Brackets(qb => {
 						for (const tags of ps.query!) {
 							qb.orWhere(new Brackets(qb => {
 								for (const tag of tags) {
 									if (!safeForSql(normalizeForSearch(tag))) throw new Error('Injection');
-									qb.andWhere(`'{"${normalizeForSearch(tag)}"}' <@ note.tags`);
+									qb.andWhere(':tag <@ note.tags', { tag: [normalizeForSearch(tag)] });
 								}
 							}));
 						}
diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts
index 06efa3d951..e140436d6b 100644
--- a/packages/backend/src/server/api/endpoints/notes/search.ts
+++ b/packages/backend/src/server/api/endpoints/notes/search.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts
index b3107f6754..f82ba5473d 100644
--- a/packages/backend/src/server/api/endpoints/notes/show.ts
+++ b/packages/backend/src/server/api/endpoints/notes/show.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -54,7 +54,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			this.queryService.generateVisibilityQuery(query, me);
 			if (me) {
-				this.queryService.generateMutedUserQuery(query, me);
 				this.queryService.generateBlockedUserQuery(query, me);
 			}
 			
diff --git a/packages/backend/src/server/api/endpoints/notes/state.ts b/packages/backend/src/server/api/endpoints/notes/state.ts
index 20faea566d..4c1eb86542 100644
--- a/packages/backend/src/server/api/endpoints/notes/state.ts
+++ b/packages/backend/src/server/api/endpoints/notes/state.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts
index b2cdaa00ac..732d644a29 100644
--- a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts
index d3f1787ee4..d94d6cd652 100644
--- a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts
+++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts
index 3dcebe7e29..1e5869663f 100644
--- a/packages/backend/src/server/api/endpoints/notes/timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/notes/translate.ts b/packages/backend/src/server/api/endpoints/notes/translate.ts
index 698c37b616..a935f761b7 100644
--- a/packages/backend/src/server/api/endpoints/notes/translate.ts
+++ b/packages/backend/src/server/api/endpoints/notes/translate.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -81,19 +81,23 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			const instance = await this.metaService.fetch();
 
-			if (instance.deeplAuthKey == null) {
+			if (instance.deeplAuthKey == null && !instance.deeplFreeMode) {
 				return 204; // TODO: 良い感じのエラー返す
 			}
 
+			if (instance.deeplFreeMode && !instance.deeplFreeInstance) {
+				return 204;
+			}
+
 			let targetLang = ps.targetLang;
 			if (targetLang.includes('-')) targetLang = targetLang.split('-')[0];
 
 			const params = new URLSearchParams();
-			params.append('auth_key', instance.deeplAuthKey);
+			if (instance.deeplAuthKey) params.append('auth_key', instance.deeplAuthKey);
 			params.append('text', note.text);
 			params.append('target_lang', targetLang);
 
-			const endpoint = instance.deeplIsPro ? 'https://api.deepl.com/v2/translate' : 'https://api-free.deepl.com/v2/translate';
+			const endpoint = instance.deeplFreeMode && instance.deeplFreeInstance ? instance.deeplFreeInstance : instance.deeplIsPro ? 'https://api.deepl.com/v2/translate' : 'https://api-free.deepl.com/v2/translate';
 
 			const res = await this.httpRequestService.send(endpoint, {
 				method: 'POST',
@@ -103,18 +107,37 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				},
 				body: params.toString(),
 			});
+			if (instance.deeplAuthKey) {
+				const json = (await res.json()) as {
+					translations: {
+						detected_source_language: string;
+						text: string;
+					}[];
+				};
 
-			const json = (await res.json()) as {
-				translations: {
-					detected_source_language: string;
-					text: string;
-				}[];
-			};
+				return {
+					sourceLang: json.translations[0].detected_source_language,
+					text: json.translations[0].text,
+				};
+			} else {
+				const json = (await res.json()) as {
+					code: number,
+					message: string,
+					data: string,
+					source_lang: string,
+					target_lang: string,
+					alternatives: string[],
+				};
 
-			return {
-				sourceLang: json.translations[0].detected_source_language,
-				text: json.translations[0].text,
-			};
+				const languageNames = new Intl.DisplayNames(['en'], {
+					type: 'language',
+				});
+
+				return {
+					sourceLang: languageNames.of(json.source_lang),
+					text: json.data,
+				};
+			}
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/notes/unrenote.ts b/packages/backend/src/server/api/endpoints/notes/unrenote.ts
index 249344a6f3..58932bd83a 100644
--- a/packages/backend/src/server/api/endpoints/notes/unrenote.ts
+++ b/packages/backend/src/server/api/endpoints/notes/unrenote.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
index 71c2b8054e..43877e61ef 100644
--- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/notes/versions.ts b/packages/backend/src/server/api/endpoints/notes/versions.ts
index 416fddcb7b..2b774ae2b0 100644
--- a/packages/backend/src/server/api/endpoints/notes/versions.ts
+++ b/packages/backend/src/server/api/endpoints/notes/versions.ts
@@ -3,9 +3,12 @@
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { Injectable } from '@nestjs/common';
+import { Inject, Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
+import { DI } from '@/di-symbols.js';
+import type { NotesRepository } from '@/models/_.js';
 import { GetterService } from '@/server/api/GetterService.js';
+import { QueryService } from '@/core/QueryService.js';
 import { ApiError } from '../../error.js';
 
 export const meta = {
@@ -38,9 +41,25 @@ export const paramDef = {
 @Injectable()
 export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
 	constructor(
+		@Inject(DI.notesRepository)
+		private notesRepository: NotesRepository,
+		
 		private getterService: GetterService,
+		private queryService: QueryService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
+			const query = await this.notesRepository.createQueryBuilder('note')
+				.select('note.id')
+				.where('note.id = :noteId', { noteId: ps.noteId });
+
+			this.queryService.generateVisibilityQuery(query, me);
+			
+			const note = await query.getOne();
+
+			if (note === null) {
+				throw new ApiError(meta.errors.noSuchNote);
+			}
+
 			const edits = await this.getterService.getEdits(ps.noteId).catch(err => {
 				if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
 				throw err;
diff --git a/packages/backend/src/server/api/endpoints/notifications/create.ts b/packages/backend/src/server/api/endpoints/notifications/create.ts
index 7c6a979160..7671b58e6b 100644
--- a/packages/backend/src/server/api/endpoints/notifications/create.ts
+++ b/packages/backend/src/server/api/endpoints/notifications/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/notifications/flush.ts b/packages/backend/src/server/api/endpoints/notifications/flush.ts
new file mode 100644
index 0000000000..47c0642fd1
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/notifications/flush.ts
@@ -0,0 +1,33 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { NotificationService } from '@/core/NotificationService.js';
+
+export const meta = {
+	tags: ['notifications', 'account'],
+
+	requireCredential: true,
+
+	kind: 'write:notifications',
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {},
+	required: [],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		private notificationService: NotificationService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			this.notificationService.flushAllNotifications(me.id);
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts
index dc092c1f3a..6565125c00 100644
--- a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts
+++ b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/notifications/test-notification.ts b/packages/backend/src/server/api/endpoints/notifications/test-notification.ts
index 8f5f8485c3..50b850a519 100644
--- a/packages/backend/src/server/api/endpoints/notifications/test-notification.ts
+++ b/packages/backend/src/server/api/endpoints/notifications/test-notification.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/page-push.ts b/packages/backend/src/server/api/endpoints/page-push.ts
index 0a68516586..ce454ab24a 100644
--- a/packages/backend/src/server/api/endpoints/page-push.ts
+++ b/packages/backend/src/server/api/endpoints/page-push.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -55,7 +55,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				var: ps.var,
 				userId: me.id,
 				user: await this.userEntityService.pack(me.id, { id: page.userId }, {
-					detail: true,
+					schema: 'UserDetailed',
 				}),
 			});
 		});
diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts
index 4c2ef516e5..3a02d359f8 100644
--- a/packages/backend/src/server/api/endpoints/pages/create.ts
+++ b/packages/backend/src/server/api/endpoints/pages/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/pages/delete.ts b/packages/backend/src/server/api/endpoints/pages/delete.ts
index 1291c0d209..aa2ba75a41 100644
--- a/packages/backend/src/server/api/endpoints/pages/delete.ts
+++ b/packages/backend/src/server/api/endpoints/pages/delete.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/pages/featured.ts b/packages/backend/src/server/api/endpoints/pages/featured.ts
index 1f43d6606c..a47b69e56e 100644
--- a/packages/backend/src/server/api/endpoints/pages/featured.ts
+++ b/packages/backend/src/server/api/endpoints/pages/featured.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/pages/like.ts b/packages/backend/src/server/api/endpoints/pages/like.ts
index 8c18982b50..11eed693ad 100644
--- a/packages/backend/src/server/api/endpoints/pages/like.ts
+++ b/packages/backend/src/server/api/endpoints/pages/like.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -70,7 +70,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			}
 
 			// if already liked
-			const exist = await this.pageLikesRepository.exist({
+			const exist = await this.pageLikesRepository.exists({
 				where: {
 					pageId: page.id,
 					userId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/pages/show.ts b/packages/backend/src/server/api/endpoints/pages/show.ts
index efb0bd0677..e08b832a3f 100644
--- a/packages/backend/src/server/api/endpoints/pages/show.ts
+++ b/packages/backend/src/server/api/endpoints/pages/show.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/pages/unlike.ts b/packages/backend/src/server/api/endpoints/pages/unlike.ts
index 7a76cd7408..70c965e0ad 100644
--- a/packages/backend/src/server/api/endpoints/pages/unlike.ts
+++ b/packages/backend/src/server/api/endpoints/pages/unlike.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts
index aaea1efa87..b8e5e70a25 100644
--- a/packages/backend/src/server/api/endpoints/pages/update.ts
+++ b/packages/backend/src/server/api/endpoints/pages/update.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/ping.ts b/packages/backend/src/server/api/endpoints/ping.ts
index ee2fe48834..e218a8f755 100644
--- a/packages/backend/src/server/api/endpoints/ping.ts
+++ b/packages/backend/src/server/api/endpoints/ping.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts
index 390042c815..784766bcb5 100644
--- a/packages/backend/src/server/api/endpoints/pinned-users.ts
+++ b/packages/backend/src/server/api/endpoints/pinned-users.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -12,6 +12,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
 import { MetaService } from '@/core/MetaService.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { DI } from '@/di-symbols.js';
+import { isNotNull } from '@/misc/is-not-null.js';
 
 export const meta = {
 	tags: ['users'],
@@ -52,7 +53,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				host: acct.host ?? IsNull(),
 			})));
 
-			return await this.userEntityService.packMany(users.filter(x => x !== null) as MiUser[], me, { detail: true });
+			return await this.userEntityService.packMany(users.filter(isNotNull), me, { schema: 'UserDetailed' });
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/promo/read.ts b/packages/backend/src/server/api/endpoints/promo/read.ts
index f427939a7a..9f7d078014 100644
--- a/packages/backend/src/server/api/endpoints/promo/read.ts
+++ b/packages/backend/src/server/api/endpoints/promo/read.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -49,7 +49,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				throw err;
 			});
 
-			const exist = await this.promoReadsRepository.exist({
+			const exist = await this.promoReadsRepository.exists({
 				where: {
 					noteId: note.id,
 					userId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/renote-mute/create.ts b/packages/backend/src/server/api/endpoints/renote-mute/create.ts
index 7ff7b5de3a..39bf0cc428 100644
--- a/packages/backend/src/server/api/endpoints/renote-mute/create.ts
+++ b/packages/backend/src/server/api/endpoints/renote-mute/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -73,7 +73,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 			}
 
 			// Get mutee
-			const mutee = await getterService.getUser(ps.userId).catch(err => {
+			const mutee = await this.getterService.getUser(ps.userId).catch(err => {
 				if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
 				throw err;
 			});
diff --git a/packages/backend/src/server/api/endpoints/renote-mute/delete.ts b/packages/backend/src/server/api/endpoints/renote-mute/delete.ts
index f4969896d9..6e037cc07e 100644
--- a/packages/backend/src/server/api/endpoints/renote-mute/delete.ts
+++ b/packages/backend/src/server/api/endpoints/renote-mute/delete.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/renote-mute/list.ts b/packages/backend/src/server/api/endpoints/renote-mute/list.ts
index 493593ae2d..3be01f989a 100644
--- a/packages/backend/src/server/api/endpoints/renote-mute/list.ts
+++ b/packages/backend/src/server/api/endpoints/renote-mute/list.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/request-reset-password.ts b/packages/backend/src/server/api/endpoints/request-reset-password.ts
index f13710e1dd..86fe6a2e6e 100644
--- a/packages/backend/src/server/api/endpoints/request-reset-password.ts
+++ b/packages/backend/src/server/api/endpoints/request-reset-password.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/reset-db.ts b/packages/backend/src/server/api/endpoints/reset-db.ts
index 0eeee81580..67d5fabd86 100644
--- a/packages/backend/src/server/api/endpoints/reset-db.ts
+++ b/packages/backend/src/server/api/endpoints/reset-db.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/reset-password.ts b/packages/backend/src/server/api/endpoints/reset-password.ts
index e76b7c9683..1639b57bc5 100644
--- a/packages/backend/src/server/api/endpoints/reset-password.ts
+++ b/packages/backend/src/server/api/endpoints/reset-password.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/retention.ts b/packages/backend/src/server/api/endpoints/retention.ts
index dac6d65407..4695f32042 100644
--- a/packages/backend/src/server/api/endpoints/retention.ts
+++ b/packages/backend/src/server/api/endpoints/retention.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -14,6 +14,32 @@ export const meta = {
 	requireCredential: false,
 
 	res: {
+		type: 'array',
+		items: {
+			type: 'object',
+			properties: {
+				createdAt: {
+					type: 'string',
+					format: 'date-time',
+				},
+				users: {
+					type: 'number',
+				},
+				data: {
+					type: 'object',
+					additionalProperties: {
+						anyOf: [{
+							type: 'number',
+						}],
+					},
+				},
+			},
+			required: [
+				'createdAt',
+				'users',
+				'data',
+			],
+		},
 	},
 
 	allowGet: true,
diff --git a/packages/backend/src/server/api/endpoints/reversi/cancel-match.ts b/packages/backend/src/server/api/endpoints/reversi/cancel-match.ts
new file mode 100644
index 0000000000..dd6f273e01
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/reversi/cancel-match.ts
@@ -0,0 +1,41 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { ReversiService } from '@/core/ReversiService.js';
+
+export const meta = {
+	requireCredential: true,
+
+	kind: 'write:account',
+
+	errors: {
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id', nullable: true },
+	},
+	required: [],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		private reversiService: ReversiService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			if (ps.userId) {
+				await this.reversiService.matchSpecificUserCancel(me, ps.userId);
+				return;
+			} else {
+				await this.reversiService.matchAnyUserCancel(me);
+			}
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/reversi/games.ts b/packages/backend/src/server/api/endpoints/reversi/games.ts
new file mode 100644
index 0000000000..6b06068727
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/reversi/games.ts
@@ -0,0 +1,64 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import { Brackets } from 'typeorm';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
+import { DI } from '@/di-symbols.js';
+import type { ReversiGamesRepository } from '@/models/_.js';
+import { QueryService } from '@/core/QueryService.js';
+
+export const meta = {
+	requireCredential: false,
+
+	res: {
+		type: 'array',
+		optional: false, nullable: false,
+		items: { ref: 'ReversiGameLite' },
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+		sinceId: { type: 'string', format: 'misskey:id' },
+		untilId: { type: 'string', format: 'misskey:id' },
+		my: { type: 'boolean', default: false },
+	},
+	required: [],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		@Inject(DI.reversiGamesRepository)
+		private reversiGamesRepository: ReversiGamesRepository,
+
+		private reversiGameEntityService: ReversiGameEntityService,
+		private queryService: QueryService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			const query = this.queryService.makePaginationQuery(this.reversiGamesRepository.createQueryBuilder('game'), ps.sinceId, ps.untilId)
+				.innerJoinAndSelect('game.user1', 'user1')
+				.innerJoinAndSelect('game.user2', 'user2');
+
+			if (ps.my && me) {
+				query.andWhere(new Brackets(qb => {
+					qb
+						.where('game.user1Id = :userId', { userId: me.id })
+						.orWhere('game.user2Id = :userId', { userId: me.id });
+				}));
+			} else {
+				query.andWhere('game.isStarted = TRUE');
+			}
+
+			const games = await query.take(ps.limit).getMany();
+
+			return await this.reversiGameEntityService.packLiteMany(games);
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/reversi/invitations.ts b/packages/backend/src/server/api/endpoints/reversi/invitations.ts
new file mode 100644
index 0000000000..5b3b9da75b
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/reversi/invitations.ts
@@ -0,0 +1,39 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { DI } from '@/di-symbols.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { ReversiService } from '@/core/ReversiService.js';
+
+export const meta = {
+	requireCredential: true,
+
+	kind: 'read:account',
+
+	res: {
+		type: 'array',
+		optional: false, nullable: false,
+		items: { ref: 'UserLite' },
+	},
+} as const;
+
+export const paramDef = {
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		private userEntityService: UserEntityService,
+		private reversiService: ReversiService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			const invitations = await this.reversiService.getInvitations(me);
+
+			return await this.userEntityService.packMany(invitations, me);
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/reversi/match.ts b/packages/backend/src/server/api/endpoints/reversi/match.ts
new file mode 100644
index 0000000000..aa8b8a7d72
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/reversi/match.ts
@@ -0,0 +1,73 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { ReversiService } from '@/core/ReversiService.js';
+import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
+import { ApiError } from '../../error.js';
+import { GetterService } from '../../GetterService.js';
+
+export const meta = {
+	requireCredential: true,
+
+	kind: 'write:account',
+
+	errors: {
+		noSuchUser: {
+			message: 'No such user.',
+			code: 'NO_SUCH_USER',
+			id: '0b4f0559-b484-4e31-9581-3f73cee89b28',
+		},
+
+		isYourself: {
+			message: 'Target user is yourself.',
+			code: 'TARGET_IS_YOURSELF',
+			id: '96fd7bd6-d2bc-426c-a865-d055dcd2828e',
+		},
+	},
+
+	res: {
+		type: 'object',
+		optional: true,
+		ref: 'ReversiGameDetailed',
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		userId: { type: 'string', format: 'misskey:id', nullable: true },
+		noIrregularRules: { type: 'boolean', default: false },
+		multiple: { type: 'boolean', default: false },
+	},
+	required: [],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		private getterService: GetterService,
+		private reversiService: ReversiService,
+		private reversiGameEntityService: ReversiGameEntityService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			if (ps.userId === me.id) throw new ApiError(meta.errors.isYourself);
+
+			const target = ps.userId ? await this.getterService.getUser(ps.userId).catch(err => {
+				if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+				throw err;
+			}) : null;
+
+			const game = target
+				? await this.reversiService.matchSpecificUser(me, target, ps.multiple)
+				: await this.reversiService.matchAnyUser(me, { noIrregularRules: ps.noIrregularRules }, ps.multiple);
+
+			if (game == null) return;
+
+			return await this.reversiGameEntityService.packDetail(game);
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/reversi/show-game.ts b/packages/backend/src/server/api/endpoints/reversi/show-game.ts
new file mode 100644
index 0000000000..fc3b96eb51
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/reversi/show-game.ts
@@ -0,0 +1,54 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { ReversiService } from '@/core/ReversiService.js';
+import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
+import { ApiError } from '../../error.js';
+
+export const meta = {
+	requireCredential: false,
+
+	errors: {
+		noSuchGame: {
+			message: 'No such game.',
+			code: 'NO_SUCH_GAME',
+			id: 'f13a03db-fae1-46c9-87f3-43c8165419e1',
+		},
+	},
+
+	res: {
+		type: 'object',
+		optional: false, nullable: false,
+		ref: 'ReversiGameDetailed',
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		gameId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['gameId'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		private reversiService: ReversiService,
+		private reversiGameEntityService: ReversiGameEntityService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			const game = await this.reversiService.get(ps.gameId);
+
+			if (game == null) {
+				throw new ApiError(meta.errors.noSuchGame);
+			}
+
+			return await this.reversiGameEntityService.packDetail(game);
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/reversi/surrender.ts b/packages/backend/src/server/api/endpoints/reversi/surrender.ts
new file mode 100644
index 0000000000..75e5372862
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/reversi/surrender.ts
@@ -0,0 +1,68 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { ReversiService } from '@/core/ReversiService.js';
+import { ApiError } from '../../error.js';
+
+export const meta = {
+	requireCredential: true,
+
+	kind: 'write:account',
+
+	errors: {
+		noSuchGame: {
+			message: 'No such game.',
+			code: 'NO_SUCH_GAME',
+			id: 'ace0b11f-e0a6-4076-a30d-e8284c81b2df',
+		},
+
+		alreadyEnded: {
+			message: 'That game has already ended.',
+			code: 'ALREADY_ENDED',
+			id: '6c2ad4a6-cbf1-4a5b-b187-b772826cfc6d',
+		},
+
+		accessDenied: {
+			message: 'Access denied.',
+			code: 'ACCESS_DENIED',
+			id: '6e04164b-a992-4c93-8489-2123069973e1',
+		},
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		gameId: { type: 'string', format: 'misskey:id' },
+	},
+	required: ['gameId'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		private reversiService: ReversiService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			const game = await this.reversiService.get(ps.gameId);
+
+			if (game == null) {
+				throw new ApiError(meta.errors.noSuchGame);
+			}
+
+			if (game.isEnded) {
+				throw new ApiError(meta.errors.alreadyEnded);
+			}
+
+			if ((game.user1Id !== me.id) && (game.user2Id !== me.id)) {
+				throw new ApiError(meta.errors.accessDenied);
+			}
+
+			await this.reversiService.surrender(game.id, me);
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/reversi/verify.ts b/packages/backend/src/server/api/endpoints/reversi/verify.ts
new file mode 100644
index 0000000000..981735a3d7
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/reversi/verify.ts
@@ -0,0 +1,64 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { ReversiService } from '@/core/ReversiService.js';
+import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
+import { ApiError } from '../../error.js';
+
+export const meta = {
+	errors: {
+		noSuchGame: {
+			message: 'No such game.',
+			code: 'NO_SUCH_GAME',
+			id: '8fb05624-b525-43dd-90f7-511852bdfeee',
+		},
+	},
+
+	res: {
+		type: 'object',
+		optional: false, nullable: false,
+		properties: {
+			desynced: { type: 'boolean' },
+			game: {
+				type: 'object',
+				optional: true, nullable: true,
+				ref: 'ReversiGameDetailed',
+			},
+		},
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+		gameId: { type: 'string', format: 'misskey:id' },
+		crc32: { type: 'string' },
+	},
+	required: ['gameId', 'crc32'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+	constructor(
+		private reversiService: ReversiService,
+		private reversiGameEntityService: ReversiGameEntityService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			const game = await this.reversiService.checkCrc(ps.gameId, ps.crc32);
+			if (game) {
+				return {
+					desynced: true,
+					game: await this.reversiGameEntityService.packDetail(game),
+				};
+			} else {
+				return {
+					desynced: false,
+				};
+			}
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/roles/list.ts b/packages/backend/src/server/api/endpoints/roles/list.ts
index d40e937d4e..b087aa242b 100644
--- a/packages/backend/src/server/api/endpoints/roles/list.ts
+++ b/packages/backend/src/server/api/endpoints/roles/list.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/roles/notes.ts b/packages/backend/src/server/api/endpoints/roles/notes.ts
index 4ce3fc8908..71f2782a5d 100644
--- a/packages/backend/src/server/api/endpoints/roles/notes.ts
+++ b/packages/backend/src/server/api/endpoints/roles/notes.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/roles/show.ts b/packages/backend/src/server/api/endpoints/roles/show.ts
index 6bfe52bb1a..38477c5e8e 100644
--- a/packages/backend/src/server/api/endpoints/roles/show.ts
+++ b/packages/backend/src/server/api/endpoints/roles/show.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/roles/users.ts b/packages/backend/src/server/api/endpoints/roles/users.ts
index d304d075b2..85d100ce1c 100644
--- a/packages/backend/src/server/api/endpoints/roles/users.ts
+++ b/packages/backend/src/server/api/endpoints/roles/users.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -33,11 +33,11 @@ export const meta = {
 			properties: {
 				id: {
 					type: 'string',
-					format: 'misskey:id'
+					format: 'misskey:id',
 				},
 				user: {
 					type: 'object',
-					ref: 'User'
+					ref: 'UserDetailed',
 				},
 			},
 			required: ['id', 'user'],
@@ -94,7 +94,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			return await Promise.all(assigns.map(async assign => ({
 				id: assign.id,
-				user: await this.userEntityService.pack(assign.user!, me, { detail: true }),
+				user: await this.userEntityService.pack(assign.user!, me, { schema: 'UserDetailed' }),
 			})));
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/server-info.ts b/packages/backend/src/server/api/endpoints/server-info.ts
index 079f2d7f1d..c13802eb06 100644
--- a/packages/backend/src/server/api/endpoints/server-info.ts
+++ b/packages/backend/src/server/api/endpoints/server-info.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/sponsors.ts b/packages/backend/src/server/api/endpoints/sponsors.ts
index 79785673aa..50e1c594f2 100644
--- a/packages/backend/src/server/api/endpoints/sponsors.ts
+++ b/packages/backend/src/server/api/endpoints/sponsors.ts
@@ -1,20 +1,20 @@
 import { Inject, Injectable } from '@nestjs/common';
+import * as Redis from 'ioredis';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { DI } from '@/di-symbols.js';
-import * as Redis from 'ioredis';
 
 export const meta = {
-	tags: ["meta"],
-	description: "Get Sharkey GH Sponsors",
+	tags: ['meta'],
+	description: 'Get Sharkey GH Sponsors',
 
 	requireCredential: false,
 	requireCredentialPrivateMode: false,
 } as const;
 
 export const paramDef = {
-	type: "object",
+	type: 'object',
 	properties: {
-		forceUpdate: { type: "boolean", default: false },
+		forceUpdate: { type: 'boolean', default: false },
 	},
 	required: [],
 } as const;
@@ -25,22 +25,29 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
         @Inject(DI.redis) private redisClient: Redis.Redis,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-            let sponsors;
-            const cachedSponsors = await this.redisClient.get("sponsors");
-            if (!ps.forceUpdate && cachedSponsors) {
-                sponsors = JSON.parse(cachedSponsors);
-            } else {
-                AbortSignal.timeout ??= function timeout(ms) {
-                    const ctrl = new AbortController();
-                    setTimeout(() => ctrl.abort(), ms);
-                    return ctrl.signal;
-                };
+			let sponsors;
+			const cachedSponsors = await this.redisClient.get('sponsors');
+			if (!ps.forceUpdate && cachedSponsors) {
+				sponsors = JSON.parse(cachedSponsors);
+			} else {
+				AbortSignal.timeout ??= function timeout(ms) {
+					const ctrl = new AbortController();
+					setTimeout(() => ctrl.abort(), ms);
+					return ctrl.signal;
+				};
 
-                sponsors = await fetch("https://kaifa.ch/transfem-sponsors.json", { signal: AbortSignal.timeout(2000) })
-                    .then((response) => response.json());
-                await this.redisClient.set("sponsors", JSON.stringify(sponsors), "EX", 3600);
-            }
-            return { sponsor_data: sponsors['sponsors'] };
-        });
-    }
+				try {
+					sponsors = await fetch('https://kaifa.ch/transfem-sponsors.json', { signal: AbortSignal.timeout(2000) })
+						.then((response) => response.json());
+
+					await this.redisClient.set('sponsors', JSON.stringify(sponsors), 'EX', 3600);
+				} catch (error) {
+					sponsors = {
+						sponsors: [],
+					};
+				}
+			}
+			return { sponsor_data: sponsors['sponsors'] };
+		});
+	}
 }
diff --git a/packages/backend/src/server/api/endpoints/stats.ts b/packages/backend/src/server/api/endpoints/stats.ts
index 05468240d3..1e6983177f 100644
--- a/packages/backend/src/server/api/endpoints/stats.ts
+++ b/packages/backend/src/server/api/endpoints/stats.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts
index bb50048d94..a9a33149f9 100644
--- a/packages/backend/src/server/api/endpoints/sw/register.ts
+++ b/packages/backend/src/server/api/endpoints/sw/register.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -9,6 +9,7 @@ import type { SwSubscriptionsRepository } from '@/models/_.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { MetaService } from '@/core/MetaService.js';
 import { DI } from '@/di-symbols.js';
+import { PushNotificationService } from '@/core/PushNotificationService.js';
 
 export const meta = {
 	tags: ['account'],
@@ -66,6 +67,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 		private idService: IdService,
 		private metaService: MetaService,
+		private pushNotificationService: PushNotificationService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			// if already subscribed
@@ -97,6 +99,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				sendReadMessage: ps.sendReadMessage,
 			});
 
+			this.pushNotificationService.refreshCache(me.id);
+
 			return {
 				state: 'subscribed' as const,
 				key: instance.swPublicKey,
diff --git a/packages/backend/src/server/api/endpoints/sw/show-registration.ts b/packages/backend/src/server/api/endpoints/sw/show-registration.ts
index 15d3df8587..797e4fd34d 100644
--- a/packages/backend/src/server/api/endpoints/sw/show-registration.ts
+++ b/packages/backend/src/server/api/endpoints/sw/show-registration.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/sw/unregister.ts b/packages/backend/src/server/api/endpoints/sw/unregister.ts
index f00fdd6697..2edf7fab1b 100644
--- a/packages/backend/src/server/api/endpoints/sw/unregister.ts
+++ b/packages/backend/src/server/api/endpoints/sw/unregister.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
 import type { SwSubscriptionsRepository } from '@/models/_.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { DI } from '@/di-symbols.js';
+import { PushNotificationService } from '@/core/PushNotificationService.js';
 
 export const meta = {
 	tags: ['account'],
@@ -29,12 +30,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 	constructor(
 		@Inject(DI.swSubscriptionsRepository)
 		private swSubscriptionsRepository: SwSubscriptionsRepository,
+
+		private pushNotificationService: PushNotificationService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			await this.swSubscriptionsRepository.delete({
 				...(me ? { userId: me.id } : {}),
 				endpoint: ps.endpoint,
 			});
+
+			if (me) {
+				this.pushNotificationService.refreshCache(me.id);
+			}
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/sw/update-registration.ts b/packages/backend/src/server/api/endpoints/sw/update-registration.ts
index 7bf59784a2..839a07c770 100644
--- a/packages/backend/src/server/api/endpoints/sw/update-registration.ts
+++ b/packages/backend/src/server/api/endpoints/sw/update-registration.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
 import type { SwSubscriptionsRepository } from '@/models/_.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { DI } from '@/di-symbols.js';
+import { PushNotificationService } from '@/core/PushNotificationService.js';
 import { ApiError } from '../../error.js';
 
 export const meta = {
@@ -58,6 +59,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 	constructor(
 		@Inject(DI.swSubscriptionsRepository)
 		private swSubscriptionsRepository: SwSubscriptionsRepository,
+
+		private pushNotificationService: PushNotificationService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const swSubscription = await this.swSubscriptionsRepository.findOneBy({
@@ -77,6 +80,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				sendReadMessage: swSubscription.sendReadMessage,
 			});
 
+			this.pushNotificationService.refreshCache(me.id);
+
 			return {
 				userId: swSubscription.userId,
 				endpoint: swSubscription.endpoint,
diff --git a/packages/backend/src/server/api/endpoints/test.ts b/packages/backend/src/server/api/endpoints/test.ts
index 949867c572..9231f0ab94 100644
--- a/packages/backend/src/server/api/endpoints/test.ts
+++ b/packages/backend/src/server/api/endpoints/test.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -18,24 +18,28 @@ export const meta = {
 		properties: {
 			id: {
 				type: 'string',
-				format: 'misskey:id'
+				format: 'misskey:id',
+				optional: true, nullable: false,
 			},
 			required: {
 				type: 'boolean',
+				optional: false, nullable: false,
 			},
 			string: {
 				type: 'string',
+				optional: true, nullable: false,
 			},
 			default: {
 				type: 'string',
+				optional: true, nullable: false,
 			},
 			nullableDefault: {
 				type: 'string',
 				default: 'hello',
-				nullable: true,
+				optional: true, nullable: true,
 			},
-		}
-	}
+		},
+	},
 } as const;
 
 export const paramDef = {
diff --git a/packages/backend/src/server/api/endpoints/username/available.ts b/packages/backend/src/server/api/endpoints/username/available.ts
index e37df62c0c..affb0996f1 100644
--- a/packages/backend/src/server/api/endpoints/username/available.ts
+++ b/packages/backend/src/server/api/endpoints/username/available.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/users.ts b/packages/backend/src/server/api/endpoints/users.ts
index 8dc5841314..e845853017 100644
--- a/packages/backend/src/server/api/endpoints/users.ts
+++ b/packages/backend/src/server/api/endpoints/users.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -89,7 +89,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			const users = await query.getMany();
 
-			return await this.userEntityService.packMany(users, me, { detail: true });
+			return await this.userEntityService.packMany(users, me, { schema: 'UserDetailed' });
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/users/achievements.ts b/packages/backend/src/server/api/endpoints/users/achievements.ts
index 3a584a819a..f7139b3684 100644
--- a/packages/backend/src/server/api/endpoints/users/achievements.ts
+++ b/packages/backend/src/server/api/endpoints/users/achievements.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/users/clips.ts b/packages/backend/src/server/api/endpoints/users/clips.ts
index 725e07db39..7f7d2ea8cc 100644
--- a/packages/backend/src/server/api/endpoints/users/clips.ts
+++ b/packages/backend/src/server/api/endpoints/users/clips.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/users/featured-notes.ts b/packages/backend/src/server/api/endpoints/users/featured-notes.ts
index 7243aa3b3e..e01f19ba7a 100644
--- a/packages/backend/src/server/api/endpoints/users/featured-notes.ts
+++ b/packages/backend/src/server/api/endpoints/users/featured-notes.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/users/flashs.ts b/packages/backend/src/server/api/endpoints/users/flashs.ts
index 18026dcefb..e5ea450215 100644
--- a/packages/backend/src/server/api/endpoints/users/flashs.ts
+++ b/packages/backend/src/server/api/endpoints/users/flashs.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts
index 5706e46b96..7ce7734f53 100644
--- a/packages/backend/src/server/api/endpoints/users/followers.ts
+++ b/packages/backend/src/server/api/endpoints/users/followers.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -101,7 +101,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				if (me == null) {
 					throw new ApiError(meta.errors.forbidden);
 				} else if (me.id !== user.id) {
-					const isFollowing = await this.followingsRepository.exist({
+					const isFollowing = await this.followingsRepository.exists({
 						where: {
 							followeeId: user.id,
 							followerId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts
index 794fb04f10..5d52ebba76 100644
--- a/packages/backend/src/server/api/endpoints/users/following.ts
+++ b/packages/backend/src/server/api/endpoints/users/following.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -109,7 +109,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				if (me == null) {
 					throw new ApiError(meta.errors.forbidden);
 				} else if (me.id !== user.id) {
-					const isFollowing = await this.followingsRepository.exist({
+					const isFollowing = await this.followingsRepository.exists({
 						where: {
 							followeeId: user.id,
 							followerId: me.id,
diff --git a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts
index 757af98e00..553886374c 100644
--- a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts
+++ b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts
index d6fb65cecb..02aa037466 100644
--- a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts
+++ b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -122,7 +122,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			// Make replies object (includes weights)
 			const repliesObj = await Promise.all(topRepliedUsers.map(async (user) => ({
-				user: await this.userEntityService.pack(user, me, { detail: true }),
+				user: await this.userEntityService.pack(user, me, { schema: 'UserDetailed' }),
 				weight: repliedUsers[user] / peak,
 			})));
 
diff --git a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts
index fa2e3338b8..e2db71c5c7 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/create-from-public.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -90,7 +90,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		private roleService: RoleService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const listExist = await this.userListsRepository.exist({
+			const listExist = await this.userListsRepository.exists({
 				where: {
 					id: ps.listId,
 					isPublic: true,
@@ -121,7 +121,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				});
 
 				if (currentUser.id !== me.id) {
-					const blockExist = await this.blockingsRepository.exist({
+					const blockExist = await this.blockingsRepository.exists({
 						where: {
 							blockerId: currentUser.id,
 							blockeeId: me.id,
@@ -132,7 +132,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 					}
 				}
 
-				const exist = await this.userListMembershipsRepository.exist({
+				const exist = await this.userListMembershipsRepository.exists({
 					where: {
 						userListId: userList.id,
 						userId: currentUser.id,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/create.ts b/packages/backend/src/server/api/endpoints/users/lists/create.ts
index e86e4c0ded..952580e639 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/create.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/users/lists/delete.ts b/packages/backend/src/server/api/endpoints/users/lists/delete.ts
index 763f5afd9d..dc0d28a0eb 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/delete.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/delete.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/users/lists/favorite.ts b/packages/backend/src/server/api/endpoints/users/lists/favorite.ts
index 864cdc2ee0..fd142d5a01 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/favorite.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/favorite.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -47,7 +47,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		private idService: IdService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const userListExist = await this.userListsRepository.exist({
+			const userListExist = await this.userListsRepository.exists({
 				where: {
 					id: ps.listId,
 					isPublic: true,
@@ -58,7 +58,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				throw new ApiError(meta.errors.noSuchList);
 			}
 
-			const exist = await this.userListFavoritesRepository.exist({
+			const exist = await this.userListFavoritesRepository.exists({
 				where: {
 					userId: me.id,
 					userListId: ps.listId,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts b/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts
index 985141515e..6d6e8d34ea 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/get-memberships.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -46,7 +46,7 @@ export const meta = {
 				},
 				user: {
 					type: 'object',
-					ref: 'User',
+					ref: 'UserLite',
 				},
 				withReplies: {
 					type: 'boolean',
diff --git a/packages/backend/src/server/api/endpoints/users/lists/list.ts b/packages/backend/src/server/api/endpoints/users/lists/list.ts
index 0e86dd3a68..4241ef1cd0 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/list.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/list.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/users/lists/pull.ts b/packages/backend/src/server/api/endpoints/users/lists/pull.ts
index e90122224c..94f06f3bea 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/pull.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/pull.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts
index c4ceec575b..c717b3959c 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/push.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -104,7 +104,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			// Check blocking
 			if (user.id !== me.id) {
-				const blockExist = await this.blockingsRepository.exist({
+				const blockExist = await this.blockingsRepository.exists({
 					where: {
 						blockerId: user.id,
 						blockeeId: me.id,
@@ -115,7 +115,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				}
 			}
 
-			const exist = await this.userListMembershipsRepository.exist({
+			const exist = await this.userListMembershipsRepository.exists({
 				where: {
 					userListId: userList.id,
 					userId: user.id,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts
index df44870b04..8756801fe4 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/show.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -74,7 +74,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 					userListId: ps.listId,
 				});
 				if (me !== null) {
-					additionalProperties.isLiked = await this.userListFavoritesRepository.exist({
+					additionalProperties.isLiked = await this.userListFavoritesRepository.exists({
 						where: {
 							userId: me.id,
 							userListId: ps.listId,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts b/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts
index d51d57343e..3f4bd5af8c 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/unfavorite.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -45,7 +45,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		private userListFavoritesRepository: UserListFavoritesRepository,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const userListExist = await this.userListsRepository.exist({
+			const userListExist = await this.userListsRepository.exists({
 				where: {
 					id: ps.listId,
 					isPublic: true,
diff --git a/packages/backend/src/server/api/endpoints/users/lists/update-membership.ts b/packages/backend/src/server/api/endpoints/users/lists/update-membership.ts
index b69465b940..3948ae1685 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/update-membership.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/update-membership.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/users/lists/update.ts b/packages/backend/src/server/api/endpoints/users/lists/update.ts
index eb6cfbaf26..a38f84d7b0 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/update.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/update.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts
index b485126ed8..cc76c12f1d 100644
--- a/packages/backend/src/server/api/endpoints/users/notes.ts
+++ b/packages/backend/src/server/api/endpoints/users/notes.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/users/pages.ts b/packages/backend/src/server/api/endpoints/users/pages.ts
index cf2f274c70..bb7de0e0b5 100644
--- a/packages/backend/src/server/api/endpoints/users/pages.ts
+++ b/packages/backend/src/server/api/endpoints/users/pages.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts
index 372ab80c4c..aca883a052 100644
--- a/packages/backend/src/server/api/endpoints/users/reactions.ts
+++ b/packages/backend/src/server/api/endpoints/users/reactions.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -9,6 +9,9 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
 import { QueryService } from '@/core/QueryService.js';
 import { NoteReactionEntityService } from '@/core/entities/NoteReactionEntityService.js';
 import { DI } from '@/di-symbols.js';
+import { CacheService } from '@/core/CacheService.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { RoleService } from '@/core/RoleService.js';
 import { ApiError } from '../../error.js';
 
 export const meta = {
@@ -34,6 +37,11 @@ export const meta = {
 			code: 'REACTIONS_NOT_PUBLIC',
 			id: '673a7dd2-6924-1093-e0c0-e68456ceae5c',
 		},
+		isRemoteUser: {
+			message: 'Currently unavailable to display reactions of remote users. See https://github.com/misskey-dev/misskey/issues/12964',
+			code: 'IS_REMOTE_USER',
+			id: '6b95fa98-8cf9-2350-e284-f0ffdb54a805',
+		},
 	},
 } as const;
 
@@ -59,14 +67,24 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 		@Inject(DI.noteReactionsRepository)
 		private noteReactionsRepository: NoteReactionsRepository,
 
+		private cacheService: CacheService,
+		private userEntityService: UserEntityService,
 		private noteReactionEntityService: NoteReactionEntityService,
 		private queryService: QueryService,
+		private roleService: RoleService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
-			const profile = await this.userProfilesRepository.findOneByOrFail({ userId: ps.userId });
+			const iAmModerator = me ? await this.roleService.isModerator(me) : false; // Moderators can see reactions of all users
+			if (!iAmModerator) {
+				const user = await this.cacheService.findUserById(ps.userId);
+				if (this.userEntityService.isRemoteUser(user)) {
+					throw new ApiError(meta.errors.isRemoteUser);
+				}
 
-			if ((me == null || me.id !== ps.userId) && !profile.publicReactions) {
-				throw new ApiError(meta.errors.reactionsNotPublic);
+				const profile = await this.userProfilesRepository.findOneByOrFail({ userId: ps.userId });
+				if ((me == null || me.id !== ps.userId) && !profile.publicReactions) {
+					throw new ApiError(meta.errors.reactionsNotPublic);
+				}
 			}
 
 			const query = this.queryService.makePaginationQuery(this.noteReactionsRepository.createQueryBuilder('reaction'),
@@ -80,7 +98,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				.limit(ps.limit)
 				.getMany();
 
-			return await Promise.all(reactions.map(reaction => this.noteReactionEntityService.pack(reaction, me, { withNote: true })));
+			return await this.noteReactionEntityService.packMany(reactions, me, { withNote: true });
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/users/recommendation.ts b/packages/backend/src/server/api/endpoints/users/recommendation.ts
index 1b30e99b15..5b3b4527f7 100644
--- a/packages/backend/src/server/api/endpoints/users/recommendation.ts
+++ b/packages/backend/src/server/api/endpoints/users/recommendation.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -76,7 +76,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 
 			const users = await query.limit(ps.limit).offset(ps.offset).getMany();
 
-			return await this.userEntityService.packMany(users, me, { detail: true });
+			return await this.userEntityService.packMany(users, me, { schema: 'UserDetailed' });
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/users/relation.ts b/packages/backend/src/server/api/endpoints/users/relation.ts
index 26b61c9fb2..6a5b2262fa 100644
--- a/packages/backend/src/server/api/endpoints/users/relation.ts
+++ b/packages/backend/src/server/api/endpoints/users/relation.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts
index bdaf78758b..0685858d77 100644
--- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts
+++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts
index 4bf25d9fbb..7b3bdab327 100644
--- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts
+++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -131,7 +131,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 					.getMany();
 			}
 
-			return await this.userEntityService.packMany(users, me, { detail: !!ps.detail });
+			return await this.userEntityService.packMany(users, me, { schema: ps.detail ? 'UserDetailed' : 'UserLite' });
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts
index 32b5c12372..df9d9f6312 100644
--- a/packages/backend/src/server/api/endpoints/users/search.ts
+++ b/packages/backend/src/server/api/endpoints/users/search.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -141,7 +141,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				}
 			}
 
-			return await this.userEntityService.packMany(users, me, { detail: ps.detail });
+			return await this.userEntityService.packMany(users, me, { schema: ps.detail ? 'UserDetailed' : 'UserLite' });
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts
index 389497301d..bd81989cb9 100644
--- a/packages/backend/src/server/api/endpoints/users/show.ts
+++ b/packages/backend/src/server/api/endpoints/users/show.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -116,7 +116,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				}
 
 				return await Promise.all(_users.map(u => this.userEntityService.pack(u, me, {
-					detail: true,
+					schema: 'UserDetailed',
 				})));
 			} else {
 				// Lookup user
@@ -146,7 +146,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
 				}
 
 				return await this.userEntityService.pack(user, me, {
-					detail: true,
+					schema: 'UserDetailed',
 				});
 			}
 		});
diff --git a/packages/backend/src/server/api/endpoints/users/update-memo.ts b/packages/backend/src/server/api/endpoints/users/update-memo.ts
index b3f67815ef..5a10de0c40 100644
--- a/packages/backend/src/server/api/endpoints/users/update-memo.ts
+++ b/packages/backend/src/server/api/endpoints/users/update-memo.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/error.ts b/packages/backend/src/server/api/error.ts
index 6506565a0d..2f8322a568 100644
--- a/packages/backend/src/server/api/error.ts
+++ b/packages/backend/src/server/api/error.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/mastodon/converters.ts b/packages/backend/src/server/api/mastodon/converters.ts
index cce7f798fe..20fccec21d 100644
--- a/packages/backend/src/server/api/mastodon/converters.ts
+++ b/packages/backend/src/server/api/mastodon/converters.ts
@@ -1,6 +1,6 @@
 import { Inject, Injectable } from '@nestjs/common';
 import { Entity } from 'megalodon';
-import mfm from '@sharkey/sfm-js';
+import mfm from '@transfem-org/sfm-js';
 import { DI } from '@/di-symbols.js';
 import { MfmService } from '@/core/MfmService.js';
 import type { Config } from '@/config.js';
@@ -9,9 +9,9 @@ import type { MiUser } from '@/models/User.js';
 import type { NoteEditRepository, NotesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
 import { awaitAll } from '@/misc/prelude/await-all.js';
 import { CustomEmojiService } from '@/core/CustomEmojiService.js';
-import { GetterService } from '../GetterService.js';
 import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
 import { IdService } from '@/core/IdService.js';
+import { GetterService } from '../GetterService.js';
 
 export enum IdConvertType {
     MastodonId,
@@ -94,10 +94,10 @@ export class MastoConverters {
 			text_url: f.url,
 			meta: {
 				width: f.properties.width,
-				height: f.properties.height
+				height: f.properties.height,
 			},
 			description: f.comment ? f.comment : null,
-			blurhash: f.blurhash ? f.blurhash : null
+			blurhash: f.blurhash ? f.blurhash : null,
 		};
 	}
 
@@ -185,7 +185,7 @@ export class MastoConverters {
 				sensitive: files.then(files => files.length > 0 ? files.some((f) => f.isSensitive) : false),
 				spoiler_text: edit.cw ?? '',
 				poll: null,
-				media_attachments: files.then(files => files.length > 0 ? files.map((f) => this.encodeFile(f)) : [])
+				media_attachments: files.then(files => files.length > 0 ? files.map((f) => this.encodeFile(f)) : []),
 			};
 			lastDate = edit.updatedAt;
 			history.push(awaitAll(item));
@@ -278,7 +278,7 @@ export class MastoConverters {
 			reactions: status.emoji_reactions,
 			emoji_reactions: status.emoji_reactions,
 			bookmarked: false,
-			quote: isQuote ? await this.convertReblog(status.reblog) : null,
+			quote: isQuote ? await this.convertReblog(status.reblog) : false,
 			edited_at: note.updatedAt?.toISOString(),
 		});
 	}
diff --git a/packages/backend/src/server/api/openapi/OpenApiServerService.ts b/packages/backend/src/server/api/openapi/OpenApiServerService.ts
index cb22d0f7c9..5210e4d2bc 100644
--- a/packages/backend/src/server/api/openapi/OpenApiServerService.ts
+++ b/packages/backend/src/server/api/openapi/OpenApiServerService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/openapi/errors.ts b/packages/backend/src/server/api/openapi/errors.ts
index 84c3c638fa..7c50122f90 100644
--- a/packages/backend/src/server/api/openapi/errors.ts
+++ b/packages/backend/src/server/api/openapi/errors.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/openapi/gen-spec.ts b/packages/backend/src/server/api/openapi/gen-spec.ts
index 0e71510b48..b5f91ff542 100644
--- a/packages/backend/src/server/api/openapi/gen-spec.ts
+++ b/packages/backend/src/server/api/openapi/gen-spec.ts
@@ -1,16 +1,16 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import type { Config } from '@/config.js';
 import endpoints, { IEndpoint } from '../endpoints.js';
 import { errors as basicErrors } from './errors.js';
-import { schemas, convertSchemaToOpenApiSchema } from './schemas.js';
+import { getSchemas, convertSchemaToOpenApiSchema } from './schemas.js';
 
-export function genOpenapiSpec(config: Config) {
+export function genOpenapiSpec(config: Config, includeSelfRef = false) {
 	const spec = {
-		openapi: '3.0.0',
+		openapi: '3.1.0',
 
 		info: {
 			version: config.version,
@@ -20,7 +20,7 @@ export function genOpenapiSpec(config: Config) {
 
 		externalDocs: {
 			description: 'Repository',
-			url: 'https://github.com/misskey-dev/misskey',
+			url: 'https://activitypub.software/TransFem-org/Sharkey',
 		},
 
 		servers: [{
@@ -30,7 +30,7 @@ export function genOpenapiSpec(config: Config) {
 		paths: {} as any,
 
 		components: {
-			schemas: schemas,
+			schemas: getSchemas(includeSelfRef),
 
 			securitySchemes: {
 				bearerAuth: {
@@ -56,7 +56,7 @@ export function genOpenapiSpec(config: Config) {
 			}
 		}
 
-		const resSchema = endpoint.meta.res ? convertSchemaToOpenApiSchema(endpoint.meta.res) : {};
+		const resSchema = endpoint.meta.res ? convertSchemaToOpenApiSchema(endpoint.meta.res, 'res', includeSelfRef) : {};
 
 		let desc = (endpoint.meta.description ? endpoint.meta.description : 'No description provided.') + '\n\n';
 
@@ -71,7 +71,7 @@ export function genOpenapiSpec(config: Config) {
 		}
 
 		const requestType = endpoint.meta.requireFile ? 'multipart/form-data' : 'application/json';
-		const schema = { ...endpoint.params };
+		const schema = { ...convertSchemaToOpenApiSchema(endpoint.params, 'param', false) };
 
 		if (endpoint.meta.requireFile) {
 			schema.properties = {
@@ -98,7 +98,7 @@ export function genOpenapiSpec(config: Config) {
 			description: desc,
 			externalDocs: {
 				description: 'Source code',
-				url: `https://github.com/misskey-dev/misskey/blob/develop/packages/backend/src/server/api/endpoints/${endpoint.name}.ts`,
+				url: `https://activitypub.software/TransFem-org/Sharkey/-/tree/develop/packages/backend/src/server/api/endpoints/${endpoint.name}.ts`,
 			},
 			...(endpoint.meta.tags ? {
 				tags: [endpoint.meta.tags[0]],
@@ -210,7 +210,9 @@ export function genOpenapiSpec(config: Config) {
 		};
 
 		spec.paths['/' + endpoint.name] = {
-			...(endpoint.meta.allowGet ? { get: info } : {}),
+			...(endpoint.meta.allowGet ? {
+				get: info,
+			} : {}),
 			post: info,
 		};
 	}
diff --git a/packages/backend/src/server/api/openapi/schemas.ts b/packages/backend/src/server/api/openapi/schemas.ts
index 2716f5f162..eb854a7141 100644
--- a/packages/backend/src/server/api/openapi/schemas.ts
+++ b/packages/backend/src/server/api/openapi/schemas.ts
@@ -1,37 +1,40 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import type { Schema } from '@/misc/json-schema.js';
 import { refs } from '@/misc/json-schema.js';
 
-export function convertSchemaToOpenApiSchema(schema: Schema) {
-	// optional, refはスキーマ定義に含まれないので分離しておく
+export function convertSchemaToOpenApiSchema(schema: Schema, type: 'param' | 'res', includeSelfRef: boolean): any {
+	// optional, nullable, refはスキーマ定義に含まれないので分離しておく
 	// eslint-disable-next-line @typescript-eslint/no-unused-vars
-	const { optional, ref, ...res }: any = schema;
+	const { optional, nullable, ref, selfRef, ...res }: any = schema;
 
 	if (schema.type === 'object' && schema.properties) {
-		const required = Object.entries(schema.properties).filter(([k, v]) => !v.optional).map(([k]) => k);
-		if (required.length > 0) {
+		if (type === 'res') {
+			const required = Object.entries(schema.properties).filter(([k, v]) => !v.optional).map(([k]) => k);
+			if (required.length > 0) {
 			// 空配列は許可されない
-			res.required = required;
+				res.required = required;
+			}
 		}
 
 		for (const k of Object.keys(schema.properties)) {
-			res.properties[k] = convertSchemaToOpenApiSchema(schema.properties[k]);
+			res.properties[k] = convertSchemaToOpenApiSchema(schema.properties[k], type, includeSelfRef);
 		}
 	}
 
 	if (schema.type === 'array' && schema.items) {
-		res.items = convertSchemaToOpenApiSchema(schema.items);
+		res.items = convertSchemaToOpenApiSchema(schema.items, type, includeSelfRef);
 	}
 
-	if (schema.anyOf) res.anyOf = schema.anyOf.map(convertSchemaToOpenApiSchema);
-	if (schema.oneOf) res.oneOf = schema.oneOf.map(convertSchemaToOpenApiSchema);
-	if (schema.allOf) res.allOf = schema.allOf.map(convertSchemaToOpenApiSchema);
+	for (const o of ['anyOf', 'oneOf', 'allOf'] as const) {
+		// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+		if (o in schema) res[o] = schema[o]!.map(schema => convertSchemaToOpenApiSchema(schema, type, includeSelfRef));
+	}
 
-	if (schema.ref) {
+	if (type === 'res' && schema.ref && (!schema.selfRef || includeSelfRef)) {
 		const $ref = `#/components/schemas/${schema.ref}`;
 		if (schema.nullable || schema.optional) {
 			res.allOf = [{ $ref }];
@@ -40,38 +43,48 @@ export function convertSchemaToOpenApiSchema(schema: Schema) {
 		}
 	}
 
+	if (schema.nullable) {
+		if (Array.isArray(schema.type) && !schema.type.includes('null')) {
+			res.type.push('null');
+		} else if (typeof schema.type === 'string') {
+			res.type = [res.type, 'null'];
+		}
+	}
+
 	return res;
 }
 
-export const schemas = {
-	Error: {
-		type: 'object',
-		properties: {
-			error: {
-				type: 'object',
-				description: 'An error object.',
-				properties: {
-					code: {
-						type: 'string',
-						description: 'An error code. Unique within the endpoint.',
-					},
-					message: {
-						type: 'string',
-						description: 'An error message.',
-					},
-					id: {
-						type: 'string',
-						format: 'uuid',
-						description: 'An error ID. This ID is static.',
+export function getSchemas(includeSelfRef: boolean) {
+	return {
+		Error: {
+			type: 'object',
+			properties: {
+				error: {
+					type: 'object',
+					description: 'An error object.',
+					properties: {
+						code: {
+							type: 'string',
+							description: 'An error code. Unique within the endpoint.',
+						},
+						message: {
+							type: 'string',
+							description: 'An error message.',
+						},
+						id: {
+							type: 'string',
+							format: 'uuid',
+							description: 'An error ID. This ID is static.',
+						},
 					},
+					required: ['code', 'id', 'message'],
 				},
-				required: ['code', 'id', 'message'],
 			},
+			required: ['error'],
 		},
-		required: ['error'],
-	},
 
-	...Object.fromEntries(
-		Object.entries(refs).map(([key, schema]) => [key, convertSchemaToOpenApiSchema(schema)]),
-	),
-};
+		...Object.fromEntries(
+			Object.entries(refs).map(([key, schema]) => [key, convertSchemaToOpenApiSchema(schema, 'res', includeSelfRef)]),
+		),
+	};
+}
diff --git a/packages/backend/src/server/api/stream/ChannelsService.ts b/packages/backend/src/server/api/stream/ChannelsService.ts
index 3fc3f4d31a..83c5fcdf52 100644
--- a/packages/backend/src/server/api/stream/ChannelsService.ts
+++ b/packages/backend/src/server/api/stream/ChannelsService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -20,6 +20,8 @@ import { AntennaChannelService } from './channels/antenna.js';
 import { DriveChannelService } from './channels/drive.js';
 import { HashtagChannelService } from './channels/hashtag.js';
 import { RoleTimelineChannelService } from './channels/role-timeline.js';
+import { ReversiChannelService } from './channels/reversi.js';
+import { ReversiGameChannelService } from './channels/reversi-game.js';
 import { type MiChannelService } from './channel.js';
 
 @Injectable()
@@ -40,6 +42,8 @@ export class ChannelsService {
 		private serverStatsChannelService: ServerStatsChannelService,
 		private queueStatsChannelService: QueueStatsChannelService,
 		private adminChannelService: AdminChannelService,
+		private reversiChannelService: ReversiChannelService,
+		private reversiGameChannelService: ReversiGameChannelService,
 	) {
 	}
 
@@ -61,6 +65,8 @@ export class ChannelsService {
 			case 'serverStats': return this.serverStatsChannelService;
 			case 'queueStats': return this.queueStatsChannelService;
 			case 'admin': return this.adminChannelService;
+			case 'reversi': return this.reversiChannelService;
+			case 'reversiGame': return this.reversiGameChannelService;
 
 			default:
 				throw new Error(`no such channel: ${name}`);
diff --git a/packages/backend/src/server/api/stream/Connection.ts b/packages/backend/src/server/api/stream/Connection.ts
index a89fbcc5e5..41c0feccc7 100644
--- a/packages/backend/src/server/api/stream/Connection.ts
+++ b/packages/backend/src/server/api/stream/Connection.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts
index 80df3803eb..44a143538b 100644
--- a/packages/backend/src/server/api/stream/channel.ts
+++ b/packages/backend/src/server/api/stream/channel.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/stream/channels/admin.ts b/packages/backend/src/server/api/stream/channels/admin.ts
index b8f369ce84..92b6d2ac04 100644
--- a/packages/backend/src/server/api/stream/channels/admin.ts
+++ b/packages/backend/src/server/api/stream/channels/admin.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/stream/channels/antenna.ts b/packages/backend/src/server/api/stream/channels/antenna.ts
index 200db8eb0e..135d162e63 100644
--- a/packages/backend/src/server/api/stream/channels/antenna.ts
+++ b/packages/backend/src/server/api/stream/channels/antenna.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/stream/channels/bubble-timeline.ts b/packages/backend/src/server/api/stream/channels/bubble-timeline.ts
index 4f8809edbe..2d85d65ba5 100644
--- a/packages/backend/src/server/api/stream/channels/bubble-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/bubble-timeline.ts
@@ -78,6 +78,9 @@ class BubbleTimelineChannel extends Channel {
 		// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
 		if (isUserRelated(note, this.userIdsWhoBlockingMe)) return;
 
+		if (note.renote && !note.text && note.renote.mentions?.some(mention => this.userIdsWhoMeMuting.has(mention))) return;
+		if (note.mentions?.some(mention => this.userIdsWhoMeMuting.has(mention))) return;
+
 		if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
 
 		if (this.user && note.renoteId && !note.text) {
diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts
index 20275249b8..90ee1ecda5 100644
--- a/packages/backend/src/server/api/stream/channels/channel.ts
+++ b/packages/backend/src/server/api/stream/channels/channel.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/stream/channels/drive.ts b/packages/backend/src/server/api/stream/channels/drive.ts
index 4bf34a72c9..0d9b486305 100644
--- a/packages/backend/src/server/api/stream/channels/drive.ts
+++ b/packages/backend/src/server/api/stream/channels/drive.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts
index e05e380aae..fc25724782 100644
--- a/packages/backend/src/server/api/stream/channels/global-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -74,6 +74,9 @@ class GlobalTimelineChannel extends Channel {
 		// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
 		if (isUserRelated(note, this.userIdsWhoBlockingMe)) return;
 
+		if (note.renote && !note.text && note.renote.mentions?.some(mention => this.userIdsWhoMeMuting.has(mention))) return;
+		if (note.mentions?.some(mention => this.userIdsWhoMeMuting.has(mention))) return;
+
 		if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
 
 		if (this.user && note.renoteId && !note.text) {
diff --git a/packages/backend/src/server/api/stream/channels/hashtag.ts b/packages/backend/src/server/api/stream/channels/hashtag.ts
index 3d4f2fc528..377b1a0162 100644
--- a/packages/backend/src/server/api/stream/channels/hashtag.ts
+++ b/packages/backend/src/server/api/stream/channels/hashtag.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts
index 3b499b7bf3..0a4852ee8d 100644
--- a/packages/backend/src/server/api/stream/channels/home-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -73,13 +73,24 @@ class HomeTimelineChannel extends Channel {
 
 		if (note.user.isSilenced && !this.following[note.userId] && note.userId !== this.user!.id) return;
 
-		if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return;
+		// 純粋なリノート(引用リノートでないリノート)の場合
+		if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && note.poll == null) {
+			if (!this.withRenotes) return;
+			if (note.renote.reply) {
+				const reply = note.renote.reply;
+				// 自分のフォローしていないユーザーの visibility: followers な投稿への返信のリノートは弾く
+				if (reply.visibility === 'followers' && !Object.hasOwn(this.following, reply.userId)) return;
+			}
+		}
 
 		// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
 		if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
 		// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
 		if (isUserRelated(note, this.userIdsWhoBlockingMe)) return;
 
+		if (note.renote && !note.text && note.renote.mentions?.some(mention => this.userIdsWhoMeMuting.has(mention))) return;
+		if (note.mentions?.some(mention => this.userIdsWhoMeMuting.has(mention))) return;
+
 		if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
 
 		if (this.user && note.renoteId && !note.text) {
diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
index 26cbbebe83..02786e9e16 100644
--- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -97,6 +97,9 @@ class HybridTimelineChannel extends Channel {
 		// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
 		if (isUserRelated(note, this.userIdsWhoBlockingMe)) return;
 
+		if (note.renote && !note.text && note.renote.mentions?.some(mention => this.userIdsWhoMeMuting.has(mention))) return;
+		if (note.mentions?.some(mention => this.userIdsWhoMeMuting.has(mention))) return;
+
 		if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
 
 		if (this.user && note.renoteId && !note.text) {
diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts
index 40342b6c7b..71b5675402 100644
--- a/packages/backend/src/server/api/stream/channels/local-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -73,6 +73,9 @@ class LocalTimelineChannel extends Channel {
 		// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
 		if (isUserRelated(note, this.userIdsWhoBlockingMe)) return;
 
+		if (note.renote && !note.text && note.renote.mentions?.some(mention => this.userIdsWhoMeMuting.has(mention))) return;
+		if (note.mentions?.some(mention => this.userIdsWhoMeMuting.has(mention))) return;
+
 		if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return;
 
 		if (this.user && note.renoteId && !note.text) {
diff --git a/packages/backend/src/server/api/stream/channels/main.ts b/packages/backend/src/server/api/stream/channels/main.ts
index ab605e3ec5..a12976d69d 100644
--- a/packages/backend/src/server/api/stream/channels/main.ts
+++ b/packages/backend/src/server/api/stream/channels/main.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/stream/channels/queue-stats.ts b/packages/backend/src/server/api/stream/channels/queue-stats.ts
index 5ceb2c3bbc..061aa76904 100644
--- a/packages/backend/src/server/api/stream/channels/queue-stats.ts
+++ b/packages/backend/src/server/api/stream/channels/queue-stats.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/stream/channels/reversi-game.ts b/packages/backend/src/server/api/stream/channels/reversi-game.ts
new file mode 100644
index 0000000000..f4a3a09367
--- /dev/null
+++ b/packages/backend/src/server/api/stream/channels/reversi-game.ts
@@ -0,0 +1,111 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import type { MiReversiGame } from '@/models/_.js';
+import { DI } from '@/di-symbols.js';
+import { bindThis } from '@/decorators.js';
+import { ReversiService } from '@/core/ReversiService.js';
+import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
+import Channel, { type MiChannelService } from '../channel.js';
+
+class ReversiGameChannel extends Channel {
+	public readonly chName = 'reversiGame';
+	public static shouldShare = false;
+	public static requireCredential = false as const;
+	private gameId: MiReversiGame['id'] | null = null;
+
+	constructor(
+		private reversiService: ReversiService,
+		private reversiGameEntityService: ReversiGameEntityService,
+
+		id: string,
+		connection: Channel['connection'],
+	) {
+		super(id, connection);
+	}
+
+	@bindThis
+	public async init(params: any) {
+		this.gameId = params.gameId as string;
+
+		this.subscriber.on(`reversiGameStream:${this.gameId}`, this.send);
+	}
+
+	@bindThis
+	public onMessage(type: string, body: any) {
+		switch (type) {
+			case 'ready': this.ready(body); break;
+			case 'updateSettings': this.updateSettings(body.key, body.value); break;
+			case 'cancel': this.cancelGame(); break;
+			case 'putStone': this.putStone(body.pos, body.id); break;
+			case 'claimTimeIsUp': this.claimTimeIsUp(); break;
+		}
+	}
+
+	@bindThis
+	private async updateSettings(key: string, value: any) {
+		if (this.user == null) return;
+
+		this.reversiService.updateSettings(this.gameId!, this.user, key, value);
+	}
+
+	@bindThis
+	private async ready(ready: boolean) {
+		if (this.user == null) return;
+
+		this.reversiService.gameReady(this.gameId!, this.user, ready);
+	}
+
+	@bindThis
+	private async cancelGame() {
+		if (this.user == null) return;
+
+		this.reversiService.cancelGame(this.gameId!, this.user);
+	}
+
+	@bindThis
+	private async putStone(pos: number, id: string) {
+		if (this.user == null) return;
+
+		this.reversiService.putStoneToGame(this.gameId!, this.user, pos, id);
+	}
+
+	@bindThis
+	private async claimTimeIsUp() {
+		if (this.user == null) return;
+
+		this.reversiService.checkTimeout(this.gameId!);
+	}
+
+	@bindThis
+	public dispose() {
+		// Unsubscribe events
+		this.subscriber.off(`reversiGameStream:${this.gameId}`, this.send);
+	}
+}
+
+@Injectable()
+export class ReversiGameChannelService implements MiChannelService<false> {
+	public readonly shouldShare = ReversiGameChannel.shouldShare;
+	public readonly requireCredential = ReversiGameChannel.requireCredential;
+	public readonly kind = ReversiGameChannel.kind;
+
+	constructor(
+		private reversiService: ReversiService,
+		private reversiGameEntityService: ReversiGameEntityService,
+	) {
+	}
+
+	@bindThis
+	public create(id: string, connection: Channel['connection']): ReversiGameChannel {
+		return new ReversiGameChannel(
+			this.reversiService,
+			this.reversiGameEntityService,
+			id,
+			connection,
+		);
+	}
+}
diff --git a/packages/backend/src/server/api/stream/channels/reversi.ts b/packages/backend/src/server/api/stream/channels/reversi.ts
new file mode 100644
index 0000000000..3998a0fd36
--- /dev/null
+++ b/packages/backend/src/server/api/stream/channels/reversi.ts
@@ -0,0 +1,52 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import { bindThis } from '@/decorators.js';
+import Channel, { type MiChannelService } from '../channel.js';
+
+class ReversiChannel extends Channel {
+	public readonly chName = 'reversi';
+	public static shouldShare = true;
+	public static requireCredential = true as const;
+	public static kind = 'read:account';
+
+	constructor(
+		id: string,
+		connection: Channel['connection'],
+	) {
+		super(id, connection);
+	}
+
+	@bindThis
+	public async init(params: any) {
+		this.subscriber.on(`reversiStream:${this.user!.id}`, this.send);
+	}
+
+	@bindThis
+	public dispose() {
+		// Unsubscribe events
+		this.subscriber.off(`reversiStream:${this.user!.id}`, this.send);
+	}
+}
+
+@Injectable()
+export class ReversiChannelService implements MiChannelService<true> {
+	public readonly shouldShare = ReversiChannel.shouldShare;
+	public readonly requireCredential = ReversiChannel.requireCredential;
+	public readonly kind = ReversiChannel.kind;
+
+	constructor(
+	) {
+	}
+
+	@bindThis
+	public create(id: string, connection: Channel['connection']): ReversiChannel {
+		return new ReversiChannel(
+			id,
+			connection,
+		);
+	}
+}
diff --git a/packages/backend/src/server/api/stream/channels/role-timeline.ts b/packages/backend/src/server/api/stream/channels/role-timeline.ts
index b3bbb77dbf..80aab4b35e 100644
--- a/packages/backend/src/server/api/stream/channels/role-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/role-timeline.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/stream/channels/server-stats.ts b/packages/backend/src/server/api/stream/channels/server-stats.ts
index 615b6946cc..eb4d8c9992 100644
--- a/packages/backend/src/server/api/stream/channels/server-stats.ts
+++ b/packages/backend/src/server/api/stream/channels/server-stats.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts
index 909b5a5e03..f7bb106c03 100644
--- a/packages/backend/src/server/api/stream/channels/user-list.ts
+++ b/packages/backend/src/server/api/stream/channels/user-list.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -21,6 +21,7 @@ class UserListChannel extends Channel {
 	private membershipsMap: Record<string, Pick<MiUserListMembership, 'withReplies'> | undefined> = {};
 	private listUsersClock: NodeJS.Timeout;
 	private withFiles: boolean;
+	private withRenotes: boolean;
 
 	constructor(
 		private userListsRepository: UserListsRepository,
@@ -39,9 +40,10 @@ class UserListChannel extends Channel {
 	public async init(params: any) {
 		this.listId = params.listId as string;
 		this.withFiles = params.withFiles ?? false;
+		this.withRenotes = params.withRenotes ?? true;
 
 		// Check existence and owner
-		const listExist = await this.userListsRepository.exist({
+		const listExist = await this.userListsRepository.exists({
 			where: {
 				id: this.listId,
 				userId: this.user!.id,
@@ -104,6 +106,8 @@ class UserListChannel extends Channel {
 			}
 		}
 
+		if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return;
+
 		// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
 		if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
 		// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
diff --git a/packages/backend/src/server/oauth/OAuth2ProviderService.ts b/packages/backend/src/server/oauth/OAuth2ProviderService.ts
index 6de9038620..6598aa9891 100644
--- a/packages/backend/src/server/oauth/OAuth2ProviderService.ts
+++ b/packages/backend/src/server/oauth/OAuth2ProviderService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/web/ClientLoggerService.ts b/packages/backend/src/server/web/ClientLoggerService.ts
index 213266f59c..83d8b5bc38 100644
--- a/packages/backend/src/server/web/ClientLoggerService.ts
+++ b/packages/backend/src/server/web/ClientLoggerService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts
index aa696046ea..cb41c4f338 100644
--- a/packages/backend/src/server/web/ClientServerService.ts
+++ b/packages/backend/src/server/web/ClientServerService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -19,6 +19,7 @@ import fastifyView from '@fastify/view';
 import fastifyCookie from '@fastify/cookie';
 import fastifyProxy from '@fastify/http-proxy';
 import vary from 'vary';
+import htmlSafeJsonStringify from 'htmlescape';
 import type { Config } from '@/config.js';
 import { getNoteSummary } from '@/misc/get-note-summary.js';
 import { DI } from '@/di-symbols.js';
@@ -28,15 +29,17 @@ import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, Obj
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import { PageEntityService } from '@/core/entities/PageEntityService.js';
+import { MetaEntityService } from '@/core/entities/MetaEntityService.js';
 import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
 import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
 import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
-import type { ChannelsRepository, ClipsRepository, FlashsRepository, GalleryPostsRepository, MiMeta, NotesRepository, PagesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
+import type { ChannelsRepository, ClipsRepository, FlashsRepository, GalleryPostsRepository, MiMeta, NotesRepository, PagesRepository, ReversiGamesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
 import type Logger from '@/logger.js';
-import { deepClone } from '@/misc/clone.js';
+import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js';
 import { bindThis } from '@/decorators.js';
 import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
 import { RoleService } from '@/core/RoleService.js';
+import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
 import { FeedService } from './FeedService.js';
 import { UrlPreviewService } from './UrlPreviewService.js';
 import { ClientLoggerService } from './ClientLoggerService.js';
@@ -50,6 +53,7 @@ const clientAssets = `${_dirname}/../../../../frontend/assets/`;
 const assets = `${_dirname}/../../../../../built/_frontend_dist_/`;
 const swAssets = `${_dirname}/../../../../../built/_sw_dist_/`;
 const viteOut = `${_dirname}/../../../../../built/_vite_/`;
+const tarball = `${_dirname}/../../../../../built/tarball/`;
 
 @Injectable()
 export class ClientServerService {
@@ -83,13 +87,18 @@ export class ClientServerService {
 		@Inject(DI.flashsRepository)
 		private flashsRepository: FlashsRepository,
 
+		@Inject(DI.reversiGamesRepository)
+		private reversiGamesRepository: ReversiGamesRepository,
+
 		private flashEntityService: FlashEntityService,
 		private userEntityService: UserEntityService,
 		private noteEntityService: NoteEntityService,
 		private pageEntityService: PageEntityService,
+		private metaEntityService: MetaEntityService,
 		private galleryPostEntityService: GalleryPostEntityService,
 		private clipEntityService: ClipEntityService,
 		private channelEntityService: ChannelEntityService,
+		private reversiGameEntityService: ReversiGameEntityService,
 		private metaService: MetaService,
 		private urlPreviewService: UrlPreviewService,
 		private feedService: FeedService,
@@ -168,7 +177,7 @@ export class ClientServerService {
 	}
 
 	@bindThis
-	private generateCommonPugData(meta: MiMeta) {
+	private async generateCommonPugData(meta: MiMeta) {
 		return {
 			instanceName: meta.name ?? 'Sharkey',
 			icon: meta.iconUrl,
@@ -179,6 +188,8 @@ export class ClientServerService {
 			notFoundImageUrl: meta.notFoundImageUrl ?? 'https://launcher.moe/missingpage.webp',
 			instanceUrl: this.config.url,
 			randomMOTD: this.config.customMOTD ? this.config.customMOTD[Math.floor(Math.random() * this.config.customMOTD.length)] : undefined,
+			metaJson: htmlSafeJsonStringify(await this.metaEntityService.packDetailed(meta)),
+			now: Date.now(),
 		};
 	}
 
@@ -250,11 +261,16 @@ export class ClientServerService {
 
 		//#region vite assets
 		if (this.config.clientManifestExists) {
-			fastify.register(fastifyStatic, {
-				root: viteOut,
-				prefix: '/vite/',
-				maxAge: ms('30 days'),
-				decorateReply: false,
+			fastify.register((fastify, options, done) => {
+				fastify.register(fastifyStatic, {
+					root: viteOut,
+					prefix: '/vite/',
+					maxAge: ms('30 days'),
+					immutable: true,
+					decorateReply: false,
+				});
+				fastify.addHook('onRequest', handleRequestRedirectToOmitSearch);
+				done();
 			});
 		} else {
 			const port = (process.env.VITE_PORT ?? '5173');
@@ -289,6 +305,18 @@ export class ClientServerService {
 			decorateReply: false,
 		});
 
+		fastify.register((fastify, options, done) => {
+			fastify.register(fastifyStatic, {
+				root: tarball,
+				prefix: '/tarball/',
+				maxAge: ms('30 days'),
+				immutable: true,
+				decorateReply: false,
+			});
+			fastify.addHook('onRequest', handleRequestRedirectToOmitSearch);
+			done();
+		});
+
 		fastify.get('/favicon.ico', async (request, reply) => {
 			return reply.sendFile('/favicon.ico', staticAssets);
 		});
@@ -327,6 +355,21 @@ export class ClientServerService {
 			});
 		});
 
+		fastify.get<{ Params: { path: string } }>('/tossface/:path(.*)', async (request, reply) => {
+			const path = request.params.path;
+
+			if (!path.match(/^[0-9a-f-]+\.svg$/)) {
+				reply.code(404);
+				return;
+			}
+
+			reply.header('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\'');
+
+			return await reply.sendFile(path, `${_dirname}/../../../../../tossface-emojis/dist`, {
+				maxAge: ms('30 days'),
+			});
+		});
+
 		fastify.get<{ Params: { path: string } }>('/twemoji-badge/:path(.*)', async (request, reply) => {
 			const path = request.params.path;
 
@@ -412,7 +455,7 @@ export class ClientServerService {
 				url: this.config.url,
 				title: meta.name ?? 'Misskey',
 				desc: meta.description,
-				...this.generateCommonPugData(meta),
+				...await this.generateCommonPugData(meta),
 			});
 		};
 
@@ -479,6 +522,8 @@ export class ClientServerService {
 				isSuspended: false,
 			});
 
+			vary(reply.raw, 'Accept');
+
 			if (user != null) {
 				const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id });
 				const meta = await this.metaService.fetch();
@@ -497,7 +542,7 @@ export class ClientServerService {
 					user, profile, me,
 					avatarUrl: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user),
 					sub: request.params.sub,
-					...this.generateCommonPugData(meta),
+					...await this.generateCommonPugData(meta),
 				});
 			} else {
 				// リモートユーザーなので
@@ -518,6 +563,8 @@ export class ClientServerService {
 				return;
 			}
 
+			vary(reply.raw, 'Accept');
+
 			reply.redirect(`/@${user.username}${ user.host == null ? '' : '@' + user.host}`);
 		});
 
@@ -545,7 +592,7 @@ export class ClientServerService {
 					avatarUrl: _note.user.avatarUrl,
 					// TODO: Let locale changeable by instance setting
 					summary: getNoteSummary(_note),
-					...this.generateCommonPugData(meta),
+					...await this.generateCommonPugData(meta),
 				});
 			} else {
 				return await renderBase(reply);
@@ -584,7 +631,7 @@ export class ClientServerService {
 					page: _page,
 					profile,
 					avatarUrl: _page.user.avatarUrl,
-					...this.generateCommonPugData(meta),
+					...await this.generateCommonPugData(meta),
 				});
 			} else {
 				return await renderBase(reply);
@@ -610,7 +657,7 @@ export class ClientServerService {
 					flash: _flash,
 					profile,
 					avatarUrl: _flash.user.avatarUrl,
-					...this.generateCommonPugData(meta),
+					...await this.generateCommonPugData(meta),
 				});
 			} else {
 				return await renderBase(reply);
@@ -636,7 +683,7 @@ export class ClientServerService {
 					clip: _clip,
 					profile,
 					avatarUrl: _clip.user.avatarUrl,
-					...this.generateCommonPugData(meta),
+					...await this.generateCommonPugData(meta),
 				});
 			} else {
 				return await renderBase(reply);
@@ -660,7 +707,7 @@ export class ClientServerService {
 					post: _post,
 					profile,
 					avatarUrl: _post.user.avatarUrl,
-					...this.generateCommonPugData(meta),
+					...await this.generateCommonPugData(meta),
 				});
 			} else {
 				return await renderBase(reply);
@@ -679,7 +726,26 @@ export class ClientServerService {
 				reply.header('Cache-Control', 'public, max-age=15');
 				return await reply.view('channel', {
 					channel: _channel,
-					...this.generateCommonPugData(meta),
+					...await this.generateCommonPugData(meta),
+				});
+			} else {
+				return await renderBase(reply);
+			}
+		});
+
+		// Reversi game
+		fastify.get<{ Params: { game: string; } }>('/reversi/g/:game', async (request, reply) => {
+			const game = await this.reversiGamesRepository.findOneBy({
+				id: request.params.game,
+			});
+
+			if (game) {
+				const _game = await this.reversiGameEntityService.packDetail(game);
+				const meta = await this.metaService.fetch();
+				reply.header('Cache-Control', 'public, max-age=3600');
+				return await reply.view('reversi-game', {
+					game: _game,
+					...await this.generateCommonPugData(meta),
 				});
 			} else {
 				return await renderBase(reply);
diff --git a/packages/backend/src/server/web/FeedService.ts b/packages/backend/src/server/web/FeedService.ts
index aaa9566a76..dc7f6452c8 100644
--- a/packages/backend/src/server/web/FeedService.ts
+++ b/packages/backend/src/server/web/FeedService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/web/UrlPreviewService.ts b/packages/backend/src/server/web/UrlPreviewService.ts
index d590244e34..c6a96e94cb 100644
--- a/packages/backend/src/server/web/UrlPreviewService.ts
+++ b/packages/backend/src/server/web/UrlPreviewService.ts
@@ -1,10 +1,10 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import { Inject, Injectable } from '@nestjs/common';
-import { summaly } from 'summaly';
+import { summaly } from '@misskey-dev/summaly';
 import { DI } from '@/di-symbols.js';
 import type { Config } from '@/config.js';
 import { MetaService } from '@/core/MetaService.js';
diff --git a/packages/backend/src/server/web/bios.css b/packages/backend/src/server/web/bios.css
index c934a55fa9..91d1af10b4 100644
--- a/packages/backend/src/server/web/bios.css
+++ b/packages/backend/src/server/web/bios.css
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  *
  * SPDX-License-Identifier: AGPL-3.0-only
  */
diff --git a/packages/backend/src/server/web/bios.js b/packages/backend/src/server/web/bios.js
index 029eb92aad..9ff5dca72a 100644
--- a/packages/backend/src/server/web/bios.js
+++ b/packages/backend/src/server/web/bios.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js
index aac6689e12..6b9dc0a103 100644
--- a/packages/backend/src/server/web/boot.js
+++ b/packages/backend/src/server/web/boot.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/web/cli.css b/packages/backend/src/server/web/cli.css
index b7737c3f21..4e6136d59c 100644
--- a/packages/backend/src/server/web/cli.css
+++ b/packages/backend/src/server/web/cli.css
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  *
  * SPDX-License-Identifier: AGPL-3.0-only
  */
diff --git a/packages/backend/src/server/web/cli.js b/packages/backend/src/server/web/cli.js
index e63a80327c..30ee77f4d9 100644
--- a/packages/backend/src/server/web/cli.js
+++ b/packages/backend/src/server/web/cli.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/src/server/web/error.css b/packages/backend/src/server/web/error.css
index caa179e25c..6c96241970 100644
--- a/packages/backend/src/server/web/error.css
+++ b/packages/backend/src/server/web/error.css
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  *
  * SPDX-License-Identifier: AGPL-3.0-only
  */
@@ -113,4 +113,4 @@ summary > * {
     details {
         width: 50%;
     }
-}
\ No newline at end of file
+}
diff --git a/packages/backend/src/server/web/style.css b/packages/backend/src/server/web/style.css
index 171827a523..b60a6da49a 100644
--- a/packages/backend/src/server/web/style.css
+++ b/packages/backend/src/server/web/style.css
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  *
  * SPDX-License-Identifier: AGPL-3.0-only
  */
diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug
index c15e123a15..2210e65e7d 100644
--- a/packages/backend/src/server/web/views/base.pug
+++ b/packages/backend/src/server/web/views/base.pug
@@ -18,7 +18,7 @@ doctype html
 
 	 Thank you for using Sharkey!
 	 If you are reading this message... how about joining the development?
-	 https://git.joinsharkey.org/Sharkey/Sharkey
+	 https://activitypub.software/TransFem-org/Sharkey
 
 
 html
@@ -75,6 +75,9 @@ html
 			var CLIENT_ENTRY = "#{clientEntry.file}";
 			window.libopenmpt = window.Module;
 
+		script(type='application/json' id='misskey_meta' data-generated-at=now)
+			!= metaJson
+
 		script
 			include ../boot.js
 
diff --git a/packages/backend/src/server/web/views/error.pug b/packages/backend/src/server/web/views/error.pug
index 00a2a72d7a..39b75abc4c 100644
--- a/packages/backend/src/server/web/views/error.pug
+++ b/packages/backend/src/server/web/views/error.pug
@@ -13,7 +13,7 @@ doctype html
 
 	 Thank you for using Sharkey!
 	 If you are reading this message... how about joining the development?
-	 https://git.joinsharkey.org/Sharkey/Sharkey
+	 https://activitypub.software/TransFem-org/Sharkey
 
 
 html
diff --git a/packages/backend/src/server/web/views/reversi-game.pug b/packages/backend/src/server/web/views/reversi-game.pug
new file mode 100644
index 0000000000..0b5ffb2bb0
--- /dev/null
+++ b/packages/backend/src/server/web/views/reversi-game.pug
@@ -0,0 +1,20 @@
+extends ./base
+
+block vars
+	- const user1 = game.user1;
+	- const user2 = game.user2;
+	- const title = `${user1.username} vs ${user2.username}`;
+	- const url = `${config.url}/reversi/g/${game.id}`;
+
+block title
+	= `${title} | ${instanceName}`
+
+block desc
+	meta(name='description' content='⚫⚪Misskey Reversi⚪⚫')
+
+block og
+	meta(property='og:type'        content='article')
+	meta(property='og:title'       content= title)
+	meta(property='og:description' content='⚫⚪Misskey Reversi⚪⚫')
+	meta(property='og:url'         content= url)
+	meta(property='twitter:card'   content='summary')
diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts
index e55952f296..edcf2530bc 100644
--- a/packages/backend/src/types.ts
+++ b/packages/backend/src/types.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -28,12 +28,21 @@ export const notificationTypes = [
 	'quote',
 	'reaction',
 	'pollEnded',
+	'edited',
 	'receiveFollowRequest',
 	'followRequestAccepted',
 	'roleAssigned',
 	'achievementEarned',
 	'app',
-	'test'] as const;
+	'test',
+] as const;
+
+export const groupedNotificationTypes = [
+	...notificationTypes,
+	'reaction:grouped',
+	'renote:grouped',
+] as const;
+
 export const obsoleteNotificationTypes = ['pollVote', 'groupInvited'] as const;
 
 export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const;
@@ -70,6 +79,7 @@ export const moderationLogTypes = [
 	'resetPassword',
 	'suspendRemoteInstance',
 	'unsuspendRemoteInstance',
+	'updateRemoteInstanceNote',
 	'markSensitiveDriveFile',
 	'unmarkSensitiveDriveFile',
 	'resolveAbuseReport',
@@ -215,6 +225,12 @@ export type ModerationLogPayloads = {
 		id: string;
 		host: string;
 	};
+	updateRemoteInstanceNote: {
+		id: string;
+		host: string;
+		before: string | null;
+		after: string | null;
+	};
 	markSensitiveDriveFile: {
 		fileId: string;
 		fileUserId: string | null;
@@ -283,7 +299,11 @@ export type Serialized<T> = {
 				? (string | null)
 				: T[K] extends Record<string, any>
 					? Serialized<T[K]>
-					: T[K];
+					: T[K] extends (Record<string, any> | null)
+					? (Serialized<T[K]> | null)
+						: T[K] extends (Record<string, any> | undefined)
+						? (Serialized<T[K]> | undefined)
+							: T[K];
 };
 
 export type FilterUnionByProperty<
diff --git a/packages/backend/test-server/.eslintrc.cjs b/packages/backend/test-server/.eslintrc.cjs
new file mode 100644
index 0000000000..c261741a36
--- /dev/null
+++ b/packages/backend/test-server/.eslintrc.cjs
@@ -0,0 +1,32 @@
+module.exports = {
+	parserOptions: {
+		tsconfigRootDir: __dirname,
+		project: ['./tsconfig.json'],
+	},
+	extends: [
+		'../../shared/.eslintrc.js',
+	],
+	rules: {
+		'import/order': ['warn', {
+			'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
+			'pathGroups': [
+				{
+					'pattern': '@/**',
+					'group': 'external',
+					'position': 'after'
+				}
+			],
+		}],
+		'no-restricted-globals': [
+			'error',
+			{
+				'name': '__dirname',
+				'message': 'Not in ESModule. Use `import.meta.url` instead.'
+			},
+			{
+				'name': '__filename',
+				'message': 'Not in ESModule. Use `import.meta.url` instead.'
+			}
+	]
+	},
+};
diff --git a/packages/backend/test-server/.swcrc b/packages/backend/test-server/.swcrc
new file mode 100644
index 0000000000..e3d6935169
--- /dev/null
+++ b/packages/backend/test-server/.swcrc
@@ -0,0 +1,23 @@
+{
+	"$schema": "https://json.schemastore.org/swcrc",
+	"jsc": {
+		"parser": {
+			"syntax": "typescript",
+			"dynamicImport": true,
+			"decorators": true
+		},
+		"transform": {
+			"legacyDecorator": true,
+			"decoratorMetadata": true
+		},
+		"experimental": {
+			"keepImportAssertions": true
+		},
+		"baseUrl": "../built",
+		"paths": {
+			"@/*": ["*"]
+		},
+		"target": "es2022"
+	},
+	"minify": false
+}
diff --git a/packages/backend/test-server/entry.ts b/packages/backend/test-server/entry.ts
new file mode 100644
index 0000000000..866a7e1f5b
--- /dev/null
+++ b/packages/backend/test-server/entry.ts
@@ -0,0 +1,80 @@
+import { portToPid } from 'pid-port';
+import fkill from 'fkill';
+import Fastify from 'fastify';
+import { NestFactory } from '@nestjs/core';
+import { MainModule } from '@/MainModule.js';
+import { ServerService } from '@/server/ServerService.js';
+import { loadConfig } from '@/config.js';
+import { NestLogger } from '@/NestLogger.js';
+
+const config = loadConfig();
+const originEnv = JSON.stringify(process.env);
+
+process.env.NODE_ENV = 'test';
+
+/**
+ * テスト用のサーバインスタンスを起動する
+ */
+async function launch() {
+	await killTestServer();
+
+	console.log('starting application...');
+
+	const app = await NestFactory.createApplicationContext(MainModule, {
+		logger: new NestLogger(),
+	});
+	const serverService = app.get(ServerService);
+	await serverService.launch();
+
+	await startControllerEndpoints();
+
+	// ジョブキューは必要な時にテストコード側で起動する
+	// ジョブキューが動くとテスト結果の確認に支障が出ることがあるので意図的に動かさないでいる
+
+	console.log('application initialized.');
+}
+
+/**
+ * 既に重複したポートで待ち受けしているサーバがある場合はkillする
+ */
+async function killTestServer() {
+	//
+	try {
+		const pid = await portToPid(config.port);
+		if (pid) {
+			await fkill(pid, { force: true });
+		}
+	} catch {
+		// NOP;
+	}
+}
+
+/**
+ * 別プロセスに切り離してしまったが故に出来なくなった環境変数の書き換え等を実現するためのエンドポイントを作る
+ * @param port
+ */
+async function startControllerEndpoints(port = config.port + 1000) {
+	const fastify = Fastify();
+
+	fastify.post<{ Body: { key?: string, value?: string } }>('/env', async (req, res) => {
+		console.log(req.body);
+		const key = req.body['key'];
+		if (!key) {
+			res.code(400).send({ success: false });
+			return;
+		}
+
+		process.env[key] = req.body['value'];
+
+		res.code(200).send({ success: true });
+	});
+
+	fastify.post<{ Body: { key?: string, value?: string } }>('/env-reset', async (req, res) => {
+		process.env = JSON.parse(originEnv);
+		res.code(200).send({ success: true });
+	});
+
+	await fastify.listen({ port: port, host: 'localhost' });
+}
+
+export default launch;
diff --git a/packages/backend/test-server/tsconfig.json b/packages/backend/test-server/tsconfig.json
new file mode 100644
index 0000000000..10313699c2
--- /dev/null
+++ b/packages/backend/test-server/tsconfig.json
@@ -0,0 +1,52 @@
+{
+	"compilerOptions": {
+		"allowJs": true,
+		"noEmitOnError": true,
+		"noImplicitAny": true,
+		"noImplicitReturns": true,
+		"noUnusedParameters": false,
+		"noUnusedLocals": false,
+		"noFallthroughCasesInSwitch": true,
+		"declaration": false,
+		"sourceMap": true,
+		"target": "ES2022",
+		"module": "nodenext",
+		"moduleResolution": "nodenext",
+		"allowSyntheticDefaultImports": true,
+		"removeComments": false,
+		"noLib": false,
+		"strict": true,
+		"strictNullChecks": true,
+		"strictPropertyInitialization": false,
+		"skipLibCheck": true,
+		"experimentalDecorators": true,
+		"emitDecoratorMetadata": true,
+		"resolveJsonModule": true,
+		"isolatedModules": true,
+		"rootDir": "../src",
+		"baseUrl": "./",
+		"paths": {
+			"@/*": ["../src/*"]
+		},
+		"outDir": "../built-test",
+		"types": [
+			"node"
+		],
+		"typeRoots": [
+			"../src/@types",
+			"../node_modules/@types",
+			"../node_modules"
+		],
+		"lib": [
+			"esnext"
+		]
+	},
+	"compileOnSave": false,
+	"include": [
+		"./**/*.ts",
+		"../src/**/*.ts"
+	],
+	"exclude": [
+		"../src/**/*.test.ts"
+	]
+}
diff --git a/packages/backend/test/e2e/2fa.ts b/packages/backend/test/e2e/2fa.ts
index ed967d2620..87a3c227d6 100644
--- a/packages/backend/test/e2e/2fa.ts
+++ b/packages/backend/test/e2e/2fa.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -10,7 +10,7 @@ import * as crypto from 'node:crypto';
 import cbor from 'cbor';
 import * as OTPAuth from 'otpauth';
 import { loadConfig } from '@/config.js';
-import { api, signup, startServer } from '../utils.js';
+import { api, signup } from '../utils.js';
 import type {
 	AuthenticationResponseJSON,
 	AuthenticatorAssertionResponseJSON,
@@ -18,13 +18,11 @@ import type {
 	PublicKeyCredentialCreationOptionsJSON,
 	PublicKeyCredentialRequestOptionsJSON,
 	RegistrationResponseJSON,
-} from '@simplewebauthn/typescript-types';
-import type { INestApplicationContext } from '@nestjs/common';
+} from '@simplewebauthn/types';
 import type * as misskey from 'misskey-js';
 
 describe('2要素認証', () => {
-	let app: INestApplicationContext;
-	let alice: misskey.entities.MeSignup;
+	let alice: misskey.entities.SignupResponse;
 
 	const config = loadConfig();
 	const password = 'test';
@@ -185,14 +183,9 @@ describe('2要素認証', () => {
 	};
 
 	beforeAll(async () => {
-		app = await startServer();
 		alice = await signup({ username, password });
 	}, 1000 * 60 * 2);
 
-	afterAll(async () => {
-		await app.close();
-	});
-
 	test('が設定でき、OTPでログインできる。', async () => {
 		const registerResponse = await api('/i/2fa/register', {
 			password,
diff --git a/packages/backend/test/e2e/antennas.ts b/packages/backend/test/e2e/antennas.ts
index c0317f1435..1a9d5bf1f0 100644
--- a/packages/backend/test/e2e/antennas.ts
+++ b/packages/backend/test/e2e/antennas.ts
@@ -1,29 +1,25 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
-import { inspect } from 'node:util';
 import { DEFAULT_POLICIES } from '@/core/RoleService.js';
 import type { Packed } from '@/misc/json-schema.js';
 import {
-	signup,
-	post,
-	userList,
-	page,
-	role,
-	startServer,
 	api,
-	successfulApiCall,
 	failedApiCall,
-	uploadFile,
+	post,
+	role,
+	signup,
+	successfulApiCall,
 	testPaginationConsistency,
+	uploadFile,
+	userList,
 } from '../utils.js';
 import type * as misskey from 'misskey-js';
-import type { INestApplicationContext } from '@nestjs/common';
 
 const compareBy = <T extends { id: string }>(selector: (s: T) => string = (s: T): string => s.id) => (a: T, b: T): number => {
 	return selector(a).localeCompare(selector(b));
@@ -37,7 +33,7 @@ describe('アンテナ', () => {
 	// - srcのenumにgroupが残っている
 	// - userGroupIdが残っている, isActiveがない
 	type Antenna = misskey.entities.Antenna | Packed<'Antenna'>;
-	type User = misskey.entities.MeSignup;
+	type User = misskey.entities.SignupResponse;
 	type Note = misskey.entities.Note;
 
 	// アンテナを作成できる最小のパラメタ
@@ -54,8 +50,6 @@ describe('アンテナ', () => {
 		withReplies: false,
 	};
 
-	let app: INestApplicationContext;
-
 	let root: User;
 	let alice: User;
 	let bob: User;
@@ -79,10 +73,6 @@ describe('アンテナ', () => {
 	let userMutingAlice: User;
 	let userMutedByAlice: User;
 
-	beforeAll(async () => {
-		app = await startServer();
-	}, 1000 * 60 * 2);
-
 	beforeAll(async () => {
 		root = await signup({ username: 'root' });
 		alice = await signup({ username: 'alice' });
@@ -136,10 +126,6 @@ describe('アンテナ', () => {
 		await api('mute/create', { userId: userMutedByAlice.id }, alice);
 	}, 1000 * 60 * 10);
 
-	afterAll(async () => {
-		await app.close();
-	});
-
 	beforeEach(async () => {
 		// テスト間で影響し合わないように毎回全部消す。
 		for (const user of [alice, bob]) {
diff --git a/packages/backend/test/e2e/api-visibility.ts b/packages/backend/test/e2e/api-visibility.ts
index 33c8d03fdb..f92384525c 100644
--- a/packages/backend/test/e2e/api-visibility.ts
+++ b/packages/backend/test/e2e/api-visibility.ts
@@ -1,38 +1,27 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
-import { signup, api, post, startServer } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import { api, post, signup } from '../utils.js';
 import type * as misskey from 'misskey-js';
 
 describe('API visibility', () => {
-	let app: INestApplicationContext;
-
-	beforeAll(async () => {
-		app = await startServer();
-	}, 1000 * 60 * 2);
-
-	afterAll(async () => {
-		await app.close();
-	});
-
 	describe('Note visibility', () => {
 		//#region vars
 		/** ヒロイン */
-		let alice: misskey.entities.MeSignup;
+		let alice: misskey.entities.SignupResponse;
 		/** フォロワー */
-		let follower: misskey.entities.MeSignup;
+		let follower: misskey.entities.SignupResponse;
 		/** 非フォロワー */
-		let other: misskey.entities.MeSignup;
+		let other: misskey.entities.SignupResponse;
 		/** 非フォロワーでもリプライやメンションをされた人 */
-		let target: misskey.entities.MeSignup;
+		let target: misskey.entities.SignupResponse;
 		/** specified mentionでmentionを飛ばされる人 */
-		let target2: misskey.entities.MeSignup;
+		let target2: misskey.entities.SignupResponse;
 
 		/** public-post */
 		let pub: any;
diff --git a/packages/backend/test/e2e/api.ts b/packages/backend/test/e2e/api.ts
index cf24228b83..b6eeec99d7 100644
--- a/packages/backend/test/e2e/api.ts
+++ b/packages/backend/test/e2e/api.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -7,27 +7,30 @@ process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
 import { IncomingMessage } from 'http';
-import { signup, api, startServer, successfulApiCall, failedApiCall, uploadFile, waitFire, connectStream, relativeFetch, createAppToken } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import {
+	api,
+	connectStream,
+	createAppToken,
+	failedApiCall,
+	relativeFetch,
+	signup,
+	successfulApiCall,
+	uploadFile,
+	waitFire,
+} from '../utils.js';
 import type * as misskey from 'misskey-js';
 
 describe('API', () => {
-	let app: INestApplicationContext;
-	let alice: misskey.entities.MeSignup;
-	let bob: misskey.entities.MeSignup;
-	let carol: misskey.entities.MeSignup;
+	let alice: misskey.entities.SignupResponse;
+	let bob: misskey.entities.SignupResponse;
+	let carol: misskey.entities.SignupResponse;
 
 	beforeAll(async () => {
-		app = await startServer();
 		alice = await signup({ username: 'alice' });
 		bob = await signup({ username: 'bob' });
 		carol = await signup({ username: 'carol' });
 	}, 1000 * 60 * 2);
 
-	afterAll(async () => {
-		await app.close();
-	});
-
 	describe('General validation', () => {
 		test('wrong type', async () => {
 			const res = await api('/test', {
diff --git a/packages/backend/test/e2e/block.ts b/packages/backend/test/e2e/block.ts
index 4445d9036c..cbd91e6e42 100644
--- a/packages/backend/test/e2e/block.ts
+++ b/packages/backend/test/e2e/block.ts
@@ -1,34 +1,26 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
-import { signup, api, post, startServer } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import { api, post, signup } from '../utils.js';
 import type * as misskey from 'misskey-js';
 
 describe('Block', () => {
-	let app: INestApplicationContext;
-
 	// alice blocks bob
-	let alice: misskey.entities.MeSignup;
-	let bob: misskey.entities.MeSignup;
-	let carol: misskey.entities.MeSignup;
+	let alice: misskey.entities.SignupResponse;
+	let bob: misskey.entities.SignupResponse;
+	let carol: misskey.entities.SignupResponse;
 
 	beforeAll(async () => {
-		app = await startServer();
 		alice = await signup({ username: 'alice' });
 		bob = await signup({ username: 'bob' });
 		carol = await signup({ username: 'carol' });
 	}, 1000 * 60 * 2);
 
-	afterAll(async () => {
-		await app.close();
-	});
-
 	test('Block作成', async () => {
 		const res = await api('/blocking/create', {
 			userId: bob.id,
diff --git a/packages/backend/test/e2e/clips.ts b/packages/backend/test/e2e/clips.ts
index 49092fba63..2cf397e22d 100644
--- a/packages/backend/test/e2e/clips.ts
+++ b/packages/backend/test/e2e/clips.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -18,25 +18,13 @@ import { paramDef as UnfavoriteParamDef } from '@/server/api/endpoints/clips/unf
 import { paramDef as AddNoteParamDef } from '@/server/api/endpoints/clips/add-note.js';
 import { paramDef as RemoveNoteParamDef } from '@/server/api/endpoints/clips/remove-note.js';
 import { paramDef as NotesParamDef } from '@/server/api/endpoints/clips/notes.js';
-import {
-	signup,
-	post,
-	startServer,
-	api,
-	successfulApiCall,
-	failedApiCall,
-	ApiRequest,
-	hiddenNote,
-} from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import { api, ApiRequest, failedApiCall, hiddenNote, post, signup, successfulApiCall } from '../utils.js';
 
 describe('クリップ', () => {
 	type User = Packed<'User'>;
 	type Note = Packed<'Note'>;
 	type Clip = Packed<'Clip'>;
 
-	let app: INestApplicationContext;
-
 	let alice: User;
 	let bob: User;
 	let aliceNote: Note;
@@ -145,7 +133,6 @@ describe('クリップ', () => {
 	};
 
 	beforeAll(async () => {
-		app = await startServer();
 		alice = await signup({ username: 'alice' });
 		bob = await signup({ username: 'bob' });
 
@@ -160,10 +147,6 @@ describe('クリップ', () => {
 		bobSpecifiedNote = await post(bob, { text: 'specified only', visibility: 'specified' }) as any;
 	}, 1000 * 60 * 2);
 
-	afterAll(async () => {
-		await app.close();
-	});
-
 	afterEach(async () => {
 		// テスト間で影響し合わないように毎回全部消す。
 		for (const user of [alice, bob]) {
diff --git a/packages/backend/test/e2e/drive.ts b/packages/backend/test/e2e/drive.ts
new file mode 100644
index 0000000000..22ec66e2af
--- /dev/null
+++ b/packages/backend/test/e2e/drive.ts
@@ -0,0 +1,95 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+process.env.NODE_ENV = 'test';
+
+import * as assert from 'assert';
+import { MiNote } from '@/models/Note.js';
+import { api, initTestDb, makeStreamCatcher, post, signup, uploadFile } from '../utils.js';
+import type * as misskey from 'misskey-js';
+import type{ Repository } from 'typeorm'
+import type { Packed } from '@/misc/json-schema.js';
+
+
+describe('Drive', () => {
+	let Notes: Repository<MiNote>;
+
+	let alice: misskey.entities.SignupResponse;
+	let bob: misskey.entities.SignupResponse;
+
+	beforeAll(async () => {
+		const connection = await initTestDb(true);
+		Notes = connection.getRepository(MiNote);
+		alice = await signup({ username: 'alice' });
+		bob = await signup({ username: 'bob' });
+	}, 1000 * 60 * 2);
+
+	test('ファイルURLからアップロードできる', async () => {
+		// utils.js uploadUrl の処理だがAPIレスポンスも見るためここで同様の処理を書いている
+
+		const marker = Math.random().toString();
+
+		const url = 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg'
+
+		const catcher = makeStreamCatcher(
+			alice,
+			'main',
+			(msg) => msg.type === 'urlUploadFinished' && msg.body.marker === marker,
+			(msg) => msg.body.file as Packed<'DriveFile'>,
+			10 * 1000);
+
+		const res = await api('drive/files/upload-from-url', {
+			url,
+			marker,
+			force: true,
+		}, alice);
+
+		const file = await catcher;
+
+		assert.strictEqual(res.status, 204);
+		assert.strictEqual(file.name, 'Lenna.jpg');
+		assert.strictEqual(file.type, 'image/jpeg');
+	})
+
+	test('ローカルからアップロードできる', async () => {
+		// APIレスポンスを直接使用するので utils.js uploadFile が通過することで成功とする
+
+		const res = await uploadFile(alice, { path: 'Lenna.jpg', name: 'テスト画像' });
+
+		assert.strictEqual(res.body?.name, 'テスト画像.jpg');
+		assert.strictEqual(res.body?.type, 'image/jpeg');
+	})
+
+	test('添付ノート一覧を取得できる', async () => {
+		const ids = (await Promise.all([uploadFile(alice), uploadFile(alice), uploadFile(alice)])).map(elm => elm.body!.id)
+
+		const note0 = await post(alice, { fileIds: [ids[0]] });
+		const note1 = await post(alice, { fileIds: [ids[0], ids[1]] });
+
+		const attached0 = await api('drive/files/attached-notes', { fileId: ids[0] }, alice);
+		assert.strictEqual(attached0.body.length, 2);
+		assert.strictEqual(attached0.body[0].id, note1.id)
+		assert.strictEqual(attached0.body[1].id, note0.id)
+
+		const attached1 = await api('drive/files/attached-notes', { fileId: ids[1] }, alice);
+		assert.strictEqual(attached1.body.length, 1);
+		assert.strictEqual(attached1.body[0].id, note1.id)
+
+		const attached2 = await api('drive/files/attached-notes', { fileId: ids[2] }, alice);
+		assert.strictEqual(attached2.body.length, 0)
+	})
+
+	test('添付ノート一覧は他の人から見えない', async () => {
+		const file = await uploadFile(alice);
+
+		await post(alice, { fileIds: [file.body!.id] });
+
+		const res = await api('drive/files/attached-notes', { fileId: file.body!.id }, bob);
+		assert.strictEqual(res.status, 400);
+		assert.strictEqual('error' in res.body, true);
+
+	})
+});
+
diff --git a/packages/backend/test/e2e/endpoints.ts b/packages/backend/test/e2e/endpoints.ts
index 2ef3434bca..d469597805 100644
--- a/packages/backend/test/e2e/endpoints.ts
+++ b/packages/backend/test/e2e/endpoints.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -10,30 +10,22 @@ import * as assert from 'assert';
 // https://github.com/node-fetch/node-fetch/pull/1664
 import { Blob } from 'node-fetch';
 import { MiUser } from '@/models/_.js';
-import { startServer, signup, post, api, uploadFile, simpleGet, initTestDb } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import { api, initTestDb, post, signup, simpleGet, uploadFile } from '../utils.js';
 import type * as misskey from 'misskey-js';
 
 describe('Endpoints', () => {
-	let app: INestApplicationContext;
-
-	let alice: misskey.entities.MeSignup;
-	let bob: misskey.entities.MeSignup;
-	let carol: misskey.entities.MeSignup;
-	let dave: misskey.entities.MeSignup;
+	let alice: misskey.entities.SignupResponse;
+	let bob: misskey.entities.SignupResponse;
+	let carol: misskey.entities.SignupResponse;
+	let dave: misskey.entities.SignupResponse;
 
 	beforeAll(async () => {
-		app = await startServer();
 		alice = await signup({ username: 'alice' });
 		bob = await signup({ username: 'bob' });
 		carol = await signup({ username: 'carol' });
 		dave = await signup({ username: 'dave' });
 	}, 1000 * 60 * 2);
 
-	afterAll(async () => {
-		await app.close();
-	});
-
 	describe('signup', () => {
 		test('不正なユーザー名でアカウントが作成できない', async () => {
 			const res = await api('signup', {
@@ -710,6 +702,18 @@ describe('Endpoints', () => {
 			assert.strictEqual(res.status, 400);
 		});
 
+		test('不正なファイル名で怒られる', async () => {
+			const file = (await uploadFile(alice)).body;
+			const newName = '';
+
+			const res = await api('/drive/files/update', {
+				fileId: file.id,
+				name: newName,
+			}, alice);
+
+			assert.strictEqual(res.status, 400);
+		});
+
 		test('間違ったIDで怒られる', async () => {
 			const res = await api('/drive/files/update', {
 				fileId: 'kyoppie',
diff --git a/packages/backend/test/e2e/exports.ts b/packages/backend/test/e2e/exports.ts
new file mode 100644
index 0000000000..eb03935a2a
--- /dev/null
+++ b/packages/backend/test/e2e/exports.ts
@@ -0,0 +1,193 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+process.env.NODE_ENV = 'test';
+
+import * as assert from 'assert';
+import { api, port, post, signup, startJobQueue } from '../utils.js';
+import type { INestApplicationContext } from '@nestjs/common';
+import type * as misskey from 'misskey-js';
+
+describe('export-clips', () => {
+	let queue: INestApplicationContext;
+	let alice: misskey.entities.SignupResponse;
+	let bob: misskey.entities.SignupResponse;
+
+	// XXX: Any better way to get the result?
+	async function pollFirstDriveFile() {
+		while (true) {
+			const files = (await api('/drive/files', {}, alice)).body;
+			if (!files.length) {
+				await new Promise(r => setTimeout(r, 100));
+				continue;
+			}
+			if (files.length > 1) {
+				throw new Error('Too many files?');
+			}
+			const file = (await api('/drive/files/show', { fileId: files[0].id }, alice)).body;
+			const res = await fetch(new URL(new URL(file.url).pathname, `http://127.0.0.1:${port}`));
+			return await res.json();
+		}
+	}
+
+	beforeAll(async () => {
+		queue = await startJobQueue();
+		alice = await signup({ username: 'alice' });
+		bob = await signup({ username: 'bob' });
+	}, 1000 * 60 * 2);
+
+	afterAll(async () => {
+		await queue.close();
+	});
+
+	beforeEach(async () => {
+		// Clean all clips and files of alice
+		const clips = (await api('/clips/list', {}, alice)).body;
+		for (const clip of clips) {
+			const res = await api('/clips/delete', { clipId: clip.id }, alice);
+			if (res.status !== 204) {
+				throw new Error('Failed to delete clip');
+			}
+		}
+		const files = (await api('/drive/files', {}, alice)).body;
+		for (const file of files) {
+			const res = await api('/drive/files/delete', { fileId: file.id }, alice);
+			if (res.status !== 204) {
+				throw new Error('Failed to delete file');
+			}
+		}
+	});
+
+	test('basic export', async () => {
+		let res = await api('/clips/create', {
+			name: 'foo',
+			description: 'bar',
+		}, alice);
+		assert.strictEqual(res.status, 200);
+
+		res = await api('/i/export-clips', {}, alice);
+		assert.strictEqual(res.status, 204);
+
+		const exported = await pollFirstDriveFile();
+		assert.strictEqual(exported[0].name, 'foo');
+		assert.strictEqual(exported[0].description, 'bar');
+		assert.strictEqual(exported[0].clipNotes.length, 0);
+	});
+
+	test('export with notes', async () => {
+		let res = await api('/clips/create', {
+			name: 'foo',
+			description: 'bar',
+		}, alice);
+		assert.strictEqual(res.status, 200);
+		const clip = res.body;
+
+		const note1 = await post(alice, {
+			text: 'baz1',
+		});
+
+		const note2 = await post(alice, {
+			text: 'baz2',
+			poll: {
+				choices: ['sakura', 'izumi', 'ako'],
+			},
+		});
+
+		for (const note of [note1, note2]) {
+			res = await api('/clips/add-note', {
+				clipId: clip.id,
+				noteId: note.id,
+			}, alice);
+			assert.strictEqual(res.status, 204);
+		}
+
+		res = await api('/i/export-clips', {}, alice);
+		assert.strictEqual(res.status, 204);
+
+		const exported = await pollFirstDriveFile();
+		assert.strictEqual(exported[0].name, 'foo');
+		assert.strictEqual(exported[0].description, 'bar');
+		assert.strictEqual(exported[0].clipNotes.length, 2);
+		assert.strictEqual(exported[0].clipNotes[0].note.text, 'baz1');
+		assert.strictEqual(exported[0].clipNotes[1].note.text, 'baz2');
+		assert.deepStrictEqual(exported[0].clipNotes[1].note.poll.choices[0], 'sakura');
+	});
+
+	test('multiple clips', async () => {
+		let res = await api('/clips/create', {
+			name: 'kawaii',
+			description: 'kawaii',
+		}, alice);
+		assert.strictEqual(res.status, 200);
+		const clip1 = res.body;
+
+		res = await api('/clips/create', {
+			name: 'yuri',
+			description: 'yuri',
+		}, alice);
+		assert.strictEqual(res.status, 200);
+		const clip2 = res.body;
+
+		const note1 = await post(alice, {
+			text: 'baz1',
+		});
+
+		const note2 = await post(alice, {
+			text: 'baz2',
+		});
+
+		res = await api('/clips/add-note', {
+			clipId: clip1.id,
+			noteId: note1.id,
+		}, alice);
+		assert.strictEqual(res.status, 204);
+
+		res = await api('/clips/add-note', {
+			clipId: clip2.id,
+			noteId: note2.id,
+		}, alice);
+		assert.strictEqual(res.status, 204);
+
+		res = await api('/i/export-clips', {}, alice);
+		assert.strictEqual(res.status, 204);
+
+		const exported = await pollFirstDriveFile();
+		assert.strictEqual(exported[0].name, 'kawaii');
+		assert.strictEqual(exported[0].clipNotes.length, 1);
+		assert.strictEqual(exported[0].clipNotes[0].note.text, 'baz1');
+		assert.strictEqual(exported[1].name, 'yuri');
+		assert.strictEqual(exported[1].clipNotes.length, 1);
+		assert.strictEqual(exported[1].clipNotes[0].note.text, 'baz2');
+	});
+
+	test('Clipping other user\'s note', async () => {
+		let res = await api('/clips/create', {
+			name: 'kawaii',
+			description: 'kawaii',
+		}, alice);
+		assert.strictEqual(res.status, 200);
+		const clip = res.body;
+
+		const note = await post(bob, {
+			text: 'baz',
+			visibility: 'followers',
+		});
+
+		res = await api('/clips/add-note', {
+			clipId: clip.id,
+			noteId: note.id,
+		}, alice);
+		assert.strictEqual(res.status, 204);
+
+		res = await api('/i/export-clips', {}, alice);
+		assert.strictEqual(res.status, 204);
+
+		const exported = await pollFirstDriveFile();
+		assert.strictEqual(exported[0].name, 'kawaii');
+		assert.strictEqual(exported[0].clipNotes.length, 1);
+		assert.strictEqual(exported[0].clipNotes[0].note.text, 'baz');
+		assert.strictEqual(exported[0].clipNotes[0].note.user.username, 'bob');
+	});
+});
diff --git a/packages/backend/test/e2e/fetch-resource.ts b/packages/backend/test/e2e/fetch-resource.ts
index 251d662760..74033b7dff 100644
--- a/packages/backend/test/e2e/fetch-resource.ts
+++ b/packages/backend/test/e2e/fetch-resource.ts
@@ -1,14 +1,13 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
-import { startServer, channel, clip, cookie, galleryPost, signup, page, play, post, simpleGet, uploadFile } from '../utils.js';
+import { channel, clip, cookie, galleryPost, page, play, post, signup, simpleGet, uploadFile } from '../utils.js';
 import type { SimpleGetResponse } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
 import type * as misskey from 'misskey-js';
 
 // Request Accept
@@ -23,9 +22,7 @@ const HTML = 'text/html; charset=utf-8';
 const JSON_UTF8 = 'application/json; charset=utf-8';
 
 describe('Webリソース', () => {
-	let app: INestApplicationContext;
-
-	let alice: misskey.entities.MeSignup;
+	let alice: misskey.entities.SignupResponse;
 	let aliceUploadedFile: any;
 	let alicesPost: any;
 	let alicePage: any;
@@ -34,7 +31,7 @@ describe('Webリソース', () => {
 	let aliceGalleryPost: any;
 	let aliceChannel: any;
 
-	let bob: misskey.entities.MeSignup;
+	let bob: misskey.entities.SignupResponse;
 
 	type Request = {
 		path: string,
@@ -79,7 +76,6 @@ describe('Webリソース', () => {
 	};
 
 	beforeAll(async () => {
-		app = await startServer();
 		alice = await signup({ username: 'alice' });
 		aliceUploadedFile = await uploadFile(alice);
 		alicesPost = await post(alice, {
@@ -96,10 +92,6 @@ describe('Webリソース', () => {
 		bob = await signup({ username: 'bob' });
 	}, 1000 * 60 * 2);
 
-	afterAll(async () => {
-		await app.close();
-	});
-
 	describe.each([
 		{ path: '/', type: HTML },
 		{ path: '/docs/ja-JP/about', type: HTML }, // "指定されたURLに該当するページはありませんでした。"
diff --git a/packages/backend/test/e2e/ff-visibility.ts b/packages/backend/test/e2e/ff-visibility.ts
index 1fbd45c741..b59dd8824a 100644
--- a/packages/backend/test/e2e/ff-visibility.ts
+++ b/packages/backend/test/e2e/ff-visibility.ts
@@ -1,31 +1,23 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
-import { signup, api, startServer, simpleGet } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import { api, signup, simpleGet } from '../utils.js';
 import type * as misskey from 'misskey-js';
 
 describe('FF visibility', () => {
-	let app: INestApplicationContext;
-
-	let alice: misskey.entities.MeSignup;
-	let bob: misskey.entities.MeSignup;
+	let alice: misskey.entities.SignupResponse;
+	let bob: misskey.entities.SignupResponse;
 
 	beforeAll(async () => {
-		app = await startServer();
 		alice = await signup({ username: 'alice' });
 		bob = await signup({ username: 'bob' });
 	}, 1000 * 60 * 2);
 
-	afterAll(async () => {
-		await app.close();
-	});
-
 	test('followingVisibility, followersVisibility がともに public なユーザーのフォロー/フォロワーを誰でも見れる', async () => {
 		await api('/i/update', {
 			followingVisibility: 'public',
diff --git a/packages/backend/test/e2e/move.ts b/packages/backend/test/e2e/move.ts
index b009ef124a..f6417e39b5 100644
--- a/packages/backend/test/e2e/move.ts
+++ b/packages/backend/test/e2e/move.ts
@@ -1,37 +1,37 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
+import { INestApplicationContext } from '@nestjs/common';
+
 process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
 import { loadConfig } from '@/config.js';
 import { MiUser, UsersRepository } from '@/models/_.js';
-import { jobQueue } from '@/boot/common.js';
 import { secureRndstr } from '@/misc/secure-rndstr.js';
-import { uploadFile, signup, startServer, initTestDb, api, sleep, successfulApiCall } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import { jobQueue } from '@/boot/common.js';
+import { api, initTestDb, signup, sleep, successfulApiCall, uploadFile } from '../utils.js';
 import type * as misskey from 'misskey-js';
 
 describe('Account Move', () => {
-	let app: INestApplicationContext;
 	let jq: INestApplicationContext;
 	let url: URL;
 
 	let root: any;
-	let alice: misskey.entities.MeSignup;
-	let bob: misskey.entities.MeSignup;
-	let carol: misskey.entities.MeSignup;
-	let dave: misskey.entities.MeSignup;
-	let eve: misskey.entities.MeSignup;
-	let frank: misskey.entities.MeSignup;
+	let alice: misskey.entities.SignupResponse;
+	let bob: misskey.entities.SignupResponse;
+	let carol: misskey.entities.SignupResponse;
+	let dave: misskey.entities.SignupResponse;
+	let eve: misskey.entities.SignupResponse;
+	let frank: misskey.entities.SignupResponse;
 
 	let Users: UsersRepository;
 
 	beforeAll(async () => {
-		app = await startServer();
 		jq = await jobQueue();
+
 		const config = loadConfig();
 		url = new URL(config.url);
 		const connection = await initTestDb(false);
@@ -46,7 +46,7 @@ describe('Account Move', () => {
 	}, 1000 * 60 * 2);
 
 	afterAll(async () => {
-		await Promise.all([app.close(), jq.close()]);
+		await jq.close();
 	});
 
 	describe('Create Alias', () => {
diff --git a/packages/backend/test/e2e/mute.ts b/packages/backend/test/e2e/mute.ts
index a4b57a1eba..1d28e07b7d 100644
--- a/packages/backend/test/e2e/mute.ts
+++ b/packages/backend/test/e2e/mute.ts
@@ -1,34 +1,26 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
-import { signup, api, post, react, startServer, waitFire } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import { api, post, react, signup, waitFire } from '../utils.js';
 import type * as misskey from 'misskey-js';
 
 describe('Mute', () => {
-	let app: INestApplicationContext;
-
 	// alice mutes carol
-	let alice: misskey.entities.MeSignup;
-	let bob: misskey.entities.MeSignup;
-	let carol: misskey.entities.MeSignup;
+	let alice: misskey.entities.SignupResponse;
+	let bob: misskey.entities.SignupResponse;
+	let carol: misskey.entities.SignupResponse;
 
 	beforeAll(async () => {
-		app = await startServer();
 		alice = await signup({ username: 'alice' });
 		bob = await signup({ username: 'bob' });
 		carol = await signup({ username: 'carol' });
 	}, 1000 * 60 * 2);
 
-	afterAll(async () => {
-		await app.close();
-	});
-
 	test('ミュート作成', async () => {
 		const res = await api('/mute/create', {
 			userId: carol.id,
@@ -125,5 +117,185 @@ describe('Mute', () => {
 			assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
 			assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
 		});
+
+		test('通知にミュートしているユーザーからのリプライが含まれない', async () => {
+			const aliceNote = await post(alice, { text: 'hi' });
+			await post(bob, { text: '@alice hi', replyId: aliceNote.id });
+			await post(carol, { text: '@alice hi', replyId: aliceNote.id });
+
+			const res = await api('/i/notifications', {}, alice);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(Array.isArray(res.body), true);
+
+			assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
+			assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
+		});
+
+		test('通知にミュートしているユーザーからのリプライが含まれない', async () => {
+			await post(alice, { text: 'hi' });
+			await post(bob, { text: '@alice hi' });
+			await post(carol, { text: '@alice hi' });
+
+			const res = await api('/i/notifications', {}, alice);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(Array.isArray(res.body), true);
+
+			assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
+			assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
+		});
+
+		test('通知にミュートしているユーザーからの引用リノートが含まれない', async () => {
+			const aliceNote = await post(alice, { text: 'hi' });
+			await post(bob, { text: 'hi', renoteId: aliceNote.id });
+			await post(carol, { text: 'hi', renoteId: aliceNote.id });
+
+			const res = await api('/i/notifications', {}, alice);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(Array.isArray(res.body), true);
+
+			assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
+			assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
+		});
+
+		test('通知にミュートしているユーザーからのリノートが含まれない', async () => {
+			const aliceNote = await post(alice, { text: 'hi' });
+			await post(bob, { renoteId: aliceNote.id });
+			await post(carol, { renoteId: aliceNote.id });
+
+			const res = await api('/i/notifications', {}, alice);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(Array.isArray(res.body), true);
+
+			assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
+			assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
+		});
+
+		test('通知にミュートしているユーザーからのフォロー通知が含まれない', async () => {
+			await api('/i/follow', { userId: alice.id }, bob);
+			await api('/i/follow', { userId: alice.id }, carol);
+
+			const res = await api('/i/notifications', {}, alice);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(Array.isArray(res.body), true);
+
+			assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
+			assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
+		});
+
+		test('通知にミュートしているユーザーからのフォローリクエストが含まれない', async () => {
+			await api('/i/update/', { isLocked: true }, alice);
+			await api('/following/create', { userId: alice.id }, bob);
+			await api('/following/create', { userId: alice.id }, carol);
+
+			const res = await api('/i/notifications', {}, alice);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(Array.isArray(res.body), true);
+
+			assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
+			assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
+		});
+	});
+
+	describe('Notification (Grouped)', () => {
+		test('通知にミュートしているユーザーの通知が含まれない(リアクション)', async () => {
+			const aliceNote = await post(alice, { text: 'hi' });
+			await react(bob, aliceNote, 'like');
+			await react(carol, aliceNote, 'like');
+
+			const res = await api('/i/notifications-grouped', {}, alice);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(Array.isArray(res.body), true);
+			assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
+			assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
+		});
+		test('通知にミュートしているユーザーからのリプライが含まれない', async () => {
+			const aliceNote = await post(alice, { text: 'hi' });
+			await post(bob, { text: '@alice hi', replyId: aliceNote.id });
+			await post(carol, { text: '@alice hi', replyId: aliceNote.id });
+
+			const res = await api('/i/notifications-grouped', {}, alice);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(Array.isArray(res.body), true);
+
+			assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
+			assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
+		});
+
+		test('通知にミュートしているユーザーからのリプライが含まれない', async () => {
+			await post(alice, { text: 'hi' });
+			await post(bob, { text: '@alice hi' });
+			await post(carol, { text: '@alice hi' });
+
+			const res = await api('/i/notifications-grouped', {}, alice);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(Array.isArray(res.body), true);
+
+			assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
+			assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
+		});
+
+		test('通知にミュートしているユーザーからの引用リノートが含まれない', async () => {
+			const aliceNote = await post(alice, { text: 'hi' });
+			await post(bob, { text: 'hi', renoteId: aliceNote.id });
+			await post(carol, { text: 'hi', renoteId: aliceNote.id });
+
+			const res = await api('/i/notifications-grouped', {}, alice);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(Array.isArray(res.body), true);
+
+			assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
+			assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
+		});
+
+		test('通知にミュートしているユーザーからのリノートが含まれない', async () => {
+			const aliceNote = await post(alice, { text: 'hi' });
+			await post(bob, { renoteId: aliceNote.id });
+			await post(carol, { renoteId: aliceNote.id });
+
+			const res = await api('/i/notifications-grouped', {}, alice);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(Array.isArray(res.body), true);
+
+			assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
+			assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
+		});
+
+		test('通知にミュートしているユーザーからのフォロー通知が含まれない', async () => {
+			await api('/i/follow', { userId: alice.id }, bob);
+			await api('/i/follow', { userId: alice.id }, carol);
+
+			const res = await api('/i/notifications-grouped', {}, alice);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(Array.isArray(res.body), true);
+
+			assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
+			assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
+		});
+
+		test('通知にミュートしているユーザーからのフォローリクエストが含まれない', async () => {
+			await api('/i/update/', { isLocked: true }, alice);
+			await api('/following/create', { userId: alice.id }, bob);
+			await api('/following/create', { userId: alice.id }, carol);
+
+			const res = await api('/i/notifications-grouped', {}, alice);
+
+			assert.strictEqual(res.status, 200);
+			assert.strictEqual(Array.isArray(res.body), true);
+
+			assert.strictEqual(res.body.some((notification: any) => notification.userId === bob.id), true);
+			assert.strictEqual(res.body.some((notification: any) => notification.userId === carol.id), false);
+		});
 	});
 });
diff --git a/packages/backend/test/e2e/nodeinfo.ts b/packages/backend/test/e2e/nodeinfo.ts
index 7eed39c5ed..28b96fe8c8 100644
--- a/packages/backend/test/e2e/nodeinfo.ts
+++ b/packages/backend/test/e2e/nodeinfo.ts
@@ -1,25 +1,14 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
-import { relativeFetch, startServer } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import { relativeFetch } from '../utils.js';
 
 describe('nodeinfo', () => {
-	let app: INestApplicationContext;
-
-	beforeAll(async () => {
-		app = await startServer();
-	}, 1000 * 60 * 2);
-
-	afterAll(async () => {
-		await app.close();
-	});
-
 	test('nodeinfo 2.1', async () => {
 		const res = await relativeFetch('nodeinfo/2.1');
 		assert.ok(res.ok);
diff --git a/packages/backend/test/e2e/note.ts b/packages/backend/test/e2e/note.ts
index 961df99cc2..2406204f41 100644
--- a/packages/backend/test/e2e/note.ts
+++ b/packages/backend/test/e2e/note.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -8,29 +8,24 @@ process.env.NODE_ENV = 'test';
 import * as assert from 'assert';
 import { MiNote } from '@/models/Note.js';
 import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
-import { signup, post, uploadUrl, startServer, initTestDb, api, uploadFile } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import { api, initTestDb, post, signup, uploadFile, uploadUrl } from '../utils.js';
 import type * as misskey from 'misskey-js';
 
 describe('Note', () => {
-	let app: INestApplicationContext;
 	let Notes: any;
 
-	let alice: misskey.entities.MeSignup;
-	let bob: misskey.entities.MeSignup;
+	let alice: misskey.entities.SignupResponse;
+	let bob: misskey.entities.SignupResponse;
+	let tom: misskey.entities.SignupResponse;
 
 	beforeAll(async () => {
-		app = await startServer();
 		const connection = await initTestDb(true);
 		Notes = connection.getRepository(MiNote);
 		alice = await signup({ username: 'alice' });
 		bob = await signup({ username: 'bob' });
+		tom = await signup({ username: 'tom', host: 'example.com' });
 	}, 1000 * 60 * 2);
 
-	afterAll(async () => {
-		await app.close();
-	});
-
 	test('投稿できる', async () => {
 		const post = {
 			text: 'test',
@@ -143,6 +138,19 @@ describe('Note', () => {
 		assert.strictEqual(res.body.createdNote.renote.text, bobPost.text);
 	});
 
+	test('引用renoteで空白文字のみで構成されたtextにするとレスポンスがtext: nullになる', async () => {
+		const bobPost = await post(bob, {
+			text: 'test',
+		});
+		const res = await api('/notes/create', {
+			text: ' ',
+			renoteId: bobPost.id,
+		}, alice);
+
+		assert.strictEqual(res.status, 200);
+		assert.strictEqual(res.body.createdNote.text, null);
+	});
+
 	test('visibility: followersでrenoteできる', async () => {
 		const createRes = await api('/notes/create', {
 			text: 'test',
@@ -168,6 +176,87 @@ describe('Note', () => {
 		assert.strictEqual(deleteRes.status, 204);
 	});
 
+	test('visibility: followersなノートに対してフォロワーはリプライできる', async () => {
+		await api('/following/create', {
+			userId: alice.id,
+		}, bob);
+
+		const aliceNote = await api('/notes/create', {
+			text: 'direct note to bob',
+			visibility: 'followers',
+		}, alice);
+
+		assert.strictEqual(aliceNote.status, 200);
+
+		const replyId = aliceNote.body.createdNote.id;
+		const bobReply = await api('/notes/create', {
+			text: 'reply to alice note',
+			replyId,
+		}, bob);
+
+		assert.strictEqual(bobReply.status, 200);
+		assert.strictEqual(bobReply.body.createdNote.replyId, replyId);
+
+		await api('/following/delete', {
+			userId: alice.id,
+		}, bob);
+	});
+
+	test('visibility: followersなノートに対してフォロワーでないユーザーがリプライしようとすると怒られる', async () => {
+		const aliceNote = await api('/notes/create', {
+			text: 'direct note to bob',
+			visibility: 'followers',
+		}, alice);
+
+		assert.strictEqual(aliceNote.status, 200);
+
+		const bobReply = await api('/notes/create', {
+			text: 'reply to alice note',
+			replyId: aliceNote.body.createdNote.id,
+		}, bob);
+
+		assert.strictEqual(bobReply.status, 400);
+		assert.strictEqual(bobReply.body.error.code, 'CANNOT_REPLY_TO_AN_INVISIBLE_NOTE');
+	});
+
+	test('visibility: specifiedなノートに対してvisibility: specifiedで返信できる', async () => {
+		const aliceNote = await api('/notes/create', {
+			text: 'direct note to bob',
+			visibility: 'specified',
+			visibleUserIds: [bob.id],
+		}, alice);
+
+		assert.strictEqual(aliceNote.status, 200);
+
+		const bobReply = await api('/notes/create', {
+			text: 'reply to alice note',
+			replyId: aliceNote.body.createdNote.id,
+			visibility: 'specified',
+			visibleUserIds: [alice.id],
+		}, bob);
+
+		assert.strictEqual(bobReply.status, 200);
+	});
+
+	test('visibility: specifiedなノートに対してvisibility: follwersで返信しようとすると怒られる', async () => {
+		const aliceNote = await api('/notes/create', {
+			text: 'direct note to bob',
+			visibility: 'specified',
+			visibleUserIds: [bob.id],
+		}, alice);
+
+		assert.strictEqual(aliceNote.status, 200);
+
+		const bobReply = await api('/notes/create', {
+			text: 'reply to alice note with visibility: followers',
+			replyId: aliceNote.body.createdNote.id,
+			visibility: 'followers',
+		}, bob);
+
+		assert.strictEqual(bobReply.status, 400);
+		assert.strictEqual(bobReply.body.error.code, 'CANNOT_REPLY_TO_SPECIFIED_VISIBILITY_NOTE_WITH_EXTENDED_VISIBILITY');
+	});
+
 	test('文字数ぎりぎりで怒られない', async () => {
 		const post = {
 			text: '!'.repeat(MAX_NOTE_TEXT_LENGTH), // 3000文字
@@ -601,6 +690,242 @@ describe('Note', () => {
 			assert.strictEqual(note2.status, 200);
 			assert.strictEqual(note2.body.createdNote.visibility, 'home');
 		});
+
+		test('禁止ワードを含む投稿はエラーになる (単語指定)', async () => {
+			const prohibited = await api('admin/update-meta', {
+				prohibitedWords: [
+					'test',
+				],
+			}, alice);
+
+			assert.strictEqual(prohibited.status, 204);
+
+			await new Promise(x => setTimeout(x, 2));
+
+			const note1 = await api('/notes/create', {
+				text: 'hogetesthuge',
+			}, alice);
+
+			assert.strictEqual(note1.status, 400);
+			assert.strictEqual(note1.body.error.code, 'CONTAINS_PROHIBITED_WORDS');
+		});
+
+		test('禁止ワードを含む投稿はエラーになる (正規表現)', async () => {
+			const prohibited = await api('admin/update-meta', {
+				prohibitedWords: [
+					'/Test/i',
+				],
+			}, alice);
+
+			assert.strictEqual(prohibited.status, 204);
+
+			const note2 = await api('/notes/create', {
+				text: 'hogetesthuge',
+			}, alice);
+
+			assert.strictEqual(note2.status, 400);
+			assert.strictEqual(note2.body.error.code, 'CONTAINS_PROHIBITED_WORDS');
+		});
+
+		test('禁止ワードを含む投稿はエラーになる (スペースアンド)', async () => {
+			const prohibited = await api('admin/update-meta', {
+				prohibitedWords: [
+					'Test hoge',
+				],
+			}, alice);
+
+			assert.strictEqual(prohibited.status, 204);
+
+			const note2 = await api('/notes/create', {
+				text: 'hogeTesthuge',
+			}, alice);
+
+			assert.strictEqual(note2.status, 400);
+			assert.strictEqual(note2.body.error.code, 'CONTAINS_PROHIBITED_WORDS');
+		});
+
+		test('禁止ワードを含んでるリモートノートもエラーになる', async () => {
+			const prohibited = await api('admin/update-meta', {
+				prohibitedWords: [
+					'test',
+				],
+			}, alice);
+
+			assert.strictEqual(prohibited.status, 204);
+
+			await new Promise(x => setTimeout(x, 2));
+
+			const note1 = await api('/notes/create', {
+				text: 'hogetesthuge',
+			}, tom);
+
+			assert.strictEqual(note1.status, 400);
+		});
+
+		test('メンションの数が上限を超えるとエラーになる', async () => {
+			const res = await api('admin/roles/create', {
+				name: 'test',
+				description: '',
+				color: null,
+				iconUrl: null,
+				displayOrder: 0,
+				target: 'manual',
+				condFormula: {},
+				isAdministrator: false,
+				isModerator: false,
+				isPublic: false,
+				isExplorable: false,
+				asBadge: false,
+				canEditMembersByModerator: false,
+				policies: {
+					mentionLimit: {
+						useDefault: false,
+						priority: 1,
+						value: 0,
+					},
+				},
+			}, alice);
+
+			assert.strictEqual(res.status, 200);
+
+			await new Promise(x => setTimeout(x, 2));
+
+			const assign = await api('admin/roles/assign', {
+				userId: alice.id,
+				roleId: res.body.id,
+			}, alice);
+
+			assert.strictEqual(assign.status, 204);
+
+			await new Promise(x => setTimeout(x, 2));
+
+			const note = await api('/notes/create', {
+				text: '@bob potentially annoying text',
+			}, alice);
+
+			assert.strictEqual(note.status, 400);
+			assert.strictEqual(note.body.error.code, 'CONTAINS_TOO_MANY_MENTIONS');
+
+			await api('admin/roles/unassign', {
+				userId: alice.id,
+				roleId: res.body.id,
+			});
+
+			await api('admin/roles/delete', {
+				roleId: res.body.id,
+			}, alice);
+		});
+
+		test('ダイレクト投稿もエラーになる', async () => {
+			const res = await api('admin/roles/create', {
+				name: 'test',
+				description: '',
+				color: null,
+				iconUrl: null,
+				displayOrder: 0,
+				target: 'manual',
+				condFormula: {},
+				isAdministrator: false,
+				isModerator: false,
+				isPublic: false,
+				isExplorable: false,
+				asBadge: false,
+				canEditMembersByModerator: false,
+				policies: {
+					mentionLimit: {
+						useDefault: false,
+						priority: 1,
+						value: 0,
+					},
+				},
+			}, alice);
+
+			assert.strictEqual(res.status, 200);
+
+			await new Promise(x => setTimeout(x, 2));
+
+			const assign = await api('admin/roles/assign', {
+				userId: alice.id,
+				roleId: res.body.id,
+			}, alice);
+
+			assert.strictEqual(assign.status, 204);
+
+			await new Promise(x => setTimeout(x, 2));
+
+			const note = await api('/notes/create', {
+				text: 'potentially annoying text',
+				visibility: 'specified',
+				visibleUserIds: [ bob.id ],
+			}, alice);
+
+			assert.strictEqual(note.status, 400);
+			assert.strictEqual(note.body.error.code, 'CONTAINS_TOO_MANY_MENTIONS');
+
+			await api('admin/roles/unassign', {
+				userId: alice.id,
+				roleId: res.body.id,
+			});
+
+			await api('admin/roles/delete', {
+				roleId: res.body.id,
+			}, alice);
+		});
+
+		test('ダイレクトの宛先とメンションが同じ場合は重複してカウントしない', async () => {
+			const res = await api('admin/roles/create', {
+				name: 'test',
+				description: '',
+				color: null,
+				iconUrl: null,
+				displayOrder: 0,
+				target: 'manual',
+				condFormula: {},
+				isAdministrator: false,
+				isModerator: false,
+				isPublic: false,
+				isExplorable: false,
+				asBadge: false,
+				canEditMembersByModerator: false,
+				policies: {
+					mentionLimit: {
+						useDefault: false,
+						priority: 1,
+						value: 1,
+					},
+				},
+			}, alice);
+
+			assert.strictEqual(res.status, 200);
+
+			await new Promise(x => setTimeout(x, 2));
+
+			const assign = await api('admin/roles/assign', {
+				userId: alice.id,
+				roleId: res.body.id,
+			}, alice);
+
+			assert.strictEqual(assign.status, 204);
+
+			await new Promise(x => setTimeout(x, 2));
+
+			const note = await api('/notes/create', {
+				text: '@bob potentially annoying text',
+				visibility: 'specified',
+				visibleUserIds: [ bob.id ],
+			}, alice);
+
+			assert.strictEqual(note.status, 200);
+
+			await api('admin/roles/unassign', {
+				userId: alice.id,
+				roleId: res.body.id,
+			});
+
+			await api('admin/roles/delete', {
+				roleId: res.body.id,
+			}, alice);
+		});
 	});
 
 	describe('notes/delete', () => {
diff --git a/packages/backend/test/e2e/oauth.ts b/packages/backend/test/e2e/oauth.ts
index 3a5e4ebdae..ef7a6a579d 100644
--- a/packages/backend/test/e2e/oauth.ts
+++ b/packages/backend/test/e2e/oauth.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -11,13 +11,18 @@
 process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
-import { AuthorizationCode, ResourceOwnerPassword, type AuthorizationTokenConfig, ClientCredentials, ModuleOptions } from 'simple-oauth2';
+import {
+	AuthorizationCode,
+	type AuthorizationTokenConfig,
+	ClientCredentials,
+	ModuleOptions,
+	ResourceOwnerPassword,
+} from 'simple-oauth2';
 import pkceChallenge from 'pkce-challenge';
 import { JSDOM } from 'jsdom';
-import Fastify, { type FastifyReply, type FastifyInstance } from 'fastify';
-import { api, port, signup, startServer } from '../utils.js';
+import Fastify, { type FastifyInstance, type FastifyReply } from 'fastify';
+import { api, port, sendEnvUpdateRequest, signup } from '../utils.js';
 import type * as misskey from 'misskey-js';
-import type { INestApplicationContext } from '@nestjs/common';
 
 const host = `http://127.0.0.1:${port}`;
 
@@ -75,7 +80,7 @@ function getMeta(html: string): { transactionId: string | undefined, clientName:
 	};
 }
 
-function fetchDecision(transactionId: string, user: misskey.entities.MeSignup, { cancel }: { cancel?: boolean } = {}): Promise<Response> {
+function fetchDecision(transactionId: string, user: misskey.entities.SignupResponse, { cancel }: { cancel?: boolean } = {}): Promise<Response> {
 	return fetch(new URL('/oauth/decision', host), {
 		method: 'post',
 		body: new URLSearchParams({
@@ -90,14 +95,14 @@ function fetchDecision(transactionId: string, user: misskey.entities.MeSignup, {
 	});
 }
 
-async function fetchDecisionFromResponse(response: Response, user: misskey.entities.MeSignup, { cancel }: { cancel?: boolean } = {}): Promise<Response> {
+async function fetchDecisionFromResponse(response: Response, user: misskey.entities.SignupResponse, { cancel }: { cancel?: boolean } = {}): Promise<Response> {
 	const { transactionId } = getMeta(await response.text());
 	assert.ok(transactionId);
 
 	return await fetchDecision(transactionId, user, { cancel });
 }
 
-async function fetchAuthorizationCode(user: misskey.entities.MeSignup, scope: string, code_challenge: string): Promise<{ client: AuthorizationCode, code: string }> {
+async function fetchAuthorizationCode(user: misskey.entities.SignupResponse, scope: string, code_challenge: string): Promise<{ client: AuthorizationCode, code: string }> {
 	const client = new AuthorizationCode(clientConfig);
 
 	const response = await fetch(client.authorizeURL({
@@ -147,16 +152,14 @@ async function assertDirectError(response: Response, status: number, error: stri
 }
 
 describe('OAuth', () => {
-	let app: INestApplicationContext;
 	let fastify: FastifyInstance;
 
-	let alice: misskey.entities.MeSignup;
-	let bob: misskey.entities.MeSignup;
+	let alice: misskey.entities.SignupResponse;
+	let bob: misskey.entities.SignupResponse;
 
 	let sender: (reply: FastifyReply) => void;
 
 	beforeAll(async () => {
-		app = await startServer();
 		alice = await signup({ username: 'alice' });
 		bob = await signup({ username: 'bob' });
 
@@ -168,7 +171,7 @@ describe('OAuth', () => {
 	}, 1000 * 60 * 2);
 
 	beforeEach(async () => {
-		process.env.MISSKEY_TEST_CHECK_IP_RANGE = '';
+		await sendEnvUpdateRequest({ key: 'MISSKEY_TEST_CHECK_IP_RANGE', value: '' });
 		sender = (reply): void => {
 			reply.send(`
 				<!DOCTYPE html>
@@ -180,7 +183,6 @@ describe('OAuth', () => {
 
 	afterAll(async () => {
 		await fastify.close();
-		await app.close();
 	});
 
 	test('Full flow', async () => {
@@ -881,7 +883,7 @@ describe('OAuth', () => {
 		});
 
 		test('Disallow loopback', async () => {
-			process.env.MISSKEY_TEST_CHECK_IP_RANGE = '1';
+			await sendEnvUpdateRequest({ key: 'MISSKEY_TEST_CHECK_IP_RANGE', value: '1' });
 
 			const client = new AuthorizationCode(clientConfig);
 			const response = await fetch(client.authorizeURL({
diff --git a/packages/backend/test/e2e/renote-mute.ts b/packages/backend/test/e2e/renote-mute.ts
index 7d57ba17b6..403de0cb8d 100644
--- a/packages/backend/test/e2e/renote-mute.ts
+++ b/packages/backend/test/e2e/renote-mute.ts
@@ -1,34 +1,26 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
-import { signup, api, post, react, startServer, waitFire, sleep } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import { api, post, signup, sleep, waitFire } from '../utils.js';
 import type * as misskey from 'misskey-js';
 
 describe('Renote Mute', () => {
-	let app: INestApplicationContext;
-
 	// alice mutes carol
-	let alice: misskey.entities.MeSignup;
-	let bob: misskey.entities.MeSignup;
-	let carol: misskey.entities.MeSignup;
+	let alice: misskey.entities.SignupResponse;
+	let bob: misskey.entities.SignupResponse;
+	let carol: misskey.entities.SignupResponse;
 
 	beforeAll(async () => {
-		app = await startServer();
 		alice = await signup({ username: 'alice' });
 		bob = await signup({ username: 'bob' });
 		carol = await signup({ username: 'carol' });
 	}, 1000 * 60 * 2);
 
-	afterAll(async () => {
-		await app.close();
-	});
-
 	test('ミュート作成', async () => {
 		const res = await api('/renote-mute/create', {
 			userId: carol.id,
diff --git a/packages/backend/test/e2e/streaming.ts b/packages/backend/test/e2e/streaming.ts
index 288c54bdbc..57ce73ba60 100644
--- a/packages/backend/test/e2e/streaming.ts
+++ b/packages/backend/test/e2e/streaming.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -8,12 +8,10 @@ process.env.NODE_ENV = 'test';
 import * as assert from 'assert';
 import { WebSocket } from 'ws';
 import { MiFollowing } from '@/models/Following.js';
-import { signup, api, post, startServer, initTestDb, waitFire, createAppToken, port } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import { api, createAppToken, initTestDb, port, post, signup, waitFire } from '../utils.js';
 import type * as misskey from 'misskey-js';
 
 describe('Streaming', () => {
-	let app: INestApplicationContext;
 	let Followings: any;
 
 	const follow = async (follower: any, followee: any) => {
@@ -32,23 +30,22 @@ describe('Streaming', () => {
 
 	describe('Streaming', () => {
 		// Local users
-		let ayano: misskey.entities.MeSignup;
-		let kyoko: misskey.entities.MeSignup;
-		let chitose: misskey.entities.MeSignup;
-		let kanako: misskey.entities.MeSignup;
+		let ayano: misskey.entities.SignupResponse;
+		let kyoko: misskey.entities.SignupResponse;
+		let chitose: misskey.entities.SignupResponse;
+		let kanako: misskey.entities.SignupResponse;
 
 		// Remote users
-		let akari: misskey.entities.MeSignup;
-		let chinatsu: misskey.entities.MeSignup;
-		let takumi: misskey.entities.MeSignup;
+		let akari: misskey.entities.SignupResponse;
+		let chinatsu: misskey.entities.SignupResponse;
+		let takumi: misskey.entities.SignupResponse;
 
-		let kyokoNote: any;
-		let kanakoNote: any;
-		let takumiNote: any;
+		let kyokoNote: misskey.entities.Note;
+		let kanakoNote: misskey.entities.Note;
+		let takumiNote: misskey.entities.Note;
 		let list: any;
 
 		beforeAll(async () => {
-			app = await startServer();
 			const connection = await initTestDb(true);
 			Followings = connection.getRepository(MiFollowing);
 
@@ -71,6 +68,9 @@ describe('Streaming', () => {
 			// Follow: ayano => akari
 			await follow(ayano, akari);
 
+			// Follow: kyoko => chitose
+			await api('following/create', { userId: chitose.id }, kyoko);
+
 			// Mute: chitose => kanako
 			await api('mute/create', { userId: kanako.id }, chitose);
 
@@ -95,10 +95,6 @@ describe('Streaming', () => {
 			}, chitose);
 		}, 1000 * 60 * 2);
 
-		afterAll(async () => {
-			await app.close();
-		});
-
 		describe('Events', () => {
 			test('mention event', async () => {
 				const fired = await waitFire(
@@ -177,7 +173,28 @@ describe('Streaming', () => {
 			*/
 
 			test('フォローしているユーザーのフォローしていないユーザーの visibility: followers な投稿への返信が流れない', async () => {
-				// TODO
+				const chitoseNote = await post(chitose, { text: 'followers-only post', visibility: 'followers' });
+
+				const fired = await waitFire(
+					ayano, 'homeTimeline',	// ayano:home
+					() => api('notes/create', { text: 'reply to chitose\'s followers-only post', replyId: chitoseNote.id }, kyoko),	// kyoko's reply to chitose's followers-only post
+					msg => msg.type === 'note' && msg.body.userId === kyoko.id,	// wait kyoko
+				);
+
+				assert.strictEqual(fired, false);
+			});
+
+			test('フォローしているユーザーのフォローしていないユーザーの visibility: followers な投稿への返信のリノートが流れない', async () => {
+				const chitoseNote = await post(chitose, { text: 'followers-only post', visibility: 'followers' });
+				const kyokoReply = await post(kyoko, { text: 'reply to followers-only post', replyId: chitoseNote.id });
+
+				const fired = await waitFire(
+					ayano, 'homeTimeline',	// ayano:home
+					() => api('notes/create', { renoteId: kyokoReply.id }, kyoko),	// kyoko's renote of kyoko's reply to chitose's followers-only post
+					msg => msg.type === 'note' && msg.body.userId === kyoko.id,	// wait kyoko
+				);
+
+				assert.strictEqual(fired, false);
 			});
 
 			test('フォローしていないユーザーの投稿は流れない', async () => {
@@ -209,6 +226,79 @@ describe('Streaming', () => {
 
 				assert.strictEqual(fired, false);
 			});
+
+			/**
+			 * TODO: 落ちる
+			 * @see https://github.com/misskey-dev/misskey/issues/13474
+			test('visibility: specified なノートで visibleUserIds に自分が含まれているときそのノートへのリプライが流れてくる', async () => {
+				const chitoseToKyokoAndAyano = await post(chitose, { text: 'direct note from chitose to kyoko and ayano', visibility: 'specified', visibleUserIds: [kyoko.id, ayano.id] });
+
+				const fired = await waitFire(
+					ayano, 'homeTimeline',	// ayano:home
+					() => api('notes/create', { text: 'direct reply from kyoko to chitose and ayano', replyId: chitoseToKyokoAndAyano.id, visibility: 'specified', visibleUserIds: [chitose.id, ayano.id] }, kyoko),
+					msg => msg.type === 'note' && msg.body.userId === kyoko.id,
+				);
+
+				assert.strictEqual(fired, true);
+			});
+			 */
+
+			test('visibility: specified な投稿に対するリプライで visibleUserIds が拡張されたとき、その拡張されたユーザーの HTL にはそのリプライが流れない', async () => {
+				const chitoseToKyoko = await post(chitose, { text: 'direct note from chitose to kyoko', visibility: 'specified', visibleUserIds: [kyoko.id] });
+
+				const fired = await waitFire(
+					ayano, 'homeTimeline',	// ayano:home
+					() => api('notes/create', { text: 'direct reply from kyoko to chitose and ayano', replyId: chitoseToKyoko.id, visibility: 'specified', visibleUserIds: [chitose.id, ayano.id] }, kyoko),
+					msg => msg.type === 'note' && msg.body.userId === kyoko.id,
+				);
+
+				assert.strictEqual(fired, false);
+			});
+
+			test('visibility: specified な投稿に対するリプライで visibleUserIds が収縮されたとき、その収縮されたユーザーの HTL にはそのリプライが流れない', async () => {
+				const chitoseToKyokoAndAyano = await post(chitose, { text: 'direct note from chitose to kyoko and ayano', visibility: 'specified', visibleUserIds: [kyoko.id, ayano.id] });
+
+				const fired = await waitFire(
+					ayano, 'homeTimeline',	// ayano:home
+					() => api('notes/create', { text: 'direct reply from kyoko to chitose', replyId: chitoseToKyokoAndAyano.id, visibility: 'specified', visibleUserIds: [chitose.id] }, kyoko),
+					msg => msg.type === 'note' && msg.body.userId === kyoko.id,
+				);
+
+				assert.strictEqual(fired, false);
+			});
+
+			test('withRenotes: false のときリノートが流れない', async () => {
+				const fired = await waitFire(
+					ayano, 'homeTimeline',	// ayano:home
+					() => api('notes/create', { renoteId: kyokoNote.id }, kyoko),	// kyoko renote
+					msg => msg.type === 'note' && msg.body.userId === kyoko.id,	// wait kyoko
+					{ withRenotes: false },
+				);
+
+				assert.strictEqual(fired, false);
+			});
+
+			test('withRenotes: false のとき引用リノートが流れる', async () => {
+				const fired = await waitFire(
+					ayano, 'homeTimeline',	// ayano:home
+					() => api('notes/create', { text: 'quote', renoteId: kyokoNote.id }, kyoko),	// kyoko quote
+					msg => msg.type === 'note' && msg.body.userId === kyoko.id,	// wait kyoko
+					{ withRenotes: false },
+				);
+
+				assert.strictEqual(fired, true);
+			});
+
+			test('withRenotes: false のとき投票のみのリノートが流れる', async () => {
+				const fired = await waitFire(
+					ayano, 'homeTimeline',	// ayano:home
+					() => api('notes/create', { poll: { choices: ['kinoko', 'takenoko'] }, renoteId: kyokoNote.id }, kyoko),	// kyoko renote with poll
+					msg => msg.type === 'note' && msg.body.userId === kyoko.id,	// wait kyoko
+					{ withRenotes: false },
+				);
+
+				assert.strictEqual(fired, true);
+			});
 		});	// Home
 
 		describe('Local Timeline', () => {
diff --git a/packages/backend/test/e2e/thread-mute.ts b/packages/backend/test/e2e/thread-mute.ts
index 0e487976dc..b4570cdef1 100644
--- a/packages/backend/test/e2e/thread-mute.ts
+++ b/packages/backend/test/e2e/thread-mute.ts
@@ -1,33 +1,25 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
-import { signup, api, post, connectStream, startServer } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import { api, connectStream, post, signup } from '../utils.js';
 import type * as misskey from 'misskey-js';
 
 describe('Note thread mute', () => {
-	let app: INestApplicationContext;
-
-	let alice: misskey.entities.MeSignup;
-	let bob: misskey.entities.MeSignup;
-	let carol: misskey.entities.MeSignup;
+	let alice: misskey.entities.SignupResponse;
+	let bob: misskey.entities.SignupResponse;
+	let carol: misskey.entities.SignupResponse;
 
 	beforeAll(async () => {
-		app = await startServer();
 		alice = await signup({ username: 'alice' });
 		bob = await signup({ username: 'bob' });
 		carol = await signup({ username: 'carol' });
 	}, 1000 * 60 * 2);
 
-	afterAll(async () => {
-		await app.close();
-	});
-
 	test('notes/mentions にミュートしているスレッドの投稿が含まれない', async () => {
 		const bobNote = await post(bob, { text: '@alice @carol root note' });
 		const aliceReply = await post(alice, { replyId: bobNote.id, text: '@bob @carol child note' });
diff --git a/packages/backend/test/e2e/timelines.ts b/packages/backend/test/e2e/timelines.ts
index cb9558b416..0e71d707dd 100644
--- a/packages/backend/test/e2e/timelines.ts
+++ b/packages/backend/test/e2e/timelines.ts
@@ -1,17 +1,13 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 // How to run:
 // pnpm jest -- e2e/timelines.ts
 
-process.env.NODE_ENV = 'test';
-process.env.FORCE_FOLLOW_REMOTE_USER_FOR_TESTING = 'true';
-
 import * as assert from 'assert';
-import { api, post, randomString, signup, sleep, startServer, uploadUrl } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import { api, post, randomString, sendEnvUpdateRequest, signup, sleep, uploadUrl } from '../utils.js';
 
 function genHost() {
 	return randomString() + '.example.com';
@@ -21,16 +17,6 @@ function waitForPushToTl() {
 	return sleep(500);
 }
 
-let app: INestApplicationContext;
-
-beforeAll(async () => {
-	app = await startServer();
-}, 1000 * 60 * 2);
-
-afterAll(async () => {
-	await app.close();
-});
-
 describe('Timelines', () => {
 	describe('Home TL', () => {
 		test.concurrent('自分の visibility: followers なノートが含まれる', async () => {
@@ -334,8 +320,9 @@ describe('Timelines', () => {
 		test.concurrent('フォローしているリモートユーザーのノートが含まれる', async () => {
 			const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
 
+			await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' });
 			await api('/following/create', { userId: bob.id }, alice);
-			await sleep(1000);
+
 			const bobNote = await post(bob, { text: 'hi' });
 
 			await waitForPushToTl();
@@ -348,8 +335,9 @@ describe('Timelines', () => {
 		test.concurrent('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => {
 			const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
 
+			await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' });
 			await api('/following/create', { userId: bob.id }, alice);
-			await sleep(1000);
+
 			const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
 
 			await waitForPushToTl();
@@ -762,8 +750,9 @@ describe('Timelines', () => {
 		test.concurrent('フォローしているリモートユーザーのノートが含まれる', async () => {
 			const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
 
+			await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' });
 			await api('/following/create', { userId: bob.id }, alice);
-			await sleep(1000);
+
 			const bobNote = await post(bob, { text: 'hi' });
 
 			await waitForPushToTl();
@@ -776,8 +765,9 @@ describe('Timelines', () => {
 		test.concurrent('フォローしているリモートユーザーの visibility: home なノートが含まれる', async () => {
 			const [alice, bob] = await Promise.all([signup(), signup({ host: genHost() })]);
 
+			await sendEnvUpdateRequest({ key: 'FORCE_FOLLOW_REMOTE_USER_FOR_TESTING', value: 'true' });
 			await api('/following/create', { userId: bob.id }, alice);
-			await sleep(1000);
+
 			const bobNote = await post(bob, { text: 'hi', visibility: 'home' });
 
 			await waitForPushToTl();
diff --git a/packages/backend/test/e2e/user-notes.ts b/packages/backend/test/e2e/user-notes.ts
index b5f00a6327..6897cf08c6 100644
--- a/packages/backend/test/e2e/user-notes.ts
+++ b/packages/backend/test/e2e/user-notes.ts
@@ -1,25 +1,21 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
-import { signup, api, post, uploadUrl, startServer } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import { api, post, signup, uploadUrl } from '../utils.js';
 import type * as misskey from 'misskey-js';
 
 describe('users/notes', () => {
-	let app: INestApplicationContext;
-
-	let alice: misskey.entities.MeSignup;
+	let alice: misskey.entities.SignupResponse;
 	let jpgNote: any;
 	let pngNote: any;
 	let jpgPngNote: any;
 
 	beforeAll(async () => {
-		app = await startServer();
 		alice = await signup({ username: 'alice' });
 		const jpg = await uploadUrl(alice, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.jpg');
 		const png = await uploadUrl(alice, 'https://raw.githubusercontent.com/misskey-dev/misskey/develop/packages/backend/test/resources/Lenna.png');
@@ -34,10 +30,6 @@ describe('users/notes', () => {
 		});
 	}, 1000 * 60 * 2);
 
-	afterAll(async() => {
-		await app.close();
-	});
-
 	test('withFiles', async () => {
 		const res = await api('/users/notes', {
 			userId: alice.id,
diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts
index be6f0ec855..19b905dd47 100644
--- a/packages/backend/test/e2e/users.ts
+++ b/packages/backend/test/e2e/users.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -8,20 +8,8 @@ process.env.NODE_ENV = 'test';
 import * as assert from 'assert';
 import { inspect } from 'node:util';
 import { DEFAULT_POLICIES } from '@/core/RoleService.js';
-import type { Packed } from '@/misc/json-schema.js';
-import {
-	signup,
-	post,
-	page,
-	role,
-	startServer,
-	api,
-	successfulApiCall,
-	failedApiCall,
-	uploadFile,
-} from '../utils.js';
+import { api, page, post, role, signup, successfulApiCall, uploadFile } from '../utils.js';
 import type * as misskey from 'misskey-js';
-import type { INestApplicationContext } from '@nestjs/common';
 
 describe('ユーザー', () => {
 	// エンティティとしてのユーザーを主眼においたテストを記述する
@@ -188,8 +176,6 @@ describe('ユーザー', () => {
 		});
 	};
 
-	let app: INestApplicationContext;
-
 	let root: User;
 	let alice: User;
 	let aliceNote: misskey.entities.Note;
@@ -233,10 +219,6 @@ describe('ユーザー', () => {
 	let userFollowRequesting: User;
 	let userFollowRequested: User;
 
-	beforeAll(async () => {
-		app = await startServer();
-	}, 1000 * 60 * 2);
-
 	beforeAll(async () => {
 		root = await signup({ username: 'root' });
 		alice = await signup({ username: 'alice' });
@@ -324,10 +306,6 @@ describe('ユーザー', () => {
 		await api('following/create', { userId: userFollowRequested.id }, userFollowRequesting);
 	}, 1000 * 60 * 10);
 
-	afterAll(async () => {
-		await app.close();
-	});
-
 	beforeEach(async () => {
 		alice = {
 			...alice,
diff --git a/packages/backend/test/e2e/well-known.ts b/packages/backend/test/e2e/well-known.ts
index 14e32e1627..bdb298dfe4 100644
--- a/packages/backend/test/e2e/well-known.ts
+++ b/packages/backend/test/e2e/well-known.ts
@@ -1,29 +1,21 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
-import { host, origin, relativeFetch, signup, startServer } from '../utils.js';
-import type { INestApplicationContext } from '@nestjs/common';
+import { host, origin, relativeFetch, signup } from '../utils.js';
 import type * as misskey from 'misskey-js';
 
 describe('.well-known', () => {
-	let app: INestApplicationContext;
 	let alice: misskey.entities.User;
 
 	beforeAll(async () => {
-		app = await startServer();
-
 		alice = await signup({ username: 'alice' });
 	}, 1000 * 60 * 2);
 
-	afterAll(async () => {
-		await app.close();
-	});
-
 	test('nodeinfo', async () => {
 		const res = await relativeFetch('.well-known/nodeinfo');
 		assert.ok(res.ok);
diff --git a/packages/backend/test/jest.setup.ts b/packages/backend/test/jest.setup.ts
new file mode 100644
index 0000000000..cf5b9bf24d
--- /dev/null
+++ b/packages/backend/test/jest.setup.ts
@@ -0,0 +1,8 @@
+import { initTestDb, sendEnvResetRequest } from './utils.js';
+
+beforeAll(async () => {
+	await Promise.all([
+		initTestDb(false),
+		sendEnvResetRequest(),
+	]);
+});
diff --git a/packages/backend/test/misc/mock-resolver.ts b/packages/backend/test/misc/mock-resolver.ts
index 7cba7a2aa8..3c7e796700 100644
--- a/packages/backend/test/misc/mock-resolver.ts
+++ b/packages/backend/test/misc/mock-resolver.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -15,7 +15,13 @@ import type { LoggerService } from '@/core/LoggerService.js';
 import type { MetaService } from '@/core/MetaService.js';
 import type { UtilityService } from '@/core/UtilityService.js';
 import { bindThis } from '@/decorators.js';
-import type { NoteReactionsRepository, NotesRepository, PollsRepository, UsersRepository, FollowRequestsRepository } from '@/models/_.js';
+import type {
+	FollowRequestsRepository,
+	NoteReactionsRepository,
+	NotesRepository,
+	PollsRepository,
+	UsersRepository,
+} from '@/models/_.js';
 
 type MockResponse = {
 	type: string;
diff --git a/packages/backend/test/prelude/get-api-validator.ts b/packages/backend/test/prelude/get-api-validator.ts
index cccd63299a..b86a7a978d 100644
--- a/packages/backend/test/prelude/get-api-validator.ts
+++ b/packages/backend/test/prelude/get-api-validator.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/test/prelude/maybe.ts b/packages/backend/test/prelude/maybe.ts
index 37ccfbf7fe..16e92216d4 100644
--- a/packages/backend/test/prelude/maybe.ts
+++ b/packages/backend/test/prelude/maybe.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/test/prelude/url.ts b/packages/backend/test/prelude/url.ts
index 340c6451ce..b26ae09444 100644
--- a/packages/backend/test/prelude/url.ts
+++ b/packages/backend/test/prelude/url.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/test/unit/AnnouncementService.ts b/packages/backend/test/unit/AnnouncementService.ts
index f2aa5d35e4..fc35837420 100644
--- a/packages/backend/test/unit/AnnouncementService.ts
+++ b/packages/backend/test/unit/AnnouncementService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -10,7 +10,13 @@ import { ModuleMocker } from 'jest-mock';
 import { Test } from '@nestjs/testing';
 import { GlobalModule } from '@/GlobalModule.js';
 import { AnnouncementService } from '@/core/AnnouncementService.js';
-import type { MiAnnouncement, AnnouncementsRepository, AnnouncementReadsRepository, UsersRepository, MiUser } from '@/models/_.js';
+import type {
+	AnnouncementReadsRepository,
+	AnnouncementsRepository,
+	MiAnnouncement,
+	MiUser,
+	UsersRepository,
+} from '@/models/_.js';
 import { DI } from '@/di-symbols.js';
 import { genAidx } from '@/misc/id/aidx.js';
 import { CacheService } from '@/core/CacheService.js';
diff --git a/packages/backend/test/unit/ApMfmService.ts b/packages/backend/test/unit/ApMfmService.ts
new file mode 100644
index 0000000000..2b79041c86
--- /dev/null
+++ b/packages/backend/test/unit/ApMfmService.ts
@@ -0,0 +1,44 @@
+import * as assert from 'assert';
+import { Test } from '@nestjs/testing';
+
+import { CoreModule } from '@/core/CoreModule.js';
+import { ApMfmService } from '@/core/activitypub/ApMfmService.js';
+import { GlobalModule } from '@/GlobalModule.js';
+import { MiNote } from '@/models/Note.js';
+
+describe('ApMfmService', () => {
+	let apMfmService: ApMfmService;
+
+	beforeAll(async () => {
+		const app = await Test.createTestingModule({
+			imports: [GlobalModule, CoreModule],
+		}).compile();
+		apMfmService = app.get<ApMfmService>(ApMfmService);
+	});
+
+	describe('getNoteHtml', () => {
+		test('Do not provide _misskey_content for simple text', () => {
+			const note: MiNote = {
+				text: 'テキスト #タグ @mention 🍊 :emoji: https://example.com',
+				mentionedRemoteUsers: '[]',
+			} as any;
+
+			const { content, noMisskeyContent } = apMfmService.getNoteHtml(note);
+
+			assert.equal(noMisskeyContent, true, 'noMisskeyContent');
+			assert.equal(content, '<p>テキスト <a href="http://misskey.local/tags/タグ" rel="tag">#タグ</a> <a href="http://misskey.local/@mention" class="u-url mention">@mention</a> 🍊 ​:emoji:​ <a href="https://example.com">https://example.com</a></p>', 'content');
+		});
+
+		test('Provide _misskey_content for MFM', () => {
+			const note: MiNote = {
+				text: '$[tada foo]',
+				mentionedRemoteUsers: '[]',
+			} as any;
+
+			const { content, noMisskeyContent } = apMfmService.getNoteHtml(note);
+
+			assert.equal(noMisskeyContent, false, 'noMisskeyContent');
+			assert.equal(content, '<p><i>foo</i></p>', 'content');
+		});
+	});
+});
diff --git a/packages/backend/test/unit/DriveService.ts b/packages/backend/test/unit/DriveService.ts
index 7234da2e36..964c65ccaa 100644
--- a/packages/backend/test/unit/DriveService.ts
+++ b/packages/backend/test/unit/DriveService.ts
@@ -1,12 +1,18 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 process.env.NODE_ENV = 'test';
 
 import { Test } from '@nestjs/testing';
-import { DeleteObjectCommandOutput, DeleteObjectCommand, NoSuchKey, InvalidObjectState, S3Client } from '@aws-sdk/client-s3';
+import {
+	DeleteObjectCommand,
+	DeleteObjectCommandOutput,
+	InvalidObjectState,
+	NoSuchKey,
+	S3Client,
+} from '@aws-sdk/client-s3';
 import { mockClient } from 'aws-sdk-client-mock';
 import { GlobalModule } from '@/GlobalModule.js';
 import { DriveService } from '@/core/DriveService.js';
diff --git a/packages/backend/test/unit/FetchInstanceMetadataService.ts b/packages/backend/test/unit/FetchInstanceMetadataService.ts
index 34200899d4..e6e68ccd6d 100644
--- a/packages/backend/test/unit/FetchInstanceMetadataService.ts
+++ b/packages/backend/test/unit/FetchInstanceMetadataService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -55,7 +55,8 @@ describe('FetchInstanceMetadataService', () => {
 					return { fetch: jest.fn() };
 				} else if (token === DI.redis) {
 					return mockRedis;
-				}})
+				}
+			})
 			.compile();
 
 		app.enableShutdownHooks();
diff --git a/packages/backend/test/unit/FileInfoService.ts b/packages/backend/test/unit/FileInfoService.ts
index ba524adff4..7d89886064 100644
--- a/packages/backend/test/unit/FileInfoService.ts
+++ b/packages/backend/test/unit/FileInfoService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -10,7 +10,7 @@ import { fileURLToPath } from 'node:url';
 import { dirname } from 'node:path';
 import { ModuleMocker } from 'jest-mock';
 import { Test } from '@nestjs/testing';
-import { describe, beforeAll, afterAll, test } from '@jest/globals';
+import { afterAll, beforeAll, describe, test } from '@jest/globals';
 import { GlobalModule } from '@/GlobalModule.js';
 import { FileInfoService } from '@/core/FileInfoService.js';
 //import { DI } from '@/di-symbols.js';
diff --git a/packages/backend/test/unit/MetaService.ts b/packages/backend/test/unit/MetaService.ts
index ab30f48283..19c98eab3d 100644
--- a/packages/backend/test/unit/MetaService.ts
+++ b/packages/backend/test/unit/MetaService.ts
@@ -1,20 +1,18 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 process.env.NODE_ENV = 'test';
 
 import { jest } from '@jest/globals';
-import { ModuleMocker } from 'jest-mock';
 import { Test } from '@nestjs/testing';
 import { GlobalModule } from '@/GlobalModule.js';
-import type { MetasRepository } from '@/models/_.js';
 import { DI } from '@/di-symbols.js';
 import { MetaService } from '@/core/MetaService.js';
 import { CoreModule } from '@/core/CoreModule.js';
-import type { DataSource } from 'typeorm';
 import type { TestingModule } from '@nestjs/testing';
+import type { DataSource } from 'typeorm';
 
 describe('MetaService', () => {
 	let app: TestingModule;
diff --git a/packages/backend/test/unit/MfmService.ts b/packages/backend/test/unit/MfmService.ts
index 49e84ccec8..85242f2bc7 100644
--- a/packages/backend/test/unit/MfmService.ts
+++ b/packages/backend/test/unit/MfmService.ts
@@ -1,10 +1,10 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import * as assert from 'assert';
-import * as mfm from '@sharkey/sfm-js';
+import * as mfm from '@transfem-org/sfm-js';
 import { Test } from '@nestjs/testing';
 
 import { CoreModule } from '@/core/CoreModule.js';
@@ -33,6 +33,12 @@ describe('MfmService', () => {
 			const output = '<p><span>foo<br>bar<br>baz</span></p>';
 			assert.equal(mfmService.toHtml(mfm.parse(input)), output);
 		});
+
+		test('Do not generate unnecessary span', () => {
+			const input = 'foo $[tada bar]';
+			const output = '<p>foo <i>bar</i></p>';
+			assert.equal(mfmService.toHtml(mfm.parse(input)), output);
+		});
 	});
 
 	describe('fromHtml', () => {
diff --git a/packages/backend/test/unit/ReactionService.ts b/packages/backend/test/unit/ReactionService.ts
index 7b5bf7d0a0..1957f4544c 100644
--- a/packages/backend/test/unit/ReactionService.ts
+++ b/packages/backend/test/unit/ReactionService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -90,4 +90,45 @@ describe('ReactionService', () => {
 			assert.strictEqual(await reactionService.normalize('unknown'), '❤');
 		});
 	});
+
+	describe('convertLegacyReactions', () => {
+		test('空の入力に対しては何もしない', () => {
+			const input = {};
+			assert.deepStrictEqual(reactionService.convertLegacyReactions(input), input);
+		});
+
+		test('Unicode絵文字リアクションを変換してしまわない', () => {
+			const input = { '👍': 1, '🍮': 2 };
+			assert.deepStrictEqual(reactionService.convertLegacyReactions(input), input);
+		});
+
+		test('カスタム絵文字リアクションを変換してしまわない', () => {
+			const input = { ':like@.:': 1, ':pudding@example.tld:': 2 };
+			assert.deepStrictEqual(reactionService.convertLegacyReactions(input), input);
+		});
+
+		test('文字列によるレガシーなリアクションを変換する', () => {
+			const input = { 'like': 1, 'pudding': 2 };
+			const output = { '👍': 1, '🍮': 2 };
+			assert.deepStrictEqual(reactionService.convertLegacyReactions(input), output);
+		});
+
+		test('host部分が省略されたレガシーなカスタム絵文字リアクションを変換する', () => {
+			const input = { ':custom_emoji:': 1 };
+			const output = { ':custom_emoji@.:': 1 };
+			assert.deepStrictEqual(reactionService.convertLegacyReactions(input), output);
+		});
+
+		test('「0個のリアクション」情報を削除する', () => {
+			const input = { 'angry': 0 };
+			const output = {};
+			assert.deepStrictEqual(reactionService.convertLegacyReactions(input), output);
+		});
+
+		test('host部分の有無によりデコードすると同じ表記になるカスタム絵文字リアクションの個数情報を正しく足し合わせる', () => {
+			const input = { ':custom_emoji:': 1, ':custom_emoji@.:': 2 };
+			const output = { ':custom_emoji@.:': 3 };
+			assert.deepStrictEqual(reactionService.convertLegacyReactions(input), output);
+		});
+	});
 });
diff --git a/packages/backend/test/unit/RelayService.ts b/packages/backend/test/unit/RelayService.ts
index f780a25388..f2a67dba46 100644
--- a/packages/backend/test/unit/RelayService.ts
+++ b/packages/backend/test/unit/RelayService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/test/unit/RoleService.ts b/packages/backend/test/unit/RoleService.ts
index 9879eb8e3e..fe5ad31597 100644
--- a/packages/backend/test/unit/RoleService.ts
+++ b/packages/backend/test/unit/RoleService.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -11,7 +11,7 @@ import { Test } from '@nestjs/testing';
 import * as lolex from '@sinonjs/fake-timers';
 import { GlobalModule } from '@/GlobalModule.js';
 import { RoleService } from '@/core/RoleService.js';
-import type { MiRole, RolesRepository, RoleAssignmentsRepository, UsersRepository, MiUser } from '@/models/_.js';
+import type { MiRole, MiUser, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/_.js';
 import { DI } from '@/di-symbols.js';
 import { MetaService } from '@/core/MetaService.js';
 import { genAidx } from '@/misc/id/aidx.js';
@@ -251,6 +251,34 @@ describe('RoleService', () => {
 			expect(user2Policies.canManageCustomEmojis).toBe(true);
 		});
 
+		test('コンディショナルロール: マニュアルロールにアサイン済み', async () => {
+			const [user1, user2, role1] = await Promise.all([
+				createUser(),
+				createUser(),
+				createRole({
+					name: 'manual role',
+				}),
+			]);
+			const role2 = await createRole({
+				name: 'conditional role',
+				target: 'conditional',
+				condFormula: {
+					// idはバックエンドのロジックに必要ない?
+					id: 'bdc612bd-9d54-4675-ae83-0499c82ea670',
+					type: 'roleAssignedTo',
+					roleId: role1.id,
+				},
+			});
+			await roleService.assign(user2.id, role1.id);
+
+			const [u1role, u2role] = await Promise.all([
+				roleService.getUserRoles(user1.id),
+				roleService.getUserRoles(user2.id),
+			]);
+			expect(u1role.some(r => r.id === role2.id)).toBe(false);
+			expect(u2role.some(r => r.id === role2.id)).toBe(true);
+		});
+
 		test('expired role', async () => {
 			const user = await createUser();
 			const role = await createRole({
diff --git a/packages/backend/test/unit/S3Service.ts b/packages/backend/test/unit/S3Service.ts
index c1eafc96b7..151f3b826a 100644
--- a/packages/backend/test/unit/S3Service.ts
+++ b/packages/backend/test/unit/S3Service.ts
@@ -1,12 +1,18 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 process.env.NODE_ENV = 'test';
 
 import { Test } from '@nestjs/testing';
-import { UploadPartCommand, CompleteMultipartUploadCommand, CreateMultipartUploadCommand, S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
+import {
+	CompleteMultipartUploadCommand,
+	CreateMultipartUploadCommand,
+	PutObjectCommand,
+	S3Client,
+	UploadPartCommand,
+} from '@aws-sdk/client-s3';
 import { mockClient } from 'aws-sdk-client-mock';
 import { GlobalModule } from '@/GlobalModule.js';
 import { CoreModule } from '@/core/CoreModule.js';
diff --git a/packages/backend/test/unit/activitypub.ts b/packages/backend/test/unit/activitypub.ts
index 014e862f9c..bd335bb2fe 100644
--- a/packages/backend/test/unit/activitypub.ts
+++ b/packages/backend/test/unit/activitypub.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -100,6 +100,7 @@ describe('ActivityPub', () => {
 		perRemoteUserUserTimelineCacheMax: 800,
 		blockedHosts: [] as string[],
 		sensitiveWords: [] as string[],
+		prohibitedWords: [] as string[],
 	} as MiMeta;
 	let meta = metaInitial;
 
@@ -222,7 +223,7 @@ describe('ActivityPub', () => {
 			await personService.createPerson(actor.id, resolver);
 
 			// All notes in `featured` are same-origin, no need to fetch notes again
-			assert.deepStrictEqual(resolver.remoteGetTrials(), [actor.id, actor.featured]);
+			assert.deepStrictEqual(resolver.remoteGetTrials(), [actor.id, `${actor.id}/outbox`, actor.featured]);
 
 			// Created notes without resolving anything
 			for (const item of featured.items as IPost[]) {
@@ -255,7 +256,7 @@ describe('ActivityPub', () => {
 			// actor2Note is from a different server and needs to be fetched again
 			assert.deepStrictEqual(
 				resolver.remoteGetTrials(),
-				[actor1.id, actor1.featured, actor2Note.id, actor2.id],
+				[actor1.id, `${actor1.id}/outbox`, actor1.featured, actor2Note.id, actor2.id, `${actor2.id}/outbox` ],
 			);
 
 			const note = await noteService.fetchNote(actor2Note.id);
diff --git a/packages/backend/test/unit/ap-request.ts b/packages/backend/test/unit/ap-request.ts
index 9edd53d274..d3d39240dc 100644
--- a/packages/backend/test/unit/ap-request.ts
+++ b/packages/backend/test/unit/ap-request.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/test/unit/chart.ts b/packages/backend/test/unit/chart.ts
index 036e73fd5e..9dedd3a79d 100644
--- a/packages/backend/test/unit/chart.ts
+++ b/packages/backend/test/unit/chart.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/test/unit/extract-mentions.ts b/packages/backend/test/unit/extract-mentions.ts
index 195e9b8198..2aad89d65b 100644
--- a/packages/backend/test/unit/extract-mentions.ts
+++ b/packages/backend/test/unit/extract-mentions.ts
@@ -1,11 +1,11 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import * as assert from 'assert';
 
-import { parse } from '@sharkey/sfm-js';
+import { parse } from '@transfem-org/sfm-js';
 import { extractMentions } from '@/misc/extract-mentions.js';
 
 describe('Extract mentions', () => {
diff --git a/packages/backend/test/unit/misc/check-word-mute.ts b/packages/backend/test/unit/misc/check-word-mute.ts
index 12bfca8bd7..eb0ca0f6cf 100644
--- a/packages/backend/test/unit/misc/check-word-mute.ts
+++ b/packages/backend/test/unit/misc/check-word-mute.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/test/unit/misc/correct-filename.ts b/packages/backend/test/unit/misc/correct-filename.ts
index 0c4482e0bf..c76fb4c494 100644
--- a/packages/backend/test/unit/misc/correct-filename.ts
+++ b/packages/backend/test/unit/misc/correct-filename.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/backend/test/unit/misc/id.ts b/packages/backend/test/unit/misc/id.ts
index 59783a9fa1..d14efb10a6 100644
--- a/packages/backend/test/unit/misc/id.ts
+++ b/packages/backend/test/unit/misc/id.ts
@@ -1,16 +1,16 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import { ulid } from 'ulid';
-import { describe, test, expect } from '@jest/globals';
+import { describe, expect, test } from '@jest/globals';
 import { aidRegExp, genAid, parseAid } from '@/misc/id/aid.js';
 import { aidxRegExp, genAidx, parseAidx } from '@/misc/id/aidx.js';
 import { genMeid, meidRegExp, parseMeid } from '@/misc/id/meid.js';
 import { genMeidg, meidgRegExp, parseMeidg } from '@/misc/id/meidg.js';
 import { genObjectId, objectIdRegExp, parseObjectId } from '@/misc/id/object-id.js';
-import { ulidRegExp, parseUlid } from '@/misc/id/ulid.js';
+import { parseUlid, ulidRegExp } from '@/misc/id/ulid.js';
 
 describe('misc:id', () => {
 	test('aid', () => {
diff --git a/packages/backend/test/unit/misc/others.ts b/packages/backend/test/unit/misc/others.ts
index b16d26d866..3bc134a2b8 100644
--- a/packages/backend/test/unit/misc/others.ts
+++ b/packages/backend/test/unit/misc/others.ts
@@ -1,9 +1,9 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { describe, test, expect } from '@jest/globals';
+import { describe, expect, test } from '@jest/globals';
 import { contentDisposition } from '@/misc/content-disposition.js';
 
 describe('misc:content-disposition', () => {
diff --git a/packages/backend/test/utils.ts b/packages/backend/test/utils.ts
index 8c5fa0006c..cd5dddd68d 100644
--- a/packages/backend/test/utils.ts
+++ b/packages/backend/test/utils.ts
@@ -1,11 +1,11 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import * as assert from 'node:assert';
 import { readFile } from 'node:fs/promises';
-import { isAbsolute, basename } from 'node:path';
+import { basename, isAbsolute } from 'node:path';
 import { randomUUID } from 'node:crypto';
 import { inspect } from 'node:util';
 import WebSocket, { ClientOptions } from 'ws';
@@ -19,7 +19,7 @@ import { entities } from '../src/postgres.js';
 import { loadConfig } from '../src/config.js';
 import type * as misskey from 'misskey-js';
 
-export { server as startServer } from '@/boot/common.js';
+export { server as startServer, jobQueue as startJobQueue } from '@/boot/common.js';
 
 interface UserToken {
 	token: string;
@@ -70,7 +70,11 @@ export const failedApiCall = async <T, >(request: ApiRequest, assertion: {
 	return res.body;
 };
 
-const request = async (path: string, params: any, me?: UserToken): Promise<{ status: number, headers: Headers, body: any }> => {
+const request = async (path: string, params: any, me?: UserToken): Promise<{
+	status: number,
+	headers: Headers,
+	body: any
+}> => {
 	const bodyAuth: Record<string, string> = {};
 	const headers: Record<string, string> = {
 		'Content-Type': 'application/json',
@@ -291,7 +295,11 @@ interface UploadOptions {
  * Upload file
  * @param user User
  */
-export const uploadFile = async (user?: UserToken, { path, name, blob }: UploadOptions = {}): Promise<{ status: number, headers: Headers, body: misskey.Endpoints['drive/files/create']['res'] | null }> => {
+export const uploadFile = async (user?: UserToken, { path, name, blob }: UploadOptions = {}): Promise<{
+	status: number,
+	headers: Headers,
+	body: misskey.Endpoints['drive/files/create']['res'] | null
+}> => {
 	const absPath = path == null
 		? new URL('resources/Lenna.jpg', import.meta.url)
 		: isAbsolute(path.toString())
@@ -327,9 +335,7 @@ export const uploadFile = async (user?: UserToken, { path, name, blob }: UploadO
 	};
 };
 
-export const uploadUrl = async (user: UserToken, url: string) => {
-	let resolve: unknown;
-	const file = new Promise(ok => resolve = ok);
+export const uploadUrl = async (user: UserToken, url: string): Promise<Packed<'DriveFile'>> => {
 	const marker = Math.random().toString();
 
 	const catcher = makeStreamCatcher(
@@ -346,10 +352,10 @@ export const uploadUrl = async (user: UserToken, url: string) => {
 		force: true,
 	}, user);
 
-	return file;
+	return catcher;
 };
 
-export function connectStream(user: UserToken, channel: string, listener: (message: Record<string, any>) => any, params?: any): Promise<WebSocket> {
+export function connectStream<C extends keyof misskey.Channels>(user: UserToken, channel: C, listener: (message: Record<string, any>) => any, params?: misskey.Channels[C]['params']): Promise<WebSocket> {
 	return new Promise((res, rej) => {
 		const url = new URL(`ws://127.0.0.1:${port}/streaming`);
 		const options: ClientOptions = {};
@@ -384,7 +390,7 @@ export function connectStream(user: UserToken, channel: string, listener: (messa
 	});
 }
 
-export const waitFire = async (user: UserToken, channel: string, trgr: () => any, cond: (msg: Record<string, any>) => boolean, params?: any) => {
+export const waitFire = async <C extends keyof misskey.Channels>(user: UserToken, channel: C, trgr: () => any, cond: (msg: Record<string, any>) => boolean, params?: misskey.Channels[C]['params']) => {
 	return new Promise<boolean>(async (res, rej) => {
 		let timer: NodeJS.Timeout | null = null;
 
@@ -429,7 +435,7 @@ export const waitFire = async (user: UserToken, channel: string, trgr: () => any
  */
 export function makeStreamCatcher<T>(
 	user: UserToken,
-	channel: string,
+	channel: keyof misskey.Channels,
 	cond: (message: Record<string, any>) => boolean,
 	extractor: (message: Record<string, any>) => T,
 	timeout = 60 * 1000): Promise<T> {
@@ -479,8 +485,8 @@ export const simpleGet = async (path: string, accept = '*/*', cookie: any = unde
 	}
 
 	const body =
-		jsonTypes.includes(res.headers.get('content-type') ?? '')	? await res.json() :
-		htmlTypes.includes(res.headers.get('content-type') ?? '')	? new JSDOM(await res.text()) :
+		jsonTypes.includes(res.headers.get('content-type') ?? '') ? await res.json() :
+		htmlTypes.includes(res.headers.get('content-type') ?? '') ? new JSDOM(await res.text()) :
 		null;
 
 	return {
@@ -610,3 +616,34 @@ export function sleep(msec: number) {
 		}, msec);
 	});
 }
+
+export async function sendEnvUpdateRequest(params: { key: string, value?: string }) {
+	const res = await fetch(
+		`http://localhost:${port + 1000}/env`,
+		{
+			method: 'POST',
+			headers: {
+				'Content-Type': 'application/json',
+			},
+			body: JSON.stringify(params),
+		},
+	);
+
+	if (res.status !== 200) {
+		throw new Error('server env update failed.');
+	}
+}
+
+export async function sendEnvResetRequest() {
+	const res = await fetch(
+		`http://localhost:${port + 1000}/env-reset`,
+		{
+			method: 'POST',
+			body: JSON.stringify({}),
+		},
+	);
+
+	if (res.status !== 200) {
+		throw new Error('server env update failed.');
+	}
+}
diff --git a/packages/backend/watch.mjs b/packages/backend/watch.mjs
index 81c23a0f50..a0ccea3b16 100644
--- a/packages/backend/watch.mjs
+++ b/packages/backend/watch.mjs
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/.storybook/changes.ts b/packages/frontend/.storybook/changes.ts
index 0cc648fbae..7c70972e1e 100644
--- a/packages/frontend/.storybook/changes.ts
+++ b/packages/frontend/.storybook/changes.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/.storybook/fakes.ts b/packages/frontend/.storybook/fakes.ts
index fdf98665d0..de28d343d1 100644
--- a/packages/frontend/.storybook/fakes.ts
+++ b/packages/frontend/.storybook/fakes.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/.storybook/generate.tsx b/packages/frontend/.storybook/generate.tsx
index d61df9e7be..1e925aede6 100644
--- a/packages/frontend/.storybook/generate.tsx
+++ b/packages/frontend/.storybook/generate.tsx
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -401,7 +401,8 @@ function toStories(component: string): Promise<string> {
 // glob('src/{components,pages,ui,widgets}/**/*.vue')
 (async () => {
 	const globs = await Promise.all([
-		glob('src/components/global/*.vue'),
+		glob('src/components/global/Mk*.vue'),
+		glob('src/components/global/RouterView.vue'),
 		glob('src/components/Mk{A,B}*.vue'),
 		glob('src/components/MkDigitalClock.vue'),
 		glob('src/components/MkGalleryPostPreview.vue'),
diff --git a/packages/frontend/.storybook/main.ts b/packages/frontend/.storybook/main.ts
index a450f8b46b..0a87488573 100644
--- a/packages/frontend/.storybook/main.ts
+++ b/packages/frontend/.storybook/main.ts
@@ -1,27 +1,30 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { resolve } from 'node:path';
+import { createRequire } from 'node:module';
+import { dirname, join, resolve } from 'node:path';
 import { fileURLToPath } from 'node:url';
 import type { StorybookConfig } from '@storybook/vue3-vite';
 import { type Plugin, mergeConfig } from 'vite';
 import turbosnap from 'vite-plugin-turbosnap';
 
-const dirname = fileURLToPath(new URL('.', import.meta.url));
+const require = createRequire(import.meta.url);
+const _dirname = fileURLToPath(new URL('.', import.meta.url));
 
 const config = {
 	stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
 	addons: [
-		'@storybook/addon-essentials',
-		'@storybook/addon-interactions',
-		'@storybook/addon-links',
-		'@storybook/addon-storysource',
-		resolve(dirname, '../node_modules/storybook-addon-misskey-theme'),
+		getAbsolutePath('@storybook/addon-essentials'),
+		getAbsolutePath('@storybook/addon-interactions'),
+		getAbsolutePath('@storybook/addon-links'),
+		getAbsolutePath('@storybook/addon-storysource'),
+		getAbsolutePath('@storybook/addon-mdx-gfm'),
+		resolve(_dirname, '../node_modules/storybook-addon-misskey-theme'),
 	],
 	framework: {
-		name: '@storybook/vue3-vite',
+		name: getAbsolutePath('@storybook/vue3-vite') as '@storybook/vue3-vite',
 		options: {},
 	},
 	docs: {
@@ -37,10 +40,13 @@ const config = {
 		}
 		return mergeConfig(config, {
 			plugins: [
-				// XXX: https://github.com/IanVS/vite-plugin-turbosnap/issues/8
-				(turbosnap as any as typeof turbosnap['default'])({
-					rootDir: config.root ?? process.cwd(),
-				}),
+				{
+					// XXX: https://github.com/IanVS/vite-plugin-turbosnap/issues/8
+					...(turbosnap as any as typeof turbosnap['default'])({
+						rootDir: config.root ?? process.cwd(),
+					}),
+					name: 'fake-turbosnap',
+				},
 			],
 			build: {
 				target: [
@@ -53,3 +59,7 @@ const config = {
 	},
 } satisfies StorybookConfig;
 export default config;
+
+function getAbsolutePath(value: string): string {
+	return dirname(require.resolve(join(value, 'package.json')));
+}
diff --git a/packages/frontend/.storybook/manager.ts b/packages/frontend/.storybook/manager.ts
index 8f501111d0..7375a1f2a9 100644
--- a/packages/frontend/.storybook/manager.ts
+++ b/packages/frontend/.storybook/manager.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/.storybook/mocks.ts b/packages/frontend/.storybook/mocks.ts
index 80e5157c5a..817b0125e7 100644
--- a/packages/frontend/.storybook/mocks.ts
+++ b/packages/frontend/.storybook/mocks.ts
@@ -1,9 +1,9 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { type SharedOptions, rest } from 'msw';
+import { type SharedOptions, http, HttpResponse } from 'msw';
 
 export const onUnhandledRequest = ((req, print) => {
 	if (req.url.hostname !== 'localhost' || /^\/(?:client-assets\/|fluent-emojis?\/|iframe.html$|node_modules\/|src\/|sb-|static-assets\/|vite\/)/.test(req.url.pathname)) {
@@ -13,19 +13,31 @@ export const onUnhandledRequest = ((req, print) => {
 }) satisfies SharedOptions['onUnhandledRequest'];
 
 export const commonHandlers = [
-	rest.get('/fluent-emoji/:codepoints.png', async (req, res, ctx) => {
-		const { codepoints } = req.params;
+	http.get('/fluent-emoji/:codepoints.png', async ({ params }) => {
+		const { codepoints } = params;
 		const value = await fetch(`https://raw.githubusercontent.com/misskey-dev/emojis/main/dist/${codepoints}.png`).then((response) => response.blob());
-		return res(ctx.set('Content-Type', 'image/png'), ctx.body(value));
+		return new HttpResponse(value, {
+			headers: {
+				'Content-Type': 'image/png',
+			},
+		});
 	}),
-	rest.get('/fluent-emojis/:codepoints.png', async (req, res, ctx) => {
-		const { codepoints } = req.params;
+	http.get('/fluent-emojis/:codepoints.png', async ({ params }) => {
+		const { codepoints } = params;
 		const value = await fetch(`https://raw.githubusercontent.com/misskey-dev/emojis/main/dist/${codepoints}.png`).then((response) => response.blob());
-		return res(ctx.set('Content-Type', 'image/png'), ctx.body(value));
+		return new HttpResponse(value, {
+			headers: {
+				'Content-Type': 'image/png',
+			},
+		});
 	}),
-	rest.get('/twemoji/:codepoints.svg', async (req, res, ctx) => {
-		const { codepoints } = req.params;
+	http.get('/twemoji/:codepoints.svg', async ({ params }) => {
+		const { codepoints } = params;
 		const value = await fetch(`https://unpkg.com/@discordapp/twemoji@15.0.2/dist/svg/${codepoints}.svg`).then((response) => response.blob());
-		return res(ctx.set('Content-Type', 'image/svg+xml'), ctx.body(value));
+		return new HttpResponse(value, {
+			headers: {
+				'Content-Type': 'image/svg+xml',
+			},
+		});
 	}),
 ];
diff --git a/packages/frontend/.storybook/preload-locale.ts b/packages/frontend/.storybook/preload-locale.ts
index 349cc13508..c823ff9bee 100644
--- a/packages/frontend/.storybook/preload-locale.ts
+++ b/packages/frontend/.storybook/preload-locale.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/.storybook/preload-theme.ts b/packages/frontend/.storybook/preload-theme.ts
index 8f32c6e622..e174c72b48 100644
--- a/packages/frontend/.storybook/preload-theme.ts
+++ b/packages/frontend/.storybook/preload-theme.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/.storybook/preview.ts b/packages/frontend/.storybook/preview.ts
index 9860b60c67..982a2979ac 100644
--- a/packages/frontend/.storybook/preview.ts
+++ b/packages/frontend/.storybook/preview.ts
@@ -1,10 +1,10 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { addons } from '@storybook/addons';
 import { FORCE_REMOUNT } from '@storybook/core-events';
+import { addons } from '@storybook/preview-api';
 import { type Preview, setup } from '@storybook/vue3';
 import isChromatic from 'chromatic/isChromatic';
 import { initialize, mswDecorator } from 'msw-storybook-addon';
diff --git a/packages/frontend/@types/global.d.ts b/packages/frontend/@types/global.d.ts
index 7d9335cc52..1025d1bedb 100644
--- a/packages/frontend/@types/global.d.ts
+++ b/packages/frontend/@types/global.d.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -16,3 +16,8 @@ declare const _DATA_TRANSFER_DECK_COLUMN_: string;
 
 // for dev-mode
 declare const _LANGS_FULL_: string[][];
+
+// TagCanvas
+interface Window {
+	TagCanvas: any;
+}
diff --git a/packages/frontend/@types/theme.d.ts b/packages/frontend/@types/theme.d.ts
index 376bbb0e9c..0a7281898d 100644
--- a/packages/frontend/@types/theme.d.ts
+++ b/packages/frontend/@types/theme.d.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/assets/drop-and-fusion/bgm_1.mp3 b/packages/frontend/assets/drop-and-fusion/bgm_1.mp3
new file mode 100644
index 0000000000..cafc34ad9c
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/bgm_1.mp3 differ
diff --git a/packages/frontend/assets/drop-and-fusion/click.mp3 b/packages/frontend/assets/drop-and-fusion/click.mp3
new file mode 100644
index 0000000000..ef03e60f61
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/click.mp3 differ
diff --git a/packages/frontend/assets/drop-and-fusion/collision.mp3 b/packages/frontend/assets/drop-and-fusion/collision.mp3
new file mode 100644
index 0000000000..59dae90965
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/collision.mp3 differ
diff --git a/packages/frontend/assets/drop-and-fusion/collision_yen.mp3 b/packages/frontend/assets/drop-and-fusion/collision_yen.mp3
new file mode 100644
index 0000000000..6737357f62
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/collision_yen.mp3 differ
diff --git a/packages/frontend/assets/drop-and-fusion/drop-arrow.svg b/packages/frontend/assets/drop-and-fusion/drop-arrow.svg
new file mode 100644
index 0000000000..f98bb8a1ac
--- /dev/null
+++ b/packages/frontend/assets/drop-and-fusion/drop-arrow.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 128 128" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <path d="M0,0L128,0L64,64L0,0Z" style="fill:rgb(255,61,0);"/>
+    <path d="M0,0L128,0L64,64L0,0ZM28.971,12L64,47.029C64,47.029 99.029,12 99.029,12L28.971,12Z" style="fill:rgb(255,122,0);"/>
+</svg>
diff --git a/packages/frontend/assets/drop-and-fusion/drop.mp3 b/packages/frontend/assets/drop-and-fusion/drop.mp3
new file mode 100644
index 0000000000..a65c653891
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/drop.mp3 differ
diff --git a/packages/frontend/assets/drop-and-fusion/drop_yen.mp3 b/packages/frontend/assets/drop-and-fusion/drop_yen.mp3
new file mode 100644
index 0000000000..bbf385f15a
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/drop_yen.mp3 differ
diff --git a/packages/frontend/assets/drop-and-fusion/dropper.png b/packages/frontend/assets/drop-and-fusion/dropper.png
new file mode 100644
index 0000000000..f4300aa5c0
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/dropper.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/frame-dark.svg b/packages/frontend/assets/drop-and-fusion/frame-dark.svg
new file mode 100644
index 0000000000..3fa7c0da81
--- /dev/null
+++ b/packages/frontend/assets/drop-and-fusion/frame-dark.svg
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 450 600" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
+    <g>
+        <g transform="matrix(0.944444,0,0,0.8125,12.5,100)">
+            <rect x="0" y="0" width="450" height="600"/>
+        </g>
+        <g transform="matrix(0.944444,0,0,0.8125,12.5,100)">
+            <rect x="0" y="0" width="450" height="600" style="fill:rgb(255,147,2);fill-opacity:0.15;"/>
+        </g>
+        <use xlink:href="#_Image1" x="0" y="49.048" width="450px" height="551px"/>
+    </g>
+    <g transform="matrix(0.755719,0.654896,-0.654896,0.755719,383.517,-217.265)">
+        <g transform="matrix(0.755719,-0.654896,0.654896,0.755719,-147.545,415.355)">
+            <use xlink:href="#_Image2" x="0" y="49" width="450px" height="551px"/>
+        </g>
+    </g>
+    <use xlink:href="#_Image3" x="25" y="99.5" width="400px" height="475px"/>
+    <g transform="matrix(1,0,0,2,1.13687e-13,25)">
+        <rect x="25" y="37.5" width="400" height="12.5" style="fill:url(#_Linear4);"/>
+    </g>
+    <defs>
+        <image id="_Image1" width="450px" height="551px" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAcIAAAInCAYAAAALeVnpAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAWlElEQVR4nO3df6yd9X3Y8c/znHN/+Ae2uRgMhFB+pCy1PasqatQVJZFGEkpImmmjycRSb5OiKZPWStMmrVs3Tauqav9N6zqWbFVWodTaukGaLm2TaM3UdYqSLZMaAiYjIRQUfti+GGN8r88953me7/54zv0BCVQh59rYn9fLMgbjc557zx+8+X6f7/P9RvyAjh09svMHfQ0AXAhvpFHV6/3LBz56+MDp1eaX/uz06K88fWZ0/alzk7lR01WLw7pcvXtucuO+xWdvWlr83aWdw3959NOPnHjjXzoA/GBm1ajXDOGv/9W3/+rvf/P0P15emdRdF1GiRCkl6hLRVRFVVUUVVdR1xP5dc909b1/6tV986Jv/bHu+XQDYNMtGfU8IH/jokR/50++e/aM/fvKlW9uuxK1LO+Kdt+6LQ9fuip07BjGYq6ObdLFyvo1Hn1+JP3niTDxx+nwM6ireffPeJ378hj13Hv30w09t/8cAQDbb0ahXhPATHz74sc8eX/7EibOTQR0Rf+P2a+Inf3Qp1iZtjCclulKiROkrW1UxP1fFwtwg/s+3Tsdv/9+T0UXEgT1z7YcO7v/4x3/n+G9eyA8HgMvbdjVqI4QPfPTwW/7zn5586tmXJoPbrl6Mj/2lt0Q9X8fqqI21to2mKdGWiNKVqOoqBlXEcFjFwmAQOxcHUda6+A9feSYePzWK6/cuNB/58f03Hf30I89cjA8LgMvLdjZqsH6RW/fv/trDz527+pardsTff+/NMeraePl8EyvjNkZrbYwmJcZNF5O2xGT6a9N00ZSIpi0xnK/j3bftj//3/Ll4+syo3jU3/MCXn3zxNy7exwbA5WI7GzWIiPiNew/+8888cuqvVVHFL9351ljrSpxbbeP8uI3za22M2y4mTRdNF9F0XbRdRNt20ZYSbVuiRETpIqKKuP0tu+JLj5+JP3txdNUvv/eW7g+On/qfF/PDA+DStt2NGhw7emTff/36yd8/u9bWP3/7gbhu/844e77pLzBu+7I2/dxr15UoJabzsBH9Sp2IrvQXKBGxc8dc7N8xjK8/ey6eO7v2rl+5+9Zff/DrJ0YX80ME4NJ0IRpVL58b/8NTK5PBjVcuxDvethSrozZGky5G0ws07eYFui62/Ox/v5kOQ0fj/nXnx2381NuW4sYrF+LUymSwfG78Dy7uxwjApepCNKp++sz459ou4o6b98a47WKtbWMyaaPptl4gopSqf05j/Uep+otNL9R0XUwmbYwm/TD1jpv2RttFPH1m/OGL/UECcGm6EI2qnzk7emuJEgev2R1rky4mkxJNF9N51f4CEf3Dilut//P6g4xt279uMimxNuni4IHdUaLEM2dHN1zoDw6Ay8OFaFR9eqVZKF3E3h2DaEsXTdf1hY2IUr7/BV59oVIiuujL23Yl2tLF3h2DKF3E6ZVmcTs+HAAufxeiUfULq01dIqKaH0a7fqOxK/0Km9e5wPdcaMucbNuVqObrKBHxwmpT//AfBQAZXYhG1aWU6R+drrjZ+sLy+hfYuND0z5VYX6nTP9sf073fAOCNuBCNqqPqh42l9DcXS/fDhat00/cpfblf/3wLAHgdF6BRG9OWJdZX3FRRyhurV79qZ/N9AGAWtrNR9Z8zvfrDMzMKwBt1ARplIQsAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKltSwjL+o8qomzHBQBIoURMW9L/2A4zD+HWL7Sa9ZsDkM7WlmxHDGcawlKmX2BXRemm/1yMCQF4g0qJUkqULiK6avpbs+3KzEK4XulSyubUaPRDWgB4I9ZvsW10ZRrBWY4MZxLCUjYrXUoVXSnRtmX6HVRx7OiR+VlcB4A8jh09srjekbYt0U0bsxHDGY20Zn+PsJTouoi2i9i7cxglSoyb7tCsrwPA5W3cdIdKlLhy5zDaLqLrZj8tGjHLqdESUaKKEhFdRHSlxL6FYXRdiZVxe3hW1wEgh5Vxe7DrSuxZGEZXSnSxPk1azXT5yWwXy0SJrut/Ttourtk9FyUillcm98zyOgBc/pZXJh8oEXHN7rmYtN1GX2a9cnSGI8J+VU9XIpq2RNOUOHTdrihdxLeWz79nVtcBIIfHl8/fWbqIQ9ftiqYp0bQluhKbTyXMyGwWy8TmKp6u66KdjghvO7ArSlXi+ImVqz75kUN3zuJaAFz+PvmRQ+997MTKVaUqcduBXTFp+7Z0XfeK5szCDEeE/ZxtFxFt2/XD2KrEHTftjXHTxZcef/GhY0ePDGd1PQAuT8eOHhl+6fEXHxw3Xdxx097oqn5w1bZdf5+wzG7FaMQ2PFBfuoi2REzaLtYmXdxz+OrYszAXj51c3fPYidWHZnk9AC4/x0+ufOaxk6tX7FuYi3sOXx1rk35w1W7DtGjENm2x1nb9PcJx00UTJf7mOw5E23Xx+eOnPnj/vYfeP+trAnB5uP/eQ/d8/tHlD7RdFz//jgPRxLQlTd+WN/UWa/0T/9Pp0a5E05UYNyVGky6uW1qMO27eG+ebiM89tvzQ/fce+tCsrgvA5eH+ew996HOPLT84aiLeefPeuG5pMUaTLsZN35SuKxvTorMM4szv2ZXSnzrRT4+2MZhUsVpF3H1ofzzxwvl4+vRo4VNf/e7v/tP33vqFg9ft+tn7Hnh4POuvAYBLx7GjR+aPP7fye5/66nfvGjUR1+6dj585vD9W19pYm7Qxadtoy9Yt1ma7d+fMnyMspeqr3UU0bcR40sZo3Ma46+IX3n1jvPOWvXG+KfHZ48t3feYbyy/cf++hn5nl1wDApeP+ew+9/8FvLL/w2ePLd51vSrzrlr3x99711lhruxiN25hMumja/t5gPyKc7WgwYjtGhNHvMdpNl5BOokRVVxFrbZT5iLuP7I+fuHF3/Nb/fj6Onzi3+/FTq3/4kduvO/ujV+/842t2z31p52B4fH6ufiyiM1IEuKzU8+NJ92OrbXPw5LnJX/7WqdV3f/Irz+xpui727RjG33rHtXFg32Ksrk0HUG2ZPkgfm/uMbsM9wm17nKFMt8OJrorxpI3S1f2jFSVi6YqF+Ed33hR/cPyF+PJ3XopHnl3dc/y51Q/WdfXBiOkwtaoiqhLVlu+5qhxlAXAp2Lqys1TTv6x3IfrRXVciBlUV77rlyrj74FUxiRIvj6bToesrRTciOPsp0XXbEsL1UWHEZgxLlOii7TfkbruYH9bxvoNXxfsP748nT56L4yfOx8lz43jpfBNnR01sFLCqHPALcKmZDlzWH32PqkSUiCsXh7F3xzCu2T0fBw/siJuv2R1NV2K1aWPc9AtjJm0bTRtbRoLbNxqM2M4R4ffEMGISEV3XRttWMWm7GDddDAdVXL+0I268emcM6zoGgyoGdRV1VUVdR6xnsNqmDwCA7VFiPYZlI2pt1x/T13RdNG2Jc2uTaKZToM10dWi7sWXn9kcwYhtDGLEZw1IiStVFvX46RemnSJumjbquYjDoYrglfv2vfQKNBwEubetP//XToZtRbKZR7LoSbYnpFmpl4wCHfveY7Y1gxDaHMKL/AKqoNlaTVlVE6Up0XRVNHVG3EXVbR11F1HUfvbqqoppOjW69LyiKAJeGrfHaepBuN/379XuEXTfdNq1bf/Jg85D3V7/Pdrkge39ufCPT0WEfuRJVqaKrIqquv31aTadC16dBX704xmIZgEvDq7dB24jhdIRXpqtmtsav/3MXZhS41QXdBHtrECPiFVGMiKim9xQ3e7f5QfSjQfcJAS4V3y9mm8HbOmLs4/dar9luF+U0iFcOmTenPF9/H1URBLh8bA3fxf3v+5viWKSL8X8AABARUVt/AkBa1Za9Rqvp6s4qysaKTQC4XFRVeUXr1tVRpruZTR9ZqGpDRAAuT1U9bV1V9QszS0Q9qDYfW3/1Q+weVwDgUrfesupVrYuoYlBVUS/tHLQRJdpxu2Vrsyqq6aSph9gBuFRtJK/uA1hX/Tae7biNiBJLuwdtvbRrblTXVZxZaWI4qGI4mMYw1qdMixgCcMmpYn0atF8QU1fVRufOrDRR11Us7Zgb1TfuW3yyrqp49MRKzA8GMRxWMawjhoO6HxluGVICwKVg6y2+uq5iOKj7tg2rmB8M4tHnVqKuqrhx3+J36hv2LR6rq4g/efJMzA8jFucGMT9Xx3BQ93On66dAVOsrbQQRgDen9U5VVdk4xGFQ9SGcn6tjcW4QC8Mq/tdTZ6KuIm7Yt/jbw6Wdw399496Ff/Hk6dHc/3j0hbjjL1wV40mJtmumm6N20Zb+XKj1/eCkEIA3p/UVof1IcFBVMTesY2GujsW5YeycH8YfPbocz780jpv3L06Wdg7/zfC+Bx5evf/DB3/xU1997t89+I3l+Im37IndOwYx3Q886qqKpu2irdbPiJJBAN68qro/+X4wnRJdmKtix0IduxcHcf7cJB56eDkGdRXvu23pF+574OHVjar9k/fd8rXPP3b69uv3LMYv33NzvDxq4tyojdGkifGki2Z6ftT6WVERsXFMBgBcTBtH90W1sTp0WMd0OnQYuxcHsXt+GL/2h0/Gs2dH8f4f2/+1X/3it38yYsteo4ev3f2ex0+unvjO6bX5X/ncE/Hxn74h9l0xF+fHdYwmbUya/jTh/mDdEqV71REbJkwBuICqV+1TXU0DWEXEcNBPiS7ODWLH/CBefnkS/+q/PxUnzk3ilqsW1w5eu/O9m++zxW/+9cMf/OLjp//Lt0+tLnQl4mcP74+7/uL+GDddrLV9CDdOEC4X9rwoAHgtmwtk1qdEq1gY1DE/rOML31iO33tkOeoq4m1X71x7321LP/ex//TIf9t87ascO3pkz/HnV7/whW++8FNNV2L/FfPx0z9yRRy6dnfsXRzE4uIwSjU9TVgHAXgTqKrp4e4lYjRq4qVRG48+fy6+/NTLsfzyOIZ1FXcfvOorb79m5133PfDw2Ve89rXe9BMfPvixLz5++t8+dXptfn06tOvK9CVFBAF4U+kfe+8bVW+ZJr15aWH8ntuW/u7Hf+f4p77v617vTY8dPTI8O2r+znfPjP/2s2dHb3vx/GTX6ZV22JZSdeXPeTEAXCAlIuoqYlBVZWnXoLlyx9zK9XsWv33Dvvn/uGdx+O/ve+Dh5rVe+/8BUsK0MAxkzhwAAAAASUVORK5CYII="/>
+        <image id="_Image2" width="450px" height="551px" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAcIAAAInCAYAAAALeVnpAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAgAElEQVR4nOzda8xt/3YX9O/8zcual3W/7dPTVktsOZ6kgAmJLbwgIVGECihGDd5CwgsCRqPx0qCWWAMmxGhUfGM0jRKRQICAFryFUgSCCialHG1TNJJ4yjnnWffbnHPN6/DFWs/z388zx1x7rf20NoXvJ2l6/nv/137W/r8ZGXOO8R2WiOBT9vu9PD094Xg8IkkSfPWrX8V0OrU++UEiIqKfZ881qqoqfOUrX4Ex5qH65LT9RlVV8hf+3J/FX/5jP4Kf+dpfw88uN1gnGcSy8O2j0PrSqIvv/e6/B7/mB/4R/H2/8bfBDaL3/22IiIjuUJal/MUf/7GXGvXN7Q6WsXDKCmsQePi4Rn34lf+gfPnv+iVwHEctkJbWES4WC/mRH/5X8Cd/9E9jEecQACKA61jo+S4AWJZlwVgWjAV8xyCUf/i3/OP4gd/xg/jyl7/MTpGIiH7efOMb35A/+Pt+8KVGAUA/9GAsvNSf5xpVVrV0LOC3/KbfiN/2Q/+eWqMahfB/+Z9/TP6zf/Ofx4//9NdRCzANHXzflwL8/d8R4Xu+awLHd60qK7FNCvyf30rxl/7mDj/zFEteVvi1X/1O/Nbf/fvxD/zAb4Zt2yyIRET0c6aqKvmz/91/iz/y+3/3S42ahQ5+8/fO8Sv/7p41Cl3YHQfPNeonvn6SP/XXn7BMShgLrTXqVSH8o3/g35X/8D/49/GNYwZLgN/wS3r49d/dQ25Z6M37ELEsAWABsG0Lnm2jShL543/lCf/93zwCAL6t38Fv/af/WfyLv+8/YjEkIqKfE1VVyX/yQ/8y/sgf/kOvatQ/+f3fDsv3rLyqUFWC5xoFETku9+igxv/wf8evatQP/u5/A//Y7/rBZiH8qZ/4q/Lb/9Ffh28cMnwpcvA7v3eEILIRl4LetAfAWJUAUgssY8G2gCIvJN3F6HoGaVzhP/0/tvhWXOLL/Q7+xJ//K/j2X/I9LIRERPRu/+OP/kn5t/+F3/6qRs2+3IcJO9a5qFCWgucaJYCsF3ugrhG65lWN2mQ1fsV3jvBf//mftPz+GABg//AP/zDiOJYf+qf+IXztZzf4UuTg3/rVM+QWsDvX8AcRqtqyzoUgL2sUlaAoa5zSQr75rf3lnysg8A1+3Xd18RNPZxwLQbj8Kev7f9M/8wv8n46IiH6x+8Y3viF/4F/6517VKLvrwwp8K85KpFmFj2qUfONbB5zS8lKf6i9q1G/4pUN8PRH8v5vUKv+fv4rnGmVERP7jf+134Md/+usAgN/5vSPs8hq7cwWvF6IUy0qLCllRIStqnIsKcVrKz35zjzivccpqnPIKu3OFXV7jX/1V34bAs60/9D/9r/jJH/0vfwH/0xER0S92ZVnKf/F7//VXNSoxNkzkI85KxOcKH9eov7U4YnfKcS5rJMUXNepYCOxhD7/rV327BQAf1yjzF/7cn8Wf+tN/BvX1easf2Tica7ihD8uxrbyskReCoqpRVjWKopa/9XTAuaiQl4KsEiRFjSSvURgHs+8c4p/4FR9QVoJ/5/f8HhRp/Av2H5CIiH5x+4s//mP4bz6qUdHQg9fr4pzXVppX+LhGrXeJ7A/nS62q6pcalZaCcBDhXIjlRc6rGrX61t8S85f+6H+ORZxjGjr49d/dQ5zXsHwXnu9ZRVmjrAS1COpaUFWQby1PyIoKtQC1CMpKkFeCyjIIBwHSvLJ+zVeH+I5hB1/fxfjJP/Nf/UL/dyQiol+k/vIf+5GXGvUDXxkgGHaRVbV1zit8XKOOp0zWuxSVAJXgVY0aDEPUsKxzUSLNKzzXqL+5OcmP/N4fhPmZr/011AJ837cFSKsatWOj1wtQ1jUqeS6CgIgly+0JaV5CcPkh1fUHwVgYjUMUpVjnokJWAr/6u/qoauBP/OE/KPv9/tPxNURERB/Z7XbyXKO+/9sC+OMeCoFVFNWrGpWkpSw3yWXn/fp/zzVqOAzgdRyrrGvk19d7WQl833d2ZXvK8VM/+b/D/OxyAwD4FdMOStjoDbooa1hVdekC6/ryhTaHBHFavHxBefn/FmbjCIBllTVQFIKsqPG9H7rIykr+t7/+f+Hp6en/x/90RET0t4PFYoHnGvVr/94ZamNbRSEoa+C5RmV5JYv1CfWbzwqAQddHFHSsyxPNy+eKQnDOK/k2q0Ytgq9/aw1nnWQQAeaRC2fewzGtrFoENS5pMgBwjDPZHzP1i84nXTiObdW4dIdVLaikRuBCDkkBFzWOx+PP138nIiL629TxeMQ6yRB4Dr40Ca1DVaOqr08qAZSVyNPqhEpJSIsCF8OBD+BSyz6qUbJc7NF1BSLAJslg9udLHZ18xxiwLOv5faBcy2uSFbLaJeqXnIxCdDqXpXmpcekgRZDnlZy2RwiAw7lGHHNghoiIHnM6nZDXFnzXtoxnf1EEa0FdiSxWJxTV214Q6Hg2pqMQACy5Pr98rlHrzQlxnMF1zUuNMoCg5zswnmPVl0VEPH8wz0tZrk/qFxz2fHRD73mB//KDcKm2i2/tUFY1AEENQV03vygREdEtk8kEUcfBpZZcCtm1RslqkyDLy8ZnHNtgPuleArGvnmvU/pjK4Xi+FNLL76CGwHR9F45tLBGBiAWpLx+oqlqe1ifUyphLN/Qw7PuN1BgRyHJxQJ7XuPx5Pwf/JYiI6O9IX/3qVwELlgjwcY3a7lIkad74942x8GHahW2sRn1K0kK2u/TymFSsVzXK+N710SYsCAQCC3UNWaxOqKpmJfM7DibDUP3S602CNCshENRfhICj0+l83n8FIiL6O9bHedXPNep4ymR/PDf+XQvAfBLBdZq3CLO8lNUm/miq9IsaFfkODJq1TpbrE/KiavyG6xjMxxGsj05dPNsfz3JKmgM1FoBf9st+2c2/LBERkeqjGnVOc9ls9ZmT6TiE7zXvDZZlLYt1DO3koO/aCFzHMm9/Y7ONkZ6Lxgfsa8tplJYzTnLZ7VP1y/UCD91ul+HbRET02Yq8lPVS30AY9X1EgdeoM3Utl1d8yju+Tti5vn8EXhXC4yGV06mt5ezCsZst5zkrZbXVp0qjjgNPaVOJiIjuVYvI9mmndnW90MOgp8+sLNYxirI5rOl5DoazPnC92PRSCM9JJvuWlnM2jtDxmrcFi7KSxVr/TDQI4bu8R0hERJ9PBHJIC9TKmoTfcTAe6TMrq22Mc8tU6WTefzVVagCgrGrZLw/qHzYeBAgDt1HQqlrkaRVfItbeCKMOeqNu61+MiIjoU0REDmmOSnm06bo2ZuMIFpozK7vDWT5OQntmLAuzWR/2m6ebTlWLHM+F3nJGHfS7HaXlvCwyluoio4PRpAsoX46IiOhef+Nv/A11Yd62DabjLmDBelsjT3EuO2WqFBYwm3bhKk8qzSHN1V3BIHAxHgbad5PlJkGmTJU6jo3Z7HXLSURE9Dm++c1vNn7NWBbm016jqwOANCtl3ZaENozgd5pPN+taxGgtp+c61yDtZle32adIlKlSc2OqdLlccrWeiIjebTrtwVO6uryoZLk+KRuBwKDnoxs1p0pFIPs0R2N9wrEN5tOu2tUd40wOp5bw7eklfPvtrxdVLT/90z/d9nciIiK6y2jche8rMytVfblA8UASGgRyPBeoanldCI3RXyQCQHIuZL3TdwWn4xAdZZGxquUy7cOsUSIieodoECJS1iRqEXlaxygfTELbr48v7x+/KIQWMJn11ReJeVHJcqOvSbQvMtatQzhERET38hzTtokgy038cBLacZ9IevqisXsphINJHx2l5SyrWp5WJzVA+9Yi426xV0deiYiI7uXYBl3fBbSZlV2C9NzcFbydhJbJ/s1AjQGAwLMRdJWWs74ePVQKWtC+yCjb9RG5MlBDRER0ryAI0A9cdVfwcDzLMW5eoPhUEtpGCYExHcdGqLzfw7XlVONpbi0y7hMksT5QQ0REdK9f/st/OYwyuJmkuWxb8q1vJaEtV8eXe7sfM73AVf+w9TbBOdNbzvkkUlvOU5zJ4dD8clwrJCKiRwVB0JxZuZ5U0txKQlusTmoSmufYMFC6uv0hlZPS1RkL+DDVW870XMi6Jav0K1/5ivrrRERE9yrLShbLozqE2f9UEprydNOxLfQCt7lHGCeZ7JSuDri0nNoiY1FUsmwJ3w49B1/60pfYEhIR0Wer61pWi4O6jhf6zs0kNG2q1HZs9P3L+8dXhTA7F+qLRACYDAMELYuMT+uTWqE7jkHYUd8/EhER3Ut2iz1KpaA9z6zgwSS00YfhS3DMSyEsi0rWy4P6InHQ7aAXNVvO50XGSllk9HzveeSViIjos53OhbqJYNsGHybRQ0loFixM5n04Hz3dNMAXRw+1K75R4GI0aLaccmOR0XFtDOeDy88kIiL6TElWSqa837OMhQ+TSA/fvpGENplE6LwJ3zaCa95a2SxoHc/G9LIr+MAio8F03lenSomIiO71rW99SxLluO7zrmBbEtqiZap0OAgRhs2nm+aYFmpGm+MYzCd6+PbhlOmLjJaF2aynhm8TERE94md+5mfUXx+PIvjK/MmtJLRu1FGT0ACIyZVO8HJSqafH06SFbFoWGafjCJ6ynJ8kCbPWiIjoIdoQ5qAfoKvNrFx3BbUkNL/jYKInoeGYFs31CQsW5pMeHKf53DW7scg4GoYItfBtEfna176mfoaIiOheYdTBYBA2dwWfZ1a0JDTHtE6VJnkpWVk1C+HlRaLScpa1LNaxevSw17bICMghKZCmegdJRER0D893MZr01N9bbxOkbUloLeHb6eksaX55IvqqEA6Gkfoisa5FntZ6yxn6busi4+lcoOQtQiIiegfbWBjOh+pJpf3xLKekObNyKwktOxeyXx+++Hef/0fYC9AbNHPdRCCL9UkN3+64NmZjfar0uDmpbSoREdG9LMtC33dbTirlsj2c1c/dSkJbLw/4+PGmAQDXNuiP1ZZTVtsY51zZFbQN5i2LjKfjWeJD0vgMERHRvWzbxiDQi+A5K2W11evMrSS0xfLQ2Jk3trHQ893LlMwbu0OKOFXiaSy0LzKmuew2p5t/OSIiok/56le/qj7aLMpKlmu9zrQloT2Hb1dV80mlGYSe+tz1FGeyP2rxNDcWGfNSVi1fjoiI6BHT6bSlqzs+lIQGQJbrGHmhD9QY7ejh+VzIetfSco7C1kXGxUoP3/7yl7+s/llERET3EhFZrY4ola7u00lo+tPNfuA11ycuJ5VO0PYkhj0f3VDZFXxZZGx+Odc2+J7v+Z7WvxgREdEdZLM6IVMi1y4zK48nofV8F7axXp9henmRqHR13dDDsN+Mp3leZCy08G1joR946pcjIiK613F7QpooB+ONhQ/TLmxloCa5kYQ2mPVf3j++FEKpRVaLg/oi0e84mAz1eJr1NsFZW2S0DXqBq75/JCIiute5qCTe66/r5pMIbksS2rItfHsUwf9oZ965/n/ZLfcolJbTdQzm4+jmIuPbsRnLWBh+GMJYSwsAfumctZCIiB7nuwOJlWYLAKbjEL6Sb30zCa3ro9t/vTNvACDOSmSpspl/bTkfXWScTHtwlS9HRER0r1ogB6U2AcBwECDS8q1vJKEFvovRKGr8uknzUs7K+z3LsjCf6PE0txYZx6MIvvLliIiIHqGUJgDtJ5UuSWixmoTmeTZmky6gTJWa9pYzQsdT4mnKqrXl7Pd8dLv6vSf1hxAREbXQ7gr6vouJ0tUBwCUJTZ9ZaZsqTfNKGusTADAehggDJZ6mFnlaxepUaRh4GA3UgRpRvhcREdFDXNfB9HKBQklCO8ujSWh5VUucKfcIe10fPaWre46naV1kbAnfPp0LKI9qiYiI7mbbBtMPfXVm5RTnsjs2Z1ZuJaEVeSmn65L9q0IYhJ76IhGALDcJMuWBrXuj5TwXlfr+kYiI6F6WBQw/DPV866x8OAmtKmvZPu1eHr2+FELXczCe6i3nZp8i0eJpbhw9PMdZ68grERHRvbq+q24iPCehaQ8dbyWhrRYH1B893TTA5Rnq6MNQ7eqOcSaHkx6+/aFlkTHPStmvDo3PEBERPaLbceEpnWBV1fK0Pqmv3tqS0ADIanVE8SZ821i4ho5qLee5kPVOj6eZjkN01EXGSlbLgxq+TUREdC/HAL6yvSAislifUFXNOuN7t5LQYpwz5elmP/TUjLa8qFrjaUZ9v3WRcbk8vmo5iYiIPodjqxGdslyfkCsH413n+WB8SxJa3Hy6CQDGVTrBL04qNT/QC732RcbVEUXZ/HLNn0BERPS47TZWTyrZn0hC27WEb6tnmC4nlY5q+HbQcTAe6buCq22snscwlgXXUT5BRET0gOMhleOpfU3i0SS0qOPAc4z1thBeXyQ2uzrPtTEbR7DURcYUSaLdewIGoad+hoiI6F7nJJP9Vn9dN2tNQruEb2uiQQj/ul/4qhBu1yf1RaJtLMwnUesi4/6oPHe18HL0UP0WREREdyirWvZLfRNhPAhuJKGd1CS0IOygN+q+/PNLIYz3scRKy2ks4MNUbznTrJRVyyLjYNKH9v6RiIjoXlUtcjwX6iZCL+qg3+20TpXqSWgOxtPX4dsGALKykuONltNT4mny6yKjpj8IEejh20RERHcRXM4wabuCge9iPAzUjy03CTJlqtSxDWazfmNn3imqWuKzngAzHgYIfKXlrGpZtCwyRmEH/WHIIkhERO9y2XtvFhrPdTAZR6hFGrVmeysJbdZTX/GZQ1K0nlTqRc2WsxaRp3WMUltk7LgYj7uNXyciInqU1mw5tsF8qudbH+NM9koSGnAN33aaTzfLqhYjShm8nFRqtpwCyHITI9fCt93L0UNtkbGseY+QiIjexxgLs1lfD9/+VBKaFr5di+xT5QxTx3MwHUeAFr69S5Aqj1GNuVyz11rOrKhE2bEnIiK6nwVMZn31pFJeVLJ4OAmtfhnCeVUIHcdWXyQCwOGUyTFWdgVxXWRUwreL6vKDiIiI3mMw6aOjzKyUVS1Pn5GEtlvsUV2fvb4UQmPajx4maSGblniatkXGsqhYBImI6N0Cz1Y3ES5JaKeXgvbqM+1JaNiuj8g/qk8GuHR1w/kAjvIiMcvL1vDttkXGunp99JCIiOhzdBwboXLpCM8zK2VzV9BzTGsS2n6fSPImfNsAQOQ78LSW8xpPo06V3lhkXC0PqPhikIiI3sFYQC9w1d9bbxOclePv9o2D8ac4k/2h+XTThB0HHaUTrGuRp7XecoY3FhnX6xNyXqYnIqJ3uh5saHZ1h1Q9qXQrCe2cFbJpCY4xWsspgks8jdZyujZm41D9cttdgiTVB2qIiIgeoT3ajJNMdkpXB7QnoRVF1fp0M/Sc5voEAKy3J2RKV+fYBh8mkb7IeMrkoGSVAuAZJiIierfsXMim5ZrE5EYS2tP6BFGebnYcg7DjNM4wYb9PJFZOKhkL+DCJWhcZNy3h2z3fhVGW7ImIiO5VFpWslwdoITCDbudmElqlJKF5voeuf3n/+KoQxqez+iLxeVewbZGxbao09Gx0lM8QERHdS0Rk+7RDrXR1UeA+nITmuDaG8wFwffT6UgjzNJfdRr8mMRmF8JV4mluLjGEvQKCPvBIREd1FADmcC3UToePZmI70mZW2JDTbGEznr3fmDXDJW9su92pBG/Z8dEMtnuZy9FCbKvUDD/1x7xN/PSIiotuOaaEeeXAcg/lED99uTUKzLMxmvcbOvFOLyCHN1ReJUehh2Ffiaa4tZ6FOlToYT3sA3wsSEdE7lBWklmYnaIyFyfiyK/i2dN1KQpuOI3jKk0qzTwr11IXfcTAZ6vE0622CVFtktA1mLfeeiIiIHqH0WrBgYT7pqSeVsrx9ZmU0DBFq4dsiYqq6+ZNcp/2k0v54lpMyVWpZFubTnjpVWmtjPkRERA+aTCL1pNIlCe2kFpteWxIaIIdEOcNkG4P5VO/q4iSX7UHfFZxN9EXGshbJGTRDRETvNBhGCENlTeJmEprTmoR2Ohco6/p1IfziRaIWT1PKaqvvCo5bFhlrETko3SMREdEjwl6A3iBoSUJrm1mxMWu5r3vcnl4Cu18VwvG0p75ILG6Eb7ctMkotckwL1DxBQURE7+DapnUTYbWNcVYeO95KQjsdzxLvv2jsXgphb9xFoKxJVNc1Ca2gtS0yApDdco9Sm8IhIiK6k20s9HxX3UTYHc4Sp827tzeT0JSdeQMAvmsj6ofqSaXF6oSyaractxYZd5sTMiV8m4iI6F6WBQxCTx3cPMW57I/NmZWbSWh5Kat1MzjGeI5B1FFTsWW1SZAp8TSu3b7IeDye5aR8OSIioke4NmCUOnM+F7LetYRv30hCW6xOEOXppun5HqCdVNqnSM5Ky3k9emgrU6VJmsu25csRERE9QjvYUBSVLNcnaEMrt5LQFqsT1HVB28BoLefxlMmhteWM4CpTpVleyqplkdGx1V8mIiK6W1XVslge1JmV7qeS0JSnm7ax0A+85h5hmuayaenqpuMQvjJVWpbtLafv2nAM49aIiOjzSS2yXhxQKTMrvnc7Ce3ckoTWD1xYFl7fIyxaXiQCwKjvI9Liaa6LjNp5DM8xL/eeiIiIPpPslnvkypqE6xjMJ9FjSWjGwvDD8OX940shrMpKVouD2tX1Qg+DntJyCmSxPqFUFhndjvtcBNkNEhHRZ4uzUt1EMMbCh2n34SS0ybQH96Onmwa4FLTtYq+2nEHHwXiktpxyWWRUnrs6BqP5ABaLIBERvUOal3JW3u9ZloX5pAtH2RW8mYQ2iuC/ebrpAJDjuUCptZzXeBqtoG0PKfRFRgvT+QBG+XJERET3qmpIXuph1dNxhI5nN84w3UpC6/d8dLvNp5vmeC5QKJ2gfY2n0VrOyyJj1viMBWA67amLjERERI9QGkEAwGgQIgya+da3ktDCwMNooD/dNNrCvLGuu4JaPE1Wynqnt5yTUQRfCd8WnmEiIqKfA72uj746syKXmZW2JLSxnoR2OitnmABgOumqJ5Xy6yKjGr7d9xFp4dsCUV4jEhERPcQPPIxGkfZbstwkyJRi49xIQjsXlZyLqlkIx6NIPalUVZejh1qOdhR6GPab5zEAyCHNwQMURET0Hq7nYDLrAQ8mobVNlZ6TTOLrfuGrQtjrB+qLxFpEntYxykpZmO+0LzKeMv39IxER0b2MBYw+DPV86ziT/UmfWWlLQsuzUvbLwxd//vP/8KMOBkrL+RxPk2vh247BfKwvMsb7WLKCRZCIiD6fBVxi0LSZlXMh612qfq49Ca2S1fL1zrwBAMe2MJj2n3/mK5tdgvSsxNPcaDmTOJPjluHbRET0Pv3QU4885EUly5Z861tJaMvlEfWbJ5XGWBb6vqu2nIdTJsdYiacBWhcZs6yQbUtMGxER0b1cG3CVOlO9nFRqfuZWEtpyfURRKpsSl6OH2kmlQrZ7veWcXRcZ3/56UVayXB7VmDYiIqJH2MrBhroWeVodH01Cw3obq+HbxrJgtJbz1kml8SBoXWRcLI/qIqOtLmkQERE9RFaro3pSyXNMaxLa7nCWWAvftoBBqJxhKstKliu9q+tHHfS72q7g5eihtsjo2AYu7xESEdE7bdcnnLPmmoR9PRjfnoSmhG9bQM93YRvr9Rmm5xeJlbIsGPoOxsNA+26y2iTqeYzL0UNeoCAioveJ97HEJ+VgvAV8mOozK7eS0AaT/sv7x5dCKHI5eqi9SPSu4dvQpkpbFxkNer77cu+JiIjoc2Rl1bqJMB9HDyeh9Qchgo925l8K4WF1RKa0nM41fLttkfGgLTJaFobzgTrySkREdK+iqiVWVvgAYDwMPiMJrYP+MHz1GQMASV5KGre0nJNIDd9ObiwyjiZdeMqXIyIiupcI5JAUrSeVekq+9aeS0MbjbuPXzbmoJNVSsa3LrqB2UunWIuNwECJUvhwREdEj8grQjhddTio1Z1ZuJqG5NmaTnpqEZk7K+z3gelKpo8TTVLU8tSwydqMO+i3h2+oPISIiaqHVmY7nYNo2s9KShGbM5Zq9NlWaFZWoG36DfoBuqMfTLFYndarU911MWs5jtB1XJCIiupfj2JjN+p+XhKaEbxdVLUftHmEUdtSTSi8tZ9ncFXRvTJXGWQkeoCAiovcwxmA67+v51mkhmweT0MqikuP1ieirQtjxXfVFIgCstwlSJZ7GNhY+TKLWljNV9guJiIjuZQEYzgdwlJmVLG+fWWlLQqurWrZPu5dHry+F0HEdTGZ99UXi/niWkxJPY66LjNpUaZ7mclIKJxER0SMi31E3EcrysiahTpXeSEJbLQ+oPtqZN8Dz0cOB2tXFSS7bgxJPg0vLqS0yFkUl2+X+5l+MiIjoU8KOg47TrDN1LfK01mdWbiWhrdcn5G+aNANc89aUH3TOSllt9XiayY1FxtViD9E2GYmIiO5kGyBUjuuKQBbrE0plZuVWEtp2nyBJlaeb/cBVM9qKspLlOlZbzkG3oy4yilyzSpUvR0RE9Ii2gw3r7QmZ8urtZhLaKZODFr4NwHhKJ1jVl6OH2kmlKHDVRUYAslyfkBfNL8egNSIi+gzNmZV9op5UMjeS0NJzIZuW8O2e7zbXJ0REliu95ex4NqaXo4cti4zN5XzLsuDxDBMREb1TfDrL/tBck3jeFXw0CS30bHRc23pbCGW9PiFTVh4c22A+6bYvMmrh2wAGgatOohIREd0rT3PZbU7q701G4cNJaGEvQHB9//iqEO63sf4i0ViXNYmWRcZtyyJj19ffPxIREd2rqkW2y71a0IY9//EktMBDf9x7+eeXQpgcUzm2tpwRXCWeJsvL1pazP+7BUz5DRER0r1pEDmmubiJEoYdh328Obn4iCW087QEfPak0AJBXtRzWR/VLTMchfGV89bLIqE+VdnsBQj18m4iI6F5ySAr1rmCn42AyDNUPtSah2QbzWTOmzSmrWtouUAz7PqJAbznbFhkD38NwrIZvExER3e0yrpJkL4MAACAASURBVKJ0dY6NybgLgVhvH5e2JaFZloX5tKdOlTqHtGg9qTTo+dbbWve8yFioi4wOptMuoEyVEhERPaKWy1rEx2xjMBp1IYD1thm7mYQ20ZPQylrEaLuCQcfFZKS3nKttjLNyyNe2DeZTfaq0qnmPkIiI3seyLMxmPfWk0q0ktHFLElotIockb+4Ruq6N6UTv6naHs8Sptit4I3y7rHmPkIiI3m087cFTZlaKGzMrrUlotcgxLVCLvC6EbS8SAeAU57JT4mluLTKW9WXah4iI6D164y4CZU2iqkWePiMJbbfco7w+Wn0phJaxMJ339XiarJR1SzxN2yJjVVZyZBEkIqJ38l0bUT9U860X6xNK5fr7rSS03eaE7KP69FIIh7MBXKXlzItKli33nm4tMm4Xe3XklYiI6F6eYxB1HO23ZLVJkCkzK7eS0I7Hs5zePN00ABB1HHSUNYmquhw91Apat2WREYBslgeUvExPRETvYCyg53uAelIpRaKs/t1OQstlu2uGwJjAc+Ar7/cuLWeMslLiaW4sMm42J5xb9hKJiIju5TpQs6rbTip9Kglt1ZKEZtpazuU6Rq6Me7qOwXwcqV9uf0jlFDfDt4mIiB5lKZ1gmuayUbo64BNJaKsTRBmo8V0bBtpJpa1+Usm+tpzaVGmc5LJTskoBwFNrLRER0f2KvJTVWr9AMfpEElqtvOPzHIOudo/weDzLMW5fk9CuSVwWGfUK3fVdGJ5hIiKid6jKWlaLg9rV9UIPg54Svi24vOLTwrc9B13fBYDX9wjTRH+RCACzcYSO13yXWJS1LNcxtLHSwLPV949ERET3EoFsFztUyppE0HEwvpmEpoRvOwajD8OXR68vhbDICtms9AsU40GAMGjG09xaZPSjDkI+EyUioveR47lQNxFcx2A2jtR3iW1JaMayMJ0PYD56ummA69HDxV5tOftRB/2uEk8jl6OH2iKj13ExmPYBhm8TEdE7HM8FCqXO2LZpnVk5Je1JaNNpr5GE5oiIHM8Faq3l9B2Mh3o8zXKTIFOmSh3HxnTeUxcZiYiI7lXWkLpu1hnLsjCfdGHbVuNCUpqVsm4L3x5F8JXwbXNIC/WuoOfZmF3uCjanSm8sMs5nPRjDy/RERPQ+ZcvBhtmkq55UupWENuj76Grh2wIxWst5M54mzuRwUnYFLWA+6cFxml+uFp5hIiKi9xuPIvWk0q0ktCj0MOwHahLaIVXOMJkbV3yTcyHrnb4rOB1F6Gjh27VIwbQ1IiJ6p14/QLfbXJOoReSpLQnNa09CO2WX94+vCqFl6S8SgWvL2RJPM+z7iJTwbZHLGSa2g0RE9B5+1MFgFGm/JavNjSS0iZ6EFu9jyYrLE9FXhXA47qovEsuqlqfVCcpQ6Y1FxssQjvb+kYiI6F6ObbVuIqx3CZKzsit4IwktiTM5fhQC81IIu8MIkdZy1pc1Ca2g3VhklP3qiEJpU4mIiO5lLAt931VnVg6nTI5x8+7trSS0LCtk+yamzQBAxzHoDiP1ReJyEyNX4mk8125dZDzsEpyVmDYiIqJ7WQAGoacWwSQtZLvXZ1bak9AqWS6PjZ1549rmOW+tYb1NkGZ6yzmfRPoiY5zJYa/vcBAREd3LdaDeFbx1UqktCa2uRRbLo5qEZvrBJXT07W/sj2c5Jc2W01jAh2lL+Pa5kO1GTwYnIiJ6hHawoSwrWa6aXR3weUlojm1gtJYzTnLZ3Wg5tUXG4sYio9NY0iAiInpMXYssl0d1ZiW8kYS22iTItPBtY6EfuGikYmdZIeuWrm4yDNoXGVcxjCV421x2HBvCzFEiInoHEZH18oBCiZt5nlmBUmu21yQ0+03PZ4xBz3dhLOv1GaayuL5IVL7EoNtBT4mnqUVksY7V8xiubaEX6O8fiYiI7nVYHZEp0Z6ObfBhErUmoe2VJDTLsjCcD17eP74Uwrq6HD3UXiRGgYvRoNlyyvNUqRa+7Tro+R7AbpCIiN4hyUtJtYPxFvBhEqlJaOmNJLTRpAvvo6ebBrgUtO1ij1JpOTuejellV7AZvr1LkGqLjLbB6MNA3eYnIiK617moJM2V9G3rsivYloS2aEtCG4QI3zzdNADkdC5QZHrL2Ra+3brIaFmYzPqwlfBtIiKie9VyqU+ayTCCr+Rb30pC60Yd9JXwbRNnpbowb4yF+bSr7nAkaSGblqnS6aQLT/lyREREj1AGPQEAg16AbtTMt76VhOb7LiZ6VilMqvyk53ga11HiafKyNXx7NAgRBEr4Npi7TURE7xeFHQwHza5ObiShuY5pnSpNslLUDb/JuKueVCrLWhbrWK1qvW4HfS18G+AZJiIiereO72I87qq/dysJrS18OysqSfKyeY9wOAjVk0p1LfK0bgnf9h2M9XtPckwL9VAiERHRvRzXwWTWV4cw25LQrGsSmjZVmqe5nK6F81UhjLq++iJRBLJYxyhuhG9DbzmRK5OoRERE9zIWMPowULu6OMlle9CPPMxvJKFtl/sv/vzn/9EJPIwmesu52sY4K+8Sby0yJsdUUmW/kIiI6BE931U3EbK8lNVWP/JwKwlttdhDPnpUaYDLM9ThbAAoXd3ucJY4bY6vmhuLjOc0l8P6ePMvRkRE9Cm9wFWPPBRl1Tqz0paEJnLNKn3zdNMY6xI6aqknlXLZHZVtftxYZMxLWS9ZBImI6H0cG+goneDzmkStDKC0JaEBkOX6hFyZ3jT94BI6+vY3zlkh611LyzkKWxcZtaOHREREj3JM8ynl5aTSEaUys/LpJLTm003LsmDUlrO4tJyaYc9Ht2WqdLE6oqqV5Xyu1xMR0fvJen1STyp9MglNC98GMAjc5vrE5aTS8dWLxGfd0MOw39wVxHWRsVCGY2xjwWsceyIiInrMfhsjSZWD8dddwbYktG1LElrXv7x/fFUIn18kald8/Y6Dib4riPU2wVlpOY0FDEJeoCAiovdJjqkcD82CdplZiVqS0KrWJLTeuAvv+pkvCqFA1suj+iLRdQzm4+ixRUZjoRd46vtHIiKie+VV3bqJMB2H8L22JLSTOlXa7QWI+uHrM0wAcNgccVZazlvxNLcWGYezARzlM0RERPcqq7r1AsWw7yNS8q1vJ6F5GI5fh28b4HLvKTm2tZxddYfjnLUvMo4mXXSUL0dERHQvEcghLVpPKg20fOubSWgOptMu8OZ1ncnKSmIlqBQApuMIHU+Jp7kRvt3vBYi66kANERHR3fIKqJUqGHRcTEb6zEpbEpptG8yn+lSpOSqpMQAwGoYIAyWephZ5Wp3ULxcGHoYt4dvqDyEiImqhdYKua2M6aXZ1QHsS2s3w7bLWzzD1uj76XT2eZrE6qVOlHc/BtCV8m5GjRET0XrZtMJ/11ZmVU/J4ElpZixzSvLlHGPhe60ml5SZBplQ150bLmealKHWTiIjobpZlYTrvq11dmpWybgvfbklCq8pajtcB0VeF0PP0F4kAsNmnSLRdQWNh3jJVmpd16/tHIiKiew3nA7jKmkReVLJsWZO4lYS2XexebuW+FELbsTGZ99Wu7hhncmiJp2lbZCyyonXklYiI6F5Rx1E3EarqsiuoHX+/lYS2WR5QfjRQY4DLi8TRh4Hecp4LWe/0eJr2RcZKtos9J2SIiOhdAs+Br7zfExFZrGOUVbPS+F57Etpmc2okoRngcvTQcfWWc9ESTzO6sci4WhxQ88UgERG9g20u3aBClusYuTKz4joG80lLEtohlVPcfLppur4LV+kEy6qWxeqkjq/2Qq91kXG5OqLkmCgREb2TawPQZla2+kkl21j4MGlPQtspWaUAYLSW8/nooRpP03EwbllkXG9PyDJ9h4OIiOhBzZmV41mOcfuahKOGb5ey2upPN6OOcoYJ1yu+2kklzzGYjSNY6iJjKrEWvg3As9WfT0REdLc0yWW70wva7BNJaNrQSuDZCDzbahTCzeaEs9LV2TfWJE5xLvuW8O1+6KnPaomIiO5VZIVsVvoFivEguJ2Epjzd9MMOwuux3FeF8LhP9BeJ13gaLXw7zUpZ7/RFxq7vqO8fiYiI7lXVctlEUIZW+lGnPQltrSeheR0Hg1kfuD7dfCmE5/gs+5aCNhtH8JR3ic+LjJruMELHaX6GiIjoXiIix3OhbiIEvoPxMFA/ttwkyHIlCc2xMX2zM28AoKhq2be0nJNhgMBXWs4bi4xh1EF3GLEIEhHRe8ghLdTBTc+zMWvJt97eSkKb9WDM6yeVTlVfqq3acnY76EXNlrMWkae2RcaOi9Gkd/uvRkRE9AlFBYg0O0HHNpiMu4AF623pOsaZ7JUkNFjAfNKDozypNPskV3cFw8DDaNBsOQWQ5aZtkdHGdNrjcAwREb2blstiLAvzae/xJLRRhI4Wvl2LGO2u4OWkUghoi4y7BOm5GaRtbIP5tKdOlVY109aIiOh9LAuYTnvqSaVbSWjDvo9ICd8WaTnD5Dg25tOeGr59OGVyjPVdwfkkUhcZi6oWBs0QEdF7Dcdd+MrMSlnV8tSShNZtTUK7vBasanldCL94kdgsgklayGbfFr4doaOEb1e1yCHhBQoiInqf7jBC1G0WtE8loU1aktAOqyOK65zLSyG0LAuTeV99kZjlpSxbWs62Rca6qi9DOHwqSkRE79BxTNsmwmVmpVR2BW8koR12iaQfxbS9FMLBtIdOR2k5r/E0Wjm7tci4XezVCk1ERHQv1zbo+q76e+ttglQ5/n4rCS2OMznsX+/MGwAIPRt+pLecT2u95QxvLDJuVicUSkwbERHRvSwL6AcuoHR1++NZTlq+9Y0ktPO5kM2mGQJjfNdGoLzfE4Es1jEKreV02xcZd7sEaaLscBARET3As6EObsZJLruWmZV5SxJacU1C055TmvaWM0aWN1tOxzb4MInUL3c8ZXI46l+OiIjoEdpOepaVsla6OuB2EtrT6gR1XdCxYaB1dXv9pJKxgA+TqHWRcdNyHsPlGSYiInqnsqhkuTqoXd3gRhLaYh2jUrbzXdtCL1DuEZ7iTPZKV/d89LBtkXG51otg2HFgGybNEBHR56urWlaLg3pSKfRdNQkNgKxaktAc10bv8kT09T3C7FzItq3lHIXwlXiasqplsTqpWaW+axAq7x+JiIjuJYBsF3uUZbOgdTwbs5YktPUuQdKShDb6MHx5xfdSCMu8lPVSbzmHPR9dJZ7m1iJjJ/AQdfT3j0RERHeS07lQNxEc22A+6T6WhGZZmM76sD/amTfA5RnqdrFXW85u6GHYV+Jpbiwyuq6D4WwAKBWaiIjoXnFWqnXGXHcF7QeT0CaTLrw3TzcdEcgxLVApLaffcTAZ6vE0rYuMtsH0Qx+W8uWIiIjuVdWQvGzWmeeZFccx1tv+Lcur1iS00SBEGDSfbprjOUepdILudVdQG19tX2S0MGs5j0FERPSItoMNk3FXPal0SULTdwV73Q76Wvg2IEZrOW1zee6qxtMkuWwP58ZnAGA26cJrWc7X/zpERET3Gw5C9aTSrSS0wHcw1p9uyjEtmusTl66uq55UOmelrLbJ218GAIxHobrIWItIzjNMRET0TlHXR78f/JwloSV5ibysmoVwOumqJ5WKW+HbLYuMIpB9Uqg3ooiIiO7lBR5Gk676e6ttjLOahGa1JqElx1TSa5f2qhAORxEC5UViVUtrPE0UtCwyCuSUFajqZoUmIiK6l20sjFo2EXaHs8Rpc7XCWJeBGm1m5Zzmclgfv/h3n/9H2A/QVVvOy65gqcTTdDwb05G+yHjYHNWRVyIionsZy0I/cNVNhFOSy+7YnFmxcJ1Z0ZLQ8lLWy+OrXzPA5YBhf9zTvoMsNwkyLZ7mxiLj8ZBKwvBtIiJ6p37gwih15pwVsm6ZWZmMQgTKVGlV1bJcHhtJaMYxL0cPGx/a7lMkZ6XlNBY+tC4y5rLf6jscRERE9/IcqHcFi6KSRUu+9a0ktKfVUX1dZ/qhq56yP54yOZyadwUvi4wRXGWqNMtLWa/1rFIiIqJHGGWPvapqWayOEGVNIgpcNQkN1yS0Qnm6aRsLRms5LyeV9JZzOg7hK1OlZVnLctVsOS8/SP2jiIiI7iYislwe1ZkV33MwHUXq59bbBGft6aYFDEIPztvfyPNSli1d3ajvI1KmSi/h2zEsCPDm3aTnsAoSEdG7yXp5RF401yRcx2A+uZ2EZr/p+SzLQi/wYCzr9RmmqtRfJAJAL/Qw0OJpBLJYn9TzGI6x0PM9gOHbRET0Dof1Eee0Ge1pGwsf2pLQ0vYktOF8AMe8OcMktchqcVBfJAYdB+ORHr692sbIlOgY27HRC1y1QhMREd3rXFTqJsLH4duNz+SlrDb6K77huIvOR083nwuhbJd7FErL6TnmEr79yCKjsTD6MFBHXomIiO6Vl5XEyqUjAJiOI3S85q7gzSS0XoDum6ebBgBOWYG8peWcT/WW8xS3LzJOZn04Li/TExHR56sFclCaLQAYDQKEQTPf+iUJTZkqDQMPQyV82yR5KVnRfBxqWZciqO1wpFkp65ap0vG4i44Svk1ERPQI5SElAKAX+fpJJZHLzIqahOZg2hK+bZKWlnM2ifR4mqKSZcu9p0E/QKSFb4NnmIiI6DFa4Qh8r21mRVbbRJ1ZcWyD+VRPQkvzUtTdhvEwUk8qVdXl6KHScSIKPQyVrFIA0lbViYiI7uV5DqbTLtCShKaHb7e/4svLWuKsbJ5h6vcC9LrNrq4Wkad1jLJqVkG/42DSssh4PBdq4SQiIrqX7diYzPt6vnWcyf7BJLQiK+R0XbJ/VQiDsKO+SJRrPE2uxNO4z1OlyppEmpeiBXYTERHdy7KA0XygnlRKz4Wsd/qRh+kohK+Eb5dlJdvF/uXR60shdDsuxi0t52aXID03n2/a1/BtreU8x2dJeJqeiIjeqee7cJRoz7yoZLHRw7dHfR9RS/j2anFA/dFAjQGuRw/nA7XlPJwyOcbN1YqXRUalQmfnQvarY+MzREREj+j6LlylzlzCt09QgtDQvZGEtlwdUb55Umksy0LPd2GUH5SkhWz2ess5a1lkLMtK1suDGtNGRER0L8cAvrK9UF93BStlACXoOJi0JKGttydkmTJQ0w9c9a5glpeyamk5x62LjLUslkd1kZGIiOgRjq1GdMpqfVJPKt1OQkslTvSnm0ZrOcvy2nIq36AfddBXpkpFRJYrPXy7+ROIiIget9nESJWu7mYSWpLLviV8ux94zfWJy0klvasLfQfjYaD9WbLaJMjy5kCNsSy4jWNPREREjznuUznFSrSnBXy4lYS21ZPQuh0HrmNen2F6fpFYKF2d59qYtcTTbPcpEiWr1LoePdTaVCIionud47Psd/rruvn4dhKapjuM0Ll+5lUh3K6P6otExzb4MIlaFxkP2iKjZaHv6+8fiYiI7lVWdesmwmQYPJyEFkYddIdR4wwTTrtYkrhZ0IwFfJhEDy8yDqY9tU0lIiK6V1WLHM6FuonQ73bQU/KtbyWhdTouRpPeq18zAJAVlZzaWs5JF25Ly9m2yDgYRvCj5g4HERHRvQSQfZKru4Jh4GE0aJtZaUtCszGb9hpJaE5R1nJquUAxaYunqWp5altkjHz0Bmr4NhER0d0uBxuUrs5zMB6FqEUatWa9S5AoSWjGWJhPe+pUqTkoQy4AMOj76LbE0yxaFxldjMd6+DYREdEjtPd7jmNj1nJS6WYS2rQLRwvfrmox2q5gFHbUk0ov4dtl8+ih69qYTvSs0rLiPUIiInofYyzMZz3Y5rEktOk4QkfJKq1qkUNaNPcI/Y7belJpvU2QKo9Rb4ZvF5UodZOIiOhulmVhMuvDcZozK1leyfLBJLS6quV4HcJ5VQgd18ZUeZEIAPvjWU5KPI2xLi2nNlVaVPXLvSciIqLPNZj20FHWJMrysiahPXbsRV5rEtp2sX95xfdSCI1tMJ331a4uTnLZtsTTzFoWGcu8lCOLIBERvVPo2eomQl2LPK31mZVLEpoavi2b1QnFRzvzBri81BvNB2rLec5KWbXE09xaZNwu9upUKRER0b06ro1Aeb8nAlmsYxTKuzfPtTFtC9/eJUiT1zvzBrjee+o0C1pR1rJYx2rLOWhZZBS5HD2slJg2IiKiexnrcpRXs97Gar61Y1v4MIkuNwbfOJ4yORybAzUm6jjwlJHS6romUSttXRS47YuMqxMK5csRERE94nqwodnV7fWTSsa6hMC0JaFtWoJjjN5yXk8qVc2Ws+PZmF6OHjY+t9kmSM/6DgcREdEjtEebcZzJXunqLACzSfdG+LZeBMOO01yfwDWeRm85DeaTlkXG41mOynkMADzDRERE75adC9ls9GsSk2GIoCUJbbE6qVmlHdcg9ByrUQh3u0Q9qWSuu4LaNYkkLWTbssjYD1wYZR2DiIjoXmVeynp5UGdWhj0f3eixJDQv8NDtXN4/viqEp+NZfZFoAZhPIrjKu8QsL2XVssh4ef/YbFOJiIjuVV/3/rSD8VHgYthvrlbcTkJzMJoNgOuj15eHllmay66l5ZyOQ/jKu8TyOlWqCfsBfOVZLRER0b1EIMe0UDcROp6D6cNJaJedeeujp5sGAMpaZLfYq3/YqO8jCvSWs22R0Q889Me9xq8TERE9QI7nHKVSZxzHxnwSPZSEZlkWZrMe7DdPN526FjmmufoisRt6GPSUlvPmIqODyawHcFiUiIjeoagAkWadsY2FybgLy8B6WyPj9EYS2qQLT3m6afZprp668H0Xk5EaT4PVNsa5Zap0NuupU6VERESPUDb4Ll3dtKeeVDrnpaw2ehLaeBiqSWi1iBjt0abr2phd7go2FxkPZ4nTZoaodT16qC0y1sIzTERE9H6TSVc9qXQrCa3f7aCnhm9DDolyhsm2DT60XPE9xbnsjnrLOZ904Wrh21UtDJohIqL3GowihMrMSlWLPK1O6lRp6LcnoZ2yAmVdvy6Elrm0nGo8TVbKetcSvj0K4SuLjLVcjh4SERG9R9gL0NMOxovIYt2ehDYb60loh/XxZbXiVSGcTHvqi8RLPI1+72nY89ENmxVarpd/taxSIiKie3mOadtEkOUmQZY3VytuJaGdDqkkH+3MvxTC/qQHX2s5q8vRQ22gpht66iIjANku9+pqBRER0b0cYy4JMMqaxHafIlHu3hrrVhJaLrvt6/13AwCBZyPs6S3n0zpGWSm7gh0HE/3oIbbrE3Ilpo2IiOhelgX0Q1fdFTzGmRxOWfMzuJ2Etl43g2OM59gIPTUV+xJPUzRbTtcxmI/1RcbDIZX4pA/UEBER3cuzod4VTM+FbFoOxk9bZlbKspbl6qjuzJte4ALaSaVdgvSsxdNcWk5tqjROctnt9S9HRET0CK3ZyvNSlkpXB1yT0JSZlfo6VaqGb9sGRrv3dDie5djacnbhKFOl56yUdUtWqWurv0xERHS3qqpludS7uk8loZVKVqljLPQCr7lHmKS5bFu6utk4Qsdr7goWZd06VRp4DmzDuDUiIvp8Uousng6o6uaaRNBxbiahafd1bcdGL7i8f3xVCPNMf5EIAONBgDBoxtNU13tP2iKj5xhEHV7lJSKid5Hdco+iaBY0zzGYjSP1mn1bEpoxFkbzwcv7x5dCWJWVrJYHteXsRx301XiaSxHUFhndjouur79/JCIiulecFciUTQTbWJi3zKycEj0JzQIwmfXhfLQzb4BLQds+7VArBS30HYyHejzNcpMgU6ZKHcfGaD5QKzQREdG9kryUc9GsTZZ1KYLazEqalbJumSodjbvovAnfdgDI8VygVAqadyN8u3WR0ViYzvswypcjIiK6V1VD8lIPq55NIniu3TjDdCsJbdAL0I2aTzfNMS1QKAvztm2uRw+bLecxzmTfMlU6m/bh8DI9ERG9k9KfAQDGw0g9qXRJQovVJLQo9DAcNINjAIjJlJHSl3gareU8F7LepY3PAMBk3EVHWWQUnmEiIqKfA/1eoJ5Uql+S0LTwbQeTUaT+ecezcobp0tXpJ5XyopLFJn77ywCA4SBQFxlFRJQ8VCIioocEYQdDPdpTVreS0CZ6Elqal5IVVbMQjsdd+J1my1lWtTytTtCOSbQtMgKQQ1qonyEiIrqX23ExnnYBZWZlvUuQtCWhTfSp0nN8luTapb0qhP1BiEh5kVhfdwW1eJpbi4ync4FCaVOJiIjuZV/3/rSZlcMpk2PcXK14SULTwrezQvar48s/vxTCoOujPwybjzafw7fLZkF7XmSEUqFPu1gy5TNERET3smCh57vqJkKSFrLZ6zMrbUloZVnJevF6Z94AgGtb6E/Uo4dYbxOkmd5yti0yxqeznHb6u0QiIqJ79UNXvSuY5aWsWmZW2pLQ6lpksTw2ktCMbSz0fE9tOffHs5ySZstpLOBDyyLj+VzIriV8m4iI6F6uDbhKnSnLWhYrfVewF3k3ktCOavi26QeeOk0TJ7nsDvpdwdn4ssj49teLopLV6sjhGCIiejftYMNlZqXZ1QHPSWhtU6WJGr5tLAtGaznPWSmrrd5yToZB+yLj6ohaqYJ2YzaViIjoMSKQ5eqIQunqPNfGtCV8e7tPkShZpZYFDELlDFNZVrJcHaH1nINuBz1lqlREZLFuCd+2De8REhHRu23XR2RZM9rTsS18mETqNftjnMlBS0KzLkM4trFen2Gq6/ryIlHp6qLAxWjQDN9+mSpVtuZtY6Ef8AIFERG9z2kXSxI3C5qxLmsSjyahDaa9l/ePL4VQRGS1OKgvEjuejellV7Dxgza7BKmyyGhsg77vqkM4RERE98qKqnUTYTbpqjMrt5LQBsMQfvRFCMxzIZT98oBcWZNwbIP5pPvYIqN1PXqovH8kIiK6V1HVclJqEwBMhiECJd/6ZhJa1EFv8Hpn3gBAkpc4J0rLaa7h20pBu7XIOJ724CoxbURERPcSgRyUFT4AGPR9dKNmvvWtJDS/42I87jZ+3aR545IcngAAIABJREFUJamWim0B80kEV4unyUtZtrSco2GEQAnfJiIiekReqXObl5NK/eZJpVtJaK5rYzbRs0pNrEzgAMB0FKHjKS1nebn3pC4ydn30WsK31R9CRETUQnu02em4mIyaXR0AbD4jCe1cVKJu+A0HoXpSqa5FntYt4du+27rI2HZckYiI6F6Oa2M27akhMPvjWY7KY1TLAuYtSWhFVctJu0fYjXz1pJIIZLGOUWjh266N2UQP346zEjxAQURE72Fsg+m8r+dbp7lsW5LQ5i1JaGVeyvF8eSL6qhD6gYfxWL/iu9rGOCvxNJep0kidKj0XlaTKZ4iIiO5lARjNB3CcZkE756WsNon6uVtJaNvF/uXR60shdDwH42nv+We+sjucJU6b7xKNBXyYROoiY5bmEreMvBIREd2r67vqJkJxY2alfyMJbb04oPpoZ94Al4I2mg/VlvMU57I7NlvO56OHrha+nZeyW+w/9XcjIiK6Keo48JTthec1CT1828VYSUIDIKvVCfmbJ5UGAHqBB1v5QeeslPWupeUchfBbFhlXb44eEhERPco2QKBsL1xOKun51h3PxmzckoS2TZCeldOC/cCDo3SCRVHJYq3fexr2fHRbpkqXywMqTscQEdE7tRxskNUmVk8qfToJTR+oMVrLWd2Kpwk9DPv6ruByfUKh7EowaI2IiD5Dc2Zll6gnlYx1Owlt2/J0sxe4zfWJ55ZT6+p8z8FE3xXEepvgrCznG8uCxzNMRET0TvHxLIdjM9rzMrPSloRWyaolCS3qOOg4tvW2EF5eJBbNltN1ntck9EXGk3Iew7KAfuCqnyEiIrpXluay3ZzU35u2zayUdesrvrAXwL8Oe74qhLvNSX2RaF/Dt9VFxiSXXcsiY9d31W1+IiKie5W1tG4ijPr+w0lofuChP+69/PNLIYwPiZxurEloBe2clbLa6s9d+5MePBZBIiJ6h1pEjmmubiJ0Q+8zktAcTGY94KMnlQYA8rKWY0vLORtH6HjKruCt8O1+gLDXTAYnIiJ6gOyTHEpTB993MRnpMyu3ktBms15jqtQpr6GjmtEgQBgo8TS1yNPqhFqp0GHgYTDSY9qIiIjudallzTrjujYmowgCuTsJzTIW5tOemoRm9mnRclKpg35Xj6dZrNsWGR1MWu49ERERPULrBG1jMJ/29CS0RE9CA65TpVr4dlWL0Z673jqptNwkyJRDvo5z+XLaImNZ8x4hERG9j2VZmM16rTMr65aZlckwhK9kldYickiVM0ye67Re8d3uUyTKY1Rz4+hhVlZS8h4hERG902TWg6dEruWfSkKLmlOlUl+KYC3yuhDajv4iEQCOcSb7k7IriOdFRr3lPCrPaomIiB7Rn/TgB82CVlWXwU3tMWoUuK1JaNvl/mW14qUQWsbCdD5QXySm50LWu+Y2PwBMxyE6SoWuyurl6CEREdHnClxb3UQQEXlax+rMiu85mLYMbm7XJ+QfxbS9FMLRfKC+SMyLShYt8TSjvo9IqdB1LbJ92qkVmoiI6F6eYyPsONpvyXITI1fyrW8loR0OqcSn1wM1BgC6HQeer7ecbeHbvRuLjOvlAaXy5YiIiO5lrEsoNrSTSrsE6bm5K2gbCx8mN5LQ9s2BGhN6DjpKJ1hfdwW1eJqg42Dcssi42Zz+P/bebEeSbUnP+9fyIXwKjzlyHzWPqKZANSQSJHTRUAsQIfEBCBDgLV9BAK/0UgLfhrogBEloAexdGfPg82S68MisynRbURFZW4KAtg9oYJ/K9iz3Cgu3ZWvZ/xtK2RIVBEEQfhHHBhSTBC/Xgq53elZszny7bGhvMI7RAWNUilvJydrT2Bqrecje3OmcU5oNb04QBEEQnoXLM3le0ZGp6oA3J7RhTqvvmG/7rj2UTwDA/piiYKo6645MIklLOjPjMQDAZbd3BUEQBOFxqrKh3Z6v6uZ3nNA2uwQds7vp2hrhiEmEl0tuHKn0sjSbb+8NQw8jz4GWMUyCIAjCL9A2Le22F9Z8exy6TzuhOSMHkdefP35IhFlasgeJALCeh3BNXaX7hLODg+9a7/OeBEEQBOErEN2UCExCCzzb6IS2O5qc0CzM1pP3rdf3RFgVNR0NJedi6sP3mJKzve27ckkw8hAwe7WCIAiC8AR0LWpWieA6FpaGnpXjOQdnvq2VwnIdQ/+wu6mBfg/1tDmzJeckGmEcDkvO7l3IOLxm5DmIF+PBnwuCIAjCM1zzGjWTZyyr1wrqJ53QVqsx7E87lXZHRNeiRtcxJafvYDbxB39Od4WMFharmLVpEwRBEIRHaVpQR8M8o5XCahFBW0p97oG554S2mEcYMebb+pLXrFZw5NpY9lrBJ4SMGqtVzHaVCoIgCMIzMAq+vqpbRE87oU0nPsKAMd8mIs1109iWxnoZsVXdJSnpmlaDa5RSWC0jVsjYkYxhEgRBEH6d2TyCx/SsNHec0CKDExoAfgyT1grrFT/0MMtrOpxN5tu8kLHtiOph8SgIgiAITxFPAkRcz8pNK2hyQlsYnNCSokbddh8TYV/VxexIpbJqaWsy3zYIGTsiOmeVlIOCIAjCL+GHHuJpMNzafOtZueOEBuaILzmlVN6u+ZAIZ4sII8ZyrbljTxOHI6OQ8Vr0Qw8FQRAE4as4lkK85JUIh2OGvOTNt01OaGlSUHL6Xti9J8LxLEJgKDlf93zJ2QsZh12lAOi8vbDSCkEQBEF4FEsrjD2H7Vk5Xwu6ZlzPyh0ntKKm0yfzbQ0AnqMRTpiSk0CbvcF827GMJef5mKIQ821BEAThF1AAYt9lk2CaVXS6FMxVZie0um5pt7sOGmq0Y2mEI4f9ZftjiqIalpy2pfGyCNmbS5KCrhe+oUYQBEEQHsWxAYvZ2izLhnZHvmflrhPa7soe1+nYdwFupNIlJ96eBnhZhLCYkjMvajoYbk4QBEEQnoEb2NA0LW32V9bfOjY4od0137Y0NDfKPklLOjMlZz/00Cxk3Bq8Sm2L/WNBEARBeJiu62izvbIjlQLPwZxxQsNbVyljvm1phdh3hjrC4k5Vt5gF8Liu0rajzS5hvUo9x4KtZQyTIAiC8HWIiHabC5pmmNBGroXVnHdC2xuc0LSl35twPiTC94NE5iamYw8RY0/zLmQ0lJy3eU+CIAiC8FXovLugYmQStqWxXjzvhDZbT97PH98TYdd2tNuc2YPEKHAxjYf2NG9CRq6r1HZtjG9DD3/2hIIgCIJgIqsaFMzAeK0UXpYR21BzzwltvhzD+cF8WwO9TOL4ekLLJDTPtbHghx5ibxIyWhqz9RTc+aMgCIIgPEpRtZQz53tQwGoRwmH8re86oU1D+J92NzUASsoaNSOTcOx+3hOX0M7XghJWyNgPPbSYmxMEQRCER+moz08cy1nI96zccUIbhx7GjPm2Toqa9WjTtwkUrD1NVtHRIGRcLiI4MpleEARB+EWY+gwAMI35kUr3nNB8z8GcN98mXTDDdZVSWC9C3p6mbGh3zNibm09D+D4z74lVfAiCIAjCc0ThCJPYf94JbcE7oaVlM5RPAOaRSnXT0Waf8ubbYw9j1nwbMoZJEARB+GU8z8F8HrE/2xmd0NTtiG+4u1nULeUVkwhn0xABU9W1HdHrLmG7SgPfwcwgZLzmFZgqVRAEQRAexnZtzFcxwDqhFUYntPUiYp3Qyryi9Nbs+SERRmOfPUi8Z08zci0sZ7yQMS0bVMw1giAIgvAoWgGz9YTtWUmyik5X3glttYh48+2qodPm/P33v/3HKBhh2k+T+AxtDxlKpn31npAxvWTEnT8KgiAIwjOMfRcWMzC+KBvaG3pWFtMAPtNV2rYd7TaXD05oGgBsrTA1lJzHc46sYEpObRYy5llF1wPvOyoIgiAIjxL7Lmwmz9R1a5RJTMceotDghLa9DJzQtFYKY58fenhNSzonQzV/b77NCxmrqqHD7nr/yQRBEAThJzgW4DJ5pm07et0lg7mCABD6DuuEBoB2+wQ1s1Op48CFZpJgXtR0OPH2NMt5AI/pKm2a3hmcM98WBEEQhGewmIENRGZ/a8+1sZyxR3w3JzSuoUZBcyVnVTW03RvsaWIPIdNV2ptvX9F1jDifFWkIgiAIwlPQbpegYjR5P3VCY7xKlQI/hqm9M1JpHLiYsF2loM0+Qc2Mx7C1hivzCAVBEIRf5HRIkRdDa09LK7wsDE5oeUUngxNa5DmwLf1xDNP7QSJT1fkj22RPg/0xRcmYb2sFxIFMoBAEQRB+jfSSUXIdHte99azYzFliUTW0O/BdpfFiDPemL/yeCAl02F7Yg0TX1ljNQ6gnhIxKK8Q+f/4oCIIgCI9SNZ1RibD6ghPaOPYRjL/btL0nwvP+ioKRSVhaGc23jULGm/iRk1YIgiAIwqM0bUcJk5sAYDbxEfjOsKv0zQmNsTULfBeTTw01GgDyqqE8YUpOBbwsI9Z8O78jZJzOI7jesKFGEARBEB6FCHTOa76qi0aIWX/re05oNhaLCPi0u6nLuqWMG3qIvuTk7GmquqWtQcg4iX2EEavhEARBEISHqVqwjZu+52DOD4yn3dHghGZrrJa8E5q+GkrO+SyA7zElZ9sPPeSMtMNghMkkYIWM7F8iCIIgCAY4Sbrr2FgxVR3QO6Hx5tv9EZ+lGROYpiVW4RePPYzDYcnZEdHrPkXTDu9uNLKxMAgZxXJUEARB+FUsW2O1Gj/thLZahHAYr9Km7eiS10MdYeC7mE2GJScBtD2kqJis1gsZI1bImFUNyQAKQRAE4VdQWmG5nrAjlfKipr3JCW0WwOPMt5uW3nZEPyRCd8QfJALA4ZQhL4ZawXtdpWXTUsboCwVBEAThGWarCRxDz8rmcMcJLeCd0I6vp/cjvvdEaNkWluuYLTkvSUnXdKjm74WMfFdpVdSUMolTEARBEJ4hGtlwuYHxd8y3oztOaPvtBc0Pu5sauOn+XqbQzEFiltd0OPMlZy9kZPZd65ZOm5N0yAiCIAi/RODaGDGVYHfTCrZM56Y/srEwOKEdDgnKT02iGgBiz4HN/EVl1dLWUHLODULGriPabS6skFEQBEEQHsXSQMCc7+HWs1I3wwaUu05o55zSbNhQo8c309HPP2iazjj0MA7NQsbt9oKGMd8WBEEQhGdwDAMb9sf0eSe0tKIz41UKAPpeycna03g25lOf+120O6Qoq+G5oBitCYIgCF9g2LNyyY0jlUxOaEXZ0P7E725GHjOGiQi03V/Zqs51LKzmIXtzx3OOLGcaapSSMUyCIAjCL5OlJZ3OvLXnPSe0zT5hbV1814LnWGqQCA+HBAUjebAtjZdFaBQyXjjzbfRDDzl9oSAIgiA8SlXUdNzzEygWUx+B0QktZbtK/dBDcJta8SERXk4Zf5CogJdFaBQyHgxCxtCz4TDXCIIgCMKjtB3RaXNmfUfjaPQTJzTGfNtzEC/H7//7PRHmSUEXpuR80wo+K2QczyKMGEsbQRAEQXiUjoiuRY2OGRgfeA7mE3PPCu+EZmGx+qiZ1wBQtx2d9xf2JhYGe5rmjpAxjDyEvPm2IAiCIDwKXfKa1QqOXBvLeQCwTmg5MoMT2mo1HnSV2k3XZ1vuIHEy9hAZ7Gk2BiGj5zmY9TZtgiAIgvBl6hYgGlaCtqVvdqCkPhdjl6SkC9tVqrBajmEzO5X6klV8VRe4mMaMPc2b+TYjZHQcC8vFGGAytCAIgiA8AzewQSuF9WoMi9EK3nNCW85DjFzGfLsj0h2TBb07I5X2xww501VqWRovy2HJeXsYsZkRBEEQfgml+q1NbqTSPSe0mcEJjYjonFVDHaFjW1gtxqzk4XwtKMk4rSD6oYdMh2jddCTzCAVBEIRfZbaIMBoNE9o9J7Rx6Bqd0C5FjY7oYyK0LI3VKmarujSr6HgZagUBs5Cx7YgujMheEARBEJ5hPAsRcDKJjuh1z/es9E5orPk2nXeX9yHz74lQKYXFKoZtM/Y0VUO7I6/mX0x9+IyQsWs7uuSV7IkKgiAIv8TI0QgnITtSabM3mG87FpYG8+3zMUXxQ0PNeyKcrGK4jEyibm7KfObmJgYhI9HHoYeCIAiC8BUcSyMaOezP9qcMBeNvbVsKL4sQmnFCS5KCrpePDTUaAMKRDS8wlJwG8+3QdzAzCBn32ytq5uYEQRAE4VGUAmLfBbiRSpecUqZnRaveBMbohHYcNtRoz+lNRz//gIhos094exrXwnLGCxmPxxSFnAsKgiAIv4hrgW3cTNKSzkzPigKwWkRG8+2twatURx5bctLukKGshu2etqWxXkSs+fblWtA14RtqBEEQBOEZuCRYGKo6AFhMA/jMEV/bdrTZJaxX6cixoMGOVMrYkUpaK7wsI6OQ8WgYj2EarigIgiAIj1LXLe12V7ZnZTr2EIW8E9rrLkHL7G46lsaYm0eYJIVxpNJ6EcJhukrLqqWdQcgYjmxYWpxmBEEQhK/TtR3tNmdwJjCh79x1QuO6Sm3XxrjfEf04j7DIK2PJuZwH8Bh7mnchI+dQ41jwmWsEQRAE4VEIoOPrCS2T0DzXwtLghHa444Q2W0/et17fE2FdNbTfXtlfNos9hL6h5NzzXaWjYIRwZBsfTBAEQRAegJKiZpUItv3Ws8I7oV1ZJzSF5TqG9YNNmwb6eU/H1xNb1Y0DF5MxU3LeEzK6NqarGBDzbUEQBOEXSIqaHfKgtcLLIuKd0HKzE9pyEcH5tFNpE/XznjrmINEb2ZjPWHsa7I6pQciosVjHbFepIAiCIDxK04G6bqheUEphvQxh21p93pAsqoZ2B75xcz4N4TO7m/qSV6xHm+NYWBnsaU6XgtK8HlyjlcJqFbNCRkEQBEF4hsYwsME0UumeE1o89jBmzbdBumYqQUtrvCz5kjPJKjoxXaVQwGoZwWGEjB2J5aggCILw68ymIQKmqmvvOKEFntkJ7VowY5jU29BDzp6mbGhvNN8O4THjMToiGcMkCIIg/DLR2MOY7Vn5iRPanHdCS8sGVdMNE+HyJ/Y0rPm2QchIBDpnFZgeHEEQBEF4mJHvYjqPuB/R7vi8E1p6yai4VWkfEuFsHrEHiW3bawW5aRJR4LJCRhDoWtTs+aMgCIIgPIqtFaarCcA6oeUw9ayYnNDyrKLr4bvv6HsiDCcBQqbk7IjodZ++DzD8Ec+1seCHHuK8v4I7fxQEQRCER9FKYew7UExCu6YlnZNycM09J7Sqauiw+6iZ1wDg2hrjmaHkPKSomEM+x9ZYL0JWyHg9Z5Qn+eAaQRAEQXiGOHDZuYJ5UdPhxOeZ5SyAx5hvN01Hm+11oJnXtqVxm0AxuOhwypAVjD3NzXybFTJmJZ1PfEONIAiCIDyKawM2k2f6nhXeDnQWewgD3glts7ui6xhxfuw7rFbwkpR0TRl7GvRDD22mq7QsGzoYbk4QBEEQnkEzO479SKVhVQf0PStmJ7QENSNMtLSG5krOLK/oaKjqVvMQI3fYVVo3LW32V3CSQXvQmyoIgiAIz9F1RJvthR2p5I9sLAxOaPtThpIx39YKmAQOBq7YVdUYRyrNJz4Cn9EKdkSbXQIFAj7lSNe2APEcFQRBEH4FAh12V9RMz4pr6/tOaFkF61PNp7RC7Pfnjx9qtaZp2YNEAIjDEWLWnuYmZOTmPVl9t48gCIIg/AqXwxUFMzDe0grrJ53QlAJmq8m7tOI9EXZdR7vNhT1IDDwb8ylvT7M98EJGy7YQe/z5oyAIgiA8Sl41lF2HHaJKAS9LvmeluOOENp1HcH/QzL8lQjptzmi4kvNmvg2DkDErGCGjVpi9TGUChSAIgvBLlHVLGVNsAX3PiskJbWNyQot9hNHHhhoN3OY9MQnNsjReFiGb0MxCRoXFOobN3JwgCIIgPEp3cyjjmE8D+N6wZ6V3QktZJ7QwcDGZBMMxTFnZUMmc7ymt8LIIefPtoqa9Qcg4X4QYMebbgiAIgvAM9bDREwAQR/xIpe9OaIz59sjGgjeOgc6Y4bpvWkFupFJVt7QxdJVOYx9hwDTUQMYwCYIgCM/BJY7AdzHjrT2NTmi2/Wa+PTziy6qGWIXffBby9jRtR6+7hJ0mEYUjTGKfqwTJlNUFQRAE4VHckY3FIgJYJ7Tc7IS24LtKy6alrGyGY5gmsY8oZErOm1aQmybh3REyXvOa3asVBEEQhEexbAvLVcz2rFySki6p2Xzb5sy3i5rSW+L8kAiDcMQeJBJA20OKijlLfBMygsnQWdVQyVjaCIIgCMKjKAXMXqbQTM9Kltd0OPM9K70TGrO7Wbd02pzft17fE6HrOZgtxuwv2x8z5Iw9zT0hY54UlBtaXgVBEAThUcaewyoRyqql7Rec0D5r5jXQJ7TpesoeJJ6vBSUZY759R8hYFjWd95efPJogCIIg3GfsOXCYPNM0nVErOA5doxPadntB82mnUiulEHsOP1Ipr+h4GdrTAMDaIGSs65b224v0iQqCIAi/hG0BIybPdF1v7dkxDSi+Z2N+p6u0ZJQSeuLzSbAsG9odeHuaxdQ3Cxm3F/bmBEEQBOEZbD3cpSQCbfcJb759c0LjrD2P5xwZ41WqoKC5rc1+pFLC3tgkGmHMdJUS3bpKGSHj8G8QBEEQhOc5HBIU5dBtxrZ6ExhutOA1LenCmW8DiANnKJ/o5z1d2aou8BzMJgbz7X2KihEMWlrBGQx7EgRBEITnuJwySrOhTEKr3gTG5IR2MDihhZ4Nx9IfxzC9HyRy9jSuhdU8AFghY4acM99WQOy7MoFCEARB+CXypKDLeXhcpwCsFpHZfNvQVTqehRjZ1scxTADosEvYg0TberOn4YWM15TrKlUYe877vCdBEARB+Ap12xmVCItpAP9JJ7Qw8hBOwsEYJlyPCXK25FR4WUZsQrsnZJysYlZaIQiCIAiP0nTUT6BgEtpkPEIUunxXqdEJzcFs8dF8WwNAUbeUMiUn0NvTOIw9zT0h43QWwmPMtwVBEAThUQigS1bxVV3gYsr4W99zQnMcC8vlGPh0XKerpqOUcY0BgOUs4M237wkZIw8Rb74tCIIgCA9TN0DHZMGRa2MxC9lrDiYnNEtjvRyzckF9YXQVADCd+AgDvuR83fMlp+85mBluThAEQRCegZOkO7aF9XJsdEK7GpzQ1gYntLrt+DFMUTjCZOwxWkHQZp+i5sy3XQsrw3iMphWfGUEQBOHX0JbGasVXdfec0FYGJ7S2I7pk1VBH6I0cY8m5O6YomK5Sy1LGrtK8aonJm4IgCILwMEopLFcxbHuY0IrqeSe0ru3oklcgfBrD5Dg2e5AIAKdLQWnOawVfDELGqu0oZRwABEEQBOEZJqsYLtOzUjcdbfYpu+0Y33FCO76e3rde3xOhtjSW65gtOZOsopPBnma1iOBw5ttVQwkjshcEQRCEZwhHNqtE6Dqi1x1vvh14DuYGJ7T99or6h91NDXwfemgxMom8bGh/NJScM17I2LYdHV9PbMurIAiCIDyK51jwmGKLqJ9A8awT2vGYovjUJKoBIPIcOMwU37puaWuQSUzHHiJDV+nu9YKOuTlBEARBeBSt+vzEQLtDhpIZ/n7XCe1a0DUZ7m7qcOTAZc732raj133Ctq+GvoNpPOwqBUC73RU1Y74tCIIgCM/g9gMbmJFKGTtSqXdCC41OaEeDcYz2XXPJ2baMPY1rY2noKt0fU3Y8hriNCoIgCF9g2LOSFMaRSr0T2jCnlVVLO4MTWjiyh/IJoB96WDElp2NrrBehUciYpEOvUgBwLPaPBUEQBOFhiryiw5FPaD91QmOaVjzHgu/aapAIj8eUHalkaYWXRWQUMp4M5tux70IziVMQBEEQHqWuGtpvr+zPZrF31wmN6yod+S7CUb/3+iERJpecPUjsS84INtNVek/IGI5suMw1giAIgvAoHRGdXk9sVRcF7tNOaI5rY7qaALet1/dEWGQlnQwl52oeYsScJb4JGTnCScC2vAqCIAjCoxCBLnmNllEieCMbi1nAXrc/ZQYntF4zr37Y3dRAP8DwvOWHHs4nPgJ/aE/T3hEy+sEI41k0+HNBEARBeAK65BU75MFxLKzmIZTBCS1hzLe1Uliv4oETmt3ehh5yJec4dBFHvD2NWchoY77kzbcFQRAE4VHqFiAa5hlLaywWEZSC+pwjTU5oUGYnNH3JK1Yr6HsO5lO25KTtHSHjahWzQkZBEARBeAbOl0UpZRypVNxzQpsG8DjzbSLSXMnpOjZWixBghYw5MqarVGuFtWE8RtvJGCZBEATh11kuIriME1pVt8aB8ZOxh4g13wadswr25x9YlsZ8FqEjqM9mode0pHPCawVXi4gVMjZtR3ULWOzkQ0EQBEF4jOk8QmfZKv+0I9m2fePmU05oBLoWNQD6KJ/oq7rhQSIA5EVN+xOvFTQJGduO6MyMbhIEQRCEZwjjABErkyB63adsz4rnWkYntMvhivp2zfdEqIDFKmYPEqu6pY3BnsYsZOyMTTiCIAiC8CiurTGes0oE2h5SVLXJCS1indCu55yy6/fC7j0RThYxRsxBYtN29LpL2JFK94SMp82ZbXkVBEEQhEextX6bQDHINYdTjqxgtIJ3nNCyrKTz6WNhpwHAdy340TChdR3RZpewCc2/I2Q87q+oZCivIAiC8AsoBcSBw2oFL0lJV8bf+s18m3NCK8uG9owJjB7ZFgKmAwdvJSdjT+Pa2ihkPJ8zygzm24IgCILwKK4FaEaOl+UVHU+8TKJ3QmPm6zYtbfZXENNXqsc+O/QQ+2OGouRLzvWSLzmTtKTzhW+oEQRBEIRn4M73qqoxjlQyOaG97W5yTmiubUGDq+oMI5WUAl6MQsbaOB5DxjAJgiAIv0rTtLTZXr/mhMbsbtqWwth3hvMIs6ykk2GK73oewmW6Suu6pc0+ZYWMgWvD0mK3JgiCIHydriPabS7oumFC8z3b6IS2O/JOaJZtYez1548fEmFZ1uxBIgAspj58pqv3w/IcAAAgAElEQVS0bTt63Scgbt6TrREw+kJBEARBeAI6bc5oGJmEe8d8+3jOkTJadq0VZi/T9/PH90TY1C3tNxf2IHESjTBm7Gm6m5CxbZl9V895a3kVBEEQhC+TFDWqYjhNwrIUXhYh21BjckJTUFisYtg/7G5qoN9DPb6e2IPEwHMwm/jcvdHOIGS0HQvT9fehh4IgCILwFbKyoZI531M3reCzTmjzRTjQzNsE0KWo0TbDhDZyLazmAcAktP0pY4WMWvdDD7WWyfSCIAjC12k7UNUM88ybVtBxrMEYpntOaNPYRxgMdzf1Na/RMFubtq2xWkTsSKVeyDgsU5XqJ1DYjPm2IAiCIDwDs+EIAJjPQnijJ53QwhEmsc9q5nXFVIL6Nu/J4uxp8poOZ4P59jxkx2MQd/AoCIIgCE8Sxz47UumeE5p3xwntmtdD+YSCwmo5ZkcqlVVLW5P59sRH4DPm20TEdK4KgiAIwlME4QjTSTAsth5wQgNzxJdVDZVNO0yE80XIjlRqms449HAcuog5822ALnnNlqmCIAiC8Ciu52C2GLM/Oxwz5E86oeVJQW9zDT8kwsk0YA8Su47odc+XnMEdIWNS1OyMKEEQBEF4FEsrTNcT1nLtfC3omnE9K2YntLKo6by/vP/v90ToRz7GXMlJoM0+Rc2VnI6FpaHkvB4TtkwVBEEQhEdRSmHsOawSIc0rOl4K9rrVHSe0/faCH7c3NQA4lsbEUHLujimKalhy2neEjMm1oNRg0yYIgiAIjxL7Dtu4WZYN7Q58nllMfQQGJ7Tt9jLQzGtL99kWTMl5uhTE2tMoYG0SMuYVnQ7J3QcTBEEQhJ/hWIDD5Jl+pBKfZ2KDExpR31XKHdfpie+y+65JWtH5Oiw5FYDVImJLzqpqaGe4OUEQBEF4Bm5gQ9cRbbZXoxPa3OCEtt2nqGq+oUZz3TRFUdP+ZDDfngXwma7Stu1os0vY8RjWoDdVEARBEJ6DiGi7vbBV3cgxO6EdTjnygt/djH0X9ucf1HVL230CTicxHXuIAkYr2BFt9ylA3SDrOZbGMNUKgiAIwlPQYZegZHtWNNbLe05oJaxPP3prwrG0+jiG6f0gkanqQt/BNOa1gttDiprxwrG0Quy7gJhvC4IgCL/A9Zggz4bTJLRSeFmGTzuhTVbxu7TiPRFSR7Tf8CWn59pYzkL2l+2PGQpGyKgtjdh32PNHQRAEQXiUom6NSoT1InzaCW0yC+H9oJl/S4R02l1QMSWnY2usF6FRyJiwQsaPQw8FQRAE4StUTUcpU2wBwHIWPO+EFnkYfzLf1gCQlg1KruS8zXviGmruCRkXqzEcxnxbEARBEB6lI9AlHxZbADCd+AgNPSsmJzTfczBjdjd1XjVUMOd7SimsFxFse6jhKCqzkHE+C+Ex5tuCIAiC8AymMUxROMKE87cm0OZgdkJbLSKA6VnRxpJzHmLkMvY0TUebfcqWnPHYQxQNbw5sD6ogCIIgmOEGNngjBwtTz8qJ71mxLGXsKi2qlliF32wSIPAZe5qO6HWX8EJG38VswptvM0ePgiAIgvAUjmNhuRwDTFV3uvA9K1oBLwYntKrtKCmZeYTjyONHKhHRZs/b04xcC0uDkDEpajB5UxAEQRAeRlsay/WE7VlJsopOd5zQHM58u2oouYnsPyRCz3fZg0QAtD1kKJkJu7alsV4YSs66Zc8fBUEQBOFRlAJmL1NYXM9K2dD+aDLfNjuhnV5P71uv74nQcW0sVnzJeTznyDh7Gq3wYhh6WGSlseVVEARBEB4lGjmsEqGuW6NMYjIeIQr5rtLd6wXtD7ubGuj3UGcvU7aqu6YlnZOhtELhTcjI7LuWDZ23l8E1giAIgvAM4ciBy+SZtu3odZ+yR2+h72D2SSt4g3b7K+pP5ttaoTcd1dxIpaKm/Ym3p1nOAnhMhm6alnbbC2u+LQiCIAiPYmvAZ9QLbz0rLeuEZt1xQktRcLubse+yHm1VbbanmcWeUci43V7RMTcnCIIgCM9gW6xFJ233CSqmZ6V3QovMTmjpcHcTADS3tfl9pNLwgihwjULG7f6Kuhne3PBvEARBEITnOR5TdqSS9RMntJPBfDv23aF8ouuIXndXtuT0RzYWM1Yr2JecnPm2UnAGw54EQRAE4TmSS07XhJdJrBfh005o4ciGa2v1ORHSbndlRyq5tsZqHkIZhIwpa74NxIHLXiMIgiAIj1JkJZ2O/HHdah5ixHWV3pzQOMI4gHfTF35IhMd9gqLkS861QSaRZBWdGSEjFDD2HNjMNYIgCILwKE3bGZUI84n/tBOaH4wwnkfv//s9EabnjFKu5FTAyzJ6H2D4I/eEjJNFDIe5RhAEQRAepe2IrkXNKhHGoYs4Ghm7SjknNNe1MV9+NN/WAFA2LV2PCXsT63kIl7Gnqe4IGeNJAJ833xYEQRCEhyD0Y5g4raDvOZhPeX/r3dHshLZajgeaebtuO0oL3gFmPvXhe0zJ2fb7rqyQMRghngaSBAVBEIRfote9DxON61hYzEN0RKwTWprzTmjr1Zg139aXvDaOVBqHw5KzI6LXfcqWnN7IxvyHfVdBEARB+CpcsWVZGmumqgPMTmjAzXzbHu5uNm1Hmtt37Ucq+dzvot0hRcV0ldq2hdVizAoZm07mEQqCIAi/Rl/VxWxV91MnNM58uyM658wYppFrYzkPAUbysD9lyJht1Hvm22XdEqOxFwRBEITHUcBiFbMjlaq6pc0XnNDemnA+JELb7oceciXnJSnpmjJaQQDrRcQKGZu2oyvjACAIgiAIzzBZxBgxPStN29HrF5zQTpsz2tve63si1FphueZLziyv6WCwp+mFjMy+a93SRZKgIAiC8Iv4rsUqEbqOaLNP3xPah2vuOKEd91dUxffCTgN9VTddT2EzJWdZmc23TULGruvo+MPQQ0EQBEH4CiPbQsC4xgCgraFnxbnjhHY+Z5R9Mt/WABB6Nlyu5Gw6o1bwnpBxt7mglYNBQRAE4RfQChj7Dvuz/TFj/a2tOz0rSVrS+TLc3dTByMaIaSntOqLXfcKWnIFnG4WM+32CSibTC4IgCL/IbWDDwyOVfuaEdjB4lWqu5CRCb0/TMPY0joWloeQ8njNkOd9QIwiCIAjPwOWZLCvpdOatPVcGJ7T6jhOa79pD+QQA7I8JSqaqsy2Fl0UIbRAyXjjzbUDGMAmCIAi/TFnWtDdMk1hMfQQGJ7TXfQpidjdHtkY4sgdjmHA+Z+xIJa16mYRJyHgwmG+PPQeaEdkLgiAIwqM0dUv7zQXE1HVxNGKd0OjmhMbN13U9B5HXnz9+SIRpUrAHiQq9PY3JfNvUVRq4FkbMNYIgCILwKEREx9cTO1Ip8BzMDU5opq5S27EwXU+A29breyKsiopOB34CxWIawGfsae4JGf3Ih8+3vAqCIAjCQxBAl6JmlQiuY2E1DwDmLPFwyg1OaBrLdQytv+9uaqD3WztuzmxCm449RCFvT7PZ8V2lnudgshj/5PEEQRAE4T7XvEbTDvOMbWusl5HRCe3CdpUqrFZj2J+UEnZHRJe8Yg8SQ9/BNGbsad5KTqar1HEszFcxIOeCgiAIwi/QtKCOhpWgVgqLRa8V/Jy67jmhLechRsxOpb5kNTvqYjSysZiF7C/bHzPkrJBRY72KWSGjIAiCIDwDU2tBQWG1HLMjle45oc0mPgKf2d0kIt10TFVnW1gvInak0vlaUMJ0lSqlsF7yXaUd1+YjCIIgCE8yX4TsSKWm/YkTGme+DdCFG8OkdT/0kKvq0ryi44XXCq4WIVym5Gw6okqMZgRBEIRfJJ4GCANmYHxH9GroWfHvOKElRY2m7T4mwu8HiYw9TdXQ7sBrBedTHz4jZOyI6MJUj4IgCILwDH7kIZ4EvBPaIUVtcEJbGebrXo/Je5/Lh0Q4X47Zg8S66WizT9mSc2ISMnZE17xGJyMoBEEQhF/AsTQmi5j92f7Em2/fc0JLrwWlP9i0vSfC8TyCz0zxbW8lp0nIODMIGU+7CxquC0cQBEEQHsTSCmPPYZUIpwvfs3LXCS2v6PhJM68BwHMshDFXclJvvs3Y04xcs5DxdEhQZkMNhyAIgiA8igIw8V22cTNJKzoz/tY/c0Lb7YfGMdrtTUe5e6DdMUNZMfY0lr51lTLm29eCEoP5tiAIgiA8imODbdwsypr2J5P5Nu+E1rYdbXZXEHNcp8eeC7AjlXJkeT28QPVDDy3m5rK8oqPh5gRBEAThGbiBDXXd0naXgGtamYxHRie0113Cmm87lobmSs5rwo9UUgDWixAO01VaVg3tDEJG22L/WBAEQRAepm072m4vbBNm6DuYxb7RCa1mzLctrRD77lBHmOcVHQxV3XIW8ELGpqPNLmFLTs+xYGuxWxMEQRC+DhHRfnNhe1Y818LS4IR2OPJdpdrSiH0HSuHjPMK6atiDRACYxR5Cpqu064he93xXqWvr93lPgiAIgvBF6LS9oGLcWRxb33VCuxqc0GYv03dpxXsibJuOdpsLW9VFgYsJZ09DoM0+RcOZb7s2opEDMOePgiAIgvAoadmwSgStFV5u5tuDa+44oS1WYzg/aOY10Ce04+bEHiT6IxuLGWtPg90xRcFkaMvWmL1M2QwtCIIgCI+SVy0VzPmeUgrrRfi8E9oshPfJfNsGQNeiRmMoOVfzEIrTCl4KSg1dpcv1BJoRMgqCIAjCo7QdqGqGeQZ4G6lkDcYw3XNCiyMPUTTc3dRJUaNmKkHL6oceciVnklV0MnSVLpdjOIyQURAEQRCegSkEAQCzSYDAH/pb33VC813MDObb2lhyLiPYTFVXlA3tj3dKTsZ8m2QMkyAIgvAHMI48fqTSHSc017GwNDihJQUzhgm4b09jmvc0iT1EnPk2gRhzGkEQBEF4Cs93MeNlEved0Ja8E1pR9+ePg0Q4n4XsSKW27fddOR/t0HcwZYSMAOiSV5ABFIIgCMKv4Lg2FqsxYHBCM/WsvCxD1gmtyEpKb/rCD4lwHPvsQWJHRK/71CBktLEwCBmTkj9/FARBEIRH0Qo3JQLjb52WdE6G0orvTmjM7mbZ0Hl7+f773/7DC0aYmErOQ4qKOUvshYwhK5NIzymVtSRBQRAE4esooLdB40YqFTXtTzl73T0ntN32o2ZeA/0Aw8kqfvs7P7A/ZcgKRit4R8iYpSVdj2K+LQiCIPwase+yW5tV3dLW4G99zwltu72g+7RTqbXqhx5yJeclKemaMvY0gFHIWJYNHQ02bYIgCILwKI4FdshDP1IpYftP7jmhbfcJ6ma4u6kngcuOss/ymo5nvuRczUOM3GHJWTctbXe8TZsgCIIgPIPFDGzoRypdn3ZC2x9TFCXfUKO5kvPeSKX5xGeFjF1HtNleWSGjxYo0BEEQBOEpaLe/siOVfuqExppvA3HAjGFqmpa2him+49BFHHFaQaLNjhcy2paGI/MIBUEQhF/kuE9QFMOqztL9wHiTE9qZcUKDAsaeA1urj2OY+oPEK1rOnsazMTfY0+wOGUrOfFsrxL5MoBAEQRB+jfScUZow1p4KePmCE9pkEcO5XfOeCImI9tsLe5DY29PwJefxnCNjMrTWfRMOd/4oCIIgCI9SNi1dj3wT5moesk5o9R0ntHgSwP9BM/+eCC+7K0omodmWwssiZBOaScgIBUzWE2hLKWmbEQRBEL5K1XaUlA0IQ9Pq+dRHYHBCezU5oQUjxNPgwzUaALKqoTzlS871IoL1hJCRAMwWEVzPkVJQEARB+DJEoMsn67S33BaPPYxZf2uzE9rItTGfR4M/10XdUs65Yt+SoNF8m+kqJQImEx+B7ynqACKSw0FBEAThS1Qt+sRC1GsGb1nQ9xzMJj53CW0NTmi2bWG9HLNOaDphtkMBYDE12NO0Hb0yQkYCIQpdxGNPEei9jO36O5cdUkEQBOEpiIAOfQJ5+z/n1rNCoEF+Opxy1glN3+kqLeuWWIXfZOyzI5W6rpdJfO4q7QjwPAfzaQAihY4IXQeAFEAKbSd1oSAIgvAcWqtbGaXQdoDSCsvF9wkU9EMuvCQlXVKz+TbnhNa0HV25eYRhMMJ0MhypRG8lZ8MMPbQtrOYhACi6JcGWOkw8G2lZU+TYjz+5IAiCIACYuH3uCF2NThHmy5j3t85rOjzphNbULV1uO6IfEuHIc9iDRAA4HDPkJa8VfBt6SFC37VCgbQFHE6Vlg9CVRCgIgiA8R+ja6IgQ2RYm8wksx1L9Fql6P54rK7P5ttkJraPj6+n9d7wnQtuxsFjF7EHi+VrQ1WBP86OQkUCgjtB1hCQtSXcNCEBo2/2PBUEQBOExKLRtEID/ch1COZbqbvnlTUjRtJ1RK3jPCW23uaD9QTOvge9DD7mSM80rOl4YexoA6x+EjEQE6vrmmLxo6PX3M/7rhQciQtUpMDuqgiAIgsDSdEDVKXiOhf/uLyeqaQhNS+gIoO6mFWR6VgDAv+OEtt8nqD7tbmqg91uzmCm+vfk2b0+zmPrwb0LGt+xMIFRVS7+/XlA0Hf7JYgQi4G/PNcau+9y/giAIgvD3lrHr4lvWwrMt9VcvIeq2Q9sRuq5DR3TrWeGd0Ezm28dzhiwf7m7qse+wHm1109Jmn/L2NNFoIGQkUug6om+7BGXVoOn65tZ/uvJQtx2WoQ+lZHtUEARBuE/dglZBANe28Df/xRidIjQdoW07dAB2h5SVSfzMCe3CmW8D0COmEnyTSXAjlQLPwdwgZNzsE5RlixZA1RHKlvBv/nGMwLWwzYH/4S9/Ex2FIAiCYKQj0H+znGBTAGPXUf/6n61RNV1fERJwPOWUMD0r+idOaAeD+fbYc4byiX6k0hUNc6g3ci2s5gHAlJz7U4a86P3g2g5oWkLZEFpL43/+F39GS6T+t28l/vNZ+NA/hiAIgvD3j8lohG2u0FKn/u1fr9FAobqdD56vJZ0uQ5mEArC644Rm6ioNXAsjx1KfEyHt9wk7Usm2NNaLXibx+WdvQsaObk4AHaG+VYTRLMRf/haqv/lzjKRuEY7Ct2Qo26SCIAgCgL4SjJwRvcRjZG2n/ubPMf689FHUDcq6Q5JVtD30rmYdfUwgi2kA/wknNADwIx/+TV/4IRGejyl/kKh6expumv1nIWOv8ej3cyfTAKQslVct/tU/X2IZOvg/9gXOuUP/aBKjbiUZCoIg/H2nbkH/KI4B5eH/PldqGTr4V/98ibxqUdaEvKjp7zb9wPjPSWMyHiEK3Yed0IDeCW3SO9QA+CERZtecroaSc70I4TD2NJ+FjIQ+U3cETMc+XNdWVd0iL1vUHfDv/uWf8dd/Maa/uxb4D7safxGN4dsuNd27jZwgCILw9wNqOpBvu/QX0Rh/mxJKIvXf/8MY/+5f/hlVS8jLFnlR0396vaC7SSd+rAZD38Esfs4JzbEtzFcx8INm3gb6eU+X/ZW90+XMYL7dmIWMYTBCFI1UPwWjg9IKKBp4rqZ/8Q9GWNtT/K//8Yz/61TBsTT+4SRG5CpYLuiUlWqblh+yeN2CmIkaUABcG6wJQNuBGANy4HaNZq4hAjHmOQAAWwO2xVwDUNWALb216v8uMGeqxme6XcO1/n7lmTrq74/DtgBbP/dMlgYc5t8Bt2u4+V/3nqnpQEwHNICvPZNjARb3TLdruHj9yjPd+2ybFsTpZr8ar/ee6dl4Bfpr/tB4tf647+AfHa9a9/eH4TNR1aL3RP7E/2/itf1jv4OjJ+MV6K/5I+O16RRi10bo2ghtG1Wn8LfnCknV4mXsq3/712v8eekjr/oCqmw6+k+vF1R113tY//Bsnmthaeg5MTuhaazWQ5s2u2k7Mk2gmMYewoAvOV/394WMRIQ+DypUdQvqNG13Cbq6xj+IXfwvf73Ev//fL/gP2wL/57HGJHRgKaW09uC7PqAIioC8bqlsGrzd9o83Mw1cXvrRdnTOKgx/AoQjB77LdMoS0SmtoJlRwiPbwpix6QFA56yCQofPm8aWVpgGIzaIsrKhqmkG96cATMMRuwVdNR1dcv6Zxp6DEXNI3HZEp4x/Js+xEDEDLQmgc8o/k21pTAKX/Qb2MdQOn0kpTAOXfaaybqkqavaZYt+Fy5nkdkTntGSvCVwbAbNoIwKdshK9CeBHnNszMdA1559JK4Vp6LIt2nnVUtXwzzQJXDj/H8Sra1uIfYd9pvvx6rI9APfidRKOYP9B8dp1RMevxKvhmWx9i1fmO5gWDUDMM/3B8dp2RCdDvPqujfBevIIGz3QnXnE1fAfvxWtRPx+vTdvd3ivDa8KRDd+1B59g13V0ymq0HeFYAPucQKgB6iUP/9M/nuFf/7O+MSYpGpQ1oar7JJiXLboftkQJgGO/9aw844TW24FyOcO+5DW76ojCESZjT33OdUSgzT5FzZlv38ZjAKR6V/DvyfB4TpGlJRxLo+1ajGyFf/1fxfg3fzXFQTv4j9tC7ZIap7LBOWsARaibfjLx97/8+3+OPQcdQX0ufduO6JxX/OGoa8HSanBNP/yxQsMkdsdSGDkWW2InZY2yHv65VkDkOaiZsRtl3X58ph+IfQdtR+rzAqPpiC6GZwpcC0pxz0R0zmt2seLaGq7NPhNdi5p9Vksr+I6Fuhk+U1G3lDLPpADEvs0+U912dM1rfkehX7oOnqkjonNWsSveka1hW3pwDdB/tnU7vMjWCp5jo2KeKasacHM6lQImvoOmJfW5vqya3smeY+zZIEO8XnL+mTzHEK+3Z2q4Z/oer8OXf9mgYJbxWgHRyEHNPFPZtJQwei2gj9euI1V9uvl730FzvILOecXGq2M9H69aA5Frsd/Br8Rr03Z0+YPj1bE0+0yXvEbNlN/WnXjNq4ayJ+O1vj0TR2SI1+722Zrj9ft38IfBEHTJqtszKYSuRmTbmPoW/tvfAvyP//QFpKHypkPVEMq6Q9N22OwTJFn9ngTftkQtrfCy4Ecq3XNCWy1CuIz5dtsR2R0Trd7IwXwWoGX2DXbHFAXbVfpRyEig278E4ZyWdDoXUBro0KEjhapTKC2N/+y3EH8VjNQ/+XMMx9KwLMBSGm1d0+H1DEX0Xga+Jf/xPEIYB8MVZdvR/vfjBw+592cKRpiuJ8N/HQIdNyeUTJOQ7diY/2nG/4OfU7oehy25SinM/zSDw/yDV3lFh82J3Z+bLGP4kTf8kJqW9r8f0TFfjGDsI16MmRUl0fH1hIp5KTtu/0zcyv96SJBehlobrTXmf5rBZlbxRVrSaXsePhCA6XoCLxh6/TV1S4ffD6xONZwEGM+i4TN1RIdvR9RM7Lmei9nLBMwj4by7IE+GXwzL0pj/aQ6LWcXn15zO3FGBAuYvU7jecJekrho6/H4EMd+n8SxCOGHitbvFK5OcRsEIMy5eATptziiy4bgZy7Gw+G0Gzax403NG12MyfCSlMP9tBoepTKqiosMrH6/xYoxgPDyb6dqO9n93QMvEqx95mCxjQ7yeURXMd9C1sfhtBsV8B6/HBOmZi1eF+Z/mbLyWWUnHjSFeVxN4zPi5tr59B5n3YRgHGM+ZeCWiw7cT6nL4HXQ9B7OXKd+Bv78iuw57NbSlsfjTjHUAy5OCzrsL+0yzlylGviFevx1BzHcwmoaIpiFbsR9+P6Kph9/Bke9itp5+2LK71XB0ej0jz0oQ0EvrqEPbAq1SmC8nyJtONR2hbjs0bS+TOFxyOl3KQRJUMI9UKu44oc1/cEL78ExElGQ1BmMhHMdCPI3QdsOhh6dLQSmzgjAJGQmEPG9oe8igFaA7oO6AVgNWR4jjAA20uhYNbEtBawUNoOta2r1e+g/p05ZoNPbhKEsV148vAiKi7et54CEHAO7IxsobYX8th890SJAwbgOWpbGaRDim1bBaSEs67AxnqusYl7JVKD++3Oq6pe23E/vyjycBHFIq+/RMXUe0/XZGzQSe57twXBf76+CFSIfdFRkzl8uyNdbTMQ7J8JmSa0GnA/+iXL7EOBeNwqfKoCob2r6e2Zf/dB4ibaHSz8/UdrT5dkbDLFb8YATHdthn2m0uKNjFioX1zGOf6XLO6HIafjGUVli/THDK6+GXKa9pZ3hRzpdjXGtSqD/eX9t0tPl2Yl/+4diDo/l43b1eUHIvStfGam6I12OKhGlq01ph/acIx2z4THlW0n7Lx+tiHeNStQqfqommbmljiNfxxIcDrXLmmTbfzuxiZeQ5sN0R99nisL8iS5h4tW7xynwH06Sg456LV2C5nvDxWjW0/cbH62QWIu2YeO062vxuilcXjsPH6357Qc5sz9m2hfXMZ+P1esnpbFhcr3+b4JQ3Cvj4TGXRxytXfc8WEZKGVPLp/tq2o83vhniNPDiWrcrBZwvabc4oDYvr9dzDPuHj9XrJ+20MAB31OnPSCqtVjLTuVHtzjGkJaDtCkla0P6TvjTHA97WYaaRS3XRPOaH1z0R0zWsohY+J0LI0VvMIdTvcGkiyik5MwlC4L2R8vb1c336duk2oiMcB7JGtiqqBZSnoWkFpBeqIXjeX99XXj7/U91zE3giXbPBFo93uyko/bNvCb4sQ17wdBt61oOOJD7yXdYSsIoVPX+qyrGmzuWLYxAvMZyEq0qr6dH9t29G31zMfeMEIcF3FPdNmc0HBvCgdx0IU+LhkzTDwzhku3ItSKbxMx0jLTvWDsr6T5xVtTYl9EaFslSo/3V/TtPTt9cKukseRh85yBs9E1H+2FfeidG2MA499psMxRcJVdVrjZRYiKYafbZqWtGcSOwCsVzHyBipvPt5HXbf0+noGt0syiQM0yho8U9cRvb6eUXMvSs9B7Hl8vO4TZExVZ1saL/OAj9ekoCP3ooTCeh0b4rWhzebCxutsGqImrWomXl9fz2i4XYjAhXL57+B2e0HOvShtC1EY4JoPP9vzOacztwuhFF5e+Hgtipo2W74CWswjlB0Xr/0zcbtcUeSBbD5eN5sLq6t2XRtjw3fweN5UxV4AABnuSURBVExxZeJVa43fZgEbr1lW0c7QsLhajp+O1zj20WqbjdfN5oKKW1yPHMQ+G6/Y7xOk3C6EpfGbIV6TpKDDLV7f7rDr+gJpvYpRt6S6tkHb9kdo1AFFVdNmn7BJ0DRSqe2IXr/ghHbaXtB0BMdS3xOhUgrLdf+CqNuPX+qibGhvsKd5Rsj4dp9x4CIKXdW2hE7hNnmYgEbRZndBWTbv21xvmweuYyOMfeTV8B/8eM7AechprfDbIkDJnQHlFW2ZFSXQJ/YWSn0+I6rfXv5c4I09WK4zuIaI6Nvmagg8G37ks2dR+2OKhKvqLI35JGSfKUlL2rMvSmC9GqMhqObT31VVDX3bXvmqbhJA2fbgmbqO6NvmYnz5e6HHPRNt94lxsbKYhCjq4TNdrgUdme0vpRReVgHqjlRdDeN1s72wq8PFLARpPXimtu3o2+bCvvyjYATXd5nPFrTZXdnFiutYCOOAjdfTOceZ2/5SCr/NQ1QtKXz6DuZFTRvDYmU1j9CpYbw2TUvfNhf23G0cebBHz8XryLURRPx38HBMceXiVWvMFny8pmlJOyZeAWC9NMRr3fYLZS5eYx/aeT5efUO87vYJUi5eLY35JGDj9ZoUdOB2IQC8rCPUHQbxWlYNvW74eJ1PA8CynorX0Hcx8kfsM222V+Tc4tq2sDDF6yWns2Fx/Wi8vn1cRNT3kWityrYD3RIj0e39uk3eezV+/Pe4N1Jps0/Yf4eRY3ZCOx1ShPQ9xt8T4XQ9QdLQYNVR1a1RJjEde08JGQmAP7Ixm4agmwuNehuzSAq7Q4Lsfeu1v1ZBwbI0ZtOAPfC9piUdjsxkYgX8NosAKPW5saesGnrdJuzLfzYN4Dj24JquI/p9c2Gt5wLfRRR6XAMRbXYJv0p2LEwnIRrmMP98Leh0ZqpvpbBchOgIqvv0dxVlTa+7hD3PWcxCWJY1eKam7ejb5spWqlE4QuC7g2uIQK+7K0puC9q1MJ0EbEPN8ZzjwmyLaa2wnoZsg0K/WOFflKtFCK314P7qpqVvmyu/BT32MBo5zDO9vfyZs+WRjfHYZ5vDdscUaTp8UfbxGrLx2i9WmEWl6md7Qg3jtar7Z+IXKz4c97l49X0H44iP136xMoxX2zY/U79YMcTrPAQBg/sryuZOvAaw7WG8tm1HvxviNQxcBMGIjdd+scLEq/O1eF0tQ3Qd1OfdkCyvn47Xpuno982F34KOPHge9x0k+rZNUDEL6NHIRhzzz7Q/ZkhM8Trjv4NJVtHecO62XoYPx+vbjsQ09jHyHPWWuN7+f9oW9G2bommHexf3RirtjhlK5t/BtvT7wPjPP7teC6KiQBh976y2gb7raeS7/F7yPmU7hELfwTQeNnfQHSGja2us3rpK3/7/qU92x3NOF+ZD0gp4mQeAgvq8rZEXtdFDbjkNYDt6cE3TdPT7ln9RjsMRwsAZXEPUl94l19TgWphNPbTdMPAOpwwJ59SjFRbzAIRh4KVZRXtmRQkA63kAy1KD+6vrln7fXtlzgsnYg+/bg2u6jujb9sq+4L2RjenE47aRaHtIkTGJ3bI0FrMAHZH6fCPXtKQjs6IE+j1/pYefbVn1L0puATab+BiNrME1bdcnNHZLz3cQj0fsM232CXJm+8uxNeazAB11g7kp52tBF+ZcS6n+czLF68YQr4tpAIeL17fFCtfUEN52Vp6IV9exMJ/6fLyec3Bt51q/JTRusVLTzhCvq3kAy2bi9bby53dWRvB9/jv4+zZhq7o+Xn1Dc1+GlIvX23fw2XhdzkJoPXymqmrpdXfl4zX2MBrx38HfTfHqOZjEpnhN2arOfv8O8vF6ZrZr3+JVMfFalA1tDLtmi2kA12W+g2+LlR/i5O0/o8BFFHrqY/Go0BHR6yH5wkilHHzPisLLMjQ4oVWUXjJMPevDn9u+a7Nakb7kTNkPyXPtLwgZew0H14F5zUo6Gs4fl4sQlm0NZBxV3dK3fcofEscefN8dXNN1RL/vEraVPvD6LxPzvqHNPkNW8quOxTwCQanP93FJSjoljJYFwHoeQWs9uL+ibGhzyPgtvamP0cgZXNO2Hf2+S9jBx1HgIuYkMLcvU8FIP1xbYzHrq87PPzteclwzvlmqT2jDZ8qKmrZcxY7+Rek49uCapuno2y4F8zEhDkeIwhEj6+lXyaXBLH4+Ddhn2p8yJDkfr8tFX6F9/ruSrKI9UwEBwMs8hG2I11fDonI69hCY4nXLx6s/sjGdBGy8bg/meF0u7sQrUwH18RpCW8PPtqwaejU0KMwnPjwuXjui37cpK1UKfQeT8fA7SABtdikKZuXv3OKVgMEznS6FcXG9XERsvOZ34nU5C+C6TLzevoNcvI5DF1HEydBuI+u4eHWs2wJsGK+HU46Eefnfi9c0N8fr2hCvdd3St13CxutkPEIQ8PH6bZeiMsTrfBawZ9X7U8ZW7D8bqXTmFqJ4c0Lj5+sejin84UkedOgNGkcBgLb7lN0q6oWM4ZNCxn7bhxMy5vfOH2fPG6lGgYvJmKlUf6J/7CtVftXBVUBaP+6/+iN919PwQ7rX9TQxdD11RPS6T1k9mTeyseC3E7D/wmIlSSs6m16UiwjOk67vs9hDyLR13zNrCDwH8yl/8L09ZGwF5Nwxiz9fC7oyL8q3Z+Li9f55Od+i3e+s8C+VKHD5nRUCbQ534nVhWCVfTKtk4GVhWCUX5nhdzgNjl54pCcbRyHyeszOc57gWljP+POdwMsfriyleDc19QP8dfHZKwTT2EJnMRUxT0kd/7Jbe23CDz3x/+T8nKVhMfQSGeDUt2m6WZsMHetsJNOQMU1V3uhRfGqm0P5kXK6wT2m13kzte8FwLGrebUyAoKCgQjqeUPdf6qpBxfS/w7p0/GgLPZKTqj2wsZvzL36x/1HhZhPxe8k9XHZz/amP8Mv2064n5kELfwYzpevpZ4K3n5sWKKfCMi5XCvP21uBN4psXK2LhY6Q++uZf/vYPvw53FytqwWEnz+q7wll+smIdV/+GLFcMq2dIKa+Mq+fnFyme/4B8xLVbaL84r/UMXK+qri5WAXay8xatxsWJaXH9lsfKVLb27i5U/VlJg3gl8tzRjmqX+uMWKwn0lgul4YXbHCW2zS9C1HdTt9ysoaBBcWyPyHGhQH1RKKShFSNKSrnde/s8KGb+ySv6V80fTquPuKvnZVcc8gMcEXnMv8ML/F1bJjOPHvcBLszuuC19ZJd9ZrNxdJfOLFdodM3b7qx8Bxi9WLknJn9Wh/2xNi5XdvcUKF68d0esufX6xcm9nxbBYOX1xsWI6W763WNnsDYuV8CeLlSe79L62WLkfr+xi5U5z32Q8wphr7ru9/NkpBV9YrPzxW3qtsQmnX6zw8fqHLla+ZGlmjtc/Sonwxt2dwE+LFaUArQjOyMHYcwCCun1WvZC9LGo6HdP3J/3xt/6RQsa7q+T/p70zCdUtu+r4f5/2O/35+nOfJZSpQUWwFEMCQagikgoq9oTCdhInKiI2qMmkJo40gnNxoAMRTBAHDmKDXQaJghBKCoNQxCJV+G7zNedrTt8sB+e7r+rds/Z53EvlpQrumTx4H/t+d93zW2uvvdbe+z/Qf7xTSe8JWYespDeYddy6pKfduqT3JP3Hu5T0Vk+jpHdKVtgseSBZeWJJj7HpLiW9p56s3CVLfj8kK+91Se9OyYrklpChZOUuJb27JCvvcUnvickKM+Y9T1ZiPlm585VmEl6flKxITyJIkpWhSuB1svLuOU0A0A0V09PtPqoQUKauDgGgOBS03xygCgFVEbiW7BV47w8yroZKegP9Rw68+/7jO899/7F77vuP3fP0+o93Lend9x+BD0b/8S6VwPdL//HRJKh071VTFSyiAG1DQgCYujqUs7GLFkRvfHMDFd0kqIhuhShEd6QgYO7AHDzIaMizjnWcIpWV9O77j/f9x3c99/3H7nn/9x8HSnr3/cf7/uMjm55+//HRJCg68V1FCCwXPkaGIfZJDaEAy7EL5bs/9Cwdsgpfu8xg6QKGJqCdZk7HNq4DBG7EveGs464lvafQf3xSSe++/3jff7x+7vuP3XPff+yeO/UfB0p6H+T+410qgU+7/3j9gSo6FrqqlAPPM4SpK3j94ggBgReefw6KF30IRIT/eJjBUgFbV2BoCuyRhmjmQhFCKApOWm7i+vD7QNbxPi7pGe99SU/Wfxwq6d33H79FJb1vc/9xMEu+7z8C+AD3H78FJb2n1X8cPlJwt/7jba80e5r9x+uJXRGnSVAIqEJgGtqYhJYY6SpMDfjKm3uoCvADL/84aR9++dNY/OUXcH4s8XdvJHjpuxxUJOCMXZQNiapuUTcAie4WmP0xp2NSoP8rAMuZA1PnwGtos03YMePAgmsz8hgt0Wp9hCDqjbNHOqaS4L/aJGjqpjdG1xREMwcKo5y8O+SUZWVvjBBANHdZwc28qGm7S1mbpmMb9ogHb7U+QgF6qLi2gbEk+K823dbfm99l6Oqp9MqXSIqi6o1RFIFo7kJX+WRlt89Ym+YTB5bJl/TWm4QXK3VNBJLgv9okoLb/bkemhvnY7gmSAsAqTlGVdW+MqgiczRyojE3HpKTjUcaryyZgZSXnNfRH8CTBf7U+Agyv1kjHbCxPVmqGV01VcCaZ0PaHnNKU4RVANBvgNeZ5nYQWn/mfeBVAb5xjG5gEPK/rbcLyqusqopmE132GPGd4FQLR3OF5zZ/EK19ZWUl49VwToTfEa98m01Af3cxyc9x6m6KU8BrNXWgcr2lJh2PO2rSYOhhJeF0P8SpZqcp57XyQeehqm6KueF6jOb8I2h8LStK+Dwp0c4bBzhk1bbZyXl0Jr13S1tkkRFfJ1FSB0DMRzV0x0jVYhoovfz3G23GBZycOmc99DNqLP/hJ/OSP/Sj+9K/+Bl/63wM+Hll49tkJsoZEVnTnLhSl049K8op2uxzM+8Ns4rCrmbpuT4GyP8ZzTYz9vqYZEdHVo0D5+MemoWEpyQ43cYqyaHpjFEXgbO6zpdckLelwKFibFjMXltn/g1dVQ+ttyo4J/BHbU21b6u7sI/TGjUY6FlO397MAYLVJUFdtb4yqKjibe2x2eDgWlCRl//cTQDTz2NVMUda0jTPWpnFos6vv5mRTFygf/9i2DNnu364PW/dt6gKlxwb/3T6nPKt6Y4QQiBYemx1meUXxjrdpKuO1aWm9SaEwa0vXMTFhdAWJiK62PK+G0VVWeF4zFEXN87rgeU2zkvYSXudTl119V3VzCir9Mb43QsjoChIRXch4NTUsJi6frGwSVGXfB4d4PSYFJUee1+XMY1sFZdnQdsu/2zCw+GpR201ogrHJtnTMZbyueV41TcHZ3Od5PeSU3ZLXvKjlvI5tuIy2ZzPAq+MYEl5Bq+0BbcPwevJBjtftLkOR87xGC48tvaZZRfs9P2fMpw5sTtuzbmi9uT2vl9sUOPmgULqeoKoIuJaOZ6IAlqnAGakokxpffO0Cmirw6Z/5eXzvRz4KTdM08ZlX/4je+K//xD//91v4wpspXv2eM6EVDRQ0UBSBqmqQNg0ddxlM7Z1r7K7/DX0LAbOaaVuiqziBeuo5vvuxRjoWMzb409X6iLZtoN/gX1MVREsPqtJ3pv0hpzwve2M6lQJJ8C9q2u/T3hgAmIQOPGY10zQtbeMEWtd5fewzxzYxm/QFLQHQxdUBoLb3XbquIprzwT/eZVSWVW+MIgSihc+WXrO8osMxY22aTVz2wGldtxTHCTQVuOlNnjvCJOTBW2+OEKDed5mGhuVcEvy3Ceq67o1RFQWRJPgnaUlJmrM2LWYeH/yrhnY7/t0GvsWWXtuW6GqVQBEE5cY4y9SxmPO8rtZHtI2E14UsWckpz4s+rwCWC1+arMhsmoQ2z2vb0nbL+6BjGZhNZcmKhFdNRbSQBP+9nNflwpcmK/vDLXm99kGOV8fEZMwnKxcSXg1dQ7SQBP84RV31eX0nWWGCf1pSktye1zhOwPyJEHgWwoARQW6JVmue15GpYzHz2GRlvUnQSHn1pclKlvG8LuY+W3oty5p2kvg6Dmz4TOm1bYkuJbzaloH5EK9tC0PrlubXmz4tQ8Ezz4zhmJpwRio8U8Xn//UtAMCnPvJh+uRnfguqqgoNAB48eCB+9nN/QOef/VWcH0vx+b9/E7/88QcIHR1GKZDkRJurPWxNoFEVEPCoCe46JiYTR9y8Fo8IdHm1hy4Iuq489pmha1gufRa8OE6BuoZ9Y4wiBKIo4MHLSirSvDcGAGYzDza7Um1ovU9gMWN8z0IYyp3JUABDeXycaepYzH0WvM3mCKVter+fqiiIokAKXpUXvTECwHzuY8Q4U1nWlOxT9u8QBjZ8ZvXdtkTx+gBTFb36lzUyMJ97fYPQ6T+q1Pa+S9NURMuAv1P2kFNTVn2brgMlm6xUlB0y1qbJ2IHLrL6bpqXNKkFXne4nK9Mppyje6enJeF1Ign+8S0ESXpfLgE9WspLyRMLr1IPNJisNrXcpy6vnjjAe9xOwTv9RwqvR2SRLVkTD87pcBnyykhRUZn1egVOglAT/VMJr4NsIJMG/4xWA+vg4a6RjPvd7PwsArdYSXtXOJjZZOeRUFyXjg53+o8kE/6KoKZXwOh478CS8biW82rbZ3SHK2HR5tYcGgnbju3RNxVLig7tdRm3V98EhXvO8ouzI2zSdunCYlWpdt7TZpad7PR//2HVHmEh4vbzcs7wahoYlw6sAYbtNoZx4FaeFsSoEDF3BdzwTwrV0YRsaiqTCH/7LW/hmXCDyRvSJX/o9nJ2dCQAQ13evERF946tfEp/99V/BN1YHCAJe+b4lXnw+oP97e4tDWqEhQt0S2pbQkoA50jBbBuhZCmCzOpBUJT0KWfCSQ05bmUr6wofJBf8hlfSxA5cN/kOq0yamXfC/OW5YJT0K+eC/S2nH6ZOJTnVa54L/E1SnHc6ZhlTS3RHGkuC/utjJVdKjgN92vk3oIFNJj0JoXPAfUkmf+7C44D+kku5bCGTONKCSPlsEfI9qfaSEE/9VFSzOJLw+QSWd47Uqa7ocUEn3JMnK5cNYqpI+7YJ/j9dBlfSzAApTWRlSSZ9HAZ+s3IXXAZV02zUxmXo8r0Mq6TJe44QOzManQV6zktaXEvHfuQeLDf4NXT7keXV9C6GE16vzHS9WbeqYSRYMg7xGIVSurJ4UtJEJcC8CjJi+W1XWdHmxA3GyZqENnym9ti3R5XmMmpM1swzMFjJeD8g4sWpNxSIKoHALhkNG8Wkzl4DoVsdK1xdcRmN4rilMDfjy12N88bULAMCDwKJP/cQr+LXf/2Ooqvr4RHj95PsN/uR3fhF/8Q//jrohslTCCxMD3z8bYWyr0PTuVlJVVzGOJhDc7rQ4oSOn/K4ITKMxNM6ZspK2F3FvDACEcx8jhwv+Da0fbtFyzuRb8CecMxFtzmNUnDilqWMShfzutPWBUk5MVVUwPRtDZVaqeZJTLFHSHi9DmNxKtaxpfb5lwXNDB27IOFNLtD7fouacyTIwXoS98y9Ap9CcJ4wzaSqmZ2MWvPSQ0Z5R0hZCYLwMYXDBv6hocx6zwd+feLC54N+0tH64RcPJ7jgmwjmTgBFoexmjYJMVDZOzMb+bcoDXSTTmk5UBXoOZD4tNVgZ49Sz4bPAn2l7EKLngP8DrYXOkhFN+V068cpl/UlB8tWNtGi8CmFzwrxraPNywwd8JbHhjJgEb4NUYGRgvJRPaak+ZJPhPH0xuxSsEMFmGMLgeVVHT5nzL8uqNXThc8G9aWp9v0XDB3zYRLtgFA20vdyg45Xf95INMspLsUjps+QXD5IzntcxL2lzE4LaIBlMPFtN3a+qWNg83bLJiuSMEM1/C6w5l3vdB3dAwicbsnHHYHilhBLgVRWByNmGTlTwtKL58h1cF1F0VCoIbeihJFa9fHPGVN/d4Oy6gqQKvfOKj9LGf+0289PIPPZoEAWYivH5e+9s/x2/89u/Sm+/aMkzU/R2FIASWwZ5lKaqGjsy2bgDwLZ2VfKpbon1WshmlbaiwmBdLRLTLKnaLtqEp3R1yDHiHvGLPCqqKQGDprAPmVUMJY5M42cQdKaialg5ZxW5NdkwNI+bFtkS0S0t2i7bZXQ7LFF5B+6xiz151NhnsCigta2IUrCEEEFgGu/urrFs6MAEZANyRBpNJBprTu+VsGukqHKa8RADts5I9AqOpAr5lsMckkqKmnAlEyskmbhIs6oaOzJlJQM5r0xLtbs0raJeVLK+6qsC3eF6PecVK9SgKpD54F17rpqX9XXjNSjAygIM+eBdes7Km9Ja8ViebuEfGa3t6t99uXn2pDzZ0kPDqjXR2x/AQr5ahwpbwus9KVi5rgFcc84pYXgUQ2Lfn1ZPNGTJeBeAaOixDFULp8n9VAb4zdPC5V1+lZ1/8KSwWi97Pk06EbdvSv/3TP+Krf/1n+J/Xv4a3ztfYpgX2RQvf1rtS0Y2hVd3SjsnGAcAd6VJnipOSPUxs6io8SfDfpSXrTJqiILB5Z0qKmjgBVgGB0OHBK+qGDhJn8i0dhiT4x0nJam9ZhsY7E4HitGTFRXW1swkMeIe8Iu6soCI6m2TgHSUTWmAbUvBipswGALapSZyJTjbxyYpvsTbRPqtYkc4hm7KyZp0JAELbkCYrO4lNT4tXVREIbfOWvAKhY0qTlb3EB72RDpOxqWm798RK1Ogqm4ARdTbVDK/aiVcu+B/zig3+QgiMbUmyUjXSBMy3DDb41y3RLinYid02NNisD8p5HfDBO/LaUMJUpQC5Dw7x6phd8L/5/4O8aio8SQJ2F17ToqZUwmvgmNCeAq+WocIfGWLmGliMHbzw/HN46Yd/Gi/8yC+QbjnsChsA/h+NHWeCutVmCwAAAABJRU5ErkJggg=="/>
+        <image id="_Image3" width="400px" height="475px" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAHbCAYAAADlIMxjAAAACXBIWXMAAA7EAAAOxAGVKw4bAAARFUlEQVR4nO3d264tx1UG4NHVPdfa3geftr3j2HJibGEHHCJCIkDJU+QKJJDgSbjLC8AjIe64JBABiUAhHBxyDt5rr57dxYX9AN1D6p41W9/3AKvr17r4VVWzR3d/892/qrGxT8933dbPeDg8kGMhOZaTYzk5ljtKju47f/zx5kH+4Qff2zzIVz+QYyk5lpNjOTmWO0qOYfMn7ESOtsjRFjnacoQc4xRRLr0IAK7PNEcMc43oDlCHm+8HdyJHW+RoixxtKefp0ksA4Bo5wgIgRYEAkDJEHOMXAUfIECFHa+RoixxtsQMBYLWuixhqHOMXAUfIECFHa+RoixztuOkjylG2UgDsp+sihqEc4zzuCBki5GiNHG2Roy2luAUBIGGoRziMiwg52iJHW+Roy1FyDIfZS8nRFjnaIkdbDpLDARYAKQoEgNWmWYEAkDBOn1+iH+I47iCXUnI0Ro62yNGUYZw/e6Pw6h2iBUOO1sjRFjmaUo7yczIA9uUOBIAUwxQbI0db5GiLHG2xAwEgpfvG+2/W2+HSywDgmsw1onv31YgHp20f9PEHH2++Y/veD763+e8a5FhOjuXkWE6O5fbIUXqHWAAklNMR3gEBYHe73H7Ug7xsIkdb5GiLHG3ZI8c+1+fdQV67lKMtcrRFjrbskMMNCAApCgSA1Wrd7Q5kj6dsT462yNEWOdqydY77abc7kF2esj052iJHW+Royw45yjht/xAAjqdM86WXAMA1cokOQIpL9BXkaIscbZGjLVvnqOESfR052iJHW+Royx6X6Ns/AoCjKUWBAJAwlIhylLEvAOxruNlhnLtLqbbI0RY52iLHcsMuO5Cj7HLkaIscbZGjLS7RAWiVAgEgRYEAkOJN9BXkaIscbZGjLVvnOBvnvpIcbZGjLXK0ZeMcc0R03/zoaR22Psiadjgp63cYKyzHcnIsJ8dyciy3cY67c8Tw2tM342bjfcg//9P3N+/0D3/nK5tvPOVYTo7l5FhOjuW2znE3Rgy1xmdjFa/dETJEyNEaOdoiR1P8CguAFAUCQIoCAWC10ikQABJOQ8RQaz3EizP1CCFCjtbI0RY52tFFxHAavDXTFjnaIkdb5GhJcYYFQIb+ACBFgQCQMlz/Vc5n5GiLHG2Roy1HyWEHAsBq50mBAJBwniPKfJS9FAC7KufzpZcAwDUqNiAAZAy1HuMbwEfIECFHa+RoixxtcYkOQIoCAWC1LhQIAAmfj3M/xnncETJEyNEaOdoiRztKF1GG/tLLAOAald4hFgAJ3R9+9MYBNlMA7G34+c/+d/NPY73/21/ZvKR++C/fl2MhOZaTYzk5ljtKDgdYAKQMezzkCB+Qj5CjNXK0RY62bJ2jVjsQABLGyTBFAJLKaJw7AAnDHh+UcqbYFjnaIkdb5Fj6992BAJCkQABIUSAApCgQAFYbyk4vEh5idnGEHK2Roy1ytGXjHKWLKMUeBICEcuN7IAAk2H8AkGKY4gpytEWOtsjRlj1y2IEAkKJAAFjNKBMAUsZZgQCQUGtEGadLLwOAazRMc8Rp43dB/KqhLXK0RY62yLH07zvCAiBJgQCQ0n31vdfqg9OllwHANbkbI4ZXXn0tbjd+H/1H//7DbtsnRLz75fc3P7iUYzk5lpNjOTmW2zrHODnCAiDh1EeUbvOuBeCIyskeBIAEOxAAUgYvzbRFjrbI0RY52uIAC4AUBQJAigIBYLVxcgfSHDnaIkdb5GjHNEeU+fpzAHABvgcCQIo7EABSFAgAKQoEgJTPfoV1hIv0A/yqISLkaI0cbZGjGV1nBwJAwk2vQABI6LqIcuovvQwArtFQunqMK5BDpJCjNXK0RY62OMICIEWBAJCiQABIUSAArHaeI7o/+OCVY9zmALCbuzFi+OUvf9mVbtsHffGddzcvqf/68Y82TiHHGnIsJ8dyciy3dY7TaJw7AEnlACNZALgAl+gApAx7POQI3/+NkKM1crRFjrZsnaNWOxAAkhQIAKudBgUCQELpIsqgQgBIGIYdvgfiUqotcrRFjrbIsZz9BwApCgSAFAUCQIoXCVeQoy1ytEWOtmydY/YiIQAZ53NEOUbXArCnGhFlPF96GQBcozLbggCQ4BJ9BTnaIkdb5GiLabwANEuBAJCiQABYbegVCAAJfYkYSrfDkw5yKSVHY+Roixxt2SFH97X3Htc9OgSAYxkeP3q8+UN+8sl/b95Rbz57a/O6lWM5OZaTYzk5ltsjhzsQAFIUCAApg7cu2yJHW+RoixztqGEHAkDCeFYgACTMNaKcp0svA4BrNIxzjf4A+5AjnClGyNEaOdoiR1sOUB0AXIICASBFgQCQokAAWK10EUPUeojhk0e5lJKjLXK0RY523AwRpTOKF4CEMjjEAiCh7PJBKQAOx/4DgBQFAkCKce6NkaMtcrRFjrbYgQCw2jgpEAASpjmiTMfYSQGwM98DASBlqEaZNEWOtsjRFjna0n34xYf1wenSywDgmtyNEcPz55929bztg1597enmdfuLn/9083fq5VhOjuXkWE6O5TbPMfgVFgAJXRcx7PGgGsc475OjLXK0RY62bJ1jKHYgACR0XUQ59ZdeBgDXqPT2IAAkqA8AUhQIACm7/ArrEK+6R8jRGjnaIkdbdshhBwLAatOsQABIGKeIcpTdGgD7Gu6niNuNb0KOMnlSjrbI0RY52rJ1jlrtQABIcgcCQIoCASBFgQCQss8494NctMjRFjnaIkdbts5hnDsAKX0xjReAJN8DASDFHcgKcrRFjrbI0ZY9cjjAAiBFgQCQ0n38zu0x9msA7GauEcPN7e3mD/rNr3/Vbf2Mx09e3rwI5VhOjuXkWE6O5bbO8eLsCAuAhFojhhdjjZt9Pmy7Kb+caIscbZGjLUfJYZw7ACmOsABIUSAApAy11jjCMdZRzhTlaIscbZGjLXYgAKxmnDsAKUMfUcrmr8wAcERlOMA7IADsb4ha4wjXOUe5lJKjLXK0RY62uAMBIEWBAJCiQABYrYYCASBhPH9+iX4IcrRFjrbI0ZYD5JhrRDnPl14GANeonKdLLwGAa+QOBIAUBQJAinHujZGjLXK0RY622IEAsFrpFAgACachonv/jaHenC69FACuzdDFuZvO2z7k9vbB5gd+L17cbf5lEzmWk2M5OZaTY7k9cvigFAAp7kAASFEgAKQoEABSdvki+lFempGjLXK0RY62bJ3jPNuBAJBwniPKfIyyBWBn5X7jd0AAOCZHWACkKBAAUhQIACkKBIDVOuPcAcg4FQUCQELpIsrQX3oZAFyjMtiDAJCgPgBIUSAApCgQAFIUCACrTca5A5AxzhFDrZ+9UbilGseYGS9HW+Roixxt2TpHrRHlftr0GQAcVPfe074+OF16GQBck7sxYih9H2Xjt9HH+/uND8kiTjc3m+875VhOjuXkWE6O5bbOMU0u0QFIUiAApCgQAFY79QoEgIS+RJRehQCQUE6+BwJAgv0HACkKBIAUBQJAigIBYLVaFQgACfdGmQCQUWtEGY1zByChTPOllwDANXKEBUCKAgEgRYEAkKJAAFitLwoEgIRTH1G6zb/+C8ARlRvj3AFIsAMBIMUdCAAp3Vfe6uulFwHA9Rmmadr8EOt0c7N5SY3393IsJMdyciwnx3JHyeEIC4DVjHMHIGWeTeMFIMn3QABIcYQFQIoCASBFgQCQokAAWK10CgSAhNOgQABI6CJiuBm2f1Ctxxi3JUdb5GiLHG3ZI0cpxrkDkOAIC4AUBQJAigIBIEWBALDaeVYgACScp4gyH+MXawDsrIznSy8BgGtUbEAAyHAHAkCKAgEgRYEAsFoXEd17T/v64HTppQBwTeYaMXSlRLfxPuQ8jpuPbBxOp81/DyDHcnIsJ8dyciy3R44yOMQCIKEM/aWXAMA1sv8AIEWBAJCiQABIUSAArDZXBQJAwv3ZMEUAkoxzByDFB6UASHEHAkCKAgEgRYEAkKJAAFht6BUIAAlDiShl84nxABxRuRkuvQQArpEjLABSFAgAKQoEgBQFAsBq1Th3ADLuJwUCQEKtEd37b5R66i+9FACuyd0YMYznues3fpmwH4bNh8ZP5/Pmr0TKsZwcy8mxnBzLbZ2jFEdYACQpEABSFAgAKQoEgNXcgQCQcuojSmecOwAJ5cY7IAAk2IEAkOIOBIAUBQJAigIBIEWBALDaaJw7ABnzHFGm+dLLAOAalXG69BIAuEaOsABIUSAApCgQAFIUCACrdZ0CASDh1CsQABK6LqKcjHMHIKH09iAAJHQffaHUSy8CgOszdGX7Lch0Pm/+2ap+GDYvQjmWk2M5OZaTY7k9cjjAAiBFgQCw2jQrEAASximiVFfoACQY5w5Aih0IACnuQABIUSAApCgQAFIUCACr3QwRQ6014ggX6Uf5NYAcbZGjLXI0o3Sm8QKQ5HsgAKTYfwCQokAASFEgAKQoEABWm6sCASBhPEf4IDoAq9WIKOP50ssA4BqV2RYEgAR3IACkKBAAUrovv17qg9OllwHANbkbI4Z5nrt53vZBfd9vftMyTVO39TPkWE6O5eRYTo7lts5x6h1hAZAw9BGlbN6DABxROQ2XXgIA18gGBIAUdyAApCgQAFIUCACr1VAgACSMZwUCQMJcI8p5uvQyALhG5bzxGBMAjskRFgApCgSAFAUCQIoCAWC10ikQABJOg2GKACR0YZw7AEk+KAVAijsQAFIUCAApCgSAFAUCwGrjFNF9+Kyrl14IANflbowYanSbvwwyz/Pmv/UqpWxehHIsJ8dyciwnx3Jb56g1otyft3wEAEflDgSAFAUCQIoCASBFgQCwWmecOwAZN70CASCh6yLKqb/0MgC4RqW3BwEgQX0AkKJAAEhRIACkKBAAVptmBQJAwjhFlOprIAAklPvp0ksA4BrZgQCQ4g4EgBQFAkCKAgEgRYEAsNrJOHcAMvpiGi8ASd2Hzzo/5AVgtaHW2m39kFLK5iU1z7McC8mxnBzLybHcUXI4wAIgRYEAkKJAAFitVgUCQML9pEAASKg1oozGuQOQUKb50ksA4Bo5wgIgRYEAkKJAAEhRIACs1hcFAkDCqY8Yus1HekXUeoyBv3K0RY62yNGWPXKUm37zZwBwQGWPHQgAx+MOBIAUBQJAigIBIEWBALDa/VmBAJAw14hyNo0XgIRy9j0QABIcYQGQMnRdV4/wMmHXdYeYPyBHW+RoixxtGfZ4SK1184ra4x8ix3JyLCfHcnIst3WOWh1hAZBQOgUCQMJpUCAAJHQRUW52uQUB4GhKOcAvsADYnyMsAFIUCAApCgSAFAUCwGrnSYEAkHCeI8p8iIksAOyt9NFfeg0AXJkHpz7Kk9vTpdcBwJV55fYU5dHJDgSAdR4OfZSbvo/qHgSAheYacdP3Uf7n/6a491lbABYap4iffDpH+enzKT58/cml1wPAFZhrxEdPn8R97aLMtcZPnnfx7qsPL70uABr35HQTnzzvoitdlG996ZWYuy7efPwkjvBtdAC2MU4Rb7/yOObo4o++9HKUv/jWO/Hoto9//OQuvv1bb116fQA0aJojfv+Lr8ePfj3Fo9s+vvO1N6OMEfFnX/9CnKcaf/8fz+Pb778TQ/HTXgA+V/v43Tdejx/+YorzVOPPv/GFGCOiPH8xxdtPX4o/+fqz6GoXf/uDX8SpexDPHj6Kab70qgG4lGmOePbwUTy+fRj/9qsponbxp19/Fm+/9lI8fzHFcDfOEXGO33v35Xj3YR9//Xc/jk8+HeNnz0t8+PTleH4e4z9/8zxOJdyRABzYXCNq/aw43n7yUrw0nOJffzbGo5dKvPfabfzlN9+K04MhPn1xjrtxjv8HxyMRVjZuGgQAAAAASUVORK5CYII="/>
+        <linearGradient id="_Linear4" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(7.65404e-16,12.5,-0.390625,2.39189e-17,225,37.5)"><stop offset="0" style="stop-color:rgb(255,14,0);stop-opacity:0.5"/><stop offset="1" style="stop-color:rgb(255,13,0);stop-opacity:0"/></linearGradient>
+    </defs>
+</svg>
diff --git a/packages/frontend/assets/drop-and-fusion/frame-light.svg b/packages/frontend/assets/drop-and-fusion/frame-light.svg
new file mode 100644
index 0000000000..6052ccbaa0
--- /dev/null
+++ b/packages/frontend/assets/drop-and-fusion/frame-light.svg
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 450 600" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
+    <g>
+        <g transform="matrix(0.944444,0,0,0.8125,12.5,100)">
+            <rect x="0" y="0" width="450" height="600" style="fill:white;"/>
+        </g>
+        <g transform="matrix(0.944444,0,0,0.8125,12.5,100)">
+            <rect x="0" y="0" width="450" height="600" style="fill:rgb(255,147,2);fill-opacity:0.15;"/>
+        </g>
+        <use xlink:href="#_Image1" x="0" y="49.048" width="450px" height="551px"/>
+    </g>
+    <g transform="matrix(0.755719,0.654896,-0.654896,0.755719,383.517,-217.265)">
+        <g transform="matrix(0.755719,-0.654896,0.654896,0.755719,-147.545,415.355)">
+            <use xlink:href="#_Image2" x="0" y="49" width="450px" height="551px"/>
+        </g>
+    </g>
+    <use xlink:href="#_Image3" x="25" y="99.5" width="400px" height="475px"/>
+    <g transform="matrix(1,0,0,2,1.13687e-13,25)">
+        <rect x="25" y="37.5" width="400" height="12.5" style="fill:url(#_Linear4);"/>
+    </g>
+    <defs>
+        <image id="_Image1" width="450px" height="551px" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAcIAAAInCAYAAAALeVnpAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAWlElEQVR4nO3df6yd9X3Y8c/znHN/+Ae2uRgMhFB+pCy1PasqatQVJZFGEkpImmmjycRSb5OiKZPWStMmrVs3Tauqav9N6zqWbFVWodTaukGaLm2TaM3UdYqSLZMaAiYjIRQUfti+GGN8r88953me7/54zv0BCVQh59rYn9fLMgbjc557zx+8+X6f7/P9RvyAjh09svMHfQ0AXAhvpFHV6/3LBz56+MDp1eaX/uz06K88fWZ0/alzk7lR01WLw7pcvXtucuO+xWdvWlr83aWdw3959NOPnHjjXzoA/GBm1ajXDOGv/9W3/+rvf/P0P15emdRdF1GiRCkl6hLRVRFVVUUVVdR1xP5dc909b1/6tV986Jv/bHu+XQDYNMtGfU8IH/jokR/50++e/aM/fvKlW9uuxK1LO+Kdt+6LQ9fuip07BjGYq6ObdLFyvo1Hn1+JP3niTDxx+nwM6ireffPeJ378hj13Hv30w09t/8cAQDbb0ahXhPATHz74sc8eX/7EibOTQR0Rf+P2a+Inf3Qp1iZtjCclulKiROkrW1UxP1fFwtwg/s+3Tsdv/9+T0UXEgT1z7YcO7v/4x3/n+G9eyA8HgMvbdjVqI4QPfPTwW/7zn5586tmXJoPbrl6Mj/2lt0Q9X8fqqI21to2mKdGWiNKVqOoqBlXEcFjFwmAQOxcHUda6+A9feSYePzWK6/cuNB/58f03Hf30I89cjA8LgMvLdjZqsH6RW/fv/trDz527+pardsTff+/NMeraePl8EyvjNkZrbYwmJcZNF5O2xGT6a9N00ZSIpi0xnK/j3bftj//3/Ll4+syo3jU3/MCXn3zxNy7exwbA5WI7GzWIiPiNew/+8888cuqvVVHFL9351ljrSpxbbeP8uI3za22M2y4mTRdNF9F0XbRdRNt20ZYSbVuiRETpIqKKuP0tu+JLj5+JP3txdNUvv/eW7g+On/qfF/PDA+DStt2NGhw7emTff/36yd8/u9bWP3/7gbhu/844e77pLzBu+7I2/dxr15UoJabzsBH9Sp2IrvQXKBGxc8dc7N8xjK8/ey6eO7v2rl+5+9Zff/DrJ0YX80ME4NJ0IRpVL58b/8NTK5PBjVcuxDvethSrozZGky5G0ws07eYFui62/Ox/v5kOQ0fj/nXnx2381NuW4sYrF+LUymSwfG78Dy7uxwjApepCNKp++sz459ou4o6b98a47WKtbWMyaaPptl4gopSqf05j/Uep+otNL9R0XUwmbYwm/TD1jpv2RttFPH1m/OGL/UECcGm6EI2qnzk7emuJEgev2R1rky4mkxJNF9N51f4CEf3Dilut//P6g4xt279uMimxNuni4IHdUaLEM2dHN1zoDw6Ay8OFaFR9eqVZKF3E3h2DaEsXTdf1hY2IUr7/BV59oVIiuujL23Yl2tLF3h2DKF3E6ZVmcTs+HAAufxeiUfULq01dIqKaH0a7fqOxK/0Km9e5wPdcaMucbNuVqObrKBHxwmpT//AfBQAZXYhG1aWU6R+drrjZ+sLy+hfYuND0z5VYX6nTP9sf073fAOCNuBCNqqPqh42l9DcXS/fDhat00/cpfblf/3wLAHgdF6BRG9OWJdZX3FRRyhurV79qZ/N9AGAWtrNR9Z8zvfrDMzMKwBt1ARplIQsAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKkJIQCpCSEAqQkhAKltSwjL+o8qomzHBQBIoURMW9L/2A4zD+HWL7Sa9ZsDkM7WlmxHDGcawlKmX2BXRemm/1yMCQF4g0qJUkqULiK6avpbs+3KzEK4XulSyubUaPRDWgB4I9ZvsW10ZRrBWY4MZxLCUjYrXUoVXSnRtmX6HVRx7OiR+VlcB4A8jh09srjekbYt0U0bsxHDGY20Zn+PsJTouoi2i9i7cxglSoyb7tCsrwPA5W3cdIdKlLhy5zDaLqLrZj8tGjHLqdESUaKKEhFdRHSlxL6FYXRdiZVxe3hW1wEgh5Vxe7DrSuxZGEZXSnSxPk1azXT5yWwXy0SJrut/Ttourtk9FyUillcm98zyOgBc/pZXJh8oEXHN7rmYtN1GX2a9cnSGI8J+VU9XIpq2RNOUOHTdrihdxLeWz79nVtcBIIfHl8/fWbqIQ9ftiqYp0bQluhKbTyXMyGwWy8TmKp6u66KdjghvO7ArSlXi+ImVqz75kUN3zuJaAFz+PvmRQ+997MTKVaUqcduBXTFp+7Z0XfeK5szCDEeE/ZxtFxFt2/XD2KrEHTftjXHTxZcef/GhY0ePDGd1PQAuT8eOHhl+6fEXHxw3Xdxx097oqn5w1bZdf5+wzG7FaMQ2PFBfuoi2REzaLtYmXdxz+OrYszAXj51c3fPYidWHZnk9AC4/x0+ufOaxk6tX7FuYi3sOXx1rk35w1W7DtGjENm2x1nb9PcJx00UTJf7mOw5E23Xx+eOnPnj/vYfeP+trAnB5uP/eQ/d8/tHlD7RdFz//jgPRxLQlTd+WN/UWa/0T/9Pp0a5E05UYNyVGky6uW1qMO27eG+ebiM89tvzQ/fce+tCsrgvA5eH+ew996HOPLT84aiLeefPeuG5pMUaTLsZN35SuKxvTorMM4szv2ZXSnzrRT4+2MZhUsVpF3H1ofzzxwvl4+vRo4VNf/e7v/tP33vqFg9ft+tn7Hnh4POuvAYBLx7GjR+aPP7fye5/66nfvGjUR1+6dj585vD9W19pYm7Qxadtoy9Yt1ma7d+fMnyMspeqr3UU0bcR40sZo3Ma46+IX3n1jvPOWvXG+KfHZ48t3feYbyy/cf++hn5nl1wDApeP+ew+9/8FvLL/w2ePLd51vSrzrlr3x99711lhruxiN25hMumja/t5gPyKc7WgwYjtGhNHvMdpNl5BOokRVVxFrbZT5iLuP7I+fuHF3/Nb/fj6Onzi3+/FTq3/4kduvO/ujV+/842t2z31p52B4fH6ufiyiM1IEuKzU8+NJ92OrbXPw5LnJX/7WqdV3f/Irz+xpui727RjG33rHtXFg32Ksrk0HUG2ZPkgfm/uMbsM9wm17nKFMt8OJrorxpI3S1f2jFSVi6YqF+Ed33hR/cPyF+PJ3XopHnl3dc/y51Q/WdfXBiOkwtaoiqhLVlu+5qhxlAXAp2Lqys1TTv6x3IfrRXVciBlUV77rlyrj74FUxiRIvj6bToesrRTciOPsp0XXbEsL1UWHEZgxLlOii7TfkbruYH9bxvoNXxfsP748nT56L4yfOx8lz43jpfBNnR01sFLCqHPALcKmZDlzWH32PqkSUiCsXh7F3xzCu2T0fBw/siJuv2R1NV2K1aWPc9AtjJm0bTRtbRoLbNxqM2M4R4ffEMGISEV3XRttWMWm7GDddDAdVXL+0I268emcM6zoGgyoGdRV1VUVdR6xnsNqmDwCA7VFiPYZlI2pt1x/T13RdNG2Jc2uTaKZToM10dWi7sWXn9kcwYhtDGLEZw1IiStVFvX46RemnSJumjbquYjDoYrglfv2vfQKNBwEubetP//XToZtRbKZR7LoSbYnpFmpl4wCHfveY7Y1gxDaHMKL/AKqoNlaTVlVE6Up0XRVNHVG3EXVbR11F1HUfvbqqoppOjW69LyiKAJeGrfHaepBuN/379XuEXTfdNq1bf/Jg85D3V7/Pdrkge39ufCPT0WEfuRJVqaKrIqquv31aTadC16dBX704xmIZgEvDq7dB24jhdIRXpqtmtsav/3MXZhS41QXdBHtrECPiFVGMiKim9xQ3e7f5QfSjQfcJAS4V3y9mm8HbOmLs4/dar9luF+U0iFcOmTenPF9/H1URBLh8bA3fxf3v+5viWKSL8X8AABARUVt/AkBa1Za9Rqvp6s4qysaKTQC4XFRVeUXr1tVRpruZTR9ZqGpDRAAuT1U9bV1V9QszS0Q9qDYfW3/1Q+weVwDgUrfesupVrYuoYlBVUS/tHLQRJdpxu2Vrsyqq6aSph9gBuFRtJK/uA1hX/Tae7biNiBJLuwdtvbRrblTXVZxZaWI4qGI4mMYw1qdMixgCcMmpYn0atF8QU1fVRufOrDRR11Us7Zgb1TfuW3yyrqp49MRKzA8GMRxWMawjhoO6HxluGVICwKVg6y2+uq5iOKj7tg2rmB8M4tHnVqKuqrhx3+J36hv2LR6rq4g/efJMzA8jFucGMT9Xx3BQ93On66dAVOsrbQQRgDen9U5VVdk4xGFQ9SGcn6tjcW4QC8Mq/tdTZ6KuIm7Yt/jbw6Wdw399496Ff/Hk6dHc/3j0hbjjL1wV40mJtmumm6N20Zb+XKj1/eCkEIA3p/UVof1IcFBVMTesY2GujsW5YeycH8YfPbocz780jpv3L06Wdg7/zfC+Bx5evf/DB3/xU1997t89+I3l+Im37IndOwYx3Q886qqKpu2irdbPiJJBAN68qro/+X4wnRJdmKtix0IduxcHcf7cJB56eDkGdRXvu23pF+574OHVjar9k/fd8rXPP3b69uv3LMYv33NzvDxq4tyojdGkifGki2Z6ftT6WVERsXFMBgBcTBtH90W1sTp0WMd0OnQYuxcHsXt+GL/2h0/Gs2dH8f4f2/+1X/3it38yYsteo4ev3f2ex0+unvjO6bX5X/ncE/Hxn74h9l0xF+fHdYwmbUya/jTh/mDdEqV71REbJkwBuICqV+1TXU0DWEXEcNBPiS7ODWLH/CBefnkS/+q/PxUnzk3ilqsW1w5eu/O9m++zxW/+9cMf/OLjp//Lt0+tLnQl4mcP74+7/uL+GDddrLV9CDdOEC4X9rwoAHgtmwtk1qdEq1gY1DE/rOML31iO33tkOeoq4m1X71x7321LP/ex//TIf9t87ascO3pkz/HnV7/whW++8FNNV2L/FfPx0z9yRRy6dnfsXRzE4uIwSjU9TVgHAXgTqKrp4e4lYjRq4qVRG48+fy6+/NTLsfzyOIZ1FXcfvOorb79m5133PfDw2Ve89rXe9BMfPvixLz5++t8+dXptfn06tOvK9CVFBAF4U+kfe+8bVW+ZJr15aWH8ntuW/u7Hf+f4p77v617vTY8dPTI8O2r+znfPjP/2s2dHb3vx/GTX6ZV22JZSdeXPeTEAXCAlIuoqYlBVZWnXoLlyx9zK9XsWv33Dvvn/uGdx+O/ve+Dh5rVe+/8BUsK0MAxkzhwAAAAASUVORK5CYII="/>
+        <image id="_Image2" width="450px" height="551px" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAcIAAAInCAYAAAALeVnpAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAgAElEQVR4nOzda6xs/ZYW9Gf+56Xmpe63ffp0t3ZH2uNJ+mJCIpcPJCSK0AKKUYO3kPCBgNFovHRQIbYBE2I0Kn4xmo4SkUCAgDZ4S0MjEFQwgeYonW6MnXi6zzmr7lWz5qya1+GHqrXevdYcs3bVXo2dhueXdPp99zq1d+33y8iYc4xnWCKCT9nv9/L09IQ4jpGmKb761a9iOp1an/wgERHR32LPNaqqKnzlK1+BMeah+uS0/aCqKvnzf/bH8Jf+6I/gp7721/Czyw3WaQaxLHz7KLS+NOrie3/J34Nf9YP/KP7+X/9b4AbR+/82REREdyjLUv7Cj/+Zlxr1ze0OlrFwzAprEHj4uEZ9+KX/kHz57/puOI6jFkhL6wgXi4X8yA//q/gTP/qnsEhyCAARwHUs9HwXACzLsmAsC8YCvmMQyj/ym/4J/OBv+yF8+ctfZqdIRER/y3zjG9+QP/B7f+ilRgFAP/RgLLzUn+caVVa1dCzgN/2GX4/f8rv+fbVGNQrh//q//Bn5z/+tfwE//pNfRy3ANHTwy74U4B/4jgjf810TOL5rVVmJbVrg//rWCX/xZ3b4qadE8rLCr/7qd+I3/87fh3/wB38jbNtmQSQiop83VVXJj/33/x3+8O/7nS81ahY6+I3fO8cv/bt71ih0YXccPNeov/r1o/zJv/6EZVrCWGitUa8K4R/5/f+e/Ef/4X+Ab8QZLAF+3Xf38Gt/SQ+5ZaE370PEsgSABcC2LXi2jSpN5Y/95Sf8Dz8TAwC+rd/Bb/5n/jn8S7/3P2YxJCKinxdVVcl/+rv+FfzhP/QHX9Wof+qXfzss37PyqkJVCZ5rFEQkXu7RQY3/8f9OXtWoH/qd/yb+8d/xQ81C+Df+6l+R3/qP/Rp845DhS5GD3/69IwSRjaQU9KY9AMaqBJBaYBkLtgUUeSGnXYKuZ3BKKvxn/+cW30pKfLnfwR//c38Z3/7d38NCSERE7/Y//eifkH/nX/ytr2rU7Mt9mLBjnYsKZSl4rlECyHqxB+oaoWte1ahNVuMHvnOE/+bP/YTl98cAAPuHf/iHkSSJ/K5/+h/G1352gy9FDv7tXzlDbgG7cw1/EKGqLetcCPKyRlEJirLG8VTIN7+1v/x7BQS+wa/5ri7+6tMZcSEIl3/D+uW/4Z/9Bf5PR0REv9h94xvfkN//L//zr2qU3fVhBb6VZCVOWYWPapR841sHHE/lpT7VX9SoX/f3DvH1VPD/bk5W+f/8FTzXKCMi8p/8678NP/6TXwcA/PbvHWGX19idK3i9EKVY1qmokBUVsqLGuaiQnEr52W/ukeQ1jlmNY15hd66wy2v8a7/i2xB4tvUH/+f/DT/xo//VL+B/OiIi+sWuLEv5L3/Pv/GqRqXGhol8JFmJ5Fzh4xr1c4sYu2OOc1kjLb6oUXEhsIc9/I5f8e0WAHxco8yf/7M/hj/5p/406uvzVj+ycTjXcEMflmNbeVkjLwRFVaOsahRFLT/3dMC5qJCXgqwSpEWNNK9RGAez7xzin/yBDygrwb/7u383ilPyC/YfkIiIfnH7Cz/+Z/DfflSjoqEHr9fFOa+tU17h4xq13qWyP5wvtaqqX2rUqRSEgwjnQiwvcl7VqNW3fk7MX/wj/wUWSY5p6ODX/pIekryG5bvwfM8qyhplJahFUNeCqoJ8a3lEVlSoBahFUFaCvBJUlkE4CHDKK+tXfXWI7xh28PVdgp/40//1L/R/RyIi+kXqL/3RH3mpUT/4lQGCYRdZVVvnvMLHNSo+ZrLenVAJUAle1ajBMEQNyzoXJU55heca9TObo/zI7/khmJ/62l9DLcAv+7YAp6pG7djo9QKUdY1KnosgIGLJcnvEKS8huPwh1fUPgrEwGocoSrHORYWsBH7ld/VR1cAf/0N/QPb7/afja4iIiD6y2+3kuUb98m8L4I97KARWUVSvalR6KmW5SS8779f/e65Rw2EAr+NYZV0jv77ey0rgl31nV7bHHH/jJ/4PmJ9dbgAAPzDtoISN3qCLsoZVVZcusK4vX2hzSJGcipcvKC//38JsHAGwrLIGikKQFTW+90MXWVnJ//7X/yaenp7+f/xPR0REfztYLBZ4rlG/+u+boTa2VRSCsgaea1SWV7JYH1G/+awAGHR9REHHujzRvHyuKATnvJJvs2rUIvj6t9Zw1mkGEWAeuXDmPcSnyqpFUOOSJgMAcZLJPs7ULzqfdOE4tlXj0h1WtaCSGoELOaQFXNSI4/hv1X8nIiL621Qcx1inGQLPwZcmoXWoalT19UklgLISeVodUSkJaVHgYjjwAVxq2Uc1SpaLPbquQATYpBnM/nypo5PvGAOWZT2/D5RreU2zQla7VP2Sk1GITueyNC81Lh2kCPK8kuM2hgA4nGskCQdmiIjoMcfjEXltwXdty3j2F0WwFtSVyGJ1RFG97QWBjmdjOgoBwJLr88vnGrXeHJEkGVzXvNQoAwh6vgPjOVZ9WUTE8wfzvJTl+qh+wWHPRzf0nhf4L38QLtV28a0dyqoGIKghqOvmFyUiIrplMpkg6ji41JJLIbvWKFltUmR52fiMYxvMJ91LIPbVc43axyc5xOdLIb38BDUEpuu7cGxjiQhELEh9+UBV1fK0PqJWxly6oYdh32+kxohAlosD8rzG5ff7efgvQUREf0f66le/CliwRICPa9R2d0J6yhv/e2MsfJh2YRurUZ/SUyHb3enymFSsVzXK+N710SYsCAQCC3UNWayOqKpmJfM7DibDUP3S602KU1ZCIKi/CAFHp9P5vP8KRET0d6yP86qfa1R8zGQfnxv/WwvAfBLBdZq3CLO8lNUm+Wiq9IsaFfkODJq1TpbrI/KiavzAdQzm4wjWR6cunu3jsxzT5kCNBeD7vu/7bv5liYiIVB/VqPMpl81WnzmZjkP4XvPeYFnWslgn0E4O+q6NwHUs8/YHm22C07lofMC+tpxGaTmTNJfd/qR+uV7godvtMnybiIg+W5GXsl7qGwijvo8o8Bp1pq7l8opPecfXCTvX94/Aq0IYH05yPLa1nF04drPlPGelrLb6VGnUceApbSoREdG9ahHZPu3Urq4Xehj09JmVxTpBUTaHNT3PwXDWB64Xm14K4TnNZN/Scs7GETpe87ZgUVayWOufiQYhfJf3CImI6POJQA6nArWyJuF3HIxH+szKapvg3DJVOpn3X02VGgAoq1r2y4P6m40HAcLAbRS0qhZ5WiWXiLU3wqiD3qjb+hcjIiL6FBGRwylHpTzadF0bs3EEC82Zld3hLB8noT0zloXZrA/7zdNNp6pF4nOht5xRB/1uR2k5L4uMpbrI6GA06QLKlyMiIrrXT//0T6sL87ZtMB13AQvW2xp5THLZKVOlsIDZtAtXeVJpDqdc3RUMAhfjYaB9N1luUmTKVKnj2JjNXrecREREn+Ob3/xm49eMZWE+7TW6OgA4ZaWs25LQhhH8TvPpZl2LGK3l9FznGqTd7Oo2+xNSZarU3JgqXS6XXK0nIqJ3m0578JSuLi8qWa6PykYgMOj56EbNqVIRyP6Uo7E+4dgG82lX7eriJJPDsSV8e3oJ337760VVy0/+5E+2/Z2IiIjuMhp34fvKzEpVXy5QPJCEBoHE5wJVLa8LoTH6i0QASM+FrHf6ruB0HKKjLDJWtVymfZg1SkRE7xANQkTKmkQtIk/rBOWDSWj7dfzy/vGLQmgBk1lffZGYF5UsN/qaRPsiY906hENERHQvzzFtmwiy3CQPJ6HF+1ROxy8au5dCOJj00VFazrKq5Wl1VAO0by0y7hZ7deSViIjoXo5t0PVdQJtZ2aU4nZu7greT0DLZvxmoMQAQeDaCrtJy1tejh0pBC9oXGWW7jpErAzVERET3CoIA/cBVdwUP8VnipHmB4lNJaBslBMZ0HBuh8n4P15ZTjae5tci4T5Em+kANERHRvb7/+78fRhncTE+5bFvyrW8loS1X8cu93Y+ZXuCqv9l6m+Kc6S3nfBKpLecxyeRwaH45rhUSEdGjgiBozqxcTyppbiWhLVZHNQnNc2wYKF3d/nCSo9LVGQv4MNVbztO5kHVLVulXvvIV9deJiIjuVZaVLJaxOoTZ/1QSmvJ007Et9AK3uUeYpJnslK4OuLSc2iJjUVSybAnfDj0HX/rSl9gSEhHRZ6vrWlaLg7qOF/rOzSQ0barUdmz0/cv7x1eFMDsX6otEAJgMAwQti4xP66NaoTuOQdhR3z8SERHdS3aLPUqloD3PrODBJLTRh+FLcMxLISyLStbLg/oicdDtoBc1W87nRcZKWWT0fO955JWIiOizHc+Fuolg2wYfJtFDSWgWLEzmfTgfPd00wBdHD7UrvlHgYjRotpxyY5HRcW0M54PLn0lERPSZ0qyUTHm/ZxkLHyaRHr59IwltMonQeRO+bQTXvLWyWdA6no3pZVfwgUVGg+m8r06VEhER3etb3/qWpMpx3eddwbYktEXLVOlwECIMm083TXwq1Iw2xzGYT/Tw7cMx0xcZLQuzWU8N3yYiInrET/3UT6m/Ph5F8JX5k1tJaN2ooyahARCTK53g5aRST4+nORWyaVlknI4jeMpyfpqmzFojIqKHaEOYg36Arjazct0V1JLQ/I6DiZ6EhvhUNNcnLFiYT3pwnOZz1+zGIuNoGCLUwrdF5Gtf+5r6GSIionuFUQeDQdjcFXyeWdGS0BzTOlWa5qVkZdUshJcXiUrLWdayWCfq0cNe2yIjIIe0wOmkd5BERET38HwXo0lP/dl6m+LUloTWEr59Op7llF+eiL4qhINhpL5IrGuRp7Xecoa+27rIeDwXKHmLkIiI3sE2FobzoXpSaR+f5Zg2Z1ZuJaFl50L268MX/9vnfwh7AXqDZq6bCGSxPqrh2x3XxmysT5XGm6PaphIREd3Lsiz0fbflpFIu28NZ/dytJLT18oCPH28aAHBtg/5YbTlltU1wzpVdQdtg3rLIeIzPkhzSxmeIiIjuZds2BoFeBM9ZKautXmduJaEtlofGzryxjYWe716mZN7YHU5ITko8jYX2RcZTLrvN8eZfjoiI6FO++tWvqo82i7KS5VqvM21JaM/h21XVfFJpBqGnPnc9JpnsYy2e5sYiY17KquXLERERPWI6nbZ0dfFDSWgAZLlOkBf6QI3Rjh6ez4Wsdy0t5yhsXWRcrPTw7S9/+cvq70VERHQvEZHVKkapdHWfTkLTn272A6+5PnE5qXSEticx7Pnohsqu4MsiY/PLubbB93zP97T+xYiIiO4gm9URmRK5dplZeTwJree7sI31+gzTy4tEpavrhh6G/WY8zfMiY6GFbxsL/cBTvxwREdG94u0Rp1Q5GG8sfJh2YSsDNemNJLTBrP/y/vGlEEotsloc1BeJfsfBZKjH06y3Kc7aIqNt0Atc9f0jERHRvc5FJclef103n0RwW5LQlm3h26MI/kc78871/8tuuUehtJyuYzAfRzcXGd+OzVjGwvDDEMZaXn6y+5stfz0iIqJ2eVlLojRbADAdh/CVfOubSWhdH93+6515AwBJViI7KZv515bz0UXGybQHV/lyREREd5NaDkptAoDhIECk5VvfSEILfBejUdT4dXPKSzkr7/csy8J8osfT3FpkHI8i+MqXIyIiekjdnPQE2k8qXZLQEjUJzfNszCZdQJkqNe0tZ4SOp8TTlFVry9nv+eh29XtP6h9CRETURhnc9H0XE6WrA4BLEpo+s9I2VXrKK2msTwDAeBgiDJR4mlrkaZWoU6Vh4GE0UAdqBJXe2hIREd3LdR1MLxcolCS0szyahJZXtSSZco+w1/XRU7q653ia1kXGlvDt47kAhOHbRET0+WzbYPqhr86sHJNcdnFzZuVWElqRl3K8Ltm/KoRB6KkvEgHIcpMiU94lujdaznNRqe8fiYiI7mVZwPDDUM+3zsqHk9Cqspbt0+7lyetLIXQ9B+Op3nJu9iekWjzNjaOH5yRrHXklIiK6V9d31U2E5yQ0bQjlVhLaanFA/dHTTQNcnqGOPgzVri5OMjkc9fDtDy2LjHlWyn51aHyGiIjoEd2OC0/pBKuqlqf1EcqWRGsSGgBZrWIUb8K3jYVr6KjWcp4LWe/0eJrpOERHXWSsZLU8qOHbREREdzMOfGV7QURksT6iqpSpUu9WElqCc6Y83eyHnprRlhdVazzNqO+3LjIul/GrlpOIiOizGDWYRZbrI3LlYLzrPB+Mb0lCS5pPNwHAuEon+MVJpeYHeqHXvsi4ilGUynCMpW5pEBERPWS7TdSTSvYnktB2LeHb6hmmy0mlWA3fDjoOxiN9V3C1TdTzGMayANtVvwAREdG94sNJ4mP7msSjSWhRx4HnmEardn2R2OzqPNfGbBzBUhcZT0hT7d4TMAi96z8RERF9nnOayX6rv66btSahXcK3NdEghH/dL3xVCLfro/oi0TYW5pOodZFxHyvPXS28HD1UvwUREdEdyqqW/VLfRBgPghtJaEc1CS0IO+iNui///lIIk30iidJyGgv4MNVbzlNWyqplkXEw6UN7/0hERHSvqhaJz4W6idCLOuh3O61TpXoSmoPx9HX4tgGArKwkvtFyeko8TX5dZNT0ByECPXybiIjoTiKHU67uCga+i/EwUD+03KTIlKlSxzaYzfqNnXlTVLUkZz0BZjwMEPhKy1nVsmhZZIzCDvrDkEWQiIjepyrUu4Ke62A2iQBlZmV7Kwlt1lNf8TmHtGg9qdSLOtbb71CLyNM6QaktMnZcjMfdxq8TERE9TDnY4NgG41EXtcB6u+MXJ5nslSQ04Bq+7TSfbpZVLUaUMng5qdRsOQWQ5SZBroVvu5ejh9oiI+qSMTNERPQuxliYzfp6+PanktC08O1aZH9SzjB1PAfTsd5ybnYpTspjVGMu1+y1ljMrKkHN8G0iInoHC5jM+upJpbyoZPFwElr9MoTzqhA6jq2+SASAwzGTOFF2BXFdZFTCt4vq8gcRERG9x2DSR0eZWSmrWp4+Iwltt9i/vH98KYTGtB89TE+FbFriadoWGcuiYhEkIqJ3Czxb3US4JKEd1YGaG0lo2K5j5B/VJwNcurrhfABHeZGY5WVr+HbbImNdvT56SERE9Dk6jo1QuXSE55mVsjlQ4zmmNQltv08lfRO+bQAg8h14Wst5jadRp0pvLDKulgdUWvg2ERHRvSyDXqBnVa+3Kc7K8Xf7xsH4Y5LJ/tB8umnCjoOO0gnWtcjTWm85wxuLjOv1ETkv0xMR0XtdDjY0u7rDST2pdCsJ7ZwVsmkJjjFayymCSzyN1nK6NmbjUP1y212K9NQcqGHmNhERPU47qZTJTunqgPYktKKoWp9uhp7TXJ8AgPX2iEzp6hzb4MMkUqdK42MmByWrFABgeIaJiIjeJzsXsmm5JjG5kYT2tD5ClKebHccg7DjNi7n7fSqJclLJWMCHSdS6yLhpCd/u+S5gMXybiIg+X1lUsl4eoIXADLod9KLmzMpzElqlJKF5voeuf2nSXhXC5HhWXyQ+7wq2LTK2TZWGno2O8hkiIqJ7iYhsn3aola4uCtyHk9Ac18ZwPgCur/heCmF+ymW30a9JTEYhfCWe5tYiY9gLEOgjr0RERHcRQA7nQt1E6Hg2piN9ZqUtCc02BtP56515A1zy1rbLvVrQhj0f3VCLp7kcPdSmSv3AQ3/c+8Rfj4iI6Lb4VKhHHhzHYD7pPpaEZlmYzXqNnXmnlsu9J+1FYhR6GPaVeJpry1moU6UOxtMeoIVvExER3asuJVc6QWMsTMaXXcG3petWEtp0HMFTnlSafVqodwX9joPJUI+nWW9TnLRFRttg1nLviYiI6CHKwQYLFuaTnnpSKcvbZ1ZGwxChFr4tIqaqm12d67SfVNrHZzkqU6WWZWE+7alTpWq7SURE9KDJJFJPKl2S0I7qrmCvLQkNkEOqnGGyjcF8qnd1SZrL9qDvCs4m+iJjWYugUpbsiYiIHjAYRghDZU3iZhKa05qEdjwXKOv6dSH84kWiFk9Tymqr7wqOWxYZaxE5KN0jERHRI8JegN4gaElCa5tZsTFrua8bb48vgd2vCuF42lNfJBY3wrfbFhmlFolPBWqeoCAiondwbdO6ibDaJjjnjyWhHeOzJPsvGruXQtgbdxEoaxLVdU1CK2hti4wAZLfco+SrQSIiegfbWNeEsmZXtzucJTk1797eTEJTduYNAPiujagfqieVFqsjyqrZct5aZNxtjsi08G0iIqJ7WRYGoacObh6TXPZxc2blZhJaXspq3QyOMZ5jEHUc7SvIapMiU+JpXLt9kTGOz3JUvhwREdFDjAuj1JnzuZD1riV8+0YS2mJ1hChPN03P9wDtpNL+hPSstJzXo4e2MlWannLZtnw5IiKihygHG4qikuX6CG1o5VYS2mJ1hLouaBsYreWMj5kcWlvOCK4yVZrlpaxaFhlh1I6TiIjoblVVy2J5UGdWup9KQlOebtrGQj/wmnuEp1Mum5aubjoO4StTpWXZ3nL6rg0Yhm8TEdHnk1pkvTigUmZWfO92Etq5JQmtH7iwLLy+R1i0vEgEgFHfR6TF01wXGbXzGJ5jXu49ERERfSbZLffIlTUJ1zGYT6LHktCMheGH4cv7x5dCWJWVrBYHtavrhR4GPaXlFMhifUSpLDK6Hfe5CLIbJCKiz5ZkpbqJYIyFD9Puw0lok2kP7kdPNw1wKWjbxV5tOYOOg/FIbTnlssioPHd1DEbzASwWQSIieodTXspZeb9nWRbmky4cZVfwZhLaKIL/5ummASDxuUCptZzXeBqtoG0PJ+iLjBam8wGMFr5NRER0L6kkUd7vAZeTSh2vuSt4Kwmt3/PR7Tafbpr4XKBQOkH7Gk+jtZyXRcas8RkLwHTaUxcZiYiIHlI1my0AGA1ChEEz3/pWEloYeBgN9KebRluYN9Z1V1CLp8lKWe/0lnMyiuAr4dsAA0eJiOj9el0ffXVmRS4zK21JaGM9Ce14Vs4wAcB00lVPKuXXRUY1fLvvI9LCtwU8w0RERO/mBx5Go0j7kSw3KTJlZsW5kYR2Lio5F1WzEI5HkXpSqaouRw+1HO0o9DDsN89jAJDDKWdDSERE7+J6DiazHvBgElrbVOk5zV7eP74qhL1+oL5IrEXkaZ2grJSF+U77IuMx098/EhER3ctYwOjDUM+3TjLZH/WZlbYktDwrZb88fPH7P/+DH3UwUFrO53iaXAvfdgzmY32RMdknkhUsgkRE9Pks4BKDps2snAtZ707q59qT0CpZLV/vzBsAcGwLg2n/+c98ZbNLcTor8TQ3Ws40ySTeMnybiIjepx966pGHvKhk2ZJvfSsJbbmMUb95UmmMZaHvu2rLeThmEidKPA3QusiYZYVsW2LaiIiI7ma7cJU6U72cVGp+5FYS2nIdoyiVTYnL0UPtpFIh273ecs5aFxkrWS5jNaaNiIjoIVazztS1yNMqfjQJDettooZvG8uC0VrOWyeVxoOgdZFxsYzVRUYYW/29iIiIHiCrVayeVPIc05qEtjucJdHCty1gECpnmMqykuVK7+r6UQf9rrYreDl6qC0yOrYBDC9QEBHR+2zXR5yz5pqEfT0Y356EpoRvW0DPd2Eb6/UZpucXiZWyLBj6DsbDQPtustqk6nmMy9FDXqAgIqL3SfaJJEflYLwFfJjqMyu3ktAGk/7L+8eXQihyOXqovUj0ruHb0KZKWxcZDXq++3LviYiI6HNkZdW6iTAfRw8nofUHIYKPduZfCuFhFSNTWk7nGr7dtsh40BYZLQvD+UAdeSUiIrpXUdWSKCt8ADAeBp+RhNZBfxg2zjAhzUs5JS0t5yRSw7fTG4uMo0kXnhq+TUREdCcROaRF60mlnpJv/akktPG42/h1cy4qOSlBpbAuu4LaSaVbi4zDQYhQ+XJEREQPqXOIUgYvJ5WaMys3k9BcG7NJT01CM0fl/R5wPanUUeJpqlqeWhYZu1EH/ZbwbfUPISIiaqMUmo7nYNo2s9KShGbM5Zq9NlWaFZWoZ5gG/QDdUI+nWayO6lSp77uYtJzHQK0XWyIions5jo3ZrP95SWhK+HZR1RJr9wijsKOeVHppOcvmrqB7Y6o0yUqgVh69EhER3ckYg+m8r+dbnwrZPJiEVhaVxNcnoq8KYcd31ReJALDepjgp8TS2sfBhErW2nCdlv5CIiOheFoDhfABHmVnJ8vaZlbYktLqqZfu0e3ny+lIIHdfBZNZXXyTu47MclXgac11k1KZK81MuR6VwEhERPSLyHXUToSwvaxLqVOmNJLTV8oDqo515AzwfPRyoXV2S5rI9KPE0uLSc2iJjUVSyXe5v/sWIiIg+Jew46Dgt4dtrfWblVhLaen1E/qZJM8A1b035g85ZKautHk8zubHIuFrsIdomIxER0b2MjVA5risCWayPKJWZlVtJaNt9ivSkPN3sB66a0VaUlSzXidpyDroddZFR5JpVqnw5IiKih7QcbFhvj8iUV283k9COmRy08G0AxlM6waq+HD3UTipFgasuMgKQ5fqIvFDeCzJpjYiIHtecWdmn6kklcyMJ7XQuZNMSvt3z3eb6hIjIcqW3nB3PxvRy9LBlkbG5L2hZFmB76hcgIiK6V3I8y/7QXJN43hV8NAkt9Gx0XNt6WwhlvT4iU1YeHNtgPum2LzJq4dsABoELtoRERPQe+SmX3eao/mwyCh9OQgt7AYLr+8dXhXC/TfQXica6rEm0LDJuWxYZu77+/pGIiOheVS2yXe7Vgjbs+Y8noQUe+uPey7+/FMI0Pknc2nJGcJV4miwvW1vO/rgHT/kMERHRvWoROZxydRMhCj0M+35zcPMTSWjjaQ/4aGfeAEBe1XJYx+qXmI5D+Mr46mWRUZ8q7fYChHr4NhER0b3kkBbqXcFOx8FkGKofak1Csw3ms2ZMm1NWtbRdoBj2fUSB3nK2LTIGvofhWA3fJiIiul+Vo6yVrvp5cv0AACAASURBVM6xMRl3IRDr7ePStiQ0y7Iwn/bUqVLncCpaTyoNer71ttY9LzIW6iKjg+m0CyhTpURERA+RZp2xjcFo1IUA1ttm7GYS2kRPQitrEaPtCgYdF5OR3nKutgnOyiFf2zaYT/WpUtTKqWAiIqIHWJaF2aynnlS6lYQ2bklCq0XkkObNPULXtTGd6F3d7nCW5KTtCt4I3y5r3iMkIqJ3G0978JSZleLGzEprElotEp8K1CKvC2Hbi0QAOCa57JR4mluLjGV9mfYhIiJ6j964i0BZk6hqkafPSELbLfcor49WXwqhZSxM5309niYrZd0ST9O2yFiVlcQsgkRE9E6+ayPqh2q+9WJ9RFk9loS22xyRfVSfXgrhcDaAq7SceVHJsuXe061Fxu1ir468EhER3ctzDKKOo/1IVpsUmTKzcisJLY7PcnzzdNMAQNRx0FHWJKrqcvRQK2jdlkVGALJZHlDyMj0REb2HZdDzPUA9qXRCqqz+3U5Cy2W7a4bAmMBz4Cvv9y4tZ4JSGfj0bywybjZHnFv2EomIiO5mu7CsZhFsO6n0qSS0VUsSmmlrOZfrBHnRbDldx2A+jtQvtz+c5Jg0w7eJiIge1+zqTqdcNkpXB3wiCW11hCgDNb5rw0A7qbTVTyrZ15ZTmypN0lx2Slbp5YM8w0RERO9T5KWs1voFitEnktBq5R2f5xh0tXuEcXyWOGlfk9CuSVwWGfUK3fXdy0gqERHRZ6rKWlaLg9rV9UIPg54Svi24vOLTwrc951KfgNf3CE+p/iIRAGbjCB2v+S6xKGtZrhNoY6WBZ6vvH4mIiO4lAtkudqiUNYmg42B8MwlNCd92DEYfhrCuT0RfCmGRFbJZ6RcoxoMAYdCMp7m1yOhHHYSe+v6RiIjoXhKfC3UTwXUMZuPopaB9rC0JzVgWpvMBzEdPNw1wPXq42KstZz/qoN9V4mnkcvRQW2T0Oi4G0z7A8G0iInqH+FygUOqMbZvWmZVj2p6ENp32GkloRkQkPheotZbTdzAe6vE0y02KTJkqdRwb03lPD98mIiK6V12KVmcuJ5X0fOtTVsq6LXx7FMFXwrfN4VSodwU9z8bsclewOVV6Y5FxPuvBGA7HEBHRO9V6MMts0lVPKt1KQhv0fXS18G2BGK3lvBlPk2RyOCq7ghYwn/TgOMpwjDBsjYiI3m88itSTSreS0KLQw7AfqEloh5NyhsncuOKbngtZ7/RdwekoQkcL366FZ5iIiOjdev0A3W5zTaIWkae2JDSvPQntmF3eP74qhJalv0gEri1nSzzNsO8jUsK3Ra5nmJQhHCIionv5UQeDUaT9SFabG0loEz0JLdknkhWXJ6KvCuFw3FVfJJZVLU+ro1rP2hcZL0M42vtHIiKiezm21bqJsN6lSM/KruCNJLQ0yST+KATmpRB2hxEireWsL2sSWkG7scgo+1WMQmlTiYiI7mUsC33fVWdWDsdM4qR59/ZWElqWFbJ9E9NmAKDjGHSHkfoicblJkCvxNJ5rty4yHnYpzkpMGxER0d0sC4PQU4tgeipku9dnVtqT0CpZLuPGzrxxbfOct9aw3qY4ZXrLOZ9E+iJjkslhr+9wEBER3c246l3BWyeV2pLQ6lpksYzVJDTTDy6ho29/sI/PckybLaexgA/TlvDtcyHbjZ4MTkRE9BDlYENZVrJcNbs64POS0BzbwGgtZ5LmsrvRcmqLjMWNRUYYZo4SEdH71LXIchmrMyvhjSS01SZFpoVvGwv9QDnDlGWFrFu6uskwaF1kbAvf7jg2YJr7hURERPcSEVkvDyjK5prE88wKlKeb29YkNIOe78JY1uszTGVxfZGofIlBt4OeEk9Ti8hinajnMVzbQi/Q3z8SERHd67CKkSkFzbENPkyi1iS0vZKEZlkWhvPBy/vHl0JYV5ejh1pXFwUuRoNmyynPU6Va+LbroOd7AC9QEBHRO6R5KSftYLwFfJhEevj2jSS00aQL76Onmwa4FLTtYo9SaTk7no3pZVewGb69S3HSFhltg9GHgbrNT0REdK9zUckpb9amS751tzUJbdGWhDYIEb55umkAyPFcoMj0lrMtfLt1kdGyMJn1YWvh20RERPeSWo7K41AAmAwj+Eq+9a0ktG7UQV8J3zZJVqoL88Zc7z1p8TSnQjYtU6XTSRee8uWIiIgeUjWbLQAY9AJ0o2a+9a0kNN93MdGzSmFOykjpczyN6yjxNHnZGr49GoQIguaXY+o2ERH9fIjCDoaDZlcnN5LQXMe0TpWmWSmN9QkAmIy76kmlsqxlsU7UqdJet4O+Fr4NCCqeYSIiovfp+C7G4676s1tJaG3h21lRSZqXzT3C4SBUTyrVtcjTuiV823cw1u89SXwqAGlWaCIions5roPJrK8OYbYloVnXJDRtqjQ/5XK8Fs5XhTDq+uqLRBHIYp2guBG+Db3lRK5MohIREd3LWMDow0Dt6pI0l+1BP/Iwv5GEtl3uv/j9n/+hE3gYTfSWc7VNcFbeJd5aZEzjk5yU/UIiIqJH9HxX3UTI8lJWW/3Iw60ktNViD/no6aYBLs9Qh7MBoHR1u8NZkpMST3NjkfF8yuWwjm/+xYiIiD6lF7jqkYeirFpnVtqS0ESuWaVvnm4aY11CRy31pFIuu1jZ5seNRca8lPWSRZCIiN7JOOgoneDzmkStzKy0JaEBkOX6iLxoPt00/eASOvr2B+eskPWupeUcha2LjNrRQyIioocpBxsuJ5VilMrMyqeT0JpPNy3LglFbzuLScmqGPR/dlqnSxSpGVSsTopa6pUFERPQIWa+P6kmlTyahaeHbAAbaGaaqqmWxil+9SHzWDT0M+81dQVwXGQtlOMY2FmB7bX8pIiKiu+y3CdKTcjD+uivYloS2bUlC6/qX94+vCuHzi0Ttiq/fcTDRdwWx3qY4a/eeLGAQ8gIFERG9TxqfJD40C9plZiVqSUKrWpPQeuMuvOtnviiEAlkvY/VFousYzMfRY4uMxkIv8NT3j0RERPfKq7p1E2E6DuF7bUloR3WqtNsLEPXD12eYAOCwiXFWWs5b8TS3FhmHswEc5TNERET3Kqv2CxTDvo9Iybe+nYTmYTh+Hb5tgMu9pzRuazm76g7HOWtfZBxNuuio4dtERET3EjmcitaTSgMt3/pmEpqD6bQLvHldZ7KykkQJKgWA6ThCx1PiaW6Eb/d7AaKuOlBDRER0vypHrVTBoONiMtJnVtqS0GzbYD7Vp0pNrKTGAMBoGCIMlHiaWuRpdVS/XBh4GLaEb6t/CBERURulzriujemk2dUB7UloN8O3y1o/w9Tr+uh39XiaxeqoTpV2PAfTlvBt1DzDRERE72PbBvNZX51ZOaaPJ6GVtcjhlDf3CAPfaz2ptNykyJRdQedGy3nKS0HN8G0iIvp8lmVhOu+rXd0pK2XdFr7dkoRWlbXE1wHRV4XQ8/QXiQCw2Z+QaruCxsK8Zao0L+vW949ERET3Gs4HcJU1ibyoZNmyJnErCW272OF5qPSlENqOjcm8r3Z1cZLJoSWepm2RsciK1pFXIiKie0UdR91EqKrLrqCyJXEzCW2zPKD8aKDGAJcXiaMPA73lPBey3unxNO2LjJVsF3tOyBAR0bsEngNfeb8nIrJYJyirZqXxvfYktM3m2EhCM8Dl6KHj6i3noiWeZnRjkXG1OKBWBmqIiIjuZtmIOo72E1muE+TKzIrrGMwnLUloh5Mck+bTTdP1XbhKJ1hWtSxWR3WRsRd6rYuMy1WMkpfpiYjovWwX0GZWtvpJJdtY+DBpT0LbKVmlAGC0lvP56KEaT9NxMG5ZZFxvj8gy5b0gk9aIiOhxzZmV+Cxx0r4m4ajh26WstvrTzaijnGHC9YqvdlLJcwxm4wiWush4kkQL3wYAwzNMRET0Pqc0l+1OL2izTyShaUMrgWcj8OzmxdzN5oiz0tXZN9Ykjkku+5bw7X7oXRZAiIiIPlORFbJZ6RcoxoPgdhKa8nTTDzsIvcv7x1eFMN6n+ovEazyNFr59ykpZ7/RFxq7vqO8fiYiI7lXVctlEUIZW+lGnPQltrSeheR0Hg1kfeH5o+fyDc3KWfUtBm40jeMq7xOdFRk13GKHjND9DRER0LxGR+FyomwiB72A8DNSPLTcpslxJQnNsTN/szBsAKKpa9i0t52QYIPCVlvPGImMYddAdRiyCRET0HnI4FergpufZmLXkW29vJaHNejDm9ZNKp6ov1VZtObsd9KJmy1mLyFPbImPHxWjSu/1XIyIi+pS6QKF0go5tMBl3AQvW29IVJ5nslSQ0WMB80oOjPKk0+zRXdwXDwMNo0Gw5BZDlpm2R0cZ02lMXGYmIiB6iHGwwloX5tPd4EtooQkcL365FjHZX8HJSKQS0RcZditO5GaRtbIP5tKdOlUKU1pGIiOgBlgVMpz31pNKtJLRh30ekhG+LtJxhchwb82lPDd8+HDOJE31XcD6J1EXGoqoFFcO3iYjofYbjLnxlZqWsanlqSULrtiahXV4LVrW8LoRfvEhsFsH0VMhm3xa+HaGjhG9XtcghZREkIqL36Q4jRN1mQftUEtqkJQntsIpRXB9WvhRCy7IwmffVF4lZXsqypeVsW2Ssq/oyhMMbFERE9A4dx7RtIlxmVkplV/BGEtphl8rpo5i2l0I4mPbQ6Sgt5zWeRitntxYZt4u9WqGJiIju5doGXd9Vf7bepjgpx99vJaElSSaH/eudeQMAoWfDj/SW82mtt5zhjUXGzeqIQgvfJiIiupdloR/oFyj28VmOWr71jSS087mQzaYZAmN810agvN8TgSzWCQqt5XTbFxl3uxSnVNnhICIieoTtqYObSZrLrmVmZd6ShFZck9C055SmveVMkOXNltOxDT5MIvXLxcdMDrH+5YiIiB7TrDNZVspa6eqA20loT6sj1HVBx4aB1tXt9ZNKxgI+TKLWRcZNy3mM63FFIiKiz1YWlSxXB7WrG9xIQlusE1RKQo1rW+gFyj3CY5LJXunqno8eti0yLtd6EQw7DmAxfJuIiD5fXdWyWhzUk0qh76pJaABk1ZKE5rg2epcnoq/vEWbnQrZtLecohK/E05RVLYvVUc0q9V2DUHn/SEREdC8BZLvYoyybBa3j2Zi1JKGtdynSliS00Yfhyyu+l0JY5qWsl3rLOez56CrxNLcWGTuBh6jDR6JERPQucjwX6iaCYxvMJ93HktAsC9NZH/ZHO/MGuDxD3S72asvZDT0M+0o8zY1FRtd1MJwNAKVCExER3SvJSrXOmOuuoP1gEtpk0oX35ummEYHEpwKV0nL6HQeToR5P07rIaBtMP/RhaeHbRERE96orOSnbCy8zK0q+dZZXrUloo0GIMGg+3TTxOUepdILudVdQO6nUvshoYdZyHoOIiOghtR7MMhl31ZNKlyQ0fVew1+2gr4VvA2K0ltM2l+euajxNmsv2cG58BgBmky48bThGm6QhIiJ60HAQqieVbiWhBb6Dsf50U+JT0VyfuHR1XfWk0jkrZbVN3/4yAGA8CtVFxlpEUDe7RyIiokdEXR/9fvDzloSW5iXysmoWwumkq55UKm6Fb7csMopA9mkB9UgUERHRnbzAw2jSVX+22iY4q0loVmsSWhqf5JRfZmNeFcLhKEKgvEisammNp4mClkVGgRyzAlXdrNBERET3so2FUcsmwu5wluTUfJdorMtAjTazcj7lcljHX/xvn/8h7Afoqi3nZVewVOJpOp6N6UhfZDxsYnXklYiI6F7meoFC20Q4prns4ubMioXrzIqWhJaXsl7Gr37NAJcDhv1xT/sOstykyLR4mhuLjPHhJCnDt4mI6J36gQuj1JlzVsi6ZWZlMgoRKFOlVVXLchk35jeNY16OHjY+tN2fkJ6VltNY+NC6yJjLftsSvk1ERHQv21PvChZFJYuWfOtbSWhPq1h9XWf6oaueso+PmRyOzbuCl0XGqGWRsZT1Ws8qJSIieojVrDNVVctiFUOUNYkocNUkNFyT0Arl6aZtLBit5bycVNJbzuk4hK9MlZZlLctVs+UEABhb/b2IiIjuJSKyXMbqzIrvOZiOIvVz622Ks/Z00wIGoddcn8jzUpYtXd2o7yNSpkrr61SptsjoOQYwDN8mIqJ3kfUyRl401yRcx2A+eTwJrRd4MJb1+gxTVeovEgGgF3oYaPE0Almsj+p5DMdY6PkewPBtIiJ6h8M6xvnULGi2sfChLQnt1J6ENpwP4Jg3Z5ikFlktDuqLxKDjYDzSw7dX2wRZrjx3dWz0Alet0ERERPc6F5W6ifAcvq0moeWlrDb6K77huIvOR083nwuhbJd7FErL6TnmEr79yCKjsTD6MFBHXomIiO6Vl5UkyqUjAJiOI3S85q7gzSS0XoDum6ebBgCOWYG8peWcT/WW85i0LzJOZn04Li/TExHRO0gtB6XZAoDRIEAYNPOtX5LQlJmVMPAwVMK3TZqXkhXNx6GWdSmC2g7HKStl3TJVOh530VHCt4mIiB7ScoapF/n6SSWRy8yKmoTmYNoSvm3SlpZzNon0eJqikmXLvadBP0CkhG8zdZuIiB6mlI7A99pmVmS1TdWZFcc2mE/1JLRTXkpjfQIAxsNIPalUVZejh0rHiSj0MFSySgEIKr2qExER3cvzHEynXaAlCU0P325/xZeXtSRZ2dwj7PcC9LrNrq4Wkad1grJqVkG/42DSssgYnwtAGL5NRESfz3ZsTOZ9Pd86yWT/YBJakRVyvC7ZvyqEQdhRXyTKNZ4mV+Jp3OepUmVN4pSXogV2ExER3cuygNF8oJ5UOp0LWe/0Iw/TUQhfCd8uy0q2i/3LK76XQuh2XIxbWs7NLsXp3HyXaF/Dt7WW85ycJVWe1RIRET2i57twlGjPvKhksdHDt0d9H1FL+PZqcUD90UCNAa5HD+cDteU8HDOJEyWeBtdFRqVCZ+dC9qu48RkiIqJHdH0XrlJnLuHbR3UUs3sjCW25ilG+eVJpLMtCz3dhlD8oPRWy2est56xlkbEsK1kvD3r4NhER0b2MA1/ZXriVbx10HExaktDW2yOyTBmo6Qeuelcwy0tZtbSc49ZFxloWy1hdZCQiInqIUYNZZLU+qieVbiehnSTRwrcBGK3lLMtry6l8g37UQV+ZKhURWa708G1Y6pYGERHRQzabBCelq7uZhJbmsm8J3+4HyhmmuhZZrPSuLvQdjIeB9nvJapMiy5sDNcayAJtnmIiI6H3i/UmOiRLtaQEfbiWhbfUktG7HgeuY163a84vEQunqPNfGrCWeZrs/IVWySq3r0cPLPxEREX2ec3KW/U5/XTcf305C03SHETrXz7wqhNt1rL5IdGyDD5OodZHxoC0yWhb6vv7+kYiI6F5lVbduIkyGwcNJaGHUQXcYNc4w4bhLJE2aBc1YwIdJ9PAi42DaU9tUIiKie1W1yOFcqJsI/W4HPSXf+lYSWqfjYjTpvfo1AwBZUcmxreWcdOG2tJxti4yDYQQ/au5wEBER3U9kn+bqrmAYeBgN2mZW2pLQbMymvUYSminKWo4tFygmbfE0VS1PbYuMkY/eQA3fJiIiul9VoFYKzeWkUggoMyvrXYpUSUIzxsJ82lOnSp2DMuQCAIO+j27oWW+fr16mStsWGV2Mx3r4NhER0UOUgw2OY2M8ilDXsN52YzeT0KZdOFr4dlWL0XYFo7CjnlR6Cd8um1/OdW1MJ3pWKeqSG/ZERPQuxliYz3qwzWNJaNNxhI6SVVrVIodT0dwj9Dtu60ml9TbFSXmMejN8u6gEtf7olYiI6B6WZWEy68NxmjMrWV7J8sEktLqqJb4O4bwqhI5rY6q8SASAfXyWoxJPY6xLy6lNlRZV/XLviYiI6HMNpj10lDWJsrysSWiPHXuR15qEtl3sX17xvRRCYxtM5321q0vSXLYt8TSzlkXGMi8lZhEkIqJ3Cj1b3USoa5GntT6zcklCU8O3ZbM6ovhoZ94Al5d6o/lAbTnPWSmrlniaW4uM28VenSolIiK6V8e1ESjv90Qgi3WCQplZ8Vwb07bw7V2KU/p6Z94A13tPnWZBK8paFutEbTkHLYuMIpejh5UWvk1ERHQvy6Dn61nV622i5ls7toUPk+hyY/CN+JjJIW4O1Jio48BTRkqr65qEtsMRBW77IuPqiEL5ckRERA+5HGxodnV7/aSSsS4hMG1JaJuW4Bijt5zXk0pVs+XseDamI32RcbNNcTore4mMGyUioocpMytJJnulq7MAzCbdG+HbehEMO05zfQLXeBq95TSYT7pq+PYhPkusnMcAABieYSIiovfJzoVsNvo1ickwRNCShLZYHdWs0o5rEHpO82LubpeqJ5XMdVdQuyaRngrZtiwy9gMXsBi+TUREn6/MS1kvD+rMyrDnoxt56lRpWxKaF3jodi5N2qtCeIzP6otEC8B8EsFV3iVmeSmrlkXGy/vHZptKRER0r/q696cdjI8CF8N+c7XidhKag9FsAFxf8TnPP8hOuexaWs7pOISvvEssr1OlmrAfwFee1RIREd1LBBKfCnUToeM5mD6chHbZmbc+erppAKCsRXaLvfqbjfo+okBvOdsWGf3AQ3/ca/w6ERHRAyQ+5yiVOuM4NuaT6KEkNMuyMJv1YL95uunUtUh8ytUXid3Qw6CntJw3FxkdTGY9QAvfJiIiulddqI82bWNhMu7CMmhcSEpON5LQJl14ytNNsz/l6il733cxGanxNFhtE5xbpkpns546VUpERPSQuvk41LIszKY99aTSOS9ltdGT0MbDUE1Cq0XEaI82XdfG7HJXsLnIeDhLcmpmiFrXo4faIiNEK7VERESPmUy66kmlW0lo/W4HPTV8G3JIlTNMtm3woeWK7zHJZRfrLed80oWrhW9XtaDSj/8SERHdazCKECozK1Ut8rQ6qlOlod+ehHbMCpR1/boQWubScqrxNFkp611L+PYohK8sMtZyOXpIRET0HmEvQE87GC8ii3V7EtpsrCehHdbxy/vHV4VwMu2pLxIv8TT6vadhz0c3bFZouV7+1bJKiYiI7uU5pm0TQZabFFnefJd4KwnteDhJ+tHO/Esh7E968LWWs7ocPdTe8nVDT11kBCDb5V5drSAiIrqXY8wlAUZZk9juT0iVu7fGupWElstu+3r/3QBA4NkIe3rL+bROUFbKrmDHwUQ/eojt+ohciWkjIiK6m2WhH7rqrmCcZHI4Zs2P4HYS2nrdDI4xnmMj9JzGD/AcT1M0W07XMZiP9UXGw+EkybElfJuIiOhexlPvCp7OhWxaDsZPW2ZWyrKW5SpWd+ZNL9DvPW12KU5nLZ7m0nJqU6VJmstur385IiKihyhFMM9LWSpdHXBNQlNmVurrVKkavm0bGO2U/SE+S9zacnbhKFOl56yUdUtWKc8wERHRe1VVLcul3tV9KgmtVLJKHWOhF3jNPcL0lMu2paubjSN0vOauYFHWrVOlgecAhuHbRET0+aQWWT0dUNXNNYmg49xMQtPu69qOjV5wef/4qhDmmf4iEQDGgwBh0Iynqa73nrRFRs8xiDrq+0ciIqJ7yW65R1E0C5rnGMzGEbSnm21JaMZYGM0HL+8fXwphVVayWh7UlrMfddBX42kuRVBbZHQ7Lrq+/v6RiIjoXklWIFM2EWxjYd4ys3JM9SQ0C8Bk1ofz0c68AS4Fbfu0Q60UtNB3MB7q8TTLTYpMmSp1HBuj+UCt0ERERPdK81LORbM2WdalCGozK6eslHXLVOlo3EXnTfi2ASDxuUCpFDTvRvh26yKjsTCd92G08G0iIqJ7SSWpclwXAGaTCJ6Sb30rCW3QC9CNmk83TXwqUCgL87ZtrkcPmy1nnGSyb5kqnU37cHiZnoiI3qvSs6rHw0g9qXRJQkvUJLQo9DAcNINjAIjJlJHSl3gareU8F7LenRqfAYDJuIuOssiovngkIiJ6UL8XqCeV6pckNC1828FkFKm/X3xWzjBdujr9pFJeVLLYJG9/GQAwHATqIqOICGrGrRER0fsEYQdDPdpTVreS0CZ6EtopLyUrqmYhHI+78DvNlrOsanlaHaH1dm2LjAAuZ5jYEBIR0Tu4HRfjaRdQZlbWuxRpWxLaRJ8qPSdnSa9XK14Vwv4gRKS8SKyvu4JaPM2tRcbjuUChtKlERET3sq97f9rMyuGYSZw0nzq+JKFp4dtZIftV/PLvL4Uw6ProD8Pmo83n8O2yWdCeFxmhVOjjLpFM+QwREdG9LFjo+a66iZCeCtns9ZmVtiS0sqxkvXi9M28AwLUt9Cfq0UOstylOyvjqrUXG5HiW405/l0hERHSvfuiqdwWzvJRVy8xKWxJaXYsslnEjCc3YxkLP99SWcx+f5Zg2W05jAR9aFhnP50J2beHbRERE97JduEqdKctaFit9V7AXeTeS0GI1fNv0A0+dpknSXHYH/a7gbKwvMhZFJatVzNkYIiJ6P6tZZy4zK82uDnhOQmubKk3V8G1jWTBay3nOSllt9ZZzMgzaFxlXMWqtChpb/b2IiIjuJQJZrmIUSlfnuTamLeHb2/0JqZJValnAIFTOMJVlJctVDK3nHHQ76ClTpSIii3VL+LZteI+QiIjebbuOkWXNtBnHtvBhEqnX7OMkk4OWhGZdhnBsY70+w1TX9eVFotLVRYGL0aAZvv0yVZo3K7RtLPQDXqAgIqL3Oe4SSZNmQTPWZU3i0SS0wbT38v7xpRCKiKwWB/VFYsezMb3sCjb+oM0uxUlZZDS2Qd931SEcIiKie2VF1bqJMJt0W8O325LQBsMQfvRFCMxzIZT98oBcWZNwbIP5pPvYIqN1PXqovH8kIiK6V1HVcmy5QDEZhgiUfOubSWhRB73B6515AwBpXuKcKi2nuYZvKwXt1iLjeNqDq8S0ERER3U1EDsoKHwAM+j66UTPf+lYSmt9xMR53G79uTnklJ+X9HixgPongavE0eSnLlpZzNIwQiasumgAAIABJREFUKOHbRERED6lzdVcwCj0M+82TSreS0FzXxmyiZ5WaRJnAAYDpKELHU1rO8nLvSV1k7ProtYRvq38IERFRG+XZZqfjYjJqdnUAsPmMJLRzUUljfQIAhoNQPalU1yJP65bwbd9tXWRsO65IRER0L8e1MZv21BCYfXyWWHmMalnAvCUJrahqOWr3CLuRr55UEoEs1gkKLXzbtTGb6OHbSVYCojx6JSIiupOxDabzvp5vfcpl25KENm9JQivzUuLzpUl7VQj9wMN4rF/xXW0TnJV4mstUaaROlZ6LSk7KZ4iIiO5lARjNB3CcZkE756WsNqn6uVtJaNvF/uXJ60shdDwH42nv+c98ZXc4S3JqPt40FvBhEqmLjNkpl6Rl5JWIiOheXd9VNxGKGzMr/RtJaOvFAdVHO/MGuBS00XyotpzHJJdd3Gw5n48eulr4dl7KbrH/1N+NiIjopqjjwFO2F57XJPTwbRdjJQkNgKxWR+RvnlQaAOgFHmzlDzpnpax3LS3nKITfssi4enP0kIiI6GHGRqBsL1xOKun51h3PxmzckoS2TXE6K6cF+4EHR+kEi6KSxVq/9zTs+ei2TJUulwdUypcjIiJ6iH6wQVabRD2p9OkkNH2gxmgtZ3Urnib0MOzru4LL9RFFoS3nc7+eiIge1pxZ2aXqSSVj3U5C27Y83ewFbnN94rnl1Lo633Mw0XcFsd6mOCvL+cayANtTP0NERHSvJD7LIW5Ge15mVtqS0CpZtSShRR0HHce23hbCy4vEotlyus7zmoS+yHhUzmNYFq5nmNgSEhHR58tOuWw3R/Vn07aZlbJufcUX9gL412HPV4VwtzmqLxLta/i2usiY5rJrWWTs+q66zU9ERHSvspbWTYRR3384Cc0PPPTHvZd/fymEySGV4401Ca2gnbNSVlv9uWt/0oPHIkhERO9Qi0h8ytVNhG7ofUYSmoPJrAd89HTTAEBe1hK3tJyzcYSOp+wK3grf7gcIe81kcCIiogfIPs2hNHXwfReTkT6zcisJbTbrNaZKnfIaOqoZDQKEgRJPU4s8rY6olQodBh4GIz2mjYiI6G5Vrj7adF0bk1EEgdydhGYZC/NpT01Cc/anouWkUgf9bsd6+x1ERBbrtkVGB5OWe09EREQPkWadsY3BaNyFCKy3RfKY6klowHWqVAvfrmox2nPXWyeVlpsUmXLI13EM5tNmywkAqEvGzBAR0btYloXZrNc6s7JumVmZDEP4SlZpLSKHk3KGyXOd1iu+2/0JqfIY1dw4epiVlaBm+DYREb3PZNaDp0Su5Z9KQouaU6VSX4pgLfK6ENqO/iIRAOIkk/1R2RXE8yKj3nLGyrNaIiKiR/QnPfhBs6BV1WVwUxuoiQK3NQltu9y/vH98KYSWsTCdD9QXiadzIetdc5sfAKbjEB2lQldl9XL0kIiI6HMFrq1uIoiIPK0TdWbF9xxMWwY3t+sj8o9i2l4K4Wg+UF8k5kUli5Z4mlHfR6RU6LoW2T7t1ApNRER0L8+xEXYc7Uey3CTIlXzrW0loh8NJkuPrgRoDAN2OA8/XW8628O3ejUXG9fKAUgvfJiIiupdl0AtcQDuptEtxOjfnT2xj4cPkRhLavjlQY0LPQUfpBOvrrqC2wxF0HIxbFhk3myP+P/beJcS2brvv+8+5Hnu99trvXd91dElEsEWwsQhERGlIjnFPICKw03PPLYMNcgQi4KSRpkkrOCStQPomdisBtfIQjnwdEJYSghTrKlwU6Tu134+19nqvkcbaVedUrTH3qV11Ihw0fnDhfKfuqrNW1dhrzDHn+P9HIVuigiAIwkexHCgmCZ7OOZ1v9KzYnPl2UdPWYByjA8aoFNeSk7WnsTUW05C9ucMxo/TSvzlBEARBuJ9+VZdlJe2Zqg54ckLr57Tqhvm279p9+QQAbPcpcqaqs27IJJK0oCMzHqO7UMYwCYIgCB+jLGrabPmqbnrDCW21SdAyu5uurREOmER4OmXGkUoPc7P59tYw9DDynK4lVRAEQRDeSVM3tFmfWPPtYegijgZsV6nJCc0ZOF1+Al7OI7ykBXuQCADLaQjX1FW6TcDVnL5rPc97EgRBEIT3QHRVIjAJLfBsoxPaZm9yQrMwWY6ej/ieE2GZV7Q3lJyzsQ/fY0rO5rrvyiXByEPA7NUKgiAIwh3QOa9YJYLrWJgbelb2xwyc+bZWCvNlDP3F7qYGuj3Uw+rIlpyjaIBh2C8522chY/+agecgng17fy8IgiAI93DOKlRMnrGsTiuo73RCWyyGsF/tVNotEZ3zCm3LlJy+g8nI7/093RQyWpgtYt58WxAEQRDeSltTUffzjFYKi1kEbanehKRbTmizaYQBY76tT1nFagUHro15pxW8Q8iosVjEbFepIAiCINwFM7BBAVjMorud0MYjH2HAmG8Tkea6aWxLYzmP2KrulBR0TsveNUopLOYRK2QEidmaIAiC8HEm0wge07NS33BCiwxOaAD4MUxaKywXQ7aqu2QV7Y4m821eyNi0RGjFaUYQBEH4GPEoQMT1rFy1giYntJnBCS3JK1RN+zIRdlVdzI5UKsqG1ibzbYOQsSWi46UEm6IFQRAE4Y34oYd4HPS3Np96Vm44oYE54ksOKRXXa14kwskswoCxXKtv2NPE4cAoZDzn3dBDQRAEQXgvjqUQz3klwm5/QVbw5tsmJ7Q0ySk5fC7snhPhcBIhMJScj1u+5OyEjP2uUgB0XJ9YaYUgCIIgvBVLKww9h+1ZOZ5zOl+4npUbTmh5RYdX5tsaADxHIxwxJSeBVluD+bZjGUvO4z5FLubbgiAIwkdQCrHvskkwvZR0OOXcVUYntKpqaLM5907rtGNphAOH/WbbfYq87JectqXxMAvZm0uSnM4ng/m2IAiCILwV7cBitjaLoqbNnu9ZuemEtjmzx3U69l2AG6l0yoi3pwEeZiEspuTM8op2hpsTBEEQhLtgBjbUdUOr7Zn1t44NTmg3zbctDc2Nsk/Sgo5MydkNPTQLGdcGr1Jom/97QRAEQXgjbdvSan1mRyoFnoMp44SGp65Sxnzb0gqx7/R1hPmNqm42CeBxXaVNS6tNwnqVeo4FaDHfFgRBEN4PEdFmdULNWK4NXAuLKe+EtjU4oWlLPzfhvEiEzweJzE2Mhx4ixp7mWchoKDmv854EQRAE4b3QcXNCycgkbEtjObvfCW2yHD2fPz4nwrZpabM6sgeJUeBiHPftaZ6EjFxXqe3aGF6HHn7tCQVBEATBxKWskTMD47VSeJhHbEPNLSe06XwI5wvzbQ10Mon94wENk9A818aMH3qIrUnIaGlMlmNw54+CIAiC8FbysqGMOd+DAhazEA7jb33TCW0cwn+1u6kBUFJUqBiZhGN38564hHY855SwQsZu6KHFmW8LgiAIwluhlpKC96qeT0K+Z+WGE9ow9DBkzLd1klesR5u+TqBg7WkuJe0NQsb5LIIjk+kFQRCEj9L0iy0AGMf8SKVbTmi+52DKm2+TzpnhukopLGchb09T1LTZX9ibm45D+H7/5sR1WxAEQfgWROEAo9i/3wltxjuhpUXdl08A5pFKVd3Sapvy5ttDD0PWfBuERsYwCYIgCB/D8xxMpxH7tY3RCU1dj/j6u5t51VBWMolwMg4RMFVd0xI9bhK2qzTwHUwMQsZzVgLUz9CCIAiC8FZs18Z0EQOsE1pudEJbziLWCa3ISkqvzZ4vEmE09NmDxFv2NAPXwnzCCxnTokbJXCMIgiAIb0UrYLIcsT0ryaWkw5l3QlvMIt58u6zpsDp+/v5PfxgEA4y7aRKvofXugoJpX70lZExPF+LOHwVBEAThHoa+C4sZGJ8XNW0NPSuzcQCf6SptmpY2q9MLJzQNALZWGBtKzv0xwyVnSk5tFjJml5LOO4PvqCAIgiC8kdh3YTN5pqoao0xiPPQQhQYntPWp54SmtVIY+vzQw3Na0DHpq/k7821eyFiWNe0259tPJgiCIAhfQztwmTzTNC09bhJWjxD6DuuEBoA22wQVs1Op48CFZpJglle0O/D2NPNpAI/pKq3rzhmcM98WBEEQhLvQ/e1QIrO/tefamE/YI76rExrXUKOguZKzLGtabw32NLGHkOkq7cy3z2hbpjlGsSoNQRAEQbgH2mwSlNU7nNAYr1KlwI9ham6MVBoGLkZsVylotU1QMeMxbK0By731YIIgCILwVQ67FFned5uxtMLDzOCElpV0MDihRZ4D29IvS7Xng0SmqvMHtsmeBtt9ioIx39YKiAOZQCEIgiB8jPR0oeTcP6576lmxmbPEvKxps+O7SuPZEO5VX/g5ERJotz6xB4murbGYhlB3CBmVVoh9/vxREARBEN5KWbdGJcLiHU5ow9hHMPxs0/acCI/bM3JGJmFpZTTfNgoZr+JHTlohCIIgCG+lblpKmNwEAJORj8B3+l2lT05ojPl24LsYvWqo0QCQlTVlCVNyKuBhHrHm29kNIeN4GsH1OPNtQRAEQXgrRMes4qu6aICY9be+5YRmYzaLgFe7m7qoGrpwQw/RlZycPU1ZNbQ2CBlHsY8wYjUcgiAIgvB2mpJt3PQ9B1N+YDxt9gYnNFtjMeed0PTZUHJOJwF8jyk5m27oIVNxIgwGGI0CVsjI/iOCIAiCYIJJgq5jY8FUdUDnhMabb3dHfJZmTGDqhliBXzz0MAz7JWdLRI/bFHXTv7nBwMbMIGREK2OYBEEQhI9h2RqLxfBuJ7TFLITDeJXWTUunrOrrCAPfxWTULzkJoPUuRcl0lXZCxogVMl7KmtCK+bYgCILwfpRWmC9H7EilLK9oa3JCmwTwOPPtuqGnHdEXidAd8AeJALA7XJDlfa3gra7Som7owugLBUEQBOEeJosRHEPPymp3wwkt4J3Q9o+H5yO+50Ro2Rbmy5gtOU9JQee0r+bvhIx8V2mZV5QyiVMQBEEQ7iEa2HC5gfE3zLejG05o2/UJ9Re7mxq46v4extDMQeIlq2h35EvOTsjI7LtWDR1WB+mQEQRBED5E4NoYMJVge9UKNkznpj+wMTM4oe12CYpXTaIaAGLPgc38Q0XZ0NpQck4NQsa2JdqsTqyQURAEQRDejLYQMOd7uPasVHVfK3jTCe2YUXrpN9To4dV09PUX6ro1Dj2MQ7OQcb0+oWbMtwVBEAThLrTD/vV2n97vhJaWdGS8SgFA3yo5WXsaz8Z07HPfiza7FEXJnAuK05ogCIJwP/2elVNmHKlkckLLi5q2B353M/KYMUxEoPX2zFZ1rmNhMQ3Zm9sfM1wypqFGKUDLGCZBEAThY1zSgg5H3trzlhPaapuwti6+a8FzrP7E3N0uQc5IHmxL42EWGoWMJ858G93QQ8gECkEQBOEDlHlF+y0/gWI29hEYndBStqvUDz0E16kVLxLh6XDhDxIV8DALjULGnUHIGHo2HOYaQRAEQXgrTUt0WB1Z39E4GnzFCY0x3/YcxPPh838/J8IsyenElJxPWsF7hYzDSYQBY2kjCIIgCG+lJaJzXqFlBsYHnoPpyNyzwjuhWZgtXmrmNQBUTUvH7Ym9iZnBnqa+IWQMIw8hb74tCIIgCG+FTlnFagUHro35NABYJ7QMF4MT2mIx7HWV2nXbZVvuIHE09BAZ7GlWBiGj5zmYdDZtgiAIgvB+mord2rQtfbUDJfW6GDslBZ3YrlKFxXwIm9mp1KdLyVd1gYtxzNjTPJlvM0JGx7Ewnw0BJkMLgiAIwl1Qf2tTK4XlYgiL0QreckKbT0MMXMZ8uyXSLZMFvRsjlbb7CzKmq9SyNB7m/ZLz+jBiMyMIgiB8CKW6rU1upNItJ7SJwQmNiOh4Kfs6Qse2sJgN2ZFKx3NOyYXTCqIbesh0iFZ1S2hkHqEgCILwMSazCINBP6HdckIbhq7RCe2UV2iJXiZCy9JYLGK2qksvJe1Pfa0gYBYyNi3RiRHZC4IgCMI9DCchAk4m0RI9bvmelc4JjTXfpuPm9Dxk/jkRKqUwW8Swbcaepqxps+fV/LOxD58RMrZNS6eslAkUgiAIwocYOBrhKGRHKq22BvNtx8LcYL593KfIv2ioeU6Eo0UMl5FJVPVVmc/c3MggZCR6OfRQEARBEN6DY2lEA4P59uGCnPG3ti2Fh1kIzbiaJUlO59PLhhoNAOHAhhcYSk6D+XboO5gYhIzb9RkVZ74tCIIgCG9FKcS+C3AjlU4ZpUzPiladCYzRCW3fb6jRntOZjr7+AhHRapvw9jSuhfmEFzLu9ylyORcUBEEQPorlso2bSVrQkelZUQAWs8hovr02eJXqyGNLTtrsLijKvobDtjSWs4g13z6dczonfEONIAiCINxHP8/khqoOAGbjAD5zxNc0La02CetVOnAsaLAjlS7sSCWtFR7mkVHIuDeMx4DF7+8KgiAIwlupqoY2mzPbszIeeohC3gntcZOgYXY3HUtjyM0jTJLcOFJpOQvhMF2lRdnQxiBkDAc2oMR8WxAEQXg/bdPSZnUEZwIT+s5NJzSuq9R2bQy7HdGX8wjzrDSWnPNpAI+xp3kWMnIONY4Fn7lGEARBEN4KAbR/PKBhEprnWpgbnNB2N5zQJsvR8/njcyKsypq26zP7zSaxh9A3lJxbvqt0EAy6alAQBEEQ3g8lecUqEWz7qWeFd0I7s05oCvNlDOsLmzYNdPOe9o8HtqobBi5GQ6bkvCVkdG2MFzEg5tuCIAjCB0jyih3yoLXCwyzindAysxPafBbBebVTqYm6eU8tc5DoDWxMJ6w9DTb71CBk1JgtY7arVBAEQRDeTFtTzgzXVUphOY/MTmg7vnFzOg7hM7ub+pSVrEeb41hYGOxpDqec0qxvpK2VwmIRs0JGQRAEQbiLljdmMY1UuuWEFg89DFnzbZCumErQ0hoPc77kTC4lHZiuUihgMY/gMEJGkJitCYIgCB9nMg4RMFVdc8MJLfDMTmjnnBnDpJ6GHnL2NEVNW6P5dgiPGY/REhFaGcMkCIIgfIxo6GHI9qx8xQltyjuhpUWNsm77iXD+FXsa1nzbIGQkAh0vJcA04QiCIAjCWxn4LsbTiPsSbfb3O6Glp8vz+eOLRDiZRuxBYtN0WkFugzMKXFbICAKd84o9fxQEQRCEt2JrhfFiBLBOaBlMPSsmJ7TsUtJ599l39DkRhqMAIVNytkT0uE2fBxh+iefamPFDD3HcnsGdPwqCIAjCW9FKYeg7UExCO6cFHZOid80tJ7SyrGm3eamZ1wDg2hrDiaHk3KUomfZVx9ZYzkJWyHg+XihLst41giAIgnAPceCycwWzvKLdgc8z80kAjzHfruuWVutzTzOvbUvjOoGid9HucMElZ+xprubbrJDxUtDxYDDfFgRBEIS3YrmwmTzT9azwdqCT2EMY8E5oq80ZbcuI82PfYbWCp6Sgc8rY06AbemgzXaVFUdPOcHOCIAiCcBeqn2e6kUr9qg7oelbMTmgJqrq/u2lpDc2VnJespL2hqltMQwzcfldpVTe02p5BXF+pFs9RQRAE4WO0LdFqfWJHKvkDGzODE9r2cEHBmG9rBYwCZgxTWdbGkUrTkY/AZ7SCLdHKIGR0bQvQMoFCEARB+AAE2m3OqJieFdfWt53QOPNtrRD73fnji0RY1w17kAgAcThAzNrTXIWM3Lwnq+v2EQRBEISPcNqdkTMD4y3d+Y7e44SmFDBZjJ6lFc+JsG1b2qxO7EFi4NmYjnl7mvWOFzJatoXY488fBUEQBOGtZGVNl3O/Q1Qp4GHO96zkN5zQxtMI7hea+adESIfVETVXcl7Nt2EQMl5yRsioFSYPY5lAIQiCIHyIomrowhRbQNezYnJCW5mc0GIfYfSyoUYD13lPTEKzLI2HWcgmNLOQUWG2jGFz5tuCIAiC8FaopTOTmwBgOg7ge/2elc4JLWWd0MLAxWgU9McwXYqaCuZ8T2mFh1nIm2/nFW0NQsbpLMSAMd8WBEEQhLswDGyII36k0mcnNMZ8e2BjxhvHQF+Y4bpPWkFupFJZNbQydJWOYx9h0L85cd0WBEEQ7oZJHYHvYsJbexqd0Gz7yXy7f8R3KWvqyScAYDoJeXuapqXHTcKmtSgcYBT7XCVIaGQMkyAIgvAx3IGN2SwCWCe0zOyENuO7Sou6oUtR93WEo9hHFDIl51UryE2T8G4IGc9ZBZCYbwuCIAjvx7ItzBcx27NySgo6pWbzbZsz384rSq+J80UiDMIBe5BIAK13KUrmLPFJyAgmQ1/KmgrG0kYQBEEQ3opSwORhDM30rFyyinZHvmelc0Jjdjerhg6r43NX6XMidD0Hk9mQ/Wbb/QUZY09zS8iYJTllhpZXQRAEQXgrQ89hlQhF2dD6HU5orzXzGugS2ng5Zg8Sj+ecEs6e5oaQscgrOm5PX3k0QRAEQbjN0HPgMHmmrlujVnAYukYntPX6hPrVTqVWSiH2HH6kUlbS/tS3pwGApUHIWFUNbdcnsHcnCIIgCG9F2xgweaZtO2tPzt/a92xMb3SVFoxSQo98PgkWRU2bHW9PMxv7ZiHj+sTenCAIgiDcBTOwgQi03ia8+fbVCY2z9twfM1wYr1IFBc1tbXYjlRL2vkbRAEOmq5To2lXKCBmhWJWGIAiCINzFbpcgL/qSPNvqTGC40YLntKATZ74NIObGMHXzns5sVRd4DiYjg/n2NkVZ8Q01sGQChSAIgvAxTocLpZe+TEKrzgTG5IS2MzihhZ4Nx9IvS7Xng0TOnsa1sJgGACtkvCDjzLcVEPsuutYaQRAEQXgfWZLT6dg/rlMAFrPIbL5t6CodTkIMbOvlGCYAtNsk7EGibT3Z0/BCxnPKdZUqDD3ned6TIAiCILyHqmmNSoTZOIB/pxNaGHkIR2FvDBPO+wQZW3IqPMwjNqHdEjKOFjErrRAEQRCEt1K31E2gYBLaaDhAFLp8V6nRCc3BZPbSfFsDQF41lDIlJ9DZ0ziMPc0tIeN4EsJjzbcFQRAE4a0QnS4lX9UFLsaMv/UtJzTHsTCfD4FXR3y6rFtKGdcYAJhPAt58+5aQMfIQ8ebbgiAIgvB2mgotkwUHro3ZJGQv2Zmc0CyN5XzIygX1idFVAMB45CMM+JLzccuXnL7nYGK4OUEQBEG4C2Zgg2NbWM6HRie0s8EJbWlwQqualh/DFIUDjIYeoxUErbYpKs5827WwMIzHQFuLwl4QBEH4ENrSWCz4qu6WE9rC4ITWtN3Way8RegPHWHJu9ilypqvUspSxqzQrG0LLb70KgiAIwltQSmG+iGHb/YSWl/c7obVNS6esBOHVGCbHsdmDRAA4nHJKM14r+GAQMpZNSynjACAIgiAI9zBaxHCZnpWqbmm1TdmelfiGE9r+8YCnE77nRKgtjfkyZkvO5FLSwWBPs5hFcDjz7bKmhBHZC4IgCMI9hAObVSK0LdHjhjffDjwHU4MT2nZ9RvXF7qYGPg89tBiZRFbUtN0bSs4JL2Rsmpb2jwe25VUQBEEQ3ornWPCYYouom0BxrxPafp8if9UkqgEg8hw4zBTfqmpobZBJjIceIkNX6ebxhJYz3xYEQRCEt6I0Io/1qqbN7oKCGf5+0wntnNM56e9u6nDgwGXO95qmpcdtAm6iUug7GMf9rlIAtNmcUTHm24IgCIJwF5YLsCOVLuxIpc4JLTQ6oe0NxjHad80lZ9Mw9jSujbmhq3S7T9nxGOK5LQiCILyDfs9KkhtHKnVOaP2cVpQNbQxOaOHA7ssngG7oYcmUnI6tsZyFRiFjkva9SgEAWsYwCYIgCB8jz0ra7fmE9lUnNKZpxXMs+K7dn5i736fsSCVLKzzMIqOQ8WAw3459F1Bivi0IgiC8n6qsabs+s1+bxN5NJzSuq3TguwgHNoBXOsLklLEHiV3JGcFmukpvCRnDgQ2XuUYQBEEQ3kpLRIfHA1vVRYF7txOa49oYL0bAdev1ORHml4IOhpJzMQ0xYM4Sn4SMHOEoYFteBUEQBOGtEIFOWYWGUSJ4AxuzScBetz1cDE5onWZefbG7qYFugOFxzQ89nI58BH7fnqa5IWT0gwGGk6j394IgCIJwB3TKSnbIg+NYWExDKIMTWsKYb2ulsFzEPSc0u7kOPeRKzmHoIo54exqzkNHGdG4w3xYEQRCEt9JWqJg8Y2mN2SyCUlCvc6TJCQ3K7ISmT1nJagV9z8F0zJactL4hZFwsYlbIKAiCIAh30fbzjFLKOFIpv+WENg7gcebbRKS5ktN1bCxmIcAKGTNcmK5SrRWWhvEYIEaQKAiCIAh3Mp9FcBkntLJqjAPjR0MPEWu+DTpyY5i6Kb68Pc05LeiY8FrBxSxihYx10xIaMd8WBEEQPsZ4GsH3+zKJpukaN+9yQiPQOa/QtPQyEXZVXf8gEQCyvKLtgdcKmoSMTUt0ZEY3CYIgCMI9hHGAiJVJED1uU7ZnxXMtoxPaaXd+Pn/8nAgVMFvE7EFiWTW0MtjTmIWMrbEJRxAEQRDeimtrDKesEoHWuxRlZXJCi1gntPMxo8v5c2H3nAhHsxgD5iCxblp63CTsSKVbQsbD6si2vAqCIAjCW7H18wSKXq7ZHTJcckYreMMJ7XIp6Hh4WdhpAPBdC37UT2htS7TaJGxC828IGffbM0oZyisIgiB8BKUQBw6rFTwlBZ0Zf+sn823OCa0oatoyJjB6YFsImA4cPJWcjD2Na2ujkPF4vNDFZL4tCIIgCG9Fu9BM4+YlK2l/4GUSnRMaM1+3bmi1PYOYvlI99PnJENv9BXnBl5zLOV9yJmlBxxPfUCMIgiAId8EkwbKsjSOVTE5oT7ubnBOaa1vQ4Ko6w0glpYAHo5CxMo7HgCVjmARBEISPUdcNrdbn9zmhMbubtqUw9J2+jvByKehgmOK7nIZwma7SqmpotU1ZIWPg2oAS821BEATh/bQt0WZ1Qtv2E5rv2UYntM2ed0KzbAtDrzt/fJEIi6JiDxIBYDb24TMvr012AAAgAElEQVRdpU3T0uM2AXHznmyNgNEXCoIgCMId0GF1RM3IJNwb5tv7Y4aU0bJrrTB5GD+fPz4nwrpqaLs6sQeJo2iAIWNP016FjA3joOZ6zlPLqyAIgiC8mySvUOb9aRKWpfAwC9mGGpMTmoLCbBHD/mJ3UwPdHur+8cAeJAaeg8nI5+6NNgYho+1YGC8/Dz0UBEEQhPdwKWoqmPM9ddUK3uuENp2FPc28JoBOeYWm7ie0gWthMQ0AJqFtDxdWyKh1N/RQa5lMLwiCIHwAaujCDNd90gre64Q2jn2EQX93U5+zCjWztWnbGosZb77dCRn7ZapS3QQKmzHfFgRBEIS7MAxsmE5CeIM7ndDCAUaxz2rmdclUgvo678ni7GmyinZHg/n2NGTHY4jhqCAIgvAtiGOfHal0ywnNu+GEds6qvnxCQWExH7IjlYqyobXJfHvkI2DGY7REhLZfPQqCIAjCPQThAONR0NcKvsEJDcwR36WsqaibfiKczkJ2pFJdt8ahh8PQRcyZbwN0yiqwdaogCIIgvBHXczCZDdmv7fYXZHc6oWVJTtlVX/giEY7GAXuQ2LZEj1u+5AxuCBmTvGJnRAmCIAjCW7G0wng5YkcqHc85nS9cz4rZCa3IKzpuT8///ZwI/cjHkCs5CbTapqi4ktOxMDeUnOd9wpapgiAIgvBWlFIYeg6rREizkvannL1uccMJbbs+4cvtTQ0AjqUxMpScm32KnGlftW8IGZNzTqnBpk0QBEEQ3krsO2zjZlHUtNnxeWY29hEYnNDW61NPM68t3WVbMCXn4ZQTa0+jgKVJyJiVdNglNx9MEARBEL6K5cBh8kw3UonPM7HBCY2o6yrljuv0yHfZfdckLel47pecCsBiFrElZ1nWtDHcnCAIgiDcBTOwoW2JVuuz0QltanBCW29TlBXfUKO5bpo8r2h7MJhvTwL4TFdp07S02iS8ZFBb7PcSBEEQhLdCRLRen9iqbuCYndB2hwxZzu9uxr7bl09UVUPrbQJOJzEeeogCRivYEj1uEjTMeAzH0oAW821BEAThQ9Buk6Bge1Y0lvNbTmjcfN3uWNDS6uUYpueDRKaqC30H45jXCq53KSrGfNvSCrHvAmK+LQiCIHyA8z5BduknNK0UHubh3U5oo0X8LK14ToTUEm1XfMnpuTbmk5D9Ztv9BTkjZNSWRuw77PmjIAiCILyVvGqMSoTlLLzbCW00CeF9oZl/SoR02JxQMiWnY2ssZ6FRyJiwQsaXQw8FQRAE4T2UdUspU2wBwHwS3O+EFnkYvjLf1gCQFjUKruS8znviGmpuCRlniyEcznxbEARBEN4KtXTKeK/q8chHaOpZMTih+Z6DCbO7qbOyppw531NKYTmLYNt9DUdemoWM00kIjzHfFgRBEIS7aPkxTFE4wIjztybQamd2QlvMIoDpWdHGknMaYuAy9jR1S6ttypac8dBDFPVvDmwPqiAIgiDcgGnc9AYOZqaelQPfs2JZythVmpcN9eQTADAZBQh8xp7mKpNghYy+i8mIN99GI2OYBEEQhI/hOBbm8yHAVHWHE9+zohXwYHBCK5uWkoKZRziMPH6kEhGttrw9zcC1MDcIGZO8AkjMtwVBEIT3oy2N+XLE9qwkl5ION5zQHM58u6wpuYrsXyRCz3fZg0QAtN5dUJT9s0Tb0ljODCVn1bDnj4IgCILwVpQCJg9jWFzPSlHTdm8y3zY7oR0eD887r8+J0HFtzBZ8ybk/Zrhw9jRa4cEw9DC/FMaWV0EQBEF4K9HAYZUIVdUYZRKj4QBRyHeVbh5PaL7Y3dRAt4c6eRizVd05LeiYMPY0eBIyMvuuRU3H9al3jSAIgiDcQzhw4DJ5pmlaetymYFpWEPoOJq+0gldosz2jemW+rRWupqPcSKW8ou2Bt6eZTwJ4TIau64Y26xNvvi0IgiAIb0Xb8Bn1wlPPSsM6oVk3nNBS5NzuZuy7rEdbWZntaSaxZxQyrtdntMzNCYIgCMJdaNaYhdbbBCXTs9I5oUVmJzTGfBsANLe1+XmkUv+CKHCNQsb19oyqZppjFKvSEARBEIS72O9TdqSS9RUntIPBfJsdw9SNVDqzJac/sDGbsFrBruTkzLeVAiwZwyQIgiB8jOSU0TnhZRLLWXi3E1o4sOHauleq0WZzZkcqubbGYhpCGYSMKWu+DcSBe/2TIAiCILyP/FLQYc8f1y2mIQZcV+nVCY0jjAN4V33hi0S43ybIC77kXBpkEsmlpCMjZIQChp4Dm7lGEARBEN5K3bRGJcJ05N/thOYHAwyn0fN/PyfC9HihlCs5FfAwj54HGH7JLSHjaBbDYa4RBEEQhLfStETnvGKVCMPQRRwNjF2lnBOa69qYzl+ab2sAKOqGzvuEvYnlNITL2NOUN4SM8SiAz5tvC4IgCMIbITplJasV9D0H0zHvb73Zm53QFvNhTzOvq6alNOcdYKZjH77HlJxNt+/KChmDAeJxIElQEARB+BhNxc4V7EYqhYDBCS3NeCe05WLImm/bp6wyjlQahgP1+h5aInrcpmzJ6Q1sTL/YdxUEQRCEd8MMbLAsjekkQktQrzV+Jic04Gq+bfd3N+umJc3tu3YjlXz2tja7FCXTVWrbFhazIStkRFuLzYwgCILwIbqqLmaruq86oXHm2y3RMWPGMA1cG/MpX3JuDxdcmG3UW+bbRdUQWjHfFgRBED6AAmaLmB2pVFYNrd7hhPbUhPMiEdp2N/SQM98+JQWdU0YrCGA5i1ghY920dGYcAARBEAThHkazGAOmZ6VuWnp8hxPaYXV8Pn98ToRaK8yXfMl5ySraGexpOiEjs+9aNXSSJCgIgiB8EN+1WCVC2xKttinbUHPLCW2/PaPMPxd2GuiquvFyDJspOYvSbL5tEjK2bUv7L4YeCoIgCMJ7GNgWAsY1BgCtDT0rzg0ntOPxQpdX5tsaAELPhsuVnHVr1AreEjJuVic0nPm2IAiCILwVpTH0ea/q7f7C+ltbN3pWkrSg46m/u6mDgY0B01LatkSP24QtOQPPNgoZt9sEpUymFwRBED5KN7DhzSOVvuaEtjN4lWqu5CRCZ09TM/Y0joW5oeTcHy+4ZP2GGvHcFgRBEO6nnzwul4IOR97ac2FwQqtuOKH5rt2XTwDAdp+gYKo621J4mIXQTFfpOS3oxJlvA4CWMUyCIAjCxyiKiraGaRKzsY/A4IT2uE1BzO7mwNYIB3Z/Yu7xeGFHKmnVySRMQsadwXx76DmAEvNtQRAE4f3UVUPb1QnE1HVxNMAw5HtWHrcpO1/X9RxEXlekvUiEaZKzB4kKnT2NyXzb1FUauBYGzDWCIAiC8FaIiPaPB3akUuA5mBqc0ExdpbZjYbwcAdcjvudEWOYlHXb8BIrZOIDP2NPcEjL6kQ+fb3kVBEEQhDdBAJ3yilUiuI6FxTQAmJ6V3SEzOKFpzJcxtP68U6mBzm9tvzqyCW089BCFvD3NasN3lXqeg9Fs+JXHEwRBEITbnLMKddPPM7atsZxHRie0E9tVqrBYDGG/UkrYLXXznriDxNB3MI4Ze5qnkpPpKnUcC9NFDHDm24IgCILwVtqaSqYS1EphNuu0gq9T1y0ntPk0xIDZqdSnS8XOFRwMbMwmIfvNtvsLMlbIqLFcxKyQURAEQRDughnYoKCwmA/ZkUq3nNAmIx+Bz+xuEpGuW6aqsy0sZxE7Uul4zilhukqVUljO+a5SttwUBEEQhDuZzkJ2pFLdfMUJjTPfBujEjWHSWmM5H7JVXZqVtD/xWsHFLITLlJx1S4SGEdkLgiAIwh3E4wBh0JdJtC3Ro6Fnxb/hhJbkFeqmfZkIPx8kMvY0ZU2bHa8VnI59+IyQsSWiE1M9CoIgCMI9+JGHeBTwTmi7FJXBCW1hmK973ifPfS4vEuF0PmQPEqu6pdU2ZUvOkUnI2BKdswqtjKAQBEEQPoBjaYxmMfu17YE3377lhJaec0q/sGl7ToTDaQSfmeLbXEtOk5BxYhAyHjYn1HI0KAiCIHwAS6urQ1m/qjuc+J6Vm05oWUn7V5p5DQCeYyGMuZKTOvNtxp5m4JqFjIddguLS13AIgiAIwptRCiPfZRs3k7SkI+Nv/TUntM22bxyj3c50lLsF2uwvKErGnsbS165Sxnz7nFNiMt8WBEEQhLeiHbZxMy8q2h5M5tu8E1rTtLTanEHMcZ0eei7AjlTKcMmq/gWqG3poMTd3yUraG25OEARBEO6CGdhQVQ2tNwm4ppXRcGB0QnvcJKz5tmNpaK7kPCf8SCUFYDkL4TBdpUVZ08YgZIRmK05BEARBeDNN09J6fWKbMEPfwST2jU5oFWO+bWmF2Hf7OsIsK2lnqOrmk4AXMtYtrTYJW3J6jgVoMd8WBEEQ3g8R0XZ1YntWPNfC3OCEttvzXaXa0oh9B0rh5TzCqqzZg0QAmMQeQqartG2JHrd8V6lr6+d5T4IgCILwTuiwPqEs+wnNsfVNJ7SzwQlt8jB+llY8J8KmbmmzOrFVXRS4GHH2NARabVPUnPm2ayMaOABz/igIgiAIbyUtalaJoLXCw9V8u3fNDSe02WII5wvNvAa6hLZfHdiDRH9gYzZh7Wmw2afImQxt2RqThzGboQVBEAThrWRlQzlzvqeUwnIW3u+ENgnhvTLf1gDonFeoDSXnYhpCcVrBU06poat0vhxBc+bbgiAIgvBWqKG06OcZwDxS6ZYTWhx5iKL+7qZO8goVUwlaVjf0kCs5k0tJB0NX6Xw+hMMIGQVBEAThLho+CU5GAQK/72990wnNdzExmG9rY8k5j2AzVV1e1LTd3yg5GfNtQAxHBUEQhI8zjDx+pNINJzTXsTA3OKElOTOGCbhtT2Oa9zSKPUSc+TZBxjAJgiAIH8bzXUx4mcRtJ7Q574SWV935Yy8RTichO1Kpabp9V85HO/QdjBkhIwA6ZaUUhIIgCMKHcFwbs8UQMDihmXpWHuYh64SWXwpKr/rCF4lwGPvsQWJLRI/b1CBktDEzCBmTgj9/FARBEIS3ohWuSgTG3zot6Jj0pRWfndCY3c2ipuP69Pn7P/3BCwYYmUrOXYqSOUvshIwhK5NIjykVlSRBQRAE4f0ooLNB40Yq5RVtDxl73S0ntM36pWZeA90Aw9Eifvo3X7A9XHDJGa3gDSHjJS3ovBfzbUEQBOFjxL7Lbm2WVUNrg7/1LSe09fqE9tVOpdaqG3rIlZynpKBzytjTAEYhY1HUtDfYtAmCIAjCm7EcdshDN1IpYdtPbjmhrbcJqrq/u6lHgcuOsr9kFe2PfMm5MAoZG1pveJs2QRAEQbgL1T/f60Yqne92QtvuU+SMOF8rBc2VnLdGKk1HPitkbFui1frMChmhLPZ7CYIgCMId0GZ7ZkcqfdUJjTXfBuKAGcNU1w2tDVN8h6GLOOK0gkSrDS9ktC0NWDKBQhAEQfgY+22CPO9XdZbuBsabnNCOjBMaFDD0HNhavRzD1B0kntFw9jSejanBnmazu6DgzLe1QuzLBApBEAThY6THC6UJY+2pgId3OKGNZjGc6zXPiZCIaLs+sQeJnT0NX3LujxkuTIbWumvC4c4fBUEQBOGtFHVD5z3fhLmYhqwTWnXDCS0eBfC/0Mw/J8LT5oyCSWi2pfAwC9mEZhIyQgGj5QjaUkraZgRBEIT3UjYtJUUNAvWS2nTsIzA4oT2anNCCAeJx0BvDhEtZU5byJedyFsG6Q8hIACazCK7nSCkoCIIgvB8iOr2yTnvKbfHQw5D1tzY7oQ1cG9Np1Pt7nVcNZYxRKa5J0Gi+zXSVEgGjkY/A9xS1ABHJ4aAgCILwPtqrVzVRpxm8ZkHfczAZ+dwVtDY4odm2heV8yDqh6YTZDgWA2dhgT9O09MgIGQmEKHQRDz1FoOcytu3uXHZIBUEQhPsgQosugTz9z7n2rBCol592h4x1QtM3ukqLqiF2DNNo6LMjldq2k0m87iptCfA8B9NxACKFlghtC4AUQAptyXftCIIgCIKJpq6uZZRC0wJKK8xnnydQ0Be58JQUdErN5tucE1rdtHTm5hGGwQDjUX+kEj2VnDUz9NC2sJiGAKDomgQbajHybKRFRes//P23P7kgCIIgANj8338AAAhdjVYRpvOY97fOKtrd6YRWVw2drjuiLxLhwHPYg0QA2O0vyApeK/g09JCgrtuhQNMAjiZKixr/z4//r688riAIgiC85I9+/HtoiRDZFkbTESzHUt0WqXo+nitKs/m22Qmtpf3j4fl7PCdC27EwW8TsQeLxnNPZYE/zpZCRQKCW0LaEJC1ItzUIwI9+83/tviwIgiAIb4P++W/+FgjAv7kMoRxLtdf88iSkqJvWqBW85YS2WZ3QfKGZ18DnoYdcyZlmJe1PjD0NgOUXQkYiArVdc0yW1/T4/RH/1swDEeE3fut3gLZfTQqCIAgCS1vjN/7Z78JzLPy7Pz1SdU2oG0JLALVXrSDTswIA/g0ntO02Qflqd1MDnd+axUzx7cy3+UaX2diHfxUyPmVnAqEsG/r+8YS8bvEXZwMQAb/zR3v85F/86L4fgiAIgvBnlp/8ix/h9z4d4dmW+pmHEFXTomkJbduiJbr2rPBOaCbz7f3xgkvW393UQ99hPdqquqHVNuXtaaJBT8hIpNC2RJ82CYqyRt12za1/aeGhalr8vV/7T0Ayn0kQBEH4ClQX9Kv/0d+Ha1v4+X9jiFYR6pbQNC1aAJtdysokvuaEduLMtwHoAVMJPskkuJFKgedgahAyrrYJiqJBA6BsCUVD+Ot/PkbgWviDdYJ//F/+A9HXC4IgCGaopf/6P/11/HhzwdB11K/85SXKuu0qQgL2h4wSpmdFf8UJbWcw3x56Tl8+0Y1UOqNmZBID18JiGgBMybk9XJDlnR9c0wJ1QyhqQmNp/N1f+CEaIvWf/zf/BH/y+//7m34WgiAIwp89/vB/+1/w3/73v4WGWvU3f26JGgrl9XzweC7ocOrLJBSAxQ0nNFNXaeBaGDiWep0IabtN2JFKtqWxnHUyiddfexIytp0TDtqWUF0rwmgS4qe/C9XP/zBGUjX41b/7956SoWyTCoIgCB3U0h/+6H+kX/uP/zNcmlb9/A9j/HDuI69qFFWL5FLSete5mrX0MoHMxgH8O5zQAMCPfPhXfeGLRHjcp/xBoursabhp9q+FjJ3Go9vPHY0DkLJUVjb45Z+dYx46+D//+ID/4D/8W/QPf/3vgOpCkqEgCMKfcagu6L/4tb+Dv/G3fh3/8vGs5qGDX/7ZObKyQVERsryiP1l1A+NfJ43RcIAodN/shAZ0TmijzqEGwBeJ8HLO6GwoOZezEA5jT/NayEjoMnVLwHjow3VtVVYNsqJB1QK/+ld/iJ/714b0J+cc/+Af/SZ+6Rf/Gn7y2/+U0NZf2KkKgiAIfwYgtDX95Lf/Kf3SL/41/Ff/w49QEKl/71+P8at/9YcoG0JWNMjyiv748YT2Kp34shoMfQeT+D4nNMe2MF3EwBeaeRvo5j2dtmf2TucTg/l2bRYyhsEAUTRQ3RSMFkorIK/huZp+4acGWNpj/KPfP+J3//iIv/I3/jZ+9odT/PIv/jv4hV/6JVr81E+r8Z/7IbRlf/6GbUVouQkZCrDc6x9e/yQaQsMbisNyO9O63jVEaJj5igCgbUD3fw4AEZqrQ3rv/vT1/pghHO95prYhtPc+U9vdH8d7nklbgO47NQC4XtMPvNvPVJNRY/qeZ7IcQPXPCb79M9363RqeSSlAuwA3rPpmvBqe6V3xiu6aP414fddn8BvHq/mZ/ozFqwKsAXBPvALdNd8wXtsywfoP/wB/9OPfwz//zd/Cb/yz38Xv/NEOltZ4GPrqb/7cEj+c+8jKroAq6pb++PGEsmo7D+svfhyea2E+CdlbMDuhaSyWfZs2u25aMk2gGMcewoAvOR+3t4WMdHUNR6tQVg2o1bTeJGirCj8Vu/j1n5vjv/uXJ/wf6xy//ZM9fvyP/yf8w3/yPyutVZeoFUERkFUNpV880Jc3Mw5cXvrRtHRkuooAIBw48F2mU5aIDmmJlgm8gW1hyNj0AKDjpUTFzL2ytMI4GLBOPZeipgtzDqsAjMMBuwVd1i2dmG1roOt6GjCHxE1LdLiU4FQrnmMhYgZaEkDHtETd9p/JtjRGgcuO1kryCjkz+kQphXHgss9UVA2dDbEX+y5cziS3JTqmBbsAC1wbAbNoIwIdLgUbr871mRjonFUoGJ2SVgrj0GVbtLOyobTgn2kUuHD+FOLVtS3EvsM+0+14ddkegFvxOgoHsL9RvLYt0f498Xop2dlztr7GK/MZTPMaWcU80zeO16YlOhji1XdthN8uXnHOKxTMZ/BWvOZVY3z/m+K1blo6GOPVhu/avYHsbdvS4VI9P1P7tMVJneTh3//zE/zKX+4aY5K8RlERyqpLglnRfP7/4zqBwn7qWbnHCa2zA+Vyhn3KKnbREYUDjIaeev27IAKttikqznz7Oh4DINW5gn9OhvtjiktawLE0mrbBwFb4lb8Q46//zBg77eD317naJBUORY3jpQYUoaq7ycSf//HPfxx6DlqCel36Ni3RMSv5w1HXgqVV7xoi0CkrUbOBpzBwLLbETooKRdX/e62AyHNQNS37YUqYlQoAxL6DpiX1+gNQt0QnwzMFrgWluGciOmYV+2FybQ3XZp+JznnFPqulFXzHQlX3nyl/tVh5QgGIfZt9pqpp6ZxV/I7CwAbQ/922RHS8lOzU6YGtYVu6dw3Q/W6rpn+RrRU8x0bJPNOlrMHN6VQKGPkO6obU6938sm6NL8qhZ4MM8XrK+GfyHEO8Xp+p5p7pc7z2X/5FzS5WtAKigYOKeaaibihh9FpAF69tS6p8dfO3PoPmeAUds9L48r83XrUGItdiP4Pvide6aen0jePVsTT7TKesMi5WTPGalTVd7ozX6vpMHJEhXtvr79Ycr58/g18MhqDT8wJMIXQ1ItvG2Lfwb38X4K/8pQeQhsrqFmVNKKoWddNitU2QXKrnJPi0JWpphYcZP1LplhPaYhbCZcy3m5bI5laU3sDBdBKgYaqCzT5FznaVvhQyEuj6kyAc04IOxxxKAy1atKRQtgqFpfHnvgvxM8FA/cUfxnAsDcsCLKXRVBXtHo9QRM9l4FPyH04jhHHQX1E2LW2/37/wkHt+pmCA8XLU/+kQaL86oGBWr7ZjY/qDCf8DP6Z03vdbcpVSmP5gAof5gZdZSbvVgT0NHc1j+JHX/yXVDW2/36NlPhjB0Ec8G7JeevvHA0rmpey43TNxK//zLkF66mtttNaY/mACm1nF52lBh/Wx/0AAxssRvKDv9VdXDe2+37E61XAUYDiJ+s/UEu0+7VExsed6LiYPI3YH57g5IUv6HwzL0pj+YAqLWcVn54yO3FGBAqYPY7hef5ekKmvafb9nq5nhJEI4YuK1vcYrk5wGwQATLl4BOqyOyC/9LVHLsTD7bgLNrHjT44XO+6T/SEph+t0EDlOZlHlJu0c+XuPZEMGwfzbTNi1t/2SHholXP/IwmseGeD2izJnPoGtj9t0EivkMnvcJ0iMXrwrTH0zZeC0uBe1XhnhdjOAx4+ea6voZZN6HYRxgOGXilYh2nw6omN0B13MweRjzHfjbMy7nfq+GtjRmP5iwDmBZktNxc2KfafIwxsA3xOunPYj5DEbjENE4ZCv23fd71EwlPfBdTJbjF1t2TzXf4fGI7NJVxU0L1NSiaYBGKUznI2R1q+qWUDUt6qaTSexOGR1ORS8JPvWscCOV8htOaNMvnNBePBMRJZcK9usvOI6FeByhaftDDw+nnFJmBWESMhIIWVbTeneBVoBugaoFGg1YLSGOA9TQ6pzXsC0FrRU0gLZtaPN46n5Jz0mwIxr6cJSl8vPLFwER0frx2POQAwB3YGPhDbA9F/1n2iVIGLcBy9JYjCLs07JfLaQF7TaGM9VljFPRKBQvX25V1dD604F9+cejAA4pdXn1TG1LtP50RMUEnue7cFwX23PvhUi7zRkXZi6XZWssx0Pskv4zJeecDjv+RTl/iHHMa4VXlUFZ1LR+PLIv//E0RNpApa+fqWlp9emImlms+MEAju2wz7RZnZCzixULy4nHPtPpeKHTof/BUFph+TDCIav6H6asoo3hRTmdD3GuSKF6eX9N3dLq04F9+YdDD47m43XzeELBvShdG4upIV73KRKmqU1rheUPIuwv/WfKLgVt13y8zpYxTmWj8KqaqKuGVoZ4HY58ONAqY55p9enILlYGngPbHXC/W+y2Z1wSJl6ta7wyn8E0yWm/5eIVmC9HfLyWNa0/8fE6moRIWyZe25ZW35vi1YXj8PG6XZ+QMdtztm1hOfHZeD2fMjoaFtfL70Y4ZLUCXj5TkXfxylXfk1mEpCaVvLq/pmlp9b0hXiMPjmWrove7BW1WRxSGxfVy6mGb8PF6PmXdNga6LdGmBUgrLBYx0qpVzdUxpiGgaQlJWtJ2lz43xgCf12KmkUpV3d7lhNY9E9E5q6AUXiZCy9JYTCNUTX9rILmUdGAShsJtIePj9eX69O3UdUJFPAxgD2yVlzUsS0FXCkorUEv0uDo9r76+/Ka+5yL2Bjhdeh802mzOrPTDti18Nwtxzpp+4J1z2h/4wHtYRriUpPDqQ10UFa1WZ/SbeIHpJERJWpWv7q9pWvr0eOQDLxgArqu4Z1qtTsiZF6XjWIgCH6dL3Q+84wUn7kWpFB7GQ6RFq7pBWZ/JspLWpsQ+i1A0ShWv7q+uG/r0eGJXycPIQ2s5vWci6n63JfeidG0MA499pt0+RcJVdVrjYRIiyfu/2zQtaMskdgBYLmJkNVRWv7yPqmro8fHInruN4gC1snrP1LZEj49HVNyL0nMQex4fr9sEF6aqsy2Nh2nAx2uS0557UUJhuYwN8VrTanVi43UyDlGRVhUTr4+PR/bcLQhcKJf/DK7XJ2Tciz/+v3UAABosSURBVNK2EIUBzln/d3s8ZnTkdiGUwsMDH695XtFqzVdAs2mEouXitXsmbpcrijyQzcfranViddWua2No+Azu9ynOTLxqrfHdJGDj9XIpaWNoWFzMh3fHaxz7aLTNxutqdULJLa4HDmKfjVdstwlSbhfC0vjOEK9JktPuGq9Pd9i2XYG0XMSoGlJtU6NprlPoWyAvK1ptEzYJmkYqNS3R4zuc0A7rE+qW4FjqcyJUSmG+7F4QVfPyQ50XNW0N9jT3CBmf7jMOXEShq5qG0CpcJw8TUCtabU4oivp5m+tp88B1bISxj6zs/8D3xws4DzmtFb6bBSi4M6CspDWzogS6xN5AqddnRNXTy58LvKEHy3V61xARfVqdDYFnw4989ixqu0+RcFWdpTEdhewzJWlBW/ZFCSwXQ9QEVb/6t8qypk/rM1/VjQIo2+49U9sSfVqdjC9/L/S4Z6L1NjEuVmajEHnVf6bTOac9s/2llMLDIkDVkqrKfryu1id2dTibhCCte8/UNC19Wp3Yl38UDOD6LvO7Ba02Z3ax4joWwjhg4/VwzHDktr+UwnfTEGVDCq8+g1le0cqwWFlMI7SqH6913dCn1Yk9dxtGHuzBffE6cG0EEf8Z3O1TnLl41RqTGR+vaVrQholXAFjODfFaNd1CmYvX2Id27o9X3xCvm22ClItXS2M6Cth4PSc57bhdCAAPywhVi168FmVNjys+XqfjALCsu+I19F0M/AH7TKv1GRm3uLYtzEzxesroaFhcvzVen35dRNT1kWitiqYFXRMj0fX9uk6eezW+/HncGqm02ibsz2HgmJ3QDrsUIX2O8edEOF6OkNTUW3WUVWOUSYyH3l1CRgLgD2xMxiHo6kKjnsYsksJml+DyvPXaXaugYFkak3HAHvie04J2e2YysQK+m0QAlHrd2FOUNT2uE/blPxkHcBy7d03bEn2/OrHWc4HvIgo9roGIVpuEXyU7FsajEDVzmH8853Q4MtW3UpjPQrQE1b76t/KiosdNwp7nzCYhLMvqPVPdtPRpdWYr1SgcIPDd3jVEoMfNGQW3Be1aGI8CtqFmf8xwYrbFtFZYjkO2QaFbrPAvysUshNa6d39V3dCn1Znfgh56GAwc5pmeXv7M2fLAxnDos81hm32KNO2/KLt4Ddl47RYrzKJSdbM9ofrxWlbdM/GLFR+Oe1+8+r6DYcTHa7dY6cerbZufqVusGOJ1GoKA3v3lRX0jXgPYdj9em6al7w3xGgYugmDAxmu3WGHi1XlfvC7mIdoW6vVuyCWr7o7Xum7p+9WJ34KOPHge9xkk+rROUDIL6MHARhzzz7TdX5CY4nXCfwaTS0lbw7nbch6+OV6fdiTGsY+B56inxPX0/2ka0Kd1irrp713cGqm02V9QMD8H29LPA+Nff+18zonyHGH0ubPaBrqup4Hv8nvJ25TtEAp9B+O439xBMAsZXVtj8dRV+vT/py7Z7Y8ZnZhfklbAwzQAFNTrbY0sr4wecvNxANvRvWvquqXv1/yLchgOEAZO7xqirvTmWpMHroXJ2EPT9gNvd7gg4Zx6tMJsGoDQD7z0UtKWWVECwHIawLJU7/6qqqHv12f2nGA09OD7du+atiX6tD6zL3hvYGM88rhtJFrvUlyYxG5ZGrNJgJZIvb6Rc1rQnllRAt2ev9L9321Rdi9KbgE2GfkYDKzeNU3bJTR2S893EA8H7DOttgkyZvvLsTWmkwAtter1jRzPOZ2Ycy2lut+TKV5XhnidjQM4XLw+LVa4pobwurNyR7y6joXp2Ofj9ZiBazvX+imhcYuVijaGeF1MA1g2E6/XlT+/szKA7/Ofwe/XCVvVdfHqG5r7Lki5eL1+Bu+N1/kkhNb9ZyrLhh43Zz5eYw+DAf8Z/N4Ur56DUWyK15St6uznzyAfr0dmu/YpXhUTr3lR08qwazYbB3Bd5jP4tFj5Ik6e/hgFLqLQUy+LR4WWiB53yTtGKmXge1YUHuahwQmtpPR0wdizXvy97bs2qxXpSs6U/SV5rv0OIWOn4eA6MM+XgvaG88f5LIRlWz0ZR1k19Gmb8ofEsQffd3vXtC3R95uEbaUPvO7DxLxvaLW94FLwq47ZNAJBqdf3cUoKOiSMlgXAchpBa927v7yoabW78Ft6Yx+DgdO7pmla+n6TgMlniAIXMSeBuX6Yckb64doas0lXdb7+2v6U4Xzhm6W6hNZ/pkte0Zqr2NG9KB3H7l1T1y192qRgfk2IwwGicMDIerpVcmEwi5+OA/aZtocLkoyP1/msq9Be/1vJpaQtUwEBwMM0hG2I10fDonI89BCY4nXNx6s/sDEeBWy8rnfmeJ3PbsQrUwF18RpCW/3fbVHW9GhoUJiOfHhcvLZE369TVqoU+g5Gw/5nkABabVLkzMrfucYrAb1nOpxy4+J6PovYeM1uxOt8EsB1mXi9fga5eB2GLqKIk6FdR9Zx8epY1wVYP153hwwJ8/K/Fa9pZo7XpSFeq6qhT5uEjdfRcIAg4OP10yZFaYjX6SRgz6q3hwtbsX9tpNKRW4jiyQmNn6+726fwGU8GHXq9xlEAoPU2ZbeKOiFjeKeQsdv24YSM2a3zx8n9RqpR4GI0ZCrVr+gfu0qVX3VwFZDWb/df/ZKu66n/S7rV9TQydD21RPS4TVk9mTewMeO3E7B9x2IlSUs6ml6UswjOna7vk9hDyLR13zJrCDwH0zF/8L3eXdgKyLlhFn8853RmXpRPz8TF6+3zcr5Fu9tZ4V8qUeDyOysEWu1uxOvMsEo+mVbJwMPMsErOzfE6nwbGLj1TEoyjgfk8Z2M4z3EtzCf8ec7uYI7XB1O8Gpr7gO4zeO+UgnHsITKZi5impA++7Zbe03CD13x++d8nKZiNfQSGeDUt2q6WZv0HetoJNOQMU1V3OOXvGqm0PZgXK6wT2nV3kzVrcC1oXG9OgdB5uhD2h5Q913qvkHF5K/BunT8aAs9kpOoPbMwm/MvfrH/UeJiF/F7yV1cdnP9qbfwwfbXrifklhb6DCdP19LXAW07NixVT4BkXK7l5+2t2I/BMi5WhcbHSHXxzL/9bB9+7G4uVpWGxkmbVTeEtv1gxD6v+5osVwyrZ0gpL4yr5/sXKa7/gLzEtVpp3ziv9posV9d7FSsAuVp7i1bhYMS2u37NYec+W3s3FyreVFJh3Ap8tzZhmqW+3WFG4rUQwHS9MbjihrTYJ2qaFun5/BQUNgmtrRJ4DDeqCSikFpQhJWtD5xsv/XiHje1bJHzl/NK06bq6S7111TAN4TODVtwIv/P9glcw4ftwKvPRyw3XhPavkG4uVm6tkfrFCm/2F3f7qRoDxi5VTUvBndeh+t6bFyubWYoWL15bocZPev1i5tbNiWKwc3rlYMZ0t31qsrLaGxUr4lcXKnV1671us3I5XdrFyo7lvNBxgyDX3XV/+7JSCdyxWvv2WXmNswukWK3y8ftPFyrsszczx+q2UCE/c3Al8tVhRCtCK4AwcDD0HIKjr76oTshd5RYd9+vykX37XbylkvLlKvnH++K4tva+sOkxbejdXHXdv6dl3b+l9bf7je7b0Nn8aW3rXxQq7Sr6xWPnqlh7zTO/Z0vtTX6y8Z5X8r8Ji5Vtv6b1rsWJwCbm1WHnPlt57FivfeEvvq4sV5ppvvlg58IuVd1uaGeL1a4sVoxLBsFi5tRP4tFj5MqcpAI5rYXZ197GUgp5FDhSA4lzQaXeGpRQsrfA0slfh2wsZN7e29G6cP3KBJ+ePn5Hzxw45f+z40zt/fO+Wnpw/Av//OH98z07gvyrnj89JUHe/V9vSWH43QtuQUgBmkQP9g0mEFkT/b3tnEqpbdtXx/z7td/rz9ec+S3imBhXBUgwJBKGKSCqo2BMKjTqJExURG9RkUhNHGsG5ONCBCCaIAwexwS6DREEIJUWCUMRHqsi7zdecrzl9sxyc776qd8/ap7gX85LAPW/w4H3s+711z2+tvfZae+//m1/fQEU3CSqiWyEK0R0pCJg7MAcPMhryrGMdp0hlJb37/uN9//Fdz33/sXu+/fuPAyW9+/7jff/xiU3Pvv/4ZBIUnfiuIgSWCx8jwxD7pIZQgOXYhfK973tIh6zCly8zWLqAoQlop5nTsY3rAIEbcW8467hrSe8Z9B/fq6R333+87z9eP/f9x+657z92z536jwMlve/k/uNdKoHPuv94/YEqOha6qpQDzzOEqSt44+IIAYEXX3geihe9D0SE/3ycwVIBW1dgaArskYZo5kIRQihKdwOMOP0Zzjq+jUt6xv9/SU/Wfxwq6d33H79JJb1vcf9xMEu+7z8C+A7uP34TSnrPqv84fKTgbv3H215p9iz7j9cTuyJOk6AQUIXANLQxCS0x0lWYGvDFR3uoCvBDr/wkae9/5eNY/NVncX4s8fdvJnj5exxUJOCMXZQNiapuUTcAie4WmP0xp2NSgNNUXs4cmDoHXkObbcKOGQcWXJuRx2iJVusjBFFvnD3SMZUE/9UmQVM3vTG6piCaOVAUPuvIsrI3Rgggmrus4GZe1LTdpaxN07ENe8SDt1ofoQA9VFzbwFgS/Febbuvvze8ydPVUeuVLJEVR9cYoikA0d6GrfLKy22esTfOJA8vkS3rrTQJGlxu+ayKQBP/VJgG1/Xc7MjXMxzanDY5VnKIq694YVRE4mzlQGZuOSUnHo4xXl03AykrOa+iP4EmC/2p9BBherZGO2VierNQMr5qq4Ewyoe0POaUpwyuAaDbAa8zzOgktPvM/8SqA3jjHNjAJeF7X24TlVddVRDMJr/sMec7wKgSiucPzmr8Xr3xlZSXh1XNNhN4Qr32bTEN9cjPLzXHrbYpSwms0d6FxvKYlHY45a9Ni6mAk4XU9xKtkpSrntfNB5qGrbYq64nmN5vwiaH8sKEn7PijQzRkGO2fUtNnKeXUlvHZJW2eTEF0lU1MFQs9ENHfFSNdgGSq+8NUYb8cFHk4cMp//ELSXfvij+Omf+HH82V//LT7/vwd8OLLw8OEEWUMiK7pzF4rS6UcleUW7XQ7m/WE2cdjVTF23p0DZH+O5JsZ+X9OMiOjqSaB8+mPT0LCUZIebOEVZNL0xiiJwNvfZ0muSlnQ4FKxNi5kLy+z/wquqofU2ZccE/ojtqbYtdXf2EXrjRiMdi6nb+1kAsNokqKu2N0ZVFZzNPTY7PBwLSpKy//8TQDTz2NVMUda0jTPWpnFos6vv5mRTFyif/ti2DNnu364PW/dt6gKlxwb/3T6nPKt6Y4QQiBYemx1meUXxjrdpKuO1aWm9SaEwa0vXMTFhdAWJiK62PK+G0VVWeF4zFEXN87rgeU2zkvYSXudTl119V3VzCir9Mb43QsjoChIRXch4NTUsJi6frGwSVGXfB4d4PSYFJUee1+XMY1sFZdnQdsu/2zCw+GpR201ogrHJtnTMZbyueV41TcHZ3Od5PeSU3ZLXvKjlvI5tuIy2ZzPAq+MYEl5Bq+0BbcPwevJBjtftLkOR87xGC48tvaZZRfs9P2fMpw5sTtuzbmi9uT2vl9sUOPmgULqeoKoIuJaO56IAlqnAGakokxqfe/0Cmirw8Z/7BXz/Bz4ITdM08cnX/pje/O//wr985S189lGK177vTGhFAwUNFEWgqhqkTUPHXQZTe+cau+u/Q99CwKxm2pboKk6gnnqO736skY7FjA3+dLU+om0b6Df411QF0dKDqvSdaX/IKc/L3phOpUAS/Iua9vu0NwYAJqEDj1nNNE1L2ziB1nVen/rMsU3MJn1BSwB0cXUAqO19l66riOZ88I93GZVl1RujCIFo4bOl1yyv6HDMWJtmE5c9cFrXLcVxAk0FbnqT544wCXnw1psjBKj3XaahYTmXBP9tgrque2NURUEkCf5JWlKS5qxNi5nHB/+qod2Of7eBb7Gl17YlulolUARBuTHOMnUs5jyvq/URbSPhdSFLVnLK86LPK4DlwpcmKzKbJqHN89q2tN3yPuhYBmZTWbIi4VVTES0kwX8v53W58KXJyv5wS16vfZDj1TExGfPJyoWEV0PXEC0kwT9OUVd9Xt9JVpjgn5aUJLfnNY4TML8iBJ6FMGBEkFui1ZrndWTqWMw8NllZbxI0Ul59abKSZTyvi7nPll7LsqadJL6OAxs+U3ptW6JLCa+2ZWA+xGvbwtC6pfn1pk/LUPDcc2M4piackQrPVPGZf3sLAPCxD7yfPvrJ34aqqkIDgAcPHoif//Qf0vmnfg3nx1J85h8e4Vc+/ACho8MoBZKcaHO1h60JNKoCAp40wV3HxGTiiJvX4hGBLq/20AVB15WnPjN0Dculz4IXxylQ17BvjFGEQBQFPHhZSUWa98YAwGzmwWZXqg2t9wksZozvWQhDuTMZCmAoT48zTR2Luc+Ct9kcobRN7/+nKgqiKJCCV+VFb4wAMJ/7GDHOVJY1JfuU/T2EgQ2fWX23LVG8PsBURa/+ZY0MzOde3yB0+o8qtb3v0jQV0TLg75Q95NSUVd+m60DJJisVZYeMtWkyduAyq++maWmzStBVp/vJynTKKYp3enoyXheS4B/vUpCE1+Uy4JOVrKQ8kfA69WCzyUpD613K8uq5I4zH/QSs03+U8Gp0NsmSFdHwvC6XAZ+sJAWVWZ9X4BQoJcE/lfAa+DYCSfDveAWgPj3OGumYz/3ezwJAq7WEV7WziU1WDjnVRcn4YKf/aDLBvyhqSiW8jscOPAmvWwmvtm12d4gyNl1e7aGBoN34Ll1TsZT44G6XUVv1fXCI1zyvKDvyNk2nLhxmpVrXLW126elez6c/dt0RJhJeLy/3LK+GoWHJ8CpA2G5TKCdexWlhrAoBQ1fwXc+FcC1d2IaGIqnwR//6Fr4eF4i8EX3kl38fZ2dnAgDE9d1rRERf+9Lnxad+41fxtdUBgoBXf2CJl14I6Btvb3FIKzREqFtC2xJaEjBHGmbLAD1LAWxWB5KqpEchC15yyGkrU0lf+DC54D+kkj524LLBf0h12sS0C/43xw2rpEchH/x3Ke04fTLRqU7rXPB/D9Vph3OmIZV0d4SxJPivLnZylfQo4LedbxM6yFTSoxAaF/yHVNLnPiwu+A+ppPsWApkzDaikzxYB36NaHynhxH9VBYszCa/voZLO8VqVNV0OqKR7kmTl8nEsVUmfdsG/x+ugSvpZAIWprAyppM+jgE9W7sLrgEq67ZqYTD2e1yGVdBmvcUIHZuPTIK9ZSetLifjv3IPFBv+GLh/zvLq+hVDC69X5jherNnXMJAuGQV6jECpXVk8K2sgEuBcBRkzfrSprurzYgThZs9CGz5Re25bo8jxGzcmaWQZmCxmvB2ScWLWmYhEFULgFwyGj+LSZS0B0q2Ol6wsuozE81xSmBnzhqzE+9/oFAOBBYNHHfupV/Pof/AlUVX16Irx+8v0Gf/q7v4S//Mf/QN0QWSrhxYmBH5yNMLZVaHp3K6mqqxhHEwhud1qc0JFTflcEptEYGudMWUnbi7g3BgDCuY+RwwX/htaPt2g5Z/It+BPOmYg25zEqTpzS1DGJQn532vpAKSemqiqYno2hMivVPMkplihpj5chTG6lWta0Pt+y4LmhAzdknKklWp9vUXPOZBkYL8Le+RegU2jOE8aZNBXTszELXnrIaM8oaQshMF6GMLjgX1S0OY/Z4O9PPNhc8G9aWj/eouFkdxwT4ZxJwAi0vYxRsMmKhsnZmN9NOcDrJBrzycoAr8HMh8UmKwO8ehZ8NvgTbS9ilFzwH+D1sDlSwim/Kydeucw/KSi+2rE2jRcBTC74Vw1tHm/Y4O8ENrwxk4AN8GqMDIyXkglttadMEvynDya34hUCmCxDGFyPqqhpc75lefXGLhwu+Dctrc+3aLjgb5sIF+yCgbaXOxSc8rt+8kEmWUl2KR22/IJhcsbzWuYlbS5icFtEg6kHi+m7NXVLm8cbNlmx3BGCmS/hdYcy7/ugbmiYRGN2zjhsj5QwAtyKIjA5m7DJSp4WFF++w6sC6q4KBcENPZSkijcujvjioz3ejgtoqsCrH/kgfegTv4WXX/mRJ5MgwEyE18/rf/cX+M3f+T169K4tw0Td71EIQmAZ7FmWomroyGzrBgDf0lnJp7ol2mclm1HahgqLebFERLusYrdoG5rS3SHHgHfIK/asoKoIBJbOOmBeNZQwNomTTdyRgqpp6ZBV7NZkx9QwYl5sS0S7tGS3aJvd5bBM4RW0zyr27FVnk8GugNKyJkbBGkIAgWWwu7/KuqUDE5ABwB1pMJlkoDm9W86mka7CYcpLBNA+K9kjMJoq4FsGe0wiKWrKmUCknGziJsGibujInJkE5Lw2LdHu1ryCdlnJ8qqrCnyL5/WYV6xUj6JA6oN34bVuWtrfhdesBCMDOOiDd+E1K2tKb8lrdbKJe2S8tqd3+63m1Zf6YEMHCa/eSGd3DA/xahkqbAmv+6xk5bIGeMUxr4jlVQCBfXtePdmcIeNVAK6hwzJUIZQu/1cV4LtDB59+7TV6+NLPYLFY9H6edCJs25b+/Z//CV/6mz/H/7zxZbx1vsY2LbAvWvi23pWKbgyt6pZ2TDYOAO5IlzpTnJTsYWJTV+FJgv8uLVln0hQFgc07U1LUxAmwCgiEDg9eUTd0kDiTb+kwJME/TkpWe8syNN6ZCBSnJSsuqqudTWDAO+QVcWcFFdHZJAPvKJnQAtuQghczZTYAsE1N4kx0solPVnyLtYn2WcWKdA7ZlJU160wAENqGNFnZSWx6VryqikBom7fkFQgdU5qs7CU+6I10mIxNTdu9J1aiRlfZBIyos6lmeNVOvHLB/5hXbPAXQmBsS5KVqpEmYL5lsMG/bol2ScFO7LahwWZ9UM7rgA/ekdeGEqYqBch9cIhXx+yC/81/H+RVU+FJErC78JoWNaUSXgPHhPYMeLUMFf7IEDPXwGLs4MUXnsfLP/qzePHHfpF0y2FX2ADwf9cRyZEmuhxdAAAAAElFTkSuQmCC"/>
+        <image id="_Image3" width="400px" height="475px" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAHbCAYAAADlIMxjAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAPUklEQVR4nO3dzatl2VkH4Hetvc+9dS1TEoKJRGj8BxQbFSGTCE4EnQTBBCJEcOBMJ0KiQhyIgiNBJCq00OhEdGYMEoWoNMEPWmnt9EdIFG1juiv9QVvJ7ao65+y9HPQ/sPer++yzD88zL9b7owY/1lr3rF32b3ytxcJ2d++Updc43D6SYyI5ppNjOjmmu5Qc/fHt15deI3bd3cXXOL59u/gackwnx3RyTCfHdKfIURdfAYDLMx4UCAAJ4xA12uJHcQBcoBrjYe0ZANggR1gApCgQAFIUCAApCgSA+UpRIAAk1CsFAkBCKVGj9muPAcAG1Sg2IQDMpz0ASFEgAKQoEABSFAgA87VBgQCQMBw85w5ATo12XHsGADbIDgSAFHcgAKQoEABSFAgAKWX/yj+36K7XngOATRmi3930Jfrdoss8fPN28Zv6m/fdLUuvIcd0ckwnx3RyTHeKHDVqt/QaAFygGnXZ3QcAl8klOgApCgSAFAUCQIoCAWC+1hQIAAntqEAASGgtagyHtccAYINqtGHtGQDYIEdYAKQoEABSFAgAKQoEgPlKVSAAJNQ+apTFn6UH4ALV6K7WngGADaoRdiAAzOcOBIAUBQJAigIBIMFz7gBkjIMCASChjVH2rz7XonbLLjScoKe6cfk15JhOjunkmE6O6ZbOcXwc/fHRO7H0b0Fu3nNv8b8VfvjNB23pNeSYTo7p5JhOjukWz3F0hAVAkgIBIEWBAJCiQACYz3PuAKR0OwUCQEaJGnW39hQAbJAPSgGQ4ggLgBQFAkCKAgEgRYEAMN94VCAAJIzHqNEWfxQSgAtUox3XngGADbIDASDFHQgAKQoEgBQFAsB8pSgQABKq59wByCg1atRu7TEA2KAaRYEAMF/Z33/eD0EAmK3fXV0v/kWph998sHhJ3bznnhwTyTGdHNPJMd2l5HCJDkCKAgFgvtYUCAAJ4yFqhDt0AOarMRzWngGADarRxrVnAGCD3IEAkKJAAEhRIACkKBAA5iu9AgEgodaoUXQIAPPV6K7WngGADbL9ACBFgQCQokAASFEgACR4zh2AjPGoQABIaC1qjJ5zB2C+GuOw9gwAbJAjLABSFAgAKWX/9WdbdNdrzwHAlhwfR3887CMW/qrtzd17ZdkVIh7ePmhLryHHdHJMJ8d0cky3eI6+OsICIKHuokYsXrYAXKAatV97BgA2qEaxAwFgPncgAKQoEABSFAgAKQoEgPmGgwIBIKENUaMt/DN0AC5SjfG49gwAbJAjLABSFAgAKQoEgBQFAsB8pSgQABK6KwUCQEbxnDsAOTWKTQgA82kPAFIUCAApCgSAFAUCwHzjMcr+/vNt7TkA2JjhcfS73a4s/ZdYD28fLF5SN3fvlaXXkGM6OaaTYzo5pls8x7CPGuNh0TUAuEw1mhMsAOZziQ5AigIBIEWBAJCiQACYr+4UCAAJxXPuACQpEABSHGEBkKJAAEhRIACkKBAA5mtNgQCQ0I5RIzymCMBMrUWNwXPuAMxXo41rzwDABrkDASBFgQCQokAASFEgAMxXOgUCQELtokbRIQDMV/b3/7VFlLXnAGBj+uNhv/giN3fvLd5QD28fLP6Tejmmk2M6OaaTY7pT5HB+BUCKAgEgRYEAkOA5dwAyhoMCASChjVFjHNYeA4ANqtEUCADzOcICIEWBAJCiQABIUSAAzFeqAgEgobuKGsVLvADMV6P0a88AwAbZgQCQ4g4EgBQFAkCKAgEgRYEAMN/oOXcAMsYharRx7TEA2KAa43HtGQDYIEdYAKSU/defbdFdrz0HAFsyPI5+t7sq0S9bIA9vH7RFF4iIm7v3Fv9JvRzTyTGdHNPJMd3iOYa9IywAMooCASCh7hQIAAkloka3W3sMADaoRunWngGADXKEBUCKAgEgRYEAkKJAAJivjQoEgITxGDXa4r/aB+AC1Rj3a88AwAbZgQCQ4g4EgBQFAkCKAgEgpT/NMot/O+VE5DgvcpwXOc7LwjlqbwcCQELpokb1Gi8A89WovgcCwHzuQGaR47zIcV7kOC/L53AHAkCKAgEgpezvf8lbJgDM08boj4flH1O8ufvtix/GPbz91uJFKMd0ckwnx3RyTLd4juGxIywAElqLPsZDRL1ae5T/B/5y4rzIcV7kOC+XkaNGG9eeAYANcoQFQIoCASClf/cs7hLO4y4hQ4Qc50aO8yLHObEDAWA+z7kDkPJugegQAOar0XnOHYD5+igXsgMpl3EpJceZkeO8yHFWLqQ9ADg1BQJAigIBIKEpEAAShkP0F/KDyEv5Yacc50aO8yLH+Whj1BiHtccAYINqjMe1ZwBgg9yBAJCiQABI6aOUy/hV5CVkiJDj3MhxXuQ4K3YgAMxXqgIBIKHbRTm89lyL7nrtUQDYmL6/ulOWfpH30aPHbdEFIuLOnevFDxXlmE6O6eSYTo7pTpGjXsxz7gCclPYAIEWBAJCiQABI6U+xSLmQH83IcV7kOC9ynJfFc4xHOxAAEtoxarRx7TEA2KAaw37tGQDYIEdYAKQoEABSFAgAKQoEgPlKUSAAJJSdAgEgodSoUU/yY3QALowCASDFERYAKQoEgBQFAkCKAgFgvjYoEAASxkP0ES0ilv3wiA+0nBc5zosc50WOySt4zh2AnHJ87bkW/fXacwCwJcfH0Q/DEFGW/Srh1dVu8T3hfn9oS68hx3RyTCfHdHJMt3iOVl2iA5CjQABIUSAAzNd5zh2AjNJFjdKtPQYAG1Sj2609AwAb5AgLgBQFAkCKAgEgRYEAMF9rCgSAhHGvQABIaC1qjIe1xwBgg2qMw9ozALBBjrAASFEgAKQoEABSFAgA89VOgQCQUHdRoyz++V8ALlCN7mrtGQDYoBphBwLAfO5AAEgpxzdeamsPAcD29F3XLX6Gtd8fFi+pq6udHBPJMZ0c08kx3aXkcIQFwHyD59wByGij13gByPE9EABSHGEBkKJAAEhRIACkKBAA5itVgQCQ0O0UCAAZJfpTPOdeLuSbI3KcFznOixzn5RQ5ahSbEADm0x4ApCgQAFIUCAApCgSA+cajAgEgYTxGjTauPQYAG+Q5dwBSarTFP5sLwAVyBwJAigIBIEWBADBfKVGOrz3Xor9eexQAtqSN0Y/jGDEue5G+2/WLPwt5OBwX/2sAOaaTYzo5ppNjuuVzdFGj9suuAcBFUiAApLhEByBFgQCQokAASFEgAMzXRgUCQMKwjxrhMUUA5qsxeM4dgPl8UAqAFHcgAKQoEABSFAgAKQoEgPlqr0AASKh91Cg6BID5anRXa88AwAbZfgCQokAASFEgAKQoEAASmgIBIOHd59wBYKbWogyvP9+i7tYeBYAtOT6KvkYrUZfdiByPw+Jfrer7riy9hhzTyTGdHNPJMd3iOUZ3IAAkKRAAUhQIACkKBID5SqdAAEjodlGjLP7HAABcoBrVc+4AzGcHAkCKOxAAUhQIACkKBIAUBQLAfONBgQCQMA5Row1rjwHABtUYDmvPAMAGOcICIEWBAJCiQABIUSAAzFeKAgEgobtSIABklKjR7daeAoANqlG6tWcAYIPK8ObLbe0hANiefhyX74++7xb/atXxOCweRI7p5JhOjunkmO4UOVyiA5CiQACYbxwUCAAJ4yFqNHfoAMxXY9yvPQMAG2QHAkCKOxAAUhQIACkKBIAUBQLAfN1V9KWUiMV/8L68cgEZIuQ4N3KcFznOSKlRo9qEADBfjep7IADMZ/sBQIoCASBFgQCQokAAmK+NCgSAhPEQNcJjigDM1FrUGI5rjwHABtVo49ozALBB7kAASFEgAKT0pcTiL3uN47j4TX05wetkckwnx3RyTCfHdIvnGMfoa61l6QcVh2FY/D+k67rF/0fkmE6O6eSYTo7pFs9ROkdYACTUPmoUHQLAfDU6z7kDMF+9iM8RAnByzq8ASFEgAKQoEAASmgIBIGE4KhAAEtoYNUbPuQMwnwIBIMURFgApCgSAFAUCQIoCAWC+UhUIAAldH3XprxECcIlK1KiecwdgPh+UAiBFewCQokAASFEgAKQoEADmGw9Rxre+3NaeA4CNOT6Kvo1DLP2XWLXWxX9sMo7j4kUox3RyTCfHdHJMt3iOUqLGsF90DQAukzsQAFIUCAApCgSAFAUCwHylKBAAEuqVAgEgoXjOHYCkGrVbewYANsgRFgApCgSAFAUCQIoCAWC+NigQABKGQ9QInwMBYD7PuQOQUqPZgQAwnzsQAFIUCAApCgSAFAUCwHzdToEAkFA6r/ECkFPGt77s73gBmK0vpZSlFxnHcfGSqrXKMZEc08kxnRzTXUyOpRcA4DIpEABSFAgA87WmQABIGPcKBICE1qLGcFh7DAA2qEYb1p4BgA1yhAVAigIBIEWBAJCiQACYr3YKBICEuos+ln9LMU7wXuNJyHFe5DgvcpyXU+So0V0tvggAl6dGXEbbAnBa7kAASFEgAKQoEABSFAgA8w2ecwcgo41RYzyuPQYAG6RAAEhxhAVASt9aa9Ha2nP8n7V2ASFCjnMjx3mR47z0p1iknOBRllP8h8gxnRzTyTGdHNMtnqMUR1gAJJSqQABI6HYKBICM4jl3AHJqFJsQAObTHgCkKBAAUhQIACkKBID5xqMCASBhPEaNNq49BgAbVN9589W1ZwBgY7711utRX/3qy2vPAcDGvPqVl6P+2wsvrj0HABvz7y+9GPUv/upv4xK+BwLAibQxPvf5v4n6zAuvRIz7tccBYCvGQ3zxpf+Kev92H5/59CfXHgeALWhj/M6v/GI8HFrUsbV46rN/F6995YW1xwLgzP3HPz0Tf/C5v49SS9QPPfEdMUbEr37yU3Ehn+kFYAHt+Dg+9cu/FkOL+OEn7kX9xIe+O+5ed/EPX/1G/PFv/fra8wFwjsYhfvfTvxQvfv1/4u51Fx/5vu+MeoiIjz/5gTgOLX7z6c/Gb/z8z8XtW/fXHhWAM3H7xtfiFz720fj9P3smjkOLn/6BD8QhIurDx0N88H038dEn3x+llXj688/Ghz/8E/HXf/R7EW1Ye24A1tKG+MIffiZ+5Ec/El/40n9GtBIfe/L98cH33sTDx0OUpz7+ve3Orsa3Xffx9pu38dtf/O/4xjuH6EqNH/v+J+JnP/FT8UM//pNR+uuIKKkZSim5fzhDO8EFjhzTyTGdHNPJMV0qRxsjWos2HuIf//xP46mn/yT+8l9eibs3fXzPe2/iZ37wu2J3p493Hh/j0WGM/wUgmOAQOqYrhAAAAABJRU5ErkJggg=="/>
+        <linearGradient id="_Linear4" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(7.65404e-16,12.5,-0.390625,2.39189e-17,225,37.5)"><stop offset="0" style="stop-color:rgb(255,14,0);stop-opacity:0.5"/><stop offset="1" style="stop-color:rgb(255,13,0);stop-opacity:0"/></linearGradient>
+    </defs>
+</svg>
diff --git a/packages/frontend/assets/drop-and-fusion/fusion.mp3 b/packages/frontend/assets/drop-and-fusion/fusion.mp3
new file mode 100644
index 0000000000..8b4f8df6e9
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/fusion.mp3 differ
diff --git a/packages/frontend/assets/drop-and-fusion/fusion_yen.mp3 b/packages/frontend/assets/drop-and-fusion/fusion_yen.mp3
new file mode 100644
index 0000000000..e8d203fb5d
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/fusion_yen.mp3 differ
diff --git a/packages/frontend/assets/drop-and-fusion/gameover.mp3 b/packages/frontend/assets/drop-and-fusion/gameover.mp3
new file mode 100644
index 0000000000..23b41c5699
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/gameover.mp3 differ
diff --git a/packages/frontend/assets/drop-and-fusion/gameover.png b/packages/frontend/assets/drop-and-fusion/gameover.png
new file mode 100644
index 0000000000..8b622577ca
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/gameover.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/gameover_yen.mp3 b/packages/frontend/assets/drop-and-fusion/gameover_yen.mp3
new file mode 100644
index 0000000000..c7fdcb5c8f
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/gameover_yen.mp3 differ
diff --git a/packages/frontend/assets/drop-and-fusion/go.png b/packages/frontend/assets/drop-and-fusion/go.png
new file mode 100644
index 0000000000..37468f1395
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/go.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/hold.mp3 b/packages/frontend/assets/drop-and-fusion/hold.mp3
new file mode 100644
index 0000000000..f064c976d3
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/hold.mp3 differ
diff --git a/packages/frontend/assets/drop-and-fusion/logo.png b/packages/frontend/assets/drop-and-fusion/logo.png
new file mode 100644
index 0000000000..c6725bea88
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/logo.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/normal_monos/cold_face.png b/packages/frontend/assets/drop-and-fusion/normal_monos/cold_face.png
new file mode 100644
index 0000000000..f5f53e9efc
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/normal_monos/cold_face.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/normal_monos/exploding_head.png b/packages/frontend/assets/drop-and-fusion/normal_monos/exploding_head.png
new file mode 100644
index 0000000000..e8ec5182c8
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/normal_monos/exploding_head.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/normal_monos/face_with_open_mouth.png b/packages/frontend/assets/drop-and-fusion/normal_monos/face_with_open_mouth.png
new file mode 100644
index 0000000000..c523020f62
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/normal_monos/face_with_open_mouth.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/normal_monos/face_with_symbols_on_mouth.png b/packages/frontend/assets/drop-and-fusion/normal_monos/face_with_symbols_on_mouth.png
new file mode 100644
index 0000000000..db9e839c84
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/normal_monos/face_with_symbols_on_mouth.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/normal_monos/grinning_squinting_face.png b/packages/frontend/assets/drop-and-fusion/normal_monos/grinning_squinting_face.png
new file mode 100644
index 0000000000..fd72d749a1
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/normal_monos/grinning_squinting_face.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/normal_monos/heart_suit.png b/packages/frontend/assets/drop-and-fusion/normal_monos/heart_suit.png
new file mode 100644
index 0000000000..b0105f8582
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/normal_monos/heart_suit.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/normal_monos/pleading_face.png b/packages/frontend/assets/drop-and-fusion/normal_monos/pleading_face.png
new file mode 100644
index 0000000000..42f58d411c
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/normal_monos/pleading_face.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/normal_monos/smiling_face_with_hearts.png b/packages/frontend/assets/drop-and-fusion/normal_monos/smiling_face_with_hearts.png
new file mode 100644
index 0000000000..416ef0410a
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/normal_monos/smiling_face_with_hearts.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/normal_monos/smiling_face_with_sunglasses.png b/packages/frontend/assets/drop-and-fusion/normal_monos/smiling_face_with_sunglasses.png
new file mode 100644
index 0000000000..c0f72254c2
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/normal_monos/smiling_face_with_sunglasses.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/normal_monos/zany_face.png b/packages/frontend/assets/drop-and-fusion/normal_monos/zany_face.png
new file mode 100644
index 0000000000..f14f9db20b
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/normal_monos/zany_face.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/ready.png b/packages/frontend/assets/drop-and-fusion/ready.png
new file mode 100644
index 0000000000..10a87fcf58
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/ready.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/square_monos/keycap_1.png b/packages/frontend/assets/drop-and-fusion/square_monos/keycap_1.png
new file mode 100644
index 0000000000..d672f2854a
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/square_monos/keycap_1.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/square_monos/keycap_10.png b/packages/frontend/assets/drop-and-fusion/square_monos/keycap_10.png
new file mode 100644
index 0000000000..32cf193540
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/square_monos/keycap_10.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/square_monos/keycap_2.png b/packages/frontend/assets/drop-and-fusion/square_monos/keycap_2.png
new file mode 100644
index 0000000000..81c3f58e6e
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/square_monos/keycap_2.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/square_monos/keycap_3.png b/packages/frontend/assets/drop-and-fusion/square_monos/keycap_3.png
new file mode 100644
index 0000000000..424d8c123d
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/square_monos/keycap_3.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/square_monos/keycap_4.png b/packages/frontend/assets/drop-and-fusion/square_monos/keycap_4.png
new file mode 100644
index 0000000000..ea6ae50531
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/square_monos/keycap_4.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/square_monos/keycap_5.png b/packages/frontend/assets/drop-and-fusion/square_monos/keycap_5.png
new file mode 100644
index 0000000000..ad435da69a
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/square_monos/keycap_5.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/square_monos/keycap_6.png b/packages/frontend/assets/drop-and-fusion/square_monos/keycap_6.png
new file mode 100644
index 0000000000..70c9522b43
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/square_monos/keycap_6.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/square_monos/keycap_7.png b/packages/frontend/assets/drop-and-fusion/square_monos/keycap_7.png
new file mode 100644
index 0000000000..5a24307487
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/square_monos/keycap_7.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/square_monos/keycap_8.png b/packages/frontend/assets/drop-and-fusion/square_monos/keycap_8.png
new file mode 100644
index 0000000000..9689d8ecfb
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/square_monos/keycap_8.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/square_monos/keycap_9.png b/packages/frontend/assets/drop-and-fusion/square_monos/keycap_9.png
new file mode 100644
index 0000000000..ac3f638841
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/square_monos/keycap_9.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/sweets_monos/candy_color.svg b/packages/frontend/assets/drop-and-fusion/sweets_monos/candy_color.svg
new file mode 100644
index 0000000000..6eab3ca49b
--- /dev/null
+++ b/packages/frontend/assets/drop-and-fusion/sweets_monos/candy_color.svg
@@ -0,0 +1,86 @@
+<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M13 3.99998C13 2.89998 12.2135 2.08134 11.0625 1.9375C9.875 1.78909 9.10938 2.49997 8.51562 3.57811C8.20282 4.14611 7.69531 4.34368 7.13281 4.15621C6.07292 3.80295 5.07886 3.24214 4.07812 4.38277C3.35156 5.21089 3.46006 6.16444 4.07812 7.07811C4.4375 7.60936 4.46599 8.50491 3.85938 8.85154C2.98438 9.35154 2.01562 9.89998 2.01562 11C2.01562 12.5937 3.22812 13.375 4.32812 13.375L13 13V3.99998Z" fill="url(#paint0_linear_18_32303)"/>
+<path d="M13 3.99998C13 2.89998 12.2135 2.08134 11.0625 1.9375C9.875 1.78909 9.10938 2.49997 8.51562 3.57811C8.20282 4.14611 7.69531 4.34368 7.13281 4.15621C6.07292 3.80295 5.07886 3.24214 4.07812 4.38277C3.35156 5.21089 3.46006 6.16444 4.07812 7.07811C4.4375 7.60936 4.46599 8.50491 3.85938 8.85154C2.98438 9.35154 2.01562 9.89998 2.01562 11C2.01562 12.5937 3.22812 13.375 4.32812 13.375L13 13V3.99998Z" fill="url(#paint1_radial_18_32303)"/>
+<path d="M13 3.99998C13 2.89998 12.2135 2.08134 11.0625 1.9375C9.875 1.78909 9.10938 2.49997 8.51562 3.57811C8.20282 4.14611 7.69531 4.34368 7.13281 4.15621C6.07292 3.80295 5.07886 3.24214 4.07812 4.38277C3.35156 5.21089 3.46006 6.16444 4.07812 7.07811C4.4375 7.60936 4.46599 8.50491 3.85938 8.85154C2.98438 9.35154 2.01562 9.89998 2.01562 11C2.01562 12.5937 3.22812 13.375 4.32812 13.375L13 13V3.99998Z" fill="url(#paint2_radial_18_32303)"/>
+<path d="M16 26C21.5228 26 26 21.5228 26 16C26 10.4772 21.5228 6 16 6C10.4772 6 6 10.4772 6 16C6 21.5228 10.4772 26 16 26Z" fill="url(#paint3_radial_18_32303)"/>
+<path d="M16 26C21.5228 26 26 21.5228 26 16C26 10.4772 21.5228 6 16 6C10.4772 6 6 10.4772 6 16C6 21.5228 10.4772 26 16 26Z" fill="url(#paint4_radial_18_32303)"/>
+<path d="M16 26C21.5228 26 26 21.5228 26 16C26 10.4772 21.5228 6 16 6C10.4772 6 6 10.4772 6 16C6 21.5228 10.4772 26 16 26Z" fill="url(#paint5_radial_18_32303)"/>
+<path d="M13.2344 13.0156C16.5007 9.6733 23.0156 8.23438 25.625 13.2695C25.3867 12.4766 24.7656 10.5391 23.0156 8.87496C19.5156 5.98433 13.8416 7.95125 10.5937 11.5937C8.46307 13.9833 5.57031 19.7031 9.61719 23.6992C10.4609 24.3555 11.8437 25.4844 14.1719 25.8281C6.98438 22.6875 10.3465 15.9707 13.2344 13.0156Z" fill="url(#paint6_linear_18_32303)"/>
+<path d="M19.1094 24.1719C15.2031 24 15.2701 20.2321 17.2812 17.9766C20.6387 14.211 24.2891 16.0156 24.2187 19.0703H25.1808C26.4349 14.6766 23.3913 12.9922 20.9375 12.9922C17.3586 12.9922 14.1562 16.1094 13.2266 19.4609C12.4375 22.6406 14.2266 26.375 19.1094 25.125V24.1719Z" fill="#C62561"/>
+<path d="M19.1094 24.1719C15.2031 24 15.2701 20.2321 17.2812 17.9766C20.6387 14.211 24.2891 16.0156 24.2187 19.0703H25.1808C26.4349 14.6766 23.3913 12.9922 20.9375 12.9922C17.3586 12.9922 14.1562 16.1094 13.2266 19.4609C12.4375 22.6406 14.2266 26.375 19.1094 25.125V24.1719Z" fill="url(#paint7_radial_18_32303)"/>
+<path d="M19 28C19 29.1 19.9 30 21 30C22.1 30 23 29.1 23 28C23 27.2 23.97 26.8 24.54 27.36L24.59 27.41C25.37 28.19 26.64 28.19 27.42 27.41C28.2 26.63 28.2 25.36 27.42 24.58L27.37 24.53C26.8 23.97 27.2 23 28 23C29.1 23 30 22.1 30 21C30 19.9 29.1 19 28 19H22.5C20.57 19 19 20.57 19 22.5V28Z" fill="url(#paint8_linear_18_32303)"/>
+<path d="M8.85156 9.00391C6.21406 11.6414 5.88281 14.8047 6.03906 16.8672C6.03906 16.8672 5.95968 12.9153 9.29687 9.57814C12.6341 6.24095 16.4219 6.01561 16.4219 6.01561C14.3125 5.89846 11.4891 6.36641 8.85156 9.00391Z" fill="#A72D36"/>
+<g filter="url(#filter0_f_18_32303)">
+<path d="M26.6641 24.0547C27.1367 24.0039 26.6094 23.2812 27.211 22.1406C26.4844 22.6484 26.1914 24.1055 26.6641 24.0547Z" fill="#E75372"/>
+</g>
+<g filter="url(#filter1_f_18_32303)">
+<path d="M27.7196 25.3724C27.9826 25.8229 27.9149 26.8411 26.9618 27.263C25.7243 27.6943 26.9514 26.1823 27.7196 25.3724Z" fill="url(#paint9_radial_18_32303)"/>
+</g>
+<g filter="url(#filter2_f_18_32303)">
+<path d="M29.5156 19.9688C29.8385 20.4434 30.125 21.5352 28.75 22.1618C27.925 22.5377 28.6927 20.9656 29.5156 19.9688Z" fill="url(#paint10_radial_18_32303)"/>
+</g>
+<defs>
+<filter id="filter0_f_18_32303" x="26.0343" y="21.7406" width="1.57663" height="2.71534" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.2" result="effect1_foregroundBlur_18_32303"/>
+</filter>
+<filter id="filter1_f_18_32303" x="26.1843" y="25.1224" width="1.91805" height="2.46649" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.125" result="effect1_foregroundBlur_18_32303"/>
+</filter>
+<filter id="filter2_f_18_32303" x="27.996" y="19.5687" width="2.18687" height="3.04994" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.2" result="effect1_foregroundBlur_18_32303"/>
+</filter>
+<linearGradient id="paint0_linear_18_32303" x1="2.01562" y1="7.64644" x2="13" y2="7.64644" gradientUnits="userSpaceOnUse">
+<stop stop-color="#AA1C3D"/>
+<stop offset="1" stop-color="#C31D45"/>
+</linearGradient>
+<radialGradient id="paint1_radial_18_32303" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(8 11.1875) rotate(123.69) scale(5.18298 4.96914)">
+<stop stop-color="#951731"/>
+<stop offset="1" stop-color="#9D1934" stop-opacity="0"/>
+</radialGradient>
+<radialGradient id="paint2_radial_18_32303" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(13.2187 4.40625) rotate(90) scale(5.0625 1.3908)">
+<stop stop-color="#EC516B"/>
+<stop offset="1" stop-color="#EB506C" stop-opacity="0"/>
+</radialGradient>
+<radialGradient id="paint3_radial_18_32303" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(21.8125 11.8125) rotate(161.633) scale(16.6613)">
+<stop stop-color="#FFD95A"/>
+<stop offset="0.423359" stop-color="#EEB53D"/>
+<stop offset="0.787547" stop-color="#CA8631"/>
+<stop offset="1" stop-color="#B28341"/>
+</radialGradient>
+<radialGradient id="paint4_radial_18_32303" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(15.625 26.6875) rotate(104.903) scale(8.01975 13.7205)">
+<stop stop-color="#CD7677"/>
+<stop offset="1" stop-color="#CE7A85" stop-opacity="0"/>
+</radialGradient>
+<radialGradient id="paint5_radial_18_32303" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(20.0625 22.0625) rotate(151.844) scale(5.03309 3.80247)">
+<stop stop-color="#CF771E"/>
+<stop offset="1" stop-color="#C96D2E" stop-opacity="0"/>
+</radialGradient>
+<linearGradient id="paint6_linear_18_32303" x1="24.0625" y1="9.8125" x2="9.6875" y2="23.875" gradientUnits="userSpaceOnUse">
+<stop stop-color="#F54353"/>
+<stop offset="0.485245" stop-color="#C01C47"/>
+<stop offset="1" stop-color="#C2355A"/>
+</linearGradient>
+<radialGradient id="paint7_radial_18_32303" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(21.625 12.5625) rotate(73.1132) scale(7.31544 7.34222)">
+<stop offset="0.341752" stop-color="#F25271"/>
+<stop offset="1" stop-color="#F15372" stop-opacity="0"/>
+</radialGradient>
+<linearGradient id="paint8_linear_18_32303" x1="20.3125" y1="20.3125" x2="27.5" y2="27.75" gradientUnits="userSpaceOnUse">
+<stop stop-color="#BF242E"/>
+<stop offset="1" stop-color="#CF1E51"/>
+</linearGradient>
+<radialGradient id="paint9_radial_18_32303" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(27.8524 26.1536) rotate(118.896) scale(1.8218 1.06121)">
+<stop stop-color="#ED5372"/>
+<stop offset="1" stop-color="#ED5372" stop-opacity="0"/>
+</radialGradient>
+<radialGradient id="paint10_radial_18_32303" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(29.7828 21.2031) rotate(120.115) scale(1.71599 0.933717)">
+<stop stop-color="#ED5372"/>
+<stop offset="1" stop-color="#ED5372" stop-opacity="0"/>
+</radialGradient>
+</defs>
+</svg>
diff --git a/packages/frontend/assets/drop-and-fusion/sweets_monos/chocolate_bar_color.svg b/packages/frontend/assets/drop-and-fusion/sweets_monos/chocolate_bar_color.svg
new file mode 100644
index 0000000000..eea5fec186
--- /dev/null
+++ b/packages/frontend/assets/drop-and-fusion/sweets_monos/chocolate_bar_color.svg
@@ -0,0 +1,316 @@
+<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M10.9844 22.625L2.875 14.4531C1.5547 13.1328 2.04687 11.8125 2.62501 11.2344L11.1875 2.71876C12.211 1.69532 13.375 1.89065 14.3125 2.82815L22.375 10.8281L10.9844 22.625Z" fill="#8A584C"/>
+<path d="M10.9844 22.625L2.875 14.4531C1.5547 13.1328 2.04687 11.8125 2.62501 11.2344L11.1875 2.71876C12.211 1.69532 13.375 1.89065 14.3125 2.82815L22.375 10.8281L10.9844 22.625Z" fill="url(#paint0_radial_18_32062)"/>
+<path d="M10.9844 22.625L2.875 14.4531C1.5547 13.1328 2.04687 11.8125 2.62501 11.2344L11.1875 2.71876C12.211 1.69532 13.375 1.89065 14.3125 2.82815L22.375 10.8281L10.9844 22.625Z" fill="url(#paint1_radial_18_32062)"/>
+<path d="M10.9844 22.625L2.875 14.4531C1.5547 13.1328 2.04687 11.8125 2.62501 11.2344L11.1875 2.71876C12.211 1.69532 13.375 1.89065 14.3125 2.82815L22.375 10.8281L10.9844 22.625Z" fill="url(#paint2_radial_18_32062)"/>
+<g filter="url(#filter0_f_18_32062)">
+<path d="M6.46309 9.72343C6.47658 9.71056 6.49039 9.6979 6.50466 9.6859C7.03753 9.23812 7.71518 9.24807 8.23887 9.69368C8.24699 9.70059 8.25463 9.70742 8.26249 9.71463C8.36319 9.80688 9.10895 10.4917 9.67969 11.0625C10.2582 11.641 10.9331 12.3785 11.0077 12.4602C11.0125 12.4654 11.0169 12.4703 11.0215 12.4756C11.4733 12.9952 11.5049 13.6871 11.0259 14.2228C11.0184 14.2312 11.0113 14.2392 11.0039 14.2477C10.9204 14.3435 10.357 14.9867 9.67969 15.664C9.02564 16.3181 8.40257 16.8978 8.2741 17.0168C8.25775 17.0319 8.24131 17.0464 8.22388 17.0602C7.64411 17.5221 7.00112 17.4542 6.48458 17C6.48373 16.9993 6.48346 16.999 6.48261 16.9983C6.45576 16.9747 5.65368 16.2708 5.04688 15.664C4.44065 15.0578 3.74501 14.2642 3.72049 14.2362C3.71971 14.2353 3.71942 14.235 3.71865 14.2341C3.25359 13.6982 3.26579 12.9793 3.70954 12.4791C3.71632 12.4715 3.72307 12.4642 3.73012 12.4568C3.82368 12.3587 4.53588 11.6125 5.08595 11.0625C5.62082 10.5276 6.33466 9.84586 6.46309 9.72343Z" fill="#824534"/>
+<path d="M6.46309 9.72343C6.47658 9.71056 6.49039 9.6979 6.50466 9.6859C7.03753 9.23812 7.71518 9.24807 8.23887 9.69368C8.24699 9.70059 8.25463 9.70742 8.26249 9.71463C8.36319 9.80688 9.10895 10.4917 9.67969 11.0625C10.2582 11.641 10.9331 12.3785 11.0077 12.4602C11.0125 12.4654 11.0169 12.4703 11.0215 12.4756C11.4733 12.9952 11.5049 13.6871 11.0259 14.2228C11.0184 14.2312 11.0113 14.2392 11.0039 14.2477C10.9204 14.3435 10.357 14.9867 9.67969 15.664C9.02564 16.3181 8.40257 16.8978 8.2741 17.0168C8.25775 17.0319 8.24131 17.0464 8.22388 17.0602C7.64411 17.5221 7.00112 17.4542 6.48458 17C6.48373 16.9993 6.48346 16.999 6.48261 16.9983C6.45576 16.9747 5.65368 16.2708 5.04688 15.664C4.44065 15.0578 3.74501 14.2642 3.72049 14.2362C3.71971 14.2353 3.71942 14.235 3.71865 14.2341C3.25359 13.6982 3.26579 12.9793 3.70954 12.4791C3.71632 12.4715 3.72307 12.4642 3.73012 12.4568C3.82368 12.3587 4.53588 11.6125 5.08595 11.0625C5.62082 10.5276 6.33466 9.84586 6.46309 9.72343Z" fill="url(#paint3_linear_18_32062)"/>
+<path d="M6.46309 9.72343C6.47658 9.71056 6.49039 9.6979 6.50466 9.6859C7.03753 9.23812 7.71518 9.24807 8.23887 9.69368C8.24699 9.70059 8.25463 9.70742 8.26249 9.71463C8.36319 9.80688 9.10895 10.4917 9.67969 11.0625C10.2582 11.641 10.9331 12.3785 11.0077 12.4602C11.0125 12.4654 11.0169 12.4703 11.0215 12.4756C11.4733 12.9952 11.5049 13.6871 11.0259 14.2228C11.0184 14.2312 11.0113 14.2392 11.0039 14.2477C10.9204 14.3435 10.357 14.9867 9.67969 15.664C9.02564 16.3181 8.40257 16.8978 8.2741 17.0168C8.25775 17.0319 8.24131 17.0464 8.22388 17.0602C7.64411 17.5221 7.00112 17.4542 6.48458 17C6.48373 16.9993 6.48346 16.999 6.48261 16.9983C6.45576 16.9747 5.65368 16.2708 5.04688 15.664C4.44065 15.0578 3.74501 14.2642 3.72049 14.2362C3.71971 14.2353 3.71942 14.235 3.71865 14.2341C3.25359 13.6982 3.26579 12.9793 3.70954 12.4791C3.71632 12.4715 3.72307 12.4642 3.73012 12.4568C3.82368 12.3587 4.53588 11.6125 5.08595 11.0625C5.62082 10.5276 6.33466 9.84586 6.46309 9.72343Z" fill="#7B3E41"/>
+</g>
+<path d="M6.77559 9.16093C6.78908 9.14806 6.80289 9.1354 6.81716 9.1234C7.35003 8.67562 8.02768 8.68557 8.55137 9.13118C8.55949 9.13809 8.56713 9.14492 8.57499 9.15213C8.67569 9.24438 9.42145 9.92923 9.99219 10.5C10.5707 11.0785 11.2456 11.816 11.3202 11.8977C11.325 11.9029 11.3294 11.9078 11.334 11.9131C11.7858 12.4327 11.8174 13.1246 11.3384 13.6603C11.3309 13.6687 11.3238 13.6767 11.3164 13.6852C11.2329 13.781 10.6695 14.4242 9.99219 15.1015C9.33814 15.7556 8.71507 16.3353 8.5866 16.4543C8.57025 16.4694 8.55381 16.4839 8.53638 16.4977C7.95661 16.9596 7.31362 16.8917 6.79708 16.4375C6.79623 16.4368 6.79596 16.4365 6.79511 16.4358C6.76826 16.4122 5.96618 15.7083 5.35938 15.1015C4.75315 14.4953 4.05751 13.7017 4.03299 13.6737C4.03221 13.6728 4.03192 13.6725 4.03115 13.6716C3.56609 13.1357 3.57829 12.4168 4.02204 11.9166C4.02882 11.909 4.03557 11.9017 4.04262 11.8943C4.13618 11.7962 4.84838 11.05 5.39845 10.5C5.93332 9.96511 6.64716 9.28336 6.77559 9.16093Z" fill="#824534"/>
+<path d="M6.77559 9.16093C6.78908 9.14806 6.80289 9.1354 6.81716 9.1234C7.35003 8.67562 8.02768 8.68557 8.55137 9.13118C8.55949 9.13809 8.56713 9.14492 8.57499 9.15213C8.67569 9.24438 9.42145 9.92923 9.99219 10.5C10.5707 11.0785 11.2456 11.816 11.3202 11.8977C11.325 11.9029 11.3294 11.9078 11.334 11.9131C11.7858 12.4327 11.8174 13.1246 11.3384 13.6603C11.3309 13.6687 11.3238 13.6767 11.3164 13.6852C11.2329 13.781 10.6695 14.4242 9.99219 15.1015C9.33814 15.7556 8.71507 16.3353 8.5866 16.4543C8.57025 16.4694 8.55381 16.4839 8.53638 16.4977C7.95661 16.9596 7.31362 16.8917 6.79708 16.4375C6.79623 16.4368 6.79596 16.4365 6.79511 16.4358C6.76826 16.4122 5.96618 15.7083 5.35938 15.1015C4.75315 14.4953 4.05751 13.7017 4.03299 13.6737C4.03221 13.6728 4.03192 13.6725 4.03115 13.6716C3.56609 13.1357 3.57829 12.4168 4.02204 11.9166C4.02882 11.909 4.03557 11.9017 4.04262 11.8943C4.13618 11.7962 4.84838 11.05 5.39845 10.5C5.93332 9.96511 6.64716 9.28336 6.77559 9.16093Z" fill="url(#paint4_linear_18_32062)"/>
+<path d="M6.77559 9.16093C6.78908 9.14806 6.80289 9.1354 6.81716 9.1234C7.35003 8.67562 8.02768 8.68557 8.55137 9.13118C8.55949 9.13809 8.56713 9.14492 8.57499 9.15213C8.67569 9.24438 9.42145 9.92923 9.99219 10.5C10.5707 11.0785 11.2456 11.816 11.3202 11.8977C11.325 11.9029 11.3294 11.9078 11.334 11.9131C11.7858 12.4327 11.8174 13.1246 11.3384 13.6603C11.3309 13.6687 11.3238 13.6767 11.3164 13.6852C11.2329 13.781 10.6695 14.4242 9.99219 15.1015C9.33814 15.7556 8.71507 16.3353 8.5866 16.4543C8.57025 16.4694 8.55381 16.4839 8.53638 16.4977C7.95661 16.9596 7.31362 16.8917 6.79708 16.4375C6.79623 16.4368 6.79596 16.4365 6.79511 16.4358C6.76826 16.4122 5.96618 15.7083 5.35938 15.1015C4.75315 14.4953 4.05751 13.7017 4.03299 13.6737C4.03221 13.6728 4.03192 13.6725 4.03115 13.6716C3.56609 13.1357 3.57829 12.4168 4.02204 11.9166C4.02882 11.909 4.03557 11.9017 4.04262 11.8943C4.13618 11.7962 4.84838 11.05 5.39845 10.5C5.93332 9.96511 6.64716 9.28336 6.77559 9.16093Z" fill="url(#paint5_linear_18_32062)"/>
+<path d="M6.77559 9.16093C6.78908 9.14806 6.80289 9.1354 6.81716 9.1234C7.35003 8.67562 8.02768 8.68557 8.55137 9.13118C8.55949 9.13809 8.56713 9.14492 8.57499 9.15213C8.67569 9.24438 9.42145 9.92923 9.99219 10.5C10.5707 11.0785 11.2456 11.816 11.3202 11.8977C11.325 11.9029 11.3294 11.9078 11.334 11.9131C11.7858 12.4327 11.8174 13.1246 11.3384 13.6603C11.3309 13.6687 11.3238 13.6767 11.3164 13.6852C11.2329 13.781 10.6695 14.4242 9.99219 15.1015C9.33814 15.7556 8.71507 16.3353 8.5866 16.4543C8.57025 16.4694 8.55381 16.4839 8.53638 16.4977C7.95661 16.9596 7.31362 16.8917 6.79708 16.4375C6.79623 16.4368 6.79596 16.4365 6.79511 16.4358C6.76826 16.4122 5.96618 15.7083 5.35938 15.1015C4.75315 14.4953 4.05751 13.7017 4.03299 13.6737C4.03221 13.6728 4.03192 13.6725 4.03115 13.6716C3.56609 13.1357 3.57829 12.4168 4.02204 11.9166C4.02882 11.909 4.03557 11.9017 4.04262 11.8943C4.13618 11.7962 4.84838 11.05 5.39845 10.5C5.93332 9.96511 6.64716 9.28336 6.77559 9.16093Z" fill="url(#paint6_linear_18_32062)"/>
+<path d="M6.77559 9.16093C6.78908 9.14806 6.80289 9.1354 6.81716 9.1234C7.35003 8.67562 8.02768 8.68557 8.55137 9.13118C8.55949 9.13809 8.56713 9.14492 8.57499 9.15213C8.67569 9.24438 9.42145 9.92923 9.99219 10.5C10.5707 11.0785 11.2456 11.816 11.3202 11.8977C11.325 11.9029 11.3294 11.9078 11.334 11.9131C11.7858 12.4327 11.8174 13.1246 11.3384 13.6603C11.3309 13.6687 11.3238 13.6767 11.3164 13.6852C11.2329 13.781 10.6695 14.4242 9.99219 15.1015C9.33814 15.7556 8.71507 16.3353 8.5866 16.4543C8.57025 16.4694 8.55381 16.4839 8.53638 16.4977C7.95661 16.9596 7.31362 16.8917 6.79708 16.4375C6.79623 16.4368 6.79596 16.4365 6.79511 16.4358C6.76826 16.4122 5.96618 15.7083 5.35938 15.1015C4.75315 14.4953 4.05751 13.7017 4.03299 13.6737C4.03221 13.6728 4.03192 13.6725 4.03115 13.6716C3.56609 13.1357 3.57829 12.4168 4.02204 11.9166C4.02882 11.909 4.03557 11.9017 4.04262 11.8943C4.13618 11.7962 4.84838 11.05 5.39845 10.5C5.93332 9.96511 6.64716 9.28336 6.77559 9.16093Z" fill="url(#paint7_linear_18_32062)"/>
+<g filter="url(#filter1_f_18_32062)">
+<path d="M4.39003 13.1845C4.2339 13.0047 4.22852 12.7406 4.38087 12.5576C4.67465 12.2047 5.19322 11.6037 5.80061 10.9963C6.40743 10.3895 7.00484 9.87436 7.35569 9.58263C7.53818 9.43089 7.80117 9.43597 7.98065 9.59126C8.38765 9.94341 9.10397 10.5727 9.55267 11.0214C9.99768 11.4664 10.6204 12.1747 10.9741 12.5834C11.1334 12.7674 11.1344 13.0384 10.9761 13.2233C10.6339 13.6226 10.0351 14.3086 9.57032 14.7734C9.1059 15.2379 8.42064 15.836 8.02119 16.1783C7.8359 16.3371 7.56387 16.3355 7.37965 16.1754C6.96194 15.8126 6.23492 15.1724 5.8183 14.7558C5.39836 14.3359 4.7513 13.6006 4.39003 13.1845Z" fill="url(#paint8_linear_18_32062)"/>
+<path d="M4.39003 13.1845C4.2339 13.0047 4.22852 12.7406 4.38087 12.5576C4.67465 12.2047 5.19322 11.6037 5.80061 10.9963C6.40743 10.3895 7.00484 9.87436 7.35569 9.58263C7.53818 9.43089 7.80117 9.43597 7.98065 9.59126C8.38765 9.94341 9.10397 10.5727 9.55267 11.0214C9.99768 11.4664 10.6204 12.1747 10.9741 12.5834C11.1334 12.7674 11.1344 13.0384 10.9761 13.2233C10.6339 13.6226 10.0351 14.3086 9.57032 14.7734C9.1059 15.2379 8.42064 15.836 8.02119 16.1783C7.8359 16.3371 7.56387 16.3355 7.37965 16.1754C6.96194 15.8126 6.23492 15.1724 5.8183 14.7558C5.39836 14.3359 4.7513 13.6006 4.39003 13.1845Z" fill="url(#paint9_linear_18_32062)"/>
+<path d="M4.39003 13.1845C4.2339 13.0047 4.22852 12.7406 4.38087 12.5576C4.67465 12.2047 5.19322 11.6037 5.80061 10.9963C6.40743 10.3895 7.00484 9.87436 7.35569 9.58263C7.53818 9.43089 7.80117 9.43597 7.98065 9.59126C8.38765 9.94341 9.10397 10.5727 9.55267 11.0214C9.99768 11.4664 10.6204 12.1747 10.9741 12.5834C11.1334 12.7674 11.1344 13.0384 10.9761 13.2233C10.6339 13.6226 10.0351 14.3086 9.57032 14.7734C9.1059 15.2379 8.42064 15.836 8.02119 16.1783C7.8359 16.3371 7.56387 16.3355 7.37965 16.1754C6.96194 15.8126 6.23492 15.1724 5.8183 14.7558C5.39836 14.3359 4.7513 13.6006 4.39003 13.1845Z" fill="url(#paint10_linear_18_32062)"/>
+</g>
+<path d="M11.9631 4.00468C11.9766 3.99181 11.9904 3.97915 12.0047 3.96715C12.5375 3.51937 13.2152 3.52932 13.7389 3.97493C13.747 3.98184 13.7546 3.98867 13.7625 3.99588C13.8632 4.08813 14.6089 4.77298 15.1797 5.34373C15.7582 5.92224 16.4331 6.6597 16.5077 6.74144C16.5125 6.74663 16.5169 6.75157 16.5215 6.75687C16.9733 7.27646 17.0049 7.96838 16.5259 8.50405C16.5184 8.51244 16.5113 8.52045 16.5039 8.52893C16.4204 8.62477 15.857 9.26797 15.1797 9.94529C14.5256 10.5993 13.9026 11.179 13.7741 11.298C13.7577 11.3132 13.7413 11.3276 13.7239 11.3415C13.1441 11.8034 12.5011 11.7354 11.9846 11.2813C11.9837 11.2805 11.9835 11.2803 11.9826 11.2795C11.9558 11.256 11.1537 10.5521 10.5469 9.94529C9.94065 9.33906 9.24501 8.54543 9.22049 8.51743C9.21971 8.51654 9.21942 8.51621 9.21865 8.51531C8.75359 7.97944 8.76579 7.26059 9.20954 6.76036C9.21632 6.75271 9.22307 6.74549 9.23012 6.73809C9.32368 6.63994 10.0359 5.89379 10.5859 5.34373C11.1208 4.80886 11.8347 4.12711 11.9631 4.00468Z" fill="#824534"/>
+<path d="M11.9631 4.00468C11.9766 3.99181 11.9904 3.97915 12.0047 3.96715C12.5375 3.51937 13.2152 3.52932 13.7389 3.97493C13.747 3.98184 13.7546 3.98867 13.7625 3.99588C13.8632 4.08813 14.6089 4.77298 15.1797 5.34373C15.7582 5.92224 16.4331 6.6597 16.5077 6.74144C16.5125 6.74663 16.5169 6.75157 16.5215 6.75687C16.9733 7.27646 17.0049 7.96838 16.5259 8.50405C16.5184 8.51244 16.5113 8.52045 16.5039 8.52893C16.4204 8.62477 15.857 9.26797 15.1797 9.94529C14.5256 10.5993 13.9026 11.179 13.7741 11.298C13.7577 11.3132 13.7413 11.3276 13.7239 11.3415C13.1441 11.8034 12.5011 11.7354 11.9846 11.2813C11.9837 11.2805 11.9835 11.2803 11.9826 11.2795C11.9558 11.256 11.1537 10.5521 10.5469 9.94529C9.94065 9.33906 9.24501 8.54543 9.22049 8.51743C9.21971 8.51654 9.21942 8.51621 9.21865 8.51531C8.75359 7.97944 8.76579 7.26059 9.20954 6.76036C9.21632 6.75271 9.22307 6.74549 9.23012 6.73809C9.32368 6.63994 10.0359 5.89379 10.5859 5.34373C11.1208 4.80886 11.8347 4.12711 11.9631 4.00468Z" fill="url(#paint11_linear_18_32062)"/>
+<path d="M11.9631 4.00468C11.9766 3.99181 11.9904 3.97915 12.0047 3.96715C12.5375 3.51937 13.2152 3.52932 13.7389 3.97493C13.747 3.98184 13.7546 3.98867 13.7625 3.99588C13.8632 4.08813 14.6089 4.77298 15.1797 5.34373C15.7582 5.92224 16.4331 6.6597 16.5077 6.74144C16.5125 6.74663 16.5169 6.75157 16.5215 6.75687C16.9733 7.27646 17.0049 7.96838 16.5259 8.50405C16.5184 8.51244 16.5113 8.52045 16.5039 8.52893C16.4204 8.62477 15.857 9.26797 15.1797 9.94529C14.5256 10.5993 13.9026 11.179 13.7741 11.298C13.7577 11.3132 13.7413 11.3276 13.7239 11.3415C13.1441 11.8034 12.5011 11.7354 11.9846 11.2813C11.9837 11.2805 11.9835 11.2803 11.9826 11.2795C11.9558 11.256 11.1537 10.5521 10.5469 9.94529C9.94065 9.33906 9.24501 8.54543 9.22049 8.51743C9.21971 8.51654 9.21942 8.51621 9.21865 8.51531C8.75359 7.97944 8.76579 7.26059 9.20954 6.76036C9.21632 6.75271 9.22307 6.74549 9.23012 6.73809C9.32368 6.63994 10.0359 5.89379 10.5859 5.34373C11.1208 4.80886 11.8347 4.12711 11.9631 4.00468Z" fill="url(#paint12_linear_18_32062)"/>
+<path d="M11.9631 4.00468C11.9766 3.99181 11.9904 3.97915 12.0047 3.96715C12.5375 3.51937 13.2152 3.52932 13.7389 3.97493C13.747 3.98184 13.7546 3.98867 13.7625 3.99588C13.8632 4.08813 14.6089 4.77298 15.1797 5.34373C15.7582 5.92224 16.4331 6.6597 16.5077 6.74144C16.5125 6.74663 16.5169 6.75157 16.5215 6.75687C16.9733 7.27646 17.0049 7.96838 16.5259 8.50405C16.5184 8.51244 16.5113 8.52045 16.5039 8.52893C16.4204 8.62477 15.857 9.26797 15.1797 9.94529C14.5256 10.5993 13.9026 11.179 13.7741 11.298C13.7577 11.3132 13.7413 11.3276 13.7239 11.3415C13.1441 11.8034 12.5011 11.7354 11.9846 11.2813C11.9837 11.2805 11.9835 11.2803 11.9826 11.2795C11.9558 11.256 11.1537 10.5521 10.5469 9.94529C9.94065 9.33906 9.24501 8.54543 9.22049 8.51743C9.21971 8.51654 9.21942 8.51621 9.21865 8.51531C8.75359 7.97944 8.76579 7.26059 9.20954 6.76036C9.21632 6.75271 9.22307 6.74549 9.23012 6.73809C9.32368 6.63994 10.0359 5.89379 10.5859 5.34373C11.1208 4.80886 11.8347 4.12711 11.9631 4.00468Z" fill="url(#paint13_linear_18_32062)"/>
+<path d="M11.9631 4.00468C11.9766 3.99181 11.9904 3.97915 12.0047 3.96715C12.5375 3.51937 13.2152 3.52932 13.7389 3.97493C13.747 3.98184 13.7546 3.98867 13.7625 3.99588C13.8632 4.08813 14.6089 4.77298 15.1797 5.34373C15.7582 5.92224 16.4331 6.6597 16.5077 6.74144C16.5125 6.74663 16.5169 6.75157 16.5215 6.75687C16.9733 7.27646 17.0049 7.96838 16.5259 8.50405C16.5184 8.51244 16.5113 8.52045 16.5039 8.52893C16.4204 8.62477 15.857 9.26797 15.1797 9.94529C14.5256 10.5993 13.9026 11.179 13.7741 11.298C13.7577 11.3132 13.7413 11.3276 13.7239 11.3415C13.1441 11.8034 12.5011 11.7354 11.9846 11.2813C11.9837 11.2805 11.9835 11.2803 11.9826 11.2795C11.9558 11.256 11.1537 10.5521 10.5469 9.94529C9.94065 9.33906 9.24501 8.54543 9.22049 8.51743C9.21971 8.51654 9.21942 8.51621 9.21865 8.51531C8.75359 7.97944 8.76579 7.26059 9.20954 6.76036C9.21632 6.75271 9.22307 6.74549 9.23012 6.73809C9.32368 6.63994 10.0359 5.89379 10.5859 5.34373C11.1208 4.80886 11.8347 4.12711 11.9631 4.00468Z" fill="url(#paint14_linear_18_32062)"/>
+<g filter="url(#filter2_f_18_32062)">
+<path d="M9.58023 8.02622C9.42436 7.84587 9.41986 7.5814 9.5728 7.39856C9.87588 7.03625 10.4173 6.41088 11.0507 5.7774C11.6836 5.1445 12.3052 4.60674 12.6654 4.30578C12.8477 4.15346 13.1111 4.15764 13.2911 4.31264C13.699 4.66392 14.4166 5.29158 14.8652 5.74017C15.3102 6.18519 15.9315 6.89491 16.2843 7.30448C16.4432 7.48892 16.4434 7.76006 16.2846 7.94456C15.9316 8.35465 15.3048 9.0702 14.8203 9.55468C14.3362 10.0388 13.6215 10.6649 13.2112 11.018C13.0263 11.1772 12.7541 11.1764 12.5695 11.0168C12.1509 10.6549 11.4224 10.0161 11.0058 9.59953C10.586 9.17972 9.94063 8.44325 9.58023 8.02622Z" fill="url(#paint15_linear_18_32062)"/>
+<path d="M9.58023 8.02622C9.42436 7.84587 9.41986 7.5814 9.5728 7.39856C9.87588 7.03625 10.4173 6.41088 11.0507 5.7774C11.6836 5.1445 12.3052 4.60674 12.6654 4.30578C12.8477 4.15346 13.1111 4.15764 13.2911 4.31264C13.699 4.66392 14.4166 5.29158 14.8652 5.74017C15.3102 6.18519 15.9315 6.89491 16.2843 7.30448C16.4432 7.48892 16.4434 7.76006 16.2846 7.94456C15.9316 8.35465 15.3048 9.0702 14.8203 9.55468C14.3362 10.0388 13.6215 10.6649 13.2112 11.018C13.0263 11.1772 12.7541 11.1764 12.5695 11.0168C12.1509 10.6549 11.4224 10.0161 11.0058 9.59953C10.586 9.17972 9.94063 8.44325 9.58023 8.02622Z" fill="url(#paint16_linear_18_32062)"/>
+<path d="M9.58023 8.02622C9.42436 7.84587 9.41986 7.5814 9.5728 7.39856C9.87588 7.03625 10.4173 6.41088 11.0507 5.7774C11.6836 5.1445 12.3052 4.60674 12.6654 4.30578C12.8477 4.15346 13.1111 4.15764 13.2911 4.31264C13.699 4.66392 14.4166 5.29158 14.8652 5.74017C15.3102 6.18519 15.9315 6.89491 16.2843 7.30448C16.4432 7.48892 16.4434 7.76006 16.2846 7.94456C15.9316 8.35465 15.3048 9.0702 14.8203 9.55468C14.3362 10.0388 13.6215 10.6649 13.2112 11.018C13.0263 11.1772 12.7541 11.1764 12.5695 11.0168C12.1509 10.6549 11.4224 10.0161 11.0058 9.59953C10.586 9.17972 9.94063 8.44325 9.58023 8.02622Z" fill="url(#paint17_linear_18_32062)"/>
+</g>
+<g filter="url(#filter3_f_18_32062)">
+<path d="M17.0902 9.92832C17.1037 9.91546 17.1175 9.90279 17.1318 9.8908C17.6646 9.44301 18.3423 9.45297 18.866 9.89858C18.8741 9.90549 18.8818 9.91232 18.8896 9.91952C18.9903 10.0118 19.7361 10.6966 20.3068 11.2674C20.8853 11.8459 21.5602 12.5833 21.6349 12.6651C21.6396 12.6703 21.644 12.6752 21.6486 12.6805C22.1004 13.2001 22.132 13.892 21.6531 14.4277C21.6456 14.4361 21.6384 14.4441 21.631 14.4526C21.5476 14.5484 20.9841 15.1916 20.3068 15.8689C19.6528 16.523 19.0297 17.1027 18.9012 17.2217C18.8849 17.2368 18.8684 17.2513 18.851 17.2651C18.2712 17.727 17.6282 17.6591 17.1117 17.2049C17.1109 17.2042 17.1106 17.2039 17.1097 17.2032C17.0829 17.1796 16.2808 16.4757 15.674 15.8689C15.0678 15.2627 14.3721 14.4691 14.3476 14.4411C14.3468 14.4402 14.3465 14.4399 14.3458 14.439C13.8807 13.9031 13.8929 13.1842 14.3367 12.684C14.3434 12.6764 14.3502 12.6691 14.3572 12.6617C14.4508 12.5636 15.163 11.8174 15.7131 11.2674C16.2479 10.7325 16.9618 10.0508 17.0902 9.92832Z" fill="#824534"/>
+<path d="M17.0902 9.92832C17.1037 9.91546 17.1175 9.90279 17.1318 9.8908C17.6646 9.44301 18.3423 9.45297 18.866 9.89858C18.8741 9.90549 18.8818 9.91232 18.8896 9.91952C18.9903 10.0118 19.7361 10.6966 20.3068 11.2674C20.8853 11.8459 21.5602 12.5833 21.6349 12.6651C21.6396 12.6703 21.644 12.6752 21.6486 12.6805C22.1004 13.2001 22.132 13.892 21.6531 14.4277C21.6456 14.4361 21.6384 14.4441 21.631 14.4526C21.5476 14.5484 20.9841 15.1916 20.3068 15.8689C19.6528 16.523 19.0297 17.1027 18.9012 17.2217C18.8849 17.2368 18.8684 17.2513 18.851 17.2651C18.2712 17.727 17.6282 17.6591 17.1117 17.2049C17.1109 17.2042 17.1106 17.2039 17.1097 17.2032C17.0829 17.1796 16.2808 16.4757 15.674 15.8689C15.0678 15.2627 14.3721 14.4691 14.3476 14.4411C14.3468 14.4402 14.3465 14.4399 14.3458 14.439C13.8807 13.9031 13.8929 13.1842 14.3367 12.684C14.3434 12.6764 14.3502 12.6691 14.3572 12.6617C14.4508 12.5636 15.163 11.8174 15.7131 11.2674C16.2479 10.7325 16.9618 10.0508 17.0902 9.92832Z" fill="url(#paint18_linear_18_32062)"/>
+<path d="M17.0902 9.92832C17.1037 9.91546 17.1175 9.90279 17.1318 9.8908C17.6646 9.44301 18.3423 9.45297 18.866 9.89858C18.8741 9.90549 18.8818 9.91232 18.8896 9.91952C18.9903 10.0118 19.7361 10.6966 20.3068 11.2674C20.8853 11.8459 21.5602 12.5833 21.6349 12.6651C21.6396 12.6703 21.644 12.6752 21.6486 12.6805C22.1004 13.2001 22.132 13.892 21.6531 14.4277C21.6456 14.4361 21.6384 14.4441 21.631 14.4526C21.5476 14.5484 20.9841 15.1916 20.3068 15.8689C19.6528 16.523 19.0297 17.1027 18.9012 17.2217C18.8849 17.2368 18.8684 17.2513 18.851 17.2651C18.2712 17.727 17.6282 17.6591 17.1117 17.2049C17.1109 17.2042 17.1106 17.2039 17.1097 17.2032C17.0829 17.1796 16.2808 16.4757 15.674 15.8689C15.0678 15.2627 14.3721 14.4691 14.3476 14.4411C14.3468 14.4402 14.3465 14.4399 14.3458 14.439C13.8807 13.9031 13.8929 13.1842 14.3367 12.684C14.3434 12.6764 14.3502 12.6691 14.3572 12.6617C14.4508 12.5636 15.163 11.8174 15.7131 11.2674C16.2479 10.7325 16.9618 10.0508 17.0902 9.92832Z" fill="url(#paint19_linear_18_32062)"/>
+<path d="M17.0902 9.92832C17.1037 9.91546 17.1175 9.90279 17.1318 9.8908C17.6646 9.44301 18.3423 9.45297 18.866 9.89858C18.8741 9.90549 18.8818 9.91232 18.8896 9.91952C18.9903 10.0118 19.7361 10.6966 20.3068 11.2674C20.8853 11.8459 21.5602 12.5833 21.6349 12.6651C21.6396 12.6703 21.644 12.6752 21.6486 12.6805C22.1004 13.2001 22.132 13.892 21.6531 14.4277C21.6456 14.4361 21.6384 14.4441 21.631 14.4526C21.5476 14.5484 20.9841 15.1916 20.3068 15.8689C19.6528 16.523 19.0297 17.1027 18.9012 17.2217C18.8849 17.2368 18.8684 17.2513 18.851 17.2651C18.2712 17.727 17.6282 17.6591 17.1117 17.2049C17.1109 17.2042 17.1106 17.2039 17.1097 17.2032C17.0829 17.1796 16.2808 16.4757 15.674 15.8689C15.0678 15.2627 14.3721 14.4691 14.3476 14.4411C14.3468 14.4402 14.3465 14.4399 14.3458 14.439C13.8807 13.9031 13.8929 13.1842 14.3367 12.684C14.3434 12.6764 14.3502 12.6691 14.3572 12.6617C14.4508 12.5636 15.163 11.8174 15.7131 11.2674C16.2479 10.7325 16.9618 10.0508 17.0902 9.92832Z" fill="url(#paint20_linear_18_32062)"/>
+<path d="M17.0902 9.92832C17.1037 9.91546 17.1175 9.90279 17.1318 9.8908C17.6646 9.44301 18.3423 9.45297 18.866 9.89858C18.8741 9.90549 18.8818 9.91232 18.8896 9.91952C18.9903 10.0118 19.7361 10.6966 20.3068 11.2674C20.8853 11.8459 21.5602 12.5833 21.6349 12.6651C21.6396 12.6703 21.644 12.6752 21.6486 12.6805C22.1004 13.2001 22.132 13.892 21.6531 14.4277C21.6456 14.4361 21.6384 14.4441 21.631 14.4526C21.5476 14.5484 20.9841 15.1916 20.3068 15.8689C19.6528 16.523 19.0297 17.1027 18.9012 17.2217C18.8849 17.2368 18.8684 17.2513 18.851 17.2651C18.2712 17.727 17.6282 17.6591 17.1117 17.2049C17.1109 17.2042 17.1106 17.2039 17.1097 17.2032C17.0829 17.1796 16.2808 16.4757 15.674 15.8689C15.0678 15.2627 14.3721 14.4691 14.3476 14.4411C14.3468 14.4402 14.3465 14.4399 14.3458 14.439C13.8807 13.9031 13.8929 13.1842 14.3367 12.684C14.3434 12.6764 14.3502 12.6691 14.3572 12.6617C14.4508 12.5636 15.163 11.8174 15.7131 11.2674C16.2479 10.7325 16.9618 10.0508 17.0902 9.92832Z" fill="#613534"/>
+</g>
+<path d="M17.4943 9.47343C17.5078 9.46056 17.5216 9.4479 17.5359 9.4359C18.0688 8.98812 18.7464 8.99807 19.2701 9.44368C19.2782 9.45059 19.2859 9.45742 19.2937 9.46463C19.3944 9.55688 20.1402 10.2417 20.7109 10.8125C21.2895 11.391 21.9643 12.1285 22.039 12.2102C22.0437 12.2154 22.0482 12.2203 22.0528 12.2256C22.5046 12.7452 22.5361 13.4371 22.0572 13.9728C22.0497 13.9812 22.0425 13.9892 22.0351 13.9977C21.9517 14.0935 21.3883 14.7367 20.7109 15.414C20.0569 16.0681 19.4338 16.6478 19.3053 16.7668C19.289 16.7819 19.2726 16.7964 19.2551 16.8102C18.6754 17.2721 18.0324 17.2042 17.5158 16.75C17.515 16.7493 17.5147 16.749 17.5139 16.7483C17.487 16.7247 16.6849 16.0208 16.0781 15.414C15.4719 14.8078 14.7763 14.0142 14.7517 13.9862C14.751 13.9853 14.7507 13.985 14.7499 13.9841C14.2848 13.4482 14.297 12.7293 14.7408 12.2291C14.7476 12.2215 14.7543 12.2142 14.7614 12.2068C14.8549 12.1087 15.5671 11.3625 16.1172 10.8125C16.6521 10.2776 17.3659 9.59586 17.4943 9.47343Z" fill="#824534"/>
+<path d="M17.4943 9.47343C17.5078 9.46056 17.5216 9.4479 17.5359 9.4359C18.0688 8.98812 18.7464 8.99807 19.2701 9.44368C19.2782 9.45059 19.2859 9.45742 19.2937 9.46463C19.3944 9.55688 20.1402 10.2417 20.7109 10.8125C21.2895 11.391 21.9643 12.1285 22.039 12.2102C22.0437 12.2154 22.0482 12.2203 22.0528 12.2256C22.5046 12.7452 22.5361 13.4371 22.0572 13.9728C22.0497 13.9812 22.0425 13.9892 22.0351 13.9977C21.9517 14.0935 21.3883 14.7367 20.7109 15.414C20.0569 16.0681 19.4338 16.6478 19.3053 16.7668C19.289 16.7819 19.2726 16.7964 19.2551 16.8102C18.6754 17.2721 18.0324 17.2042 17.5158 16.75C17.515 16.7493 17.5147 16.749 17.5139 16.7483C17.487 16.7247 16.6849 16.0208 16.0781 15.414C15.4719 14.8078 14.7763 14.0142 14.7517 13.9862C14.751 13.9853 14.7507 13.985 14.7499 13.9841C14.2848 13.4482 14.297 12.7293 14.7408 12.2291C14.7476 12.2215 14.7543 12.2142 14.7614 12.2068C14.8549 12.1087 15.5671 11.3625 16.1172 10.8125C16.6521 10.2776 17.3659 9.59586 17.4943 9.47343Z" fill="url(#paint21_linear_18_32062)"/>
+<path d="M17.4943 9.47343C17.5078 9.46056 17.5216 9.4479 17.5359 9.4359C18.0688 8.98812 18.7464 8.99807 19.2701 9.44368C19.2782 9.45059 19.2859 9.45742 19.2937 9.46463C19.3944 9.55688 20.1402 10.2417 20.7109 10.8125C21.2895 11.391 21.9643 12.1285 22.039 12.2102C22.0437 12.2154 22.0482 12.2203 22.0528 12.2256C22.5046 12.7452 22.5361 13.4371 22.0572 13.9728C22.0497 13.9812 22.0425 13.9892 22.0351 13.9977C21.9517 14.0935 21.3883 14.7367 20.7109 15.414C20.0569 16.0681 19.4338 16.6478 19.3053 16.7668C19.289 16.7819 19.2726 16.7964 19.2551 16.8102C18.6754 17.2721 18.0324 17.2042 17.5158 16.75C17.515 16.7493 17.5147 16.749 17.5139 16.7483C17.487 16.7247 16.6849 16.0208 16.0781 15.414C15.4719 14.8078 14.7763 14.0142 14.7517 13.9862C14.751 13.9853 14.7507 13.985 14.7499 13.9841C14.2848 13.4482 14.297 12.7293 14.7408 12.2291C14.7476 12.2215 14.7543 12.2142 14.7614 12.2068C14.8549 12.1087 15.5671 11.3625 16.1172 10.8125C16.6521 10.2776 17.3659 9.59586 17.4943 9.47343Z" fill="url(#paint22_linear_18_32062)"/>
+<path d="M17.4943 9.47343C17.5078 9.46056 17.5216 9.4479 17.5359 9.4359C18.0688 8.98812 18.7464 8.99807 19.2701 9.44368C19.2782 9.45059 19.2859 9.45742 19.2937 9.46463C19.3944 9.55688 20.1402 10.2417 20.7109 10.8125C21.2895 11.391 21.9643 12.1285 22.039 12.2102C22.0437 12.2154 22.0482 12.2203 22.0528 12.2256C22.5046 12.7452 22.5361 13.4371 22.0572 13.9728C22.0497 13.9812 22.0425 13.9892 22.0351 13.9977C21.9517 14.0935 21.3883 14.7367 20.7109 15.414C20.0569 16.0681 19.4338 16.6478 19.3053 16.7668C19.289 16.7819 19.2726 16.7964 19.2551 16.8102C18.6754 17.2721 18.0324 17.2042 17.5158 16.75C17.515 16.7493 17.5147 16.749 17.5139 16.7483C17.487 16.7247 16.6849 16.0208 16.0781 15.414C15.4719 14.8078 14.7763 14.0142 14.7517 13.9862C14.751 13.9853 14.7507 13.985 14.7499 13.9841C14.2848 13.4482 14.297 12.7293 14.7408 12.2291C14.7476 12.2215 14.7543 12.2142 14.7614 12.2068C14.8549 12.1087 15.5671 11.3625 16.1172 10.8125C16.6521 10.2776 17.3659 9.59586 17.4943 9.47343Z" fill="url(#paint23_linear_18_32062)"/>
+<path d="M17.4943 9.47343C17.5078 9.46056 17.5216 9.4479 17.5359 9.4359C18.0688 8.98812 18.7464 8.99807 19.2701 9.44368C19.2782 9.45059 19.2859 9.45742 19.2937 9.46463C19.3944 9.55688 20.1402 10.2417 20.7109 10.8125C21.2895 11.391 21.9643 12.1285 22.039 12.2102C22.0437 12.2154 22.0482 12.2203 22.0528 12.2256C22.5046 12.7452 22.5361 13.4371 22.0572 13.9728C22.0497 13.9812 22.0425 13.9892 22.0351 13.9977C21.9517 14.0935 21.3883 14.7367 20.7109 15.414C20.0569 16.0681 19.4338 16.6478 19.3053 16.7668C19.289 16.7819 19.2726 16.7964 19.2551 16.8102C18.6754 17.2721 18.0324 17.2042 17.5158 16.75C17.515 16.7493 17.5147 16.749 17.5139 16.7483C17.487 16.7247 16.6849 16.0208 16.0781 15.414C15.4719 14.8078 14.7763 14.0142 14.7517 13.9862C14.751 13.9853 14.7507 13.985 14.7499 13.9841C14.2848 13.4482 14.297 12.7293 14.7408 12.2291C14.7476 12.2215 14.7543 12.2142 14.7614 12.2068C14.8549 12.1087 15.5671 11.3625 16.1172 10.8125C16.6521 10.2776 17.3659 9.59586 17.4943 9.47343Z" fill="url(#paint24_linear_18_32062)"/>
+<g filter="url(#filter4_f_18_32062)">
+<path d="M15.1088 13.497C14.9527 13.3172 14.9473 13.0531 15.0996 12.8701C15.3934 12.5172 15.912 11.9162 16.5194 11.3088C17.1262 10.702 17.7236 10.1869 18.0744 9.89513C18.2569 9.74339 18.5199 9.74847 18.6994 9.90376C19.1064 10.2559 19.8227 10.8852 20.2714 11.3339C20.7164 11.7789 21.3391 12.4872 21.6929 12.8959C21.8522 13.0799 21.8532 13.3509 21.6948 13.5358C21.3526 13.9351 20.7539 14.6211 20.2891 15.0859C19.8246 15.5504 19.1394 16.1485 18.7399 16.4908C18.5546 16.6496 18.2826 16.648 18.0984 16.4879C17.6807 16.1251 16.9537 15.4849 16.537 15.0683C16.1171 14.6484 15.4701 13.9131 15.1088 13.497Z" fill="url(#paint25_linear_18_32062)"/>
+<path d="M15.1088 13.497C14.9527 13.3172 14.9473 13.0531 15.0996 12.8701C15.3934 12.5172 15.912 11.9162 16.5194 11.3088C17.1262 10.702 17.7236 10.1869 18.0744 9.89513C18.2569 9.74339 18.5199 9.74847 18.6994 9.90376C19.1064 10.2559 19.8227 10.8852 20.2714 11.3339C20.7164 11.7789 21.3391 12.4872 21.6929 12.8959C21.8522 13.0799 21.8532 13.3509 21.6948 13.5358C21.3526 13.9351 20.7539 14.6211 20.2891 15.0859C19.8246 15.5504 19.1394 16.1485 18.7399 16.4908C18.5546 16.6496 18.2826 16.648 18.0984 16.4879C17.6807 16.1251 16.9537 15.4849 16.537 15.0683C16.1171 14.6484 15.4701 13.9131 15.1088 13.497Z" fill="url(#paint26_linear_18_32062)"/>
+<path d="M15.1088 13.497C14.9527 13.3172 14.9473 13.0531 15.0996 12.8701C15.3934 12.5172 15.912 11.9162 16.5194 11.3088C17.1262 10.702 17.7236 10.1869 18.0744 9.89513C18.2569 9.74339 18.5199 9.74847 18.6994 9.90376C19.1064 10.2559 19.8227 10.8852 20.2714 11.3339C20.7164 11.7789 21.3391 12.4872 21.6929 12.8959C21.8522 13.0799 21.8532 13.3509 21.6948 13.5358C21.3526 13.9351 20.7539 14.6211 20.2891 15.0859C19.8246 15.5504 19.1394 16.1485 18.7399 16.4908C18.5546 16.6496 18.2826 16.648 18.0984 16.4879C17.6807 16.1251 16.9537 15.4849 16.537 15.0683C16.1171 14.6484 15.4701 13.9131 15.1088 13.497Z" fill="url(#paint27_linear_18_32062)"/>
+</g>
+<g filter="url(#filter5_f_18_32062)">
+<path d="M11.9631 15.1578C11.9766 15.1449 11.9904 15.1322 12.0047 15.1202C12.5375 14.6724 13.2152 14.6824 13.7389 15.128C13.747 15.1349 13.7546 15.1418 13.7625 15.149C13.8632 15.2412 14.6089 15.9261 15.1797 16.4968C15.7582 17.0753 16.4331 17.8128 16.5077 17.8945C16.5125 17.8997 16.5169 17.9046 16.5215 17.91C16.9733 18.4295 17.0049 19.1215 16.5259 19.6571C16.5184 19.6655 16.5113 19.6735 16.5039 19.682C16.4204 19.7779 15.857 20.421 15.1797 21.0984C14.5256 21.7524 13.9026 22.3321 13.7741 22.4511C13.7577 22.4662 13.7413 22.4807 13.7239 22.4946C13.1441 22.9565 12.5011 22.8885 11.9846 22.4344C11.9837 22.4336 11.9835 22.4334 11.9826 22.4326C11.9558 22.4091 11.1537 21.7052 10.5469 21.0984C9.94065 20.4921 9.24501 19.6985 9.22049 19.6705C9.21971 19.6696 9.21942 19.6693 9.21865 19.6684C8.75359 19.1325 8.76579 18.4137 9.20954 17.9134C9.21632 17.9058 9.22307 17.8986 9.23012 17.8912C9.32368 17.793 10.0359 17.0469 10.5859 16.4968C11.1208 15.9619 11.8347 15.2802 11.9631 15.1578Z" fill="url(#paint28_linear_18_32062)"/>
+</g>
+<path d="M12.3225 14.6766C12.336 14.6637 12.3498 14.651 12.364 14.639C12.8969 14.1912 13.5746 14.2012 14.0982 14.6468C14.1064 14.6537 14.114 14.6605 14.1219 14.6678C14.2226 14.76 14.9683 15.4449 15.5391 16.0156C16.1176 16.5941 16.7925 17.3316 16.8671 17.4133C16.8719 17.4185 16.8763 17.4234 16.8809 17.4287C17.3327 17.9483 17.3642 18.6403 16.8853 19.1759C16.8778 19.1843 16.8707 19.1923 16.8633 19.2008C16.7798 19.2966 16.2164 19.9398 15.5391 20.6172C14.885 21.2712 14.2619 21.8509 14.1335 21.9699C14.1171 21.985 14.1007 21.9995 14.0833 22.0134C13.5035 22.4753 12.8605 22.4073 12.344 21.9531C12.3431 21.9524 12.3428 21.9522 12.342 21.9514C12.3151 21.9279 11.5131 21.224 10.9063 20.6172C10.3 20.0109 9.60438 19.2173 9.57986 19.1893C9.57908 19.1884 9.5788 19.1881 9.57802 19.1872C9.11297 18.6513 9.12517 17.9325 9.56892 17.4322C9.5757 17.4246 9.58244 17.4174 9.58949 17.41C9.68306 17.3118 10.3953 16.5657 10.9453 16.0156C11.4802 15.4807 12.194 14.799 12.3225 14.6766Z" fill="#824534"/>
+<path d="M12.3225 14.6766C12.336 14.6637 12.3498 14.651 12.364 14.639C12.8969 14.1912 13.5746 14.2012 14.0982 14.6468C14.1064 14.6537 14.114 14.6605 14.1219 14.6678C14.2226 14.76 14.9683 15.4449 15.5391 16.0156C16.1176 16.5941 16.7925 17.3316 16.8671 17.4133C16.8719 17.4185 16.8763 17.4234 16.8809 17.4287C17.3327 17.9483 17.3642 18.6403 16.8853 19.1759C16.8778 19.1843 16.8707 19.1923 16.8633 19.2008C16.7798 19.2966 16.2164 19.9398 15.5391 20.6172C14.885 21.2712 14.2619 21.8509 14.1335 21.9699C14.1171 21.985 14.1007 21.9995 14.0833 22.0134C13.5035 22.4753 12.8605 22.4073 12.344 21.9531C12.3431 21.9524 12.3428 21.9522 12.342 21.9514C12.3151 21.9279 11.5131 21.224 10.9063 20.6172C10.3 20.0109 9.60438 19.2173 9.57986 19.1893C9.57908 19.1884 9.5788 19.1881 9.57802 19.1872C9.11297 18.6513 9.12517 17.9325 9.56892 17.4322C9.5757 17.4246 9.58244 17.4174 9.58949 17.41C9.68306 17.3118 10.3953 16.5657 10.9453 16.0156C11.4802 15.4807 12.194 14.799 12.3225 14.6766Z" fill="url(#paint29_linear_18_32062)"/>
+<path d="M12.3225 14.6766C12.336 14.6637 12.3498 14.651 12.364 14.639C12.8969 14.1912 13.5746 14.2012 14.0982 14.6468C14.1064 14.6537 14.114 14.6605 14.1219 14.6678C14.2226 14.76 14.9683 15.4449 15.5391 16.0156C16.1176 16.5941 16.7925 17.3316 16.8671 17.4133C16.8719 17.4185 16.8763 17.4234 16.8809 17.4287C17.3327 17.9483 17.3642 18.6403 16.8853 19.1759C16.8778 19.1843 16.8707 19.1923 16.8633 19.2008C16.7798 19.2966 16.2164 19.9398 15.5391 20.6172C14.885 21.2712 14.2619 21.8509 14.1335 21.9699C14.1171 21.985 14.1007 21.9995 14.0833 22.0134C13.5035 22.4753 12.8605 22.4073 12.344 21.9531C12.3431 21.9524 12.3428 21.9522 12.342 21.9514C12.3151 21.9279 11.5131 21.224 10.9063 20.6172C10.3 20.0109 9.60438 19.2173 9.57986 19.1893C9.57908 19.1884 9.5788 19.1881 9.57802 19.1872C9.11297 18.6513 9.12517 17.9325 9.56892 17.4322C9.5757 17.4246 9.58244 17.4174 9.58949 17.41C9.68306 17.3118 10.3953 16.5657 10.9453 16.0156C11.4802 15.4807 12.194 14.799 12.3225 14.6766Z" fill="url(#paint30_linear_18_32062)"/>
+<path d="M12.3225 14.6766C12.336 14.6637 12.3498 14.651 12.364 14.639C12.8969 14.1912 13.5746 14.2012 14.0982 14.6468C14.1064 14.6537 14.114 14.6605 14.1219 14.6678C14.2226 14.76 14.9683 15.4449 15.5391 16.0156C16.1176 16.5941 16.7925 17.3316 16.8671 17.4133C16.8719 17.4185 16.8763 17.4234 16.8809 17.4287C17.3327 17.9483 17.3642 18.6403 16.8853 19.1759C16.8778 19.1843 16.8707 19.1923 16.8633 19.2008C16.7798 19.2966 16.2164 19.9398 15.5391 20.6172C14.885 21.2712 14.2619 21.8509 14.1335 21.9699C14.1171 21.985 14.1007 21.9995 14.0833 22.0134C13.5035 22.4753 12.8605 22.4073 12.344 21.9531C12.3431 21.9524 12.3428 21.9522 12.342 21.9514C12.3151 21.9279 11.5131 21.224 10.9063 20.6172C10.3 20.0109 9.60438 19.2173 9.57986 19.1893C9.57908 19.1884 9.5788 19.1881 9.57802 19.1872C9.11297 18.6513 9.12517 17.9325 9.56892 17.4322C9.5757 17.4246 9.58244 17.4174 9.58949 17.41C9.68306 17.3118 10.3953 16.5657 10.9453 16.0156C11.4802 15.4807 12.194 14.799 12.3225 14.6766Z" fill="url(#paint31_linear_18_32062)"/>
+<path d="M12.3225 14.6766C12.336 14.6637 12.3498 14.651 12.364 14.639C12.8969 14.1912 13.5746 14.2012 14.0982 14.6468C14.1064 14.6537 14.114 14.6605 14.1219 14.6678C14.2226 14.76 14.9683 15.4449 15.5391 16.0156C16.1176 16.5941 16.7925 17.3316 16.8671 17.4133C16.8719 17.4185 16.8763 17.4234 16.8809 17.4287C17.3327 17.9483 17.3642 18.6403 16.8853 19.1759C16.8778 19.1843 16.8707 19.1923 16.8633 19.2008C16.7798 19.2966 16.2164 19.9398 15.5391 20.6172C14.885 21.2712 14.2619 21.8509 14.1335 21.9699C14.1171 21.985 14.1007 21.9995 14.0833 22.0134C13.5035 22.4753 12.8605 22.4073 12.344 21.9531C12.3431 21.9524 12.3428 21.9522 12.342 21.9514C12.3151 21.9279 11.5131 21.224 10.9063 20.6172C10.3 20.0109 9.60438 19.2173 9.57986 19.1893C9.57908 19.1884 9.5788 19.1881 9.57802 19.1872C9.11297 18.6513 9.12517 17.9325 9.56892 17.4322C9.5757 17.4246 9.58244 17.4174 9.58949 17.41C9.68306 17.3118 10.3953 16.5657 10.9453 16.0156C11.4802 15.4807 12.194 14.799 12.3225 14.6766Z" fill="url(#paint32_linear_18_32062)"/>
+<g filter="url(#filter6_f_18_32062)">
+<path d="M9.93691 18.7001C9.78078 18.5203 9.77539 18.2563 9.92775 18.0732C10.2215 17.7203 10.7401 17.1193 11.3475 16.5119C11.9543 15.9051 12.5517 15.39 12.9026 15.0983C13.0851 14.9465 13.348 14.9516 13.5275 15.1069C13.9345 15.459 14.6508 16.0883 15.0995 16.537C15.5446 16.9821 16.1672 17.6903 16.521 18.099C16.6803 18.283 16.6813 18.5541 16.5229 18.7389C16.1808 19.1382 15.582 19.8242 15.1172 20.2891C14.6528 20.7535 13.9675 21.3516 13.5681 21.6939C13.3828 21.8527 13.1107 21.8511 12.9265 21.6911C12.5088 21.3282 11.7818 20.688 11.3652 20.2714C10.9452 19.8515 10.2982 19.1162 9.93691 18.7001Z" fill="url(#paint33_linear_18_32062)"/>
+<path d="M9.93691 18.7001C9.78078 18.5203 9.77539 18.2563 9.92775 18.0732C10.2215 17.7203 10.7401 17.1193 11.3475 16.5119C11.9543 15.9051 12.5517 15.39 12.9026 15.0983C13.0851 14.9465 13.348 14.9516 13.5275 15.1069C13.9345 15.459 14.6508 16.0883 15.0995 16.537C15.5446 16.9821 16.1672 17.6903 16.521 18.099C16.6803 18.283 16.6813 18.5541 16.5229 18.7389C16.1808 19.1382 15.582 19.8242 15.1172 20.2891C14.6528 20.7535 13.9675 21.3516 13.5681 21.6939C13.3828 21.8527 13.1107 21.8511 12.9265 21.6911C12.5088 21.3282 11.7818 20.688 11.3652 20.2714C10.9452 19.8515 10.2982 19.1162 9.93691 18.7001Z" fill="url(#paint34_linear_18_32062)"/>
+<path d="M9.93691 18.7001C9.78078 18.5203 9.77539 18.2563 9.92775 18.0732C10.2215 17.7203 10.7401 17.1193 11.3475 16.5119C11.9543 15.9051 12.5517 15.39 12.9026 15.0983C13.0851 14.9465 13.348 14.9516 13.5275 15.1069C13.9345 15.459 14.6508 16.0883 15.0995 16.537C15.5446 16.9821 16.1672 17.6903 16.521 18.099C16.6803 18.283 16.6813 18.5541 16.5229 18.7389C16.1808 19.1382 15.582 19.8242 15.1172 20.2891C14.6528 20.7535 13.9675 21.3516 13.5681 21.6939C13.3828 21.8527 13.1107 21.8511 12.9265 21.6911C12.5088 21.3282 11.7818 20.688 11.3652 20.2714C10.9452 19.8515 10.2982 19.1162 9.93691 18.7001Z" fill="url(#paint35_linear_18_32062)"/>
+</g>
+<path d="M10.2578 21.7831C10.2578 21.7831 16.5546 28.3456 17.664 29.33C18.7734 30.3144 19.9921 30.1269 20.9453 29.33C21.8984 28.5331 28.3203 22.0956 29.3359 21.0331C30.3515 19.9706 30.0703 18.5488 29.1796 17.6581C28.289 16.7675 21.7109 10.2519 21.7109 10.2519L10.2578 21.7831Z" fill="url(#paint36_linear_18_32062)"/>
+<path d="M10.2578 21.7831C10.2578 21.7831 16.5546 28.3456 17.664 29.33C18.7734 30.3144 19.9921 30.1269 20.9453 29.33C21.8984 28.5331 28.3203 22.0956 29.3359 21.0331C30.3515 19.9706 30.0703 18.5488 29.1796 17.6581C28.289 16.7675 21.7109 10.2519 21.7109 10.2519L10.2578 21.7831Z" fill="url(#paint37_linear_18_32062)"/>
+<g filter="url(#filter7_f_18_32062)">
+<path d="M12.1329 21.5234C12.1329 21.5234 16.8586 26.498 17.8978 27.4201C18.937 28.3421 19.376 28.869 20.2688 28.1226C21.1616 27.3762 27.1771 21.3461 28.1284 20.3508C29.0798 19.3556 28.6577 18.7408 27.8235 17.9066C26.9892 17.0723 22 12.1416 22 12.1416L12.1329 21.5234Z" fill="#D3245A"/>
+<path d="M12.1329 21.5234C12.1329 21.5234 16.8586 26.498 17.8978 27.4201C18.937 28.3421 19.376 28.869 20.2688 28.1226C21.1616 27.3762 27.1771 21.3461 28.1284 20.3508C29.0798 19.3556 28.6577 18.7408 27.8235 17.9066C26.9892 17.0723 22 12.1416 22 12.1416L12.1329 21.5234Z" fill="url(#paint38_linear_18_32062)"/>
+<path d="M12.1329 21.5234C12.1329 21.5234 16.8586 26.498 17.8978 27.4201C18.937 28.3421 19.376 28.869 20.2688 28.1226C21.1616 27.3762 27.1771 21.3461 28.1284 20.3508C29.0798 19.3556 28.6577 18.7408 27.8235 17.9066C26.9892 17.0723 22 12.1416 22 12.1416L12.1329 21.5234Z" fill="url(#paint39_linear_18_32062)"/>
+</g>
+<g filter="url(#filter8_f_18_32062)">
+<path d="M22.4907 12.0862C22.4907 12.0862 14.132 23.4293 13.8188 23.7425C13.5057 24.0557 13.2494 24.1126 12.8223 23.8422C12.3952 23.5717 11.0855 22.0342 10.872 21.7922C10.6584 21.5501 10.6653 21.2495 10.9466 20.987C11.2279 20.7244 21.1525 10.8406 21.1525 10.8406C21.3127 10.6804 21.6015 10.7222 21.7291 10.8406C21.7291 10.8406 22.3128 11.3388 22.4907 11.5168C22.6687 11.6948 22.5818 11.9587 22.4907 12.0862Z" fill="#572916"/>
+</g>
+<path d="M23.2266 11.5469C23.2266 11.5469 13.7188 24.4531 13.375 24.7969C13.0312 25.1406 12.75 25.2031 12.2812 24.9063C11.8125 24.6094 10.375 22.9219 10.1406 22.6563C9.90625 22.3906 9.91374 22.0606 10.2225 21.7725C10.5312 21.4844 21.7578 10.1797 21.7578 10.1797C21.9336 10.0039 22.2506 10.0497 22.3906 10.1797C22.3906 10.1797 23.0312 10.7266 23.2266 10.9219C23.4219 11.1172 23.3266 11.4069 23.2266 11.5469Z" fill="url(#paint40_linear_18_32062)"/>
+<g filter="url(#filter9_f_18_32062)">
+<path d="M22.875 11.2812C22.875 10.8281 22.375 10.3438 21.9219 10.5C21.4688 10.6562 16.5 16.2812 16.5 16.2812L18.0781 17.5468C19.6615 15.651 22.875 11.7422 22.875 11.2812Z" fill="url(#paint41_linear_18_32062)"/>
+</g>
+<defs>
+<filter id="filter0_f_18_32062" x="2.37329" y="8.35474" width="9.99959" height="10.0206" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.5" result="effect1_foregroundBlur_18_32062"/>
+</filter>
+<filter id="filter1_f_18_32062" x="4.0197" y="9.22174" width="7.32452" height="7.32471" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.125" result="effect1_foregroundBlur_18_32062"/>
+</filter>
+<filter id="filter2_f_18_32062" x="9.21066" y="3.94391" width="7.44289" height="7.44305" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.125" result="effect1_foregroundBlur_18_32062"/>
+</filter>
+<filter id="filter3_f_18_32062" x="13.0004" y="8.55963" width="9.99959" height="10.0206" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.5" result="effect1_foregroundBlur_18_32062"/>
+</filter>
+<filter id="filter4_f_18_32062" x="14.7384" y="9.53424" width="7.32452" height="7.32471" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.125" result="effect1_foregroundBlur_18_32062"/>
+</filter>
+<filter id="filter5_f_18_32062" x="7.87329" y="13.7891" width="9.99959" height="10.0206" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.5" result="effect1_foregroundBlur_18_32062"/>
+</filter>
+<filter id="filter6_f_18_32062" x="9.56657" y="14.7374" width="7.32452" height="7.32471" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.125" result="effect1_foregroundBlur_18_32062"/>
+</filter>
+<filter id="filter7_f_18_32062" x="11.1329" y="11.1417" width="18.5395" height="18.3354" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.5" result="effect1_foregroundBlur_18_32062"/>
+</filter>
+<filter id="filter8_f_18_32062" x="9.72244" y="9.73553" width="13.8717" height="15.2796" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.5" result="effect1_foregroundBlur_18_32062"/>
+</filter>
+<filter id="filter9_f_18_32062" x="16" y="9.97003" width="7.375" height="8.07678" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.25" result="effect1_foregroundBlur_18_32062"/>
+</filter>
+<radialGradient id="paint0_radial_18_32062" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(6.375 5.875) rotate(135) scale(12.3302 6.13734)">
+<stop stop-color="#735040"/>
+<stop offset="1" stop-color="#724A3A" stop-opacity="0"/>
+</radialGradient>
+<radialGradient id="paint1_radial_18_32062" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(4.03125 18.9062) rotate(45) scale(11.5596 8.97142)">
+<stop stop-color="#834D4F"/>
+<stop offset="1" stop-color="#834B4D" stop-opacity="0"/>
+</radialGradient>
+<radialGradient id="paint2_radial_18_32062" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(17.5 6.6875) rotate(45) scale(10.1647 3.13779)">
+<stop stop-color="#AC7A64"/>
+<stop offset="1" stop-color="#AC7A63" stop-opacity="0"/>
+</radialGradient>
+<linearGradient id="paint3_linear_18_32062" x1="6.59375" y1="14.8125" x2="5.3125" y2="16.0937" gradientUnits="userSpaceOnUse">
+<stop stop-color="#835355" stop-opacity="0"/>
+<stop offset="0.542683" stop-color="#825254"/>
+</linearGradient>
+<linearGradient id="paint4_linear_18_32062" x1="6.90625" y1="14.25" x2="5.625" y2="15.5312" gradientUnits="userSpaceOnUse">
+<stop stop-color="#835355" stop-opacity="0"/>
+<stop offset="0.542683" stop-color="#825254"/>
+</linearGradient>
+<linearGradient id="paint5_linear_18_32062" x1="9.55469" y1="11.6016" x2="10.375" y2="10.7813" gradientUnits="userSpaceOnUse">
+<stop stop-color="#91634B" stop-opacity="0"/>
+<stop offset="0.257143" stop-color="#8E5E45"/>
+<stop offset="0.971429" stop-color="#79452B"/>
+</linearGradient>
+<linearGradient id="paint6_linear_18_32062" x1="5.3125" y1="10.5625" x2="6.25781" y2="11.5078" gradientUnits="userSpaceOnUse">
+<stop stop-color="#754A38"/>
+<stop offset="0.198347" stop-color="#764B38"/>
+<stop offset="1" stop-color="#8A5648" stop-opacity="0"/>
+</linearGradient>
+<linearGradient id="paint7_linear_18_32062" x1="10.4375" y1="15.0313" x2="9.54687" y2="14.1406" gradientUnits="userSpaceOnUse">
+<stop offset="0.192983" stop-color="#7F4131"/>
+<stop offset="0.508772" stop-color="#884B3F"/>
+<stop offset="1" stop-color="#905452" stop-opacity="0"/>
+</linearGradient>
+<linearGradient id="paint8_linear_18_32062" x1="4.71562" y1="13.599" x2="10.6952" y2="13.5756" gradientUnits="userSpaceOnUse">
+<stop stop-color="#865345"/>
+<stop offset="1" stop-color="#945C4E"/>
+</linearGradient>
+<linearGradient id="paint9_linear_18_32062" x1="5.9333" y1="14.7546" x2="9.4925" y2="11.2617" gradientUnits="userSpaceOnUse">
+<stop offset="0.0276698" stop-color="#7F4A40"/>
+<stop offset="0.183829" stop-color="#844A45" stop-opacity="0"/>
+</linearGradient>
+<linearGradient id="paint10_linear_18_32062" x1="9.61" y1="11.1453" x2="6.4587" y2="14.2966" gradientUnits="userSpaceOnUse">
+<stop offset="0.0194806" stop-color="#9B705C"/>
+<stop offset="0.204546" stop-color="#9B705C" stop-opacity="0"/>
+</linearGradient>
+<linearGradient id="paint11_linear_18_32062" x1="12.0937" y1="9.09375" x2="10.8125" y2="10.375" gradientUnits="userSpaceOnUse">
+<stop offset="0.402439" stop-color="#7A3C35" stop-opacity="0"/>
+<stop offset="0.865854" stop-color="#783930"/>
+<stop offset="1" stop-color="#713126"/>
+</linearGradient>
+<linearGradient id="paint12_linear_18_32062" x1="14.6562" y1="6.46875" x2="15.5625" y2="5.625" gradientUnits="userSpaceOnUse">
+<stop stop-color="#A87760" stop-opacity="0"/>
+<stop offset="0.342675" stop-color="#A8775F"/>
+<stop offset="1" stop-color="#A3735A"/>
+</linearGradient>
+<linearGradient id="paint13_linear_18_32062" x1="10.5" y1="5.40625" x2="11.4453" y2="6.35156" gradientUnits="userSpaceOnUse">
+<stop stop-color="#754A38"/>
+<stop offset="0.198347" stop-color="#764B38"/>
+<stop offset="1" stop-color="#8A5648" stop-opacity="0"/>
+</linearGradient>
+<linearGradient id="paint14_linear_18_32062" x1="15.625" y1="9.875" x2="14.7344" y2="8.98438" gradientUnits="userSpaceOnUse">
+<stop offset="0.192983" stop-color="#7F4131"/>
+<stop offset="0.508772" stop-color="#884B3F"/>
+<stop offset="1" stop-color="#905452" stop-opacity="0"/>
+</linearGradient>
+<linearGradient id="paint15_linear_18_32062" x1="9.90402" y1="8.44186" x2="15.9816" y2="8.5181" gradientUnits="userSpaceOnUse">
+<stop stop-color="#8B5A4A"/>
+<stop offset="1" stop-color="#9B6351"/>
+</linearGradient>
+<linearGradient id="paint16_linear_18_32062" x1="11.1227" y1="9.59642" x2="13.8559" y2="6.86243" gradientUnits="userSpaceOnUse">
+<stop offset="0.0276698" stop-color="#804643"/>
+<stop offset="0.183829" stop-color="#874B4F" stop-opacity="0"/>
+</linearGradient>
+<linearGradient id="paint17_linear_18_32062" x1="14.9214" y1="5.86515" x2="12.6745" y2="8.00623" gradientUnits="userSpaceOnUse">
+<stop offset="0.0194806" stop-color="#9B705C"/>
+<stop offset="0.204546" stop-color="#9B705C" stop-opacity="0"/>
+</linearGradient>
+<linearGradient id="paint18_linear_18_32062" x1="17.2209" y1="15.0174" x2="15.9396" y2="16.2986" gradientUnits="userSpaceOnUse">
+<stop offset="0.109756" stop-color="#6D3F3D" stop-opacity="0"/>
+<stop offset="0.670732" stop-color="#643026"/>
+<stop offset="1" stop-color="#5D2A1F"/>
+</linearGradient>
+<linearGradient id="paint19_linear_18_32062" x1="19.8693" y1="12.369" x2="20.6896" y2="11.5486" gradientUnits="userSpaceOnUse">
+<stop stop-color="#91634B" stop-opacity="0"/>
+<stop offset="0.257143" stop-color="#8E5E45"/>
+<stop offset="0.971429" stop-color="#79452B"/>
+</linearGradient>
+<linearGradient id="paint20_linear_18_32062" x1="15.6271" y1="11.3299" x2="16.5724" y2="12.2752" gradientUnits="userSpaceOnUse">
+<stop stop-color="#733821"/>
+<stop offset="0.198347" stop-color="#773D25"/>
+<stop offset="1" stop-color="#7F4B35" stop-opacity="0"/>
+</linearGradient>
+<linearGradient id="paint21_linear_18_32062" x1="17.625" y1="14.5625" x2="16.3437" y2="15.8437" gradientUnits="userSpaceOnUse">
+<stop offset="0.109756" stop-color="#6D3F3D" stop-opacity="0"/>
+<stop offset="0.670732" stop-color="#643026"/>
+<stop offset="1" stop-color="#5D2A1F"/>
+</linearGradient>
+<linearGradient id="paint22_linear_18_32062" x1="20.2734" y1="11.9141" x2="21.0938" y2="11.0938" gradientUnits="userSpaceOnUse">
+<stop stop-color="#91634B" stop-opacity="0"/>
+<stop offset="0.257143" stop-color="#8E5E45"/>
+<stop offset="0.971429" stop-color="#79452B"/>
+</linearGradient>
+<linearGradient id="paint23_linear_18_32062" x1="16.0312" y1="10.875" x2="16.9766" y2="11.8203" gradientUnits="userSpaceOnUse">
+<stop stop-color="#733821"/>
+<stop offset="0.198347" stop-color="#773D25"/>
+<stop offset="1" stop-color="#7F4B35" stop-opacity="0"/>
+</linearGradient>
+<linearGradient id="paint24_linear_18_32062" x1="21.1562" y1="15.3438" x2="20.2656" y2="14.4531" gradientUnits="userSpaceOnUse">
+<stop offset="0.192983" stop-color="#7F4131"/>
+<stop offset="0.508772" stop-color="#884B3F"/>
+<stop offset="1" stop-color="#905452" stop-opacity="0"/>
+</linearGradient>
+<linearGradient id="paint25_linear_18_32062" x1="15.4344" y1="13.9115" x2="21.414" y2="13.8881" gradientUnits="userSpaceOnUse">
+<stop stop-color="#865345"/>
+<stop offset="1" stop-color="#945C4E"/>
+</linearGradient>
+<linearGradient id="paint26_linear_18_32062" x1="16.652" y1="15.0671" x2="20.2113" y2="11.5742" gradientUnits="userSpaceOnUse">
+<stop offset="0.0276698" stop-color="#7F4A40"/>
+<stop offset="0.183829" stop-color="#844A45" stop-opacity="0"/>
+</linearGradient>
+<linearGradient id="paint27_linear_18_32062" x1="20.3288" y1="11.4578" x2="17.1775" y2="14.6091" gradientUnits="userSpaceOnUse">
+<stop offset="0.0194806" stop-color="#9B705C"/>
+<stop offset="0.204546" stop-color="#9B705C" stop-opacity="0"/>
+</linearGradient>
+<linearGradient id="paint28_linear_18_32062" x1="11.375" y1="21.375" x2="9.21875" y2="19.2188" gradientUnits="userSpaceOnUse">
+<stop stop-color="#64342F"/>
+<stop offset="1" stop-color="#773C41"/>
+</linearGradient>
+<linearGradient id="paint29_linear_18_32062" x1="12.4531" y1="19.7656" x2="11.1719" y2="21.0469" gradientUnits="userSpaceOnUse">
+<stop stop-color="#835355" stop-opacity="0"/>
+<stop offset="0.542683" stop-color="#825254"/>
+</linearGradient>
+<linearGradient id="paint30_linear_18_32062" x1="15.1016" y1="17.1172" x2="15.9219" y2="16.2969" gradientUnits="userSpaceOnUse">
+<stop stop-color="#91634B" stop-opacity="0"/>
+<stop offset="0.257143" stop-color="#8E5E45"/>
+<stop offset="0.971429" stop-color="#79452B"/>
+</linearGradient>
+<linearGradient id="paint31_linear_18_32062" x1="10.8594" y1="16.0781" x2="11.8047" y2="17.0234" gradientUnits="userSpaceOnUse">
+<stop stop-color="#733821"/>
+<stop offset="0.198347" stop-color="#783F28"/>
+<stop offset="1" stop-color="#7E4934" stop-opacity="0"/>
+</linearGradient>
+<linearGradient id="paint32_linear_18_32062" x1="15.9844" y1="20.5469" x2="15.0937" y2="19.6563" gradientUnits="userSpaceOnUse">
+<stop offset="0.192983" stop-color="#7F4131"/>
+<stop offset="0.508772" stop-color="#884B3F"/>
+<stop offset="1" stop-color="#905452" stop-opacity="0"/>
+</linearGradient>
+<linearGradient id="paint33_linear_18_32062" x1="10.2625" y1="19.1146" x2="16.2421" y2="19.0912" gradientUnits="userSpaceOnUse">
+<stop stop-color="#865345"/>
+<stop offset="1" stop-color="#945C4E"/>
+</linearGradient>
+<linearGradient id="paint34_linear_18_32062" x1="11.4802" y1="20.2702" x2="15.0394" y2="16.7774" gradientUnits="userSpaceOnUse">
+<stop offset="0.0276698" stop-color="#7F4A40"/>
+<stop offset="0.183829" stop-color="#844A45" stop-opacity="0"/>
+</linearGradient>
+<linearGradient id="paint35_linear_18_32062" x1="15.1569" y1="16.6609" x2="12.0056" y2="19.8122" gradientUnits="userSpaceOnUse">
+<stop offset="0.0194806" stop-color="#9B705C"/>
+<stop offset="0.204546" stop-color="#9B705C" stop-opacity="0"/>
+</linearGradient>
+<linearGradient id="paint36_linear_18_32062" x1="26.9375" y1="16.3125" x2="15.75" y2="27.5" gradientUnits="userSpaceOnUse">
+<stop stop-color="#ED4253"/>
+<stop offset="1" stop-color="#C41959"/>
+</linearGradient>
+<linearGradient id="paint37_linear_18_32062" x1="25.9375" y1="25.125" x2="22.6875" y2="21.875" gradientUnits="userSpaceOnUse">
+<stop stop-color="#D61E58"/>
+<stop offset="0.932692" stop-color="#D71D59" stop-opacity="0"/>
+</linearGradient>
+<linearGradient id="paint38_linear_18_32062" x1="25.75" y1="15.125" x2="22.8437" y2="18.0312" gradientUnits="userSpaceOnUse">
+<stop offset="0.0860215" stop-color="#EA4B6C"/>
+<stop offset="0.693548" stop-color="#EC4B6C" stop-opacity="0"/>
+</linearGradient>
+<linearGradient id="paint39_linear_18_32062" x1="17.4375" y1="18.5" x2="18.8438" y2="19.5313" gradientUnits="userSpaceOnUse">
+<stop offset="0.384393" stop-color="#B3154F"/>
+<stop offset="0.766859" stop-color="#B71550" stop-opacity="0"/>
+</linearGradient>
+<linearGradient id="paint40_linear_18_32062" x1="23.3401" y1="11.4724" x2="11.25" y2="23.625" gradientUnits="userSpaceOnUse">
+<stop stop-color="#C1C1C2"/>
+<stop offset="0.0875295" stop-color="#ADABB2"/>
+<stop offset="0.198407" stop-color="#B1A4C2"/>
+<stop offset="0.757899" stop-color="#B3A5C6"/>
+<stop offset="0.879116" stop-color="#A892C1"/>
+<stop offset="1" stop-color="#A485C3"/>
+</linearGradient>
+<linearGradient id="paint41_linear_18_32062" x1="22.25" y1="10.7831" x2="16.5" y2="16.5331" gradientUnits="userSpaceOnUse">
+<stop stop-color="#D4D1DB"/>
+<stop offset="1" stop-color="#B3A6C5" stop-opacity="0"/>
+</linearGradient>
+</defs>
+</svg>
diff --git a/packages/frontend/assets/drop-and-fusion/sweets_monos/cookie_color.svg b/packages/frontend/assets/drop-and-fusion/sweets_monos/cookie_color.svg
new file mode 100644
index 0000000000..42b628cca1
--- /dev/null
+++ b/packages/frontend/assets/drop-and-fusion/sweets_monos/cookie_color.svg
@@ -0,0 +1,116 @@
+<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M2 16C2 25.29 8.27 30 16 30C23.73 30 30 25.26 30 16C30 6.57 23.73 2 16 2C8.27 2 2 6.43 2 16Z" fill="#DDB78F"/>
+<path d="M2 16C2 25.29 8.27 30 16 30C23.73 30 30 25.26 30 16C30 6.57 23.73 2 16 2C8.27 2 2 6.43 2 16Z" fill="url(#paint0_radial_18_31720)"/>
+<path d="M2 16C2 25.29 8.27 30 16 30C23.73 30 30 25.26 30 16C30 6.57 23.73 2 16 2C8.27 2 2 6.43 2 16Z" fill="url(#paint1_radial_18_31720)"/>
+<path d="M2 16C2 25.29 8.27 30 16 30C23.73 30 30 25.26 30 16C30 6.57 23.73 2 16 2C8.27 2 2 6.43 2 16Z" fill="url(#paint2_radial_18_31720)"/>
+<path d="M26.92 14.61L26.96 13.99C27 13.42 26.52 12.94 25.95 12.98L25.34 13.02C25.1 13.03 24.85 13.11 24.63 13.24C24.01 13.63 23.76 14.42 24.04 15.09C24.38 15.89 25.32 16.23 26.09 15.85C26.59 15.62 26.88 15.13 26.92 14.61Z" fill="#6F434A"/>
+<g filter="url(#filter0_f_18_31720)">
+<path d="M26.7344 13.875C26.7688 13.3847 26.4531 13.25 26.0156 13.25C25.5781 13.25 25.1653 13.4633 24.8125 13.6718C24.2792 14.0073 23.9955 14.43 24.2364 15.0063C24.5288 15.6944 25.0877 15.7019 25.75 15.375C26.3626 15.0932 26.5801 14.4399 26.7344 13.875Z" fill="url(#paint3_radial_18_31720)"/>
+</g>
+<path d="M19.89 8.32001L20.51 8.36001C21.08 8.40001 21.56 7.92001 21.52 7.35001L21.48 6.74001C21.47 6.50001 21.39 6.25001 21.26 6.03001C20.87 5.41001 20.08 5.16001 19.41 5.44001C18.61 5.78001 18.27 6.72001 18.65 7.49001C18.88 7.99001 19.37 8.28001 19.89 8.32001Z" fill="#6F434A"/>
+<g filter="url(#filter1_f_18_31720)">
+<path d="M20.0001 7.68832L20.4808 7.71707C20.9227 7.74582 21.2948 7.40083 21.2638 6.99115L21.2328 6.55273C21.2251 6.38023 21.163 6.20055 21.0622 6.04243C20.7599 5.59681 20.1474 5.41713 19.6279 5.61837C19.0077 5.86274 18.7441 6.53835 19.0387 7.09178C19.217 7.45114 19.5969 7.65957 20.0001 7.68832Z" fill="url(#paint4_radial_18_31720)"/>
+</g>
+<path d="M10.67 23.75L10.62 24.52C10.57 25.23 11.16 25.82 11.87 25.77L12.63 25.72C12.93 25.7 13.23 25.61 13.51 25.44C14.28 24.96 14.59 23.98 14.24 23.14C13.82 22.14 12.65 21.72 11.7 22.2C11.08 22.51 10.71 23.11 10.67 23.75Z" fill="#6F434A"/>
+<g filter="url(#filter2_f_18_31720)">
+<path d="M10.9819 23.765L10.9402 24.4071C10.8985 24.9991 11.3905 25.4911 11.9825 25.4494L12.6162 25.4077C12.8664 25.3911 13.1165 25.316 13.35 25.1743C13.9921 24.774 14.2506 23.9568 13.9587 23.2564C13.6085 22.4225 12.6329 22.0723 11.8407 22.4726C11.3238 22.7311 11.0152 23.2314 10.9819 23.765Z" fill="url(#paint5_radial_18_31720)"/>
+</g>
+<path d="M20.4 15.19L20.43 14.73C20.53 13.22 19.28 11.97 17.76 12.06L17.31 12.09C16.86 12.11 16.41 12.24 15.99 12.49C14.75 13.22 14.22 14.78 14.77 16.11C15.42 17.7 17.27 18.37 18.78 17.62C19.76 17.15 20.34 16.2 20.4 15.19Z" fill="#6F434A"/>
+<g filter="url(#filter3_f_18_31720)">
+<path d="M20.024 14.9836L20.0483 14.6099C20.1296 13.3833 19.1142 12.368 17.8795 12.4411L17.514 12.4655C17.1485 12.4817 16.7829 12.5873 16.4418 12.7904C15.4345 13.3833 15.004 14.6505 15.4508 15.7309C15.9788 17.0224 17.4815 17.5666 18.7081 16.9574C19.5041 16.5756 19.9752 15.804 20.024 14.9836Z" fill="url(#paint6_radial_18_31720)"/>
+</g>
+<path d="M7.68 9.41994L7.65 8.99994C7.57 7.62994 8.7 6.48994 10.07 6.57994L10.48 6.60994C10.89 6.62994 11.3 6.74994 11.68 6.96994C12.81 7.62994 13.28 9.04994 12.79 10.2599C12.2 11.6999 10.52 12.3099 9.15 11.6299C8.27 11.1899 7.74 10.3299 7.68 9.41994Z" fill="url(#paint7_radial_18_31720)"/>
+<g filter="url(#filter4_f_18_31720)">
+<path d="M8.15663 9.38764L8.13206 9.04363C8.06654 7.92149 8.99209 6.98774 10.1142 7.06146L10.45 7.08603C10.7859 7.10241 11.2825 7.0698 11.5938 7.25C12.5193 7.79059 12.8076 8.94642 12.4062 9.9375C11.923 11.117 10.4828 11.7548 9.36068 11.1978C8.63989 10.8374 8.20578 10.133 8.15663 9.38764Z" fill="url(#paint8_radial_18_31720)"/>
+</g>
+<path d="M24.26 22.82L24.28 23.18C24.36 24.35 23.38 25.33 22.21 25.25L21.86 25.23C21.51 25.21 21.16 25.11 20.83 24.92C19.86 24.35 19.46 23.14 19.88 22.11C20.39 20.88 21.82 20.35 23 20.94C23.76 21.3 24.21 22.03 24.26 22.82Z" fill="#6F434A"/>
+<g filter="url(#filter5_f_18_31720)">
+<path d="M24.0262 22.7197L24.043 23.0224C24.1103 24.0064 23.2861 24.8306 22.3021 24.7633L22.0078 24.7465C21.7134 24.7297 21.4191 24.6456 21.1415 24.4858C20.3258 24.0064 19.9893 22.9888 20.3426 22.1226C20.7715 21.0881 21.9741 20.6424 22.9665 21.1386C23.6057 21.4413 23.9842 22.0553 24.0262 22.7197Z" fill="url(#paint9_radial_18_31720)"/>
+</g>
+<path d="M5.90999 17.54L5.86999 16.92C5.82999 16.35 6.30999 15.87 6.87999 15.91L7.48999 15.95C7.72999 15.96 7.97999 16.04 8.19999 16.17C8.81999 16.56 9.06999 17.35 8.78999 18.02C8.44999 18.82 7.50999 19.16 6.73999 18.78C6.23999 18.55 5.93999 18.06 5.90999 17.54Z" fill="#6F434A"/>
+<g filter="url(#filter6_f_18_31720)">
+<path d="M6.21167 17.5158L6.17985 17.0226C6.14803 16.5691 6.52991 16.1872 6.9834 16.219L7.46872 16.2508C7.65966 16.2588 7.85856 16.3224 8.03359 16.4259C8.52686 16.7362 8.72576 17.3647 8.50299 17.8977C8.23249 18.5342 7.48463 18.8047 6.87202 18.5024C6.47422 18.3194 6.23554 17.9296 6.21167 17.5158Z" fill="url(#paint10_radial_18_31720)"/>
+</g>
+<defs>
+<filter id="filter0_f_18_31720" x="23.6498" y="12.75" width="3.58717" height="3.33459" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.25" result="effect1_foregroundBlur_18_31720"/>
+</filter>
+<filter id="filter1_f_18_31720" x="18.4181" y="5.03638" width="3.34752" height="3.18237" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.25" result="effect1_foregroundBlur_18_31720"/>
+</filter>
+<filter id="filter2_f_18_31720" x="10.4377" y="21.807" width="4.14041" height="4.1449" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.25" result="effect1_foregroundBlur_18_31720"/>
+</filter>
+<filter id="filter3_f_18_31720" x="14.7748" y="11.9374" width="5.77806" height="5.76941" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.25" result="effect1_foregroundBlur_18_31720"/>
+</filter>
+<filter id="filter4_f_18_31720" x="7.62878" y="6.55737" width="5.45396" height="5.34436" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.25" result="effect1_foregroundBlur_18_31720"/>
+</filter>
+<filter id="filter5_f_18_31720" x="19.7031" y="20.4355" width="4.84375" height="4.83167" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.25" result="effect1_foregroundBlur_18_31720"/>
+</filter>
+<filter id="filter6_f_18_31720" x="5.67799" y="15.7172" width="3.41576" height="3.40894" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.25" result="effect1_foregroundBlur_18_31720"/>
+</filter>
+<radialGradient id="paint0_radial_18_31720" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(23.4375 7.8125) rotate(119.554) scale(20.9077)">
+<stop stop-color="#FFDAAE"/>
+<stop offset="1" stop-color="#D59077" stop-opacity="0"/>
+</radialGradient>
+<radialGradient id="paint1_radial_18_31720" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(17.375 16) rotate(83.2902) scale(17.1172 16.2696)">
+<stop offset="0.772068" stop-color="#BF9E7A" stop-opacity="0"/>
+<stop offset="1" stop-color="#C4A47E"/>
+</radialGradient>
+<radialGradient id="paint2_radial_18_31720" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(19.375 6.5) rotate(92.6808) scale(37.4159)">
+<stop offset="0.324983" stop-color="#E9AB8B" stop-opacity="0"/>
+<stop offset="0.505034" stop-color="#DE9A80"/>
+<stop offset="0.656015" stop-color="#D07067"/>
+</radialGradient>
+<radialGradient id="paint3_radial_18_31720" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(26.6119 13.3594) rotate(140.104) scale(2.75277 2.61177)">
+<stop offset="0.175443" stop-color="#886562"/>
+<stop offset="1" stop-color="#8E6C67" stop-opacity="0"/>
+</radialGradient>
+<radialGradient id="paint4_radial_18_31720" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(21.2656 7.41039) rotate(-149.903) scale(2.90043 2.79159)">
+<stop offset="0.175443" stop-color="#886562"/>
+<stop offset="1" stop-color="#8E6C67" stop-opacity="0"/>
+</radialGradient>
+<radialGradient id="paint5_radial_18_31720" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(11.1292 25.2582) rotate(-41.0159) scale(3.90831 4.00044)">
+<stop offset="0.469854" stop-color="#896764" stop-opacity="0"/>
+<stop offset="0.935135" stop-color="#896763"/>
+</radialGradient>
+<radialGradient id="paint6_radial_18_31720" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(15.2748 16) rotate(-21.7768) scale(5.55942 5.69235)">
+<stop offset="0.388269" stop-color="#896764" stop-opacity="0"/>
+<stop offset="0.935135" stop-color="#896763"/>
+</radialGradient>
+<radialGradient id="paint7_radial_18_31720" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(11.125 8.875) rotate(123.403) scale(3.63294 3.63565)">
+<stop stop-color="#7D5755"/>
+<stop offset="1" stop-color="#60383B"/>
+</radialGradient>
+<radialGradient id="paint8_radial_18_31720" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(8.12878 9.5625) rotate(-3.96087) scale(4.82023 4.82383)">
+<stop offset="0.596528" stop-color="#896764" stop-opacity="0"/>
+<stop offset="0.935135" stop-color="#896763"/>
+</radialGradient>
+<radialGradient id="paint9_radial_18_31720" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(20.4375 24.5313) rotate(-40.886) scale(4.77422 4.88362)">
+<stop offset="0.469854" stop-color="#896764" stop-opacity="0"/>
+<stop offset="0.935135" stop-color="#896763"/>
+</radialGradient>
+<radialGradient id="paint10_radial_18_31720" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(6.3253 18.4778) rotate(-40.8959) scale(3.00101 3.06992)">
+<stop offset="0.469854" stop-color="#896764" stop-opacity="0"/>
+<stop offset="0.935135" stop-color="#896763"/>
+</radialGradient>
+</defs>
+</svg>
diff --git a/packages/frontend/assets/drop-and-fusion/sweets_monos/custard_color.svg b/packages/frontend/assets/drop-and-fusion/sweets_monos/custard_color.svg
new file mode 100644
index 0000000000..d967fec7fe
--- /dev/null
+++ b/packages/frontend/assets/drop-and-fusion/sweets_monos/custard_color.svg
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="32px" height="32px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <g transform="matrix(1,0,0,1,0,-6.5)">
+        <path d="M7.97,16.47L5.61,26L26.38,26L24.08,16.48C23.88,15.61 23.1,15 22.21,15L9.84,15C8.96,15 8.18,15.61 7.97,16.47Z" style="fill:url(#_Linear1);fill-rule:nonzero;"/>
+        <path d="M9.84,15C8.96,15 8.18,15.61 7.97,16.47L7.34,19L24.7,19L24.09,16.48C23.88,15.61 23.1,15 22.21,15L9.84,15Z" style="fill:url(#_Linear2);fill-rule:nonzero;"/>
+        <path d="M4,28C5.28,29.28 7.02,30 8.83,30L23.17,30C24.98,30 26.72,29.28 28,28L4,28Z" style="fill:url(#_Linear3);fill-rule:nonzero;"/>
+        <path d="M4,28C5.28,29.28 7.02,30 8.83,30L23.17,30C24.98,30 26.72,29.28 28,28L4,28Z" style="fill:url(#_Linear4);fill-rule:nonzero;"/>
+        <path d="M29,28L3,28C2.45,28 2,27.55 2,27C2,26.45 2.45,26 3,26L29,26C29.55,26 30,26.45 30,27C30,27.55 29.55,28 29,28Z" style="fill:url(#_Linear5);fill-rule:nonzero;"/>
+        <path d="M29,28L3,28C2.45,28 2,27.55 2,27C2,26.45 2.45,26 3,26L29,26C29.55,26 30,26.45 30,27C30,27.55 29.55,28 29,28Z" style="fill:url(#_Radial6);fill-rule:nonzero;"/>
+        <path d="M29,28L3,28C2.45,28 2,27.55 2,27C2,26.45 2.45,26 3,26L29,26C29.55,26 30,26.45 30,27C30,27.55 29.55,28 29,28Z" style="fill:url(#_Radial7);fill-rule:nonzero;"/>
+    </g>
+    <defs>
+        <linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(20.77,0,0,20.77,5.61,26)"><stop offset="0" style="stop-color:rgb(148,107,83);stop-opacity:1"/><stop offset="0.25" style="stop-color:rgb(166,108,58);stop-opacity:1"/><stop offset="0.54" style="stop-color:rgb(204,139,83);stop-opacity:1"/><stop offset="0.75" style="stop-color:rgb(224,165,108);stop-opacity:1"/><stop offset="0.86" style="stop-color:rgb(230,165,103);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(204,153,104);stop-opacity:1"/></linearGradient>
+        <linearGradient id="_Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(16.8875,0,0,16.8875,7.8125,17.3125)"><stop offset="0" style="stop-color:rgb(113,76,64);stop-opacity:1"/><stop offset="0.25" style="stop-color:rgb(122,74,57);stop-opacity:1"/><stop offset="0.49" style="stop-color:rgb(149,95,75);stop-opacity:1"/><stop offset="0.78" style="stop-color:rgb(180,128,107);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(172,121,98);stop-opacity:1"/></linearGradient>
+        <linearGradient id="_Linear3" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(23.625,0,0,23.625,4.375,30)"><stop offset="0" style="stop-color:rgb(173,153,193);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(173,150,195);stop-opacity:1"/></linearGradient>
+        <linearGradient id="_Linear4" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-0.0313,1.9219,-1.9219,-0.0313,16.2813,26.5469)"><stop offset="0" style="stop-color:rgb(152,131,172);stop-opacity:1"/><stop offset="0.73" style="stop-color:rgb(152,131,172);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(156,132,180);stop-opacity:0"/></linearGradient>
+        <linearGradient id="_Linear5" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(28.3125,0,0,28.3125,2,27)"><stop offset="0" style="stop-color:rgb(134,133,137);stop-opacity:1"/><stop offset="0.51" style="stop-color:rgb(172,170,172);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(188,185,194);stop-opacity:1"/></linearGradient>
+        <radialGradient id="_Radial6" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-2.99107e-16,0.78125,-14.5,-5.55142e-15,26.1875,26.7187)"><stop offset="0" style="stop-color:rgb(221,218,228);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(222,219,228);stop-opacity:0"/></radialGradient>
+        <radialGradient id="_Radial7" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-5.98214e-16,1.5625,-60.1813,-2.30408e-14,11.1875,28)"><stop offset="0" style="stop-color:rgb(175,152,197);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(174,152,197);stop-opacity:0"/></radialGradient>
+    </defs>
+</svg>
diff --git a/packages/frontend/assets/drop-and-fusion/sweets_monos/doughnut_color.svg b/packages/frontend/assets/drop-and-fusion/sweets_monos/doughnut_color.svg
new file mode 100644
index 0000000000..e8e225bc0a
--- /dev/null
+++ b/packages/frontend/assets/drop-and-fusion/sweets_monos/doughnut_color.svg
@@ -0,0 +1,272 @@
+<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M27.32 14C25.67 9.34 21.22 6 16 6C10.78 6 6.33 9.34 4.68 14H4V18C4 24.63 9.37 30 16 30C22.63 30 28 24.63 28 18V14H27.32ZM20.5 17.64C20.5 19.85 18.71 21.64 16.5 21.64H15.5C13.29 21.64 11.5 19.85 11.5 17.64C11.5 15.43 13.29 14 15.5 14H16.5C18.71 14 20.5 15.43 20.5 17.64Z" fill="url(#paint0_linear_18_31224)"/>
+<path d="M27.32 14C25.67 9.34 21.22 6 16 6C10.78 6 6.33 9.34 4.68 14H4V18C4 24.63 9.37 30 16 30C22.63 30 28 24.63 28 18V14H27.32ZM20.5 17.64C20.5 19.85 18.71 21.64 16.5 21.64H15.5C13.29 21.64 11.5 19.85 11.5 17.64C11.5 15.43 13.29 14 15.5 14H16.5C18.71 14 20.5 15.43 20.5 17.64Z" fill="url(#paint1_radial_18_31224)"/>
+<path d="M27.32 14C25.67 9.34 21.22 6 16 6C10.78 6 6.33 9.34 4.68 14H4V18C4 24.63 9.37 30 16 30C22.63 30 28 24.63 28 18V14H27.32ZM20.5 17.64C20.5 19.85 18.71 21.64 16.5 21.64H15.5C13.29 21.64 11.5 19.85 11.5 17.64C11.5 15.43 13.29 14 15.5 14H16.5C18.71 14 20.5 15.43 20.5 17.64Z" fill="url(#paint2_radial_18_31224)"/>
+<path d="M16 2C9.37 2 3.8125 7.37 3.8125 14C3.8125 20.63 8.96875 26.3438 16 26.3438C23.0312 26.3438 28.25 20.63 28.25 14C28.25 7.37 22.63 2 16 2ZM20.5 14C20.5 16.21 18.71 18 16.5 18H15.5C13.29 18 11.5 16.21 11.5 14C11.5 12 13.29 10.2812 15.5 10.2812H16.5C18.71 10.2812 20.5 11.9688 20.5 14Z" fill="url(#paint3_radial_18_31224)"/>
+<path d="M16 2C9.37 2 3.8125 7.37 3.8125 14C3.8125 20.63 8.96875 26.3438 16 26.3438C23.0312 26.3438 28.25 20.63 28.25 14C28.25 7.37 22.63 2 16 2ZM20.5 14C20.5 16.21 18.71 18 16.5 18H15.5C13.29 18 11.5 16.21 11.5 14C11.5 12 13.29 10.2812 15.5 10.2812H16.5C18.71 10.2812 20.5 11.9688 20.5 14Z" fill="url(#paint4_radial_18_31224)"/>
+<path d="M16 2C9.37 2 3.8125 7.37 3.8125 14C3.8125 20.63 8.96875 26.3438 16 26.3438C23.0312 26.3438 28.25 20.63 28.25 14C28.25 7.37 22.63 2 16 2ZM20.5 14C20.5 16.21 18.71 18 16.5 18H15.5C13.29 18 11.5 16.21 11.5 14C11.5 12 13.29 10.2812 15.5 10.2812H16.5C18.71 10.2812 20.5 11.9688 20.5 14Z" fill="url(#paint5_radial_18_31224)"/>
+<g filter="url(#filter0_f_18_31224)">
+<path d="M24.4825 11.3693C24.6825 11.1693 24.6825 10.8293 24.4825 10.6293L23.7025 9.84934C23.5025 9.64934 23.1625 9.64934 22.9625 9.84934C22.7625 10.0493 22.7625 10.3893 22.9625 10.5893L23.7425 11.3693C23.9425 11.5793 24.2825 11.5793 24.4825 11.3693Z" fill="#783C43"/>
+</g>
+<g filter="url(#filter1_f_18_31224)">
+<path d="M23.0563 16.19C22.8563 15.99 22.8563 15.65 23.0563 15.45L23.8363 14.67C24.0363 14.47 24.3763 14.47 24.5763 14.67C24.7763 14.87 24.7763 15.21 24.5763 15.41L23.7963 16.19C23.5963 16.4 23.2563 16.4 23.0563 16.19Z" fill="#733D42"/>
+</g>
+<g filter="url(#filter2_f_18_31224)">
+<path d="M21.7019 20.335C21.9019 20.135 21.9019 19.795 21.7019 19.595L20.9219 18.815C20.7219 18.615 20.3819 18.615 20.1819 18.815C19.9819 19.015 19.9819 19.355 20.1819 19.555L20.9619 20.335C21.1619 20.545 21.5019 20.545 21.7019 20.335Z" fill="#7E3B46"/>
+</g>
+<g filter="url(#filter3_f_18_31224)">
+<path d="M14.0331 23.2882C13.8331 23.0882 13.8331 22.7482 14.0331 22.5482L14.8131 21.7682C15.0131 21.5682 15.3531 21.5682 15.5531 21.7682C15.7531 21.9682 15.7531 22.3082 15.5531 22.5082L14.7731 23.2882C14.5731 23.4982 14.2331 23.4982 14.0331 23.2882Z" fill="#823C46"/>
+</g>
+<path d="M24.7525 11.2912C24.5525 11.4912 24.2125 11.4912 24.0125 11.2912L23.2325 10.5112C23.0325 10.3112 23.0325 9.97122 23.2325 9.77122C23.4325 9.57122 23.7725 9.57122 23.9725 9.77122L24.7525 10.5512C24.9625 10.7512 24.9625 11.0912 24.7525 11.2912Z" fill="url(#paint6_linear_18_31224)"/>
+<path d="M24.7525 11.2912C24.5525 11.4912 24.2125 11.4912 24.0125 11.2912L23.2325 10.5112C23.0325 10.3112 23.0325 9.97122 23.2325 9.77122C23.4325 9.57122 23.7725 9.57122 23.9725 9.77122L24.7525 10.5512C24.9625 10.7512 24.9625 11.0912 24.7525 11.2912Z" fill="url(#paint7_radial_18_31224)"/>
+<g filter="url(#filter4_f_18_31224)">
+<path d="M15.9156 6.40181C15.7156 6.20181 15.7156 5.86181 15.9156 5.66181L16.6956 4.88181C16.8956 4.68181 17.2356 4.68181 17.4356 4.88181C17.6356 5.08181 17.6356 5.42181 17.4356 5.62181L16.6556 6.40181C16.4556 6.61181 16.1156 6.61181 15.9156 6.40181Z" fill="#67383D"/>
+</g>
+<g filter="url(#filter5_f_18_31224)">
+<path d="M20.9781 8.42061C20.7781 8.22061 20.7781 7.88061 20.9781 7.68061L21.7581 6.90061C21.9581 6.70061 22.2981 6.70061 22.4981 6.90061C22.6981 7.10061 22.6981 7.44061 22.4981 7.64061L21.7181 8.42061C21.5181 8.63061 21.1781 8.63061 20.9781 8.42061Z" fill="#6B3C40"/>
+</g>
+<path d="M16.15 6.27999C15.95 6.07999 15.95 5.73999 16.15 5.53999L16.93 4.75999C17.13 4.55999 17.47 4.55999 17.67 4.75999C17.87 4.95999 17.87 5.29999 17.67 5.49999L16.89 6.27999C16.69 6.48999 16.35 6.48999 16.15 6.27999Z" fill="url(#paint8_linear_18_31224)"/>
+<path d="M16.15 6.27999C15.95 6.07999 15.95 5.73999 16.15 5.53999L16.93 4.75999C17.13 4.55999 17.47 4.55999 17.67 4.75999C17.87 4.95999 17.87 5.29999 17.67 5.49999L16.89 6.27999C16.69 6.48999 16.35 6.48999 16.15 6.27999Z" fill="url(#paint9_radial_18_31224)"/>
+<g filter="url(#filter6_f_18_31224)">
+<path d="M8.06003 9.36556C7.86003 9.16556 7.86003 8.82556 8.06003 8.62556L8.84003 7.84556C9.04003 7.64556 9.38003 7.64556 9.58003 7.84556C9.78003 8.04556 9.78003 8.38556 9.58003 8.58556L8.80003 9.36556C8.60003 9.56556 8.26003 9.56556 8.06003 9.36556Z" fill="#62393D"/>
+</g>
+<path d="M8.33378 9.25618C8.13378 9.05618 8.13378 8.71618 8.33378 8.51618L9.11378 7.73618C9.31378 7.53618 9.65378 7.53618 9.85378 7.73618C10.0538 7.93618 10.0538 8.27618 9.85378 8.47618L9.07378 9.25618C8.87378 9.45618 8.53378 9.45618 8.33378 9.25618Z" fill="url(#paint10_linear_18_31224)"/>
+<path d="M8.33378 9.25618C8.13378 9.05618 8.13378 8.71618 8.33378 8.51618L9.11378 7.73618C9.31378 7.53618 9.65378 7.53618 9.85378 7.73618C10.0538 7.93618 10.0538 8.27618 9.85378 8.47618L9.07378 9.25618C8.87378 9.45618 8.53378 9.45618 8.33378 9.25618Z" fill="url(#paint11_radial_18_31224)"/>
+<path d="M14.33 23.1975C14.13 22.9975 14.13 22.6575 14.33 22.4575L15.11 21.6775C15.31 21.4775 15.65 21.4775 15.85 21.6775C16.05 21.8775 16.05 22.2175 15.85 22.4175L15.07 23.1975C14.87 23.4075 14.53 23.4075 14.33 23.1975Z" fill="url(#paint12_linear_18_31224)"/>
+<path d="M14.33 23.1975C14.13 22.9975 14.13 22.6575 14.33 22.4575L15.11 21.6775C15.31 21.4775 15.65 21.4775 15.85 21.6775C16.05 21.8775 16.05 22.2175 15.85 22.4175L15.07 23.1975C14.87 23.4075 14.53 23.4075 14.33 23.1975Z" fill="url(#paint13_radial_18_31224)"/>
+<path d="M21.8425 20.1624C21.6425 20.3624 21.3025 20.3624 21.1025 20.1624L20.3225 19.3824C20.1225 19.1824 20.1225 18.8424 20.3225 18.6424C20.5225 18.4424 20.8625 18.4424 21.0625 18.6424L21.8425 19.4224C22.0525 19.6224 22.0525 19.9624 21.8425 20.1624Z" fill="url(#paint14_linear_18_31224)"/>
+<path d="M21.8425 20.1624C21.6425 20.3624 21.3025 20.3624 21.1025 20.1624L20.3225 19.3824C20.1225 19.1824 20.1225 18.8424 20.3225 18.6424C20.5225 18.4424 20.8625 18.4424 21.0625 18.6424L21.8425 19.4224C22.0525 19.6224 22.0525 19.9624 21.8425 20.1624Z" fill="url(#paint15_radial_18_31224)"/>
+<path d="M23.24 16.1037C23.04 15.9037 23.04 15.5637 23.24 15.3637L24.02 14.5837C24.22 14.3837 24.56 14.3837 24.76 14.5837C24.96 14.7837 24.96 15.1237 24.76 15.3237L23.98 16.1037C23.78 16.3137 23.44 16.3137 23.24 16.1037Z" fill="url(#paint16_linear_18_31224)"/>
+<path d="M23.24 16.1037C23.04 15.9037 23.04 15.5637 23.24 15.3637L24.02 14.5837C24.22 14.3837 24.56 14.3837 24.76 14.5837C24.96 14.7837 24.96 15.1237 24.76 15.3237L23.98 16.1037C23.78 16.3137 23.44 16.3137 23.24 16.1037Z" fill="url(#paint17_radial_18_31224)"/>
+<g filter="url(#filter7_f_18_31224)">
+<path d="M11.618 21.2525C11.418 21.4525 11.078 21.4525 10.878 21.2525L10.098 20.4725C9.89801 20.2725 9.89801 19.9325 10.098 19.7325C10.298 19.5325 10.638 19.5325 10.838 19.7325L11.618 20.5125C11.828 20.7125 11.828 21.0525 11.618 21.2525Z" fill="#693D49"/>
+</g>
+<path d="M11.8375 21.0725C11.6375 21.2725 11.2975 21.2725 11.0975 21.0725L10.3175 20.2925C10.1175 20.0925 10.1175 19.7525 10.3175 19.5525C10.5175 19.3525 10.8575 19.3525 11.0575 19.5525L11.8375 20.3325C12.0475 20.5325 12.0475 20.8725 11.8375 21.0725Z" fill="url(#paint18_linear_18_31224)"/>
+<path d="M11.8375 21.0725C11.6375 21.2725 11.2975 21.2725 11.0975 21.0725L10.3175 20.2925C10.1175 20.0925 10.1175 19.7525 10.3175 19.5525C10.5175 19.3525 10.8575 19.3525 11.0575 19.5525L11.8375 20.3325C12.0475 20.5325 12.0475 20.8725 11.8375 21.0725Z" fill="url(#paint19_radial_18_31224)"/>
+<g filter="url(#filter8_f_18_31224)">
+<path d="M6.05543 14.1662C5.85543 13.9662 5.85543 13.6262 6.05543 13.4262L6.83543 12.6462C7.03543 12.4462 7.37543 12.4462 7.57543 12.6462C7.77543 12.8462 7.77543 13.1862 7.57543 13.3862L6.79543 14.1662C6.59543 14.3762 6.25543 14.3762 6.05543 14.1662Z" fill="#5A3840"/>
+</g>
+<g filter="url(#filter9_f_18_31224)">
+<path d="M12.5852 7.31929C12.3852 7.51929 12.0452 7.51929 11.8452 7.31929L11.0552 6.53929C10.8552 6.33929 10.8552 5.99929 11.0552 5.79929C11.2552 5.59929 11.5952 5.59929 11.7952 5.79929L12.5752 6.57929C12.7852 6.77929 12.7852 7.11929 12.5852 7.31929Z" fill="#5A3840"/>
+</g>
+<path d="M12.7613 7.19747C12.5613 7.39747 12.2213 7.39747 12.0213 7.19747L11.2313 6.41747C11.0313 6.21747 11.0313 5.87747 11.2313 5.67747C11.4313 5.47747 11.7713 5.47747 11.9713 5.67747L12.7513 6.45747C12.9613 6.65747 12.9613 6.99747 12.7613 7.19747Z" fill="url(#paint20_linear_18_31224)"/>
+<path d="M12.7613 7.19747C12.5613 7.39747 12.2213 7.39747 12.0213 7.19747L11.2313 6.41747C11.0313 6.21747 11.0313 5.87747 11.2313 5.67747C11.4313 5.47747 11.7713 5.47747 11.9713 5.67747L12.7513 6.45747C12.9613 6.65747 12.9613 6.99747 12.7613 7.19747Z" fill="url(#paint21_radial_18_31224)"/>
+<path d="M6.33379 14.0725C6.13379 13.8725 6.13379 13.5325 6.33379 13.3325L7.11379 12.5525C7.31379 12.3525 7.65379 12.3525 7.85379 12.5525C8.05379 12.7525 8.05379 13.0925 7.85379 13.2925L7.07379 14.0725C6.87379 14.2825 6.53379 14.2825 6.33379 14.0725Z" fill="url(#paint22_linear_18_31224)"/>
+<path d="M6.33379 14.0725C6.13379 13.8725 6.13379 13.5325 6.33379 13.3325L7.11379 12.5525C7.31379 12.3525 7.65379 12.3525 7.85379 12.5525C8.05379 12.7525 8.05379 13.0925 7.85379 13.2925L7.07379 14.0725C6.87379 14.2825 6.53379 14.2825 6.33379 14.0725Z" fill="url(#paint23_radial_18_31224)"/>
+<path d="M21.2363 8.3387C21.0363 8.1387 21.0363 7.7987 21.2363 7.5987L22.0163 6.8187C22.2163 6.6187 22.5563 6.6187 22.7563 6.8187C22.9563 7.0187 22.9563 7.3587 22.7563 7.5587L21.9763 8.3387C21.7763 8.5487 21.4363 8.5487 21.2363 8.3387Z" fill="url(#paint24_linear_18_31224)"/>
+<path d="M21.2363 8.3387C21.0363 8.1387 21.0363 7.7987 21.2363 7.5987L22.0163 6.8187C22.2163 6.6187 22.5563 6.6187 22.7563 6.8187C22.9563 7.0187 22.9563 7.3587 22.7563 7.5587L21.9763 8.3387C21.7763 8.5487 21.4363 8.5487 21.2363 8.3387Z" fill="url(#paint25_radial_18_31224)"/>
+<g filter="url(#filter10_f_18_31224)">
+<path d="M10.5019 17.4825C10.3019 17.6825 9.96191 17.6825 9.76191 17.4825L8.98191 16.7025C8.78191 16.5025 8.78191 16.1625 8.98191 15.9625C9.18191 15.7625 9.52191 15.7625 9.72191 15.9625L10.5019 16.7425C10.7119 16.9425 10.7119 17.2725 10.5019 17.4825Z" fill="#6F3A43"/>
+</g>
+<path d="M10.76 17.2638C10.56 17.4638 10.22 17.4638 10.02 17.2638L9.24003 16.4837C9.04003 16.2837 9.04003 15.9438 9.24003 15.7438C9.44003 15.5438 9.78003 15.5438 9.98003 15.7438L10.76 16.5237C10.97 16.7237 10.97 17.0538 10.76 17.2638Z" fill="url(#paint26_linear_18_31224)"/>
+<path d="M10.76 17.2638C10.56 17.4638 10.22 17.4638 10.02 17.2638L9.24003 16.4837C9.04003 16.2837 9.04003 15.9438 9.24003 15.7438C9.44003 15.5438 9.78003 15.5438 9.98003 15.7438L10.76 16.5237C10.97 16.7237 10.97 17.0538 10.76 17.2638Z" fill="url(#paint27_radial_18_31224)"/>
+<g filter="url(#filter11_f_18_31224)">
+<path d="M10.5781 10.4844C8.93209 13.1078 11.0312 15.5938 11.0312 15.5938C10.5298 14.3629 10.6853 12.5943 11.2031 11.5156C12.1052 9.63666 13.4219 8.45318 16.3125 8.25C13.5781 7.85943 11.7593 8.60176 10.5781 10.4844Z" fill="url(#paint28_radial_18_31224)"/>
+</g>
+<g filter="url(#filter12_f_18_31224)">
+<path d="M25.375 20.1875C20.625 25.75 12.6875 23.6874 12.6875 23.6874C19.2953 23.6874 22.2286 22.608 24.75 19.1249C27.2714 15.6417 26.9375 9.6874 24.125 5.65623C27.375 8.99994 28.6902 16.3051 25.375 20.1875Z" fill="url(#paint29_radial_18_31224)"/>
+</g>
+<defs>
+<filter id="filter0_f_18_31224" x="22.5625" y="9.44934" width="2.32001" height="2.32751" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.125" result="effect1_foregroundBlur_18_31224"/>
+</filter>
+<filter id="filter1_f_18_31224" x="22.6563" y="14.27" width="2.32001" height="2.32751" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.125" result="effect1_foregroundBlur_18_31224"/>
+</filter>
+<filter id="filter2_f_18_31224" x="19.7819" y="18.415" width="2.32001" height="2.32751" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.125" result="effect1_foregroundBlur_18_31224"/>
+</filter>
+<filter id="filter3_f_18_31224" x="13.6331" y="21.3682" width="2.32001" height="2.32751" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.125" result="effect1_foregroundBlur_18_31224"/>
+</filter>
+<filter id="filter4_f_18_31224" x="15.5156" y="4.48181" width="2.32001" height="2.32751" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.125" result="effect1_foregroundBlur_18_31224"/>
+</filter>
+<filter id="filter5_f_18_31224" x="20.5781" y="6.50061" width="2.32001" height="2.32751" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.125" result="effect1_foregroundBlur_18_31224"/>
+</filter>
+<filter id="filter6_f_18_31224" x="7.66003" y="7.44556" width="2.32001" height="2.31995" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.125" result="effect1_foregroundBlur_18_31224"/>
+</filter>
+<filter id="filter7_f_18_31224" x="9.69801" y="19.3325" width="2.3275" height="2.31995" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.125" result="effect1_foregroundBlur_18_31224"/>
+</filter>
+<filter id="filter8_f_18_31224" x="5.65543" y="12.2462" width="2.32001" height="2.32751" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.125" result="effect1_foregroundBlur_18_31224"/>
+</filter>
+<filter id="filter9_f_18_31224" x="10.6552" y="5.39929" width="2.32877" height="2.31995" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.125" result="effect1_foregroundBlur_18_31224"/>
+</filter>
+<filter id="filter10_f_18_31224" x="8.58191" y="15.5625" width="2.3275" height="2.31995" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.125" result="effect1_foregroundBlur_18_31224"/>
+</filter>
+<filter id="filter11_f_18_31224" x="8.94801" y="7.14905" width="8.36449" height="9.4447" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.5" result="effect1_foregroundBlur_18_31224"/>
+</filter>
+<filter id="filter12_f_18_31224" x="11.6875" y="4.65625" width="16.6161" height="20.4052" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.5" result="effect1_foregroundBlur_18_31224"/>
+</filter>
+<linearGradient id="paint0_linear_18_31224" x1="3.5625" y1="20.5625" x2="28.375" y2="20.5625" gradientUnits="userSpaceOnUse">
+<stop stop-color="#BE8A63"/>
+<stop offset="0.183879" stop-color="#DF8777"/>
+<stop offset="0.463476" stop-color="#F28886"/>
+<stop offset="0.803526" stop-color="#F0AE9C"/>
+<stop offset="1" stop-color="#EAC891"/>
+</linearGradient>
+<radialGradient id="paint1_radial_18_31224" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(16.75 11.6875) rotate(97.5709) scale(9.96184 9.16466)">
+<stop offset="0.405405" stop-color="#F48F75"/>
+<stop offset="0.598905" stop-color="#FFA85B"/>
+<stop offset="0.954955" stop-color="#FFA85B" stop-opacity="0"/>
+</radialGradient>
+<radialGradient id="paint2_radial_18_31224" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(15.6875 16.6875) rotate(90) scale(5.1875 5.88997)">
+<stop offset="0.53012" stop-color="#CD777A"/>
+<stop offset="1" stop-color="#D47A70" stop-opacity="0"/>
+</radialGradient>
+<radialGradient id="paint3_radial_18_31224" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(23.875 20.125) rotate(-135.234) scale(21.6553 18.6519)">
+<stop offset="0.393247" stop-color="#AB6B59"/>
+<stop offset="0.912271" stop-color="#7E4C42"/>
+<stop offset="1" stop-color="#664946"/>
+</radialGradient>
+<radialGradient id="paint4_radial_18_31224" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(16.875 13) rotate(88.698) scale(5.50142 5.67515)">
+<stop offset="0.686112" stop-color="#854D42"/>
+<stop offset="1" stop-color="#844C43" stop-opacity="0"/>
+</radialGradient>
+<radialGradient id="paint5_radial_18_31224" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(16.3125 9.75) rotate(90) scale(17.9375 19.5)">
+<stop offset="0.790941" stop-color="#A15B79" stop-opacity="0"/>
+<stop offset="0.9144" stop-color="#A15B75"/>
+</radialGradient>
+<linearGradient id="paint6_linear_18_31224" x1="23.5634" y1="10.9375" x2="24.3447" y2="10.1568" gradientUnits="userSpaceOnUse">
+<stop stop-color="#D256BC"/>
+<stop offset="0.501103" stop-color="#FF73E1"/>
+<stop offset="1" stop-color="#FF82E8"/>
+</linearGradient>
+<radialGradient id="paint7_radial_18_31224" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(24.7111 10.8047) rotate(138.898) scale(0.404057 0.258873)">
+<stop stop-color="#FF94FF"/>
+<stop offset="1" stop-color="#FF94FF" stop-opacity="0"/>
+</radialGradient>
+<linearGradient id="paint8_linear_18_31224" x1="16.5937" y1="5.125" x2="17.2812" y2="5.90625" gradientUnits="userSpaceOnUse">
+<stop stop-color="#F66EC9"/>
+<stop offset="0.498647" stop-color="#FF8AF5"/>
+<stop offset="1" stop-color="#FF72DB"/>
+</linearGradient>
+<radialGradient id="paint9_radial_18_31224" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(17.3281 5.03125) rotate(135) scale(1.5026 0.85995)">
+<stop offset="0.636927" stop-color="#E55DC8" stop-opacity="0"/>
+<stop offset="1" stop-color="#DB5BC1"/>
+</radialGradient>
+<linearGradient id="paint10_linear_18_31224" x1="8.77753" y1="8.09908" x2="9.46184" y2="8.87992" gradientUnits="userSpaceOnUse">
+<stop stop-color="#F66EC9"/>
+<stop offset="0.498647" stop-color="#FF8AF5"/>
+<stop offset="1" stop-color="#FF72DB"/>
+</linearGradient>
+<radialGradient id="paint11_radial_18_31224" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(9.5119 8.00572) rotate(135.118) scale(1.49952 0.85818)">
+<stop offset="0.636927" stop-color="#E55DC8" stop-opacity="0"/>
+<stop offset="1" stop-color="#DB5BC1"/>
+</radialGradient>
+<linearGradient id="paint12_linear_18_31224" x1="14.7737" y1="22.0425" x2="15.4612" y2="22.8237" gradientUnits="userSpaceOnUse">
+<stop stop-color="#F66EC9"/>
+<stop offset="0.498647" stop-color="#FF8AF5"/>
+<stop offset="1" stop-color="#FF72DB"/>
+</linearGradient>
+<radialGradient id="paint13_radial_18_31224" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(15.5081 21.9487) rotate(135) scale(1.5026 0.85995)">
+<stop offset="0.636927" stop-color="#E55DC8" stop-opacity="0"/>
+<stop offset="1" stop-color="#DB5BC1"/>
+</radialGradient>
+<linearGradient id="paint14_linear_18_31224" x1="20.6534" y1="19.8087" x2="21.4347" y2="19.028" gradientUnits="userSpaceOnUse">
+<stop stop-color="#D256BC"/>
+<stop offset="0.501103" stop-color="#FF73E1"/>
+<stop offset="1" stop-color="#FF82E8"/>
+</linearGradient>
+<radialGradient id="paint15_radial_18_31224" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(21.8011 19.6759) rotate(138.898) scale(0.404057 0.258873)">
+<stop stop-color="#FF94FF"/>
+<stop offset="1" stop-color="#FF94FF" stop-opacity="0"/>
+</radialGradient>
+<linearGradient id="paint16_linear_18_31224" x1="23.6838" y1="14.9487" x2="24.3713" y2="15.73" gradientUnits="userSpaceOnUse">
+<stop stop-color="#22A4FA"/>
+<stop offset="0.498647" stop-color="#49C0FF"/>
+<stop offset="1" stop-color="#2AB1FF"/>
+</linearGradient>
+<radialGradient id="paint17_radial_18_31224" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(24.4182 14.855) rotate(133.986) scale(1.69913 1.74052)">
+<stop offset="0.658529" stop-color="#2A77DD" stop-opacity="0"/>
+<stop offset="1" stop-color="#2B73DA"/>
+</radialGradient>
+<linearGradient id="paint18_linear_18_31224" x1="10.6484" y1="20.7187" x2="11.4297" y2="19.938" gradientUnits="userSpaceOnUse">
+<stop stop-color="#2A61B8"/>
+<stop offset="0.501103" stop-color="#25A1FF"/>
+<stop offset="1" stop-color="#2DBFFF"/>
+</linearGradient>
+<radialGradient id="paint19_radial_18_31224" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(11.796 20.5859) rotate(138.898) scale(0.404057 0.258873)">
+<stop stop-color="#4FC9FF"/>
+<stop offset="1" stop-color="#50CBFF" stop-opacity="0"/>
+</radialGradient>
+<linearGradient id="paint20_linear_18_31224" x1="11.5625" y1="6.84375" x2="12.3438" y2="6.0625" gradientUnits="userSpaceOnUse">
+<stop stop-color="#2A61B8"/>
+<stop offset="0.501103" stop-color="#25A1FF"/>
+<stop offset="1" stop-color="#2DBFFF"/>
+</linearGradient>
+<radialGradient id="paint21_radial_18_31224" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(12.7109 6.71094) rotate(138.918) scale(0.404217 0.258951)">
+<stop stop-color="#4FC9FF"/>
+<stop offset="1" stop-color="#50CBFF" stop-opacity="0"/>
+</radialGradient>
+<linearGradient id="paint22_linear_18_31224" x1="6.77754" y1="12.9175" x2="7.46504" y2="13.6987" gradientUnits="userSpaceOnUse">
+<stop stop-color="#22A4FA"/>
+<stop offset="0.498647" stop-color="#49C0FF"/>
+<stop offset="1" stop-color="#2AB1FF"/>
+</linearGradient>
+<radialGradient id="paint23_radial_18_31224" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(7.51192 12.8237) rotate(133.986) scale(1.69913 1.74052)">
+<stop offset="0.658529" stop-color="#2A77DD" stop-opacity="0"/>
+<stop offset="1" stop-color="#2B73DA"/>
+</radialGradient>
+<linearGradient id="paint24_linear_18_31224" x1="21.68" y1="7.18372" x2="22.3675" y2="7.96497" gradientUnits="userSpaceOnUse">
+<stop stop-color="#22A4FA"/>
+<stop offset="0.498647" stop-color="#49C0FF"/>
+<stop offset="1" stop-color="#2AB1FF"/>
+</linearGradient>
+<radialGradient id="paint25_radial_18_31224" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(22.4144 7.08997) rotate(133.986) scale(1.69913 1.74052)">
+<stop offset="0.658529" stop-color="#2A77DD" stop-opacity="0"/>
+<stop offset="1" stop-color="#2B73DA"/>
+</radialGradient>
+<linearGradient id="paint26_linear_18_31224" x1="9.57092" y1="16.91" x2="10.3522" y2="16.1293" gradientUnits="userSpaceOnUse">
+<stop stop-color="#D256BC"/>
+<stop offset="0.501103" stop-color="#FF73E1"/>
+<stop offset="1" stop-color="#FF82E8"/>
+</linearGradient>
+<radialGradient id="paint27_radial_18_31224" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(10.7186 16.7772) rotate(138.898) scale(0.404057 0.258873)">
+<stop stop-color="#FF94FF"/>
+<stop offset="1" stop-color="#FF94FF" stop-opacity="0"/>
+</radialGradient>
+<radialGradient id="paint28_radial_18_31224" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(12.1875 10.4375) rotate(47.0901) scale(6.05863 29.5218)">
+<stop stop-color="#C79A91"/>
+<stop offset="1" stop-color="#C79A91" stop-opacity="0"/>
+</radialGradient>
+<radialGradient id="paint29_radial_18_31224" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(25.25 19.0625) rotate(-157.529) scale(15.083 71.364)">
+<stop stop-color="#CF9689"/>
+<stop offset="1" stop-color="#D1988A" stop-opacity="0"/>
+</radialGradient>
+</defs>
+</svg>
diff --git a/packages/frontend/assets/drop-and-fusion/sweets_monos/lollipop_color.svg b/packages/frontend/assets/drop-and-fusion/sweets_monos/lollipop_color.svg
new file mode 100644
index 0000000000..ad90ac6f52
--- /dev/null
+++ b/packages/frontend/assets/drop-and-fusion/sweets_monos/lollipop_color.svg
@@ -0,0 +1,112 @@
+<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M28.25 28.25L16.5 17" stroke="url(#paint0_linear_18_29825)" stroke-width="3.5" stroke-linecap="round" stroke-linejoin="round"/>
+<mask id="mask0_18_29825" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="14" y="15" width="16" height="15">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M15.236 15.7898C15.9044 15.0916 17.0121 15.0676 17.7103 15.736L29.4603 26.986C30.1584 27.6544 30.1824 28.7621 29.514 29.4603C28.8456 30.1584 27.7379 30.1824 27.0398 29.514L15.2898 18.264C14.5916 17.5956 14.5676 16.4879 15.236 15.7898Z" fill="#212121"/>
+</mask>
+<g mask="url(#mask0_18_29825)">
+<g filter="url(#filter0_f_18_29825)">
+<path d="M29 27.5L17.25 16.25" stroke="#FFE5C1" stroke-linecap="round" stroke-linejoin="round"/>
+</g>
+</g>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M15.236 15.7898C15.9044 15.0916 17.0121 15.0676 17.7103 15.736L29.4603 26.986C30.1584 27.6544 30.1824 28.7621 29.514 29.4603C28.8456 30.1584 27.7379 30.1824 27.0398 29.514L15.2898 18.264C14.5916 17.5956 14.5676 16.4879 15.236 15.7898Z" fill="url(#paint1_linear_18_29825)"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M3.38559 6.91832C4.28822 5.39149 5.58521 4.12544 7.13636 3.26038C13.3511 6.72017 12 12 12 12C9.95937 8.79099 8.01466 7.26385 3.38559 6.91832Z" fill="url(#paint2_linear_18_29825)"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M2.00043 11.9068C2.01725 10.0646 2.53218 8.34146 3.41704 6.86548C10.2965 6.96669 12 12 12 12C8.66694 10.2518 6.29 9.90635 2.00043 11.9068Z" fill="url(#paint3_linear_18_29825)"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M3.29277 16.9213C2.4698 15.4683 2 13.789 2 12C2 11.9479 2.0004 11.8959 2.00119 11.844C8.00872 8.49337 12 12 12 12C8.21727 12.0949 5.9829 13.1457 3.29277 16.9213Z" fill="url(#paint4_linear_18_29825)"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M6.92097 20.6159C5.39375 19.7137 4.12726 18.417 3.26172 16.866C6.78909 10.9586 12 12 12 12C8.7792 13.9043 7.41959 16.0271 6.92097 20.6159Z" fill="url(#paint5_linear_18_29825)"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M6.86548 20.583C6.96669 13.7035 12 12 12 12C10.173 15.2975 9.97217 17.7746 11.9074 21.9996C10.065 21.9829 8.34163 21.4679 6.86548 20.583Z" fill="url(#paint6_linear_18_29825)"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M11.844 21.9988C8.49337 15.9913 12 12 12 12C12.1732 15.8368 13.1795 18.105 16.9204 20.7077C15.4676 21.5304 13.7887 22 12 22C11.9479 22 11.8959 21.9996 11.844 21.9988Z" fill="url(#paint7_linear_18_29825)"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M16.8661 20.7383C10.9586 17.2109 12 12 12 12C14.0949 15.3202 15.9729 16.7474 20.6143 17.0819C19.7121 18.6078 18.4161 19.8733 16.8661 20.7383Z" fill="url(#paint8_linear_18_29825)"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M20.583 17.1345C13.7035 17.0333 12 12 12 12C15.3417 13.8027 17.8524 14.0929 21.9996 12.0944C21.9825 13.9361 21.4676 15.6589 20.583 17.1345Z" fill="url(#paint9_linear_18_29825)"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M21.9988 12.156C21.9996 12.1041 22 12.0521 22 12C22 10.2115 21.5305 8.53271 20.708 7.08008C18.0379 10.9644 15.7923 11.8814 12 12C12 12 15.9913 15.5066 21.9988 12.156Z" fill="url(#paint10_linear_18_29825)"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M17.0816 3.3855C18.6076 4.28766 19.8732 5.58378 20.7383 7.13389C17.2109 13.0414 12 12 12 12C15.1071 10.0716 16.7119 8.22757 17.0816 3.3855Z" fill="url(#paint11_linear_18_29825)"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M17.1345 3.41708C15.6593 2.53265 13.9371 2.0178 12.096 2.00049C14.2371 6.27017 13.7353 8.83597 12 12C12 12 17.0333 10.2965 17.1345 3.41708Z" fill="url(#paint12_linear_18_29825)"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M12.156 2.00119C12.1041 2.0004 12.0521 2 12 2C10.213 2 8.53548 2.46873 7.08368 3.28996C11.2283 5.87922 12.1157 8.21834 12 12C12 12 15.5066 8.00872 12.156 2.00119Z" fill="url(#paint13_linear_18_29825)"/>
+<mask id="mask1_18_29825" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="2" y="2" width="20" height="20">
+<circle cx="12" cy="12" r="10" fill="black"/>
+</mask>
+<g mask="url(#mask1_18_29825)">
+<g filter="url(#filter1_f_18_29825)">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22ZM2.125 12C2.125 6.54619 6.54619 2.125 12 2.125C17.4538 2.125 21.875 6.54619 21.875 12C21.875 17.4538 17.4538 21.875 12 21.875C6.54619 21.875 2.125 17.4538 2.125 12Z" fill="black" fill-opacity="0.32"/>
+</g>
+</g>
+<defs>
+<filter id="filter0_f_18_29825" x="15.75" y="14.75" width="14.75" height="14.25" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.5" result="effect1_foregroundBlur_18_29825"/>
+</filter>
+<filter id="filter1_f_18_29825" x="1.5" y="1.5" width="21" height="21" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.25" result="effect1_foregroundBlur_18_29825"/>
+</filter>
+<linearGradient id="paint0_linear_18_29825" x1="25.0221" y1="22.7615" x2="22.5" y2="25.5" gradientUnits="userSpaceOnUse">
+<stop stop-color="#FDDAB2"/>
+<stop offset="0.442708" stop-color="#EA9BB3"/>
+<stop offset="0.677083" stop-color="#E37DC3"/>
+<stop offset="1" stop-color="#C969AB"/>
+</linearGradient>
+<linearGradient id="paint1_linear_18_29825" x1="14.5" y1="15.5" x2="22.5" y2="23.5" gradientUnits="userSpaceOnUse">
+<stop stop-color="#B05C92"/>
+<stop offset="0.765625" stop-color="#B05C92" stop-opacity="0"/>
+</linearGradient>
+<linearGradient id="paint2_linear_18_29825" x1="5.5" y1="4.50003" x2="12" y2="12" gradientUnits="userSpaceOnUse">
+<stop stop-color="#5E9BEB"/>
+<stop offset="0.526042" stop-color="#6FA0F3"/>
+<stop offset="1" stop-color="#7EA4F4"/>
+</linearGradient>
+<linearGradient id="paint3_linear_18_29825" x1="12" y1="12" x2="2.5" y2="9" gradientUnits="userSpaceOnUse">
+<stop stop-color="#7DC3A2"/>
+<stop offset="0.515625" stop-color="#71C398"/>
+<stop offset="1" stop-color="#74D099"/>
+</linearGradient>
+<linearGradient id="paint4_linear_18_29825" x1="12" y1="12" x2="2" y2="14" gradientUnits="userSpaceOnUse">
+<stop stop-color="#EAC27C"/>
+<stop offset="0.489583" stop-color="#EBC16A"/>
+<stop offset="1" stop-color="#FFE885"/>
+</linearGradient>
+<linearGradient id="paint5_linear_18_29825" x1="12" y1="12" x2="4.5" y2="18.5" gradientUnits="userSpaceOnUse">
+<stop stop-color="#ED8876"/>
+<stop offset="0.442708" stop-color="#FE8765"/>
+<stop offset="1" stop-color="#FF916D"/>
+</linearGradient>
+<linearGradient id="paint6_linear_18_29825" x1="12" y1="12" x2="9" y2="21.5" gradientUnits="userSpaceOnUse">
+<stop stop-color="#BD4C97"/>
+<stop offset="0.515625" stop-color="#B93A90"/>
+<stop offset="1" stop-color="#B83A8B"/>
+</linearGradient>
+<linearGradient id="paint7_linear_18_29825" x1="12" y1="12" x2="13.6573" y2="22" gradientUnits="userSpaceOnUse">
+<stop stop-color="#B976DB"/>
+<stop offset="0.510417" stop-color="#AF64D6"/>
+<stop offset="1" stop-color="#9B4BC5"/>
+</linearGradient>
+<linearGradient id="paint8_linear_18_29825" x1="19" y1="19.5" x2="12" y2="12" gradientUnits="userSpaceOnUse">
+<stop stop-color="#5E92F8"/>
+<stop offset="0.5" stop-color="#6D95F1"/>
+<stop offset="1" stop-color="#7CA0F2"/>
+</linearGradient>
+<linearGradient id="paint9_linear_18_29825" x1="21.5" y1="15" x2="12" y2="12" gradientUnits="userSpaceOnUse">
+<stop stop-color="#90F7BA"/>
+<stop offset="0.526042" stop-color="#76C5A0"/>
+<stop offset="1" stop-color="#7EC1A4"/>
+</linearGradient>
+<linearGradient id="paint10_linear_18_29825" x1="21.5" y1="10" x2="12" y2="12" gradientUnits="userSpaceOnUse">
+<stop stop-color="#FFFF90"/>
+<stop offset="0.515625" stop-color="#FFD677"/>
+<stop offset="1" stop-color="#EDC47E"/>
+</linearGradient>
+<linearGradient id="paint11_linear_18_29825" x1="19.5" y1="5.49997" x2="12" y2="12" gradientUnits="userSpaceOnUse">
+<stop stop-color="#FFB281"/>
+<stop offset="0.5" stop-color="#FF9372"/>
+<stop offset="1" stop-color="#F18C79"/>
+</linearGradient>
+<linearGradient id="paint12_linear_18_29825" x1="15" y1="2.50003" x2="12" y2="12" gradientUnits="userSpaceOnUse">
+<stop stop-color="#FC70BD"/>
+<stop offset="1" stop-color="#C2509A"/>
+</linearGradient>
+<linearGradient id="paint13_linear_18_29825" x1="9.5" y1="2.5" x2="12" y2="12" gradientUnits="userSpaceOnUse">
+<stop stop-color="#DB8BFB"/>
+<stop offset="1" stop-color="#BC79DD"/>
+</linearGradient>
+</defs>
+</svg>
diff --git a/packages/frontend/assets/drop-and-fusion/sweets_monos/pancakes_color.svg b/packages/frontend/assets/drop-and-fusion/sweets_monos/pancakes_color.svg
new file mode 100644
index 0000000000..94b2ab69b5
--- /dev/null
+++ b/packages/frontend/assets/drop-and-fusion/sweets_monos/pancakes_color.svg
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="32px" height="32px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;">
+    <g transform="matrix(1,0,0,1,0,0.3089)">
+        <path d="M21.357,12.066L10.653,12.066C5.871,12.066 2,15.947 2,20.729C2,25.511 5.871,29.382 10.653,29.382L21.347,29.382C26.129,29.382 30,25.511 30,20.729C30.01,15.947 26.139,12.066 21.357,12.066Z" style="fill:url(#_Linear1);fill-rule:nonzero;"/>
+        <g>
+            <path d="M21.283,12.484L10.758,12.484C6.057,12.484 2.25,16.155 2.25,20.677C2.25,25.198 6.057,28.859 10.758,28.859L21.273,28.859C25.975,28.859 29.781,25.198 29.781,20.677C29.791,16.155 25.985,12.484 21.283,12.484Z" style="fill:url(#_Linear2);fill-rule:nonzero;"/>
+        </g>
+        <g>
+            <path d="M21.417,14.823L10.593,14.823L4.011,16.714L4.011,19.275C4.011,22.916 6.962,25.857 10.593,25.857L21.407,25.857C25.048,25.857 27.989,22.906 27.989,19.275L27.989,16.714L21.417,14.823Z" style="fill:url(#_Linear3);fill-rule:nonzero;"/>
+        </g>
+        <path d="M21.417,14.287L10.593,14.287L4.011,16.177L4.011,18.738C4.011,22.38 6.962,25.321 10.593,25.321L21.407,25.321C25.048,25.321 27.989,22.37 27.989,18.738L27.989,16.177L21.417,14.287Z" style="fill:url(#_Linear4);fill-rule:nonzero;"/>
+        <path d="M21.417,9.195L10.593,9.195C6.952,9.195 4.011,12.146 4.011,15.777C4.011,19.419 6.962,22.36 10.593,22.36L21.407,22.36C25.048,22.36 27.989,19.409 27.989,15.777C27.999,12.136 25.048,9.195 21.417,9.195Z" style="fill:url(#_Linear5);fill-rule:nonzero;"/>
+        <path d="M21.417,9.195L10.593,9.195C6.952,9.195 4.011,12.146 4.011,15.777C4.011,19.419 6.962,22.36 10.593,22.36L21.407,22.36C25.048,22.36 27.989,19.409 27.989,15.777C27.999,12.136 25.048,9.195 21.417,9.195Z" style="fill:url(#_Radial6);fill-rule:nonzero;"/>
+        <path d="M21.417,9.195L10.593,9.195C6.952,9.195 4.011,12.146 4.011,15.777C4.011,19.419 6.962,22.36 10.593,22.36L21.407,22.36C25.048,22.36 27.989,19.409 27.989,15.777C27.999,12.136 25.048,9.195 21.417,9.195Z" style="fill:url(#_Radial7);fill-rule:nonzero;"/>
+        <path d="M21.417,8.515L10.593,8.515L4.011,10.416L4.011,12.977C4.011,16.618 6.962,19.559 10.593,19.559L21.407,19.559C25.048,19.559 27.989,16.608 27.989,12.977L27.989,10.416L21.417,8.515Z" style="fill:url(#_Linear8);fill-rule:nonzero;"/>
+        <path d="M21.416,3.422L10.592,3.422C6.951,3.422 4.01,6.374 4.01,10.005C4.01,13.646 6.961,16.587 10.592,16.587L21.406,16.587C25.048,16.587 27.989,13.636 27.989,10.005C27.999,6.374 25.048,3.422 21.416,3.422Z" style="fill:url(#_Linear9);fill-rule:nonzero;"/>
+        <g>
+            <path d="M18.375,3.579L10.687,3.579C7.109,3.579 4.219,6.53 4.219,10.161C4.219,13.802 7.119,16.743 10.687,16.743L18.375,16.743L18.375,3.579Z" style="fill:url(#_Linear10);fill-rule:nonzero;"/>
+            <path d="M18.375,3.579L10.687,3.579C7.109,3.579 4.219,6.53 4.219,10.161C4.219,13.802 7.119,16.743 10.687,16.743L18.375,16.743L18.375,3.579Z" style="fill:url(#_Radial11);fill-rule:nonzero;"/>
+            <path d="M18.375,3.579L10.687,3.579C7.109,3.579 4.219,6.53 4.219,10.161C4.219,13.802 7.119,16.743 10.687,16.743L18.375,16.743L18.375,3.579Z" style="fill:url(#_Radial12);fill-rule:nonzero;"/>
+        </g>
+        <path d="M19.436,6.204L12.564,6.204C10.523,6.204 8.872,7.855 8.872,9.895C8.872,11.566 9.983,12.986 11.513,13.437C11.974,13.577 12.284,14.007 12.284,14.487L12.284,20.299C12.284,21.009 12.834,21.61 13.544,21.63C14.264,21.64 14.855,21.059 14.855,20.349L14.855,14.677C14.855,14.077 15.335,13.597 15.935,13.597C16.535,13.597 17.015,14.077 17.015,14.677L17.015,16.328C17.015,17.038 17.566,17.638 18.276,17.658C18.996,17.668 19.586,17.088 19.586,16.378L19.586,14.537C19.586,14.047 19.906,13.607 20.387,13.477C21.977,13.066 23.148,11.616 23.148,9.905C23.138,7.855 21.477,6.204 19.436,6.204Z" style="fill:url(#_Radial13);fill-rule:nonzero;"/>
+        <path d="M19.436,6.204L12.564,6.204C10.523,6.204 8.872,7.855 8.872,9.895C8.872,11.566 9.983,12.986 11.513,13.437C11.974,13.577 12.284,14.007 12.284,14.487L12.284,20.299C12.284,21.009 12.834,21.61 13.544,21.63C14.264,21.64 14.855,21.059 14.855,20.349L14.855,14.677C14.855,14.077 15.335,13.597 15.935,13.597C16.535,13.597 17.015,14.077 17.015,14.677L17.015,16.328C17.015,17.038 17.566,17.638 18.276,17.658C18.996,17.668 19.586,17.088 19.586,16.378L19.586,14.537C19.586,14.047 19.906,13.607 20.387,13.477C21.977,13.066 23.148,11.616 23.148,9.905C23.138,7.855 21.477,6.204 19.436,6.204Z" style="fill:rgb(136,71,52);fill-rule:nonzero;"/>
+        <path d="M19.436,6.204L12.564,6.204C10.523,6.204 8.872,7.855 8.872,9.895C8.872,11.566 9.983,12.986 11.513,13.437C11.974,13.577 12.284,14.007 12.284,14.487L12.284,20.299C12.284,21.009 12.834,21.61 13.544,21.63C14.264,21.64 14.855,21.059 14.855,20.349L14.855,14.677C14.855,14.077 15.335,13.597 15.935,13.597C16.535,13.597 17.015,14.077 17.015,14.677L17.015,16.328C17.015,17.038 17.566,17.638 18.276,17.658C18.996,17.668 19.586,17.088 19.586,16.378L19.586,14.537C19.586,14.047 19.906,13.607 20.387,13.477C21.977,13.066 23.148,11.616 23.148,9.905C23.138,7.855 21.477,6.204 19.436,6.204Z" style="fill:url(#_Linear14);fill-rule:nonzero;"/>
+        <g>
+            <path d="M8.953,9.805C8.953,11.4 10.107,12.552 11.606,12.982C12.057,13.115 12.361,13.526 12.361,13.985L12.361,19.964C12.361,20.642 12.9,21.2 13.595,21.219C14.3,21.228 14.75,20.85 14.75,20.172L14.75,14.079C14.75,13.505 15.349,13.047 15.937,13.047C16.524,13.047 17.156,13.505 17.156,14.079L17.156,16.219C17.156,16.897 17.533,17.497 18.229,17.516C18.934,17.525 19.512,16.897 19.512,16.219L19.512,14.059C19.512,13.591 19.826,13.171 20.296,13.047C21.854,12.655 23.094,11.439 23.094,9.805C23.094,3.859 8.953,3.947 8.953,9.805Z" style="fill:url(#_Radial15);fill-rule:nonzero;"/>
+            <path d="M8.953,9.805C8.953,11.4 10.107,12.552 11.606,12.982C12.057,13.115 12.361,13.526 12.361,13.985L12.361,19.964C12.361,20.642 12.9,21.2 13.595,21.219C14.3,21.228 14.75,20.85 14.75,20.172L14.75,14.079C14.75,13.505 15.349,13.047 15.937,13.047C16.524,13.047 17.156,13.505 17.156,14.079L17.156,16.219C17.156,16.897 17.533,17.497 18.229,17.516C18.934,17.525 19.512,16.897 19.512,16.219L19.512,14.059C19.512,13.591 19.826,13.171 20.296,13.047C21.854,12.655 23.094,11.439 23.094,9.805C23.094,3.859 8.953,3.947 8.953,9.805Z" style="fill:url(#_Radial16);fill-rule:nonzero;"/>
+            <path d="M8.953,9.805C8.953,11.4 10.107,12.552 11.606,12.982C12.057,13.115 12.361,13.526 12.361,13.985L12.361,19.964C12.361,20.642 12.9,21.2 13.595,21.219C14.3,21.228 14.75,20.85 14.75,20.172L14.75,14.079C14.75,13.505 15.349,13.047 15.937,13.047C16.524,13.047 17.156,13.505 17.156,14.079L17.156,16.219C17.156,16.897 17.533,17.497 18.229,17.516C18.934,17.525 19.512,16.897 19.512,16.219L19.512,14.059C19.512,13.591 19.826,13.171 20.296,13.047C21.854,12.655 23.094,11.439 23.094,9.805C23.094,3.859 8.953,3.947 8.953,9.805Z" style="fill:url(#_Radial17);fill-rule:nonzero;"/>
+            <path d="M8.953,9.805C8.953,11.4 10.107,12.552 11.606,12.982C12.057,13.115 12.361,13.526 12.361,13.985L12.361,19.964C12.361,20.642 12.9,21.2 13.595,21.219C14.3,21.228 14.75,20.85 14.75,20.172L14.75,14.079C14.75,13.505 15.349,13.047 15.937,13.047C16.524,13.047 17.156,13.505 17.156,14.079L17.156,16.219C17.156,16.897 17.533,17.497 18.229,17.516C18.934,17.525 19.512,16.897 19.512,16.219L19.512,14.059C19.512,13.591 19.826,13.171 20.296,13.047C21.854,12.655 23.094,11.439 23.094,9.805C23.094,3.859 8.953,3.947 8.953,9.805Z" style="fill:url(#_Radial18);fill-rule:nonzero;"/>
+            <path d="M8.953,9.805C8.953,11.4 10.107,12.552 11.606,12.982C12.057,13.115 12.361,13.526 12.361,13.985L12.361,19.964C12.361,20.642 12.9,21.2 13.595,21.219C14.3,21.228 14.75,20.85 14.75,20.172L14.75,14.079C14.75,13.505 15.349,13.047 15.937,13.047C16.524,13.047 17.156,13.505 17.156,14.079L17.156,16.219C17.156,16.897 17.533,17.497 18.229,17.516C18.934,17.525 19.512,16.897 19.512,16.219L19.512,14.059C19.512,13.591 19.826,13.171 20.296,13.047C21.854,12.655 23.094,11.439 23.094,9.805C23.094,3.859 8.953,3.947 8.953,9.805Z" style="fill:url(#_Radial19);fill-rule:nonzero;"/>
+            <path d="M8.953,9.805C8.953,11.4 10.107,12.552 11.606,12.982C12.057,13.115 12.361,13.526 12.361,13.985L12.361,19.964C12.361,20.642 12.9,21.2 13.595,21.219C14.3,21.228 14.75,20.85 14.75,20.172L14.75,14.079C14.75,13.505 15.349,13.047 15.937,13.047C16.524,13.047 17.156,13.505 17.156,14.079L17.156,16.219C17.156,16.897 17.533,17.497 18.229,17.516C18.934,17.525 19.512,16.897 19.512,16.219L19.512,14.059C19.512,13.591 19.826,13.171 20.296,13.047C21.854,12.655 23.094,11.439 23.094,9.805C23.094,3.859 8.953,3.947 8.953,9.805Z" style="fill:url(#_Radial20);fill-rule:nonzero;"/>
+            <path d="M8.953,9.805C8.953,11.4 10.107,12.552 11.606,12.982C12.057,13.115 12.361,13.526 12.361,13.985L12.361,19.964C12.361,20.642 12.9,21.2 13.595,21.219C14.3,21.228 14.75,20.85 14.75,20.172L14.75,14.079C14.75,13.505 15.349,13.047 15.937,13.047C16.524,13.047 17.156,13.505 17.156,14.079L17.156,16.219C17.156,16.897 17.533,17.497 18.229,17.516C18.934,17.525 19.512,16.897 19.512,16.219L19.512,14.059C19.512,13.591 19.826,13.171 20.296,13.047C21.854,12.655 23.094,11.439 23.094,9.805C23.094,3.859 8.953,3.947 8.953,9.805Z" style="fill:url(#_Radial21);fill-rule:nonzero;"/>
+        </g>
+        <g>
+            <path d="M11.479,7.266L11.479,7.641C11.479,8.062 11.671,8.484 12.055,8.721L15.092,10.511C15.659,10.871 16.361,10.871 16.918,10.511L19.955,8.721C20.339,8.474 20.531,8.052 20.531,7.641L20.531,7.266L11.479,7.266Z" style="fill:rgb(115,57,25);fill-rule:nonzero;"/>
+        </g>
+        <path d="M17.406,5.113L16.955,4.843C16.365,4.492 15.635,4.492 15.055,4.843L14.605,5.113L11.293,5.113L11.293,7.213C11.293,7.624 11.493,8.034 11.894,8.264L15.055,10.134C15.645,10.485 16.375,10.485 16.955,10.134L20.116,8.264C20.517,8.024 20.717,7.614 20.717,7.213L20.717,5.113L17.406,5.113Z" style="fill:url(#_Linear22);fill-rule:nonzero;"/>
+        <path d="M17.406,5.113L16.955,4.843C16.365,4.492 15.635,4.492 15.055,4.843L14.605,5.113L11.293,5.113L11.293,7.213C11.293,7.624 11.493,8.034 11.894,8.264L15.055,10.134C15.645,10.485 16.375,10.485 16.955,10.134L20.116,8.264C20.517,8.024 20.717,7.614 20.717,7.213L20.717,5.113L17.406,5.113Z" style="fill:url(#_Radial23);fill-rule:nonzero;"/>
+        <path d="M15.055,2.263L11.894,4.133C11.093,4.603 11.093,5.764 11.894,6.244L15.055,8.115C15.645,8.465 16.375,8.465 16.955,8.115L20.116,6.244C20.917,5.774 20.917,4.613 20.116,4.133L16.955,2.263C16.365,1.912 15.645,1.912 15.055,2.263Z" style="fill:url(#_Linear24);fill-rule:nonzero;"/>
+        <circle cx="22.492" cy="26.922" r="1.492" style="fill:url(#_Radial25);"/>
+        <circle cx="24.52" cy="25.857" r="0.536" style="fill:url(#_Radial26);"/>
+        <g>
+            <path d="M19.516,13.375C20.234,12.75 22.984,11.922 22.75,9.375C22.125,11.156 21.731,11.677 20.125,12.031C18.922,12.297 18.844,13.234 18.844,13.719L18.953,16.766L19.281,16.766C19.369,16.237 19.18,13.667 19.516,13.375Z" style="fill:url(#_Radial27);fill-rule:nonzero;"/>
+        </g>
+        <g>
+            <path d="M13.828,17.047C13.828,17.185 13.94,17.297 14.078,17.297C14.216,17.297 14.328,17.185 14.328,17.047L13.828,17.047ZM17.283,14.283C17.283,14.421 17.395,14.533 17.533,14.533C17.671,14.533 17.783,14.421 17.783,14.283L17.283,14.283ZM14.328,17.047L14.328,14.283L13.828,14.283L13.828,17.047L14.328,17.047ZM14.328,14.283C14.328,13.975 14.487,13.586 14.769,13.271C15.048,12.958 15.418,12.75 15.813,12.75L15.813,12.25C15.238,12.25 14.741,12.551 14.396,12.938C14.052,13.322 13.828,13.825 13.828,14.283L14.328,14.283ZM15.813,12.75C16.201,12.75 16.568,12.968 16.847,13.291C17.128,13.617 17.283,14.007 17.283,14.283L17.783,14.283C17.783,13.855 17.561,13.354 17.225,12.964C16.886,12.572 16.393,12.25 15.813,12.25L15.813,12.75Z" style="fill:url(#_Linear28);fill-rule:nonzero;"/>
+        </g>
+        <g>
+            <path d="M21.359,16.438C23.75,16.438 27.094,14.859 27.75,11.453" style="fill:none;fill-rule:nonzero;stroke:url(#_Radial29);stroke-width:0.3px;"/>
+        </g>
+        <g>
+            <path d="M23.782,28.281C26,27.688 28.781,25.406 29.625,21.469" style="fill:none;fill-rule:nonzero;stroke:url(#_Radial30);stroke-width:0.3px;"/>
+        </g>
+        <g>
+            <path d="M20.695,5.273C20.695,5.555 20.506,5.938 20.156,6.188C19.806,6.438 17.74,7.563 16.594,8.219C16.345,8.361 15.773,8.453 15.258,8.094" style="fill:none;fill-rule:nonzero;stroke:url(#_Radial31);stroke-width:0.3px;"/>
+        </g>
+    </g>
+    <defs>
+        <linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(28,0,0,28,2,21.125)"><stop offset="0" style="stop-color:rgb(187,183,188);stop-opacity:1"/><stop offset="0.1" style="stop-color:rgb(173,149,194);stop-opacity:1"/><stop offset="0.23" style="stop-color:rgb(195,160,226);stop-opacity:1"/><stop offset="0.8" style="stop-color:rgb(201,166,231);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(216,208,223);stop-opacity:1"/></linearGradient>
+        <linearGradient id="_Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(27.3125,0,0,27.3125,2.8125,20.125)"><stop offset="0" style="stop-color:rgb(190,180,182);stop-opacity:1"/><stop offset="0.16" style="stop-color:rgb(219,206,213);stop-opacity:1"/><stop offset="0.49" style="stop-color:rgb(223,205,210);stop-opacity:1"/><stop offset="0.82" style="stop-color:rgb(223,209,214);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(227,216,205);stop-opacity:1"/></linearGradient>
+        <linearGradient id="_Linear3" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(7.18837e-16,11.7395,-11.7395,7.18837e-16,16,14.823)"><stop offset="0" style="stop-color:rgb(180,157,159);stop-opacity:1"/><stop offset="0.89" style="stop-color:rgb(180,157,159);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(180,157,159);stop-opacity:0"/></linearGradient>
+        <linearGradient id="_Linear4" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(23.9786,0,0,23.9786,4.01074,19.1875)"><stop offset="0" style="stop-color:rgb(184,140,67);stop-opacity:1"/><stop offset="0.16" style="stop-color:rgb(198,144,57);stop-opacity:1"/><stop offset="0.33" style="stop-color:rgb(217,159,63);stop-opacity:1"/><stop offset="0.76" style="stop-color:rgb(217,167,52);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(236,211,76);stop-opacity:1"/></linearGradient>
+        <linearGradient id="_Linear5" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(23.4268,0,0,23.4268,4.5625,17.875)"><stop offset="0" style="stop-color:rgb(169,110,68);stop-opacity:1"/><stop offset="0.15" style="stop-color:rgb(184,120,76);stop-opacity:1"/><stop offset="0.34" style="stop-color:rgb(186,119,75);stop-opacity:1"/><stop offset="0.59" style="stop-color:rgb(214,140,86);stop-opacity:1"/><stop offset="0.82" style="stop-color:rgb(227,152,93);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(234,173,116);stop-opacity:1"/></linearGradient>
+        <radialGradient id="_Radial6" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-2.04736e-15,5.34759,-13.25,-5.07285e-15,16,15.7774)"><stop offset="0" style="stop-color:rgb(153,81,44);stop-opacity:1"/><stop offset="0.66" style="stop-color:rgb(153,81,44);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(166,87,47);stop-opacity:0"/></radialGradient>
+        <radialGradient id="_Radial7" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(2.875,0,0,2.85976,12.25,19.5)"><stop offset="0" style="stop-color:rgb(147,74,40);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(147,74,40);stop-opacity:0"/></radialGradient>
+        <linearGradient id="_Linear8" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(23.4268,0,0,23.4268,4.5625,13.625)"><stop offset="0" style="stop-color:rgb(178,140,76);stop-opacity:1"/><stop offset="0.13" style="stop-color:rgb(185,127,51);stop-opacity:1"/><stop offset="0.28" style="stop-color:rgb(189,118,40);stop-opacity:1"/><stop offset="0.49" style="stop-color:rgb(195,124,40);stop-opacity:1"/><stop offset="0.59" style="stop-color:rgb(208,135,38);stop-opacity:1"/><stop offset="0.7" style="stop-color:rgb(214,146,45);stop-opacity:1"/><stop offset="0.82" style="stop-color:rgb(222,169,51);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(229,206,83);stop-opacity:1"/></linearGradient>
+        <linearGradient id="_Linear9" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(23.3636,0,0,23.3636,4.625,8.8125)"><stop offset="0" style="stop-color:rgb(204,162,117);stop-opacity:1"/><stop offset="0.14" style="stop-color:rgb(214,168,122);stop-opacity:1"/><stop offset="0.72" style="stop-color:rgb(225,177,124);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(230,178,125);stop-opacity:1"/></linearGradient>
+        <linearGradient id="_Linear10" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(19.3405,0,0,19.3405,7.22204,7.96546)"><stop offset="0" style="stop-color:rgb(212,167,120);stop-opacity:1"/><stop offset="0.06" style="stop-color:rgb(212,167,120);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(231,178,125);stop-opacity:1"/></linearGradient>
+        <radialGradient id="_Radial11" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(4.5,4.3125,-3.88579,4.05475,9.875,9.8125)"><stop offset="0" style="stop-color:rgb(179,130,88);stop-opacity:1"/><stop offset="0.77" style="stop-color:rgb(187,143,101);stop-opacity:0"/><stop offset="1" style="stop-color:rgb(187,143,101);stop-opacity:0"/></radialGradient>
+        <radialGradient id="_Radial12" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(2.375,0,0,4.25084,12.8125,15.4375)"><stop offset="0" style="stop-color:rgb(175,119,78);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(175,119,78);stop-opacity:0"/></radialGradient>
+        <radialGradient id="_Radial13" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-12.4374,5.12507,-4.74283,-11.5098,20.5,10.0625)"><stop offset="0" style="stop-color:rgb(157,98,60);stop-opacity:1"/><stop offset="0.03" style="stop-color:rgb(157,98,60);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(130,82,62);stop-opacity:1"/></radialGradient>
+        <linearGradient id="_Linear14" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(13.5225,0,0,13.5225,9.625,11.875)"><stop offset="0" style="stop-color:rgb(128,75,51);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(159,94,62);stop-opacity:1"/></linearGradient>
+        <radialGradient id="_Radial15" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-12.1805,4.89355,-4.44716,-11.0694,20.4072,9.95537)"><stop offset="0" style="stop-color:rgb(157,98,60);stop-opacity:1"/><stop offset="0.03" style="stop-color:rgb(157,98,60);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(130,82,62);stop-opacity:1"/></radialGradient>
+        <radialGradient id="_Radial16" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(3.45827,1.87983,-1.00439,1.84774,13.6437,9.00053)"><stop offset="0" style="stop-color:rgb(118,67,38);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(115,62,33);stop-opacity:0"/></radialGradient>
+        <radialGradient id="_Radial17" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-3.62501,4.18749,-4.06664,-3.5204,10.75,6.75)"><stop offset="0" style="stop-color:rgb(114,77,61);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(114,76,59);stop-opacity:0"/></radialGradient>
+        <radialGradient id="_Radial18" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-2.37499,-0.625018,0.413551,-1.57144,18.6563,17.6875)"><stop offset="0" style="stop-color:rgb(138,70,84);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(140,71,86);stop-opacity:0"/></radialGradient>
+        <radialGradient id="_Radial19" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1.29214e-15,-3.375,1.27037,-4.8637e-16,13.1562,19.875)"><stop offset="0" style="stop-color:rgb(136,75,79);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(134,68,83);stop-opacity:0"/></radialGradient>
+        <radialGradient id="_Radial20" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-0.1875,1.09374,-0.673499,-0.115457,13.9063,16.0625)"><stop offset="0" style="stop-color:rgb(163,118,101);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(165,120,103);stop-opacity:0"/></radialGradient>
+        <radialGradient id="_Radial21" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-0.281246,0.937501,-0.651233,-0.195367,18.8906,15.7344)"><stop offset="0" style="stop-color:rgb(166,121,104);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(168,123,104);stop-opacity:0"/></radialGradient>
+        <linearGradient id="_Linear22" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(9.2792,0,0,9.2792,11.4375,7.48849)"><stop offset="0" style="stop-color:rgb(199,162,43);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(232,193,46);stop-opacity:1"/></linearGradient>
+        <radialGradient id="_Radial23" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1.14857e-15,3,-3.44251,-1.31799e-15,16.005,10.9375)"><stop offset="0" style="stop-color:rgb(219,165,41);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(217,163,43);stop-opacity:0"/></radialGradient>
+        <linearGradient id="_Linear24" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(3.1641,5.02344,-5.02344,3.1641,14.3984,2.60156)"><stop offset="0" style="stop-color:rgb(207,186,58);stop-opacity:1"/><stop offset="0.01" style="stop-color:rgb(207,186,58);stop-opacity:1"/><stop offset="0.09" style="stop-color:rgb(213,193,55);stop-opacity:1"/><stop offset="0.93" style="stop-color:rgb(231,206,55);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(231,206,55);stop-opacity:1"/></linearGradient>
+        <radialGradient id="_Radial25" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-2.98437,2.28517e-15,-2.16435e-15,-2.82658,23.5774,26.9224)"><stop offset="0" style="stop-color:rgb(160,103,64);stop-opacity:1"/><stop offset="0.71" style="stop-color:rgb(142,81,46);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(142,81,46);stop-opacity:1"/></radialGradient>
+        <radialGradient id="_Radial26" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1.07227,8.21052e-16,-7.77636e-16,-1.01557,24.9104,25.8569)"><stop offset="0" style="stop-color:rgb(160,103,64);stop-opacity:1"/><stop offset="0.71" style="stop-color:rgb(142,81,46);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(142,81,46);stop-opacity:1"/></radialGradient>
+        <radialGradient id="_Radial27" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-2.98808e-15,7.80469,-4.14003,-1.58504e-15,20.8039,13.0703)"><stop offset="0" style="stop-color:rgb(173,124,104);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(172,123,101);stop-opacity:0"/></radialGradient>
+        <linearGradient id="_Linear28" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(3.7188,0,0,3.7188,14.0312,13.5027)"><stop offset="0" style="stop-color:rgb(162,119,100);stop-opacity:1"/><stop offset="0.46" style="stop-color:rgb(156,111,94);stop-opacity:1"/><stop offset="0.62" style="stop-color:rgb(111,63,46);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(111,63,46);stop-opacity:1"/></linearGradient>
+        <radialGradient id="_Radial29" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(2.53125,-2.40625,1.27457,1.34078,25.7188,14.5)"><stop offset="0" style="stop-color:rgb(239,185,146);stop-opacity:1"/><stop offset="0.45" style="stop-color:rgb(241,189,146);stop-opacity:1"/><stop offset="0.99" style="stop-color:rgb(240,188,143);stop-opacity:0"/><stop offset="1" style="stop-color:rgb(240,188,143);stop-opacity:0"/></radialGradient>
+        <radialGradient id="_Radial30" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(3.50413,-4.40625,2.33395,1.8561,26.9687,25.875)"><stop offset="0" style="stop-color:rgb(235,225,240);stop-opacity:1"/><stop offset="0.45" style="stop-color:rgb(235,226,240);stop-opacity:1"/><stop offset="0.99" style="stop-color:rgb(232,223,236);stop-opacity:0"/><stop offset="1" style="stop-color:rgb(232,223,236);stop-opacity:0"/></radialGradient>
+        <radialGradient id="_Radial31" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(2.88719,-1.6592,0.823999,1.43385,18.1875,7.3125)"><stop offset="0" style="stop-color:rgb(240,209,81);stop-opacity:1"/><stop offset="0.45" style="stop-color:rgb(242,211,89);stop-opacity:1"/><stop offset="0.99" style="stop-color:rgb(243,211,87);stop-opacity:0"/><stop offset="1" style="stop-color:rgb(243,211,87);stop-opacity:0"/></radialGradient>
+    </defs>
+</svg>
diff --git a/packages/frontend/assets/drop-and-fusion/sweets_monos/shaved_ice_color.svg b/packages/frontend/assets/drop-and-fusion/sweets_monos/shaved_ice_color.svg
new file mode 100644
index 0000000000..64dfef8e05
--- /dev/null
+++ b/packages/frontend/assets/drop-and-fusion/sweets_monos/shaved_ice_color.svg
@@ -0,0 +1,161 @@
+<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M16 22C21.5228 22 26 17.5228 26 12C26 6.47715 21.5228 2 16 2C10.4772 2 6 6.47715 6 12C6 17.5228 10.4772 22 16 22Z" fill="#CDA3AF"/>
+<path d="M16 22C21.5228 22 26 17.5228 26 12C26 6.47715 21.5228 2 16 2C10.4772 2 6 6.47715 6 12C6 17.5228 10.4772 22 16 22Z" fill="url(#paint0_radial_18_28637)"/>
+<g filter="url(#filter0_f_18_28637)">
+<path d="M8.80999 13.595C9.03999 12.855 9.72999 12.375 10.49 12.375C11.25 12.375 12.3325 12.865 12.5625 13.585C13.0725 15.205 14.19 15.7188 15.98 15.7188C17.77 15.7188 18.9275 15.205 19.4375 13.595C19.6675 12.865 20.71 12.385 21.47 12.385H21.51C22.27 12.385 23.2387 12.4987 23.4688 13.2188C23.8088 14.2888 24.0838 14.6337 25.0938 15.0938C23.5238 2.90129 7.99999 6.46882 8.80999 13.595Z" fill="#E17CA3"/>
+<path d="M8.80999 13.595C9.03999 12.855 9.72999 12.375 10.49 12.375C11.25 12.375 12.3325 12.865 12.5625 13.585C13.0725 15.205 14.19 15.7188 15.98 15.7188C17.77 15.7188 18.9275 15.205 19.4375 13.595C19.6675 12.865 20.71 12.385 21.47 12.385H21.51C22.27 12.385 23.2387 12.4987 23.4688 13.2188C23.8088 14.2888 24.0838 14.6337 25.0938 15.0938C23.5238 2.90129 7.99999 6.46882 8.80999 13.595Z" fill="url(#paint1_radial_18_28637)"/>
+<path d="M8.80999 13.595C9.03999 12.855 9.72999 12.375 10.49 12.375C11.25 12.375 12.3325 12.865 12.5625 13.585C13.0725 15.205 14.19 15.7188 15.98 15.7188C17.77 15.7188 18.9275 15.205 19.4375 13.595C19.6675 12.865 20.71 12.385 21.47 12.385H21.51C22.27 12.385 23.2387 12.4987 23.4688 13.2188C23.8088 14.2888 24.0838 14.6337 25.0938 15.0938C23.5238 2.90129 7.99999 6.46882 8.80999 13.595Z" fill="url(#paint2_linear_18_28637)"/>
+<path d="M8.80999 13.595C9.03999 12.855 9.72999 12.375 10.49 12.375C11.25 12.375 12.3325 12.865 12.5625 13.585C13.0725 15.205 14.19 15.7188 15.98 15.7188C17.77 15.7188 18.9275 15.205 19.4375 13.595C19.6675 12.865 20.71 12.385 21.47 12.385H21.51C22.27 12.385 23.2387 12.4987 23.4688 13.2188C23.8088 14.2888 24.0838 14.6337 25.0938 15.0938C23.5238 2.90129 7.99999 6.46882 8.80999 13.595Z" fill="url(#paint3_radial_18_28637)"/>
+<path d="M8.80999 13.595C9.03999 12.855 9.72999 12.375 10.49 12.375C11.25 12.375 12.3325 12.865 12.5625 13.585C13.0725 15.205 14.19 15.7188 15.98 15.7188C17.77 15.7188 18.9275 15.205 19.4375 13.595C19.6675 12.865 20.71 12.385 21.47 12.385H21.51C22.27 12.385 23.2387 12.4987 23.4688 13.2188C23.8088 14.2888 24.0838 14.6337 25.0938 15.0938C23.5238 2.90129 7.99999 6.46882 8.80999 13.595Z" fill="url(#paint4_radial_18_28637)"/>
+<path d="M8.80999 13.595C9.03999 12.855 9.72999 12.375 10.49 12.375C11.25 12.375 12.3325 12.865 12.5625 13.585C13.0725 15.205 14.19 15.7188 15.98 15.7188C17.77 15.7188 18.9275 15.205 19.4375 13.595C19.6675 12.865 20.71 12.385 21.47 12.385H21.51C22.27 12.385 23.2387 12.4987 23.4688 13.2188C23.8088 14.2888 24.0838 14.6337 25.0938 15.0938C23.5238 2.90129 7.99999 6.46882 8.80999 13.595Z" fill="url(#paint5_linear_18_28637)"/>
+</g>
+<path d="M16 2C10.48 2 6 6.48 6 12C6 13.28 6.24 14.5 6.68 15.63C7.69 15.16 8.47 14.29 8.81 13.22C9.04 12.48 9.73 12 10.49 12C11.25 12 11.94 12.49 12.17 13.21C12.68 14.83 14.19 16 15.98 16C17.77 16 19.28 14.83 19.79 13.22C20.02 12.49 20.71 12.01 21.47 12.01H21.51C22.27 12.01 22.96 12.5 23.19 13.22C23.53 14.29 24.31 15.17 25.32 15.63C25.76 14.5 26 13.28 26 12C25.99 6.48 21.52 2 16 2Z" fill="#E17CA3"/>
+<path d="M16 2C10.48 2 6 6.48 6 12C6 13.28 6.24 14.5 6.68 15.63C7.69 15.16 8.47 14.29 8.81 13.22C9.04 12.48 9.73 12 10.49 12C11.25 12 11.94 12.49 12.17 13.21C12.68 14.83 14.19 16 15.98 16C17.77 16 19.28 14.83 19.79 13.22C20.02 12.49 20.71 12.01 21.47 12.01H21.51C22.27 12.01 22.96 12.5 23.19 13.22C23.53 14.29 24.31 15.17 25.32 15.63C25.76 14.5 26 13.28 26 12C25.99 6.48 21.52 2 16 2Z" fill="url(#paint6_radial_18_28637)"/>
+<path d="M16 2C10.48 2 6 6.48 6 12C6 13.28 6.24 14.5 6.68 15.63C7.69 15.16 8.47 14.29 8.81 13.22C9.04 12.48 9.73 12 10.49 12C11.25 12 11.94 12.49 12.17 13.21C12.68 14.83 14.19 16 15.98 16C17.77 16 19.28 14.83 19.79 13.22C20.02 12.49 20.71 12.01 21.47 12.01H21.51C22.27 12.01 22.96 12.5 23.19 13.22C23.53 14.29 24.31 15.17 25.32 15.63C25.76 14.5 26 13.28 26 12C25.99 6.48 21.52 2 16 2Z" fill="url(#paint7_linear_18_28637)"/>
+<path d="M16 2C10.48 2 6 6.48 6 12C6 13.28 6.24 14.5 6.68 15.63C7.69 15.16 8.47 14.29 8.81 13.22C9.04 12.48 9.73 12 10.49 12C11.25 12 11.94 12.49 12.17 13.21C12.68 14.83 14.19 16 15.98 16C17.77 16 19.28 14.83 19.79 13.22C20.02 12.49 20.71 12.01 21.47 12.01H21.51C22.27 12.01 22.96 12.5 23.19 13.22C23.53 14.29 24.31 15.17 25.32 15.63C25.76 14.5 26 13.28 26 12C25.99 6.48 21.52 2 16 2Z" fill="url(#paint8_radial_18_28637)"/>
+<path d="M16 2C10.48 2 6 6.48 6 12C6 13.28 6.24 14.5 6.68 15.63C7.69 15.16 8.47 14.29 8.81 13.22C9.04 12.48 9.73 12 10.49 12C11.25 12 11.94 12.49 12.17 13.21C12.68 14.83 14.19 16 15.98 16C17.77 16 19.28 14.83 19.79 13.22C20.02 12.49 20.71 12.01 21.47 12.01H21.51C22.27 12.01 22.96 12.5 23.19 13.22C23.53 14.29 24.31 15.17 25.32 15.63C25.76 14.5 26 13.28 26 12C25.99 6.48 21.52 2 16 2Z" fill="url(#paint9_radial_18_28637)"/>
+<path d="M16 2C10.48 2 6 6.48 6 12C6 13.28 6.24 14.5 6.68 15.63C7.69 15.16 8.47 14.29 8.81 13.22C9.04 12.48 9.73 12 10.49 12C11.25 12 11.94 12.49 12.17 13.21C12.68 14.83 14.19 16 15.98 16C17.77 16 19.28 14.83 19.79 13.22C20.02 12.49 20.71 12.01 21.47 12.01H21.51C22.27 12.01 22.96 12.5 23.19 13.22C23.53 14.29 24.31 15.17 25.32 15.63C25.76 14.5 26 13.28 26 12C25.99 6.48 21.52 2 16 2Z" fill="url(#paint10_radial_18_28637)"/>
+<path d="M25.1 8C25.76 8 26.19 8.69 25.9 9.28L21.65 18H19.67L24.3 8.5C24.45 8.19 24.77 8 25.1 8Z" fill="url(#paint11_linear_18_28637)"/>
+<g filter="url(#filter1_f_18_28637)">
+<path d="M20.5625 18.4688L25.2812 8.90625" stroke="url(#paint12_linear_18_28637)" stroke-width="0.75" stroke-linecap="round"/>
+</g>
+<path d="M8 18L15.41 29.68C15.68 30.11 16.32 30.11 16.59 29.68L24 18H8Z" fill="url(#paint13_radial_18_28637)"/>
+<path d="M8 18L15.41 29.68C15.68 30.11 16.32 30.11 16.59 29.68L24 18H8Z" fill="url(#paint14_linear_18_28637)"/>
+<g filter="url(#filter2_f_18_28637)">
+<path d="M13.2962 6.15625C13.591 6.15625 13.83 5.97477 13.83 5.67999C13.83 5.38521 13.591 5.07812 13.2962 5.07812C13.0015 5.07812 12.4219 5.59584 12.4219 5.89062C12.4219 6.18541 13.0015 6.15625 13.2962 6.15625Z" fill="url(#paint15_radial_18_28637)"/>
+</g>
+<g filter="url(#filter3_f_18_28637)">
+<path d="M11.2962 7.23523C11.591 7.23523 11.83 7.05375 11.83 6.75897C11.83 6.46419 11.591 6.1571 11.2962 6.1571C11.0015 6.1571 10.4219 6.67482 10.4219 6.9696C10.4219 7.26438 11.0015 7.23523 11.2962 7.23523Z" fill="url(#paint16_radial_18_28637)"/>
+</g>
+<g filter="url(#filter4_f_18_28637)">
+<path d="M13.2962 8.2179C13.591 8.2179 13.83 8.03641 13.83 7.74163C13.83 7.44685 13.591 7.13977 13.2962 7.13977C13.0015 7.13977 12.4219 7.65749 12.4219 7.95227C12.4219 8.24705 13.0015 8.2179 13.2962 8.2179Z" fill="url(#paint17_radial_18_28637)"/>
+</g>
+<path d="M13.4837 6.05748C13.7785 6.05748 14.0175 5.81852 14.0175 5.52374C14.0175 5.22896 13.7785 4.98999 13.4837 4.98999C13.189 4.98999 12.95 5.22896 12.95 5.52374C12.95 5.81852 13.189 6.05748 13.4837 6.05748Z" fill="url(#paint18_radial_18_28637)"/>
+<path d="M11.4994 7.04186C11.7941 7.04186 12.0331 6.80289 12.0331 6.50811C12.0331 6.21333 11.7941 5.97437 11.4994 5.97437C11.2046 5.97437 10.9656 6.21333 10.9656 6.50811C10.9656 6.80289 11.2046 7.04186 11.4994 7.04186Z" fill="#E5D6EB"/>
+<path d="M11.4994 7.04186C11.7941 7.04186 12.0331 6.80289 12.0331 6.50811C12.0331 6.21333 11.7941 5.97437 11.4994 5.97437C11.2046 5.97437 10.9656 6.21333 10.9656 6.50811C10.9656 6.80289 11.2046 7.04186 11.4994 7.04186Z" fill="url(#paint19_radial_18_28637)"/>
+<path d="M13.4837 8.10936C13.7785 8.10936 14.0175 7.8704 14.0175 7.57562C14.0175 7.28084 13.7785 7.04187 13.4837 7.04187C13.189 7.04187 12.95 7.28084 12.95 7.57562C12.95 7.8704 13.189 8.10936 13.4837 8.10936Z" fill="url(#paint20_radial_18_28637)"/>
+<g filter="url(#filter5_f_18_28637)">
+<ellipse cx="25.4492" cy="8.65625" rx="0.324167" ry="0.34375" fill="#74D8FF"/>
+</g>
+<defs>
+<filter id="filter0_f_18_28637" x="7.77939" y="6.07935" width="18.3144" height="10.6394" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.5" result="effect1_foregroundBlur_18_28637"/>
+</filter>
+<filter id="filter1_f_18_28637" x="19.4374" y="7.78113" width="6.9689" height="11.8127" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.375" result="effect1_foregroundBlur_18_28637"/>
+</filter>
+<filter id="filter2_f_18_28637" x="12.1719" y="4.82812" width="1.90811" height="1.57898" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.125" result="effect1_foregroundBlur_18_28637"/>
+</filter>
+<filter id="filter3_f_18_28637" x="10.1719" y="5.9071" width="1.90811" height="1.57898" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.125" result="effect1_foregroundBlur_18_28637"/>
+</filter>
+<filter id="filter4_f_18_28637" x="12.1719" y="6.88977" width="1.90811" height="1.57898" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.125" result="effect1_foregroundBlur_18_28637"/>
+</filter>
+<filter id="filter5_f_18_28637" x="24.625" y="7.8125" width="1.64833" height="1.6875" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.25" result="effect1_foregroundBlur_18_28637"/>
+</filter>
+<radialGradient id="paint0_radial_18_28637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(21 12) rotate(119.358) scale(11.4735)">
+<stop stop-color="#FFEDE4"/>
+<stop offset="0.451632" stop-color="#FFDBDF"/>
+<stop offset="1" stop-color="#D8AEBD" stop-opacity="0"/>
+</radialGradient>
+<radialGradient id="paint1_radial_18_28637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(21 7.75) rotate(129.523) scale(12.9639 14.3163)">
+<stop stop-color="#FFBAEA"/>
+<stop offset="0.451156" stop-color="#FF97E4"/>
+<stop offset="1" stop-color="#FF83E1" stop-opacity="0"/>
+</radialGradient>
+<linearGradient id="paint2_linear_18_28637" x1="16" y1="2.375" x2="10" y2="17.125" gradientUnits="userSpaceOnUse">
+<stop offset="0.399003" stop-color="#FF80E1" stop-opacity="0"/>
+<stop offset="0.953305" stop-color="#E153BB"/>
+</linearGradient>
+<radialGradient id="paint3_radial_18_28637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(25.25 14.5) rotate(140.793) scale(6.1301 2.87771)">
+<stop stop-color="#FF74DE"/>
+<stop offset="1" stop-color="#FF73DB" stop-opacity="0"/>
+</radialGradient>
+<radialGradient id="paint4_radial_18_28637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(10.5 16.375) scale(5.5 8.8125)">
+<stop offset="0.465909" stop-color="#DB62C1"/>
+<stop offset="0.545455" stop-color="#DE60C3" stop-opacity="0"/>
+</radialGradient>
+<linearGradient id="paint5_linear_18_28637" x1="22" y1="13.1875" x2="8.77939" y2="13.1875" gradientUnits="userSpaceOnUse">
+<stop stop-color="#E9B4AF"/>
+<stop offset="0.888764" stop-color="#C9939B"/>
+</linearGradient>
+<radialGradient id="paint6_radial_18_28637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(21 7.375) rotate(129.523) scale(12.9639 14.3163)">
+<stop stop-color="#FFBAEA"/>
+<stop offset="0.451156" stop-color="#FF97E4"/>
+<stop offset="1" stop-color="#FF83E1" stop-opacity="0"/>
+</radialGradient>
+<linearGradient id="paint7_linear_18_28637" x1="16" y1="2" x2="10" y2="16.75" gradientUnits="userSpaceOnUse">
+<stop offset="0.399003" stop-color="#FF80E1" stop-opacity="0"/>
+<stop offset="0.953305" stop-color="#E153BB"/>
+</linearGradient>
+<radialGradient id="paint8_radial_18_28637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(25.25 14.125) rotate(140.793) scale(6.1301 2.87771)">
+<stop stop-color="#FF74DE"/>
+<stop offset="1" stop-color="#FF73DB" stop-opacity="0"/>
+</radialGradient>
+<radialGradient id="paint9_radial_18_28637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(10.5 16) scale(5.5 8.8125)">
+<stop offset="0.465909" stop-color="#DB62C1"/>
+<stop offset="0.545455" stop-color="#DE60C3" stop-opacity="0"/>
+</radialGradient>
+<radialGradient id="paint10_radial_18_28637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(21.6875 15.5625) scale(4.9375 8.07417)">
+<stop offset="0.414524" stop-color="#FF67CC"/>
+<stop offset="0.50886" stop-color="#DE60C3" stop-opacity="0"/>
+</radialGradient>
+<linearGradient id="paint11_linear_18_28637" x1="20.0625" y1="18" x2="24.875" y2="8" gradientUnits="userSpaceOnUse">
+<stop stop-color="#2E9CF8"/>
+<stop offset="1" stop-color="#42B6F8"/>
+</linearGradient>
+<linearGradient id="paint12_linear_18_28637" x1="25.1875" y1="9.4375" x2="20.4063" y2="18.5" gradientUnits="userSpaceOnUse">
+<stop stop-color="#5AC6FF"/>
+<stop offset="1" stop-color="#3FACFF"/>
+</linearGradient>
+<radialGradient id="paint13_radial_18_28637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(10.875 25.25) rotate(-31.6755) scale(13.8067 26.8563)">
+<stop offset="0.137131" stop-color="#518EF4"/>
+<stop offset="0.415574" stop-color="#60BCFF"/>
+<stop offset="0.680287" stop-color="#62D2FF"/>
+<stop offset="1" stop-color="#5FD4FF"/>
+</radialGradient>
+<linearGradient id="paint14_linear_18_28637" x1="20.8125" y1="23.3125" x2="19.875" y2="22.6875" gradientUnits="userSpaceOnUse">
+<stop offset="0.138462" stop-color="#64BDFF"/>
+<stop offset="1" stop-color="#64B6FF" stop-opacity="0"/>
+</linearGradient>
+<radialGradient id="paint15_radial_18_28637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(13.2031 5.61759) rotate(153.418) scale(1.20556 1.11753)">
+<stop stop-color="#CD7BA2"/>
+<stop offset="1" stop-color="#CD7BA2" stop-opacity="0"/>
+</radialGradient>
+<radialGradient id="paint16_radial_18_28637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(11.2031 6.69656) rotate(153.418) scale(1.20556 1.11753)">
+<stop stop-color="#CD7BA2"/>
+<stop offset="1" stop-color="#CD7BA2" stop-opacity="0"/>
+</radialGradient>
+<radialGradient id="paint17_radial_18_28637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(13.2031 7.67923) rotate(153.418) scale(1.20556 1.11753)">
+<stop offset="0.0843161" stop-color="#CB71AE"/>
+<stop offset="1" stop-color="#CD7BA2" stop-opacity="0"/>
+</radialGradient>
+<radialGradient id="paint18_radial_18_28637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(13.7188 5.29688) rotate(123.32) scale(0.910235)">
+<stop offset="0.159787" stop-color="#FBEDFD"/>
+<stop offset="0.854934" stop-color="#E2C9E5"/>
+</radialGradient>
+<radialGradient id="paint19_radial_18_28637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(11.7344 6.28125) rotate(123.32) scale(0.910235)">
+<stop offset="0.159787" stop-color="#FBEDFD"/>
+<stop offset="0.854934" stop-color="#E2C9E5"/>
+</radialGradient>
+<radialGradient id="paint20_radial_18_28637" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(13.7188 7.34875) rotate(123.32) scale(0.910235)">
+<stop offset="0.159787" stop-color="#FBEDFD"/>
+<stop offset="0.854934" stop-color="#E2C9E5"/>
+</radialGradient>
+</defs>
+</svg>
diff --git a/packages/frontend/assets/drop-and-fusion/sweets_monos/shortcake_color.svg b/packages/frontend/assets/drop-and-fusion/sweets_monos/shortcake_color.svg
new file mode 100644
index 0000000000..66e8f91f19
--- /dev/null
+++ b/packages/frontend/assets/drop-and-fusion/sweets_monos/shortcake_color.svg
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="32px" height="32px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <g transform="matrix(1,0,0,1,0,0.528489)">
+        <path d="M3.627,29.819L28.256,22.752L29.044,22.028L29.044,7.978L28.022,5.999L2.073,13.343L2.073,28.287C2.073,29.512 2.568,30.072 3.627,29.819Z" style="fill:url(#_Linear1);fill-rule:nonzero;"/>
+        <path d="M3.627,29.819L28.256,22.752L29.044,22.028L29.044,7.978L28.022,5.999L2.073,13.343L2.073,28.287C2.073,29.512 2.568,30.072 3.627,29.819Z" style="fill:url(#_Linear2);fill-rule:nonzero;"/>
+        <path d="M3.627,29.819L28.256,22.752L29.044,22.028L29.044,7.978L28.022,5.999L2.073,13.343L2.073,28.287C2.073,29.512 2.568,30.072 3.627,29.819Z" style="fill:url(#_Linear3);fill-rule:nonzero;"/>
+        <path d="M3.627,29.819L28.256,22.752L29.044,22.028L29.044,7.978L28.022,5.999L2.073,13.343L2.073,28.287C2.073,29.512 2.568,30.072 3.627,29.819Z" style="fill:url(#_Linear4);fill-rule:nonzero;"/>
+        <path d="M3.627,29.819L28.256,22.752L29.044,22.028L29.044,7.978L28.022,5.999L2.073,13.343L2.073,28.287C2.073,29.512 2.568,30.072 3.627,29.819Z" style="fill:url(#_Linear5);fill-rule:nonzero;"/>
+        <path d="M13.034,2.508L3.064,11.731C1.118,13.393 2.129,14.542 3.998,14.13L27.024,7.524C27.616,7.383 27.99,7.338 27.99,8.023L27.99,12.042C27.99,12.73 28.054,13.088 27.616,13.226C27.616,13.226 5.411,19.92 4.933,20.05C4.455,20.18 3.393,20.378 3.064,20.33C2.734,20.282 2.316,20.05 2.129,20.05C1.942,20.05 1.942,20.158 1.942,20.673L1.942,22.84C1.942,23.003 2.083,23.108 2.083,23.108C2.61,23.445 3.335,23.59 3.998,23.415L27.243,16.685C27.616,16.591 27.99,16.841 27.99,17.308L27.99,22.606C27.99,22.928 28.185,22.945 28.322,22.928C29.314,22.642 30,22.055 30,21.047L30,8.023C30,7.17 29.383,5.745 27.99,5.312L16.96,2.726C15.913,2.508 13.888,1.847 13.034,2.508Z" style="fill:url(#_Linear6);fill-rule:nonzero;"/>
+        <path d="M13.034,2.508L3.064,11.731C1.118,13.393 2.129,14.542 3.998,14.13L27.024,7.524C27.616,7.383 27.99,7.338 27.99,8.023L27.99,12.042C27.99,12.73 28.054,13.088 27.616,13.226C27.616,13.226 5.411,19.92 4.933,20.05C4.455,20.18 3.393,20.378 3.064,20.33C2.734,20.282 2.316,20.05 2.129,20.05C1.942,20.05 1.942,20.158 1.942,20.673L1.942,22.84C1.942,23.003 2.083,23.108 2.083,23.108C2.61,23.445 3.335,23.59 3.998,23.415L27.243,16.685C27.616,16.591 27.99,16.841 27.99,17.308L27.99,22.606C27.99,22.928 28.185,22.945 28.322,22.928C29.314,22.642 30,22.055 30,21.047L30,8.023C30,7.17 29.383,5.745 27.99,5.312L16.96,2.726C15.913,2.508 13.888,1.847 13.034,2.508Z" style="fill:url(#_Linear7);fill-rule:nonzero;"/>
+        <path d="M13.034,2.508L3.064,11.731C1.118,13.393 2.129,14.542 3.998,14.13L27.024,7.524C27.616,7.383 27.99,7.338 27.99,8.023L27.99,12.042C27.99,12.73 28.054,13.088 27.616,13.226C27.616,13.226 5.411,19.92 4.933,20.05C4.455,20.18 3.393,20.378 3.064,20.33C2.734,20.282 2.316,20.05 2.129,20.05C1.942,20.05 1.942,20.158 1.942,20.673L1.942,22.84C1.942,23.003 2.083,23.108 2.083,23.108C2.61,23.445 3.335,23.59 3.998,23.415L27.243,16.685C27.616,16.591 27.99,16.841 27.99,17.308L27.99,22.606C27.99,22.928 28.185,22.945 28.322,22.928C29.314,22.642 30,22.055 30,21.047L30,8.023C30,7.17 29.383,5.745 27.99,5.312L16.96,2.726C15.913,2.508 13.888,1.847 13.034,2.508Z" style="fill:url(#_Linear8);fill-rule:nonzero;"/>
+        <path d="M13.034,2.508L3.064,11.731C1.118,13.393 2.129,14.542 3.998,14.13L27.024,7.524C27.616,7.383 27.99,7.338 27.99,8.023L27.99,12.042C27.99,12.73 28.054,13.088 27.616,13.226C27.616,13.226 5.411,19.92 4.933,20.05C4.455,20.18 3.393,20.378 3.064,20.33C2.734,20.282 2.316,20.05 2.129,20.05C1.942,20.05 1.942,20.158 1.942,20.673L1.942,22.84C1.942,23.003 2.083,23.108 2.083,23.108C2.61,23.445 3.335,23.59 3.998,23.415L27.243,16.685C27.616,16.591 27.99,16.841 27.99,17.308L27.99,22.606C27.99,22.928 28.185,22.945 28.322,22.928C29.314,22.642 30,22.055 30,21.047L30,8.023C30,7.17 29.383,5.745 27.99,5.312L16.96,2.726C15.913,2.508 13.888,1.847 13.034,2.508Z" style="fill:url(#_Linear9);fill-rule:nonzero;"/>
+        <path d="M13.034,2.508L3.064,11.731C1.118,13.393 2.129,14.542 3.998,14.13L27.024,7.524C27.616,7.383 27.99,7.338 27.99,8.023L27.99,12.042C27.99,12.73 28.054,13.088 27.616,13.226C27.616,13.226 5.411,19.92 4.933,20.05C4.455,20.18 3.393,20.378 3.064,20.33C2.734,20.282 2.316,20.05 2.129,20.05C1.942,20.05 1.942,20.158 1.942,20.673L1.942,22.84C1.942,23.003 2.083,23.108 2.083,23.108C2.61,23.445 3.335,23.59 3.998,23.415L27.243,16.685C27.616,16.591 27.99,16.841 27.99,17.308L27.99,22.606C27.99,22.928 28.185,22.945 28.322,22.928C29.314,22.642 30,22.055 30,21.047L30,8.023C30,7.17 29.383,5.745 27.99,5.312L16.96,2.726C15.913,2.508 13.888,1.847 13.034,2.508Z" style="fill:url(#_Radial10);fill-rule:nonzero;"/>
+        <path d="M13.034,2.508L3.064,11.731C1.118,13.393 2.129,14.542 3.998,14.13L27.024,7.524C27.616,7.383 27.99,7.338 27.99,8.023L27.99,12.042C27.99,12.73 28.054,13.088 27.616,13.226C27.616,13.226 5.411,19.92 4.933,20.05C4.455,20.18 3.393,20.378 3.064,20.33C2.734,20.282 2.316,20.05 2.129,20.05C1.942,20.05 1.942,20.158 1.942,20.673L1.942,22.84C1.942,23.003 2.083,23.108 2.083,23.108C2.61,23.445 3.335,23.59 3.998,23.415L27.243,16.685C27.616,16.591 27.99,16.841 27.99,17.308L27.99,22.606C27.99,22.928 28.185,22.945 28.322,22.928C29.314,22.642 30,22.055 30,21.047L30,8.023C30,7.17 29.383,5.745 27.99,5.312L16.96,2.726C15.913,2.508 13.888,1.847 13.034,2.508Z" style="fill:url(#_Linear11);fill-rule:nonzero;"/>
+        <path d="M13.034,2.508L3.064,11.731C1.118,13.393 2.129,14.542 3.998,14.13L27.024,7.524C27.616,7.383 27.99,7.338 27.99,8.023L27.99,12.042C27.99,12.73 28.054,13.088 27.616,13.226C27.616,13.226 5.411,19.92 4.933,20.05C4.455,20.18 3.393,20.378 3.064,20.33C2.734,20.282 2.316,20.05 2.129,20.05C1.942,20.05 1.942,20.158 1.942,20.673L1.942,22.84C1.942,23.003 2.083,23.108 2.083,23.108C2.61,23.445 3.335,23.59 3.998,23.415L27.243,16.685C27.616,16.591 27.99,16.841 27.99,17.308L27.99,22.606C27.99,22.928 28.185,22.945 28.322,22.928C29.314,22.642 30,22.055 30,21.047L30,8.023C30,7.17 29.383,5.745 27.99,5.312L16.96,2.726C15.913,2.508 13.888,1.847 13.034,2.508Z" style="fill:url(#_Linear12);fill-rule:nonzero;"/>
+        <path d="M13.034,2.508L3.064,11.731C1.118,13.393 2.129,14.542 3.998,14.13L27.024,7.524C27.616,7.383 27.99,7.338 27.99,8.023L27.99,12.042C27.99,12.73 28.054,13.088 27.616,13.226C27.616,13.226 5.411,19.92 4.933,20.05C4.455,20.18 3.393,20.378 3.064,20.33C2.734,20.282 2.316,20.05 2.129,20.05C1.942,20.05 1.942,20.158 1.942,20.673L1.942,22.84C1.942,23.003 2.083,23.108 2.083,23.108C2.61,23.445 3.335,23.59 3.998,23.415L27.243,16.685C27.616,16.591 27.99,16.841 27.99,17.308L27.99,22.606C27.99,22.928 28.185,22.945 28.322,22.928C29.314,22.642 30,22.055 30,21.047L30,8.023C30,7.17 29.383,5.745 27.99,5.312L16.96,2.726C15.913,2.508 13.888,1.847 13.034,2.508Z" style="fill:url(#_Radial13);fill-rule:nonzero;"/>
+        <g>
+            <path d="M12.664,7.578C12.024,7.578 11.803,7.217 11.891,6.554C12.556,-2.951 20.734,7.578 14.627,7.578L12.664,7.578Z" style="fill:rgb(213,45,38);fill-rule:nonzero;"/>
+            <path d="M12.664,7.578C12.024,7.578 11.803,7.217 11.891,6.554C12.556,-2.951 20.734,7.578 14.627,7.578L12.664,7.578Z" style="fill:rgb(162,56,40);fill-rule:nonzero;"/>
+        </g>
+        <path d="M12.024,4.517C12.15,3.889 12.359,2.115 14.54,1.681L16.896,2.888L17.754,5.463C16.95,7.624 15.291,7.47 14.627,7.47L12.664,7.47C12.024,7.47 11.66,7.106 11.748,6.437L12.024,4.517Z" style="fill:rgb(213,45,38);fill-rule:nonzero;"/>
+        <path d="M12.024,4.517C12.15,3.889 12.359,2.115 14.54,1.681L16.896,2.888L17.754,5.463C16.95,7.624 15.291,7.47 14.627,7.47L12.664,7.47C12.024,7.47 11.66,7.106 11.748,6.437L12.024,4.517Z" style="fill:url(#_Radial14);fill-rule:nonzero;"/>
+        <g>
+            <path d="M16.898,4.469C16.459,3.974 16.542,3.199 16.542,3.199C16.542,3.199 15.848,3.183 15.5,2.805C13.208,0.515 16.006,2.044 16.727,2.687C17.664,3.523 17.828,4.992 17.828,4.992C17.751,5.087 17.984,5.484 17.734,5.344C17.484,5.203 16.898,4.469 16.898,4.469Z" style="fill:rgb(141,197,39);fill-rule:nonzero;"/>
+            <path d="M16.898,4.469C16.459,3.974 16.542,3.199 16.542,3.199C16.542,3.199 15.848,3.183 15.5,2.805C13.208,0.515 16.006,2.044 16.727,2.687C17.664,3.523 17.828,4.992 17.828,4.992C17.751,5.087 17.984,5.484 17.734,5.344C17.484,5.203 16.898,4.469 16.898,4.469Z" style="fill:rgb(103,42,23);fill-rule:nonzero;"/>
+        </g>
+        <path d="M16.94,4.372C16.5,3.877 16.62,3.121 16.62,3.121C16.62,3.121 15.906,3.121 15.558,2.743L14.365,1.507C14.301,1.419 14.365,1.323 14.453,1.259C14.453,1.259 15.154,0.728 16.242,1.419C16.844,1.863 17.988,3.281 18.278,3.877C18.805,4.777 18.132,5.55 18.132,5.55C18.055,5.645 17.965,5.672 17.856,5.55L16.94,4.372Z" style="fill:rgb(141,197,39);fill-rule:nonzero;"/>
+        <path d="M16.94,4.372C16.5,3.877 16.62,3.121 16.62,3.121C16.62,3.121 15.906,3.121 15.558,2.743L14.365,1.507C14.301,1.419 14.365,1.323 14.453,1.259C14.453,1.259 15.154,0.728 16.242,1.419C16.844,1.863 17.988,3.281 18.278,3.877C18.805,4.777 18.132,5.55 18.132,5.55C18.055,5.645 17.965,5.672 17.856,5.55L16.94,4.372Z" style="fill:url(#_Radial15);fill-rule:nonzero;"/>
+    </g>
+    <defs>
+        <linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(24.25,-0.0312,0.0312,24.25,1.6875,18.4687)"><stop offset="0" style="stop-color:rgb(202,149,127);stop-opacity:1"/><stop offset="0.01" style="stop-color:rgb(202,149,127);stop-opacity:1"/><stop offset="0.05" style="stop-color:rgb(255,180,164);stop-opacity:1"/><stop offset="0.08" style="stop-color:rgb(255,214,176);stop-opacity:1"/><stop offset="0.14" style="stop-color:rgb(255,217,176);stop-opacity:1"/><stop offset="0.52" style="stop-color:rgb(255,193,138);stop-opacity:1"/><stop offset="0.92" style="stop-color:rgb(255,157,80);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(255,157,80);stop-opacity:1"/></linearGradient>
+        <linearGradient id="_Linear2" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.5583,5.125,-5.125,1.5583,14,21.6875)"><stop offset="0" style="stop-color:rgb(255,179,123);stop-opacity:0"/><stop offset="0.78" style="stop-color:rgb(255,179,123);stop-opacity:0"/><stop offset="0.89" style="stop-color:rgb(255,156,129);stop-opacity:1"/><stop offset="0.93" style="stop-color:rgb(246,127,157);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(246,127,157);stop-opacity:1"/></linearGradient>
+        <linearGradient id="_Linear3" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.125,4.0625,-4.0625,1.125,19.875,15.1875)"><stop offset="0" style="stop-color:rgb(240,132,86);stop-opacity:0"/><stop offset="0.26" style="stop-color:rgb(240,132,86);stop-opacity:0"/><stop offset="0.76" style="stop-color:rgb(228,121,75);stop-opacity:1"/><stop offset="0.92" style="stop-color:rgb(228,121,75);stop-opacity:0"/><stop offset="1" style="stop-color:rgb(228,121,75);stop-opacity:0"/></linearGradient>
+        <linearGradient id="_Linear4" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.8792,3.125,-3.125,0.8792,15.5583,8.0625)"><stop offset="0" style="stop-color:rgb(240,132,86);stop-opacity:0"/><stop offset="0.26" style="stop-color:rgb(240,132,86);stop-opacity:0"/><stop offset="0.76" style="stop-color:rgb(228,121,75);stop-opacity:1"/><stop offset="0.92" style="stop-color:rgb(228,121,75);stop-opacity:0"/><stop offset="1" style="stop-color:rgb(228,121,75);stop-opacity:0"/></linearGradient>
+        <linearGradient id="_Linear5" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.92737,0,0,1.92737,2.07263,20.5)"><stop offset="0" style="stop-color:rgb(194,150,119);stop-opacity:1"/><stop offset="0.58" style="stop-color:rgb(254,179,163);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(255,206,177);stop-opacity:0"/></linearGradient>
+        <linearGradient id="_Linear6" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(6.6562,22.5312,-22.5312,6.6562,10.9688,3.46875)"><stop offset="0" style="stop-color:rgb(243,207,162);stop-opacity:1"/><stop offset="0.26" style="stop-color:rgb(243,207,162);stop-opacity:1"/><stop offset="0.3" style="stop-color:rgb(255,240,203);stop-opacity:1"/><stop offset="0.32" style="stop-color:rgb(255,213,206);stop-opacity:1"/><stop offset="0.55" style="stop-color:rgb(255,214,207);stop-opacity:1"/><stop offset="0.6" style="stop-color:rgb(255,254,226);stop-opacity:1"/><stop offset="0.62" style="stop-color:rgb(255,228,229);stop-opacity:1"/><stop offset="0.97" style="stop-color:rgb(255,227,227);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(249,170,224);stop-opacity:1"/></linearGradient>
+        <linearGradient id="_Linear7" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.74547,0,0,1.74547,1.94203,21.625)"><stop offset="0" style="stop-color:rgb(198,156,155);stop-opacity:1"/><stop offset="0.14" style="stop-color:rgb(221,164,181);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(248,182,211);stop-opacity:0"/></linearGradient>
+        <linearGradient id="_Linear8" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.45313,0.48438,-0.48438,0.45313,8.5,6.54687)"><stop offset="0" style="stop-color:rgb(183,162,135);stop-opacity:1"/><stop offset="0.82" style="stop-color:rgb(207,177,140);stop-opacity:0"/><stop offset="1" style="stop-color:rgb(207,177,140);stop-opacity:0"/></linearGradient>
+        <linearGradient id="_Linear9" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-0.0782,0.35156,-0.35156,-0.0782,21.2969,3.67188)"><stop offset="0" style="stop-color:rgb(183,162,135);stop-opacity:1"/><stop offset="0.82" style="stop-color:rgb(207,177,140);stop-opacity:0"/><stop offset="1" style="stop-color:rgb(207,177,140);stop-opacity:0"/></linearGradient>
+        <radialGradient id="_Radial10" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-2.53191e-15,6.61319,-3.0625,-1.1725e-15,28.9375,12.8556)"><stop offset="0" style="stop-color:rgb(255,222,211);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(255,222,213);stop-opacity:0"/></radialGradient>
+        <linearGradient id="_Linear11" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-2.4375,2.98508e-16,-2.98508e-16,-2.4375,30,12.8556)"><stop offset="0" style="stop-color:rgb(214,192,170);stop-opacity:1"/><stop offset="0.04" style="stop-color:rgb(246,212,188);stop-opacity:1"/><stop offset="0.19" style="stop-color:rgb(255,222,212);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(255,222,211);stop-opacity:0"/></linearGradient>
+        <linearGradient id="_Linear12" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.9218,2.2343,-2.2343,0.9218,27.6094,20.9844)"><stop offset="0" style="stop-color:rgb(255,214,227);stop-opacity:0"/><stop offset="0.29" style="stop-color:rgb(255,214,227);stop-opacity:0"/><stop offset="0.59" style="stop-color:rgb(244,168,220);stop-opacity:0"/><stop offset="0.95" style="stop-color:rgb(229,150,221);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(229,150,221);stop-opacity:1"/></linearGradient>
+        <radialGradient id="_Radial13" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.0624985,1.78125,-4.40342,0.154502,13.8281,6.6875)"><stop offset="0" style="stop-color:rgb(178,129,98);stop-opacity:1"/><stop offset="0.52" style="stop-color:rgb(178,129,98);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(194,154,121);stop-opacity:0"/></radialGradient>
+        <radialGradient id="_Radial14" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1.81247,4.09702,-4.25876,-1.88402,16.1875,3.375)"><stop offset="0" style="stop-color:rgb(255,106,131);stop-opacity:1"/><stop offset="0.14" style="stop-color:rgb(255,106,131);stop-opacity:1"/><stop offset="0.52" style="stop-color:rgb(238,45,71);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(243,47,89);stop-opacity:0"/></radialGradient>
+        <radialGradient id="_Radial15" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-8.743e-16,2.28362,-2.07009,-7.92548e-16,16.4093,3.34783)"><stop offset="0" style="stop-color:rgb(177,235,103);stop-opacity:1"/><stop offset="0.9" style="stop-color:rgb(175,234,99);stop-opacity:0"/><stop offset="1" style="stop-color:rgb(175,234,99);stop-opacity:0"/></radialGradient>
+    </defs>
+</svg>
diff --git a/packages/frontend/assets/drop-and-fusion/sweets_monos/soft_ice_cream_color.svg b/packages/frontend/assets/drop-and-fusion/sweets_monos/soft_ice_cream_color.svg
new file mode 100644
index 0000000000..37be9c0cb3
--- /dev/null
+++ b/packages/frontend/assets/drop-and-fusion/sweets_monos/soft_ice_cream_color.svg
@@ -0,0 +1,140 @@
+<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M14.8021 18.0831H14.0721H12.0221H11.2921V20.1093C11.2921 20.1093 11.7321 20.0387 11.9421 20.2907C12.0521 20.4218 12.0321 20.7444 12.0321 20.7444C12.0321 21.3189 12.4921 21.7726 13.0521 21.7726C13.6121 21.7726 14.0721 21.3089 14.0721 20.7444C14.0721 20.7444 14.0621 20.3411 14.1821 20.1395C14.3321 19.8774 14.6121 19.8674 14.6121 19.8674L14.8021 18.0831Z" fill="url(#paint0_radial_18_28477)"/>
+<path d="M15.7121 22.6293H10.7021L11.5221 29.2319C11.5721 29.6654 11.9421 29.988 12.3721 29.988H15.7221H19.9821C20.4121 29.988 20.7721 29.6654 20.8321 29.2319L21.6521 22.6293H15.7121Z" fill="url(#paint1_linear_18_28477)"/>
+<path d="M15.7121 22.6293H10.7021L11.5221 29.2319C11.5721 29.6654 11.9421 29.988 12.3721 29.988H15.7221H19.9821C20.4121 29.988 20.7721 29.6654 20.8321 29.2319L21.6521 22.6293H15.7121Z" fill="url(#paint2_linear_18_28477)"/>
+<g opacity="0.5">
+<path d="M19.8421 22.6293H18.4421L16.2321 24.857L14.0221 22.6293H12.6221L10.9021 24.3631L11.2221 26.9739L12.6721 28.4356L11.5921 29.5243C11.7321 29.7964 12.0221 29.988 12.3521 29.988H12.4221L13.3121 29.0908L14.2021 29.988H15.4921L13.9821 28.4356L16.2421 26.1574L18.5021 28.4356L16.9621 29.988H18.2521L19.1421 29.0908L20.0321 29.988C20.3721 29.9678 20.6521 29.746 20.7721 29.4335L19.7821 28.4356L21.0821 27.1251L21.2621 25.6433L19.1321 27.7904L16.8721 25.5123L19.1321 23.2341L21.2921 25.4115L21.4321 24.2522L19.8421 22.6293ZM13.3321 27.7804L11.0721 25.5022L13.3321 23.224L15.5921 25.5022L13.3321 27.7804Z" fill="url(#paint3_linear_18_28477)"/>
+<path d="M19.8421 22.6293H18.4421L16.2321 24.857L14.0221 22.6293H12.6221L10.9021 24.3631L11.2221 26.9739L12.6721 28.4356L11.5921 29.5243C11.7321 29.7964 12.0221 29.988 12.3521 29.988H12.4221L13.3121 29.0908L14.2021 29.988H15.4921L13.9821 28.4356L16.2421 26.1574L18.5021 28.4356L16.9621 29.988H18.2521L19.1421 29.0908L20.0321 29.988C20.3721 29.9678 20.6521 29.746 20.7721 29.4335L19.7821 28.4356L21.0821 27.1251L21.2621 25.6433L19.1321 27.7904L16.8721 25.5123L19.1321 23.2341L21.2921 25.4115L21.4321 24.2522L19.8421 22.6293ZM13.3321 27.7804L11.0721 25.5022L13.3321 23.224L15.5921 25.5022L13.3321 27.7804Z" fill="url(#paint4_linear_18_28477)"/>
+</g>
+<path d="M21.8359 7.40625C21.8359 6.46094 20.5156 5.81254 19.7969 5.81254H18.6719C18.5019 5.82262 18 5.69535 18 5.31254C18 5.31254 17.9531 4.30926 17.9531 3.78129C17.9531 3.25331 17.6791 2.46623 16.7891 2.54688C16.0391 2.61744 15.7821 3.31528 15.7221 3.6177C15.6621 3.95035 15.4288 4.43069 14.9688 4.77342C14.0188 5.47905 11.1641 6.59373 11.1641 6.59373C10.3227 6.87643 9.64311 7.8143 9.71094 8.70311C9.71094 9.14752 10.0234 9.55505 10.0234 9.87498C10.0234 10.1949 9.41406 10.3672 9.41406 10.3672L22.0156 9.35938C22.0156 9.35938 21.5547 9.07812 21.5547 8.74219C21.5547 8.46094 21.8359 8.17188 21.8359 7.40625Z" fill="url(#paint5_linear_18_28477)"/>
+<path d="M21.8359 7.40625C21.8359 6.46094 20.5156 5.81254 19.7969 5.81254H18.6719C18.5019 5.82262 18 5.69535 18 5.31254C18 5.31254 17.9531 4.30926 17.9531 3.78129C17.9531 3.25331 17.6791 2.46623 16.7891 2.54688C16.0391 2.61744 15.7821 3.31528 15.7221 3.6177C15.6621 3.95035 15.4288 4.43069 14.9688 4.77342C14.0188 5.47905 11.1641 6.59373 11.1641 6.59373C10.3227 6.87643 9.64311 7.8143 9.71094 8.70311C9.71094 9.14752 10.0234 9.55505 10.0234 9.87498C10.0234 10.1949 9.41406 10.3672 9.41406 10.3672L22.0156 9.35938C22.0156 9.35938 21.5547 9.07812 21.5547 8.74219C21.5547 8.46094 21.8359 8.17188 21.8359 7.40625Z" fill="url(#paint6_radial_18_28477)"/>
+<path d="M21.8359 7.40625C21.8359 6.46094 20.5156 5.81254 19.7969 5.81254H18.6719C18.5019 5.82262 18 5.69535 18 5.31254C18 5.31254 17.9531 4.30926 17.9531 3.78129C17.9531 3.25331 17.6791 2.46623 16.7891 2.54688C16.0391 2.61744 15.7821 3.31528 15.7221 3.6177C15.6621 3.95035 15.4288 4.43069 14.9688 4.77342C14.0188 5.47905 11.1641 6.59373 11.1641 6.59373C10.3227 6.87643 9.64311 7.8143 9.71094 8.70311C9.71094 9.14752 10.0234 9.55505 10.0234 9.87498C10.0234 10.1949 9.41406 10.3672 9.41406 10.3672L22.0156 9.35938C22.0156 9.35938 21.5547 9.07812 21.5547 8.74219C21.5547 8.46094 21.8359 8.17188 21.8359 7.40625Z" fill="url(#paint7_radial_18_28477)"/>
+<path d="M21.8359 7.40625C21.8359 6.46094 20.5156 5.81254 19.7969 5.81254H18.6719C18.5019 5.82262 18 5.69535 18 5.31254C18 5.31254 17.9531 4.30926 17.9531 3.78129C17.9531 3.25331 17.6791 2.46623 16.7891 2.54688C16.0391 2.61744 15.7821 3.31528 15.7221 3.6177C15.6621 3.95035 15.4288 4.43069 14.9688 4.77342C14.0188 5.47905 11.1641 6.59373 11.1641 6.59373C10.3227 6.87643 9.64311 7.8143 9.71094 8.70311C9.71094 9.14752 10.0234 9.55505 10.0234 9.87498C10.0234 10.1949 9.41406 10.3672 9.41406 10.3672L22.0156 9.35938C22.0156 9.35938 21.5547 9.07812 21.5547 8.74219C21.5547 8.46094 21.8359 8.17188 21.8359 7.40625Z" fill="url(#paint8_radial_18_28477)"/>
+<path d="M21.3021 9.24249C19.7917 9.36788 9.8621 10.2505 9.8621 10.2505C8.5021 10.3715 7.59375 11.5468 7.59375 12.6484C7.59375 13.4453 7.99999 13.6406 7.99999 14.1171C7.99999 14.5937 7.59375 14.7265 7.59375 14.7265L24.1953 13.6406C24.1953 13.6406 23.7882 13.375 23.7882 12.7696C23.7882 12.3984 24.0625 12.2656 24.0625 11.5469C24.0625 10.2734 22.8125 9.11711 21.3021 9.24249Z" fill="url(#paint9_linear_18_28477)"/>
+<path d="M21.3021 9.24249C19.7917 9.36788 9.8621 10.2505 9.8621 10.2505C8.5021 10.3715 7.59375 11.5468 7.59375 12.6484C7.59375 13.4453 7.99999 13.6406 7.99999 14.1171C7.99999 14.5937 7.59375 14.7265 7.59375 14.7265L24.1953 13.6406C24.1953 13.6406 23.7882 13.375 23.7882 12.7696C23.7882 12.3984 24.0625 12.2656 24.0625 11.5469C24.0625 10.2734 22.8125 9.11711 21.3021 9.24249Z" fill="url(#paint10_radial_18_28477)"/>
+<path d="M21.3021 9.24249C19.7917 9.36788 9.8621 10.2505 9.8621 10.2505C8.5021 10.3715 7.59375 11.5468 7.59375 12.6484C7.59375 13.4453 7.99999 13.6406 7.99999 14.1171C7.99999 14.5937 7.59375 14.7265 7.59375 14.7265L24.1953 13.6406C24.1953 13.6406 23.7882 13.375 23.7882 12.7696C23.7882 12.3984 24.0625 12.2656 24.0625 11.5469C24.0625 10.2734 22.8125 9.11711 21.3021 9.24249Z" fill="url(#paint11_radial_18_28477)"/>
+<path d="M25.7721 15.956C25.6221 14.2827 24.1621 13.0428 22.5021 13.1839L8.54211 14.4137C6.88211 14.5649 5.65211 16.0367 5.79211 17.71C5.94211 19.3834 7.40211 20.6233 9.06211 20.4821L17.1021 19.7765C17.3721 19.7563 20.5221 19.4741 20.7621 19.4539L23.0221 19.2523C24.6821 19.1011 25.9121 17.6294 25.7721 15.956Z" fill="url(#paint12_radial_18_28477)"/>
+<path d="M22.2221 22.6294H10.2521C9.7821 22.6294 9.39211 22.2463 9.39211 21.7624V20.2605L23.0721 19.202V21.7624C23.0821 22.2463 22.6921 22.6294 22.2221 22.6294Z" fill="url(#paint13_linear_18_28477)"/>
+<path d="M22.2221 22.6294H10.2521C9.7821 22.6294 9.39211 22.2463 9.39211 21.7624V20.2605L23.0721 19.202V21.7624C23.0821 22.2463 22.6921 22.6294 22.2221 22.6294Z" fill="url(#paint14_linear_18_28477)"/>
+<g filter="url(#filter0_f_18_28477)">
+<path d="M22.5021 13.7943C19.496 14.0591 12.4038 17.4238 10.1094 19.4374C9.64133 19.8482 10.8122 20.1392 11.4175 20.4009C11.7187 20.5312 11.768 20.9875 11.8125 21.1249C11.9463 21.5384 12.3125 21.8749 12.8125 21.8749C13.3125 21.8749 13.8047 21.3327 13.8047 21.039C13.8047 20.8374 13.7109 20.4791 14.8437 20.4009C15.9766 20.3228 16.8518 20.2724 20.8125 19.9634C24.7732 19.6545 22.8858 16.4166 22.5021 13.7943Z" fill="url(#paint15_linear_18_28477)"/>
+<path d="M22.5021 13.7943C19.496 14.0591 12.4038 17.4238 10.1094 19.4374C9.64133 19.8482 10.8122 20.1392 11.4175 20.4009C11.7187 20.5312 11.768 20.9875 11.8125 21.1249C11.9463 21.5384 12.3125 21.8749 12.8125 21.8749C13.3125 21.8749 13.8047 21.3327 13.8047 21.039C13.8047 20.8374 13.7109 20.4791 14.8437 20.4009C15.9766 20.3228 16.8518 20.2724 20.8125 19.9634C24.7732 19.6545 22.8858 16.4166 22.5021 13.7943Z" fill="url(#paint16_radial_18_28477)"/>
+<path d="M22.5021 13.7943C19.496 14.0591 12.4038 17.4238 10.1094 19.4374C9.64133 19.8482 10.8122 20.1392 11.4175 20.4009C11.7187 20.5312 11.768 20.9875 11.8125 21.1249C11.9463 21.5384 12.3125 21.8749 12.8125 21.8749C13.3125 21.8749 13.8047 21.3327 13.8047 21.039C13.8047 20.8374 13.7109 20.4791 14.8437 20.4009C15.9766 20.3228 16.8518 20.2724 20.8125 19.9634C24.7732 19.6545 22.8858 16.4166 22.5021 13.7943Z" fill="#C67B4D"/>
+</g>
+<path d="M25.7721 15.956C25.6221 14.2827 24.1621 13.0428 22.5021 13.1839L8.54211 14.4137C6.88211 14.5649 5.65211 16.0367 5.79211 17.71C5.94211 19.3834 7.40211 20.6233 9.06211 20.4821L11.3921 20.2805C11.6121 20.2604 11.8021 20.4418 11.8021 20.6636L11.8321 20.8551C11.8321 21.3591 12.2421 21.7724 12.7421 21.7724C13.2421 21.7724 13.6521 21.3591 13.6521 20.8551L13.6221 20.4317C13.6221 20.2301 13.7721 20.0688 13.9721 20.0487L17.0921 19.7765C17.3621 19.7563 20.5121 19.4741 20.7521 19.4539L23.0121 19.2523C24.6821 19.1011 25.9121 17.6294 25.7721 15.956Z" fill="url(#paint17_linear_18_28477)"/>
+<path d="M25.7721 15.956C25.6221 14.2827 24.1621 13.0428 22.5021 13.1839L8.54211 14.4137C6.88211 14.5649 5.65211 16.0367 5.79211 17.71C5.94211 19.3834 7.40211 20.6233 9.06211 20.4821L11.3921 20.2805C11.6121 20.2604 11.8021 20.4418 11.8021 20.6636L11.8321 20.8551C11.8321 21.3591 12.2421 21.7724 12.7421 21.7724C13.2421 21.7724 13.6521 21.3591 13.6521 20.8551L13.6221 20.4317C13.6221 20.2301 13.7721 20.0688 13.9721 20.0487L17.0921 19.7765C17.3621 19.7563 20.5121 19.4741 20.7521 19.4539L23.0121 19.2523C24.6821 19.1011 25.9121 17.6294 25.7721 15.956Z" fill="url(#paint18_radial_18_28477)"/>
+<path d="M25.7721 15.956C25.6221 14.2827 24.1621 13.0428 22.5021 13.1839L8.54211 14.4137C6.88211 14.5649 5.65211 16.0367 5.79211 17.71C5.94211 19.3834 7.40211 20.6233 9.06211 20.4821L11.3921 20.2805C11.6121 20.2604 11.8021 20.4418 11.8021 20.6636L11.8321 20.8551C11.8321 21.3591 12.2421 21.7724 12.7421 21.7724C13.2421 21.7724 13.6521 21.3591 13.6521 20.8551L13.6221 20.4317C13.6221 20.2301 13.7721 20.0688 13.9721 20.0487L17.0921 19.7765C17.3621 19.7563 20.5121 19.4741 20.7521 19.4539L23.0121 19.2523C24.6821 19.1011 25.9121 17.6294 25.7721 15.956Z" fill="url(#paint19_radial_18_28477)"/>
+<path d="M25.7721 15.956C25.6221 14.2827 24.1621 13.0428 22.5021 13.1839L8.54211 14.4137C6.88211 14.5649 5.65211 16.0367 5.79211 17.71C5.94211 19.3834 7.40211 20.6233 9.06211 20.4821L11.3921 20.2805C11.6121 20.2604 11.8021 20.4418 11.8021 20.6636L11.8321 20.8551C11.8321 21.3591 12.2421 21.7724 12.7421 21.7724C13.2421 21.7724 13.6521 21.3591 13.6521 20.8551L13.6221 20.4317C13.6221 20.2301 13.7721 20.0688 13.9721 20.0487L17.0921 19.7765C17.3621 19.7563 20.5121 19.4741 20.7521 19.4539L23.0121 19.2523C24.6821 19.1011 25.9121 17.6294 25.7721 15.956Z" fill="url(#paint20_radial_18_28477)"/>
+<defs>
+<filter id="filter0_f_18_28477" x="9.00156" y="12.7943" width="15.2857" height="10.0806" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
+<feFlood flood-opacity="0" result="BackgroundImageFix"/>
+<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
+<feGaussianBlur stdDeviation="0.5" result="effect1_foregroundBlur_18_28477"/>
+</filter>
+<radialGradient id="paint0_radial_18_28477" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(13.046 19.9264) scale(1.7953 1.80973)">
+<stop stop-color="#FFE0A8"/>
+<stop offset="1" stop-color="#FFDEA3"/>
+</radialGradient>
+<linearGradient id="paint1_linear_18_28477" x1="11.125" y1="26.75" x2="21.25" y2="26.75" gradientUnits="userSpaceOnUse">
+<stop stop-color="#C5987C"/>
+<stop offset="0.216049" stop-color="#CC8953"/>
+<stop offset="0.567901" stop-color="#EAA36E"/>
+<stop offset="1" stop-color="#FCBD73"/>
+</linearGradient>
+<linearGradient id="paint2_linear_18_28477" x1="16.1771" y1="22.6293" x2="16.1771" y2="29.988" gradientUnits="userSpaceOnUse">
+<stop offset="0.882728" stop-color="#C8746A" stop-opacity="0"/>
+<stop offset="1" stop-color="#BE6D82"/>
+</linearGradient>
+<linearGradient id="paint3_linear_18_28477" x1="10.9021" y1="25.75" x2="21.4321" y2="25.75" gradientUnits="userSpaceOnUse">
+<stop stop-color="#D4A789"/>
+<stop offset="0.424775" stop-color="#FAB88D"/>
+<stop offset="0.757159" stop-color="#FFF0C9"/>
+<stop offset="1" stop-color="#F9CF9A"/>
+</linearGradient>
+<linearGradient id="paint4_linear_18_28477" x1="16.1671" y1="22.6293" x2="16.1671" y2="29.988" gradientUnits="userSpaceOnUse">
+<stop offset="0.916702" stop-color="#E09A80" stop-opacity="0"/>
+<stop offset="1" stop-color="#CA7D95"/>
+</linearGradient>
+<linearGradient id="paint5_linear_18_28477" x1="16.25" y1="9.84375" x2="15.5977" y2="3.19204" gradientUnits="userSpaceOnUse">
+<stop offset="0.0275603" stop-color="#FFE4A8"/>
+<stop offset="0.143815" stop-color="#FFD59B"/>
+<stop offset="0.250725" stop-color="#FEDCAB"/>
+</linearGradient>
+<radialGradient id="paint6_radial_18_28477" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(20.9375 6.875) rotate(115.278) scale(2.48825 4.09612)">
+<stop stop-color="#FFF6BA"/>
+<stop offset="1" stop-color="#FFE5B1" stop-opacity="0"/>
+</radialGradient>
+<radialGradient id="paint7_radial_18_28477" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(17.1875 8.0625) rotate(82.6476) scale(5.86069 10.89)">
+<stop offset="0.45355" stop-color="#E0BB89" stop-opacity="0"/>
+<stop offset="0.662231" stop-color="#E6BE8D"/>
+</radialGradient>
+<radialGradient id="paint8_radial_18_28477" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(17.9063 4.46875) rotate(177.089) scale(3.69226 9.43834)">
+<stop offset="0.128265" stop-color="#FFF8BA"/>
+<stop offset="0.347135" stop-color="#FFE0AF"/>
+<stop offset="0.645989" stop-color="#FAD8A6" stop-opacity="0"/>
+</radialGradient>
+<linearGradient id="paint9_linear_18_28477" x1="16.9054" y1="13.754" x2="16.5214" y2="9.54913" gradientUnits="userSpaceOnUse">
+<stop offset="0.0571005" stop-color="#FFE4A8"/>
+<stop offset="0.157977" stop-color="#FFD59B"/>
+<stop offset="0.356286" stop-color="#FFDEA7"/>
+<stop offset="0.846352" stop-color="#FFE1A8"/>
+<stop offset="0.968133" stop-color="#FDD594"/>
+</linearGradient>
+<radialGradient id="paint10_radial_18_28477" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(20.4375 10.9375) rotate(174.189) scale(14.198 7.83563)">
+<stop offset="0.685205" stop-color="#FED29C" stop-opacity="0"/>
+<stop offset="0.780293" stop-color="#F9C994"/>
+<stop offset="0.880128" stop-color="#E4B783"/>
+</radialGradient>
+<radialGradient id="paint11_radial_18_28477" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(22.875 10.5625) rotate(123.275) scale(2.39221 3.65631)">
+<stop stop-color="#FFF8BD"/>
+<stop offset="1" stop-color="#FFE7B1" stop-opacity="0"/>
+</radialGradient>
+<radialGradient id="paint12_radial_18_28477" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(15.7821 16.8319) scale(7.523 7.58349)">
+<stop offset="0.00558659" stop-color="#FFF5D4"/>
+<stop offset="1" stop-color="#FFDEA3"/>
+</radialGradient>
+<linearGradient id="paint13_linear_18_28477" x1="9.39211" y1="21.625" x2="22.625" y2="21.625" gradientUnits="userSpaceOnUse">
+<stop stop-color="#D2AD8C"/>
+<stop offset="0.0884459" stop-color="#D19870"/>
+<stop offset="0.319877" stop-color="#EAA26B"/>
+<stop offset="0.744954" stop-color="#F8BA81"/>
+<stop offset="1" stop-color="#F8C589"/>
+</linearGradient>
+<linearGradient id="paint14_linear_18_28477" x1="16.2322" y1="19.202" x2="16.2322" y2="22.6294" gradientUnits="userSpaceOnUse">
+<stop offset="0.798134" stop-color="#DB9176" stop-opacity="0"/>
+<stop offset="0.89843" stop-color="#E19978"/>
+<stop offset="1" stop-color="#C97758"/>
+</linearGradient>
+<linearGradient id="paint15_linear_18_28477" x1="17" y1="20.9855" x2="16.5625" y2="14.2355" gradientUnits="userSpaceOnUse">
+<stop offset="0.029796" stop-color="#D89387"/>
+<stop offset="0.312982" stop-color="#F8C6A3"/>
+<stop offset="0.577315" stop-color="#FFDBA7"/>
+<stop offset="0.840851" stop-color="#FFE0A6"/>
+<stop offset="0.978024" stop-color="#FDD594"/>
+</linearGradient>
+<radialGradient id="paint16_radial_18_28477" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(18.5625 16.048) rotate(83.3255) scale(5.91509 13.7581)">
+<stop offset="0.720443" stop-color="#E1AE83" stop-opacity="0"/>
+<stop offset="1" stop-color="#E7B286"/>
+</radialGradient>
+<linearGradient id="paint17_linear_18_28477" x1="17" y1="20.375" x2="16.5625" y2="13.625" gradientUnits="userSpaceOnUse">
+<stop offset="0.029796" stop-color="#D89387"/>
+<stop offset="0.312982" stop-color="#F8C6A3"/>
+<stop offset="0.577315" stop-color="#FFDBA7"/>
+<stop offset="0.840851" stop-color="#FFE0A6"/>
+<stop offset="0.978024" stop-color="#FDD594"/>
+</linearGradient>
+<radialGradient id="paint18_radial_18_28477" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(18.5625 15.4375) rotate(83.3255) scale(5.91509 13.7581)">
+<stop offset="0.720443" stop-color="#E1AE83" stop-opacity="0"/>
+<stop offset="0.925198" stop-color="#E7B286"/>
+</radialGradient>
+<radialGradient id="paint19_radial_18_28477" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(24.3125 14.9375) rotate(113.929) scale(2.7736 3.64835)">
+<stop stop-color="#FFF8BD"/>
+<stop offset="1" stop-color="#FFEEB6" stop-opacity="0"/>
+</radialGradient>
+<radialGradient id="paint20_radial_18_28477" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(13.3125 20.8437) rotate(-5.0796) scale(1.41179 2.5081)">
+<stop offset="0.197452" stop-color="#FFD4A8"/>
+<stop offset="0.407382" stop-color="#FDC59F"/>
+<stop offset="0.831214" stop-color="#FBC49E" stop-opacity="0"/>
+</radialGradient>
+</defs>
+</svg>
diff --git a/packages/frontend/assets/drop-and-fusion/sweets_monos/verts/candy_color.svg b/packages/frontend/assets/drop-and-fusion/sweets_monos/verts/candy_color.svg
new file mode 100644
index 0000000000..e673f430f5
--- /dev/null
+++ b/packages/frontend/assets/drop-and-fusion/sweets_monos/verts/candy_color.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <path d="M12,1.918L4,4L2.016,12L6,13.375L6,18L8,22L12,25.372L16.008,26L19,25.372L20,30L28,27L30,20L25.473,19L26,15L24,10L20,7L16.008,6L13,6L12,1.918Z"/>
+</svg>
diff --git a/packages/frontend/assets/drop-and-fusion/sweets_monos/verts/chocolate_bar_color.svg b/packages/frontend/assets/drop-and-fusion/sweets_monos/verts/chocolate_bar_color.svg
new file mode 100644
index 0000000000..5cd39cc9e3
--- /dev/null
+++ b/packages/frontend/assets/drop-and-fusion/sweets_monos/verts/chocolate_bar_color.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="32px" height="32px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <path d="M12,2.034L2.035,12L2.035,14L18,30L20,30L29.982,20L29.982,18L14,2.034L12,2.034Z"/>
+</svg>
diff --git a/packages/frontend/assets/drop-and-fusion/sweets_monos/verts/custard_color.svg b/packages/frontend/assets/drop-and-fusion/sweets_monos/verts/custard_color.svg
new file mode 100644
index 0000000000..dd4870dd7d
--- /dev/null
+++ b/packages/frontend/assets/drop-and-fusion/sweets_monos/verts/custard_color.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="100%" height="100%" viewBox="0 0 32 32" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <path d="M9,15L23,15L30,27L25.7,30L6.34,30L2,27L9,15Z"/>
+</svg>
diff --git a/packages/frontend/assets/drop-and-fusion/sweets_monos/verts/doughnut_color.svg b/packages/frontend/assets/drop-and-fusion/sweets_monos/verts/doughnut_color.svg
new file mode 100644
index 0000000000..a8d5557f5c
--- /dev/null
+++ b/packages/frontend/assets/drop-and-fusion/sweets_monos/verts/doughnut_color.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="32px" height="32px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <path d="M15,2L11,3L8,5L7,6L5,9L4,12L4,20L5,23L7,26L11,29L14,30L18,30L21,29L25,26L27,23L28,20L28,12L27,9L25,6L24,5L21,3L17,2L15,2Z"/>
+</svg>
diff --git a/packages/frontend/assets/drop-and-fusion/sweets_monos/verts/lollipop_color.svg b/packages/frontend/assets/drop-and-fusion/sweets_monos/verts/lollipop_color.svg
new file mode 100644
index 0000000000..d3778737a9
--- /dev/null
+++ b/packages/frontend/assets/drop-and-fusion/sweets_monos/verts/lollipop_color.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="32px" height="32px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <path d="M12,2L7,3L3,7L2,12L3,16L5,19L8,21L12,22L18,21L27,30L30,30L30,27L21,18L22,14.25L22,11L21,8L19,5L16.5,3L12,2Z"/>
+</svg>
diff --git a/packages/frontend/assets/drop-and-fusion/sweets_monos/verts/pancakes_color.svg b/packages/frontend/assets/drop-and-fusion/sweets_monos/verts/pancakes_color.svg
new file mode 100644
index 0000000000..b1a1a322e0
--- /dev/null
+++ b/packages/frontend/assets/drop-and-fusion/sweets_monos/verts/pancakes_color.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="32px" height="32px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <path d="M15,2L14,3L8,4L6,5L4,8L4.011,15L2,19L2,22.36L3,25L5,28L10,30L22,30L27,28L29,25L30,22L30,19L27.989,15L27.989,8L26,5L24,4L18,3L17,2L15,2Z"/>
+</svg>
diff --git a/packages/frontend/assets/drop-and-fusion/sweets_monos/verts/shaved_ice_color.svg b/packages/frontend/assets/drop-and-fusion/sweets_monos/verts/shaved_ice_color.svg
new file mode 100644
index 0000000000..00872c7a0c
--- /dev/null
+++ b/packages/frontend/assets/drop-and-fusion/sweets_monos/verts/shaved_ice_color.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="32px" height="32px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <path d="M15,2L11,3L8,6L7,8L6,11L6,13L7,16L8,18L15,30L17,30L24,18L25,16L26,13L26,11L25,8L24,6L21,3L17,2L15,2Z"/>
+</svg>
diff --git a/packages/frontend/assets/drop-and-fusion/sweets_monos/verts/shortcake_color.svg b/packages/frontend/assets/drop-and-fusion/sweets_monos/verts/shortcake_color.svg
new file mode 100644
index 0000000000..e6ed1fbbf9
--- /dev/null
+++ b/packages/frontend/assets/drop-and-fusion/sweets_monos/verts/shortcake_color.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="32px" height="32px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <path d="M14,2L2,13L2,31L30,23L30,7L29,6L20,4L17,3L16,2L14,2Z"/>
+</svg>
diff --git a/packages/frontend/assets/drop-and-fusion/sweets_monos/verts/soft_ice_cream_color.svg b/packages/frontend/assets/drop-and-fusion/sweets_monos/verts/soft_ice_cream_color.svg
new file mode 100644
index 0000000000..b77e0c3655
--- /dev/null
+++ b/packages/frontend/assets/drop-and-fusion/sweets_monos/verts/soft_ice_cream_color.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg width="32px" height="32px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
+    <path d="M17,2.541L14.078,5.402L10,7L10,10.367L8,11L8,14L5.781,16.265L6.594,19.627L9.414,20.916L12,29.988L21,29.988L22.016,22.629L23.072,21.772L23.072,19.202L25.783,17.473L25.783,14.727L24,13.173L24,10.367L22,9.233L22.016,6.454L18,5L17,2.541Z"/>
+</svg>
diff --git a/packages/frontend/assets/drop-and-fusion/yen_monos/10000yen.png b/packages/frontend/assets/drop-and-fusion/yen_monos/10000yen.png
new file mode 100644
index 0000000000..bda777719d
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/yen_monos/10000yen.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/yen_monos/1000yen.png b/packages/frontend/assets/drop-and-fusion/yen_monos/1000yen.png
new file mode 100644
index 0000000000..4c462fb1f6
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/yen_monos/1000yen.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/yen_monos/100yen.png b/packages/frontend/assets/drop-and-fusion/yen_monos/100yen.png
new file mode 100644
index 0000000000..8911543af9
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/yen_monos/100yen.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/yen_monos/10yen.png b/packages/frontend/assets/drop-and-fusion/yen_monos/10yen.png
new file mode 100644
index 0000000000..041f773891
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/yen_monos/10yen.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/yen_monos/1yen.png b/packages/frontend/assets/drop-and-fusion/yen_monos/1yen.png
new file mode 100644
index 0000000000..cc6dcfd740
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/yen_monos/1yen.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/yen_monos/2000yen.png b/packages/frontend/assets/drop-and-fusion/yen_monos/2000yen.png
new file mode 100644
index 0000000000..6048b7c996
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/yen_monos/2000yen.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/yen_monos/5000yen.png b/packages/frontend/assets/drop-and-fusion/yen_monos/5000yen.png
new file mode 100644
index 0000000000..b0fe26db11
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/yen_monos/5000yen.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/yen_monos/500yen.png b/packages/frontend/assets/drop-and-fusion/yen_monos/500yen.png
new file mode 100644
index 0000000000..9e3d2b766b
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/yen_monos/500yen.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/yen_monos/50yen.png b/packages/frontend/assets/drop-and-fusion/yen_monos/50yen.png
new file mode 100644
index 0000000000..c8ef089972
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/yen_monos/50yen.png differ
diff --git a/packages/frontend/assets/drop-and-fusion/yen_monos/5yen.png b/packages/frontend/assets/drop-and-fusion/yen_monos/5yen.png
new file mode 100644
index 0000000000..b120bdca36
Binary files /dev/null and b/packages/frontend/assets/drop-and-fusion/yen_monos/5yen.png differ
diff --git a/packages/frontend/assets/oneko.gif b/packages/frontend/assets/oneko.gif
new file mode 100644
index 0000000000..a009c2cc19
Binary files /dev/null and b/packages/frontend/assets/oneko.gif differ
diff --git a/packages/frontend/assets/reversi/logo.png b/packages/frontend/assets/reversi/logo.png
new file mode 100644
index 0000000000..724a311ea1
Binary files /dev/null and b/packages/frontend/assets/reversi/logo.png differ
diff --git a/packages/frontend/assets/reversi/lose.mp3 b/packages/frontend/assets/reversi/lose.mp3
new file mode 100644
index 0000000000..b62d50baf7
Binary files /dev/null and b/packages/frontend/assets/reversi/lose.mp3 differ
diff --git a/packages/frontend/assets/reversi/matched.mp3 b/packages/frontend/assets/reversi/matched.mp3
new file mode 100644
index 0000000000..f26d07614e
Binary files /dev/null and b/packages/frontend/assets/reversi/matched.mp3 differ
diff --git a/packages/frontend/assets/reversi/put.mp3 b/packages/frontend/assets/reversi/put.mp3
new file mode 100644
index 0000000000..baa1b83195
Binary files /dev/null and b/packages/frontend/assets/reversi/put.mp3 differ
diff --git a/packages/frontend/assets/reversi/stone_b.png b/packages/frontend/assets/reversi/stone_b.png
new file mode 100644
index 0000000000..9e98455a3e
Binary files /dev/null and b/packages/frontend/assets/reversi/stone_b.png differ
diff --git a/packages/frontend/assets/reversi/stone_w.png b/packages/frontend/assets/reversi/stone_w.png
new file mode 100644
index 0000000000..f2bee593dc
Binary files /dev/null and b/packages/frontend/assets/reversi/stone_w.png differ
diff --git a/packages/frontend/assets/reversi/win.mp3 b/packages/frontend/assets/reversi/win.mp3
new file mode 100644
index 0000000000..25402ce2a6
Binary files /dev/null and b/packages/frontend/assets/reversi/win.mp3 differ
diff --git a/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.test.ts b/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.test.ts
index d78f054395..948cc8b2c9 100644
--- a/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.test.ts
+++ b/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.test.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.ts b/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.ts
index 68cdc0bc78..0ed2e14d2a 100644
--- a/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.ts
+++ b/packages/frontend/lib/rollup-plugin-unwind-css-module-class-name.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index 68aa501c84..72b26961c3 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -4,11 +4,11 @@
 	"type": "module",
 	"scripts": {
 		"watch": "vite",
-		"dev": "vite --config vite.config.local-dev.ts",
+		"dev": "vite --config vite.config.local-dev.ts --debug hmr",
 		"build": "vite build",
 		"storybook-dev": "nodemon --verbose --watch src --ext \"mdx,ts,vue\" --ignore \"*.stories.ts\" --exec \"pnpm build-storybook-pre && pnpm exec storybook dev -p 6006 --ci\"",
 		"build-storybook-pre": "(tsc -p .storybook || echo done.) && node .storybook/generate.js && node .storybook/preload-locale.js && node .storybook/preload-theme.js",
-		"build-storybook": "pnpm build-storybook-pre && storybook build",
+		"build-storybook": "pnpm build-storybook-pre && storybook build --webpack-stats-json storybook-static",
 		"chromatic": "chromatic",
 		"test": "vitest --run --globals",
 		"test-and-coverage": "vitest --run --coverage --globals",
@@ -19,120 +19,124 @@
 	"dependencies": {
 		"@discordapp/twemoji": "15.0.2",
 		"@github/webauthn-json": "2.1.1",
+		"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
+		"@misskey-dev/browser-image-resizer": "2024.1.0",
 		"@rollup/plugin-json": "6.1.0",
 		"@rollup/plugin-replace": "5.0.5",
 		"@rollup/pluginutils": "5.1.0",
-		"@sharkey/sfm-js": "0.24.3",
-		"@syuilo/aiscript": "0.16.0",
+		"@transfem-org/sfm-js": "0.24.4",
+		"@syuilo/aiscript": "0.17.0",
 		"@phosphor-icons/web": "^2.0.3",
 		"@twemoji/parser": "15.0.0",
-		"@vitejs/plugin-vue": "4.5.2",
-		"@vue/compiler-sfc": "3.3.12",
-		"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.0.6",
+		"@vitejs/plugin-vue": "5.0.4",
+		"@vue/compiler-sfc": "3.4.21",
+		"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.2",
 		"astring": "1.8.6",
 		"broadcast-channel": "7.0.0",
-		"browser-image-resizer": "github:misskey-dev/browser-image-resizer#v2.2.1-misskey.3",
 		"buraha": "0.0.1",
-		"canvas-confetti": "1.6.1",
-		"chart.js": "4.4.1",
+		"canvas-confetti": "1.9.2",
+		"chart.js": "4.4.2",
 		"chartjs-adapter-date-fns": "3.0.0",
 		"chartjs-chart-matrix": "2.0.1",
 		"chartjs-plugin-gradient": "0.6.1",
 		"chartjs-plugin-zoom": "2.0.1",
-		"chromatic": "10.1.0",
+		"chromatic": "11.0.0",
 		"compare-versions": "6.1.0",
 		"cropperjs": "2.0.0-beta.4",
 		"date-fns": "2.30.0",
 		"escape-regexp": "0.0.1",
 		"estree-walker": "3.0.3",
 		"eventemitter3": "5.0.1",
-		"gsap": "3.12.4",
 		"idb-keyval": "6.2.1",
 		"insert-text-at-cursor": "0.3.0",
 		"is-file-animated": "1.0.2",
 		"json5": "2.2.3",
 		"katex": "0.16.9",
 		"matter-js": "0.19.0",
+		"misskey-bubble-game": "workspace:*",
 		"misskey-js": "workspace:*",
+		"misskey-reversi": "workspace:*",
 		"photoswipe": "5.4.3",
 		"punycode": "2.3.1",
-		"rollup": "4.9.1",
-		"sanitize-html": "2.11.0",
-		"sass": "1.69.5",
-		"shiki": "0.14.7",
+		"rollup": "4.12.0",
+		"sanitize-html": "2.12.1",
+		"sass": "1.71.1",
+		"shiki": "1.1.7",
 		"strict-event-emitter-types": "2.0.0",
 		"textarea-caret": "3.1.0",
-		"three": "0.159.0",
+		"three": "0.162.0",
 		"throttle-debounce": "5.0.0",
 		"tinycolor2": "1.6.0",
 		"tsc-alias": "1.8.8",
 		"tsconfig-paths": "4.2.0",
 		"typescript": "5.3.3",
 		"uuid": "9.0.1",
-		"v-code-diff": "1.7.2",
-		"vite": "5.0.10",
-		"vue": "3.3.12",
+		"v-code-diff": "1.9.0",
+		"vite": "5.1.4",
+		"vue": "3.4.21",
 		"vuedraggable": "next"
 	},
 	"devDependencies": {
-		"@storybook/addon-actions": "7.6.5",
-		"@storybook/addon-essentials": "7.6.5",
-		"@storybook/addon-interactions": "7.6.5",
-		"@storybook/addon-links": "7.6.5",
-		"@storybook/addon-storysource": "7.6.5",
-		"@storybook/addons": "7.6.5",
-		"@storybook/blocks": "7.6.5",
-		"@storybook/core-events": "7.6.5",
-		"@storybook/jest": "0.2.3",
-		"@storybook/manager-api": "7.6.5",
-		"@storybook/preview-api": "7.6.5",
-		"@storybook/react": "7.6.5",
-		"@storybook/react-vite": "7.6.5",
-		"@storybook/testing-library": "0.2.2",
-		"@storybook/theming": "7.6.5",
-		"@storybook/types": "7.6.5",
-		"@storybook/vue3": "7.6.5",
-		"@storybook/vue3-vite": "7.6.5",
-		"@testing-library/vue": "8.0.1",
+		"@misskey-dev/eslint-plugin": "1.0.0",
+		"@misskey-dev/summaly": "5.0.3",
+		"@storybook/addon-actions": "8.0.0-beta.6",
+		"@storybook/addon-essentials": "8.0.0-beta.6",
+		"@storybook/addon-interactions": "8.0.0-beta.6",
+		"@storybook/addon-links": "8.0.0-beta.6",
+		"@storybook/addon-mdx-gfm": "8.0.0-beta.6",
+		"@storybook/addon-storysource": "8.0.0-beta.6",
+		"@storybook/blocks": "8.0.0-beta.6",
+		"@storybook/components": "8.0.0-beta.6",
+		"@storybook/core-events": "8.0.0-beta.6",
+		"@storybook/manager-api": "8.0.0-beta.6",
+		"@storybook/preview-api": "8.0.0-beta.6",
+		"@storybook/react": "8.0.0-beta.6",
+		"@storybook/react-vite": "8.0.0-beta.6",
+		"@storybook/test": "8.0.0-beta.6",
+		"@storybook/theming": "8.0.0-beta.6",
+		"@storybook/types": "8.0.0-beta.6",
+		"@storybook/vue3": "8.0.0-beta.6",
+		"@storybook/vue3-vite": "8.0.0-beta.6",
+		"@testing-library/vue": "8.0.2",
 		"@types/escape-regexp": "0.0.3",
 		"@types/estree": "1.0.5",
-		"@types/matter-js": "0.19.5",
+		"@types/matter-js": "0.19.6",
 		"@types/micromatch": "4.0.6",
-		"@types/node": "20.10.5",
-		"@types/punycode": "2.1.3",
-		"@types/sanitize-html": "2.9.5",
+		"@types/node": "20.11.22",
+		"@types/punycode": "2.1.4",
+		"@types/sanitize-html": "2.11.0",
 		"@types/throttle-debounce": "5.0.2",
 		"@types/tinycolor2": "1.4.6",
-		"@types/uuid": "9.0.7",
+		"@types/uuid": "9.0.8",
 		"@types/ws": "8.5.10",
-		"@typescript-eslint/eslint-plugin": "6.14.0",
-		"@typescript-eslint/parser": "6.14.0",
+		"@typescript-eslint/eslint-plugin": "7.1.0",
+		"@typescript-eslint/parser": "7.1.0",
 		"@vitest/coverage-v8": "0.34.6",
-		"@vue/runtime-core": "3.3.12",
-		"acorn": "8.11.2",
+		"@vue/runtime-core": "3.4.21",
+		"acorn": "8.11.3",
 		"cross-env": "7.0.3",
-		"cypress": "13.6.1",
-		"eslint": "8.56.0",
+		"cypress": "13.6.6",
+		"eslint": "8.57.0",
 		"eslint-plugin-import": "2.29.1",
-		"eslint-plugin-vue": "9.19.2",
+		"eslint-plugin-vue": "9.22.0",
 		"fast-glob": "3.3.2",
-		"happy-dom": "10.0.3",
+		"happy-dom": "13.6.2",
 		"intersection-observer": "0.12.2",
 		"micromatch": "4.0.5",
-		"msw": "1.3.2",
-		"msw-storybook-addon": "1.10.0",
-		"nodemon": "3.0.2",
-		"prettier": "3.1.1",
+		"msw": "2.1.7",
+		"msw-storybook-addon": "2.0.0-beta.1",
+		"nodemon": "3.1.0",
+		"prettier": "3.2.5",
 		"react": "18.2.0",
 		"react-dom": "18.2.0",
 		"start-server-and-test": "2.0.3",
-		"storybook": "7.6.5",
+		"storybook": "8.0.0-beta.6",
 		"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
-		"summaly": "github:misskey-dev/summaly",
 		"vite-plugin-turbosnap": "1.0.3",
 		"vitest": "0.34.6",
 		"vitest-fetch-mock": "0.2.2",
-		"vue-eslint-parser": "9.3.2",
-		"vue-tsc": "1.8.25"
+		"vue-component-type-helpers": "1.8.27",
+		"vue-eslint-parser": "9.4.2",
+		"vue-tsc": "1.8.27"
 	}
 }
diff --git a/packages/frontend/public/mockServiceWorker.js b/packages/frontend/public/mockServiceWorker.js
index 5384ce6b94..3bb1e66910 100644
--- a/packages/frontend/public/mockServiceWorker.js
+++ b/packages/frontend/public/mockServiceWorker.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/_boot_.ts b/packages/frontend/src/_boot_.ts
index efb78fe447..875353f8a4 100644
--- a/packages/frontend/src/_boot_.ts
+++ b/packages/frontend/src/_boot_.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/_dev_boot_.ts b/packages/frontend/src/_dev_boot_.ts
index d419ade527..09495dece4 100644
--- a/packages/frontend/src/_dev_boot_.ts
+++ b/packages/frontend/src/_dev_boot_.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/account.ts b/packages/frontend/src/account.ts
index 05008194f0..171826c9d8 100644
--- a/packages/frontend/src/account.ts
+++ b/packages/frontend/src/account.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -11,7 +11,8 @@ import { miLocalStorage } from '@/local-storage.js';
 import { MenuButton } from '@/types/menu.js';
 import { del, get, set } from '@/scripts/idb-proxy.js';
 import { apiUrl } from '@/config.js';
-import { waiting, api, popup, popupMenu, success, alert } from '@/os.js';
+import { waiting, popup, popupMenu, success, alert } from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { unisonReload, reloadChannel } from '@/scripts/unison-reload.js';
 
 // TODO: 他のタブと永続化されたstateを同期
@@ -23,9 +24,14 @@ const accountData = miLocalStorage.getItem('account');
 // TODO: 外部からはreadonlyに
 export const $i = accountData ? reactive(JSON.parse(accountData) as Account) : null;
 
-export const iAmModerator = $i != null && ($i.isAdmin || $i.isModerator);
+export const iAmModerator = $i != null && ($i.isAdmin === true || $i.isModerator === true);
 export const iAmAdmin = $i != null && $i.isAdmin;
 
+export function signinRequired() {
+	if ($i == null) throw new Error('signin required');
+	return $i;
+}
+
 export let notesCount = $i == null ? 0 : $i.notesCount;
 export function incNotesCount() {
 	notesCount++;
@@ -246,7 +252,7 @@ export async function openAccountMenu(opts: {
 	}
 
 	const storedAccounts = await getAccounts().then(accounts => accounts.filter(x => x.id !== $i.id));
-	const accountsPromise = api('users/show', { userIds: storedAccounts.map(x => x.id) });
+	const accountsPromise = misskeyApi('users/show', { userIds: storedAccounts.map(x => x.id) });
 
 	function createItem(account: Misskey.entities.UserDetailed) {
 		return {
@@ -284,7 +290,7 @@ export async function openAccountMenu(opts: {
 			text: i18n.ts.profile,
 			to: `/@${ $i.username }`,
 			avatar: $i,
-		}, { type: 'divider' }, ...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises, {
+		}, { type: 'divider' as const }, ...(opts.includeCurrentAccount ? [createItem($i)] : []), ...accountItemPromises, {
 			type: 'parent' as const,
 			icon: 'ph-plus ph-bold ph-lg',
 			text: i18n.ts.addAccount,
diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts
index 63f169d9ad..9694a5b627 100644
--- a/packages/frontend/src/boot/common.ts
+++ b/packages/frontend/src/boot/common.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -22,6 +22,7 @@ import { getAccountFromId } from '@/scripts/get-account-from-id.js';
 import { deckStore } from '@/ui/deck/deck-store.js';
 import { miLocalStorage } from '@/local-storage.js';
 import { fetchCustomEmojis } from '@/custom-emojis.js';
+import { setupRouter } from '@/router/definition.js';
 
 export async function common(createVue: () => App<Element>) {
 	console.info(`Sharkey v${version}`);
@@ -59,12 +60,6 @@ export async function common(createVue: () => App<Element>) {
 		});
 	}
 
-	const splash = document.getElementById('splash');
-	// 念のためnullチェック(HTMLが古い場合があるため(そのうち消す))
-	if (splash) splash.addEventListener('transitionend', () => {
-		splash.remove();
-	});
-
 	let isClientUpdated = false;
 
 	//#region クライアントが更新されたかチェック
@@ -245,6 +240,8 @@ export async function common(createVue: () => App<Element>) {
 
 	const app = createVue();
 
+	setupRouter(app);
+
 	if (_DEV_) {
 		app.config.performance = true;
 	}
@@ -290,5 +287,10 @@ function removeSplash() {
 	if (splash) {
 		splash.style.opacity = '0';
 		splash.style.pointerEvents = 'none';
+
+		// transitionendイベントが発火しない場合があるため
+		window.setTimeout(() => {
+			splash.remove();
+		}, 1000);
 	}
 }
diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts
index cdc1d11ca2..fbb4baebdc 100644
--- a/packages/frontend/src/boot/main-boot.ts
+++ b/packages/frontend/src/boot/main-boot.ts
@@ -1,25 +1,26 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { createApp, markRaw, defineAsyncComponent } from 'vue';
+import { createApp, defineAsyncComponent, markRaw } from 'vue';
 import { common } from './common.js';
 import { ui } from '@/config.js';
 import { i18n } from '@/i18n.js';
-import { confirm, alert, post, popup, toast } from '@/os.js';
+import { alert, confirm, popup, post, toast } from '@/os.js';
 import { useStream } from '@/stream.js';
 import * as sound from '@/scripts/sound.js';
-import { $i, updateAccount, signout } from '@/account.js';
-import { defaultStore, ColdDeviceStorage } from '@/store.js';
+import { $i, signout, updateAccount } from '@/account.js';
+import { instance } from '@/instance.js';
+import { ColdDeviceStorage, defaultStore } from '@/store.js';
 import { makeHotkey } from '@/scripts/hotkey.js';
 import { reactionPicker } from '@/scripts/reaction-picker.js';
 import { miLocalStorage } from '@/local-storage.js';
 import { claimAchievement, claimedAchievements } from '@/scripts/achievements.js';
-import { mainRouter } from '@/router.js';
 import { initializeSw } from '@/scripts/initialize-sw.js';
 import { deckStore } from '@/ui/deck/deck-store.js';
 import { emojiPicker } from '@/scripts/emoji-picker.js';
+import { mainRouter } from '@/router/main.js';
 
 export async function mainBoot() {
 	const { isClientUpdated } = await common(() => createApp(
@@ -75,9 +76,23 @@ export async function mainBoot() {
 
 	if (defaultStore.state.enableSeasonalScreenEffect) {
 		const month = new Date().getMonth() + 1;
-		if (month === 12 || month === 1) {
-			const SnowfallEffect = (await import('@/scripts/snowfall-effect.js')).SnowfallEffect;
-			new SnowfallEffect().render();
+		if (defaultStore.state.hemisphere === 'S') {
+			// ▼南半球
+			if (month === 7 || month === 8) {
+				const SnowfallEffect = (await import('@/scripts/snowfall-effect.js')).SnowfallEffect;
+				new SnowfallEffect({}).render();
+			}
+		} else {
+			// ▼北半球
+			if (month === 12 || month === 1) {
+				const SnowfallEffect = (await import('@/scripts/snowfall-effect.js')).SnowfallEffect;
+				new SnowfallEffect({}).render();
+			} else if (month === 3 || month === 4) {
+				const SakuraEffect = (await import('@/scripts/snowfall-effect.js')).SnowfallEffect;
+				new SakuraEffect({
+					sakura: true,
+				}).render();
+			}
 		}
 	}
 
@@ -203,7 +218,7 @@ export async function mainBoot() {
 			const lastUsedDate = parseInt(lastUsed, 10);
 			// 二時間以上前なら
 			if (Date.now() - lastUsedDate > 1000 * 60 * 60 * 2) {
-				toast(i18n.t('welcomeBackWithName', {
+				toast(i18n.tsx.welcomeBackWithName({
 					name: $i.name || $i.username,
 				}));
 			}
@@ -218,6 +233,11 @@ export async function mainBoot() {
 			}
 		}
 
+		const modifiedVersionMustProminentlyOfferInAgplV3Section13Read = miLocalStorage.getItem('modifiedVersionMustProminentlyOfferInAgplV3Section13Read');
+		if (modifiedVersionMustProminentlyOfferInAgplV3Section13Read !== 'true' && instance.repositoryUrl !== 'https://activitypub.software/TransFem-org/Sharkey/') {
+			popup(defineAsyncComponent(() => import('@/components/MkSourceCodeAvailablePopup.vue')), {}, {}, 'closed');
+		}
+
 		if ('Notification' in window) {
 			// 許可を得ていなかったらリクエスト
 			if (Notification.permission === 'default') {
@@ -269,7 +289,7 @@ export async function mainBoot() {
 
 		main.on('unreadAntenna', () => {
 			updateAccount({ hasUnreadAntenna: true });
-			sound.play('antenna');
+			sound.playMisskeySfx('antenna');
 		});
 
 		main.on('readAllAnnouncements', () => {
diff --git a/packages/frontend/src/boot/sub-boot.ts b/packages/frontend/src/boot/sub-boot.ts
index 92ee074afb..017457822b 100644
--- a/packages/frontend/src/boot/sub-boot.ts
+++ b/packages/frontend/src/boot/sub-boot.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/cache.ts b/packages/frontend/src/cache.ts
index 25d2b3c15f..b286528de6 100644
--- a/packages/frontend/src/cache.ts
+++ b/packages/frontend/src/cache.ts
@@ -1,13 +1,13 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import * as Misskey from 'misskey-js';
 import { Cache } from '@/scripts/cache.js';
-import { api } from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 
-export const clipsCache = new Cache<Misskey.entities.Clip[]>(1000 * 60 * 30, () => api('clips/list'));
-export const rolesCache = new Cache(1000 * 60 * 30, () => api('admin/roles/list'));
-export const userListsCache = new Cache<Misskey.entities.UserList[]>(1000 * 60 * 30, () => api('users/lists/list'));
-export const antennasCache = new Cache<Misskey.entities.Antenna[]>(1000 * 60 * 30, () => api('antennas/list'));
+export const clipsCache = new Cache<Misskey.entities.Clip[]>(1000 * 60 * 30, () => misskeyApi('clips/list'));
+export const rolesCache = new Cache(1000 * 60 * 30, () => misskeyApi('admin/roles/list'));
+export const userListsCache = new Cache<Misskey.entities.UserList[]>(1000 * 60 * 30, () => misskeyApi('users/lists/list'));
+export const antennasCache = new Cache<Misskey.entities.Antenna[]>(1000 * 60 * 30, () => misskeyApi('antennas/list'));
diff --git a/packages/frontend/src/components/MkAbuseReport.stories.impl.ts b/packages/frontend/src/components/MkAbuseReport.stories.impl.ts
index 77e7c84d5c..cf09c96fd4 100644
--- a/packages/frontend/src/components/MkAbuseReport.stories.impl.ts
+++ b/packages/frontend/src/components/MkAbuseReport.stories.impl.ts
@@ -1,12 +1,12 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
 import { action } from '@storybook/addon-actions';
 import { StoryObj } from '@storybook/vue3';
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
 import { abuseUserReport } from '../../.storybook/fakes.js';
 import { commonHandlers } from '../../.storybook/mocks.js';
 import MkAbuseReport from './MkAbuseReport.vue';
@@ -44,9 +44,9 @@ export const Default = {
 		msw: {
 			handlers: [
 				...commonHandlers,
-				rest.post('/api/admin/resolve-abuse-user-report', async (req, res, ctx) => {
-					action('POST /api/admin/resolve-abuse-user-report')(await req.json());
-					return res(ctx.json({}));
+				http.post('/api/admin/resolve-abuse-user-report', async ({ request }) => {
+					action('POST /api/admin/resolve-abuse-user-report')(await request.json());
+					return HttpResponse.json({});
 				}),
 			],
 		},
diff --git a/packages/frontend/src/components/MkAbuseReport.vue b/packages/frontend/src/components/MkAbuseReport.vue
index 611c8a1782..0493e885b9 100644
--- a/packages/frontend/src/components/MkAbuseReport.vue
+++ b/packages/frontend/src/components/MkAbuseReport.vue
@@ -1,12 +1,12 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
 <div class="bcekxzvu _margin _panel">
 	<div class="target">
-		<MkA v-user-preview="report.targetUserId" class="info" :to="`/admin/user/${report.targetUserId}`">
+		<MkA v-user-preview="report.targetUserId" class="info" :to="`/admin/user/${report.targetUserId}`" :behavior="'window'">
 			<MkAvatar class="avatar" :user="report.targetUser" indicator/>
 			<div class="names">
 				<MkUserName class="name" :user="report.targetUser"/>
@@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<Mfm :text="report.comment"/>
 		</div>
 		<hr/>
-		<div>{{ i18n.ts.reporter }}: <MkA :to="`/admin/user/${report.reporter.id}`" class="_link">@{{ report.reporter.username }}</MkA></div>
+		<div>{{ i18n.ts.reporter }}: <MkA :to="`/admin/user/${report.reporter.id}`" class="_link" :behavior="'window'">@{{ report.reporter.username }}</MkA></div>
 		<div v-if="report.assignee">
 			{{ i18n.ts.moderator }}:
 			<MkAcct :user="report.assignee"/>
diff --git a/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts b/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts
index dc842b3d1b..9df957f3ec 100644
--- a/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts
+++ b/packages/frontend/src/components/MkAbuseReportWindow.stories.impl.ts
@@ -1,12 +1,12 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
 import { action } from '@storybook/addon-actions';
 import { StoryObj } from '@storybook/vue3';
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
 import { userDetailed } from '../../.storybook/fakes.js';
 import { commonHandlers } from '../../.storybook/mocks.js';
 import MkAbuseReportWindow from './MkAbuseReportWindow.vue';
@@ -44,9 +44,9 @@ export const Default = {
 		msw: {
 			handlers: [
 				...commonHandlers,
-				rest.post('/api/users/report-abuse', async (req, res, ctx) => {
-					action('POST /api/users/report-abuse')(await req.json());
-					return res(ctx.json({}));
+				http.post('/api/users/report-abuse', async ({ request }) => {
+					action('POST /api/users/report-abuse')(await request.json());
+					return HttpResponse.json({});
 				}),
 			],
 		},
diff --git a/packages/frontend/src/components/MkAbuseReportWindow.vue b/packages/frontend/src/components/MkAbuseReportWindow.vue
index 6819630b74..f228df85a6 100644
--- a/packages/frontend/src/components/MkAbuseReportWindow.vue
+++ b/packages/frontend/src/components/MkAbuseReportWindow.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -39,7 +39,7 @@ import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
 
 const props = defineProps<{
-	user: Misskey.entities.User;
+	user: Misskey.entities.UserDetailed;
 	initialComment?: string;
 }>();
 
diff --git a/packages/frontend/src/components/MkAccountMoved.stories.impl.ts b/packages/frontend/src/components/MkAccountMoved.stories.impl.ts
index 33c6c24631..f1cfdc157a 100644
--- a/packages/frontend/src/components/MkAccountMoved.stories.impl.ts
+++ b/packages/frontend/src/components/MkAccountMoved.stories.impl.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/components/MkAccountMoved.vue b/packages/frontend/src/components/MkAccountMoved.vue
index b11cf1c8a0..83283a7073 100644
--- a/packages/frontend/src/components/MkAccountMoved.vue
+++ b/packages/frontend/src/components/MkAccountMoved.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -17,7 +17,7 @@ import * as Misskey from 'misskey-js';
 import MkMention from './MkMention.vue';
 import { i18n } from '@/i18n.js';
 import { host as localHost } from '@/config.js';
-import { api } from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 
 const user = ref<Misskey.entities.UserLite>();
 
@@ -25,7 +25,7 @@ const props = defineProps<{
 	movedTo: string; // user id
 }>();
 
-api('users/show', { userId: props.movedTo }).then(u => user.value = u);
+misskeyApi('users/show', { userId: props.movedTo }).then(u => user.value = u);
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/components/MkAchievements.stories.impl.ts b/packages/frontend/src/components/MkAchievements.stories.impl.ts
index 6d972467b1..7614da51da 100644
--- a/packages/frontend/src/components/MkAchievements.stories.impl.ts
+++ b/packages/frontend/src/components/MkAchievements.stories.impl.ts
@@ -1,11 +1,11 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
 import { StoryObj } from '@storybook/vue3';
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
 import { userDetailed } from '../../.storybook/fakes.js';
 import { commonHandlers } from '../../.storybook/mocks.js';
 import MkAchievements from './MkAchievements.vue';
@@ -39,8 +39,8 @@ export const Empty = {
 		msw: {
 			handlers: [
 				...commonHandlers,
-				rest.post('/api/users/achievements', (req, res, ctx) => {
-					return res(ctx.json([]));
+				http.post('/api/users/achievements', () => {
+					return HttpResponse.json([]);
 				}),
 			],
 		},
@@ -52,8 +52,8 @@ export const All = {
 		msw: {
 			handlers: [
 				...commonHandlers,
-				rest.post('/api/users/achievements', (req, res, ctx) => {
-					return res(ctx.json(ACHIEVEMENT_TYPES.map((name) => ({ name, unlockedAt: 0 }))));
+				http.post('/api/users/achievements', () => {
+					return HttpResponse.json(ACHIEVEMENT_TYPES.map((name) => ({ name, unlockedAt: 0 })));
 				}),
 			],
 		},
diff --git a/packages/frontend/src/components/MkAchievements.vue b/packages/frontend/src/components/MkAchievements.vue
index cdd9cb87b1..8ec3ec0505 100644
--- a/packages/frontend/src/components/MkAchievements.vue
+++ b/packages/frontend/src/components/MkAchievements.vue
@@ -1,12 +1,12 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
 <div>
 	<div v-if="achievements" :class="$style.root">
-		<div v-for="achievement in achievements" :key="achievement" :class="$style.achievement" class="_panel">
+		<div v-for="achievement in achievements" :key="achievement.name" :class="$style.achievement" class="_panel">
 			<div :class="$style.icon">
 				<div
 					:class="[$style.iconFrame, {
@@ -55,6 +55,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import * as Misskey from 'misskey-js';
 import { onMounted, ref, computed } from 'vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { ACHIEVEMENT_TYPES, ACHIEVEMENT_BADGES, claimAchievement } from '@/scripts/achievements.js';
 
@@ -71,7 +72,7 @@ const achievements = ref<Misskey.entities.UsersAchievementsResponse | null>(null
 const lockedAchievements = computed(() => ACHIEVEMENT_TYPES.filter(x => !(achievements.value ?? []).some(a => a.name === x)));
 
 function fetch() {
-	os.api('users/achievements', { userId: props.user.id }).then(res => {
+	misskeyApi('users/achievements', { userId: props.user.id }).then(res => {
 		achievements.value = [];
 		for (const t of ACHIEVEMENT_TYPES) {
 			const a = res.find(x => x.name === t);
@@ -120,8 +121,8 @@ onMounted(() => {
 
 .iconFrame {
 	position: relative;
-	width: 58px;
-	height: 58px;
+	width: var(--avatar);
+	height: var(--avatar);
 	padding: 6px;
 	border-radius: var(--radius-full);
 	box-sizing: border-box;
diff --git a/packages/frontend/src/components/MkAnalogClock.stories.impl.ts b/packages/frontend/src/components/MkAnalogClock.stories.impl.ts
index f87ad30f9b..270ca40825 100644
--- a/packages/frontend/src/components/MkAnalogClock.stories.impl.ts
+++ b/packages/frontend/src/components/MkAnalogClock.stories.impl.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/components/MkAnalogClock.vue b/packages/frontend/src/components/MkAnalogClock.vue
index 0e252f7b1d..835efbd6cd 100644
--- a/packages/frontend/src/components/MkAnalogClock.vue
+++ b/packages/frontend/src/components/MkAnalogClock.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkAnimBg.vue b/packages/frontend/src/components/MkAnimBg.vue
index 284ee8f3f8..4bf6125af5 100644
--- a/packages/frontend/src/components/MkAnimBg.vue
+++ b/packages/frontend/src/components/MkAnimBg.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts b/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts
index 42cfb90f7c..ffa4e56f5f 100644
--- a/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts
+++ b/packages/frontend/src/components/MkAnnouncementDialog.stories.impl.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/components/MkAnnouncementDialog.vue b/packages/frontend/src/components/MkAnnouncementDialog.vue
index 4c6e3e693a..74d0e7214f 100644
--- a/packages/frontend/src/components/MkAnnouncementDialog.vue
+++ b/packages/frontend/src/components/MkAnnouncementDialog.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -25,6 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { onMounted, shallowRef } from 'vue';
 import * as Misskey from 'misskey-js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import MkModal from '@/components/MkModal.vue';
 import MkButton from '@/components/MkButton.vue';
 import { i18n } from '@/i18n.js';
@@ -43,20 +44,20 @@ async function ok() {
 		const confirm = await os.confirm({
 			type: 'question',
 			title: i18n.ts._announcement.readConfirmTitle,
-			text: i18n.t('_announcement.readConfirmText', { title: props.announcement.title }),
+			text: i18n.tsx._announcement.readConfirmText({ title: props.announcement.title }),
 		});
 		if (confirm.canceled) return;
 	}
 
-	modal.value.close();
-	os.api('i/read-announcement', { announcementId: props.announcement.id });
+	modal.value?.close();
+	misskeyApi('i/read-announcement', { announcementId: props.announcement.id });
 	updateAccount({
 		unreadAnnouncements: $i!.unreadAnnouncements.filter(a => a.id !== props.announcement.id),
 	});
 }
 
 function onBgClick() {
-	rootEl.value.animate([{
+	rootEl.value?.animate([{
 		offset: 0,
 		transform: 'scale(1)',
 	}, {
diff --git a/packages/frontend/src/components/MkAsUi.stories.impl.ts b/packages/frontend/src/components/MkAsUi.stories.impl.ts
index 564fa902ba..cf8d5483b9 100644
--- a/packages/frontend/src/components/MkAsUi.stories.impl.ts
+++ b/packages/frontend/src/components/MkAsUi.stories.impl.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/components/MkAsUi.vue b/packages/frontend/src/components/MkAsUi.vue
index 8475233dfa..11f454daa2 100644
--- a/packages/frontend/src/components/MkAsUi.vue
+++ b/packages/frontend/src/components/MkAsUi.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -10,8 +10,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<MkAsUi v-if="!g(child).hidden" :component="g(child)" :components="props.components" :size="size"/>
 		</template>
 	</div>
-	<span v-else-if="c.type === 'text'" :class="{ [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace' }" :style="{ fontSize: c.size ? `${c.size * 100}%` : null, fontWeight: c.bold ? 'bold' : null, color: c.color ?? null }">{{ c.text }}</span>
-	<Mfm v-else-if="c.type === 'mfm'" :class="{ [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace' }" :style="{ fontSize: c.size ? `${c.size * 100}%` : null, fontWeight: c.bold ? 'bold' : null, color: c.color ?? null }" :text="c.text" @clickEv="c.onClickEv"/>
+	<span v-else-if="c.type === 'text'" :class="{ [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace' }" :style="{ fontSize: c.size ? `${c.size * 100}%` : undefined, fontWeight: c.bold ? 'bold' : undefined, color: c.color }">{{ c.text }}</span>
+	<Mfm v-else-if="c.type === 'mfm'" :class="{ [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace' }" :style="{ fontSize: c.size ? `${c.size * 100}%` : null, fontWeight: c.bold ? 'bold' : null, color: c.color ?? null }" :text="c.text ?? ''" @clickEv="c.onClickEv"/>
 	<MkButton v-else-if="c.type === 'button'" :primary="c.primary" :rounded="c.rounded" :disabled="c.disabled" :small="size === 'small'" inline @click="c.onClick">{{ c.text }}</MkButton>
 	<div v-else-if="c.type === 'buttons'" class="_buttons" :style="{ justifyContent: align }">
 		<MkButton v-for="button in c.buttons" :primary="button.primary" :rounded="button.rounded" :disabled="button.disabled" inline :small="size === 'small'" @click="button.onClick">{{ button.text }}</MkButton>
@@ -20,19 +20,19 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<template v-if="c.label" #label>{{ c.label }}</template>
 		<template v-if="c.caption" #caption>{{ c.caption }}</template>
 	</MkSwitch>
-	<MkTextarea v-else-if="c.type === 'textarea'" :modelValue="c.default" @update:modelValue="c.onInput">
+	<MkTextarea v-else-if="c.type === 'textarea'" :modelValue="c.default ?? null" @update:modelValue="c.onInput">
 		<template v-if="c.label" #label>{{ c.label }}</template>
 		<template v-if="c.caption" #caption>{{ c.caption }}</template>
 	</MkTextarea>
-	<MkInput v-else-if="c.type === 'textInput'" :small="size === 'small'" :modelValue="c.default" @update:modelValue="c.onInput">
+	<MkInput v-else-if="c.type === 'textInput'" :small="size === 'small'" :modelValue="c.default ?? null" @update:modelValue="c.onInput">
 		<template v-if="c.label" #label>{{ c.label }}</template>
 		<template v-if="c.caption" #caption>{{ c.caption }}</template>
 	</MkInput>
-	<MkInput v-else-if="c.type === 'numberInput'" :small="size === 'small'" :modelValue="c.default" type="number" @update:modelValue="c.onInput">
+	<MkInput v-else-if="c.type === 'numberInput'" :small="size === 'small'" :modelValue="c.default ?? null" type="number" @update:modelValue="c.onInput">
 		<template v-if="c.label" #label>{{ c.label }}</template>
 		<template v-if="c.caption" #caption>{{ c.caption }}</template>
 	</MkInput>
-	<MkSelect v-else-if="c.type === 'select'" :small="size === 'small'" :modelValue="c.default" @update:modelValue="c.onChange">
+	<MkSelect v-else-if="c.type === 'select'" :small="size === 'small'" :modelValue="c.default ?? null" @update:modelValue="c.onChange">
 		<template v-if="c.label" #label>{{ c.label }}</template>
 		<template v-if="c.caption" #caption>{{ c.caption }}</template>
 		<option v-for="item in c.items" :key="item.value" :value="item.value">{{ item.text }}</option>
@@ -42,8 +42,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<MkPostForm
 			fixed
 			:instant="true"
-			:initialText="c.form.text"
-			:initialCw="c.form.cw"
+			:initialText="c.form?.text"
+			:initialCw="c.form?.cw"
 		/>
 	</div>
 	<MkFolder v-else-if="c.type === 'folder'" :defaultOpen="c.opened">
@@ -52,7 +52,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<MkAsUi v-if="!g(child).hidden" :component="g(child)" :components="props.components" :size="size"/>
 		</template>
 	</MkFolder>
-	<div v-else-if="c.type === 'container'" :class="[$style.container, { [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace' }]" :style="{ textAlign: c.align ?? null, backgroundColor: c.bgColor ?? null, color: c.fgColor ?? null, borderWidth: c.borderWidth ? `${c.borderWidth}px` : 0, borderColor: c.borderColor ?? 'var(--divider)', padding: c.padding ? `${c.padding}px` : 0, borderRadius: c.rounded ? '8px' : 0 }">
+	<div v-else-if="c.type === 'container'" :class="[$style.container, { [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace' }]" :style="{ textAlign: c.align, backgroundColor: c.bgColor, color: c.fgColor, borderWidth: c.borderWidth ? `${c.borderWidth}px` : 0, borderColor: c.borderColor ?? 'var(--divider)', padding: c.padding ? `${c.padding}px` : 0, borderRadius: c.rounded ? '8px' : 0 }">
 		<template v-for="child in c.children" :key="child">
 			<MkAsUi v-if="!g(child).hidden" :component="g(child)" :components="props.components" :size="size" :align="c.align"/>
 		</template>
@@ -68,7 +68,7 @@ import MkInput from '@/components/MkInput.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
 import MkSelect from '@/components/MkSelect.vue';
-import { AsUiComponent } from '@/scripts/aiscript/ui.js';
+import { AsUiComponent, AsUiRoot, AsUiPostFormButton } from '@/scripts/aiscript/ui.js';
 import MkFolder from '@/components/MkFolder.vue';
 import MkPostForm from '@/components/MkPostForm.vue';
 
@@ -85,20 +85,32 @@ const props = withDefaults(defineProps<{
 const c = props.component;
 
 function g(id) {
-	return props.components.find(x => x.value.id === id).value;
+	const v = props.components.find(x => x.value.id === id)?.value;
+	if (v) return v;
+
+	return {
+		id: 'dummy',
+		type: 'root',
+		children: [],
+	} as AsUiRoot;
 }
 
-const valueForSwitch = ref(c.default ?? false);
+const valueForSwitch = ref('default' in c && typeof c.default === 'boolean' ? c.default : false);
 
 function onSwitchUpdate(v) {
 	valueForSwitch.value = v;
-	if (c.onChange) c.onChange(v);
+	if ('onChange' in c && c.onChange) {
+		c.onChange(v as never);
+	}
 }
 
 function openPostForm() {
+	const form = (c as AsUiPostFormButton).form;
+	if (!form) return;
+
 	os.post({
-		initialText: c.form.text,
-		initialCw: c.form.cw,
+		initialText: form.text,
+		initialCw: form.cw,
 		instant: true,
 	});
 }
diff --git a/packages/frontend/src/components/MkAutocomplete.stories.impl.ts b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts
index 969519386f..ec24b8c240 100644
--- a/packages/frontend/src/components/MkAutocomplete.stories.impl.ts
+++ b/packages/frontend/src/components/MkAutocomplete.stories.impl.ts
@@ -1,14 +1,13 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
 import { action } from '@storybook/addon-actions';
-import { expect } from '@storybook/jest';
-import { userEvent, waitFor, within } from '@storybook/testing-library';
+import { expect, userEvent, waitFor, within } from '@storybook/test';
 import { StoryObj } from '@storybook/vue3';
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
 import { userDetailed } from '../../.storybook/fakes.js';
 import { commonHandlers } from '../../.storybook/mocks.js';
 import MkAutocomplete from './MkAutocomplete.vue';
@@ -99,11 +98,11 @@ export const User = {
 		msw: {
 			handlers: [
 				...commonHandlers,
-				rest.post('/api/users/search-by-username-and-host', (req, res, ctx) => {
-					return res(ctx.json([
+				http.post('/api/users/search-by-username-and-host', () => {
+					return HttpResponse.json([
 						userDetailed('44', 'mizuki', 'misskey-hub.net', 'Mizuki'),
 						userDetailed('49', 'momoko', 'misskey-hub.net', 'Momoko'),
-					]));
+					]);
 				}),
 			],
 		},
@@ -132,12 +131,12 @@ export const Hashtag = {
 		msw: {
 			handlers: [
 				...commonHandlers,
-				rest.post('/api/hashtags/search', (req, res, ctx) => {
-					return res(ctx.json([
+				http.post('/api/hashtags/search', () => {
+					return HttpResponse.json([
 						'気象警報注意報',
 						'気象警報',
 						'気象情報',
-					]));
+					]);
 				}),
 			],
 		},
diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue
index 1f819cf601..8b665bfacd 100644
--- a/packages/frontend/src/components/MkAutocomplete.vue
+++ b/packages/frontend/src/components/MkAutocomplete.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	</ol>
 	<ol v-else-if="emojis.length > 0" ref="suggests" :class="$style.list">
 		<li v-for="emoji in emojis" :key="emoji.emoji" :class="$style.item" tabindex="-1" @click="complete(type, emoji.emoji)" @keydown="onKeydown">
-			<MkCustomEmoji v-if="'isCustomEmoji' in emoji && emoji.isCustomEmoji" :name="emoji.emoji" :class="$style.emoji"/>
+			<MkCustomEmoji v-if="'isCustomEmoji' in emoji && emoji.isCustomEmoji" :name="emoji.emoji" :class="$style.emoji" :fallbackToImage="true"/>
 			<MkEmoji v-else :emoji="emoji.emoji" :class="$style.emoji"/>
 			<!-- eslint-disable-next-line vue/no-v-html -->
 			<span v-if="q" :class="$style.emojiName" v-html="sanitizeHtml(emoji.name.replace(q, `<b>${q}</b>`))"></span>
@@ -35,6 +35,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<span>{{ tag }}</span>
 		</li>
 	</ol>
+	<ol v-else-if="mfmParams.length > 0" ref="suggests" :class="$style.list">
+		<li v-for="param in mfmParams" tabindex="-1" :class="$style.item" @click="complete(type, q.params.toSpliced(-1, 1, param).join(','))" @keydown="onKeydown">
+			<span>{{ param }}</span>
+		</li>
+	</ol>
 </div>
 </template>
 
@@ -42,33 +47,23 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { markRaw, ref, shallowRef, computed, onUpdated, onMounted, onBeforeUnmount, nextTick, watch } from 'vue';
 import sanitizeHtml from 'sanitize-html';
 import contains from '@/scripts/contains.js';
-import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@/scripts/emoji-base.js';
+import { char2twemojiFilePath, char2fluentEmojiFilePath, char2tossfaceFilePath } from '@/scripts/emoji-base.js';
 import { acct } from '@/filters/user.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { defaultStore } from '@/store.js';
 import { emojilist, getEmojiName } from '@/scripts/emojilist.js';
 import { i18n } from '@/i18n.js';
 import { miLocalStorage } from '@/local-storage.js';
 import { customEmojis } from '@/custom-emojis.js';
-import { MFM_TAGS } from '@/const.js';
-
-type EmojiDef = {
-	emoji: string;
-	name: string;
-	url: string;
-	aliasOf?: string;
-} | {
-	emoji: string;
-	name: string;
-	aliasOf?: string;
-	isCustomEmoji?: true;
-};
+import { MFM_TAGS, MFM_PARAMS } from '@/const.js';
+import { searchEmoji, EmojiDef } from '@/scripts/search-emoji.js';
 
 const lib = emojilist.filter(x => x.category !== 'flags');
 
 const emojiDb = computed(() => {
 	//#region Unicode Emoji
-	const char2path = defaultStore.reactiveState.emojiStyle.value === 'twemoji' ? char2twemojiFilePath : char2fluentEmojiFilePath;
+	const char2path = defaultStore.reactiveState.emojiStyle.value === 'twemoji' ? char2twemojiFilePath : defaultStore.reactiveState.emojiStyle.value === 'tossface' ? char2tossfaceFilePath : char2fluentEmojiFilePath;
 
 	const unicodeEmojiDB: EmojiDef[] = lib.map(x => ({
 		emoji: x.char,
@@ -82,7 +77,7 @@ const emojiDb = computed(() => {
 				unicodeEmojiDB.push({
 					emoji: emoji,
 					name: k,
-					aliasOf: getEmojiName(emoji)!,
+					aliasOf: getEmojiName(emoji),
 					url: char2path(emoji),
 				});
 			}
@@ -129,7 +124,7 @@ export default {
 <script lang="ts" setup>
 const props = defineProps<{
 	type: string;
-	q: string | null;
+	q: any;
 	textarea: HTMLTextAreaElement;
 	close: () => void;
 	x: number;
@@ -150,6 +145,7 @@ const hashtags = ref<any[]>([]);
 const emojis = ref<(EmojiDef)[]>([]);
 const items = ref<Element[] | HTMLCollection>([]);
 const mfmTags = ref<string[]>([]);
+const mfmParams = ref<string[]>([]);
 const select = ref(-1);
 const zIndex = os.claimZIndex('high');
 
@@ -201,7 +197,7 @@ function exec() {
 			users.value = JSON.parse(cache);
 			fetching.value = false;
 		} else {
-			os.api('users/search-by-username-and-host', {
+			misskeyApi('users/search-by-username-and-host', {
 				username: props.q,
 				limit: 10,
 				detail: false,
@@ -224,7 +220,7 @@ function exec() {
 				hashtags.value = hashtags;
 				fetching.value = false;
 			} else {
-				os.api('hashtags/search', {
+				misskeyApi('hashtags/search', {
 					query: props.q,
 					limit: 30,
 				}).then(searchedHashtags => {
@@ -242,7 +238,7 @@ function exec() {
 			return;
 		}
 
-		emojis.value = emojiAutoComplete(props.q.toLowerCase(), emojiDb.value);
+		emojis.value = searchEmoji(props.q.toLowerCase(), emojiDb.value);
 	} else if (props.type === 'mfmTag') {
 		if (!props.q || props.q === '') {
 			mfmTags.value = MFM_TAGS;
@@ -250,79 +246,14 @@ function exec() {
 		}
 
 		mfmTags.value = MFM_TAGS.filter(tag => tag.startsWith(props.q ?? ''));
-	}
-}
-
-type EmojiScore = { emoji: EmojiDef, score: number };
-
-function emojiAutoComplete(query: string | null, emojiDb: EmojiDef[], max = 30): EmojiDef[] {
-	if (!query) {
-		return [];
-	}
-
-	const matched = new Map<string, EmojiScore>();
-
-	// 前方一致(エイリアスなし)
-	emojiDb.some(x => {
-		if (x.name.toLowerCase().startsWith(query) && !x.aliasOf) {
-			matched.set(x.name, { emoji: x, score: query.length + 1 });
-		}
-		return matched.size === max;
-	});
-
-	// 前方一致(エイリアス込み)
-	if (matched.size < max) {
-		emojiDb.some(x => {
-			if (x.name.toLowerCase().startsWith(query) && !matched.has(x.aliasOf ?? x.name)) {
-				matched.set(x.aliasOf ?? x.name, { emoji: x, score: query.length });
-			}
-			return matched.size === max;
-		});
-	}
-
-	// 部分一致(エイリアス込み)
-	if (matched.size < max) {
-		emojiDb.some(x => {
-			if (x.name.toLowerCase().includes(query) && !matched.has(x.aliasOf ?? x.name)) {
-				matched.set(x.aliasOf ?? x.name, { emoji: x, score: query.length - 1 });
-			}
-			return matched.size === max;
-		});
-	}
-
-	// 簡易あいまい検索(3文字以上)
-	if (matched.size < max && query.length > 3) {
-		const queryChars = [...query];
-		const hitEmojis = new Map<string, EmojiScore>();
-
-		for (const x of emojiDb) {
-			// 文字列の位置を進めながら、クエリの文字を順番に探す
-
-			let pos = 0;
-			let hit = 0;
-			for (const c of queryChars) {
-				pos = x.name.toLowerCase().indexOf(c, pos);
-				if (pos <= -1) break;
-				hit++;
-			}
-
-			// 半分以上の文字が含まれていればヒットとする
-			if (hit > Math.ceil(queryChars.length / 2) && hit - 2 > (matched.get(x.aliasOf ?? x.name)?.score ?? 0)) {
-				hitEmojis.set(x.aliasOf ?? x.name, { emoji: x, score: hit - 2 });
-			}
+	} else if (props.type === 'mfmParam') {
+		if (props.q.params.at(-1) === '') {
+			mfmParams.value = MFM_PARAMS[props.q.tag] ?? [];
+			return;
 		}
 
-		// ヒットしたものを全部追加すると雑多になるので、先頭の6件程度だけにしておく(6件=オートコンプリートのポップアップのサイズ分)
-		[...hitEmojis.values()]
-			.sort((x, y) => y.score - x.score)
-			.slice(0, 6)
-			.forEach(it => matched.set(it.emoji.name, it));
+		mfmParams.value = MFM_PARAMS[props.q.tag].filter(param => param.startsWith(props.q.params.at(-1) ?? ''));
 	}
-
-	return [...matched.values()]
-		.sort((x, y) => y.score - x.score)
-		.slice(0, max)
-		.map(it => it.emoji);
 }
 
 function onMousedown(event: Event) {
@@ -408,7 +339,7 @@ function applySelect() {
 
 function chooseUser() {
 	props.close();
-	os.selectUser().then(user => {
+	os.selectUser({ includeSelf: true }).then(user => {
 		complete('user', user);
 		props.textarea.focus();
 	});
diff --git a/packages/frontend/src/components/MkAvatars.stories.impl.ts b/packages/frontend/src/components/MkAvatars.stories.impl.ts
index d41b64695f..d2a4a9f03b 100644
--- a/packages/frontend/src/components/MkAvatars.stories.impl.ts
+++ b/packages/frontend/src/components/MkAvatars.stories.impl.ts
@@ -1,11 +1,11 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
 import { StoryObj } from '@storybook/vue3';
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
 import { userDetailed } from '../../.storybook/fakes.js';
 import { commonHandlers } from '../../.storybook/mocks.js';
 import MkAvatars from './MkAvatars.vue';
@@ -38,12 +38,12 @@ export const Default = {
 		msw: {
 			handlers: [
 				...commonHandlers,
-				rest.post('/api/users/show', (req, res, ctx) => {
-					return res(ctx.json([
+				http.post('/api/users/show', () => {
+					return HttpResponse.json([
 						userDetailed('17'),
 						userDetailed('20'),
 						userDetailed('18'),
-					]));
+					]);
 				}),
 			],
 		},
diff --git a/packages/frontend/src/components/MkAvatars.vue b/packages/frontend/src/components/MkAvatars.vue
index 5644a324cf..8236d0ddb9 100644
--- a/packages/frontend/src/components/MkAvatars.vue
+++ b/packages/frontend/src/components/MkAvatars.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { onMounted, ref } from 'vue';
 import * as Misskey from 'misskey-js';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 
 const props = withDefaults(defineProps<{
 	userIds: string[];
@@ -27,7 +27,7 @@ const props = withDefaults(defineProps<{
 const users = ref<Misskey.entities.UserLite[]>([]);
 
 onMounted(async () => {
-	users.value = await os.api('users/show', {
+	users.value = await misskeyApi('users/show', {
 		userIds: props.userIds,
 	}) as unknown as Misskey.entities.UserLite[];
 });
diff --git a/packages/frontend/src/components/MkButton.stories.impl.ts b/packages/frontend/src/components/MkButton.stories.impl.ts
index e852557b12..e8802e4f8f 100644
--- a/packages/frontend/src/components/MkButton.stories.impl.ts
+++ b/packages/frontend/src/components/MkButton.stories.impl.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/components/MkButton.vue b/packages/frontend/src/components/MkButton.vue
index 9fcc49d3f0..c0f41b64d0 100644
--- a/packages/frontend/src/components/MkButton.vue
+++ b/packages/frontend/src/components/MkButton.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <MkA
 	v-else class="_button"
 	:class="[$style.root, { [$style.inline]: inline, [$style.primary]: primary, [$style.gradate]: gradate, [$style.danger]: danger, [$style.rounded]: rounded, [$style.full]: full, [$style.small]: small, [$style.large]: large, [$style.transparent]: transparent, [$style.asLike]: asLike }]"
-	:to="to"
+	:to="to ?? '#'"
 	@mousedown="onMousedown"
 >
 	<div ref="ripples" :class="$style.ripples" :data-children-class="$style.ripple"></div>
@@ -131,6 +131,10 @@ function onMousedown(evt: MouseEvent): void {
 	box-sizing: border-box;
 	transition: background 0.1s ease;
 
+	&:hover {
+		text-decoration: none;
+	}
+
 	&:not(:disabled):hover {
 		background: var(--buttonHoverBg);
 	}
diff --git a/packages/frontend/src/components/MkCaptcha.stories.impl.ts b/packages/frontend/src/components/MkCaptcha.stories.impl.ts
index fb50e50b18..475257cc45 100644
--- a/packages/frontend/src/components/MkCaptcha.stories.impl.ts
+++ b/packages/frontend/src/components/MkCaptcha.stories.impl.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/components/MkCaptcha.vue b/packages/frontend/src/components/MkCaptcha.vue
index 40bca11e64..c64bb47e77 100644
--- a/packages/frontend/src/components/MkCaptcha.vue
+++ b/packages/frontend/src/components/MkCaptcha.vue
@@ -1,19 +1,22 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
 <div>
-	<span v-if="!available">{{ i18n.ts.waiting }}<MkEllipsis/></span>
-	<div ref="captchaEl"></div>
+	<span v-if="!available">Loading<MkEllipsis/></span>
+	<div v-if="props.provider == 'mcaptcha'">
+		<div id="mcaptcha__widget-container" class="m-captcha-style"></div>
+		<div ref="captchaEl"></div>
+	</div>
+	<div v-else ref="captchaEl"></div>
 </div>
 </template>
 
 <script lang="ts" setup>
-import { ref, shallowRef, computed, onMounted, onBeforeUnmount, watch } from 'vue';
+import { ref, shallowRef, computed, onMounted, onBeforeUnmount, watch, onUnmounted } from 'vue';
 import { defaultStore } from '@/store.js';
-import { i18n } from '@/i18n.js';
 
 // APIs provided by Captcha services
 export type Captcha = {
@@ -26,7 +29,7 @@ export type Captcha = {
 	getResponse(id: string): string;
 };
 
-export type CaptchaProvider = 'hcaptcha' | 'recaptcha' | 'turnstile';
+export type CaptchaProvider = 'hcaptcha' | 'recaptcha' | 'turnstile' | 'mcaptcha';
 
 type CaptchaContainer = {
 	readonly [_ in CaptchaProvider]?: Captcha;
@@ -39,6 +42,7 @@ declare global {
 const props = defineProps<{
 	provider: CaptchaProvider;
 	sitekey: string | null; // null will show error on request
+	instanceUrl?: string | null;
 	modelValue?: string | null;
 }>();
 
@@ -55,6 +59,7 @@ const variable = computed(() => {
 		case 'hcaptcha': return 'hcaptcha';
 		case 'recaptcha': return 'grecaptcha';
 		case 'turnstile': return 'turnstile';
+		case 'mcaptcha': return 'mcaptcha';
 	}
 });
 
@@ -65,6 +70,7 @@ const src = computed(() => {
 		case 'hcaptcha': return 'https://js.hcaptcha.com/1/api.js?render=explicit&recaptchacompat=off';
 		case 'recaptcha': return 'https://www.recaptcha.net/recaptcha/api.js?render=explicit';
 		case 'turnstile': return 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit';
+		case 'mcaptcha': return null;
 	}
 });
 
@@ -72,9 +78,9 @@ const scriptId = computed(() => `script-${props.provider}`);
 
 const captcha = computed<Captcha>(() => window[variable.value] || {} as unknown as Captcha);
 
-if (loaded) {
+if (loaded || props.provider === 'mcaptcha') {
 	available.value = true;
-} else {
+} else if (src.value !== null) {
 	(document.getElementById(scriptId.value) ?? document.head.appendChild(Object.assign(document.createElement('script'), {
 		async: true,
 		id: scriptId.value,
@@ -87,7 +93,7 @@ function reset() {
 	if (captcha.value.reset) captcha.value.reset();
 }
 
-function requestRender() {
+async function requestRender() {
 	if (captcha.value.render && captchaEl.value instanceof Element) {
 		captcha.value.render(captchaEl.value, {
 			sitekey: props.sitekey,
@@ -96,6 +102,15 @@ function requestRender() {
 			'expired-callback': callback,
 			'error-callback': callback,
 		});
+	} else if (props.provider === 'mcaptcha' && props.instanceUrl && props.sitekey) {
+		const { default: Widget } = await import('@mcaptcha/vanilla-glue');
+		// @ts-expect-error avoid typecheck error
+		new Widget({
+			siteKey: {
+				instanceUrl: new URL(props.instanceUrl),
+				key: props.sitekey,
+			},
+		});
 	} else {
 		window.setTimeout(requestRender, 1);
 	}
@@ -105,14 +120,27 @@ function callback(response?: string) {
 	emit('update:modelValue', typeof response === 'string' ? response : null);
 }
 
+function onReceivedMessage(message: MessageEvent) {
+	if (message.data.token) {
+		if (props.instanceUrl && new URL(message.origin).host === new URL(props.instanceUrl).host) {
+			callback(message.data.token);
+		}
+	}
+}
+
 onMounted(() => {
 	if (available.value) {
+		window.addEventListener('message', onReceivedMessage);
 		requestRender();
 	} else {
 		watch(available, requestRender);
 	}
 });
 
+onUnmounted(() => {
+	window.removeEventListener('message', onReceivedMessage);
+});
+
 onBeforeUnmount(() => {
 	reset();
 });
diff --git a/packages/frontend/src/components/MkChannelFollowButton.vue b/packages/frontend/src/components/MkChannelFollowButton.vue
index 4a58204b5b..07732d9205 100644
--- a/packages/frontend/src/components/MkChannelFollowButton.vue
+++ b/packages/frontend/src/components/MkChannelFollowButton.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { ref } from 'vue';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 
 const props = withDefaults(defineProps<{
@@ -44,12 +44,12 @@ async function onClick() {
 
 	try {
 		if (isFollowing.value) {
-			await os.api('channels/unfollow', {
+			await misskeyApi('channels/unfollow', {
 				channelId: props.channel.id,
 			});
 			isFollowing.value = false;
 		} else {
-			await os.api('channels/follow', {
+			await misskeyApi('channels/follow', {
 				channelId: props.channel.id,
 			});
 			isFollowing.value = true;
diff --git a/packages/frontend/src/components/MkChannelList.vue b/packages/frontend/src/components/MkChannelList.vue
index 83d4401d2e..2850ecca16 100644
--- a/packages/frontend/src/components/MkChannelList.vue
+++ b/packages/frontend/src/components/MkChannelList.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkChannelPreview.vue b/packages/frontend/src/components/MkChannelPreview.vue
index f870b0eef1..1bac59d6df 100644
--- a/packages/frontend/src/components/MkChannelPreview.vue
+++ b/packages/frontend/src/components/MkChannelPreview.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					</I18n>
 				</div>
 				<div>
-					<i class="ph-pencil ph-bold ph-lg"></i>
+					<i class="ph-pencil-simple ph-bold ph-lg"></i>
 					<I18n :src="i18n.ts._channel.notesCount" tag="span" style="margin-left: 4px;">
 						<template #n>
 							<b>{{ channel.notesCount }}</b>
diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue
index adb3c134ae..04b6d2f29c 100644
--- a/packages/frontend/src/components/MkChart.vue
+++ b/packages/frontend/src/components/MkChart.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -21,13 +21,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 */
 import { onMounted, ref, shallowRef, watch, PropType } from 'vue';
 import { Chart } from 'chart.js';
-import gradient from 'chartjs-plugin-gradient';
-import * as os from '@/os.js';
+import { misskeyApiGet } from '@/scripts/misskey-api.js';
 import { defaultStore } from '@/store.js';
 import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
 import { chartVLine } from '@/scripts/chart-vline.js';
 import { alpha } from '@/scripts/color.js';
 import date from '@/filters/date.js';
+import bytes from '@/filters/bytes.js';
 import { initChart } from '@/scripts/init-chart.js';
 import { chartLegend } from '@/scripts/chart-legend.js';
 import MkChartLegend from '@/components/MkChartLegend.vue';
@@ -95,7 +95,7 @@ const getColor = (i) => {
 };
 
 const now = new Date();
-let chartInstance: Chart = null;
+let chartInstance: Chart | null = null;
 let chartData: {
 	series: {
 		name: string;
@@ -108,9 +108,10 @@ let chartData: {
 			y: number;
 		}[];
 	}[];
-} = null;
+	bytes?: boolean;
+} | null = null;
 
-const chartEl = shallowRef<HTMLCanvasElement>(null);
+const chartEl = shallowRef<HTMLCanvasElement | null>(null);
 const fetching = ref(true);
 
 const getDate = (ago: number) => {
@@ -132,6 +133,7 @@ const format = (arr) => {
 const { handler: externalTooltipHandler } = useChartTooltip();
 
 const render = () => {
+	if (chartData == null || chartEl.value == null) return;
 	if (chartInstance) {
 		chartInstance.destroy();
 	}
@@ -188,7 +190,6 @@ const render = () => {
 					stacked: props.stacked,
 					offset: false,
 					time: {
-						stepSize: 1,
 						unit: props.span === 'day' ? 'month' : 'day',
 						displayFormats: {
 							day: 'M/d',
@@ -198,6 +199,7 @@ const render = () => {
 					grid: {
 					},
 					ticks: {
+						stepSize: 1,
 						display: props.detailed,
 						maxRotation: 0,
 						autoSkipPadding: 16,
@@ -237,6 +239,9 @@ const render = () => {
 						duration: 0,
 					},
 					external: externalTooltipHandler,
+					callbacks: {
+						label: (item) => `${item.dataset.label}: ${chartData?.bytes ? bytes(item.parsed.y * 1000, 1) : item.parsed.y.toString()}`,
+					},
 				},
 				zoom: props.detailed ? {
 					pan: {
@@ -265,10 +270,9 @@ const render = () => {
 						},
 					},
 				} : undefined,
-				gradient,
 			},
 		},
-		plugins: [chartVLine(vLineColor), ...(props.detailed ? [chartLegend(legendEl.value)] : [])],
+		plugins: [chartVLine(vLineColor), ...(props.detailed && legendEl.value ? [chartLegend(legendEl.value)] : [])],
 	});
 };
 
@@ -277,7 +281,7 @@ const exportData = () => {
 };
 
 const fetchFederationChart = async (): Promise<typeof chartData> => {
-	const raw = await os.apiGet('charts/federation', { limit: props.limit, span: props.span });
+	const raw = await misskeyApiGet('charts/federation', { limit: props.limit, span: props.span });
 	return {
 		series: [{
 			name: 'Received',
@@ -327,7 +331,7 @@ const fetchFederationChart = async (): Promise<typeof chartData> => {
 };
 
 const fetchApRequestChart = async (): Promise<typeof chartData> => {
-	const raw = await os.apiGet('charts/ap-request', { limit: props.limit, span: props.span });
+	const raw = await misskeyApiGet('charts/ap-request', { limit: props.limit, span: props.span });
 	return {
 		series: [{
 			name: 'In',
@@ -349,7 +353,7 @@ const fetchApRequestChart = async (): Promise<typeof chartData> => {
 };
 
 const fetchNotesChart = async (type: string): Promise<typeof chartData> => {
-	const raw = await os.apiGet('charts/notes', { limit: props.limit, span: props.span });
+	const raw = await misskeyApiGet('charts/notes', { limit: props.limit, span: props.span });
 	return {
 		series: [{
 			name: 'All',
@@ -396,7 +400,7 @@ const fetchNotesChart = async (type: string): Promise<typeof chartData> => {
 };
 
 const fetchNotesTotalChart = async (): Promise<typeof chartData> => {
-	const raw = await os.apiGet('charts/notes', { limit: props.limit, span: props.span });
+	const raw = await misskeyApiGet('charts/notes', { limit: props.limit, span: props.span });
 	return {
 		series: [{
 			name: 'Combined',
@@ -415,7 +419,7 @@ const fetchNotesTotalChart = async (): Promise<typeof chartData> => {
 };
 
 const fetchUsersChart = async (total: boolean): Promise<typeof chartData> => {
-	const raw = await os.apiGet('charts/users', { limit: props.limit, span: props.span });
+	const raw = await misskeyApiGet('charts/users', { limit: props.limit, span: props.span });
 	return {
 		series: [{
 			name: 'Combined',
@@ -443,7 +447,7 @@ const fetchUsersChart = async (total: boolean): Promise<typeof chartData> => {
 };
 
 const fetchActiveUsersChart = async (): Promise<typeof chartData> => {
-	const raw = await os.apiGet('charts/active-users', { limit: props.limit, span: props.span });
+	const raw = await misskeyApiGet('charts/active-users', { limit: props.limit, span: props.span });
 	return {
 		series: [{
 			name: 'Read & Write',
@@ -495,7 +499,7 @@ const fetchActiveUsersChart = async (): Promise<typeof chartData> => {
 };
 
 const fetchDriveChart = async (): Promise<typeof chartData> => {
-	const raw = await os.apiGet('charts/drive', { limit: props.limit, span: props.span });
+	const raw = await misskeyApiGet('charts/drive', { limit: props.limit, span: props.span });
 	return {
 		bytes: true,
 		series: [{
@@ -531,7 +535,7 @@ const fetchDriveChart = async (): Promise<typeof chartData> => {
 };
 
 const fetchDriveFilesChart = async (): Promise<typeof chartData> => {
-	const raw = await os.apiGet('charts/drive', { limit: props.limit, span: props.span });
+	const raw = await misskeyApiGet('charts/drive', { limit: props.limit, span: props.span });
 	return {
 		series: [{
 			name: 'All',
@@ -566,7 +570,7 @@ const fetchDriveFilesChart = async (): Promise<typeof chartData> => {
 };
 
 const fetchInstanceRequestsChart = async (): Promise<typeof chartData> => {
-	const raw = await os.apiGet('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
+	const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span });
 	return {
 		series: [{
 			name: 'In',
@@ -588,7 +592,7 @@ const fetchInstanceRequestsChart = async (): Promise<typeof chartData> => {
 };
 
 const fetchInstanceUsersChart = async (total: boolean): Promise<typeof chartData> => {
-	const raw = await os.apiGet('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
+	const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span });
 	return {
 		series: [{
 			name: 'Users',
@@ -603,7 +607,7 @@ const fetchInstanceUsersChart = async (total: boolean): Promise<typeof chartData
 };
 
 const fetchInstanceNotesChart = async (total: boolean): Promise<typeof chartData> => {
-	const raw = await os.apiGet('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
+	const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span });
 	return {
 		series: [{
 			name: 'Notes',
@@ -618,7 +622,7 @@ const fetchInstanceNotesChart = async (total: boolean): Promise<typeof chartData
 };
 
 const fetchInstanceFfChart = async (total: boolean): Promise<typeof chartData> => {
-	const raw = await os.apiGet('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
+	const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span });
 	return {
 		series: [{
 			name: 'Following',
@@ -641,7 +645,7 @@ const fetchInstanceFfChart = async (total: boolean): Promise<typeof chartData> =
 };
 
 const fetchInstanceDriveUsageChart = async (total: boolean): Promise<typeof chartData> => {
-	const raw = await os.apiGet('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
+	const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span });
 	return {
 		bytes: true,
 		series: [{
@@ -649,7 +653,7 @@ const fetchInstanceDriveUsageChart = async (total: boolean): Promise<typeof char
 			type: 'area',
 			color: '#008FFB',
 			data: format(total
-				? raw.drive.totalUsage
+				? sum(raw.drive.incUsage)
 				: sum(raw.drive.incUsage, negate(raw.drive.decUsage)),
 			),
 		}],
@@ -657,7 +661,7 @@ const fetchInstanceDriveUsageChart = async (total: boolean): Promise<typeof char
 };
 
 const fetchInstanceDriveFilesChart = async (total: boolean): Promise<typeof chartData> => {
-	const raw = await os.apiGet('charts/instance', { host: props.args.host, limit: props.limit, span: props.span });
+	const raw = await misskeyApiGet('charts/instance', { host: props.args?.host, limit: props.limit, span: props.span });
 	return {
 		series: [{
 			name: 'Drive files',
@@ -672,11 +676,11 @@ const fetchInstanceDriveFilesChart = async (total: boolean): Promise<typeof char
 };
 
 const fetchPerUserNotesChart = async (): Promise<typeof chartData> => {
-	const raw = await os.apiGet('charts/user/notes', { userId: props.args.user.id, limit: props.limit, span: props.span });
+	const raw = await misskeyApiGet('charts/user/notes', { userId: props.args?.user?.id, limit: props.limit, span: props.span });
 	return {
-		series: [...(props.args.withoutAll ? [] : [{
+		series: [...(props.args?.withoutAll ? [] : [{
 			name: 'All',
-			type: 'line',
+			type: 'line' as const,
 			data: format(sum(raw.inc, negate(raw.dec))),
 			color: '#888888',
 		}]), {
@@ -704,7 +708,7 @@ const fetchPerUserNotesChart = async (): Promise<typeof chartData> => {
 };
 
 const fetchPerUserPvChart = async (): Promise<typeof chartData> => {
-	const raw = await os.apiGet('charts/user/pv', { userId: props.args.user.id, limit: props.limit, span: props.span });
+	const raw = await misskeyApiGet('charts/user/pv', { userId: props.args?.user?.id, limit: props.limit, span: props.span });
 	return {
 		series: [{
 			name: 'Unique PV (user)',
@@ -731,7 +735,7 @@ const fetchPerUserPvChart = async (): Promise<typeof chartData> => {
 };
 
 const fetchPerUserFollowingChart = async (): Promise<typeof chartData> => {
-	const raw = await os.apiGet('charts/user/following', { userId: props.args.user.id, limit: props.limit, span: props.span });
+	const raw = await misskeyApiGet('charts/user/following', { userId: props.args?.user?.id, limit: props.limit, span: props.span });
 	return {
 		series: [{
 			name: 'Local',
@@ -746,7 +750,7 @@ const fetchPerUserFollowingChart = async (): Promise<typeof chartData> => {
 };
 
 const fetchPerUserFollowersChart = async (): Promise<typeof chartData> => {
-	const raw = await os.apiGet('charts/user/following', { userId: props.args.user.id, limit: props.limit, span: props.span });
+	const raw = await misskeyApiGet('charts/user/following', { userId: props.args?.user?.id, limit: props.limit, span: props.span });
 	return {
 		series: [{
 			name: 'Local',
@@ -761,8 +765,9 @@ const fetchPerUserFollowersChart = async (): Promise<typeof chartData> => {
 };
 
 const fetchPerUserDriveChart = async (): Promise<typeof chartData> => {
-	const raw = await os.apiGet('charts/user/drive', { userId: props.args.user.id, limit: props.limit, span: props.span });
+	const raw = await misskeyApiGet('charts/user/drive', { userId: props.args?.user?.id, limit: props.limit, span: props.span });
 	return {
+		bytes: true,
 		series: [{
 			name: 'Inc',
 			type: 'area',
@@ -806,6 +811,8 @@ const fetchAndRender = async () => {
 			case 'per-user-following': return fetchPerUserFollowingChart();
 			case 'per-user-followers': return fetchPerUserFollowersChart();
 			case 'per-user-drive': return fetchPerUserDriveChart();
+
+			default: return null;
 		}
 	};
 	fetching.value = true;
diff --git a/packages/frontend/src/components/MkChartLegend.vue b/packages/frontend/src/components/MkChartLegend.vue
index c265fe6e97..240c9c919e 100644
--- a/packages/frontend/src/components/MkChartLegend.vue
+++ b/packages/frontend/src/components/MkChartLegend.vue
@@ -1,12 +1,12 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
 <div :class="$style.root">
 	<button v-for="item in items" class="_button item" :class="{ disabled: item.hidden }" @click="onClick(item)">
-		<span class="box" :style="{ background: chart.config.type === 'line' ? item.strokeStyle?.toString() : item.fillStyle?.toString() }"></span>
+		<span class="box" :style="{ background: type === 'line' ? item.strokeStyle?.toString() : item.fillStyle?.toString() }"></span>
 		{{ item.text }}
 	</button>
 </div>
@@ -16,25 +16,23 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { shallowRef } from 'vue';
 import { Chart, LegendItem } from 'chart.js';
 
-const props = defineProps({
-});
-
 const chart = shallowRef<Chart>();
+const type = shallowRef<string>();
 const items = shallowRef<LegendItem[]>([]);
 
 function update(_chart: Chart, _items: LegendItem[]) {
 	chart.value = _chart,
 	items.value = _items;
+	if ('type' in _chart.config) type.value = _chart.config.type;
 }
 
 function onClick(item: LegendItem) {
 	if (chart.value == null) return;
-	const { type } = chart.value.config;
-	if (type === 'pie' || type === 'doughnut') {
+	if (type.value === 'pie' || type.value === 'doughnut') {
 		// Pie and doughnut charts only have a single dataset and visibility is per item
-		chart.value.toggleDataVisibility(item.index);
+		if (item.index != null) chart.value.toggleDataVisibility(item.index);
 	} else {
-		chart.value.setDatasetVisibility(item.datasetIndex, !chart.value.isDatasetVisible(item.datasetIndex));
+		if (item.datasetIndex != null) chart.value.setDatasetVisibility(item.datasetIndex, !chart.value.isDatasetVisible(item.datasetIndex));
 	}
 	chart.value.update();
 }
diff --git a/packages/frontend/src/components/MkChartTooltip.vue b/packages/frontend/src/components/MkChartTooltip.vue
index c11f516e37..51081ede23 100644
--- a/packages/frontend/src/components/MkChartTooltip.vue
+++ b/packages/frontend/src/components/MkChartTooltip.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkClickerGame.vue b/packages/frontend/src/components/MkClickerGame.vue
index 1e72319010..892ad31b09 100644
--- a/packages/frontend/src/components/MkClickerGame.vue
+++ b/packages/frontend/src/components/MkClickerGame.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkClipPreview.vue b/packages/frontend/src/components/MkClipPreview.vue
index 2f6790fa49..c51ad4356d 100644
--- a/packages/frontend/src/components/MkClipPreview.vue
+++ b/packages/frontend/src/components/MkClipPreview.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkCode.core.vue b/packages/frontend/src/components/MkCode.core.vue
index 579c72b186..f9aaf4eff3 100644
--- a/packages/frontend/src/components/MkCode.core.vue
+++ b/packages/frontend/src/components/MkCode.core.vue
@@ -1,18 +1,19 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <!-- eslint-disable vue/no-v-html -->
 <template>
-<div :class="['codeBlockRoot', { 'codeEditor': codeEditor }]" v-html="html"></div>
+<div :class="[$style.codeBlockRoot, { [$style.codeEditor]: codeEditor }, (darkMode ? $style.dark : $style.light)]" v-html="html"></div>
 </template>
 
 <script lang="ts" setup>
 import { ref, computed, watch } from 'vue';
-import { BUNDLED_LANGUAGES } from 'shiki';
-import type { Lang as ShikiLang } from 'shiki';
-import { getHighlighter } from '@/scripts/code-highlighter.js';
+import { bundledLanguagesInfo } from 'shiki';
+import type { BuiltinLanguage } from 'shiki';
+import { getHighlighter, getTheme } from '@/scripts/code-highlighter.js';
+import { defaultStore } from '@/store.js';
 
 const props = defineProps<{
 	code: string;
@@ -21,25 +22,38 @@ const props = defineProps<{
 }>();
 
 const highlighter = await getHighlighter();
+const darkMode = defaultStore.reactiveState.darkMode;
+const codeLang = ref<BuiltinLanguage | 'aiscript'>('js');
+
+const [lightThemeName, darkThemeName] = await Promise.all([
+	getTheme('light', true),
+	getTheme('dark', true),
+]);
 
-const codeLang = ref<ShikiLang | 'aiscript'>('js');
 const html = computed(() => highlighter.codeToHtml(props.code, {
 	lang: codeLang.value,
-	theme: 'dark-plus',
+	themes: {
+		fallback: 'dark-plus',
+		light: lightThemeName,
+		dark: darkThemeName,
+	},
+	defaultColor: false,
+	cssVariablePrefix: '--shiki-',
 }));
 
 async function fetchLanguage(to: string): Promise<void> {
-	const language = to as ShikiLang;
+	const language = to as BuiltinLanguage;
 
 	// Check for the loaded languages, and load the language if it's not loaded yet.
 	if (!highlighter.getLoadedLanguages().includes(language)) {
 		// Check if the language is supported by Shiki
-		const bundles = BUNDLED_LANGUAGES.filter((bundle) => {
+		const bundles = bundledLanguagesInfo.filter((bundle) => {
 			// Languages are specified by their id, they can also have aliases (i. e. "js" and "javascript")
 			return bundle.id === language || bundle.aliases?.includes(language);
 		});
 		if (bundles.length > 0) {
-			await highlighter.loadLanguage(language);
+			if (_DEV_) console.log(`Loading language: ${language}`);
+			await highlighter.loadLanguage(bundles[0].import);
 			codeLang.value = language;
 		} else {
 			codeLang.value = 'js';
@@ -57,12 +71,37 @@ watch(() => props.lang, (to) => {
 }, { immediate: true });
 </script>
 
-<style scoped lang="scss">
-.codeBlockRoot :deep(.shiki) {
+<style module lang="scss">
+.codeBlockRoot :global(.shiki) > code {
+  counter-reset: step;
+  counter-increment: step 0;
+}
+
+.codeBlockRoot :global(.shiki) > code > .line::before {
+  content: counter(step);
+  counter-increment: step;
+  width: 1rem;
+  margin-right: 1.5rem;
+  display: inline-block;
+  text-align: right;
+  color: rgba(115,138,148,.4)
+}
+
+.codeBlockRoot :global(.shiki) {
 	padding: 1em;
 	margin: .5em 0;
 	overflow: auto;
 	border-radius: var(--radius-sm);
+	border: 1px solid var(--divider);
+	font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace;
+
+	color: var(--shiki-fallback);
+	background-color: var(--shiki-fallback-bg);
+
+	& span {
+		color: var(--shiki-fallback);
+		background-color: var(--shiki-fallback-bg);
+	}
 
 	& pre,
 	& code {
@@ -70,14 +109,35 @@ watch(() => props.lang, (to) => {
 	}
 }
 
+.light.codeBlockRoot :global(.shiki) {
+	color: var(--shiki-light);
+	background-color: var(--shiki-light-bg);
+
+	& span {
+		color: var(--shiki-light);
+		background-color: var(--shiki-light-bg);
+	}
+}
+
+.dark.codeBlockRoot :global(.shiki) {
+	color: var(--shiki-dark);
+	background-color: var(--shiki-dark-bg);
+
+	& span {
+		color: var(--shiki-dark);
+		background-color: var(--shiki-dark-bg);
+	}
+}
+
 .codeBlockRoot.codeEditor {
 	min-width: 100%;
 	height: 100%;
 
-	& :deep(.shiki) {
+	& :global(.shiki) {
 		padding: 12px;
 		margin: 0;
 		border-radius: var(--radius-sm);
+		border: none;
 		min-height: 130px;
 		pointer-events: none;
 		min-width: calc(100% - 24px);
@@ -89,6 +149,11 @@ watch(() => props.lang, (to) => {
 		text-rendering: inherit;
     text-transform: inherit;
     white-space: pre;
+
+		& span {
+			display: inline-block;
+			min-height: 1em;
+		}
 	}
 }
 </style>
diff --git a/packages/frontend/src/components/MkCode.vue b/packages/frontend/src/components/MkCode.vue
index e0973b676a..acd2ea6f97 100644
--- a/packages/frontend/src/components/MkCode.vue
+++ b/packages/frontend/src/components/MkCode.vue
@@ -1,58 +1,72 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<Suspense>
-	<template #fallback>
-		<MkLoading v-if="!inline ?? true"/>
-	</template>
-	<code v-if="inline" :class="$style.codeInlineRoot">{{ code }}</code>
-	<XCode v-else-if="show && lang" :code="code" :lang="lang"/>
-	<pre v-else-if="show" :class="$style.codeBlockFallbackRoot"><code :class="$style.codeBlockFallbackCode">{{ code }}</code></pre>
-	<button v-else :class="$style.codePlaceholderRoot" @click="show = true">
-		<div :class="$style.codePlaceholderContainer">
-			<div><i class="ph-code ph-bold ph-lg"></i> {{ i18n.ts.code }}</div>
-			<div>{{ i18n.ts.clickToShow }}</div>
-		</div>
+<div :class="$style.codeBlockRoot">
+	<button :class="$style.codeBlockCopyButton" class="_button" @click="copy">
+		<i class="ph-copy ph-bold ph-lg"></i>
 	</button>
-</Suspense>
+	<Suspense>
+		<template #fallback>
+			<MkLoading />
+		</template>
+		<XCode v-if="show && lang" :code="code" :lang="lang"/>
+		<pre v-else-if="show" :class="$style.codeBlockFallbackRoot"><code :class="$style.codeBlockFallbackCode">{{ code }}</code></pre>
+		<button v-else :class="$style.codePlaceholderRoot" @click="show = true">
+			<div :class="$style.codePlaceholderContainer">
+				<div><i class="ph-code ph-bold ph-lg"></i> {{ i18n.ts.code }}</div>
+				<div>{{ i18n.ts.clickToShow }}</div>
+			</div>
+		</button>
+	</Suspense>
+</div>
 </template>
 
 <script lang="ts" setup>
 import { defineAsyncComponent, ref } from 'vue';
+import * as os from '@/os.js';
 import MkLoading from '@/components/global/MkLoading.vue';
 import { defaultStore } from '@/store.js';
 import { i18n } from '@/i18n.js';
+import copyToClipboard from '@/scripts/copy-to-clipboard.js';
 
-defineProps<{
+const props = defineProps<{
 	code: string;
 	lang?: string;
-	inline?: boolean;
 }>();
 
 const show = ref(!defaultStore.state.dataSaver.code);
 
 const XCode = defineAsyncComponent(() => import('@/components/MkCode.core.vue'));
+
+function copy() {
+	copyToClipboard(props.code);
+	os.success();
+}
 </script>
 
 <style module lang="scss">
-.codeInlineRoot {
-	display: inline-block;
-	font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace;
-	overflow-wrap: anywhere;
-	color: #D4D4D4;
-	background: #1E1E1E;
-	padding: .1em;
-	border-radius: .3em;
+.codeBlockRoot {
+	position: relative;
+}
+
+.codeBlockCopyButton {
+	position: absolute;
+	top: 8px;
+	right: 8px;
+	opacity: 0.5;
+
+	&:hover {
+		opacity: 0.8;
+	}
 }
 
 .codeBlockFallbackRoot {
 	display: block;
 	overflow-wrap: anywhere;
-	color: #D4D4D4;
-	background: #1E1E1E;
+	background: var(--bg);
 	padding: 1em;
 	margin: .5em 0;
 	overflow: auto;
@@ -77,8 +91,8 @@ const XCode = defineAsyncComponent(() => import('@/components/MkCode.core.vue'))
 	border-radius: var(--radius-sm);
 	padding: 24px;
 	margin-top: 4px;
-	color: #D4D4D4;
-	background: #1E1E1E;
+	color: var(--fg);
+	background: var(--bg);
 }
 
 .codePlaceholderContainer {
diff --git a/packages/frontend/src/components/MkCodeEditor.vue b/packages/frontend/src/components/MkCodeEditor.vue
index 0ec69a69af..30e518f8f0 100644
--- a/packages/frontend/src/components/MkCodeEditor.vue
+++ b/packages/frontend/src/components/MkCodeEditor.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<div :class="$style.codeEditorScroller">
 			<textarea
 				ref="inputEl"
-				v-model="vModel"
+				v-model="v"
 				:class="[$style.textarea]"
 				:disabled="disabled"
 				:required="required"
@@ -58,7 +58,6 @@ const emit = defineEmits<{
 }>();
 
 const { modelValue } = toRefs(props);
-const vModel = ref<string>(modelValue.value ?? '');
 const v = ref<string>(modelValue.value ?? '');
 const focused = ref(false);
 const changed = ref(false);
@@ -79,15 +78,14 @@ const onKeydown = (ev: KeyboardEvent) => {
 
 	if (ev.code === 'Enter') {
 		const pos = inputEl.value?.selectionStart ?? 0;
-		const posEnd = inputEl.value?.selectionEnd ?? vModel.value.length;
+		const posEnd = inputEl.value?.selectionEnd ?? v.value.length;
 		if (pos === posEnd) {
-			const lines = vModel.value.slice(0, pos).split('\n');
+			const lines = v.value.slice(0, pos).split('\n');
 			const currentLine = lines[lines.length - 1];
 			const currentLineSpaces = currentLine.match(/^\s+/);
 			const posDelta = currentLineSpaces ? currentLineSpaces[0].length : 0;
 			ev.preventDefault();
-			vModel.value = vModel.value.slice(0, pos) + '\n' + (currentLineSpaces ? currentLineSpaces[0] : '') + vModel.value.slice(pos);
-			v.value = vModel.value;
+			v.value = v.value.slice(0, pos) + '\n' + (currentLineSpaces ? currentLineSpaces[0] : '') + v.value.slice(pos);
 			nextTick(() => {
 				inputEl.value?.setSelectionRange(pos + 1 + posDelta, pos + 1 + posDelta);
 			});
@@ -97,9 +95,8 @@ const onKeydown = (ev: KeyboardEvent) => {
 
 	if (ev.key === 'Tab') {
 		const pos = inputEl.value?.selectionStart ?? 0;
-		const posEnd = inputEl.value?.selectionEnd ?? vModel.value.length;
-		vModel.value = vModel.value.slice(0, pos) + '\t' + vModel.value.slice(posEnd);
-		v.value = vModel.value;
+		const posEnd = inputEl.value?.selectionEnd ?? v.value.length;
+		v.value = v.value.slice(0, pos) + '\t' + v.value.slice(posEnd);
 		nextTick(() => {
 			inputEl.value?.setSelectionRange(pos + 1, pos + 1);
 		});
@@ -199,20 +196,23 @@ watch(v, newValue => {
 	resize: none;
 	text-align: left;
 	color: transparent;
-	caret-color: rgb(225, 228, 232);
+	caret-color: var(--fg);
 	background-color: transparent;
 	border: 0;
 	border-radius: var(--radius-sm);
+	box-sizing: border-box;
 	outline: 0;
 	min-width: calc(100% - 24px);
 	height: 100%;
 	padding: 12px;
+	// the +2.5 rem is because of the line numbers
+	padding-left: calc(12px + 2.5rem);
 	line-height: 1.5em;
 	font-size: 1em;
 	font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace;
 }
 
 .textarea::selection {
-	color: #fff;
+	color: var(--bg);
 }
 </style>
diff --git a/packages/frontend/src/components/MkCodeInline.vue b/packages/frontend/src/components/MkCodeInline.vue
new file mode 100644
index 0000000000..6add80d1bc
--- /dev/null
+++ b/packages/frontend/src/components/MkCodeInline.vue
@@ -0,0 +1,25 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<code :class="$style.root">{{ code }}</code>
+</template>
+
+<script lang="ts" setup>
+const props = defineProps<{
+	code: string;
+}>();
+</script>
+
+<style module lang="scss">
+.root {
+	display: inline-block;
+	font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace;
+	overflow-wrap: anywhere;
+	background: var(--bg);
+	padding: .1em;
+	border-radius: .3em;
+}
+</style>
diff --git a/packages/frontend/src/components/MkColorInput.vue b/packages/frontend/src/components/MkColorInput.vue
index 4f15e88951..99aa46d561 100644
--- a/packages/frontend/src/components/MkColorInput.vue
+++ b/packages/frontend/src/components/MkColorInput.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -41,8 +41,8 @@ const { modelValue } = toRefs(props);
 const v = ref(modelValue.value);
 const inputEl = shallowRef<HTMLElement>();
 
-const onInput = (ev: KeyboardEvent) => {
-	emit('update:modelValue', v.value);
+const onInput = () => {
+	emit('update:modelValue', v.value ?? '');
 };
 </script>
 
diff --git a/packages/frontend/src/components/MkContainer.vue b/packages/frontend/src/components/MkContainer.vue
index 42c6cc1075..95188c335e 100644
--- a/packages/frontend/src/components/MkContainer.vue
+++ b/packages/frontend/src/components/MkContainer.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkContextMenu.vue b/packages/frontend/src/components/MkContextMenu.vue
index e29cf472f7..5ca3c77fb2 100644
--- a/packages/frontend/src/components/MkContextMenu.vue
+++ b/packages/frontend/src/components/MkContextMenu.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -44,8 +44,8 @@ onMounted(() => {
 	let left = props.ev.pageX + 1; // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1
 	let top = props.ev.pageY + 1; // 間違って右ダブルクリックした場合に意図せずアイテムがクリックされるのを防ぐため + 1
 
-	const width = rootEl.value.offsetWidth;
-	const height = rootEl.value.offsetHeight;
+	const width = rootEl.value!.offsetWidth;
+	const height = rootEl.value!.offsetHeight;
 
 	if (left + width - window.pageXOffset >= (window.innerWidth - SCROLLBAR_THICKNESS)) {
 		left = (window.innerWidth - SCROLLBAR_THICKNESS) - width + window.pageXOffset;
@@ -63,8 +63,10 @@ onMounted(() => {
 		left = 0;
 	}
 
-	rootEl.value.style.top = `${top}px`;
-	rootEl.value.style.left = `${left}px`;
+	if (rootEl.value) {
+		rootEl.value.style.top = `${top}px`;
+		rootEl.value.style.left = `${left}px`;
+	}
 
 	document.body.addEventListener('mousedown', onMousedown);
 });
diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue
index 0a1ddd3171..54f6f39c9d 100644
--- a/packages/frontend/src/components/MkCropperDialog.vue
+++ b/packages/frontend/src/components/MkCropperDialog.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -63,18 +63,25 @@ const loading = ref(true);
 
 const ok = async () => {
 	const promise = new Promise<Misskey.entities.DriveFile>(async (res) => {
-		const croppedCanvas = await cropper?.getCropperSelection()?.$toCanvas();
+		const croppedImage = await cropper?.getCropperImage();
+		const croppedSection = await cropper?.getCropperSelection();
+
+		// 拡大率を計算し、(ほぼ)元の大きさに戻す
+		const zoomedRate = croppedImage.getBoundingClientRect().width / croppedImage.clientWidth;
+		const widthToRender = croppedSection.getBoundingClientRect().width / zoomedRate;
+
+		const croppedCanvas = await croppedSection?.$toCanvas({ width: widthToRender });
 		croppedCanvas?.toBlob(blob => {
 			if (!blob) return;
 			const formData = new FormData();
 			formData.append('file', blob);
 			formData.append('name', `cropped_${props.file.name}`);
 			formData.append('isSensitive', props.file.isSensitive ? 'true' : 'false');
-			formData.append('comment', props.file.comment ?? 'null');
+			if (props.file.comment) { formData.append('comment', props.file.comment);}
 			formData.append('i', $i!.token);
-			if (props.uploadFolder || props.uploadFolder === null) {
-				formData.append('folderId', props.uploadFolder ?? 'null');
-			} else if (defaultStore.state.uploadFolder) {
+			if (props.uploadFolder) {
+				formData.append('folderId', props.uploadFolder);
+			} else if (props.uploadFolder !== null && defaultStore.state.uploadFolder) {
 				formData.append('folderId', defaultStore.state.uploadFolder);
 			}
 
diff --git a/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue b/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue
new file mode 100644
index 0000000000..84b5375a41
--- /dev/null
+++ b/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue
@@ -0,0 +1,104 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+  <MkModalWindow ref="dialogEl" @close="cancel()" @closed="$emit('closed')">
+    <template #header>:{{ emoji.name }}:</template>
+    <template #default>
+      <MkSpacer>
+        <div style="display: flex; flex-direction: column; gap: 1em;">
+          <div :class="$style.emojiImgWrapper">
+            <MkCustomEmoji :name="emoji.name" :normal="true" :useOriginalSize="true" style="height: 100%;"></MkCustomEmoji>
+          </div>
+          <MkKeyValue :copy="`:${emoji.name}:`">
+            <template #key>{{ i18n.ts.name }}</template>
+            <template #value>{{ emoji.name }}</template>
+          </MkKeyValue>
+          <MkKeyValue>
+            <template #key>{{ i18n.ts.tags }}</template>
+            <template #value>
+              <div v-if="emoji.aliases.length === 0">{{ i18n.ts.none }}</div>
+              <div v-else :class="$style.aliases">
+                <span v-for="alias in emoji.aliases" :key="alias" :class="$style.alias">
+                  {{ alias }}
+                </span>
+              </div>
+            </template>
+          </MkKeyValue>
+          <MkKeyValue>
+            <template #key>{{ i18n.ts.category }}</template>
+            <template #value>{{ emoji.category ?? i18n.ts.none }}</template>
+          </MkKeyValue>
+          <MkKeyValue>
+            <template #key>{{ i18n.ts.sensitive }}</template>
+            <template #value>{{ emoji.isSensitive ? i18n.ts.yes : i18n.ts.no }}</template>
+          </MkKeyValue>
+          <MkKeyValue>
+            <template #key>{{ i18n.ts.localOnly }}</template>
+            <template #value>{{ emoji.localOnly ? i18n.ts.yes : i18n.ts.no }}</template>
+          </MkKeyValue>
+          <MkKeyValue>
+            <template #key>{{ i18n.ts.license }}</template>
+            <template #value><Mfm :text="emoji.license ?? i18n.ts.none" /></template>
+          </MkKeyValue>
+          <MkKeyValue :copy="emoji.url">
+            <template #key>{{ i18n.ts.emojiUrl }}</template>
+            <template #value>
+              <MkLink :url="emoji.url" target="_blank">{{ emoji.url }}</MkLink>
+            </template>
+          </MkKeyValue>
+        </div>
+      </MkSpacer>
+    </template>
+  </MkModalWindow>
+</template>
+
+<script lang="ts" setup>
+import * as Misskey from 'misskey-js';
+import { defineProps, shallowRef } from 'vue';
+import { i18n } from '@/i18n.js';
+import MkModalWindow from '@/components/MkModalWindow.vue';
+import MkKeyValue from '@/components/MkKeyValue.vue';
+import MkLink from './MkLink.vue';
+const props = defineProps<{
+  emoji: Misskey.entities.EmojiDetailed,
+}>();
+const emit = defineEmits<{
+	(ev: 'ok', cropped: Misskey.entities.DriveFile): void;
+	(ev: 'cancel'): void;
+	(ev: 'closed'): void;
+}>();
+const dialogEl = shallowRef<InstanceType<typeof MkModalWindow>>();
+const cancel = () => {
+	emit('cancel');
+	dialogEl.value!.close();
+};
+</script>
+
+<style lang="scss" module>
+.emojiImgWrapper {
+  max-width: 100%;
+  height: 40cqh;
+  background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, var(--X5) 8px, var(--X5) 14px);
+  border-radius: var(--radius);
+  margin: auto;
+  overflow-y: hidden;
+}
+
+.aliases {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 3px;
+}
+
+.alias {
+  display: inline-block;
+  word-break: break-all;
+  padding: 3px 10px;
+  background-color: var(--X5);
+  border: solid 1px var(--divider);
+  border-radius: var(--radius);
+}
+</style>
diff --git a/packages/frontend/src/components/MkCwButton.vue b/packages/frontend/src/components/MkCwButton.vue
index 4a6d2dfba2..a2cb3185f4 100644
--- a/packages/frontend/src/components/MkCwButton.vue
+++ b/packages/frontend/src/components/MkCwButton.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -10,6 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { computed } from 'vue';
 import * as Misskey from 'misskey-js';
+import type { PollEditorModelValue } from '@/components/MkPollEditor.vue';
 import { concat } from '@/scripts/array.js';
 import { i18n } from '@/i18n.js';
 import MkButton from '@/components/MkButton.vue';
@@ -17,22 +18,9 @@ import MkButton from '@/components/MkButton.vue';
 const props = defineProps<{
 	modelValue: boolean;
 	text: string | null;
-	renote: Misskey.entities.Note | null;
-	files: Misskey.entities.DriveFile[];
-	poll?: {
-		expiresAt: string | null;
-		multiple: boolean;
-		choices: {
-			isVoted: boolean;
-			text: string;
-			votes: number;
-		}[];
-	} | {
-		choices: string[];
-		multiple: boolean;
-		expiresAt: string | null;
-		expiredAfter: string | null;
-	};
+	renote?: Misskey.entities.Note | null;
+	files?: Misskey.entities.DriveFile[];
+	poll?: Misskey.entities.Note['poll'] | PollEditorModelValue | null;
 }>();
 
 const emit = defineEmits<{
@@ -41,9 +29,9 @@ const emit = defineEmits<{
 
 const label = computed(() => {
 	return concat([
-		props.text ? [i18n.t('_cw.chars', { count: props.text.length })] : [],
+		props.text ? [i18n.tsx._cw.chars({ count: props.text.length })] : [],
 		props.renote ? [i18n.ts.quote] : [],
-		props.files.length !== 0 ? [i18n.t('_cw.files', { count: props.files.length })] : [],
+		props.files && props.files.length !== 0 ? [i18n.tsx._cw.files({ count: props.files.length })] : [],
 		props.poll != null ? [i18n.ts.poll] : [],
 	] as string[][]).join(' / ');
 });
diff --git a/packages/frontend/src/components/MkDateSeparatedList.vue b/packages/frontend/src/components/MkDateSeparatedList.vue
index b45aef45ff..475fbcb397 100644
--- a/packages/frontend/src/components/MkDateSeparatedList.vue
+++ b/packages/frontend/src/components/MkDateSeparatedList.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -46,7 +46,7 @@ export default defineComponent({
 		function getDateText(time: string) {
 			const date = new Date(time).getDate();
 			const month = new Date(time).getMonth() + 1;
-			return i18n.t('monthAndDay', {
+			return i18n.tsx.monthAndDay({
 				month: month.toString(),
 				day: date.toString(),
 			});
@@ -118,34 +118,36 @@ export default defineComponent({
 			return children;
 		};
 
-		function onBeforeLeave(el: HTMLElement) {
+		function onBeforeLeave(element: Element) {
+			const el = element as HTMLElement;
 			el.style.top = `${el.offsetTop}px`;
 			el.style.left = `${el.offsetLeft}px`;
 		}
 
-		function onLeaveCanceled(el: HTMLElement) {
+		function onLeaveCancelled(element: Element) {
+			const el = element as HTMLElement;
 			el.style.top = '';
 			el.style.left = '';
 		}
 
-		return () => h(
-			defaultStore.state.animation ? TransitionGroup : 'div',
-			{
-				class: {
-					[$style['date-separated-list']]: true,
-					[$style['date-separated-list-nogap']]: props.noGap,
-					[$style['reversed']]: props.reversed,
-					[$style['direction-down']]: props.direction === 'down',
-					[$style['direction-up']]: props.direction === 'up',
-				},
-				...(defaultStore.state.animation ? {
-					name: 'list',
-					tag: 'div',
-					onBeforeLeave,
-					onLeaveCanceled,
-				} : {}),
-			},
-			{ default: renderChildren });
+		// eslint-disable-next-line vue/no-setup-props-destructure
+		const classes = {
+			[$style['date-separated-list']]: true,
+			[$style['date-separated-list-nogap']]: props.noGap,
+			[$style['reversed']]: props.reversed,
+			[$style['direction-down']]: props.direction === 'down',
+			[$style['direction-up']]: props.direction === 'up',
+		};
+
+		return () => defaultStore.state.animation ? h(TransitionGroup, {
+			class: classes,
+			name: 'list',
+			tag: 'div',
+			onBeforeLeave,
+			onLeaveCancelled,
+		}, { default: renderChildren }) : h('div', {
+			class: classes,
+		}, { default: renderChildren });
 	},
 });
 </script>
diff --git a/packages/frontend/src/components/MkDialog.vue b/packages/frontend/src/components/MkDialog.vue
index 2c0f6a4d78..b81ebbbb11 100644
--- a/packages/frontend/src/components/MkDialog.vue
+++ b/packages/frontend/src/components/MkDialog.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -30,19 +30,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<MkInput v-if="input" v-model="inputValue" autofocus :type="input.type || 'text'" :placeholder="input.placeholder || undefined" :autocomplete="input.autocomplete" @keydown="onInputKeydown">
 			<template v-if="input.type === 'password'" #prefix><i class="ph-lock ph-bold ph-lg"></i></template>
 			<template #caption>
-				<span v-if="okButtonDisabledReason === 'charactersExceeded'" v-text="i18n.t('_dialog.charactersExceeded', { current: (inputValue as string).length, max: input.maxLength ?? 'NaN' })"/>
-				<span v-else-if="okButtonDisabledReason === 'charactersBelow'" v-text="i18n.t('_dialog.charactersBelow', { current: (inputValue as string).length, min: input.minLength ?? 'NaN' })"/>
+				<span v-if="okButtonDisabledReason === 'charactersExceeded'" v-text="i18n.tsx._dialog.charactersExceeded({ current: (inputValue as string)?.length ?? 0, max: input.maxLength ?? 'NaN' })"/>
+				<span v-else-if="okButtonDisabledReason === 'charactersBelow'" v-text="i18n.tsx._dialog.charactersBelow({ current: (inputValue as string)?.length ?? 0, min: input.minLength ?? 'NaN' })"/>
 			</template>
 		</MkInput>
 		<MkSelect v-if="select" v-model="selectedValue" autofocus>
 			<template v-if="select.items">
 				<option v-for="item in select.items" :value="item.value">{{ item.text }}</option>
 			</template>
-			<template v-else>
-				<optgroup v-for="groupedItem in select.groupedItems" :label="groupedItem.label">
-					<option v-for="item in groupedItem.items" :value="item.value">{{ item.text }}</option>
-				</optgroup>
-			</template>
 		</MkSelect>
 		<div v-if="(showOkButton || showCancelButton) && !actions" :class="$style.buttons">
 			<MkButton v-if="showOkButton" data-cy-modal-dialog-ok inline primary rounded :autofocus="!input && !select" :disabled="okButtonDisabledReason" @click="ok">{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }}</MkButton>
@@ -64,7 +59,7 @@ import MkSelect from '@/components/MkSelect.vue';
 import { i18n } from '@/i18n.js';
 
 type Input = {
-	type: 'text' | 'number' | 'password' | 'email' | 'url' | 'date' | 'time' | 'search' | 'datetime-local';
+	type?: 'text' | 'number' | 'password' | 'email' | 'url' | 'date' | 'time' | 'search' | 'datetime-local';
 	placeholder?: string | null;
 	autocomplete?: string;
 	default: string | number | null;
@@ -74,22 +69,17 @@ type Input = {
 
 type Select = {
 	items: {
-		value: string;
+		value: any;
 		text: string;
 	}[];
-	groupedItems: {
-		label: string;
-		items: {
-			value: string;
-			text: string;
-		}[];
-	}[];
 	default: string | null;
 };
 
+type Result = string | number | true | null;
+
 const props = withDefaults(defineProps<{
 	type?: 'success' | 'error' | 'warning' | 'info' | 'question' | 'waiting';
-	title: string;
+	title?: string;
 	text?: string;
 	input?: Input;
 	select?: Select;
@@ -113,7 +103,7 @@ const props = withDefaults(defineProps<{
 });
 
 const emit = defineEmits<{
-	(ev: 'done', v: { canceled: boolean; result: any }): void;
+	(ev: 'done', v: { canceled: true } | { canceled: false, result: Result }): void;
 	(ev: 'closed'): void;
 }>();
 
@@ -125,7 +115,7 @@ const selectedValue = ref(props.select?.default ?? null);
 const okButtonDisabledReason = computed<null | 'charactersExceeded' | 'charactersBelow'>(() => {
 	if (props.input) {
 		if (props.input.minLength) {
-			if ((inputValue.value || inputValue.value === '') && (inputValue.value as string).length < props.input.minLength) {
+			if (inputValue.value == null || (inputValue.value as string).length < props.input.minLength) {
 				return 'charactersBelow';
 			}
 		}
@@ -139,8 +129,11 @@ const okButtonDisabledReason = computed<null | 'charactersExceeded' | 'character
 	return null;
 });
 
-function done(canceled: boolean, result?) {
-	emit('done', { canceled, result });
+// overload function を使いたいので lint エラーを無視する
+function done(canceled: true): void;
+function done(canceled: false, result: Result): void; // eslint-disable-line no-redeclare
+function done(canceled: boolean, result?: Result): void { // eslint-disable-line no-redeclare
+	emit('done', { canceled, result } as { canceled: true } | { canceled: false, result: Result });
 	modal.value?.close();
 }
 
diff --git a/packages/frontend/src/components/MkDigitalClock.stories.impl.ts b/packages/frontend/src/components/MkDigitalClock.stories.impl.ts
index 5d16c09bc5..e3391bcf7e 100644
--- a/packages/frontend/src/components/MkDigitalClock.stories.impl.ts
+++ b/packages/frontend/src/components/MkDigitalClock.stories.impl.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/components/MkDigitalClock.vue b/packages/frontend/src/components/MkDigitalClock.vue
index dff6e7d4dd..2e2321e6ac 100644
--- a/packages/frontend/src/components/MkDigitalClock.vue
+++ b/packages/frontend/src/components/MkDigitalClock.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkDonation.vue b/packages/frontend/src/components/MkDonation.vue
index a77ff42f94..a2780ddfe9 100644
--- a/packages/frontend/src/components/MkDonation.vue
+++ b/packages/frontend/src/components/MkDonation.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -26,6 +26,16 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<MkLink target="_blank" url="https://ko-fi.com/transfem">{{ i18n.ts.learnMore }}</MkLink>
 			</div>
 		</div>
+		<div v-if="instance.donationUrl" :class="$style.text">
+			<I18n :src="i18n.ts.pleaseDonateInstance" tag="span">
+				<template #host>
+					{{ instance.name ?? host }}
+				</template>
+			</I18n>
+			<div style="margin-top: 0.2em;">
+				<MkLink target="_blank" :url="instance.donationUrl">{{ i18n.ts.learnMore }}</MkLink>
+			</div>
+		</div>
 		<div class="_buttons">
 			<MkButton @click="close">{{ i18n.ts.remindMeLater }}</MkButton>
 			<MkButton @click="neverShow">{{ i18n.ts.neverShow }}</MkButton>
diff --git a/packages/frontend/src/components/MkDrive.file.vue b/packages/frontend/src/components/MkDrive.file.vue
index 9969c10258..13a2a2126c 100644
--- a/packages/frontend/src/components/MkDrive.file.vue
+++ b/packages/frontend/src/components/MkDrive.file.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -49,9 +49,9 @@ import bytes from '@/filters/bytes.js';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
 import { $i } from '@/account.js';
-import { useRouter } from '@/router.js';
 import { getDriveFileMenu } from '@/scripts/get-drive-file-menu.js';
 import { deviceKind } from '@/scripts/device-kind.js';
+import { useRouter } from '@/router/supplier.js';
 
 const router = useRouter();
 
diff --git a/packages/frontend/src/components/MkDrive.folder.vue b/packages/frontend/src/components/MkDrive.folder.vue
index dcaaa72cf4..945f45c012 100644
--- a/packages/frontend/src/components/MkDrive.folder.vue
+++ b/packages/frontend/src/components/MkDrive.folder.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -35,6 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed, defineAsyncComponent, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { defaultStore } from '@/store.js';
 import { claimAchievement } from '@/scripts/achievements.js';
@@ -144,7 +145,7 @@ function onDrop(ev: DragEvent) {
 	if (driveFile != null && driveFile !== '') {
 		const file = JSON.parse(driveFile);
 		emit('removeFile', file.id);
-		os.api('drive/files/update', {
+		misskeyApi('drive/files/update', {
 			fileId: file.id,
 			folderId: props.folder.id,
 		});
@@ -160,7 +161,7 @@ function onDrop(ev: DragEvent) {
 		if (folder.id === props.folder.id) return;
 
 		emit('removeFolder', folder.id);
-		os.api('drive/folders/update', {
+		misskeyApi('drive/folders/update', {
 			folderId: folder.id,
 			parentId: props.folder.id,
 		}).then(() => {
@@ -204,7 +205,7 @@ function onDragend() {
 }
 
 function go() {
-	emit('move', props.folder.id);
+	emit('move', props.folder);
 }
 
 function rename() {
@@ -214,7 +215,7 @@ function rename() {
 		default: props.folder.name,
 	}).then(({ canceled, result: name }) => {
 		if (canceled) return;
-		os.api('drive/folders/update', {
+		misskeyApi('drive/folders/update', {
 			folderId: props.folder.id,
 			name: name,
 		});
@@ -222,7 +223,7 @@ function rename() {
 }
 
 function deleteFolder() {
-	os.api('drive/folders/delete', {
+	misskeyApi('drive/folders/delete', {
 		folderId: props.folder.id,
 	}).then(() => {
 		if (defaultStore.state.uploadFolder === props.folder.id) {
diff --git a/packages/frontend/src/components/MkDrive.navFolder.vue b/packages/frontend/src/components/MkDrive.navFolder.vue
index cac3c17c85..d78c215328 100644
--- a/packages/frontend/src/components/MkDrive.navFolder.vue
+++ b/packages/frontend/src/components/MkDrive.navFolder.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { ref } from 'vue';
 import * as Misskey from 'misskey-js';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 
 const props = defineProps<{
@@ -112,7 +112,7 @@ function onDrop(ev: DragEvent) {
 	if (driveFile != null && driveFile !== '') {
 		const file = JSON.parse(driveFile);
 		emit('removeFile', file.id);
-		os.api('drive/files/update', {
+		misskeyApi('drive/files/update', {
 			fileId: file.id,
 			folderId: props.folder ? props.folder.id : null,
 		});
@@ -126,7 +126,7 @@ function onDrop(ev: DragEvent) {
 		// 移動先が自分自身ならreject
 		if (props.folder && folder.id === props.folder.id) return;
 		emit('removeFolder', folder.id);
-		os.api('drive/folders/update', {
+		misskeyApi('drive/folders/update', {
 			folderId: folder.id,
 			parentId: props.folder ? props.folder.id : null,
 		});
diff --git a/packages/frontend/src/components/MkDrive.vue b/packages/frontend/src/components/MkDrive.vue
index 00bb0e6e2b..2990ea6861 100644
--- a/packages/frontend/src/components/MkDrive.vue
+++ b/packages/frontend/src/components/MkDrive.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -82,8 +82,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<MkButton v-show="moreFiles" ref="loadMoreFiles" @click="fetchMoreFiles">{{ i18n.ts.loadMore }}</MkButton>
 			</div>
 			<div v-if="files.length == 0 && folders.length == 0 && !fetching" :class="$style.empty">
-				<div v-if="draghover">{{ i18n.t('empty-draghover') }}</div>
-				<div v-if="!draghover && folder == null"><strong>{{ i18n.ts.emptyDrive }}</strong><br/>{{ i18n.t('empty-drive-description') }}</div>
+				<div v-if="draghover">{{ i18n.ts['empty-draghover'] }}</div>
+				<div v-if="!draghover && folder == null"><strong>{{ i18n.ts.emptyDrive }}</strong><br/>{{ i18n.ts['empty-drive-description'] }}</div>
 				<div v-if="!draghover && folder != null">{{ i18n.ts.emptyFolder }}</div>
 			</div>
 		</div>
@@ -98,10 +98,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { nextTick, onActivated, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkButton from './MkButton.vue';
+import type { MenuItem } from '@/types/menu.js';
 import XNavFolder from '@/components/MkDrive.navFolder.vue';
 import XFolder from '@/components/MkDrive.folder.vue';
 import XFile from '@/components/MkDrive.file.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { useStream } from '@/stream.js';
 import { defaultStore } from '@/store.js';
 import { i18n } from '@/i18n.js';
@@ -254,7 +256,7 @@ function onDrop(ev: DragEvent): any {
 		const file = JSON.parse(driveFile);
 		if (files.value.some(f => f.id === file.id)) return;
 		removeFile(file.id);
-		os.api('drive/files/update', {
+		misskeyApi('drive/files/update', {
 			fileId: file.id,
 			folderId: folder.value ? folder.value.id : null,
 		});
@@ -270,7 +272,7 @@ function onDrop(ev: DragEvent): any {
 		if (folder.value && droppedFolder.id === folder.value.id) return false;
 		if (folders.value.some(f => f.id === droppedFolder.id)) return false;
 		removeFolder(droppedFolder.id);
-		os.api('drive/folders/update', {
+		misskeyApi('drive/folders/update', {
 			folderId: droppedFolder.id,
 			parentId: folder.value ? folder.value.id : null,
 		}).then(() => {
@@ -307,7 +309,7 @@ function urlUpload() {
 		placeholder: i18n.ts.uploadFromUrlDescription,
 	}).then(({ canceled, result: url }) => {
 		if (canceled || !url) return;
-		os.api('drive/files/upload-from-url', {
+		misskeyApi('drive/files/upload-from-url', {
 			url: url,
 			folderId: folder.value ? folder.value.id : undefined,
 		});
@@ -325,7 +327,7 @@ function createFolder() {
 		placeholder: i18n.ts.folderName,
 	}).then(({ canceled, result: name }) => {
 		if (canceled) return;
-		os.api('drive/folders/create', {
+		misskeyApi('drive/folders/create', {
 			name: name,
 			parentId: folder.value ? folder.value.id : undefined,
 		}).then(createdFolder => {
@@ -341,7 +343,7 @@ function renameFolder(folderToRename: Misskey.entities.DriveFolder) {
 		default: folderToRename.name,
 	}).then(({ canceled, result: name }) => {
 		if (canceled) return;
-		os.api('drive/folders/update', {
+		misskeyApi('drive/folders/update', {
 			folderId: folderToRename.id,
 			name: name,
 		}).then(updatedFolder => {
@@ -352,7 +354,7 @@ function renameFolder(folderToRename: Misskey.entities.DriveFolder) {
 }
 
 function deleteFolder(folderToDelete: Misskey.entities.DriveFolder) {
-	os.api('drive/folders/delete', {
+	misskeyApi('drive/folders/delete', {
 		folderId: folderToDelete.id,
 	}).then(() => {
 		// 削除時に親フォルダに移動
@@ -426,7 +428,7 @@ function chooseFolder(folderToChoose: Misskey.entities.DriveFolder) {
 	}
 }
 
-function move(target?: Misskey.entities.DriveFolder) {
+function move(target?: Misskey.entities.DriveFolder | Misskey.entities.DriveFolder['id' | 'parentId']) {
 	if (!target) {
 		goRoot();
 		return;
@@ -436,7 +438,7 @@ function move(target?: Misskey.entities.DriveFolder) {
 
 	fetching.value = true;
 
-	os.api('drive/folders/show', {
+	misskeyApi('drive/folders/show', {
 		folderId: target,
 	}).then(folderToMove => {
 		folder.value = folderToMove;
@@ -535,7 +537,7 @@ async function fetch() {
 	const foldersMax = 30;
 	const filesMax = 30;
 
-	const foldersPromise = os.api('drive/folders', {
+	const foldersPromise = misskeyApi('drive/folders', {
 		folderId: folder.value ? folder.value.id : null,
 		limit: foldersMax + 1,
 	}).then(fetchedFolders => {
@@ -546,7 +548,7 @@ async function fetch() {
 		return fetchedFolders;
 	});
 
-	const filesPromise = os.api('drive/files', {
+	const filesPromise = misskeyApi('drive/files', {
 		folderId: folder.value ? folder.value.id : null,
 		type: props.type,
 		limit: filesMax + 1,
@@ -571,7 +573,7 @@ function fetchMoreFolders() {
 
 	const max = 30;
 
-	os.api('drive/folders', {
+	misskeyApi('drive/folders', {
 		folderId: folder.value ? folder.value.id : null,
 		type: props.type,
 		untilId: folders.value.at(-1)?.id,
@@ -594,7 +596,7 @@ function fetchMoreFiles() {
 	const max = 30;
 
 	// ファイル一覧取得
-	os.api('drive/files', {
+	misskeyApi('drive/files', {
 		folderId: folder.value ? folder.value.id : null,
 		type: props.type,
 		untilId: files.value.at(-1)?.id,
@@ -612,7 +614,7 @@ function fetchMoreFiles() {
 }
 
 function getMenu() {
-	return [{
+	const menu: MenuItem[] = [{
 		type: 'switch',
 		text: i18n.ts.keepOriginalUploading,
 		ref: keepOriginal,
@@ -633,7 +635,7 @@ function getMenu() {
 	}, folder.value ? {
 		text: i18n.ts.renameFolder,
 		icon: 'ph-textbox ph-bold ph-lg',
-		action: () => { renameFolder(folder.value); },
+		action: () => { if (folder.value) renameFolder(folder.value); },
 	} : undefined, folder.value ? {
 		text: i18n.ts.deleteFolder,
 		icon: 'ph-trash ph-bold ph-lg',
@@ -643,6 +645,8 @@ function getMenu() {
 		icon: 'ph-folder ph-bold ph-lg-plus',
 		action: () => { createFolder(); },
 	}];
+
+	return menu;
 }
 
 function showMenu(ev: MouseEvent) {
diff --git a/packages/frontend/src/components/MkDriveFileThumbnail.vue b/packages/frontend/src/components/MkDriveFileThumbnail.vue
index 3063523791..2f1fef4ea6 100644
--- a/packages/frontend/src/components/MkDriveFileThumbnail.vue
+++ b/packages/frontend/src/components/MkDriveFileThumbnail.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkDriveSelectDialog.vue b/packages/frontend/src/components/MkDriveSelectDialog.vue
index e65f4dd403..f1ecc27123 100644
--- a/packages/frontend/src/components/MkDriveSelectDialog.vue
+++ b/packages/frontend/src/components/MkDriveSelectDialog.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -39,13 +39,13 @@ withDefaults(defineProps<{
 });
 
 const emit = defineEmits<{
-	(ev: 'done', r?: Misskey.entities.DriveFile[]): void;
+	(ev: 'done', r?: Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[]): void;
 	(ev: 'closed'): void;
 }>();
 
 const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
 
-const selected = ref<Misskey.entities.DriveFile[]>([]);
+const selected = ref<Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[]>([]);
 
 function ok() {
 	emit('done', selected.value);
@@ -57,7 +57,7 @@ function cancel() {
 	dialog.value?.close();
 }
 
-function onChangeSelection(files: Misskey.entities.DriveFile[]) {
-	selected.value = files;
+function onChangeSelection(v: Misskey.entities.DriveFile[] | Misskey.entities.DriveFolder[]) {
+	selected.value = v;
 }
 </script>
diff --git a/packages/frontend/src/components/MkDriveWindow.vue b/packages/frontend/src/components/MkDriveWindow.vue
index 72aa79b153..c0142ec76e 100644
--- a/packages/frontend/src/components/MkDriveWindow.vue
+++ b/packages/frontend/src/components/MkDriveWindow.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkEmojiPicker.section.vue b/packages/frontend/src/components/MkEmojiPicker.section.vue
index dabc12237a..a5839586b6 100644
--- a/packages/frontend/src/components/MkEmojiPicker.section.vue
+++ b/packages/frontend/src/components/MkEmojiPicker.section.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -16,10 +16,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 			:key="emoji"
 			:data-emoji="emoji"
 			class="_button item"
+			:disabled="disabledEmojis?.value.includes(emoji)"
 			@pointerenter="computeButtonTitle"
 			@click="emit('chosen', emoji, $event)"
 		>
-			<MkCustomEmoji v-if="emoji[0] === ':'" class="emoji" :name="emoji" :normal="true"/>
+			<MkCustomEmoji v-if="emoji[0] === ':'" class="emoji" :name="emoji" :normal="true" :fallbackToImage="true"/>
 			<MkEmoji v-else class="emoji" :emoji="emoji" :normal="true"/>
 		</button>
 	</div>
@@ -27,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <!-- フォルダの中にはカスタム絵文字やフォルダがある -->
 <section v-else v-panel style="border-radius: var(--radius-sm); border-bottom: 0.5px solid var(--divider);">
 	<header class="_acrylic" @click="shown = !shown">
-		<i class="toggle ti-fw" :class="shown ? 'ph-caret-down ph-bold ph-lg' : 'ph-caret-up ph-bold ph-lg'"></i> <slot></slot> (<i class="ph-folder ph-bold ph-lg"></i>:{{ customEmojiTree.length }} <i class="ph-smiley-sticker ph-bold ph-lg ti-fw"></i>:{{ emojis.length }})
+		<i class="toggle ti-fw" :class="shown ? 'ph-caret-down ph-bold ph-lg' : 'ph-caret-up ph-bold ph-lg'"></i> <slot></slot> (<i class="ph-folder ph-bold ph-lg"></i>:{{ customEmojiTree?.length }} <i class="ph-smiley-sticker ph-bold ph-lg ti-fw"></i>:{{ emojis.length }})
 	</header>
 	<div v-if="shown" style="padding-left: 9px;">
 		<MkEmojiPickerSection
@@ -48,6 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			:key="emoji"
 			:data-emoji="emoji"
 			class="_button item"
+			:disabled="disabledEmojis?.value.includes(emoji)"
 			@pointerenter="computeButtonTitle"
 			@click="emit('chosen', emoji, $event)"
 		>
@@ -60,13 +62,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { ref, computed, Ref } from 'vue';
-import { i18n } from '../i18n.js';
 import { CustomEmojiFolderTree, getEmojiName } from '@/scripts/emojilist.js';
+import { i18n } from '@/i18n.js';
 import { customEmojis } from '@/custom-emojis.js';
 import MkEmojiPickerSection from '@/components/MkEmojiPicker.section.vue';
 
 const props = defineProps<{
 	emojis: string[] | Ref<string[]>;
+	disabledEmojis?: Ref<string[]>;
 	initialShown?: boolean;
 	hasChildSection?: boolean;
 	customEmojiTree?: CustomEmojiFolderTree[];
@@ -84,10 +87,10 @@ const shown = ref(!!props.initialShown);
 function computeButtonTitle(ev: MouseEvent): void {
 	const elm = ev.target as HTMLElement;
 	const emoji = elm.dataset.emoji as string;
-	elm.title = getEmojiName(emoji) ?? emoji;
+	elm.title = getEmojiName(emoji);
 }
 
-function nestedChosen(emoji: any, ev?: MouseEvent) {
+function nestedChosen(emoji: any, ev: MouseEvent) {
 	emit('chosen', emoji, ev);
 }
 </script>
diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue
index b7e329d7c2..1219a29d85 100644
--- a/packages/frontend/src/components/MkEmojiPicker.vue
+++ b/packages/frontend/src/components/MkEmojiPicker.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -14,11 +14,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 					v-for="emoji in searchResultCustom"
 					:key="emoji.name"
 					class="_button item"
+					:disabled="!canReact(emoji)"
 					:title="emoji.name"
 					tabindex="0"
 					@click="chosen(emoji, $event)"
 				>
-					<MkCustomEmoji class="emoji" :name="emoji.name"/>
+					<MkCustomEmoji class="emoji" :name="emoji.name" :fallbackToImage="true"/>
 				</button>
 			</div>
 			<div v-if="searchResultUnicode.length > 0" class="body">
@@ -36,19 +37,20 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</section>
 
 		<div v-if="tab === 'index'" class="group index">
-			<section v-if="showPinned && pinned.length > 0">
+			<section v-if="showPinned && (pinned && pinned.length > 0)">
 				<div class="body">
 					<button
-						v-for="emoji in pinned"
-						:key="emoji"
-						:data-emoji="emoji"
+						v-for="emoji in pinnedEmojisDef"
+						:key="getKey(emoji)"
+						:data-emoji="getKey(emoji)"
 						class="_button item"
+						:disabled="!canReact(emoji)"
 						tabindex="0"
 						@pointerenter="computeButtonTitle"
 						@click="chosen(emoji, $event)"
 					>
-						<MkCustomEmoji v-if="emoji[0] === ':'" class="emoji" :name="emoji" :normal="true"/>
-						<MkEmoji v-else class="emoji" :emoji="emoji" :normal="true"/>
+						<MkCustomEmoji v-if="!emoji.hasOwnProperty('char')" class="emoji" :name="getKey(emoji)" :normal="true"/>
+						<MkEmoji v-else class="emoji" :emoji="getKey(emoji)" :normal="true"/>
 					</button>
 				</div>
 			</section>
@@ -57,15 +59,16 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<header class="_acrylic"><i class="ph-clock ph-bold ph-lg ti-fw"></i> {{ i18n.ts.recentUsed }}</header>
 				<div class="body">
 					<button
-						v-for="emoji in recentlyUsedEmojis"
-						:key="emoji"
+						v-for="emoji in recentlyUsedEmojisDef"
+						:key="getKey(emoji)"
 						class="_button item"
-						:data-emoji="emoji"
+						:disabled="!canReact(emoji)"
+						:data-emoji="getKey(emoji)"
 						@pointerenter="computeButtonTitle"
 						@click="chosen(emoji, $event)"
 					>
-						<MkCustomEmoji v-if="emoji[0] === ':'" class="emoji" :name="emoji" :normal="true"/>
-						<MkEmoji v-else class="emoji" :emoji="emoji" :normal="true"/>
+						<MkCustomEmoji v-if="!emoji.hasOwnProperty('char')" class="emoji" :name="getKey(emoji)" :normal="true"/>
+						<MkEmoji v-else class="emoji" :emoji="getKey(emoji)" :normal="true"/>
 					</button>
 				</div>
 			</section>
@@ -76,7 +79,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 				v-for="child in customEmojiFolderRoot.children"
 				:key="`custom:${child.value}`"
 				:initialShown="false"
-				:emojis="computed(() => customEmojis.filter(e => child.value === '' ? (e.category === 'null' || !e.category) : e.category === child.value).filter(filterAvailable).map(e => `:${e.name}:`))"
+				:emojis="computed(() => customEmojis.filter(e => filterCategory(e, child.value)).map(e => `:${e.name}:`))"
+				:disabledEmojis="computed(() => customEmojis.filter(e => filterCategory(e, child.value)).filter(e => !canReact(e)).map(e => `:${e.name}:`))"
 				:hasChildSection="child.children.length !== 0"
 				:customEmojiTree="child.children"
 				@chosen="chosen"
@@ -109,6 +113,7 @@ import {
 	unicodeEmojiCategories as categories,
 	getEmojiName,
 	CustomEmojiFolderTree,
+	getUnicodeEmoji,
 } from '@/scripts/emojilist.js';
 import MkRippleEffect from '@/components/MkRippleEffect.vue';
 import * as os from '@/os.js';
@@ -118,6 +123,7 @@ import { i18n } from '@/i18n.js';
 import { defaultStore } from '@/store.js';
 import { customEmojiCategories, customEmojis, customEmojisMap } from '@/custom-emojis.js';
 import { $i } from '@/account.js';
+import { checkReactionPermissions } from '@/scripts/check-reaction-permissions.js';
 
 const props = withDefaults(defineProps<{
 	showPinned?: boolean;
@@ -126,6 +132,7 @@ const props = withDefaults(defineProps<{
 	asDrawer?: boolean;
 	asWindow?: boolean;
 	asReactionPicker?: boolean; // 今は使われてないが将来的に使いそう
+	targetNote?: Misskey.entities.Note;
 }>(), {
 	showPinned: true,
 });
@@ -144,6 +151,13 @@ const {
 	recentlyUsedEmojis,
 } = defaultStore.reactiveState;
 
+const recentlyUsedEmojisDef = computed(() => {
+	return recentlyUsedEmojis.value.map(getDef).filter(x => x != null);
+});
+const pinnedEmojisDef = computed(() => {
+	return pinned.value?.map(getDef).filter(x => x != null);
+});
+
 const pinned = computed(() => props.pinnedEmojis);
 const size = computed(() => emojiPickerScale.value);
 const width = computed(() => emojiPickerWidth.value);
@@ -221,6 +235,19 @@ watch(q, () => {
 				}
 			}
 		} else {
+			if (customEmojisMap.has(newQ)) {
+				matches.add(customEmojisMap.get(newQ)!);
+			}
+			if (matches.size >= max) return matches;
+
+			for (const emoji of emojis) {
+				if (emoji.aliases.some(alias => alias === newQ)) {
+					matches.add(emoji);
+					if (matches.size >= max) break;
+				}
+			}
+			if (matches.size >= max) return matches;
+
 			for (const emoji of emojis) {
 				if (emoji.name.startsWith(newQ)) {
 					matches.add(emoji);
@@ -322,12 +349,16 @@ watch(q, () => {
 		return matches;
 	};
 
-	searchResultCustom.value = Array.from(searchCustom()).filter(filterAvailable);
+	searchResultCustom.value = Array.from(searchCustom());
 	searchResultUnicode.value = Array.from(searchUnicode());
 });
 
-function filterAvailable(emoji: Misskey.entities.EmojiSimple): boolean {
-	return (emoji.roleIdsThatCanBeUsedThisEmojiAsReaction == null || emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0) || ($i && $i.roles.some(r => emoji.roleIdsThatCanBeUsedThisEmojiAsReaction.includes(r.id)));
+function canReact(emoji: Misskey.entities.EmojiSimple | UnicodeEmojiDef | string): boolean {
+	return !props.targetNote || checkReactionPermissions($i!, props.targetNote, emoji);
+}
+
+function filterCategory(emoji: Misskey.entities.EmojiSimple, category: string): boolean {
+	return category === '' ? (emoji.category === 'null' || !emoji.category) : emoji.category === category;
 }
 
 function focus() {
@@ -347,11 +378,22 @@ function getKey(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef):
 	return typeof emoji === 'string' ? emoji : 'char' in emoji ? emoji.char : `:${emoji.name}:`;
 }
 
+function getDef(emoji: string): string | Misskey.entities.EmojiSimple | UnicodeEmojiDef {
+	if (emoji.includes(':')) {
+		// カスタム絵文字が存在する場合はその情報を持つオブジェクトを返し、
+		// サーバの管理画面から削除された等で情報が見つからない場合は名前の文字列をそのまま返しておく(undefinedを返すとエラーになるため)
+		const name = emoji.replaceAll(':', '');
+		return customEmojisMap.get(name) ?? emoji;
+	} else {
+		return getUnicodeEmoji(emoji);
+	}
+}
+
 /** @see MkEmojiPicker.section.vue */
 function computeButtonTitle(ev: MouseEvent): void {
 	const elm = ev.target as HTMLElement;
 	const emoji = elm.dataset.emoji as string;
-	elm.title = getEmojiName(emoji) ?? emoji;
+	elm.title = getEmojiName(emoji);
 }
 
 function chosen(emoji: any, ev?: MouseEvent) {
@@ -511,6 +553,18 @@ defineExpose({
 						width: auto;
 						height: auto;
 						min-width: 0;
+
+						&:disabled {
+							cursor: not-allowed;
+							background: linear-gradient(-45deg, transparent 0% 48%, var(--X6) 48% 52%, transparent 52% 100%);
+							opacity: 1;
+
+							> .emoji {
+								filter: grayscale(1);
+								mix-blend-mode: exclusion;
+								opacity: 0.8;
+							}
+						}
 					}
 				}
 			}
@@ -533,6 +587,18 @@ defineExpose({
 						width: auto;
 						height: auto;
 						min-width: 0;
+
+						&:disabled {
+							cursor: not-allowed;
+							background: linear-gradient(-45deg, transparent 0% 48%, var(--X6) 48% 52%, transparent 52% 100%);
+							opacity: 1;
+
+							> .emoji {
+								filter: grayscale(1);
+								mix-blend-mode: exclusion;
+								opacity: 0.8;
+							}
+						}
 					}
 				}
 			}
@@ -648,6 +714,18 @@ defineExpose({
 						box-shadow: inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15);
 					}
 
+					&:disabled {
+						cursor: not-allowed;
+						background: linear-gradient(-45deg, transparent 0% 48%, var(--X6) 48% 52%, transparent 52% 100%);
+						opacity: 1;
+
+						> .emoji {
+							filter: grayscale(1);
+							mix-blend-mode: exclusion;
+							opacity: 0.8;
+						}
+					}
+
 					> .emoji {
 						height: 1.25em;
 						vertical-align: -.25em;
diff --git a/packages/frontend/src/components/MkEmojiPickerDialog.vue b/packages/frontend/src/components/MkEmojiPickerDialog.vue
index 4068a79f08..c6b3896989 100644
--- a/packages/frontend/src/components/MkEmojiPickerDialog.vue
+++ b/packages/frontend/src/components/MkEmojiPickerDialog.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -24,6 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		:showPinned="showPinned"
 		:pinnedEmojis="pinnedEmojis"
 		:asReactionPicker="asReactionPicker"
+		:targetNote="targetNote"
 		:asDrawer="type === 'drawer'"
 		:max-height="maxHeight"
 		@chosen="chosen"
@@ -32,6 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
+import * as Misskey from 'misskey-js';
 import { shallowRef } from 'vue';
 import MkModal from '@/components/MkModal.vue';
 import MkEmojiPicker from '@/components/MkEmojiPicker.vue';
@@ -43,6 +45,7 @@ const props = withDefaults(defineProps<{
 	showPinned?: boolean;
   pinnedEmojis?: string[],
 	asReactionPicker?: boolean;
+	targetNote?: Misskey.entities.Note;
   choseAndClose?: boolean;
 }>(), {
 	manualShowing: null,
@@ -53,7 +56,7 @@ const props = withDefaults(defineProps<{
 });
 
 const emit = defineEmits<{
-	(ev: 'done', v: any): void;
+	(ev: 'done', v: string): void;
 	(ev: 'close'): void;
 	(ev: 'closed'): void;
 }>();
@@ -61,7 +64,7 @@ const emit = defineEmits<{
 const modal = shallowRef<InstanceType<typeof MkModal>>();
 const picker = shallowRef<InstanceType<typeof MkEmojiPicker>>();
 
-function chosen(emoji: any) {
+function chosen(emoji: string) {
 	emit('done', emoji);
 	if (props.choseAndClose) {
 		modal.value?.close();
diff --git a/packages/frontend/src/components/MkEmojiPickerWindow.vue b/packages/frontend/src/components/MkEmojiPickerWindow.vue
deleted file mode 100644
index 1a2c55e785..0000000000
--- a/packages/frontend/src/components/MkEmojiPickerWindow.vue
+++ /dev/null
@@ -1,47 +0,0 @@
-<!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
-SPDX-License-Identifier: AGPL-3.0-only
--->
-
-<template>
-<MkWindow
-	ref="window"
-	:initialWidth="300"
-	:initialHeight="290"
-	:canResize="true"
-	:mini="true"
-	:front="true"
-	@closed="emit('closed')"
->
-	<MkEmojiPicker :showPinned="showPinned" :asReactionPicker="asReactionPicker" asWindow :class="$style.picker" @chosen="chosen"/>
-</MkWindow>
-</template>
-
-<script lang="ts" setup>
-import { } from 'vue';
-import MkWindow from '@/components/MkWindow.vue';
-import MkEmojiPicker from '@/components/MkEmojiPicker.vue';
-
-withDefaults(defineProps<{
-	src?: HTMLElement;
-	showPinned?: boolean;
-	asReactionPicker?: boolean;
-}>(), {
-	showPinned: true,
-});
-
-const emit = defineEmits<{
-	(ev: 'chosen', v: any): void;
-	(ev: 'closed'): void;
-}>();
-
-function chosen(emoji: any) {
-	emit('chosen', emoji);
-}
-</script>
-
-<style lang="scss" module>
-.picker {
-	height: 100%;
-}
-</style>
diff --git a/packages/frontend/src/components/MkFeaturedPhotos.vue b/packages/frontend/src/components/MkFeaturedPhotos.vue
index 6d1bad7433..8d875790bc 100644
--- a/packages/frontend/src/components/MkFeaturedPhotos.vue
+++ b/packages/frontend/src/components/MkFeaturedPhotos.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -10,11 +10,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { ref } from 'vue';
 import * as Misskey from 'misskey-js';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 
 const meta = ref<Misskey.entities.MetaResponse>();
 
-os.api('meta', { detail: true }).then(gotMeta => {
+misskeyApi('meta', { detail: true }).then(gotMeta => {
 	meta.value = gotMeta;
 });
 </script>
diff --git a/packages/frontend/src/components/MkFileCaptionEditWindow.vue b/packages/frontend/src/components/MkFileCaptionEditWindow.vue
index b799fb9447..39551e6b3c 100644
--- a/packages/frontend/src/components/MkFileCaptionEditWindow.vue
+++ b/packages/frontend/src/components/MkFileCaptionEditWindow.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	:withOkButton="true"
 	:okButtonDisabled="false"
 	@ok="ok()"
-	@close="dialog.close()"
+	@close="dialog?.close()"
 	@closed="emit('closed')"
 >
 	<template #header>{{ i18n.ts.describeFile }}</template>
@@ -48,6 +48,6 @@ const caption = ref(props.default);
 
 async function ok() {
 	emit('done', caption.value);
-	dialog.value.close();
+	dialog.value?.close();
 }
 </script>
diff --git a/packages/frontend/src/components/MkFileListForAdmin.vue b/packages/frontend/src/components/MkFileListForAdmin.vue
index eb0d4d61ac..f3305e9f54 100644
--- a/packages/frontend/src/components/MkFileListForAdmin.vue
+++ b/packages/frontend/src/components/MkFileListForAdmin.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <div>
 	<MkPagination v-slot="{items}" :pagination="pagination" class="urempief" :class="{ grid: viewMode === 'grid' }">
 		<MkA
-			v-for="file in items"
+			v-for="file in (items as Misskey.entities.DriveFile[])"
 			:key="file.id"
 			v-tooltip.mfm="`${file.type}\n${bytes(file.size)}\n${dateString(file.createdAt)}\nby ${file.user ? '@' + Misskey.acct.toString(file.user) : 'system'}`"
 			:to="`/admin/file/${file.id}`"
diff --git a/packages/frontend/src/components/MkFlashPreview.vue b/packages/frontend/src/components/MkFlashPreview.vue
index ab435585d9..c5dd877971 100644
--- a/packages/frontend/src/components/MkFlashPreview.vue
+++ b/packages/frontend/src/components/MkFlashPreview.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -9,7 +9,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<header>
 			<h1 :title="flash.title">{{ flash.title }}</h1>
 		</header>
-		<p v-if="flash.summary" :title="flash.summary">{{ flash.summary.length > 85 ? flash.summary.slice(0, 85) + '…' : flash.summary }}</p>
+		<p v-if="flash.summary" :title="flash.summary">
+			<Mfm class="summaryMfm" :text="flash.summary" :plain="true" :nowrap="true"/>
+		</p>
 		<footer>
 			<img class="icon" :src="flash.user.avatarUrl"/>
 			<p>{{ userName(flash.user) }}</p>
@@ -54,6 +56,12 @@ const props = defineProps<{
 			margin: 0;
 			color: var(--urlPreviewText);
 			font-size: 0.8em;
+			overflow: clip;
+
+			> .summaryMfm {
+				display: block;
+				width: 100%;
+			}
 		}
 
 		> footer {
diff --git a/packages/frontend/src/components/MkFoldableSection.vue b/packages/frontend/src/components/MkFoldableSection.vue
index 65afc48f06..51bcafd1c2 100644
--- a/packages/frontend/src/components/MkFoldableSection.vue
+++ b/packages/frontend/src/components/MkFoldableSection.vue
@@ -1,10 +1,10 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<div ref="el" :class="$style.root">
+<div ref="rootEl" :class="$style.root">
 	<header :class="$style.header" class="_button" :style="{ background: bg }" @click="showBody = !showBody">
 		<div :class="$style.title"><div><slot name="header"></slot></div></div>
 		<div :class="$style.divider"></div>
@@ -14,7 +14,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</button>
 	</header>
 	<Transition
-		:name="defaultStore.state.animation ? 'folder-toggle' : ''"
+		:enterActiveClass="defaultStore.state.animation ? $style.folderToggleEnterActive : ''"
+		:leaveActiveClass="defaultStore.state.animation ? $style.folderToggleLeaveActive : ''"
+		:enterFromClass="defaultStore.state.animation ? $style.folderToggleEnterFrom : ''"
+		:leaveToClass="defaultStore.state.animation ? $style.folderToggleLeaveTo : ''"
 		@enter="enter"
 		@afterEnter="afterEnter"
 		@leave="leave"
@@ -42,8 +45,8 @@ const props = withDefaults(defineProps<{
 	expanded: true,
 });
 
-const el = shallowRef<HTMLDivElement>();
-const bg = ref<string | null>(null);
+const rootEl = shallowRef<HTMLDivElement>();
+const bg = ref<string>();
 const showBody = ref((props.persistKey && miLocalStorage.getItem(`${miLocalStoragePrefix}${props.persistKey}`)) ? (miLocalStorage.getItem(`${miLocalStoragePrefix}${props.persistKey}`) === 't') : props.expanded);
 
 watch(showBody, () => {
@@ -52,40 +55,44 @@ watch(showBody, () => {
 	}
 });
 
-function enter(el: Element) {
+function enter(element: Element) {
+	const el = element as HTMLElement;
 	const elementHeight = el.getBoundingClientRect().height;
-	el.style.height = 0;
+	el.style.height = '0';
 	el.offsetHeight; // reflow
 	el.style.height = elementHeight + 'px';
 }
 
-function afterEnter(el: Element) {
-	el.style.height = null;
+function afterEnter(element: Element) {
+	const el = element as HTMLElement;
+	el.style.height = 'unset';
 }
 
-function leave(el: Element) {
+function leave(element: Element) {
+	const el = element as HTMLElement;
 	const elementHeight = el.getBoundingClientRect().height;
 	el.style.height = elementHeight + 'px';
 	el.offsetHeight; // reflow
-	el.style.height = 0;
+	el.style.height = '0';
 }
 
-function afterLeave(el: Element) {
-	el.style.height = null;
+function afterLeave(element: Element) {
+	const el = element as HTMLElement;
+	el.style.height = 'unset';
 }
 
 onMounted(() => {
-	function getParentBg(el: HTMLElement | null): string {
+	function getParentBg(el?: HTMLElement | null): string {
 		if (el == null || el.tagName === 'BODY') return 'var(--bg)';
-		const bg = el.style.background || el.style.backgroundColor;
-		if (bg) {
-			return bg;
+		const background = el.style.background || el.style.backgroundColor;
+		if (background) {
+			return background;
 		} else {
 			return getParentBg(el.parentElement);
 		}
 	}
 
-	const rawBg = getParentBg(el.value);
+	const rawBg = getParentBg(rootEl.value);
 	const _bg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg);
 	_bg.setAlpha(0.85);
 	bg.value = _bg.toRgbString();
@@ -93,14 +100,12 @@ onMounted(() => {
 </script>
 
 <style lang="scss" module>
-.folder-toggle-enter-active, .folder-toggle-leave-active {
+.folderToggleEnterActive, .folderToggleLeaveActive {
 	overflow-y: clip;
 	transition: opacity 0.5s, height 0.5s !important;
 }
-.folder-toggle-enter-from {
-	opacity: 0;
-}
-.folder-toggle-leave-to {
+
+.folderToggleEnterFrom, .folderToggleLeaveTo {
 	opacity: 0;
 }
 
diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue
index 03621a4255..64d390f52b 100644
--- a/packages/frontend/src/components/MkFolder.vue
+++ b/packages/frontend/src/components/MkFolder.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</div>
 		</template>
 
-		<div v-if="openedAtLeastOnce" :class="[$style.body, { [$style.bgSame]: bgSame }]" :style="{ maxHeight: maxHeight ? `${maxHeight}px` : null, overflow: maxHeight ? `auto` : null }" :aria-hidden="!opened">
+		<div v-if="openedAtLeastOnce" :class="[$style.body, { [$style.bgSame]: bgSame }]" :style="{ maxHeight: maxHeight ? `${maxHeight}px` : undefined, overflow: maxHeight ? `auto` : undefined }" :aria-hidden="!opened">
 			<Transition
 				:enterActiveClass="defaultStore.state.animation ? $style.transition_toggle_enterActive : ''"
 				:leaveActiveClass="defaultStore.state.animation ? $style.transition_toggle_leaveActive : ''"
@@ -109,7 +109,7 @@ function toggle() {
 
 onMounted(() => {
 	const computedStyle = getComputedStyle(document.documentElement);
-	const parentBg = getBgColor(rootEl.value.parentElement);
+	const parentBg = getBgColor(rootEl.value!.parentElement!);
 	const myBg = computedStyle.getPropertyValue('--panel');
 	bgSame.value = parentBg === myBg;
 });
diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue
index d1b1956a03..d0e8750e6a 100644
--- a/packages/frontend/src/components/MkFollowButton.vue
+++ b/packages/frontend/src/components/MkFollowButton.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -38,11 +38,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { onBeforeUnmount, onMounted, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { useStream } from '@/stream.js';
 import { i18n } from '@/i18n.js';
 import { claimAchievement } from '@/scripts/achievements.js';
 import { $i } from '@/account.js';
-import { defaultStore } from "@/store.js";
+import { defaultStore } from '@/store.js';
 
 const props = withDefaults(defineProps<{
 	user: Misskey.entities.UserDetailed,
@@ -63,7 +64,7 @@ const wait = ref(false);
 const connection = useStream().useChannel('main');
 
 if (props.user.isFollowing == null) {
-	os.api('users/show', {
+	misskeyApi('users/show', {
 		userId: props.user.id,
 	})
 		.then(onFollowChange);
@@ -83,22 +84,22 @@ async function onClick() {
 		if (isFollowing.value) {
 			const { canceled } = await os.confirm({
 				type: 'warning',
-				text: i18n.t('unfollowConfirm', { name: props.user.name || props.user.username }),
+				text: i18n.tsx.unfollowConfirm({ name: props.user.name || props.user.username }),
 			});
 
 			if (canceled) return;
 
-			await os.api('following/delete', {
+			await misskeyApi('following/delete', {
 				userId: props.user.id,
 			});
 		} else {
 			if (hasPendingFollowRequestFromYou.value) {
-				await os.api('following/requests/cancel', {
+				await misskeyApi('following/requests/cancel', {
 					userId: props.user.id,
 				});
 				hasPendingFollowRequestFromYou.value = false;
 			} else {
-				await os.api('following/create', {
+				await misskeyApi('following/create', {
 					userId: props.user.id,
 					withReplies: defaultStore.state.defaultWithReplies,
 				});
diff --git a/packages/frontend/src/components/MkForgotPassword.vue b/packages/frontend/src/components/MkForgotPassword.vue
index 9b57688a02..35112ad45d 100644
--- a/packages/frontend/src/components/MkForgotPassword.vue
+++ b/packages/frontend/src/components/MkForgotPassword.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	ref="dialog"
 	:width="370"
 	:height="400"
-	@close="dialog.close()"
+	@close="dialog?.close()"
 	@closed="emit('closed')"
 >
 	<template #header>{{ i18n.ts.forgotPassword }}</template>
@@ -66,6 +66,6 @@ async function onSubmit() {
 		email: email.value,
 	});
 	emit('done');
-	dialog.value.close();
+	dialog.value?.close();
 }
 </script>
diff --git a/packages/frontend/src/components/MkFormDialog.vue b/packages/frontend/src/components/MkFormDialog.vue
index 6f882cfab7..deedc5badb 100644
--- a/packages/frontend/src/components/MkFormDialog.vue
+++ b/packages/frontend/src/components/MkFormDialog.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -20,41 +20,45 @@ SPDX-License-Identifier: AGPL-3.0-only
 	</template>
 
 	<MkSpacer :marginMin="20" :marginMax="32">
-		<div class="_gaps_m">
-			<template v-for="item in Object.keys(form).filter(item => !form[item].hidden)">
-				<MkInput v-if="form[item].type === 'number'" v-model="values[item]" type="number" :step="form[item].step || 1">
-					<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ i18n.ts.optional }})</span></template>
-					<template v-if="form[item].description" #caption>{{ form[item].description }}</template>
+		<div v-if="Object.keys(form).filter(item => !form[item].hidden).length > 0" class="_gaps_m">
+			<template v-for="(v, k) in Object.fromEntries(Object.entries(form).filter(([_, v]) => !('hidden' in v) || 'hidden' in v && !v.hidden))">
+				<MkInput v-if="v.type === 'number'" v-model="values[k]" type="number" :step="v.step || 1">
+					<template #label><span v-text="v.label || k"></span><span v-if="v.required === false"> ({{ i18n.ts.optional }})</span></template>
+					<template v-if="v.description" #caption>{{ v.description }}</template>
 				</MkInput>
-				<MkInput v-else-if="form[item].type === 'string' && !form[item].multiline" v-model="values[item]" type="text" :mfmAutocomplete="form[item].treatAsMfm">
-					<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ i18n.ts.optional }})</span></template>
-					<template v-if="form[item].description" #caption>{{ form[item].description }}</template>
+				<MkInput v-else-if="v.type === 'string' && !v.multiline" v-model="values[k]" type="text" :mfmAutocomplete="v.treatAsMfm">
+					<template #label><span v-text="v.label || k"></span><span v-if="v.required === false"> ({{ i18n.ts.optional }})</span></template>
+					<template v-if="v.description" #caption>{{ v.description }}</template>
 				</MkInput>
-				<MkTextarea v-else-if="form[item].type === 'string' && form[item].multiline" v-model="values[item]" :mfmAutocomplete="form[item].treatAsMfm" :mfmPreview="form[item].treatAsMfm">
-					<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ i18n.ts.optional }})</span></template>
-					<template v-if="form[item].description" #caption>{{ form[item].description }}</template>
+				<MkTextarea v-else-if="v.type === 'string' && v.multiline" v-model="values[k]" :mfmAutocomplete="v.treatAsMfm" :mfmPreview="v.treatAsMfm">
+					<template #label><span v-text="v.label || k"></span><span v-if="v.required === false"> ({{ i18n.ts.optional }})</span></template>
+					<template v-if="v.description" #caption>{{ v.description }}</template>
 				</MkTextarea>
-				<MkSwitch v-else-if="form[item].type === 'boolean'" v-model="values[item]">
-					<span v-text="form[item].label || item"></span>
-					<template v-if="form[item].description" #caption>{{ form[item].description }}</template>
+				<MkSwitch v-else-if="v.type === 'boolean'" v-model="values[k]">
+					<span v-text="v.label || k"></span>
+					<template v-if="v.description" #caption>{{ v.description }}</template>
 				</MkSwitch>
-				<MkSelect v-else-if="form[item].type === 'enum'" v-model="values[item]">
-					<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ i18n.ts.optional }})</span></template>
-					<option v-for="item in form[item].enum" :key="item.value" :value="item.value">{{ item.label }}</option>
+				<MkSelect v-else-if="v.type === 'enum'" v-model="values[k]">
+					<template #label><span v-text="v.label || k"></span><span v-if="v.required === false"> ({{ i18n.ts.optional }})</span></template>
+					<option v-for="option in v.enum" :key="option.value" :value="option.value">{{ option.label }}</option>
 				</MkSelect>
-				<MkRadios v-else-if="form[item].type === 'radio'" v-model="values[item]">
-					<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ i18n.ts.optional }})</span></template>
-					<option v-for="item in form[item].options" :key="item.value" :value="item.value">{{ item.label }}</option>
+				<MkRadios v-else-if="v.type === 'radio'" v-model="values[k]">
+					<template #label><span v-text="v.label || k"></span><span v-if="v.required === false"> ({{ i18n.ts.optional }})</span></template>
+					<option v-for="option in v.options" :key="option.value" :value="option.value">{{ option.label }}</option>
 				</MkRadios>
-				<MkRange v-else-if="form[item].type === 'range'" v-model="values[item]" :min="form[item].min" :max="form[item].max" :step="form[item].step" :textConverter="form[item].textConverter">
-					<template #label><span v-text="form[item].label || item"></span><span v-if="form[item].required === false"> ({{ i18n.ts.optional }})</span></template>
-					<template v-if="form[item].description" #caption>{{ form[item].description }}</template>
+				<MkRange v-else-if="v.type === 'range'" v-model="values[k]" :min="v.min" :max="v.max" :step="v.step" :textConverter="v.textConverter">
+					<template #label><span v-text="v.label || k"></span><span v-if="v.required === false"> ({{ i18n.ts.optional }})</span></template>
+					<template v-if="v.description" #caption>{{ v.description }}</template>
 				</MkRange>
-				<MkButton v-else-if="form[item].type === 'button'" @click="form[item].action($event, values)">
-					<span v-text="form[item].content || item"></span>
+				<MkButton v-else-if="v.type === 'button'" @click="v.action($event, values)">
+					<span v-text="v.content || k"></span>
 				</MkButton>
 			</template>
 		</div>
+		<div v-else class="_fullinfo">
+			<img :src="infoImageUrl" class="_ghost"/>
+			<div>{{ i18n.ts.nothing }}</div>
+		</div>
 	</MkSpacer>
 </MkModalWindow>
 </template>
@@ -68,19 +72,23 @@ import MkSelect from './MkSelect.vue';
 import MkRange from './MkRange.vue';
 import MkButton from './MkButton.vue';
 import MkRadios from './MkRadios.vue';
+import type { Form } from '@/scripts/form.js';
 import MkModalWindow from '@/components/MkModalWindow.vue';
 import { i18n } from '@/i18n.js';
+import { infoImageUrl } from '@/instance.js';
 
 const props = defineProps<{
 	title: string;
-	form: any;
+	form: Form;
 }>();
 
 const emit = defineEmits<{
 	(ev: 'done', v: {
-		canceled?: boolean;
-		result?: any;
+		canceled: true;
+	} | {
+		result: Record<string, any>;
 	}): void;
+	(ev: 'closed'): void;
 }>();
 
 const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
@@ -94,13 +102,13 @@ function ok() {
 	emit('done', {
 		result: values,
 	});
-	dialog.value.close();
+	dialog.value?.close();
 }
 
 function cancel() {
 	emit('done', {
 		canceled: true,
 	});
-	dialog.value.close();
+	dialog.value?.close();
 }
 </script>
diff --git a/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts b/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts
index 035b727a35..a433ad680b 100644
--- a/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts
+++ b/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts
@@ -1,11 +1,10 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { expect } from '@storybook/jest';
-import { userEvent, waitFor, within } from '@storybook/testing-library';
+import { expect, userEvent, waitFor, within } from '@storybook/test';
 import { StoryObj } from '@storybook/vue3';
 import { galleryPost } from '../../.storybook/fakes.js';
 import MkGalleryPostPreview from './MkGalleryPostPreview.vue';
diff --git a/packages/frontend/src/components/MkGalleryPostPreview.vue b/packages/frontend/src/components/MkGalleryPostPreview.vue
index 316632b1a6..47cccd9b7c 100644
--- a/packages/frontend/src/components/MkGalleryPostPreview.vue
+++ b/packages/frontend/src/components/MkGalleryPostPreview.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -14,8 +14,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 					leaveActiveClass: $style.transition_toggle_leaveActive,
 					leaveToClass: $style.transition_toggle_leaveTo,
 				}"
-				:src="post.files[0].thumbnailUrl"
-				:hash="post.files[0].blurhash"
+				:src="post.files?.[0]?.thumbnailUrl"
+				:hash="post.files?.[0]?.blurhash"
 				:forceBlurhash="!show"
 			/>
 		</Transition>
diff --git a/packages/frontend/src/components/MkGoogle.vue b/packages/frontend/src/components/MkGoogle.vue
index c0b20507fc..c92a49d32a 100644
--- a/packages/frontend/src/components/MkGoogle.vue
+++ b/packages/frontend/src/components/MkGoogle.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkHeatmap.vue b/packages/frontend/src/components/MkHeatmap.vue
index a57e6c9292..0cc0df9911 100644
--- a/packages/frontend/src/components/MkHeatmap.vue
+++ b/packages/frontend/src/components/MkHeatmap.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -15,7 +15,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { onMounted, nextTick, watch, shallowRef, ref } from 'vue';
 import { Chart } from 'chart.js';
-import * as os from '@/os.js';
+import * as Misskey from 'misskey-js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { defaultStore } from '@/store.js';
 import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
 import { alpha } from '@/scripts/color.js';
@@ -23,14 +24,21 @@ import { initChart } from '@/scripts/init-chart.js';
 
 initChart();
 
-const props = defineProps<{
-	src: string;
-}>();
+export type HeatmapSource = 'active-users' | 'notes' | 'ap-requests-inbox-received' | 'ap-requests-deliver-succeeded' | 'ap-requests-deliver-failed';
 
-const rootEl = shallowRef<HTMLDivElement>(null);
-const chartEl = shallowRef<HTMLCanvasElement>(null);
+const props = withDefaults(defineProps<{
+	src: HeatmapSource;
+	user?: Misskey.entities.User;
+	label?: string;
+}>(), {
+	user: undefined,
+	label: '',
+});
+
+const rootEl = shallowRef<HTMLDivElement | null>(null);
+const chartEl = shallowRef<HTMLCanvasElement | null>(null);
 const now = new Date();
-let chartInstance: Chart = null;
+let chartInstance: Chart | null = null;
 const fetching = ref(true);
 
 const { handler: externalTooltipHandler } = useChartTooltip({
@@ -38,6 +46,7 @@ const { handler: externalTooltipHandler } = useChartTooltip({
 });
 
 async function renderChart() {
+	if (rootEl.value == null) return;
 	if (chartInstance) {
 		chartInstance.destroy();
 	}
@@ -56,7 +65,7 @@ async function renderChart() {
 		return new Date(y, m, d - ago);
 	};
 
-	const format = (arr) => {
+	const format = (arr: number[]) => {
 		return arr.map((v, i) => {
 			const dt = getDate(i);
 			const iso = `${dt.getFullYear()}-${(dt.getMonth() + 1).toString().padStart(2, '0')}-${dt.getDate().toString().padStart(2, '0')}`;
@@ -69,22 +78,27 @@ async function renderChart() {
 		});
 	};
 
-	let values;
+	let values: number[] = [];
 
 	if (props.src === 'active-users') {
-		const raw = await os.api('charts/active-users', { limit: chartLimit, span: 'day' });
+		const raw = await misskeyApi('charts/active-users', { limit: chartLimit, span: 'day' });
 		values = raw.readWrite;
 	} else if (props.src === 'notes') {
-		const raw = await os.api('charts/notes', { limit: chartLimit, span: 'day' });
-		values = raw.local.inc;
+		if (props.user) {
+			const raw = await misskeyApi('charts/user/notes', { userId: props.user.id, limit: chartLimit, span: 'day' });
+			values = raw.inc;
+		} else {
+			const raw = await misskeyApi('charts/notes', { limit: chartLimit, span: 'day' });
+			values = raw.local.inc;
+		}
 	} else if (props.src === 'ap-requests-inbox-received') {
-		const raw = await os.api('charts/ap-request', { limit: chartLimit, span: 'day' });
+		const raw = await misskeyApi('charts/ap-request', { limit: chartLimit, span: 'day' });
 		values = raw.inboxReceived;
 	} else if (props.src === 'ap-requests-deliver-succeeded') {
-		const raw = await os.api('charts/ap-request', { limit: chartLimit, span: 'day' });
+		const raw = await misskeyApi('charts/ap-request', { limit: chartLimit, span: 'day' });
 		values = raw.deliverSucceeded;
 	} else if (props.src === 'ap-requests-deliver-failed') {
-		const raw = await os.api('charts/ap-request', { limit: chartLimit, span: 'day' });
+		const raw = await misskeyApi('charts/ap-request', { limit: chartLimit, span: 'day' });
 		values = raw.deliverFailed;
 	}
 
@@ -101,25 +115,25 @@ async function renderChart() {
 
 	const marginEachCell = 4;
 
+	if (chartEl.value == null) return;
+
 	chartInstance = new Chart(chartEl.value, {
 		type: 'matrix',
 		data: {
 			datasets: [{
-				label: 'Read & Write',
-				data: format(values),
-				pointRadius: 0,
+				label: props.label,
+				data: format(values) as any,
 				borderWidth: 0,
-				borderJoinStyle: 'round',
 				borderRadius: 3,
 				backgroundColor(c) {
-					const value = c.dataset.data[c.dataIndex].v;
+					// @ts-expect-error TS(2339)
+					const value = c.dataset.data[c.dataIndex].v as number;
 					let a = (value - min) / max;
 					if (value !== 0) { // 0でない限りは完全に不可視にはしない
 						a = Math.max(a, 0.05);
 					}
 					return alpha(color, a);
 				},
-				fill: true,
 				width(c) {
 					const a = c.chart.chartArea ?? {};
 					return (a.right - a.left) / weeks - marginEachCell;
@@ -128,6 +142,9 @@ async function renderChart() {
 					const a = c.chart.chartArea ?? {};
 					return (a.bottom - a.top) / 7 - marginEachCell;
 				},
+			/* @see <https://github.com/misskey-dev/misskey/pull/10365#discussion_r1155511107>
+			}] satisfies ChartData[],
+			 */
 			}],
 		},
 		options: {
@@ -190,12 +207,14 @@ async function renderChart() {
 					enabled: false,
 					callbacks: {
 						title(context) {
-							const v = context[0].dataset.data[context[0].dataIndex];
-							return v.d;
+							// @ts-expect-error TS(2339)
+							return context[0].dataset.data[context[0].dataIndex].d;
 						},
 						label(context) {
 							const v = context.dataset.data[context.dataIndex];
-							return ['Active: ' + v.v];
+
+							// @ts-expect-error TS(2339)
+							return [v.v];
 						},
 					},
 					//mode: 'index',
diff --git a/packages/frontend/src/components/MkHorizontalSwipe.vue b/packages/frontend/src/components/MkHorizontalSwipe.vue
new file mode 100644
index 0000000000..196c962a06
--- /dev/null
+++ b/packages/frontend/src/components/MkHorizontalSwipe.vue
@@ -0,0 +1,239 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div
+	ref="rootEl"
+	:class="[$style.transitionRoot, { [$style.enableAnimation]: shouldAnimate }]"
+	@touchstart.passive="touchStart"
+	@touchmove.passive="touchMove"
+	@touchend.passive="touchEnd"
+>
+	<Transition
+		:class="[$style.transitionChildren, { [$style.swiping]: isSwipingForClass }]"
+		:enterActiveClass="$style.swipeAnimation_enterActive"
+		:leaveActiveClass="$style.swipeAnimation_leaveActive"
+		:enterFromClass="transitionName === 'swipeAnimationLeft' ? $style.swipeAnimationLeft_enterFrom : $style.swipeAnimationRight_enterFrom"
+		:leaveToClass="transitionName === 'swipeAnimationLeft' ? $style.swipeAnimationLeft_leaveTo : $style.swipeAnimationRight_leaveTo"
+		:style="`--swipe: ${pullDistance}px;`"
+	>
+		<!-- 【注意】slot内の最上位要素に動的にkeyを設定すること -->
+		<!-- 各最上位要素にユニークなkeyの指定がないとTransitionがうまく動きません -->
+		<slot></slot>
+	</Transition>
+</div>
+</template>
+<script lang="ts" setup>
+import { ref, shallowRef, computed, nextTick, watch } from 'vue';
+import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
+import { defaultStore } from '@/store.js';
+import { isHorizontalSwipeSwiping as isSwiping } from '@/scripts/touch.js';
+
+const rootEl = shallowRef<HTMLDivElement>();
+
+// eslint-disable-next-line no-undef
+const tabModel = defineModel<string>('tab');
+
+const props = defineProps<{
+	tabs: Tab[];
+}>();
+
+const emit = defineEmits<{
+	(ev: 'swiped', newKey: string, direction: 'left' | 'right'): void;
+}>();
+
+const shouldAnimate = computed(() => defaultStore.reactiveState.enableHorizontalSwipe.value || defaultStore.reactiveState.animation.value);
+
+// ▼ しきい値 ▼ //
+
+// スワイプと判定される最小の距離
+const MIN_SWIPE_DISTANCE = 20;
+
+// スワイプ時の動作を発火する最小の距離
+const SWIPE_DISTANCE_THRESHOLD = 70;
+
+// スワイプを中断するY方向の移動距離
+const SWIPE_ABORT_Y_THRESHOLD = 75;
+
+// スワイプできる最大の距離
+const MAX_SWIPE_DISTANCE = 120;
+
+// ▲ しきい値 ▲ //
+
+let startScreenX: number | null = null;
+let startScreenY: number | null = null;
+
+const currentTabIndex = computed(() => props.tabs.findIndex(tab => tab.key === tabModel.value));
+
+const pullDistance = ref(0);
+const isSwipingForClass = ref(false);
+let swipeAborted = false;
+
+function touchStart(event: TouchEvent) {
+	if (!defaultStore.reactiveState.enableHorizontalSwipe.value) return;
+
+	if (event.touches.length !== 1) return;
+
+	if (hasSomethingToDoWithXSwipe(event.target as HTMLElement)) return;
+
+	startScreenX = event.touches[0].screenX;
+	startScreenY = event.touches[0].screenY;
+}
+
+function touchMove(event: TouchEvent) {
+	if (!defaultStore.reactiveState.enableHorizontalSwipe.value) return;
+
+	if (event.touches.length !== 1) return;
+
+	if (startScreenX == null || startScreenY == null) return;
+
+	if (swipeAborted) return;
+
+	if (hasSomethingToDoWithXSwipe(event.target as HTMLElement)) return;
+
+	let distanceX = event.touches[0].screenX - startScreenX;
+	let distanceY = event.touches[0].screenY - startScreenY;
+
+	if (Math.abs(distanceY) > SWIPE_ABORT_Y_THRESHOLD) {
+		swipeAborted = true;
+
+		pullDistance.value = 0;
+		isSwiping.value = false;
+		setTimeout(() => {
+			isSwipingForClass.value = false;
+		}, 400);
+
+		return;
+	}
+
+	if (Math.abs(distanceX) < MIN_SWIPE_DISTANCE) return;
+	if (Math.abs(distanceX) > MAX_SWIPE_DISTANCE) return;
+
+	if (currentTabIndex.value === 0 || props.tabs[currentTabIndex.value - 1].onClick) {
+		distanceX = Math.min(distanceX, 0);
+	}
+	if (currentTabIndex.value === props.tabs.length - 1 || props.tabs[currentTabIndex.value + 1].onClick) {
+		distanceX = Math.max(distanceX, 0);
+	}
+	if (distanceX === 0) return;
+
+	isSwiping.value = true;
+	isSwipingForClass.value = true;
+	nextTick(() => {
+		// グリッチを控えるため、1.5px以上の差がないと更新しない
+		if (Math.abs(distanceX - pullDistance.value) < 1.5) return;
+		pullDistance.value = distanceX;
+	});
+}
+
+function touchEnd(event: TouchEvent) {
+	if (swipeAborted) {
+		swipeAborted = false;
+		return;
+	}
+
+	if (!defaultStore.reactiveState.enableHorizontalSwipe.value) return;
+
+	if (event.touches.length !== 0) return;
+
+	if (startScreenX == null) return;
+
+	if (!isSwiping.value) return;
+
+	if (hasSomethingToDoWithXSwipe(event.target as HTMLElement)) return;
+
+	const distance = event.changedTouches[0].screenX - startScreenX;
+
+	if (Math.abs(distance) > SWIPE_DISTANCE_THRESHOLD) {
+		if (distance > 0) {
+			if (props.tabs[currentTabIndex.value - 1] && !props.tabs[currentTabIndex.value - 1].onClick) {
+				tabModel.value = props.tabs[currentTabIndex.value - 1].key;
+				emit('swiped', props.tabs[currentTabIndex.value - 1].key, 'right');
+			}
+		} else {
+			if (props.tabs[currentTabIndex.value + 1] && !props.tabs[currentTabIndex.value + 1].onClick) {
+				tabModel.value = props.tabs[currentTabIndex.value + 1].key;
+				emit('swiped', props.tabs[currentTabIndex.value + 1].key, 'left');
+			}
+		}
+	}
+
+	pullDistance.value = 0;
+	isSwiping.value = false;
+	window.setTimeout(() => {
+		isSwipingForClass.value = false;
+	}, 400);
+}
+
+/** 横スワイプに関与する可能性のある要素を調べる */
+function hasSomethingToDoWithXSwipe(el: HTMLElement) {
+	if (['INPUT', 'TEXTAREA'].includes(el.tagName)) return true;
+	if (el.isContentEditable) return true;
+	if (el.scrollWidth > el.clientWidth) return true;
+
+	const style = window.getComputedStyle(el);
+	if (['absolute', 'fixed', 'sticky'].includes(style.position)) return true;
+	if (['scroll', 'auto'].includes(style.overflowX)) return true;
+	if (style.touchAction === 'pan-x') return true;
+
+	if (el.parentElement && el.parentElement !== rootEl.value) {
+		return hasSomethingToDoWithXSwipe(el.parentElement);
+	} else {
+		return false;
+	}
+}
+
+const transitionName = ref<'swipeAnimationLeft' | 'swipeAnimationRight' | undefined>(undefined);
+
+watch(tabModel, (newTab, oldTab) => {
+	const newIndex = props.tabs.findIndex(tab => tab.key === newTab);
+	const oldIndex = props.tabs.findIndex(tab => tab.key === oldTab);
+
+	if (oldIndex >= 0 && newIndex && oldIndex < newIndex) {
+		transitionName.value = 'swipeAnimationLeft';
+	} else {
+		transitionName.value = 'swipeAnimationRight';
+	}
+
+	window.setTimeout(() => {
+		transitionName.value = undefined;
+	}, 400);
+});
+</script>
+
+<style lang="scss" module>
+.transitionRoot {
+	touch-action: pan-y pinch-zoom;
+	display: grid;
+	grid-template-columns: 100%;
+	overflow: clip;
+}
+
+.transitionChildren {
+	grid-area: 1 / 1 / 2 / 2;
+	transform: translateX(var(--swipe));
+}
+
+.enableAnimation .transitionChildren {
+	&.swipeAnimation_enterActive,
+	&.swipeAnimation_leaveActive {
+		transition: transform .3s cubic-bezier(0.65, 0.05, 0.36, 1);
+	}
+
+	&.swipeAnimationRight_leaveTo,
+	&.swipeAnimationLeft_enterFrom {
+		transform: translateX(calc(100% + 24px));
+	}
+
+	&.swipeAnimationRight_enterFrom,
+	&.swipeAnimationLeft_leaveTo {
+		transform: translateX(calc(-100% - 24px));
+	}
+}
+
+.swiping {
+	transition: transform .2s ease-out;
+}
+</style>
diff --git a/packages/frontend/src/components/MkImgWithBlurhash.vue b/packages/frontend/src/components/MkImgWithBlurhash.vue
index 942861e1f4..4e3fafe845 100644
--- a/packages/frontend/src/components/MkImgWithBlurhash.vue
+++ b/packages/frontend/src/components/MkImgWithBlurhash.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -73,7 +73,7 @@ const props = withDefaults(defineProps<{
 		leaveFromClass?: string;
 	} | null;
 	src?: string | null;
-	hash?: string;
+	hash?: string | null;
 	alt?: string | null;
 	title?: string | null;
 	height?: number;
diff --git a/packages/frontend/src/components/MkInfo.vue b/packages/frontend/src/components/MkInfo.vue
index 6e643639f2..9a5874b5c0 100644
--- a/packages/frontend/src/components/MkInfo.vue
+++ b/packages/frontend/src/components/MkInfo.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkInput.vue b/packages/frontend/src/components/MkInput.vue
index b4b4e1b0b7..b026903b66 100644
--- a/packages/frontend/src/components/MkInput.vue
+++ b/packages/frontend/src/components/MkInput.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -88,17 +88,18 @@ const focused = ref(false);
 const changed = ref(false);
 const invalid = ref(false);
 const filled = computed(() => v.value !== '' && v.value != null);
-const inputEl = shallowRef<HTMLElement>();
+const inputEl = shallowRef<HTMLInputElement>();
 const prefixEl = shallowRef<HTMLElement>();
 const suffixEl = shallowRef<HTMLElement>();
 const height =
 	props.small ? 33 :
 	props.large ? 39 :
 	36;
-let autocomplete: Autocomplete;
+let autocompleteWorker: Autocomplete | null = null;
 
-const focus = () => inputEl.value.focus();
-const onInput = (ev: KeyboardEvent) => {
+const focus = () => inputEl.value?.focus();
+const onInput = (event: Event) => {
+	const ev = event as KeyboardEvent;
 	changed.value = true;
 	emit('change', ev);
 };
@@ -115,9 +116,9 @@ const onKeydown = (ev: KeyboardEvent) => {
 const updated = () => {
 	changed.value = false;
 	if (type.value === 'number') {
-		emit('update:modelValue', parseFloat(v.value));
+		emit('update:modelValue', typeof v.value === 'number' ? v.value : parseFloat(v.value ?? '0'));
 	} else {
-		emit('update:modelValue', v.value);
+		emit('update:modelValue', v.value ?? '');
 	}
 };
 
@@ -127,7 +128,7 @@ watch(modelValue, newValue => {
 	v.value = newValue;
 });
 
-watch(v, newValue => {
+watch(v, () => {
 	if (!props.manualSave) {
 		if (props.debounce) {
 			debouncedUpdated();
@@ -136,12 +137,14 @@ watch(v, newValue => {
 		}
 	}
 
-	invalid.value = inputEl.value.validity.badInput;
+	invalid.value = inputEl.value?.validity.badInput ?? true;
 });
 
 // このコンポーネントが作成された時、非表示状態である場合がある
 // 非表示状態だと要素の幅などは0になってしまうので、定期的に計算する
 useInterval(() => {
+	if (inputEl.value == null) return;
+
 	if (prefixEl.value) {
 		if (prefixEl.value.offsetWidth) {
 			inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px';
@@ -163,15 +166,15 @@ onMounted(() => {
 			focus();
 		}
 	});
-	
-	if (props.mfmAutocomplete) {
-		autocomplete = new Autocomplete(inputEl.value, v, props.mfmAutocomplete === true ? null : props.mfmAutocomplete);
+
+	if (props.mfmAutocomplete && inputEl.value) {
+		autocompleteWorker = new Autocomplete(inputEl.value, v, props.mfmAutocomplete === true ? undefined : props.mfmAutocomplete);
 	}
 });
 
 onUnmounted(() => {
-	if (autocomplete) {
-		autocomplete.detach();
+	if (autocompleteWorker) {
+		autocompleteWorker.detach();
 	}
 });
 
diff --git a/packages/frontend/src/components/MkInstanceCardMini.vue b/packages/frontend/src/components/MkInstanceCardMini.vue
index 9cde197e19..feb62415aa 100644
--- a/packages/frontend/src/components/MkInstanceCardMini.vue
+++ b/packages/frontend/src/components/MkInstanceCardMini.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkMiniChart from '@/components/MkMiniChart.vue';
-import * as os from '@/os.js';
+import { misskeyApiGet } from '@/scripts/misskey-api.js';
 import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
 
 const props = defineProps<{
@@ -27,7 +27,7 @@ const props = defineProps<{
 
 const chartValues = ref<number[] | null>(null);
 
-os.apiGet('charts/instance', { host: props.instance.host, limit: 16 + 1, span: 'day' }).then(res => {
+misskeyApiGet('charts/instance', { host: props.instance.host, limit: 16 + 1, span: 'day' }).then(res => {
 	// 今日のぶんの値はまだ途中の値であり、それも含めると大抵の場合前日よりも下降しているようなグラフになってしまうため今日は弾く
 	res['requests.received'].splice(0, 1);
 	chartValues.value = res['requests.received'];
diff --git a/packages/frontend/src/components/MkInstanceStats.vue b/packages/frontend/src/components/MkInstanceStats.vue
index 7b763ad385..d74c885041 100644
--- a/packages/frontend/src/components/MkInstanceStats.vue
+++ b/packages/frontend/src/components/MkInstanceStats.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -51,7 +51,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<option value="ap-requests-deliver-failed">AP Requests: deliverFailed</option>
 		</MkSelect>
 		<div class="_panel" :class="$style.heatmap">
-			<MkHeatmap :src="heatmapSrc"/>
+			<MkHeatmap :src="heatmapSrc" :label="'Read & Write'"/>
 		</div>
 	</MkFoldableSection>
 
@@ -90,8 +90,9 @@ import MkSelect from '@/components/MkSelect.vue';
 import MkChart from '@/components/MkChart.vue';
 import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
 import * as os from '@/os.js';
+import { misskeyApiGet } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
-import MkHeatmap from '@/components/MkHeatmap.vue';
+import MkHeatmap, { type HeatmapSource } from '@/components/MkHeatmap.vue';
 import MkFoldableSection from '@/components/MkFoldableSection.vue';
 import MkRetentionHeatmap from '@/components/MkRetentionHeatmap.vue';
 import MkRetentionLineChart from '@/components/MkRetentionLineChart.vue';
@@ -102,7 +103,7 @@ initChart();
 const chartLimit = 500;
 const chartSpan = ref<'hour' | 'day'>('hour');
 const chartSrc = ref('active-users');
-const heatmapSrc = ref('active-users');
+const heatmapSrc = ref<HeatmapSource>('active-users');
 const subDoughnutEl = shallowRef<HTMLCanvasElement>();
 const pubDoughnutEl = shallowRef<HTMLCanvasElement>();
 
@@ -137,7 +138,8 @@ function createDoughnut(chartEl, tooltip, data) {
 				},
 			},
 			onClick: (ev) => {
-				const hit = chartInstance.getElementsAtEventForMode(ev, 'nearest', { intersect: true }, false)[0];
+				if (ev.native == null) return;
+				const hit = chartInstance.getElementsAtEventForMode(ev.native, 'nearest', { intersect: true }, false)[0];
 				if (hit && data[hit.index].onClick) {
 					data[hit.index].onClick();
 				}
@@ -162,24 +164,47 @@ function createDoughnut(chartEl, tooltip, data) {
 }
 
 onMounted(() => {
-	os.apiGet('federation/stats', { limit: 30 }).then(fedStats => {
-		createDoughnut(subDoughnutEl.value, externalTooltipHandler1, fedStats.topSubInstances.map(x => ({
+	misskeyApiGet('federation/stats', { limit: 30 }).then(fedStats => {
+		type ChartData = {
+			name: string,
+			color: string | null,
+			value: number,
+			onClick?: () => void,
+		}[];
+
+		const subs: ChartData = fedStats.topSubInstances.map(x => ({
 			name: x.host,
 			color: x.themeColor,
 			value: x.followersCount,
 			onClick: () => {
 				os.pageWindow(`/instance-info/${x.host}`);
 			},
-		})).concat([{ name: '(other)', color: '#80808080', value: fedStats.otherFollowersCount }]));
+		}));
 
-		createDoughnut(pubDoughnutEl.value, externalTooltipHandler2, fedStats.topPubInstances.map(x => ({
+		subs.push({
+			name: '(other)',
+			color: '#80808080',
+			value: fedStats.otherFollowersCount,
+		});
+
+		createDoughnut(subDoughnutEl.value, externalTooltipHandler1, subs);
+
+		const pubs: ChartData = fedStats.topPubInstances.map(x => ({
 			name: x.host,
 			color: x.themeColor,
 			value: x.followingCount,
 			onClick: () => {
 				os.pageWindow(`/instance-info/${x.host}`);
 			},
-		})).concat([{ name: '(other)', color: '#80808080', value: fedStats.otherFollowingCount }]));
+		}));
+
+		pubs.push({
+			name: '(other)',
+			color: '#80808080',
+			value: fedStats.otherFollowingCount,
+		});
+
+		createDoughnut(pubDoughnutEl.value, externalTooltipHandler2, pubs);
 	});
 });
 </script>
diff --git a/packages/frontend/src/components/MkInstanceTicker.vue b/packages/frontend/src/components/MkInstanceTicker.vue
index e358a1c549..094d2f177f 100644
--- a/packages/frontend/src/components/MkInstanceTicker.vue
+++ b/packages/frontend/src/components/MkInstanceTicker.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -18,9 +18,9 @@ import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
 
 const props = defineProps<{
 	instance?: {
-		faviconUrl?: string
-		name: string
-		themeColor?: string
+		faviconUrl?: string | null
+		name?: string | null
+		themeColor?: string | null
 	}
 }>();
 
@@ -30,7 +30,7 @@ const instance = props.instance ?? {
 	themeColor: (document.querySelector('meta[name="theme-color-orig"]') as HTMLMetaElement).content,
 };
 
-const faviconUrl = computed(() => props.instance ? getProxiedImageUrlNullable(props.instance.faviconUrl, 'preview') : getProxiedImageUrlNullable(Instance.iconUrl, 'preview') ?? getProxiedImageUrlNullable(Instance.faviconUrl, 'preview') ?? '/favicon.ico');
+const faviconUrl = computed(() => props.instance ? getProxiedImageUrlNullable(props.instance.faviconUrl, 'preview') : getProxiedImageUrlNullable(Instance.iconUrl, 'preview') ?? '/favicon.ico');
 
 const themeColor = instance.themeColor ?? '#777777';
 
diff --git a/packages/frontend/src/components/MkInviteCode.stories.impl.ts b/packages/frontend/src/components/MkInviteCode.stories.impl.ts
index 2ea32dd3b6..456d215288 100644
--- a/packages/frontend/src/components/MkInviteCode.stories.impl.ts
+++ b/packages/frontend/src/components/MkInviteCode.stories.impl.ts
@@ -1,11 +1,11 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
 import { StoryObj } from '@storybook/vue3';
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
 import { userDetailed, inviteCode } from '../../.storybook/fakes.js';
 import { commonHandlers } from '../../.storybook/mocks.js';
 import MkInviteCode from './MkInviteCode.vue';
@@ -39,8 +39,8 @@ export const Default = {
 		msw: {
 			handlers: [
 				...commonHandlers,
-				rest.post('/api/users/show', (req, res, ctx) => {
-					return res(ctx.json(userDetailed(req.params.userId as string)));
+				http.post('/api/users/show', ({ params }) => {
+					return HttpResponse.json(userDetailed(params.userId as string));
 				}),
 			],
 		},
diff --git a/packages/frontend/src/components/MkInviteCode.vue b/packages/frontend/src/components/MkInviteCode.vue
index 54d997d1c9..b095df20e5 100644
--- a/packages/frontend/src/components/MkInviteCode.vue
+++ b/packages/frontend/src/components/MkInviteCode.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkKeyValue.vue b/packages/frontend/src/components/MkKeyValue.vue
index 7a1a5eb016..2175c0e888 100644
--- a/packages/frontend/src/components/MkKeyValue.vue
+++ b/packages/frontend/src/components/MkKeyValue.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkLaunchPad.vue b/packages/frontend/src/components/MkLaunchPad.vue
index 099082f539..e232b4d66f 100644
--- a/packages/frontend/src/components/MkLaunchPad.vue
+++ b/packages/frontend/src/components/MkLaunchPad.vue
@@ -1,10 +1,10 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<MkModal ref="modal" v-slot="{ type, maxHeight }" :preferType="preferedModalType" :anchor="anchor" :transparentBg="true" :src="src" @click="modal.close()" @closed="emit('closed')">
+<MkModal ref="modal" v-slot="{ type, maxHeight }" :preferType="preferedModalType" :anchor="anchor" :transparentBg="true" :src="src" @click="modal?.close()" @closed="emit('closed')">
 	<div class="szkkfdyq _popup _shadow" :class="{ asDrawer: type === 'drawer' }" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : '' }">
 		<div class="main">
 			<template v-for="item in items" :key="item.text">
@@ -63,7 +63,7 @@ const items = Object.keys(navbarItemDef).filter(k => !menu.includes(k)).map(k =>
 }));
 
 function close() {
-	modal.value.close();
+	modal.value?.close();
 }
 </script>
 
@@ -119,6 +119,7 @@ function close() {
 				margin-top: 12px;
 				font-size: 0.8em;
 				line-height: 1.5em;
+				text-align: center;
 			}
 
 			> .indicatorWithValue {
@@ -138,7 +139,7 @@ function close() {
 				left: 32px;
 				color: var(--indicator);
 				font-size: 8px;
-				animation: blink 1s infinite;
+				animation: global-blink 1s infinite;
 
 				@media (max-width: 500px) {
 					top: 16px;
diff --git a/packages/frontend/src/components/MkLink.vue b/packages/frontend/src/components/MkLink.vue
index bda683002d..95de0d0247 100644
--- a/packages/frontend/src/components/MkLink.vue
+++ b/packages/frontend/src/components/MkLink.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -7,6 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <component
 	:is="self ? 'MkA' : 'a'" ref="el" style="word-break: break-all;" class="_link" :[attr]="self ? url.substring(local.length) : url" :rel="rel ?? 'nofollow noopener'" :target="target"
 	:title="url"
+	@click.stop
 >
 	<slot></slot>
 	<i v-if="target === '_blank'" class="ph-arrow-square-out ph-bold ph-lg" :class="$style.icon"></i>
diff --git a/packages/frontend/src/components/MkMarquee.vue b/packages/frontend/src/components/MkMarquee.vue
index 145b60c8e7..4a89d21b92 100644
--- a/packages/frontend/src/components/MkMarquee.vue
+++ b/packages/frontend/src/components/MkMarquee.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -30,6 +30,7 @@ export default {
 		const contentEl = ref<HTMLElement>();
 
 		function calc() {
+			if (contentEl.value == null) return;
 			const eachLength = contentEl.value.offsetWidth / props.repeat;
 			const factor = 3000;
 			const duration = props.duration / ((1 / eachLength) * factor);
diff --git a/packages/frontend/src/components/MkMediaAudio.vue b/packages/frontend/src/components/MkMediaAudio.vue
new file mode 100644
index 0000000000..6351f5cfbe
--- /dev/null
+++ b/packages/frontend/src/components/MkMediaAudio.vue
@@ -0,0 +1,364 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div
+	:class="[
+		$style.audioContainer,
+		(audio.isSensitive && defaultStore.state.highlightSensitiveMedia) && $style.sensitive,
+	]"
+	@contextmenu.stop
+>
+	<button v-if="hide" :class="$style.hidden" @click="hide = false">
+		<div :class="$style.hiddenTextWrapper">
+			<b v-if="audio.isSensitive" style="display: block;"><i class="ph-eye-slash ph-bold ph-lg"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.dataSaver.media ? ` (${i18n.ts.audio}${audio.size ? ' ' + bytes(audio.size) : ''})` : '' }}</b>
+			<b v-else style="display: block;"><i class="ph-music-notes ph-bold ph-lg"></i> {{ defaultStore.state.dataSaver.media && audio.size ? bytes(audio.size) : i18n.ts.audio }}</b>
+			<span style="display: block;">{{ i18n.ts.clickToShow }}</span>
+		</div>
+	</button>
+	<div v-else :class="$style.audioControls">
+		<audio
+			ref="audioEl"
+			preload="metadata"
+		>
+			<source :src="audio.url">
+		</audio>
+		<div :class="[$style.controlsChild, $style.controlsLeft]">
+			<button class="_button" :class="$style.controlButton" @click="togglePlayPause">
+				<i v-if="isPlaying" class="ph-pause ph-bold ph-lg"></i>
+				<i v-else class="ph-play ph-bold ph-lg"></i>
+			</button>
+		</div>
+		<div :class="[$style.controlsChild, $style.controlsRight]">
+			<a class="_button" :class="$style.controlButton" :href="audio.url" :download="audio.name" target="_blank">
+				<i class="ph-download ph-bold ph-lg"></i>
+			</a>
+			<button class="_button" :class="$style.controlButton" @click="showMenu">
+				<i class="ph-gear ph-bold ph-lg"></i>
+			</button>
+		</div>
+		<div :class="[$style.controlsChild, $style.controlsTime]">{{ hms(elapsedTimeMs) }}</div>
+		<div :class="[$style.controlsChild, $style.controlsVolume]">
+			<button class="_button" :class="$style.controlButton" @click="toggleMute">
+				<i v-if="volume === 0" class="ph-speaker-x ph-bold ph-lg"></i>
+				<i v-else class="ph-speaker-high ph-bold ph-lg"></i>
+			</button>
+			<MkMediaRange
+				v-model="volume"
+				:class="$style.volumeSeekbar"
+			/>
+		</div>
+		<MkMediaRange
+			v-model="rangePercent"
+			:class="$style.seekbarRoot"
+			:buffer="bufferedDataRatio"
+		/>
+	</div>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { shallowRef, watch, computed, ref, onDeactivated, onActivated, onMounted } from 'vue';
+import * as Misskey from 'misskey-js';
+import type { MenuItem } from '@/types/menu.js';
+import { defaultStore } from '@/store.js';
+import { i18n } from '@/i18n.js';
+import * as os from '@/os.js';
+import bytes from '@/filters/bytes.js';
+import { hms } from '@/filters/hms.js';
+import MkMediaRange from '@/components/MkMediaRange.vue';
+import { iAmModerator } from '@/account.js';
+
+const props = defineProps<{
+	audio: Misskey.entities.DriveFile;
+}>();
+
+const audioEl = shallowRef<HTMLAudioElement>();
+
+// eslint-disable-next-line vue/no-setup-props-destructure
+const hide = ref((defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.audio.isSensitive && defaultStore.state.nsfw !== 'ignore'));
+
+// Menu
+const menuShowing = ref(false);
+
+function showMenu(ev: MouseEvent) {
+	let menu: MenuItem[] = [];
+
+	menu = [
+		// TODO: 再生キューに追加
+		{
+			text: i18n.ts.hide,
+			icon: 'ph-eye-closed ph-bold ph-lg',
+			action: () => {
+				hide.value = true;
+			},
+		},
+	];
+
+	if (iAmModerator) {
+		menu.push({
+			type: 'divider',
+		}, {
+			text: props.audio.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive,
+			icon: props.audio.isSensitive ? 'ph-eye ph-bold ph-lg' : 'ph-eye-slash ph-bold ph-lg',
+			danger: true,
+			action: () => toggleSensitive(props.audio),
+		});
+	}
+
+	menuShowing.value = true;
+	os.popupMenu(menu, ev.currentTarget ?? ev.target, {
+		align: 'right',
+		onClosing: () => {
+			menuShowing.value = false;
+		},
+	});
+}
+
+function toggleSensitive(file: Misskey.entities.DriveFile) {
+	os.apiWithDialog('drive/files/update', {
+		fileId: file.id,
+		isSensitive: !file.isSensitive,
+	});
+}
+
+// MediaControl: Common State
+const oncePlayed = ref(false);
+const isReady = ref(false);
+const isPlaying = ref(false);
+const isActuallyPlaying = ref(false);
+const elapsedTimeMs = ref(0);
+const durationMs = ref(0);
+const rangePercent = computed({
+	get: () => {
+		return (elapsedTimeMs.value / durationMs.value) || 0;
+	},
+	set: (to) => {
+		if (!audioEl.value) return;
+		audioEl.value.currentTime = to * durationMs.value / 1000;
+	},
+});
+const volume = ref(.25);
+const bufferedEnd = ref(0);
+const bufferedDataRatio = computed(() => {
+	if (!audioEl.value) return 0;
+	return bufferedEnd.value / audioEl.value.duration;
+});
+
+// MediaControl Events
+function togglePlayPause() {
+	if (!isReady.value || !audioEl.value) return;
+
+	if (isPlaying.value) {
+		audioEl.value.pause();
+		isPlaying.value = false;
+	} else {
+		audioEl.value.play();
+		isPlaying.value = true;
+		oncePlayed.value = true;
+	}
+}
+
+function toggleMute() {
+	if (volume.value === 0) {
+		volume.value = .25;
+	} else {
+		volume.value = 0;
+	}
+}
+
+let onceInit = false;
+let stopAudioElWatch: () => void;
+
+function init() {
+	if (onceInit) return;
+	onceInit = true;
+
+	stopAudioElWatch = watch(audioEl, () => {
+		if (audioEl.value) {
+			isReady.value = true;
+
+			function updateMediaTick() {
+				if (audioEl.value) {
+					try {
+						bufferedEnd.value = audioEl.value.buffered.end(0);
+					} catch (err) {
+						bufferedEnd.value = 0;
+					}
+
+					elapsedTimeMs.value = audioEl.value.currentTime * 1000;
+				}
+				window.requestAnimationFrame(updateMediaTick);
+			}
+
+			updateMediaTick();
+
+			audioEl.value.addEventListener('play', () => {
+				isActuallyPlaying.value = true;
+			});
+
+			audioEl.value.addEventListener('pause', () => {
+				isActuallyPlaying.value = false;
+				isPlaying.value = false;
+			});
+
+			audioEl.value.addEventListener('ended', () => {
+				oncePlayed.value = false;
+				isActuallyPlaying.value = false;
+				isPlaying.value = false;
+			});
+
+			durationMs.value = audioEl.value.duration * 1000;
+			audioEl.value.addEventListener('durationchange', () => {
+				if (audioEl.value) {
+					durationMs.value = audioEl.value.duration * 1000;
+				}
+			});
+
+			audioEl.value.volume = volume.value;
+		}
+	}, {
+		immediate: true,
+	});
+}
+
+watch(volume, (to) => {
+	if (audioEl.value) audioEl.value.volume = to;
+});
+
+onMounted(() => {
+	init();
+});
+
+onActivated(() => {
+	init();
+});
+
+onDeactivated(() => {
+	isReady.value = false;
+	isPlaying.value = false;
+	isActuallyPlaying.value = false;
+	elapsedTimeMs.value = 0;
+	durationMs.value = 0;
+	bufferedEnd.value = 0;
+	hide.value = (defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.audio.isSensitive && defaultStore.state.nsfw !== 'ignore');
+	stopAudioElWatch();
+	onceInit = false;
+});
+</script>
+
+<style lang="scss" module>
+.audioContainer {
+	container-type: inline-size;
+	position: relative;
+	border: .5px solid var(--divider);
+	border-radius: var(--radius);
+	overflow: clip;
+}
+
+.sensitive {
+	position: relative;
+
+	&::after {
+		content: "";
+		position: absolute;
+		top: 0;
+		left: 0;
+		width: 100%;
+		height: 100%;
+		pointer-events: none;
+		border-radius: inherit;
+		box-shadow: inset 0 0 0 4px var(--warn);
+	}
+}
+
+.hidden {
+	width: 100%;
+	background: #000;
+	border: none;
+	outline: none;
+	font: inherit;
+	color: inherit;
+	cursor: pointer;
+	padding: 12px 0;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.hiddenTextWrapper {
+	text-align: center;
+	font-size: 0.8em;
+	color: #fff;
+}
+
+.audioControls {
+	display: grid;
+	grid-template-areas:
+		"left time . volume right"
+		"seekbar seekbar seekbar seekbar seekbar";
+	grid-template-columns: auto auto 1fr auto auto;
+	align-items: center;
+	gap: 4px 8px;
+	padding: 10px;
+}
+
+.controlsChild {
+	display: flex;
+	align-items: center;
+	gap: 4px;
+
+	.controlButton {
+		padding: 6px;
+		border-radius: calc(var(--radius) / 2);
+		font-size: 1.05rem;
+
+		&:hover {
+			color: var(--accent);
+			background-color: var(--accentedBg);
+		}
+	}
+}
+
+.controlsLeft {
+	grid-area: left;
+}
+
+.controlsRight {
+	grid-area: right;
+}
+
+.controlsTime {
+	grid-area: time;
+	font-size: .9rem;
+}
+
+.controlsVolume {
+	grid-area: volume;
+
+	.volumeSeekbar {
+		display: none;
+	}
+}
+
+.seekbarRoot {
+	grid-area: seekbar;
+}
+
+@container (min-width: 500px) {
+	.audioControls {
+		grid-template-areas: "left seekbar time volume right";
+		grid-template-columns: auto 1fr auto auto auto;
+	}
+
+	.controlsVolume {
+		.volumeSeekbar {
+			max-width: 90px;
+			display: block;
+			flex-grow: 1;
+		}
+	}
+}
+</style>
diff --git a/packages/frontend/src/components/MkMediaBanner.vue b/packages/frontend/src/components/MkMediaBanner.vue
index 7b0387cefe..605c1a4c80 100644
--- a/packages/frontend/src/components/MkMediaBanner.vue
+++ b/packages/frontend/src/components/MkMediaBanner.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -10,15 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<b>{{ i18n.ts.sensitive }}</b>
 		<span>{{ i18n.ts.clickToShow }}</span>
 	</div>
-	<div v-else-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" :class="$style.audio">
-		<audio
-			ref="audioEl"
-			:src="media.url"
-			:title="media.comment ?? undefined"
-			controls
-			preload="metadata"
-		/>
-	</div>
+	<MkMediaAudio v-else-if="media.type.startsWith('audio') && media.type !== 'audio/midi'" :audio="media"/>
 	<a
 		v-else :class="$style.download"
 		:href="media.url"
@@ -35,6 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { shallowRef, watch, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import { i18n } from '@/i18n.js';
+import MkMediaAudio from '@/components/MkMediaAudio.vue';
 
 const props = withDefaults(defineProps<{
 	media: Misskey.entities.DriveFile;
diff --git a/packages/frontend/src/components/MkMediaImage.vue b/packages/frontend/src/components/MkMediaImage.vue
index ef57cea32a..3f9cff8b71 100644
--- a/packages/frontend/src/components/MkMediaImage.vue
+++ b/packages/frontend/src/components/MkMediaImage.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -43,7 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<div v-if="['image/gif', 'image/apng'].includes(image.type)" :class="$style.indicator">GIF</div>
 			<div v-if="image.comment" :class="$style.indicator">ALT</div>
 			<div v-if="image.isSensitive" :class="$style.indicator" style="color: var(--warn);" :title="i18n.ts.sensitive"><i class="ph-eye-closed ph-bold ph-lg"></i></div>
-			<div v-if="!image.comment" :class="$style.indicator" title="Image lacks descriptive text"><i class="ph-pencil ph-bold ph-lg-off"></i></div>
+			<div v-if="!image.comment" :class="$style.indicator" title="Image lacks descriptive text"><i class="ph-pencil-simple ph-bold ph-lg-off"></i></div>
 		</div>
 		<button :class="$style.menu" class="_button" @click.stop="showMenu"><i class="ph-dots-three ph-bold ph-lg" style="vertical-align: middle;"></i></button>
 		<i class="ph-eye-slash ph-bold ph-lg" :class="$style.hide" @click.stop="hide = true"></i>
diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue
index 8f73018734..3bf44aea8e 100644
--- a/packages/frontend/src/components/MkMediaList.vue
+++ b/packages/frontend/src/components/MkMediaList.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -54,7 +54,7 @@ const count = computed(() => props.mediaList.filter(media => previewable(media))
 let lightbox: PhotoSwipeLightbox | null;
 
 const popstateHandler = (): void => {
-	if (lightbox.pswp && lightbox.pswp.isOpen === true) {
+	if (lightbox?.pswp && lightbox.pswp.isOpen === true) {
 		lightbox.pswp.close();
 	}
 };
@@ -69,7 +69,10 @@ async function calcAspectRatio() {
 		return;
 	}
 
-	const ratioMax = (ratio: number) => `${Math.max(ratio, img.properties.width / img.properties.height).toString()} / 1`;
+	const ratioMax = (ratio: number) => {
+		if (img.properties.width == null || img.properties.height == null) return '';
+		return `${Math.max(ratio, img.properties.width / img.properties.height).toString()} / 1`;
+	};
 
 	switch (defaultStore.state.mediaListWithOneImageAppearance) {
 		case '16_9':
@@ -145,7 +148,7 @@ onMounted(() => {
 		// element is children
 		const { element } = itemData;
 
-		const id = element.dataset.id;
+		const id = element?.dataset.id;
 		const file = props.mediaList.find(media => media.id === id);
 		if (!file) return;
 
@@ -155,14 +158,14 @@ onMounted(() => {
 		if (file.properties.orientation != null && file.properties.orientation >= 5) {
 			[itemData.w, itemData.h] = [itemData.h, itemData.w];
 		}
-		itemData.msrc = file.thumbnailUrl;
+		itemData.msrc = file.thumbnailUrl ?? undefined;
 		itemData.alt = file.comment ?? undefined;
 		itemData.comment = file.comment;
 		itemData.thumbCropped = true;
 	});
 
 	lightbox.on('uiRegister', () => {
-		lightbox.pswp.ui.registerElement({
+		lightbox?.pswp?.ui?.registerElement({
 			name: 'altText',
 			className: 'pwsp__alt-text-container',
 			appendTo: 'wrapper',
@@ -178,7 +181,7 @@ onMounted(() => {
 						textBox.style.display = 'none';
 					}
 
-					textBox.textContent = pwsp.currSlide.data.comment;
+					textBox.textContent = pwsp.currSlide?.data.comment;
 				});
 			},
 		});
diff --git a/packages/frontend/src/components/MkMediaRange.vue b/packages/frontend/src/components/MkMediaRange.vue
new file mode 100644
index 0000000000..86ed8ba2cf
--- /dev/null
+++ b/packages/frontend/src/components/MkMediaRange.vue
@@ -0,0 +1,152 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<!-- Media系専用のinput range -->
+<template>
+<div :style="sliderBgWhite ? '--sliderBg: rgba(255,255,255,.25);' : '--sliderBg: var(--scrollbarHandle);'">
+	<div :class="$style.controlsSeekbar">
+		<progress v-if="buffer !== undefined" :class="$style.buffer" :value="isNaN(buffer) ? 0 : buffer" min="0" max="1">{{ Math.round(buffer * 100) }}% buffered</progress>
+		<input v-model="model" :class="$style.seek" :style="`--value: ${modelValue * 100}%;`" type="range" min="0" max="1" step="any" @change="emit('dragEnded', modelValue)"/>
+	</div>
+</div>
+</template>
+
+<script setup lang="ts">
+import { computed, ModelRef } from 'vue';
+
+withDefaults(defineProps<{
+	buffer?: number;
+	sliderBgWhite?: boolean;
+}>(), {
+	buffer: undefined,
+	sliderBgWhite: false,
+});
+
+const emit = defineEmits<{
+	(ev: 'dragEnded', value: number): void;
+}>();
+
+// eslint-disable-next-line no-undef
+const model = defineModel({ required: true }) as ModelRef<string | number>;
+const modelValue = computed({
+	get: () => typeof model.value === 'number' ? model.value : parseFloat(model.value),
+	set: v => { model.value = v; },
+});
+</script>
+
+<style lang="scss" module>
+.controlsSeekbar {
+	position: relative;
+}
+
+.seek {
+	position: relative;
+	-webkit-appearance: none;
+	appearance: none;
+	background: transparent;
+	border: 0;
+	border-radius: 26px;
+	color: var(--accent);
+	display: block;
+	height: 19px;
+	margin: 0;
+	min-width: 0;
+	padding: 0;
+	transition: box-shadow .3s ease;
+	width: 100%;
+
+	&::-webkit-slider-runnable-track {
+		background-color: var(--sliderBg);
+		background-image: linear-gradient(to right,currentColor var(--value,0),transparent var(--value,0));
+		border: 0;
+		border-radius: 99rem;
+		height: 5px;
+		transition: box-shadow .3s ease;
+		user-select: none;
+	}
+
+	&::-moz-range-track {
+		background: transparent;
+		border: 0;
+		border-radius: 99rem;
+		height: 5px;
+		transition: box-shadow .3s ease;
+		user-select: none;
+		background-color: var(--sliderBg);
+	}
+
+	&::-webkit-slider-thumb {
+		-webkit-appearance: none;
+		appearance: none;
+		background: #fff;
+		border: 0;
+		border-radius: 100%;
+		box-shadow: 0 1px 1px rgba(35, 40, 47, .15),0 0 0 1px rgba(35, 40, 47, .2);
+		height: 13px;
+		margin-top: -4px;
+		position: relative;
+		transition: all .2s ease;
+		width: 13px;
+
+		&:active {
+			box-shadow: 0 1px 1px rgba(35, 40, 47, .15), 0 0 0 1px rgba(35, 40, 47, .15), 0 0 0 3px rgba(255, 255, 255, .5);
+		}
+	}
+
+	&::-moz-range-thumb {
+		background: #fff;
+		border: 0;
+		border-radius: 100%;
+		box-shadow: 0 1px 1px rgba(35, 40, 47, .15),0 0 0 1px rgba(35, 40, 47, .2);
+		height: 13px;
+		position: relative;
+		transition: all .2s ease;
+		width: 13px;
+
+		&:active {
+			box-shadow: 0 1px 1px rgba(35, 40, 47, .15), 0 0 0 1px rgba(35, 40, 47, .15), 0 0 0 3px rgba(255, 255, 255, .5);
+		}
+	}
+
+	&::-moz-range-progress {
+		background: currentColor;
+		border-radius: 99rem;
+		height: 5px;
+	}
+}
+
+.buffer {
+	appearance: none;
+	background: transparent;
+	color: var(--sliderBg);
+	border: 0;
+	border-radius: 99rem;
+	height: 5px;
+	left: 0;
+	margin-top: -2.5px;
+	padding: 0;
+	position: absolute;
+	top: 50%;
+	width: 100%;
+
+	&::-webkit-progress-bar {
+		background: transparent;
+	}
+
+	&::-webkit-progress-value {
+		background: currentColor;
+		border-radius: 100px;
+		min-width: 5px;
+		transition: width .2s ease;
+	}
+
+	&::-moz-progress-bar {
+		background: currentColor;
+		border-radius: 100px;
+		min-width: 5px;
+		transition: width .2s ease;
+	}
+}
+</style>
diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue
index a1950b110a..7c14ade130 100644
--- a/packages/frontend/src/components/MkMediaVideo.vue
+++ b/packages/frontend/src/components/MkMediaVideo.vue
@@ -1,71 +1,351 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<div v-if="hide" :class="[$style.hidden, (video.isSensitive && defaultStore.state.highlightSensitiveMedia) && $style.sensitiveContainer]" @click="hide = false">
-	<!-- 【注意】dataSaverMode が有効になっている際には、hide が false になるまでサムネイルや動画を読み込まないようにすること -->
-	<div :class="$style.sensitive">
-		<b v-if="video.isSensitive" style="display: block;"><i class="ph-warning ph-bold ph-lg"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.dataSaver.media ? ` (${i18n.ts.video}${video.size ? ' ' + bytes(video.size) : ''})` : '' }}</b>
-		<b v-else style="display: block;"><i class="ph-film-strip ph-bold ph-lg"></i> {{ defaultStore.state.dataSaver.media && video.size ? bytes(video.size) : i18n.ts.video }}</b>
-		<span>{{ i18n.ts.clickToShow }}</span>
-	</div>
-</div>
-<div v-else :class="[$style.visible, (video.isSensitive && defaultStore.state.highlightSensitiveMedia) && $style.sensitiveContainer]">
-	<video
-		ref="videoEl"
-		:class="$style.video"
-		:poster="video.thumbnailUrl"
-		:title="video.comment ?? undefined"
-		:alt="video.comment"
-		preload="none"
-		controls
-		@contextmenu.stop
-	>
-		<source
-			:src="video.url"
+<div
+	ref="playerEl"
+	:class="[
+		$style.videoContainer,
+		controlsShowing && $style.active,
+		(video.isSensitive && defaultStore.state.highlightSensitiveMedia) && $style.sensitive,
+	]"
+	@mouseover="onMouseOver"
+	@mouseleave="onMouseLeave"
+	@contextmenu.stop
+>
+	<button v-if="hide" :class="$style.hidden" @click="hide = false">
+		<div :class="$style.hiddenTextWrapper">
+			<b v-if="video.isSensitive" style="display: block;"><i class="ph-warning ph-bold ph-lg"></i> {{ i18n.ts.sensitive }}{{ defaultStore.state.dataSaver.media ? ` (${i18n.ts.video}${video.size ? ' ' + bytes(video.size) : ''})` : '' }}</b>
+			<b v-else style="display: block;"><i class="ph-film-strip ph-bold ph-lg"></i> {{ defaultStore.state.dataSaver.media && video.size ? bytes(video.size) : i18n.ts.video }}</b>
+			<span style="display: block;">{{ i18n.ts.clickToShow }}</span>
+		</div>
+	</button>
+	<div v-else :class="$style.videoRoot" @click.self="togglePlayPause">
+		<video
+			ref="videoEl"
+			:class="$style.video"
+			:poster="video.thumbnailUrl ?? undefined"
+			:title="video.comment ?? undefined"
+			:alt="video.comment"
+			preload="metadata"
+			playsinline
 		>
-	</video>
-	<i class="ph-eye-slash ph-bold ph-lg" :class="$style.hide" @click="hide = true"></i>
+			<source :src="video.url">
+		</video>
+		<button v-if="isReady && !isPlaying" class="_button" :class="$style.videoOverlayPlayButton" @click="togglePlayPause"><i class="ph-play ph-bold ph-lg"></i></button>
+		<div v-else-if="!isActuallyPlaying" :class="$style.videoLoading">
+			<MkLoading/>
+		</div>
+		<i class="ph-eye-closed ph-bold ph-lg" :class="$style.hide" @click="hide = true"></i>
+		<div :class="$style.indicators">
+			<div v-if="video.comment" :class="$style.indicator">ALT</div>
+			<div v-if="video.isSensitive" :class="$style.indicator" style="color: var(--warn);" :title="i18n.ts.sensitive"><i class="ph-warning ph-bold ph-lg"></i></div>
+		</div>
+		<div :class="$style.videoControls" @click.self="togglePlayPause">
+			<div :class="[$style.controlsChild, $style.controlsLeft]">
+				<button class="_button" :class="$style.controlButton" @click="togglePlayPause">
+					<i v-if="isPlaying" class="ph-pause ph-bold ph-lg"></i>
+					<i v-else class="ph-play ph-bold ph-lg"></i>
+				</button>
+			</div>
+			<div :class="[$style.controlsChild, $style.controlsRight]">
+				<a class="_button" :class="$style.controlButton" :href="video.url" :download="video.name" target="_blank">
+					<i class="ph-download ph-bold ph-lg"></i>
+				</a>
+				<button class="_button" :class="$style.controlButton" @click="showMenu">
+					<i class="ph-gear ph-bold ph-lg"></i>
+				</button>
+				<button class="_button" :class="$style.controlButton" @click="toggleFullscreen">
+					<i v-if="isFullscreen" class="ph-arrows-in ph-bold ph-lg"></i>
+					<i v-else class="ph-arrows-out ph-bold ph-lg"></i>
+				</button>
+			</div>
+			<div :class="[$style.controlsChild, $style.controlsTime]">{{ hms(elapsedTimeMs) }}</div>
+			<div :class="[$style.controlsChild, $style.controlsVolume]">
+				<button class="_button" :class="$style.controlButton" @click="toggleMute">
+					<i v-if="volume === 0" class="ph-speaker-x ph-bold ph-lg"></i>
+					<i v-else class="ph-speaker-high ph-bold ph-lg"></i>
+				</button>
+				<MkMediaRange
+					v-model="volume"
+					:sliderBgWhite="true"
+					:class="$style.volumeSeekbar"
+				/>
+			</div>
+			<MkMediaRange
+				v-model="rangePercent"
+				:sliderBgWhite="true"
+				:class="$style.seekbarRoot"
+				:buffer="bufferedDataRatio"
+			/>
+		</div>
+	</div>
 </div>
 </template>
 
 <script lang="ts" setup>
-import { ref, shallowRef, watch } from 'vue';
+import { ref, shallowRef, computed, watch, onDeactivated, onActivated, onMounted } from 'vue';
 import * as Misskey from 'misskey-js';
+import type { MenuItem } from '@/types/menu.js';
 import bytes from '@/filters/bytes.js';
+import { hms } from '@/filters/hms.js';
 import { defaultStore } from '@/store.js';
 import { i18n } from '@/i18n.js';
+import * as os from '@/os.js';
+import { isFullscreenNotSupported } from '@/scripts/device-kind.js';
 import hasAudio from '@/scripts/media-has-audio.js';
+import MkMediaRange from '@/components/MkMediaRange.vue';
+import { iAmModerator } from '@/account.js';
 
 const props = defineProps<{
 	video: Misskey.entities.DriveFile;
 }>();
 
+// eslint-disable-next-line vue/no-setup-props-destructure
 const hide = ref((defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.video.isSensitive && defaultStore.state.nsfw !== 'ignore'));
 
-const videoEl = shallowRef<HTMLVideoElement>();
+// Menu
+const menuShowing = ref(false);
 
-watch(videoEl, () => {
-	if (videoEl.value) {
-		videoEl.value.volume = 0.3;
-		hasAudio(videoEl.value).then(had => {
-			if (!had) {
-				videoEl.value.loop = videoEl.value.muted = true;
-				videoEl.value.play();
-			}
+function showMenu(ev: MouseEvent) {
+	let menu: MenuItem[] = [];
+
+	menu = [
+		// TODO: 再生キューに追加
+		{
+			text: i18n.ts.hide,
+			icon: 'ph-eye-closed ph-bold ph-lg',
+			action: () => {
+				hide.value = true;
+			},
+		},
+	];
+
+	if (iAmModerator) {
+		menu.push({
+			type: 'divider',
+		}, {
+			text: props.video.isSensitive ? i18n.ts.unmarkAsSensitive : i18n.ts.markAsSensitive,
+			icon: props.video.isSensitive ? 'ph-eye ph-bold ph-lg' : 'ph-eye-slash ph-bold ph-lg',
+			danger: true,
+			action: () => toggleSensitive(props.video),
 		});
 	}
+
+	menuShowing.value = true;
+	os.popupMenu(menu, ev.currentTarget ?? ev.target, {
+		align: 'right',
+		onClosing: () => {
+			menuShowing.value = false;
+		},
+	});
+}
+
+function toggleSensitive(file: Misskey.entities.DriveFile) {
+	os.apiWithDialog('drive/files/update', {
+		fileId: file.id,
+		isSensitive: !file.isSensitive,
+	});
+}
+
+// MediaControl: Video State
+const videoEl = shallowRef<HTMLVideoElement>();
+const playerEl = shallowRef<HTMLDivElement>();
+const isHoverring = ref(false);
+const controlsShowing = computed(() => {
+	if (!oncePlayed.value) return true;
+	if (isHoverring.value) return true;
+	if (menuShowing.value) return true;
+	return false;
+});
+const isFullscreen = ref(false);
+let controlStateTimer: string | number;
+
+// MediaControl: Common State
+const oncePlayed = ref(false);
+const isReady = ref(false);
+const isPlaying = ref(false);
+const isActuallyPlaying = ref(false);
+const elapsedTimeMs = ref(0);
+const durationMs = ref(0);
+const rangePercent = computed({
+	get: () => {
+		return (elapsedTimeMs.value / durationMs.value) || 0;
+	},
+	set: (to) => {
+		if (!videoEl.value) return;
+		videoEl.value.currentTime = to * durationMs.value / 1000;
+	},
+});
+const volume = ref(.25);
+const bufferedEnd = ref(0);
+const bufferedDataRatio = computed(() => {
+	if (!videoEl.value) return 0;
+	return bufferedEnd.value / videoEl.value.duration;
+});
+
+// MediaControl Events
+function onMouseOver() {
+	if (controlStateTimer) {
+		clearTimeout(controlStateTimer);
+	}
+	isHoverring.value = true;
+}
+
+function onMouseLeave() {
+	controlStateTimer = window.setTimeout(() => {
+		isHoverring.value = false;
+	}, 100);
+}
+
+function togglePlayPause() {
+	if (!isReady.value || !videoEl.value) return;
+
+	if (isPlaying.value) {
+		videoEl.value.pause();
+		isPlaying.value = false;
+	} else {
+		videoEl.value.play();
+		isPlaying.value = true;
+		oncePlayed.value = true;
+	}
+}
+
+function toggleFullscreen() {
+	if (isFullscreenNotSupported && videoEl.value) {
+		if (isFullscreen.value) {
+			// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+			//@ts-ignore
+			videoEl.value.webkitExitFullscreen();
+			isFullscreen.value = false;
+		} else {
+			// eslint-disable-next-line @typescript-eslint/ban-ts-comment
+			//@ts-ignore
+			videoEl.value.webkitEnterFullscreen();
+			isFullscreen.value = true;
+		}
+	} else if (playerEl.value) {
+		if (isFullscreen.value) {
+			document.exitFullscreen();
+			isFullscreen.value = false;
+		} else {
+			playerEl.value.requestFullscreen({ navigationUI: 'hide' });
+			isFullscreen.value = true;
+		}
+	}
+}
+
+function toggleMute() {
+	if (volume.value === 0) {
+		volume.value = .25;
+	} else {
+		volume.value = 0;
+	}
+}
+
+let onceInit = false;
+let stopVideoElWatch: () => void;
+
+function init() {
+	if (onceInit) return;
+	onceInit = true;
+
+	stopVideoElWatch = watch(videoEl, () => {
+		if (videoEl.value) {
+			isReady.value = true;
+
+			function updateMediaTick() {
+				if (videoEl.value) {
+					try {
+						bufferedEnd.value = videoEl.value.buffered.end(0);
+					} catch (err) {
+						bufferedEnd.value = 0;
+					}
+
+					elapsedTimeMs.value = videoEl.value.currentTime * 1000;
+				}
+				window.requestAnimationFrame(updateMediaTick);
+			}
+
+			updateMediaTick();
+
+			videoEl.value.addEventListener('play', () => {
+				isActuallyPlaying.value = true;
+			});
+
+			videoEl.value.addEventListener('pause', () => {
+				isActuallyPlaying.value = false;
+				isPlaying.value = false;
+			});
+
+			videoEl.value.addEventListener('ended', () => {
+				oncePlayed.value = false;
+				isActuallyPlaying.value = false;
+				isPlaying.value = false;
+			});
+
+			durationMs.value = videoEl.value.duration * 1000;
+			videoEl.value.addEventListener('durationchange', () => {
+				if (videoEl.value) {
+					durationMs.value = videoEl.value.duration * 1000;
+				}
+			});
+
+			videoEl.value.volume = volume.value;
+			hasAudio(videoEl.value).then(had => {
+				if (!had && videoEl.value) {
+					videoEl.value.loop = videoEl.value.muted = true;
+					videoEl.value.play();
+				}
+			});
+		}
+	}, {
+		immediate: true,
+	});
+}
+
+watch(volume, (to) => {
+	if (videoEl.value) videoEl.value.volume = to;
+});
+
+watch(hide, (to) => {
+	if (to && isFullscreen.value) {
+		document.exitFullscreen();
+		isFullscreen.value = false;
+	}
+});
+
+onMounted(() => {
+	init();
+});
+
+onActivated(() => {
+	init();
+});
+
+onDeactivated(() => {
+	isReady.value = false;
+	isPlaying.value = false;
+	isActuallyPlaying.value = false;
+	elapsedTimeMs.value = 0;
+	durationMs.value = 0;
+	bufferedEnd.value = 0;
+	hide.value = (defaultStore.state.nsfw === 'force' || defaultStore.state.dataSaver.media) ? true : (props.video.isSensitive && defaultStore.state.nsfw !== 'ignore');
+	stopVideoElWatch();
+	onceInit = false;
 });
 </script>
 
 <style lang="scss" module>
-.visible {
+.videoContainer {
+	container-type: inline-size;
 	position: relative;
+	overflow: clip;
 }
 
-.sensitiveContainer {
+.sensitive {
 	position: relative;
 
 	&::after {
@@ -81,45 +361,199 @@ watch(videoEl, () => {
 	}
 }
 
+.indicators {
+	display: inline-flex;
+	position: absolute;
+	top: 10px;
+	left: 10px;
+	pointer-events: none;
+	opacity: .5;
+	gap: 6px;
+}
+
+.indicator {
+	/* Hardcode to black because either --bg or --fg makes it hard to read in dark/light mode */
+	background-color: black;
+	border-radius: 6px;
+	color: var(--accentLighten);
+	display: inline-block;
+	font-weight: bold;
+	font-size: 0.8em;
+	padding: 2px 5px;
+}
+
 .hide {
 	display: block;
 	position: absolute;
 	border-radius: var(--radius-sm);
 	background-color: black;
 	color: var(--accentLighten);
-	font-size: 14px;
+	font-size: 12px;
 	opacity: .5;
-	padding: 3px 6px;
+	padding: 5px 8px;
 	text-align: center;
 	cursor: pointer;
 	top: 12px;
 	right: 12px;
 }
 
-.video {
-	display: flex;
-	justify-content: center;
-	align-items: center;
-	font-size: 3.5em;
-	overflow: hidden;
-	background-position: center;
-	background-size: cover;
+.hidden {
 	width: 100%;
 	height: 100%;
+	background: #000;
+	border: none;
+	outline: none;
+	font: inherit;
+	color: inherit;
+	cursor: pointer;
+	padding: 120px 0;
+	display: flex;
+	align-items: center;
+	justify-content: center;
 }
 
-.hidden {
-	display: flex;
-	justify-content: center;
-	align-items: center;
-	background: #111;
+.hiddenTextWrapper {
+	text-align: center;
+	font-size: 0.8em;
 	color: #fff;
 }
 
-.sensitive {
-	display: table-cell;
-	text-align: center;
-	font-size: 12px;
+.videoRoot {
+	background: #000;
+	position: relative;
+	width: 100%;
+	height: 100%;
+	object-fit: contain;
+}
+
+.video {
+	display: block;
+	height: 100%;
+	width: 100%;
+}
+
+.videoOverlayPlayButton {
+	position: absolute;
+	top: 50%;
+	left: 50%;
+	transform: translate(-50%,-50%);
+
+	opacity: 0;
+	transition: opacity .4s ease-in-out;
+
+	background: var(--accent);
+	color: #fff;
+	padding: 1rem;
+	border-radius: 99rem;
+
+	font-size: 1.1rem;
+}
+
+.videoLoading {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+}
+
+.videoControls {
+	display: grid;
+	grid-template-areas:
+		"left time . volume right"
+		"seekbar seekbar seekbar seekbar seekbar";
+	grid-template-columns: auto auto 1fr auto auto;
+	align-items: center;
+	gap: 4px 8px;
+
+	padding: 35px 10px 10px 10px;
+	background: linear-gradient(rgba(0, 0, 0, 0),rgba(0, 0, 0, .75));
+
+	position: absolute;
+	left: 0;
+	right: 0;
+	bottom: 0;
+
+	transform: translateY(100%);
+	pointer-events: none;
+	opacity: 0;
+	transition: opacity .4s ease-in-out, transform .4s ease-in-out;
+}
+
+.active {
+	.videoControls {
+		transform: translateY(0);
+		opacity: 1;
+		pointer-events: auto;
+	}
+
+	.videoOverlayPlayButton {
+		opacity: 1;
+	}
+}
+
+.controlsChild {
+	display: flex;
+	align-items: center;
+	gap: 4px;
+	color: #fff;
+
+	.controlButton {
+		padding: 6px;
+		border-radius: calc(var(--radius) / 2);
+		transition: background-color .2s ease-in-out;
+		font-size: 1.05rem;
+
+		&:hover {
+			background-color: var(--accent);
+		}
+	}
+}
+
+.controlsLeft {
+	grid-area: left;
+}
+
+.controlsRight {
+	grid-area: right;
+}
+
+.controlsTime {
+	grid-area: time;
+	font-size: .9rem;
+}
+
+.controlsVolume {
+	grid-area: volume;
+
+	.volumeSeekbar {
+		display: none;
+	}
+}
+
+.seekbarRoot {
+	grid-area: seekbar;
+	/* ▼シークバー操作をやりやすくするためにクリックイベントが伝播されないエリアを拡張する */
+	margin: -10px;
+	padding: 10px;
+}
+
+@container (min-width: 500px) {
+	.videoControls {
+		grid-template-areas: "left seekbar time volume right";
+		grid-template-columns: auto 1fr auto auto auto;
+	}
+
+	.controlsVolume {
+		.volumeSeekbar {
+			max-width: 90px;
+			display: block;
+			flex-grow: 1;
+		}
+	}
 }
 .indicators {
 	display: inline-flex;
diff --git a/packages/frontend/src/components/MkMention.vue b/packages/frontend/src/components/MkMention.vue
index 4d42053657..942c23a145 100644
--- a/packages/frontend/src/components/MkMention.vue
+++ b/packages/frontend/src/components/MkMention.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -51,6 +51,7 @@ const avatarUrl = computed(() => defaultStore.state.disableShowingAnimatedImages
 	padding: 4px 8px 4px 4px;
 	border-radius: var(--radius-ellipse);
 	color: var(--mention);
+	white-space: nowrap;
 
 	&.isMe {
 		color: var(--mentionMe);
diff --git a/packages/frontend/src/components/MkMenu.child.vue b/packages/frontend/src/components/MkMenu.child.vue
index 962dcd91eb..dfb6d34618 100644
--- a/packages/frontend/src/components/MkMenu.child.vue
+++ b/packages/frontend/src/components/MkMenu.child.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -33,6 +33,7 @@ const align = 'left';
 const SCROLLBAR_THICKNESS = 16;
 
 function setPosition() {
+	if (el.value == null) return;
 	const rootRect = props.rootElement.getBoundingClientRect();
 	const parentRect = props.targetElement.getBoundingClientRect();
 	const myRect = el.value.getBoundingClientRect();
@@ -66,7 +67,7 @@ const ro = new ResizeObserver((entries, observer) => {
 });
 
 onMounted(() => {
-	ro.observe(el.value);
+	if (el.value) ro.observe(el.value);
 	setPosition();
 	nextTick(() => {
 		setPosition();
@@ -79,7 +80,7 @@ onUnmounted(() => {
 
 defineExpose({
 	checkHit: (ev: MouseEvent) => {
-		return (ev.target === el.value || el.value.contains(ev.target));
+		return (ev.target === el.value || el.value?.contains(ev.target as Node));
 	},
 });
 </script>
diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue
index 5f48f43bfb..8395879d02 100644
--- a/packages/frontend/src/components/MkMenu.vue
+++ b/packages/frontend/src/components/MkMenu.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		:style="{ width: (width && !asDrawer) ? width + 'px' : '', maxHeight: maxHeight ? maxHeight + 'px' : '' }"
 		@contextmenu.self="e => e.preventDefault()"
 	>
-		<template v-for="(item, i) in items2">
+		<template v-for="(item, i) in (items2 ?? [])">
 			<div v-if="item.type === 'divider'" role="separator" :class="$style.divider"></div>
 			<span v-else-if="item.type === 'label'" role="menuitem" :class="[$style.label, $style.item]">
 				<span style="opacity: 0.7;">{{ item.text }}</span>
@@ -54,7 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<span :class="$style.caret" style="pointer-events: none;"><i class="ph-caret-right ph-bold ph-lg ti-fw"></i></span>
 				</div>
 			</button>
-			<button v-else :tabindex="i" class="_button" role="menuitem" :class="[$style.item, { [$style.danger]: item.danger, [$style.active]: item.active }]" :disabled="item.active" @click="clicked(item.action, $event)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
+			<button v-else :tabindex="i" class="_button" role="menuitem" :class="[$style.item, { [$style.danger]: item.danger, [$style.active]: getValue(item.active) }]" :disabled="getValue(item.active)" @click="clicked(item.action, $event)" @mouseenter.passive="onItemMouseEnter(item)" @mouseleave.passive="onItemMouseLeave(item)">
 				<i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i>
 				<MkAvatar v-if="item.avatar" :user="item.avatar" :class="$style.avatar"/>
 				<div :class="$style.item_content">
@@ -63,18 +63,18 @@ SPDX-License-Identifier: AGPL-3.0-only
 				</div>
 			</button>
 		</template>
-		<span v-if="items2.length === 0" :class="[$style.none, $style.item]">
+		<span v-if="items2 == null || items2.length === 0" :class="[$style.none, $style.item]">
 			<span>{{ i18n.ts.none }}</span>
 		</span>
 	</div>
 	<div v-if="childMenu">
-		<XChild ref="child" :items="childMenu" :targetElement="childTarget" :rootElement="itemsEl" showing @actioned="childActioned" @close="close(false)"/>
+		<XChild ref="child" :items="childMenu" :targetElement="childTarget!" :rootElement="itemsEl!" showing @actioned="childActioned" @close="close(false)"/>
 	</div>
 </div>
 </template>
 
 <script lang="ts">
-import { computed, defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue';
+import { ComputedRef, computed, defineAsyncComponent, isRef, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue';
 import { focusPrev, focusNext } from '@/scripts/focus.js';
 import MkSwitchButton from '@/components/MkSwitch.button.vue';
 import { MenuItem, InnerMenuItem, MenuPending, MenuAction, MenuSwitch, MenuParent } from '@/types/menu.js';
@@ -104,7 +104,7 @@ const emit = defineEmits<{
 
 const itemsEl = shallowRef<HTMLDivElement>();
 
-const items2 = ref<InnerMenuItem[]>([]);
+const items2 = ref<InnerMenuItem[]>();
 
 const child = shallowRef<InstanceType<typeof XChild>>();
 
@@ -119,15 +119,15 @@ const childShowingItem = ref<MenuItem | null>();
 let preferClick = isTouchUsing || props.asDrawer;
 
 watch(() => props.items, () => {
-	const items: (MenuItem | MenuPending)[] = [...props.items].filter(item => item !== undefined);
+	const items = [...props.items].filter(item => item !== undefined) as (NonNullable<MenuItem> | MenuPending)[];
 
 	for (let i = 0; i < items.length; i++) {
 		const item = items[i];
 
-		if (item && 'then' in item) { // if item is Promise
+		if ('then' in item) { // if item is Promise
 			items[i] = { type: 'pending' };
 			item.then(actualItem => {
-				items2.value[i] = actualItem;
+				if (items2.value?.[i]) items2.value[i] = actualItem;
 			});
 		}
 	}
@@ -151,7 +151,7 @@ function childActioned() {
 }
 
 const onGlobalMousedown = (event: MouseEvent) => {
-	if (childTarget.value && (event.target === childTarget.value || childTarget.value.contains(event.target))) return;
+	if (childTarget.value && (event.target === childTarget.value || childTarget.value.contains(event.target as Node))) return;
 	if (child.value && child.value.checkHit(event)) return;
 	closeChild();
 };
@@ -169,7 +169,7 @@ function onItemMouseLeave(item) {
 }
 
 async function showChildren(item: MenuParent, ev: MouseEvent) {
-	const children = await (async () => {
+	const children: MenuItem[] = await (async () => {
 		if (childrenCache.has(item)) {
 			return childrenCache.get(item)!;
 		} else {
@@ -189,7 +189,7 @@ async function showChildren(item: MenuParent, ev: MouseEvent) {
 		});
 		emit('hide');
 	} else {
-		childTarget.value = ev.currentTarget ?? ev.target;
+		childTarget.value = (ev.currentTarget ?? ev.target) as HTMLElement;
 		// これでもリアクティビティは保たれる
 		childMenu.value = children;
 		childShowingItem.value = item;
@@ -218,6 +218,10 @@ function switchItem(item: MenuSwitch & { ref: any }) {
 	item.ref = !item.ref;
 }
 
+function getValue<T>(item?: ComputedRef<T> | T) {
+	return isRef(item) ? item.value : item;
+}
+
 onMounted(() => {
 	if (props.viaKeyboard) {
 		nextTick(() => {
@@ -450,7 +454,7 @@ onBeforeUnmount(() => {
 	align-items: center;
 	color: var(--indicator);
 	font-size: 12px;
-	animation: blink 1s infinite;
+	animation: global-blink 1s infinite;
 }
 
 .divider {
diff --git a/packages/frontend/src/components/MkMiniChart.vue b/packages/frontend/src/components/MkMiniChart.vue
index f0a2c232bd..f2f2bf47a8 100644
--- a/packages/frontend/src/components/MkMiniChart.vue
+++ b/packages/frontend/src/components/MkMiniChart.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -22,8 +22,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 		stroke-width="2"
 	/>
 	<circle
-		:cx="headX"
-		:cy="headY"
+		:cx="headX ?? undefined"
+		:cy="headY ?? undefined"
 		r="3"
 		:fill="color"
 	/>
diff --git a/packages/frontend/src/components/MkModPlayer.vue b/packages/frontend/src/components/MkModPlayer.vue
index f61144cbca..75053cbc37 100644
--- a/packages/frontend/src/components/MkModPlayer.vue
+++ b/packages/frontend/src/components/MkModPlayer.vue
@@ -7,14 +7,17 @@
 </div>
 
 <div v-else class="mod-player-enabled">
-	<div class="pattern-display" @click="togglePattern()">
+	<div class="pattern-display" @click="togglePattern()" @scroll="scrollHandler" @scrollend="scrollEndHandle">
 		<div v-if="patternHide" class="pattern-hide">
 			<b><i class="ph-eye ph-bold ph-lg"></i> Pattern Hidden</b>
 			<span>{{ i18n.ts.clickToShow }}</span>
 		</div>
+		<span class="patternShadowTop"></span>
+		<span class="patternShadowBottom"></span>
 		<canvas ref="displayCanvas" class="pattern-canvas"></canvas>
 	</div>
 	<div class="controls">
+		<input v-if="patternScrollSliderShow" ref="patternScrollSlider" v-model="patternScrollSliderPos" class="pattern-slider" type="range" min="0" max="100" step="0.01" style=""/>
 		<button class="play" @click="playPause()">
 			<i v-if="playing" class="ph-pause ph-bold ph-lg"></i>
 			<i v-else class="ph-play ph-bold ph-lg"></i>
@@ -33,43 +36,30 @@
 </template>
 
 <script lang="ts" setup>
-import { ref, nextTick, computed } from 'vue';
+import { ref, nextTick, computed, watch, onDeactivated, onMounted } from 'vue';
 import * as Misskey from 'misskey-js';
 import { i18n } from '@/i18n.js';
 import { defaultStore } from '@/store.js';
 import { ChiptuneJsPlayer, ChiptuneJsConfig } from '@/scripts/chiptune2.js';
+import { isTouchUsing } from '@/scripts/touch.js';
+
+const colours = {
+	background: '#000000',
+	foreground: {
+		default: '#ffffff',
+		quarter: '#ffff00',
+		instr: '#80e0ff',
+		volume: '#80ff80',
+		fx: '#ff80e0',
+		operant: '#ffe080',
+	},
+};
 
 const CHAR_WIDTH = 6;
 const CHAR_HEIGHT = 12;
 const ROW_OFFSET_Y = 10;
-
-const colours = {
-	background: '#000000',
-	default: {
-		active: '#ffffff',
-		inactive: '#808080',
-	},
-	quarter: {
-		active: '#ffff00',
-		inactive: '#ffe135',
-	},
-	instr: {
-		active: '#80e0ff',
-		inactive: '#0099cc',
-	},
-	volume: {
-		active: '#80ff80',
-		inactive: '#008000',
-	},
-	fx: {
-		active: '#ff80e0',
-		inactive: '#800060',
-	},
-	operant: {
-		active: '#ffe080',
-		inactive: '#806000',
-	},
-};
+const MAX_TIME_SPENT = 50;
+const MAX_TIME_PER_ROW = 15;
 
 const props = defineProps<{
 	module: Misskey.entities.DriveFile
@@ -79,29 +69,57 @@ const isSensitive = computed(() => { return props.module.isSensitive; });
 const url = computed(() => { return props.module.url; });
 let hide = ref((defaultStore.state.nsfw === 'force') ? true : isSensitive.value && (defaultStore.state.nsfw !== 'ignore'));
 let patternHide = ref(false);
-let firstFrame = ref(true);
 let playing = ref(false);
 let displayCanvas = ref<HTMLCanvasElement>();
 let progress = ref<HTMLProgressElement>();
 let position = ref(0);
+let patternScrollSlider = ref<HTMLProgressElement>();
+let patternScrollSliderShow = ref(false);
+let patternScrollSliderPos = ref(0);
 const player = ref(new ChiptuneJsPlayer(new ChiptuneJsConfig()));
 
-const rowBuffer = 24;
+const maxRowNumbers = 0xFF;
+const rowBuffer = 26;
 let buffer = null;
 let isSeeking = false;
+let firstFrame = true;
+let lastPattern = -1;
+let lastDrawnRow = -1;
+let numberRowCanvas = new OffscreenCanvas(2 * CHAR_WIDTH + 1, maxRowNumbers * CHAR_HEIGHT + 1);
+let alreadyHiddenOnce = false;
+let alreadyDrawn = [false];
+let patternTime = { 'current': 0, 'max': 0, 'initial': 0 };
 
-player.value.load(url.value).then((result) => {
-	buffer = result;
-	try {
-		player.value.play(buffer);
-		progress.value!.max = player.value.duration();
-		display();
-	} catch (err) {
-		console.warn(err);
+function bakeNumberRow() {
+	let ctx = numberRowCanvas.getContext('2d', { alpha: false }) as OffscreenCanvasRenderingContext2D;
+	ctx.font = '10px monospace';
+
+	for (let i = 0; i < maxRowNumbers; i++) {
+		let rowText = i.toString(16);
+		if (rowText.length === 1) rowText = '0' + rowText;
+
+		ctx.fillStyle = colours.foreground.default;
+		if (i % 4 === 0) ctx.fillStyle = colours.foreground.quarter;
+
+		ctx.fillText(rowText, 0, 10 + i * 12);
 	}
-	player.value.stop();
-}).catch((error) => {
-	console.error(error);
+}
+
+onMounted(() => {
+	player.value.load(url.value).then((result) => {
+		buffer = result;
+		try {
+			player.value.play(buffer);
+			progress.value!.max = player.value.duration();
+			bakeNumberRow();
+			display();
+		} catch (err) {
+			console.warn(err);
+		}
+		player.value.stop();
+	}).catch((error) => {
+		console.error(error);
+	});
 });
 
 function playPause() {
@@ -133,7 +151,7 @@ function stop(noDisplayUpdate = false) {
 	if (!noDisplayUpdate) {
 		try {
 			player.value.play(buffer);
-			display();
+			display(true);
 		} catch (err) {
 			console.warn(err);
 		}
@@ -162,104 +180,256 @@ function performSeek() {
 
 function toggleVisible() {
 	hide.value = !hide.value;
-	if (!hide.value && patternHide.value) {
-		firstFrame.value = true;
-		patternHide.value = false;
+	if (!hide.value) {
+		lastPattern = -1;
+		lastDrawnRow = -1;
 	}
 	nextTick(() => { stop(hide.value); });
 }
 
 function togglePattern() {
 	patternHide.value = !patternHide.value;
-	if (!patternHide.value) {
-		if (player.value.getRow() === 0) {
-			try {
-				player.value.play(buffer);
-				display();
-			} catch (err) {
-				console.warn(err);
-			}
-			player.value.stop();
+	handleScrollBarEnable();
+
+	if (player.value.getRow() === 0 && player.value.getPattern() === 0) {
+		try {
+			player.value.play(buffer);
+			display(true);
+		} catch (err) {
+			console.warn(err);
 		}
+		player.value.stop();
+	} else {
+		display(true);
 	}
 }
 
-function display() {
-	if (!displayCanvas.value) {
-		stop();
-		return;
-	}
-
-	if (patternHide.value) return;
-
-	if (firstFrame.value) {
-		firstFrame.value = false;
-		patternHide.value = true;
-	}
-
+function drawPattern() {
+	if (!displayCanvas.value) return;
 	const canvas = displayCanvas.value;
 
+	const startTime = performance.now();
 	const pattern = player.value.getPattern();
+	const nbRows = player.value.getPatternNumRows(pattern);
 	const row = player.value.getRow();
+	const halfbuf = rowBuffer / 2;
+	const minRow = row - halfbuf;
+	const maxRow = row + halfbuf;
+
+	let rowDif = 0;
+
 	let nbChannels = 0;
 	if (player.value.currentPlayingNode) {
 		nbChannels = player.value.currentPlayingNode.nbChannels;
 	}
-	if (canvas.width !== 12 + 84 * nbChannels + 2) {
-		canvas.width = 12 + 84 * nbChannels + 2;
-		canvas.height = 12 * rowBuffer;
-	}
-	const nbRows = player.value.getPatternNumRows(pattern);
-	const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
-	ctx.font = '10px monospace';
-	ctx.fillStyle = colours.background;
-	ctx.fillRect(0, 0, canvas.width, canvas.height);
-	ctx.fillStyle = colours.default.inactive;
-	for (let rowOffset = 0; rowOffset < rowBuffer; rowOffset++) {
-		const rowToDraw = row - rowBuffer / 2 + rowOffset;
-		if (rowToDraw >= 0 && rowToDraw < nbRows) {
-			const active = (rowToDraw === row) ? 'active' : 'inactive';
-			let rowText = parseInt(rowToDraw).toString(16);
-			if (rowText.length === 1) {
-				rowText = '0' + rowText;
-			}
-			ctx.fillStyle = colours.default[active];
-			if (rowToDraw % 4 === 0) {
-				ctx.fillStyle = colours.quarter[active];
-			}
-			ctx.fillText(rowText, 0, 10 + rowOffset * 12);
-			for (let channel = 0; channel < nbChannels; channel++) {
-				const part = player.value.getPatternRowChannel(pattern, rowToDraw, channel);
-				const baseOffset = (2 + (part.length + 1) * channel) * CHAR_WIDTH;
-				const baseRowOffset = ROW_OFFSET_Y + rowOffset * CHAR_HEIGHT;
+	if (pattern === lastPattern) {
+		rowDif = row - lastDrawnRow;
+	} else {
+		if (patternTime.initial !== 0 && !alreadyHiddenOnce) {
+			const trackerTime = player.value.currentPlayingNode.getProcessTime();
 
-				ctx.fillStyle = colours.default[active];
-				ctx.fillText('|', baseOffset, baseRowOffset);
-
-				const note = part.substring(0, 3);
-				ctx.fillStyle = colours.default[active];
-				ctx.fillText(note, baseOffset + CHAR_WIDTH, baseRowOffset);
-
-				const instr = part.substring(4, 6);
-				ctx.fillStyle = colours.instr[active];
-				ctx.fillText(instr, baseOffset + CHAR_WIDTH * 5, baseRowOffset);
-
-				const volume = part.substring(6, 9);
-				ctx.fillStyle = colours.volume[active];
-				ctx.fillText(volume, baseOffset + CHAR_WIDTH * 7, baseRowOffset);
-
-				const fx = part.substring(10, 11);
-				ctx.fillStyle = colours.fx[active];
-				ctx.fillText(fx, baseOffset + CHAR_WIDTH * 11, baseRowOffset);
-
-				const op = part.substring(11, 13);
-				ctx.fillStyle = colours.operant[active];
-				ctx.fillText(op, baseOffset + CHAR_WIDTH * 12, baseRowOffset);
+			if (patternTime.initial + trackerTime.max > MAX_TIME_SPENT && trackerTime.max + patternTime.max > MAX_TIME_PER_ROW) {
+				alreadyHiddenOnce = true;
+				togglePattern();
+				return;
 			}
 		}
+
+		patternTime = { 'current': 0, 'max': 0, 'initial': 0 };
+		alreadyDrawn = [];
+		if (canvas.width !== (12 + 84 * nbChannels + 2)) canvas.width = 12 + 84 * nbChannels + 2;
+		if (canvas.height !== (12 * nbRows)) canvas.height = 12 * nbRows;
+	}
+
+	const ctx = canvas.getContext('2d', { alpha: false, desynchronized: true }) as CanvasRenderingContext2D;
+	if (ctx.font !== '10px monospace') ctx.font = '10px monospace';
+	ctx.imageSmoothingEnabled = false;
+	if (pattern !== lastPattern) {
+		ctx.fillStyle = colours.background;
+		ctx.fillRect(0, 0, canvas.width, canvas.height);
+		ctx.drawImage( numberRowCanvas, 0, 0 );
+	}
+
+	ctx.fillStyle = colours.foreground.default;
+	for (let rowOffset = minRow + rowDif; rowOffset < maxRow + rowDif; rowOffset++) {
+		const rowToDraw = rowOffset - rowDif;
+
+		if (alreadyDrawn[rowToDraw] === true) continue;
+
+		if (rowToDraw >= 0 && rowToDraw < nbRows) {
+			const baseOffset = 2 * CHAR_WIDTH;
+			const baseRowOffset = ROW_OFFSET_Y + rowToDraw * CHAR_HEIGHT;
+			let done = drawRow(ctx, rowToDraw, nbChannels, pattern, baseOffset, baseRowOffset);
+
+			alreadyDrawn[rowToDraw] = done;
+		}
+	}
+
+	lastDrawnRow = row;
+	lastPattern = pattern;
+
+	patternTime.current = performance.now() - startTime;
+	if (patternTime.initial !== 0 && patternTime.current > patternTime.max) patternTime.max = patternTime.current;
+	else if (patternTime.initial === 0) patternTime.initial = patternTime.current;
+}
+
+function drawPetternPreview() {
+	if (!displayCanvas.value) return;
+	const canvas = displayCanvas.value;
+
+	const pattern = player.value.getPattern();
+	const nbRows = player.value.getPatternNumRows(pattern);
+	const row = player.value.getRow();
+	const halfbuf = rowBuffer / 2;
+	alreadyDrawn = [];
+
+	let nbChannels = 0;
+	if (player.value.currentPlayingNode) {
+		nbChannels = player.value.currentPlayingNode.nbChannels;
+	}
+	if (canvas.width !== (12 + 84 * nbChannels + 2)) canvas.width = 12 + 84 * nbChannels + 2;
+	if (canvas.height !== (12 * rowBuffer)) canvas.height = 12 * rowBuffer;
+
+	const ctx = canvas.getContext('2d', { alpha: false }) as CanvasRenderingContext2D;
+	ctx.font = '10px monospace';
+	ctx.imageSmoothingEnabled = false;
+	ctx.fillStyle = colours.background;
+	ctx.fillRect(0, 0, canvas.width, canvas.height);
+	ctx.drawImage( numberRowCanvas, 0, (halfbuf - row) * CHAR_HEIGHT );
+
+	for (let rowOffset = 0; rowOffset < rowBuffer; rowOffset++) {
+		const rowToDraw = rowOffset + row - halfbuf;
+
+		if (rowToDraw >= 0 && rowToDraw < nbRows) {
+			const baseOffset = 2 * CHAR_WIDTH;
+			const baseRowOffset = ROW_OFFSET_Y + rowOffset * CHAR_HEIGHT;
+			drawRow(ctx, rowToDraw, nbChannels, pattern, baseOffset, baseRowOffset);
+		} else if (rowToDraw >= 0) {
+			const baseRowOffset = ROW_OFFSET_Y + rowOffset * CHAR_HEIGHT;
+			ctx.fillStyle = colours.background;
+			ctx.fillRect(0, baseRowOffset - CHAR_HEIGHT, CHAR_WIDTH * 2, baseRowOffset);
+		}
+	}
+
+	lastPattern = -1;
+	lastDrawnRow = -1;
+}
+
+function drawRow(ctx: CanvasRenderingContext2D, row: number, channels: number, pattern: number, drawX = (2 * CHAR_WIDTH), drawY = ROW_OFFSET_Y) {
+	if (!player.value.currentPlayingNode) return false;
+	if (alreadyDrawn[row]) return true;
+	const spacer = 11;
+	const space = ' ';
+	let seperators = '';
+	let note = '';
+	let instr = '';
+	let volume = '';
+	let fx = '';
+	let op = '';
+	for (let channel = 0; channel < channels; channel++) {
+		const part = player.value.getPatternRowChannel(pattern, row, channel);
+
+		seperators += '|' + space.repeat( spacer + 2 );
+		note += part.substring(0, 3) + space.repeat( spacer );
+		instr += part.substring(4, 6) + space.repeat( spacer + 1 );
+		volume += part.substring(6, 9) + space.repeat( spacer );
+		fx += part.substring(10, 11) + space.repeat( spacer + 2 );
+		op += part.substring(11, 13) + space.repeat( spacer + 1 );
+	}
+
+	ctx.fillStyle = colours.foreground.default;
+	ctx.fillText(seperators, drawX, drawY);
+
+	ctx.fillStyle = colours.foreground.default;
+	ctx.fillText(note, drawX + CHAR_WIDTH, drawY);
+
+	ctx.fillStyle = colours.foreground.instr;
+	ctx.fillText(instr, drawX + CHAR_WIDTH * 5, drawY);
+
+	ctx.fillStyle = colours.foreground.volume;
+	ctx.fillText(volume, drawX + CHAR_WIDTH * 7, drawY);
+
+	ctx.fillStyle = colours.foreground.fx;
+	ctx.fillText(fx, drawX + CHAR_WIDTH * 11, drawY);
+
+	ctx.fillStyle = colours.foreground.operant;
+	ctx.fillText(op, drawX + CHAR_WIDTH * 12, drawY);
+
+	return true;
+}
+
+function display(skipOptimizationChecks = false) {
+	if (!displayCanvas.value || !displayCanvas.value.parentElement) {
+		stop();
+		return;
+	}
+
+	if (patternHide.value && !skipOptimizationChecks) return;
+
+	if (firstFrame) {
+		// Changing it to false should enable pattern display by default.
+		patternHide.value = true;
+		handleScrollBarEnable();
+		firstFrame = false;
+	}
+
+	const row = player.value.getRow();
+	const pattern = player.value.getPattern();
+
+	if ( row === lastDrawnRow && pattern === lastPattern && !skipOptimizationChecks) return;
+
+	// Size vs speed
+	if (patternHide.value) drawPetternPreview();
+	else drawPattern();
+
+	displayCanvas.value.style.top = !patternHide.value ? 'calc( 50% - ' + (row * CHAR_HEIGHT) + 'px )' : '0%';
+}
+
+let suppressScrollSliderWatcher = false;
+
+function scrollHandler() {
+	suppressScrollSliderWatcher = true;
+
+	if (!patternScrollSlider.value) return;
+	if (!displayCanvas.value) return;
+	if (!displayCanvas.value.parentElement) return;
+
+	patternScrollSliderPos.value = (displayCanvas.value.parentElement.scrollLeft) / (displayCanvas.value.width - displayCanvas.value.parentElement.offsetWidth) * 100;
+	patternScrollSlider.value.style.opacity = '1';
+}
+
+function scrollEndHandle() {
+	suppressScrollSliderWatcher = false;
+
+	if (!patternScrollSlider.value) return;
+	patternScrollSlider.value.style.opacity = '';
+}
+
+function handleScrollBarEnable() {
+	patternScrollSliderShow.value = (!patternHide.value && !isTouchUsing);
+	if (patternScrollSliderShow.value !== true) return;
+
+	if (!displayCanvas.value) return;
+	if (!displayCanvas.value.parentElement) return;
+	if (firstFrame) {
+		patternScrollSliderShow.value = (12 + 84 * player.value.getPatternNumRows(player.value.getPattern()) + 2 > displayCanvas.value.parentElement.offsetWidth);
+	} else {
+		patternScrollSliderShow.value = (displayCanvas.value.width > displayCanvas.value.parentElement.offsetWidth);
 	}
 }
 
+watch(patternScrollSliderPos, () => {
+	if (suppressScrollSliderWatcher) return;
+	if (!displayCanvas.value) return;
+	if (!displayCanvas.value.parentElement) return;
+
+	displayCanvas.value.parentElement.scrollLeft = (displayCanvas.value.width - displayCanvas.value.parentElement.offsetWidth) * patternScrollSliderPos.value / 100;
+});
+
+onDeactivated(() => {
+	stop();
+});
+
 </script>
 
 <style lang="scss" scoped>
@@ -290,6 +460,7 @@ function display() {
 		cursor: pointer;
 		top: 12px;
 		right: 12px;
+		z-index: 4;
 	}
 
 	> .pattern-display {
@@ -299,22 +470,55 @@ function display() {
 		overflow-y: hidden;
 		background-color: black;
 		text-align: center;
-		.pattern-canvas {
-			background-color: black;
-			height: 100%;
+		max-height: 312px; /* magic_number = CHAR_HEIGHT * rowBuffer, needs to be in px */
+
+		scrollbar-width: none;
+
+		&::-webkit-scrollbar {
+			display: none;
 		}
+
+		.pattern-canvas {
+			position: relative;
+			background-color: black;
+			image-rendering: pixelated;
+			pointer-events: none;
+			z-index: 0;
+		}
+
+		.patternShadowTop {
+			background: #00000080;
+			width: 100%;
+			height: calc( 50% - 14px );
+			translate: 0 -100%;
+			top: calc( 50% - 14px );
+			position: absolute;
+			pointer-events: none;
+			z-index: 2;
+		}
+
+		.patternShadowBottom {
+			background: #00000080;
+			width: 100%;
+			height: calc( 50% - 12px );
+			top: calc( 50% - 1px );
+			position: absolute;
+			pointer-events: none;
+			z-index: 2;
+		}
+
 		.pattern-hide {
 			display: flex;
 			flex-direction: column;
 			justify-content: center;
 			align-items: center;
 			background: rgba(64, 64, 64, 0.3);
-			backdrop-filter: blur(2em);
+			backdrop-filter: var(--modalBgFilter);
 			color: #fff;
 			font-size: 12px;
 
 			position: absolute;
-			z-index: 0;
+			z-index: 4;
 			width: 100%;
 			height: 100%;
 
@@ -328,7 +532,7 @@ function display() {
 		display: flex;
 		width: 100%;
 		background-color: var(--bg);
-		z-index: 1;
+		z-index: 5;
 
 		> * {
 			padding: 4px 8px;
@@ -353,6 +557,18 @@ function display() {
 			margin: 4px 8px;
 			overflow-x: hidden;
 
+			&.pattern-slider {
+				position: absolute;
+				width: calc( 100% - 8px * 2 );
+				top: calc( 100% - 21px * 3 );
+				opacity: 0%;
+				transition: opacity 0.2s;
+
+				&:hover {
+					opacity: 100%;
+				}
+			}
+
 			&:focus {
 				outline: none;
 
diff --git a/packages/frontend/src/components/MkModal.vue b/packages/frontend/src/components/MkModal.vue
index 5cd31cdf7c..40e67fb4e0 100644
--- a/packages/frontend/src/components/MkModal.vue
+++ b/packages/frontend/src/components/MkModal.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkModalWindow.vue b/packages/frontend/src/components/MkModalWindow.vue
index b91988304d..fc634176c7 100644
--- a/packages/frontend/src/components/MkModalWindow.vue
+++ b/packages/frontend/src/components/MkModalWindow.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -51,7 +51,7 @@ const bodyWidth = ref(0);
 const bodyHeight = ref(0);
 
 const close = () => {
-	modal.value.close();
+	modal.value?.close();
 };
 
 const onBgClick = () => {
@@ -67,11 +67,13 @@ const onKeydown = (evt) => {
 };
 
 const ro = new ResizeObserver((entries, observer) => {
+	if (rootEl.value == null || headerEl.value == null) return;
 	bodyWidth.value = rootEl.value.offsetWidth;
 	bodyHeight.value = rootEl.value.offsetHeight - headerEl.value.offsetHeight;
 });
 
 onMounted(() => {
+	if (rootEl.value == null || headerEl.value == null) return;
 	bodyWidth.value = rootEl.value.offsetWidth;
 	bodyHeight.value = rootEl.value.offsetHeight - headerEl.value.offsetHeight;
 	ro.observe(rootEl.value);
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 8a3b4cef48..9a667c3118 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -1,13 +1,13 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
 <div
-	v-if="!hardMuted && !muted"
+	v-if="!hardMuted && muted === false"
 	v-show="!isDeleted"
-	ref="el"
+	ref="rootEl"
 	v-hotkey="keymap"
 	:class="[$style.root, { [$style.showActionsOnlyHover]: defaultStore.state.showNoteActionsOnlyHover }]"
 	:tabindex="!isDeleted ? '-1' : undefined"
@@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</span>
 			<span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ph-rocket ph-bold ph-lg"></i></span>
 			<span v-if="note.channel" style="margin-left: 0.5em;" :title="note.channel.name"><i class="ph-television ph-bold ph-lg"></i></span>
-			<span v-if="note.updatedAt" ref="menuVersionsButton" style="margin-left: 0.5em;" title="Edited" @mousedown="menuVersions()"><i class="ph-pencil ph-bold ph-lg"></i></span>
+			<span v-if="note.updatedAt" ref="menuVersionsButton" style="margin-left: 0.5em;" title="Edited" @mousedown="menuVersions()"><i class="ph-pencil-simple ph-bold ph-lg"></i></span>
 		</div>
 	</div>
 	<div v-if="renoteCollapsed" :class="$style.collapsedRenoteTarget">
@@ -49,7 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<article v-else :class="$style.article" @contextmenu.stop="onContextmenu">
 		<div v-if="appearNote.channel" :class="$style.colorBar" :style="{ background: appearNote.channel.color }"></div>
 		<MkAvatar :class="$style.avatar" :user="appearNote.user" :link="!mock" :preview="!mock"/>
-		<div :class="[$style.main, { [$style.clickToOpen]: defaultStore.state.clickToOpen }]" @click="defaultStore.state.clickToOpen ? noteclick(appearNote.id) : undefined">
+		<div :class="[$style.main, { [$style.clickToOpen]: defaultStore.state.clickToOpen }]" @click.stop="defaultStore.state.clickToOpen ? noteclick(appearNote.id) : undefined">
 			<MkNoteHeader :note="appearNote" :mini="true" @click.stop/>
 			<MkInstanceTicker v-if="showTicker" :instance="appearNote.user.instance"/>
 			<div style="container-type: inline-size;">
@@ -74,18 +74,18 @@ SPDX-License-Identifier: AGPL-3.0-only
 						/>
 						<div v-if="translating || translation" :class="$style.translation">
 							<MkLoading v-if="translating" mini/>
-							<div v-else>
-								<b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b>
+							<div v-else-if="translation">
+								<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
 								<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
 							</div>
 						</div>
 						<MkButton v-if="!allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-play ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.play }}</MkButton>
 						<MkButton v-else-if="!defaultStore.state.animatedMfm && allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-stop ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.stop }}</MkButton>
 					</div>
-					<div v-if="appearNote.files.length > 0">
+					<div v-if="appearNote.files && appearNote.files.length > 0">
 						<MkMediaList :mediaList="appearNote.files" @click.stop/>
 					</div>
-					<MkPoll v-if="appearNote.poll" :note="appearNote" :class="$style.poll" @click.stop/>
+					<MkPoll v-if="appearNote.poll" :noteId="appearNote.id" :poll="appearNote.poll" :class="$style.poll" @click.stop/>
 					<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" :class="$style.urlPreview" @click.stop/>
 					<div v-if="appearNote.renote" :class="$style.quote"><MkNoteSimple :note="appearNote.renote" :class="$style.quoteNote"/></div>
 					<button v-if="isLong && collapsed" :class="$style.collapsed" class="_button" @click.stop @click="collapsed = false">
@@ -145,7 +145,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown="clip()">
 					<i class="ph-paperclip ph-bold ph-lg"></i>
 				</button>
-				<button ref="menuButton" :class="$style.footerButton" class="_button" @mousedown="menu()">
+				<button ref="menuButton" :class="$style.footerButton" class="_button" @mousedown="showMenu()">
 					<i class="ph-dots-three ph-bold ph-lg"></i>
 				</button>
 			</footer>
@@ -153,7 +153,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 	</article>
 </div>
 <div v-else-if="!hardMuted" :class="$style.muted" @click="muted = false">
-	<I18n :src="i18n.ts.userSaysSomething" tag="small">
+	<I18n v-if="muted === 'sensitiveMute'" :src="i18n.ts.userSaysSomethingSensitive" tag="small">
+		<template #name>
+			<MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)">
+				<MkUserName :user="appearNote.user"/>
+			</MkA>
+		</template>
+	</I18n>
+	<I18n v-else :src="i18n.ts.userSaysSomething" tag="small">
 		<template #name>
 			<MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)">
 				<MkUserName :user="appearNote.user"/>
@@ -171,7 +178,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { computed, inject, onMounted, ref, shallowRef, Ref, watch, provide } from 'vue';
-import * as mfm from '@sharkey/sfm-js';
+import * as mfm from '@transfem-org/sfm-js';
 import * as Misskey from 'misskey-js';
 import MkNoteSub from '@/components/MkNoteSub.vue';
 import MkNoteHeader from '@/components/MkNoteHeader.vue';
@@ -190,6 +197,7 @@ import { checkWordMute } from '@/scripts/check-word-mute.js';
 import { userPage } from '@/filters/user.js';
 import * as os from '@/os.js';
 import * as sound from '@/scripts/sound.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { defaultStore, noteViewInterruptors } from '@/store.js';
 import { reactionPicker } from '@/scripts/reaction-picker.js';
 import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js';
@@ -207,7 +215,8 @@ import { MenuItem } from '@/types/menu.js';
 import MkRippleEffect from '@/components/MkRippleEffect.vue';
 import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
 import { shouldCollapsed } from '@/scripts/collapsed.js';
-import { useRouter } from '@/router.js';
+import { useRouter } from '@/router/supplier.js';
+import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js';
 
 const props = withDefaults(defineProps<{
 	note: Misskey.entities.Note;
@@ -227,6 +236,7 @@ const emit = defineEmits<{
 
 const router = useRouter();
 
+const inTimeline = inject<boolean>('inTimeline', false);
 const inChannel = inject('inChannel', null);
 const currentClip = inject<Ref<Misskey.entities.Clip> | null>('currentClip', null);
 
@@ -245,7 +255,7 @@ if (noteViewInterruptors.length > 0) {
 		let result: Misskey.entities.Note | null = deepClone(note.value);
 		for (const interruptor of noteViewInterruptors) {
 			try {
-				result = await interruptor.handler(result);
+				result = await interruptor.handler(result!) as Misskey.entities.Note | null;
 				if (result === null) {
 					isDeleted.value = true;
 					return;
@@ -254,7 +264,7 @@ if (noteViewInterruptors.length > 0) {
 				console.error(err);
 			}
 		}
-		note.value = result;
+		note.value = result as Misskey.entities.Note;
 	});
 }
 
@@ -262,11 +272,11 @@ const isRenote = (
 	note.value.renote != null &&
 	note.value.text == null &&
 	note.value.cw == null &&
-	note.value.fileIds.length === 0 &&
+	note.value.fileIds && note.value.fileIds.length === 0 &&
 	note.value.poll == null
 );
 
-const el = shallowRef<HTMLElement>();
+const rootEl = shallowRef<HTMLElement>();
 const menuButton = shallowRef<HTMLElement>();
 const menuVersionsButton = shallowRef<HTMLElement>();
 const renoteButton = shallowRef<HTMLElement>();
@@ -276,50 +286,61 @@ const quoteButton = shallowRef<HTMLElement>();
 const clipButton = shallowRef<HTMLElement>();
 const likeButton = shallowRef<HTMLElement>();
 const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value);
-const renoteUrl = appearNote.value.renote ? appearNote.value.renote.url : null;
-const renoteUri = appearNote.value.renote ? appearNote.value.renote.uri : null;
 
 const isMyRenote = $i && ($i.id === note.value.userId);
 const showContent = ref(defaultStore.state.uncollapseCW);
-const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text).filter(u => u !== renoteUrl && u !== renoteUri) : null);
-const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value) : null);
+const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
+const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null);
 const isLong = shouldCollapsed(appearNote.value, urls.value ?? []);
-const collapsed = defaultStore.state.expandLongNote && appearNote.value.cw == null ? false : ref(appearNote.value.cw == null && isLong);
+const collapsed = ref(defaultStore.state.expandLongNote && appearNote.value.cw == null && isLong ? false : appearNote.value.cw == null && isLong);
 const isDeleted = ref(false);
 const renoted = ref(false);
 const muted = ref(checkMute(appearNote.value, $i?.mutedWords));
-const hardMuted = ref(props.withHardMute && checkMute(appearNote.value, $i?.hardMutedWords));
+const hardMuted = ref(props.withHardMute && checkMute(appearNote.value, $i?.hardMutedWords, true));
 const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null);
 const translating = ref(false);
 const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance);
-const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || (appearNote.value.visibility === 'followers' && appearNote.value.userId === $i.id));
-const renoteCollapsed = ref(defaultStore.state.collapseRenotes && isRenote && (($i && ($i.id === note.value.userId || $i.id === appearNote.value.userId)) || (appearNote.value.myReaction != null)));
+const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || (appearNote.value.visibility === 'followers' && appearNote.value.userId === $i?.id));
+const renoteCollapsed = ref(
+	defaultStore.state.collapseRenotes && isRenote && (
+		($i && ($i.id === note.value.userId || $i.id === appearNote.value.userId)) || // `||` must be `||`! See https://github.com/misskey-dev/misskey/issues/13131
+		(appearNote.value.myReaction != null)
+	)
+);
 const defaultLike = computed(() => defaultStore.state.like ? defaultStore.state.like : null);
 const animated = computed(() => parsed.value ? checkAnimationFromMfm(parsed.value) : null);
 const allowAnim = ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false);
 
-function checkMute(note: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null): boolean {
+/* Overload FunctionにLintが対応していないのでコメントアウト
+function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: true): boolean;
+function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: false): boolean | 'sensitiveMute';
+*/
+function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly = false): boolean | 'sensitiveMute' {
 	if (mutedWords == null) return false;
 
-	if (checkWordMute(note, $i, mutedWords)) return true;
-	if (note.reply && checkWordMute(note.reply, $i, mutedWords)) return true;
-	if (note.renote && checkWordMute(note.renote, $i, mutedWords)) return true;
+	if (checkWordMute(noteToCheck, $i, mutedWords)) return true;
+	if (noteToCheck.reply && checkWordMute(noteToCheck.reply, $i, mutedWords)) return true;
+	if (noteToCheck.renote && checkWordMute(noteToCheck.renote, $i, mutedWords)) return true;
+
+	if (checkOnly) return false;
+
+	if (inTimeline && !defaultStore.state.tl.filter.withSensitive && noteToCheck.files?.some((v) => v.isSensitive)) return 'sensitiveMute';
 	return false;
 }
 
 const keymap = {
 	'r': () => reply(true),
 	'e|a|plus': () => react(true),
-	'q': () => renoteButton.value.renote(true),
+	'q': () => renote(appearNote.value.visibility),
 	'up|k|shift+tab': focusBefore,
 	'down|j|tab': focusAfter,
 	'esc': blur,
-	'm|o': () => menu(true),
+	'm|o': () => showMenu(true),
 	's': () => showContent.value !== showContent.value,
 };
 
 provide('react', (reaction: string) => {
-	os.api('notes/reactions/create', {
+	misskeyApi('notes/reactions/create', {
 		noteId: appearNote.value.id,
 		reaction: reaction,
 	});
@@ -331,7 +352,7 @@ if (props.mock) {
 	}, { deep: true });
 } else {
 	useNoteCapture({
-		rootEl: el,
+		rootEl: rootEl,
 		note: appearNote,
 		pureNote: note,
 		isDeletedRef: isDeleted,
@@ -340,7 +361,7 @@ if (props.mock) {
 
 if (!props.mock) {
 	useTooltip(renoteButton, async (showing) => {
-		const renotes = await os.api('notes/renotes', {
+		const renotes = await misskeyApi('notes/renotes', {
 			noteId: appearNote.value.id,
 			limit: 11,
 		});
@@ -358,7 +379,7 @@ if (!props.mock) {
 	});
 
 	useTooltip(quoteButton, async (showing) => {
-		const renotes = await os.api('notes/renotes', {
+		const renotes = await misskeyApi('notes/renotes', {
 			noteId: appearNote.value.id,
 			limit: 11,
 			quote: true,
@@ -377,7 +398,7 @@ if (!props.mock) {
 	});
 
 	if ($i) {
-		os.api('notes/renotes', {
+		misskeyApi('notes/renotes', {
 			noteId: appearNote.value.id,
 			userId: $i.id,
 			limit: 1,
@@ -387,54 +408,15 @@ if (!props.mock) {
 	}
 }
 
-type Visibility = 'public' | 'home' | 'followers' | 'specified';
-
-// defaultStore.state.visibilityがstringなためstringも受け付けている
-function smallerVisibility(a: Visibility | string, b: Visibility | string): Visibility {
-	if (a === 'specified' || b === 'specified') return 'specified';
-	if (a === 'followers' || b === 'followers') return 'followers';
-	if (a === 'home' || b === 'home') return 'home';
-	// if (a === 'public' || b === 'public')
-	return 'public';
-}
-
 function boostVisibility() {
-	os.popupMenu([
-		{
-			type: 'button',
-			icon: 'ph-globe-hemisphere-west ph-bold ph-lg',
-			text: i18n.ts._visibility['public'],
-			action: () => {
-				renote('public');
-			},
-		},
-		{
-			type: 'button',
-			icon: 'ph-house ph-bold ph-lg',
-			text: i18n.ts._visibility['home'],
-			action: () => {
-				renote('home');
-			},
-		},
-		{
-			type: 'button',
-			icon: 'ph-lock ph-bold ph-lg',
-			text: i18n.ts._visibility['followers'],
-			action: () => {
-				renote('followers');
-			},
-		},
-		{
-			type: 'button',
-			icon: 'ph-planet ph-bold ph-lg',
-			text: i18n.ts._timelines.local,
-			action: () => {
-				renote('local');
-			},
-		}], renoteButton.value);
+	if (!defaultStore.state.showVisibilitySelectorOnBoost) {
+		renote(defaultStore.state.visibilityOnBoost);
+	} else {
+		os.popupMenu(boostMenuItems(appearNote, renote), renoteButton.value);
+	}
 }
 
-function renote(visibility: Visibility | 'local') {
+function renote(visibility: Visibility, localOnly: boolean = false) {
 	pleaseLogin();
 	showMovedDialog();
 
@@ -448,7 +430,7 @@ function renote(visibility: Visibility | 'local') {
 		}
 
 		if (!props.mock) {
-			os.api('notes/create', {
+			misskeyApi('notes/create', {
 				renoteId: appearNote.value.id,
 				channelId: appearNote.value.channelId,
 			}).then(() => {
@@ -456,7 +438,7 @@ function renote(visibility: Visibility | 'local') {
 				renoted.value = true;
 			});
 		}
-	} else if (!appearNote.value.channel || appearNote.value.channel?.allowRenoteToExternal) {
+	} else if (!appearNote.value.channel || appearNote.value.channel.allowRenoteToExternal) {
 		const el = renoteButton.value as HTMLElement | null | undefined;
 		if (el) {
 			const rect = el.getBoundingClientRect();
@@ -465,18 +447,10 @@ function renote(visibility: Visibility | 'local') {
 			os.popup(MkRippleEffect, { x, y }, {}, 'end');
 		}
 
-		const configuredVisibility = defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility;
-		const localOnlySetting = defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly;
-
-		let noteVisibility = visibility === 'local' || visibility === 'specified' ? smallerVisibility(appearNote.value.visibility, configuredVisibility) : smallerVisibility(visibility, configuredVisibility);
-		if (appearNote.value.channel?.isSensitive) {
-			noteVisibility = smallerVisibility(visibility === 'local' || visibility === 'specified' ? appearNote.value.visibility : visibility, 'home');
-		}
-
 		if (!props.mock) {
-			os.api('notes/create', {
-				localOnly: visibility === 'local' ? true : localOnlySetting,
-				visibility: noteVisibility,
+			misskeyApi('notes/create', {
+				localOnly: localOnly,
+				visibility: visibility,
 				renoteId: appearNote.value.id,
 			}).then(() => {
 				os.toast(i18n.ts.renoted);
@@ -498,9 +472,9 @@ function quote() {
 			renote: appearNote.value,
 			channel: appearNote.value.channel,
 		}).then(() => {
-			os.api('notes/renotes', {
+			misskeyApi('notes/renotes', {
 				noteId: appearNote.value.id,
-				userId: $i.id,
+				userId: $i?.id,
 				limit: 1,
 				quote: true,
 			}).then((res) => {
@@ -520,9 +494,9 @@ function quote() {
 		os.post({
 			renote: appearNote.value,
 		}).then(() => {
-			os.api('notes/renotes', {
+			misskeyApi('notes/renotes', {
 				noteId: appearNote.value.id,
-				userId: $i.id,
+				userId: $i?.id,
 				limit: 1,
 				quote: true,
 			}).then((res) => {
@@ -550,7 +524,7 @@ function reply(viaKeyboard = false): void {
 		reply: appearNote.value,
 		channel: appearNote.value.channel,
 		animation: !viaKeyboard,
-	}, () => {
+	}).then(() => {
 		focus();
 	});
 }
@@ -558,10 +532,11 @@ function reply(viaKeyboard = false): void {
 function like(): void {
 	pleaseLogin();
 	showMovedDialog();
+	sound.playMisskeySfx('reaction');
 	if (props.mock) {
 		return;
 	}
-	os.api('notes/like', {
+	misskeyApi('notes/like', {
 		noteId: appearNote.value.id,
 		override: defaultLike.value,
 	});
@@ -578,17 +553,17 @@ function react(viaKeyboard = false): void {
 	pleaseLogin();
 	showMovedDialog();
 	if (appearNote.value.reactionAcceptance === 'likeOnly') {
-		sound.play('reaction');
+		sound.playMisskeySfx('reaction');
 
 		if (props.mock) {
 			return;
 		}
 
-		os.api('notes/like', {
+		misskeyApi('notes/like', {
 			noteId: appearNote.value.id,
 			override: defaultLike.value,
 		});
-		const el = reactButton.value as HTMLElement | null | undefined;
+		const el = reactButton.value;
 		if (el) {
 			const rect = el.getBoundingClientRect();
 			const x = rect.left + (el.offsetWidth / 2);
@@ -597,15 +572,15 @@ function react(viaKeyboard = false): void {
 		}
 	} else {
 		blur();
-		reactionPicker.show(reactButton.value, reaction => {
-			sound.play('reaction');
+		reactionPicker.show(reactButton.value ?? null, note.value, reaction => {
+			sound.playMisskeySfx('reaction');
 
 			if (props.mock) {
 				emit('reaction', reaction);
 				return;
 			}
 
-			os.api('notes/reactions/create', {
+			misskeyApi('notes/reactions/create', {
 				noteId: appearNote.value.id,
 				reaction: reaction,
 			});
@@ -618,8 +593,8 @@ function react(viaKeyboard = false): void {
 	}
 }
 
-function undoReact(note): void {
-	const oldReaction = note.myReaction;
+function undoReact(targetNote: Misskey.entities.Note): void {
+	const oldReaction = targetNote.myReaction;
 	if (!oldReaction) return;
 
 	if (props.mock) {
@@ -627,8 +602,8 @@ function undoReact(note): void {
 		return;
 	}
 
-	os.api('notes/reactions/delete', {
-		noteId: note.id,
+	misskeyApi('notes/reactions/delete', {
+		noteId: targetNote.id,
 	});
 }
 
@@ -636,7 +611,7 @@ function undoRenote(note) : void {
 	if (props.mock) {
 		return;
 	}
-	os.api('notes/unrenote', {
+	misskeyApi('notes/unrenote', {
 		noteId: note.id,
 	});
 	os.toast(i18n.ts.rmboost);
@@ -656,32 +631,34 @@ function onContextmenu(ev: MouseEvent): void {
 		return;
 	}
 
-	const isLink = (el: HTMLElement) => {
+	const isLink = (el: HTMLElement): boolean => {
 		if (el.tagName === 'A') return true;
 		// 再生速度の選択などのために、Audio要素のコンテキストメニューはブラウザデフォルトとする。
 		if (el.tagName === 'AUDIO') return true;
 		if (el.parentElement) {
 			return isLink(el.parentElement);
 		}
+		return false;
 	};
-	if (isLink(ev.target)) return;
-	if (window.getSelection().toString() !== '') return;
+
+	if (ev.target && isLink(ev.target as HTMLElement)) return;
+	if (window.getSelection()?.toString() !== '') return;
 
 	if (defaultStore.state.useReactionPickerForContextMenu) {
 		ev.preventDefault();
 		react();
 	} else {
-		const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, menuButton, isDeleted, currentClip: currentClip?.value });
+		const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value });
 		os.contextMenu(menu, ev).then(focus).finally(cleanup);
 	}
 }
 
-function menu(viaKeyboard = false): void {
+function showMenu(viaKeyboard = false): void {
 	if (props.mock) {
 		return;
 	}
 
-	const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, menuButton, isDeleted, currentClip: currentClip?.value });
+	const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value });
 	os.popupMenu(menu, menuButton.value, {
 		viaKeyboard,
 	}).then(focus).finally(cleanup);
@@ -713,7 +690,7 @@ function showRenoteMenu(viaKeyboard = false): void {
 			icon: 'ph-trash ph-bold ph-lg',
 			danger: true,
 			action: () => {
-				os.api('notes/delete', {
+				misskeyApi('notes/delete', {
 					noteId: note.value.id,
 				});
 				isDeleted.value = true;
@@ -735,7 +712,7 @@ function showRenoteMenu(viaKeyboard = false): void {
 			getCopyNoteLinkMenu(note.value, i18n.ts.copyLinkRenote),
 			{ type: 'divider' },
 			getAbuseNoteMenu(note.value, i18n.ts.reportAbuseRenote),
-			$i.isModerator || $i.isAdmin ? getUnrenote() : undefined,
+			($i?.isModerator || $i?.isAdmin) ? getUnrenote() : undefined,
 		], renoteTime.value, {
 			viaKeyboard: viaKeyboard,
 		});
@@ -755,23 +732,23 @@ function animatedMFM() {
 }
 
 function focus() {
-	el.value.focus();
+	rootEl.value?.focus();
 }
 
 function blur() {
-	el.value.blur();
+	rootEl.value?.blur();
 }
 
 function focusBefore() {
-	focusPrev(el.value);
+	focusPrev(rootEl.value ?? null);
 }
 
 function focusAfter() {
-	focusNext(el.value);
+	focusNext(rootEl.value ?? null);
 }
 
 function readPromo() {
-	os.api('promo/read', {
+	misskeyApi('promo/read', {
 		noteId: appearNote.value.id,
 	});
 	isDeleted.value = true;
@@ -825,12 +802,13 @@ function emitUpdReaction(emoji: string, delta: number) {
 	}
 
 	.footer {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
 		position: relative;
 		z-index: 1;
 		margin-top: 0.4em;
-		width: max-content;
-		min-width: min-content;
-		max-width: fit-content;
+		max-width: 400px;
 	}
 
 	&:hover > .article > .main > .footer > .footerButton {
@@ -986,8 +964,8 @@ function emitUpdReaction(emoji: string, delta: number) {
 	flex-shrink: 0;
 	display: block !important;
 	margin: 0 14px 0 0;
-	width: 58px;
-	height: 58px;
+	width: var(--avatar);
+	height: var(--avatar);
 	position: sticky !important;
 	top: calc(22px + var(--stickyTop, 0px));
 	left: 0;
@@ -1249,5 +1227,6 @@ function emitUpdReaction(emoji: string, delta: number) {
 
 .clickToOpen {
 	cursor: pointer;
+	-webkit-tap-highlight-color: transparent;
 }
 </style>
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index e287890e2c..3d15f69f73 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <div
 	v-if="!muted"
 	v-show="!isDeleted"
-	ref="el"
+	ref="rootEl"
 	v-hotkey="keymap"
 	:class="$style.root"
 >
@@ -58,7 +58,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 							<i v-else-if="appearNote.visibility === 'followers'" class="ph-lock ph-bold ph-lg"></i>
 							<i v-else-if="appearNote.visibility === 'specified'" ref="specified" class="ph-envelope ph-bold ph-lg"></i>
 						</span>
-						<span v-if="appearNote.updatedAt" ref="menuVersionsButton" style="margin-left: 0.5em;" title="Edited" @mousedown="menuVersions()"><i class="ph-pencil ph-bold ph-lg"></i></span>
+						<span v-if="appearNote.updatedAt" ref="menuVersionsButton" style="margin-left: 0.5em;" title="Edited" @mousedown="menuVersions()"><i class="ph-pencil-simple ph-bold ph-lg"></i></span>
 						<span v-if="appearNote.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ph-rocket ph-bold ph-lg"></i></span>
 					</div>
 				</div>
@@ -88,17 +88,17 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<a v-if="appearNote.renote != null" :class="$style.rn">RN:</a>
 				<div v-if="translating || translation" :class="$style.translation">
 					<MkLoading v-if="translating" mini/>
-					<div v-else>
-						<b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b>
+					<div v-else-if="translation">
+						<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
 						<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
 					</div>
 				</div>
 				<MkButton v-if="!allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-play ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.play }}</MkButton>
 				<MkButton v-else-if="!defaultStore.state.animatedMfm && allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-stop ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.stop }}</MkButton>
-				<div v-if="appearNote.files.length > 0">
+				<div v-if="appearNote.files && appearNote.files.length > 0">
 					<MkMediaList :mediaList="appearNote.files"/>
 				</div>
-				<MkPoll v-if="appearNote.poll" ref="pollViewer" :note="appearNote" :class="$style.poll"/>
+				<MkPoll v-if="appearNote.poll" ref="pollViewer" :noteId="appearNote.id" :poll="appearNote.poll" :class="$style.poll"/>
 				<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="true" style="margin-top: 6px;"/>
 				<div v-if="appearNote.renote" :class="$style.quote"><MkNoteSimple :note="appearNote.renote" :class="$style.quoteNote" :expandAllCws="props.expandAllCws"/></div>
 			</div>
@@ -154,7 +154,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @mousedown="clip()">
 				<i class="ph-paperclip ph-bold ph-lg"></i>
 			</button>
-			<button ref="menuButton" class="_button" :class="$style.noteFooterButton" @mousedown="menu()">
+			<button ref="menuButton" class="_button" :class="$style.noteFooterButton" @mousedown="showMenu()">
 				<i class="ph-dots-three ph-bold ph-lg"></i>
 			</button>
 		</footer>
@@ -166,11 +166,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'reactions' }]" @click="tab = 'reactions'"><i class="ph-smiley ph-bold ph-lg"></i> {{ i18n.ts.reactions }}</button>
 	</div>
 	<div>
-		<div v-if="tab === 'replies'" :class="$style.tab_replies">
+		<div v-if="tab === 'replies'">
 			<div v-if="!repliesLoaded" style="padding: 16px">
 				<MkButton style="margin: 0 auto;" primary rounded @click="loadReplies">{{ i18n.ts.loadReplies }}</MkButton>
 			</div>
-			<MkNoteSub v-for="note in replies" :key="note.id" :note="note" :class="$style.reply" :detail="true" :expandAllCws="props.expandAllCws" :onDeleteCallback="removeReply" />
+			<MkNoteSub v-for="note in replies" :key="note.id" :note="note" :class="$style.reply" :detail="true" :expandAllCws="props.expandAllCws" :onDeleteCallback="removeReply"/>
 		</div>
 		<div v-else-if="tab === 'renotes'" :class="$style.tab_renotes">
 			<MkPagination :pagination="renotesPagination" :disableAutoLoad="true">
@@ -183,7 +183,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				</template>
 			</MkPagination>
 		</div>
-		<div v-if="tab === 'quotes'" :class="$style.tab_replies">
+		<div v-if="tab === 'quotes'">
 			<div v-if="!quotesLoaded" style="padding: 16px">
 				<MkButton style="margin: 0 auto;" primary rounded @click="loadQuotes">{{ i18n.ts.loadReplies }}</MkButton>
 			</div>
@@ -221,7 +221,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { computed, inject, onMounted, provide, ref, shallowRef, watch } from 'vue';
-import * as mfm from '@sharkey/sfm-js';
+import * as mfm from '@transfem-org/sfm-js';
 import * as Misskey from 'misskey-js';
 import MkNoteSub from '@/components/MkNoteSub.vue';
 import MkNoteSimple from '@/components/MkNoteSimple.vue';
@@ -237,6 +237,7 @@ import { checkWordMute } from '@/scripts/check-word-mute.js';
 import { userPage } from '@/filters/user.js';
 import { notePage } from '@/filters/note.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import * as sound from '@/scripts/sound.js';
 import { defaultStore, noteViewInterruptors } from '@/store.js';
 import { reactionPicker } from '@/scripts/reaction-picker.js';
@@ -253,9 +254,10 @@ import { checkAnimationFromMfm } from '@/scripts/check-animated-mfm.js';
 import MkRippleEffect from '@/components/MkRippleEffect.vue';
 import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
 import MkUserCardMini from '@/components/MkUserCardMini.vue';
-import MkPagination from '@/components/MkPagination.vue';
+import MkPagination, { type Paging } from '@/components/MkPagination.vue';
 import MkReactionIcon from '@/components/MkReactionIcon.vue';
 import MkButton from '@/components/MkButton.vue';
+import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js';
 
 const props = defineProps<{
 	note: Misskey.entities.Note;
@@ -272,7 +274,7 @@ if (noteViewInterruptors.length > 0) {
 		let result: Misskey.entities.Note | null = deepClone(note.value);
 		for (const interruptor of noteViewInterruptors) {
 			try {
-				result = await interruptor.handler(result);
+				result = await interruptor.handler(result!) as Misskey.entities.Note | null;
 				if (result === null) {
 					isDeleted.value = true;
 					return;
@@ -281,18 +283,18 @@ if (noteViewInterruptors.length > 0) {
 				console.error(err);
 			}
 		}
-		note.value = result;
+		note.value = result as Misskey.entities.Note;
 	});
 }
 
 const isRenote = (
 	note.value.renote != null &&
 	note.value.text == null &&
-	note.value.fileIds.length === 0 &&
+	note.value.fileIds && note.value.fileIds.length === 0 &&
 	note.value.poll == null
 );
 
-const el = shallowRef<HTMLElement>();
+const rootEl = shallowRef<HTMLElement>();
 const menuButton = shallowRef<HTMLElement>();
 const menuVersionsButton = shallowRef<HTMLElement>();
 const renoteButton = shallowRef<HTMLElement>();
@@ -302,8 +304,6 @@ const quoteButton = shallowRef<HTMLElement>();
 const clipButton = shallowRef<HTMLElement>();
 const likeButton = shallowRef<HTMLElement>();
 const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value);
-const renoteUrl = appearNote.value.renote ? appearNote.value.renote.url : null;
-const renoteUri = appearNote.value.renote ? appearNote.value.renote.uri : null;
 const isMyRenote = $i && ($i.id === note.value.userId);
 const showContent = ref(defaultStore.state.uncollapseCW);
 const isDeleted = ref(false);
@@ -312,14 +312,14 @@ const muted = ref($i ? checkWordMute(appearNote.value, $i, $i.mutedWords) : fals
 const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null);
 const translating = ref(false);
 const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null;
-const urls = parsed ? extractUrlFromMfm(parsed).filter(u => u !== renoteUrl && u !== renoteUri) : null;
+const urls = parsed ? extractUrlFromMfm(parsed).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null;
 const animated = computed(() => parsed ? checkAnimationFromMfm(parsed) : null);
 const allowAnim = ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false);
 const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance);
 const conversation = ref<Misskey.entities.Note[]>([]);
 const replies = ref<Misskey.entities.Note[]>([]);
 const quotes = ref<Misskey.entities.Note[]>([]);
-const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || appearNote.value.userId === $i.id);
+const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || (appearNote.value.visibility === 'followers' && appearNote.value.userId === $i?.id));
 const defaultLike = computed(() => defaultStore.state.like ? defaultStore.state.like : null);
 
 watch(() => props.expandAllCws, (expandAllCws) => {
@@ -327,7 +327,7 @@ watch(() => props.expandAllCws, (expandAllCws) => {
 });
 
 if ($i) {
-	os.api('notes/renotes', {
+	misskeyApi('notes/renotes', {
 		noteId: appearNote.value.id,
 		userId: $i.id,
 		limit: 1,
@@ -339,14 +339,14 @@ if ($i) {
 const keymap = {
 	'r': () => reply(true),
 	'e|a|plus': () => react(true),
-	'q': () => renoteButton.value.renote(true),
+	'q': () => renote(appearNote.value.visibility),
 	'esc': blur,
-	'm|o': () => menu(true),
+	'm|o': () => showMenu(true),
 	's': () => showContent.value !== showContent.value,
 };
 
 provide('react', (reaction: string) => {
-	os.api('notes/reactions/create', {
+	misskeyApi('notes/reactions/create', {
 		noteId: appearNote.value.id,
 		reaction: reaction,
 	});
@@ -355,7 +355,7 @@ provide('react', (reaction: string) => {
 const tab = ref('replies');
 const reactionTabType = ref<string | null>(null);
 
-const renotesPagination = computed(() => ({
+const renotesPagination = computed<Paging>(() => ({
 	endpoint: 'notes/renotes',
 	limit: 10,
 	params: {
@@ -363,7 +363,7 @@ const renotesPagination = computed(() => ({
 	},
 }));
 
-const reactionsPagination = computed(() => ({
+const reactionsPagination = computed<Paging>(() => ({
 	endpoint: 'notes/reactions',
 	limit: 10,
 	params: {
@@ -373,20 +373,20 @@ const reactionsPagination = computed(() => ({
 }));
 
 async function addReplyTo(replyNote: Misskey.entities.Note) {
-		replies.value.unshift(replyNote);
-		appearNote.value.repliesCount += 1;
+	replies.value.unshift(replyNote);
+	appearNote.value.repliesCount += 1;
 }
 
 async function removeReply(id: Misskey.entities.Note['id']) {
-		const replyIdx = replies.value.findIndex(note => note.id === id);
-		if (replyIdx >= 0) {
-			replies.value.splice(replyIdx, 1);
-			appearNote.value.repliesCount -= 1;
-		}
+	const replyIdx = replies.value.findIndex(note => note.id === id);
+	if (replyIdx >= 0) {
+		replies.value.splice(replyIdx, 1);
+		appearNote.value.repliesCount -= 1;
+	}
 }
 
 useNoteCapture({
-	rootEl: el,
+	rootEl: rootEl,
 	note: appearNote,
 	pureNote: note,
 	isDeletedRef: isDeleted,
@@ -394,7 +394,7 @@ useNoteCapture({
 });
 
 useTooltip(renoteButton, async (showing) => {
-	const renotes = await os.api('notes/renotes', {
+	const renotes = await misskeyApi('notes/renotes', {
 		noteId: appearNote.value.id,
 		limit: 11,
 	});
@@ -412,7 +412,7 @@ useTooltip(renoteButton, async (showing) => {
 });
 
 useTooltip(quoteButton, async (showing) => {
-	const renotes = await os.api('notes/renotes', {
+	const renotes = await misskeyApi('notes/renotes', {
 		noteId: appearNote.value.id,
 		limit: 11,
 		quote: true,
@@ -430,53 +430,15 @@ useTooltip(quoteButton, async (showing) => {
 	}, {}, 'closed');
 });
 
-type Visibility = 'public' | 'home' | 'followers' | 'specified';
-
-function smallerVisibility(a: Visibility | string, b: Visibility | string): Visibility {
-	if (a === 'specified' || b === 'specified') return 'specified';
-	if (a === 'followers' || b === 'followers') return 'followers';
-	if (a === 'home' || b === 'home') return 'home';
-	// if (a === 'public' || b === 'public')
-	return 'public';
-}
-
 function boostVisibility() {
-	os.popupMenu([
-		{
-			type: 'button',
-			icon: 'ph-globe-hemisphere-west ph-bold ph-lg',
-			text: i18n.ts._visibility['public'],
-			action: () => {
-				renote('public');
-			},
-		},
-		{
-			type: 'button',
-			icon: 'ph-house ph-bold ph-lg',
-			text: i18n.ts._visibility['home'],
-			action: () => {
-				renote('home');
-			},
-		},
-		{
-			type: 'button',
-			icon: 'ph-lock ph-bold ph-lg',
-			text: i18n.ts._visibility['followers'],
-			action: () => {
-				renote('followers');
-			},
-		},
-		{
-			type: 'button',
-			icon: 'ph-planet ph-bold ph-lg',
-			text: i18n.ts._timelines.local,
-			action: () => {
-				renote('local');
-			},
-		}], renoteButton.value);
+	if (!defaultStore.state.showVisibilitySelectorOnBoost) {
+		renote(defaultStore.state.visibilityOnBoost);
+	} else {
+		os.popupMenu(boostMenuItems(appearNote, renote), renoteButton.value);
+	}
 }
 
-function renote(visibility: Visibility | 'local') {
+function renote(visibility: Visibility, localOnly: boolean = false) {
 	pleaseLogin();
 	showMovedDialog();
 
@@ -489,14 +451,14 @@ function renote(visibility: Visibility | 'local') {
 			os.popup(MkRippleEffect, { x, y }, {}, 'end');
 		}
 
-		os.api('notes/create', {
+		misskeyApi('notes/create', {
 			renoteId: appearNote.value.id,
 			channelId: appearNote.value.channelId,
 		}).then(() => {
 			os.toast(i18n.ts.renoted);
 			renoted.value = true;
 		});
-	} else if (!appearNote.value.channel || appearNote.value.channel?.allowRenoteToExternal) {
+	} else if (!appearNote.value.channel || appearNote.value.channel.allowRenoteToExternal) {
 		const el = renoteButton.value as HTMLElement | null | undefined;
 		if (el) {
 			const rect = el.getBoundingClientRect();
@@ -505,17 +467,9 @@ function renote(visibility: Visibility | 'local') {
 			os.popup(MkRippleEffect, { x, y }, {}, 'end');
 		}
 
-		const configuredVisibility = defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility;
-		const localOnlySetting = defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly;
-
-		let noteVisibility = visibility === 'local' || visibility === 'specified' ? smallerVisibility(appearNote.value.visibility, configuredVisibility) : smallerVisibility(visibility, configuredVisibility);
-		if (appearNote.value.channel?.isSensitive) {
-			noteVisibility = smallerVisibility(visibility === 'local' || visibility === 'specified' ? appearNote.value.visibility : visibility, 'home');
-		}
-
-		os.api('notes/create', {
-			localOnly: visibility === 'local' ? true : localOnlySetting,
-			visibility: noteVisibility,
+		misskeyApi('notes/create', {
+			localOnly: localOnly,
+			visibility: visibility,
 			renoteId: appearNote.value.id,
 		}).then(() => {
 			os.toast(i18n.ts.renoted);
@@ -533,9 +487,9 @@ function quote() {
 			renote: appearNote.value,
 			channel: appearNote.value.channel,
 		}).then(() => {
-			os.api('notes/renotes', {
+			misskeyApi('notes/renotes', {
 				noteId: appearNote.value.id,
-				userId: $i.id,
+				userId: $i?.id,
 				limit: 1,
 				quote: true,
 			}).then((res) => {
@@ -555,9 +509,9 @@ function quote() {
 		os.post({
 			renote: appearNote.value,
 		}).then(() => {
-			os.api('notes/renotes', {
+			misskeyApi('notes/renotes', {
 				noteId: appearNote.value.id,
-				userId: $i.id,
+				userId: $i?.id,
 				limit: 1,
 				quote: true,
 			}).then((res) => {
@@ -583,7 +537,7 @@ function reply(viaKeyboard = false): void {
 		reply: appearNote.value,
 		channel: appearNote.value.channel,
 		animation: !viaKeyboard,
-	}, () => {
+	}).then(() => {
 		focus();
 	});
 }
@@ -592,7 +546,9 @@ function react(viaKeyboard = false): void {
 	pleaseLogin();
 	showMovedDialog();
 	if (appearNote.value.reactionAcceptance === 'likeOnly') {
-		os.api('notes/like', {
+		sound.playMisskeySfx('reaction');
+
+		misskeyApi('notes/like', {
 			noteId: appearNote.value.id,
 			override: defaultLike.value,
 		});
@@ -605,10 +561,10 @@ function react(viaKeyboard = false): void {
 		}
 	} else {
 		blur();
-		reactionPicker.show(reactButton.value, reaction => {
-			sound.play('reaction');
+		reactionPicker.show(reactButton.value ?? null, note.value, reaction => {
+			sound.playMisskeySfx('reaction');
 
-			os.api('notes/reactions/create', {
+			misskeyApi('notes/reactions/create', {
 				noteId: appearNote.value.id,
 				reaction: reaction,
 			});
@@ -624,7 +580,8 @@ function react(viaKeyboard = false): void {
 function like(): void {
 	pleaseLogin();
 	showMovedDialog();
-	os.api('notes/like', {
+	sound.playMisskeySfx('reaction');
+	misskeyApi('notes/like', {
 		noteId: appearNote.value.id,
 		override: defaultLike.value,
 	});
@@ -640,14 +597,14 @@ function like(): void {
 function undoReact(note): void {
 	const oldReaction = note.myReaction;
 	if (!oldReaction) return;
-	os.api('notes/reactions/delete', {
+	misskeyApi('notes/reactions/delete', {
 		noteId: note.id,
 	});
 }
 
 function undoRenote() : void {
 	if (!renoted.value) return;
-	os.api('notes/unrenote', {
+	misskeyApi('notes/unrenote', {
 		noteId: appearNote.value.id,
 	});
 	os.toast(i18n.ts.rmboost);
@@ -663,26 +620,28 @@ function undoRenote() : void {
 }
 
 function onContextmenu(ev: MouseEvent): void {
-	const isLink = (el: HTMLElement) => {
+	const isLink = (el: HTMLElement): boolean => {
 		if (el.tagName === 'A') return true;
 		if (el.parentElement) {
 			return isLink(el.parentElement);
 		}
+		return false;
 	};
-	if (isLink(ev.target)) return;
-	if (window.getSelection().toString() !== '') return;
+
+	if (ev.target && isLink(ev.target as HTMLElement)) return;
+	if (window.getSelection()?.toString() !== '') return;
 
 	if (defaultStore.state.useReactionPickerForContextMenu) {
 		ev.preventDefault();
 		react();
 	} else {
-		const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, menuButton, isDeleted });
+		const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted });
 		os.contextMenu(menu, ev).then(focus).finally(cleanup);
 	}
 }
 
-function menu(viaKeyboard = false): void {
-	const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, menuButton, isDeleted });
+function showMenu(viaKeyboard = false): void {
+	const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted });
 	os.popupMenu(menu, menuButton.value, {
 		viaKeyboard,
 	}).then(focus).finally(cleanup);
@@ -707,7 +666,7 @@ function showRenoteMenu(viaKeyboard = false): void {
 		icon: 'ph-trash ph-bold ph-lg',
 		danger: true,
 		action: () => {
-			os.api('notes/delete', {
+			misskeyApi('notes/delete', {
 				noteId: note.value.id,
 			});
 			isDeleted.value = true;
@@ -718,18 +677,18 @@ function showRenoteMenu(viaKeyboard = false): void {
 }
 
 function focus() {
-	el.value.focus();
+	rootEl.value?.focus();
 }
 
 function blur() {
-	el.value.blur();
+	rootEl.value?.blur();
 }
 
 const repliesLoaded = ref(false);
 
 function loadReplies() {
 	repliesLoaded.value = true;
-	os.api('notes/children', {
+	misskeyApi('notes/children', {
 		noteId: appearNote.value.id,
 		limit: 30,
 		showQuotes: false,
@@ -744,7 +703,7 @@ const quotesLoaded = ref(false);
 
 function loadQuotes() {
 	quotesLoaded.value = true;
-	os.api('notes/renotes', {
+	misskeyApi('notes/renotes', {
 		noteId: appearNote.value.id,
 		limit: 30,
 		quote: true,
@@ -759,7 +718,8 @@ const conversationLoaded = ref(false);
 
 function loadConversation() {
 	conversationLoaded.value = true;
-	os.api('notes/conversation', {
+	if (appearNote.value.replyId == null) return;
+	misskeyApi('notes/conversation', {
 		noteId: appearNote.value.replyId,
 	}).then(res => {
 		conversation.value = res.reverse();
@@ -871,8 +831,8 @@ function animatedMFM() {
 .noteHeaderAvatar {
 	display: block;
 	flex-shrink: 0;
-	width: 58px;
-	height: 58px;
+	width: var(--avatar);
+	height: var(--avatar);
 }
 
 .noteHeaderBody {
diff --git a/packages/frontend/src/components/MkNoteHeader.vue b/packages/frontend/src/components/MkNoteHeader.vue
index 6121db3f8f..e643590e86 100644
--- a/packages/frontend/src/components/MkNoteHeader.vue
+++ b/packages/frontend/src/components/MkNoteHeader.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<div v-if="note.user.isBot" :class="$style.isBot">bot</div>
 	<div :class="$style.username"><MkAcct :user="note.user"/></div>
 	<div v-if="note.user.badgeRoles" :class="$style.badgeRoles">
-		<img v-for="role in note.user.badgeRoles" :key="role.id" v-tooltip="role.name" :class="$style.badgeRole" :src="role.iconUrl"/>
+		<img v-for="(role, i) in note.user.badgeRoles" :key="i" v-tooltip="role.name" :class="$style.badgeRole" :src="role.iconUrl!"/>
 	</div>
 	<div :class="$style.info">
 		<div v-if="mock">
@@ -28,7 +28,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<i v-else-if="note.visibility === 'followers'" class="ph-lock ph-bold ph-lg"></i>
 			<i v-else-if="note.visibility === 'specified'" ref="specified" class="ph-envelope ph-bold ph-lg"></i>
 		</span>
-		<span v-if="note.updatedAt" ref="menuVersionsButton" style="margin-left: 0.5em; cursor: pointer;" title="Edited" @mousedown="menuVersions()"><i class="ph-pencil ph-bold ph-lg"></i></span>
+		<span v-if="note.updatedAt" ref="menuVersionsButton" style="margin-left: 0.5em; cursor: pointer;" title="Edited" @mousedown="menuVersions()"><i class="ph-pencil-simple ph-bold ph-lg"></i></span>
 		<span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ph-rocket ph-bold ph-lg"></i></span>
 		<span v-if="note.channel" style="margin-left: 0.5em;" :title="note.channel.name"><i class="ph-television ph-bold ph-lg"></i></span>
 	</div>
diff --git a/packages/frontend/src/components/MkNotePreview.vue b/packages/frontend/src/components/MkNotePreview.vue
index c517bc6800..3fcd7593ba 100644
--- a/packages/frontend/src/components/MkNotePreview.vue
+++ b/packages/frontend/src/components/MkNotePreview.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</div>
 		<div>
 			<p v-if="useCw" :class="$style.cw">
-				<Mfm v-if="cw != ''" :text="cw" :author="user" :nyaize="'respect'" :i="user" style="margin-right: 8px;"/>
+				<Mfm v-if="cw != null && cw != ''" :text="cw" :author="user" :nyaize="'respect'" :i="user" style="margin-right: 8px;"/>
 				<MkCwButton v-model="showContent" :text="text.trim()" :files="files" :poll="poll" style="margin: 4px 0;"/>
 			</p>
 			<div v-show="!useCw || showContent">
@@ -26,6 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { ref } from 'vue';
 import * as Misskey from 'misskey-js';
+import type { PollEditorModelValue } from '@/components/MkPollEditor.vue';
 import MkCwButton from '@/components/MkCwButton.vue';
 
 const showContent = ref(false);
@@ -33,12 +34,7 @@ const showContent = ref(false);
 const props = defineProps<{
 	text: string;
 	files: Misskey.entities.DriveFile[];
-	poll?: {
-		choices: string[];
-		multiple: boolean;
-		expiresAt: string | null;
-		expiredAfter: string | null;
-	};
+	poll?: PollEditorModelValue;
 	useCw: boolean;
 	cw: string | null;
 	user: Misskey.entities.User;
diff --git a/packages/frontend/src/components/MkNoteSimple.vue b/packages/frontend/src/components/MkNoteSimple.vue
index 7a6109ee0b..477cf4521a 100644
--- a/packages/frontend/src/components/MkNoteSimple.vue
+++ b/packages/frontend/src/components/MkNoteSimple.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<MkCwButton v-model="showContent" :text="note.text" :files="note.files" :poll="note.poll" @click.stop/>
 			</p>
 			<div v-show="note.cw == null || showContent">
-				<MkSubNoteContent :hideFiles="hideFiles" :class="$style.text" :note="note"/>
+				<MkSubNoteContent :hideFiles="hideFiles" :class="$style.text" :note="note" :expandAllCws="props.expandAllCws"/>
 			</div>
 		</div>
 	</div>
diff --git a/packages/frontend/src/components/MkNoteSub.vue b/packages/frontend/src/components/MkNoteSub.vue
index d96785a2d9..37811dd52e 100644
--- a/packages/frontend/src/components/MkNoteSub.vue
+++ b/packages/frontend/src/components/MkNoteSub.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<MkCwButton v-model="showContent" :text="note.text" :files="note.files" :poll="note.poll"/>
 				</p>
 				<div v-show="note.cw == null || showContent">
-					<MkSubNoteContent :class="$style.text" :note="note" :translating="translating" :translation="translation"/>
+					<MkSubNoteContent :class="$style.text" :note="note" :translating="translating" :translation="translation" :expandAllCws="props.expandAllCws"/>
 				</div>
 			</div>
 			<footer :class="$style.footer">
@@ -91,6 +91,8 @@ import MkSubNoteContent from '@/components/MkSubNoteContent.vue';
 import MkCwButton from '@/components/MkCwButton.vue';
 import { notePage } from '@/filters/note.js';
 import * as os from '@/os.js';
+import * as sound from '@/scripts/sound.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { $i } from '@/account.js';
 import { userPage } from '@/filters/user.js';
@@ -103,6 +105,7 @@ import { reactionPicker } from '@/scripts/reaction-picker.js';
 import { claimAchievement } from '@/scripts/achievements.js';
 import { getNoteMenu } from '@/scripts/get-note-menu.js';
 import { useNoteCapture } from '@/scripts/use-note-capture.js';
+import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js';
 
 const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i.id);
 
@@ -138,21 +141,21 @@ const replies = ref<Misskey.entities.Note[]>([]);
 const isRenote = (
 	props.note.renote != null &&
 	props.note.text == null &&
-	props.note.fileIds.length === 0 &&
+	props.note.fileIds && props.note.fileIds.length === 0 &&
 	props.note.poll == null
 );
 
 async function addReplyTo(replyNote: Misskey.entities.Note) {
-		replies.value.unshift(replyNote);
-		appearNote.value.repliesCount += 1;
+	replies.value.unshift(replyNote);
+	appearNote.value.repliesCount += 1;
 }
 
 async function removeReply(id: Misskey.entities.Note['id']) {
-		const replyIdx = replies.value.findIndex(note => note.id === id);
-		if (replyIdx >= 0) {
-			replies.value.splice(replyIdx, 1);
-			appearNote.value.repliesCount -= 1;
-		}
+	const replyIdx = replies.value.findIndex(note => note.id === id);
+	if (replyIdx >= 0) {
+		replies.value.splice(replyIdx, 1);
+		appearNote.value.repliesCount -= 1;
+	}
 }
 
 useNoteCapture({
@@ -165,7 +168,7 @@ useNoteCapture({
 });
 
 if ($i) {
-	os.api('notes/renotes', {
+	misskeyApi('notes/renotes', {
 		noteId: appearNote.value.id,
 		userId: $i.id,
 		limit: 1,
@@ -193,8 +196,9 @@ function reply(viaKeyboard = false): void {
 function react(viaKeyboard = false): void {
 	pleaseLogin();
 	showMovedDialog();
+	sound.playMisskeySfx('reaction');
 	if (props.note.reactionAcceptance === 'likeOnly') {
-		os.api('notes/like', {
+		misskeyApi('notes/like', {
 			noteId: props.note.id,
 			override: defaultLike.value,
 		});
@@ -207,8 +211,8 @@ function react(viaKeyboard = false): void {
 		}
 	} else {
 		blur();
-		reactionPicker.show(reactButton.value, reaction => {
-			os.api('notes/reactions/create', {
+		reactionPicker.show(reactButton.value ?? null, props.note, reaction => {
+			misskeyApi('notes/reactions/create', {
 				noteId: props.note.id,
 				reaction: reaction,
 			});
@@ -224,7 +228,8 @@ function react(viaKeyboard = false): void {
 function like(): void {
 	pleaseLogin();
 	showMovedDialog();
-	os.api('notes/like', {
+	sound.playMisskeySfx('reaction');
+	misskeyApi('notes/like', {
 		noteId: props.note.id,
 		override: defaultLike.value,
 	});
@@ -240,14 +245,14 @@ function like(): void {
 function undoReact(note): void {
 	const oldReaction = note.myReaction;
 	if (!oldReaction) return;
-	os.api('notes/reactions/delete', {
+	misskeyApi('notes/reactions/delete', {
 		noteId: note.id,
 	});
 }
 
 function undoRenote() : void {
 	if (!renoted.value) return;
-	os.api('notes/unrenote', {
+	misskeyApi('notes/unrenote', {
 		noteId: appearNote.value.id,
 	});
 	os.toast(i18n.ts.rmboost);
@@ -269,42 +274,14 @@ watch(() => props.expandAllCws, (expandAllCws) => {
 });
 
 function boostVisibility() {
-	os.popupMenu([
-		{
-			type: 'button',
-			icon: 'ph-globe-hemisphere-west ph-bold ph-lg',
-			text: i18n.ts._visibility['public'],
-			action: () => {
-				renote('public');
-			},
-		},
-		{
-			type: 'button',
-			icon: 'ph-house ph-bold ph-lg',
-			text: i18n.ts._visibility['home'],
-			action: () => {
-				renote('home');
-			},
-		},
-		{
-			type: 'button',
-			icon: 'ph-lock ph-bold ph-lg',
-			text: i18n.ts._visibility['followers'],
-			action: () => {
-				renote('followers');
-			},
-		},
-		{
-			type: 'button',
-			icon: 'ph-planet ph-bold ph-lg',
-			text: i18n.ts._timelines.local,
-			action: () => {
-				renote('local');
-			},
-		}], renoteButton.value);
+	if (!defaultStore.state.showVisibilitySelectorOnBoost) {
+		renote(defaultStore.state.visibilityOnBoost);
+	} else {
+		os.popupMenu(boostMenuItems(appearNote, renote), renoteButton.value);
+	}
 }
 
-function renote(visibility: 'public' | 'home' | 'followers' | 'specified' | 'local') {
+function renote(visibility: Visibility, localOnly: boolean = false) {
 	pleaseLogin();
 	showMovedDialog();
 
@@ -317,9 +294,9 @@ function renote(visibility: 'public' | 'home' | 'followers' | 'specified' | 'loc
 			os.popup(MkRippleEffect, { x, y }, {}, 'end');
 		}
 
-		os.api('notes/create', {
-			renoteId: props.note.id,
-			channelId: props.note.channelId,
+		misskeyApi('notes/create', {
+			renoteId: appearNote.value.id,
+			channelId: appearNote.value.channelId,
 		}).then(() => {
 			os.toast(i18n.ts.renoted);
 			renoted.value = true;
@@ -333,10 +310,10 @@ function renote(visibility: 'public' | 'home' | 'followers' | 'specified' | 'loc
 			os.popup(MkRippleEffect, { x, y }, {}, 'end');
 		}
 
-		os.api('notes/create', {
-			renoteId: props.note.id,
-			localOnly: visibility === 'local' ? true : false,
-			visibility: visibility === 'local' || visibility === 'specified' ? props.note.visibility : visibility,
+		misskeyApi('notes/create', {
+			renoteId: appearNote.value.id,
+			localOnly: localOnly,
+			visibility: visibility,
 		}).then(() => {
 			os.toast(i18n.ts.renoted);
 			renoted.value = true;
@@ -353,7 +330,7 @@ function quote() {
 			renote: appearNote.value,
 			channel: appearNote.value.channel,
 		}).then(() => {
-			os.api('notes/renotes', {
+			misskeyApi('notes/renotes', {
 				noteId: props.note.id,
 				userId: $i.id,
 				limit: 1,
@@ -375,7 +352,7 @@ function quote() {
 		os.post({
 			renote: appearNote.value,
 		}).then(() => {
-			os.api('notes/renotes', {
+			misskeyApi('notes/renotes', {
 				noteId: props.note.id,
 				userId: $i.id,
 				limit: 1,
@@ -404,7 +381,7 @@ function menu(viaKeyboard = false): void {
 }
 
 if (props.detail) {
-	os.api('notes/children', {
+	misskeyApi('notes/children', {
 		noteId: props.note.id,
 		limit: numberOfReplies.value,
 		showQuotes: false,
diff --git a/packages/frontend/src/components/MkNotes.vue b/packages/frontend/src/components/MkNotes.vue
index fc1c8a0f09..afe43d965c 100644
--- a/packages/frontend/src/components/MkNotes.vue
+++ b/packages/frontend/src/components/MkNotes.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -38,7 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				:ad="true"
 				:class="$style.notes"
 			>
-				<SkNote :key="note._featuredId_ || note._prId_ || note.id" :class="$style.note" :note="note"/>
+				<SkNote :key="note._featuredId_ || note._prId_ || note.id" :class="$style.note" :note="note" :withHardMute="true"/>
 			</MkDateSeparatedList>
 		</div>
 	</template>
diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue
index ed79ca0d86..562cc38bf3 100644
--- a/packages/frontend/src/components/MkNotification.vue
+++ b/packages/frontend/src/components/MkNotification.vue
@@ -1,15 +1,13 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
 <div :class="$style.root">
 	<div :class="$style.head">
-		<MkAvatar v-if="notification.type === 'pollEnded'" :class="$style.icon" :user="notification.note.user" link preview/>
-		<MkAvatar v-else-if="notification.type === 'note'" :class="$style.icon" :user="notification.note.user" link preview/>
-		<MkAvatar v-else-if="notification.type === 'roleAssigned'" :class="$style.icon" :user="$i" link preview/>
-		<MkAvatar v-else-if="notification.type === 'achievementEarned'" :class="$style.icon" :user="$i" link preview/>
+		<MkAvatar v-if="['pollEnded', 'note', 'edited'].includes(notification.type) && notification.note" :class="$style.icon" :user="notification.note.user" link preview/>
+		<MkAvatar v-else-if="['roleAssigned', 'achievementEarned'].includes(notification.type)" :class="$style.icon" :user="$i" link preview/>
 		<div v-else-if="notification.type === 'reaction:grouped'" :class="[$style.icon, $style.icon_reactionGroup]"><i class="ph-smiley ph-bold ph-lg" style="line-height: 1;"></i></div>
 		<div v-else-if="notification.type === 'renote:grouped'" :class="[$style.icon, $style.icon_renoteGroup]"><i class="ph-rocket-launch ph-bold ph-lg" style="line-height: 1;"></i></div>
 		<img v-else-if="notification.type === 'test'" :class="$style.icon" :src="infoImageUrl"/>
@@ -26,8 +24,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 				[$style.t_quote]: notification.type === 'quote',
 				[$style.t_pollEnded]: notification.type === 'pollEnded',
 				[$style.t_achievementEarned]: notification.type === 'achievementEarned',
+				[$style.t_roleAssigned]: notification.type === 'roleAssigned' && notification.role.iconUrl == null,
+				[$style.t_pollEnded]: notification.type === 'edited',
 			}]"
-		>
+		> <!-- we re-use t_pollEnded for "edited" instead of making an identical style -->
 			<i v-if="notification.type === 'follow'" class="ph-plus ph-bold ph-lg"></i>
 			<i v-else-if="notification.type === 'receiveFollowRequest'" class="ph-clock ph-bold ph-lg"></i>
 			<i v-else-if="notification.type === 'followRequestAccepted'" class="ph-check ph-bold ph-lg"></i>
@@ -37,12 +37,16 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<i v-else-if="notification.type === 'quote'" class="ph-quotes ph-bold ph-lg"></i>
 			<i v-else-if="notification.type === 'pollEnded'" class="ph-chart-bar-horizontal ph-bold ph-lg"></i>
 			<i v-else-if="notification.type === 'achievementEarned'" class="ph-trophy ph-bold ph-lg"></i>
-			<img v-else-if="notification.type === 'roleAssigned'" style="height: 1.3em; vertical-align: -22%;" :src="notification.role.iconUrl" alt=""/>
+			<template v-else-if="notification.type === 'roleAssigned'">
+				<img v-if="notification.role.iconUrl" style="height: 1.3em; vertical-align: -22%;" :src="notification.role.iconUrl" alt=""/>
+				<i v-else class="ph-seal-check ph-bold ph-lg"></i>
+			</template>
+			<i v-else-if="notification.type === 'edited'" class="ph-pencil ph-bold ph-lg"></i>
 			<!-- notification.reaction が null になることはまずないが、ここでoptional chaining使うと一部ブラウザで刺さるので念の為 -->
 			<MkReactionIcon
 				v-else-if="notification.type === 'reaction'"
 				:withTooltip="true"
-				:reaction="notification.reaction ? notification.reaction.replace(/^:(\w+):$/, ':$1@.:') : notification.reaction"
+				:reaction="notification.reaction.replace(/^:(\w+):$/, ':$1@.:')"
 				:noStyle="true"
 				style="width: 100%; height: 100%;"
 			/>
@@ -55,10 +59,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<span v-else-if="notification.type === 'roleAssigned'">{{ i18n.ts._notification.roleAssigned }}</span>
 			<span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span>
 			<span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span>
-			<MkA v-else-if="notification.user" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
-			<span v-else-if="notification.type === 'reaction:grouped'">{{ i18n.t('_notification.reactedBySomeUsers', { n: notification.reactions.length }) }}</span>
-			<span v-else-if="notification.type === 'renote:grouped'">{{ i18n.t('_notification.renotedBySomeUsers', { n: notification.users.length }) }}</span>
-			<span v-else>{{ notification.header }}</span>
+			<MkA v-else-if="notification.type === 'follow' || notification.type === 'mention' || notification.type === 'reply' || notification.type === 'renote' || notification.type === 'quote' || notification.type === 'reaction' || notification.type === 'receiveFollowRequest' || notification.type === 'followRequestAccepted'" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA>
+			<span v-else-if="notification.type === 'reaction:grouped'">{{ i18n.tsx._notification.reactedBySomeUsers({ n: notification.reactions.length }) }}</span>
+			<span v-else-if="notification.type === 'renote:grouped'">{{ i18n.tsx._notification.renotedBySomeUsers({ n: notification.users.length }) }}</span>
+			<span v-else-if="notification.type === 'app'">{{ notification.header }}</span>
+			<span v-else-if="notification.type === 'edited'">{{ i18n.ts._notification.edited }}</span>
 			<MkTime v-if="withTime" :time="notification.createdAt" :class="$style.headerTime"/>
 		</header>
 		<div>
@@ -97,7 +102,6 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</MkA>
 			<template v-else-if="notification.type === 'follow'">
 				<span :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}</span>
-				<div v-if="full"><MkFollowButton :user="notification.user" :full="true"/></div>
 			</template>
 			<span v-else-if="notification.type === 'followRequestAccepted'" :class="$style.text" style="opacity: 0.6;">{{ i18n.ts.followRequestAccepted }}</span>
 			<template v-else-if="notification.type === 'receiveFollowRequest'">
@@ -113,12 +117,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</span>
 
 			<div v-if="notification.type === 'reaction:grouped'">
-				<div v-for="reaction of notification.reactions" :class="$style.reactionsItem">
+				<div v-for="reaction of notification.reactions" :key="reaction.user.id + reaction.reaction" :class="$style.reactionsItem">
 					<MkAvatar :class="$style.reactionsItemAvatar" :user="reaction.user" link preview/>
 					<div :class="$style.reactionsItemReaction">
 						<MkReactionIcon
 							:withTooltip="true"
-							:reaction="reaction.reaction ? reaction.reaction.replace(/^:(\w+):$/, ':$1@.:') : reaction.reaction"
+							:reaction="reaction.reaction.replace(/^:(\w+):$/, ':$1@.:')"
 							:noStyle="true"
 							style="width: 100%; height: 100%;"
 						/>
@@ -126,10 +130,16 @@ SPDX-License-Identifier: AGPL-3.0-only
 				</div>
 			</div>
 			<div v-else-if="notification.type === 'renote:grouped'">
-				<div v-for="user of notification.users" :class="$style.reactionsItem">
+				<div v-for="user of notification.users" :key="user.id" :class="$style.reactionsItem">
 					<MkAvatar :class="$style.reactionsItemAvatar" :user="user" link preview/>
 				</div>
 			</div>
+
+			<MkA v-else-if="notification.type === 'edited'" :class="$style.text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
+				<i class="ph-quotes ph-bold ph-lg" :class="$style.quote"></i>
+				<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="true" :author="notification.note.user"/>
+				<i class="ph-quotes ph-bold ph-lg" :class="$style.quote"></i>
+			</MkA>
 		</div>
 	</div>
 </div>
@@ -139,16 +149,17 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkReactionIcon from '@/components/MkReactionIcon.vue';
-import MkFollowButton from '@/components/MkFollowButton.vue';
 import MkButton from '@/components/MkButton.vue';
 import { getNoteSummary } from '@/scripts/get-note-summary.js';
 import { notePage } from '@/filters/note.js';
 import { userPage } from '@/filters/user.js';
 import { i18n } from '@/i18n.js';
-import * as os from '@/os.js';
-import { $i } from '@/account.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
+import { signinRequired } from '@/account.js';
 import { infoImageUrl } from '@/instance.js';
 
+const $i = signinRequired();
+
 const props = withDefaults(defineProps<{
 	notification: Misskey.entities.Notification;
 	withTime?: boolean;
@@ -161,13 +172,15 @@ const props = withDefaults(defineProps<{
 const followRequestDone = ref(false);
 
 const acceptFollowRequest = () => {
+	if (props.notification.user == null) return;
 	followRequestDone.value = true;
-	os.api('following/requests/accept', { userId: props.notification.user.id });
+	misskeyApi('following/requests/accept', { userId: props.notification.user.id });
 };
 
 const rejectFollowRequest = () => {
+	if (props.notification.user == null) return;
 	followRequestDone.value = true;
-	os.api('following/requests/reject', { userId: props.notification.user.id });
+	misskeyApi('following/requests/reject', { userId: props.notification.user.id });
 };
 </script>
 
@@ -283,6 +296,12 @@ const rejectFollowRequest = () => {
 	pointer-events: none;
 }
 
+.t_roleAssigned {
+	padding: 3px;
+	background: #88a6b7;
+	pointer-events: none;
+}
+
 .tail {
 	flex: 1;
 	min-width: 0;
diff --git a/packages/frontend/src/components/MkNotificationSelectWindow.vue b/packages/frontend/src/components/MkNotificationSelectWindow.vue
index 6725776f43..71b38d99ed 100644
--- a/packages/frontend/src/components/MkNotificationSelectWindow.vue
+++ b/packages/frontend/src/components/MkNotificationSelectWindow.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<MkButton inline @click="disableAll">{{ i18n.ts.disableAll }}</MkButton>
 				<MkButton inline @click="enableAll">{{ i18n.ts.enableAll }}</MkButton>
 			</div>
-			<MkSwitch v-for="ntype in notificationTypes" :key="ntype" v-model="typesMap[ntype].value">{{ i18n.t(`_notification._types.${ntype}`) }}</MkSwitch>
+			<MkSwitch v-for="ntype in notificationTypes" :key="ntype" v-model="typesMap[ntype].value">{{ i18n.ts._notification._types[ntype] }}</MkSwitch>
 		</div>
 	</MkSpacer>
 </MkModalWindow>
diff --git a/packages/frontend/src/components/MkNotifications.vue b/packages/frontend/src/components/MkNotifications.vue
index a157820d56..68bf1bf3d8 100644
--- a/packages/frontend/src/components/MkNotifications.vue
+++ b/packages/frontend/src/components/MkNotifications.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -15,11 +15,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 		<template #default="{ items: notifications }">
 			<MkDateSeparatedList v-if="defaultStore.state.noteDesign === 'misskey'" v-slot="{ item: notification }" :class="$style.list" :items="notifications" :noGap="true">
-				<MkNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id" :note="notification.note" :withHardMute="true"/>
+				<MkNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id + ':note'" :note="notification.note" :withHardMute="true"/>
 				<XNotification v-else :key="notification.id" :notification="notification" :withTime="true" :full="true" class="_panel"/>
 			</MkDateSeparatedList>
 			<MkDateSeparatedList v-else-if="defaultStore.state.noteDesign === 'sharkey'" v-slot="{ item: notification }" :class="$style.list" :items="notifications" :noGap="true">
-				<SkNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id" :note="notification.note" :withHardMute="true"/>
+				<SkNote v-if="['reply', 'quote', 'mention'].includes(notification.type)" :key="notification.id + ':note'" :note="notification.note" :withHardMute="true"/>
 				<XNotification v-else :key="notification.id" :notification="notification" :withTime="true" :full="true" class="_panel"/>
 			</MkDateSeparatedList>
 		</template>
@@ -40,6 +40,7 @@ import { notificationTypes } from '@/const.js';
 import { infoImageUrl } from '@/instance.js';
 import { defaultStore } from '@/store.js';
 import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
+import * as Misskey from 'misskey-js';
 
 const props = defineProps<{
 	excludeTypes?: typeof notificationTypes[number][];
@@ -68,7 +69,7 @@ function onNotification(notification) {
 	}
 
 	if (!isMuted) {
-		pagingComponent.value.prepend(notification);
+		pagingComponent.value?.prepend(notification);
 	}
 }
 
@@ -80,17 +81,19 @@ function reload() {
 	});
 }
 
-let connection;
+let connection: Misskey.ChannelConnection<Misskey.Channels['main']>;
 
 onMounted(() => {
 	connection = useStream().useChannel('main');
 	connection.on('notification', onNotification);
+	connection.on('notificationFlushed', reload);
 });
 
 onActivated(() => {
 	pagingComponent.value?.reload();
 	connection = useStream().useChannel('main');
 	connection.on('notification', onNotification);
+	connection.on('notificationFlushed', reload);
 });
 
 onUnmounted(() => {
diff --git a/packages/frontend/src/components/MkNumber.vue b/packages/frontend/src/components/MkNumber.vue
index aa04ab253b..a278205b61 100644
--- a/packages/frontend/src/components/MkNumber.vue
+++ b/packages/frontend/src/components/MkNumber.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -9,7 +9,6 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { reactive, watch } from 'vue';
-import gsap from 'gsap';
 import number from '@/filters/number.js';
 
 const props = defineProps<{
@@ -20,8 +19,24 @@ const tweened = reactive({
 	number: 0,
 });
 
-watch(() => props.value, (n) => {
-	gsap.to(tweened, { duration: 1, number: Number(n) || 0 });
+watch(() => props.value, (to, from) => {
+	// requestAnimationFrameを利用して、500msでfromからtoまでを1次関数的に変化させる
+	let start: number | null = null;
+
+	function step(timestamp: number) {
+		if (start === null) {
+			start = timestamp;
+		}
+		const elapsed = timestamp - start;
+		tweened.number = (from ?? 0) + (to - (from ?? 0)) * elapsed / 500;
+		if (elapsed < 500) {
+			window.requestAnimationFrame(step);
+		} else {
+			tweened.number = to;
+		}
+	}
+
+	window.requestAnimationFrame(step);
 }, {
 	immediate: true,
 });
diff --git a/packages/frontend/src/components/MkNumberDiff.vue b/packages/frontend/src/components/MkNumberDiff.vue
index a98b6c4713..1825cc5405 100644
--- a/packages/frontend/src/components/MkNumberDiff.vue
+++ b/packages/frontend/src/components/MkNumberDiff.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkObjectView.value.vue b/packages/frontend/src/components/MkObjectView.value.vue
index aa05c43c0b..870599aa94 100644
--- a/packages/frontend/src/components/MkObjectView.value.vue
+++ b/packages/frontend/src/components/MkObjectView.value.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkObjectView.vue b/packages/frontend/src/components/MkObjectView.vue
index 30ec896ce4..bb9122c976 100644
--- a/packages/frontend/src/components/MkObjectView.vue
+++ b/packages/frontend/src/components/MkObjectView.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkOmit.vue b/packages/frontend/src/components/MkOmit.vue
index 702bb95dc7..a0bc0c628e 100644
--- a/packages/frontend/src/components/MkOmit.vue
+++ b/packages/frontend/src/components/MkOmit.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -27,7 +27,7 @@ const omitted = ref(false);
 const ignoreOmit = ref(false);
 
 const calcOmit = () => {
-	if (omitted.value || ignoreOmit.value) return;
+	if (omitted.value || ignoreOmit.value || content.value == null) return;
 	omitted.value = content.value.offsetHeight > props.maxHeight;
 };
 
@@ -37,7 +37,7 @@ const omitObserver = new ResizeObserver((entries, observer) => {
 
 onMounted(() => {
 	calcOmit();
-	omitObserver.observe(content.value);
+	omitObserver.observe(content.value as HTMLElement);
 });
 
 onUnmounted(() => {
diff --git a/packages/frontend/src/components/MkPagePreview.vue b/packages/frontend/src/components/MkPagePreview.vue
index 6c8a0e56a6..f6dc00698c 100644
--- a/packages/frontend/src/components/MkPagePreview.vue
+++ b/packages/frontend/src/components/MkPagePreview.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</header>
 		<p v-if="page.summary" :title="page.summary">{{ page.summary.length > 85 ? page.summary.slice(0, 85) + '…' : page.summary }}</p>
 		<footer>
-			<img class="icon" :src="page.user.avatarUrl"/>
+			<img v-if="page.user.avatarUrl" class="icon" :src="page.user.avatarUrl"/>
 			<p>{{ userName(page.user) }}</p>
 		</footer>
 	</article>
diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue
index 13a703e9f6..c3fa724a7a 100644
--- a/packages/frontend/src/components/MkPageWindow.vue
+++ b/packages/frontend/src/components/MkPageWindow.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -16,33 +16,33 @@ SPDX-License-Identifier: AGPL-3.0-only
 	@closed="$emit('closed')"
 >
 	<template #header>
-		<template v-if="pageMetadata?.value">
-			<i v-if="pageMetadata.value.icon" :class="pageMetadata.value.icon" style="margin-right: 0.5em;"></i>
-			<span>{{ pageMetadata.value.title }}</span>
+		<template v-if="pageMetadata">
+			<i v-if="pageMetadata.icon" :class="pageMetadata.icon" style="margin-right: 0.5em;"></i>
+			<span>{{ pageMetadata.title }}</span>
 		</template>
 	</template>
 
 	<div ref="contents" :class="$style.root" style="container-type: inline-size;">
-		<RouterView :key="reloadCount" :router="router"/>
+		<RouterView :key="reloadCount" :router="windowRouter"/>
 	</div>
 </MkWindow>
 </template>
 
 <script lang="ts" setup>
-import { ComputedRef, onMounted, onUnmounted, provide, shallowRef, ref, computed } from 'vue';
+import { computed, onMounted, onUnmounted, provide, ref, shallowRef } from 'vue';
 import RouterView from '@/components/global/RouterView.vue';
 import MkWindow from '@/components/MkWindow.vue';
 import { popout as _popout } from '@/scripts/popout.js';
 import copyToClipboard from '@/scripts/copy-to-clipboard.js';
 import { url } from '@/config.js';
-import { mainRouter, routes, page } from '@/router.js';
-import { $i } from '@/account.js';
-import { Router, useScrollPositionManager } from '@/nirax.js';
+import { useScrollPositionManager } from '@/nirax.js';
 import { i18n } from '@/i18n.js';
-import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
+import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
 import { openingWindowsCount } from '@/os.js';
 import { claimAchievement } from '@/scripts/achievements.js';
 import { getScrollContainer } from '@/scripts/scroll.js';
+import { useRouterFactory } from '@/router/supplier.js';
+import { mainRouter } from '@/router/main.js';
 
 const props = defineProps<{
 	initialPath: string;
@@ -52,17 +52,18 @@ defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-const router = new Router(routes, props.initialPath, !!$i, page(() => import('@/pages/not-found.vue')));
+const routerFactory = useRouterFactory();
+const windowRouter = routerFactory(props.initialPath);
 
-const contents = shallowRef<HTMLElement>();
-const pageMetadata = ref<null | ComputedRef<PageMetadata>>();
+const contents = shallowRef<HTMLElement | null>(null);
+const pageMetadata = ref<null | PageMetadata>(null);
 const windowEl = shallowRef<InstanceType<typeof MkWindow>>();
 const history = ref<{ path: string; key: any; }[]>([{
-	path: router.getCurrentPath(),
-	key: router.getCurrentKey(),
+	path: windowRouter.getCurrentPath(),
+	key: windowRouter.getCurrentKey(),
 }]);
 const buttonsLeft = computed(() => {
-	const buttons = [];
+	const buttons: Record<string, unknown>[] = [];
 
 	if (history.value.length > 1) {
 		buttons.push({
@@ -75,7 +76,7 @@ const buttonsLeft = computed(() => {
 });
 const buttonsRight = computed(() => {
 	const buttons = [{
-		icon: 'ph-arrow-clockwise ph-bold ph-lg',
+		icon: 'ph-arrows-clockwise ph-bold ph-lg',
 		title: i18n.ts.reload,
 		onClick: reload,
 	}, {
@@ -88,14 +89,23 @@ const buttonsRight = computed(() => {
 });
 const reloadCount = ref(0);
 
-router.addListener('push', ctx => {
+windowRouter.addListener('push', ctx => {
 	history.value.push({ path: ctx.path, key: ctx.key });
 });
 
-provide('router', router);
-provideMetadataReceiver((info) => {
+windowRouter.addListener('replace', ctx => {
+	history.value.pop();
+	history.value.push({ path: ctx.path, key: ctx.key });
+});
+
+windowRouter.init();
+
+provide('router', windowRouter);
+provideMetadataReceiver((metadataGetter) => {
+	const info = metadataGetter();
 	pageMetadata.value = info;
 });
+provideReactiveMetadata(pageMetadata);
 provide('shouldOmitHeaderTitle', true);
 provide('shouldHeaderThin', true);
 provide('forceSpacerMin', true);
@@ -113,20 +123,20 @@ const contextmenu = computed(() => ([{
 	icon: 'ph-arrow-square-out ph-bold ph-lg',
 	text: i18n.ts.openInNewTab,
 	action: () => {
-		window.open(url + router.getCurrentPath(), '_blank', 'noopener');
-		windowEl.value.close();
+		window.open(url + windowRouter.getCurrentPath(), '_blank', 'noopener');
+		windowEl.value?.close();
 	},
 }, {
 	icon: 'ph-link ph-bold ph-lg',
 	text: i18n.ts.copyLink,
 	action: () => {
-		copyToClipboard(url + router.getCurrentPath());
+		copyToClipboard(url + windowRouter.getCurrentPath());
 	},
 }]));
 
 function back() {
 	history.value.pop();
-	router.replace(history.value.at(-1)!.path, history.value.at(-1)!.key);
+	windowRouter.replace(history.value.at(-1)!.path, history.value.at(-1)!.key);
 }
 
 function reload() {
@@ -134,20 +144,20 @@ function reload() {
 }
 
 function close() {
-	windowEl.value.close();
+	windowEl.value?.close();
 }
 
 function expand() {
-	mainRouter.push(router.getCurrentPath(), 'forcePage');
-	windowEl.value.close();
+	mainRouter.push(windowRouter.getCurrentPath(), 'forcePage');
+	windowEl.value?.close();
 }
 
 function popout() {
-	_popout(router.getCurrentPath(), windowEl.value.$el);
-	windowEl.value.close();
+	_popout(windowRouter.getCurrentPath(), windowEl.value?.$el);
+	windowEl.value?.close();
 }
 
-useScrollPositionManager(() => getScrollContainer(contents.value), router);
+useScrollPositionManager(() => getScrollContainer(contents.value), windowRouter);
 
 onMounted(() => {
 	openingWindowsCount.value++;
diff --git a/packages/frontend/src/components/MkPagination.vue b/packages/frontend/src/components/MkPagination.vue
index bdd96238d3..62a85389ad 100644
--- a/packages/frontend/src/components/MkPagination.vue
+++ b/packages/frontend/src/components/MkPagination.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -46,6 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed, ComputedRef, isRef, nextTick, onActivated, onBeforeMount, onBeforeUnmount, onDeactivated, ref, shallowRef, watch } from 'vue';
 import * as Misskey from 'misskey-js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { onScrollTop, isTopVisible, getBodyScrollHeight, getScrollContainer, onScrollBottom, scrollToBottom, scroll, isBottomVisible } from '@/scripts/scroll.js';
 import { useDocumentVisibility } from '@/scripts/use-document-visibility.js';
 import { defaultStore } from '@/store.js';
@@ -203,7 +204,7 @@ async function init(): Promise<void> {
 	queue.value = new Map();
 	fetching.value = true;
 	const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
-	await os.api(props.pagination.endpoint, {
+	await misskeyApi<MisskeyEntity[]>(props.pagination.endpoint, {
 		...params,
 		limit: props.pagination.limit ?? 10,
 		allowPartial: true,
@@ -239,7 +240,7 @@ const fetchMore = async (): Promise<void> => {
 	if (!more.value || fetching.value || moreFetching.value || items.value.size === 0) return;
 	moreFetching.value = true;
 	const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
-	await os.api(props.pagination.endpoint, {
+	await misskeyApi<MisskeyEntity[]>(props.pagination.endpoint, {
 		...params,
 		limit: SECOND_FETCH_LIMIT,
 		...(props.pagination.offsetMode ? {
@@ -303,7 +304,7 @@ const fetchMoreAhead = async (): Promise<void> => {
 	if (!more.value || fetching.value || moreFetching.value || items.value.size === 0) return;
 	moreFetching.value = true;
 	const params = props.pagination.params ? isRef(props.pagination.params) ? props.pagination.params.value : props.pagination.params : {};
-	await os.api(props.pagination.endpoint, {
+	await misskeyApi<MisskeyEntity[]>(props.pagination.endpoint, {
 		...params,
 		limit: SECOND_FETCH_LIMIT,
 		...(props.pagination.offsetMode ? {
diff --git a/packages/frontend/src/components/MkPasswordDialog.vue b/packages/frontend/src/components/MkPasswordDialog.vue
index 85dd402730..3c0cdaa786 100644
--- a/packages/frontend/src/components/MkPasswordDialog.vue
+++ b/packages/frontend/src/components/MkPasswordDialog.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -41,7 +41,9 @@ import MkInput from '@/components/MkInput.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkModalWindow from '@/components/MkModalWindow.vue';
 import { i18n } from '@/i18n.js';
-import { $i } from '@/account.js';
+import { signinRequired } from '@/account.js';
+
+const $i = signinRequired();
 
 const emit = defineEmits<{
 	(ev: 'done', v: { password: string; token: string | null; }): void;
diff --git a/packages/frontend/src/components/MkPlusOneEffect.vue b/packages/frontend/src/components/MkPlusOneEffect.vue
index a741a3f7a8..6c22edb943 100644
--- a/packages/frontend/src/components/MkPlusOneEffect.vue
+++ b/packages/frontend/src/components/MkPlusOneEffect.vue
@@ -1,11 +1,11 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
 <div :class="$style.root" :style="{ zIndex, top: `${y - 64}px`, left: `${x - 64}px` }">
-	<span class="text" :class="{ up }">+1</span>
+	<span class="text" :class="{ up }">+{{ value }}</span>
 </div>
 </template>
 
@@ -16,7 +16,9 @@ import * as os from '@/os.js';
 const props = withDefaults(defineProps<{
 	x: number;
 	y: number;
+	value?: number | string;
 }>(), {
+	value: 1,
 });
 
 const emit = defineEmits<{
@@ -40,6 +42,7 @@ onMounted(() => {
 
 <style lang="scss" module>
 .root {
+	user-select: none;
 	pointer-events: none;
 	position: fixed;
 	width: 128px;
diff --git a/packages/frontend/src/components/MkPoll.vue b/packages/frontend/src/components/MkPoll.vue
index 6ee0c44658..8c0804de04 100644
--- a/packages/frontend/src/components/MkPoll.vue
+++ b/packages/frontend/src/components/MkPoll.vue
@@ -1,29 +1,28 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
 <div :class="{ [$style.done]: closed || isVoted }">
 	<ul :class="$style.choices">
-		<li v-for="(choice, i) in note.poll.choices" :key="i" :class="$style.choice" @click="vote(i)">
+		<li v-for="(choice, i) in poll.choices" :key="i" :class="$style.choice" @click="vote(i)">
 			<div :class="$style.bg" :style="{ 'width': `${showResult ? (choice.votes / total * 100) : 0}%` }"></div>
 			<span :class="$style.fg">
 				<template v-if="choice.isVoted"><i class="ph-check ph-bold ph-lg" style="margin-right: 4px; color: var(--accent);"></i></template>
 				<Mfm :text="choice.text" :plain="true"/>
-				<span v-if="showResult" style="margin-left: 4px; opacity: 0.7;">({{ i18n.t('_poll.votesCount', { n: choice.votes }) }})</span>
+				<span v-if="showResult" style="margin-left: 4px; opacity: 0.7;">({{ i18n.tsx._poll.votesCount({ n: choice.votes }) }})</span>
 			</span>
 		</li>
 	</ul>
 	<p v-if="!readOnly" :class="$style.info">
-		<span>{{ i18n.t('_poll.totalVotes', { n: total }) }}</span>
-		<span v-if="note.poll.multiple"> · </span>
-		<span v-if="note.poll.multiple" style="color: var(--accent); font-weight: bolder;">{{ i18n.ts._poll.multiple }}</span>
+		<span>{{ i18n.tsx._poll.totalVotes({ n: total }) }}</span>
+		<span v-if="poll.multiple"> · </span>
+		<span v-if="poll.multiple" style="color: var(--accent); font-weight: bolder;">{{ i18n.ts._poll.multiple }}</span>
 		<span> · </span>
 		<a v-if="!closed && !isVoted" style="color: inherit;" @click="showResult = !showResult">{{ showResult ? i18n.ts._poll.vote : i18n.ts._poll.showResult }}</a>
 		<span v-if="isVoted">{{ i18n.ts._poll.voted }}</span>
 		<span v-else-if="closed">{{ i18n.ts._poll.closed }}</span>
-		<span v-if="!isLocal"><span> · </span><a @click.stop="refresh">{{ i18n.ts.reload }}</a></span>
 		<span v-if="remaining > 0"> · {{ timer }}</span>
 	</p>
 </div>
@@ -35,36 +34,38 @@ import * as Misskey from 'misskey-js';
 import { sum } from '@/scripts/array.js';
 import { pleaseLogin } from '@/scripts/please-login.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { useInterval } from '@/scripts/use-interval.js';
 
 const props = defineProps<{
-	note: Misskey.entities.Note;
+	noteId: string;
+	poll: NonNullable<Misskey.entities.Note['poll']>;
 	readOnly?: boolean;
 }>();
 
 const remaining = ref(-1);
 
-const total = computed(() => sum(props.note.poll.choices.map(x => x.votes)));
+const total = computed(() => sum(props.poll.choices.map(x => x.votes)));
 const closed = computed(() => remaining.value === 0);
-const isLocal = computed(() => !props.note.uri);
-const isVoted = computed(() => !props.note.poll.multiple && props.note.poll.choices.some(c => c.isVoted));
-const timer = computed(() => i18n.t(
-	remaining.value >= 86400 ? '_poll.remainingDays' :
-	remaining.value >= 3600 ? '_poll.remainingHours' :
-	remaining.value >= 60 ? '_poll.remainingMinutes' : '_poll.remainingSeconds', {
-		s: Math.floor(remaining.value % 60),
-		m: Math.floor(remaining.value / 60) % 60,
-		h: Math.floor(remaining.value / 3600) % 24,
-		d: Math.floor(remaining.value / 86400),
-	}));
+const isVoted = computed(() => !props.poll.multiple && props.poll.choices.some(c => c.isVoted));
+const timer = computed(() => i18n.tsx._poll[
+	remaining.value >= 86400 ? 'remainingDays' :
+	remaining.value >= 3600 ? 'remainingHours' :
+	remaining.value >= 60 ? 'remainingMinutes' : 'remainingSeconds'
+]({
+	s: Math.floor(remaining.value % 60),
+	m: Math.floor(remaining.value / 60) % 60,
+	h: Math.floor(remaining.value / 3600) % 24,
+	d: Math.floor(remaining.value / 86400),
+}));
 
 const showResult = ref(props.readOnly || isVoted.value);
 
 // 期限付きアンケート
-if (props.note.poll.expiresAt) {
+if (props.poll.expiresAt) {
 	const tick = () => {
-		remaining.value = Math.floor(Math.max(new Date(props.note.poll.expiresAt).getTime() - Date.now(), 0) / 1000);
+		remaining.value = Math.floor(Math.max(new Date(props.poll.expiresAt!).getTime() - Date.now(), 0) / 1000);
 		if (remaining.value === 0) {
 			showResult.value = true;
 		}
@@ -80,34 +81,26 @@ const vote = async (id) => {
 	pleaseLogin();
 
 	if (props.readOnly || closed.value || isVoted.value) return;
-	if (!props.note.poll.multiple) {
+	if (!props.poll.multiple) {
 		const { canceled } = await os.confirm({
 			type: 'question',
-			text: i18n.t('voteConfirm', { choice: props.note.poll.choices[id].text }),
+			text: i18n.tsx.voteConfirm({ choice: props.poll.choices[id].text }),
 		});
 		if (canceled) return;
 	} else {
 		const { canceled } = await os.confirm({
 			type: 'question',
-			text: i18n.t('voteConfirmMulti', { choice: props.note.poll.choices[id].text }),
+			text: i18n.tsx.voteConfirmMulti({ choice: props.poll.choices[id].text }),
 		});
 		if (canceled) return;
 	}
 
-	await os.api('notes/polls/vote', {
-		noteId: props.note.id,
+	await misskeyApi('notes/polls/vote', {
+		noteId: props.noteId,
 		choice: id,
 	});
-	if (!showResult.value) showResult.value = !props.note.poll.multiple;
+	if (!showResult.value) showResult.value = !props.poll.multiple;
 };
-
-async function refresh() {
-	if (!props.note.uri) return;
-	const obj = await os.apiWithDialog('ap/show', { uri: props.note.uri });
-	if (obj.type === 'Note' && obj.object.poll) {
-		props.note.poll = obj.object.poll; // eslint-disable-line vue/no-mutating-props
-	}
-}
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/components/MkPollEditor.vue b/packages/frontend/src/components/MkPollEditor.vue
index f46779a632..98fbf25370 100644
--- a/packages/frontend/src/components/MkPollEditor.vue
+++ b/packages/frontend/src/components/MkPollEditor.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	</p>
 	<ul>
 		<li v-for="(choice, i) in choices" :key="i">
-			<MkInput class="input" small :modelValue="choice" :placeholder="i18n.t('_poll.choiceN', { n: i + 1 })" @update:modelValue="onInput(i, $event)">
+			<MkInput class="input" small :modelValue="choice" :placeholder="i18n.tsx._poll.choiceN({ n: i + 1 })" @update:modelValue="onInput(i, $event)">
 			</MkInput>
 			<button class="_button" @click="remove(i)">
 				<i class="ph-x ph-bold ph-lg"></i>
@@ -62,21 +62,18 @@ import { formatDateTimeString } from '@/scripts/format-time-string.js';
 import { addTime } from '@/scripts/time.js';
 import { i18n } from '@/i18n.js';
 
+export type PollEditorModelValue = {
+	expiresAt: number | null;
+	expiredAfter: number | null;
+	choices: string[];
+	multiple: boolean;
+};
+
 const props = defineProps<{
-	modelValue: {
-		expiresAt: string;
-		expiredAfter: number;
-		choices: string[];
-		multiple: boolean;
-	};
+	modelValue: PollEditorModelValue;
 }>();
 const emit = defineEmits<{
-	(ev: 'update:modelValue', v: {
-		expiresAt: string;
-		expiredAfter: number;
-		choices: string[];
-		multiple: boolean;
-	}): void;
+	(ev: 'update:modelValue', v: PollEditorModelValue): void;
 }>();
 
 const choices = ref(props.modelValue.choices);
@@ -89,7 +86,9 @@ const unit = ref('second');
 
 if (props.modelValue.expiresAt) {
 	expiration.value = 'at';
-	atDate.value = atTime.value = props.modelValue.expiresAt;
+	const expiresAt = new Date(props.modelValue.expiresAt);
+	atDate.value = formatDateTimeString(expiresAt, 'yyyy-MM-dd');
+	atTime.value = formatDateTimeString(expiresAt, 'HH:mm');
 } else if (typeof props.modelValue.expiredAfter === 'number') {
 	expiration.value = 'after';
 	after.value = props.modelValue.expiredAfter / 1000;
@@ -113,20 +112,21 @@ function remove(i) {
 	choices.value = choices.value.filter((_, _i) => _i !== i);
 }
 
-function get() {
+function get(): PollEditorModelValue {
 	const calcAt = () => {
 		return new Date(`${atDate.value} ${atTime.value}`).getTime();
 	};
 
 	const calcAfter = () => {
-		let base = parseInt(after.value);
+		let base = parseInt(after.value.toString());
 		switch (unit.value) {
+			// @ts-expect-error fallthrough
 			case 'day': base *= 24;
-				// fallthrough
+			// @ts-expect-error fallthrough
 			case 'hour': base *= 60;
-				// fallthrough
+			// @ts-expect-error fallthrough
 			case 'minute': base *= 60;
-				// fallthrough
+			// eslint-disable-next-line no-fallthrough
 			case 'second': return base *= 1000;
 			default: return null;
 		}
@@ -135,10 +135,8 @@ function get() {
 	return {
 		choices: choices.value,
 		multiple: multiple.value,
-		...(
-			expiration.value === 'at' ? { expiresAt: calcAt() } :
-			expiration.value === 'after' ? { expiredAfter: calcAfter() } : {}
-		),
+		expiresAt: expiration.value === 'at' ? calcAt() : null,
+		expiredAfter: expiration.value === 'after' ? calcAfter() : null,
 	};
 }
 
diff --git a/packages/frontend/src/components/MkPopupMenu.vue b/packages/frontend/src/components/MkPopupMenu.vue
index 1d92374f4f..3748f0cc64 100644
--- a/packages/frontend/src/components/MkPopupMenu.vue
+++ b/packages/frontend/src/components/MkPopupMenu.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index aa37cef6c2..d9e50fbb79 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -84,7 +84,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<button v-tooltip="i18n.ts.useCw" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: useCw }]" @click="useCw = !useCw"><i class="ph-eye-slash ph-bold ph-lg"></i></button>
 			<button v-tooltip="i18n.ts.mention" class="_button" :class="$style.footerButton" @click="insertMention"><i class="ph-at ph-bold ph-lg"></i></button>
 			<button v-tooltip="i18n.ts.hashtags" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: withHashtags }]" @click="withHashtags = !withHashtags"><i class="ph-hash ph-bold ph-lg"></i></button>
-			<button v-if="postFormActions.length > 0" v-tooltip="i18n.ts.plugin" class="_button" :class="$style.footerButton" @click="showActions"><i class="ph-plug ph-bold ph-lg"></i></button>
+			<button v-if="postFormActions.length > 0" v-tooltip="i18n.ts.plugins" class="_button" :class="$style.footerButton" @click="showActions"><i class="ph-plug ph-bold ph-lg"></i></button>
 			<button v-tooltip="i18n.ts.emoji" :class="['_button', $style.footerButton]" @click="insertEmoji"><i class="ph-smiley ph-bold ph-lg"></i></button>
 			<button v-if="showAddMfmFunction" v-tooltip="i18n.ts.addMfmFunction" :class="['_button', $style.footerButton]" @click="insertMfmFunction"><i class="ph-palette ph-bold ph-lg"></i></button>
 		</div>
@@ -101,27 +101,28 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed } from 'vue';
-import * as mfm from '@sharkey/sfm-js';
+import { inject, watch, nextTick, onMounted, defineAsyncComponent, provide, shallowRef, ref, computed, toRaw } from 'vue';
+import * as mfm from '@transfem-org/sfm-js';
 import * as Misskey from 'misskey-js';
 import insertTextAtCursor from 'insert-text-at-cursor';
 import { toASCII } from 'punycode/';
 import MkNoteSimple from '@/components/MkNoteSimple.vue';
 import MkNotePreview from '@/components/MkNotePreview.vue';
 import XPostFormAttaches from '@/components/MkPostFormAttaches.vue';
-import MkPollEditor from '@/components/MkPollEditor.vue';
+import MkPollEditor, { type PollEditorModelValue } from '@/components/MkPollEditor.vue';
 import { host, url } from '@/config.js';
 import { erase, unique } from '@/scripts/array.js';
 import { extractMentions } from '@/scripts/extract-mentions.js';
 import { formatTimeString } from '@/scripts/format-time-string.js';
 import { Autocomplete } from '@/scripts/autocomplete.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { selectFiles } from '@/scripts/select-file.js';
 import { defaultStore, notePostInterruptors, postFormActions } from '@/store.js';
 import MkInfo from '@/components/MkInfo.vue';
 import { i18n } from '@/i18n.js';
 import { instance } from '@/instance.js';
-import { $i, notesCount, incNotesCount, getAccounts, openAccountMenu as openAccountMenu_ } from '@/account.js';
+import { signinRequired, notesCount, incNotesCount, getAccounts, openAccountMenu as openAccountMenu_ } from '@/account.js';
 import { uploadFile } from '@/scripts/upload.js';
 import { deepClone } from '@/scripts/clone.js';
 import MkRippleEffect from '@/components/MkRippleEffect.vue';
@@ -130,6 +131,8 @@ import { claimAchievement } from '@/scripts/achievements.js';
 import { emojiPicker } from '@/scripts/emoji-picker.js';
 import { mfmFunctionPicker } from '@/scripts/mfm-function-picker.js';
 
+const $i = signinRequired();
+
 const modal = inject('modal');
 
 const props = withDefaults(defineProps<{
@@ -137,13 +140,13 @@ const props = withDefaults(defineProps<{
 	renote?: Misskey.entities.Note;
 	channel?: Misskey.entities.Channel; // TODO
 	mention?: Misskey.entities.User;
-	specified?: Misskey.entities.User;
+	specified?: Misskey.entities.UserDetailed;
 	initialText?: string;
 	initialCw?: string;
 	initialVisibility?: (typeof Misskey.noteVisibilities)[number];
 	initialFiles?: Misskey.entities.DriveFile[];
 	initialLocalOnly?: boolean;
-	initialVisibleUsers?: Misskey.entities.User[];
+	initialVisibleUsers?: Misskey.entities.UserDetailed[];
 	initialNote?: Misskey.entities.Note;
 	instant?: boolean;
 	fixed?: boolean;
@@ -171,18 +174,13 @@ const emit = defineEmits<{
 const textareaEl = shallowRef<HTMLTextAreaElement | null>(null);
 const cwInputEl = shallowRef<HTMLInputElement | null>(null);
 const hashtagsInputEl = shallowRef<HTMLInputElement | null>(null);
-const visibilityButton = shallowRef<HTMLElement | null>(null);
+const visibilityButton = shallowRef<HTMLElement>();
 
 const posting = ref(false);
 const posted = ref(false);
 const text = ref(props.initialText ?? '');
 const files = ref(props.initialFiles ?? []);
-const poll = ref<{
-	choices: string[];
-	multiple: boolean;
-	expiresAt: string | null;
-	expiredAfter: string | null;
-} | null>(null);
+const poll = ref<PollEditorModelValue | null>(null);
 const useCw = ref<boolean>(!!props.initialCw);
 const showPreview = ref(defaultStore.state.showPreview);
 watch(showPreview, () => defaultStore.set('showPreview', showPreview.value));
@@ -309,7 +307,7 @@ if (props.reply && props.reply.text != null) {
 	}
 }
 
-if ($i?.isSilenced && visibility.value === 'public') {
+if ($i.isSilenced && visibility.value === 'public') {
 	visibility.value = 'home';
 }
 
@@ -330,15 +328,15 @@ if (props.reply && ['home', 'followers', 'specified'].includes(props.reply.visib
 
 	if (visibility.value === 'specified') {
 		if (props.reply.visibleUserIds) {
-			os.api('users/show', {
-				userIds: props.reply.visibleUserIds.filter(uid => uid !== $i.id && uid !== props.reply.userId),
+			misskeyApi('users/show', {
+				userIds: props.reply.visibleUserIds.filter(uid => uid !== $i.id && uid !== props.reply?.userId),
 			}).then(users => {
 				users.forEach(pushVisibleUser);
 			});
 		}
 
 		if (props.reply.userId !== $i.id) {
-			os.api('users/show', { userId: props.reply.userId }).then(user => {
+			misskeyApi('users/show', { userId: props.reply.userId }).then(user => {
 				pushVisibleUser(user);
 			});
 		}
@@ -389,7 +387,7 @@ function addMissingMention() {
 
 	for (const x of extractMentions(ast)) {
 		if (!visibleUsers.value.some(u => (u.username === x.username) && (u.host === x.host))) {
-			os.api('users/show', { username: x.username, host: x.host }).then(user => {
+			misskeyApi('users/show', { username: x.username, host: x.host }).then(user => {
 				visibleUsers.value.push(user);
 			});
 		}
@@ -466,9 +464,10 @@ function setVisibility() {
 
 	os.popup(defineAsyncComponent(() => import('@/components/MkVisibilityPicker.vue')), {
 		currentVisibility: visibility.value,
-		isSilenced: $i?.isSilenced,
+		isSilenced: $i.isSilenced,
 		localOnly: localOnly.value,
 		src: visibilityButton.value,
+		...(props.reply ? { isReplyVisibilitySpecified: props.reply.visibility === 'specified' } : {}),
 	}, {
 		changeVisibility: v => {
 			visibility.value = v;
@@ -537,7 +536,7 @@ async function toggleReactionAcceptance() {
 	reactionAcceptance.value = select.result;
 }
 
-function pushVisibleUser(user) {
+function pushVisibleUser(user: Misskey.entities.UserDetailed) {
 	if (!visibleUsers.value.some(u => u.username === user.username && u.host === user.host)) {
 		visibleUsers.value.push(user);
 	}
@@ -579,10 +578,12 @@ function onCompositionEnd(ev: CompositionEvent) {
 
 async function onPaste(ev: ClipboardEvent) {
 	if (props.mock) return;
+	if (!ev.clipboardData) return;
 
-	for (const { item, i } of Array.from(ev.clipboardData.items, (item, i) => ({ item, i }))) {
+	for (const { item, i } of Array.from(ev.clipboardData.items, (data, x) => ({ item: data, i: x }))) {
 		if (item.kind === 'file') {
 			const file = item.getAsFile();
+			if (!file) continue;
 			const lio = file.name.lastIndexOf('.');
 			const ext = lio >= 0 ? file.name.slice(lio) : '';
 			const formatted = `${formatTimeString(new Date(file.lastModified), defaultStore.state.pastedFileName).replace(/{{number}}/g, `${i + 1}`)}${ext}`;
@@ -604,7 +605,7 @@ async function onPaste(ev: ClipboardEvent) {
 				return;
 			}
 
-			quoteId.value = paste.substring(url.length).match(/^\/notes\/(.+?)\/?$/)[1];
+			quoteId.value = paste.substring(url.length).match(/^\/notes\/(.+?)\/?$/)?.[1] ?? null;
 		});
 	}
 }
@@ -635,26 +636,26 @@ function onDragover(ev) {
 	}
 }
 
-function onDragenter(ev) {
+function onDragenter() {
 	draghover.value = true;
 }
 
-function onDragleave(ev) {
+function onDragleave() {
 	draghover.value = false;
 }
 
-function onDrop(ev): void {
+function onDrop(ev: DragEvent): void {
 	draghover.value = false;
 
 	// ファイルだったら
-	if (ev.dataTransfer.files.length > 0) {
+	if (ev.dataTransfer && ev.dataTransfer.files.length > 0) {
 		ev.preventDefault();
 		for (const x of Array.from(ev.dataTransfer.files)) upload(x);
 		return;
 	}
 
 	//#region ドライブのファイル
-	const driveFile = ev.dataTransfer.getData(_DATA_TRANSFER_DRIVE_FILE_);
+	const driveFile = ev.dataTransfer?.getData(_DATA_TRANSFER_DRIVE_FILE_);
 	if (driveFile != null && driveFile !== '') {
 		const file = JSON.parse(driveFile);
 		files.value.push(file);
@@ -702,11 +703,14 @@ async function post(ev?: MouseEvent) {
 	}
 
 	if (ev) {
-		const el = ev.currentTarget ?? ev.target;
-		const rect = el.getBoundingClientRect();
-		const x = rect.left + (el.offsetWidth / 2);
-		const y = rect.top + (el.offsetHeight / 2);
-		os.popup(MkRippleEffect, { x, y }, {}, 'end');
+		const el = (ev.currentTarget ?? ev.target) as HTMLElement | null;
+
+		if (el) {
+			const rect = el.getBoundingClientRect();
+			const x = rect.left + (el.offsetWidth / 2);
+			const y = rect.top + (el.offsetHeight / 2);
+			os.popup(MkRippleEffect, { x, y }, {}, 'end');
+		}
 	}
 
 	if (props.mock) return;
@@ -741,6 +745,29 @@ async function post(ev?: MouseEvent) {
 			visibility.value = 'home';
 		}
 	}
+	
+	if (defaultStore.state.warnMissingAltText) {
+		const filesData = toRaw(files.value);
+
+		const isMissingAltText = filesData.some(file => !file.comment);
+
+		if (isMissingAltText) {
+			const { canceled, result } = await os.actions({
+				type: 'warning',
+				text: i18n.ts.thisPostIsMissingAltText,
+				actions: [{
+					value: 'cancel',
+					text: i18n.ts.thisPostIsMissingAltTextCancel,
+				}, {
+					value: 'ignore',
+					text: i18n.ts.thisPostIsMissingAltTextIgnore,
+				}],
+			});
+
+			if (canceled) return;
+			if (result === 'cancel') return;	
+		}
+	}
 
 	let postData = {
 		text: text.value === '' ? null : text.value,
@@ -759,29 +786,39 @@ async function post(ev?: MouseEvent) {
 
 	if (withHashtags.value && hashtags.value && hashtags.value.trim() !== '') {
 		const hashtags_ = hashtags.value.trim().split(' ').map(x => x.startsWith('#') ? x : '#' + x).join(' ');
-		postData.text = postData.text ? `${postData.text} ${hashtags_}` : hashtags_;
+		if (!postData.text) {
+			postData.text = hashtags_;
+		} else {
+			const postTextLines = postData.text.split('\n');
+			if (postTextLines[postTextLines.length - 1].trim() === '') {
+				postTextLines[postTextLines.length - 1] += hashtags_;
+			} else {
+				postTextLines[postTextLines.length - 1] += ' ' + hashtags_;
+			}
+			postData.text = postTextLines.join('\n');
+		}
 	}
 
 	// plugin
 	if (notePostInterruptors.length > 0) {
 		for (const interruptor of notePostInterruptors) {
 			try {
-				postData = await interruptor.handler(deepClone(postData));
+				postData = await interruptor.handler(deepClone(postData)) as typeof postData;
 			} catch (err) {
 				console.error(err);
 			}
 		}
 	}
 
-	let token = undefined;
+	let token: string | undefined = undefined;
 
 	if (postAccount.value) {
 		const storedAccounts = await getAccounts();
-		token = storedAccounts.find(x => x.id === postAccount.value.id)?.token;
+		token = storedAccounts.find(x => x.id === postAccount.value?.id)?.token;
 	}
 
 	posting.value = true;
-	os.api(postData.editId ? 'notes/edit' : 'notes/create', postData, token).then(() => {
+	misskeyApi(postData.editId ? 'notes/edit' : 'notes/create', postData, token).then(() => {
 		if (props.freezeAfterPosted) {
 			posted.value = true;
 		} else {
@@ -791,7 +828,7 @@ async function post(ev?: MouseEvent) {
 			deleteDraft();
 			emit('posted');
 			if (postData.text && postData.text !== '') {
-				const hashtags_ = mfm.parse(postData.text).filter(x => x.type === 'hashtag').map(x => x.props.hashtag);
+				const hashtags_ = mfm.parse(postData.text).map(x => x.type === 'hashtag' && x.props.hashtag).filter(x => x) as string[];
 				const history = JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]') as string[];
 				miLocalStorage.setItem('hashtags', JSON.stringify(unique(hashtags_.concat(history))));
 			}
@@ -854,16 +891,17 @@ function cancel() {
 }
 
 function insertMention() {
-	os.selectUser().then(user => {
+	os.selectUser({ localOnly: localOnly.value, includeSelf: true }).then(user => {
 		insertTextAtCursor(textareaEl.value, '@' + Misskey.acct.toString(user) + ' ');
 	});
 }
 
 async function insertEmoji(ev: MouseEvent) {
 	textAreaReadOnly.value = true;
-
+	const target = ev.currentTarget ?? ev.target;
+	if (target == null) return;
 	emojiPicker.show(
-		ev.currentTarget ?? ev.target,
+		target as HTMLElement,
 		emoji => {
 			insertTextAtCursor(textareaEl.value, emoji);
 		},
@@ -875,6 +913,7 @@ async function insertEmoji(ev: MouseEvent) {
 }
 
 async function insertMfmFunction(ev: MouseEvent) {
+	if (textareaEl.value == null) return;
 	mfmFunctionPicker(
 		ev.currentTarget ?? ev.target,
 		textareaEl.value,
@@ -882,14 +921,15 @@ async function insertMfmFunction(ev: MouseEvent) {
 	);
 }
 
-function showActions(ev) {
+function showActions(ev: MouseEvent) {
 	os.popupMenu(postFormActions.map(action => ({
 		text: action.title,
 		action: () => {
 			action.handler({
 				text: text.value,
 				cw: cw.value,
-			}, (key, value) => {
+			}, (key, value: any) => {
+				if (typeof key !== 'string') return;
 				if (key === 'text') { text.value = value; }
 				if (key === 'cw') { useCw.value = value !== null; cw.value = value; }
 			});
@@ -926,9 +966,9 @@ onMounted(() => {
 	}
 
 	// TODO: detach when unmount
-	new Autocomplete(textareaEl.value, text);
-	new Autocomplete(cwInputEl.value, cw);
-	new Autocomplete(hashtagsInputEl.value, hashtags);
+	if (textareaEl.value) new Autocomplete(textareaEl.value, text);
+	if (cwInputEl.value) new Autocomplete(cwInputEl.value, cw);
+	if (hashtagsInputEl.value) new Autocomplete(hashtagsInputEl.value, hashtags);
 
 	nextTick(() => {
 		// 書きかけの投稿を復元
@@ -958,19 +998,19 @@ onMounted(() => {
 		if (props.initialNote) {
 			const init = props.initialNote;
 			text.value = init.text ? init.text : '';
-			files.value = init.files;
-			cw.value = init.cw;
+			files.value = init.files ?? [];
+			cw.value = init.cw ?? null;
 			useCw.value = init.cw != null;
 			if (init.poll) {
 				poll.value = {
 					choices: init.poll.choices.map(x => x.text),
 					multiple: init.poll.multiple,
-					expiresAt: init.poll.expiresAt ? new Date(init.poll.expiresAt).getTime().toString() : null,
-					expiredAfter: init.poll.expiredAfter ? new Date(init.poll.expiredAfter).getTime().toString() : null,
+					expiresAt: init.poll.expiresAt ? (new Date(init.poll.expiresAt)).getTime() : null,
+					expiredAfter: null,
 				};
 			}
 			visibility.value = init.visibility;
-			localOnly.value = init.localOnly;
+			localOnly.value = init.localOnly ?? false;
 			quoteId.value = init.renote ? init.renote.id : null;
 		}
 
diff --git a/packages/frontend/src/components/MkPostFormAttaches.vue b/packages/frontend/src/components/MkPostFormAttaches.vue
index b2597d090b..956dad8021 100644
--- a/packages/frontend/src/components/MkPostFormAttaches.vue
+++ b/packages/frontend/src/components/MkPostFormAttaches.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -24,6 +24,7 @@ import { defineAsyncComponent, inject } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 
 const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
@@ -55,13 +56,30 @@ function detachMedia(id: string) {
 	}
 }
 
+async function detachAndDeleteMedia(file: Misskey.entities.DriveFile) {
+	if (mock) return;
+
+	detachMedia(file.id);
+
+	const { canceled } = await os.confirm({
+		type: 'warning',
+		text: i18n.t('driveFileDeleteConfirm', { name: file.name }),
+	});
+
+	if (canceled) return;
+
+	os.apiWithDialog('drive/files/delete', {
+		fileId: file.id,
+	});
+}
+
 function toggleSensitive(file) {
 	if (mock) {
 		emit('changeSensitive', file, !file.isSensitive);
 		return;
 	}
 
-	os.api('drive/files/update', {
+	misskeyApi('drive/files/update', {
 		fileId: file.id,
 		isSensitive: !file.isSensitive,
 	}).then(() => {
@@ -75,10 +93,10 @@ async function rename(file) {
 	const { canceled, result } = await os.inputText({
 		title: i18n.ts.enterFileName,
 		default: file.name,
-		allowEmpty: false,
+		minLength: 1,
 	});
 	if (canceled) return;
-	os.api('drive/files/update', {
+	misskeyApi('drive/files/update', {
 		fileId: file.id,
 		name: result,
 	}).then(() => {
@@ -96,7 +114,7 @@ async function describe(file) {
 	}, {
 		done: caption => {
 			let comment = caption.length === 0 ? null : caption;
-			os.api('drive/files/update', {
+			misskeyApi('drive/files/update', {
 				fileId: file.id,
 				comment: comment,
 			}).then(() => {
@@ -134,9 +152,16 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent): void {
 		icon: 'ph-crop ph-bold ph-lg',
 		action: () : void => { crop(file); },
 	}] : [], {
+		type: 'divider',
+	}, {
 		text: i18n.ts.attachCancel,
 		icon: 'ph-x-circle ph-bold ph-lg',
 		action: () => { detachMedia(file.id); },
+	}, {
+		text: i18n.ts.deleteFile,
+		icon: 'ph-trash ph-bold ph-lg',
+		danger: true,
+		action: () => { detachAndDeleteMedia(file); },
 	}], ev.currentTarget ?? ev.target).then(() => menuShowing = false);
 	menuShowing = true;
 }
diff --git a/packages/frontend/src/components/MkPostFormDialog.vue b/packages/frontend/src/components/MkPostFormDialog.vue
index cd25077bfb..5260ac2a08 100644
--- a/packages/frontend/src/components/MkPostFormDialog.vue
+++ b/packages/frontend/src/components/MkPostFormDialog.vue
@@ -1,11 +1,11 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<MkModal ref="modal" :preferType="'dialog'" @click="modal.close()" @closed="onModalClosed()">
-	<MkPostForm ref="form" :class="$style.form" v-bind="props" autofocus freezeAfterPosted @posted="onPosted" @cancel="modal.close()" @esc="modal.close()"/>
+<MkModal ref="modal" :preferType="'dialog'" @click="modal?.close()" @closed="onModalClosed()">
+	<MkPostForm ref="form" :class="$style.form" v-bind="props" autofocus freezeAfterPosted @posted="onPosted" @cancel="modal?.close()" @esc="modal?.close()"/>
 </MkModal>
 </template>
 
@@ -20,13 +20,13 @@ const props = defineProps<{
 	renote?: Misskey.entities.Note;
 	channel?: any; // TODO
 	mention?: Misskey.entities.User;
-	specified?: Misskey.entities.User;
+	specified?: Misskey.entities.UserDetailed;
 	initialText?: string;
 	initialCw?: string;
-	initialVisibility?: typeof Misskey.noteVisibilities;
+	initialVisibility?: (typeof Misskey.noteVisibilities)[number];
 	initialFiles?: Misskey.entities.DriveFile[];
 	initialLocalOnly?: boolean;
-	initialVisibleUsers?: Misskey.entities.User[];
+	initialVisibleUsers?: Misskey.entities.UserDetailed[];
 	initialNote?: Misskey.entities.Note;
 	instant?: boolean;
 	fixed?: boolean;
@@ -42,7 +42,7 @@ const modal = shallowRef<InstanceType<typeof MkModal>>();
 const form = shallowRef<InstanceType<typeof MkPostForm>>();
 
 function onPosted() {
-	modal.value.close({
+	modal.value?.close({
 		useSendAnimation: true,
 	});
 }
diff --git a/packages/frontend/src/components/MkPullToRefresh.vue b/packages/frontend/src/components/MkPullToRefresh.vue
index e963697997..b1ec440e42 100644
--- a/packages/frontend/src/components/MkPullToRefresh.vue
+++ b/packages/frontend/src/components/MkPullToRefresh.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -26,6 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { onMounted, onUnmounted, ref, shallowRef } from 'vue';
 import { i18n } from '@/i18n.js';
 import { getScrollContainer } from '@/scripts/scroll.js';
+import { isHorizontalSwipeSwiping } from '@/scripts/touch.js';
 
 const SCROLL_STOP = 10;
 const MAX_PULL_DISTANCE = Infinity;
@@ -129,7 +130,7 @@ function moveEnd() {
 function moving(event: TouchEvent | PointerEvent) {
 	if (!isPullStart.value || isRefreshing.value || disabled) return;
 
-	if ((scrollEl?.scrollTop ?? 0) > (supportPointerDesktop ? SCROLL_STOP : SCROLL_STOP + pullDistance.value)) {
+	if ((scrollEl?.scrollTop ?? 0) > (supportPointerDesktop ? SCROLL_STOP : SCROLL_STOP + pullDistance.value) || isHorizontalSwipeSwiping.value) {
 		pullDistance.value = 0;
 		isPullEnd.value = false;
 		moveEnd();
@@ -148,6 +149,10 @@ function moving(event: TouchEvent | PointerEvent) {
 		if (event.cancelable) event.preventDefault();
 	}
 
+	if (pullDistance.value > SCROLL_STOP) {
+		event.stopPropagation();
+	}
+
 	isPullEnd.value = pullDistance.value >= FIRE_THRESHOLD;
 }
 
diff --git a/packages/frontend/src/components/MkPushNotificationAllowButton.vue b/packages/frontend/src/components/MkPushNotificationAllowButton.vue
index ebbd5e6cdc..5e42df4795 100644
--- a/packages/frontend/src/components/MkPushNotificationAllowButton.vue
+++ b/packages/frontend/src/components/MkPushNotificationAllowButton.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -45,7 +45,8 @@ import { ref } from 'vue';
 import { $i, getAccounts } from '@/account.js';
 import MkButton from '@/components/MkButton.vue';
 import { instance } from '@/instance.js';
-import { api, apiWithDialog, promiseDialog } from '@/os.js';
+import { apiWithDialog, promiseDialog } from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 
 defineProps<{
@@ -82,7 +83,7 @@ function subscribe() {
 			pushSubscription.value = subscription;
 
 			// Register
-			pushRegistrationInServer.value = await api('sw/register', {
+			pushRegistrationInServer.value = await misskeyApi('sw/register', {
 				endpoint: subscription.endpoint,
 				auth: encode(subscription.getKey('auth')),
 				publickey: encode(subscription.getKey('p256dh')),
@@ -125,7 +126,7 @@ async function unsubscribe() {
 }
 
 function encode(buffer: ArrayBuffer | null) {
-	return btoa(String.fromCharCode.apply(null, new Uint8Array(buffer)));
+	return btoa(String.fromCharCode.apply(null, buffer ? new Uint8Array(buffer) as any : []));
 }
 
 /**
@@ -159,7 +160,7 @@ if (navigator.serviceWorker == null) {
 			supported.value = true;
 
 			if (pushSubscription.value) {
-				const res = await api('sw/show-registration', {
+				const res = await misskeyApi('sw/show-registration', {
 					endpoint: pushSubscription.value.endpoint,
 				});
 
diff --git a/packages/frontend/src/components/MkRadio.vue b/packages/frontend/src/components/MkRadio.vue
index edb3abe5f7..0b4023f254 100644
--- a/packages/frontend/src/components/MkRadio.vue
+++ b/packages/frontend/src/components/MkRadio.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkRadios.vue b/packages/frontend/src/components/MkRadios.vue
index d9178f3362..549438f61b 100644
--- a/packages/frontend/src/components/MkRadios.vue
+++ b/packages/frontend/src/components/MkRadios.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -18,6 +18,9 @@ export default defineComponent({
 		watch(value, () => {
 			context.emit('update:modelValue', value.value);
 		});
+		watch(() => props.modelValue, v => {
+			value.value = v;
+		});
 		if (!context.slots.default) return null;
 		let options = context.slots.default();
 		const label = context.slots.label && context.slots.label();
@@ -35,7 +38,7 @@ export default defineComponent({
 			h('div', {
 				class: 'body',
 			}, options.map(option => h(MkRadio, {
-				key: option.key,
+				key: option.key as string,
 				value: option.props?.value,
 				modelValue: value.value,
 				'onUpdate:modelValue': _v => value.value = _v,
diff --git a/packages/frontend/src/components/MkRange.vue b/packages/frontend/src/components/MkRange.vue
index c1f5b6a790..46d76e2551 100644
--- a/packages/frontend/src/components/MkRange.vue
+++ b/packages/frontend/src/components/MkRange.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -43,6 +43,7 @@ const props = withDefaults(defineProps<{
 
 const emit = defineEmits<{
 	(ev: 'update:modelValue', value: number): void;
+	(ev: 'dragEnded', value: number): void;
 }>();
 
 const containerEl = shallowRef<HTMLElement>();
@@ -85,7 +86,7 @@ onMounted(() => {
 	ro = new ResizeObserver((entries, observer) => {
 		calcThumbPosition();
 	});
-	ro.observe(containerEl.value);
+	if (containerEl.value) ro.observe(containerEl.value);
 });
 
 onUnmounted(() => {
@@ -121,7 +122,7 @@ const onMousedown = (ev: MouseEvent | TouchEvent) => {
 	const onDrag = (ev: MouseEvent | TouchEvent) => {
 		ev.preventDefault();
 		const containerRect = containerEl.value!.getBoundingClientRect();
-		const pointerX = ev.touches && ev.touches.length > 0 ? ev.touches[0].clientX : ev.clientX;
+		const pointerX = 'touches' in ev && ev.touches.length > 0 ? ev.touches[0].clientX : 'clientX' in ev ? ev.clientX : 0;
 		const pointerPositionOnContainer = pointerX - (containerRect.left + (thumbWidth / 2));
 		rawValue.value = Math.min(1, Math.max(0, pointerPositionOnContainer / (containerEl.value!.offsetWidth - thumbWidth)));
 
@@ -143,6 +144,7 @@ const onMousedown = (ev: MouseEvent | TouchEvent) => {
 		// 値が変わってたら通知
 		if (beforeValue !== finalValue.value) {
 			emit('update:modelValue', finalValue.value);
+			emit('dragEnded', finalValue.value);
 		}
 	};
 
diff --git a/packages/frontend/src/components/MkReactionEffect.vue b/packages/frontend/src/components/MkReactionEffect.vue
index 75eb91e7ad..361e246e9f 100644
--- a/packages/frontend/src/components/MkReactionEffect.vue
+++ b/packages/frontend/src/components/MkReactionEffect.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkReactionIcon.vue b/packages/frontend/src/components/MkReactionIcon.vue
index fdc3bfd23c..068a2968db 100644
--- a/packages/frontend/src/components/MkReactionIcon.vue
+++ b/packages/frontend/src/components/MkReactionIcon.vue
@@ -1,10 +1,10 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<MkCustomEmoji v-if="reaction[0] === ':'" ref="elRef" :name="reaction" :normal="true" :noStyle="noStyle" :url="emojiUrl"/>
+<MkCustomEmoji v-if="reaction[0] === ':'" ref="elRef" :name="reaction" :normal="true" :noStyle="noStyle" :url="emojiUrl" :fallbackToImage="true"/>
 <MkEmoji v-else ref="elRef" :emoji="reaction" :normal="true" :noStyle="noStyle"/>
 </template>
 
diff --git a/packages/frontend/src/components/MkReactionTooltip.vue b/packages/frontend/src/components/MkReactionTooltip.vue
index 8527b45347..15409a216a 100644
--- a/packages/frontend/src/components/MkReactionTooltip.vue
+++ b/packages/frontend/src/components/MkReactionTooltip.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkReactionsViewer.details.vue b/packages/frontend/src/components/MkReactionsViewer.details.vue
index 1b0d8f74a3..8b5e6efdf3 100644
--- a/packages/frontend/src/components/MkReactionsViewer.details.vue
+++ b/packages/frontend/src/components/MkReactionsViewer.details.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -44,7 +44,7 @@ function getReactionName(reaction: string): string {
 	if (trimLocal.startsWith(':')) {
 		return trimLocal;
 	}
-	return getEmojiName(reaction) ?? reaction;
+	return getEmojiName(reaction);
 }
 </script>
 
diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
index 09e864e497..2464d21b6a 100644
--- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue
+++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -10,8 +10,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 	class="_button"
 	:class="[$style.root, { [$style.reacted]: note.myReaction == reaction, [$style.canToggle]: canToggle, [$style.small]: defaultStore.state.reactionsDisplaySize === 'small', [$style.large]: defaultStore.state.reactionsDisplaySize === 'large' }]"
 	@click="toggleReaction()"
+	@contextmenu.prevent.stop="menu"
 >
-	<MkReactionIcon :class="defaultStore.state.limitWidthOfReaction ? $style.limitWidth : ''" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substring(1, reaction.length - 1)]" @click="toggleReaction()"/>
+	<MkReactionIcon :class="defaultStore.state.limitWidthOfReaction ? $style.limitWidth : ''" :reaction="reaction" :emojiUrl="note.reactionEmojis[reaction.substring(1, reaction.length - 1)]" @click="toggleReaction()" @click.stop/>
 	<span :class="$style.count">{{ count }}</span>
 </button>
 </template>
@@ -19,9 +20,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { computed, inject, onMounted, shallowRef, watch } from 'vue';
 import * as Misskey from 'misskey-js';
+import MkCustomEmojiDetailedDialog from './MkCustomEmojiDetailedDialog.vue';
 import XDetails from '@/components/MkReactionsViewer.details.vue';
 import MkReactionIcon from '@/components/MkReactionIcon.vue';
 import * as os from '@/os.js';
+import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
 import { useTooltip } from '@/scripts/use-tooltip.js';
 import { $i } from '@/account.js';
 import MkReactionEffect from '@/components/MkReactionEffect.vue';
@@ -29,6 +32,9 @@ import { claimAchievement } from '@/scripts/achievements.js';
 import { defaultStore } from '@/store.js';
 import { i18n } from '@/i18n.js';
 import * as sound from '@/scripts/sound.js';
+import { checkReactionPermissions } from '@/scripts/check-reaction-permissions.js';
+import { customEmojisMap } from '@/custom-emojis.js';
+import { getUnicodeEmoji } from '@/scripts/emojilist.js';
 
 const props = defineProps<{
 	reaction: string;
@@ -45,13 +51,17 @@ const emit = defineEmits<{
 
 const buttonEl = shallowRef<HTMLElement>();
 
-const canToggle = computed(() => !props.reaction.match(/@\w/) && $i);
+const emojiName = computed(() => props.reaction.replace(/:/g, '').replace(/@\./, ''));
+const emoji = computed(() => customEmojisMap.get(emojiName.value) ?? getUnicodeEmoji(props.reaction));
+
+const canToggle = computed(() => {
+	return !props.reaction.match(/@\w/) && $i && emoji.value && checkReactionPermissions($i, props.note, emoji.value);
+});
+const canGetInfo = computed(() => !props.reaction.match(/@\w/) && props.reaction.includes(':'));
 
 async function toggleReaction() {
 	if (!canToggle.value) return;
 
-	// TODO: その絵文字を使う権限があるかどうか確認
-
 	const oldReaction = props.note.myReaction;
 	if (oldReaction) {
 		const confirm = await os.confirm({
@@ -61,7 +71,7 @@ async function toggleReaction() {
 		if (confirm.canceled) return;
 
 		if (oldReaction !== props.reaction) {
-			sound.play('reaction');
+			sound.playMisskeySfx('reaction');
 		}
 
 		if (mock) {
@@ -69,25 +79,25 @@ async function toggleReaction() {
 			return;
 		}
 
-		os.api('notes/reactions/delete', {
+		misskeyApi('notes/reactions/delete', {
 			noteId: props.note.id,
 		}).then(() => {
 			if (oldReaction !== props.reaction) {
-				os.api('notes/reactions/create', {
+				misskeyApi('notes/reactions/create', {
 					noteId: props.note.id,
 					reaction: props.reaction,
 				});
 			}
 		});
 	} else {
-		sound.play('reaction');
+		sound.playMisskeySfx('reaction');
 
 		if (mock) {
 			emit('reactionToggled', props.reaction, (props.count + 1));
 			return;
 		}
 
-		os.api('notes/reactions/create', {
+		misskeyApi('notes/reactions/create', {
 			noteId: props.note.id,
 			reaction: props.reaction,
 		});
@@ -97,9 +107,24 @@ async function toggleReaction() {
 	}
 }
 
+async function menu(ev) {
+	if (!canGetInfo.value) return;
+
+	os.popupMenu([{
+		text: i18n.ts.info,
+		icon: 'ph-info ph-bold ph-lg',
+		action: async () => {
+			os.popup(MkCustomEmojiDetailedDialog, {
+				emoji: await misskeyApiGet('emoji', {
+					name: props.reaction.replace(/:/g, '').replace(/@\./, ''),
+				}),
+			});
+		},
+	}], ev.currentTarget ?? ev.target);
+}
+
 function anime() {
-	if (document.hidden) return;
-	if (!defaultStore.state.animation) return;
+	if (document.hidden || !defaultStore.state.animation || buttonEl.value == null) return;
 
 	const rect = buttonEl.value.getBoundingClientRect();
 	const x = rect.left + 16;
@@ -117,7 +142,7 @@ onMounted(() => {
 
 if (!mock) {
 	useTooltip(buttonEl, async (showing) => {
-		const reactions = await os.apiGet('notes/reactions', {
+		const reactions = await misskeyApiGet('notes/reactions', {
 			noteId: props.note.id,
 			type: props.reaction,
 			limit: 10,
diff --git a/packages/frontend/src/components/MkReactionsViewer.vue b/packages/frontend/src/components/MkReactionsViewer.vue
index d2a5c431fe..3d3130cd51 100644
--- a/packages/frontend/src/components/MkReactionsViewer.vue
+++ b/packages/frontend/src/components/MkReactionsViewer.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkRemoteCaution.vue b/packages/frontend/src/components/MkRemoteCaution.vue
index e8ca9260bb..5106cdfd6a 100644
--- a/packages/frontend/src/components/MkRemoteCaution.vue
+++ b/packages/frontend/src/components/MkRemoteCaution.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkRetentionHeatmap.vue b/packages/frontend/src/components/MkRetentionHeatmap.vue
index e69aa1be80..64b573c4d3 100644
--- a/packages/frontend/src/components/MkRetentionHeatmap.vue
+++ b/packages/frontend/src/components/MkRetentionHeatmap.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { onMounted, nextTick, shallowRef, ref } from 'vue';
 import { Chart } from 'chart.js';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { defaultStore } from '@/store.js';
 import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
 import { alpha } from '@/scripts/color.js';
@@ -23,10 +23,9 @@ import { initChart } from '@/scripts/init-chart.js';
 
 initChart();
 
-const rootEl = shallowRef<HTMLDivElement>(null);
-const chartEl = shallowRef<HTMLCanvasElement>(null);
-const now = new Date();
-let chartInstance: Chart = null;
+const rootEl = shallowRef<HTMLDivElement | null>(null);
+const chartEl = shallowRef<HTMLCanvasElement | null>(null);
+let chartInstance: Chart | null = null;
 const fetching = ref(true);
 
 const { handler: externalTooltipHandler } = useChartTooltip({
@@ -34,6 +33,7 @@ const { handler: externalTooltipHandler } = useChartTooltip({
 });
 
 async function renderChart() {
+	if (rootEl.value == null) return;
 	if (chartInstance) {
 		chartInstance.destroy();
 	}
@@ -43,11 +43,16 @@ async function renderChart() {
 
 	const maxDays = wide ? 10 : narrow ? 5 : 7;
 
-	let raw = await os.api('retention', { });
+	let raw = await misskeyApi('retention', { });
 
 	raw = raw.slice(0, maxDays + 1);
 
-	const data = [];
+	const data: {
+		x: number;
+		y: string;
+		v: number;
+	}[] = [];
+
 	for (const record of raw) {
 		data.push({
 			x: 0,
@@ -83,19 +88,20 @@ async function renderChart() {
 
 	const marginEachCell = 12;
 
+	if (chartEl.value == null) return;
+
 	chartInstance = new Chart(chartEl.value, {
 		type: 'matrix',
 		data: {
 			datasets: [{
 				label: 'Active',
-				data: data,
-				pointRadius: 0,
+				data: data as any,
 				borderWidth: 0,
-				borderJoinStyle: 'round',
 				borderRadius: 3,
 				backgroundColor(c) {
-					const value = c.dataset.data[c.dataIndex].v;
-					const m = max(c.dataset.data[c.dataIndex].y);
+					const v = c.dataset.data[c.dataIndex] as unknown as typeof data[0];
+					const value = v.v;
+					const m = max(v.y);
 					if (m === 0) {
 						return alpha(color, 0);
 					} else {
@@ -103,7 +109,6 @@ async function renderChart() {
 						return alpha(color, a);
 					}
 				},
-				fill: true,
 				width(c) {
 					const a = c.chart.chartArea ?? {};
 					return (a.right - a.left) / maxDays - marginEachCell;
@@ -146,7 +151,6 @@ async function renderChart() {
 				},
 				y: {
 					type: 'time',
-					min: new Date(new Date().getFullYear(), new Date().getMonth(), new Date().getDate() - maxDays),
 					offset: true,
 					reverse: true,
 					position: 'left',
@@ -179,7 +183,7 @@ async function renderChart() {
 							return getYYYYMMDD(new Date(new Date(v.y).getTime() + (v.x * 86400000)));
 						},
 						label(context) {
-							const v = context.dataset.data[context.dataIndex];
+							const v = context.dataset.data[context.dataIndex] as unknown as typeof data[0];
 							const m = max(v.y);
 							if (m === 0) {
 								return [`Active: ${v.v} (-%)`];
diff --git a/packages/frontend/src/components/MkRetentionLineChart.vue b/packages/frontend/src/components/MkRetentionLineChart.vue
index e2682ec06b..c3daa9c9a4 100644
--- a/packages/frontend/src/components/MkRetentionLineChart.vue
+++ b/packages/frontend/src/components/MkRetentionLineChart.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -16,15 +16,15 @@ import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
 import { chartVLine } from '@/scripts/chart-vline.js';
 import { alpha } from '@/scripts/color.js';
 import { initChart } from '@/scripts/init-chart.js';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 
 initChart();
 
-const chartEl = shallowRef<HTMLCanvasElement>(null);
+const chartEl = shallowRef<HTMLCanvasElement | null>(null);
 
 const { handler: externalTooltipHandler } = useChartTooltip();
 
-let chartInstance: Chart;
+let chartInstance: Chart | null = null;
 
 const getYYYYMMDD = (date: Date) => {
 	const y = date.getFullYear().toString().padStart(2, '0');
@@ -40,13 +40,15 @@ const getDate = (ymd: string) => {
 };
 
 onMounted(async () => {
-	let raw = await os.api('retention', { });
+	let raw = await misskeyApi('retention', { });
 
 	const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
 
 	const accent = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--accent'));
 	const color = accent.toHex();
 
+	if (chartEl.value == null) return;
+
 	chartInstance = new Chart(chartEl.value, {
 		type: 'line',
 		data: {
@@ -67,7 +69,7 @@ onMounted(async () => {
 					x: (i + 1).toString(),
 					y: (v / record.users) * 100,
 					d: getYYYYMMDD(new Date(record.createdAt)),
-				}))],
+				}))] as any,
 			})),
 		},
 		options: {
@@ -109,11 +111,11 @@ onMounted(async () => {
 					enabled: false,
 					callbacks: {
 						title(context) {
-							const v = context[0].dataset.data[context[0].dataIndex];
+							const v = context[0].dataset.data[context[0].dataIndex] as unknown as { x: string, y: number, d: string };
 							return `${v.x} days later`;
 						},
 						label(context) {
-							const v = context.dataset.data[context.dataIndex];
+							const v = context.dataset.data[context.dataIndex] as unknown as { x: string, y: number, d: string };
 							const p = Math.round(v.y) + '%';
 							return `${v.d} ${p}`;
 						},
diff --git a/packages/frontend/src/components/MkRippleEffect.vue b/packages/frontend/src/components/MkRippleEffect.vue
index 860b083327..ee5bb73ebf 100644
--- a/packages/frontend/src/components/MkRippleEffect.vue
+++ b/packages/frontend/src/components/MkRippleEffect.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -77,7 +77,14 @@ const emit = defineEmits<{
 	(ev: 'end'): void;
 }>();
 
-const particles = [];
+const particles: {
+	size: number;
+	xA: number;
+	yA: number;
+	xB: number;
+	yB: number;
+	color: string;
+}[] = [];
 const origin = 64;
 const colors = ['#FF1493', '#00FFFF', '#FFE202'];
 const zIndex = os.claimZIndex('high');
diff --git a/packages/frontend/src/components/MkRolePreview.vue b/packages/frontend/src/components/MkRolePreview.vue
index bd1767155b..f0343d499b 100644
--- a/packages/frontend/src/components/MkRolePreview.vue
+++ b/packages/frontend/src/components/MkRolePreview.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkSelect.vue b/packages/frontend/src/components/MkSelect.vue
index 665ae2b813..ecac99ae45 100644
--- a/packages/frontend/src/components/MkSelect.vue
+++ b/packages/frontend/src/components/MkSelect.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -27,16 +27,17 @@ SPDX-License-Identifier: AGPL-3.0-only
 	</div>
 	<div :class="$style.caption"><slot name="caption"></slot></div>
 
-	<MkButton v-if="manualSave && changed" primary @click="updated"><i class="ph-floppy-disk ph-bold ph-lg"></i> {{ i18n.ts.save }}</MkButton>
+	<MkButton v-if="manualSave && changed" primary :class="$style.save" @click="updated"><i class="ph-floppy-disk ph-bold ph-lg"></i> {{ i18n.ts.save }}</MkButton>
 </div>
 </template>
 
 <script lang="ts" setup>
-import { onMounted, nextTick, ref, watch, computed, toRefs, VNode, useSlots } from 'vue';
+import { onMounted, nextTick, ref, watch, computed, toRefs, VNode, useSlots, VNodeChild } from 'vue';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
 import { useInterval } from '@/scripts/use-interval.js';
 import { i18n } from '@/i18n.js';
+import { MenuItem } from '@/types/menu.js';
 
 const props = defineProps<{
 	modelValue: string | null;
@@ -52,7 +53,7 @@ const props = defineProps<{
 }>();
 
 const emit = defineEmits<{
-	(ev: 'change', _ev: KeyboardEvent): void;
+	(ev: 'changeByUser', value: string | null): void;
 	(ev: 'update:modelValue', value: string | null): void;
 }>();
 
@@ -74,10 +75,9 @@ const height =
 	props.large ? 39 :
 	36;
 
-const focus = () => inputEl.value.focus();
+const focus = () => inputEl.value?.focus();
 const onInput = (ev) => {
 	changed.value = true;
-	emit('change', ev);
 };
 
 const updated = () => {
@@ -89,17 +89,19 @@ watch(modelValue, newValue => {
 	v.value = newValue;
 });
 
-watch(v, newValue => {
+watch(v, () => {
 	if (!props.manualSave) {
 		updated();
 	}
 
-	invalid.value = inputEl.value.validity.badInput;
+	invalid.value = inputEl.value?.validity.badInput ?? true;
 });
 
 // このコンポーネントが作成された時、非表示状態である場合がある
 // 非表示状態だと要素の幅などは0になってしまうので、定期的に計算する
 useInterval(() => {
+	if (inputEl.value == null) return;
+
 	if (prefixEl.value) {
 		if (prefixEl.value.offsetWidth) {
 			inputEl.value.style.paddingLeft = prefixEl.value.offsetWidth + 'px';
@@ -123,35 +125,38 @@ onMounted(() => {
 	});
 });
 
-function show(ev: MouseEvent) {
+function show() {
 	focused.value = true;
 	opening.value = true;
 
-	const menu = [];
+	const menu: MenuItem[] = [];
 	let options = slots.default!();
 
 	const pushOption = (option: VNode) => {
 		menu.push({
-			text: option.children,
-			active: computed(() => v.value === option.props.value),
+			text: option.children as string,
+			active: computed(() => v.value === option.props?.value),
 			action: () => {
-				v.value = option.props.value;
+				v.value = option.props?.value;
+				changed.value = true;
+				emit('changeByUser', v.value);
 			},
 		});
 	};
 
-	const scanOptions = (options: VNode[]) => {
+	const scanOptions = (options: VNodeChild[]) => {
 		for (const vnode of options) {
+			if (typeof vnode !== 'object' || vnode === null || Array.isArray(vnode)) continue;
 			if (vnode.type === 'optgroup') {
 				const optgroup = vnode;
 				menu.push({
 					type: 'label',
-					text: optgroup.props.label,
+					text: optgroup.props?.label,
 				});
-				scanOptions(optgroup.children);
+				if (Array.isArray(optgroup.children)) scanOptions(optgroup.children);
 			} else if (Array.isArray(vnode.children)) { // 何故かフラグメントになってくることがある
 				const fragment = vnode;
-				scanOptions(fragment.children);
+				if (Array.isArray(fragment.children)) scanOptions(fragment.children);
 			} else if (vnode.props == null) { // v-if で条件が false のときにこうなる
 				// nop?
 			} else {
@@ -164,7 +169,7 @@ function show(ev: MouseEvent) {
 	scanOptions(options);
 
 	os.popupMenu(menu, container.value, {
-		width: container.value.offsetWidth,
+		width: container.value?.offsetWidth,
 		onClosing: () => {
 			opening.value = false;
 		},
@@ -284,6 +289,10 @@ function show(ev: MouseEvent) {
 	padding-left: 6px;
 }
 
+.save {
+	margin: 8px 0 0 0;
+}
+
 .chevron {
 	transition: transform 0.1s ease-out;
 }
diff --git a/packages/frontend/src/components/MkSignin.vue b/packages/frontend/src/components/MkSignin.vue
index c884ce53ea..dc68a99593 100644
--- a/packages/frontend/src/components/MkSignin.vue
+++ b/packages/frontend/src/components/MkSignin.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -59,6 +59,7 @@ import MkInput from '@/components/MkInput.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import { host as configHost } from '@/config.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { login } from '@/account.js';
 import { i18n } from '@/i18n.js';
 
@@ -95,7 +96,7 @@ const props = defineProps({
 });
 
 function onUsernameChange(): void {
-	os.api('users/show', {
+	misskeyApi('users/show', {
 		username: username.value,
 	}).then(userResponse => {
 		user.value = userResponse;
@@ -111,6 +112,7 @@ function onLogin(res: any): Promise<void> | void {
 }
 
 async function queryKey(): Promise<void> {
+	if (credentialRequest.value == null) return;
 	queryingKey.value = true;
 	await webAuthnRequest(credentialRequest.value)
 		.catch(() => {
@@ -120,7 +122,7 @@ async function queryKey(): Promise<void> {
 			credentialRequest.value = null;
 			queryingKey.value = false;
 			signing.value = true;
-			return os.api('signin', {
+			return misskeyApi('signin', {
 				username: username.value,
 				password: password.value,
 				credential: credential.toJSON(),
@@ -142,7 +144,7 @@ function onSubmit(): void {
 	signing.value = true;
 	if (!totpLogin.value && user.value && user.value.twoFactorEnabled) {
 		if (webAuthnSupported() && user.value.securityKeys) {
-			os.api('signin', {
+			misskeyApi('signin', {
 				username: username.value,
 				password: password.value,
 			}).then(res => {
@@ -159,7 +161,7 @@ function onSubmit(): void {
 			signing.value = false;
 		}
 	} else {
-		os.api('signin', {
+		misskeyApi('signin', {
 			username: username.value,
 			password: password.value,
 			token: user.value?.twoFactorEnabled ? token.value : undefined,
diff --git a/packages/frontend/src/components/MkSigninDialog.vue b/packages/frontend/src/components/MkSigninDialog.vue
index 6f961cff05..33355bb99e 100644
--- a/packages/frontend/src/components/MkSigninDialog.vue
+++ b/packages/frontend/src/components/MkSigninDialog.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkSignupDialog.form.vue b/packages/frontend/src/components/MkSignupDialog.form.vue
index 9984b09c1a..7d03381a49 100644
--- a/packages/frontend/src/components/MkSignupDialog.form.vue
+++ b/packages/frontend/src/components/MkSignupDialog.form.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -67,6 +67,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<template #prefix><i class="ph-chalkboard-teacher ph-bold ph-lg"></i></template>
 			</MkInput>
 			<MkCaptcha v-if="instance.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" :class="$style.captcha" provider="hcaptcha" :sitekey="instance.hcaptchaSiteKey"/>
+			<MkCaptcha v-if="instance.enableMcaptcha" ref="mcaptcha" v-model="mCaptchaResponse" :class="$style.captcha" provider="mcaptcha" :sitekey="instance.mcaptchaSiteKey" :instanceUrl="instance.mcaptchaInstanceUrl"/>
 			<MkCaptcha v-if="instance.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" :class="$style.captcha" provider="recaptcha" :sitekey="instance.recaptchaSiteKey"/>
 			<MkCaptcha v-if="instance.enableTurnstile" ref="turnstile" v-model="turnstileResponse" :class="$style.captcha" provider="turnstile" :sitekey="instance.turnstileSiteKey"/>
 			<MkButton type="submit" :disabled="shouldDisableSubmitting" large gradate rounded data-cy-signup-submit style="margin: 0 auto;">
@@ -83,11 +84,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { ref, computed } from 'vue';
 import { toUnicode } from 'punycode/';
+import * as Misskey from 'misskey-js';
 import MkButton from './MkButton.vue';
 import MkInput from './MkInput.vue';
 import MkCaptcha, { type Captcha } from '@/components/MkCaptcha.vue';
 import * as config from '@/config.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { login } from '@/account.js';
 import { instance } from '@/instance.js';
 import { i18n } from '@/i18n.js';
@@ -99,7 +102,7 @@ const props = withDefaults(defineProps<{
 });
 
 const emit = defineEmits<{
-	(ev: 'signup', user: Record<string, any>): void;
+	(ev: 'signup', user: Misskey.entities.SigninResponse): void;
 	(ev: 'signupEmailPending'): void;
 	(ev: 'approvalPending'): void;
 }>();
@@ -122,6 +125,7 @@ const passwordStrength = ref<'' | 'low' | 'medium' | 'high'>('');
 const passwordRetypeState = ref<null | 'match' | 'not-match'>(null);
 const submitting = ref<boolean>(false);
 const hCaptchaResponse = ref<string | null>(null);
+const mCaptchaResponse = ref<string | null>(null);
 const reCaptchaResponse = ref<string | null>(null);
 const turnstileResponse = ref<string | null>(null);
 const usernameAbortController = ref<null | AbortController>(null);
@@ -130,6 +134,7 @@ const emailAbortController = ref<null | AbortController>(null);
 const shouldDisableSubmitting = computed((): boolean => {
 	return submitting.value ||
 		instance.enableHcaptcha && !hCaptchaResponse.value ||
+		instance.enableMcaptcha && !mCaptchaResponse.value ||
 		instance.enableRecaptcha && !reCaptchaResponse.value ||
 		instance.enableTurnstile && !turnstileResponse.value ||
 		instance.emailRequiredForSignup && emailState.value !== 'ok' ||
@@ -186,7 +191,7 @@ function onChangeUsername(): void {
 	usernameState.value = 'wait';
 	usernameAbortController.value = new AbortController();
 
-	os.api('username/available', {
+	misskeyApi('username/available', {
 		username: username.value,
 	}, undefined, usernameAbortController.value.signal).then(result => {
 		usernameState.value = result.available ? 'ok' : 'unavailable';
@@ -209,7 +214,7 @@ function onChangeEmail(): void {
 	emailState.value = 'wait';
 	emailAbortController.value = new AbortController();
 
-	os.api('email-address/available', {
+	misskeyApi('email-address/available', {
 		emailAddress: email.value,
 	}, undefined, emailAbortController.value.signal).then(result => {
 		emailState.value = result.available ? 'ok' :
@@ -251,20 +256,22 @@ async function onSubmit(): Promise<void> {
 	submitting.value = true;
 
 	try {
-		await os.api('signup', {
+		await misskeyApi('signup', {
 			username: username.value,
 			password: password.value,
 			emailAddress: email.value,
 			invitationCode: invitationCode.value,
 			reason: reason.value,
 			'hcaptcha-response': hCaptchaResponse.value,
+			'm-captcha-response': mCaptchaResponse.value,
 			'g-recaptcha-response': reCaptchaResponse.value,
+			'turnstile-response': turnstileResponse.value,
 		});
 		if (instance.emailRequiredForSignup) {
 			os.alert({
 				type: 'success',
 				title: i18n.ts._signup.almostThere,
-				text: i18n.t('_signup.emailSent', { email: email.value }),
+				text: i18n.tsx._signup.emailSent({ email: email.value }),
 			});
 			emit('signupEmailPending');
 		} else if (instance.approvalRequiredForSignup) {
@@ -275,7 +282,7 @@ async function onSubmit(): Promise<void> {
 			});
 			emit('approvalPending');
 		} else {
-			const res = await os.api('signin', {
+			const res = await misskeyApi('signin', {
 				username: username.value,
 				password: password.value,
 			});
diff --git a/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts b/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts
index ab26df6342..fcd1ffde3e 100644
--- a/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts
+++ b/packages/frontend/src/components/MkSignupDialog.rules.stories.impl.ts
@@ -1,11 +1,10 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { expect } from '@storybook/jest';
-import { userEvent, waitFor, within } from '@storybook/testing-library';
+import { expect, userEvent, waitFor, within } from '@storybook/test';
 import { StoryObj } from '@storybook/vue3';
 import { onBeforeUnmount } from 'vue';
 import MkSignupServerRules from './MkSignupDialog.rules.vue';
diff --git a/packages/frontend/src/components/MkSignupDialog.rules.vue b/packages/frontend/src/components/MkSignupDialog.rules.vue
index bc4fec305b..18a9eeda23 100644
--- a/packages/frontend/src/components/MkSignupDialog.rules.vue
+++ b/packages/frontend/src/components/MkSignupDialog.rules.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -24,7 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<template #suffix><i v-if="agreeServerRules" class="ph-check ph-bold ph-lg" style="color: var(--success)"></i></template>
 
 				<ol class="_gaps_s" :class="$style.rules">
-					<li v-for="item in instance.serverRules" :class="$style.rule"><div :class="$style.ruleText" v-html="item"></div></li>
+					<li v-for="item in instance.serverRules" :class="$style.rule"><div :class="$style.ruleText" v-html="sanitizeHtml(item)"></div></li>
 				</ol>
 
 				<MkSwitch :modelValue="agreeServerRules" style="margin-top: 16px;" @update:modelValue="updateAgreeServerRules">{{ i18n.ts.agree }}</MkSwitch>
@@ -34,8 +34,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<template #label>{{ tosPrivacyPolicyLabel }}</template>
 				<template #suffix><i v-if="agreeTosAndPrivacyPolicy" class="ph-check ph-bold ph-lg" style="color: var(--success)"></i></template>
 				<div class="_gaps_s">
-					<div v-if="availableTos"><a :href="instance.tosUrl" class="_link" target="_blank">{{ i18n.ts.termsOfService }} <i class="ph-arrow-square-out ph-bold ph-lg"></i></a></div>
-					<div v-if="availablePrivacyPolicy"><a :href="instance.privacyPolicyUrl" class="_link" target="_blank">{{ i18n.ts.privacyPolicy }} <i class="ph-arrow-square-out ph-bold ph-lg"></i></a></div>
+					<div v-if="availableTos"><a :href="instance.tosUrl ?? undefined" class="_link" target="_blank">{{ i18n.ts.termsOfService }} <i class="ph-arrow-square-out ph-bold ph-lg"></i></a></div>
+					<div v-if="availablePrivacyPolicy"><a :href="instance.privacyPolicyUrl ?? undefined" class="_link" target="_blank">{{ i18n.ts.privacyPolicy }} <i class="ph-arrow-square-out ph-bold ph-lg"></i></a></div>
 				</div>
 
 				<MkSwitch :modelValue="agreeTosAndPrivacyPolicy" style="margin-top: 16px;" @update:modelValue="updateAgreeTosAndPrivacyPolicy">{{ i18n.ts.agree }}</MkSwitch>
@@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<template #label>{{ i18n.ts.basicNotesBeforeCreateAccount }}</template>
 				<template #suffix><i v-if="agreeNote" class="ph-check ph-bold ph-lg" style="color: var(--success)"></i></template>
 
-				<a href="https://git.joinsharkey.org/Sharkey/JoinSharkey/src/branch/main/IMPORTANT_NOTES.md" class="_link" target="_blank">{{ i18n.ts.basicNotesBeforeCreateAccount }} <i class="ph-arrow-square-out ph-bold ph-lg"></i></a>
+				<a href="https://activitypub.software/TransFem-org/Sharkey/-/blob/stable/IMPORTANT_NOTES.md" class="_link" target="_blank">{{ i18n.ts.basicNotesBeforeCreateAccount }} <i class="ph-arrow-square-out ph-bold ph-lg"></i></a>
 
 				<MkSwitch :modelValue="agreeNote" style="margin-top: 16px;" data-cy-signup-rules-notes-agree @update:modelValue="updateAgreeNote">{{ i18n.ts.agree }}</MkSwitch>
 			</MkFolder>
@@ -65,6 +65,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed, ref } from 'vue';
 import { instance } from '@/instance.js';
 import { i18n } from '@/i18n.js';
+import sanitizeHtml from 'sanitize-html';
 import MkButton from '@/components/MkButton.vue';
 import MkFolder from '@/components/MkFolder.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
@@ -105,7 +106,7 @@ async function updateAgreeServerRules(v: boolean) {
 		const confirm = await os.confirm({
 			type: 'question',
 			title: i18n.ts.doYouAgree,
-			text: i18n.t('iHaveReadXCarefullyAndAgree', { x: i18n.ts.serverRules }),
+			text: i18n.tsx.iHaveReadXCarefullyAndAgree({ x: i18n.ts.serverRules }),
 		});
 		if (confirm.canceled) return;
 		agreeServerRules.value = true;
@@ -119,7 +120,7 @@ async function updateAgreeTosAndPrivacyPolicy(v: boolean) {
 		const confirm = await os.confirm({
 			type: 'question',
 			title: i18n.ts.doYouAgree,
-			text: i18n.t('iHaveReadXCarefullyAndAgree', {
+			text: i18n.tsx.iHaveReadXCarefullyAndAgree({
 				x: tosPrivacyPolicyLabel.value,
 			}),
 		});
@@ -135,7 +136,7 @@ async function updateAgreeNote(v: boolean) {
 		const confirm = await os.confirm({
 			type: 'question',
 			title: i18n.ts.doYouAgree,
-			text: i18n.t('iHaveReadXCarefullyAndAgree', { x: i18n.ts.basicNotesBeforeCreateAccount }),
+			text: i18n.tsx.iHaveReadXCarefullyAndAgree({ x: i18n.ts.basicNotesBeforeCreateAccount }),
 		});
 		if (confirm.canceled) return;
 		agreeNote.value = true;
diff --git a/packages/frontend/src/components/MkSignupDialog.vue b/packages/frontend/src/components/MkSignupDialog.vue
index 6efdced69f..91e7d5dd53 100644
--- a/packages/frontend/src/components/MkSignupDialog.vue
+++ b/packages/frontend/src/components/MkSignupDialog.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	ref="dialog"
 	:width="500"
 	:height="600"
-	@close="dialog.close()"
+	@close="dialog?.close()"
 	@closed="$emit('closed')"
 >
 	<template #header>{{ i18n.ts.signup }}</template>
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			:leaveToClass="$style.transition_x_leaveTo"
 		>
 			<template v-if="!isAcceptedServerRule">
-				<XServerRules @done="isAcceptedServerRule = true" @cancel="dialog.close()"/>
+				<XServerRules @done="isAcceptedServerRule = true" @cancel="dialog?.close()"/>
 			</template>
 			<template v-else>
 				<XSignup :autoSet="autoSet" @signup="onSignup" @signupEmailPending="onSignupEmailPending" @approvalPending="onApprovalPending"/>
@@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { shallowRef, ref } from 'vue';
-
+import * as Misskey from 'misskey-js';
 import XSignup from '@/components/MkSignupDialog.form.vue';
 import XServerRules from '@/components/MkSignupDialog.rules.vue';
 import MkModalWindow from '@/components/MkModalWindow.vue';
@@ -47,7 +47,7 @@ const props = withDefaults(defineProps<{
 });
 
 const emit = defineEmits<{
-	(ev: 'done'): void;
+	(ev: 'done', res: Misskey.entities.SigninResponse): void;
 	(ev: 'closed'): void;
 }>();
 
@@ -55,13 +55,13 @@ const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
 
 const isAcceptedServerRule = ref(false);
 
-function onSignup(res) {
+function onSignup(res: Misskey.entities.SigninResponse) {
 	emit('done', res);
-	dialog.value.close();
+	dialog.value?.close();
 }
 
 function onSignupEmailPending() {
-	dialog.value.close();
+	dialog.value?.close();
 }
 
 function onApprovalPending() {
diff --git a/packages/frontend/src/components/MkSourceCodeAvailablePopup.vue b/packages/frontend/src/components/MkSourceCodeAvailablePopup.vue
new file mode 100644
index 0000000000..7b936b656c
--- /dev/null
+++ b/packages/frontend/src/components/MkSourceCodeAvailablePopup.vue
@@ -0,0 +1,113 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div class="_panel _shadow" :class="$style.root">
+	<div :class="$style.icon">
+		<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-open-source" width="40" height="40" viewBox="0 0 24 24" stroke-width="1" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+			<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
+			<path d="M12 3a9 9 0 0 1 3.618 17.243l-2.193 -5.602a3 3 0 1 0 -2.849 0l-2.193 5.603a9 9 0 0 1 3.617 -17.244z"/>
+		</svg>
+	</div>
+	<div :class="$style.main">
+		<div :class="$style.title">
+			<I18n :src="i18n.ts.aboutX" tag="span">
+				<template #x>
+					{{ instance.name ?? host }}
+				</template>
+			</I18n>
+		</div>
+		<div :class="$style.text">
+			<I18n :src="i18n.ts._aboutMisskey.thisIsModifiedVersion" tag="span">
+				<template #name>
+					{{ instance.name ?? host }}
+				</template>
+			</I18n>
+			<I18n :src="i18n.ts.correspondingSourceIsAvailable" tag="span">
+				<template #anchor>
+					<MkA to="/about-sharkey" class="_link">{{ i18n.ts.aboutMisskey }}</MkA>
+				</template>
+			</I18n>
+		</div>
+		<div class="_buttons">
+			<MkButton @click="close">{{ i18n.ts.gotIt }}</MkButton>
+		</div>
+	</div>
+	<button class="_button" :class="$style.close" @click="close"><i class="ph-x ph-bold ph-lg"></i></button>
+</div>
+</template>
+
+<script lang="ts" setup>
+import MkButton from '@/components/MkButton.vue';
+import { host } from '@/config.js';
+import { i18n } from '@/i18n.js';
+import { instance } from '@/instance.js';
+import { miLocalStorage } from '@/local-storage.js';
+import * as os from '@/os.js';
+
+const emit = defineEmits<{
+	(ev: 'closed'): void;
+}>();
+
+const zIndex = os.claimZIndex('low');
+
+function close() {
+	miLocalStorage.setItem('modifiedVersionMustProminentlyOfferInAgplV3Section13Read', 'true');
+	emit('closed');
+}
+</script>
+
+<style lang="scss" module>
+.root {
+	position: fixed;
+	z-index: v-bind(zIndex);
+	bottom: var(--margin);
+	left: 0;
+	right: 0;
+	margin: auto;
+	box-sizing: border-box;
+	width: calc(100% - (var(--margin) * 2));
+	max-width: 500px;
+	display: flex;
+	backdrop-filter: var(--blur, blur(15px));
+}
+
+.icon {
+	text-align: center;
+	padding-top: 25px;
+	width: 100px;
+	color: var(--accent);
+}
+@media (max-width: 500px) {
+	.icon {
+		width: 80px;
+	}
+}
+@media (max-width: 450px) {
+	.icon {
+		width: 70px;
+	}
+}
+
+.main {
+	padding: 25px 25px 25px 0;
+	flex: 1;
+}
+
+.close {
+	position: absolute;
+	top: 8px;
+	right: 8px;
+	padding: 8px;
+}
+
+.title {
+	font-weight: bold;
+}
+
+.text {
+	margin: 0.7em 0 1em 0;
+}
+</style>
diff --git a/packages/frontend/src/components/MkSparkle.vue b/packages/frontend/src/components/MkSparkle.vue
index 269825e25e..8491ce2f84 100644
--- a/packages/frontend/src/components/MkSparkle.vue
+++ b/packages/frontend/src/components/MkSparkle.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -89,10 +89,11 @@ let ro: ResizeObserver | undefined;
 
 onMounted(() => {
 	ro = new ResizeObserver((entries, observer) => {
-		width.value = el.value?.offsetWidth + 64;
-		height.value = el.value?.offsetHeight + 64;
+		if (el.value == null) return;
+		width.value = el.value.offsetWidth + 64;
+		height.value = el.value.offsetHeight + 64;
 	});
-	ro.observe(el.value);
+	if (el.value) ro.observe(el.value);
 	const add = () => {
 		if (stop) return;
 		const x = (Math.random() * (width.value - 64));
diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue
index c071fb938a..7e63bbe82d 100644
--- a/packages/frontend/src/components/MkSubNoteContent.vue
+++ b/packages/frontend/src/components/MkSubNoteContent.vue
@@ -1,13 +1,13 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
 <div :class="[$style.root, { [$style.collapsed]: collapsed }]">
-	<div :class="{ [$style.clickToOpen]: defaultStore.state.clickToOpen }" @click="defaultStore.state.clickToOpen ? noteclick(note.id) : undefined">
+	<div :class="{ [$style.clickToOpen]: defaultStore.state.clickToOpen }" @click.stop="defaultStore.state.clickToOpen ? noteclick(note.id) : undefined">
 		<span v-if="note.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
-		<span v-if="note.deletedAt" style="opacity: 0.5">({{ i18n.ts.deleted }})</span>
+		<span v-if="note.deletedAt" style="opacity: 0.5">({{ i18n.ts.deletedNote }})</span>
 		<MkA v-if="note.replyId" :class="$style.reply" :to="`/notes/${note.replyId}`" @click.stop><i class="ph-arrow-bend-left-up ph-bold ph-lg"></i></MkA>
 		<Mfm v-if="note.text" :text="note.text" :author="note.user" :nyaize="'respect'" :isAnim="allowAnim" :emojiUrls="note.emojis"/>
 		<MkButton v-if="!allowAnim && animated && !hideFiles" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-play ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.play }}</MkButton>
@@ -15,40 +15,40 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<div v-if="note.text && translating || note.text && translation" :class="$style.translation">
 			<MkLoading v-if="translating" mini/>
 			<div v-else>
-				<b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b>
+				<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
 				<Mfm :text="translation.text" :author="note.user" :nyaize="'respect'" :emojiUrls="note.emojis"/>
 			</div>
 		</div>
 		<MkA v-if="note.renoteId" :class="$style.rp" :to="`/notes/${note.renoteId}`" @click.stop>RN: ...</MkA>
 	</div>
-	<details v-if="note.files.length > 0" :open="!defaultStore.state.collapseFiles && !hideFiles">
-		<summary>({{ i18n.t('withNFiles', { n: note.files.length }) }})</summary>
+	<details v-if="note.files && note.files.length > 0" :open="!defaultStore.state.collapseFiles && !hideFiles">
+		<summary>({{ i18n.tsx.withNFiles({ n: note.files.length }) }})</summary>
 		<MkMediaList :mediaList="note.files"/>
 	</details>
 	<details v-if="note.poll">
 		<summary>{{ i18n.ts.poll }}</summary>
-		<MkPoll :note="note"/>
+		<MkPoll :noteId="note.id" :poll="note.poll"/>
 	</details>
-	<button v-if="isLong && collapsed" :class="$style.fade" class="_button" @click="collapsed = false">
+	<button v-if="isLong && collapsed" :class="$style.fade" class="_button" @click.stop="collapsed = false">
 		<span :class="$style.fadeLabel">{{ i18n.ts.showMore }}</span>
 	</button>
-	<button v-else-if="isLong && !collapsed" :class="$style.showLess" class="_button" @click="collapsed = true">
+	<button v-else-if="isLong && !collapsed" :class="$style.showLess" class="_button" @click.stop="collapsed = true">
 		<span :class="$style.showLessLabel">{{ i18n.ts.showLess }}</span>
 	</button>
 </div>
 </template>
 
 <script lang="ts" setup>
-import { ref, computed } from 'vue';
+import { ref, computed, watch } from 'vue';
 import * as Misskey from 'misskey-js';
-import * as mfm from '@sharkey/sfm-js';
+import * as mfm from '@transfem-org/sfm-js';
 import MkMediaList from '@/components/MkMediaList.vue';
 import MkPoll from '@/components/MkPoll.vue';
 import MkButton from '@/components/MkButton.vue';
 import { i18n } from '@/i18n.js';
 import { shouldCollapsed } from '@/scripts/collapsed.js';
 import { defaultStore } from '@/store.js';
-import { useRouter } from '@/router.js';
+import { useRouter } from '@/router/supplier.js';
 import * as os from '@/os.js';
 import { checkAnimationFromMfm } from '@/scripts/check-animated-mfm.js';
 
@@ -57,6 +57,7 @@ const props = defineProps<{
 	translating?: boolean;
 	translation?: any;
 	hideFiles?: boolean;
+	expandAllCws?: boolean;
 }>();
 
 const router = useRouter();
@@ -87,6 +88,10 @@ function animatedMFM() {
 }
 
 const collapsed = ref(isLong);
+
+watch(() => props.expandAllCws, (expandAllCws) => {
+	if (expandAllCws) collapsed.value = false;
+});
 </script>
 
 <style lang="scss" module>
@@ -165,5 +170,6 @@ const collapsed = ref(isLong);
 
 .clickToOpen {
 	cursor: pointer;
+	-webkit-tap-highlight-color: transparent;
 }
 </style>
diff --git a/packages/frontend/src/components/MkSuperMenu.vue b/packages/frontend/src/components/MkSuperMenu.vue
index 93296dd9d5..2a7c72ccd9 100644
--- a/packages/frontend/src/components/MkSuperMenu.vue
+++ b/packages/frontend/src/components/MkSuperMenu.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkSwitch.button.vue b/packages/frontend/src/components/MkSwitch.button.vue
index b82f36cdd3..21339d1b4e 100644
--- a/packages/frontend/src/components/MkSwitch.button.vue
+++ b/packages/frontend/src/components/MkSwitch.button.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -24,7 +24,7 @@ import { i18n } from '@/i18n.js';
 
 const props = withDefaults(defineProps<{
 	checked: boolean | Ref<boolean>;
-	disabled?: boolean;
+	disabled?: boolean | Ref<boolean>;
 }>(), {
 	disabled: false,
 });
diff --git a/packages/frontend/src/components/MkSwitch.vue b/packages/frontend/src/components/MkSwitch.vue
index 35e5aebbdd..5672c8e9f7 100644
--- a/packages/frontend/src/components/MkSwitch.vue
+++ b/packages/frontend/src/components/MkSwitch.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkTab.vue b/packages/frontend/src/components/MkTab.vue
index 2b56b946d2..54ab8fc663 100644
--- a/packages/frontend/src/components/MkTab.vue
+++ b/packages/frontend/src/components/MkTab.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -13,18 +13,18 @@ export default defineComponent({
 		},
 	},
 	setup(props, { emit, slots }) {
-		const options = slots.default();
+		const options = slots.default?.() ?? [];
 
 		return () => h('div', {
 			class: 'pxhvhrfw',
 		}, options.map(option => withDirectives(h('button', {
-			class: ['_button', { active: props.modelValue === option.props.value }],
-			key: option.key,
-			disabled: props.modelValue === option.props.value,
+			class: ['_button', { active: props.modelValue === option.props?.value }],
+			key: option.key as string,
+			disabled: props.modelValue === option.props?.value,
 			onClick: () => {
-				emit('update:modelValue', option.props.value);
+				emit('update:modelValue', option.props?.value);
 			},
-		}, option.children), [
+		}, option.children ?? []), [
 			[resolveDirective('click-anime')],
 		])));
 	},
diff --git a/packages/frontend/src/components/MkTagCloud.vue b/packages/frontend/src/components/MkTagCloud.vue
index 083c34906f..6b9c181597 100644
--- a/packages/frontend/src/components/MkTagCloud.vue
+++ b/packages/frontend/src/components/MkTagCloud.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -52,7 +52,7 @@ watch(available, () => {
 });
 
 onMounted(() => {
-	width.value = rootEl.value.offsetWidth;
+	if (rootEl.value) width.value = rootEl.value.offsetWidth;
 
 	if (loaded) {
 		available.value = true;
diff --git a/packages/frontend/src/components/MkTextarea.vue b/packages/frontend/src/components/MkTextarea.vue
index 5c70adde11..3082842699 100644
--- a/packages/frontend/src/components/MkTextarea.vue
+++ b/packages/frontend/src/components/MkTextarea.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			:readonly="readonly"
 			:placeholder="placeholder"
 			:pattern="pattern"
-			:autocomplete="props.autocomplete"
+			:autocomplete="autocomplete"
 			:spellcheck="spellcheck"
 			@focus="focused = true"
 			@blur="focused = false"
@@ -76,9 +76,9 @@ const invalid = ref(false);
 const filled = computed(() => v.value !== '' && v.value != null);
 const inputEl = shallowRef<HTMLTextAreaElement>();
 const preview = ref(false);
-let autocomplete: Autocomplete;
+let autocompleteWorker: Autocomplete | null = null;
 
-const focus = () => inputEl.value.focus();
+const focus = () => inputEl.value?.focus();
 const onInput = (ev) => {
 	changed.value = true;
 	emit('change', ev);
@@ -111,10 +111,10 @@ const updated = () => {
 const debouncedUpdated = debounce(1000, updated);
 
 watch(modelValue, newValue => {
-	v.value = newValue;
+	v.value = newValue ?? '';
 });
 
-watch(v, newValue => {
+watch(v, () => {
 	if (!props.manualSave) {
 		if (props.debounce) {
 			debouncedUpdated();
@@ -123,7 +123,7 @@ watch(v, newValue => {
 		}
 	}
 
-	invalid.value = inputEl.value.validity.badInput;
+	invalid.value = inputEl.value?.validity.badInput ?? true;
 });
 
 onMounted(() => {
@@ -133,14 +133,14 @@ onMounted(() => {
 		}
 	});
 
-	if (props.mfmAutocomplete) {
-		autocomplete = new Autocomplete(inputEl.value, v, props.mfmAutocomplete === true ? null : props.mfmAutocomplete);
+	if (props.mfmAutocomplete && inputEl.value) {
+		autocompleteWorker = new Autocomplete(inputEl.value, v, props.mfmAutocomplete === true ? undefined : props.mfmAutocomplete);
 	}
 });
 
 onUnmounted(() => {
-	if (autocomplete) {
-		autocomplete.detach();
+	if (autocompleteWorker) {
+		autocompleteWorker.detach();
 	}
 });
 </script>
diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue
index 8bd68c0fd2..1c14174a37 100644
--- a/packages/frontend/src/components/MkTimeline.vue
+++ b/packages/frontend/src/components/MkTimeline.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -11,14 +11,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 		:pagination="paginationQuery"
 		:noGap="!defaultStore.state.showGapBetweenNotesInTimeline"
 		@queue="emit('queue', $event)"
-		@status="prComponent.setDisabled($event)"
+		@status="prComponent?.setDisabled($event)"
 	/>
 </MkPullToRefresh>
 </template>
 
 <script lang="ts" setup>
-import { computed, watch, onUnmounted, provide, ref } from 'vue';
-import { Connection } from 'misskey-js/built/streaming.js';
+import { computed, watch, onUnmounted, provide, ref, shallowRef } from 'vue';
+import * as Misskey from 'misskey-js';
 import MkNotes from '@/components/MkNotes.vue';
 import MkPullToRefresh from '@/components/MkPullToRefresh.vue';
 import { useStream } from '@/stream.js';
@@ -29,7 +29,7 @@ import { defaultStore } from '@/store.js';
 import { Paging } from '@/components/MkPagination.vue';
 
 const props = withDefaults(defineProps<{
-	src: string;
+	src: 'home' | 'local' | 'social' | 'bubble' | 'global' | 'mentions' | 'directs' | 'list' | 'antenna' | 'channel' | 'role';
 	list?: string;
 	antenna?: string;
 	channel?: string;
@@ -51,6 +51,7 @@ const emit = defineEmits<{
 	(ev: 'queue', count: number): void;
 }>();
 
+provide('inTimeline', true);
 provide('inChannel', computed(() => props.src === 'channel'));
 
 type TimelineQueryType = {
@@ -65,12 +66,14 @@ type TimelineQueryType = {
   roleId?: string
 }
 
-const prComponent = ref<InstanceType<typeof MkPullToRefresh>>();
-const tlComponent = ref<InstanceType<typeof MkNotes>>();
+const prComponent = shallowRef<InstanceType<typeof MkPullToRefresh>>();
+const tlComponent = shallowRef<InstanceType<typeof MkNotes>>();
 
 let tlNotesCount = 0;
 
-const prepend = note => {
+function prepend(note) {
+	if (tlComponent.value == null) return;
+
 	tlNotesCount++;
 
 	if (instance.notesPerOneAd > 0 && tlNotesCount % instance.notesPerOneAd === 0) {
@@ -82,18 +85,19 @@ const prepend = note => {
 	emit('note');
 
 	if (props.sound) {
-		sound.play($i && (note.userId === $i.id) ? 'noteMy' : 'note');
+		sound.playMisskeySfx($i && (note.userId === $i.id) ? 'noteMy' : 'note');
 	}
-};
+}
 
-let connection: Connection;
-let connection2: Connection;
+let connection: Misskey.ChannelConnection | null = null;
+let connection2: Misskey.ChannelConnection | null = null;
 let paginationQuery: Paging | null = null;
 
 const stream = useStream();
 
 function connectChannel() {
 	if (props.src === 'antenna') {
+		if (props.antenna == null) return;
 		connection = stream.useChannel('antenna', {
 			antennaId: props.antenna,
 		});
@@ -141,20 +145,24 @@ function connectChannel() {
 		connection = stream.useChannel('main');
 		connection.on('mention', onNote);
 	} else if (props.src === 'list') {
+		if (props.list == null) return;
 		connection = stream.useChannel('userList', {
+			withRenotes: props.withRenotes,
 			withFiles: props.onlyFiles ? true : undefined,
 			listId: props.list,
 		});
 	} else if (props.src === 'channel') {
+		if (props.channel == null) return;
 		connection = stream.useChannel('channel', {
 			channelId: props.channel,
 		});
 	} else if (props.src === 'role') {
+		if (props.role == null) return;
 		connection = stream.useChannel('roleTimeline', {
 			roleId: props.role,
 		});
 	}
-	if (props.src !== 'directs' || props.src !== 'mentions') connection.on('note', prepend);
+	if (props.src !== 'directs' && props.src !== 'mentions') connection?.on('note', prepend);
 }
 
 function disconnectChannel() {
@@ -163,7 +171,7 @@ function disconnectChannel() {
 }
 
 function updatePaginationQuery() {
-	let endpoint: string | null;
+	let endpoint: keyof Misskey.Endpoints | null;
 	let query: TimelineQueryType | null;
 
 	if (props.src === 'antenna') {
@@ -219,6 +227,7 @@ function updatePaginationQuery() {
 	} else if (props.src === 'list') {
 		endpoint = 'notes/user-list-timeline';
 		query = {
+			withRenotes: props.withRenotes,
 			withFiles: props.onlyFiles ? true : undefined,
 			listId: props.list,
 		};
@@ -257,8 +266,9 @@ function refreshEndpointAndChannel() {
 	updatePaginationQuery();
 }
 
+// デッキのリストカラムでwithRenotesを変更した場合に自動的に更新されるようにさせる
 // IDが切り替わったら切り替え先のTLを表示させたい
-watch(() => [props.list, props.antenna, props.channel, props.role], refreshEndpointAndChannel);
+watch(() => [props.list, props.antenna, props.channel, props.role, props.withRenotes], refreshEndpointAndChannel);
 
 // 初回表示用
 refreshEndpointAndChannel();
@@ -269,6 +279,8 @@ onUnmounted(() => {
 
 function reloadTimeline() {
 	return new Promise<void>((res) => {
+		if (tlComponent.value == null) return;
+
 		tlNotesCount = 0;
 
 		tlComponent.value.pagingComponent?.reload().then(() => {
diff --git a/packages/frontend/src/components/MkToast.vue b/packages/frontend/src/components/MkToast.vue
index 82cd236193..a117e49350 100644
--- a/packages/frontend/src/components/MkToast.vue
+++ b/packages/frontend/src/components/MkToast.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkTokenGenerateWindow.vue b/packages/frontend/src/components/MkTokenGenerateWindow.vue
index 8e8e26ed5f..b32066c950 100644
--- a/packages/frontend/src/components/MkTokenGenerateWindow.vue
+++ b/packages/frontend/src/components/MkTokenGenerateWindow.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	:withOkButton="true"
 	:okButtonDisabled="false"
 	:canClose="false"
-	@close="dialog.close()"
+	@close="dialog?.close()"
 	@closed="$emit('closed')"
 	@ok="ok()"
 >
@@ -33,7 +33,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<MkButton inline @click="enableAll">{{ i18n.ts.enableAll }}</MkButton>
 			</div>
 			<div class="_gaps_s">
-				<MkSwitch v-for="kind in Object.keys(permissions)" :key="kind" v-model="permissions[kind]">{{ i18n.t(`_permissions.${kind}`) }}</MkSwitch>
+				<MkSwitch v-for="kind in Object.keys(permissionSwitches)" :key="kind" v-model="permissionSwitches[kind]">{{ i18n.ts._permissions[kind] }}</MkSwitch>
+			</div>
+			<div v-if="iAmAdmin" :class="$style.adminPermissions">
+				<div :class="$style.adminPermissionsHeader"><b>{{ i18n.ts.adminPermission }}</b></div>
+				<div class="_gaps_s">
+					<MkSwitch v-for="kind in Object.keys(permissionSwitchesForAdmin)" :key="kind" v-model="permissionSwitchesForAdmin[kind]">{{ i18n.ts._permissions[kind] }}</MkSwitch>
+				</div>
 			</div>
 		</div>
 	</MkSpacer>
@@ -49,6 +55,7 @@ import MkButton from './MkButton.vue';
 import MkInfo from './MkInfo.vue';
 import MkModalWindow from '@/components/MkModalWindow.vue';
 import { i18n } from '@/i18n.js';
+import { iAmAdmin } from '@/account.js';
 
 const props = withDefaults(defineProps<{
 	title?: string | null;
@@ -68,37 +75,76 @@ const emit = defineEmits<{
 }>();
 
 const defaultPermissions = Misskey.permissions.filter(p => !p.startsWith('read:admin') && !p.startsWith('write:admin'));
+const adminPermissions = Misskey.permissions.filter(p => p.startsWith('read:admin') || p.startsWith('write:admin'));
+
 const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
 const name = ref(props.initialName);
-const permissions = ref(<Record<(typeof Misskey.permissions)[number], boolean>>{});
+const permissionSwitches = ref(<Record<(typeof Misskey.permissions)[number], boolean>>{});
+const permissionSwitchesForAdmin = ref(<Record<(typeof Misskey.permissions)[number], boolean>>{});
 
 if (props.initialPermissions) {
 	for (const kind of props.initialPermissions) {
-		permissions.value[kind] = true;
+		permissionSwitches.value[kind] = true;
 	}
 } else {
 	for (const kind of defaultPermissions) {
-		permissions.value[kind] = false;
+		permissionSwitches.value[kind] = false;
+	}
+
+	if (iAmAdmin) {
+		for (const kind of adminPermissions) {
+			permissionSwitchesForAdmin.value[kind] = false;
+		}
 	}
 }
 
 function ok(): void {
 	emit('done', {
 		name: name.value,
-		permissions: Object.keys(permissions.value).filter(p => permissions.value[p]),
+		permissions: [
+			...Object.keys(permissionSwitches.value).filter(p => permissionSwitches.value[p]),
+			...(iAmAdmin ? Object.keys(permissionSwitchesForAdmin.value).filter(p => permissionSwitchesForAdmin.value[p]) : []),
+		],
 	});
-	dialog.value.close();
+	dialog.value?.close();
 }
 
 function disableAll(): void {
-	for (const p in permissions.value) {
-		permissions.value[p] = false;
+	for (const p in permissionSwitches.value) {
+		permissionSwitches.value[p] = false;
+	}
+	if (iAmAdmin) {
+		for (const p in permissionSwitchesForAdmin.value) {
+			permissionSwitchesForAdmin.value[p] = false;
+		}
 	}
 }
 
 function enableAll(): void {
-	for (const p in permissions.value) {
-		permissions.value[p] = true;
+	for (const p in permissionSwitches.value) {
+		permissionSwitches.value[p] = true;
+	}
+	if (iAmAdmin) {
+		for (const p in permissionSwitchesForAdmin.value) {
+			permissionSwitchesForAdmin.value[p] = true;
+		}
 	}
 }
 </script>
+
+<style module lang="scss">
+.adminPermissions {
+	margin: 8px -6px 0;
+	padding: 24px 6px 6px;
+	border: 2px solid var(--error);
+	border-radius: calc(var(--radius) / 2);
+}
+
+.adminPermissionsHeader {
+	margin: -34px 0 6px 12px;
+	padding: 0 4px;
+	width: fit-content;
+	color: var(--error);
+	background: var(--panel);
+}
+</style>
diff --git a/packages/frontend/src/components/MkTooltip.vue b/packages/frontend/src/components/MkTooltip.vue
index eeb9325a29..aac07008a4 100644
--- a/packages/frontend/src/components/MkTooltip.vue
+++ b/packages/frontend/src/components/MkTooltip.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -13,8 +13,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 >
 	<div v-show="showing" ref="el" :class="$style.root" class="_acrylic _shadow" :style="{ zIndex, maxWidth: maxWidth + 'px' }">
 		<slot>
-			<Mfm v-if="asMfm" :text="text"/>
-			<span v-else>{{ text }}</span>
+			<template v-if="text">
+				<Mfm v-if="asMfm" :text="text"/>
+				<span v-else>{{ text }}</span>
+			</template>
 		</slot>
 	</div>
 </Transition>
@@ -53,6 +55,7 @@ const el = shallowRef<HTMLElement>();
 const zIndex = os.claimZIndex('high');
 
 function setPosition() {
+	if (el.value == null) return;
 	const data = calcPopupPosition(el.value, {
 		anchorElement: props.targetElement,
 		direction: props.direction,
diff --git a/packages/frontend/src/components/MkTutorialDialog.Note.vue b/packages/frontend/src/components/MkTutorialDialog.Note.vue
index 3fca958055..5544434b5f 100644
--- a/packages/frontend/src/components/MkTutorialDialog.Note.vue
+++ b/packages/frontend/src/components/MkTutorialDialog.Note.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <div v-else-if="phase === 'howToReact'" class="_gaps">
 	<div style="text-align: center; padding: 0 16px;">{{ i18n.ts._initialTutorial._reaction.description }}</div>
 	<div>{{ i18n.ts._initialTutorial._reaction.letsTryReacting }}</div>
-	<MkNote :class="$style.exampleNoteRoot" :note="exampleNote" :mock="true" @reaction="addReaction" @removeReaction="removeReaction" @updateReaction="updateReaction"/>
+	<MkNote :class="$style.exampleNoteRoot" :note="exampleNote" :mock="true" @reaction="addReaction" @removeReaction="removeReaction"/>
 	<div v-if="onceReacted"><b style="color: var(--accent);"><i class="ph-check ph-bold ph-lg"></i> {{ i18n.ts._initialTutorial.wellDone }}</b> {{ i18n.ts._initialTutorial._reaction.reactNotification }}<br>{{ i18n.ts._initialTutorial._reaction.reactDone }}</div>
 </div>
 </template>
@@ -53,7 +53,7 @@ const exampleNote = reactive<Misskey.entities.Note>({
 		isBot: false,
 		isCat: true,
 		emojis: {},
-		onlineStatus: null,
+		onlineStatus: 'unknown',
 		badgeRoles: [],
 	},
 	text: 'just setting up my shonk',
@@ -86,7 +86,6 @@ function doNotification(emoji: string): void {
 	const notification: Misskey.entities.Notification = {
 		id: Math.random().toString(),
 		createdAt: new Date().toUTCString(),
-		isRead: false,
 		type: 'reaction',
 		reaction: emoji,
 		user: $i,
diff --git a/packages/frontend/src/components/MkTutorialDialog.PostNote.vue b/packages/frontend/src/components/MkTutorialDialog.PostNote.vue
index f093d6d9ef..1771559a9b 100644
--- a/packages/frontend/src/components/MkTutorialDialog.PostNote.vue
+++ b/packages/frontend/src/components/MkTutorialDialog.PostNote.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -58,7 +58,7 @@ const exampleCWNote = reactive<Misskey.entities.Note>({
 		isBot: false,
 		isCat: true,
 		emojis: {},
-		onlineStatus: null,
+		onlineStatus: 'unknown',
 		badgeRoles: [],
 	},
 	text: i18n.ts._initialTutorial._postNote._cw._exampleNote.note,
diff --git a/packages/frontend/src/components/MkTutorialDialog.Sensitive.vue b/packages/frontend/src/components/MkTutorialDialog.Sensitive.vue
index dd255a2214..4b4e8ea8f8 100644
--- a/packages/frontend/src/components/MkTutorialDialog.Sensitive.vue
+++ b/packages/frontend/src/components/MkTutorialDialog.Sensitive.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -40,7 +40,7 @@ const emit = defineEmits<{
 const onceSucceeded = ref<boolean>(false);
 
 function doSucceeded(fileId: string, to: boolean) {
-	if (fileId === exampleNote.fileIds[0] && to) {
+	if (fileId === exampleNote.fileIds?.[0] && to) {
 		onceSucceeded.value = true;
 		emit('succeeded');
 	}
diff --git a/packages/frontend/src/components/MkTutorialDialog.Timeline.vue b/packages/frontend/src/components/MkTutorialDialog.Timeline.vue
index c2384423fd..f5670c7ebd 100644
--- a/packages/frontend/src/components/MkTutorialDialog.Timeline.vue
+++ b/packages/frontend/src/components/MkTutorialDialog.Timeline.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkTutorialDialog.vue b/packages/frontend/src/components/MkTutorialDialog.vue
index a734f93ec9..6cd7019fed 100644
--- a/packages/frontend/src/components/MkTutorialDialog.vue
+++ b/packages/frontend/src/components/MkTutorialDialog.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	@close="close(true)"
 	@closed="emit('closed')"
 >
-	<template v-if="page === 1" #header><i class="ph-pencil ph-bold pg-lg"></i> {{ i18n.ts._initialTutorial._note.title }}</template>
+	<template v-if="page === 1" #header><i class="ph-pencil-simple ph-bold pg-lg"></i> {{ i18n.ts._initialTutorial._note.title }}</template>
 	<template v-else-if="page === 2" #header><i class="ph-smiley ph-bold pg-lg"></i> {{ i18n.ts._initialTutorial._reaction.title }}</template>
 	<template v-else-if="page === 3" #header><i class="ph-house ph-bold pg-lg"></i> {{ i18n.ts._initialTutorial._timeline.title }}</template>
 	<template v-else-if="page === 4" #header><i class="ph-plus ph-bold pg-lg"></i> {{ i18n.ts._initialTutorial._postNote.title }}</template>
@@ -133,7 +133,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 									<a href="https://misskey-hub.net/docs/for-users/" target="_blank" class="_link">{{ i18n.ts.help }}</a>
 								</template>
 							</I18n>
-							<div>{{ i18n.t('_initialAccountSetting.haveFun', { name: instance.name ?? host }) }}</div>
+							<div>{{ i18n.tsx._initialAccountSetting.haveFun({ name: instance.name ?? host }) }}</div>
 							<div class="_buttonsCenter" style="margin-top: 16px;">
 								<MkButton v-if="initialPage !== 4" rounded @click="page--"><i class="ph-arrow-left ph-bold pg-lg"></i> {{ i18n.ts.goBack }}</MkButton>
 								<MkButton rounded primary gradate @click="close(false)">{{ i18n.ts.close }}</MkButton>
diff --git a/packages/frontend/src/components/MkUpdated.vue b/packages/frontend/src/components/MkUpdated.vue
index 07efaf8982..4fb0749931 100644
--- a/packages/frontend/src/components/MkUpdated.vue
+++ b/packages/frontend/src/components/MkUpdated.vue
@@ -1,15 +1,15 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<MkModal ref="modal" :zPriority="'middle'" @click="$refs.modal.close()" @closed="$emit('closed')">
+<MkModal ref="modal" :zPriority="'middle'" @click="modal?.close()" @closed="$emit('closed')">
 	<div :class="$style.root">
 		<div :class="$style.title"><MkSparkle>{{ i18n.ts.misskeyUpdated }}</MkSparkle></div>
 		<div :class="$style.version">✨{{ version }}🚀</div>
 		<MkButton full @click="whatIsNew">{{ i18n.ts.whatIsNew }}</MkButton>
-		<MkButton :class="$style.gotIt" primary full @click="$refs.modal.close()">{{ i18n.ts.gotIt }}</MkButton>
+		<MkButton :class="$style.gotIt" primary full @click="modal?.close()">{{ i18n.ts.gotIt }}</MkButton>
 	</div>
 </MkModal>
 </template>
@@ -26,8 +26,8 @@ import { confetti } from '@/scripts/confetti.js';
 const modal = shallowRef<InstanceType<typeof MkModal>>();
 
 const whatIsNew = () => {
-	modal.value.close();
-	window.open(`https://git.joinsharkey.org/Sharkey/Sharkey/releases/tag/${version}`, '_blank');
+	modal.value?.close();
+	window.open(`https://activitypub.software/TransFem-org/Sharkey/-/releases/${version}`, '_blank');
 };
 
 onMounted(() => {
diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue
index 486aaa0bbd..10ba137b94 100644
--- a/packages/frontend/src/components/MkUrlPreview.vue
+++ b/packages/frontend/src/components/MkUrlPreview.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			v-if="player.url.startsWith('http://') || player.url.startsWith('https://')"
 			sandbox="allow-popups allow-scripts allow-storage-access-by-user-activation allow-same-origin"
 			scrolling="no"
-			:allow="player.allow.join(';')"
+			:allow="player.allow == null ? 'autoplay;encrypted-media;fullscreen' : player.allow.filter(x => ['autoplay', 'clipboard-write', 'fullscreen', 'encrypted-media', 'picture-in-picture', 'web-share'].includes(x)).join(';')"
 			:class="$style.playerIframe"
 			:src="player.url + (player.url.match(/\?/) ? '&autoplay=1&auto_play=1' : '?autoplay=1&auto_play=1')"
 			:style="{ border: 0 }"
@@ -83,8 +83,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { defineAsyncComponent, onUnmounted, ref } from 'vue';
-import type { summaly } from 'summaly';
+import { defineAsyncComponent, onDeactivated, onUnmounted, ref } from 'vue';
+import type { summaly } from '@misskey-dev/summaly';
 import { url as local } from '@/config.js';
 import { i18n } from '@/i18n.js';
 import * as os from '@/os.js';
@@ -131,6 +131,10 @@ const embedId = `embed${Math.random().toString().replace(/\D/, '')}`;
 const tweetHeight = ref(150);
 const unknownUrl = ref(false);
 
+onDeactivated(() => {
+	playerEnabled.value = false;
+});
+
 const requestUrl = new URL(props.url);
 if (!['http:', 'https:'].includes(requestUrl.protocol)) throw new Error('invalid url');
 
diff --git a/packages/frontend/src/components/MkUrlPreviewPopup.vue b/packages/frontend/src/components/MkUrlPreviewPopup.vue
index 81c383540c..cf75064be7 100644
--- a/packages/frontend/src/components/MkUrlPreviewPopup.vue
+++ b/packages/frontend/src/components/MkUrlPreviewPopup.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue b/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue
index 3fbadbe34f..13ab6fd763 100644
--- a/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue
+++ b/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <MkModalWindow
 	ref="dialog"
 	:width="400"
-	@close="dialog.close()"
+	@close="dialog?.close()"
 	@closed="$emit('closed')"
 >
 	<template v-if="announcement" #header>:{{ announcement.title }}:</template>
@@ -56,6 +56,7 @@ import MkModalWindow from '@/components/MkModalWindow.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import MkTextarea from '@/components/MkTextarea.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
@@ -63,14 +64,14 @@ import MkRadios from '@/components/MkRadios.vue';
 
 const props = defineProps<{
 	user: Misskey.entities.User,
-	announcement?: any,
+	announcement?: Misskey.entities.Announcement,
 }>();
 
 const dialog = ref<InstanceType<typeof MkModalWindow> | null>(null);
-const title = ref<string>(props.announcement ? props.announcement.title : '');
-const text = ref<string>(props.announcement ? props.announcement.text : '');
-const icon = ref<string>(props.announcement ? props.announcement.icon : 'info');
-const display = ref<string>(props.announcement ? props.announcement.display : 'dialog');
+const title = ref(props.announcement ? props.announcement.title : '');
+const text = ref(props.announcement ? props.announcement.text : '');
+const icon = ref(props.announcement ? props.announcement.icon : 'info');
+const display = ref(props.announcement ? props.announcement.display : 'dialog');
 const needConfirmationToRead = ref(props.announcement ? props.announcement.needConfirmationToRead : false);
 
 const emit = defineEmits<{
@@ -91,18 +92,18 @@ async function done() {
 
 	if (props.announcement) {
 		await os.apiWithDialog('admin/announcements/update', {
-			id: props.announcement.id,
 			...params,
+			id: props.announcement.id,
 		});
 
 		emit('done', {
 			updated: {
-				id: props.announcement.id,
 				...params,
+				id: props.announcement.id,
 			},
 		});
 
-		dialog.value.close();
+		dialog.value?.close();
 	} else {
 		const created = await os.apiWithDialog('admin/announcements/create', params);
 
@@ -110,25 +111,27 @@ async function done() {
 			created: created,
 		});
 
-		dialog.value.close();
+		dialog.value?.close();
 	}
 }
 
 async function del() {
 	const { canceled } = await os.confirm({
 		type: 'warning',
-		text: i18n.t('removeAreYouSure', { x: title.value }),
+		text: i18n.tsx.removeAreYouSure({ x: title.value }),
 	});
 	if (canceled) return;
 
-	os.api('admin/announcements/delete', {
-		id: props.announcement.id,
-	}).then(() => {
-		emit('done', {
-			deleted: true,
+	if (props.announcement) {
+		await misskeyApi('admin/announcements/delete', {
+			id: props.announcement.id,
 		});
-		dialog.value.close();
+	}
+
+	emit('done', {
+		deleted: true,
 	});
+	dialog.value?.close();
 }
 </script>
 
diff --git a/packages/frontend/src/components/MkUserCardMini.vue b/packages/frontend/src/components/MkUserCardMini.vue
index b9c7377972..603f9f2435 100644
--- a/packages/frontend/src/components/MkUserCardMini.vue
+++ b/packages/frontend/src/components/MkUserCardMini.vue
@@ -1,16 +1,16 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<div v-adaptive-bg :class="[$style.root, { yellow: user.isSilenced, blue: !user.approved, red: user.isSuspended, gray: false }]">
-	<MkAvatar class="avatar" :user="user" indicator/>
-	<div class="body">
-		<span class="name"><MkUserName class="name" :user="user"/></span>
-		<span class="sub"><span class="acct _monospace">@{{ acct(user) }}</span></span>
+<div v-adaptive-bg :class="[$style.root]">
+	<MkAvatar :class="$style.avatar" :user="user" indicator/>
+	<div :class="$style.body">
+		<span :class="$style.name"><MkUserName :user="user"/></span>
+		<span :class="$style.sub"><span class="_monospace">@{{ acct(user) }}</span></span>
 	</div>
-	<MkMiniChart v-if="chartValues" class="chart" :src="chartValues"/>
+	<MkMiniChart v-if="chartValues" :class="$style.chart" :src="chartValues"/>
 </div>
 </template>
 
@@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import * as Misskey from 'misskey-js';
 import { onMounted, ref } from 'vue';
 import MkMiniChart from '@/components/MkMiniChart.vue';
-import * as os from '@/os.js';
+import { misskeyApiGet } from '@/scripts/misskey-api.js';
 import { acct } from '@/filters/user.js';
 
 const props = withDefaults(defineProps<{
@@ -32,7 +32,7 @@ const chartValues = ref<number[] | null>(null);
 
 onMounted(() => {
 	if (props.withChart) {
-		os.apiGet('charts/user/notes', { userId: props.user.id, limit: 16 + 1, span: 'day' }).then(res => {
+		misskeyApiGet('charts/user/notes', { userId: props.user.id, limit: 16 + 1, span: 'day' }).then(res => {
 			// 今日のぶんの値はまだ途中の値であり、それも含めると大抵の場合前日よりも下降しているようなグラフになってしまうため今日は弾く
 			res.inc.splice(0, 1);
 			chartValues.value = res.inc;
@@ -42,77 +42,53 @@ onMounted(() => {
 </script>
 
 <style lang="scss" module>
-.root {
-	$bodyTitleHieght: 18px;
-	$bodyInfoHieght: 16px;
+$bodyTitleHieght: 18px;
+$bodyInfoHieght: 16px;
 
+.root {
 	display: flex;
 	align-items: center;
 	padding: 16px;
 	background: var(--panel);
 	border-radius: var(--radius-sm);
+}
 
-	> :global(.avatar) {
-		display: block;
-		width: ($bodyTitleHieght + $bodyInfoHieght);
-		height: ($bodyTitleHieght + $bodyInfoHieght);
-		margin-right: 12px;
-	}
+.avatar {
+	display: block;
+	width: ($bodyTitleHieght + $bodyInfoHieght);
+	height: ($bodyTitleHieght + $bodyInfoHieght);
+	margin-right: 12px;
+}
 
-	> :global(.body) {
-		flex: 1;
-		overflow: hidden;
-		font-size: 0.9em;
-		color: var(--fg);
-		padding-right: 8px;
+.body {
+	flex: 1;
+	overflow: hidden;
+	font-size: 0.9em;
+	color: var(--fg);
+	padding-right: 8px;
+}
 
-		> :global(.name) {
-			display: block;
-			width: 100%;
-			white-space: nowrap;
-			overflow: hidden;
-			text-overflow: ellipsis;
-			line-height: $bodyTitleHieght;
-		}
+.name {
+	display: block;
+	width: 100%;
+	white-space: nowrap;
+	overflow: hidden;
+	text-overflow: ellipsis;
+	line-height: $bodyTitleHieght;
+}
 
-		> :global(.sub) {
-			display: block;
-			width: 100%;
-			font-size: 95%;
-			opacity: 0.7;
-			line-height: $bodyInfoHieght;
-			white-space: nowrap;
-			overflow: hidden;
-			text-overflow: ellipsis;
-		}
-	}
+.sub {
+	display: block;
+	width: 100%;
+	font-size: 95%;
+	opacity: 0.7;
+	line-height: $bodyInfoHieght;
+	white-space: nowrap;
+	overflow: hidden;
+	text-overflow: ellipsis;
+}
 
-	> :global(.chart) {
-		height: 30px;
-	}
-
-	&:global(.yellow) {
-		--c: rgb(255 196 0 / 15%);
-		background-image: linear-gradient(45deg, var(--c) 16.67%, transparent 16.67%, transparent 50%, var(--c) 50%, var(--c) 66.67%, transparent 66.67%, transparent 100%);
-		background-size: 16px 16px;
-	}
-
-	&:global(.blue) {
-		--c: rgba(0 153 255 / 15%);
-		background-image: linear-gradient(45deg, var(--c) 16.67%, transparent 16.67%, transparent 50%, var(--c) 50%, var(--c) 66.67%, transparent 66.67%, transparent 100%);
-		background-size: 16px 16px;
-	}
-
-	&:global(.red) {
-		--c: rgb(255 0 0 / 15%);
-		background-image: linear-gradient(45deg, var(--c) 16.67%, transparent 16.67%, transparent 50%, var(--c) 50%, var(--c) 66.67%, transparent 66.67%, transparent 100%);
-		background-size: 16px 16px;
-	}
-
-	&:global(.gray) {
-		--c: var(--bg);
-		background-image: linear-gradient(45deg, var(--c) 16.67%, transparent 16.67%, transparent 50%, var(--c) 50%, var(--c) 66.67%, transparent 66.67%, transparent 100%);
-		background-size: 16px 16px;
-	}
+.chart {
+	height: 30px;
 }
 </style>
diff --git a/packages/frontend/src/components/MkUserInfo.vue b/packages/frontend/src/components/MkUserInfo.vue
index 4e326911d8..63c4af41a0 100644
--- a/packages/frontend/src/components/MkUserInfo.vue
+++ b/packages/frontend/src/components/MkUserInfo.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -65,8 +65,8 @@ defineProps<{
 	top: 62px;
 	left: 13px;
 	z-index: 2;
-	width: 58px;
-	height: 58px;
+	width: var(--avatar);
+	height: var(--avatar);
 	border: solid 4px var(--panel);
 }
 
diff --git a/packages/frontend/src/components/MkUserList.vue b/packages/frontend/src/components/MkUserList.vue
index 56a61dce23..17a9254d01 100644
--- a/packages/frontend/src/components/MkUserList.vue
+++ b/packages/frontend/src/components/MkUserList.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkUserOnlineIndicator.vue b/packages/frontend/src/components/MkUserOnlineIndicator.vue
index 76470cba88..9f04353f62 100644
--- a/packages/frontend/src/components/MkUserOnlineIndicator.vue
+++ b/packages/frontend/src/components/MkUserOnlineIndicator.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkUserPopup.vue b/packages/frontend/src/components/MkUserPopup.vue
index ec2c48b1cf..6550fc4ec1 100644
--- a/packages/frontend/src/components/MkUserPopup.vue
+++ b/packages/frontend/src/components/MkUserPopup.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -38,7 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					</dt>
 					<dd :class="$style.fieldvalue">
 						<Mfm :text="field.value" :nyaize="false" :author="user" :colored="false"/>
-						<i v-if="user.verifiedLinks.includes(field.value)" v-tooltip:dialog="i18n.ts.verifiedLink" class="ph-seal-check ph-bold ph-lg" :class="$style.verifiedLink"></i>
+						<i v-if="user.verifiedLinks.includes(field.value)" v-tooltip:dialog="i18n.ts.verifiedLink" class="ph-seal-check ph-bold ph-lg"></i>
 					</dd>
 				</dl>
 			</div>
@@ -72,6 +72,7 @@ import * as Misskey from 'misskey-js';
 import MkFollowButton from '@/components/MkFollowButton.vue';
 import { userPage } from '@/filters/user.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { getUserMenu } from '@/scripts/get-user-menu.js';
 import number from '@/filters/number.js';
 import { i18n } from '@/i18n.js';
@@ -97,6 +98,7 @@ const top = ref(0);
 const left = ref(0);
 
 function showMenu(ev: MouseEvent) {
+	if (user.value == null) return;
 	const { menu, cleanup } = getUserMenu(user.value);
 	os.popupMenu(menu, ev.currentTarget ?? ev.target).finally(cleanup);
 }
@@ -109,7 +111,7 @@ onMounted(() => {
 			Misskey.acct.parse(props.q.substring(1)) :
 			{ userId: props.q };
 
-		os.api('users/show', query).then(res => {
+		misskeyApi('users/show', query).then(res => {
 			if (!props.showing) return;
 			user.value = res;
 		});
@@ -199,8 +201,8 @@ onMounted(() => {
 	right: 0;
 	margin: 0 auto;
 	z-index: 2;
-	width: 58px;
-	height: 58px;
+	width: var(--avatar);
+	height: var(--avatar);
 }
 
 .title {
diff --git a/packages/frontend/src/components/MkUserSelectDialog.vue b/packages/frontend/src/components/MkUserSelectDialog.vue
index 9d41147bd2..b76be051d8 100644
--- a/packages/frontend/src/components/MkUserSelectDialog.vue
+++ b/packages/frontend/src/components/MkUserSelectDialog.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -16,7 +16,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<template #header>{{ i18n.ts.selectUser }}</template>
 	<div>
 		<div :class="$style.form">
-			<FormSplit :minWidth="170">
+			<MkInput v-if="localOnly" v-model="username" :autofocus="true" @update:modelValue="search">
+				<template #label>{{ i18n.ts.username }}</template>
+				<template #prefix>@</template>
+			</MkInput>
+			<FormSplit v-else :minWidth="170">
 				<MkInput v-model="username" :autofocus="true" @update:modelValue="search">
 					<template #label>{{ i18n.ts.username }}</template>
 					<template #prefix>@</template>
@@ -62,11 +66,11 @@ import * as Misskey from 'misskey-js';
 import MkInput from '@/components/MkInput.vue';
 import FormSplit from '@/components/form/split.vue';
 import MkModalWindow from '@/components/MkModalWindow.vue';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { defaultStore } from '@/store.js';
 import { i18n } from '@/i18n.js';
 import { $i } from '@/account.js';
-import { hostname } from '@/config.js';
+import { host as currentHost, hostname } from '@/config.js';
 
 const emit = defineEmits<{
 	(ev: 'ok', selected: Misskey.entities.UserDetailed): void;
@@ -74,58 +78,85 @@ const emit = defineEmits<{
 	(ev: 'closed'): void;
 }>();
 
-const props = defineProps<{
+const props = withDefaults(defineProps<{
 	includeSelf?: boolean;
-}>();
+	localOnly?: boolean;
+}>(), {
+	includeSelf: false,
+	localOnly: false,
+});
 
 const username = ref('');
 const host = ref('');
-const users = ref<Misskey.entities.UserDetailed[]>([]);
+const users = ref<Misskey.entities.UserLite[]>([]);
 const recentUsers = ref<Misskey.entities.UserDetailed[]>([]);
-const selected = ref<Misskey.entities.UserDetailed | null>(null);
+const selected = ref<Misskey.entities.UserLite | null>(null);
 const dialogEl = ref();
 
-const search = () => {
+function search() {
 	if (username.value === '' && host.value === '') {
 		users.value = [];
 		return;
 	}
-	os.api('users/search-by-username-and-host', {
+
+	misskeyApi('users/search-by-username-and-host', {
 		username: username.value,
-		host: host.value,
+		host: props.localOnly ? '.' : host.value,
 		limit: 10,
 		detail: false,
 	}).then(_users => {
-		users.value = _users;
+		users.value = _users.filter((u) => {
+			if (props.includeSelf) {
+				return true;
+			} else {
+				return u.id !== $i?.id;
+			}
+		});
 	});
-};
+}
 
-const ok = () => {
+async function ok() {
 	if (selected.value == null) return;
-	emit('ok', selected.value);
+
+	const user = await misskeyApi('users/show', {
+		userId: selected.value.id,
+	});
+	emit('ok', user);
+
 	dialogEl.value.close();
 
 	// 最近使ったユーザー更新
 	let recents = defaultStore.state.recentlyUsedUsers;
-	recents = recents.filter(x => x !== selected.value.id);
+	recents = recents.filter(x => x !== selected.value?.id);
 	recents.unshift(selected.value.id);
 	defaultStore.set('recentlyUsedUsers', recents.splice(0, 16));
-};
+}
 
-const cancel = () => {
+function cancel() {
 	emit('cancel');
 	dialogEl.value.close();
-};
+}
 
 onMounted(() => {
-	os.api('users/show', {
+	misskeyApi('users/show', {
 		userIds: defaultStore.state.recentlyUsedUsers,
-	}).then(users => {
-		if (props.includeSelf && users.find(x => $i ? x.id === $i.id : true) == null) {
-			recentUsers.value = [$i, ...users];
-		} else {
-			recentUsers.value = users;
-		}
+	}).then(foundUsers => {
+		let _users = foundUsers;
+		_users = _users.filter((u) => {
+			if (props.localOnly) {
+				return u.host == null;
+			} else {
+				return true;
+			}
+		});
+		_users = _users.filter((u) => {
+			if (props.includeSelf) {
+				return true;
+			} else {
+				return u.id !== $i?.id;
+			}
+		});
+		recentUsers.value = _users;
 	});
 });
 </script>
@@ -133,7 +164,7 @@ onMounted(() => {
 <style lang="scss" module>
 
 .form {
-	padding: 0 var(--root-margin);
+	padding: calc(var(--root-margin) / 2) var(--root-margin);
 }
 
 .result,
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts
index 45c7da40ce..638bfb4372 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts
+++ b/packages/frontend/src/components/MkUserSetupDialog.Follow.stories.impl.ts
@@ -1,11 +1,11 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
 import { StoryObj } from '@storybook/vue3';
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
 import { commonHandlers } from '../../.storybook/mocks.js';
 import { userDetailed } from '../../.storybook/fakes.js';
 import MkUserSetupDialog_Follow from './MkUserSetupDialog.Follow.vue';
@@ -38,17 +38,17 @@ export const Default = {
 		msw: {
 			handlers: [
 				...commonHandlers,
-				rest.post('/api/users', (req, res, ctx) => {
-					return res(ctx.json([
+				http.post('/api/users', () => {
+					return HttpResponse.json([
 						userDetailed('44'),
 						userDetailed('49'),
-					]));
+					]);
 				}),
-				rest.post('/api/pinned-users', (req, res, ctx) => {
-					return res(ctx.json([
+				http.post('/api/pinned-users', () => {
+					return HttpResponse.json([
 						userDetailed('44'),
 						userDetailed('49'),
-					]));
+					]);
 				}),
 			],
 		},
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Follow.vue b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue
index 5f3f5b81dd..1524ea0ec9 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.Follow.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<MkPagination :pagination="pinnedUsers">
 			<template #default="{ items }">
 				<div :class="$style.users">
-					<XUser v-for="item in items" :key="item.id" :user="item"/>
+					<XUser v-for="item in (items as Misskey.entities.UserDetailed[])" :key="item.id" :user="item"/>
 				</div>
 			</template>
 		</MkPagination>
@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<MkPagination :pagination="popularUsers">
 			<template #default="{ items }">
 				<div :class="$style.users">
-					<XUser v-for="item in items" :key="item.id" :user="item"/>
+					<XUser v-for="item in (items as Misskey.entities.UserDetailed[])" :key="item.id" :user="item"/>
 				</div>
 			</template>
 		</MkPagination>
@@ -34,18 +34,28 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
+import * as Misskey from 'misskey-js';
 import { i18n } from '@/i18n.js';
 import MkFolder from '@/components/MkFolder.vue';
 import XUser from '@/components/MkUserSetupDialog.User.vue';
-import MkPagination from '@/components/MkPagination.vue';
+import MkPagination, { type Paging } from '@/components/MkPagination.vue';
 
-const pinnedUsers = { endpoint: 'pinned-users', noPaging: true };
+const pinnedUsers: Paging = {
+	endpoint: 'pinned-users',
+	noPaging: true,
+	limit: 10,
+};
 
-const popularUsers = { endpoint: 'users', limit: 10, noPaging: true, params: {
-	state: 'alive',
-	origin: 'local',
-	sort: '+follower',
-} };
+const popularUsers: Paging = {
+	endpoint: 'users',
+	limit: 10,
+	noPaging: true,
+	params: {
+		state: 'alive',
+		origin: 'local',
+		sort: '+follower',
+	},
+};
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Privacy.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.Privacy.stories.impl.ts
index 0f81c0817d..2a7947c6f8 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.Privacy.stories.impl.ts
+++ b/packages/frontend/src/components/MkUserSetupDialog.Privacy.stories.impl.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue b/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue
index 664c4da203..6d2f0bbb99 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.Privacy.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -41,14 +41,14 @@ import { i18n } from '@/i18n.js';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import MkFolder from '@/components/MkFolder.vue';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 
 const isLocked = ref(false);
 const hideOnlineStatus = ref(false);
 const noCrawle = ref(false);
 
 watch([isLocked, hideOnlineStatus, noCrawle], () => {
-	os.api('i/update', {
+	misskeyApi('i/update', {
 		isLocked: !!isLocked.value,
 		hideOnlineStatus: !!hideOnlineStatus.value,
 		noCrawle: !!noCrawle.value,
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts
index d2c6f7d479..c6088a5ae3 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts
+++ b/packages/frontend/src/components/MkUserSetupDialog.Profile.stories.impl.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue
index 37aa677b44..3194641cdb 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -39,7 +39,9 @@ import FormSlot from '@/components/form/slot.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import { chooseFileFromPc } from '@/scripts/select-file.js';
 import * as os from '@/os.js';
-import { $i } from '@/account.js';
+import { signinRequired } from '@/account.js';
+
+const $i = signinRequired();
 
 const name = ref($i.name ?? '');
 const description = ref($i.description ?? '');
@@ -68,7 +70,7 @@ function setAvatar(ev) {
 
 		const { canceled } = await os.confirm({
 			type: 'question',
-			text: i18n.t('cropImageAsk'),
+			text: i18n.ts.cropImageAsk,
 			okText: i18n.ts.cropYes,
 			cancelText: i18n.ts.cropNo,
 		});
diff --git a/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts
index 31176c0832..f0206e0cb4 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts
+++ b/packages/frontend/src/components/MkUserSetupDialog.User.stories.impl.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/components/MkUserSetupDialog.User.vue b/packages/frontend/src/components/MkUserSetupDialog.User.vue
index 621995cc5b..a4b9746f4b 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.User.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.User.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -29,7 +29,7 @@ import * as Misskey from 'misskey-js';
 import { ref } from 'vue';
 import MkButton from '@/components/MkButton.vue';
 import { i18n } from '@/i18n.js';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 
 const props = defineProps<{
 	user: Misskey.entities.UserDetailed;
@@ -39,7 +39,7 @@ const isFollowing = ref(false);
 
 async function follow() {
 	isFollowing.value = true;
-	os.api('following/create', {
+	misskeyApi('following/create', {
 		userId: props.user.id,
 	});
 }
@@ -59,8 +59,8 @@ async function follow() {
 	top: 30px;
 	left: 13px;
 	z-index: 2;
-	width: 58px;
-	height: 58px;
+	width: var(--avatar);
+	height: var(--avatar);
 	border: solid 4px var(--panel);
 }
 
diff --git a/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts b/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts
index 5182db12b2..3f5ae734bd 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts
+++ b/packages/frontend/src/components/MkUserSetupDialog.stories.impl.ts
@@ -1,11 +1,11 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
 import { StoryObj } from '@storybook/vue3';
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
 import { commonHandlers } from '../../.storybook/mocks.js';
 import { userDetailed } from '../../.storybook/fakes.js';
 import MkUserSetupDialog from './MkUserSetupDialog.vue';
@@ -38,17 +38,17 @@ export const Default = {
 		msw: {
 			handlers: [
 				...commonHandlers,
-				rest.post('/api/users', (req, res, ctx) => {
-					return res(ctx.json([
+				http.post('/api/users', () => {
+					return HttpResponse.json([
 						userDetailed('44'),
 						userDetailed('49'),
-					]));
+					]);
 				}),
-				rest.post('/api/pinned-users', (req, res, ctx) => {
-					return res(ctx.json([
+				http.post('/api/pinned-users', () => {
+					return HttpResponse.json([
 						userDetailed('44'),
 						userDetailed('49'),
-					]));
+					]);
 				}),
 			],
 		},
diff --git a/packages/frontend/src/components/MkUserSetupDialog.vue b/packages/frontend/src/components/MkUserSetupDialog.vue
index be945c1066..bd8949890c 100644
--- a/packages/frontend/src/components/MkUserSetupDialog.vue
+++ b/packages/frontend/src/components/MkUserSetupDialog.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -93,7 +93,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 						<div class="_gaps" style="text-align: center;">
 							<i class="ph-bell-ringing ph-bold ph-lg" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i>
 							<div style="font-size: 120%;">{{ i18n.ts.pushNotification }}</div>
-							<div style="padding: 0 16px;">{{ i18n.t('_initialAccountSetting.pushNotificationDescription', { name: instance.name ?? host }) }}</div>
+							<div style="padding: 0 16px;">{{ i18n.tsx._initialAccountSetting.pushNotificationDescription({ name: instance.name ?? host }) }}</div>
 							<MkPushNotificationAllowButton primary showOnlyToRegister style="margin: 0 auto;"/>
 							<div class="_buttonsCenter" style="margin-top: 16px;">
 								<MkButton rounded data-cy-user-setup-back @click="page--"><i class="ph-arrow-left ph-bold ph-lg"></i> {{ i18n.ts.goBack }}</MkButton>
@@ -110,7 +110,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 						<div class="_gaps" style="text-align: center;">
 							<i class="ph-check ph-bold ph-lg" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i>
 							<div style="font-size: 120%;">{{ i18n.ts._initialAccountSetting.initialAccountSettingCompleted }}</div>
-							<div>{{ i18n.t('_initialAccountSetting.youCanContinueTutorial', { name: instance.name ?? host }) }}</div>
+							<div>{{ i18n.tsx._initialAccountSetting.youCanContinueTutorial({ name: instance.name ?? host }) }}</div>
 							<div class="_buttonsCenter" style="margin-top: 16px;">
 								<MkButton rounded primary gradate data-cy-user-setup-continue @click="launchTutorial()">{{ i18n.ts._initialAccountSetting.startTutorial }} <i class="ph-arrow-right ph-bold ph-lg"></i></MkButton>
 							</div>
diff --git a/packages/frontend/src/components/MkUsersTooltip.vue b/packages/frontend/src/components/MkUsersTooltip.vue
index 37548952b6..054a503257 100644
--- a/packages/frontend/src/components/MkUsersTooltip.vue
+++ b/packages/frontend/src/components/MkUsersTooltip.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/MkVisibilityPicker.vue b/packages/frontend/src/components/MkVisibilityPicker.vue
index 61edc345a9..bd6edad663 100644
--- a/packages/frontend/src/components/MkVisibilityPicker.vue
+++ b/packages/frontend/src/components/MkVisibilityPicker.vue
@@ -1,29 +1,29 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<MkModal ref="modal" v-slot="{ type }" :zPriority="'high'" :src="src" @click="modal.close()" @closed="emit('closed')">
+<MkModal ref="modal" v-slot="{ type }" :zPriority="'high'" :src="src" @click="modal?.close()" @closed="emit('closed')">
 	<div class="_popup" :class="{ [$style.root]: true, [$style.asDrawer]: type === 'drawer' }">
 		<div :class="[$style.label, $style.item]">
 			{{ i18n.ts.visibility }}
 		</div>
-		<button key="public" :disabled="isSilenced" class="_button" :class="[$style.item, { [$style.active]: v === 'public' }]" data-index="1" @click="choose('public')">
+		<button key="public" :disabled="isSilenced || isReplyVisibilitySpecified" class="_button" :class="[$style.item, { [$style.active]: v === 'public' }]" data-index="1" @click="choose('public')">
 			<div :class="$style.icon"><i class="ph-globe-hemisphere-west ph-bold ph-lg"></i></div>
 			<div :class="$style.body">
 				<span :class="$style.itemTitle">{{ i18n.ts._visibility.public }}</span>
 				<span :class="$style.itemDescription">{{ i18n.ts._visibility.publicDescription }}</span>
 			</div>
 		</button>
-		<button key="home" class="_button" :class="[$style.item, { [$style.active]: v === 'home' }]" data-index="2" @click="choose('home')">
+		<button key="home" :disabled="isReplyVisibilitySpecified" class="_button" :class="[$style.item, { [$style.active]: v === 'home' }]" data-index="2" @click="choose('home')">
 			<div :class="$style.icon"><i class="ph-house ph-bold ph-lg"></i></div>
 			<div :class="$style.body">
 				<span :class="$style.itemTitle">{{ i18n.ts._visibility.home }}</span>
 				<span :class="$style.itemDescription">{{ i18n.ts._visibility.homeDescription }}</span>
 			</div>
 		</button>
-		<button key="followers" class="_button" :class="[$style.item, { [$style.active]: v === 'followers' }]" data-index="3" @click="choose('followers')">
+		<button key="followers" :disabled="isReplyVisibilitySpecified" class="_button" :class="[$style.item, { [$style.active]: v === 'followers' }]" data-index="3" @click="choose('followers')">
 			<div :class="$style.icon"><i class="ph-lock ph-bold ph-lg"></i></div>
 			<div :class="$style.body">
 				<span :class="$style.itemTitle">{{ i18n.ts._visibility.followers }}</span>
@@ -54,6 +54,7 @@ const props = withDefaults(defineProps<{
 	isSilenced: boolean;
 	localOnly: boolean;
 	src?: HTMLElement;
+	isReplyVisibilitySpecified?: boolean;
 }>(), {
 });
 
diff --git a/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue
index 746ed3e0de..cab42cd59d 100644
--- a/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue
+++ b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -13,11 +13,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { onMounted, shallowRef, ref } from 'vue';
+import { onMounted, shallowRef, ref, nextTick } from 'vue';
 import { Chart } from 'chart.js';
 import gradient from 'chartjs-plugin-gradient';
 import tinycolor from 'tinycolor2';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { defaultStore } from '@/store.js';
 import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
 import { chartVLine } from '@/scripts/chart-vline.js';
@@ -25,9 +25,9 @@ import { initChart } from '@/scripts/init-chart.js';
 
 initChart();
 
-const chartEl = shallowRef<HTMLCanvasElement>(null);
+const chartEl = shallowRef<HTMLCanvasElement | null>(null);
 const now = new Date();
-let chartInstance: Chart = null;
+let chartInstance: Chart | null = null;
 const chartLimit = 30;
 const fetching = ref(true);
 
@@ -53,7 +53,11 @@ async function renderChart() {
 		}));
 	};
 
-	const raw = await os.api('charts/active-users', { limit: chartLimit, span: 'day' });
+	const raw = await misskeyApi('charts/active-users', { limit: chartLimit, span: 'day' });
+
+	fetching.value = false;
+
+	await nextTick();
 
 	const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
 
@@ -65,6 +69,8 @@ async function renderChart() {
 
 	const max = Math.max(...raw.read);
 
+	if (chartEl.value == null) return;
+
 	chartInstance = new Chart(chartEl.value, {
 		type: 'bar',
 		data: {
@@ -97,7 +103,6 @@ async function renderChart() {
 					type: 'time',
 					offset: true,
 					time: {
-						stepSize: 1,
 						unit: 'day',
 						displayFormats: {
 							day: 'M/d',
@@ -108,6 +113,7 @@ async function renderChart() {
 						display: false,
 					},
 					ticks: {
+						stepSize: 1,
 						display: true,
 						maxRotation: 0,
 						autoSkipPadding: 8,
@@ -141,13 +147,10 @@ async function renderChart() {
 					},
 					external: externalTooltipHandler,
 				},
-				gradient,
 			},
 		},
 		plugins: [chartVLine(vLineColor)],
 	});
-
-	fetching.value = false;
 }
 
 onMounted(async () => {
diff --git a/packages/frontend/src/components/MkVisitorDashboard.vue b/packages/frontend/src/components/MkVisitorDashboard.vue
index 862a38bd54..d8e6ba9a09 100644
--- a/packages/frontend/src/components/MkVisitorDashboard.vue
+++ b/packages/frontend/src/components/MkVisitorDashboard.vue
@@ -1,12 +1,12 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
 <div v-if="meta" :class="$style.root">
 	<div :class="[$style.main, $style.panel]">
-		<img :src="instance.iconUrl || instance.faviconUrl || '/apple-touch-icon.png'" alt="" :class="$style.mainIcon"/>
+		<img :src="instance.iconUrl || '/apple-touch-icon.png'" alt="" :class="$style.mainIcon"/>
 		<button class="_button _acrylic" :class="$style.mainMenu" @click="showMenu"><i class="ph-dots-three ph-bold ph-lg"></i></button>
 		<div :class="$style.mainFg">
 			<h1 :class="$style.mainTitle">
@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</h1>
 			<div :class="$style.mainAbout">
 				<!-- eslint-disable-next-line vue/no-v-html -->
-				<div v-html="meta.description || i18n.ts.headlineMisskey"></div>
+				<div v-html="sanitizeHtml(meta.description) || i18n.ts.headlineMisskey"></div>
 			</div>
 			<div v-if="instance.disableRegistration" :class="$style.mainWarn">
 				<MkInfo warn>{{ i18n.ts.invitationRequiredToRegister }}</MkInfo>
@@ -56,6 +56,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { ref } from 'vue';
 import * as Misskey from 'misskey-js';
+import sanitizeHtml from 'sanitize-html';
 import XSigninDialog from '@/components/MkSigninDialog.vue';
 import XSignupDialog from '@/components/MkSignupDialog.vue';
 import MkButton from '@/components/MkButton.vue';
@@ -63,6 +64,7 @@ import MkTimeline from '@/components/MkTimeline.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import { instanceName } from '@/config.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { instance } from '@/instance.js';
 import MkNumber from '@/components/MkNumber.vue';
@@ -71,11 +73,11 @@ import XActiveUsersChart from '@/components/MkVisitorDashboard.ActiveUsersChart.
 const meta = ref<Misskey.entities.MetaResponse | null>(null);
 const stats = ref<Misskey.entities.StatsResponse | null>(null);
 
-os.api('meta', { detail: true }).then(_meta => {
+misskeyApi('meta', { detail: true }).then(_meta => {
 	meta.value = _meta;
 });
 
-os.api('stats', {}).then((res) => {
+misskeyApi('stats', {}).then((res) => {
 	stats.value = res;
 });
 
@@ -108,21 +110,27 @@ function showMenu(ev) {
 		text: i18n.ts.impressum,
 		icon: 'ph-newspaper-clipping ph-bold ph-lg',
 		action: () => {
-			window.open(instance.impressumUrl, '_blank', 'noopener');
+			window.open(instance.impressumUrl!, '_blank', 'noopener');
 		},
 	} : undefined, (instance.tosUrl) ? {
 		text: i18n.ts.termsOfService,
 		icon: 'ph-notebook ph-bold ph-lg',
 		action: () => {
-			window.open(instance.tosUrl, '_blank', 'noopener');
+			window.open(instance.tosUrl!, '_blank', 'noopener');
 		},
 	} : undefined, (instance.privacyPolicyUrl) ? {
 		text: i18n.ts.privacyPolicy,
 		icon: 'ph-shield ph-bold ph-lg',
 		action: () => {
-			window.open(instance.privacyPolicyUrl, '_blank', 'noopener');
+			window.open(instance.privacyPolicyUrl!, '_blank', 'noopener');
 		},
-	} : undefined, (!instance.impressumUrl && !instance.tosUrl && !instance.privacyPolicyUrl) ? undefined : { type: 'divider' }, {
+	} : undefined, (instance.donationUrl) ? {
+		text: i18n.ts.donation,
+		icon: 'ph-hand-coins ph-bold ph-lg',
+		action: () => {
+			window.open(instance.donationUrl, '_blank', 'noopener');
+		},
+	} : undefined, (!instance.impressumUrl && !instance.tosUrl && !instance.privacyPolicyUrl && !instance.donationUrl) ? undefined : { type: 'divider' }, {
 		text: i18n.ts.help,
 		icon: 'ph-question ph-bold ph-lg',
 		action: () => {
diff --git a/packages/frontend/src/components/MkWaitingDialog.vue b/packages/frontend/src/components/MkWaitingDialog.vue
index 28943efd1a..ad2105cc0b 100644
--- a/packages/frontend/src/components/MkWaitingDialog.vue
+++ b/packages/frontend/src/components/MkWaitingDialog.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -32,7 +32,7 @@ const emit = defineEmits<{
 
 function done() {
 	emit('done');
-	modal.value.close();
+	modal.value?.close();
 }
 
 watch(() => props.showing, () => {
diff --git a/packages/frontend/src/components/MkWidgets.vue b/packages/frontend/src/components/MkWidgets.vue
index bc1f33c43e..05a0f6e04e 100644
--- a/packages/frontend/src/components/MkWidgets.vue
+++ b/packages/frontend/src/components/MkWidgets.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<header :class="$style.editHeader">
 			<MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--margin)" data-cy-widget-select>
 				<template #label>{{ i18n.ts.selectWidget }}</template>
-				<option v-for="widget in widgetDefs" :key="widget" :value="widget">{{ i18n.t(`_widgets.${widget}`) }}</option>
+				<option v-for="widget in widgetDefs" :key="widget" :value="widget">{{ i18n.ts._widgets[widget] }}</option>
 			</MkSelect>
 			<MkButton inline primary data-cy-widget-add @click="addWidget"><i class="ph-plus ph-bold ph-lg"></i> {{ i18n.ts.add }}</MkButton>
 			<MkButton inline @click="$emit('exit')">{{ i18n.ts.close }}</MkButton>
@@ -104,19 +104,21 @@ const updateWidget = (id, data) => {
 };
 
 function onContextmenu(widget: Widget, ev: MouseEvent) {
-	const isLink = (el: HTMLElement) => {
+	const element = ev.target as HTMLElement | null;
+	const isLink = (el: HTMLElement): boolean => {
 		if (el.tagName === 'A') return true;
 		if (el.parentElement) {
 			return isLink(el.parentElement);
 		}
+		return false;
 	};
-	if (isLink(ev.target)) return;
-	if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return;
+	if (element && isLink(element)) return;
+	if (element && (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(element.tagName) || element.attributes['contenteditable'])) return;
 	if (window.getSelection()?.toString() !== '') return;
 
 	os.contextMenu([{
 		type: 'label',
-		text: i18n.t(`_widgets.${widget.name}`),
+		text: i18n.ts._widgets[widget.name],
 	}, {
 		icon: 'ph-gear ph-bold ph-lg',
 		text: i18n.ts.settings,
diff --git a/packages/frontend/src/components/MkWindow.vue b/packages/frontend/src/components/MkWindow.vue
index e5b8bd9b15..f13b53b005 100644
--- a/packages/frontend/src/components/MkWindow.vue
+++ b/packages/frontend/src/components/MkWindow.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -63,7 +63,7 @@ import { defaultStore } from '@/store.js';
 const minHeight = 50;
 const minWidth = 250;
 
-function dragListen(fn: (ev: MouseEvent) => void) {
+function dragListen(fn: (ev: MouseEvent | TouchEvent) => void) {
 	window.addEventListener('mousemove', fn);
 	window.addEventListener('touchmove', fn);
 	window.addEventListener('mouseleave', dragClear.bind(null, fn));
@@ -138,11 +138,12 @@ function onContextmenu(ev: MouseEvent) {
 // 最前面へ移動
 function top() {
 	if (rootEl.value) {
-		rootEl.value.style.zIndex = os.claimZIndex(props.front ? 'middle' : 'low');
+		rootEl.value.style.zIndex = os.claimZIndex(props.front ? 'middle' : 'low').toString();
 	}
 }
 
 function maximize() {
+	if (rootEl.value == null) return;
 	maximized.value = true;
 	unResizedTop = rootEl.value.style.top;
 	unResizedLeft = rootEl.value.style.left;
@@ -155,6 +156,7 @@ function maximize() {
 }
 
 function unMaximize() {
+	if (rootEl.value == null) return;
 	maximized.value = false;
 	rootEl.value.style.top = unResizedTop;
 	rootEl.value.style.left = unResizedLeft;
@@ -163,6 +165,7 @@ function unMaximize() {
 }
 
 function minimize() {
+	if (rootEl.value == null) return;
 	minimized.value = true;
 	unResizedWidth = rootEl.value.style.width;
 	unResizedHeight = rootEl.value.style.height;
@@ -171,8 +174,8 @@ function minimize() {
 }
 
 function unMinimize() {
+	if (rootEl.value == null) return;
 	const main = rootEl.value;
-	if (main == null) return;
 
 	minimized.value = false;
 	rootEl.value.style.width = unResizedWidth;
@@ -199,9 +202,17 @@ function onDblClick() {
 	}
 }
 
-function onHeaderMousedown(evt: MouseEvent) {
+function getPositionX(event: MouseEvent | TouchEvent) {
+	return 'touches' in event && event.touches.length > 0 ? event.touches[0].clientX : 'clientX' in event ? event.clientX : 0;
+}
+
+function getPositionY(event: MouseEvent | TouchEvent) {
+	return 'touches' in event && event.touches.length > 0 ? event.touches[0].clientY : 'clientY' in event ? event.clientY : 0;
+}
+
+function onHeaderMousedown(evt: MouseEvent | TouchEvent) {
 	// 右クリックはコンテキストメニューを開こうとした可能性が高いため無視
-	if (evt.button === 2) return;
+	if ('button' in evt && evt.button === 2) return;
 
 	let beforeMaximized = false;
 
@@ -226,8 +237,8 @@ function onHeaderMousedown(evt: MouseEvent) {
 
 	const position = main.getBoundingClientRect();
 
-	const clickX = evt.touches && evt.touches.length > 0 ? evt.touches[0].clientX : evt.clientX;
-	const clickY = evt.touches && evt.touches.length > 0 ? evt.touches[0].clientY : evt.clientY;
+	const clickX = getPositionX(evt);
+	const clickY = getPositionY(evt);
 	const moveBaseX = beforeMaximized ? parseInt(unResizedWidth, 10) / 2 : clickX - position.left; // TODO: parseIntやめる
 	const moveBaseY = beforeMaximized ? 20 : clickY - position.top;
 	const browserWidth = window.innerWidth;
@@ -251,8 +262,10 @@ function onHeaderMousedown(evt: MouseEvent) {
 		// 右はみ出し
 		if (moveLeft + windowWidth > browserWidth) moveLeft = browserWidth - windowWidth;
 
-		rootEl.value.style.left = moveLeft + 'px';
-		rootEl.value.style.top = moveTop + 'px';
+		if (rootEl.value) {
+			rootEl.value.style.left = moveLeft + 'px';
+			rootEl.value.style.top = moveTop + 'px';
+		}
 	}
 
 	if (beforeMaximized) {
@@ -261,26 +274,26 @@ function onHeaderMousedown(evt: MouseEvent) {
 
 	// 動かした時
 	dragListen(me => {
-		const x = me.touches && me.touches.length > 0 ? me.touches[0].clientX : me.clientX;
-		const y = me.touches && me.touches.length > 0 ? me.touches[0].clientY : me.clientY;
+		const x = getPositionX(me);
+		const y = getPositionY(me);
 
 		move(x, y);
 	});
 }
 
 // 上ハンドル掴み時
-function onTopHandleMousedown(evt) {
+function onTopHandleMousedown(evt: MouseEvent | TouchEvent) {
 	const main = rootEl.value;
 	// どういうわけかnullになることがある
 	if (main == null) return;
 
-	const base = evt.clientY;
+	const base = getPositionY(evt);
 	const height = parseInt(getComputedStyle(main, '').height, 10);
 	const top = parseInt(getComputedStyle(main, '').top, 10);
 
 	// 動かした時
 	dragListen(me => {
-		const move = me.clientY - base;
+		const move = getPositionY(me) - base;
 		if (top + move > 0) {
 			if (height + -move > minHeight) {
 				applyTransformHeight(height + -move);
@@ -297,18 +310,18 @@ function onTopHandleMousedown(evt) {
 }
 
 // 右ハンドル掴み時
-function onRightHandleMousedown(evt) {
+function onRightHandleMousedown(evt: MouseEvent | TouchEvent) {
 	const main = rootEl.value;
 	if (main == null) return;
 
-	const base = evt.clientX;
+	const base = getPositionX(evt);
 	const width = parseInt(getComputedStyle(main, '').width, 10);
 	const left = parseInt(getComputedStyle(main, '').left, 10);
 	const browserWidth = window.innerWidth;
 
 	// 動かした時
 	dragListen(me => {
-		const move = me.clientX - base;
+		const move = getPositionX(me) - base;
 		if (left + width + move < browserWidth) {
 			if (width + move > minWidth) {
 				applyTransformWidth(width + move);
@@ -322,18 +335,18 @@ function onRightHandleMousedown(evt) {
 }
 
 // 下ハンドル掴み時
-function onBottomHandleMousedown(evt) {
+function onBottomHandleMousedown(evt: MouseEvent | TouchEvent) {
 	const main = rootEl.value;
 	if (main == null) return;
 
-	const base = evt.clientY;
+	const base = getPositionY(evt);
 	const height = parseInt(getComputedStyle(main, '').height, 10);
 	const top = parseInt(getComputedStyle(main, '').top, 10);
 	const browserHeight = window.innerHeight;
 
 	// 動かした時
 	dragListen(me => {
-		const move = me.clientY - base;
+		const move = getPositionY(me) - base;
 		if (top + height + move < browserHeight) {
 			if (height + move > minHeight) {
 				applyTransformHeight(height + move);
@@ -347,17 +360,17 @@ function onBottomHandleMousedown(evt) {
 }
 
 // 左ハンドル掴み時
-function onLeftHandleMousedown(evt) {
+function onLeftHandleMousedown(evt: MouseEvent | TouchEvent) {
 	const main = rootEl.value;
 	if (main == null) return;
 
-	const base = evt.clientX;
+	const base = getPositionX(evt);
 	const width = parseInt(getComputedStyle(main, '').width, 10);
 	const left = parseInt(getComputedStyle(main, '').left, 10);
 
 	// 動かした時
 	dragListen(me => {
-		const move = me.clientX - base;
+		const move = getPositionX(me) - base;
 		if (left + move > 0) {
 			if (width + -move > minWidth) {
 				applyTransformWidth(width + -move);
@@ -374,25 +387,25 @@ function onLeftHandleMousedown(evt) {
 }
 
 // 左上ハンドル掴み時
-function onTopLeftHandleMousedown(evt) {
+function onTopLeftHandleMousedown(evt: MouseEvent | TouchEvent) {
 	onTopHandleMousedown(evt);
 	onLeftHandleMousedown(evt);
 }
 
 // 右上ハンドル掴み時
-function onTopRightHandleMousedown(evt) {
+function onTopRightHandleMousedown(evt: MouseEvent | TouchEvent) {
 	onTopHandleMousedown(evt);
 	onRightHandleMousedown(evt);
 }
 
 // 右下ハンドル掴み時
-function onBottomRightHandleMousedown(evt) {
+function onBottomRightHandleMousedown(evt: MouseEvent | TouchEvent) {
 	onBottomHandleMousedown(evt);
 	onRightHandleMousedown(evt);
 }
 
 // 左下ハンドル掴み時
-function onBottomLeftHandleMousedown(evt) {
+function onBottomLeftHandleMousedown(evt: MouseEvent | TouchEvent) {
 	onBottomHandleMousedown(evt);
 	onLeftHandleMousedown(evt);
 }
@@ -400,23 +413,23 @@ function onBottomLeftHandleMousedown(evt) {
 // 高さを適用
 function applyTransformHeight(height) {
 	if (height > window.innerHeight) height = window.innerHeight;
-	rootEl.value.style.height = height + 'px';
+	if (rootEl.value) rootEl.value.style.height = height + 'px';
 }
 
 // 幅を適用
 function applyTransformWidth(width) {
 	if (width > window.innerWidth) width = window.innerWidth;
-	rootEl.value.style.width = width + 'px';
+	if (rootEl.value) rootEl.value.style.width = width + 'px';
 }
 
 // Y座標を適用
 function applyTransformTop(top) {
-	rootEl.value.style.top = top + 'px';
+	if (rootEl.value) rootEl.value.style.top = top + 'px';
 }
 
 // X座標を適用
 function applyTransformLeft(left) {
-	rootEl.value.style.left = left + 'px';
+	if (rootEl.value) rootEl.value.style.left = left + 'px';
 }
 
 function onBrowserResize() {
@@ -438,8 +451,10 @@ onMounted(() => {
 	applyTransformWidth(props.initialWidth);
 	if (props.initialHeight) applyTransformHeight(props.initialHeight);
 
-	applyTransformTop((window.innerHeight / 2) - (rootEl.value.offsetHeight / 2));
-	applyTransformLeft((window.innerWidth / 2) - (rootEl.value.offsetWidth / 2));
+	if (rootEl.value) {
+		applyTransformTop((window.innerHeight / 2) - (rootEl.value.offsetHeight / 2));
+		applyTransformLeft((window.innerWidth / 2) - (rootEl.value.offsetWidth / 2));
+	}
 
 	// 他のウィンドウ内のボタンなどを押してこのウィンドウが開かれた場合、親が最前面になろうとするのでそれに隠されないようにする
 	top();
diff --git a/packages/frontend/src/components/MkYouTubePlayer.vue b/packages/frontend/src/components/MkYouTubePlayer.vue
index a9b2e8a00d..3ad2a95bc3 100644
--- a/packages/frontend/src/components/MkYouTubePlayer.vue
+++ b/packages/frontend/src/components/MkYouTubePlayer.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -39,7 +39,7 @@ if (!['http:', 'https:'].includes(requestUrl.protocol)) throw new Error('invalid
 const fetching = ref(true);
 const title = ref<string | null>(null);
 const player = ref({
-	url: null,
+	url: null as string | null,
 	width: null,
 	height: null,
 });
diff --git a/packages/frontend/src/components/SkApprovalUser.vue b/packages/frontend/src/components/SkApprovalUser.vue
index 2bf6361ac8..f85944cd04 100644
--- a/packages/frontend/src/components/SkApprovalUser.vue
+++ b/packages/frontend/src/components/SkApprovalUser.vue
@@ -33,6 +33,7 @@ import MkFolder from '@/components/MkFolder.vue';
 import MkButton from '@/components/MkButton.vue';
 import { i18n } from '@/i18n.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 
 const props = defineProps<{
 	user: Misskey.entities.User;
@@ -42,7 +43,7 @@ let reason = ref('');
 let email = ref('');
 
 function getReason() {
-	return os.api('admin/show-user', {
+	return misskeyApi('admin/show-user', {
 		userId: props.user.id,
 	}).then(info => {
 		reason.value = info?.signupReason;
@@ -87,7 +88,7 @@ async function approveAccount() {
 		text: i18n.ts.approveConfirm,
 	});
 	if (confirm.canceled) return;
-	await os.api('admin/approve-user', { userId: props.user.id });
+	await misskeyApi('admin/approve-user', { userId: props.user.id });
 	emits('deleted', props.user.id);
 }
 </script>
diff --git a/packages/frontend/src/components/SkInstanceTicker.vue b/packages/frontend/src/components/SkInstanceTicker.vue
index d69e5fecec..9cfc332698 100644
--- a/packages/frontend/src/components/SkInstanceTicker.vue
+++ b/packages/frontend/src/components/SkInstanceTicker.vue
@@ -46,11 +46,22 @@ const bg = {
 	align-items: center;
 	height: 1.5ex;
 	border-radius: var(--radius-xl);
-	margin-top: 5px;
 	padding: 4px;
 	overflow: clip;
 	color: #fff;
-	text-shadow: -1px -1px 0 #000,1px -1px 0 #000,-1px 1px 0 #000,1px 1px 0 #000;
+	text-shadow: /* .866 ≈ sin(60deg) */
+		1px 0 1px #000,
+		.866px .5px 1px #000,
+		.5px .866px 1px #000,
+		0 1px 1px #000,
+		-.5px .866px 1px #000,
+		-.866px .5px 1px #000,
+		-1px 0 1px #000,
+		-.866px -.5px 1px #000,
+		-.5px -.866px 1px #000,
+		0 -1px 1px #000,
+		.5px -.866px 1px #000,
+		.866px -.5px 1px #000;
 }
 
 .icon {
@@ -59,7 +70,9 @@ const bg = {
 }
 
 .name {
-	margin-left: 4px;
+	padding: 0.5ex;
+	margin: -0.5ex;
+	margin-left: calc(4px - 0.5ex);
 	line-height: 1;
 	font-size: 0.8em;
 	font-weight: bold;
diff --git a/packages/frontend/src/components/SkNote.vue b/packages/frontend/src/components/SkNote.vue
index 83909654c7..09decad1a2 100644
--- a/packages/frontend/src/components/SkNote.vue
+++ b/packages/frontend/src/components/SkNote.vue
@@ -5,9 +5,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <template>
 <div
-	v-if="!hardMuted && !muted"
+	v-if="!hardMuted && muted === false"
 	v-show="!isDeleted"
-	ref="el"
+	ref="rootEl"
 	v-hotkey="keymap"
 	:class="[$style.root, { [$style.showActionsOnlyHover]: defaultStore.state.showNoteActionsOnlyHover }]"
 	:tabindex="!isDeleted ? '-1' : undefined"
@@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</span>
 			<span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ph-rocket ph-bold ph-lg"></i></span>
 			<span v-if="note.channel" style="margin-left: 0.5em;" :title="note.channel.name"><i class="ph-television ph-bold ph-lg"></i></span>
-			<span v-if="note.updatedAt" ref="menuVersionsButton" style="margin-left: 0.5em;" title="Edited" @mousedown="menuVersions()"><i class="ph-pencil ph-bold ph-lg"></i></span>
+			<span v-if="note.updatedAt" ref="menuVersionsButton" style="margin-left: 0.5em;" title="Edited" @mousedown="menuVersions()"><i class="ph-pencil-simple ph-bold ph-lg"></i></span>
 		</div>
 	</div>
 	<div v-if="renoteCollapsed" :class="$style.collapsedRenoteTarget">
@@ -54,7 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<SkNoteHeader :note="appearNote" :mini="true"/>
 			</div>
 		</div>
-		<div :class="[{ [$style.clickToOpen]: defaultStore.state.clickToOpen }]" @click="defaultStore.state.clickToOpen ? noteclick(appearNote.id) : undefined">
+		<div :class="[{ [$style.clickToOpen]: defaultStore.state.clickToOpen }]" @click.stop="defaultStore.state.clickToOpen ? noteclick(appearNote.id) : undefined">
 			<div style="container-type: inline-size;">
 				<p v-if="appearNote.cw != null" :class="$style.cw">
 					<Mfm v-if="appearNote.cw != ''" style="margin-right: 8px;" :text="appearNote.cw" :author="appearNote.user" :nyaize="'respect'"/>
@@ -76,18 +76,18 @@ SPDX-License-Identifier: AGPL-3.0-only
 						/>
 						<div v-if="translating || translation" :class="$style.translation">
 							<MkLoading v-if="translating" mini/>
-							<div v-else>
-								<b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b>
+							<div v-else-if="translation">
+								<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
 								<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
 							</div>
 						</div>
 						<MkButton v-if="!allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-play ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.play }}</MkButton>
 						<MkButton v-else-if="!defaultStore.state.animatedMfm && allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-stop ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.stop }}</MkButton>
 					</div>
-					<div v-if="appearNote.files.length > 0">
+					<div v-if="appearNote.files && appearNote.files.length > 0">
 						<MkMediaList :mediaList="appearNote.files" @click.stop/>
 					</div>
-					<MkPoll v-if="appearNote.poll" :note="appearNote" :class="$style.poll" @click.stop/>
+					<MkPoll v-if="appearNote.poll" :noteId="appearNote.id" :poll="appearNote.poll" :class="$style.poll" @click.stop/>
 					<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" :class="$style.urlPreview" @click.stop/>
 					<div v-if="appearNote.renote" :class="$style.quote"><SkNoteSimple :note="appearNote.renote" :class="$style.quoteNote"/></div>
 					<button v-if="isLong && collapsed" :class="$style.collapsed" class="_button" @click.stop @click="collapsed = false">
@@ -147,7 +147,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" :class="$style.footerButton" class="_button" @mousedown="clip()">
 					<i class="ph-paperclip ph-bold ph-lg"></i>
 				</button>
-				<button ref="menuButton" :class="$style.footerButton" class="_button" @mousedown="menu()">
+				<button ref="menuButton" :class="$style.footerButton" class="_button" @mousedown="showMenu()">
 					<i class="ph-dots-three ph-bold ph-lg"></i>
 				</button>
 			</footer>
@@ -155,7 +155,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 	</article>
 </div>
 <div v-else-if="!hardMuted" :class="$style.muted" @click="muted = false">
-	<I18n :src="i18n.ts.userSaysSomething" tag="small">
+	<I18n v-if="muted === 'sensitiveMute'" :src="i18n.ts.userSaysSomethingSensitive" tag="small">
+		<template #name>
+			<MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)">
+				<MkUserName :user="appearNote.user"/>
+			</MkA>
+		</template>
+	</I18n>
+	<I18n v-else :src="i18n.ts.userSaysSomething" tag="small">
 		<template #name>
 			<MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)">
 				<MkUserName :user="appearNote.user"/>
@@ -173,7 +180,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { computed, inject, onMounted, ref, shallowRef, Ref, watch, provide } from 'vue';
-import * as mfm from '@sharkey/sfm-js';
+import * as mfm from '@transfem-org/sfm-js';
 import * as Misskey from 'misskey-js';
 import SkNoteSub from '@/components/SkNoteSub.vue';
 import SkNoteHeader from '@/components/SkNoteHeader.vue';
@@ -190,6 +197,7 @@ import { focusPrev, focusNext } from '@/scripts/focus.js';
 import { checkWordMute } from '@/scripts/check-word-mute.js';
 import { userPage } from '@/filters/user.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import * as sound from '@/scripts/sound.js';
 import { defaultStore, noteViewInterruptors } from '@/store.js';
 import { reactionPicker } from '@/scripts/reaction-picker.js';
@@ -208,7 +216,8 @@ import { MenuItem } from '@/types/menu.js';
 import MkRippleEffect from '@/components/MkRippleEffect.vue';
 import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
 import { shouldCollapsed } from '@/scripts/collapsed.js';
-import { useRouter } from '@/router.js';
+import { useRouter } from '@/router/supplier.js';
+import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js';
 
 const props = withDefaults(defineProps<{
 	note: Misskey.entities.Note;
@@ -228,6 +237,7 @@ const emit = defineEmits<{
 
 const router = useRouter();
 
+const inTimeline = inject<boolean>('inTimeline', false);
 const inChannel = inject('inChannel', null);
 const currentClip = inject<Ref<Misskey.entities.Clip> | null>('currentClip', null);
 
@@ -246,7 +256,7 @@ if (noteViewInterruptors.length > 0) {
 		let result: Misskey.entities.Note | null = deepClone(note.value);
 		for (const interruptor of noteViewInterruptors) {
 			try {
-				result = await interruptor.handler(result);
+				result = await interruptor.handler(result!) as Misskey.entities.Note | null;
 				if (result === null) {
 					isDeleted.value = true;
 					return;
@@ -255,7 +265,7 @@ if (noteViewInterruptors.length > 0) {
 				console.error(err);
 			}
 		}
-		note.value = result;
+		note.value = result as Misskey.entities.Note;
 	});
 }
 
@@ -263,11 +273,11 @@ const isRenote = (
 	note.value.renote != null &&
 	note.value.text == null &&
 	note.value.cw == null &&
-	note.value.fileIds.length === 0 &&
+	note.value.fileIds && note.value.fileIds.length === 0 &&
 	note.value.poll == null
 );
 
-const el = shallowRef<HTMLElement>();
+const rootEl = shallowRef<HTMLElement>();
 const menuButton = shallowRef<HTMLElement>();
 const menuVersionsButton = shallowRef<HTMLElement>();
 const renoteButton = shallowRef<HTMLElement>();
@@ -277,50 +287,61 @@ const quoteButton = shallowRef<HTMLElement>();
 const clipButton = shallowRef<HTMLElement>();
 const likeButton = shallowRef<HTMLElement>();
 const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value);
-const renoteUrl = appearNote.value.renote ? appearNote.value.renote.url : null;
-const renoteUri = appearNote.value.renote ? appearNote.value.renote.uri : null;
 
 const isMyRenote = $i && ($i.id === note.value.userId);
 const showContent = ref(defaultStore.state.uncollapseCW);
-const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text).filter(u => u !== renoteUrl && u !== renoteUri) : null);
-const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value) : null);
+const parsed = computed(() => appearNote.value.text ? mfm.parse(appearNote.value.text) : null);
+const urls = computed(() => parsed.value ? extractUrlFromMfm(parsed.value).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null);
 const isLong = shouldCollapsed(appearNote.value, urls.value ?? []);
-const collapsed = defaultStore.state.expandLongNote && appearNote.value.cw == null ? false : ref(appearNote.value.cw == null && isLong);
+const collapsed = ref(defaultStore.state.expandLongNote && appearNote.value.cw == null && isLong ? false : appearNote.value.cw == null && isLong);
 const isDeleted = ref(false);
 const renoted = ref(false);
 const muted = ref(checkMute(appearNote.value, $i?.mutedWords));
-const hardMuted = ref(props.withHardMute && checkMute(appearNote.value, $i?.hardMutedWords));
-const translation = ref<any>(null);
+const hardMuted = ref(props.withHardMute && checkMute(appearNote.value, $i?.hardMutedWords, true));
+const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null);
 const translating = ref(false);
 const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance);
-const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || (appearNote.value.visibility === 'followers' && appearNote.value.userId === $i.id));
-const renoteCollapsed = ref(defaultStore.state.collapseRenotes && isRenote && (($i && ($i.id === note.value.userId || $i.id === appearNote.value.userId)) || (appearNote.value.myReaction != null)));
+const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || (appearNote.value.visibility === 'followers' && appearNote.value.userId === $i?.id));
+const renoteCollapsed = ref(
+	defaultStore.state.collapseRenotes && isRenote && (
+		($i && ($i.id === note.value.userId || $i.id === appearNote.value.userId)) || // `||` must be `||`! See https://github.com/misskey-dev/misskey/issues/13131
+		(appearNote.value.myReaction != null)
+	)
+);
 const defaultLike = computed(() => defaultStore.state.like ? defaultStore.state.like : null);
 const animated = computed(() => parsed.value ? checkAnimationFromMfm(parsed.value) : null);
 const allowAnim = ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false);
 
-function checkMute(note: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null): boolean {
+/* Overload FunctionにLintが対応していないのでコメントアウト
+function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: true): boolean;
+function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: false): boolean | 'sensitiveMute';
+*/
+function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly = false): boolean | 'sensitiveMute' {
 	if (mutedWords == null) return false;
 
-	if (checkWordMute(note, $i, mutedWords)) return true;
-	if (note.reply && checkWordMute(note.reply, $i, mutedWords)) return true;
-	if (note.renote && checkWordMute(note.renote, $i, mutedWords)) return true;
+	if (checkWordMute(noteToCheck, $i, mutedWords)) return true;
+	if (noteToCheck.reply && checkWordMute(noteToCheck.reply, $i, mutedWords)) return true;
+	if (noteToCheck.renote && checkWordMute(noteToCheck.renote, $i, mutedWords)) return true;
+
+	if (checkOnly) return false;
+
+	if (inTimeline && !defaultStore.state.tl.filter.withSensitive && noteToCheck.files?.some((v) => v.isSensitive)) return 'sensitiveMute';
 	return false;
 }
 
 const keymap = {
 	'r': () => reply(true),
 	'e|a|plus': () => react(true),
-	'q': () => renoteButton.value.renote(true),
+	'q': () => renote(appearNote.value.visibility),
 	'up|k|shift+tab': focusBefore,
 	'down|j|tab': focusAfter,
 	'esc': blur,
-	'm|o': () => menu(true),
+	'm|o': () => showMenu(true),
 	's': () => showContent.value !== showContent.value,
 };
 
 provide('react', (reaction: string) => {
-	os.api('notes/reactions/create', {
+	misskeyApi('notes/reactions/create', {
 		noteId: appearNote.value.id,
 		reaction: reaction,
 	});
@@ -332,7 +353,7 @@ if (props.mock) {
 	}, { deep: true });
 } else {
 	useNoteCapture({
-		rootEl: el,
+		rootEl: rootEl,
 		note: appearNote,
 		pureNote: note,
 		isDeletedRef: isDeleted,
@@ -341,7 +362,7 @@ if (props.mock) {
 
 if (!props.mock) {
 	useTooltip(renoteButton, async (showing) => {
-		const renotes = await os.api('notes/renotes', {
+		const renotes = await misskeyApi('notes/renotes', {
 			noteId: appearNote.value.id,
 			limit: 11,
 		});
@@ -359,7 +380,7 @@ if (!props.mock) {
 	});
 
 	useTooltip(quoteButton, async (showing) => {
-		const renotes = await os.api('notes/renotes', {
+		const renotes = await misskeyApi('notes/renotes', {
 			noteId: appearNote.value.id,
 			limit: 11,
 			quote: true,
@@ -378,7 +399,7 @@ if (!props.mock) {
 	});
 
 	if ($i) {
-		os.api('notes/renotes', {
+		misskeyApi('notes/renotes', {
 			noteId: appearNote.value.id,
 			userId: $i.id,
 			limit: 1,
@@ -388,54 +409,15 @@ if (!props.mock) {
 	}
 }
 
-type Visibility = 'public' | 'home' | 'followers' | 'specified';
-
-// defaultStore.state.visibilityがstringなためstringも受け付けている
-function smallerVisibility(a: Visibility | string, b: Visibility | string): Visibility {
-	if (a === 'specified' || b === 'specified') return 'specified';
-	if (a === 'followers' || b === 'followers') return 'followers';
-	if (a === 'home' || b === 'home') return 'home';
-	// if (a === 'public' || b === 'public')
-	return 'public';
-}
-
 function boostVisibility() {
-	os.popupMenu([
-		{
-			type: 'button',
-			icon: 'ph-globe-hemisphere-west ph-bold ph-lg',
-			text: i18n.ts._visibility['public'],
-			action: () => {
-				renote('public');
-			},
-		},
-		{
-			type: 'button',
-			icon: 'ph-house ph-bold ph-lg',
-			text: i18n.ts._visibility['home'],
-			action: () => {
-				renote('home');
-			},
-		},
-		{
-			type: 'button',
-			icon: 'ph-lock ph-bold ph-lg',
-			text: i18n.ts._visibility['followers'],
-			action: () => {
-				renote('followers');
-			},
-		},
-		{
-			type: 'button',
-			icon: 'ph-planet ph-bold ph-lg',
-			text: i18n.ts._timelines.local,
-			action: () => {
-				renote('local');
-			},
-		}], renoteButton.value);
+	if (!defaultStore.state.showVisibilitySelectorOnBoost) {
+		renote(defaultStore.state.visibilityOnBoost);
+	} else {
+		os.popupMenu(boostMenuItems(appearNote, renote), renoteButton.value);
+	}
 }
 
-function renote(visibility: Visibility | 'local') {
+function renote(visibility: Visibility, localOnly: boolean = false) {
 	pleaseLogin();
 	showMovedDialog();
 
@@ -449,7 +431,7 @@ function renote(visibility: Visibility | 'local') {
 		}
 
 		if (!props.mock) {
-			os.api('notes/create', {
+			misskeyApi('notes/create', {
 				renoteId: appearNote.value.id,
 				channelId: appearNote.value.channelId,
 			}).then(() => {
@@ -457,7 +439,7 @@ function renote(visibility: Visibility | 'local') {
 				renoted.value = true;
 			});
 		}
-	} else if (!appearNote.value.channel || appearNote.value.channel?.allowRenoteToExternal) {
+	} else if (!appearNote.value.channel || appearNote.value.channel.allowRenoteToExternal) {
 		const el = renoteButton.value as HTMLElement | null | undefined;
 		if (el) {
 			const rect = el.getBoundingClientRect();
@@ -466,18 +448,10 @@ function renote(visibility: Visibility | 'local') {
 			os.popup(MkRippleEffect, { x, y }, {}, 'end');
 		}
 
-		const configuredVisibility = defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility;
-		const localOnlySetting = defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly;
-
-		let noteVisibility = visibility === 'local' || visibility === 'specified' ? smallerVisibility(appearNote.value.visibility, configuredVisibility) : smallerVisibility(visibility, configuredVisibility);
-		if (appearNote.value.channel?.isSensitive) {
-			noteVisibility = smallerVisibility(visibility === 'local' || visibility === 'specified' ? appearNote.value.visibility : visibility, 'home');
-		}
-
 		if (!props.mock) {
-			os.api('notes/create', {
-				localOnly: visibility === 'local' ? true : localOnlySetting,
-				visibility: noteVisibility,
+			misskeyApi('notes/create', {
+				localOnly: localOnly,
+				visibility: visibility,
 				renoteId: appearNote.value.id,
 			}).then(() => {
 				os.toast(i18n.ts.renoted);
@@ -499,9 +473,9 @@ function quote() {
 			renote: appearNote.value,
 			channel: appearNote.value.channel,
 		}).then(() => {
-			os.api('notes/renotes', {
+			misskeyApi('notes/renotes', {
 				noteId: appearNote.value.id,
-				userId: $i.id,
+				userId: $i?.id,
 				limit: 1,
 				quote: true,
 			}).then((res) => {
@@ -521,9 +495,9 @@ function quote() {
 		os.post({
 			renote: appearNote.value,
 		}).then(() => {
-			os.api('notes/renotes', {
+			misskeyApi('notes/renotes', {
 				noteId: appearNote.value.id,
-				userId: $i.id,
+				userId: $i?.id,
 				limit: 1,
 				quote: true,
 			}).then((res) => {
@@ -551,7 +525,7 @@ function reply(viaKeyboard = false): void {
 		reply: appearNote.value,
 		channel: appearNote.value.channel,
 		animation: !viaKeyboard,
-	}, () => {
+	}).then(() => {
 		focus();
 	});
 }
@@ -559,10 +533,11 @@ function reply(viaKeyboard = false): void {
 function like(): void {
 	pleaseLogin();
 	showMovedDialog();
+	sound.playMisskeySfx('reaction');
 	if (props.mock) {
 		return;
 	}
-	os.api('notes/like', {
+	misskeyApi('notes/like', {
 		noteId: appearNote.value.id,
 		override: defaultLike.value,
 	});
@@ -579,17 +554,17 @@ function react(viaKeyboard = false): void {
 	pleaseLogin();
 	showMovedDialog();
 	if (appearNote.value.reactionAcceptance === 'likeOnly') {
-		sound.play('reaction');
+		sound.playMisskeySfx('reaction');
 
 		if (props.mock) {
 			return;
 		}
 
-		os.api('notes/like', {
+		misskeyApi('notes/like', {
 			noteId: appearNote.value.id,
 			override: defaultLike.value,
 		});
-		const el = reactButton.value as HTMLElement | null | undefined;
+		const el = reactButton.value;
 		if (el) {
 			const rect = el.getBoundingClientRect();
 			const x = rect.left + (el.offsetWidth / 2);
@@ -598,15 +573,15 @@ function react(viaKeyboard = false): void {
 		}
 	} else {
 		blur();
-		reactionPicker.show(reactButton.value, reaction => {
-			sound.play('reaction');
+		reactionPicker.show(reactButton.value ?? null, note.value, reaction => {
+			sound.playMisskeySfx('reaction');
 
 			if (props.mock) {
 				emit('reaction', reaction);
 				return;
 			}
 
-			os.api('notes/reactions/create', {
+			misskeyApi('notes/reactions/create', {
 				noteId: appearNote.value.id,
 				reaction: reaction,
 			});
@@ -619,8 +594,8 @@ function react(viaKeyboard = false): void {
 	}
 }
 
-function undoReact(note): void {
-	const oldReaction = note.myReaction;
+function undoReact(targetNote: Misskey.entities.Note): void {
+	const oldReaction = targetNote.myReaction;
 	if (!oldReaction) return;
 
 	if (props.mock) {
@@ -628,8 +603,8 @@ function undoReact(note): void {
 		return;
 	}
 
-	os.api('notes/reactions/delete', {
-		noteId: note.id,
+	misskeyApi('notes/reactions/delete', {
+		noteId: targetNote.id,
 	});
 }
 
@@ -637,7 +612,7 @@ function undoRenote(note) : void {
 	if (props.mock) {
 		return;
 	}
-	os.api('notes/unrenote', {
+	misskeyApi('notes/unrenote', {
 		noteId: note.id,
 	});
 	os.toast(i18n.ts.rmboost);
@@ -657,32 +632,34 @@ function onContextmenu(ev: MouseEvent): void {
 		return;
 	}
 
-	const isLink = (el: HTMLElement) => {
+	const isLink = (el: HTMLElement): boolean => {
 		if (el.tagName === 'A') return true;
 		// 再生速度の選択などのために、Audio要素のコンテキストメニューはブラウザデフォルトとする。
 		if (el.tagName === 'AUDIO') return true;
 		if (el.parentElement) {
 			return isLink(el.parentElement);
 		}
+		return false;
 	};
-	if (isLink(ev.target)) return;
-	if (window.getSelection().toString() !== '') return;
+
+	if (ev.target && isLink(ev.target as HTMLElement)) return;
+	if (window.getSelection()?.toString() !== '') return;
 
 	if (defaultStore.state.useReactionPickerForContextMenu) {
 		ev.preventDefault();
 		react();
 	} else {
-		const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, menuButton, isDeleted, currentClip: currentClip?.value });
+		const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value });
 		os.contextMenu(menu, ev).then(focus).finally(cleanup);
 	}
 }
 
-function menu(viaKeyboard = false): void {
+function showMenu(viaKeyboard = false): void {
 	if (props.mock) {
 		return;
 	}
 
-	const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, menuButton, isDeleted, currentClip: currentClip?.value });
+	const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted, currentClip: currentClip?.value });
 	os.popupMenu(menu, menuButton.value, {
 		viaKeyboard,
 	}).then(focus).finally(cleanup);
@@ -714,7 +691,7 @@ function showRenoteMenu(viaKeyboard = false): void {
 			icon: 'ph-trash ph-bold ph-lg',
 			danger: true,
 			action: () => {
-				os.api('notes/delete', {
+				misskeyApi('notes/delete', {
 					noteId: note.value.id,
 				});
 				isDeleted.value = true;
@@ -736,7 +713,7 @@ function showRenoteMenu(viaKeyboard = false): void {
 			getCopyNoteLinkMenu(note.value, i18n.ts.copyLinkRenote),
 			{ type: 'divider' },
 			getAbuseNoteMenu(note.value, i18n.ts.reportAbuseRenote),
-			$i.isModerator || $i.isAdmin ? getUnrenote() : undefined,
+			($i?.isModerator || $i?.isAdmin) ? getUnrenote() : undefined,
 		], renoteTime.value, {
 			viaKeyboard: viaKeyboard,
 		});
@@ -756,23 +733,23 @@ function animatedMFM() {
 }
 
 function focus() {
-	el.value.focus();
+	rootEl.value?.focus();
 }
 
 function blur() {
-	el.value.blur();
+	rootEl.value?.blur();
 }
 
 function focusBefore() {
-	focusPrev(el.value);
+	focusPrev(rootEl.value ?? null);
 }
 
 function focusAfter() {
-	focusNext(el.value);
+	focusNext(rootEl.value ?? null);
 }
 
 function readPromo() {
-	os.api('promo/read', {
+	misskeyApi('promo/read', {
 		noteId: appearNote.value.id,
 	});
 	isDeleted.value = true;
@@ -819,19 +796,20 @@ function emitUpdReaction(emoji: string, delta: number) {
 			margin: auto;
 			width: calc(100% - 8px);
 			height: calc(100% - 8px);
-			border: dashed 1px var(--focus);
+			border: solid 1px var(--focus);
 			border-radius: var(--radius);
 			box-sizing: border-box;
 		}
 	}
 
 	.footer {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
 		position: relative;
 		z-index: 1;
 		margin-top: 0.4em;
-		width: max-content;
-		min-width: min-content;
-		max-width: fit-content;
+		max-width: 400px;
 	}
 
 	&:hover > .article > .main > .footer > .footerButton {
@@ -882,7 +860,6 @@ function emitUpdReaction(emoji: string, delta: number) {
 }
 
 .replyTo {
-	opacity: 0.7;
 	padding-bottom: 0;
 }
 
@@ -890,11 +867,28 @@ function emitUpdReaction(emoji: string, delta: number) {
 	position: relative;
 	display: flex;
 	align-items: center;
-	padding: 24px 38px 16px;
+	padding: 24px 32px 0 calc(32px + var(--avatar) + 14px);
 	line-height: 28px;
 	white-space: pre;
 	color: var(--renote);
 
+	&::before {
+		content: '';
+		position: absolute;
+		top: 0;
+		left: calc(32px + .5 * var(--avatar));
+		bottom: -8px;
+		border-left: var(--thread-width) solid var(--thread);
+	}
+
+	&:first-child {
+		padding-left: 32px;
+
+		&::before {
+			display: none;
+		}
+	}
+
 	& + .article {
 		padding-top: 8px;
 	}
@@ -906,7 +900,7 @@ function emitUpdReaction(emoji: string, delta: number) {
 
 .renoteAvatar {
 	flex-shrink: 0;
-	display: inline-block;
+	display: none; /* same as Firefish, but keeping the element around in case someone wants to add it back via CSS override */
 	width: 28px;
 	height: 28px;
 	margin: 0 8px 0 0;
@@ -987,8 +981,8 @@ function emitUpdReaction(emoji: string, delta: number) {
 	display: block !important;
 	position: sticky !important;
 	margin: 0 14px 0 0;
-	width: 58px;
-	height: 58px;
+	width: var(--avatar);
+	height: var(--avatar);
 	position: sticky !important;
 	top: calc(22px + var(--stickyTop, 0px));
 	left: 0;
@@ -1130,24 +1124,24 @@ function emitUpdReaction(emoji: string, delta: number) {
 @container (max-width: 580px) {
 	.root {
 		font-size: 0.95em;
+		--avatar: 46px;
 	}
 
 	.renote {
-		padding: 24px 28px 16px;
+		padding: 24px 26px 0 calc(26px + var(--avatar) + 14px);
+
+		&::before {
+			left: calc(26px + .5 * var(--avatar));
+		}
 	}
 
 	.collapsedRenoteTarget {
-		padding: 8px 28px 24px;
+		padding: 8px 26px 24px;
 	}
 
 	.article {
 		padding: 24px 26px;
 	}
-
-	.avatar {
-		width: 50px;
-		height: 50px;
-	}
 }
 
 @container (max-width: 500px) {
@@ -1164,9 +1158,23 @@ function emitUpdReaction(emoji: string, delta: number) {
 	}
 }
 
+@container (max-width: 500px) {
+	.renote {
+		padding: 23px 25px 0 calc(25px + var(--avatar) + 14px);
+
+		&::before {
+			left: calc(25px + .5 * var(--avatar));
+		}
+	}
+}
+
 @container (max-width: 480px) {
 	.renote {
-		padding: 20px 24px 8px;
+		padding: 22px 24px 0 calc(24px + var(--avatar) + 14px);
+
+		&::before {
+			left: calc(24px + .5 * var(--avatar));
+		}
 	}
 
 	.tip {
@@ -1184,10 +1192,12 @@ function emitUpdReaction(emoji: string, delta: number) {
 }
 
 @container (max-width: 450px) {
+	.root {
+		--avatar: 44px;
+	}
+
 	.avatar {
 		margin: 0 10px 0 0;
-		width: 46px;
-		height: 46px;
 		top: calc(14px + var(--stickyTop, 0px));
 	}
 }
@@ -1220,11 +1230,6 @@ function emitUpdReaction(emoji: string, delta: number) {
 }
 
 @container (max-width: 300px) {
-	.avatar {
-		width: 44px;
-		height: 44px;
-	}
-
 	.root:not(.showActionsOnlyHover) {
 		.footerButton {
 			&:not(:last-child) {
@@ -1256,5 +1261,6 @@ function emitUpdReaction(emoji: string, delta: number) {
 
 .clickToOpen {
 	cursor: pointer;
+	-webkit-tap-highlight-color: transparent;
 }
 </style>
diff --git a/packages/frontend/src/components/SkNoteDetailed.vue b/packages/frontend/src/components/SkNoteDetailed.vue
index f850adba1b..ced7e7a176 100644
--- a/packages/frontend/src/components/SkNoteDetailed.vue
+++ b/packages/frontend/src/components/SkNoteDetailed.vue
@@ -7,7 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <div
 	v-if="!muted"
 	v-show="!isDeleted"
-	ref="el"
+	ref="rootEl"
 	v-hotkey="keymap"
 	:class="$style.root"
 >
@@ -40,10 +40,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</div>
 	</div>
 	<template v-if="appearNote.reply && appearNote.reply.replyId">
-		<SkNoteSub v-for="note in conversation" :key="note.id" :class="$style.replyToMore" :note="note" :expandAllCws="props.expandAllCws"/>
+		<SkNoteSub v-for="note in conversation" :key="note.id" :note="note" :expandAllCws="props.expandAllCws" detailed/>
 	</template>
-	<SkNoteSub v-if="appearNote.reply" :note="appearNote.reply" :class="$style.replyTo" :expandAllCws="props.expandAllCws"/>
-	<article :class="$style.note" @contextmenu.stop="onContextmenu">
+	<SkNoteSub v-if="appearNote.reply" :note="appearNote.reply" :class="$style.replyTo" :expandAllCws="props.expandAllCws" detailed/>
+	<article :id="appearNote.id" ref="noteEl" :class="$style.note" tabindex="-1" @contextmenu.stop="onContextmenu">
 		<header :class="$style.noteHeader">
 			<MkAvatar :class="$style.noteHeaderAvatar" :user="appearNote.user" indicator link preview/>
 			<div style="display: flex; align-items: center; white-space: nowrap; overflow: hidden;">
@@ -68,7 +68,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 							<i v-else-if="appearNote.visibility === 'followers'" class="ph-lock ph-bold ph-lg"></i>
 							<i v-else-if="appearNote.visibility === 'specified'" ref="specified" class="ph-envelope ph-bold ph-lg"></i>
 						</span>
-						<span v-if="appearNote.updatedAt" ref="menuVersionsButton" style="margin-left: 0.5em;" title="Edited" @mousedown="menuVersions()"><i class="ph-pencil ph-bold ph-lg"></i></span>
+						<span v-if="appearNote.updatedAt" ref="menuVersionsButton" style="margin-left: 0.5em;" title="Edited" @mousedown="menuVersions()"><i class="ph-pencil-simple ph-bold ph-lg"></i></span>
 						<span v-if="appearNote.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ph-rocket ph-bold ph-lg"></i></span>
 					</div>
 					<SkInstanceTicker v-if="showTicker" :instance="appearNote.user.instance"/>
@@ -96,32 +96,32 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<a v-if="appearNote.renote != null" :class="$style.rn">RN:</a>
 				<div v-if="translating || translation" :class="$style.translation">
 					<MkLoading v-if="translating" mini/>
-					<div v-else>
-						<b>{{ i18n.t('translatedFrom', { x: translation.sourceLang }) }}: </b>
+					<div v-else-if="translation">
+						<b>{{ i18n.tsx.translatedFrom({ x: translation.sourceLang }) }}: </b>
 						<Mfm :text="translation.text" :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis"/>
 					</div>
 				</div>
 				<MkButton v-if="!allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-play ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.play }}</MkButton>
 				<MkButton v-else-if="!defaultStore.state.animatedMfm && allowAnim && animated" :class="$style.playMFMButton" :small="true" @click="animatedMFM()" @click.stop><i class="ph-stop ph-bold ph-lg "></i> {{ i18n.ts._animatedMFM.stop }}</MkButton>
-				<div v-if="appearNote.files.length > 0">
+				<div v-if="appearNote.files && appearNote.files.length > 0">
 					<MkMediaList :mediaList="appearNote.files"/>
 				</div>
-				<MkPoll v-if="appearNote.poll" ref="pollViewer" :note="appearNote" :class="$style.poll"/>
+				<MkPoll v-if="appearNote.poll" ref="pollViewer" :noteId="appearNote.id" :poll="appearNote.poll" :class="$style.poll"/>
 				<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="true" style="margin-top: 6px;"/>
 				<div v-if="appearNote.renote" :class="$style.quote"><SkNoteSimple :note="appearNote.renote" :class="$style.quoteNote" :expandAllCws="props.expandAllCws"/></div>
 			</div>
 			<MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ph-television ph-bold ph-lg"></i> {{ appearNote.channel.name }}</MkA>
 		</div>
-		<footer :class="$style.footer">
-			<div :class="$style.noteFooterInfo">
-				<div v-if="appearNote.updatedAt">
-					{{ i18n.ts.edited }}: <MkTime :time="appearNote.updatedAt" mode="detail"/>
-				</div>
-				<MkA :to="notePage(appearNote)">
-					<MkTime :time="appearNote.createdAt" mode="detail" colored/>
-				</MkA>
+		<div :class="$style.noteFooterInfo">
+			<div v-if="appearNote.updatedAt">
+				{{ i18n.ts.edited }}: <MkTime :time="appearNote.updatedAt" mode="detail"/>
 			</div>
-			<MkReactionsViewer ref="reactionsViewer" :note="appearNote"/>
+			<MkA :to="notePage(appearNote)">
+				<MkTime :time="appearNote.createdAt" mode="detail" colored/>
+			</MkA>
+		</div>
+		<MkReactionsViewer ref="reactionsViewer" :note="appearNote"/>
+		<footer :class="$style.footer">
 			<button class="_button" :class="$style.noteFooterButton" @click="reply()">
 				<i class="ph-arrow-u-up-left ph-bold ph-lg"></i>
 				<p v-if="appearNote.repliesCount > 0" :class="$style.noteFooterButtonCount">{{ appearNote.repliesCount }}</p>
@@ -162,7 +162,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<button v-if="defaultStore.state.showClipButtonInNoteFooter" ref="clipButton" class="_button" :class="$style.noteFooterButton" @mousedown="clip()">
 				<i class="ph-paperclip ph-bold ph-lg"></i>
 			</button>
-			<button ref="menuButton" class="_button" :class="$style.noteFooterButton" @mousedown="menu()">
+			<button ref="menuButton" class="_button" :class="$style.noteFooterButton" @mousedown="showMenu()">
 				<i class="ph-dots-three ph-bold ph-lg"></i>
 			</button>
 		</footer>
@@ -174,11 +174,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<button class="_button" :class="[$style.tab, { [$style.tabActive]: tab === 'reactions' }]" @click="tab = 'reactions'"><i class="ph-smiley ph-bold ph-lg"></i> {{ i18n.ts.reactions }}</button>
 	</div>
 	<div>
-		<div v-if="tab === 'replies'" :class="$style.tab_replies">
+		<div v-if="tab === 'replies'">
 			<div v-if="!repliesLoaded" style="padding: 16px">
 				<MkButton style="margin: 0 auto;" primary rounded @click="loadReplies">{{ i18n.ts.loadReplies }}</MkButton>
 			</div>
-			<SkNoteSub v-for="note in replies" :key="note.id" :note="note" :class="$style.reply" :detail="true" :expandAllCws="props.expandAllCws" :onDeleteCallback="removeReply" />
+			<SkNoteSub v-for="note in replies" :key="note.id" :note="note" :class="$style.reply" :detail="true" :expandAllCws="props.expandAllCws" :onDeleteCallback="removeReply" :isReply="true"/>
 		</div>
 		<div v-else-if="tab === 'renotes'" :class="$style.tab_renotes">
 			<MkPagination :pagination="renotesPagination" :disableAutoLoad="true">
@@ -191,11 +191,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 				</template>
 			</MkPagination>
 		</div>
-		<div v-if="tab === 'quotes'" :class="$style.tab_replies">
+		<div v-if="tab === 'quotes'">
 			<div v-if="!quotesLoaded" style="padding: 16px">
 				<MkButton style="margin: 0 auto;" primary rounded @click="loadQuotes">{{ i18n.ts.loadReplies }}</MkButton>
 			</div>
-			<SkNoteSub v-for="note in quotes" :key="note.id" :note="note" :class="$style.reply" :detail="true" :expandAllCws="props.expandAllCws"/>
+			<SkNoteSub v-for="note in quotes" :key="note.id" :note="note" :class="$style.reply" :detail="true" :expandAllCws="props.expandAllCws" :reply="true"/>
 		</div>
 		<div v-else-if="tab === 'reactions'" :class="$style.tab_reactions">
 			<div :class="$style.reactionTabs">
@@ -228,8 +228,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, inject, onMounted, provide, ref, shallowRef, watch } from 'vue';
-import * as mfm from '@sharkey/sfm-js';
+import { computed, inject, onMounted, onUnmounted, onUpdated, provide, ref, shallowRef, watch } from 'vue';
+import * as mfm from '@transfem-org/sfm-js';
 import * as Misskey from 'misskey-js';
 import SkNoteSub from '@/components/SkNoteSub.vue';
 import SkNoteSimple from '@/components/SkNoteSimple.vue';
@@ -245,6 +245,7 @@ import { checkWordMute } from '@/scripts/check-word-mute.js';
 import { userPage } from '@/filters/user.js';
 import { notePage } from '@/filters/note.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import * as sound from '@/scripts/sound.js';
 import { defaultStore, noteViewInterruptors } from '@/store.js';
 import { reactionPicker } from '@/scripts/reaction-picker.js';
@@ -261,9 +262,10 @@ import { checkAnimationFromMfm } from '@/scripts/check-animated-mfm.js';
 import MkRippleEffect from '@/components/MkRippleEffect.vue';
 import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
 import MkUserCardMini from '@/components/MkUserCardMini.vue';
-import MkPagination from '@/components/MkPagination.vue';
+import MkPagination, { type Paging } from '@/components/MkPagination.vue';
 import MkReactionIcon from '@/components/MkReactionIcon.vue';
 import MkButton from '@/components/MkButton.vue';
+import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js';
 
 const props = defineProps<{
 	note: Misskey.entities.Note;
@@ -280,7 +282,7 @@ if (noteViewInterruptors.length > 0) {
 		let result: Misskey.entities.Note | null = deepClone(note.value);
 		for (const interruptor of noteViewInterruptors) {
 			try {
-				result = await interruptor.handler(result);
+				result = await interruptor.handler(result!) as Misskey.entities.Note | null;
 				if (result === null) {
 					isDeleted.value = true;
 					return;
@@ -289,18 +291,19 @@ if (noteViewInterruptors.length > 0) {
 				console.error(err);
 			}
 		}
-		note.value = result;
+		note.value = result as Misskey.entities.Note;
 	});
 }
 
 const isRenote = (
 	note.value.renote != null &&
 	note.value.text == null &&
-	note.value.fileIds.length === 0 &&
+	note.value.fileIds && note.value.fileIds.length === 0 &&
 	note.value.poll == null
 );
 
-const el = shallowRef<HTMLElement>();
+const rootEl = shallowRef<HTMLElement>();
+const noteEl = shallowRef<HTMLElement>();
 const menuButton = shallowRef<HTMLElement>();
 const menuVersionsButton = shallowRef<HTMLElement>();
 const renoteButton = shallowRef<HTMLElement>();
@@ -310,24 +313,22 @@ const quoteButton = shallowRef<HTMLElement>();
 const clipButton = shallowRef<HTMLElement>();
 const likeButton = shallowRef<HTMLElement>();
 const appearNote = computed(() => isRenote ? note.value.renote as Misskey.entities.Note : note.value);
-const renoteUrl = appearNote.value.renote ? appearNote.value.renote.url : null;
-const renoteUri = appearNote.value.renote ? appearNote.value.renote.uri : null;
 const isMyRenote = $i && ($i.id === note.value.userId);
 const showContent = ref(defaultStore.state.uncollapseCW);
 const isDeleted = ref(false);
 const renoted = ref(false);
 const muted = ref($i ? checkWordMute(appearNote.value, $i, $i.mutedWords) : false);
-const translation = ref(null);
+const translation = ref<Misskey.entities.NotesTranslateResponse | null>(null);
 const translating = ref(false);
 const parsed = appearNote.value.text ? mfm.parse(appearNote.value.text) : null;
-const urls = parsed ? extractUrlFromMfm(parsed).filter(u => u !== renoteUrl && u !== renoteUri) : null;
+const urls = parsed ? extractUrlFromMfm(parsed).filter((url) => appearNote.value.renote?.url !== url && appearNote.value.renote?.uri !== url) : null;
 const animated = computed(() => parsed ? checkAnimationFromMfm(parsed) : null);
 const allowAnim = ref(defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : false);
 const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.value.user.instance);
 const conversation = ref<Misskey.entities.Note[]>([]);
 const replies = ref<Misskey.entities.Note[]>([]);
 const quotes = ref<Misskey.entities.Note[]>([]);
-const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || appearNote.value.userId === $i.id);
+const canRenote = computed(() => ['public', 'home'].includes(appearNote.value.visibility) || (appearNote.value.visibility === 'followers' && appearNote.value.userId === $i?.id));
 const defaultLike = computed(() => defaultStore.state.like ? defaultStore.state.like : null);
 
 watch(() => props.expandAllCws, (expandAllCws) => {
@@ -335,7 +336,7 @@ watch(() => props.expandAllCws, (expandAllCws) => {
 });
 
 if ($i) {
-	os.api('notes/renotes', {
+	misskeyApi('notes/renotes', {
 		noteId: appearNote.value.id,
 		userId: $i.id,
 		limit: 1,
@@ -347,23 +348,23 @@ if ($i) {
 const keymap = {
 	'r': () => reply(true),
 	'e|a|plus': () => react(true),
-	'q': () => renoteButton.value.renote(true),
+	'q': () => renote(appearNote.value.visibility),
 	'esc': blur,
-	'm|o': () => menu(true),
+	'm|o': () => showMenu(true),
 	's': () => showContent.value !== showContent.value,
 };
 
 provide('react', (reaction: string) => {
-	os.api('notes/reactions/create', {
+	misskeyApi('notes/reactions/create', {
 		noteId: appearNote.value.id,
 		reaction: reaction,
 	});
 });
 
 const tab = ref('replies');
-const reactionTabType = ref(null);
+const reactionTabType = ref<string | null>(null);
 
-const renotesPagination = computed(() => ({
+const renotesPagination = computed<Paging>(() => ({
 	endpoint: 'notes/renotes',
 	limit: 10,
 	params: {
@@ -371,7 +372,7 @@ const renotesPagination = computed(() => ({
 	},
 }));
 
-const reactionsPagination = computed(() => ({
+const reactionsPagination = computed<Paging>(() => ({
 	endpoint: 'notes/reactions',
 	limit: 10,
 	params: {
@@ -381,20 +382,20 @@ const reactionsPagination = computed(() => ({
 }));
 
 async function addReplyTo(replyNote: Misskey.entities.Note) {
-		replies.value.unshift(replyNote);
-		appearNote.value.repliesCount += 1;
+	replies.value.unshift(replyNote);
+	appearNote.value.repliesCount += 1;
 }
 
 async function removeReply(id: Misskey.entities.Note['id']) {
-		const replyIdx = replies.value.findIndex(note => note.id === id);
-		if (replyIdx >= 0) {
-			replies.value.splice(replyIdx, 1);
-			appearNote.value.repliesCount -= 1;
-		}
+	const replyIdx = replies.value.findIndex(note => note.id === id);
+	if (replyIdx >= 0) {
+		replies.value.splice(replyIdx, 1);
+		appearNote.value.repliesCount -= 1;
+	}
 }
 
 useNoteCapture({
-	rootEl: el,
+	rootEl: rootEl,
 	note: appearNote,
 	pureNote: note,
 	isDeletedRef: isDeleted,
@@ -402,7 +403,7 @@ useNoteCapture({
 });
 
 useTooltip(renoteButton, async (showing) => {
-	const renotes = await os.api('notes/renotes', {
+	const renotes = await misskeyApi('notes/renotes', {
 		noteId: appearNote.value.id,
 		limit: 11,
 	});
@@ -420,7 +421,7 @@ useTooltip(renoteButton, async (showing) => {
 });
 
 useTooltip(quoteButton, async (showing) => {
-	const renotes = await os.api('notes/renotes', {
+	const renotes = await misskeyApi('notes/renotes', {
 		noteId: appearNote.value.id,
 		limit: 11,
 		quote: true,
@@ -438,53 +439,15 @@ useTooltip(quoteButton, async (showing) => {
 	}, {}, 'closed');
 });
 
-type Visibility = 'public' | 'home' | 'followers' | 'specified';
-
-function smallerVisibility(a: Visibility | string, b: Visibility | string): Visibility {
-	if (a === 'specified' || b === 'specified') return 'specified';
-	if (a === 'followers' || b === 'followers') return 'followers';
-	if (a === 'home' || b === 'home') return 'home';
-	// if (a === 'public' || b === 'public')
-	return 'public';
-}
-
 function boostVisibility() {
-	os.popupMenu([
-		{
-			type: 'button',
-			icon: 'ph-globe-hemisphere-west ph-bold ph-lg',
-			text: i18n.ts._visibility['public'],
-			action: () => {
-				renote('public');
-			},
-		},
-		{
-			type: 'button',
-			icon: 'ph-house ph-bold ph-lg',
-			text: i18n.ts._visibility['home'],
-			action: () => {
-				renote('home');
-			},
-		},
-		{
-			type: 'button',
-			icon: 'ph-lock ph-bold ph-lg',
-			text: i18n.ts._visibility['followers'],
-			action: () => {
-				renote('followers');
-			},
-		},
-		{
-			type: 'button',
-			icon: 'ph-planet ph-bold ph-lg',
-			text: i18n.ts._timelines.local,
-			action: () => {
-				renote('local');
-			},
-		}], renoteButton.value);
+	if (!defaultStore.state.showVisibilitySelectorOnBoost) {
+		renote(defaultStore.state.visibilityOnBoost);
+	} else {
+		os.popupMenu(boostMenuItems(appearNote, renote), renoteButton.value);
+	}
 }
 
-function renote(visibility: Visibility | 'local') {
+function renote(visibility: Visibility, localOnly: boolean = false) {
 	pleaseLogin();
 	showMovedDialog();
 
@@ -497,14 +460,14 @@ function renote(visibility: Visibility | 'local') {
 			os.popup(MkRippleEffect, { x, y }, {}, 'end');
 		}
 
-		os.api('notes/create', {
+		misskeyApi('notes/create', {
 			renoteId: appearNote.value.id,
 			channelId: appearNote.value.channelId,
 		}).then(() => {
 			os.toast(i18n.ts.renoted);
 			renoted.value = true;
 		});
-	} else if (!appearNote.value.channel || appearNote.value.channel?.allowRenoteToExternal) {
+	} else if (!appearNote.value.channel || appearNote.value.channel.allowRenoteToExternal) {
 		const el = renoteButton.value as HTMLElement | null | undefined;
 		if (el) {
 			const rect = el.getBoundingClientRect();
@@ -513,17 +476,9 @@ function renote(visibility: Visibility | 'local') {
 			os.popup(MkRippleEffect, { x, y }, {}, 'end');
 		}
 
-		const configuredVisibility = defaultStore.state.rememberNoteVisibility ? defaultStore.state.visibility : defaultStore.state.defaultNoteVisibility;
-		const localOnlySetting = defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly;
-
-		let noteVisibility = visibility === 'local' || visibility === 'specified' ? smallerVisibility(appearNote.value.visibility, configuredVisibility) : smallerVisibility(visibility, configuredVisibility);
-		if (appearNote.value.channel?.isSensitive) {
-			noteVisibility = smallerVisibility(visibility === 'local' || visibility === 'specified' ? appearNote.value.visibility : visibility, 'home');
-		}
-
-		os.api('notes/create', {
-			localOnly: visibility === 'local' ? true : localOnlySetting,
-			visibility: noteVisibility,
+		misskeyApi('notes/create', {
+			localOnly: localOnly,
+			visibility: visibility,
 			renoteId: appearNote.value.id,
 		}).then(() => {
 			os.toast(i18n.ts.renoted);
@@ -541,9 +496,9 @@ function quote() {
 			renote: appearNote.value,
 			channel: appearNote.value.channel,
 		}).then(() => {
-			os.api('notes/renotes', {
+			misskeyApi('notes/renotes', {
 				noteId: appearNote.value.id,
-				userId: $i.id,
+				userId: $i?.id,
 				limit: 1,
 				quote: true,
 			}).then((res) => {
@@ -563,9 +518,9 @@ function quote() {
 		os.post({
 			renote: appearNote.value,
 		}).then(() => {
-			os.api('notes/renotes', {
+			misskeyApi('notes/renotes', {
 				noteId: appearNote.value.id,
-				userId: $i.id,
+				userId: $i?.id,
 				limit: 1,
 				quote: true,
 			}).then((res) => {
@@ -591,7 +546,7 @@ function reply(viaKeyboard = false): void {
 		reply: appearNote.value,
 		channel: appearNote.value.channel,
 		animation: !viaKeyboard,
-	}, () => {
+	}).then(() => {
 		focus();
 	});
 }
@@ -600,7 +555,9 @@ function react(viaKeyboard = false): void {
 	pleaseLogin();
 	showMovedDialog();
 	if (appearNote.value.reactionAcceptance === 'likeOnly') {
-		os.api('notes/like', {
+		sound.playMisskeySfx('reaction');
+
+		misskeyApi('notes/like', {
 			noteId: appearNote.value.id,
 			override: defaultLike.value,
 		});
@@ -613,10 +570,10 @@ function react(viaKeyboard = false): void {
 		}
 	} else {
 		blur();
-		reactionPicker.show(reactButton.value, reaction => {
-			sound.play('reaction');
+		reactionPicker.show(reactButton.value ?? null, note.value, reaction => {
+			sound.playMisskeySfx('reaction');
 
-			os.api('notes/reactions/create', {
+			misskeyApi('notes/reactions/create', {
 				noteId: appearNote.value.id,
 				reaction: reaction,
 			});
@@ -632,7 +589,8 @@ function react(viaKeyboard = false): void {
 function like(): void {
 	pleaseLogin();
 	showMovedDialog();
-	os.api('notes/like', {
+	sound.playMisskeySfx('reaction');
+	misskeyApi('notes/like', {
 		noteId: appearNote.value.id,
 		override: defaultLike.value,
 	});
@@ -648,14 +606,14 @@ function like(): void {
 function undoReact(note): void {
 	const oldReaction = note.myReaction;
 	if (!oldReaction) return;
-	os.api('notes/reactions/delete', {
+	misskeyApi('notes/reactions/delete', {
 		noteId: note.id,
 	});
 }
 
 function undoRenote() : void {
 	if (!renoted.value) return;
-	os.api('notes/unrenote', {
+	misskeyApi('notes/unrenote', {
 		noteId: appearNote.value.id,
 	});
 	os.toast(i18n.ts.rmboost);
@@ -671,26 +629,28 @@ function undoRenote() : void {
 }
 
 function onContextmenu(ev: MouseEvent): void {
-	const isLink = (el: HTMLElement) => {
+	const isLink = (el: HTMLElement): boolean => {
 		if (el.tagName === 'A') return true;
 		if (el.parentElement) {
 			return isLink(el.parentElement);
 		}
+		return false;
 	};
-	if (isLink(ev.target)) return;
-	if (window.getSelection().toString() !== '') return;
+
+	if (ev.target && isLink(ev.target as HTMLElement)) return;
+	if (window.getSelection()?.toString() !== '') return;
 
 	if (defaultStore.state.useReactionPickerForContextMenu) {
 		ev.preventDefault();
 		react();
 	} else {
-		const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, menuButton, isDeleted });
+		const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted });
 		os.contextMenu(menu, ev).then(focus).finally(cleanup);
 	}
 }
 
-function menu(viaKeyboard = false): void {
-	const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, menuButton, isDeleted });
+function showMenu(viaKeyboard = false): void {
+	const { menu, cleanup } = getNoteMenu({ note: note.value, translating, translation, isDeleted });
 	os.popupMenu(menu, menuButton.value, {
 		viaKeyboard,
 	}).then(focus).finally(cleanup);
@@ -715,7 +675,7 @@ function showRenoteMenu(viaKeyboard = false): void {
 		icon: 'ph-trash ph-bold ph-lg',
 		danger: true,
 		action: () => {
-			os.api('notes/delete', {
+			misskeyApi('notes/delete', {
 				noteId: note.value.id,
 			});
 			isDeleted.value = true;
@@ -726,18 +686,18 @@ function showRenoteMenu(viaKeyboard = false): void {
 }
 
 function focus() {
-	el.value.focus();
+	noteEl.value?.focus();
 }
 
 function blur() {
-	el.value.blur();
+	noteEl.value?.blur();
 }
 
 const repliesLoaded = ref(false);
 
 function loadReplies() {
 	repliesLoaded.value = true;
-	os.api('notes/children', {
+	misskeyApi('notes/children', {
 		noteId: appearNote.value.id,
 		limit: 30,
 		showQuotes: false,
@@ -752,7 +712,7 @@ const quotesLoaded = ref(false);
 
 function loadQuotes() {
 	quotesLoaded.value = true;
-	os.api('notes/renotes', {
+	misskeyApi('notes/renotes', {
 		noteId: appearNote.value.id,
 		limit: 30,
 		quote: true,
@@ -767,10 +727,12 @@ const conversationLoaded = ref(false);
 
 function loadConversation() {
 	conversationLoaded.value = true;
-	os.api('notes/conversation', {
+	if (appearNote.value.replyId == null) return;
+	misskeyApi('notes/conversation', {
 		noteId: appearNote.value.replyId,
 	}).then(res => {
 		conversation.value = res.reverse();
+		focus();
 	});
 }
 
@@ -787,6 +749,31 @@ function animatedMFM() {
 		}).then((res) => { if (!res.canceled) allowAnim.value = true; });
 	}
 }
+
+let isScrolling = false;
+
+function setScrolling() {
+	isScrolling = true;
+}
+
+onMounted(() => {
+	document.addEventListener('wheel', setScrolling);
+	isScrolling = false;
+	noteEl.value?.scrollIntoView({ block: 'center' });
+});
+
+onUpdated(() => {
+	if (!isScrolling) {
+		noteEl.value?.scrollIntoView({ block: 'center' });
+		if (location.hash) {
+			location.replace(location.hash); // Jump to highlighted reply
+		}
+	}
+});
+
+onUnmounted(() => {
+	document.removeEventListener('wheel', setScrolling);
+});
 </script>
 
 <style lang="scss" module>
@@ -798,23 +785,19 @@ function animatedMFM() {
 }
 
 .footer {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
 		position: relative;
 		z-index: 1;
 		margin-top: 0.4em;
-		width: max-content;
-		min-width: min-content;
-		max-width: fit-content;
+		max-width: 400px;
 }
 
 .replyTo {
-	opacity: 0.7;
 	padding-bottom: 0;
 }
 
-.replyToMore {
-	opacity: 0.7;
-}
-
 .renote {
 	display: flex;
 	align-items: center;
@@ -859,6 +842,7 @@ function animatedMFM() {
 }
 
 .note {
+	position: relative;
 	padding: 32px;
 	font-size: 1.2em;
 	overflow: hidden;
@@ -866,6 +850,28 @@ function animatedMFM() {
 	&:hover > .main > .footer > .button {
 		opacity: 1;
 	}
+
+	&:focus-visible {
+		outline: none;
+
+		&:after {
+			content: "";
+			pointer-events: none;
+			display: block;
+			position: absolute;
+			z-index: 10;
+			top: 0;
+			left: 0;
+			right: 0;
+			bottom: 0;
+			margin: auto;
+			width: calc(100% - 8px);
+			height: calc(100% - 8px);
+			border: solid 1px var(--focus);
+			border-radius: var(--radius);
+			box-sizing: border-box;
+		}
+	}
 }
 
 .noteHeader {
@@ -879,8 +885,8 @@ function animatedMFM() {
 .noteHeaderAvatar {
 	display: block;
 	flex-shrink: 0;
-	width: 58px;
-	height: 58px;
+	width: var(--avatar);
+	height: var(--avatar);
 }
 
 .noteHeaderBody {
@@ -1021,10 +1027,17 @@ function animatedMFM() {
 }
 
 .tab {
+	display: flex;
+	align-items: center;
+	justify-content: center;
 	flex: 1;
 	padding: 12px 8px;
 	border-top: solid 2px transparent;
 	border-bottom: solid 2px transparent;
+
+	> i {
+		margin-right: 8px;
+	}
 }
 
 .tabActive {
diff --git a/packages/frontend/src/components/SkNoteHeader.vue b/packages/frontend/src/components/SkNoteHeader.vue
index d3ecdf17bb..7dc4c8f019 100644
--- a/packages/frontend/src/components/SkNoteHeader.vue
+++ b/packages/frontend/src/components/SkNoteHeader.vue
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</MkA>
 			<div v-if="note.user.isBot" :class="$style.isBot">bot</div>
 			<div v-if="note.user.badgeRoles" :class="$style.badgeRoles">
-				<img v-for="role in note.user.badgeRoles" :key="role.id" v-tooltip="role.name" :class="$style.badgeRole" :src="role.iconUrl"/>
+				<img v-for="(role, i) in note.user.badgeRoles" :key="i" v-tooltip="role.name" :class="$style.badgeRole" :src="role.iconUrl!"/>
 			</div>
 		</div>
 		<div :class="$style.username"><MkAcct :user="note.user"/></div>
@@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<i v-else-if="note.visibility === 'followers'" class="ph-lock ph-bold ph-lg"></i>
 				<i v-else-if="note.visibility === 'specified'" ref="specified" class="ph-envelope ph-bold ph-lg"></i>
 			</span>
-			<span v-if="note.updatedAt" ref="menuVersionsButton" style="margin-left: 0.5em; cursor: pointer;" title="Edited" @mousedown="menuVersions()"><i class="ph-pencil ph-bold ph-lg"></i></span>
+			<span v-if="note.updatedAt" ref="menuVersionsButton" style="margin-left: 0.5em; cursor: pointer;" title="Edited" @mousedown="menuVersions()"><i class="ph-pencil-simple ph-bold ph-lg"></i></span>
 			<span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ph-rocket ph-bold ph-lg"></i></span>
 			<span v-if="note.channel" style="margin-left: 0.5em;" :title="note.channel.name"><i class="ph-television ph-bold ph-lg"></i></span>
 		</div>
@@ -65,7 +65,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<i v-else-if="note.visibility === 'followers'" class="ph-lock ph-bold ph-lg"></i>
 			<i v-else-if="note.visibility === 'specified'" ref="specified" class="ph-envelope ph-bold ph-lg"></i>
 		</span>
-		<span v-if="note.updatedAt" ref="menuVersionsButton" style="margin-left: 0.5em; cursor: pointer;" title="Edited" @mousedown="menuVersions()"><i class="ph-pencil ph-bold ph-lg"></i></span>
+		<span v-if="note.updatedAt" ref="menuVersionsButton" style="margin-left: 0.5em; cursor: pointer;" title="Edited" @mousedown="menuVersions()"><i class="ph-pencil-simple ph-bold ph-lg"></i></span>
 		<span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ph-rocket ph-bold ph-lg"></i></span>
 		<span v-if="note.channel" style="margin-left: 0.5em;" :title="note.channel.name"><i class="ph-television ph-bold ph-lg"></i></span>
 	</div>
@@ -82,7 +82,7 @@ import { getNoteVersionsMenu } from '@/scripts/get-note-versions-menu.js';
 import SkInstanceTicker from '@/components/SkInstanceTicker.vue';
 import { popupMenu } from '@/os.js';
 import { defaultStore } from '@/store.js';
-import { useRouter } from '@/router.js';
+import { useRouter } from '@/router/supplier.js';
 import { deviceKind } from '@/scripts/device-kind.js';
 
 const props = defineProps<{
@@ -116,6 +116,8 @@ const mock = inject<boolean>('mock', false);
 .root {
 	display: flex;
 	cursor: auto; /* not clickToOpen-able */
+	min-height: 100%;
+	align-items: center;
 }
 
 .classicRoot {
@@ -135,6 +137,7 @@ const mock = inject<boolean>('mock', false);
 			display: flex;
 			align-items: flex-end;
 			margin-left: auto;
+			margin-bottom: auto;
 			padding-left: 10px;
 			overflow: clip;
 		}
@@ -143,10 +146,9 @@ const mock = inject<boolean>('mock', false);
 .name {
 	flex-shrink: 1;
 	display: block;
-	// note, these margin top values were done by hand may need futher checking if it actualy aligns pixel perfect
-	margin: 3px .5em 0 0;
+	margin: 0 .5em 0 0;
 	padding: 0;
-	overflow: scroll;
+	overflow: hidden;
 	overflow-wrap: anywhere;
 	font-size: 1em;
 	font-weight: bold;
@@ -192,8 +194,7 @@ const mock = inject<boolean>('mock', false);
 
 .username {
 	flex-shrink: 9999999;
-	// note these top margins were made to align with the instance ticker
-	margin: 4px .5em 0 0;
+	margin: 0 .5em 0 0;
 	overflow: hidden;
 	text-overflow: ellipsis;
 	font-size: .95em;
diff --git a/packages/frontend/src/components/SkNoteSimple.vue b/packages/frontend/src/components/SkNoteSimple.vue
index fe12baedeb..533aa60961 100644
--- a/packages/frontend/src/components/SkNoteSimple.vue
+++ b/packages/frontend/src/components/SkNoteSimple.vue
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<MkCwButton v-model="showContent" :text="note.text" :files="note.files" :poll="note.poll" @click.stop/>
 			</p>
 			<div v-show="note.cw == null || showContent">
-				<MkSubNoteContent :hideFiles="hideFiles" :class="$style.text" :note="note"/>
+				<MkSubNoteContent :hideFiles="hideFiles" :class="$style.text" :note="note" :expandAllCws="props.expandAllCws"/>
 			</div>
 		</div>
 	</div>
@@ -48,6 +48,11 @@ watch(() => props.expandAllCws, (expandAllCws) => {
 	margin: 0;
 	padding: 0;
 	font-size: 0.95em;
+
+	&:hover, &:focus-within {
+		background: var(--panelHighlight);
+		transition: background .2s;
+	}
 }
 
 .avatar {
diff --git a/packages/frontend/src/components/SkNoteSub.vue b/packages/frontend/src/components/SkNoteSub.vue
index bc482294b4..1cffd8dd66 100644
--- a/packages/frontend/src/components/SkNoteSub.vue
+++ b/packages/frontend/src/components/SkNoteSub.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<div v-show="!isDeleted" v-if="!muted" ref="el" :class="[$style.root, { [$style.children]: depth > 1 }]">
+<div v-show="!isDeleted" v-if="!muted" ref="el" :class="[$style.root, { [$style.children]: depth > 1, [$style.isReply]: props.isReply, [$style.detailed]: props.detailed }]">
 	<div v-if="!hideLine" :class="$style.line"></div>
 	<div :class="$style.main">
 		<div v-if="note.channel" :class="$style.colorBar" :style="{ background: note.channel.color }"></div>
@@ -24,11 +24,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<MkCwButton v-model="showContent" :text="note.text" :files="note.files" :poll="note.poll"/>
 				</p>
 				<div v-show="note.cw == null || showContent">
-					<MkSubNoteContent :class="$style.text" :note="note" :translating="translating" :translation="translation"/>
+					<MkSubNoteContent :class="$style.text" :note="note" :translating="translating" :translation="translation" :expandAllCws="props.expandAllCws"/>
 				</div>
 			</div>
+			<MkReactionsViewer ref="reactionsViewer" :note="note"/>
 			<footer :class="$style.footer">
-				<MkReactionsViewer ref="reactionsViewer" :note="note"/>
 				<button class="_button" :class="$style.noteFooterButton" @click="reply()">
 					<i class="ph-arrow-u-up-left ph-bold ph-lg"></i>
 					<p v-if="note.repliesCount > 0" :class="$style.noteFooterButtonCount">{{ note.repliesCount }}</p>
@@ -73,7 +73,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</div>
 	</div>
 	<template v-if="depth < numberOfReplies">
-		<SkNoteSub v-for="reply in replies" :key="reply.id" :note="reply" :class="[$style.reply, { [$style.single]: replies.length === 1 }]" :detail="true" :depth="depth + 1" :expandAllCws="props.expandAllCws" :onDeleteCallback="removeReply"/>
+		<SkNoteSub v-for="reply in replies" :key="reply.id" :note="reply" :class="[$style.reply, { [$style.single]: replies.length === 1 }]" :detail="true" :depth="depth + 1" :expandAllCws="props.expandAllCws" :onDeleteCallback="removeReply" :isReply="props.isReply"/>
 	</template>
 	<div v-else :class="$style.more">
 		<MkA class="_link" :to="notePage(note)">{{ i18n.ts.continueThread }} <i class="ph-caret-double-right ph-bold ph-lg"></i></MkA>
@@ -99,6 +99,8 @@ import MkSubNoteContent from '@/components/MkSubNoteContent.vue';
 import MkCwButton from '@/components/MkCwButton.vue';
 import { notePage } from '@/filters/note.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
+import * as sound from '@/scripts/sound.js';
 import { i18n } from '@/i18n.js';
 import { $i } from '@/account.js';
 import { userPage } from '@/filters/user.js';
@@ -111,6 +113,7 @@ import { reactionPicker } from '@/scripts/reaction-picker.js';
 import { claimAchievement } from '@/scripts/achievements.js';
 import { getNoteMenu } from '@/scripts/get-note-menu.js';
 import { useNoteCapture } from '@/scripts/use-note-capture.js';
+import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js';
 
 const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i.id);
 const hideLine = computed(() => { return props.detail ? true : false; });
@@ -123,8 +126,13 @@ const props = withDefaults(defineProps<{
 
 	// how many notes are in between this one and the note being viewed in detail
 	depth?: number;
+
+	isReply?: boolean;
+	detailed?: boolean;
 }>(), {
 	depth: 1,
+	isReply: false,
+	detailed: false,
 });
 
 const el = shallowRef<HTMLElement>();
@@ -147,7 +155,7 @@ const replies = ref<Misskey.entities.Note[]>([]);
 const isRenote = (
 	props.note.renote != null &&
 	props.note.text == null &&
-	props.note.fileIds.length === 0 &&
+	props.note.fileIds && props.note.fileIds.length === 0 &&
 	props.note.poll == null
 );
 
@@ -174,7 +182,7 @@ useNoteCapture({
 });
 
 if ($i) {
-	os.api('notes/renotes', {
+	misskeyApi('notes/renotes', {
 		noteId: appearNote.value.id,
 		userId: $i.id,
 		limit: 1,
@@ -202,8 +210,9 @@ function reply(viaKeyboard = false): void {
 function react(viaKeyboard = false): void {
 	pleaseLogin();
 	showMovedDialog();
+	sound.playMisskeySfx('reaction');
 	if (props.note.reactionAcceptance === 'likeOnly') {
-		os.api('notes/like', {
+		misskeyApi('notes/like', {
 			noteId: props.note.id,
 			override: defaultLike.value,
 		});
@@ -216,8 +225,8 @@ function react(viaKeyboard = false): void {
 		}
 	} else {
 		blur();
-		reactionPicker.show(reactButton.value, reaction => {
-			os.api('notes/reactions/create', {
+		reactionPicker.show(reactButton.value ?? null, props.note, reaction => {
+			misskeyApi('notes/reactions/create', {
 				noteId: props.note.id,
 				reaction: reaction,
 			});
@@ -233,7 +242,8 @@ function react(viaKeyboard = false): void {
 function like(): void {
 	pleaseLogin();
 	showMovedDialog();
-	os.api('notes/like', {
+	sound.playMisskeySfx('reaction');
+	misskeyApi('notes/like', {
 		noteId: props.note.id,
 		override: defaultLike.value,
 	});
@@ -249,14 +259,14 @@ function like(): void {
 function undoReact(note): void {
 	const oldReaction = note.myReaction;
 	if (!oldReaction) return;
-	os.api('notes/reactions/delete', {
+	misskeyApi('notes/reactions/delete', {
 		noteId: note.id,
 	});
 }
 
 function undoRenote() : void {
 	if (!renoted.value) return;
-	os.api('notes/unrenote', {
+	misskeyApi('notes/unrenote', {
 		noteId: appearNote.value.id,
 	});
 	os.toast(i18n.ts.rmboost);
@@ -278,42 +288,14 @@ watch(() => props.expandAllCws, (expandAllCws) => {
 });
 
 function boostVisibility() {
-	os.popupMenu([
-		{
-			type: 'button',
-			icon: 'ph-globe-hemisphere-west ph-bold ph-lg',
-			text: i18n.ts._visibility['public'],
-			action: () => {
-				renote('public');
-			},
-		},
-		{
-			type: 'button',
-			icon: 'ph-house ph-bold ph-lg',
-			text: i18n.ts._visibility['home'],
-			action: () => {
-				renote('home');
-			},
-		},
-		{
-			type: 'button',
-			icon: 'ph-lock ph-bold ph-lg',
-			text: i18n.ts._visibility['followers'],
-			action: () => {
-				renote('followers');
-			},
-		},
-		{
-			type: 'button',
-			icon: 'ph-planet ph-bold ph-lg',
-			text: i18n.ts._timelines.local,
-			action: () => {
-				renote('local');
-			},
-		}], renoteButton.value);
+	if (!defaultStore.state.showVisibilitySelectorOnBoost) {
+		renote(defaultStore.state.visibilityOnBoost);
+	} else {
+		os.popupMenu(boostMenuItems(appearNote, renote), renoteButton.value);
+	}
 }
 
-function renote(visibility: 'public' | 'home' | 'followers' | 'specified' | 'local') {
+function renote(visibility: Visibility, localOnly: boolean = false) {
 	pleaseLogin();
 	showMovedDialog();
 
@@ -326,9 +308,9 @@ function renote(visibility: 'public' | 'home' | 'followers' | 'specified' | 'loc
 			os.popup(MkRippleEffect, { x, y }, {}, 'end');
 		}
 
-		os.api('notes/create', {
-			renoteId: props.note.id,
-			channelId: props.note.channelId,
+		misskeyApi('notes/create', {
+			renoteId: appearNote.value.id,
+			channelId: appearNote.value.channelId,
 		}).then(() => {
 			os.toast(i18n.ts.renoted);
 			renoted.value = true;
@@ -342,10 +324,10 @@ function renote(visibility: 'public' | 'home' | 'followers' | 'specified' | 'loc
 			os.popup(MkRippleEffect, { x, y }, {}, 'end');
 		}
 
-		os.api('notes/create', {
-			renoteId: props.note.id,
-			localOnly: visibility === 'local' ? true : false,
-			visibility: visibility === 'local' || visibility === 'specified' ? props.note.visibility : visibility,
+		misskeyApi('notes/create', {
+			renoteId: appearNote.value.id,
+			localOnly: localOnly,
+			visibility: visibility,
 		}).then(() => {
 			os.toast(i18n.ts.renoted);
 			renoted.value = true;
@@ -362,7 +344,7 @@ function quote() {
 			renote: appearNote.value,
 			channel: appearNote.value.channel,
 		}).then(() => {
-			os.api('notes/renotes', {
+			misskeyApi('notes/renotes', {
 				noteId: props.note.id,
 				userId: $i.id,
 				limit: 1,
@@ -384,7 +366,7 @@ function quote() {
 		os.post({
 			renote: appearNote.value,
 		}).then(() => {
-			os.api('notes/renotes', {
+			misskeyApi('notes/renotes', {
 				noteId: props.note.id,
 				userId: $i.id,
 				limit: 1,
@@ -413,7 +395,7 @@ function menu(viaKeyboard = false): void {
 }
 
 if (props.detail) {
-	os.api('notes/children', {
+	misskeyApi('notes/children', {
 		noteId: props.note.id,
 		limit: numberOfReplies.value,
 		showQuotes: false,
@@ -426,35 +408,61 @@ if (props.detail) {
 <style lang="scss" module>
 .root {
 	padding: 28px 32px;
-	font-size: 0.9em;
 	position: relative;
 
+	--reply-indent: calc(.5 * var(--avatar));
+
 	&.children {
-		padding: 10px 0 0 16px;
-		font-size: 1em;
+		padding: 10px 0 0 8px;
+	}
+
+	&.isReply {
+		/* @link https://utopia.fyi/clamp/calculator?a=450,580,26—36 */
+		--avatar: clamp(26px, -8.6154px + 7.6923cqi, 36px);
 	}
 }
 
 .line {
 	position: absolute;
-	height: calc(100% - 58px); // 58px of avatar height (see SkNote)
-	left: 60px;
+	left: calc(32px + .5 * var(--avatar));
 	// using solid instead of dotted, stylelistic choice
-	border-left: 2.5px solid rgb(174, 174, 174);
-	top: 86px; // 28px of .root padding, plus 58px of avatar height (see SkNote)
+	border-left: var(--thread-width) solid var(--thread);
+	top: calc(28px + var(--avatar)); // 28px of .root padding, plus 58px of avatar height (see SkNote)
+	bottom: -28px;
 }
 
 .footer {
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
 	position: relative;
 	z-index: 1;
 	margin-top: 0.4em;
-	width: max-content;
-	min-width: min-content;
-	max-width: fit-content;
+	max-width: 400px;
 }
 
 .main {
-	display: flex;
+	position: relative;
+	display:  flex;
+
+	:is(.detailed, .isReply) &::after {
+		content: "";
+		position: absolute;
+		top: -12px;
+		right: -12px;
+		left: -12px;
+		bottom: -12px;
+		background: var(--panelHighlight);
+		border-radius: var(--radius);
+		opacity: 0;
+		transition: opacity .2s, background .2s;
+		z-index: -1;
+	}
+
+	:is(.detailed, .isReply) &:hover::after,
+	:is(.detailed, .isReply) &:focus-within::after {
+		opacity: 1;
+	}
 }
 
 .colorBar {
@@ -471,8 +479,8 @@ if (props.detail) {
 	flex-shrink: 0;
 	display: block;
 	margin: 0 14px 0 0;
-	width: 58px;
-	height: 58px;
+	width: var(--avatar);
+	height: var(--avatar);
 	border-radius: var(--radius-sm);
 }
 
@@ -500,10 +508,6 @@ if (props.detail) {
 	padding-top: 10px;
 	opacity: 0.7;
 
-	&:not(:last-child) {
-		margin-right: 1.5em;
-	}
-
 	&:hover {
 		color: var(--fgHighlighted);
 	}
@@ -521,15 +525,11 @@ if (props.detail) {
 @container (max-width: 580px) {
 	.root {
 		padding: 28px 26px 0;
+		--avatar: 46px;
 	}
 
 	.line {
-		left: 50.5px;
-	}
-
-	.avatar {
-		width: 50px;
-		height: 50px;
+		left: calc(26px + .5 * var(--avatar));
 	}
 }
 
@@ -537,6 +537,11 @@ if (props.detail) {
 	.root {
 		padding: 23px 25px;
 	}
+
+	.line {
+		top: calc(23px + var(--avatar));
+		left: calc(25px + .5 * var(--avatar));
+	}
 }
 
 @container (max-width: 400px) {
@@ -581,21 +586,17 @@ if (props.detail) {
 @container (max-width: 480px) {
 	.root {
 		padding: 22px 24px;
+	}
 
-		&.children {
-			padding: 10px 0 0 8px;
-		}
+	.line {
+		top: calc(22px + var(--avatar));
+		left: calc(24px + .5 * var(--avatar));
 	}
 }
 
 @container (max-width: 450px) {
-	.line {
-		left: 46px;
-	}
-
-	.avatar {
-		width: 46px;
-		height: 46px;
+	.root {
+		--avatar: 44px;
 	}
 }
 
@@ -616,19 +617,19 @@ if (props.detail) {
 .threadLine {
 	width: 0;
 	flex-grow: 1;
-	border-left: 2.5px solid rgb(174, 174, 174);
-	margin-left: 29px;
+	border-left: var(--thread-width) solid var(--thread);
+	margin-left: var(--reply-indent);
 }
 
 .reply {
-	margin-left: 29px;
+	margin-left: var(--reply-indent);
 }
 
 .reply:not(:last-child) {
-	border-left: 2.5px solid rgb(174, 174, 174);
+	border-left: var(--thread-width) solid var(--thread);
 
 	&::before {
-		left: -2px;
+		left: calc(-1 * var(--thread-width));
 	}
 }
 
@@ -637,10 +638,10 @@ if (props.detail) {
 	content: '';
 	left: 0px;
 	top: -10px;
-	height: 49px;
+	height: calc(10px + 10px + .5 * var(--avatar));
 	width: 15px;
-	border-left: 2.5px solid rgb(174, 174, 174);
-	border-bottom: 2.5px solid rgb(174, 174, 174);
+	border-left: var(--thread-width) solid var(--thread);
+	border-bottom: var(--thread-width) solid var(--thread);
 	border-bottom-left-radius: 15px;
 }
 
@@ -649,40 +650,9 @@ if (props.detail) {
 	padding-left: 0 !important;
 
 	&::before {
-		left: 29px;
+		left: var(--reply-indent);
 		width: 0;
 		border-bottom: unset;
 	}
 }
-
-@container (max-width: 580px) {
-	.threadLine, .reply {
-		margin-left: 25px;
-	}
-	.reply::before {
-		height: 45px;
-	}
-	.single::before {
-		left: 25px;
-	}
-	.single {
-		margin-left: 0;
-	}
-}
-
-@container (max-width: 450px) {
-	.threadLine, .reply {
-		margin-left: 23px;
-	}
-	.reply::before {
-		height: 43px;
-	}
-	.single::before {
-		left: 23px;
-		width: 9px;
-	}
-	.single {
-		margin-left: 0;
-	}
-}
 </style>
diff --git a/packages/frontend/src/components/SkOldNoteWindow.vue b/packages/frontend/src/components/SkOldNoteWindow.vue
index f8de28e346..bed44bbb08 100644
--- a/packages/frontend/src/components/SkOldNoteWindow.vue
+++ b/packages/frontend/src/components/SkOldNoteWindow.vue
@@ -77,7 +77,7 @@
 
 <script lang="ts" setup>
 import { inject, onMounted, ref, shallowRef, computed } from 'vue';
-import * as mfm from '@sharkey/sfm-js';
+import * as mfm from '@transfem-org/sfm-js';
 import * as Misskey from 'misskey-js';
 import MkNoteSimple from '@/components/MkNoteSimple.vue';
 import MkMediaList from '@/components/MkMediaList.vue';
@@ -177,8 +177,8 @@ const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultS
 .noteHeaderAvatar {
 	display: block;
 	flex-shrink: 0;
-	width: 58px;
-	height: 58px;
+	width: var(--avatar);
+	height: var(--avatar);
 }
 
 .noteHeaderBody {
diff --git a/packages/frontend/src/components/SkOneko.vue b/packages/frontend/src/components/SkOneko.vue
new file mode 100644
index 0000000000..fbf50067a9
--- /dev/null
+++ b/packages/frontend/src/components/SkOneko.vue
@@ -0,0 +1,240 @@
+<template>
+<div ref="nekoEl" :class="$style.oneko" aria-hidden="true"></div>
+</template>
+
+<script lang="ts" setup>
+// oneko.js: https://github.com/adryd325/oneko.js
+// modified to be a vue component by ShittyKopper :3
+
+import { shallowRef, onMounted } from 'vue';
+
+const nekoEl = shallowRef<HTMLDivElement>();
+
+let nekoPosX = 32;
+let nekoPosY = 32;
+
+let mousePosX = 0;
+let mousePosY = 0;
+
+let frameCount = 0;
+let idleTime = 0;
+let idleAnimation: string|null = null;
+let idleAnimationFrame = 0;
+let lastFrameTimestamp;
+
+const nekoSpeed = 10;
+const spriteSets = {
+	idle: [[-3, -3]],
+	alert: [[-7, -3]],
+	scratchSelf: [
+		[-5, 0],
+		[-6, 0],
+		[-7, 0],
+	],
+	scratchWallN: [
+		[0, 0],
+		[0, -1],
+	],
+	scratchWallS: [
+		[-7, -1],
+		[-6, -2],
+	],
+	scratchWallE: [
+		[-2, -2],
+		[-2, -3],
+	],
+	scratchWallW: [
+		[-4, 0],
+		[-4, -1],
+	],
+	tired: [[-3, -2]],
+	sleeping: [
+		[-2, 0],
+		[-2, -1],
+	],
+	N: [
+		[-1, -2],
+		[-1, -3],
+	],
+	NE: [
+		[0, -2],
+		[0, -3],
+	],
+	E: [
+		[-3, 0],
+		[-3, -1],
+	],
+	SE: [
+		[-5, -1],
+		[-5, -2],
+	],
+	S: [
+		[-6, -3],
+		[-7, -2],
+	],
+	SW: [
+		[-5, -3],
+		[-6, -1],
+	],
+	W: [
+		[-4, -2],
+		[-4, -3],
+	],
+	NW: [
+		[-1, 0],
+		[-1, -1],
+	],
+};
+
+function init() {
+	if (!nekoEl.value) return;
+
+	nekoEl.value.style.left = `${nekoPosX - 16}px`;
+	nekoEl.value.style.top = `${nekoPosY - 16}px`;
+
+	document.addEventListener('mousemove', (event) => {
+		mousePosX = event.clientX;
+		mousePosY = event.clientY;
+	});
+
+	window.requestAnimationFrame(onAnimationFrame);
+}
+
+function onAnimationFrame(timestamp) {
+	// Stops execution if the neko element is removed from DOM
+	if (!nekoEl.value?.isConnected) {
+		return;
+	}
+	if (!lastFrameTimestamp) {
+		lastFrameTimestamp = timestamp;
+	}
+	if (timestamp - lastFrameTimestamp > 100) {
+		lastFrameTimestamp = timestamp;
+		frame();
+	}
+	window.requestAnimationFrame(onAnimationFrame);
+}
+
+// eslint-disable-next-line no-shadow
+function setSprite(name, frame) {
+	if (!nekoEl.value) return;
+
+	const sprite = spriteSets[name][frame % spriteSets[name].length];
+	nekoEl.value.style.backgroundPosition = `${sprite[0] * 32}px ${sprite[1] * 32}px`;
+}
+
+function resetIdleAnimation() {
+	idleAnimation = null;
+	idleAnimationFrame = 0;
+}
+
+function idle() {
+	idleTime += 1;
+
+	// every ~ 20 seconds
+	if (
+		idleTime > 10 &&
+      Math.floor(Math.random() * 200) === 0 &&
+      // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+      idleAnimation == null
+	) {
+		let avalibleIdleAnimations = ['sleeping', 'scratchSelf'];
+		if (nekoPosX < 32) {
+			avalibleIdleAnimations.push('scratchWallW');
+		}
+		if (nekoPosY < 32) {
+			avalibleIdleAnimations.push('scratchWallN');
+		}
+		if (nekoPosX > window.innerWidth - 32) {
+			avalibleIdleAnimations.push('scratchWallE');
+		}
+		if (nekoPosY > window.innerHeight - 32) {
+			avalibleIdleAnimations.push('scratchWallS');
+		}
+		idleAnimation =
+        avalibleIdleAnimations[Math.floor(Math.random() * avalibleIdleAnimations.length)];
+	}
+
+	switch (idleAnimation) {
+		case 'sleeping':
+			if (idleAnimationFrame < 8) {
+				setSprite('tired', 0);
+				break;
+			}
+			setSprite('sleeping', Math.floor(idleAnimationFrame / 4));
+			if (idleAnimationFrame > 192) {
+				resetIdleAnimation();
+			}
+			break;
+		case 'scratchWallN':
+		case 'scratchWallS':
+		case 'scratchWallE':
+		case 'scratchWallW':
+		case 'scratchSelf':
+			setSprite(idleAnimation, idleAnimationFrame);
+			if (idleAnimationFrame > 9) {
+				resetIdleAnimation();
+			}
+			break;
+		default:
+			setSprite('idle', 0);
+			return;
+	}
+	idleAnimationFrame += 1;
+}
+
+function frame() {
+	if (!nekoEl.value) return;
+
+	frameCount += 1;
+	const diffX = nekoPosX - mousePosX;
+	const diffY = nekoPosY - mousePosY;
+	const distance = Math.sqrt(diffX ** 2 + diffY ** 2);
+
+	if (distance < nekoSpeed || distance < 48) {
+		idle();
+		return;
+	}
+
+	idleAnimation = null;
+	idleAnimationFrame = 0;
+
+	if (idleTime > 1) {
+		setSprite('alert', 0);
+		// count down after being alerted before moving
+		idleTime = Math.min(idleTime, 7);
+		idleTime -= 1;
+		return;
+	}
+
+	let direction;
+	direction = diffY / distance > 0.5 ? 'N' : '';
+	direction += diffY / distance < -0.5 ? 'S' : '';
+	direction += diffX / distance > 0.5 ? 'W' : '';
+	direction += diffX / distance < -0.5 ? 'E' : '';
+	setSprite(direction, frameCount);
+
+	nekoPosX -= (diffX / distance) * nekoSpeed;
+	nekoPosY -= (diffY / distance) * nekoSpeed;
+
+	nekoPosX = Math.min(Math.max(16, nekoPosX), window.innerWidth - 16);
+	nekoPosY = Math.min(Math.max(16, nekoPosY), window.innerHeight - 16);
+
+	nekoEl.value.style.left = `${nekoPosX - 16}px`;
+	nekoEl.value.style.top = `${nekoPosY - 16}px`;
+}
+
+onMounted(init);
+</script>
+
+<style module>
+.oneko {
+	width: 32px;
+	height: 32px;
+	position: fixed;
+	pointer-events: none;
+	image-rendering: pixelated;
+	z-index: 2147483647;
+	background-image: url(/client-assets/oneko.gif);
+}
+</style>
diff --git a/packages/frontend/src/components/form/link.vue b/packages/frontend/src/components/form/link.vue
index 88602a007c..8c8a343010 100644
--- a/packages/frontend/src/components/form/link.vue
+++ b/packages/frontend/src/components/form/link.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/form/section.vue b/packages/frontend/src/components/form/section.vue
index 6af63d1ec6..ad37daa265 100644
--- a/packages/frontend/src/components/form/section.vue
+++ b/packages/frontend/src/components/form/section.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/form/slot.vue b/packages/frontend/src/components/form/slot.vue
index dc4d197507..f54db0ca82 100644
--- a/packages/frontend/src/components/form/slot.vue
+++ b/packages/frontend/src/components/form/slot.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/form/split.vue b/packages/frontend/src/components/form/split.vue
index 8cb24b479e..2a015c9520 100644
--- a/packages/frontend/src/components/form/split.vue
+++ b/packages/frontend/src/components/form/split.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/form/suspense.vue b/packages/frontend/src/components/form/suspense.vue
index 933f00b081..54566dc135 100644
--- a/packages/frontend/src/components/form/suspense.vue
+++ b/packages/frontend/src/components/form/suspense.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/global/I18n.vue b/packages/frontend/src/components/global/I18n.vue
new file mode 100644
index 0000000000..162aa2bcf8
--- /dev/null
+++ b/packages/frontend/src/components/global/I18n.vue
@@ -0,0 +1,46 @@
+<template>
+<render/>
+</template>
+
+<script setup lang="ts" generic="T extends string | ParameterizedString">
+import { computed, h } from 'vue';
+import type { ParameterizedString } from '../../../../../locales/index.js';
+
+const props = withDefaults(defineProps<{
+	src: T;
+	tag?: string;
+	// eslint-disable-next-line vue/require-default-prop
+	textTag?: string;
+}>(), {
+	tag: 'span',
+});
+
+const slots = defineSlots<T extends ParameterizedString<infer R> ? { [K in R]: () => unknown } : NonNullable<unknown>>();
+
+const parsed = computed(() => {
+	let str = props.src as string;
+	const value: (string | { arg: string; })[] = [];
+	for (;;) {
+		const nextBracketOpen = str.indexOf('{');
+		const nextBracketClose = str.indexOf('}');
+
+		if (nextBracketOpen === -1) {
+			value.push(str);
+			break;
+		} else {
+			if (nextBracketOpen > 0) value.push(str.substring(0, nextBracketOpen));
+			value.push({
+				arg: str.substring(nextBracketOpen + 1, nextBracketClose),
+			});
+		}
+
+		str = str.substring(nextBracketClose + 1);
+	}
+
+	return value;
+});
+
+const render = () => {
+	return h(props.tag, parsed.value.map(x => typeof x === 'string' ? (props.textTag ? h(props.textTag, x) : x) : slots[x.arg]()));
+};
+</script>
diff --git a/packages/frontend/src/components/global/MkA.stories.impl.ts b/packages/frontend/src/components/global/MkA.stories.impl.ts
index 62f4805a11..c1d8cf0ca6 100644
--- a/packages/frontend/src/components/global/MkA.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkA.stories.impl.ts
@@ -1,11 +1,10 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { expect } from '@storybook/jest';
-import { userEvent, within } from '@storybook/testing-library';
+import { expect, userEvent, within } from '@storybook/test';
 import { StoryObj } from '@storybook/vue3';
 import MkA from './MkA.vue';
 import { tick } from '@/scripts/test-utils.js';
@@ -33,7 +32,8 @@ export const Default = {
 	async play({ canvasElement }) {
 		const canvas = within(canvasElement);
 		const a = canvas.getByRole<HTMLAnchorElement>('link');
-		await expect(a.href).toMatch(/^https?:\/\/.*#test$/);
+		// FIXME: 通るけどその後落ちるのでコメントアウト
+		// await expect(a.href).toMatch(/^https?:\/\/.*#test$/);
 		await userEvent.pointer({ keys: '[MouseRight]', target: a });
 		await tick();
 		const menu = canvas.getByRole('menu');
@@ -45,6 +45,7 @@ export const Default = {
 	},
 	args: {
 		to: '#test',
+		behavior: 'browser',
 	},
 	parameters: {
 		layout: 'centered',
diff --git a/packages/frontend/src/components/global/MkA.vue b/packages/frontend/src/components/global/MkA.vue
index e2b59869a4..b3c58cf235 100644
--- a/packages/frontend/src/components/global/MkA.vue
+++ b/packages/frontend/src/components/global/MkA.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -15,7 +15,7 @@ import * as os from '@/os.js';
 import copyToClipboard from '@/scripts/copy-to-clipboard.js';
 import { url } from '@/config.js';
 import { i18n } from '@/i18n.js';
-import { useRouter } from '@/router.js';
+import { useRouter } from '@/router/supplier.js';
 
 const props = withDefaults(defineProps<{
 	to: string;
diff --git a/packages/frontend/src/components/global/MkAcct.stories.impl.ts b/packages/frontend/src/components/global/MkAcct.stories.impl.ts
index 49ec61211c..04960ec60c 100644
--- a/packages/frontend/src/components/global/MkAcct.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkAcct.stories.impl.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/components/global/MkAcct.vue b/packages/frontend/src/components/global/MkAcct.vue
index 594494f3c8..8cb082585b 100644
--- a/packages/frontend/src/components/global/MkAcct.vue
+++ b/packages/frontend/src/components/global/MkAcct.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -21,7 +21,7 @@ import { host as hostRaw } from '@/config.js';
 import { defaultStore } from '@/store.js';
 
 defineProps<{
-	user: Misskey.entities.UserDetailed;
+	user: Misskey.entities.User;
 	detail?: boolean;
 }>();
 
diff --git a/packages/frontend/src/components/global/MkAd.stories.impl.ts b/packages/frontend/src/components/global/MkAd.stories.impl.ts
index 5ae45ec58f..f6cdc2bf23 100644
--- a/packages/frontend/src/components/global/MkAd.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkAd.stories.impl.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/components/global/MkAd.vue b/packages/frontend/src/components/global/MkAd.vue
index b3eb6d681f..f13a161ae8 100644
--- a/packages/frontend/src/components/global/MkAd.vue
+++ b/packages/frontend/src/components/global/MkAd.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/global/MkAvatar.stories.impl.ts b/packages/frontend/src/components/global/MkAvatar.stories.impl.ts
index 515d7eab18..933754ec4c 100644
--- a/packages/frontend/src/components/global/MkAvatar.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkAvatar.stories.impl.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/components/global/MkAvatar.vue b/packages/frontend/src/components/global/MkAvatar.vue
index 4a876931c3..de62fe12a9 100644
--- a/packages/frontend/src/components/global/MkAvatar.vue
+++ b/packages/frontend/src/components/global/MkAvatar.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<img
 			v-for="decoration in decorations ?? user.avatarDecorations"
 			:class="[$style.decoration]"
-			:src="decoration.url"
+			:src="getDecorationUrl(decoration)"
 			:style="{
 				rotate: getDecorationAngle(decoration),
 				scale: getDecorationScale(decoration),
@@ -81,15 +81,22 @@ const bound = computed(() => props.link
 	? { to: userPage(props.user), target: props.target }
 	: {});
 
-const url = computed(() => (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.dataSaver.avatar)
-	? getStaticImageUrl(props.user.avatarUrl)
-	: props.user.avatarUrl);
+const url = computed(() => {
+	if (props.user.avatarUrl == null) return null;
+	if (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.dataSaver.avatar) return getStaticImageUrl(props.user.avatarUrl);
+	return props.user.avatarUrl;
+});
 
 function onClick(ev: MouseEvent): void {
 	if (props.link) return;
 	emit('click', ev);
 }
 
+function getDecorationUrl(decoration: Omit<Misskey.entities.UserDetailed['avatarDecorations'][number], 'id'>) {
+	if (defaultStore.state.disableShowingAnimatedImages || defaultStore.state.dataSaver.avatar) return getStaticImageUrl(decoration.url);
+	return decoration.url;
+}
+
 function getDecorationAngle(decoration: Omit<Misskey.entities.UserDetailed['avatarDecorations'][number], 'id'>) {
 	const angle = decoration.angle ?? 0;
 	return angle === 0 ? undefined : `${angle * 360}deg`;
@@ -109,6 +116,7 @@ function getDecorationOffset(decoration: Omit<Misskey.entities.UserDetailed['ava
 const color = ref<string | undefined>();
 
 watch(() => props.user.avatarBlurhash, () => {
+	if (props.user.avatarBlurhash == null) return;
 	color.value = extractAvgColorFromBlurhash(props.user.avatarBlurhash);
 }, {
 	immediate: true,
diff --git a/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts b/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts
index 7df49a2066..e4e90cddd5 100644
--- a/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkCondensedLine.stories.impl.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/components/global/MkCondensedLine.vue b/packages/frontend/src/components/global/MkCondensedLine.vue
index 2ed615f5ff..7c4957d77f 100644
--- a/packages/frontend/src/components/global/MkCondensedLine.vue
+++ b/packages/frontend/src/components/global/MkCondensedLine.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/global/MkCustomEmoji.stories.impl.ts b/packages/frontend/src/components/global/MkCustomEmoji.stories.impl.ts
index f50217b70d..9e6177045d 100644
--- a/packages/frontend/src/components/global/MkCustomEmoji.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkCustomEmoji.stories.impl.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -48,3 +48,18 @@ export const Missing = {
 		name: Default.args.name,
 	},
 } satisfies StoryObj<typeof MkCustomEmoji>;
+export const ErrorToText = {
+	...Default,
+	args: {
+		url: 'https://example.com/404',
+		name: Default.args.name,
+	},
+} satisfies StoryObj<typeof MkCustomEmoji>;
+export const ErrorToImage = {
+	...Default,
+	args: {
+		url: 'https://example.com/404',
+		name: Default.args.name,
+		fallbackToImage: true,
+	},
+} satisfies StoryObj<typeof MkCustomEmoji>;
diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue
index e8732d1b16..b57a311c0c 100644
--- a/packages/frontend/src/components/global/MkCustomEmoji.vue
+++ b/packages/frontend/src/components/global/MkCustomEmoji.vue
@@ -1,10 +1,16 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<span v-if="errored">:{{ customEmojiName }}:</span>
+<img
+	v-if="errored && fallbackToImage"
+	:class="[$style.root, { [$style.normal]: normal, [$style.noStyle]: noStyle }]"
+	src="/client-assets/dummy.png"
+	:title="alt"
+/>
+<span v-else-if="errored">:{{ customEmojiName }}:</span>
 <img
 	v-else
 	:class="[$style.root, { [$style.normal]: normal, [$style.noStyle]: noStyle }]"
@@ -24,9 +30,11 @@ import { getProxiedImageUrl, getStaticImageUrl } from '@/scripts/media-proxy.js'
 import { defaultStore } from '@/store.js';
 import { customEmojisMap } from '@/custom-emojis.js';
 import * as os from '@/os.js';
+import { misskeyApiGet } from '@/scripts/misskey-api.js';
 import copyToClipboard from '@/scripts/copy-to-clipboard.js';
 import * as sound from '@/scripts/sound.js';
 import { i18n } from '@/i18n.js';
+import MkCustomEmojiDetailedDialog from '@/components/MkCustomEmojiDetailedDialog.vue';
 
 const props = defineProps<{
 	name: string;
@@ -37,6 +45,7 @@ const props = defineProps<{
 	useOriginalSize?: boolean;
 	menu?: boolean;
 	menuReaction?: boolean;
+	fallbackToImage?: boolean;
 }>();
 
 const react = inject<((name: string) => void) | null>('react', null);
@@ -55,7 +64,7 @@ const rawUrl = computed(() => {
 });
 
 const url = computed(() => {
-	if (rawUrl.value == null) return null;
+	if (rawUrl.value == null) return undefined;
 
 	const proxied =
 		(rawUrl.value.startsWith('/emoji/') || (props.useOriginalSize && isLocal.value))
@@ -91,9 +100,21 @@ function onClick(ev: MouseEvent) {
 			icon: 'ph-smiley ph-bold ph-lg',
 			action: () => {
 				react(`:${props.name}:`);
-				sound.play('reaction');
+				sound.playMisskeySfx('reaction');
 			},
-		}] : [])], ev.currentTarget ?? ev.target);
+		}] : []), {
+			text: i18n.ts.info,
+			icon: 'ph-info ph-bold ph-lg',
+			action: async () => {
+				os.popup(MkCustomEmojiDetailedDialog, {
+					emoji: await misskeyApiGet('emoji', {
+						name: customEmojiName.value,
+					}),
+				}, {
+					anchor: ev.target,
+				});
+			},
+		}], ev.currentTarget ?? ev.target);
 	}
 }
 </script>
diff --git a/packages/frontend/src/components/global/MkEllipsis.stories.impl.ts b/packages/frontend/src/components/global/MkEllipsis.stories.impl.ts
index 32deaae8e2..6a8fcf4fe3 100644
--- a/packages/frontend/src/components/global/MkEllipsis.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkEllipsis.stories.impl.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/components/global/MkEllipsis.vue b/packages/frontend/src/components/global/MkEllipsis.vue
index 5cc07f7040..4ba6be10fe 100644
--- a/packages/frontend/src/components/global/MkEllipsis.vue
+++ b/packages/frontend/src/components/global/MkEllipsis.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/global/MkEmoji.stories.impl.ts b/packages/frontend/src/components/global/MkEmoji.stories.impl.ts
index c8beec7e8f..309c015757 100644
--- a/packages/frontend/src/components/global/MkEmoji.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkEmoji.stories.impl.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/components/global/MkEmoji.vue b/packages/frontend/src/components/global/MkEmoji.vue
index b1d62db33c..2e7a0c5bb7 100644
--- a/packages/frontend/src/components/global/MkEmoji.vue
+++ b/packages/frontend/src/components/global/MkEmoji.vue
@@ -1,19 +1,18 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
 <img v-if="!useOsNativeEmojis" :class="$style.root" :src="url" :alt="props.emoji" decoding="async" @pointerenter="computeTitle" @click="onClick" v-on:click.stop/>
-<span v-else-if="useOsNativeEmojis" :alt="props.emoji" @pointerenter="computeTitle" @click="onClick" v-on:click.stop>{{ props.emoji }}</span>
-<span v-else>{{ emoji }}</span>
+<span v-else :alt="props.emoji" @pointerenter="computeTitle" @click="onClick" v-on:click.stop>{{ colorizedNativeEmoji }}</span>
 </template>
 
 <script lang="ts" setup>
 import { computed, inject } from 'vue';
-import { char2twemojiFilePath, char2fluentEmojiFilePath } from '@/scripts/emoji-base.js';
+import { char2fluentEmojiFilePath, char2twemojiFilePath, char2tossfaceFilePath } from '@/scripts/emoji-base.js';
 import { defaultStore } from '@/store.js';
-import { getEmojiName } from '@/scripts/emojilist.js';
+import { colorizeEmoji, getEmojiName } from '@/scripts/emojilist.js';
 import * as os from '@/os.js';
 import copyToClipboard from '@/scripts/copy-to-clipboard.js';
 import * as sound from '@/scripts/sound.js';
@@ -27,17 +26,15 @@ const props = defineProps<{
 
 const react = inject<((name: string) => void) | null>('react', null);
 
-const char2path = defaultStore.state.emojiStyle === 'twemoji' ? char2twemojiFilePath : char2fluentEmojiFilePath;
+const char2path = defaultStore.state.emojiStyle === 'twemoji' ? char2twemojiFilePath : defaultStore.state.emojiStyle === 'tossface' ? char2tossfaceFilePath : char2fluentEmojiFilePath;
 
 const useOsNativeEmojis = computed(() => defaultStore.state.emojiStyle === 'native');
-const url = computed(() => {
-	return char2path(props.emoji);
-});
+const url = computed(() => char2path(props.emoji));
+const colorizedNativeEmoji = computed(() => colorizeEmoji(props.emoji));
 
 // Searching from an array with 2000 items for every emoji felt like too energy-consuming, so I decided to do it lazily on pointerenter
 function computeTitle(event: PointerEvent): void {
-	const title = getEmojiName(props.emoji as string) ?? props.emoji as string;
-	(event.target as HTMLElement).title = title;
+	(event.target as HTMLElement).title = getEmojiName(props.emoji);
 }
 
 function onClick(ev: MouseEvent) {
@@ -57,7 +54,7 @@ function onClick(ev: MouseEvent) {
 			icon: 'ph-smiley ph-bold ph-lg',
 			action: () => {
 				react(props.emoji);
-				sound.play('reaction');
+				sound.playMisskeySfx('reaction');
 			},
 		}] : [])], ev.currentTarget ?? ev.target);
 	}
diff --git a/packages/frontend/src/components/global/MkError.stories.impl.ts b/packages/frontend/src/components/global/MkError.stories.impl.ts
index cf0a1dbb5f..daef04cd87 100644
--- a/packages/frontend/src/components/global/MkError.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkError.stories.impl.ts
@@ -1,12 +1,11 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
 import { action } from '@storybook/addon-actions';
-import { expect } from '@storybook/jest';
-import { waitFor } from '@storybook/testing-library';
+import { expect, waitFor } from '@storybook/test';
 import { StoryObj } from '@storybook/vue3';
 import MkError from './MkError.vue';
 export const Default = {
diff --git a/packages/frontend/src/components/global/MkError.stories.meta.ts b/packages/frontend/src/components/global/MkError.stories.meta.ts
index a3955c5786..1abbc56f50 100644
--- a/packages/frontend/src/components/global/MkError.stories.meta.ts
+++ b/packages/frontend/src/components/global/MkError.stories.meta.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/components/global/MkError.vue b/packages/frontend/src/components/global/MkError.vue
index 47b42467d6..2976cd7be8 100644
--- a/packages/frontend/src/components/global/MkError.vue
+++ b/packages/frontend/src/components/global/MkError.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/global/MkFooterSpacer.vue b/packages/frontend/src/components/global/MkFooterSpacer.vue
index e78df6b8d9..1a75855fa1 100644
--- a/packages/frontend/src/components/global/MkFooterSpacer.vue
+++ b/packages/frontend/src/components/global/MkFooterSpacer.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/global/MkLazy.vue b/packages/frontend/src/components/global/MkLazy.vue
index 6d7ff4ca49..f35932ae77 100644
--- a/packages/frontend/src/components/global/MkLazy.vue
+++ b/packages/frontend/src/components/global/MkLazy.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/global/MkLoading.stories.impl.ts b/packages/frontend/src/components/global/MkLoading.stories.impl.ts
index 9cedd68fd8..c781ad0479 100644
--- a/packages/frontend/src/components/global/MkLoading.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkLoading.stories.impl.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/components/global/MkLoading.vue b/packages/frontend/src/components/global/MkLoading.vue
index 3f34e83f58..49d8ace37b 100644
--- a/packages/frontend/src/components/global/MkLoading.vue
+++ b/packages/frontend/src/components/global/MkLoading.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.stories.impl.ts b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.stories.impl.ts
index 9cdb490e4b..730351f795 100644
--- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.stories.impl.ts
@@ -1,12 +1,11 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
 import { StoryObj } from '@storybook/vue3';
-import { within } from '@storybook/testing-library';
-import { expect } from '@storybook/jest';
+import { expect, within } from '@storybook/test';
 import MkMisskeyFlavoredMarkdown from './MkMisskeyFlavoredMarkdown.js';
 export const Default = {
 	render(args) {
diff --git a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts
index a3bfdf0bb4..f8b5fcfedc 100644
--- a/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts
+++ b/packages/frontend/src/components/global/MkMisskeyFlavoredMarkdown.ts
@@ -1,10 +1,10 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import { VNode, h, defineAsyncComponent, SetupContext } from 'vue';
-import * as mfm from '@sharkey/sfm-js';
+import * as mfm from '@transfem-org/sfm-js';
 import * as Misskey from 'misskey-js';
 import MkUrl from '@/components/global/MkUrl.vue';
 import MkTime from '@/components/global/MkTime.vue';
@@ -13,12 +13,14 @@ import MkMention from '@/components/MkMention.vue';
 import MkEmoji from '@/components/global/MkEmoji.vue';
 import MkCustomEmoji from '@/components/global/MkCustomEmoji.vue';
 import MkCode from '@/components/MkCode.vue';
+import MkCodeInline from '@/components/MkCodeInline.vue';
 import MkGoogle from '@/components/MkGoogle.vue';
 import MkSparkle from '@/components/MkSparkle.vue';
 import MkA from '@/components/global/MkA.vue';
 import { host } from '@/config.js';
 import { defaultStore } from '@/store.js';
 import { nyaize as doNyaize } from '@/scripts/nyaize.js';
+import { safeParseFloat } from '@/scripts/safe-parse.js';
 
 const QUOTE_STYLE = `
 display: block;
@@ -35,7 +37,7 @@ type MfmProps = {
 	nowrap?: boolean;
 	author?: Misskey.entities.UserLite;
 	isNote?: boolean;
-	emojiUrls?: string[];
+	emojiUrls?: Record<string, string>;
 	rootScale?: number;
 	nyaize?: boolean | 'respect';
 	parsedNodes?: mfm.MfmNode[] | null;
@@ -49,22 +51,28 @@ type MfmEvents = {
 };
 
 // eslint-disable-next-line import/no-default-export
-export default function(props: MfmProps, context: SetupContext<MfmEvents>) {
+export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEvents>['emit'] }) {
 	const isNote = props.isNote ?? true;
-	const shouldNyaize = props.nyaize ? props.nyaize === 'respect' ? props.author?.isCat ? props.author?.speakAsCat : false : false : false;
+	const shouldNyaize = props.nyaize ? props.nyaize === 'respect' ? props.author?.isCat ? props.author.speakAsCat : false : false : false;
 
 	// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 	if (props.text == null || props.text === '') return;
 
 	const rootAst = props.parsedNodes ?? (props.plain ? mfm.parseSimple : mfm.parse)(props.text);
 
-	const validTime = (t: string | null | undefined) => {
+	const validTime = (t: string | boolean | null | undefined) => {
 		if (t == null) return null;
+		if (typeof t === 'boolean') return null;
 		return t.match(/^[0-9.]+s$/) ? t : null;
 	};
 
 	const useAnim = defaultStore.state.advancedMfm && defaultStore.state.animatedMfm ? true : props.isAnim ? true : false;
 
+	const validColor = (c: unknown): string | null => {
+		if (typeof c !== 'string') return null;
+		return c.match(/^[0-9a-f]{3,6}$/i) ? c : null;
+	};
+
 	const MkFormula = defineAsyncComponent(() => import('@/components/MkFormula.vue'));
 
 	/**
@@ -115,7 +123,7 @@ export default function(props: MfmProps, context: SetupContext<MfmEvents>) {
 					case 'tada': {
 						const speed = validTime(token.props.args.speed) ?? '1s';
 						const delay = validTime(token.props.args.delay) ?? '0s';
-						style = 'font-size: 150%;' + (useAnim ? `animation: tada ${speed} linear infinite both; animation-delay: ${delay};` : '');
+						style = 'font-size: 150%;' + (useAnim ? `animation: global-tada ${speed} linear infinite both; animation-delay: ${delay};` : '');
 						break;
 					}
 					case 'jelly': {
@@ -220,14 +228,14 @@ export default function(props: MfmProps, context: SetupContext<MfmEvents>) {
 						return h(MkSparkle, {}, genEl(token.children, scale));
 					}
 					case 'rotate': {
-						const degrees = parseFloat(token.props.args.deg ?? '90');
+						const degrees = safeParseFloat(token.props.args.deg) ?? 90;
 						style = `transform: rotate(${degrees}deg); transform-origin: center center;`;
 						break;
 					}
 					case 'position': {
 						if (!defaultStore.state.advancedMfm) break;
-						const x = parseFloat(token.props.args.x ?? '0');
-						const y = parseFloat(token.props.args.y ?? '0');
+						const x = safeParseFloat(token.props.args.x) ?? 0;
+						const y = safeParseFloat(token.props.args.y) ?? 0;
 						style = `transform: translateX(${x}em) translateY(${y}em);`;
 						break;
 					}
@@ -236,24 +244,38 @@ export default function(props: MfmProps, context: SetupContext<MfmEvents>) {
 							style = '';
 							break;
 						}
-						const x = Math.min(parseFloat(token.props.args.x ?? '1'), 5);
-						const y = Math.min(parseFloat(token.props.args.y ?? '1'), 5);
+						const x = Math.min(safeParseFloat(token.props.args.x) ?? 1, 5);
+						const y = Math.min(safeParseFloat(token.props.args.y) ?? 1, 5);
 						style = `transform: scale(${x}, ${y});`;
 						scale = scale * Math.max(x, y);
 						break;
 					}
 					case 'fg': {
-						let color = token.props.args.color;
-						if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00';
+						let color = validColor(token.props.args.color);
+						color = color ?? 'f00';
 						style = `color: #${color}; overflow-wrap: anywhere;`;
 						break;
 					}
 					case 'bg': {
-						let color = token.props.args.color;
-						if (!/^[0-9a-f]{3,6}$/i.test(color)) color = 'f00';
+						let color = validColor(token.props.args.color);
+						color = color ?? 'f00';
 						style = `background-color: #${color}; overflow-wrap: anywhere;`;
 						break;
 					}
+					case 'border': {
+						let color = validColor(token.props.args.color);
+						color = color ? `#${color}` : 'var(--accent)';
+						let b_style = token.props.args.style;
+						if (
+							typeof b_style !== 'string' ||
+							!['hidden', 'dotted', 'dashed', 'solid', 'double', 'groove', 'ridge', 'inset', 'outset']
+								.includes(b_style)
+						) b_style = 'solid';
+						const width = safeParseFloat(token.props.args.width) ?? 1;
+						const radius = safeParseFloat(token.props.args.radius) ?? 0;
+						style = `border: ${width}px ${b_style} ${color}; border-radius: ${radius}px;${token.props.args.noclip ? '' : ' overflow: clip;'}`;
+						break;
+					}
 					case 'ruby': {
 						if (token.children.length === 1) {
 							const child = token.children[0];
@@ -292,7 +314,8 @@ export default function(props: MfmProps, context: SetupContext<MfmEvents>) {
 						return h('span', { onClick(ev: MouseEvent): void {
 							ev.stopPropagation();
 							ev.preventDefault();
-							context.emit('clickEv', token.props.args.ev ?? '');
+							const clickEv = typeof token.props.args.ev === 'string' ? token.props.args.ev : '';
+							emit('clickEv', clickEv);
 						} }, genEl(token.children, scale));
 					}
 				}
@@ -353,15 +376,14 @@ export default function(props: MfmProps, context: SetupContext<MfmEvents>) {
 				return [h(MkCode, {
 					key: Math.random(),
 					code: token.props.code,
-					lang: token.props.lang,
+					lang: token.props.lang ?? undefined,
 				})];
 			}
 
 			case 'inlineCode': {
-				return [h(MkCode, {
+				return [h(MkCodeInline, {
 					key: Math.random(),
 					code: token.props.code,
-					inline: true,
 				})];
 			}
 
@@ -388,6 +410,7 @@ export default function(props: MfmProps, context: SetupContext<MfmEvents>) {
 						useOriginalSize: scale >= 2.5,
 						menu: props.enableEmojiMenu,
 						menuReaction: props.enableEmojiMenuReaction,
+						fallbackToImage: false,
 					})];
 				} else {
 					// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
@@ -397,8 +420,7 @@ export default function(props: MfmProps, context: SetupContext<MfmEvents>) {
 						return [h(MkCustomEmoji, {
 							key: Math.random(),
 							name: token.props.name,
-							// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-							url: props.emojiUrls ? props.emojiUrls[token.props.name] : null,
+							url: props.emojiUrls && props.emojiUrls[token.props.name],
 							normal: props.plain,
 							host: props.author.host,
 							useOriginalSize: scale >= 2.5,
diff --git a/packages/frontend/src/components/global/MkPageHeader.stories.impl.ts b/packages/frontend/src/components/global/MkPageHeader.stories.impl.ts
index 05d2872e91..d4327e1463 100644
--- a/packages/frontend/src/components/global/MkPageHeader.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkPageHeader.stories.impl.ts
@@ -1,10 +1,10 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { waitFor } from '@storybook/testing-library';
+import { waitFor } from '@storybook/test';
 import { StoryObj } from '@storybook/vue3';
 import MkPageHeader from './MkPageHeader.vue';
 export const Empty = {
diff --git a/packages/frontend/src/components/global/MkPageHeader.tabs.stories.impl.ts b/packages/frontend/src/components/global/MkPageHeader.tabs.stories.impl.ts
index 130dde63af..5d2126435e 100644
--- a/packages/frontend/src/components/global/MkPageHeader.tabs.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkPageHeader.tabs.stories.impl.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/components/global/MkPageHeader.tabs.vue b/packages/frontend/src/components/global/MkPageHeader.tabs.vue
index 75c8e73582..53bb5472dc 100644
--- a/packages/frontend/src/components/global/MkPageHeader.tabs.vue
+++ b/packages/frontend/src/components/global/MkPageHeader.tabs.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -38,6 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts">
 export type Tab = {
 	key: string;
+	title: string;
 	onClick?: (ev: MouseEvent) => void;
 } & (
 	| {
@@ -120,8 +121,9 @@ function onTabWheel(ev: WheelEvent) {
 
 let entering = false;
 
-async function enter(el: HTMLElement) {
+async function enter(element: Element) {
 	entering = true;
+	const el = element as HTMLElement;
 	const elementWidth = el.getBoundingClientRect().width;
 	el.style.width = '0';
 	el.style.paddingLeft = '0';
@@ -135,11 +137,12 @@ async function enter(el: HTMLElement) {
 	setTimeout(renderTab, 170);
 }
 
-function afterEnter(el: HTMLElement) {
+function afterEnter(element: Element) {
 	//el.style.width = '';
 }
 
-async function leave(el: HTMLElement) {
+async function leave(element: Element) {
+	const el = element as HTMLElement;
 	const elementWidth = el.getBoundingClientRect().width;
 	el.style.width = elementWidth + 'px';
 	el.style.paddingLeft = '';
@@ -148,7 +151,8 @@ async function leave(el: HTMLElement) {
 	el.style.paddingLeft = '0';
 }
 
-function afterLeave(el: HTMLElement) {
+function afterLeave(element: Element) {
+	const el = element as HTMLElement;
 	el.style.width = '';
 }
 
diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue
index a36d9517cd..95ac102013 100644
--- a/packages/frontend/src/components/global/MkPageHeader.vue
+++ b/packages/frontend/src/components/global/MkPageHeader.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -15,23 +15,23 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</button>
 		</div>
 
-		<template v-if="metadata">
+		<template v-if="pageMetadata">
 			<div v-if="displayBackButton && !narrow" style="margin: 0 -45px 0 0;" :class="$style.buttonsLeft">
 				<button class="_button" :class="$style.button" style="left: 5px;" @click.stop="goBack()" @touchstart="preventDrag">
 					<i class="ph-caret-left ph-bold ph-lg"></i>
 				</button>
 			</div>
 			<div v-if="!hideTitle" :class="$style.titleContainer" @click="top">
-				<div v-if="metadata.avatar" :class="$style.titleAvatarContainer">
-					<MkAvatar :class="$style.titleAvatar" :user="metadata.avatar" indicator/>
+				<div v-if="pageMetadata.avatar" :class="$style.titleAvatarContainer">
+					<MkAvatar :class="$style.titleAvatar" :user="pageMetadata.avatar" indicator/>
 				</div>
-				<i v-else-if="metadata.icon" :class="[$style.titleIcon, metadata.icon]"></i>
+				<i v-else-if="pageMetadata.icon" :class="[$style.titleIcon, pageMetadata.icon]"></i>
 
 				<div :class="$style.title">
-					<MkUserName v-if="metadata.userName" :user="metadata.userName" :nowrap="false"/>
-					<div v-else-if="metadata.title">{{ metadata.title }}</div>
-					<div v-if="metadata.subtitle" :class="$style.subtitle">
-						{{ metadata.subtitle }}
+					<MkUserName v-if="pageMetadata.userName" :user="pageMetadata.userName" :nowrap="true"/>
+					<div v-else-if="pageMetadata.title">{{ pageMetadata.title }}</div>
+					<div v-if="pageMetadata.subtitle" :class="$style.subtitle">
+						{{ pageMetadata.subtitle }}
 					</div>
 				</div>
 			</div>
@@ -55,7 +55,7 @@ import tinycolor from 'tinycolor2';
 import XTabs, { Tab } from './MkPageHeader.tabs.vue';
 import { scrollToTop } from '@/scripts/scroll.js';
 import { globalEvents } from '@/events.js';
-import { injectPageMetadata } from '@/scripts/page-metadata.js';
+import { injectReactiveMetadata } from '@/scripts/page-metadata.js';
 import { $i, openAccountMenu as openAccountMenu_ } from '@/account.js';
 import { PageHeaderItem } from '@/types/page-header.js';
 
@@ -76,7 +76,7 @@ const emit = defineEmits<{
 
 const displayBackButton = props.displayBackButton && history.state.key !== 'index' && history.length > 1 && inject('shouldBackButton', true);
 
-const metadata = injectPageMetadata();
+const pageMetadata = injectReactiveMetadata();
 
 const hideTitle = inject('shouldOmitHeaderTitle', false);
 const thin_ = props.thin || inject('shouldHeaderThin', false);
diff --git a/packages/frontend/src/components/global/MkSpacer.vue b/packages/frontend/src/components/global/MkSpacer.vue
index a384e06f77..db01c10eb0 100644
--- a/packages/frontend/src/components/global/MkSpacer.vue
+++ b/packages/frontend/src/components/global/MkSpacer.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/global/MkStickyContainer.stories.impl.ts b/packages/frontend/src/components/global/MkStickyContainer.stories.impl.ts
index 16c62ce03d..186048991e 100644
--- a/packages/frontend/src/components/global/MkStickyContainer.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkStickyContainer.stories.impl.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/components/global/MkStickyContainer.vue b/packages/frontend/src/components/global/MkStickyContainer.vue
index 70cc68b14c..89993e1b8e 100644
--- a/packages/frontend/src/components/global/MkStickyContainer.vue
+++ b/packages/frontend/src/components/global/MkStickyContainer.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -63,27 +63,32 @@ onMounted(() => {
 	watch([parentStickyTop, parentStickyBottom], calc);
 
 	watch(childStickyTop, () => {
+		if (bodyEl.value == null) return;
 		bodyEl.value.style.setProperty('--stickyTop', `${childStickyTop.value}px`);
 	}, {
 		immediate: true,
 	});
 
 	watch(childStickyBottom, () => {
+		if (bodyEl.value == null) return;
 		bodyEl.value.style.setProperty('--stickyBottom', `${childStickyBottom.value}px`);
 	}, {
 		immediate: true,
 	});
 
-	headerEl.value.style.position = 'sticky';
-	headerEl.value.style.top = 'var(--stickyTop, 0)';
-	headerEl.value.style.zIndex = '1000';
+	if (headerEl.value != null) {
+		headerEl.value.style.position = 'sticky';
+		headerEl.value.style.top = 'var(--stickyTop, 0)';
+		headerEl.value.style.zIndex = '1000';
+		observer.observe(headerEl.value);
+	}
 
-	footerEl.value.style.position = 'sticky';
-	footerEl.value.style.bottom = 'var(--stickyBottom, 0)';
-	footerEl.value.style.zIndex = '1000';
-
-	observer.observe(headerEl.value);
-	observer.observe(footerEl.value);
+	if (footerEl.value != null) {
+		footerEl.value.style.position = 'sticky';
+		footerEl.value.style.bottom = 'var(--stickyBottom, 0)';
+		footerEl.value.style.zIndex = '1000';
+		observer.observe(footerEl.value);
+	}
 });
 
 onUnmounted(() => {
diff --git a/packages/frontend/src/components/global/MkTime.stories.impl.ts b/packages/frontend/src/components/global/MkTime.stories.impl.ts
index 0eeefa4859..355c839113 100644
--- a/packages/frontend/src/components/global/MkTime.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkTime.stories.impl.ts
@@ -1,16 +1,16 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { expect } from '@storybook/jest';
+import { expect } from '@storybook/test';
 import { StoryObj } from '@storybook/vue3';
 import MkTime from './MkTime.vue';
 import { i18n } from '@/i18n.js';
 import { dateTimeFormat } from '@/scripts/intl-const.js';
 const now = new Date('2023-04-01T00:00:00.000Z');
-const future = new Date(8640000000000000);
+const future = new Date('2024-04-01T00:00:00.000Z');
 const oneHourAgo = new Date(now.getTime() - 3600000);
 const oneDayAgo = new Date(now.getTime() - 86400000);
 const oneWeekAgo = new Date(now.getTime() - 604800000);
@@ -49,11 +49,12 @@ export const Empty = {
 export const RelativeFuture = {
 	...Empty,
 	async play({ canvasElement }) {
-		await expect(canvasElement).toHaveTextContent(i18n.ts._ago.future);
+		await expect(canvasElement).toHaveTextContent(i18n.tsx._timeIn.years({ n: 1 })); // n (1) = future (2024) - now (2023)
 	},
 	args: {
 		...Empty.args,
 		time: future,
+		origin: now,
 	},
 } satisfies StoryObj<typeof MkTime>;
 export const AbsoluteFuture = {
@@ -123,7 +124,7 @@ export const DetailNow = {
 export const RelativeOneHourAgo = {
 	...Empty,
 	async play({ canvasElement }) {
-		await expect(canvasElement).toHaveTextContent(i18n.t('_ago.hoursAgo', { n: 1 }));
+		await expect(canvasElement).toHaveTextContent(i18n.tsx._ago.hoursAgo({ n: 1 }));
 	},
 	args: {
 		...Empty.args,
@@ -162,7 +163,7 @@ export const DetailOneHourAgo = {
 export const RelativeOneDayAgo = {
 	...Empty,
 	async play({ canvasElement }) {
-		await expect(canvasElement).toHaveTextContent(i18n.t('_ago.daysAgo', { n: 1 }));
+		await expect(canvasElement).toHaveTextContent(i18n.tsx._ago.daysAgo({ n: 1 }));
 	},
 	args: {
 		...Empty.args,
@@ -201,7 +202,7 @@ export const DetailOneDayAgo = {
 export const RelativeOneWeekAgo = {
 	...Empty,
 	async play({ canvasElement }) {
-		await expect(canvasElement).toHaveTextContent(i18n.t('_ago.weeksAgo', { n: 1 }));
+		await expect(canvasElement).toHaveTextContent(i18n.tsx._ago.weeksAgo({ n: 1 }));
 	},
 	args: {
 		...Empty.args,
@@ -240,7 +241,7 @@ export const DetailOneWeekAgo = {
 export const RelativeOneMonthAgo = {
 	...Empty,
 	async play({ canvasElement }) {
-		await expect(canvasElement).toHaveTextContent(i18n.t('_ago.monthsAgo', { n: 1 }));
+		await expect(canvasElement).toHaveTextContent(i18n.tsx._ago.monthsAgo({ n: 1 }));
 	},
 	args: {
 		...Empty.args,
@@ -279,7 +280,7 @@ export const DetailOneMonthAgo = {
 export const RelativeOneYearAgo = {
 	...Empty,
 	async play({ canvasElement }) {
-		await expect(canvasElement).toHaveTextContent(i18n.t('_ago.yearsAgo', { n: 1 }));
+		await expect(canvasElement).toHaveTextContent(i18n.tsx._ago.yearsAgo({ n: 1 }));
 	},
 	args: {
 		...Empty.args,
diff --git a/packages/frontend/src/components/global/MkTime.vue b/packages/frontend/src/components/global/MkTime.vue
index e11db9dc31..67532268d3 100644
--- a/packages/frontend/src/components/global/MkTime.vue
+++ b/packages/frontend/src/components/global/MkTime.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -24,7 +24,7 @@ const props = withDefaults(defineProps<{
 	mode?: 'relative' | 'absolute' | 'detail';
 	colored?: boolean;
 }>(), {
-	origin: isChromatic() ? new Date('2023-04-01T00:00:00Z') : null,
+	origin: isChromatic() ? () => new Date('2023-04-01T00:00:00Z') : null,
 	mode: 'relative',
 });
 
@@ -55,21 +55,21 @@ const relative = computed<string>(() => {
 	if (invalid) return i18n.ts._ago.invalid;
 
 	return (
-		ago.value >= 31536000 ? i18n.t('_ago.yearsAgo', { n: Math.round(ago.value / 31536000).toString() }) :
-		ago.value >= 2592000 ? i18n.t('_ago.monthsAgo', { n: Math.round(ago.value / 2592000).toString() }) :
-		ago.value >= 604800 ? i18n.t('_ago.weeksAgo', { n: Math.round(ago.value / 604800).toString() }) :
-		ago.value >= 86400 ? i18n.t('_ago.daysAgo', { n: Math.round(ago.value / 86400).toString() }) :
-		ago.value >= 3600 ? i18n.t('_ago.hoursAgo', { n: Math.round(ago.value / 3600).toString() }) :
-		ago.value >= 60 ? i18n.t('_ago.minutesAgo', { n: (~~(ago.value / 60)).toString() }) :
-		ago.value >= 10 ? i18n.t('_ago.secondsAgo', { n: (~~(ago.value % 60)).toString() }) :
+		ago.value >= 31536000 ? i18n.tsx._ago.yearsAgo({ n: Math.round(ago.value / 31536000).toString() }) :
+		ago.value >= 2592000 ? i18n.tsx._ago.monthsAgo({ n: Math.round(ago.value / 2592000).toString() }) :
+		ago.value >= 604800 ? i18n.tsx._ago.weeksAgo({ n: Math.round(ago.value / 604800).toString() }) :
+		ago.value >= 86400 ? i18n.tsx._ago.daysAgo({ n: Math.round(ago.value / 86400).toString() }) :
+		ago.value >= 3600 ? i18n.tsx._ago.hoursAgo({ n: Math.round(ago.value / 3600).toString() }) :
+		ago.value >= 60 ? i18n.tsx._ago.minutesAgo({ n: (~~(ago.value / 60)).toString() }) :
+		ago.value >= 10 ? i18n.tsx._ago.secondsAgo({ n: (~~(ago.value % 60)).toString() }) :
 		ago.value >= -3 ? i18n.ts._ago.justNow :
-		ago.value < -31536000 ? i18n.t('_timeIn.years', { n: Math.round(-ago.value / 31536000).toString() }) :
-		ago.value < -2592000 ? i18n.t('_timeIn.months', { n: Math.round(-ago.value / 2592000).toString() }) :
-		ago.value < -604800 ? i18n.t('_timeIn.weeks', { n: Math.round(-ago.value / 604800).toString() }) :
-		ago.value < -86400 ? i18n.t('_timeIn.days', { n: Math.round(-ago.value / 86400).toString() }) :
-		ago.value < -3600 ? i18n.t('_timeIn.hours', { n: Math.round(-ago.value / 3600).toString() }) :
-		ago.value < -60 ? i18n.t('_timeIn.minutes', { n: (~~(-ago.value / 60)).toString() }) :
-		i18n.t('_timeIn.seconds', { n: (~~(-ago.value % 60)).toString() })
+		ago.value < -31536000 ? i18n.tsx._timeIn.years({ n: Math.round(-ago.value / 31536000).toString() }) :
+		ago.value < -2592000 ? i18n.tsx._timeIn.months({ n: Math.round(-ago.value / 2592000).toString() }) :
+		ago.value < -604800 ? i18n.tsx._timeIn.weeks({ n: Math.round(-ago.value / 604800).toString() }) :
+		ago.value < -86400 ? i18n.tsx._timeIn.days({ n: Math.round(-ago.value / 86400).toString() }) :
+		ago.value < -3600 ? i18n.tsx._timeIn.hours({ n: Math.round(-ago.value / 3600).toString() }) :
+		ago.value < -60 ? i18n.tsx._timeIn.minutes({ n: (~~(-ago.value / 60)).toString() }) :
+		i18n.tsx._timeIn.seconds({ n: (~~(-ago.value % 60)).toString() })
 	);
 });
 
diff --git a/packages/frontend/src/components/global/MkUrl.stories.impl.ts b/packages/frontend/src/components/global/MkUrl.stories.impl.ts
index b35b6114fd..34a4adfe49 100644
--- a/packages/frontend/src/components/global/MkUrl.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkUrl.stories.impl.ts
@@ -1,13 +1,12 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { expect } from '@storybook/jest';
-import { userEvent, waitFor, within } from '@storybook/testing-library';
+import { expect, userEvent, waitFor, within } from '@storybook/test';
 import { StoryObj } from '@storybook/vue3';
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
 import { commonHandlers } from '../../../.storybook/mocks.js';
 import MkUrl from './MkUrl.vue';
 export const Default = {
@@ -59,8 +58,8 @@ export const Default = {
 		msw: {
 			handlers: [
 				...commonHandlers,
-				rest.get('/url', (req, res, ctx) => {
-					return res(ctx.json({
+				http.get('/url', () => {
+					return HttpResponse.json({
 						title: 'Misskey Hub',
 						icon: 'https://misskey-hub.net/favicon.ico',
 						description: 'Misskeyはオープンソースの分散型ソーシャルネットワーキングプラットフォームです。',
@@ -74,7 +73,7 @@ export const Default = {
 						sitename: 'misskey-hub.net',
 						sensitive: false,
 						url: 'https://misskey-hub.net/',
-					}));
+					});
 				}),
 			],
 		},
diff --git a/packages/frontend/src/components/global/MkUrl.vue b/packages/frontend/src/components/global/MkUrl.vue
index 667a113432..b810840b69 100644
--- a/packages/frontend/src/components/global/MkUrl.vue
+++ b/packages/frontend/src/components/global/MkUrl.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -7,6 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <component
 	:is="self ? 'MkA' : 'a'" ref="el" :class="$style.root" class="_link" :[attr]="self ? props.url.substring(local.length) : props.url" :rel="rel ?? 'nofollow noopener'" :target="target"
 	@contextmenu.stop="() => {}"
+	@click.stop
 >
 	<template v-if="!self">
 		<span :class="$style.schema">{{ schema }}//</span>
diff --git a/packages/frontend/src/components/global/MkUserName.stories.impl.ts b/packages/frontend/src/components/global/MkUserName.stories.impl.ts
index 8f47a6c1ab..88bf4f4e6c 100644
--- a/packages/frontend/src/components/global/MkUserName.stories.impl.ts
+++ b/packages/frontend/src/components/global/MkUserName.stories.impl.ts
@@ -1,10 +1,10 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
-import { expect } from '@storybook/jest';
+import { expect } from '@storybook/test';
 import { StoryObj } from '@storybook/vue3';
 import { userDetailed } from '../../../.storybook/fakes.js';
 import MkUserName from './MkUserName.vue';
diff --git a/packages/frontend/src/components/global/MkUserName.vue b/packages/frontend/src/components/global/MkUserName.vue
index be283ea922..c5bcf53102 100644
--- a/packages/frontend/src/components/global/MkUserName.vue
+++ b/packages/frontend/src/components/global/MkUserName.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/components/global/RouterView.stories.impl.ts b/packages/frontend/src/components/global/RouterView.stories.impl.ts
index 2fe4c53e78..5dfe12b0c9 100644
--- a/packages/frontend/src/components/global/RouterView.stories.impl.ts
+++ b/packages/frontend/src/components/global/RouterView.stories.impl.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/components/global/RouterView.vue b/packages/frontend/src/components/global/RouterView.vue
index 99ed8adbef..06cb30eff1 100644
--- a/packages/frontend/src/components/global/RouterView.vue
+++ b/packages/frontend/src/components/global/RouterView.vue
@@ -1,10 +1,13 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<KeepAlive :max="defaultStore.state.numberOfPageCache">
+<KeepAlive
+	:max="defaultStore.state.numberOfPageCache"
+	:exclude="pageCacheController"
+>
 	<Suspense :timeout="0">
 		<component :is="currentPageComponent" :key="key" v-bind="Object.fromEntries(currentPageProps)"/>
 
@@ -16,12 +19,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { inject, onBeforeUnmount, provide, shallowRef, ref } from 'vue';
-import { Resolved, Router } from '@/nirax.js';
+import { inject, onBeforeUnmount, provide, ref, shallowRef, computed, nextTick } from 'vue';
+import { IRouter, Resolved, RouteDef } from '@/nirax.js';
 import { defaultStore } from '@/store.js';
+import { globalEvents } from '@/events.js';
+import MkLoadingPage from '@/pages/_loading_.vue';
 
 const props = defineProps<{
-	router?: Router;
+	router?: IRouter;
 }>();
 
 const router = props.router ?? inject('router');
@@ -46,20 +51,47 @@ function resolveNested(current: Resolved, d = 0): Resolved | null {
 }
 
 const current = resolveNested(router.current)!;
-const currentPageComponent = shallowRef(current.route.component);
+const currentPageComponent = shallowRef('component' in current.route ? current.route.component : MkLoadingPage);
 const currentPageProps = ref(current.props);
 const key = ref(current.route.path + JSON.stringify(Object.fromEntries(current.props)));
 
 function onChange({ resolved, key: newKey }) {
 	const current = resolveNested(resolved);
-	if (current == null) return;
+	if (current == null || 'redirect' in current.route) return;
 	currentPageComponent.value = current.route.component;
 	currentPageProps.value = current.props;
 	key.value = current.route.path + JSON.stringify(Object.fromEntries(current.props));
+
+	nextTick(() => {
+		// ページ遷移完了後に再びキャッシュを有効化
+		if (clearCacheRequested.value) {
+			clearCacheRequested.value = false;
+		}
+	});
 }
 
 router.addListener('change', onChange);
 
+// #region キャッシュ制御
+
+/**
+ * キャッシュクリアが有効になったら、全キャッシュをクリアする
+ *
+ * keepAlive側にwatcherがあるのですぐ消えるとはおもうけど、念のためページ遷移完了まではキャッシュを無効化しておく。
+ * キャッシュ有効時向けにexcludeを使いたい場合は、pageCacheControllerに並列に突っ込むのではなく、下に追記すること
+ */
+const pageCacheController = computed(() => clearCacheRequested.value ? /.*/ : undefined);
+const clearCacheRequested = ref(false);
+
+globalEvents.on('requestClearPageCache', () => {
+	if (_DEV_) console.log('clear page cache requested');
+	if (!clearCacheRequested.value) {
+		clearCacheRequested.value = true;
+	}
+});
+
+// #endregion
+
 onBeforeUnmount(() => {
 	router.removeListener('change', onChange);
 });
diff --git a/packages/frontend/src/components/global/i18n.ts b/packages/frontend/src/components/global/i18n.ts
deleted file mode 100644
index 2f4d7edabd..0000000000
--- a/packages/frontend/src/components/global/i18n.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import { h } from 'vue';
-
-export default function(props: { src: string; tag?: string; textTag?: string; }, { slots }) {
-	let str = props.src;
-	const parsed = [] as (string | { arg: string; })[];
-	while (true) {
-		const nextBracketOpen = str.indexOf('{');
-		const nextBracketClose = str.indexOf('}');
-
-		if (nextBracketOpen === -1) {
-			parsed.push(str);
-			break;
-		} else {
-			if (nextBracketOpen > 0) parsed.push(str.substring(0, nextBracketOpen));
-			parsed.push({
-				arg: str.substring(nextBracketOpen + 1, nextBracketClose),
-			});
-		}
-
-		str = str.substring(nextBracketClose + 1);
-	}
-
-	return h(props.tag ?? 'span', parsed.map(x => typeof x === 'string' ? (props.textTag ? h(props.textTag, x) : x) : slots[x.arg]()));
-}
diff --git a/packages/frontend/src/components/index.ts b/packages/frontend/src/components/index.ts
index a3e13c3a50..44d8d59941 100644
--- a/packages/frontend/src/components/index.ts
+++ b/packages/frontend/src/components/index.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -16,7 +16,7 @@ import MkUserName from './global/MkUserName.vue';
 import MkEllipsis from './global/MkEllipsis.vue';
 import MkTime from './global/MkTime.vue';
 import MkUrl from './global/MkUrl.vue';
-import I18n from './global/i18n.js';
+import I18n from './global/I18n.vue';
 import RouterView from './global/RouterView.vue';
 import MkLoading from './global/MkLoading.vue';
 import MkError from './global/MkError.vue';
diff --git a/packages/frontend/src/components/page/block.type.ts b/packages/frontend/src/components/page/block.type.ts
deleted file mode 100644
index cdd39339e6..0000000000
--- a/packages/frontend/src/components/page/block.type.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-export type BlockBase = {
-	id: string;
-	type: string;
-};
-
-export type TextBlock = BlockBase & {
-	type: 'text';
-	text: string;
-};
-
-export type SectionBlock = BlockBase & {
-	type: 'section';
-	title: string;
-	children: Block[];
-};
-
-export type ImageBlock = BlockBase & {
-	type: 'image';
-	fileId: string | null;
-};
-
-export type NoteBlock = BlockBase & {
-	type: 'note';
-	detailed: boolean;
-	note: string | null;
-};
-
-export type Block =
-	TextBlock | SectionBlock | ImageBlock | NoteBlock;
diff --git a/packages/frontend/src/components/page/page.block.vue b/packages/frontend/src/components/page/page.block.vue
index 7dbbaa03b4..164720ac6b 100644
--- a/packages/frontend/src/components/page/page.block.vue
+++ b/packages/frontend/src/components/page/page.block.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -14,7 +14,6 @@ import XText from './page.text.vue';
 import XSection from './page.section.vue';
 import XImage from './page.image.vue';
 import XNote from './page.note.vue';
-import { Block } from './block.type.js';
 
 function getComponent(type: string) {
 	switch (type) {
@@ -27,7 +26,7 @@ function getComponent(type: string) {
 }
 
 defineProps<{
-	block: Block,
+	block: Misskey.entities.PageBlock,
 	h: number,
 	page: Misskey.entities.Page,
 }>();
diff --git a/packages/frontend/src/components/page/page.image.vue b/packages/frontend/src/components/page/page.image.vue
index 29aebf63e5..ced02943db 100644
--- a/packages/frontend/src/components/page/page.image.vue
+++ b/packages/frontend/src/components/page/page.image.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -14,15 +14,19 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ref } from 'vue';
+import { onMounted, ref } from 'vue';
 import * as Misskey from 'misskey-js';
-import { ImageBlock } from './block.type.js';
 import MediaImage from '@/components/MkMediaImage.vue';
 
 const props = defineProps<{
-	block: ImageBlock,
+	block: Misskey.entities.PageBlock,
 	page: Misskey.entities.Page,
 }>();
 
-const image = ref<Misskey.entities.DriveFile>(props.page.attachedFiles.find(x => x.id === props.block.fileId));
+const image = ref<Misskey.entities.DriveFile | null>(null);
+
+onMounted(() => {
+	image.value = props.page.attachedFiles.find(x => x.id === props.block.fileId) ?? null;
+});
+
 </script>
diff --git a/packages/frontend/src/components/page/page.note.vue b/packages/frontend/src/components/page/page.note.vue
index d885ebb1d6..7b56494a6e 100644
--- a/packages/frontend/src/components/page/page.note.vue
+++ b/packages/frontend/src/components/page/page.note.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -13,20 +13,20 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { onMounted, ref } from 'vue';
 import * as Misskey from 'misskey-js';
-import { NoteBlock } from './block.type.js';
 import MkNote from '@/components/MkNote.vue';
 import MkNoteDetailed from '@/components/MkNoteDetailed.vue';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 
 const props = defineProps<{
-	block: NoteBlock,
+	block: Misskey.entities.PageBlock,
 	page: Misskey.entities.Page,
 }>();
 
 const note = ref<Misskey.entities.Note | null>(null);
 
 onMounted(() => {
-	os.api('notes/show', { noteId: props.block.note })
+	if (props.block.note == null) return;
+	misskeyApi('notes/show', { noteId: props.block.note })
 		.then(result => {
 			note.value = result;
 		});
diff --git a/packages/frontend/src/components/page/page.section.vue b/packages/frontend/src/components/page/page.section.vue
index e4e5a43b59..e3d26d924f 100644
--- a/packages/frontend/src/components/page/page.section.vue
+++ b/packages/frontend/src/components/page/page.section.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -25,12 +25,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { defineAsyncComponent } from 'vue';
 import * as Misskey from 'misskey-js';
-import { SectionBlock } from './block.type.js';
 
 const XBlock = defineAsyncComponent(() => import('./page.block.vue'));
 
 defineProps<{
-	block: SectionBlock,
+	block: Misskey.entities.PageBlock,
 	h: number,
 	page: Misskey.entities.Page,
 }>();
diff --git a/packages/frontend/src/components/page/page.text.vue b/packages/frontend/src/components/page/page.text.vue
index c0849a6d42..6a9415e137 100644
--- a/packages/frontend/src/components/page/page.text.vue
+++ b/packages/frontend/src/components/page/page.text.vue
@@ -1,26 +1,25 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
 <div class="_gaps">
-	<Mfm :text="block.text" :isNote="false"/>
+	<Mfm :text="block.text ?? ''" :isNote="false"/>
 	<MkUrlPreview v-for="url in urls" :key="url" :url="url"/>
 </div>
 </template>
 
 <script lang="ts" setup>
 import { defineAsyncComponent } from 'vue';
-import * as mfm from '@sharkey/sfm-js';
+import * as mfm from '@transfem-org/sfm-js';
 import * as Misskey from 'misskey-js';
-import { TextBlock } from './block.type.js';
 import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js';
 
 const MkUrlPreview = defineAsyncComponent(() => import('@/components/MkUrlPreview.vue'));
 
 const props = defineProps<{
-	block: TextBlock,
+	block: Misskey.entities.PageBlock,
 	page: Misskey.entities.Page,
 }>();
 
diff --git a/packages/frontend/src/components/page/page.vue b/packages/frontend/src/components/page/page.vue
index 94ca7bdf04..53c70b01f4 100644
--- a/packages/frontend/src/components/page/page.vue
+++ b/packages/frontend/src/components/page/page.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/config.ts b/packages/frontend/src/config.ts
index 636e51c374..e3922a0cd5 100644
--- a/packages/frontend/src/config.ts
+++ b/packages/frontend/src/config.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -18,7 +18,7 @@ export const langs = _LANGS_;
 const preParseLocale = miLocalStorage.getItem('locale');
 export let locale = preParseLocale ? JSON.parse(preParseLocale) : null;
 export const version = _VERSION_;
-export const instanceName = siteName === 'Sharkey' ? host : siteName;
+export const instanceName = siteName === 'Sharkey' || siteName == null ? host : siteName;
 export const ui = miLocalStorage.getItem('ui');
 export const debug = miLocalStorage.getItem('debug') === 'true';
 
diff --git a/packages/frontend/src/const.ts b/packages/frontend/src/const.ts
index cdd6731269..ad798067b3 100644
--- a/packages/frontend/src/const.ts
+++ b/packages/frontend/src/const.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -128,6 +128,7 @@ export const ROLE_POLICIES = [
 	'btlAvailable',
 	'canPublicNote',
 	'canImportNotes',
+	'mentionLimit',
 	'canInvite',
 	'inviteLimit',
 	'inviteLimitCycle',
@@ -161,4 +162,28 @@ export const DEFAULT_SERVER_ERROR_IMAGE_URL = 'https://launcher.moe/error.png';
 export const DEFAULT_NOT_FOUND_IMAGE_URL = 'https://launcher.moe/missingpage.webp';
 export const DEFAULT_INFO_IMAGE_URL = 'https://launcher.moe/nothinghere.png';
 
-export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'scale', 'position', 'fg', 'bg', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'ruby', 'unixtime'];
+export const MFM_TAGS = ['tada', 'jelly', 'twitch', 'shake', 'spin', 'jump', 'bounce', 'flip', 'x2', 'x3', 'x4', 'scale', 'position', 'fg', 'bg', 'border', 'font', 'blur', 'rainbow', 'sparkle', 'rotate', 'ruby', 'unixtime'];
+export const MFM_PARAMS: Record<typeof MFM_TAGS[number], string[]> = {
+	tada: ['speed=', 'delay='],
+	jelly: ['speed=', 'delay='],
+	twitch: ['speed=', 'delay='],
+	shake: ['speed=', 'delay='],
+	spin: ['speed=', 'delay=', 'left', 'alternate', 'x', 'y'],
+	jump: ['speed=', 'delay='],
+	bounce: ['speed=', 'delay='],
+	flip: ['h', 'v'],
+	x2: [],
+	x3: [],
+	x4: [],
+	scale: ['x=', 'y='],
+	position: ['x=', 'y='],
+	fg: ['color='],
+	bg: ['color='],
+  border: ['width=', 'style=', 'color=', 'radius=', 'noclip'],
+	font: ['serif', 'monospace', 'cursive', 'fantasy', 'emoji', 'math'],
+	blur: [],
+	rainbow: ['speed=', 'delay='],
+	rotate: ['deg='],
+	ruby: [],
+	unixtime: [],
+};
diff --git a/packages/frontend/src/custom-emojis.ts b/packages/frontend/src/custom-emojis.ts
index 6a48159f13..9da3582e1a 100644
--- a/packages/frontend/src/custom-emojis.ts
+++ b/packages/frontend/src/custom-emojis.ts
@@ -1,11 +1,11 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import { shallowRef, computed, markRaw, watch } from 'vue';
 import * as Misskey from 'misskey-js';
-import { api, apiGet } from '@/os.js';
+import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
 import { useStream } from '@/stream.js';
 import { get, set } from '@/scripts/idb-proxy.js';
 
@@ -52,11 +52,11 @@ export async function fetchCustomEmojis(force = false) {
 
 	let res;
 	if (force) {
-		res = await api('emojis', {});
+		res = await misskeyApi('emojis', {});
 	} else {
 		const lastFetchedAt = await get('lastEmojisFetchedAt');
 		if (lastFetchedAt && (now - lastFetchedAt) < 1000 * 60 * 60) return;
-		res = await apiGet('emojis', {});
+		res = await misskeyApiGet('emojis', {});
 	}
 
 	customEmojis.value = res.emojis;
diff --git a/packages/frontend/src/debug.ts b/packages/frontend/src/debug.ts
index 6df65bb763..8bb8012ae3 100644
--- a/packages/frontend/src/debug.ts
+++ b/packages/frontend/src/debug.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/directives/adaptive-bg.ts b/packages/frontend/src/directives/adaptive-bg.ts
index dd9691d9e2..23fd1bddf4 100644
--- a/packages/frontend/src/directives/adaptive-bg.ts
+++ b/packages/frontend/src/directives/adaptive-bg.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/directives/adaptive-border.ts b/packages/frontend/src/directives/adaptive-border.ts
index 220cf4b9a6..b436075fcd 100644
--- a/packages/frontend/src/directives/adaptive-border.ts
+++ b/packages/frontend/src/directives/adaptive-border.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/directives/anim.ts b/packages/frontend/src/directives/anim.ts
index cf49799ef5..d5b6ae4287 100644
--- a/packages/frontend/src/directives/anim.ts
+++ b/packages/frontend/src/directives/anim.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/directives/appear.ts b/packages/frontend/src/directives/appear.ts
index 3fcff4d978..706d4a9ee4 100644
--- a/packages/frontend/src/directives/appear.ts
+++ b/packages/frontend/src/directives/appear.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/directives/click-anime.ts b/packages/frontend/src/directives/click-anime.ts
index 2b3cdb27a5..5bb48bbcdd 100644
--- a/packages/frontend/src/directives/click-anime.ts
+++ b/packages/frontend/src/directives/click-anime.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/directives/follow-append.ts b/packages/frontend/src/directives/follow-append.ts
index ae3e31e291..f200f242ed 100644
--- a/packages/frontend/src/directives/follow-append.ts
+++ b/packages/frontend/src/directives/follow-append.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/directives/get-size.ts b/packages/frontend/src/directives/get-size.ts
index 56ff64035f..2655c76c48 100644
--- a/packages/frontend/src/directives/get-size.ts
+++ b/packages/frontend/src/directives/get-size.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/directives/hotkey.ts b/packages/frontend/src/directives/hotkey.ts
index 13e548299f..b082b6edf2 100644
--- a/packages/frontend/src/directives/hotkey.ts
+++ b/packages/frontend/src/directives/hotkey.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/directives/index.ts b/packages/frontend/src/directives/index.ts
index fcd7c3091e..bda7738ccd 100644
--- a/packages/frontend/src/directives/index.ts
+++ b/packages/frontend/src/directives/index.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/directives/panel.ts b/packages/frontend/src/directives/panel.ts
index 4916fcbd8d..bbcc220e09 100644
--- a/packages/frontend/src/directives/panel.ts
+++ b/packages/frontend/src/directives/panel.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/directives/ripple.ts b/packages/frontend/src/directives/ripple.ts
index cabd155c87..2d724f771e 100644
--- a/packages/frontend/src/directives/ripple.ts
+++ b/packages/frontend/src/directives/ripple.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/directives/tooltip.ts b/packages/frontend/src/directives/tooltip.ts
index 5d6ec2928b..b1c1b19907 100644
--- a/packages/frontend/src/directives/tooltip.ts
+++ b/packages/frontend/src/directives/tooltip.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/directives/user-preview.ts b/packages/frontend/src/directives/user-preview.ts
index e0fd10047a..7a008a4486 100644
--- a/packages/frontend/src/directives/user-preview.ts
+++ b/packages/frontend/src/directives/user-preview.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -99,7 +99,6 @@ export class UserPreview {
 		this.el.removeEventListener('mouseover', this.onMouseover);
 		this.el.removeEventListener('mouseleave', this.onMouseleave);
 		this.el.removeEventListener('click', this.onClick);
-		window.clearInterval(this.checkTimer);
 	}
 }
 
diff --git a/packages/frontend/src/events.ts b/packages/frontend/src/events.ts
index 90d5f6eede..d476aec04a 100644
--- a/packages/frontend/src/events.ts
+++ b/packages/frontend/src/events.ts
@@ -1,9 +1,13 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import { EventEmitter } from 'eventemitter3';
+import * as Misskey from 'misskey-js';
 
-// TODO: 型付け
-export const globalEvents = new EventEmitter();
+export const globalEvents = new EventEmitter<{
+	themeChanged: () => void;
+	clientNotification: (notification: Misskey.entities.Notification) => void;
+	requestClearPageCache: () => void;
+}>();
diff --git a/packages/frontend/src/filters/bytes.ts b/packages/frontend/src/filters/bytes.ts
index d40b020a9e..49b44167d4 100644
--- a/packages/frontend/src/filters/bytes.ts
+++ b/packages/frontend/src/filters/bytes.ts
@@ -1,14 +1,14 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 export default (v, digits = 0) => {
 	if (v == null) return '?';
-	const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
+	const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', 'RB', 'QB'];
 	if (v === 0) return '0';
 	const isMinus = v < 0;
 	if (isMinus) v = -v;
 	const i = Math.floor(Math.log(v) / Math.log(1024));
-	return (isMinus ? '-' : '') + (v / Math.pow(1024, i)).toFixed(digits).replace(/\.0+$/, '') + sizes[i];
+	return (isMinus ? '-' : '') + (v / Math.pow(1024, i)).toFixed(digits).replace(/(\.[1-9]*)0+$/, '$1').replace(/\.$/, '') + (sizes[i] ?? `e+${ i * 3 }B`);
 };
diff --git a/packages/frontend/src/filters/date.ts b/packages/frontend/src/filters/date.ts
index 23541f1094..2ffe93e868 100644
--- a/packages/frontend/src/filters/date.ts
+++ b/packages/frontend/src/filters/date.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/filters/hms.ts b/packages/frontend/src/filters/hms.ts
new file mode 100644
index 0000000000..7f90c92e99
--- /dev/null
+++ b/packages/frontend/src/filters/hms.ts
@@ -0,0 +1,65 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { i18n } from '@/i18n.js';
+
+export function hms(ms: number, options?: {
+	textFormat?: 'colon' | 'locale';
+	enableSeconds?: boolean;
+	enableMs?: boolean;
+}) {
+	const _options = {
+		textFormat: 'colon',
+		enableSeconds: true,
+		enableMs: false,
+		...options,
+	};
+
+	const res: {
+		h?: string;
+		m?: string;
+		s?: string;
+		ms?: string;
+	} = {};
+
+	// ミリ秒を秒に変換
+	let seconds = Math.floor(ms / 1000);
+
+	// 小数点以下の値(2位まで)
+	const mili = ms - seconds * 1000;
+
+	// 時間を計算
+	const hours = Math.floor(seconds / 3600);
+	res.h = format(hours);
+	seconds %= 3600;
+
+	// 分を計算
+	const minutes = Math.floor(seconds / 60);
+	res.m = format(minutes);
+	seconds %= 60;
+
+	// 残った秒数を取得
+	seconds = seconds % 60;
+	res.s = format(seconds);
+
+	// ミリ秒を取得
+	res.ms = format(Math.floor(mili / 10));
+
+	// 結果を返す
+	if (_options.textFormat === 'locale') {
+		res.h += i18n.ts._time.hour;
+		res.m += i18n.ts._time.minute;
+		res.s += i18n.ts._time.second;
+	}
+	return [
+		res.h.startsWith('00') ? undefined : res.h,
+		res.m,
+		(_options.enableSeconds ? res.s : undefined),
+	].filter(v => v !== undefined).join(_options.textFormat === 'colon' ? ':' : ' ') + (_options.enableMs ? _options.textFormat === 'colon' ? `.${res.ms}` : ` ${res.ms}` : '');
+}
+
+function format(n: number) {
+	return n.toString().padStart(2, '0');
+}
diff --git a/packages/frontend/src/filters/kmg.ts b/packages/frontend/src/filters/kmg.ts
new file mode 100644
index 0000000000..4dcb5c5800
--- /dev/null
+++ b/packages/frontend/src/filters/kmg.ts
@@ -0,0 +1,9 @@
+export default (v, fractionDigits = 0) => {
+	if (v == null) return 'N/A';
+	if (v === 0) return '0';
+	const sizes = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y', 'R', 'Q'];
+	const isMinus = v < 0;
+	if (isMinus) v = -v;
+	const i = Math.floor(Math.log(v) / Math.log(1000));
+	return (isMinus ? '-' : '') + (v / Math.pow(1000, i)).toFixed(fractionDigits).replace(/(\.[1-9]*)0+$/, '$1').replace(/\.$/, '') + (sizes[i] ?? `e+${ i * 3 }`);
+};
diff --git a/packages/frontend/src/filters/note.ts b/packages/frontend/src/filters/note.ts
index 626d03a096..ce31021469 100644
--- a/packages/frontend/src/filters/note.ts
+++ b/packages/frontend/src/filters/note.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/filters/number.ts b/packages/frontend/src/filters/number.ts
index d0e4f4991f..2e7cc60ff4 100644
--- a/packages/frontend/src/filters/number.ts
+++ b/packages/frontend/src/filters/number.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/filters/user.ts b/packages/frontend/src/filters/user.ts
index 8d20603725..b713d41789 100644
--- a/packages/frontend/src/filters/user.ts
+++ b/packages/frontend/src/filters/user.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/i18n.ts b/packages/frontend/src/i18n.ts
index 858db74dac..cc9faddb20 100644
--- a/packages/frontend/src/i18n.ts
+++ b/packages/frontend/src/i18n.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -10,6 +10,7 @@ import { I18n } from '@/scripts/i18n.js';
 
 export const i18n = markRaw(new I18n<Locale>(locale));
 
-export function updateI18n(newLocale) {
-	i18n.ts = newLocale;
+export function updateI18n(newLocale: Locale) {
+	// @ts-expect-error -- private field
+	i18n.locale = newLocale;
 }
diff --git a/packages/frontend/src/index.html b/packages/frontend/src/index.html
index 8de01e4802..ecd4f44713 100644
--- a/packages/frontend/src/index.html
+++ b/packages/frontend/src/index.html
@@ -1,5 +1,5 @@
 <!--
-  SPDX-FileCopyrightText: syuilo and other misskey contributors
+  SPDX-FileCopyrightText: syuilo and misskey-project
   SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -16,20 +16,22 @@
 	<!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
 	<meta
 		http-equiv="Content-Security-Policy"
-		content="default-src 'self';
+		content="default-src 'self' https://newassets.hcaptcha.com/ https://challenges.cloudflare.com/ http://localhost:7493/;
 			worker-src 'self';
-			script-src 'self' 'unsafe-eval';
+			script-src 'self' 'unsafe-eval' https://*.hcaptcha.com https://challenges.cloudflare.com;
 			style-src 'self' 'unsafe-inline';
-			img-src 'self' data: www.google.com xn--931a.moe localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
+			img-src 'self' data: blob: www.google.com xn--931a.moe launcher.moe localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
 			media-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;
-			connect-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000;"
+			connect-src 'self' localhost:3000 localhost:5173 127.0.0.1:5173 127.0.0.1:3000 https://newassets.hcaptcha.com;
+			frame-src *;"
 	/>
 	<meta property="og:site_name" content="[DEV BUILD] Misskey" />
 	<meta name="viewport" content="width=device-width, initial-scale=1">
+	<meta name="theme-color-orig" content="#86b300">
 </head>
 
 <body>
-<div id="misskey_app"></div>
+<div id="sharkey_app"></div>
 <script type="module" src="./_dev_boot_.ts"></script>
 </body>
 </html>
diff --git a/packages/frontend/src/instance.ts b/packages/frontend/src/instance.ts
index b09264dabb..4232cbcd78 100644
--- a/packages/frontend/src/instance.ts
+++ b/packages/frontend/src/instance.ts
@@ -1,23 +1,34 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import { computed, reactive } from 'vue';
 import * as Misskey from 'misskey-js';
-import { api } from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { miLocalStorage } from '@/local-storage.js';
 import { DEFAULT_INFO_IMAGE_URL, DEFAULT_NOT_FOUND_IMAGE_URL, DEFAULT_SERVER_ERROR_IMAGE_URL } from '@/const.js';
 
 // TODO: 他のタブと永続化されたstateを同期
 
-const cached = miLocalStorage.getItem('instance');
+//#region loader
+const providedMetaEl = document.getElementById('misskey_meta');
+
+let cachedMeta = miLocalStorage.getItem('instance') ? JSON.parse(miLocalStorage.getItem('instance')!) : null;
+let cachedAt = miLocalStorage.getItem('instanceCachedAt') ? parseInt(miLocalStorage.getItem('instanceCachedAt')!) : 0;
+const providedMeta = providedMetaEl && providedMetaEl.textContent ? JSON.parse(providedMetaEl.textContent) : null;
+const providedAt = providedMetaEl && providedMetaEl.dataset.generatedAt ? parseInt(providedMetaEl.dataset.generatedAt) : 0;
+if (providedAt > cachedAt) {
+	miLocalStorage.setItem('instance', JSON.stringify(providedMeta));
+	miLocalStorage.setItem('instanceCachedAt', providedAt.toString());
+	cachedMeta = providedMeta;
+	cachedAt = providedAt;
+}
+//#endregion
 
 // TODO: instanceをリアクティブにするかは再考の余地あり
 
-export const instance: Misskey.entities.MetaResponse = reactive(cached ? JSON.parse(cached) : {
-	// TODO: set default values
-});
+export const instance: Misskey.entities.MetaResponse = reactive(cachedMeta ?? {});
 
 export const serverErrorImageUrl = computed(() => instance.serverErrorImageUrl ?? DEFAULT_SERVER_ERROR_IMAGE_URL);
 
@@ -25,8 +36,16 @@ export const infoImageUrl = computed(() => instance.infoImageUrl ?? DEFAULT_INFO
 
 export const notFoundImageUrl = computed(() => instance.notFoundImageUrl ?? DEFAULT_NOT_FOUND_IMAGE_URL);
 
-export async function fetchInstance() {
-	const meta = await api('meta', {
+export async function fetchInstance(force = false): Promise<void> {
+	if (!force) {
+		const cachedAt = miLocalStorage.getItem('instanceCachedAt') ? parseInt(miLocalStorage.getItem('instanceCachedAt')!) : 0;
+
+		if (Date.now() - cachedAt < 1000 * 60 * 60) {
+			return;
+		}
+	}
+
+	const meta = await misskeyApi('meta', {
 		detail: false,
 	});
 
@@ -35,4 +54,5 @@ export async function fetchInstance() {
 	}
 
 	miLocalStorage.setItem('instance', JSON.stringify(instance));
+	miLocalStorage.setItem('instanceCachedAt', Date.now().toString());
 }
diff --git a/packages/frontend/src/local-storage.ts b/packages/frontend/src/local-storage.ts
index d95ff2119c..2b2f59edb3 100644
--- a/packages/frontend/src/local-storage.ts
+++ b/packages/frontend/src/local-storage.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -7,11 +7,13 @@ type Keys =
 	'v' |
 	'lastVersion' |
 	'instance' |
+	'instanceCachedAt' |
 	'account' |
 	'accounts' |
 	'latestDonationInfoShownAt' |
 	'neverShowDonationInfo' |
 	'neverShowLocalOnlyInfo' |
+	'modifiedVersionMustProminentlyOfferInAgplV3Section13Read' |
 	'lastUsed' |
 	'lang' |
 	'drafts' |
diff --git a/packages/frontend/src/navbar.ts b/packages/frontend/src/navbar.ts
index d615c751ee..b71c15d19f 100644
--- a/packages/frontend/src/navbar.ts
+++ b/packages/frontend/src/navbar.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -118,6 +118,11 @@ export const navbarItemDef = reactive({
 		show: computed(() => $i != null && instance.enableAchievements),
 		to: '/my/achievements',
 	},
+	games: {
+		title: 'Games',
+		icon: 'ph-game-controller ph-bold ph-lg',
+		to: '/games',
+	},
 	ui: {
 		title: i18n.ts.switchUi,
 		icon: 'ph-devices ph-bold ph-lg',
diff --git a/packages/frontend/src/nirax.ts b/packages/frontend/src/nirax.ts
index 9755bdcb18..616fb104e6 100644
--- a/packages/frontend/src/nirax.ts
+++ b/packages/frontend/src/nirax.ts
@@ -1,24 +1,33 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 // NIRAX --- A lightweight router
 
-import { EventEmitter } from 'eventemitter3';
 import { Component, onMounted, shallowRef, ShallowRef } from 'vue';
+import { EventEmitter } from 'eventemitter3';
 import { safeURIDecode } from '@/scripts/safe-uri-decode.js';
 
-type RouteDef = {
+interface RouteDefBase {
 	path: string;
-	component: Component;
 	query?: Record<string, string>;
 	loginRequired?: boolean;
 	name?: string;
 	hash?: string;
 	globalCacheKey?: string;
 	children?: RouteDef[];
-};
+}
+
+interface RouteDefWithComponent extends RouteDefBase {
+	component: Component,
+}
+
+interface RouteDefWithRedirect extends RouteDefBase {
+	redirect: string | ((props: Map<string, string | boolean>) => string);
+}
+
+export type RouteDef = RouteDefWithComponent | RouteDefWithRedirect;
 
 type ParsedPath = (string | {
 	name: string;
@@ -27,7 +36,40 @@ type ParsedPath = (string | {
 	optional?: boolean;
 })[];
 
-export type Resolved = { route: RouteDef; props: Map<string, string | boolean>; child?: Resolved; };
+export type RouterEvent = {
+	change: (ctx: {
+		beforePath: string;
+		path: string;
+		resolved: Resolved;
+		key: string;
+	}) => void;
+	replace: (ctx: {
+		path: string;
+		key: string;
+	}) => void;
+	push: (ctx: {
+		beforePath: string;
+		path: string;
+		route: RouteDef | null;
+		props: Map<string, string> | null;
+		key: string;
+	}) => void;
+	same: () => void;
+}
+
+export type Resolved = {
+	route: RouteDef;
+	props: Map<string, string | boolean>;
+	child?: Resolved;
+	redirected?: boolean;
+
+	/** @internal */
+	_parsedRoute: {
+		fullPath: string;
+		queryString: string | null;
+		hash: string | null;
+	};
+};
 
 function parsePath(path: string): ParsedPath {
 	const res = [] as ParsedPath;
@@ -54,34 +96,99 @@ function parsePath(path: string): ParsedPath {
 	return res;
 }
 
-export class Router extends EventEmitter<{
-	change: (ctx: {
-		beforePath: string;
-		path: string;
-		resolved: Resolved;
-		key: string;
-	}) => void;
-	replace: (ctx: {
-		path: string;
-		key: string;
-	}) => void;
-	push: (ctx: {
-		beforePath: string;
-		path: string;
-		route: RouteDef | null;
-		props: Map<string, string> | null;
-		key: string;
-	}) => void;
-	same: () => void;
-}> {
+export interface IRouter extends EventEmitter<RouterEvent> {
+	current: Resolved;
+	currentRef: ShallowRef<Resolved>;
+	currentRoute: ShallowRef<RouteDef>;
+	navHook: ((path: string, flag?: any) => boolean) | null;
+
+	/**
+	 * ルートの初期化(eventListenerの定義後に必ず呼び出すこと)
+	 */
+	init(): void;
+
+	resolve(path: string): Resolved | null;
+
+	getCurrentPath(): any;
+
+	getCurrentKey(): string;
+
+	push(path: string, flag?: any): void;
+
+	replace(path: string, key?: string | null): void;
+
+	/** @see EventEmitter */
+	eventNames(): Array<EventEmitter.EventNames<RouterEvent>>;
+
+	/** @see EventEmitter */
+	listeners<T extends EventEmitter.EventNames<RouterEvent>>(
+		event: T
+	): Array<EventEmitter.EventListener<RouterEvent, T>>;
+
+	/** @see EventEmitter */
+	listenerCount(
+		event: EventEmitter.EventNames<RouterEvent>
+	): number;
+
+	/** @see EventEmitter */
+	emit<T extends EventEmitter.EventNames<RouterEvent>>(
+		event: T,
+		...args: EventEmitter.EventArgs<RouterEvent, T>
+	): boolean;
+
+	/** @see EventEmitter */
+	on<T extends EventEmitter.EventNames<RouterEvent>>(
+		event: T,
+		fn: EventEmitter.EventListener<RouterEvent, T>,
+		context?: any
+	): this;
+
+	/** @see EventEmitter */
+	addListener<T extends EventEmitter.EventNames<RouterEvent>>(
+		event: T,
+		fn: EventEmitter.EventListener<RouterEvent, T>,
+		context?: any
+	): this;
+
+	/** @see EventEmitter */
+	once<T extends EventEmitter.EventNames<RouterEvent>>(
+		event: T,
+		fn: EventEmitter.EventListener<RouterEvent, T>,
+		context?: any
+	): this;
+
+	/** @see EventEmitter */
+	removeListener<T extends EventEmitter.EventNames<RouterEvent>>(
+		event: T,
+		fn?: EventEmitter.EventListener<RouterEvent, T>,
+		context?: any,
+		once?: boolean | undefined
+	): this;
+
+	/** @see EventEmitter */
+	off<T extends EventEmitter.EventNames<RouterEvent>>(
+		event: T,
+		fn?: EventEmitter.EventListener<RouterEvent, T>,
+		context?: any,
+		once?: boolean | undefined
+	): this;
+
+	/** @see EventEmitter */
+	removeAllListeners(
+		event?: EventEmitter.EventNames<RouterEvent>
+	): this;
+}
+
+export class Router extends EventEmitter<RouterEvent> implements IRouter {
 	private routes: RouteDef[];
 	public current: Resolved;
-	public currentRef: ShallowRef<Resolved> = shallowRef();
-	public currentRoute: ShallowRef<RouteDef> = shallowRef();
+	public currentRef: ShallowRef<Resolved>;
+	public currentRoute: ShallowRef<RouteDef>;
 	private currentPath: string;
 	private isLoggedIn: boolean;
 	private notFoundPageComponent: Component;
 	private currentKey = Date.now().toString();
+	private redirectCount = 0;
 
 	public navHook: ((path: string, flag?: any) => boolean) | null = null;
 
@@ -89,13 +196,24 @@ export class Router extends EventEmitter<{
 		super();
 
 		this.routes = routes;
+		this.current = this.resolve(currentPath)!;
+		this.currentRef = shallowRef(this.current);
+		this.currentRoute = shallowRef(this.current.route);
 		this.currentPath = currentPath;
 		this.isLoggedIn = isLoggedIn;
 		this.notFoundPageComponent = notFoundPageComponent;
-		this.navigate(currentPath, null, false);
+	}
+
+	public init() {
+		const res = this.navigate(this.currentPath, null, false);
+		this.emit('replace', {
+			path: res._parsedRoute.fullPath,
+			key: this.currentKey,
+		});
 	}
 
 	public resolve(path: string): Resolved | null {
+		const fullPath = path;
 		let queryString: string | null = null;
 		let hash: string | null = null;
 		if (path[0] === '/') path = path.substring(1);
@@ -108,6 +226,12 @@ export class Router extends EventEmitter<{
 			path = path.substring(0, path.indexOf('?'));
 		}
 
+		const _parsedRoute = {
+			fullPath,
+			queryString,
+			hash,
+		};
+
 		if (_DEV_) console.log('Routing: ', path, queryString);
 
 		function check(routes: RouteDef[], _parts: string[]): Resolved | null {
@@ -158,6 +282,7 @@ export class Router extends EventEmitter<{
 								route,
 								props,
 								child,
+								_parsedRoute,
 							};
 						} else {
 							continue forEachRouteLoop;
@@ -183,6 +308,7 @@ export class Router extends EventEmitter<{
 					return {
 						route,
 						props,
+						_parsedRoute,
 					};
 				} else {
 					if (route.children) {
@@ -192,6 +318,7 @@ export class Router extends EventEmitter<{
 								route,
 								props,
 								child,
+								_parsedRoute,
 							};
 						} else {
 							continue forEachRouteLoop;
@@ -210,7 +337,7 @@ export class Router extends EventEmitter<{
 		return check(this.routes, _parts);
 	}
 
-	private navigate(path: string, key: string | null | undefined, emitChange = true) {
+	private navigate(path: string, key: string | null | undefined, emitChange = true, _redirected = false): Resolved {
 		const beforePath = this.currentPath;
 		this.currentPath = path;
 
@@ -220,6 +347,20 @@ export class Router extends EventEmitter<{
 			throw new Error('no route found for: ' + path);
 		}
 
+		if ('redirect' in res.route) {
+			let redirectPath: string;
+			if (typeof res.route.redirect === 'function') {
+				redirectPath = res.route.redirect(res.props);
+			} else {
+				redirectPath = res.route.redirect + (res._parsedRoute.queryString ? '?' + res._parsedRoute.queryString : '') + (res._parsedRoute.hash ? '#' + res._parsedRoute.hash : '');
+			}
+			if (_DEV_) console.log('Redirecting to: ', redirectPath);
+			if (_redirected && this.redirectCount++ > 10) {
+				throw new Error('redirect loop detected');
+			}
+			return this.navigate(redirectPath, null, emitChange, true);
+		}
+
 		if (res.route.loginRequired && !this.isLoggedIn) {
 			res.route.component = this.notFoundPageComponent;
 			res.props.set('showLoginPopup', true);
@@ -241,7 +382,11 @@ export class Router extends EventEmitter<{
 			});
 		}
 
-		return res;
+		this.redirectCount = 0;
+		return {
+			...res,
+			redirected: _redirected,
+		};
 	}
 
 	public getCurrentPath() {
@@ -265,7 +410,7 @@ export class Router extends EventEmitter<{
 		const res = this.navigate(path, null);
 		this.emit('push', {
 			beforePath,
-			path,
+			path: res._parsedRoute.fullPath,
 			route: res.route,
 			props: res.props,
 			key: this.currentKey,
@@ -273,15 +418,20 @@ export class Router extends EventEmitter<{
 	}
 
 	public replace(path: string, key?: string | null) {
-		this.navigate(path, key);
+		const res = this.navigate(path, key);
+		this.emit('replace', {
+			path: res._parsedRoute.fullPath,
+			key: this.currentKey,
+		});
 	}
 }
 
-export function useScrollPositionManager(getScrollContainer: () => HTMLElement, router: Router) {
+export function useScrollPositionManager(getScrollContainer: () => HTMLElement | null, router: IRouter) {
 	const scrollPosStore = new Map<string, number>();
 
 	onMounted(() => {
 		const scrollContainer = getScrollContainer();
+		if (scrollContainer == null) return;
 
 		scrollContainer.addEventListener('scroll', () => {
 			scrollPosStore.set(router.getCurrentKey(), scrollContainer.scrollTop);
diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts
index b02f6aa640..fc73622d6b 100644
--- a/packages/frontend/src/os.ts
+++ b/packages/frontend/src/os.ts
@@ -1,16 +1,16 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 // TODO: なんでもかんでもos.tsに突っ込むのやめたいのでよしなに分割する
 
-import { pendingApiRequestsCount, api, apiGet } from '@/scripts/api.js';
-export { pendingApiRequestsCount, api, apiGet };
 import { Component, markRaw, Ref, ref, defineAsyncComponent } from 'vue';
 import { EventEmitter } from 'eventemitter3';
-import insertTextAtCursor from 'insert-text-at-cursor';
 import * as Misskey from 'misskey-js';
+import type { ComponentProps as CP } from 'vue-component-type-helpers';
+import type { Form, GetFormResultType } from '@/scripts/form.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import MkPostFormDialog from '@/components/MkPostFormDialog.vue';
 import MkWaitingDialog from '@/components/MkWaitingDialog.vue';
@@ -19,7 +19,6 @@ import MkToast from '@/components/MkToast.vue';
 import MkDialog from '@/components/MkDialog.vue';
 import MkPasswordDialog from '@/components/MkPasswordDialog.vue';
 import MkEmojiPickerDialog from '@/components/MkEmojiPickerDialog.vue';
-import MkEmojiPickerWindow from '@/components/MkEmojiPickerWindow.vue';
 import MkPopupMenu from '@/components/MkPopupMenu.vue';
 import MkContextMenu from '@/components/MkContextMenu.vue';
 import { MenuItem } from '@/types/menu.js';
@@ -28,15 +27,15 @@ import { showMovedDialog } from '@/scripts/show-moved-dialog.js';
 
 export const openingWindowsCount = ref(0);
 
-export const apiWithDialog = ((
-	endpoint: string,
-	data: Record<string, any> = {},
+export const apiWithDialog = (<E extends keyof Misskey.Endpoints = keyof Misskey.Endpoints, P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req']>(
+	endpoint: E,
+	data: P = {} as any,
 	token?: string | null | undefined,
 ) => {
-	const promise = api(endpoint, data, token);
+	const promise = misskeyApi(endpoint, data, token);
 	promiseDialog(promise, null, async (err) => {
-		let title = null;
-		let text = err.message + '\n' + (err as any).id;
+		let title: string | undefined;
+		let text = err.message + '\n' + err.id;
 		if (err.code === 'INTERNAL_ERROR') {
 			title = i18n.ts.internalServerError;
 			text = i18n.ts.internalServerErrorDescription;
@@ -83,12 +82,12 @@ export const apiWithDialog = ((
 	});
 
 	return promise;
-}) as typeof api;
+}) as typeof misskeyApi;
 
 export function promiseDialog<T extends Promise<any>>(
 	promise: T,
 	onSuccess?: ((res: any) => void) | null,
-	onFailure?: ((err: Error) => void) | null,
+	onFailure?: ((err: Misskey.api.APIError) => void) | null,
 	text?: string,
 ): T {
 	const showing = ref(true);
@@ -109,10 +108,17 @@ export function promiseDialog<T extends Promise<any>>(
 		if (onFailure) {
 			onFailure(err);
 		} else {
-			alert({
-				type: 'error',
-				text: err,
-			});
+			if (err.message) {
+				alert({
+					type: 'error',
+					text: err.message,
+				});
+			} else {
+				alert({
+					type: 'error',
+					text: err,
+				});
+			}
 		}
 	});
 
@@ -128,9 +134,10 @@ export function promiseDialog<T extends Promise<any>>(
 
 let popupIdCount = 0;
 export const popups = ref([]) as Ref<{
-	id: any;
-	component: any;
+	id: number;
+	component: Component;
 	props: Record<string, any>;
+	events: Record<string, any>;
 }[]>;
 
 const zIndexes = {
@@ -144,7 +151,34 @@ export function claimZIndex(priority: keyof typeof zIndexes = 'low'): number {
 	return zIndexes[priority];
 }
 
-export async function popup(component: Component, props: Record<string, any>, events = {}, disposeEvent?: string) {
+// InstanceType<typeof Component>['$emit'] だとインターセクション型が返ってきて
+// 使い物にならないので、代わりに ['$props'] から色々省くことで emit の型を生成する
+// FIXME: 何故か *.ts ファイルからだと型がうまく取れない?ことがあるのをなんとかしたい
+type ComponentEmit<T> = T extends new () => { $props: infer Props }
+	? [keyof Pick<T, Extract<keyof T, `on${string}`>>] extends [never]
+		? Record<string, unknown> // *.ts ファイルから型がうまく取れないとき用(これがないと {} になって型エラーがうるさい)
+		: EmitsExtractor<Props>
+	: T extends (...args: any) => any
+		? ReturnType<T> extends { [x: string]: any; __ctx?: { [x: string]: any; props: infer Props } }
+			? [keyof Pick<T, Extract<keyof T, `on${string}`>>] extends [never]
+				? Record<string, unknown>
+				: EmitsExtractor<Props>
+			: never
+		: never;
+
+// props に ref を許可するようにする
+type ComponentProps<T extends Component> = { [K in keyof CP<T>]: CP<T>[K] | Ref<CP<T>[K]> };
+
+type EmitsExtractor<T> = {
+	[K in keyof T as K extends `onVnode${string}` ? never : K extends `on${infer E}` ? Uncapitalize<E> : K extends string ? never : K]: T[K];
+};
+
+export async function popup<T extends Component>(
+	component: T,
+	props: ComponentProps<T>,
+	events: ComponentEmit<T> = {} as ComponentEmit<T>,
+	disposeEvent?: keyof ComponentEmit<T>,
+): Promise<{ dispose: () => void }> {
 	markRaw(component);
 
 	const id = ++popupIdCount;
@@ -185,12 +219,12 @@ export function toast(message: string) {
 
 export function alert(props: {
 	type?: 'error' | 'info' | 'success' | 'warning' | 'waiting' | 'question';
-	title?: string | null;
-	text?: string | null;
+	title?: string;
+	text?: string;
 }): Promise<void> {
-	return new Promise((resolve, reject) => {
+	return new Promise(resolve => {
 		popup(MkDialog, props, {
-			done: result => {
+			done: () => {
 				resolve();
 			},
 		}, 'closed');
@@ -199,12 +233,12 @@ export function alert(props: {
 
 export function confirm(props: {
 	type: 'error' | 'info' | 'success' | 'warning' | 'waiting' | 'question';
-	title?: string | null;
-	text?: string | null;
+	title?: string;
+	text?: string;
 	okText?: string;
 	cancelText?: string;
 }): Promise<{ canceled: boolean }> {
-	return new Promise((resolve, reject) => {
+	return new Promise(resolve => {
 		popup(MkDialog, {
 			...props,
 			showCancelButton: true,
@@ -225,13 +259,15 @@ export function actions<T extends {
 	danger?: boolean,
 }[]>(props: {
 	type: 'error' | 'info' | 'success' | 'warning' | 'waiting' | 'question';
-	title?: string | null;
-	text?: string | null;
+	title?: string;
+	text?: string;
 	actions: T;
-}): Promise<{ canceled: true; result: undefined; } | {
+}): Promise<{
+	canceled: true; result: undefined;
+} | {
 	canceled: false; result: T[number]['value'];
 }> {
-	return new Promise((resolve, reject) => {
+	return new Promise(resolve => {
 		popup(MkDialog, {
 			...props,
 			actions: props.actions.map(a => ({
@@ -250,19 +286,50 @@ export function actions<T extends {
 	});
 }
 
+// default が指定されていたら result は null になり得ないことを保証する overload function
 export function inputText(props: {
 	type?: 'text' | 'email' | 'password' | 'url';
-	title?: string | null;
-	text?: string | null;
+	title?: string;
+	text?: string;
+	placeholder?: string | null;
+	autocomplete?: string;
+	default: string;
+	minLength?: number;
+	maxLength?: number;
+}): Promise<{
+	canceled: true; result: undefined;
+} | {
+	canceled: false; result: string;
+}>;
+export function inputText(props: {
+	type?: 'text' | 'email' | 'password' | 'url';
+	title?: string;
+	text?: string;
 	placeholder?: string | null;
 	autocomplete?: string;
 	default?: string | null;
 	minLength?: number;
 	maxLength?: number;
-}): Promise<{ canceled: true; result: undefined; } | {
-	canceled: false; result: string;
+}): Promise<{
+	canceled: true; result: undefined;
+} | {
+	canceled: false; result: string | null;
+}>;
+export function inputText(props: {
+	type?: 'text' | 'email' | 'password' | 'url';
+	title?: string;
+	text?: string;
+	placeholder?: string | null;
+	autocomplete?: string;
+	default?: string | null;
+	minLength?: number;
+	maxLength?: number;
+}): Promise<{
+	canceled: true; result: undefined;
+} | {
+	canceled: false; result: string | null;
 }> {
-	return new Promise((resolve, reject) => {
+	return new Promise(resolve => {
 		popup(MkDialog, {
 			title: props.title,
 			text: props.text,
@@ -270,7 +337,7 @@ export function inputText(props: {
 				type: props.type,
 				placeholder: props.placeholder,
 				autocomplete: props.autocomplete,
-				default: props.default,
+				default: props.default ?? null,
 				minLength: props.minLength,
 				maxLength: props.maxLength,
 			},
@@ -282,16 +349,41 @@ export function inputText(props: {
 	});
 }
 
+// default が指定されていたら result は null になり得ないことを保証する overload function
 export function inputNumber(props: {
-	title?: string | null;
-	text?: string | null;
+	title?: string;
+	text?: string;
+	placeholder?: string | null;
+	autocomplete?: string;
+	default: number;
+}): Promise<{
+	canceled: true; result: undefined;
+} | {
+	canceled: false; result: number;
+}>;
+export function inputNumber(props: {
+	title?: string;
+	text?: string;
 	placeholder?: string | null;
 	autocomplete?: string;
 	default?: number | null;
-}): Promise<{ canceled: true; result: undefined; } | {
-	canceled: false; result: number;
+}): Promise<{
+	canceled: true; result: undefined;
+} | {
+	canceled: false; result: number | null;
+}>;
+export function inputNumber(props: {
+	title?: string;
+	text?: string;
+	placeholder?: string | null;
+	autocomplete?: string;
+	default?: number | null;
+}): Promise<{
+	canceled: true; result: undefined;
+} | {
+	canceled: false; result: number | null;
 }> {
-	return new Promise((resolve, reject) => {
+	return new Promise(resolve => {
 		popup(MkDialog, {
 			title: props.title,
 			text: props.text,
@@ -299,7 +391,7 @@ export function inputNumber(props: {
 				type: 'number',
 				placeholder: props.placeholder,
 				autocomplete: props.autocomplete,
-				default: props.default,
+				default: props.default ?? null,
 			},
 		}, {
 			done: result => {
@@ -310,34 +402,38 @@ export function inputNumber(props: {
 }
 
 export function inputDate(props: {
-	title?: string | null;
-	text?: string | null;
+	title?: string;
+	text?: string;
 	placeholder?: string | null;
-	default?: Date | null;
-}): Promise<{ canceled: true; result: undefined; } | {
+	default?: string | null;
+}): Promise<{
+	canceled: true; result: undefined;
+} | {
 	canceled: false; result: Date;
 }> {
-	return new Promise((resolve, reject) => {
+	return new Promise(resolve => {
 		popup(MkDialog, {
 			title: props.title,
 			text: props.text,
 			input: {
 				type: 'date',
 				placeholder: props.placeholder,
-				default: props.default,
+				default: props.default ?? null,
 			},
 		}, {
 			done: result => {
-				resolve(result ? { result: new Date(result.result), canceled: false } : { canceled: true });
+				resolve(result ? { result: new Date(result.result), canceled: false } : { result: undefined, canceled: true });
 			},
 		}, 'closed');
 	});
 }
 
-export function authenticateDialog(): Promise<{ canceled: true; result: undefined; } | {
+export function authenticateDialog(): Promise<{
+	canceled: true; result: undefined;
+} | {
 	canceled: false; result: { password: string; token: string | null; };
 }> {
-	return new Promise((resolve, reject) => {
+	return new Promise(resolve => {
 		popup(MkPasswordDialog, {}, {
 			done: result => {
 				resolve(result ? { canceled: false, result } : { canceled: true, result: undefined });
@@ -346,34 +442,53 @@ export function authenticateDialog(): Promise<{ canceled: true; result: undefine
 	});
 }
 
+// default が指定されていたら result は null になり得ないことを保証する overload function
 export function select<C = any>(props: {
-	title?: string | null;
-	text?: string | null;
-	default?: string | null;
-} & ({
+	title?: string;
+	text?: string;
+	default: string;
 	items: {
 		value: C;
 		text: string;
 	}[];
+}): Promise<{
+	canceled: true; result: undefined;
 } | {
-	groupedItems: {
-		label: string;
-		items: {
-			value: C;
-			text: string;
-		}[];
-	}[];
-})): Promise<{ canceled: true; result: undefined; } | {
 	canceled: false; result: C;
+}>;
+export function select<C = any>(props: {
+	title?: string;
+	text?: string;
+	default?: string | null;
+	items: {
+		value: C;
+		text: string;
+	}[];
+}): Promise<{
+	canceled: true; result: undefined;
+} | {
+	canceled: false; result: C | null;
+}>;
+export function select<C = any>(props: {
+	title?: string;
+	text?: string;
+	default?: string | null;
+	items: {
+		value: C;
+		text: string;
+	}[];
+}): Promise<{
+	canceled: true; result: undefined;
+} | {
+	canceled: false; result: C | null;
 }> {
-	return new Promise((resolve, reject) => {
+	return new Promise(resolve => {
 		popup(MkDialog, {
 			title: props.title,
 			text: props.text,
 			select: {
 				items: props.items,
-				groupedItems: props.groupedItems,
-				default: props.default,
+				default: props.default ?? null,
 			},
 		}, {
 			done: result => {
@@ -384,7 +499,7 @@ export function select<C = any>(props: {
 }
 
 export function success(): Promise<void> {
-	return new Promise((resolve, reject) => {
+	return new Promise(resolve => {
 		const showing = ref(true);
 		window.setTimeout(() => {
 			showing.value = false;
@@ -399,7 +514,7 @@ export function success(): Promise<void> {
 }
 
 export function waiting(): Promise<void> {
-	return new Promise((resolve, reject) => {
+	return new Promise(resolve => {
 		const showing = ref(true);
 		popup(MkWaitingDialog, {
 			success: false,
@@ -410,9 +525,9 @@ export function waiting(): Promise<void> {
 	});
 }
 
-export function form(title, form) {
-	return new Promise((resolve, reject) => {
-		popup(defineAsyncComponent(() => import('@/components/MkFormDialog.vue')), { title, form }, {
+export function form<F extends Form>(title: string, f: F): Promise<{ canceled: true } | { result: GetFormResultType<F> }> {
+	return new Promise(resolve => {
+		popup(defineAsyncComponent(() => import('@/components/MkFormDialog.vue')), { title, form: f }, {
 			done: result => {
 				resolve(result);
 			},
@@ -420,10 +535,11 @@ export function form(title, form) {
 	});
 }
 
-export async function selectUser(opts: { includeSelf?: boolean } = {}) {
-	return new Promise((resolve, reject) => {
+export async function selectUser(opts: { includeSelf?: boolean; localOnly?: boolean; } = {}): Promise<Misskey.entities.UserDetailed> {
+	return new Promise(resolve => {
 		popup(defineAsyncComponent(() => import('@/components/MkUserSelectDialog.vue')), {
 			includeSelf: opts.includeSelf,
+			localOnly: opts.localOnly,
 		}, {
 			ok: user => {
 				resolve(user);
@@ -433,7 +549,7 @@ export async function selectUser(opts: { includeSelf?: boolean } = {}) {
 }
 
 export async function selectDriveFile(multiple: boolean): Promise<Misskey.entities.DriveFile[]> {
-	return new Promise((resolve, reject) => {
+	return new Promise(resolve => {
 		popup(defineAsyncComponent(() => import('@/components/MkDriveSelectDialog.vue')), {
 			type: 'file',
 			multiple,
@@ -447,23 +563,23 @@ export async function selectDriveFile(multiple: boolean): Promise<Misskey.entiti
 	});
 }
 
-export async function selectDriveFolder(multiple: boolean) {
-	return new Promise((resolve, reject) => {
+export async function selectDriveFolder(multiple: boolean): Promise<Misskey.entities.DriveFolder[]> {
+	return new Promise(resolve => {
 		popup(defineAsyncComponent(() => import('@/components/MkDriveSelectDialog.vue')), {
 			type: 'folder',
 			multiple,
 		}, {
 			done: folders => {
 				if (folders) {
-					resolve(multiple ? folders : folders[0]);
+					resolve(folders);
 				}
 			},
 		}, 'closed');
 	});
 }
 
-export async function pickEmoji(src: HTMLElement | null, opts) {
-	return new Promise((resolve, reject) => {
+export async function pickEmoji(src: HTMLElement, opts: ComponentProps<typeof MkEmojiPickerDialog>): Promise<string> {
+	return new Promise(resolve => {
 		popup(MkEmojiPickerDialog, {
 			src,
 			...opts,
@@ -479,7 +595,7 @@ export async function cropImage(image: Misskey.entities.DriveFile, options: {
 	aspectRatio: number;
 	uploadFolder?: string | null;
 }): Promise<Misskey.entities.DriveFile> {
-	return new Promise((resolve, reject) => {
+	return new Promise(resolve => {
 		popup(defineAsyncComponent(() => import('@/components/MkCropperDialog.vue')), {
 			file: image,
 			aspectRatio: options.aspectRatio,
@@ -492,67 +608,13 @@ export async function cropImage(image: Misskey.entities.DriveFile, options: {
 	});
 }
 
-type AwaitType<T> =
-	T extends Promise<infer U> ? U :
-	T extends (...args: any[]) => Promise<infer V> ? V :
-	T;
-let openingEmojiPicker: AwaitType<ReturnType<typeof popup>> | null = null;
-let activeTextarea: HTMLTextAreaElement | HTMLInputElement | null = null;
-export async function openEmojiPicker(src?: HTMLElement, opts, initialTextarea: typeof activeTextarea) {
-	if (openingEmojiPicker) return;
-
-	activeTextarea = initialTextarea;
-
-	const textareas = document.querySelectorAll('textarea, input');
-	for (const textarea of Array.from(textareas)) {
-		textarea.addEventListener('focus', () => {
-			activeTextarea = textarea;
-		});
-	}
-
-	const observer = new MutationObserver(records => {
-		for (const record of records) {
-			for (const node of Array.from(record.addedNodes).filter(node => node instanceof HTMLElement) as HTMLElement[]) {
-				const textareas = node.querySelectorAll('textarea, input') as NodeListOf<NonNullable<typeof activeTextarea>>;
-				for (const textarea of Array.from(textareas).filter(textarea => textarea.dataset.preventEmojiInsert == null)) {
-					if (document.activeElement === textarea) activeTextarea = textarea;
-					textarea.addEventListener('focus', () => {
-						activeTextarea = textarea;
-					});
-				}
-			}
-		}
-	});
-
-	observer.observe(document.body, {
-		childList: true,
-		subtree: true,
-		attributes: false,
-		characterData: false,
-	});
-
-	openingEmojiPicker = await popup(MkEmojiPickerWindow, {
-		src,
-		...opts,
-	}, {
-		chosen: emoji => {
-			insertTextAtCursor(activeTextarea, emoji);
-		},
-		closed: () => {
-			openingEmojiPicker!.dispose();
-			openingEmojiPicker = null;
-			observer.disconnect();
-		},
-	});
-}
-
-export function popupMenu(items: MenuItem[] | Ref<MenuItem[]>, src?: HTMLElement | EventTarget | null, options?: {
+export function popupMenu(items: MenuItem[], src?: HTMLElement | EventTarget | null, options?: {
 	align?: string;
 	width?: number;
 	viaKeyboard?: boolean;
 	onClosing?: () => void;
 }): Promise<void> {
-	return new Promise((resolve, reject) => {
+	return new Promise(resolve => {
 		let dispose;
 		popup(MkPopupMenu, {
 			items,
@@ -574,9 +636,9 @@ export function popupMenu(items: MenuItem[] | Ref<MenuItem[]>, src?: HTMLElement
 	});
 }
 
-export function contextMenu(items: MenuItem[] | Ref<MenuItem[]>, ev: MouseEvent): Promise<void> {
+export function contextMenu(items: MenuItem[], ev: MouseEvent): Promise<void> {
 	ev.preventDefault();
-	return new Promise((resolve, reject) => {
+	return new Promise(resolve => {
 		let dispose;
 		popup(MkContextMenu, {
 			items,
@@ -595,7 +657,7 @@ export function contextMenu(items: MenuItem[] | Ref<MenuItem[]>, ev: MouseEvent)
 export function post(props: Record<string, any> = {}): Promise<void> {
 	showMovedDialog();
 
-	return new Promise((resolve, reject) => {
+	return new Promise(resolve => {
 		// NOTE: MkPostFormDialogをdynamic importするとiOSでテキストエリアに自動フォーカスできない
 		// NOTE: ただ、dynamic importしない場合、MkPostFormDialogインスタンスが使いまわされ、
 		//       Vueが渡されたコンポーネントに内部的に__propsというプロパティを生やす影響で、
@@ -621,7 +683,7 @@ export function checkExistence(fileData: ArrayBuffer): Promise<any> {
 		const data = new FormData();
 		data.append('md5', getMD5(fileData));
 
-		os.api('drive/files/find-by-hash', {
+		api('drive/files/find-by-hash', {
 			md5: getMD5(fileData)
 		}).then(resp => {
 			resolve(resp.length > 0 ? resp[0] : null);
diff --git a/packages/frontend/src/pages/_empty_.vue b/packages/frontend/src/pages/_empty_.vue
index 9403d862c2..236d3fa14d 100644
--- a/packages/frontend/src/pages/_empty_.vue
+++ b/packages/frontend/src/pages/_empty_.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/pages/_error_.vue b/packages/frontend/src/pages/_error_.vue
index 2cdf8f2e8c..6b1eff5bcb 100644
--- a/packages/frontend/src/pages/_error_.vue
+++ b/packages/frontend/src/pages/_error_.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<div>{{ i18n.ts.youShouldUpgradeClient }}</div>
 				<MkButton style="margin: 8px auto;" @click="reload">{{ i18n.ts.reload }}</MkButton>
 			</template>
-			<div><MkA to="/docs/general/troubleshooting" class="_link">{{ i18n.ts.troubleshooting }}</MkA></div>
+			<div><MkLink url="https://misskey-hub.net/docs/for-users/resources/troubleshooting/" target="_blank">{{ i18n.ts.troubleshooting }}</MkLink></div>
 			<div v-if="error" style="opacity: 0.7;">ERROR: {{ error }}</div>
 		</div>
 	</div>
@@ -28,8 +28,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { ref, computed } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkButton from '@/components/MkButton.vue';
+import MkLink from '@/components/MkLink.vue';
 import { version } from '@/config.js';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { unisonReload } from '@/scripts/unison-reload.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
@@ -46,7 +47,7 @@ const loaded = ref(false);
 const serverIsDead = ref(false);
 const meta = ref<Misskey.entities.MetaResponse | null>(null);
 
-os.api('meta', {
+misskeyApi('meta', {
 	detail: false,
 }).then(res => {
 	loaded.value = true;
@@ -66,10 +67,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.error,
 	icon: 'ph-warning ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/_loading_.vue b/packages/frontend/src/pages/_loading_.vue
index 9f3c9fd355..5175979642 100644
--- a/packages/frontend/src/pages/_loading_.vue
+++ b/packages/frontend/src/pages/_loading_.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/pages/about-sharkey.vue b/packages/frontend/src/pages/about-sharkey.vue
index 2e4ff5d041..30788e24ce 100644
--- a/packages/frontend/src/pages/about-sharkey.vue
+++ b/packages/frontend/src/pages/about-sharkey.vue
@@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 						<div class="misskey">Sharkey</div>
 						<div class="version">v{{ version }}</div>
 						<span v-for="emoji in easterEggEmojis" :key="emoji.id" class="emoji" :data-physics-x="emoji.left" :data-physics-y="emoji.top" :class="{ _physics_circle_: !emoji.emoji.startsWith(':') }">
-							<MkCustomEmoji v-if="emoji.emoji[0] === ':'" class="emoji" :name="emoji.emoji" :normal="true" :noStyle="true"/>
+							<MkCustomEmoji v-if="emoji.emoji[0] === ':'" class="emoji" :name="emoji.emoji" :normal="true" :noStyle="true" :fallbackToImage="true"/>
 							<MkEmoji v-else class="emoji" :emoji="emoji.emoji" :normal="true" :noStyle="true"/>
 						</span>
 					</div>
@@ -27,33 +27,66 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<div v-if="$i != null" style="text-align: center;">
 					<MkButton primary rounded inline @click="iLoveMisskey">I <Mfm text="$[jelly ❤]"/> #Sharkey</MkButton>
 				</div>
-				<FormSection>
+				<FormSection v-if="instance.repositoryUrl !== 'https://activitypub.software/TransFem-org/Sharkey/'">
 					<div class="_gaps_s">
-						<FormLink to="https://git.joinsharkey.org/Sharkey/Sharkey" external>
+						<MkInfo>
+							{{ i18n.tsx._aboutMisskey.thisIsModifiedVersion({ name: instance.name }) }}
+						</MkInfo>
+						<FormLink v-if="instance.repositoryUrl" :to="instance.repositoryUrl" external>
 							<template #icon><i class="ph-code ph-bold ph-lg"></i></template>
 							{{ i18n.ts._aboutMisskey.source }}
-							<template #suffix>Forgejo</template>
+						</FormLink>
+						<FormLink v-if="instance.providesTarball" :to="`/tarball/sharkey-${version}.tar.gz`" external>
+							<template #icon><i class="ph-download ph-bold ph-lg"></i></template>
+							{{ i18n.ts._aboutMisskey.source }}
+							<template #suffix>Tarball</template>
+						</FormLink>
+						<MkInfo v-if="!instance.repositoryUrl && !instance.providesTarball" warn>
+							{{ i18n.ts.sourceCodeIsNotYetProvided }}
+						</MkInfo>
+					</div>
+				</FormSection>
+				<FormSection>
+					<div class="_gaps_s">
+						<FormLink to="https://activitypub.software/TransFem-org/Sharkey/" external>
+							<template #icon><i class="ph-code ph-bold ph-lg"></i></template>
+							{{ i18n.ts._aboutMisskey.source }} ({{ i18n.ts._aboutMisskey.original_sharkey }})
+							<template #suffix>GitLab</template>
 						</FormLink>
 						<FormLink to="https://ko-fi.com/transfem" external>
 							<template #icon><i class="ph-piggy-bank ph-bold ph-lg"></i></template>
-							{{ i18n.ts._aboutMisskey.donate }}
+							{{ i18n.ts._aboutMisskey.donate_sharkey }}
 							<template #suffix>Ko-Fi</template>
 						</FormLink>
 					</div>
 				</FormSection>
+				<FormSection>
+					<div class="_gaps_s">
+						<FormLink to="https://github.com/misskey-dev/misskey" external>
+							<template #icon><i class="ph-code ph-bold ph-lg"></i></template>
+							{{ i18n.ts._aboutMisskey.source }} ({{ i18n.ts._aboutMisskey.original }})
+							<template #suffix>GitHub</template>
+						</FormLink>
+						<FormLink to="https://www.patreon.com/syuilo" external>
+							<template #icon><i class="ph-piggy-bank ph-bold ph-lg"></i></template>
+							{{ i18n.ts._aboutMisskey.donate }}
+							<template #suffix>Patreon</template>
+						</FormLink>
+					</div>
+				</FormSection>
 				<FormSection>
 					<template #label>{{ i18n.ts._aboutMisskey.projectMembers }}</template>
 					<div :class="$style.contributors" style="margin-bottom: 8px;">
-						<a href="https://git.joinsharkey.org/Marie" target="_blank" :class="$style.contributor">
-							<img src="https://git.joinsharkey.org/avatar/0d57abf583f5ed6cf37f47055a1e1aa4?size=512" :class="$style.contributorAvatar">
+						<a href="https://activitypub.software/Marie" target="_blank" :class="$style.contributor">
+							<img src="https://activitypub.software/uploads/-/system/user/avatar/2/avatar.png?width=128" :class="$style.contributorAvatar">
 							<span :class="$style.contributorUsername">@Marie</span>
 						</a>
-						<a href="https://git.joinsharkey.org/Amelia" target="_blank" :class="$style.contributor">
-							<img src="https://git.joinsharkey.org/avatars/0634b661b89d6e45137074b6ddcd0b9ffc4cf467f2188ec12416ec6f91bb9d42?size=512" :class="$style.contributorAvatar">
+						<a href="https://activitypub.software/Amelia" target="_blank" :class="$style.contributor">
+							<img src="https://activitypub.software/uploads/-/system/user/avatar/1/avatar.png?width=128" :class="$style.contributorAvatar">
 							<span :class="$style.contributorUsername">@Amelia</span>
 						</a>
 					</div>
-					<template #caption><MkLink url="https://git.joinsharkey.org/Sharkey/Sharkey/graph">{{ i18n.ts._aboutMisskey.allContributors }}</MkLink></template>
+					<template #caption><MkLink url="https://activitypub.software/TransFem-org/Sharkey/-/graphs/develop">{{ i18n.ts._aboutMisskey.allContributors }}</MkLink></template>
 				</FormSection>
 				<FormSection>
 					<template #label>Misskey Contributors</template>
@@ -88,7 +121,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 						</a>
 					</div>
 				</FormSection>
-				<FormSection>
+				<FormSection v-if="sponsors[0].length > 0">
 					<template #label>Our lovely Sponsors</template>
 					<div :class="$style.contributors">
 						<span
@@ -116,10 +149,13 @@ import FormLink from '@/components/form/link.vue';
 import FormSection from '@/components/form/section.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkLink from '@/components/MkLink.vue';
+import MkInfo from '@/components/MkInfo.vue';
 import { physics } from '@/scripts/physics.js';
 import { i18n } from '@/i18n.js';
+import { instance } from '@/instance.js';
 import { defaultStore } from '@/store.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { claimAchievement, claimedAchievements } from '@/scripts/achievements.js';
 import { $i } from '@/account.js';
@@ -132,7 +168,7 @@ const easterEggEngine = ref(null);
 const sponsors = ref([]);
 const containerEl = shallowRef<HTMLElement>();
 
-await os.api('sponsors', { forceUpdate: true }).then((res) => sponsors.value.push(res.sponsor_data));
+await misskeyApi('sponsors', { forceUpdate: true }).then((res) => sponsors.value.push(res.sponsor_data));
 
 function iconLoaded() {
 	const emojis = defaultStore.state.reactions;
@@ -179,10 +215,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.aboutMisskey,
 	icon: null,
-});
+}));
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/pages/about.emojis.vue b/packages/frontend/src/pages/about.emojis.vue
index eda6455fd6..f37e9dbf96 100644
--- a/packages/frontend/src/pages/about.emojis.vue
+++ b/packages/frontend/src/pages/about.emojis.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/pages/about.federation.vue b/packages/frontend/src/pages/about.federation.vue
index 0de000ee3e..c7f2315faa 100644
--- a/packages/frontend/src/pages/about.federation.vue
+++ b/packages/frontend/src/pages/about.federation.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -17,10 +17,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<option value="federating">{{ i18n.ts.federating }}</option>
 				<option value="subscribing">{{ i18n.ts.subscribing }}</option>
 				<option value="publishing">{{ i18n.ts.publishing }}</option>
+				<option value="bubble">Bubble</option>
 				<option value="nsfw">NSFW</option>
-				<option value="suspended">{{ i18n.ts.suspended }}</option>
-				<option value="silenced">{{ i18n.ts.silence }}</option>
-				<option value="blocked">{{ i18n.ts.blocked }}</option>
+				<option v-if="$i" value="suspended">{{ i18n.ts.suspended }}</option>
+				<option v-if="$i" value="silenced">{{ i18n.ts.silence }}</option>
+				<option v-if="$i" value="blocked">{{ i18n.ts.blocked }}</option>
 				<option value="notResponding">{{ i18n.ts.notResponding }}</option>
 			</MkSelect>
 			<MkSelect v-model="sort">
@@ -59,6 +60,7 @@ import MkPagination, { Paging } from '@/components/MkPagination.vue';
 import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue';
 import FormSplit from '@/components/form/split.vue';
 import { i18n } from '@/i18n.js';
+import { $i } from '@/account.js';
 
 const host = ref('');
 const state = ref('federating');
@@ -80,6 +82,7 @@ const pagination = {
 			state.value === 'silenced' ? { silenced: true } :
 			state.value === 'notResponding' ? { notResponding: true } :
 			state.value === 'nsfw' ? { nsfw: true } :
+			state.value === 'bubble' ? { bubble: true } :
 			{}),
 	})),
 } as Paging;
diff --git a/packages/frontend/src/pages/about.vue b/packages/frontend/src/pages/about.vue
index b532314745..f2aceada7d 100644
--- a/packages/frontend/src/pages/about.vue
+++ b/packages/frontend/src/pages/about.vue
@@ -1,107 +1,136 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
-	<MkSpacer v-if="tab === 'overview'" :contentMax="600" :marginMin="20">
-		<div class="_gaps_m">
-			<div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }">
-				<div style="overflow: clip;">
-					<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" alt="" :class="$style.bannerIcon"/>
-					<div :class="$style.bannerName">
-						<b>{{ instance.name ?? host }}</b>
+	<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
+		<MkSpacer v-if="tab === 'overview'" :contentMax="600" :marginMin="20">
+			<div class="_gaps_m">
+				<div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }">
+					<div style="overflow: clip;">
+						<img :src="instance.iconUrl ?? instance.faviconUrl ?? '/favicon.ico'" alt="" :class="$style.bannerIcon"/>
+						<div :class="$style.bannerName">
+							<b>{{ instance.name ?? host }}</b>
+						</div>
 					</div>
 				</div>
-			</div>
 
-			<MkKeyValue>
-				<template #key>{{ i18n.ts.description }}</template>
-				<template #value><div v-html="instance.description"></div></template>
-			</MkKeyValue>
+				<MkKeyValue>
+					<template #key>{{ i18n.ts.description }}</template>
+					<template #value><div v-html="sanitizeHtml(instance.description)"></div></template>
+				</MkKeyValue>
 
-			<FormSection>
-				<div class="_gaps_m">
-					<MkKeyValue :copy="version">
-						<template #key>Sharkey</template>
-						<template #value>{{ version }}</template>
-					</MkKeyValue>
-					<div v-html="i18n.t('poweredByMisskeyDescription', { name: instance.name ?? host })">
-					</div>
-					<FormLink to="/about-sharkey">{{ i18n.ts.aboutMisskey }}</FormLink>
-				</div>
-			</FormSection>
-
-			<FormSection>
-				<div class="_gaps_m">
-					<FormSplit>
-						<MkKeyValue>
-							<template #key>{{ i18n.ts.administrator }}</template>
-							<template #value>{{ instance.maintainerName }}</template>
-						</MkKeyValue>
-						<MkKeyValue>
-							<template #key>{{ i18n.ts.contact }}</template>
-							<template #value>{{ instance.maintainerEmail }}</template>
-						</MkKeyValue>
-					</FormSplit>
-					<FormLink v-if="instance.impressumUrl" :to="instance.impressumUrl" external>{{ i18n.ts.impressum }}</FormLink>
-					<div class="_gaps_s">
-						<MkFolder v-if="instance.serverRules.length > 0">
-							<template #label>{{ i18n.ts.serverRules }}</template>
-
-							<ol class="_gaps_s" :class="$style.rules">
-								<li v-for="item, index in instance.serverRules" :key="index" :class="$style.rule"><div :class="$style.ruleText" v-html="item"></div></li>
-							</ol>
-						</MkFolder>
-						<FormLink v-if="instance.tosUrl" :to="instance.tosUrl" external>{{ i18n.ts.termsOfService }}</FormLink>
-						<FormLink v-if="instance.privacyPolicyUrl" :to="instance.privacyPolicyUrl" external>{{ i18n.ts.privacyPolicy }}</FormLink>
-					</div>
-				</div>
-			</FormSection>
-
-			<FormSuspense :p="initStats">
 				<FormSection>
-					<template #label>{{ i18n.ts.statistics }}</template>
-					<FormSplit>
-						<MkKeyValue>
-							<template #key>{{ i18n.ts.users }}</template>
-							<template #value>{{ number(stats.originalUsersCount) }}</template>
+					<div class="_gaps_m">
+						<MkKeyValue :copy="version">
+							<template #key>Sharkey</template>
+							<template #value>{{ version }}</template>
 						</MkKeyValue>
-						<MkKeyValue>
-							<template #key>{{ i18n.ts.notes }}</template>
-							<template #value>{{ number(stats.originalNotesCount) }}</template>
-						</MkKeyValue>
-					</FormSplit>
+						<div v-html="i18n.tsx.poweredByMisskeyDescription({ name: instance.name ?? host })">
+						</div>
+						<FormLink to="/about-sharkey">
+							<template #icon><i class="ph-info ph-bold ph-lg"></i></template>
+							{{ i18n.ts.aboutMisskey }}
+						</FormLink>
+						<FormLink v-if="instance.repositoryUrl || instance.providesTarball" :to="instance.repositoryUrl || `/tarball/sharkey-${version}.tar.gz`" external>
+							<template #icon><i class="ph-code ph-bold ph-lg"></i></template>
+							{{ i18n.ts.sourceCode }}
+						</FormLink>
+						<MkInfo v-else warn>
+							{{ i18n.ts.sourceCodeIsNotYetProvided }}
+						</MkInfo>
+					</div>
 				</FormSection>
-			</FormSuspense>
 
-			<FormSection>
-				<template #label>Well-known resources</template>
-				<div class="_gaps_s">
-					<FormLink :to="`/.well-known/host-meta`" external>host-meta</FormLink>
-					<FormLink :to="`/.well-known/host-meta.json`" external>host-meta.json</FormLink>
-					<FormLink :to="`/.well-known/nodeinfo`" external>nodeinfo</FormLink>
-					<FormLink :to="`/robots.txt`" external>robots.txt</FormLink>
-					<FormLink :to="`/manifest.json`" external>manifest.json</FormLink>
-				</div>
-			</FormSection>
-		</div>
-	</MkSpacer>
-	<MkSpacer v-else-if="tab === 'emojis'" :contentMax="1000" :marginMin="20">
-		<XEmojis/>
-	</MkSpacer>
-	<MkSpacer v-else-if="tab === 'federation'" :contentMax="1000" :marginMin="20">
-		<XFederation/>
-	</MkSpacer>
-	<MkSpacer v-else-if="tab === 'charts'" :contentMax="1000" :marginMin="20">
-		<MkInstanceStats/>
-	</MkSpacer>
+				<FormSection>
+					<div class="_gaps_m">
+						<FormSplit>
+							<MkKeyValue>
+								<template #key>{{ i18n.ts.administrator }}</template>
+								<template #value>{{ instance.maintainerName }}</template>
+							</MkKeyValue>
+							<MkKeyValue>
+								<template #key>{{ i18n.ts.contact }}</template>
+								<template #value>{{ instance.maintainerEmail }}</template>
+							</MkKeyValue>
+						</FormSplit>
+						<FormLink v-if="instance.impressumUrl" :to="instance.impressumUrl" external>
+							<template #icon><i class="ph-newspaper-clipping ph-bold ph-lg"></i></template>
+							{{ i18n.ts.impressum }}
+						</FormLink>
+						<div class="_gaps_s">
+							<MkFolder v-if="instance.serverRules.length > 0">
+								<template #label>
+									<i class="ph-list-checks ph-bold ph-lg"></i>
+									{{ i18n.ts.serverRules }}
+								</template>
+
+								<ol class="_gaps_s" :class="$style.rules">
+									<li v-for="(item, index) in instance.serverRules" :key="index" :class="$style.rule"><div :class="$style.ruleText" v-html="sanitizeHtml(item)"></div></li>
+								</ol>
+							</MkFolder>
+							<FormLink v-if="instance.tosUrl" :to="instance.tosUrl" external>
+								<template #icon><i class="ph-notebook ph-bold ph-lg"></i></template>
+								{{ i18n.ts.termsOfService }}
+							</FormLink>
+							<FormLink v-if="instance.privacyPolicyUrl" :to="instance.privacyPolicyUrl" external>
+								<template #icon><i class="ph-shield ph-bold ph-lg"></i></template>
+								{{ i18n.ts.privacyPolicy }}
+							</FormLink>
+							<FormLink v-if="instance.feedbackUrl" :to="instance.feedbackUrl" external>
+								<template #icon><i class="ph-envelope ph-bold ph-lg"></i></template>
+								{{ i18n.ts.feedback }}
+							</FormLink>
+						</div>
+					</div>
+				</FormSection>
+
+				<FormSuspense :p="initStats">
+					<FormSection>
+						<template #label>{{ i18n.ts.statistics }}</template>
+						<FormSplit>
+							<MkKeyValue>
+								<template #key>{{ i18n.ts.users }}</template>
+								<template #value>{{ number(stats.originalUsersCount) }}</template>
+							</MkKeyValue>
+							<MkKeyValue>
+								<template #key>{{ i18n.ts.notes }}</template>
+								<template #value>{{ number(stats.originalNotesCount) }}</template>
+							</MkKeyValue>
+						</FormSplit>
+					</FormSection>
+				</FormSuspense>
+
+				<FormSection>
+					<template #label>Well-known resources</template>
+					<div class="_gaps_s">
+						<FormLink :to="`/.well-known/host-meta`" external>host-meta</FormLink>
+						<FormLink :to="`/.well-known/host-meta.json`" external>host-meta.json</FormLink>
+						<FormLink :to="`/.well-known/nodeinfo`" external>nodeinfo</FormLink>
+						<FormLink :to="`/robots.txt`" external>robots.txt</FormLink>
+						<FormLink :to="`/manifest.json`" external>manifest.json</FormLink>
+					</div>
+				</FormSection>
+			</div>
+		</MkSpacer>
+		<MkSpacer v-else-if="tab === 'emojis'" :contentMax="1000" :marginMin="20">
+			<XEmojis/>
+		</MkSpacer>
+		<MkSpacer v-else-if="tab === 'federation'" :contentMax="1000" :marginMin="20">
+			<XFederation/>
+		</MkSpacer>
+		<MkSpacer v-else-if="tab === 'charts'" :contentMax="1000" :marginMin="20">
+			<MkInstanceStats/>
+		</MkSpacer>
+	</MkHorizontalSwipe>
 </MkStickyContainer>
 </template>
 
 <script lang="ts" setup>
+import sanitizeHtml from 'sanitize-html';
 import { computed, watch, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import XEmojis from './about.emojis.vue';
@@ -113,8 +142,10 @@ import FormSuspense from '@/components/form/suspense.vue';
 import FormSplit from '@/components/form/split.vue';
 import MkFolder from '@/components/MkFolder.vue';
 import MkKeyValue from '@/components/MkKeyValue.vue';
+import MkInfo from '@/components/MkInfo.vue';
 import MkInstanceStats from '@/components/MkInstanceStats.vue';
-import * as os from '@/os.js';
+import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import number from '@/filters/number.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
@@ -136,7 +167,7 @@ watch(tab, () => {
 	}
 });
 
-const initStats = () => os.api('stats', {
+const initStats = () => misskeyApi('stats', {
 }).then((res) => {
 	stats.value = res;
 });
@@ -160,10 +191,10 @@ const headerTabs = computed(() => [{
 	icon: 'ph-chart-line ph-bold ph-lg',
 }]);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts.instanceInfo,
 	icon: 'ph-info ph-bold ph-lg',
-})));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/achievements.vue b/packages/frontend/src/pages/achievements.vue
index f735da7e67..4e496c3c6c 100644
--- a/packages/frontend/src/pages/achievements.vue
+++ b/packages/frontend/src/pages/achievements.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -48,10 +48,10 @@ onDeactivated(() => {
 	}
 });
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.achievements,
 	icon: 'ph-trophy ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin-file.vue b/packages/frontend/src/pages/admin-file.vue
index 845beebbaf..b8f7e2c163 100644
--- a/packages/frontend/src/pages/admin-file.vue
+++ b/packages/frontend/src/pages/admin-file.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -79,6 +79,7 @@ import MkUserCardMini from '@/components/MkUserCardMini.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import bytes from '@/filters/bytes.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { iAmAdmin, iAmModerator } from '@/account.js';
@@ -93,8 +94,8 @@ const props = defineProps<{
 }>();
 
 async function fetch() {
-	file.value = await os.api('drive/files/show', { fileId: props.fileId });
-	info.value = await os.api('admin/drive/show-file', { fileId: props.fileId });
+	file.value = await misskeyApi('drive/files/show', { fileId: props.fileId });
+	info.value = await misskeyApi('admin/drive/show-file', { fileId: props.fileId });
 	isSensitive.value = file.value.isSensitive;
 }
 
@@ -103,7 +104,7 @@ fetch();
 async function del() {
 	const { canceled } = await os.confirm({
 		type: 'warning',
-		text: i18n.t('removeAreYouSure', { x: file.value.name }),
+		text: i18n.tsx.removeAreYouSure({ x: file.value.name }),
 	});
 	if (canceled) return;
 
@@ -113,7 +114,7 @@ async function del() {
 }
 
 async function toggleIsSensitive(v) {
-	await os.api('drive/files/update', { fileId: props.fileId, isSensitive: v });
+	await misskeyApi('drive/files/update', { fileId: props.fileId, isSensitive: v });
 	isSensitive.value = v;
 }
 
@@ -139,10 +140,10 @@ const headerTabs = computed(() => [{
 	icon: 'ph-code ph-bold ph-lg',
 }]);
 
-definePageMetadata(computed(() => ({
-	title: file.value ? i18n.ts.file + ': ' + file.value.name : i18n.ts.file,
+definePageMetadata(() => ({
+	title: file.value ? `${i18n.ts.file}: ${file.value.name}` : i18n.ts.file,
 	icon: 'ph-file ph-bold ph-lg',
-})));
+}));
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue
index 741897b5f0..3beaf5d08b 100644
--- a/packages/frontend/src/pages/admin-user.vue
+++ b/packages/frontend/src/pages/admin-user.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -124,7 +124,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<div v-for="role in info.roles" :key="role.id">
 					<div :class="$style.roleItemMain">
 						<MkRolePreview :class="$style.role" :role="role" :forModeration="true"/>
-						<button class="_button" :class="$style.roleToggle" @click="toggleRoleItem(role)"><i class="ph-caret-down ph-bold ph-lg"></i></button>
+						<button class="_button" @click="toggleRoleItem(role)"><i class="ph-caret-down ph-bold ph-lg"></i></button>
 						<button v-if="role.target === 'manual'" class="_button" :class="$style.roleUnassign" @click="unassignRole(role, $event)"><i class="ph-x ph-bold ph-lg"></i></button>
 						<button v-else class="_button" :class="$style.roleUnassign" disabled><i class="ph-prohibit ph-bold ph-lg"></i></button>
 					</div>
@@ -169,9 +169,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 						</MkSelect>
 					</div>
 					<div class="charts">
-						<div class="label">{{ i18n.t('recentNHours', { n: 90 }) }}</div>
+						<div class="label">{{ i18n.tsx.recentNHours({ n: 90 }) }}</div>
 						<MkChart class="chart" :src="chartSrc" span="hour" :limit="90" :args="{ user, withoutAll: true }" :detailed="true"></MkChart>
-						<div class="label">{{ i18n.t('recentNDays', { n: 90 }) }}</div>
+						<div class="label">{{ i18n.tsx.recentNDays({ n: 90 }) }}</div>
 						<MkChart class="chart" :src="chartSrc" span="day" :limit="90" :args="{ user, withoutAll: true }" :detailed="true"></MkChart>
 					</div>
 				</div>
@@ -206,11 +206,12 @@ import FormSuspense from '@/components/form/suspense.vue';
 import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { url } from '@/config.js';
 import { acct } from '@/filters/user.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { i18n } from '@/i18n.js';
-import { iAmAdmin, $i } from '@/account.js';
+import { iAmAdmin, $i, iAmModerator } from '@/account.js';
 import MkRolePreview from '@/components/MkRolePreview.vue';
 import MkPagination from '@/components/MkPagination.vue';
 
@@ -251,11 +252,11 @@ const announcementsPagination = {
 const expandedRoles = ref([]);
 
 function createFetcher() {
-	return () => Promise.all([os.api('users/show', {
+	return () => Promise.all([misskeyApi('users/show', {
 		userId: props.userId,
-	}), os.api('admin/show-user', {
+	}), misskeyApi('admin/show-user', {
 		userId: props.userId,
-	}), iAmAdmin ? os.api('admin/get-user-ips', {
+	}), iAmAdmin ? misskeyApi('admin/get-user-ips', {
 		userId: props.userId,
 	}) : Promise.resolve(null)]).then(([_user, _info, _ips]) => {
 		user.value = _user;
@@ -268,7 +269,7 @@ function createFetcher() {
 		moderationNote.value = info.value.moderationNote;
 
 		watch(moderationNote, async () => {
-			await os.api('admin/update-user-note', { userId: user.value.id, text: moderationNote.value });
+			await misskeyApi('admin/update-user-note', { userId: user.value.id, text: moderationNote.value });
 			await refreshUser();
 		});
 	});
@@ -291,12 +292,12 @@ async function resetPassword() {
 	if (confirm.canceled) {
 		return;
 	} else {
-		const { password } = await os.api('admin/reset-password', {
+		const { password } = await misskeyApi('admin/reset-password', {
 			userId: user.value.id,
 		});
 		os.alert({
 			type: 'success',
-			text: i18n.t('newPasswordIs', { password }),
+			text: i18n.tsx.newPasswordIs({ password }),
 		});
 	}
 }
@@ -309,7 +310,7 @@ async function toggleNSFW(v) {
 	if (confirm.canceled) {
 		markedAsNSFW.value = !v;
 	} else {
-		await os.api(v ? 'admin/nsfw-user' : 'admin/unnsfw-user', { userId: user.value.id });
+		await misskeyApi(v ? 'admin/nsfw-user' : 'admin/unnsfw-user', { userId: user.value.id });
 		await refreshUser();
 	}
 }
@@ -322,7 +323,7 @@ async function toggleSilence(v) {
 	if (confirm.canceled) {
 		silenced.value = !v;
 	} else {
-		await os.api(v ? 'admin/silence-user' : 'admin/unsilence-user', { userId: user.value.id });
+		await misskeyApi(v ? 'admin/silence-user' : 'admin/unsilence-user', { userId: user.value.id });
 		await refreshUser();
 	}
 }
@@ -335,7 +336,7 @@ async function toggleSuspend(v) {
 	if (confirm.canceled) {
 		suspended.value = !v;
 	} else {
-		await os.api(v ? 'admin/suspend-user' : 'admin/unsuspend-user', { userId: user.value.id });
+		await misskeyApi(v ? 'admin/suspend-user' : 'admin/unsuspend-user', { userId: user.value.id });
 		await refreshUser();
 	}
 }
@@ -347,7 +348,7 @@ async function unsetUserAvatar() {
 	});
 	if (confirm.canceled) return;
 	const process = async () => {
-		await os.api('admin/unset-user-avatar', { userId: user.value.id });
+		await misskeyApi('admin/unset-user-avatar', { userId: user.value.id });
 		os.success();
 	};
 	await process().catch(err => {
@@ -366,7 +367,7 @@ async function unsetUserBanner() {
 	});
 	if (confirm.canceled) return;
 	const process = async () => {
-		await os.api('admin/unset-user-banner', { userId: user.value.id });
+		await misskeyApi('admin/unset-user-banner', { userId: user.value.id });
 		os.success();
 	};
 	await process().catch(err => {
@@ -385,7 +386,7 @@ async function deleteAllFiles() {
 	});
 	if (confirm.canceled) return;
 	const process = async () => {
-		await os.api('admin/delete-all-files-of-a-user', { userId: user.value.id });
+		await misskeyApi('admin/delete-all-files-of-a-user', { userId: user.value.id });
 		os.success();
 	};
 	await process().catch(err => {
@@ -405,7 +406,7 @@ async function deleteAccount() {
 	if (confirm.canceled) return;
 
 	const typed = await os.inputText({
-		text: i18n.t('typeToConfirm', { x: user.value?.username }),
+		text: i18n.tsx.typeToConfirm({ x: user.value?.username }),
 	});
 	if (typed.canceled) return;
 
@@ -422,7 +423,7 @@ async function deleteAccount() {
 }
 
 async function assignRole() {
-	const roles = await os.api('admin/roles/list');
+	const roles = await misskeyApi('admin/roles/list');
 
 	const { canceled, result: roleId } = await os.select({
 		title: i18n.ts._role.chooseRoleToAssign,
@@ -498,7 +499,7 @@ watch(() => props.userId, () => {
 });
 
 watch(user, () => {
-	os.api('ap/get', {
+	misskeyApi('ap/get', {
 		uri: user.value.uri ?? `${url}/users/${user.value.id}`,
 	}).then(res => {
 		ap.value = res;
@@ -533,10 +534,10 @@ const headerTabs = computed(() => [{
 	icon: 'ph-code ph-bold ph-lg',
 }]);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: user.value ? acct(user.value) : i18n.ts.userInfo,
 	icon: 'ph-warning-circle ph-bold ph-lg',
-})));
+}));
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/pages/admin/RolesEditorFormula.vue b/packages/frontend/src/pages/admin/RolesEditorFormula.vue
index 92010f771c..1dbcc867a1 100644
--- a/packages/frontend/src/pages/admin/RolesEditorFormula.vue
+++ b/packages/frontend/src/pages/admin/RolesEditorFormula.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -9,6 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<MkSelect v-model="type" :class="$style.typeSelect">
 			<option value="isLocal">{{ i18n.ts._role._condition.isLocal }}</option>
 			<option value="isRemote">{{ i18n.ts._role._condition.isRemote }}</option>
+			<option value="roleAssignedTo">{{ i18n.ts._role._condition.roleAssignedTo }}</option>
 			<option value="createdLessThan">{{ i18n.ts._role._condition.createdLessThan }}</option>
 			<option value="createdMoreThan">{{ i18n.ts._role._condition.createdMoreThan }}</option>
 			<option value="followersLessThanOrEq">{{ i18n.ts._role._condition.followersLessThanOrEq }}</option>
@@ -51,6 +52,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 	<MkInput v-else-if="['followersLessThanOrEq', 'followersMoreThanOrEq', 'followingLessThanOrEq', 'followingMoreThanOrEq', 'notesLessThanOrEq', 'notesMoreThanOrEq'].includes(type)" v-model="v.value" type="number">
 	</MkInput>
+
+	<MkSelect v-else-if="type === 'roleAssignedTo'" v-model="v.roleId">
+		<option v-for="role in roles.filter(r => r.target === 'manual')" :key="role.id" :value="role.id">{{ role.name }}</option>
+	</MkSelect>
 </div>
 </template>
 
@@ -62,6 +67,7 @@ import MkSelect from '@/components/MkSelect.vue';
 import MkButton from '@/components/MkButton.vue';
 import { i18n } from '@/i18n.js';
 import { deepClone } from '@/scripts/clone.js';
+import { rolesCache } from '@/cache.js';
 
 const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
 
@@ -77,6 +83,8 @@ const props = defineProps<{
 
 const v = ref(deepClone(props.modelValue));
 
+const roles = await rolesCache.fetch();
+
 watch(() => props.modelValue, () => {
 	if (JSON.stringify(props.modelValue) === JSON.stringify(v.value)) return;
 	v.value = deepClone(props.modelValue);
@@ -92,6 +100,7 @@ const type = computed({
 		if (t === 'and') v.value.values = [];
 		if (t === 'or') v.value.values = [];
 		if (t === 'not') v.value.value = { id: uuid(), type: 'isRemote' };
+		if (t === 'roleAssignedTo') v.value.roleId = '';
 		if (t === 'createdLessThan') v.value.sec = 86400;
 		if (t === 'createdMoreThan') v.value.sec = 86400;
 		if (t === 'followersLessThanOrEq') v.value.value = 10;
diff --git a/packages/frontend/src/pages/admin/_header_.vue b/packages/frontend/src/pages/admin/_header_.vue
index 6f1a31616a..5c9c32c964 100644
--- a/packages/frontend/src/pages/admin/_header_.vue
+++ b/packages/frontend/src/pages/admin/_header_.vue
@@ -1,16 +1,16 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
 <div ref="el" class="fdidabkc" :style="{ background: bg }" @click="onClick">
-	<template v-if="metadata">
+	<template v-if="pageMetadata">
 		<div class="titleContainer" @click="showTabsPopup">
-			<i v-if="metadata.icon" class="icon" :class="metadata.icon"></i>
+			<i v-if="pageMetadata.icon" class="icon" :class="pageMetadata.icon"></i>
 
 			<div class="title">
-				<div class="title">{{ metadata.title }}</div>
+				<div class="title">{{ pageMetadata.title }}</div>
 			</div>
 		</div>
 		<div class="tabs">
@@ -39,7 +39,7 @@ import { popupMenu } from '@/os.js';
 import { scrollToTop } from '@/scripts/scroll.js';
 import MkButton from '@/components/MkButton.vue';
 import { globalEvents } from '@/events.js';
-import { injectPageMetadata } from '@/scripts/page-metadata.js';
+import { injectReactiveMetadata } from '@/scripts/page-metadata.js';
 
 type Tab = {
 	key?: string | null;
@@ -65,7 +65,7 @@ const emit = defineEmits<{
 	(ev: 'update:tab', key: string);
 }>();
 
-const metadata = injectPageMetadata();
+const pageMetadata = injectReactiveMetadata();
 
 const el = shallowRef<HTMLElement>(null);
 const tabRefs = {};
@@ -118,7 +118,7 @@ function onTabClick(tab: Tab, ev: MouseEvent): void {
 }
 
 const calcBg = () => {
-	const rawBg = metadata?.bg ?? 'var(--bg)';
+	const rawBg = pageMetadata.value?.bg ?? 'var(--bg)';
 	const tinyBg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg);
 	tinyBg.setAlpha(0.85);
 	bg.value = tinyBg.toRgbString();
diff --git a/packages/frontend/src/pages/admin/abuses.vue b/packages/frontend/src/pages/admin/abuses.vue
index 92688989d2..42fcc3a598 100644
--- a/packages/frontend/src/pages/admin/abuses.vue
+++ b/packages/frontend/src/pages/admin/abuses.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -87,8 +87,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.abuseReports,
 	icon: 'ph-warning-circle ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/admin/ads.vue b/packages/frontend/src/pages/admin/ads.vue
index 8a1e03c30d..6ec5abd2f2 100644
--- a/packages/frontend/src/pages/admin/ads.vue
+++ b/packages/frontend/src/pages/admin/ads.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -96,6 +96,7 @@ import MkFolder from '@/components/MkFolder.vue';
 import MkSelect from '@/components/MkSelect.vue';
 import FormSplit from '@/components/form/split.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
@@ -108,7 +109,7 @@ const daysOfWeek: string[] = [i18n.ts._weekday.sunday, i18n.ts._weekday.monday,
 const filterType = ref('all');
 let publishing: boolean | null = null;
 
-os.api('admin/ad/list', { publishing: publishing }).then(adsResponse => {
+misskeyApi('admin/ad/list', { publishing: publishing }).then(adsResponse => {
 	if (adsResponse != null) {
 		ads.value = adsResponse.map(r => {
 			const exdate = new Date(r.expiresAt);
@@ -159,7 +160,7 @@ function add() {
 function remove(ad) {
 	os.confirm({
 		type: 'warning',
-		text: i18n.t('removeAreYouSure', { x: ad.url }),
+		text: i18n.tsx.removeAreYouSure({ x: ad.url }),
 	}).then(({ canceled }) => {
 		if (canceled) return;
 		ads.value = ads.value.filter(x => x !== ad);
@@ -174,7 +175,7 @@ function remove(ad) {
 
 function save(ad) {
 	if (ad.id == null) {
-		os.api('admin/ad/create', {
+		misskeyApi('admin/ad/create', {
 			...ad,
 			expiresAt: new Date(ad.expiresAt).getTime(),
 			startsAt: new Date(ad.startsAt).getTime(),
@@ -191,7 +192,7 @@ function save(ad) {
 			});
 		});
 	} else {
-		os.api('admin/ad/update', {
+		misskeyApi('admin/ad/update', {
 			...ad,
 			expiresAt: new Date(ad.expiresAt).getTime(),
 			startsAt: new Date(ad.startsAt).getTime(),
@@ -210,7 +211,7 @@ function save(ad) {
 }
 
 function more() {
-	os.api('admin/ad/list', { untilId: ads.value.reduce((acc, ad) => ad.id != null ? ad : acc).id, publishing: publishing }).then(adsResponse => {
+	misskeyApi('admin/ad/list', { untilId: ads.value.reduce((acc, ad) => ad.id != null ? ad : acc).id, publishing: publishing }).then(adsResponse => {
 		if (adsResponse == null) return;
 		ads.value = ads.value.concat(adsResponse.map(r => {
 			const exdate = new Date(r.expiresAt);
@@ -227,7 +228,7 @@ function more() {
 }
 
 function refresh() {
-	os.api('admin/ad/list', { publishing: publishing }).then(adsResponse => {
+	misskeyApi('admin/ad/list', { publishing: publishing }).then(adsResponse => {
 		if (adsResponse == null) return;
 		ads.value = adsResponse.map(r => {
 			const exdate = new Date(r.expiresAt);
@@ -254,10 +255,10 @@ const headerActions = computed(() => [{
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.ads,
 	icon: 'ph-flag ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/announcements.vue b/packages/frontend/src/pages/admin/announcements.vue
index 931bd9bbc8..a8832b99fd 100644
--- a/packages/frontend/src/pages/admin/announcements.vue
+++ b/packages/frontend/src/pages/admin/announcements.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -54,7 +54,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<MkSwitch v-model="announcement.needConfirmationToRead" :helpText="i18n.ts._announcement.needConfirmationToReadDescription">
 						{{ i18n.ts._announcement.needConfirmationToRead }}
 					</MkSwitch>
-					<p v-if="announcement.reads">{{ i18n.t('nUsersRead', { n: announcement.reads }) }}</p>
+					<p v-if="announcement.reads">{{ i18n.tsx.nUsersRead({ n: announcement.reads }) }}</p>
 					<div class="buttons _buttons">
 						<MkButton class="button" inline primary @click="save(announcement)"><i class="ph-floppy-disk ph-bold ph-lg"></i> {{ i18n.ts.save }}</MkButton>
 						<MkButton v-if="announcement.id != null" class="button" inline @click="archive(announcement)"><i class="ph-check ph-bold ph-lg"></i> {{ i18n.ts._announcement.end }} ({{ i18n.ts.archive }})</MkButton>
@@ -79,6 +79,7 @@ import MkSwitch from '@/components/MkSwitch.vue';
 import MkRadios from '@/components/MkRadios.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkFolder from '@/components/MkFolder.vue';
@@ -86,7 +87,7 @@ import MkTextarea from '@/components/MkTextarea.vue';
 
 const announcements = ref<any[]>([]);
 
-os.api('admin/announcements/list').then(announcementResponse => {
+misskeyApi('admin/announcements/list').then(announcementResponse => {
 	announcements.value = announcementResponse;
 });
 
@@ -108,11 +109,11 @@ function add() {
 function del(announcement) {
 	os.confirm({
 		type: 'warning',
-		text: i18n.t('deleteAreYouSure', { x: announcement.title }),
+		text: i18n.tsx.deleteAreYouSure({ x: announcement.title }),
 	}).then(({ canceled }) => {
 		if (canceled) return;
 		announcements.value = announcements.value.filter(x => x !== announcement);
-		os.api('admin/announcements/delete', announcement);
+		misskeyApi('admin/announcements/delete', announcement);
 	});
 }
 
@@ -134,13 +135,13 @@ async function save(announcement) {
 }
 
 function more() {
-	os.api('admin/announcements/list', { untilId: announcements.value.reduce((acc, announcement) => announcement.id != null ? announcement : acc).id }).then(announcementResponse => {
+	misskeyApi('admin/announcements/list', { untilId: announcements.value.reduce((acc, announcement) => announcement.id != null ? announcement : acc).id }).then(announcementResponse => {
 		announcements.value = announcements.value.concat(announcementResponse);
 	});
 }
 
 function refresh() {
-	os.api('admin/announcements/list').then(announcementResponse => {
+	misskeyApi('admin/announcements/list').then(announcementResponse => {
 		announcements.value = announcementResponse;
 	});
 }
@@ -156,8 +157,8 @@ const headerActions = computed(() => [{
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.announcements,
 	icon: 'ph-megaphone ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/admin/approvals.vue b/packages/frontend/src/pages/admin/approvals.vue
index 7d0535bd7f..998e16681a 100644
--- a/packages/frontend/src/pages/admin/approvals.vue
+++ b/packages/frontend/src/pages/admin/approvals.vue
@@ -48,10 +48,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts.approvals,
 	icon: 'ph-chalkboard-teacher ph-bold ph-lg',
-})));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/bot-protection.vue b/packages/frontend/src/pages/admin/bot-protection.vue
index eebea51bf1..052d2f0bc2 100644
--- a/packages/frontend/src/pages/admin/bot-protection.vue
+++ b/packages/frontend/src/pages/admin/bot-protection.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -10,6 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<MkRadios v-model="provider">
 				<option :value="null">{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</option>
 				<option value="hcaptcha">hCaptcha</option>
+				<option value="mcaptcha">mCaptcha</option>
 				<option value="recaptcha">reCAPTCHA</option>
 				<option value="turnstile">Turnstile</option>
 			</MkRadios>
@@ -28,6 +29,24 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<MkCaptcha provider="hcaptcha" :sitekey="hcaptchaSiteKey || '10000000-ffff-ffff-ffff-000000000001'"/>
 				</FormSlot>
 			</template>
+			<template v-else-if="provider === 'mcaptcha'">
+				<MkInput v-model="mcaptchaSiteKey">
+					<template #prefix><i class="ph-key ph-bold ph-lg"></i></template>
+					<template #label>{{ i18n.ts.mcaptchaSiteKey }}</template>
+				</MkInput>
+				<MkInput v-model="mcaptchaSecretKey">
+					<template #prefix><i class="ph-key ph-bold ph-lg"></i></template>
+					<template #label>{{ i18n.ts.mcaptchaSecretKey }}</template>
+				</MkInput>
+				<MkInput v-model="mcaptchaInstanceUrl">
+					<template #prefix><i class="ph-globe-simple ph-bold ph-lg"></i></template>
+					<template #label>{{ i18n.ts.mcaptchaInstanceUrl }}</template>
+				</MkInput>
+				<FormSlot v-if="mcaptchaSiteKey && mcaptchaInstanceUrl">
+					<template #label>{{ i18n.ts.preview }}</template>
+					<MkCaptcha provider="mcaptcha" :sitekey="mcaptchaSiteKey" :instanceUrl="mcaptchaInstanceUrl"/>
+				</FormSlot>
+			</template>
 			<template v-else-if="provider === 'recaptcha'">
 				<MkInput v-model="recaptchaSiteKey">
 					<template #prefix><i class="ph-key ph-bold ph-lg"></i></template>
@@ -72,6 +91,7 @@ import MkButton from '@/components/MkButton.vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import FormSlot from '@/components/form/slot.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { fetchInstance } from '@/instance.js';
 import { i18n } from '@/i18n.js';
 
@@ -80,21 +100,30 @@ const MkCaptcha = defineAsyncComponent(() => import('@/components/MkCaptcha.vue'
 const provider = ref<CaptchaProvider | null>(null);
 const hcaptchaSiteKey = ref<string | null>(null);
 const hcaptchaSecretKey = ref<string | null>(null);
+const mcaptchaSiteKey = ref<string | null>(null);
+const mcaptchaSecretKey = ref<string | null>(null);
+const mcaptchaInstanceUrl = ref<string | null>(null);
 const recaptchaSiteKey = ref<string | null>(null);
 const recaptchaSecretKey = ref<string | null>(null);
 const turnstileSiteKey = ref<string | null>(null);
 const turnstileSecretKey = ref<string | null>(null);
 
 async function init() {
-	const meta = await os.api('admin/meta');
+	const meta = await misskeyApi('admin/meta');
 	hcaptchaSiteKey.value = meta.hcaptchaSiteKey;
 	hcaptchaSecretKey.value = meta.hcaptchaSecretKey;
+	mcaptchaSiteKey.value = meta.mcaptchaSiteKey;
+	mcaptchaSecretKey.value = meta.mcaptchaSecretKey;
+	mcaptchaInstanceUrl.value = meta.mcaptchaInstanceUrl;
 	recaptchaSiteKey.value = meta.recaptchaSiteKey;
 	recaptchaSecretKey.value = meta.recaptchaSecretKey;
 	turnstileSiteKey.value = meta.turnstileSiteKey;
 	turnstileSecretKey.value = meta.turnstileSecretKey;
 
-	provider.value = meta.enableHcaptcha ? 'hcaptcha' : meta.enableRecaptcha ? 'recaptcha' : meta.enableTurnstile ? 'turnstile' : null;
+	provider.value = meta.enableHcaptcha ? 'hcaptcha' :
+		meta.enableRecaptcha ? 'recaptcha' :
+		meta.enableTurnstile ? 'turnstile' :
+		meta.enableMcaptcha ? 'mcaptcha' : null;
 }
 
 function save() {
@@ -102,6 +131,10 @@ function save() {
 		enableHcaptcha: provider.value === 'hcaptcha',
 		hcaptchaSiteKey: hcaptchaSiteKey.value,
 		hcaptchaSecretKey: hcaptchaSecretKey.value,
+		enableMcaptcha: provider.value === 'mcaptcha',
+		mcaptchaSiteKey: mcaptchaSiteKey.value,
+		mcaptchaSecretKey: mcaptchaSecretKey.value,
+		mcaptchaInstanceUrl: mcaptchaInstanceUrl.value,
 		enableRecaptcha: provider.value === 'recaptcha',
 		recaptchaSiteKey: recaptchaSiteKey.value,
 		recaptchaSecretKey: recaptchaSecretKey.value,
@@ -109,7 +142,7 @@ function save() {
 		turnstileSiteKey: turnstileSiteKey.value,
 		turnstileSecretKey: turnstileSecretKey.value,
 	}).then(() => {
-		fetchInstance();
+		fetchInstance(true);
 	});
 }
 </script>
diff --git a/packages/frontend/src/pages/admin/branding.vue b/packages/frontend/src/pages/admin/branding.vue
index fc6a9e0d67..9310f52bfb 100644
--- a/packages/frontend/src/pages/admin/branding.vue
+++ b/packages/frontend/src/pages/admin/branding.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -19,10 +19,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 						<template #prefix><i class="ph-link ph-bold ph-lg"></i></template>
 						<template #label>{{ i18n.ts._serverSettings.iconUrl }} (App/192px)</template>
 						<template #caption>
-							<div>{{ i18n.t('_serverSettings.appIconDescription', { host: instance.name ?? host }) }}</div>
+							<div>{{ i18n.tsx._serverSettings.appIconDescription({ host: instance.name ?? host }) }}</div>
 							<div>({{ i18n.ts._serverSettings.appIconUsageExample }})</div>
 							<div>{{ i18n.ts._serverSettings.appIconStyleRecommendation }}</div>
-							<div><strong>{{ i18n.t('_serverSettings.appIconResolutionMustBe', { resolution: '192x192px' }) }}</strong></div>
+							<div><strong>{{ i18n.tsx._serverSettings.appIconResolutionMustBe({ resolution: '192x192px' }) }}</strong></div>
 						</template>
 					</MkInput>
 
@@ -30,10 +30,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 						<template #prefix><i class="ph-link ph-bold ph-lg"></i></template>
 						<template #label>{{ i18n.ts._serverSettings.iconUrl }} (App/512px)</template>
 						<template #caption>
-							<div>{{ i18n.t('_serverSettings.appIconDescription', { host: instance.name ?? host }) }}</div>
+							<div>{{ i18n.tsx._serverSettings.appIconDescription({ host: instance.name ?? host }) }}</div>
 							<div>({{ i18n.ts._serverSettings.appIconUsageExample }})</div>
 							<div>{{ i18n.ts._serverSettings.appIconStyleRecommendation }}</div>
-							<div><strong>{{ i18n.t('_serverSettings.appIconResolutionMustBe', { resolution: '512x512px' }) }}</strong></div>
+							<div><strong>{{ i18n.tsx._serverSettings.appIconResolutionMustBe({ resolution: '512x512px' }) }}</strong></div>
 						</template>
 					</MkInput>
 
@@ -49,7 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 					<FromSlot>
 						<template #label>{{ i18n.ts.defaultLike }}</template>
-						<MkCustomEmoji v-if="defaultLike.startsWith(':')" style="max-height: 3em; font-size: 1.1em;" :useOriginalSize="false" :class="$style.reaction" :name="defaultLike" :normal="true" :noStyle="true"/>
+						<MkCustomEmoji v-if="defaultLike.startsWith(':')" style="max-height: 3em; font-size: 1.1em;" :useOriginalSize="false" :name="defaultLike" :normal="true" :noStyle="true"/>
 						<MkEmoji v-else :emoji="defaultLike" style="max-height: 3em; font-size: 1.1em;" :normal="true" :noStyle="true"/>
 						<MkButton rounded :small="true" @click="chooseNewLike"><i class="ph-smiley ph-bold ph-lg"></i> Change</MkButton>
 					</FromSlot>
@@ -83,6 +83,16 @@ SPDX-License-Identifier: AGPL-3.0-only
 						<template #caption>{{ i18n.ts.instanceDefaultThemeDescription }}</template>
 					</MkTextarea>
 
+					<MkInput v-model="repositoryUrl" type="url">
+						<template #prefix><i class="ph-link ph-bold ph-lg"></i></template>
+						<template #label>{{ i18n.ts.repositoryUrl }}</template>
+					</MkInput>
+
+					<MkInput v-model="feedbackUrl" type="url">
+						<template #prefix><i class="ph-link ph-bold ph-lg"></i></template>
+						<template #label>{{ i18n.ts.feedbackUrl }}</template>
+					</MkInput>
+
 					<MkTextarea v-model="manifestJsonOverride">
 						<template #label>{{ i18n.ts._serverSettings.manifestJsonOverride }}</template>
 					</MkTextarea>
@@ -109,6 +119,7 @@ import MkTextarea from '@/components/MkTextarea.vue';
 import FromSlot from '@/components/form/slot.vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { instance, fetchInstance } from '@/instance.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
@@ -128,10 +139,12 @@ const defaultLike = ref<string>('');
 const serverErrorImageUrl = ref<string | null>(null);
 const infoImageUrl = ref<string | null>(null);
 const notFoundImageUrl = ref<string | null>(null);
+const repositoryUrl = ref<string | null>(null);
+const feedbackUrl = ref<string | null>(null);
 const manifestJsonOverride = ref<string>('{}');
 
 async function init() {
-	const meta = await os.api('admin/meta');
+	const meta = await misskeyApi('admin/meta');
 	iconUrl.value = meta.iconUrl;
 	app192IconUrl.value = meta.app192IconUrl;
 	app512IconUrl.value = meta.app512IconUrl;
@@ -144,6 +157,8 @@ async function init() {
 	serverErrorImageUrl.value = meta.serverErrorImageUrl;
 	infoImageUrl.value = meta.infoImageUrl;
 	notFoundImageUrl.value = meta.notFoundImageUrl;
+	repositoryUrl.value = meta.repositoryUrl;
+	feedbackUrl.value = meta.feedbackUrl;
 	manifestJsonOverride.value = meta.manifestJsonOverride === '' ? '{}' : JSON.stringify(JSON.parse(meta.manifestJsonOverride), null, '\t');
 }
 
@@ -157,12 +172,14 @@ function save() {
 		themeColor: themeColor.value === '' ? null : themeColor.value,
 		defaultLightTheme: defaultLightTheme.value === '' ? null : defaultLightTheme.value,
 		defaultDarkTheme: defaultDarkTheme.value === '' ? null : defaultDarkTheme.value,
-		infoImageUrl: infoImageUrl.value,
-		notFoundImageUrl: notFoundImageUrl.value,
-		serverErrorImageUrl: serverErrorImageUrl.value,
+		infoImageUrl: infoImageUrl.value === '' ? null : infoImageUrl.value,
+		notFoundImageUrl: notFoundImageUrl.value === '' ? null : notFoundImageUrl.value,
+		serverErrorImageUrl: serverErrorImageUrl.value === '' ? null : serverErrorImageUrl.value,
+		repositoryUrl: repositoryUrl.value === '' ? null : repositoryUrl.value,
+		feedbackUrl: feedbackUrl.value === '' ? null : feedbackUrl.value,
 		manifestJsonOverride: manifestJsonOverride.value === '' ? '{}' : JSON.stringify(JSON5.parse(manifestJsonOverride.value)),
 	}).then(() => {
-		fetchInstance();
+		fetchInstance(true);
 	});
 }
 
@@ -181,10 +198,10 @@ function chooseNewLike(ev: MouseEvent) {
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.branding,
 	icon: 'ph-paint-roller ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/database.vue b/packages/frontend/src/pages/admin/database.vue
index d9fc672fbf..a64e07b4c7 100644
--- a/packages/frontend/src/pages/admin/database.vue
+++ b/packages/frontend/src/pages/admin/database.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -21,20 +21,20 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed } from 'vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import MkKeyValue from '@/components/MkKeyValue.vue';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import bytes from '@/filters/bytes.js';
 import number from '@/filters/number.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
-const databasePromiseFactory = () => os.api('admin/get-table-stats').then(res => Object.entries(res).sort((a, b) => b[1].size - a[1].size));
+const databasePromiseFactory = () => misskeyApi('admin/get-table-stats').then(res => Object.entries(res).sort((a, b) => b[1].size - a[1].size));
 
 const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.database,
 	icon: 'ph-database ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/admin/email-settings.vue b/packages/frontend/src/pages/admin/email-settings.vue
index 819619df90..1cbfdab094 100644
--- a/packages/frontend/src/pages/admin/email-settings.vue
+++ b/packages/frontend/src/pages/admin/email-settings.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -73,6 +73,7 @@ import FormSuspense from '@/components/form/suspense.vue';
 import FormSplit from '@/components/form/split.vue';
 import FormSection from '@/components/form/section.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { fetchInstance, instance } from '@/instance.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
@@ -87,7 +88,7 @@ const smtpUser = ref<string>('');
 const smtpPass = ref<string>('');
 
 async function init() {
-	const meta = await os.api('admin/meta');
+	const meta = await misskeyApi('admin/meta');
 	enableEmail.value = meta.enableEmail;
 	email.value = meta.email;
 	smtpSecure.value = meta.smtpSecure;
@@ -123,16 +124,16 @@ function save() {
 		smtpUser: smtpUser.value,
 		smtpPass: smtpPass.value,
 	}).then(() => {
-		fetchInstance();
+		fetchInstance(true);
 	});
 }
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.emailServer,
 	icon: 'ph-envelope ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/external-services.vue b/packages/frontend/src/pages/admin/external-services.vue
index f4359270b6..728cdc60b0 100644
--- a/packages/frontend/src/pages/admin/external-services.vue
+++ b/packages/frontend/src/pages/admin/external-services.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -19,6 +19,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<MkSwitch v-model="deeplIsPro">
 						<template #label>Pro account</template>
 					</MkSwitch>
+					<MkSwitch v-model="deeplFreeMode">
+						<template #label>{{ i18n.ts.deeplFreeMode }}</template>
+					</MkSwitch>
+					<MkInput v-if="deeplFreeMode" v-model="deeplFreeInstance" :placeholder="'example.com/translate'">
+						<template #prefix><i class="ph-globe-simple ph-bold ph-lg"></i></template>
+						<template #label>DeepLX-JS URL</template>
+						<template #caption>{{ i18n.ts.deeplFreeModeDescription }}</template>
+					</MkInput>
 				</div>
 			</FormSection>
 		</FormSuspense>
@@ -42,25 +50,32 @@ import MkSwitch from '@/components/MkSwitch.vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import FormSection from '@/components/form/section.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { fetchInstance } from '@/instance.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
 const deeplAuthKey = ref<string>('');
 const deeplIsPro = ref<boolean>(false);
+const deeplFreeMode = ref<boolean>(false);
+const deeplFreeInstance = ref<string>('');
 
 async function init() {
-	const meta = await os.api('admin/meta');
+	const meta = await misskeyApi('admin/meta');
 	deeplAuthKey.value = meta.deeplAuthKey;
 	deeplIsPro.value = meta.deeplIsPro;
+	deeplFreeMode.value = meta.deeplFreeMode;
+	deeplFreeInstance.value = meta.deeplFreeInstance;
 }
 
 function save() {
 	os.apiWithDialog('admin/update-meta', {
 		deeplAuthKey: deeplAuthKey.value,
 		deeplIsPro: deeplIsPro.value,
+		deeplFreeMode: deeplFreeMode.value,
+		deeplFreeInstance: deeplFreeInstance.value,
 	}).then(() => {
-		fetchInstance();
+		fetchInstance(true);
 	});
 }
 
@@ -68,10 +83,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.externalServices,
 	icon: 'ph-arrow-square-out ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/federation.vue b/packages/frontend/src/pages/admin/federation.vue
index 1888a0eb16..f8c4a3b272 100644
--- a/packages/frontend/src/pages/admin/federation.vue
+++ b/packages/frontend/src/pages/admin/federation.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -105,10 +105,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts.federation,
 	icon: 'ph-globe-hemisphere-west ph-bold ph-lg',
-})));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/files.vue b/packages/frontend/src/pages/admin/files.vue
index 6808da6088..2a70f1c4ec 100644
--- a/packages/frontend/src/pages/admin/files.vue
+++ b/packages/frontend/src/pages/admin/files.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -42,6 +42,7 @@ import MkInput from '@/components/MkInput.vue';
 import MkSelect from '@/components/MkSelect.vue';
 import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
@@ -79,11 +80,11 @@ function show(file) {
 async function find() {
 	const { canceled, result: q } = await os.inputText({
 		title: i18n.ts.fileIdOrUrl,
-		allowEmpty: false,
+		minLength: 1,
 	});
 	if (canceled) return;
 
-	os.api('admin/drive/show-file', q.startsWith('http://') || q.startsWith('https://') ? { url: q.trim() } : { fileId: q.trim() }).then(file => {
+	misskeyApi('admin/drive/show-file', q.startsWith('http://') || q.startsWith('https://') ? { url: q.trim() } : { fileId: q.trim() }).then(file => {
 		show(file);
 	}).catch(err => {
 		if (err.code === 'NO_SUCH_FILE') {
@@ -107,8 +108,8 @@ const headerActions = computed(() => [{
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts.files,
 	icon: 'ph-cloud ph-bold ph-lg',
-})));
+}));
 </script>
diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue
index 1b41a48cb4..0fd073dd0d 100644
--- a/packages/frontend/src/pages/admin/index.vue
+++ b/packages/frontend/src/pages/admin/index.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -29,15 +29,16 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ComputedRef, Ref, onActivated, onMounted, onUnmounted, provide, watch, ref, computed } from 'vue';
+import { onActivated, onMounted, onUnmounted, provide, watch, ref, computed } from 'vue';
 import { i18n } from '@/i18n.js';
 import MkSuperMenu from '@/components/MkSuperMenu.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import { instance } from '@/instance.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { lookupUser, lookupUserByEmail } from '@/scripts/lookup-user.js';
-import { useRouter } from '@/router.js';
-import { PageMetadata, definePageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
+import { PageMetadata, definePageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
+import { useRouter } from '@/router/supplier.js';
 
 const isEmpty = (x: string | null) => x == null || x === '';
 
@@ -52,26 +53,26 @@ const indexInfo = {
 provide('shouldOmitHeaderTitle', false);
 
 const INFO = ref(indexInfo);
-const childInfo: Ref<ComputedRef<PageMetadata> | null> = ref(null);
+const childInfo = ref<null | PageMetadata>(null);
 const narrow = ref(false);
 const view = ref(null);
 const el = ref<HTMLDivElement | null>(null);
 const pageProps = ref({});
 let noMaintainerInformation = isEmpty(instance.maintainerName) || isEmpty(instance.maintainerEmail);
-let noBotProtection = !instance.disableRegistration && !instance.enableHcaptcha && !instance.enableRecaptcha && !instance.enableTurnstile;
+let noBotProtection = !instance.disableRegistration && !instance.enableHcaptcha && !instance.enableRecaptcha && !instance.enableMcaptcha && !instance.enableTurnstile;
 let noEmailServer = !instance.enableEmail;
 const thereIsUnresolvedAbuseReport = ref(false);
 const pendingUserApprovals = ref(false);
 const currentPage = computed(() => router.currentRef.value.child);
 
-os.api('admin/abuse-user-reports', {
+misskeyApi('admin/abuse-user-reports', {
 	state: 'unresolved',
 	limit: 1,
 }).then(reports => {
 	if (reports.length > 0) thereIsUnresolvedAbuseReport.value = true;
 });
 
-os.api('admin/show-users', {
+misskeyApi('admin/show-users', {
 	state: 'approved',
 	origin: 'local',
 	limit: 1,
@@ -271,17 +272,19 @@ watch(router.currentRef, (to) => {
 	}
 });
 
-provideMetadataReceiver((info) => {
+provideMetadataReceiver((metadataGetter) => {
+	const info = metadataGetter();
 	if (info == null) {
 		childInfo.value = null;
 	} else {
 		childInfo.value = info;
-		INFO.value.needWideArea = info.value.needWideArea ?? undefined;
+		INFO.value.needWideArea = info.needWideArea ?? undefined;
 	}
 });
+provideReactiveMetadata(INFO);
 
 function invite() {
-	os.api('admin/invite/create').then(x => {
+	misskeyApi('admin/invite/create').then(x => {
 		os.alert({
 			type: 'info',
 			text: x[0].code,
@@ -309,7 +312,7 @@ function lookup(ev: MouseEvent) {
 		},
 	}, {
 		text: i18n.ts.note,
-		icon: 'ph-pencil ph-bold ph-lg',
+		icon: 'ph-pencil-simple ph-bold ph-lg',
 		action: () => {
 			alert('TODO');
 		},
@@ -332,7 +335,7 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(INFO.value);
+definePageMetadata(() => INFO.value);
 
 defineExpose({
 	header: {
diff --git a/packages/frontend/src/pages/admin/instance-block.vue b/packages/frontend/src/pages/admin/instance-block.vue
index e54f6dc065..fcb67633f6 100644
--- a/packages/frontend/src/pages/admin/instance-block.vue
+++ b/packages/frontend/src/pages/admin/instance-block.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -29,6 +29,7 @@ import MkButton from '@/components/MkButton.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { fetchInstance } from '@/instance.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
@@ -38,7 +39,7 @@ const silencedHosts = ref<string>('');
 const tab = ref('block');
 
 async function init() {
-	const meta = await os.api('admin/meta');
+	const meta = await misskeyApi('admin/meta');
 	blockedHosts.value = meta.blockedHosts.join('\n');
 	silencedHosts.value = meta.silencedHosts.join('\n');
 }
@@ -49,7 +50,7 @@ function save() {
 		silencedHosts: silencedHosts.value.split('\n') || [],
 
 	}).then(() => {
-		fetchInstance();
+		fetchInstance(true);
 	});
 }
 
@@ -65,8 +66,8 @@ const headerTabs = computed(() => [{
 	icon: 'ph-eye-closed ph-bold ph-lg',
 }]);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.instanceBlocking,
 	icon: 'ph-prohibit ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/admin/invites.vue b/packages/frontend/src/pages/admin/invites.vue
index 6314d0ce4e..7b8a1e1d4e 100644
--- a/packages/frontend/src/pages/admin/invites.vue
+++ b/packages/frontend/src/pages/admin/invites.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -59,6 +59,7 @@ import { computed, ref, shallowRef } from 'vue';
 import XHeader from './_header_.vue';
 import { i18n } from '@/i18n.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import MkButton from '@/components/MkButton.vue';
 import MkFolder from '@/components/MkFolder.vue';
 import MkSelect from '@/components/MkSelect.vue';
@@ -93,14 +94,14 @@ async function createWithOptions() {
 		count: createCount.value,
 	};
 
-	const tickets = await os.api('admin/invite/create', options);
+	const tickets = await misskeyApi('admin/invite/create', options);
 	os.alert({
 		type: 'success',
 		title: i18n.ts.inviteCodeCreated,
-		text: tickets?.map(x => x.code).join('\n'),
+		text: tickets.map(x => x.code).join('\n'),
 	});
 
-	tickets?.forEach(ticket => pagingComponent.value?.prepend(ticket));
+	tickets.forEach(ticket => pagingComponent.value?.prepend(ticket));
 }
 
 function deleted(id: string) {
@@ -112,10 +113,10 @@ function deleted(id: string) {
 const headerActions = computed(() => []);
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.invite,
 	icon: 'ph-user-plus ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/moderation.vue b/packages/frontend/src/pages/admin/moderation.vue
index 9539611f76..13af28b659 100644
--- a/packages/frontend/src/pages/admin/moderation.vue
+++ b/packages/frontend/src/pages/admin/moderation.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -49,6 +49,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 						<template #caption>{{ i18n.ts.sensitiveWordsDescription }}<br>{{ i18n.ts.sensitiveWordsDescription2 }}</template>
 					</MkTextarea>
 
+					<MkTextarea v-model="prohibitedWords">
+						<template #label>{{ i18n.ts.prohibitedWords }}</template>
+						<template #caption>{{ i18n.ts.prohibitedWordsDescription }}<br>{{ i18n.ts.prohibitedWordsDescription2 }}</template>
+					</MkTextarea>
+
 					<MkTextarea v-model="hiddenTags">
 						<template #label>{{ i18n.ts.hiddenTags }}</template>
 						<template #caption>{{ i18n.ts.hiddenTagsDescription }}</template>
@@ -75,6 +80,7 @@ import MkInput from '@/components/MkInput.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { fetchInstance } from '@/instance.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
@@ -86,6 +92,7 @@ const emailRequiredForSignup = ref<boolean>(false);
 const approvalRequiredForSignup = ref<boolean>(false);
 const bubbleTimelineEnabled = ref<boolean>(false);
 const sensitiveWords = ref<string>('');
+const prohibitedWords = ref<string>('');
 const hiddenTags = ref<string>('');
 const preservedUsernames = ref<string>('');
 const bubbleTimeline = ref<string>('');
@@ -93,11 +100,12 @@ const tosUrl = ref<string | null>(null);
 const privacyPolicyUrl = ref<string | null>(null);
 
 async function init() {
-	const meta = await os.api('admin/meta');
+	const meta = await misskeyApi('admin/meta');
 	enableRegistration.value = !meta.disableRegistration;
 	emailRequiredForSignup.value = meta.emailRequiredForSignup;
 	approvalRequiredForSignup.value = meta.approvalRequiredForSignup;
 	sensitiveWords.value = meta.sensitiveWords.join('\n');
+	prohibitedWords.value = meta.prohibitedWords.join('\n');
 	hiddenTags.value = meta.hiddenTags.join('\n');
 	preservedUsernames.value = meta.preservedUsernames.join('\n');
 	tosUrl.value = meta.tosUrl;
@@ -114,20 +122,21 @@ function save() {
 		tosUrl: tosUrl.value,
 		privacyPolicyUrl: privacyPolicyUrl.value,
 		sensitiveWords: sensitiveWords.value.split('\n'),
+		prohibitedWords: prohibitedWords.value.split('\n'),
 		hiddenTags: hiddenTags.value.split('\n'),
 		preservedUsernames: preservedUsernames.value.split('\n'),
 		bubbleInstances: bubbleTimeline.value.split('\n'),
 	}).then(() => {
-		fetchInstance();
+		fetchInstance(true);
 	});
 }
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.moderation,
 	icon: 'ph-shield ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/modlog.ModLog.vue b/packages/frontend/src/pages/admin/modlog.ModLog.vue
index 322d2d531c..03d5d6ece1 100644
--- a/packages/frontend/src/pages/admin/modlog.ModLog.vue
+++ b/packages/frontend/src/pages/admin/modlog.ModLog.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -114,6 +114,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<CodeDiff :context="5" :hideHeader="true" :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/>
 			</div>
 		</template>
+		<template v-else-if="log.type === 'updateRemoteInstanceNote'">
+			<div>{{ i18n.ts.user }}: {{ log.info.userId }}</div>
+			<div :class="$style.diff">
+				<CodeDiff :context="5" :hideHeader="true" :oldString="log.info.before ?? ''" :newString="log.info.after ?? ''" maxHeight="300px"/>
+			</div>
+		</template>
 
 		<details>
 			<summary>raw</summary>
diff --git a/packages/frontend/src/pages/admin/modlog.vue b/packages/frontend/src/pages/admin/modlog.vue
index acb0336491..4651bb4516 100644
--- a/packages/frontend/src/pages/admin/modlog.vue
+++ b/packages/frontend/src/pages/admin/modlog.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -54,14 +54,12 @@ const pagination = {
 	})),
 };
 
-console.log(Misskey);
-
 const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.moderationLogs,
 	icon: 'ph-list ph-bold ph-lg-search',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/admin/object-storage.vue b/packages/frontend/src/pages/admin/object-storage.vue
index e71e53c942..4f362e1814 100644
--- a/packages/frontend/src/pages/admin/object-storage.vue
+++ b/packages/frontend/src/pages/admin/object-storage.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -90,6 +90,7 @@ import MkInput from '@/components/MkInput.vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import FormSplit from '@/components/form/split.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { fetchInstance } from '@/instance.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
@@ -110,7 +111,7 @@ const objectStorageSetPublicRead = ref<boolean>(false);
 const objectStorageS3ForcePathStyle = ref<boolean>(true);
 
 async function init() {
-	const meta = await os.api('admin/meta');
+	const meta = await misskeyApi('admin/meta');
 	useObjectStorage.value = meta.useObjectStorage;
 	objectStorageBaseUrl.value = meta.objectStorageBaseUrl;
 	objectStorageBucket.value = meta.objectStorageBucket;
@@ -142,16 +143,16 @@ function save() {
 		objectStorageSetPublicRead: objectStorageSetPublicRead.value,
 		objectStorageS3ForcePathStyle: objectStorageS3ForcePathStyle.value,
 	}).then(() => {
-		fetchInstance();
+		fetchInstance(true);
 	});
 }
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.objectStorage,
 	icon: 'ph-cloud ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/other-settings.vue b/packages/frontend/src/pages/admin/other-settings.vue
index 6523676a18..20e0d6e578 100644
--- a/packages/frontend/src/pages/admin/other-settings.vue
+++ b/packages/frontend/src/pages/admin/other-settings.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -61,6 +61,7 @@ import { ref, computed } from 'vue';
 import XHeader from './_header_.vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { fetchInstance } from '@/instance.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
@@ -74,7 +75,7 @@ const enableChartsForRemoteUser = ref<boolean>(false);
 const enableChartsForFederatedInstances = ref<boolean>(false);
 
 async function init() {
-	const meta = await os.api('admin/meta');
+	const meta = await misskeyApi('admin/meta');
 	enableServerMachineStats.value = meta.enableServerMachineStats;
 	enableAchievements.value = meta.enableAchievements;
 	enableBotTrending.value = meta.enableBotTrending;
@@ -92,7 +93,7 @@ function save() {
 		enableChartsForRemoteUser: enableChartsForRemoteUser.value,
 		enableChartsForFederatedInstances: enableChartsForFederatedInstances.value,
 	}).then(() => {
-		fetchInstance();
+		fetchInstance(true);
 	});
 }
 
@@ -105,8 +106,8 @@ const headerActions = computed(() => [{
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.other,
 	icon: 'ph-faders ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/admin/overview.active-users.vue b/packages/frontend/src/pages/admin/overview.active-users.vue
index 5e67370c2b..79dd6fd5fd 100644
--- a/packages/frontend/src/pages/admin/overview.active-users.vue
+++ b/packages/frontend/src/pages/admin/overview.active-users.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { onMounted, shallowRef, ref } from 'vue';
 import { Chart } from 'chart.js';
 import gradient from 'chartjs-plugin-gradient';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { defaultStore } from '@/store.js';
 import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
 import { chartVLine } from '@/scripts/chart-vline.js';
@@ -52,7 +52,7 @@ async function renderChart() {
 		}));
 	};
 
-	const raw = await os.api('charts/active-users', { limit: chartLimit, span: 'day' });
+	const raw = await misskeyApi('charts/active-users', { limit: chartLimit, span: 'day' });
 
 	const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
 
diff --git a/packages/frontend/src/pages/admin/overview.ap-requests.vue b/packages/frontend/src/pages/admin/overview.ap-requests.vue
index 0de62fadea..d4c83f21b6 100644
--- a/packages/frontend/src/pages/admin/overview.ap-requests.vue
+++ b/packages/frontend/src/pages/admin/overview.ap-requests.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { onMounted, shallowRef, ref } from 'vue';
 import { Chart } from 'chart.js';
 import gradient from 'chartjs-plugin-gradient';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
 import { chartVLine } from '@/scripts/chart-vline.js';
 import { defaultStore } from '@/store.js';
@@ -65,7 +65,7 @@ onMounted(async () => {
 		}));
 	};
 
-	const raw = await os.api('charts/ap-request', { limit: chartLimit, span: 'day' });
+	const raw = await misskeyApi('charts/ap-request', { limit: chartLimit, span: 'day' });
 
 	const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
 	const succColor = '#87e000';
diff --git a/packages/frontend/src/pages/admin/overview.federation.vue b/packages/frontend/src/pages/admin/overview.federation.vue
index 2fad222bda..3a3550c6c0 100644
--- a/packages/frontend/src/pages/admin/overview.federation.vue
+++ b/packages/frontend/src/pages/admin/overview.federation.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -49,6 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { onMounted, ref } from 'vue';
 import XPie, { type InstanceForPie } from './overview.pie.vue';
 import * as os from '@/os.js';
+import { misskeyApiGet } from '@/scripts/misskey-api.js';
 import number from '@/filters/number.js';
 import MkNumberDiff from '@/components/MkNumberDiff.vue';
 import { i18n } from '@/i18n.js';
@@ -65,13 +66,13 @@ const fetching = ref(true);
 const { handler: externalTooltipHandler } = useChartTooltip();
 
 onMounted(async () => {
-	const chart = await os.apiGet('charts/federation', { limit: 2, span: 'day' });
+	const chart = await misskeyApiGet('charts/federation', { limit: 2, span: 'day' });
 	federationPubActive.value = chart.pubActive[0];
 	federationPubActiveDiff.value = chart.pubActive[0] - chart.pubActive[1];
 	federationSubActive.value = chart.subActive[0];
 	federationSubActiveDiff.value = chart.subActive[0] - chart.subActive[1];
 
-	os.apiGet('federation/stats', { limit: 10 }).then(res => {
+	misskeyApiGet('federation/stats', { limit: 10 }).then(res => {
 		topSubInstancesForPie.value = [
 			...res.topSubInstances.map(x => ({
 				name: x.host,
diff --git a/packages/frontend/src/pages/admin/overview.heatmap.vue b/packages/frontend/src/pages/admin/overview.heatmap.vue
index 8e3c809353..7b2b142b16 100644
--- a/packages/frontend/src/pages/admin/overview.heatmap.vue
+++ b/packages/frontend/src/pages/admin/overview.heatmap.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/pages/admin/overview.instances.vue b/packages/frontend/src/pages/admin/overview.instances.vue
index de34f0c09b..a09db2a6d5 100644
--- a/packages/frontend/src/pages/admin/overview.instances.vue
+++ b/packages/frontend/src/pages/admin/overview.instances.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -18,8 +18,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { ref } from 'vue';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import * as Misskey from 'misskey-js';
-import * as os from '@/os.js';
 import { useInterval } from '@/scripts/use-interval.js';
 import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue';
 import { defaultStore } from '@/store.js';
@@ -28,7 +28,7 @@ const instances = ref<Misskey.entities.FederationInstance[]>([]);
 const fetching = ref(true);
 
 const fetch = async () => {
-	const fetchedInstances = await os.api('federation/instances', {
+	const fetchedInstances = await misskeyApi('federation/instances', {
 		sort: '+latestRequestReceivedAt',
 		limit: 6,
 	});
diff --git a/packages/frontend/src/pages/admin/overview.moderators.vue b/packages/frontend/src/pages/admin/overview.moderators.vue
index 3034bdd57e..f0691534c8 100644
--- a/packages/frontend/src/pages/admin/overview.moderators.vue
+++ b/packages/frontend/src/pages/admin/overview.moderators.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -18,15 +18,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { onMounted, ref } from 'vue';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import * as Misskey from 'misskey-js';
-import * as os from '@/os.js';
 import { defaultStore } from '@/store.js';
 
 const moderators = ref<Misskey.entities.UserDetailed[] | null>(null);
 const fetching = ref(true);
 
 onMounted(async () => {
-	moderators.value = await os.api('admin/show-users', {
+	moderators.value = await misskeyApi('admin/show-users', {
 		sort: '+lastActiveDate',
 		state: 'adminOrModerator',
 		limit: 30,
diff --git a/packages/frontend/src/pages/admin/overview.pie.vue b/packages/frontend/src/pages/admin/overview.pie.vue
index 95c1f57b29..c7a9f2a702 100644
--- a/packages/frontend/src/pages/admin/overview.pie.vue
+++ b/packages/frontend/src/pages/admin/overview.pie.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/pages/admin/overview.queue.chart.vue b/packages/frontend/src/pages/admin/overview.queue.chart.vue
index 38309e351a..2efc17c888 100644
--- a/packages/frontend/src/pages/admin/overview.queue.chart.vue
+++ b/packages/frontend/src/pages/admin/overview.queue.chart.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/pages/admin/overview.queue.vue b/packages/frontend/src/pages/admin/overview.queue.vue
index b6b3bf194a..c7478f252a 100644
--- a/packages/frontend/src/pages/admin/overview.queue.vue
+++ b/packages/frontend/src/pages/admin/overview.queue.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/pages/admin/overview.retention.vue b/packages/frontend/src/pages/admin/overview.retention.vue
index 514db663ab..adcb9d5948 100644
--- a/packages/frontend/src/pages/admin/overview.retention.vue
+++ b/packages/frontend/src/pages/admin/overview.retention.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/pages/admin/overview.stats.vue b/packages/frontend/src/pages/admin/overview.stats.vue
index adbfe3f9e2..27ae52c32c 100644
--- a/packages/frontend/src/pages/admin/overview.stats.vue
+++ b/packages/frontend/src/pages/admin/overview.stats.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				</div>
 			</div>
 			<div class="item _panel notes">
-				<div class="icon"><i class="ph-pencil ph-bold ph-lg"></i></div>
+				<div class="icon"><i class="ph-pencil-simple ph-bold ph-lg"></i></div>
 				<div class="body">
 					<div class="value">
 						<MkNumber :value="stats.originalNotesCount" style="margin-right: 0.5em;"/>
@@ -63,7 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { onMounted, ref } from 'vue';
 import * as Misskey from 'misskey-js';
-import * as os from '@/os.js';
+import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
 import MkNumberDiff from '@/components/MkNumberDiff.vue';
 import MkNumber from '@/components/MkNumber.vue';
 import { i18n } from '@/i18n.js';
@@ -78,17 +78,17 @@ const fetching = ref(true);
 
 onMounted(async () => {
 	const [_stats, _onlineUsersCount] = await Promise.all([
-		os.api('stats', {}),
-		os.apiGet('get-online-users-count').then(res => res.count),
+		misskeyApi('stats', {}),
+		misskeyApiGet('get-online-users-count').then(res => res.count),
 	]);
 	stats.value = _stats;
 	onlineUsersCount.value = _onlineUsersCount;
 
-	os.apiGet('charts/users', { limit: 2, span: 'day' }).then(chart => {
+	misskeyApiGet('charts/users', { limit: 2, span: 'day' }).then(chart => {
 		usersComparedToThePrevDay.value = stats.value.originalUsersCount - chart.local.total[1];
 	});
 
-	os.apiGet('charts/notes', { limit: 2, span: 'day' }).then(chart => {
+	misskeyApiGet('charts/notes', { limit: 2, span: 'day' }).then(chart => {
 		notesComparedToThePrevDay.value = stats.value.originalNotesCount - chart.local.total[1];
 	});
 
diff --git a/packages/frontend/src/pages/admin/overview.users.vue b/packages/frontend/src/pages/admin/overview.users.vue
index 79579367c1..408be88d47 100644
--- a/packages/frontend/src/pages/admin/overview.users.vue
+++ b/packages/frontend/src/pages/admin/overview.users.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -18,8 +18,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { ref } from 'vue';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import * as Misskey from 'misskey-js';
-import * as os from '@/os.js';
 import { useInterval } from '@/scripts/use-interval.js';
 import MkUserCardMini from '@/components/MkUserCardMini.vue';
 import { defaultStore } from '@/store.js';
@@ -28,7 +28,7 @@ const newUsers = ref<Misskey.entities.UserDetailed[] | null>(null);
 const fetching = ref(true);
 
 const fetch = async () => {
-	const _newUsers = await os.api('admin/show-users', {
+	const _newUsers = await misskeyApi('admin/show-users', {
 		limit: 5,
 		sort: '+createdAt',
 		origin: 'local',
diff --git a/packages/frontend/src/pages/admin/overview.vue b/packages/frontend/src/pages/admin/overview.vue
index 9f2920ee0c..bf766600e5 100644
--- a/packages/frontend/src/pages/admin/overview.vue
+++ b/packages/frontend/src/pages/admin/overview.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -79,6 +79,7 @@ import XModerators from './overview.moderators.vue';
 import XHeatmap from './overview.heatmap.vue';
 import type { InstanceForPie } from './overview.pie.vue';
 import * as os from '@/os.js';
+import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
 import { useStream } from '@/stream.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
@@ -117,14 +118,14 @@ onMounted(async () => {
 	magicGrid.listen();
 	*/
 
-	os.apiGet('charts/federation', { limit: 2, span: 'day' }).then(chart => {
+	misskeyApiGet('charts/federation', { limit: 2, span: 'day' }).then(chart => {
 		federationPubActive.value = chart.pubActive[0];
 		federationPubActiveDiff.value = chart.pubActive[0] - chart.pubActive[1];
 		federationSubActive.value = chart.subActive[0];
 		federationSubActiveDiff.value = chart.subActive[0] - chart.subActive[1];
 	});
 
-	os.apiGet('federation/stats', { limit: 10 }).then(res => {
+	misskeyApiGet('federation/stats', { limit: 10 }).then(res => {
 		topSubInstancesForPie.value = [
 			...res.topSubInstances.map(x => ({
 				name: x.host,
@@ -149,18 +150,18 @@ onMounted(async () => {
 		];
 	});
 
-	os.api('admin/server-info').then(serverInfoResponse => {
+	misskeyApi('admin/server-info').then(serverInfoResponse => {
 		serverInfo.value = serverInfoResponse;
 	});
 
-	os.api('admin/show-users', {
+	misskeyApi('admin/show-users', {
 		limit: 5,
 		sort: '+createdAt',
 	}).then(res => {
 		newUsers.value = res;
 	});
 
-	os.api('federation/instances', {
+	misskeyApi('federation/instances', {
 		sort: '+latestRequestReceivedAt',
 		limit: 25,
 	}).then(res => {
@@ -183,10 +184,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.dashboard,
 	icon: 'ph-gauge ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/proxy-account.vue b/packages/frontend/src/pages/admin/proxy-account.vue
index 1425749bd4..59fd5911d4 100644
--- a/packages/frontend/src/pages/admin/proxy-account.vue
+++ b/packages/frontend/src/pages/admin/proxy-account.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -28,6 +28,7 @@ import MkButton from '@/components/MkButton.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { fetchInstance } from '@/instance.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
@@ -36,15 +37,15 @@ const proxyAccount = ref<Misskey.entities.UserDetailed | null>(null);
 const proxyAccountId = ref<string | null>(null);
 
 async function init() {
-	const meta = await os.api('admin/meta');
+	const meta = await misskeyApi('admin/meta');
 	proxyAccountId.value = meta.proxyAccountId;
 	if (proxyAccountId.value) {
-		proxyAccount.value = await os.api('users/show', { userId: proxyAccountId.value });
+		proxyAccount.value = await misskeyApi('users/show', { userId: proxyAccountId.value });
 	}
 }
 
 function chooseProxyAccount() {
-	os.selectUser().then(user => {
+	os.selectUser({ localOnly: true }).then(user => {
 		proxyAccount.value = user;
 		proxyAccountId.value = user.id;
 		save();
@@ -55,7 +56,7 @@ function save() {
 	os.apiWithDialog('admin/update-meta', {
 		proxyAccountId: proxyAccountId.value,
 	}).then(() => {
-		fetchInstance();
+		fetchInstance(true);
 	});
 }
 
@@ -63,8 +64,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.proxyAccount,
 	icon: 'ph-ghost ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/admin/queue.chart.chart.vue b/packages/frontend/src/pages/admin/queue.chart.chart.vue
index 566670c843..cc18898172 100644
--- a/packages/frontend/src/pages/admin/queue.chart.chart.vue
+++ b/packages/frontend/src/pages/admin/queue.chart.chart.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/pages/admin/queue.chart.vue b/packages/frontend/src/pages/admin/queue.chart.vue
index b829dd5738..f7b4b27a68 100644
--- a/packages/frontend/src/pages/admin/queue.chart.vue
+++ b/packages/frontend/src/pages/admin/queue.chart.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -51,7 +51,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { markRaw, onMounted, onUnmounted, ref, shallowRef } from 'vue';
 import XChart from './queue.chart.chart.vue';
 import number from '@/filters/number.js';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { useStream } from '@/stream.js';
 import { i18n } from '@/i18n.js';
 import MkFolder from '@/components/MkFolder.vue';
@@ -105,7 +105,7 @@ const onStatsLog = (statsLog) => {
 
 onMounted(() => {
 	if (props.domain === 'inbox' || props.domain === 'deliver') {
-		os.api(`admin/queue/${props.domain}-delayed`).then(result => {
+		misskeyApi(`admin/queue/${props.domain}-delayed`).then(result => {
 			jobs.value = result;
 		});
 	}
diff --git a/packages/frontend/src/pages/admin/queue.vue b/packages/frontend/src/pages/admin/queue.vue
index 245c55f6e7..ba6911a943 100644
--- a/packages/frontend/src/pages/admin/queue.vue
+++ b/packages/frontend/src/pages/admin/queue.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -68,8 +68,8 @@ const headerTabs = computed(() => [{
 	title: 'Inbox',
 }]);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.jobQueue,
 	icon: 'ph-clock ph-bold ph-lg-play',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/admin/relays.vue b/packages/frontend/src/pages/admin/relays.vue
index 578c29ee6c..6ff0d8bd22 100644
--- a/packages/frontend/src/pages/admin/relays.vue
+++ b/packages/frontend/src/pages/admin/relays.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<i v-if="relay.status === 'accepted'" class="ph-check ph-bold ph-lg" :class="$style.icon" style="color: var(--success);"></i>
 					<i v-else-if="relay.status === 'rejected'" class="ph-prohibit ph-bold ph-lg" :class="$style.icon" style="color: var(--error);"></i>
 					<i v-else class="ph-clock ph-bold ph-lg" :class="$style.icon"></i>
-					<span>{{ i18n.t(`_relayStatus.${relay.status}`) }}</span>
+					<span>{{ i18n.ts._relayStatus[relay.status] }}</span>
 				</div>
 				<MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="ph-trash ph-bold ph-lg"></i> {{ i18n.ts.remove }}</MkButton>
 			</div>
@@ -29,6 +29,7 @@ import * as Misskey from 'misskey-js';
 import XHeader from './_header_.vue';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
@@ -41,7 +42,7 @@ async function addRelay() {
 		placeholder: i18n.ts.inboxUrl,
 	});
 	if (canceled) return;
-	os.api('admin/relays/add', {
+	misskeyApi('admin/relays/add', {
 		inbox,
 	}).then((relay: any) => {
 		refresh();
@@ -54,7 +55,7 @@ async function addRelay() {
 }
 
 function remove(inbox: string) {
-	os.api('admin/relays/remove', {
+	misskeyApi('admin/relays/remove', {
 		inbox,
 	}).then(() => {
 		refresh();
@@ -67,7 +68,7 @@ function remove(inbox: string) {
 }
 
 function refresh() {
-	os.api('admin/relays/list').then(relayList => {
+	misskeyApi('admin/relays/list').then(relayList => {
 		relays.value = relayList;
 	});
 }
@@ -83,10 +84,10 @@ const headerActions = computed(() => [{
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.relays,
 	icon: 'ph-planet ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/roles.edit.vue b/packages/frontend/src/pages/admin/roles.edit.vue
index 980c311156..e6023d2f2a 100644
--- a/packages/frontend/src/pages/admin/roles.edit.vue
+++ b/packages/frontend/src/pages/admin/roles.edit.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -28,11 +28,12 @@ import { v4 as uuid } from 'uuid';
 import XHeader from './_header_.vue';
 import XEditor from './roles.editor.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { useRouter } from '@/router.js';
 import MkButton from '@/components/MkButton.vue';
 import { rolesCache } from '@/cache.js';
+import { useRouter } from '@/router/supplier.js';
 
 const router = useRouter();
 
@@ -44,7 +45,7 @@ const role = ref<Misskey.entities.Role | null>(null);
 const data = ref<any>(null);
 
 if (props.id) {
-	role.value = await os.api('admin/roles/show', {
+	role.value = await misskeyApi('admin/roles/show', {
 		roleId: props.id,
 	});
 
@@ -86,11 +87,8 @@ async function save() {
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => role.value ? {
-	title: i18n.ts._role.edit + ': ' + role.value.name,
-	icon: 'ph-seal-check ph-bold ph-lg',
-} : {
-	title: i18n.ts._role.new,
+definePageMetadata(() => ({
+	title: role.value ? `${i18n.ts._role.edit}: ${role.value.name}` : i18n.ts._role.new,
 	icon: 'ph-seal-check ph-bold ph-lg',
 }));
 </script>
diff --git a/packages/frontend/src/pages/admin/roles.editor.vue b/packages/frontend/src/pages/admin/roles.editor.vue
index 164510fd24..99a31d5157 100644
--- a/packages/frontend/src/pages/admin/roles.editor.vue
+++ b/packages/frontend/src/pages/admin/roles.editor.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -200,6 +200,25 @@ SPDX-License-Identifier: AGPL-3.0-only
 				</div>
 			</MkFolder>
 
+			<MkFolder v-if="matchQuery([i18n.ts._role._options.mentionMax, 'mentionLimit'])">
+				<template #label>{{ i18n.ts._role._options.mentionMax }}</template>
+				<template #suffix>
+					<span v-if="role.policies.mentionLimit.useDefault" :class="$style.useDefaultLabel">{{ i18n.ts._role.useBaseValue }}</span>
+					<span v-else>{{ role.policies.mentionLimit.value }}</span>
+					<span :class="$style.priorityIndicator"><i :class="getPriorityIcon(role.policies.mentionLimit)"></i></span>
+				</template>
+				<div class="_gaps">
+					<MkSwitch v-model="role.policies.mentionLimit.useDefault" :readonly="readonly">
+						<template #label>{{ i18n.ts._role.useBaseValue }}</template>
+					</MkSwitch>
+					<MkInput v-model="role.policies.mentionLimit.value" :disabled="role.policies.mentionLimit.useDefault" type="number" :readonly="readonly">
+					</MkInput>
+					<MkRange v-model="role.policies.mentionLimit.priority" :min="0" :max="2" :step="1" easing :textConverter="(v) => v === 0 ? i18n.ts._role._priority.low : v === 1 ? i18n.ts._role._priority.middle : v === 2 ? i18n.ts._role._priority.high : ''">
+						<template #label>{{ i18n.ts._role.priority }}</template>
+					</MkRange>
+				</div>
+			</MkFolder>
+
 			<MkFolder v-if="matchQuery([i18n.ts._role._options.canInvite, 'canInvite'])">
 				<template #label>{{ i18n.ts._role._options.canInvite }}</template>
 				<template #suffix>
diff --git a/packages/frontend/src/pages/admin/roles.role.vue b/packages/frontend/src/pages/admin/roles.role.vue
index 92818cc3de..cda524f787 100644
--- a/packages/frontend/src/pages/admin/roles.role.vue
+++ b/packages/frontend/src/pages/admin/roles.role.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<MkSpacer :contentMax="700">
 			<div class="_gaps">
 				<div class="_buttons">
-					<MkButton primary rounded @click="edit"><i class="ph-pencil ph-bold ph-lg"></i> {{ i18n.ts.edit }}</MkButton>
+					<MkButton primary rounded @click="edit"><i class="ph-pencil-simple ph-bold ph-lg"></i> {{ i18n.ts.edit }}</MkButton>
 					<MkButton danger rounded @click="del"><i class="ph-trash ph-bold ph-lg"></i> {{ i18n.ts.delete }}</MkButton>
 				</div>
 				<MkFolder>
@@ -67,14 +67,15 @@ import XHeader from './_header_.vue';
 import XEditor from './roles.editor.vue';
 import MkFolder from '@/components/MkFolder.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { useRouter } from '@/router.js';
 import MkButton from '@/components/MkButton.vue';
 import MkUserCardMini from '@/components/MkUserCardMini.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import MkPagination from '@/components/MkPagination.vue';
 import { infoImageUrl } from '@/instance.js';
+import { useRouter } from '@/router/supplier.js';
 
 const router = useRouter();
 
@@ -92,7 +93,7 @@ const usersPagination = {
 
 const expandedItems = ref([]);
 
-const role = reactive(await os.api('admin/roles/show', {
+const role = reactive(await misskeyApi('admin/roles/show', {
 	roleId: props.id,
 }));
 
@@ -103,7 +104,7 @@ function edit() {
 async function del() {
 	const { canceled } = await os.confirm({
 		type: 'warning',
-		text: i18n.t('deleteAreYouSure', { x: role.name }),
+		text: i18n.tsx.deleteAreYouSure({ x: role.name }),
 	});
 	if (canceled) return;
 
@@ -115,9 +116,7 @@ async function del() {
 }
 
 async function assign() {
-	const user = await os.selectUser({
-		includeSelf: true,
-	});
+	const user = await os.selectUser({ includeSelf: true });
 
 	const { canceled: canceled2, result: period } = await os.select({
 		title: i18n.ts.period,
@@ -171,10 +170,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => ({
-	title: i18n.ts.role + ': ' + role.name,
+definePageMetadata(() => ({
+	title: `${i18n.ts.role}: ${role.name}`,
 	icon: 'ph-seal-check ph-bold ph-lg',
-})));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue
index 9cb48130d8..f104f9a00d 100644
--- a/packages/frontend/src/pages/admin/roles.vue
+++ b/packages/frontend/src/pages/admin/roles.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -67,6 +67,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 							</MkSwitch>
 						</MkFolder>
 
+						<MkFolder v-if="matchQuery([i18n.ts._role._options.mentionMax, 'mentionLimit'])">
+							<template #label>{{ i18n.ts._role._options.mentionMax }}</template>
+							<template #suffix>{{ policies.mentionLimit }}</template>
+							<MkInput v-model="policies.mentionLimit" type="number">
+							</MkInput>
+						</MkFolder>
+
 						<MkFolder v-if="matchQuery([i18n.ts._role._options.canInvite, 'canInvite'])">
 							<template #label>{{ i18n.ts._role._options.canInvite }}</template>
 							<template #suffix>{{ policies.canInvite ? i18n.ts.yes : i18n.ts.no }}</template>
@@ -253,17 +260,18 @@ import MkRange from '@/components/MkRange.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import MkRolePreview from '@/components/MkRolePreview.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { instance } from '@/instance.js';
-import { useRouter } from '@/router.js';
 import MkFoldableSection from '@/components/MkFoldableSection.vue';
 import { ROLE_POLICIES } from '@/const.js';
+import { useRouter } from '@/router/supplier.js';
 
 const router = useRouter();
 const baseRoleQ = ref('');
 
-const roles = await os.api('admin/roles/list');
+const roles = await misskeyApi('admin/roles/list');
 
 const policies = reactive<Record<typeof ROLE_POLICIES[number], any>>({});
 for (const ROLE_POLICY of ROLE_POLICIES) {
@@ -289,10 +297,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts.roles,
 	icon: 'ph-seal-check ph-bold ph-lg',
-})));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue
index 8ed3e20af3..8e75975209 100644
--- a/packages/frontend/src/pages/admin/security.vue
+++ b/packages/frontend/src/pages/admin/security.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -13,6 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<template #icon><i class="ph-shield ph-bold ph-lg"></i></template>
 					<template #label>{{ i18n.ts.botProtection }}</template>
 					<template v-if="enableHcaptcha" #suffix>hCaptcha</template>
+					<template v-else-if="enableMcaptcha" #suffix>mCaptcha</template>
 					<template v-else-if="enableRecaptcha" #suffix>reCAPTCHA</template>
 					<template v-else-if="enableTurnstile" #suffix>Turnstile</template>
 					<template v-else #suffix>{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</template>
@@ -27,16 +28,28 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 					<div class="_gaps_m">
 						<span>{{ i18n.ts.activeEmailValidationDescription }}</span>
-						<MkSwitch v-model="enableActiveEmailValidation" @update:modelValue="save">
+						<MkSwitch v-model="enableActiveEmailValidation">
 							<template #label>Enable</template>
 						</MkSwitch>
-						<MkSwitch v-model="enableVerifymailApi" @update:modelValue="save">
+						<MkSwitch v-model="enableVerifymailApi">
 							<template #label>Use Verifymail.io API</template>
 						</MkSwitch>
-						<MkInput v-model="verifymailAuthKey" @update:modelValue="save">
+						<MkInput v-model="verifymailAuthKey">
 							<template #prefix><i class="ph-key ph-bold ph-lg"></i></template>
 							<template #label>Verifymail.io API Auth Key</template>
 						</MkInput>
+						<MkSwitch v-model="enableTruemailApi">
+							<template #label>Use TrueMail API</template>
+						</MkSwitch>
+						<MkInput v-model="truemailInstance">
+							<template #prefix><i class="ph-key ph-bold ph-lg"></i></template>
+							<template #label>TrueMail API Instance</template>
+						</MkInput>
+						<MkInput v-model="truemailAuthKey">
+							<template #prefix><i class="ph-key ph-bold ph-lg"></i></template>
+							<template #label>TrueMail API Auth Key</template>
+						</MkInput>
+						<MkButton primary @click="save"><i class="ph-floppy-disk ph-bold ph-lg"></i> {{ i18n.ts.save }}</MkButton>
 					</div>
 				</MkFolder>
 
@@ -94,31 +107,40 @@ import MkInput from '@/components/MkInput.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { fetchInstance } from '@/instance.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
 const summalyProxy = ref<string>('');
 const enableHcaptcha = ref<boolean>(false);
+const enableMcaptcha = ref<boolean>(false);
 const enableRecaptcha = ref<boolean>(false);
 const enableTurnstile = ref<boolean>(false);
 const enableIpLogging = ref<boolean>(false);
 const enableActiveEmailValidation = ref<boolean>(false);
 const enableVerifymailApi = ref<boolean>(false);
 const verifymailAuthKey = ref<string | null>(null);
+const enableTruemailApi = ref<boolean>(false);
+const truemailInstance = ref<string | null>(null);
+const truemailAuthKey = ref<string | null>(null);
 const bannedEmailDomains = ref<string>('');
 
 async function init() {
-	const meta = await os.api('admin/meta');
+	const meta = await misskeyApi('admin/meta');
 	summalyProxy.value = meta.summalyProxy;
 	enableHcaptcha.value = meta.enableHcaptcha;
+	enableMcaptcha.value = meta.enableMcaptcha;
 	enableRecaptcha.value = meta.enableRecaptcha;
 	enableTurnstile.value = meta.enableTurnstile;
 	enableIpLogging.value = meta.enableIpLogging;
 	enableActiveEmailValidation.value = meta.enableActiveEmailValidation;
 	enableVerifymailApi.value = meta.enableVerifymailApi;
 	verifymailAuthKey.value = meta.verifymailAuthKey;
-	bannedEmailDomains.value = meta.bannedEmailDomains.join('\n');
+	enableTruemailApi.value = meta.enableTruemailApi;
+	truemailInstance.value = meta.truemailInstance;
+	truemailAuthKey.value = meta.truemailAuthKey;
+	bannedEmailDomains.value = meta.bannedEmailDomains?.join('\n') || '';
 }
 
 function save() {
@@ -128,9 +150,12 @@ function save() {
 		enableActiveEmailValidation: enableActiveEmailValidation.value,
 		enableVerifymailApi: enableVerifymailApi.value,
 		verifymailAuthKey: verifymailAuthKey.value,
+		enableTruemailApi: enableTruemailApi.value,
+		truemailInstance: truemailInstance.value,
+		truemailAuthKey: truemailAuthKey.value,
 		bannedEmailDomains: bannedEmailDomains.value.split('\n'),
 	}).then(() => {
-		fetchInstance();
+		fetchInstance(true);
 	});
 }
 
@@ -138,8 +163,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.security,
 	icon: 'ph-lock ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/admin/server-rules.vue b/packages/frontend/src/pages/admin/server-rules.vue
index 6aecb43399..db2bb56eac 100644
--- a/packages/frontend/src/pages/admin/server-rules.vue
+++ b/packages/frontend/src/pages/admin/server-rules.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -58,7 +58,7 @@ const save = async () => {
 	await os.apiWithDialog('admin/update-meta', {
 		serverRules: serverRules.value,
 	});
-	fetchInstance();
+	fetchInstance(true);
 };
 
 const remove = (index: number): void => {
@@ -67,10 +67,10 @@ const remove = (index: number): void => {
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.serverRules,
-	icon: 'ph-check ph-bold ph-lgbox',
-});
+	icon: 'ph-check ph-bold ph-lg',
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue
index 649f22e644..887ac6fb4c 100644
--- a/packages/frontend/src/pages/admin/settings.vue
+++ b/packages/frontend/src/pages/admin/settings.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -34,12 +34,27 @@ SPDX-License-Identifier: AGPL-3.0-only
 						</MkInput>
 					</FormSplit>
 
+					<MkInput v-model="repositoryUrl" type="url">
+						<template #label>{{ i18n.ts.repositoryUrl }}</template>
+						<template #prefix><i class="ph-link ph-bold ph-lg"></i></template>
+						<template #caption>{{ i18n.ts.repositoryUrlDescription }}</template>
+					</MkInput>
+
+					<MkInfo v-if="!instance.providesTarball && !repositoryUrl" warn>
+						{{ i18n.ts.repositoryUrlOrTarballRequired }}
+					</MkInfo>
+
 					<MkInput v-model="impressumUrl" type="url">
 						<template #label>{{ i18n.ts.impressumUrl }}</template>
 						<template #prefix><i class="ph-link ph-bold ph-lg"></i></template>
 						<template #caption>{{ i18n.ts.impressumDescription }}</template>
 					</MkInput>
 
+					<MkInput v-model="donationUrl" type="url">
+						<template #label>{{ i18n.ts.donationUrl }}</template>
+						<template #prefix><i class="ph-link ph-bold ph-lg"></i></template>
+					</MkInput>
+
 					<MkTextarea v-model="pinnedUsers">
 						<template #label>{{ i18n.ts.pinnedUsers }}</template>
 						<template #caption>{{ i18n.ts.pinnedUsersDescription }}</template>
@@ -158,7 +173,8 @@ import FormSection from '@/components/form/section.vue';
 import FormSplit from '@/components/form/split.vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import * as os from '@/os.js';
-import { fetchInstance } from '@/instance.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
+import { fetchInstance, instance } from '@/instance.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkButton from '@/components/MkButton.vue';
@@ -168,7 +184,9 @@ const shortName = ref<string | null>(null);
 const description = ref<string | null>(null);
 const maintainerName = ref<string | null>(null);
 const maintainerEmail = ref<string | null>(null);
+const repositoryUrl = ref<string | null>(null);
 const impressumUrl = ref<string | null>(null);
+const donationUrl = ref<string | null>(null);
 const pinnedUsers = ref<string>('');
 const cacheRemoteFiles = ref<boolean>(false);
 const cacheRemoteSensitiveFiles = ref<boolean>(false);
@@ -184,13 +202,15 @@ const perUserListTimelineCacheMax = ref<number>(0);
 const notesPerOneAd = ref<number>(0);
 
 async function init(): Promise<void> {
-	const meta = await os.api('admin/meta');
+	const meta = await misskeyApi('admin/meta');
 	name.value = meta.name;
 	shortName.value = meta.shortName;
 	description.value = meta.description;
 	maintainerName.value = meta.maintainerName;
 	maintainerEmail.value = meta.maintainerEmail;
+	repositoryUrl.value = meta.repositoryUrl;
 	impressumUrl.value = meta.impressumUrl;
+	donationUrl.value = meta.donationUrl;
 	pinnedUsers.value = meta.pinnedUsers.join('\n');
 	cacheRemoteFiles.value = meta.cacheRemoteFiles;
 	cacheRemoteSensitiveFiles.value = meta.cacheRemoteSensitiveFiles;
@@ -213,7 +233,9 @@ async function save(): void {
 		description: description.value,
 		maintainerName: maintainerName.value,
 		maintainerEmail: maintainerEmail.value,
+		repositoryUrl: repositoryUrl.value,
 		impressumUrl: impressumUrl.value,
+		donationUrl: donationUrl.value,
 		pinnedUsers: pinnedUsers.value.split('\n'),
 		cacheRemoteFiles: cacheRemoteFiles.value,
 		cacheRemoteSensitiveFiles: cacheRemoteSensitiveFiles.value,
@@ -229,15 +251,15 @@ async function save(): void {
 		notesPerOneAd: notesPerOneAd.value,
 	});
 
-	fetchInstance();
+	fetchInstance(true);
 }
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.general,
 	icon: 'ph-gear ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/admin/users.vue b/packages/frontend/src/pages/admin/users.vue
index 1bc4eb4089..626346a998 100644
--- a/packages/frontend/src/pages/admin/users.vue
+++ b/packages/frontend/src/pages/admin/users.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -91,7 +91,7 @@ const pagination = {
 };
 
 function searchUser() {
-	os.selectUser().then(user => {
+	os.selectUser({ includeSelf: true }).then(user => {
 		show(user);
 	});
 }
@@ -138,10 +138,10 @@ const headerActions = computed(() => [{
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts.users,
 	icon: 'ph-users ph-bold ph-lg',
-})));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/ads.vue b/packages/frontend/src/pages/ads.vue
index 9d508937af..c6373e8d60 100644
--- a/packages/frontend/src/pages/ads.vue
+++ b/packages/frontend/src/pages/ads.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -20,9 +20,9 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { i18n } from '@/i18n.js';
 import { instance } from '@/instance.js';
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.ads,
 	icon: 'ph-flag ph-bold ph-lg',
-});
+}));
 </script>
 
diff --git a/packages/frontend/src/pages/announcements.vue b/packages/frontend/src/pages/announcements.vue
index 705115abb0..4f5abdb385 100644
--- a/packages/frontend/src/pages/announcements.vue
+++ b/packages/frontend/src/pages/announcements.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -7,34 +7,36 @@ SPDX-License-Identifier: AGPL-3.0-only
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
 	<MkSpacer :contentMax="800">
-		<div class="_gaps">
-			<MkInfo v-if="$i && $i.hasUnreadAnnouncement && tab === 'current'" warn>{{ i18n.ts.youHaveUnreadAnnouncements }}</MkInfo>
-			<MkPagination ref="paginationEl" :key="tab" v-slot="{items}" :pagination="tab === 'current' ? paginationCurrent : paginationPast" class="_gaps">
-				<section v-for="announcement in items" :key="announcement.id" class="_panel" :class="$style.announcement">
-					<div v-if="announcement.forYou" :class="$style.forYou"><i class="ph-push-pin ph-bold ph-lg"></i> {{ i18n.ts.forYou }}</div>
-					<div :class="$style.header">
-						<span v-if="$i && !announcement.silence && !announcement.isRead" style="margin-right: 0.5em;">🆕</span>
-						<span style="margin-right: 0.5em;">
-							<i v-if="announcement.icon === 'info'" class="ph-info ph-bold ph-lg"></i>
-							<i v-else-if="announcement.icon === 'warning'" class="ph-warning ph-bold ph-lg" style="color: var(--warn);"></i>
-							<i v-else-if="announcement.icon === 'error'" class="ph-x-circle ph-bold ph-lg" style="color: var(--error);"></i>
-							<i v-else-if="announcement.icon === 'success'" class="ph-check ph-bold ph-lg" style="color: var(--success);"></i>
-						</span>
-						<span>{{ announcement.title }}</span>
-					</div>
-					<div :class="$style.content">
-						<Mfm :text="announcement.text"/>
-						<img v-if="announcement.imageUrl" :src="announcement.imageUrl"/>
-						<div style="opacity: 0.7; font-size: 85%;">
-							<MkTime :time="announcement.updatedAt ?? announcement.createdAt" mode="detail"/>
+		<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
+			<div :key="tab" class="_gaps">
+				<MkInfo v-if="$i && $i.hasUnreadAnnouncement && tab === 'current'" warn>{{ i18n.ts.youHaveUnreadAnnouncements }}</MkInfo>
+				<MkPagination ref="paginationEl" :key="tab" v-slot="{items}" :pagination="tab === 'current' ? paginationCurrent : paginationPast" class="_gaps">
+					<section v-for="announcement in items" :key="announcement.id" class="_panel" :class="$style.announcement">
+						<div v-if="announcement.forYou" :class="$style.forYou"><i class="ph-push-pin ph-bold ph-lg"></i> {{ i18n.ts.forYou }}</div>
+						<div :class="$style.header">
+							<span v-if="$i && !announcement.silence && !announcement.isRead" style="margin-right: 0.5em;">🆕</span>
+							<span style="margin-right: 0.5em;">
+								<i v-if="announcement.icon === 'info'" class="ph-info ph-bold ph-lg"></i>
+								<i v-else-if="announcement.icon === 'warning'" class="ph-warning ph-bold ph-lg" style="color: var(--warn);"></i>
+								<i v-else-if="announcement.icon === 'error'" class="ph-x-circle ph-bold ph-lg" style="color: var(--error);"></i>
+								<i v-else-if="announcement.icon === 'success'" class="ph-check ph-bold ph-lg" style="color: var(--success);"></i>
+							</span>
+							<span>{{ announcement.title }}</span>
 						</div>
-					</div>
-					<div v-if="tab !== 'past' && $i && !announcement.silence && !announcement.isRead" :class="$style.footer">
-						<MkButton primary @click="read(announcement)"><i class="ph-check ph-bold ph-lg"></i> {{ i18n.ts.gotIt }}</MkButton>
-					</div>
-				</section>
-			</MkPagination>
-		</div>
+						<div :class="$style.content">
+							<Mfm :text="announcement.text"/>
+							<img v-if="announcement.imageUrl" :src="announcement.imageUrl"/>
+							<div style="opacity: 0.7; font-size: 85%;">
+								<MkTime :time="announcement.updatedAt ?? announcement.createdAt" mode="detail"/>
+							</div>
+						</div>
+						<div v-if="tab !== 'past' && $i && !announcement.silence && !announcement.isRead" :class="$style.footer">
+							<MkButton primary @click="read(announcement)"><i class="ph-check ph-bold ph-lg"></i> {{ i18n.ts.gotIt }}</MkButton>
+						</div>
+					</section>
+				</MkPagination>
+			</div>
+		</MkHorizontalSwipe>
 	</MkSpacer>
 </MkStickyContainer>
 </template>
@@ -44,7 +46,9 @@ import { ref, computed } from 'vue';
 import MkPagination from '@/components/MkPagination.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInfo from '@/components/MkInfo.vue';
+import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { $i, updateAccount } from '@/account.js';
@@ -74,7 +78,7 @@ async function read(announcement) {
 		const confirm = await os.confirm({
 			type: 'question',
 			title: i18n.ts._announcement.readConfirmTitle,
-			text: i18n.t('_announcement.readConfirmText', { title: announcement.title }),
+			text: i18n.tsx._announcement.readConfirmText({ title: announcement.title }),
 		});
 		if (confirm.canceled) return;
 	}
@@ -84,7 +88,7 @@ async function read(announcement) {
 		a.isRead = true;
 		return a;
 	});
-	os.api('i/read-announcement', { announcementId: announcement.id });
+	misskeyApi('i/read-announcement', { announcementId: announcement.id });
 	updateAccount({
 		unreadAnnouncements: $i!.unreadAnnouncements.filter(a => a.id !== announcement.id),
 	});
@@ -102,10 +106,10 @@ const headerTabs = computed(() => [{
 	icon: 'ph-circle ph-bold ph-lg',
 }]);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.announcements,
 	icon: 'ph-megaphone ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/antenna-timeline.vue b/packages/frontend/src/pages/antenna-timeline.vue
index 9abf0b9776..3e8deff711 100644
--- a/packages/frontend/src/pages/antenna-timeline.vue
+++ b/packages/frontend/src/pages/antenna-timeline.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -29,9 +29,10 @@ import * as Misskey from 'misskey-js';
 import MkTimeline from '@/components/MkTimeline.vue';
 import { scroll } from '@/scripts/scroll.js';
 import * as os from '@/os.js';
-import { useRouter } from '@/router.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { i18n } from '@/i18n.js';
+import { useRouter } from '@/router/supplier.js';
 
 const router = useRouter();
 
@@ -73,7 +74,7 @@ function focus() {
 }
 
 watch(() => props.antennaId, async () => {
-	antenna.value = await os.api('antennas/show', {
+	antenna.value = await misskeyApi('antennas/show', {
 		antennaId: props.antennaId,
 	});
 }, { immediate: true });
@@ -90,10 +91,10 @@ const headerActions = computed(() => antenna.value ? [{
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => antenna.value ? {
-	title: antenna.value.name,
+definePageMetadata(() => ({
+	title: antenna.value ? antenna.value.name : i18n.ts.antennas,
 	icon: 'ph-flying-saucer ph-bold ph-lg',
-} : null));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/api-console.vue b/packages/frontend/src/pages/api-console.vue
index dcdb5b8fe3..4d0cb2897f 100644
--- a/packages/frontend/src/pages/api-console.vue
+++ b/packages/frontend/src/pages/api-console.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -41,7 +41,7 @@ import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
 const body = ref('{}');
@@ -51,14 +51,14 @@ const sending = ref(false);
 const res = ref('');
 const withCredential = ref(true);
 
-os.api('endpoints').then(endpointResponse => {
+misskeyApi('endpoints').then(endpointResponse => {
 	endpoints.value = endpointResponse;
 });
 
 function send() {
 	sending.value = true;
 	const requestBody = JSON5.parse(body.value);
-	os.api(endpoint.value as keyof Endpoints, requestBody, requestBody.i || (withCredential.value ? undefined : null)).then(resp => {
+	misskeyApi(endpoint.value as keyof Endpoints, requestBody, requestBody.i || (withCredential.value ? undefined : null)).then(resp => {
 		sending.value = false;
 		res.value = JSON5.stringify(resp, null, 2);
 	}, err => {
@@ -68,7 +68,7 @@ function send() {
 }
 
 function onEndpointChange() {
-	os.api('endpoint', { endpoint: endpoint.value }, withCredential.value ? undefined : null).then(resp => {
+	misskeyApi('endpoint', { endpoint: endpoint.value }, withCredential.value ? undefined : null).then(resp => {
 		const endpointBody = {};
 		for (const p of resp.params) {
 			endpointBody[p.name] =
@@ -87,8 +87,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: 'API console',
 	icon: 'ph-terminal-window ph-bold ph-lg-2',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/auth.form.vue b/packages/frontend/src/pages/auth.form.vue
index 8a17e5895d..f4fb2ef4d5 100644
--- a/packages/frontend/src/pages/auth.form.vue
+++ b/packages/frontend/src/pages/auth.form.vue
@@ -1,17 +1,17 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
 <section>
 	<div v-if="app.permission.length > 0">
-		<p>{{ i18n.t('_auth.permission', { name }) }}</p>
+		<p>{{ i18n.tsx._auth.permission({ name }) }}</p>
 		<ul>
-			<li v-for="p in app.permission" :key="p">{{ i18n.t(`_permissions.${p}`) }}</li>
+			<li v-for="p in app.permission" :key="p">{{ i18n.ts._permissions[p] }}</li>
 		</ul>
 	</div>
-	<div>{{ i18n.t('_auth.shareAccess', { name: `${name} (${app.id})` }) }}</div>
+	<div>{{ i18n.tsx._auth.shareAccess({ name: `${name} (${app.id})` }) }}</div>
 	<div :class="$style.buttons">
 		<MkButton inline @click="cancel">{{ i18n.ts.cancel }}</MkButton>
 		<MkButton inline primary @click="accept">{{ i18n.ts.accept }}</MkButton>
@@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkButton from '@/components/MkButton.vue';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 
 const props = defineProps<{
@@ -44,7 +44,7 @@ const name = computed(() => {
 });
 
 function cancel() {
-	os.api('auth/deny', {
+	misskeyApi('auth/deny', {
 		token: props.session.token,
 	}).then(() => {
 		emit('denied');
@@ -52,7 +52,7 @@ function cancel() {
 }
 
 function accept() {
-	os.api('auth/accept', {
+	misskeyApi('auth/accept', {
 		token: props.session.token,
 	}).then(() => {
 		emit('accepted');
diff --git a/packages/frontend/src/pages/auth.vue b/packages/frontend/src/pages/auth.vue
index d97e89842d..cb735a26d8 100644
--- a/packages/frontend/src/pages/auth.vue
+++ b/packages/frontend/src/pages/auth.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<h1>{{ i18n.ts._auth.denied }}</h1>
 			</div>
 			<div v-if="state == 'accepted' && session">
-				<h1>{{ session.app.isAuthorized ? i18n.t('already-authorized') : i18n.ts.allowed }}</h1>
+				<h1>{{ session.app.isAuthorized ? i18n.ts['already-authorized'] : i18n.ts.allowed }}</h1>
 				<p v-if="session.app.callbackUrl">
 					{{ i18n.ts._auth.callback }}
 					<MkEllipsis/>
@@ -46,7 +46,7 @@ import { onMounted, ref, computed } from 'vue';
 import * as Misskey from 'misskey-js';
 import XForm from './auth.form.vue';
 import MkSignin from '@/components/MkSignin.vue';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { $i, login } from '@/account.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { i18n } from '@/i18n.js';
@@ -96,13 +96,13 @@ onMounted(async () => {
 	if (!$i) return;
 
 	try {
-		session.value = await os.api('auth/session/show', {
+		session.value = await misskeyApi('auth/session/show', {
 			token: props.token,
 		});
 
 		// 既に連携していた場合
 		if (session.value.app.isAuthorized) {
-			await os.api('auth/accept', {
+			await misskeyApi('auth/accept', {
 				token: session.value.token,
 			});
 			accepted();
@@ -118,10 +118,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts._auth.shareAccessTitle,
 	icon: 'ph-squares-four ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/avatar-decorations.vue b/packages/frontend/src/pages/avatar-decorations.vue
index 30b100a7fb..41c87d3625 100644
--- a/packages/frontend/src/pages/avatar-decorations.vue
+++ b/packages/frontend/src/pages/avatar-decorations.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -40,6 +40,7 @@ import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkFolder from '@/components/MkFolder.vue';
@@ -59,11 +60,11 @@ function add() {
 function del(avatarDecoration) {
 	os.confirm({
 		type: 'warning',
-		text: i18n.t('deleteAreYouSure', { x: avatarDecoration.name }),
+		text: i18n.tsx.deleteAreYouSure({ x: avatarDecoration.name }),
 	}).then(({ canceled }) => {
 		if (canceled) return;
 		avatarDecorations.value = avatarDecorations.value.filter(x => x !== avatarDecoration);
-		os.api('admin/avatar-decorations/delete', avatarDecoration);
+		misskeyApi('admin/avatar-decorations/delete', avatarDecoration);
 	});
 }
 
@@ -77,7 +78,7 @@ async function save(avatarDecoration) {
 }
 
 function load() {
-	os.api('admin/avatar-decorations/list').then(_avatarDecorations => {
+	misskeyApi('admin/avatar-decorations/list').then(_avatarDecorations => {
 		avatarDecorations.value = _avatarDecorations;
 	});
 }
@@ -93,8 +94,8 @@ const headerActions = computed(() => [{
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.avatarDecorations,
 	icon: 'ph-sparkle ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/channel-editor.vue b/packages/frontend/src/pages/channel-editor.vue
index 912d02c7fc..ea4374dd3c 100644
--- a/packages/frontend/src/pages/channel-editor.vue
+++ b/packages/frontend/src/pages/channel-editor.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -76,12 +76,13 @@ import MkInput from '@/components/MkInput.vue';
 import MkColorInput from '@/components/MkColorInput.vue';
 import { selectFile } from '@/scripts/select-file.js';
 import * as os from '@/os.js';
-import { useRouter } from '@/router.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { i18n } from '@/i18n.js';
 import MkFolder from '@/components/MkFolder.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
+import { useRouter } from '@/router/supplier.js';
 
 const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
 
@@ -105,7 +106,7 @@ watch(() => bannerId.value, async () => {
 	if (bannerId.value == null) {
 		bannerUrl.value = null;
 	} else {
-		bannerUrl.value = (await os.api('drive/files/show', {
+		bannerUrl.value = (await misskeyApi('drive/files/show', {
 			fileId: bannerId.value,
 		})).url;
 	}
@@ -114,7 +115,7 @@ watch(() => bannerId.value, async () => {
 async function fetchChannel() {
 	if (props.channelId == null) return;
 
-	channel.value = await os.api('channels/show', {
+	channel.value = await misskeyApi('channels/show', {
 		channelId: props.channelId,
 	});
 
@@ -173,13 +174,13 @@ function save() {
 async function archive() {
 	const { canceled } = await os.confirm({
 		type: 'warning',
-		title: i18n.t('channelArchiveConfirmTitle', { name: name.value }),
+		title: i18n.tsx.channelArchiveConfirmTitle({ name: name.value }),
 		text: i18n.ts.channelArchiveConfirmDescription,
 	});
 
 	if (canceled) return;
 
-	os.api('channels/update', {
+	misskeyApi('channels/update', {
 		channelId: props.channelId,
 		isArchived: true,
 	}).then(() => {
@@ -201,11 +202,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => props.channelId ? {
-	title: i18n.ts._channel.edit,
-	icon: 'ph-television ph-bold ph-lg',
-} : {
-	title: i18n.ts._channel.create,
+definePageMetadata(() => ({
+	title: props.channelId ? i18n.ts._channel.edit : i18n.ts._channel.create,
 	icon: 'ph-television ph-bold ph-lg',
 }));
 </script>
diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue
index b0873ea336..881acd0197 100644
--- a/packages/frontend/src/pages/channel.vue
+++ b/packages/frontend/src/pages/channel.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -7,59 +7,61 @@ SPDX-License-Identifier: AGPL-3.0-only
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
 	<MkSpacer :contentMax="700" :class="$style.main">
-		<div v-if="channel && tab === 'overview'" class="_gaps">
-			<div class="_panel" :class="$style.bannerContainer">
-				<XChannelFollowButton :channel="channel" :full="true" :class="$style.subscribe"/>
-				<MkButton v-if="favorited" v-tooltip="i18n.ts.unfavorite" asLike class="button" rounded primary :class="$style.favorite" @click="unfavorite()"><i class="ph-star ph-bold ph-lg"></i></MkButton>
-				<MkButton v-else v-tooltip="i18n.ts.favorite" asLike class="button" rounded :class="$style.favorite" @click="favorite()"><i class="ph-star ph-bold ph-lg"></i></MkButton>
-				<div :style="{ backgroundImage: channel.bannerUrl ? `url(${channel.bannerUrl})` : undefined }" :class="$style.banner">
-					<div :class="$style.bannerStatus">
-						<div><i class="ph-users ph-bold ph-lg ti-fw"></i><I18n :src="i18n.ts._channel.usersCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.usersCount }}</b></template></I18n></div>
-						<div><i class="ph-pencil ph-bold ph-lg ti-fw"></i><I18n :src="i18n.ts._channel.notesCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.notesCount }}</b></template></I18n></div>
+		<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
+			<div v-if="channel && tab === 'overview'" key="overview" class="_gaps">
+				<div class="_panel" :class="$style.bannerContainer">
+					<XChannelFollowButton :channel="channel" :full="true" :class="$style.subscribe"/>
+					<MkButton v-if="favorited" v-tooltip="i18n.ts.unfavorite" asLike class="button" rounded primary :class="$style.favorite" @click="unfavorite()"><i class="ph-star ph-bold ph-lg"></i></MkButton>
+					<MkButton v-else v-tooltip="i18n.ts.favorite" asLike class="button" rounded :class="$style.favorite" @click="favorite()"><i class="ph-star ph-bold ph-lg"></i></MkButton>
+					<div :style="{ backgroundImage: channel.bannerUrl ? `url(${channel.bannerUrl})` : undefined }" :class="$style.banner">
+						<div :class="$style.bannerStatus">
+							<div><i class="ph-users ph-bold ph-lg"></i><I18n :src="i18n.ts._channel.usersCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.usersCount }}</b></template></I18n></div>
+							<div><i class="ph-pencil-simple ph-bold ph-lg"></i><I18n :src="i18n.ts._channel.notesCount" tag="span" style="margin-left: 4px;"><template #n><b>{{ channel.notesCount }}</b></template></I18n></div>
+						</div>
+						<div v-if="channel.isSensitive" :class="$style.sensitiveIndicator">{{ i18n.ts.sensitive }}</div>
+						<div :class="$style.bannerFade"></div>
+					</div>
+					<div v-if="channel.description" :class="$style.description">
+						<Mfm :text="channel.description" :isNote="false"/>
 					</div>
-					<div v-if="channel.isSensitive" :class="$style.sensitiveIndicator">{{ i18n.ts.sensitive }}</div>
-					<div :class="$style.bannerFade"></div>
 				</div>
-				<div v-if="channel.description" :class="$style.description">
-					<Mfm :text="channel.description" :isNote="false"/>
+
+				<MkFoldableSection>
+					<template #header><i class="ph-push-pin ph-bold ph-lg" style="margin-right: 0.5em;"></i>{{ i18n.ts.pinnedNotes }}</template>
+					<div v-if="channel.pinnedNotes && channel.pinnedNotes.length > 0" class="_gaps">
+						<MkNote v-for="note in channel.pinnedNotes" :key="note.id" class="_panel" :note="note"/>
+					</div>
+				</MkFoldableSection>
+			</div>
+			<div v-if="channel && tab === 'timeline'" key="timeline" class="_gaps">
+				<MkInfo v-if="channel.isArchived" warn>{{ i18n.ts.thisChannelArchived }}</MkInfo>
+
+				<!-- スマホ・タブレットの場合、キーボードが表示されると投稿が見づらくなるので、デスクトップ場合のみ自動でフォーカスを当てる -->
+				<MkPostForm v-if="$i && defaultStore.reactiveState.showFixedPostFormInChannel.value" :channel="channel" class="post-form _panel" fixed :autofocus="deviceKind === 'desktop'"/>
+
+				<MkTimeline :key="channelId" src="channel" :channel="channelId" @before="before" @after="after" @note="miLocalStorage.setItemAsJson(`channelLastReadedAt:${channel.id}`, Date.now())"/>
+			</div>
+			<div v-else-if="tab === 'featured'" key="featured">
+				<MkNotes :pagination="featuredPagination"/>
+			</div>
+			<div v-else-if="tab === 'search'" key="search">
+				<div class="_gaps">
+					<div>
+						<MkInput v-model="searchQuery" @enter="search()">
+							<template #prefix><i class="ph-magnifying-glass ph-bold ph-lg"></i></template>
+						</MkInput>
+						<MkButton primary rounded style="margin-top: 8px;" @click="search()">{{ i18n.ts.search }}</MkButton>
+					</div>
+					<MkNotes v-if="searchPagination" :key="searchKey" :pagination="searchPagination"/>
 				</div>
 			</div>
-
-			<MkFoldableSection>
-				<template #header><i class="ph-push-pin ph-bold ph-lg ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.pinnedNotes }}</template>
-				<div v-if="channel.pinnedNotes && channel.pinnedNotes.length > 0" class="_gaps">
-					<MkNote v-for="note in channel.pinnedNotes" :key="note.id" class="_panel" :note="note"/>
-				</div>
-			</MkFoldableSection>
-		</div>
-		<div v-if="channel && tab === 'timeline'" class="_gaps">
-			<MkInfo v-if="channel.isArchived" warn>{{ i18n.ts.thisChannelArchived }}</MkInfo>
-
-			<!-- スマホ・タブレットの場合、キーボードが表示されると投稿が見づらくなるので、デスクトップ場合のみ自動でフォーカスを当てる -->
-			<MkPostForm v-if="$i && defaultStore.reactiveState.showFixedPostFormInChannel.value" :channel="channel" class="post-form _panel" fixed :autofocus="deviceKind === 'desktop'"/>
-
-			<MkTimeline :key="channelId" src="channel" :channel="channelId" @before="before" @after="after" @note="miLocalStorage.setItemAsJson(`channelLastReadedAt:${channel.id}`, Date.now())"/>
-		</div>
-		<div v-else-if="tab === 'featured'">
-			<MkNotes :pagination="featuredPagination"/>
-		</div>
-		<div v-else-if="tab === 'search'">
-			<div class="_gaps">
-				<div>
-					<MkInput v-model="searchQuery" @enter="search()">
-						<template #prefix><i class="ph-magnifying-glass ph-bold ph-lg"></i></template>
-					</MkInput>
-					<MkButton primary rounded style="margin-top: 8px;" @click="search()">{{ i18n.ts.search }}</MkButton>
-				</div>
-				<MkNotes v-if="searchPagination" :key="searchKey" :pagination="searchPagination"/>
-			</div>
-		</div>
+		</MkHorizontalSwipe>
 	</MkSpacer>
 	<template #footer>
 		<div :class="$style.footer">
 			<MkSpacer :contentMax="700" :marginMin="16" :marginMax="16">
 				<div class="_buttonsCenter">
-					<MkButton inline rounded primary gradate @click="openPostForm()"><i class="ph-pencil ph-bold ph-lg"></i> {{ i18n.ts.postToTheChannel }}</MkButton>
+					<MkButton inline rounded primary gradate @click="openPostForm()"><i class="ph-pencil-simple ph-bold ph-lg"></i> {{ i18n.ts.postToTheChannel }}</MkButton>
 				</div>
 			</MkSpacer>
 		</div>
@@ -74,7 +76,7 @@ import MkPostForm from '@/components/MkPostForm.vue';
 import MkTimeline from '@/components/MkTimeline.vue';
 import XChannelFollowButton from '@/components/MkChannelFollowButton.vue';
 import * as os from '@/os.js';
-import { useRouter } from '@/router.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { $i, iAmModerator } from '@/account.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
@@ -87,10 +89,12 @@ import { defaultStore } from '@/store.js';
 import MkNote from '@/components/MkNote.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import MkFoldableSection from '@/components/MkFoldableSection.vue';
+import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
 import { PageHeaderItem } from '@/types/page-header.js';
 import { isSupportShare } from '@/scripts/navigator.js';
 import copyToClipboard from '@/scripts/copy-to-clipboard.js';
 import { miLocalStorage } from '@/local-storage.js';
+import { useRouter } from '@/router/supplier.js';
 
 const router = useRouter();
 
@@ -99,6 +103,7 @@ const props = defineProps<{
 }>();
 
 const tab = ref('overview');
+
 const channel = ref<Misskey.entities.Channel | null>(null);
 const favorited = ref(false);
 const searchQuery = ref('');
@@ -113,7 +118,7 @@ const featuredPagination = computed(() => ({
 }));
 
 watch(() => props.channelId, async () => {
-	channel.value = await os.api('channels/show', {
+	channel.value = await misskeyApi('channels/show', {
 		channelId: props.channelId,
 	});
 	favorited.value = channel.value.isFavorited ?? false;
@@ -253,10 +258,10 @@ const headerTabs = computed(() => [{
 	icon: 'ph-magnifying-glass ph-bold ph-lg',
 }]);
 
-definePageMetadata(computed(() => channel.value ? {
-	title: channel.value.name,
+definePageMetadata(() => ({
+	title: channel.value ? channel.value.name : i18n.ts.channel,
 	icon: 'ph-television ph-bold ph-lg',
-} : null));
+}));
 </script>
 
 <style lang="scss" module>
@@ -267,6 +272,7 @@ definePageMetadata(computed(() => channel.value ? {
 .footer {
 	-webkit-backdrop-filter: var(--blur, blur(15px));
 	backdrop-filter: var(--blur, blur(15px));
+	background: var(--acrylicBg);
 	border-top: solid 0.5px var(--divider);
 }
 
diff --git a/packages/frontend/src/pages/channels.vue b/packages/frontend/src/pages/channels.vue
index 63d1e454a2..253b272d2a 100644
--- a/packages/frontend/src/pages/channels.vue
+++ b/packages/frontend/src/pages/channels.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -7,44 +7,46 @@ SPDX-License-Identifier: AGPL-3.0-only
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
 	<MkSpacer :contentMax="700">
-		<div v-if="tab === 'search'">
-			<div class="_gaps">
-				<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" @enter="search">
-					<template #prefix><i class="ph-magnifying-glass ph-bold ph-lg"></i></template>
-				</MkInput>
-				<MkRadios v-model="searchType" @update:modelValue="search()">
-					<option value="nameAndDescription">{{ i18n.ts._channel.nameAndDescription }}</option>
-					<option value="nameOnly">{{ i18n.ts._channel.nameOnly }}</option>
-				</MkRadios>
-				<MkButton large primary gradate rounded @click="search">{{ i18n.ts.search }}</MkButton>
-			</div>
+		<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
+			<div v-if="tab === 'search'" key="search">
+				<div class="_gaps">
+					<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" @enter="search">
+						<template #prefix><i class="ph-magnifying-glass ph-bold ph-lg"></i></template>
+					</MkInput>
+					<MkRadios v-model="searchType" @update:modelValue="search()">
+						<option value="nameAndDescription">{{ i18n.ts._channel.nameAndDescription }}</option>
+						<option value="nameOnly">{{ i18n.ts._channel.nameOnly }}</option>
+					</MkRadios>
+					<MkButton large primary gradate rounded @click="search">{{ i18n.ts.search }}</MkButton>
+				</div>
 
-			<MkFoldableSection v-if="channelPagination">
-				<template #header>{{ i18n.ts.searchResult }}</template>
-				<MkChannelList :key="key" :pagination="channelPagination"/>
-			</MkFoldableSection>
-		</div>
-		<div v-if="tab === 'featured'">
-			<MkPagination v-slot="{items}" :pagination="featuredPagination">
-				<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
-			</MkPagination>
-		</div>
-		<div v-else-if="tab === 'favorites'">
-			<MkPagination v-slot="{items}" :pagination="favoritesPagination">
-				<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
-			</MkPagination>
-		</div>
-		<div v-else-if="tab === 'following'">
-			<MkPagination v-slot="{items}" :pagination="followingPagination">
-				<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
-			</MkPagination>
-		</div>
-		<div v-else-if="tab === 'owned'">
-			<MkButton class="new" @click="create()"><i class="ph-plus ph-bold ph-lg"></i></MkButton>
-			<MkPagination v-slot="{items}" :pagination="ownedPagination">
-				<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
-			</MkPagination>
-		</div>
+				<MkFoldableSection v-if="channelPagination">
+					<template #header>{{ i18n.ts.searchResult }}</template>
+					<MkChannelList :key="key" :pagination="channelPagination"/>
+				</MkFoldableSection>
+			</div>
+			<div v-if="tab === 'featured'" key="featured">
+				<MkPagination v-slot="{items}" :pagination="featuredPagination">
+					<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
+				</MkPagination>
+			</div>
+			<div v-else-if="tab === 'favorites'" key="favorites">
+				<MkPagination v-slot="{items}" :pagination="favoritesPagination">
+					<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
+				</MkPagination>
+			</div>
+			<div v-else-if="tab === 'following'" key="following">
+				<MkPagination v-slot="{items}" :pagination="followingPagination">
+					<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
+				</MkPagination>
+			</div>
+			<div v-else-if="tab === 'owned'" key="owned">
+				<MkButton class="new" @click="create()"><i class="ph-plus ph-bold ph-lg"></i></MkButton>
+				<MkPagination v-slot="{items}" :pagination="ownedPagination">
+					<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
+				</MkPagination>
+			</div>
+		</MkHorizontalSwipe>
 	</MkSpacer>
 </MkStickyContainer>
 </template>
@@ -58,9 +60,10 @@ import MkInput from '@/components/MkInput.vue';
 import MkRadios from '@/components/MkRadios.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkFoldableSection from '@/components/MkFoldableSection.vue';
-import { useRouter } from '@/router.js';
+import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { i18n } from '@/i18n.js';
+import { useRouter } from '@/router/supplier.js';
 
 const router = useRouter();
 
@@ -146,11 +149,11 @@ const headerTabs = computed(() => [{
 }, {
 	key: 'owned',
 	title: i18n.ts._channel.owned,
-	icon: 'ph-pencil-line ph-bold ph-lg',
+	icon: 'ph-pencil-simple-line ph-bold ph-lg',
 }]);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts.channel,
 	icon: 'ph-television ph-bold ph-lg',
-})));
+}));
 </script>
diff --git a/packages/frontend/src/pages/clicker.vue b/packages/frontend/src/pages/clicker.vue
index 8c1322d732..679fb67d25 100644
--- a/packages/frontend/src/pages/clicker.vue
+++ b/packages/frontend/src/pages/clicker.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -16,10 +16,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 import MkClickerGame from '@/components/MkClickerGame.vue';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: '🍪👈',
 	icon: 'ph-cookie ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/clip.vue b/packages/frontend/src/pages/clip.vue
index 9b5f0224cc..5c646889fd 100644
--- a/packages/frontend/src/pages/clip.vue
+++ b/packages/frontend/src/pages/clip.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -32,6 +32,7 @@ import MkNotes from '@/components/MkNotes.vue';
 import { $i } from '@/account.js';
 import { i18n } from '@/i18n.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { url } from '@/config.js';
 import MkButton from '@/components/MkButton.vue';
@@ -56,7 +57,7 @@ const pagination = {
 const isOwned = computed<boolean | null>(() => $i && clip.value && ($i.id === clip.value.userId));
 
 watch(() => props.clipId, async () => {
-	clip.value = await os.api('clips/show', {
+	clip.value = await misskeyApi('clips/show', {
 		clipId: props.clipId,
 	});
 	favorited.value = clip.value.isFavorited;
@@ -88,7 +89,7 @@ async function unfavorite() {
 }
 
 const headerActions = computed(() => clip.value && isOwned.value ? [{
-	icon: 'ph-pencil ph-bold ph-lg',
+	icon: 'ph-pencil-simple ph-bold ph-lg',
 	text: i18n.ts.edit,
 	handler: async (): Promise<void> => {
 		const { canceled, result } = await os.form(clip.value.name, {
@@ -144,7 +145,7 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{
 	handler: async (): Promise<void> => {
 		const { canceled } = await os.confirm({
 			type: 'warning',
-			text: i18n.t('deleteAreYouSure', { x: clip.value.name }),
+			text: i18n.tsx.deleteAreYouSure({ x: clip.value.name }),
 		});
 		if (canceled) return;
 
@@ -156,10 +157,10 @@ const headerActions = computed(() => clip.value && isOwned.value ? [{
 	},
 }] : null);
 
-definePageMetadata(computed(() => clip.value ? {
-	title: clip.value.name,
+definePageMetadata(() => ({
+	title: clip.value ? clip.value.name : i18n.ts.clip,
 	icon: 'ph-paperclip ph-bold ph-lg',
-} : null));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/custom-emojis-manager.vue b/packages/frontend/src/pages/custom-emojis-manager.vue
index bc2a268f34..1a745d6626 100644
--- a/packages/frontend/src/pages/custom-emojis-manager.vue
+++ b/packages/frontend/src/pages/custom-emojis-manager.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -82,6 +82,7 @@ import MkSwitch from '@/components/MkSwitch.vue';
 import FormSplit from '@/components/form/split.vue';
 import { selectFile } from '@/scripts/select-file.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
@@ -187,7 +188,7 @@ const menu = (ev: MouseEvent) => {
 		icon: 'ph-download ph-bold ph-lg',
 		text: i18n.ts.export,
 		action: async () => {
-			os.api('export-custom-emojis', {
+			misskeyApi('export-custom-emojis', {
 			})
 				.then(() => {
 					os.alert({
@@ -206,7 +207,7 @@ const menu = (ev: MouseEvent) => {
 		text: i18n.ts.import,
 		action: async () => {
 			const file = await selectFile(ev.currentTarget ?? ev.target);
-			os.api('admin/emoji/import-zip', {
+			misskeyApi('admin/emoji/import-zip', {
 				fileId: file.id,
 			})
 				.then(() => {
@@ -314,10 +315,10 @@ const headerTabs = computed(() => [{
 	title: i18n.ts.remote,
 }]);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts.customEmojis,
 	icon: 'ph-smiley ph-bold ph-lg',
-})));
+}));
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/pages/drive.file.info.vue b/packages/frontend/src/pages/drive.file.info.vue
index 69309b37f3..872dd4d5cf 100644
--- a/packages/frontend/src/pages/drive.file.info.vue
+++ b/packages/frontend/src/pages/drive.file.info.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -14,11 +14,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<div :class="$style.fileQuickActionsRoot">
 			<button class="_button" :class="$style.fileNameEditBtn" @click="rename()">
 				<h2 class="_nowrap" :class="$style.fileName">{{ file.name }}</h2>
-				<i class="ph-pencil ph-bold ph-lg" :class="$style.fileNameEditIcon"></i>
+				<i class="ph-pencil-simple ph-bold ph-lg" :class="$style.fileNameEditIcon"></i>
 			</button>
 			<div :class="$style.fileQuickActionsOthers">
 				<button v-tooltip="i18n.ts.createNoteFromTheFile" class="_button" :class="$style.fileQuickActionsOthersButton" @click="postThis()">
-					<i class="ph-pencil ph-bold ph-lg"></i>
+					<i class="ph-pencil-simple ph-bold ph-lg"></i>
 				</button>
 				<button v-if="isImage" v-tooltip="i18n.ts.cropImage" class="_button" :class="$style.fileQuickActionsOthersButton" @click="crop()">
 					<i class="ph-crop ph-bold ph-lg"></i>
@@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<button class="_button" :class="$style.fileAltEditBtn" @click="describe()">
 				<MkKeyValue>
 					<template #key>{{ i18n.ts.description }}</template>
-					<template #value>{{ file.comment ? file.comment : `(${i18n.ts.none})` }}<i class="ph-pencil ph-bold ph-lg" :class="$style.fileAltEditIcon"></i></template>
+					<template #value>{{ file.comment ? file.comment : `(${i18n.ts.none})` }}<i class="ph-pencil-simple ph-bold ph-lg" :class="$style.fileAltEditIcon"></i></template>
 				</MkKeyValue>
 			</button>
 			<MkKeyValue :class="$style.fileMetaDataChildren">
@@ -79,7 +79,8 @@ import bytes from '@/filters/bytes.js';
 import { infoImageUrl } from '@/instance.js';
 import { i18n } from '@/i18n.js';
 import * as os from '@/os.js';
-import { useRouter } from '@/router.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
+import { useRouter } from '@/router/supplier.js';
 
 const router = useRouter();
 
@@ -94,7 +95,7 @@ const isImage = computed(() => file.value?.type.startsWith('image/'));
 async function fetch() {
 	fetching.value = true;
 
-	file.value = await os.api('drive/files/show', {
+	file.value = await misskeyApi('drive/files/show', {
 		fileId: props.fileId,
 	}).catch((err) => {
 		console.error(err);
@@ -179,7 +180,7 @@ async function deleteFile() {
 
 	const { canceled } = await os.confirm({
 		type: 'warning',
-		text: i18n.t('driveFileDeleteConfirm', { name: file.value.name }),
+		text: i18n.tsx.driveFileDeleteConfirm({ name: file.value.name }),
 	});
 
 	if (canceled) return;
diff --git a/packages/frontend/src/pages/drive.file.notes.vue b/packages/frontend/src/pages/drive.file.notes.vue
index ee1a0ee9b0..ca63d43747 100644
--- a/packages/frontend/src/pages/drive.file.notes.vue
+++ b/packages/frontend/src/pages/drive.file.notes.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/pages/drive.file.vue b/packages/frontend/src/pages/drive.file.vue
index b1bb84b488..7cb2976e76 100644
--- a/packages/frontend/src/pages/drive.file.vue
+++ b/packages/frontend/src/pages/drive.file.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -9,13 +9,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/>
 	</template>
 
-	<MkSpacer v-if="tab === 'info'" :contentMax="800">
-		<XFileInfo :fileId="fileId"/>
-	</MkSpacer>
+	<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
+		<MkSpacer v-if="tab === 'info'" key="info" :contentMax="800">
+			<XFileInfo :fileId="fileId"/>
+		</MkSpacer>
 
-	<MkSpacer v-else-if="tab === 'notes'" :contentMax="800">
-		<XNotes :fileId="fileId"/>
-	</MkSpacer>
+		<MkSpacer v-else-if="tab === 'notes'" key="notes" :contentMax="800">
+			<XNotes :fileId="fileId"/>
+		</MkSpacer>
+	</MkHorizontalSwipe>
 </MkStickyContainer>
 </template>
 
@@ -23,6 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed, ref, defineAsyncComponent } from 'vue';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
+import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
 
 const props = defineProps<{
 	fileId: string;
@@ -42,11 +45,11 @@ const headerTabs = computed(() => [{
 }, {
 	key: 'notes',
 	title: i18n.ts._fileViewer.attachedNotes,
-	icon: 'ph-pencil ph-bold ph-lg',
+	icon: 'ph-pencil-simple ph-bold ph-lg',
 }]);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts._fileViewer.title,
 	icon: 'ph-file-text ph-bold ph-lg',
-})));
+}));
 </script>
diff --git a/packages/frontend/src/pages/drive.vue b/packages/frontend/src/pages/drive.vue
index f3a3af677f..7403986061 100644
--- a/packages/frontend/src/pages/drive.vue
+++ b/packages/frontend/src/pages/drive.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -22,9 +22,9 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: folder.value ? folder.value.name : i18n.ts.drive,
 	icon: 'ph-cloud ph-bold ph-lg',
 	hideHeader: true,
-})));
+}));
 </script>
diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue
new file mode 100644
index 0000000000..f1fa580c36
--- /dev/null
+++ b/packages/frontend/src/pages/drop-and-fusion.game.vue
@@ -0,0 +1,1499 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkSpacer :contentMax="800">
+	<div :class="$style.root">
+		<div v-if="!gameLoaded" :class="$style.loadingScreen">
+			<div>{{ i18n.ts.loading }}<MkEllipsis/></div>
+		</div>
+		<!-- ↓に対してTransitionコンポーネントを使うと何故かkeyを指定していてもキャッシュが効かず様々なコンポーネントが都度再評価されてパフォーマンスが低下する -->
+		<div v-show="gameLoaded" class="_gaps_s">
+			<div v-if="readyGo === 'ready'" :class="$style.readyGo_bg">
+			</div>
+			<Transition
+				:enterActiveClass="$style.transition_zoom_enterActive"
+				:leaveActiveClass="$style.transition_zoom_leaveActive"
+				:enterFromClass="$style.transition_zoom_enterFrom"
+				:leaveToClass="$style.transition_zoom_leaveTo"
+				:moveClass="$style.transition_zoom_move"
+				mode="default"
+			>
+				<div v-if="readyGo === 'ready'" :class="$style.readyGo_ready">
+					<img src="/client-assets/drop-and-fusion/ready.png" :class="$style.readyGo_img"/>
+				</div>
+				<div v-else-if="readyGo === 'go'" :class="$style.readyGo_go">
+					<img src="/client-assets/drop-and-fusion/go.png" :class="$style.readyGo_img"/>
+				</div>
+			</Transition>
+
+			<div :class="$style.header">
+				<div class="_woodenFrame" :class="[$style.headerTitle]">
+					<div class="_woodenFrameInner">
+						<b>{{ i18n.ts.bubbleGame }}</b>
+						<div>- {{ gameMode.toUpperCase() }} -</div>
+					</div>
+				</div>
+				<div class="_woodenFrame _woodenFrameH">
+					<div class="_woodenFrameInner">
+						<MkButton inline small @click="hold">{{ i18n.ts._bubbleGame.hold }}</MkButton>
+						<img v-if="holdingStock" :src="getTextureImageUrl(holdingStock.mono)" style="width: 32px; margin-left: 8px; vertical-align: bottom;"/>
+					</div>
+					<div class="_woodenFrameInner" :class="$style.stock" style="text-align: center;">
+						<TransitionGroup
+							:enterActiveClass="$style.transition_stock_enterActive"
+							:leaveActiveClass="$style.transition_stock_leaveActive"
+							:enterFromClass="$style.transition_stock_enterFrom"
+							:leaveToClass="$style.transition_stock_leaveTo"
+							:moveClass="$style.transition_stock_move"
+						>
+							<img v-for="x in stock" :key="x.id" :src="getTextureImageUrl(x.mono)" style="width: 32px; vertical-align: bottom;"/>
+						</TransitionGroup>
+					</div>
+				</div>
+			</div>
+
+			<div ref="containerEl" :class="[$style.gameContainer, { [$style.gameOver]: isGameOver && !replaying }]" @contextmenu.stop.prevent @click.stop.prevent="onClick" @touchmove.stop.prevent="onTouchmove" @touchend="onTouchend" @mousemove="onMousemove">
+				<img v-if="defaultStore.state.darkMode" src="/client-assets/drop-and-fusion/frame-dark.svg" :class="$style.mainFrameImg"/>
+				<img v-else src="/client-assets/drop-and-fusion/frame-light.svg" :class="$style.mainFrameImg"/>
+				<canvas ref="canvasEl" :class="$style.canvas"/>
+				<Transition
+					:enterActiveClass="$style.transition_combo_enterActive"
+					:leaveActiveClass="$style.transition_combo_leaveActive"
+					:enterFromClass="$style.transition_combo_enterFrom"
+					:leaveToClass="$style.transition_combo_leaveTo"
+					:moveClass="$style.transition_combo_move"
+				>
+					<div v-show="combo > 1" :class="$style.combo" :style="{ fontSize: `${100 + ((comboPrev - 2) * 15)}%` }">{{ comboPrev }} Chain!</div>
+				</Transition>
+				<div v-if="!isGameOver && !replaying && readyGo !== 'ready'" :class="$style.dropperContainer" :style="{ left: dropperX + 'px' }">
+					<!--<img v-if="currentPick" src="/client-assets/drop-and-fusion/dropper.png" :class="$style.dropper" :style="{ left: dropperX + 'px' }"/>-->
+					<Transition
+						:enterActiveClass="$style.transition_picked_enterActive"
+						:leaveActiveClass="$style.transition_picked_leaveActive"
+						:enterFromClass="$style.transition_picked_enterFrom"
+						:leaveToClass="$style.transition_picked_leaveTo"
+						:moveClass="$style.transition_picked_move"
+						mode="out-in"
+					>
+						<img v-if="currentPick" :key="currentPick.id" :src="getTextureImageUrl(currentPick.mono)" :class="$style.currentMono" :style="{ marginBottom: -((currentPick?.mono.sizeY * viewScale) / 2) + 'px', left: -((currentPick?.mono.sizeX * viewScale) / 2) + 'px', width: `${currentPick?.mono.sizeX * viewScale}px` }"/>
+					</Transition>
+					<template v-if="dropReady && currentPick">
+						<img src="/client-assets/drop-and-fusion/drop-arrow.svg" :class="$style.currentMonoArrow"/>
+						<div :class="$style.dropGuide"/>
+					</template>
+				</div>
+				<div v-if="isGameOver && !replaying" :class="$style.gameOverLabel">
+					<div class="_gaps_s">
+						<img src="/client-assets/drop-and-fusion/gameover.png" style="width: 200px; max-width: 100%; display: block; margin: auto; margin-bottom: -5px;"/>
+						<div>{{ i18n.ts._bubbleGame._score.score }}: <MkNumber :value="score"/>{{ getScoreUnit(gameMode) }}</div>
+						<div>{{ i18n.ts._bubbleGame._score.maxChain }}: <MkNumber :value="maxCombo"/></div>
+						<div v-if="gameMode === 'yen'">
+							{{ i18n.ts._bubbleGame._score.scoreYen }}:
+							<I18n :src="i18n.ts._bubbleGame._score.yen" tag="b">
+								<template #yen><MkNumber :value="yenTotal ?? score"/></template>
+							</I18n>
+						</div>
+						<I18n v-if="gameMode === 'sweets'" :src="i18n.ts._bubbleGame._score.scoreSweets" tag="div">
+							<template #onigiriQtyWithUnit>
+								<I18n :src="i18n.ts._bubbleGame._score.estimatedQty" tag="b">
+									<template #qty><MkNumber :value="score / 130"/></template>
+								</I18n>
+							</template>
+						</I18n>
+					</div>
+				</div>
+				<div v-if="replaying" :class="$style.replayIndicator"><span :class="$style.replayIndicatorText"><i class="ph-play ph-bold ph-lg"></i> {{ i18n.ts.replaying }}</span></div>
+			</div>
+
+			<div v-if="replaying" class="_woodenFrame">
+				<div class="_woodenFrameInner">
+					<div style="background: #0004;">
+						<div style="height: 10px; background: var(--accent); will-change: width;" :style="{ width: `${(currentFrame / endedAtFrame) * 100}%` }"></div>
+					</div>
+				</div>
+				<div class="_woodenFrameInner">
+					<div class="_buttonsCenter">
+						<MkButton @click="endReplay"><i class="ph-stop ph-bold ph-lg"></i> {{ i18n.ts.endReplay }}</MkButton>
+						<MkButton :primary="replayPlaybackRate === 4" @click="replayPlaybackRate = replayPlaybackRate === 4 ? 1 : 4"><i class="ph-skip-forward ph-bold ph-lg"></i> x4</MkButton>
+						<MkButton :primary="replayPlaybackRate === 16" @click="replayPlaybackRate = replayPlaybackRate === 16 ? 1 : 16"><i class="ph-skip-forward ph-bold ph-lg"></i> x16</MkButton>
+					</div>
+				</div>
+			</div>
+
+			<div v-if="isGameOver" class="_woodenFrame">
+				<div class="_woodenFrameInner">
+					<div class="_buttonsCenter">
+						<MkButton primary rounded @click="backToTitle">{{ i18n.ts.backToTitle }}</MkButton>
+						<MkButton primary rounded @click="replay">{{ i18n.ts.showReplay }}</MkButton>
+						<MkButton primary rounded @click="share">{{ i18n.ts.share }}</MkButton>
+						<MkButton rounded @click="exportLog">{{ i18n.ts.copyReplayData }}</MkButton>
+					</div>
+				</div>
+			</div>
+
+			<div style="display: flex;">
+				<div class="_woodenFrame" style="flex: 1; margin-right: 10px;">
+					<div class="_woodenFrameInner">
+						<div>{{ i18n.ts._bubbleGame._score.score }}: <MkNumber :value="score"/>{{ getScoreUnit(gameMode) }}</div>
+						<div>{{ i18n.ts._bubbleGame._score.highScore }}: <b v-if="highScore"><MkNumber :value="highScore"/>{{ getScoreUnit(gameMode) }}</b><b v-else>-</b></div>
+						<div v-if="gameMode === 'yen'">
+							{{ i18n.ts._bubbleGame._score.scoreYen }}:
+							<I18n :src="i18n.ts._bubbleGame._score.yen" tag="b">
+								<template #yen><MkNumber :value="yenTotal ?? score"/></template>
+							</I18n>
+						</div>
+					</div>
+				</div>
+				<div class="_woodenFrame" style="margin-left: auto;">
+					<div class="_woodenFrameInner" style="text-align: center;">
+						<div @click="showConfig = !showConfig"><i class="ph-gear ph-bold ph-lg"></i></div>
+					</div>
+				</div>
+			</div>
+
+			<div v-if="showConfig" class="_woodenFrame">
+				<div class="_woodenFrameInner">
+					<div class="_gaps">
+						<MkRange v-model="bgmVolume" :min="0" :max="1" :step="0.01" :textConverter="(v) => `${Math.floor(v * 100)}%`" :continuousUpdate="true" @dragEnded="(v) => updateSettings('bgmVolume', v)">
+							<template #label>BGM {{ i18n.ts.volume }}</template>
+						</MkRange>
+						<MkRange v-model="sfxVolume" :min="0" :max="1" :step="0.01" :textConverter="(v) => `${Math.floor(v * 100)}%`" :continuousUpdate="true" @dragEnded="(v) => updateSettings('sfxVolume', v)">
+							<template #label>{{ i18n.ts.sfx }} {{ i18n.ts.volume }}</template>
+						</MkRange>
+					</div>
+				</div>
+			</div>
+
+			<div class="_woodenFrame">
+				<div class="_woodenFrameInner">
+					<div>FUSION RECIPE</div>
+					<div>
+						<div v-for="(mono, i) in game.monoDefinitions.sort((a, b) => a.level - b.level)" :key="mono.id" style="display: inline-block;">
+							<img :src="getTextureImageUrl(mono)" style="width: 32px; vertical-align: bottom;"/>
+							<div v-if="i < game.monoDefinitions.length - 1" style="display: inline-block; margin-left: 4px; vertical-align: bottom;"><i class="ph-arrow-fat-right ph-bold ph-lg"></i></div>
+						</div>
+					</div>
+				</div>
+			</div>
+
+			<div class="_woodenFrame">
+				<div class="_woodenFrameInner">
+					<MkButton v-if="!isGameOver && !replaying" full danger @click="surrender">{{ i18n.ts.surrender }}</MkButton>
+					<MkButton v-else full @click="restart">{{ i18n.ts.gameRetry }}</MkButton>
+				</div>
+			</div>
+		</div>
+	</div>
+</MkSpacer>
+</template>
+
+<script lang="ts" setup>
+import { computed, onDeactivated, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
+import * as Matter from 'matter-js';
+import * as Misskey from 'misskey-js';
+import { DropAndFusionGame, Mono } from 'misskey-bubble-game';
+import { definePageMetadata } from '@/scripts/page-metadata.js';
+import MkRippleEffect from '@/components/MkRippleEffect.vue';
+import * as os from '@/os.js';
+import MkNumber from '@/components/MkNumber.vue';
+import MkPlusOneEffect from '@/components/MkPlusOneEffect.vue';
+import MkButton from '@/components/MkButton.vue';
+import { claimAchievement } from '@/scripts/achievements.js';
+import { defaultStore } from '@/store.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
+import { i18n } from '@/i18n.js';
+import { useInterval } from '@/scripts/use-interval.js';
+import { apiUrl } from '@/config.js';
+import { $i } from '@/account.js';
+import * as sound from '@/scripts/sound.js';
+import MkRange from '@/components/MkRange.vue';
+import copyToClipboard from '@/scripts/copy-to-clipboard.js';
+
+type FrontendMonoDefinition = {
+	id: string;
+	img: string;
+	imgSizeX: number;
+	imgSizeY: number;
+	spriteScale: number;
+	sfxPitch: number;
+};
+
+const NORAML_MONOS: FrontendMonoDefinition[] = [{
+	id: '9377076d-c980-4d83-bdaf-175bc58275b7',
+	sfxPitch: 0.25,
+	img: '/client-assets/drop-and-fusion/normal_monos/exploding_head.png',
+	imgSizeX: 256,
+	imgSizeY: 256,
+	spriteScale: 1.12,
+}, {
+	id: 'be9f38d2-b267-4b1a-b420-904e22e80568',
+	sfxPitch: 0.5,
+	img: '/client-assets/drop-and-fusion/normal_monos/face_with_symbols_on_mouth.png',
+	imgSizeX: 256,
+	imgSizeY: 256,
+	spriteScale: 1.12,
+}, {
+	id: 'beb30459-b064-4888-926b-f572e4e72e0c',
+	sfxPitch: 0.75,
+	img: '/client-assets/drop-and-fusion/normal_monos/cold_face.png',
+	imgSizeX: 256,
+	imgSizeY: 256,
+	spriteScale: 1.12,
+}, {
+	id: 'feab6426-d9d8-49ae-849c-048cdbb6cdf0',
+	sfxPitch: 1,
+	img: '/client-assets/drop-and-fusion/normal_monos/zany_face.png',
+	imgSizeX: 256,
+	imgSizeY: 256,
+	spriteScale: 1.12,
+}, {
+	id: 'd6d8fed6-6d18-4726-81a1-6cf2c974df8a',
+	sfxPitch: 1.5,
+	img: '/client-assets/drop-and-fusion/normal_monos/pleading_face.png',
+	imgSizeX: 256,
+	imgSizeY: 256,
+	spriteScale: 1.12,
+}, {
+	id: '249c728e-230f-4332-bbbf-281c271c75b2',
+	sfxPitch: 2,
+	img: '/client-assets/drop-and-fusion/normal_monos/face_with_open_mouth.png',
+	imgSizeX: 256,
+	imgSizeY: 256,
+	spriteScale: 1.12,
+}, {
+	id: '23d67613-d484-4a93-b71e-3e81b19d6186',
+	sfxPitch: 2.5,
+	img: '/client-assets/drop-and-fusion/normal_monos/smiling_face_with_sunglasses.png',
+	imgSizeX: 256,
+	imgSizeY: 256,
+	spriteScale: 1.12,
+}, {
+	id: '3cbd0add-ad7d-4685-bad0-29f6dddc0b99',
+	sfxPitch: 3,
+	img: '/client-assets/drop-and-fusion/normal_monos/grinning_squinting_face.png',
+	imgSizeX: 256,
+	imgSizeY: 256,
+	spriteScale: 1.12,
+}, {
+	id: '8f86d4f4-ee02-41bf-ad38-1ce0ae457fb5',
+	sfxPitch: 3.5,
+	img: '/client-assets/drop-and-fusion/normal_monos/smiling_face_with_hearts.png',
+	imgSizeX: 256,
+	imgSizeY: 256,
+	spriteScale: 1.12,
+}, {
+	id: '64ec4add-ce39-42b4-96cb-33908f3f118d',
+	sfxPitch: 4,
+	img: '/client-assets/drop-and-fusion/normal_monos/heart_suit.png',
+	imgSizeX: 256,
+	imgSizeY: 256,
+	spriteScale: 1.12,
+}];
+
+const YEN_MONOS: FrontendMonoDefinition[] = [{
+	id: '880f9bd9-802f-4135-a7e1-fd0e0331f726',
+	sfxPitch: 0.25,
+	img: '/client-assets/drop-and-fusion/yen_monos/10000yen.png',
+	imgSizeX: 512,
+	imgSizeY: 256,
+	spriteScale: 0.97,
+}, {
+	id: 'e807beb6-374a-4314-9cc2-aa5f17d96b6b',
+	sfxPitch: 0.5,
+	img: '/client-assets/drop-and-fusion/yen_monos/5000yen.png',
+	imgSizeX: 512,
+	imgSizeY: 256,
+	spriteScale: 0.97,
+}, {
+	id: '033445b7-8f90-4fc9-beca-71a9e87cb530',
+	sfxPitch: 0.75,
+	img: '/client-assets/drop-and-fusion/yen_monos/2000yen.png',
+	imgSizeX: 512,
+	imgSizeY: 256,
+	spriteScale: 0.97,
+}, {
+	id: '410a09ec-5f7f-46f6-b26f-cbca4ccbd091',
+	sfxPitch: 1,
+	img: '/client-assets/drop-and-fusion/yen_monos/1000yen.png',
+	imgSizeX: 512,
+	imgSizeY: 256,
+	spriteScale: 0.97,
+}, {
+	id: '2aae82bc-3fa4-49ad-a6b5-94d888e809f5',
+	sfxPitch: 1.5,
+	img: '/client-assets/drop-and-fusion/yen_monos/500yen.png',
+	imgSizeX: 256,
+	imgSizeY: 256,
+	spriteScale: 0.97,
+}, {
+	id: 'a619bd67-d08f-4cc0-8c7e-c8072a4950cd',
+	sfxPitch: 2,
+	img: '/client-assets/drop-and-fusion/yen_monos/100yen.png',
+	imgSizeX: 256,
+	imgSizeY: 256,
+	spriteScale: 0.97,
+}, {
+	id: 'c1c5d8e4-17d6-4455-befd-12154d731faa',
+	sfxPitch: 2.5,
+	img: '/client-assets/drop-and-fusion/yen_monos/50yen.png',
+	imgSizeX: 256,
+	imgSizeY: 256,
+	spriteScale: 0.97,
+}, {
+	id: '7082648c-e428-44c4-887a-25c07a8ebdd5',
+	sfxPitch: 3,
+	img: '/client-assets/drop-and-fusion/yen_monos/10yen.png',
+	imgSizeX: 256,
+	imgSizeY: 256,
+	spriteScale: 0.97,
+}, {
+	id: '0d8d40d5-e6e0-4d26-8a95-b8d842363379',
+	sfxPitch: 3.5,
+	img: '/client-assets/drop-and-fusion/yen_monos/5yen.png',
+	imgSizeX: 256,
+	imgSizeY: 256,
+	spriteScale: 0.97,
+}, {
+	id: '9dec1b38-d99d-40de-8288-37367b983d0d',
+	sfxPitch: 4,
+	img: '/client-assets/drop-and-fusion/yen_monos/1yen.png',
+	imgSizeX: 256,
+	imgSizeY: 256,
+	spriteScale: 0.97,
+}];
+
+const SQUARE_MONOS: FrontendMonoDefinition[] = [{
+	id: 'f75fd0ba-d3d4-40a4-9712-b470e45b0525',
+	sfxPitch: 0.25,
+	img: '/client-assets/drop-and-fusion/square_monos/keycap_10.png',
+	imgSizeX: 256,
+	imgSizeY: 256,
+	spriteScale: 1.12,
+}, {
+	id: '7b70f4af-1c01-45fd-af72-61b1f01e03d1',
+	sfxPitch: 0.5,
+	img: '/client-assets/drop-and-fusion/square_monos/keycap_9.png',
+	imgSizeX: 256,
+	imgSizeY: 256,
+	spriteScale: 1.12,
+}, {
+	id: '41607ef3-b6d6-4829-95b6-3737bf8bb956',
+	sfxPitch: 0.75,
+	img: '/client-assets/drop-and-fusion/square_monos/keycap_8.png',
+	imgSizeX: 256,
+	imgSizeY: 256,
+	spriteScale: 1.12,
+}, {
+	id: '8a8310d2-0374-460f-bb50-ca9cd3ee3416',
+	sfxPitch: 1,
+	img: '/client-assets/drop-and-fusion/square_monos/keycap_7.png',
+	imgSizeX: 256,
+	imgSizeY: 256,
+	spriteScale: 1.12,
+}, {
+	id: '1092e069-fe1a-450b-be97-b5d477ec398c',
+	sfxPitch: 1.5,
+	img: '/client-assets/drop-and-fusion/square_monos/keycap_6.png',
+	imgSizeX: 256,
+	imgSizeY: 256,
+	spriteScale: 1.12,
+}, {
+	id: '2294734d-7bb8-4781-bb7b-ef3820abf3d0',
+	sfxPitch: 2,
+	img: '/client-assets/drop-and-fusion/square_monos/keycap_5.png',
+	imgSizeX: 256,
+	imgSizeY: 256,
+	spriteScale: 1.12,
+}, {
+	id: 'ea8a61af-e350-45f7-ba6a-366fcd65692a',
+	sfxPitch: 2.5,
+	img: '/client-assets/drop-and-fusion/square_monos/keycap_4.png',
+	imgSizeX: 256,
+	imgSizeY: 256,
+	spriteScale: 1.12,
+}, {
+	id: 'd0c74815-fc1c-4fbe-9953-c92e4b20f919',
+	sfxPitch: 3,
+	img: '/client-assets/drop-and-fusion/square_monos/keycap_3.png',
+	imgSizeX: 256,
+	imgSizeY: 256,
+	spriteScale: 1.12,
+}, {
+	id: 'd8fbd70e-611d-402d-87da-1a7fd8cd2c8d',
+	sfxPitch: 3.5,
+	img: '/client-assets/drop-and-fusion/square_monos/keycap_2.png',
+	imgSizeX: 256,
+	imgSizeY: 256,
+	spriteScale: 1.12,
+}, {
+	id: '35e476ee-44bd-4711-ad42-87be245d3efd',
+	sfxPitch: 4,
+	img: '/client-assets/drop-and-fusion/square_monos/keycap_1.png',
+	imgSizeX: 256,
+	imgSizeY: 256,
+	spriteScale: 1.12,
+}];
+
+const SWEETS_MONOS: FrontendMonoDefinition[] = [{
+	id: '77f724c0-88be-4aeb-8e1a-a00ed18e3844',
+	sfxPitch: 0.25,
+	img: '/client-assets/drop-and-fusion/sweets_monos/shortcake_color.svg',
+	imgSizeX: 32,
+	imgSizeY: 32,
+	spriteScale: 1,
+}, {
+	id: 'f3468ef4-2e1e-4906-8795-f147f39f7e1f',
+	sfxPitch: 0.5,
+	img: '/client-assets/drop-and-fusion/sweets_monos/pancakes_color.svg',
+	imgSizeX: 32,
+	imgSizeY: 32,
+	spriteScale: 1,
+}, {
+	id: 'bcb41129-6f2d-44ee-89d3-86eb2df564ba',
+	sfxPitch: 0.75,
+	img: '/client-assets/drop-and-fusion/sweets_monos/shaved_ice_color.svg',
+	imgSizeX: 32,
+	imgSizeY: 32,
+	spriteScale: 1,
+}, {
+	id: 'f058e1ad-1981-409b-b3a7-302de0a43744',
+	sfxPitch: 1,
+	img: '/client-assets/drop-and-fusion/sweets_monos/soft_ice_cream_color.svg',
+	imgSizeX: 32,
+	imgSizeY: 32,
+	spriteScale: 1,
+}, {
+	id: 'd22cfe38-5a3b-4b9c-a1a6-907930a3d732',
+	sfxPitch: 1.5,
+	img: '/client-assets/drop-and-fusion/sweets_monos/doughnut_color.svg',
+	imgSizeX: 32,
+	imgSizeY: 32,
+	spriteScale: 1,
+}, {
+	id: '79867083-a073-427e-ae82-07a70d9f3b4f',
+	sfxPitch: 2,
+	img: '/client-assets/drop-and-fusion/sweets_monos/custard_color.svg',
+	imgSizeX: 32,
+	imgSizeY: 32,
+	spriteScale: 1,
+}, {
+	id: '2e152a12-a567-4100-b4d4-d15d81ba47b1',
+	sfxPitch: 2.5,
+	img: '/client-assets/drop-and-fusion/sweets_monos/chocolate_bar_color.svg',
+	imgSizeX: 32,
+	imgSizeY: 32,
+	spriteScale: 1,
+}, {
+	id: '12250376-2258-4716-8eec-b3a7239461fc',
+	sfxPitch: 3,
+	img: '/client-assets/drop-and-fusion/sweets_monos/lollipop_color.svg',
+	imgSizeX: 32,
+	imgSizeY: 32,
+	spriteScale: 1,
+}, {
+	id: '4d4f2668-4be7-44a3-aa3a-856df6e25aa6',
+	sfxPitch: 3.5,
+	img: '/client-assets/drop-and-fusion/sweets_monos/candy_color.svg',
+	imgSizeX: 32,
+	imgSizeY: 32,
+	spriteScale: 1,
+}, {
+	id: 'c9984b40-4045-44c3-b260-d47b7b4625b2',
+	sfxPitch: 4,
+	img: '/client-assets/drop-and-fusion/sweets_monos/cookie_color.svg',
+	imgSizeX: 32,
+	imgSizeY: 32,
+	spriteScale: 1,
+}];
+
+const props = defineProps<{
+	gameMode: 'normal' | 'square' | 'yen' | 'sweets' | 'space';
+	mute: boolean;
+}>();
+
+const emit = defineEmits<{
+	(ev: 'end'): void;
+}>();
+
+const monoDefinitions = computed(() => {
+	return props.gameMode === 'normal' ? NORAML_MONOS :
+		props.gameMode === 'square' ? SQUARE_MONOS :
+		props.gameMode === 'yen' ? YEN_MONOS :
+		props.gameMode === 'sweets' ? SWEETS_MONOS :
+		props.gameMode === 'space' ? NORAML_MONOS :
+		[] as never;
+});
+
+function getScoreUnit(gameMode: string) {
+	return gameMode === 'normal' ? 'pt' :
+		gameMode === 'square' ? 'pt' :
+		gameMode === 'yen' ? '円' :
+		gameMode === 'sweets' ? 'kcal' :
+		'' as never;
+}
+
+function getMonoRenderOptions(mono: Mono) {
+	const def = monoDefinitions.value.find(x => x.id === mono.id)!;
+	return {
+
+		sprite: {
+			texture: def.img,
+			xScale: (mono.sizeX / def.imgSizeX) * def.spriteScale,
+			yScale: (mono.sizeY / def.imgSizeY) * def.spriteScale,
+		},
+
+	};
+}
+
+let viewScale = 1;
+let seed: string = Date.now().toString();
+let containerElRect: DOMRect | null = null;
+let logs: ReturnType<DropAndFusionGame['getLogs']> | null = null;
+let endedAtFrame = 0;
+let bgmNodes: ReturnType<typeof sound.createSourceNode> | null = null;
+let renderer: Matter.Render | null = null;
+let monoTextures: Record<string, Blob> = {};
+let monoTextureUrls: Record<string, string> = {};
+let tickRaf: number | null = null;
+let game = new DropAndFusionGame({
+	seed: seed,
+	gameMode: props.gameMode,
+	getMonoRenderOptions,
+});
+attachGameEvents();
+
+const containerEl = shallowRef<HTMLElement>();
+const canvasEl = shallowRef<HTMLCanvasElement>();
+const dropperX = ref(0);
+const currentPick = shallowRef<{ id: string; mono: Mono } | null>(null);
+const stock = shallowRef<{ id: string; mono: Mono }[]>([]);
+const holdingStock = shallowRef<{ id: string; mono: Mono } | null>(null);
+const score = ref(0);
+const combo = ref(0);
+const comboPrev = ref(0);
+const maxCombo = ref(0);
+const dropReady = ref(true);
+const isGameOver = ref(false);
+const gameLoaded = ref(false);
+const readyGo = ref<'ready' | 'go' | null>('ready');
+const highScore = ref<number | null>(null);
+const yenTotal = ref<number | null>(null);
+const showConfig = ref(false);
+const replaying = ref(false);
+const replayPlaybackRate = ref(1);
+const currentFrame = ref(0);
+const bgmVolume = ref(defaultStore.state.dropAndFusion.bgmVolume);
+const sfxVolume = ref(defaultStore.state.dropAndFusion.sfxVolume);
+
+watch(replayPlaybackRate, (newValue) => {
+	game.replayPlaybackRate = newValue;
+});
+
+watch(bgmVolume, (newValue) => {
+	if (bgmNodes) {
+		bgmNodes.gainNode.gain.value = props.mute ? 0 : newValue;
+	}
+});
+
+function createRendererInstance(game: DropAndFusionGame) {
+	return Matter.Render.create({
+		engine: game.engine,
+		canvas: canvasEl.value!,
+		options: {
+			width: game.GAME_WIDTH,
+			height: game.GAME_HEIGHT,
+			background: 'transparent', // transparent to hide
+			wireframeBackground: 'transparent', // transparent to hide
+			wireframes: false,
+			showSleeping: false,
+			pixelRatio: Math.max(2, window.devicePixelRatio),
+		},
+	});
+}
+
+function loadMonoTextures() {
+	async function loadSingleMonoTexture(mono: FrontendMonoDefinition) {
+		if (renderer == null) return;
+
+		// Matter-js内にキャッシュがある場合はスキップ
+		if (renderer.textures[mono.img]) return;
+
+		let src = mono.img;
+		// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+		if (monoTextureUrls[mono.img]) {
+			src = monoTextureUrls[mono.img];
+			// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+		} else if (monoTextures[mono.img]) {
+			src = URL.createObjectURL(monoTextures[mono.img]);
+			monoTextureUrls[mono.img] = src;
+		} else {
+			const res = await fetch(mono.img);
+			const blob = await res.blob();
+			monoTextures[mono.img] = blob;
+			src = URL.createObjectURL(blob);
+			monoTextureUrls[mono.img] = src;
+		}
+
+		const image = new Image();
+		image.src = src;
+		renderer.textures[mono.img] = image;
+	}
+
+	return Promise.all(monoDefinitions.value.map(x => loadSingleMonoTexture(x)));
+}
+
+function getTextureImageUrl(mono: Mono) {
+	const def = monoDefinitions.value.find(x => x.id === mono.id)!;
+
+	// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+	if (monoTextureUrls[def.img]) {
+		return monoTextureUrls[def.img];
+
+		// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+	} else if (monoTextures[def.img]) {
+		// Gameクラス内にキャッシュがある場合はそれを使う
+		const out = URL.createObjectURL(monoTextures[def.img]);
+		monoTextureUrls[def.img] = out;
+		return out;
+	} else {
+		return def.img;
+	}
+}
+
+function tick() {
+	const hasNextTick = game.tick();
+	if (hasNextTick) {
+		tickRaf = window.requestAnimationFrame(tick);
+	} else {
+		tickRaf = null;
+	}
+}
+
+function tickReplay() {
+	let hasNextTick;
+	for (let i = 0; i < replayPlaybackRate.value; i++) {
+		const log = logs!.find(x => x.frame === game.frame);
+		if (log) {
+			switch (log.operation) {
+				case 'drop': {
+					game.drop(log.x);
+					break;
+				}
+				case 'hold': {
+					game.hold();
+					break;
+				}
+				case 'surrender': {
+					game.surrender();
+					break;
+				}
+				default:
+					break;
+			}
+		}
+
+		hasNextTick = game.tick();
+		currentFrame.value = game.frame;
+		if (!hasNextTick) break;
+	}
+
+	if (hasNextTick) {
+		tickRaf = window.requestAnimationFrame(tickReplay);
+	} else {
+		tickRaf = null;
+	}
+}
+
+async function start() {
+	renderer = createRendererInstance(game);
+	await loadMonoTextures();
+	Matter.Render.lookAt(renderer, {
+		min: { x: 0, y: 0 },
+		max: { x: game.GAME_WIDTH, y: game.GAME_HEIGHT },
+	});
+	Matter.Render.run(renderer);
+	game.start();
+	window.requestAnimationFrame(tick);
+
+	gameLoaded.value = true;
+
+	window.setTimeout(() => {
+		readyGo.value = 'go';
+		window.setTimeout(() => {
+			readyGo.value = null;
+		}, 1000);
+	}, 1500);
+}
+
+function onClick(ev: MouseEvent) {
+	if (!containerElRect) return;
+	if (replaying.value) return;
+	const x = (ev.clientX - containerElRect.left) / viewScale;
+	game.drop(x);
+}
+
+function onTouchend(ev: TouchEvent) {
+	if (!containerElRect) return;
+	if (replaying.value) return;
+	const x = (ev.changedTouches[0].clientX - containerElRect.left) / viewScale;
+	game.drop(x);
+}
+
+function onMousemove(ev: MouseEvent) {
+	if (!containerElRect) return;
+	const x = (ev.clientX - containerElRect.left);
+	moveDropper(containerElRect, x);
+}
+
+function onTouchmove(ev: TouchEvent) {
+	if (!containerElRect) return;
+	const x = (ev.touches[0].clientX - containerElRect.left);
+	moveDropper(containerElRect, x);
+}
+
+function moveDropper(rect: DOMRect, x: number) {
+	dropperX.value = Math.min(rect.width * ((game.GAME_WIDTH - game.PLAYAREA_MARGIN) / game.GAME_WIDTH), Math.max(rect.width * (game.PLAYAREA_MARGIN / game.GAME_WIDTH), x));
+}
+
+function hold() {
+	game.hold();
+}
+
+async function surrender() {
+	const { canceled } = await os.confirm({
+		type: 'warning',
+		text: i18n.ts.areYouSure,
+	});
+	if (canceled) return;
+	game.surrender();
+}
+
+async function restart() {
+	reset();
+	game = new DropAndFusionGame({
+		seed: seed,
+		gameMode: props.gameMode,
+		getMonoRenderOptions,
+	});
+	attachGameEvents();
+	await start();
+}
+
+function reset() {
+	dispose();
+	seed = Date.now().toString();
+	isGameOver.value = false;
+	replaying.value = false;
+	replayPlaybackRate.value = 1;
+	currentPick.value = null;
+	dropReady.value = true;
+	stock.value = [];
+	holdingStock.value = null;
+	score.value = 0;
+	combo.value = 0;
+	comboPrev.value = 0;
+	maxCombo.value = 0;
+	gameLoaded.value = false;
+	readyGo.value = null;
+}
+
+function dispose() {
+	game.dispose();
+	if (renderer) Matter.Render.stop(renderer);
+	if (tickRaf) {
+		window.cancelAnimationFrame(tickRaf);
+	}
+}
+
+function backToTitle() {
+	emit('end');
+}
+
+function replay() {
+	replaying.value = true;
+	dispose();
+	game = new DropAndFusionGame({
+		seed: seed,
+		gameMode: props.gameMode,
+		getMonoRenderOptions,
+	});
+	attachGameEvents();
+	os.promiseDialog(loadMonoTextures(), async () => {
+		renderer = createRendererInstance(game);
+		Matter.Render.lookAt(renderer, {
+			min: { x: 0, y: 0 },
+			max: { x: game.GAME_WIDTH, y: game.GAME_HEIGHT },
+		});
+		Matter.Render.run(renderer);
+		game.start();
+		window.requestAnimationFrame(tickReplay);
+	});
+}
+
+function endReplay() {
+	replaying.value = false;
+	dispose();
+}
+
+function exportLog() {
+	if (!logs) return;
+	const data = JSON.stringify({
+		v: game.GAME_VERSION,
+		m: props.gameMode,
+		s: seed,
+		d: new Date().toISOString(),
+		l: DropAndFusionGame.serializeLogs(logs),
+	});
+	copyToClipboard(data);
+	os.success();
+}
+
+function updateSettings<
+	K extends keyof typeof defaultStore.state.dropAndFusion,
+	V extends typeof defaultStore.state.dropAndFusion[K],
+>(key: K, value: V) {
+	const changes: { [P in K]?: V } = {};
+	changes[key] = value;
+	defaultStore.set('dropAndFusion', {
+		...defaultStore.state.dropAndFusion,
+		...changes,
+	});
+}
+
+function loadImage(url: string) {
+	return new Promise<HTMLImageElement>(res => {
+		const img = new Image();
+		img.src = url;
+		img.addEventListener('load', () => {
+			res(img);
+		});
+	});
+}
+
+function getGameImageDriveFile() {
+	return new Promise<Misskey.entities.DriveFile | null>(res => {
+		const dcanvas = document.createElement('canvas');
+		dcanvas.width = game.GAME_WIDTH;
+		dcanvas.height = game.GAME_HEIGHT;
+		const ctx = dcanvas.getContext('2d');
+		if (!ctx || !canvasEl.value) return res(null);
+		Promise.all([
+			loadImage('/client-assets/drop-and-fusion/frame-light.svg'),
+			loadImage('/client-assets/drop-and-fusion/logo.png'),
+		]).then((images) => {
+			const [frame, logo] = images;
+			ctx.fillStyle = '#fff';
+			ctx.fillRect(0, 0, game.GAME_WIDTH, game.GAME_HEIGHT);
+
+			ctx.drawImage(frame, 0, 0, game.GAME_WIDTH, game.GAME_HEIGHT);
+			ctx.drawImage(canvasEl.value!, 0, 0, game.GAME_WIDTH, game.GAME_HEIGHT);
+
+			ctx.fillStyle = '#000';
+			ctx.font = '16px bold sans-serif';
+			ctx.textBaseline = 'top';
+			ctx.fillText(`SCORE: ${score.value.toLocaleString()}${getScoreUnit(props.gameMode)}`, 10, 10);
+
+			ctx.globalAlpha = 0.7;
+			ctx.drawImage(logo, game.GAME_WIDTH * 0.55, 6, game.GAME_WIDTH * 0.45, game.GAME_WIDTH * 0.45 * (logo.height / logo.width));
+			ctx.globalAlpha = 1;
+
+			dcanvas.toBlob(blob => {
+				if (!blob) return res(null);
+				if ($i == null) return res(null);
+				const formData = new FormData();
+				formData.append('file', blob);
+				formData.append('name', `bubble-game-${Date.now()}.png`);
+				formData.append('isSensitive', 'false');
+				formData.append('i', $i.token);
+				if (defaultStore.state.uploadFolder) {
+					formData.append('folderId', defaultStore.state.uploadFolder);
+				}
+
+				window.fetch(apiUrl + '/drive/files/create', {
+					method: 'POST',
+					body: formData,
+				})
+					.then(response => response.json())
+					.then(f => {
+						res(f);
+					});
+			}, 'image/png');
+
+			dcanvas.remove();
+		});
+	});
+}
+
+async function share() {
+	const uploading = getGameImageDriveFile();
+	os.promiseDialog(uploading);
+	const file = await uploading;
+	if (!file) return;
+	os.post({
+		initialText: `#BubbleGame (${props.gameMode})
+SCORE: ${score.value.toLocaleString()}${getScoreUnit(props.gameMode)}`,
+		initialFiles: [file],
+		instant: true,
+	});
+}
+
+function attachGameEvents() {
+	game.addListener('changeScore', value => {
+		score.value = value;
+	});
+
+	game.addListener('changeCombo', value => {
+		if (value === 0) {
+			comboPrev.value = combo.value;
+		} else {
+			comboPrev.value = value;
+		}
+		maxCombo.value = Math.max(maxCombo.value, value);
+		combo.value = value;
+	});
+
+	game.addListener('changeStock', value => {
+		currentPick.value = JSON.parse(JSON.stringify(value[0]));
+		stock.value = JSON.parse(JSON.stringify(value.slice(1)));
+	});
+
+	game.addListener('changeHolding', value => {
+		holdingStock.value = value;
+
+		if (!props.mute) {
+			sound.playUrl('/client-assets/drop-and-fusion/hold.mp3', {
+				volume: 0.5 * sfxVolume.value,
+				playbackRate: replayPlaybackRate.value,
+			});
+		}
+	});
+
+	game.addListener('dropped', (x) => {
+		if (!props.mute) {
+			const panV = x - game.PLAYAREA_MARGIN;
+			const panW = game.GAME_WIDTH - game.PLAYAREA_MARGIN - game.PLAYAREA_MARGIN;
+			const pan = ((panV / panW) - 0.5) * 2;
+			if (props.gameMode === 'yen') {
+				sound.playUrl('/client-assets/drop-and-fusion/drop_yen.mp3', {
+					volume: sfxVolume.value,
+					pan,
+					playbackRate: replayPlaybackRate.value,
+				});
+			} else {
+				sound.playUrl('/client-assets/drop-and-fusion/drop.mp3', {
+					volume: sfxVolume.value,
+					pan,
+					playbackRate: replayPlaybackRate.value,
+				});
+			}
+		}
+
+		if (replaying.value) return;
+
+		dropReady.value = false;
+		window.setTimeout(() => {
+			if (!isGameOver.value) {
+				dropReady.value = true;
+			}
+		}, game.frameToMs(game.DROP_COOLTIME));
+	});
+
+	game.addListener('fusioned', (x, y, nextMono, scoreDelta) => {
+		if (!canvasEl.value) return;
+
+		const rect = canvasEl.value.getBoundingClientRect();
+		const domX = rect.left + (x * viewScale);
+		const domY = rect.top + (y * viewScale);
+		const scoreUnit = getScoreUnit(props.gameMode);
+		os.popup(MkRippleEffect, { x: domX, y: domY }, {}, 'end');
+		os.popup(MkPlusOneEffect, { x: domX, y: domY, value: scoreDelta + (scoreUnit === 'pt' ? '' : scoreUnit) }, {}, 'end');
+
+		if (nextMono) {
+			const def = monoDefinitions.value.find(x => x.id === nextMono.id)!;
+			if (!props.mute) {
+				const panV = x - game.PLAYAREA_MARGIN;
+				const panW = game.GAME_WIDTH - game.PLAYAREA_MARGIN - game.PLAYAREA_MARGIN;
+				const pan = ((panV / panW) - 0.5) * 2;
+				const pitch = def.sfxPitch;
+				if (props.gameMode === 'yen') {
+					sound.playUrl('/client-assets/drop-and-fusion/fusion_yen.mp3', {
+						volume: 0.25 * sfxVolume.value,
+						pan: pan,
+						playbackRate: (pitch / 4) * replayPlaybackRate.value,
+					});
+				} else {
+					sound.playUrl('/client-assets/drop-and-fusion/fusion.mp3', {
+						volume: sfxVolume.value,
+						pan: pan,
+						playbackRate: pitch * replayPlaybackRate.value,
+					});
+				}
+			}
+		} else {
+			if (!props.mute) {
+				// TODO: 融合後のモノがない場合でも何らかの効果音を再生
+			}
+		}
+	});
+
+	const minCollisionEnergyForSound = 2.5;
+	const maxCollisionEnergyForSound = 9;
+	const soundPitchMax = 4;
+	const soundPitchMin = 0.5;
+
+	game.addListener('collision', (energy, bodyA, bodyB) => {
+		if (!props.mute && (energy > minCollisionEnergyForSound)) {
+			const volume = (Math.min(maxCollisionEnergyForSound, energy - minCollisionEnergyForSound) / maxCollisionEnergyForSound) / 4;
+			const panV =
+				bodyA.label === '_wall_' ? bodyB.position.x - game.PLAYAREA_MARGIN :
+				bodyB.label === '_wall_' ? bodyA.position.x - game.PLAYAREA_MARGIN :
+				((bodyA.position.x + bodyB.position.x) / 2) - game.PLAYAREA_MARGIN;
+			const panW = game.GAME_WIDTH - game.PLAYAREA_MARGIN - game.PLAYAREA_MARGIN;
+			const pan = ((panV / panW) - 0.5) * 2;
+			const pitch = soundPitchMin + ((soundPitchMax - soundPitchMin) * (1 - (Math.min(10, energy) / 10)));
+
+			if (props.gameMode === 'yen') {
+				sound.playUrl('/client-assets/drop-and-fusion/collision_yen.mp3', {
+					volume: volume * sfxVolume.value,
+					pan: pan,
+					playbackRate: Math.max(1, pitch) * replayPlaybackRate.value,
+				});
+			} else {
+				sound.playUrl('/client-assets/drop-and-fusion/collision.mp3', {
+					volume: volume * sfxVolume.value,
+					pan: pan,
+					playbackRate: pitch * replayPlaybackRate.value,
+				});
+			}
+		}
+	});
+
+	game.addListener('monoAdded', (mono) => {
+		if (replaying.value) return;
+
+		// 実績関連
+		if (mono.level === 10) {
+			claimAchievement('bubbleGameExplodingHead');
+
+			const monos = game.getActiveMonos();
+			if (monos.filter(x => x.level === 10).length >= 2) {
+				claimAchievement('bubbleGameDoubleExplodingHead');
+			}
+		}
+	});
+
+	game.addListener('gameOver', () => {
+		if (!props.mute) {
+			if (props.gameMode === 'yen') {
+				sound.playUrl('/client-assets/drop-and-fusion/gameover_yen.mp3', {
+					volume: 0.5 * sfxVolume.value,
+				});
+			} else {
+				sound.playUrl('/client-assets/drop-and-fusion/gameover.mp3', {
+					volume: sfxVolume.value,
+				});
+			}
+		}
+
+		if (replaying.value) {
+			endReplay();
+			return;
+		}
+
+		logs = game.getLogs();
+		endedAtFrame = game.frame;
+		currentPick.value = null;
+		dropReady.value = false;
+		isGameOver.value = true;
+
+		misskeyApi('bubble-game/register', {
+			seed,
+			score: score.value,
+			gameMode: props.gameMode,
+			gameVersion: game.GAME_VERSION,
+			logs: DropAndFusionGame.serializeLogs(logs),
+		});
+
+		if (props.gameMode === 'yen') {
+			yenTotal.value = (yenTotal.value ?? 0) + score.value;
+			misskeyApi('i/registry/set', {
+				scope: ['dropAndFusionGame'],
+				key: 'yenTotal',
+				value: yenTotal.value,
+			});
+		}
+
+		if (score.value > (highScore.value ?? 0)) {
+			highScore.value = score.value;
+
+			misskeyApi('i/registry/set', {
+				scope: ['dropAndFusionGame'],
+				key: 'highScore:' + props.gameMode,
+				value: highScore.value,
+			});
+		}
+	});
+}
+
+useInterval(() => {
+	if (!canvasEl.value) return;
+	const actualCanvasWidth = canvasEl.value.getBoundingClientRect().width;
+	if (actualCanvasWidth === 0) return;
+	viewScale = actualCanvasWidth / game.GAME_WIDTH;
+	containerElRect = containerEl.value?.getBoundingClientRect() ?? null;
+}, 1000, { immediate: false, afterMounted: true });
+
+onMounted(async () => {
+	try {
+		highScore.value = await misskeyApi('i/registry/get', {
+			scope: ['dropAndFusionGame'],
+			key: 'highScore:' + props.gameMode,
+		});
+	} catch (err) {
+		highScore.value = null;
+	}
+
+	if (props.gameMode === 'yen') {
+		try {
+			yenTotal.value = await misskeyApi('i/registry/get', {
+				scope: ['dropAndFusionGame'],
+				key: 'yenTotal',
+			});
+		} catch (err: any) {
+			if (err.code === 'NO_SUCH_KEY') {
+				// nop
+			} else {
+				os.alert({
+					type: 'error',
+					text: i18n.ts.cannotLoad,
+				});
+				return;
+			}
+		}
+	}
+
+	/*
+	const getVerticesFromSvg = async (path: string) => {
+		const svgDoc = await fetch(path)
+			.then((response) => response.text())
+			.then((svgString) => {
+				const parser = new DOMParser();
+				return parser.parseFromString(svgString, 'image/svg+xml');
+			});
+		const pathDatas = svgDoc.querySelectorAll('path');
+		if (!pathDatas) return;
+		const vertices = Array.from(pathDatas).map((pathData) => {
+			return Matter.Svg.pathToVertices(pathData);
+		});
+		return vertices;
+	};
+
+	getVerticesFromSvg('/client-assets/drop-and-fusion/sweets_monos/verts/doughnut_color.svg').then((vertices) => {
+		console.log('doughnut_color', vertices);
+	});
+	*/
+
+	await start();
+
+	const bgmBuffer = await sound.loadAudio('/client-assets/drop-and-fusion/bgm_1.mp3');
+	if (!bgmBuffer) return;
+	bgmNodes = sound.createSourceNode(bgmBuffer, {
+		volume: props.mute ? 0 : bgmVolume.value,
+	});
+	if (!bgmNodes) return;
+	bgmNodes.soundSource.loop = true;
+	bgmNodes.soundSource.start();
+});
+
+onUnmounted(() => {
+	dispose();
+	bgmNodes?.soundSource.stop();
+});
+
+onDeactivated(() => {
+	dispose();
+	bgmNodes?.soundSource.stop();
+});
+
+definePageMetadata(() => ({
+	title: i18n.ts.bubbleGame,
+	icon: 'ph-orange-slice ph-bold ph-lg',
+}));
+</script>
+
+<style lang="scss" module>
+.transition_zoom_move,
+.transition_zoom_enterActive,
+.transition_zoom_leaveActive {
+	transition: opacity 0.5s cubic-bezier(0,.5,.5,1), transform 0.5s cubic-bezier(0,.5,.5,1) !important;
+}
+.transition_zoom_enterFrom,
+.transition_zoom_leaveTo {
+	opacity: 0;
+	transform: scale(0.8);
+}
+
+.transition_stock_move,
+.transition_stock_enterActive,
+.transition_stock_leaveActive {
+	transition: opacity 0.4s cubic-bezier(0,.5,.5,1), transform 0.4s cubic-bezier(0,.5,.5,1) !important;
+}
+.transition_stock_enterFrom,
+.transition_stock_leaveTo {
+	opacity: 0;
+	transform: scale(0.7);
+}
+.transition_stock_leaveActive {
+	position: absolute;
+}
+
+.transition_picked_move,
+.transition_picked_enterActive {
+	transition: opacity 0.5s cubic-bezier(0,.5,.5,1), transform 0.5s cubic-bezier(0,.5,.5,1) !important;
+}
+.transition_picked_leaveActive {
+	transition: all 0s !important;
+}
+.transition_picked_enterFrom,
+.transition_picked_leaveTo {
+	opacity: 0;
+	transform: translateY(-50px);
+}
+.transition_picked_leaveActive {
+	position: absolute;
+}
+
+.transition_combo_move,
+.transition_combo_enterActive {
+	transition: all 0s !important;
+}
+.transition_combo_leaveActive {
+	transition: opacity 0.4s cubic-bezier(0,.5,.5,1), transform 0.4s cubic-bezier(0,.5,.5,1) !important;
+}
+.transition_combo_enterFrom,
+.transition_combo_leaveTo {
+	opacity: 0;
+	transform: scale(0.7);
+}
+.transition_combo_leaveActive {
+	position: absolute;
+}
+
+.root {
+	margin: 0 auto;
+	max-width: 600px;
+	user-select: none;
+
+	* {
+		user-select: none;
+	}
+}
+
+.loadingScreen {
+	text-align: center;
+	padding: 32px;
+}
+
+.readyGo_bg {
+	position: absolute;
+	top: 0;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	z-index: 100;
+	backdrop-filter: blur(4px);
+}
+
+.readyGo_ready,
+.readyGo_go {
+	position: absolute;
+	top: 0;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	z-index: 101;
+	pointer-events: none;
+}
+
+.readyGo_img {
+	display: block;
+	width: 250px;
+	max-width: 100%;
+}
+
+.header {
+	position: relative;
+	z-index: 10;
+	display: grid;
+	grid-template-columns: 1fr;
+	grid-template-rows: auto auto;
+	gap: 8px;
+
+	> .headerTitle {
+		text-align: center;
+	}
+
+	@media (min-width: 500px) {
+		grid-template-columns: 1fr auto;
+		grid-template-rows: auto;
+
+		> .headerTitle {
+			text-align: start;
+		}
+	}
+}
+
+.mainFrameImg {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 100%;
+	// なんかiOSでちらつく
+	//filter: drop-shadow(0 6px 16px #0007);
+	pointer-events: none;
+	user-select: none;
+}
+
+.canvas {
+	position: relative;
+	display: block;
+	z-index: 1;
+	width: 100% !important;
+	height: auto !important;
+	pointer-events: none;
+	user-select: none;
+}
+
+.gameContainer {
+	position: relative;
+	margin-top: -20px;
+}
+
+.stock {
+	pointer-events: none;
+	user-select: none;
+}
+
+.combo {
+	position: absolute;
+	z-index: 3;
+	top: 50%;
+	width: 100%;
+	text-align: center;
+	font-weight: bold;
+	font-style: oblique;
+	color: #fff;
+	-webkit-text-stroke: 1px rgb(255, 145, 0);
+	text-shadow: 0 0 6px #0005;
+	pointer-events: none;
+	user-select: none;
+}
+
+.dropperContainer {
+	position: absolute;
+	top: 0;
+	height: 100%;
+	z-index: 2;
+	pointer-events: none;
+	user-select: none;
+	will-change: left;
+}
+
+.currentMono {
+	position: absolute;
+	display: block;
+	bottom: 88%;
+	z-index: 2;
+	filter: drop-shadow(0 6px 16px #0007);
+}
+
+.dropper {
+	position: relative;
+	top: 0;
+	width: 70px;
+	margin-top: -10px;
+	margin-left: -30px;
+	z-index: 2;
+	filter: drop-shadow(0 6px 16px #0007);
+}
+
+.currentMonoArrow {
+	position: absolute;
+	width: 20px;
+	bottom: 80%;
+	left: -10px;
+	z-index: 3;
+	animation: currentMonoArrow 2s ease infinite;
+}
+
+.dropGuide {
+	position: absolute;
+	z-index: 3;
+	bottom: 0;
+	width: 3px;
+	margin-left: -2px;
+	height: 85%;
+	background: #f002;
+}
+
+.gameOverLabel {
+	position: absolute;
+	z-index: 10;
+	top: 50%;
+	left: 0;
+	right: 0;
+	margin: auto;
+	width: calc(100% - 50px);
+	max-width: 320px;
+	padding: 16px;
+	box-sizing: border-box;
+	background: #0007;
+	border-radius: 16px;
+	color: #fff;
+	text-align: center;
+	font-weight: bold;
+}
+
+.gameOver {
+	.canvas {
+		filter: grayscale(1);
+	}
+}
+
+.replayIndicator {
+	position: absolute;
+	z-index: 10;
+	left: 10px;
+	bottom: 10px;
+	padding: 6px 8px;
+	color: #f00;
+	font-weight: bold;
+	background: #0008;
+	border-radius: 6px;
+	pointer-events: none;
+}
+
+.replayIndicatorText {
+	animation: replayIndicator-blink 2s infinite;
+}
+
+@keyframes replayIndicator-blink {
+	0% { opacity: 1; }
+	50% { opacity: 0; }
+	100% { opacity: 1; }
+}
+
+@keyframes currentMonoArrow {
+	0% { transform: translateY(0); }
+	25% { transform: translateY(-8px); }
+	50% { transform: translateY(0); }
+	75% { transform: translateY(-8px); }
+	100% { transform: translateY(0); }
+}
+</style>
diff --git a/packages/frontend/src/pages/drop-and-fusion.vue b/packages/frontend/src/pages/drop-and-fusion.vue
new file mode 100644
index 0000000000..3ece281468
--- /dev/null
+++ b/packages/frontend/src/pages/drop-and-fusion.vue
@@ -0,0 +1,160 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<Transition
+	:enterActiveClass="$style.transition_zoom_enterActive"
+	:leaveActiveClass="$style.transition_zoom_leaveActive"
+	:enterFromClass="$style.transition_zoom_enterFrom"
+	:leaveToClass="$style.transition_zoom_leaveTo"
+	:moveClass="$style.transition_zoom_move"
+	mode="out-in"
+>
+	<MkSpacer v-if="!gameStarted" :contentMax="800">
+		<div :class="$style.root">
+			<div class="_gaps">
+				<div class="_woodenFrame" style="text-align: center;">
+					<div class="_woodenFrameInner">
+						<img src="/client-assets/drop-and-fusion/logo.png" style="display: block; max-width: 100%; max-height: 200px; margin: auto;"/>
+					</div>
+				</div>
+				<div class="_woodenFrame" style="text-align: center;">
+					<div class="_woodenFrameInner">
+						<div class="_gaps" style="padding: 16px;">
+							<MkSelect v-model="gameMode">
+								<option value="normal">NORMAL</option>
+								<option value="square">SQUARE</option>
+								<option value="yen">YEN</option>
+								<option value="sweets">SWEETS</option>
+								<!--<option value="space">SPACE</option>-->
+							</MkSelect>
+							<MkButton primary gradate large rounded inline @click="start">{{ i18n.ts.start }}</MkButton>
+						</div>
+					</div>
+					<div class="_woodenFrameInner">
+						<div class="_gaps" style="padding: 16px;">
+							<div style="font-size: 90%;"><i class="ph-music-notes ph-bold ph-lg"></i> {{ i18n.ts.soundWillBePlayed }}</div>
+							<MkSwitch v-model="mute">
+								<template #label>{{ i18n.ts.mute }}</template>
+							</MkSwitch>
+						</div>
+					</div>
+				</div>
+				<div class="_woodenFrame">
+					<div class="_woodenFrameInner">
+						<div class="_gaps_s" style="padding: 16px;">
+							<div><b>{{ i18n.tsx.lastNDays({ n: 7 }) }} {{ i18n.ts.ranking }}</b> ({{ gameMode.toUpperCase() }})</div>
+							<div v-if="ranking" class="_gaps_s">
+								<div v-for="r in ranking" :key="r.id" :class="$style.rankingRecord">
+									<MkAvatar :link="true" style="width: 24px; height: 24px; margin-right: 4px;" :user="r.user"/>
+									<MkUserName :user="r.user" :nowrap="true"/>
+									<b style="margin-left: auto;">{{ r.score.toLocaleString() }} {{ getScoreUnit(gameMode) }}</b>
+								</div>
+							</div>
+							<div v-else>{{ i18n.ts.loading }}</div>
+						</div>
+					</div>
+				</div>
+				<div class="_woodenFrame">
+					<div class="_woodenFrameInner" style="padding: 16px;">
+						<div style="font-weight: bold;">{{ i18n.ts._bubbleGame.howToPlay }}</div>
+						<ol>
+							<li>{{ i18n.ts._bubbleGame._howToPlay.section1 }}</li>
+							<li>{{ i18n.ts._bubbleGame._howToPlay.section2 }}</li>
+							<li>{{ i18n.ts._bubbleGame._howToPlay.section3 }}</li>
+						</ol>
+					</div>
+				</div>
+				<div class="_woodenFrame">
+					<div class="_woodenFrameInner">
+						<div class="_gaps_s" style="padding: 16px;">
+							<div><b>Credit</b></div>
+							<div>
+								<div>Ai-chan illustration: @poteriri@misskey.io</div>
+								<div>BGM: @ys@misskey.design</div>
+							</div>
+						</div>
+					</div>
+				</div>
+			</div>
+		</div>
+	</MkSpacer>
+	<XGame v-else :gameMode="gameMode" :mute="mute" @end="onGameEnd"/>
+</Transition>
+</template>
+
+<script lang="ts" setup>
+import { computed, ref, watch } from 'vue';
+import XGame from './drop-and-fusion.game.vue';
+import { definePageMetadata } from '@/scripts/page-metadata.js';
+import MkButton from '@/components/MkButton.vue';
+import { i18n } from '@/i18n.js';
+import MkSelect from '@/components/MkSelect.vue';
+import MkSwitch from '@/components/MkSwitch.vue';
+import { misskeyApiGet } from '@/scripts/misskey-api.js';
+
+const gameMode = ref<'normal' | 'square' | 'yen' | 'sweets' | 'space'>('normal');
+const gameStarted = ref(false);
+const mute = ref(false);
+const ranking = ref(null);
+
+watch(gameMode, async () => {
+	ranking.value = await misskeyApiGet('bubble-game/ranking', { gameMode: gameMode.value });
+}, { immediate: true });
+
+function getScoreUnit(gameMode: string) {
+	return gameMode === 'normal' ? 'pt' :
+		gameMode === 'square' ? 'pt' :
+		gameMode === 'yen' ? '円' :
+		gameMode === 'sweets' ? 'kcal' :
+		gameMode === 'space' ? 'pt' :
+		'' as never;
+}
+
+async function start() {
+	gameStarted.value = true;
+}
+
+function onGameEnd() {
+	gameStarted.value = false;
+}
+
+definePageMetadata(() => ({
+	title: i18n.ts.bubbleGame,
+	icon: 'ph-game-controller ph-bold ph-lg',
+}));
+</script>
+
+<style lang="scss" module>
+.transition_zoom_move,
+.transition_zoom_enterActive,
+.transition_zoom_leaveActive {
+	transition: opacity 0.5s cubic-bezier(0,.5,.5,1), transform 0.5s cubic-bezier(0,.5,.5,1) !important;
+}
+.transition_zoom_enterFrom,
+.transition_zoom_leaveTo {
+	opacity: 0;
+	transform: scale(0.8);
+}
+
+.root {
+	margin: 0 auto;
+	max-width: 600px;
+	user-select: none;
+
+	* {
+		user-select: none;
+	}
+}
+
+.rankingRecord {
+	display: flex;
+	line-height: 24px;
+	padding-top: 4px;
+	white-space: nowrap;
+	overflow: visible;
+	text-overflow: ellipsis;
+}
+</style>
diff --git a/packages/frontend/src/pages/emoji-edit-dialog.vue b/packages/frontend/src/pages/emoji-edit-dialog.vue
index 82cfa92f6a..64960fd063 100644
--- a/packages/frontend/src/pages/emoji-edit-dialog.vue
+++ b/packages/frontend/src/pages/emoji-edit-dialog.vue
@@ -1,13 +1,15 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<MkModalWindow
-	ref="dialog"
-	:width="400"
-	@close="dialog.close()"
+<MkWindow
+	ref="windowEl"
+	:initialWidth="400"
+	:initialHeight="500"
+	:canResize="false"
+	@close="windowEl.close()"
 	@closed="$emit('closed')"
 >
 	<template v-if="emoji" #header>:{{ emoji.name }}:</template>
@@ -39,9 +41,12 @@ SPDX-License-Identifier: AGPL-3.0-only
 				</MkInput>
 				<MkInput v-model="aliases" autocapitalize="off">
 					<template #label>{{ i18n.ts.tags }}</template>
-					<template #caption>{{ i18n.ts.setMultipleBySeparatingWithSpace }}</template>
+					<template #caption>
+						{{ i18n.ts.theKeywordWhenSearchingForCustomEmoji }}<br/>
+						{{ i18n.ts.setMultipleBySeparatingWithSpace }}
+					</template>
 				</MkInput>
-				<MkInput v-model="license">
+				<MkInput v-model="license" :mfmAutocomplete="true">
 					<template #label>{{ i18n.ts.license }}</template>
 				</MkInput>
 				<MkFolder>
@@ -70,18 +75,19 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<MkButton primary rounded style="margin: 0 auto;" @click="done"><i class="ph-check ph-bold ph-lg"></i> {{ props.emoji ? i18n.ts.update : i18n.ts.create }}</MkButton>
 		</div>
 	</div>
-</MkModalWindow>
+</MkWindow>
 </template>
 
 <script lang="ts" setup>
 import { computed, watch, ref } from 'vue';
 import * as Misskey from 'misskey-js';
-import MkModalWindow from '@/components/MkModalWindow.vue';
+import MkWindow from '@/components/MkWindow.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import MkFolder from '@/components/MkFolder.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { customEmojiCategories } from '@/custom-emojis.js';
 import MkSwitch from '@/components/MkSwitch.vue';
@@ -92,7 +98,7 @@ const props = defineProps<{
 	emoji?: any,
 }>();
 
-const dialog = ref<InstanceType<typeof MkModalWindow> | null>(null);
+const windowEl = ref<InstanceType<typeof MkWindow> | null>(null);
 const name = ref<string>(props.emoji ? props.emoji.name : '');
 const category = ref<string>(props.emoji ? props.emoji.category : '');
 const aliases = ref<string>(props.emoji ? props.emoji.aliases.join(' ') : '');
@@ -104,7 +110,7 @@ const rolesThatCanBeUsedThisEmojiAsReaction = ref<Misskey.entities.Role[]>([]);
 const file = ref<Misskey.entities.DriveFile>();
 
 watch(roleIdsThatCanBeUsedThisEmojiAsReaction, async () => {
-	rolesThatCanBeUsedThisEmojiAsReaction.value = (await Promise.all(roleIdsThatCanBeUsedThisEmojiAsReaction.value.map((id) => os.api('admin/roles/show', { roleId: id }).catch(() => null)))).filter(x => x != null);
+	rolesThatCanBeUsedThisEmojiAsReaction.value = (await Promise.all(roleIdsThatCanBeUsedThisEmojiAsReaction.value.map((id) => misskeyApi('admin/roles/show', { roleId: id }).catch(() => null)))).filter(x => x != null);
 }, { immediate: true });
 
 const imgUrl = computed(() => file.value ? file.value.url : props.emoji ? `/emoji/${props.emoji.name}.webp` : null);
@@ -123,13 +129,13 @@ async function changeImage(ev) {
 }
 
 async function addRole() {
-	const roles = await os.api('admin/roles/list');
+	const roles = await misskeyApi('admin/roles/list');
 	const currentRoleIds = rolesThatCanBeUsedThisEmojiAsReaction.value.map(x => x.id);
 
 	const { canceled, result: role } = await os.select({
 		items: roles.filter(r => r.isPublic).filter(r => !currentRoleIds.includes(r.id)).map(r => ({ text: r.name, value: r })),
 	});
-	if (canceled) return;
+	if (canceled || role == null) return;
 
 	rolesThatCanBeUsedThisEmojiAsReaction.value.push(role);
 }
@@ -166,7 +172,7 @@ async function done() {
 			},
 		});
 
-		dialog.value.close();
+		windowEl.value.close();
 	} else {
 		const created = await os.apiWithDialog('admin/emoji/add', params);
 
@@ -174,24 +180,24 @@ async function done() {
 			created: created,
 		});
 
-		dialog.value.close();
+		windowEl.value.close();
 	}
 }
 
 async function del() {
 	const { canceled } = await os.confirm({
 		type: 'warning',
-		text: i18n.t('removeAreYouSure', { x: name.value }),
+		text: i18n.tsx.removeAreYouSure({ x: name.value }),
 	});
 	if (canceled) return;
 
-	os.api('admin/emoji/delete', {
+	misskeyApi('admin/emoji/delete', {
 		id: props.emoji.id,
 	}).then(() => {
 		emit('done', {
 			deleted: true,
 		});
-		dialog.value.close();
+		windowEl.value.close();
 	});
 }
 </script>
diff --git a/packages/frontend/src/pages/emojis.emoji.vue b/packages/frontend/src/pages/emojis.emoji.vue
index d94fe96fa2..c9805af51b 100644
--- a/packages/frontend/src/pages/emojis.emoji.vue
+++ b/packages/frontend/src/pages/emojis.emoji.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -14,18 +14,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { } from 'vue';
 import * as os from '@/os.js';
+import * as Misskey from 'misskey-js';
+import { misskeyApiGet } from '@/scripts/misskey-api.js';
 import copyToClipboard from '@/scripts/copy-to-clipboard.js';
 import { i18n } from '@/i18n.js';
+import MkCustomEmojiDetailedDialog from '@/components/MkCustomEmojiDetailedDialog.vue';
 
 const props = defineProps<{
-	emoji: {
-		name: string;
-		aliases: string[];
-		category: string;
-		url: string;
-	};
+  emoji: Misskey.entities.EmojiSimple;
 }>();
 
 function menu(ev) {
@@ -42,12 +39,13 @@ function menu(ev) {
 	}, {
 		text: i18n.ts.info,
 		icon: 'ph-info ph-bold ph-lg',
-		action: () => {
-			os.apiGet('emoji', { name: props.emoji.name }).then(res => {
-				os.alert({
-					type: 'info',
-					text: `Name: ${res.name}\nAliases: ${res.aliases.join(' ')}\nCategory: ${res.category}\nisSensitive: ${res.isSensitive}\nlocalOnly: ${res.localOnly}\nLicense: ${res.license}\nURL: ${res.url}`,
-				});
+		action: async () => {
+			os.popup(MkCustomEmojiDetailedDialog, {
+				emoji: await misskeyApiGet('emoji', {
+					name: props.emoji.name,
+				})
+			}, {
+				anchor: ev.target,
 			});
 		},
 	}], ev.currentTarget ?? ev.target);
diff --git a/packages/frontend/src/pages/explore.featured.vue b/packages/frontend/src/pages/explore.featured.vue
index 000371528e..b5c8e70166 100644
--- a/packages/frontend/src/pages/explore.featured.vue
+++ b/packages/frontend/src/pages/explore.featured.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/pages/explore.roles.vue b/packages/frontend/src/pages/explore.roles.vue
index d30e107e97..389cd23ad2 100644
--- a/packages/frontend/src/pages/explore.roles.vue
+++ b/packages/frontend/src/pages/explore.roles.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -15,11 +15,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkRolePreview from '@/components/MkRolePreview.vue';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 
 const roles = ref<Misskey.entities.Role[] | null>(null);
 
-os.api('roles/list').then(res => {
+misskeyApi('roles/list').then(res => {
 	roles.value = res.filter(x => x.target === 'manual').sort((a, b) => b.displayOrder - a.displayOrder);
 });
 </script>
diff --git a/packages/frontend/src/pages/explore.users.vue b/packages/frontend/src/pages/explore.users.vue
index fbca2b8ede..c9ab5443b6 100644
--- a/packages/frontend/src/pages/explore.users.vue
+++ b/packages/frontend/src/pages/explore.users.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -68,7 +68,7 @@ import * as Misskey from 'misskey-js';
 import MkUserList from '@/components/MkUserList.vue';
 import MkFoldableSection from '@/components/MkFoldableSection.vue';
 import MkTab from '@/components/MkTab.vue';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 
 const props = defineProps<{
@@ -123,14 +123,14 @@ const recentlyRegisteredUsersF = { endpoint: 'users', limit: 10, noPaging: true,
 	sort: '+createdAt',
 } };
 
-os.api('hashtags/list', {
+misskeyApi('hashtags/list', {
 	sort: '+attachedLocalUsers',
 	attachedToLocalUserOnly: true,
 	limit: 30,
 }).then(tags => {
 	tagsLocal.value = tags;
 });
-os.api('hashtags/list', {
+misskeyApi('hashtags/list', {
 	sort: '+attachedRemoteUsers',
 	attachedToRemoteUserOnly: true,
 	limit: 30,
diff --git a/packages/frontend/src/pages/explore.vue b/packages/frontend/src/pages/explore.vue
index 9693e26598..c599a290f7 100644
--- a/packages/frontend/src/pages/explore.vue
+++ b/packages/frontend/src/pages/explore.vue
@@ -1,22 +1,22 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
-	<div>
-		<div v-if="tab === 'featured'">
+	<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
+		<div v-if="tab === 'featured'" key="featured">
 			<XFeatured/>
 		</div>
-		<div v-else-if="tab === 'users'">
+		<div v-else-if="tab === 'users'" key="users">
 			<XUsers/>
 		</div>
-		<div v-else-if="tab === 'roles'">
+		<div v-else-if="tab === 'roles'" key="roles">
 			<XRoles/>
 		</div>
-	</div>
+	</MkHorizontalSwipe>
 </MkStickyContainer>
 </template>
 
@@ -26,6 +26,7 @@ import XFeatured from './explore.featured.vue';
 import XUsers from './explore.users.vue';
 import XRoles from './explore.roles.vue';
 import MkFoldableSection from '@/components/MkFoldableSection.vue';
+import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { i18n } from '@/i18n.js';
 
@@ -59,8 +60,8 @@ const headerTabs = computed(() => [{
 	title: i18n.ts.roles,
 }]);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts.explore,
 	icon: 'ph-hash ph-bold ph-lg',
-})));
+}));
 </script>
diff --git a/packages/frontend/src/pages/favorites.vue b/packages/frontend/src/pages/favorites.vue
index 10f4a96a98..2827898194 100644
--- a/packages/frontend/src/pages/favorites.vue
+++ b/packages/frontend/src/pages/favorites.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -38,10 +38,10 @@ const pagination = {
 	limit: 10,
 };
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.favorites,
 	icon: 'ph-star ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/flash/flash-edit.vue b/packages/frontend/src/pages/flash/flash-edit.vue
index 21e6d00613..53c8c78914 100644
--- a/packages/frontend/src/pages/flash/flash-edit.vue
+++ b/packages/frontend/src/pages/flash/flash-edit.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<MkInput v-model="title">
 				<template #label>{{ i18n.ts._play.title }}</template>
 			</MkInput>
-			<MkTextarea v-model="summary">
+			<MkTextarea v-model="summary" :mfmAutocomplete="true" :mfmPreview="true">
 				<template #label>{{ i18n.ts._play.summary }}</template>
 			</MkTextarea>
 			<MkButton primary @click="selectPreset">{{ i18n.ts.selectFromPresets }}<i class="ph-caret-down ph-bold ph-lg"></i></MkButton>
@@ -38,13 +38,14 @@ import { computed, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkTextarea from '@/components/MkTextarea.vue';
 import MkCodeEditor from '@/components/MkCodeEditor.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkSelect from '@/components/MkSelect.vue';
-import { useRouter } from '@/router.js';
+import { useRouter } from '@/router/supplier.js';
 
 const PRESET_DEFAULT = `/// @ 0.16.0
 
@@ -369,7 +370,7 @@ const flash = ref<Misskey.entities.Flash | null>(null);
 const visibility = ref<Misskey.entities.FlashUpdateRequest['visibility']>('public');
 
 if (props.id) {
-	flash.value = await os.api('flash/show', {
+	flash.value = await misskeyApi('flash/show', {
 		flashId: props.id,
 	});
 }
@@ -437,7 +438,7 @@ function show() {
 async function del() {
 	const { canceled } = await os.confirm({
 		type: 'warning',
-		text: i18n.t('deleteAreYouSure', { x: flash.value.title }),
+		text: i18n.tsx.deleteAreYouSure({ x: flash.value.title }),
 	});
 	if (canceled) return;
 
@@ -451,9 +452,7 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => flash.value ? {
-	title: i18n.ts._play.edit + ': ' + flash.value.title,
-} : {
-	title: i18n.ts._play.new,
+definePageMetadata(() => ({
+	title: flash.value ? `${i18n.ts._play.edit}: ${flash.value.title}` : i18n.ts._play.new,
 }));
 </script>
diff --git a/packages/frontend/src/pages/flash/flash-index.vue b/packages/frontend/src/pages/flash/flash-index.vue
index 2b9346fcac..7e56d3f51b 100644
--- a/packages/frontend/src/pages/flash/flash-index.vue
+++ b/packages/frontend/src/pages/flash/flash-index.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -7,32 +7,34 @@ SPDX-License-Identifier: AGPL-3.0-only
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
 	<MkSpacer :contentMax="700">
-		<div v-if="tab === 'featured'">
-			<MkPagination v-slot="{items}" :pagination="featuredFlashsPagination">
-				<div class="_gaps_s">
-					<MkFlashPreview v-for="flash in items" :key="flash.id" :flash="flash"/>
-				</div>
-			</MkPagination>
-		</div>
-
-		<div v-else-if="tab === 'my'">
-			<div class="_gaps">
-				<MkButton gradate rounded style="margin: 0 auto;" @click="create()"><i class="ph-plus ph-bold ph-lg"></i></MkButton>
-				<MkPagination v-slot="{items}" :pagination="myFlashsPagination">
+		<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
+			<div v-if="tab === 'featured'" key="featured">
+				<MkPagination v-slot="{items}" :pagination="featuredFlashsPagination">
 					<div class="_gaps_s">
 						<MkFlashPreview v-for="flash in items" :key="flash.id" :flash="flash"/>
 					</div>
 				</MkPagination>
 			</div>
-		</div>
 
-		<div v-else-if="tab === 'liked'">
-			<MkPagination v-slot="{items}" :pagination="likedFlashsPagination">
-				<div class="_gaps_s">
-					<MkFlashPreview v-for="like in items" :key="like.flash.id" :flash="like.flash"/>
+			<div v-else-if="tab === 'my'" key="my">
+				<div class="_gaps">
+					<MkButton gradate rounded style="margin: 0 auto;" @click="create()"><i class="ph-plus ph-bold ph-lg"></i></MkButton>
+					<MkPagination v-slot="{items}" :pagination="myFlashsPagination">
+						<div class="_gaps_s">
+							<MkFlashPreview v-for="flash in items" :key="flash.id" :flash="flash"/>
+						</div>
+					</MkPagination>
 				</div>
-			</MkPagination>
-		</div>
+			</div>
+
+			<div v-else-if="tab === 'liked'" key="liked">
+				<MkPagination v-slot="{items}" :pagination="likedFlashsPagination">
+					<div class="_gaps_s">
+						<MkFlashPreview v-for="like in items" :key="like.flash.id" :flash="like.flash"/>
+					</div>
+				</MkPagination>
+			</div>
+		</MkHorizontalSwipe>
 	</MkSpacer>
 </MkStickyContainer>
 </template>
@@ -42,9 +44,10 @@ import { computed, ref } from 'vue';
 import MkFlashPreview from '@/components/MkFlashPreview.vue';
 import MkPagination from '@/components/MkPagination.vue';
 import MkButton from '@/components/MkButton.vue';
-import { useRouter } from '@/router.js';
+import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { useRouter } from '@/router/supplier.js';
 
 const router = useRouter();
 
@@ -80,15 +83,15 @@ const headerTabs = computed(() => [{
 }, {
 	key: 'my',
 	title: i18n.ts._play.my,
-	icon: 'ph-pencil-line ph-bold ph-lg',
+	icon: 'ph-pencil-simple-line ph-bold ph-lg',
 }, {
 	key: 'liked',
 	title: i18n.ts._play.liked,
 	icon: 'ph-heart ph-bold ph-lg',
 }]);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: 'Play',
 	icon: 'ph-play ph-bold ph-lg',
-})));
+}));
 </script>
diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue
index 5fae1248e9..cbb52a2e23 100644
--- a/packages/frontend/src/pages/flash/flash.vue
+++ b/packages/frontend/src/pages/flash/flash.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<div v-else :class="$style.ready">
 						<div class="_panel main">
 							<div class="title">{{ flash.title }}</div>
-							<div class="summary">{{ flash.summary }}</div>
+							<div class="summary"><Mfm :text="flash.summary"/></div>
 							<MkButton class="start" gradate rounded large @click="start">Play</MkButton>
 							<div class="info">
 								<span v-tooltip="i18n.ts.numberOfLikes"><i class="ph-heart ph-bold ph-lg"></i> {{ flash.likedCount }}</span>
@@ -37,7 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<template #icon><i class="ph-code ph-bold ph-lg"></i></template>
 					<template #label>{{ i18n.ts._play.viewSource }}</template>
 
-					<MkCode :code="flash.script" lang="is" :inline="false" class="_monospace"/>
+					<MkCode :code="flash.script" lang="is" class="_monospace"/>
 				</MkFolder>
 				<div :class="$style.footer">
 					<Mfm :text="`By @${flash.user.username}`"/>
@@ -62,12 +62,13 @@ import * as Misskey from 'misskey-js';
 import { Interpreter, Parser, values } from '@syuilo/aiscript';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { url } from '@/config.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkAsUi from '@/components/MkAsUi.vue';
 import { AsUiComponent, AsUiRoot, registerAsUiLib } from '@/scripts/aiscript/ui.js';
-import { createAiScriptEnv } from '@/scripts/aiscript/api.js';
+import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js';
 import MkFolder from '@/components/MkFolder.vue';
 import MkCode from '@/components/MkCode.vue';
 import { defaultStore } from '@/store.js';
@@ -84,7 +85,7 @@ const error = ref<any>(null);
 
 function fetchFlash() {
 	flash.value = null;
-	os.api('flash/show', {
+	misskeyApi('flash/show', {
 		flashId: props.id,
 	}).then(_flash => {
 		flash.value = _flash;
@@ -162,15 +163,7 @@ async function run() {
 		THIS_ID: values.STR(flash.value.id),
 		THIS_URL: values.STR(`${url}/play/${flash.value.id}`),
 	}, {
-		in: (q) => {
-			return new Promise(ok => {
-				os.inputText({
-					title: q,
-				}).then(({ result: a }) => {
-					ok(a ?? '');
-				});
-			});
-		},
+		in: aiScriptReadline,
 		out: (value) => {
 			// nop
 		},
@@ -212,15 +205,17 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => flash.value ? {
-	title: flash.value.title,
-	avatar: flash.value.user,
-	path: `/play/${flash.value.id}`,
-	share: {
-		title: flash.value.title,
-		text: flash.value.summary,
-	},
-} : null));
+definePageMetadata(() => ({
+	title: flash.value ? flash.value.title : 'Play',
+	...flash.value ? {
+		avatar: flash.value.user,
+		path: `/play/${flash.value.id}`,
+		share: {
+			title: flash.value.title,
+			text: flash.value.summary,
+		},
+	} : {},
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/follow-requests.vue b/packages/frontend/src/pages/follow-requests.vue
index d750664221..4cdfe28916 100644
--- a/packages/frontend/src/pages/follow-requests.vue
+++ b/packages/frontend/src/pages/follow-requests.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -41,7 +41,7 @@ import { shallowRef, computed } from 'vue';
 import MkPagination from '@/components/MkPagination.vue';
 import MkButton from '@/components/MkButton.vue';
 import { userPage, acct } from '@/filters/user.js';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { infoImageUrl } from '@/instance.js';
@@ -54,13 +54,13 @@ const pagination = {
 };
 
 function accept(user) {
-	os.api('following/requests/accept', { userId: user.id }).then(() => {
+	misskeyApi('following/requests/accept', { userId: user.id }).then(() => {
 		paginationComponent.value.reload();
 	});
 }
 
 function reject(user) {
-	os.api('following/requests/reject', { userId: user.id }).then(() => {
+	misskeyApi('following/requests/reject', { userId: user.id }).then(() => {
 		paginationComponent.value.reload();
 	});
 }
@@ -69,10 +69,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts.followRequests,
 	icon: 'ph-user-plus ph-bold ph-lg',
-})));
+}));
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/pages/follow.vue b/packages/frontend/src/pages/follow.vue
index a0a4a480b5..247b0ac639 100644
--- a/packages/frontend/src/pages/follow.vue
+++ b/packages/frontend/src/pages/follow.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -12,14 +12,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { } from 'vue';
 import * as Misskey from 'misskey-js';
 import * as os from '@/os.js';
-import { mainRouter } from '@/router.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
-import { defaultStore } from "@/store.js";
+import { defaultStore } from '@/store.js';
+import { mainRouter } from '@/router/main.js';
 
 async function follow(user): Promise<void> {
 	const { canceled } = await os.confirm({
 		type: 'question',
-		text: i18n.t('followConfirm', { name: user.name || user.username }),
+		text: i18n.tsx.followConfirm({ name: user.name || user.username }),
 	});
 
 	if (canceled) {
@@ -42,7 +43,7 @@ if (acct == null) {
 let promise;
 
 if (acct.startsWith('https://')) {
-	promise = os.api('ap/show', {
+	promise = misskeyApi('ap/show', {
 		uri: acct,
 	});
 	promise.then(res => {
@@ -60,7 +61,7 @@ if (acct.startsWith('https://')) {
 		}
 	});
 } else {
-	promise = os.api('users/show', Misskey.acct.parse(acct));
+	promise = misskeyApi('users/show', Misskey.acct.parse(acct));
 	promise.then(user => {
 		follow(user);
 	});
diff --git a/packages/frontend/src/pages/gallery/edit.vue b/packages/frontend/src/pages/gallery/edit.vue
index 857317c48f..d2fe271b0f 100644
--- a/packages/frontend/src/pages/gallery/edit.vue
+++ b/packages/frontend/src/pages/gallery/edit.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -47,9 +47,10 @@ import MkSwitch from '@/components/MkSwitch.vue';
 import FormSuspense from '@/components/form/suspense.vue';
 import { selectFiles } from '@/scripts/select-file.js';
 import * as os from '@/os.js';
-import { useRouter } from '@/router.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { i18n } from '@/i18n.js';
+import { useRouter } from '@/router/supplier.js';
 
 const router = useRouter();
 
@@ -107,7 +108,7 @@ async function del() {
 }
 
 watch(() => props.postId, () => {
-	init.value = () => props.postId ? os.api('gallery/posts/show', {
+	init.value = () => props.postId ? misskeyApi('gallery/posts/show', {
 		postId: props.postId,
 	}).then(post => {
 		files.value = post.files ?? [];
@@ -121,12 +122,9 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => props.postId ? {
-	title: i18n.ts.edit,
-	icon: 'ph-pencil ph-bold ph-lg',
-} : {
-	title: i18n.ts.postToGallery,
-	icon: 'ph-pencil ph-bold ph-lg',
+definePageMetadata(() => ({
+	title: props.postId ? i18n.ts.edit : i18n.ts.postToGallery,
+	icon: 'ph-pencil-simple ph-bold ph-lg',
 }));
 </script>
 
diff --git a/packages/frontend/src/pages/gallery/index.vue b/packages/frontend/src/pages/gallery/index.vue
index 936d9b8393..96979250bd 100644
--- a/packages/frontend/src/pages/gallery/index.vue
+++ b/packages/frontend/src/pages/gallery/index.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -7,8 +7,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
 	<MkSpacer :contentMax="1400">
-		<div class="_root">
-			<div v-if="tab === 'explore'">
+		<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
+			<div v-if="tab === 'explore'" key="explore">
 				<MkFoldableSection class="_margin">
 					<template #header><i class="ph-clock ph-bold ph-lg"></i>{{ i18n.ts.recentPosts }}</template>
 					<MkPagination v-slot="{items}" :pagination="recentPostsPagination" :disableAutoLoad="true">
@@ -26,14 +26,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 					</MkPagination>
 				</MkFoldableSection>
 			</div>
-			<div v-else-if="tab === 'liked'">
+			<div v-else-if="tab === 'liked'" key="liked">
 				<MkPagination v-slot="{items}" :pagination="likedPostsPagination">
 					<div :class="$style.items">
 						<MkGalleryPostPreview v-for="like in items" :key="like.id" :post="like.post" class="post"/>
 					</div>
 				</MkPagination>
 			</div>
-			<div v-else-if="tab === 'my'">
+			<div v-else-if="tab === 'my'" key="my">
 				<MkA to="/gallery/new" class="_link" style="margin: 16px;"><i class="ph-plus ph-bold ph-lg"></i> {{ i18n.ts.postToGallery }}</MkA>
 				<MkPagination v-slot="{items}" :pagination="myPostsPagination">
 					<div :class="$style.items">
@@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					</div>
 				</MkPagination>
 			</div>
-		</div>
+		</MkHorizontalSwipe>
 	</MkSpacer>
 </MkStickyContainer>
 </template>
@@ -51,9 +51,10 @@ import { watch, ref, computed } from 'vue';
 import MkFoldableSection from '@/components/MkFoldableSection.vue';
 import MkPagination from '@/components/MkPagination.vue';
 import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue';
+import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { i18n } from '@/i18n.js';
-import { useRouter } from '@/router.js';
+import { useRouter } from '@/router/supplier.js';
 
 const router = useRouter();
 
@@ -115,13 +116,13 @@ const headerTabs = computed(() => [{
 }, {
 	key: 'my',
 	title: i18n.ts._gallery.my,
-	icon: 'ph-pencil-line ph-bold ph-lg',
+	icon: 'ph-pencil-simple-line ph-bold ph-lg',
 }]);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.gallery,
 	icon: 'ph-images-square ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/gallery/post.vue b/packages/frontend/src/pages/gallery/post.vue
index 54a8790ef9..1511928d55 100644
--- a/packages/frontend/src/pages/gallery/post.vue
+++ b/packages/frontend/src/pages/gallery/post.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 								<MkButton v-else v-tooltip="i18n.ts._gallery.like" class="button" @click="like()"><i class="ph-heart ph-bold ph-lg"></i><span v-if="post.likedCount > 0" class="count">{{ post.likedCount }}</span></MkButton>
 							</div>
 							<div class="other">
-								<button v-if="$i && $i.id === post.user.id" v-tooltip="i18n.ts.edit" v-click-anime class="_button" @click="edit"><i class="ph-pencil ph-bold ph-lg ti-fw"></i></button>
+								<button v-if="$i && $i.id === post.user.id" v-tooltip="i18n.ts.edit" v-click-anime class="_button" @click="edit"><i class="ph-pencil-simple ph-bold ph-lg ti-fw"></i></button>
 								<button v-tooltip="i18n.ts.shareWithNote" v-click-anime class="_button" @click="shareWithNote"><i class="ph-repeat ph-bold ph-lg ti-fw"></i></button>
 								<button v-tooltip="i18n.ts.copyLink" v-click-anime class="_button" @click="copyLink"><i class="ph-share-network ph-bold ph-lg ti-fw"></i></button>
 								<button v-if="isSupportShare()" v-tooltip="i18n.ts.share" v-click-anime class="_button" @click="share"><i class="ph-share-network ph-bold ph-lg ti-fw"></i></button>
@@ -66,18 +66,19 @@ import { computed, watch, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import MkContainer from '@/components/MkContainer.vue';
 import MkPagination from '@/components/MkPagination.vue';
 import MkGalleryPostPreview from '@/components/MkGalleryPostPreview.vue';
 import MkFollowButton from '@/components/MkFollowButton.vue';
 import { url } from '@/config.js';
-import { useRouter } from '@/router.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { defaultStore } from '@/store.js';
 import { $i } from '@/account.js';
 import { isSupportShare } from '@/scripts/navigator.js';
 import copyToClipboard from '@/scripts/copy-to-clipboard.js';
+import { useRouter } from '@/router/supplier.js';
 
 const router = useRouter();
 
@@ -97,7 +98,7 @@ const otherPostsPagination = {
 
 function fetchPost() {
 	post.value = null;
-	os.api('gallery/posts/show', {
+	misskeyApi('gallery/posts/show', {
 		postId: props.postId,
 	}).then(_post => {
 		post.value = _post;
@@ -155,17 +156,19 @@ function edit() {
 watch(() => props.postId, fetchPost, { immediate: true });
 
 const headerActions = computed(() => [{
-	icon: 'ph-pencil ph-bold ph-lg',
+	icon: 'ph-pencil-simple ph-bold ph-lg',
 	text: i18n.ts.edit,
 	handler: edit,
 }]);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => post.value ? {
-	title: post.value.title,
-	avatar: post.value.user,
-} : null));
+definePageMetadata(() => ({
+	title: post.value ? post.value.title : i18n.ts.gallery,
+	...post.value ? {
+		avatar: post.value.user,
+	} : {},
+}));
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/pages/games.vue b/packages/frontend/src/pages/games.vue
new file mode 100644
index 0000000000..2822fbde89
--- /dev/null
+++ b/packages/frontend/src/pages/games.vue
@@ -0,0 +1,34 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkStickyContainer>
+	<template #header><MkPageHeader/></template>
+	<MkSpacer :contentMax="800">
+		<div class="_gaps">
+			<div class="_panel">
+				<MkA to="/bubble-game">
+					<img src="/client-assets/drop-and-fusion/logo.png" style="display: block; max-width: 100%; max-height: 200px; margin: auto;"/>
+				</MkA>
+			</div>
+			<div class="_panel">
+				<MkA to="/reversi">
+					<img src="/client-assets/reversi/logo.png" style="display: block; max-width: 100%; max-height: 200px; margin: auto;"/>
+				</MkA>
+			</div>
+		</div>
+	</MkSpacer>
+</MkStickyContainer>
+</template>
+
+<script lang="ts" setup>
+import { i18n } from '@/i18n.js';
+import { definePageMetadata } from '@/scripts/page-metadata.js';
+
+definePageMetadata(() => ({
+	title: 'Misskey Games',
+	icon: 'ph-game-controller ph-bold ph-lg',
+}));
+</script>
diff --git a/packages/frontend/src/pages/install-extentions.vue b/packages/frontend/src/pages/install-extensions.vue
similarity index 98%
rename from packages/frontend/src/pages/install-extentions.vue
rename to packages/frontend/src/pages/install-extensions.vue
index 7e6c75ac99..32f6fbd185 100644
--- a/packages/frontend/src/pages/install-extentions.vue
+++ b/packages/frontend/src/pages/install-extensions.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -105,6 +105,7 @@ import MkInfo from '@/components/MkInfo.vue';
 import MkFolder from '@/components/MkFolder.vue';
 import MkKeyValue from '@/components/MkKeyValue.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { AiScriptPluginMeta, parsePluginMeta, installPlugin } from '@/scripts/install-plugin.js';
 import { parseThemeCode, installTheme } from '@/scripts/install-theme.js';
 import { unisonReload } from '@/scripts/unison-reload.js';
@@ -159,7 +160,7 @@ async function fetch() {
 		uiPhase.value = 'error';
 		return;
 	}
-	const res = await os.api('fetch-external-resources', {
+	const res = await misskeyApi('fetch-external-resources', {
 		url: url.value,
 		hash: hash.value,
 	}).catch((err) => {
@@ -311,10 +312,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts._externalResourceInstaller.title,
 	icon: 'ph-download ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue
index 683a31c36d..4099e2bac9 100644
--- a/packages/frontend/src/pages/instance-info.vue
+++ b/packages/frontend/src/pages/instance-info.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -7,118 +7,123 @@ SPDX-License-Identifier: AGPL-3.0-only
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
 	<MkSpacer v-if="instance" :contentMax="600" :marginMin="16" :marginMax="32">
-		<div v-if="tab === 'overview'" class="_gaps_m">
-			<div class="fnfelxur">
-				<img :src="faviconUrl" alt="" class="icon"/>
-				<span class="name">{{ instance.name || `(${i18n.ts.unknown})` }}</span>
-			</div>
-			<div style="display: flex; flex-direction: column; gap: 1em;">
-				<MkKeyValue :copy="host" oneline>
-					<template #key>Host</template>
-					<template #value><span class="_monospace"><MkLink :url="`https://${host}`">{{ host }}</MkLink></span></template>
-				</MkKeyValue>
-				<MkKeyValue oneline>
-					<template #key>{{ i18n.ts.software }}</template>
-					<template #value><span class="_monospace">{{ instance.softwareName || `(${i18n.ts.unknown})` }} / {{ instance.softwareVersion || `(${i18n.ts.unknown})` }}</span></template>
-				</MkKeyValue>
-				<MkKeyValue oneline>
-					<template #key>{{ i18n.ts.administrator }}</template>
-					<template #value>{{ instance.maintainerName || `(${i18n.ts.unknown})` }} ({{ instance.maintainerEmail || `(${i18n.ts.unknown})` }})</template>
-				</MkKeyValue>
-			</div>
-			<MkKeyValue>
-				<template #key>{{ i18n.ts.description }}</template>
-				<template #value>{{ instance.description }}</template>
-			</MkKeyValue>
-
-			<FormSection v-if="iAmModerator">
-				<template #label>Moderation</template>
-				<div class="_gaps_s">
-					<MkSwitch v-model="suspended" :disabled="!instance" @update:modelValue="toggleSuspend">{{ i18n.ts.stopActivityDelivery }}</MkSwitch>
-					<MkSwitch v-model="isBlocked" :disabled="!meta || !instance" @update:modelValue="toggleBlock">{{ i18n.ts.blockThisInstance }}</MkSwitch>
-					<MkSwitch v-model="isSilenced" :disabled="!meta || !instance" @update:modelValue="toggleSilenced">{{ i18n.ts.silenceThisInstance }}</MkSwitch>
-					<MkSwitch v-model="isNSFW" :disabled="!instance" @update:modelValue="toggleNSFW">Mark as NSFW</MkSwitch>
-					<MkButton @click="refreshMetadata"><i class="ph-arrows-counter-clockwise ph-bold ph-lg"></i> Refresh metadata</MkButton>
+		<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
+			<div v-if="tab === 'overview'" key="overview" class="_gaps_m">
+				<div class="fnfelxur">
+					<img :src="faviconUrl" alt="" class="icon"/>
+					<span class="name">{{ instance.name || `(${i18n.ts.unknown})` }}</span>
 				</div>
-			</FormSection>
-
-			<FormSection>
-				<MkKeyValue oneline style="margin: 1em 0;">
-					<template #key>{{ i18n.ts.registeredAt }}</template>
-					<template #value><MkTime mode="detail" :time="instance.firstRetrievedAt"/></template>
-				</MkKeyValue>
-				<MkKeyValue oneline style="margin: 1em 0;">
-					<template #key>{{ i18n.ts.updatedAt }}</template>
-					<template #value><MkTime mode="detail" :time="instance.infoUpdatedAt"/></template>
-				</MkKeyValue>
-				<MkKeyValue oneline style="margin: 1em 0;">
-					<template #key>{{ i18n.ts.latestRequestReceivedAt }}</template>
-					<template #value><MkTime v-if="instance.latestRequestReceivedAt" mode="detail" :time="instance.latestRequestReceivedAt"/><span v-else>N/A</span></template>
-				</MkKeyValue>
-			</FormSection>
-
-			<FormSection>
-				<MkKeyValue oneline style="margin: 1em 0;">
-					<template #key>Following (Pub)</template>
-					<template #value>{{ number(instance.followingCount) }}</template>
-				</MkKeyValue>
-				<MkKeyValue oneline style="margin: 1em 0;">
-					<template #key>Followers (Sub)</template>
-					<template #value>{{ number(instance.followersCount) }}</template>
-				</MkKeyValue>
-			</FormSection>
-
-			<FormSection>
-				<template #label>Well-known resources</template>
-				<FormLink :to="`https://${host}/.well-known/host-meta`" external style="margin-bottom: 8px;">host-meta</FormLink>
-				<FormLink :to="`https://${host}/.well-known/host-meta.json`" external style="margin-bottom: 8px;">host-meta.json</FormLink>
-				<FormLink :to="`https://${host}/.well-known/nodeinfo`" external style="margin-bottom: 8px;">nodeinfo</FormLink>
-				<FormLink :to="`https://${host}/robots.txt`" external style="margin-bottom: 8px;">robots.txt</FormLink>
-				<FormLink :to="`https://${host}/manifest.json`" external style="margin-bottom: 8px;">manifest.json</FormLink>
-			</FormSection>
-		</div>
-		<div v-else-if="tab === 'chart'" class="_gaps_m">
-			<div class="cmhjzshl">
-				<div class="selects">
-					<MkSelect v-model="chartSrc" style="margin: 0 10px 0 0; flex: 1;">
-						<option value="instance-requests">{{ i18n.ts._instanceCharts.requests }}</option>
-						<option value="instance-users">{{ i18n.ts._instanceCharts.users }}</option>
-						<option value="instance-users-total">{{ i18n.ts._instanceCharts.usersTotal }}</option>
-						<option value="instance-notes">{{ i18n.ts._instanceCharts.notes }}</option>
-						<option value="instance-notes-total">{{ i18n.ts._instanceCharts.notesTotal }}</option>
-						<option value="instance-ff">{{ i18n.ts._instanceCharts.ff }}</option>
-						<option value="instance-ff-total">{{ i18n.ts._instanceCharts.ffTotal }}</option>
-						<option value="instance-drive-usage">{{ i18n.ts._instanceCharts.cacheSize }}</option>
-						<option value="instance-drive-usage-total">{{ i18n.ts._instanceCharts.cacheSizeTotal }}</option>
-						<option value="instance-drive-files">{{ i18n.ts._instanceCharts.files }}</option>
-						<option value="instance-drive-files-total">{{ i18n.ts._instanceCharts.filesTotal }}</option>
-					</MkSelect>
+				<div style="display: flex; flex-direction: column; gap: 1em;">
+					<MkKeyValue :copy="host" oneline>
+						<template #key>Host</template>
+						<template #value><span class="_monospace"><MkLink :url="`https://${host}`">{{ host }}</MkLink></span></template>
+					</MkKeyValue>
+					<MkKeyValue oneline>
+						<template #key>{{ i18n.ts.software }}</template>
+						<template #value><span class="_monospace">{{ instance.softwareName || `(${i18n.ts.unknown})` }} / {{ instance.softwareVersion || `(${i18n.ts.unknown})` }}</span></template>
+					</MkKeyValue>
+					<MkKeyValue oneline>
+						<template #key>{{ i18n.ts.administrator }}</template>
+						<template #value>{{ instance.maintainerName || `(${i18n.ts.unknown})` }} ({{ instance.maintainerEmail || `(${i18n.ts.unknown})` }})</template>
+					</MkKeyValue>
 				</div>
-				<div class="charts">
-					<div class="label">{{ i18n.t('recentNHours', { n: 90 }) }}</div>
-					<MkChart class="chart" :src="chartSrc" span="hour" :limit="90" :args="{ host: host }" :detailed="true"></MkChart>
-					<div class="label">{{ i18n.t('recentNDays', { n: 90 }) }}</div>
-					<MkChart class="chart" :src="chartSrc" span="day" :limit="90" :args="{ host: host }" :detailed="true"></MkChart>
+				<MkKeyValue>
+					<template #key>{{ i18n.ts.description }}</template>
+					<template #value>{{ instance.description }}</template>
+				</MkKeyValue>
+
+				<FormSection v-if="iAmModerator">
+					<template #label>Moderation</template>
+					<div class="_gaps_s">
+						<MkSwitch v-model="suspended" :disabled="!instance" @update:modelValue="toggleSuspend">{{ i18n.ts.stopActivityDelivery }}</MkSwitch>
+						<MkSwitch v-model="isBlocked" :disabled="!meta || !instance" @update:modelValue="toggleBlock">{{ i18n.ts.blockThisInstance }}</MkSwitch>
+						<MkSwitch v-model="isSilenced" :disabled="!meta || !instance" @update:modelValue="toggleSilenced">{{ i18n.ts.silenceThisInstance }}</MkSwitch>
+						<MkSwitch v-model="isNSFW" :disabled="!instance" @update:modelValue="toggleNSFW">Mark as NSFW</MkSwitch>
+						<MkButton @click="refreshMetadata"><i class="ph-arrows-clockwise ph-bold ph-lg"></i> Refresh metadata</MkButton>
+						<MkTextarea v-model="moderationNote" manualSave>
+							<template #label>{{ i18n.ts.moderationNote }}</template>
+						</MkTextarea>
+					</div>
+				</FormSection>
+
+				<FormSection>
+					<MkKeyValue oneline style="margin: 1em 0;">
+						<template #key>{{ i18n.ts.registeredAt }}</template>
+						<template #value><MkTime mode="detail" :time="instance.firstRetrievedAt"/></template>
+					</MkKeyValue>
+					<MkKeyValue oneline style="margin: 1em 0;">
+						<template #key>{{ i18n.ts.updatedAt }}</template>
+						<template #value><MkTime mode="detail" :time="instance.infoUpdatedAt"/></template>
+					</MkKeyValue>
+					<MkKeyValue oneline style="margin: 1em 0;">
+						<template #key>{{ i18n.ts.latestRequestReceivedAt }}</template>
+						<template #value><MkTime v-if="instance.latestRequestReceivedAt" :time="instance.latestRequestReceivedAt"/><span v-else>N/A</span></template>
+					</MkKeyValue>
+				</FormSection>
+
+				<FormSection>
+					<MkKeyValue oneline style="margin: 1em 0;">
+						<template #key>Following (Pub)</template>
+						<template #value>{{ number(instance.followingCount) }}</template>
+					</MkKeyValue>
+					<MkKeyValue oneline style="margin: 1em 0;">
+						<template #key>Followers (Sub)</template>
+						<template #value>{{ number(instance.followersCount) }}</template>
+					</MkKeyValue>
+				</FormSection>
+
+				<FormSection>
+					<template #label>Well-known resources</template>
+					<FormLink :to="`https://${host}/.well-known/host-meta`" external style="margin-bottom: 8px;">host-meta</FormLink>
+					<FormLink :to="`https://${host}/.well-known/host-meta.json`" external style="margin-bottom: 8px;">host-meta.json</FormLink>
+					<FormLink :to="`https://${host}/.well-known/nodeinfo`" external style="margin-bottom: 8px;">nodeinfo</FormLink>
+					<FormLink :to="`https://${host}/robots.txt`" external style="margin-bottom: 8px;">robots.txt</FormLink>
+					<FormLink :to="`https://${host}/manifest.json`" external style="margin-bottom: 8px;">manifest.json</FormLink>
+				</FormSection>
+			</div>
+			<div v-else-if="tab === 'chart'" key="chart" class="_gaps_m">
+				<div class="cmhjzshl">
+					<div class="selects">
+						<MkSelect v-model="chartSrc" style="margin: 0 10px 0 0; flex: 1;">
+							<option value="instance-requests">{{ i18n.ts._instanceCharts.requests }}</option>
+							<option value="instance-users">{{ i18n.ts._instanceCharts.users }}</option>
+							<option value="instance-users-total">{{ i18n.ts._instanceCharts.usersTotal }}</option>
+							<option value="instance-notes">{{ i18n.ts._instanceCharts.notes }}</option>
+							<option value="instance-notes-total">{{ i18n.ts._instanceCharts.notesTotal }}</option>
+							<option value="instance-ff">{{ i18n.ts._instanceCharts.ff }}</option>
+							<option value="instance-ff-total">{{ i18n.ts._instanceCharts.ffTotal }}</option>
+							<option value="instance-drive-usage">{{ i18n.ts._instanceCharts.cacheSize }}</option>
+							<option value="instance-drive-usage-total">{{ i18n.ts._instanceCharts.cacheSizeTotal }}</option>
+							<option value="instance-drive-files">{{ i18n.ts._instanceCharts.files }}</option>
+							<option value="instance-drive-files-total">{{ i18n.ts._instanceCharts.filesTotal }}</option>
+						</MkSelect>
+					</div>
+					<div class="charts">
+						<div class="label">{{ i18n.tsx.recentNHours({ n: 90 }) }}</div>
+						<MkChart class="chart" :src="chartSrc" span="hour" :limit="90" :args="{ host: host }" :detailed="true"></MkChart>
+						<div class="label">{{ i18n.tsx.recentNDays({ n: 90 }) }}</div>
+						<MkChart class="chart" :src="chartSrc" span="day" :limit="90" :args="{ host: host }" :detailed="true"></MkChart>
+					</div>
 				</div>
 			</div>
-		</div>
-		<div v-else-if="tab === 'users'" class="_gaps_m">
-			<MkPagination v-slot="{items}" :pagination="usersPagination" style="display: grid; grid-template-columns: repeat(auto-fill,minmax(270px,1fr)); grid-gap: 12px;">
-				<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" class="user" :to="`/admin/user/${user.id}`">
-					<MkUserCardMini :user="user"/>
-				</MkA>
-			</MkPagination>
-		</div>
-		<div v-else-if="tab === 'raw'" class="_gaps_m">
-			<MkObjectView tall :value="instance">
-			</MkObjectView>
-		</div>
+			<div v-else-if="tab === 'users'" key="users" class="_gaps_m">
+				<MkPagination v-slot="{items}" :pagination="usersPagination" style="display: grid; grid-template-columns: repeat(auto-fill,minmax(270px,1fr)); grid-gap: 12px;">
+					<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" class="user" :to="`/admin/user/${user.id}`">
+						<MkUserCardMini :user="user"/>
+					</MkA>
+				</MkPagination>
+			</div>
+			<div v-else-if="tab === 'raw'" key="raw" class="_gaps_m">
+				<MkObjectView tall :value="instance">
+				</MkObjectView>
+			</div>
+		</MkHorizontalSwipe>
 	</MkSpacer>
 </MkStickyContainer>
 </template>
 
 <script lang="ts" setup>
-import { ref, computed } from 'vue';
+import { ref, computed, watch } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkChart from '@/components/MkChart.vue';
 import MkObjectView from '@/components/MkObjectView.vue';
@@ -130,20 +135,24 @@ import MkKeyValue from '@/components/MkKeyValue.vue';
 import MkSelect from '@/components/MkSelect.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import number from '@/filters/number.js';
 import { iAmModerator, iAmAdmin } from '@/account.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { i18n } from '@/i18n.js';
 import MkUserCardMini from '@/components/MkUserCardMini.vue';
 import MkPagination from '@/components/MkPagination.vue';
+import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
 import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
 import { dateString } from '@/filters/date.js';
+import MkTextarea from '@/components/MkTextarea.vue';
 
 const props = defineProps<{
 	host: string;
 }>();
 
 const tab = ref('overview');
+
 const chartSrc = ref('instance-requests');
 const meta = ref<Misskey.entities.AdminMetaResponse | null>(null);
 const instance = ref<Misskey.entities.FederationInstance | null>(null);
@@ -152,6 +161,7 @@ const isBlocked = ref(false);
 const isSilenced = ref(false);
 const isNSFW = ref(false);
 const faviconUrl = ref<string | null>(null);
+const moderationNote = ref('');
 
 const usersPagination = {
 	endpoint: iAmModerator ? 'admin/show-users' : 'users' as const,
@@ -164,11 +174,15 @@ const usersPagination = {
 	offsetMode: true,
 };
 
+watch(moderationNote, async () => {
+	await misskeyApi('admin/federation/update-instance', { host: instance.value.host, moderationNote: moderationNote.value });
+});
+
 async function fetch(): Promise<void> {
 	if (iAmAdmin) {
-		meta.value = await os.api('admin/meta');
+		meta.value = await misskeyApi('admin/meta');
 	}
-	instance.value = await os.api('federation/show-instance', {
+	instance.value = await misskeyApi('federation/show-instance', {
 		host: props.host,
 	});
 	suspended.value = instance.value?.isSuspended ?? false;
@@ -176,13 +190,14 @@ async function fetch(): Promise<void> {
 	isSilenced.value = instance.value?.isSilenced ?? false;
 	isNSFW.value = instance.value?.isNSFW ?? false;
 	faviconUrl.value = getProxiedImageUrlNullable(instance.value?.faviconUrl, 'preview') ?? getProxiedImageUrlNullable(instance.value?.iconUrl, 'preview');
+	moderationNote.value = instance.value?.moderationNote;
 }
 
 async function toggleBlock(): Promise<void> {
 	if (!meta.value) throw new Error('No meta?');
 	if (!instance.value) throw new Error('No instance?');
 	const { host } = instance.value;
-	await os.api('admin/update-meta', {
+	await misskeyApi('admin/update-meta', {
 		blockedHosts: isBlocked.value ? meta.value.blockedHosts.concat([host]) : meta.value.blockedHosts.filter(x => x !== host),
 	});
 }
@@ -192,14 +207,14 @@ async function toggleSilenced(): Promise<void> {
 	if (!instance.value) throw new Error('No instance?');
 	const { host } = instance.value;
 	const silencedHosts = meta.value.silencedHosts ?? [];
-	await os.api('admin/update-meta', {
+	await misskeyApi('admin/update-meta', {
 		silencedHosts: isSilenced.value ? silencedHosts.concat([host]) : silencedHosts.filter(x => x !== host),
 	});
 }
 
 async function toggleSuspend(): Promise<void> {
 	if (!instance.value) throw new Error('No instance?');
-	await os.api('admin/federation/update-instance', {
+	await misskeyApi('admin/federation/update-instance', {
 		host: instance.value.host,
 		isSuspended: suspended.value,
 	});
@@ -207,7 +222,7 @@ async function toggleSuspend(): Promise<void> {
 
 async function toggleNSFW(): Promise<void> {
 	if (!instance.value) throw new Error('No instance?');
-	await os.api('admin/federation/update-instance', {
+	await misskeyApi('admin/federation/update-instance', {
 		host: instance.value.host,
 		isNSFW: isNSFW.value,
 	});
@@ -215,7 +230,7 @@ async function toggleNSFW(): Promise<void> {
 
 function refreshMetadata(): void {
 	if (!instance.value) throw new Error('No instance?');
-	os.api('admin/federation/refresh-remote-instance-metadata', {
+	misskeyApi('admin/federation/refresh-remote-instance-metadata', {
 		host: instance.value.host,
 	});
 	os.alert({
@@ -251,10 +266,10 @@ const headerTabs = computed(() => [{
 	icon: 'ph-code ph-bold ph-lg',
 }]);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: props.host,
 	icon: 'ph-hard-drives ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/pages/invite.vue b/packages/frontend/src/pages/invite.vue
index 6ac78a2068..b8c006eb77 100644
--- a/packages/frontend/src/pages/invite.vue
+++ b/packages/frontend/src/pages/invite.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -19,9 +19,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 	</MKSpacer>
 	<MkSpacer v-else :contentMax="800">
 		<div class="_gaps_m" style="text-align: center;">
-			<div v-if="resetCycle && inviteLimit">{{ i18n.t('inviteLimitResetCycle', { time: resetCycle, limit: inviteLimit }) }}</div>
+			<div v-if="resetCycle && inviteLimit">{{ i18n.tsx.inviteLimitResetCycle({ time: resetCycle, limit: inviteLimit }) }}</div>
 			<MkButton inline primary rounded :disabled="currentInviteLimit !== null && currentInviteLimit <= 0" @click="create"><i class="ph-user-plus ph-bold ph-lg"></i> {{ i18n.ts.createInviteCode }}</MkButton>
-			<div v-if="currentInviteLimit !== null">{{ i18n.t('createLimitRemaining', { limit: currentInviteLimit }) }}</div>
+			<div v-if="currentInviteLimit !== null">{{ i18n.tsx.createLimitRemaining({ limit: currentInviteLimit }) }}</div>
 
 			<MkPagination ref="pagingComponent" :pagination="pagination">
 				<template #default="{ items }">
@@ -40,6 +40,7 @@ import { computed, ref, shallowRef } from 'vue';
 import type * as Misskey from 'misskey-js';
 import { i18n } from '@/i18n.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import MkButton from '@/components/MkButton.vue';
 import MkPagination, { Paging } from '@/components/MkPagination.vue';
 import MkInviteCode from '@/components/MkInviteCode.vue';
@@ -68,7 +69,7 @@ const resetCycle = computed<null | string>(() => {
 });
 
 async function create() {
-	const ticket = await os.api('invite/create');
+	const ticket = await misskeyApi('invite/create');
 	os.alert({
 		type: 'success',
 		title: i18n.ts.inviteCodeCreated,
@@ -87,15 +88,15 @@ function deleted(id: string) {
 }
 
 async function update() {
-	currentInviteLimit.value = (await os.api('invite/limit')).remaining;
+	currentInviteLimit.value = (await misskeyApi('invite/limit')).remaining;
 }
 
 update();
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.invite,
 	icon: 'ph-user-plus ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/list.vue b/packages/frontend/src/pages/list.vue
index d6c6685a79..87070d9167 100644
--- a/packages/frontend/src/pages/list.vue
+++ b/packages/frontend/src/pages/list.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -37,6 +37,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { watch, computed, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { userPage } from '@/filters/user.js';
 import { i18n } from '@/i18n.js';
 import MkUserCardMini from '@/components/MkUserCardMini.vue';
@@ -53,12 +54,12 @@ const error = ref();
 const users = ref<Misskey.entities.UserDetailed[]>([]);
 
 function fetchList(): void {
-	os.api('users/lists/show', {
+	misskeyApi('users/lists/show', {
 		listId: props.listId,
 		forPublic: true,
 	}).then(_list => {
 		list.value = _list;
-		os.api('users/show', {
+		misskeyApi('users/show', {
 			userIds: list.value.userIds,
 		}).then(_users => {
 			users.value = _users;
@@ -100,10 +101,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => list.value ? {
-	title: list.value.name,
+definePageMetadata(() => ({
+	title: list.value ? list.value.name : i18n.ts.lists,
 	icon: 'ph-list ph-bold ph-lg',
-} : null));
+}));
 </script>
 <style lang="scss" module>
 .main {
diff --git a/packages/frontend/src/pages/miauth.vue b/packages/frontend/src/pages/miauth.vue
index 2b53b67ab3..4812bfe70f 100644
--- a/packages/frontend/src/pages/miauth.vue
+++ b/packages/frontend/src/pages/miauth.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -20,13 +20,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</div>
 			<div v-else>
 				<div v-if="_permissions.length > 0">
-					<p v-if="name">{{ i18n.t('_auth.permission', { name }) }}</p>
+					<p v-if="name">{{ i18n.tsx._auth.permission({ name }) }}</p>
 					<p v-else>{{ i18n.ts._auth.permissionAsk }}</p>
 					<ul>
-						<li v-for="p in _permissions" :key="p">{{ i18n.t(`_permissions.${p}`) }}</li>
+						<li v-for="p in _permissions" :key="p">{{ i18n.ts._permissions[p] }}</li>
 					</ul>
 				</div>
-				<div v-if="name">{{ i18n.t('_auth.shareAccess', { name }) }}</div>
+				<div v-if="name">{{ i18n.tsx._auth.shareAccess({ name }) }}</div>
 				<div v-else>{{ i18n.ts._auth.shareAccessAsk }}</div>
 				<div :class="$style.buttons">
 					<MkButton inline @click="deny">{{ i18n.ts.cancel }}</MkButton>
@@ -46,7 +46,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { ref, computed } from 'vue';
 import MkSignin from '@/components/MkSignin.vue';
 import MkButton from '@/components/MkButton.vue';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { $i, login } from '@/account.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
@@ -65,7 +65,7 @@ const state = ref<string | null>(null);
 
 async function accept(): Promise<void> {
 	state.value = 'waiting';
-	await os.api('miauth/gen-token', {
+	await misskeyApi('miauth/gen-token', {
 		session: props.session,
 		name: props.name,
 		iconUrl: props.icon,
@@ -93,10 +93,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: 'MiAuth',
 	icon: 'ph-squares-four ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/my-antennas/create.vue b/packages/frontend/src/pages/my-antennas/create.vue
index 79b592dada..f511c48a06 100644
--- a/packages/frontend/src/pages/my-antennas/create.vue
+++ b/packages/frontend/src/pages/my-antennas/create.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -14,8 +14,8 @@ import { ref } from 'vue';
 import XAntenna from './editor.vue';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { useRouter } from '@/router.js';
 import { antennasCache } from '@/cache.js';
+import { useRouter } from '@/router/supplier.js';
 
 const router = useRouter();
 
@@ -38,8 +38,8 @@ function onAntennaCreated() {
 	router.push('/my/antennas');
 }
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.manageAntennas,
 	icon: 'ph-flying-saucer ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/my-antennas/edit.vue b/packages/frontend/src/pages/my-antennas/edit.vue
index 851b32527c..a262e932f3 100644
--- a/packages/frontend/src/pages/my-antennas/edit.vue
+++ b/packages/frontend/src/pages/my-antennas/edit.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -13,11 +13,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import XAntenna from './editor.vue';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
-import { useRouter } from '@/router.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { antennasCache } from '@/cache.js';
+import { useRouter } from '@/router/supplier.js';
 
 const router = useRouter();
 
@@ -32,12 +32,12 @@ function onAntennaUpdated() {
 	router.push('/my/antennas');
 }
 
-os.api('antennas/show', { antennaId: props.antennaId }).then((antennaResponse) => {
+misskeyApi('antennas/show', { antennaId: props.antennaId }).then((antennaResponse) => {
 	antenna.value = antennaResponse;
 });
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.manageAntennas,
 	icon: 'ph-flying-saucer ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/my-antennas/editor.vue b/packages/frontend/src/pages/my-antennas/editor.vue
index 0fc7f862a3..2d29e2d375 100644
--- a/packages/frontend/src/pages/my-antennas/editor.vue
+++ b/packages/frontend/src/pages/my-antennas/editor.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -57,6 +57,7 @@ import MkTextarea from '@/components/MkTextarea.vue';
 import MkSelect from '@/components/MkSelect.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 
 const props = defineProps<{
@@ -84,7 +85,7 @@ const userLists = ref<Misskey.entities.UserList[] | null>(null);
 
 watch(() => src.value, async () => {
 	if (src.value === 'list' && userLists.value === null) {
-		userLists.value = await os.api('users/lists/list');
+		userLists.value = await misskeyApi('users/lists/list');
 	}
 });
 
@@ -115,11 +116,11 @@ async function saveAntenna() {
 async function deleteAntenna() {
 	const { canceled } = await os.confirm({
 		type: 'warning',
-		text: i18n.t('removeAreYouSure', { x: props.antenna.name }),
+		text: i18n.tsx.removeAreYouSure({ x: props.antenna.name }),
 	});
 	if (canceled) return;
 
-	await os.api('antennas/delete', {
+	await misskeyApi('antennas/delete', {
 		antennaId: props.antenna.id,
 	});
 
@@ -128,7 +129,7 @@ async function deleteAntenna() {
 }
 
 function addUser() {
-	os.selectUser().then(user => {
+	os.selectUser({ includeSelf: true }).then(user => {
 		users.value = users.value.trim();
 		users.value += '\n@' + Misskey.acct.toString(user as any);
 		users.value = users.value.trim();
diff --git a/packages/frontend/src/pages/my-antennas/index.vue b/packages/frontend/src/pages/my-antennas/index.vue
index b46fb7a5d7..a312672f74 100644
--- a/packages/frontend/src/pages/my-antennas/index.vue
+++ b/packages/frontend/src/pages/my-antennas/index.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -55,10 +55,10 @@ const headerActions = computed(() => [{
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.manageAntennas,
 	icon: 'ph-flying-saucer ph-bold ph-lg',
-});
+}));
 
 onActivated(() => {
 	antennasCache.fetch();
diff --git a/packages/frontend/src/pages/my-clips/index.vue b/packages/frontend/src/pages/my-clips/index.vue
index d787e53bb0..f46ea0e0ea 100644
--- a/packages/frontend/src/pages/my-clips/index.vue
+++ b/packages/frontend/src/pages/my-clips/index.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -7,20 +7,22 @@ SPDX-License-Identifier: AGPL-3.0-only
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
 	<MkSpacer :contentMax="700">
-		<div v-if="tab === 'my'" class="_gaps">
-			<MkButton primary rounded class="add" @click="create"><i class="ph-plus ph-bold ph-lg"></i> {{ i18n.ts.add }}</MkButton>
+		<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
+			<div v-if="tab === 'my'" key="my" class="_gaps">
+				<MkButton primary rounded class="add" @click="create"><i class="ph-plus ph-bold ph-lg"></i> {{ i18n.ts.add }}</MkButton>
 
-			<MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination" class="_gaps">
-				<MkA v-for="item in items" :key="item.id" :to="`/clips/${item.id}`">
+				<MkPagination v-slot="{items}" ref="pagingComponent" :pagination="pagination" class="_gaps">
+					<MkA v-for="item in items" :key="item.id" :to="`/clips/${item.id}`">
+						<MkClipPreview :clip="item"/>
+					</MkA>
+				</MkPagination>
+			</div>
+			<div v-else-if="tab === 'favorites'" key="favorites" class="_gaps">
+				<MkA v-for="item in favorites" :key="item.id" :to="`/clips/${item.id}`">
 					<MkClipPreview :clip="item"/>
 				</MkA>
-			</MkPagination>
-		</div>
-		<div v-else-if="tab === 'favorites'" class="_gaps">
-			<MkA v-for="item in favorites" :key="item.id" :to="`/clips/${item.id}`">
-				<MkClipPreview :clip="item"/>
-			</MkA>
-		</div>
+			</div>
+		</MkHorizontalSwipe>
 	</MkSpacer>
 </MkStickyContainer>
 </template>
@@ -32,9 +34,11 @@ import MkPagination from '@/components/MkPagination.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkClipPreview from '@/components/MkClipPreview.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { clipsCache } from '@/cache.js';
+import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
 
 const pagination = {
 	endpoint: 'clips/list' as const,
@@ -43,12 +47,13 @@ const pagination = {
 };
 
 const tab = ref('my');
+
 const favorites = ref<Misskey.entities.Clip[] | null>(null);
 
 const pagingComponent = shallowRef<InstanceType<typeof MkPagination>>();
 
 watch(tab, async () => {
-	favorites.value = await os.api('clips/my-favorites');
+	favorites.value = await misskeyApi('clips/my-favorites');
 });
 
 async function create() {
@@ -99,14 +104,10 @@ const headerTabs = computed(() => [{
 	icon: 'ph-heart ph-bold ph-lg',
 }]);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.clip,
 	icon: 'ph-paperclip ph-bold ph-lg',
-	action: {
-		icon: 'ph-plus ph-bold ph-lg',
-		handler: create,
-	},
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/my-lists/index.vue b/packages/frontend/src/pages/my-lists/index.vue
index 3379cf43d4..f2469be8de 100644
--- a/packages/frontend/src/pages/my-lists/index.vue
+++ b/packages/frontend/src/pages/my-lists/index.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 			<div v-if="items.length > 0" class="_gaps">
 				<MkA v-for="list in items" :key="list.id" class="_panel" :class="$style.list" :to="`/my/lists/${ list.id }`">
-					<div style="margin-bottom: 4px;">{{ list.name }} <span :class="$style.nUsers">({{ i18n.t('nUsers', { n: `${list.userIds.length}/${$i?.policies['userEachUserListsLimit']}` }) }})</span></div>
+					<div style="margin-bottom: 4px;">{{ list.name }} <span :class="$style.nUsers">({{ i18n.tsx.nUsers({ n: `${list.userIds.length}/${$i.policies['userEachUserListsLimit']}` }) }})</span></div>
 					<MkAvatars :userIds="list.userIds" :limit="10"/>
 				</MkA>
 			</div>
@@ -37,7 +37,9 @@ import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { userListsCache } from '@/cache.js';
 import { infoImageUrl } from '@/instance.js';
-import { $i } from '@/account.js';
+import { signinRequired } from '@/account.js';
+
+const $i = signinRequired();
 
 const items = computed(() => userListsCache.value.value ?? []);
 
@@ -69,10 +71,10 @@ const headerActions = computed(() => [{
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.manageLists,
 	icon: 'ph-list ph-bold ph-lg',
-});
+}));
 
 onActivated(() => {
 	fetch();
diff --git a/packages/frontend/src/pages/my-lists/list.vue b/packages/frontend/src/pages/my-lists/list.vue
index df9cdb0fce..1b7aa3f938 100644
--- a/packages/frontend/src/pages/my-lists/list.vue
+++ b/packages/frontend/src/pages/my-lists/list.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -25,7 +25,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 			<MkFolder defaultOpen>
 				<template #label>{{ i18n.ts.members }}</template>
-				<template #caption>{{ i18n.t('nUsers', { n: `${list.userIds.length}/${$i?.policies['userEachUserListsLimit']}` }) }}</template>
+				<template #caption>{{ i18n.tsx.nUsers({ n: `${list.userIds.length}/${$i.policies['userEachUserListsLimit']}` }) }}</template>
 
 				<div class="_gaps_s">
 					<MkButton rounded primary style="margin: 0 auto;" @click="addUser()">{{ i18n.ts.addUser }}</MkButton>
@@ -57,7 +57,7 @@ import { computed, ref, watch } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
-import { mainRouter } from '@/router.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { i18n } from '@/i18n.js';
 import { userPage } from '@/filters/user.js';
@@ -66,9 +66,12 @@ import MkSwitch from '@/components/MkSwitch.vue';
 import MkFolder from '@/components/MkFolder.vue';
 import MkInput from '@/components/MkInput.vue';
 import { userListsCache } from '@/cache.js';
-import { $i } from '@/account.js';
+import { signinRequired } from '@/account.js';
 import { defaultStore } from '@/store.js';
 import MkPagination from '@/components/MkPagination.vue';
+import { mainRouter } from '@/router/main.js';
+
+const $i = signinRequired();
 
 const {
 	enableInfiniteScroll,
@@ -91,7 +94,7 @@ const membershipsPagination = {
 };
 
 function fetchList() {
-	os.api('users/lists/show', {
+	misskeyApi('users/lists/show', {
 		listId: props.listId,
 	}).then(_list => {
 		list.value = _list;
@@ -119,7 +122,7 @@ async function removeUser(item, ev) {
 		danger: true,
 		action: async () => {
 			if (!list.value) return;
-			os.api('users/lists/pull', {
+			misskeyApi('users/lists/pull', {
 				listId: list.value.id,
 				userId: item.userId,
 			}).then(() => {
@@ -134,7 +137,7 @@ async function showMembershipMenu(item, ev) {
 		text: item.withReplies ? i18n.ts.hideRepliesToOthersInTimeline : i18n.ts.showRepliesToOthersInTimeline,
 		icon: item.withReplies ? 'ph-envelope-open ph-bold ph-lg' : 'ph-envelope ph-bold ph-lg',
 		action: async () => {
-			os.api('users/lists/update-membership', {
+			misskeyApi('users/lists/update-membership', {
 				listId: list.value.id,
 				userId: item.userId,
 				withReplies: !item.withReplies,
@@ -152,7 +155,7 @@ async function deleteList() {
 	if (!list.value) return;
 	const { canceled } = await os.confirm({
 		type: 'warning',
-		text: i18n.t('removeAreYouSure', { x: list.value.name }),
+		text: i18n.tsx.removeAreYouSure({ x: list.value.name }),
 	});
 	if (canceled) return;
 
@@ -183,10 +186,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => list.value ? {
-	title: list.value.name,
+definePageMetadata(() => ({
+	title: list.value ? list.value.name : i18n.ts.lists,
 	icon: 'ph-list ph-bold ph-lg',
-} : null));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/not-found.vue b/packages/frontend/src/pages/not-found.vue
index e8ba31395e..6f69f9285d 100644
--- a/packages/frontend/src/pages/not-found.vue
+++ b/packages/frontend/src/pages/not-found.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -31,8 +31,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.notFound,
 	icon: 'ph-warning ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/note.vue b/packages/frontend/src/pages/note.vue
index a98a7bde2c..6ccb5b61e5 100644
--- a/packages/frontend/src/pages/note.vue
+++ b/packages/frontend/src/pages/note.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -11,11 +11,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<Transition :name="defaultStore.state.animation ? 'fade' : ''" mode="out-in">
 				<div v-if="note">
 					<div v-if="showNext" class="_margin">
-						<MkNotes class="" :pagination="nextPagination" :noGap="true" :disableAutoLoad="true"/>
+						<MkNotes class="" :pagination="showNext === 'channel' ? nextChannelPagination : nextUserPagination" :noGap="true" :disableAutoLoad="true"/>
 					</div>
 
 					<div class="_margin">
-						<MkButton v-if="!showNext" :class="$style.loadNext" @click="showNext = true"><i class="ph-caret-up ph-bold ph-lg"></i></MkButton>
+						<div v-if="!showNext" class="_buttons" :class="$style.loadNext">
+							<MkButton v-if="note.channelId" rounded :class="$style.loadButton" @click="showNext = 'channel'"><i class="ph-caret-up ph-bold ph-lg"></i> <i class="ph-television-simple ph-bold ph-lg"></i></MkButton>
+							<MkButton rounded :class="$style.loadButton" @click="showNext = 'user'"><i class="ph-caret-up ph-bold ph-lg"></i> <i class="ph-user ph-bold ph-lg"></i></MkButton>
+						</div>
 						<div v-if="defaultStore.state.noteDesign === 'misskey'" class="_margin _gaps_s">
 							<MkRemoteCaution v-if="note.user.host != null" :href="note.url ?? note.uri"/>
 							<MkNoteDetailed :key="note.id" v-model:note="note" :class="$style.note" :expandAllCws="expandAllCws"/>
@@ -32,11 +35,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 								</MkA>
 							</div>
 						</div>
-						<MkButton v-if="!showPrev" :class="$style.loadPrev" @click="showPrev = true"><i class="ph-caret-down ph-bold ph-lg"></i></MkButton>
+						<div v-if="!showPrev" class="_buttons" :class="$style.loadPrev">
+							<MkButton v-if="note.channelId" rounded :class="$style.loadButton" @click="showPrev = 'channel'"><i class="ph-caret-down ph-bold ph-lg"></i> <i class="ph-television-simple ph-bold ph-lg"></i></MkButton>
+							<MkButton rounded :class="$style.loadButton" @click="showPrev = 'user'"><i class="ph-caret-down ph-bold ph-lg"></i> <i class="ph-user ph-bold ph-lg"></i></MkButton>
+						</div>
 					</div>
 
 					<div v-if="showPrev" class="_margin">
-						<MkNotes class="" :pagination="prevPagination" :noGap="true"/>
+						<MkNotes class="" :pagination="showPrev === 'channel' ? prevChannelPagination : prevUserPagination" :noGap="true"/>
 					</div>
 				</div>
 				<MkError v-else-if="error" @retry="fetchNote()"/>
@@ -50,12 +56,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { computed, watch, ref } from 'vue';
 import * as Misskey from 'misskey-js';
+import type { Paging } from '@/components/MkPagination.vue';
 import MkNoteDetailed from '@/components/MkNoteDetailed.vue';
 import MkNotes from '@/components/MkNotes.vue';
 import SkNoteDetailed from '@/components/SkNoteDetailed.vue';
 import MkRemoteCaution from '@/components/MkRemoteCaution.vue';
 import MkButton from '@/components/MkButton.vue';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { i18n } from '@/i18n.js';
 import { dateString } from '@/filters/date.js';
@@ -68,41 +75,60 @@ const props = defineProps<{
 
 const note = ref<null | Misskey.entities.Note>();
 const clips = ref<Misskey.entities.Clip[]>();
-const showPrev = ref(false);
-const showNext = ref(false);
+const showPrev = ref<'user' | 'channel' | false>(false);
+const showNext = ref<'user' | 'channel' | false>(false);
 const expandAllCws = ref(false);
 const error = ref();
 
-const prevPagination = {
-	endpoint: 'users/notes' as const,
+const prevUserPagination: Paging = {
+	endpoint: 'users/notes',
 	limit: 10,
 	params: computed(() => note.value ? ({
 		userId: note.value.userId,
 		untilId: note.value.id,
-	}) : null),
+	}) : undefined),
 };
 
-const nextPagination = {
+const nextUserPagination: Paging = {
 	reversed: true,
-	endpoint: 'users/notes' as const,
+	endpoint: 'users/notes',
 	limit: 10,
 	params: computed(() => note.value ? ({
 		userId: note.value.userId,
 		sinceId: note.value.id,
-	}) : null),
+	}) : undefined),
+};
+
+const prevChannelPagination: Paging = {
+	endpoint: 'channels/timeline',
+	limit: 10,
+	params: computed(() => note.value ? ({
+		channelId: note.value.channelId,
+		untilId: note.value.id,
+	}) : undefined),
+};
+
+const nextChannelPagination: Paging = {
+	reversed: true,
+	endpoint: 'channels/timeline',
+	limit: 10,
+	params: computed(() => note.value ? ({
+		channelId: note.value.channelId,
+		sinceId: note.value.id,
+	}) : undefined),
 };
 
 function fetchNote() {
 	showPrev.value = false;
 	showNext.value = false;
 	note.value = null;
-	os.api('notes/show', {
+	misskeyApi('notes/show', {
 		noteId: props.noteId,
 	}).then(res => {
 		note.value = res;
 		// 古いノートは被クリップ数をカウントしていないので、2023-10-01以前のものは強制的にnotes/clipsを叩く
 		if (note.value.clippedCount > 0 || new Date(note.value.createdAt).getTime() < new Date('2023-10-01').getTime()) {
-			os.api('notes/clips', {
+			misskeyApi('notes/clips', {
 				noteId: note.value.id,
 			}).then((_clips) => {
 				clips.value = _clips;
@@ -127,16 +153,18 @@ const headerActions = computed(() => note.value ? [
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => note.value ? {
+definePageMetadata(() => ({
 	title: i18n.ts.note,
-	subtitle: dateString(note.value.createdAt),
-	avatar: note.value.user,
-	path: `/notes/${note.value.id}`,
-	share: {
-		title: i18n.t('noteOf', { user: note.value.user.name }),
-		text: note.value.text,
-	},
-} : null));
+	...note.value ? {
+		subtitle: dateString(note.value.createdAt),
+		avatar: note.value.user,
+		path: `/notes/${note.value.id}`,
+		share: {
+			title: i18n.tsx.noteOf({ user: note.value.user.name }),
+			text: note.value.text,
+		},
+	} : {},
+}));
 </script>
 
 <style lang="scss" module>
@@ -151,9 +179,7 @@ definePageMetadata(computed(() => note.value ? {
 
 .loadNext,
 .loadPrev {
-	min-width: 0;
-	margin: 0 auto;
-	border-radius: var(--radius-ellipse);
+	justify-content: center;
 }
 
 .loadNext {
@@ -164,6 +190,10 @@ definePageMetadata(computed(() => note.value ? {
 	margin-top: var(--margin);
 }
 
+.loadButton {
+	min-width: 0;
+}
+
 .note {
 	border-radius: var(--radius);
 	background: var(--panel);
diff --git a/packages/frontend/src/pages/notifications.vue b/packages/frontend/src/pages/notifications.vue
index f3fadf5c8e..9cbb6323a8 100644
--- a/packages/frontend/src/pages/notifications.vue
+++ b/packages/frontend/src/pages/notifications.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -7,15 +7,17 @@ SPDX-License-Identifier: AGPL-3.0-only
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
 	<MkSpacer :contentMax="800">
-		<div v-if="tab === 'all'">
-			<XNotifications class="notifications" :excludeTypes="excludeTypes"/>
-		</div>
-		<div v-else-if="tab === 'mentions'">
-			<MkNotes :pagination="mentionsPagination"/>
-		</div>
-		<div v-else-if="tab === 'directNotes'">
-			<MkNotes :pagination="directNotesPagination"/>
-		</div>
+		<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
+			<div v-if="tab === 'all'" key="all">
+				<XNotifications :class="$style.notifications" :excludeTypes="excludeTypes"/>
+			</div>
+			<div v-else-if="tab === 'mentions'" key="mention">
+				<MkNotes :pagination="mentionsPagination"/>
+			</div>
+			<div v-else-if="tab === 'directNotes'" key="directNotes">
+				<MkNotes :pagination="directNotesPagination"/>
+			</div>
+		</MkHorizontalSwipe>
 	</MkSpacer>
 </MkStickyContainer>
 </template>
@@ -24,6 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed, ref } from 'vue';
 import XNotifications from '@/components/MkNotifications.vue';
 import MkNotes from '@/components/MkNotes.vue';
+import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
@@ -48,8 +51,8 @@ const directNotesPagination = {
 
 function setFilter(ev) {
 	const typeItems = notificationTypes.map(t => ({
-		text: i18n.t(`_notification._types.${t}`),
-		active: includeTypes.value && includeTypes.value.includes(t),
+		text: i18n.ts._notification._types[t],
+		active: (includeTypes.value && includeTypes.value.includes(t)) ?? false,
 		action: () => {
 			includeTypes.value = [t];
 		},
@@ -60,7 +63,7 @@ function setFilter(ev) {
 		action: () => {
 			includeTypes.value = null;
 		},
-	}, { type: 'divider' }, ...typeItems] : typeItems;
+	}, { type: 'divider' as const }, ...typeItems] : typeItems;
 	os.popupMenu(items, ev.currentTarget ?? ev.target);
 }
 
@@ -91,8 +94,15 @@ const headerTabs = computed(() => [{
 	icon: 'ph-envelope ph-bold ph-lg',
 }]);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts.notifications,
 	icon: 'ph-bell ph-bold ph-lg',
-})));
+}));
 </script>
+
+<style module lang="scss">
+.notifications {
+	border-radius: var(--radius);
+	overflow: clip;
+}
+</style>
diff --git a/packages/frontend/src/pages/oauth.vue b/packages/frontend/src/pages/oauth.vue
index 53b609e0bd..80b6a237ea 100644
--- a/packages/frontend/src/pages/oauth.vue
+++ b/packages/frontend/src/pages/oauth.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -9,13 +9,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<MkSpacer :contentMax="800">
 		<div v-if="$i">
 			<div v-if="permissions.length > 0">
-				<p v-if="name">{{ i18n.t('_auth.permission', { name }) }}</p>
+				<p v-if="name">{{ i18n.tsx._auth.permission({ name }) }}</p>
 				<p v-else>{{ i18n.ts._auth.permissionAsk }}</p>
 				<ul>
-					<li v-for="p in permissions" :key="p">{{ i18n.t(`_permissions.${p}`) }}</li>
+					<li v-for="p in permissions" :key="p">{{ i18n.ts._permissions[p] }}</li>
 				</ul>
 			</div>
-			<div v-if="name">{{ i18n.t('_auth.shareAccess', { name }) }}</div>
+			<div v-if="name">{{ i18n.tsx._auth.shareAccess({ name }) }}</div>
 			<div v-else>{{ i18n.ts._auth.shareAccessAsk }}</div>
 			<form :class="$style.buttons" action="/oauth/decision" accept-charset="utf-8" method="post">
 				<input name="login_token" type="hidden" :value="$i.token"/>
@@ -51,10 +51,10 @@ function onLogin(res): void {
 	login(res.i);
 }
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: 'OAuth',
 	icon: 'ph-squares-four ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue
index 459454a9be..2a55c083d1 100644
--- a/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue
+++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -26,6 +26,7 @@ import * as Misskey from 'misskey-js';
 import XContainer from '../page-editor.container.vue';
 import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 
 const props = defineProps<{
@@ -52,7 +53,7 @@ onMounted(async () => {
 	if (props.modelValue.fileId == null) {
 		await choose();
 	} else {
-		os.api('drive/files/show', {
+		misskeyApi('drive/files/show', {
 			fileId: props.modelValue.fileId,
 		}).then(fileResponse => {
 			file.value = fileResponse;
diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue
index 442558cc2a..978d03c1cd 100644
--- a/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue
+++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -30,7 +30,7 @@ import MkInput from '@/components/MkInput.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkNote from '@/components/MkNote.vue';
 import MkNoteDetailed from '@/components/MkNoteDetailed.vue';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 
 const props = defineProps<{
@@ -53,7 +53,7 @@ watch(id, async () => {
 		...props.modelValue,
 		note: id.value,
 	});
-	note.value = await os.api('notes/show', { noteId: id.value });
+	note.value = await misskeyApi('notes/show', { noteId: id.value });
 }, {
 	immediate: true,
 });
diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.section.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.section.vue
index 885fa55bc9..2e4402085c 100644
--- a/packages/frontend/src/pages/page-editor/els/page-editor.el.section.vue
+++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.section.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<template #header><i class="ph-note ph-bold ph-lg"></i> {{ props.modelValue.title }}</template>
 	<template #func>
 		<button class="_button" @click="rename()">
-			<i class="ph-pencil ph-bold ph-lg"></i>
+			<i class="ph-pencil-simple ph-bold ph-lg"></i>
 		</button>
 	</template>
 
diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue
index 2af4e4e365..e83ad058b4 100644
--- a/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue
+++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/pages/page-editor/page-editor.blocks.vue b/packages/frontend/src/pages/page-editor/page-editor.blocks.vue
index 52220d36bb..4967e73000 100644
--- a/packages/frontend/src/pages/page-editor/page-editor.blocks.vue
+++ b/packages/frontend/src/pages/page-editor/page-editor.blocks.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/pages/page-editor/page-editor.container.vue b/packages/frontend/src/pages/page-editor/page-editor.container.vue
index 71fa890f63..7ef66c1c0c 100644
--- a/packages/frontend/src/pages/page-editor/page-editor.container.vue
+++ b/packages/frontend/src/pages/page-editor/page-editor.container.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/pages/page-editor/page-editor.vue b/packages/frontend/src/pages/page-editor/page-editor.vue
index 8c4696b04b..92de9d57a5 100644
--- a/packages/frontend/src/pages/page-editor/page-editor.vue
+++ b/packages/frontend/src/pages/page-editor/page-editor.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -70,11 +70,12 @@ import MkSelect from '@/components/MkSelect.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkInput from '@/components/MkInput.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { selectFile } from '@/scripts/select-file.js';
-import { mainRouter } from '@/router.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { $i } from '@/account.js';
+import { mainRouter } from '@/router/main.js';
 
 const props = defineProps<{
 	initPageId?: string;
@@ -105,7 +106,7 @@ watch(eyeCatchingImageId, async () => {
 	if (eyeCatchingImageId.value == null) {
 		eyeCatchingImage.value = null;
 	} else {
-		eyeCatchingImage.value = await os.api('drive/files/show', {
+		eyeCatchingImage.value = await misskeyApi('drive/files/show', {
 			fileId: eyeCatchingImageId.value,
 		});
 	}
@@ -148,7 +149,7 @@ function save() {
 
 	if (pageId.value) {
 		options.pageId = pageId.value;
-		os.api('pages/update', options)
+		misskeyApi('pages/update', options)
 			.then(page => {
 				currentName.value = name.value.trim();
 				os.alert({
@@ -157,7 +158,7 @@ function save() {
 				});
 			}).catch(onError);
 	} else {
-		os.api('pages/create', options)
+		misskeyApi('pages/create', options)
 			.then(created => {
 				pageId.value = created.id;
 				currentName.value = name.value.trim();
@@ -173,10 +174,10 @@ function save() {
 function del() {
 	os.confirm({
 		type: 'warning',
-		text: i18n.t('removeAreYouSure', { x: title.value.trim() }),
+		text: i18n.tsx.removeAreYouSure({ x: title.value.trim() }),
 	}).then(({ canceled }) => {
 		if (canceled) return;
-		os.api('pages/delete', {
+		misskeyApi('pages/delete', {
 			pageId: pageId.value,
 		}).then(() => {
 			os.alert({
@@ -191,7 +192,7 @@ function del() {
 function duplicate() {
 	title.value = title.value + ' - copy';
 	name.value = name.value + '-copy';
-	os.api('pages/create', getSaveOptions()).then(created => {
+	misskeyApi('pages/create', getSaveOptions()).then(created => {
 		pageId.value = created.id;
 		currentName.value = name.value.trim();
 		os.alert({
@@ -235,11 +236,11 @@ function removeEyeCatchingImage() {
 
 async function init() {
 	if (props.initPageId) {
-		page.value = await os.api('pages/show', {
+		page.value = await misskeyApi('pages/show', {
 			pageId: props.initPageId,
 		});
 	} else if (props.initPageName && props.initUser) {
-		page.value = await os.api('pages/show', {
+		page.value = await misskeyApi('pages/show', {
 			name: props.initPageName,
 			username: props.initUser,
 		});
@@ -282,17 +283,11 @@ const headerTabs = computed(() => [{
 	icon: 'ph-note ph-bold ph-lg',
 }]);
 
-definePageMetadata(computed(() => {
-	let title = i18n.ts._pages.newPage;
-	if (props.initPageId) {
-		title = i18n.ts._pages.editPage;
-	} else if (props.initPageName && props.initUser) {
-		title = i18n.ts._pages.readPage;
-	}
-	return {
-		title: title,
-		icon: 'ph-pencil ph-bold ph-lg',
-	};
+definePageMetadata(() => ({
+	title: props.initPageId ? i18n.ts._pages.editPage
+				: props.initPageName && props.initUser ? i18n.ts._pages.readPage
+				: i18n.ts._pages.newPage,
+	icon: 'ph-pencil-simple ph-bold ph-lg',
 }));
 </script>
 
diff --git a/packages/frontend/src/pages/page.vue b/packages/frontend/src/pages/page.vue
index 6b06da9a24..dc47f20bee 100644
--- a/packages/frontend/src/pages/page.vue
+++ b/packages/frontend/src/pages/page.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -81,6 +81,7 @@ import * as Misskey from 'misskey-js';
 import XPage from '@/components/page/page.vue';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { url } from '@/config.js';
 import MkMediaImage from '@/components/MkMediaImage.vue';
 import MkFollowButton from '@/components/MkFollowButton.vue';
@@ -113,7 +114,7 @@ const path = computed(() => props.username + '/' + props.pageName);
 
 function fetchPage() {
 	page.value = null;
-	os.api('pages/show', {
+	misskeyApi('pages/show', {
 		name: props.pageName,
 		username: props.username,
 	}).then(async _page => {
@@ -186,15 +187,17 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => page.value ? {
-	title: page.value.title || page.value.name,
-	avatar: page.value.user,
-	path: `/@${page.value.user.username}/pages/${page.value.name}`,
-	share: {
-		title: page.value.title || page.value.name,
-		text: page.value.summary,
-	},
-} : null));
+definePageMetadata(() => ({
+	title: page.value ? page.value.title || page.value.name : i18n.ts.pages,
+	...page.value ? {
+		avatar: page.value.user,
+		path: `/@${page.value.user.username}/pages/${page.value.name}`,
+		share: {
+			title: page.value.title || page.value.name,
+			text: page.value.summary,
+		},
+	} : {},
+}));
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/pages/pages.vue b/packages/frontend/src/pages/pages.vue
index a7ca433ed3..7b4dd83068 100644
--- a/packages/frontend/src/pages/pages.vue
+++ b/packages/frontend/src/pages/pages.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -7,30 +7,32 @@ SPDX-License-Identifier: AGPL-3.0-only
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
 	<MkSpacer :contentMax="700">
-		<div v-if="tab === 'featured'">
-			<MkPagination v-slot="{items}" :pagination="featuredPagesPagination">
-				<div class="_gaps">
-					<MkPagePreview v-for="page in items" :key="page.id" :page="page"/>
-				</div>
-			</MkPagination>
-		</div>
+		<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
+			<div v-if="tab === 'featured'" key="featured">
+				<MkPagination v-slot="{items}" :pagination="featuredPagesPagination">
+					<div class="_gaps">
+						<MkPagePreview v-for="page in items" :key="page.id" :page="page"/>
+					</div>
+				</MkPagination>
+			</div>
 
-		<div v-else-if="tab === 'my'" class="_gaps">
-			<MkButton class="new" @click="create()"><i class="ph-plus ph-bold ph-lg"></i></MkButton>
-			<MkPagination v-slot="{items}" :pagination="myPagesPagination">
-				<div class="_gaps">
-					<MkPagePreview v-for="page in items" :key="page.id" :page="page"/>
-				</div>
-			</MkPagination>
-		</div>
+			<div v-else-if="tab === 'my'" key="my" class="_gaps">
+				<MkButton class="new" @click="create()"><i class="ph-plus ph-bold ph-lg"></i></MkButton>
+				<MkPagination v-slot="{items}" :pagination="myPagesPagination">
+					<div class="_gaps">
+						<MkPagePreview v-for="page in items" :key="page.id" :page="page"/>
+					</div>
+				</MkPagination>
+			</div>
 
-		<div v-else-if="tab === 'liked'">
-			<MkPagination v-slot="{items}" :pagination="likedPagesPagination">
-				<div class="_gaps">
-					<MkPagePreview v-for="like in items" :key="like.page.id" :page="like.page"/>
-				</div>
-			</MkPagination>
-		</div>
+			<div v-else-if="tab === 'liked'" key="liked">
+				<MkPagination v-slot="{items}" :pagination="likedPagesPagination">
+					<div class="_gaps">
+						<MkPagePreview v-for="like in items" :key="like.page.id" :page="like.page"/>
+					</div>
+				</MkPagination>
+			</div>
+		</MkHorizontalSwipe>
 	</MkSpacer>
 </MkStickyContainer>
 </template>
@@ -40,9 +42,10 @@ import { computed, ref } from 'vue';
 import MkPagePreview from '@/components/MkPagePreview.vue';
 import MkPagination from '@/components/MkPagination.vue';
 import MkButton from '@/components/MkButton.vue';
-import { useRouter } from '@/router.js';
+import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { useRouter } from '@/router/supplier.js';
 
 const router = useRouter();
 
@@ -78,15 +81,15 @@ const headerTabs = computed(() => [{
 }, {
 	key: 'my',
 	title: i18n.ts._pages.my,
-	icon: 'ph-pencil-line ph-bold ph-lg',
+	icon: 'ph-pencil-simple-line ph-bold ph-lg',
 }, {
 	key: 'liked',
 	title: i18n.ts._pages.liked,
 	icon: 'ph-heart ph-bold ph-lg',
 }]);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts.pages,
 	icon: 'ph-note ph-bold ph-lg',
-})));
+}));
 </script>
diff --git a/packages/frontend/src/pages/registry.keys.vue b/packages/frontend/src/pages/registry.keys.vue
index 95aa64f8d3..350c4fea1d 100644
--- a/packages/frontend/src/pages/registry.keys.vue
+++ b/packages/frontend/src/pages/registry.keys.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -36,6 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { watch, computed, ref } from 'vue';
 import JSON5 from 'json5';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import FormLink from '@/components/form/link.vue';
@@ -54,7 +55,7 @@ const scope = computed(() => props.path ? props.path.split('/') : []);
 const keys = ref<any>(null);
 
 function fetchKeys() {
-	os.api('i/registry/keys-with-type', {
+	misskeyApi('i/registry/keys-with-type', {
 		scope: scope.value,
 		domain: props.domain === '@' ? null : props.domain,
 	}).then(res => {
@@ -95,8 +96,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.registry,
 	icon: 'ph-faders ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/registry.value.vue b/packages/frontend/src/pages/registry.value.vue
index fb3cc4a556..61bf5f4545 100644
--- a/packages/frontend/src/pages/registry.value.vue
+++ b/packages/frontend/src/pages/registry.value.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -48,6 +48,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { watch, computed, ref } from 'vue';
 import JSON5 from 'json5';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkButton from '@/components/MkButton.vue';
@@ -68,7 +69,7 @@ const value = ref<any>(null);
 const valueForEditor = ref<string | null>(null);
 
 function fetchValue() {
-	os.api('i/registry/get-detail', {
+	misskeyApi('i/registry/get-detail', {
 		scope: scope.value,
 		key: key.value,
 		domain: props.domain === '@' ? null : props.domain,
@@ -122,8 +123,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.registry,
 	icon: 'ph-faders ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/registry.vue b/packages/frontend/src/pages/registry.vue
index 7d1dd751ab..de0c898187 100644
--- a/packages/frontend/src/pages/registry.vue
+++ b/packages/frontend/src/pages/registry.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -26,6 +26,7 @@ import { ref, computed } from 'vue';
 import * as Misskey from 'misskey-js';
 import JSON5 from 'json5';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import FormLink from '@/components/form/link.vue';
@@ -35,7 +36,7 @@ import MkButton from '@/components/MkButton.vue';
 const scopesWithDomain = ref<Misskey.entities.IRegistryScopesWithDomainResponse | null>(null);
 
 function fetchScopes() {
-	os.api('i/registry/scopes-with-domain').then(res => {
+	misskeyApi('i/registry/scopes-with-domain').then(res => {
 		scopesWithDomain.value = res;
 	});
 }
@@ -72,8 +73,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.registry,
 	icon: 'ph-faders ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/reset-password.vue b/packages/frontend/src/pages/reset-password.vue
index 1aed57724e..8b0b4baa67 100644
--- a/packages/frontend/src/pages/reset-password.vue
+++ b/packages/frontend/src/pages/reset-password.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -25,8 +25,8 @@ import MkInput from '@/components/MkInput.vue';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
-import { mainRouter } from '@/router.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { mainRouter } from '@/router/main.js';
 
 const props = defineProps<{
 	token?: string;
@@ -53,8 +53,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.resetPassword,
 	icon: 'ph-lock ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/reversi/game.board.vue b/packages/frontend/src/pages/reversi/game.board.vue
new file mode 100644
index 0000000000..c5c3d491fc
--- /dev/null
+++ b/packages/frontend/src/pages/reversi/game.board.vue
@@ -0,0 +1,622 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkSpacer :contentMax="500">
+	<div :class="$style.root" class="_gaps">
+		<div style="display: flex; align-items: center; justify-content: center; gap: 10px;">
+			<span>({{ i18n.ts._reversi.black }})</span>
+			<MkAvatar style="width: 32px; height: 32px;" :user="blackUser" :showIndicator="true"/>
+			<span> vs </span>
+			<MkAvatar style="width: 32px; height: 32px;" :user="whiteUser" :showIndicator="true"/>
+			<span>({{ i18n.ts._reversi.white }})</span>
+		</div>
+
+		<div style="overflow: clip; line-height: 28px;">
+			<div v-if="!iAmPlayer && !game.isEnded && turnUser">
+				<Mfm :key="'turn:' + turnUser.id" :text="i18n.tsx._reversi.turnOf({ name: turnUser.name ?? turnUser.username })" :plain="true" :customEmojis="turnUser.emojis"/>
+				<MkEllipsis/>
+			</div>
+			<div v-if="(logPos !== game.logs.length) && turnUser">
+				<Mfm :key="'past-turn-of:' + turnUser.id" :text="i18n.tsx._reversi.pastTurnOf({ name: turnUser.name ?? turnUser.username })" :plain="true" :customEmojis="turnUser.emojis"/>
+			</div>
+			<div v-if="iAmPlayer && !game.isEnded && !isMyTurn">{{ i18n.ts._reversi.opponentTurn }}<MkEllipsis/><span style="margin-left: 1em; opacity: 0.7;">({{ i18n.tsx.remainingN({ n: opTurnTimerRmain }) }})</span></div>
+			<div v-if="iAmPlayer && !game.isEnded && isMyTurn"><span style="display: inline-block; font-weight: bold; animation: global-tada 1s linear infinite both;">{{ i18n.ts._reversi.myTurn }}</span><span style="margin-left: 1em; opacity: 0.7;">({{ i18n.tsx.remainingN({ n: myTurnTimerRmain }) }})</span></div>
+			<div v-if="game.isEnded && logPos == game.logs.length">
+				<template v-if="game.winner">
+					<Mfm :key="'won'" :text="i18n.tsx._reversi.won({ name: game.winner.name ?? game.winner.username })" :plain="true" :customEmojis="game.winner.emojis"/>
+					<span v-if="game.surrenderedUserId != null"> ({{ i18n.ts._reversi.surrendered }})</span>
+					<span v-if="game.timeoutUserId != null"> ({{ i18n.ts._reversi.timeout }})</span>
+				</template>
+				<template v-else>{{ i18n.ts._reversi.drawn }}</template>
+			</div>
+		</div>
+
+		<div class="_woodenFrame">
+			<div :class="$style.boardInner">
+				<div v-if="showBoardLabels" :class="$style.labelsX">
+					<span v-for="i in game.map[0].length" :key="i" :class="$style.labelsXLabel">{{ String.fromCharCode(64 + i) }}</span>
+				</div>
+				<div style="display: flex;">
+					<div v-if="showBoardLabels" :class="$style.labelsY">
+						<div v-for="i in game.map.length" :key="i" :class="$style.labelsYLabel">{{ i }}</div>
+					</div>
+					<div :class="$style.boardCells" :style="cellsStyle">
+						<div
+							v-for="(stone, i) in engine.board"
+							:key="i"
+							v-tooltip="`${String.fromCharCode(65 + engine.posToXy(i)[0])}${engine.posToXy(i)[1] + 1}`"
+							:class="[$style.boardCell, {
+								[$style.boardCell_empty]: stone == null,
+								[$style.boardCell_none]: engine.map[i] === 'null',
+								[$style.boardCell_isEnded]: game.isEnded,
+								[$style.boardCell_myTurn]: !game.isEnded && isMyTurn,
+								[$style.boardCell_can]: turnUser ? engine.canPut(turnUser.id === blackUser.id, i) : null,
+								[$style.boardCell_prev]: engine.prevPos === i
+							}]"
+							@click="putStone(i)"
+						>
+							<Transition
+								:enterActiveClass="$style.transition_flip_enterActive"
+								:leaveActiveClass="$style.transition_flip_leaveActive"
+								:enterFromClass="$style.transition_flip_enterFrom"
+								:leaveToClass="$style.transition_flip_leaveTo"
+								mode="default"
+							>
+								<template v-if="useAvatarAsStone">
+									<img v-if="stone === true" :class="$style.boardCellStone" :src="blackUser.avatarUrl ?? undefined"/>
+									<img v-else-if="stone === false" :class="$style.boardCellStone" :src="whiteUser.avatarUrl ?? undefined"/>
+								</template>
+								<template v-else>
+									<img v-if="stone === true" :class="$style.boardCellStone" src="/client-assets/reversi/stone_b.png"/>
+									<img v-else-if="stone === false" :class="$style.boardCellStone" src="/client-assets/reversi/stone_w.png"/>
+								</template>
+							</Transition>
+						</div>
+					</div>
+					<div v-if="showBoardLabels" :class="$style.labelsY">
+						<div v-for="i in game.map.length" :key="i" :class="$style.labelsYLabel">{{ i }}</div>
+					</div>
+				</div>
+				<div v-if="showBoardLabels" :class="$style.labelsX">
+					<span v-for="i in game.map[0].length" :key="i" :class="$style.labelsXLabel">{{ String.fromCharCode(64 + i) }}</span>
+				</div>
+			</div>
+		</div>
+
+		<div v-if="game.isEnded" class="_panel _gaps_s" style="padding: 16px;">
+			<div>{{ logPos }} / {{ game.logs.length }}</div>
+			<div v-if="!autoplaying" class="_buttonsCenter">
+				<MkButton :disabled="logPos === 0" @click="logPos = 0"><i class="ph-caret-left ph-bold ph-lg"></i></MkButton>
+				<MkButton :disabled="logPos === 0" @click="logPos--"><i class="ph-caret-left ph-bold ph-lg"></i></MkButton>
+				<MkButton :disabled="logPos === game.logs.length" @click="logPos++"><i class="ph-caret-right ph-bold ph-lg"></i></MkButton>
+				<MkButton :disabled="logPos === game.logs.length" @click="logPos = game.logs.length"><i class="ph-caret-right ph-bold ph-lg"></i></MkButton>
+			</div>
+			<MkButton style="margin: auto;" :disabled="autoplaying" @click="autoplay()"><i class="ph-play ph-bold ph-lg"></i></MkButton>
+		</div>
+
+		<div class="_panel" style="padding: 16px;">
+			<div>
+				<b>{{ i18n.tsx._reversi.turnCount({ count: logPos }) }}</b> {{ i18n.ts._reversi.black }}:{{ engine.blackCount }} {{ i18n.ts._reversi.white }}:{{ engine.whiteCount }} {{ i18n.ts._reversi.total }}:{{ engine.blackCount + engine.whiteCount }}
+			</div>
+			<div>
+				<div style="display: flex; align-items: center;">
+					<span style="margin-right: 8px;">({{ i18n.ts._reversi.black }})</span>
+					<MkAvatar style="width: 32px; height: 32px; margin-right: 8px;" :user="blackUser" :showIndicator="true"/>
+					<MkA :to="userPage(blackUser)"><MkUserName :user="blackUser"/></MkA>
+				</div>
+				<div> vs </div>
+				<div style="display: flex; align-items: center;">
+					<span style="margin-right: 8px;">({{ i18n.ts._reversi.white }})</span>
+					<MkAvatar style="width: 32px; height: 32px; margin-right: 8px;" :user="whiteUser" :showIndicator="true"/>
+					<MkA :to="userPage(whiteUser)"><MkUserName :user="whiteUser"/></MkA>
+				</div>
+			</div>
+			<div>
+				<p v-if="game.isLlotheo">{{ i18n.ts._reversi.isLlotheo }}</p>
+				<p v-if="game.loopedBoard">{{ i18n.ts._reversi.loopedMap }}</p>
+				<p v-if="game.canPutEverywhere">{{ i18n.ts._reversi.canPutEverywhere }}</p>
+			</div>
+		</div>
+
+		<MkFolder>
+			<template #label>{{ i18n.ts.options }}</template>
+			<div class="_gaps_s" style="text-align: left;">
+				<MkSwitch v-model="showBoardLabels">{{ i18n.ts._reversi.showBoardLabels }}</MkSwitch>
+				<MkSwitch v-model="useAvatarAsStone">{{ i18n.ts._reversi.useAvatarAsStone }}</MkSwitch>
+			</div>
+		</MkFolder>
+
+		<div class="_buttonsCenter">
+			<MkButton v-if="!game.isEnded && iAmPlayer" danger @click="surrender">{{ i18n.ts._reversi.surrender }}</MkButton>
+			<MkButton @click="share">{{ i18n.ts.share }}</MkButton>
+		</div>
+
+		<MkA v-if="game.isEnded" :to="`/reversi`">
+			<img src="/client-assets/reversi/logo.png" style="display: block; max-width: 100%; width: 200px; margin: auto;"/>
+		</MkA>
+	</div>
+</MkSpacer>
+</template>
+
+<script lang="ts" setup>
+import { computed, onActivated, onDeactivated, onMounted, onUnmounted, ref, shallowRef, triggerRef, watch } from 'vue';
+import * as Misskey from 'misskey-js';
+import * as Reversi from 'misskey-reversi';
+import MkButton from '@/components/MkButton.vue';
+import MkFolder from '@/components/MkFolder.vue';
+import MkSwitch from '@/components/MkSwitch.vue';
+import { deepClone } from '@/scripts/clone.js';
+import { useInterval } from '@/scripts/use-interval.js';
+import { signinRequired } from '@/account.js';
+import { i18n } from '@/i18n.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
+import { userPage } from '@/filters/user.js';
+import * as sound from '@/scripts/sound.js';
+import * as os from '@/os.js';
+import { confetti } from '@/scripts/confetti.js';
+
+const $i = signinRequired();
+
+const props = defineProps<{
+	game: Misskey.entities.ReversiGameDetailed;
+	connection?: Misskey.ChannelConnection<Misskey.Channels['reversiGame']> | null;
+}>();
+
+const showBoardLabels = ref<boolean>(false);
+const useAvatarAsStone = ref<boolean>(true);
+const autoplaying = ref<boolean>(false);
+// eslint-disable-next-line vue/no-setup-props-destructure
+const game = ref<Misskey.entities.ReversiGameDetailed & { logs: Reversi.Serializer.SerializedLog[] }>(deepClone(props.game));
+const logPos = ref<number>(game.value.logs.length);
+const engine = shallowRef<Reversi.Game>(Reversi.Serializer.restoreGame({
+	map: game.value.map,
+	isLlotheo: game.value.isLlotheo,
+	canPutEverywhere: game.value.canPutEverywhere,
+	loopedBoard: game.value.loopedBoard,
+	logs: game.value.logs,
+}));
+
+const iAmPlayer = computed(() => {
+	return game.value.user1Id === $i.id || game.value.user2Id === $i.id;
+});
+
+const myColor = computed(() => {
+	if (!iAmPlayer.value) return null;
+	if (game.value.user1Id === $i.id && game.value.black === 1) return true;
+	if (game.value.user2Id === $i.id && game.value.black === 2) return true;
+	return false;
+});
+
+const opColor = computed(() => {
+	if (!iAmPlayer.value) return null;
+	return !myColor.value;
+});
+
+const blackUser = computed(() => {
+	return game.value.black === 1 ? game.value.user1 : game.value.user2;
+});
+
+const whiteUser = computed(() => {
+	return game.value.black === 1 ? game.value.user2 : game.value.user1;
+});
+
+const turnUser = computed(() => {
+	if (engine.value.turn === true) {
+		return game.value.black === 1 ? game.value.user1 : game.value.user2;
+	} else if (engine.value.turn === false) {
+		return game.value.black === 1 ? game.value.user2 : game.value.user1;
+	} else {
+		return null;
+	}
+});
+
+const isMyTurn = computed(() => {
+	if (!iAmPlayer.value) return false;
+	const u = turnUser.value;
+	if (u == null) return false;
+	return u.id === $i.id;
+});
+
+const cellsStyle = computed(() => {
+	return {
+		'grid-template-rows': `repeat(${game.value.map.length}, 1fr)`,
+		'grid-template-columns': `repeat(${game.value.map[0].length}, 1fr)`,
+	};
+});
+
+watch(logPos, (v) => {
+	if (!game.value.isEnded) return;
+	engine.value = Reversi.Serializer.restoreGame({
+		map: game.value.map,
+		isLlotheo: game.value.isLlotheo,
+		canPutEverywhere: game.value.canPutEverywhere,
+		loopedBoard: game.value.loopedBoard,
+		logs: game.value.logs.slice(0, v),
+	});
+});
+
+if (game.value.isStarted && !game.value.isEnded) {
+	useInterval(() => {
+		if (game.value.isEnded) return;
+		const crc32 = engine.value.calcCrc32();
+		if (_DEV_) console.log('crc32', crc32);
+		misskeyApi('reversi/verify', {
+			gameId: game.value.id,
+			crc32: crc32.toString(),
+		}).then((res) => {
+			if (res.desynced) {
+				if (_DEV_) console.log('resynced');
+				restoreGame(res.game!);
+			}
+		});
+	}, 10000, { immediate: false, afterMounted: true });
+}
+
+const appliedOps: string[] = [];
+
+function putStone(pos: number) {
+	if (game.value.isEnded) return;
+	if (!iAmPlayer.value) return;
+	if (!isMyTurn.value) return;
+	if (!engine.value.canPut(myColor.value!, pos)) return;
+
+	engine.value.putStone(pos);
+
+	triggerRef(engine);
+
+	sound.playUrl('/client-assets/reversi/put.mp3', {
+		volume: 1,
+		playbackRate: 1,
+	});
+
+	const id = Math.random().toString(36).slice(2);
+	props.connection!.send('putStone', {
+		pos: pos,
+		id,
+	});
+	appliedOps.push(id);
+
+	myTurnTimerRmain.value = game.value.timeLimitForEachTurn;
+	opTurnTimerRmain.value = game.value.timeLimitForEachTurn;
+
+	checkEnd();
+}
+
+const myTurnTimerRmain = ref<number>(game.value.timeLimitForEachTurn);
+const opTurnTimerRmain = ref<number>(game.value.timeLimitForEachTurn);
+
+const TIMER_INTERVAL_SEC = 3;
+if (!props.game.isEnded) {
+	useInterval(() => {
+		if (myTurnTimerRmain.value > 0) {
+			myTurnTimerRmain.value = Math.max(0, myTurnTimerRmain.value - TIMER_INTERVAL_SEC);
+		}
+		if (opTurnTimerRmain.value > 0) {
+			opTurnTimerRmain.value = Math.max(0, opTurnTimerRmain.value - TIMER_INTERVAL_SEC);
+		}
+
+		if (iAmPlayer.value) {
+			if ((isMyTurn.value && myTurnTimerRmain.value === 0) || (!isMyTurn.value && opTurnTimerRmain.value === 0)) {
+			props.connection!.send('claimTimeIsUp', {});
+			}
+		}
+	}, TIMER_INTERVAL_SEC * 1000, { immediate: false, afterMounted: true });
+}
+
+async function onStreamLog(log) {
+	game.value.logs = Reversi.Serializer.serializeLogs([
+		...Reversi.Serializer.deserializeLogs(game.value.logs),
+		log,
+	]);
+
+	logPos.value++;
+
+	if (log.id == null || !appliedOps.includes(log.id)) {
+		switch (log.operation) {
+			case 'put': {
+				sound.playUrl('/client-assets/reversi/put.mp3', {
+					volume: 1,
+					playbackRate: 1,
+				});
+
+				if (log.player !== engine.value.turn) { // = desyncが発生している
+					const _game = await misskeyApi('reversi/show-game', {
+						gameId: props.game.id,
+					});
+					restoreGame(_game);
+					return;
+				}
+
+				engine.value.putStone(log.pos);
+				triggerRef(engine);
+
+				myTurnTimerRmain.value = game.value.timeLimitForEachTurn;
+				opTurnTimerRmain.value = game.value.timeLimitForEachTurn;
+
+				checkEnd();
+				break;
+			}
+
+			default:
+				break;
+		}
+	}
+}
+
+function onStreamEnded(x) {
+	game.value = deepClone(x.game);
+
+	if (game.value.winnerId === $i.id) {
+		confetti({
+			duration: 1000 * 3,
+		});
+
+		sound.playUrl('/client-assets/reversi/win.mp3', {
+			volume: 1,
+			playbackRate: 1,
+		});
+	} else {
+		sound.playUrl('/client-assets/reversi/lose.mp3', {
+			volume: 1,
+			playbackRate: 1,
+		});
+	}
+}
+
+function checkEnd() {
+	game.value.isEnded = engine.value.isEnded;
+	if (game.value.isEnded) {
+		if (engine.value.winner === true) {
+			game.value.winnerId = game.value.black === 1 ? game.value.user1Id : game.value.user2Id;
+			game.value.winner = game.value.black === 1 ? game.value.user1 : game.value.user2;
+		} else if (engine.value.winner === false) {
+			game.value.winnerId = game.value.black === 1 ? game.value.user2Id : game.value.user1Id;
+			game.value.winner = game.value.black === 1 ? game.value.user2 : game.value.user1;
+		} else {
+			game.value.winnerId = null;
+			game.value.winner = null;
+		}
+	}
+}
+
+function restoreGame(_game) {
+	game.value = deepClone(_game);
+
+	engine.value = Reversi.Serializer.restoreGame({
+		map: game.value.map,
+		isLlotheo: game.value.isLlotheo,
+		canPutEverywhere: game.value.canPutEverywhere,
+		loopedBoard: game.value.loopedBoard,
+		logs: game.value.logs,
+	});
+
+	logPos.value = game.value.logs.length;
+
+	checkEnd();
+}
+
+async function surrender() {
+	const { canceled } = await os.confirm({
+		type: 'warning',
+		text: i18n.ts.areYouSure,
+	});
+	if (canceled) return;
+
+	misskeyApi('reversi/surrender', {
+		gameId: game.value.id,
+	});
+}
+
+function autoplay() {
+	autoplaying.value = true;
+	logPos.value = 0;
+	const logs = Reversi.Serializer.deserializeLogs(game.value.logs);
+
+	window.setTimeout(() => {
+		logPos.value = 1;
+
+		let i = 1;
+		let previousLog = logs[0];
+		const tick = () => {
+			const log = logs[i];
+			const time = log.time - previousLog.time;
+			setTimeout(() => {
+				i++;
+				logPos.value++;
+				previousLog = log;
+
+				if (i < logs.length) {
+					tick();
+				} else {
+					autoplaying.value = false;
+				}
+			}, time);
+		};
+
+		tick();
+	}, 1000);
+}
+
+function share() {
+	os.post({
+		initialText: `#MisskeyReversi ${location.href}`,
+		instant: true,
+	});
+}
+
+onMounted(() => {
+	if (props.connection != null) {
+		props.connection.on('log', onStreamLog);
+		props.connection.on('ended', onStreamEnded);
+	}
+});
+
+onActivated(() => {
+	if (props.connection != null) {
+		props.connection.on('log', onStreamLog);
+		props.connection.on('ended', onStreamEnded);
+	}
+});
+
+onDeactivated(() => {
+	if (props.connection != null) {
+		props.connection.off('log', onStreamLog);
+		props.connection.off('ended', onStreamEnded);
+	}
+});
+
+onUnmounted(() => {
+	if (props.connection != null) {
+		props.connection.off('log', onStreamLog);
+		props.connection.off('ended', onStreamEnded);
+	}
+});
+</script>
+
+<style lang="scss" module>
+@use "sass:math";
+
+.transition_flip_enterActive,
+.transition_flip_leaveActive {
+	backface-visibility: hidden;
+	transition: opacity 0.5s ease, transform 0.5s ease;
+}
+.transition_flip_enterFrom {
+	transform: rotateY(-180deg);
+	opacity: 0;
+}
+.transition_flip_leaveTo {
+	transform: rotateY(180deg);
+	opacity: 0;
+}
+
+$label-size: 16px;
+$gap: 4px;
+
+.root {
+	text-align: center;
+}
+
+.boardInner {
+	padding: 32px;
+
+	background: var(--panel);
+	box-shadow: 0 0 2px 1px #ce8a5c, inset 0 0 1px 1px #693410;
+	border-radius: 8px;
+}
+
+@container (max-width: 400px) {
+	.boardInner {
+		padding: 16px;
+	}
+}
+
+.labelsX {
+	height: $label-size;
+	padding: 0 $label-size;
+	display: flex;
+}
+
+.labelsXLabel {
+	flex: 1;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	font-size: 0.8em;
+
+	&:first-child {
+		margin-left: -(math.div($gap, 2));
+	}
+
+	&:last-child {
+		margin-right: -(math.div($gap, 2));
+	}
+}
+
+.labelsY {
+	width: $label-size;
+	display: flex;
+	flex-direction: column;
+}
+
+.labelsYLabel {
+	flex: 1;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	font-size: 12px;
+
+	&:first-child {
+		margin-top: -(math.div($gap, 2));
+	}
+
+	&:last-child {
+		margin-bottom: -(math.div($gap, 2));
+	}
+}
+
+.boardCells {
+	flex: 1;
+	display: grid;
+	grid-gap: $gap;
+}
+
+.boardCell {
+	background: transparent;
+	border-radius: 100%;
+	aspect-ratio: 1;
+	transform-style: preserve-3d;
+	perspective: 150px;
+	transition: border 0.25s ease, opacity 0.25s ease;
+
+	&.boardCell_empty {
+		border: solid 2px var(--divider);
+	}
+
+	&.boardCell_empty.boardCell_can {
+		border-color: var(--accent);
+		opacity: 0.5;
+	}
+
+	&.boardCell_empty.boardCell_myTurn {
+		border-color: var(--divider);
+		opacity: 1;
+
+		&.boardCell_can {
+			border-color: var(--accent);
+			cursor: pointer;
+
+			&:hover {
+				background: var(--accent);
+			}
+		}
+	}
+
+	&.boardCell_prev {
+		box-shadow: 0 0 0 4px var(--accent);
+	}
+
+	&.boardCell_isEnded {
+		border-color: var(--divider);
+	}
+
+	&.boardCell_none {
+		border-color: transparent !important;
+	}
+}
+
+.boardCellStone {
+	position: absolute;
+	top: 0;
+	left: 0;
+	pointer-events: none;
+	user-select: none;
+	display: block;
+	width: 100%;
+	height: 100%;
+	border-radius: 100%;
+}
+</style>
diff --git a/packages/frontend/src/pages/reversi/game.setting.vue b/packages/frontend/src/pages/reversi/game.setting.vue
new file mode 100644
index 0000000000..93b0972e9c
--- /dev/null
+++ b/packages/frontend/src/pages/reversi/game.setting.vue
@@ -0,0 +1,298 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkStickyContainer>
+	<MkSpacer :contentMax="600">
+		<div style="text-align: center;"><b><MkUserName :user="game.user1"/></b> vs <b><MkUserName :user="game.user2"/></b></div>
+
+		<div :class="{ [$style.disallow]: isReady }">
+			<div class="_gaps" :class="{ [$style.disallowInner]: isReady }">
+				<div style="font-size: 1.5em; text-align: center;">{{ i18n.ts._reversi.gameSettings }}</div>
+
+				<template v-if="game.noIrregularRules">
+					<div>{{ i18n.ts._reversi.disallowIrregularRules }}</div>
+				</template>
+				<template v-else>
+					<div class="_panel">
+						<div style="display: flex; align-items: center; padding: 16px; border-bottom: solid 1px var(--divider);">
+							<div>{{ mapName }}</div>
+							<MkButton style="margin-left: auto;" @click="chooseMap">{{ i18n.ts._reversi.chooseBoard }}</MkButton>
+						</div>
+
+						<div style="padding: 16px;">
+							<div v-if="game.map == null"><i class="ph-dice-five ph-bold ph-lg"></i></div>
+							<div v-else :class="$style.board" :style="{ 'grid-template-rows': `repeat(${ game.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.map[0].length }, 1fr)` }">
+								<div v-for="(x, i) in game.map.join('')" :class="[$style.boardCell, { [$style.boardCellNone]: x == ' ' }]" @click="onMapCellClick(i, x)">
+									<i v-if="x === 'b' || x === 'w'" style="pointer-events: none; user-select: none;" :class="x === 'b' ? 'ph-circle-half ph-bold ph-lg' : 'ph-circle ph-bold ph-lg'"></i>
+								</div>
+							</div>
+						</div>
+					</div>
+
+					<MkFolder :defaultOpen="true">
+						<template #label>{{ i18n.ts._reversi.blackOrWhite }}</template>
+
+						<MkRadios v-model="game.bw">
+							<option value="random">{{ i18n.ts.random }}</option>
+							<option :value="'1'">
+								<I18n :src="i18n.ts._reversi.blackIs" tag="span">
+									<template #name>
+										<b><MkUserName :user="game.user1"/></b>
+									</template>
+								</I18n>
+							</option>
+							<option :value="'2'">
+								<I18n :src="i18n.ts._reversi.blackIs" tag="span">
+									<template #name>
+										<b><MkUserName :user="game.user2"/></b>
+									</template>
+								</I18n>
+							</option>
+						</MkRadios>
+					</MkFolder>
+
+					<MkFolder :defaultOpen="true">
+						<template #label>{{ i18n.ts._reversi.timeLimitForEachTurn }}</template>
+						<template #suffix>{{ game.timeLimitForEachTurn }}{{ i18n.ts._time.second }}</template>
+
+						<MkRadios v-model="game.timeLimitForEachTurn">
+							<option :value="5">5{{ i18n.ts._time.second }}</option>
+							<option :value="10">10{{ i18n.ts._time.second }}</option>
+							<option :value="30">30{{ i18n.ts._time.second }}</option>
+							<option :value="60">60{{ i18n.ts._time.second }}</option>
+							<option :value="90">90{{ i18n.ts._time.second }}</option>
+							<option :value="120">120{{ i18n.ts._time.second }}</option>
+							<option :value="180">180{{ i18n.ts._time.second }}</option>
+							<option :value="3600">3600{{ i18n.ts._time.second }}</option>
+						</MkRadios>
+					</MkFolder>
+
+					<MkFolder :defaultOpen="true">
+						<template #label>{{ i18n.ts._reversi.rules }}</template>
+
+						<div class="_gaps_s">
+							<MkSwitch v-model="game.isLlotheo" @update:modelValue="updateSettings('isLlotheo')">{{ i18n.ts._reversi.isLlotheo }}</MkSwitch>
+							<MkSwitch v-model="game.loopedBoard" @update:modelValue="updateSettings('loopedBoard')">{{ i18n.ts._reversi.loopedMap }}</MkSwitch>
+							<MkSwitch v-model="game.canPutEverywhere" @update:modelValue="updateSettings('canPutEverywhere')">{{ i18n.ts._reversi.canPutEverywhere }}</MkSwitch>
+						</div>
+					</MkFolder>
+				</template>
+			</div>
+		</div>
+	</MkSpacer>
+	<template #footer>
+		<div :class="$style.footer">
+			<MkSpacer :contentMax="700" :marginMin="16" :marginMax="16">
+				<div style="text-align: center;" class="_gaps_s">
+					<div v-if="opponentHasSettingsChanged" style="color: var(--warn);">{{ i18n.ts._reversi.opponentHasSettingsChanged }}</div>
+					<div>
+						<template v-if="isReady && isOpReady">{{ i18n.ts._reversi.thisGameIsStartedSoon }}<MkEllipsis/></template>
+						<template v-if="isReady && !isOpReady">{{ i18n.ts._reversi.waitingForOther }}<MkEllipsis/></template>
+						<template v-if="!isReady && isOpReady">{{ i18n.ts._reversi.waitingForMe }}</template>
+						<template v-if="!isReady && !isOpReady">{{ i18n.ts._reversi.waitingBoth }}<MkEllipsis/></template>
+					</div>
+					<div class="_buttonsCenter">
+						<MkButton rounded danger @click="cancel">{{ i18n.ts.cancel }}</MkButton>
+						<MkButton v-if="!isReady" rounded primary @click="ready">{{ i18n.ts._reversi.ready }}</MkButton>
+						<MkButton v-if="isReady" rounded @click="unready">{{ i18n.ts._reversi.cancelReady }}</MkButton>
+					</div>
+					<div>
+						<MkSwitch v-model="shareWhenStart">{{ i18n.ts._reversi.shareToTlTheGameWhenStart }}</MkSwitch>
+					</div>
+				</div>
+			</MkSpacer>
+		</div>
+	</template>
+</MkStickyContainer>
+</template>
+
+<script lang="ts" setup>
+import { computed, watch, ref, onMounted, shallowRef, onUnmounted } from 'vue';
+import * as Misskey from 'misskey-js';
+import * as Reversi from 'misskey-reversi';
+import { i18n } from '@/i18n.js';
+import { signinRequired } from '@/account.js';
+import { deepClone } from '@/scripts/clone.js';
+import MkButton from '@/components/MkButton.vue';
+import MkRadios from '@/components/MkRadios.vue';
+import MkSwitch from '@/components/MkSwitch.vue';
+import MkFolder from '@/components/MkFolder.vue';
+import * as os from '@/os.js';
+import { MenuItem } from '@/types/menu.js';
+import { useRouter } from '@/router/supplier.js';
+
+const $i = signinRequired();
+
+const router = useRouter();
+
+const mapCategories = Array.from(new Set(Object.values(Reversi.maps).map(x => x.category)));
+
+const props = defineProps<{
+	game: Misskey.entities.ReversiGameDetailed;
+	connection: Misskey.ChannelConnection;
+}>();
+
+const shareWhenStart = defineModel<boolean>('shareWhenStart', { default: false });
+
+const game = ref<Misskey.entities.ReversiGameDetailed>(deepClone(props.game));
+
+const mapName = computed(() => {
+	if (game.value.map == null) return 'Random';
+	const found = Object.values(Reversi.maps).find(x => x.data.join('') === game.value.map.join(''));
+	return found ? found.name! : '-Custom-';
+});
+const isReady = computed(() => {
+	if (game.value.user1Id === $i.id && game.value.user1Ready) return true;
+	if (game.value.user2Id === $i.id && game.value.user2Ready) return true;
+	return false;
+});
+const isOpReady = computed(() => {
+	if (game.value.user1Id !== $i.id && game.value.user1Ready) return true;
+	if (game.value.user2Id !== $i.id && game.value.user2Ready) return true;
+	return false;
+});
+
+const opponentHasSettingsChanged = ref(false);
+
+watch(() => game.value.bw, () => {
+	updateSettings('bw');
+});
+
+watch(() => game.value.timeLimitForEachTurn, () => {
+	updateSettings('timeLimitForEachTurn');
+});
+
+function chooseMap(ev: MouseEvent) {
+	const menu: MenuItem[] = [];
+
+	for (const c of mapCategories) {
+		const maps = Object.values(Reversi.maps).filter(x => x.category === c);
+		if (maps.length === 0) continue;
+		if (c != null) {
+			menu.push({
+				type: 'label',
+				text: c,
+			});
+		}
+		for (const m of maps) {
+			menu.push({
+				text: m.name!,
+				action: () => {
+					game.value.map = m.data;
+					updateSettings('map');
+				},
+			});
+		}
+	}
+
+	os.popupMenu(menu, ev.currentTarget ?? ev.target);
+}
+
+async function cancel() {
+	const { canceled } = await os.confirm({
+		type: 'warning',
+		text: i18n.ts.areYouSure,
+	});
+	if (canceled) return;
+
+	props.connection.send('cancel', {});
+
+	router.push('/reversi');
+}
+
+function ready() {
+	props.connection.send('ready', true);
+	opponentHasSettingsChanged.value = false;
+}
+
+function unready() {
+	props.connection.send('ready', false);
+}
+
+function onChangeReadyStates(states) {
+	game.value.user1Ready = states.user1;
+	game.value.user2Ready = states.user2;
+}
+
+function updateSettings(key: keyof Misskey.entities.ReversiGameDetailed) {
+	props.connection.send('updateSettings', {
+		key: key,
+		value: game.value[key],
+	});
+}
+
+function onUpdateSettings({ userId, key, value }: { userId: string; key: keyof Misskey.entities.ReversiGameDetailed; value: any; }) {
+	if (userId === $i.id) return;
+	if (game.value[key] === value) return;
+	game.value[key] = value;
+	if (isReady.value) {
+		opponentHasSettingsChanged.value = true;
+		unready();
+	}
+}
+
+function onMapCellClick(pos: number, pixel: string) {
+	const x = pos % game.value.map[0].length;
+	const y = Math.floor(pos / game.value.map[0].length);
+	const newPixel =
+		pixel === ' ' ? '-' :
+		pixel === '-' ? 'b' :
+		pixel === 'b' ? 'w' :
+		' ';
+	const line = game.value.map[y].split('');
+	line[x] = newPixel;
+	game.value.map[y] = line.join('');
+	updateSettings('map');
+}
+
+props.connection.on('changeReadyStates', onChangeReadyStates);
+props.connection.on('updateSettings', onUpdateSettings);
+
+onUnmounted(() => {
+	props.connection.off('changeReadyStates', onChangeReadyStates);
+	props.connection.off('updateSettings', onUpdateSettings);
+});
+</script>
+
+<style lang="scss" module>
+.disallow {
+	cursor: not-allowed;
+}
+.disallowInner {
+	pointer-events: none;
+	user-select: none;
+	opacity: 0.7;
+}
+
+.board {
+	display: grid;
+	grid-gap: 4px;
+	width: 300px;
+	height: 300px;
+	margin: 0 auto;
+	color: var(--fg);
+}
+
+.boardCell {
+	display: grid;
+	place-items: center;
+	background: transparent;
+	border: solid 2px var(--divider);
+	border-radius: 6px;
+	overflow: clip;
+	cursor: pointer;
+}
+.boardCellNone {
+	border-color: transparent;
+}
+
+.footer {
+	-webkit-backdrop-filter: var(--blur, blur(15px));
+	backdrop-filter: var(--blur, blur(15px));
+	background: var(--acrylicBg);
+	border-top: solid 0.5px var(--divider);
+}
+</style>
diff --git a/packages/frontend/src/pages/reversi/game.vue b/packages/frontend/src/pages/reversi/game.vue
new file mode 100644
index 0000000000..21b7797240
--- /dev/null
+++ b/packages/frontend/src/pages/reversi/game.vue
@@ -0,0 +1,120 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<div v-if="game == null || (!game.isEnded && connection == null)"><MkLoading/></div>
+<GameSetting v-else-if="!game.isStarted" v-model:shareWhenStart="shareWhenStart" :game="game" :connection="connection!"/>
+<GameBoard v-else :game="game" :connection="connection"/>
+</template>
+
+<script lang="ts" setup>
+import { computed, watch, ref, onMounted, shallowRef, onUnmounted } from 'vue';
+import * as Misskey from 'misskey-js';
+import GameSetting from './game.setting.vue';
+import GameBoard from './game.board.vue';
+import { misskeyApi } from '@/scripts/misskey-api.js';
+import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { useStream } from '@/stream.js';
+import { signinRequired } from '@/account.js';
+import { useRouter } from '@/router/supplier.js';
+import * as os from '@/os.js';
+import { i18n } from '@/i18n.js';
+import { useInterval } from '@/scripts/use-interval.js';
+
+const $i = signinRequired();
+
+const router = useRouter();
+
+const props = defineProps<{
+	gameId: string;
+}>();
+
+const game = shallowRef<Misskey.entities.ReversiGameDetailed | null>(null);
+const connection = shallowRef<Misskey.ChannelConnection | null>(null);
+const shareWhenStart = ref(false);
+
+watch(() => props.gameId, () => {
+	fetchGame();
+});
+
+function start(_game: Misskey.entities.ReversiGameDetailed) {
+	if (game.value?.isStarted) return;
+
+	if (shareWhenStart.value) {
+		misskeyApi('notes/create', {
+			text: i18n.ts._reversi.iStartedAGame + '\n' + location.href,
+			visibility: 'home',
+		});
+	}
+
+	game.value = _game;
+}
+
+async function fetchGame() {
+	const _game = await misskeyApi('reversi/show-game', {
+		gameId: props.gameId,
+	});
+
+	game.value = _game;
+	shareWhenStart.value = false;
+
+	if (connection.value) {
+		connection.value.dispose();
+	}
+	if (!game.value.isEnded) {
+		connection.value = useStream().useChannel('reversiGame', {
+			gameId: game.value.id,
+		});
+		connection.value.on('started', x => {
+			start(x.game);
+		});
+		connection.value.on('canceled', x => {
+			connection.value?.dispose();
+
+			if (x.userId !== $i.id) {
+				os.alert({
+					type: 'warning',
+					text: i18n.ts._reversi.gameCanceled,
+				});
+				router.push('/reversi');
+			}
+		});
+	}
+}
+
+// 通信を取りこぼした場合の救済
+useInterval(async () => {
+	if (game.value == null) return;
+	if (game.value.isStarted) return;
+
+	const _game = await misskeyApi('reversi/show-game', {
+		gameId: props.gameId,
+	});
+
+	if (_game.isStarted) {
+		start(_game);
+	} else {
+		game.value = _game;
+	}
+}, 1000 * 10, {
+	immediate: false,
+	afterMounted: true,
+});
+
+onMounted(() => {
+	fetchGame();
+});
+
+onUnmounted(() => {
+	if (connection.value) {
+		connection.value.dispose();
+	}
+});
+
+definePageMetadata(() => ({
+	title: 'Reversi',
+	icon: 'ph-game-controller ph-bold ph-lg',
+}));
+</script>
diff --git a/packages/frontend/src/pages/reversi/index.vue b/packages/frontend/src/pages/reversi/index.vue
new file mode 100644
index 0000000000..c863b91834
--- /dev/null
+++ b/packages/frontend/src/pages/reversi/index.vue
@@ -0,0 +1,353 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<MkSpacer v-if="!matchingAny && !matchingUser" :contentMax="600">
+	<div class="_gaps">
+		<div>
+			<img src="/client-assets/reversi/logo.png" style="display: block; max-width: 100%; max-height: 200px; margin: auto;"/>
+		</div>
+
+		<div class="_panel _gaps" style="padding: 16px;">
+			<div class="_buttonsCenter">
+				<MkButton primary gradate rounded @click="matchAny">{{ i18n.ts._reversi.freeMatch }}</MkButton>
+				<MkButton primary gradate rounded @click="matchUser">{{ i18n.ts.invite }}</MkButton>
+			</div>
+			<div style="font-size: 90%; opacity: 0.7; text-align: center;"><i class="ph-music-notes ph-bold ph-lg"></i> {{ i18n.ts.soundWillBePlayed }}</div>
+		</div>
+
+		<MkFolder v-if="invitations.length > 0" :defaultOpen="true">
+			<template #label>{{ i18n.ts.invitations }}</template>
+			<div class="_gaps_s">
+				<button v-for="user in invitations" :key="user.id" v-panel :class="$style.invitation" class="_button" tabindex="-1" @click="accept(user)">
+					<MkAvatar style="width: 32px; height: 32px; margin-right: 8px;" :user="user" :showIndicator="true"/>
+					<span style="margin-right: 8px;"><b><MkUserName :user="user"/></b></span>
+					<span>@{{ user.username }}</span>
+				</button>
+			</div>
+		</MkFolder>
+
+		<MkFolder v-if="$i" :defaultOpen="true">
+			<template #label>{{ i18n.ts._reversi.myGames }}</template>
+			<MkPagination :pagination="myGamesPagination" :disableAutoLoad="true">
+				<template #default="{ items }">
+					<div :class="$style.gamePreviews">
+						<MkA v-for="g in items" :key="g.id" v-panel :class="[$style.gamePreview, !g.isStarted && !g.isEnded && $style.gamePreviewWaiting, g.isStarted && !g.isEnded && $style.gamePreviewActive]" tabindex="-1" :to="`/reversi/g/${g.id}`">
+							<div :class="$style.gamePreviewPlayers">
+								<span v-if="g.winnerId === g.user1Id" style="margin-right: 0.75em; color: var(--accent); font-weight: bold;"><i class="ph-trophy ph-bold ph-lg"></i></span>
+								<span v-if="g.winnerId === g.user2Id" style="margin-right: 0.75em; visibility: hidden;"><i class="ph-x ph-bold ph-lg"></i></span>
+								<MkAvatar :class="$style.gamePreviewPlayersAvatar" :user="g.user1"/>
+								<span style="margin: 0 1em;">vs</span>
+								<MkAvatar :class="$style.gamePreviewPlayersAvatar" :user="g.user2"/>
+								<span v-if="g.winnerId === g.user1Id" style="margin-left: 0.75em; visibility: hidden;"><i class="ph-x ph-bold ph-lg"></i></span>
+								<span v-if="g.winnerId === g.user2Id" style="margin-left: 0.75em; color: var(--accent); font-weight: bold;"><i class="ph-trophy ph-bold ph-lg"></i></span>
+							</div>
+							<div :class="$style.gamePreviewFooter">
+								<span v-if="g.isStarted && !g.isEnded" :class="$style.gamePreviewStatusActive">{{ i18n.ts._reversi.playing }}</span>
+								<span v-else-if="!g.isEnded" :class="$style.gamePreviewStatusWaiting"><MkEllipsis/></span>
+								<span v-else>{{ i18n.ts._reversi.ended }}</span>
+								<MkTime style="margin-left: auto; opacity: 0.7;" :time="g.createdAt"/>
+							</div>
+						</MkA>
+					</div>
+				</template>
+			</MkPagination>
+		</MkFolder>
+
+		<MkFolder :defaultOpen="true">
+			<template #label>{{ i18n.ts._reversi.allGames }}</template>
+			<MkPagination :pagination="gamesPagination" :disableAutoLoad="true">
+				<template #default="{ items }">
+					<div :class="$style.gamePreviews">
+						<MkA v-for="g in items" :key="g.id" v-panel :class="[$style.gamePreview, !g.isStarted && !g.isEnded && $style.gamePreviewWaiting, g.isStarted && !g.isEnded && $style.gamePreviewActive]" tabindex="-1" :to="`/reversi/g/${g.id}`">
+							<div :class="$style.gamePreviewPlayers">
+								<span v-if="g.winnerId === g.user1Id" style="margin-right: 0.75em; color: var(--accent); font-weight: bold;"><i class="ph-trophy ph-bold ph-lg"></i></span>
+								<span v-if="g.winnerId === g.user2Id" style="margin-right: 0.75em; visibility: hidden;"><i class="ph-x ph-bold ph-lg"></i></span>
+								<MkAvatar :class="$style.gamePreviewPlayersAvatar" :user="g.user1"/>
+								<span style="margin: 0 1em;">vs</span>
+								<MkAvatar :class="$style.gamePreviewPlayersAvatar" :user="g.user2"/>
+								<span v-if="g.winnerId === g.user1Id" style="margin-left: 0.75em; visibility: hidden;"><i class="ph-x ph-bold ph-lg"></i></span>
+								<span v-if="g.winnerId === g.user2Id" style="margin-left: 0.75em; color: var(--accent); font-weight: bold;"><i class="ph-trophy ph-bold ph-lg"></i></span>
+							</div>
+							<div :class="$style.gamePreviewFooter">
+								<span v-if="g.isStarted && !g.isEnded" :class="$style.gamePreviewStatusActive">{{ i18n.ts._reversi.playing }}</span>
+								<span v-else-if="!g.isEnded" :class="$style.gamePreviewStatusWaiting"><MkEllipsis/></span>
+								<span v-else>{{ i18n.ts._reversi.ended }}</span>
+								<MkTime style="margin-left: auto; opacity: 0.7;" :time="g.createdAt"/>
+							</div>
+						</MkA>
+					</div>
+				</template>
+			</MkPagination>
+		</MkFolder>
+	</div>
+</MkSpacer>
+<MkSpacer v-else :contentMax="600">
+	<div :class="$style.waitingScreen">
+		<div v-if="matchingUser" :class="$style.waitingScreenTitle">
+			<I18n :src="i18n.ts.waitingFor" tag="span">
+				<template #x>
+					<b><MkUserName :user="matchingUser"/></b>
+				</template>
+			</I18n>
+			<MkEllipsis/>
+		</div>
+		<div v-else :class="$style.waitingScreenTitle">
+			{{ i18n.ts._reversi.lookingForPlayer }}<MkEllipsis/>
+		</div>
+		<div class="cancel">
+			<MkButton inline rounded @click="cancelMatching">{{ i18n.ts.cancel }}</MkButton>
+		</div>
+	</div>
+</MkSpacer>
+</template>
+
+<script lang="ts" setup>
+import { onDeactivated, onMounted, onUnmounted, ref } from 'vue';
+import * as Misskey from 'misskey-js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
+import { definePageMetadata } from '@/scripts/page-metadata.js';
+import { useStream } from '@/stream.js';
+import MkButton from '@/components/MkButton.vue';
+import MkFolder from '@/components/MkFolder.vue';
+import { i18n } from '@/i18n.js';
+import { $i } from '@/account.js';
+import MkPagination from '@/components/MkPagination.vue';
+import { useRouter } from '@/router/supplier.js';
+import * as os from '@/os.js';
+import { useInterval } from '@/scripts/use-interval.js';
+import { pleaseLogin } from '@/scripts/please-login.js';
+import * as sound from '@/scripts/sound.js';
+
+const myGamesPagination = {
+	endpoint: 'reversi/games' as const,
+	limit: 10,
+	params: {
+		my: true,
+	},
+};
+
+const gamesPagination = {
+	endpoint: 'reversi/games' as const,
+	limit: 10,
+};
+
+const router = useRouter();
+
+if ($i) {
+	const connection = useStream().useChannel('reversi');
+
+	connection.on('matched', x => {
+		if (matchingUser.value != null || matchingAny.value) {
+			startGame(x.game);
+		}
+	});
+
+	connection.on('invited', invitation => {
+		if (invitations.value.some(x => x.id === invitation.user.id)) return;
+		invitations.value.unshift(invitation.user);
+	});
+
+	onUnmounted(() => {
+		connection.dispose();
+	});
+}
+
+const invitations = ref<Misskey.entities.UserLite[]>([]);
+const matchingUser = ref<Misskey.entities.UserLite | null>(null);
+const matchingAny = ref<boolean>(false);
+const noIrregularRules = ref<boolean>(false);
+
+function startGame(game: Misskey.entities.ReversiGameDetailed) {
+	matchingUser.value = null;
+	matchingAny.value = false;
+
+	sound.playUrl('/client-assets/reversi/matched.mp3', {
+		volume: 1,
+		playbackRate: 1,
+	});
+
+	router.push(`/reversi/g/${game.id}`);
+}
+
+async function matchHeatbeat() {
+	if (matchingUser.value) {
+		const res = await misskeyApi('reversi/match', {
+			userId: matchingUser.value.id,
+		});
+
+		if (res != null) {
+			startGame(res);
+		}
+	} else if (matchingAny.value) {
+		const res = await misskeyApi('reversi/match', {
+			userId: null,
+			noIrregularRules: noIrregularRules.value,
+		});
+
+		if (res != null) {
+			startGame(res);
+		}
+	}
+}
+
+async function matchUser() {
+	pleaseLogin();
+
+	const user = await os.selectUser({ includeSelf: false, localOnly: true });
+	if (user == null) return;
+
+	matchingUser.value = user;
+
+	matchHeatbeat();
+}
+
+function matchAny(ev: MouseEvent) {
+	pleaseLogin();
+
+	os.popupMenu([{
+		text: i18n.ts._reversi.allowIrregularRules,
+		action: () => {
+			noIrregularRules.value = false;
+			matchingAny.value = true;
+			matchHeatbeat();
+		},
+	}, {
+		text: i18n.ts._reversi.disallowIrregularRules,
+		action: () => {
+			noIrregularRules.value = true;
+			matchingAny.value = true;
+			matchHeatbeat();
+		},
+	}], ev.currentTarget ?? ev.target);
+}
+
+function cancelMatching() {
+	if (matchingUser.value) {
+		misskeyApi('reversi/cancel-match', { userId: matchingUser.value.id });
+		matchingUser.value = null;
+	} else if (matchingAny.value) {
+		misskeyApi('reversi/cancel-match', { userId: null });
+		matchingAny.value = false;
+	}
+}
+
+async function accept(user) {
+	const game = await misskeyApi('reversi/match', {
+		userId: user.id,
+	});
+	if (game) {
+		startGame(game);
+	}
+}
+
+useInterval(matchHeatbeat, 1000 * 5, { immediate: false, afterMounted: true });
+
+onMounted(() => {
+	misskeyApi('reversi/invitations').then(_invitations => {
+		invitations.value = _invitations;
+	});
+
+	window.addEventListener('beforeunload', cancelMatching);
+});
+
+onDeactivated(() => {
+	cancelMatching();
+});
+
+onUnmounted(() => {
+	cancelMatching();
+});
+
+definePageMetadata(() => ({
+	title: 'Reversi',
+	icon: 'ph-game-controller ph-bold ph-lg',
+}));
+</script>
+
+<style lang="scss" module>
+@keyframes blink {
+	0% { opacity: 1; }
+	50% { opacity: 0.2; }
+}
+
+.invitation {
+	display: flex;
+	box-sizing: border-box;
+	width: 100%;
+	padding: 16px;
+	line-height: 32px;
+	text-align: left;
+}
+
+.gamePreviews {
+	display: grid;
+	grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
+	grid-gap: var(--margin);
+}
+
+.gamePreview {
+	font-size: 90%;
+	border-radius: 8px;
+	overflow: clip;
+}
+
+.gamePreviewActive {
+	box-shadow: inset 0 0 8px 0px var(--accent);
+}
+
+.gamePreviewWaiting {
+	box-shadow: inset 0 0 8px 0px var(--warn);
+}
+
+.gamePreviewPlayers {
+	text-align: center;
+	padding: 16px;
+	line-height: 32px;
+}
+
+.gamePreviewPlayersAvatar {
+	width: 32px;
+	height: 32px;
+
+	&:first-child {
+		margin-right: 8px;
+	}
+
+	&:last-child {
+		margin-left: 8px;
+	}
+}
+
+.gamePreviewFooter {
+	display: flex;
+	align-items: baseline;
+	border-top: solid 0.5px var(--divider);
+	padding: 6px 10px;
+	font-size: 0.9em;
+}
+
+.gamePreviewStatusActive {
+	color: var(--accent);
+	font-weight: bold;
+	animation: blink 2s infinite;
+}
+
+.gamePreviewStatusWaiting {
+	color: var(--warn);
+	font-weight: bold;
+	animation: blink 2s infinite;
+}
+
+.waitingScreen {
+	text-align: center;
+}
+
+.waitingScreenTitle {
+	font-size: 1.5em;
+	margin-bottom: 16px;
+	margin-top: 32px;
+}
+</style>
diff --git a/packages/frontend/src/pages/role.vue b/packages/frontend/src/pages/role.vue
index 6dce4f187d..8621b61eeb 100644
--- a/packages/frontend/src/pages/role.vue
+++ b/packages/frontend/src/pages/role.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -38,7 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { computed, watch, ref } from 'vue';
 import * as Misskey from 'misskey-js';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import MkUserList from '@/components/MkUserList.vue';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { i18n } from '@/i18n.js';
@@ -59,7 +59,7 @@ const error = ref();
 const visible = ref(false);
 
 watch(() => props.role, () => {
-	os.api('roles/show', {
+	misskeyApi('roles/show', {
 		roleId: props.role,
 	}).then(res => {
 		role.value = res;
@@ -89,14 +89,14 @@ const headerTabs = computed(() => [{
 	title: i18n.ts.users,
 }, {
 	key: 'timeline',
-	icon: 'ph-pencil ph-bold ph-lg',
+	icon: 'ph-pencil-simple ph-bold ph-lg',
 	title: i18n.ts.timeline,
 }]);
 
-definePageMetadata(computed(() => ({
-	title: role.value?.name,
+definePageMetadata(() => ({
+	title: role.value ? role.value.name : i18n.ts.role,
 	icon: 'ph-seal-check ph-bold ph-lg',
-})));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/scratchpad.vue b/packages/frontend/src/pages/scratchpad.vue
index ccda5bb8ac..fb3657cdc9 100644
--- a/packages/frontend/src/pages/scratchpad.vue
+++ b/packages/frontend/src/pages/scratchpad.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -44,7 +44,7 @@ import { Interpreter, Parser, utils } from '@syuilo/aiscript';
 import MkContainer from '@/components/MkContainer.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkCodeEditor from '@/components/MkCodeEditor.vue';
-import { createAiScriptEnv } from '@/scripts/aiscript/api.js';
+import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js';
 import * as os from '@/os.js';
 import { $i } from '@/account.js';
 import { i18n } from '@/i18n.js';
@@ -86,19 +86,7 @@ async function run() {
 			root.value = _root.value;
 		}),
 	}), {
-		in: (q) => {
-			return new Promise(ok => {
-				os.inputText({
-					title: q,
-				}).then(({ canceled, result: a }) => {
-					if (canceled) {
-						ok('');
-					} else {
-						ok(a);
-					}
-				});
-			});
-		},
+		in: aiScriptReadline,
 		out: (value) => {
 			if (value.type === 'str' && value.value.toLowerCase().replace(',', '').includes('hello world')) {
 				claimAchievement('outputHelloWorldOnScratchpad');
@@ -164,10 +152,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.scratchpad,
 	icon: 'ph-terminal-window ph-bold ph-lg-2',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/search.note.vue b/packages/frontend/src/pages/search.note.vue
index 405db06758..33de0d72cf 100644
--- a/packages/frontend/src/pages/search.note.vue
+++ b/packages/frontend/src/pages/search.note.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<option value="audio">Audio</option>
 				</MkSelect>
 
-				<MkFolder>
+				<MkFolder :defaultOpen="true">
 					<template #label>{{ i18n.ts.specifyUser }}</template>
 					<template v-if="user" #suffix>@{{ user.username }}</template>
 
@@ -58,9 +58,10 @@ import MkSwitch from '@/components/MkSwitch.vue';
 import MkSelect from '@/components/MkSelect.vue';
 import { i18n } from '@/i18n.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import MkFoldableSection from '@/components/MkFoldableSection.vue';
-import { useRouter } from '@/router.js';
 import MkFolder from '@/components/MkFolder.vue';
+import { useRouter } from '@/router/supplier.js';
 
 const router = useRouter();
 
@@ -74,7 +75,7 @@ const order = ref(false);
 const filetype = ref(null);
 
 function selectUser() {
-	os.selectUser().then(_user => {
+	os.selectUser({ includeSelf: true }).then(_user => {
 		user.value = _user;
 	});
 }
@@ -85,7 +86,7 @@ async function search() {
 	if (query == null || query === '') return;
 
 	if (query.startsWith('https://')) {
-		const promise = os.api('ap/show', {
+		const promise = misskeyApi('ap/show', {
 			uri: query,
 		});
 
diff --git a/packages/frontend/src/pages/search.user.vue b/packages/frontend/src/pages/search.user.vue
index 596f4da711..dad9cd910a 100644
--- a/packages/frontend/src/pages/search.user.vue
+++ b/packages/frontend/src/pages/search.user.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -33,7 +33,8 @@ import MkButton from '@/components/MkButton.vue';
 import { i18n } from '@/i18n.js';
 import * as os from '@/os.js';
 import MkFoldableSection from '@/components/MkFoldableSection.vue';
-import { useRouter } from '@/router.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
+import { useRouter } from '@/router/supplier.js';
 
 const router = useRouter();
 
@@ -48,7 +49,7 @@ async function search() {
 	if (query == null || query === '') return;
 
 	if (query.startsWith('https://')) {
-		const promise = os.api('ap/show', {
+		const promise = misskeyApi('ap/show', {
 			uri: query,
 		});
 
diff --git a/packages/frontend/src/pages/search.vue b/packages/frontend/src/pages/search.vue
index acc291c73e..fe56297c65 100644
--- a/packages/frontend/src/pages/search.vue
+++ b/packages/frontend/src/pages/search.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -7,18 +7,20 @@ SPDX-License-Identifier: AGPL-3.0-only
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
 
-	<MkSpacer v-if="tab === 'note'" :contentMax="800">
-		<div v-if="notesSearchAvailable">
-			<XNote/>
-		</div>
-		<div v-else>
-			<MkInfo warn>{{ i18n.ts.notesSearchNotAvailable }}</MkInfo>
-		</div>
-	</MkSpacer>
+	<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
+		<MkSpacer v-if="tab === 'note'" key="note" :contentMax="800">
+			<div v-if="notesSearchAvailable">
+				<XNote/>
+			</div>
+			<div v-else>
+				<MkInfo warn>{{ i18n.ts.notesSearchNotAvailable }}</MkInfo>
+			</div>
+		</MkSpacer>
 
-	<MkSpacer v-else-if="tab === 'user'" :contentMax="800">
-		<XUser/>
-	</MkSpacer>
+		<MkSpacer v-else-if="tab === 'user'" key="user" :contentMax="800">
+			<XUser/>
+		</MkSpacer>
+	</MkHorizontalSwipe>
 </MkStickyContainer>
 </template>
 
@@ -29,6 +31,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { $i } from '@/account.js';
 import { instance } from '@/instance.js';
 import MkInfo from '@/components/MkInfo.vue';
+import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
 
 const XNote = defineAsyncComponent(() => import('./search.note.vue'));
 const XUser = defineAsyncComponent(() => import('./search.user.vue'));
@@ -42,15 +45,15 @@ const headerActions = computed(() => []);
 const headerTabs = computed(() => [{
 	key: 'note',
 	title: i18n.ts.notes,
-	icon: 'ph-pencil ph-bold ph-lg',
+	icon: 'ph-pencil-simple ph-bold ph-lg',
 }, {
 	key: 'user',
 	title: i18n.ts.users,
 	icon: 'ph-users ph-bold ph-lg',
 }]);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts.search,
 	icon: 'ph-magnifying-glass ph-bold ph-lg',
-})));
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/2fa.qrdialog.vue b/packages/frontend/src/pages/settings/2fa.qrdialog.vue
index 9a2a98ad89..13f475c2f2 100644
--- a/packages/frontend/src/pages/settings/2fa.qrdialog.vue
+++ b/packages/frontend/src/pages/settings/2fa.qrdialog.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -110,7 +110,9 @@ import * as os from '@/os.js';
 import MkFolder from '@/components/MkFolder.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import { confetti } from '@/scripts/confetti.js';
-import { $i } from '@/account.js';
+import { signinRequired } from '@/account.js';
+
+const $i = signinRequired();
 
 defineProps<{
 	twoFactorData: {
@@ -151,7 +153,7 @@ function downloadBackupCodes() {
 		const txtBlob = new Blob([backupCodes.value.join('\n')], { type: 'text/plain' });
 		const dummya = document.createElement('a');
 		dummya.href = URL.createObjectURL(txtBlob);
-		dummya.download = `${$i?.username}-2fa-backup-codes.txt`;
+		dummya.download = `${$i.username}-2fa-backup-codes.txt`;
 		dummya.click();
 	}
 }
diff --git a/packages/frontend/src/pages/settings/2fa.vue b/packages/frontend/src/pages/settings/2fa.vue
index 09421ba2c2..ba85a43084 100644
--- a/packages/frontend/src/pages/settings/2fa.vue
+++ b/packages/frontend/src/pages/settings/2fa.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -80,9 +80,11 @@ import MkSwitch from '@/components/MkSwitch.vue';
 import FormSection from '@/components/form/section.vue';
 import MkFolder from '@/components/MkFolder.vue';
 import * as os from '@/os.js';
-import { $i } from '@/account.js';
+import { signinRequired } from '@/account.js';
 import { i18n } from '@/i18n.js';
 
+const $i = signinRequired();
+
 // メモ: 各エンドポイントはmeUpdatedを発行するため、refreshAccountは不要
 
 withDefaults(defineProps<{
@@ -91,7 +93,7 @@ withDefaults(defineProps<{
 	first: false,
 });
 
-const usePasswordLessLogin = computed(() => $i?.usePasswordLessLogin ?? false);
+const usePasswordLessLogin = computed(() => $i.usePasswordLessLogin ?? false);
 
 async function registerTOTP(): Promise<void> {
 	const auth = await os.authenticateDialog();
@@ -139,7 +141,7 @@ async function unregisterKey(key) {
 	const confirm = await os.confirm({
 		type: 'question',
 		title: i18n.ts._2fa.removeKey,
-		text: i18n.t('_2fa.removeKeyConfirm', { name: key.name }),
+		text: i18n.tsx._2fa.removeKeyConfirm({ name: key.name }),
 	});
 	if (confirm.canceled) return;
 
diff --git a/packages/frontend/src/pages/settings/accounts.vue b/packages/frontend/src/pages/settings/accounts.vue
index 697ce27f2f..f5effbd68b 100644
--- a/packages/frontend/src/pages/settings/accounts.vue
+++ b/packages/frontend/src/pages/settings/accounts.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -24,6 +24,7 @@ import type * as Misskey from 'misskey-js';
 import FormSuspense from '@/components/form/suspense.vue';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { getAccounts, addAccount as addAccounts, removeAccount as _removeAccount, login, $i } from '@/account.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
@@ -36,7 +37,7 @@ const init = async () => {
 	getAccounts().then(accounts => {
 		storedAccounts.value = accounts.filter(x => x.id !== $i!.id);
 
-		return os.api('users/show', {
+		return misskeyApi('users/show', {
 			userIds: storedAccounts.value.map(x => x.id),
 		});
 	}).then(response => {
@@ -105,10 +106,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.accounts,
 	icon: 'ph-users ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/settings/api.vue b/packages/frontend/src/pages/settings/api.vue
index ca38bd2e3d..f8f340d602 100644
--- a/packages/frontend/src/pages/settings/api.vue
+++ b/packages/frontend/src/pages/settings/api.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -16,6 +16,7 @@ import { defineAsyncComponent, ref, computed } from 'vue';
 import FormLink from '@/components/form/link.vue';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
@@ -25,7 +26,7 @@ function generateToken() {
 	os.popup(defineAsyncComponent(() => import('@/components/MkTokenGenerateWindow.vue')), {}, {
 		done: async result => {
 			const { name, permissions } = result;
-			const { token } = await os.api('miauth/gen-token', {
+			const { token } = await misskeyApi('miauth/gen-token', {
 				session: null,
 				name: name,
 				permission: permissions,
@@ -44,8 +45,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: 'API',
 	icon: 'ph-webhooks-logo ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/apps.vue b/packages/frontend/src/pages/settings/apps.vue
index f492dc6d31..abdb5d1cdd 100644
--- a/packages/frontend/src/pages/settings/apps.vue
+++ b/packages/frontend/src/pages/settings/apps.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 						<details>
 							<summary>{{ i18n.ts.details }}</summary>
 							<ul>
-								<li v-for="p in token.permission" :key="p">{{ i18n.t(`_permissions.${p}`) }}</li>
+								<li v-for="p in token.permission" :key="p">{{ i18n.ts._permissions[p] }}</li>
 							</ul>
 						</details>
 						<div>
@@ -47,7 +47,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { ref, computed } from 'vue';
 import FormPagination from '@/components/MkPagination.vue';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkKeyValue from '@/components/MkKeyValue.vue';
@@ -66,7 +66,7 @@ const pagination = {
 };
 
 function revoke(token) {
-	os.api('i/revoke-token', { tokenId: token.id }).then(() => {
+	misskeyApi('i/revoke-token', { tokenId: token.id }).then(() => {
 		list.value.reload();
 	});
 }
@@ -75,10 +75,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.installedApps,
 	icon: 'ph-plug ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/settings/avatar-decoration.decoration.vue b/packages/frontend/src/pages/settings/avatar-decoration.decoration.vue
index 2bf261abd9..1b731ff624 100644
--- a/packages/frontend/src/pages/settings/avatar-decoration.decoration.vue
+++ b/packages/frontend/src/pages/settings/avatar-decoration.decoration.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -16,7 +16,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { } from 'vue';
-import { $i } from '@/account.js';
+import { signinRequired } from '@/account.js';
+
+const $i = signinRequired();
 
 const props = defineProps<{
 	active?: boolean;
diff --git a/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue b/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue
index a46a92d1c6..327e0ef723 100644
--- a/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue
+++ b/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -51,7 +51,9 @@ import MkModalWindow from '@/components/MkModalWindow.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import { i18n } from '@/i18n.js';
 import MkRange from '@/components/MkRange.vue';
-import { $i } from '@/account.js';
+import { signinRequired } from '@/account.js';
+
+const $i = signinRequired();
 
 const props = defineProps<{
 	usingIndex: number | null;
diff --git a/packages/frontend/src/pages/settings/avatar-decoration.vue b/packages/frontend/src/pages/settings/avatar-decoration.vue
index 976f6aa68c..a60d7209cf 100644
--- a/packages/frontend/src/pages/settings/avatar-decoration.vue
+++ b/packages/frontend/src/pages/settings/avatar-decoration.vue
@@ -1,12 +1,12 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
 <div>
 	<div v-if="!loading" class="_gaps">
-		<MkInfo>{{ i18n.t('_profile.avatarDecorationMax', { max: $i.policies.avatarDecorationLimit }) }} ({{ i18n.t('remainingN', { n: $i.policies.avatarDecorationLimit - $i.avatarDecorations.length }) }})</MkInfo>
+		<MkInfo>{{ i18n.tsx._profile.avatarDecorationMax({ max: $i.policies.avatarDecorationLimit }) }} ({{ i18n.tsx.remainingN({ n: $i.policies.avatarDecorationLimit - $i.avatarDecorations.length }) }})</MkInfo>
 
 		<MkAvatar :class="$style.avatar" :user="$i" forceShowDecoration/>
 
@@ -50,15 +50,18 @@ import * as Misskey from 'misskey-js';
 import XDecoration from './avatar-decoration.decoration.vue';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
-import { $i } from '@/account.js';
+import { signinRequired } from '@/account.js';
 import MkInfo from '@/components/MkInfo.vue';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
+const $i = signinRequired();
+
 const loading = ref(true);
 const avatarDecorations = ref<Misskey.entities.GetAvatarDecorationsResponse>([]);
 
-os.api('get-avatar-decorations').then(_avatarDecorations => {
+misskeyApi('get-avatar-decorations').then(_avatarDecorations => {
 	avatarDecorations.value = _avatarDecorations;
 	loading.value = false;
 });
@@ -125,10 +128,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.avatarDecorations,
 	icon: 'ph-sparkle ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/settings/custom-css.vue b/packages/frontend/src/pages/settings/custom-css.vue
index 00a1fca856..59733e896f 100644
--- a/packages/frontend/src/pages/settings/custom-css.vue
+++ b/packages/frontend/src/pages/settings/custom-css.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -45,8 +45,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.customCss,
 	icon: 'ph-code ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/deck.vue b/packages/frontend/src/pages/settings/deck.vue
index 32acd5e7a6..81ae9bc2f7 100644
--- a/packages/frontend/src/pages/settings/deck.vue
+++ b/packages/frontend/src/pages/settings/deck.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -36,8 +36,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.deck,
 	icon: 'ph-text-columns ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/drive-cleaner.vue b/packages/frontend/src/pages/settings/drive-cleaner.vue
index 601479b73c..fef12fee06 100644
--- a/packages/frontend/src/pages/settings/drive-cleaner.vue
+++ b/packages/frontend/src/pages/settings/drive-cleaner.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -51,6 +51,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed, ref, watch } from 'vue';
 import tinycolor from 'tinycolor2';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import MkPagination from '@/components/MkPagination.vue';
 import MkDriveFileThumbnail from '@/components/MkDriveFileThumbnail.vue';
 import { i18n } from '@/i18n.js';
@@ -94,7 +95,7 @@ watch(sortModeSelect, () => {
 
 function fetchDriveInfo(): void {
 	fetching.value = true;
-	os.api('drive').then(info => {
+	misskeyApi('drive').then(info => {
 		capacity.value = info.capacity;
 		usage.value = info.usage;
 		fetching.value = false;
@@ -116,10 +117,10 @@ function onContextMenu(ev: MouseEvent, file): void {
 	os.contextMenu(getDriveFileMenu(file), ev);
 }
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.drivecleaner,
 	icon: 'ph-trash ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/settings/drive.vue b/packages/frontend/src/pages/settings/drive.vue
index 166e49ac54..4185a1c855 100644
--- a/packages/frontend/src/pages/settings/drive.vue
+++ b/packages/frontend/src/pages/settings/drive.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -62,12 +62,15 @@ import FormSection from '@/components/form/section.vue';
 import MkKeyValue from '@/components/MkKeyValue.vue';
 import FormSplit from '@/components/form/split.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import bytes from '@/filters/bytes.js';
 import { defaultStore } from '@/store.js';
 import MkChart from '@/components/MkChart.vue';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { $i } from '@/account.js';
+import { signinRequired } from '@/account.js';
+
+const $i = signinRequired();
 
 const fetching = ref(true);
 const usage = ref<number | null>(null);
@@ -76,6 +79,7 @@ const uploadFolder = ref<Misskey.entities.DriveFolder | null>(null);
 const alwaysMarkNsfw = ref($i.alwaysMarkNsfw);
 
 const meterStyle = computed(() => {
+	if (!capacity.value || !usage.value) return {};
 	return {
 		width: `${usage.value / capacity.value * 100}%`,
 		background: tinycolor({
@@ -88,14 +92,14 @@ const meterStyle = computed(() => {
 
 const keepOriginalUploading = computed(defaultStore.makeGetterSetter('keepOriginalUploading'));
 
-os.api('drive').then(info => {
+misskeyApi('drive').then(info => {
 	capacity.value = info.capacity;
 	usage.value = info.usage;
 	fetching.value = false;
 });
 
 if (defaultStore.state.uploadFolder) {
-	os.api('drive/folders/show', {
+	misskeyApi('drive/folders/show', {
 		folderId: defaultStore.state.uploadFolder,
 	}).then(response => {
 		uploadFolder.value = response;
@@ -104,10 +108,10 @@ if (defaultStore.state.uploadFolder) {
 
 function chooseUploadFolder() {
 	os.selectDriveFolder(false).then(async folder => {
-		defaultStore.set('uploadFolder', folder ? folder.id : null);
+		defaultStore.set('uploadFolder', folder[0] ? folder[0].id : null);
 		os.success();
 		if (defaultStore.state.uploadFolder) {
-			uploadFolder.value = await os.api('drive/folders/show', {
+			uploadFolder.value = await misskeyApi('drive/folders/show', {
 				folderId: defaultStore.state.uploadFolder,
 			});
 		} else {
@@ -117,7 +121,7 @@ function chooseUploadFolder() {
 }
 
 function saveProfile() {
-	os.api('i/update', {
+	misskeyApi('i/update', {
 		alwaysMarkNsfw: !!alwaysMarkNsfw.value,
 	}).catch(err => {
 		os.alert({
@@ -133,10 +137,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.drive,
 	icon: 'ph-cloud ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/settings/email.vue b/packages/frontend/src/pages/settings/email.vue
index 003501f45a..938abb0651 100644
--- a/packages/frontend/src/pages/settings/email.vue
+++ b/packages/frontend/src/pages/settings/email.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -54,15 +54,18 @@ import MkInfo from '@/components/MkInfo.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import * as os from '@/os.js';
-import { $i } from '@/account.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
+import { signinRequired } from '@/account.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { instance } from '@/instance.js';
 
-const emailAddress = ref($i!.email);
+const $i = signinRequired();
+
+const emailAddress = ref($i.email);
 
 const onChangeReceiveAnnouncementEmail = (v) => {
-	os.api('i/update', {
+	misskeyApi('i/update', {
 		receiveAnnouncementEmail: v,
 	});
 };
@@ -78,14 +81,14 @@ async function saveEmailAddress() {
 	});
 }
 
-const emailNotification_mention = ref($i!.emailNotificationTypes.includes('mention'));
-const emailNotification_reply = ref($i!.emailNotificationTypes.includes('reply'));
-const emailNotification_quote = ref($i!.emailNotificationTypes.includes('quote'));
-const emailNotification_follow = ref($i!.emailNotificationTypes.includes('follow'));
-const emailNotification_receiveFollowRequest = ref($i!.emailNotificationTypes.includes('receiveFollowRequest'));
+const emailNotification_mention = ref($i.emailNotificationTypes.includes('mention'));
+const emailNotification_reply = ref($i.emailNotificationTypes.includes('reply'));
+const emailNotification_quote = ref($i.emailNotificationTypes.includes('quote'));
+const emailNotification_follow = ref($i.emailNotificationTypes.includes('follow'));
+const emailNotification_receiveFollowRequest = ref($i.emailNotificationTypes.includes('receiveFollowRequest'));
 
 const saveNotificationSettings = () => {
-	os.api('i/update', {
+	misskeyApi('i/update', {
 		emailNotificationTypes: [
 			...[emailNotification_mention.value ? 'mention' : null],
 			...[emailNotification_reply.value ? 'reply' : null],
@@ -110,8 +113,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.email,
 	icon: 'ph-envelope ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/emoji-picker.vue b/packages/frontend/src/pages/settings/emoji-picker.vue
index 40bb823ac6..e9936ca5f2 100644
--- a/packages/frontend/src/pages/settings/emoji-picker.vue
+++ b/packages/frontend/src/pages/settings/emoji-picker.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					>
 						<template #item="{element}">
 							<button class="_button" :class="$style.emojisItem" @click="removeReaction(element, $event)">
-								<MkCustomEmoji v-if="element[0] === ':'" :name="element" :normal="true"/>
+								<MkCustomEmoji v-if="element[0] === ':'" :name="element" :normal="true" :fallbackToImage="true"/>
 								<MkEmoji v-else :emoji="element" :normal="true"/>
 							</button>
 						</template>
@@ -63,7 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					>
 						<template #item="{element}">
 							<button class="_button" :class="$style.emojisItem" @click="removeEmoji(element, $event)">
-								<MkCustomEmoji v-if="element[0] === ':'" :name="element" :normal="true"/>
+								<MkCustomEmoji v-if="element[0] === ':'" :name="element" :normal="true" :fallbackToImage="true"/>
 								<MkEmoji v-else :emoji="element" :normal="true"/>
 							</button>
 						</template>
@@ -87,7 +87,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 	<FromSlot>
 		<template #label>{{ i18n.ts.defaultLike }}</template>
-		<MkCustomEmoji v-if="like && like.startsWith(':')" style="max-height: 3em; font-size: 1.1em;" :useOriginalSize="false" :class="$style.reaction" :name="like" :normal="true" :noStyle="true"/>
+		<MkCustomEmoji v-if="like && like.startsWith(':')" style="max-height: 3em; font-size: 1.1em;" :useOriginalSize="false" :name="like" :normal="true" :noStyle="true"/>
 		<MkEmoji v-else-if="like && !like.startsWith(':')" :emoji="like" style="max-height: 3em; font-size: 1.1em;" :normal="true" :noStyle="true"/>
 		<span v-else-if="!like">{{ i18n.ts.notSet }}</span>
 		<div class="_buttons" style="padding-top: 8px;">
@@ -172,7 +172,7 @@ const chooseEmoji = (ev: MouseEvent) => pickEmoji(pinnedEmojis, ev);
 const setDefaultEmoji = () => setDefault(pinnedEmojis);
 
 function previewReaction(ev: MouseEvent) {
-	reactionPicker.show(getHTMLElement(ev));
+	reactionPicker.show(getHTMLElement(ev), null);
 }
 
 function previewEmoji(ev: MouseEvent) {
@@ -228,7 +228,7 @@ async function pickEmoji(itemsRef: Ref<string[]>, ev: MouseEvent) {
 	os.pickEmoji(getHTMLElement(ev), {
 		showPinned: false,
 	}).then(it => {
-		const emoji = it as string;
+		const emoji = it;
 		if (!itemsRef.value.includes(emoji)) {
 			itemsRef.value.push(emoji);
 		}
@@ -276,10 +276,10 @@ watch(pinnedEmojis, () => {
 	deep: true,
 });
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.emojiPicker,
 	icon: 'ph-smiley ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue
index 8eacdd32e6..1e4e815d5d 100644
--- a/packages/frontend/src/pages/settings/general.vue
+++ b/packages/frontend/src/pages/settings/general.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -17,6 +17,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</template>
 	</MkSelect>
 
+	<MkRadios v-model="hemisphere">
+		<template #label>{{ i18n.ts.hemisphere }}</template>
+		<option value="N">{{ i18n.ts._hemisphere.N }}</option>
+		<option value="S">{{ i18n.ts._hemisphere.S }}</option>
+		<template #caption>{{ i18n.ts._hemisphere.caption }}</template>
+	</MkRadios>
+
 	<MkRadios v-model="overridedDeviceKind">
 		<template #label>{{ i18n.ts.overridedDeviceKind }}</template>
 		<option :value="null">{{ i18n.ts.auto }}</option>
@@ -87,9 +94,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<MkRadios v-model="mediaListWithOneImageAppearance">
 				<template #label>{{ i18n.ts.mediaListWithOneImageAppearance }}</template>
 				<option value="expand">{{ i18n.ts.default }}</option>
-				<option value="16_9">{{ i18n.t('limitTo', { x: '16:9' }) }}</option>
-				<option value="1_1">{{ i18n.t('limitTo', { x: '1:1' }) }}</option>
-				<option value="2_3">{{ i18n.t('limitTo', { x: '2:3' }) }}</option>
+				<option value="16_9">{{ i18n.tsx.limitTo({ x: '16:9' }) }}</option>
+				<option value="1_1">{{ i18n.tsx.limitTo({ x: '1:1' }) }}</option>
+				<option value="2_3">{{ i18n.tsx.limitTo({ x: '2:3' }) }}</option>
 			</MkRadios>
 
 			<MkRange v-model="numberOfReplies" :min="2" :max="20" :step="1" easing>
@@ -138,6 +145,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<MkSwitch v-model="useSystemFont">{{ i18n.ts.useSystemFont }}</MkSwitch>
 				<MkSwitch v-model="disableDrawer">{{ i18n.ts.disableDrawer }}</MkSwitch>
 				<MkSwitch v-model="forceShowAds">{{ i18n.ts.forceShowAds }}</MkSwitch>
+				<MkSwitch v-model="oneko">{{ i18n.ts.oneko }}</MkSwitch>
 				<MkSwitch v-model="enableSeasonalScreenEffect">{{ i18n.ts.seasonalScreenEffect }}</MkSwitch>
 			</div>
 			<div>
@@ -146,6 +154,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<option value="native">{{ i18n.ts.native }}</option>
 					<option value="fluentEmoji">Fluent Emoji</option>
 					<option value="twemoji">Twemoji</option>
+					<option value="tossface">Tossface</option>
 				</MkRadios>
 				<div style="margin: 8px 0 0 0; font-size: 1.5em;"><Mfm :key="emojiStyle" text="🍮🍦🍭🍩🍰🍫🍬🥞🍪"/></div>
 			</div>
@@ -171,6 +180,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 		<div class="_gaps_m">
 			<div class="_gaps_s">
+				<MkSwitch v-model="warnMissingAltText">{{ i18n.ts.warnForMissingAltText }}</MkSwitch>
 				<MkSwitch v-model="imageNewTab">{{ i18n.ts.openImageInNewTab }}</MkSwitch>
 				<MkSwitch v-model="useReactionPickerForContextMenu">{{ i18n.ts.useReactionPickerForContextMenu }}</MkSwitch>
 				<MkSwitch v-model="enableInfiniteScroll">{{ i18n.ts.enableInfiniteScroll }}</MkSwitch>
@@ -178,6 +188,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<MkSwitch v-model="clickToOpen">{{ i18n.ts.clickToOpen }}</MkSwitch>
 				<MkSwitch v-model="showBots">{{ i18n.ts.showBots }}</MkSwitch>
 				<MkSwitch v-model="disableStreamingTimeline">{{ i18n.ts.disableStreamingTimeline }}</MkSwitch>
+				<MkSwitch v-model="enableHorizontalSwipe">{{ i18n.ts.enableHorizontalSwipe }}</MkSwitch>
 			</div>
 			<MkSelect v-model="serverDisconnectedBehavior">
 				<template #label>{{ i18n.ts.whenServerDisconnected }}</template>
@@ -190,6 +201,22 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<template #caption>{{ i18n.ts.numberOfPageCacheDescription }}</template>
 			</MkRange>
 
+			<MkFolder>
+				<template #label>{{ i18n.ts.boostSettings }}</template>
+				<div class="_gaps_m">
+					<MkSwitch v-model="showVisibilitySelectorOnBoost">
+						{{ i18n.ts.showVisibilitySelectorOnBoost }}
+						<template #caption>{{ i18n.ts.showVisibilitySelectorOnBoostDescription }}</template>
+					</MkSwitch>
+					<MkSelect v-model="visibilityOnBoost">
+						<template #label>{{ i18n.ts.visibilityOnBoost }}</template>
+						<option value="public">{{ i18n.ts._visibility['public'] }}</option>
+						<option value="home">{{ i18n.ts._visibility['home'] }}</option>
+						<option value="followers">{{ i18n.ts._visibility['followers'] }}</option>
+					</MkSelect>
+				</div>
+			</MkFolder>
+
 			<MkFolder>
 				<template #label>{{ i18n.ts.dataSaver }}</template>
 
@@ -229,9 +256,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<div class="_gaps">
 			<MkFolder>
 				<template #label>{{ i18n.ts.additionalEmojiDictionary }}</template>
-				<div v-for="lang in emojiIndexLangs" class="_buttons">
-					<MkButton @click="downloadEmojiIndex(lang)"><i class="ph-download ph-bold ph-lg"></i> {{ lang }}{{ defaultStore.reactiveState.additionalUnicodeEmojiIndexes.value[lang] ? ` (${ i18n.ts.installed })` : '' }}</MkButton>
-					<MkButton v-if="defaultStore.reactiveState.additionalUnicodeEmojiIndexes.value[lang]" danger @click="removeEmojiIndex(lang)"><i class="ph-trash ph-bold ph-lg"></i> {{ i18n.ts.remove }}</MkButton>
+				<div class="_buttons">
+					<template v-for="lang in emojiIndexLangs" :key="lang">
+						<MkButton v-if="defaultStore.reactiveState.additionalUnicodeEmojiIndexes.value[lang]" danger @click="removeEmojiIndex(lang)"><i class="ph-trash ph-bold ph-lg"></i> {{ i18n.ts.remove }} ({{ getEmojiIndexLangName(lang) }})</MkButton>
+						<MkButton v-else @click="downloadEmojiIndex(lang)"><i class="ph-download ph-bold ph-lg"></i> {{ getEmojiIndexLangName(lang) }}{{ defaultStore.reactiveState.additionalUnicodeEmojiIndexes.value[lang] ? ` (${ i18n.ts.installed })` : '' }}</MkButton>
+					</template>
 				</div>
 			</MkFolder>
 			<FormLink to="/settings/deck">{{ i18n.ts.deck }}</FormLink>
@@ -257,6 +286,7 @@ import MkInfo from '@/components/MkInfo.vue';
 import { langs } from '@/config.js';
 import { defaultStore } from '@/store.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { unisonReload } from '@/scripts/unison-reload.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
@@ -280,6 +310,7 @@ async function reloadAsk() {
 	unisonReload();
 }
 
+const hemisphere = computed(defaultStore.makeGetterSetter('hemisphere'));
 const overridedDeviceKind = computed(defaultStore.makeGetterSetter('overridedDeviceKind'));
 const serverDisconnectedBehavior = computed(defaultStore.makeGetterSetter('serverDisconnectedBehavior'));
 const showNoteActionsOnlyHover = computed(defaultStore.makeGetterSetter('showNoteActionsOnlyHover'));
@@ -302,9 +333,11 @@ const emojiStyle = computed(defaultStore.makeGetterSetter('emojiStyle'));
 const disableDrawer = computed(defaultStore.makeGetterSetter('disableDrawer'));
 const disableShowingAnimatedImages = computed(defaultStore.makeGetterSetter('disableShowingAnimatedImages'));
 const forceShowAds = computed(defaultStore.makeGetterSetter('forceShowAds'));
+const oneko = computed(defaultStore.makeGetterSetter('oneko'));
 const loadRawImages = computed(defaultStore.makeGetterSetter('loadRawImages'));
 const highlightSensitiveMedia = computed(defaultStore.makeGetterSetter('highlightSensitiveMedia'));
 const imageNewTab = computed(defaultStore.makeGetterSetter('imageNewTab'));
+const warnMissingAltText = computed(defaultStore.makeGetterSetter('warnMissingAltText'));
 const nsfw = computed(defaultStore.makeGetterSetter('nsfw'));
 const showFixedPostForm = computed(defaultStore.makeGetterSetter('showFixedPostForm'));
 const showFixedPostFormInChannel = computed(defaultStore.makeGetterSetter('showFixedPostFormInChannel'));
@@ -326,6 +359,9 @@ const noteDesign = computed(defaultStore.makeGetterSetter('noteDesign'));
 const uncollapseCW = computed(defaultStore.makeGetterSetter('uncollapseCW'));
 const expandLongNote = computed(defaultStore.makeGetterSetter('expandLongNote'));
 const enableSeasonalScreenEffect = computed(defaultStore.makeGetterSetter('enableSeasonalScreenEffect'));
+const showVisibilitySelectorOnBoost = computed(defaultStore.makeGetterSetter('showVisibilitySelectorOnBoost'));
+const visibilityOnBoost = computed(defaultStore.makeGetterSetter('visibilityOnBoost'));
+const enableHorizontalSwipe = computed(defaultStore.makeGetterSetter('enableHorizontalSwipe'));
 
 watch(lang, () => {
 	miLocalStorage.setItem('lang', lang.value as string);
@@ -364,6 +400,7 @@ watch(noteDesign, async (newval) => {
 });
 
 watch([
+	hemisphere,
 	lang,
 	fontSize,
 	cornerRadius,
@@ -381,19 +418,35 @@ watch([
 	keepScreenOn,
 	disableStreamingTimeline,
 	enableSeasonalScreenEffect,
+	showVisibilitySelectorOnBoost,
+	visibilityOnBoost,
 ], async () => {
 	await reloadAsk();
 });
 
-const emojiIndexLangs = ['en-US'];
+const emojiIndexLangs = ['en-US', 'ja-JP', 'ja-JP_hira'] as const;
 
-function downloadEmojiIndex(lang: string) {
+function getEmojiIndexLangName(targetLang: typeof emojiIndexLangs[number]) {
+	if (langs.find(x => x[0] === targetLang)) {
+		return langs.find(x => x[0] === targetLang)![1];
+	} else {
+		// 絵文字辞書限定の言語定義
+		switch (targetLang) {
+			case 'ja-JP_hira': return 'ひらがな';
+			default: return targetLang;
+		}
+	}
+}
+
+function downloadEmojiIndex(lang: typeof emojiIndexLangs[number]) {
 	async function main() {
 		const currentIndexes = defaultStore.state.additionalUnicodeEmojiIndexes;
 
 		function download() {
 			switch (lang) {
 				case 'en-US': return import('../../unicode-emoji-indexes/en-US.json').then(x => x.default);
+				case 'ja-JP': return import('../../unicode-emoji-indexes/ja-JP.json').then(x => x.default);
+				case 'ja-JP_hira': return import('../../unicode-emoji-indexes/ja-JP_hira.json').then(x => x.default);
 				default: throw new Error('unrecognized lang: ' + lang);
 			}
 		}
@@ -416,7 +469,7 @@ function removeEmojiIndex(lang: string) {
 }
 
 async function setPinnedList() {
-	const lists = await os.api('users/lists/list');
+	const lists = await misskeyApi('users/lists/list');
 	const { canceled, result: list } = await os.select({
 		title: i18n.ts.selectList,
 		items: lists.map(x => ({
@@ -485,8 +538,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.general,
 	icon: 'ph-faders ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/import-export.vue b/packages/frontend/src/pages/settings/import-export.vue
index 7ca1faf406..87bde70fc2 100644
--- a/packages/frontend/src/pages/settings/import-export.vue
+++ b/packages/frontend/src/pages/settings/import-export.vue
@@ -1,12 +1,12 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
 <div class="_gaps_m">
 	<FormSection first>
-		<template #label><i class="ph-pencil ph-bold ph-lg"></i> {{ i18n.ts._exportOrImport.allNotes }}</template>
+		<template #label><i class="ph-pencil-simple ph-bold ph-lg"></i> {{ i18n.ts._exportOrImport.allNotes }}</template>
 		<div class="_gaps_s">
 			<MkFolder>
 				<template #label>{{ i18n.ts.export }}</template>
@@ -36,6 +36,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<MkButton primary :class="$style.button" inline @click="exportFavorites()"><i class="ph-download ph-bold ph-lg"></i> {{ i18n.ts.export }}</MkButton>
 		</MkFolder>
 	</FormSection>
+	<FormSection>
+		<template #label><i class="ph-paperclip ph-bold ph-lg"></i> {{ i18n.ts._exportOrImport.clips }}</template>
+		<MkFolder>
+			<template #label>{{ i18n.ts.export }}</template>
+			<template #icon><i class="ph-download ph-bold ph-lg"></i></template>
+			<MkButton primary :class="$style.button" inline @click="exportClips()"><i class="ph-download ph-bold ph-lg"></i> {{ i18n.ts.export }}</MkButton>
+		</MkFolder>
+	</FormSection>
 	<FormSection>
 		<template #label><i class="ph-users ph-bold ph-lg"></i> {{ i18n.ts._exportOrImport.followingList }}</template>
 		<div class="_gaps_s">
@@ -133,11 +141,12 @@ import MkFolder from '@/components/MkFolder.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkRadios from '@/components/MkRadios.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { selectFile } from '@/scripts/select-file.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { $i } from '@/account.js';
-import { defaultStore } from "@/store.js";
+import { defaultStore } from '@/store.js';
 
 const excludeMutingUsers = ref(false);
 const excludeInactiveUsers = ref(false);
@@ -166,15 +175,19 @@ const onError = (ev) => {
 };
 
 const exportNotes = () => {
-	os.api('i/export-notes', {}).then(onExportSuccess).catch(onError);
+	misskeyApi('i/export-notes', {}).then(onExportSuccess).catch(onError);
 };
 
 const exportFavorites = () => {
-	os.api('i/export-favorites', {}).then(onExportSuccess).catch(onError);
+	misskeyApi('i/export-favorites', {}).then(onExportSuccess).catch(onError);
+};
+
+const exportClips = () => {
+	misskeyApi('i/export-clips', {}).then(onExportSuccess).catch(onError);
 };
 
 const exportFollowing = () => {
-	os.api('i/export-following', {
+	misskeyApi('i/export-following', {
 		excludeMuting: excludeMutingUsers.value,
 		excludeInactive: excludeInactiveUsers.value,
 	})
@@ -182,24 +195,24 @@ const exportFollowing = () => {
 };
 
 const exportBlocking = () => {
-	os.api('i/export-blocking', {}).then(onExportSuccess).catch(onError);
+	misskeyApi('i/export-blocking', {}).then(onExportSuccess).catch(onError);
 };
 
 const exportUserLists = () => {
-	os.api('i/export-user-lists', {}).then(onExportSuccess).catch(onError);
+	misskeyApi('i/export-user-lists', {}).then(onExportSuccess).catch(onError);
 };
 
 const exportMuting = () => {
-	os.api('i/export-mute', {}).then(onExportSuccess).catch(onError);
+	misskeyApi('i/export-mute', {}).then(onExportSuccess).catch(onError);
 };
 
 const exportAntennas = () => {
-	os.api('i/export-antennas', {}).then(onExportSuccess).catch(onError);
+	misskeyApi('i/export-antennas', {}).then(onExportSuccess).catch(onError);
 };
 
 const importFollowing = async (ev) => {
 	const file = await selectFile(ev.currentTarget ?? ev.target);
-	os.api('i/import-following', {
+	misskeyApi('i/import-following', {
 		fileId: file.id,
 		withReplies: withReplies.value,
 	}).then(onImportSuccess).catch(onError);
@@ -207,7 +220,7 @@ const importFollowing = async (ev) => {
 
 const importNotes = async (ev) => {
 	const file = await selectFile(ev.currentTarget ?? ev.target);
-	os.api('i/import-notes', {
+	misskeyApi('i/import-notes', {
 		fileId: file.id,
 		type: noteType.value,
 	}).then(onImportSuccess).catch(onError);
@@ -215,32 +228,32 @@ const importNotes = async (ev) => {
 
 const importUserLists = async (ev) => {
 	const file = await selectFile(ev.currentTarget ?? ev.target);
-	os.api('i/import-user-lists', { fileId: file.id }).then(onImportSuccess).catch(onError);
+	misskeyApi('i/import-user-lists', { fileId: file.id }).then(onImportSuccess).catch(onError);
 };
 
 const importMuting = async (ev) => {
 	const file = await selectFile(ev.currentTarget ?? ev.target);
-	os.api('i/import-muting', { fileId: file.id }).then(onImportSuccess).catch(onError);
+	misskeyApi('i/import-muting', { fileId: file.id }).then(onImportSuccess).catch(onError);
 };
 
 const importBlocking = async (ev) => {
 	const file = await selectFile(ev.currentTarget ?? ev.target);
-	os.api('i/import-blocking', { fileId: file.id }).then(onImportSuccess).catch(onError);
+	misskeyApi('i/import-blocking', { fileId: file.id }).then(onImportSuccess).catch(onError);
 };
 
 const importAntennas = async (ev) => {
 	const file = await selectFile(ev.currentTarget ?? ev.target);
-	os.api('i/import-antennas', { fileId: file.id }).then(onImportSuccess).catch(onError);
+	misskeyApi('i/import-antennas', { fileId: file.id }).then(onImportSuccess).catch(onError);
 };
 
 const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.importAndExport,
 	icon: 'ph-package ph-bold ph-lg',
-});
+}));
 </script>
 
 <style module>
diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue
index 96575e097b..35fb1a03f4 100644
--- a/packages/frontend/src/pages/settings/index.vue
+++ b/packages/frontend/src/pages/settings/index.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -27,16 +27,16 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script setup lang="ts">
-import { ComputedRef, Ref, computed, onActivated, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
+import { computed, onActivated, onMounted, onUnmounted, ref, shallowRef, watch } from 'vue';
 import { i18n } from '@/i18n.js';
 import MkInfo from '@/components/MkInfo.vue';
 import MkSuperMenu from '@/components/MkSuperMenu.vue';
 import { signout, $i } from '@/account.js';
 import { clearCache } from '@/scripts/clear-cache.js';
 import { instance } from '@/instance.js';
-import { useRouter } from '@/router.js';
-import { PageMetadata, definePageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
+import { PageMetadata, definePageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
 import * as os from '@/os.js';
+import { useRouter } from '@/router/supplier.js';
 
 const indexInfo = {
 	title: i18n.ts.settings,
@@ -45,7 +45,7 @@ const indexInfo = {
 };
 const INFO = ref(indexInfo);
 const el = shallowRef<HTMLElement | null>(null);
-const childInfo: Ref<ComputedRef<PageMetadata> | null> = ref(null);
+const childInfo = ref<null | PageMetadata>(null);
 
 const router = useRouter();
 
@@ -230,20 +230,22 @@ watch(router.currentRef, (to) => {
 
 const emailNotConfigured = computed(() => instance.enableEmail && ($i.email == null || !$i.emailVerified));
 
-provideMetadataReceiver((info) => {
+provideMetadataReceiver((metadataGetter) => {
+	const info = metadataGetter();
 	if (info == null) {
 		childInfo.value = null;
 	} else {
 		childInfo.value = info;
-		INFO.value.needWideArea = info.value.needWideArea ?? undefined;
+		INFO.value.needWideArea = info.needWideArea ?? undefined;
 	}
 });
+provideReactiveMetadata(INFO);
 
 const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(INFO);
+definePageMetadata(() => INFO.value);
 // w 890
 // h 700
 </script>
diff --git a/packages/frontend/src/pages/settings/migration.vue b/packages/frontend/src/pages/settings/migration.vue
index 3b47189eb4..12f29e2ff8 100644
--- a/packages/frontend/src/pages/settings/migration.vue
+++ b/packages/frontend/src/pages/settings/migration.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -21,13 +21,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<div class="_gaps">
 				<MkInput v-for="(_, i) in accountAliases" v-model="accountAliases[i]">
 					<template #prefix><i class="ph-airplane-landing ph-bold ph-lg"></i></template>
-					<template #label>{{ i18n.t('_accountMigration.moveFromLabel', { n: i + 1 }) }}</template>
+					<template #label>{{ i18n.tsx._accountMigration.moveFromLabel({ n: i + 1 }) }}</template>
 				</MkInput>
 			</div>
 		</div>
 	</MkFolder>
 
-	<MkFolder :defaultOpen="!!$i?.movedTo">
+	<MkFolder :defaultOpen="!!$i.movedTo">
 		<template #icon><i class="ph-airplane-takeoff ph-bold ph-lg"></i></template>
 		<template #label>{{ i18n.ts._accountMigration.moveTo }}</template>
 
@@ -66,24 +66,27 @@ import MkButton from '@/components/MkButton.vue';
 import MkFolder from '@/components/MkFolder.vue';
 import MkUserInfo from '@/components/MkUserInfo.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { $i } from '@/account.js';
+import { signinRequired } from '@/account.js';
 import { unisonReload } from '@/scripts/unison-reload.js';
 
+const $i = signinRequired();
+
 const moveToAccount = ref('');
 const movedTo = ref<Misskey.entities.UserDetailed>();
 const accountAliases = ref(['']);
 
 async function init() {
-	if ($i?.movedTo) {
-		movedTo.value = await os.api('users/show', { userId: $i.movedTo });
+	if ($i.movedTo) {
+		movedTo.value = await misskeyApi('users/show', { userId: $i.movedTo });
 	} else {
 		moveToAccount.value = '';
 	}
 
-	if ($i?.alsoKnownAs && $i.alsoKnownAs.length > 0) {
-		const alsoKnownAs = await os.api('users/show', { userIds: $i.alsoKnownAs });
+	if ($i.alsoKnownAs && $i.alsoKnownAs.length > 0) {
+		const alsoKnownAs = await misskeyApi('users/show', { userIds: $i.alsoKnownAs });
 		accountAliases.value = (alsoKnownAs && alsoKnownAs.length > 0) ? alsoKnownAs.map(user => `@${Misskey.acct.toString(user)}`) : [''];
 	} else {
 		accountAliases.value = [''];
@@ -94,7 +97,7 @@ async function move(): Promise<void> {
 	const account = moveToAccount.value;
 	const confirm = await os.confirm({
 		type: 'warning',
-		text: i18n.t('_accountMigration.migrationConfirm', { account }),
+		text: i18n.tsx._accountMigration.migrationConfirm({ account }),
 	});
 	if (confirm.canceled) return;
 	await os.apiWithDialog('i/move', {
@@ -118,10 +121,10 @@ async function save(): Promise<void> {
 
 init();
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.accountMigration,
 	icon: 'ph-airplane ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss">
diff --git a/packages/frontend/src/pages/settings/mute-block.instance-mute.vue b/packages/frontend/src/pages/settings/mute-block.instance-mute.vue
index 0e149fd461..3b3376a9a7 100644
--- a/packages/frontend/src/pages/settings/mute-block.instance-mute.vue
+++ b/packages/frontend/src/pages/settings/mute-block.instance-mute.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -19,11 +19,13 @@ import { ref, watch } from 'vue';
 import MkTextarea from '@/components/MkTextarea.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import MkButton from '@/components/MkButton.vue';
-import * as os from '@/os.js';
-import { $i } from '@/account.js';
+import { signinRequired } from '@/account.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 
-const instanceMutes = ref($i!.mutedInstances.join('\n'));
+const $i = signinRequired();
+
+const instanceMutes = ref($i.mutedInstances.join('\n'));
 const changed = ref(false);
 
 async function save() {
@@ -32,7 +34,7 @@ async function save() {
 		.map(el => el.trim())
 		.filter(el => el);
 
-	await os.api('i/update', {
+	await misskeyApi('i/update', {
 		mutedInstances: mutes,
 	});
 
diff --git a/packages/frontend/src/pages/settings/mute-block.vue b/packages/frontend/src/pages/settings/mute-block.vue
index a996a03cce..588184826d 100644
--- a/packages/frontend/src/pages/settings/mute-block.vue
+++ b/packages/frontend/src/pages/settings/mute-block.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -9,14 +9,14 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<template #icon><i class="ph-envelope ph-bold ph-lg"></i></template>
 		<template #label>{{ i18n.ts.wordMute }}</template>
 
-		<XWordMute :muted="$i!.mutedWords" @save="saveMutedWords"/>
+		<XWordMute :muted="$i.mutedWords" @save="saveMutedWords"/>
 	</MkFolder>
 
 	<MkFolder>
 		<template #icon><i class="ph-x-square ph-bold ph-lg"></i></template>
 		<template #label>{{ i18n.ts.hardWordMute }}</template>
 
-		<XWordMute :muted="$i!.hardMutedWords" @save="saveHardMutedWords"/>
+		<XWordMute :muted="$i.hardMutedWords" @save="saveHardMutedWords"/>
 	</MkFolder>
 
 	<MkFolder>
@@ -136,9 +136,11 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkUserCardMini from '@/components/MkUserCardMini.vue';
 import * as os from '@/os.js';
 import { infoImageUrl } from '@/instance.js';
-import { $i } from '@/account.js';
+import { signinRequired } from '@/account.js';
 import MkFolder from '@/components/MkFolder.vue';
 
+const $i = signinRequired();
+
 const renoteMutingPagination = {
 	endpoint: 'renote-mute/list' as const,
 	limit: 10,
@@ -227,10 +229,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.muteAndBlock,
 	icon: 'ph-prohibit ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/settings/mute-block.word-mute.vue b/packages/frontend/src/pages/settings/mute-block.word-mute.vue
index 96ee48cdba..faf16ca368 100644
--- a/packages/frontend/src/pages/settings/mute-block.word-mute.vue
+++ b/packages/frontend/src/pages/settings/mute-block.word-mute.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -64,7 +64,7 @@ async function save() {
 					os.alert({
 						type: 'error',
 						title: i18n.ts.regexpError,
-						text: i18n.t('regexpErrorDescription', { tab: 'word mute', line: i + 1 }) + '\n' + err.toString(),
+						text: i18n.tsx.regexpErrorDescription({ tab: 'word mute', line: i + 1 }) + '\n' + err.toString(),
 					});
 					// re-throw error so these invalid settings are not saved
 					throw err;
diff --git a/packages/frontend/src/pages/settings/navbar.vue b/packages/frontend/src/pages/settings/navbar.vue
index f3c9ec8926..ae5f081e1c 100644
--- a/packages/frontend/src/pages/settings/navbar.vue
+++ b/packages/frontend/src/pages/settings/navbar.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -118,10 +118,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.navbar,
 	icon: 'ph-list ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/settings/notifications.notification-config.vue b/packages/frontend/src/pages/settings/notifications.notification-config.vue
index 06686c3204..6dde006106 100644
--- a/packages/frontend/src/pages/settings/notifications.notification-config.vue
+++ b/packages/frontend/src/pages/settings/notifications.notification-config.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -7,10 +7,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 <div class="_gaps_m">
 	<MkSelect v-model="type">
 		<option value="all">{{ i18n.ts.all }}</option>
-		<option value="following">{{ i18n.ts.following }}</option>
-		<option value="follower">{{ i18n.ts.followers }}</option>
-		<option value="mutualFollow">{{ i18n.ts.mutualFollow }}</option>
-		<option value="list">{{ i18n.ts.userList }}</option>
+		<option value="following" v-if="hasSender">{{ i18n.ts.following }}</option>
+		<option value="follower" v-if="hasSender">{{ i18n.ts.followers }}</option>
+		<option value="mutualFollow" v-if="hasSender">{{ i18n.ts.mutualFollow }}</option>
+		<option value="followingOrFollower" v-if="hasSender">{{ i18n.ts.followingOrFollower }}</option>
+		<option value="list" v-if="hasSender">{{ i18n.ts.userList }}</option>
 		<option value="never">{{ i18n.ts.none }}</option>
 	</MkSelect>
 
@@ -32,10 +33,13 @@ import MkSelect from '@/components/MkSelect.vue';
 import MkButton from '@/components/MkButton.vue';
 import { i18n } from '@/i18n.js';
 
-const props = defineProps<{
+const props = withDefaults(defineProps<{
 	value: any;
 	userLists: Misskey.entities.UserList[];
-}>();
+	hasSender: boolean;
+}>(), {
+	hasSender: true,
+});
 
 const emit = defineEmits<{
 	(ev: 'update', result: any): void;
diff --git a/packages/frontend/src/pages/settings/notifications.vue b/packages/frontend/src/pages/settings/notifications.vue
index 0bdfbdf741..36fe7df03e 100644
--- a/packages/frontend/src/pages/settings/notifications.vue
+++ b/packages/frontend/src/pages/settings/notifications.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -9,19 +9,20 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<template #label>{{ i18n.ts.notificationRecieveConfig }}</template>
 		<div class="_gaps_s">
 			<MkFolder v-for="type in notificationTypes.filter(x => !nonConfigurableNotificationTypes.includes(x))" :key="type">
-				<template #label>{{ i18n.t('_notification._types.' + type) }}</template>
+				<template #label>{{ i18n.ts._notification._types[type] }}</template>
 				<template #suffix>
 					{{
 						$i.notificationRecieveConfig[type]?.type === 'never' ? i18n.ts.none :
 						$i.notificationRecieveConfig[type]?.type === 'following' ? i18n.ts.following :
 						$i.notificationRecieveConfig[type]?.type === 'follower' ? i18n.ts.followers :
 						$i.notificationRecieveConfig[type]?.type === 'mutualFollow' ? i18n.ts.mutualFollow :
+						$i.notificationRecieveConfig[type]?.type === 'followingOrFollower' ? i18n.ts.followingOrFollower :
 						$i.notificationRecieveConfig[type]?.type === 'list' ? i18n.ts.userList :
 						i18n.ts.all
 					}}
 				</template>
 
-				<XNotificationConfig :userLists="userLists" :value="$i.notificationRecieveConfig[type] ?? { type: 'all' }" @update="(res) => updateReceiveConfig(type, res)"/>
+				<XNotificationConfig :userLists="userLists" :value="$i.notificationRecieveConfig[type] ?? { type: 'all' }" :hasSender="!(notificationTypesWithoutSender.includes(type))" @update="(res) => updateReceiveConfig(type, res)"/>
 			</MkFolder>
 		</div>
 	</FormSection>
@@ -34,6 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<FormSection>
 		<div class="_gaps_m">
 			<FormLink @click="testNotification">{{ i18n.ts._notification.sendTestNotification }}</FormLink>
+			<FormLink @click="flushNotification">{{ i18n.ts._notification.flushNotification }}</FormLink>
 		</div>
 	</FormSection>
 	<FormSection>
@@ -62,18 +64,22 @@ import FormSection from '@/components/form/section.vue';
 import MkFolder from '@/components/MkFolder.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import * as os from '@/os.js';
-import { $i } from '@/account.js';
+import { signinRequired } from '@/account.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkPushNotificationAllowButton from '@/components/MkPushNotificationAllowButton.vue';
 import { notificationTypes } from '@/const.js';
 
-const nonConfigurableNotificationTypes = ['note', 'roleAssigned', 'followRequestAccepted', 'achievementEarned'];
+const $i = signinRequired();
+
+const nonConfigurableNotificationTypes = ['note', 'roleAssigned', 'followRequestAccepted'];
+const notificationTypesWithoutSender = ['achievementEarned'];
 
 const allowButton = shallowRef<InstanceType<typeof MkPushNotificationAllowButton>>();
 const pushRegistrationInServer = computed(() => allowButton.value?.pushRegistrationInServer);
 const sendReadMessage = computed(() => pushRegistrationInServer.value?.sendReadMessage || false);
-const userLists = await os.api('users/lists/list');
+const userLists = await misskeyApi('users/lists/list');
 
 async function readAllUnreadNotes() {
 	await os.apiWithDialog('i/read-all-unread-notes');
@@ -86,11 +92,11 @@ async function readAllNotifications() {
 async function updateReceiveConfig(type, value) {
 	await os.apiWithDialog('i/update', {
 		notificationRecieveConfig: {
-			...$i!.notificationRecieveConfig,
+			...$i.notificationRecieveConfig,
 			[type]: value,
 		},
 	}).then(i => {
-		$i!.notificationRecieveConfig = i.notificationRecieveConfig;
+		$i.notificationRecieveConfig = i.notificationRecieveConfig;
 	});
 }
 
@@ -107,15 +113,26 @@ function onChangeSendReadMessage(v: boolean) {
 }
 
 function testNotification(): void {
-	os.api('notifications/test-notification');
+	misskeyApi('notifications/test-notification');
+}
+
+async function flushNotification() {
+	const { canceled } = await os.confirm({
+		type: 'warning',
+		text: i18n.ts.resetAreYouSure,
+	});
+
+	if (canceled) return;
+
+	os.apiWithDialog('notifications/flush');
 }
 
 const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.notifications,
 	icon: 'ph-bell ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/other.vue b/packages/frontend/src/pages/settings/other.vue
index efda0c00b3..683e5f0e30 100644
--- a/packages/frontend/src/pages/settings/other.vue
+++ b/packages/frontend/src/pages/settings/other.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -104,26 +104,21 @@ import FormInfo from '@/components/MkInfo.vue';
 import MkKeyValue from '@/components/MkKeyValue.vue';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { defaultStore } from '@/store.js';
-import { signout, $i } from '@/account.js';
+import { signout, signinRequired } from '@/account.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { unisonReload } from '@/scripts/unison-reload.js';
 import FormSection from '@/components/form/section.vue';
 
+const $i = signinRequired();
+
 const reportError = computed(defaultStore.makeGetterSetter('reportError'));
 const enableCondensedLineForAcct = computed(defaultStore.makeGetterSetter('enableCondensedLineForAcct'));
 const devMode = computed(defaultStore.makeGetterSetter('devMode'));
 const defaultWithReplies = computed(defaultStore.makeGetterSetter('defaultWithReplies'));
 
-function onChangeInjectFeaturedNote(v) {
-	os.api('i/update', {
-		injectFeaturedNote: v,
-	}).then((i) => {
-		$i!.injectFeaturedNote = i.injectFeaturedNote;
-	});
-}
-
 async function deleteAccount() {
 	{
 		const { canceled } = await os.confirm({
@@ -165,11 +160,11 @@ async function updateRepliesAll(withReplies: boolean) {
 	});
 	if (canceled) return;
 
-	os.api('following/update-all', { withReplies });
+	misskeyApi('following/update-all', { withReplies });
 }
 
 const exportData = () => {
-	os.api('i/export-data', {}).then(() => {
+	misskeyApi('i/export-data', {}).then(() => {
 		os.alert({
 			type: 'info',
 			text: i18n.ts.exportRequested,
@@ -192,8 +187,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.other,
 	icon: 'ph-dots-three ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/plugin.install.vue b/packages/frontend/src/pages/settings/plugin.install.vue
index be8db548a4..f3dd862bd1 100644
--- a/packages/frontend/src/pages/settings/plugin.install.vue
+++ b/packages/frontend/src/pages/settings/plugin.install.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -53,8 +53,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts._plugin.install,
 	icon: 'ph-download ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/plugin.vue b/packages/frontend/src/pages/settings/plugin.vue
index 5b5c282f39..f1699f726e 100644
--- a/packages/frontend/src/pages/settings/plugin.vue
+++ b/packages/frontend/src/pages/settings/plugin.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -125,8 +125,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.plugins,
 	icon: 'ph-plug ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/preferences-backups.vue b/packages/frontend/src/pages/settings/preferences-backups.vue
index c7538f3a1b..f180e0b72c 100644
--- a/packages/frontend/src/pages/settings/preferences-backups.vue
+++ b/packages/frontend/src/pages/settings/preferences-backups.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -37,12 +37,13 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { computed, onMounted, onUnmounted, ref } from 'vue';
+import { onMounted, onUnmounted, ref } from 'vue';
 import { v4 as uuid } from 'uuid';
 import FormSection from '@/components/form/section.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { ColdDeviceStorage, defaultStore } from '@/store.js';
 import { unisonReload } from '@/scripts/unison-reload.js';
 import { useStream } from '@/stream.js';
@@ -70,6 +71,7 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
 	'animatedMfm',
 	'advancedMfm',
 	'loadRawImages',
+	'warnMissingAltText',
 	'imageNewTab',
 	'dataSaver',
 	'disableShowingAnimatedImages',
@@ -97,6 +99,7 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
 	'showClipButtonInNoteFooter',
 	'reactionsDisplaySize',
 	'forceShowAds',
+	'oneko',
 	'numberOfReplies',
 	'aiChanMode',
 	'devMode',
@@ -146,7 +149,7 @@ const connection = $i && useStream().useChannel('main');
 
 const profiles = ref<Record<string, Profile> | null>(null);
 
-os.api('i/registry/get-all', { scope })
+misskeyApi('i/registry/get-all', { scope })
 	.then(res => {
 		profiles.value = res || {};
 	});
@@ -205,6 +208,7 @@ async function saveNew(): Promise<void> {
 
 	const { canceled, result: name } = await os.inputText({
 		title: ts._preferencesBackups.inputName,
+		default: '',
 	});
 	if (canceled) return;
 
@@ -380,6 +384,7 @@ async function rename(id: string): Promise<void> {
 
 	const { canceled: cancel1, result: name } = await os.inputText({
 		title: ts._preferencesBackups.inputName,
+		default: '',
 	});
 	if (cancel1 || profiles.value[id].name === name) return;
 
@@ -446,10 +451,10 @@ onUnmounted(() => {
 	connection?.off('registryUpdated');
 });
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: ts.preferencesBackups,
 	icon: 'ph-floppy-disk ph-bold ph-lg',
-})));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/settings/privacy.vue b/packages/frontend/src/pages/settings/privacy.vue
index 62056ff8a6..86cf5ab241 100644
--- a/packages/frontend/src/pages/settings/privacy.vue
+++ b/packages/frontend/src/pages/settings/privacy.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -77,12 +77,14 @@ import MkSwitch from '@/components/MkSwitch.vue';
 import MkSelect from '@/components/MkSelect.vue';
 import FormSection from '@/components/form/section.vue';
 import MkFolder from '@/components/MkFolder.vue';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { defaultStore } from '@/store.js';
 import { i18n } from '@/i18n.js';
-import { $i } from '@/account.js';
+import { signinRequired } from '@/account.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
+const $i = signinRequired();
+
 const isLocked = ref($i.isLocked);
 const autoAcceptFollowed = ref($i.autoAcceptFollowed);
 const noCrawle = ref($i.noCrawle);
@@ -90,8 +92,8 @@ const noindex = ref($i.noindex);
 const isExplorable = ref($i.isExplorable);
 const hideOnlineStatus = ref($i.hideOnlineStatus);
 const publicReactions = ref($i.publicReactions);
-const followingVisibility = ref($i?.followingVisibility);
-const followersVisibility = ref($i?.followersVisibility);
+const followingVisibility = ref($i.followingVisibility);
+const followersVisibility = ref($i.followersVisibility);
 
 const defaultNoteVisibility = computed(defaultStore.makeGetterSetter('defaultNoteVisibility'));
 const defaultNoteLocalOnly = computed(defaultStore.makeGetterSetter('defaultNoteLocalOnly'));
@@ -99,7 +101,7 @@ const rememberNoteVisibility = computed(defaultStore.makeGetterSetter('rememberN
 const keepCw = computed(defaultStore.makeGetterSetter('keepCw'));
 
 function save() {
-	os.api('i/update', {
+	misskeyApi('i/update', {
 		isLocked: !!isLocked.value,
 		autoAcceptFollowed: !!autoAcceptFollowed.value,
 		noCrawle: !!noCrawle.value,
@@ -116,8 +118,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.privacy,
 	icon: 'ph-lock ph-bold ph-lg-open',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue
index 4bae635d05..408cf4ed67 100644
--- a/packages/frontend/src/pages/settings/profile.vue
+++ b/packages/frontend/src/pages/settings/profile.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -127,14 +127,17 @@ import FormSlot from '@/components/form/slot.vue';
 import { selectFile } from '@/scripts/select-file.js';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
-import { $i } from '@/account.js';
+import { signinRequired } from '@/account.js';
 import { langmap } from '@/scripts/langmap.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { claimAchievement } from '@/scripts/achievements.js';
 import { defaultStore } from '@/store.js';
+import { globalEvents } from '@/events.js';
 import MkInfo from '@/components/MkInfo.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
 
+const $i = signinRequired();
+
 const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
 
 const reactionAcceptance = computed(defaultStore.makeGetterSetter('reactionAcceptance'));
@@ -152,11 +155,11 @@ const profile = reactive({
 	description: $i.description,
 	location: $i.location,
 	birthday: $i.birthday,
-	listenbrainz: $i?.listenbrainz,
+	listenbrainz: $i.listenbrainz,
 	lang: $i.lang,
-	isBot: $i.isBot,
-	isCat: $i.isCat,
-	speakAsCat: $i.speakAsCat,
+	isBot: $i.isBot ?? false,
+	isCat: $i.isCat ?? false,
+	speakAsCat: $i.speakAsCat ?? false,
 });
 
 watch(() => profile, () => {
@@ -165,7 +168,7 @@ watch(() => profile, () => {
 	deep: true,
 });
 
-const fields = ref($i?.fields.map(field => ({ id: Math.random().toString(), name: field.name, value: field.value })) ?? []);
+const fields = ref($i.fields.map(field => ({ id: Math.random().toString(), name: field.name, value: field.value })) ?? []);
 const fieldEditMode = ref(false);
 
 function addField() {
@@ -188,6 +191,7 @@ function saveFields() {
 	os.apiWithDialog('i/update', {
 		fields: fields.value.filter(field => field.name !== '' && field.value !== '').map(field => ({ name: field.name, value: field.value })),
 	});
+	globalEvents.emit('requestClearPageCache');
 }
 
 function save() {
@@ -215,6 +219,7 @@ function save() {
 		isCat: !!profile.isCat,
 		speakAsCat: !!profile.speakAsCat,
 	});
+	globalEvents.emit('requestClearPageCache');
 	claimAchievement('profileFilled');
 	if (profile.name === 'syuilo' || profile.name === 'しゅいろ') {
 		claimAchievement('setNameToSyuilo');
@@ -230,7 +235,7 @@ function changeAvatar(ev) {
 
 		const { canceled } = await os.confirm({
 			type: 'question',
-			text: i18n.t('cropImageAsk'),
+			text: i18n.ts.cropImageAsk,
 			okText: i18n.ts.cropYes,
 			cancelText: i18n.ts.cropNo,
 		});
@@ -246,68 +251,153 @@ function changeAvatar(ev) {
 		});
 		$i.avatarId = i.avatarId;
 		$i.avatarUrl = i.avatarUrl;
+		globalEvents.emit('requestClearPageCache');
 		claimAchievement('profileFilled');
 	});
 }
 
 function changeBanner(ev) {
-	selectFile(ev.currentTarget ?? ev.target, i18n.ts.banner).then(async (file) => {
-		let originalOrCropped = file;
+	if ($i.bannerId) {
+		os.popupMenu([{
+			text: i18n.ts._profile.updateBanner,
+			action: async () => {
+				selectFile(ev.currentTarget ?? ev.target, i18n.ts.banner).then(async (file) => {
+					let originalOrCropped = file;
 
-		const { canceled } = await os.confirm({
-			type: 'question',
-			text: i18n.t('cropImageAsk'),
-			okText: i18n.ts.cropYes,
-			cancelText: i18n.ts.cropNo,
-		});
+					const { canceled } = await os.confirm({
+						type: 'question',
+						text: i18n.ts.cropImageAsk,
+						okText: i18n.ts.cropYes,
+						cancelText: i18n.ts.cropNo,
+					});
 
-		if (!canceled) {
-			originalOrCropped = await os.cropImage(file, {
-				aspectRatio: 2,
+					if (!canceled) {
+						originalOrCropped = await os.cropImage(file, {
+							aspectRatio: 2,
+						});
+					}
+
+					const i = await os.apiWithDialog('i/update', {
+						bannerId: originalOrCropped.id,
+					});
+					$i.bannerId = i.bannerId;
+					$i.bannerUrl = i.bannerUrl;
+					globalEvents.emit('requestClearPageCache');
+				});
+			},
+		}, {
+			text: i18n.ts._profile.removeBanner,
+			action: async () => {
+				const i = await os.apiWithDialog('i/update', {
+					bannerId: null,
+				});
+				$i.bannerId = i.bannerId;
+				$i.bannerUrl = i.bannerUrl;
+				globalEvents.emit('requestClearPageCache');
+			},
+		}], ev.currentTarget ?? ev.target);
+	} else {
+		selectFile(ev.currentTarget ?? ev.target, i18n.ts.banner).then(async (file) => {
+			let originalOrCropped = file;
+
+			const { canceled } = await os.confirm({
+				type: 'question',
+				text: i18n.ts.cropImageAsk,
+				okText: i18n.ts.cropYes,
+				cancelText: i18n.ts.cropNo,
 			});
-		}
 
-		const i = await os.apiWithDialog('i/update', {
-			bannerId: originalOrCropped.id,
+			if (!canceled) {
+				originalOrCropped = await os.cropImage(file, {
+					aspectRatio: 2,
+				});
+			}
+
+			const i = await os.apiWithDialog('i/update', {
+				bannerId: originalOrCropped.id,
+			});
+			$i.bannerId = i.bannerId;
+			$i.bannerUrl = i.bannerUrl;
+			globalEvents.emit('requestClearPageCache');
 		});
-		$i.bannerId = i.bannerId;
-		$i.bannerUrl = i.bannerUrl;
-	});
+	}
 }
 
 function changeBackground(ev) {
-	selectFile(ev.currentTarget ?? ev.target, i18n.ts.background).then(async (file) => {
-		let originalOrCropped = file;
+	if ($i.backgroundId) {
+		os.popupMenu([{
+			text: i18n.ts._profile.updateBackground,
+			action: async () => {
+				selectFile(ev.currentTarget ?? ev.target, i18n.ts.background).then(async (file) => {
+					let originalOrCropped = file;
 
-		const { canceled } = await os.confirm({
-			type: 'question',
-			text: i18n.t('cropImageAsk'),
-			okText: i18n.ts.cropYes,
-			cancelText: i18n.ts.cropNo,
-		});
+					const { canceled } = await os.confirm({
+						type: 'question',
+						text: i18n.ts.cropImageAsk,
+						okText: i18n.ts.cropYes,
+						cancelText: i18n.ts.cropNo,
+					});
 
-		if (!canceled) {
-			originalOrCropped = await os.cropImage(file, {
-				aspectRatio: 1,
+					if (!canceled) {
+						originalOrCropped = await os.cropImage(file, {
+							aspectRatio: 1,
+						});
+					}
+
+					const i = await os.apiWithDialog('i/update', {
+						backgroundId: originalOrCropped.id,
+					});
+					$i.backgroundId = i.backgroundId;
+					$i.backgroundUrl = i.backgroundUrl;
+					globalEvents.emit('requestClearPageCache');
+				});
+			},
+		}, {
+			text: i18n.ts._profile.removeBackground,
+			action: async () => {
+				const i = await os.apiWithDialog('i/update', {
+					backgroundId: null,
+				});
+				$i.backgroundId = i.backgroundId;
+				$i.backgroundUrl = i.backgroundUrl;
+				globalEvents.emit('requestClearPageCache');
+			},
+		}], ev.currentTarget ?? ev.target);
+	} else {
+		selectFile(ev.currentTarget ?? ev.target, i18n.ts.background).then(async (file) => {
+			let originalOrCropped = file;
+
+			const { canceled } = await os.confirm({
+				type: 'question',
+				text: i18n.ts.cropImageAsk,
+				okText: i18n.ts.cropYes,
+				cancelText: i18n.ts.cropNo,
 			});
-		}
 
-		const i = await os.apiWithDialog('i/update', {
-			backgroundId: originalOrCropped.id,
+			if (!canceled) {
+				originalOrCropped = await os.cropImage(file, {
+					aspectRatio: 1,
+				});
+			}
+
+			const i = await os.apiWithDialog('i/update', {
+				backgroundId: originalOrCropped.id,
+			});
+			$i.backgroundId = i.backgroundId;
+			$i.backgroundUrl = i.backgroundUrl;
+			globalEvents.emit('requestClearPageCache');
 		});
-		$i.backgroundId = i.backgroundId;
-		$i.backgroundUrl = i.backgroundUrl;
-	});
+	}
 }
 
 const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.profile,
 	icon: 'ph-user ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/settings/roles.vue b/packages/frontend/src/pages/settings/roles.vue
index 716b168c92..273cf013f0 100644
--- a/packages/frontend/src/pages/settings/roles.vue
+++ b/packages/frontend/src/pages/settings/roles.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -27,24 +27,20 @@ import { computed } from 'vue';
 import FormSection from '@/components/form/section.vue';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
-import { $i } from '@/account.js';
+import { signinRequired } from '@/account.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import MkRolePreview from '@/components/MkRolePreview.vue';
 
-function save() {
-	os.apiWithDialog('i/update', {
-
-	});
-}
+const $i = signinRequired();
 
 const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.roles,
 	icon: 'ph-seal-check ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/settings/security.vue b/packages/frontend/src/pages/settings/security.vue
index 9ae479e6e4..43e5104d71 100644
--- a/packages/frontend/src/pages/settings/security.vue
+++ b/packages/frontend/src/pages/settings/security.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -47,6 +47,7 @@ import FormSlot from '@/components/form/slot.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkPagination from '@/components/MkPagination.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
@@ -92,7 +93,7 @@ async function regenerateToken() {
 	const auth = await os.authenticateDialog();
 	if (auth.canceled) return;
 
-	os.api('i/regenerate-token', {
+	misskeyApi('i/regenerate-token', {
 		password: auth.result.password,
 		token: auth.result.token,
 	});
@@ -102,10 +103,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.security,
 	icon: 'ph-lock ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/pages/settings/sounds.sound.vue b/packages/frontend/src/pages/settings/sounds.sound.vue
index a43ffb1f0b..307c5eaae4 100644
--- a/packages/frontend/src/pages/settings/sounds.sound.vue
+++ b/packages/frontend/src/pages/settings/sounds.sound.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -32,7 +32,8 @@ import MkButton from '@/components/MkButton.vue';
 import MkRange from '@/components/MkRange.vue';
 import { i18n } from '@/i18n.js';
 import * as os from '@/os.js';
-import { playFile, soundsTypes, getSoundDuration } from '@/scripts/sound.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
+import { playMisskeySfxFile, soundsTypes, getSoundDuration } from '@/scripts/sound.js';
 import { selectFile } from '@/scripts/select-file.js';
 
 const props = defineProps<{
@@ -53,7 +54,7 @@ const fileName = ref<string>('');
 const volume = ref(props.volume);
 
 if (type.value === '_driveFile_' && fileId.value) {
-	const apiRes = await os.api('drive/files/show', {
+	const apiRes = await misskeyApi('drive/files/show', {
 		fileId: fileId.value,
 	});
 	fileName.value = apiRes.name;
@@ -118,7 +119,7 @@ function listen() {
 		return;
 	}
 
-	playFile(type.value === '_driveFile_' ? {
+	playMisskeySfxFile(type.value === '_driveFile_' ? {
 		type: '_driveFile_',
 		fileId: fileId.value as string,
 		fileUrl: fileUrl.value as string,
diff --git a/packages/frontend/src/pages/settings/sounds.vue b/packages/frontend/src/pages/settings/sounds.vue
index bec41a6cec..bf398ac303 100644
--- a/packages/frontend/src/pages/settings/sounds.vue
+++ b/packages/frontend/src/pages/settings/sounds.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<template #label>{{ i18n.ts.sounds }}</template>
 		<div class="_gaps_s">
 			<MkFolder v-for="type in operationTypes" :key="type">
-				<template #label>{{ i18n.t('_sfx.' + type) }}</template>
+				<template #label>{{ i18n.ts._sfx[type] }}</template>
 				<template #suffix>{{ getSoundTypeName(sounds[type].type) }}</template>
 
 				<XSound :type="sounds[type].type" :volume="sounds[type].volume" :fileId="sounds[type].fileId" :fileUrl="sounds[type].fileUrl" @update="(res) => updated(type, res)"/>
@@ -33,9 +33,9 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import { Ref, computed, ref } from 'vue';
+import XSound from './sounds.sound.vue';
 import type { SoundType, OperationType } from '@/scripts/sound.js';
 import type { SoundStore } from '@/store.js';
-import XSound from './sounds.sound.vue';
 import MkRange from '@/components/MkRange.vue';
 import MkButton from '@/components/MkButton.vue';
 import FormSection from '@/components/form/section.vue';
@@ -94,8 +94,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.sounds,
 	icon: 'ph-music-notes ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/statusbar.statusbar.vue b/packages/frontend/src/pages/settings/statusbar.statusbar.vue
index de5f1a3db9..92e389a288 100644
--- a/packages/frontend/src/pages/settings/statusbar.statusbar.vue
+++ b/packages/frontend/src/pages/settings/statusbar.statusbar.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/pages/settings/statusbar.vue b/packages/frontend/src/pages/settings/statusbar.vue
index c45e386ac5..fa924d13f0 100644
--- a/packages/frontend/src/pages/settings/statusbar.vue
+++ b/packages/frontend/src/pages/settings/statusbar.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -21,7 +21,7 @@ import { v4 as uuid } from 'uuid';
 import XStatusbar from './statusbar.statusbar.vue';
 import MkFolder from '@/components/MkFolder.vue';
 import MkButton from '@/components/MkButton.vue';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { defaultStore } from '@/store.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
@@ -31,7 +31,7 @@ const statusbars = defaultStore.reactiveState.statusbars;
 const userLists = ref<Misskey.entities.UserList[] | null>(null);
 
 onMounted(() => {
-	os.api('users/lists/list').then(res => {
+	misskeyApi('users/lists/list').then(res => {
 		userLists.value = res;
 	});
 });
@@ -50,8 +50,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.statusbar,
 	icon: 'ph-list ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/theme.install.vue b/packages/frontend/src/pages/settings/theme.install.vue
index d377590b9d..01ae5286b7 100644
--- a/packages/frontend/src/pages/settings/theme.install.vue
+++ b/packages/frontend/src/pages/settings/theme.install.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -33,7 +33,7 @@ async function install(code: string): Promise<void> {
 		await installTheme(code);
 		os.alert({
 			type: 'success',
-			text: i18n.t('_theme.installed', { name: theme.name }),
+			text: i18n.tsx._theme.installed({ name: theme.name }),
 		});
 	} catch (err) {
 		switch (err.message.toLowerCase()) {
@@ -59,8 +59,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts._theme.install,
 	icon: 'ph-download ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/theme.manage.vue b/packages/frontend/src/pages/settings/theme.manage.vue
index f7856d122f..43d76951c0 100644
--- a/packages/frontend/src/pages/settings/theme.manage.vue
+++ b/packages/frontend/src/pages/settings/theme.manage.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -76,8 +76,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts._theme.manage,
 	icon: 'ph-wrench ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/theme.vue b/packages/frontend/src/pages/settings/theme.vue
index cb9c714441..9b493f5ffe 100644
--- a/packages/frontend/src/pages/settings/theme.vue
+++ b/packages/frontend/src/pages/settings/theme.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -88,6 +88,18 @@ import { uniqueBy } from '@/scripts/array.js';
 import { fetchThemes, getThemes } from '@/theme-store.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { miLocalStorage } from '@/local-storage.js';
+import { unisonReload } from '@/scripts/unison-reload.js';
+import * as os from '@/os.js';
+
+async function reloadAsk() {
+	const { canceled } = await os.confirm({
+		type: 'info',
+		text: i18n.ts.reloadToApplySetting,
+	});
+	if (canceled) return;
+
+	unisonReload();
+}
 
 const installedThemes = ref(getThemes());
 const builtinThemes = getBuiltinThemesRef();
@@ -124,6 +136,7 @@ const lightThemeId = computed({
 		}
 	},
 });
+
 const darkMode = computed(defaultStore.makeGetterSetter('darkMode'));
 const syncDeviceDarkMode = computed(ColdDeviceStorage.makeGetterSetter('syncDeviceDarkMode'));
 const wallpaper = ref(miLocalStorage.getItem('wallpaper'));
@@ -141,7 +154,7 @@ watch(wallpaper, () => {
 	} else {
 		miLocalStorage.setItem('wallpaper', wallpaper.value);
 	}
-	location.reload();
+	reloadAsk();
 });
 
 onActivated(() => {
@@ -164,10 +177,10 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.theme,
 	icon: 'ph-palette ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/pages/settings/webhook.edit.vue b/packages/frontend/src/pages/settings/webhook.edit.vue
index f6e2f63317..99326c8671 100644
--- a/packages/frontend/src/pages/settings/webhook.edit.vue
+++ b/packages/frontend/src/pages/settings/webhook.edit.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -48,9 +48,10 @@ import FormSection from '@/components/form/section.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
-import { useRouter } from '@/router.js';
+import { useRouter } from '@/router/supplier.js';
 
 const router = useRouter();
 
@@ -58,7 +59,7 @@ const props = defineProps<{
 	webhookId: string;
 }>();
 
-const webhook = await os.api('i/webhooks/show', {
+const webhook = await misskeyApi('i/webhooks/show', {
 	webhookId: props.webhookId,
 });
 
@@ -98,7 +99,7 @@ async function save(): Promise<void> {
 async function del(): Promise<void> {
 	const { canceled } = await os.confirm({
 		type: 'warning',
-		text: i18n.t('deleteAreYouSure', { x: webhook.name }),
+		text: i18n.tsx.deleteAreYouSure({ x: webhook.name }),
 	});
 	if (canceled) return;
 
@@ -113,8 +114,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: 'Edit webhook',
 	icon: 'ph-webhooks-logo ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/webhook.new.vue b/packages/frontend/src/pages/settings/webhook.new.vue
index 032796caf0..299386338a 100644
--- a/packages/frontend/src/pages/settings/webhook.new.vue
+++ b/packages/frontend/src/pages/settings/webhook.new.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -82,8 +82,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: 'Create new webhook',
 	icon: 'ph-webhooks-logo ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/settings/webhook.vue b/packages/frontend/src/pages/settings/webhook.vue
index c391458274..3717abb13e 100644
--- a/packages/frontend/src/pages/settings/webhook.vue
+++ b/packages/frontend/src/pages/settings/webhook.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -50,8 +50,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: 'Webhook',
 	icon: 'ph-webhooks-logo ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/share.vue b/packages/frontend/src/pages/share.vue
index a978be0ae5..1eeeb587eb 100644
--- a/packages/frontend/src/pages/share.vue
+++ b/packages/frontend/src/pages/share.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -37,6 +37,7 @@ import * as Misskey from 'misskey-js';
 import MkButton from '@/components/MkButton.vue';
 import MkPostForm from '@/components/MkPostForm.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { postMessageToParentWindow } from '@/scripts/post-message.js';
 import { i18n } from '@/i18n.js';
@@ -55,7 +56,7 @@ const renote = ref<Misskey.entities.Note | undefined>();
 const visibility = ref(Misskey.noteVisibilities.includes(visibilityQuery) ? visibilityQuery : undefined);
 const localOnly = ref(localOnlyQuery === '0' ? false : localOnlyQuery === '1' ? true : undefined);
 const files = ref([] as Misskey.entities.DriveFile[]);
-const visibleUsers = ref([] as Misskey.entities.User[]);
+const visibleUsers = ref([] as Misskey.entities.UserDetailed[]);
 
 async function init() {
 	let noteText = '';
@@ -76,7 +77,7 @@ async function init() {
 			]
 			// TypeScriptの指示通りに変換する
 				.map(q => 'username' in q ? { username: q.username, host: q.host === null ? undefined : q.host } : q)
-				.map(q => os.api('users/show', q)
+				.map(q => misskeyApi('users/show', q)
 					.then(user => {
 						visibleUsers.value.push(user);
 					}, () => {
@@ -91,11 +92,11 @@ async function init() {
 		const replyId = urlParams.get('replyId');
 		const replyUri = urlParams.get('replyUri');
 		if (replyId) {
-			reply.value = await os.api('notes/show', {
+			reply.value = await misskeyApi('notes/show', {
 				noteId: replyId,
 			});
 		} else if (replyUri) {
-			const obj = await os.api('ap/show', {
+			const obj = await misskeyApi('ap/show', {
 				uri: replyUri,
 			});
 			if (obj.type === 'Note') {
@@ -108,11 +109,11 @@ async function init() {
 		const renoteId = urlParams.get('renoteId');
 		const renoteUri = urlParams.get('renoteUri');
 		if (renoteId) {
-			renote.value = await os.api('notes/show', {
+			renote.value = await misskeyApi('notes/show', {
 				noteId: renoteId,
 			});
 		} else if (renoteUri) {
-			const obj = await os.api('ap/show', {
+			const obj = await misskeyApi('ap/show', {
 				uri: renoteUri,
 			});
 			if (obj.type === 'Note') {
@@ -126,7 +127,7 @@ async function init() {
 		if (fileIds) {
 			await Promise.all(
 				fileIds.split(',')
-					.map(fileId => os.api('drive/files/show', { fileId })
+					.map(fileId => misskeyApi('drive/files/show', { fileId })
 						.then(file => {
 							files.value.push(file);
 						}, () => {
@@ -171,8 +172,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.share,
 	icon: 'ph-share-network ph-bold ph-lg',
-});
+}));
 </script>
diff --git a/packages/frontend/src/pages/signup-complete.vue b/packages/frontend/src/pages/signup-complete.vue
index 4009652bcf..b08a304cfd 100644
--- a/packages/frontend/src/pages/signup-complete.vue
+++ b/packages/frontend/src/pages/signup-complete.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<i class="ph-check ph-bold ph-lg"></i>
 			</div>
 			<div class="_gaps_m" style="padding: 32px;">
-				<div>{{ i18n.t('clickToFinishEmailVerification', { ok: i18n.ts.gotIt }) }}</div>
+				<div>{{ i18n.tsx.clickToFinishEmailVerification({ ok: i18n.ts.gotIt }) }}</div>
 				<div>
 					<MkButton gradate large rounded type="submit" :disabled="submitting" data-cy-admin-ok style="margin: 0 auto;">
 						{{ submitting ? i18n.ts.processing : i18n.ts.gotIt }}<MkEllipsis v-if="submitting"/>
@@ -31,6 +31,7 @@ import MkAnimBg from '@/components/MkAnimBg.vue';
 import { login } from '@/account.js';
 import { i18n } from '@/i18n.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 
 const submitting = ref(false);
 
@@ -42,7 +43,7 @@ function submit() {
 	if (submitting.value) return;
 	submitting.value = true;
 
-	os.api('signup-pending', {
+	misskeyApi('signup-pending', {
 		code: props.code,
 	}).then(res => {
 		if (res.pendingApproval) {
diff --git a/packages/frontend/src/pages/tag.vue b/packages/frontend/src/pages/tag.vue
index 167816638c..d9c94569a7 100644
--- a/packages/frontend/src/pages/tag.vue
+++ b/packages/frontend/src/pages/tag.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<template v-if="$i" #footer>
 		<div :class="$style.footer">
 			<MkSpacer :contentMax="800" :marginMin="16" :marginMax="16">
-				<MkButton rounded primary :class="$style.button" @click="post()"><i class="ph-pencil ph-bold ph-lg"></i>{{ i18n.ts.postToHashtag }}</MkButton>
+				<MkButton rounded primary :class="$style.button" @click="post()"><i class="ph-pencil-simple ph-bold ph-lg"></i>{{ i18n.ts.postToHashtag }}</MkButton>
 			</MkSpacer>
 		</div>
 	</template>
@@ -55,21 +55,22 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: props.tag,
 	icon: 'ph-hash ph-bold ph-lg',
-})));
+}));
 </script>
 
 <style lang="scss" module>
 .footer {
 	-webkit-backdrop-filter: var(--blur, blur(15px));
 	backdrop-filter: var(--blur, blur(15px));
+	background: var(--acrylicBg);
 	border-top: solid 0.5px var(--divider);
 	display: flex;
 }
 
 .button {
-		margin: 0 auto var(--margin) auto;
+	margin: 0 auto;
 }
 </style>
diff --git a/packages/frontend/src/pages/theme-editor.vue b/packages/frontend/src/pages/theme-editor.vue
index 4b4196d0a9..d020320b44 100644
--- a/packages/frontend/src/pages/theme-editor.vue
+++ b/packages/frontend/src/pages/theme-editor.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -186,7 +186,7 @@ function applyThemeCode() {
 async function saveAs() {
 	const { canceled, result: name } = await os.inputText({
 		title: i18n.ts.name,
-		allowEmpty: false,
+		minLength: 1,
 	});
 	if (canceled) return;
 
@@ -204,7 +204,7 @@ async function saveAs() {
 	changed.value = false;
 	os.alert({
 		type: 'success',
-		text: i18n.t('_theme.installed', { name: theme.value.name }),
+		text: i18n.tsx._theme.installed({ name: theme.value.name }),
 	});
 }
 
@@ -219,10 +219,10 @@ const headerActions = computed(() => [{
 
 const headerTabs = computed(() => []);
 
-definePageMetadata({
+definePageMetadata(() => ({
 	title: i18n.ts.themeEditor,
 	icon: 'ph-palette ph-bold ph-lg',
-});
+}));
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue
index f5cefeddb4..a9f7a163f6 100644
--- a/packages/frontend/src/pages/timeline.vue
+++ b/packages/frontend/src/pages/timeline.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -7,28 +7,29 @@ SPDX-License-Identifier: AGPL-3.0-only
 <MkStickyContainer>
 	<template #header><MkPageHeader v-model:tab="src" :actions="headerActions" :tabs="$i ? headerTabs : headerTabsWhenNotLogin" :displayMyAvatar="true"/></template>
 	<MkSpacer :contentMax="800">
-		<div ref="rootEl" v-hotkey.global="keymap">
-			<MkInfo v-if="['home', 'local', 'social', 'global'].includes(src) && !defaultStore.reactiveState.timelineTutorials.value[src]" style="margin-bottom: var(--margin);" closable @close="closeTutorial()">
-				{{ i18n.ts._timelineDescription[src] }}
-			</MkInfo>
-			<MkPostForm v-if="defaultStore.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--margin);"/>
-
-			<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
-			<div :class="$style.tl">
-				<MkTimeline
-					ref="tlComponent"
-					:key="src + withRenotes + withReplies + onlyFiles"
-					:src="src.split(':')[0]"
-					:list="src.split(':')[1]"
-					:withRenotes="withRenotes"
-					:withReplies="withReplies"
-					:onlyFiles="onlyFiles"
-					:withBots="withBots"
-					:sound="true"
-					@queue="queueUpdated"
-				/>
+		<MkHorizontalSwipe v-model:tab="src" :tabs="$i ? headerTabs : headerTabsWhenNotLogin">
+			<div :key="src" ref="rootEl" v-hotkey.global="keymap">
+				<MkInfo v-if="['home', 'local', 'social', 'global'].includes(src) && !defaultStore.reactiveState.timelineTutorials.value[src]" style="margin-bottom: var(--margin);" closable @close="closeTutorial()">
+					{{ i18n.ts._timelineDescription[src] }}
+				</MkInfo>
+				<MkPostForm v-if="defaultStore.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--margin);"/>
+				<div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div>
+				<div :class="$style.tl">
+					<MkTimeline
+						ref="tlComponent"
+						:key="src + withRenotes + withReplies + onlyFiles"
+						:src="src.split(':')[0]"
+						:list="src.split(':')[1]"
+						:withRenotes="withRenotes"
+						:withReplies="withReplies"
+						:onlyFiles="onlyFiles"
+						:withBots="withBots"
+						:sound="true"
+						@queue="queueUpdated"
+					/>
+				</div>
 			</div>
-		</div>
+		</MkHorizontalSwipe>
 	</MkSpacer>
 </MkStickyContainer>
 </template>
@@ -39,8 +40,10 @@ import type { Tab } from '@/components/global/MkPageHeader.tabs.vue';
 import MkTimeline from '@/components/MkTimeline.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import MkPostForm from '@/components/MkPostForm.vue';
+import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
 import { scroll } from '@/scripts/scroll.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { defaultStore } from '@/store.js';
 import { i18n } from '@/i18n.js';
 import { instance } from '@/instance.js';
@@ -48,6 +51,7 @@ import { $i } from '@/account.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { antennasCache, userListsCache } from '@/cache.js';
 import { deviceKind } from '@/scripts/device-kind.js';
+import { deepMerge } from '@/scripts/merge.js';
 import { MenuItem } from '@/types/menu.js';
 import { miLocalStorage } from '@/local-storage.js';
 
@@ -64,17 +68,68 @@ const tlComponent = shallowRef<InstanceType<typeof MkTimeline>>();
 const rootEl = shallowRef<HTMLElement>();
 
 const queue = ref(0);
-const srcWhenNotSignin = ref(isLocalTimelineAvailable ? 'local' : 'global');
-const src = computed({ get: () => ($i ? defaultStore.reactiveState.tl.value.src : srcWhenNotSignin.value), set: (x) => saveSrc(x) });
-const withRenotes = ref(true);
-const withReplies = ref($i ? defaultStore.state.tlWithReplies : false);
-const withBots = ref($i ? defaultStore.state.tlWithBots : true);
-const onlyFiles = ref(false);
+const srcWhenNotSignin = ref<'local' | 'global'>(isLocalTimelineAvailable ? 'local' : 'global');
+const src = computed<'home' | 'local' | 'social' | 'global' | 'bubble' | `list:${string}`>({
+	get: () => ($i ? defaultStore.reactiveState.tl.value.src : srcWhenNotSignin.value),
+	set: (x) => saveSrc(x),
+});
+const withRenotes = computed<boolean>({
+	get: () => defaultStore.reactiveState.tl.value.filter.withRenotes,
+	set: (x) => saveTlFilter('withRenotes', x),
+});
 
-watch(src, () => queue.value = 0);
+// computed内での無限ループを防ぐためのフラグ
+const localSocialTLFilterSwitchStore = ref<'withReplies' | 'onlyFiles' | false>('withReplies');
 
-watch(withReplies, (x) => {
-	if ($i) defaultStore.set('tlWithReplies', x);
+const withReplies = computed<boolean>({
+	get: () => {
+		if (!$i) return false;
+		if (['local', 'social'].includes(src.value) && localSocialTLFilterSwitchStore.value === 'onlyFiles') {
+			return false;
+		} else {
+			return defaultStore.reactiveState.tl.value.filter.withReplies;
+		}
+	},
+	set: (x) => saveTlFilter('withReplies', x),
+});
+const withBots = computed<boolean>({
+	// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+	get: () => (defaultStore.reactiveState.tl.value.filter?.withBots ?? saveTlFilter('withBots', true)),
+	set: (x) => saveTlFilter('withBots', x),
+});
+const onlyFiles = computed<boolean>({
+	get: () => {
+		if (['local', 'social'].includes(src.value) && localSocialTLFilterSwitchStore.value === 'withReplies') {
+			return false;
+		} else {
+			return defaultStore.reactiveState.tl.value.filter.onlyFiles;
+		}
+	},
+	set: (x) => saveTlFilter('onlyFiles', x),
+});
+
+watch([withReplies, onlyFiles], ([withRepliesTo, onlyFilesTo]) => {
+	if (withRepliesTo) {
+		localSocialTLFilterSwitchStore.value = 'withReplies';
+	} else if (onlyFilesTo) {
+		localSocialTLFilterSwitchStore.value = 'onlyFiles';
+	} else {
+		localSocialTLFilterSwitchStore.value = false;
+	}
+});
+
+const withSensitive = computed<boolean>({
+	get: () => defaultStore.reactiveState.tl.value.filter.withSensitive,
+	set: (x) => saveTlFilter('withSensitive', x),
+});
+
+watch(src, () => {
+	queue.value = 0;
+});
+
+watch(withSensitive, () => {
+	// これだけはクライアント側で完結する処理なので手動でリロード
+	tlComponent.value?.reloadTimeline();
 });
 
 function queueUpdated(q: number): void {
@@ -125,7 +180,7 @@ async function chooseAntenna(ev: MouseEvent): Promise<void> {
 }
 
 async function chooseChannel(ev: MouseEvent): Promise<void> {
-	const channels = await os.api('channels/my-favorites', {
+	const channels = await misskeyApi('channels/my-favorites', {
 		limit: 100,
 	});
 	const items: MenuItem[] = [
@@ -152,16 +207,24 @@ async function chooseChannel(ev: MouseEvent): Promise<void> {
 }
 
 function saveSrc(newSrc: 'home' | 'local' | 'social' | 'global' | 'bubble' | `list:${string}`): void {
-	let userList = null;
+	const out = deepMerge({ src: newSrc }, defaultStore.state.tl);
+
 	if (newSrc.startsWith('userList:')) {
 		const id = newSrc.substring('userList:'.length);
-		userList = defaultStore.reactiveState.pinnedUserLists.value.find(l => l.id === id);
+		out.userList = defaultStore.reactiveState.pinnedUserLists.value.find(l => l.id === id) ?? null;
+	}
+
+	defaultStore.set('tl', out);
+	if (['local', 'global'].includes(newSrc)) {
+		srcWhenNotSignin.value = newSrc as 'local' | 'global';
+	}
+}
+
+function saveTlFilter(key: keyof typeof defaultStore.state.tl.filter, newValue: boolean) {
+	if (key !== 'withReplies' || $i) {
+		const out = deepMerge({ filter: { [key]: newValue } }, defaultStore.state.tl);
+		defaultStore.set('tl', out);
 	}
-	defaultStore.set('tl', {
-		src: newSrc,
-		userList,
-	});
-	srcWhenNotSignin.value = newSrc;
 }
 
 async function timetravel(): Promise<void> {
@@ -200,6 +263,10 @@ const headerActions = computed(() => {
 					ref: withReplies,
 					disabled: onlyFiles,
 				} : undefined, {
+					type: 'switch',
+					text: i18n.ts.withSensitive,
+					ref: withSensitive,
+				}, {
 					type: 'switch',
 					text: i18n.ts.fileAttachedOnly,
 					ref: onlyFiles,
@@ -213,8 +280,7 @@ const headerActions = computed(() => {
 			icon: 'ph-arrows-counter-clockwise ph-bold ph-lg',
 			text: i18n.ts.reload,
 			handler: (ev: Event) => {
-				console.log('called');
-				tlComponent.value.reloadTimeline();
+				tlComponent.value?.reloadTimeline();
 			},
 		});
 	}
@@ -283,10 +349,10 @@ const headerTabsWhenNotLogin = computed(() => [
 	}] : []),
 ] as Tab[]);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: i18n.ts.timeline,
 	icon: src.value === 'local' ? 'ph-planet ph-bold ph-lg' : src.value === 'social' ? 'ph-rocket-launch ph-bold ph-lg' : src.value === 'global' ? 'ph-globe-hemisphere-west ph-bold ph-lg' : src.value === 'bubble' ? 'ph-drop ph-bold ph-lg' : 'ph-house ph-bold ph-lg',
-})));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/user-list-timeline.vue b/packages/frontend/src/pages/user-list-timeline.vue
index 3ec23df7b8..dd0b7fb675 100644
--- a/packages/frontend/src/pages/user-list-timeline.vue
+++ b/packages/frontend/src/pages/user-list-timeline.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -28,10 +28,10 @@ import { computed, watch, ref, shallowRef } from 'vue';
 import * as Misskey from 'misskey-js';
 import MkTimeline from '@/components/MkTimeline.vue';
 import { scroll } from '@/scripts/scroll.js';
-import * as os from '@/os.js';
-import { useRouter } from '@/router.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { i18n } from '@/i18n.js';
+import { useRouter } from '@/router/supplier.js';
 
 const router = useRouter();
 
@@ -45,7 +45,7 @@ const tlEl = shallowRef<InstanceType<typeof MkTimeline>>();
 const rootEl = shallowRef<HTMLElement>();
 
 watch(() => props.listId, async () => {
-	list.value = await os.api('users/lists/show', {
+	list.value = await misskeyApi('users/lists/show', {
 		listId: props.listId,
 	});
 }, { immediate: true });
@@ -70,10 +70,10 @@ const headerActions = computed(() => list.value ? [{
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => list.value ? {
-	title: list.value.name,
+definePageMetadata(() => ({
+	title: list.value ? list.value.name : i18n.ts.lists,
 	icon: 'ph-list ph-bold ph-lg',
-} : null));
+}));
 </script>
 
 <style lang="scss" module>
diff --git a/packages/frontend/src/pages/user-tag.vue b/packages/frontend/src/pages/user-tag.vue
index 7e6757bba5..b6ebfb8abc 100644
--- a/packages/frontend/src/pages/user-tag.vue
+++ b/packages/frontend/src/pages/user-tag.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -34,9 +34,9 @@ const tagUsers = computed(() => ({
 	},
 }));
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: props.tag,
 	icon: 'ph-user-circle ph-bold ph-lg',
-})));
+}));
 </script>
 
diff --git a/packages/frontend/src/pages/user/achievements.vue b/packages/frontend/src/pages/user/achievements.vue
index 4e14443074..403e74904c 100644
--- a/packages/frontend/src/pages/user/achievements.vue
+++ b/packages/frontend/src/pages/user/achievements.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/pages/user/activity.following.vue b/packages/frontend/src/pages/user/activity.following.vue
index bd1159cb32..aa2c791c76 100644
--- a/packages/frontend/src/pages/user/activity.following.vue
+++ b/packages/frontend/src/pages/user/activity.following.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -18,7 +18,7 @@ import { onMounted, shallowRef, ref } from 'vue';
 import { Chart, ChartDataset } from 'chart.js';
 import * as Misskey from 'misskey-js';
 import gradient from 'chartjs-plugin-gradient';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { defaultStore } from '@/store.js';
 import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
 import { chartVLine } from '@/scripts/chart-vline.js';
@@ -61,7 +61,7 @@ async function renderChart() {
 		}));
 	};
 
-	const raw = await os.api('charts/user/following', { userId: props.user.id, limit: chartLimit, span: 'day' });
+	const raw = await misskeyApi('charts/user/following', { userId: props.user.id, limit: chartLimit, span: 'day' });
 
 	const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
 
diff --git a/packages/frontend/src/pages/user/activity.heatmap.vue b/packages/frontend/src/pages/user/activity.heatmap.vue
deleted file mode 100644
index ff46db9653..0000000000
--- a/packages/frontend/src/pages/user/activity.heatmap.vue
+++ /dev/null
@@ -1,219 +0,0 @@
-<!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
-SPDX-License-Identifier: AGPL-3.0-only
--->
-
-<template>
-<div ref="rootEl">
-	<MkLoading v-if="fetching"/>
-	<div v-else :class="$style.root" class="_panel">
-		<canvas ref="chartEl"></canvas>
-	</div>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { onMounted, nextTick, watch, shallowRef, ref } from 'vue';
-import { Chart } from 'chart.js';
-import * as Misskey from 'misskey-js';
-import * as os from '@/os.js';
-import { defaultStore } from '@/store.js';
-import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
-import { alpha } from '@/scripts/color.js';
-import { initChart } from '@/scripts/init-chart.js';
-
-initChart();
-
-const props = defineProps<{
-	src: string;
-	user: Misskey.entities.User;
-}>();
-
-const rootEl = shallowRef<HTMLDivElement>(null);
-const chartEl = shallowRef<HTMLCanvasElement>(null);
-const now = new Date();
-let chartInstance: Chart = null;
-const fetching = ref(true);
-
-const { handler: externalTooltipHandler } = useChartTooltip({
-	position: 'middle',
-});
-
-async function renderChart() {
-	if (chartInstance) {
-		chartInstance.destroy();
-	}
-
-	const wide = rootEl.value.offsetWidth > 700;
-	const narrow = rootEl.value.offsetWidth < 400;
-
-	const weeks = wide ? 50 : narrow ? 10 : 25;
-	const chartLimit = 7 * weeks;
-
-	const getDate = (ago: number) => {
-		const y = now.getFullYear();
-		const m = now.getMonth();
-		const d = now.getDate();
-
-		return new Date(y, m, d - ago);
-	};
-
-	const format = (arr) => {
-		return arr.map((v, i) => {
-			const dt = getDate(i);
-			const iso = `${dt.getFullYear()}-${(dt.getMonth() + 1).toString().padStart(2, '0')}-${dt.getDate().toString().padStart(2, '0')}`;
-			return {
-				x: iso,
-				y: dt.getDay(),
-				d: iso,
-				v,
-			};
-		});
-	};
-
-	let values;
-
-	if (props.src === 'notes') {
-		const raw = await os.api('charts/user/notes', { userId: props.user.id, limit: chartLimit, span: 'day' });
-		values = raw.inc;
-	}
-
-	fetching.value = false;
-
-	await nextTick();
-
-	const color = defaultStore.state.darkMode ? '#b4e900' : '#86b300';
-
-	// 視覚上の分かりやすさのため上から最も大きい3つの値の平均を最大値とする
-	const max = values.slice().sort((a, b) => b - a).slice(0, 3).reduce((a, b) => a + b, 0) / 3;
-
-	const min = Math.max(0, Math.min(...values) - 1);
-
-	const marginEachCell = 4;
-
-	chartInstance = new Chart(chartEl.value, {
-		type: 'matrix',
-		data: {
-			datasets: [{
-				label: '',
-				data: format(values),
-				pointRadius: 0,
-				borderWidth: 0,
-				borderJoinStyle: 'round',
-				borderRadius: 3,
-				backgroundColor(c) {
-					const value = c.dataset.data[c.dataIndex].v;
-					let a = (value - min) / max;
-					if (value !== 0) { // 0でない限りは完全に不可視にはしない
-						a = Math.max(a, 0.05);
-					}
-					return alpha(color, a);
-				},
-				fill: true,
-				width(c) {
-					const a = c.chart.chartArea ?? {};
-					return (a.right - a.left) / weeks - marginEachCell;
-				},
-				height(c) {
-					const a = c.chart.chartArea ?? {};
-					return (a.bottom - a.top) / 7 - marginEachCell;
-				},
-			/* @see <https://github.com/misskey-dev/misskey/pull/10365#discussion_r1155511107>
-			}] satisfies ChartData[],
-			 */
-			}],
-		},
-		options: {
-			aspectRatio: wide ? 6 : narrow ? 1.8 : 3.2,
-			layout: {
-				padding: {
-					left: 8,
-					right: 0,
-					top: 0,
-					bottom: 0,
-				},
-			},
-			scales: {
-				x: {
-					type: 'time',
-					offset: true,
-					position: 'bottom',
-					time: {
-						unit: 'week',
-						round: 'week',
-						isoWeekday: 0,
-						displayFormats: {
-							day: 'M/d',
-							month: 'Y/M',
-							week: 'M/d',
-						},
-					},
-					grid: {
-						display: false,
-					},
-					ticks: {
-						display: true,
-						maxRotation: 0,
-						autoSkipPadding: 8,
-					},
-				},
-				y: {
-					offset: true,
-					reverse: true,
-					position: 'right',
-					grid: {
-						display: false,
-					},
-					ticks: {
-						maxRotation: 0,
-						autoSkip: true,
-						padding: 1,
-						font: {
-							size: 9,
-						},
-						callback: (value, index, values) => ['', 'Mon', '', 'Wed', '', 'Fri', ''][value],
-					},
-				},
-			},
-			plugins: {
-				legend: {
-					display: false,
-				},
-				tooltip: {
-					enabled: false,
-					callbacks: {
-						title(context) {
-							const v = context[0].dataset.data[context[0].dataIndex];
-							return v.d;
-						},
-						label(context) {
-							const v = context.dataset.data[context.dataIndex];
-							return [v.v];
-						},
-					},
-					//mode: 'index',
-					animation: {
-						duration: 0,
-					},
-					external: externalTooltipHandler,
-				},
-			},
-		},
-	});
-}
-
-watch(() => props.src, () => {
-	fetching.value = true;
-	renderChart();
-});
-
-onMounted(async () => {
-	renderChart();
-});
-</script>
-
-<style lang="scss" module>
-.root {
-	padding: 20px;
-}
-</style>
diff --git a/packages/frontend/src/pages/user/activity.notes.vue b/packages/frontend/src/pages/user/activity.notes.vue
index dd035641d8..64514716d6 100644
--- a/packages/frontend/src/pages/user/activity.notes.vue
+++ b/packages/frontend/src/pages/user/activity.notes.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -18,7 +18,7 @@ import { onMounted, shallowRef, ref } from 'vue';
 import { Chart, ChartDataset } from 'chart.js';
 import * as Misskey from 'misskey-js';
 import gradient from 'chartjs-plugin-gradient';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { defaultStore } from '@/store.js';
 import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
 import { chartVLine } from '@/scripts/chart-vline.js';
@@ -61,7 +61,7 @@ async function renderChart() {
 		}));
 	};
 
-	const raw = await os.api('charts/user/notes', { userId: props.user.id, limit: chartLimit, span: 'day' });
+	const raw = await misskeyApi('charts/user/notes', { userId: props.user.id, limit: chartLimit, span: 'day' });
 
 	const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
 
diff --git a/packages/frontend/src/pages/user/activity.pv.vue b/packages/frontend/src/pages/user/activity.pv.vue
index 2dd9a1570f..ce24807f93 100644
--- a/packages/frontend/src/pages/user/activity.pv.vue
+++ b/packages/frontend/src/pages/user/activity.pv.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -18,7 +18,7 @@ import { onMounted, shallowRef, ref } from 'vue';
 import { Chart, ChartDataset } from 'chart.js';
 import * as Misskey from 'misskey-js';
 import gradient from 'chartjs-plugin-gradient';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { defaultStore } from '@/store.js';
 import { useChartTooltip } from '@/scripts/use-chart-tooltip.js';
 import { chartVLine } from '@/scripts/chart-vline.js';
@@ -61,7 +61,7 @@ async function renderChart() {
 		}));
 	};
 
-	const raw = await os.api('charts/user/pv', { userId: props.user.id, limit: chartLimit, span: 'day' });
+	const raw = await misskeyApi('charts/user/pv', { userId: props.user.id, limit: chartLimit, span: 'day' });
 
 	const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)';
 
diff --git a/packages/frontend/src/pages/user/activity.vue b/packages/frontend/src/pages/user/activity.vue
index 42035cc619..271631e8d1 100644
--- a/packages/frontend/src/pages/user/activity.vue
+++ b/packages/frontend/src/pages/user/activity.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -8,10 +8,10 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<div class="_gaps">
 		<MkFoldableSection class="item">
 			<template #header><i class="ph-pulse ph-bold ph-lg"></i> Heatmap</template>
-			<XHeatmap :user="user" :src="'notes'"/>
+			<MkHeatmap :user="user" :src="'notes'"/>
 		</MkFoldableSection>
 		<MkFoldableSection class="item">
-			<template #header><i class="ph-pencil ph-bold ph-lg"></i> Notes</template>
+			<template #header><i class="ph-pencil-simple ph-bold ph-lg"></i> Notes</template>
 			<XNotes :user="user"/>
 		</MkFoldableSection>
 		<MkFoldableSection class="item">
@@ -28,11 +28,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 <script lang="ts" setup>
 import * as Misskey from 'misskey-js';
-import XHeatmap from './activity.heatmap.vue';
 import XPv from './activity.pv.vue';
 import XNotes from './activity.notes.vue';
 import XFollowing from './activity.following.vue';
 import MkFoldableSection from '@/components/MkFoldableSection.vue';
+import MkHeatmap from '@/components/MkHeatmap.vue';
 
 const props = defineProps<{
 	user: Misskey.entities.User;
diff --git a/packages/frontend/src/pages/user/clips.vue b/packages/frontend/src/pages/user/clips.vue
index eaae472516..ac01cff8cd 100644
--- a/packages/frontend/src/pages/user/clips.vue
+++ b/packages/frontend/src/pages/user/clips.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/pages/user/flashs.vue b/packages/frontend/src/pages/user/flashs.vue
index 5e93a0b04c..b3313476e1 100644
--- a/packages/frontend/src/pages/user/flashs.vue
+++ b/packages/frontend/src/pages/user/flashs.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/pages/user/follow-list.vue b/packages/frontend/src/pages/user/follow-list.vue
index 19b7290353..e60dccec17 100644
--- a/packages/frontend/src/pages/user/follow-list.vue
+++ b/packages/frontend/src/pages/user/follow-list.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/pages/user/followers.vue b/packages/frontend/src/pages/user/followers.vue
index 36f1b4543e..e8addf88b7 100644
--- a/packages/frontend/src/pages/user/followers.vue
+++ b/packages/frontend/src/pages/user/followers.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed, watch, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import XFollowList from './follow-list.vue';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { i18n } from '@/i18n.js';
 
@@ -37,7 +37,7 @@ const error = ref<any>(null);
 function fetchUser(): void {
 	if (props.acct == null) return;
 	user.value = null;
-	os.api('users/show', Misskey.acct.parse(props.acct)).then(u => {
+	misskeyApi('users/show', Misskey.acct.parse(props.acct)).then(u => {
 		user.value = u;
 	}).catch(err => {
 		error.value = err;
@@ -52,11 +52,14 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => user.value ? {
+definePageMetadata(() => ({
+	title: i18n.ts.user,
 	icon: 'ph-user ph-bold ph-lg',
-	title: user.value.name ? `${user.value.name} (@${user.value.username})` : `@${user.value.username}`,
-	subtitle: i18n.ts.followers,
-	userName: user.value,
-	avatar: user.value,
-} : null));
+	...user.value ? {
+		title: user.value.name ? `${user.value.name} (@${user.value.username})` : `@${user.value.username}`,
+		subtitle: i18n.ts.followers,
+		userName: user.value,
+		avatar: user.value,
+	} : {},
+}));
 </script>
diff --git a/packages/frontend/src/pages/user/following.vue b/packages/frontend/src/pages/user/following.vue
index 43876b77c0..8e4da40383 100644
--- a/packages/frontend/src/pages/user/following.vue
+++ b/packages/frontend/src/pages/user/following.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { computed, watch, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import XFollowList from './follow-list.vue';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { i18n } from '@/i18n.js';
 
@@ -37,7 +37,7 @@ const error = ref<any>(null);
 function fetchUser(): void {
 	if (props.acct == null) return;
 	user.value = null;
-	os.api('users/show', Misskey.acct.parse(props.acct)).then(u => {
+	misskeyApi('users/show', Misskey.acct.parse(props.acct)).then(u => {
 		user.value = u;
 	}).catch(err => {
 		error.value = err;
@@ -52,11 +52,14 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => user.value ? {
+definePageMetadata(() => ({
+	title: i18n.ts.user,
 	icon: 'ph-user ph-bold ph-lg',
-	title: user.value.name ? `${user.value.name} (@${user.value.username})` : `@${user.value.username}`,
-	subtitle: i18n.ts.following,
-	userName: user.value,
-	avatar: user.value,
-} : null));
+	...user.value ? {
+		title: user.value.name ? `${user.value.name} (@${user.value.username})` : `@${user.value.username}`,
+		subtitle: i18n.ts.following,
+		userName: user.value,
+		avatar: user.value,
+	} : {},
+}));
 </script>
diff --git a/packages/frontend/src/pages/user/gallery.vue b/packages/frontend/src/pages/user/gallery.vue
index 0d806100d9..9ba81322ba 100644
--- a/packages/frontend/src/pages/user/gallery.vue
+++ b/packages/frontend/src/pages/user/gallery.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/pages/user/home.stories.impl.ts b/packages/frontend/src/pages/user/home.stories.impl.ts
index a2ef5d50d1..c623ef9ee4 100644
--- a/packages/frontend/src/pages/user/home.stories.impl.ts
+++ b/packages/frontend/src/pages/user/home.stories.impl.ts
@@ -1,11 +1,11 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 /* eslint-disable @typescript-eslint/explicit-function-return-type */
 import { StoryObj } from '@storybook/vue3';
-import { rest } from 'msw';
+import { HttpResponse, http } from 'msw';
 import { userDetailed } from '../../../.storybook/fakes.js';
 import { commonHandlers } from '../../../.storybook/mocks.js';
 import home_ from './home.vue';
@@ -39,12 +39,13 @@ export const Default = {
 		msw: {
 			handlers: [
 				...commonHandlers,
-				rest.post('/api/users/notes', (req, res, ctx) => {
-					return res(ctx.json([]));
+				http.post('/api/users/notes', () => {
+					return HttpResponse.json([]);
 				}),
-				rest.get('/api/charts/user/notes', (req, res, ctx) => {
-					const length = Math.max(Math.min(parseInt(req.url.searchParams.get('limit') ?? '30', 10), 1), 300);
-					return res(ctx.json({
+				http.get('/api/charts/user/notes', ({ request }) => {
+					const url = new URL(request.url);
+					const length = Math.max(Math.min(parseInt(url.searchParams.get('limit') ?? '30', 10), 1), 300);
+					return HttpResponse.json({
 						total: Array.from({ length }, () => 0),
 						inc: Array.from({ length }, () => 0),
 						dec: Array.from({ length }, () => 0),
@@ -54,11 +55,12 @@ export const Default = {
 							renote: Array.from({ length }, () => 0),
 							withFile: Array.from({ length }, () => 0),
 						},
-					}));
+					});
 				}),
-				rest.get('/api/charts/user/pv', (req, res, ctx) => {
-					const length = Math.max(Math.min(parseInt(req.url.searchParams.get('limit') ?? '30', 10), 1), 300);
-					return res(ctx.json({
+				http.get('/api/charts/user/pv', ({ request }) => {
+					const url = new URL(request.url);
+					const length = Math.max(Math.min(parseInt(url.searchParams.get('limit') ?? '30', 10), 1), 300);
+					return HttpResponse.json({
 						upv: {
 							user: Array.from({ length }, () => 0),
 							visitor: Array.from({ length }, () => 0),
@@ -67,7 +69,7 @@ export const Default = {
 							user: Array.from({ length }, () => 0),
 							visitor: Array.from({ length }, () => 0),
 						},
-					}));
+					});
 				}),
 			],
 		},
diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue
index 44a8ca250b..96ae4824f0 100644
--- a/packages/frontend/src/pages/user/home.vue
+++ b/packages/frontend/src/pages/user/home.vue
@@ -1,10 +1,10 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<MkSpacer :contentMax="narrow ? 800 : 1100" :style="background">
+<MkSpacer :contentMax="narrow ? 800 : 1100" :style="background" style="transform: none !important;">
 	<div ref="rootEl" class="ftskorzw" :class="{ wide: !narrow }" style="container-type: inline-size;">
 		<div class="main _gaps">
 			<MkInfo v-if="user.isSuspended" :warn="true">{{ i18n.ts.userSuspended }}</MkInfo>
@@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 								<span v-if="user.isLocked" :title="i18n.ts.isLocked"><i class="ph-lock ph-bold ph-lg"></i></span>
 								<span v-if="user.isBot" :title="i18n.ts.isBot"><i class="ph-robot ph-bold ph-lg"></i></span>
 								<button v-if="$i && !isEditingMemo && !memoDraft" class="_button add-note-button" @click="showMemoTextarea">
-									<i class="ph-pencil-line ph-bold ph-lg"/> {{ i18n.ts.addMemo }}
+									<i class="ph-pencil-simple-line ph-bold ph-lg"/> {{ i18n.ts.addMemo }}
 								</button>
 							</div>
 						</div>
@@ -84,7 +84,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 						</dl>
 						<dl v-if="user.birthday" class="field">
 							<dt class="name"><i class="ph-cake ph-bold ph-lg ti-fw"></i> {{ i18n.ts.birthday }}</dt>
-							<dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ i18n.t('yearsOld', { age }) }})</dd>
+							<dd class="value">{{ user.birthday.replace('-', '/').replace('-', '/') }} ({{ i18n.tsx.yearsOld({ age }) }})</dd>
 						</dl>
 						<dl class="field">
 							<dt class="name"><i class="ph-calendar ph-bold ph-lg ti-fw"></i> {{ i18n.ts.registeredDate }}</dt>
@@ -185,14 +185,14 @@ import { getUserMenu } from '@/scripts/get-user-menu.js';
 import number from '@/filters/number.js';
 import { userPage } from '@/filters/user.js';
 import * as os from '@/os.js';
-import { useRouter } from '@/router.js';
 import { i18n } from '@/i18n.js';
 import { $i, iAmModerator } from '@/account.js';
 import { dateString } from '@/filters/date.js';
 import { confetti } from '@/scripts/confetti.js';
-import { api } from '@/os.js';
 import { defaultStore } from '@/store.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/scripts/isFfVisibleForMe.js';
+import { useRouter } from '@/router/supplier.js';
 
 function calcAge(birthdate: string): number {
 	const date = new Date(birthdate);
@@ -262,7 +262,7 @@ const background = computed(() => {
 });
 
 watch(moderationNote, async () => {
-	await os.api('admin/update-user-note', { userId: props.user.id, text: moderationNote.value });
+	await misskeyApi('admin/update-user-note', { userId: props.user.id, text: moderationNote.value });
 });
 
 const pagination = {
@@ -279,7 +279,7 @@ const AllPagination = {
 	params: computed(() => ({
 		userId: props.user.id,
 		withRenotes: noteview.value === 'all',
-		withReplies: noteview.value === 'all' || noteview.value === 'files',
+		withReplies: noteview.value === 'all',
 		withChannelNotes: noteview.value === 'all',
 		withFiles: noteview.value === 'files',
 	})),
@@ -333,7 +333,7 @@ function adjustMemoTextarea() {
 }
 
 async function updateMemo() {
-	await api('users/update-memo', {
+	await misskeyApi('users/update-memo', {
 		memo: memoDraft.value,
 		userId: props.user.id,
 	});
diff --git a/packages/frontend/src/pages/user/index.activity.vue b/packages/frontend/src/pages/user/index.activity.vue
index f555486a6d..857cf996ff 100644
--- a/packages/frontend/src/pages/user/index.activity.vue
+++ b/packages/frontend/src/pages/user/index.activity.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/pages/user/index.files.vue b/packages/frontend/src/pages/user/index.files.vue
index 30817db77c..be58cec24a 100644
--- a/packages/frontend/src/pages/user/index.files.vue
+++ b/packages/frontend/src/pages/user/index.files.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -37,7 +37,7 @@ import { onMounted, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import { getStaticImageUrl } from '@/scripts/media-proxy.js';
 import { notePage } from '@/filters/note.js';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import MkContainer from '@/components/MkContainer.vue';
 import ImgWithBlurhash from '@/components/MkImgWithBlurhash.vue';
 import { defaultStore } from '@/store.js';
@@ -61,7 +61,7 @@ function thumbnail(image: Misskey.entities.DriveFile): string {
 }
 
 onMounted(() => {
-	os.api('users/notes', {
+	misskeyApi('users/notes', {
 		userId: props.user.id,
 		withFiles: true,
 		limit: 15,
diff --git a/packages/frontend/src/pages/user/index.timeline.vue b/packages/frontend/src/pages/user/index.timeline.vue
index e5a0f49e3d..8dbf90f344 100644
--- a/packages/frontend/src/pages/user/index.timeline.vue
+++ b/packages/frontend/src/pages/user/index.timeline.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/pages/user/index.vue b/packages/frontend/src/pages/user/index.vue
index 44b4f84ca3..7f20e941d3 100644
--- a/packages/frontend/src/pages/user/index.vue
+++ b/packages/frontend/src/pages/user/index.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -8,19 +8,21 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<template #header><MkPageHeader v-model:tab="tab" :displayBackButton="true" :actions="headerActions" :tabs="headerTabs"/></template>
 	<div>
 		<div v-if="user">
-			<XHome v-if="tab === 'home'" :user="user"/>
-			<MkSpacer v-else-if="tab === 'notes'" :contentMax="800" style="padding-top: 0">
-				<XTimeline :user="user"/>
-			</MkSpacer>
-			<XActivity v-else-if="tab === 'activity'" :user="user"/>
-			<XAchievements v-else-if="tab === 'achievements'" :user="user"/>
-			<XReactions v-else-if="tab === 'reactions'" :user="user"/>
-			<XClips v-else-if="tab === 'clips'" :user="user"/>
-			<XLists v-else-if="tab === 'lists'" :user="user"/>
-			<XPages v-else-if="tab === 'pages'" :user="user"/>
-			<XFlashs v-else-if="tab === 'flashs'" :user="user"/>
-			<XGallery v-else-if="tab === 'gallery'" :user="user"/>
-			<XRaw v-else-if="tab === 'raw'" :user="user"/>
+			<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
+				<XHome v-if="tab === 'home'" key="home" :user="user"/>
+				<MkSpacer v-else-if="tab === 'notes'" key="notes" :contentMax="800" style="padding-top: 0">
+					<XTimeline :user="user"/>
+				</MkSpacer>
+				<XActivity v-else-if="tab === 'activity'" key="activity" :user="user"/>
+				<XAchievements v-else-if="tab === 'achievements'" key="achievements" :user="user"/>
+				<XReactions v-else-if="tab === 'reactions'" key="reactions" :user="user"/>
+				<XClips v-else-if="tab === 'clips'" key="clips" :user="user"/>
+				<XLists v-else-if="tab === 'lists'" key="lists" :user="user"/>
+				<XPages v-else-if="tab === 'pages'" key="pages" :user="user"/>
+				<XFlashs v-else-if="tab === 'flashs'" key="flashs" :user="user"/>
+				<XGallery v-else-if="tab === 'gallery'" key="gallery" :user="user"/>
+				<XRaw v-else-if="tab === 'raw'" key="raw" :user="user"/>
+			</MkHorizontalSwipe>
 		</div>
 		<MkError v-else-if="error" @retry="fetchUser()"/>
 		<MkLoading v-else/>
@@ -32,10 +34,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { defineAsyncComponent, computed, watch, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import { acct as getAcct } from '@/filters/user.js';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 import { i18n } from '@/i18n.js';
 import { $i } from '@/account.js';
+import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
 
 const XHome = defineAsyncComponent(() => import('./home.vue'));
 const XTimeline = defineAsyncComponent(() => import('./index.timeline.vue'));
@@ -57,13 +60,14 @@ const props = withDefaults(defineProps<{
 });
 
 const tab = ref(props.page);
+
 const user = ref<null | Misskey.entities.UserDetailed>(null);
 const error = ref<any>(null);
 
 function fetchUser(): void {
 	if (props.acct == null) return;
 	user.value = null;
-	os.api('users/show', Misskey.acct.parse(props.acct)).then(u => {
+	misskeyApi('users/show', Misskey.acct.parse(props.acct)).then(u => {
 		user.value = u;
 	}).catch(err => {
 		error.value = err;
@@ -83,7 +87,7 @@ const headerTabs = computed(() => user.value ? [{
 }, {
 	key: 'notes',
 	title: i18n.ts.notes,
-	icon: 'ph-pencil ph-bold ph-lg',
+	icon: 'ph-pencil-simple ph-bold ph-lg',
 }, {
 	key: 'activity',
 	title: i18n.ts.activity,
@@ -92,7 +96,7 @@ const headerTabs = computed(() => user.value ? [{
 	key: 'achievements',
 	title: i18n.ts.achievements,
 	icon: 'ph-trophy ph-bold ph-lg',
-}] : []), ...($i && ($i.id === user.value.id)) || user.value.publicReactions ? [{
+}] : []), ...($i && ($i.id === user.value.id || $i.isAdmin || $i.isModerator)) || user.value.publicReactions ? [{
 	key: 'reactions',
 	title: i18n.ts.reaction,
 	icon: 'ph-smiley ph-bold ph-lg',
@@ -122,15 +126,18 @@ const headerTabs = computed(() => user.value ? [{
 	icon: 'ph-code ph-bold ph-lg',
 }] : []);
 
-definePageMetadata(computed(() => user.value ? {
+definePageMetadata(() => ({
+	title: i18n.ts.user,
 	icon: 'ph-user ph-bold ph-lg',
-	title: user.value.name ? `${user.value.name} (@${user.value.username})` : `@${user.value.username}`,
-	subtitle: `@${getAcct(user.value)}`,
-	userName: user.value,
-	avatar: user.value,
-	path: `/@${user.value.username}`,
-	share: {
-		title: user.value.name,
-	},
-} : null));
+	...user.value ? {
+		title: user.value.name ? `${user.value.name} (@${user.value.username})` : `@${user.value.username}`,
+		subtitle: `@${getAcct(user.value)}`,
+		userName: user.value,
+		avatar: user.value,
+		path: `/@${user.value.username}`,
+		share: {
+			title: user.value.name,
+		},
+	} : {},
+}));
 </script>
diff --git a/packages/frontend/src/pages/user/lists.vue b/packages/frontend/src/pages/user/lists.vue
index c58a8abdfb..8f95ce2dc9 100644
--- a/packages/frontend/src/pages/user/lists.vue
+++ b/packages/frontend/src/pages/user/lists.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/pages/user/pages.vue b/packages/frontend/src/pages/user/pages.vue
index 94ec80d05e..6375bf7d74 100644
--- a/packages/frontend/src/pages/user/pages.vue
+++ b/packages/frontend/src/pages/user/pages.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/pages/user/raw.vue b/packages/frontend/src/pages/user/raw.vue
index ebe40d5860..ac18ad9392 100644
--- a/packages/frontend/src/pages/user/raw.vue
+++ b/packages/frontend/src/pages/user/raw.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/pages/user/reactions.vue b/packages/frontend/src/pages/user/reactions.vue
index 916b6615d5..3671decc18 100644
--- a/packages/frontend/src/pages/user/reactions.vue
+++ b/packages/frontend/src/pages/user/reactions.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/pages/welcome.entrance.a.vue b/packages/frontend/src/pages/welcome.entrance.a.vue
index 50f86a0ae2..255e07c3fa 100644
--- a/packages/frontend/src/pages/welcome.entrance.a.vue
+++ b/packages/frontend/src/pages/welcome.entrance.a.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -39,7 +39,7 @@ import XTimeline from './welcome.timeline.vue';
 import MarqueeText from '@/components/MkMarquee.vue';
 import MkFeaturedPhotos from '@/components/MkFeaturedPhotos.vue';
 import misskeysvg from '/client-assets/sharkey.svg';
-import * as os from '@/os.js';
+import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
 import MkVisitorDashboard from '@/components/MkVisitorDashboard.vue';
 import { getProxiedImageUrl } from '@/scripts/media-proxy.js';
 
@@ -53,11 +53,11 @@ function getInstanceIcon(instance: Misskey.entities.FederationInstance): string
 	return getProxiedImageUrl(instance.iconUrl, 'preview');
 }
 
-os.api('meta', { detail: true }).then(_meta => {
+misskeyApi('meta', { detail: true }).then(_meta => {
 	meta.value = _meta;
 });
 
-os.apiGet('federation/instances', {
+misskeyApiGet('federation/instances', {
 	sort: '+pubSub',
 	limit: 20,
 }).then(_instances => {
diff --git a/packages/frontend/src/pages/welcome.setup.vue b/packages/frontend/src/pages/welcome.setup.vue
index c2f9d4e585..7d5861d2ae 100644
--- a/packages/frontend/src/pages/welcome.setup.vue
+++ b/packages/frontend/src/pages/welcome.setup.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -40,6 +40,7 @@ import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
 import { host, version } from '@/config.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { login } from '@/account.js';
 import { i18n } from '@/i18n.js';
 import MkAnimBg from '@/components/MkAnimBg.vue';
@@ -52,7 +53,7 @@ function submit() {
 	if (submitting.value) return;
 	submitting.value = true;
 
-	os.api('admin/accounts/create', {
+	misskeyApi('admin/accounts/create', {
 		username: username.value,
 		password: password.value,
 	}).then(res => {
diff --git a/packages/frontend/src/pages/welcome.timeline.vue b/packages/frontend/src/pages/welcome.timeline.vue
index 2cbe0ed9b1..59f91e8b4c 100644
--- a/packages/frontend/src/pages/welcome.timeline.vue
+++ b/packages/frontend/src/pages/welcome.timeline.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 					<MkMediaList :mediaList="note.files"/>
 				</div>
 				<div v-if="note.poll">
-					<MkPoll :note="note" :readOnly="true"/>
+					<MkPoll :noteId="note.id" :poll="note.poll" :readOnly="true"/>
 				</div>
 			</div>
 			<MkReactionsViewer ref="reactionsViewer" :note="note"/>
@@ -32,14 +32,14 @@ import { onUpdated, ref, shallowRef } from 'vue';
 import MkReactionsViewer from '@/components/MkReactionsViewer.vue';
 import MkMediaList from '@/components/MkMediaList.vue';
 import MkPoll from '@/components/MkPoll.vue';
-import * as os from '@/os.js';
+import { misskeyApiGet } from '@/scripts/misskey-api.js';
 import { getScrollContainer } from '@/scripts/scroll.js';
 
 const notes = ref<Misskey.entities.Note[]>([]);
 const isScrolling = ref(false);
 const scrollEl = shallowRef<HTMLElement>();
 
-os.apiGet('notes/featured').then(_notes => {
+misskeyApiGet('notes/featured').then(_notes => {
 	notes.value = _notes;
 });
 
diff --git a/packages/frontend/src/pages/welcome.vue b/packages/frontend/src/pages/welcome.vue
index 7f0af1b83e..9ba6a5885e 100644
--- a/packages/frontend/src/pages/welcome.vue
+++ b/packages/frontend/src/pages/welcome.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -16,12 +16,12 @@ import * as Misskey from 'misskey-js';
 import XSetup from './welcome.setup.vue';
 import XEntrance from './welcome.entrance.a.vue';
 import { instanceName } from '@/config.js';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { definePageMetadata } from '@/scripts/page-metadata.js';
 
 const meta = ref<Misskey.entities.MetaResponse | null>(null);
 
-os.api('meta', { detail: true }).then(res => {
+misskeyApi('meta', { detail: true }).then(res => {
 	meta.value = res;
 });
 
@@ -29,8 +29,8 @@ const headerActions = computed(() => []);
 
 const headerTabs = computed(() => []);
 
-definePageMetadata(computed(() => ({
+definePageMetadata(() => ({
 	title: instanceName,
 	icon: null,
-})));
+}));
 </script>
diff --git a/packages/frontend/src/pizzax.ts b/packages/frontend/src/pizzax.ts
index b2254a0611..ac325e923f 100644
--- a/packages/frontend/src/pizzax.ts
+++ b/packages/frontend/src/pizzax.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -8,11 +8,12 @@
 import { onUnmounted, Ref, ref, watch } from 'vue';
 import { BroadcastChannel } from 'broadcast-channel';
 import { $i } from '@/account.js';
-import { api } from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { get, set } from '@/scripts/idb-proxy.js';
 import { defaultStore } from '@/store.js';
 import { useStream } from '@/stream.js';
 import { deepClone } from '@/scripts/clone.js';
+import { deepMerge } from '@/scripts/merge.js';
 
 type StateDef = Record<string, {
 	where: 'account' | 'device' | 'deviceAccount';
@@ -80,6 +81,21 @@ export class Storage<T extends StateDef> {
 		this.loaded = this.ready.then(() => this.load());
 	}
 
+	private isPureObject(value: unknown): value is Record<string | number | symbol, unknown> {
+		return typeof value === 'object' && value !== null && !Array.isArray(value);
+	}
+
+	private mergeState<X>(value: X, def: X): X {
+		if (this.isPureObject(value) && this.isPureObject(def)) {
+			const merged = deepMerge(value, def);
+
+			if (_DEV_) console.log('Merging state. Incoming: ', value, ' Default: ', def, ' Result: ', merged);
+
+			return merged as X;
+		}
+		return value;
+	}
+
 	private async init(): Promise<void> {
 		await this.migrate();
 
@@ -89,11 +105,11 @@ export class Storage<T extends StateDef> {
 
 		for (const [k, v] of Object.entries(this.def) as [keyof T, T[keyof T]['default']][]) {
 			if (v.where === 'device' && Object.prototype.hasOwnProperty.call(deviceState, k)) {
-				this.reactiveState[k].value = this.state[k] = deviceState[k];
+				this.reactiveState[k].value = this.state[k] = this.mergeState<T[keyof T]['default']>(deviceState[k], v.default);
 			} else if (v.where === 'account' && $i && Object.prototype.hasOwnProperty.call(registryCache, k)) {
-				this.reactiveState[k].value = this.state[k] = registryCache[k];
+				this.reactiveState[k].value = this.state[k] = this.mergeState<T[keyof T]['default']>(registryCache[k], v.default);
 			} else if (v.where === 'deviceAccount' && Object.prototype.hasOwnProperty.call(deviceAccountState, k)) {
-				this.reactiveState[k].value = this.state[k] = deviceAccountState[k];
+				this.reactiveState[k].value = this.state[k] = this.mergeState<T[keyof T]['default']>(deviceAccountState[k], v.default);
 			} else {
 				this.reactiveState[k].value = this.state[k] = v.default;
 				if (_DEV_) console.log('Use default value', k, v.default);
@@ -134,7 +150,7 @@ export class Storage<T extends StateDef> {
 				window.setTimeout(async () => {
 					await defaultStore.ready;
 
-					api('i/registry/get-all', { scope: ['client', this.key] })
+					misskeyApi('i/registry/get-all', { scope: ['client', this.key] })
 						.then(kvs => {
 							const cache: Partial<T> = {};
 							for (const [k, v] of Object.entries(this.def) as [keyof T, T[keyof T]['default']][]) {
@@ -168,7 +184,7 @@ export class Storage<T extends StateDef> {
 		this.reactiveState[key].value = this.state[key] = rawValue;
 
 		return this.addIdbSetJob(async () => {
-			if (_DEV_) console.log(`set ${key} start`);
+			if (_DEV_) console.log(`set ${String(key)} start`);
 			switch (this.def[key].where) {
 				case 'device': {
 					this.pizzaxChannel.postMessage({
@@ -199,7 +215,7 @@ export class Storage<T extends StateDef> {
 					const cache = await get(this.registryCacheKeyName) || {};
 					cache[key] = rawValue;
 					await set(this.registryCacheKeyName, cache);
-					await api('i/registry/set', {
+					await misskeyApi('i/registry/set', {
 						scope: ['client', this.key],
 						key: key.toString(),
 						value: rawValue,
@@ -207,7 +223,7 @@ export class Storage<T extends StateDef> {
 					break;
 				}
 			}
-			if (_DEV_) console.log(`set ${key} complete`);
+			if (_DEV_) console.log(`set ${String(key)} complete`);
 		});
 	}
 
@@ -223,9 +239,12 @@ export class Storage<T extends StateDef> {
 
 	/**
 	 * 特定のキーの、簡易的なgetter/setterを作ります
-	 * 主にvue場で設定コントロールのmodelとして使う用
+	 * 主にvue上で設定コントロールのmodelとして使う用
 	 */
-	public makeGetterSetter<K extends keyof T>(key: K, getter?: (v: T[K]) => unknown, setter?: (v: unknown) => T[K]) {
+	public makeGetterSetter<K extends keyof T>(key: K, getter?: (v: T[K]) => unknown, setter?: (v: unknown) => T[K]): {
+		get: () => T[K]['default'];
+		set: (value: T[K]['default']) => void;
+	} {
 		const valueRef = ref(this.state[key]);
 
 		const stop = watch(this.reactiveState[key], val => {
diff --git a/packages/frontend/src/plugin.ts b/packages/frontend/src/plugin.ts
index 5e49af4858..743cadc36a 100644
--- a/packages/frontend/src/plugin.ts
+++ b/packages/frontend/src/plugin.ts
@@ -1,10 +1,10 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import { Interpreter, Parser, utils, values } from '@syuilo/aiscript';
-import { createAiScriptEnv } from '@/scripts/aiscript/api.js';
+import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js';
 import { inputText } from '@/os.js';
 import { Plugin, noteActions, notePostInterruptors, noteViewInterruptors, postFormActions, userActions, pageViewInterruptors } from '@/store.js';
 
@@ -19,19 +19,7 @@ export async function install(plugin: Plugin): Promise<void> {
 		plugin: plugin,
 		storageKey: 'plugins:' + plugin.id,
 	}), {
-		in: (q): Promise<string> => {
-			return new Promise(ok => {
-				inputText({
-					title: q,
-				}).then(({ canceled, result: a }) => {
-					if (canceled) {
-						ok('');
-					} else {
-						ok(a);
-					}
-				});
-			});
-		},
+		in: aiScriptReadline,
 		out: (value): void => {
 			console.log(value);
 		},
diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts
deleted file mode 100644
index b861afa9a3..0000000000
--- a/packages/frontend/src/router.ts
+++ /dev/null
@@ -1,558 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import { AsyncComponentLoader, defineAsyncComponent, inject } from 'vue';
-import { Router } from '@/nirax.js';
-import { $i, iAmModerator } from '@/account.js';
-import MkLoading from '@/pages/_loading_.vue';
-import MkError from '@/pages/_error_.vue';
-
-export const page = (loader: AsyncComponentLoader<any>) => defineAsyncComponent({
-	loader: loader,
-	loadingComponent: MkLoading,
-	errorComponent: MkError,
-});
-
-export const routes = [{
-	path: '/@:initUser/pages/:initPageName/view-source',
-	component: page(() => import('./pages/page-editor/page-editor.vue')),
-}, {
-	path: '/@:username/pages/:pageName',
-	component: page(() => import('./pages/page.vue')),
-}, {
-	path: '/@:acct/following',
-	component: page(() => import('./pages/user/following.vue')),
-}, {
-	path: '/@:acct/followers',
-	component: page(() => import('./pages/user/followers.vue')),
-}, {
-	name: 'user',
-	path: '/@:acct/:page?',
-	component: page(() => import('./pages/user/index.vue')),
-}, {
-	name: 'note',
-	path: '/notes/:noteId',
-	component: page(() => import('./pages/note.vue')),
-}, {
-	name: 'list',
-	path: '/list/:listId',
-	component: page(() => import('./pages/list.vue')),
-}, {
-	path: '/clips/:clipId',
-	component: page(() => import('./pages/clip.vue')),
-}, {
-	path: '/instance-info/:host',
-	component: page(() => import('./pages/instance-info.vue')),
-}, {
-	name: 'settings',
-	path: '/settings',
-	component: page(() => import('./pages/settings/index.vue')),
-	loginRequired: true,
-	children: [{
-		path: '/profile',
-		name: 'profile',
-		component: page(() => import('./pages/settings/profile.vue')),
-	}, {
-		path: '/avatar-decoration',
-		name: 'avatarDecoration',
-		component: page(() => import('./pages/settings/avatar-decoration.vue')),
-	}, {
-		path: '/roles',
-		name: 'roles',
-		component: page(() => import('./pages/settings/roles.vue')),
-	}, {
-		path: '/privacy',
-		name: 'privacy',
-		component: page(() => import('./pages/settings/privacy.vue')),
-	}, {
-		path: '/emoji-picker',
-		name: 'emojiPicker',
-		component: page(() => import('./pages/settings/emoji-picker.vue')),
-	}, {
-		path: '/drive',
-		name: 'drive',
-		component: page(() => import('./pages/settings/drive.vue')),
-	}, {
-		path: '/drive/cleaner',
-		name: 'drive',
-		component: page(() => import('./pages/settings/drive-cleaner.vue')),
-	}, {
-		path: '/notifications',
-		name: 'notifications',
-		component: page(() => import('./pages/settings/notifications.vue')),
-	}, {
-		path: '/email',
-		name: 'email',
-		component: page(() => import('./pages/settings/email.vue')),
-	}, {
-		path: '/security',
-		name: 'security',
-		component: page(() => import('./pages/settings/security.vue')),
-	}, {
-		path: '/general',
-		name: 'general',
-		component: page(() => import('./pages/settings/general.vue')),
-	}, {
-		path: '/theme/install',
-		name: 'theme',
-		component: page(() => import('./pages/settings/theme.install.vue')),
-	}, {
-		path: '/theme/manage',
-		name: 'theme',
-		component: page(() => import('./pages/settings/theme.manage.vue')),
-	}, {
-		path: '/theme',
-		name: 'theme',
-		component: page(() => import('./pages/settings/theme.vue')),
-	}, {
-		path: '/navbar',
-		name: 'navbar',
-		component: page(() => import('./pages/settings/navbar.vue')),
-	}, {
-		path: '/statusbar',
-		name: 'statusbar',
-		component: page(() => import('./pages/settings/statusbar.vue')),
-	}, {
-		path: '/sounds',
-		name: 'sounds',
-		component: page(() => import('./pages/settings/sounds.vue')),
-	}, {
-		path: '/plugin/install',
-		name: 'plugin',
-		component: page(() => import('./pages/settings/plugin.install.vue')),
-	}, {
-		path: '/plugin',
-		name: 'plugin',
-		component: page(() => import('./pages/settings/plugin.vue')),
-	}, {
-		path: '/import-export',
-		name: 'import-export',
-		component: page(() => import('./pages/settings/import-export.vue')),
-	}, {
-		path: '/mute-block',
-		name: 'mute-block',
-		component: page(() => import('./pages/settings/mute-block.vue')),
-	}, {
-		path: '/api',
-		name: 'api',
-		component: page(() => import('./pages/settings/api.vue')),
-	}, {
-		path: '/apps',
-		name: 'api',
-		component: page(() => import('./pages/settings/apps.vue')),
-	}, {
-		path: '/webhook/edit/:webhookId',
-		name: 'webhook',
-		component: page(() => import('./pages/settings/webhook.edit.vue')),
-	}, {
-		path: '/webhook/new',
-		name: 'webhook',
-		component: page(() => import('./pages/settings/webhook.new.vue')),
-	}, {
-		path: '/webhook',
-		name: 'webhook',
-		component: page(() => import('./pages/settings/webhook.vue')),
-	}, {
-		path: '/deck',
-		name: 'deck',
-		component: page(() => import('./pages/settings/deck.vue')),
-	}, {
-		path: '/preferences-backups',
-		name: 'preferences-backups',
-		component: page(() => import('./pages/settings/preferences-backups.vue')),
-	}, {
-		path: '/migration',
-		name: 'migration',
-		component: page(() => import('./pages/settings/migration.vue')),
-	}, {
-		path: '/custom-css',
-		name: 'general',
-		component: page(() => import('./pages/settings/custom-css.vue')),
-	}, {
-		path: '/accounts',
-		name: 'profile',
-		component: page(() => import('./pages/settings/accounts.vue')),
-	}, {
-		path: '/other',
-		name: 'other',
-		component: page(() => import('./pages/settings/other.vue')),
-	}, {
-		path: '/',
-		component: page(() => import('./pages/_empty_.vue')),
-	}],
-}, {
-	path: '/reset-password/:token?',
-	component: page(() => import('./pages/reset-password.vue')),
-}, {
-	path: '/signup-complete/:code',
-	component: page(() => import('./pages/signup-complete.vue')),
-}, {
-	path: '/announcements',
-	component: page(() => import('./pages/announcements.vue')),
-}, {
-	path: '/about',
-	component: page(() => import('./pages/about.vue')),
-	hash: 'initialTab',
-}, {
-	path: '/about-sharkey',
-	component: page(() => import('./pages/about-sharkey.vue')),
-}, {
-	path: '/invite',
-	name: 'invite',
-	component: page(() => import('./pages/invite.vue')),
-}, {
-	path: '/ads',
-	component: page(() => import('./pages/ads.vue')),
-}, {
-	path: '/theme-editor',
-	component: page(() => import('./pages/theme-editor.vue')),
-	loginRequired: true,
-}, {
-	path: '/roles/:role',
-	component: page(() => import('./pages/role.vue')),
-}, {
-	path: '/user-tags/:tag',
-	component: page(() => import('./pages/user-tag.vue')),
-}, {
-	path: '/explore',
-	component: page(() => import('./pages/explore.vue')),
-	hash: 'initialTab',
-}, {
-	path: '/search',
-	component: page(() => import('./pages/search.vue')),
-	query: {
-		q: 'query',
-		channel: 'channel',
-		type: 'type',
-		origin: 'origin',
-	},
-}, {
-	path: '/authorize-follow',
-	component: page(() => import('./pages/follow.vue')),
-	loginRequired: true,
-}, {
-	path: '/share',
-	component: page(() => import('./pages/share.vue')),
-	loginRequired: true,
-}, {
-	path: '/api-console',
-	component: page(() => import('./pages/api-console.vue')),
-	loginRequired: true,
-}, {
-	path: '/scratchpad',
-	component: page(() => import('./pages/scratchpad.vue')),
-}, {
-	path: '/auth/:token',
-	component: page(() => import('./pages/auth.vue')),
-}, {
-	path: '/miauth/:session',
-	component: page(() => import('./pages/miauth.vue')),
-	query: {
-		callback: 'callback',
-		name: 'name',
-		icon: 'icon',
-		permission: 'permission',
-	},
-}, {
-	path: '/tags/:tag',
-	component: page(() => import('./pages/tag.vue')),
-}, {
-	path: '/pages/new',
-	component: page(() => import('./pages/page-editor/page-editor.vue')),
-	loginRequired: true,
-}, {
-	path: '/pages/edit/:initPageId',
-	component: page(() => import('./pages/page-editor/page-editor.vue')),
-	loginRequired: true,
-}, {
-	path: '/pages',
-	component: page(() => import('./pages/pages.vue')),
-}, {
-	path: '/play/:id/edit',
-	component: page(() => import('./pages/flash/flash-edit.vue')),
-	loginRequired: true,
-}, {
-	path: '/play/new',
-	component: page(() => import('./pages/flash/flash-edit.vue')),
-	loginRequired: true,
-}, {
-	path: '/play/:id',
-	component: page(() => import('./pages/flash/flash.vue')),
-}, {
-	path: '/play',
-	component: page(() => import('./pages/flash/flash-index.vue')),
-}, {
-	path: '/gallery/:postId/edit',
-	component: page(() => import('./pages/gallery/edit.vue')),
-	loginRequired: true,
-}, {
-	path: '/gallery/new',
-	component: page(() => import('./pages/gallery/edit.vue')),
-	loginRequired: true,
-}, {
-	path: '/gallery/:postId',
-	component: page(() => import('./pages/gallery/post.vue')),
-}, {
-	path: '/gallery',
-	component: page(() => import('./pages/gallery/index.vue')),
-}, {
-	path: '/channels/:channelId/edit',
-	component: page(() => import('./pages/channel-editor.vue')),
-	loginRequired: true,
-}, {
-	path: '/channels/new',
-	component: page(() => import('./pages/channel-editor.vue')),
-	loginRequired: true,
-}, {
-	path: '/channels/:channelId',
-	component: page(() => import('./pages/channel.vue')),
-}, {
-	path: '/channels',
-	component: page(() => import('./pages/channels.vue')),
-}, {
-	path: '/avatar-decorations',
-	name: 'avatarDecorations',
-	component: page(() => import('./pages/avatar-decorations.vue')),
-}, {
-	path: '/custom-emojis-manager',
-	component: page(() => import('./pages/custom-emojis-manager.vue')),
-}, {
-	path: '/registry/keys/:domain/:path(*)?',
-	component: page(() => import('./pages/registry.keys.vue')),
-}, {
-	path: '/registry/value/:domain/:path(*)?',
-	component: page(() => import('./pages/registry.value.vue')),
-}, {
-	path: '/registry',
-	component: page(() => import('./pages/registry.vue')),
-}, {
-	path: '/install-extentions',
-	component: page(() => import('./pages/install-extentions.vue')),
-	loginRequired: true,
-}, {
-	path: '/admin/user/:userId',
-	component: iAmModerator ? page(() => import('./pages/admin-user.vue')) : page(() => import('./pages/not-found.vue')),
-}, {
-	path: '/admin/file/:fileId',
-	component: iAmModerator ? page(() => import('./pages/admin-file.vue')) : page(() => import('./pages/not-found.vue')),
-}, {
-	path: '/admin',
-	component: iAmModerator ? page(() => import('./pages/admin/index.vue')) : page(() => import('./pages/not-found.vue')),
-	children: [{
-		path: '/overview',
-		name: 'overview',
-		component: page(() => import('./pages/admin/overview.vue')),
-	}, {
-		path: '/users',
-		name: 'users',
-		component: page(() => import('./pages/admin/users.vue')),
-	}, {
-		path: '/emojis',
-		name: 'emojis',
-		component: page(() => import('./pages/custom-emojis-manager.vue')),
-	}, {
-		path: '/avatar-decorations',
-		name: 'avatarDecorations',
-		component: page(() => import('./pages/avatar-decorations.vue')),
-	}, {
-		path: '/queue',
-		name: 'queue',
-		component: page(() => import('./pages/admin/queue.vue')),
-	}, {
-		path: '/files',
-		name: 'files',
-		component: page(() => import('./pages/admin/files.vue')),
-	}, {
-		path: '/federation',
-		name: 'federation',
-		component: page(() => import('./pages/admin/federation.vue')),
-	}, {
-		path: '/announcements',
-		name: 'announcements',
-		component: page(() => import('./pages/admin/announcements.vue')),
-	}, {
-		path: '/ads',
-		name: 'ads',
-		component: page(() => import('./pages/admin/ads.vue')),
-	}, {
-		path: '/roles/:id/edit',
-		name: 'roles',
-		component: page(() => import('./pages/admin/roles.edit.vue')),
-	}, {
-		path: '/roles/new',
-		name: 'roles',
-		component: page(() => import('./pages/admin/roles.edit.vue')),
-	}, {
-		path: '/roles/:id',
-		name: 'roles',
-		component: page(() => import('./pages/admin/roles.role.vue')),
-	}, {
-		path: '/roles',
-		name: 'roles',
-		component: page(() => import('./pages/admin/roles.vue')),
-	}, {
-		path: '/database',
-		name: 'database',
-		component: page(() => import('./pages/admin/database.vue')),
-	}, {
-		path: '/abuses',
-		name: 'abuses',
-		component: page(() => import('./pages/admin/abuses.vue')),
-	}, {
-		path: '/modlog',
-		name: 'modlog',
-		component: page(() => import('./pages/admin/modlog.vue')),
-	}, {
-		path: '/settings',
-		name: 'settings',
-		component: page(() => import('./pages/admin/settings.vue')),
-	}, {
-		path: '/branding',
-		name: 'branding',
-		component: page(() => import('./pages/admin/branding.vue')),
-	}, {
-		path: '/moderation',
-		name: 'moderation',
-		component: page(() => import('./pages/admin/moderation.vue')),
-	}, {
-		path: '/email-settings',
-		name: 'email-settings',
-		component: page(() => import('./pages/admin/email-settings.vue')),
-	}, {
-		path: '/object-storage',
-		name: 'object-storage',
-		component: page(() => import('./pages/admin/object-storage.vue')),
-	}, {
-		path: '/security',
-		name: 'security',
-		component: page(() => import('./pages/admin/security.vue')),
-	}, {
-		path: '/relays',
-		name: 'relays',
-		component: page(() => import('./pages/admin/relays.vue')),
-	}, {
-		path: '/instance-block',
-		name: 'instance-block',
-		component: page(() => import('./pages/admin/instance-block.vue')),
-	}, {
-		path: '/proxy-account',
-		name: 'proxy-account',
-		component: page(() => import('./pages/admin/proxy-account.vue')),
-	}, {
-		path: '/external-services',
-		name: 'external-services',
-		component: page(() => import('./pages/admin/external-services.vue')),
-	}, {
-		path: '/other-settings',
-		name: 'other-settings',
-		component: page(() => import('./pages/admin/other-settings.vue')),
-	}, {
-		path: '/server-rules',
-		name: 'server-rules',
-		component: page(() => import('./pages/admin/server-rules.vue')),
-	}, {
-		path: '/invites',
-		name: 'invites',
-		component: page(() => import('./pages/admin/invites.vue')),
-	}, {
-		path: '/approvals',
-		name: 'approvals',
-		component: page(() => import('./pages/admin/approvals.vue')),
-	}, {
-		path: '/',
-		component: page(() => import('./pages/_empty_.vue')),
-	}],
-}, {
-	path: '/my/notifications',
-	component: page(() => import('./pages/notifications.vue')),
-	loginRequired: true,
-}, {
-	path: '/my/favorites',
-	component: page(() => import('./pages/favorites.vue')),
-	loginRequired: true,
-}, {
-	path: '/my/achievements',
-	component: page(() => import('./pages/achievements.vue')),
-	loginRequired: true,
-}, {
-	path: '/my/drive/folder/:folder',
-	component: page(() => import('./pages/drive.vue')),
-	loginRequired: true,
-}, {
-	path: '/my/drive',
-	component: page(() => import('./pages/drive.vue')),
-	loginRequired: true,
-}, {
-	path: '/my/drive/file/:fileId',
-	component: page(() => import('./pages/drive.file.vue')),
-	loginRequired: true,
-}, {
-	path: '/my/follow-requests',
-	component: page(() => import('./pages/follow-requests.vue')),
-	loginRequired: true,
-}, {
-	path: '/my/lists/:listId',
-	component: page(() => import('./pages/my-lists/list.vue')),
-	loginRequired: true,
-}, {
-	path: '/my/lists',
-	component: page(() => import('./pages/my-lists/index.vue')),
-	loginRequired: true,
-}, {
-	path: '/my/clips',
-	component: page(() => import('./pages/my-clips/index.vue')),
-	loginRequired: true,
-}, {
-	path: '/my/antennas/create',
-	component: page(() => import('./pages/my-antennas/create.vue')),
-	loginRequired: true,
-}, {
-	path: '/my/antennas/:antennaId',
-	component: page(() => import('./pages/my-antennas/edit.vue')),
-	loginRequired: true,
-}, {
-	path: '/my/antennas',
-	component: page(() => import('./pages/my-antennas/index.vue')),
-	loginRequired: true,
-}, {
-	path: '/timeline/list/:listId',
-	component: page(() => import('./pages/user-list-timeline.vue')),
-	loginRequired: true,
-}, {
-	path: '/timeline/antenna/:antennaId',
-	component: page(() => import('./pages/antenna-timeline.vue')),
-	loginRequired: true,
-}, {
-	path: '/clicker',
-	component: page(() => import('./pages/clicker.vue')),
-	loginRequired: true,
-}, {
-	path: '/timeline',
-	component: page(() => import('./pages/timeline.vue')),
-}, {
-	name: 'index',
-	path: '/',
-	component: $i ? page(() => import('./pages/timeline.vue')) : page(() => import('./pages/welcome.vue')),
-	globalCacheKey: 'index',
-}, {
-	path: '/:(*)',
-	component: page(() => import('./pages/not-found.vue')),
-}];
-
-export const mainRouter = new Router(routes, location.pathname + location.search + location.hash, !!$i, page(() => import('@/pages/not-found.vue')));
-
-window.history.replaceState({ key: mainRouter.getCurrentKey() }, '', location.href);
-
-mainRouter.addListener('push', ctx => {
-	window.history.pushState({ key: ctx.key }, '', ctx.path);
-});
-
-window.addEventListener('popstate', (event) => {
-	mainRouter.replace(location.pathname + location.search + location.hash, event.state?.key);
-});
-
-export function useRouter(): Router {
-	return inject<Router | null>('router', null) ?? mainRouter;
-}
diff --git a/packages/frontend/src/router/definition.ts b/packages/frontend/src/router/definition.ts
new file mode 100644
index 0000000000..c5fc28a345
--- /dev/null
+++ b/packages/frontend/src/router/definition.ts
@@ -0,0 +1,606 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { App, AsyncComponentLoader, defineAsyncComponent, provide } from 'vue';
+import type { RouteDef } from '@/nirax.js';
+import { IRouter, Router } from '@/nirax.js';
+import { $i, iAmModerator } from '@/account.js';
+import MkLoading from '@/pages/_loading_.vue';
+import MkError from '@/pages/_error_.vue';
+import { setMainRouter } from '@/router/main.js';
+
+const page = (loader: AsyncComponentLoader<any>) => defineAsyncComponent({
+	loader: loader,
+	loadingComponent: MkLoading,
+	errorComponent: MkError,
+});
+
+const routes: RouteDef[] = [{
+	path: '/@:initUser/pages/:initPageName/view-source',
+	component: page(() => import('@/pages/page-editor/page-editor.vue')),
+}, {
+	path: '/@:username/pages/:pageName',
+	component: page(() => import('@/pages/page.vue')),
+}, {
+	path: '/@:acct/following',
+	component: page(() => import('@/pages/user/following.vue')),
+}, {
+	path: '/@:acct/followers',
+	component: page(() => import('@/pages/user/followers.vue')),
+}, {
+	name: 'user',
+	path: '/@:acct/:page?',
+	component: page(() => import('@/pages/user/index.vue')),
+}, {
+	name: 'note',
+	path: '/notes/:noteId',
+	component: page(() => import('@/pages/note.vue')),
+}, {
+	name: 'list',
+	path: '/list/:listId',
+	component: page(() => import('@/pages/list.vue')),
+}, {
+	path: '/clips/:clipId',
+	component: page(() => import('@/pages/clip.vue')),
+}, {
+	path: '/instance-info/:host',
+	component: page(() => import('@/pages/instance-info.vue')),
+}, {
+	name: 'settings',
+	path: '/settings',
+	component: page(() => import('@/pages/settings/index.vue')),
+	loginRequired: true,
+	children: [{
+		path: '/profile',
+		name: 'profile',
+		component: page(() => import('@/pages/settings/profile.vue')),
+	}, {
+		path: '/avatar-decoration',
+		name: 'avatarDecoration',
+		component: page(() => import('@/pages/settings/avatar-decoration.vue')),
+	}, {
+		path: '/roles',
+		name: 'roles',
+		component: page(() => import('@/pages/settings/roles.vue')),
+	}, {
+		path: '/privacy',
+		name: 'privacy',
+		component: page(() => import('@/pages/settings/privacy.vue')),
+	}, {
+		path: '/emoji-picker',
+		name: 'emojiPicker',
+		component: page(() => import('@/pages/settings/emoji-picker.vue')),
+	}, {
+		path: '/drive',
+		name: 'drive',
+		component: page(() => import('@/pages/settings/drive.vue')),
+	}, {
+		path: '/drive/cleaner',
+		name: 'drive',
+		component: page(() => import('@/pages/settings/drive-cleaner.vue')),
+	}, {
+		path: '/notifications',
+		name: 'notifications',
+		component: page(() => import('@/pages/settings/notifications.vue')),
+	}, {
+		path: '/email',
+		name: 'email',
+		component: page(() => import('@/pages/settings/email.vue')),
+	}, {
+		path: '/security',
+		name: 'security',
+		component: page(() => import('@/pages/settings/security.vue')),
+	}, {
+		path: '/general',
+		name: 'general',
+		component: page(() => import('@/pages/settings/general.vue')),
+	}, {
+		path: '/theme/install',
+		name: 'theme',
+		component: page(() => import('@/pages/settings/theme.install.vue')),
+	}, {
+		path: '/theme/manage',
+		name: 'theme',
+		component: page(() => import('@/pages/settings/theme.manage.vue')),
+	}, {
+		path: '/theme',
+		name: 'theme',
+		component: page(() => import('@/pages/settings/theme.vue')),
+	}, {
+		path: '/navbar',
+		name: 'navbar',
+		component: page(() => import('@/pages/settings/navbar.vue')),
+	}, {
+		path: '/statusbar',
+		name: 'statusbar',
+		component: page(() => import('@/pages/settings/statusbar.vue')),
+	}, {
+		path: '/sounds',
+		name: 'sounds',
+		component: page(() => import('@/pages/settings/sounds.vue')),
+	}, {
+		path: '/plugin/install',
+		name: 'plugin',
+		component: page(() => import('@/pages/settings/plugin.install.vue')),
+	}, {
+		path: '/plugin',
+		name: 'plugin',
+		component: page(() => import('@/pages/settings/plugin.vue')),
+	}, {
+		path: '/import-export',
+		name: 'import-export',
+		component: page(() => import('@/pages/settings/import-export.vue')),
+	}, {
+		path: '/mute-block',
+		name: 'mute-block',
+		component: page(() => import('@/pages/settings/mute-block.vue')),
+	}, {
+		path: '/api',
+		name: 'api',
+		component: page(() => import('@/pages/settings/api.vue')),
+	}, {
+		path: '/apps',
+		name: 'api',
+		component: page(() => import('@/pages/settings/apps.vue')),
+	}, {
+		path: '/webhook/edit/:webhookId',
+		name: 'webhook',
+		component: page(() => import('@/pages/settings/webhook.edit.vue')),
+	}, {
+		path: '/webhook/new',
+		name: 'webhook',
+		component: page(() => import('@/pages/settings/webhook.new.vue')),
+	}, {
+		path: '/webhook',
+		name: 'webhook',
+		component: page(() => import('@/pages/settings/webhook.vue')),
+	}, {
+		path: '/deck',
+		name: 'deck',
+		component: page(() => import('@/pages/settings/deck.vue')),
+	}, {
+		path: '/preferences-backups',
+		name: 'preferences-backups',
+		component: page(() => import('@/pages/settings/preferences-backups.vue')),
+	}, {
+		path: '/migration',
+		name: 'migration',
+		component: page(() => import('@/pages/settings/migration.vue')),
+	}, {
+		path: '/custom-css',
+		name: 'general',
+		component: page(() => import('@/pages/settings/custom-css.vue')),
+	}, {
+		path: '/accounts',
+		name: 'profile',
+		component: page(() => import('@/pages/settings/accounts.vue')),
+	}, {
+		path: '/other',
+		name: 'other',
+		component: page(() => import('@/pages/settings/other.vue')),
+	}, {
+		path: '/',
+		component: page(() => import('@/pages/_empty_.vue')),
+	}],
+}, {
+	path: '/reset-password/:token?',
+	component: page(() => import('@/pages/reset-password.vue')),
+}, {
+	path: '/signup-complete/:code',
+	component: page(() => import('@/pages/signup-complete.vue')),
+}, {
+	path: '/announcements',
+	component: page(() => import('@/pages/announcements.vue')),
+}, {
+	path: '/about',
+	component: page(() => import('@/pages/about.vue')),
+	hash: 'initialTab',
+}, {
+	path: '/about-sharkey',
+	component: page(() => import('@/pages/about-sharkey.vue')),
+}, {
+	path: '/invite',
+	name: 'invite',
+	component: page(() => import('@/pages/invite.vue')),
+}, {
+	path: '/ads',
+	component: page(() => import('@/pages/ads.vue')),
+}, {
+	path: '/theme-editor',
+	component: page(() => import('@/pages/theme-editor.vue')),
+	loginRequired: true,
+}, {
+	path: '/roles/:role',
+	component: page(() => import('@/pages/role.vue')),
+}, {
+	path: '/user-tags/:tag',
+	component: page(() => import('@/pages/user-tag.vue')),
+}, {
+	path: '/explore',
+	component: page(() => import('@/pages/explore.vue')),
+	hash: 'initialTab',
+}, {
+	path: '/search',
+	component: page(() => import('@/pages/search.vue')),
+	query: {
+		q: 'query',
+		channel: 'channel',
+		type: 'type',
+		origin: 'origin',
+	},
+}, {
+	path: '/authorize-follow',
+	component: page(() => import('@/pages/follow.vue')),
+	loginRequired: true,
+}, {
+	path: '/share',
+	component: page(() => import('@/pages/share.vue')),
+	loginRequired: true,
+}, {
+	path: '/api-console',
+	component: page(() => import('@/pages/api-console.vue')),
+	loginRequired: true,
+}, {
+	path: '/scratchpad',
+	component: page(() => import('@/pages/scratchpad.vue')),
+}, {
+	path: '/auth/:token',
+	component: page(() => import('@/pages/auth.vue')),
+}, {
+	path: '/miauth/:session',
+	component: page(() => import('@/pages/miauth.vue')),
+	query: {
+		callback: 'callback',
+		name: 'name',
+		icon: 'icon',
+		permission: 'permission',
+	},
+}, {
+	path: '/oauth/authorize',
+	component: page(() => import('@/pages/oauth.vue')),
+}, {
+	path: '/tags/:tag',
+	component: page(() => import('@/pages/tag.vue')),
+}, {
+	path: '/pages/new',
+	component: page(() => import('@/pages/page-editor/page-editor.vue')),
+	loginRequired: true,
+}, {
+	path: '/pages/edit/:initPageId',
+	component: page(() => import('@/pages/page-editor/page-editor.vue')),
+	loginRequired: true,
+}, {
+	path: '/pages',
+	component: page(() => import('@/pages/pages.vue')),
+}, {
+	path: '/play/:id/edit',
+	component: page(() => import('@/pages/flash/flash-edit.vue')),
+	loginRequired: true,
+}, {
+	path: '/play/new',
+	component: page(() => import('@/pages/flash/flash-edit.vue')),
+	loginRequired: true,
+}, {
+	path: '/play/:id',
+	component: page(() => import('@/pages/flash/flash.vue')),
+}, {
+	path: '/play',
+	component: page(() => import('@/pages/flash/flash-index.vue')),
+}, {
+	path: '/gallery/:postId/edit',
+	component: page(() => import('@/pages/gallery/edit.vue')),
+	loginRequired: true,
+}, {
+	path: '/gallery/new',
+	component: page(() => import('@/pages/gallery/edit.vue')),
+	loginRequired: true,
+}, {
+	path: '/gallery/:postId',
+	component: page(() => import('@/pages/gallery/post.vue')),
+}, {
+	path: '/gallery',
+	component: page(() => import('@/pages/gallery/index.vue')),
+}, {
+	path: '/channels/:channelId/edit',
+	component: page(() => import('@/pages/channel-editor.vue')),
+	loginRequired: true,
+}, {
+	path: '/channels/new',
+	component: page(() => import('@/pages/channel-editor.vue')),
+	loginRequired: true,
+}, {
+	path: '/channels/:channelId',
+	component: page(() => import('@/pages/channel.vue')),
+}, {
+	path: '/channels',
+	component: page(() => import('@/pages/channels.vue')),
+}, {
+	path: '/custom-emojis-manager',
+	component: page(() => import('@/pages/custom-emojis-manager.vue')),
+}, {
+	path: '/avatar-decorations',
+	name: 'avatarDecorations',
+	component: page(() => import('@/pages/avatar-decorations.vue')),
+}, {
+	path: '/registry/keys/:domain/:path(*)?',
+	component: page(() => import('@/pages/registry.keys.vue')),
+}, {
+	path: '/registry/value/:domain/:path(*)?',
+	component: page(() => import('@/pages/registry.value.vue')),
+}, {
+	path: '/registry',
+	component: page(() => import('@/pages/registry.vue')),
+}, {
+	path: '/install-extentions',
+	redirect: '/install-extensions',
+	loginRequired: true,
+}, {
+	path: '/install-extensions',
+	component: page(() => import('@/pages/install-extensions.vue')),
+	loginRequired: true,
+}, {
+	path: '/admin/user/:userId',
+	component: iAmModerator ? page(() => import('@/pages/admin-user.vue')) : page(() => import('@/pages/not-found.vue')),
+}, {
+	path: '/admin/file/:fileId',
+	component: iAmModerator ? page(() => import('@/pages/admin-file.vue')) : page(() => import('@/pages/not-found.vue')),
+}, {
+	path: '/admin',
+	component: iAmModerator ? page(() => import('@/pages/admin/index.vue')) : page(() => import('@/pages/not-found.vue')),
+	children: [{
+		path: '/overview',
+		name: 'overview',
+		component: page(() => import('@/pages/admin/overview.vue')),
+	}, {
+		path: '/users',
+		name: 'users',
+		component: page(() => import('@/pages/admin/users.vue')),
+	}, {
+		path: '/emojis',
+		name: 'emojis',
+		component: page(() => import('@/pages/custom-emojis-manager.vue')),
+	}, {
+		path: '/avatar-decorations',
+		name: 'avatarDecorations',
+		component: page(() => import('@/pages/avatar-decorations.vue')),
+	}, {
+		path: '/queue',
+		name: 'queue',
+		component: page(() => import('@/pages/admin/queue.vue')),
+	}, {
+		path: '/files',
+		name: 'files',
+		component: page(() => import('@/pages/admin/files.vue')),
+	}, {
+		path: '/federation',
+		name: 'federation',
+		component: page(() => import('@/pages/admin/federation.vue')),
+	}, {
+		path: '/announcements',
+		name: 'announcements',
+		component: page(() => import('@/pages/admin/announcements.vue')),
+	}, {
+		path: '/ads',
+		name: 'ads',
+		component: page(() => import('@/pages/admin/ads.vue')),
+	}, {
+		path: '/roles/:id/edit',
+		name: 'roles',
+		component: page(() => import('@/pages/admin/roles.edit.vue')),
+	}, {
+		path: '/roles/new',
+		name: 'roles',
+		component: page(() => import('@/pages/admin/roles.edit.vue')),
+	}, {
+		path: '/roles/:id',
+		name: 'roles',
+		component: page(() => import('@/pages/admin/roles.role.vue')),
+	}, {
+		path: '/roles',
+		name: 'roles',
+		component: page(() => import('@/pages/admin/roles.vue')),
+	}, {
+		path: '/database',
+		name: 'database',
+		component: page(() => import('@/pages/admin/database.vue')),
+	}, {
+		path: '/abuses',
+		name: 'abuses',
+		component: page(() => import('@/pages/admin/abuses.vue')),
+	}, {
+		path: '/modlog',
+		name: 'modlog',
+		component: page(() => import('@/pages/admin/modlog.vue')),
+	}, {
+		path: '/settings',
+		name: 'settings',
+		component: page(() => import('@/pages/admin/settings.vue')),
+	}, {
+		path: '/branding',
+		name: 'branding',
+		component: page(() => import('@/pages/admin/branding.vue')),
+	}, {
+		path: '/moderation',
+		name: 'moderation',
+		component: page(() => import('@/pages/admin/moderation.vue')),
+	}, {
+		path: '/email-settings',
+		name: 'email-settings',
+		component: page(() => import('@/pages/admin/email-settings.vue')),
+	}, {
+		path: '/object-storage',
+		name: 'object-storage',
+		component: page(() => import('@/pages/admin/object-storage.vue')),
+	}, {
+		path: '/security',
+		name: 'security',
+		component: page(() => import('@/pages/admin/security.vue')),
+	}, {
+		path: '/relays',
+		name: 'relays',
+		component: page(() => import('@/pages/admin/relays.vue')),
+	}, {
+		path: '/instance-block',
+		name: 'instance-block',
+		component: page(() => import('@/pages/admin/instance-block.vue')),
+	}, {
+		path: '/proxy-account',
+		name: 'proxy-account',
+		component: page(() => import('@/pages/admin/proxy-account.vue')),
+	}, {
+		path: '/external-services',
+		name: 'external-services',
+		component: page(() => import('@/pages/admin/external-services.vue')),
+	}, {
+		path: '/other-settings',
+		name: 'other-settings',
+		component: page(() => import('@/pages/admin/other-settings.vue')),
+	}, {
+		path: '/server-rules',
+		name: 'server-rules',
+		component: page(() => import('@/pages/admin/server-rules.vue')),
+	}, {
+		path: '/invites',
+		name: 'invites',
+		component: page(() => import('@/pages/admin/invites.vue')),
+	}, {
+		path: '/approvals',
+		name: 'approvals',
+		component: page(() => import('@/pages/admin/approvals.vue')),
+	}, {
+		path: '/',
+		component: page(() => import('@/pages/_empty_.vue')),
+	}],
+}, {
+	path: '/my/notifications',
+	component: page(() => import('@/pages/notifications.vue')),
+	loginRequired: true,
+}, {
+	path: '/my/favorites',
+	component: page(() => import('@/pages/favorites.vue')),
+	loginRequired: true,
+}, {
+	path: '/my/achievements',
+	component: page(() => import('@/pages/achievements.vue')),
+	loginRequired: true,
+}, {
+	path: '/my/drive/folder/:folder',
+	component: page(() => import('@/pages/drive.vue')),
+	loginRequired: true,
+}, {
+	path: '/my/drive',
+	component: page(() => import('@/pages/drive.vue')),
+	loginRequired: true,
+}, {
+	path: '/my/drive/file/:fileId',
+	component: page(() => import('@/pages/drive.file.vue')),
+	loginRequired: true,
+}, {
+	path: '/my/follow-requests',
+	component: page(() => import('@/pages/follow-requests.vue')),
+	loginRequired: true,
+}, {
+	path: '/my/lists/:listId',
+	component: page(() => import('@/pages/my-lists/list.vue')),
+	loginRequired: true,
+}, {
+	path: '/my/lists',
+	component: page(() => import('@/pages/my-lists/index.vue')),
+	loginRequired: true,
+}, {
+	path: '/my/clips',
+	component: page(() => import('@/pages/my-clips/index.vue')),
+	loginRequired: true,
+}, {
+	path: '/my/antennas/create',
+	component: page(() => import('@/pages/my-antennas/create.vue')),
+	loginRequired: true,
+}, {
+	path: '/my/antennas/:antennaId',
+	component: page(() => import('@/pages/my-antennas/edit.vue')),
+	loginRequired: true,
+}, {
+	path: '/my/antennas',
+	component: page(() => import('@/pages/my-antennas/index.vue')),
+	loginRequired: true,
+}, {
+	path: '/timeline/list/:listId',
+	component: page(() => import('@/pages/user-list-timeline.vue')),
+	loginRequired: true,
+}, {
+	path: '/timeline/antenna/:antennaId',
+	component: page(() => import('@/pages/antenna-timeline.vue')),
+	loginRequired: true,
+}, {
+	path: '/clicker',
+	component: page(() => import('@/pages/clicker.vue')),
+	loginRequired: true,
+}, {
+	path: '/games',
+	component: page(() => import('@/pages/games.vue')),
+	loginRequired: false,
+}, {
+	path: '/bubble-game',
+	component: page(() => import('@/pages/drop-and-fusion.vue')),
+	loginRequired: true,
+}, {
+	path: '/reversi',
+	component: page(() => import('@/pages/reversi/index.vue')),
+	loginRequired: false,
+}, {
+	path: '/reversi/g/:gameId',
+	component: page(() => import('@/pages/reversi/game.vue')),
+	loginRequired: false,
+}, {
+	path: '/timeline',
+	component: page(() => import('@/pages/timeline.vue')),
+}, {
+	name: 'index',
+	path: '/',
+	component: $i ? page(() => import('@/pages/timeline.vue')) : page(() => import('@/pages/welcome.vue')),
+	globalCacheKey: 'index',
+}, {
+	// テスト用リダイレクト設定。ログイン中ユーザのプロフィールにリダイレクトする
+	path: '/redirect-test',
+	redirect: $i ? `@${$i.username}` : '/',
+	loginRequired: true,
+}, {
+	path: '/:(*)',
+	component: page(() => import('@/pages/not-found.vue')),
+}];
+
+function createRouterImpl(path: string): IRouter {
+	return new Router(routes, path, !!$i, page(() => import('@/pages/not-found.vue')));
+}
+
+/**
+ * {@link Router}による画面遷移を可能とするために{@link mainRouter}をセットアップする。
+ * また、{@link Router}のインスタンスを作成するためのファクトリも{@link provide}経由で公開する(`routerFactory`というキーで取得可能)
+ */
+export function setupRouter(app: App) {
+	app.provide('routerFactory', createRouterImpl);
+
+	const mainRouter = createRouterImpl(location.pathname + location.search + location.hash);
+
+	window.addEventListener('popstate', (event) => {
+		mainRouter.replace(location.pathname + location.search + location.hash, event.state?.key);
+	});
+
+	mainRouter.addListener('push', ctx => {
+		window.history.pushState({ key: ctx.key }, '', ctx.path);
+	});
+
+	mainRouter.addListener('same', () => {
+		window.scroll({ top: 0, behavior: 'smooth' });
+	});
+
+	mainRouter.addListener('replace', ctx => {
+		window.history.replaceState({ key: ctx.key }, '', ctx.path);
+	});
+
+	mainRouter.init();
+
+	setMainRouter(mainRouter);
+}
diff --git a/packages/frontend/src/router/main.ts b/packages/frontend/src/router/main.ts
new file mode 100644
index 0000000000..7a3fde131e
--- /dev/null
+++ b/packages/frontend/src/router/main.ts
@@ -0,0 +1,167 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { ShallowRef } from 'vue';
+import { EventEmitter } from 'eventemitter3';
+import { IRouter, Resolved, RouteDef, RouterEvent } from '@/nirax.js';
+
+function getMainRouter(): IRouter {
+	const router = mainRouterHolder;
+	if (!router) {
+		throw new Error('mainRouter is not found.');
+	}
+
+	return router;
+}
+
+/**
+ * メインルータを設定する。一度設定すると、それ以降は変更できない。
+ * {@link setupRouter}から呼び出されることのみを想定している。
+ */
+export function setMainRouter(router: IRouter) {
+	if (mainRouterHolder) {
+		throw new Error('mainRouter is already exists.');
+	}
+
+	mainRouterHolder = router;
+}
+
+/**
+ * {@link mainRouter}用のプロキシ実装。
+ * {@link mainRouter}は起動シーケンスの一部にて初期化されるため、僅かにundefinedになる期間がある。
+ * その僅かな期間のためだけに型をundefined込みにしたくないのでこのクラスを緩衝材として使用する。
+ */
+class MainRouterProxy implements IRouter {
+	private supplier: () => IRouter;
+
+	constructor(supplier: () => IRouter) {
+		this.supplier = supplier;
+	}
+
+	get current(): Resolved {
+		return this.supplier().current;
+	}
+
+	get currentRef(): ShallowRef<Resolved> {
+		return this.supplier().currentRef;
+	}
+
+	get currentRoute(): ShallowRef<RouteDef> {
+		return this.supplier().currentRoute;
+	}
+
+	get navHook(): ((path: string, flag?: any) => boolean) | null {
+		return this.supplier().navHook;
+	}
+
+	set navHook(value) {
+		this.supplier().navHook = value;
+	}
+
+	getCurrentKey(): string {
+		return this.supplier().getCurrentKey();
+	}
+
+	getCurrentPath(): any {
+		return this.supplier().getCurrentPath();
+	}
+
+	push(path: string, flag?: any): void {
+		this.supplier().push(path, flag);
+	}
+
+	replace(path: string, key?: string | null): void {
+		this.supplier().replace(path, key);
+	}
+
+	resolve(path: string): Resolved | null {
+		return this.supplier().resolve(path);
+	}
+
+	init(): void {
+		this.supplier().init();
+	}
+
+	eventNames(): Array<EventEmitter.EventNames<RouterEvent>> {
+		return this.supplier().eventNames();
+	}
+
+	listeners<T extends EventEmitter.EventNames<RouterEvent>>(
+		event: T,
+	): Array<EventEmitter.EventListener<RouterEvent, T>> {
+		return this.supplier().listeners(event);
+	}
+
+	listenerCount(
+		event: EventEmitter.EventNames<RouterEvent>,
+	): number {
+		return this.supplier().listenerCount(event);
+	}
+
+	emit<T extends EventEmitter.EventNames<RouterEvent>>(
+		event: T,
+		...args: EventEmitter.EventArgs<RouterEvent, T>
+	): boolean {
+		return this.supplier().emit(event, ...args);
+	}
+
+	on<T extends EventEmitter.EventNames<RouterEvent>>(
+		event: T,
+		fn: EventEmitter.EventListener<RouterEvent, T>,
+		context?: any,
+	): this {
+		this.supplier().on(event, fn, context);
+		return this;
+	}
+
+	addListener<T extends EventEmitter.EventNames<RouterEvent>>(
+		event: T,
+		fn: EventEmitter.EventListener<RouterEvent, T>,
+		context?: any,
+	): this {
+		this.supplier().addListener(event, fn, context);
+		return this;
+	}
+
+	once<T extends EventEmitter.EventNames<RouterEvent>>(
+		event: T,
+		fn: EventEmitter.EventListener<RouterEvent, T>,
+		context?: any,
+	): this {
+		this.supplier().once(event, fn, context);
+		return this;
+	}
+
+	removeListener<T extends EventEmitter.EventNames<RouterEvent>>(
+		event: T,
+		fn?: EventEmitter.EventListener<RouterEvent, T>,
+		context?: any,
+		once?: boolean,
+	): this {
+		this.supplier().removeListener(event, fn, context, once);
+		return this;
+	}
+
+	off<T extends EventEmitter.EventNames<RouterEvent>>(
+		event: T,
+		fn?: EventEmitter.EventListener<RouterEvent, T>,
+		context?: any,
+		once?: boolean,
+	): this {
+		this.supplier().off(event, fn, context, once);
+		return this;
+	}
+
+	removeAllListeners(
+		event?: EventEmitter.EventNames<RouterEvent>,
+	): this {
+		this.supplier().removeAllListeners(event);
+		return this;
+	}
+}
+
+let mainRouterHolder: IRouter | null = null;
+
+export const mainRouter: IRouter = new MainRouterProxy(getMainRouter);
diff --git a/packages/frontend/src/router/supplier.ts b/packages/frontend/src/router/supplier.ts
new file mode 100644
index 0000000000..7da236f4e7
--- /dev/null
+++ b/packages/frontend/src/router/supplier.ts
@@ -0,0 +1,30 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { inject } from 'vue';
+import { IRouter, Router } from '@/nirax.js';
+import { mainRouter } from '@/router/main.js';
+
+/**
+ * メインの{@link Router}を取得する。
+ * あらかじめ{@link setupRouter}を実行しておく必要がある({@link provide}により{@link IRouter}のインスタンスを注入可能であるならばこの限りではない)
+ */
+export function useRouter(): IRouter {
+	return inject<Router | null>('router', null) ?? mainRouter;
+}
+
+/**
+ * 任意の{@link Router}を取得するためのファクトリを取得する。
+ * あらかじめ{@link setupRouter}を実行しておく必要がある。
+ */
+export function useRouterFactory(): (path: string) => IRouter {
+	const factory = inject<(path: string) => IRouter>('routerFactory');
+	if (!factory) {
+		console.error('routerFactory is not defined.');
+		throw new Error('routerFactory is not defined.');
+	}
+
+	return factory;
+}
diff --git a/packages/frontend/src/scripts/achievements.ts b/packages/frontend/src/scripts/achievements.ts
index e7585fcf81..f5d0ab559f 100644
--- a/packages/frontend/src/scripts/achievements.ts
+++ b/packages/frontend/src/scripts/achievements.ts
@@ -1,9 +1,9 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { $i } from '@/account.js';
 
 export const ACHIEVEMENT_TYPES = [
@@ -83,6 +83,8 @@ export const ACHIEVEMENT_TYPES = [
 	'brainDiver',
 	'smashTestNotificationButton',
 	'tutorialCompleted',
+	'bubbleGameExplodingHead',
+	'bubbleGameDoubleExplodingHead',
 ] as const;
 
 export const ACHIEVEMENT_BADGES = {
@@ -466,6 +468,16 @@ export const ACHIEVEMENT_BADGES = {
 		bg: 'linear-gradient(0deg, rgb(220 223 225), rgb(172 192 207))',
 		frame: 'bronze',
 	},
+	'bubbleGameExplodingHead': {
+		img: '/fluent-emoji/1f92f.png',
+		bg: 'linear-gradient(0deg, rgb(255 77 77), rgb(247 155 214))',
+		frame: 'bronze',
+	},
+	'bubbleGameDoubleExplodingHead': {
+		img: '/fluent-emoji/1f92f.png',
+		bg: 'linear-gradient(0deg, rgb(255 77 77), rgb(247 155 214))',
+		frame: 'silver',
+	},
 /* @see <https://github.com/misskey-dev/misskey/pull/10365#discussion_r1155511107>
 } as const satisfies Record<typeof ACHIEVEMENT_TYPES[number], {
 	img: string;
@@ -489,7 +501,7 @@ export async function claimAchievement(type: typeof ACHIEVEMENT_TYPES[number]) {
 	window.setTimeout(() => {
 		claimingQueue.delete(type);
 	}, 500);
-	os.api('i/claim-achievement', { name: type });
+	misskeyApi('i/claim-achievement', { name: type });
 }
 
 if (_DEV_) {
diff --git a/packages/frontend/src/scripts/aiscript/api.ts b/packages/frontend/src/scripts/aiscript/api.ts
index 038ae23109..98a0c61752 100644
--- a/packages/frontend/src/scripts/aiscript/api.ts
+++ b/packages/frontend/src/scripts/aiscript/api.ts
@@ -1,16 +1,27 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import { utils, values } from '@syuilo/aiscript';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { $i } from '@/account.js';
 import { miLocalStorage } from '@/local-storage.js';
 import { customEmojis } from '@/custom-emojis.js';
 import { url, lang } from '@/config.js';
 import { nyaize } from '@/scripts/nyaize.js';
 
+export function aiScriptReadline(q: string): Promise<string> {
+	return new Promise(ok => {
+		os.inputText({
+			title: q,
+		}).then(({ result: a }) => {
+			ok(a ?? '');
+		});
+	});
+}
+
 export function createAiScriptEnv(opts) {
 	return {
 		USER_ID: $i ? values.STR($i.id) : values.NULL,
@@ -44,7 +55,7 @@ export function createAiScriptEnv(opts) {
 				if (typeof token.value !== 'string') throw new Error('invalid token');
 			}
 			const actualToken: string|null = token?.value ?? opts.token ?? null;
-			return os.api(ep.value, utils.valToJs(param), actualToken).then(res => {
+			return misskeyApi(ep.value, utils.valToJs(param), actualToken).then(res => {
 				return utils.jsToVal(res);
 			}, err => {
 				return values.ERROR('request_failed', utils.jsToVal(err));
diff --git a/packages/frontend/src/scripts/aiscript/ui.ts b/packages/frontend/src/scripts/aiscript/ui.ts
index 08ba1e6d9b..f2493264d3 100644
--- a/packages/frontend/src/scripts/aiscript/ui.ts
+++ b/packages/frontend/src/scripts/aiscript/ui.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -218,7 +218,7 @@ function getTextOptions(def: values.Value | undefined): Omit<AsUiText, 'id' | 't
 	};
 }
 
-function getMfmOptions(def: values.Value | undefined): Omit<AsUiMfm, 'id' | 'type'> {
+function getMfmOptions(def: values.Value | undefined, call: (fn: values.VFn, args: values.Value[]) => Promise<values.Value>): Omit<AsUiMfm, 'id' | 'type'> {
 	utils.assertObject(def);
 
 	const text = def.value.get('text');
@@ -241,7 +241,7 @@ function getMfmOptions(def: values.Value | undefined): Omit<AsUiMfm, 'id' | 'typ
 		color: color?.value,
 		font: font?.value,
 		onClickEv: (evId: string) => {
-			if (onClickEv) call(onClickEv, values.STR(evId));
+			if (onClickEv) call(onClickEv, [values.STR(evId)]);
 		},
 	};
 }
diff --git a/packages/frontend/src/scripts/array.ts b/packages/frontend/src/scripts/array.ts
index 082703a450..b3d76e149f 100644
--- a/packages/frontend/src/scripts/array.ts
+++ b/packages/frontend/src/scripts/array.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/autocomplete.ts b/packages/frontend/src/scripts/autocomplete.ts
index 2a9a42ace5..9fc8f7843e 100644
--- a/packages/frontend/src/scripts/autocomplete.ts
+++ b/packages/frontend/src/scripts/autocomplete.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -8,18 +8,18 @@ import getCaretCoordinates from 'textarea-caret';
 import { toASCII } from 'punycode/';
 import { popup } from '@/os.js';
 
-export type SuggestionType = 'user' | 'hashtag' | 'emoji' | 'mfmTag';
+export type SuggestionType = 'user' | 'hashtag' | 'emoji' | 'mfmTag' | 'mfmParam';
 
 export class Autocomplete {
 	private suggestion: {
 		x: Ref<number>;
 		y: Ref<number>;
-		q: Ref<string | null>;
+		q: Ref<any>;
 		close: () => void;
 	} | null;
 	private textarea: HTMLInputElement | HTMLTextAreaElement;
 	private currentType: string;
-	private textRef: Ref<string>;
+	private textRef: Ref<string | number | null>;
 	private opening: boolean;
 	private onlyType: SuggestionType[];
 
@@ -38,7 +38,7 @@ export class Autocomplete {
 	/**
 	 * 対象のテキストエリアを与えてインスタンスを初期化します。
 	 */
-	constructor(textarea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string>, onlyType?: SuggestionType[]) {
+	constructor(textarea: HTMLInputElement | HTMLTextAreaElement, textRef: Ref<string | number | null>, onlyType?: SuggestionType[]) {
 		//#region BIND
 		this.onInput = this.onInput.bind(this);
 		this.complete = this.complete.bind(this);
@@ -49,7 +49,7 @@ export class Autocomplete {
 		this.textarea = textarea;
 		this.textRef = textRef;
 		this.opening = false;
-		this.onlyType = onlyType ?? ['user', 'hashtag', 'emoji', 'mfmTag'];
+		this.onlyType = onlyType ?? ['user', 'hashtag', 'emoji', 'mfmTag', 'mfmParam'];
 
 		this.attach();
 	}
@@ -80,6 +80,7 @@ export class Autocomplete {
 		const hashtagIndex = text.lastIndexOf('#');
 		const emojiIndex = text.lastIndexOf(':');
 		const mfmTagIndex = text.lastIndexOf('$');
+		const mfmParamIndex = text.lastIndexOf('.');
 
 		const max = Math.max(
 			mentionIndex,
@@ -92,9 +93,12 @@ export class Autocomplete {
 			return;
 		}
 
+		const afterLastMfmParam = text.split(/\$\[[a-zA-Z]+/).pop();
+
 		const isMention = mentionIndex !== -1;
 		const isHashtag = hashtagIndex !== -1;
-		const isMfmTag = mfmTagIndex !== -1;
+		const isMfmParam = mfmParamIndex !== -1 && afterLastMfmParam?.includes('.') && !afterLastMfmParam?.includes(' ');
+		const isMfmTag = mfmTagIndex !== -1 && !isMfmParam;
 		const isEmoji = emojiIndex !== -1 && text.split(/:[a-z0-9_+\-]+:/).pop()!.includes(':');
 
 		let opened = false;
@@ -134,6 +138,17 @@ export class Autocomplete {
 			}
 		}
 
+		if (isMfmParam && !opened && this.onlyType.includes('mfmParam')) {
+			const mfmParam = text.substring(mfmParamIndex + 1);
+			if (!mfmParam.includes(' ')) {
+				this.open('mfmParam', {
+					tag: text.substring(mfmTagIndex + 2, mfmParamIndex),
+					params: mfmParam.split(','),
+				});
+				opened = true;
+			}
+		}
+
 		if (!opened) {
 			this.close();
 		}
@@ -142,7 +157,7 @@ export class Autocomplete {
 	/**
 	 * サジェストを提示します。
 	 */
-	private async open(type: string, q: string | null) {
+	private async open(type: string, q: any) {
 		if (type !== this.currentType) {
 			this.close();
 		}
@@ -280,6 +295,22 @@ export class Autocomplete {
 				const pos = trimmedBefore.length + (value.length + 3);
 				this.textarea.setSelectionRange(pos, pos);
 			});
+		} else if (type === 'mfmParam') {
+			const source = this.text;
+
+			const before = source.substring(0, caret);
+			const trimmedBefore = before.substring(0, before.lastIndexOf('.'));
+			const after = source.substring(caret);
+
+			// 挿入
+			this.text = `${trimmedBefore}.${value}${after}`;
+
+			// キャレットを戻す
+			nextTick(() => {
+				this.textarea.focus();
+				const pos = trimmedBefore.length + (value.length + 1);
+				this.textarea.setSelectionRange(pos, pos);
+			});
 		}
 	}
 }
diff --git a/packages/frontend/src/scripts/boost-quote.ts b/packages/frontend/src/scripts/boost-quote.ts
new file mode 100644
index 0000000000..4e025f5d4f
--- /dev/null
+++ b/packages/frontend/src/scripts/boost-quote.ts
@@ -0,0 +1,81 @@
+/*
+ * SPDX-FileCopyrightText: dakkar and other misskey contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+*/
+
+import { ref, Ref } from 'vue';
+import * as Misskey from 'misskey-js';
+import { i18n } from '@/i18n.js';
+import { defaultStore } from '@/store.js';
+import { MenuItem } from '@/types/menu.js';
+
+/*
+	this script should eventually contain all Sharkey-specific bits of
+	boosting and quoting that we would otherwise have to replicate in
+	`{M,S}kNote{,Detailed,Sub}.vue`
+ */
+
+export type Visibility = 'public' | 'home' | 'followers' | 'specified';
+
+export function smallerVisibility(a: Visibility | string, b: Visibility | string): Visibility {
+	if (a === 'specified' || b === 'specified') return 'specified';
+	if (a === 'followers' || b === 'followers') return 'followers';
+	if (a === 'home' || b === 'home') return 'home';
+	// if (a === 'public' || b === 'public')
+	return 'public';
+}
+
+export function visibilityIsAtLeast(a: Visibility | string, b: Visibility | string): boolean {
+	return smallerVisibility(a, b) === b;
+}
+
+export function boostMenuItems(appearNote: Ref<Misskey.entities.Note>, renote: (v: Visibility, l: boolean) => void): MenuItem[] {
+	const localOnly = ref(defaultStore.state.rememberNoteVisibility ? defaultStore.state.localOnly : defaultStore.state.defaultNoteLocalOnly);
+	const effectiveVisibility = (
+		appearNote.value.channel?.isSensitive
+			? smallerVisibility(appearNote.value.visibility, 'home')
+			: appearNote.value.visibility
+	);
+
+	const menuItems: MenuItem[] = [];
+	if (visibilityIsAtLeast(effectiveVisibility, 'public')) {
+		menuItems.push({
+			type: 'button',
+			icon: 'ph-globe-hemisphere-west ph-bold ph-lg',
+			text: i18n.ts._visibility['public'],
+			action: () => {
+				renote('public', localOnly.value);
+			},
+		} as MenuItem);
+	}
+	if (visibilityIsAtLeast(effectiveVisibility, 'home')) {
+		menuItems.push({
+			type: 'button',
+			icon: 'ph-house ph-bold ph-lg',
+			text: i18n.ts._visibility['home'],
+			action: () => {
+				renote('home', localOnly.value);
+			},
+		} as MenuItem);
+	}
+	if (visibilityIsAtLeast(effectiveVisibility, 'followers')) {
+		menuItems.push({
+			type: 'button',
+			icon: 'ph-lock ph-bold ph-lg',
+			text: i18n.ts._visibility['followers'],
+			action: () => {
+				renote('followers', localOnly.value);
+			},
+		} as MenuItem);
+	}
+
+	return [
+		...menuItems,
+		{
+			type: 'switch',
+			icon: 'ph-planet ph-bold ph-lg',
+			text: i18n.ts._timelines.local,
+			ref: localOnly,
+		} as MenuItem,
+	];
+}
diff --git a/packages/frontend/src/scripts/cache.ts b/packages/frontend/src/scripts/cache.ts
index 12347cf4b1..0fbdf34d5d 100644
--- a/packages/frontend/src/scripts/cache.ts
+++ b/packages/frontend/src/scripts/cache.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/chart-legend.ts b/packages/frontend/src/scripts/chart-legend.ts
index e91908e0cb..2d534f60c1 100644
--- a/packages/frontend/src/scripts/chart-legend.ts
+++ b/packages/frontend/src/scripts/chart-legend.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/chart-vline.ts b/packages/frontend/src/scripts/chart-vline.ts
index 336ec6cfbb..24e41245e7 100644
--- a/packages/frontend/src/scripts/chart-vline.ts
+++ b/packages/frontend/src/scripts/chart-vline.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/check-animated-mfm.ts b/packages/frontend/src/scripts/check-animated-mfm.ts
index 8e3ef7ee46..eac18738ee 100644
--- a/packages/frontend/src/scripts/check-animated-mfm.ts
+++ b/packages/frontend/src/scripts/check-animated-mfm.ts
@@ -1,4 +1,4 @@
-import * as mfm from '@sharkey/sfm-js';
+import * as mfm from '@transfem-org/sfm-js';
 
 export function checkAnimationFromMfm(nodes: mfm.MfmNode[]): boolean {
 	const animatedNodes = mfm.extract(nodes, (node) => {
diff --git a/packages/frontend/src/scripts/check-reaction-permissions.ts b/packages/frontend/src/scripts/check-reaction-permissions.ts
new file mode 100644
index 0000000000..e7b473dd75
--- /dev/null
+++ b/packages/frontend/src/scripts/check-reaction-permissions.ts
@@ -0,0 +1,12 @@
+import * as Misskey from 'misskey-js';
+import { UnicodeEmojiDef } from './emojilist.js';
+
+export function checkReactionPermissions(me: Misskey.entities.MeDetailed, note: Misskey.entities.Note, emoji: Misskey.entities.EmojiSimple | UnicodeEmojiDef | string): boolean {
+	if (typeof emoji === 'string') return true; // UnicodeEmojiDefにも無い絵文字であれば文字列で来る。Unicode絵文字であることには変わりないので常にリアクション可能とする;
+	if ('char' in emoji) return true; // UnicodeEmojiDefなら常にリアクション可能
+
+	const roleIdsThatCanBeUsedThisEmojiAsReaction = emoji.roleIdsThatCanBeUsedThisEmojiAsReaction ?? [];
+	return !(emoji.localOnly && note.user.host !== me.host)
+      && !(emoji.isSensitive && (note.reactionAcceptance === 'nonSensitiveOnly' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote'))
+      && (roleIdsThatCanBeUsedThisEmojiAsReaction.length === 0 || me.roles.some(role => roleIdsThatCanBeUsedThisEmojiAsReaction.includes(role.id)));
+}
diff --git a/packages/frontend/src/scripts/check-word-mute.ts b/packages/frontend/src/scripts/check-word-mute.ts
index 5ac19c8d5b..67e896b4b9 100644
--- a/packages/frontend/src/scripts/check-word-mute.ts
+++ b/packages/frontend/src/scripts/check-word-mute.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/chiptune2.ts b/packages/frontend/src/scripts/chiptune2.ts
index e52adb07d1..5a5b1d6c24 100644
--- a/packages/frontend/src/scripts/chiptune2.ts
+++ b/packages/frontend/src/scripts/chiptune2.ts
@@ -150,6 +150,27 @@ ChiptuneJsPlayer.prototype.getRow = function () {
 	return 0;
 };
 
+ChiptuneJsPlayer.prototype.getNumPatterns = function () {
+	if (this.currentPlayingNode && this.currentPlayingNode.modulePtr) {
+		return libopenmpt._openmpt_module_get_num_patterns(this.currentPlayingNode.modulePtr);
+	}
+	return 0;
+};
+
+ChiptuneJsPlayer.prototype.getCurrentSpeed = function () {
+	if (this.currentPlayingNode && this.currentPlayingNode.modulePtr) {
+		return libopenmpt._openmpt_module_get_current_speed(this.currentPlayingNode.modulePtr);
+	}
+	return 0;
+};
+
+ChiptuneJsPlayer.prototype.getCurrentTempo = function () {
+	if (this.currentPlayingNode && this.currentPlayingNode.modulePtr) {
+		return libopenmpt._openmpt_module_get_current_tempo(this.currentPlayingNode.modulePtr);
+	}
+	return 0;
+};
+
 ChiptuneJsPlayer.prototype.getPatternNumRows = function (pattern: number) {
 	if (this.currentPlayingNode && this.currentPlayingNode.modulePtr) {
 		return libopenmpt._openmpt_module_get_pattern_num_rows(this.currentPlayingNode.modulePtr, pattern);
@@ -164,6 +185,20 @@ ChiptuneJsPlayer.prototype.getPatternRowChannel = function (pattern: number, row
 	return '';
 };
 
+ChiptuneJsPlayer.prototype.getCtls = function () {
+	if (this.currentPlayingNode && this.currentPlayingNode.modulePtr) {
+		return libopenmpt._openmpt_module_get_ctls(this.currentPlayingNode.modulePtr);
+	}
+	return 0;
+};
+
+ChiptuneJsPlayer.prototype.version = function () {
+	if (this.currentPlayingNode && this.currentPlayingNode.modulePtr) {
+		return libopenmpt._openmpt_get_library_version();
+	}
+	return 0;
+};
+
 ChiptuneJsPlayer.prototype.createLibopenmptNode = function (buffer, config: object) {
 	const maxFramesPerChunk = 4096;
 	const processNode = this.audioContext.createScriptProcessor(2048, 0, 2);
@@ -178,6 +213,7 @@ ChiptuneJsPlayer.prototype.createLibopenmptNode = function (buffer, config: obje
 	processNode.paused = false;
 	processNode.leftBufferPtr = libopenmpt._malloc(4 * maxFramesPerChunk);
 	processNode.rightBufferPtr = libopenmpt._malloc(4 * maxFramesPerChunk);
+	processNode.perf = { 'current': 0, 'max': 0 };
 	processNode.cleanup = function () {
 		if (this.modulePtr !== 0) {
 			libopenmpt._openmpt_module_destroy(this.modulePtr);
@@ -205,7 +241,13 @@ ChiptuneJsPlayer.prototype.createLibopenmptNode = function (buffer, config: obje
 	processNode.togglePause = function () {
 		this.paused = !this.paused;
 	};
+	processNode.getProcessTime = function() {
+		const max = this.perf.max;
+		this.perf.max = 0;
+		return { 'current': this.perf.current, 'max': max };
+	};
 	processNode.onaudioprocess = function (e) {
+		let startTimeP1 = performance.now();
 		const outputL = e.outputBuffer.getChannelData(0);
 		const outputR = e.outputBuffer.getChannelData(1);
 		let framesToRender = outputL.length;
@@ -231,11 +273,13 @@ ChiptuneJsPlayer.prototype.createLibopenmptNode = function (buffer, config: obje
 
 		const currentPattern = libopenmpt._openmpt_module_get_current_pattern(this.modulePtr);
 		const currentRow = libopenmpt._openmpt_module_get_current_row(this.modulePtr);
+		startTimeP1 = startTimeP1 - performance.now();
 		if (currentPattern !== this.patternIndex) {
 			processNode.player.fireEvent('onPatternChange');
 		}
 		processNode.player.fireEvent('onRowChange', { index: currentRow });
 
+		const startTimeP2 = performance.now();
 		while (framesToRender > 0) {
 			const framesPerChunk = Math.min(framesToRender, maxFramesPerChunk);
 			const actualFramesPerChunk = libopenmpt._openmpt_module_read_float_stereo(this.modulePtr, this.context.sampleRate, framesPerChunk, this.leftBufferPtr, this.rightBufferPtr);
@@ -262,6 +306,8 @@ ChiptuneJsPlayer.prototype.createLibopenmptNode = function (buffer, config: obje
 			this.cleanup();
 			error ? processNode.player.fireEvent('onError', { type: 'openmpt' }) : processNode.player.fireEvent('onEnded');
 		}
+		this.perf.current = performance.now() - startTimeP2 + startTimeP1;
+		if (this.perf.current > this.perf.max) this.perf.max = this.perf.current;
 	};
 	return processNode;
 };
diff --git a/packages/frontend/src/scripts/clear-cache.ts b/packages/frontend/src/scripts/clear-cache.ts
index f2db87c4fb..b20109ec72 100644
--- a/packages/frontend/src/scripts/clear-cache.ts
+++ b/packages/frontend/src/scripts/clear-cache.ts
@@ -2,14 +2,18 @@ import { unisonReload } from '@/scripts/unison-reload.js';
 import * as os from '@/os.js';
 import { miLocalStorage } from '@/local-storage.js';
 import { fetchCustomEmojis } from '@/custom-emojis.js';
+import { fetchInstance } from '@/instance.js';
 
 export async function clearCache() {
 	os.waiting();
+	miLocalStorage.removeItem('instance');
+	miLocalStorage.removeItem('instanceCachedAt');
 	miLocalStorage.removeItem('locale');
 	miLocalStorage.removeItem('localeVersion');
 	miLocalStorage.removeItem('theme');
 	miLocalStorage.removeItem('emojis');
 	miLocalStorage.removeItem('lastEmojisFetchedAt');
+	await fetchInstance(true);
 	await fetchCustomEmojis(true);
 	unisonReload();
 }
diff --git a/packages/frontend/src/scripts/clicker-game.ts b/packages/frontend/src/scripts/clicker-game.ts
index 5ad076e5ef..f9c4bc1829 100644
--- a/packages/frontend/src/scripts/clicker-game.ts
+++ b/packages/frontend/src/scripts/clicker-game.ts
@@ -1,10 +1,10 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import { ref, computed } from 'vue';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 
 type SaveData = {
 	gameVersion: number;
@@ -23,7 +23,7 @@ let prev = '';
 
 export async function load() {
 	try {
-		saveData.value = await os.api('i/registry/get', {
+		saveData.value = await misskeyApi('i/registry/get', {
 			scope: ['clickerGame'],
 			key: 'saveData',
 		});
@@ -63,7 +63,7 @@ export async function save() {
 	const current = JSON.stringify(saveData.value);
 	if (current === prev) return;
 
-	await os.api('i/registry/set', {
+	await misskeyApi('i/registry/set', {
 		scope: ['clickerGame'],
 		key: 'saveData',
 		value: saveData.value,
diff --git a/packages/frontend/src/scripts/clone.ts b/packages/frontend/src/scripts/clone.ts
index 96b53684f3..ea8eea14b5 100644
--- a/packages/frontend/src/scripts/clone.ts
+++ b/packages/frontend/src/scripts/clone.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -8,15 +8,15 @@
 // あと、Vue RefをIndexedDBに保存しようとしてstructredCloneを使ったらエラーになった
 // https://github.com/misskey-dev/misskey/pull/8098#issuecomment-1114144045
 
-type Cloneable = string | number | boolean | null | { [key: string]: Cloneable } | Cloneable[];
+export type Cloneable = string | number | boolean | null | undefined | { [key: string]: Cloneable } | { [key: number]: Cloneable } | { [key: symbol]: Cloneable } | Cloneable[];
 
 export function deepClone<T extends Cloneable>(x: T): T {
 	if (typeof x === 'object') {
 		if (x === null) return x;
 		if (Array.isArray(x)) return x.map(deepClone) as T;
-		const obj = {} as Record<string, Cloneable>;
+		const obj = {} as Record<string | number | symbol, Cloneable>;
 		for (const [k, v] of Object.entries(x)) {
-			obj[k] = deepClone(v);
+			obj[k] = v === undefined ? undefined : deepClone(v);
 		}
 		return obj as T;
 	} else {
diff --git a/packages/frontend/src/scripts/code-highlighter.ts b/packages/frontend/src/scripts/code-highlighter.ts
index 957669122e..2733897bab 100644
--- a/packages/frontend/src/scripts/code-highlighter.ts
+++ b/packages/frontend/src/scripts/code-highlighter.ts
@@ -1,10 +1,51 @@
-import { setWasm, setCDN, Highlighter, getHighlighter as _getHighlighter } from 'shiki';
-
-setWasm('/assets/shiki/dist/onig.wasm');
-setCDN('/assets/shiki/');
+import { bundledThemesInfo } from 'shiki';
+import { getHighlighterCore, loadWasm } from 'shiki/core';
+import darkPlus from 'shiki/themes/dark-plus.mjs';
+import { unique } from './array.js';
+import { deepClone } from './clone.js';
+import { deepMerge } from './merge.js';
+import type { Highlighter, LanguageRegistration, ThemeRegistration, ThemeRegistrationRaw } from 'shiki';
+import { ColdDeviceStorage } from '@/store.js';
+import lightTheme from '@/themes/_light.json5';
+import darkTheme from '@/themes/_dark.json5';
 
 let _highlighter: Highlighter | null = null;
 
+export async function getTheme(mode: 'light' | 'dark', getName: true): Promise<string>;
+export async function getTheme(mode: 'light' | 'dark', getName?: false): Promise<ThemeRegistration | ThemeRegistrationRaw>;
+export async function getTheme(mode: 'light' | 'dark', getName = false): Promise<ThemeRegistration | ThemeRegistrationRaw | string | null> {
+	const theme = deepClone(ColdDeviceStorage.get(mode === 'light' ? 'lightTheme' : 'darkTheme'));
+
+	if (theme.base) {
+		const base = [lightTheme, darkTheme].find(x => x.id === theme.base);
+		if (base && base.codeHighlighter) theme.codeHighlighter = Object.assign({}, base.codeHighlighter, theme.codeHighlighter);
+	}
+
+	if (theme.codeHighlighter) {
+		let _res: ThemeRegistration = {};
+		if (theme.codeHighlighter.base === '_none_') {
+			_res = deepClone(theme.codeHighlighter.overrides);
+		} else {
+			const base = await bundledThemesInfo.find(t => t.id === theme.codeHighlighter!.base)?.import() ?? darkPlus;
+			_res = deepMerge(theme.codeHighlighter.overrides ?? {}, 'default' in base ? base.default : base);
+		}
+		if (_res.name == null) {
+			_res.name = theme.id;
+		}
+		_res.type = mode;
+
+		if (getName) {
+			return _res.name;
+		}
+		return _res;
+	}
+
+	if (getName) {
+		return 'dark-plus';
+	}
+	return darkPlus;
+}
+
 export async function getHighlighter(): Promise<Highlighter> {
 	if (!_highlighter) {
 		return await initHighlighter();
@@ -13,16 +54,36 @@ export async function getHighlighter(): Promise<Highlighter> {
 }
 
 export async function initHighlighter() {
-	const highlighter = await _getHighlighter({
-		theme: 'dark-plus',
-		langs: ['js'],
+	const aiScriptGrammar = await import('aiscript-vscode/aiscript/syntaxes/aiscript.tmLanguage.json');
+
+	await loadWasm(import('shiki/onig.wasm?init'));
+
+	// テーマの重複を消す
+	const themes = unique([
+		darkPlus,
+		...(await Promise.all([getTheme('light'), getTheme('dark')])),
+	]);
+
+	const highlighter = await getHighlighterCore({
+		themes,
+		langs: [
+			import('shiki/langs/javascript.mjs'),
+			aiScriptGrammar.default as unknown as LanguageRegistration,
+		],
 	});
 
-	await highlighter.loadLanguage({
-		path: 'languages/aiscript.tmLanguage.json',
-		id: 'aiscript',
-		scopeName: 'source.aiscript',
-		aliases: ['is', 'ais'],
+	ColdDeviceStorage.watch('lightTheme', async () => {
+		const newTheme = await getTheme('light');
+		if (newTheme.name && !highlighter.getLoadedThemes().includes(newTheme.name)) {
+			highlighter.loadTheme(newTheme);
+		}
+	});
+
+	ColdDeviceStorage.watch('darkTheme', async () => {
+		const newTheme = await getTheme('dark');
+		if (newTheme.name && !highlighter.getLoadedThemes().includes(newTheme.name)) {
+			highlighter.loadTheme(newTheme);
+		}
 	});
 
 	_highlighter = highlighter;
diff --git a/packages/frontend/src/scripts/collapsed.ts b/packages/frontend/src/scripts/collapsed.ts
index 57e6ecf5b5..237bd37c7a 100644
--- a/packages/frontend/src/scripts/collapsed.ts
+++ b/packages/frontend/src/scripts/collapsed.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/collect-page-vars.ts b/packages/frontend/src/scripts/collect-page-vars.ts
index 79356e60eb..5096c0669e 100644
--- a/packages/frontend/src/scripts/collect-page-vars.ts
+++ b/packages/frontend/src/scripts/collect-page-vars.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/color.ts b/packages/frontend/src/scripts/color.ts
index 25ef41d9b7..a11255ffd1 100644
--- a/packages/frontend/src/scripts/color.ts
+++ b/packages/frontend/src/scripts/color.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/confetti.ts b/packages/frontend/src/scripts/confetti.ts
index b394ba3e2a..8e53a6ceeb 100644
--- a/packages/frontend/src/scripts/confetti.ts
+++ b/packages/frontend/src/scripts/confetti.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/contains.ts b/packages/frontend/src/scripts/contains.ts
index b50ce4128c..6137c06e85 100644
--- a/packages/frontend/src/scripts/contains.ts
+++ b/packages/frontend/src/scripts/contains.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/copy-to-clipboard.ts b/packages/frontend/src/scripts/copy-to-clipboard.ts
index 3884d4a20a..216c0464b3 100644
--- a/packages/frontend/src/scripts/copy-to-clipboard.ts
+++ b/packages/frontend/src/scripts/copy-to-clipboard.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/device-kind.ts b/packages/frontend/src/scripts/device-kind.ts
index 3843052a24..7c33f8ccee 100644
--- a/packages/frontend/src/scripts/device-kind.ts
+++ b/packages/frontend/src/scripts/device-kind.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -11,6 +11,13 @@ const ua = navigator.userAgent.toLowerCase();
 const isTablet = /ipad/.test(ua) || (/mobile|iphone|android/.test(ua) && window.innerWidth > 700);
 const isSmartphone = !isTablet && /mobile|iphone|android/.test(ua);
 
+const isIPhone = /iphone|ipod/gi.test(ua) && navigator.maxTouchPoints > 1;
+// navigator.platform may be deprecated but this check is still required
+const isIPadOS = navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1;
+const isIos = /ipad|iphone|ipod/gi.test(ua) && navigator.maxTouchPoints > 1;
+
+export const isFullscreenNotSupported = isIPhone || isIos;
+
 export const deviceKind: 'smartphone' | 'tablet' | 'desktop' = defaultStore.state.overridedDeviceKind ? defaultStore.state.overridedDeviceKind
 	: isSmartphone ? 'smartphone'
 	: isTablet ? 'tablet'
diff --git a/packages/frontend/src/scripts/emoji-base.ts b/packages/frontend/src/scripts/emoji-base.ts
index 46a13462a1..16a5a6aa5b 100644
--- a/packages/frontend/src/scripts/emoji-base.ts
+++ b/packages/frontend/src/scripts/emoji-base.ts
@@ -1,10 +1,11 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 const twemojiSvgBase = '/twemoji';
 const fluentEmojiPngBase = '/fluent-emoji';
+const tossfaceSvgBase = '/tossface';
 
 export function char2twemojiFilePath(char: string): string {
 	let codes = Array.from(char, x => x.codePointAt(0)?.toString(16));
@@ -23,3 +24,14 @@ export function char2fluentEmojiFilePath(char: string): string {
 	const fileName = codes.map(x => x!.padStart(4, '0')).join('-');
 	return `${fluentEmojiPngBase}/${fileName}.png`;
 }
+
+export function char2tossfaceFilePath(char: string): string {
+	let codes = Array.from(char, x => x.codePointAt(0)?.toString(16));
+	// Twemoji is the only emoji font which still supports the shibuya 50 emoji to this day
+	if (codes[0]?.startsWith('e50a')) return char2twemojiFilePath(char);
+	// Tossface does not use the fe0f modifier
+	codes = codes.filter(x => x !== 'fe0f');
+	codes = codes.filter(x => x && x.length);
+	const fileName = codes.join('-');
+	return `${tossfaceSvgBase}/${fileName}.svg`;
+}
diff --git a/packages/frontend/src/scripts/emoji-picker.ts b/packages/frontend/src/scripts/emoji-picker.ts
index f87c3f6fb2..14b5cbf35e 100644
--- a/packages/frontend/src/scripts/emoji-picker.ts
+++ b/packages/frontend/src/scripts/emoji-picker.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/emojilist.ts b/packages/frontend/src/scripts/emojilist.ts
index 8885bf4b7f..6565feba97 100644
--- a/packages/frontend/src/scripts/emojilist.ts
+++ b/packages/frontend/src/scripts/emojilist.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -20,6 +20,10 @@ export const emojilist: UnicodeEmojiDef[] = _emojilist.map(x => ({
 	category: unicodeEmojiCategories[x[2]],
 }));
 
+const unicodeEmojisMap = new Map<string, UnicodeEmojiDef>(
+	emojilist.map(x => [x.char, x]),
+);
+
 const _indexByChar = new Map<string, number>();
 const _charGroupByCategory = new Map<string, string[]>();
 for (let i = 0; i < emojilist.length; i++) {
@@ -35,15 +39,33 @@ for (let i = 0; i < emojilist.length; i++) {
 
 export const emojiCharByCategory = _charGroupByCategory;
 
-export function getEmojiName(char: string): string | null {
-	const idx = _indexByChar.get(char);
-	if (idx == null) {
-		return null;
+export function getUnicodeEmoji(char: string): UnicodeEmojiDef | string {
+	// Colorize it because emojilist.json assumes that
+	return unicodeEmojisMap.get(colorizeEmoji(char))
+		// カラースタイル絵文字がjsonに無い場合はテキストスタイル絵文字にフォールバックする
+		?? unicodeEmojisMap.get(char)
+		// それでも見つからない場合はそのまま返す(絵文字情報がjsonに無い場合、このフォールバックが無いとレンダリングに失敗する)
+		?? char;
+}
+
+export function getEmojiName(char: string): string {
+	// Colorize it because emojilist.json assumes that
+	const idx = _indexByChar.get(colorizeEmoji(char)) ?? _indexByChar.get(char);
+	if (idx === undefined) {
+		// 絵文字情報がjsonに無い場合は名前の取得が出来ないのでそのまま返すしか無い
+		return char;
 	} else {
 		return emojilist[idx].name;
 	}
 }
 
+/**
+ * テキストスタイル絵文字(U+260Eなどの1文字で表現される絵文字)をカラースタイル絵文字に変換します(VS16:U+FE0Fを付与)。
+ */
+export function colorizeEmoji(char: string) {
+	return char.length === 1 ? `${char}\uFE0F` : char;
+}
+
 export interface CustomEmojiFolderTree {
 	value: string;
 	category: string;
diff --git a/packages/frontend/src/scripts/extract-avg-color-from-blurhash.ts b/packages/frontend/src/scripts/extract-avg-color-from-blurhash.ts
index 57b296ab2a..992f6e9a16 100644
--- a/packages/frontend/src/scripts/extract-avg-color-from-blurhash.ts
+++ b/packages/frontend/src/scripts/extract-avg-color-from-blurhash.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/extract-mentions.ts b/packages/frontend/src/scripts/extract-mentions.ts
index fe60f9a851..89a5ce1df8 100644
--- a/packages/frontend/src/scripts/extract-mentions.ts
+++ b/packages/frontend/src/scripts/extract-mentions.ts
@@ -1,11 +1,11 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 // test is located in test/extract-mentions
 
-import * as mfm from '@sharkey/sfm-js';
+import * as mfm from '@transfem-org/sfm-js';
 
 export function extractMentions(nodes: mfm.MfmNode[]): mfm.MfmMention['props'][] {
 	// TODO: 重複を削除
diff --git a/packages/frontend/src/scripts/extract-url-from-mfm.ts b/packages/frontend/src/scripts/extract-url-from-mfm.ts
index d2fbfbcc00..a4c84aa740 100644
--- a/packages/frontend/src/scripts/extract-url-from-mfm.ts
+++ b/packages/frontend/src/scripts/extract-url-from-mfm.ts
@@ -1,9 +1,9 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import * as mfm from '@sharkey/sfm-js';
+import * as mfm from '@transfem-org/sfm-js';
 import { unique } from '@/scripts/array.js';
 
 // unique without hash
diff --git a/packages/frontend/src/scripts/focus.ts b/packages/frontend/src/scripts/focus.ts
index 6a31ebd431..ea6ee61c88 100644
--- a/packages/frontend/src/scripts/focus.ts
+++ b/packages/frontend/src/scripts/focus.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/form.ts b/packages/frontend/src/scripts/form.ts
index 222fd9b0b7..b0db404f28 100644
--- a/packages/frontend/src/scripts/form.ts
+++ b/packages/frontend/src/scripts/form.ts
@@ -1,51 +1,77 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-type EnumItem = string | {label: string; value: string;};
+type EnumItem = string | {
+	label: string;
+	value: string;
+};
+
 export type FormItem = {
 	label?: string;
 	type: 'string';
 	default: string | null;
+	description?: string;
+	required?: boolean;
 	hidden?: boolean;
 	multiline?: boolean;
+	treatAsMfm?: boolean;
 } | {
 	label?: string;
 	type: 'number';
 	default: number | null;
+	description?: string;
+	required?: boolean;
 	hidden?: boolean;
 	step?: number;
 } | {
 	label?: string;
 	type: 'boolean';
 	default: boolean | null;
+	description?: string;
 	hidden?: boolean;
 } | {
 	label?: string;
 	type: 'enum';
 	default: string | null;
+	required?: boolean;
 	hidden?: boolean;
 	enum: EnumItem[];
 } | {
 	label?: string;
 	type: 'radio';
 	default: unknown | null;
+	required?: boolean;
 	hidden?: boolean;
 	options: {
 		label: string;
 		value: unknown;
 	}[];
+} | {
+	label?: string;
+	type: 'range';
+	default: number | null;
+	description?: string;
+	required?: boolean;
+	step?: number;
+	min: number;
+	max: number;
+	textConverter?: (value: number) => string;
 } | {
 	label?: string;
 	type: 'object';
 	default: Record<string, unknown> | null;
-	hidden: true;
+	hidden: boolean;
 } | {
 	label?: string;
 	type: 'array';
 	default: unknown[] | null;
-	hidden: true;
+	hidden: boolean;
+} | {
+	type: 'button';
+	content?: string;
+	action: (ev: MouseEvent, v: any) => void;
 };
 
 export type Form = Record<string, FormItem>;
@@ -55,6 +81,7 @@ type GetItemType<Item extends FormItem> =
 	Item['type'] extends 'number' ? number :
 	Item['type'] extends 'boolean' ? boolean :
 	Item['type'] extends 'radio' ? unknown :
+	Item['type'] extends 'range' ? number :
 	Item['type'] extends 'enum' ? string :
 	Item['type'] extends 'array' ? unknown[] :
 	Item['type'] extends 'object' ? Record<string, unknown>
diff --git a/packages/frontend/src/scripts/format-time-string.ts b/packages/frontend/src/scripts/format-time-string.ts
index 918996dd10..35ad77d982 100644
--- a/packages/frontend/src/scripts/format-time-string.ts
+++ b/packages/frontend/src/scripts/format-time-string.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/gen-search-query.ts b/packages/frontend/src/scripts/gen-search-query.ts
index 54654980f2..60884d08d3 100644
--- a/packages/frontend/src/scripts/gen-search-query.ts
+++ b/packages/frontend/src/scripts/gen-search-query.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -18,7 +18,7 @@ export async function genSearchQuery(v: any, q: string) {
 					host = at;
 				}
 			} else {
-				const user = await v.os.api('users/show', Misskey.acct.parse(at)).catch(x => null);
+				const user = await v.api('users/show', Misskey.acct.parse(at)).catch(x => null);
 				if (user) {
 					userId = user.id;
 				} else {
diff --git a/packages/frontend/src/scripts/get-account-from-id.ts b/packages/frontend/src/scripts/get-account-from-id.ts
index 346d283572..40afa10f2d 100644
--- a/packages/frontend/src/scripts/get-account-from-id.ts
+++ b/packages/frontend/src/scripts/get-account-from-id.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/get-drive-file-menu.ts b/packages/frontend/src/scripts/get-drive-file-menu.ts
index d6a5b00c0b..a883404307 100644
--- a/packages/frontend/src/scripts/get-drive-file-menu.ts
+++ b/packages/frontend/src/scripts/get-drive-file-menu.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -8,6 +8,7 @@ import { defineAsyncComponent } from 'vue';
 import { i18n } from '@/i18n.js';
 import copyToClipboard from '@/scripts/copy-to-clipboard.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { MenuItem } from '@/types/menu.js';
 import { defaultStore } from '@/store.js';
 
@@ -18,7 +19,7 @@ function rename(file: Misskey.entities.DriveFile) {
 		default: file.name,
 	}).then(({ canceled, result: name }) => {
 		if (canceled) return;
-		os.api('drive/files/update', {
+		misskeyApi('drive/files/update', {
 			fileId: file.id,
 			name: name,
 		});
@@ -31,7 +32,7 @@ function describe(file: Misskey.entities.DriveFile) {
 		file: file,
 	}, {
 		done: caption => {
-			os.api('drive/files/update', {
+			misskeyApi('drive/files/update', {
 				fileId: file.id,
 				comment: caption.length === 0 ? null : caption,
 			});
@@ -40,7 +41,7 @@ function describe(file: Misskey.entities.DriveFile) {
 }
 
 function toggleSensitive(file: Misskey.entities.DriveFile) {
-	os.api('drive/files/update', {
+	misskeyApi('drive/files/update', {
 		fileId: file.id,
 		isSensitive: !file.isSensitive,
 	}).catch(err => {
@@ -65,11 +66,11 @@ function addApp() {
 async function deleteFile(file: Misskey.entities.DriveFile) {
 	const { canceled } = await os.confirm({
 		type: 'warning',
-		text: i18n.t('driveFileDeleteConfirm', { name: file.name }),
+		text: i18n.tsx.driveFileDeleteConfirm({ name: file.name }),
 	});
 
 	if (canceled) return;
-	os.api('drive/files/delete', {
+	misskeyApi('drive/files/delete', {
 		fileId: file.id,
 	});
 }
@@ -103,7 +104,7 @@ export function getDriveFileMenu(file: Misskey.entities.DriveFile, folder?: Miss
 		}),
 	}] : [], { type: 'divider' }, {
 		text: i18n.ts.createNoteFromTheFile,
-		icon: 'ph-pencil ph-bold ph-lg',
+		icon: 'ph-pencil-simple ph-bold ph-lg',
 		action: () => os.post({
 			initialFiles: [file],
 		}),
diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts
index a409f1b775..40359a88bb 100644
--- a/packages/frontend/src/scripts/get-note-menu.ts
+++ b/packages/frontend/src/scripts/get-note-menu.ts
@@ -1,15 +1,16 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { defineAsyncComponent, Ref } from 'vue';
+import { defineAsyncComponent, Ref, ShallowRef } from 'vue';
 import * as Misskey from 'misskey-js';
 import { claimAchievement } from './achievements.js';
 import { $i } from '@/account.js';
 import { i18n } from '@/i18n.js';
 import { instance } from '@/instance.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import copyToClipboard from '@/scripts/copy-to-clipboard.js';
 import { url } from '@/config.js';
 import { defaultStore, noteActions } from '@/store.js';
@@ -35,18 +36,18 @@ export async function getNoteClipMenu(props: {
 	const appearNote = isRenote ? props.note.renote as Misskey.entities.Note : props.note;
 
 	const clips = await clipsCache.fetch();
-	return [...clips.map(clip => ({
+	const menu: MenuItem[] = [...clips.map(clip => ({
 		text: clip.name,
 		action: () => {
 			claimAchievement('noteClipped1');
 			os.promiseDialog(
-				os.api('clips/add-note', { clipId: clip.id, noteId: appearNote.id }),
+				misskeyApi('clips/add-note', { clipId: clip.id, noteId: appearNote.id }),
 				null,
 				async (err) => {
 					if (err.id === '734806c4-542c-463a-9311-15c512803965') {
 						const confirm = await os.confirm({
 							type: 'warning',
-							text: i18n.t('confirmToUnclipAlreadyClippedNote', { name: clip.name }),
+							text: i18n.tsx.confirmToUnclipAlreadyClippedNote({ name: clip.name }),
 						});
 						if (!confirm.canceled) {
 							os.apiWithDialog('clips/remove-note', { clipId: clip.id, noteId: appearNote.id });
@@ -92,6 +93,8 @@ export async function getNoteClipMenu(props: {
 			os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: appearNote.id });
 		},
 	}];
+
+	return menu;
 }
 
 export function getAbuseNoteMenu(note: Misskey.entities.Note, text: string): MenuItem {
@@ -99,10 +102,13 @@ export function getAbuseNoteMenu(note: Misskey.entities.Note, text: string): Men
 		icon: 'ph-warning-circle ph-bold ph-lg',
 		text,
 		action: (): void => {
-			const u = note.url ?? note.uri ?? `${url}/notes/${note.id}`;
+			const localUrl = `${url}/notes/${note.id}`;
+			let noteInfo = '';
+			if (note.url ?? note.uri != null) noteInfo = `Note: ${note.url ?? note.uri}\n`;
+			noteInfo += `Local Note: ${localUrl}\n`;
 			os.popup(defineAsyncComponent(() => import('@/components/MkAbuseReportWindow.vue')), {
 				user: note.user,
-				initialComment: `Note: ${u}\n-----\n`,
+				initialComment: `${noteInfo}-----\n`,
 			}, {}, 'closed');
 		},
 	};
@@ -132,7 +138,6 @@ export function getCopyNoteOriginLinkMenu(note: misskey.entities.Note, text: str
 
 export function getNoteMenu(props: {
 	note: Misskey.entities.Note;
-	menuButton: Ref<HTMLElement>;
 	translation: Ref<Misskey.entities.NotesTranslateResponse | null>;
 	translating: Ref<boolean>;
 	isDeleted: Ref<boolean>;
@@ -156,7 +161,7 @@ export function getNoteMenu(props: {
 		}).then(({ canceled }) => {
 			if (canceled) return;
 
-			os.api('notes/delete', {
+			misskeyApi('notes/delete', {
 				noteId: appearNote.id,
 			});
 
@@ -173,7 +178,7 @@ export function getNoteMenu(props: {
 		}).then(({ canceled }) => {
 			if (canceled) return;
 
-			os.api('notes/delete', {
+			misskeyApi('notes/delete', {
 				noteId: appearNote.id,
 			});
 
@@ -252,7 +257,7 @@ export function getNoteMenu(props: {
 
 	function share(): void {
 		navigator.share({
-			title: i18n.t('noteOf', { user: appearNote.user.name }),
+			title: i18n.tsx.noteOf({ user: appearNote.user.name }),
 			text: appearNote.text,
 			url: `${url}/notes/${appearNote.id}`,
 		});
@@ -265,7 +270,7 @@ export function getNoteMenu(props: {
 	async function translate(): Promise<void> {
 		if (props.translation.value != null) return;
 		props.translating.value = true;
-		const res = await os.api('notes/translate', {
+		const res = await misskeyApi('notes/translate', {
 			noteId: appearNote.id,
 			targetLang: miLocalStorage.getItem('lang') ?? navigator.language,
 		});
@@ -275,7 +280,7 @@ export function getNoteMenu(props: {
 
 	let menu: MenuItem[];
 	if ($i) {
-		const statePromise = os.api('notes/state', {
+		const statePromise = misskeyApi('notes/state', {
 			noteId: appearNote.id,
 		});
 
@@ -296,7 +301,7 @@ export function getNoteMenu(props: {
 				text: i18n.ts.copyContent,
 				action: copyContent,
 			}, getCopyNoteLinkMenu(appearNote, i18n.ts.copyLink)
-			, (appearNote.url || appearNote.uri) ? 
+			, (appearNote.url || appearNote.uri) ?
 				getCopyNoteOriginLinkMenu(appearNote, 'Copy link (Origin)')
 			: undefined,
 			(appearNote.url || appearNote.uri) ? {
@@ -355,7 +360,7 @@ export function getNoteMenu(props: {
 				icon: 'ph-user ph-bold ph-lg',
 				text: i18n.ts.user,
 				children: async () => {
-					const user = appearNote.userId === $i?.id ? $i : await os.api('users/show', { userId: appearNote.userId });
+					const user = appearNote.userId === $i?.id ? $i : await misskeyApi('users/show', { userId: appearNote.userId });
 					const { menu, cleanup } = getUserMenu(user);
 					cleanups.push(cleanup);
 					return menu;
@@ -377,19 +382,55 @@ export function getNoteMenu(props: {
 			]
 			: []
 			),
+			...(appearNote.channel && (appearNote.channel.userId === $i.id || $i.isModerator || $i.isAdmin) ? [
+				{ type: 'divider' },
+				{
+					type: 'parent' as const,
+					icon: 'ti ti-device-tv',
+					text: i18n.ts.channel,
+					children: async () => {
+						const channelChildMenu = [] as MenuItem[];
+
+						const channel = await misskeyApi('channels/show', { channelId: appearNote.channel!.id });
+
+						if (channel.pinnedNoteIds.includes(appearNote.id)) {
+							channelChildMenu.push({
+								icon: 'ti ti-pinned-off',
+								text: i18n.ts.unpin,
+								action: () => os.apiWithDialog('channels/update', {
+									channelId: appearNote.channel!.id,
+									pinnedNoteIds: channel.pinnedNoteIds.filter(id => id !== appearNote.id),
+								}),
+							});
+						} else {
+							channelChildMenu.push({
+								icon: 'ti ti-pin',
+								text: i18n.ts.pin,
+								action: () => os.apiWithDialog('channels/update', {
+									channelId: appearNote.channel!.id,
+									pinnedNoteIds: [...channel.pinnedNoteIds, appearNote.id],
+								}),
+							});
+						}
+						return channelChildMenu;
+					},
+				},
+			]
+			: []
+			),
 			...(appearNote.userId === $i.id || $i.isModerator || $i.isAdmin ? [
 				{ type: 'divider' },
 				appearNote.userId === $i.id ? {
-					icon: 'ph-pencil ph-bold ph-lg',
+					icon: 'ph-pencil-simple ph-bold ph-lg',
 					text: i18n.ts.edit,
 					action: edit,
 				} : undefined,
 				{
-					icon: 'ph-pencil-line ph-bold ph-lg',
+					icon: 'ph-pencil-simple-line ph-bold ph-lg',
 					text: i18n.ts.deleteAndEdit,
 					danger: true,
 					action: delEdit,
-				}, 
+				},
 				{
 					icon: 'ph-trash ph-bold ph-lg',
 					text: i18n.ts.delete,
@@ -409,7 +450,7 @@ export function getNoteMenu(props: {
 			text: i18n.ts.copyContent,
 			action: copyContent,
 		}, getCopyNoteLinkMenu(appearNote, i18n.ts.copyLink)
-		, (appearNote.url || appearNote.uri) ? 
+		, (appearNote.url || appearNote.uri) ?
 			getCopyNoteOriginLinkMenu(appearNote, 'Copy link (Origin)')
 		: undefined,
 		(appearNote.url || appearNote.uri) ? {
@@ -468,7 +509,7 @@ function smallerVisibility(a: Visibility | string, b: Visibility | string): Visi
 
 export function getRenoteMenu(props: {
 	note: Misskey.entities.Note;
-	renoteButton: Ref<HTMLElement>;
+	renoteButton: ShallowRef<HTMLElement | undefined>;
 	mock?: boolean;
 }) {
 	const isRenote = (
@@ -488,7 +529,7 @@ export function getRenoteMenu(props: {
 			text: i18n.ts.inChannelRenote,
 			icon: 'ti ti-repeat',
 			action: () => {
-				const el = props.renoteButton.value as HTMLElement | null | undefined;
+				const el = props.renoteButton.value;
 				if (el) {
 					const rect = el.getBoundingClientRect();
 					const x = rect.left + (el.offsetWidth / 2);
@@ -497,7 +538,7 @@ export function getRenoteMenu(props: {
 				}
 
 				if (!props.mock) {
-					os.api('notes/create', {
+					misskeyApi('notes/create', {
 						renoteId: appearNote.id,
 						channelId: appearNote.channelId,
 					}).then(() => {
@@ -524,7 +565,7 @@ export function getRenoteMenu(props: {
 			text: i18n.ts.renote,
 			icon: 'ti ti-repeat',
 			action: () => {
-				const el = props.renoteButton.value as HTMLElement | null | undefined;
+				const el = props.renoteButton.value;
 				if (el) {
 					const rect = el.getBoundingClientRect();
 					const x = rect.left + (el.offsetWidth / 2);
@@ -542,7 +583,7 @@ export function getRenoteMenu(props: {
 				}
 
 				if (!props.mock) {
-					os.api('notes/create', {
+					misskeyApi('notes/create', {
 						localOnly,
 						visibility,
 						renoteId: appearNote.id,
@@ -564,7 +605,7 @@ export function getRenoteMenu(props: {
 
 	const renoteItems = [
 		...normalRenoteItems,
-		...(channelRenoteItems.length > 0 && normalRenoteItems.length > 0) ? [{ type: 'divider' }] : [],
+		...(channelRenoteItems.length > 0 && normalRenoteItems.length > 0) ? [{ type: 'divider' }] as MenuItem[] : [],
 		...channelRenoteItems,
 	];
 
diff --git a/packages/frontend/src/scripts/get-note-summary.ts b/packages/frontend/src/scripts/get-note-summary.ts
index 1fd9f04d46..6fd9947ac1 100644
--- a/packages/frontend/src/scripts/get-note-summary.ts
+++ b/packages/frontend/src/scripts/get-note-summary.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -10,7 +10,11 @@ import { i18n } from '@/i18n.js';
  * 投稿を表す文字列を取得します。
  * @param {*} note (packされた)投稿
  */
-export const getNoteSummary = (note: Misskey.entities.Note): string => {
+export const getNoteSummary = (note?: Misskey.entities.Note | null): string => {
+	if (note == null) {
+		return '';
+	}
+
 	if (note.deletedAt) {
 		return `(${i18n.ts.deletedNote})`;
 	}
@@ -30,7 +34,7 @@ export const getNoteSummary = (note: Misskey.entities.Note): string => {
 
 	// ファイルが添付されているとき
 	if ((note.files || []).length !== 0) {
-		summary += ` (${i18n.t('withNFiles', { n: note.files.length })})`;
+		summary += ` (${i18n.tsx.withNFiles({ n: note.files.length })})`;
 	}
 
 	// 投票が添付されているとき
diff --git a/packages/frontend/src/scripts/get-note-versions-menu.ts b/packages/frontend/src/scripts/get-note-versions-menu.ts
index 46e3bab3a7..84292f1277 100644
--- a/packages/frontend/src/scripts/get-note-versions-menu.ts
+++ b/packages/frontend/src/scripts/get-note-versions-menu.ts
@@ -2,6 +2,7 @@ import { Ref, defineAsyncComponent } from 'vue';
 import * as Misskey from 'misskey-js';
 import { i18n } from '@/i18n.js';
 import * as os from '@/os.js';
+import { misskeyApi } from './misskey-api.js';
 import { MenuItem } from '@/types/menu.js';
 import { dateTimeFormat } from './intl-const.js';
 
@@ -30,7 +31,7 @@ export async function getNoteVersionsMenu(props: {
 	}
 
 	const menu: MenuItem[] = [];
-	const statePromise = os.api('notes/versions', {
+	const statePromise = misskeyApi('notes/versions', {
 		noteId: appearNote.id,
 	});
 
@@ -39,9 +40,9 @@ export async function getNoteVersionsMenu(props: {
 			const _time = edit.oldDate == null ? NaN :
 				typeof edit.oldDate === 'number' ? edit.oldDate :
 				(edit.oldDate instanceof Date ? edit.oldDate : new Date(edit.oldDate)).getTime();
-			
+
 			menu.push({
-				icon: 'ph-pencil ph-bold ph-lg',
+				icon: 'ph-pencil-simple ph-bold ph-lg',
 				text: _time ? dateTimeFormat.format(_time) : dateTimeFormat.format(new Date(edit.updatedAt)),
 				action: () => openVersion(edit),
 			});
diff --git a/packages/frontend/src/scripts/get-user-menu.ts b/packages/frontend/src/scripts/get-user-menu.ts
index 67bc781aef..61f9a453dc 100644
--- a/packages/frontend/src/scripts/get-user-menu.ts
+++ b/packages/frontend/src/scripts/get-user-menu.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -10,13 +10,14 @@ import { i18n } from '@/i18n.js';
 import copyToClipboard from '@/scripts/copy-to-clipboard.js';
 import { host, url } from '@/config.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { defaultStore, userActions } from '@/store.js';
 import { $i, iAmModerator } from '@/account.js';
-import { mainRouter } from '@/router.js';
-import { Router } from '@/nirax.js';
+import { IRouter } from '@/nirax.js';
 import { antennasCache, rolesCache, userListsCache } from '@/cache.js';
+import { mainRouter } from '@/router/main.js';
 
-export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router = mainRouter) {
+export function getUserMenu(user: Misskey.entities.UserDetailed, router: IRouter = mainRouter) {
 	const meId = $i ? $i.id : null;
 
 	const cleanups = [] as (() => void)[];
@@ -131,7 +132,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
 	}
 
 	async function editMemo(): Promise<void> {
-		const userDetailed = await os.api('users/show', {
+		const userDetailed = await misskeyApi('users/show', {
 			userId: user.id,
 		});
 		const { canceled, result } = await os.form(i18n.ts.editMemo, {
@@ -169,20 +170,21 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
 		action: () => {
 			copyToClipboard(`${user.host ?? host}/@${user.username}.atom`);
 		},
-	}, {
+	}, ...(user.host != null && user.url != null ? [{
+		icon: 'ph-share ph-bold ph-lg',
+		text: i18n.ts.showOnRemote,
+		action: () => {
+			if (user.url == null) return;
+			window.open(user.url, '_blank', 'noopener');
+		},
+	}] : []), {
 		icon: 'ph-share-network ph-bold ph-lg',
 		text: i18n.ts.copyProfileUrl,
 		action: () => {
 			const canonical = user.host === null ? `@${user.username}` : `@${user.username}@${toUnicode(user.host)}`;
 			copyToClipboard(`${url}/${canonical}`);
 		},
-	}, ...(user.host ? [{
-		icon: 'ph-share ph-bold ph-lg',
-		text: i18n.ts.openRemoteProfile,
-		action: () => {
-			open(`${user.uri}`, '_blank');
-		},
-	}] : []), {
+	}, {
 		icon: 'ph-envelope ph-bold ph-lg',
 		text: i18n.ts.sendMessage,
 		action: () => {
@@ -190,7 +192,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
 			os.post({ specified: user, initialText: `${canonical} ` });
 		},
 	}, { type: 'divider' }, {
-		icon: 'ph-pencil ph-bold ph-lg',
+		icon: 'ph-pencil-simple ph-bold ph-lg',
 		text: i18n.ts.editMemo,
 		action: () => {
 			editMemo();
@@ -362,7 +364,7 @@ export function getUserMenu(user: Misskey.entities.UserDetailed, router: Router
 
 	if ($i && meId === user.id) {
 		menu = menu.concat([{ type: 'divider' }, {
-			icon: 'ph-pencil ph-bold ph-lg',
+			icon: 'ph-pencil-simple ph-bold ph-lg',
 			text: i18n.ts.editProfile,
 			action: () => {
 				router.push('/settings/profile');
diff --git a/packages/frontend/src/scripts/get-user-name.ts b/packages/frontend/src/scripts/get-user-name.ts
index 3ae80d7fc3..56e91abba0 100644
--- a/packages/frontend/src/scripts/get-user-name.ts
+++ b/packages/frontend/src/scripts/get-user-name.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/hotkey.ts b/packages/frontend/src/scripts/hotkey.ts
index 48c80c066b..0600bff893 100644
--- a/packages/frontend/src/scripts/hotkey.ts
+++ b/packages/frontend/src/scripts/hotkey.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/i18n.ts b/packages/frontend/src/scripts/i18n.ts
index 8e5f17f38a..c2f44a33cc 100644
--- a/packages/frontend/src/scripts/i18n.ts
+++ b/packages/frontend/src/scripts/i18n.ts
@@ -1,34 +1,294 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
+import type { ILocale, ParameterizedString } from '../../../../locales/index.js';
 
-export class I18n<T extends Record<string, any>> {
-	public ts: T;
+type FlattenKeys<T extends ILocale, TPrediction> = keyof {
+	[K in keyof T as T[K] extends ILocale
+		? FlattenKeys<T[K], TPrediction> extends infer C extends string
+			? `${K & string}.${C}`
+			: never
+		: T[K] extends TPrediction
+			? K
+			: never]: T[K];
+};
 
-	constructor(locale: T) {
-		this.ts = locale;
+type ParametersOf<T extends ILocale, TKey extends FlattenKeys<T, ParameterizedString>> = TKey extends `${infer K}.${infer C}`
+	// @ts-expect-error -- C は明らかに FlattenKeys<T[K], ParameterizedString> になるが、型システムはここでは TKey がドット区切りであることのコンテキストを持たないので、型システムに合法にて示すことはできない。
+	? ParametersOf<T[K], C>
+	: TKey extends keyof T
+		? T[TKey] extends ParameterizedString<infer P>
+			? P
+			: never
+		: never;
 
+type Tsx<T extends ILocale> = {
+	readonly [K in keyof T as T[K] extends string ? never : K]: T[K] extends ParameterizedString<infer P>
+		? (arg: { readonly [_ in P]: string | number }) => string
+		// @ts-expect-error -- 証明省略
+		: Tsx<T[K]>;
+};
+
+export class I18n<T extends ILocale> {
+	private tsxCache?: Tsx<T>;
+
+	constructor(public locale: T) {
 		//#region BIND
 		this.t = this.t.bind(this);
 		//#endregion
 	}
 
-	// string にしているのは、ドット区切りでのパス指定を許可するため
-	// なるべくこのメソッド使うよりもlocale直接参照の方がvueのキャッシュ効いてパフォーマンスが良いかも
-	public t(key: string, args?: Record<string, string | number>): string {
-		try {
-			let str = key.split('.').reduce((o, i) => o[i], this.ts) as unknown as string;
+	public get ts(): T {
+		if (_DEV_) {
+			class Handler<TTarget extends ILocale> implements ProxyHandler<TTarget> {
+				get(target: TTarget, p: string | symbol): unknown {
+					const value = target[p as keyof TTarget];
 
-			if (args) {
-				for (const [k, v] of Object.entries(args)) {
-					str = str.replace(`{${k}}`, v.toString());
+					if (typeof value === 'object') {
+						return new Proxy(value, new Handler<TTarget[keyof TTarget] & ILocale>());
+					}
+
+					if (typeof value === 'string') {
+						const parameters = Array.from(value.matchAll(/\{(\w+)\}/g), ([, parameter]) => parameter);
+
+						if (parameters.length) {
+							console.error(`Missing locale parameters: ${parameters.join(', ')} at ${String(p)}`);
+						}
+
+						return value;
+					}
+
+					console.error(`Unexpected locale key: ${String(p)}`);
+
+					return p;
 				}
 			}
-			return str;
-		} catch (err) {
-			console.warn(`missing localization '${key}'`);
-			return key;
+
+			return new Proxy(this.locale, new Handler());
 		}
+
+		return this.locale;
+	}
+
+	public get tsx(): Tsx<T> {
+		if (_DEV_) {
+			if (this.tsxCache) {
+				return this.tsxCache;
+			}
+
+			class Handler<TTarget extends ILocale> implements ProxyHandler<TTarget> {
+				get(target: TTarget, p: string | symbol): unknown {
+					const value = target[p as keyof TTarget];
+
+					if (typeof value === 'object') {
+						return new Proxy(value, new Handler<TTarget[keyof TTarget] & ILocale>());
+					}
+
+					if (typeof value === 'string') {
+						const quasis: string[] = [];
+						const expressions: string[] = [];
+						let cursor = 0;
+
+						while (~cursor) {
+							const start = value.indexOf('{', cursor);
+
+							if (!~start) {
+								quasis.push(value.slice(cursor));
+								break;
+							}
+
+							quasis.push(value.slice(cursor, start));
+
+							const end = value.indexOf('}', start);
+
+							expressions.push(value.slice(start + 1, end));
+
+							cursor = end + 1;
+						}
+
+						if (!expressions.length) {
+							console.error(`Unexpected locale key: ${String(p)}`);
+
+							return () => value;
+						}
+
+						return (arg) => {
+							let str = quasis[0];
+
+							for (let i = 0; i < expressions.length; i++) {
+								if (!Object.hasOwn(arg, expressions[i])) {
+									console.error(`Missing locale parameters: ${expressions[i]} at ${String(p)}`);
+								}
+
+								str += arg[expressions[i]] + quasis[i + 1];
+							}
+
+							return str;
+						};
+					}
+
+					console.error(`Unexpected locale key: ${String(p)}`);
+
+					return p;
+				}
+			}
+
+			return this.tsxCache = new Proxy(this.locale, new Handler()) as unknown as Tsx<T>;
+		}
+
+		// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+		if (this.tsxCache) {
+			return this.tsxCache;
+		}
+
+		function build(target: ILocale): Tsx<T> {
+			const result = {} as Tsx<T>;
+
+			for (const k in target) {
+				if (!Object.hasOwn(target, k)) {
+					continue;
+				}
+
+				const value = target[k as keyof typeof target];
+
+				if (typeof value === 'object') {
+					result[k] = build(value as ILocale);
+				} else if (typeof value === 'string') {
+					const quasis: string[] = [];
+					const expressions: string[] = [];
+					let cursor = 0;
+
+					while (~cursor) {
+						const start = value.indexOf('{', cursor);
+
+						if (!~start) {
+							quasis.push(value.slice(cursor));
+							break;
+						}
+
+						quasis.push(value.slice(cursor, start));
+
+						const end = value.indexOf('}', start);
+
+						expressions.push(value.slice(start + 1, end));
+
+						cursor = end + 1;
+					}
+
+					if (!expressions.length) {
+						continue;
+					}
+
+					result[k] = (arg) => {
+						let str = quasis[0];
+
+						for (let i = 0; i < expressions.length; i++) {
+							str += arg[expressions[i]] + quasis[i + 1];
+						}
+
+						return str;
+					};
+				}
+			}
+			return result;
+		}
+
+		return this.tsxCache = build(this.locale);
+	}
+
+	/**
+	 * @deprecated なるべくこのメソッド使うよりも ts 直接参照の方が vue のキャッシュ効いてパフォーマンスが良いかも
+	 */
+	public t<TKey extends FlattenKeys<T, string>>(key: TKey): string;
+	/**
+	 * @deprecated なるべくこのメソッド使うよりも tsx 直接参照の方が vue のキャッシュ効いてパフォーマンスが良いかも
+	 */
+	public t<TKey extends FlattenKeys<T, ParameterizedString>>(key: TKey, args: { readonly [_ in ParametersOf<T, TKey>]: string | number }): string;
+	public t(key: string, args?: { readonly [_: string]: string | number }) {
+		let str: string | ParameterizedString | ILocale = this.locale;
+
+		for (const k of key.split('.')) {
+			str = str[k];
+
+			if (_DEV_) {
+				if (typeof str === 'undefined') {
+					console.error(`Unexpected locale key: ${key}`);
+					return key;
+				}
+			}
+		}
+
+		if (args) {
+			if (_DEV_) {
+				const missing = Array.from((str as string).matchAll(/\{(\w+)\}/g), ([, parameter]) => parameter).filter(parameter => !Object.hasOwn(args, parameter));
+
+				if (missing.length) {
+					console.error(`Missing locale parameters: ${missing.join(', ')} at ${key}`);
+				}
+			}
+
+			for (const [k, v] of Object.entries(args)) {
+				const search = `{${k}}`;
+
+				if (_DEV_) {
+					if (!(str as string).includes(search)) {
+						console.error(`Unexpected locale parameter: ${k} at ${key}`);
+					}
+				}
+
+				str = (str as string).replace(search, v.toString());
+			}
+		}
+
+		return str;
 	}
 }
+
+if (import.meta.vitest) {
+	const { describe, expect, it } = import.meta.vitest;
+
+	describe('i18n', () => {
+		it('t', () => {
+			const i18n = new I18n({
+				foo: 'foo',
+				bar: {
+					baz: 'baz',
+					qux: 'qux {0}' as unknown as ParameterizedString<'0'>,
+					quux: 'quux {0} {1}' as unknown as ParameterizedString<'0' | '1'>,
+				},
+			});
+
+			expect(i18n.t('foo')).toBe('foo');
+			expect(i18n.t('bar.baz')).toBe('baz');
+			expect(i18n.tsx.bar.qux({ 0: 'hoge' })).toBe('qux hoge');
+			expect(i18n.tsx.bar.quux({ 0: 'hoge', 1: 'fuga' })).toBe('quux hoge fuga');
+		});
+		it('ts', () => {
+			const i18n = new I18n({
+				foo: 'foo',
+				bar: {
+					baz: 'baz',
+					qux: 'qux {0}' as unknown as ParameterizedString<'0'>,
+					quux: 'quux {0} {1}' as unknown as ParameterizedString<'0' | '1'>,
+				},
+			});
+
+			expect(i18n.ts.foo).toBe('foo');
+			expect(i18n.ts.bar.baz).toBe('baz');
+		});
+		it('tsx', () => {
+			const i18n = new I18n({
+				foo: 'foo',
+				bar: {
+					baz: 'baz',
+					qux: 'qux {0}' as unknown as ParameterizedString<'0'>,
+					quux: 'quux {0} {1}' as unknown as ParameterizedString<'0' | '1'>,
+				},
+			});
+
+			expect(i18n.tsx.bar.qux({ 0: 'hoge' })).toBe('qux hoge');
+			expect(i18n.tsx.bar.quux({ 0: 'hoge', 1: 'fuga' })).toBe('quux hoge fuga');
+		});
+	});
+}
diff --git a/packages/frontend/src/scripts/idb-proxy.ts b/packages/frontend/src/scripts/idb-proxy.ts
index a20cfcb1d0..1ca0990ba9 100644
--- a/packages/frontend/src/scripts/idb-proxy.ts
+++ b/packages/frontend/src/scripts/idb-proxy.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/idle-render.ts b/packages/frontend/src/scripts/idle-render.ts
index ac1be50c73..6adfedcb9f 100644
--- a/packages/frontend/src/scripts/idle-render.ts
+++ b/packages/frontend/src/scripts/idle-render.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/init-chart.ts b/packages/frontend/src/scripts/init-chart.ts
index ebf27667d7..2465a14703 100644
--- a/packages/frontend/src/scripts/init-chart.ts
+++ b/packages/frontend/src/scripts/init-chart.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/initialize-sw.ts b/packages/frontend/src/scripts/initialize-sw.ts
index 007fc0f2f7..1517e4e1e8 100644
--- a/packages/frontend/src/scripts/initialize-sw.ts
+++ b/packages/frontend/src/scripts/initialize-sw.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/install-plugin.ts b/packages/frontend/src/scripts/install-plugin.ts
index 1310a0dc73..15b0cedc79 100644
--- a/packages/frontend/src/scripts/install-plugin.ts
+++ b/packages/frontend/src/scripts/install-plugin.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -10,6 +10,7 @@ import { Interpreter, Parser, utils } from '@syuilo/aiscript';
 import type { Plugin } from '@/store.js';
 import { ColdDeviceStorage } from '@/store.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 
 export type AiScriptPluginMeta = {
@@ -63,7 +64,11 @@ export async function parsePluginMeta(code: string): Promise<AiScriptPluginMeta>
 	try {
 		ast = parser.parse(code);
 	} catch (err) {
-		throw new Error('Aiscript syntax error');
+		if (err instanceof Error) {
+			throw new Error(`Aiscript syntax error\n${(err as Error).message}`);
+		} else {
+			throw new Error('Aiscript syntax error');
+		}
 	}
 
 	const meta = Interpreter.collectMetadata(ast);
@@ -110,7 +115,7 @@ export async function installPlugin(code: string, meta?: AiScriptPluginMeta) {
 		}, {
 			done: async result => {
 				const { name, permissions } = result;
-				const { token } = await os.api('miauth/gen-token', {
+				const { token } = await misskeyApi('miauth/gen-token', {
 					session: null,
 					name: name,
 					permission: permissions,
diff --git a/packages/frontend/src/scripts/install-theme.ts b/packages/frontend/src/scripts/install-theme.ts
index 394b642bf4..866f1225bf 100644
--- a/packages/frontend/src/scripts/install-theme.ts
+++ b/packages/frontend/src/scripts/install-theme.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/intl-const.ts b/packages/frontend/src/scripts/intl-const.ts
index ea16c9c2ae..aaa4f0a86e 100644
--- a/packages/frontend/src/scripts/intl-const.ts
+++ b/packages/frontend/src/scripts/intl-const.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -33,6 +33,10 @@ try {
 }
 export const dateTimeFormat = _dateTimeFormat;
 
+export const timeZone = dateTimeFormat.resolvedOptions().timeZone;
+
+export const hemisphere = /^(australia|pacific|antarctica|indian)\//i.test(timeZone) ? 'S' : 'N';
+
 let _numberFormat: Intl.NumberFormat;
 try {
 	_numberFormat = new Intl.NumberFormat(versatileLang);
diff --git a/packages/frontend/src/scripts/is-device-darkmode.ts b/packages/frontend/src/scripts/is-device-darkmode.ts
index badc295726..4f487c7cb9 100644
--- a/packages/frontend/src/scripts/is-device-darkmode.ts
+++ b/packages/frontend/src/scripts/is-device-darkmode.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/isFfVisibleForMe.ts b/packages/frontend/src/scripts/isFfVisibleForMe.ts
index dc0e90d20a..406404c462 100644
--- a/packages/frontend/src/scripts/isFfVisibleForMe.ts
+++ b/packages/frontend/src/scripts/isFfVisibleForMe.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/keycode.ts b/packages/frontend/src/scripts/keycode.ts
index 57bc4d19ba..bc1f485f5e 100644
--- a/packages/frontend/src/scripts/keycode.ts
+++ b/packages/frontend/src/scripts/keycode.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/langmap.ts b/packages/frontend/src/scripts/langmap.ts
index 3912d58d82..b32de15963 100644
--- a/packages/frontend/src/scripts/langmap.ts
+++ b/packages/frontend/src/scripts/langmap.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/login-id.ts b/packages/frontend/src/scripts/login-id.ts
index fe0e17e66e..b52735caa0 100644
--- a/packages/frontend/src/scripts/login-id.ts
+++ b/packages/frontend/src/scripts/login-id.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/lookup-user.ts b/packages/frontend/src/scripts/lookup-user.ts
index a35fe898e4..efc9132e75 100644
--- a/packages/frontend/src/scripts/lookup-user.ts
+++ b/packages/frontend/src/scripts/lookup-user.ts
@@ -1,11 +1,12 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import * as Misskey from 'misskey-js';
 import { i18n } from '@/i18n.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 
 export async function lookupUser() {
 	const { canceled, result } = await os.inputText({
@@ -17,8 +18,8 @@ export async function lookupUser() {
 		os.pageWindow(`/admin/user/${user.id}`);
 	};
 
-	const usernamePromise = os.api('users/show', Misskey.acct.parse(result));
-	const idPromise = os.api('users/show', { userId: result });
+	const usernamePromise = misskeyApi('users/show', Misskey.acct.parse(result));
+	const idPromise = misskeyApi('users/show', { userId: result });
 	let _notFound = false;
 	const notFound = () => {
 		if (_notFound) {
diff --git a/packages/frontend/src/scripts/lookup.ts b/packages/frontend/src/scripts/lookup.ts
index 979f40f038..7f020b15cc 100644
--- a/packages/frontend/src/scripts/lookup.ts
+++ b/packages/frontend/src/scripts/lookup.ts
@@ -1,12 +1,13 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
-import { mainRouter } from '@/router.js';
 import { Router } from '@/nirax.js';
+import { mainRouter } from '@/router/main.js';
 
 export async function lookup(router?: Router) {
 	const _router = router ?? mainRouter;
@@ -28,7 +29,7 @@ export async function lookup(router?: Router) {
 	}
 
 	if (query.startsWith('https://')) {
-		const promise = os.api('ap/show', {
+		const promise = misskeyApi('ap/show', {
 			uri: query,
 		});
 
diff --git a/packages/frontend/src/scripts/media-proxy.ts b/packages/frontend/src/scripts/media-proxy.ts
index 559e61211d..099a22163a 100644
--- a/packages/frontend/src/scripts/media-proxy.ts
+++ b/packages/frontend/src/scripts/media-proxy.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/merge.ts b/packages/frontend/src/scripts/merge.ts
new file mode 100644
index 0000000000..4e39a0fa06
--- /dev/null
+++ b/packages/frontend/src/scripts/merge.ts
@@ -0,0 +1,35 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { deepClone } from './clone.js';
+import type { Cloneable } from './clone.js';
+
+type DeepPartial<T> = {
+	[P in keyof T]?: T[P] extends Record<string | number | symbol, unknown> ? DeepPartial<T[P]> : T[P];
+};
+
+function isPureObject(value: unknown): value is Record<string | number | symbol, unknown> {
+	return typeof value === 'object' && value !== null && !Array.isArray(value);
+}
+
+/**
+ * valueにないキーをdefからもらう(再帰的)\
+ * nullはそのまま、undefinedはdefの値
+ **/
+export function deepMerge<X extends Record<string | number | symbol, unknown>>(value: DeepPartial<X>, def: X): X {
+	if (isPureObject(value) && isPureObject(def)) {
+		const result = deepClone(value as Cloneable) as X;
+		for (const [k, v] of Object.entries(def) as [keyof X, X[keyof X]][]) {
+			if (!Object.prototype.hasOwnProperty.call(value, k) || value[k] === undefined) {
+				result[k] = v;
+			} else if (isPureObject(v) && isPureObject(result[k])) {
+				const child = deepClone(result[k] as Cloneable) as DeepPartial<X[keyof X] & Record<string | number | symbol, unknown>>;
+				result[k] = deepMerge<typeof v>(child, v);
+			}
+		}
+		return result;
+	}
+	throw new Error('deepMerge: value and def must be pure objects');
+}
diff --git a/packages/frontend/src/scripts/mfm-function-picker.ts b/packages/frontend/src/scripts/mfm-function-picker.ts
index 6e25cc856c..36de146c27 100644
--- a/packages/frontend/src/scripts/mfm-function-picker.ts
+++ b/packages/frontend/src/scripts/mfm-function-picker.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/api.ts b/packages/frontend/src/scripts/misskey-api.ts
similarity index 68%
rename from packages/frontend/src/scripts/api.ts
rename to packages/frontend/src/scripts/misskey-api.ts
index 8f3a163938..49fb6f9e59 100644
--- a/packages/frontend/src/scripts/api.ts
+++ b/packages/frontend/src/scripts/misskey-api.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -10,12 +10,17 @@ import { $i } from '@/account.js';
 export const pendingApiRequestsCount = ref(0);
 
 // Implements Misskey.api.ApiClient.request
-export function api<E extends keyof Misskey.Endpoints, P extends Misskey.Endpoints[E]['req']>(
+export function misskeyApi<
+	ResT = void,
+	E extends keyof Misskey.Endpoints = keyof Misskey.Endpoints,
+	P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req'],
+	_ResT = ResT extends void ? Misskey.api.SwitchCaseResponseType<E, P> : ResT,
+>(
 	endpoint: E,
 	data: P = {} as any,
 	token?: string | null | undefined,
 	signal?: AbortSignal,
-): Promise<Misskey.api.SwitchCaseResponseType<E, P>> {
+): Promise<_ResT> {
 	if (endpoint.includes('://')) throw new Error('invalid endpoint');
 	pendingApiRequestsCount.value++;
 
@@ -23,7 +28,7 @@ export function api<E extends keyof Misskey.Endpoints, P extends Misskey.Endpoin
 		pendingApiRequestsCount.value--;
 	};
 
-	const promise = new Promise<Misskey.Endpoints[E]['res'] | void>((resolve, reject) => {
+	const promise = new Promise<_ResT>((resolve, reject) => {
 		// Append a credential
 		if ($i) (data as any).i = $i.token;
 		if (token !== undefined) (data as any).i = token;
@@ -44,7 +49,7 @@ export function api<E extends keyof Misskey.Endpoints, P extends Misskey.Endpoin
 			if (res.status === 200) {
 				resolve(body);
 			} else if (res.status === 204) {
-				resolve();
+				resolve(undefined as _ResT); // void -> undefined
 			} else {
 				reject(body.error);
 			}
@@ -57,10 +62,15 @@ export function api<E extends keyof Misskey.Endpoints, P extends Misskey.Endpoin
 }
 
 // Implements Misskey.api.ApiClient.request
-export function apiGet<E extends keyof Misskey.Endpoints, P extends Misskey.Endpoints[E]['req']>(
+export function misskeyApiGet<
+	ResT = void,
+	E extends keyof Misskey.Endpoints = keyof Misskey.Endpoints,
+	P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req'],
+	_ResT = ResT extends void ? Misskey.api.SwitchCaseResponseType<E, P> : ResT,
+>(
 	endpoint: E,
 	data: P = {} as any,
-): Promise<Misskey.api.SwitchCaseResponseType<E, P>> {
+): Promise<_ResT> {
 	pendingApiRequestsCount.value++;
 
 	const onFinally = () => {
@@ -69,7 +79,7 @@ export function apiGet<E extends keyof Misskey.Endpoints, P extends Misskey.Endp
 
 	const query = new URLSearchParams(data as any);
 
-	const promise = new Promise<Misskey.Endpoints[E]['res'] | void>((resolve, reject) => {
+	const promise = new Promise<_ResT>((resolve, reject) => {
 		// Send request
 		window.fetch(`${apiUrl}/${endpoint}?${query}`, {
 			method: 'GET',
@@ -81,7 +91,7 @@ export function apiGet<E extends keyof Misskey.Endpoints, P extends Misskey.Endp
 			if (res.status === 200) {
 				resolve(body);
 			} else if (res.status === 204) {
-				resolve();
+				resolve(undefined as _ResT); // void -> undefined
 			} else {
 				reject(body.error);
 			}
diff --git a/packages/frontend/src/scripts/navigator.ts b/packages/frontend/src/scripts/navigator.ts
index b13186a10e..ffc0a457f4 100644
--- a/packages/frontend/src/scripts/navigator.ts
+++ b/packages/frontend/src/scripts/navigator.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/nyaize.ts b/packages/frontend/src/scripts/nyaize.ts
index 62833b4de3..58ed88fed1 100644
--- a/packages/frontend/src/scripts/nyaize.ts
+++ b/packages/frontend/src/scripts/nyaize.ts
@@ -1,23 +1,28 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-const enRegex1 = /(?<=n)a/gi;
-const enRegex2 = /(?<=morn)ing/gi;
-const enRegex3 = /(?<=every)one/gi;
 const koRegex1 = /[나-낳]/g;
 const koRegex2 = /(다$)|(다(?=\.))|(다(?= ))|(다(?=!))|(다(?=\?))/gm;
 const koRegex3 = /(야(?=\?))|(야$)|(야(?= ))/gm;
 
+function ifAfter(prefix, fn) {
+	const preLen = prefix.length;
+	const regex = new RegExp(prefix,'i');
+	return (x,pos,string) => {
+		return pos > 0 && string.substring(pos-preLen,pos).match(regex) ? fn(x) : x;
+	};
+}
+
 export function nyaize(text: string): string {
 	return text
 		// ja-JP
 		.replaceAll('な', 'にゃ').replaceAll('ナ', 'ニャ').replaceAll('ナ', 'ニャ')
 		// en-US
-		.replace(enRegex1, x => x === 'A' ? 'YA' : 'ya')
-		.replace(enRegex2, x => x === 'ING' ? 'YAN' : 'yan')
-		.replace(enRegex3, x => x === 'ONE' ? 'NYAN' : 'nyan')
+		.replace(/a/gi, ifAfter('n', x => x === 'A' ? 'YA' : 'ya'))
+		.replace(/ing/gi, ifAfter('morn', x => x === 'ING' ? 'YAN' : 'yan'))
+		.replace(/one/gi, ifAfter('every', x => x === 'ONE' ? 'NYAN' : 'nyan'))
 		// ko-KR
 		.replace(koRegex1, match => String.fromCharCode(
 			match.charCodeAt(0)! + '냐'.charCodeAt(0) - '나'.charCodeAt(0),
diff --git a/packages/frontend/src/scripts/page-metadata.ts b/packages/frontend/src/scripts/page-metadata.ts
index 369e46aae1..0e3b093ecf 100644
--- a/packages/frontend/src/scripts/page-metadata.ts
+++ b/packages/frontend/src/scripts/page-metadata.ts
@@ -1,13 +1,10 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import * as Misskey from 'misskey-js';
-import { ComputedRef, inject, isRef, onActivated, onMounted, provide, ref, Ref } from 'vue';
-
-export const setPageMetadata = Symbol('setPageMetadata');
-export const pageMetadataProvider = Symbol('pageMetadataProvider');
+import { MaybeRefOrGetter, Ref, inject, isRef, onActivated, onBeforeUnmount, provide, ref, toValue, watch } from 'vue';
 
 export type PageMetadata = {
 	title: string;
@@ -18,29 +15,56 @@ export type PageMetadata = {
 	needWideArea?: boolean;
 };
 
-export function definePageMetadata(metadata: PageMetadata | null | Ref<PageMetadata | null> | ComputedRef<PageMetadata | null>): void {
-	const _metadata = isRef(metadata) ? metadata : ref(metadata);
+type PageMetadataGetter = () => PageMetadata;
+type PageMetadataReceiver = (getter: PageMetadataGetter) => void;
 
-	provide(pageMetadataProvider, _metadata);
+const RECEIVER_KEY = Symbol('ReceiverKey');
+const setReceiver = (v: PageMetadataReceiver): void => {
+	provide<PageMetadataReceiver>(RECEIVER_KEY, v);
+};
+const getReceiver = (): PageMetadataReceiver | undefined => {
+	return inject<PageMetadataReceiver>(RECEIVER_KEY);
+};
 
-	const set = inject(setPageMetadata) as any;
-	if (set) {
-		set(_metadata);
+const METADATA_KEY = Symbol('MetadataKey');
+const setMetadata = (v: Ref<PageMetadata | null>): void => {
+	provide<Ref<PageMetadata | null>>(METADATA_KEY, v);
+};
+const getMetadata = (): Ref<PageMetadata | null> | undefined => {
+	return inject<Ref<PageMetadata | null>>(METADATA_KEY);
+};
 
-		onMounted(() => {
-			set(_metadata);
-		});
+export const definePageMetadata = (maybeRefOrGetterMetadata: MaybeRefOrGetter<PageMetadata>): void => {
+	const metadataRef = ref(toValue(maybeRefOrGetterMetadata));
+	const metadataGetter = () => metadataRef.value;
+	const receiver = getReceiver();
 
-		onActivated(() => {
-			set(_metadata);
-		});
-	}
-}
+	// setup handler
+	receiver?.(metadataGetter);
 
-export function provideMetadataReceiver(callback: (info: ComputedRef<PageMetadata>) => void): void {
-	provide(setPageMetadata, callback);
-}
+	// update handler
+	onBeforeUnmount(watch(
+		() => toValue(maybeRefOrGetterMetadata),
+		(metadata) => {
+			metadataRef.value = metadata;
+			receiver?.(metadataGetter);
+		},
+		{ deep: true },
+	));
+	onActivated(() => {
+		receiver?.(metadataGetter);
+	});
+};
 
-export function injectPageMetadata(): PageMetadata | undefined {
-	return inject(pageMetadataProvider);
-}
+export const provideMetadataReceiver = (receiver: PageMetadataReceiver): void => {
+	setReceiver(receiver);
+};
+
+export const provideReactiveMetadata = (metadataRef: Ref<PageMetadata | null>): void => {
+	setMetadata(metadataRef);
+};
+
+export const injectReactiveMetadata = (): Ref<PageMetadata | null> => {
+	const metadataRef = getMetadata();
+	return isRef(metadataRef) ? metadataRef : ref(null);
+};
diff --git a/packages/frontend/src/scripts/physics.ts b/packages/frontend/src/scripts/physics.ts
index cf9fad70eb..8a4e9319b3 100644
--- a/packages/frontend/src/scripts/physics.ts
+++ b/packages/frontend/src/scripts/physics.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/please-login.ts b/packages/frontend/src/scripts/please-login.ts
index e6c08dfbc0..9e51272791 100644
--- a/packages/frontend/src/scripts/please-login.ts
+++ b/packages/frontend/src/scripts/please-login.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/popout.ts b/packages/frontend/src/scripts/popout.ts
index 0c2ff16992..1caa2dfc21 100644
--- a/packages/frontend/src/scripts/popout.ts
+++ b/packages/frontend/src/scripts/popout.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/popup-position.ts b/packages/frontend/src/scripts/popup-position.ts
index 0a799c5665..8c9e3c02c3 100644
--- a/packages/frontend/src/scripts/popup-position.ts
+++ b/packages/frontend/src/scripts/popup-position.ts
@@ -1,10 +1,10 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 export function calcPopupPosition(el: HTMLElement, props: {
-	anchorElement: HTMLElement | null;
+	anchorElement?: HTMLElement | null;
 	innerMargin: number;
 	direction: 'top' | 'bottom' | 'left' | 'right';
 	align: 'top' | 'bottom' | 'left' | 'right' | 'center';
diff --git a/packages/frontend/src/scripts/post-message.ts b/packages/frontend/src/scripts/post-message.ts
index 80441caf15..31a9ac1ad9 100644
--- a/packages/frontend/src/scripts/post-message.ts
+++ b/packages/frontend/src/scripts/post-message.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/reaction-picker.ts b/packages/frontend/src/scripts/reaction-picker.ts
index 9b13e794f5..7aec05c0cf 100644
--- a/packages/frontend/src/scripts/reaction-picker.ts
+++ b/packages/frontend/src/scripts/reaction-picker.ts
@@ -1,8 +1,9 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
+import * as Misskey from 'misskey-js';
 import { defineAsyncComponent, Ref, ref } from 'vue';
 import { popup } from '@/os.js';
 import { defaultStore } from '@/store.js';
@@ -10,6 +11,7 @@ import { defaultStore } from '@/store.js';
 class ReactionPicker {
 	private src: Ref<HTMLElement | null> = ref(null);
 	private manualShowing = ref(false);
+	private targetNote: Ref<Misskey.entities.Note | null> = ref(null);
 	private onChosen?: (reaction: string) => void;
 	private onClosed?: () => void;
 
@@ -23,6 +25,7 @@ class ReactionPicker {
 			src: this.src,
 			pinnedEmojis: reactionsRef,
 			asReactionPicker: true,
+			targetNote: this.targetNote,
 			manualShowing: this.manualShowing,
 		}, {
 			done: reaction => {
@@ -38,8 +41,9 @@ class ReactionPicker {
 		});
 	}
 
-	public show(src: HTMLElement, onChosen?: ReactionPicker['onChosen'], onClosed?: ReactionPicker['onClosed']) {
+	public show(src: HTMLElement | null, targetNote: Misskey.entities.Note | null, onChosen?: ReactionPicker['onChosen'], onClosed?: ReactionPicker['onClosed']) {
 		this.src.value = src;
+		this.targetNote.value = targetNote;
 		this.manualShowing.value = true;
 		this.onChosen = onChosen;
 		this.onClosed = onClosed;
diff --git a/packages/frontend/src/scripts/safe-parse.ts b/packages/frontend/src/scripts/safe-parse.ts
new file mode 100644
index 0000000000..6bfcef6c36
--- /dev/null
+++ b/packages/frontend/src/scripts/safe-parse.ts
@@ -0,0 +1,11 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export function safeParseFloat(str: unknown): number | null {
+	if (typeof str !== 'string' || str === '') return null;
+	const num = parseFloat(str);
+	if (isNaN(num)) return null;
+	return num;
+}
diff --git a/packages/frontend/src/scripts/safe-uri-decode.ts b/packages/frontend/src/scripts/safe-uri-decode.ts
index 625d8c34a7..0edf4e9eba 100644
--- a/packages/frontend/src/scripts/safe-uri-decode.ts
+++ b/packages/frontend/src/scripts/safe-uri-decode.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/scroll.ts b/packages/frontend/src/scripts/scroll.ts
index 1f626e4c0d..8edb6fca05 100644
--- a/packages/frontend/src/scripts/scroll.ts
+++ b/packages/frontend/src/scripts/scroll.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/search-emoji.ts b/packages/frontend/src/scripts/search-emoji.ts
new file mode 100644
index 0000000000..4192a2df8f
--- /dev/null
+++ b/packages/frontend/src/scripts/search-emoji.ts
@@ -0,0 +1,106 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export type EmojiDef = {
+	emoji: string;
+	name: string;
+	url: string;
+	aliasOf?: string;
+} | {
+	emoji: string;
+	name: string;
+	aliasOf?: string;
+	isCustomEmoji?: true;
+};
+type EmojiScore = { emoji: EmojiDef, score: number };
+
+export function searchEmoji(query: string | null, emojiDb: EmojiDef[], max = 30): EmojiDef[] {
+	if (!query) {
+		return [];
+	}
+
+	const matched = new Map<string, EmojiScore>();
+	// 完全一致(エイリアスなし)
+	emojiDb.some(x => {
+		if (x.name.toLowerCase() === query && !x.aliasOf) {
+			matched.set(x.name, { emoji: x, score: query.length + 3 });
+		}
+		return matched.size === max;
+	});
+
+	// 完全一致(エイリアス込み)
+	if (matched.size < max) {
+		emojiDb.some(x => {
+			if (x.name.toLowerCase() === query && !matched.has(x.aliasOf ?? x.name)) {
+				matched.set(x.aliasOf ?? x.name, { emoji: x, score: query.length + 2 });
+			}
+			return matched.size === max;
+		});
+	}
+
+	// 前方一致(エイリアスなし)
+	if (matched.size < max) {
+		emojiDb.some(x => {
+			if (x.name.toLowerCase().startsWith(query) && !x.aliasOf && !matched.has(x.name)) {
+				matched.set(x.name, { emoji: x, score: query.length + 1 });
+			}
+			return matched.size === max;
+		});
+	}
+
+	// 前方一致(エイリアス込み)
+	if (matched.size < max) {
+		emojiDb.some(x => {
+			if (x.name.toLowerCase().startsWith(query) && !matched.has(x.aliasOf ?? x.name)) {
+				matched.set(x.aliasOf ?? x.name, { emoji: x, score: query.length });
+			}
+			return matched.size === max;
+		});
+	}
+
+	// 部分一致(エイリアス込み)
+	if (matched.size < max) {
+		emojiDb.some(x => {
+			if (x.name.toLowerCase().includes(query) && !matched.has(x.aliasOf ?? x.name)) {
+				matched.set(x.aliasOf ?? x.name, { emoji: x, score: query.length - 1 });
+			}
+			return matched.size === max;
+		});
+	}
+
+	// 簡易あいまい検索(3文字以上)
+	if (matched.size < max && query.length > 3) {
+		const queryChars = [...query];
+		const hitEmojis = new Map<string, EmojiScore>();
+
+		for (const x of emojiDb) {
+			// 文字列の位置を進めながら、クエリの文字を順番に探す
+
+			let pos = 0;
+			let hit = 0;
+			for (const c of queryChars) {
+				pos = x.name.indexOf(c, pos);
+				if (pos <= -1) break;
+				hit++;
+			}
+
+			// 半分以上の文字が含まれていればヒットとする
+			if (hit > Math.ceil(queryChars.length / 2) && hit - 2 > (matched.get(x.aliasOf ?? x.name)?.score ?? 0)) {
+				hitEmojis.set(x.aliasOf ?? x.name, { emoji: x, score: hit - 2 });
+			}
+		}
+
+		// ヒットしたものを全部追加すると雑多になるので、先頭の6件程度だけにしておく(6件=オートコンプリートのポップアップのサイズ分)
+		[...hitEmojis.values()]
+			.sort((x, y) => y.score - x.score)
+			.slice(0, 6)
+			.forEach(it => matched.set(it.emoji.name, it));
+	}
+
+	return [...matched.values()]
+		.sort((x, y) => y.score - x.score)
+		.slice(0, max)
+		.map(it => it.emoji);
+}
diff --git a/packages/frontend/src/scripts/select-file.ts b/packages/frontend/src/scripts/select-file.ts
index 674c762fac..fd7cfc697b 100644
--- a/packages/frontend/src/scripts/select-file.ts
+++ b/packages/frontend/src/scripts/select-file.ts
@@ -1,11 +1,12 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import { ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { useStream } from '@/stream.js';
 import { i18n } from '@/i18n.js';
 import { defaultStore } from '@/store.js';
@@ -65,7 +66,7 @@ export function chooseFileFromUrl(): Promise<Misskey.entities.DriveFile> {
 				}
 			});
 
-			os.api('drive/files/upload-from-url', {
+			misskeyApi('drive/files/upload-from-url', {
 				url: url,
 				folderId: defaultStore.state.uploadFolder,
 				marker,
diff --git a/packages/frontend/src/scripts/show-moved-dialog.ts b/packages/frontend/src/scripts/show-moved-dialog.ts
index b4defbfe7d..35b3ef79d8 100644
--- a/packages/frontend/src/scripts/show-moved-dialog.ts
+++ b/packages/frontend/src/scripts/show-moved-dialog.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/show-suspended-dialog.ts b/packages/frontend/src/scripts/show-suspended-dialog.ts
index a2fd5db453..8b89dbb936 100644
--- a/packages/frontend/src/scripts/show-suspended-dialog.ts
+++ b/packages/frontend/src/scripts/show-suspended-dialog.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/shuffle.ts b/packages/frontend/src/scripts/shuffle.ts
index d9d5bb1037..fed16bc71c 100644
--- a/packages/frontend/src/scripts/shuffle.ts
+++ b/packages/frontend/src/scripts/shuffle.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/snowfall-effect.ts b/packages/frontend/src/scripts/snowfall-effect.ts
index a09f02cec0..11fcaa0716 100644
--- a/packages/frontend/src/scripts/snowfall-effect.ts
+++ b/packages/frontend/src/scripts/snowfall-effect.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -17,20 +17,20 @@ export class SnowfallEffect {
 		uniform vec3 u_worldSize;
 		uniform float u_gravity;
 		uniform float u_wind;
+		uniform float u_spin_factor;
+		uniform float u_turbulence;
 
 		void main() {
 			v_color = a_color;
-			v_rotation = a_rotation.x + u_time * a_rotation.y;
+			v_rotation = a_rotation.x + (u_time * u_spin_factor) * a_rotation.y;
 
 			vec3 pos = a_position.xyz;
 
-			float turbulence = 1.0;
-
 			pos.x = mod(pos.x + u_time + u_wind * a_speed.x, u_worldSize.x * 2.0) - u_worldSize.x;
 			pos.y = mod(pos.y - u_time * a_speed.y * u_gravity, u_worldSize.y * 2.0) - u_worldSize.y;
 
-			pos.x += sin(u_time * a_speed.z * turbulence) * a_rotation.z;
-			pos.z += cos(u_time * a_speed.z * turbulence) * a_rotation.z;
+			pos.x += sin(u_time * a_speed.z * u_turbulence) * a_rotation.z;
+			pos.z += cos(u_time * a_speed.z * u_turbulence) * a_rotation.z;
 
 			gl_Position = u_projection * vec4(pos.xyz, a_position.w);
 			gl_PointSize = (a_size / gl_Position.w) * 100.0;
@@ -105,6 +105,7 @@ export class SnowfallEffect {
 	private opacity = 1;
 	private size = 4;
 	private snowflake = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAACxMBAJqcGAAAAVlpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IlhNUCBDb3JlIDUuNC4wIj4KICAgPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICAgICAgPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIKICAgICAgICAgICAgeG1sbnM6dGlmZj0iaHR0cDovL25zLmFkb2JlLmNvbS90aWZmLzEuMC8iPgogICAgICAgICA8dGlmZjpPcmllbnRhdGlvbj4xPC90aWZmOk9yaWVudGF0aW9uPgogICAgICA8L3JkZjpEZXNjcmlwdGlvbj4KICAgPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KTMInWQAAErRJREFUeAHdmgnYlmPax5MShaxRKRElPmXJXpaSsRxDU0bTZ+kt65RloiRDltEMQsxYKmS+zzYjxCCamCzV2LchResMIxFRQ1G93+93Pdf5dL9v7zuf4/hm0fc/jt9znddy3/e1nNd53c/7vHXq/AtVWVnZA/bzkaQjoWG298DeMdvrmP6/EIOqC4fBsbAx7Arz4TaYBPXgWVDnO2jSBrB2T0IMIA9mCmmoE8aonPkR6WPZHlp9xSlfeyeBzq9bHBD5feEdUGfDXBgBqnde+a2wvw/dYdNctvZNAp1PnTaFttA6JgP7eVgBM0CNzgO9HNvy0AcYDda6SaDTdXOnz8X+IkZDugAGQmOYA+ob6Ah/MIOMDRPhJjgJ6uV7pXtWt81/50SnY/Wvwn4ZDHAvwJ9ATYcxyaqsnEnqZCyCPaE80BgYZXG/5A3VyyP/b08LHa11z9KmFUwA5eqruRBHYX1s8WSI1Xcbme8Mt8PWUCU+kF8XbFN+dtH+p06OD4IU8EjD/VOZ5bnezq0XHcHuC2oV7BDlkVIWq56uIX8UjAO31GRIMYW0Vo/xXtSXJyTuXVO6xk1qalRTmQ9AfqzEvog2XYpllnsd6Qr4unCPT7NtByu0uU7vuAaOoy1JuvfXpJdTvSX0gI1gCXwGZdFmEFxoQb7Wid8s7lNu+I8wuHGsTqz2zpQ9DAa5R6HC55A2gvCMXthvwi25bjx26H0M9/9f4Rnok9s0zulFlC2HzzP9cnld8nH/p7DVrbmuIfYs6JLz9U3/z+KGadDeCDsmwre7GyEifn/su8HVSsL2HeBn8CK8AW+B7u9R5yrPgyOjvSn5DWAaXAG2UU7CE9Ayt4k4sR1lX4LaLdd9gn2ftsL+Vtuh1Dp/elH1C8lvCdUj8kDK3gbP8XdhCnSC86rcsNSR9pQvhc/gVlB9bUfqoFNAy/mLrUROrpMwCtpBxBbTtLqkF4K6IF9rf57I9pnYekx5AS0P1VhopXso9pR5buC7+kewU86nFcB+BT4EXdIvNO73sRBubGTXLZtTtgp+DEb++bACdqBuJOlAaMMzLVM3whegNznQDtCb+pW5b8YY76euB5+7pxm0IbzCfS8m3Zf2q4T8/+4JNArXGoptpxz8LqDmQJq0Qnostt/sfIn5GygD4/Zeq7B7wljQO2yjB/QGj0Pjxz4wGdqXrkjXtCT/ISyDa6EPpHrSraFjvnecFpMoMx40Br3xSlD262rYObevddHTs2kYwWUG9uP5It/f1eU5Xw9btwoXPALbwYXcg+unG/KB3Rq8n9ddAOpn4Kr8BAaBcltcDo9D7Ouavig1o34x7F94xqPk74eLQH0MH8HvwS3SLPe9iheEG6f70KiuLpZv6sxG/Va5bFJOabaO7ucAvGEbeAH+AN1hV7iDOidQFz4A2oJb6D1YDhXZHkTqpL8EbqHDYRtwW20AsdIb8syl5N2e6dTAPB2mWYa+hE4Qk7I59iMwFZ70GlJlfyuTVfygs7Hyw7HbwI0w3Tak14BqEtdg7wVdIx8pZbtBUbrjZeA3vUPBANkU+sEehev8O4Db6QpwYm+D8II0KPKHwUFeQ3oLDIMN4WgID1yOPQ+MAXMhNAtju3ztmtuAypiAw7EXwo/Am+0NfUG5mknYc6GfGVIjsoFNuyuoh8COuDcd2LmwA9jWE8bB3Q7N4XrwWAz5XOXR+Tx4n6FgdHeB6sF/w2QwhlSXdXvl/jixx4NH8GW5LDzb7GrR4ES4F5QddB99CieAwStOAPegdUZ2B71F3AXbQSn3vJ1bYaYWrayh3NUPTcbYFExVW3CfXwlvgfoavMbnDAY9dxGo6dCt0LeaB54H4UydDEPA2R4PDlrFLB9XuNmTlO+Xr7X9ZNBr9J4+EN8AMcv6ButpMND9FM6EnTOHkLrSnvtzwbbq3vwMB2ow/qWFSC8ZC++ZQaldbquH2afQWbl8TdcvVtC6LtipifAuOKt6gA9Tzqgzb5R2gP1hX3DVtZVHVvdklY5DA5beIkVPuZn8LOgAnWEfeAaUkxCan/voBNkfF+U5cFu5z5XlxZU20OmZtgm1K45VO4naNCukrcBZVk/CD+E/YBjoYjXJY8Zg9DxsDrbbBHTRotxOrug4eBs+hHgWZtKzfHrdXHBi9gDvqzxFHNA5KVfyBCf0ExgB7nkXStLLEKkniNf0AzUs5+ublkVFKiC9FBZAvGxshT0NnN3zoSUYSJQPcjAvm0HmjcIPemNS96F6E36drFLwugx7EEzNZV/l9IjoEPkW4B7eFtYH9QKcBcfA/aCWgpPQOT+zMbb9fS3nDbYR2MdgV0S5aVlUhLs0w45IHi7sqnnGJ2E7CXqHWgZXgJ1y8KqpDUmfSLmSV5yB/XrpDqVP8ofmehNdOv7I0ShfP4yyJdl2a4SchI1gCXgkHgljYfvc1i3cs/SU1A9jQRpfri/b0Sal1RrtSj4ULyHprY5C6+6E1+EBULq0E+DK7A96iwqX0z4td8B3dCdob5gD3UB3j9fUcNuDKFOvgc+bZAZFf4Zgu/q/AGPMgfm+5ShPWay+k6I31BwAvVDRYL2cuqfUVTkfnTqvVFx5ai7/MXn3tp1UrtRkDWRsaAMjzaD08uJ1irz7+8ps/6ZYj90V3FKrQBkvmubULbN7vs7tZRyJV9w0ePLbQ4PcJspqXnkbhbgoGk/AVptZRxpB0hU7Mpc1x34cdgKPm1dzeTts9XPwlFAO5Au4BDbO7ZycO7J9A/Zh2b4A2+ucALefWpTrflDKVq4kHQBOoi9PO1qvsDeGd6AxXAJbQ5VxlFrW8EnDcJlTsOPcjElxL7WNy7AduC4f2+A/rSN/Hyg7YMBTxgqPUT3F2HAqtIb58GvQW86GqyG+ff4UWz0FBuH4UhaTal1vmAGfg98dfP4d4HPGwmwYAg+D2/J7uU0ap/YaolHZVbBj5d1DaSK8ADsmqiH2JIhgNRhbPZrbhSdZ5heVJGw7477VfYuaagMK2sM8iMloga1HXAt/AeWELgQnR/0Z7k3W6pe3xTn/JamTFPGnPMZSj6p90rA8YOziwHcnH/EgTovJlJ0LPSHkyrTKmZNJ+8KrYKBsCQeB0pWdBFNleieMgzjL44jejTK1CPSY0CiMdyOT09g6ni5O3Ceg51U4VNLaPSA3SDNEwwiKFdgHgANNrpjb7UVejYTYCuZ92DR42HYh8gfDJfAMqBi4dqxk+RrKGkD0YXNsA6AT5qCUXhBe5CR0gPCC4dhqKFwI1m1qX0hr94CotDE4aAd3PCyBX4Jyn+sNL5tBDsRAp3S7b5KVYwa2A0nHaO5AXBeDtnlMxizsW+HomLh8zX9R5sTeBSEn/cqc2Tvak9eDXCyP2PgbYWzn2gefHxT7+0Qu/h18DO7XmPWYcYqSXuHz2myb6G7RNs7meLgeMxXugbiPA3clQx0xtgNPGN819L7+oCzvm6zSx+EkI+Du3Pe0LbOd/jqc7dhG9Wib+mJ5jaJBuL8e4B5aAMpAomKlb8d+KZWUVnw+dgzKSdDtvKaLDyJ1ReZB7O0J2EV5Xwd8OsTJExNpu7Q1SJ8zgy7K93UCX4P4mr4udoyhPGDKygOP+tomIFarMw2d+cfgF2DnDVAGoBvzw33YTHgPDoXQ7Fx/Wy6YkdMrcrmrehO4Pz3WvP90cIVPgonwITg4973yu0XTZK0+ZQaQd+K816twVAwKO71ZRj9zeg7lcVzXHghpVN4n2G3BAHQ1NILx4MBjoppgLwL3Ww8IHZsf6vGk3O8fwx9heK7rhD0o2zdg75JtT6GzQQ8KzcZwElSr3M5J85ktYCzEG+Gx2NNzm/Cm5pSp+K2gfLrZbg3RcB2IQcZN1qPM3+l06SjbAltX/TiXe1wtg7+AdR+AcgIs7xUPw94XxuTrnOD4E1bEoe9Rptw+DWGOGeQi7JOs1SfKKfk+epcakPNxbI8uFVdem8vT6aJdq7jASYjOFPdQDP4Q6t+Em8HVutmbkbYH9Tv4LcQW+H6ujy9Wrtxc6A7vQnznb5TbHUPZ0mw7CeoaOBAegmfBIKw8WZzs34M/oNiPGPzB2KHdrVMUlD29VFLLpw2jMWmnaIbdDNxXur+dWgVumTMglI4zMgbUEV5LmjqW7XnRkDS9qhbu/xZlZ8LWuc3UfM22Of80aVcYDJ/lstdIWxXu0TGXm/TO19vveHWuOglUxOo6iMfyBe7JOEp01ech9puuuBCMA8pVcUUNUB5lqgMYwJyE1oXOGTh9v1gO6kmogKEwHtREMHYofz5zAl3lJ2AWqJfgfohJiKB8HWWfg54YA9Zr1fn5Xmm80SdvHhNwVmq2umF8vWxA+WRwwE9BPNhOulrq0nxz97j6Go6DF8HYcBfYyer6MwWuoINeDG6roq4iE97QCtsJuxWc2JrkCeKEbgX7waOgnLiavxdQEWfohtgRwCrygIoxoQv1K0FNgR7gAKPTB+dr5lAWMliqmbAb7AzbgCs42vYK21NmOiwHJ9atpdxqDlhdA75QdYJT4XUYDfbBiVRe5ySoZTAbBpeekp6T4lo5uFnBz0fpJ6P8E9SJufEdXHipdRA/mw2hzmvfhrfgfjCKPwJnwn2g3igldb4hNaD5a6/fz7eHVuAb2wPwPs+4DB7E/hTagd64BbgoC6Ab9IAfgn+OX0p/ppAaGxZjnw6+Ep8DK8Cj0IDrmHw3GaeN9EZ/AlxFfk1RuVGUYu8K00D9Fa6EvrAUVKzO29gXg9vC1VW3g540w0xBcU2hKJnz+FxYvTCXWaduK/StuTZlLcD6JjnfEvsb6A56m32z78q4FMGw1gA4lEa60WmwMeiSnsljIBSDmEOBE3RdfvggbMuMIbNhItgJtbyUpE9ddjA0Bid1sderXDaQ1OdPAO9zH6hDcpuG2Ml7SQfArHRx6Xpf3JTluySrsrIP6Seg9/iMqsEvF6YZoXIDeAZCRmpneAHEnnLQnaEuXATX53schR3n/e7YyuvOT1bpnyV107Io3xZ6QWs4EirAyXkEqqvK3xa9CQ0c5C5xQ+zN8kWjcr2xZxTsBHfmsipbP671ZmW3wHYA58DdEPobhtwVF2HfBE9H3pT8xjkdja3iiDK4PQBO8Dx4B9wiH8JKeANcKTUW9IITwKNMeYrcArfDhVDsb1pVyty26le5D97/zWzrzVUGXyVjI0WjHUgq4CjoAuGiRuuJkN7mSJX7cn+uaZNyfBBgDHZqXvqsU2cZ6aPwChgE/ap8M9wLbSH+0DKOaw18z8N12GPAyf4BfADbwBmwCbxAHY9NvxQXx2GgVLZXPvurZDE0rqk5+NmAm8U2aIbdH9yDalgpSS80ltlB29fPqW9c8XLUHnsIuGquqt8gN7edwtazrOsAn4MysLryX8BD4Ap3y+0dZROIwPsl9h/hHjgit4lXdrdvHN8dc91wyk7JdvIS7VpF46Jb2ZGz4WJIRyBpBKQW3oR8lZuSvwQMhKtAfQUpYuf27cgbNx6EEeDAzgMHPwYMYi2gEcSfxC7B9qicDMoo/1vQI8p9IG88WAY/yeVpYrJdHpf5vytu4Ky7X46xIamrvjDb52OrG3K+HrZt4xq9wYEZPGPVfp7bhsdE2os2ylV6J1n5mbYPUX4S7AkGX+OAk2t6mm1Iw3PtQ+O4LuooK26RYvW3s7nBLZDiAGlbUHYiRV/S5AWk28DTEFqB4eo+B+n1M55Ivhu4kspj92uYCm6Px0Gv61lor0fcDQNBrQQnOr71lVeYsm894L/bkBuFe/u93eBngJtJMlwTDIDKyfDt6n3se8Dt8jHoNU0o70waq34obZ8lPx4coG+LbifrP6Pt0aQvwn65LFzcAHY8ZUtgAnwExp2WoMpeQLvaA12p7bf/pLPFmS3a/ajr750cfE43wX4YYmU9wi7IddHBCsrc69vm8uuwQydYVhQVvmsUn7s+ebfD0GhXrI+yf2jqA4oPKdo+iHxMwHbYRmgjta4cUTqCWXkg0UHatIR4SxxWKK9PeXhgKiZfxWOthzXuGff4p6b54bH3Y3W3pNxJcK8ebgdI44iys0G0N/8qKGOAGg9Ni50n3yjy2GkxSKtMRtT/21I7Fg/H9lRIX6qK5YX6zSjvDL4BGiBfBnUNmFdzwfKX4Ct40OtJv1sDj0Hlzrk6xbM3tob7uCf4amyk96VHvQg7gltGzQG9wpcwX6BCesfJ3/kJiMmgs+Gm4errUeZqF+Up4IoOzoWLcmqETyLve/2BsKkFpGUvK7VYCz6j06RbQx+ogHhN3Qdb3QF+a/wVKF94OhSHR77sWcXytcKm82usHGW9QE2B3skq/QB7APaqnJ9NuvaufnF1GIhxYH3LSAeA+hM0hMfgNzATdHvjgDHDv+qkP8gW77XW2gwmYsJe2F3zZDgxI7NteTo+/1WD/B9Au3Zjh2RyrgAAAABJRU5ErkJggg==';
+	private mode = 'snow';
 
 	private INITIAL_BUFFERS = () => ({
 		position: { size: 3, value: [] },
@@ -119,6 +120,8 @@ export class SnowfallEffect {
 		worldSize: { type: 'vec3', value: [0, 0, 0] },
 		gravity: { type: 'float', value: this.gravity },
 		wind: { type: 'float', value: 0 },
+		spin_factor: { type: 'float', value: this.mode === 'sakura' ? 8 : 1 },
+		turbulence: { type: 'float', value: this.mode === 'sakura' ? 2 : 1 },
 		projection: {
 			type: 'mat4',
 			value: [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1],
@@ -153,7 +156,16 @@ export class SnowfallEffect {
 		easing: 0.0005,
 	};
 
-	constructor() {
+	constructor(options: {
+		sakura?: boolean;
+	}) {
+		if (options.sakura) {
+			this.mode = 'sakura';
+			this.snowflake = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAFEmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICAgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgeG1wOkNyZWF0ZURhdGU9IjIwMjQtMDItMDFUMTQ6Mzk6NTYrMDkwMCIKICAgeG1wOk1vZGlmeURhdGU9IjIwMjQtMDItMDFUMTQ6NDU6MzQrMDk6MDAiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMjQtMDItMDFUMTQ6NDU6MzQrMDk6MDAiCiAgIHBob3Rvc2hvcDpEYXRlQ3JlYXRlZD0iMjAyNC0wMi0wMVQxNDozOTo1NiswOTAwIgogICBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIgogICBwaG90b3Nob3A6SUNDUHJvZmlsZT0ic1JHQiBJRUM2MTk2Ni0yLjEiCiAgIGV4aWY6UGl4ZWxYRGltZW5zaW9uPSI2NCIKICAgZXhpZjpQaXhlbFlEaW1lbnNpb249IjY0IgogICBleGlmOkNvbG9yU3BhY2U9IjEiCiAgIHRpZmY6SW1hZ2VXaWR0aD0iNjQiCiAgIHRpZmY6SW1hZ2VMZW5ndGg9IjY0IgogICB0aWZmOlJlc29sdXRpb25Vbml0PSIyIgogICB0aWZmOlhSZXNvbHV0aW9uPSI3Mi8xIgogICB0aWZmOllSZXNvbHV0aW9uPSI3Mi8xIj4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0icHJvZHVjZWQiCiAgICAgIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFmZmluaXR5IFBob3RvIDIgMi4zLjEiCiAgICAgIHN0RXZ0OndoZW49IjIwMjQtMDItMDFUMTQ6NDU6MzQrMDk6MDAiLz4KICAgIDwvcmRmOlNlcT4KICAgPC94bXBNTTpIaXN0b3J5PgogIDwvcmRmOkRlc2NyaXB0aW9uPgogPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KPD94cGFja2V0IGVuZD0iciI/PhldI30AAAGBaUNDUHNSR0IgSUVDNjE5NjYtMi4xAAAokXWRu0sDQRCHP6Mh4oOIWlhYBPHRJBIjiDYWEV+gFjGCr+ZyuUuEJB53JyK2gq2gINr4KvQv0FawFgRFEcTaWtFG5ZwzgQQxs+zst7+dGXZnwRPPqFmrKgzZnG3GRqOB2bn5gO8FDxV46aJRUS1jcnokTln7uJdYsduQW6t83L9Wm9QsFSqqhQdVw7SFx4QnVm3D5R3hZjWtJIXPhIOmXFD4ztUTeX5xOZXnL5fNeGwIPA3CgVQJJ0pYTZtZYXk57dnMilq4j/uSOi03My1rm8xWLGKMEiXAOMMM0UcPA+L7CBGhW3aUyQ//5k+xLLmqeIM1TJZIkcYmKOqKVNdk1UXXZGRYc/v/t6+W3hvJV6+LgvfZcd46wLcN31uO83nkON/HUPkEl7li/vIh9L+LvlXU2g/AvwHnV0UtsQsXm9DyaCim8itVyvToOryeQv0cNN1AzUK+Z4VzTh4gvi5fdQ17+9Ap8f7FHyc6Z8kcDq1+AAAACXBIWXMAAAsTAAALEwEAmpwYAAADwElEQVR4nO2bT4hWVRjGf75TkhoEkhSa/9ocRIIwCsrE1pVnLbkYdFdGgQRS6caVm3CVy2oRuqmQ2yJXKTJh4GqCGs/CJCcLccAJ/yDpnGnxHYeZ4TrNfOc55y78nuWdc3/ve57v+b65f86BgQaqotiE5bEJKxYx7onYhOU1egKwGkViE/YCN4Cx2ITNC4xbDVwAJmMT9tXobVnpArEJe4CvZx0aB7aZdxPzxhkwArw66/Ae8+5Eyf6KJiA2YRPw+bzD64EjLcP3MXfyAMdjEzYWaG1GxRIQmzAEnAVeb/nzFPCSeTeaxj4FBOCZlrEjwBvm3VSJPksm4BPaJw8wBHwXm/BibMIW4HvaJ09ifFygP6BQAtKkfgEeEyHvAy+YdxdFvBmVSsBBdJMnsQ4KeTOSJyA2YT1wCXhcjL4HPG/e/amElkjAAfSTJzEPqKHSBKQLmSvAKiV3lm4BG8y7GyqgOgHvU27yAE+mGjLJEhCbsBL4A3haxXyIJoCN5t0dBUyZgF2UnzypxtsqmNKAt4SsarUkX4F0I3ONOgkAuA48a97FXJAqAa9Qb/IAa4CXFSCVATXjL635yBuQ/RsQm7AWuCroZamaBtaZd3/nQBQJeFPA6EfLFLUVBrwmYPSr7bkAhQHPCRj9al0uQGHAWgGjs9oKA7I/hS5rZ/0XSC86JDclGVph3t3t9+TcBHT56T9QVg+5BnT5/X+grB4GCcgs/sgnYCjzfIWyesg14Hrm+Qpl9ZBrwMT/DymurB4GCeiyuEidGnCN3n15V5pOPfStLAPMu1vAWA4jU7+Zd7dzAIqboREBo7PaCgN+EjA6qz1IQDbAu9/prQeorUvm3eVciOqx+JcizlL0hQKiMuAreiu/amkq1cyWxADz7ipwWsFapH4w7/5SgJRvh+cviCyp4yqQeonMOWCHktmic+bdThVMvUSmyFK2kjWkBph354FTSuY8nTLvflYCSyyT+xD4pwB3EvhADZUbYN5dAfarucB+825cDS25WvwksFuEO2nevSNizVHJ1eLvAoplrePAewJOq4oZYN5NAsPkPTCZBoYTq4iK7hgx734EjmUgjpl3Z1T9tKnGpqlP6e+p0Vg6t6iKG5De3A6ztJul+/Si3/db38WqyrY58+4CcHQJpxxN5xRXFQOSjgCjixg3SvuusiKqZoB59y+964KbCwy7Cew27+7V6apuAkibnhbaEbq3xMaohVTVAADz7hvgMHN/FKeAQ+bdt7X7Kb519mGKTdgKfEbvYucj8+7XLvr4DxAA134c0w/5AAAAAElFTkSuQmCC';
+			this.size = 10;
+			this.density = 1 / 280;
+		}
+
 		const canvas = this.initCanvas();
 		const gl = canvas.getContext('webgl2', { antialias: true });
 		if (gl == null) throw new Error('Failed to get WebGL context');
diff --git a/packages/frontend/src/scripts/sound.ts b/packages/frontend/src/scripts/sound.ts
index 2f7545ef0d..fcd59510df 100644
--- a/packages/frontend/src/scripts/sound.ts
+++ b/packages/frontend/src/scripts/sound.ts
@@ -1,11 +1,10 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import type { SoundStore } from '@/store.js';
 import { defaultStore } from '@/store.js';
-import * as os from '@/os.js';
 
 let ctx: AudioContext;
 const cache = new Map<string, AudioBuffer>();
@@ -89,63 +88,33 @@ export type OperationType = typeof operationTypes[number];
 
 /**
  * 音声を読み込む
- * @param soundStore サウンド設定
+ * @param url url
  * @param options `useCache`: デフォルトは`true` 一度再生した音声はキャッシュする
  */
-export async function loadAudio(soundStore: SoundStore, options?: { useCache?: boolean; }) {
-	if (_DEV_) console.log('loading audio. opts:', options);
-	// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
-	if (soundStore.type === null || (soundStore.type === '_driveFile_' && !soundStore.fileUrl)) {
-		return;
-	}
+export async function loadAudio(url: string, options?: { useCache?: boolean; }) {
 	// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
 	if (ctx == null) {
 		ctx = new AudioContext();
 	}
 	if (options?.useCache ?? true) {
-		if (soundStore.type === '_driveFile_' && cache.has(soundStore.fileId)) {
-			if (_DEV_) console.log('use cache');
-			return cache.get(soundStore.fileId) as AudioBuffer;
-		} else if (cache.has(soundStore.type)) {
-			if (_DEV_) console.log('use cache');
-			return cache.get(soundStore.type) as AudioBuffer;
+		if (cache.has(url)) {
+			return cache.get(url) as AudioBuffer;
 		}
 	}
 
 	let response: Response;
 
-	if (soundStore.type === '_driveFile_') {
-		try {
-			response = await fetch(soundStore.fileUrl);
-		} catch (err) {
-			try {
-				// URLが変わっている可能性があるのでドライブ側からURLを取得するフォールバック
-				const apiRes = await os.api('drive/files/show', {
-					fileId: soundStore.fileId,
-				});
-				response = await fetch(apiRes.url);
-			} catch (fbErr) {
-				// それでも無理なら諦める
-				return;
-			}
-		}
-	} else {
-		try {
-			response = await fetch(`/client-assets/sounds/${soundStore.type}.mp3`);
-		} catch (err) {
-			return;
-		}
+	try {
+		response = await fetch(url);
+	} catch (err) {
+		return;
 	}
 
 	const arrayBuffer = await response.arrayBuffer();
 	const audioBuffer = await ctx.decodeAudioData(arrayBuffer);
 
 	if (options?.useCache ?? true) {
-		if (soundStore.type === '_driveFile_') {
-			cache.set(soundStore.fileId, audioBuffer);
-		} else {
-			cache.set(soundStore.type, audioBuffer);
-		}
+		cache.set(url, audioBuffer);
 	}
 
 	return audioBuffer;
@@ -155,13 +124,12 @@ export async function loadAudio(soundStore: SoundStore, options?: { useCache?: b
  * 既定のスプライトを再生する
  * @param type スプライトの種類を指定
  */
-export function play(operationType: OperationType) {
+export function playMisskeySfx(operationType: OperationType) {
 	const sound = defaultStore.state[`sound_${operationType}`];
-	if (_DEV_) console.log('play', operationType, sound);
-	if (sound.type == null || !canPlay) return;
+	if (sound.type == null || !canPlay || ('userActivation' in navigator && !navigator.userActivation.hasBeenActive)) return;
 
 	canPlay = false;
-	playFile(sound).finally(() => {
+	playMisskeySfxFile(sound).finally(() => {
 		// ごく短時間に音が重複しないように
 		setTimeout(() => {
 			canPlay = true;
@@ -173,26 +141,59 @@ export function play(operationType: OperationType) {
  * サウンド設定形式で指定された音声を再生する
  * @param soundStore サウンド設定
  */
-export async function playFile(soundStore: SoundStore) {
-	const buffer = await loadAudio(soundStore);
+export async function playMisskeySfxFile(soundStore: SoundStore) {
+	if (soundStore.type === null || (soundStore.type === '_driveFile_' && !soundStore.fileUrl)) {
+		return;
+	}
+	const masterVolume = defaultStore.state.sound_masterVolume;
+	if (isMute() || masterVolume === 0 || soundStore.volume === 0) {
+		return;
+	}
+	const url = soundStore.type === '_driveFile_' ? soundStore.fileUrl : `/client-assets/sounds/${soundStore.type}.mp3`;
+	const buffer = await loadAudio(url);
 	if (!buffer) return;
-	createSourceNode(buffer, soundStore.volume)?.start();
+	const volume = soundStore.volume * masterVolume;
+	createSourceNode(buffer, { volume }).soundSource.start();
 }
 
-export function createSourceNode(buffer: AudioBuffer, volume: number) : AudioBufferSourceNode | null {
-	const masterVolume = defaultStore.state.sound_masterVolume;
-	if (isMute() || masterVolume === 0 || volume === 0) {
-		return null;
+export async function playUrl(url: string, opts: {
+	volume?: number;
+	pan?: number;
+	playbackRate?: number;
+}) {
+	if (opts.volume === 0) {
+		return;
 	}
+	const buffer = await loadAudio(url);
+	if (!buffer) return;
+	createSourceNode(buffer, opts).soundSource.start();
+}
+
+export function createSourceNode(buffer: AudioBuffer, opts: {
+	volume?: number;
+	pan?: number;
+	playbackRate?: number;
+}): {
+	soundSource: AudioBufferSourceNode;
+	panNode: StereoPannerNode;
+	gainNode: GainNode;
+} {
+	const panNode = ctx.createStereoPanner();
+	panNode.pan.value = opts.pan ?? 0;
 
 	const gainNode = ctx.createGain();
-	gainNode.gain.value = masterVolume * volume;
+
+	gainNode.gain.value = opts.volume ?? 1;
 
 	const soundSource = ctx.createBufferSource();
 	soundSource.buffer = buffer;
-	soundSource.connect(gainNode).connect(ctx.destination);
+	soundSource.playbackRate.value = opts.playbackRate ?? 1;
+	soundSource
+		.connect(panNode)
+		.connect(gainNode)
+		.connect(ctx.destination);
 
-	return soundSource;
+	return { soundSource, panNode, gainNode };
 }
 
 /**
diff --git a/packages/frontend/src/scripts/sticky-sidebar.ts b/packages/frontend/src/scripts/sticky-sidebar.ts
index f233c3648e..50f1e6ecc8 100644
--- a/packages/frontend/src/scripts/sticky-sidebar.ts
+++ b/packages/frontend/src/scripts/sticky-sidebar.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/test-utils.ts b/packages/frontend/src/scripts/test-utils.ts
index 1b42811faa..52bb2d94e0 100644
--- a/packages/frontend/src/scripts/test-utils.ts
+++ b/packages/frontend/src/scripts/test-utils.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/theme-editor.ts b/packages/frontend/src/scripts/theme-editor.ts
index 275f4bcdaa..0092af1640 100644
--- a/packages/frontend/src/scripts/theme-editor.ts
+++ b/packages/frontend/src/scripts/theme-editor.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/theme.ts b/packages/frontend/src/scripts/theme.ts
index a174f51756..c49593ed42 100644
--- a/packages/frontend/src/scripts/theme.ts
+++ b/packages/frontend/src/scripts/theme.ts
@@ -1,11 +1,12 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import { ref } from 'vue';
 import tinycolor from 'tinycolor2';
 import { deepClone } from './clone.js';
+import type { BuiltinTheme } from 'shiki';
 import { globalEvents } from '@/events.js';
 import lightTheme from '@/themes/_light.json5';
 import darkTheme from '@/themes/_dark.json5';
@@ -18,6 +19,13 @@ export type Theme = {
 	desc?: string;
 	base?: 'dark' | 'light';
 	props: Record<string, string>;
+	codeHighlighter?: {
+		base: BuiltinTheme;
+		overrides?: Record<string, any>;
+	} | {
+		base: '_none_';
+		overrides: Record<string, any>;
+	};
 };
 
 export const themeProps = Object.keys(lightTheme.props).filter(key => !key.startsWith('X'));
@@ -57,7 +65,7 @@ export const getBuiltinThemesRef = () => {
 
 const themeFontFaceName = 'sharkey-theme-font-face';
 
-let timeout = null;
+let timeout: number | null = null;
 
 export function applyTheme(theme: Theme, persist = true) {
 	if (timeout) window.clearTimeout(timeout);
diff --git a/packages/frontend/src/scripts/time.ts b/packages/frontend/src/scripts/time.ts
index 4479db1081..275b67ed00 100644
--- a/packages/frontend/src/scripts/time.ts
+++ b/packages/frontend/src/scripts/time.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/timezones.ts b/packages/frontend/src/scripts/timezones.ts
index 55f9be393f..c7582e06da 100644
--- a/packages/frontend/src/scripts/timezones.ts
+++ b/packages/frontend/src/scripts/timezones.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/touch.ts b/packages/frontend/src/scripts/touch.ts
index 05f379e4aa..13c9d648dc 100644
--- a/packages/frontend/src/scripts/touch.ts
+++ b/packages/frontend/src/scripts/touch.ts
@@ -1,8 +1,9 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
+import { ref } from 'vue';
 import { deviceKind } from '@/scripts/device-kind.js';
 
 const isTouchSupported = 'maxTouchPoints' in navigator && navigator.maxTouchPoints > 0;
@@ -16,3 +17,6 @@ if (isTouchSupported && !isTouchUsing) {
 		isTouchUsing = true;
 	}, { passive: true });
 }
+
+/** (MkHorizontalSwipe) 横スワイプ中か? */
+export const isHorizontalSwipeSwiping = ref(false);
diff --git a/packages/frontend/src/scripts/unison-reload.ts b/packages/frontend/src/scripts/unison-reload.ts
index 65fc090888..a24941d02e 100644
--- a/packages/frontend/src/scripts/unison-reload.ts
+++ b/packages/frontend/src/scripts/unison-reload.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/upload.ts b/packages/frontend/src/scripts/upload.ts
index b896376ec8..6c46b2bc1b 100644
--- a/packages/frontend/src/scripts/upload.ts
+++ b/packages/frontend/src/scripts/upload.ts
@@ -1,11 +1,11 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import { reactive, ref } from 'vue';
 import * as Misskey from 'misskey-js';
-import { readAndCompressImage } from 'browser-image-resizer';
+import { readAndCompressImage } from '@misskey-dev/browser-image-resizer';
 import { getCompressionConfig } from './upload/compress-config.js';
 import { defaultStore } from '@/store.js';
 import { apiUrl } from '@/config.js';
diff --git a/packages/frontend/src/scripts/upload/compress-config.ts b/packages/frontend/src/scripts/upload/compress-config.ts
index 2deb9cbb81..3046b7f518 100644
--- a/packages/frontend/src/scripts/upload/compress-config.ts
+++ b/packages/frontend/src/scripts/upload/compress-config.ts
@@ -1,11 +1,11 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import isAnimated from 'is-file-animated';
 import { isWebpSupported } from './isWebpSupported.js';
-import type { BrowserImageResizerConfig } from 'browser-image-resizer';
+import type { BrowserImageResizerConfigWithConvertedOutput } from '@misskey-dev/browser-image-resizer';
 
 const compressTypeMap = {
 	'image/jpeg': { quality: 0.90, mimeType: 'image/webp' },
@@ -21,7 +21,7 @@ const compressTypeMapFallback = {
 	'image/svg+xml': { quality: 1, mimeType: 'image/png' },
 } as const;
 
-export async function getCompressionConfig(file: File): Promise<BrowserImageResizerConfig | undefined> {
+export async function getCompressionConfig(file: File): Promise<BrowserImageResizerConfigWithConvertedOutput | undefined> {
 	const imgConfig = (isWebpSupported() ? compressTypeMap : compressTypeMapFallback)[file.type];
 	if (!imgConfig || await isAnimated(file)) {
 		return;
diff --git a/packages/frontend/src/scripts/upload/isWebpSupported.ts b/packages/frontend/src/scripts/upload/isWebpSupported.ts
index 185c3e6b40..2511236ecc 100644
--- a/packages/frontend/src/scripts/upload/isWebpSupported.ts
+++ b/packages/frontend/src/scripts/upload/isWebpSupported.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/url.ts b/packages/frontend/src/scripts/url.ts
index 625f4ce057..e3072b3b7d 100644
--- a/packages/frontend/src/scripts/url.ts
+++ b/packages/frontend/src/scripts/url.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/use-chart-tooltip.ts b/packages/frontend/src/scripts/use-chart-tooltip.ts
index 3d6489c3b8..7e4bf5c9c6 100644
--- a/packages/frontend/src/scripts/use-chart-tooltip.ts
+++ b/packages/frontend/src/scripts/use-chart-tooltip.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/use-document-visibility.ts b/packages/frontend/src/scripts/use-document-visibility.ts
index a9e2512eb3..a8f4d5e03a 100644
--- a/packages/frontend/src/scripts/use-document-visibility.ts
+++ b/packages/frontend/src/scripts/use-document-visibility.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/use-interval.ts b/packages/frontend/src/scripts/use-interval.ts
index b8c5431fb6..b50e78c3cc 100644
--- a/packages/frontend/src/scripts/use-interval.ts
+++ b/packages/frontend/src/scripts/use-interval.ts
@@ -1,9 +1,9 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { onMounted, onUnmounted } from 'vue';
+import { onActivated, onDeactivated, onMounted, onUnmounted } from 'vue';
 
 export function useInterval(fn: () => void, interval: number, options: {
 	immediate: boolean;
@@ -28,6 +28,16 @@ export function useInterval(fn: () => void, interval: number, options: {
 		intervalId = null;
 	};
 
+	onActivated(() => {
+		if (intervalId) return;
+		if (options.immediate) fn();
+		intervalId = window.setInterval(fn, interval);
+	});
+
+	onDeactivated(() => {
+		clear();
+	});
+
 	onUnmounted(() => {
 		clear();
 	});
diff --git a/packages/frontend/src/scripts/use-leave-guard.ts b/packages/frontend/src/scripts/use-leave-guard.ts
index c9750c3923..5f7e56e8a9 100644
--- a/packages/frontend/src/scripts/use-leave-guard.ts
+++ b/packages/frontend/src/scripts/use-leave-guard.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/use-note-capture.ts b/packages/frontend/src/scripts/use-note-capture.ts
index bcdba5455a..3baa45d50f 100644
--- a/packages/frontend/src/scripts/use-note-capture.ts
+++ b/packages/frontend/src/scripts/use-note-capture.ts
@@ -1,16 +1,17 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { onUnmounted, Ref } from 'vue';
+import { onUnmounted, Ref, ShallowRef } from 'vue';
 import * as Misskey from 'misskey-js';
 import { useStream } from '@/stream.js';
 import { $i } from '@/account.js';
 import * as os from '@/os.js';
+import { misskeyApi } from './misskey-api.js';
 
 export function useNoteCapture(props: {
-	rootEl: Ref<HTMLElement>;
+	rootEl: ShallowRef<HTMLElement | undefined>;
 	note: Ref<Misskey.entities.Note>;
 	pureNote: Ref<Misskey.entities.Note>;
 	isDeletedRef: Ref<boolean>;
@@ -32,7 +33,7 @@ export function useNoteCapture(props: {
 
 				// notes/show may throw if the current user can't see the note
 				try {
-					const replyNote = await os.api('notes/show', {
+					const replyNote = await misskeyApi('notes/show', {
 						noteId: body.id,
 					});
 
@@ -100,7 +101,7 @@ export function useNoteCapture(props: {
 
 			case 'updated': {
 				try {
-					const editedNote = await os.api('notes/show', {
+					const editedNote = await misskeyApi('notes/show', {
 						noteId: id,
 					});
 					
@@ -121,7 +122,7 @@ export function useNoteCapture(props: {
 	function capture(withHandler = false): void {
 		if (connection) {
 			// TODO: このノートがストリーミング経由で流れてきた場合のみ sr する
-			connection.send(document.body.contains(props.rootEl.value) ? 'sr' : 's', { id: note.value.id });
+			connection.send(document.body.contains(props.rootEl.value ?? null as Node | null) ? 'sr' : 's', { id: note.value.id });
 			if (pureNote.value.id !== note.value.id) connection.send('s', { id: pureNote.value.id });
 			if (withHandler) connection.on('noteUpdated', onStreamNoteUpdated);
 		}
diff --git a/packages/frontend/src/scripts/use-tooltip.ts b/packages/frontend/src/scripts/use-tooltip.ts
index aaf0a0285a..a26d08cce7 100644
--- a/packages/frontend/src/scripts/use-tooltip.ts
+++ b/packages/frontend/src/scripts/use-tooltip.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/scripts/worker-multi-dispatch.ts b/packages/frontend/src/scripts/worker-multi-dispatch.ts
index 7686b687c5..6b3fcd9383 100644
--- a/packages/frontend/src/scripts/worker-multi-dispatch.ts
+++ b/packages/frontend/src/scripts/worker-multi-dispatch.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts
index 18cfad2102..2cf17b27c5 100644
--- a/packages/frontend/src/store.ts
+++ b/packages/frontend/src/store.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -7,7 +7,9 @@ import { markRaw, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import { miLocalStorage } from './local-storage.js';
 import type { SoundType } from '@/scripts/sound.js';
+import type { BuiltinTheme as ShikiBuiltinTheme } from 'shiki';
 import { Storage } from '@/pizzax.js';
+import { hemisphere } from '@/scripts/intl-const.js';
 
 interface PostFormAction {
 	title: string,
@@ -151,6 +153,14 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'account',
 		default: true,
 	},
+	showVisibilitySelectorOnBoost: {
+		where: 'account',
+		default: true,
+	},
+	visibilityOnBoost: {
+		where: 'account',
+		default: 'public' as 'public' | 'home' | 'followers',
+	},
 
 	menu: {
 		where: 'deviceAccount',
@@ -204,6 +214,13 @@ export const defaultStore = markRaw(new Storage('base', {
 		default: {
 			src: 'home' as 'home' | 'local' | 'social' | 'global' | 'bubble' | `list:${string}`,
 			userList: null as Misskey.entities.UserList | null,
+			filter: {
+				withReplies: true,
+				withRenotes: true,
+				withBots: true,
+				withSensitive: true,
+				onlyFiles: false,
+			},
 		},
 	},
 	pinnedUserLists: {
@@ -247,6 +264,10 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'device',
 		default: false,
 	},
+	warnMissingAltText: {
+		where: 'device',
+		default: true,
+	},
 	imageNewTab: {
 		where: 'device',
 		default: false,
@@ -391,6 +412,10 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'device',
 		default: false,
 	},
+	oneko: {
+		where: 'device',
+		default: false,
+	},
 	clickToOpen: {
 		where: 'device',
 		default: true,
@@ -427,14 +452,6 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'device',
 		default: false,
 	},
-	tlWithReplies: {
-		where: 'device',
-		default: false,
-	},
-	tlWithBots: {
-		where: 'device',
-		default: true,
-	},
 	defaultWithReplies: {
 		where: 'account',
 		default: false,
@@ -460,6 +477,21 @@ export const defaultStore = markRaw(new Storage('base', {
 		where: 'device',
 		default: false,
 	},
+	dropAndFusion: {
+		where: 'device',
+		default: {
+			bgmVolume: 0.25,
+			sfxVolume: 1,
+		},
+	},
+	hemisphere: {
+		where: 'device',
+		default: hemisphere as 'N' | 'S',
+	},
+	enableHorizontalSwipe: {
+		where: 'device',
+		default: true,
+	},
 
 	sound_masterVolume: {
 		where: 'device',
diff --git a/packages/frontend/src/stream.ts b/packages/frontend/src/stream.ts
index 5f0826b4e3..0c5ee06197 100644
--- a/packages/frontend/src/stream.ts
+++ b/packages/frontend/src/stream.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/style.scss b/packages/frontend/src/style.scss
index fd2716bf9d..d876009961 100644
--- a/packages/frontend/src/style.scss
+++ b/packages/frontend/src/style.scss
@@ -9,7 +9,7 @@
 }
 
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  *
  * SPDX-License-Identifier: AGPL-3.0-only
  */
@@ -37,6 +37,9 @@
 		--margin: var(--marginHalf);
 	}
 
+	--avatar: 48px;
+	--thread-width: 2px;
+
 	//--ad: rgb(255 169 0 / 10%);
 }
 
@@ -250,6 +253,10 @@ rt {
 	line-height: inherit;
 	max-width: 100%;
 
+	&:hover {
+		text-decoration: none;
+	}
+
 	&:focus-visible {
 		outline: none;
 	}
@@ -444,6 +451,39 @@ rt {
 	transition-timing-function: cubic-bezier(0,.5,.5,1);
 }
 
+._woodenFrame {
+	padding: 7px;
+	background: #8C4F26;
+	box-shadow: 0 6px 16px #0007, 0 0 1px 1px #693410, inset 0 0 2px 1px #ce8a5c;
+	border-radius: 10px;
+
+	--bg: #F1E8DC;
+	--panel: #fff;
+	--fg: #693410;
+	--switchOffBg: rgba(0, 0, 0, 0.1);
+	--switchOffFg: rgb(255, 255, 255);
+	--switchOnBg: var(--accent);
+	--switchOnFg: rgb(255, 255, 255);
+}
+
+._woodenFrameH {
+	display: flex;
+	gap: 6px;
+}
+
+._woodenFrameInner {
+	padding: 8px;
+	margin-top: 8px;
+	background: var(--bg);
+	box-shadow: 0 0 2px 1px #ce8a5c, inset 0 0 1px 1px #693410;
+	border-radius: 6px;
+	color: var(--fg);
+
+	&:first-child {
+		margin-top: 0;
+	}
+}
+
 ._transition_zoom-enter-active, ._transition_zoom-leave-active {
 	transition: opacity 0.5s, transform 0.5s !important;
 }
@@ -452,13 +492,13 @@ rt {
 	transform: scale(0.9);
 }
 
-@keyframes blink {
+@keyframes global-blink {
 	0% { opacity: 1; transform: scale(1); }
 	30% { opacity: 1; transform: scale(1); }
 	90% { opacity: 0; transform: scale(0.5); }
 }
 
-@keyframes tada {
+@keyframes global-tada {
 	from {
 		transform: scale3d(1, 1, 1);
 	}
@@ -488,7 +528,7 @@ rt {
 
 ._anime_bounce {
 	will-change: transform;
-  animation: bounce ease 0.7s;
+  animation: global-bounce ease 0.7s;
   animation-iteration-count: 1;
   transform-origin: 50% 50%;
 }
@@ -500,7 +540,7 @@ rt {
 	transition: transform 0.1s ease;
 }
 
-@keyframes bounce {
+@keyframes global-bounce {
   0% {
     transform:  scaleX(0.90) scaleY(0.90) ;
   }
diff --git a/packages/frontend/src/theme-store.ts b/packages/frontend/src/theme-store.ts
index f37c01cca1..c41cc17652 100644
--- a/packages/frontend/src/theme-store.ts
+++ b/packages/frontend/src/theme-store.ts
@@ -1,11 +1,11 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import { Theme, getBuiltinThemes } from '@/scripts/theme.js';
 import { miLocalStorage } from '@/local-storage.js';
-import { api } from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { $i } from '@/account.js';
 
 const lsCacheKey = $i ? `themes:${$i.id}` as const : null;
@@ -19,7 +19,7 @@ export async function fetchThemes(): Promise<void> {
 	if ($i == null) return;
 
 	try {
-		const themes = await api('i/registry/get', { scope: ['client'], key: 'themes' });
+		const themes = await misskeyApi('i/registry/get', { scope: ['client'], key: 'themes' });
 		miLocalStorage.setItem(lsCacheKey!, JSON.stringify(themes));
 	} catch (err) {
 		if (err.code === 'NO_SUCH_KEY') return;
@@ -35,13 +35,13 @@ export async function addTheme(theme: Theme): Promise<void> {
 	}
 	await fetchThemes();
 	const themes = getThemes().concat(theme);
-	await api('i/registry/set', { scope: ['client'], key: 'themes', value: themes });
+	await misskeyApi('i/registry/set', { scope: ['client'], key: 'themes', value: themes });
 	miLocalStorage.setItem(lsCacheKey!, JSON.stringify(themes));
 }
 
 export async function removeTheme(theme: Theme): Promise<void> {
 	if ($i == null) return;
 	const themes = getThemes().filter(t => t.id !== theme.id);
-	await api('i/registry/set', { scope: ['client'], key: 'themes', value: themes });
+	await misskeyApi('i/registry/set', { scope: ['client'], key: 'themes', value: themes });
 	miLocalStorage.setItem(lsCacheKey!, JSON.stringify(themes));
 }
diff --git a/packages/frontend/src/themes/_dark.json5 b/packages/frontend/src/themes/_dark.json5
index 3f5822977a..7b70aa1e09 100644
--- a/packages/frontend/src/themes/_dark.json5
+++ b/packages/frontend/src/themes/_dark.json5
@@ -30,6 +30,7 @@
 		panelHeaderFg: '@fg',
 		panelHeaderDivider: 'rgba(0, 0, 0, 0)',
 		panelBorder: '" solid 1px var(--divider)',
+		thread: ':lighten<12<@panel',
 		acrylicPanel: ':alpha<0.5<@panel',
 		windowHeader: ':alpha<0.85<@panel',
 		popup: ':lighten<3<@panel',
@@ -94,4 +95,8 @@
 		X16: ':alpha<0.7<@panel',
 		X17: ':alpha<0.8<@bg',
 	},
+
+	codeHighlighter: {
+		base: 'one-dark-pro',
+	},
 }
diff --git a/packages/frontend/src/themes/_light.json5 b/packages/frontend/src/themes/_light.json5
index 6ebfcaafeb..d797aec734 100644
--- a/packages/frontend/src/themes/_light.json5
+++ b/packages/frontend/src/themes/_light.json5
@@ -30,6 +30,7 @@
 		panelHeaderFg: '@fg',
 		panelHeaderDivider: 'rgba(0, 0, 0, 0)',
 		panelBorder: '" solid 1px var(--divider)',
+		thread: ':darken<12<@panel',
 		acrylicPanel: ':alpha<0.5<@panel',
 		windowHeader: ':alpha<0.85<@panel',
 		popup: ':lighten<3<@panel',
@@ -94,4 +95,8 @@
 		X16: ':alpha<0.7<@panel',
 		X17: ':alpha<0.8<@bg',
 	},
+
+	codeHighlighter: {
+		base: 'catppuccin-latte',
+	},
 }
diff --git a/packages/frontend/src/themes/l-sushi.json5 b/packages/frontend/src/themes/l-sushi.json5
index e787d63734..f1523b698c 100644
--- a/packages/frontend/src/themes/l-sushi.json5
+++ b/packages/frontend/src/themes/l-sushi.json5
@@ -14,6 +14,6 @@
 		renote: '@accent',
 		link: '@accent',
 		mention: '@accent',
-		hashtag: '#229e82',
+		hashtag: '@accent',
 	},
 }
diff --git a/packages/frontend/src/type.ts b/packages/frontend/src/type.ts
new file mode 100644
index 0000000000..9c0fc2a11e
--- /dev/null
+++ b/packages/frontend/src/type.ts
@@ -0,0 +1,3 @@
+export type WithRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] };
+
+export type WithNonNullable<T, K extends keyof T> = T & { [P in K]-?: NonNullable<T[P]> };
diff --git a/packages/frontend/src/types/date-separated-list.ts b/packages/frontend/src/types/date-separated-list.ts
index 678193ca98..af685cff12 100644
--- a/packages/frontend/src/types/date-separated-list.ts
+++ b/packages/frontend/src/types/date-separated-list.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/types/menu.ts b/packages/frontend/src/types/menu.ts
index f4516bbe5b..712f3464e5 100644
--- a/packages/frontend/src/types/menu.ts
+++ b/packages/frontend/src/types/menu.ts
@@ -1,10 +1,10 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import * as Misskey from 'misskey-js';
-import { Ref } from 'vue';
+import { ComputedRef, Ref } from 'vue';
 
 export type MenuAction = (ev: MouseEvent) => void;
 
@@ -15,7 +15,7 @@ export type MenuLink = { type: 'link', to: string, text: string, icon?: string,
 export type MenuA = { type: 'a', href: string, target?: string, download?: string, text: string, icon?: string, indicate?: boolean };
 export type MenuUser = { type: 'user', user: Misskey.entities.User, active?: boolean, indicate?: boolean, action: MenuAction };
 export type MenuSwitch = { type: 'switch', ref: Ref<boolean>, text: string, disabled?: boolean | Ref<boolean> };
-export type MenuButton = { type?: 'button', text: string, icon?: string, indicate?: boolean, danger?: boolean, active?: boolean, avatar?: Misskey.entities.User; action: MenuAction };
+export type MenuButton = { type?: 'button', text: string, icon?: string, indicate?: boolean, danger?: boolean, active?: boolean | ComputedRef<boolean>, avatar?: Misskey.entities.User; action: MenuAction };
 export type MenuParent = { type: 'parent', text: string, icon?: string, children: MenuItem[] | (() => Promise<MenuItem[]> | MenuItem[]) };
 
 export type MenuPending = { type: 'pending' };
diff --git a/packages/frontend/src/types/page-header.ts b/packages/frontend/src/types/page-header.ts
index 295b97a7fd..e9807a2939 100644
--- a/packages/frontend/src/types/page-header.ts
+++ b/packages/frontend/src/types/page-header.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/ui/_common_/announcements.vue b/packages/frontend/src/ui/_common_/announcements.vue
index 913fa35cc2..b49eff9148 100644
--- a/packages/frontend/src/ui/_common_/announcements.vue
+++ b/packages/frontend/src/ui/_common_/announcements.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts
index a3adbfb1b1..3d5b42241e 100644
--- a/packages/frontend/src/ui/_common_/common.ts
+++ b/packages/frontend/src/ui/_common_/common.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -97,7 +97,13 @@ export function openInstanceMenu(ev: MouseEvent) {
 		action: () => {
 			window.open(instance.privacyPolicyUrl, '_blank', 'noopener');
 		},
-	} : undefined, (!instance.impressumUrl && !instance.tosUrl && !instance.privacyPolicyUrl) ? undefined : { type: 'divider' }, {
+	} : undefined, (instance.donationUrl) ? {
+		text: i18n.ts.donation,
+		icon: 'ph-hand-coins ph-bold ph-lg',
+		action: () => {
+			window.open(instance.donationUrl, '_blank', 'noopener');
+		},
+	} : undefined, (!instance.impressumUrl && !instance.tosUrl && !instance.privacyPolicyUrl && !instance.donationUrl) ? undefined : { type: 'divider' }, {
 		text: i18n.ts.help,
 		icon: 'ph-question ph-bold ph-lg',
 		action: () => {
diff --git a/packages/frontend/src/ui/_common_/common.vue b/packages/frontend/src/ui/_common_/common.vue
index 6ece7d86d7..4fe53ae6a3 100644
--- a/packages/frontend/src/ui/_common_/common.vue
+++ b/packages/frontend/src/ui/_common_/common.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -42,6 +42,8 @@ SPDX-License-Identifier: AGPL-3.0-only
 <div v-if="dev" id="devTicker"><span>DEV BUILD</span></div>
 
 <div v-if="$i && $i.isBot" id="botWarn"><span>{{ i18n.ts.loggedInAsBot }}</span></div>
+
+<SkOneko v-if="defaultStore.state.oneko"/>
 </template>
 
 <script lang="ts" setup>
@@ -49,7 +51,8 @@ import { defineAsyncComponent, ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import { swInject } from './sw-inject.js';
 import XNotification from './notification.vue';
-import { popups, pendingApiRequestsCount } from '@/os.js';
+import { popups } from '@/os.js';
+import { pendingApiRequestsCount } from '@/scripts/misskey-api.js';
 import { uploads } from '@/scripts/upload.js';
 import * as sound from '@/scripts/sound.js';
 import { $i } from '@/account.js';
@@ -58,6 +61,8 @@ import { i18n } from '@/i18n.js';
 import { defaultStore } from '@/store.js';
 import { globalEvents } from '@/events.js';
 
+const SkOneko = defineAsyncComponent(() => import('@/components/SkOneko.vue'));
+
 const XStreamIndicator = defineAsyncComponent(() => import('./stream-indicator.vue'));
 const XUpload = defineAsyncComponent(() => import('./upload.vue'));
 
@@ -82,7 +87,7 @@ function onNotification(notification: Misskey.entities.Notification, isClient =
 		}, 6000);
 	}
 
-	sound.play('notification');
+	sound.playMisskeySfx('notification');
 }
 
 if ($i) {
diff --git a/packages/frontend/src/ui/_common_/navbar-for-mobile.vue b/packages/frontend/src/ui/_common_/navbar-for-mobile.vue
index 618be2db88..85340fa2b7 100644
--- a/packages/frontend/src/ui/_common_/navbar-for-mobile.vue
+++ b/packages/frontend/src/ui/_common_/navbar-for-mobile.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -39,7 +39,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	</div>
 	<div :class="$style.bottom">
 		<button class="_button" :class="$style.post" data-cy-open-post-form @click="os.post">
-			<i :class="$style.postIcon" class="ph-pencil ph-bold ph-lg ti-fw"></i><span style="position: relative;">{{ i18n.ts.note }}</span>
+			<i :class="$style.postIcon" class="ph-pencil-simple ph-bold ph-lg ti-fw"></i><span style="position: relative;">{{ i18n.ts.note }}</span>
 		</button>
 		<button class="_button" :class="$style.account" @click="openAccountMenu">
 			<MkAvatar :user="$i" :class="$style.avatar"/><MkAcct :class="$style.acct" class="_nowrap" :user="$i"/>
@@ -254,7 +254,7 @@ function more() {
 	left: 20px;
 	color: var(--navIndicator);
 	font-size: 8px;
-	animation: blink 1s infinite;
+	animation: global-blink 1s infinite;
 
 	&:has(.itemIndicateValueIcon) {
 		animation: none;
diff --git a/packages/frontend/src/ui/_common_/navbar.vue b/packages/frontend/src/ui/_common_/navbar.vue
index 20d4564770..65763bcfa8 100644
--- a/packages/frontend/src/ui/_common_/navbar.vue
+++ b/packages/frontend/src/ui/_common_/navbar.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -49,7 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</div>
 		<div :class="$style.bottom">
 			<button v-tooltip.noDelay.right="i18n.ts.note" class="_button" :class="[$style.post]" data-cy-open-post-form @click="os.post">
-				<i class="ph-pencil ph-bold ph-lg ti-fw" :class="$style.postIcon"></i><span :class="$style.postText">{{ i18n.ts.note }}</span>
+				<i class="ph-pencil-simple ph-bold ph-lg ti-fw" :class="$style.postIcon"></i><span :class="$style.postText">{{ i18n.ts.note }}</span>
 			</button>
 			<button v-tooltip.noDelay.right="`${i18n.ts.account}: @${$i.username}`" class="_button" :class="[$style.account]" @click="openAccountMenu">
 				<MkAvatar :user="$i" :class="$style.avatar"/><MkAcct class="_nowrap" :class="$style.acct" :user="$i"/>
@@ -313,7 +313,7 @@ function more(ev: MouseEvent) {
 		left: 20px;
 		color: var(--navIndicator);
 		font-size: 8px;
-		animation: blink 1s infinite;
+		animation: global-blink 1s infinite;
 
 		&:has(.itemIndicateValueIcon) {
 			animation: none;
@@ -483,7 +483,7 @@ function more(ev: MouseEvent) {
 		left: 24px;
 		color: var(--navIndicator);
 		font-size: 8px;
-		animation: blink 1s infinite;
+		animation: global-blink 1s infinite;
 
 		&:has(.itemIndicateValueIcon) {
 			animation: none;
diff --git a/packages/frontend/src/ui/_common_/notification.vue b/packages/frontend/src/ui/_common_/notification.vue
index dc1a9a1b24..29ae04387a 100644
--- a/packages/frontend/src/ui/_common_/notification.vue
+++ b/packages/frontend/src/ui/_common_/notification.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/ui/_common_/statusbar-federation.vue b/packages/frontend/src/ui/_common_/statusbar-federation.vue
index c92695afed..8dad666623 100644
--- a/packages/frontend/src/ui/_common_/statusbar-federation.vue
+++ b/packages/frontend/src/ui/_common_/statusbar-federation.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { ref } from 'vue';
 import * as Misskey from 'misskey-js';
 import MarqueeText from '@/components/MkMarquee.vue';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { useInterval } from '@/scripts/use-interval.js';
 import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
 
@@ -52,7 +52,7 @@ const fetching = ref(true);
 const key = ref(0);
 
 const tick = () => {
-	os.api('federation/instances', {
+	misskeyApi('federation/instances', {
 		sort: '+latestRequestReceivedAt',
 		limit: 30,
 	}).then(res => {
diff --git a/packages/frontend/src/ui/_common_/statusbar-rss.vue b/packages/frontend/src/ui/_common_/statusbar-rss.vue
index 58e109ad7f..b973a4fd6b 100644
--- a/packages/frontend/src/ui/_common_/statusbar-rss.vue
+++ b/packages/frontend/src/ui/_common_/statusbar-rss.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/ui/_common_/statusbar-user-list.vue b/packages/frontend/src/ui/_common_/statusbar-user-list.vue
index 6057174ba8..67f8b109c4 100644
--- a/packages/frontend/src/ui/_common_/statusbar-user-list.vue
+++ b/packages/frontend/src/ui/_common_/statusbar-user-list.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -34,7 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { ref, watch } from 'vue';
 import * as Misskey from 'misskey-js';
 import MarqueeText from '@/components/MkMarquee.vue';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { useInterval } from '@/scripts/use-interval.js';
 import { getNoteSummary } from '@/scripts/get-note-summary.js';
 import { notePage } from '@/filters/note.js';
@@ -54,7 +54,7 @@ const key = ref(0);
 
 const tick = () => {
 	if (props.userListId == null) return;
-	os.api('notes/user-list-timeline', {
+	misskeyApi('notes/user-list-timeline', {
 		listId: props.userListId,
 	}).then(res => {
 		notes.value = res;
diff --git a/packages/frontend/src/ui/_common_/statusbars.vue b/packages/frontend/src/ui/_common_/statusbars.vue
index 81445df1e9..872c69810c 100644
--- a/packages/frontend/src/ui/_common_/statusbars.vue
+++ b/packages/frontend/src/ui/_common_/statusbars.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/ui/_common_/stream-indicator.vue b/packages/frontend/src/ui/_common_/stream-indicator.vue
index 4cb773d28a..968c3969bb 100644
--- a/packages/frontend/src/ui/_common_/stream-indicator.vue
+++ b/packages/frontend/src/ui/_common_/stream-indicator.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/ui/_common_/sw-inject.ts b/packages/frontend/src/ui/_common_/sw-inject.ts
index 5239b76705..ff851ad99f 100644
--- a/packages/frontend/src/ui/_common_/sw-inject.ts
+++ b/packages/frontend/src/ui/_common_/sw-inject.ts
@@ -1,13 +1,14 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
-import { api, post } from '@/os.js';
+import { post } from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { $i, login } from '@/account.js';
 import { getAccountFromId } from '@/scripts/get-account-from-id.js';
-import { mainRouter } from '@/router.js';
 import { deepClone } from '@/scripts/clone.js';
+import { mainRouter } from '@/router/main.js';
 
 export function swInject() {
 	navigator.serviceWorker.addEventListener('message', async ev => {
@@ -30,10 +31,10 @@ export function swInject() {
 				// プッシュ通知から来たreply,renoteはtruncateBodyが通されているため、
 				// 完全なノートを取得しなおす
 				if (props.reply) {
-					props.reply = await api('notes/show', { noteId: props.reply.id });
+					props.reply = await misskeyApi('notes/show', { noteId: props.reply.id });
 				}
 				if (props.renote) {
-					props.renote = await api('notes/show', { noteId: props.renote.id });
+					props.renote = await misskeyApi('notes/show', { noteId: props.renote.id });
 				}
 				return post(props);
 			}
diff --git a/packages/frontend/src/ui/_common_/upload.vue b/packages/frontend/src/ui/_common_/upload.vue
index eb8c114f17..244bac6f10 100644
--- a/packages/frontend/src/ui/_common_/upload.vue
+++ b/packages/frontend/src/ui/_common_/upload.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/ui/classic.header.vue b/packages/frontend/src/ui/classic.header.vue
index f0e0271128..527670e103 100644
--- a/packages/frontend/src/ui/classic.header.vue
+++ b/packages/frontend/src/ui/classic.header.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -38,7 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</button>
 			<div class="post" @click="os.post()">
 				<MkButton class="button" gradate full rounded>
-					<i class="ph-pencil ph-bold ph-lg ti-fw"></i>
+					<i class="ph-pencil-simple ph-bold ph-lg ti-fw"></i>
 				</MkButton>
 			</div>
 		</div>
@@ -141,7 +141,7 @@ onMounted(() => {
 					left: 0;
 					color: var(--navIndicator);
 					font-size: 8px;
-					animation: blink 1s infinite;
+					animation: global-blink 1s infinite;
 				}
 
 				&:hover {
diff --git a/packages/frontend/src/ui/classic.sidebar.vue b/packages/frontend/src/ui/classic.sidebar.vue
index 4fa8f1b434..25b9095574 100644
--- a/packages/frontend/src/ui/classic.sidebar.vue
+++ b/packages/frontend/src/ui/classic.sidebar.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	</button>
 	<div class="post" data-cy-open-post-form @click="os.post">
 		<MkButton class="button" gradate full rounded>
-			<i class="ph-pencil ph-bold ph-lg ti-fw"></i><span v-if="!iconOnly" class="text">{{ i18n.ts.note }}</span>
+			<i class="ph-pencil-simple ph-bold ph-lg ti-fw"></i><span v-if="!iconOnly" class="text">{{ i18n.ts.note }}</span>
 		</MkButton>
 	</div>
 	<div class="divider"></div>
@@ -221,7 +221,7 @@ watch(defaultStore.reactiveState.menuDisplay, () => {
 			left: 0;
 			color: var(--navIndicator);
 			font-size: 8px;
-			animation: blink 1s infinite;
+			animation: global-blink 1s infinite;
 
 			&:has(.itemIndicateValueIcon) {
 				animation: none;
diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue
index 3bb9097985..ea9ea56b90 100644
--- a/packages/frontend/src/ui/classic.vue
+++ b/packages/frontend/src/ui/classic.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -52,19 +52,21 @@ import XCommon from './_common_/common.vue';
 import { instanceName } from '@/config.js';
 import { StickySidebar } from '@/scripts/sticky-sidebar.js';
 import * as os from '@/os.js';
-import { mainRouter } from '@/router.js';
-import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
+import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
 import { defaultStore } from '@/store.js';
 import { i18n } from '@/i18n.js';
 import { miLocalStorage } from '@/local-storage.js';
+import { mainRouter } from '@/router/main.js';
 const XHeaderMenu = defineAsyncComponent(() => import('./classic.header.vue'));
 const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue'));
 
+const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
+
 const DESKTOP_THRESHOLD = 1100;
 
 const isDesktop = ref(window.innerWidth >= DESKTOP_THRESHOLD);
 
-const pageMetadata = ref<null | PageMetadata>();
+const pageMetadata = ref<null | PageMetadata>(null);
 const widgetsShowing = ref(false);
 const fullView = ref(false);
 const globalHeaderHeight = ref(0);
@@ -75,12 +77,18 @@ const widgetsLeft = ref<HTMLElement>();
 const widgetsRight = ref<HTMLElement>();
 
 provide('router', mainRouter);
-provideMetadataReceiver((info) => {
-	pageMetadata.value = info.value;
+provideMetadataReceiver((metadataGetter) => {
+	const info = metadataGetter();
+	pageMetadata.value = info;
 	if (pageMetadata.value) {
-		document.title = `${pageMetadata.value.title} | ${instanceName}`;
+		if (isRoot.value && pageMetadata.value.title === instanceName) {
+			document.title = pageMetadata.value.title;
+		} else {
+			document.title = `${pageMetadata.value.title} | ${instanceName}`;
+		}
 	}
 });
+provideReactiveMetadata(pageMetadata);
 provide('shouldHeaderThin', showMenuOnTop.value);
 provide('forceSpacerMin', true);
 
diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue
index 0df814fc88..68c7f0fcd2 100644
--- a/packages/frontend/src/ui/deck.vue
+++ b/packages/frontend/src/ui/deck.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -58,7 +58,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 				<span class="_indicateCounter" :class="$style.itemIndicateValueIcon">{{ $i.unreadNotificationsCount > 99 ? '99+' : $i.unreadNotificationsCount }}</span>
 			</span>
 		</button>
-		<button :class="$style.postButton" class="_button" @click="os.post()"><i :class="$style.navButtonIcon" class="ph-pencil ph-bold ph-lg"></i></button>
+		<button :class="$style.postButton" class="_button" @click="os.post()"><i :class="$style.navButtonIcon" class="ph-pencil-simple ph-bold ph-lg"></i></button>
 	</div>
 
 	<Transition
@@ -103,7 +103,6 @@ import * as os from '@/os.js';
 import { navbarItemDef } from '@/navbar.js';
 import { $i } from '@/account.js';
 import { i18n } from '@/i18n.js';
-import { mainRouter } from '@/router.js';
 import { unisonReload } from '@/scripts/unison-reload.js';
 import { deviceKind } from '@/scripts/device-kind.js';
 import { defaultStore } from '@/store.js';
@@ -117,6 +116,8 @@ import XWidgetsColumn from '@/ui/deck/widgets-column.vue';
 import XMentionsColumn from '@/ui/deck/mentions-column.vue';
 import XDirectColumn from '@/ui/deck/direct-column.vue';
 import XRoleTimelineColumn from '@/ui/deck/role-timeline-column.vue';
+import { mainRouter } from '@/router/main.js';
+import { MenuItem } from '@/types/menu.js';
 const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue'));
 const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue'));
 
@@ -189,7 +190,7 @@ const addColumn = async (ev) => {
 	const { canceled, result: column } = await os.select({
 		title: i18n.ts._deck.addColumn,
 		items: columns.map(column => ({
-			value: column, text: i18n.t('_deck._columns.' + column),
+			value: column, text: i18n.ts._deck._columns[column],
 		})),
 	});
 	if (canceled) return;
@@ -197,7 +198,7 @@ const addColumn = async (ev) => {
 	addColumnToStore({
 		type: column,
 		id: uuid(),
-		name: i18n.t('_deck._columns.' + column),
+		name: i18n.ts._deck._columns[column],
 		width: 330,
 	});
 };
@@ -221,42 +222,41 @@ document.documentElement.style.scrollBehavior = 'auto';
 loadDeck();
 
 function changeProfile(ev: MouseEvent) {
-	const items = ref([{
+	let items: MenuItem[] = [{
 		text: deckStore.state.profile,
-		active: true.valueOf,
-	}]);
+		active: true,
+		action: () => {},
+	}];
 	getProfiles().then(profiles => {
-		items.value = [{
-			text: deckStore.state.profile,
-			active: true.valueOf,
-		}, ...(profiles.filter(k => k !== deckStore.state.profile).map(k => ({
+		items.push(...(profiles.filter(k => k !== deckStore.state.profile).map(k => ({
 			text: k,
 			action: () => {
 				deckStore.set('profile', k);
 				unisonReload();
 			},
-		}))), { type: 'divider' }, {
+		}))), { type: 'divider' as const }, {
 			text: i18n.ts._deck.newProfile,
 			icon: 'ph-plus ph-bold ph-lg',
 			action: async () => {
 				const { canceled, result: name } = await os.inputText({
 					title: i18n.ts._deck.profile,
-					allowEmpty: false,
+					minLength: 1,
 				});
 				if (canceled) return;
 
 				deckStore.set('profile', name);
 				unisonReload();
 			},
-		}];
+		});
+	}).then(() => {
+		os.popupMenu(items, ev.currentTarget ?? ev.target);
 	});
-	os.popupMenu(items, ev.currentTarget ?? ev.target);
 }
 
 async function deleteProfile() {
 	const { canceled } = await os.confirm({
 		type: 'warning',
-		text: i18n.t('deleteAreYouSure', { x: deckStore.state.profile }),
+		text: i18n.tsx.deleteAreYouSure({ x: deckStore.state.profile }),
 	});
 	if (canceled) return;
 
@@ -325,7 +325,7 @@ body {
 }
 
 .rootIsMobile {
-	padding-bottom: 100px;
+	padding-bottom: 58px;
 }
 
 .main {
@@ -446,20 +446,20 @@ body {
 .navButton {
 	position: relative;
 	padding: 0;
-	aspect-ratio: 1;
+	height: 32px;
 	width: 100%;
 	max-width: 60px;
 	margin: auto;
-	border-radius: var(--radius-full);
-	background: var(--panel);
+	border-radius: var(--radius-lg);
+	background: transparent;
 	color: var(--fg);
 
 	&:hover {
-		background: var(--panelHighlight);
+		color: var(--accent);
 	}
 
 	&:active {
-		background: var(--X2);
+		color: var(--accent);
 	}
 }
 
@@ -470,15 +470,17 @@ body {
 
 	&:hover {
 		background: linear-gradient(90deg, var(--X8), var(--X8));
+		color: var(--fgOnAccent);
 	}
 
 	&:active {
 		background: linear-gradient(90deg, var(--X8), var(--X8));
+		color: var(--fgOnAccent);
 	}
 }
 
 .navButtonIcon {
-	font-size: 18px;
+	font-size: 16px;
 	vertical-align: middle;
 }
 
@@ -488,7 +490,7 @@ body {
 	left: 0;
 	color: var(--indicator);
 	font-size: 16px;
-	animation: blink 1s infinite;
+	animation: global-blink 1s infinite;
 
 	&:has(.itemIndicateValueIcon) {
 		animation: none;
diff --git a/packages/frontend/src/ui/deck/antenna-column.vue b/packages/frontend/src/ui/deck/antenna-column.vue
index 7cd1d6aee9..79c7c48073 100644
--- a/packages/frontend/src/ui/deck/antenna-column.vue
+++ b/packages/frontend/src/ui/deck/antenna-column.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -19,6 +19,7 @@ import XColumn from './column.vue';
 import { updateColumn, Column } from './deck-store.js';
 import MkTimeline from '@/components/MkTimeline.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 
 const props = defineProps<{
@@ -35,7 +36,7 @@ onMounted(() => {
 });
 
 async function setAntenna() {
-	const antennas = await os.api('antennas/list');
+	const antennas = await misskeyApi('antennas/list');
 	const { canceled, result: antenna } = await os.select({
 		title: i18n.ts.selectAntenna,
 		items: antennas.map(x => ({
@@ -55,7 +56,7 @@ function editAntenna() {
 
 const menu = [
 	{
-		icon: 'ph-pencil ph-bold ph-lg',
+		icon: 'ph-pencil-simple ph-bold ph-lg',
 		text: i18n.ts.selectAntenna,
 		action: setAntenna,
 	},
diff --git a/packages/frontend/src/ui/deck/channel-column.vue b/packages/frontend/src/ui/deck/channel-column.vue
index 95ed900f7d..984de82c3f 100644
--- a/packages/frontend/src/ui/deck/channel-column.vue
+++ b/packages/frontend/src/ui/deck/channel-column.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 
 	<template v-if="column.channelId">
 		<div style="padding: 8px; text-align: center;">
-			<MkButton primary gradate rounded inline @click="post"><i class="ph-pencil ph-bold ph-lg"></i></MkButton>
+			<MkButton primary gradate rounded inline small @click="post"><i class="ph-pencil-simple ph-bold ph-lg"></i></MkButton>
 		</div>
 		<MkTimeline ref="timeline" src="channel" :channel="column.channelId"/>
 	</template>
@@ -26,6 +26,7 @@ import { updateColumn, Column } from './deck-store.js';
 import MkTimeline from '@/components/MkTimeline.vue';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 
 const props = defineProps<{
@@ -41,7 +42,7 @@ if (props.column.channelId == null) {
 }
 
 async function setChannel() {
-	const channels = await os.api('channels/my-favorites', {
+	const channels = await misskeyApi('channels/my-favorites', {
 		limit: 100,
 	});
 	const { canceled, result: channel } = await os.select({
@@ -60,7 +61,7 @@ async function setChannel() {
 
 async function post() {
 	if (!channel.value || channel.value.id !== props.column.channelId) {
-		channel.value = await os.api('channels/show', {
+		channel.value = await misskeyApi('channels/show', {
 			channelId: props.column.channelId,
 		});
 	}
@@ -71,7 +72,7 @@ async function post() {
 }
 
 const menu = [{
-	icon: 'ph-pencil ph-bold ph-lg',
+	icon: 'ph-pencil-simple ph-bold ph-lg',
 	text: i18n.ts.selectChannel,
 	action: setChannel,
 }];
diff --git a/packages/frontend/src/ui/deck/column.vue b/packages/frontend/src/ui/deck/column.vue
index 9ed7e452e3..f9efb9d88c 100644
--- a/packages/frontend/src/ui/deck/column.vue
+++ b/packages/frontend/src/ui/deck/column.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/ui/deck/deck-store.ts b/packages/frontend/src/ui/deck/deck-store.ts
index e68b7bba8c..6c4e2fd52b 100644
--- a/packages/frontend/src/ui/deck/deck-store.ts
+++ b/packages/frontend/src/ui/deck/deck-store.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -7,7 +7,7 @@ import { throttle } from 'throttle-debounce';
 import { markRaw } from 'vue';
 import { notificationTypes } from 'misskey-js';
 import { Storage } from '@/pizzax.js';
-import { api } from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { deepClone } from '@/scripts/clone.js';
 
 type ColumnWidget = {
@@ -70,7 +70,7 @@ export const loadDeck = async () => {
 	let deck;
 
 	try {
-		deck = await api('i/registry/get', {
+		deck = await misskeyApi('i/registry/get', {
 			scope: ['client', 'deck', 'profiles'],
 			key: deckStore.state.profile,
 		});
@@ -95,7 +95,7 @@ export const loadDeck = async () => {
 
 // TODO: deckがloadされていない状態でsaveすると意図せず上書きが発生するので対策する
 export const saveDeck = throttle(1000, () => {
-	api('i/registry/set', {
+	misskeyApi('i/registry/set', {
 		scope: ['client', 'deck', 'profiles'],
 		key: deckStore.state.profile,
 		value: {
@@ -106,13 +106,13 @@ export const saveDeck = throttle(1000, () => {
 });
 
 export async function getProfiles(): Promise<string[]> {
-	return await api('i/registry/keys', {
+	return await misskeyApi('i/registry/keys', {
 		scope: ['client', 'deck', 'profiles'],
 	});
 }
 
 export async function deleteProfile(key: string): Promise<void> {
-	return await api('i/registry/remove', {
+	return await misskeyApi('i/registry/remove', {
 		scope: ['client', 'deck', 'profiles'],
 		key: key,
 	});
diff --git a/packages/frontend/src/ui/deck/direct-column.vue b/packages/frontend/src/ui/deck/direct-column.vue
index e2a212be46..09412ce386 100644
--- a/packages/frontend/src/ui/deck/direct-column.vue
+++ b/packages/frontend/src/ui/deck/direct-column.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/ui/deck/list-column.vue b/packages/frontend/src/ui/deck/list-column.vue
index 45ecc476e7..128562823b 100644
--- a/packages/frontend/src/ui/deck/list-column.vue
+++ b/packages/frontend/src/ui/deck/list-column.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -19,6 +19,7 @@ import XColumn from './column.vue';
 import { updateColumn, Column } from './deck-store.js';
 import MkTimeline from '@/components/MkTimeline.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 
 const props = defineProps<{
@@ -40,7 +41,7 @@ watch(withRenotes, v => {
 });
 
 async function setList() {
-	const lists = await os.api('users/lists/list');
+	const lists = await misskeyApi('users/lists/list');
 	const { canceled, result: list } = await os.select({
 		title: i18n.ts.selectList,
 		items: lists.map(x => ({
@@ -60,7 +61,7 @@ function editList() {
 
 const menu = [
 	{
-		icon: 'ph-pencil ph-bold ph-lg',
+		icon: 'ph-pencil-simple ph-bold ph-lg',
 		text: i18n.ts.selectList,
 		action: setList,
 	},
diff --git a/packages/frontend/src/ui/deck/main-column.vue b/packages/frontend/src/ui/deck/main-column.vue
index cd567040f4..847dcf247a 100644
--- a/packages/frontend/src/ui/deck/main-column.vue
+++ b/packages/frontend/src/ui/deck/main-column.vue
@@ -1,14 +1,14 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
 <XColumn v-if="deckStore.state.alwaysShowMainColumn || mainRouter.currentRoute.value.name !== 'index'" :column="column" :isStacked="isStacked">
 	<template #header>
-		<template v-if="pageMetadata?.value">
-			<i :class="pageMetadata?.value.icon"></i>
-			{{ pageMetadata?.value.title }}
+		<template v-if="pageMetadata">
+			<i :class="pageMetadata.icon"></i>
+			{{ pageMetadata.title }}
 		</template>
 	</template>
 
@@ -19,15 +19,15 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ComputedRef, provide, shallowRef, ref } from 'vue';
+import { provide, shallowRef, ref } from 'vue';
 import XColumn from './column.vue';
 import { deckStore, Column } from '@/ui/deck/deck-store.js';
 import * as os from '@/os.js';
 import { i18n } from '@/i18n.js';
-import { mainRouter } from '@/router.js';
-import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
+import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
 import { useScrollPositionManager } from '@/nirax.js';
 import { getScrollContainer } from '@/scripts/scroll.js';
+import { mainRouter } from '@/router/main.js';
 
 defineProps<{
 	column: Column;
@@ -35,12 +35,14 @@ defineProps<{
 }>();
 
 const contents = shallowRef<HTMLElement>();
-const pageMetadata = ref<null | ComputedRef<PageMetadata>>();
+const pageMetadata = ref<null | PageMetadata>(null);
 
 provide('router', mainRouter);
-provideMetadataReceiver((info) => {
+provideMetadataReceiver((metadataGetter) => {
+	const info = metadataGetter();
 	pageMetadata.value = info;
 });
+provideReactiveMetadata(pageMetadata);
 
 /*
 function back() {
diff --git a/packages/frontend/src/ui/deck/mentions-column.vue b/packages/frontend/src/ui/deck/mentions-column.vue
index 7df07fd8d7..70ec98119a 100644
--- a/packages/frontend/src/ui/deck/mentions-column.vue
+++ b/packages/frontend/src/ui/deck/mentions-column.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/ui/deck/notifications-column.vue b/packages/frontend/src/ui/deck/notifications-column.vue
index 6a28bab091..837953b1e8 100644
--- a/packages/frontend/src/ui/deck/notifications-column.vue
+++ b/packages/frontend/src/ui/deck/notifications-column.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -40,7 +40,7 @@ function func() {
 }
 
 const menu = [{
-	icon: 'ph-pencil ph-bold ph-lg',
+	icon: 'ph-pencil-simple ph-bold ph-lg',
 	text: i18n.ts.notificationSetting,
 	action: func,
 }];
diff --git a/packages/frontend/src/ui/deck/role-timeline-column.vue b/packages/frontend/src/ui/deck/role-timeline-column.vue
index 5fbd1389b7..1a673a1753 100644
--- a/packages/frontend/src/ui/deck/role-timeline-column.vue
+++ b/packages/frontend/src/ui/deck/role-timeline-column.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -19,6 +19,7 @@ import XColumn from './column.vue';
 import { updateColumn, Column } from './deck-store.js';
 import MkTimeline from '@/components/MkTimeline.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { i18n } from '@/i18n.js';
 
 const props = defineProps<{
@@ -35,7 +36,7 @@ onMounted(() => {
 });
 
 async function setRole() {
-	const roles = (await os.api('roles/list')).filter(x => x.isExplorable);
+	const roles = (await misskeyApi('roles/list')).filter(x => x.isExplorable);
 	const { canceled, result: role } = await os.select({
 		title: i18n.ts.role,
 		items: roles.map(x => ({
@@ -50,7 +51,7 @@ async function setRole() {
 }
 
 const menu = [{
-	icon: 'ph-pencil ph-bold ph-lg',
+	icon: 'ph-pencil-simple ph-bold ph-lg',
 	text: i18n.ts.role,
 	action: setRole,
 }];
diff --git a/packages/frontend/src/ui/deck/tl-column.vue b/packages/frontend/src/ui/deck/tl-column.vue
index f6167b08f9..3745d026e8 100644
--- a/packages/frontend/src/ui/deck/tl-column.vue
+++ b/packages/frontend/src/ui/deck/tl-column.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -114,7 +114,7 @@ async function setType() {
 }
 
 const menu = [{
-	icon: 'ph-pencil ph-bold ph-lg',
+	icon: 'ph-pencil-simple ph-bold ph-lg',
 	text: i18n.ts.timeline,
 	action: setType,
 }, {
diff --git a/packages/frontend/src/ui/deck/widgets-column.vue b/packages/frontend/src/ui/deck/widgets-column.vue
index d111f92443..92d1a673f6 100644
--- a/packages/frontend/src/ui/deck/widgets-column.vue
+++ b/packages/frontend/src/ui/deck/widgets-column.vue
@@ -1,11 +1,11 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
 <XColumn :menu="menu" :naked="true" :column="column" :isStacked="isStacked">
-	<template #header><i class="ph-squares-four ph-bold ph-lg" style="margin-right: 8px;"></i>{{ column.name }}</template>
+	<template #header><i class="ph-stack ph-bold ph-lg" style="margin-right: 8px;"></i>{{ column.name }}</template>
 
 	<div :class="$style.root">
 		<div v-if="!(column.widgets && column.widgets.length > 0) && !edit" :class="$style.intro">{{ i18n.ts._deck.widgetsIntroduction }}</div>
@@ -49,7 +49,7 @@ function func() {
 }
 
 const menu = [{
-	icon: 'ph-pencil ph-bold ph-lg',
+	icon: 'ph-pencil-simple ph-bold ph-lg',
 	text: i18n.ts.editWidgets,
 	action: func,
 }];
diff --git a/packages/frontend/src/ui/minimum.vue b/packages/frontend/src/ui/minimum.vue
index f32f2de3df..db5eb19c20 100644
--- a/packages/frontend/src/ui/minimum.vue
+++ b/packages/frontend/src/ui/minimum.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -14,21 +14,29 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { provide, ComputedRef, ref } from 'vue';
+import { computed, provide, ref } from 'vue';
 import XCommon from './_common_/common.vue';
-import { mainRouter } from '@/router.js';
-import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
+import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
 import { instanceName } from '@/config.js';
+import { mainRouter } from '@/router/main.js';
 
-const pageMetadata = ref<null | ComputedRef<PageMetadata>>();
+const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
+
+const pageMetadata = ref<null | PageMetadata>(null);
 
 provide('router', mainRouter);
-provideMetadataReceiver((info) => {
+provideMetadataReceiver((metadataGetter) => {
+	const info = metadataGetter();
 	pageMetadata.value = info;
-	if (pageMetadata.value.value) {
-		document.title = `${pageMetadata.value.value.title} | ${instanceName}`;
+	if (pageMetadata.value) {
+		if (isRoot.value && pageMetadata.value.title === instanceName) {
+			document.title = pageMetadata.value.title;
+		} else {
+			document.title = `${pageMetadata.value.title} | ${instanceName}`;
+		}
 	}
 });
+provideReactiveMetadata(pageMetadata);
 
 document.documentElement.style.overflowY = 'scroll';
 </script>
diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue
index 1d8e26bfcc..3a48c5eab9 100644
--- a/packages/frontend/src/ui/universal.vue
+++ b/packages/frontend/src/ui/universal.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -22,19 +22,19 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<XWidgets/>
 	</div>
 
-	<button v-if="(!isDesktop || pageMetadata?.needWideArea) && !isMobile" :class="$style.widgetButton" class="_button" @click="widgetsShowing = true"><i class="ph-squares-four ph-bold ph-lg"></i></button>
+	<button v-if="(!isDesktop || pageMetadata?.needWideArea) && !isMobile" :class="$style.widgetButton" class="_button" @click="widgetsShowing = true"><i class="ph-stack ph-bold ph-lg"></i></button>
 
 	<div v-if="isMobile" ref="navFooter" :class="$style.nav">
 		<button :class="$style.navButton" class="_button" @click="drawerMenuShowing = true"><i :class="$style.navButtonIcon" class="ph-list ph-bold ph-lg-2"></i><span v-if="menuIndicated" :class="$style.navButtonIndicator"><i class="_indicatorCircle"></i></span></button>
-		<button :class="$style.navButton" class="_button" @click="mainRouter.currentRoute.value.name === 'index' ? top() : mainRouter.push('/')"><i :class="$style.navButtonIcon" class="ph-house ph-bold ph-lg"></i></button>
+		<button :class="$style.navButton" class="_button" @click="isRoot ? top() : mainRouter.push('/')"><i :class="$style.navButtonIcon" class="ph-house ph-bold ph-lg"></i></button>
 		<button :class="$style.navButton" class="_button" @click="mainRouter.push('/my/notifications')">
 			<i :class="$style.navButtonIcon" class="ph-bell ph-bold ph-lg"></i>
 			<span v-if="$i?.hasUnreadNotification" :class="$style.navButtonIndicator">
 				<span class="_indicateCounter" :class="$style.itemIndicateValueIcon">{{ $i.unreadNotificationsCount > 99 ? '99+' : $i.unreadNotificationsCount }}</span>
 			</span>
 		</button>
-		<button :class="$style.navButton" class="_button" @click="widgetsShowing = true"><i :class="$style.navButtonIcon" class="ph-squares-four ph-bold ph-lg"></i></button>
-		<button :class="$style.postButton" class="_button" @click="os.post()"><i :class="$style.navButtonIcon" class="ph-pencil ph-bold ph-lg"></i></button>
+		<button :class="$style.navButton" class="_button" @click="widgetsShowing = true"><i :class="$style.navButtonIcon" class="ph-stack ph-bold ph-lg"></i></button>
+		<button :class="$style.postButton" class="_button" @click="os.post()"><i :class="$style.navButtonIcon" class="ph-pencil-simple ph-bold ph-lg"></i></button>
 	</div>
 
 	<Transition
@@ -105,18 +105,20 @@ import { defaultStore } from '@/store.js';
 import { navbarItemDef } from '@/navbar.js';
 import { i18n } from '@/i18n.js';
 import { $i } from '@/account.js';
-import { mainRouter } from '@/router.js';
-import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
+import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
 import { deviceKind } from '@/scripts/device-kind.js';
 import { miLocalStorage } from '@/local-storage.js';
 import { CURRENT_STICKY_BOTTOM } from '@/const.js';
 import { useScrollPositionManager } from '@/nirax.js';
+import { mainRouter } from '@/router/main.js';
 
 const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue'));
 const XSidebar = defineAsyncComponent(() => import('@/ui/_common_/navbar.vue'));
 const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue'));
 const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue'));
 
+const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
+
 const DESKTOP_THRESHOLD = 1100;
 const MOBILE_THRESHOLD = 500;
 
@@ -127,18 +129,24 @@ window.addEventListener('resize', () => {
 	isMobile.value = deviceKind === 'smartphone' || window.innerWidth <= MOBILE_THRESHOLD;
 });
 
-const pageMetadata = ref<null | PageMetadata>();
+const pageMetadata = ref<null | PageMetadata>(null);
 const widgetsShowing = ref(false);
 const navFooter = shallowRef<HTMLElement>();
 const contents = shallowRef<InstanceType<typeof MkStickyContainer>>();
 
 provide('router', mainRouter);
-provideMetadataReceiver((info) => {
-	pageMetadata.value = info.value;
+provideMetadataReceiver((metadataGetter) => {
+	const info = metadataGetter();
+	pageMetadata.value = info;
 	if (pageMetadata.value) {
-		document.title = `${pageMetadata.value.title} | ${instanceName}`;
+		if (isRoot.value && pageMetadata.value.title === instanceName) {
+			document.title = pageMetadata.value.title;
+		} else {
+			document.title = `${pageMetadata.value.title} | ${instanceName}`;
+		}
 	}
 });
+provideReactiveMetadata(pageMetadata);
 
 const menuIndicated = computed(() => {
 	for (const def in navbarItemDef) {
@@ -406,20 +414,20 @@ $widgets-hide-threshold: 1090px;
 .navButton {
 	position: relative;
 	padding: 0;
-	aspect-ratio: 1;
+	height: 32px;
 	width: 100%;
 	max-width: 60px;
 	margin: auto;
-	border-radius: var(--radius-full);
-	background: var(--panel);
+	border-radius: var(--radius-lg);
+	background: transparent;
 	color: var(--fg);
 
 	&:hover {
-		background: var(--panelHighlight);
+		color: var(--accent);
 	}
 
 	&:active {
-		background: var(--X2);
+		color: var(--accent);
 	}
 }
 
@@ -430,15 +438,17 @@ $widgets-hide-threshold: 1090px;
 
 	&:hover {
 		background: linear-gradient(90deg, var(--X8), var(--X8));
+		color: var(--fgOnAccent);
 	}
 
 	&:active {
 		background: linear-gradient(90deg, var(--X8), var(--X8));
+		color: var(--fgOnAccent);
 	}
 }
 
 .navButtonIcon {
-	font-size: 18px;
+	font-size: 16px;
 	vertical-align: middle;
 }
 
@@ -448,7 +458,7 @@ $widgets-hide-threshold: 1090px;
 	left: 0;
 	color: var(--indicator);
 	font-size: 16px;
-	animation: blink 1s infinite;
+	animation: global-blink 1s infinite;
 
 	&:has(.itemIndicateValueIcon) {
 		animation: none;
diff --git a/packages/frontend/src/ui/universal.widgets.vue b/packages/frontend/src/ui/universal.widgets.vue
index 57d6ae0330..7e41328403 100644
--- a/packages/frontend/src/ui/universal.widgets.vue
+++ b/packages/frontend/src/ui/universal.widgets.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<XWidgets :edit="editMode" :widgets="widgets" @addWidget="addWidget" @removeWidget="removeWidget" @updateWidget="updateWidget" @updateWidgets="updateWidgets" @exit="editMode = false"/>
 
 	<button v-if="editMode" class="_textButton" style="font-size: 0.9em;" @click="editMode = false"><i class="ph-check ph-bold ph-lg"></i> {{ i18n.ts.editWidgetsExit }}</button>
-	<button v-else class="_textButton" data-cy-widget-edit :class="$style.edit" style="font-size: 0.9em;" @click="editMode = true"><i class="ph-pencil ph-bold ph-lg"></i> {{ i18n.ts.editWidgets }}</button>
+	<button v-else class="_textButton" data-cy-widget-edit :class="$style.edit" style="font-size: 0.9em;" @click="editMode = true"><i class="ph-pencil-simple ph-bold ph-lg"></i> {{ i18n.ts.editWidgets }}</button>
 </div>
 </template>
 
diff --git a/packages/frontend/src/ui/visitor.vue b/packages/frontend/src/ui/visitor.vue
index 78d7e23689..f76372ae34 100644
--- a/packages/frontend/src/ui/visitor.vue
+++ b/packages/frontend/src/ui/visitor.vue
@@ -1,11 +1,11 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
 <div class="mk-app">
-	<div v-if="!narrow && !root" class="side">
+	<div v-if="!narrow && !isRoot" class="side">
 		<div class="banner" :style="{ backgroundImage: instance.backgroundImageUrl ? `url(${ instance.backgroundImageUrl })` : 'none' }"></div>
 		<div class="dashboard">
 			<MkVisitorDashboard/>
@@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	</div>
 
 	<div class="main">
-		<div v-if="!root" class="header">
+		<div v-if="!isRoot" class="header">
 			<div v-if="narrow === false" class="wide">
 				<MkA to="/" class="link" activeClass="active"><i class="ph-house ph-bold ph-lg icon"></i> {{ i18n.ts.home }}</MkA>
 				<MkA v-if="isTimelineAvailable" to="/timeline" class="link" activeClass="active"><i class="ph-chat-text ph-bold ph-lg icon"></i> {{ i18n.ts.timeline }}</MkA>
@@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			</div>
 		</div>
 		<div class="contents">
-			<main v-if="!root" style="container-type: inline-size;">
+			<main v-if="!isRoot" style="container-type: inline-size;">
 				<RouterView/>
 			</main>
 			<main v-else>
@@ -67,31 +67,40 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { ComputedRef, onMounted, provide, ref, computed } from 'vue';
+import { onMounted, provide, ref, computed } from 'vue';
 import * as Misskey from 'misskey-js';
 import XCommon from './_common_/common.vue';
 import { instanceName } from '@/config.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { instance } from '@/instance.js';
 import XSigninDialog from '@/components/MkSigninDialog.vue';
 import XSignupDialog from '@/components/MkSignupDialog.vue';
 import { ColdDeviceStorage, defaultStore } from '@/store.js';
-import { mainRouter } from '@/router.js';
-import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
+import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
 import { i18n } from '@/i18n.js';
 import MkVisitorDashboard from '@/components/MkVisitorDashboard.vue';
+import { mainRouter } from '@/router/main.js';
+
+const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
 
 const DESKTOP_THRESHOLD = 1100;
 
-const pageMetadata = ref<null | ComputedRef<PageMetadata>>();
+const pageMetadata = ref<null | PageMetadata>(null);
 
 provide('router', mainRouter);
-provideMetadataReceiver((info) => {
+provideMetadataReceiver((metadataGetter) => {
+	const info = metadataGetter();
 	pageMetadata.value = info;
-	if (pageMetadata.value.value) {
-		document.title = `${pageMetadata.value.value.title} | ${instanceName}`;
+	if (pageMetadata.value) {
+		if (isRoot.value && pageMetadata.value.title === instanceName) {
+			document.title = pageMetadata.value.title;
+		} else {
+			document.title = `${pageMetadata.value.title} | ${instanceName}`;
+		}
 	}
 });
+provideReactiveMetadata(pageMetadata);
 
 const announcements = {
 	endpoint: 'announcements',
@@ -117,9 +126,7 @@ const keymap = computed(() => {
 	};
 });
 
-const root = computed(() => mainRouter.currentRoute.value.name === 'index');
-
-os.api('meta', { detail: true }).then(res => {
+misskeyApi('meta', { detail: true }).then(res => {
 	meta.value = res;
 });
 
diff --git a/packages/frontend/src/ui/zen.vue b/packages/frontend/src/ui/zen.vue
index 9f92f78764..77193a3475 100644
--- a/packages/frontend/src/ui/zen.vue
+++ b/packages/frontend/src/ui/zen.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -22,24 +22,32 @@ SPDX-License-Identifier: AGPL-3.0-only
 </template>
 
 <script lang="ts" setup>
-import { provide, ComputedRef, ref } from 'vue';
+import { computed, provide, ref } from 'vue';
 import XCommon from './_common_/common.vue';
-import { mainRouter } from '@/router.js';
-import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata.js';
+import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js';
 import { instanceName, ui } from '@/config.js';
 import { i18n } from '@/i18n.js';
+import { mainRouter } from '@/router/main.js';
 
-const pageMetadata = ref<null | ComputedRef<PageMetadata>>();
+const isRoot = computed(() => mainRouter.currentRoute.value.name === 'index');
+
+const pageMetadata = ref<null | PageMetadata>(null);
 
 const showBottom = !(new URLSearchParams(location.search)).has('zen') && ui === 'deck';
 
 provide('router', mainRouter);
-provideMetadataReceiver((info) => {
+provideMetadataReceiver((metadataGetter) => {
+	const info = metadataGetter();
 	pageMetadata.value = info;
-	if (pageMetadata.value.value) {
-		document.title = `${pageMetadata.value.value.title} | ${instanceName}`;
+	if (pageMetadata.value) {
+		if (isRoot.value && pageMetadata.value.title === instanceName) {
+			document.title = pageMetadata.value.title;
+		} else {
+			document.title = `${pageMetadata.value.title} | ${instanceName}`;
+		}
 	}
 });
+provideReactiveMetadata(pageMetadata);
 
 function goToMisskey() {
 	window.location.href = '/';
diff --git a/packages/frontend/src/unicode-emoji-indexes/ja-JP.json b/packages/frontend/src/unicode-emoji-indexes/ja-JP.json
new file mode 100644
index 0000000000..9c491804f2
--- /dev/null
+++ b/packages/frontend/src/unicode-emoji-indexes/ja-JP.json
@@ -0,0 +1,1866 @@
+{
+	"😀":["にやにやした顔","顔","にやにや","幸せ","しあわせ"],
+	"😃":["口を開けた笑顔","顔","口","開ける","笑顔","幸せ","しあわせ"],
+	"😄":["口を開けて目が笑っている笑顔","目","顔","口","開ける","笑顔","幸せ","しあわせ"],
+	"😁":["にやにやした顔","目","顔","にやにや","笑顔"],
+	"😆":["口を開けて笑っている顔","顔","笑い","口","開ける","満足","笑顔"],
+	"😅":["口を開けて冷や汗をかいた笑顔","ぞっとする","顔","口を開ける","笑顔","冷や汗"],
+	"😂":["嬉し泣き","顔","嬉しい","うれしい","笑う","泣く","涙"],
+	"🤣":["大爆笑","顔","床","笑い","大笑い","爆笑","ぐるぐる"],
+	"😇":["天使の笑顔","天使","顔","おとぎ話","ファンタジー","天使の輪","無邪気","笑顔"],
+	"😉":["ウインクした顔","顔","ウインク"],
+	"😊":["目が笑っている笑顔","赤面","目","顔","笑顔"],
+	"🙂":["微笑み","顔","笑顔","幸せ","しあわせ"],
+	"🙃":["逆さの顔","顔","逆さ","さかさ"],
+	"☺️":["笑顔","顔","輪郭","リラックス"],
+	"😋":["食べ物を味わう顔","美味しい","おいしい","顔","味わう","ふーむ","うまい"],
+	"😌":["ほっとした顔","顔","安心","ほっとする"],
+	"😍":["目がハートの笑顔","目","顔","ハート","愛","笑顔"],
+	"🥰":["笑顔とハート","顔","敬愛","べたぼれ","愛"],
+	"😘":["投げキッス","顔","ハート","キス"],
+	"😗":["キスをする顔","顔","キス"],
+	"😙":["笑顔でキス","目","顔","キス","笑顔"],
+	"😚":["目を閉じてキスをする顔","閉じた","目","顔","キス"],
+	"🥲":["涙の出ている笑顔","泣く","幸せ","感謝する","誇りに思う","安心する","笑う"],
+	"🤪":["おどけた顔","目","にやにや","変","興奮","ワイルド"],
+	"😜":["舌を出してウインクしている顔","目","顔","冗談","舌","ウインク"],
+	"😝":["舌を出して目を細めている顔","目","顔","怖い","恐い","こわい","味","舌"],
+	"😛":["舌を出している顔","顔","舌"],
+	"🤑":["強欲な顔","顔","お金","口"],
+	"😎":["サングラスをかけた顔","明るい","かっこいい","目","アイウエア","顔","眼鏡","メガネ","笑顔","太陽","サングラス","天気"],
+	"🤓":["オタク","顔","変な人"],
+	"🥸":["仮装した顔","仮装","メガネ","匿名の人","鼻"],
+	"🧐":["片メガネをかけた顔","退屈","裕福","豊か"],
+	"🤠":["カウボーイハットの顔","カウボーイ","カウガール","顔","帽子"],
+	"🥳":["パーティーフェイス","顔","祝典","帽子","角","パーティー"],
+	"🤡":["ピエロの顔","ピエロ","顔"],
+	"😏":["にやにやした顔","顔","にやにや"],
+	"😶":["口のない顔","顔","口","静かに","沈黙"],
+	"🫥":["点線の顔","落ち込んだ","消える","隠れる","内向的","目に見えない"],
+	"😐":["普通の顔","無表情","顔","平静"],
+	"🫤":["口が斜めになった顔","がっかり","無関心","疑い深い","不安"],
+	"😑":["無表情","顔","ポーカーフェイス","無感情"],
+	"😒":["面白くなさそうな顔","顔","つまらない","不幸"],
+	"🙄":["ぐるぐる目の顔","目","顔","ぐるぐる"],
+	"🤨":["眉が上がっている顔","不信","疑い深い","非難","疑念","やや驚き","懐疑的"],
+	"🤔":["考えている顔","顔","考え中"],
+	"🤫":["シッと言っている顔","シーッ","静か","黙る"],
+	"🤭":["口を手で覆った顔","目","笑顔","覆う","口","手"],
+	"🫢":["目を開いて口を手で覆った顔","驚嘆","畏敬","不信","狼狽","怖い","驚き"],
+	"🫡":["敬礼している顔","ok","敬礼","晴天","部隊","はい"],
+	"🤗":["両手を広げた笑顔","顔","ハグ","抱きしめる"],
+	"🫣":["のぞき見している顔","魅了","のぞき見","凝視","チラ見"],
+	"🤥":["嘘つき顔","顔","嘘","うそ","ピノキオ"],
+	"😳":["赤くなった顔","ぼーっとした","ぼうっとした","顔","赤面"],
+	"😞":["がっかりした顔","がっかり","顔"],
+	"😟":["不安な顔","顔","心配","不安"],
+	"😤":["勝ち誇った顔","顔","勝利","勝つ"],
+	"😠":["怒った顔","怒り","怒った","顔","激怒"],
+	"😡":["ふくれ顔","怒り","怒った","顔","激怒","ふくれっ面","ふくれっつら","憤怒","赤"],
+	"🤬":["口が記号で覆われた顔","呪い","ののしり","罵り"],
+	"😔":["悲しげな顔","がっかり","顔","悲しい"],
+	"😕":["困った顔","困った","こまった","顔"],
+	"🙁":["ご機嫌斜め","顔","しかめっ面","しかめっつら","悲しい","不幸"],
+	"☹":["しかめっつら","顔","しかめっ面","悲しい","不幸"],
+	"😬":["しかめっ面","顔","しかめっつら"],
+	"🥺":["訴えかける顔","顔","物乞い","慈悲","子犬の目"],
+	"😣":["我慢している顔","顔","がんばる","頑張る"],
+	"😖":["うろたえた顔","戸惑い","とまどい","うろたえ","顔"],
+	"😫":["疲れた顔","顔","疲れた","つかれた"],
+	"😩":["うんざりしている顔","顔","疲れた","つかれた","うんざり"],
+	"🥱":["あくびしている顔","飽きた","疲れた","あくび"],
+	"😪":["眠い顔","顔","寝る","睡眠"],
+	"😮‍💨":["ため息の出ている顔","顔","ため息","息切れ","うめき","安心","ささやき","口笛"],
+	"😮":["口を開けた笑顔","顔","口","開ける","同情"],
+	"😱":["絶叫した顔","顔","恐怖","怖い","恐い","こわい","ムンク","怯え","絶叫"],
+	"😨":["ゾッとしている顔","顔","恐怖","恐い","怖い","こわい","怯え"],
+	"😰":["口を開けて冷や汗をかいた顔","青ざめる","ぞっとする","顔","口","開ける","急ぐ","冷や汗"],
+	"😥":["がっかりしたが安心した顔","がっかり","顔","安心","ほっとする","やれやれ"],
+	"😓":["冷や汗をかいている顔","ぞっとする","顔","冷や汗"],
+	"😯":["落ち着いた顔","顔","黙る","呆然","驚き"],
+	"😦":["心配そうな顔の絵文字","顔","しかめっ面","しかめっつら","口","開ける"],
+	"😧":["苦悩に満ちた顔","苦悩","顔"],
+	"🥹":["涙をこらえている顔","怒る","泣く","誇りに思う","逆らう","悲しむ"],
+	"😢":["泣き顔","泣く","顔","悲しい","涙"],
+	"😭":["号泣","泣く","顔","悲しい","涙"],
+	"🤤":["よだれを垂らした顔","よだれ","顔"],
+	"🤩":["スターに夢中","目","顔","にやにや","星","夢想的"],
+	"😵":["目がバツになった顔","めまい","顔","バツ","目"],
+	"😵‍💫":["目がぐるぐるしている顔","めまい","顔","目","うっとり","ぐるぐる","トラブル","おー"],
+	"🥴":["ぼんやしりた顔","顔","目まい","酩酊","ほろ酔い","まっすぐでない目","波状の口"],
+	"😲":["驚いた顔","驚き","びっくり","顔","ショック","驚愕"],
+	"🫨":["震える顔","地震","顔","震え","衝撃","振動"],
+	"🤯":["爆発した頭","顔","ショック","爆発","狂気","びっくり"],
+	"🫠":["ほろりとした顔","消える","溶解する","液体","溶ける"],
+	"🤐":["お口チャック","顔","口","チャック"],
+	"😷":["マスクをした顔","風邪","かぜ","医者","顔","マスク","薬","病気"],
+	"🤕":["怪我","包帯","顔","傷","キズ","けが"],
+	"🤒":["温度計をくわえた顔","顔","病気","風邪","かぜ","体温計"],
+	"🤮":["吐きそうな顔","病気","嘔吐","風邪","かぜ","吐く"],
+	"🤢":["吐きそうな顔","顔","吐き気","嘔吐"],
+	"🤧":["くしゃみをする顔","顔","くしゃみ","ハクション"],
+	"🥵":["ほてった顔","顔","熱っぽい","熱射病","ほてった","赤ら顔","汗をかいた"],
+	"🥶":["青ざめた顔","顔","ぞっとする","凍える","凍傷","つらら"],
+	"😶‍🌫️":["雲で覆われた顔","顔","おっちょこちょい","非現実的","夢","もや","雲で覆われた頭"],
+	"😴":["寝顔","顔","寝る","睡眠","スヤスヤ"],
+	"💤":["睡眠","マンガ","漫画","寝る","スヤスヤ"],
+	"😈":["角つき笑顔","顔","おとぎ話","ファンタジー","角","笑顔"],
+	"👿":["小悪魔","鬼","悪魔","顔","おとぎ話","ファンタジー"],
+	"👹":["鬼","妖怪","顔","昔話","ファンタジー","日本","モンスター"],
+	"👺":["天狗","妖怪","顔","昔話","ファンタジー","日本","モンスター"],
+	"💩":["うんち","マンガ","漫画","フン","顔","モンスター"],
+	"👻":["お化け","妖怪","顔","おとぎ話","ファンタジー","幽霊","モンスター","ハロウィーン"],
+	"💀":["ドクロ","体","死","顔","おとぎ話","モンスター","骸骨","ハロウィーン"],
+	"☠":["ドクロマーク","体","交差した骨","死","顔","モンスター","骸骨","ハロウィーン"],
+	"👽":["宇宙人","怪獣","異星人","顔","おとぎ話","ファンタジー","モンスター","宇宙","UFO"],
+	"🤖":["ロボットの顔","顔","モンスター","ロボット"],
+	"🎃":["ジャック・オ・ランタン","イベント","お祝い","エンタメ","ハロウィン","ジャックオランタン","ランタン","かぼちゃ"],
+	"😺":["口を開けて笑う猫","猫","ネコ","顔","口","開ける","笑顔"],
+	"😸":["ニヤニヤ笑う猫","猫","ネコ","目","顔","ニヤニヤ","笑顔"],
+	"😹":["嬉し泣きしたネコの顔","猫","ネコ","顔","嬉しい","うれしい","涙"],
+	"😻":["ハートの目をした猫の笑顔","猫","ネコ","目","顔","ハート","愛","笑顔"],
+	"😼":["ニヤリと笑う猫の顔","猫","ネコ","顔","皮肉","笑顔","ニヤリ"],
+	"😽":["目を閉じてキスをする猫","猫","ネコ","目","顔","キス"],
+	"🙀":["疲れたネコの顔","猫","ネコ","顔","びっくり","驚く","うんざり"],
+	"😿":["泣いたネコの顔","猫","ネコ","泣く","顔","悲しい","涙"],
+	"😾":["怒ったネコの顔","猫","ネコ","顔","怒る","ふくれっ面","ふくれっつら"],
+	"🫶":["ハートポーズ","愛"],
+	"👐":["開いた手","体","手","広げる"],
+	"🤲":["上に向けた両手のひら","体","祈り","カップのように丸めた手"],
+	"🙌":["両手を上げる","体","お祝い","ジェスチャー","手","バンザイ","万歳","挙げる"],
+	"👏":["拍手","体","手を叩く","手"],
+	"🙏":["握った手","頼む","体","お辞儀","手を合わせる","ジェスチャー","手","お願い","祈る","ありがとう","感謝"],
+	"🤝":["握手","合意","手","手を結ぶ","会議"],
+	"👍":["イイね","体","上","手","指","サムズアップ","+1"],
+	"👎":["ダメ","体","下","手","指","サムズダウン","-1"],
+	"👊":["握りこぶし","体","握る","拳","こぶし","グー","手","パンチ","接近"],
+	"✊":["こぶし","体","握る","拳","グー","手","パンチ"],
+	"🤛":["左向きのこぶし","体","拳","左向き"],
+	"🤜":["右向きのこぶし","体","拳","右向き"],
+	"🤞":["交差させた指","体","交差","指","手","幸運"],
+	"✌":["Vサイン","体","手","V","ブイ","勝つ","勝利","ピース"],
+	"🫰":["人差し指と親指を交差した手","高い","ハート","愛","お金","スナップ"],
+	"🤘":["コルナ","体","指","手","角","最高"],
+	"🤟":["愛してるのジェスチャー","体","愛してる","好き","手"],
+	"👌":["OKサイン","体","手","OK"],
+	"🤌":["つまんでいる指","指","手ぶり","尋問","つまむ","皮肉"],
+	"🤏":["つまんでいる手","体","手","小さい","小型","ちっちゃい"],
+	"👈":["左指差し","手の甲","体","指","手","人差し指","指さす"],
+	"🫳":["手のひらを下にした手","退ける","落とす","シッシ"],
+	"🫴":["手のひらを上にした手","手招き","捕獲","来る","申し出"],
+	"👉":["指差し","手の甲","体","指","手","人差し指","指さす"],
+	"👆":["指差し","手の甲","体","指","手","人差し指","指さす","上"],
+	"👇":["指差し","手の甲","体","下","指","手","人差し指","指さす"],
+	"☝":["指差し","体","指","手","人差し指","指さす","上"],
+	"✋":["挙手","体","手"],
+	"🤚":["手の甲","体","挙げる"],
+	"🖐":["広げた手のひら","体","指","手","広げる"],
+	"🖖":["長寿と繁栄を","体","指","手","スポック","バルカン"],
+	"👋":["バイバイ","体","手","振る","やっほー","ヤッホー","こんにちは"],
+	"🤙":["電話の形の手","体","電話","手"],
+	"🫲":["左手","手","左","ひだり"],
+	"🫱":["右手","手","右","みぎ"],
+	"🫷":["左を押している手","辞退","ハイタッチ","左方向","押し付ける","断る","停止","待つ"],
+	"🫸":["右を押している手","辞退","ハイタッチ","押し付ける","断る","右方向","停止","待つ"],
+	"💪":["曲げた上腕二頭筋","力こぶ","体","マンガ","漫画","運動","筋肉","力","マッスル","マッチョ"],
+	"🦾":["メカニカルアーム","アクセシビリティ","義手","人口装具","体"],
+	"🖕":["中指を立てた手","体","指","手","中指"],
+	"🫵":["見ている人を指している人差し指","指す","あなた","指"],
+	"✍":["書いている手","体","手","書く"],
+	"🤳":["自撮り","カメラ","携帯","腕"],
+	"💅":["マニキュア","体","ケア","化粧品","コスメ","爪","ネイル"],
+	"🦵":["脚","体","キック","手足"],
+	"🦿":["機械の脚","アクセシビリティ","義足","人口装具","体"],
+	"🦶":["足","体","キック","踏みつける"],
+	"👄":["口","体","唇","クチビル"],
+	"🫦":["かんでいる唇","心配","怖い","浮気","神経質","不愉快","不安"],
+	"🦷":["歯","体","歯医者"],
+	"👅":["舌","体"],
+	"👂":["耳","体","鼻"],
+	"🦻":["補聴器を付けている耳","アクセシビリティ","補聴器","聞く","体","耳"],
+	"👃":["鼻","体"],
+	"👁":["目","体"],
+	"👀":["目","体","顔"],
+	"🧠":["脳","体","臓器","知的","賢い"],
+	"🫀":["解剖学的な心臓","解剖学","心臓学","心臓","臓器","脈"],
+	"🫁":["肺","息","呼気","吸入","臓器","呼吸"],
+	"🦴":["骨","体","骨格"],
+	"👤":["上半身のシルエット","上半身","シルエット"],
+	"👥":["上半身のシルエット","上半身","シルエット"],
+	"🗣":["喋る頭のシルエット","顔","頭","シルエット","しゃべる","話す"],
+	"🫂":["ハグしている人たち","さようなら","こんにちは","ハグ","ありがとう"],
+	"👶":["赤ちゃん"],
+	"👧":["女の子","少女","処女","おとめ座","星座","子供"],
+	"🧒":["子供","人","少年","少女"],
+	"👦":["男の子","少年","子供"],
+	"👩":["女性","女","おんな"],
+	"🧑":["成人向け","人","大人","男性","女性","女","男","おとこ","おんな"],
+	"👨":["男性","口ひげ","男","おとこ"],
+	"👩‍🦱":["女性,巻き毛","巻き毛","髪","女性","女","おんな"],
+	"🧑‍🦱":["人,巻き毛","巻き毛","髪"],
+	"👨‍🦱":["男性,巻き毛","巻き毛","髪","男性","男","おとこ"],
+	"👩‍🦰":["女性,赤毛","赤","髪","女性","女","おんな"],
+	"🧑‍🦰":["人,赤毛","赤","髪"],
+	"👨‍🦰":["男性,赤毛","赤","髪","男性","男","おとこ"],
+	"👱‍♀️":["女性,金髪","ブロンド","髪","女","おんな"],
+	"👱":["人,金髪","金髪","ブロンド","髪"],
+	"👱‍♂️":["男性,金髪","ブロンド","髪","男","男性","おとこ"],
+	"👩‍🦳":["女性,白髪","白","髪","女性","女","おんな"],
+	"🧑‍🦳":["人,白髪","白","髪"],
+	"👨‍🦳":["男性,白髪","白","髪","男性","男","おとこ"],
+	"👩‍🦲":["女性,禿","禿","女性","女","おんな"],
+	"🧑‍🦲":["人,禿","禿"],
+	"👨‍🦲":["男性,禿","禿","男性","男","おとこ"],
+	"🧔‍♀️":["ひげのある女性","あごひげ","ひげを生やした","女性","女","おんな"],
+	"🧔":["あごひげのある人","あごひげ","ひげを生やした"],
+	"🧔‍♂️":["ひげのある男性","あごひげ","ひげを生やした","男性","男","おとこ"],
+	"👵":["おばあさん","おばあちゃん","老人","女性","女","おんな"],
+	"🧓":["高齢者","人","男性","女性","女","男","おとこ","おんな"],
+	"👴":["おじいさん","おじいちゃん","老人","男","おとこ","男性"],
+	"👲":["スカルキャップをかぶっている人","中国帽","帽子"],
+	"👳‍♀️":["ターバンを巻いている女性","ターバン","女性","女","おんな"],
+	"👳":["ターバンを巻いている人","ターバン"],
+	"👳‍♂️":["ターバンを巻いている男性","ターバン","男","おとこ","男性"],
+	"🧕":["ヘッドスカーフをかぶった女性","ヘッドスカーフ","ヒジャブ","マンティラ","ティチェル","バンダナ","頭のスカーフ","女性","女","おんな"],
+	"👮‍♀️":["女性警察官","警察官","警官","警察","女性","女","おんな"],
+	"👮":["警察官","警官","警察"],
+	"👮‍♂️":["男性警察官","警察官","警官","警察","男","おとこ","男性"],
+	"👩‍🚒":["女性消防士","火","火事","消防","消防士","女性","女","おんな"],
+	"🧑‍🚒":["消防士","火事"],
+	"👨‍🚒":["男性消防士","火","火事","消防","消防士","男","おとこ","男性"],
+	"👷‍♀️":["女性の建設作業員","工事","建設","作業員","女性","女","おんな"],
+	"👷":["建設作業員","工事","建設","作業員"],
+	"👷‍♂️":["男性の建設作業員","建設","作業員","男性","男","おとこ"],
+	"👩‍🏭":["男性の工場作業員","工場","工業","作業員","女性","女","おんな"],
+	"🧑‍🏭":["工場作業員","工場","工業","溶接"],
+	"👨‍🏭":["男性の工場作業員","工場","工業","作業員","男","おとこ","男性"],
+	"👩‍🔧":["女性整備士","職人","配管工","電気技師","修理人","女性","女","おんな"],
+	"🧑‍🔧":["整備士","職人","配管工","電気技師","修理人"],
+	"👨‍🔧":["男性整備士","職人","配管工","電気技師","修理人","男","おとこ","男性"],
+	"👩‍🌾":["女性の農業従事者","農場労働者","牧場主","庭師","農家","女性","女","おんな"],
+	"🧑‍🌾":["農業従事者","農場労働者","牧場主","庭師","農家"],
+	"👨‍🌾":["男性の農業従事者","農場労働者","牧場主","庭師","農家","男","おとこ","男性"],
+	"👩‍🍳":["女性の料理人","食品","サービス","シェフ","コック","料理人","料理","女性","女","おんな"],
+	"🧑‍🍳":["料理人","食品","サービス","シェフ","コック","料理"],
+	"👨‍🍳":["男性の料理人","食品","サービス","シェフ","コック","料理人","料理","男","おとこ","男性"],
+	"👩‍🎤":["男性シンガー","音楽","ミュージシャン","ロック","ロッカー","ロックスター","芸能人","女性","女","おんな"],
+	"🧑‍🎤":["歌手","音楽","ミュージシャン","ロック","ロッカー","ロックスター","芸能人"],
+	"👨‍🎤":["男性シンガー","音楽","ミュージシャン","ロック","ロッカー","ロックスター","芸能人","男","おとこ","男性"],
+	"👩‍🎨":["女性アーティスト","芸術","アート","芸術家","アーティスト","絵画","画家","女性","女","おんな"],
+	"🧑‍🎨":["アーティスト","芸術","アート","芸術家","絵画","画家"],
+	"👨‍🎨":["男性アーティスト","芸術","アート","芸術家","アーティスト","絵画","画家","男","おとこ","男性"],
+	"👩‍🏫":["女性の教師","教育","先生","教授","教師","講師","女性","女","おんな"],
+	"🧑‍🏫":["教師","教育","先生","教授","講師"],
+	"👨‍🏫":["男性の教師","教育","先生","教授","教師","講師","男","おとこ","男性"],
+	"👩‍🎓":["女子生徒","学生","卒業生","教育","学校","女性","女","おんな"],
+	"🧑‍🎓":["生徒","学生","卒業生","教育","学校"],
+	"👨‍🎓":["男子生徒","学生","卒業生","教育","学校","男","おとこ","男性"],
+	"👩‍💼":["男性会社員","オフィス","会計士","銀行家","管理職","顧問","事務員","アナリスト","女性","女","おんな"],
+	"🧑‍💼":["会社員","オフィス","会計士","銀行家","管理職","顧問","事務員","アナリスト"],
+	"👨‍💼":["男性会社員","オフィス","会計士","銀行家","管理職","顧問","事務員","アナリスト","男","おとこ","男性"],
+	"👩‍💻":["女性技術者","テクノロジー","ソフトウェア","エンジニア","プログラマー","ラップトップ","ノートパソコン","女性","女","おんな"],
+	"🧑‍💻":["技術者","テクノロジー","ソフトウェア","エンジニア","プログラマー","ラップトップ","ノートパソコン"],
+	"👨‍💻":["男性技術者","テクノロジー","ソフトウェア","エンジニア","プログラマー","ラップトップ","ノートパソコン","男","おとこ","男性"],
+	"👩‍🔬":["女性科学者","科学者","化学者","技術者","数学者","物理学者","生物学者","検査技師","女性","女","おんな"],
+	"🧑‍🔬":["科学者","化学者","技術者","数学者","物理学者","生物学者","検査技師"],
+	"👨‍🔬":["男性科学者","科学者","化学者","技術者","数学者","物理学者","生物学者","検査技師","男","おとこ","男性"],
+	"👩‍🚀":["女性宇宙飛行士","宇宙","星","月","惑星","女性","女","おんな"],
+	"🧑‍🚀":["宇宙飛行士","宇宙","星","月","惑星"],
+	"👨‍🚀":["男性宇宙飛行士","宇宙","星","月","惑星","男","おとこ","男性"],
+	"👩‍⚕️":["女性医療関係者","医師","内科医","医学博士","看護師","歯科医","医療専門家","療法士","女性","女","おんな"],
+	"🧑‍⚕️":["医療関係者","医師","内科医","医学博士","看護師","歯科医","医療専門家","療法士"],
+	"👨‍⚕️":["男性医療関係者","医師","内科医","医学博士","看護師","歯科医","医療専門家","療法士","男","おとこ","男性"],
+	"👩‍⚖️":["女性裁判官","裁判官","法廷","裁判所","法律","女性","女","おんな"],
+	"🧑‍⚖️":["裁判官","法廷","裁判所","法律"],
+	"👨‍⚖️":["男性裁判官","裁判官","法廷","裁判所","法律","男","おとこ","男性"],
+	"👩‍✈️":["女性パイロット","パイロット","飛行機","操縦士","航空","女性","女","おんな"],
+	"🧑‍✈️":["パイロット","飛行機","操縦士","航空"],
+	"👨‍✈️":["男性パイロット","パイロット","飛行機","操縦士","航空","男","おとこ","男性"],
+	"💂‍♀️":["女性警備員","警備員","警備","女性","女","おんな"],
+	"💂":["警備員","警備"],
+	"💂‍♂️":["男性警備員","警備員","警備","男","おとこ","男性"],
+	"🥷":["忍者","戦士","隠された","ステルス"],
+	"🕵️‍♀️":["女性の探偵","探偵","刑事","スパイ","女性","女","おんな"],
+	"🕵":["探偵","刑事","スパイ"],
+	"🕵️‍♂️":["男性の探偵","探偵","刑事","スパイ","男","おとこ","男性"],
+	"🤶":["ミセス・クロース","イベント","お祝い","クリスマス","母","サンタ","クロース","女性","女","おんな"],
+	"🧑‍🎄":["ミクスクロース","アクティビティ","お祝い","クリスマス","サンタ","クロース"],
+	"🎅":["サンタクロース","イベント","お祝い","クリスマス","父","サンタ","クロース","男","おとこ","男性"],
+	"👼":["天使の赤ちゃん","天使","赤ちゃん","顔","おとぎ話","ファンタジー"],
+	"👸":["お姫さま","おとぎ話","ファンタジー","女王","女性","女","おんな"],
+	"🫅":["王冠をかぶった人","おとぎ話","ファンタジー","国王","貴族","王","王族"],
+	"🤴":["王子様","おとぎ話","ファンタジー","王","男","おとこ","男性"],
+	"👰":["ベールを付けた女性","花嫁","ベール","結婚式","女性","女","おんな"],
+	"👰‍♀️":["ベールを付けた人","花嫁","ベール","結婚式"],
+	"👰‍♂️":["ベールを付けた男性","花嫁","ベール","ウェディング","男性","男","おとこ"],
+	"🤵‍♀️":["タキシードの女性","タキシード","ウェディング","女性","女","おんな"],
+	"🤵":["タキシードを着る人","花婿","タキシード","ウェディング"],
+	"🤵‍♂️":["タキシードの男性","花婿","タキシード","ウェディング","男性","男","おとこ"],
+	"🩷":["ピンクのハート","かわいい","ハート","好き","愛","ピンク"],
+	"🩵":["ライトブルーのハート","シアン","ハート","ライトブルー","コガモ"],
+	"🩶":["グレーのハート","グレー","ハート","シルバー","スレート"],
+	"🕴️‍♀️":["宙に浮いたスーツの女性","ビジネス","スーツ","女性","女","おんな"],
+	"🕴":["宙に浮いたスーツの人","ビジネス","スーツ"],
+	"🕴️‍♂️":["宙に浮いたスーツの男性","ビジネス","スーツ","男","おとこ","男性"],
+	"🦸‍♀️":["女性のスーパーヒーロー","空想","善","ヒロイン","超大国","女性","女","おんな"],
+	"🦸":["スーパーヒーロー","空想","善","ヒーロー","ヒロイン","超大国"],
+	"🦸‍♂️":["男性のスーパーヒーロー","空想","善","ヒーロー","超大国","男性","男","おとこ"],
+	"🦹‍♀️":["女性の悪党","空想","悪","犯罪","悪事","超大国","悪役","女性","女","おんな"],
+	"🦹":["悪党","空想","悪","犯罪","悪事","超大国","悪役"],
+	"🦹‍♂️":["男性の悪党","空想","悪","犯罪","悪事","超大国","悪役","男性","男","おとこ"],
+	"🧙‍♀️":["女性の魔法使い","空想","魔女","女の魔法使い","女性","女","おんな"],
+	"🧙":["魔法使い","空想","魔術師","男の魔法使い"],
+	"🧙‍♂️":["男性の魔法使い","空想","魔術師","男の魔法使い","男性","男","おとこ"],
+	"🧝‍♀️":["女性の小人","空想","小人","先のとがった耳","女性","女","おんな"],
+	"🧝":["小人","空想","先のとがった耳"],
+	"🧝‍♂️":["男性の小人","空想","小人","先のとがった耳","男性","男","おとこ"],
+	"🧚‍♀️":["女性の妖精","空想","ティターニア","ウィングス","女性","女","おんな"],
+	"🧚":["妖精","空想","ティターニア","ウィングス"],
+	"🧚‍♂️":["男性の妖精","空想","オベロン","小妖精","男性","男","おとこ"],
+	"🧞‍♀️":["女性の精霊","空想","精霊","女性","女","おんな"],
+	"🧞":["精霊","空想"],
+	"🧞‍♂️":["男性の精霊","空想","精霊","男性","男","おとこ"],
+	"🧜‍♀️":["女性の人魚","空想","女性","女","おんな"],
+	"🧜":["人魚","空想"],
+	"🧜‍♂️":["男性の人魚","空想","人魚","男性","男","おとこ"],
+	"🧌":["釣り","おとぎ話","ファンタジ","モンスター"],
+	"🧛‍♀️":["女性の吸血鬼","空想","アンデッド","女性","女","おんな"],
+	"🧛":["吸血鬼","空想","ドラキュラ","アンデッド"],
+	"🧛‍♂️":["男性の吸血鬼","空想","ドラキュラ","アンデッド","男性","男","おとこ"],
+	"🧟‍♀️":["女性のゾンビ","空想","アンデッド","女性","女","おんな"],
+	"🧟":["ゾンビ","空想","アンデッド"],
+	"🧟‍♂️":["男性のゾンビ","空想","アンデッド","男性","男","おとこ"],
+	"🙇‍♀️":["深くお辞儀する女性","謝罪","お辞儀","ジェスチャー","ごめんなさい","女性","女","おんな"],
+	"🙇":["深くお辞儀した人","謝罪","お辞儀","ジェスチャー","ごめんなさい"],
+	"🙇‍♂️":["深くお辞儀する男性","謝罪","お辞儀","ジェスチャー","ごめんなさい","男","おとこ","男性"],
+	"💁‍♀️":["案内する女性","手","助け","情報","ずうずうしい","女性","女","おんな"],
+	"💁":["案内する人","手","助け","情報","ずうずうしい","女性","女","おんな"],
+	"💁‍♂️":["案内する男性","手","助け","情報","ずうずうしい","男","おとこ","男性"],
+	"🙅‍♀️":["NGサインの女性","禁じる","ジェスチャー","手","だめ","ダメ","禁止","女性","女","おんな"],
+	"🙅":["NGサインの人","禁じる","ジェスチャー","手","だめ","ダメ","禁止"],
+	"🙅‍♂️":["NGサインの男性","禁じる","ジェスチャー","手","だめ","ダメ","禁止","男","おとこ","男性"],
+	"🙆‍♀️":["OKサインの女性","ジェスチャー","手","ok","女性","女","おんな"],
+	"🙆":["OKサインの人","ジェスチャー","手","OK"],
+	"🙆‍♂️":["OKサインの男性","ジェスチャー","手","ok","男","おとこ","男性"],
+	"🤷‍♀️":["肩をすくめる女性","疑い","無知","無関心","肩をすくめる","女性","女","おんな"],
+	"🤷":["肩をすくめる人","疑い","無知","無関心","肩をすくめる"],
+	"🤷‍♂️":["肩をすくめる男性","疑い","無知","無関心","肩をすくめる","男","おとこ","男性"],
+	"🙋‍♀️":["片手を上げて喜ぶ女性","ジェスチャー","手","幸せ","しあわせ","挙げる","女性","女","おんな"],
+	"🙋":["片手を上げて喜ぶ人","ジェスチャー","手","幸せ","しあわせ","挙げる"],
+	"🙋‍♂️":["片手を上げて喜ぶ男性","ジェスチャー","手","幸せ","しあわせ","挙げる","男","おとこ","男性"],
+	"🤦‍♀️":["顔を押さえる女性","不信","憤慨","顔","手のひら","女性","女","おんな"],
+	"🤦":["手のひらを顔に当てる人","不信","憤慨","顔","手のひら"],
+	"🤦‍♂️":["顔を押さえる男性","不信","憤慨","顔","手のひら","男","おとこ","男性"],
+	"🧏‍♀️":["耳が不自由な女性","アクセシビリティ","耳が不自由","女性","女","おんな"],
+	"🧏":["耳が不自由な人","アクセシビリティ","耳が不自由"],
+	"🧏‍♂️":["耳が不自由な男性","アクセシビリティ","耳が不自由","男性","男","おとこ"],
+	"🙎‍♀️":["ふくれっ面の女性","ジェスチャー","ふくれっ面","ふくれっつら","女性","女","おんな"],
+	"🙎":["怒った顔の人","ジェスチャー","ふくれっ面","ふくれっつら"],
+	"🙎‍♂️":["ふくれっ面の男性","ジェスチャー","ふくれっ面","ふくれっつら","男","おとこ","男性"],
+	"🙍‍♀️":["顔をしかめた女性","しかめ面","ジェスチャー","悲しい","女性","女","おんな"],
+	"🙍":["不満な顔の人","しかめ面","ジェスチャー","悲しい"],
+	"🙍‍♂️":["顔をしかめた男性","しかめ面","ジェスチャー","悲しい","男性","男","おとこ"],
+	"💇‍♀️":["髪を切られている女性","理髪師","美容師","美容","散髪","ヘアカット","美容院","女性","女","おんな"],
+	"💇":["髪を切られている人","理髪師","美容師","美容","散髪","ヘアカット","美容院"],
+	"💇‍♂️":["髪を切られている男性","理髪師","美容師","美容","散髪","ヘアカット","美容院","男","おとこ","男性"],
+	"💆‍♀️":["フェイスマッサージを受ける女性","マッサージ","サロン","女性","女","おんな"],
+	"💆":["フェイスマッサージを受ける人","マッサージ","サロン"],
+	"💆‍♂️":["フェイスマッサージを受ける男性","マッサージ","サロン","男","おとこ","男性"],
+	"🤰":["妊婦","妊娠","赤ちゃん","女性","女","おんな","腹","ふくれた","ふっくらした"],
+	"🫄":["妊娠した人","腹","ふくれた","ふっくらした","妊娠","赤ちゃん"],
+	"🫃":["妊娠している男性","腹","ふくれた","ふっくらした","妊娠","赤ちゃん","男性","男","おとこ"],
+	"🤱":["母乳","胸","赤ちゃん","赤ん坊","乳児","幼児","母","子供","保育","ミルク","女性","女","おんな"],
+	"👩‍🍼":["赤ちゃんにご飯をあげる女性","赤ちゃん","乳児","子供","授乳","ミルク","ボトル","女性","女","おんな"],
+	"🧑‍🍼":["赤ちゃんにご飯をあげる人","赤ちゃん","乳児","子供","授乳","ミルク","ボトル"],
+	"👨‍🍼":["赤ちゃんにご飯をあげる男性","赤ちゃん","乳児","子供","授乳","ミルク","ボトル","男性","男","おとこ"],
+	"🧎‍♀️":["膝立ちしている女性","膝","膝立ち","女性","女","おんな"],
+	"🧎":["膝立ちしている人","膝","膝立ち"],
+	"🧎‍♂️":["膝立ちしている男性","膝","膝立ち","男性","男","おとこ"],
+	"🧍‍♀️":["立っている女性","立つ","スタンディング","女性","女","おんな"],
+	"🧍":["立っている人","立ち","スタンディング"],
+	"🧍‍♂️":["立っている男性","立つ","スタンディング","男性","男","おとこ"],
+	"🚶‍♀️":["歩く女性","ハイキング","歩行者","歩く","ウォーキング","女性","女","おんな"],
+	"🚶":["歩く人","ハイキング","歩行者","歩く","ウォーキング"],
+	"🚶‍♂️":["歩く男性","ハイキング","歩行者","歩く","ウォーキング","男","おとこ","男性"],
+	"👩‍🦯":["白杖を持った女性","アクセシビリティ","目が不自由","女性","女","おんな"],
+	"🧑‍🦯":["白杖を持った人","アクセシビリティ","目が不自由"],
+	"👨‍🦯":["白杖を持った男性","アクセシビリティ","目が不自由","男性","男","おとこ"],
+	"🏃‍♀️":["走る女性","マラソン","ランナー","ランニング","女性","女","おんな"],
+	"🏃":["走る人","マラソン","ランナー","ランニング"],
+	"🏃‍♂️":["走る男性","マラソン","ランナー","ランニング","男","おとこ","男性"],
+	"👩‍🦼":["電動車いすに座っている女性","アクセシビリティ","車いす","女性","女","おんな"],
+	"🧑‍🦼":["電動車いすに座っている人","アクセシビリティ","車いす"],
+	"👨‍🦼":["電動車いすに座っている男性","アクセシビリティ","車いす","男性","男","おとこ"],
+	"👩‍🦽":["手動車いすに座っている女性","アクセシビリティ","車いす","女性","女","おんな"],
+	"🧑‍🦽":["手動車いすに座っている人","アクセシビリティ","車いす"],
+	"👨‍🦽":["手動車いすに座っている男性","アクセシビリティ","車いす","男性","男","おとこ"],
+	"💃":["女性ダンサー","ダンス","踊る","ダンサー","女性","女","おんな"],
+	"🕺":["男性ダンサー","ダンス","踊る","ダンサー","男","おとこ","男性"],
+	"👯‍♀️":["バニーガール","うさぎ耳","ダンサー","女性","女","おんな"],
+	"👯":["うさぎ耳の人","うさぎ耳","ダンサー"],
+	"👯‍♂️":["うさぎ耳の男性","うさぎ耳","ダンサー","男","おとこ","男性"],
+	"👫":["手をつないだ男女","カップル","手","つなぐ","男","女","男女","おとこ","おんな"],
+	"👭":["手をつないだ女性","カップル","手","つなぐ","女性","女","おんな","プライド","lgbt","レズビアン"],
+	"👬":["手をつないだ男性","カップル","手","つなぐ","男性","男","おとこ","プライド","lgbt","ゲイ"],
+	"🧑‍🤝‍🧑":["手をつないだ人たち","カップル","手","握る"],
+	"👩‍❤️‍👨":["ハートのカップル (女性、男性)","カップル","ハート","愛","恋愛","男","女","男女","おとこ","おんな"],
+	"👩‍❤️‍👩":["ハートのカップル (女性、女性)","カップル","ハート","愛","恋愛","女性","女","おんな","プライド","lgbt","レズビアン"],
+	"💑":["ハートのカップル","カップル","ハート","愛","恋愛","男","女","男女","おとこ","おんな"],
+	"👨‍❤️‍👨":["ハートのカップル (男性、男性)","カップル","ハート","愛","恋愛","男性","男","おとこ","プライド","lgbt","ゲイ"],
+	"👩‍❤️‍💋‍👨":["キス (女性、男性)","カップル","キス","ハート","愛","恋愛","男","女","男女","おとこ","おんな"],
+	"👩‍❤️‍💋‍👩":["キス (女性、女性)","カップル","キス","ハート","愛","恋愛","女性","女","おんな","プライド","lgbt","ゲイ"],
+	"💏":["キス","カップル","キス","ハート","愛","恋愛","男","女","男女","おとこ","おんな"],
+	"👨‍❤️‍💋‍👨":["キス (男性、男性)","カップル","キス","ハート","愛","恋愛","男性","男","おとこ","プライド","lgbt","ゲイ"],
+	"👪":["家族","父親","母親","男","女","男女","おとこ","おんな","男の子","こども"],
+	"👨‍👩‍👧":["家族 (男性、女性、女の子)","父親","母親","男","女","男女","おとこ","おんな","女の子","こども"],
+	"👨‍👩‍👧‍👦":["家族 (男性、女性、女の子、男の子)","父親","母親","男","女","男女","おとこ","おんな","男の子","女の子","こども"],
+	"👨‍👩‍👦‍👦":["家族 (男性、女性、男の子、男の子)","父親","母親","男","女","男女","おとこ","おんな","男の子","こども"],
+	"👨‍👩‍👧‍👧":["家族 (男性、女性、女の子、女の子)","父親","母親","男","女","男女","おとこ","おんな","女の子","こども"],
+	"👩‍👩‍👦":["家族 (女性、女性、男の子)","家族","母親","女性","女","おんな","男の子","子供","プライド","lgbt","レズビアン"],
+	"👩‍👩‍👧":["家族 (女性、女性、女の子)","家族","母親","女性","女","おんな","女の子","子供","プライド","lgbt","レズビアン"],
+	"👩‍👩‍👧‍👦":["家族 (女性、女性、女の子、男の子)","家族","母親","女性","女","おんな","男の子","女の子","子供","プライド","lgbt","レズビアン"],
+	"👩‍👩‍👦‍👦":["家族 (女性、女性、男の子、男の子)","家族","母親","女性","女","おんな","男の子","子供","プライド","lgbt","レズビアン"],
+	"👩‍👩‍👧‍👧":["家族 (女性、女性、女の子、女の子)","家族","母親","女性","女","おんな","女の子","子供","プライド","lgbt","レズビアン"],
+	"👨‍👨‍👦":["家族 (男性、男性、男の子)","家族","父親","男性","男","おとこ","男の子","子供","プライド","lgbt","ゲイ"],
+	"👨‍👨‍👧":["家族 (男性、男性、女の子)","家族","父親","男性","男","おとこ","女の子","子供","プライド","lgbt","ゲイ"],
+	"👨‍👨‍👧‍👦":["家族 (男性、男性、女の子、男の子)","家族","父親","男性","男","おとこ","男の子","女の子","子供","プライド","lgbt","ゲイ"],
+	"👨‍👨‍👦‍👦":["家族 (男性、男性、男の子、男の子)","家族","父親","男性","男","おとこ","男の子","子供","プライド","lgbt","ゲイ"],
+	"👨‍👨‍👧‍👧":["家族 (男性、男性、女の子、女の子)","家族","父親","男性","男","おとこ","女の子","子供","プライド","lgbt","ゲイ"],
+	"👩‍👦":["家族(女性、男の子)","家族","母親","女性","女","おんな","男の子","こども"],
+	"👩‍👧":["家族(女性、女の子)","家族","母親","女性","女","おんな","女の子","こども"],
+	"👩‍👧‍👦":["家族(女性、女の子、男の子)","家族","母親","女性","女","男性","女の子","男の子","こども"],
+	"👩‍👦‍👦":["家族(女性、男の子、男の子)","家族","母親","女性","女","おんな","男の子","こども"],
+	"👩‍👧‍👧":["家族(女性、女の子、女の子)","家族","母親","女性","女","おんな","女の子","こども"],
+	"👨‍👦":["家族(男性、男の子)","父親","男","おとこ","男性","男の子","こども"],
+	"👨‍👧":["家族(男性、女の子)","父親","男","男女","女の子","こども"],
+	"👨‍👧‍👦":["家族(男性、女の子、男の子)","父親","男","おとこ","男性","男の子","女の子","こども"],
+	"👨‍👦‍👦":["家族(男性、男の子、男の子)","父親","男","おとこ","男性","男の子","こども"],
+	"👨‍👧‍👧":["家族(男性、女の子、女の子)","父親","男","男女","女の子","こども"],
+	"👚":["レディースウェア","服","女性","おんな"],
+	"👕":["Tシャツ","服","シャツ"],
+	"🥼":["白衣","服","医者","実験","科学者"],
+	"🦺":["安全ベスト","緊急","安全","ベスト"],
+	"🧥":["コート","服","ジャケット"],
+	"👖":["ジーンズ","服","パンツ","ズボン"],
+	"👔":["ネクタイ","服"],
+	"👗":["ドレス","服"],
+	"👘":["着物","服","和服"],
+	"🥻":["サリー","服","ドレス"],
+	"🩱":["ワンピース","服","水着","スイミングウェア","水泳"],
+	"👙":["ビキニ","服","水泳"],
+	"🩲":["ブリーフ","服","水着","スイミングウェア","水泳","下着"],
+	"🩳":["ショーツ","服","水着","スイミングウェア","水泳","下着"],
+	"💄":["口紅","化粧品","コスメ","化粧","メイク"],
+	"💋":["キスマーク","ハート","キス","唇","クチビル","マーク","恋愛","ロマンス"],
+	"👣":["足あと","体","服","足跡","あしあと"],
+	"🧦":["靴下","服","ソックス","一組"],
+	"🩴":["ゴム製サンダル","ビーチ","サンダル","草履"],
+	"👠":["ハイヒール","服","ヒール","靴","女性","おんな"],
+	"👡":["レディースサンダル","服","サンダル","靴","女性","おんな"],
+	"👢":["レディースブーツ","ブーツ","服","靴","女性","おんな"],
+	"🥿":["レディースフラットシューズ","服","バレエフラット","スリッポン","スリッパ"],
+	"👞":["メンズシューズ","服","男性","おとこ","靴"],
+	"👟":["運動靴","運動","服","シューズ","スニーカー"],
+	"🩰":["バレエシューズ","服","シューズ","バレエ","ダンス"],
+	"🥾":["ハイキングブーツ","服","バックパック","ブーツ","キャンプ","ハイキング"],
+	"🧢":["キャップ","服","野球","ハット","帽子"],
+	"👒":["レディースハット","服","帽子","女性","おんな"],
+	"🎩":["シルクハット","アクティビティ","服","エンターテインメント","娯楽","帽子","トップス"],
+	"🎓":["卒業式の角帽","アクティビティ","帽子","お祝い","服","卒業","ハット"],
+	"👑":["冠","服","王冠","王","女王"],
+	"⛑":["白十字のヘルメット","救助","十字","顔","帽子","ヘルメット"],
+	"🪖":["軍隊のヘルメット","軍","ヘルメット","軍隊","軍人","兵士"],
+	"🎒":["ランドセル","アクティビティ","鞄","バッグ","学生鞄","学校"],
+	"👝":["ポーチ","鞄","バッグ","服"],
+	"👛":["財布","服","コイン"],
+	"👜":["ハンドバッグ","鞄","バッグ","服"],
+	"💼":["ブリーフケース"],
+	"👓":["眼鏡","服","目","メガネ","アイウェア"],
+	"🕶":["サングラス","暗い","目","眼鏡","メガネ"],
+	"🥽":["ゴーグル","服","目の保護","水泳","溶接"],
+	"🧣":["スカーフ","服","首"],
+	"🧤":["手袋","服","手"],
+	"💍":["指輪","ダイヤモンド","恋愛","ロマンス"],
+	"🌂":["閉じた傘","服","雨","傘","天気"],
+	"☂":["傘","服","雨","天気"],
+	"🐶":["イヌの顔","犬","イヌ","顔","ペット"],
+	"🐱":["ネコの顔","猫","ネコ","顔","ペット"],
+	"🐭":["ネズミの顔","顔","ネズミ"],
+	"🐹":["ハムスターの顔","顔","ハムスター","ペット"],
+	"🐰":["ウサギの顔","バニー","顔","ペット","ウサギ"],
+	"🐻":["クマの顔","熊","クマ","顔"],
+	"🧸":["テディベア","玩具","ビロード","ぬいぐるみ","おもちゃ"],
+	"🐼":["パンダの顔","顔","パンダ","熊"],
+	"🐻‍❄️":["シロクマ","顔","北極","熊","白"],
+	"🐨":["コアラ","熊","有袋類","オーストラリア"],
+	"🐯":["トラの顔","顔","虎","トラ"],
+	"🦁":["ライオンの顔","顔","しし座","ライオン","星座"],
+	"🐮":["ウシの顔","牛","ウシ","顔"],
+	"🐷":["ブタの顔","顔","豚","ブタ"],
+	"🐽":["ブタの鼻","顔","鼻","豚","ブタ"],
+	"🐸":["カエルの顔","顔","蛙","カエル"],
+	"🐵":["サルの顔","顔","猿","サル"],
+	"🙈":["見ざる","悪い","顔","禁じる","ジェスチャー","猿","サル","だめ","ダメ","禁止","見る"],
+	"🙉":["聞かざる","悪い","顔","禁じる","ジェスチャー","聞く","サル","ない","なし","禁止"],
+	"🙊":["言わざる","悪い","顔","禁じる","ジェスチャー","猿","サル","ない","なし","禁止","話す"],
+	"🐒":["サル","猿"],
+	"🦍":["ゴリラ"],
+	"🦧":["オランウータン","類人猿"],
+	"🐔":["ニワトリ"],
+	"🐧":["ペンギン"],
+	"🐦":["鳥"],
+	"🐦‍⬛":["黒い鳥","鳥","黒","カラス","ワタリガラス","ミヤマガラス"],
+	"🐤":["ヒヨコ","赤ちゃん","ひよこ"],
+	"🐣":["ひよこ","赤ちゃん","孵化"],
+	"🐥":["正面を向いたヒヨコ","赤ちゃん","ひよこ"],
+	"🐺":["オオカミの顔","顔","オオカミ"],
+	"🦊":["キツネの顔","顔","キツネ"],
+	"🦝":["アライグマ","顔","好奇心が強い","ずる賢い"],
+	"🐗":["イノシシ","豚"],
+	"🐴":["ウマの顔","顔","馬"],
+	"🦓":["シマウマ","顔"],
+	"🦒":["キリン","顔"],
+	"🦌":["シカ"],
+	"🫎":["ヘラジカ","動物","枝角","エルク","哺乳類"],
+	"🦘":["カンガルー","オーストラリア","ジャンプ","有袋類"],
+	"🦥":["怠惰","なまける","遅い"],
+	"🦦":["カワウソ","釣り","ふざける"],
+	"🦫":["ビーバー","ダム"],
+	"🦄":["ユニコーンの顔","顔","ユニコーン"],
+	"🐝":["ミツバチ","ハチ","昆虫"],
+	"🐛":["虫","昆虫"],
+	"🦋":["チョウ","蝶","昆虫","美しい"],
+	"🐌":["カタツムリ"],
+	"🪲":["甲虫","虫","昆虫"],
+	"🐞":["テントウムシ","カブトムシ","昆虫","てんとう虫"],
+	"🐜":["アリ","蟻","昆虫"],
+	"🦗":["クリケット","コオロギ","バッタ目","昆虫"],
+	"🪳":["ゴキブリ","昆虫","害虫"],
+	"🕷":["クモ","昆虫","蜘蛛"],
+	"🕸":["クモの巣","クモ","巣"],
+	"🦂":["サソリ","さそり座","さそり","星座"],
+	"🦟":["蚊","病気","熱","昆虫","マラリア","ウイルス"],
+	"🪰":["ハエ","害虫","昆虫","蛆虫"],
+	"🪱":["蠕虫","環形動物","ミミズ","寄生虫"],
+	"🦠":["微生物","アメーバ","バクテリア","ウイルス"],
+	"🐢":["カメ"],
+	"🐍":["ヘビ","運搬人","へびつかい座","蛇","星座"],
+	"🦎":["トカゲ","爬虫類"],
+	"🐙":["タコ","蛸"],
+	"🦑":["イカ","軟体動物","烏賊"],
+	"🪼":["クラゲ","焼く","無脊椎動物","ゼリー","海","痛い","刺毛"],
+	"🦞":["ロブスター","ビスク","爪","シーフード"],
+	"🦀":["カニ","かに座","蟹","星座"],
+	"🦐":["エビ","貝","小さい"],
+	"🦪":["カキ","真珠","ダイビング"],
+	"🐠":["熱帯魚","魚","熱帯"],
+	"🐟":["魚","うお座","星座"],
+	"🐡":["フグ","魚"],
+	"🐬":["イルカ","ひれ"],
+	"🦈":["サメ","魚"],
+	"🦭":["アザラシ","アシカ"],
+	"🐳":["潮吹きクジラ","顔","潮吹き","クジラ"],
+	"🐋":["クジラ"],
+	"🐊":["ワニ"],
+	"🐆":["ヒョウ"],
+	"🐅":["トラ","虎"],
+	"🐃":["スイギュウ","水牛","水"],
+	"🐂":["雄牛","牡牛","おうし座","星座"],
+	"🐄":["ウシ","牛"],
+	"🦬":["バイソン","バッファロー","群れ","ヴィセント"],
+	"🐪":["ヒトコブラクダ","ラクダ","こぶ"],
+	"🐫":["フタコブラクダ","フタコブ","ラクダ","こぶ"],
+	"🦙":["ラマ","アルパカ","グアナコ","ビクーニャ","ウール"],
+	"🐘":["ゾウ","象"],
+	"🦏":["サイ"],
+	"🦛":["カバ"],
+	"🦣":["マンモス","絶滅","大型","牙","毛に覆われた"],
+	"🐐":["ヤギ","やぎ座","星座"],
+	"🐏":["仔羊","おひつじ座","ヒツジ","星座"],
+	"🐑":["ヒツジ","雌羊"],
+	"🐎":["馬","競馬","レース"],
+	"🫏":["ロバ","動物","ブーロ","哺乳類","ラバ"],
+	"🐖":["ブタ","雌豚"],
+	"🦇":["コウモリ","吸血鬼"],
+	"🐓":["おんどり"],
+	"🦃":["七面鳥(鳥)","七面鳥","鳥"],
+	"🕊":["平和の鳩","鳥","鳩","飛行","平和"],
+	"🦅":["ワシ","鳥"],
+	"🦆":["アヒル","鳥"],
+	"🪿":["ガチョウ","鳥","家禽","警笛の音"],
+	"🦢":["白鳥","鳥","白鳥の雄","醜いアヒルの子"],
+	"🦉":["フクロウ","鳥","賢い"],
+	"🦩":["フラミンゴ","熱帯","鮮やか"],
+	"🦚":["オスのクジャク","鳥","メスのクジャク"],
+	"🦜":["オウム","鳥","海賊"],
+	"🦤":["ドードー","鳥","絶滅"],
+	"🪽":["羽","天使","航空","鳥","飛行","神話"],
+	"🪶":["羽毛","鳥","軽い","羽"],
+	"🐕":["イヌ","犬","ペット"],
+	"🦮":["盲導犬","アクセシビリティ","目が不自由","犬","ガイド"],
+	"🐕‍🦺":["介助犬","アクセシビリティ","支援","犬","サービス"],
+	"🐩":["プードル","イヌ","犬"],
+	"🐈":["ネコ","猫","ペット"],
+	"🐈‍⬛":["黒猫","黒","猫","ペット","ハロウィーン"],
+	"🐇":["ウサギ","バニー","ペット"],
+	"🐀":["ネズミ"],
+	"🐁":["ネズミ"],
+	"🐿":["シマリス"],
+	"🦨":["スカンク","悪臭","臭う"],
+	"🦡":["アナグマ","ラーテル","ねだる"],
+	"🦔":["ハリネズミ","顔"],
+	"🐾":["動物の足あと","足","跡"],
+	"🐉":["ドラゴン","おとぎ話"],
+	"🐲":["ドラゴンの顔","ドラゴン","顔","おとぎ話"],
+	"🦕":["竜脚類","ブラキオサウルス","ブロントサウルス","ディプロドクス","恐竜"],
+	"🦖":["ティラノサウルス","Tレックス","恐竜"],
+	"🌵":["サボテン","植物"],
+	"🎄":["クリスマスツリー","アクティビティ","お祝い","クリスマス","エンターテイメント","ツリー"],
+	"🌲":["常緑樹","常緑","植物","木"],
+	"🌳":["落葉樹","落葉性","植物","落葉","木"],
+	"🌴":["ヤシの木","ヤシ","植物","木"],
+	"🪴":["鉢植え","植物","観葉植物"],
+	"🌱":["苗木","植物","若い"],
+	"🌿":["ハーブ","葉","植物"],
+	"☘":["クローバー","植物"],
+	"🍀":["四つ葉のクローバー","4","クローバー","四","葉","植物"],
+	"🎍":["門松","アクティビティ","竹","お祝い","日本","松","植物"],
+	"🎋":["七夕","アクティビティ","旗","お祝い","エンターテイメント","日本","木"],
+	"🍃":["風になびく葉","吹く","はためく","葉","植物","風"],
+	"🍂":["落ち葉","落下","葉","植物"],
+	"🍁":["カエデの葉","落下","葉","カエデ","植物"],
+	"🌾":["稲穂","稲束","穂","植物","米"],
+	"🪺":["卵のある巣","巣作り","鳥の巣","卵"],
+	"🪹":["空の巣","巣作り","鳥の巣"],
+	"🌺":["ハイビスカス","花","植物"],
+	"🌻":["ヒマワリ","花","植物","太陽","ひまわり"],
+	"🌹":["バラ","花","植物"],
+	"🥀":["しおれた花","花","しおれた"],
+	"🌷":["チューリップ","花","植物"],
+	"🌼":["花","植物"],
+	"🌸":["桜","花","植物"],
+	"🪷":["ハス","仏教","花","ヒンドゥー教","インド","清浄","ベトナム"],
+	"🪻":["ヒアシンス","ブルーボンネット","花","ラベンダー","ルピナス","ノウルーズ","紫","キンギョソウ"],
+	"💐":["花束","花","植物","ロマンス"],
+	"🍄":["キノコ","植物"],
+	"🐚":["巻き貝","貝"],
+	"🪸":["サンゴ","大洋","礁"],
+	"🌎":["アメリカ大陸","アメリカ","地球","世界"],
+	"🌍":["ヨーロッパとアフリカ地域","アフリカ","地球","ヨーロッパ","世界"],
+	"🌏":["アジアとオーストラリア","アジア","オーストラリア","地球","世界"],
+	"🌕":["満月","月","宇宙","天気"],
+	"🌖":["寝待月","十三夜","月","宇宙","欠け","天気"],
+	"🌗":["下弦の月","月","弦","宇宙","天気"],
+	"🌘":["欠けていく三日月","三日月","月","宇宙","欠け","天気"],
+	"🌑":["新月","晦","月","宇宙","天気"],
+	"🌒":["満ちていく三日月","三日月","月","宇宙","上弦","天気"],
+	"🌓":["上弦の月","月","弦","宇宙","天気"],
+	"🌔":["十三夜月","十三夜","月","宇宙","上弦","天気"],
+	"🌙":["三日月","月","宇宙","天気"],
+	"🌚":["顔つき新月","顔","月","宇宙","天気"],
+	"🌝":["顔つき満月","明るい","顔","満ちた","月","宇宙","天気"],
+	"🌛":["顔つき上弦の月","顔","月","弦","宇宙","天気"],
+	"🌜":["顔がある下弦の月","顔","月","弦","宇宙","天気"],
+	"⭐":["中くらいの星","星"],
+	"🌟":["光る星","きらめき","赤い光","輝く","輝き","星"],
+	"💫":["くらくら","漫画","めまい","星"],
+	"✨":["キラキラ","エンターテイメント","輝き","星"],
+	"☄":["彗星","宇宙"],
+	"🪐":["環のある惑星","宇宙","惑星","土星"],
+	"🌞":["顔つき太陽","明るい","顔","宇宙","太陽","天気"],
+	"☀️":["太陽の光","明るい","光線","宇宙","太陽","晴天","天気"],
+	"🌤":["太陽と小さな雲","雲","太陽","天気"],
+	"⛅":["晴れ時々曇り","雲","太陽","天気"],
+	"🌥":["晴れのち曇り","雲","太陽","天気"],
+	"🌦":["晴れのち曇り時々雨","雲","雨","太陽","天気"],
+	"☁️":["雲","天気"],
+	"🌧":["雨雲","雲","雨","天気"],
+	"⛈":["雷雨","雲","雨","雷","天気"],
+	"🌩":["雷雲","雲","雷","天気"],
+	"⚡":["高電圧記号","危険","電気","雷","電圧","ビリビリ"],
+	"🔥":["炎","火","道具"],
+	"💥":["衝突マーク","どかーん","衝突","漫画"],
+	"❄️":["雪の結晶","冷たい","雪","天気"],
+	"🌨":["雪雲","雲","冷","雪","天気"],
+	"☃":["雪だるま","冷","雪","天気"],
+	"⛄":["雪だるま","冷","雪","天気"],
+	"🌬":["風が吹いている","風が吹く","雲","顔","天気","風"],
+	"💨":["ダッシュ","漫画","走る"],
+	"🌪":["竜巻雲","雲","竜巻","天気","旋風"],
+	"🌫":["霧","雲","天気"],
+	"🌈":["虹","雨","レインボー","天気","プライド","lgbt"],
+	"☔":["雨と傘","衣類","しずく","雨","傘","天気"],
+	"💧":["雫","ぞっとする","漫画","したたり","汗","天気"],
+	"💦":["汗マーク","漫画","濡れている","汗"],
+	"🌊":["波","海","水","天気"],
+	"🍏":["青りんご","リンゴ","フルーツ","果物","緑","植物"],
+	"🍎":["赤いリンゴ","リンゴ","フルーツ","果物","植物","赤"],
+	"🍐":["梨","フルーツ","果物","植物"],
+	"🍊":["みかん","フルーツ","果物","オレンジ","植物","赤橙色"],
+	"🍋":["レモン","柑橘類","フルーツ","果物","植物"],
+	"🍌":["バナナ","フルーツ","果物","植物"],
+	"🍉":["スイカ","フルーツ","果物","植物"],
+	"🍇":["ブドウ","フルーツ","果物","植物"],
+	"🍓":["イチゴ","ベリー","フルーツ","果物","植物"],
+	"🍈":["メロン","フルーツ","果物","植物"],
+	"🍒":["さくらんぼ","フルーツ","果物","植物"],
+	"🫐":["ブルーベリー","ベリー","ビルベリー","青","フルーツ"],
+	"🍑":["桃","フルーツ","果物","植物"],
+	"🥭":["マンゴー","熱帯","フルーツ"],
+	"🍍":["パイナップル","フルーツ","果物","植物"],
+	"🥥":["ココナッツ","フルーツ"],
+	"🥝":["キウイフルーツ","フルーツ","果物","キウイ"],
+	"🍅":["トマト","植物","野菜"],
+	"🥑":["アボカド","フルーツ","果物"],
+	"🫒":["オリーブ","フルーツ"],
+	"🍆":["ナス","茄子","植物","野菜"],
+	"🌶":["トウガラシ","辛い","コショウ","植物"],
+	"🫑":["ピーマン","唐辛子","コショウ","植物","野菜"],
+	"🥒":["キュウリ","ピクルス","野菜"],
+	"🥬":["葉っぱの緑","チンゲン菜","キャベツ","ケール","レタス"],
+	"🥦":["ブロッコリー","野菜"],
+	"🫛":["エンドウ豆のさや","豆","枝豆","マメ科","エンドウ豆","さや","野菜"],
+	"🧄":["にんにく","野菜","植物","香味料"],
+	"🧅":["玉ねぎ","野菜","植物","香味料"],
+	"🌽":["トウモロコシ","コーン","植物"],
+	"🥕":["ニンジン","野菜"],
+	"🥗":["グリーンサラダ","緑","サラダ"],
+	"🥔":["ジャガイモ","野菜"],
+	"🍠":["焼き芋","ジャガイモ","焼き","スイーツ"],
+	"🌰":["栗","植物"],
+	"🥜":["ピーナッツ","ナッツ","野菜"],
+	"🫘":["豆","食べ物","腎臓","マメ"],
+	"🍯":["ハニーポット","はちみつ","ポット","スイーツ"],
+	"🍞":["パン","ローフ"],
+	"🥐":["クロワッサン","パン","三日月","ロール","フレンチ"],
+	"🥖":["フランスパン","パン","フレンチ"],
+	"🫓":["フラットブレッド","アレパ","ラヴァシュ","ナン","ピタ"],
+	"🥨":["プレッツェル","ソフトプレッツェル","プレッツェルツイスト","パン"],
+	"🥯":["ベーグル","パン","クリームチーズ","ひと塗り"],
+	"🥞":["パンケーキ","クレープ","ホットケーキ"],
+	"🧇":["ワッフル","ホットケーキ"],
+	"🧀":["チーズ"],
+	"🍗":["ターキー","骨","ニワトリ","脚","家禽"],
+	"🍖":["骨付き肉","骨","肉"],
+	"🥩":["一切れの肉","肉","切り身","ラムチョップ","豚","ステーキ"],
+	"🍤":["エビフライ","フライ","エビ","小エビ","てんぷら"],
+	"🥚":["卵"],
+	"🍳":["料理","卵","フライパン","鍋"],
+	"🥓":["ベーコン","肉"],
+	"🍔":["ハンバーガー","バーガー"],
+	"🍟":["フライドポテト","フライド","ポテト"],
+	"🌭":["ホットドッグ","フランクフルトソーセージ","ホットドッグソーセージ","ソーセージ","ウィンナー","レッドホット"],
+	"🍕":["ピザ","チーズ","1枚"],
+	"🍝":["スパゲッティ","パスタ"],
+	"🥪":["サンドウィッチ","パン","野菜","チーズ","肉","デリ"],
+	"🌮":["タコス","メキシコ"],
+	"🌯":["ブリトー","メキシコ"],
+	"🫔":["タマーレ","タマーリ","メキシカン","包まれた"],
+	"🥙":["フラットブレッドサンド","ファラフェル","フラットブレッド","ジャイロ","ケバブ","詰め物"],
+	"🧆":["ファラフェル","ひよこ豆"],
+	"🍜":["どんぶり","麺","ラーメン","蒸し加熱","スープ"],
+	"🥘":["パエリア","キャセロール","鍋","浅い"],
+	"🍲":["なべ","鍋","シチュー"],
+	"🫕":["フォンデュ","チーズ","チョコレート","フォデュ","溶けた","ポット","スイス"],
+	"🥫":["缶詰","かんづめ","保存用食品"],
+	"🫙":["瓶","香辛料","容器","空","ソース","貯蔵"],
+	"🧂":["塩","香辛料","シェーカー"],
+	"🧈":["バター","乳製品"],
+	"🫚":["ショウガ","ビール","根","スパイス"],
+	"🍥":["なると","固形の食べ物","魚","練り物"],
+	"🍣":["寿司"],
+	"🍱":["弁当箱","弁当","箱"],
+	"🍛":["カレーライス","カレー","ご飯"],
+	"🍙":["おにぎり","日本","米"],
+	"🍚":["ごはん","料理","米"],
+	"🍘":["せんべい","米"],
+	"🥟":["餃子","ギョウザ"],
+	"🍢":["おでん","シーフード","串","スティック"],
+	"🍡":["団子","デザート","日本","串","スティック","スイーツ"],
+	"🍧":["かき氷","デザート","氷","スイーツ"],
+	"🍨":["アイスクリーム","クリーム","デザート","氷","スイーツ"],
+	"🍦":["ソフトクリーム","クリーム","デザート","氷","アイスクリーム","ソフト","スイーツ"],
+	"🍰":["ショートケーキ","ケーキ","デザート","ペイストリー","スライス","スイーツ"],
+	"🎂":["バースデーケーキ","誕生日","ケーキ","お祝い","デザート","ペイストリー","スイーツ"],
+	"🧁":["カップケーキ","ベーカリー","スイーツ","デザート","ペイストリー"],
+	"🥧":["パイ","デザート","スイーツ"],
+	"🍮":["カスタード","デザート","プリン","スイーツ"],
+	"🍭":["ペロペロキャンディー","キャンディ","デザート","ロリポップキャンディ","スイーツ"],
+	"🍬":["アメ","デザート","スイーツ"],
+	"🍫":["チョコレート","バー","デザート","スイーツ"],
+	"🍿":["ポップコーン"],
+	"🍩":["ドーナツ","デザート","スイーツ"],
+	"🍪":["クッキー","デザート","甘い"],
+	"🥠":["おみくじ入りクッキー","フォーチュンクッキー"],
+	"🥮":["月餅","秋","祭"],
+	"☕":["ホットドリンク","飲料","コーヒー","飲み物","温かい","蒸気","お茶"],
+	"🍵":["湯のみ","飲料","カップ","飲み物","お茶","湯飲み"],
+	"🫖":["ティーポット","ドリンク","ポット","ティー","ケトル"],
+	"🥣":["ボウルとスプーン","朝食","シリアル","お粥","オートミール","ポリッジ","食器"],
+	"🍼":["哺乳瓶","赤ちゃん","ボトル","ドリンク","ミルク"],
+	"🥤":["カップとストロー","ジュース","ソーダ","モルト","ソフトドリンク","水","食器"],
+	"🧋":["タピオカティー","バブル","ミルク","パール","ティー","ボバ","タピオカ","モミ"],
+	"🧃":["飲料ボックス","ジュース","飲料","ボックス","ドリンク","ストロー"],
+	"🧉":["マテ","ドリンク","ボンビリヤ","イエルバ"],
+	"🥛":["コップに入った牛乳","ドリンク","グラス","ミルク"],
+	"🫗":["流れ込む液体","飲み物","空","グラス","こぼれる"],
+	"🍺":["ビール","バー","飲む","マグカップ"],
+	"🍻":["乾杯","バー","ビール","カチン","飲み物","マグカップ"],
+	"🍷":["ワイングラス","バー","飲料","飲み物","グラス","ワイン"],
+	"🥂":["グラスで乾杯","祝う","カチン","飲み物","グラス"],
+	"🥃":["タンブラー","グラス","酒","ショット","ウイスキー","ウィスキー","バーボン"],
+	"🍸":["カクテルグラス","バー","カクテル","飲み物","グラス"],
+	"🍹":["トロピカルドリンク","バー","飲み物","トロピカル"],
+	"🍾":["瓶と飛び出す栓","バー","ボトル","シャンパン","シャンペン","シャンパーニュ","コルク","飲み物","飛び出す","パーティー"],
+	"🍶":["とっくりとおちょこ","バー","飲料","ボトル","カップ","飲み物","酒"],
+	"🧊":["角氷","氷","立方体","冷たい","氷山"],
+	"🥄":["スプーン","食器"],
+	"🍴":["フォークとナイフ","調理","フォーク","ナイフ","食器"],
+	"🍽":["フォークとナイフとプレート","調理","フォーク","ナイフ","プレート","食器"],
+	"🥢":["箸","はし"],
+	"🥡":["テイクアウトボックス","テイクアウト","容器","お持ち帰り"],
+	"⚽":["サッカーボール","ボール","サッカー"],
+	"🏀":["バスケットボール","ボール","バスケットリング"],
+	"🏈":["アメリカンフットボール","アメリカン","ボール","フットボール"],
+	"⚾":["野球","ボール"],
+	"🥎":["ソフトボール","ボール","試合","スポーツ"],
+	"🎾":["テニスボール","ボール","ラケット","テニス"],
+	"🏐":["バレーボール","ボール","試合"],
+	"🏉":["ラグビー","ボール","フットボール"],
+	"🎱":["ビリヤード","8","エイトボール","ボール","エイト","ゲーム"],
+	"🥏":["空飛ぶ円盤","ディスク","アルティメット","ゴルフ","試合","スポーツ","フリスビー"],
+	"🪃":["ブーメラン","オーストラリア","逆戻り","跳ね返り"],
+	"🏓":["卓球のラケットとボール","ボール","バット","試合","パドル","卓球"],
+	"🏸":["バドミントンのラケットとシャトル","バドミントン","バーディー","試合","ラケット","シャトル"],
+	"🥅":["ゴールネット","ゴール","ネット"],
+	"🏒":["アイスホッケーのスティックとパック","試合","ホッケー","氷","パック","スティック"],
+	"🏑":["フィールドホッケーのスティックとボール","ボール","フィールド","試合","ホッケー","スティック"],
+	"🏏":["クリケットのバットとボール","ボール","フィールド","クリケット","試合"],
+	"🥍":["ラクロス","ボール","スティック","試合","スポーツ"],
+	"🥌":["カーリングストーン","カーリング","ストーン"],
+	"⛳":["ゴルフのカップ","ピンフラッグ","ゴルフ","ホール"],
+	"🏹":["弓矢","射手","矢","弓","射手座","道具","星座"],
+	"🎣":["釣竿と魚","エンターテイメント","魚","棒"],
+	"🤿":["ダイビングマスク","ダイビング","スキューバ","シュノーケル"],
+	"🥊":["ボクシンググローブ","ボクシング","グローブ"],
+	"🥋":["道着","柔道","空手","武道","テコンドー","ユニフォーム"],
+	"⛸":["アイススケート","氷"],
+	"🎿":["スキーとスキーブーツ","スキー","雪"],
+	"🛷":["そり","ソリ","ルージュ","トボガン"],
+	"⛷":["スキー","雪"],
+	"🏂":["スノーボーダー","スキー","雪","スノーボード"],
+	"🏋️‍♀️":["ウエイトを持ち上げる女性","挙げ","重量","女性","女","おんな"],
+	"🏋":["ウエイトを持ち上げる人","挙げ","重量"],
+	"🏋️‍♂️":["ウエイトを持ち上げる男性","挙げ","重量","男","おとこ","男性"],
+	"🤺":["フェンシングをする人","剣士","剣術","剣"],
+	"🤼‍♀️":["レスリングをする女性","レスリング","レスリング選手","女性","女","おんな"],
+	"🤼":["レスリングをする人たち","レスリング","レスリング選手"],
+	"🤼‍♂️":["レスリングをする男性","レスリング","レスリング選手","男","おとこ","男性"],
+	"🤸‍♀️":["側転をする女性","側方転回","体操","女性","女","おんな"],
+	"🤸":["側転をする人","側方転回","体操"],
+	"🤸‍♂️":["側転をする男性","側方転回","体操","男","おとこ","男性"],
+	"⛹️‍♀️":["ボールをバウンドさせる女性","ボール","女性","女","おんな"],
+	"⛹":["ボールをバウンドさせる人","ボール"],
+	"⛹️‍♂️":["ボールをバウンドさせる男性","ボール","男","おとこ","男性"],
+	"🤾‍♀️":["ハンドボールをする女性","ボール","ハンドボール","女性","女","おんな"],
+	"🤾":["ハンドボールをする人","ボール","ハンドボール"],
+	"🤾‍♂️":["ハンドボールをする男性","ボール","ハンドボール","男","おとこ","男性"],
+	"🧗‍♀️":["クライミングしている女性","クライミング","ロック","女性","女","おんな"],
+	"🧗":["クライミングしている人","クライミング","ロック"],
+	"🧗‍♂️":["クライミングしている男性","クライミング","ロック","男性","男","おとこ"],
+	"🏌️‍♀️":["ゴルフをする女性","ボール","ゴルフ","ゴルファー","ゴルフする","女性","女","おんな"],
+	"🏌":["ゴルフをする人","ボール","ゴルフ","ゴルファー","ゴルフする"],
+	"🏌️‍♂️":["ゴルフをする男性","ボール","ゴルフ","ゴルファー","ゴルフする","男","おとこ","男性"],
+	"🧘‍♀️":["蓮華座の女性","瞑想","ヨガ","静穏","女性","女","おんな"],
+	"🧘":["蓮華座の人","瞑想","ヨガ","静穏"],
+	"🧘‍♂️":["蓮華座の男性","瞑想","ヨガ","静穏","男性","男","おとこ"],
+	"🧖‍♀️":["スチームルームにいる女性","サウナ","スチームルーム","ハマム","スチームバス","女性","女","おんな"],
+	"🧖":["スチームルームにいる人","サウナ","スチームルーム","ハマム","スチームバス"],
+	"🧖‍♂️":["スチームルームにいる男性","サウナ","スチームルーム","ハマム","スチームバス","男性","男","おとこ"],
+	"🏄‍♀️":["サーフィンをする女性","サーファー","サーフィン","波乗り","女性","女","おんな"],
+	"🏄":["サーフィンをする人","サーファー","サーフィン","波乗り"],
+	"🏄‍♂️":["サーフィンをする男性","サーファー","サーフィン","波乗り","男","おとこ","男性"],
+	"🏊‍♀️":["泳ぐ女性","泳ぐ","水泳","女性","女","おんな"],
+	"🏊":["水泳をする人","泳ぐ","水泳"],
+	"🏊‍♂️":["泳ぐ男性","泳ぐ","水泳","男","おとこ","男性"],
+	"🤽‍♀️":["水球をする女性","ポロ","水","水球","女性","女","おんな"],
+	"🤽":["水球をする人","ポロ","水","水球"],
+	"🤽‍♂️":["水球をする男性","ポロ","水","水球","男","おとこ","男性"],
+	"🚣‍♀️":["ボートを漕ぐ女性","ボート","漕ぎ船","乗り物","漕艇","女性","女","おんな"],
+	"🚣":["ボートをこぐ人","ボート","漕ぎ船","乗り物","漕艇"],
+	"🚣‍♂️":["ボートを漕ぐ男性","ボート","漕ぎ船","乗り物","漕艇","男","おとこ","男性"],
+	"🏇":["競馬","馬","騎手","競走馬"],
+	"🚴‍♀️":["自転車に乗る女性","自転車","自転車乗り","自転車に乗る人","サイクリスト","女性","女","おんな"],
+	"🚴":["自転車に乗る人","自転車","自転車乗り","サイクリスト"],
+	"🚴‍♂️":["自転車に乗る男性","自転車","自転車乗り","自転車に乗る人","サイクリスト","男","おとこ","男性"],
+	"🚵‍♀️":["マウンテンバイクに乗る女性","マウンテンバイクライダー","クロスバイク","自転車","自転車乗り","自転車に乗る人","サイクリスト","山","女性","女","おんな"],
+	"🚵":["マウンテンバイクに乗る人","マウンテンバイクライダー","クロスバイク","自転車","自転車乗り","自転車に乗る人","山"],
+	"🚵‍♂️":["マウンテンバイクに乗る男性","マウンテンバイクライダー","クロスバイク","自転車","自転車乗り","自転車に乗る人","サイクリスト","山","男","おとこ","男性"],
+	"🎽":["ランニングシャツと襷","ランニング","襷","シャツ"],
+	"🎖":["勲章","お祝い","メダル","軍事"],
+	"🏅":["スポーツのメダル","メダル"],
+	"🥇":["金メダル","1位","金","メダル","1","第1位"],
+	"🥈":["銀メダル","メダル","2位","銀","2","第2位"],
+	"🥉":["銅メダル","銅","メダル","3位","3","第3位"],
+	"🏆":["トロフィー","賞"],
+	"🏵":["バラ飾り","植物"],
+	"🎗":["リマインダーリボン","お祝い","リマインダー","リボン"],
+	"🎫":["きっぷ","アクティビティ","入場料","エンターテイメント","チケット"],
+	"🎟":["入場券","入場料","エンターテイメント","チケット"],
+	"🎪":["サーカス小屋","アクティビティ","サーカス","エンターテイメント","テント"],
+	"🤹‍♀️":["ジャグリングをする女性","天秤","ジャグリング","女性","女","おんな"],
+	"🤹":["ジャグリングをする人","バランス","ジャグリング"],
+	"🤹‍♂️":["ジャグリングをする男性","天秤","ジャグリング","男性","男","おとこ"],
+	"🎭":["舞台芸術","アクティビティ","芸術","エンターテイメント","仮面","舞台","シアター"],
+	"🎨":["絵の具パレット","アクティビティ","アート","エンターテイメント","美術館","絵画","パレット"],
+	"🎬":["カチンコ","アクティビティ","エンターテイメント","映画"],
+	"🎤":["マイク","アクティビティ","エンターテイメント","カラオケ","マイクロフォン"],
+	"🎧":["ヘッドホン","アクティビティ","イヤホン","エンターテイメント","ヘッドフォン"],
+	"🎼":["楽譜","アクティビティ","エンターテイメント","音楽"],
+	"🎹":["鍵盤","アクティビティ","エンターテイメント","楽器","キーボード","音楽","ピアノ"],
+	"🪗":["アコーディオン","コンサーティーナ","スクイーズボックス"],
+	"🥁":["ドラム","ドラムスティック","音楽"],
+	"🪘":["長いドラム","ビート","コンガ","ドラム","リズム","ジャンベ"],
+	"🪇":["マラカス","祝う","楽器","音楽","騒音","打楽器","ガタガタ","リズム","シェイク"],
+	"🎷":["サックス","アクティビティ","エンターテイメント","楽器","音楽","サクソフォーン"],
+	"🎺":["トランペット","アクティビティ","エンターテイメント","楽器","音楽"],
+	"🪈":["フルート","竹","横笛奏者","フルート奏者","音楽","パイプ","リコーダー","吹く","木管楽器"],
+	"🎸":["ギター","アクティビティ","エンターテイメント","楽器","音楽"],
+	"🪕":["バンジョー","アクティビティ","エンターテイメント","楽器","音楽"],
+	"🎻":["バイオリン","アクティビティ","エンターテイメント","楽器","音楽"],
+	"🎲":["サイコロ","さい","エンターテイメント","ゲーム"],
+	"🧩":["パズルのピース","手がかり","噛み合う","ピース","パズル","ジグソー"],
+	"♟️":["チェスのポーン","チェス","駒","ゲーム","捨て駒"],
+	"🎯":["的中","アクティビティ","ブル","ブルズアイ","ダーツ","エンターテイメント","目","試合","ヒット","標的"],
+	"🎳":["ボウリング","ボール","試合"],
+	"🪀":["ヨーヨー","おもちゃ","上下"],
+	"🪁":["凧","おもちゃ","飛ぶ","舞う"],
+	"🛝":["滑り台","遊園地","遊び"],
+	"🎮":["テレビゲーム","コントローラー","エンターテイメント","ゲーム","ビデオゲーム"],
+	"👾":["エイリアン","宇宙人","怪獣","異星人","顔","おとぎ話","ファンタジー","モンスター","宇宙","UFO"],
+	"🎰":["スロットマシン","アクティビティ","ゲーム","スロット"],
+	"🚗":["自動車","車","乗り物"],
+	"🚙":["キャンピングカー","レクリエーション","RV","乗り物"],
+	"🚕":["タクシー","乗り物"],
+	"🛺":["オートリキシャ","人力車","トゥクトゥク"],
+	"🚌":["バス","乗り物"],
+	"🚎":["トロリーバス","バス","路面電車","市街電車","乗り物"],
+	"🏎":["レーシングカー","車","競争"],
+	"🚓":["パトカー","車","パトロール","警察","乗り物"],
+	"🚑":["救急車","乗り物"],
+	"🚒":["消防車","エンジン","炎","トラック","乗り物"],
+	"🚐":["マイクロバス","バス","乗り物"],
+	"🛻":["ピックアップトラック","ピックアップ","トラック","乗り物"],
+	"🚚":["配達用トラック","配達","トラック","乗り物"],
+	"🚛":["トレーラー","大型トラック","セミ","トラック","乗り物"],
+	"🚜":["トラクター","乗り物"],
+	"🏍":["レースバイク","オートバイ","レース"],
+	"🛵":["スクーター","モーター"],
+	"🚲":["自転車","バイク","乗り物"],
+	"🦼":["電動車いす","アクセシビリティ","車いす"],
+	"🦽":["手動車いす","アクセシビリティ","車いす"],
+	"🛴":["キックボード","キック","スクーター"],
+	"🛹":["スケボー","スケート","ボード"],
+	"🛼":["ローラースケート","ローラー","スケート"],
+	"🛞":["車輪","円","タイヤ","回転"],
+	"🚨":["パトライト","車","光","警察","回転","乗り物","サイレン","警告"],
+	"🚔":["パトカー","車","対向車","警察","乗り物"],
+	"🚍":["バス","対向車","乗り物"],
+	"🚘":["対向車","自動車","車","乗り物"],
+	"🚖":["タクシー","対向車","乗り物"],
+	"🚡":["ロープウェイ","空中","ケーブル","車","ゴンドラ","トラムウェイ","乗り物"],
+	"🚠":["ロープウェイ","ケーブル","ゴンドラ","山","乗り物"],
+	"🚟":["高架鉄道","鉄道","乗り物"],
+	"🚃":["鉄道車両","車","電気","鉄道","列車","路面","トロリーバス","乗り物"],
+	"🚋":["路面電車","車","路面","トロリーバス","乗り物"],
+	"🚝":["モノレール","乗り物"],
+	"🚄":["新幹線","鉄道","高速","列車","乗り物"],
+	"🚅":["新幹線","弾丸","鉄道","高速","列車","乗り物"],
+	"🚈":["ライトレール","鉄道","乗り物"],
+	"🚞":["山岳鉄道","車","山","鉄道","乗り物"],
+	"🚂":["蒸気機関車","エンジン","機関車","鉄道","蒸気","列車","乗り物"],
+	"🚆":["電車","線路","乗り物"],
+	"🚇":["地下鉄","メトロ","乗り物"],
+	"🚊":["路面電車","トロリーバス","乗り物"],
+	"🚉":["駅","線路","電車","乗り物"],
+	"🚁":["ヘリコプター","乗り物"],
+	"🛩":["小型航空機","飛行機","乗り物"],
+	"✈️":["飛行機","乗り物"],
+	"🛫":["飛行機の離陸","飛行機","チェックイン","出発","乗り物"],
+	"🛬":["飛行機の着陸","飛行機","到着","着陸","乗り物"],
+	"🪂":["パラシュート","パラセール","スカイダイブ","ハンググライダー"],
+	"💺":["座席","椅子"],
+	"🛰":["サテライト","衛星","宇宙","乗り物"],
+	"🚀":["ロケット","宇宙","乗り物"],
+	"🛸":["空飛ぶ円盤","UFO","宇宙人","異星人","宇宙","空想"],
+	"🛶":["カヌー","ボート"],
+	"⛵":["ヨット","ボート","リゾート","海","乗り物"],
+	"🛥":["モーターボート","ボート","乗り物"],
+	"🚤":["スピードボート","ボート","乗り物"],
+	"⛴":["フェリー","ボート"],
+	"🛳":["旅客船","旅客","船","乗り物"],
+	"🚢":["船","乗り物"],
+	"🛟":["救命浮き輪","浮き輪","ライフジャケット","ライフセーバー","救助","安全"],
+	"⚓":["いかり","船","ツール"],
+	"⛽":["ガソリンスタンド","燃料","ガソリン","給油機","サービスステーション"],
+	"🚧":["工事中","工事用フェンス","建設工事"],
+	"🚏":["バス停","バス","停止"],
+	"🚦":["縦向きの信号機","信号機","信号","交通"],
+	"🚥":["横向きの信号機","信号機","信号","交通"],
+	"🛑":["一時停止標識","八角形","標識","停止"],
+	"🎡":["観覧車","アクティビティ","遊園地","エンターテイメント","フェリス"],
+	"🎢":["ジェットコースター","アクティビティ","遊園地","コースター","エンターテイメント","ローラー"],
+	"🎠":["メリーゴーランド","アクティビティ","メリーゴーラウンド","エンターテイメント","馬"],
+	"🏗":["建設中","建物","建設"],
+	"🌁":["霧","天気"],
+	"🗼":["東京タワー","東京","タワー"],
+	"🏭":["工場","建物"],
+	"⛲":["噴水"],
+	"🎑":["お月見","アクティビティ","お祝い","授賞式","エンターテイメント","月"],
+	"⛰":["山"],
+	"🏔":["雪山","寒い","山","雪"],
+	"🗻":["富士山","山"],
+	"🌋":["火山","噴火","山","気象"],
+	"🗾":["日本列島","日本","地図"],
+	"🏕":["キャンプ"],
+	"⛺":["テント","キャンプ"],
+	"🏞":["国立公園","公園"],
+	"🛣":["高速道路","ハイウェイ","道路"],
+	"🛤":["線路","鉄道","電車"],
+	"🌅":["日の出","朝","太陽","天候"],
+	"🌄":["山からの日の出","朝","山","太陽","日の出","天候"],
+	"🏜":["砂漠"],
+	"🏖":["ビーチと傘","ビーチ","傘","パラソル"],
+	"🏝":["無人島","砂漠","島"],
+	"🌇":["ビルに沈む夕陽","建物","夕暮れ","太陽","夕日","天気"],
+	"🌆":["夕暮れの街並み","建物","街","夕暮れ","日暮れ","風景","太陽","夕日","天気"],
+	"🏙":["街並み","建物","街"],
+	"🌃":["星空","夜","星","天気"],
+	"🌉":["夜の橋","橋","夜","天気"],
+	"🌌":["天の川","宇宙","天気"],
+	"🌠":["流れ星","アクティビティ","落下","流れる","宇宙","星"],
+	"🎇":["線香花火","アクティビティ","お祝い","エンターテイメント","花火","キラキラ"],
+	"🎆":["花火","アクティビティ","お祝い","エンターテイメント"],
+	"🛖":["小屋","家","扇形庫","パオ"],
+	"🏘":["家","建物"],
+	"🏰":["西洋の城","建物","城","ヨーロッパ"],
+	"🏯":["日本の城","建物","城","日本"],
+	"🏟":["スタジアム"],
+	"🗽":["自由の女神","自由","像"],
+	"🏠":["家","建物","自宅"],
+	"🏡":["庭付きの家","建物","庭","自宅","家"],
+	"🏚":["廃墟","建物","廃屋","家"],
+	"🏢":["オフィスビル","建物"],
+	"🏬":["デパート","建物","店"],
+	"🏣":["日本の郵便局","建物","日本","ポスト"],
+	"🏤":["ヨーロッパの郵便局","建物","ヨーロッパ","ポスト"],
+	"🏥":["病院","建物","医師","薬"],
+	"🏦":["銀行","建物"],
+	"🏨":["ホテル","建物"],
+	"🏪":["コンビニエンスストア","建物","コンビニエンス","ストア"],
+	"🏫":["学校","建物"],
+	"🏩":["ラブホテル","建物","ホテル","ラブ"],
+	"💒":["結婚式","アクティビティ","チャペル","ロマンス"],
+	"🏛":["歴史的な建物","建物","歴史的な"],
+	"⛪":["教会","建物","クリスチャン","十字架","宗教"],
+	"🕌":["モスク","イスラム","ムスリム","宗教"],
+	"🛕":["ヒンドゥー教寺院","ヒンドゥー教","寺院","宗教"],
+	"🕍":["シナゴーグ","ユダヤ人","ユダヤ教","宗教","会堂"],
+	"🕋":["カアバ","イスラム","ムスリム","宗教"],
+	"⛩":["神社","宗教","神道"],
+	"⌚":["腕時計","時計"],
+	"📱":["携帯電話","携帯","コミュニケーション","モバイル","電話"],
+	"📲":["着信中","矢印","通話","携帯","コミュニケーション","モバイル","携帯電話","受信","電話"],
+	"💻":["パソコン","ノートパソコン","コンピューター","パーソナル"],
+	"⌨":["キーボード","コンピューター"],
+	"🖥":["デスクトップパソコン","コンピューター","デスクトップ"],
+	"🖨":["プリンター","コンピューター"],
+	"🖱":["3ボタンマウス","3","ボタン","コンピューター","マウス","三"],
+	"🖲":["トラックボール","コンピューター"],
+	"🕹":["ジョイスティック","エンターテイメント","ゲーム","ビデオゲーム"],
+	"🗜":["圧縮","ツール","欠陥"],
+	"💽":["MD","パソコン","光ディスク","エンターテイメント","ミニディスク","光学"],
+	"💾":["フロッピーディスク","コンピューター","ディスク","フロッピー"],
+	"💿":["CDディスク","ブルーレイ","CD","コンピューター","ディスク","DVD","光学"],
+	"📀":["DVD","ブルーレイ","CD","コンピューター","ディスク","エンターテイメント","光学"],
+	"📼":["ビデオテープ","エンターテイメント","テープ","VHS","ビデオ","ビデオカセット"],
+	"📷":["カメラ","エンターテイメント","ビデオ"],
+	"📸":["フラッシュを焚いたカメラ","カメラ","フラッシュ","ビデオ"],
+	"📹":["ビデオカメラ","カメラ","エンターテイメント","ビデオ"],
+	"🎥":["ビデオカメラ","アクティビティ","カメラ","シネマ","エンターテイメント","映画"],
+	"📽":["映写機","シネマ","娯楽","フィルム","映画","プロジェクター","ビデオ"],
+	"🎞":["フィルムのフレーム","シネマ","エンターテイメント","フィルム","フレーム","映画"],
+	"📞":["受話器","コミュニケーション","電話","受信機"],
+	"☎️":["電話","携帯電話"],
+	"📟":["ポケットベル","コミュニケーション","ポケベル"],
+	"📠":["FAX","コミュニケーション; fAX"],
+	"📺":["テレビ","エンターテイメント","TV","ビデオ"],
+	"📻":["ラジオ","エンターテイメント","ビデオ"],
+	"🎙":["スタジオマイク","マイク","音楽","スタジオ"],
+	"🎚":["調節バー","調節","音楽","バー"],
+	"🎛":["コントロールノブ","コントロール","つまみ","音楽"],
+	"⏱":["ストップウォッチ","時計"],
+	"⏲":["タイマー時計","時計","タイマー"],
+	"⏰":["目覚まし時計","アラーム","時計"],
+	"🕰":["置き時計","時計"],
+	"⏳":["砂時計","砂","タイマー"],
+	"⌛":["砂時計","砂","タイマー"],
+	"🧮":["そろばん","計算","カウント","集計表","数学"],
+	"📡":["衛星アンテナ","アンテナ","コミュニケーション","パラボラアンテナ","衛星"],
+	"🔋":["電池","バッテリー","電子","高エネルギー"],
+	"🪫":["バッテリー残量少","バッテリー","電子","低エネルギー"],
+	"🔌":["コンセント","電気","プラグ"],
+	"💡":["電球","漫画","電気","ひらめき","光"],
+	"🔦":["懐中電灯","電気","光","道具","たいまつ"],
+	"🕯":["ろうそく","光"],
+	"🧯":["消火器","消火","火","消す"],
+	"🗑":["ごみ箱","ゴミ箱","ごみ","ゴミ","缶","ビン"],
+	"🛢":["ドラム缶","ドラム","オイル"],
+	"🛒":["ショッピングカート","カート","ショッピング","トロリー"],
+	"💸":["羽の生えたお札","銀行","紙幣","請求書","ドル","飛ぶ","お金","羽"],
+	"💵":["ドル札","銀行","紙幣","お札","通貨","ドル","お金"],
+	"💴":["円記号の入った小切手","銀行","紙幣","お札","通貨","お金","円"],
+	"💶":["ユーロ札","銀行","紙幣","お札","通貨","ユーロ","お金"],
+	"💷":["ポンド札","銀行","紙幣","お札","通貨","お金","ポンド"],
+	"💰":["ドル袋","バッグ","ドル","お金"],
+	"🪙":["コイン","金","金属","お金","銀","宝"],
+	"💳":["クレジットカード","銀行","カード","クレジット","お金"],
+	"🪪":["身分証明書","資格情報","ID","ライセンス","セキュリティ"],
+	"🧾":["領収書","会計","簿記","証拠","証明"],
+	"💎":["宝石","ダイアモンド","ジュエル","ロマンス"],
+	"⚖":["はかり","天秤","公正","てんびん座","物差し","道具","重量","星座"],
+	"🦯":["白杖","アクセシビリティ","目が不自由"],
+	"🧰":["道具箱","胸","整備士","工具"],
+	"🔧":["レンチ","道具"],
+	"🪛":["ドライバー","ねじ","工具"],
+	"🔨":["ハンマー","道具"],
+	"⚒":["ハンマーとつるはし","ハンマー","つるはし","道具"],
+	"🛠":["ハンマーとレンチ","ハンマー","道具","レンチ"],
+	"⛏":["つるはし","採掘","道具"],
+	"🪓":["斧","たたき切り","手斧","割る","木材","工具"],
+	"🪚":["木工用のこぎり","大工","材木","のこぎり","工具"],
+	"🔩":["ナットとボルト","ボルト","ナット","道具"],
+	"⚙":["歯車","ギア","道具"],
+	"⛓":["鎖"],
+	"🪝":["フック","わな","いかさま","ペテン","誘惑","フィッシング","ツール"],
+	"🪜":["はしご","登る","横木","段","工具"],
+	"🧱":["れんが","粘土","建設","モルタル","壁"],
+	"🪨":["ロック","岩","建造物","重い","固体","石"],
+	"🪵":["木材","建造物","丸太","材木","木"],
+	"🔫":["水鉄砲","水","ピストル","噴射器","銃"],
+	"🧨":["爆竹","ダイナマイト","火薬","花火"],
+	"💣":["爆弾"],
+	"🔪":["包丁","キッチンナイフ","調理","ナイフ"],
+	"🗡":["短剣","ナイフ"],
+	"⚔":["交差した剣","交差","剣"],
+	"🛡":["盾"],
+	"🚬":["喫煙マーク","アクティビティ","喫煙"],
+	"⚰":["棺","死"],
+	"🪦":["墓石","墓地","死","墓","墓場","ハロウィーン"],
+	"⚱":["骨壷","死","葬儀"],
+	"🏺":["アンフォラ","みずがめ座","料理","飲み物","水差し","道具","星座"],
+	"🔮":["水晶玉","玉","水晶","おとぎ話","ファンタジー","占い","道具"],
+	"🪄":["魔法の杖","魔法","棒","魔女","魔法使い"],
+	"📿":["数珠状の祈りの用具","数珠","衣類","ネックレス","祈り","宗教"],
+	"🧿":["ナザールのお守り","数珠玉","お守り","邪視","ナザール","護符"],
+	"🪬":["ハムサ","お守り","ファティマ","手","メアリー","ミリアム","保護"],
+	"💈":["理髪店の看板柱","理髪店","床屋","散髪","看板柱"],
+	"🧲":["磁石","アトラクション","馬蹄"],
+	"⚗":["蒸留器","化学","実験","道具"],
+	"🧪":["試験管","化学者","化学","実験","実験室","科学"],
+	"🧫":["ペトリ皿","バクテリア","生物学者","生物学","文化","実験室"],
+	"🧬":["DNA","生物学者","進化","遺伝子","遺伝子学","生命"],
+	"🔭":["望遠鏡","ツール"],
+	"🔬":["顕微鏡","ツール"],
+	"🕳":["穴"],
+	"🩻":["X線","骨","医師","医療","骨格"],
+	"💊":["薬","医師","ピル","病気"],
+	"💉":["注射器","医師","薬","注射針","注射","病気","道具","ワクチン"],
+	"🩸":["血1滴","医師","薬","血液","生理"],
+	"🩹":["ガーゼ付きばんそうこう","医師","薬","バンドエイド","包帯","ばんそうこう"],
+	"🩺":["聴診器","医師","薬","心臓"],
+	"🌡":["温度計","天気","温度"],
+	"🩼":["松葉杖","杖","障碍","怪我","移動補助","棒"],
+	"🏷":["ラベル","荷札"],
+	"🔖":["ブックマーク","しおり","印"],
+	"🚽":["トイレ"],
+	"🪠":["プランジャー","フォースカップ","配管工","吸引","トイレ"],
+	"🚿":["シャワー","水"],
+	"🛁":["バスタブ","風呂","浴槽"],
+	"🛀":["風呂","浴槽"],
+	"🪮":["ヘアピック","アフロ","くし","髪","ピック"],
+	"🪥":["歯ブラシ","バスルーム","ブラシ","きれい","歯医者","衛生","歯"],
+	"🪒":["カミソリ","鋭い","髭剃り"],
+	"🧴":["ローションボトル","ローション","保湿剤","シャンプー","日焼け止め"],
+	"🧻":["ペーパーロール","ペーパータオル","トイレットペーパー"],
+	"🧼":["せっけん","棒","水浴び","クリーニング","泡","せっけん入れ"],
+	"🫧":["バブル","げっぷ","きれい","せっけん","水中"],
+	"🧽":["スポンジ","吸収","クリーニング","多孔性"],
+	"🧹":["ほうき","クリーニング","掃除","魔女"],
+	"🧺":["バスケット","農業","ランドリー","ピクニック"],
+	"🪣":["バケツ","たる","手桶","大だる"],
+	"🔑":["鍵","錠","パスワード"],
+	"🗝":["古い鍵","かぎ","鍵","錠","古い"],
+	"🪤":["ネズミ捕り器","餌","ネズミ","齧歯動物","輪なわ","わな"],
+	"🛋":["ソファーとランプ","ソファー","ホテル","ランプ"],
+	"🪑":["椅子","座席","座る"],
+	"🛌":["宿泊施設","寝る","ホテル","睡眠","ベッド"],
+	"🛏":["ベッド","ホテル","睡眠"],
+	"🚪":["ドア","扉"],
+	"🪞":["鏡","反射","反射体","反射鏡"],
+	"🪟":["窓","枠","新鮮な空気","ガラス","開口部","透明","視界"],
+	"🧳":["手荷物","パッキング","旅行","スーツケース"],
+	"🛎":["卓上ベル","ベル","ホテル"],
+	"🖼":["額に入った写真","アート","額縁","美術館","絵画","写真"],
+	"🧭":["コンパス","磁石","ナビゲーション","オリエンテーリング"],
+	"🗺":["世界地図","地図","世界"],
+	"⛱":["立てられたパラソル","雨","晴れ","傘","天気"],
+	"🪭":["折り畳み扇子","冷却","遠慮がち","ダンス","ファン","フラッター","熱","熱い","内気","広がる"],
+	"🗿":["モヤイ像","モアイ像","顔","像"],
+	"🛍":["買い物袋","鞄","ホテル","買い物"],
+	"🎈":["風船","アクティビティ","お祝い","エンターテイメント"],
+	"🎏":["こいのぼり","アクティビティ","鯉","お祝い","エンターテイメント","旗","吹流し"],
+	"🎀":["リボン","お祝い"],
+	"🧧":["赤い封筒","ギフト","幸運","紅包","利是","お金"],
+	"🎁":["プレゼント","箱","お祝い","エンターテイメント","贈り物","包装"],
+	"🎊":["くす玉","アクティビティ","お祝い","紙吹雪","エンターテイメント"],
+	"🎉":["クラッカー","アクティビティ","お祝い","エンターテイメント","パーティー","ジャーン"],
+	"🪅":["ピニャータ","お祝い","パーティー","ピナータ"],
+	"🪩":["ミラーボール","ダンス","ディスコ","輝き","パーティー"],
+	"🪆":["入れ子人形","人形","入れ子","ロシア"],
+	"🎎":["ひな祭り","アクティビティ","お祝い","人形","エンターテイメント","祭り","日本"],
+	"🎐":["風鈴","アクティビティ","鐘","お祝い","エンターテイメント","風"],
+	"🏮":["居酒屋の提灯","赤ちょうちん","居酒屋","日本","提灯","灯り","赤"],
+	"🪔":["ディヤランプ","ディヤ","ランプ","オイル"],
+	"✉️":["封筒","Eメール","電子メール"],
+	"📩":["メール受信中","矢印","コミュニケーション","下","Eメール","電子メール","封筒","手紙","メール","送る","送信"],
+	"📨":["メール受信","コミュニケーション","Eメール","電子メール","封筒","受け取る","手紙","メール","受信"],
+	"📧":["Eメール","コミュニケーション","電子メール","手紙","メール"],
+	"💌":["ラブレター","ハート","手紙","愛","メール","ロマンス"],
+	"📮":["ポスト","コミュニケーション","メール","郵便受け"],
+	"📪":["旗が下がっていて閉じている状態の郵便受け","閉じる","コミュニケーション","旗","下がった","メール","ポスト","郵便受け"],
+	"📫":["旗が上がっていて閉じている状態の郵便受け","閉じる","コミュニケーション","旗","メール","郵便受け","ポスト"],
+	"📬":["旗が上がっていて開いている状態の郵便受け","コミュニケーション","旗","メール","ポスト","開ける","郵便受け"],
+	"📭":["旗が下がっていて開いている郵便受け","コミュニケーション","旗","下げ","メール","メールボックス","開ける","郵便受け"],
+	"📦":["荷物","箱","コミュニケーション","パッケージ","小包"],
+	"📯":["郵便ラッパ","コミュニケーション","エンターテイメント","角","ポスト","郵便"],
+	"📥":["受信トレイ","箱","コミュニケーション","手紙","メール","受信","トレイ"],
+	"📤":["送信トレイ","箱","コミュニケーション","手紙","メール","送信","トレイ"],
+	"📜":["巻物","紙"],
+	"📃":["原稿","カール","ドキュメント","ページ"],
+	"📑":["ブックマークタブ","ブックマーク","マーク","マーカー","タブ"],
+	"📊":["棒グラフ","バー","チャート","グラフ"],
+	"📈":["上昇するグラフ","上昇チャート","チャート","グラフ","成長","トレンド","上向き"],
+	"📉":["下降するグラフ","下降チャート","チャート","下","グラフ","トレンド"],
+	"📄":["文書","ページ"],
+	"📅":["カレンダー","日付"],
+	"📆":["日めくりカレンダー","カレンダー"],
+	"🗓":["リングカレンダー","カレンダー","パッド","らせん状"],
+	"📇":["名刺フォルダ","カード","索引","ローラデックス"],
+	"🗃":["カードファイル","箱","カード","ファイル"],
+	"🗳":["投票用紙と投票箱","投票用紙","箱","票","投票"],
+	"🗄":["ファイル収納庫","収納","ファイル"],
+	"📋":["クリップボード"],
+	"🗒":["リングノート","ノート","パッド","らせん状"],
+	"📁":["フォルダ","ファイル"],
+	"📂":["開いたフォルダ","ファイル","フォルダ","開いた"],
+	"🗂":["仕切りカード","カード","仕切り","索引"],
+	"🗞":["丸めた新聞","ニュース","新聞","紙","丸めた"],
+	"📰":["新聞","コミュニケーション","ニュース","紙"],
+	"🪧":["プラカード","デモ","柵","抗議","看板"],
+	"📓":["ノート"],
+	"📕":["閉じた本","本","閉じている"],
+	"📗":["緑色の本","本","緑"],
+	"📘":["青い本","青","本"],
+	"📙":["オレンジ色の本","本","オレンジ"],
+	"📔":["装飾カバーのノート","本","カバー","装飾","ノート"],
+	"📒":["帳簿","元帳","ノート"],
+	"📚":["書籍","本"],
+	"📖":["開いた本","本","開いた"],
+	"🔗":["リンク"],
+	"📎":["クリップ","ペーパークリップ"],
+	"🖇":["繋がったペーパークリップ","コミュニケーション","リンク","ペーパークリップ"],
+	"✂️":["ハサミ","はさみ","道具"],
+	"📐":["三角定規","定規","配置","三角"],
+	"📏":["定規","直定規"],
+	"📌":["画鋲","ピン"],
+	"📍":["画鋲","ピン"],
+	"🧷":["安全ピン","おむつ","パンクロック"],
+	"🪡":["縫い針","刺しゅう","裁縫","縫い目","縫合","仕立て"],
+	"🧵":["スレッド","縫い編み","裁縫","糸巻","糸","手工芸"],
+	"🧶":["糸","ボール","かぎ針編み","ニット","手工芸"],
+	"🪢":["結び目","ロープ","絡んだ","ひも","より糸","ねじれ"],
+	"🔐":["コインロッカー","閉まっている","鍵","施錠","防犯"],
+	"🔒":["鍵","閉じられた","施錠"],
+	"🔓":["解錠","施錠","開ける"],
+	"🔏":["錠前とペン","インク","錠","ペン先","ペン","プライバシー"],
+	"🖊":["左下向きのボールペン","ボールペン","コミュニケーション","ペン"],
+	"🖋":["左下向きの万年筆","コミュニケーション","万年筆","ペン"],
+	"✒️":["ペン先","ペン"],
+	"📝":["メモ","コミュニケーション","鉛筆"],
+	"✏️":["鉛筆"],
+	"🖍":["左下向きのクレヨン","コミュニケーション","クレヨン"],
+	"🖌":["左下向きのブラシ","コミュニケーション","ペイントブラシ","絵"],
+	"🔍":["左向き虫眼鏡","眼鏡","拡大","検索","ツール"],
+	"🔎":["右向き虫眼鏡","眼鏡","拡大","検索","ツール"],
+	"❤️":["赤色のハート","ハート"],
+	"🧡":["オレンジ色のハート","ハート","オレンジ色"],
+	"💛":["黄色のハート","ハート","黄色"],
+	"💚":["緑のハート","ハート","緑"],
+	"💙":["青のハート","ハート","青"],
+	"💜":["紫のハート","ハート","紫"],
+	"🤎":["茶色のハート","ハート","茶色"],
+	"🖤":["黒いハート","ハート","黒","悪","悪者"],
+	"🤍":["白のハート","ハート","白"],
+	"💔":["割れたハート","ハート","壊れる","破局"],
+	"❣":["ハートのビックリマーク","ハート","ビックリマーク","記号"],
+	"💕":["2つのハート","ハート","愛"],
+	"💞":["回転するハート","ハート","回転"],
+	"💓":["鼓動するハート","ハート","鼓動","ドキドキ"],
+	"💗":["光るハート","ハート","ワクワク","光る","鼓動","緊張"],
+	"💖":["きらめくハート","ハート","ワクワク","キラキラ"],
+	"💘":["射抜かれたハート","ハート","矢","キューピッド","ロマンス"],
+	"💝":["リボン付きのハート","ハート","リボン","バレンタイン"],
+	"❤️‍🔥":["燃えているハート","ハート","火","燃える","愛","熱情","神聖なハート"],
+	"❤️‍🩹":["手当しているハート","ハート","健康になる","改善している","手当している","回復している","病み上がり","元気"],
+	"💟":["ハートのデコレーション","ハート"],
+	"☮":["ピースマーク","平和"],
+	"✝":["ラテン十字","クリスチャン","十字架","宗教"],
+	"☪":["星と三日月","イスラム","ムスリム","宗教"],
+	"🕉":["オームマーク","ヒンドゥー教","オーム","宗教"],
+	"☸":["法輪","仏教徒","ダーマ","宗教"],
+	"✡":["ダビデの星","ダビデ","ユダヤ人","ユダヤ教","宗教","星"],
+	"🔯":["六芒星","占い","星"],
+	"🕎":["ハヌッキーヤー","燭台","メノーラー","宗教"],
+	"☯":["陰陽","宗教","道","道家","陽","陰"],
+	"☦":["八端十字架","クリスチャン","十字架","宗教"],
+	"🪯":["カンダ","宗教","シーク教徒"],
+	"🛐":["礼拝所","宗教","礼拝"],
+	"⛎":["へびつかい座","運搬人","蛇","ヘビ","星座"],
+	"♈":["おひつじ座","仔羊","星座"],
+	"♉":["おうし座","牡牛","雄牛","星座"],
+	"♊":["ふたご座","ふたご","星座"],
+	"♋":["ガン","かに座","カニ","蟹","星座"],
+	"♌":["しし座","ライオン","星座"],
+	"♍":["おとめ座","乙女","処女","星座"],
+	"♎":["てんびん座","天秤","公正","はかり","星座"],
+	"♏":["さそり座","さそり","サソリ","星座"],
+	"♐":["いて座","射手","射手座","星座"],
+	"♑":["やぎ座","ヤギ","星座"],
+	"♒":["みずがめ座","運搬人","水","星座"],
+	"♓":["うお座","魚","星座"],
+	"🆔":["四角囲みID","ID","識別"],
+	"⚛":["元素記号","無神論者","原子"],
+	"⚕️":["アスクレピオスの杖","健康","世話","医師","薬","杖","ヘビ"],
+	"☢":["放射能標識","放射能"],
+	"☣":["バイオハザード標識","生物災害"],
+	"📴":["携帯電話電源オフ","携帯","コミュニケーション","モバイル","オフ","携帯電話","電話"],
+	"📳":["マナーモード","携帯","コミュニケーション","モバイル","モード","携帯電話","電話","バイブレーション"],
+	"🈶":["四角囲み有","日本語","あり"],
+	"🈚":["四角囲み無","四角囲み否","日本語","なし"],
+	"🈸":["四角囲み申","四角囲み適","中国語","申請"],
+	"🈺":["四角囲み営","中国語","営業"],
+	"🈷️":["四角囲み月","日本語","月極"],
+	"✴️":["八稜星","星"],
+	"🆚":["四角囲みVS","対","VS"],
+	"🉑":["丸囲み許可","丸囲み可","中国語","可能"],
+	"💮":["白い花","花","たいへんよくできました"],
+	"🉐":["丸囲み得","日本語","得"],
+	"㊙️":["丸囲み秘","中国語","表意文字","秘"],
+	"㊗️":["丸囲み祝","中国語","おめでとう","しゅく"],
+	"🈴":["四角囲みの合","四角囲み合","中国語","合格","適合"],
+	"🈵":["四角囲み満","中国語","満室","満車","満タン"],
+	"🈹":["四角囲み割","四角囲みの割","日本語","割引"],
+	"🈲":["四角囲み禁","日本語","禁止"],
+	"🅰️":["黒四角囲みA","A","血液型"],
+	"🅱️":["黒四角囲みB","B","血液型"],
+	"🆎":["黒四角囲みAB","AB","血液型"],
+	"🆑":["四角囲みCL","CL"],
+	"🅾️":["黒四角囲みO","血液型","O"],
+	"🆘":["四角囲みSOS","ヘルプ","SOS"],
+	"⛔":["立入禁止","立ち入り","禁止","だめ","できない","禁じる","交通"],
+	"📛":["名札","バッジ","名前"],
+	"🚫":["進入禁止","立ち入り","禁止","だめ","できない","禁じる"],
+	"❌":["バツ印","キャンセル","記号","掛け算","乗算","x"],
+	"⭕":["太い大きな丸","丸","O"],
+	"💢":["怒りマーク","怒り","漫画","激怒"],
+	"♨️":["温泉","温かい","湧き出る","蒸気"],
+	"🚷":["歩行者立入禁止","禁止","だめ","ない","歩行者","禁じる"],
+	"🚯":["ポイ捨て禁止","禁止","ごみ","だめ","ない","禁止されている"],
+	"🚳":["自転車禁止","自転車","バイク","禁止","だめ","できない","禁じる","乗り物"],
+	"🚱":["飲用不可","非飲料水","飲料","禁止","だめ","ない","飲用","禁止されている","水"],
+	"🔞":["18歳未満禁止","18","年齢制限","十八","禁止","だめ","ない","禁止した","未成年者"],
+	"📵":["携帯電話禁止","携帯","通信","禁止","モバイル","だめ","できない","携帯電話","禁止されている","電話"],
+	"🚭":["禁煙","禁止","だめ","できない","禁止されている","喫煙"],
+	"❗":["赤いビックリマーク","ビックリ","マーク","記号"],
+	"❕":["白いビックリマーク","ビックリ","マーク","囲み","記号"],
+	"❓":["赤いはてなマーク","マーク","記号","はてな"],
+	"❔":["白いはてなマーク","マーク","囲み","記号","はてな"],
+	"‼️":["!!マーク","バンバン","ビックリ","マーク","記号"],
+	"⁉️":["!?","ビックリ","インテロバング","マーク","記号","はてな"],
+	"💯":["100点","100","フル","百","スコア"],
+	"🔅":["低輝度","明るさ","薄暗い","低"],
+	"🔆":["高輝度","明るい","明るさ"],
+	"🔱":["トライデント","いかり","エンブレム","船","工具"],
+	"⚜":["ユリの紋章"],
+	"〽️":["庵点","印","部分"],
+	"⚠️":["警告"],
+	"🚸":["交差点を渡る子供たち","子供","交差点","歩行者","交通"],
+	"🔰":["初心者マーク","初心者","マーク","緑","日本","若葉","道具","黄"],
+	"♻️":["リサイクルマーク","リサイクル"],
+	"🈯":["四角囲み指","日本語"],
+	"💹":["上昇トレンドのチャートと円記号","上昇中円チャート","銀行","チャート","通貨","グラフ","成長","市場","お金","上昇","トレンド","上向き","円"],
+	"❇️":["キラキラ"],
+	"✳️":["アスタリスク (8本構成)","アスタリスク"],
+	"❎":["四角で囲まれたバツ印","マーク","四角"],
+	"✅":["白い太字のチェックマーク","チェック","マーク"],
+	"💠":["ドット模様のダイヤ","漫画","ダイヤモンド","幾何学","内部"],
+	"🌀":["サイクロン","低気圧","めまい","竜巻","台風","天気"],
+	"➿":["二重のカール状のループ","カール","ダブル","ループ"],
+	"🌐":["子午線・経線のある地球","地球","地球儀","経線","世界"],
+	"♾":["無限","永遠","普遍的"],
+	"Ⓜ️":["丸囲みM","円","M"],
+	"🏧":["ATM","ATM記号","自動","銀行","出納"],
+	"🚾":["トイレ","化粧室","お手洗い","水","WC"],
+	"♿":["車いす","アクセス","車椅子"],
+	"🅿️":["黒四角囲みP","駐車場"],
+	"🈳":["四角囲み空","四角囲みの空","中国語","空室","空き","空車"],
+	"🈂️":["四角囲みサ","日本人","サービス"],
+	"🛂":["入国審査","パスポート"],
+	"🛃":["税関"],
+	"🛄":["手荷物受取所","手荷物","受け取り"],
+	"🛅":["手荷物預かり所","手荷物","ロッカー","携行品"],
+	"🚰":["飲料水","飲み物","水"],
+	"🛗":["エレベーター","アクセシビリティ","引き上げ","昇降機"],
+	"🚹":["男性の記号","男性用","トイレ","男","おとこ","男性"],
+	"♂️":["男性記号","男性","男","おとこ"],
+	"🚺":["女性の記号","女性用","トイレ","女","おんな","女性"],
+	"♀️":["女性記号","女性","女","おんな"],
+	"⚧️":["トランスジェンダーサイン","トランスジェンダー","プライド","lgbt"],
+	"🚼":["赤ちゃんマーク","赤ちゃん","おむつ替え"],
+	"🚻":["トイレ","化粧室","WC"],
+	"🚮":["ゴミ捨て場","ビンのゴミ捨て場","ゴミ","ゴミ箱"],
+	"🎦":["映画","アクティビティ","カメラ","エンターテイメント","フィルム","動画"],
+	"📶":["アンテナ","バー","携帯","コミュニケーション","モバイル","携帯電話","シグナル","電話"],
+	"🛜":["無線","コンピュータ","インターネット","ネットワーク","Wi-Fi","接続"],
+	"🈁":["四角囲みココ","日本人"],
+	"🆖":["四角囲みNG","NG"],
+	"🆗":["四角囲みOK","OK"],
+	"🆙":["四角囲みUP!","マーク","上"],
+	"🆒":["COOL","かっこいい","クール"],
+	"🆕":["四角囲みnew","新"],
+	"🆓":["四角囲みFREE","フリー","無料"],
+	"0️⃣":["0キー","0","キー","ゼロ"],
+	"1️⃣":["1キー","1","キー","一"],
+	"2️⃣":["2キー","2","キー","ニ"],
+	"3️⃣":["3キー","3","キー","三"],
+	"4️⃣":["4キー","4","四","キー"],
+	"5️⃣":["5キー","5","五","キー"],
+	"6️⃣":["6キー","6","キー","六"],
+	"7️⃣":["7キー","7","キー","七"],
+	"8️⃣":["8キー","8","八","キー"],
+	"9️⃣":["9キー","9","キー","九"],
+	"🔟":["10キー","10","キー","十"],
+	"🔢":["番号の入力記号","1234","入力","数字"],
+	"▶️":["右向き三角","再生ボタン","矢印","再生","右","三角形"],
+	"⏸":["2本の垂直バー","一時停止ボタン","バー","2倍","一時停止","垂直"],
+	"⏯":["右向きの三角形と二重垂直棒","再生または一時停止ボタン","矢印","一時停止","再生","右","三角形"],
+	"⏹":["停止","停止ボタン","四角"],
+	"⏺":["録画","録画ボタン","丸"],
+	"⏏️":["取り出しマーク","取り出しボタン"],
+	"⏭":["右向きの二重三角形と垂直棒","「次の曲」ボタン","矢印","次の場面","次の曲","三角形"],
+	"⏮":["左向きの二重三角形と垂直棒","「前の曲」ボタン","矢印","前の場面","前の曲","三角形"],
+	"⏩":["右向きの二重三角形","早送りボタン","矢印","2倍","高速","進む"],
+	"⏪":["左向きの二重三角形","早戻しボタン","矢印","2倍","巻き戻し"],
+	"🔀":["ねじり右向き矢印の絵文字","シャッフル","矢印","交差"],
+	"🔁":["リピート","リピートボタン","矢印","時計回り"],
+	"🔂":["1曲をリピート再生","リピートボタン","矢印","時計回り","一度"],
+	"◀️":["左向きの三角形","反転ボタン","矢印","左","反転","三角形"],
+	"🔼":["上向きの三角形","上ボタン","矢印","ボタン","上"],
+	"🔽":["下向きの三角形","下ボタン","矢印","ボタン","下"],
+	"⏫":["上向きの二重三角形","高速上昇ボタン","矢印","ダブル","上"],
+	"⏬":["下向きの二重三角形","高速ダウンボタン","矢印","ダブル","下"],
+	"➡️":["右向き矢印","右矢印","矢印","主要","方向","東"],
+	"⬅️":["左向き矢印","左矢印","矢印","主要","方向","西"],
+	"⬆️":["上向き矢印","上矢印","矢印","主要","方向","北"],
+	"⬇️":["下向き矢印","下矢印","矢印","主要","方向","下","南"],
+	"↗️":["右上矢印","矢印","方向","斜め","北東"],
+	"↘️":["右下矢印","矢印","方向","斜め","南東"],
+	"↙️":["左下矢印","矢印","方向","斜め","南西"],
+	"↖️":["左上矢印","矢印","方向","斜め","北西"],
+	"↕️":["上下矢印","矢印","方向","斜め","北西"],
+	"↔️":["左右矢印","矢印"],
+	"🔄":["うずまき矢印","反時計回り","矢印","左回り"],
+	"↪️":["右向き段付き矢印","右に曲がった矢印","矢印"],
+	"↩️":["左向き段付き矢印","左に曲がった矢印","矢印"],
+	"🔃":["ループ矢印","時計の針","矢印","時計回り","リロード"],
+	"⤴️":["右上へカーブする矢印","上へカーブする右矢印","矢印"],
+	"⤵️":["右下へカーブする矢印","下にカーブする右矢印","矢印","下"],
+	"#️⃣":["#キー","ハッシュ","キー","ポンド"],
+	"*⃣":["アスタリスクキー","アスタリスク","キー","星"],
+	"ℹ️":["情報源","i","インフォメーション"],
+	"🔤":["アルファベット入力","abc","アルファベット","入力","ラテン","文字"],
+	"🔡":["アルファベット小文字入力","abcd","入力","ラテン","文字","小文字"],
+	"🔠":["アルファベット大文字入力","入力","ラテン","文字","大文字"],
+	"🔣":["記号入力","入力"],
+	"🎵":["音符","アクティビティ","エンターテイメント","音楽"],
+	"🎶":["複数の音符","アクティビティ","エンターテイメント","音楽","音符"],
+	"〰️":["波線","ダッシュ","記号","波"],
+	"➰":["カール状のループ","カール","ループ"],
+	"✔️":["太字のチェックマーク","チェック","マーク"],
+	"➕":["太字の+記号","数学","プラス"],
+	"➖":["太字のマイナス記号","数学","マイナス"],
+	"➗":["太字の÷記号","割り算","数学"],
+	"✖️":["太字の×印","キャンセル","乗算","かける","x"],
+	"🟰":["太い等号","等式","数学","等しい"],
+	"💲":["太字のドル記号","通貨","ドル","お金"],
+	"💱":["外貨両替","銀行","通貨","両替","お金"],
+	"©️":["コピーライトマーク","著作権"],
+	"®️":["登録商標マーク","登録済み","商標"],
+	"™️":["商標マーク","マーク","tm","商標"],
+	"🔚":["ENDと左矢印","矢印","端"],
+	"🔙":["BACKと左矢印","矢印","戻る"],
+	"🔛":["ON!と左右矢印","矢印","マーク","オン"],
+	"🔝":["TOPと上矢印","矢印","トップ","上"],
+	"🔜":["SOONと右矢印","矢印","まもなく"],
+	"☑️":["チェック入りチェックボックス","投票","ボックス","チェック"],
+	"🔘":["ラジオボタン","ボタン","幾何学","ラジオ"],
+	"🔴":["赤丸","円","幾何学","赤"],
+	"🟠":["オレンジ色の円","円","幾何学","オレンジ"],
+	"🟡":["黄色の丸","円","幾何学","茶色"],
+	"🟢":["緑丸","円","幾何学","緑"],
+	"🔵":["青丸","青","円","幾何学"],
+	"🟣":["紫の丸","円","幾何学","紫"],
+	"🟤":["茶色の丸","円","幾何学","茶色"],
+	"⚫":["黒丸","円","幾何学"],
+	"⚪":["白丸","円","幾何学"],
+	"🟥":["赤の正方形","正方形","幾何学","赤"],
+	"🟧":["オレンジ色の正方形","正方形","幾何学","オレンジ"],
+	"🟨":["黄色の正方形","正方形","幾何学","黄色"],
+	"🟩":["緑の正方形","正方形","幾何学","緑"],
+	"🟦":["青の正方形","正方形","幾何学","青"],
+	"🟪":["紫の正方形","正方形","幾何学","紫"],
+	"🟫":["茶色の正方形","正方形","幾何学","茶色"],
+	"⬛":["黒い大きな四角","幾何学","正方形"],
+	"⬜":["白い大きな四角","幾何学","正方形"],
+	"◼️":["黒い中くらいの四角","幾何学","正方形"],
+	"◻️":["白くて中くらいの四角","幾何学","正方形"],
+	"◾":["黒くて中くらいの小さい四角","幾何学","正方形"],
+	"◽":["白い中くらいの小さな四角","幾何学","正方形"],
+	"▪️":["黒い小さな四角","幾何学","正方形"],
+	"▫️":["白い小さな四角","幾何学","正方形"],
+	"🔸":["小さいオレンジのダイヤモンド","ダイヤモンド","幾何学","オレンジ"],
+	"🔹":["小さくて青いダイヤモンド","青","ダイヤモンド","幾何学"],
+	"🔶":["大きいオレンジのダイヤ","ダイヤモンド","幾何学","オレンジ"],
+	"🔷":["大きくて青いダイヤモンド","青","ダイヤモンド","幾何学"],
+	"🔺":["上向きの赤い三角形","上","幾何学","赤"],
+	"🔻":["下向きの三角形","ダウン","幾何学","赤"],
+	"🔲":["黒い四角ボタン","ボタン","幾何学","正方形"],
+	"🔳":["白い四角ボタン","ボタン","幾何学","囲み","四角"],
+	"🔈":["スピーカー","音量"],
+	"🔉":["音量小","電源が入ったスピーカー","低い","スピーカー","音量","波"],
+	"🔊":["音量大","大音量のスピーカー","3","エンターテイメント","高い","音の大きい","スピーカー","ボリューム"],
+	"🔇":["無音のスピーカー","スピーカー","オフ","ミュート","静音","無音","音量"],
+	"📣":["メガホン","応援","コミュニケーション","拡声器"],
+	"📢":["拡声器","コミュニケーション","大声","スピーカー","パブリックアドレス","メガホン"],
+	"🔔":["ベル"],
+	"🔕":["ミュート","スラッシュベル","鐘","禁じられた","だめ","ない","禁止","静か"],
+	"🃏":["トランプのジョーカー","カード","エンターテイメント","ゲーム","ジョーカー","プレイ"],
+	"🀄":["麻雀牌の中","ゲーム","麻雀","赤"],
+	"♠️":["トランプのスペード","カード","ゲーム","スペード","スーツ"],
+	"♣️":["トランプのクラブ","カード","クラブ","ゲーム","スーツ"],
+	"♥️":["トランプのハート","カード","ゲーム","ハート","スーツ"],
+	"♦️":["トランプのダイヤ","カード","ダイヤ","ダイヤモンド","ゲーム","スーツ"],
+	"🎴":["花札","アクティビティ","カード","エンターテイメント","花","ゲーム","日本","プレイ"],
+	"👁‍🗨":["吹き出しの目","吹き出し","目","スピーチ","証人"],
+	"🗨":["左向きの吹き出し","セリフ","スピーチ"],
+	"💭":["考え吹き出し","吹き出し","泡","漫画","考え"],
+	"🗯":["右向きの怒りの吹き出し","怒り","吹き出し","泡","激怒"],
+	"💬":["吹き出し","泡","漫画","セリフ","スピーチ"],
+	"🕐":["1時","0分","1","時計","時","一"],
+	"🕑":["2時","0分","2","時計","時","二"],
+	"🕒":["3時","0分","3","時計","時","三"],
+	"🕓":["4時","0分","4","時計","四","時"],
+	"🕔":["5時","0分","5","時計","五","時"],
+	"🕕":["6時","0分","6","時計","時","六"],
+	"🕖":["7時","0分","7","時計","時","七"],
+	"🕗":["8時","0分","8","時計","八","時"],
+	"🕘":["9時","0分","9","時計","九","時"],
+	"🕙":["10時","0分","10","時計","時","十"],
+	"🕚":["11時","0分","11","時計","十一","時"],
+	"🕛":["12時","0分","12","時計","十二","時"],
+	"🕜":["1時半","1時","半","時刻","一","30"],
+	"🕝":["2時半","2時","半","時刻","30","二"],
+	"🕞":["3時半","3時","半","時刻","30","三"],
+	"🕟":["4時半","30","4時","時刻","四","半"],
+	"🕠":["5時半","30","5時","時刻","五","半"],
+	"🕡":["6時半","30","6時","時刻","六","半"],
+	"🕢":["7時半","30","7時","時刻","七","半"],
+	"🕣":["8時半","30","8時","時刻","八","半"],
+	"🕤":["9時半","30","9時","時刻","九","半"],
+	"🕥":["10時半","10時","半","時刻","十","30"],
+	"🕦":["11時半","11時","半","時刻","十一","30"],
+	"🕧":["12時半","12時","半","時刻","30","十二"],
+	"🏳":["なびく白旗","旗","なびく"],
+	"🏴":["なびく黒旗","旗","なびく"],
+	"🏁":["チェッカーフラッグ","市松模様","旗","レース"],
+	"🚩":["三角旗","旗","ポスト"],
+	"🎌":["交差旗","アクティビティ","お祝い","交差","交差した","旗","日本"],
+	"🏴‍☠️":["海賊旗","旗","海賊"],
+	"🏳️‍🌈":["レインボーフラッグ","フラッグ","レインボー","プライド","lgbt"],
+	"🏳️‍⚧️":["トラスジェンダーフラッグ","フラッグ","トランスジェンダー","プライド","lgbt"],
+	"🇦🇨":["アセンション島の旗","アセンション","国旗","島"],
+	"🇦🇩":["アンドラ国旗","アンドラ","国旗"],
+	"🇦🇪":["アラブ首長国連邦国旗","首長国","国旗","アラブ首長国連邦","連邦"],
+	"🇦🇫":["アフガニスタン国旗","アフガニスタン","国旗"],
+	"🇦🇬":["アンティグア・バーブーダ国旗","アンティグア","バーブーダ","国旗"],
+	"🇦🇮":["アンギラ島の旗","アンギラ島","国旗"],
+	"🇦🇱":["アルバニア国旗","アルバニア","国旗"],
+	"🇦🇲":["アルメニア国旗","アルメニア","国旗"],
+	"🇦🇴":["アンゴラ国旗","アンゴラ","国旗"],
+	"🇦🇶":["南極大陸の旗","南極大陸","国旗"],
+	"🇦🇷":["アルゼンチン国旗","アルゼンチン","国旗"],
+	"🇦🇸":["アメリカ領サモアの旗","アメリカ領","国旗","サモア"],
+	"🇦🇹":["オーストリア国旗","オーストリア","国旗"],
+	"🇦🇺":["オーストラリア国旗","オーストラリア","国旗","ハード","マクドナルド"],
+	"🇦🇼":["アルバ国旗","アルバ","国旗"],
+	"🇦🇽":["オーランド諸島の旗","オーランド諸島","国旗"],
+	"🇦🇿":["アゼルバイジャン国旗","アゼルバイジャン","国旗"],
+	"🇧🇦":["ボスニア・ヘルツェゴビナ国旗","ボスニア","国旗","ヘルツェゴビナ"],
+	"🇧🇧":["バルバドス国旗","バルバドス","国旗"],
+	"🇧🇩":["バングラデシュ国旗","バングラデシュ","国旗"],
+	"🇧🇪":["ベルギー国旗","ベルギー","国旗"],
+	"🇧🇫":["ブルキナファソ国旗","ブルキナファソ","国旗"],
+	"🇧🇬":["ブルガリア国旗","ブルガリア","国旗"],
+	"🇧🇭":["バーレーン国旗","バーレーン","国旗"],
+	"🇧🇮":["ブルンジ国旗","ブルンジ","国旗"],
+	"🇧🇯":["ベナン国旗","ベナン","国旗"],
+	"🇧🇱":["サン・バルテルミー島の旗","バルテルミー","国旗","サン"],
+	"🇧🇲":["バミューダ諸島の旗","バミューダ諸島","国旗"],
+	"🇧🇳":["ブルネイ国旗","ブルネイ","ダルサラーム","国旗"],
+	"🇧🇴":["ボリビア国旗","ボリビア","国旗"],
+	"🇧🇶":["カリブ海のオランダ領島の旗","ボネール島","カリブ海","ユースタティウス","国旗","オランダ","サバ","シント"],
+	"🇧🇷":["ブラジル国旗","ブラジル","国旗"],
+	"🇧🇸":["バハマ国旗","バハマ","国旗"],
+	"🇧🇹":["ブータン国旗","ブータン","国旗"],
+	"🇧🇼":["ボツワナ国旗","ボツワナ","国旗"],
+	"🇧🇾":["ベラルーシ国旗","ベラルーシ","国旗"],
+	"🇧🇿":["ベリーズ国旗","ベリーズ","国旗"],
+	"🇨🇦":["カナダ国旗","カナダ","国旗"],
+	"🇨🇨":["ココス諸島の旗","ココス","国旗","諸島","キーリング"],
+	"🇨🇩":["コンゴ国旗 - キンシャサ","コンゴ","コンゴ - キンシャサ","コンゴ民主共和国","国旗","キンシャサ","共和国"],
+	"🇨🇫":["中央アフリカ国旗","中央アフリカ共和国","国旗","共和国"],
+	"🇨🇬":["コンゴの旗 - ブラザビル","ブラザビル","コンゴ","コンゴ共和国","コンゴ - ブラザビル","国旗","共和国"],
+	"🇨🇭":["スイス国旗","国旗","スイス"],
+	"🇨🇮":["コートジボワール国旗","コートジボワール","国旗"],
+	"🇨🇰":["クック諸島国旗","クック","国旗","諸島"],
+	"🇨🇱":["チリ国旗","チリ","国旗"],
+	"🇨🇲":["カメルーン国旗","カメルーン","国旗"],
+	"🇨🇳":["中国国旗","中国","国旗"],
+	"🇨🇴":["コロンビア国旗","コロンビア","国旗"],
+	"🇨🇷":["コスタリカ国旗","コスタリカ","国旗"],
+	"🇨🇺":["キューバ国旗","キューバ","国旗"],
+	"🇨🇻":["カーボベルデ国旗","カーボ","ケープ","国旗","ベルデ"],
+	"🇨🇼":["キュラソー島の旗","アンティル諸島","キュラソー","国旗"],
+	"🇨🇽":["クリスマス島の旗","クリスマス","国旗","島"],
+	"🇨🇾":["キプロス国旗","キプロス","国旗"],
+	"🇨🇿":["チェコ国旗","チェコ共和国","国旗"],
+	"🇩🇪":["ドイツ国旗","国旗","ドイツ"],
+	"🇩🇯":["ジブチ国旗","ジブチ","国旗"],
+	"🇩🇰":["デンマーク国旗","デンマーク","国旗"],
+	"🇩🇲":["ドミニカ国旗","ドミニカ","国旗"],
+	"🇩🇴":["ドミニカ共和国国旗","ドミニカ共和国","国旗"],
+	"🇩🇿":["アルジェリア国旗","アルジェリア","国旗"],
+	"🇪🇨":["エクアドル国旗","エクアドル","国旗"],
+	"🏴󠁧󠁢󠁥󠁮󠁧󠁿":["イングランドの旗","イングランド","旗"],
+	"🇪🇪":["エストニア国旗","エストニア","国旗"],
+	"🇪🇬":["エジプト国旗","エジプト","国旗"],
+	"🇪🇭":["西サハラの旗","国旗","サハラ","西","西サハラ"],
+	"🇪🇷":["エリトリア国旗","エリトリア","国旗"],
+	"🇪🇸":["スペイン国旗","国旗","スペイン","セウタ","メリリャ"],
+	"🇪🇹":["エチオピア国旗","エチオピア","国旗"],
+	"🇪🇺":["欧州旗","欧州連合","旗"],
+	"🇫🇮":["フィンランド国旗","フィンランド","国旗"],
+	"🇫🇯":["フィジー国旗","フィジー","国旗"],
+	"🇫🇰":["フォークランド諸島の旗","フォークランド","フォークランド諸島","国旗","諸島","マルビナス"],
+	"🇫🇲":["ミクロネシア国旗","国旗","ミクロネシア"],
+	"🇫🇴":["フェロー諸島の旗","フェロー","旗","諸島"],
+	"🇫🇷":["フランス国旗","国旗","フランス","クリッパートン島","セント・マーチン","サン・マルタン"],
+	"🇬🇦":["ガボン国旗","国旗","ガボン"],
+	"🇬🇧":["イギリス国旗","イギリス","イギリス領","コーンウォール","イングランド","国旗","グレートブリテン","アイルランド","北アイルランド","スコットランド","UK","ユニオンジャック","連合","連合王国","ウェールズ"],
+	"🇬🇩":["グレナダ国旗","国旗","グレナダ"],
+	"🇬🇪":["ジョージア国旗","国旗","ジョージア"],
+	"🇬🇫":["フランス領ギアナの旗","国旗","フランス領","ギアナ"],
+	"🇬🇬":["ガーンジー国旗","国旗","ガーンジー"],
+	"🇬🇭":["ガーナ国旗","国旗","ガーナ"],
+	"🇬🇮":["ジブラルタル国旗","国旗","ジブラルタル"],
+	"🇬🇱":["グリーンランド国旗","国旗","グリーンランド"],
+	"🇬🇲":["ガンビア国旗","国旗","ガンビア"],
+	"🇬🇳":["ギニア国旗","国旗","ギニア"],
+	"🇬🇵":["グアドループ国旗","国旗","グアドループ"],
+	"🇬🇶":["赤道ギニア国旗","赤道ギニア","国旗","ギニア"],
+	"🇬🇷":["ギリシャ国旗","国旗","ギリシャ"],
+	"🇬🇸":["サウスジョージア・サウスサンドウィッチ諸島国旗","国旗","ジョージア","諸島","サウス","サウスジョージア","サウスサンドウィッチ"],
+	"🇬🇹":["グアテマラ国旗","国旗","グアテマラ"],
+	"🇬🇺":["グアム旗","国旗","グアム"],
+	"🇬🇼":["ギニアビサウ国旗","ビサウ","国旗","ギニア"],
+	"🇬🇾":["ガイアナ国旗","国旗","ガイアナ"],
+	"🇭🇰":["香港の旗","中国","国旗","香港"],
+	"🇭🇳":["ホンジュラス国旗","国旗","ホンジュラス"],
+	"🇭🇷":["クロアチア国旗","クロアチア","国旗"],
+	"🇭🇹":["ハイチ国旗","国旗","ハイチ"],
+	"🇭🇺":["ハンガリー国旗","国旗","ハンガリー"],
+	"🇮🇨":["カナリア諸島の旗","カナリア","国旗","諸島"],
+	"🇮🇩":["インドネシア国旗","国旗","インドネシア"],
+	"🇮🇪":["アイルランド国旗","国旗","アイルランド"],
+	"🇮🇱":["イスラエル国旗","国旗","イスラエル"],
+	"🇮🇲":["マン島の旗","国旗","マン島"],
+	"🇮🇳":["インド国旗","国旗","インド"],
+	"🇮🇴":["イギリス領インド洋地域の旗","イギリス領","チャゴス","旗","インド洋","島","ディエゴガルシア"],
+	"🇮🇶":["イラク国旗","国旗","イラク"],
+	"🇮🇷":["イラン国旗","国旗","イラン"],
+	"🇮🇸":["アイスランド国旗","国旗","アイスランド"],
+	"🇮🇹":["イタリア国旗","国旗","イタリア"],
+	"🇯🇪":["ジャージー代官管轄区の旗","国旗","ジャージー代官管轄区"],
+	"🇯🇲":["ジャマイカ国旗","国旗","ジャマイカ"],
+	"🇯🇴":["ヨルダン国旗","国旗","ヨルダン"],
+	"🇯🇵":["日本国旗","国旗","日本"],
+	"🇰🇪":["ケニア国旗","国旗","ケニア"],
+	"🇰🇬":["キルギス国旗","国旗","キルギス"],
+	"🇰🇭":["カンボジア国旗","カンボジア","国旗"],
+	"🇰🇮":["キリバス国旗","国旗","キリバス"],
+	"🇰🇲":["コモロ国旗","コモロ","国旗"],
+	"🇰🇳":["セントクリストファー・ネイビス国旗","国旗","キッツ","ネイビス","セント"],
+	"🇰🇵":["北朝鮮国旗","国旗","朝鮮","北","北朝鮮"],
+	"🇰🇷":["韓国国旗","国旗","韓国","南","大韓民国"],
+	"🇰🇼":["クウェート国旗","国旗","クウェート"],
+	"🇰🇾":["ケイマン諸島の旗","ケイマン","国旗","諸島"],
+	"🇰🇿":["カザフスタン国旗","国旗","カザフスタン"],
+	"🇱🇦":["ラオス国旗","国旗","ラオス"],
+	"🇱🇧":["レバノン国旗","国旗","レバノン"],
+	"🇱🇨":["セントルシア国旗","国旗","セントルシア"],
+	"🇱🇮":["リヒテンシュタイン国旗","国旗","リヒテンシュタイン"],
+	"🇱🇰":["スリランカ国旗","国旗","スリランカ"],
+	"🇱🇷":["リベリア国旗","国旗","リベリア"],
+	"🇱🇸":["レソト国旗","国旗","レソト"],
+	"🇱🇹":["リトアニア国旗","国旗","リトアニア"],
+	"🇱🇺":["ルクセンブルク国旗","国旗","ルクセンブルク"],
+	"🇱🇻":["ラトビア国旗","国旗","ラトビア"],
+	"🇱🇾":["リビア国旗","国旗","リビア"],
+	"🇲🇦":["モロッコ国旗","国旗","モロッコ"],
+	"🇲🇨":["モナコ国旗","国旗","モナコ"],
+	"🇲🇩":["モルドバ国旗","国旗","モルドバ"],
+	"🇲🇪":["モンテネグロ国旗","国旗","モンテネグロ"],
+	"🇲🇬":["マダガスカル国旗","国旗","マダガスカル"],
+	"🇲🇭":["マーシャル諸島国旗","国旗","諸島","マーシャル"],
+	"🇲🇰":["マケドニア国旗","国旗","マケドニア"],
+	"🇲🇱":["マリ国旗","国旗","マリ"],
+	"🇲🇲":["ミャンマー国旗","ビルマ","国旗","ミャンマー"],
+	"🇲🇳":["モンゴル国旗","国旗","モンゴル"],
+	"🇲🇴":["マカオの旗","中国","国旗","マカオ"],
+	"🇲🇵":["北マリアナ諸島の旗","国旗","諸島","マリアナ","北","北マリアナ"],
+	"🇲🇶":["マルティニークの旗","旗","マルティニーク"],
+	"🇲🇷":["モーリタニア国旗","国旗","モーリタニア"],
+	"🇲🇸":["モントセラトの旗","旗","モントセラト"],
+	"🇲🇹":["マルタ国旗","国旗","マルタ"],
+	"🇲🇺":["モーリシャス国旗","国旗","モーリシャス"],
+	"🇲🇻":["モルディブ国旗","国旗","モルディブ"],
+	"🇲🇼":["マラウイ国旗","国旗","マラウイ"],
+	"🇲🇽":["メキシコ国旗","国旗","メキシコ"],
+	"🇲🇾":["マレーシア国旗","国旗","マレーシア"],
+	"🇲🇿":["モザンビーク国旗","国旗","モザンビーク"],
+	"🇳🇦":["ナミビア国旗","国旗","ナミビア"],
+	"🇳🇨":["ニューカレドニアの旗","国旗","ニュー","ニューカレドニア"],
+	"🇳🇪":["ニジェール国旗","国旗","ニジェール"],
+	"🇳🇫":["ノーフォーク島の旗","旗","島","ノーフォーク"],
+	"🇳🇬":["ナイジェリア国旗","国旗","ナイジェリア"],
+	"🇳🇮":["ニカラグア国旗","国旗","ニカラグア"],
+	"🇳🇱":["オランダ国旗","国旗","オランダ"],
+	"🇳🇴":["ノルウェー国旗","旗","ノルウェー","ブーべ","スヴァールバル","ヤンマイエン"],
+	"🇳🇵":["ネパール国旗","国旗","ネパール"],
+	"🇳🇷":["ナウル国旗","国旗","ナウル"],
+	"🇳🇺":["ニウエ国旗","国旗","ニウエ"],
+	"🇳🇿":["ニュージーランド国旗","国旗","ニュー","ニュージーランド"],
+	"🇴🇲":["オマーン国旗","国旗","オマーン"],
+	"🇵🇦":["パナマ国旗","国旗","パナマ"],
+	"🇵🇪":["ペルー国旗","国旗","ペルー"],
+	"🇵🇫":["フランス領ポリネシアの旗","国旗","フランス領","ポリネシア"],
+	"🇵🇬":["パプアニューギニア国旗","国旗","ギニア","ニュー","パプアニューギニア"],
+	"🇵🇭":["フィリピン国旗","国旗","フィリピン"],
+	"🇵🇰":["パキスタン国旗","国旗","パキスタン"],
+	"🇵🇱":["ポーランド国旗","国旗","ポーランド"],
+	"🇵🇲":["サンピエール島・ミクロン島の旗","旗","ミクロン","ピエール","サン"],
+	"🇵🇳":["ピトケアン諸島の旗","旗","諸島","ピトケアン"],
+	"🇵🇷":["プエルトリコの旗","国旗","プエルトリコ"],
+	"🇵🇸":["パレスチナ自治政府の旗","国旗","パレスチナ"],
+	"🇵🇹":["ポルトガル国旗","国旗","ポルトガル"],
+	"🇵🇼":["パラオ国旗","国旗","パラオ"],
+	"🇵🇾":["パラグアイ国旗","国旗","パラグアイ"],
+	"🇶🇦":["カタール国旗","国旗","カタール"],
+	"🇷🇪":["レユニオンの旗","旗","レユニオン"],
+	"🇷🇴":["ルーマニア国旗","国旗","ルーマニア"],
+	"🇷🇸":["セルビア国旗","国旗","セルビア"],
+	"🇷🇺":["ロシア国旗","国旗","ロシア"],
+	"🇷🇼":["ルワンダ国旗","国旗","ルワンダ"],
+	"🇸🇦":["サウジアラビア国旗","国旗","サウジアラビア"],
+	"🏴󠁧󠁢󠁳󠁣󠁴󠁿":["スコットランドの旗","スコットランド","旗"],
+	"🇸🇧":["ソロモン諸島国旗","旗","諸島","ソロモン"],
+	"🇸🇨":["セーシェル国旗","国旗","セーシェル"],
+	"🇸🇩":["スーダン国旗","国旗","スーダン"],
+	"🇸🇪":["スウェーデン国旗","国旗","スウェーデン"],
+	"🇸🇬":["シンガポール国旗","国旗","シンガポール"],
+	"🇸🇭":["セントヘレナ島の旗","旗","ヘレナ","セント"],
+	"🇸🇮":["スロベニア国旗","国旗","スロベニア"],
+	"🇸🇰":["スロバキア国旗","国旗","スロバキア"],
+	"🇸🇱":["シエラレオネ国旗","国旗","シエラレオネ"],
+	"🇸🇲":["サンマリノ国旗","国旗","サンマリノ"],
+	"🇸🇳":["セネガル国旗","国旗","セネガル"],
+	"🇸🇴":["ソマリア国旗","国旗","ソマリア"],
+	"🇸🇷":["スリナム国旗","国旗","スリナム"],
+	"🇸🇸":["南スーダン国旗","国旗","南","南スーダン","スーダン"],
+	"🇸🇹":["サントメ・プリンシペ国旗","国旗","プリンシペ","プリンシピ","サントメ","サォントメー"],
+	"🇸🇻":["エルサルバドル国旗","エルサルバドル","国旗"],
+	"🇸🇽":["セント・マーチン島の旗","旗","マーチン","セント"],
+	"🇸🇾":["シリア国旗","国旗","シリア"],
+	"🇸🇿":["スワジランド国旗","国旗","スワジランド"],
+	"🇹🇦":["トリスタンダクーニャの旗","旗","トリスタン・ダ・クーニャ"],
+	"🇹🇨":["タークス・カイコス諸島の旗","カイコス","旗","諸島","タークス"],
+	"🇹🇩":["チャド国旗","チャド","国旗"],
+	"🇹🇫":["フランス領南方・南極地域の旗","南極","国旗","フランス領"],
+	"🇹🇬":["トーゴ国旗","国旗","トーゴ"],
+	"🇹🇭":["タイ国旗","国旗","タイ"],
+	"🇹🇯":["タジキスタン国旗","国旗","タジキスタン"],
+	"🇹🇰":["トケラウ旗","国旗","トケラウ"],
+	"🇹🇱":["東ティモール国旗","東","東ティモール","国旗","ティモール・レステ"],
+	"🇹🇲":["トルクメニスタン国旗","国旗","トルクメニスタン"],
+	"🇹🇳":["チュニジア国旗","国旗","チュニジア"],
+	"🇹🇴":["トンガ国旗","国旗","トンガ"],
+	"🇹🇷":["トルコ国旗","国旗","トルコ"],
+	"🇹🇹":["トリニダード・トバゴ国旗","国旗","トバゴ","トリニダード"],
+	"🇹🇻":["ツバル国旗","国旗","ツバル"],
+	"🇹🇼":["台湾の旗","中国","国旗","台湾"],
+	"🇹🇿":["タンザニア国旗","国旗","タンザニア"],
+	"🇺🇦":["ウクライナ国旗","国旗","ウクライナ"],
+	"🇺🇬":["ウガンダ国旗","国旗","ウガンダ"],
+	"🇺🇳":["国連の旗","旗","国連","連合","国際"],
+	"🇺🇸":["アメリカ国旗","アメリカ","旗","合衆","合衆国","アメリカ合衆国","合衆国領有小離島"],
+	"🇺🇾":["ウルグアイ国旗","国旗","ウルグアイ"],
+	"🇺🇿":["ウズベキスタン国旗","国旗","ウズベキスタン"],
+	"🇻🇦":["バチカン市国旗","国旗","バチカン"],
+	"🇻🇨":["セントビンセント・グレナディーン国旗","国旗","グレナディーン諸島","セント","ビンセント"],
+	"🇻🇪":["ベネズエラ国旗","国旗","ベネズエラ"],
+	"🇻🇬":["イギリス領ヴァージン諸島の旗","イギリス領","国旗","島","ヴァージン"],
+	"🇻🇮":["アメリカ領ヴァージン諸島の旗","アメリカ","国旗","島","アメリカ合衆国","合衆国","ヴァージン"],
+	"🇻🇳":["ベトナム国旗","国旗","ベトナム","ヴェトナム"],
+	"🇻🇺":["バヌアツ国旗","国旗","バヌアツ"],
+	"🏴󠁧󠁢󠁷󠁬󠁳󠁿":["ウェールズの旗","ウェールズ","旗"],
+	"🇼🇫":["ウォリス・フツナの旗","国旗","フツナ","ウォリス"],
+	"🇼🇸":["サモア国旗","国旗","サモア"],
+	"🇽🇰":["コソボ国旗","国旗","コソボ"],
+	"🇾🇪":["イエメン国旗","国旗","イエメン"],
+	"🇾🇹":["マヨットの旗","国旗","マヨット"],
+	"🇿🇦":["南アフリカ国旗","国旗","南","南アフリカ"],
+	"🇿🇲":["ザンビア国旗","国旗","ザンビア"],
+	"🇿🇼":["ジンバブエ国旗","国旗","ジンバブエ"],
+	"": ["渋谷109", "SHIBUYA109", "109"]
+}
diff --git a/packages/frontend/src/unicode-emoji-indexes/ja-JP_hira.json b/packages/frontend/src/unicode-emoji-indexes/ja-JP_hira.json
new file mode 100644
index 0000000000..2ad282d501
--- /dev/null
+++ b/packages/frontend/src/unicode-emoji-indexes/ja-JP_hira.json
@@ -0,0 +1,1866 @@
+{
+	"😀": ["にやにやしたかお","かお","にやにや","しあわせ"],
+	"😃": ["くちをあけたえがお","かお","くち","あける","えがお","しあわせ"],
+	"😄": ["くちをあけてめがわらっているえがお","め","かお","くち","あける","えがお","しあわせ"],
+	"😁": ["にやにやしたかお","め","かお","にやにや","えがお"],
+	"😆": ["くちをあけてわらっているかお","かお","わらい","くち","あける","まんぞく","えがお"],
+	"😅": ["くちをあけてひやあせをかいたえがお","ぞっとする","かお","くちをあける","えがお","ひやあせ"],
+	"😂": ["うれしなき","かお","うれしい","わらう","なく","なみだ"],
+	"🤣": ["だいばくしょう","かお","ゆか","わらい","おおわらい","ばくしょう","ぐるぐる"],
+	"😇": ["てんしのえがお","てんし","かお","おとぎばなし","ふぁんたじー","てんしのわ","むじゃき","えがお"],
+	"😉": ["ういんくしたかお","かお","ういんく"],
+	"😊": ["めがわらっているえがお","せきめん","め","かお","えがお"],
+	"🙂": ["ほほえみ","かお","えがお","しあわせ"],
+	"🙃": ["さかさのかお","かお","さかさ"],
+	"☺️": ["えがお","かお","りんかく","りらっくす"],
+	"😋": ["たべものをあじわうかお","おいしい","かお","あじわう","ふーむ","うまい"],
+	"😌": ["ほっとしたかお","かお","あんしん","ほっとする"],
+	"😍": ["めがはーとのえがお","め","かお","はーと","あい","えがお"],
+	"🥰": ["えがおとはーと","かお","けいあい","べたぼれ","あい"],
+	"😘": ["なげきっす","かお","はーと","きす"],
+	"😗": ["きすをするかお","かお","きす"],
+	"😙": ["えがおできす","め","かお","きす","えがお"],
+	"😚": ["めをとじてきすをするかお","とじた","め","かお","きす"],
+	"🥲": ["なみだのでているえがお","なく","しあわせ","かんしゃする","ほこりにおもう","あんしんする","わらう"],
+	"🤪": ["おどけたかお","め","にやにや","へん","こうふん","わいるど"],
+	"😜": ["したをだしてういんくしているかお","め","かお","じょうだん","した","ういんく"],
+	"😝": ["したをだしてめをほそめているかお","め","かお","こわい","あじ","した"],
+	"😛": ["したをだしているかお","かお","した"],
+	"🤑": ["ごうよくなかお","かお","おかね","くち"],
+	"😎": ["さんぐらすをかけたかお","あかるい","かっこいい","め","あいうぇあ","かお","めがね","えがお","たいよう","さんぐらす","てんき"],
+	"🤓": ["おたく","かお","へんなひと"],
+	"🥸": ["かそうしたかお","かそう","めがね","とくめいのひと","はな"],
+	"🧐": ["かためがねをかけたかお","たいくつ","ゆうふく","ゆたか"],
+	"🤠": ["かうぼーいはっとのかお","かうぼーい","かうがーる","かお","ぼうし"],
+	"🥳": ["ぱーてぃーふぇいす","かお","しゅくてん","ぼうし","つの","ぱーてぃー"],
+	"🤡": ["ぴえろのかお","ぴえろ","かお"],
+	"😏": ["にやにやしたかお","かお","にやにや"],
+	"😶": ["くちのないかお","かお","くち","しずかに","ちんもく"],
+	"🫥": ["てんせんのかお","おちこんだ","きえる","かくれる","ないこうてき","めにみえない"],
+	"😐": ["ふつうのかお","むひょうじょう","かお","へいせい"],
+	"🫤": ["くちがななめになったかお","がっかり","むかんしん","うたがいぶかい","ふあん"],
+	"😑": ["むひょうじょう","かお","ぽーかーふぇいす","むかんじょう"],
+	"😒": ["おもしろくなさそうなかお","かお","つまらない","ふこう"],
+	"🙄": ["ぐるぐるめのかお","め","かお","ぐるぐる"],
+	"🤨": ["まゆがあがっているかお","ふしん","うたがいぶかい","ひなん","ぎねん","ややおどろき","かいぎてき"],
+	"🤔": ["かんがえているかお","かお","かんがえちゅう"],
+	"🤫": ["しっといっているかお","しーっ","しずか","だまる"],
+	"🤭": ["くちをてでおおったかお","め","えがお","おおう","くち","て"],
+	"🫢": ["めをひらいてくちをてでおおったかお","きょうたん","いけい","ふしん","ろうばい","こわい","おどろき"],
+	"🫡": ["けいれいしているかお","ok","けいれい","せいてん","ぶたい","はい"],
+	"🤗": ["りょうてをひろげたえがお","かお","はぐ","だきしめる"],
+	"🫣": ["のぞきみしているかお","みりょう","のぞきみ","ぎょうし","ちらみ"],
+	"🤥": ["うそつきがお","かお","うそ","ぴのきお"],
+	"😳": ["あかくなったかお","ぼーっとした","ぼうっとした","かお","せきめん"],
+	"😞": ["がっかりしたかお","がっかり","かお"],
+	"😟": ["ふあんなかお","かお","しんぱい","ふあん"],
+	"😤": ["かちほこったかお","かお","しょうり","かつ"],
+	"😠": ["おこったかお","いかり","おこった","かお","げきど"],
+	"😡": ["ふくれがお","いかり","おこった","かお","げきど","ふくれっつら","ふんど","あか"],
+	"🤬": ["くちがきごうでおおわれたかお","のろい","ののしり"],
+	"😔": ["かなしげなかお","がっかり","かお","かなしい"],
+	"😕": ["こまったかお","こまった","かお"],
+	"🙁": ["ごきげんななめ","かお","しかめっつら","かなしい","ふこう"],
+	"☹": ["しかめっつら","かお","かなしい","ふこう"],
+	"😬": ["しかめっつら","かお"],
+	"🥺": ["うったえかけるかお","かお","ものごい","じひ","こいぬのめ"],
+	"😣": ["がまんしているかお","かお","がんばる"],
+	"😖": ["うろたえたかお","とまどい","うろたえ","かお"],
+	"😫": ["つかれたかお","かお","つかれた"],
+	"😩": ["うんざりしているかお","かお","つかれた","うんざり"],
+	"🥱": ["あくびしているかお","あきた","つかれた","あくび"],
+	"😪": ["ねむいかお","かお","ねる","すいみん"],
+	"😮‍💨": ["ためいきのでているかお","かお","ためいき","いきぎれ","うめき","あんしん","ささやき","くちぶえ"],
+	"😮": ["くちをあけたえがお","かお","くち","あける","どうじょう"],
+	"😱": ["ぜっきょうしたかお","かお","きょうふ","こわい","むんく","おびえ","ぜっきょう"],
+	"😨": ["ぞっとしているかお","かお","きょうふ","こわい","おびえ"],
+	"😰": ["くちをあけてひやあせをかいたかお","あおざめる","ぞっとする","かお","くち","あける","いそぐ","ひやあせ"],
+	"😥": ["がっかりしたがあんしんしたかお","がっかり","かお","あんしん","ほっとする","やれやれ"],
+	"😓": ["ひやあせをかいているかお","ぞっとする","かお","ひやあせ"],
+	"😯": ["おちついたかお","かお","だまる","ぼうぜん","おどろき"],
+	"😦": ["しんぱいそうなかおのえもじ","かお","しかめっつら","くち","あける"],
+	"😧": ["くのうにみちたかお","くのう","かお"],
+	"🥹": ["なみだをこらえているかお","おこる","なく","ほこりにおもう","さからう","かなしむ"],
+	"😢": ["なきがお","なく","かお","かなしい","なみだ"],
+	"😭": ["ごうきゅう","なく","かお","かなしい","なみだ"],
+	"🤤": ["よだれをたらしたかお","よだれ","かお"],
+	"🤩": ["すたーにむちゅう","め","かお","にやにや","ほし","むそうてき"],
+	"😵": ["めがばつになったかお","めまい","かお","ばつ","め"],
+	"😵‍💫": ["めがぐるぐるしているかお","めまい","かお","め","うっとり","ぐるぐる","とらぶる","おー"],
+	"🥴": ["ぼんやしりたかお","かお","めまい","めいてい","ほろよい","まっすぐでないめ","はじょうのくち"],
+	"😲": ["おどろいたかお","おどろき","びっくり","かお","しょっく","きょうがく"],
+	"🫨": ["ふるえるかお","じしん","かお","ふるえ","しょうげき","しんどう"],
+	"🤯": ["ばくはつしたあたま","かお","しょっく","ばくはつ","きょうき","びっくり"],
+	"🫠": ["ほろりとしたかお","きえる","ようかいする","えきたい","とける"],
+	"🤐": ["おくちちゃっく","かお","くち","ちゃっく"],
+	"😷": ["ますくをしたかお","かぜ","いしゃ","かお","ますく","くすり","びょうき"],
+	"🤕": ["けが","ほうたい","かお","きず"],
+	"🤒": ["おんどけいをくわえたかお","かお","びょうき","かぜ","たいおんけい"],
+	"🤮": ["はきそうなかお","びょうき","おうと","かぜ","はく"],
+	"🤢": ["はきそうなかお","かお","はきけ","おうと"],
+	"🤧": ["くしゃみをするかお","かお","くしゃみ","はくしょん"],
+	"🥵": ["ほてったかお","かお","ねつっぽい","ねっしゃびょう","ほてった","あからがお","あせをかいた"],
+	"🥶": ["あおざめたかお","かお","ぞっとする","こごえる","とうしょう","つらら"],
+	"😶‍🌫️": ["くもでおおわれたかお","かお","おっちょこちょい","ひげんじつてき","ゆめ","もや","くもでおおわれたあたま"],
+	"😴": ["ねがお","かお","ねる","すいみん","すやすや"],
+	"💤": ["すいみん","まんが","ねる","すやすや"],
+	"😈": ["つのつきえがお","かお","おとぎばなし","ふぁんたじー","つの","えがお"],
+	"👿": ["しょうあくま","おに","あくま","かお","おとぎばなし","ふぁんたじー"],
+	"👹": ["おに","ようかい","かお","むかしばなし","ふぁんたじー","にっぽん","もんすたー"],
+	"👺": ["てんぐ","ようかい","かお","むかしばなし","ふぁんたじー","にっぽん","もんすたー"],
+	"💩": ["うんち","まんが","ふん","かお","もんすたー"],
+	"👻": ["おばけ","ようかい","かお","おとぎばなし","ふぁんたじー","ゆうれい","もんすたー","はろうぃーん"],
+	"💀": ["どくろ","からだ","し","かお","おとぎばなし","もんすたー","がいこつ","はろうぃーん"],
+	"☠": ["どくろまーく","からだ","こうさしたほね","し","かお","もんすたー","がいこつ","はろうぃーん"],
+	"👽": ["うちゅうじん","かいじゅう","いせいじん","かお","おとぎばなし","ふぁんたじー","もんすたー","うちゅう","UFO"],
+	"🤖": ["ろぼっとのかお","かお","もんすたー","ろぼっと"],
+	"🎃": ["じゃっく・お・らんたん","いべんと","おいわい","えんため","はろうぃん","じゃっくおらんたん","らんたん","かぼちゃ"],
+	"😺": ["くちをあけてわらうねこ","ねこ","かお","くち","あける","えがお"],
+	"😸": ["にやにやわらうねこ","ねこ","め","かお","にやにや","えがお"],
+	"😹": ["うれしなきしたねこのかお","ねこ","かお","うれしい","なみだ"],
+	"😻": ["はーとのめをしたねこのえがお","ねこ","め","かお","はーと","あい","えがお"],
+	"😼": ["にやりとわらうねこのかお","ねこ","かお","ひにく","えがお","にやり"],
+	"😽": ["めをとじてきすをするねこ","ねこ","め","かお","きす"],
+	"🙀": ["つかれたねこのかお","ねこ","かお","びっくり","おどろく","うんざり"],
+	"😿": ["ないたねこのかお","ねこ","なく","かお","かなしい","なみだ"],
+	"😾": ["おこったねこのかお","ねこ","かお","おこる","ふくれっつら"],
+	"🫶": ["はーとぽーず","あい"],
+	"👐": ["ひらいたて","からだ","て","ひろげる"],
+	"🤲": ["うえにむけたりょうてのひら","からだ","いのり","かっぷのようにまるめたて"],
+	"🙌": ["りょうてをあげる","からだ","おいわい","じぇすちゃー","て","ばんざい","あげる"],
+	"👏": ["はくしゅ","からだ","てをたたく","て"],
+	"🙏": ["にぎったて","たのむ","からだ","おじぎ","てをあわせる","じぇすちゃー","て","おねがい","いのる","ありがとう","かんしゃ"],
+	"🤝": ["あくしゅ","ごうい","て","しゅをむすぶ","かいぎ"],
+	"👍": ["いいね","からだ","うえ","て","ゆび","さむずあっぷ","+1"],
+	"👎": ["だめ","からだ","した","て","ゆび","さむずだうん","-1"],
+	"👊": ["にぎりこぶし","からだ","にぎる","こぶし","ぐー","て","ぱんち","せっきん"],
+	"✊": ["こぶし","からだ","にぎる","ぐー","て","ぱんち"],
+	"🤛": ["ひだりむきのこぶし","からだ","こぶし","ひだりむき"],
+	"🤜": ["みぎむきのこぶし","からだ","こぶし","みぎむき"],
+	"🤞": ["こうささせたゆび","からだ","こうさ","ゆび","て","こううん"],
+	"✌": ["Vさいん","からだ","て","V","ぶい","かつ","しょうり","ぴーす"],
+	"🫰": ["ひとさしゆびとおやゆびをこうさしたて","たかい","はーと","あい","おかね","すなっぷ"],
+	"🤘": ["こるな","からだ","ゆび","て","つの","さいこう"],
+	"🤟": ["あいしてるのじぇすちゃー","からだ","あいしてる","すき","て"],
+	"👌": ["OKさいん","からだ","て","OK"],
+	"🤌": ["つまんでいるゆび","ゆび","てぶり","じんもん","つまむ","ひにく"],
+	"🤏": ["つまんでいるて","からだ","て","ちいさい","こがた","ちっちゃい"],
+	"👈": ["ひだりゆびさし","てのこう","からだ","ゆび","て","ひとさしゆび","ゆびさす"],
+	"🫳": ["てのひらをしたにしたて","しりぞける","おとす","しっし"],
+	"🫴": ["てのひらをうえにしたて","てまねき","ほかく","くる","もうしで"],
+	"👉": ["ゆびさし","てのこう","からだ","ゆび","て","ひとさしゆび","ゆびさす"],
+	"👆": ["ゆびさし","てのこう","からだ","ゆび","て","ひとさしゆび","ゆびさす","うえ"],
+	"👇": ["ゆびさし","てのこう","からだ","した","ゆび","て","ひとさしゆび","ゆびさす"],
+	"☝": ["ゆびさし","からだ","ゆび","て","ひとさしゆび","ゆびさす","うえ"],
+	"✋": ["きょしゅ","からだ","て"],
+	"🤚": ["てのこう","からだ","あげる"],
+	"🖐": ["ひろげたてのひら","からだ","ゆび","て","ひろげる"],
+	"🖖": ["ちょうじゅとはんえいを","からだ","ゆび","て","すぽっく","ばるかん"],
+	"👋": ["ばいばい","からだ","て","ふる","やっほー","こんにちは"],
+	"🤙": ["でんわのかたちのて","からだ","でんわ","て"],
+	"🫲": ["ひだりて","て","ひだり"],
+	"🫱": ["みぎて","て","みぎ"],
+	"🫷": ["ひだりをおしているて","じたい","はいたっち","ひだりほうこう","おしつける","ことわる","ていし","まつ"],
+	"🫸": ["みぎをおしているて","じたい","はいたっち","おしつける","ことわる","みぎほうこう","ていし","まつ"],
+	"💪": ["まげたじょうわんにとうきん","ちからこぶ","からだ","まんが","うんどう","きんにく","ちから","まっする","まっちょ"],
+	"🦾": ["めかにかるあーむ","あくせしびりてぃ","ぎしゅ","じんこうそうぐ","からだ"],
+	"🖕": ["なかゆびをたてたて","からだ","ゆび","て","なかゆび"],
+	"🫵": ["みているひとをさしているひとさしゆび","さす","あなた","ゆび"],
+	"✍": ["かいているて","からだ","て","かく"],
+	"🤳": ["じどり","かめら","けいたい","うで"],
+	"💅": ["まにきゅあ","からだ","けあ","けしょうひん","こすめ","つめ","ねいる"],
+	"🦵": ["あし","からだ","きっく","てあし"],
+	"🦿": ["きかいのあし","あくせしびりてぃ","ぎそく","じんこうそうぐ","からだ"],
+	"🦶": ["あし","からだ","きっく","ふみつける"],
+	"👄": ["くち","からだ","くちびる"],
+	"🫦": ["かんでいるくちびる","しんぱい","こわい","うわき","しんけいしつ","ふゆかい","ふあん"],
+	"🦷": ["は","からだ","はいしゃ"],
+	"👅": ["した","からだ"],
+	"👂": ["みみ","からだ","はな"],
+	"🦻": ["ほちょうきをつけているみみ","あくせしびりてぃ","ほちょうき","きく","からだ","みみ"],
+	"👃": ["はな","からだ"],
+	"👁": ["め","からだ"],
+	"👀": ["め","からだ","かお"],
+	"🧠": ["のう","からだ","ぞうき","ちてき","かしこい"],
+	"🫀": ["かいぼうがくてきなしんぞう","かいぼうがく","しんぞうがく","しんぞう","ぞうき","みゃく"],
+	"🫁": ["はい","いき","こき","きゅうにゅう","ぞうき","こきゅう"],
+	"🦴": ["ほね","からだ","こっかく"],
+	"👤": ["じょうはんしんのしるえっと","じょうはんしん","しるえっと"],
+	"👥": ["じょうはんしんのしるえっと","じょうはんしん","しるえっと"],
+	"🗣": ["しゃべるあたまのしるえっと","かお","あたま","しるえっと","しゃべる","はなす"],
+	"🫂": ["はぐしているひとたち","さようなら","こんにちは","はぐ","ありがとう"],
+	"👶": ["あかちゃん"],
+	"👧": ["おんなのこ","しょうじょ","しょじょ","おとめざ","せいざ","こども"],
+	"🧒": ["こども","ひと","しょうねん","しょうじょ"],
+	"👦": ["おとこのこ","しょうねん","こども"],
+	"👩": ["じょせい","おんな"],
+	"🧑": ["せいじんむけ","ひと","おとな","だんせい","じょせい","おんな","おとこ"],
+	"👨": ["だんせい","くちひげ","おとこ"],
+	"👩‍🦱": ["じょせい","まきげ","かみ","おんな"],
+	"🧑‍🦱": ["ひと","まきげ","かみ"],
+	"👨‍🦱": ["だんせい","まきげ","かみ","おとこ"],
+	"👩‍🦰": ["じょせい","あかげ","あか","かみ","おんな"],
+	"🧑‍🦰": ["ひと","あかげ","あか","かみ"],
+	"👨‍🦰": ["だんせい","あかげ","あか","かみ","おとこ"],
+	"👱‍♀️": ["じょせい","きんぱつ","ぶろんど","かみ","おんな"],
+	"👱": ["ひと","きんぱつ","ぶろんど","かみ"],
+	"👱‍♂️": ["だんせい","きんぱつ","ぶろんど","かみ","おとこ"],
+	"👩‍🦳": ["じょせい","はくはつ","しろ","かみ","おんな"],
+	"🧑‍🦳": ["ひと","はくはつ","しろ","かみ"],
+	"👨‍🦳": ["だんせい","はくはつ","しろ","かみ","おとこ"],
+	"👩‍🦲": ["じょせい","はげ","おんな"],
+	"🧑‍🦲": ["ひと","はげ"],
+	"👨‍🦲": ["だんせい","はげ","おとこ"],
+	"🧔‍♀️": ["ひげのあるじょせい","あごひげ","ひげをはやした","じょせい","おんな"],
+	"🧔": ["あごひげのあるひと","あごひげ","ひげをはやした"],
+	"🧔‍♂️": ["ひげのあるだんせい","あごひげ","ひげをはやした","だんせい","おとこ"],
+	"👵": ["おばあさん","おばあちゃん","ろうじん","じょせい","おんな"],
+	"🧓": ["こうれいしゃ","ひと","だんせい","じょせい","おんな","おとこ"],
+	"👴": ["おじいさん","おじいちゃん","ろうじん","おとこ","だんせい"],
+	"👲": ["すかるきゃっぷをかぶっているひと","ちゅうごくぼう","ぼうし"],
+	"👳‍♀️": ["たーばんをまいているじょせい","たーばん","じょせい","おんな"],
+	"👳": ["たーばんをまいているひと","たーばん"],
+	"👳‍♂️": ["たーばんをまいているだんせい","たーばん","おとこ","だんせい"],
+	"🧕": ["へっどすかーふをかぶったじょせい","へっどすかーふ","ひじゃぶ","まんてぃら","てぃちぇる","ばんだな","あたまのすかーふ","じょせい","おんな"],
+	"👮‍♀️": ["じょせいけいさつかん","けいさつかん","けいかん","けいさつ","じょせい","おんな"],
+	"👮": ["けいさつかん","けいかん","けいさつ"],
+	"👮‍♂️": ["だんせいけいさつかん","けいさつかん","けいかん","けいさつ","おとこ","だんせい"],
+	"👩‍🚒": ["じょせいしょうぼうし","ひ","かじ","しょうぼう","しょうぼうし","じょせい","おんな"],
+	"🧑‍🚒": ["しょうぼうし","かじ"],
+	"👨‍🚒": ["だんせいしょうぼうし","ひ","かじ","しょうぼう","しょうぼうし","おとこ","だんせい"],
+	"👷‍♀️": ["じょせいのけんせつさぎょういん","こうじ","けんせつ","さぎょういん","じょせい","おんな"],
+	"👷": ["けんせつさぎょういん","こうじ","けんせつ","さぎょういん"],
+	"👷‍♂️": ["だんせいのけんせつさぎょういん","けんせつ","さぎょういん","だんせい","おとこ"],
+	"👩‍🏭": ["だんせいのこうじょうさぎょういん","こうじょう","こうぎょう","さぎょういん","じょせい","おんな"],
+	"🧑‍🏭": ["こうじょうさぎょういん","こうじょう","こうぎょう","ようせつ"],
+	"👨‍🏭": ["だんせいのこうじょうさぎょういん","こうじょう","こうぎょう","さぎょういん","おとこ","だんせい"],
+	"👩‍🔧": ["じょせいせいびし","しょくにん","はいかんこう","でんきぎし","しゅうりにん","じょせい","おんな"],
+	"🧑‍🔧": ["せいびし","しょくにん","はいかんこう","でんきぎし","しゅうりじん"],
+	"👨‍🔧": ["だんせいせいびし","しょくにん","はいかんこう","でんきぎし","しゅうりじん","おとこ","だんせい"],
+	"👩‍🌾": ["じょせいののうぎょうじゅうじしゃ","のうじょうろうどうしゃ","ぼくじょうぬし","にわし","のうか","じょせい","おんな"],
+	"🧑‍🌾": ["のうぎょうじゅうじしゃ","のうじょうろうどうしゃ","ぼくじょうぬし","にわし","のうか"],
+	"👨‍🌾": ["だんせいののうぎょうじゅうじしゃ","のうじょうろうどうしゃ","ぼくじょうぬし","にわし","のうか","おとこ","だんせい"],
+	"👩‍🍳": ["じょせいのりょうりにん","しょくひん","さーびす","しぇふ","こっく","りょうりにん","りょうり","じょせい","おんな"],
+	"🧑‍🍳": ["りょうりにん","しょくひん","さーびす","しぇふ","こっく","りょうり"],
+	"👨‍🍳": ["だんせいのりょうりじん","しょくひん","さーびす","しぇふ","こっく","りょうりにん","りょうり","おとこ","だんせい"],
+	"👩‍🎤": ["だんせいしんがー","おんがく","みゅーじしゃん","ろっく","ろっかー","ろっくすたー","げいのうじん","じょせい","おんな"],
+	"🧑‍🎤": ["かしゅ","おんがく","みゅーじしゃん","ろっく","ろっかー","ろっくすたー","げいのうじん"],
+	"👨‍🎤": ["だんせいしんがー","おんがく","みゅーじしゃん","ろっく","ろっかー","ろっくすたー","げいのうじん","おとこ","だんせい"],
+	"👩‍🎨": ["じょせいあーてぃすと","げいじゅつ","あーと","げいじゅつか","あーてぃすと","かいが","がか","じょせい","おんな"],
+	"🧑‍🎨": ["あーてぃすと","げいじゅつ","あーと","げいじゅつか","かいが","がか"],
+	"👨‍🎨": ["だんせいあーてぃすと","げいじゅつ","あーと","げいじゅつか","あーてぃすと","かいが","がか","おとこ","だんせい"],
+	"👩‍🏫": ["じょせいのきょうし","きょういく","せんせい","きょうじゅ","きょうし","こうし","じょせい","おんな"],
+	"🧑‍🏫": ["きょうし","きょういく","せんせい","きょうじゅ","こうし"],
+	"👨‍🏫": ["だんせいのきょうし","きょういく","せんせい","きょうじゅ","きょうし","こうし","おとこ","だんせい"],
+	"👩‍🎓": ["じょしせいと","がくせい","そつぎょうせい","きょういく","がっこう","じょせい","おんな"],
+	"🧑‍🎓": ["せいと","がくせい","そつぎょうせい","きょういく","がっこう"],
+	"👨‍🎓": ["だんしせいと","がくせい","そつぎょうせい","きょういく","がっこう","おとこ","だんせい"],
+	"👩‍💼": ["だんせいかいしゃいん","おふぃす","かいけいし","ぎんこうか","かんりしょく","こもん","じむいん","あなりすと","じょせい","おんな"],
+	"🧑‍💼": ["かいしゃいん","おふぃす","かいけいし","ぎんこうか","かんりしょく","こもん","じむいん","あなりすと"],
+	"👨‍💼": ["だんせいかいしゃいん","おふぃす","かいけいし","ぎんこうか","かんりしょく","こもん","じむいん","あなりすと","おとこ","だんせい"],
+	"👩‍💻": ["じょせいぎじゅつしゃ","てくのろじー","そふとうぇあ","えんじにあ","ぷろぐらまー","らっぷとっぷ","のーとぱそこん","じょせい","おんな"],
+	"🧑‍💻": ["ぎじゅつしゃ","てくのろじー","そふとうぇあ","えんじにあ","ぷろぐらまー","らっぷとっぷ","のーとぱそこん"],
+	"👨‍💻": ["だんせいぎじゅつしゃ","てくのろじー","そふとうぇあ","えんじにあ","ぷろぐらまー","らっぷとっぷ","のーとぱそこん","おとこ","だんせい"],
+	"👩‍🔬": ["じょせいかがくしゃ","かがくしゃ","ぎじゅつしゃ","すうがくしゃ","ぶつりがくしゃ","せいぶつがくしゃ","けんさぎし","じょせい","おんな"],
+	"🧑‍🔬": ["かがくしゃ","ぎじゅつしゃ","すうがくしゃ","ぶつりがくしゃ","せいぶつがくしゃ","けんさぎし"],
+	"👨‍🔬": ["だんせいかがくしゃ","かがくしゃ","ぎじゅつしゃ","すうがくしゃ","ぶつりがくしゃ","せいぶつがくしゃ","けんさぎし","おとこ","だんせい"],
+	"👩‍🚀": ["じょせいうちゅうひこうし","うちゅう","ほし","つき","わくせい","じょせい","おんな"],
+	"🧑‍🚀": ["うちゅうひこうし","うちゅう","ほし","つき","わくせい"],
+	"👨‍🚀": ["だんせいうちゅうひこうし","うちゅう","ほし","つき","わくせい","おとこ","だんせい"],
+	"👩‍⚕️": ["じょせいいりょうかんけいしゃ","いし","ないかい","いがくはかせ","かんごし","しかい","いりょうせんもんか","りょうほうし","じょせい","おんな"],
+	"🧑‍⚕️": ["いりょうかんけいしゃ","いし","ないかい","いがくはかせ","かんごし","しかい","いりょうせんもんか","りょうほうし"],
+	"👨‍⚕️": ["だんせいいりょうかんけいしゃ","いし","ないかい","いがくはかせ","かんごし","しかい","いりょうせんもんか","りょうほうし","おとこ","だんせい"],
+	"👩‍⚖️": ["じょせいさいばんかん","さいばんかん","ほうてい","さいばんしょ","ほうりつ","じょせい","おんな"],
+	"🧑‍⚖️": ["さいばんかん","ほうてい","さいばんしょ","ほうりつ"],
+	"👨‍⚖️": ["だんせいさいばんかん","さいばんかん","ほうてい","さいばんしょ","ほうりつ","おとこ","だんせい"],
+	"👩‍✈️": ["じょせいぱいろっと","ぱいろっと","ひこうき","そうじゅうし","こうくう","じょせい","おんな"],
+	"🧑‍✈️": ["ぱいろっと","ひこうき","そうじゅうし","こうくう"],
+	"👨‍✈️": ["だんせいぱいろっと","ぱいろっと","ひこうき","そうじゅうし","こうくう","おとこ","だんせい"],
+	"💂‍♀️": ["じょせいけいびいん","けいびいん","けいび","じょせい","おんな"],
+	"💂": ["けいびいん","けいび"],
+	"💂‍♂️": ["だんせいけいびいん","けいびいん","けいび","おとこ","だんせい"],
+	"🥷": ["にんじゃ","せんし","かくされた","すてるす"],
+	"🕵️‍♀️": ["じょせいのたんてい","たんてい","けいじ","すぱい","じょせい","おんな"],
+	"🕵": ["たんてい","けいじ","すぱい"],
+	"🕵️‍♂️": ["だんせいのたんてい","たんてい","けいじ","すぱい","おとこ","だんせい"],
+	"🤶": ["みせす・くろーす","いべんと","おいわい","くりすます","はは","さんた","くろーす","じょせい","おんな"],
+	"🧑‍🎄": ["みくすくろーす","あくてぃびてぃ","おいわい","くりすます","さんた","くろーす"],
+	"🎅": ["さんたくろーす","いべんと","おいわい","くりすます","ちち","さんた","くろーす","おとこ","だんせい"],
+	"👼": ["てんしのあかちゃん","てんし","あかちゃん","かお","おとぎばなし","ふぁんたじー"],
+	"👸": ["おひめさま","おとぎばなし","ふぁんたじー","じょおう","じょせい","おんな"],
+	"🫅": ["おうかんをかぶったひと","おとぎばなし","ふぁんたじー","こくおう","きぞく","おう","おうぞく"],
+	"🤴": ["おうじさま","おとぎばなし","ふぁんたじー","おう","おとこ","だんせい"],
+	"👰": ["べーるをつけたじょせい","はなよめ","べーる","けっこんしき","じょせい","おんな"],
+	"👰‍♀️": ["べーるをつけたひと","はなよめ","べーる","けっこんしき"],
+	"👰‍♂️": ["べーるをつけただんせい","はなよめ","べーる","うぇでぃんぐ","だんせい","おとこ"],
+	"🤵‍♀️": ["たきしーどのじょせい","たきしーど","うぇでぃんぐ","じょせい","おんな"],
+	"🤵": ["たきしーどをきるひと","はなむこ","たきしーど","うぇでぃんぐ"],
+	"🤵‍♂️": ["たきしーどのだんせい","はなむこ","たきしーど","うぇでぃんぐ","だんせい","おとこ"],
+	"🩷": ["ぴんくのはーと","かわいい","はーと","すき","あい","ぴんく"],
+	"🩵": ["らいとぶるーのはーと","しあん","はーと","らいとぶるー","こがも"],
+	"🩶": ["ぐれーのはーと","ぐれー","はーと","しるばー","すれーと"],
+	"🕴️‍♀️": ["ちゅうにういたすーつのじょせい","びじねす","すーつ","じょせい","おんな"],
+	"🕴": ["ちゅうにういたすーつのひと","びじねす","すーつ"],
+	"🕴️‍♂️": ["ちゅうにういたすーつのだんせい","びじねす","すーつ","おとこ","だんせい"],
+	"🦸‍♀️": ["じょせいのすーぱーひーろー","くうそう","ぜん","ひろいん","ちょうたいこく","じょせい","おんな"],
+	"🦸": ["すーぱーひーろー","くうそう","ぜん","ひーろー","ひろいん","ちょうたいこく"],
+	"🦸‍♂️": ["だんせいのすーぱーひーろー","くうそう","ぜん","ひーろー","ちょうたいこく","だんせい","おとこ"],
+	"🦹‍♀️": ["じょせいのあくとう","くうそう","あく","はんざい","あくじ","ちょうたいこく","あくやく","じょせい","おんな"],
+	"🦹": ["あくとう","くうそう","あく","はんざい","あくじ","ちょうたいこく","あくやく"],
+	"🦹‍♂️": ["だんせいのあくとう","くうそう","あく","はんざい","あくじ","ちょうたいこく","あくやく","だんせい","おとこ"],
+	"🧙‍♀️": ["じょせいのまほうつかい","くうそう","まじょ","おんなのまほうつかい","じょせい","おんな"],
+	"🧙": ["まほうつかい","くうそう","まじゅつし","おとこのまほうつかい"],
+	"🧙‍♂️": ["だんせいのまほうつかい","くうそう","まじゅつし","おとこのまほうつかい","だんせい","おとこ"],
+	"🧝‍♀️": ["じょせいのこども","くうそう","こども","さきのとがったみみ","じょせい","おんな"],
+	"🧝": ["こども","くうそう","さきのとがったみみ"],
+	"🧝‍♂️": ["だんせいのこども","くうそう","こども","さきのとがったみみ","だんせい","おとこ"],
+	"🧚‍♀️": ["じょせいのようせい","くうそう","てぃたーにあ","うぃんぐす","じょせい","おんな"],
+	"🧚": ["ようせい","くうそう","てぃたーにあ","うぃんぐす"],
+	"🧚‍♂️": ["だんせいのようせい","くうそう","おべろん","しょうようせい","だんせい","おとこ"],
+	"🧞‍♀️": ["じょせいのせいれい","くうそう","せいれい","じょせい","おんな"],
+	"🧞": ["せいれい","くうそう"],
+	"🧞‍♂️": ["だんせいのせいれい","くうそう","せいれい","だんせい","おとこ"],
+	"🧜‍♀️": ["じょせいのにんぎょ","くうそう","じょせい","おんな"],
+	"🧜": ["にんぎょ","くうそう"],
+	"🧜‍♂️": ["だんせいのにんぎょ","くうそう","にんぎょ","だんせい","おとこ"],
+	"🧌": ["つり","おとぎばなし","ふぁんたじ","もんすたー"],
+	"🧛‍♀️": ["じょせいのきゅうけつき","くうそう","あんでっど","じょせい","おんな"],
+	"🧛": ["きゅうけつき","くうそう","どらきゅら","あんでっど"],
+	"🧛‍♂️": ["だんせいのきゅうけつき","くうそう","どらきゅら","あんでっど","だんせい","おとこ"],
+	"🧟‍♀️": ["じょせいのぞんび","くうそう","あんでっど","じょせい","おんな"],
+	"🧟": ["ぞんび","くうそう","あんでっど"],
+	"🧟‍♂️": ["だんせいのぞんび","くうそう","あんでっど","だんせい","おとこ"],
+	"🙇‍♀️": ["ふかくおじぎするじょせい","しゃざい","おじぎ","じぇすちゃー","ごめんなさい","じょせい","おんな"],
+	"🙇": ["ふかくおじぎしたひと","しゃざい","おじぎ","じぇすちゃー","ごめんなさい"],
+	"🙇‍♂️": ["ふかくおじぎするだんせい","しゃざい","おじぎ","じぇすちゃー","ごめんなさい","おとこ","だんせい"],
+	"💁‍♀️": ["あんないするじょせい","て","たすけ","じょうほう","ずうずうしい","じょせい","おんな"],
+	"💁": ["あんないするひと","て","たすけ","じょうほう","ずうずうしい","じょせい","おんな"],
+	"💁‍♂️": ["あんないするだんせい","て","たすけ","じょうほう","ずうずうしい","おとこ","だんせい"],
+	"🙅‍♀️": ["NGさいんのじょせい","きんじる","じぇすちゃー","て","だめ","きんし","じょせい","おんな"],
+	"🙅": ["NGさいんのひと","きんじる","じぇすちゃー","て","だめ","きんし"],
+	"🙅‍♂️": ["NGさいんのだんせい","きんじる","じぇすちゃー","て","だめ","きんし","おとこ","だんせい"],
+	"🙆‍♀️": ["OKさいんのじょせい","じぇすちゃー","て","ok","じょせい","おんな"],
+	"🙆": ["OKさいんのひと","じぇすちゃー","て","OK"],
+	"🙆‍♂️": ["OKさいんのだんせい","じぇすちゃー","て","ok","おとこ","だんせい"],
+	"🤷‍♀️": ["かたをすくめるじょせい","うたがい","むち","むかんしん","かたをすくめる","じょせい","おんな"],
+	"🤷": ["かたをすくめるひと","うたがい","むち","むかんしん","かたをすくめる"],
+	"🤷‍♂️": ["かたをすくめるだんせい","うたがい","むち","むかんしん","かたをすくめる","おとこ","だんせい"],
+	"🙋‍♀️": ["かたてをあげてよろこぶじょせい","じぇすちゃー","て","しあわせ","あげる","じょせい","おんな"],
+	"🙋": ["かたてをあげてよろこぶひと","じぇすちゃー","て","しあわせ","あげる"],
+	"🙋‍♂️": ["かたてをあげてよろこぶだんせい","じぇすちゃー","て","しあわせ","あげる","おとこ","だんせい"],
+	"🤦‍♀️": ["かおをおさえるじょせい","ふしん","ふんがい","かお","てのひら","じょせい","おんな"],
+	"🤦": ["てのひらをかおにあてるひと","ふしん","ふんがい","かお","てのひら"],
+	"🤦‍♂️": ["がおをおさえるだんせい","ふしん","ふんがい","かお","てのひら","おとこ","だんせい"],
+	"🧏‍♀️": ["みみがふじゆうなじょせい","あくせしびりてぃ","みみがふじゆう","じょせい","おんな"],
+	"🧏": ["みみがふじゆうなひと","あくせしびりてぃ","みみがふじゆう"],
+	"🧏‍♂️": ["みみがふじゆうなだんせい","あくせしびりてぃ","みみがふじゆう","だんせい","おとこ"],
+	"🙎‍♀️": ["ふくれっつらのじょせい","じぇすちゃー","ふくれっつら","じょせい","おんな"],
+	"🙎": ["おこったかおのひと","じぇすちゃー","ふくれっつら"],
+	"🙎‍♂️": ["ふくれっつらのだんせい","じぇすちゃー","ふくれっつら","おとこ","だんせい"],
+	"🙍‍♀️": ["がおをしかめたじょせい","しかめめん","じぇすちゃー","かなしい","じょせい","おんな"],
+	"🙍": ["ふまんなかおのひと","しかめめん","じぇすちゃー","かなしい"],
+	"🙍‍♂️": ["がおをしかめただんせい","しかめめん","じぇすちゃー","かなしい","だんせい","おとこ"],
+	"💇‍♀️": ["かみをきられているじょせい","りはつし","びようし","びよう","さんぱつ","へあかっと","びよういん","じょせい","おんな"],
+	"💇": ["かみをきられているひと","りはつし","びようし","びよう","さんぱつ","へあかっと","びよういん"],
+	"💇‍♂️": ["かみをきられているだんせい","りはつし","びようし","びよう","さんぱつ","へあかっと","びよういん","おとこ","だんせい"],
+	"💆‍♀️": ["ふぇいすまっさーじをうけるじょせい","まっさーじ","さろん","じょせい","おんな"],
+	"💆": ["ふぇいすまっさーじをうけるひと","まっさーじ","さろん"],
+	"💆‍♂️": ["ふぇいすまっさーじをうけるだんせい","まっさーじ","さろん","おとこ","だんせい"],
+	"🤰": ["にんぷ","にんしん","あかちゃん","じょせい","おんな","はら","ふくれた","ふっくらした"],
+	"🫄": ["にんしんしたひと","はら","ふくれた","ふっくらした","にんしん","あかちゃん"],
+	"🫃": ["にんしんしているだんせい","はら","ふくれた","ふっくらした","にんしん","あかちゃん","だんせい","おとこ"],
+	"🤱": ["ぼにゅう","むね","あかちゃん","あかんぼう","にゅうじ","ようじ","はは","こども","ほいく","みるく","じょせい","おんな"],
+	"👩‍🍼": ["あかちゃんにごはんをあげるじょせい","あかちゃん","にゅうじ","こども","じゅにゅう","みるく","ぼとる","じょせい","おんな"],
+	"🧑‍🍼": ["あかちゃんにごはんをあげるひと","あかちゃん","にゅうじ","こども","じゅにゅう","みるく","ぼとる"],
+	"👨‍🍼": ["あかちゃんにごはんをあげるだんせい","あかちゃん","にゅうじ","こども","じゅにゅう","みるく","ぼとる","だんせい","おとこ"],
+	"🧎‍♀️": ["ひざたちしているじょせい","ひざ","ひざたち","じょせい","おんな"],
+	"🧎": ["ひざたちしているひと","ひざ","ひざたち"],
+	"🧎‍♂️": ["ひざたちしているだんせい","ひざ","ひざたち","だんせい","おとこ"],
+	"🧍‍♀️": ["たっているじょせい","たつ","すたんでぃんぐ","じょせい","おんな"],
+	"🧍": ["たっているひと","たつ","すたんでぃんぐ"],
+	"🧍‍♂️": ["たっているだんせい","たつ","すたんでぃんぐ","だんせい","おとこ"],
+	"🚶‍♀️": ["あるくじょせい","はいきんぐ","ほこうしゃ","あるく","うぉーきんぐ","じょせい","おんな"],
+	"🚶": ["あるくひと","はいきんぐ","ほこうしゃ","あるく","うぉーきんぐ"],
+	"🚶‍♂️": ["あるくだんせい","はいきんぐ","ほこうしゃ","あるく","うぉーきんぐ","おとこ","だんせい"],
+	"👩‍🦯": ["しろつえをもったじょせい","あくせしびりてぃ","めがふじゆう","じょせい","おんな"],
+	"🧑‍🦯": ["しろつえをもったひと","あくせしびりてぃ","めがふじゆう"],
+	"👨‍🦯": ["しろつえをもっただんせい","あくせしびりてぃ","めがふじゆう","だんせい","おとこ"],
+	"🏃‍♀️": ["はしるじょせい","まらそん","らんなー","らんにんぐ","じょせい","おんな"],
+	"🏃": ["はしるひと","まらそん","らんなー","らんにんぐ"],
+	"🏃‍♂️": ["はしるだんせい","まらそん","らんなー","らんにんぐ","おとこ","だんせい"],
+	"👩‍🦼": ["でんどうくるまいすにすわっているじょせい","あくせしびりてぃ","くるまいす","じょせい","おんな"],
+	"🧑‍🦼": ["でんどうくるまいすにすわっているひと","あくせしびりてぃ","くるまいす"],
+	"👨‍🦼": ["でんどうくるまいすにすわっているだんせい","あくせしびりてぃ","くるまいす","だんせい","おとこ"],
+	"👩‍🦽": ["しゅどうくるまいすにすわっているじょせい","あくせしびりてぃ","くるまいす","じょせい","おんな"],
+	"🧑‍🦽": ["しゅどうくるまいすにすわっているひと","あくせしびりてぃ","くるまいす"],
+	"👨‍🦽": ["しゅどうくるまいすにすわっているだんせい","あくせしびりてぃ","くるまいす","だんせい","おとこ"],
+	"💃": ["じょせいだんさー","だんす","おどる","だんさー","じょせい","おんな"],
+	"🕺": ["だんせいだんさー","だんす","おどる","だんさー","おとこ","だんせい"],
+	"👯‍♀️": ["ばにーがーる","うさぎみみ","だんさー","じょせい","おんな"],
+	"👯": ["うさぎみみのひと","うさぎみみ","だんさー"],
+	"👯‍♂️": ["うさぎみみのだんせい","うさぎみみ","だんさー","おとこ","だんせい"],
+	"👫": ["しゅをつないだだんじょ","かっぷる","て","つなぐ","おとこ","おんな","だんじょ"],
+	"👭": ["しゅをつないだじょせい","かっぷる","て","つなぐ","じょせい","おんな","ぷらいど","lgbt","れずびあん"],
+	"👬": ["しゅをつないだだんせい","かっぷる","て","つなぐ","だんせい","おとこ","ぷらいど","lgbt","げい"],
+	"🧑‍🤝‍🧑": ["しゅをつないだひとたち","かっぷる","て","にぎる"],
+	"👩‍❤️‍👨": ["はーとのかっぷる (じょせい、だんせい)","かっぷる","はーと","あい","れんあい","おとこ","おんな","だんじょ"],
+	"👩‍❤️‍👩": ["はーとのかっぷる (じょせい、じょせい)","かっぷる","はーと","あい","れんあい","じょせい","おんな","ぷらいど","lgbt","れずびあん"],
+	"💑": ["はーとのかっぷる","かっぷる","はーと","あい","れんあい","おとこ","おんな","だんじょ"],
+	"👨‍❤️‍👨": ["はーとのかっぷる (だんせい、だんせい)","かっぷる","はーと","あい","れんあい","だんせい","おとこ","ぷらいど","lgbt","げい"],
+	"👩‍❤️‍💋‍👨": ["きす (じょせい、だんせい)","かっぷる","きす","はーと","あい","れんあい","おとこ","おんな","だんじょ"],
+	"👩‍❤️‍💋‍👩": ["きす (じょせい、じょせい)","かっぷる","きす","はーと","あい","れんあい","じょせい","おんな","ぷらいど","lgbt","げい"],
+	"💏": ["きす","かっぷる","はーと","あい","れんあい","おとこ","おんな","だんじょ"],
+	"👨‍❤️‍💋‍👨": ["きす (だんせい、だんせい)","かっぷる","きす","はーと","あい","れんあい","だんせい","おとこ","ぷらいど","lgbt","げい"],
+	"👪": ["かぞく","ちちおや","ははおや","おとこ","おんな","だんじょ","おとこのこ","こども"],
+	"👨‍👩‍👧": ["かぞく (だんせい、じょせい、おんなのこ)","ちちおや","ははおや","おとこ","おんな","だんじょ","おんなのこ","こども"],
+	"👨‍👩‍👧‍👦": ["かぞく (だんせい、じょせい、おんなのこ、おとこのこ)","ちちおや","ははおや","おとこ","おんな","だんじょ","おとこのこ","おんなのこ","こども"],
+	"👨‍👩‍👦‍👦": ["かぞく (だんせい、じょせい、おとこのこ、おとこのこ)","ちちおや","ははおや","おとこ","おんな","だんじょ","おとこのこ","こども"],
+	"👨‍👩‍👧‍👧": ["かぞく (だんせい、じょせい、おんなのこ、おんなのこ)","ちちおや","ははおや","おとこ","おんな","だんじょ","おんなのこ","こども"],
+	"👩‍👩‍👦": ["かぞく (じょせい、じょせい、おとこのこ)","かぞく","ははおや","じょせい","おんな","おとこのこ","こども","ぷらいど","lgbt","れずびあん"],
+	"👩‍👩‍👧": ["かぞく (じょせい、じょせい、おんなのこ)","かぞく","ははおや","じょせい","おんな","おんなのこ","こども","ぷらいど","lgbt","れずびあん"],
+	"👩‍👩‍👧‍👦": ["かぞく (じょせい、じょせい、おんなのこ、おとこのこ)","かぞく","ははおや","じょせい","おんな","おとこのこ","おんなのこ","こども","ぷらいど","lgbt","れずびあん"],
+	"👩‍👩‍👦‍👦": ["かぞく (じょせい、じょせい、おとこのこ、おとこのこ)","かぞく","ははおや","じょせい","おんな","おとこのこ","こども","ぷらいど","lgbt","れずびあん"],
+	"👩‍👩‍👧‍👧": ["かぞく (じょせい、じょせい、おんなのこ、おんなのこ)","かぞく","ははおや","じょせい","おんな","おんなのこ","こども","ぷらいど","lgbt","れずびあん"],
+	"👨‍👨‍👦": ["かぞく (だんせい、だんせい、おとこのこ)","かぞく","ちちおや","だんせい","おとこ","おとこのこ","こども","ぷらいど","lgbt","げい"],
+	"👨‍👨‍👧": ["かぞく (だんせい、だんせい、おんなのこ)","かぞく","ちちおや","だんせい","おとこ","おんなのこ","こども","ぷらいど","lgbt","げい"],
+	"👨‍👨‍👧‍👦": ["かぞく (だんせい、だんせい、おんなのこ、おとこのこ)","かぞく","ちちおや","だんせい","おとこ","おとこのこ","おんなのこ","こども","ぷらいど","lgbt","げい"],
+	"👨‍👨‍👦‍👦": ["かぞく (だんせい、だんせい、おとこのこ、おとこのこ)","かぞく","ちちおや","だんせい","おとこ","おとこのこ","こども","ぷらいど","lgbt","げい"],
+	"👨‍👨‍👧‍👧": ["かぞく (だんせい、だんせい、おんなのこ、おんなのこ)","かぞく","ちちおや","だんせい","おとこ","おんなのこ","こども","ぷらいど","lgbt","げい"],
+	"👩‍👦": ["かぞく(じょせい、おとこのこ)","かぞく","ははおや","じょせい","おんな","おとこのこ","こども"],
+	"👩‍👧": ["かぞく(じょせい、おんなのこ)","かぞく","ははおや","じょせい","おんな","おんなのこ","こども"],
+	"👩‍👧‍👦": ["かぞく(じょせい、おんなのこ、おとこのこ)","かぞく","ははおや","じょせい","おんな","だんせい","おんなのこ","おとこのこ","こども"],
+	"👩‍👦‍👦": ["かぞく(じょせい、おとこのこ、おとこのこ)","かぞく","ははおや","じょせい","おんな","おとこのこ","こども"],
+	"👩‍👧‍👧": ["かぞく(じょせい、おんなのこ、おんなのこ)","かぞく","ははおや","じょせい","おんな","おんなのこ","こども"],
+	"👨‍👦": ["かぞく(だんせい、おとこのこ)","ちちおや","おとこ","だんせい","おとこのこ","こども"],
+	"👨‍👧": ["かぞく(だんせい、おんなのこ)","ちちおや","おとこ","だんじょ","おんなのこ","こども"],
+	"👨‍👧‍👦": ["かぞく(だんせい、おんなのこ、おとこのこ)","ちちおや","おとこ","だんせい","おとこのこ","おんなのこ","こども"],
+	"👨‍👦‍👦": ["かぞく(だんせい、おとこのこ、おとこのこ)","ちちおや","おとこ","だんせい","おとこのこ","こども"],
+	"👨‍👧‍👧": ["かぞく(だんせい、おんなのこ、おんなのこ)","ちちおや","おとこ","だんじょ","おんなのこ","こども"],
+	"👚": ["れでぃーすうぇあ","ふく","じょせい","おんな"],
+	"👕": ["てぃーしゃつ","ふく","しゃつ"],
+	"🥼": ["はくい","ふく","いしゃ","じっけん","かがくしゃ"],
+	"🦺": ["あんぜんべすと","きんきゅう","あんぜん","べすと"],
+	"🧥": ["こーと","ふく","じゃけっと"],
+	"👖": ["じーんず","ふく","ぱんつ","ずぼん"],
+	"👔": ["ねくたい","ふく"],
+	"👗": ["どれす","ふく"],
+	"👘": ["きもの","ふく","わふく"],
+	"🥻": ["さりー","ふく","どれす"],
+	"🩱": ["わんぴーす","ふく","みずぎ","すいみんぐうぇあ","すいえい"],
+	"👙": ["びきに","ふく","すいえい"],
+	"🩲": ["ぶりーふ","ふく","みずぎ","すいみんぐうぇあ","すいえい","したぎ"],
+	"🩳": ["しょーつ","ふく","みずぎ","すいみんぐうぇあ","すいえい","したぎ"],
+	"💄": ["くちべに","けしょうひん","こすめ","けしょう","めいく"],
+	"💋": ["きすまーく","はーと","きす","くちびる","まーく","れんあい","ろまんす"],
+	"👣": ["あしあと","からだ","ふく"],
+	"🧦": ["くつした","ふく","そっくす","いちくみ"],
+	"🩴": ["ごむせいさんだる","びーち","さんだる","ぞうり"],
+	"👠": ["はいひーる","ふく","ひーる","くつ","じょせい","おんな"],
+	"👡": ["れでぃーすさんだる","ふく","さんだる","くつ","じょせい","おんな"],
+	"👢": ["れでぃーすぶーつ","ぶーつ","ふく","くつ","じょせい","おんな"],
+	"🥿": ["れでぃーすふらっとしゅーず","ふく","ばれえふらっと","すりっぽん","すりっぱ"],
+	"👞": ["めんずしゅーず","ふく","だんせい","おとこ","くつ"],
+	"👟": ["うんどうくつ","うんどう","ふく","しゅーず","すにーかー"],
+	"🩰": ["ばれえしゅーず","ふく","しゅーず","ばれえ","だんす"],
+	"🥾": ["はいきんぐぶーつ","ふく","ばっくぱっく","ぶーつ","きゃんぷ","はいきんぐ"],
+	"🧢": ["きゃっぷ","ふく","やきゅう","はっと","ぼうし"],
+	"👒": ["れでぃーすはっと","ふく","ぼうし","じょせい","おんな"],
+	"🎩": ["しるくはっと","あくてぃびてぃ","ふく","えんたーていんめんと","ごらく","ぼうし","とっぷす"],
+	"🎓": ["そつぎょうしきのかくぼう","あくてぃびてぃ","ぼうし","おいわい","ふく","そつぎょう","はっと"],
+	"👑": ["かんむり","ふく","おうかん","おう","じょおう"],
+	"⛑": ["しろじゅうじのへるめっと","きゅうじょ","じゅうじ","かお","ぼうし","へるめっと"],
+	"🪖": ["ぐんたいのへるめっと","ぐん","へるめっと","ぐんたい","ぐんじん","へいし"],
+	"🎒": ["らんどせる","あくてぃびてぃ","かばん","ばっぐ","がくせいかばん","がっこう"],
+	"👝": ["ぽーち","かばん","ばっぐ","ふく"],
+	"👛": ["さいふ","ふく","こいん"],
+	"👜": ["はんどばっぐ","かばん","ばっぐ","ふく"],
+	"💼": ["ぶりーふけーす"],
+	"👓": ["めがね","ふく","め","あいうぇあ"],
+	"🕶": ["さんぐらす","くらい","め","めがね"],
+	"🥽": ["ごーぐる","ふく","めのほご","すいえい","ようせつ"],
+	"🧣": ["すかーふ","ふく","くび"],
+	"🧤": ["てぶくろ","ふく","て"],
+	"💍": ["ゆびわ","だいやもんど","れんあい","ろまんす"],
+	"🌂": ["とじたかさ","ふく","あめ","かさ","てんき"],
+	"☂": ["かさ","ふく","あめ","てんき"],
+	"🐶": ["いぬのかお","けん","いぬ","かお","ぺっと"],
+	"🐱": ["ねこのかお","ねこ","かお","ぺっと"],
+	"🐭": ["ねずみのかお","かお","ねずみ"],
+	"🐹": ["はむすたーのかお","かお","はむすたー","ぺっと"],
+	"🐰": ["うさぎのかお","ばにー","かお","ぺっと","うさぎ"],
+	"🐻": ["くまのかお","くま","かお"],
+	"🧸": ["てでぃべあ","おもちゃ","びろーど","ぬいぐるみ"],
+	"🐼": ["ぱんだのかお","かお","ぱんだ","くま"],
+	"🐻‍❄️": ["しろくま","かお","ほっきょく","くま","しろ"],
+	"🐨": ["こあら","くま","ゆうぶくろるい","おーすとらりあ"],
+	"🐯": ["とらのかお","かお","とら"],
+	"🦁": ["らいおんのかお","かお","ししざ","らいおん","せいざ"],
+	"🐮": ["うしのかお","うし","かお"],
+	"🐷": ["ぶたのかお","かお","ぶた"],
+	"🐽": ["ぶたのはな","かお","はな","ぶた"],
+	"🐸": ["かえるのかお","かお","かえる"],
+	"🐵": ["さるのかお","かお","さる"],
+	"🙈": ["みざる","わるい","かお","きんじる","じぇすちゃー","さる","だめ","きんし","みる"],
+	"🙉": ["きかざる","わるい","かお","きんじる","じぇすちゃー","きく","さる","ない","なし","きんし"],
+	"🙊": ["いわざる","わるい","かお","きんじる","じぇすちゃー","さる","ない","なし","きんし","はなす"],
+	"🐒": ["さる"],
+	"🦍": ["ごりら"],
+	"🦧": ["おらんうーたん","るいじんえん"],
+	"🐔": ["にわとり"],
+	"🐧": ["ぺんぎん"],
+	"🐦": ["とり"],
+	"🐦‍⬛": ["くろいとり","とり","くろ","からす","わたりがらす","みやまがらす"],
+	"🐤": ["ひよこ","あかちゃん"],
+	"🐣": ["ひよこ","あかちゃん","ふか"],
+	"🐥": ["しょうめんをむいたひよこ","あかちゃん","ひよこ"],
+	"🐺": ["おおかみのかお","かお","おおかみ"],
+	"🦊": ["きつねのかお","かお","きつね"],
+	"🦝": ["あらいぐま","かお","こうきしんがつよい","ずるかしこい"],
+	"🐗": ["いのしし","ぶた"],
+	"🐴": ["うまのかお","かお","うま"],
+	"🦓": ["しまうま","かお"],
+	"🦒": ["きりん","かお"],
+	"🦌": ["しか"],
+	"🫎": ["へらじか","どうぶつ","えだつの","えるく","ほにゅうるい"],
+	"🦘": ["かんがるー","おーすとらりあ","じゃんぷ","ゆうぶくろるい"],
+	"🦥": ["たいだ","なまける","おそい"],
+	"🦦": ["かわうそ","づり","ふざける"],
+	"🦫": ["びーばー","だむ"],
+	"🦄": ["ゆにこーんのかお","かお","ゆにこーん"],
+	"🐝": ["みつばち","はち","こんちゅう"],
+	"🐛": ["むし","こんちゅう"],
+	"🦋": ["ちょう","こんちゅう","うつくしい"],
+	"🐌": ["かたつむり"],
+	"🪲": ["かぶとむし","むし","こんちゅう"],
+	"🐞": ["てんとうむし","かぶとむし","こんちゅう","てんとうちゅう"],
+	"🐜": ["あり","こんちゅう"],
+	"🦗": ["くりけっと","こおろぎ","ばっため","こんちゅう"],
+	"🪳": ["ごきぶり","こんちゅう","がいちゅう"],
+	"🕷": ["くも","こんちゅう"],
+	"🕸": ["くものす","くも","す"],
+	"🦂": ["さそり","さそりざ","せいざ"],
+	"🦟": ["か","びょうき","ねつ","こんちゅう","まらりあ","ういるす"],
+	"🪰": ["はえ","がいちゅう","こんちゅう","うじむし"],
+	"🪱": ["ぜんちゅう","たまきがたどうぶつ","みみず","きせいちゅう"],
+	"🦠": ["びせいぶつ","あめーば","ばくてりあ","ういるす"],
+	"🐢": ["かめ"],
+	"🐍": ["へび","うんぱんにん","へびつかいざ","せいざ"],
+	"🦎": ["とかげ","はちゅうるい"],
+	"🐙": ["たこ"],
+	"🦑": ["いか","なんたいどうぶつ"],
+	"🪼": ["くらげ","くすり","むせきついどうぶつ","ぜりー","うみ","いたい","しもう"],
+	"🦞": ["ろぶすたー","びすく","つめ","しーふーど"],
+	"🦀": ["かに","かにざ","せいざ"],
+	"🦐": ["えび","かい","ちいさい"],
+	"🦪": ["かき","しんじゅ","だいびんぐ"],
+	"🐠": ["ねったいぎょ","さかな","ねったい"],
+	"🐟": ["さかな","うおざ","せいざ"],
+	"🐡": ["ふぐ","さかな"],
+	"🐬": ["いるか","ひれ"],
+	"🦈": ["さめ","さかな"],
+	"🦭": ["あざらし","あしか"],
+	"🐳": ["しおふきくじら","かお","しおふき","くじら"],
+	"🐋": ["くじら"],
+	"🐊": ["わに"],
+	"🐆": ["ひょう"],
+	"🐅": ["とら"],
+	"🐃": ["すいぎゅう","みず"],
+	"🐂": ["ゆううし","おすうし","おうしざ","せいざ"],
+	"🐄": ["うし"],
+	"🦬": ["ばいそん","ばっふぁろー","むれ","ヴぃせんと"],
+	"🐪": ["ひとこぶらくだ","らくだ","こぶ"],
+	"🐫": ["ふたこぶらくだ","ふたこぶ","らくだ","こぶ"],
+	"🦙": ["らま","あるぱか","ぐあなこ","びくーにゃ","うーる"],
+	"🐘": ["ぞう"],
+	"🦏": ["さい"],
+	"🦛": ["かば"],
+	"🦣": ["まんもす","ぜつめつ","おおがた","きば","けにおおわれた"],
+	"🐐": ["やぎ","やぎざ","せいざ"],
+	"🐏": ["こひつじ","おひつじざ","ひつじ","せいざ"],
+	"🐑": ["ひつじ","めすひつじ"],
+	"🐎": ["うま","けいば","れーす"],
+	"🫏": ["ろば","どうぶつ","ぶーろ","ほにゅうるい","らば"],
+	"🐖": ["ぶた","めすぶた"],
+	"🦇": ["こうもり","きゅうけつき"],
+	"🐓": ["おんどり"],
+	"🦃": ["しちめんちょう(とり)","しちめんちょう","とり"],
+	"🕊": ["へいわのはと","とり","はと","ひこう","へいわ"],
+	"🦅": ["わし","とり"],
+	"🦆": ["あひる","とり"],
+	"🪿": ["がちょう","とり","かきん","けいてきのおと"],
+	"🦢": ["はくちょう","とり","はくちょうのお","みにくいあひるのこ"],
+	"🦉": ["ふくろう","とり","かしこい"],
+	"🦩": ["ふらみんご","ねったい","あざやか"],
+	"🦚": ["おすのくじゃく","とり","めすのくじゃく"],
+	"🦜": ["おうむ","とり","かいぞく"],
+	"🦤": ["どーどー","とり","ぜつめつ"],
+	"🪽": ["はね","てんし","こうくう","とり","ひこう","しんわ"],
+	"🪶": ["うもう","とり","かるい","はね"],
+	"🐕": ["いぬ","けん","ぺっと"],
+	"🦮": ["もうどうけん","あくせしびりてぃ","めがふじゆう","けん","がいど"],
+	"🐕‍🦺": ["かいじょいぬ","あくせしびりてぃ","しえん","けん","さーびす"],
+	"🐩": ["ぷーどる","いぬ","けん"],
+	"🐈": ["ねこ","ぺっと"],
+	"🐈‍⬛": ["くろねこ","くろ","ねこ","ぺっと","はろうぃーん"],
+	"🐇": ["うさぎ","ばにー","ぺっと"],
+	"🐀": ["ねずみ"],
+	"🐁": ["ねずみ"],
+	"🐿": ["しまりす"],
+	"🦨": ["すかんく","あくしゅう","におう"],
+	"🦡": ["あなぐま","らーてる","ねだる"],
+	"🦔": ["はりねずみ","かお"],
+	"🐾": ["どうぶつのあしあと","あし","あと"],
+	"🐉": ["どらごん","おとぎばなし"],
+	"🐲": ["どらごんのかお","どらごん","かお","おとぎばなし"],
+	"🦕": ["りゅうあしるい","ぶらきおさうるす","ぶろんとさうるす","でぃぷろどくす","きょうりゅう"],
+	"🦖": ["てぃらのさうるす","Tれっくす","きょうりゅう"],
+	"🌵": ["さぼてん","しょくぶつ"],
+	"🎄": ["くりすますつりー","あくてぃびてぃ","おいわい","くりすます","えんたーていめんと","つりー"],
+	"🌲": ["じょうりょくじゅ","じょうりょく","しょくぶつ","はた"],
+	"🌳": ["らくようじゅ","らくようせい","しょくぶつ","らくよう","はた"],
+	"🌴": ["やしのき","やし","しょくぶつ","はた"],
+	"🪴": ["はちうえ","しょくぶつ","かんようしょくぶつ"],
+	"🌱": ["なえぎ","しょくぶつ","わかい"],
+	"🌿": ["はーぶ","は","しょくぶつ"],
+	"☘": ["くろーばー","しょくぶつ"],
+	"🍀": ["よっつはのくろーばー","4","くろーばー","よん","は","しょくぶつ"],
+	"🎍": ["かどまつ","あくてぃびてぃ","たけ","おいわい","にっぽん","まつ","しょくぶつ"],
+	"🎋": ["ななゆう","あくてぃびてぃ","はた","おいわい","えんたーていめんと","にっぽん"],
+	"🍃": ["かぜになびくは","ふく","はためく","は","しょくぶつ","ふう"],
+	"🍂": ["おちば","らっか","は","しょくぶつ"],
+	"🍁": ["かえでのは","らっか","は","かえで","しょくぶつ"],
+	"🌾": ["いなほ","いねたば","ほ","しょくぶつ","こめ"],
+	"🪺": ["たまごのあるす","すづくり","とりのす","たまご"],
+	"🪹": ["そらのす","すづくり","とりのす"],
+	"🌺": ["はいびすかす","はな","しょくぶつ"],
+	"🌻": ["ひまわり","はな","しょくぶつ","たいよう"],
+	"🌹": ["ばら","はな","しょくぶつ"],
+	"🥀": ["しおれたはな","はな","しおれた"],
+	"🌷": ["ちゅーりっぷ","はな","しょくぶつ"],
+	"🌼": ["はな","しょくぶつ"],
+	"🌸": ["さくら","はな","しょくぶつ"],
+	"🪷": ["はす","ぶっきょう","はな","ひんどぅーきょう","いんど","せいじょう","べとなむ"],
+	"🪻": ["ひあしんす","ぶるーぼんねっと","はな","らべんだー","るぴなす","のうるーず","むらさき","きんぎょそう"],
+	"💐": ["はなたば","はな","しょくぶつ","ろまんす"],
+	"🍄": ["きのこ","しょくぶつ"],
+	"🐚": ["まきがい","かい"],
+	"🪸": ["さんご","たいよう","しょう"],
+	"🌎": ["あめりかたいりく","あめりか","ちきゅう","せかい"],
+	"🌍": ["よーろっぱとあふりかちいき","あふりか","ちきゅう","よーろっぱ","せかい"],
+	"🌏": ["あじあとおーすとらりあ","あじあ","おーすとらりあ","ちきゅう","せかい"],
+	"🌕": ["まんげつ","つき","うちゅう","てんき"],
+	"🌖": ["ねまちのつき","じゅうさんや","つき","うちゅう","かけ","てんき"],
+	"🌗": ["かげんのつき","つき","げん","うちゅう","てんき"],
+	"🌘": ["かけていくみかづき","さんじつげつ","つき","うちゅう","かけ","てんき"],
+	"🌑": ["しんげつ","かい","つき","うちゅう","てんき"],
+	"🌒": ["みちていくみかづき","さんじつげつ","つき","うちゅう","じょうげん","てんき"],
+	"🌓": ["じょうげんのつき","つき","げん","うちゅう","てんき"],
+	"🌔": ["じゅうさんやつき","じゅうさんや","つき","うちゅう","じょうげん","てんき"],
+	"🌙": ["さんじつげつ","つき","うちゅう","てんき"],
+	"🌚": ["かおつきしんげつ","かお","つき","うちゅう","てんき"],
+	"🌝": ["かおつきまんげつ","あかるい","かお","みちた","つき","うちゅう","てんき"],
+	"🌛": ["かおつきじょうげんのつき","かお","つき","げん","うちゅう","てんき"],
+	"🌜": ["がおがあるかげんのつき","かお","つき","げん","うちゅう","てんき"],
+	"⭐": ["ちゅうくらいのほし","ほし"],
+	"🌟": ["ひかるほし","きらめき","あかいひかり","かがやく","かがやき","ほし"],
+	"💫": ["くらくら","まんが","めまい","ほし"],
+	"✨": ["きらきら","えんたーていめんと","かがやき","ほし"],
+	"☄": ["すいせい","うちゅう"],
+	"🪐": ["たまきのあるわくせい","うちゅう","わくせい","どせい"],
+	"🌞": ["かおつきたいよう","あかるい","かお","うちゅう","たいよう","てんき"],
+	"☀️": ["たいようのひかり","あかるい","こうせん","うちゅう","たいよう","せいてん","てんき"],
+	"🌤": ["たいようとちいさなくも","くも","たいよう","てんき"],
+	"⛅": ["はれときどきくもり","くも","たいよう","てんき"],
+	"🌥": ["はれのちくもり","くも","たいよう","てんき"],
+	"🌦": ["はれのちくもりときどきあめ","くも","あめ","たいよう","てんき"],
+	"☁️": ["くも","てんき"],
+	"🌧": ["あまぐも","くも","あめ","てんき"],
+	"⛈": ["らいう","くも","あめ","かみなり","てんき"],
+	"🌩": ["らいうん","くも","かみなり","てんき"],
+	"⚡": ["だかでんあつきごう","きけん","でんき","かみなり","でんあつ","びりびり"],
+	"🔥": ["えん","ひ","どうぐ"],
+	"💥": ["しょうとつまーく","どかーん","しょうとつ","まんが"],
+	"❄️": ["せつのけっしょう","つめたい","ゆき","てんき"],
+	"🌨": ["ゆきぐも","くも","れい","ゆき","てんき"],
+	"☃": ["ゆきだるま","れい","ゆき","てんき"],
+	"⛄": ["ゆきだるま","れい","ゆき","てんき"],
+	"🌬": ["かぜがふいている","かぜがふく","くも","かお","てんき","ふう"],
+	"💨": ["だっしゅ","まんが","はしる"],
+	"🌪": ["たつまきぐも","くも","たつまき","てんき","せんぷう"],
+	"🌫": ["きり","くも","てんき"],
+	"🌈": ["にじ","あめ","れいんぼー","てんき","ぷらいど","lgbt"],
+	"☔": ["うとかさ","いるい","しずく","あめ","かさ","てんき"],
+	"💧": ["しずく","ぞっとする","まんが","したたり","あせ","てんき"],
+	"💦": ["あせまーく","まんが","ぬれている","あせ"],
+	"🌊": ["なみ","うみ","みず","てんき"],
+	"🍏": ["あおりんご","りんご","ふるーつ","くだもの","みどり","しょくぶつ"],
+	"🍎": ["あかいりんご","りんご","ふるーつ","くだもの","しょくぶつ","あか"],
+	"🍐": ["なし","ふるーつ","くだもの","しょくぶつ"],
+	"🍊": ["みかん","ふるーつ","くだもの","おれんじ","しょくぶつ","あかだいだいいろ"],
+	"🍋": ["れもん","かんきつるい","ふるーつ","くだもの","しょくぶつ"],
+	"🍌": ["ばなな","ふるーつ","くだもの","しょくぶつ"],
+	"🍉": ["すいか","ふるーつ","くだもの","しょくぶつ"],
+	"🍇": ["ぶどう","ふるーつ","くだもの","しょくぶつ"],
+	"🍓": ["いちご","べりー","ふるーつ","くだもの","しょくぶつ"],
+	"🍈": ["めろん","ふるーつ","くだもの","しょくぶつ"],
+	"🍒": ["さくらんぼ","ふるーつ","くだもの","しょくぶつ"],
+	"🫐": ["ぶるーべりー","べりー","びるべりー","あお","ふるーつ"],
+	"🍑": ["もも","ふるーつ","くだもの","しょくぶつ"],
+	"🥭": ["まんごー","ねったい","ふるーつ"],
+	"🍍": ["ぱいなっぷる","ふるーつ","くだもの","しょくぶつ"],
+	"🥥": ["ここなっつ","ふるーつ"],
+	"🥝": ["きういふるーつ","ふるーつ","くだもの","きうい"],
+	"🍅": ["とまと","しょくぶつ","やさい"],
+	"🥑": ["あぼかど","ふるーつ","くだもの"],
+	"🫒": ["おりーぶ","ふるーつ"],
+	"🍆": ["なす","なすび","しょくぶつ","やさい"],
+	"🌶": ["とうがらし","からい","こしょう","しょくぶつ"],
+	"🫑": ["ぴーまん","とうがらし","こしょう","しょくぶつ","やさい"],
+	"🥒": ["きゅうり","ぴくるす","やさい"],
+	"🥬": ["はっぱのみどり","ちんげんさい","きゃべつ","けーる","れたす"],
+	"🥦": ["ぶろっこりー","やさい"],
+	"🫛": ["えんどうまめのさや","まめ","えだまめ","まめか","えんどうまめ","さや","やさい"],
+	"🧄": ["にんにく","やさい","しょくぶつ","こうみりょう"],
+	"🧅": ["たまねぎ","やさい","しょくぶつ","こうみりょう"],
+	"🌽": ["とうもろこし","こーん","しょくぶつ"],
+	"🥕": ["にんじん","やさい"],
+	"🥗": ["ぐりーんさらだ","みどり","さらだ"],
+	"🥔": ["じゃがいも","やさい"],
+	"🍠": ["やきいも","じゃがいも","やき","すいーつ"],
+	"🌰": ["くり","しょくぶつ"],
+	"🥜": ["ぴーなっつ","なっつ","やさい"],
+	"🫘": ["まめ","たべもの","じんぞう"],
+	"🍯": ["はにーぽっと","はちみつ","ぽっと","すいーつ"],
+	"🍞": ["ぱん","ろーふ"],
+	"🥐": ["くろわっさん","ぱん","さんじつげつ","ろーる","ふれんち"],
+	"🥖": ["ふらんすぱん","ぱん","ふれんち"],
+	"🫓": ["ふらっとぶれっど","あれぱ","らヴぁしゅ","なん","ぴた"],
+	"🥨": ["ぷれっつぇる","そふとぷれっつぇる","ぷれっつぇるついすと","ぱん"],
+	"🥯": ["べーぐる","ぱん","くりーむちーず","ひとぬり"],
+	"🥞": ["ぱんけーき","くれーぷ","ほっとけーき"],
+	"🧇": ["わっふる","ほっとけーき"],
+	"🧀": ["ちーず"],
+	"🍗": ["たーきー","ほね","にわとり","あし","かきん"],
+	"🍖": ["ほねつきにく","ほね","にく"],
+	"🥩": ["いちきれのにく","にく","きりみ","らむちょっぷ","ぶた","すてーき"],
+	"🍤": ["えびふらい","ふらい","えび","こえび","てんぷら"],
+	"🥚": ["たまご"],
+	"🍳": ["りょうり","たまご","ふらいぱん","なべ"],
+	"🥓": ["べーこん","にく"],
+	"🍔": ["はんばーがー","ばーがー"],
+	"🍟": ["ふらいどぽてと","ふらいど","ぽてと"],
+	"🌭": ["ほっとどっぐ","ふらんくふるとそーせーじ","ほっとどっぐそーせーじ","そーせーじ","うぃんなー","れっどほっと"],
+	"🍕": ["ぴざ","ちーず","1まい"],
+	"🍝": ["すぱげってぃ","ぱすた"],
+	"🥪": ["さんどうぃっち","ぱん","やさい","ちーず","にく","でり"],
+	"🌮": ["たこす","めきしこ"],
+	"🌯": ["ぶりとー","めきしこ"],
+	"🫔": ["たまーれ","たまーり","めきしかん","つつまれた"],
+	"🥙": ["ふらっとぶれっどさんど","ふぁらふぇる","ふらっとぶれっど","じゃいろ","けばぶ","つめもの"],
+	"🧆": ["ふぁらふぇる","ひよこまめ"],
+	"🍜": ["どんぶり","めん","らーめん","むしかねつ","すーぷ"],
+	"🥘": ["ぱえりあ","きゃせろーる","なべ","あさい"],
+	"🍲": ["なべ","しちゅー"],
+	"🫕": ["ふぉんでゅ","ちーず","ちょこれーと","ふぉでゅ","とけた","ぽっと","すいす"],
+	"🥫": ["かんづめ","ほぞんようしょくひん"],
+	"🫙": ["びん","こうしんりょう","ようき","そら","そーす","ちょぞう"],
+	"🧂": ["しお","こうしんりょう","しぇーかー"],
+	"🧈": ["ばたー","にゅうせいひん"],
+	"🫚": ["しょうが","びーる","ね","すぱいす"],
+	"🍥": ["なると","こけいのたべもの","さかな","ねりもの"],
+	"🍣": ["すし"],
+	"🍱": ["べんとうばこ","べんとう","はこ"],
+	"🍛": ["かれーらいす","かれー","ごはん"],
+	"🍙": ["おにぎり","にっぽん","こめ"],
+	"🍚": ["ごはん","りょうり","こめ"],
+	"🍘": ["せんべい","こめ"],
+	"🥟": ["ぎょうざ"],
+	"🍢": ["おでん","しーふーど","くし","すてぃっく"],
+	"🍡": ["だんご","でざーと","にっぽん","くし","すてぃっく","すいーつ"],
+	"🍧": ["かきごおり","でざーと","こおり","すいーつ"],
+	"🍨": ["あいすくりーむ","くりーむ","でざーと","こおり","すいーつ"],
+	"🍦": ["そふとくりーむ","くりーむ","でざーと","こおり","あいすくりーむ","そふと","すいーつ"],
+	"🍰": ["しょーとけーき","けーき","でざーと","ぺいすとりー","すらいす","すいーつ"],
+	"🎂": ["ばーすでーけーき","たんじょうび","けーき","おいわい","でざーと","ぺいすとりー","すいーつ"],
+	"🧁": ["かっぷけーき","べーかりー","すいーつ","でざーと","ぺいすとりー"],
+	"🥧": ["ぱい","でざーと","すいーつ"],
+	"🍮": ["かすたーど","でざーと","ぷりん","すいーつ"],
+	"🍭": ["ぺろぺろきゃんでぃー","きゃんでぃ","でざーと","ろりぽっぷきゃんでぃ","すいーつ"],
+	"🍬": ["あめ","でざーと","すいーつ"],
+	"🍫": ["ちょこれーと","ばー","でざーと","すいーつ"],
+	"🍿": ["ぽっぷこーん"],
+	"🍩": ["どーなつ","でざーと","すいーつ"],
+	"🍪": ["くっきー","でざーと","あまい"],
+	"🥠": ["おみくじいりくっきー","ふぉーちゅんくっきー"],
+	"🥮": ["げっぺい","あき","まつり"],
+	"☕": ["ほっとどりんく","いんりょう","こーひー","のみもの","あたたかい","じょうき","おちゃ"],
+	"🍵": ["ゆのみ","いんりょう","かっぷ","のみもの","おちゃ"],
+	"🫖": ["てぃーぽっと","どりんく","ぽっと","てぃー","けとる"],
+	"🥣": ["ぼうるとすぷーん","ちょうしょく","しりある","おかゆ","おーとみーる","ぽりっじ","しょっき"],
+	"🍼": ["ほにゅうびん","あかちゃん","ぼとる","どりんく","みるく"],
+	"🥤": ["かっぷとすとろー","じゅーす","そーだ","もると","そふとどりんく","みず","しょっき"],
+	"🧋": ["たぴおかてぃー","ばぶる","みるく","ぱーる","てぃー","ぼば","たぴおか","もみ"],
+	"🧃": ["いんりょうぼっくす","じゅーす","いんりょう","ぼっくす","どりんく","すとろー"],
+	"🧉": ["まて","どりんく","ぼんびりや","いえるば"],
+	"🥛": ["こっぷにはいったぎゅうにゅう","どりんく","ぐらす","みるく"],
+	"🫗": ["ながれこむえきたい","のみもの","そら","ぐらす","こぼれる"],
+	"🍺": ["びーる","ばー","のむ","まぐかっぷ"],
+	"🍻": ["かんぱい","ばー","びーる","かちん","のみもの","まぐかっぷ"],
+	"🍷": ["わいんぐらす","ばー","いんりょう","のみもの","ぐらす","わいん"],
+	"🥂": ["ぐらすでかんぱい","いわう","かちん","のみもの","ぐらす"],
+	"🥃": ["たんぶらー","ぐらす","て","しょっと","ういすきー","うぃすきー","ばーぼん"],
+	"🍸": ["かくてるぐらす","ばー","かくてる","のみもの","ぐらす"],
+	"🍹": ["とろぴかるどりんく","ばー","のみもの","とろぴかる"],
+	"🍾": ["びんととびだすせん","ばー","ぼとる","しゃんぱん","しゃんぺん","しゃんぱーにゅ","こるく","のみもの","とびだす","ぱーてぃー"],
+	"🍶": ["とっくりとおちょこ","ばー","いんりょう","ぼとる","かっぷ","のみもの","て"],
+	"🧊": ["かくこおり","こおり","りっぽうたい","つめたい","ひょうざん"],
+	"🥄": ["すぷーん","しょっき"],
+	"🍴": ["ふぉーくとないふ","ちょうり","ふぉーく","ないふ","しょっき"],
+	"🍽": ["ふぉーくとないふとぷれーと","ちょうり","ふぉーく","ないふ","ぷれーと","しょっき"],
+	"🥢": ["はし"],
+	"🥡": ["ていくあうとぼっくす","ていくあうと","ようき","おもちかえり"],
+	"⚽": ["さっかーぼーる","ぼーる","さっかー"],
+	"🏀": ["ばすけっとぼーる","ぼーる","ばすけっとりんぐ"],
+	"🏈": ["あめりかんふっとぼーる","あめりかん","ぼーる","ふっとぼーる"],
+	"⚾": ["やきゅう","ぼーる"],
+	"🥎": ["そふとぼーる","ぼーる","しあい","すぽーつ"],
+	"🎾": ["てにすぼーる","ぼーる","らけっと","てにす"],
+	"🏐": ["ばれーぼーる","ぼーる","しあい"],
+	"🏉": ["らぐびー","ぼーる","ふっとぼーる"],
+	"🎱": ["びりやーど","8","えいとぼーる","ぼーる","えいと","げーむ"],
+	"🥏": ["そらとぶえんばん","でぃすく","あるてぃめっと","ごるふ","しあい","すぽーつ","ふりすびー"],
+	"🪃": ["ぶーめらん","おーすとらりあ","ぎゃくもどり","はねかえり"],
+	"🏓": ["たっきゅうのらけっととぼーる","ぼーる","ばっと","しあい","ぱどる","たっきゅう"],
+	"🏸": ["ばどみんとんのらけっととしゃとる","ばどみんとん","ばーでぃー","しあい","らけっと","しゃとる"],
+	"🥅": ["ごーるねっと","ごーる","ねっと"],
+	"🏒": ["あいすほっけーのすてぃっくとぱっく","しあい","ほっけー","こおり","ぱっく","すてぃっく"],
+	"🏑": ["ふぃーるどほっけーのすてぃっくとぼーる","ぼーる","ふぃーるど","しあい","ほっけー","すてぃっく"],
+	"🏏": ["くりけっとのばっととぼーる","ぼーる","ふぃーるど","くりけっと","しあい"],
+	"🥍": ["らくろす","ぼーる","すてぃっく","しあい","すぽーつ"],
+	"🥌": ["かーりんぐすとーん","かーりんぐ","すとーん"],
+	"⛳": ["ごるふのかっぷ","ぴんふらっぐ","ごるふ","ほーる"],
+	"🏹": ["ゆみや","しゃしゅ","や","ゆみ","しゃしゅざ","どうぐ","せいざ"],
+	"🎣": ["つりざおとさかな","えんたーていめんと","さかな","ぼう"],
+	"🤿": ["だいびんぐますく","だいびんぐ","すきゅーば","しゅのーける"],
+	"🥊": ["ぼくしんぐぐろーぶ","ぼくしんぐ","ぐろーぶ"],
+	"🥋": ["どうぎ","じゅうどう","からて","ぶどう","てこんどー","ゆにふぉーむ"],
+	"⛸": ["あいすすけーと","こおり"],
+	"🎿": ["すきーとすきーぶーつ","すきー","ゆき"],
+	"🛷": ["そり","るーじゅ","とぼがん"],
+	"⛷": ["すきー","ゆき"],
+	"🏂": ["すのーぼーだー","すきー","ゆき","すのーぼーど"],
+	"🏋️‍♀️": ["うえいとをもちあげるじょせい","あげ","じゅうりょう","じょせい","おんな"],
+	"🏋": ["うえいとをもちあげるひと","あげ","じゅうりょう"],
+	"🏋️‍♂️": ["うえいとをもちあげるだんせい","あげ","じゅうりょう","おとこ","だんせい"],
+	"🤺": ["ふぇんしんぐをするひと","けんし","けんじゅつ","けん"],
+	"🤼‍♀️": ["れすりんぐをするじょせい","れすりんぐ","れすりんぐせんしゅ","じょせい","おんな"],
+	"🤼": ["れすりんぐをするひとたち","れすりんぐ","れすりんぐせんしゅ"],
+	"🤼‍♂️": ["れすりんぐをするだんせい","れすりんぐ","れすりんぐせんしゅ","おとこ","だんせい"],
+	"🤸‍♀️": ["そくてんをするじょせい","そくほうてんかい","たいそう","じょせい","おんな"],
+	"🤸": ["そくてんをするひと","そくほうてんかい","たいそう"],
+	"🤸‍♂️": ["そくてんをするだんせい","そくほうてんかい","たいそう","おとこ","だんせい"],
+	"⛹️‍♀️": ["ぼーるをばうんどさせるじょせい","ぼーる","じょせい","おんな"],
+	"⛹": ["ぼーるをばうんどさせるひと","ぼーる"],
+	"⛹️‍♂️": ["ぼーるをばうんどさせるだんせい","ぼーる","おとこ","だんせい"],
+	"🤾‍♀️": ["はんどぼーるをするじょせい","ぼーる","はんどぼーる","じょせい","おんな"],
+	"🤾": ["はんどぼーるをするひと","ぼーる","はんどぼーる"],
+	"🤾‍♂️": ["はんどぼーるをするだんせい","ぼーる","はんどぼーる","おとこ","だんせい"],
+	"🧗‍♀️": ["くらいみんぐしているじょせい","くらいみんぐ","ろっく","じょせい","おんな"],
+	"🧗": ["くらいみんぐしているひと","くらいみんぐ","ろっく"],
+	"🧗‍♂️": ["くらいみんぐしているだんせい","くらいみんぐ","ろっく","だんせい","おとこ"],
+	"🏌️‍♀️": ["ごるふをするじょせい","ぼーる","ごるふ","ごるふぁー","ごるふする","じょせい","おんな"],
+	"🏌": ["ごるふをするひと","ぼーる","ごるふ","ごるふぁー","ごるふする"],
+	"🏌️‍♂️": ["ごるふをするだんせい","ぼーる","ごるふ","ごるふぁー","ごるふする","おとこ","だんせい"],
+	"🧘‍♀️": ["れんげざのじょせい","めいそう","よが","せいおん","じょせい","おんな"],
+	"🧘": ["れんげざのひと","めいそう","よが","せいおん"],
+	"🧘‍♂️": ["れんげざのだんせい","めいそう","よが","せいおん","だんせい","おとこ"],
+	"🧖‍♀️": ["すちーむるーむにいるじょせい","さうな","すちーむるーむ","はまむ","すちーむばす","じょせい","おんな"],
+	"🧖": ["すちーむるーむにいるひと","さうな","すちーむるーむ","はまむ","すちーむばす"],
+	"🧖‍♂️": ["すちーむるーむにいるだんせい","さうな","すちーむるーむ","はまむ","すちーむばす","だんせい","おとこ"],
+	"🏄‍♀️": ["さーふぃんをするじょせい","さーふぁー","さーふぃん","なみのり","じょせい","おんな"],
+	"🏄": ["さーふぃんをするひと","さーふぁー","さーふぃん","なみのり"],
+	"🏄‍♂️": ["さーふぃんをするだんせい","さーふぁー","さーふぃん","なみのり","おとこ","だんせい"],
+	"🏊‍♀️": ["およぐじょせい","およぐ","すいえい","じょせい","おんな"],
+	"🏊": ["すいえいをするひと","およぐ","すいえい"],
+	"🏊‍♂️": ["およぐだんせい","およぐ","すいえい","おとこ","だんせい"],
+	"🤽‍♀️": ["すいきゅうをするじょせい","ぽろ","みず","すいきゅう","じょせい","おんな"],
+	"🤽": ["すいきゅうをするひと","ぽろ","みず","すいきゅう"],
+	"🤽‍♂️": ["すいきゅうをするだんせい","ぽろ","みず","すいきゅう","おとこ","だんせい"],
+	"🚣‍♀️": ["ぼーとをこぐじょせい","ぼーと","こぎぶね","のりもの","そうてい","じょせい","おんな"],
+	"🚣": ["ぼーとをこぐひと","ぼーと","こぎぶね","のりもの","そうてい"],
+	"🚣‍♂️": ["ぼーとをこぐだんせい","ぼーと","こぎぶね","のりもの","そうてい","おとこ","だんせい"],
+	"🏇": ["けいば","うま","きしゅ","きょうそうば"],
+	"🚴‍♀️": ["じてんしゃにのるじょせい","じてんしゃ","じてんしゃのり","じてんしゃにのるひと","さいくりすと","じょせい","おんな"],
+	"🚴": ["じてんしゃにのるひと","じてんしゃ","じてんしゃのり","さいくりすと"],
+	"🚴‍♂️": ["じてんしゃにのるだんせい","じてんしゃ","じてんしゃのり","じてんしゃにのるひと","さいくりすと","おとこ","だんせい"],
+	"🚵‍♀️": ["まうんてんばいくにのるじょせい","まうんてんばいくらいだー","くろすばいく","じてんしゃ","じてんしゃのり","じてんしゃにのるひと","さいくりすと","やま","じょせい","おんな"],
+	"🚵": ["まうんてんばいくにのるひと","まうんてんばいくらいだー","くろすばいく","じてんしゃ","じてんしゃのり","じてんしゃにのるひと","やま"],
+	"🚵‍♂️": ["まうんてんばいくにのるだんせい","まうんてんばいくらいだー","くろすばいく","じてんしゃ","じてんしゃのり","じてんしゃにのるひと","さいくりすと","やま","おとこ","だんせい"],
+	"🎽": ["らんにんぐしゃつとたすき","らんにんぐ","たすき","しゃつ"],
+	"🎖": ["くんしょう","おいわい","めだる","ぐんじ"],
+	"🏅": ["すぽーつのめだる","めだる"],
+	"🥇": ["きんめだる","1い","きん","めだる","1","だい1い"],
+	"🥈": ["ぎんめだる","めだる","2い","ぎん","2","だい2い"],
+	"🥉": ["どうめだる","どう","めだる","3い","3","だい3い"],
+	"🏆": ["とろふぃー","しょう"],
+	"🏵": ["ばらかざり","しょくぶつ"],
+	"🎗": ["りまいんだーりぼん","おいわい","りまいんだー","りぼん"],
+	"🎫": ["きっぷ","あくてぃびてぃ","にゅうじょうりょう","えんたーていめんと","ちけっと"],
+	"🎟": ["にゅうじょうけん","にゅうじょうりょう","えんたーていめんと","ちけっと"],
+	"🎪": ["さーかすごや","あくてぃびてぃ","さーかす","えんたーていめんと","てんと"],
+	"🤹‍♀️": ["じゃぐりんぐをするじょせい","てんびん","じゃぐりんぐ","じょせい","おんな"],
+	"🤹": ["じゃぐりんぐをするひと","ばらんす","じゃぐりんぐ"],
+	"🤹‍♂️": ["じゃぐりんぐをするだんせい","てんびん","じゃぐりんぐ","だんせい","おとこ"],
+	"🎭": ["ぶたいげいじゅつ","あくてぃびてぃ","げいじゅつ","えんたーていめんと","かめん","ぶたい","しあたー"],
+	"🎨": ["えのぐぱれっと","あくてぃびてぃ","あーと","えんたーていめんと","びじゅつかん","かいが","ぱれっと"],
+	"🎬": ["かちんこ","あくてぃびてぃ","えんたーていめんと","えいが"],
+	"🎤": ["まいく","あくてぃびてぃ","えんたーていめんと","からおけ","まいくろふぉん"],
+	"🎧": ["へっどほん","あくてぃびてぃ","いやほん","えんたーていめんと","へっどふぉん"],
+	"🎼": ["がくふ","あくてぃびてぃ","えんたーていめんと","おんがく"],
+	"🎹": ["けんばん","あくてぃびてぃ","えんたーていめんと","がっき","きーぼーど","おんがく","ぴあの"],
+	"🪗": ["あこーでぃおん","こんさーてぃーな","すくいーずぼっくす"],
+	"🥁": ["どらむ","どらむすてぃっく","おんがく"],
+	"🪘": ["ながいどらむ","びーと","こんが","どらむ","りずむ","じゃんべ"],
+	"🪇": ["まらかす","いわう","がっき","おんがく","そうおん","だがっき","がたがた","りずむ","しぇいく"],
+	"🎷": ["さっくす","あくてぃびてぃ","えんたーていめんと","がっき","おんがく","さくそふぉーん"],
+	"🎺": ["とらんぺっと","あくてぃびてぃ","えんたーていめんと","がっき","おんがく"],
+	"🪈": ["ふるーと","たけ","よこぶえそうしゃ","ふるーとそうしゃ","おんがく","ぱいぷ","りこーだー","ふく","もっかんがっき"],
+	"🎸": ["ぎたー","あくてぃびてぃ","えんたーていめんと","がっき","おんがく"],
+	"🪕": ["ばんじょー","あくてぃびてぃ","えんたーていめんと","がっき","おんがく"],
+	"🎻": ["ばいおりん","あくてぃびてぃ","えんたーていめんと","がっき","おんがく"],
+	"🎲": ["さいころ","さい","えんたーていめんと","げーむ"],
+	"🧩": ["ぱずるのぴーす","てがかり","かみあう","ぴーす","ぱずる","じぐそー"],
+	"♟️": ["ちぇすのぽーん","ちぇす","こま","げーむ","すてこま"],
+	"🎯": ["てきちゅう","あくてぃびてぃ","ぶる","ぶるずあい","だーつ","えんたーていめんと","め","しあい","ひっと","ひょうてき"],
+	"🎳": ["ぼうりんぐ","ぼーる","しあい"],
+	"🪀": ["よーよー","おもちゃ","じょうげ"],
+	"🪁": ["たこ","おもちゃ","とぶ","まう"],
+	"🛝": ["すべりだい","ゆうえんち","あそび"],
+	"🎮": ["てれびげーむ","こんとろーらー","えんたーていめんと","げーむ","びでおげーむ"],
+	"👾": ["えいりあん","うちゅうじん","かいじゅう","いせいじん","かお","おとぎばなし","ふぁんたじー","もんすたー","うちゅう","UFO"],
+	"🎰": ["すろっとましん","あくてぃびてぃ","げーむ","すろっと"],
+	"🚗": ["じどうしゃ","くるま","のりもの"],
+	"🚙": ["きゃんぴんぐかー","れくりえーしょん","RV","のりもの"],
+	"🚕": ["たくしー","のりもの"],
+	"🛺": ["おーとりきしゃ","じんりきしゃ","とぅくとぅく"],
+	"🚌": ["ばす","のりもの"],
+	"🚎": ["とろりーばす","ばす","ろめんでんしゃ","しがいでんしゃ","のりもの"],
+	"🏎": ["れーしんぐかー","くるま","きょうそう"],
+	"🚓": ["ぱとかー","くるま","ぱとろーる","けいさつ","のりもの"],
+	"🚑": ["きゅうきゅうしゃ","のりもの"],
+	"🚒": ["しょうぼうしゃ","えんじん","えん","とらっく","のりもの"],
+	"🚐": ["まいくろばす","ばす","のりもの"],
+	"🛻": ["ぴっくあっぷとらっく","ぴっくあっぷ","とらっく","のりもの"],
+	"🚚": ["はいたつようとらっく","はいたつ","とらっく","のりもの"],
+	"🚛": ["とれーらー","おおがたとらっく","せみ","とらっく","のりもの"],
+	"🚜": ["とらくたー","のりもの"],
+	"🏍": ["れーすばいく","おーとばい","れーす"],
+	"🛵": ["すくーたー","もーたー"],
+	"🚲": ["じてんしゃ","ばいく","のりもの"],
+	"🦼": ["でんどうくるまいす","あくせしびりてぃ","くるまいす"],
+	"🦽": ["しゅどうくるまいす","あくせしびりてぃ","くるまいす"],
+	"🛴": ["きっくぼーど","きっく","すくーたー"],
+	"🛹": ["すけぼー","すけーと","ぼーど"],
+	"🛼": ["ろーらーすけーと","ろーらー","すけーと"],
+	"🛞": ["しゃりん","えん","たいや","かいてん"],
+	"🚨": ["ぱとらいと","くるま","ひかり","けいさつ","かいてん","のりもの","さいれん","けいこく"],
+	"🚔": ["ぱとかー","くるま","たいこうしゃ","けいさつ","のりもの"],
+	"🚍": ["ばす","たいこうしゃ","のりもの"],
+	"🚘": ["たいこうしゃ","じどうしゃ","くるま","のりもの"],
+	"🚖": ["たくしー","たいこうしゃ","のりもの"],
+	"🚡": ["ろーぷうぇい","くうちゅう","けーぶる","くるま","ごんどら","とらむうぇい","のりもの"],
+	"🚠": ["ろーぷうぇい","けーぶる","ごんどら","やま","のりもの"],
+	"🚟": ["こうかてつどう","てつどう","のりもの"],
+	"🚃": ["てつどうしゃりょう","くるま","でんき","てつどう","れっしゃ","ろめん","とろりーばす","のりもの"],
+	"🚋": ["ろめんでんしゃ","くるま","ろめん","とろりーばす","のりもの"],
+	"🚝": ["ものれーる","のりもの"],
+	"🚄": ["しんかんせん","てつどう","こうそく","れっしゃ","のりもの"],
+	"🚅": ["しんかんせん","だんがん","てつどう","こうそく","れっしゃ","のりもの"],
+	"🚈": ["らいとれーる","てつどう","のりもの"],
+	"🚞": ["さんがくてつどう","くるま","やま","てつどう","のりもの"],
+	"🚂": ["じょうききかんしゃ","えんじん","きかんしゃ","てつどう","じょうき","れっしゃ","のりもの"],
+	"🚆": ["でんしゃ","せんろ","のりもの"],
+	"🚇": ["ちかてつ","めとろ","のりもの"],
+	"🚊": ["ろめんでんしゃ","とろりーばす","のりもの"],
+	"🚉": ["えき","せんろ","でんしゃ","のりもの"],
+	"🚁": ["へりこぷたー","のりもの"],
+	"🛩": ["こがたこうくうき","ひこうき","のりもの"],
+	"✈️": ["ひこうき","のりもの"],
+	"🛫": ["ひこうきのりりく","ひこうき","ちぇっくいん","しゅっぱつ","のりもの"],
+	"🛬": ["ひこうきのちゃくりく","ひこうき","とうちゃく","ちゃくりく","のりもの"],
+	"🪂": ["ぱらしゅーと","ぱらせーる","すかいだいぶ","はんぐぐらいだー"],
+	"💺": ["ざせき","いす"],
+	"🛰": ["さてらいと","えいせい","うちゅう","のりもの"],
+	"🚀": ["ろけっと","うちゅう","のりもの"],
+	"🛸": ["そらとぶえんばん","UFO","うちゅうじん","いほしじん","うちゅう","くうそう"],
+	"🛶": ["かぬー","ぼーと"],
+	"⛵": ["よっと","ぼーと","りぞーと","うみ","のりもの"],
+	"🛥": ["もーたーぼーと","ぼーと","のりもの"],
+	"🚤": ["すぴーどぼーと","ぼーと","のりもの"],
+	"⛴": ["ふぇりー","ぼーと"],
+	"🛳": ["りょかくせん","りょかく","ふね","のりもの"],
+	"🚢": ["ふね","のりもの"],
+	"🛟": ["きゅうめいうきわ","うきわ","らいふじゃけっと","らいふせーばー","きゅうじょ","あんぜん"],
+	"⚓": ["いかり","ふね","つーる"],
+	"⛽": ["がそりんすたんど","ねんりょう","がそりん","きゅうゆき","さーびすすてーしょん"],
+	"🚧": ["こうじちゅう","こうじようふぇんす","けんせつこうじ"],
+	"🚏": ["ばすてい","ばす","ていし"],
+	"🚦": ["たてむきのしんごうき","しんごうき","しんごう","こうつう"],
+	"🚥": ["よこむきのしんごうき","しんごうき","しんごう","こうつう"],
+	"🛑": ["いちじていしひょうしき","はっかっけい","ひょうしき","ていし"],
+	"🎡": ["かんらんしゃ","あくてぃびてぃ","ゆうえんち","えんたーていめんと","ふぇりす"],
+	"🎢": ["じぇっとこーすたー","あくてぃびてぃ","ゆうえんち","こーすたー","えんたーていめんと","ろーらー"],
+	"🎠": ["めりーごーらんど","あくてぃびてぃ","めりーごーらうんど","えんたーていめんと","うま"],
+	"🏗": ["けんせつちゅう","たてもの","けんせつ"],
+	"🌁": ["きり","てんき"],
+	"🗼": ["とうきょうたわー","とうきょう","たわー"],
+	"🏭": ["こうじょう","たてもの"],
+	"⛲": ["ふんすい"],
+	"🎑": ["おつきみ","あくてぃびてぃ","おいわい","じゅしょうしき","えんたーていめんと","つき"],
+	"⛰": ["やま"],
+	"🏔": ["ゆきやま","さむい","やま","ゆき"],
+	"🗻": ["ふじさん","やま"],
+	"🌋": ["かざん","ふんか","やま","きしょう"],
+	"🗾": ["にっぽんれっとう","にっぽん","ちず"],
+	"🏕": ["きゃんぷ"],
+	"⛺": ["てんと","きゃんぷ"],
+	"🏞": ["こくりつこうえん","こうえん"],
+	"🛣": ["こうそくどうろ","はいうぇい","どうろ"],
+	"🛤": ["せんろ","てつどう","でんしゃ"],
+	"🌅": ["ひので","あさ","たいよう","てんこう"],
+	"🌄": ["やまからのひので","あさ","やま","たいよう","ひので","てんこう"],
+	"🏜": ["さばく"],
+	"🏖": ["びーちとかさ","びーち","かさ","ぱらそる"],
+	"🏝": ["むじんとう","さばく","しま"],
+	"🌇": ["びるにしずむゆうひ","たてもの","ゆうぐれ","たいよう","ゆうひ","てんき"],
+	"🌆": ["ゆうぐれのまちなみ","たてもの","まち","ゆうぐれ","ひぐれ","ふうけい","たいよう","ゆうひ","てんき"],
+	"🏙": ["まちなみ","たてもの","まち"],
+	"🌃": ["ほしぞら","よる","ほし","てんき"],
+	"🌉": ["よるのはし","はし","よる","てんき"],
+	"🌌": ["あまのがわ","うちゅう","てんき"],
+	"🌠": ["ながれぼし","あくてぃびてぃ","らっか","ながれる","うちゅう","ほし"],
+	"🎇": ["せんこうはなび","あくてぃびてぃ","おいわい","えんたーていめんと","はなび","きらきら"],
+	"🎆": ["はなび","あくてぃびてぃ","おいわい","えんたーていめんと"],
+	"🛖": ["こや","いえ","せんけいこ","ぱお"],
+	"🏘": ["いえ","たてもの"],
+	"🏰": ["せいようのしろ","たてもの","しろ","よーろっぱ"],
+	"🏯": ["にっぽんのしろ","たてもの","しろ","にっぽん"],
+	"🏟": ["すたじあむ"],
+	"🗽": ["じゆうのめがみ","じゆう","ぞう"],
+	"🏠": ["いえ","たてもの","じたく"],
+	"🏡": ["にわつきのいえ","たてもの","にわ","じたく","いえ"],
+	"🏚": ["はいきょ","たてもの","はいおく","いえ"],
+	"🏢": ["おふぃすびる","たてもの"],
+	"🏬": ["でぱーと","たてもの","てん"],
+	"🏣": ["にっぽんのゆうびんきょく","たてもの","にっぽん","ぽすと"],
+	"🏤": ["よーろっぱのゆうびんきょく","たてもの","よーろっぱ","ぽすと"],
+	"🏥": ["びょういん","たてもの","いし","くすり"],
+	"🏦": ["ぎんこう","たてもの"],
+	"🏨": ["ほてる","たてもの"],
+	"🏪": ["こんびにえんすすとあ","たてもの","こんびにえんす","すとあ"],
+	"🏫": ["がっこう","たてもの"],
+	"🏩": ["らぶほてる","たてもの","ほてる","らぶ"],
+	"💒": ["けっこんしき","あくてぃびてぃ","ちゃぺる","ろまんす"],
+	"🏛": ["れきしてきなたてもの","たてもの","れきしてきな"],
+	"⛪": ["きょうかい","たてもの","くりすちゃん","じゅうじか","しゅうきょう"],
+	"🕌": ["もすく","いすらむ","むすりむ","しゅうきょう"],
+	"🛕": ["ひんどぅーきょうじいん","ひんどぅーきょう","じいん","しゅうきょう"],
+	"🕍": ["しなごーぐ","ゆだやじん","ゆだやきょう","しゅうきょう","かいどう"],
+	"🕋": ["かあば","いすらむ","むすりむ","しゅうきょう"],
+	"⛩": ["じんじゃ","しゅうきょう","しんとう"],
+	"⌚": ["うでどけい","とけい"],
+	"📱": ["けいたいでんわ","けいたい","こみゅにけーしょん","もばいる","でんわ"],
+	"📲": ["ちゃくしんちゅう","やじるし","つうわ","けいたい","こみゅにけーしょん","もばいる","けいたいでんわ","じゅしん","でんわ"],
+	"💻": ["ぱそこん","のーとぱそこん","こんぴゅーたー","ぱーそなる"],
+	"⌨": ["きーぼーど","こんぴゅーたー"],
+	"🖥": ["ですくとっぷぱそこん","こんぴゅーたー","ですくとっぷ"],
+	"🖨": ["ぷりんたー","こんぴゅーたー"],
+	"🖱": ["3ぼたんまうす","3","ぼたん","こんぴゅーたー","まうす","さん"],
+	"🖲": ["とらっくぼーる","こんぴゅーたー"],
+	"🕹": ["じょいすてぃっく","えんたーていめんと","げーむ","びでおげーむ"],
+	"🗜": ["あっしゅく","つーる","けっかん"],
+	"💽": ["MD","ぱそこん","ひかりでぃすく","えんたーていめんと","みにでぃすく","こうがく"],
+	"💾": ["ふろっぴーでぃすく","こんぴゅーたー","でぃすく","ふろっぴー"],
+	"💿": ["CDでぃすく","ぶるーれい","CD","こんぴゅーたー","でぃすく","DVD","こうがく"],
+	"📀": ["DVD","ぶるーれい","CD","こんぴゅーたー","でぃすく","えんたーていめんと","こうがく"],
+	"📼": ["びでおてーぷ","えんたーていめんと","てーぷ","VHS","びでお","びでおかせっと"],
+	"📷": ["かめら","えんたーていめんと","びでお"],
+	"📸": ["ふらっしゅをたいたかめら","かめら","ふらっしゅ","びでお"],
+	"📹": ["びでおかめら","かめら","えんたーていめんと","びでお"],
+	"🎥": ["びでおかめら","あくてぃびてぃ","かめら","しねま","えんたーていめんと","えいが"],
+	"📽": ["えいしゃき","しねま","ごらく","ふぃるむ","えいが","ぷろじぇくたー","びでお"],
+	"🎞": ["ふぃるむのふれーむ","しねま","えんたーていめんと","ふぃるむ","ふれーむ","えいが"],
+	"📞": ["じゅわき","こみゅにけーしょん","でんわ","じゅしんき"],
+	"☎️": ["でんわ","けいたいでんわ"],
+	"📟": ["ぽけっとべる","こみゅにけーしょん","ぽけべる"],
+	"📠": ["FAX","こみゅにけーしょん; fAX"],
+	"📺": ["てれび","えんたーていめんと","TV","びでお"],
+	"📻": ["らじお","えんたーていめんと","びでお"],
+	"🎙": ["すたじおまいく","まいく","おんがく","すたじお"],
+	"🎚": ["ちょうせつばー","ちょうせつ","おんがく","ばー"],
+	"🎛": ["こんとろーるのぶ","こんとろーる","つまみ","おんがく"],
+	"⏱": ["すとっぷうぉっち","とけい"],
+	"⏲": ["たいまーとけい","とけい","たいまー"],
+	"⏰": ["めざましとけい","あらーむ","とけい"],
+	"🕰": ["おきどけい","とけい"],
+	"⏳": ["すなどけい","すな","たいまー"],
+	"⌛": ["すなどけい","すな","たいまー"],
+	"🧮": ["そろばん","けいさん","かうんと","しゅうけいひょう","すうがく"],
+	"📡": ["えいせいあんてな","あんてな","こみゅにけーしょん","ぱらぼらあんてな","えいせい"],
+	"🔋": ["でんち","ばってりー","でんし","だかえねるぎー"],
+	"🪫": ["ばってりーざんりょうしょう","ばってりー","でんし","ていえねるぎー"],
+	"🔌": ["こんせんと","でんき","ぷらぐ"],
+	"💡": ["でんきゅう","まんが","でんき","ひらめき","ひかり"],
+	"🔦": ["かいちゅうでんとう","でんき","ひかり","どうぐ","たいまつ"],
+	"🕯": ["ろうそく","ひかり"],
+	"🧯": ["しょうかき","しょうか","ひ","けす"],
+	"🗑": ["ごみばこ","ごみ","かん","びん"],
+	"🛢": ["どらむかん","どらむ","おいる"],
+	"🛒": ["しょっぴんぐかーと","かーと","しょっぴんぐ","とろりー"],
+	"💸": ["はねのはえたおさつ","ぎんこう","しへい","せいきゅうしょ","どる","とぶ","おかね","はね"],
+	"💵": ["どるさつ","ぎんこう","しへい","おさつ","つうか","どる","おかね"],
+	"💴": ["えんきごうのはいったこぎって","ぎんこう","しへい","おさつ","つうか","おかね","えん"],
+	"💶": ["ゆーろさつ","ぎんこう","しへい","おさつ","つうか","ゆーろ","おかね"],
+	"💷": ["ぽんどさつ","ぎんこう","しへい","おさつ","つうか","おかね","ぽんど"],
+	"💰": ["どるぶくろ","ばっぐ","どる","おかね"],
+	"🪙": ["こいん","きん","きんぞく","おかね","ぎん","たから"],
+	"💳": ["くれじっとかーど","ぎんこう","かーど","くれじっと","おかね"],
+	"🪪": ["みぶんしょうめいしょ","しかくじょうほう","ID","らいせんす","せきゅりてぃ"],
+	"🧾": ["りょうしゅうしょ","かいけい","ぼき","しょうこ","しょうめい"],
+	"💎": ["ほうせき","だいあもんど","じゅえる","ろまんす"],
+	"⚖": ["はかり","てんびん","こうせい","てんびんざ","ものさし","どうぐ","じゅうりょう","せいざ"],
+	"🦯": ["しろつえ","あくせしびりてぃ","めがふじゆう"],
+	"🧰": ["どうぐばこ","むね","せいびし","こうぐ"],
+	"🔧": ["れんち","どうぐ"],
+	"🪛": ["どらいばー","ねじ","こうぐ"],
+	"🔨": ["はんまー","どうぐ"],
+	"⚒": ["はんまーとつるはし","はんまー","つるはし","どうぐ"],
+	"🛠": ["はんまーとれんち","はんまー","どうぐ","れんち"],
+	"⛏": ["つるはし","さいくつ","どうぐ"],
+	"🪓": ["おの","たたきぎり","ておの","われる","もくざい","こうぐ"],
+	"🪚": ["もっこうようのこぎり","だいく","ざいもく","のこぎり","こうぐ"],
+	"🔩": ["なっととぼると","ぼると","なっと","どうぐ"],
+	"⚙": ["はぐるま","ぎあ","どうぐ"],
+	"⛓": ["くさり"],
+	"🪝": ["ふっく","わな","いかさま","ぺてん","ゆうわく","ふぃっしんぐ","つーる"],
+	"🪜": ["はしご","のぼる","よこぎ","だん","こうぐ"],
+	"🧱": ["れんが","ねんど","けんせつ","もるたる","かべ"],
+	"🪨": ["ろっく","いわ","けんぞうぶつ","おもい","こたい","いし"],
+	"🪵": ["もくざい","けんぞうぶつ","まるた","ざいもく","はた"],
+	"🔫": ["みずでっぽう","みず","ぴすとる","ふんしゃき","じゅう"],
+	"🧨": ["ばくちく","だいなまいと","かやく","はなび"],
+	"💣": ["ばくだん"],
+	"🔪": ["ほうちょう","きっちんないふ","ちょうり","ないふ"],
+	"🗡": ["たんけん","ないふ"],
+	"⚔": ["こうさしたけん","こうさ","けん"],
+	"🛡": ["たて"],
+	"🚬": ["きつえんまーく","あくてぃびてぃ","きつえん"],
+	"⚰": ["かん","し"],
+	"🪦": ["はかいし","ぼち","し","ぼ","はかば","はろうぃーん"],
+	"⚱": ["こつつぼ","し","そうぎ"],
+	"🏺": ["あんふぉら","みずがめざ","りょうり","のみもの","みずさし","どうぐ","せいざ"],
+	"🔮": ["すいしょうだま","たま","すいしょう","おとぎばなし","ふぁんたじー","うらない","どうぐ"],
+	"🪄": ["まほうのつえ","まほう","ぼう","まじょ","まほうつかい"],
+	"📿": ["じゅずじょうのいのりのようぐ","じゅず","いるい","ねっくれす","いのり","しゅうきょう"],
+	"🧿": ["なざーるのおまもり","じゅずだま","おまもり","よこしまし","なざーる","ごふ"],
+	"🪬": ["はむさ","おまもり","ふぁてぃま","て","めありー","みりあむ","ほご"],
+	"💈": ["りはつてんのかんばんばしら","りはつてん","とこや","さんぱつ","かんばんばしら"],
+	"🧲": ["じしゃく","あとらくしょん","ばてい"],
+	"⚗": ["じょうりゅうき","かがく","じっけん","どうぐ"],
+	"🧪": ["しけんかん","かがくしゃ","かがく","じっけん","じっけんしつ"],
+	"🧫": ["ぺとりさら","ばくてりあ","せいぶつがくしゃ","せいぶつがく","ぶんか","じっけんしつ"],
+	"🧬": ["DNA","せいぶつがくしゃ","しんか","いでんし","いでんしがく","せいめい"],
+	"🔭": ["ぼうえんきょう","つーる"],
+	"🔬": ["けんびきょう","つーる"],
+	"🕳": ["あな"],
+	"🩻": ["Xせん","ほね","いし","いりょう","こっかく"],
+	"💊": ["くすり","いし","ぴる","びょうき"],
+	"💉": ["ちゅうしゃき","いし","くすり","ちゅうしゃはり","ちゅうしゃ","びょうき","どうぐ","わくちん"],
+	"🩸": ["ち1てき","いし","くすり","けつえき","せいり"],
+	"🩹": ["がーぜつきばんそうこう","いし","くすり","ばんどえいど","ほうたい","ばんそうこう"],
+	"🩺": ["ちょうしんき","いし","くすり","しんぞう"],
+	"🌡": ["おんどけい","てんき","おんど"],
+	"🩼": ["まつばづえ","つえ","しょうがい","けが","いどうほじょ","ぼう"],
+	"🏷": ["らべる","にふだ"],
+	"🔖": ["ぶっくまーく","しおり","しるし"],
+	"🚽": ["といれ"],
+	"🪠": ["ぷらんじゃー","ふぉーすかっぷ","はいかんこう","きゅういん","といれ"],
+	"🚿": ["しゃわー","みず"],
+	"🛁": ["ばすたぶ","ふろ","よくそう"],
+	"🛀": ["ふろ","よくそう"],
+	"🪮": ["へあぴっく","あふろ","くし","かみ","ぴっく"],
+	"🪥": ["はぶらし","ばするーむ","ぶらし","きれい","はいしゃ","えいせい","は"],
+	"🪒": ["かみそり","するどい","ひげすり"],
+	"🧴": ["ろーしょんぼとる","ろーしょん","ほしめざい","しゃんぷー","ひやけとめ"],
+	"🧻": ["ぺーぱーろーる","ぺーぱーたおる","といれっとぺーぱー"],
+	"🧼": ["せっけん","ぼう","みずあび","くりーにんぐ","あわ","せっけんいれ"],
+	"🫧": ["ばぶる","げっぷ","きれい","せっけん","すいちゅう"],
+	"🧽": ["すぽんじ","きゅうしゅう","くりーにんぐ","たこうせい"],
+	"🧹": ["ほうき","くりーにんぐ","そうじ","まじょ"],
+	"🧺": ["ばすけっと","のうぎょう","らんどりー","ぴくにっく"],
+	"🪣": ["ばけつ","たる","ておけ","おおだる"],
+	"🔑": ["かぎ","じょう","ぱすわーど"],
+	"🗝": ["ふるいかぎ","かぎ","じょう","ふるい"],
+	"🪤": ["ねずみとりき","えさ","ねずみ","かじはどうぶつ","わなわ","わな"],
+	"🛋": ["そふぁーとらんぷ","そふぁー","ほてる","らんぷ"],
+	"🪑": ["いす","ざせき","すわる"],
+	"🛌": ["しゅくはくしせつ","ねる","ほてる","すいみん","べっど"],
+	"🛏": ["べっど","ほてる","すいみん"],
+	"🚪": ["どあ","とびら"],
+	"🪞": ["かがみ","はんしゃ","はんしゃたい","はんしゃきょう"],
+	"🪟": ["まど","わく","しんせんなくうき","がらす","かいこうぶ","とうめい","しかい"],
+	"🧳": ["てにもつ","ぱっきんぐ","りょこう","すーつけーす"],
+	"🛎": ["たくじょうべる","べる","ほてる"],
+	"🖼": ["がくにはいったしゃしん","あーと","がくぶち","びじゅつかん","かいが","しゃしん"],
+	"🧭": ["こんぱす","じしゃく","なびげーしょん","おりえんてーりんぐ"],
+	"🗺": ["せかいちず","ちず","せかい"],
+	"⛱": ["たてられたぱらそる","あめ","はれ","かさ","てんき"],
+	"🪭": ["おりたたみせんす","れいきゃく","えんりょがち","だんす","ふぁん","ふらったー","ねつ","あつい","うちき","ひろがる"],
+	"🗿": ["もやいぞう","もあいぞう","かお","ぞう"],
+	"🛍": ["かいものぶくろ","かばん","ほてる","かいもの"],
+	"🎈": ["ふうせん","あくてぃびてぃ","おいわい","えんたーていめんと"],
+	"🎏": ["こいのぼり","あくてぃびてぃ","こい","おいわい","えんたーていめんと","はた","ふきながし"],
+	"🎀": ["りぼん","おいわい"],
+	"🧧": ["あかいふうとう","ぎふと","こううん","ほんばお","らいしー","おかね"],
+	"🎁": ["ぷれぜんと","はこ","おいわい","えんたーていめんと","おくりもの","ほうそう"],
+	"🎊": ["くすだま","あくてぃびてぃ","おいわい","かみふぶき","えんたーていめんと"],
+	"🎉": ["くらっかー","あくてぃびてぃ","おいわい","えんたーていめんと","ぱーてぃー","じゃーん"],
+	"🪅": ["ぴにゃーた","おいわい","ぱーてぃー","ぴなーた"],
+	"🪩": ["みらーぼーる","だんす","でぃすこ","かがやき","ぱーてぃー"],
+	"🪆": ["いれこにんぎょう","にんぎょう","いれこ","ろしあ"],
+	"🎎": ["ひなまつり","あくてぃびてぃ","おいわい","にんぎょう","えんたーていめんと","まつり","にっぽん"],
+	"🎐": ["ふうりん","あくてぃびてぃ","かね","おいわい","えんたーていめんと","ふう"],
+	"🏮": ["いざかやのちょうちん","あかちょうちん","いざかや","にっぽん","ちょうちん","あかり","あか"],
+	"🪔": ["でぃやらんぷ","でぃや","らんぷ","おいる"],
+	"✉️": ["ふうとう","Eめーる","でんしめーる"],
+	"📩": ["めーるじゅしんちゅう","やじるし","こみゅにけーしょん","した","Eめーる","でんしめーる","ふうとう","てがみ","めーる","おくる","そうしん"],
+	"📨": ["めーるじゅしん","こみゅにけーしょん","Eめーる","でんしめーる","ふうとう","うけとる","てがみ","めーる","じゅしん"],
+	"📧": ["Eめーる","こみゅにけーしょん","でんしめーる","てがみ","めーる"],
+	"💌": ["らぶれたー","はーと","てがみ","あい","めーる","ろまんす"],
+	"📮": ["ぽすと","こみゅにけーしょん","めーる","ゆうびんうけ"],
+	"📪": ["はたがさがっていてとじているじょうたいのゆうびんうけ","とじる","こみゅにけーしょん","はた","さがった","めーる","ぽすと","ゆうびんうけ"],
+	"📫": ["はたがあがっていてとじているじょうたいのゆうびんうけ","とじる","こみゅにけーしょん","はた","めーる","ゆうびんうけ","ぽすと"],
+	"📬": ["はたがあがっていてひらいているじょうたいのゆうびんうけ","こみゅにけーしょん","はた","めーる","ぽすと","あける","ゆうびんうけ"],
+	"📭": ["はたがさがっていてひらいているゆうびんうけ","こみゅにけーしょん","はた","さげ","めーる","めーるぼっくす","あける","ゆうびんうけ"],
+	"📦": ["にもつ","はこ","こみゅにけーしょん","ぱっけーじ","こづつみ"],
+	"📯": ["ゆうびんらっぱ","こみゅにけーしょん","えんたーていめんと","かく","ぽすと","ゆうびん"],
+	"📥": ["じゅしんとれい","はこ","こみゅにけーしょん","てがみ","めーる","じゅしん","とれい"],
+	"📤": ["そうしんとれい","はこ","こみゅにけーしょん","てがみ","めーる","そうしん","とれい"],
+	"📜": ["まきもの","かみ"],
+	"📃": ["げんこう","かーる","どきゅめんと","ぺーじ"],
+	"📑": ["ぶっくまーくたぶ","ぶっくまーく","まーく","まーかー","たぶ"],
+	"📊": ["ぼうぐらふ","ばー","ちゃーと","ぐらふ"],
+	"📈": ["じょうしょうするぐらふ","じょうしょうちゃーと","ちゃーと","ぐらふ","せいちょう","とれんど","うわむき"],
+	"📉": ["かこうするぐらふ","かこうちゃーと","ちゃーと","うえ","ぐらふ","とれんど"],
+	"📄": ["ぶんしょ","ぺーじ"],
+	"📅": ["かれんだー","ひづけ"],
+	"📆": ["ひめくりかれんだー","かれんだー"],
+	"🗓": ["りんぐかれんだー","かれんだー","ぱっど","らせんじょう"],
+	"📇": ["めいしふぉるだ","かーど","さくいん","ろーらでっくす"],
+	"🗃": ["かーどふぁいる","はこ","かーど","ふぁいる"],
+	"🗳": ["とうひょうようしととうひょうばこ","とうひょうようし","はこ","ひょう","とうひょう"],
+	"🗄": ["ふぁいるしゅうのうこ","しゅうのう","ふぁいる"],
+	"📋": ["くりっぷぼーど"],
+	"🗒": ["りんぐのーと","のーと","ぱっど","らせんじょう"],
+	"📁": ["ふぉるだ","ふぁいる"],
+	"📂": ["ひらいたふぉるだ","ふぁいる","ふぉるだ","ひらいた"],
+	"🗂": ["しきりかーど","かーど","しきり","さくいん"],
+	"🗞": ["まるめたしんぶん","にゅーす","しんぶん","かみ","まるめた"],
+	"📰": ["しんぶん","こみゅにけーしょん","にゅーす","かみ"],
+	"🪧": ["ぷらかーど","でも","しがらみ","こうぎ","かんばん"],
+	"📓": ["のーと"],
+	"📕": ["とじたほん","ほん","とじている"],
+	"📗": ["みどりいろのほん","ほん","みどり"],
+	"📘": ["あおいほん","あお","ほん"],
+	"📙": ["おれんじいろのほん","ほん","おれんじ"],
+	"📔": ["そうしょくかばーののーと","ほん","かばー","そうしょく","のーと"],
+	"📒": ["ちょうぼ","もとちょう","のーと"],
+	"📚": ["しょせき","ほん"],
+	"📖": ["ひらいたほん","ほん","ひらいた"],
+	"🔗": ["りんく"],
+	"📎": ["くりっぷ","ぺーぱーくりっぷ"],
+	"🖇": ["つながったぺーぱーくりっぷ","こみゅにけーしょん","りんく","ぺーぱーくりっぷ"],
+	"✂️": ["はさみ","どうぐ"],
+	"📐": ["さんかくじょうぎ","じょうぎ","はいち","さんかく"],
+	"📏": ["じょうぎ","ちょくじょうぎ"],
+	"📌": ["がびょう","ぴん"],
+	"📍": ["がびょう","ぴん"],
+	"🧷": ["あんぜんぴん","おむつ","ぱんくろっく"],
+	"🪡": ["ぬいはり","ししゅう","さいほう","ぬいめ","ほうごう","したて"],
+	"🧵": ["すれっど","ぬいあみ","さいほう","いとまき","いと","しゅこうげい"],
+	"🧶": ["いと","ぼーる","かぎばりあみ","にっと","しゅこうげい"],
+	"🪢": ["むすびめ","ろーぷ","からんだ","ひも","よりいと","ねじれ"],
+	"🔐": ["こいんろっかー","しまっている","かぎ","せじょう","ぼうはん"],
+	"🔒": ["かぎ","とじられた","せじょう"],
+	"🔓": ["かいじょう","せじょう","あける"],
+	"🔏": ["じょうまえとぺん","いんく","じょう","ぺんさき","ぺん","ぷらいばしー"],
+	"🖊": ["ひだりしたむきのぼーるぺん","ぼーるぺん","こみゅにけーしょん","ぺん"],
+	"🖋": ["ひだりしたむきのまんねんひつ","こみゅにけーしょん","まんねんひつ","ぺん"],
+	"✒️": ["ぺんさき","ぺん"],
+	"📝": ["めも","こみゅにけーしょん","えんぴつ"],
+	"✏️": ["えんぴつ"],
+	"🖍": ["ひだりしたむきのくれよん","こみゅにけーしょん","くれよん"],
+	"🖌": ["ひだりしたむきのぶらし","こみゅにけーしょん","ぺいんとぶらし","え"],
+	"🔍": ["ひだりむきむしめがね","めがね","かくだい","けんさく","つーる"],
+	"🔎": ["みぎむきむしめがね","めがね","かくだい","けんさく","つーる"],
+	"❤️": ["あかいろのはーと","はーと"],
+	"🧡": ["おれんじいろのはーと","はーと","おれんじいろ"],
+	"💛": ["きいろのはーと","はーと","きいろ"],
+	"💚": ["みどりのはーと","はーと","みどり"],
+	"💙": ["あおのはーと","はーと","あお"],
+	"💜": ["むらさきのはーと","はーと","むらさき"],
+	"🤎": ["ちゃいろのはーと","はーと","ちゃいろ"],
+	"🖤": ["くろいはーと","はーと","くろ","あく","わるもの"],
+	"🤍": ["しろのはーと","はーと","しろ"],
+	"💔": ["われたはーと","はーと","こわれる","はきょく"],
+	"❣": ["はーとのびっくりまーく","はーと","びっくりまーく","きごう"],
+	"💕": ["2つのはーと","はーと","あい"],
+	"💞": ["かいてんするはーと","はーと","かいてん"],
+	"💓": ["こどうするはーと","はーと","こどう","どきどき"],
+	"💗": ["ひかるはーと","はーと","わくわく","ひかる","こどう","きんちょう"],
+	"💖": ["きらめくはーと","はーと","わくわく","きらきら"],
+	"💘": ["いぬかれたはーと","はーと","や","きゅーぴっど","ろまんす"],
+	"💝": ["りぼんつきのはーと","はーと","りぼん","ばれんたいん"],
+	"❤️‍🔥": ["もえているはーと","はーと","ひ","もえる","あい","ねつじょう","しんせいなはーと"],
+	"❤️‍🩹": ["てあてしているはーと","はーと","けんこうになる","かいぜんしている","てあてしている","かいふくしている","やみあがり","げんき"],
+	"💟": ["はーとのでこれーしょん","はーと"],
+	"☮": ["ぴーすまーく","へいわ"],
+	"✝": ["らてんじゅうじ","くりすちゃん","じゅうじか","しゅうきょう"],
+	"☪": ["ほしとみかづき","いすらむ","むすりむ","しゅうきょう"],
+	"🕉": ["おーむまーく","ひんどぅーきょう","おーむ","しゅうきょう"],
+	"☸": ["ほうりん","ぶっきょうと","だーま","しゅうきょう"],
+	"✡": ["だびでのほし","だびで","ゆだやじん","ゆだやきょう","しゅうきょう","ほし"],
+	"🔯": ["ろくぼうせい","うらない","ほし"],
+	"🕎": ["はぬっきーやー","しょくだい","めのーらー","しゅうきょう"],
+	"☯": ["いんよう","しゅうきょう","どう","どうか","ひ","かげ"],
+	"☦": ["はったんじゅうじか","くりすちゃん","じゅうじか","しゅうきょう"],
+	"🪯": ["かんだ","しゅうきょう","しーくきょうと"],
+	"🛐": ["れいはいしょ","しゅうきょう","れいはい"],
+	"⛎": ["へびつかいざ","うんぱんにん","へび","せいざ"],
+	"♈": ["おひつじざ","こひつじ","せいざ"],
+	"♉": ["おうしざ","おすうし","ゆううし","せいざ"],
+	"♊": ["ふたござ","ふたご","せいざ"],
+	"♋": ["がん","かにざ","かに","せいざ"],
+	"♌": ["ししざ","らいおん","せいざ"],
+	"♍": ["おとめざ","おとめ","しょじょ","せいざ"],
+	"♎": ["てんびんざ","てんびん","こうせい","はかり","せいざ"],
+	"♏": ["さそりざ","さそり","せいざ"],
+	"♐": ["いてざ","しゃしゅ","しゃしゅざ","せいざ"],
+	"♑": ["やぎざ","やぎ","せいざ"],
+	"♒": ["みずがめざ","うんぱんじん","みず","せいざ"],
+	"♓": ["うおざ","さかな","せいざ"],
+	"🆔": ["しかくかこみID","ID","しきべつ"],
+	"⚛": ["げんそきごう","むしんろんしゃ","げんし"],
+	"⚕️": ["あすくれぴおすのつえ","けんこう","せわ","いし","くすり","つえ","へび"],
+	"☢": ["ほうしゃのうひょうしき","ほうしゃのう"],
+	"☣": ["ばいおはざーどひょうしき","せいぶつさいがい"],
+	"📴": ["けいたいでんわでんげんおふ","けいたい","こみゅにけーしょん","もばいる","おふ","けいたいでんわ","でんわ"],
+	"📳": ["まなーもーど","けいたい","こみゅにけーしょん","もばいる","もーど","けいたいでんわ","でんわ","ばいぶれーしょん"],
+	"🈶": ["しかくかこみゆう","にほんご","あり"],
+	"🈚": ["しかくかこみむ","しかくかこみいな","にほんご","なし"],
+	"🈸": ["しかくかこみしん","しかくかこみてき","ちゅうごくご","しんせい"],
+	"🈺": ["しかくかこみえい","ちゅうごくご","えいぎょう"],
+	"🈷️": ["しかくかこみつき","にほんご","つきぎめ"],
+	"✴️": ["はちりょうぼし","ほし"],
+	"🆚": ["しかくかこみVS","たい","VS"],
+	"🉑": ["まるかこみきょか","まるかこみか","ちゅうごくご","かのう"],
+	"💮": ["しろいはな","はな","たいへんよくできました"],
+	"🉐": ["まるかこみとく","にほんご","とく"],
+	"㊙️": ["まるかこみひ","ちゅうごくご","ひょういもじ","ひ"],
+	"㊗️": ["まるかこみしゅく","ちゅうごくご","おめでとう","しゅく"],
+	"🈴": ["しかくかこみのごう","しかくかこみごう","ちゅうごくご","ごうかく","てきごう"],
+	"🈵": ["しかくかこみまん","ちゅうごくご","まんしつ","まんしゃ","まんたん"],
+	"🈹": ["しかくかこみわり","しかくかこみのわり","にほんご","わりびき"],
+	"🈲": ["しかくかこみきん","にほんご","きんし"],
+	"🅰️": ["くろしかくかこみA","A","けつえきがた"],
+	"🅱️": ["くろしかくかこみB","B","けつえきがた"],
+	"🆎": ["くろしかくかこみAB","AB","けつえきがた"],
+	"🆑": ["しかくかこみCL","CL"],
+	"🅾️": ["くろしかくかこみO","けつえきがた","O"],
+	"🆘": ["しかくかこみSOS","へるぷ","SOS"],
+	"⛔": ["たちいりきんし","たちいり","きんし","だめ","できない","きんじる","こうつう"],
+	"📛": ["なふだ","ばっじ","なまえ"],
+	"🚫": ["しんにゅうきんし","たちいり","きんし","だめ","できない","きんじる"],
+	"❌": ["ばつしるし","きゃんせる","きごう","かけざん","じょうざん","x"],
+	"⭕": ["ふといおおきなまる","まる","O"],
+	"💢": ["いかりまーく","いかり","まんが","げきど"],
+	"♨️": ["おんせん","あたたかい","わきでる","じょうき"],
+	"🚷": ["ほこうしゃたちいりきんし","きんし","だめ","ない","ほこうしゃ","きんじる"],
+	"🚯": ["ぽいすてきんし","きんし","ごみ","だめ","ない","きんしされている"],
+	"🚳": ["じてんしゃきんし","じてんしゃ","ばいく","きんし","だめ","できない","きんじる","のりもの"],
+	"🚱": ["いんようふか","ひいんりょうすい","いんりょう","きんし","だめ","ない","いんよう","きんしされている","みず"],
+	"🔞": ["18さいみまんきんし","18","ねんれいせいげん","じゅうはち","きんし","だめ","ない","きんしした","みせいねんしゃ"],
+	"📵": ["けいたいでんわきんし","けいたい","つうしん","きんし","もばいる","だめ","できない","けいたいでんわ","きんしされている","でんわ"],
+	"🚭": ["きんえん","きんし","だめ","できない","きんしされている","きつえん"],
+	"❗": ["あかいびっくりまーく","びっくり","まーく","きごう"],
+	"❕": ["しろいびっくりまーく","びっくり","まーく","かこみ","きごう"],
+	"❓": ["あかいはてなまーく","まーく","きごう","はてな"],
+	"❔": ["しろいはてなまーく","まーく","かこみ","きごう","はてな"],
+	"‼️": ["!!まーく","ばんばん","びっくり","まーく","きごう"],
+	"⁉️": ["!?","びっくり","いんてろばんぐ","まーく","きごう","はてな"],
+	"💯": ["100てん","100","ふる","ひゃく","すこあ"],
+	"🔅": ["ていきど","あかるさ","うすぐらい","てい"],
+	"🔆": ["こうきど","あかるい","あかるさ"],
+	"🔱": ["とらいでんと","いかり","えんぶれむ","ふね","こうぐ"],
+	"⚜": ["ゆりのもんしょう"],
+	"〽️": ["いおりてん","しるし","ぶぶん"],
+	"⚠️": ["けいこく"],
+	"🚸": ["こうさてんをわたるこどもたち","こども","こうさてん","ほこうしゃ","こうつう"],
+	"🔰": ["しょしんしゃまーく","しょしんしゃ","まーく","みどり","にっぽん","わかば","どうぐ","き"],
+	"♻️": ["りさいくるまーく","りさいくる"],
+	"🈯": ["しかくかこみゆび","にほんご"],
+	"💹": ["じょうしょうとれんどのちゃーととえんきごう","じょうしょうちゅうえんちゃーと","ぎんこう","ちゃーと","つうか","ぐらふ","せいちょう","しじょう","おかね","じょうしょう","とれんど","うわむき","えん"],
+	"❇️": ["きらきら"],
+	"✳️": ["あすたりすく (8ほんこうせい)","あすたりすく"],
+	"❎": ["しかくでかこまれたばつしるし","まーく","しかく"],
+	"✅": ["しろいふとじのちぇっくまーく","ちぇっく","まーく"],
+	"💠": ["どっともようのだいや","まんが","だいやもんど","きかがく","ないぶ"],
+	"🌀": ["さいくろん","ていきあつ","めまい","たつまき","たいふう","てんき"],
+	"➿": ["にじゅうのかーるじょうのるーぷ","かーる","だぶる","るーぷ"],
+	"🌐": ["しごせん・けいせんのあるちきゅう","ちきゅう","ちきゅうぎ","けいせん","せかい"],
+	"♾": ["むげん","えいえん","ふへんてき"],
+	"Ⓜ️": ["まるかこみM","えん","M"],
+	"🏧": ["ATM","ATMきごう","じどう","ぎんこう","すいとう"],
+	"🚾": ["といれ","けしょうしつ","おてあらい","みず","WC"],
+	"♿": ["くるまいす","あくせす"],
+	"🅿️": ["くろしかくかこみP","ちゅうしゃじょう"],
+	"🈳": ["しかくかこみそら","しかくかこみのそら","ちゅうごくご","そらしつ","あき","くうしゃ"],
+	"🈂️": ["しかくかこみさ","にっぽんじん","さーびす"],
+	"🛂": ["にゅうこくしんさ","ぱすぽーと"],
+	"🛃": ["ぜいかん"],
+	"🛄": ["てにもつうけとりしょ","てにもつ","うけとり"],
+	"🛅": ["てにもつあずかりしょ","てにもつ","ろっかー","けいこうひん"],
+	"🚰": ["いんりょうすい","のみもの","みず"],
+	"🛗": ["えれべーたー","あくせしびりてぃ","ひきあげ","しょうこうき"],
+	"🚹": ["だんせいのきごう","だんせいよう","といれ","おとこ","だんせい"],
+	"♂️": ["だんせいきごう","だんせい","おとこ"],
+	"🚺": ["じょせいのきごう","じょせいよう","といれ","おんな","じょせい"],
+	"♀️": ["じょせいきごう","じょせい","おんな"],
+	"⚧️": ["とらんすじぇんだーさいん","とらんすじぇんだー","ぷらいど","lgbt"],
+	"🚼": ["あかちゃんまーく","あかちゃん","おむつかえ"],
+	"🚻": ["といれ","けしょうしつ","WC"],
+	"🚮": ["ごみすてじょう","びんのごみすてじょう","ごみ","ごみばこ"],
+	"🎦": ["えいが","あくてぃびてぃ","かめら","えんたーていめんと","ふぃるむ","どうが"],
+	"📶": ["あんてな","ばー","けいたい","こみゅにけーしょん","もばいる","けいたいでんわ","しぐなる","でんわ"],
+	"🛜": ["むせん","こんぴゅーた","いんたーねっと","ねっとわーく","Wi-Fi","せつぞく"],
+	"🈁": ["しかくかこみここ","にっぽんじん"],
+	"🆖": ["しかくかこみNG","NG"],
+	"🆗": ["しかくかこみOK","OK"],
+	"🆙": ["しかくかこみUP!","まーく","うえ"],
+	"🆒": ["COOL","かっこいい","くーる"],
+	"🆕": ["しかくかこみnew","しん"],
+	"🆓": ["しかくかこみFREE","ふりー","むりょう"],
+	"0️⃣": ["0きー","0","きー","ぜろ"],
+	"1️⃣": ["1きー","いち","きー"],
+	"2️⃣": ["2きー","2","きー","に"],
+	"3️⃣": ["3きー","3","きー","さん"],
+	"4️⃣": ["4きー","4","よん","きー"],
+	"5️⃣": ["5きー","5","ご","きー"],
+	"6️⃣": ["6きー","6","きー","ろく"],
+	"7️⃣": ["7きー","7","きー","なな"],
+	"8️⃣": ["8きー","8","はち","きー"],
+	"9️⃣": ["9きー","9","きー","きゅう"],
+	"🔟": ["10きー","10","きー","じゅう"],
+	"🔢": ["ばんごうのにゅうりょくきごう","1234","にゅうりょく","すうじ"],
+	"▶️": ["みぎむきさんかく","さいせいぼたん","やじるし","さいせい","みぎ","さんかっけい"],
+	"⏸": ["2ほんのすいちょくばー","いちじていしぼたん","ばー","2ばい","いちじていし","すいちょく"],
+	"⏯": ["みぎむきのさんかっけいとにじゅうすいちょくぼう","さいせいまたはいちじていしぼたん","やじるし","いちじていし","さいせい","みぎ","さんかっけい"],
+	"⏹": ["ていし","ていしぼたん","しかく"],
+	"⏺": ["ろくが","ろくがぼたん","まる"],
+	"⏏️": ["とりだしまーく","とりだしぼたん"],
+	"⏭": ["みぎむきのにじゅうさんかっけいとすいちょくぼう","「つぎのきょく」ぼたん","やじるし","つぎのばめん","つぎのきょく","さんかっけい"],
+	"⏮": ["ひだりむきのにじゅうさんかっけいとすいちょくぼう","「まえのきょく」ぼたん","やじるし","まえのばめん","まえのきょく","さんかっけい"],
+	"⏩": ["みぎむきのにじゅうさんかっけい","はやおくりぼたん","やじるし","2ばい","こうそく","すすむ"],
+	"⏪": ["ひだりむきのにじゅうさんかっけい","はやもどしぼたん","やじるし","2ばい","まきもどし"],
+	"🔀": ["ねじりみぎむきやじるしのえもじ","しゃっふる","やじるし","こうさ"],
+	"🔁": ["りぴーと","りぴーとぼたん","やじるし","とけいまわり"],
+	"🔂": ["1きょくをりぴーとさいせい","りぴーとぼたん","やじるし","とけいまわり","いちど"],
+	"◀️": ["ひだりむきのさんかっけい","はんてんぼたん","やじるし","ひだり","はんてん","さんかっけい"],
+	"🔼": ["うわむきのさんかっけい","うえぼたん","やじるし","ぼたん","うえ"],
+	"🔽": ["したむきのさんかっけい","したぼたん","やじるし","ぼたん","した"],
+	"⏫": ["うわむきのにじゅうさんかっけい","こうそくじょうしょうぼたん","やじるし","だぶる","うえ"],
+	"⏬": ["したむきのにじゅうさんかっけい","こうそくだうんぼたん","やじるし","だぶる","した"],
+	"➡️": ["みぎむきやじるし","みぎやじるし","やじるし","しゅよう","ほうこう","ひがし"],
+	"⬅️": ["ひだりむきやじるし","ひだりやじるし","やじるし","しゅよう","ほうこう","にし"],
+	"⬆️": ["うわむきやじるし","うえやじるし","やじるし","しゅよう","ほうこう","きた"],
+	"⬇️": ["したむきやじるし","したやじるし","やじるし","しゅよう","ほうこう","した","みなみ"],
+	"↗️": ["みぎうえやじるし","やじるし","ほうこう","ななめ","ほくとう"],
+	"↘️": ["みぎしたやじるし","やじるし","ほうこう","ななめ","なんとう"],
+	"↙️": ["ひだりしたやじるし","やじるし","ほうこう","ななめ","なんせい"],
+	"↖️": ["ひだりうえやじるし","やじるし","ほうこう","ななめ","ほくせい"],
+	"↕️": ["じょうげやじるし","やじるし","ほうこう","ななめ","ほくせい"],
+	"↔️": ["さゆうやじるし","やじるし"],
+	"🔄": ["うずまきやじるし","はんとけいまわり","やじるし","ひだりまわり"],
+	"↪️": ["みぎむきだんつきやじるし","みぎにまがったやじるし","やじるし"],
+	"↩️": ["ひだりむきだんつきやじるし","ひだりにまがったやじるし","やじるし"],
+	"🔃": ["るーぷやじるし","とけいのはり","やじるし","とけいまわり","りろーど"],
+	"⤴️": ["みぎうえへかーぶするやじるし","うえへかーぶするみぎやじるし","やじるし"],
+	"⤵️": ["みぎしたへかーぶするやじるし","したにかーぶするみぎやじるし","やじるし","した"],
+	"#️⃣": ["#きー","はっしゅ","きー","ぽんど"],
+	"*⃣": ["あすたりすくきー","あすたりすく","きー","ほし"],
+	"ℹ️": ["じょうほうげん","i","いんふぉめーしょん"],
+	"🔤": ["あるふぁべっとにゅうりょく","abc","あるふぁべっと","にゅうりょく","らてん","もじ"],
+	"🔡": ["あるふぁべっとこもじにゅうりょく","abcd","にゅうりょく","らてん","もじ","こもじ"],
+	"🔠": ["あるふぁべっとおおもじにゅうりょく","にゅうりょく","らてん","もじ","おおもじ"],
+	"🔣": ["きごうにゅうりょく","にゅうりょく"],
+	"🎵": ["おんぷ","あくてぃびてぃ","えんたーていめんと","おんがく"],
+	"🎶": ["ふくすうのおんぷ","あくてぃびてぃ","えんたーていめんと","おんがく","おんぷ"],
+	"〰️": ["はせん","だっしゅ","きごう","なみ"],
+	"➰": ["かーるじょうのるーぷ","かーる","るーぷ"],
+	"✔️": ["ふとじのちぇっくまーく","ちぇっく","まーく"],
+	"➕": ["ふとじの+きごう","すうがく","ぷらす"],
+	"➖": ["ふとじのまいなすきごう","すうがく","まいなす"],
+	"➗": ["ふとじのわるきごう","わりざん","すうがく"],
+	"✖️": ["ふとじのかけるしるし","きゃんせる","じょうざん","かける","x"],
+	"🟰": ["ふといとうごう","とうしき","すうがく","ひとしい"],
+	"💲": ["ふとじのどるきごう","つうか","どる","おかね"],
+	"💱": ["がいかりょうがえ","ぎんこう","つうか","りょうがえ","おかね"],
+	"©️": ["こぴーらいとまーく","ちょさくけん"],
+	"®️": ["とうろくしょうひょうまーく","とうろくずみ","しょうひょう"],
+	"™️": ["しょうひょうまーく","まーく","tm","しょうひょう"],
+	"🔚": ["ENDとひだりやじるし","やじるし","はじ"],
+	"🔙": ["BACKとひだりやじるし","やじるし","もどる"],
+	"🔛": ["ON!とさゆうやじるし","やじるし","まーく","おん"],
+	"🔝": ["TOPとうえやじるし","やじるし","とっぷ","うえ"],
+	"🔜": ["SOONとみぎやじるし","やじるし","まもなく"],
+	"☑️": ["ちぇっくいりちぇっくぼっくす","とうひょう","ぼっくす","ちぇっく"],
+	"🔘": ["らじおぼたん","ぼたん","きかがく","らじお"],
+	"🔴": ["あかまる","えん","きかがく","あか"],
+	"🟠": ["おれんじいろのえん","えん","きかがく","おれんじ"],
+	"🟡": ["きいろのまる","えん","きかがく","ちゃいろ"],
+	"🟢": ["みどりまる","えん","きかがく","みどり"],
+	"🔵": ["あおまる","あお","えん","きかがく"],
+	"🟣": ["むらさきのまる","えん","きかがく","むらさき"],
+	"🟤": ["ちゃいろのまる","えん","きかがく","ちゃいろ"],
+	"⚫": ["くろまる","えん","きかがく"],
+	"⚪": ["しろまる","えん","きかがく"],
+	"🟥": ["あかのせいほうけい","せいほうけい","きかがく","あか"],
+	"🟧": ["おれんじしょくのせいほうけい","せいほうけい","きかがく","おれんじ"],
+	"🟨": ["きいろのせいほうけい","せいほうけい","きかがく","きいろ"],
+	"🟩": ["みどりのせいほうけい","せいほうけい","きかがく","みどり"],
+	"🟦": ["あおのせいほうけい","せいほうけい","きかがく","あお"],
+	"🟪": ["むらさきのせいほうけい","せいほうけい","きかがく","むらさき"],
+	"🟫": ["ちゃいろのせいほうけい","せいほうけい","きかがく","ちゃいろ"],
+	"⬛": ["くろいおおきなしかく","きかがく","せいほうけい"],
+	"⬜": ["しろいおおきなしかく","きかがく","せいほうけい"],
+	"◼️": ["くろいちゅうくらいのしかく","きかがく","せいほうけい"],
+	"◻️": ["しろくてちゅうくらいのしかく","きかがく","せいほうけい"],
+	"◾": ["くろくてちゅうくらいのちいさいしかく","きかがく","せいほうけい"],
+	"◽": ["しろいちゅうくらいのちいさなしかく","きかがく","せいほうけい"],
+	"▪️": ["くろいちいさなしかく","きかがく","せいほうけい"],
+	"▫️": ["しろいちいさなしかく","きかがく","せいほうけい"],
+	"🔸": ["ちいさいおれんじのだいやもんど","だいやもんど","きかがく","おれんじ"],
+	"🔹": ["ちいさくてあおいだいやもんど","あお","だいやもんど","きかがく"],
+	"🔶": ["おおきいおれんじのだいや","だいやもんど","きかがく","おれんじ"],
+	"🔷": ["おおきくてあおいだいやもんど","あお","だいやもんど","きかがく"],
+	"🔺": ["うわむきのあかいさんかっけい","うえ","きかがく","あか"],
+	"🔻": ["したむきのさんかっけい","だうん","きかがく","あか"],
+	"🔲": ["くろいしかくぼたん","ぼたん","きかがく","せいほうけい"],
+	"🔳": ["しろいしかくぼたん","ぼたん","きかがく","かこみ","しかく"],
+	"🔈": ["すぴーかー","おんりょう"],
+	"🔉": ["おんりょうしょう","でんげんがはいったすぴーかー","ひくい","すぴーかー","おんりょう","なみ"],
+	"🔊": ["おんりょうだい","だいおんりょうのすぴーかー","3","えんたーていめんと","たかい","おとのおおきい","すぴーかー","ぼりゅーむ"],
+	"🔇": ["むおんのすぴーかー","すぴーかー","おふ","みゅーと","せいおん","むおん","おんりょう"],
+	"📣": ["めがほん","おうえん","こみゅにけーしょん","かくせいき"],
+	"📢": ["かくせいき","こみゅにけーしょん","おおごえ","すぴーかー","ぱぶりっくあどれす","めがほん"],
+	"🔔": ["べる"],
+	"🔕": ["みゅーと","すらっしゅべる","かね","きんじられた","だめ","ない","きんし","しずか"],
+	"🃏": ["とらんぷのじょーかー","かーど","えんたーていめんと","げーむ","じょーかー","ぷれい"],
+	"🀄": ["まーじゃんぱいのちゅう","げーむ","まーじゃん","あか"],
+	"♠️": ["とらんぷのすぺーど","かーど","げーむ","すぺーど","すーつ"],
+	"♣️": ["とらんぷのくらぶ","かーど","くらぶ","げーむ","すーつ"],
+	"♥️": ["とらんぷのはーと","かーど","げーむ","はーと","すーつ"],
+	"♦️": ["とらんぷのだいや","かーど","だいや","だいやもんど","げーむ","すーつ"],
+	"🎴": ["はなふだ","あくてぃびてぃ","かーど","えんたーていめんと","はな","げーむ","にっぽん","ぷれい"],
+	"👁‍🗨": ["ふきだしのめ","ふきだし","め","すぴーち","しょうにん"],
+	"🗨": ["ひだりむきのふきだし","せりふ","すぴーち"],
+	"💭": ["かんがえふきだし","ふきだし","あわ","まんが","かんがえ"],
+	"🗯": ["みぎむきのいかりのふきだし","いかり","ふきだし","あわ","げきど"],
+	"💬": ["ふきだし","あわ","まんが","せりふ","すぴーち"],
+	"🕐": ["1じ","0ふん","1","とけい","とき","いち"],
+	"🕑": ["2じ","0ふん","2","とけい","とき","に"],
+	"🕒": ["3じ","0ふん","3","とけい","とき","さん"],
+	"🕓": ["4じ","0ふん","4","とけい","よん","とき"],
+	"🕔": ["5じ","0ふん","5","とけい","ご","とき"],
+	"🕕": ["6じ","0ふん","6","とけい","とき","ろく"],
+	"🕖": ["7じ","0ふん","7","とけい","とき","なな"],
+	"🕗": ["8じ","0ふん","8","とけい","はち","とき"],
+	"🕘": ["9じ","0ふん","9","とけい","きゅう","とき"],
+	"🕙": ["10じ","0ふん","10","とけい","とき","じゅう"],
+	"🕚": ["11じ","0ふん","11","とけい","じゅういち","とき"],
+	"🕛": ["12じ","0ふん","12","とけい","じゅうに","とき"],
+	"🕜": ["1じはん","1じ","はん","じこく","いち","30"],
+	"🕝": ["2じはん","2じ","はん","じこく","30","に"],
+	"🕞": ["3じはん","3じ","はん","じこく","30","さん"],
+	"🕟": ["4じはん","30","4じ","じこく","よん","はん"],
+	"🕠": ["5じはん","30","5じ","じこく","ご","はん"],
+	"🕡": ["6じはん","30","6じ","じこく","ろく","はん"],
+	"🕢": ["7じはん","30","7じ","じこく","なな","はん"],
+	"🕣": ["8じはん","30","8じ","じこく","はち","はん"],
+	"🕤": ["9じはん","30","9じ","じこく","きゅう","はん"],
+	"🕥": ["10じはん","10じ","はん","じこく","じゅう","30"],
+	"🕦": ["11じはん","11じ","はん","じこく","じゅういち","30"],
+	"🕧": ["12じはん","12じ","はん","じこく","30","じゅうに"],
+	"🏳": ["なびくしろはた","はた","なびく"],
+	"🏴": ["なびくくろはた","はた","なびく"],
+	"🏁": ["ちぇっかーふらっぐ","いちまつもよう","はた","れーす"],
+	"🚩": ["さんかくはた","はた","ぽすと"],
+	"🎌": ["こうさき","あくてぃびてぃ","おいわい","こうさ","こうさした","はた","にっぽん"],
+	"🏴‍☠️": ["かいぞくはた","はた","かいぞく"],
+	"🏳️‍🌈": ["れいんぼーふらっぐ","ふらっぐ","れいんぼー","ぷらいど","lgbt"],
+	"🏳️‍⚧️": ["とらすじぇんだーふらっぐ","ふらっぐ","とらんすじぇんだー","ぷらいど","lgbt"],
+	"🇦🇨": ["あせんしょんとうのはた","あせんしょん","こっき","しま"],
+	"🇦🇩": ["あんどらこっき","あんどら","こっき"],
+	"🇦🇪": ["あらぶしゅちょうこくれんぽうこっき","しゅちょうこく","こっき","あらぶしゅちょうこくれんぽう","れんぽう"],
+	"🇦🇫": ["あふがにすたんこっき","あふがにすたん","こっき"],
+	"🇦🇬": ["あんてぃぐあばーぶーだこっき","あんてぃぐあ","ばーぶーだ","こっき"],
+	"🇦🇮": ["あんぎらとうのはた","あんぎらとう","こっき"],
+	"🇦🇱": ["あるばにあこっき","あるばにあ","こっき"],
+	"🇦🇲": ["あるめにあこっき","あるめにあ","こっき"],
+	"🇦🇴": ["あんごらこっき","あんごら","こっき"],
+	"🇦🇶": ["なんきょくたいりくのはた","なんきょくたいりく","こっき"],
+	"🇦🇷": ["あるぜんちんこっき","あるぜんちん","こっき"],
+	"🇦🇸": ["あめりかりょうさもあのはた","あめりかりょう","こっき","さもあ"],
+	"🇦🇹": ["おーすとりあこっき","おーすとりあ","こっき"],
+	"🇦🇺": ["おーすとらりあこっき","おーすとらりあ","こっき","はーど","まくどなるど"],
+	"🇦🇼": ["あるばこっき","あるば","こっき"],
+	"🇦🇽": ["おーらんどしょとうのはた","おーらんどしょとう","こっき"],
+	"🇦🇿": ["あぜるばいじゃんこっき","あぜるばいじゃん","こっき"],
+	"🇧🇦": ["ぼすにあへるつぇごびなこっき","ぼすにあ","こっき","へるつぇごびな"],
+	"🇧🇧": ["ばるばどすこっき","ばるばどす","こっき"],
+	"🇧🇩": ["ばんぐらでしゅこっき","ばんぐらでしゅ","こっき"],
+	"🇧🇪": ["べるぎーこっき","べるぎー","こっき"],
+	"🇧🇫": ["ぶるきなふぁそこっき","ぶるきなふぁそ","こっき"],
+	"🇧🇬": ["ぶるがりあこっき","ぶるがりあ","こっき"],
+	"🇧🇭": ["ばーれーんこっき","ばーれーん","こっき"],
+	"🇧🇮": ["ぶるんじこっき","ぶるんじ","こっき"],
+	"🇧🇯": ["べなんこっき","べなん","こっき"],
+	"🇧🇱": ["さん・ばるてるみーとうのはた","ばるてるみー","こっき","さん"],
+	"🇧🇲": ["ばみゅーだしょとうのはた","ばみゅーだしょとう","こっき"],
+	"🇧🇳": ["ぶるねいこっき","ぶるねい","だるさらーむ","こっき"],
+	"🇧🇴": ["ぼりびあこっき","ぼりびあ","こっき"],
+	"🇧🇶": ["かりぶかいのおらんだりょうとうのはた","ぼねーるとう","かりぶかい","ゆーすたてぃうす","こっき","おらんだ","さば","しんと"],
+	"🇧🇷": ["ぶらじるこっき","ぶらじる","こっき"],
+	"🇧🇸": ["ばはまこっき","ばはま","こっき"],
+	"🇧🇹": ["ぶーたんこっき","ぶーたん","こっき"],
+	"🇧🇼": ["ぼつわなこっき","ぼつわな","こっき"],
+	"🇧🇾": ["べらるーしこっき","べらるーし","こっき"],
+	"🇧🇿": ["べりーずこっき","べりーず","こっき"],
+	"🇨🇦": ["かなだこっき","かなだ","こっき"],
+	"🇨🇨": ["ここすしょとうのはた","ここす","こっき","しょとう","きーりんぐ"],
+	"🇨🇩": ["こんごこっき - きんしゃさ","こんご","こんご - きんしゃさ","こんごみんしゅきょうわこく","こっき","きんしゃさ","きょうわこく"],
+	"🇨🇫": ["ちゅうおうあふりかこっき","ちゅうおうあふりかきょうわこく","こっき","きょうわこく"],
+	"🇨🇬": ["こんごのはた - ぶらざびる","ぶらざびる","こんご","こんごきょうわこく","こんご - ぶらざびる","こっき","きょうわこく"],
+	"🇨🇭": ["すいすこっき","こっき","すいす"],
+	"🇨🇮": ["こーとじぼわーるこっき","こーとじぼわーる","こっき"],
+	"🇨🇰": ["くっくしょとうこっき","くっく","こっき","しょとう"],
+	"🇨🇱": ["ちりこっき","ちり","こっき"],
+	"🇨🇲": ["かめるーんこっき","かめるーん","こっき"],
+	"🇨🇳": ["ちゅうごくこっき","ちゅうごく","こっき"],
+	"🇨🇴": ["ころんびあこっき","ころんびあ","こっき"],
+	"🇨🇷": ["こすたりかこっき","こすたりか","こっき"],
+	"🇨🇺": ["きゅーばこっき","きゅーば","こっき"],
+	"🇨🇻": ["かーぼべるでこっき","かーぼ","けーぷ","こっき","べるで"],
+	"🇨🇼": ["きゅらそーとうのはた","あんてぃるしょとう","きゅらそー","こっき"],
+	"🇨🇽": ["くりすますとうのはた","くりすます","こっき","しま"],
+	"🇨🇾": ["きぷろすこっき","きぷろす","こっき"],
+	"🇨🇿": ["ちぇここっき","ちぇこきょうわこく","こっき"],
+	"🇩🇪": ["どいつこっき","こっき","どいつ"],
+	"🇩🇯": ["じぶちこっき","じぶち","こっき"],
+	"🇩🇰": ["でんまーくこっき","でんまーく","こっき"],
+	"🇩🇲": ["どみにかこっき","どみにか","こっき"],
+	"🇩🇴": ["どみにかきょうわこくこっき","どみにかきょうわこく","こっき"],
+	"🇩🇿": ["あるじぇりあこっき","あるじぇりあ","こっき"],
+	"🇪🇨": ["えくあどるこっき","えくあどる","こっき"],
+	"🏴󠁧󠁢󠁥󠁮󠁧󠁿": ["いんぐらんどのはた","いんぐらんど","こっき"],
+	"🇪🇪": ["えすとにあこっき","えすとにあ","こっき"],
+	"🇪🇬": ["えじぷとこっき","えじぷと","こっき"],
+	"🇪🇭": ["にしさはらのはた","こっき","さはら","にし","にしさはら"],
+	"🇪🇷": ["えりとりあこっき","えりとりあ","こっき"],
+	"🇪🇸": ["すぺいんこっき","こっき","すぺいん","せうた","めりりゃ"],
+	"🇪🇹": ["えちおぴあこっき","えちおぴあ","こっき"],
+	"🇪🇺": ["おうしゅうはた","おうしゅうれんごう","こっき"],
+	"🇫🇮": ["ふぃんらんどこっき","ふぃんらんど","こっき"],
+	"🇫🇯": ["ふぃじーこっき","ふぃじー","こっき"],
+	"🇫🇰": ["ふぉーくらんどしょとうのはた","ふぉーくらんど","ふぉーくらんどしょとう","こっき","しょとう","まるびなす"],
+	"🇫🇲": ["みくろねしあこっき","こっき","みくろねしあ"],
+	"🇫🇴": ["ふぇろーしょとうのはた","ふぇろー","はた","しょとう"],
+	"🇫🇷": ["ふらんすこっき","こっき","ふらんす","くりっぱーとんとう","せんと・まーちん","さん・まるたん"],
+	"🇬🇦": ["がぼんこっき","こっき","がぼん"],
+	"🇬🇧": ["いぎりすこっき","いぎりす","いぎりすりょう","こーんうぉーる","いんぐらんど","こっき","ぐれーとぶりてん","あいるらんど","きたあいるらんど","すこっとらんど","UK","ゆにおんじゃっく","れんごう","れんごうおうこく","うぇーるず"],
+	"🇬🇩": ["ぐれなだこっき","こっき","ぐれなだ"],
+	"🇬🇪": ["じょーじあこっき","こっき","じょーじあ"],
+	"🇬🇫": ["ふらんすりょうぎあなのはた","こっき","ふらんすりょう","ぎあな"],
+	"🇬🇬": ["がーんじーこっき","こっき","がーんじー"],
+	"🇬🇭": ["がーなこっき","こっき","がーな"],
+	"🇬🇮": ["じぶらるたるこっき","こっき","じぶらるたる"],
+	"🇬🇱": ["ぐりーんらんどこっき","こっき","ぐりーんらんど"],
+	"🇬🇲": ["がんびあこっき","こっき","がんびあ"],
+	"🇬🇳": ["ぎにあこっき","こっき","ぎにあ"],
+	"🇬🇵": ["ぐあどるーぷこっき","こっき","ぐあどるーぷ"],
+	"🇬🇶": ["せきどうぎにあこっき","せきどうぎにあ","こっき","ぎにあ"],
+	"🇬🇷": ["ぎりしゃこっき","こっき","ぎりしゃ"],
+	"🇬🇸": ["さうすじょーじあ・さうすさんどうぃっちしょとうこっき","こっき","じょーじあ","しょとう","さうす","さうすじょーじあ","さうすさんどうぃっち"],
+	"🇬🇹": ["ぐあてまらこっき","こっき","ぐあてまら"],
+	"🇬🇺": ["ぐあむはた","こっき","ぐあむ"],
+	"🇬🇼": ["ぎにあびさうこっき","びさう","こっき","ぎにあ"],
+	"🇬🇾": ["がいあなこっき","こっき","がいあな"],
+	"🇭🇰": ["ほんこんのはた","ちゅうごく","こっき","ほんこん"],
+	"🇭🇳": ["ほんじゅらすこっき","こっき","ほんじゅらす"],
+	"🇭🇷": ["くろあちあこっき","くろあちあ","こっき"],
+	"🇭🇹": ["はいちこっき","こっき","はいち"],
+	"🇭🇺": ["はんがりーこっき","こっき","はんがりー"],
+	"🇮🇨": ["かなりあしょとうのはた","かなりあ","こっき","しょとう"],
+	"🇮🇩": ["いんどねしあこっき","こっき","いんどねしあ"],
+	"🇮🇪": ["あいるらんどこっき","こっき","あいるらんど"],
+	"🇮🇱": ["いすらえるこっき","こっき","いすらえる"],
+	"🇮🇲": ["まんとうのはた","こっき","まんとう"],
+	"🇮🇳": ["いんどこっき","こっき","いんど"],
+	"🇮🇴": ["いぎりすりょういんどようちいきのはた","いぎりすりょう","ちゃごす","はた","いんどよう","しま","でぃえごがるしあ"],
+	"🇮🇶": ["いらくこっき","こっき","いらく"],
+	"🇮🇷": ["いらんこっき","こっき","いらん"],
+	"🇮🇸": ["あいすらんどこっき","こっき","あいすらんど"],
+	"🇮🇹": ["いたりあこっき","こっき","いたりあ"],
+	"🇯🇪": ["じゃーじーだいかんかんかつくのはた","こっき","じゃーじーだいかんかんかつく"],
+	"🇯🇲": ["じゃまいかこっき","こっき","じゃまいか"],
+	"🇯🇴": ["よるだんこっき","こっき","よるだん"],
+	"🇯🇵": ["にっぽんこっき","こっき","にっぽん"],
+	"🇰🇪": ["けにあこっき","こっき","けにあ"],
+	"🇰🇬": ["きるぎすこっき","こっき","きるぎす"],
+	"🇰🇭": ["かんぼじあこっき","かんぼじあ","こっき"],
+	"🇰🇮": ["きりばすこっき","こっき","きりばす"],
+	"🇰🇲": ["こもろこっき","こもろ","こっき"],
+	"🇰🇳": ["せんとくりすとふぁーねいびすこっき","こっき","きっつ","ねいびす","せんと"],
+	"🇰🇵": ["きたちょうせんこっき","こっき","ちょうせん","きた","きたちょうせん"],
+	"🇰🇷": ["かんこくこっき","こっき","かんこく","みなみ","だいかんみんこく"],
+	"🇰🇼": ["くうぇーとこっき","こっき","くうぇーと"],
+	"🇰🇾": ["けいまんしょとうのはた","けいまん","こっき","しょとう"],
+	"🇰🇿": ["かざふすたんこっき","こっき","かざふすたん"],
+	"🇱🇦": ["らおすこっき","こっき","らおす"],
+	"🇱🇧": ["ればのんこっき","こっき","ればのん"],
+	"🇱🇨": ["せんとるしあこっき","こっき","せんとるしあ"],
+	"🇱🇮": ["りひてんしゅたいんこっき","こっき","りひてんしゅたいん"],
+	"🇱🇰": ["すりらんかこっき","こっき","すりらんか"],
+	"🇱🇷": ["りべりあこっき","こっき","りべりあ"],
+	"🇱🇸": ["れそとこっき","こっき","れそと"],
+	"🇱🇹": ["りとあにあこっき","こっき","りとあにあ"],
+	"🇱🇺": ["るくせんぶるくこっき","こっき","るくせんぶるく"],
+	"🇱🇻": ["らとびあこっき","こっき","らとびあ"],
+	"🇱🇾": ["りびあこっき","こっき","りびあ"],
+	"🇲🇦": ["もろっここっき","こっき","もろっこ"],
+	"🇲🇨": ["もなここっき","こっき","もなこ"],
+	"🇲🇩": ["もるどばこっき","こっき","もるどば"],
+	"🇲🇪": ["もんてねぐろこっき","こっき","もんてねぐろ"],
+	"🇲🇬": ["まだがすかるこっき","こっき","まだがすかる"],
+	"🇲🇭": ["まーしゃるしょとうこっき","こっき","しょとう","まーしゃる"],
+	"🇲🇰": ["まけどにあこっき","こっき","まけどにあ"],
+	"🇲🇱": ["まりこっき","こっき","まり"],
+	"🇲🇲": ["みゃんまーこっき","びるま","こっき","みゃんまー"],
+	"🇲🇳": ["もんごるこっき","こっき","もんごる"],
+	"🇲🇴": ["まかおのはた","ちゅうごく","こっき","まかお"],
+	"🇲🇵": ["きたまりあなしょとうのはた","こっき","しょとう","まりあな","きた","きたまりあな"],
+	"🇲🇶": ["まるてぃにーくのはた","はた","まるてぃにーく"],
+	"🇲🇷": ["もーりたにあこっき","こっき","もーりたにあ"],
+	"🇲🇸": ["もんとせらとのはた","はた","もんとせらと"],
+	"🇲🇹": ["まるたこっき","こっき","まるた"],
+	"🇲🇺": ["もーりしゃすこっき","こっき","もーりしゃす"],
+	"🇲🇻": ["もるでぃぶこっき","こっき","もるでぃぶ"],
+	"🇲🇼": ["まらういこっき","こっき","まらうい"],
+	"🇲🇽": ["めきしここっき","こっき","めきしこ"],
+	"🇲🇾": ["まれーしあこっき","こっき","まれーしあ"],
+	"🇲🇿": ["もざんびーくこっき","こっき","もざんびーく"],
+	"🇳🇦": ["なみびあこっき","こっき","なみびあ"],
+	"🇳🇨": ["にゅーかれどにあのはた","こっき","にゅー","にゅーかれどにあ"],
+	"🇳🇪": ["にじぇーるこっき","こっき","にじぇーる"],
+	"🇳🇫": ["のーふぉーくとうのはた","はた","しま","のーふぉーく"],
+	"🇳🇬": ["ないじぇりあこっき","こっき","ないじぇりあ"],
+	"🇳🇮": ["にからぐあこっき","こっき","にからぐあ"],
+	"🇳🇱": ["おらんだこっき","こっき","おらんだ"],
+	"🇳🇴": ["のるうぇーこっき","はた","のるうぇー","ぶーべ","すヴぁーるばる","やんまいえん"],
+	"🇳🇵": ["ねぱーるこっき","こっき","ねぱーる"],
+	"🇳🇷": ["なうるこっき","こっき","なうる"],
+	"🇳🇺": ["にうえこっき","こっき","にうえ"],
+	"🇳🇿": ["にゅーじーらんどこっき","こっき","にゅー","にゅーじーらんど"],
+	"🇴🇲": ["おまーんこっき","こっき","おまーん"],
+	"🇵🇦": ["ぱなまこっき","こっき","ぱなま"],
+	"🇵🇪": ["ぺるーこっき","こっき","ぺるー"],
+	"🇵🇫": ["ふらんすりょうぽりねしあのはた","こっき","ふらんすりょう","ぽりねしあ"],
+	"🇵🇬": ["ぱぷあにゅーぎにあこっき","こっき","ぎにあ","にゅー","ぱぷあにゅーぎにあ"],
+	"🇵🇭": ["ふぃりぴんこっき","こっき","ふぃりぴん"],
+	"🇵🇰": ["ぱきすたんこっき","こっき","ぱきすたん"],
+	"🇵🇱": ["ぽーらんどこっき","こっき","ぽーらんど"],
+	"🇵🇲": ["さんぴえーるとう・みくろんとうのはた","はた","みくろん","ぴえーる","さん"],
+	"🇵🇳": ["ぴとけあんしょとうのはた","はた","しょとう","ぴとけあん"],
+	"🇵🇷": ["ぷえるとりこのはた","こっき","ぷえるとりこ"],
+	"🇵🇸": ["ぱれすちなじちせいふのはた","こっき","ぱれすちな"],
+	"🇵🇹": ["ぽるとがるこっき","こっき","ぽるとがる"],
+	"🇵🇼": ["ぱらおこっき","こっき","ぱらお"],
+	"🇵🇾": ["ぱらぐあいこっき","こっき","ぱらぐあい"],
+	"🇶🇦": ["かたーるこっき","こっき","かたーる"],
+	"🇷🇪": ["れゆにおんのはた","はた","れゆにおん"],
+	"🇷🇴": ["るーまにあこっき","こっき","るーまにあ"],
+	"🇷🇸": ["せるびあこっき","こっき","せるびあ"],
+	"🇷🇺": ["ろしあこっき","こっき","ろしあ"],
+	"🇷🇼": ["るわんだこっき","こっき","るわんだ"],
+	"🇸🇦": ["さうじあらびあこっき","こっき","さうじあらびあ"],
+	"🏴󠁧󠁢󠁳󠁣󠁴󠁿": ["すこっとらんどのはた","すこっとらんど","はた"],
+	"🇸🇧": ["そろもんしょとうこっき","はた","しょとう","そろもん"],
+	"🇸🇨": ["せーしぇるこっき","こっき","せーしぇる"],
+	"🇸🇩": ["すーだんこっき","こっき","すーだん"],
+	"🇸🇪": ["すうぇーでんこっき","こっき","すうぇーでん"],
+	"🇸🇬": ["しんがぽーるこっき","こっき","しんがぽーる"],
+	"🇸🇭": ["せんとへれなとうのはた","はた","へれな","せんと"],
+	"🇸🇮": ["すろべにあこっき","こっき","すろべにあ"],
+	"🇸🇰": ["すろばきあこっき","こっき","すろばきあ"],
+	"🇸🇱": ["しえられおねこっき","こっき","しえられおね"],
+	"🇸🇲": ["さんまりのこっき","こっき","さんまりの"],
+	"🇸🇳": ["せねがるこっき","こっき","せねがる"],
+	"🇸🇴": ["そまりあこっき","こっき","そまりあ"],
+	"🇸🇷": ["すりなむこっき","こっき","すりなむ"],
+	"🇸🇸": ["みなみすーだんこっき","こっき","みなみ","みなみすーだん","すーだん"],
+	"🇸🇹": ["さんとめぷりんしぺこっき","こっき","ぷりんしぺ","ぷりんしぴ","さんとめ","さぉんとめー"],
+	"🇸🇻": ["えるさるばどるこっき","えるさるばどる","こっき"],
+	"🇸🇽": ["せんと・まーちんとうのはた","はた","まーちん","せんと"],
+	"🇸🇾": ["しりあこっき","こっき","しりあ"],
+	"🇸🇿": ["すわじらんどこっき","こっき","すわじらんど"],
+	"🇹🇦": ["とりすたんだくーにゃのはた","はた","とりすたん・だ・くーにゃ"],
+	"🇹🇨": ["たーくす・かいこすしょとうのはた","かいこす","はた","しょとう","たーくす"],
+	"🇹🇩": ["ちゃどこっき","ちゃど","こっき"],
+	"🇹🇫": ["ふらんすりょうなんぽう・なんきょくちいきのはた","なんきょく","こっき","ふらんすりょう"],
+	"🇹🇬": ["とーごこっき","こっき","とーご"],
+	"🇹🇭": ["たいこっき","こっき","たい"],
+	"🇹🇯": ["たじきすたんこっき","こっき","たじきすたん"],
+	"🇹🇰": ["とけらうはた","こっき","とけらう"],
+	"🇹🇱": ["ひがしてぃもーるこっき","ひがし","ひがしてぃもーる","こっき","てぃもーる・れすて"],
+	"🇹🇲": ["とるくめにすたんこっき","こっき","とるくめにすたん"],
+	"🇹🇳": ["ちゅにじあこっき","こっき","ちゅにじあ"],
+	"🇹🇴": ["とんがこっき","こっき","とんが"],
+	"🇹🇷": ["とるここっき","こっき","とるこ"],
+	"🇹🇹": ["とりにだーどとばごこっき","こっき","とばご","とりにだーど"],
+	"🇹🇻": ["つばるこっき","こっき","つばる"],
+	"🇹🇼": ["たいわんのはた","ちゅうごく","こっき","たいわん"],
+	"🇹🇿": ["たんざにあこっき","こっき","たんざにあ"],
+	"🇺🇦": ["うくらいなこっき","こっき","うくらいな"],
+	"🇺🇬": ["うがんだこっき","こっき","うがんだ"],
+	"🇺🇳": ["こくれんのはた","はた","こくれん","れんごう","こくさい"],
+	"🇺🇸": ["あめりかこっき","あめりか","はた","ごうしゅう","がっしゅうこく","あめりかがっしゅうこく","がっしゅうこくりょうゆうしょうりとう"],
+	"🇺🇾": ["うるぐあいこっき","こっき","うるぐあい"],
+	"🇺🇿": ["うずべきすたんこっき","こっき","うずべきすたん"],
+	"🇻🇦": ["ばちかんしこっき","こっき","ばちかん"],
+	"🇻🇨": ["せんとびんせんと・ぐれなでぃーんこっき","こっき","ぐれなでぃーんしょとう","せんと","びんせんと"],
+	"🇻🇪": ["べねずえらこっき","こっき","べねずえら"],
+	"🇻🇬": ["いぎりすりょうヴぁぁーじんしょとうのはた","いぎりすりょう","こっき","しま","ヴぁーじん"],
+	"🇻🇮": ["あめりかりょうヴぁーじんしょとうのはた","あめりか","こっき","しま","あめりかがっしゅうこく","がっしゅうこく","ヴぁーじん"],
+	"🇻🇳": ["べとなむこっき","こっき","べとなむ","ヴぇとなむ"],
+	"🇻🇺": ["ばぬあつこっき","こっき","ばぬあつ"],
+	"🏴󠁧󠁢󠁷󠁬󠁳󠁿": ["うぇーるずのはた","うぇーるず","はた"],
+	"🇼🇫": ["うぉりす・ふつなのはた","こっき","ふつな","うぉりす"],
+	"🇼🇸": ["さもあこっき","こっき","さもあ"],
+	"🇽🇰": ["こそぼこっき","こっき","こそぼ"],
+	"🇾🇪": ["いえめんこっき","こっき","いえめん"],
+	"🇾🇹": ["まよっとのはた","こっき","まよっと"],
+	"🇿🇦": ["みなみあふりかこっき","こっき","みなみ","みなみあふりか"],
+	"🇿🇲": ["ざんびあこっき","こっき","ざんびあ"],
+	"🇿🇼": ["じんばぶえこっき","こっき","じんばぶえ"],
+	"": ["しぶや109", "SHIBUYA109", "109"]
+}
diff --git a/packages/frontend/src/widgets/WidgetActivity.calendar.vue b/packages/frontend/src/widgets/WidgetActivity.calendar.vue
index bb5a2676dd..58d231d9d4 100644
--- a/packages/frontend/src/widgets/WidgetActivity.calendar.vue
+++ b/packages/frontend/src/widgets/WidgetActivity.calendar.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/widgets/WidgetActivity.chart.vue b/packages/frontend/src/widgets/WidgetActivity.chart.vue
index 0e87ec3ec3..41c6126c72 100644
--- a/packages/frontend/src/widgets/WidgetActivity.chart.vue
+++ b/packages/frontend/src/widgets/WidgetActivity.chart.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/widgets/WidgetActivity.vue b/packages/frontend/src/widgets/WidgetActivity.vue
index d2842143b1..9b65ca5e4a 100644
--- a/packages/frontend/src/widgets/WidgetActivity.vue
+++ b/packages/frontend/src/widgets/WidgetActivity.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -25,7 +25,7 @@ import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, Wid
 import XCalendar from './WidgetActivity.calendar.vue';
 import XChart from './WidgetActivity.chart.vue';
 import { GetFormResultType } from '@/scripts/form.js';
-import * as os from '@/os.js';
+import { misskeyApiGet } from '@/scripts/misskey-api.js';
 import MkContainer from '@/components/MkContainer.vue';
 import { $i } from '@/account.js';
 import { i18n } from '@/i18n.js';
@@ -76,7 +76,7 @@ const toggleView = () => {
 	save();
 };
 
-os.apiGet('charts/user/notes', {
+misskeyApiGet('charts/user/notes', {
 	userId: $i.id,
 	span: 'day',
 	limit: 7 * 21,
diff --git a/packages/frontend/src/widgets/WidgetAichan.vue b/packages/frontend/src/widgets/WidgetAichan.vue
index fef026244c..00001005de 100644
--- a/packages/frontend/src/widgets/WidgetAichan.vue
+++ b/packages/frontend/src/widgets/WidgetAichan.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/widgets/WidgetAiscript.vue b/packages/frontend/src/widgets/WidgetAiscript.vue
index c17e9728a5..70fac9ae55 100644
--- a/packages/frontend/src/widgets/WidgetAiscript.vue
+++ b/packages/frontend/src/widgets/WidgetAiscript.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -25,7 +25,7 @@ import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, Wid
 import { GetFormResultType } from '@/scripts/form.js';
 import * as os from '@/os.js';
 import MkContainer from '@/components/MkContainer.vue';
-import { createAiScriptEnv } from '@/scripts/aiscript/api.js';
+import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js';
 import { $i } from '@/account.js';
 import { i18n } from '@/i18n.js';
 
@@ -69,19 +69,7 @@ const run = async () => {
 		storageKey: 'widget',
 		token: $i?.token,
 	}), {
-		in: (q) => {
-			return new Promise(ok => {
-				os.inputText({
-					title: q,
-				}).then(({ canceled, result: a }) => {
-					if (canceled) {
-						ok('');
-					} else {
-						ok(a);
-					}
-				});
-			});
-		},
+		in: aiScriptReadline,
 		out: (value) => {
 			logs.value.push({
 				id: Math.random().toString(),
diff --git a/packages/frontend/src/widgets/WidgetAiscriptApp.vue b/packages/frontend/src/widgets/WidgetAiscriptApp.vue
index 10248a840a..fa79e4aeb7 100644
--- a/packages/frontend/src/widgets/WidgetAiscriptApp.vue
+++ b/packages/frontend/src/widgets/WidgetAiscriptApp.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -18,7 +18,7 @@ import { Interpreter, Parser } from '@syuilo/aiscript';
 import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
 import { GetFormResultType } from '@/scripts/form.js';
 import * as os from '@/os.js';
-import { createAiScriptEnv } from '@/scripts/aiscript/api.js';
+import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js';
 import { $i } from '@/account.js';
 import MkAsUi from '@/components/MkAsUi.vue';
 import MkContainer from '@/components/MkContainer.vue';
@@ -64,19 +64,7 @@ async function run() {
 			root.value = _root.value;
 		}),
 	}, {
-		in: (q) => {
-			return new Promise(ok => {
-				os.inputText({
-					title: q,
-				}).then(({ canceled, result: a }) => {
-					if (canceled) {
-						ok('');
-					} else {
-						ok(a);
-					}
-				});
-			});
-		},
+		in: aiScriptReadline,
 		out: (value) => {
 			// nop
 		},
diff --git a/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue b/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue
index 0a83eba9c1..36ba9f8255 100644
--- a/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue
+++ b/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -27,7 +27,7 @@ import * as Misskey from 'misskey-js';
 import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
 import { GetFormResultType } from '@/scripts/form.js';
 import MkContainer from '@/components/MkContainer.vue';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { useInterval } from '@/scripts/use-interval.js';
 import { i18n } from '@/i18n.js';
 import { infoImageUrl } from '@/instance.js';
@@ -70,7 +70,7 @@ const fetch = () => {
 	now.setHours(0, 0, 0, 0);
 
 	if (now > lfAtD) {
-		os.api('users/following', {
+		misskeyApi('users/following', {
 			limit: 18,
 			birthday: now.toISOString(),
 			userId: $i.id,
diff --git a/packages/frontend/src/widgets/WidgetButton.vue b/packages/frontend/src/widgets/WidgetButton.vue
index 11082c1e3f..6080e120ec 100644
--- a/packages/frontend/src/widgets/WidgetButton.vue
+++ b/packages/frontend/src/widgets/WidgetButton.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -16,7 +16,7 @@ import { Interpreter, Parser } from '@syuilo/aiscript';
 import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
 import { GetFormResultType } from '@/scripts/form.js';
 import * as os from '@/os.js';
-import { createAiScriptEnv } from '@/scripts/aiscript/api.js';
+import { aiScriptReadline, createAiScriptEnv } from '@/scripts/aiscript/api.js';
 import { $i } from '@/account.js';
 import MkButton from '@/components/MkButton.vue';
 
@@ -56,19 +56,7 @@ const run = async () => {
 		storageKey: 'widget',
 		token: $i?.token,
 	}), {
-		in: (q) => {
-			return new Promise(ok => {
-				os.inputText({
-					title: q,
-				}).then(({ canceled, result: a }) => {
-					if (canceled) {
-						ok('');
-					} else {
-						ok(a);
-					}
-				});
-			});
-		},
+		in: aiScriptReadline,
 		out: (value) => {
 			// nop
 		},
diff --git a/packages/frontend/src/widgets/WidgetCalendar.vue b/packages/frontend/src/widgets/WidgetCalendar.vue
index b3f814a0a7..06b71311c4 100644
--- a/packages/frontend/src/widgets/WidgetCalendar.vue
+++ b/packages/frontend/src/widgets/WidgetCalendar.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -7,11 +7,11 @@ SPDX-License-Identifier: AGPL-3.0-only
 <div :class="[$style.root, { _panel: !widgetProps.transparent }]" data-cy-mkw-calendar>
 	<div :class="[$style.calendar, { [$style.isHoliday]: isHoliday }]">
 		<p :class="$style.monthAndYear">
-			<span :class="$style.year">{{ i18n.t('yearX', { year }) }}</span>
-			<span :class="$style.month">{{ i18n.t('monthX', { month }) }}</span>
+			<span :class="$style.year">{{ i18n.tsx.yearX({ year }) }}</span>
+			<span :class="$style.month">{{ i18n.tsx.monthX({ month }) }}</span>
 		</p>
-		<p v-if="month === 1 && day === 1" class="day">🎉{{ i18n.t('dayX', { day }) }}<span style="display: inline-block; transform: scaleX(-1);">🎉</span></p>
-		<p v-else :class="$style.day">{{ i18n.t('dayX', { day }) }}</p>
+		<p v-if="month === 1 && day === 1" class="day">🎉{{ i18n.tsx.dayX({ day }) }}<span style="display: inline-block; transform: scaleX(-1);">🎉</span></p>
+		<p v-else :class="$style.day">{{ i18n.tsx.dayX({ day }) }}</p>
 		<p :class="$style.weekDay">{{ weekDay }}</p>
 	</div>
 	<div :class="$style.info">
diff --git a/packages/frontend/src/widgets/WidgetClicker.vue b/packages/frontend/src/widgets/WidgetClicker.vue
index aa49269017..9d231ae715 100644
--- a/packages/frontend/src/widgets/WidgetClicker.vue
+++ b/packages/frontend/src/widgets/WidgetClicker.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/widgets/WidgetClock.vue b/packages/frontend/src/widgets/WidgetClock.vue
index 22f053db59..b3128ef27e 100644
--- a/packages/frontend/src/widgets/WidgetClock.vue
+++ b/packages/frontend/src/widgets/WidgetClock.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/widgets/WidgetDigitalClock.vue b/packages/frontend/src/widgets/WidgetDigitalClock.vue
index a4b90c49d3..fa9a98d571 100644
--- a/packages/frontend/src/widgets/WidgetDigitalClock.vue
+++ b/packages/frontend/src/widgets/WidgetDigitalClock.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/widgets/WidgetFederation.vue b/packages/frontend/src/widgets/WidgetFederation.vue
index 9be7d084e9..ae770f9816 100644
--- a/packages/frontend/src/widgets/WidgetFederation.vue
+++ b/packages/frontend/src/widgets/WidgetFederation.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -31,7 +31,7 @@ import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, Wid
 import { GetFormResultType } from '@/scripts/form.js';
 import MkContainer from '@/components/MkContainer.vue';
 import MkMiniChart from '@/components/MkMiniChart.vue';
-import * as os from '@/os.js';
+import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
 import { useInterval } from '@/scripts/use-interval.js';
 import { i18n } from '@/i18n.js';
 import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
@@ -62,11 +62,11 @@ const charts = ref<Misskey.entities.ChartsInstanceResponse[]>([]);
 const fetching = ref(true);
 
 const fetch = async () => {
-	const fetchedInstances = await os.api('federation/instances', {
+	const fetchedInstances = await misskeyApi('federation/instances', {
 		sort: '+latestRequestReceivedAt',
 		limit: 5,
 	});
-	const fetchedCharts = await Promise.all(fetchedInstances.map(i => os.apiGet('charts/instance', { host: i.host, limit: 16, span: 'hour' })));
+	const fetchedCharts = await Promise.all(fetchedInstances.map(i => misskeyApiGet('charts/instance', { host: i.host, limit: 16, span: 'hour' })));
 	instances.value = fetchedInstances;
 	charts.value = fetchedCharts;
 	fetching.value = false;
diff --git a/packages/frontend/src/widgets/WidgetInstanceCloud.vue b/packages/frontend/src/widgets/WidgetInstanceCloud.vue
index 38323ed040..76ccdb3971 100644
--- a/packages/frontend/src/widgets/WidgetInstanceCloud.vue
+++ b/packages/frontend/src/widgets/WidgetInstanceCloud.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -25,6 +25,7 @@ import { GetFormResultType } from '@/scripts/form.js';
 import MkContainer from '@/components/MkContainer.vue';
 import MkTagCloud from '@/components/MkTagCloud.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { useInterval } from '@/scripts/use-interval.js';
 import { getProxiedImageUrlNullable } from '@/scripts/media-proxy.js';
 
@@ -56,7 +57,7 @@ function onInstanceClick(i) {
 }
 
 useInterval(() => {
-	os.api('federation/instances', {
+	misskeyApi('federation/instances', {
 		sort: '+latestRequestReceivedAt',
 		limit: 25,
 	}).then(res => {
diff --git a/packages/frontend/src/widgets/WidgetInstanceInfo.vue b/packages/frontend/src/widgets/WidgetInstanceInfo.vue
index 2133deb363..962521b25c 100644
--- a/packages/frontend/src/widgets/WidgetInstanceInfo.vue
+++ b/packages/frontend/src/widgets/WidgetInstanceInfo.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/widgets/WidgetJobQueue.vue b/packages/frontend/src/widgets/WidgetJobQueue.vue
index c54682bb87..b3e364a6d7 100644
--- a/packages/frontend/src/widgets/WidgetJobQueue.vue
+++ b/packages/frontend/src/widgets/WidgetJobQueue.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -10,19 +10,19 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<div class="values">
 			<div>
 				<div>Process</div>
-				<div :class="{ inc: current.inbox.activeSincePrevTick > prev.inbox.activeSincePrevTick, dec: current.inbox.activeSincePrevTick < prev.inbox.activeSincePrevTick }">{{ number(current.inbox.activeSincePrevTick) }}</div>
+				<div :class="{ inc: current.inbox.activeSincePrevTick > prev.inbox.activeSincePrevTick, dec: current.inbox.activeSincePrevTick < prev.inbox.activeSincePrevTick }" :title="`${current.inbox.activeSincePrevTick}`">{{ kmg(current.inbox.activeSincePrevTick, 2) }}</div>
 			</div>
 			<div>
 				<div>Active</div>
-				<div :class="{ inc: current.inbox.active > prev.inbox.active, dec: current.inbox.active < prev.inbox.active }">{{ number(current.inbox.active) }}</div>
+				<div :class="{ inc: current.inbox.active > prev.inbox.active, dec: current.inbox.active < prev.inbox.active }" :title="`${current.inbox.active}`">{{ kmg(current.inbox.active, 2) }}</div>
 			</div>
 			<div>
 				<div>Delayed</div>
-				<div :class="{ inc: current.inbox.delayed > prev.inbox.delayed, dec: current.inbox.delayed < prev.inbox.delayed }">{{ number(current.inbox.delayed) }}</div>
+				<div :class="{ inc: current.inbox.delayed > prev.inbox.delayed, dec: current.inbox.delayed < prev.inbox.delayed }" :title="`${current.inbox.delayed}`">{{ kmg(current.inbox.delayed, 2) }}</div>
 			</div>
 			<div>
 				<div>Waiting</div>
-				<div :class="{ inc: current.inbox.waiting > prev.inbox.waiting, dec: current.inbox.waiting < prev.inbox.waiting }">{{ number(current.inbox.waiting) }}</div>
+				<div :class="{ inc: current.inbox.waiting > prev.inbox.waiting, dec: current.inbox.waiting < prev.inbox.waiting }" :title="`${current.inbox.waiting}`">{{ kmg(current.inbox.waiting, 2) }}</div>
 			</div>
 		</div>
 	</div>
@@ -31,19 +31,19 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<div class="values">
 			<div>
 				<div>Process</div>
-				<div :class="{ inc: current.deliver.activeSincePrevTick > prev.deliver.activeSincePrevTick, dec: current.deliver.activeSincePrevTick < prev.deliver.activeSincePrevTick }">{{ number(current.deliver.activeSincePrevTick) }}</div>
+				<div :class="{ inc: current.deliver.activeSincePrevTick > prev.deliver.activeSincePrevTick, dec: current.deliver.activeSincePrevTick < prev.deliver.activeSincePrevTick }" :title="`${current.deliver.activeSincePrevTick}`">{{ kmg(current.deliver.activeSincePrevTick, 2) }}</div>
 			</div>
 			<div>
 				<div>Active</div>
-				<div :class="{ inc: current.deliver.active > prev.deliver.active, dec: current.deliver.active < prev.deliver.active }">{{ number(current.deliver.active) }}</div>
+				<div :class="{ inc: current.deliver.active > prev.deliver.active, dec: current.deliver.active < prev.deliver.active }" :title="`${current.deliver.active}`">{{ kmg(current.deliver.active, 2) }}</div>
 			</div>
 			<div>
 				<div>Delayed</div>
-				<div :class="{ inc: current.deliver.delayed > prev.deliver.delayed, dec: current.deliver.delayed < prev.deliver.delayed }">{{ number(current.deliver.delayed) }}</div>
+				<div :class="{ inc: current.deliver.delayed > prev.deliver.delayed, dec: current.deliver.delayed < prev.deliver.delayed }" :title="`${current.deliver.delayed}`">{{ kmg(current.deliver.delayed, 2) }}</div>
 			</div>
 			<div>
 				<div>Waiting</div>
-				<div :class="{ inc: current.deliver.waiting > prev.deliver.waiting, dec: current.deliver.waiting < prev.deliver.waiting }">{{ number(current.deliver.waiting) }}</div>
+				<div :class="{ inc: current.deliver.waiting > prev.deliver.waiting, dec: current.deliver.waiting < prev.deliver.waiting }" :title="`${current.deliver.waiting}`">{{ kmg(current.deliver.waiting, 2) }}</div>
 			</div>
 		</div>
 	</div>
@@ -55,7 +55,7 @@ import { onUnmounted, reactive, ref } from 'vue';
 import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
 import { GetFormResultType } from '@/scripts/form.js';
 import { useStream } from '@/stream.js';
-import number from '@/filters/number.js';
+import kmg from '@/filters/kmg.js';
 import * as sound from '@/scripts/sound.js';
 import { deepClone } from '@/scripts/clone.js';
 import { defaultStore } from '@/store.js';
@@ -104,10 +104,7 @@ const jammedAudioBuffer = ref<AudioBuffer | null>(null);
 const jammedSoundNodePlaying = ref<boolean>(false);
 
 if (defaultStore.state.sound_masterVolume) {
-	sound.loadAudio({
-		type: 'syuilo/queue-jammed',
-		volume: 1,
-	}).then(buf => {
+	sound.loadAudio('/client-assets/sounds/syuilo/queue-jammed.mp3').then(buf => {
 		if (!buf) throw new Error('[WidgetJobQueue] Failed to initialize AudioBuffer');
 		jammedAudioBuffer.value = buf;
 	});
@@ -126,7 +123,7 @@ const onStats = (stats) => {
 		current[domain].delayed = stats[domain].delayed;
 
 		if (current[domain].waiting > 0 && widgetProps.sound && jammedAudioBuffer.value && !jammedSoundNodePlaying.value) {
-			const soundNode = sound.createSourceNode(jammedAudioBuffer.value, 1);
+			const soundNode = sound.createSourceNode(jammedAudioBuffer.value, {}).soundSource;
 			if (soundNode) {
 				jammedSoundNodePlaying.value = true;
 				soundNode.onended = () => jammedSoundNodePlaying.value = false;
diff --git a/packages/frontend/src/widgets/WidgetMemo.vue b/packages/frontend/src/widgets/WidgetMemo.vue
index 8e9e67ade5..d9efe54623 100644
--- a/packages/frontend/src/widgets/WidgetMemo.vue
+++ b/packages/frontend/src/widgets/WidgetMemo.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/widgets/WidgetNotifications.vue b/packages/frontend/src/widgets/WidgetNotifications.vue
index e858741aa1..d590e7768e 100644
--- a/packages/frontend/src/widgets/WidgetNotifications.vue
+++ b/packages/frontend/src/widgets/WidgetNotifications.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/widgets/WidgetOnlineUsers.vue b/packages/frontend/src/widgets/WidgetOnlineUsers.vue
index 0a6fec7f2e..5c89a06c62 100644
--- a/packages/frontend/src/widgets/WidgetOnlineUsers.vue
+++ b/packages/frontend/src/widgets/WidgetOnlineUsers.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 import { ref } from 'vue';
 import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
 import { GetFormResultType } from '@/scripts/form.js';
-import * as os from '@/os.js';
+import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js';
 import { useInterval } from '@/scripts/use-interval.js';
 import { i18n } from '@/i18n.js';
 import number from '@/filters/number.js';
@@ -45,7 +45,7 @@ const { widgetProps, configure } = useWidgetPropsManager(name,
 const onlineUsersCount = ref(0);
 
 const tick = () => {
-	os.apiGet('get-online-users-count').then(res => {
+	misskeyApiGet('get-online-users-count').then(res => {
 		onlineUsersCount.value = res.count;
 	});
 };
diff --git a/packages/frontend/src/widgets/WidgetPhotos.vue b/packages/frontend/src/widgets/WidgetPhotos.vue
index ff9b6e19f5..e578ebe2c5 100644
--- a/packages/frontend/src/widgets/WidgetPhotos.vue
+++ b/packages/frontend/src/widgets/WidgetPhotos.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -28,7 +28,7 @@ import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, Wid
 import { GetFormResultType } from '@/scripts/form.js';
 import { useStream } from '@/stream.js';
 import { getStaticImageUrl } from '@/scripts/media-proxy.js';
-import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import MkContainer from '@/components/MkContainer.vue';
 import { defaultStore } from '@/store.js';
 import { i18n } from '@/i18n.js';
@@ -74,7 +74,7 @@ const thumbnail = (image: any): string => {
 		: image.thumbnailUrl;
 };
 
-os.api('drive/stream', {
+misskeyApi('drive/stream', {
 	type: 'image/*',
 	limit: 9,
 }).then(res => {
diff --git a/packages/frontend/src/widgets/WidgetPostForm.vue b/packages/frontend/src/widgets/WidgetPostForm.vue
index 9979ae256e..7f344505d8 100644
--- a/packages/frontend/src/widgets/WidgetPostForm.vue
+++ b/packages/frontend/src/widgets/WidgetPostForm.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/widgets/WidgetProfile.vue b/packages/frontend/src/widgets/WidgetProfile.vue
index 3ff57bab86..a5578d4de6 100644
--- a/packages/frontend/src/widgets/WidgetProfile.vue
+++ b/packages/frontend/src/widgets/WidgetProfile.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/widgets/WidgetRss.vue b/packages/frontend/src/widgets/WidgetRss.vue
index a718548731..e0272bc7d7 100644
--- a/packages/frontend/src/widgets/WidgetRss.vue
+++ b/packages/frontend/src/widgets/WidgetRss.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/widgets/WidgetRssTicker.vue b/packages/frontend/src/widgets/WidgetRssTicker.vue
index 607bb2f0ab..7456f9d35f 100644
--- a/packages/frontend/src/widgets/WidgetRssTicker.vue
+++ b/packages/frontend/src/widgets/WidgetRssTicker.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/widgets/WidgetSearch.vue b/packages/frontend/src/widgets/WidgetSearch.vue
index 9999139776..cf91a8f089 100644
--- a/packages/frontend/src/widgets/WidgetSearch.vue
+++ b/packages/frontend/src/widgets/WidgetSearch.vue
@@ -20,8 +20,9 @@ import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, Wid
 import MkInput from '@/components/MkInput.vue';
 import MkContainer from '@/components/MkContainer.vue';
 import { i18n } from '@/i18n.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import * as os from '@/os.js';
-import { useRouter } from '@/router.js';
+import { useRouter } from '@/router/supplier.js';
 import { GetFormResultType } from '@/scripts/form.js';
 
 const name = 'search';
@@ -100,7 +101,7 @@ async function search() {
 	if (query == null || query === '') return;
 
 	if (query.startsWith('https://')) {
-		const promise = os.api('ap/show', {
+		const promise = misskeyApi('ap/show', {
 			uri: query,
 		});
 
diff --git a/packages/frontend/src/widgets/WidgetSlideshow.vue b/packages/frontend/src/widgets/WidgetSlideshow.vue
index 7e39a05881..b8efd3bda9 100644
--- a/packages/frontend/src/widgets/WidgetSlideshow.vue
+++ b/packages/frontend/src/widgets/WidgetSlideshow.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 		<p v-if="widgetProps.folderId == null">
 			{{ i18n.ts.folder }}
 		</p>
-		<p v-if="widgetProps.folderId != null && images.length === 0 && !fetching">{{ i18n.t('no-image') }}</p>
+		<p v-if="widgetProps.folderId != null && images.length === 0 && !fetching">{{ i18n.ts['no-image'] }}</p>
 		<div ref="slideA" class="slide a"></div>
 		<div ref="slideB" class="slide b"></div>
 	</div>
@@ -22,6 +22,7 @@ import * as Misskey from 'misskey-js';
 import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
 import { GetFormResultType } from '@/scripts/form.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { useInterval } from '@/scripts/use-interval.js';
 import { i18n } from '@/i18n.js';
 
@@ -77,7 +78,7 @@ const change = () => {
 const fetch = () => {
 	fetching.value = true;
 
-	os.api('drive/files', {
+	misskeyApi('drive/files', {
 		folderId: widgetProps.folderId,
 		type: 'image/*',
 		limit: 100,
@@ -92,10 +93,10 @@ const fetch = () => {
 
 const choose = () => {
 	os.selectDriveFolder(false).then(folder => {
-		if (folder == null) {
+		if (folder[0] == null) {
 			return;
 		}
-		widgetProps.folderId = folder.id;
+		widgetProps.folderId = folder[0].id;
 		save();
 		fetch();
 	});
diff --git a/packages/frontend/src/widgets/WidgetTimeline.vue b/packages/frontend/src/widgets/WidgetTimeline.vue
index 070466f476..f6cf13290f 100644
--- a/packages/frontend/src/widgets/WidgetTimeline.vue
+++ b/packages/frontend/src/widgets/WidgetTimeline.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -16,7 +16,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 	</template>
 	<template #header>
 		<button class="_button" @click="choose">
-			<span>{{ widgetProps.src === 'list' ? widgetProps.list.name : widgetProps.src === 'antenna' ? widgetProps.antenna.name : i18n.t('_timelines.' + widgetProps.src) }}</span>
+			<span>{{ widgetProps.src === 'list' ? widgetProps.list.name : widgetProps.src === 'antenna' ? widgetProps.antenna.name : i18n.ts._timelines[widgetProps.src] }}</span>
 			<i :class="menuOpened ? 'ph-caret-up ph-bold ph-lg' : 'ph-caret-down ph-bold ph-lg'" style="margin-left: 8px;"></i>
 		</button>
 	</template>
@@ -39,6 +39,7 @@ import { ref } from 'vue';
 import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget.js';
 import { GetFormResultType } from '@/scripts/form.js';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import MkContainer from '@/components/MkContainer.vue';
 import MkTimeline from '@/components/MkTimeline.vue';
 import { i18n } from '@/i18n.js';
@@ -97,8 +98,8 @@ const setSrc = (src) => {
 const choose = async (ev) => {
 	menuOpened.value = true;
 	const [antennas, lists] = await Promise.all([
-		os.api('antennas/list'),
-		os.api('users/lists/list'),
+		misskeyApi('antennas/list'),
+		misskeyApi('users/lists/list'),
 	]);
 	const antennaItems = antennas.map(antenna => ({
 		text: antenna.name,
diff --git a/packages/frontend/src/widgets/WidgetTrends.vue b/packages/frontend/src/widgets/WidgetTrends.vue
index 3416a1c0a7..978a1a86f7 100644
--- a/packages/frontend/src/widgets/WidgetTrends.vue
+++ b/packages/frontend/src/widgets/WidgetTrends.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			<div v-for="stat in stats" :key="stat.tag">
 				<div class="tag">
 					<MkA class="a" :to="`/tags/${ encodeURIComponent(stat.tag) }`" :title="stat.tag">#{{ stat.tag }}</MkA>
-					<p>{{ i18n.t('nUsersMentioned', { n: stat.usersCount }) }}</p>
+					<p>{{ i18n.tsx.nUsersMentioned({ n: stat.usersCount }) }}</p>
 				</div>
 				<MkMiniChart class="chart" :src="stat.chart"/>
 			</div>
@@ -30,7 +30,7 @@ import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, Wid
 import { GetFormResultType } from '@/scripts/form.js';
 import MkContainer from '@/components/MkContainer.vue';
 import MkMiniChart from '@/components/MkMiniChart.vue';
-import * as os from '@/os.js';
+import { misskeyApiGet } from '@/scripts/misskey-api.js';
 import { useInterval } from '@/scripts/use-interval.js';
 import { i18n } from '@/i18n.js';
 import { defaultStore } from '@/store.js';
@@ -59,7 +59,7 @@ const stats = ref<Misskey.entities.HashtagsTrendResponse>([]);
 const fetching = ref(true);
 
 const fetch = () => {
-	os.apiGet('hashtags/trend').then(res => {
+	misskeyApiGet('hashtags/trend').then(res => {
 		stats.value = res;
 		fetching.value = false;
 	});
diff --git a/packages/frontend/src/widgets/WidgetUnixClock.vue b/packages/frontend/src/widgets/WidgetUnixClock.vue
index 35f29b5e21..2ac7d1c781 100644
--- a/packages/frontend/src/widgets/WidgetUnixClock.vue
+++ b/packages/frontend/src/widgets/WidgetUnixClock.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/widgets/WidgetUserList.vue b/packages/frontend/src/widgets/WidgetUserList.vue
index c40328d2fa..0e4fe2fbd3 100644
--- a/packages/frontend/src/widgets/WidgetUserList.vue
+++ b/packages/frontend/src/widgets/WidgetUserList.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -30,6 +30,7 @@ import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, Wid
 import { GetFormResultType } from '@/scripts/form.js';
 import MkContainer from '@/components/MkContainer.vue';
 import * as os from '@/os.js';
+import { misskeyApi } from '@/scripts/misskey-api.js';
 import { useInterval } from '@/scripts/use-interval.js';
 import { i18n } from '@/i18n.js';
 import MkButton from '@/components/MkButton.vue';
@@ -64,7 +65,7 @@ const users = ref<Misskey.entities.UserDetailed[]>([]);
 const fetching = ref(true);
 
 async function chooseList() {
-	const lists = await os.api('users/lists/list');
+	const lists = await misskeyApi('users/lists/list');
 	const { canceled, result: list } = await os.select({
 		title: i18n.ts.selectList,
 		items: lists.map(x => ({
@@ -85,11 +86,11 @@ const fetch = () => {
 		return;
 	}
 
-	os.api('users/lists/show', {
+	misskeyApi('users/lists/show', {
 		listId: widgetProps.listId,
 	}).then(_list => {
 		list.value = _list;
-		os.api('users/show', {
+		misskeyApi('users/show', {
 			userIds: list.value.userIds,
 		}).then(_users => {
 			users.value = _users;
diff --git a/packages/frontend/src/widgets/index.ts b/packages/frontend/src/widgets/index.ts
index b783d783bc..29e4558f1e 100644
--- a/packages/frontend/src/widgets/index.ts
+++ b/packages/frontend/src/widgets/index.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/widgets/server-metric/cpu-mem.vue b/packages/frontend/src/widgets/server-metric/cpu-mem.vue
index f13b6a370d..27d3234207 100644
--- a/packages/frontend/src/widgets/server-metric/cpu-mem.vue
+++ b/packages/frontend/src/widgets/server-metric/cpu-mem.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -80,13 +80,13 @@ import * as Misskey from 'misskey-js';
 import { v4 as uuid } from 'uuid';
 
 const props = defineProps<{
-	connection: any,
+	connection: Misskey.ChannelConnection<Misskey.Channels['serverStats']>,
 	meta: Misskey.entities.ServerInfoResponse
 }>();
 
 const viewBoxX = ref<number>(50);
 const viewBoxY = ref<number>(30);
-const stats = ref<any[]>([]);
+const stats = ref<Misskey.entities.ServerStats[]>([]);
 const cpuGradientId = uuid();
 const cpuMaskId = uuid();
 const memGradientId = uuid();
@@ -107,6 +107,7 @@ onMounted(() => {
 	props.connection.on('statsLog', onStatsLog);
 	props.connection.send('requestLog', {
 		id: Math.random().toString().substring(2, 10),
+		length: 50,
 	});
 });
 
@@ -115,7 +116,7 @@ onBeforeUnmount(() => {
 	props.connection.off('statsLog', onStatsLog);
 });
 
-function onStats(connStats) {
+function onStats(connStats: Misskey.entities.ServerStats) {
 	stats.value.push(connStats);
 	if (stats.value.length > 50) stats.value.shift();
 
@@ -136,8 +137,8 @@ function onStats(connStats) {
 	memP.value = (connStats.mem.active / props.meta.mem.total * 100).toFixed(0);
 }
 
-function onStatsLog(statsLog) {
-	for (const revStats of [...statsLog].reverse()) {
+function onStatsLog(statsLog: Misskey.entities.ServerStatsLog) {
+	for (const revStats of statsLog.reverse()) {
 		onStats(revStats);
 	}
 }
diff --git a/packages/frontend/src/widgets/server-metric/cpu.vue b/packages/frontend/src/widgets/server-metric/cpu.vue
index 35c20c8935..e00ef187f3 100644
--- a/packages/frontend/src/widgets/server-metric/cpu.vue
+++ b/packages/frontend/src/widgets/server-metric/cpu.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -20,13 +20,13 @@ import * as Misskey from 'misskey-js';
 import XPie from './pie.vue';
 
 const props = defineProps<{
-	connection: any,
+	connection: Misskey.ChannelConnection<Misskey.Channels['serverStats']>,
 	meta: Misskey.entities.ServerInfoResponse
 }>();
 
 const usage = ref<number>(0);
 
-function onStats(stats) {
+function onStats(stats: Misskey.entities.ServerStats) {
 	usage.value = stats.cpu;
 }
 
diff --git a/packages/frontend/src/widgets/server-metric/disk.vue b/packages/frontend/src/widgets/server-metric/disk.vue
index 0704854878..e94a8b6848 100644
--- a/packages/frontend/src/widgets/server-metric/disk.vue
+++ b/packages/frontend/src/widgets/server-metric/disk.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/widgets/server-metric/index.vue b/packages/frontend/src/widgets/server-metric/index.vue
index 9a785d9112..1180a2a059 100644
--- a/packages/frontend/src/widgets/server-metric/index.vue
+++ b/packages/frontend/src/widgets/server-metric/index.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script lang="ts" setup>
 import { onUnmounted, ref } from 'vue';
 import * as Misskey from 'misskey-js';
-import { useWidgetPropsManager, Widget, WidgetComponentExpose } from '../widget.js';
+import { useWidgetPropsManager, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from '../widget.js';
 import XCpuMemory from './cpu-mem.vue';
 import XNet from './net.vue';
 import XCpu from './cpu.vue';
@@ -30,7 +30,7 @@ import XMemory from './mem.vue';
 import XDisk from './disk.vue';
 import MkContainer from '@/components/MkContainer.vue';
 import { GetFormResultType } from '@/scripts/form.js';
-import * as os from '@/os.js';
+import { misskeyApiGet } from '@/scripts/misskey-api.js';
 import { useStream } from '@/stream.js';
 import { i18n } from '@/i18n.js';
 
@@ -54,11 +54,8 @@ const widgetPropsDef = {
 
 type WidgetProps = GetFormResultType<typeof widgetPropsDef>;
 
-// 現時点ではvueの制限によりimportしたtypeをジェネリックに渡せない
-//const props = defineProps<WidgetComponentProps<WidgetProps>>();
-//const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
-const props = defineProps<{ widget?: Widget<WidgetProps>; }>();
-const emit = defineEmits<{ (ev: 'updateProps', props: WidgetProps); }>();
+const props = defineProps<WidgetComponentProps<WidgetProps>>();
+const emit = defineEmits<WidgetComponentEmits<WidgetProps>>();
 
 const { widgetProps, configure, save } = useWidgetPropsManager(name,
 	widgetPropsDef,
@@ -68,7 +65,7 @@ const { widgetProps, configure, save } = useWidgetPropsManager(name,
 
 const meta = ref<Misskey.entities.ServerInfoResponse | null>(null);
 
-os.apiGet('server-info', {}).then(res => {
+misskeyApiGet('server-info', {}).then(res => {
 	meta.value = res;
 });
 
diff --git a/packages/frontend/src/widgets/server-metric/mem.vue b/packages/frontend/src/widgets/server-metric/mem.vue
index 34a1f1ae3d..ba56d14211 100644
--- a/packages/frontend/src/widgets/server-metric/mem.vue
+++ b/packages/frontend/src/widgets/server-metric/mem.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -22,7 +22,7 @@ import XPie from './pie.vue';
 import bytes from '@/filters/bytes.js';
 
 const props = defineProps<{
-	connection: any,
+	connection: Misskey.ChannelConnection<Misskey.Channels['serverStats']>,
 	meta: Misskey.entities.ServerInfoResponse
 }>();
 
@@ -31,7 +31,7 @@ const total = ref<number>(0);
 const used = ref<number>(0);
 const free = ref<number>(0);
 
-function onStats(stats) {
+function onStats(stats: Misskey.entities.ServerStats) {
 	usage.value = stats.mem.active / props.meta.mem.total;
 	total.value = props.meta.mem.total;
 	used.value = stats.mem.active;
diff --git a/packages/frontend/src/widgets/server-metric/net.vue b/packages/frontend/src/widgets/server-metric/net.vue
index 7af88a94eb..d46aaa5f69 100644
--- a/packages/frontend/src/widgets/server-metric/net.vue
+++ b/packages/frontend/src/widgets/server-metric/net.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
@@ -54,13 +54,13 @@ import * as Misskey from 'misskey-js';
 import bytes from '@/filters/bytes.js';
 
 const props = defineProps<{
-	connection: any,
+	connection: Misskey.ChannelConnection<Misskey.Channels['serverStats']>,
 	meta: Misskey.entities.ServerInfoResponse
 }>();
 
 const viewBoxX = ref<number>(50);
 const viewBoxY = ref<number>(30);
-const stats = ref<any[]>([]);
+const stats = ref<Misskey.entities.ServerStats[]>([]);
 const inPolylinePoints = ref<string>('');
 const outPolylinePoints = ref<string>('');
 const inPolygonPoints = ref<string>('');
@@ -77,6 +77,7 @@ onMounted(() => {
 	props.connection.on('statsLog', onStatsLog);
 	props.connection.send('requestLog', {
 		id: Math.random().toString().substring(2, 10),
+		length: 50,
 	});
 });
 
@@ -85,7 +86,7 @@ onBeforeUnmount(() => {
 	props.connection.off('statsLog', onStatsLog);
 });
 
-function onStats(connStats) {
+function onStats(connStats: Misskey.entities.ServerStats) {
 	stats.value.push(connStats);
 	if (stats.value.length > 50) stats.value.shift();
 
@@ -109,8 +110,8 @@ function onStats(connStats) {
 	outRecent.value = connStats.net.tx;
 }
 
-function onStatsLog(statsLog) {
-	for (const revStats of [...statsLog].reverse()) {
+function onStatsLog(statsLog: Misskey.entities.ServerStatsLog) {
+	for (const revStats of statsLog.reverse()) {
 		onStats(revStats);
 	}
 }
diff --git a/packages/frontend/src/widgets/server-metric/pie.vue b/packages/frontend/src/widgets/server-metric/pie.vue
index fd18a6a4f2..400cbe9fa2 100644
--- a/packages/frontend/src/widgets/server-metric/pie.vue
+++ b/packages/frontend/src/widgets/server-metric/pie.vue
@@ -1,5 +1,5 @@
 <!--
-SPDX-FileCopyrightText: syuilo and other misskey contributors
+SPDX-FileCopyrightText: syuilo and misskey-project
 SPDX-License-Identifier: AGPL-3.0-only
 -->
 
diff --git a/packages/frontend/src/widgets/widget.ts b/packages/frontend/src/widgets/widget.ts
index 9c7632fc9b..bfe8067adf 100644
--- a/packages/frontend/src/widgets/widget.ts
+++ b/packages/frontend/src/widgets/widget.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/workers/draw-blurhash.ts b/packages/frontend/src/workers/draw-blurhash.ts
index b919092223..22de6cd3a8 100644
--- a/packages/frontend/src/workers/draw-blurhash.ts
+++ b/packages/frontend/src/workers/draw-blurhash.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/src/workers/test-webgl2.ts b/packages/frontend/src/workers/test-webgl2.ts
index 8f57e5039b..b203ebe666 100644
--- a/packages/frontend/src/workers/test-webgl2.ts
+++ b/packages/frontend/src/workers/test-webgl2.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/test/autocomplete.test.ts b/packages/frontend/test/autocomplete.test.ts
new file mode 100644
index 0000000000..394ac3a821
--- /dev/null
+++ b/packages/frontend/test/autocomplete.test.ts
@@ -0,0 +1,34 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { assert, describe, test } from 'vitest';
+import { searchEmoji } from '@/scripts/search-emoji.js';
+
+describe('emoji autocomplete', () => {
+	test('名前の完全一致は名前の前方一致より優先される', async () => {
+		const result = searchEmoji('foooo', [{ emoji: ':foooo:', name: 'foooo' }, { emoji: ':foooobaaar:', name: 'foooobaaar' }]);
+		assert.equal(result[0].emoji, ':foooo:');
+	});
+
+	test('名前の前方一致は名前の部分一致より優先される', async () => {
+		const result = searchEmoji('baaa', [{ emoji: ':baaar:', name: 'baaar' }, { emoji: ':foooobaaar:', name: 'foooobaaar' }]);
+		assert.equal(result[0].emoji, ':baaar:');
+	});
+
+	test('名前の完全一致はタグの完全一致より優先される', async () => {
+		const result = searchEmoji('foooo', [{ emoji: ':foooo:', name: 'foooo' }, { emoji: ':baaar:', name: 'foooo', aliasOf: 'baaar' }]);
+		assert.equal(result[0].emoji, ':foooo:');
+	});
+
+	test('名前の前方一致はタグの前方一致より優先される', async () => {
+		const result = searchEmoji('foo', [{ emoji: ':foooo:', name: 'foooo' }, { emoji: ':baaar:', name: 'foooo', aliasOf: 'baaar' }]);
+		assert.equal(result[0].emoji, ':foooo:');
+	});
+
+	test('名前の部分一致はタグの部分一致より優先される', async () => {
+		const result = searchEmoji('oooo', [{ emoji: ':foooo:', name: 'foooo' }, { emoji: ':baaar:', name: 'foooo', aliasOf: 'baaar' }]);
+		assert.equal(result[0].emoji, ':foooo:');
+	});
+});
diff --git a/packages/frontend/test/emoji.test.ts b/packages/frontend/test/emoji.test.ts
new file mode 100644
index 0000000000..9a2989b373
--- /dev/null
+++ b/packages/frontend/test/emoji.test.ts
@@ -0,0 +1,41 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { describe, test, assert, afterEach } from 'vitest';
+import { render, cleanup, type RenderResult } from '@testing-library/vue';
+import { defaultStoreState } from './init.js';
+import { getEmojiName } from '@/scripts/emojilist.js';
+import { components } from '@/components/index.js';
+import { directives } from '@/directives/index.js';
+import MkEmoji from '@/components/global/MkEmoji.vue';
+
+describe('Emoji', () => {
+	const renderEmoji = (emoji: string): RenderResult => {
+		return render(MkEmoji, {
+			props: { emoji },
+			global: { directives, components },
+		});
+	};
+
+	afterEach(() => {
+		cleanup();
+		defaultStoreState.emojiStyle = '';
+	});
+
+	describe('MkEmoji', () => {
+		test('Should render selector-less heart with color in native mode', async () => {
+			defaultStoreState.emojiStyle = 'native';
+			const mkEmoji = await renderEmoji('\u2764'); // monochrome heart
+			assert.ok(mkEmoji.queryByText('\u2764\uFE0F')); // colored heart
+			assert.ok(!mkEmoji.queryByText('\u2764'));
+		});
+	});
+
+	describe('Emoji list', () => {
+		test('Should get the name of the heart', () => {
+			assert.strictEqual(getEmojiName('\u2764'), 'heart');
+		});
+	});
+});
diff --git a/packages/frontend/test/home.test.ts b/packages/frontend/test/home.test.ts
index 094ea071b9..b3a4e8ff3a 100644
--- a/packages/frontend/test/home.test.ts
+++ b/packages/frontend/test/home.test.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/test/init.ts b/packages/frontend/test/init.ts
index 6d93ff8cb0..0cde571dcb 100644
--- a/packages/frontend/test/init.ts
+++ b/packages/frontend/test/init.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -17,21 +17,23 @@ updateI18n(locales['en-US']);
 // XXX: misskey-js panics if WebSocket is not defined
 vi.stubGlobal('WebSocket', class WebSocket extends EventTarget { static CLOSING = 2; });
 
+export const defaultStoreState: Record<string, unknown> = {
+
+	// なんかtestがうまいこと動かないのでここに書く
+	dataSaver: {
+		media: false,
+		avatar: false,
+		urlPreview: false,
+		code: false,
+	},
+
+};
+
 // XXX: defaultStore somehow becomes undefined in vitest?
 vi.mock('@/store.js', () => {
 	return {
 		defaultStore: {
-			state: {
-
-				// なんかtestがうまいこと動かないのでここに書く
-				dataSaver: {
-					media: false,
-					avatar: false,
-					urlPreview: false,
-					code: false,
-				},
-
-			},
+			state: defaultStoreState,
 		},
 	};
 });
diff --git a/packages/frontend/test/note.test.ts b/packages/frontend/test/note.test.ts
index 8ccc05ff3e..7ce5f23e22 100644
--- a/packages/frontend/test/note.test.ts
+++ b/packages/frontend/test/note.test.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/frontend/test/nyaize.test.ts b/packages/frontend/test/nyaize.test.ts
new file mode 100644
index 0000000000..d4f0fff1db
--- /dev/null
+++ b/packages/frontend/test/nyaize.test.ts
@@ -0,0 +1,32 @@
+import { describe, test, assert, afterEach } from 'vitest';
+import { nyaize } from '@/scripts/nyaize.js';
+
+function runTests(cases) {
+    for (const c of cases) {
+        const [input,expected] = c;
+        const got = nyaize(input);
+        assert.strictEqual(got, expected);
+    }
+}
+
+describe('nyaize', () => {
+    test('ja-JP', () => {
+        runTests([
+            ['きれいな','きれいにゃ'],
+            ['ナナナ', 'ニャニャニャ'],
+            ['ナナ','ニャニャ'],
+        ]);
+    });
+    test('en-US', () => {
+        runTests([
+            ['bar','bar'],
+            ['banana','banyanya'],
+            ['booting','booting'],
+            ['morning','mornyan'],
+            ['mmmorning','mmmornyan'],
+            ['someone','someone'],
+            ['everyone','everynyan'],
+            ['foreveryone','foreverynyan'],
+        ]);
+    });
+});
diff --git a/packages/frontend/test/scroll.test.ts b/packages/frontend/test/scroll.test.ts
index 2334268d43..a0b56b7221 100644
--- a/packages/frontend/test/scroll.test.ts
+++ b/packages/frontend/test/scroll.test.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -9,6 +9,7 @@ import { onScrollBottom, onScrollTop } from '@/scripts/scroll.js';
 
 describe('Scroll', () => {
 	describe('onScrollTop', () => {
+		/* 動作しない(happy-domのバグ?)
 		test('Initial onScrollTop callback for connected elements', () => {
 			const { document } = new Window();
 			const div = document.createElement('div');
@@ -21,6 +22,7 @@ describe('Scroll', () => {
 
 			assert.ok(called);
 		});
+		*/
 
 		test('No onScrollTop callback for disconnected elements', () => {
 			const { document } = new Window();
@@ -35,11 +37,11 @@ describe('Scroll', () => {
 	});
 
 	describe('onScrollBottom', () => {
+		/* 動作しない(happy-domのバグ?)
 		test('Initial onScrollBottom callback for connected elements', () => {
 			const { document } = new Window();
 			const div = document.createElement('div');
 			assert.strictEqual(div.scrollTop, 0);
-			(div as any).scrollHeight = 100; // happy-dom has no scrollHeight
 
 			document.body.append(div);
 
@@ -48,12 +50,12 @@ describe('Scroll', () => {
 
 			assert.ok(called);
 		});
+		*/
 
 		test('No onScrollBottom callback for disconnected elements', () => {
 			const { document } = new Window();
 			const div = document.createElement('div');
 			assert.strictEqual(div.scrollTop, 0);
-			(div as any).scrollHeight = 100; // happy-dom has no scrollHeight
 
 			let called = false;
 			onScrollBottom(div as any as HTMLElement, () => called = true);
diff --git a/packages/frontend/test/url-preview.test.ts b/packages/frontend/test/url-preview.test.ts
index f760de9274..4b79d33348 100644
--- a/packages/frontend/test/url-preview.test.ts
+++ b/packages/frontend/test/url-preview.test.ts
@@ -1,12 +1,12 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import { describe, test, assert, afterEach } from 'vitest';
 import { render, cleanup, type RenderResult } from '@testing-library/vue';
 import './init';
-import type { summaly } from 'summaly';
+import type { summaly } from '@misskey-dev/summaly';
 import { components } from '@/components/index.js';
 import { directives } from '@/directives/index.js';
 import MkUrlPreview from '@/components/MkUrlPreview.vue';
@@ -116,6 +116,34 @@ describe('MkUrlPreview', () => {
 		assert.strictEqual(iframe?.allow, 'fullscreen;web-share');
 	});
 
+	test('A Summaly proxy response without allow falls back to the default', async () => {
+		const iframe = await renderAndOpenPreview({
+			url: 'https://example.local',
+			player: {
+				url: 'https://example.local/player',
+				width: null,
+				height: null,
+				allow: undefined as any,
+			},
+		});
+		assert.exists(iframe, 'iframe should exist');
+		assert.strictEqual(iframe?.allow, 'autoplay;encrypted-media;fullscreen');
+	});
+
+	test('Filtering the allow list from the Summaly proxy', async () => {
+		const iframe = await renderAndOpenPreview({
+			url: 'https://example.local',
+			player: {
+				url: 'https://example.local/player',
+				width: null,
+				height: null,
+				allow: ['autoplay', 'camera', 'fullscreen'],
+			},
+		});
+		assert.exists(iframe, 'iframe should exist');
+		assert.strictEqual(iframe?.allow, 'autoplay;fullscreen');
+	});
+
 	test('Having a player width should keep the fixed aspect ratio', async () => {
 		const iframe = await renderAndOpenPreview({
 			url: 'https://example.local',
diff --git a/packages/frontend/tsconfig.json b/packages/frontend/tsconfig.json
index 5d451c878c..819629a9cf 100644
--- a/packages/frontend/tsconfig.json
+++ b/packages/frontend/tsconfig.json
@@ -33,6 +33,7 @@
 		],
 		"types": [
 			"vite/client",
+			"vitest/importMeta",
 		],
 		"lib": [
 			"esnext",
diff --git a/packages/frontend/vite.config.local-dev.ts b/packages/frontend/vite.config.local-dev.ts
index 5a6f511c66..4c19dfbc66 100644
--- a/packages/frontend/vite.config.local-dev.ts
+++ b/packages/frontend/vite.config.local-dev.ts
@@ -1,5 +1,7 @@
 import dns from 'dns';
+import { readFile } from 'node:fs/promises';
 import { defineConfig } from 'vite';
+import * as yaml from 'js-yaml';
 import locales from '../../locales/index.js';
 import { getConfig } from './vite.config.js';
 
@@ -7,6 +9,11 @@ dns.setDefaultResultOrder('ipv4first');
 
 const defaultConfig = getConfig();
 
+const { port } = yaml.load(await readFile('../../.config/default.yml', 'utf-8'));
+
+const httpUrl = `http://localhost:${port}/`;
+const websocketUrl = `ws://localhost:${port}/`;
+
 const devConfig = {
 	// 基本の設定は vite.config.js から引き継ぐ
 	...defaultConfig,
@@ -19,28 +26,29 @@ const devConfig = {
 		proxy: {
 			'/api': {
 				changeOrigin: true,
-				target: 'http://localhost:3000/',
+				target: httpUrl,
 			},
-			'/assets': 'http://localhost:3000/',
-			'/static-assets': 'http://localhost:3000/',
-			'/client-assets': 'http://localhost:3000/',
-			'/files': 'http://localhost:3000/',
-			'/twemoji': 'http://localhost:3000/',
-			'/fluent-emoji': 'http://localhost:3000/',
-			'/sw.js': 'http://localhost:3000/',
+			'/assets': httpUrl,
+			'/static-assets': httpUrl,
+			'/client-assets': httpUrl,
+			'/files': httpUrl,
+			'/twemoji': httpUrl,
+			'/fluent-emoji': httpUrl,
+			'/tossface': httpUrl,
+			'/sw.js': httpUrl,
 			'/streaming': {
-				target: 'ws://localhost:3000/',
+				target: websocketUrl,
 				ws: true,
 			},
-			'/favicon.ico': 'http://localhost:3000/',
+			'/favicon.ico': httpUrl,
 			'/identicon': {
-				target: 'http://localhost:3000/',
+				target: httpUrl,
 				rewrite(path) {
 					return path.replace('@localhost:5173', '');
 				},
 			},
-			'/url': 'http://localhost:3000',
-			'/proxy': 'http://localhost:3000',
+			'/url': httpUrl,
+			'/proxy': httpUrl,
 		},
 	},
 	build: {
diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts
index ea8c4ced60..657f6002c6 100644
--- a/packages/frontend/vite.config.ts
+++ b/packages/frontend/vite.config.ts
@@ -71,6 +71,7 @@ export function getConfig(): UserConfig {
 				'/client-assets/': __dirname + '/assets/',
 				'/static-assets/': __dirname + '/../backend/assets/',
 				'/fluent-emojis/': __dirname + '/../../fluent-emojis/dist/',
+				'/tossface/': __dirname + '/../../tossface-emojis/dist/',
 				'/fluent-emoji/': __dirname + '/../../fluent-emojis/dist/',
 			},
 		},
@@ -98,11 +99,6 @@ export function getConfig(): UserConfig {
 			__VUE_PROD_DEVTOOLS__: false,
 		},
 
-		// https://vitejs.dev/guide/dep-pre-bundling.html#monorepos-and-linked-dependencies
-		optimizeDeps: {
-			include: ['misskey-js'],
-		},
-
 		build: {
 			target: [
 				'chrome116',
@@ -132,7 +128,7 @@ export function getConfig(): UserConfig {
 
 			// https://vitejs.dev/guide/dep-pre-bundling.html#monorepos-and-linked-dependencies
 			commonjsOptions: {
-				include: [/misskey-js/, /node_modules/],
+				include: [/misskey-js/, /misskey-reversi/, /misskey-bubble-game/, /node_modules/],
 			},
 		},
 
@@ -152,6 +148,7 @@ export function getConfig(): UserConfig {
 					},
 				},
 			},
+			includeSource: ['src/**/*.ts'],
 		},
 	};
 }
diff --git a/packages/misskey-bubble-game/.eslintignore b/packages/misskey-bubble-game/.eslintignore
new file mode 100644
index 0000000000..f22128f047
--- /dev/null
+++ b/packages/misskey-bubble-game/.eslintignore
@@ -0,0 +1,7 @@
+node_modules
+/built
+/coverage
+/.eslintrc.js
+/jest.config.ts
+/test
+/test-d
diff --git a/packages/misskey-bubble-game/.eslintrc.cjs b/packages/misskey-bubble-game/.eslintrc.cjs
new file mode 100644
index 0000000000..e2e31e9e33
--- /dev/null
+++ b/packages/misskey-bubble-game/.eslintrc.cjs
@@ -0,0 +1,9 @@
+module.exports = {
+	parserOptions: {
+		tsconfigRootDir: __dirname,
+		project: ['./tsconfig.json'],
+	},
+	extends: [
+		'../shared/.eslintrc.js',
+	],
+};
diff --git a/packages/misskey-bubble-game/build.js b/packages/misskey-bubble-game/build.js
new file mode 100644
index 0000000000..4744dfaf7b
--- /dev/null
+++ b/packages/misskey-bubble-game/build.js
@@ -0,0 +1,31 @@
+import { build } from "esbuild";
+import { globSync } from "glob";
+
+const entryPoints = globSync("./src/**/**.{ts,tsx}");
+
+/** @type {import('esbuild').BuildOptions} */
+const options = {
+  entryPoints,
+  minify: true,
+  outdir: "./built/esm",
+  target: "es2022",
+  platform: "browser",
+  format: "esm",
+};
+
+if (process.env.WATCH === "true") {
+  options.watch = {
+    onRebuild(error, result) {
+      if (error) {
+        console.error("watch build failed:", error);
+      } else {
+        console.log("watch build succeeded:", result);
+      }
+    },
+  };
+}
+
+build(options).catch((err) => {
+  process.stderr.write(err.stderr);
+  process.exit(1);
+});
diff --git a/packages/misskey-bubble-game/package.json b/packages/misskey-bubble-game/package.json
new file mode 100644
index 0000000000..ddc4c2134b
--- /dev/null
+++ b/packages/misskey-bubble-game/package.json
@@ -0,0 +1,48 @@
+{
+	"type": "module",
+	"name": "misskey-bubble-game",
+	"version": "0.0.1",
+	"types": "./built/dts/index.d.ts",
+	"exports": {
+		".": {
+			"import": "./built/esm/index.js",
+			"types": "./built/dts/index.d.ts"
+		},
+		"./*": {
+			"import": "./built/esm/*",
+			"types": "./built/dts/*"
+		}
+	},
+	"scripts": {
+		"build": "node ./build.js",
+		"build:tsc": "npm run tsc",
+		"tsc": "npm run tsc-esm && npm run tsc-dts",
+		"tsc-esm": "tsc --outDir built/esm",
+		"tsc-dts": "tsc --outDir built/dts --declaration true --emitDeclarationOnly true --declarationMap true",
+		"watch": "nodemon -w src -e ts,js,cjs,mjs,json --exec \"pnpm run build:tsc\"",
+		"eslint": "eslint . --ext .js,.jsx,.ts,.tsx",
+		"typecheck": "tsc --noEmit",
+		"lint": "pnpm typecheck && pnpm eslint"
+	},
+	"devDependencies": {
+		"@misskey-dev/eslint-plugin": "1.0.0",
+		"@types/matter-js": "0.19.6",
+		"@types/node": "20.11.5",
+		"@types/seedrandom": "3.0.8",
+		"@typescript-eslint/eslint-plugin": "7.1.0",
+		"@typescript-eslint/parser": "7.1.0",
+		"eslint": "8.57.0",
+		"nodemon": "3.0.2",
+		"typescript": "5.3.3"
+	},
+	"files": [
+		"built"
+	],
+	"dependencies": {
+		"esbuild": "0.19.11",
+		"eventemitter3": "5.0.1",
+		"glob": "^10.3.10",
+		"matter-js": "0.19.0",
+		"seedrandom": "3.0.5"
+	}
+}
diff --git a/packages/misskey-bubble-game/src/game.ts b/packages/misskey-bubble-game/src/game.ts
new file mode 100644
index 0000000000..3bce4b1dcf
--- /dev/null
+++ b/packages/misskey-bubble-game/src/game.ts
@@ -0,0 +1,495 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { EventEmitter } from 'eventemitter3';
+import * as Matter from 'matter-js';
+import seedrandom from 'seedrandom';
+import { NORAML_MONOS, SQUARE_MONOS, SWEETS_MONOS, YEN_MONOS } from './monos.js';
+
+export type Mono = {
+	id: string;
+	level: number;
+	sizeX: number;
+	sizeY: number;
+	shape: 'circle' | 'rectangle' | 'custom';
+	vertices?: Matter.Vector[][];
+	verticesSize?: number;
+	score: number;
+	dropCandidate: boolean;
+};
+
+type Log = {
+	frame: number;
+	operation: 'drop';
+	x: number;
+} | {
+	frame: number;
+	operation: 'hold';
+} | {
+	frame: number;
+	operation: 'surrender';
+};
+
+export class DropAndFusionGame extends EventEmitter<{
+	changeScore: (newScore: number) => void;
+	changeCombo: (newCombo: number) => void;
+	changeStock: (newStock: { id: string; mono: Mono }[]) => void;
+	changeHolding: (newHolding: { id: string; mono: Mono } | null) => void;
+	dropped: (x: number) => void;
+	fusioned: (x: number, y: number, nextMono: Mono | null, scoreDelta: number) => void;
+	collision: (energy: number, bodyA: Matter.Body, bodyB: Matter.Body) => void;
+	monoAdded: (mono: Mono) => void;
+	gameOver: () => void;
+}> {
+	private PHYSICS_QUALITY_FACTOR = 16; // 低いほどパフォーマンスが高いがガタガタして安定しなくなる、逆に高すぎても何故か不安定になる
+	private COMBO_INTERVAL = 60; // frame
+	public readonly GAME_VERSION = 3;
+	public readonly GAME_WIDTH = 450;
+	public readonly GAME_HEIGHT = 600;
+	public readonly DROP_COOLTIME = 30; // frame
+	public readonly PLAYAREA_MARGIN = 25;
+	private STOCK_MAX = 4;
+	private TICK_DELTA = 1000 / 60; // 60fps
+
+	public frame = 0;
+	public engine: Matter.Engine;
+	private tickCallbackQueue: { frame: number; callback: () => void; }[] = [];
+	private overflowCollider: Matter.Body;
+	private isGameOver = false;
+	private gameMode: 'normal' | 'yen' | 'square' | 'sweets' | 'space';
+	private rng: () => number;
+	private logs: Log[] = [];
+
+	/**
+	 * フィールドに出ていて、かつ合体の対象となるアイテム
+	 */
+	private fusionReadyBodyIds: Matter.Body['id'][] = [];
+
+	private gameOverReadyBodyIds: Matter.Body['id'][] = [];
+
+	/**
+	 * fusion予約アイテムのペア
+	 * TODO: これらのモノは光らせるなどの演出をすると視覚的に楽しそう
+	 */
+	private fusionReservedPairs: { bodyA: Matter.Body; bodyB: Matter.Body }[] = [];
+
+	private latestDroppedAt = 0; // frame
+	private latestFusionedAt = 0; // frame
+	private stock: { id: string; mono: Mono }[] = [];
+	private holding: { id: string; mono: Mono } | null = null;
+
+	public get monoDefinitions() {
+		switch (this.gameMode) {
+			case 'normal': return NORAML_MONOS;
+			case 'yen': return YEN_MONOS;
+			case 'square': return SQUARE_MONOS;
+			case 'sweets': return SWEETS_MONOS;
+			case 'space': return NORAML_MONOS;
+		}
+	}
+
+	private _combo = 0;
+	private get combo() {
+		return this._combo;
+	}
+	private set combo(value: number) {
+		this._combo = value;
+		this.emit('changeCombo', value);
+	}
+
+	private _score = 0;
+	private get score() {
+		return this._score;
+	}
+	private set score(value: number) {
+		this._score = value;
+		this.emit('changeScore', value);
+	}
+
+	private getMonoRenderOptions: null | ((mono: Mono) => Partial<Matter.IBodyRenderOptions>) = null;
+
+	public replayPlaybackRate = 1;
+
+	constructor(env: {
+		seed: string;
+		gameMode: DropAndFusionGame['gameMode'];
+		getMonoRenderOptions?: (mono: Mono) => Partial<Matter.IBodyRenderOptions>;
+	}) {
+		super();
+
+		//#region BIND
+		this.tick = this.tick.bind(this);
+		//#endregion
+
+		this.gameMode = env.gameMode;
+		this.getMonoRenderOptions = env.getMonoRenderOptions ?? null;
+		this.rng = seedrandom(env.seed);
+
+		// sweetsモードは重いため
+		const physicsQualityFactor = this.gameMode === 'sweets' ? 4 : this.PHYSICS_QUALITY_FACTOR;
+		this.engine = Matter.Engine.create({
+			constraintIterations: 2 * physicsQualityFactor,
+			positionIterations: 6 * physicsQualityFactor,
+			velocityIterations: 4 * physicsQualityFactor,
+			gravity: {
+				x: 0,
+				y: this.gameMode === 'space' ? 0.0125 : 1,
+			},
+			timing: {
+				timeScale: 2,
+			},
+			enableSleeping: false,
+		});
+
+		this.engine.world.bodies = [];
+
+		//#region walls
+		const WALL_OPTIONS: Matter.IChamferableBodyDefinition = {
+			label: '_wall_',
+			isStatic: true,
+			friction: 0.7,
+			slop: this.gameMode === 'space' ? 0.01 : 0.7,
+			render: {
+				strokeStyle: 'transparent',
+				fillStyle: 'transparent',
+			},
+		};
+
+		const thickness = 100;
+		Matter.Composite.add(this.engine.world, [
+			Matter.Bodies.rectangle(this.GAME_WIDTH / 2, this.GAME_HEIGHT + (thickness / 2) - this.PLAYAREA_MARGIN, this.GAME_WIDTH, thickness, WALL_OPTIONS),
+			Matter.Bodies.rectangle(this.GAME_WIDTH + (thickness / 2) - this.PLAYAREA_MARGIN, this.GAME_HEIGHT / 2, thickness, this.GAME_HEIGHT, WALL_OPTIONS),
+			Matter.Bodies.rectangle(-((thickness / 2) - this.PLAYAREA_MARGIN), this.GAME_HEIGHT / 2, thickness, this.GAME_HEIGHT, WALL_OPTIONS),
+		]);
+		//#endregion
+
+		this.overflowCollider = Matter.Bodies.rectangle(this.GAME_WIDTH / 2, 0, this.GAME_WIDTH, 200, {
+			label: '_overflow_',
+			isStatic: true,
+			isSensor: true,
+			render: {
+				strokeStyle: 'transparent',
+				fillStyle: 'transparent',
+			},
+		});
+		Matter.Composite.add(this.engine.world, this.overflowCollider);
+	}
+
+	public msToFrame(ms: number) {
+		return Math.round(ms / this.TICK_DELTA);
+	}
+
+	public frameToMs(frame: number) {
+		return frame * this.TICK_DELTA;
+	}
+
+	private createBody(mono: Mono, x: number, y: number) {
+		const options: Matter.IBodyDefinition = {
+			label: mono.id,
+			density: this.gameMode === 'space' ? 0.01 : ((mono.sizeX * mono.sizeY) / 10000),
+			restitution: this.gameMode === 'space' ? 0.5 : 0.2,
+			frictionAir: this.gameMode === 'space' ? 0 : 0.01,
+			friction: this.gameMode === 'space' ? 0.5 : 0.7,
+			frictionStatic: this.gameMode === 'space' ? 0 : 5,
+			slop: this.gameMode === 'space' ? 0.01 : 0.7,
+			//mass: 0,
+			render: this.getMonoRenderOptions ? this.getMonoRenderOptions(mono) : undefined,
+		};
+		if (mono.shape === 'circle') {
+			return Matter.Bodies.circle(x, y, mono.sizeX / 2, options);
+		// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+		} else if (mono.shape === 'rectangle') {
+			return Matter.Bodies.rectangle(x, y, mono.sizeX, mono.sizeY, options);
+		} else if (mono.shape === 'custom') {
+			return Matter.Bodies.fromVertices(x, y, mono.vertices!.map(i => i.map(j => ({
+				x: (j.x / mono.verticesSize!) * mono.sizeX,
+				y: (j.y / mono.verticesSize!) * mono.sizeY,
+			}))), options);
+		} else {
+			throw new Error('unrecognized shape');
+		}
+	}
+
+	private fusion(bodyA: Matter.Body, bodyB: Matter.Body) {
+		if (this.latestFusionedAt > this.frame - this.COMBO_INTERVAL) {
+			this.combo++;
+		} else {
+			this.combo = 1;
+		}
+		this.latestFusionedAt = this.frame;
+
+		const newX = (bodyA.position.x + bodyB.position.x) / 2;
+		const newY = (bodyA.position.y + bodyB.position.y) / 2;
+
+		this.fusionReadyBodyIds = this.fusionReadyBodyIds.filter(x => x !== bodyA.id && x !== bodyB.id);
+		this.gameOverReadyBodyIds = this.gameOverReadyBodyIds.filter(x => x !== bodyA.id && x !== bodyB.id);
+		Matter.Composite.remove(this.engine.world, [bodyA, bodyB]);
+
+		const currentMono = this.monoDefinitions.find(y => y.id === bodyA.label)!;
+		const nextMono = this.monoDefinitions.find(x => x.level === currentMono.level + 1) ?? null;
+
+		if (nextMono) {
+			const body = this.createBody(nextMono, newX, newY);
+			Matter.Composite.add(this.engine.world, body);
+
+			// 連鎖してfusionした場合の分かりやすさのため少し間を置いてからfusion対象になるようにする
+			this.tickCallbackQueue.push({
+				frame: this.frame + this.msToFrame(100),
+				callback: () => {
+					this.fusionReadyBodyIds.push(body.id);
+				},
+			});
+
+			this.emit('monoAdded', nextMono);
+		}
+
+		const hasComboBonus = this.gameMode !== 'yen' && this.gameMode !== 'sweets';
+		const comboBonus = hasComboBonus ? 1 + ((this.combo - 1) / 5) : 1;
+		const additionalScore = Math.round(currentMono.score * comboBonus);
+		this.score += additionalScore;
+
+		this.emit('fusioned', newX, newY, nextMono, additionalScore);
+	}
+
+	private onCollision(event: Matter.IEventCollision<Matter.Engine>) {
+		for (const pairs of event.pairs) {
+			const { bodyA, bodyB } = pairs;
+
+			const shouldFusion = (bodyA.label === bodyB.label) &&
+				!this.fusionReservedPairs.some(x =>
+					x.bodyA.id === bodyA.id ||
+					x.bodyA.id === bodyB.id ||
+					x.bodyB.id === bodyA.id ||
+					x.bodyB.id === bodyB.id);
+
+			if (shouldFusion) {
+				if (this.fusionReadyBodyIds.includes(bodyA.id) && this.fusionReadyBodyIds.includes(bodyB.id)) {
+					this.fusion(bodyA, bodyB);
+				} else {
+					this.fusionReservedPairs.push({ bodyA, bodyB });
+					this.tickCallbackQueue.push({
+						frame: this.frame + this.msToFrame(100),
+						callback: () => {
+							this.fusionReservedPairs = this.fusionReservedPairs.filter(x => x.bodyA.id !== bodyA.id && x.bodyB.id !== bodyB.id);
+							this.fusion(bodyA, bodyB);
+						},
+					});
+				}
+			} else {
+				const energy = pairs.collision.depth;
+
+				if (bodyA.label === '_overflow_' || bodyB.label === '_overflow_') continue;
+
+				if (bodyA.label !== '_wall_' && bodyB.label !== '_wall_') {
+					if (!this.gameOverReadyBodyIds.includes(bodyA.id)) this.gameOverReadyBodyIds.push(bodyA.id);
+					if (!this.gameOverReadyBodyIds.includes(bodyB.id)) this.gameOverReadyBodyIds.push(bodyB.id);
+				}
+
+				this.emit('collision', energy, bodyA, bodyB);
+			}
+		}
+	}
+
+	private onCollisionActive(event: Matter.IEventCollision<Matter.Engine>) {
+		for (const pairs of event.pairs) {
+			const { bodyA, bodyB } = pairs;
+
+			// ハコからあふれたかどうかの判定
+			if (bodyA.id === this.overflowCollider.id || bodyB.id === this.overflowCollider.id) {
+				if (this.gameOverReadyBodyIds.includes(bodyA.id) || this.gameOverReadyBodyIds.includes(bodyB.id)) {
+					this.gameOver();
+					break;
+				}
+				continue;
+			}
+		}
+	}
+
+	public surrender() {
+		this.logs.push({
+			frame: this.frame,
+			operation: 'surrender',
+		});
+
+		this.gameOver();
+	}
+
+	private gameOver() {
+		this.isGameOver = true;
+		this.emit('gameOver');
+	}
+
+	public start() {
+		for (let i = 0; i < this.STOCK_MAX; i++) {
+			this.stock.push({
+				id: this.rng().toString(),
+				mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(this.rng() * this.monoDefinitions.filter(x => x.dropCandidate).length)],
+			});
+		}
+		this.emit('changeStock', this.stock);
+
+		Matter.Events.on(this.engine, 'collisionStart', this.onCollision.bind(this));
+		Matter.Events.on(this.engine, 'collisionActive', this.onCollisionActive.bind(this));
+	}
+
+	public getLogs() {
+		return this.logs;
+	}
+
+	public tick() {
+		this.frame++;
+
+		if (this.latestFusionedAt < this.frame - this.COMBO_INTERVAL) {
+			this.combo = 0;
+		}
+
+		this.tickCallbackQueue = this.tickCallbackQueue.filter(x => {
+			if (x.frame === this.frame) {
+				x.callback();
+				return false;
+			} else {
+				return true;
+			}
+		});
+
+		Matter.Engine.update(this.engine, this.TICK_DELTA);
+
+		const hasNextTick = !this.isGameOver;
+
+		return hasNextTick;
+	}
+
+	public getActiveMonos() {
+		return this.engine.world.bodies.map(x => this.monoDefinitions.find((mono) => mono.id === x.label)!).filter(x => x !== undefined);
+	}
+
+	public drop(_x: number) {
+		if (this.isGameOver) return;
+		if (this.frame - this.latestDroppedAt < this.DROP_COOLTIME) return;
+
+		const head = this.stock.shift()!;
+		this.stock.push({
+			id: this.rng().toString(),
+			mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(this.rng() * this.monoDefinitions.filter(x => x.dropCandidate).length)],
+		});
+		this.emit('changeStock', this.stock);
+
+		const inputX = Math.round(_x);
+		const x = Math.min(this.GAME_WIDTH - this.PLAYAREA_MARGIN - (head.mono.sizeX / 2), Math.max(this.PLAYAREA_MARGIN + (head.mono.sizeX / 2), inputX));
+		const body = this.createBody(head.mono, x, 50 + head.mono.sizeY / 2);
+		this.logs.push({
+			frame: this.frame,
+			operation: 'drop',
+			x: inputX,
+		});
+
+		// add force
+		if (this.gameMode === 'space') {
+			Matter.Body.applyForce(body, body.position, {
+				x: 0,
+				y: (Math.PI * head.mono.sizeX * head.mono.sizeY) / 65536,
+			});
+		}
+
+		Matter.Composite.add(this.engine.world, body);
+
+		this.fusionReadyBodyIds.push(body.id);
+		this.latestDroppedAt = this.frame;
+
+		this.emit('dropped', x);
+		this.emit('monoAdded', head.mono);
+	}
+
+	public hold() {
+		if (this.isGameOver) return;
+
+		this.logs.push({
+			frame: this.frame,
+			operation: 'hold',
+		});
+
+		if (this.holding) {
+			const head = this.stock.shift()!;
+			this.stock.unshift(this.holding);
+			this.holding = head;
+			this.emit('changeHolding', this.holding);
+			this.emit('changeStock', this.stock);
+		} else {
+			const head = this.stock.shift()!;
+			this.holding = head;
+			this.stock.push({
+				id: this.rng().toString(),
+				mono: this.monoDefinitions.filter(x => x.dropCandidate)[Math.floor(this.rng() * this.monoDefinitions.filter(x => x.dropCandidate).length)],
+			});
+			this.emit('changeHolding', this.holding);
+			this.emit('changeStock', this.stock);
+		}
+	}
+
+	public static serializeLogs(logs: Log[]) {
+		const _logs: number[][] = [];
+
+		for (let i = 0; i < logs.length; i++) {
+			const log = logs[i];
+			const frameDelta = i === 0 ? log.frame : log.frame - logs[i - 1].frame;
+
+			switch (log.operation) {
+				case 'drop':
+					_logs.push([frameDelta, 0, log.x]);
+					break;
+				case 'hold':
+					_logs.push([frameDelta, 1]);
+					break;
+				case 'surrender':
+					_logs.push([frameDelta, 2]);
+					break;
+			}
+		}
+
+		return _logs;
+	}
+
+	public static deserializeLogs(logs: number[][]) {
+		const _logs: Log[] = [];
+
+		let frame = 0;
+
+		for (const log of logs) {
+			const frameDelta = log[0];
+			frame += frameDelta;
+
+			const operation = log[1];
+
+			switch (operation) {
+				case 0:
+					_logs.push({
+						frame,
+						operation: 'drop',
+						x: log[2],
+					});
+					break;
+				case 1:
+					_logs.push({
+						frame,
+						operation: 'hold',
+					});
+					break;
+				case 2:
+					_logs.push({
+						frame,
+						operation: 'surrender',
+					});
+					break;
+			}
+		}
+
+		return _logs;
+	}
+
+	public dispose() {
+		Matter.World.clear(this.engine.world, false);
+		Matter.Engine.clear(this.engine);
+	}
+}
diff --git a/packages/misskey-bubble-game/src/index.ts b/packages/misskey-bubble-game/src/index.ts
new file mode 100644
index 0000000000..004a7d008e
--- /dev/null
+++ b/packages/misskey-bubble-game/src/index.ts
@@ -0,0 +1,10 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { DropAndFusionGame, Mono } from './game.js';
+
+export {
+	DropAndFusionGame, Mono,
+};
diff --git a/packages/misskey-bubble-game/src/monos.ts b/packages/misskey-bubble-game/src/monos.ts
new file mode 100644
index 0000000000..41ab2358c4
--- /dev/null
+++ b/packages/misskey-bubble-game/src/monos.ts
@@ -0,0 +1,952 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Mono } from './game.js';
+
+const NORMAL_BASE_SIZE = 32;
+export const NORAML_MONOS: Mono[] = [{
+	id: '9377076d-c980-4d83-bdaf-175bc58275b7',
+	level: 10,
+	sizeX: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	sizeY: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	shape: 'circle',
+	score: 512,
+	dropCandidate: false,
+}, {
+	id: 'be9f38d2-b267-4b1a-b420-904e22e80568',
+	level: 9,
+	sizeX: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	sizeY: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	shape: 'circle',
+	score: 256,
+	dropCandidate: false,
+}, {
+	id: 'beb30459-b064-4888-926b-f572e4e72e0c',
+	level: 8,
+	sizeX: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	sizeY: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	shape: 'circle',
+	score: 128,
+	dropCandidate: false,
+}, {
+	id: 'feab6426-d9d8-49ae-849c-048cdbb6cdf0',
+	level: 7,
+	sizeX: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	sizeY: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	shape: 'circle',
+	score: 64,
+	dropCandidate: false,
+}, {
+	id: 'd6d8fed6-6d18-4726-81a1-6cf2c974df8a',
+	level: 6,
+	sizeX: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	sizeY: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	shape: 'circle',
+	score: 32,
+	dropCandidate: false,
+}, {
+	id: '249c728e-230f-4332-bbbf-281c271c75b2',
+	level: 5,
+	sizeX: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25,
+	sizeY: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25,
+	shape: 'circle',
+	score: 16,
+	dropCandidate: true,
+}, {
+	id: '23d67613-d484-4a93-b71e-3e81b19d6186',
+	level: 4,
+	sizeX: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25,
+	sizeY: NORMAL_BASE_SIZE * 1.25 * 1.25 * 1.25,
+	shape: 'circle',
+	score: 8,
+	dropCandidate: true,
+}, {
+	id: '3cbd0add-ad7d-4685-bad0-29f6dddc0b99',
+	level: 3,
+	sizeX: NORMAL_BASE_SIZE * 1.25 * 1.25,
+	sizeY: NORMAL_BASE_SIZE * 1.25 * 1.25,
+	shape: 'circle',
+	score: 4,
+	dropCandidate: true,
+}, {
+	id: '8f86d4f4-ee02-41bf-ad38-1ce0ae457fb5',
+	level: 2,
+	sizeX: NORMAL_BASE_SIZE * 1.25,
+	sizeY: NORMAL_BASE_SIZE * 1.25,
+	shape: 'circle',
+	score: 2,
+	dropCandidate: true,
+}, {
+	id: '64ec4add-ce39-42b4-96cb-33908f3f118d',
+	level: 1,
+	sizeX: NORMAL_BASE_SIZE,
+	sizeY: NORMAL_BASE_SIZE,
+	shape: 'circle',
+	score: 1,
+	dropCandidate: true,
+}];
+
+const YEN_BASE_SIZE = 32;
+const YEN_SATSU_BASE_SIZE = 70;
+export const YEN_MONOS: Mono[] = [{
+	id: '880f9bd9-802f-4135-a7e1-fd0e0331f726',
+	level: 10,
+	sizeX: (YEN_SATSU_BASE_SIZE * 2) * 1.25 * 1.25 * 1.25,
+	sizeY: YEN_SATSU_BASE_SIZE * 1.25 * 1.25 * 1.25,
+	shape: 'rectangle',
+	score: 10000,
+	dropCandidate: false,
+}, {
+	id: 'e807beb6-374a-4314-9cc2-aa5f17d96b6b',
+	level: 9,
+	sizeX: (YEN_SATSU_BASE_SIZE * 2) * 1.25 * 1.25,
+	sizeY: YEN_SATSU_BASE_SIZE * 1.25 * 1.25,
+	shape: 'rectangle',
+	score: 5000,
+	dropCandidate: false,
+}, {
+	id: '033445b7-8f90-4fc9-beca-71a9e87cb530',
+	level: 8,
+	sizeX: (YEN_SATSU_BASE_SIZE * 2) * 1.25,
+	sizeY: YEN_SATSU_BASE_SIZE * 1.25,
+	shape: 'rectangle',
+	score: 2000,
+	dropCandidate: false,
+}, {
+	id: '410a09ec-5f7f-46f6-b26f-cbca4ccbd091',
+	level: 7,
+	sizeX: YEN_SATSU_BASE_SIZE * 2,
+	sizeY: YEN_SATSU_BASE_SIZE,
+	shape: 'rectangle',
+	score: 1000,
+	dropCandidate: false,
+}, {
+	id: '2aae82bc-3fa4-49ad-a6b5-94d888e809f5',
+	level: 6,
+	sizeX: YEN_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	sizeY: YEN_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	shape: 'circle',
+	score: 500,
+	dropCandidate: false,
+}, {
+	id: 'a619bd67-d08f-4cc0-8c7e-c8072a4950cd',
+	level: 5,
+	sizeX: YEN_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25,
+	sizeY: YEN_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25,
+	shape: 'circle',
+	score: 100,
+	dropCandidate: true,
+}, {
+	id: 'c1c5d8e4-17d6-4455-befd-12154d731faa',
+	level: 4,
+	sizeX: YEN_BASE_SIZE * 1.25 * 1.25 * 1.25,
+	sizeY: YEN_BASE_SIZE * 1.25 * 1.25 * 1.25,
+	shape: 'circle',
+	score: 50,
+	dropCandidate: true,
+}, {
+	id: '7082648c-e428-44c4-887a-25c07a8ebdd5',
+	level: 3,
+	sizeX: YEN_BASE_SIZE * 1.25 * 1.25,
+	sizeY: YEN_BASE_SIZE * 1.25 * 1.25,
+	shape: 'circle',
+	score: 10,
+	dropCandidate: true,
+}, {
+	id: '0d8d40d5-e6e0-4d26-8a95-b8d842363379',
+	level: 2,
+	sizeX: YEN_BASE_SIZE * 1.25,
+	sizeY: YEN_BASE_SIZE * 1.25,
+	shape: 'circle',
+	score: 5,
+	dropCandidate: true,
+}, {
+	id: '9dec1b38-d99d-40de-8288-37367b983d0d',
+	level: 1,
+	sizeX: YEN_BASE_SIZE,
+	sizeY: YEN_BASE_SIZE,
+	shape: 'circle',
+	score: 1,
+	dropCandidate: true,
+}];
+
+const SQUARE_BASE_SIZE = 28;
+export const SQUARE_MONOS: Mono[] = [{
+	id: 'f75fd0ba-d3d4-40a4-9712-b470e45b0525',
+	level: 10,
+	sizeX: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	sizeY: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	shape: 'rectangle',
+	score: 512,
+	dropCandidate: false,
+}, {
+	id: '7b70f4af-1c01-45fd-af72-61b1f01e03d1',
+	level: 9,
+	sizeX: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	sizeY: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	shape: 'rectangle',
+	score: 256,
+	dropCandidate: false,
+}, {
+	id: '41607ef3-b6d6-4829-95b6-3737bf8bb956',
+	level: 8,
+	sizeX: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	sizeY: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	shape: 'rectangle',
+	score: 128,
+	dropCandidate: false,
+}, {
+	id: '8a8310d2-0374-460f-bb50-ca9cd3ee3416',
+	level: 7,
+	sizeX: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	sizeY: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	shape: 'rectangle',
+	score: 64,
+	dropCandidate: false,
+}, {
+	id: '1092e069-fe1a-450b-be97-b5d477ec398c',
+	level: 6,
+	sizeX: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	sizeY: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	shape: 'rectangle',
+	score: 32,
+	dropCandidate: false,
+}, {
+	id: '2294734d-7bb8-4781-bb7b-ef3820abf3d0',
+	level: 5,
+	sizeX: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25,
+	sizeY: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25,
+	shape: 'rectangle',
+	score: 16,
+	dropCandidate: true,
+}, {
+	id: 'ea8a61af-e350-45f7-ba6a-366fcd65692a',
+	level: 4,
+	sizeX: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25,
+	sizeY: SQUARE_BASE_SIZE * 1.25 * 1.25 * 1.25,
+	shape: 'rectangle',
+	score: 8,
+	dropCandidate: true,
+}, {
+	id: 'd0c74815-fc1c-4fbe-9953-c92e4b20f919',
+	level: 3,
+	sizeX: SQUARE_BASE_SIZE * 1.25 * 1.25,
+	sizeY: SQUARE_BASE_SIZE * 1.25 * 1.25,
+	shape: 'rectangle',
+	score: 4,
+	dropCandidate: true,
+}, {
+	id: 'd8fbd70e-611d-402d-87da-1a7fd8cd2c8d',
+	level: 2,
+	sizeX: SQUARE_BASE_SIZE * 1.25,
+	sizeY: SQUARE_BASE_SIZE * 1.25,
+	shape: 'rectangle',
+	score: 2,
+	dropCandidate: true,
+}, {
+	id: '35e476ee-44bd-4711-ad42-87be245d3efd',
+	level: 1,
+	sizeX: SQUARE_BASE_SIZE,
+	sizeY: SQUARE_BASE_SIZE,
+	shape: 'rectangle',
+	score: 1,
+	dropCandidate: true,
+}];
+
+const SWEETS_BASE_SIZE = 40;
+export const SWEETS_MONOS: Mono[] = [{
+	id: '77f724c0-88be-4aeb-8e1a-a00ed18e3844',
+	level: 10,
+	sizeX: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	sizeY: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	shape: 'custom',
+	vertices: [
+		[
+			{
+				'x': 14,
+				'y': 2,
+			},
+			{
+				'x': 2,
+				'y': 13,
+			},
+			{
+				'x': 2,
+				'y': 31,
+			},
+			{
+				'x': 30,
+				'y': 23,
+			},
+			{
+				'x': 30,
+				'y': 7,
+			},
+			{
+				'x': 29,
+				'y': 6,
+			},
+			{
+				'x': 20,
+				'y': 4,
+			},
+			{
+				'x': 17,
+				'y': 3,
+			},
+			{
+				'x': 16,
+				'y': 2,
+			},
+		],
+	],
+	verticesSize: 32,
+	score: 400,
+	dropCandidate: false,
+}, {
+	id: 'f3468ef4-2e1e-4906-8795-f147f39f7e1f',
+	level: 9,
+	sizeX: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	sizeY: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	shape: 'custom',
+	vertices: [
+		[
+			{
+				'x': 15,
+				'y': 2,
+			},
+			{
+				'x': 14,
+				'y': 3,
+			},
+			{
+				'x': 8,
+				'y': 4,
+			},
+			{
+				'x': 6,
+				'y': 5,
+			},
+			{
+				'x': 4,
+				'y': 8,
+			},
+			{
+				'x': 4,
+				'y': 15,
+			},
+			{
+				'x': 2,
+				'y': 19,
+			},
+			{
+				'x': 2,
+				'y': 22.36,
+			},
+			{
+				'x': 3,
+				'y': 25,
+			},
+			{
+				'x': 5,
+				'y': 28,
+			},
+			{
+				'x': 10,
+				'y': 30,
+			},
+			{
+				'x': 22,
+				'y': 30,
+			},
+			{
+				'x': 27,
+				'y': 28,
+			},
+			{
+				'x': 29,
+				'y': 25,
+			},
+			{
+				'x': 30,
+				'y': 22,
+			},
+			{
+				'x': 30,
+				'y': 19,
+			},
+			{
+				'x': 28,
+				'y': 15,
+			},
+			{
+				'x': 28,
+				'y': 8,
+			},
+			{
+				'x': 26,
+				'y': 5,
+			},
+			{
+				'x': 24,
+				'y': 4,
+			},
+			{
+				'x': 18,
+				'y': 3,
+			},
+			{
+				'x': 17,
+				'y': 2,
+			},
+		],
+	],
+	verticesSize: 32,
+	score: 380,
+	dropCandidate: false,
+}, {
+	id: 'bcb41129-6f2d-44ee-89d3-86eb2df564ba',
+	level: 8,
+	sizeX: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	sizeY: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	shape: 'custom',
+	vertices: [
+		[
+			{
+				'x': 15,
+				'y': 2,
+			},
+			{
+				'x': 11,
+				'y': 3,
+			},
+			{
+				'x': 8,
+				'y': 6,
+			},
+			{
+				'x': 7,
+				'y': 8,
+			},
+			{
+				'x': 6,
+				'y': 11,
+			},
+			{
+				'x': 6,
+				'y': 13,
+			},
+			{
+				'x': 7,
+				'y': 16,
+			},
+			{
+				'x': 8,
+				'y': 18,
+			},
+			{
+				'x': 15,
+				'y': 30,
+			},
+			{
+				'x': 17,
+				'y': 30,
+			},
+			{
+				'x': 24,
+				'y': 18,
+			},
+			{
+				'x': 25,
+				'y': 16,
+			},
+			{
+				'x': 26,
+				'y': 13,
+			},
+			{
+				'x': 26,
+				'y': 11,
+			},
+			{
+				'x': 25,
+				'y': 8,
+			},
+			{
+				'x': 24,
+				'y': 6,
+			},
+			{
+				'x': 21,
+				'y': 3,
+			},
+			{
+				'x': 17,
+				'y': 2,
+			},
+		],
+	],
+	verticesSize: 32,
+	score: 300,
+	dropCandidate: false,
+}, {
+	id: 'f058e1ad-1981-409b-b3a7-302de0a43744',
+	level: 7,
+	sizeX: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	sizeY: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	shape: 'custom',
+	vertices: [
+		[
+			{
+				'x': 17,
+				'y': 2.541,
+			},
+			{
+				'x': 14,
+				'y': 5.402,
+			},
+			{
+				'x': 10,
+				'y': 7,
+			},
+			{
+				'x': 10,
+				'y': 10.367,
+			},
+			{
+				'x': 8,
+				'y': 11,
+			},
+			{
+				'x': 8,
+				'y': 14,
+			},
+			{
+				'x': 5.781,
+				'y': 16.265,
+			},
+			{
+				'x': 6.594,
+				'y': 19.627,
+			},
+			{
+				'x': 9.414,
+				'y': 21,
+			},
+			{
+				'x': 12,
+				'y': 29.988,
+			},
+			{
+				'x': 21,
+				'y': 29.988,
+			},
+			{
+				'x': 22.016,
+				'y': 22.629,
+			},
+			{
+				'x': 23,
+				'y': 21.772,
+			},
+			{
+				'x': 23,
+				'y': 19.202,
+			},
+			{
+				'x': 25.783,
+				'y': 17.473,
+			},
+			{
+				'x': 25.783,
+				'y': 14.727,
+			},
+			{
+				'x': 24,
+				'y': 13.173,
+			},
+			{
+				'x': 24,
+				'y': 10.367,
+			},
+			{
+				'x': 22,
+				'y': 9.233,
+			},
+			{
+				'x': 22,
+				'y': 6.454,
+			},
+			{
+				'x': 18,
+				'y': 5,
+			},
+		],
+	],
+	verticesSize: 32,
+	score: 300,
+	dropCandidate: false,
+}, {
+	id: 'd22cfe38-5a3b-4b9c-a1a6-907930a3d732',
+	level: 6,
+	sizeX: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	sizeY: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25 * 1.25,
+	shape: 'custom',
+	vertices: [
+		[
+			{
+				'x': 15,
+				'y': 2,
+			},
+			{
+				'x': 11,
+				'y': 3,
+			},
+			{
+				'x': 8,
+				'y': 5,
+			},
+			{
+				'x': 7,
+				'y': 6,
+			},
+			{
+				'x': 5,
+				'y': 9,
+			},
+			{
+				'x': 4,
+				'y': 12,
+			},
+			{
+				'x': 4,
+				'y': 20,
+			},
+			{
+				'x': 5,
+				'y': 23,
+			},
+			{
+				'x': 7,
+				'y': 26,
+			},
+			{
+				'x': 11,
+				'y': 29,
+			},
+			{
+				'x': 14,
+				'y': 30,
+			},
+			{
+				'x': 18,
+				'y': 30,
+			},
+			{
+				'x': 21,
+				'y': 29,
+			},
+			{
+				'x': 25,
+				'y': 26,
+			},
+			{
+				'x': 27,
+				'y': 23,
+			},
+			{
+				'x': 28,
+				'y': 20,
+			},
+			{
+				'x': 28,
+				'y': 12,
+			},
+			{
+				'x': 27,
+				'y': 9,
+			},
+			{
+				'x': 25,
+				'y': 6,
+			},
+			{
+				'x': 24,
+				'y': 5,
+			},
+			{
+				'x': 21,
+				'y': 3,
+			},
+			{
+				'x': 17,
+				'y': 2,
+			},
+		],
+	],
+	verticesSize: 32,
+	score: 250,
+	dropCandidate: false,
+}, {
+	id: '79867083-a073-427e-ae82-07a70d9f3b4f',
+	level: 5,
+	sizeX: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25,
+	sizeY: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25 * 1.25,
+	shape: 'custom',
+	vertices: [
+		[
+			{
+				'x': 9,
+				'y': 15,
+			},
+			{
+				'x': 23,
+				'y': 15,
+			},
+			{
+				'x': 30,
+				'y': 27,
+			},
+			{
+				'x': 25.7,
+				'y': 30,
+			},
+			{
+				'x': 6.34,
+				'y': 30,
+			},
+			{
+				'x': 2,
+				'y': 27,
+			},
+		],
+	],
+	verticesSize: 32,
+	score: 200,
+	dropCandidate: true,
+}, {
+	id: '2e152a12-a567-4100-b4d4-d15d81ba47b1',
+	level: 4,
+	sizeX: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25,
+	sizeY: SWEETS_BASE_SIZE * 1.25 * 1.25 * 1.25,
+	shape: 'custom',
+	vertices: [
+		[
+			{
+				'x': 12,
+				'y': 2,
+			},
+			{
+				'x': 2,
+				'y': 12,
+			},
+			{
+				'x': 2,
+				'y': 14,
+			},
+			{
+				'x': 18,
+				'y': 30,
+			},
+			{
+				'x': 20,
+				'y': 30,
+			},
+			{
+				'x': 30,
+				'y': 20,
+			},
+			{
+				'x': 30,
+				'y': 18,
+			},
+			{
+				'x': 14,
+				'y': 2,
+			},
+		],
+	],
+	verticesSize: 32,
+	score: 200,
+	dropCandidate: true,
+}, {
+	id: '12250376-2258-4716-8eec-b3a7239461fc',
+	level: 3,
+	sizeX: SWEETS_BASE_SIZE * 1.25 * 1.25,
+	sizeY: SWEETS_BASE_SIZE * 1.25 * 1.25,
+	shape: 'custom',
+	vertices: [
+		[
+			{
+				'x': 12,
+				'y': 2,
+			},
+			{
+				'x': 7,
+				'y': 3,
+			},
+			{
+				'x': 3,
+				'y': 7,
+			},
+			{
+				'x': 2,
+				'y': 12,
+			},
+			{
+				'x': 3,
+				'y': 16,
+			},
+			{
+				'x': 5,
+				'y': 19,
+			},
+			{
+				'x': 8,
+				'y': 21,
+			},
+			{
+				'x': 12,
+				'y': 22,
+			},
+			{
+				'x': 18,
+				'y': 21,
+			},
+			{
+				'x': 27,
+				'y': 30,
+			},
+			{
+				'x': 30,
+				'y': 30,
+			},
+			{
+				'x': 30,
+				'y': 27,
+			},
+			{
+				'x': 21,
+				'y': 18,
+			},
+			{
+				'x': 22,
+				'y': 14.25,
+			},
+			{
+				'x': 22,
+				'y': 11,
+			},
+			{
+				'x': 21,
+				'y': 8,
+			},
+			{
+				'x': 19,
+				'y': 5,
+			},
+			{
+				'x': 16.5,
+				'y': 3,
+			},
+		],
+	],
+	verticesSize: 32,
+	score: 120,
+	dropCandidate: true,
+}, {
+	id: '4d4f2668-4be7-44a3-aa3a-856df6e25aa6',
+	level: 2,
+	sizeX: SWEETS_BASE_SIZE * 1.25,
+	sizeY: SWEETS_BASE_SIZE * 1.25,
+	shape: 'custom',
+	vertices: [
+		[
+			{
+				'x': 12,
+				'y': 1.9,
+			},
+			{
+				'x': 4,
+				'y': 4,
+			},
+			{
+				'x': 2,
+				'y': 12,
+			},
+			{
+				'x': 6,
+				'y': 13.375,
+			},
+			{
+				'x': 6,
+				'y': 18,
+			},
+			{
+				'x': 8,
+				'y': 22,
+			},
+			{
+				'x': 12,
+				'y': 25.372,
+			},
+			{
+				'x': 16,
+				'y': 26,
+			},
+			{
+				'x': 19,
+				'y': 25.372,
+			},
+			{
+				'x': 20,
+				'y': 30,
+			},
+			{
+				'x': 28,
+				'y': 27,
+			},
+			{
+				'x': 30,
+				'y': 20,
+			},
+			{
+				'x': 25.473,
+				'y': 19,
+			},
+			{
+				'x': 26,
+				'y': 15,
+			},
+			{
+				'x': 24,
+				'y': 10,
+			},
+			{
+				'x': 20,
+				'y': 7,
+			},
+			{
+				'x': 16,
+				'y': 6,
+			},
+			{
+				'x': 13,
+				'y': 6,
+			},
+		],
+	],
+	verticesSize: 32,
+	score: 20,
+	dropCandidate: true,
+}, {
+	id: 'c9984b40-4045-44c3-b260-d47b7b4625b2',
+	level: 1,
+	sizeX: SWEETS_BASE_SIZE,
+	sizeY: SWEETS_BASE_SIZE,
+	shape: 'circle',
+	score: 30,
+	dropCandidate: true,
+}];
diff --git a/packages/misskey-bubble-game/tsconfig.json b/packages/misskey-bubble-game/tsconfig.json
new file mode 100644
index 0000000000..f56b65e868
--- /dev/null
+++ b/packages/misskey-bubble-game/tsconfig.json
@@ -0,0 +1,33 @@
+{
+	"$schema": "https://json.schemastore.org/tsconfig",
+	"compilerOptions": {
+		"target": "ES2022",
+		"module": "nodenext",
+		"moduleResolution": "nodenext",
+		"declaration": true,
+		"declarationMap": true,
+		"sourceMap": true,
+		"outDir": "./built/",
+		"removeComments": true,
+		"strict": true,
+		"strictFunctionTypes": true,
+		"strictNullChecks": true,
+		"experimentalDecorators": true,
+		"noImplicitReturns": true,
+		"esModuleInterop": true,
+		"typeRoots": [
+			"./node_modules/@types"
+		],
+		"lib": [
+			"esnext",
+			"dom"
+		]
+	},
+	"include": [
+		"src/**/*"
+	],
+	"exclude": [
+		"node_modules",
+		"test/**/*"
+	]
+}
diff --git a/packages/misskey-js/LICENSE b/packages/misskey-js/LICENSE
index 11c1f9ce22..63762b85d8 100644
--- a/packages/misskey-js/LICENSE
+++ b/packages/misskey-js/LICENSE
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2021-2022 syuilo and other contributors
+Copyright (c) 2021-2024 syuilo and other contributors
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/packages/misskey-js/api-extractor.json b/packages/misskey-js/api-extractor.json
index a95281a6d5..f80d0f20a8 100644
--- a/packages/misskey-js/api-extractor.json
+++ b/packages/misskey-js/api-extractor.json
@@ -45,7 +45,7 @@
    *
    * SUPPORTED TOKENS: <projectFolder>, <packageName>, <unscopedPackageName>
    */
-  "mainEntryPointFilePath": "<projectFolder>/built/index.d.ts",
+  "mainEntryPointFilePath": "<projectFolder>/built/dts/index.d.ts",
 
   /**
    * A list of NPM package names whose exports should be treated as part of this package.
diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md
index d4c43f207c..b17776e8dd 100644
--- a/packages/misskey-js/etc/misskey-js.api.md
+++ b/packages/misskey-js/etc/misskey-js.api.md
@@ -85,6 +85,9 @@ type AdminAnnouncementsListResponse = operations['admin/announcements/list']['re
 // @public (undocumented)
 type AdminAnnouncementsUpdateRequest = operations['admin/announcements/update']['requestBody']['content']['application/json'];
 
+// @public (undocumented)
+type AdminApproveUserRequest = operations['admin/approve-user']['requestBody']['content']['application/json'];
+
 // @public (undocumented)
 type AdminAvatarDecorationsCreateRequest = operations['admin/avatar-decorations/create']['requestBody']['content']['application/json'];
 
@@ -103,9 +106,6 @@ type AdminAvatarDecorationsUpdateRequest = operations['admin/avatar-decorations/
 // @public (undocumented)
 type AdminDeleteAccountRequest = operations['admin/delete-account']['requestBody']['content']['application/json'];
 
-// @public (undocumented)
-type AdminDeleteAccountResponse = operations['admin/delete-account']['responses']['200']['content']['application/json'];
-
 // @public (undocumented)
 type AdminDeleteAllFilesOfAUserRequest = operations['admin/delete-all-files-of-a-user']['requestBody']['content']['application/json'];
 
@@ -127,6 +127,9 @@ type AdminEmojiAddAliasesBulkRequest = operations['admin/emoji/add-aliases-bulk'
 // @public (undocumented)
 type AdminEmojiAddRequest = operations['admin/emoji/add']['requestBody']['content']['application/json'];
 
+// @public (undocumented)
+type AdminEmojiAddResponse = operations['admin/emoji/add']['responses']['200']['content']['application/json'];
+
 // @public (undocumented)
 type AdminEmojiCopyRequest = operations['admin/emoji/copy']['requestBody']['content']['application/json'];
 
@@ -208,6 +211,9 @@ type AdminInviteListResponse = operations['admin/invite/list']['responses']['200
 // @public (undocumented)
 type AdminMetaResponse = operations['admin/meta']['responses']['200']['content']['application/json'];
 
+// @public (undocumented)
+type AdminNsfwUserRequest = operations['admin/nsfw-user']['requestBody']['content']['application/json'];
+
 // @public (undocumented)
 type AdminPromoCreateRequest = operations['admin/promo/create']['requestBody']['content']['application/json'];
 
@@ -304,15 +310,24 @@ type AdminShowUsersRequest = operations['admin/show-users']['requestBody']['cont
 // @public (undocumented)
 type AdminShowUsersResponse = operations['admin/show-users']['responses']['200']['content']['application/json'];
 
+// @public (undocumented)
+type AdminSilenceUserRequest = operations['admin/silence-user']['requestBody']['content']['application/json'];
+
 // @public (undocumented)
 type AdminSuspendUserRequest = operations['admin/suspend-user']['requestBody']['content']['application/json'];
 
+// @public (undocumented)
+type AdminUnnsfwUserRequest = operations['admin/unnsfw-user']['requestBody']['content']['application/json'];
+
 // @public (undocumented)
 type AdminUnsetUserAvatarRequest = operations['admin/unset-user-avatar']['requestBody']['content']['application/json'];
 
 // @public (undocumented)
 type AdminUnsetUserBannerRequest = operations['admin/unset-user-banner']['requestBody']['content']['application/json'];
 
+// @public (undocumented)
+type AdminUnsilenceUserRequest = operations['admin/unsilence-user']['requestBody']['content']['application/json'];
+
 // @public (undocumented)
 type AdminUnsuspendUserRequest = operations['admin/unsuspend-user']['requestBody']['content']['application/json'];
 
@@ -473,6 +488,15 @@ type BlockingListRequest = operations['blocking/list']['requestBody']['content']
 // @public (undocumented)
 type BlockingListResponse = operations['blocking/list']['responses']['200']['content']['application/json'];
 
+// @public (undocumented)
+type BubbleGameRankingRequest = operations['bubble-game/ranking']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type BubbleGameRankingResponse = operations['bubble-game/ranking']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type BubbleGameRegisterRequest = operations['bubble-game/register']['requestBody']['content']['application/json'];
+
 // @public (undocumented)
 type Channel = components['schemas']['Channel'];
 
@@ -508,10 +532,10 @@ export type Channels = {
             mention: (payload: Note) => void;
             reply: (payload: Note) => void;
             renote: (payload: Note) => void;
-            follow: (payload: User) => void;
-            followed: (payload: User) => void;
-            unfollow: (payload: User) => void;
-            meUpdated: (payload: MeDetailed) => void;
+            follow: (payload: UserDetailedNotMe) => void;
+            followed: (payload: UserDetailed | UserLite) => void;
+            unfollow: (payload: UserDetailed) => void;
+            meUpdated: (payload: UserDetailed) => void;
             pageEvent: (payload: PageEvent) => void;
             urlUploadFinished: (payload: {
                 marker: string;
@@ -521,6 +545,7 @@ export type Channels = {
             unreadNotification: (payload: Notification_2) => void;
             unreadMention: (payload: Note['id']) => void;
             readAllUnreadMentions: () => void;
+            notificationFlushed: () => void;
             unreadSpecifiedNote: (payload: Note['id']) => void;
             readAllUnreadSpecifiedNotes: () => void;
             readAllAntennas: () => void;
@@ -537,6 +562,7 @@ export type Channels = {
             readAntenna: (payload: Antenna) => void;
             receiveFollowRequest: (payload: User) => void;
             announcementCreated: (payload: AnnouncementCreated) => void;
+            edited: (payload: Note) => void;
         };
         receives: null;
     };
@@ -544,6 +570,7 @@ export type Channels = {
         params: {
             withRenotes?: boolean;
             withFiles?: boolean;
+            withBots?: boolean;
         };
         events: {
             note: (payload: Note) => void;
@@ -555,6 +582,7 @@ export type Channels = {
             withRenotes?: boolean;
             withReplies?: boolean;
             withFiles?: boolean;
+            withBots?: boolean;
         };
         events: {
             note: (payload: Note) => void;
@@ -566,6 +594,7 @@ export type Channels = {
             withRenotes?: boolean;
             withReplies?: boolean;
             withFiles?: boolean;
+            withBots?: boolean;
         };
         events: {
             note: (payload: Note) => void;
@@ -576,6 +605,18 @@ export type Channels = {
         params: {
             withRenotes?: boolean;
             withFiles?: boolean;
+            withBots?: boolean;
+        };
+        events: {
+            note: (payload: Note) => void;
+        };
+        receives: null;
+    };
+    bubbleTimeline: {
+        params: {
+            withRenotes?: boolean;
+            withFiles?: boolean;
+            withBots?: boolean;
         };
         events: {
             note: (payload: Note) => void;
@@ -586,6 +627,7 @@ export type Channels = {
         params: {
             listId: string;
             withFiles?: boolean;
+            withRenotes?: boolean;
         };
         events: {
             note: (payload: Note) => void;
@@ -636,7 +678,7 @@ export type Channels = {
             fileUpdated: (payload: DriveFile) => void;
             folderCreated: (payload: DriveFolder) => void;
             folderDeleted: (payload: DriveFolder['id']) => void;
-            folderUpdated: (payload: DriveFile) => void;
+            folderUpdated: (payload: DriveFolder) => void;
         };
         receives: null;
     };
@@ -678,6 +720,46 @@ export type Channels = {
         };
         receives: null;
     };
+    reversiGame: {
+        params: {
+            gameId: string;
+        };
+        events: {
+            started: (payload: {
+                game: ReversiGameDetailed;
+            }) => void;
+            ended: (payload: {
+                winnerId: User['id'] | null;
+                game: ReversiGameDetailed;
+            }) => void;
+            canceled: (payload: {
+                userId: User['id'];
+            }) => void;
+            changeReadyStates: (payload: {
+                user1: boolean;
+                user2: boolean;
+            }) => void;
+            updateSettings: (payload: {
+                userId: User['id'];
+                key: string;
+                value: any;
+            }) => void;
+            log: (payload: Record<string, any>) => void;
+        };
+        receives: {
+            putStone: {
+                pos: number;
+                id: string;
+            };
+            ready: boolean;
+            cancel: null | Record<string, never>;
+            updateSettings: {
+                key: string;
+                value: any;
+            };
+            claimTimeIsUp: null | Record<string, never>;
+        };
+    };
 };
 
 // @public (undocumented)
@@ -1034,6 +1116,18 @@ export type Endpoints = Overwrite<Endpoints_2, {
             };
         };
     };
+    'signup': {
+        req: SignupRequest;
+        res: SignupResponse;
+    };
+    'signup-pending': {
+        req: SignupPendingRequest;
+        res: SignupPendingResponse;
+    };
+    'signin': {
+        req: SigninRequest;
+        res: SigninResponse;
+    };
 }>;
 
 // @public (undocumented)
@@ -1053,6 +1147,12 @@ declare namespace entities {
         EmojiUpdated,
         EmojiDeleted,
         AnnouncementCreated,
+        SignupRequest,
+        SignupResponse,
+        SignupPendingRequest,
+        SignupPendingResponse,
+        SigninRequest,
+        SigninResponse,
         EmptyRequest,
         EmptyResponse,
         AdminMetaResponse,
@@ -1089,6 +1189,7 @@ declare namespace entities {
         AdminDriveShowFileResponse,
         AdminEmojiAddAliasesBulkRequest,
         AdminEmojiAddRequest,
+        AdminEmojiAddResponse,
         AdminEmojiCopyRequest,
         AdminEmojiCopyResponse,
         AdminEmojiDeleteBulkRequest,
@@ -1135,11 +1236,15 @@ declare namespace entities {
         AdminShowUserResponse,
         AdminShowUsersRequest,
         AdminShowUsersResponse,
+        AdminNsfwUserRequest,
+        AdminUnnsfwUserRequest,
+        AdminSilenceUserRequest,
+        AdminUnsilenceUserRequest,
         AdminSuspendUserRequest,
+        AdminApproveUserRequest,
         AdminUnsuspendUserRequest,
         AdminUpdateMetaRequest,
         AdminDeleteAccountRequest,
-        AdminDeleteAccountResponse,
         AdminUpdateUserNoteRequest,
         AdminRolesCreateRequest,
         AdminRolesCreateResponse,
@@ -1337,6 +1442,7 @@ declare namespace entities {
         HashtagsUsersResponse,
         IResponse,
         I2faDoneRequest,
+        I2faDoneResponse,
         I2faKeyDoneRequest,
         I2faKeyDoneResponse,
         I2faPasswordLessRequest,
@@ -1363,6 +1469,7 @@ declare namespace entities {
         IGalleryPostsResponse,
         IImportBlockingRequest,
         IImportFollowingRequest,
+        IImportNotesRequest,
         IImportMutingRequest,
         IImportUserListsRequest,
         IImportAntennasRequest,
@@ -1380,6 +1487,7 @@ declare namespace entities {
         IRegenerateTokenRequest,
         IRegistryGetAllRequest,
         IRegistryGetAllResponse,
+        IRegistryGetUnsecureRequest,
         IRegistryGetDetailRequest,
         IRegistryGetDetailResponse,
         IRegistryGetRequest,
@@ -1387,6 +1495,7 @@ declare namespace entities {
         IRegistryKeysWithTypeRequest,
         IRegistryKeysWithTypeResponse,
         IRegistryKeysRequest,
+        IRegistryKeysResponse,
         IRegistryRemoveRequest,
         IRegistryScopesWithDomainResponse,
         IRegistrySetRequest,
@@ -1447,6 +1556,8 @@ declare namespace entities {
         NotesFeaturedResponse,
         NotesGlobalTimelineRequest,
         NotesGlobalTimelineResponse,
+        NotesBubbleTimelineRequest,
+        NotesBubbleTimelineResponse,
         NotesHybridTimelineRequest,
         NotesHybridTimelineResponse,
         NotesLocalTimelineRequest,
@@ -1460,6 +1571,7 @@ declare namespace entities {
         NotesReactionsResponse,
         NotesReactionsCreateRequest,
         NotesReactionsDeleteRequest,
+        NotesLikeRequest,
         NotesRenotesRequest,
         NotesRenotesResponse,
         NotesRepliesRequest,
@@ -1481,6 +1593,10 @@ declare namespace entities {
         NotesUnrenoteRequest,
         NotesUserListTimelineRequest,
         NotesUserListTimelineResponse,
+        NotesEditRequest,
+        NotesEditResponse,
+        NotesVersionsRequest,
+        NotesVersionsResponse,
         NotificationsCreateRequest,
         PagePushRequest,
         PagesCreateRequest,
@@ -1589,6 +1705,21 @@ declare namespace entities {
         FetchExternalResourcesRequest,
         FetchExternalResourcesResponse,
         RetentionResponse,
+        SponsorsRequest,
+        BubbleGameRegisterRequest,
+        BubbleGameRankingRequest,
+        BubbleGameRankingResponse,
+        ReversiCancelMatchRequest,
+        ReversiGamesRequest,
+        ReversiGamesResponse,
+        ReversiMatchRequest,
+        ReversiMatchResponse,
+        ReversiInvitationsResponse,
+        ReversiShowGameRequest,
+        ReversiShowGameResponse,
+        ReversiSurrenderRequest,
+        ReversiVerifyRequest,
+        ReversiVerifyResponse,
         Error_2 as Error,
         UserLite,
         UserDetailedNotMeOnly,
@@ -1614,6 +1745,7 @@ declare namespace entities {
         Hashtag,
         InviteCode,
         Page,
+        PageBlock,
         Channel,
         QueueCount,
         Antenna,
@@ -1624,8 +1756,21 @@ declare namespace entities {
         EmojiDetailed,
         Flash,
         Signin,
+        RoleCondFormulaLogics,
+        RoleCondFormulaValueNot,
+        RoleCondFormulaValueIsLocalOrRemote,
+        RoleCondFormulaValueAssignedRole,
+        RoleCondFormulaValueCreated,
+        RoleCondFormulaFollowersOrFollowingOrNotes,
+        RoleCondFormulaValue,
         RoleLite,
-        Role
+        Role,
+        RolePolicies,
+        ReversiGameLite,
+        ReversiGameDetailed,
+        MetaLite,
+        MetaDetailedOnly,
+        MetaDetailed
     }
 }
 export { entities }
@@ -1881,6 +2026,9 @@ type HashtagsUsersResponse = operations['hashtags/users']['responses']['200']['c
 // @public (undocumented)
 type I2faDoneRequest = operations['i/2fa/done']['requestBody']['content']['application/json'];
 
+// @public (undocumented)
+type I2faDoneResponse = operations['i/2fa/done']['responses']['200']['content']['application/json'];
+
 // @public (undocumented)
 type I2faKeyDoneRequest = operations['i/2fa/key-done']['requestBody']['content']['application/json'];
 
@@ -1968,6 +2116,9 @@ type IImportFollowingRequest = operations['i/import-following']['requestBody']['
 // @public (undocumented)
 type IImportMutingRequest = operations['i/import-muting']['requestBody']['content']['application/json'];
 
+// @public (undocumented)
+type IImportNotesRequest = operations['i/import-notes']['requestBody']['content']['application/json'];
+
 // @public (undocumented)
 type IImportUserListsRequest = operations['i/import-user-lists']['requestBody']['content']['application/json'];
 
@@ -2049,9 +2200,15 @@ type IRegistryGetRequest = operations['i/registry/get']['requestBody']['content'
 // @public (undocumented)
 type IRegistryGetResponse = operations['i/registry/get']['responses']['200']['content']['application/json'];
 
+// @public (undocumented)
+type IRegistryGetUnsecureRequest = operations['i/registry/get-unsecure']['requestBody']['content']['application/json'];
+
 // @public (undocumented)
 type IRegistryKeysRequest = operations['i/registry/keys']['requestBody']['content']['application/json'];
 
+// @public (undocumented)
+type IRegistryKeysResponse = operations['i/registry/keys']['responses']['200']['content']['application/json'];
+
 // @public (undocumented)
 type IRegistryKeysWithTypeRequest = operations['i/registry/keys-with-type']['requestBody']['content']['application/json'];
 
@@ -2074,7 +2231,7 @@ type IResponse = operations['i']['responses']['200']['content']['application/jso
 type IRevokeTokenRequest = operations['i/revoke-token']['requestBody']['content']['application/json'];
 
 // @public (undocumented)
-function isAPIError(reason: any): reason is APIError;
+function isAPIError(reason: Record<PropertyKey, unknown>): reason is APIError;
 
 // @public (undocumented)
 type ISigninHistoryRequest = operations['i/signin-history']['requestBody']['content']['application/json'];
@@ -2127,6 +2284,15 @@ type MeDetailed = components['schemas']['MeDetailed'];
 // @public (undocumented)
 type MeDetailedOnly = components['schemas']['MeDetailedOnly'];
 
+// @public (undocumented)
+type MetaDetailed = components['schemas']['MetaDetailed'];
+
+// @public (undocumented)
+type MetaDetailedOnly = components['schemas']['MetaDetailedOnly'];
+
+// @public (undocumented)
+type MetaLite = components['schemas']['MetaLite'];
+
 // @public (undocumented)
 type MetaRequest = operations['meta']['requestBody']['content']['application/json'];
 
@@ -2144,10 +2310,13 @@ type ModerationLog = {
     id: ID;
     createdAt: DateString;
     userId: User['id'];
-    user: UserDetailed | null;
+    user: UserDetailedNotMe | null;
 } & ({
     type: 'updateServerSettings';
     info: ModerationLogPayloads['updateServerSettings'];
+} | {
+    type: 'approve';
+    info: ModerationLogPayloads['approve'];
 } | {
     type: 'suspend';
     info: ModerationLogPayloads['suspend'];
@@ -2220,6 +2389,9 @@ type ModerationLog = {
 } | {
     type: 'unsuspendRemoteInstance';
     info: ModerationLogPayloads['unsuspendRemoteInstance'];
+} | {
+    type: 'updateRemoteInstanceNote';
+    info: ModerationLogPayloads['updateRemoteInstanceNote'];
 } | {
     type: 'markSensitiveDriveFile';
     info: ModerationLogPayloads['markSensitiveDriveFile'];
@@ -2259,7 +2431,7 @@ type ModerationLog = {
 });
 
 // @public (undocumented)
-export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "createInvitation", "createAd", "updateAd", "deleteAd", "createAvatarDecoration", "updateAvatarDecoration", "deleteAvatarDecoration", "unsetUserAvatar", "unsetUserBanner"];
+export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "approve", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "updateRemoteInstanceNote", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "createInvitation", "createAd", "updateAd", "deleteAd", "createAvatarDecoration", "updateAvatarDecoration", "deleteAvatarDecoration", "unsetUserAvatar", "unsetUserBanner"];
 
 // @public (undocumented)
 type MuteCreateRequest = operations['mute/create']['requestBody']['content']['application/json'];
@@ -2294,6 +2466,12 @@ type NoteFavorite = components['schemas']['NoteFavorite'];
 // @public (undocumented)
 type NoteReaction = components['schemas']['NoteReaction'];
 
+// @public (undocumented)
+type NotesBubbleTimelineRequest = operations['notes/bubble-timeline']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type NotesBubbleTimelineResponse = operations['notes/bubble-timeline']['responses']['200']['content']['application/json'];
+
 // @public (undocumented)
 type NotesChildrenRequest = operations['notes/children']['requestBody']['content']['application/json'];
 
@@ -2321,6 +2499,12 @@ type NotesCreateResponse = operations['notes/create']['responses']['200']['conte
 // @public (undocumented)
 type NotesDeleteRequest = operations['notes/delete']['requestBody']['content']['application/json'];
 
+// @public (undocumented)
+type NotesEditRequest = operations['notes/edit']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type NotesEditResponse = operations['notes/edit']['responses']['200']['content']['application/json'];
+
 // @public (undocumented)
 type NotesFavoritesCreateRequest = operations['notes/favorites/create']['requestBody']['content']['application/json'];
 
@@ -2345,6 +2529,9 @@ type NotesHybridTimelineRequest = operations['notes/hybrid-timeline']['requestBo
 // @public (undocumented)
 type NotesHybridTimelineResponse = operations['notes/hybrid-timeline']['responses']['200']['content']['application/json'];
 
+// @public (undocumented)
+type NotesLikeRequest = operations['notes/like']['requestBody']['content']['application/json'];
+
 // @public (undocumented)
 type NotesLocalTimelineRequest = operations['notes/local-timeline']['requestBody']['content']['application/json'];
 
@@ -2447,6 +2634,12 @@ type NotesUserListTimelineRequest = operations['notes/user-list-timeline']['requ
 // @public (undocumented)
 type NotesUserListTimelineResponse = operations['notes/user-list-timeline']['responses']['200']['content']['application/json'];
 
+// @public (undocumented)
+type NotesVersionsRequest = operations['notes/versions']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type NotesVersionsResponse = operations['notes/versions']['responses']['200']['content']['application/json'];
+
 // @public (undocumented)
 export const noteVisibilities: readonly ["public", "home", "followers", "specified"];
 
@@ -2457,11 +2650,14 @@ type Notification_2 = components['schemas']['Notification'];
 type NotificationsCreateRequest = operations['notifications/create']['requestBody']['content']['application/json'];
 
 // @public (undocumented)
-export const notificationTypes: readonly ["note", "follow", "mention", "reply", "renote", "quote", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "roleAssigned", "achievementEarned"];
+export const notificationTypes: readonly ["note", "follow", "mention", "reply", "renote", "quote", "reaction", "pollVote", "pollEnded", "receiveFollowRequest", "followRequestAccepted", "groupInvited", "app", "roleAssigned", "achievementEarned", "edited"];
 
 // @public (undocumented)
 type Page = components['schemas']['Page'];
 
+// @public (undocumented)
+type PageBlock = components['schemas']['PageBlock'];
+
 // @public (undocumented)
 type PageEvent = {
     pageId: Page['id'];
@@ -2505,7 +2701,7 @@ type PagesUpdateRequest = operations['pages/update']['requestBody']['content']['
 function parse(acct: string): Acct;
 
 // @public (undocumented)
-export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "write:admin:delete-account", "write:admin:delete-all-files-of-a-user", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "read:admin:show-users", "write:admin:suspend-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unsuspend-user", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse"];
+export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "write:admin:delete-account", "write:admin:delete-all-files-of-a-user", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "read:admin:show-users", "write:admin:suspend-user", "write:admin:approve-user", "write:admin:nsfw-user", "write:admin:unnsfw-user", "write:admin:silence-user", "write:admin:unsilence-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unsuspend-user", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse"];
 
 // @public (undocumented)
 type PingResponse = operations['ping']['responses']['200']['content']['application/json'];
@@ -2536,7 +2732,7 @@ type QueueStats = {
 };
 
 // @public (undocumented)
-type QueueStatsLog = string[];
+type QueueStatsLog = QueueStats[];
 
 // @public (undocumented)
 type RenoteMuteCreateRequest = operations['renote-mute/create']['requestBody']['content']['application/json'];
@@ -2562,12 +2758,75 @@ type ResetPasswordRequest = operations['reset-password']['requestBody']['content
 // @public (undocumented)
 type RetentionResponse = operations['retention']['responses']['200']['content']['application/json'];
 
+// @public (undocumented)
+type ReversiCancelMatchRequest = operations['reversi/cancel-match']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ReversiGameDetailed = components['schemas']['ReversiGameDetailed'];
+
+// @public (undocumented)
+type ReversiGameLite = components['schemas']['ReversiGameLite'];
+
+// @public (undocumented)
+type ReversiGamesRequest = operations['reversi/games']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ReversiGamesResponse = operations['reversi/games']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ReversiInvitationsResponse = operations['reversi/invitations']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ReversiMatchRequest = operations['reversi/match']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ReversiMatchResponse = operations['reversi/match']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ReversiShowGameRequest = operations['reversi/show-game']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ReversiShowGameResponse = operations['reversi/show-game']['responses']['200']['content']['application/json'];
+
+// @public (undocumented)
+type ReversiSurrenderRequest = operations['reversi/surrender']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ReversiVerifyRequest = operations['reversi/verify']['requestBody']['content']['application/json'];
+
+// @public (undocumented)
+type ReversiVerifyResponse = operations['reversi/verify']['responses']['200']['content']['application/json'];
+
 // @public (undocumented)
 type Role = components['schemas']['Role'];
 
+// @public (undocumented)
+type RoleCondFormulaFollowersOrFollowingOrNotes = components['schemas']['RoleCondFormulaFollowersOrFollowingOrNotes'];
+
+// @public (undocumented)
+type RoleCondFormulaLogics = components['schemas']['RoleCondFormulaLogics'];
+
+// @public (undocumented)
+type RoleCondFormulaValue = components['schemas']['RoleCondFormulaValue'];
+
+// @public (undocumented)
+type RoleCondFormulaValueAssignedRole = components['schemas']['RoleCondFormulaValueAssignedRole'];
+
+// @public (undocumented)
+type RoleCondFormulaValueCreated = components['schemas']['RoleCondFormulaValueCreated'];
+
+// @public (undocumented)
+type RoleCondFormulaValueIsLocalOrRemote = components['schemas']['RoleCondFormulaValueIsLocalOrRemote'];
+
+// @public (undocumented)
+type RoleCondFormulaValueNot = components['schemas']['RoleCondFormulaValueNot'];
+
 // @public (undocumented)
 type RoleLite = components['schemas']['RoleLite'];
 
+// @public (undocumented)
+type RolePolicies = components['schemas']['RolePolicies'];
+
 // @public (undocumented)
 type RolesListResponse = operations['roles/list']['responses']['200']['content']['application/json'];
 
@@ -2610,11 +2869,55 @@ type ServerStats = {
 };
 
 // @public (undocumented)
-type ServerStatsLog = string[];
+type ServerStatsLog = ServerStats[];
 
 // @public (undocumented)
 type Signin = components['schemas']['Signin'];
 
+// @public (undocumented)
+type SigninRequest = {
+    username: string;
+    password: string;
+    token?: string;
+};
+
+// @public (undocumented)
+type SigninResponse = {
+    id: User['id'];
+    i: string;
+};
+
+// @public (undocumented)
+type SignupPendingRequest = {
+    code: string;
+};
+
+// @public (undocumented)
+type SignupPendingResponse = {
+    id: User['id'];
+    i: string;
+};
+
+// @public (undocumented)
+type SignupRequest = {
+    username: string;
+    password: string;
+    host?: string;
+    invitationCode?: string;
+    emailAddress?: string;
+    'hcaptcha-response'?: string | null;
+    'g-recaptcha-response'?: string | null;
+    'turnstile-response'?: string | null;
+};
+
+// @public (undocumented)
+type SignupResponse = MeDetailed & {
+    token: string;
+};
+
+// @public (undocumented)
+type SponsorsRequest = operations['sponsors']['requestBody']['content']['application/json'];
+
 // @public (undocumented)
 type StatsResponse = operations['stats']['responses']['200']['content']['application/json'];
 
diff --git a/packages/misskey-js/generator/package.json b/packages/misskey-js/generator/package.json
index 50b23f5792..a1c0f41cb2 100644
--- a/packages/misskey-js/generator/package.json
+++ b/packages/misskey-js/generator/package.json
@@ -7,16 +7,17 @@
 		"generate": "tsx src/generator.ts && eslint ./built/**/* --ext .ts --fix"
 	},
 	"devDependencies": {
-		"@apidevtools/swagger-parser": "10.1.0",
+		"@misskey-dev/eslint-plugin": "^1.0.0",
+		"@readme/openapi-parser": "2.5.0",
 		"@types/node": "20.9.1",
 		"@typescript-eslint/eslint-plugin": "6.11.0",
 		"@typescript-eslint/parser": "6.11.0",
 		"eslint": "8.53.0",
-		"typescript": "5.3.3",
-		"tsx": "4.4.0",
-		"ts-case-convert": "2.0.2",
 		"openapi-types": "12.1.3",
-		"openapi-typescript": "6.7.1"
+		"openapi-typescript": "6.7.3",
+		"ts-case-convert": "2.0.2",
+		"tsx": "4.4.0",
+		"typescript": "5.3.3"
 	},
 	"files": [
 		"built"
diff --git a/packages/misskey-js/generator/src/generator.ts b/packages/misskey-js/generator/src/generator.ts
index f12ed94513..f091e599a9 100644
--- a/packages/misskey-js/generator/src/generator.ts
+++ b/packages/misskey-js/generator/src/generator.ts
@@ -1,27 +1,11 @@
 import { mkdir, writeFile } from 'fs/promises';
-import { OpenAPIV3 } from 'openapi-types';
+import { OpenAPIV3_1 } from 'openapi-types';
 import { toPascal } from 'ts-case-convert';
-import SwaggerParser from '@apidevtools/swagger-parser';
+import OpenAPIParser from '@readme/openapi-parser';
 import openapiTS from 'openapi-typescript';
 
-function generateVersionHeaderComment(openApiDocs: OpenAPIV3.Document): string {
-	const contents = {
-		version: openApiDocs.info.version,
-		generatedAt: new Date().toISOString(),
-	};
-
-	const lines: string[] = [];
-	lines.push('/*');
-	for (const [key, value] of Object.entries(contents)) {
-		lines.push(` * ${key}: ${value}`);
-	}
-	lines.push(' */');
-
-	return lines.join('\n');
-}
-
 async function generateBaseTypes(
-	openApiDocs: OpenAPIV3.Document,
+	openApiDocs: OpenAPIV3_1.Document,
 	openApiJsonPath: string,
 	typeFileName: string,
 ) {
@@ -36,9 +20,6 @@ async function generateBaseTypes(
 	}
 	lines.push('');
 
-	lines.push(generateVersionHeaderComment(openApiDocs));
-	lines.push('');
-
 	const generatedTypes = await openapiTS(openApiJsonPath, { exportType: true });
 	lines.push(generatedTypes);
 	lines.push('');
@@ -47,7 +28,7 @@ async function generateBaseTypes(
 }
 
 async function generateSchemaEntities(
-	openApiDocs: OpenAPIV3.Document,
+	openApiDocs: OpenAPIV3_1.Document,
 	typeFileName: string,
 	outputPath: string,
 ) {
@@ -59,8 +40,6 @@ async function generateSchemaEntities(
 	const schemaNames = Object.keys(schemas);
 	const typeAliasLines: string[] = [];
 
-	typeAliasLines.push(generateVersionHeaderComment(openApiDocs));
-	typeAliasLines.push('');
 	typeAliasLines.push(`import { components } from '${toImportPath(typeFileName)}';`);
 	typeAliasLines.push(
 		...schemaNames.map(it => `export type ${it} = components['schemas']['${it}'];`),
@@ -71,7 +50,7 @@ async function generateSchemaEntities(
 }
 
 async function generateEndpoints(
-	openApiDocs: OpenAPIV3.Document,
+	openApiDocs: OpenAPIV3_1.Document,
 	typeFileName: string,
 	entitiesOutputPath: string,
 	endpointOutputPath: string,
@@ -79,7 +58,7 @@ async function generateEndpoints(
 	const endpoints: Endpoint[] = [];
 
 	// misskey-jsはPOST固定で送っているので、こちらも決め打ちする。別メソッドに対応することがあればこちらも直す必要あり
-	const paths = openApiDocs.paths;
+	const paths = openApiDocs.paths ?? {};
 	const postPathItems = Object.keys(paths)
 		.map(it => paths[it]?.post)
 		.filter(filterUndefined);
@@ -119,9 +98,6 @@ async function generateEndpoints(
 
 	const entitiesOutputLine: string[] = [];
 
-	entitiesOutputLine.push(generateVersionHeaderComment(openApiDocs));
-	entitiesOutputLine.push('');
-
 	entitiesOutputLine.push(`import { operations } from '${toImportPath(typeFileName)}';`);
 	entitiesOutputLine.push('');
 
@@ -139,9 +115,6 @@ async function generateEndpoints(
 
 	const endpointOutputLine: string[] = [];
 
-	endpointOutputLine.push(generateVersionHeaderComment(openApiDocs));
-	endpointOutputLine.push('');
-
 	endpointOutputLine.push('import type {');
 	endpointOutputLine.push(
 		...[emptyRequest, emptyResponse, ...entities].map(it => '\t' + it.generateName() + ','),
@@ -160,7 +133,7 @@ async function generateEndpoints(
 }
 
 async function generateApiClientJSDoc(
-	openApiDocs: OpenAPIV3.Document,
+	openApiDocs: OpenAPIV3_1.Document,
 	apiClientFileName: string,
 	endpointsFileName: string,
 	warningsOutputPath: string,
@@ -168,7 +141,7 @@ async function generateApiClientJSDoc(
 	const endpoints: { operationId: string; description: string; }[] = [];
 
 	// misskey-jsはPOST固定で送っているので、こちらも決め打ちする。別メソッドに対応することがあればこちらも直す必要あり
-	const paths = openApiDocs.paths;
+	const paths = openApiDocs.paths ?? {};
 	const postPathItems = Object.keys(paths)
 		.map(it => paths[it]?.post)
 		.filter(filterUndefined);
@@ -187,9 +160,6 @@ async function generateApiClientJSDoc(
 
 	const endpointOutputLine: string[] = [];
 
-	endpointOutputLine.push(generateVersionHeaderComment(openApiDocs));
-	endpointOutputLine.push('');
-
 	endpointOutputLine.push(`import type { SwitchCaseResponseType } from '${toImportPath(apiClientFileName)}';`);
 	endpointOutputLine.push(`import type { Endpoints } from '${toImportPath(endpointsFileName)}';`);
 	endpointOutputLine.push('');
@@ -221,21 +191,21 @@ async function generateApiClientJSDoc(
 	await writeFile(warningsOutputPath, endpointOutputLine.join('\n'));
 }
 
-function isRequestBodyObject(value: unknown): value is OpenAPIV3.RequestBodyObject {
+function isRequestBodyObject(value: unknown): value is OpenAPIV3_1.RequestBodyObject {
 	if (!value) {
 		return false;
 	}
 
-	const { content } = value as Record<keyof OpenAPIV3.RequestBodyObject, unknown>;
+	const { content } = value as Record<keyof OpenAPIV3_1.RequestBodyObject, unknown>;
 	return content !== undefined;
 }
 
-function isResponseObject(value: unknown): value is OpenAPIV3.ResponseObject {
+function isResponseObject(value: unknown): value is OpenAPIV3_1.ResponseObject {
 	if (!value) {
 		return false;
 	}
 
-	const { description } = value as Record<keyof OpenAPIV3.ResponseObject, unknown>;
+	const { description } = value as Record<keyof OpenAPIV3_1.ResponseObject, unknown>;
 	return description !== undefined;
 }
 
@@ -330,7 +300,7 @@ async function main() {
 	await mkdir(generatePath, { recursive: true });
 
 	const openApiJsonPath = './api.json';
-	const openApiDocs = await SwaggerParser.validate(openApiJsonPath) as OpenAPIV3.Document;
+	const openApiDocs = await OpenAPIParser.parse(openApiJsonPath) as OpenAPIV3_1.Document;
 
 	const typeFileName = './built/autogen/types.ts';
 	await generateBaseTypes(openApiDocs, openApiJsonPath, typeFileName);
diff --git a/packages/misskey-js/jest.config.cjs b/packages/misskey-js/jest.config.cjs
index e5a74170ea..1230a4b5e2 100644
--- a/packages/misskey-js/jest.config.cjs
+++ b/packages/misskey-js/jest.config.cjs
@@ -81,7 +81,17 @@ module.exports = {
 	// ],
 
 	// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
-	// moduleNameMapper: {},
+	moduleNameMapper: {
+		// Do not resolve .wasm.js to .wasm by the rule below
+		'^(.+)\\.wasm\\.js$': '$1.wasm.js',
+		// SWC converts @/foo/bar.js to `../../src/foo/bar.js`, and then this rule
+		// converts it again to `../../src/foo/bar` which then can be resolved to
+		// `.ts` files.
+		// See https://github.com/swc-project/jest/issues/64#issuecomment-1029753225
+		// TODO: Use `--allowImportingTsExtensions` on TypeScript 5.0 so that we can
+		// directly import `.ts` files without this hack.
+		'^((?:\\.{1,2}|[A-Z:])*/.*)\\.js$': '$1',
+	},
 
 	// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
 	// modulePathIgnorePatterns: [],
diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json
index 53d5044d68..772f001c07 100644
--- a/packages/misskey-js/package.json
+++ b/packages/misskey-js/package.json
@@ -1,12 +1,25 @@
 {
+	"type": "module",
 	"name": "misskey-js",
-	"version": "0.0.16",
+	"version": "2024.3.1",
 	"description": "Misskey SDK for JavaScript",
-	"main": "./built/index.js",
-	"types": "./built/index.d.ts",
+	"types": "./built/dts/index.d.ts",
+	"exports": {
+		".": {
+			"import": "./built/esm/index.js",
+			"types": "./built/dts/index.d.ts"
+		},
+		"./*": {
+			"import": "./built/esm/*",
+			"types": "./built/dts/*"
+		}
+	},
 	"scripts": {
-		"build": "tsc",
-		"watch": "nodemon -w src -e ts,js,cjs,mjs,json --exec \"pnpm run build\"",
+		"build": "npm run ts",
+		"ts": "npm run ts-esm && npm run ts-dts",
+		"ts-esm": "tsc --outDir built/esm",
+		"ts-dts": "tsc --outDir built/dts --declaration true --emitDeclarationOnly true --declarationMap true",
+		"watch": "nodemon -w src -e ts,js,cjs,mjs,json --exec \"pnpm run ts\"",
 		"tsd": "tsd",
 		"api": "pnpm api-extractor run --local --verbose",
 		"api-prod": "pnpm api-extractor run --verbose",
@@ -22,28 +35,31 @@
 		"url": "git+https://github.com/misskey-dev/misskey.js.git"
 	},
 	"devDependencies": {
-		"@microsoft/api-extractor": "7.38.5",
-		"@swc/jest": "0.2.29",
-		"@types/jest": "29.5.11",
-		"@types/node": "20.10.5",
-		"@typescript-eslint/eslint-plugin": "6.14.0",
-		"@typescript-eslint/parser": "6.14.0",
-		"eslint": "8.56.0",
+		"@microsoft/api-extractor": "7.39.1",
+		"@misskey-dev/eslint-plugin": "1.0.0",
+		"@swc/jest": "0.2.31",
+		"@types/jest": "29.5.12",
+		"@types/node": "20.11.22",
+		"@typescript-eslint/eslint-plugin": "7.1.0",
+		"@typescript-eslint/parser": "7.1.0",
+		"eslint": "8.57.0",
 		"jest": "29.7.0",
 		"jest-fetch-mock": "3.0.3",
 		"jest-websocket-mock": "2.5.0",
 		"mock-socket": "9.3.1",
 		"ncp": "2.0.0",
-		"nodemon": "3.0.2",
-		"tsd": "0.30.0",
+		"nodemon": "3.1.0",
+		"tsd": "0.30.7",
 		"typescript": "5.3.3"
 	},
 	"files": [
-		"built"
+		"built",
+		"built/esm",
+		"built/dts"
 	],
 	"dependencies": {
 		"@swc/cli": "0.1.63",
-		"@swc/core": "1.3.100",
+		"@swc/core": "1.3.105",
 		"eventemitter3": "5.0.1",
 		"reconnecting-websocket": "4.4.0"
 	}
diff --git a/packages/misskey-js/src/api.ts b/packages/misskey-js/src/api.ts
index 0d10faaada..134ead0d79 100644
--- a/packages/misskey-js/src/api.ts
+++ b/packages/misskey-js/src/api.ts
@@ -1,11 +1,11 @@
-import './autogen/apiClientJSDoc';
+import './autogen/apiClientJSDoc.js';
 
-import { SwitchCaseResponseType } from './api.types';
-import type { Endpoints } from './api.types';
+import { SwitchCaseResponseType } from './api.types.js';
+import type { Endpoints } from './api.types.js';
 
 export {
 	SwitchCaseResponseType,
-} from './api.types';
+} from './api.types.js';
 
 const MK_API_ERROR = Symbol();
 
@@ -17,7 +17,7 @@ export type APIError = {
 	info: Record<string, any>;
 };
 
-export function isAPIError(reason: any): reason is APIError {
+export function isAPIError(reason: Record<PropertyKey, unknown>): reason is APIError {
 	return reason[MK_API_ERROR] === true;
 }
 
diff --git a/packages/misskey-js/src/api.types.ts b/packages/misskey-js/src/api.types.ts
index d97646b7cc..af0bade5b3 100644
--- a/packages/misskey-js/src/api.types.ts
+++ b/packages/misskey-js/src/api.types.ts
@@ -1,16 +1,24 @@
-import { Endpoints as Gen } from './autogen/endpoint';
-import { UserDetailed } from './autogen/models';
-import { UsersShowRequest } from './autogen/entities';
+import { Endpoints as Gen } from './autogen/endpoint.js';
+import { UserDetailed } from './autogen/models.js';
+import { UsersShowRequest } from './autogen/entities.js';
+import {
+	SigninRequest,
+	SigninResponse,
+	SignupPendingRequest,
+	SignupPendingResponse,
+	SignupRequest,
+	SignupResponse,
+} from './entities.js';
 
 type Overwrite<T, U extends { [Key in keyof T]?: unknown }> = Omit<
 	T,
 	keyof U
 > & U;
 
-type SwitchCase = {
+type SwitchCase<Condition = unknown, Result = unknown> = {
 	$switch: {
-		$cases: [any, any][],
-		$default: any;
+		$cases: [Condition, Result][],
+		$default: Result;
 	};
 };
 
@@ -55,6 +63,21 @@ export type Endpoints = Overwrite<
 					$default: UserDetailed;
 				};
 			};
-		}
+		},
+		// api.jsonには載せないものなのでここで定義
+		'signup': {
+			req: SignupRequest;
+			res: SignupResponse;
+		},
+		// api.jsonには載せないものなのでここで定義
+		'signup-pending': {
+			req: SignupPendingRequest;
+			res: SignupPendingResponse;
+		},
+		// api.jsonには載せないものなのでここで定義
+		'signin': {
+			req: SigninRequest;
+			res: SigninResponse;
+		},
 	}
 >
diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
index 758beaf3a0..1796148530 100644
--- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts
+++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts
@@ -1,8 +1,3 @@
-/*
- * version: 2023.12.0
- * generatedAt: 2023-12-26T23:35:09.494Z
- */
-
 import type { SwitchCaseResponseType } from '../api.js';
 import type { Endpoints } from './endpoint.js';
 
@@ -33,7 +28,6 @@ declare module '../api.js' {
     /**
      * No description provided.
      * 
-     * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
      * **Credential required**: *No*
      */
     request<E extends 'admin/accounts/create', P extends Endpoints[E]['req']>(
@@ -692,6 +686,50 @@ declare module '../api.js' {
       credential?: string | null,
     ): Promise<SwitchCaseResponseType<E, P>>;
 
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *Yes* / **Permission**: *write:admin:nsfw-user*
+     */
+    request<E extends 'admin/nsfw-user', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *Yes* / **Permission**: *write:admin:unnsfw-user*
+     */
+    request<E extends 'admin/unnsfw-user', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *Yes* / **Permission**: *write:admin:silence-user*
+     */
+    request<E extends 'admin/silence-user', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *Yes* / **Permission**: *write:admin:unsilence-user*
+     */
+    request<E extends 'admin/unsilence-user', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
     /**
      * No description provided.
      * 
@@ -703,6 +741,17 @@ declare module '../api.js' {
       credential?: string | null,
     ): Promise<SwitchCaseResponseType<E, P>>;
 
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *Yes* / **Permission**: *write:admin:approve-user*
+     */
+    request<E extends 'admin/approve-user', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
     /**
      * No description provided.
      * 
@@ -2202,6 +2251,18 @@ declare module '../api.js' {
       credential?: string | null,
     ): Promise<SwitchCaseResponseType<E, P>>;
 
+    /**
+     * No description provided.
+     * 
+     * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
+     * **Credential required**: *Yes*
+     */
+    request<E extends 'i/export-data', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
     /**
      * No description provided.
      * 
@@ -2250,6 +2311,18 @@ declare module '../api.js' {
       credential?: string | null,
     ): Promise<SwitchCaseResponseType<E, P>>;
 
+    /**
+     * No description provided.
+     * 
+     * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
+     * **Credential required**: *Yes*
+     */
+    request<E extends 'i/export-clips', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
     /**
      * No description provided.
      * 
@@ -2343,6 +2416,18 @@ declare module '../api.js' {
       credential?: string | null,
     ): Promise<SwitchCaseResponseType<E, P>>;
 
+    /**
+     * No description provided.
+     * 
+     * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
+     * **Credential required**: *Yes*
+     */
+    request<E extends 'i/import-notes', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
     /**
      * No description provided.
      * 
@@ -2479,6 +2564,17 @@ declare module '../api.js' {
       credential?: string | null,
     ): Promise<SwitchCaseResponseType<E, P>>;
 
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *Yes* / **Permission**: *read:account*
+     */
+    request<E extends 'i/registry/get-unsecure', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
     /**
      * No description provided.
      * 
@@ -2958,6 +3054,17 @@ declare module '../api.js' {
       credential?: string | null,
     ): Promise<SwitchCaseResponseType<E, P>>;
 
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *No*
+     */
+    request<E extends 'notes/bubble-timeline', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
     /**
      * No description provided.
      * 
@@ -3046,6 +3153,17 @@ declare module '../api.js' {
       credential?: string | null,
     ): Promise<SwitchCaseResponseType<E, P>>;
 
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *Yes* / **Permission**: *write:reactions*
+     */
+    request<E extends 'notes/like', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
     /**
      * No description provided.
      * 
@@ -3178,6 +3296,28 @@ declare module '../api.js' {
       credential?: string | null,
     ): Promise<SwitchCaseResponseType<E, P>>;
 
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *Yes* / **Permission**: *write:notes*
+     */
+    request<E extends 'notes/edit', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *No*
+     */
+    request<E extends 'notes/versions', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
     /**
      * No description provided.
      * 
@@ -3189,6 +3329,17 @@ declare module '../api.js' {
       credential?: string | null,
     ): Promise<SwitchCaseResponseType<E, P>>;
 
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *Yes* / **Permission**: *write:notifications*
+     */
+    request<E extends 'notifications/flush', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
     /**
      * No description provided.
      * 
@@ -3974,5 +4125,115 @@ declare module '../api.js' {
       params: P,
       credential?: string | null,
     ): Promise<SwitchCaseResponseType<E, P>>;
+
+    /**
+     * Get Sharkey GH Sponsors
+     * 
+     * **Credential required**: *No*
+     */
+    request<E extends 'sponsors', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *Yes* / **Permission**: *write:account*
+     */
+    request<E extends 'bubble-game/register', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *No*
+     */
+    request<E extends 'bubble-game/ranking', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *Yes* / **Permission**: *write:account*
+     */
+    request<E extends 'reversi/cancel-match', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *No*
+     */
+    request<E extends 'reversi/games', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *Yes* / **Permission**: *write:account*
+     */
+    request<E extends 'reversi/match', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *Yes* / **Permission**: *read:account*
+     */
+    request<E extends 'reversi/invitations', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *No*
+     */
+    request<E extends 'reversi/show-game', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *Yes* / **Permission**: *write:account*
+     */
+    request<E extends 'reversi/surrender', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
+
+    /**
+     * No description provided.
+     * 
+     * **Credential required**: *No*
+     */
+    request<E extends 'reversi/verify', P extends Endpoints[E]['req']>(
+      endpoint: E,
+      params: P,
+      credential?: string | null,
+    ): Promise<SwitchCaseResponseType<E, P>>;
   }
 }
diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts
index 2ed76a22f9..e223f5faf7 100644
--- a/packages/misskey-js/src/autogen/endpoint.ts
+++ b/packages/misskey-js/src/autogen/endpoint.ts
@@ -1,8 +1,3 @@
-/*
- * version: 2023.12.0
- * generatedAt: 2023-12-26T23:35:09.491Z
- */
-
 import type {
 	EmptyRequest,
 	EmptyResponse,
@@ -40,6 +35,7 @@ import type {
 	AdminDriveShowFileResponse,
 	AdminEmojiAddAliasesBulkRequest,
 	AdminEmojiAddRequest,
+	AdminEmojiAddResponse,
 	AdminEmojiCopyRequest,
 	AdminEmojiCopyResponse,
 	AdminEmojiDeleteBulkRequest,
@@ -86,11 +82,15 @@ import type {
 	AdminShowUserResponse,
 	AdminShowUsersRequest,
 	AdminShowUsersResponse,
+	AdminNsfwUserRequest,
+	AdminUnnsfwUserRequest,
+	AdminSilenceUserRequest,
+	AdminUnsilenceUserRequest,
 	AdminSuspendUserRequest,
+	AdminApproveUserRequest,
 	AdminUnsuspendUserRequest,
 	AdminUpdateMetaRequest,
 	AdminDeleteAccountRequest,
-	AdminDeleteAccountResponse,
 	AdminUpdateUserNoteRequest,
 	AdminRolesCreateRequest,
 	AdminRolesCreateResponse,
@@ -288,6 +288,7 @@ import type {
 	HashtagsUsersResponse,
 	IResponse,
 	I2faDoneRequest,
+	I2faDoneResponse,
 	I2faKeyDoneRequest,
 	I2faKeyDoneResponse,
 	I2faPasswordLessRequest,
@@ -314,6 +315,7 @@ import type {
 	IGalleryPostsResponse,
 	IImportBlockingRequest,
 	IImportFollowingRequest,
+	IImportNotesRequest,
 	IImportMutingRequest,
 	IImportUserListsRequest,
 	IImportAntennasRequest,
@@ -331,6 +333,7 @@ import type {
 	IRegenerateTokenRequest,
 	IRegistryGetAllRequest,
 	IRegistryGetAllResponse,
+	IRegistryGetUnsecureRequest,
 	IRegistryGetDetailRequest,
 	IRegistryGetDetailResponse,
 	IRegistryGetRequest,
@@ -338,6 +341,7 @@ import type {
 	IRegistryKeysWithTypeRequest,
 	IRegistryKeysWithTypeResponse,
 	IRegistryKeysRequest,
+	IRegistryKeysResponse,
 	IRegistryRemoveRequest,
 	IRegistryScopesWithDomainResponse,
 	IRegistrySetRequest,
@@ -398,6 +402,8 @@ import type {
 	NotesFeaturedResponse,
 	NotesGlobalTimelineRequest,
 	NotesGlobalTimelineResponse,
+	NotesBubbleTimelineRequest,
+	NotesBubbleTimelineResponse,
 	NotesHybridTimelineRequest,
 	NotesHybridTimelineResponse,
 	NotesLocalTimelineRequest,
@@ -411,6 +417,7 @@ import type {
 	NotesReactionsResponse,
 	NotesReactionsCreateRequest,
 	NotesReactionsDeleteRequest,
+	NotesLikeRequest,
 	NotesRenotesRequest,
 	NotesRenotesResponse,
 	NotesRepliesRequest,
@@ -432,6 +439,10 @@ import type {
 	NotesUnrenoteRequest,
 	NotesUserListTimelineRequest,
 	NotesUserListTimelineResponse,
+	NotesEditRequest,
+	NotesEditResponse,
+	NotesVersionsRequest,
+	NotesVersionsResponse,
 	NotificationsCreateRequest,
 	PagePushRequest,
 	PagesCreateRequest,
@@ -540,6 +551,21 @@ import type {
 	FetchExternalResourcesRequest,
 	FetchExternalResourcesResponse,
 	RetentionResponse,
+	SponsorsRequest,
+	BubbleGameRegisterRequest,
+	BubbleGameRankingRequest,
+	BubbleGameRankingResponse,
+	ReversiCancelMatchRequest,
+	ReversiGamesRequest,
+	ReversiGamesResponse,
+	ReversiMatchRequest,
+	ReversiMatchResponse,
+	ReversiInvitationsResponse,
+	ReversiShowGameRequest,
+	ReversiShowGameResponse,
+	ReversiSurrenderRequest,
+	ReversiVerifyRequest,
+	ReversiVerifyResponse,
 } from './entities.js';
 
 export type Endpoints = {
@@ -568,7 +594,7 @@ export type Endpoints = {
 	'admin/drive/files': { req: AdminDriveFilesRequest; res: AdminDriveFilesResponse };
 	'admin/drive/show-file': { req: AdminDriveShowFileRequest; res: AdminDriveShowFileResponse };
 	'admin/emoji/add-aliases-bulk': { req: AdminEmojiAddAliasesBulkRequest; res: EmptyResponse };
-	'admin/emoji/add': { req: AdminEmojiAddRequest; res: EmptyResponse };
+	'admin/emoji/add': { req: AdminEmojiAddRequest; res: AdminEmojiAddResponse };
 	'admin/emoji/copy': { req: AdminEmojiCopyRequest; res: AdminEmojiCopyResponse };
 	'admin/emoji/delete-bulk': { req: AdminEmojiDeleteBulkRequest; res: EmptyResponse };
 	'admin/emoji/delete': { req: AdminEmojiDeleteRequest; res: EmptyResponse };
@@ -605,10 +631,15 @@ export type Endpoints = {
 	'admin/show-moderation-logs': { req: AdminShowModerationLogsRequest; res: AdminShowModerationLogsResponse };
 	'admin/show-user': { req: AdminShowUserRequest; res: AdminShowUserResponse };
 	'admin/show-users': { req: AdminShowUsersRequest; res: AdminShowUsersResponse };
+	'admin/nsfw-user': { req: AdminNsfwUserRequest; res: EmptyResponse };
+	'admin/unnsfw-user': { req: AdminUnnsfwUserRequest; res: EmptyResponse };
+	'admin/silence-user': { req: AdminSilenceUserRequest; res: EmptyResponse };
+	'admin/unsilence-user': { req: AdminUnsilenceUserRequest; res: EmptyResponse };
 	'admin/suspend-user': { req: AdminSuspendUserRequest; res: EmptyResponse };
+	'admin/approve-user': { req: AdminApproveUserRequest; res: EmptyResponse };
 	'admin/unsuspend-user': { req: AdminUnsuspendUserRequest; res: EmptyResponse };
 	'admin/update-meta': { req: AdminUpdateMetaRequest; res: EmptyResponse };
-	'admin/delete-account': { req: AdminDeleteAccountRequest; res: AdminDeleteAccountResponse };
+	'admin/delete-account': { req: AdminDeleteAccountRequest; res: EmptyResponse };
 	'admin/update-user-note': { req: AdminUpdateUserNoteRequest; res: EmptyResponse };
 	'admin/roles/create': { req: AdminRolesCreateRequest; res: AdminRolesCreateResponse };
 	'admin/roles/delete': { req: AdminRolesDeleteRequest; res: EmptyResponse };
@@ -728,7 +759,7 @@ export type Endpoints = {
 	'hashtags/trend': { req: EmptyRequest; res: HashtagsTrendResponse };
 	'hashtags/users': { req: HashtagsUsersRequest; res: HashtagsUsersResponse };
 	'i': { req: EmptyRequest; res: IResponse };
-	'i/2fa/done': { req: I2faDoneRequest; res: EmptyResponse };
+	'i/2fa/done': { req: I2faDoneRequest; res: I2faDoneResponse };
 	'i/2fa/key-done': { req: I2faKeyDoneRequest; res: I2faKeyDoneResponse };
 	'i/2fa/password-less': { req: I2faPasswordLessRequest; res: EmptyResponse };
 	'i/2fa/register-key': { req: I2faRegisterKeyRequest; res: I2faRegisterKeyResponse };
@@ -741,10 +772,12 @@ export type Endpoints = {
 	'i/claim-achievement': { req: IClaimAchievementRequest; res: EmptyResponse };
 	'i/change-password': { req: IChangePasswordRequest; res: EmptyResponse };
 	'i/delete-account': { req: IDeleteAccountRequest; res: EmptyResponse };
+	'i/export-data': { req: EmptyRequest; res: EmptyResponse };
 	'i/export-blocking': { req: EmptyRequest; res: EmptyResponse };
 	'i/export-following': { req: IExportFollowingRequest; res: EmptyResponse };
 	'i/export-mute': { req: EmptyRequest; res: EmptyResponse };
 	'i/export-notes': { req: EmptyRequest; res: EmptyResponse };
+	'i/export-clips': { req: EmptyRequest; res: EmptyResponse };
 	'i/export-favorites': { req: EmptyRequest; res: EmptyResponse };
 	'i/export-user-lists': { req: EmptyRequest; res: EmptyResponse };
 	'i/export-antennas': { req: EmptyRequest; res: EmptyResponse };
@@ -753,6 +786,7 @@ export type Endpoints = {
 	'i/gallery/posts': { req: IGalleryPostsRequest; res: IGalleryPostsResponse };
 	'i/import-blocking': { req: IImportBlockingRequest; res: EmptyResponse };
 	'i/import-following': { req: IImportFollowingRequest; res: EmptyResponse };
+	'i/import-notes': { req: IImportNotesRequest; res: EmptyResponse };
 	'i/import-muting': { req: IImportMutingRequest; res: EmptyResponse };
 	'i/import-user-lists': { req: IImportUserListsRequest; res: EmptyResponse };
 	'i/import-antennas': { req: IImportAntennasRequest; res: EmptyResponse };
@@ -765,10 +799,11 @@ export type Endpoints = {
 	'i/read-announcement': { req: IReadAnnouncementRequest; res: EmptyResponse };
 	'i/regenerate-token': { req: IRegenerateTokenRequest; res: EmptyResponse };
 	'i/registry/get-all': { req: IRegistryGetAllRequest; res: IRegistryGetAllResponse };
+	'i/registry/get-unsecure': { req: IRegistryGetUnsecureRequest; res: EmptyResponse };
 	'i/registry/get-detail': { req: IRegistryGetDetailRequest; res: IRegistryGetDetailResponse };
 	'i/registry/get': { req: IRegistryGetRequest; res: IRegistryGetResponse };
 	'i/registry/keys-with-type': { req: IRegistryKeysWithTypeRequest; res: IRegistryKeysWithTypeResponse };
-	'i/registry/keys': { req: IRegistryKeysRequest; res: EmptyResponse };
+	'i/registry/keys': { req: IRegistryKeysRequest; res: IRegistryKeysResponse };
 	'i/registry/remove': { req: IRegistryRemoveRequest; res: EmptyResponse };
 	'i/registry/scopes-with-domain': { req: EmptyRequest; res: IRegistryScopesWithDomainResponse };
 	'i/registry/set': { req: IRegistrySetRequest; res: EmptyResponse };
@@ -808,6 +843,7 @@ export type Endpoints = {
 	'notes/favorites/delete': { req: NotesFavoritesDeleteRequest; res: EmptyResponse };
 	'notes/featured': { req: NotesFeaturedRequest; res: NotesFeaturedResponse };
 	'notes/global-timeline': { req: NotesGlobalTimelineRequest; res: NotesGlobalTimelineResponse };
+	'notes/bubble-timeline': { req: NotesBubbleTimelineRequest; res: NotesBubbleTimelineResponse };
 	'notes/hybrid-timeline': { req: NotesHybridTimelineRequest; res: NotesHybridTimelineResponse };
 	'notes/local-timeline': { req: NotesLocalTimelineRequest; res: NotesLocalTimelineResponse };
 	'notes/mentions': { req: NotesMentionsRequest; res: NotesMentionsResponse };
@@ -816,6 +852,7 @@ export type Endpoints = {
 	'notes/reactions': { req: NotesReactionsRequest; res: NotesReactionsResponse };
 	'notes/reactions/create': { req: NotesReactionsCreateRequest; res: EmptyResponse };
 	'notes/reactions/delete': { req: NotesReactionsDeleteRequest; res: EmptyResponse };
+	'notes/like': { req: NotesLikeRequest; res: EmptyResponse };
 	'notes/renotes': { req: NotesRenotesRequest; res: NotesRenotesResponse };
 	'notes/replies': { req: NotesRepliesRequest; res: NotesRepliesResponse };
 	'notes/search-by-tag': { req: NotesSearchByTagRequest; res: NotesSearchByTagResponse };
@@ -828,7 +865,10 @@ export type Endpoints = {
 	'notes/translate': { req: NotesTranslateRequest; res: NotesTranslateResponse };
 	'notes/unrenote': { req: NotesUnrenoteRequest; res: EmptyResponse };
 	'notes/user-list-timeline': { req: NotesUserListTimelineRequest; res: NotesUserListTimelineResponse };
+	'notes/edit': { req: NotesEditRequest; res: NotesEditResponse };
+	'notes/versions': { req: NotesVersionsRequest; res: NotesVersionsResponse };
 	'notifications/create': { req: NotificationsCreateRequest; res: EmptyResponse };
+	'notifications/flush': { req: EmptyRequest; res: EmptyResponse };
 	'notifications/mark-all-as-read': { req: EmptyRequest; res: EmptyResponse };
 	'notifications/test-notification': { req: EmptyRequest; res: EmptyResponse };
 	'page-push': { req: PagePushRequest; res: EmptyResponse };
@@ -900,4 +940,14 @@ export type Endpoints = {
 	'fetch-rss': { req: FetchRssRequest; res: FetchRssResponse };
 	'fetch-external-resources': { req: FetchExternalResourcesRequest; res: FetchExternalResourcesResponse };
 	'retention': { req: EmptyRequest; res: RetentionResponse };
+	'sponsors': { req: SponsorsRequest; res: EmptyResponse };
+	'bubble-game/register': { req: BubbleGameRegisterRequest; res: EmptyResponse };
+	'bubble-game/ranking': { req: BubbleGameRankingRequest; res: BubbleGameRankingResponse };
+	'reversi/cancel-match': { req: ReversiCancelMatchRequest; res: EmptyResponse };
+	'reversi/games': { req: ReversiGamesRequest; res: ReversiGamesResponse };
+	'reversi/match': { req: ReversiMatchRequest; res: ReversiMatchResponse };
+	'reversi/invitations': { req: EmptyRequest; res: ReversiInvitationsResponse };
+	'reversi/show-game': { req: ReversiShowGameRequest; res: ReversiShowGameResponse };
+	'reversi/surrender': { req: ReversiSurrenderRequest; res: EmptyResponse };
+	'reversi/verify': { req: ReversiVerifyRequest; res: ReversiVerifyResponse };
 }
diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts
index c857e8e370..70568ac690 100644
--- a/packages/misskey-js/src/autogen/entities.ts
+++ b/packages/misskey-js/src/autogen/entities.ts
@@ -1,8 +1,3 @@
-/*
- * version: 2023.12.0
- * generatedAt: 2023-12-26T23:35:09.489Z
- */
-
 import { operations } from './types.js';
 
 export type EmptyRequest = Record<string, unknown> | undefined;
@@ -42,6 +37,7 @@ export type AdminDriveShowFileRequest = operations['admin/drive/show-file']['req
 export type AdminDriveShowFileResponse = operations['admin/drive/show-file']['responses']['200']['content']['application/json'];
 export type AdminEmojiAddAliasesBulkRequest = operations['admin/emoji/add-aliases-bulk']['requestBody']['content']['application/json'];
 export type AdminEmojiAddRequest = operations['admin/emoji/add']['requestBody']['content']['application/json'];
+export type AdminEmojiAddResponse = operations['admin/emoji/add']['responses']['200']['content']['application/json'];
 export type AdminEmojiCopyRequest = operations['admin/emoji/copy']['requestBody']['content']['application/json'];
 export type AdminEmojiCopyResponse = operations['admin/emoji/copy']['responses']['200']['content']['application/json'];
 export type AdminEmojiDeleteBulkRequest = operations['admin/emoji/delete-bulk']['requestBody']['content']['application/json'];
@@ -88,11 +84,15 @@ export type AdminShowUserRequest = operations['admin/show-user']['requestBody'][
 export type AdminShowUserResponse = operations['admin/show-user']['responses']['200']['content']['application/json'];
 export type AdminShowUsersRequest = operations['admin/show-users']['requestBody']['content']['application/json'];
 export type AdminShowUsersResponse = operations['admin/show-users']['responses']['200']['content']['application/json'];
+export type AdminNsfwUserRequest = operations['admin/nsfw-user']['requestBody']['content']['application/json'];
+export type AdminUnnsfwUserRequest = operations['admin/unnsfw-user']['requestBody']['content']['application/json'];
+export type AdminSilenceUserRequest = operations['admin/silence-user']['requestBody']['content']['application/json'];
+export type AdminUnsilenceUserRequest = operations['admin/unsilence-user']['requestBody']['content']['application/json'];
 export type AdminSuspendUserRequest = operations['admin/suspend-user']['requestBody']['content']['application/json'];
+export type AdminApproveUserRequest = operations['admin/approve-user']['requestBody']['content']['application/json'];
 export type AdminUnsuspendUserRequest = operations['admin/unsuspend-user']['requestBody']['content']['application/json'];
 export type AdminUpdateMetaRequest = operations['admin/update-meta']['requestBody']['content']['application/json'];
 export type AdminDeleteAccountRequest = operations['admin/delete-account']['requestBody']['content']['application/json'];
-export type AdminDeleteAccountResponse = operations['admin/delete-account']['responses']['200']['content']['application/json'];
 export type AdminUpdateUserNoteRequest = operations['admin/update-user-note']['requestBody']['content']['application/json'];
 export type AdminRolesCreateRequest = operations['admin/roles/create']['requestBody']['content']['application/json'];
 export type AdminRolesCreateResponse = operations['admin/roles/create']['responses']['200']['content']['application/json'];
@@ -290,6 +290,7 @@ export type HashtagsUsersRequest = operations['hashtags/users']['requestBody']['
 export type HashtagsUsersResponse = operations['hashtags/users']['responses']['200']['content']['application/json'];
 export type IResponse = operations['i']['responses']['200']['content']['application/json'];
 export type I2faDoneRequest = operations['i/2fa/done']['requestBody']['content']['application/json'];
+export type I2faDoneResponse = operations['i/2fa/done']['responses']['200']['content']['application/json'];
 export type I2faKeyDoneRequest = operations['i/2fa/key-done']['requestBody']['content']['application/json'];
 export type I2faKeyDoneResponse = operations['i/2fa/key-done']['responses']['200']['content']['application/json'];
 export type I2faPasswordLessRequest = operations['i/2fa/password-less']['requestBody']['content']['application/json'];
@@ -316,6 +317,7 @@ export type IGalleryPostsRequest = operations['i/gallery/posts']['requestBody'][
 export type IGalleryPostsResponse = operations['i/gallery/posts']['responses']['200']['content']['application/json'];
 export type IImportBlockingRequest = operations['i/import-blocking']['requestBody']['content']['application/json'];
 export type IImportFollowingRequest = operations['i/import-following']['requestBody']['content']['application/json'];
+export type IImportNotesRequest = operations['i/import-notes']['requestBody']['content']['application/json'];
 export type IImportMutingRequest = operations['i/import-muting']['requestBody']['content']['application/json'];
 export type IImportUserListsRequest = operations['i/import-user-lists']['requestBody']['content']['application/json'];
 export type IImportAntennasRequest = operations['i/import-antennas']['requestBody']['content']['application/json'];
@@ -333,6 +335,7 @@ export type IReadAnnouncementRequest = operations['i/read-announcement']['reques
 export type IRegenerateTokenRequest = operations['i/regenerate-token']['requestBody']['content']['application/json'];
 export type IRegistryGetAllRequest = operations['i/registry/get-all']['requestBody']['content']['application/json'];
 export type IRegistryGetAllResponse = operations['i/registry/get-all']['responses']['200']['content']['application/json'];
+export type IRegistryGetUnsecureRequest = operations['i/registry/get-unsecure']['requestBody']['content']['application/json'];
 export type IRegistryGetDetailRequest = operations['i/registry/get-detail']['requestBody']['content']['application/json'];
 export type IRegistryGetDetailResponse = operations['i/registry/get-detail']['responses']['200']['content']['application/json'];
 export type IRegistryGetRequest = operations['i/registry/get']['requestBody']['content']['application/json'];
@@ -340,6 +343,7 @@ export type IRegistryGetResponse = operations['i/registry/get']['responses']['20
 export type IRegistryKeysWithTypeRequest = operations['i/registry/keys-with-type']['requestBody']['content']['application/json'];
 export type IRegistryKeysWithTypeResponse = operations['i/registry/keys-with-type']['responses']['200']['content']['application/json'];
 export type IRegistryKeysRequest = operations['i/registry/keys']['requestBody']['content']['application/json'];
+export type IRegistryKeysResponse = operations['i/registry/keys']['responses']['200']['content']['application/json'];
 export type IRegistryRemoveRequest = operations['i/registry/remove']['requestBody']['content']['application/json'];
 export type IRegistryScopesWithDomainResponse = operations['i/registry/scopes-with-domain']['responses']['200']['content']['application/json'];
 export type IRegistrySetRequest = operations['i/registry/set']['requestBody']['content']['application/json'];
@@ -400,6 +404,8 @@ export type NotesFeaturedRequest = operations['notes/featured']['requestBody']['
 export type NotesFeaturedResponse = operations['notes/featured']['responses']['200']['content']['application/json'];
 export type NotesGlobalTimelineRequest = operations['notes/global-timeline']['requestBody']['content']['application/json'];
 export type NotesGlobalTimelineResponse = operations['notes/global-timeline']['responses']['200']['content']['application/json'];
+export type NotesBubbleTimelineRequest = operations['notes/bubble-timeline']['requestBody']['content']['application/json'];
+export type NotesBubbleTimelineResponse = operations['notes/bubble-timeline']['responses']['200']['content']['application/json'];
 export type NotesHybridTimelineRequest = operations['notes/hybrid-timeline']['requestBody']['content']['application/json'];
 export type NotesHybridTimelineResponse = operations['notes/hybrid-timeline']['responses']['200']['content']['application/json'];
 export type NotesLocalTimelineRequest = operations['notes/local-timeline']['requestBody']['content']['application/json'];
@@ -413,6 +419,7 @@ export type NotesReactionsRequest = operations['notes/reactions']['requestBody']
 export type NotesReactionsResponse = operations['notes/reactions']['responses']['200']['content']['application/json'];
 export type NotesReactionsCreateRequest = operations['notes/reactions/create']['requestBody']['content']['application/json'];
 export type NotesReactionsDeleteRequest = operations['notes/reactions/delete']['requestBody']['content']['application/json'];
+export type NotesLikeRequest = operations['notes/like']['requestBody']['content']['application/json'];
 export type NotesRenotesRequest = operations['notes/renotes']['requestBody']['content']['application/json'];
 export type NotesRenotesResponse = operations['notes/renotes']['responses']['200']['content']['application/json'];
 export type NotesRepliesRequest = operations['notes/replies']['requestBody']['content']['application/json'];
@@ -434,6 +441,10 @@ export type NotesTranslateResponse = operations['notes/translate']['responses'][
 export type NotesUnrenoteRequest = operations['notes/unrenote']['requestBody']['content']['application/json'];
 export type NotesUserListTimelineRequest = operations['notes/user-list-timeline']['requestBody']['content']['application/json'];
 export type NotesUserListTimelineResponse = operations['notes/user-list-timeline']['responses']['200']['content']['application/json'];
+export type NotesEditRequest = operations['notes/edit']['requestBody']['content']['application/json'];
+export type NotesEditResponse = operations['notes/edit']['responses']['200']['content']['application/json'];
+export type NotesVersionsRequest = operations['notes/versions']['requestBody']['content']['application/json'];
+export type NotesVersionsResponse = operations['notes/versions']['responses']['200']['content']['application/json'];
 export type NotificationsCreateRequest = operations['notifications/create']['requestBody']['content']['application/json'];
 export type PagePushRequest = operations['page-push']['requestBody']['content']['application/json'];
 export type PagesCreateRequest = operations['pages/create']['requestBody']['content']['application/json'];
@@ -542,3 +553,18 @@ export type FetchRssResponse = operations['fetch-rss']['responses']['200']['cont
 export type FetchExternalResourcesRequest = operations['fetch-external-resources']['requestBody']['content']['application/json'];
 export type FetchExternalResourcesResponse = operations['fetch-external-resources']['responses']['200']['content']['application/json'];
 export type RetentionResponse = operations['retention']['responses']['200']['content']['application/json'];
+export type SponsorsRequest = operations['sponsors']['requestBody']['content']['application/json'];
+export type BubbleGameRegisterRequest = operations['bubble-game/register']['requestBody']['content']['application/json'];
+export type BubbleGameRankingRequest = operations['bubble-game/ranking']['requestBody']['content']['application/json'];
+export type BubbleGameRankingResponse = operations['bubble-game/ranking']['responses']['200']['content']['application/json'];
+export type ReversiCancelMatchRequest = operations['reversi/cancel-match']['requestBody']['content']['application/json'];
+export type ReversiGamesRequest = operations['reversi/games']['requestBody']['content']['application/json'];
+export type ReversiGamesResponse = operations['reversi/games']['responses']['200']['content']['application/json'];
+export type ReversiMatchRequest = operations['reversi/match']['requestBody']['content']['application/json'];
+export type ReversiMatchResponse = operations['reversi/match']['responses']['200']['content']['application/json'];
+export type ReversiInvitationsResponse = operations['reversi/invitations']['responses']['200']['content']['application/json'];
+export type ReversiShowGameRequest = operations['reversi/show-game']['requestBody']['content']['application/json'];
+export type ReversiShowGameResponse = operations['reversi/show-game']['responses']['200']['content']['application/json'];
+export type ReversiSurrenderRequest = operations['reversi/surrender']['requestBody']['content']['application/json'];
+export type ReversiVerifyRequest = operations['reversi/verify']['requestBody']['content']['application/json'];
+export type ReversiVerifyResponse = operations['reversi/verify']['responses']['200']['content']['application/json'];
diff --git a/packages/misskey-js/src/autogen/models.ts b/packages/misskey-js/src/autogen/models.ts
index c5b81a6b41..6f61458600 100644
--- a/packages/misskey-js/src/autogen/models.ts
+++ b/packages/misskey-js/src/autogen/models.ts
@@ -1,8 +1,3 @@
-/*
- * version: 2023.12.0
- * generatedAt: 2023-12-26T23:35:09.485Z
- */
-
 import { components } from './types.js';
 export type Error = components['schemas']['Error'];
 export type UserLite = components['schemas']['UserLite'];
@@ -29,6 +24,7 @@ export type Blocking = components['schemas']['Blocking'];
 export type Hashtag = components['schemas']['Hashtag'];
 export type InviteCode = components['schemas']['InviteCode'];
 export type Page = components['schemas']['Page'];
+export type PageBlock = components['schemas']['PageBlock'];
 export type Channel = components['schemas']['Channel'];
 export type QueueCount = components['schemas']['QueueCount'];
 export type Antenna = components['schemas']['Antenna'];
@@ -39,5 +35,18 @@ export type EmojiSimple = components['schemas']['EmojiSimple'];
 export type EmojiDetailed = components['schemas']['EmojiDetailed'];
 export type Flash = components['schemas']['Flash'];
 export type Signin = components['schemas']['Signin'];
+export type RoleCondFormulaLogics = components['schemas']['RoleCondFormulaLogics'];
+export type RoleCondFormulaValueNot = components['schemas']['RoleCondFormulaValueNot'];
+export type RoleCondFormulaValueIsLocalOrRemote = components['schemas']['RoleCondFormulaValueIsLocalOrRemote'];
+export type RoleCondFormulaValueAssignedRole = components['schemas']['RoleCondFormulaValueAssignedRole'];
+export type RoleCondFormulaValueCreated = components['schemas']['RoleCondFormulaValueCreated'];
+export type RoleCondFormulaFollowersOrFollowingOrNotes = components['schemas']['RoleCondFormulaFollowersOrFollowingOrNotes'];
+export type RoleCondFormulaValue = components['schemas']['RoleCondFormulaValue'];
 export type RoleLite = components['schemas']['RoleLite'];
 export type Role = components['schemas']['Role'];
+export type RolePolicies = components['schemas']['RolePolicies'];
+export type ReversiGameLite = components['schemas']['ReversiGameLite'];
+export type ReversiGameDetailed = components['schemas']['ReversiGameDetailed'];
+export type MetaLite = components['schemas']['MetaLite'];
+export type MetaDetailedOnly = components['schemas']['MetaDetailedOnly'];
+export type MetaDetailed = components['schemas']['MetaDetailed'];
diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts
index 94bb263980..6b4ee9361e 100644
--- a/packages/misskey-js/src/autogen/types.ts
+++ b/packages/misskey-js/src/autogen/types.ts
@@ -1,11 +1,6 @@
 /* eslint @typescript-eslint/naming-convention: 0 */
 /* eslint @typescript-eslint/no-explicit-any: 0 */
 
-/*
- * version: 2023.12.0
- * generatedAt: 2023-12-26T23:35:09.389Z
- */
-
 /**
  * This file was auto-generated by openapi-typescript.
  * Do not make direct changes to the file.
@@ -40,7 +35,6 @@ export type paths = {
      * admin/accounts/create
      * @description No description provided.
      *
-     * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
      * **Credential required**: *No*
      */
     post: operations['admin/accounts/create'];
@@ -577,6 +571,42 @@ export type paths = {
      */
     post: operations['admin/show-users'];
   };
+  '/admin/nsfw-user': {
+    /**
+     * admin/nsfw-user
+     * @description No description provided.
+     *
+     * **Credential required**: *Yes* / **Permission**: *write:admin:nsfw-user*
+     */
+    post: operations['admin/nsfw-user'];
+  };
+  '/admin/unnsfw-user': {
+    /**
+     * admin/unnsfw-user
+     * @description No description provided.
+     *
+     * **Credential required**: *Yes* / **Permission**: *write:admin:unnsfw-user*
+     */
+    post: operations['admin/unnsfw-user'];
+  };
+  '/admin/silence-user': {
+    /**
+     * admin/silence-user
+     * @description No description provided.
+     *
+     * **Credential required**: *Yes* / **Permission**: *write:admin:silence-user*
+     */
+    post: operations['admin/silence-user'];
+  };
+  '/admin/unsilence-user': {
+    /**
+     * admin/unsilence-user
+     * @description No description provided.
+     *
+     * **Credential required**: *Yes* / **Permission**: *write:admin:unsilence-user*
+     */
+    post: operations['admin/unsilence-user'];
+  };
   '/admin/suspend-user': {
     /**
      * admin/suspend-user
@@ -586,6 +616,15 @@ export type paths = {
      */
     post: operations['admin/suspend-user'];
   };
+  '/admin/approve-user': {
+    /**
+     * admin/approve-user
+     * @description No description provided.
+     *
+     * **Credential required**: *Yes* / **Permission**: *write:admin:approve-user*
+     */
+    post: operations['admin/approve-user'];
+  };
   '/admin/unsuspend-user': {
     /**
      * admin/unsuspend-user
@@ -1927,6 +1966,16 @@ export type paths = {
      */
     post: operations['i/delete-account'];
   };
+  '/i/export-data': {
+    /**
+     * i/export-data
+     * @description No description provided.
+     *
+     * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
+     * **Credential required**: *Yes*
+     */
+    post: operations['i/export-data'];
+  };
   '/i/export-blocking': {
     /**
      * i/export-blocking
@@ -1967,6 +2016,16 @@ export type paths = {
      */
     post: operations['i/export-notes'];
   };
+  '/i/export-clips': {
+    /**
+     * i/export-clips
+     * @description No description provided.
+     *
+     * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
+     * **Credential required**: *Yes*
+     */
+    post: operations['i/export-clips'];
+  };
   '/i/export-favorites': {
     /**
      * i/export-favorites
@@ -2044,6 +2103,16 @@ export type paths = {
      */
     post: operations['i/import-following'];
   };
+  '/i/import-notes': {
+    /**
+     * i/import-notes
+     * @description No description provided.
+     *
+     * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
+     * **Credential required**: *Yes*
+     */
+    post: operations['i/import-notes'];
+  };
   '/i/import-muting': {
     /**
      * i/import-muting
@@ -2156,6 +2225,15 @@ export type paths = {
      */
     post: operations['i/registry/get-all'];
   };
+  '/i/registry/get-unsecure': {
+    /**
+     * i/registry/get-unsecure
+     * @description No description provided.
+     *
+     * **Credential required**: *Yes* / **Permission**: *read:account*
+     */
+    post: operations['i/registry/get-unsecure'];
+  };
   '/i/registry/get-detail': {
     /**
      * i/registry/get-detail
@@ -2570,6 +2648,15 @@ export type paths = {
      */
     post: operations['notes/global-timeline'];
   };
+  '/notes/bubble-timeline': {
+    /**
+     * notes/bubble-timeline
+     * @description No description provided.
+     *
+     * **Credential required**: *No*
+     */
+    post: operations['notes/bubble-timeline'];
+  };
   '/notes/hybrid-timeline': {
     /**
      * notes/hybrid-timeline
@@ -2649,6 +2736,15 @@ export type paths = {
      */
     post: operations['notes/reactions/delete'];
   };
+  '/notes/like': {
+    /**
+     * notes/like
+     * @description No description provided.
+     *
+     * **Credential required**: *Yes* / **Permission**: *write:reactions*
+     */
+    post: operations['notes/like'];
+  };
   '/notes/renotes': {
     /**
      * notes/renotes
@@ -2757,6 +2853,24 @@ export type paths = {
      */
     post: operations['notes/user-list-timeline'];
   };
+  '/notes/edit': {
+    /**
+     * notes/edit
+     * @description No description provided.
+     *
+     * **Credential required**: *Yes* / **Permission**: *write:notes*
+     */
+    post: operations['notes/edit'];
+  };
+  '/notes/versions': {
+    /**
+     * notes/versions
+     * @description No description provided.
+     *
+     * **Credential required**: *No*
+     */
+    post: operations['notes/versions'];
+  };
   '/notifications/create': {
     /**
      * notifications/create
@@ -2766,6 +2880,15 @@ export type paths = {
      */
     post: operations['notifications/create'];
   };
+  '/notifications/flush': {
+    /**
+     * notifications/flush
+     * @description No description provided.
+     *
+     * **Credential required**: *Yes* / **Permission**: *write:notifications*
+     */
+    post: operations['notifications/flush'];
+  };
   '/notifications/mark-all-as-read': {
     /**
      * notifications/mark-all-as-read
@@ -3438,6 +3561,103 @@ export type paths = {
      */
     post: operations['retention'];
   };
+  '/sponsors': {
+    /**
+     * sponsors
+     * @description Get Sharkey GH Sponsors
+     *
+     * **Credential required**: *No*
+     */
+    post: operations['sponsors'];
+  };
+  '/bubble-game/register': {
+    /**
+     * bubble-game/register
+     * @description No description provided.
+     *
+     * **Credential required**: *Yes* / **Permission**: *write:account*
+     */
+    post: operations['bubble-game/register'];
+  };
+  '/bubble-game/ranking': {
+    /**
+     * bubble-game/ranking
+     * @description No description provided.
+     *
+     * **Credential required**: *No*
+     */
+    get: operations['bubble-game/ranking'];
+    /**
+     * bubble-game/ranking
+     * @description No description provided.
+     *
+     * **Credential required**: *No*
+     */
+    post: operations['bubble-game/ranking'];
+  };
+  '/reversi/cancel-match': {
+    /**
+     * reversi/cancel-match
+     * @description No description provided.
+     *
+     * **Credential required**: *Yes* / **Permission**: *write:account*
+     */
+    post: operations['reversi/cancel-match'];
+  };
+  '/reversi/games': {
+    /**
+     * reversi/games
+     * @description No description provided.
+     *
+     * **Credential required**: *No*
+     */
+    post: operations['reversi/games'];
+  };
+  '/reversi/match': {
+    /**
+     * reversi/match
+     * @description No description provided.
+     *
+     * **Credential required**: *Yes* / **Permission**: *write:account*
+     */
+    post: operations['reversi/match'];
+  };
+  '/reversi/invitations': {
+    /**
+     * reversi/invitations
+     * @description No description provided.
+     *
+     * **Credential required**: *Yes* / **Permission**: *read:account*
+     */
+    post: operations['reversi/invitations'];
+  };
+  '/reversi/show-game': {
+    /**
+     * reversi/show-game
+     * @description No description provided.
+     *
+     * **Credential required**: *No*
+     */
+    post: operations['reversi/show-game'];
+  };
+  '/reversi/surrender': {
+    /**
+     * reversi/surrender
+     * @description No description provided.
+     *
+     * **Credential required**: *Yes* / **Permission**: *write:account*
+     */
+    post: operations['reversi/surrender'];
+  };
+  '/reversi/verify': {
+    /**
+     * reversi/verify
+     * @description No description provided.
+     *
+     * **Credential required**: *No*
+     */
+    post: operations['reversi/verify'];
+  };
 };
 
 export type webhooks = Record<string, never>;
@@ -3486,8 +3706,15 @@ export type components = {
           offsetX?: number;
           offsetY?: number;
         }[];
+      /** @default false */
+      isAdmin?: boolean;
+      /** @default false */
+      isModerator?: boolean;
+      isSilenced: boolean;
+      noindex: boolean;
       isBot?: boolean;
       isCat?: boolean;
+      speakAsCat?: boolean;
       instance?: {
         name: string | null;
         softwareName: string | null;
@@ -3496,7 +3723,9 @@ export type components = {
         faviconUrl: string | null;
         themeColor: string | null;
       };
-      emojis: Record<string, never>;
+      emojis: {
+        [key: string]: string;
+      };
       /** @enum {string} */
       onlineStatus: 'unknown' | 'online' | 'active' | 'offline';
       badgeRoles?: ({
@@ -3522,8 +3751,10 @@ export type components = {
       /** Format: url */
       bannerUrl: string | null;
       bannerBlurhash: string | null;
+      /** Format: url */
+      backgroundUrl: string | null;
+      backgroundBlurhash: string | null;
       isLocked: boolean;
-      isSilenced: boolean;
       /** @example false */
       isSuspended: boolean;
       /** @example Hi masters, I am Ai! */
@@ -3531,6 +3762,8 @@ export type components = {
       location: string | null;
       /** @example 2018-03-12 */
       birthday: string | null;
+      /** @example Steve */
+      ListenBrainz: string | null;
       /** @example ja-JP */
       lang: string | null;
       fields: {
@@ -3576,6 +3809,8 @@ export type components = {
       avatarId: string | null;
       /** Format: id */
       bannerId: string | null;
+      /** Format: id */
+      backgroundId: string | null;
       isModerator: boolean | null;
       isAdmin: boolean | null;
       injectFeaturedNote: boolean;
@@ -3604,42 +3839,132 @@ export type components = {
       hardMutedWords: string[][];
       mutedInstances: string[] | null;
       notificationRecieveConfig: {
-        app?: {
+        note?: OneOf<[{
           /** @enum {string} */
-          type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'list' | 'never';
-        };
-        quote?: {
+          type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+        }, {
           /** @enum {string} */
-          type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'list' | 'never';
-        };
-        reply?: {
+          type: 'list';
+          /** Format: misskey:id */
+          userListId: string;
+        }]>;
+        follow?: OneOf<[{
           /** @enum {string} */
-          type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'list' | 'never';
-        };
-        follow?: {
+          type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+        }, {
           /** @enum {string} */
-          type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'list' | 'never';
-        };
-        renote?: {
+          type: 'list';
+          /** Format: misskey:id */
+          userListId: string;
+        }]>;
+        mention?: OneOf<[{
           /** @enum {string} */
-          type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'list' | 'never';
-        };
-        mention?: {
+          type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+        }, {
           /** @enum {string} */
-          type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'list' | 'never';
-        };
-        reaction?: {
+          type: 'list';
+          /** Format: misskey:id */
+          userListId: string;
+        }]>;
+        reply?: OneOf<[{
           /** @enum {string} */
-          type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'list' | 'never';
-        };
-        pollEnded?: {
+          type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+        }, {
           /** @enum {string} */
-          type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'list' | 'never';
-        };
-        receiveFollowRequest?: {
+          type: 'list';
+          /** Format: misskey:id */
+          userListId: string;
+        }]>;
+        renote?: OneOf<[{
           /** @enum {string} */
-          type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'list' | 'never';
-        };
+          type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+        }, {
+          /** @enum {string} */
+          type: 'list';
+          /** Format: misskey:id */
+          userListId: string;
+        }]>;
+        quote?: OneOf<[{
+          /** @enum {string} */
+          type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+        }, {
+          /** @enum {string} */
+          type: 'list';
+          /** Format: misskey:id */
+          userListId: string;
+        }]>;
+        reaction?: OneOf<[{
+          /** @enum {string} */
+          type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+        }, {
+          /** @enum {string} */
+          type: 'list';
+          /** Format: misskey:id */
+          userListId: string;
+        }]>;
+        pollEnded?: OneOf<[{
+          /** @enum {string} */
+          type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+        }, {
+          /** @enum {string} */
+          type: 'list';
+          /** Format: misskey:id */
+          userListId: string;
+        }]>;
+        receiveFollowRequest?: OneOf<[{
+          /** @enum {string} */
+          type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+        }, {
+          /** @enum {string} */
+          type: 'list';
+          /** Format: misskey:id */
+          userListId: string;
+        }]>;
+        followRequestAccepted?: OneOf<[{
+          /** @enum {string} */
+          type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+        }, {
+          /** @enum {string} */
+          type: 'list';
+          /** Format: misskey:id */
+          userListId: string;
+        }]>;
+        roleAssigned?: OneOf<[{
+          /** @enum {string} */
+          type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+        }, {
+          /** @enum {string} */
+          type: 'list';
+          /** Format: misskey:id */
+          userListId: string;
+        }]>;
+        achievementEarned?: OneOf<[{
+          /** @enum {string} */
+          type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+        }, {
+          /** @enum {string} */
+          type: 'list';
+          /** Format: misskey:id */
+          userListId: string;
+        }]>;
+        app?: OneOf<[{
+          /** @enum {string} */
+          type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+        }, {
+          /** @enum {string} */
+          type: 'list';
+          /** Format: misskey:id */
+          userListId: string;
+        }]>;
+        test?: OneOf<[{
+          /** @enum {string} */
+          type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+        }, {
+          /** @enum {string} */
+          type: 'list';
+          /** Format: misskey:id */
+          userListId: string;
+        }]>;
       };
       emailNotificationTypes: string[];
       achievements: {
@@ -3647,32 +3972,7 @@ export type components = {
           unlockedAt: number;
         }[];
       loggedInDays: number;
-      policies: {
-        gtlAvailable: boolean;
-        ltlAvailable: boolean;
-        canPublicNote: boolean;
-        canInvite: boolean;
-        inviteLimit: number;
-        inviteLimitCycle: number;
-        inviteExpirationTime: number;
-        canManageCustomEmojis: boolean;
-        canManageAvatarDecorations: boolean;
-        canSearchNotes: boolean;
-        canUseTranslator: boolean;
-        canHideAds: boolean;
-        driveCapacityMb: number;
-        alwaysMarkNsfw: boolean;
-        pinLimit: number;
-        antennaLimit: number;
-        wordMuteLimit: number;
-        webhookLimit: number;
-        clipLimit: number;
-        noteEachClipsLimit: number;
-        userListLimit: number;
-        userEachUserListsLimit: number;
-        rateLimitFactor: number;
-        avatarDecorationLimit: number;
-      };
+      policies: components['schemas']['RolePolicies'];
       email?: string | null;
       emailVerified?: boolean | null;
       securityKeysList?: {
@@ -3689,7 +3989,7 @@ export type components = {
     UserDetailedNotMe: components['schemas']['UserLite'] & components['schemas']['UserDetailedNotMeOnly'];
     MeDetailed: components['schemas']['UserLite'] & components['schemas']['UserDetailedNotMeOnly'] & components['schemas']['MeDetailedOnly'];
     UserDetailed: components['schemas']['UserDetailedNotMe'] | components['schemas']['MeDetailed'];
-    User: components['schemas']['UserLite'] | components['schemas']['UserDetailed'] | components['schemas']['UserDetailedNotMe'] | components['schemas']['MeDetailed'];
+    User: components['schemas']['UserLite'] | components['schemas']['UserDetailed'];
     UserList: {
       /**
        * Format: id
@@ -3733,8 +4033,10 @@ export type components = {
       text: string;
       title: string;
       imageUrl: string | null;
-      icon: string;
-      display: string;
+      /** @enum {string} */
+      icon: 'info' | 'warning' | 'error' | 'success';
+      /** @enum {string} */
+      display: 'dialog' | 'normal' | 'banner';
       needConfirmationToRead: boolean;
       silence: boolean;
       forYou: boolean;
@@ -3776,35 +4078,54 @@ export type components = {
       reply?: components['schemas']['Note'] | null;
       renote?: components['schemas']['Note'] | null;
       isHidden?: boolean;
-      visibility: string;
+      /** @enum {string} */
+      visibility: 'public' | 'home' | 'followers' | 'specified';
       mentions?: string[];
       visibleUserIds?: string[];
       fileIds?: string[];
       files?: components['schemas']['DriveFile'][];
       tags?: string[];
-      poll?: Record<string, unknown> | null;
+      poll?: ({
+        /** Format: date-time */
+        expiresAt?: string | null;
+        multiple: boolean;
+        choices: {
+            isVoted: boolean;
+            text: string;
+            votes: number;
+          }[];
+      }) | null;
+      emojis?: {
+        [key: string]: string;
+      };
       /**
        * Format: id
        * @example xxxxxxxxxx
        */
       channelId?: string | null;
-      channel?: {
+      channel?: ({
         id: string;
         name: string;
         color: string;
         isSensitive: boolean;
         allowRenoteToExternal: boolean;
-      } | null;
+        userId: string | null;
+      }) | null;
       localOnly?: boolean;
       reactionAcceptance: string | null;
-      reactions: Record<string, never>;
+      reactionEmojis: {
+        [key: string]: string;
+      };
+      reactions: {
+        [key: string]: number;
+      };
       renoteCount: number;
       repliesCount: number;
       uri?: string;
       url?: string;
       reactionAndUserPairCache?: string[];
       clippedCount?: number;
-      myReaction?: Record<string, unknown> | null;
+      myReaction?: string | null;
     };
     NoteReaction: {
       /**
@@ -3835,21 +4156,173 @@ export type components = {
       /** Format: date-time */
       createdAt: string;
       /** @enum {string} */
-      type: 'note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped';
-      user?: components['schemas']['UserLite'] | null;
+      type: 'note';
+      user: components['schemas']['UserLite'];
       /** Format: id */
-      userId?: string | null;
-      note?: components['schemas']['Note'] | null;
-      reaction?: string | null;
-      achievement?: string;
-      body?: string | null;
-      header?: string | null;
-      icon?: string | null;
-      reactions?: {
+      userId: string;
+      note: components['schemas']['Note'];
+    } | {
+      /** Format: id */
+      id: string;
+      /** Format: date-time */
+      createdAt: string;
+      /** @enum {string} */
+      type: 'mention';
+      user: components['schemas']['UserLite'];
+      /** Format: id */
+      userId: string;
+      note: components['schemas']['Note'];
+    } | {
+      /** Format: id */
+      id: string;
+      /** Format: date-time */
+      createdAt: string;
+      /** @enum {string} */
+      type: 'reply';
+      user: components['schemas']['UserLite'];
+      /** Format: id */
+      userId: string;
+      note: components['schemas']['Note'];
+    } | {
+      /** Format: id */
+      id: string;
+      /** Format: date-time */
+      createdAt: string;
+      /** @enum {string} */
+      type: 'renote';
+      user: components['schemas']['UserLite'];
+      /** Format: id */
+      userId: string;
+      note: components['schemas']['Note'];
+    } | {
+      /** Format: id */
+      id: string;
+      /** Format: date-time */
+      createdAt: string;
+      /** @enum {string} */
+      type: 'quote';
+      user: components['schemas']['UserLite'];
+      /** Format: id */
+      userId: string;
+      note: components['schemas']['Note'];
+    } | {
+      /** Format: id */
+      id: string;
+      /** Format: date-time */
+      createdAt: string;
+      /** @enum {string} */
+      type: 'reaction';
+      user: components['schemas']['UserLite'];
+      /** Format: id */
+      userId: string;
+      note: components['schemas']['Note'];
+      reaction: string;
+    } | {
+      /** Format: id */
+      id: string;
+      /** Format: date-time */
+      createdAt: string;
+      /** @enum {string} */
+      type: 'pollEnded';
+      user: components['schemas']['UserLite'];
+      /** Format: id */
+      userId: string;
+      note: components['schemas']['Note'];
+    } | {
+      /** Format: id */
+      id: string;
+      /** Format: date-time */
+      createdAt: string;
+      /** @enum {string} */
+      type: 'follow';
+      user: components['schemas']['UserLite'];
+      /** Format: id */
+      userId: string;
+    } | {
+      /** Format: id */
+      id: string;
+      /** Format: date-time */
+      createdAt: string;
+      /** @enum {string} */
+      type: 'receiveFollowRequest';
+      user: components['schemas']['UserLite'];
+      /** Format: id */
+      userId: string;
+    } | {
+      /** Format: id */
+      id: string;
+      /** Format: date-time */
+      createdAt: string;
+      /** @enum {string} */
+      type: 'followRequestAccepted';
+      user: components['schemas']['UserLite'];
+      /** Format: id */
+      userId: string;
+    } | {
+      /** Format: id */
+      id: string;
+      /** Format: date-time */
+      createdAt: string;
+      /** @enum {string} */
+      type: 'roleAssigned';
+      role: components['schemas']['Role'];
+    } | {
+      /** Format: id */
+      id: string;
+      /** Format: date-time */
+      createdAt: string;
+      /** @enum {string} */
+      type: 'achievementEarned';
+      achievement: string;
+    } | {
+      /** Format: id */
+      id: string;
+      /** Format: date-time */
+      createdAt: string;
+      /** @enum {string} */
+      type: 'app';
+      body: string;
+      header: string;
+      icon: string;
+    } | {
+      /** Format: id */
+      id: string;
+      /** Format: date-time */
+      createdAt: string;
+      /** @enum {string} */
+      type: 'edited';
+      user: components['schemas']['UserLite'];
+      /** Format: id */
+      userId: string;
+      note: components['schemas']['Note'];
+    } | {
+      /** Format: id */
+      id: string;
+      /** Format: date-time */
+      createdAt: string;
+      /** @enum {string} */
+      type: 'reaction:grouped';
+      note: components['schemas']['Note'];
+      reactions: {
           user: components['schemas']['UserLite'];
           reaction: string;
-        }[] | null;
-      users?: components['schemas']['UserLite'][] | null;
+        }[];
+    } | {
+      /** Format: id */
+      id: string;
+      /** Format: date-time */
+      createdAt: string;
+      /** @enum {string} */
+      type: 'renote:grouped';
+      note: components['schemas']['Note'];
+      users: components['schemas']['UserLite'][];
+    } | {
+      /** Format: id */
+      id: string;
+      /** Format: date-time */
+      createdAt: string;
+      /** @enum {string} */
+      type: 'test';
     };
     DriveFile: {
       /**
@@ -3930,8 +4403,8 @@ export type components = {
       followeeId: string;
       /** Format: id */
       followerId: string;
-      followee?: components['schemas']['UserDetailed'];
-      follower?: components['schemas']['UserDetailed'];
+      followee?: components['schemas']['UserDetailedNotMe'];
+      follower?: components['schemas']['UserDetailedNotMe'];
     };
     Muting: {
       /**
@@ -3945,7 +4418,7 @@ export type components = {
       expiresAt: string | null;
       /** Format: id */
       muteeId: string;
-      mutee: components['schemas']['UserDetailed'];
+      mutee: components['schemas']['UserDetailedNotMe'];
     };
     RenoteMuting: {
       /**
@@ -3957,7 +4430,7 @@ export type components = {
       createdAt: string;
       /** Format: id */
       muteeId: string;
-      mutee: components['schemas']['UserDetailed'];
+      mutee: components['schemas']['UserDetailedNotMe'];
     };
     Blocking: {
       /**
@@ -3969,7 +4442,7 @@ export type components = {
       createdAt: string;
       /** Format: id */
       blockeeId: string;
-      blockee: components['schemas']['UserDetailed'];
+      blockee: components['schemas']['UserDetailedNotMe'];
     };
     Hashtag: {
       /** @example misskey */
@@ -4012,7 +4485,7 @@ export type components = {
       /** Format: id */
       userId: string;
       user: components['schemas']['UserLite'];
-      content: Record<string, never>[];
+      content: components['schemas']['PageBlock'][];
       variables: Record<string, never>[];
       title: string;
       name: string;
@@ -4027,6 +4500,29 @@ export type components = {
       likedCount: number;
       isLiked?: boolean;
     };
+    PageBlock: OneOf<[{
+      id: string;
+      /** @enum {string} */
+      type: 'text';
+      text: string;
+    }, {
+      id: string;
+      /** @enum {string} */
+      type: 'section';
+      title: string;
+      children: components['schemas']['PageBlock'][];
+    }, {
+      id: string;
+      /** @enum {string} */
+      type: 'image';
+      fileId: string | null;
+    }, {
+      id: string;
+      /** @enum {string} */
+      type: 'note';
+      detailed: boolean;
+      note: string | null;
+    }]>;
     Channel: {
       /**
        * Format: id
@@ -4138,6 +4634,8 @@ export type components = {
       infoUpdatedAt: string | null;
       /** Format: date-time */
       latestRequestReceivedAt: string | null;
+      isNSFW: boolean;
+      moderationNote?: string | null;
     };
     GalleryPost: {
       /**
@@ -4166,6 +4664,7 @@ export type components = {
       name: string;
       category: string | null;
       url: string;
+      localOnly?: boolean;
       isSensitive?: boolean;
       roleIdsThatCanBeUsedThisEmojiAsReaction?: string[];
     };
@@ -4210,6 +4709,46 @@ export type components = {
       headers: Record<string, never>;
       success: boolean;
     };
+    RoleCondFormulaLogics: {
+      id: string;
+      /** @enum {string} */
+      type: 'and' | 'or';
+      values: components['schemas']['RoleCondFormulaValue'][];
+    };
+    RoleCondFormulaValueNot: {
+      id: string;
+      /** @enum {string} */
+      type: 'not';
+      value: components['schemas']['RoleCondFormulaValue'];
+    };
+    RoleCondFormulaValueIsLocalOrRemote: {
+      id: string;
+      /** @enum {string} */
+      type: 'isLocal' | 'isRemote';
+    };
+    RoleCondFormulaValueAssignedRole: {
+      id: string;
+      /** @enum {string} */
+      type: 'roleAssignedTo';
+      /**
+       * Format: id
+       * @example xxxxxxxxxx
+       */
+      roleId: string;
+    };
+    RoleCondFormulaValueCreated: {
+      id: string;
+      /** @enum {string} */
+      type: 'createdLessThan' | 'createdMoreThan';
+      sec: number;
+    };
+    RoleCondFormulaFollowersOrFollowingOrNotes: {
+      id: string;
+      /** @enum {string} */
+      type: 'followersLessThanOrEq' | 'followersMoreThanOrEq' | 'followingLessThanOrEq' | 'followingMoreThanOrEq' | 'notesLessThanOrEq' | 'notesMoreThanOrEq';
+      value: number;
+    };
+    RoleCondFormulaValue: components['schemas']['RoleCondFormulaLogics'] | components['schemas']['RoleCondFormulaValueNot'] | components['schemas']['RoleCondFormulaValueIsLocalOrRemote'] | components['schemas']['RoleCondFormulaValueAssignedRole'] | components['schemas']['RoleCondFormulaValueCreated'] | components['schemas']['RoleCondFormulaFollowersOrFollowingOrNotes'];
     RoleLite: {
       /**
        * Format: id
@@ -4236,7 +4775,7 @@ export type components = {
       updatedAt: string;
       /** @enum {string} */
       target: 'manual' | 'conditional';
-      condFormula: Record<string, never>;
+      condFormula: components['schemas']['RoleCondFormulaValue'];
       /** @example false */
       isPublic: boolean;
       /** @example false */
@@ -4246,129 +4785,206 @@ export type components = {
       /** @example false */
       canEditMembersByModerator: boolean;
       policies: {
-        pinLimit: {
-          value: number | boolean;
-          priority: number;
-          useDefault: boolean;
-        };
-        canInvite: {
-          value: number | boolean;
-          priority: number;
-          useDefault: boolean;
-        };
-        clipLimit: {
-          value: number | boolean;
-          priority: number;
-          useDefault: boolean;
-        };
-        canHideAds: {
-          value: number | boolean;
-          priority: number;
-          useDefault: boolean;
-        };
-        inviteLimit: {
-          value: number | boolean;
-          priority: number;
-          useDefault: boolean;
-        };
-        antennaLimit: {
-          value: number | boolean;
-          priority: number;
-          useDefault: boolean;
-        };
-        gtlAvailable: {
-          value: number | boolean;
-          priority: number;
-          useDefault: boolean;
-        };
-        ltlAvailable: {
-          value: number | boolean;
-          priority: number;
-          useDefault: boolean;
-        };
-        webhookLimit: {
-          value: number | boolean;
-          priority: number;
-          useDefault: boolean;
-        };
-        canPublicNote: {
-          value: number | boolean;
-          priority: number;
-          useDefault: boolean;
-        };
-        userListLimit: {
-          value: number | boolean;
-          priority: number;
-          useDefault: boolean;
-        };
-        wordMuteLimit: {
-          value: number | boolean;
-          priority: number;
-          useDefault: boolean;
-        };
-        alwaysMarkNsfw: {
-          value: number | boolean;
-          priority: number;
-          useDefault: boolean;
-        };
-        canSearchNotes: {
-          value: number | boolean;
-          priority: number;
-          useDefault: boolean;
-        };
-        driveCapacityMb: {
-          value: number | boolean;
-          priority: number;
-          useDefault: boolean;
-        };
-        rateLimitFactor: {
-          value: number | boolean;
-          priority: number;
-          useDefault: boolean;
-        };
-        inviteLimitCycle: {
-          value: number | boolean;
-          priority: number;
-          useDefault: boolean;
-        };
-        noteEachClipsLimit: {
-          value: number | boolean;
-          priority: number;
-          useDefault: boolean;
-        };
-        inviteExpirationTime: {
-          value: number | boolean;
-          priority: number;
-          useDefault: boolean;
-        };
-        canManageCustomEmojis: {
-          value: number | boolean;
-          priority: number;
-          useDefault: boolean;
-        };
-        userEachUserListsLimit: {
-          value: number | boolean;
-          priority: number;
-          useDefault: boolean;
-        };
-        canManageAvatarDecorations: {
-          value: number | boolean;
-          priority: number;
-          useDefault: boolean;
-        };
-        canUseTranslator: {
-          value: number | boolean;
-          priority: number;
-          useDefault: boolean;
-        };
-        avatarDecorationLimit: {
-          value: number | boolean;
-          priority: number;
-          useDefault: boolean;
+        [key: string]: {
+          value?: number | boolean;
+          priority?: number;
+          useDefault?: boolean;
         };
       };
       usersCount: number;
     });
+    RolePolicies: {
+      gtlAvailable: boolean;
+      ltlAvailable: boolean;
+      btlAvailable: boolean;
+      canPublicNote: boolean;
+      mentionLimit: number;
+      canInvite: boolean;
+      inviteLimit: number;
+      inviteLimitCycle: number;
+      inviteExpirationTime: number;
+      canManageCustomEmojis: boolean;
+      canManageAvatarDecorations: boolean;
+      canSearchNotes: boolean;
+      canUseTranslator: boolean;
+      canHideAds: boolean;
+      driveCapacityMb: number;
+      alwaysMarkNsfw: boolean;
+      pinLimit: number;
+      antennaLimit: number;
+      wordMuteLimit: number;
+      webhookLimit: number;
+      clipLimit: number;
+      noteEachClipsLimit: number;
+      userListLimit: number;
+      userEachUserListsLimit: number;
+      rateLimitFactor: number;
+      avatarDecorationLimit: number;
+    };
+    ReversiGameLite: {
+      /** Format: id */
+      id: string;
+      /** Format: date-time */
+      createdAt: string;
+      /** Format: date-time */
+      startedAt: string | null;
+      /** Format: date-time */
+      endedAt: string | null;
+      isStarted: boolean;
+      isEnded: boolean;
+      /** Format: id */
+      user1Id: string;
+      /** Format: id */
+      user2Id: string;
+      user1: components['schemas']['UserLite'];
+      user2: components['schemas']['UserLite'];
+      /** Format: id */
+      winnerId: string | null;
+      winner: components['schemas']['UserLite'] | null;
+      /** Format: id */
+      surrenderedUserId: string | null;
+      /** Format: id */
+      timeoutUserId: string | null;
+      black: number | null;
+      bw: string;
+      noIrregularRules: boolean;
+      isLlotheo: boolean;
+      canPutEverywhere: boolean;
+      loopedBoard: boolean;
+      timeLimitForEachTurn: number;
+    };
+    ReversiGameDetailed: {
+      /** Format: id */
+      id: string;
+      /** Format: date-time */
+      createdAt: string;
+      /** Format: date-time */
+      startedAt: string | null;
+      /** Format: date-time */
+      endedAt: string | null;
+      isStarted: boolean;
+      isEnded: boolean;
+      form1: Record<string, never> | null;
+      form2: Record<string, never> | null;
+      user1Ready: boolean;
+      user2Ready: boolean;
+      /** Format: id */
+      user1Id: string;
+      /** Format: id */
+      user2Id: string;
+      user1: components['schemas']['UserLite'];
+      user2: components['schemas']['UserLite'];
+      /** Format: id */
+      winnerId: string | null;
+      winner: components['schemas']['UserLite'] | null;
+      /** Format: id */
+      surrenderedUserId: string | null;
+      /** Format: id */
+      timeoutUserId: string | null;
+      black: number | null;
+      bw: string;
+      noIrregularRules: boolean;
+      isLlotheo: boolean;
+      canPutEverywhere: boolean;
+      loopedBoard: boolean;
+      timeLimitForEachTurn: number;
+      logs: number[][];
+      map: string[];
+    };
+    MetaLite: {
+      maintainerName: string | null;
+      maintainerEmail: string | null;
+      version: string;
+      providesTarball: boolean;
+      name: string | null;
+      shortName: string | null;
+      /**
+       * Format: url
+       * @example https://misskey.example.com
+       */
+      uri: string;
+      description: string | null;
+      langs: string[];
+      tosUrl: string | null;
+      /** @default https://github.com/misskey-dev/misskey */
+      repositoryUrl: string | null;
+      /** @default https://github.com/misskey-dev/misskey/issues/new */
+      feedbackUrl: string | null;
+      donationUrl: string | null;
+      defaultDarkTheme: string | null;
+      defaultLightTheme: string | null;
+      defaultLike: string | null;
+      disableRegistration: boolean;
+      emailRequiredForSignup: boolean;
+      approvalRequiredForSignup: boolean;
+      enableHcaptcha: boolean;
+      hcaptchaSiteKey: string | null;
+      enableMcaptcha: boolean;
+      mcaptchaSiteKey: string | null;
+      mcaptchaInstanceUrl: string | null;
+      enableRecaptcha: boolean;
+      recaptchaSiteKey: string | null;
+      enableTurnstile: boolean;
+      turnstileSiteKey: string | null;
+      enableAchievements: boolean | null;
+      swPublickey: string | null;
+      /** @default /assets/ai.png */
+      mascotImageUrl: string;
+      bannerUrl: string | null;
+      serverErrorImageUrl: string | null;
+      infoImageUrl: string | null;
+      notFoundImageUrl: string | null;
+      iconUrl: string | null;
+      maxNoteTextLength: number;
+      ads: {
+          /**
+           * Format: id
+           * @example xxxxxxxxxx
+           */
+          id: string;
+          /** Format: url */
+          url: string;
+          place: string;
+          ratio: number;
+          /** Format: url */
+          imageUrl: string;
+          dayOfWeek: number;
+        }[];
+      /** @default 0 */
+      notesPerOneAd: number;
+      enableEmail: boolean;
+      enableServiceWorker: boolean;
+      translatorAvailable: boolean;
+      mediaProxy: string;
+      backgroundImageUrl: string | null;
+      impressumUrl: string | null;
+      logoImageUrl: string | null;
+      privacyPolicyUrl: string | null;
+      serverRules: string[];
+      themeColor: string | null;
+      policies: components['schemas']['RolePolicies'];
+    };
+    MetaDetailedOnly: {
+      features?: {
+        registration: boolean;
+        emailRequiredForSignup: boolean;
+        localTimeline: boolean;
+        globalTimeline: boolean;
+        hcaptcha: boolean;
+        turnstile: boolean;
+        recaptcha: boolean;
+        objectStorage: boolean;
+        serviceWorker: boolean;
+        /** @default true */
+        miauth?: boolean;
+      };
+      proxyAccountName: string | null;
+      /** @example false */
+      requireSetup: boolean;
+      cacheRemoteFiles: boolean;
+      cacheRemoteSensitiveFiles: boolean;
+    };
+    MetaDetailed: components['schemas']['MetaLite'] & components['schemas']['MetaDetailedOnly'];
   };
   responses: never;
   parameters: never;
@@ -4398,8 +5014,12 @@ export type operations = {
             cacheRemoteFiles: boolean;
             cacheRemoteSensitiveFiles: boolean;
             emailRequiredForSignup: boolean;
+            approvalRequiredForSignup: boolean;
             enableHcaptcha: boolean;
             hcaptchaSiteKey: string | null;
+            enableMcaptcha: boolean;
+            mcaptchaSiteKey: string | null;
+            mcaptchaInstanceUrl: string | null;
             enableRecaptcha: boolean;
             recaptchaSiteKey: string | null;
             enableTurnstile: boolean;
@@ -4422,15 +5042,19 @@ export type operations = {
             hiddenTags: string[];
             blockedHosts: string[];
             sensitiveWords: string[];
+            prohibitedWords: string[];
             bannedEmailDomains?: string[];
             preservedUsernames: string[];
+            bubbleInstances: string[];
             hcaptchaSecretKey: string | null;
+            mcaptchaSecretKey: string | null;
             recaptchaSecretKey: string | null;
             turnstileSecretKey: string | null;
             sensitiveMediaDetection: string;
             sensitiveMediaDetectionSensitivity: string;
             setSensitiveFlagAutomatically: boolean;
             enableSensitiveMediaDetectionForVideos: boolean;
+            enableBotTrending: boolean;
             /** Format: id */
             proxyAccountId: string | null;
             email: string | null;
@@ -4456,9 +5080,13 @@ export type operations = {
             enableActiveEmailValidation: boolean;
             enableVerifymailApi: boolean;
             verifymailAuthKey: string | null;
+            enableTruemailApi: boolean;
+            truemailInstance: string | null;
+            truemailAuthKey: string | null;
             enableChartsForRemoteUser: boolean;
             enableChartsForFederatedInstances: boolean;
             enableServerMachineStats: boolean;
+            enableAchievements: boolean;
             enableIdenticonGeneration: boolean;
             manifestJsonOverride: string;
             policies: Record<string, never>;
@@ -4472,18 +5100,21 @@ export type operations = {
             backgroundImageUrl: string | null;
             deeplAuthKey: string | null;
             deeplIsPro: boolean;
+            deeplFreeMode: boolean;
+            deeplFreeInstance: string | null;
             defaultDarkTheme: string | null;
             defaultLightTheme: string | null;
             description: string | null;
             disableRegistration: boolean;
             impressumUrl: string | null;
+            donationUrl: string | null;
             maintainerEmail: string | null;
             maintainerName: string | null;
             name: string | null;
             shortName: string | null;
             objectStorageS3ForcePathStyle: boolean;
             privacyPolicyUrl: string | null;
-            repositoryUrl: string;
+            repositoryUrl: string | null;
             summalyProxy: string | null;
             themeColor: string | null;
             tosUrl: string | null;
@@ -4578,9 +5209,9 @@ export type operations = {
               targetUserId: string;
               /** Format: id */
               assigneeId: string | null;
-              reporter: components['schemas']['User'];
-              targetUser: components['schemas']['User'];
-              assignee?: components['schemas']['User'] | null;
+              reporter: components['schemas']['UserDetailedNotMe'];
+              targetUser: components['schemas']['UserDetailedNotMe'];
+              assignee?: components['schemas']['UserDetailedNotMe'] | null;
             })[];
         };
       };
@@ -4620,7 +5251,6 @@ export type operations = {
    * admin/accounts/create
    * @description No description provided.
    *
-   * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
    * **Credential required**: *No*
    */
   'admin/accounts/create': {
@@ -4636,7 +5266,7 @@ export type operations = {
       /** @description OK (with results) */
       200: {
         content: {
-          'application/json': components['schemas']['User'];
+          'application/json': components['schemas']['MeDetailed'];
         };
       };
       /** @description Client error */
@@ -4741,7 +5371,7 @@ export type operations = {
       /** @description OK (with results) */
       200: {
         content: {
-          'application/json': components['schemas']['User'];
+          'application/json': components['schemas']['UserDetailedNotMe'];
         };
       };
       /** @description Client error */
@@ -5887,7 +6517,12 @@ export type operations = {
             size: number;
             comment: string | null;
             blurhash: string | null;
-            properties: Record<string, never>;
+            properties: {
+              width?: number;
+              height?: number;
+              orientation?: number;
+              avgColor?: string;
+            };
             /** @example true */
             storedInternal: boolean | null;
             /** Format: url */
@@ -6019,9 +6654,11 @@ export type operations = {
       };
     };
     responses: {
-      /** @description OK (without any results) */
-      204: {
-        content: never;
+      /** @description OK (with results) */
+      200: {
+        content: {
+          'application/json': components['schemas']['EmojiDetailed'];
+        };
       };
       /** @description Client error */
       400: {
@@ -6632,13 +7269,13 @@ export type operations = {
       content: {
         'application/json': {
           /** Format: misskey:id */
-          id: string;
-          name: string;
+          id?: string;
+          name?: string;
           /** Format: misskey:id */
           fileId?: string;
           /** @description Use `null` to reset the category. */
           category?: string | null;
-          aliases: string[];
+          aliases?: string[];
           license?: string | null;
           isSensitive?: boolean;
           localOnly?: boolean;
@@ -6847,7 +7484,9 @@ export type operations = {
       content: {
         'application/json': {
           host: string;
-          isSuspended: boolean;
+          isSuspended?: boolean;
+          isNSFW?: boolean;
+          moderationNote?: string;
         };
       };
     };
@@ -6948,7 +7587,12 @@ export type operations = {
       /** @description OK (with results) */
       200: {
         content: {
-          'application/json': Record<string, never>;
+          'application/json': {
+            [key: string]: {
+              count: number;
+              size: number;
+            };
+          };
         };
       };
       /** @description Client error */
@@ -7889,7 +8533,7 @@ export type operations = {
               info: Record<string, never>;
               /** Format: id */
               userId: string;
-              user: components['schemas']['UserDetailed'];
+              user: components['schemas']['UserDetailedNotMe'];
             }[];
         };
       };
@@ -7944,7 +8588,162 @@ export type operations = {
       /** @description OK (with results) */
       200: {
         content: {
-          'application/json': Record<string, never>;
+          'application/json': {
+            email: string | null;
+            emailVerified: boolean;
+            autoAcceptFollowed: boolean;
+            noCrawle: boolean;
+            preventAiLearning: boolean;
+            alwaysMarkNsfw: boolean;
+            autoSensitive: boolean;
+            carefulBot: boolean;
+            injectFeaturedNote: boolean;
+            receiveAnnouncementEmail: boolean;
+            mutedWords: (string | string[])[];
+            mutedInstances: string[];
+            notificationRecieveConfig: {
+              note?: OneOf<[{
+                /** @enum {string} */
+                type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+              }, {
+                /** @enum {string} */
+                type: 'list';
+                /** Format: misskey:id */
+                userListId: string;
+              }]>;
+              follow?: OneOf<[{
+                /** @enum {string} */
+                type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+              }, {
+                /** @enum {string} */
+                type: 'list';
+                /** Format: misskey:id */
+                userListId: string;
+              }]>;
+              mention?: OneOf<[{
+                /** @enum {string} */
+                type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+              }, {
+                /** @enum {string} */
+                type: 'list';
+                /** Format: misskey:id */
+                userListId: string;
+              }]>;
+              reply?: OneOf<[{
+                /** @enum {string} */
+                type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+              }, {
+                /** @enum {string} */
+                type: 'list';
+                /** Format: misskey:id */
+                userListId: string;
+              }]>;
+              renote?: OneOf<[{
+                /** @enum {string} */
+                type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+              }, {
+                /** @enum {string} */
+                type: 'list';
+                /** Format: misskey:id */
+                userListId: string;
+              }]>;
+              quote?: OneOf<[{
+                /** @enum {string} */
+                type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+              }, {
+                /** @enum {string} */
+                type: 'list';
+                /** Format: misskey:id */
+                userListId: string;
+              }]>;
+              reaction?: OneOf<[{
+                /** @enum {string} */
+                type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+              }, {
+                /** @enum {string} */
+                type: 'list';
+                /** Format: misskey:id */
+                userListId: string;
+              }]>;
+              pollEnded?: OneOf<[{
+                /** @enum {string} */
+                type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+              }, {
+                /** @enum {string} */
+                type: 'list';
+                /** Format: misskey:id */
+                userListId: string;
+              }]>;
+              receiveFollowRequest?: OneOf<[{
+                /** @enum {string} */
+                type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+              }, {
+                /** @enum {string} */
+                type: 'list';
+                /** Format: misskey:id */
+                userListId: string;
+              }]>;
+              followRequestAccepted?: OneOf<[{
+                /** @enum {string} */
+                type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+              }, {
+                /** @enum {string} */
+                type: 'list';
+                /** Format: misskey:id */
+                userListId: string;
+              }]>;
+              roleAssigned?: OneOf<[{
+                /** @enum {string} */
+                type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+              }, {
+                /** @enum {string} */
+                type: 'list';
+                /** Format: misskey:id */
+                userListId: string;
+              }]>;
+              achievementEarned?: OneOf<[{
+                /** @enum {string} */
+                type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+              }, {
+                /** @enum {string} */
+                type: 'list';
+                /** Format: misskey:id */
+                userListId: string;
+              }]>;
+              app?: OneOf<[{
+                /** @enum {string} */
+                type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+              }, {
+                /** @enum {string} */
+                type: 'list';
+                /** Format: misskey:id */
+                userListId: string;
+              }]>;
+              test?: OneOf<[{
+                /** @enum {string} */
+                type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+              }, {
+                /** @enum {string} */
+                type: 'list';
+                /** Format: misskey:id */
+                userListId: string;
+              }]>;
+            };
+            isModerator: boolean;
+            isSilenced: boolean;
+            isSuspended: boolean;
+            isHibernated: boolean;
+            lastActiveDate: string | null;
+            moderationNote: string;
+            signins: components['schemas']['Signin'][];
+            policies: components['schemas']['RolePolicies'];
+            roles: components['schemas']['Role'][];
+            roleAssigns: ({
+                createdAt: string;
+                expiresAt: string | null;
+                roleId: string;
+              })[];
+          };
         };
       };
       /** @description Client error */
@@ -7999,7 +8798,7 @@ export type operations = {
            * @default all
            * @enum {string}
            */
-          state?: 'all' | 'alive' | 'available' | 'admin' | 'moderator' | 'adminOrModerator' | 'suspended';
+          state?: 'all' | 'alive' | 'available' | 'admin' | 'moderator' | 'adminOrModerator' | 'suspended' | 'approved';
           /**
            * @default combined
            * @enum {string}
@@ -8054,6 +8853,214 @@ export type operations = {
       };
     };
   };
+  /**
+   * admin/nsfw-user
+   * @description No description provided.
+   *
+   * **Credential required**: *Yes* / **Permission**: *write:admin:nsfw-user*
+   */
+  'admin/nsfw-user': {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** Format: misskey:id */
+          userId: string;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (without any results) */
+      204: {
+        content: never;
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
+  /**
+   * admin/unnsfw-user
+   * @description No description provided.
+   *
+   * **Credential required**: *Yes* / **Permission**: *write:admin:unnsfw-user*
+   */
+  'admin/unnsfw-user': {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** Format: misskey:id */
+          userId: string;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (without any results) */
+      204: {
+        content: never;
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
+  /**
+   * admin/silence-user
+   * @description No description provided.
+   *
+   * **Credential required**: *Yes* / **Permission**: *write:admin:silence-user*
+   */
+  'admin/silence-user': {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** Format: misskey:id */
+          userId: string;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (without any results) */
+      204: {
+        content: never;
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
+  /**
+   * admin/unsilence-user
+   * @description No description provided.
+   *
+   * **Credential required**: *Yes* / **Permission**: *write:admin:unsilence-user*
+   */
+  'admin/unsilence-user': {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** Format: misskey:id */
+          userId: string;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (without any results) */
+      204: {
+        content: never;
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
   /**
    * admin/suspend-user
    * @description No description provided.
@@ -8106,6 +9113,58 @@ export type operations = {
       };
     };
   };
+  /**
+   * admin/approve-user
+   * @description No description provided.
+   *
+   * **Credential required**: *Yes* / **Permission**: *write:admin:approve-user*
+   */
+  'admin/approve-user': {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** Format: misskey:id */
+          userId: string;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (without any results) */
+      204: {
+        content: never;
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
   /**
    * admin/unsuspend-user
    * @description No description provided.
@@ -8173,6 +9232,7 @@ export type operations = {
           hiddenTags?: string[] | null;
           blockedHosts?: string[] | null;
           sensitiveWords?: string[] | null;
+          prohibitedWords?: string[] | null;
           themeColor?: string | null;
           mascotImageUrl?: string | null;
           bannerUrl?: string | null;
@@ -8189,12 +9249,18 @@ export type operations = {
           description?: string | null;
           defaultLightTheme?: string | null;
           defaultDarkTheme?: string | null;
+          defaultLike?: string | null;
           cacheRemoteFiles?: boolean;
           cacheRemoteSensitiveFiles?: boolean;
           emailRequiredForSignup?: boolean;
+          approvalRequiredForSignup?: boolean;
           enableHcaptcha?: boolean;
           hcaptchaSiteKey?: string | null;
           hcaptchaSecretKey?: string | null;
+          enableMcaptcha?: boolean;
+          mcaptchaSiteKey?: string | null;
+          mcaptchaInstanceUrl?: string | null;
+          mcaptchaSecretKey?: string | null;
           enableRecaptcha?: boolean;
           recaptchaSiteKey?: string | null;
           recaptchaSecretKey?: string | null;
@@ -8207,6 +9273,7 @@ export type operations = {
           sensitiveMediaDetectionSensitivity?: 'medium' | 'low' | 'high' | 'veryLow' | 'veryHigh';
           setSensitiveFlagAutomatically?: boolean;
           enableSensitiveMediaDetectionForVideos?: boolean;
+          enableBotTrending?: boolean;
           /** Format: misskey:id */
           proxyAccountId?: string | null;
           maintainerName?: string | null;
@@ -8215,6 +9282,8 @@ export type operations = {
           summalyProxy?: string | null;
           deeplAuthKey?: string | null;
           deeplIsPro?: boolean;
+          deeplFreeMode?: boolean;
+          deeplFreeInstance?: string | null;
           enableEmail?: boolean;
           email?: string | null;
           smtpSecure?: boolean;
@@ -8226,9 +9295,10 @@ export type operations = {
           swPublicKey?: string | null;
           swPrivateKey?: string | null;
           tosUrl?: string | null;
-          repositoryUrl?: string;
-          feedbackUrl?: string;
+          repositoryUrl?: string | null;
+          feedbackUrl?: string | null;
           impressumUrl?: string | null;
+          donationUrl?: string | null;
           privacyPolicyUrl?: string | null;
           useObjectStorage?: boolean;
           objectStorageBaseUrl?: string | null;
@@ -8247,13 +9317,18 @@ export type operations = {
           enableActiveEmailValidation?: boolean;
           enableVerifymailApi?: boolean;
           verifymailAuthKey?: string | null;
+          enableTruemailApi?: boolean;
+          truemailInstance?: string | null;
+          truemailAuthKey?: string | null;
           enableChartsForRemoteUser?: boolean;
           enableChartsForFederatedInstances?: boolean;
           enableServerMachineStats?: boolean;
+          enableAchievements?: boolean;
           enableIdenticonGeneration?: boolean;
           serverRules?: string[];
           bannedEmailDomains?: string[];
           preservedUsernames?: string[];
+          bubbleInstances?: string[];
           manifestJsonOverride?: string;
           enableFanoutTimeline?: boolean;
           enableFanoutTimelineDbFallback?: boolean;
@@ -8319,11 +9394,9 @@ export type operations = {
       };
     };
     responses: {
-      /** @description OK (with results) */
-      200: {
-        content: {
-          'application/json': unknown;
-        };
+      /** @description OK (without any results) */
+      204: {
+        content: never;
       };
       /** @description Client error */
       400: {
@@ -10849,14 +11922,18 @@ export type operations = {
       200: {
         content: {
           'application/json': {
-            'local.incCount': number[];
-            'local.incSize': number[];
-            'local.decCount': number[];
-            'local.decSize': number[];
-            'remote.incCount': number[];
-            'remote.incSize': number[];
-            'remote.decCount': number[];
-            'remote.decSize': number[];
+            local: {
+              incCount: number[];
+              incSize: number[];
+              decCount: number[];
+              decSize: number[];
+            };
+            remote: {
+              incCount: number[];
+              incSize: number[];
+              decCount: number[];
+              decSize: number[];
+            };
           };
         };
       };
@@ -10984,30 +12061,44 @@ export type operations = {
       200: {
         content: {
           'application/json': {
-            'requests.failed': number[];
-            'requests.succeeded': number[];
-            'requests.received': number[];
-            'notes.total': number[];
-            'notes.inc': number[];
-            'notes.dec': number[];
-            'notes.diffs.normal': number[];
-            'notes.diffs.reply': number[];
-            'notes.diffs.renote': number[];
-            'notes.diffs.withFile': number[];
-            'users.total': number[];
-            'users.inc': number[];
-            'users.dec': number[];
-            'following.total': number[];
-            'following.inc': number[];
-            'following.dec': number[];
-            'followers.total': number[];
-            'followers.inc': number[];
-            'followers.dec': number[];
-            'drive.totalFiles': number[];
-            'drive.incFiles': number[];
-            'drive.decFiles': number[];
-            'drive.incUsage': number[];
-            'drive.decUsage': number[];
+            requests: {
+              failed: number[];
+              succeeded: number[];
+              received: number[];
+            };
+            notes: {
+              total: number[];
+              inc: number[];
+              dec: number[];
+              diffs: {
+                normal: number[];
+                reply: number[];
+                renote: number[];
+                withFile: number[];
+              };
+            };
+            users: {
+              total: number[];
+              inc: number[];
+              dec: number[];
+            };
+            following: {
+              total: number[];
+              inc: number[];
+              dec: number[];
+            };
+            followers: {
+              total: number[];
+              inc: number[];
+              dec: number[];
+            };
+            drive: {
+              totalFiles: number[];
+              incFiles: number[];
+              decFiles: number[];
+              incUsage: number[];
+              decUsage: number[];
+            };
           };
         };
       };
@@ -11067,20 +12158,28 @@ export type operations = {
       200: {
         content: {
           'application/json': {
-            'local.total': number[];
-            'local.inc': number[];
-            'local.dec': number[];
-            'local.diffs.normal': number[];
-            'local.diffs.reply': number[];
-            'local.diffs.renote': number[];
-            'local.diffs.withFile': number[];
-            'remote.total': number[];
-            'remote.inc': number[];
-            'remote.dec': number[];
-            'remote.diffs.normal': number[];
-            'remote.diffs.reply': number[];
-            'remote.diffs.renote': number[];
-            'remote.diffs.withFile': number[];
+            local: {
+              total: number[];
+              inc: number[];
+              dec: number[];
+              diffs: {
+                normal: number[];
+                reply: number[];
+                renote: number[];
+                withFile: number[];
+              };
+            };
+            remote: {
+              total: number[];
+              inc: number[];
+              dec: number[];
+              diffs: {
+                normal: number[];
+                reply: number[];
+                renote: number[];
+                withFile: number[];
+              };
+            };
           };
         };
       };
@@ -11209,18 +12308,30 @@ export type operations = {
       200: {
         content: {
           'application/json': {
-            'local.followings.total': number[];
-            'local.followings.inc': number[];
-            'local.followings.dec': number[];
-            'local.followers.total': number[];
-            'local.followers.inc': number[];
-            'local.followers.dec': number[];
-            'remote.followings.total': number[];
-            'remote.followings.inc': number[];
-            'remote.followings.dec': number[];
-            'remote.followers.total': number[];
-            'remote.followers.inc': number[];
-            'remote.followers.dec': number[];
+            local: {
+              followings: {
+                total: number[];
+                inc: number[];
+                dec: number[];
+              };
+              followers: {
+                total: number[];
+                inc: number[];
+                dec: number[];
+              };
+            };
+            remote: {
+              followings: {
+                total: number[];
+                inc: number[];
+                dec: number[];
+              };
+              followers: {
+                total: number[];
+                inc: number[];
+                dec: number[];
+              };
+            };
           };
         };
       };
@@ -11285,10 +12396,12 @@ export type operations = {
             total: number[];
             inc: number[];
             dec: number[];
-            'diffs.normal': number[];
-            'diffs.reply': number[];
-            'diffs.renote': number[];
-            'diffs.withFile': number[];
+            diffs: {
+              normal: number[];
+              reply: number[];
+              renote: number[];
+              withFile: number[];
+            };
           };
         };
       };
@@ -11350,10 +12463,14 @@ export type operations = {
       200: {
         content: {
           'application/json': {
-            'upv.user': number[];
-            'pv.user': number[];
-            'upv.visitor': number[];
-            'pv.visitor': number[];
+            upv: {
+              user: number[];
+              visitor: number[];
+            };
+            pv: {
+              user: number[];
+              visitor: number[];
+            };
           };
         };
       };
@@ -11415,8 +12532,12 @@ export type operations = {
       200: {
         content: {
           'application/json': {
-            'local.count': number[];
-            'remote.count': number[];
+            local: {
+              count: number[];
+            };
+            remote: {
+              count: number[];
+            };
           };
         };
       };
@@ -11476,12 +12597,16 @@ export type operations = {
       200: {
         content: {
           'application/json': {
-            'local.total': number[];
-            'local.inc': number[];
-            'local.dec': number[];
-            'remote.total': number[];
-            'remote.inc': number[];
-            'remote.dec': number[];
+            local: {
+              total: number[];
+              inc: number[];
+              dec: number[];
+            };
+            remote: {
+              total: number[];
+              inc: number[];
+              dec: number[];
+            };
           };
         };
       };
@@ -13514,6 +14639,8 @@ export type operations = {
           federating?: boolean | null;
           subscribing?: boolean | null;
           publishing?: boolean | null;
+          nsfw?: boolean | null;
+          bubble?: boolean | null;
           /** @default 30 */
           limit?: number;
           /** @default 0 */
@@ -15266,9 +16393,13 @@ export type operations = {
       };
     };
     responses: {
-      /** @description OK (without any results) */
-      204: {
-        content: never;
+      /** @description OK (with results) */
+      200: {
+        content: {
+          'application/json': {
+            backupCodes: string[];
+          };
+        };
       };
       /** @description Client error */
       400: {
@@ -15436,7 +16567,7 @@ export type operations = {
         content: {
           'application/json': {
             rp: {
-              id: string | null;
+              id?: string;
             };
             user: {
               id: string;
@@ -15747,11 +16878,11 @@ export type operations = {
           'application/json': {
               /** Format: misskey:id */
               id: string;
-              name: string;
+              name?: string;
               /** Format: date-time */
               createdAt: string;
               /** Format: date-time */
-              lastUsedAt: string;
+              lastUsedAt?: string;
               permission: string[];
             }[];
         };
@@ -15821,7 +16952,7 @@ export type operations = {
               name: string;
               callbackUrl: string | null;
               permission: string[];
-              isAuthorized: boolean;
+              isAuthorized?: boolean;
             })[];
         };
       };
@@ -15868,7 +16999,7 @@ export type operations = {
       content: {
         'application/json': {
           /** @enum {string} */
-          name: 'notes1' | 'notes10' | 'notes100' | 'notes500' | 'notes1000' | 'notes5000' | 'notes10000' | 'notes20000' | 'notes30000' | 'notes40000' | 'notes50000' | 'notes60000' | 'notes70000' | 'notes80000' | 'notes90000' | 'notes100000' | 'login3' | 'login7' | 'login15' | 'login30' | 'login60' | 'login100' | 'login200' | 'login300' | 'login400' | 'login500' | 'login600' | 'login700' | 'login800' | 'login900' | 'login1000' | 'passedSinceAccountCreated1' | 'passedSinceAccountCreated2' | 'passedSinceAccountCreated3' | 'loggedInOnBirthday' | 'loggedInOnNewYearsDay' | 'noteClipped1' | 'noteFavorited1' | 'myNoteFavorited1' | 'profileFilled' | 'markedAsCat' | 'following1' | 'following10' | 'following50' | 'following100' | 'following300' | 'followers1' | 'followers10' | 'followers50' | 'followers100' | 'followers300' | 'followers500' | 'followers1000' | 'collectAchievements30' | 'viewAchievements3min' | 'iLoveMisskey' | 'foundTreasure' | 'client30min' | 'client60min' | 'noteDeletedWithin1min' | 'postedAtLateNight' | 'postedAt0min0sec' | 'selfQuote' | 'htl20npm' | 'viewInstanceChart' | 'outputHelloWorldOnScratchpad' | 'open3windows' | 'driveFolderCircularReference' | 'reactWithoutRead' | 'clickedClickHere' | 'justPlainLucky' | 'setNameToSyuilo' | 'cookieClicked' | 'brainDiver' | 'smashTestNotificationButton' | 'tutorialCompleted';
+          name: 'notes1' | 'notes10' | 'notes100' | 'notes500' | 'notes1000' | 'notes5000' | 'notes10000' | 'notes20000' | 'notes30000' | 'notes40000' | 'notes50000' | 'notes60000' | 'notes70000' | 'notes80000' | 'notes90000' | 'notes100000' | 'login3' | 'login7' | 'login15' | 'login30' | 'login60' | 'login100' | 'login200' | 'login300' | 'login400' | 'login500' | 'login600' | 'login700' | 'login800' | 'login900' | 'login1000' | 'passedSinceAccountCreated1' | 'passedSinceAccountCreated2' | 'passedSinceAccountCreated3' | 'loggedInOnBirthday' | 'loggedInOnNewYearsDay' | 'noteClipped1' | 'noteFavorited1' | 'myNoteFavorited1' | 'profileFilled' | 'markedAsCat' | 'following1' | 'following10' | 'following50' | 'following100' | 'following300' | 'followers1' | 'followers10' | 'followers50' | 'followers100' | 'followers300' | 'followers500' | 'followers1000' | 'collectAchievements30' | 'viewAchievements3min' | 'iLoveMisskey' | 'foundTreasure' | 'client30min' | 'client60min' | 'noteDeletedWithin1min' | 'postedAtLateNight' | 'postedAt0min0sec' | 'selfQuote' | 'htl20npm' | 'viewInstanceChart' | 'outputHelloWorldOnScratchpad' | 'open3windows' | 'driveFolderCircularReference' | 'reactWithoutRead' | 'clickedClickHere' | 'justPlainLucky' | 'setNameToSyuilo' | 'cookieClicked' | 'brainDiver' | 'smashTestNotificationButton' | 'tutorialCompleted' | 'bubbleGameExplodingHead' | 'bubbleGameDoubleExplodingHead';
         };
       };
     };
@@ -16016,6 +17147,57 @@ export type operations = {
       };
     };
   };
+  /**
+   * i/export-data
+   * @description No description provided.
+   *
+   * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
+   * **Credential required**: *Yes*
+   */
+  'i/export-data': {
+    responses: {
+      /** @description OK (without any results) */
+      204: {
+        content: never;
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description To many requests */
+      429: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
   /**
    * i/export-blocking
    * @description No description provided.
@@ -16230,6 +17412,57 @@ export type operations = {
       };
     };
   };
+  /**
+   * i/export-clips
+   * @description No description provided.
+   *
+   * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
+   * **Credential required**: *Yes*
+   */
+  'i/export-clips': {
+    responses: {
+      /** @description OK (without any results) */
+      204: {
+        content: never;
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description To many requests */
+      429: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
   /**
    * i/export-favorites
    * @description No description provided.
@@ -16680,6 +17913,66 @@ export type operations = {
       };
     };
   };
+  /**
+   * i/import-notes
+   * @description No description provided.
+   *
+   * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
+   * **Credential required**: *Yes*
+   */
+  'i/import-notes': {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** Format: misskey:id */
+          fileId: string;
+          type?: string | null;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (without any results) */
+      204: {
+        content: never;
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description To many requests */
+      429: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
   /**
    * i/import-muting
    * @description No description provided.
@@ -16875,8 +18168,8 @@ export type operations = {
           untilId?: string;
           /** @default true */
           markAsRead?: boolean;
-          includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
-          excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
+          includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
+          excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
         };
       };
     };
@@ -16943,8 +18236,8 @@ export type operations = {
           untilId?: string;
           /** @default true */
           markAsRead?: boolean;
-          includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
-          excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'pollVote' | 'groupInvited')[];
+          includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
+          excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[];
         };
       };
     };
@@ -17370,6 +18663,59 @@ export type operations = {
       };
     };
   };
+  /**
+   * i/registry/get-unsecure
+   * @description No description provided.
+   *
+   * **Credential required**: *Yes* / **Permission**: *read:account*
+   */
+  'i/registry/get-unsecure': {
+    requestBody: {
+      content: {
+        'application/json': {
+          key: string;
+          /** @default [] */
+          scope?: string[];
+        };
+      };
+    };
+    responses: {
+      /** @description OK (without any results) */
+      204: {
+        content: never;
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
   /**
    * i/registry/get-detail
    * @description No description provided.
@@ -17391,7 +18737,10 @@ export type operations = {
       /** @description OK (with results) */
       200: {
         content: {
-          'application/json': Record<string, never>;
+          'application/json': {
+            updatedAt: string;
+            value: unknown;
+          };
         };
       };
       /** @description Client error */
@@ -17502,7 +18851,9 @@ export type operations = {
       /** @description OK (with results) */
       200: {
         content: {
-          'application/json': Record<string, never>;
+          'application/json': {
+            [key: string]: string;
+          };
         };
       };
       /** @description Client error */
@@ -17554,9 +18905,11 @@ export type operations = {
       };
     };
     responses: {
-      /** @description OK (without any results) */
-      204: {
-        content: never;
+      /** @description OK (with results) */
+      200: {
+        content: {
+          'application/json': string[];
+        };
       };
       /** @description Client error */
       400: {
@@ -17937,7 +19290,7 @@ export type operations = {
       /** @description OK (with results) */
       200: {
         content: {
-          'application/json': components['schemas']['UserDetailed'];
+          'application/json': components['schemas']['MeDetailed'];
         };
       };
       /** @description Client error */
@@ -17992,6 +19345,7 @@ export type operations = {
           description?: string | null;
           location?: string | null;
           birthday?: string | null;
+          listenbrainz?: string | null;
           /** @enum {string|null} */
           lang?: null | 'ach' | 'ady' | 'af' | 'af-NA' | 'af-ZA' | 'ak' | 'ar' | 'ar-AR' | 'ar-MA' | 'ar-SA' | 'ay-BO' | 'az' | 'az-AZ' | 'be-BY' | 'bg' | 'bg-BG' | 'bn' | 'bn-IN' | 'bn-BD' | 'br' | 'bs-BA' | 'ca' | 'ca-ES' | 'cak' | 'ck-US' | 'cs' | 'cs-CZ' | 'cy' | 'cy-GB' | 'da' | 'da-DK' | 'de' | 'de-AT' | 'de-DE' | 'de-CH' | 'dsb' | 'el' | 'el-GR' | 'en' | 'en-GB' | 'en-AU' | 'en-CA' | 'en-IE' | 'en-IN' | 'en-PI' | 'en-SG' | 'en-UD' | 'en-US' | 'en-ZA' | 'en@pirate' | 'eo' | 'eo-EO' | 'es' | 'es-AR' | 'es-419' | 'es-CL' | 'es-CO' | 'es-EC' | 'es-ES' | 'es-LA' | 'es-NI' | 'es-MX' | 'es-US' | 'es-VE' | 'et' | 'et-EE' | 'eu' | 'eu-ES' | 'fa' | 'fa-IR' | 'fb-LT' | 'ff' | 'fi' | 'fi-FI' | 'fo' | 'fo-FO' | 'fr' | 'fr-CA' | 'fr-FR' | 'fr-BE' | 'fr-CH' | 'fy-NL' | 'ga' | 'ga-IE' | 'gd' | 'gl' | 'gl-ES' | 'gn-PY' | 'gu-IN' | 'gv' | 'gx-GR' | 'he' | 'he-IL' | 'hi' | 'hi-IN' | 'hr' | 'hr-HR' | 'hsb' | 'ht' | 'hu' | 'hu-HU' | 'hy' | 'hy-AM' | 'id' | 'id-ID' | 'is' | 'is-IS' | 'it' | 'it-IT' | 'ja' | 'ja-JP' | 'jv-ID' | 'ka-GE' | 'kk-KZ' | 'km' | 'kl' | 'km-KH' | 'kab' | 'kn' | 'kn-IN' | 'ko' | 'ko-KR' | 'ku-TR' | 'kw' | 'la' | 'la-VA' | 'lb' | 'li-NL' | 'lt' | 'lt-LT' | 'lv' | 'lv-LV' | 'mai' | 'mg-MG' | 'mk' | 'mk-MK' | 'ml' | 'ml-IN' | 'mn-MN' | 'mr' | 'mr-IN' | 'ms' | 'ms-MY' | 'mt' | 'mt-MT' | 'my' | 'no' | 'nb' | 'nb-NO' | 'ne' | 'ne-NP' | 'nl' | 'nl-BE' | 'nl-NL' | 'nn-NO' | 'oc' | 'or-IN' | 'pa' | 'pa-IN' | 'pl' | 'pl-PL' | 'ps-AF' | 'pt' | 'pt-BR' | 'pt-PT' | 'qu-PE' | 'rm-CH' | 'ro' | 'ro-RO' | 'ru' | 'ru-RU' | 'sa-IN' | 'se-NO' | 'sh' | 'si-LK' | 'sk' | 'sk-SK' | 'sl' | 'sl-SI' | 'so-SO' | 'sq' | 'sq-AL' | 'sr' | 'sr-RS' | 'su' | 'sv' | 'sv-SE' | 'sw' | 'sw-KE' | 'ta' | 'ta-IN' | 'te' | 'te-IN' | 'tg' | 'tg-TJ' | 'th' | 'th-TH' | 'fil' | 'tlh' | 'tr' | 'tr-TR' | 'tt-RU' | 'uk' | 'uk-UA' | 'ur' | 'ur-PK' | 'uz' | 'uz-UZ' | 'vi' | 'vi-VN' | 'xh-ZA' | 'yi' | 'yi-DE' | 'zh' | 'zh-Hans' | 'zh-Hant' | 'zh-CN' | 'zh-HK' | 'zh-SG' | 'zh-TW' | 'zu-ZA';
           /** Format: misskey:id */
@@ -18006,6 +19360,8 @@ export type operations = {
             })[];
           /** Format: misskey:id */
           bannerId?: string | null;
+          /** Format: misskey:id */
+          backgroundId?: string | null;
           fields?: {
               name: string;
               value: string;
@@ -18018,8 +19374,10 @@ export type operations = {
           autoAcceptFollowed?: boolean;
           noCrawle?: boolean;
           preventAiLearning?: boolean;
+          noindex?: boolean;
           isBot?: boolean;
           isCat?: boolean;
+          speakAsCat?: boolean;
           injectFeaturedNote?: boolean;
           receiveAnnouncementEmail?: boolean;
           alwaysMarkNsfw?: boolean;
@@ -18033,7 +19391,134 @@ export type operations = {
           mutedWords?: (string[] | string)[];
           hardMutedWords?: (string[] | string)[];
           mutedInstances?: string[];
-          notificationRecieveConfig?: Record<string, never>;
+          notificationRecieveConfig?: {
+            note?: OneOf<[{
+              /** @enum {string} */
+              type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+            }, {
+              /** @enum {string} */
+              type: 'list';
+              /** Format: misskey:id */
+              userListId: string;
+            }]>;
+            follow?: OneOf<[{
+              /** @enum {string} */
+              type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+            }, {
+              /** @enum {string} */
+              type: 'list';
+              /** Format: misskey:id */
+              userListId: string;
+            }]>;
+            mention?: OneOf<[{
+              /** @enum {string} */
+              type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+            }, {
+              /** @enum {string} */
+              type: 'list';
+              /** Format: misskey:id */
+              userListId: string;
+            }]>;
+            reply?: OneOf<[{
+              /** @enum {string} */
+              type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+            }, {
+              /** @enum {string} */
+              type: 'list';
+              /** Format: misskey:id */
+              userListId: string;
+            }]>;
+            renote?: OneOf<[{
+              /** @enum {string} */
+              type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+            }, {
+              /** @enum {string} */
+              type: 'list';
+              /** Format: misskey:id */
+              userListId: string;
+            }]>;
+            quote?: OneOf<[{
+              /** @enum {string} */
+              type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+            }, {
+              /** @enum {string} */
+              type: 'list';
+              /** Format: misskey:id */
+              userListId: string;
+            }]>;
+            reaction?: OneOf<[{
+              /** @enum {string} */
+              type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+            }, {
+              /** @enum {string} */
+              type: 'list';
+              /** Format: misskey:id */
+              userListId: string;
+            }]>;
+            pollEnded?: OneOf<[{
+              /** @enum {string} */
+              type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+            }, {
+              /** @enum {string} */
+              type: 'list';
+              /** Format: misskey:id */
+              userListId: string;
+            }]>;
+            receiveFollowRequest?: OneOf<[{
+              /** @enum {string} */
+              type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+            }, {
+              /** @enum {string} */
+              type: 'list';
+              /** Format: misskey:id */
+              userListId: string;
+            }]>;
+            followRequestAccepted?: OneOf<[{
+              /** @enum {string} */
+              type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+            }, {
+              /** @enum {string} */
+              type: 'list';
+              /** Format: misskey:id */
+              userListId: string;
+            }]>;
+            roleAssigned?: OneOf<[{
+              /** @enum {string} */
+              type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+            }, {
+              /** @enum {string} */
+              type: 'list';
+              /** Format: misskey:id */
+              userListId: string;
+            }]>;
+            achievementEarned?: OneOf<[{
+              /** @enum {string} */
+              type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+            }, {
+              /** @enum {string} */
+              type: 'list';
+              /** Format: misskey:id */
+              userListId: string;
+            }]>;
+            app?: OneOf<[{
+              /** @enum {string} */
+              type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+            }, {
+              /** @enum {string} */
+              type: 'list';
+              /** Format: misskey:id */
+              userListId: string;
+            }]>;
+            test?: OneOf<[{
+              /** @enum {string} */
+              type: 'all' | 'following' | 'follower' | 'mutualFollow' | 'followingOrFollower' | 'never';
+            }, {
+              /** @enum {string} */
+              type: 'list';
+              /** Format: misskey:id */
+              userListId: string;
+            }]>;
+          };
           emailNotificationTypes?: string[];
           alsoKnownAs?: string[];
         };
@@ -18158,7 +19643,7 @@ export type operations = {
           url: string;
           /** @default */
           secret?: string;
-          on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[];
+          on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction' | 'edited')[];
         };
       };
     };
@@ -18172,7 +19657,7 @@ export type operations = {
             /** Format: misskey:id */
             userId: string;
             name: string;
-            on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[];
+            on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction' | 'edited')[];
             url: string;
             secret: string;
             active: boolean;
@@ -18231,7 +19716,7 @@ export type operations = {
               /** Format: misskey:id */
               userId: string;
               name: string;
-              on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[];
+              on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction' | 'edited')[];
               url: string;
               secret: string;
               active: boolean;
@@ -18298,7 +19783,7 @@ export type operations = {
             /** Format: misskey:id */
             userId: string;
             name: string;
-            on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[];
+            on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction' | 'edited')[];
             url: string;
             secret: string;
             active: boolean;
@@ -18356,7 +19841,7 @@ export type operations = {
           url: string;
           /** @default */
           secret?: string;
-          on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction')[];
+          on: ('mention' | 'unfollow' | 'follow' | 'followed' | 'note' | 'reply' | 'renote' | 'reaction' | 'edited')[];
           active: boolean;
         };
       };
@@ -18673,86 +20158,7 @@ export type operations = {
       /** @description OK (with results) */
       200: {
         content: {
-          'application/json': {
-            maintainerName: string | null;
-            maintainerEmail: string | null;
-            version: string;
-            name: string;
-            shortName: string | null;
-            /**
-             * Format: url
-             * @example https://misskey.example.com
-             */
-            uri: string;
-            description: string | null;
-            langs: string[];
-            tosUrl: string | null;
-            /** @default https://github.com/misskey-dev/misskey */
-            repositoryUrl: string;
-            /** @default https://github.com/misskey-dev/misskey/issues/new */
-            feedbackUrl: string;
-            defaultDarkTheme: string | null;
-            defaultLightTheme: string | null;
-            disableRegistration: boolean;
-            cacheRemoteFiles: boolean;
-            cacheRemoteSensitiveFiles: boolean;
-            emailRequiredForSignup: boolean;
-            enableHcaptcha: boolean;
-            hcaptchaSiteKey: string | null;
-            enableRecaptcha: boolean;
-            recaptchaSiteKey: string | null;
-            enableTurnstile: boolean;
-            turnstileSiteKey: string | null;
-            swPublickey: string | null;
-            /** @default /assets/ai.png */
-            mascotImageUrl: string;
-            bannerUrl: string;
-            serverErrorImageUrl: string | null;
-            infoImageUrl: string | null;
-            notFoundImageUrl: string | null;
-            iconUrl: string | null;
-            maxNoteTextLength: number;
-            ads: {
-                /**
-                 * Format: id
-                 * @example xxxxxxxxxx
-                 */
-                id: string;
-                /** Format: url */
-                url: string;
-                place: string;
-                ratio: number;
-                /** Format: url */
-                imageUrl: string;
-                dayOfWeek: number;
-              }[];
-            /** @default 0 */
-            notesPerOneAd: number;
-            /** @example false */
-            requireSetup: boolean;
-            enableEmail: boolean;
-            enableServiceWorker: boolean;
-            translatorAvailable: boolean;
-            proxyAccountName: string | null;
-            mediaProxy: string;
-            features?: {
-              registration: boolean;
-              localTimeline: boolean;
-              globalTimeline: boolean;
-              hcaptcha: boolean;
-              recaptcha: boolean;
-              objectStorage: boolean;
-              serviceWorker: boolean;
-              /** @default true */
-              miauth?: boolean;
-            };
-            backgroundImageUrl: string | null;
-            impressumUrl: string | null;
-            logoImageUrl: string | null;
-            privacyPolicyUrl: string | null;
-            serverRules: string[];
-            themeColor: string | null;
-          };
+          'application/json': components['schemas']['MetaLite'] | components['schemas']['MetaDetailed'];
         };
       };
       /** @description Client error */
@@ -19424,6 +20830,8 @@ export type operations = {
           sinceId?: string;
           /** Format: misskey:id */
           untilId?: string;
+          /** @default true */
+          showQuotes?: boolean;
         };
       };
     };
@@ -19912,6 +21320,74 @@ export type operations = {
           /** @default false */
           withFiles?: boolean;
           /** @default true */
+          withBots?: boolean;
+          /** @default true */
+          withRenotes?: boolean;
+          /** @default 10 */
+          limit?: number;
+          /** Format: misskey:id */
+          sinceId?: string;
+          /** Format: misskey:id */
+          untilId?: string;
+          sinceDate?: number;
+          untilDate?: number;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (with results) */
+      200: {
+        content: {
+          'application/json': components['schemas']['Note'][];
+        };
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
+  /**
+   * notes/bubble-timeline
+   * @description No description provided.
+   *
+   * **Credential required**: *No*
+   */
+  'notes/bubble-timeline': {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** @default false */
+          withFiles?: boolean;
+          /** @default true */
+          withBots?: boolean;
+          /** @default true */
           withRenotes?: boolean;
           /** @default 10 */
           limit?: number;
@@ -19995,6 +21471,8 @@ export type operations = {
           withRenotes?: boolean;
           /** @default false */
           withReplies?: boolean;
+          /** @default true */
+          withBots?: boolean;
         };
       };
     };
@@ -20053,6 +21531,8 @@ export type operations = {
           withRenotes?: boolean;
           /** @default false */
           withReplies?: boolean;
+          /** @default true */
+          withBots?: boolean;
           /** @default 10 */
           limit?: number;
           /** Format: misskey:id */
@@ -20447,6 +21927,59 @@ export type operations = {
       };
     };
   };
+  /**
+   * notes/like
+   * @description No description provided.
+   *
+   * **Credential required**: *Yes* / **Permission**: *write:reactions*
+   */
+  'notes/like': {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** Format: misskey:id */
+          noteId: string;
+          override?: string | null;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (without any results) */
+      204: {
+        content: never;
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
   /**
    * notes/renotes
    * @description No description provided.
@@ -20459,12 +21992,16 @@ export type operations = {
         'application/json': {
           /** Format: misskey:id */
           noteId: string;
+          /** Format: misskey:id */
+          userId?: string;
           /** @default 10 */
           limit?: number;
           /** Format: misskey:id */
           sinceId?: string;
           /** Format: misskey:id */
           untilId?: string;
+          /** @default false */
+          quote?: boolean;
         };
       };
     };
@@ -20660,6 +22197,7 @@ export type operations = {
           offset?: number;
           /** @description The local host is represented with `.`. */
           host?: string;
+          filetype?: string | null;
           /**
            * Format: misskey:id
            * @default null
@@ -20670,6 +22208,7 @@ export type operations = {
            * @default null
            */
           channelId?: string | null;
+          order?: string;
         };
       };
     };
@@ -20963,6 +22502,8 @@ export type operations = {
           withFiles?: boolean;
           /** @default true */
           withRenotes?: boolean;
+          /** @default true */
+          withBots?: boolean;
         };
       };
     };
@@ -21075,6 +22616,8 @@ export type operations = {
         'application/json': {
           /** Format: misskey:id */
           noteId: string;
+          /** @default false */
+          quote?: boolean;
         };
       };
     };
@@ -21198,6 +22741,157 @@ export type operations = {
       };
     };
   };
+  /**
+   * notes/edit
+   * @description No description provided.
+   *
+   * **Credential required**: *Yes* / **Permission**: *write:notes*
+   */
+  'notes/edit': {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** Format: misskey:id */
+          editId?: string;
+          /**
+           * @default public
+           * @enum {string}
+           */
+          visibility?: 'public' | 'home' | 'followers' | 'specified';
+          visibleUserIds?: string[];
+          cw?: string | null;
+          /** @default false */
+          localOnly?: boolean;
+          /**
+           * @default null
+           * @enum {string|null}
+           */
+          reactionAcceptance?: null | 'likeOnly' | 'likeOnlyForRemote' | 'nonSensitiveOnly' | 'nonSensitiveOnlyForLocalLikeOnlyForRemote';
+          /** @default false */
+          noExtractMentions?: boolean;
+          /** @default false */
+          noExtractHashtags?: boolean;
+          /** @default false */
+          noExtractEmojis?: boolean;
+          /** Format: misskey:id */
+          replyId?: string | null;
+          /** Format: misskey:id */
+          renoteId?: string | null;
+          /** Format: misskey:id */
+          channelId?: string | null;
+          text?: string | null;
+          fileIds?: string[];
+          mediaIds?: string[];
+          poll?: ({
+            choices: string[];
+            multiple?: boolean;
+            expiresAt?: number | null;
+            expiredAfter?: number | null;
+          }) | null;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (with results) */
+      200: {
+        content: {
+          'application/json': {
+            createdNote: components['schemas']['Note'];
+          };
+        };
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description To many requests */
+      429: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
+  /**
+   * notes/versions
+   * @description No description provided.
+   *
+   * **Credential required**: *No*
+   */
+  'notes/versions': {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** Format: misskey:id */
+          noteId: string;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (with results) */
+      200: {
+        content: {
+          'application/json': Record<string, never>;
+        };
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
   /**
    * notifications/create
    * @description No description provided.
@@ -21257,6 +22951,50 @@ export type operations = {
       };
     };
   };
+  /**
+   * notifications/flush
+   * @description No description provided.
+   *
+   * **Credential required**: *Yes* / **Permission**: *write:notifications*
+   */
+  'notifications/flush': {
+    responses: {
+      /** @description OK (without any results) */
+      204: {
+        content: never;
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
   /**
    * notifications/mark-all-as-read
    * @description No description provided.
@@ -22147,10 +23885,10 @@ export type operations = {
         'application/json': {
           /** Format: misskey:id */
           flashId: string;
-          title: string;
-          summary: string;
-          script: string;
-          permissions: string[];
+          title?: string;
+          summary?: string;
+          script?: string;
+          permissions?: string[];
           /** @enum {string} */
           visibility?: 'public' | 'private';
         };
@@ -22593,7 +24331,7 @@ export type operations = {
           'application/json': {
               /** Format: misskey:id */
               id: string;
-              user: components['schemas']['User'];
+              user: components['schemas']['UserDetailed'];
             }[];
         };
       };
@@ -23222,12 +24960,12 @@ export type operations = {
         content: {
           'application/json': {
             /** Format: misskey:id */
-            id: string;
+            id?: string;
             required: boolean;
-            string: string;
-            default: string;
+            string?: string;
+            default?: string;
             /** @default hello */
-            nullableDefault: string | null;
+            nullableDefault?: string | null;
           };
         };
       };
@@ -24388,7 +26126,7 @@ export type operations = {
               createdAt: string;
               /** Format: misskey:id */
               userId: string;
-              user: components['schemas']['User'];
+              user: components['schemas']['UserLite'];
               withReplies: boolean;
             }[];
         };
@@ -25284,7 +27022,569 @@ export type operations = {
       /** @description OK (with results) */
       200: {
         content: {
-          'application/json': unknown;
+          'application/json': {
+              /** Format: date-time */
+              createdAt: string;
+              users: number;
+              data: {
+                [key: string]: number;
+              };
+            }[];
+        };
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
+  /**
+   * sponsors
+   * @description Get Sharkey GH Sponsors
+   *
+   * **Credential required**: *No*
+   */
+  sponsors: {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** @default false */
+          forceUpdate?: boolean;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (without any results) */
+      204: {
+        content: never;
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
+  /**
+   * bubble-game/register
+   * @description No description provided.
+   *
+   * **Credential required**: *Yes* / **Permission**: *write:account*
+   */
+  'bubble-game/register': {
+    requestBody: {
+      content: {
+        'application/json': {
+          score: number;
+          seed: string;
+          logs: number[][];
+          gameMode: string;
+          gameVersion: number;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (without any results) */
+      204: {
+        content: never;
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description To many requests */
+      429: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
+  /**
+   * bubble-game/ranking
+   * @description No description provided.
+   *
+   * **Credential required**: *No*
+   */
+  'bubble-game/ranking': {
+    requestBody: {
+      content: {
+        'application/json': {
+          gameMode: string;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (with results) */
+      200: {
+        content: {
+          'application/json': {
+              /** Format: misskey:id */
+              id: string;
+              score: number;
+              user?: components['schemas']['UserLite'];
+            }[];
+        };
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
+  /**
+   * reversi/cancel-match
+   * @description No description provided.
+   *
+   * **Credential required**: *Yes* / **Permission**: *write:account*
+   */
+  'reversi/cancel-match': {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** Format: misskey:id */
+          userId?: string | null;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (without any results) */
+      204: {
+        content: never;
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
+  /**
+   * reversi/games
+   * @description No description provided.
+   *
+   * **Credential required**: *No*
+   */
+  'reversi/games': {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** @default 10 */
+          limit?: number;
+          /** Format: misskey:id */
+          sinceId?: string;
+          /** Format: misskey:id */
+          untilId?: string;
+          /** @default false */
+          my?: boolean;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (with results) */
+      200: {
+        content: {
+          'application/json': components['schemas']['ReversiGameLite'][];
+        };
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
+  /**
+   * reversi/match
+   * @description No description provided.
+   *
+   * **Credential required**: *Yes* / **Permission**: *write:account*
+   */
+  'reversi/match': {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** Format: misskey:id */
+          userId?: string | null;
+          /** @default false */
+          noIrregularRules?: boolean;
+          /** @default false */
+          multiple?: boolean;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (with results) */
+      200: {
+        content: {
+          'application/json': components['schemas']['ReversiGameDetailed'];
+        };
+      };
+      /** @description OK (without any results) */
+      204: {
+        content: never;
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
+  /**
+   * reversi/invitations
+   * @description No description provided.
+   *
+   * **Credential required**: *Yes* / **Permission**: *read:account*
+   */
+  'reversi/invitations': {
+    responses: {
+      /** @description OK (with results) */
+      200: {
+        content: {
+          'application/json': components['schemas']['UserLite'][];
+        };
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
+  /**
+   * reversi/show-game
+   * @description No description provided.
+   *
+   * **Credential required**: *No*
+   */
+  'reversi/show-game': {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** Format: misskey:id */
+          gameId: string;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (with results) */
+      200: {
+        content: {
+          'application/json': components['schemas']['ReversiGameDetailed'];
+        };
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
+  /**
+   * reversi/surrender
+   * @description No description provided.
+   *
+   * **Credential required**: *Yes* / **Permission**: *write:account*
+   */
+  'reversi/surrender': {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** Format: misskey:id */
+          gameId: string;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (without any results) */
+      204: {
+        content: never;
+      };
+      /** @description Client error */
+      400: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Authentication error */
+      401: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Forbidden error */
+      403: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description I'm Ai */
+      418: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+      /** @description Internal server error */
+      500: {
+        content: {
+          'application/json': components['schemas']['Error'];
+        };
+      };
+    };
+  };
+  /**
+   * reversi/verify
+   * @description No description provided.
+   *
+   * **Credential required**: *No*
+   */
+  'reversi/verify': {
+    requestBody: {
+      content: {
+        'application/json': {
+          /** Format: misskey:id */
+          gameId: string;
+          crc32: string;
+        };
+      };
+    };
+    responses: {
+      /** @description OK (with results) */
+      200: {
+        content: {
+          'application/json': {
+            desynced: boolean;
+            game?: components['schemas']['ReversiGameDetailed'] | null;
+          };
         };
       };
       /** @description Client error */
diff --git a/packages/misskey-js/src/consts.ts b/packages/misskey-js/src/consts.ts
index 0748d9863e..4de567e6d4 100644
--- a/packages/misskey-js/src/consts.ts
+++ b/packages/misskey-js/src/consts.ts
@@ -1,4 +1,4 @@
-export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app', 'roleAssigned', 'achievementEarned'] as const;
+export const notificationTypes = ['note', 'follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'app', 'roleAssigned', 'achievementEarned', 'edited'] as const;
 
 export const noteVisibilities = ['public', 'home', 'followers', 'specified'] as const;
 
@@ -127,6 +127,7 @@ export const moderationLogTypes = [
 	'resetPassword',
 	'suspendRemoteInstance',
 	'unsuspendRemoteInstance',
+	'updateRemoteInstanceNote',
 	'markSensitiveDriveFile',
 	'unmarkSensitiveDriveFile',
 	'resolveAbuseReport',
@@ -272,6 +273,12 @@ export type ModerationLogPayloads = {
 		id: string;
 		host: string;
 	};
+	updateRemoteInstanceNote: {
+		id: string;
+		host: string;
+		before: string | null;
+		after: string | null;
+	};
 	markSensitiveDriveFile: {
 		fileId: string;
 		fileUserId: string | null;
diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts
index 2c490fa55e..5a19bf7446 100644
--- a/packages/misskey-js/src/entities.ts
+++ b/packages/misskey-js/src/entities.ts
@@ -1,8 +1,8 @@
 import { ModerationLogPayloads } from './consts.js';
-import { Announcement, EmojiDetailed, Page, User, UserDetailed } from './autogen/models';
+import { Announcement, EmojiDetailed, MeDetailed, Page, User, UserDetailedNotMe } from './autogen/models.js';
 
-export * from './autogen/entities';
-export * from './autogen/models';
+export * from './autogen/entities.js';
+export * from './autogen/models.js';
 
 export type ID = string;
 export type DateString = string;
@@ -19,7 +19,7 @@ export type ModerationLog = {
 	id: ID;
 	createdAt: DateString;
 	userId: User['id'];
-	user: UserDetailed | null;
+	user: UserDetailedNotMe | null;
 } & ({
 	type: 'updateServerSettings';
 	info: ModerationLogPayloads['updateServerSettings'];
@@ -98,6 +98,9 @@ export type ModerationLog = {
 } | {
 	type: 'unsuspendRemoteInstance';
 	info: ModerationLogPayloads['unsuspendRemoteInstance'];
+} | {
+	type: 'updateRemoteInstanceNote';
+	info: ModerationLogPayloads['updateRemoteInstanceNote'];
 } | {
 	type: 'markSensitiveDriveFile';
 	info: ModerationLogPayloads['markSensitiveDriveFile'];
@@ -152,7 +155,7 @@ export type ServerStats = {
 	}
 };
 
-export type ServerStatsLog = string[];
+export type ServerStatsLog = ServerStats[];
 
 export type QueueStats = {
 	deliver: {
@@ -169,7 +172,7 @@ export type QueueStats = {
 	};
 };
 
-export type QueueStatsLog = string[];
+export type QueueStatsLog = QueueStats[];
 
 export type EmojiAdded = {
 	emoji: EmojiDetailed
@@ -186,3 +189,38 @@ export type EmojiDeleted = {
 export type AnnouncementCreated = {
 	announcement: Announcement;
 };
+
+export type SignupRequest = {
+	username: string;
+	password: string;
+	host?: string;
+	invitationCode?: string;
+	emailAddress?: string;
+	'hcaptcha-response'?: string | null;
+	'g-recaptcha-response'?: string | null;
+	'turnstile-response'?: string | null;
+}
+
+export type SignupResponse = MeDetailed & {
+	token: string;
+}
+
+export type SignupPendingRequest = {
+	code: string;
+};
+
+export type SignupPendingResponse = {
+	id: User['id'],
+	i: string,
+};
+
+export type SigninRequest = {
+	username: string;
+	password: string;
+	token?: string;
+};
+
+export type SigninResponse = {
+	id: User['id'],
+	i: string,
+};
diff --git a/packages/misskey-js/src/streaming.ts b/packages/misskey-js/src/streaming.ts
index c641706a4b..0f26857782 100644
--- a/packages/misskey-js/src/streaming.ts
+++ b/packages/misskey-js/src/streaming.ts
@@ -1,7 +1,9 @@
 import { EventEmitter } from 'eventemitter3';
-import ReconnectingWebsocket from 'reconnecting-websocket';
+import _ReconnectingWebsocket from 'reconnecting-websocket';
 import type { BroadcastEvents, Channels } from './streaming.types.js';
 
+const ReconnectingWebsocket = _ReconnectingWebsocket as unknown as typeof _ReconnectingWebsocket['default'];
+
 export function urlQuery(obj: Record<string, string | number | boolean | undefined>): string {
 	const params = Object.entries(obj)
 		.filter(([, v]) => Array.isArray(v) ? v.length : v !== undefined)
@@ -24,7 +26,7 @@ type StreamEvents = {
  * Misskey stream connection
  */
 export default class Stream extends EventEmitter<StreamEvents> {
-	private stream: ReconnectingWebsocket;
+	private stream: _ReconnectingWebsocket.default;
 	public state: 'initializing' | 'reconnecting' | 'connected' = 'initializing';
 	private sharedConnectionPools: Pool[] = [];
 	private sharedConnections: SharedConnection[] = [];
diff --git a/packages/misskey-js/src/streaming.types.ts b/packages/misskey-js/src/streaming.types.ts
index e2b550ab7a..912ad56f63 100644
--- a/packages/misskey-js/src/streaming.types.ts
+++ b/packages/misskey-js/src/streaming.types.ts
@@ -2,11 +2,13 @@ import {
 	Antenna,
 	DriveFile,
 	DriveFolder,
-	MeDetailed,
 	Note,
 	Notification,
 	Signin,
 	User,
+	UserDetailed,
+	UserDetailedNotMe,
+	UserLite,
 } from './autogen/models.js';
 import {
 	AnnouncementCreated,
@@ -17,6 +19,7 @@ import {
 	QueueStatsLog,
 	ServerStats,
 	ServerStatsLog,
+	ReversiGameDetailed,
 } from './entities.js';
 
 export type Channels = {
@@ -27,16 +30,17 @@ export type Channels = {
 			mention: (payload: Note) => void;
 			reply: (payload: Note) => void;
 			renote: (payload: Note) => void;
-			follow: (payload: User) => void; // 自分が他人をフォローしたとき
-			followed: (payload: User) => void; // 他人が自分をフォローしたとき
-			unfollow: (payload: User) => void; // 自分が他人をフォロー解除したとき
-			meUpdated: (payload: MeDetailed) => void;
+			follow: (payload: UserDetailedNotMe) => void; // 自分が他人をフォローしたとき
+			followed: (payload: UserDetailed | UserLite) => void; // 他人が自分をフォローしたとき
+			unfollow: (payload: UserDetailed) => void; // 自分が他人をフォロー解除したとき
+			meUpdated: (payload: UserDetailed) => void;
 			pageEvent: (payload: PageEvent) => void;
 			urlUploadFinished: (payload: { marker: string; file: DriveFile; }) => void;
 			readAllNotifications: () => void;
 			unreadNotification: (payload: Notification) => void;
 			unreadMention: (payload: Note['id']) => void;
 			readAllUnreadMentions: () => void;
+			notificationFlushed: () => void;
 			unreadSpecifiedNote: (payload: Note['id']) => void;
 			readAllUnreadSpecifiedNotes: () => void;
 			readAllAntennas: () => void;
@@ -53,6 +57,7 @@ export type Channels = {
 			readAntenna: (payload: Antenna) => void;
 			receiveFollowRequest: (payload: User) => void;
 			announcementCreated: (payload: AnnouncementCreated) => void;
+			edited: (payload: Note) => void;
 		};
 		receives: null;
 	};
@@ -60,6 +65,7 @@ export type Channels = {
 		params: {
 			withRenotes?: boolean;
 			withFiles?: boolean;
+			withBots?: boolean;
 		};
 		events: {
 			note: (payload: Note) => void;
@@ -71,6 +77,7 @@ export type Channels = {
 			withRenotes?: boolean;
 			withReplies?: boolean;
 			withFiles?: boolean;
+			withBots?: boolean;
 		};
 		events: {
 			note: (payload: Note) => void;
@@ -82,6 +89,7 @@ export type Channels = {
 			withRenotes?: boolean;
 			withReplies?: boolean;
 			withFiles?: boolean;
+			withBots?: boolean;
 		};
 		events: {
 			note: (payload: Note) => void;
@@ -92,6 +100,7 @@ export type Channels = {
 		params: {
 			withRenotes?: boolean;
 			withFiles?: boolean;
+			withBots?: boolean;
 		};
 		events: {
 			note: (payload: Note) => void;
@@ -99,7 +108,11 @@ export type Channels = {
 		receives: null;
 	};
 	bubbleTimeline: {
-		params: null;
+		params: {
+			withRenotes?: boolean;
+			withFiles?: boolean;
+			withBots?: boolean;
+		};
 		events: {
 			note: (payload: Note) => void;
 		};
@@ -109,6 +122,7 @@ export type Channels = {
 		params: {
 			listId: string;
 			withFiles?: boolean;
+			withRenotes?: boolean;
 		};
 		events: {
 			note: (payload: Note) => void;
@@ -159,7 +173,7 @@ export type Channels = {
 			fileUpdated: (payload: DriveFile) => void;
 			folderCreated: (payload: DriveFolder) => void;
 			folderDeleted: (payload: DriveFolder['id']) => void;
-			folderUpdated: (payload: DriveFile) => void;
+			folderUpdated: (payload: DriveFolder) => void;
 		};
 		receives: null;
 	};
@@ -200,6 +214,32 @@ export type Channels = {
 			}
 		};
 		receives: null;
+	};
+	reversiGame: {
+		params: {
+			gameId: string;
+		};
+		events: {
+			started: (payload: { game: ReversiGameDetailed; }) => void;
+			ended: (payload: { winnerId: User['id'] | null; game: ReversiGameDetailed; }) => void;
+			canceled: (payload: { userId: User['id']; }) => void;
+			changeReadyStates: (payload: { user1: boolean; user2: boolean; }) => void;
+			updateSettings: (payload: { userId: User['id']; key: string; value: any; }) => void;
+			log: (payload: Record<string, any>) => void;
+		};
+		receives: {
+			putStone: {
+				pos: number;
+				id: string;
+			};
+			ready: boolean;
+			cancel: null | Record<string, never>;
+			updateSettings: {
+				key: string;
+				value: any;
+			};
+			claimTimeIsUp: null | Record<string, never>;
+		}
 	}
 };
 
diff --git a/packages/misskey-js/test-d/api.ts b/packages/misskey-js/test-d/api.ts
index 1c2a142e8b..4b72ff4e9d 100644
--- a/packages/misskey-js/test-d/api.ts
+++ b/packages/misskey-js/test-d/api.ts
@@ -1,5 +1,5 @@
 import { expectType } from 'tsd';
-import * as Misskey from '../src';
+import * as Misskey from '../src/index.js';
 
 describe('API', () => {
 	test('success', async () => {
diff --git a/packages/misskey-js/test-d/streaming.ts b/packages/misskey-js/test-d/streaming.ts
index 6b186bd45a..b46b06e4df 100644
--- a/packages/misskey-js/test-d/streaming.ts
+++ b/packages/misskey-js/test-d/streaming.ts
@@ -1,5 +1,5 @@
 import { expectType } from 'tsd';
-import * as Misskey from '../src';
+import * as Misskey from '../src/index.js';
 
 describe('Streaming', () => {
 	test('emit type', async () => {
diff --git a/packages/misskey-js/test/api.ts b/packages/misskey-js/test/api.ts
index 6f9e656fef..fa31d23faa 100644
--- a/packages/misskey-js/test/api.ts
+++ b/packages/misskey-js/test/api.ts
@@ -1,5 +1,5 @@
 import { enableFetchMocks } from 'jest-fetch-mock';
-import { APIClient, isAPIError } from '../src/api';
+import { APIClient, isAPIError } from '../src/api.js';
 
 enableFetchMocks();
 
diff --git a/packages/misskey-js/test/streaming.ts b/packages/misskey-js/test/streaming.ts
index 9f6615a8d8..06b55cd8af 100644
--- a/packages/misskey-js/test/streaming.ts
+++ b/packages/misskey-js/test/streaming.ts
@@ -1,5 +1,5 @@
 import WS from 'jest-websocket-mock';
-import Stream from '../src/streaming';
+import Stream from '../src/streaming.js';
 
 describe('Streaming', () => {
 	test('useChannel', async () => {
diff --git a/packages/misskey-reversi/.eslintignore b/packages/misskey-reversi/.eslintignore
new file mode 100644
index 0000000000..f22128f047
--- /dev/null
+++ b/packages/misskey-reversi/.eslintignore
@@ -0,0 +1,7 @@
+node_modules
+/built
+/coverage
+/.eslintrc.js
+/jest.config.ts
+/test
+/test-d
diff --git a/packages/misskey-reversi/.eslintrc.cjs b/packages/misskey-reversi/.eslintrc.cjs
new file mode 100644
index 0000000000..db37a01098
--- /dev/null
+++ b/packages/misskey-reversi/.eslintrc.cjs
@@ -0,0 +1,10 @@
+module.exports = {
+	root: true,
+	parserOptions: {
+		tsconfigRootDir: __dirname,
+		project: ['./tsconfig.json'],
+	},
+	extends: [
+		'../shared/.eslintrc.js',
+	],
+};
diff --git a/packages/misskey-reversi/build.js b/packages/misskey-reversi/build.js
new file mode 100644
index 0000000000..4744dfaf7b
--- /dev/null
+++ b/packages/misskey-reversi/build.js
@@ -0,0 +1,31 @@
+import { build } from "esbuild";
+import { globSync } from "glob";
+
+const entryPoints = globSync("./src/**/**.{ts,tsx}");
+
+/** @type {import('esbuild').BuildOptions} */
+const options = {
+  entryPoints,
+  minify: true,
+  outdir: "./built/esm",
+  target: "es2022",
+  platform: "browser",
+  format: "esm",
+};
+
+if (process.env.WATCH === "true") {
+  options.watch = {
+    onRebuild(error, result) {
+      if (error) {
+        console.error("watch build failed:", error);
+      } else {
+        console.log("watch build succeeded:", result);
+      }
+    },
+  };
+}
+
+build(options).catch((err) => {
+  process.stderr.write(err.stderr);
+  process.exit(1);
+});
diff --git a/packages/misskey-reversi/package.json b/packages/misskey-reversi/package.json
new file mode 100644
index 0000000000..7bfc890fef
--- /dev/null
+++ b/packages/misskey-reversi/package.json
@@ -0,0 +1,44 @@
+{
+	"type": "module",
+	"name": "misskey-reversi",
+	"version": "0.0.1",
+	"types": "./built/dts/index.d.ts",
+	"exports": {
+		".": {
+			"import": "./built/esm/index.js",
+			"types": "./built/dts/index.d.ts"
+		},
+		"./*": {
+			"import": "./built/esm/*",
+			"types": "./built/dts/*"
+		}
+	},
+	"scripts": {
+		"build": "node ./build.js",
+		"build:tsc": "npm run tsc",
+		"tsc": "npm run tsc-esm && npm run tsc-dts",
+		"tsc-esm": "tsc --outDir built/esm",
+		"tsc-dts": "tsc --outDir built/dts --declaration true --emitDeclarationOnly true --declarationMap true",
+		"watch": "nodemon -w src -e ts,js,cjs,mjs,json --exec \"pnpm run build:tsc\"",
+		"eslint": "eslint . --ext .js,.jsx,.ts,.tsx",
+		"typecheck": "tsc --noEmit",
+		"lint": "pnpm typecheck && pnpm eslint"
+	},
+	"devDependencies": {
+		"@misskey-dev/eslint-plugin": "1.0.0",
+		"@types/node": "20.11.5",
+		"@typescript-eslint/eslint-plugin": "7.1.0",
+		"@typescript-eslint/parser": "7.1.0",
+		"eslint": "8.57.0",
+		"nodemon": "3.0.2",
+		"typescript": "5.3.3"
+	},
+	"files": [
+		"built"
+	],
+	"dependencies": {
+		"crc-32": "1.2.2",
+		"esbuild": "0.19.11",
+		"glob": "10.3.10"
+	}
+}
diff --git a/packages/misskey-reversi/src/game.ts b/packages/misskey-reversi/src/game.ts
new file mode 100644
index 0000000000..4afca9898c
--- /dev/null
+++ b/packages/misskey-reversi/src/game.ts
@@ -0,0 +1,226 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import CRC32 from 'crc-32';
+
+/**
+ * true ... 黒
+ * false ... 白
+ */
+export type Color = boolean;
+const BLACK = true;
+const WHITE = false;
+
+export type MapCell = 'null' | 'empty';
+
+export type Options = {
+	isLlotheo: boolean;
+	canPutEverywhere: boolean;
+	loopedBoard: boolean;
+};
+
+export type Undo = {
+	color: Color;
+	pos: number;
+
+	/**
+	 * 反転した石の位置の配列
+	 */
+	effects: number[];
+
+	turn: Color | null;
+};
+
+export class Game {
+	public map: MapCell[];
+	public mapWidth: number;
+	public mapHeight: number;
+	public board: (Color | null | undefined)[];
+	public turn: Color | null = BLACK;
+	public opts: Options;
+
+	public prevPos = -1;
+	public prevColor: Color | null = null;
+
+	private logs: Undo[] = [];
+
+	constructor(map: string[], opts: Options) {
+		//#region binds
+		this.putStone = this.putStone.bind(this);
+		//#endregion
+
+		//#region Options
+		this.opts = opts;
+		if (this.opts.isLlotheo == null) this.opts.isLlotheo = false;
+		if (this.opts.canPutEverywhere == null) this.opts.canPutEverywhere = false;
+		if (this.opts.loopedBoard == null) this.opts.loopedBoard = false;
+		//#endregion
+
+		//#region Parse map data
+		this.mapWidth = map[0].length;
+		this.mapHeight = map.length;
+		const mapData = map.join('');
+
+		this.board = mapData.split('').map(d => d === '-' ? null : d === 'b' ? BLACK : d === 'w' ? WHITE : undefined);
+
+		this.map = mapData.split('').map(d => d === '-' || d === 'b' || d === 'w' ? 'empty' : 'null');
+		//#endregion
+
+		// ゲームが始まった時点で片方の色の石しかないか、始まった時点で勝敗が決定するようなマップの場合がある
+		if (!this.canPutSomewhere(BLACK)) this.turn = this.canPutSomewhere(WHITE) ? WHITE : null;
+	}
+
+	public get blackCount() {
+		return this.board.filter(x => x === BLACK).length;
+	}
+
+	public get whiteCount() {
+		return this.board.filter(x => x === WHITE).length;
+	}
+
+	public posToXy(pos: number): number[] {
+		const x = pos % this.mapWidth;
+		const y = Math.floor(pos / this.mapWidth);
+		return [x, y];
+	}
+
+	public xyToPos(x: number, y: number): number {
+		return x + (y * this.mapWidth);
+	}
+
+	public putStone(pos: number) {
+		const color = this.turn;
+		if (color == null) return;
+
+		this.prevPos = pos;
+		this.prevColor = color;
+
+		this.board[pos] = color;
+
+		// 反転させられる石を取得
+		const effects = this.effects(color, pos);
+
+		// 反転させる
+		for (const pos of effects) {
+			this.board[pos] = color;
+		}
+
+		const turn = this.turn;
+
+		this.logs.push({
+			color,
+			pos,
+			effects,
+			turn,
+		});
+
+		this.calcTurn();
+	}
+
+	private calcTurn() {
+		// ターン計算
+		this.turn =
+			this.canPutSomewhere(!this.prevColor) ? !this.prevColor :
+			this.canPutSomewhere(this.prevColor!) ? this.prevColor :
+			null;
+	}
+
+	public undo() {
+		const undo = this.logs.pop()!;
+		this.prevColor = undo.color;
+		this.prevPos = undo.pos;
+		this.board[undo.pos] = null;
+		for (const pos of undo.effects) {
+			const color = this.board[pos];
+			this.board[pos] = !color;
+		}
+		this.turn = undo.turn;
+	}
+
+	public mapDataGet(pos: number): MapCell {
+		const [x, y] = this.posToXy(pos);
+		return x < 0 || y < 0 || x >= this.mapWidth || y >= this.mapHeight ? 'null' : this.map[pos];
+	}
+
+	public getPuttablePlaces(color: Color): number[] {
+		return Array.from(this.board.keys()).filter(i => this.canPut(color, i));
+	}
+
+	public canPutSomewhere(color: Color): boolean {
+		return this.getPuttablePlaces(color).length > 0;
+	}
+
+	public canPut(color: Color, pos: number): boolean {
+		return (
+			this.board[pos] !== null ? false : // 既に石が置いてある場所には打てない
+			this.opts.canPutEverywhere ? this.mapDataGet(pos) === 'empty' : // 挟んでなくても置けるモード
+			this.effects(color, pos).length !== 0); // 相手の石を1つでも反転させられるか
+	}
+
+	/**
+	 * 指定のマスに石を置いた時の、反転させられる石を取得します
+	 * @param color 自分の色
+	 * @param initPos 位置
+	 */
+	public effects(color: Color, initPos: number): number[] {
+		const enemyColor = !color;
+
+		const diffVectors: [number, number][] = [
+			[0, -1], // 上
+			[+1, -1], // 右上
+			[+1, 0], // 右
+			[+1, +1], // 右下
+			[0, +1], // 下
+			[-1, +1], // 左下
+			[-1, 0], // 左
+			[-1, -1], // 左上
+		];
+
+		const effectsInLine = ([dx, dy]: [number, number]): number[] => {
+			const nextPos = (x: number, y: number): [number, number] => [x + dx, y + dy];
+
+			const found: number[] = []; // 挟めるかもしれない相手の石を入れておく配列
+			let [x, y] = this.posToXy(initPos);
+			while (true) {
+				[x, y] = nextPos(x, y);
+
+				// 座標が指し示す位置がボード外に出たとき
+				if (this.opts.loopedBoard && this.xyToPos(
+					(x = ((x % this.mapWidth) + this.mapWidth) % this.mapWidth),
+					(y = ((y % this.mapHeight) + this.mapHeight) % this.mapHeight)) === initPos) {
+					// 盤面の境界でループし、自分が石を置く位置に戻ってきたとき、挟めるようにしている (ref: Test4のマップ)
+					return found;
+				} else if (x === -1 || y === -1 || x === this.mapWidth || y === this.mapHeight) return []; // 挟めないことが確定 (盤面外に到達)
+
+				const pos = this.xyToPos(x, y);
+				if (this.mapDataGet(pos) === 'null') return []; // 挟めないことが確定 (配置不可能なマスに到達)
+				const stone = this.board[pos];
+				if (stone === null) return []; // 挟めないことが確定 (石が置かれていないマスに到達)
+				if (stone === enemyColor) found.push(pos); // 挟めるかもしれない (相手の石を発見)
+				if (stone === color) return found; // 挟めることが確定 (対となる自分の石を発見)
+			}
+		};
+
+		return ([] as number[]).concat(...diffVectors.map(effectsInLine));
+	}
+
+	public calcCrc32(): number {
+		return CRC32.str(JSON.stringify({
+			board: this.board,
+			turn: this.turn,
+		}));
+	}
+
+	public get isEnded(): boolean {
+		return this.turn === null;
+	}
+
+	public get winner(): Color | null {
+		return this.isEnded ?
+			this.blackCount === this.whiteCount ? null :
+			this.opts.isLlotheo === this.blackCount > this.whiteCount ? WHITE : BLACK :
+			undefined as never;
+	}
+}
diff --git a/packages/misskey-reversi/src/index.ts b/packages/misskey-reversi/src/index.ts
new file mode 100644
index 0000000000..cfd27d48cb
--- /dev/null
+++ b/packages/misskey-reversi/src/index.ts
@@ -0,0 +1,8 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export { Game } from './game.js';
+export * as Serializer from './serializer.js';
+export * as maps from './maps.js';
diff --git a/packages/misskey-reversi/src/maps.ts b/packages/misskey-reversi/src/maps.ts
new file mode 100644
index 0000000000..29ea3591c2
--- /dev/null
+++ b/packages/misskey-reversi/src/maps.ts
@@ -0,0 +1,697 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+/**
+ * 組み込みマップ定義
+ *
+ * データ値:
+ * (スペース) ... マス無し
+ * - ... マス
+ * b ... 初期配置される黒石
+ * w ... 初期配置される白石
+ */
+
+export type Map = {
+	name?: string;
+	category?: string;
+	author?: string;
+	data: string[];
+};
+
+export const fourfour: Map = {
+	name: '4x4',
+	category: '4x4',
+	data: [
+		'----',
+		'-wb-',
+		'-bw-',
+		'----',
+	],
+};
+
+export const sixsix: Map = {
+	name: '6x6',
+	category: '6x6',
+	data: [
+		'------',
+		'------',
+		'--wb--',
+		'--bw--',
+		'------',
+		'------',
+	],
+};
+
+export const roundedSixsix: Map = {
+	name: '6x6 rounded',
+	category: '6x6',
+	author: 'syuilo',
+	data: [
+		' ---- ',
+		'------',
+		'--wb--',
+		'--bw--',
+		'------',
+		' ---- ',
+	],
+};
+
+export const roundedSixsix2: Map = {
+	name: '6x6 rounded 2',
+	category: '6x6',
+	author: 'syuilo',
+	data: [
+		'  --  ',
+		' ---- ',
+		'--wb--',
+		'--bw--',
+		' ---- ',
+		'  --  ',
+	],
+};
+
+export const eighteight: Map = {
+	name: '8x8',
+	category: '8x8',
+	data: [
+		'--------',
+		'--------',
+		'--------',
+		'---wb---',
+		'---bw---',
+		'--------',
+		'--------',
+		'--------',
+	],
+};
+
+export const eighteightH28: Map = {
+	name: '8x8 handicap 28',
+	category: '8x8',
+	data: [
+		'bbbbbbbb',
+		'b------b',
+		'b------b',
+		'b--wb--b',
+		'b--bw--b',
+		'b------b',
+		'b------b',
+		'bbbbbbbb',
+	],
+};
+
+export const roundedEighteight: Map = {
+	name: '8x8 rounded',
+	category: '8x8',
+	author: 'syuilo',
+	data: [
+		' ------ ',
+		'--------',
+		'--------',
+		'---wb---',
+		'---bw---',
+		'--------',
+		'--------',
+		' ------ ',
+	],
+};
+
+export const roundedEighteight2: Map = {
+	name: '8x8 rounded 2',
+	category: '8x8',
+	author: 'syuilo',
+	data: [
+		'  ----  ',
+		' ------ ',
+		'--------',
+		'---wb---',
+		'---bw---',
+		'--------',
+		' ------ ',
+		'  ----  ',
+	],
+};
+
+export const roundedEighteight3: Map = {
+	name: '8x8 rounded 3',
+	category: '8x8',
+	author: 'syuilo',
+	data: [
+		'   --   ',
+		'  ----  ',
+		' ------ ',
+		'---wb---',
+		'---bw---',
+		' ------ ',
+		'  ----  ',
+		'   --   ',
+	],
+};
+
+export const eighteightWithNotch: Map = {
+	name: '8x8 with notch',
+	category: '8x8',
+	author: 'syuilo',
+	data: [
+		'---  ---',
+		'--------',
+		'--------',
+		' --wb-- ',
+		' --bw-- ',
+		'--------',
+		'--------',
+		'---  ---',
+	],
+};
+
+export const eighteightWithSomeHoles: Map = {
+	name: '8x8 with some holes',
+	category: '8x8',
+	author: 'syuilo',
+	data: [
+		'--- ----',
+		'----- --',
+		'-- -----',
+		'---wb---',
+		'---bw- -',
+		' -------',
+		'--- ----',
+		'--------',
+	],
+};
+
+export const circle: Map = {
+	name: 'Circle',
+	category: '8x8',
+	author: 'syuilo',
+	data: [
+		'   --   ',
+		' ------ ',
+		' ------ ',
+		'---wb---',
+		'---bw---',
+		' ------ ',
+		' ------ ',
+		'   --   ',
+	],
+};
+
+export const smile: Map = {
+	name: 'Smile',
+	category: '8x8',
+	author: 'syuilo',
+	data: [
+		' ------ ',
+		'--------',
+		'-- -- --',
+		'---wb---',
+		'-- bw --',
+		'---  ---',
+		'--------',
+		' ------ ',
+	],
+};
+
+export const window: Map = {
+	name: 'Window',
+	category: '8x8',
+	author: 'syuilo',
+	data: [
+		'--------',
+		'-  --  -',
+		'-  --  -',
+		'---wb---',
+		'---bw---',
+		'-  --  -',
+		'-  --  -',
+		'--------',
+	],
+};
+
+export const reserved: Map = {
+	name: 'Reserved',
+	category: '8x8',
+	author: 'Aya',
+	data: [
+		'w------b',
+		'--------',
+		'--------',
+		'---wb---',
+		'---bw---',
+		'--------',
+		'--------',
+		'b------w',
+	],
+};
+
+export const x: Map = {
+	name: 'X',
+	category: '8x8',
+	author: 'Aya',
+	data: [
+		'w------b',
+		'-w----b-',
+		'--w--b--',
+		'---wb---',
+		'---bw---',
+		'--b--w--',
+		'-b----w-',
+		'b------w',
+	],
+};
+
+export const parallel: Map = {
+	name: 'Parallel',
+	category: '8x8',
+	author: 'Aya',
+	data: [
+		'--------',
+		'--------',
+		'--------',
+		'---bb---',
+		'---ww---',
+		'--------',
+		'--------',
+		'--------',
+	],
+};
+
+export const lackOfBlack: Map = {
+	name: 'Lack of Black',
+	category: '8x8',
+	data: [
+		'--------',
+		'--------',
+		'--------',
+		'---w----',
+		'---bw---',
+		'--------',
+		'--------',
+		'--------',
+	],
+};
+
+export const squareParty: Map = {
+	name: 'Square Party',
+	category: '8x8',
+	author: 'syuilo',
+	data: [
+		'--------',
+		'-wwwbbb-',
+		'-w-wb-b-',
+		'-wwwbbb-',
+		'-bbbwww-',
+		'-b-bw-w-',
+		'-bbbwww-',
+		'--------',
+	],
+};
+
+export const minesweeper: Map = {
+	name: 'Minesweeper',
+	category: '8x8',
+	author: 'syuilo',
+	data: [
+		'b-b--w-w',
+		'-w-wb-b-',
+		'w-b--w-b',
+		'-b-wb-w-',
+		'-w-bw-b-',
+		'b-w--b-w',
+		'-b-bw-w-',
+		'w-w--b-b',
+	],
+};
+
+export const tenthtenth: Map = {
+	name: '10x10',
+	category: '10x10',
+	data: [
+		'----------',
+		'----------',
+		'----------',
+		'----------',
+		'----wb----',
+		'----bw----',
+		'----------',
+		'----------',
+		'----------',
+		'----------',
+	],
+};
+
+export const hole: Map = {
+	name: 'The Hole',
+	category: '10x10',
+	author: 'syuilo',
+	data: [
+		'----------',
+		'----------',
+		'--wb--wb--',
+		'--bw--bw--',
+		'----  ----',
+		'----  ----',
+		'--wb--wb--',
+		'--bw--bw--',
+		'----------',
+		'----------',
+	],
+};
+
+export const grid: Map = {
+	name: 'Grid',
+	category: '10x10',
+	author: 'syuilo',
+	data: [
+		'----------',
+		'- - -- - -',
+		'----------',
+		'- - -- - -',
+		'----wb----',
+		'----bw----',
+		'- - -- - -',
+		'----------',
+		'- - -- - -',
+		'----------',
+	],
+};
+
+export const cross: Map = {
+	name: 'Cross',
+	category: '10x10',
+	author: 'Aya',
+	data: [
+		'   ----   ',
+		'   ----   ',
+		'   ----   ',
+		'----------',
+		'----wb----',
+		'----bw----',
+		'----------',
+		'   ----   ',
+		'   ----   ',
+		'   ----   ',
+	],
+};
+
+export const charX: Map = {
+	name: 'Char X',
+	category: '10x10',
+	author: 'syuilo',
+	data: [
+		'---    ---',
+		'----  ----',
+		'----------',
+		' -------- ',
+		'  --wb--  ',
+		'  --bw--  ',
+		' -------- ',
+		'----------',
+		'----  ----',
+		'---    ---',
+	],
+};
+
+export const charY: Map = {
+	name: 'Char Y',
+	category: '10x10',
+	author: 'syuilo',
+	data: [
+		'---    ---',
+		'----  ----',
+		'----------',
+		' -------- ',
+		'  --wb--  ',
+		'  --bw--  ',
+		'  ------  ',
+		'  ------  ',
+		'  ------  ',
+		'  ------  ',
+	],
+};
+
+export const walls: Map = {
+	name: 'Walls',
+	category: '10x10',
+	author: 'Aya',
+	data: [
+		' bbbbbbbb ',
+		'w--------w',
+		'w--------w',
+		'w--------w',
+		'w---wb---w',
+		'w---bw---w',
+		'w--------w',
+		'w--------w',
+		'w--------w',
+		' bbbbbbbb ',
+	],
+};
+
+export const cpu: Map = {
+	name: 'CPU',
+	category: '10x10',
+	author: 'syuilo',
+	data: [
+		' b b  b b ',
+		'w--------w',
+		' -------- ',
+		'w--------w',
+		' ---wb--- ',
+		' ---bw--- ',
+		'w--------w',
+		' -------- ',
+		'w--------w',
+		' b b  b b ',
+	],
+};
+
+export const checker: Map = {
+	name: 'Checker',
+	category: '10x10',
+	author: 'Aya',
+	data: [
+		'----------',
+		'----------',
+		'----------',
+		'---wbwb---',
+		'---bwbw---',
+		'---wbwb---',
+		'---bwbw---',
+		'----------',
+		'----------',
+		'----------',
+	],
+};
+
+export const japaneseCurry: Map = {
+	name: 'Japanese curry',
+	category: '10x10',
+	author: 'syuilo',
+	data: [
+		'w-b-b-b-b-',
+		'-w-b-b-b-b',
+		'w-w-b-b-b-',
+		'-w-w-b-b-b',
+		'w-w-wwb-b-',
+		'-w-wbb-b-b',
+		'w-w-w-b-b-',
+		'-w-w-w-b-b',
+		'w-w-w-w-b-',
+		'-w-w-w-w-b',
+	],
+};
+
+export const mosaic: Map = {
+	name: 'Mosaic',
+	category: '10x10',
+	author: 'syuilo',
+	data: [
+		'- - - - - ',
+		' - - - - -',
+		'- - - - - ',
+		' - w w - -',
+		'- - b b - ',
+		' - w w - -',
+		'- - b b - ',
+		' - - - - -',
+		'- - - - - ',
+		' - - - - -',
+	],
+};
+
+export const arena: Map = {
+	name: 'Arena',
+	category: '10x10',
+	author: 'syuilo',
+	data: [
+		'- - -- - -',
+		' - -  - - ',
+		'- ------ -',
+		' -------- ',
+		'- --wb-- -',
+		'- --bw-- -',
+		' -------- ',
+		'- ------ -',
+		' - -  - - ',
+		'- - -- - -',
+	],
+};
+
+export const reactor: Map = {
+	name: 'Reactor',
+	category: '10x10',
+	author: 'syuilo',
+	data: [
+		'-w------b-',
+		'b- -  - -w',
+		'- --wb-- -',
+		'---b  w---',
+		'- b wb w -',
+		'- w bw b -',
+		'---w  b---',
+		'- --bw-- -',
+		'w- -  - -b',
+		'-b------w-',
+	],
+};
+
+export const sixeight: Map = {
+	name: '6x8',
+	category: 'Special',
+	data: [
+		'------',
+		'------',
+		'------',
+		'--wb--',
+		'--bw--',
+		'------',
+		'------',
+		'------',
+	],
+};
+
+export const spark: Map = {
+	name: 'Spark',
+	category: 'Special',
+	author: 'syuilo',
+	data: [
+		' -      - ',
+		'----------',
+		' -------- ',
+		' -------- ',
+		' ---wb--- ',
+		' ---bw--- ',
+		' -------- ',
+		' -------- ',
+		'----------',
+		' -      - ',
+	],
+};
+
+export const islands: Map = {
+	name: 'Islands',
+	category: 'Special',
+	author: 'syuilo',
+	data: [
+		'--------  ',
+		'---wb---  ',
+		'---bw---  ',
+		'--------  ',
+		'  -    -  ',
+		'  -    -  ',
+		'  --------',
+		'  --------',
+		'  --------',
+		'  --------',
+	],
+};
+
+export const galaxy: Map = {
+	name: 'Galaxy',
+	category: 'Special',
+	author: 'syuilo',
+	data: [
+		'   ------   ',
+		'  --www---  ',
+		' ------w--- ',
+		'---bbb--w---',
+		'--b---b-w-b-',
+		'-b--wwb-w-b-',
+		'-b-w-bww--b-',
+		'-b-w-b---b--',
+		'---w--bbb---',
+		' ---w------ ',
+		'  ---www--  ',
+		'   ------   ',
+	],
+};
+
+export const triangle: Map = {
+	name: 'Triangle',
+	category: 'Special',
+	author: 'syuilo',
+	data: [
+		'    --    ',
+		'    --    ',
+		'   ----   ',
+		'   ----   ',
+		'  --wb--  ',
+		'  --bw--  ',
+		' -------- ',
+		' -------- ',
+		'----------',
+		'----------',
+	],
+};
+
+export const iphonex: Map = {
+	name: 'iPhone X',
+	category: 'Special',
+	author: 'syuilo',
+	data: [
+		' --  -- ',
+		'--------',
+		'--------',
+		'--------',
+		'--------',
+		'---wb---',
+		'---bw---',
+		'--------',
+		'--------',
+		'--------',
+		'--------',
+		' ------ ',
+	],
+};
+
+export const dealWithIt: Map = {
+	name: 'Deal with it!',
+	category: 'Special',
+	author: 'syuilo',
+	data: [
+		'------------',
+		'--w-b-------',
+		' --b-w------',
+		'  --w-b---- ',
+		'   -------  ',
+	],
+};
+
+export const twoBoard: Map = {
+	name: 'Two board',
+	category: 'Special',
+	author: 'Aya',
+	data: [
+		'-------- --------',
+		'-------- --------',
+		'-------- --------',
+		'---wb--- ---wb---',
+		'---bw--- ---bw---',
+		'-------- --------',
+		'-------- --------',
+		'-------- --------',
+	],
+};
diff --git a/packages/misskey-reversi/src/serializer.ts b/packages/misskey-reversi/src/serializer.ts
new file mode 100644
index 0000000000..aa5987a1e5
--- /dev/null
+++ b/packages/misskey-reversi/src/serializer.ts
@@ -0,0 +1,98 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Game } from './game.js';
+
+export type Log = {
+	time: number;
+	player: boolean;
+	operation: 'put';
+	pos: number;
+};
+
+export type SerializedLog = number[];
+
+export function serializeLogs(logs: Log[]) {
+	const _logs: number[][] = [];
+
+	for (let i = 0; i < logs.length; i++) {
+		const log = logs[i];
+		const timeDelta = i === 0 ? log.time : log.time - logs[i - 1].time;
+
+		switch (log.operation) {
+			case 'put':
+				_logs.push([timeDelta, log.player ? 1 : 0, 0, log.pos]);
+				break;
+			//case 'surrender':
+			//	_logs.push([timeDelta, log.player, 1]);
+			//	break;
+		}
+	}
+
+	return _logs;
+}
+
+export function deserializeLogs(logs: SerializedLog[]) {
+	const _logs: Log[] = [];
+
+	let time = 0;
+
+	for (const log of logs) {
+		const timeDelta = log[0];
+		time += timeDelta;
+
+		const player = log[1];
+		const operation = log[2];
+
+		switch (operation) {
+			case 0:
+				_logs.push({
+					time,
+					player: player === 1,
+					operation: 'put',
+					pos: log[3],
+				});
+				break;
+			//case 1:
+			//	_logs.push({
+			//		time,
+			//		player: player === 1,
+			//		operation: 'surrender',
+			//	});
+			//	break;
+		}
+	}
+
+	return _logs;
+}
+
+export function restoreGame(env: {
+	map: string[];
+	isLlotheo: boolean;
+	canPutEverywhere: boolean;
+	loopedBoard: boolean;
+	logs: SerializedLog[];
+}) {
+	const logs = deserializeLogs(env.logs);
+
+	const game = new Game(env.map, {
+		isLlotheo: env.isLlotheo,
+		canPutEverywhere: env.canPutEverywhere,
+		loopedBoard: env.loopedBoard,
+	});
+
+	for (const log of logs) {
+		switch (log.operation) {
+			case 'put':
+				game.putStone(log.pos);
+				break;
+			//case 'surrender':
+			//	game.surrender(log.player);
+			//	break;
+		}
+	}
+
+	return game;
+}
diff --git a/packages/misskey-reversi/tsconfig.json b/packages/misskey-reversi/tsconfig.json
new file mode 100644
index 0000000000..f56b65e868
--- /dev/null
+++ b/packages/misskey-reversi/tsconfig.json
@@ -0,0 +1,33 @@
+{
+	"$schema": "https://json.schemastore.org/tsconfig",
+	"compilerOptions": {
+		"target": "ES2022",
+		"module": "nodenext",
+		"moduleResolution": "nodenext",
+		"declaration": true,
+		"declarationMap": true,
+		"sourceMap": true,
+		"outDir": "./built/",
+		"removeComments": true,
+		"strict": true,
+		"strictFunctionTypes": true,
+		"strictNullChecks": true,
+		"experimentalDecorators": true,
+		"noImplicitReturns": true,
+		"esModuleInterop": true,
+		"typeRoots": [
+			"./node_modules/@types"
+		],
+		"lib": [
+			"esnext",
+			"dom"
+		]
+	},
+	"include": [
+		"src/**/*"
+	],
+	"exclude": [
+		"node_modules",
+		"test/**/*"
+	]
+}
diff --git a/packages/shared/.eslintrc.js b/packages/shared/.eslintrc.js
index b3c7626a39..58247877ae 100644
--- a/packages/shared/.eslintrc.js
+++ b/packages/shared/.eslintrc.js
@@ -1,118 +1,7 @@
 module.exports = {
 	root: true,
-	parser: '@typescript-eslint/parser',
-	plugins: [
-		'@typescript-eslint',
-		'import'
-	],
+	ignorePatterns: ['**/.eslintrc.cjs'],
 	extends: [
-		'eslint:recommended',
-		'plugin:@typescript-eslint/recommended',
-		'plugin:import/recommended',
-		'plugin:import/typescript'
+		'plugin:@misskey-dev/recommended',
 	],
-	rules: {
-		'indent': ['warn', 'tab', {
-			'SwitchCase': 1,
-			'MemberExpression': 1,
-			'flatTernaryExpressions': true,
-			'ArrayExpression': 'first',
-			'ObjectExpression': 'first',
-		}],
-		'eol-last': ['error', 'always'],
-		'semi': ['error', 'always'],
-		'semi-spacing': ['error', { 'before': false, 'after': true }],
-		'quotes': ['warn', 'single'],
-		'comma-dangle': ['warn', 'always-multiline'],
-		'comma-spacing': ['error', { 'before': false, 'after': true }],
-		'array-bracket-spacing': ['error', 'never'],
-		'keyword-spacing': ['error', {
-			'before': true,
-			'after': true,
-		}],
-		'key-spacing': ['error', {
-			'beforeColon': false,
-			'afterColon': true,
-		}],
-		'arrow-spacing': ['error', {
-			'before': true,
-			'after': true,
-		}],
-		'brace-style': ['error', '1tbs', {
-			'allowSingleLine': true,
-		}],
-		'padded-blocks': ['error', 'never'],
-		/* TODO: path aliasを使わないとwarnする
-		'no-restricted-imports': ['warn', {
-			'patterns': [
-			]
-		}],
-		*/
-		'eqeqeq': ['error', 'always', { 'null': 'ignore' }],
-		'no-multi-spaces': ['error'],
-		'no-var': ['error'],
-		'prefer-arrow-callback': ['error'],
-		'no-throw-literal': ['error'],
-		'no-param-reassign': ['warn'],
-		'no-constant-condition': ['warn'],
-		'no-empty-pattern': ['warn'],
-		'no-async-promise-executor': ['off'],
-		'no-useless-escape': ['off'],
-		'no-multiple-empty-lines': ['error', { 'max': 1 }],
-		'no-control-regex': ['warn'],
-		'no-empty': ['warn'],
-		'no-inner-declarations': ['off'],
-		'no-sparse-arrays': ['off'],
-		'nonblock-statement-body-position': ['error', 'beside'],
-		'object-curly-spacing': ['error', 'always'],
-		'space-infix-ops': ['error'],
-		'space-before-blocks': ['error', 'always'],
-		'padding-line-between-statements': [
-			'error',
-			{ 'blankLine': 'always', 'prev': 'function', 'next': '*' },
-			{ 'blankLine': 'always', 'prev': '*', 'next': 'function' },
-		],
-		"lines-between-class-members": "off",
-		/* typescript-eslint では enforce に対応してないっぽい
-		'@typescript-eslint/lines-between-class-members': ['error', {
-			enforce: [{
-				blankLine: 'always',
-				prev: 'method',
-				next: '*',
-			}]
-		}],
-		*/
-		'@typescript-eslint/func-call-spacing': ['error', 'never'],
-		'@typescript-eslint/no-explicit-any': ['warn'],
-		'@typescript-eslint/no-unused-vars': ['warn'],
-		'@typescript-eslint/no-unnecessary-condition': ['warn'],
-		'@typescript-eslint/no-var-requires': ['warn'],
-		'@typescript-eslint/no-inferrable-types': ['warn'],
-		'@typescript-eslint/no-empty-function': ['off'],
-		'@typescript-eslint/no-non-null-assertion': ['warn'],
-		'@typescript-eslint/explicit-function-return-type': ['off'],
-		'@typescript-eslint/no-misused-promises': ['error', {
-			'checksVoidReturn': false,
-		}],
-		'@typescript-eslint/consistent-type-imports': 'off',
-		'@typescript-eslint/prefer-nullish-coalescing': [
-			'warn',
-		],
-		'@typescript-eslint/naming-convention': [
-			'error',
-			{
-				"selector": "typeLike",
-				"format": ["PascalCase"]
-			},
-			{
-				"selector": "typeParameter",
-				"format": []
-			}
-		],
-		'import/no-unresolved': ['off'],
-		'import/no-default-export': ['warn'],
-		'import/order': ['warn', {
-			'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
-		}]
-	},
 };
diff --git a/packages/sw/build.js b/packages/sw/build.js
index ee190079b9..eb9a944f47 100644
--- a/packages/sw/build.js
+++ b/packages/sw/build.js
@@ -1,7 +1,7 @@
 // @ts-check
 
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/sw/package.json b/packages/sw/package.json
index c48efd6ea6..bac0cc1ff5 100644
--- a/packages/sw/package.json
+++ b/packages/sw/package.json
@@ -9,16 +9,17 @@
 		"lint": "pnpm typecheck && pnpm eslint"
 	},
 	"dependencies": {
-		"esbuild": "0.19.9",
+		"esbuild": "0.19.11",
 		"idb-keyval": "6.2.1",
 		"misskey-js": "workspace:*"
 	},
 	"devDependencies": {
-		"@typescript-eslint/parser": "6.14.0",
+		"@misskey-dev/eslint-plugin": "1.0.0",
+		"@typescript-eslint/parser": "7.1.0",
 		"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67",
-		"eslint": "8.56.0",
+		"eslint": "8.57.0",
 		"eslint-plugin-import": "2.29.1",
-		"nodemon": "3.0.2",
+		"nodemon": "3.1.0",
 		"typescript": "5.3.3"
 	},
 	"type": "module"
diff --git a/packages/sw/src/@types/global.d.ts b/packages/sw/src/@types/global.d.ts
index 80d7b02fe5..bf63810e6d 100644
--- a/packages/sw/src/@types/global.d.ts
+++ b/packages/sw/src/@types/global.d.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/sw/src/scripts/create-notification.ts b/packages/sw/src/scripts/create-notification.ts
index 2e1ab719ac..32b12f4b4f 100644
--- a/packages/sw/src/scripts/create-notification.ts
+++ b/packages/sw/src/scripts/create-notification.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -231,7 +231,15 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif
 						badge: iconUrl('bell'),
 						data,
 					}];
-		
+
+				case 'edited':
+					return [t('_notification.edited', { name: getUserName(data.body.user) }), {
+						body: data.body.note.text ?? '',
+						icon: data.body.user.avatarUrl,
+						badge: iconUrl('messages'),
+						data,
+					}];
+
 				default:
 					return null;
 			}
diff --git a/packages/sw/src/scripts/get-account-from-id.ts b/packages/sw/src/scripts/get-account-from-id.ts
index bbd306374e..19bfe052ee 100644
--- a/packages/sw/src/scripts/get-account-from-id.ts
+++ b/packages/sw/src/scripts/get-account-from-id.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/sw/src/scripts/get-user-name.ts b/packages/sw/src/scripts/get-user-name.ts
index 2acdb91159..6472a6c4e6 100644
--- a/packages/sw/src/scripts/get-user-name.ts
+++ b/packages/sw/src/scripts/get-user-name.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/sw/src/scripts/i18n.ts b/packages/sw/src/scripts/i18n.ts
index 2c7feccc44..77b955dbe8 100644
--- a/packages/sw/src/scripts/i18n.ts
+++ b/packages/sw/src/scripts/i18n.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/sw/src/scripts/lang.ts b/packages/sw/src/scripts/lang.ts
index a2b99ff6b1..6fccedd746 100644
--- a/packages/sw/src/scripts/lang.ts
+++ b/packages/sw/src/scripts/lang.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/sw/src/scripts/login-id.ts b/packages/sw/src/scripts/login-id.ts
index 7002485925..084b52d1e4 100644
--- a/packages/sw/src/scripts/login-id.ts
+++ b/packages/sw/src/scripts/login-id.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/sw/src/scripts/operations.ts b/packages/sw/src/scripts/operations.ts
index 0cbf4c7953..24eea06231 100644
--- a/packages/sw/src/scripts/operations.ts
+++ b/packages/sw/src/scripts/operations.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/sw/src/scripts/twemoji-base.ts b/packages/sw/src/scripts/twemoji-base.ts
index 32c5172131..e5b0603660 100644
--- a/packages/sw/src/scripts/twemoji-base.ts
+++ b/packages/sw/src/scripts/twemoji-base.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/packages/sw/src/sw.ts b/packages/sw/src/sw.ts
index b79fd8ce7a..c38419eadc 100644
--- a/packages/sw/src/sw.ts
+++ b/packages/sw/src/sw.ts
@@ -1,11 +1,12 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import { get } from 'idb-keyval';
 import * as Misskey from 'misskey-js';
 import type { PushNotificationDataMap } from '@/types.js';
+import type { I18n, Locale } from '@/scripts/i18n.js';
 import { createEmptyNotification, createNotification } from '@/scripts/create-notification.js';
 import { swLang } from '@/scripts/lang.js';
 import * as swos from '@/scripts/operations.js';
@@ -26,8 +27,15 @@ globalThis.addEventListener('activate', ev => {
 	);
 });
 
-function offlineContentHTML(): string {
-	return `<!doctype html>Offline. Service Worker @${_VERSION_} <button onclick="location.reload()">reload</button>`;
+async function offlineContentHTML() {
+	const i18n = await (swLang.i18n ?? swLang.fetchLocale()) as Partial<I18n<Locale>>;
+	const messages = {
+		title: i18n.ts?._offlineScreen?.title ?? 'Offline - Could not connect to server',
+		header: i18n.ts?._offlineScreen?.header ?? 'Could not connect to server',
+		reload: i18n.ts?.reload ?? 'Reload',
+	};
+
+	return `<!DOCTYPE html><html lang="ja"><head><meta charset="UTF-8"><meta content="width=device-width,initial-scale=1"name="viewport"><title>${messages.title}</title><style>body{background-color:#0c1210;color:#dee7e4;font-family:Hiragino Maru Gothic Pro,BIZ UDGothic,Roboto,HelveticaNeue,Arial,sans-serif;line-height:1.35;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:100vh;margin:0;padding:24px;box-sizing:border-box}.icon{max-width:120px;width:100%;height:auto;margin-bottom:20px;}.message{text-align:center;font-size:20px;font-weight:700;margin-bottom:20px}.version{text-align:center;font-size:90%;margin-bottom:20px}button{padding:7px 14px;min-width:100px;font-weight:700;font-family:Hiragino Maru Gothic Pro,BIZ UDGothic,Roboto,HelveticaNeue,Arial,sans-serif;line-height:1.35;border-radius:99rem;background-color:#b4e900;color:#192320;border:none;cursor:pointer;-webkit-tap-highlight-color:transparent}button:hover{background-color:#c6ff03}</style></head><body><svg class="icon"fill="none"height="24"stroke="currentColor"stroke-linecap="round"stroke-linejoin="round"stroke-width="2"viewBox="0 0 24 24"width="24"xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z"fill="none"stroke="none"/><path d="M9.58 5.548c.24 -.11 .492 -.207 .752 -.286c1.88 -.572 3.956 -.193 5.444 1c1.488 1.19 2.162 3.007 1.77 4.769h.99c1.913 0 3.464 1.56 3.464 3.486c0 .957 -.383 1.824 -1.003 2.454m-2.997 1.033h-11.343c-2.572 -.004 -4.657 -2.011 -4.657 -4.487c0 -2.475 2.085 -4.482 4.657 -4.482c.13 -.582 .37 -1.128 .7 -1.62"/><path d="M3 3l18 18"/></svg><div class="message">${messages.header}</div><div class="version">v${_VERSION_}</div><button onclick="reloadPage()">${messages.reload}</button><script>function reloadPage(){location.reload(!0)}</script></body></html>`;
 }
 
 globalThis.addEventListener('fetch', ev => {
@@ -43,8 +51,9 @@ globalThis.addEventListener('fetch', ev => {
 	if (!isHTMLRequest) return;
 	ev.respondWith(
 		fetch(ev.request)
-			.catch(() => {
-				return new Response(offlineContentHTML(), {
+			.catch(async () => {
+				const html = await offlineContentHTML();
+				return new Response(html, {
 					status: 200,
 					headers: {
 						'content-type': 'text/html',
@@ -124,6 +133,9 @@ globalThis.addEventListener('notificationclick', (ev: ServiceWorkerGlobalScopeEv
 					case 'showFollowRequests':
 						client = await swos.openClient('push', '/my/follow-requests', loginId);
 						break;
+					case 'edited':
+						if ('note' in data.body) client = await swos.openPost({ reply: data.body.note }, loginId);
+						break;
 					default:
 						switch (data.body.type) {
 							case 'receiveFollowRequest':
diff --git a/packages/sw/src/types.ts b/packages/sw/src/types.ts
index c63e489c71..fac3e707d8 100644
--- a/packages/sw/src/types.ts
+++ b/packages/sw/src/types.ts
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index badbc43294..b54383a88f 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -13,39 +13,48 @@ importers:
   .:
     dependencies:
       cssnano:
-        specifier: 6.0.2
-        version: 6.0.2(postcss@8.4.32)
+        specifier: 6.0.5
+        version: 6.0.5(postcss@8.4.35)
       execa:
         specifier: 8.0.1
         version: 8.0.1
+      fast-glob:
+        specifier: 3.3.2
+        version: 3.3.2
+      ignore-walk:
+        specifier: 6.0.4
+        version: 6.0.4
       js-yaml:
         specifier: 4.1.0
         version: 4.1.0
       postcss:
-        specifier: 8.4.32
-        version: 8.4.32
+        specifier: 8.4.35
+        version: 8.4.35
+      tar:
+        specifier: 6.2.0
+        version: 6.2.0
       terser:
-        specifier: 5.26.0
-        version: 5.26.0
+        specifier: 5.28.1
+        version: 5.28.1
       typescript:
         specifier: 5.3.3
         version: 5.3.3
     devDependencies:
       '@typescript-eslint/eslint-plugin':
-        specifier: 6.14.0
-        version: 6.14.0(@typescript-eslint/parser@6.14.0)(eslint@8.56.0)(typescript@5.3.3)
+        specifier: 7.1.0
+        version: 7.1.0(@typescript-eslint/parser@7.1.0)(eslint@8.57.0)(typescript@5.3.3)
       '@typescript-eslint/parser':
-        specifier: 6.14.0
-        version: 6.14.0(eslint@8.56.0)(typescript@5.3.3)
+        specifier: 7.1.0
+        version: 7.1.0(eslint@8.57.0)(typescript@5.3.3)
       cross-env:
         specifier: 7.0.3
         version: 7.0.3
       cypress:
-        specifier: 13.6.1
-        version: 13.6.1
+        specifier: 13.6.6
+        version: 13.6.6
       eslint:
-        specifier: 8.56.0
-        version: 8.56.0
+        specifier: 8.57.0
+        version: 8.57.0
       ncp:
         specifier: 2.0.0
         version: 2.0.0
@@ -62,14 +71,14 @@ importers:
         specifier: 3.412.0
         version: 3.412.0(@aws-sdk/client-s3@3.412.0)
       '@bull-board/api':
-        specifier: 5.10.2
-        version: 5.10.2(@bull-board/ui@5.10.2)
+        specifier: 5.14.2
+        version: 5.14.2(@bull-board/ui@5.14.2)
       '@bull-board/fastify':
-        specifier: 5.10.2
-        version: 5.10.2
+        specifier: 5.14.2
+        version: 5.14.2
       '@bull-board/ui':
-        specifier: 5.10.2
-        version: 5.10.2
+        specifier: 5.14.2
+        version: 5.14.2
       '@discordapp/twemoji':
         specifier: 15.0.2
         version: 15.0.2
@@ -77,8 +86,8 @@ importers:
         specifier: 4.3.0
         version: 4.3.0
       '@fastify/cookie':
-        specifier: 9.2.0
-        version: 9.2.0
+        specifier: 9.3.1
+        version: 9.3.1
       '@fastify/cors':
         specifier: 8.5.0
         version: 8.5.0
@@ -89,32 +98,35 @@ importers:
         specifier: 9.3.0
         version: 9.3.0(bufferutil@4.0.7)(utf-8-validate@6.0.3)
       '@fastify/multipart':
-        specifier: 8.0.0
-        version: 8.0.0
+        specifier: 8.1.0
+        version: 8.1.0
       '@fastify/static':
         specifier: 6.12.0
         version: 6.12.0
       '@fastify/view':
         specifier: 8.2.0
         version: 8.2.0
+      '@misskey-dev/sharp-read-bmp':
+        specifier: 1.2.0
+        version: 1.2.0
+      '@misskey-dev/summaly':
+        specifier: 5.0.3
+        version: 5.0.3
       '@nestjs/common':
-        specifier: 10.2.10
-        version: 10.2.10(reflect-metadata@0.1.14)(rxjs@7.8.1)
+        specifier: 10.3.3
+        version: 10.3.3(reflect-metadata@0.2.1)(rxjs@7.8.1)
       '@nestjs/core':
-        specifier: 10.2.10
-        version: 10.2.10(@nestjs/common@10.2.10)(reflect-metadata@0.1.14)(rxjs@7.8.1)
+        specifier: 10.3.3
+        version: 10.3.3(@nestjs/common@10.3.3)(@nestjs/platform-express@10.3.3)(reflect-metadata@0.2.1)(rxjs@7.8.1)
       '@nestjs/testing':
-        specifier: 10.2.10
-        version: 10.2.10(@nestjs/common@10.2.10)(@nestjs/core@10.2.10)
+        specifier: 10.3.3
+        version: 10.3.3(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(@nestjs/platform-express@10.3.3)
       '@peertube/http-signature':
         specifier: 1.7.0
         version: 1.7.0
-      '@sharkey/sfm-js':
-        specifier: 0.24.3
-        version: 0.24.3
       '@simplewebauthn/server':
-        specifier: 8.3.5
-        version: 8.3.5
+        specifier: 9.0.3
+        version: 9.0.3
       '@sinonjs/fake-timers':
         specifier: 11.2.2
         version: 11.2.2
@@ -123,10 +135,13 @@ importers:
         version: 2.1.10
       '@swc/cli':
         specifier: 0.1.63
-        version: 0.1.63(@swc/core@1.3.100)(chokidar@3.5.3)
+        version: 0.1.63(@swc/core@1.3.107)(chokidar@3.5.3)
       '@swc/core':
-        specifier: 1.3.100
-        version: 1.3.100
+        specifier: 1.3.107
+        version: 1.3.107
+      '@transfem-org/sfm-js':
+        specifier: 0.24.4
+        version: 0.24.4
       '@twemoji/parser':
         specifier: 15.0.0
         version: 15.0.0
@@ -140,11 +155,11 @@ importers:
         specifier: 6.0.1
         version: 6.0.1
       argon2:
-        specifier: ^0.31.1
-        version: 0.31.1
+        specifier: ^0.40.1
+        version: 0.40.1
       async-mutex:
-        specifier: 0.4.0
-        version: 0.4.0
+        specifier: 0.4.1
+        version: 0.4.1
       bcryptjs:
         specifier: 2.4.3
         version: 2.4.3
@@ -155,14 +170,14 @@ importers:
         specifier: 1.20.2
         version: 1.20.2
       bullmq:
-        specifier: 4.15.4
-        version: 4.15.4
+        specifier: 5.4.0
+        version: 5.4.0
       cacheable-lookup:
         specifier: 7.0.0
         version: 7.0.0
       cbor:
-        specifier: 9.0.1
-        version: 9.0.1
+        specifier: 9.0.2
+        version: 9.0.2
       chalk:
         specifier: 5.3.0
         version: 5.3.0
@@ -188,8 +203,8 @@ importers:
         specifier: 0.1.21
         version: 0.1.21
       fastify:
-        specifier: 4.24.3
-        version: 4.24.3
+        specifier: 4.25.2
+        version: 4.25.2
       fastify-multer:
         specifier: ^2.0.3
         version: 2.0.3
@@ -200,8 +215,8 @@ importers:
         specifier: 4.2.2
         version: 4.2.2
       file-type:
-        specifier: 18.7.0
-        version: 18.7.0
+        specifier: 19.0.0
+        version: 19.0.0
       fluent-ffmpeg:
         specifier: 2.1.2
         version: 2.1.2
@@ -212,17 +227,20 @@ importers:
         specifier: 10.3.10
         version: 10.3.10
       got:
-        specifier: 14.0.0
-        version: 14.0.0
+        specifier: 14.2.0
+        version: 14.2.0
       happy-dom:
         specifier: 10.0.3
         version: 10.0.3
       hpagent:
         specifier: 1.2.0
         version: 1.2.0
-      http-link-header:
+      htmlescape:
         specifier: 1.1.1
         version: 1.1.1
+      http-link-header:
+        specifier: 1.1.2
+        version: 1.1.2
       ioredis:
         specifier: 5.3.2
         version: 5.3.2
@@ -239,8 +257,8 @@ importers:
         specifier: 4.1.0
         version: 4.1.0
       jsdom:
-        specifier: 23.0.1
-        version: 23.0.1(bufferutil@4.0.7)(utf-8-validate@6.0.3)
+        specifier: 23.2.0
+        version: 23.2.0(bufferutil@4.0.7)(utf-8-validate@6.0.3)
       json5:
         specifier: 2.2.3
         version: 2.2.3
@@ -248,14 +266,14 @@ importers:
         specifier: 8.3.2
         version: 8.3.2
       jsrsasign:
-        specifier: 10.9.0
-        version: 10.9.0
+        specifier: 11.1.0
+        version: 11.1.0
       megalodon:
         specifier: workspace:*
         version: link:../megalodon
       meilisearch:
-        specifier: 0.36.0
-        version: 0.36.0
+        specifier: 0.37.0
+        version: 0.37.0
       microformats-parser:
         specifier: 2.0.2
         version: 2.0.2
@@ -265,12 +283,15 @@ importers:
       misskey-js:
         specifier: workspace:*
         version: link:../misskey-js
+      misskey-reversi:
+        specifier: workspace:*
+        version: link:../misskey-reversi
       ms:
         specifier: 3.0.0-canary.1
         version: 3.0.0-canary.1
       nanoid:
-        specifier: 5.0.4
-        version: 5.0.4
+        specifier: 5.0.6
+        version: 5.0.6
       nested-property:
         specifier: 4.0.0
         version: 4.0.0
@@ -278,8 +299,8 @@ importers:
         specifier: 3.3.2
         version: 3.3.2
       nodemailer:
-        specifier: 6.9.7
-        version: 6.9.7
+        specifier: 6.9.10
+        version: 6.9.10
       oauth:
         specifier: 0.10.0
         version: 0.10.0
@@ -293,8 +314,8 @@ importers:
         specifier: 0.0.14
         version: 0.0.14
       otpauth:
-        specifier: 9.2.1
-        version: 9.2.1
+        specifier: 9.2.2
+        version: 9.2.2
       parse5:
         specifier: 7.1.2
         version: 7.1.2
@@ -302,8 +323,8 @@ importers:
         specifier: 8.11.3
         version: 8.11.3
       pkce-challenge:
-        specifier: 4.0.1
-        version: 4.0.1
+        specifier: 4.1.0
+        version: 4.1.0
       probe-image-size:
         specifier: 7.2.3
         version: 7.2.3
@@ -335,8 +356,8 @@ importers:
         specifier: 0.1.4
         version: 0.1.4
       reflect-metadata:
-        specifier: 0.1.14
-        version: 0.1.14
+        specifier: 0.2.1
+        version: 0.2.1
       rename:
         specifier: 1.0.4
         version: 1.0.4
@@ -347,17 +368,14 @@ importers:
         specifier: 7.8.1
         version: 7.8.1
       sanitize-html:
-        specifier: 2.11.0
-        version: 2.11.0
+        specifier: 2.12.1
+        version: 2.12.1
       secure-json-parse:
         specifier: 2.7.0
         version: 2.7.0
       sharp:
-        specifier: 0.32.6
-        version: 0.32.6
-      sharp-read-bmp:
-        specifier: github:misskey-dev/sharp-read-bmp
-        version: github.com/misskey-dev/sharp-read-bmp/02d9dc189fa7df0c4bea09330be26741772dac01
+        specifier: 0.33.2
+        version: 0.33.2
       slacc:
         specifier: 0.0.10
         version: 0.0.10
@@ -367,18 +385,15 @@ importers:
       stringz:
         specifier: 2.1.0
         version: 2.1.0
-      summaly:
-        specifier: github:misskey-dev/summaly
-        version: github.com/misskey-dev/summaly/d2a3e07205c3c9769bc5a7b42031c8884b5a25c8
       systeminformation:
-        specifier: 5.21.20
-        version: 5.21.20
+        specifier: 5.22.0
+        version: 5.22.0
       tinycolor2:
         specifier: 1.6.0
         version: 1.6.0
       tmp:
-        specifier: 0.2.1
-        version: 0.2.1
+        specifier: 0.2.2
+        version: 0.2.2
       tsc-alias:
         specifier: 1.8.8
         version: 1.8.8
@@ -386,8 +401,8 @@ importers:
         specifier: 4.2.0
         version: 4.2.0
       typeorm:
-        specifier: 0.3.17
-        version: 0.3.17(ioredis@5.3.2)(pg@8.11.3)
+        specifier: 0.3.20
+        version: 0.3.20(ioredis@5.3.2)(pg@8.11.3)
       typescript:
         specifier: 5.3.3
         version: 5.3.3
@@ -401,11 +416,11 @@ importers:
         specifier: 1.1.2
         version: 1.1.2
       web-push:
-        specifier: 3.6.6
-        version: 3.6.6
+        specifier: 3.6.7
+        version: 3.6.7
       ws:
-        specifier: 8.15.1
-        version: 8.15.1(bufferutil@4.0.7)(utf-8-validate@6.0.3)
+        specifier: 8.16.0
+        version: 8.16.0(bufferutil@4.0.7)(utf-8-validate@6.0.3)
       xev:
         specifier: 3.0.2
         version: 3.0.2
@@ -495,12 +510,18 @@ importers:
       '@jest/globals':
         specifier: 29.7.0
         version: 29.7.0
-      '@simplewebauthn/typescript-types':
-        specifier: 8.3.4
-        version: 8.3.4
+      '@misskey-dev/eslint-plugin':
+        specifier: 1.0.0
+        version: 1.0.0(@typescript-eslint/eslint-plugin@7.1.0)(@typescript-eslint/parser@7.1.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0)
+      '@nestjs/platform-express':
+        specifier: 10.3.3
+        version: 10.3.3(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)
+      '@simplewebauthn/types':
+        specifier: 9.0.1
+        version: 9.0.1
       '@swc/jest':
-        specifier: 0.2.29
-        version: 0.2.29(@swc/core@1.3.100)
+        specifier: 0.2.31
+        version: 0.2.31(@swc/core@1.3.107)
       '@types/accepts':
         specifier: 1.3.7
         version: 1.3.7
@@ -513,9 +534,6 @@ importers:
       '@types/body-parser':
         specifier: 1.19.5
         version: 1.19.5
-      '@types/cbor':
-        specifier: 6.0.0
-        version: 6.0.0
       '@types/color-convert':
         specifier: 2.0.3
         version: 2.0.3
@@ -525,6 +543,9 @@ importers:
       '@types/fluent-ffmpeg':
         specifier: 2.1.24
         version: 2.1.24
+      '@types/htmlescape':
+        specifier: ^1.1.3
+        version: 1.1.3
       '@types/http-link-header':
         specifier: 1.0.5
         version: 1.0.5
@@ -550,8 +571,8 @@ importers:
         specifier: 0.7.34
         version: 0.7.34
       '@types/node':
-        specifier: 20.10.5
-        version: 20.10.5
+        specifier: 20.11.22
+        version: 20.11.22
       '@types/node-fetch':
         specifier: 3.0.3
         version: 3.0.3
@@ -568,14 +589,14 @@ importers:
         specifier: 0.1.2
         version: 0.1.2
       '@types/pg':
-        specifier: 8.10.9
-        version: 8.10.9
+        specifier: 8.11.2
+        version: 8.11.2
       '@types/pug':
         specifier: 2.0.10
         version: 2.0.10
       '@types/punycode':
-        specifier: 2.1.3
-        version: 2.1.3
+        specifier: 2.1.4
+        version: 2.1.4
       '@types/qrcode':
         specifier: 1.5.5
         version: 1.5.5
@@ -589,14 +610,11 @@ importers:
         specifier: 1.0.7
         version: 1.0.7
       '@types/sanitize-html':
-        specifier: 2.9.5
-        version: 2.9.5
+        specifier: 2.11.0
+        version: 2.11.0
       '@types/semver':
-        specifier: 7.5.6
-        version: 7.5.6
-      '@types/sharp':
-        specifier: 0.32.0
-        version: 0.32.0
+        specifier: 7.5.8
+        version: 7.5.8
       '@types/simple-oauth2':
         specifier: 5.0.7
         version: 5.0.7
@@ -622,35 +640,41 @@ importers:
         specifier: 8.5.10
         version: 8.5.10
       '@typescript-eslint/eslint-plugin':
-        specifier: 6.14.0
-        version: 6.14.0(@typescript-eslint/parser@6.14.0)(eslint@8.56.0)(typescript@5.3.3)
+        specifier: 7.1.0
+        version: 7.1.0(@typescript-eslint/parser@7.1.0)(eslint@8.57.0)(typescript@5.3.3)
       '@typescript-eslint/parser':
-        specifier: 6.14.0
-        version: 6.14.0(eslint@8.56.0)(typescript@5.3.3)
+        specifier: 7.1.0
+        version: 7.1.0(eslint@8.57.0)(typescript@5.3.3)
       aws-sdk-client-mock:
-        specifier: 3.0.0
-        version: 3.0.0
+        specifier: 3.0.1
+        version: 3.0.1
       cross-env:
         specifier: 7.0.3
         version: 7.0.3
       eslint:
-        specifier: 8.56.0
-        version: 8.56.0
+        specifier: 8.57.0
+        version: 8.57.0
       eslint-plugin-import:
         specifier: 2.29.1
-        version: 2.29.1(@typescript-eslint/parser@6.14.0)(eslint@8.56.0)
+        version: 2.29.1(@typescript-eslint/parser@7.1.0)(eslint@8.57.0)
       execa:
         specifier: 8.0.1
         version: 8.0.1
+      fkill:
+        specifier: ^9.0.0
+        version: 9.0.0
       jest:
         specifier: 29.7.0
-        version: 29.7.0(@types/node@20.10.5)
+        version: 29.7.0(@types/node@20.11.22)
       jest-mock:
         specifier: 29.7.0
         version: 29.7.0
       nodemon:
-        specifier: 3.0.2
-        version: 3.0.2
+        specifier: 3.1.0
+        version: 3.1.0
+      pid-port:
+        specifier: 1.0.0
+        version: 1.0.0
       simple-oauth2:
         specifier: 5.0.0
         version: 5.0.0
@@ -663,69 +687,72 @@ importers:
       '@github/webauthn-json':
         specifier: 2.1.1
         version: 2.1.1
+      '@mcaptcha/vanilla-glue':
+        specifier: 0.1.0-alpha-3
+        version: 0.1.0-alpha-3
+      '@misskey-dev/browser-image-resizer':
+        specifier: 2024.1.0
+        version: 2024.1.0
       '@phosphor-icons/web':
         specifier: ^2.0.3
         version: 2.0.3
       '@rollup/plugin-json':
         specifier: 6.1.0
-        version: 6.1.0(rollup@4.9.1)
+        version: 6.1.0(rollup@4.12.0)
       '@rollup/plugin-replace':
         specifier: 5.0.5
-        version: 5.0.5(rollup@4.9.1)
+        version: 5.0.5(rollup@4.12.0)
       '@rollup/pluginutils':
         specifier: 5.1.0
-        version: 5.1.0(rollup@4.9.1)
-      '@sharkey/sfm-js':
-        specifier: 0.24.3
-        version: 0.24.3
+        version: 5.1.0(rollup@4.12.0)
       '@syuilo/aiscript':
-        specifier: 0.16.0
-        version: 0.16.0
+        specifier: 0.17.0
+        version: 0.17.0
+      '@transfem-org/sfm-js':
+        specifier: 0.24.4
+        version: 0.24.4
       '@twemoji/parser':
         specifier: 15.0.0
         version: 15.0.0
       '@vitejs/plugin-vue':
-        specifier: 4.5.2
-        version: 4.5.2(vite@5.0.10)(vue@3.3.12)
+        specifier: 5.0.4
+        version: 5.0.4(vite@5.1.4)(vue@3.4.21)
       '@vue/compiler-sfc':
-        specifier: 3.3.12
-        version: 3.3.12
+        specifier: 3.4.21
+        version: 3.4.21
       aiscript-vscode:
-        specifier: github:aiscript-dev/aiscript-vscode#v0.0.6
-        version: github.com/aiscript-dev/aiscript-vscode/b5a8aa0ad927831a0b867d1c183460a14e6c48cd
+        specifier: github:aiscript-dev/aiscript-vscode#v0.1.2
+        version: github.com/aiscript-dev/aiscript-vscode/793211d40243c8775f6b85f015c221c82cbffb07
       astring:
         specifier: 1.8.6
         version: 1.8.6
       broadcast-channel:
         specifier: 7.0.0
         version: 7.0.0
-      browser-image-resizer:
-        specifier: github:misskey-dev/browser-image-resizer#v2.2.1-misskey.3
-        version: github.com/misskey-dev/browser-image-resizer/0227e860621e55cbed0aabe6dc601096a7748c4a
       buraha:
         specifier: 0.0.1
         version: 0.0.1
       canvas-confetti:
-        specifier: 1.6.1
-        version: 1.6.1
+        specifier: 1.9.2
+        version: 1.9.2
       chart.js:
-        specifier: 4.4.1
-        version: 4.4.1
+        specifier: 4.4.2
+        version: 4.4.2
       chartjs-adapter-date-fns:
         specifier: 3.0.0
-        version: 3.0.0(chart.js@4.4.1)(date-fns@2.30.0)
+        version: 3.0.0(chart.js@4.4.2)(date-fns@2.30.0)
       chartjs-chart-matrix:
         specifier: 2.0.1
-        version: 2.0.1(chart.js@4.4.1)
+        version: 2.0.1(chart.js@4.4.2)
       chartjs-plugin-gradient:
         specifier: 0.6.1
-        version: 0.6.1(chart.js@4.4.1)
+        version: 0.6.1(chart.js@4.4.2)
       chartjs-plugin-zoom:
         specifier: 2.0.1
-        version: 2.0.1(chart.js@4.4.1)
+        version: 2.0.1(chart.js@4.4.2)
       chromatic:
-        specifier: 10.1.0
-        version: 10.1.0
+        specifier: 11.0.0
+        version: 11.0.0
       compare-versions:
         specifier: 6.1.0
         version: 6.1.0
@@ -744,9 +771,6 @@ importers:
       eventemitter3:
         specifier: 5.0.1
         version: 5.0.1
-      gsap:
-        specifier: 3.12.4
-        version: 3.12.4
       idb-keyval:
         specifier: 6.2.1
         version: 6.2.1
@@ -765,9 +789,15 @@ importers:
       matter-js:
         specifier: 0.19.0
         version: 0.19.0
+      misskey-bubble-game:
+        specifier: workspace:*
+        version: link:../misskey-bubble-game
       misskey-js:
         specifier: workspace:*
         version: link:../misskey-js
+      misskey-reversi:
+        specifier: workspace:*
+        version: link:../misskey-reversi
       photoswipe:
         specifier: 5.4.3
         version: 5.4.3
@@ -775,17 +805,17 @@ importers:
         specifier: 2.3.1
         version: 2.3.1
       rollup:
-        specifier: 4.9.1
-        version: 4.9.1
+        specifier: 4.12.0
+        version: 4.12.0
       sanitize-html:
-        specifier: 2.11.0
-        version: 2.11.0
+        specifier: 2.12.1
+        version: 2.12.1
       sass:
-        specifier: 1.69.5
-        version: 1.69.5
+        specifier: 1.71.1
+        version: 1.71.1
       shiki:
-        specifier: 0.14.7
-        version: 0.14.7
+        specifier: 1.1.7
+        version: 1.1.7
       strict-event-emitter-types:
         specifier: 2.0.0
         version: 2.0.0
@@ -793,8 +823,8 @@ importers:
         specifier: 3.1.0
         version: 3.1.0
       three:
-        specifier: 0.159.0
-        version: 0.159.0
+        specifier: 0.162.0
+        version: 0.162.0
       throttle-debounce:
         specifier: 5.0.0
         version: 5.0.0
@@ -814,75 +844,81 @@ importers:
         specifier: 9.0.1
         version: 9.0.1
       v-code-diff:
-        specifier: 1.7.2
-        version: 1.7.2(vue@3.3.12)
+        specifier: 1.9.0
+        version: 1.9.0(vue@3.4.21)
       vite:
-        specifier: 5.0.10
-        version: 5.0.10(@types/node@20.10.5)(sass@1.69.5)(terser@5.26.0)
+        specifier: 5.1.4
+        version: 5.1.4(@types/node@20.11.22)(sass@1.71.1)(terser@5.28.1)
       vue:
-        specifier: 3.3.12
-        version: 3.3.12(typescript@5.3.3)
+        specifier: 3.4.21
+        version: 3.4.21(typescript@5.3.3)
       vuedraggable:
         specifier: next
-        version: 4.1.0(vue@3.3.12)
+        version: 4.1.0(vue@3.4.21)
     devDependencies:
+      '@misskey-dev/eslint-plugin':
+        specifier: 1.0.0
+        version: 1.0.0(@typescript-eslint/eslint-plugin@7.1.0)(@typescript-eslint/parser@7.1.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0)
+      '@misskey-dev/summaly':
+        specifier: 5.0.3
+        version: 5.0.3
       '@storybook/addon-actions':
-        specifier: 7.6.5
-        version: 7.6.5
+        specifier: 8.0.0-beta.6
+        version: 8.0.0-beta.6
       '@storybook/addon-essentials':
-        specifier: 7.6.5
-        version: 7.6.5(react-dom@18.2.0)(react@18.2.0)
+        specifier: 8.0.0-beta.6
+        version: 8.0.0-beta.6(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)
       '@storybook/addon-interactions':
-        specifier: 7.6.5
-        version: 7.6.5
+        specifier: 8.0.0-beta.6
+        version: 8.0.0-beta.6
       '@storybook/addon-links':
-        specifier: 7.6.5
-        version: 7.6.5(react@18.2.0)
+        specifier: 8.0.0-beta.6
+        version: 8.0.0-beta.6(react@18.2.0)
+      '@storybook/addon-mdx-gfm':
+        specifier: 8.0.0-beta.6
+        version: 8.0.0-beta.6
       '@storybook/addon-storysource':
-        specifier: 7.6.5
-        version: 7.6.5
-      '@storybook/addons':
-        specifier: 7.6.5
-        version: 7.6.5(react-dom@18.2.0)(react@18.2.0)
+        specifier: 8.0.0-beta.6
+        version: 8.0.0-beta.6
       '@storybook/blocks':
-        specifier: 7.6.5
-        version: 7.6.5(react-dom@18.2.0)(react@18.2.0)
+        specifier: 8.0.0-beta.6
+        version: 8.0.0-beta.6(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/components':
+        specifier: 8.0.0-beta.6
+        version: 8.0.0-beta.6(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)
       '@storybook/core-events':
-        specifier: 7.6.5
-        version: 7.6.5
-      '@storybook/jest':
-        specifier: 0.2.3
-        version: 0.2.3(vitest@0.34.6)
+        specifier: 8.0.0-beta.6
+        version: 8.0.0-beta.6
       '@storybook/manager-api':
-        specifier: 7.6.5
-        version: 7.6.5(react-dom@18.2.0)(react@18.2.0)
+        specifier: 8.0.0-beta.6
+        version: 8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0)
       '@storybook/preview-api':
-        specifier: 7.6.5
-        version: 7.6.5
+        specifier: 8.0.0-beta.6
+        version: 8.0.0-beta.6
       '@storybook/react':
-        specifier: 7.6.5
-        version: 7.6.5(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3)
+        specifier: 8.0.0-beta.6
+        version: 8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3)
       '@storybook/react-vite':
-        specifier: 7.6.5
-        version: 7.6.5(react-dom@18.2.0)(react@18.2.0)(rollup@4.9.1)(typescript@5.3.3)(vite@5.0.10)
-      '@storybook/testing-library':
-        specifier: 0.2.2
-        version: 0.2.2
+        specifier: 8.0.0-beta.6
+        version: 8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0)(rollup@4.12.0)(typescript@5.3.3)(vite@5.1.4)
+      '@storybook/test':
+        specifier: 8.0.0-beta.6
+        version: 8.0.0-beta.6(vitest@0.34.6)
       '@storybook/theming':
-        specifier: 7.6.5
-        version: 7.6.5(react-dom@18.2.0)(react@18.2.0)
+        specifier: 8.0.0-beta.6
+        version: 8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0)
       '@storybook/types':
-        specifier: 7.6.5
-        version: 7.6.5
+        specifier: 8.0.0-beta.6
+        version: 8.0.0-beta.6
       '@storybook/vue3':
-        specifier: 7.6.5
-        version: 7.6.5(@vue/compiler-core@3.3.12)(vue@3.3.12)
+        specifier: 8.0.0-beta.6
+        version: 8.0.0-beta.6(vue@3.4.21)
       '@storybook/vue3-vite':
-        specifier: 7.6.5
-        version: 7.6.5(@vue/compiler-core@3.3.12)(typescript@5.3.3)(vite@5.0.10)(vue@3.3.12)
+        specifier: 8.0.0-beta.6
+        version: 8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0)(vite@5.1.4)(vue@3.4.21)
       '@testing-library/vue':
-        specifier: 8.0.1
-        version: 8.0.1(@vue/compiler-sfc@3.3.12)(vue@3.3.12)
+        specifier: 8.0.2
+        version: 8.0.2(@vue/compiler-sfc@3.4.21)(vue@3.4.21)
       '@types/escape-regexp':
         specifier: 0.0.3
         version: 0.0.3
@@ -890,20 +926,20 @@ importers:
         specifier: 1.0.5
         version: 1.0.5
       '@types/matter-js':
-        specifier: 0.19.5
-        version: 0.19.5
+        specifier: 0.19.6
+        version: 0.19.6
       '@types/micromatch':
         specifier: 4.0.6
         version: 4.0.6
       '@types/node':
-        specifier: 20.10.5
-        version: 20.10.5
+        specifier: 20.11.22
+        version: 20.11.22
       '@types/punycode':
-        specifier: 2.1.3
-        version: 2.1.3
+        specifier: 2.1.4
+        version: 2.1.4
       '@types/sanitize-html':
-        specifier: 2.9.5
-        version: 2.9.5
+        specifier: 2.11.0
+        version: 2.11.0
       '@types/throttle-debounce':
         specifier: 5.0.2
         version: 5.0.2
@@ -911,47 +947,47 @@ importers:
         specifier: 1.4.6
         version: 1.4.6
       '@types/uuid':
-        specifier: 9.0.7
-        version: 9.0.7
+        specifier: 9.0.8
+        version: 9.0.8
       '@types/ws':
         specifier: 8.5.10
         version: 8.5.10
       '@typescript-eslint/eslint-plugin':
-        specifier: 6.14.0
-        version: 6.14.0(@typescript-eslint/parser@6.14.0)(eslint@8.56.0)(typescript@5.3.3)
+        specifier: 7.1.0
+        version: 7.1.0(@typescript-eslint/parser@7.1.0)(eslint@8.57.0)(typescript@5.3.3)
       '@typescript-eslint/parser':
-        specifier: 6.14.0
-        version: 6.14.0(eslint@8.56.0)(typescript@5.3.3)
+        specifier: 7.1.0
+        version: 7.1.0(eslint@8.57.0)(typescript@5.3.3)
       '@vitest/coverage-v8':
         specifier: 0.34.6
         version: 0.34.6(vitest@0.34.6)
       '@vue/runtime-core':
-        specifier: 3.3.12
-        version: 3.3.12
+        specifier: 3.4.21
+        version: 3.4.21
       acorn:
-        specifier: 8.11.2
-        version: 8.11.2
+        specifier: 8.11.3
+        version: 8.11.3
       cross-env:
         specifier: 7.0.3
         version: 7.0.3
       cypress:
-        specifier: 13.6.1
-        version: 13.6.1
+        specifier: 13.6.6
+        version: 13.6.6
       eslint:
-        specifier: 8.56.0
-        version: 8.56.0
+        specifier: 8.57.0
+        version: 8.57.0
       eslint-plugin-import:
         specifier: 2.29.1
-        version: 2.29.1(@typescript-eslint/parser@6.14.0)(eslint@8.56.0)
+        version: 2.29.1(@typescript-eslint/parser@7.1.0)(eslint@8.57.0)
       eslint-plugin-vue:
-        specifier: 9.19.2
-        version: 9.19.2(eslint@8.56.0)
+        specifier: 9.22.0
+        version: 9.22.0(eslint@8.57.0)
       fast-glob:
         specifier: 3.3.2
         version: 3.3.2
       happy-dom:
-        specifier: 10.0.3
-        version: 10.0.3
+        specifier: 13.6.2
+        version: 13.6.2
       intersection-observer:
         specifier: 0.12.2
         version: 0.12.2
@@ -959,17 +995,17 @@ importers:
         specifier: 4.0.5
         version: 4.0.5
       msw:
-        specifier: 1.3.2
-        version: 1.3.2(typescript@5.3.3)
+        specifier: 2.1.7
+        version: 2.1.7(typescript@5.3.3)
       msw-storybook-addon:
-        specifier: 1.10.0
-        version: 1.10.0(msw@1.3.2)
+        specifier: 2.0.0-beta.1
+        version: 2.0.0-beta.1(msw@2.1.7)
       nodemon:
-        specifier: 3.0.2
-        version: 3.0.2
+        specifier: 3.1.0
+        version: 3.1.0
       prettier:
-        specifier: 3.1.1
-        version: 3.1.1
+        specifier: 3.2.5
+        version: 3.2.5
       react:
         specifier: 18.2.0
         version: 18.2.0
@@ -980,29 +1016,29 @@ importers:
         specifier: 2.0.3
         version: 2.0.3
       storybook:
-        specifier: 7.6.5
-        version: 7.6.5
+        specifier: 8.0.0-beta.6
+        version: 8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0)
       storybook-addon-misskey-theme:
         specifier: github:misskey-dev/storybook-addon-misskey-theme
-        version: github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.6.5)(@storybook/components@7.6.5)(@storybook/core-events@7.6.5)(@storybook/manager-api@7.6.5)(@storybook/preview-api@7.6.5)(@storybook/theming@7.6.5)(@storybook/types@7.6.5)(react-dom@18.2.0)(react@18.2.0)
-      summaly:
-        specifier: github:misskey-dev/summaly
-        version: github.com/misskey-dev/summaly/d2a3e07205c3c9769bc5a7b42031c8884b5a25c8
+        version: github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@8.0.0-beta.6)(@storybook/components@8.0.0-beta.6)(@storybook/core-events@8.0.0-beta.6)(@storybook/manager-api@8.0.0-beta.6)(@storybook/preview-api@8.0.0-beta.6)(@storybook/theming@8.0.0-beta.6)(@storybook/types@8.0.0-beta.6)(react-dom@18.2.0)(react@18.2.0)
       vite-plugin-turbosnap:
         specifier: 1.0.3
         version: 1.0.3
       vitest:
         specifier: 0.34.6
-        version: 0.34.6(happy-dom@10.0.3)(sass@1.69.5)(terser@5.26.0)
+        version: 0.34.6(happy-dom@13.6.2)(sass@1.71.1)(terser@5.28.1)
       vitest-fetch-mock:
         specifier: 0.2.2
         version: 0.2.2(vitest@0.34.6)
+      vue-component-type-helpers:
+        specifier: 1.8.27
+        version: 1.8.27
       vue-eslint-parser:
-        specifier: 9.3.2
-        version: 9.3.2(eslint@8.56.0)
+        specifier: 9.4.2
+        version: 9.4.2(eslint@8.57.0)
       vue-tsc:
-        specifier: 1.8.25
-        version: 1.8.25(typescript@5.3.3)
+        specifier: 1.8.27
+        version: 1.8.27(typescript@5.3.3)
 
   packages/megalodon:
     dependencies:
@@ -1066,10 +1102,10 @@ importers:
     devDependencies:
       '@typescript-eslint/eslint-plugin':
         specifier: ^6.12.0
-        version: 6.12.0(@typescript-eslint/parser@6.12.0)(eslint@8.54.0)(typescript@5.1.6)
+        version: 6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.54.0)(typescript@5.1.6)
       '@typescript-eslint/parser':
         specifier: ^6.12.0
-        version: 6.12.0(eslint@8.54.0)(typescript@5.1.6)
+        version: 6.21.0(eslint@8.54.0)(typescript@5.1.6)
       eslint:
         specifier: ^8.54.0
         version: 8.54.0
@@ -1078,7 +1114,7 @@ importers:
         version: 9.0.0(eslint@8.54.0)
       jest:
         specifier: ^29.7.0
-        version: 29.7.0(@types/node@20.10.5)
+        version: 29.7.0(@types/node@20.11.22)
       jest-worker:
         specifier: ^29.7.0
         version: 29.7.0
@@ -1090,19 +1126,65 @@ importers:
         version: 3.1.0
       ts-jest:
         specifier: ^29.1.1
-        version: 29.1.1(@babel/core@7.23.3)(jest@29.7.0)(typescript@5.1.6)
+        version: 29.1.1(@babel/core@7.24.0)(jest@29.7.0)(typescript@5.1.6)
       typedoc:
         specifier: ^0.25.3
         version: 0.25.3(typescript@5.1.6)
 
+  packages/misskey-bubble-game:
+    dependencies:
+      esbuild:
+        specifier: 0.19.11
+        version: 0.19.11
+      eventemitter3:
+        specifier: 5.0.1
+        version: 5.0.1
+      glob:
+        specifier: ^10.3.10
+        version: 10.3.10
+      matter-js:
+        specifier: 0.19.0
+        version: 0.19.0
+      seedrandom:
+        specifier: 3.0.5
+        version: 3.0.5
+    devDependencies:
+      '@misskey-dev/eslint-plugin':
+        specifier: 1.0.0
+        version: 1.0.0(@typescript-eslint/eslint-plugin@7.1.0)(@typescript-eslint/parser@7.1.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0)
+      '@types/matter-js':
+        specifier: 0.19.6
+        version: 0.19.6
+      '@types/node':
+        specifier: 20.11.5
+        version: 20.11.5
+      '@types/seedrandom':
+        specifier: 3.0.8
+        version: 3.0.8
+      '@typescript-eslint/eslint-plugin':
+        specifier: 7.1.0
+        version: 7.1.0(@typescript-eslint/parser@7.1.0)(eslint@8.57.0)(typescript@5.3.3)
+      '@typescript-eslint/parser':
+        specifier: 7.1.0
+        version: 7.1.0(eslint@8.57.0)(typescript@5.3.3)
+      eslint:
+        specifier: 8.57.0
+        version: 8.57.0
+      nodemon:
+        specifier: 3.0.2
+        version: 3.0.2
+      typescript:
+        specifier: 5.3.3
+        version: 5.3.3
+
   packages/misskey-js:
     dependencies:
       '@swc/cli':
         specifier: 0.1.63
-        version: 0.1.63(@swc/core@1.3.100)(chokidar@3.5.3)
+        version: 0.1.63(@swc/core@1.3.105)
       '@swc/core':
-        specifier: 1.3.100
-        version: 1.3.100
+        specifier: 1.3.105
+        version: 1.3.105
       eventemitter3:
         specifier: 5.0.1
         version: 5.0.1
@@ -1111,29 +1193,32 @@ importers:
         version: 4.4.0
     devDependencies:
       '@microsoft/api-extractor':
-        specifier: 7.38.5
-        version: 7.38.5(@types/node@20.10.5)
+        specifier: 7.39.1
+        version: 7.39.1(@types/node@20.11.22)
+      '@misskey-dev/eslint-plugin':
+        specifier: 1.0.0
+        version: 1.0.0(@typescript-eslint/eslint-plugin@7.1.0)(@typescript-eslint/parser@7.1.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0)
       '@swc/jest':
-        specifier: 0.2.29
-        version: 0.2.29(@swc/core@1.3.100)
+        specifier: 0.2.31
+        version: 0.2.31(@swc/core@1.3.105)
       '@types/jest':
-        specifier: 29.5.11
-        version: 29.5.11
+        specifier: 29.5.12
+        version: 29.5.12
       '@types/node':
-        specifier: 20.10.5
-        version: 20.10.5
+        specifier: 20.11.22
+        version: 20.11.22
       '@typescript-eslint/eslint-plugin':
-        specifier: 6.14.0
-        version: 6.14.0(@typescript-eslint/parser@6.14.0)(eslint@8.56.0)(typescript@5.3.3)
+        specifier: 7.1.0
+        version: 7.1.0(@typescript-eslint/parser@7.1.0)(eslint@8.57.0)(typescript@5.3.3)
       '@typescript-eslint/parser':
-        specifier: 6.14.0
-        version: 6.14.0(eslint@8.56.0)(typescript@5.3.3)
+        specifier: 7.1.0
+        version: 7.1.0(eslint@8.57.0)(typescript@5.3.3)
       eslint:
-        specifier: 8.56.0
-        version: 8.56.0
+        specifier: 8.57.0
+        version: 8.57.0
       jest:
         specifier: 29.7.0
-        version: 29.7.0(@types/node@20.10.5)
+        version: 29.7.0(@types/node@20.11.22)
       jest-fetch-mock:
         specifier: 3.0.3
         version: 3.0.3
@@ -1147,20 +1232,23 @@ importers:
         specifier: 2.0.0
         version: 2.0.0
       nodemon:
-        specifier: 3.0.2
-        version: 3.0.2
+        specifier: 3.1.0
+        version: 3.1.0
       tsd:
-        specifier: 0.30.0
-        version: 0.30.0
+        specifier: 0.30.7
+        version: 0.30.7
       typescript:
         specifier: 5.3.3
         version: 5.3.3
 
   packages/misskey-js/generator:
     devDependencies:
-      '@apidevtools/swagger-parser':
-        specifier: 10.1.0
-        version: 10.1.0(openapi-types@12.1.3)
+      '@misskey-dev/eslint-plugin':
+        specifier: ^1.0.0
+        version: 1.0.0(@typescript-eslint/eslint-plugin@6.11.0)(@typescript-eslint/parser@6.11.0)(eslint-plugin-import@2.29.1)(eslint@8.53.0)
+      '@readme/openapi-parser':
+        specifier: 2.5.0
+        version: 2.5.0(openapi-types@12.1.3)
       '@types/node':
         specifier: 20.9.1
         version: 20.9.1
@@ -1177,8 +1265,8 @@ importers:
         specifier: 12.1.3
         version: 12.1.3
       openapi-typescript:
-        specifier: 6.7.1
-        version: 6.7.1
+        specifier: 6.7.3
+        version: 6.7.3
       ts-case-convert:
         specifier: 2.0.2
         version: 2.0.2
@@ -1189,11 +1277,45 @@ importers:
         specifier: 5.3.3
         version: 5.3.3
 
+  packages/misskey-reversi:
+    dependencies:
+      crc-32:
+        specifier: 1.2.2
+        version: 1.2.2
+      esbuild:
+        specifier: 0.19.11
+        version: 0.19.11
+      glob:
+        specifier: 10.3.10
+        version: 10.3.10
+    devDependencies:
+      '@misskey-dev/eslint-plugin':
+        specifier: 1.0.0
+        version: 1.0.0(@typescript-eslint/eslint-plugin@7.1.0)(@typescript-eslint/parser@7.1.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0)
+      '@types/node':
+        specifier: 20.11.5
+        version: 20.11.5
+      '@typescript-eslint/eslint-plugin':
+        specifier: 7.1.0
+        version: 7.1.0(@typescript-eslint/parser@7.1.0)(eslint@8.57.0)(typescript@5.3.3)
+      '@typescript-eslint/parser':
+        specifier: 7.1.0
+        version: 7.1.0(eslint@8.57.0)(typescript@5.3.3)
+      eslint:
+        specifier: 8.57.0
+        version: 8.57.0
+      nodemon:
+        specifier: 3.0.2
+        version: 3.0.2
+      typescript:
+        specifier: 5.3.3
+        version: 5.3.3
+
   packages/sw:
     dependencies:
       esbuild:
-        specifier: 0.19.9
-        version: 0.19.9
+        specifier: 0.19.11
+        version: 0.19.11
       idb-keyval:
         specifier: 6.2.1
         version: 6.2.1
@@ -1201,21 +1323,24 @@ importers:
         specifier: workspace:*
         version: link:../misskey-js
     devDependencies:
+      '@misskey-dev/eslint-plugin':
+        specifier: 1.0.0
+        version: 1.0.0(@typescript-eslint/eslint-plugin@6.21.0)(@typescript-eslint/parser@7.1.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0)
       '@typescript-eslint/parser':
-        specifier: 6.14.0
-        version: 6.14.0(eslint@8.56.0)(typescript@5.3.3)
+        specifier: 7.1.0
+        version: 7.1.0(eslint@8.57.0)(typescript@5.3.3)
       '@typescript/lib-webworker':
         specifier: npm:@types/serviceworker@0.0.67
         version: /@types/serviceworker@0.0.67
       eslint:
-        specifier: 8.56.0
-        version: 8.56.0
+        specifier: 8.57.0
+        version: 8.57.0
       eslint-plugin-import:
         specifier: 2.29.1
-        version: 2.29.1(@typescript-eslint/parser@6.14.0)(eslint@8.56.0)
+        version: 2.29.1(@typescript-eslint/parser@7.1.0)(eslint@8.57.0)
       nodemon:
-        specifier: 3.0.2
-        version: 3.0.2
+        specifier: 3.1.0
+        version: 3.1.0
       typescript:
         specifier: 5.3.3
         version: 5.3.3
@@ -1227,24 +1352,16 @@ packages:
     engines: {node: '>=0.10.0'}
     dev: true
 
-  /@adobe/css-tools@4.3.1:
-    resolution: {integrity: sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg==}
+  /@adobe/css-tools@4.3.3:
+    resolution: {integrity: sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==}
     dev: true
 
   /@ampproject/remapping@2.2.1:
     resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==}
     engines: {node: '>=6.0.0'}
     dependencies:
-      '@jridgewell/gen-mapping': 0.3.2
-      '@jridgewell/trace-mapping': 0.3.18
-    dev: true
-
-  /@apidevtools/json-schema-ref-parser@9.0.6:
-    resolution: {integrity: sha512-M3YgsLjI0lZxvrpeGVk9Ap032W6TPQkH6pRAZz81Ac3WUNF79VQooAFnp8umjvVzUmD93NkogxEwbSce7qMsUg==}
-    dependencies:
-      '@jsdevtools/ono': 7.1.3
-      call-me-maybe: 1.0.2
-      js-yaml: 3.14.1
+      '@jridgewell/gen-mapping': 0.3.3
+      '@jridgewell/trace-mapping': 0.3.20
     dev: true
 
   /@apidevtools/openapi-schemas@2.1.0:
@@ -1256,20 +1373,13 @@ packages:
     resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==}
     dev: true
 
-  /@apidevtools/swagger-parser@10.1.0(openapi-types@12.1.3):
-    resolution: {integrity: sha512-9Kt7EuS/7WbMAUv2gSziqjvxwDbFSg3Xeyfuj5laUODX8o/k/CpsAKiQ8W7/R88eXFTMbJYg6+7uAmOWNKmwnw==}
-    peerDependencies:
-      openapi-types: '>=7'
+  /@asamuzakjp/dom-selector@2.0.2:
+    resolution: {integrity: sha512-x1KXOatwofR6ZAYzXRBL5wrdV0vwNxlTCK9NCuLqAzQYARqGcvFwiJA6A1ERuh+dgeA4Dxm3JBYictIes+SqUQ==}
     dependencies:
-      '@apidevtools/json-schema-ref-parser': 9.0.6
-      '@apidevtools/openapi-schemas': 2.1.0
-      '@apidevtools/swagger-methods': 3.0.2
-      '@jsdevtools/ono': 7.1.3
-      ajv: 8.12.0
-      ajv-draft-04: 1.0.0(ajv@8.12.0)
-      call-me-maybe: 1.0.2
-      openapi-types: 12.1.3
-    dev: true
+      bidi-js: 1.0.3
+      css-tree: 2.3.1
+      is-potential-custom-element-name: 1.0.1
+    dev: false
 
   /@aw-web-design/x-default-browser@1.4.126:
     resolution: {integrity: sha512-Xk1sIhyNC/esHGGVjL/niHLowM0csl/kFO5uawBy4IrWwy0o1G8LGt3jP6nmWGz+USxeeqbihAmp/oVZju6wug==}
@@ -1852,14 +1962,6 @@ packages:
       tslib: 2.6.2
     dev: false
 
-  /@babel/code-frame@7.22.13:
-    resolution: {integrity: sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==}
-    engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/highlight': 7.22.13
-      chalk: 2.4.2
-    dev: true
-
   /@babel/code-frame@7.23.4:
     resolution: {integrity: sha512-r1IONyb6Ia+jYR2vvIDhdWdlTGhqbBoFqLTQidzZ4kepUFH15ejXvFHxCVbtl7BOXIudsIubf4E81xeA3h3IXA==}
     engines: {node: '>=6.9.0'}
@@ -1867,6 +1969,14 @@ packages:
       '@babel/highlight': 7.23.4
       chalk: 2.4.2
 
+  /@babel/code-frame@7.23.5:
+    resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/highlight': 7.23.4
+      chalk: 2.4.2
+    dev: true
+
   /@babel/compat-data@7.23.3:
     resolution: {integrity: sha512-BmR4bWbDIoFJmJ9z2cZ8Gmm2MXgEDgjdWgpKmKWUt54UGFJdlj31ECtbaDvCG/qVdG3AQ1SfpZEs01lUFbzLOQ==}
     engines: {node: '>=6.9.0'}
@@ -1883,14 +1993,14 @@ packages:
     dependencies:
       '@ampproject/remapping': 2.2.1
       '@babel/code-frame': 7.23.4
-      '@babel/generator': 7.23.4
+      '@babel/generator': 7.23.6
       '@babel/helper-compilation-targets': 7.22.15
       '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.3)
       '@babel/helpers': 7.23.4
       '@babel/parser': 7.23.4
       '@babel/template': 7.22.15
-      '@babel/traverse': 7.23.4
-      '@babel/types': 7.23.4
+      '@babel/traverse': 7.24.0
+      '@babel/types': 7.24.0
       convert-source-map: 2.0.0
       debug: 4.3.4(supports-color@8.1.1)
       gensync: 1.0.0-beta.2
@@ -1900,11 +2010,34 @@ packages:
       - supports-color
     dev: true
 
-  /@babel/generator@7.23.4:
-    resolution: {integrity: sha512-esuS49Cga3HcThFNebGhlgsrVLkvhqvYDTzgjfFFlHJcIfLe5jFmRRfCQ1KuBfc4Jrtn3ndLgKWAKjBE+IraYQ==}
+  /@babel/core@7.24.0:
+    resolution: {integrity: sha512-fQfkg0Gjkza3nf0c7/w6Xf34BW4YvzNfACRLmmb7XRLa6XHdR+K9AlJlxneFfWYf6uhOzuzZVTjF/8KfndZANw==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.23.4
+      '@ampproject/remapping': 2.2.1
+      '@babel/code-frame': 7.23.5
+      '@babel/generator': 7.23.6
+      '@babel/helper-compilation-targets': 7.23.6
+      '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.0)
+      '@babel/helpers': 7.24.0
+      '@babel/parser': 7.24.0
+      '@babel/template': 7.24.0
+      '@babel/traverse': 7.24.0
+      '@babel/types': 7.24.0
+      convert-source-map: 2.0.0
+      debug: 4.3.4(supports-color@8.1.1)
+      gensync: 1.0.0-beta.2
+      json5: 2.2.3
+      semver: 6.3.1
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@babel/generator@7.23.6:
+    resolution: {integrity: sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/types': 7.24.0
       '@jridgewell/gen-mapping': 0.3.3
       '@jridgewell/trace-mapping': 0.3.20
       jsesc: 2.5.2
@@ -1914,14 +2047,14 @@ packages:
     resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.23.4
+      '@babel/types': 7.24.0
     dev: true
 
   /@babel/helper-builder-binary-assignment-operator-visitor@7.22.15:
     resolution: {integrity: sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.23.4
+      '@babel/types': 7.24.0
     dev: true
 
   /@babel/helper-compilation-targets@7.22.15:
@@ -1941,77 +2074,65 @@ packages:
     dependencies:
       '@babel/compat-data': 7.23.5
       '@babel/helper-validator-option': 7.23.5
-      browserslist: 4.22.2
+      browserslist: 4.23.0
       lru-cache: 5.1.1
       semver: 6.3.1
     dev: true
 
-  /@babel/helper-create-class-features-plugin@7.22.9(@babel/core@7.23.3):
-    resolution: {integrity: sha512-Pwyi89uO4YrGKxL/eNJ8lfEH55DnRloGPOseaA8NFNL6jAUnn+KccaISiFazCj5IolPPDjGSdzQzXVzODVRqUQ==}
+  /@babel/helper-create-class-features-plugin@7.23.5(@babel/core@7.24.0):
+    resolution: {integrity: sha512-QELlRWxSpgdwdJzSJn4WAhKC+hvw/AtHbbrIoncKHkhKKR/luAlKkgBDcri1EzWAo8f8VvYVryEHN4tax/V67A==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0
     dependencies:
-      '@babel/core': 7.23.3
-      '@babel/helper-annotate-as-pure': 7.22.5
-      '@babel/helper-environment-visitor': 7.22.20
-      '@babel/helper-function-name': 7.23.0
-      '@babel/helper-member-expression-to-functions': 7.22.5
-      '@babel/helper-optimise-call-expression': 7.22.5
-      '@babel/helper-replace-supers': 7.22.9(@babel/core@7.23.3)
-      '@babel/helper-skip-transparent-expression-wrappers': 7.22.5
-      '@babel/helper-split-export-declaration': 7.22.6
-      semver: 6.3.1
-    dev: true
-
-  /@babel/helper-create-class-features-plugin@7.23.6(@babel/core@7.23.3):
-    resolution: {integrity: sha512-cBXU1vZni/CpGF29iTu4YRbOZt3Wat6zCoMDxRF1MayiEc4URxOj31tT65HUM0CRpMowA3HCJaAOVOUnMf96cw==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0
-    dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-annotate-as-pure': 7.22.5
       '@babel/helper-environment-visitor': 7.22.20
       '@babel/helper-function-name': 7.23.0
       '@babel/helper-member-expression-to-functions': 7.23.0
       '@babel/helper-optimise-call-expression': 7.22.5
-      '@babel/helper-replace-supers': 7.22.20(@babel/core@7.23.3)
+      '@babel/helper-replace-supers': 7.22.20(@babel/core@7.24.0)
       '@babel/helper-skip-transparent-expression-wrappers': 7.22.5
       '@babel/helper-split-export-declaration': 7.22.6
       semver: 6.3.1
     dev: true
 
-  /@babel/helper-create-regexp-features-plugin@7.22.15(@babel/core@7.23.3):
+  /@babel/helper-create-class-features-plugin@7.24.0(@babel/core@7.24.0):
+    resolution: {integrity: sha512-QAH+vfvts51BCsNZ2PhY6HAggnlS6omLLFTsIpeqZk/MmJ6cW7tgz5yRv0fMJThcr6FmbMrENh1RgrWPTYA76g==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0
+    dependencies:
+      '@babel/core': 7.24.0
+      '@babel/helper-annotate-as-pure': 7.22.5
+      '@babel/helper-environment-visitor': 7.22.20
+      '@babel/helper-function-name': 7.23.0
+      '@babel/helper-member-expression-to-functions': 7.23.0
+      '@babel/helper-optimise-call-expression': 7.22.5
+      '@babel/helper-replace-supers': 7.22.20(@babel/core@7.24.0)
+      '@babel/helper-skip-transparent-expression-wrappers': 7.22.5
+      '@babel/helper-split-export-declaration': 7.22.6
+      semver: 6.3.1
+    dev: true
+
+  /@babel/helper-create-regexp-features-plugin@7.22.15(@babel/core@7.24.0):
     resolution: {integrity: sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-annotate-as-pure': 7.22.5
       regexpu-core: 5.3.2
       semver: 6.3.1
     dev: true
 
-  /@babel/helper-create-regexp-features-plugin@7.22.9(@babel/core@7.23.3):
-    resolution: {integrity: sha512-+svjVa/tFwsNSG4NEy1h85+HQ5imbT92Q5/bgtS7P0GTQlP8WuFdqsiABmQouhiFGyV66oGxZFpeYHza1rNsKw==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0
-    dependencies:
-      '@babel/core': 7.23.3
-      '@babel/helper-annotate-as-pure': 7.22.5
-      regexpu-core: 5.3.2
-      semver: 6.3.1
-    dev: true
-
-  /@babel/helper-define-polyfill-provider@0.4.4(@babel/core@7.23.3):
+  /@babel/helper-define-polyfill-provider@0.4.4(@babel/core@7.24.0):
     resolution: {integrity: sha512-QcJMILQCu2jm5TFPGA3lCpJJTeEP+mqeXooG/NZbg/h5FTFi6V0+99ahlRsW8/kRLyb24LZVCCiclDedhLKcBA==}
     peerDependencies:
       '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-compilation-targets': 7.23.6
       '@babel/helper-plugin-utils': 7.22.5
       debug: 4.3.4(supports-color@8.1.1)
@@ -2030,36 +2151,29 @@ packages:
     resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/template': 7.22.15
-      '@babel/types': 7.23.4
+      '@babel/template': 7.24.0
+      '@babel/types': 7.24.0
     dev: true
 
   /@babel/helper-hoist-variables@7.22.5:
     resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.23.4
-    dev: true
-
-  /@babel/helper-member-expression-to-functions@7.22.5:
-    resolution: {integrity: sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==}
-    engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/types': 7.23.4
+      '@babel/types': 7.24.0
     dev: true
 
   /@babel/helper-member-expression-to-functions@7.23.0:
     resolution: {integrity: sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.23.4
+      '@babel/types': 7.24.0
     dev: true
 
   /@babel/helper-module-imports@7.22.15:
     resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.23.4
+      '@babel/types': 7.24.0
     dev: true
 
   /@babel/helper-module-transforms@7.23.3(@babel/core@7.23.3):
@@ -2076,11 +2190,25 @@ packages:
       '@babel/helper-validator-identifier': 7.22.20
     dev: true
 
+  /@babel/helper-module-transforms@7.23.3(@babel/core@7.24.0):
+    resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0
+    dependencies:
+      '@babel/core': 7.24.0
+      '@babel/helper-environment-visitor': 7.22.20
+      '@babel/helper-module-imports': 7.22.15
+      '@babel/helper-simple-access': 7.22.5
+      '@babel/helper-split-export-declaration': 7.22.6
+      '@babel/helper-validator-identifier': 7.22.20
+    dev: true
+
   /@babel/helper-optimise-call-expression@7.22.5:
     resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.23.4
+      '@babel/types': 7.24.0
     dev: true
 
   /@babel/helper-plugin-utils@7.22.5:
@@ -2088,75 +2216,55 @@ packages:
     engines: {node: '>=6.9.0'}
     dev: true
 
-  /@babel/helper-remap-async-to-generator@7.22.20(@babel/core@7.23.3):
+  /@babel/helper-remap-async-to-generator@7.22.20(@babel/core@7.24.0):
     resolution: {integrity: sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-annotate-as-pure': 7.22.5
       '@babel/helper-environment-visitor': 7.22.20
       '@babel/helper-wrap-function': 7.22.20
     dev: true
 
-  /@babel/helper-replace-supers@7.22.20(@babel/core@7.23.3):
+  /@babel/helper-replace-supers@7.22.20(@babel/core@7.24.0):
     resolution: {integrity: sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-environment-visitor': 7.22.20
       '@babel/helper-member-expression-to-functions': 7.23.0
       '@babel/helper-optimise-call-expression': 7.22.5
     dev: true
 
-  /@babel/helper-replace-supers@7.22.9(@babel/core@7.23.3):
-    resolution: {integrity: sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0
-    dependencies:
-      '@babel/core': 7.23.3
-      '@babel/helper-environment-visitor': 7.22.20
-      '@babel/helper-member-expression-to-functions': 7.22.5
-      '@babel/helper-optimise-call-expression': 7.22.5
-    dev: true
-
   /@babel/helper-simple-access@7.22.5:
     resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.23.4
+      '@babel/types': 7.24.0
     dev: true
 
   /@babel/helper-skip-transparent-expression-wrappers@7.22.5:
     resolution: {integrity: sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.23.4
+      '@babel/types': 7.24.0
     dev: true
 
   /@babel/helper-split-export-declaration@7.22.6:
     resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/types': 7.23.4
+      '@babel/types': 7.24.0
     dev: true
 
-  /@babel/helper-string-parser@7.22.5:
-    resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==}
-    engines: {node: '>=6.9.0'}
-
   /@babel/helper-string-parser@7.23.4:
     resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==}
     engines: {node: '>=6.9.0'}
 
-  /@babel/helper-validator-identifier@7.22.15:
-    resolution: {integrity: sha512-4E/F9IIEi8WR94324mbDUMo074YTheJmd7eZF5vITTeYchqAi6sYXRLHUVsmkdmY4QjfKTcB2jB7dVP3NaBElQ==}
-    engines: {node: '>=6.9.0'}
-
   /@babel/helper-validator-identifier@7.22.20:
     resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==}
     engines: {node: '>=6.9.0'}
@@ -2176,8 +2284,8 @@ packages:
     engines: {node: '>=6.9.0'}
     dependencies:
       '@babel/helper-function-name': 7.23.0
-      '@babel/template': 7.22.15
-      '@babel/types': 7.23.4
+      '@babel/template': 7.24.0
+      '@babel/types': 7.24.0
     dev: true
 
   /@babel/helpers@7.23.4:
@@ -2185,19 +2293,21 @@ packages:
     engines: {node: '>=6.9.0'}
     dependencies:
       '@babel/template': 7.22.15
-      '@babel/traverse': 7.23.4
-      '@babel/types': 7.23.4
+      '@babel/traverse': 7.24.0
+      '@babel/types': 7.24.0
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@babel/highlight@7.22.13:
-    resolution: {integrity: sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==}
+  /@babel/helpers@7.24.0:
+    resolution: {integrity: sha512-ulDZdc0Aj5uLc5nETsa7EPx2L7rM0YJM8r7ck7U73AXi7qOV44IHHRAYZHY6iU1rr3C5N4NtTmMRUJP6kwCWeA==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/helper-validator-identifier': 7.22.15
-      chalk: 2.4.2
-      js-tokens: 4.0.0
+      '@babel/template': 7.24.0
+      '@babel/traverse': 7.24.0
+      '@babel/types': 7.24.0
+    transitivePeerDependencies:
+      - supports-color
     dev: true
 
   /@babel/highlight@7.23.4:
@@ -2213,14 +2323,14 @@ packages:
     engines: {node: '>=6.0.0'}
     hasBin: true
     dependencies:
-      '@babel/types': 7.22.17
+      '@babel/types': 7.24.0
 
   /@babel/parser@7.23.4:
     resolution: {integrity: sha512-vf3Xna6UEprW+7t6EtOmFpHNAuxw3xqPZghy+brsnusscJRW5BMUzzHZc5ICjULee81WeUV2jjakG09MDglJXQ==}
     engines: {node: '>=6.0.0'}
     hasBin: true
     dependencies:
-      '@babel/types': 7.23.4
+      '@babel/types': 7.24.0
     dev: true
 
   /@babel/parser@7.23.6:
@@ -2228,48 +2338,64 @@ packages:
     engines: {node: '>=6.0.0'}
     hasBin: true
     dependencies:
-      '@babel/types': 7.23.4
+      '@babel/types': 7.24.0
+    dev: true
 
-  /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.23.3(@babel/core@7.23.3):
+  /@babel/parser@7.23.9:
+    resolution: {integrity: sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==}
+    engines: {node: '>=6.0.0'}
+    hasBin: true
+    dependencies:
+      '@babel/types': 7.24.0
+
+  /@babel/parser@7.24.0:
+    resolution: {integrity: sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==}
+    engines: {node: '>=6.0.0'}
+    hasBin: true
+    dependencies:
+      '@babel/types': 7.24.0
+    dev: true
+
+  /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.13.0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
       '@babel/helper-skip-transparent-expression-wrappers': 7.22.5
-      '@babel/plugin-transform-optional-chaining': 7.23.4(@babel/core@7.23.3)
+      '@babel/plugin-transform-optional-chaining': 7.23.4(@babel/core@7.24.0)
     dev: true
 
-  /@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-XaJak1qcityzrX0/IU5nKHb34VaibwP3saKqG6a/tppelgllOH13LUann4ZCIBcVOeE6H18K4Vx9QKkVww3z/w==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-environment-visitor': 7.22.20
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.23.3):
+  /@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.0):
     resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
     dev: true
 
   /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.3):
@@ -2281,6 +2407,15 @@ packages:
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
+  /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.24.0):
+    resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.24.0
+      '@babel/helper-plugin-utils': 7.22.5
+    dev: true
+
   /@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.23.3):
     resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==}
     peerDependencies:
@@ -2299,61 +2434,70 @@ packages:
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.23.3):
+  /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.0):
+    resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.24.0
+      '@babel/helper-plugin-utils': 7.22.5
+    dev: true
+
+  /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.24.0):
     resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.23.3):
+  /@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.23.3):
+  /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-syntax-flow@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-syntax-flow@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-YZiAIpkJAwQXBJLIQbRFayR5c+gJ35Vcz3bg954k7cd73zqjvhacJuL9RbrzPz8qPmZdgqP6EUKwy0PCNhaaPA==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-syntax-import-assertions@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-syntax-import-assertions@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-syntax-import-attributes@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-syntax-import-attributes@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
@@ -2366,6 +2510,15 @@ packages:
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
+  /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.0):
+    resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.24.0
+      '@babel/helper-plugin-utils': 7.22.5
+    dev: true
+
   /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.23.3):
     resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==}
     peerDependencies:
@@ -2375,6 +2528,15 @@ packages:
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
+  /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.0):
+    resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.24.0
+      '@babel/helper-plugin-utils': 7.22.5
+    dev: true
+
   /@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.23.3):
     resolution: {integrity: sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==}
     engines: {node: '>=6.9.0'}
@@ -2385,6 +2547,16 @@ packages:
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
+  /@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.24.0):
+    resolution: {integrity: sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.24.0
+      '@babel/helper-plugin-utils': 7.22.5
+    dev: true
+
   /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.23.3):
     resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==}
     peerDependencies:
@@ -2394,6 +2566,15 @@ packages:
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
+  /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.0):
+    resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.24.0
+      '@babel/helper-plugin-utils': 7.22.5
+    dev: true
+
   /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.23.3):
     resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==}
     peerDependencies:
@@ -2403,6 +2584,15 @@ packages:
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
+  /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.0):
+    resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.24.0
+      '@babel/helper-plugin-utils': 7.22.5
+    dev: true
+
   /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.3):
     resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==}
     peerDependencies:
@@ -2412,6 +2602,15 @@ packages:
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
+  /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.0):
+    resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.24.0
+      '@babel/helper-plugin-utils': 7.22.5
+    dev: true
+
   /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.23.3):
     resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==}
     peerDependencies:
@@ -2421,6 +2620,15 @@ packages:
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
+  /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.0):
+    resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.24.0
+      '@babel/helper-plugin-utils': 7.22.5
+    dev: true
+
   /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.3):
     resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==}
     peerDependencies:
@@ -2430,6 +2638,15 @@ packages:
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
+  /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.0):
+    resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.24.0
+      '@babel/helper-plugin-utils': 7.22.5
+    dev: true
+
   /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.23.3):
     resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==}
     peerDependencies:
@@ -2439,13 +2656,22 @@ packages:
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.23.3):
+  /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.0):
+    resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.24.0
+      '@babel/helper-plugin-utils': 7.22.5
+    dev: true
+
+  /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.24.0):
     resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
@@ -2459,6 +2685,16 @@ packages:
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
+  /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.0):
+    resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.24.0
+      '@babel/helper-plugin-utils': 7.22.5
+    dev: true
+
   /@babel/plugin-syntax-typescript@7.23.3(@babel/core@7.23.3):
     resolution: {integrity: sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==}
     engines: {node: '>=6.9.0'}
@@ -2469,751 +2705,719 @@ packages:
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.23.3):
+  /@babel/plugin-syntax-typescript@7.23.3(@babel/core@7.24.0):
+    resolution: {integrity: sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==}
+    engines: {node: '>=6.9.0'}
+    peerDependencies:
+      '@babel/core': ^7.0.0-0
+    dependencies:
+      '@babel/core': 7.24.0
+      '@babel/helper-plugin-utils': 7.22.5
+    dev: true
+
+  /@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.24.0):
     resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0
     dependencies:
-      '@babel/core': 7.23.3
-      '@babel/helper-create-regexp-features-plugin': 7.22.9(@babel/core@7.23.3)
+      '@babel/core': 7.24.0
+      '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.0)
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-arrow-functions@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-arrow-functions@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-async-generator-functions@7.23.4(@babel/core@7.23.3):
+  /@babel/plugin-transform-async-generator-functions@7.23.4(@babel/core@7.24.0):
     resolution: {integrity: sha512-efdkfPhHYTtn0G6n2ddrESE91fgXxjlqLsnUtPWnJs4a4mZIbUaK7ffqKIIUKXSHwcDvaCVX6GXkaJJFqtX7jw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-environment-visitor': 7.22.20
       '@babel/helper-plugin-utils': 7.22.5
-      '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.23.3)
-      '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.23.3)
+      '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.24.0)
+      '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.0)
     dev: true
 
-  /@babel/plugin-transform-async-to-generator@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-async-to-generator@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-module-imports': 7.22.15
       '@babel/helper-plugin-utils': 7.22.5
-      '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.23.3)
+      '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.24.0)
     dev: true
 
-  /@babel/plugin-transform-block-scoped-functions@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-block-scoped-functions@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-block-scoping@7.23.4(@babel/core@7.23.3):
+  /@babel/plugin-transform-block-scoping@7.23.4(@babel/core@7.24.0):
     resolution: {integrity: sha512-0QqbP6B6HOh7/8iNR4CQU2Th/bbRtBp4KS9vcaZd1fZ0wSh5Fyssg0UCIHwxh+ka+pNDREbVLQnHCMHKZfPwfw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-class-properties@7.22.5(@babel/core@7.23.3):
-    resolution: {integrity: sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.23.3
-      '@babel/helper-create-class-features-plugin': 7.22.9(@babel/core@7.23.3)
-      '@babel/helper-plugin-utils': 7.22.5
-    dev: true
-
-  /@babel/plugin-transform-class-properties@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-class-properties@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
-      '@babel/helper-create-class-features-plugin': 7.23.6(@babel/core@7.23.3)
+      '@babel/core': 7.24.0
+      '@babel/helper-create-class-features-plugin': 7.23.5(@babel/core@7.24.0)
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-class-static-block@7.23.4(@babel/core@7.23.3):
+  /@babel/plugin-transform-class-static-block@7.23.4(@babel/core@7.24.0):
     resolution: {integrity: sha512-nsWu/1M+ggti1SOALj3hfx5FXzAY06fwPJsUZD4/A5e1bWi46VUIWtD+kOX6/IdhXGsXBWllLFDSnqSCdUNydQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.12.0
     dependencies:
-      '@babel/core': 7.23.3
-      '@babel/helper-create-class-features-plugin': 7.23.6(@babel/core@7.23.3)
+      '@babel/core': 7.24.0
+      '@babel/helper-create-class-features-plugin': 7.23.5(@babel/core@7.24.0)
       '@babel/helper-plugin-utils': 7.22.5
-      '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.23.3)
+      '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.0)
     dev: true
 
-  /@babel/plugin-transform-classes@7.23.5(@babel/core@7.23.3):
+  /@babel/plugin-transform-classes@7.23.5(@babel/core@7.24.0):
     resolution: {integrity: sha512-jvOTR4nicqYC9yzOHIhXG5emiFEOpappSJAl73SDSEDcybD+Puuze8Tnpb9p9qEyYup24tq891gkaygIFvWDqg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-annotate-as-pure': 7.22.5
       '@babel/helper-compilation-targets': 7.23.6
       '@babel/helper-environment-visitor': 7.22.20
       '@babel/helper-function-name': 7.23.0
       '@babel/helper-optimise-call-expression': 7.22.5
       '@babel/helper-plugin-utils': 7.22.5
-      '@babel/helper-replace-supers': 7.22.20(@babel/core@7.23.3)
+      '@babel/helper-replace-supers': 7.22.20(@babel/core@7.24.0)
       '@babel/helper-split-export-declaration': 7.22.6
       globals: 11.12.0
     dev: true
 
-  /@babel/plugin-transform-computed-properties@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-computed-properties@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
-      '@babel/template': 7.22.15
+      '@babel/template': 7.24.0
     dev: true
 
-  /@babel/plugin-transform-destructuring@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-destructuring@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-dotall-regex@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-dotall-regex@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
-      '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.3)
+      '@babel/core': 7.24.0
+      '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.0)
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-duplicate-keys@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-duplicate-keys@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-dynamic-import@7.23.4(@babel/core@7.23.3):
+  /@babel/plugin-transform-dynamic-import@7.23.4(@babel/core@7.24.0):
     resolution: {integrity: sha512-V6jIbLhdJK86MaLh4Jpghi8ho5fGzt3imHOBu/x0jlBaPYqDoWz4RDXjmMOfnh+JWNaQleEAByZLV0QzBT4YQQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
-      '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.23.3)
+      '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.0)
     dev: true
 
-  /@babel/plugin-transform-exponentiation-operator@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-exponentiation-operator@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-builder-binary-assignment-operator-visitor': 7.22.15
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-export-namespace-from@7.23.4(@babel/core@7.23.3):
+  /@babel/plugin-transform-export-namespace-from@7.23.4(@babel/core@7.24.0):
     resolution: {integrity: sha512-GzuSBcKkx62dGzZI1WVgTWvkkz84FZO5TC5T8dl/Tht/rAla6Dg/Mz9Yhypg+ezVACf/rgDuQt3kbWEv7LdUDQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
-      '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.23.3)
+      '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.0)
     dev: true
 
-  /@babel/plugin-transform-flow-strip-types@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-flow-strip-types@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-26/pQTf9nQSNVJCrLB1IkHUKyPxR+lMrH2QDPG89+Znu9rAMbtrybdbWeE9bb7gzjmE5iXHEY+e0HUwM6Co93Q==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
-      '@babel/plugin-syntax-flow': 7.23.3(@babel/core@7.23.3)
+      '@babel/plugin-syntax-flow': 7.23.3(@babel/core@7.24.0)
     dev: true
 
-  /@babel/plugin-transform-for-of@7.23.6(@babel/core@7.23.3):
+  /@babel/plugin-transform-for-of@7.23.6(@babel/core@7.24.0):
     resolution: {integrity: sha512-aYH4ytZ0qSuBbpfhuofbg/e96oQ7U2w1Aw/UQmKT+1l39uEhUPoFS3fHevDc1G0OvewyDudfMKY1OulczHzWIw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
       '@babel/helper-skip-transparent-expression-wrappers': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-function-name@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-function-name@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-compilation-targets': 7.23.6
       '@babel/helper-function-name': 7.23.0
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-json-strings@7.23.4(@babel/core@7.23.3):
+  /@babel/plugin-transform-json-strings@7.23.4(@babel/core@7.24.0):
     resolution: {integrity: sha512-81nTOqM1dMwZ/aRXQ59zVubN9wHGqk6UtqRK+/q+ciXmRy8fSolhGVvG09HHRGo4l6fr/c4ZhXUQH0uFW7PZbg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
-      '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.23.3)
+      '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.0)
     dev: true
 
-  /@babel/plugin-transform-literals@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-literals@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-logical-assignment-operators@7.23.4(@babel/core@7.23.3):
+  /@babel/plugin-transform-logical-assignment-operators@7.23.4(@babel/core@7.24.0):
     resolution: {integrity: sha512-Mc/ALf1rmZTP4JKKEhUwiORU+vcfarFVLfcFiolKUo6sewoxSEgl36ak5t+4WamRsNr6nzjZXQjM35WsU+9vbg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
-      '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.23.3)
+      '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.0)
     dev: true
 
-  /@babel/plugin-transform-member-expression-literals@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-member-expression-literals@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-modules-amd@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-modules-amd@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
-      '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.3)
+      '@babel/core': 7.24.0
+      '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.0)
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-modules-commonjs@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-modules-commonjs@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
-      '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.3)
+      '@babel/core': 7.24.0
+      '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.0)
       '@babel/helper-plugin-utils': 7.22.5
       '@babel/helper-simple-access': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-modules-systemjs@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-modules-systemjs@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-hoist-variables': 7.22.5
-      '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.3)
+      '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.0)
       '@babel/helper-plugin-utils': 7.22.5
       '@babel/helper-validator-identifier': 7.22.20
     dev: true
 
-  /@babel/plugin-transform-modules-umd@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-modules-umd@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
-      '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.3)
+      '@babel/core': 7.24.0
+      '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.0)
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-named-capturing-groups-regex@7.22.5(@babel/core@7.23.3):
+  /@babel/plugin-transform-named-capturing-groups-regex@7.22.5(@babel/core@7.24.0):
     resolution: {integrity: sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0
     dependencies:
-      '@babel/core': 7.23.3
-      '@babel/helper-create-regexp-features-plugin': 7.22.9(@babel/core@7.23.3)
+      '@babel/core': 7.24.0
+      '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.0)
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-new-target@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-new-target@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-nullish-coalescing-operator@7.23.4(@babel/core@7.23.3):
+  /@babel/plugin-transform-nullish-coalescing-operator@7.23.4(@babel/core@7.24.0):
     resolution: {integrity: sha512-jHE9EVVqHKAQx+VePv5LLGHjmHSJR76vawFPTdlxR/LVJPfOEGxREQwQfjuZEOPTwG92X3LINSh3M40Rv4zpVA==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
-      '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.23.3)
+      '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.0)
     dev: true
 
-  /@babel/plugin-transform-numeric-separator@7.23.4(@babel/core@7.23.3):
+  /@babel/plugin-transform-numeric-separator@7.23.4(@babel/core@7.24.0):
     resolution: {integrity: sha512-mps6auzgwjRrwKEZA05cOwuDc9FAzoyFS4ZsG/8F43bTLf/TgkJg7QXOrPO1JO599iA3qgK9MXdMGOEC8O1h6Q==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
-      '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.23.3)
+      '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.0)
     dev: true
 
-  /@babel/plugin-transform-object-rest-spread@7.23.4(@babel/core@7.23.3):
+  /@babel/plugin-transform-object-rest-spread@7.23.4(@babel/core@7.24.0):
     resolution: {integrity: sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
       '@babel/compat-data': 7.23.5
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-compilation-targets': 7.23.6
       '@babel/helper-plugin-utils': 7.22.5
-      '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-parameters': 7.23.3(@babel/core@7.23.3)
+      '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-parameters': 7.23.3(@babel/core@7.24.0)
     dev: true
 
-  /@babel/plugin-transform-object-super@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-object-super@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
-      '@babel/helper-replace-supers': 7.22.20(@babel/core@7.23.3)
+      '@babel/helper-replace-supers': 7.22.20(@babel/core@7.24.0)
     dev: true
 
-  /@babel/plugin-transform-optional-catch-binding@7.23.4(@babel/core@7.23.3):
+  /@babel/plugin-transform-optional-catch-binding@7.23.4(@babel/core@7.24.0):
     resolution: {integrity: sha512-XIq8t0rJPHf6Wvmbn9nFxU6ao4c7WhghTR5WyV8SrJfUFzyxhCm4nhC+iAp3HFhbAKLfYpgzhJ6t4XCtVwqO5A==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
-      '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.23.3)
+      '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.0)
     dev: true
 
-  /@babel/plugin-transform-optional-chaining@7.23.4(@babel/core@7.23.3):
+  /@babel/plugin-transform-optional-chaining@7.23.4(@babel/core@7.24.0):
     resolution: {integrity: sha512-ZU8y5zWOfjM5vZ+asjgAPwDaBjJzgufjES89Rs4Lpq63O300R/kOz30WCLo6BxxX6QVEilwSlpClnG5cZaikTA==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
       '@babel/helper-skip-transparent-expression-wrappers': 7.22.5
-      '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.3)
+      '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.0)
     dev: true
 
-  /@babel/plugin-transform-parameters@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-parameters@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-private-methods@7.22.5(@babel/core@7.23.3):
-    resolution: {integrity: sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.23.3
-      '@babel/helper-create-class-features-plugin': 7.22.9(@babel/core@7.23.3)
-      '@babel/helper-plugin-utils': 7.22.5
-    dev: true
-
-  /@babel/plugin-transform-private-methods@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-private-methods@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
-      '@babel/helper-create-class-features-plugin': 7.23.6(@babel/core@7.23.3)
+      '@babel/core': 7.24.0
+      '@babel/helper-create-class-features-plugin': 7.23.5(@babel/core@7.24.0)
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-private-property-in-object@7.23.4(@babel/core@7.23.3):
+  /@babel/plugin-transform-private-property-in-object@7.23.4(@babel/core@7.24.0):
     resolution: {integrity: sha512-9G3K1YqTq3F4Vt88Djx1UZ79PDyj+yKRnUy7cZGSMe+a7jkwD259uKKuUzQlPkGam7R+8RJwh5z4xO27fA1o2A==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-annotate-as-pure': 7.22.5
-      '@babel/helper-create-class-features-plugin': 7.23.6(@babel/core@7.23.3)
+      '@babel/helper-create-class-features-plugin': 7.23.5(@babel/core@7.24.0)
       '@babel/helper-plugin-utils': 7.22.5
-      '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.23.3)
+      '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.0)
     dev: true
 
-  /@babel/plugin-transform-property-literals@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-property-literals@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-react-jsx-self@7.21.0(@babel/core@7.23.3):
-    resolution: {integrity: sha512-f/Eq+79JEu+KUANFks9UZCcvydOOGMgF7jBrcwjHa5jTZD8JivnhCJYvmlhR/WTXBWonDExPoW0eO/CR4QJirA==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.23.3
-      '@babel/helper-plugin-utils': 7.22.5
-    dev: true
-
-  /@babel/plugin-transform-react-jsx-source@7.19.6(@babel/core@7.23.3):
-    resolution: {integrity: sha512-RpAi004QyMNisst/pvSanoRdJ4q+jMCWyk9zdw/CyLB9j8RXEahodR6l2GyttDRyEVWZtbN+TpLiHJ3t34LbsQ==}
-    engines: {node: '>=6.9.0'}
-    peerDependencies:
-      '@babel/core': ^7.0.0-0
-    dependencies:
-      '@babel/core': 7.23.3
-      '@babel/helper-plugin-utils': 7.22.5
-    dev: true
-
-  /@babel/plugin-transform-regenerator@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-regenerator@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
       regenerator-transform: 0.15.2
     dev: true
 
-  /@babel/plugin-transform-reserved-words@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-reserved-words@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-shorthand-properties@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-shorthand-properties@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-spread@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-spread@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
       '@babel/helper-skip-transparent-expression-wrappers': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-sticky-regex@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-sticky-regex@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-template-literals@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-template-literals@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-typeof-symbol@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-typeof-symbol@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-typescript@7.23.6(@babel/core@7.23.3):
+  /@babel/plugin-transform-typescript@7.23.6(@babel/core@7.24.0):
     resolution: {integrity: sha512-6cBG5mBvUu4VUD04OHKnYzbuHNP8huDsD3EDqqpIpsswTDoqHCjLoHb6+QgsV1WsT2nipRqCPgxD3LXnEO7XfA==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-annotate-as-pure': 7.22.5
-      '@babel/helper-create-class-features-plugin': 7.23.6(@babel/core@7.23.3)
+      '@babel/helper-create-class-features-plugin': 7.24.0(@babel/core@7.24.0)
       '@babel/helper-plugin-utils': 7.22.5
-      '@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.23.3)
+      '@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.24.0)
     dev: true
 
-  /@babel/plugin-transform-unicode-escapes@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-unicode-escapes@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-unicode-property-regex@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-unicode-property-regex@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
-      '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.3)
+      '@babel/core': 7.24.0
+      '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.0)
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-unicode-regex@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-unicode-regex@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
-      '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.3)
+      '@babel/core': 7.24.0
+      '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.0)
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/plugin-transform-unicode-sets-regex@7.23.3(@babel/core@7.23.3):
+  /@babel/plugin-transform-unicode-sets-regex@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0
     dependencies:
-      '@babel/core': 7.23.3
-      '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.3)
+      '@babel/core': 7.24.0
+      '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.24.0)
       '@babel/helper-plugin-utils': 7.22.5
     dev: true
 
-  /@babel/preset-env@7.23.6(@babel/core@7.23.3):
+  /@babel/preset-env@7.23.6(@babel/core@7.24.0):
     resolution: {integrity: sha512-2XPn/BqKkZCpzYhUUNZ1ssXw7DcXfKQEjv/uXZUXgaebCMYmkEsfZ2yY+vv+xtXv50WmL5SGhyB6/xsWxIvvOQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
       '@babel/compat-data': 7.23.5
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-compilation-targets': 7.23.6
       '@babel/helper-plugin-utils': 7.22.5
       '@babel/helper-validator-option': 7.23.5
-      '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.23.3)
-      '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.23.3)
-      '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.23.3)
-      '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.23.3)
-      '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.23.3)
-      '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.23.3)
-      '@babel/plugin-syntax-import-assertions': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-syntax-import-attributes': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.23.3)
-      '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.23.3)
-      '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.23.3)
-      '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.23.3)
-      '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.23.3)
-      '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.23.3)
-      '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.23.3)
-      '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.3)
-      '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.23.3)
-      '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.23.3)
-      '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.23.3)
-      '@babel/plugin-transform-arrow-functions': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-async-generator-functions': 7.23.4(@babel/core@7.23.3)
-      '@babel/plugin-transform-async-to-generator': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-block-scoped-functions': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-block-scoping': 7.23.4(@babel/core@7.23.3)
-      '@babel/plugin-transform-class-properties': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-class-static-block': 7.23.4(@babel/core@7.23.3)
-      '@babel/plugin-transform-classes': 7.23.5(@babel/core@7.23.3)
-      '@babel/plugin-transform-computed-properties': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-destructuring': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-dotall-regex': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-duplicate-keys': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-dynamic-import': 7.23.4(@babel/core@7.23.3)
-      '@babel/plugin-transform-exponentiation-operator': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-export-namespace-from': 7.23.4(@babel/core@7.23.3)
-      '@babel/plugin-transform-for-of': 7.23.6(@babel/core@7.23.3)
-      '@babel/plugin-transform-function-name': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-json-strings': 7.23.4(@babel/core@7.23.3)
-      '@babel/plugin-transform-literals': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-logical-assignment-operators': 7.23.4(@babel/core@7.23.3)
-      '@babel/plugin-transform-member-expression-literals': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-modules-amd': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-modules-commonjs': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-modules-systemjs': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-modules-umd': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-named-capturing-groups-regex': 7.22.5(@babel/core@7.23.3)
-      '@babel/plugin-transform-new-target': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-nullish-coalescing-operator': 7.23.4(@babel/core@7.23.3)
-      '@babel/plugin-transform-numeric-separator': 7.23.4(@babel/core@7.23.3)
-      '@babel/plugin-transform-object-rest-spread': 7.23.4(@babel/core@7.23.3)
-      '@babel/plugin-transform-object-super': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-optional-catch-binding': 7.23.4(@babel/core@7.23.3)
-      '@babel/plugin-transform-optional-chaining': 7.23.4(@babel/core@7.23.3)
-      '@babel/plugin-transform-parameters': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-private-methods': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-private-property-in-object': 7.23.4(@babel/core@7.23.3)
-      '@babel/plugin-transform-property-literals': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-regenerator': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-reserved-words': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-shorthand-properties': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-spread': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-sticky-regex': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-template-literals': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-typeof-symbol': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-unicode-escapes': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-unicode-property-regex': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-unicode-regex': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-unicode-sets-regex': 7.23.3(@babel/core@7.23.3)
-      '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.23.3)
-      babel-plugin-polyfill-corejs2: 0.4.7(@babel/core@7.23.3)
-      babel-plugin-polyfill-corejs3: 0.8.7(@babel/core@7.23.3)
-      babel-plugin-polyfill-regenerator: 0.5.4(@babel/core@7.23.3)
-      core-js-compat: 3.31.1
+      '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.0)
+      '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.0)
+      '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.0)
+      '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.0)
+      '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.0)
+      '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.0)
+      '@babel/plugin-syntax-import-assertions': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-syntax-import-attributes': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.0)
+      '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.0)
+      '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.0)
+      '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.0)
+      '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.0)
+      '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.0)
+      '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.0)
+      '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.0)
+      '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.0)
+      '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.0)
+      '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.24.0)
+      '@babel/plugin-transform-arrow-functions': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-async-generator-functions': 7.23.4(@babel/core@7.24.0)
+      '@babel/plugin-transform-async-to-generator': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-block-scoped-functions': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-block-scoping': 7.23.4(@babel/core@7.24.0)
+      '@babel/plugin-transform-class-properties': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-class-static-block': 7.23.4(@babel/core@7.24.0)
+      '@babel/plugin-transform-classes': 7.23.5(@babel/core@7.24.0)
+      '@babel/plugin-transform-computed-properties': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-destructuring': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-dotall-regex': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-duplicate-keys': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-dynamic-import': 7.23.4(@babel/core@7.24.0)
+      '@babel/plugin-transform-exponentiation-operator': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-export-namespace-from': 7.23.4(@babel/core@7.24.0)
+      '@babel/plugin-transform-for-of': 7.23.6(@babel/core@7.24.0)
+      '@babel/plugin-transform-function-name': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-json-strings': 7.23.4(@babel/core@7.24.0)
+      '@babel/plugin-transform-literals': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-logical-assignment-operators': 7.23.4(@babel/core@7.24.0)
+      '@babel/plugin-transform-member-expression-literals': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-modules-amd': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-modules-commonjs': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-modules-systemjs': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-modules-umd': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-named-capturing-groups-regex': 7.22.5(@babel/core@7.24.0)
+      '@babel/plugin-transform-new-target': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-nullish-coalescing-operator': 7.23.4(@babel/core@7.24.0)
+      '@babel/plugin-transform-numeric-separator': 7.23.4(@babel/core@7.24.0)
+      '@babel/plugin-transform-object-rest-spread': 7.23.4(@babel/core@7.24.0)
+      '@babel/plugin-transform-object-super': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-optional-catch-binding': 7.23.4(@babel/core@7.24.0)
+      '@babel/plugin-transform-optional-chaining': 7.23.4(@babel/core@7.24.0)
+      '@babel/plugin-transform-parameters': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-private-methods': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-private-property-in-object': 7.23.4(@babel/core@7.24.0)
+      '@babel/plugin-transform-property-literals': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-regenerator': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-reserved-words': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-shorthand-properties': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-spread': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-sticky-regex': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-template-literals': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-typeof-symbol': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-unicode-escapes': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-unicode-property-regex': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-unicode-regex': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-unicode-sets-regex': 7.23.3(@babel/core@7.24.0)
+      '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.24.0)
+      babel-plugin-polyfill-corejs2: 0.4.7(@babel/core@7.24.0)
+      babel-plugin-polyfill-corejs3: 0.8.7(@babel/core@7.24.0)
+      babel-plugin-polyfill-regenerator: 0.5.4(@babel/core@7.24.0)
+      core-js-compat: 3.34.0
       semver: 6.3.1
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@babel/preset-flow@7.23.3(@babel/core@7.23.3):
+  /@babel/preset-flow@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-7yn6hl8RIv+KNk6iIrGZ+D06VhVY35wLVf23Cz/mMu1zOr7u4MMP4j0nZ9tLf8+4ZFpnib8cFYgB/oYg9hfswA==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
-      '@babel/helper-validator-option': 7.22.15
-      '@babel/plugin-transform-flow-strip-types': 7.23.3(@babel/core@7.23.3)
+      '@babel/helper-validator-option': 7.23.5
+      '@babel/plugin-transform-flow-strip-types': 7.23.3(@babel/core@7.24.0)
     dev: true
 
-  /@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.23.3):
+  /@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.24.0):
     resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==}
     peerDependencies:
       '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
-      '@babel/types': 7.23.4
+      '@babel/types': 7.24.0
       esutils: 2.0.3
     dev: true
 
-  /@babel/preset-typescript@7.23.3(@babel/core@7.23.3):
+  /@babel/preset-typescript@7.23.3(@babel/core@7.24.0):
     resolution: {integrity: sha512-17oIGVlqz6CchO9RFYn5U6ZpWRZIngayYCtrPRSgANSwC2V1Jb+iP74nVxzzXJte8b8BYxrL1yY96xfhTBrNNQ==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@babel/helper-plugin-utils': 7.22.5
-      '@babel/helper-validator-option': 7.22.15
-      '@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-modules-commonjs': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-typescript': 7.23.6(@babel/core@7.23.3)
+      '@babel/helper-validator-option': 7.23.5
+      '@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-modules-commonjs': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-typescript': 7.23.6(@babel/core@7.24.0)
     dev: true
 
-  /@babel/register@7.22.15(@babel/core@7.23.3):
+  /@babel/register@7.22.15(@babel/core@7.24.0):
     resolution: {integrity: sha512-V3Q3EqoQdn65RCgTLwauZaTfd1ShhwPmbBv+1dkZV/HpCGMKVyn6oFcRlI7RaKqiDQjX2Qd3AuoEguBgdjIKlg==}
     engines: {node: '>=6.9.0'}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       clone-deep: 4.0.1
       find-cache-dir: 2.1.0
       make-dir: 2.1.0
@@ -3230,20 +3434,13 @@ packages:
     engines: {node: '>=6.9.0'}
     dependencies:
       regenerator-runtime: 0.13.11
-
-  /@babel/runtime@7.23.2:
-    resolution: {integrity: sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==}
-    engines: {node: '>=6.9.0'}
-    dependencies:
-      regenerator-runtime: 0.14.0
-    dev: true
+    dev: false
 
   /@babel/runtime@7.23.4:
     resolution: {integrity: sha512-2Yv65nlWnWlSpe3fXEyX5i7fx5kIKo4Qbcj+hMO0odwaneFjfXw5fdum+4yL20O0QiaHpia0cYQ9xpNMqrBwHg==}
     engines: {node: '>=6.9.0'}
     dependencies:
       regenerator-runtime: 0.14.0
-    dev: false
 
   /@babel/template@7.22.15:
     resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==}
@@ -3251,37 +3448,38 @@ packages:
     dependencies:
       '@babel/code-frame': 7.23.4
       '@babel/parser': 7.23.4
-      '@babel/types': 7.23.4
+      '@babel/types': 7.24.0
     dev: true
 
-  /@babel/traverse@7.23.4:
-    resolution: {integrity: sha512-IYM8wSUwunWTB6tFC2dkKZhxbIjHoWemdK+3f8/wq8aKhbUscxD5MX72ubd90fxvFknaLPeGw5ycU84V1obHJg==}
+  /@babel/template@7.24.0:
+    resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==}
     engines: {node: '>=6.9.0'}
     dependencies:
-      '@babel/code-frame': 7.23.4
-      '@babel/generator': 7.23.4
+      '@babel/code-frame': 7.23.5
+      '@babel/parser': 7.24.0
+      '@babel/types': 7.24.0
+    dev: true
+
+  /@babel/traverse@7.24.0:
+    resolution: {integrity: sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==}
+    engines: {node: '>=6.9.0'}
+    dependencies:
+      '@babel/code-frame': 7.23.5
+      '@babel/generator': 7.23.6
       '@babel/helper-environment-visitor': 7.22.20
       '@babel/helper-function-name': 7.23.0
       '@babel/helper-hoist-variables': 7.22.5
       '@babel/helper-split-export-declaration': 7.22.6
-      '@babel/parser': 7.23.4
-      '@babel/types': 7.23.4
+      '@babel/parser': 7.24.0
+      '@babel/types': 7.24.0
       debug: 4.3.4(supports-color@8.1.1)
       globals: 11.12.0
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@babel/types@7.22.17:
-    resolution: {integrity: sha512-YSQPHLFtQNE5xN9tHuZnzu8vPr61wVTBZdfv1meex1NBosa4iT05k/Jw06ddJugi4bk7The/oSwQGFcksmEJQg==}
-    engines: {node: '>=6.9.0'}
-    dependencies:
-      '@babel/helper-string-parser': 7.22.5
-      '@babel/helper-validator-identifier': 7.22.15
-      to-fast-properties: 2.0.0
-
-  /@babel/types@7.23.4:
-    resolution: {integrity: sha512-7uIFwVYpoplT5jp/kVv6EF93VaJ8H+Yn5IczYiaAi98ajzjfoZfslet/e0sLh+wVBjb2qqIut1b0S26VSafsSQ==}
+  /@babel/types@7.24.0:
+    resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==}
     engines: {node: '>=6.9.0'}
     dependencies:
       '@babel/helper-string-parser': 7.23.4
@@ -3296,83 +3494,47 @@ packages:
     resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
     dev: true
 
-  /@bull-board/api@5.10.2(@bull-board/ui@5.10.2):
-    resolution: {integrity: sha512-Gx98cqN0cryJB35mVKjYsEnD3NxArWY3Xi2E5Wrr17QTVOzWEP4jyDQ/riiapVdnYqc9RSsxCCmdIaNdNPcXlQ==}
+  /@bull-board/api@5.14.2(@bull-board/ui@5.14.2):
+    resolution: {integrity: sha512-0wppAGPU7ZMwWMpzkmtrlmm7ySI5immymyaRS1cVNJ54rUiGOZP5tnm+Sj7MwPdf63rxqIM843un8+PvQyARGg==}
     peerDependencies:
-      '@bull-board/ui': 5.10.2
+      '@bull-board/ui': 5.14.2
     dependencies:
-      '@bull-board/ui': 5.10.2
+      '@bull-board/ui': 5.14.2
       redis-info: 3.1.0
     dev: false
 
-  /@bull-board/fastify@5.10.2:
-    resolution: {integrity: sha512-NrV1PBu1jwXMBnLslxWLjmt4Qb0oPDSngcUXRll5B8Lvm6E8jtecmnVuNb2X1EtpIGVqhgwlGZ+Q7AC+3ZBMFg==}
+  /@bull-board/fastify@5.14.2:
+    resolution: {integrity: sha512-GQMK70tKOu2gjBi2pjWXMXcftzWRvQNSm+deLmGlJUgqUUbNlzIGRyvaTk7giT4CFzgKcP+hT+lphcAsGTKBQw==}
     dependencies:
-      '@bull-board/api': 5.10.2(@bull-board/ui@5.10.2)
-      '@bull-board/ui': 5.10.2
+      '@bull-board/api': 5.14.2(@bull-board/ui@5.14.2)
+      '@bull-board/ui': 5.14.2
       '@fastify/static': 6.12.0
       '@fastify/view': 8.2.0
       ejs: 3.1.9
     dev: false
 
-  /@bull-board/ui@5.10.2:
-    resolution: {integrity: sha512-wU9XmrX/COISZ3+sn3VEDB1UtPt7szu4QSKTw1O0q+U1JLM4Kxfs3tH9ZAIulzMrY+CQtkJXd+dKZPuRqy4rfQ==}
+  /@bull-board/ui@5.14.2:
+    resolution: {integrity: sha512-NiyKWLjKjy29I6ySCnSYbzGX4ZJyPE4xlS5/Z5dVsF2bJLoAV+yD1obflxteJMt60FiEgLV7tfs6tMSVa+Htew==}
     dependencies:
-      '@bull-board/api': 5.10.2(@bull-board/ui@5.10.2)
+      '@bull-board/api': 5.14.2(@bull-board/ui@5.14.2)
     dev: false
 
+  /@bundled-es-modules/cookie@2.0.0:
+    resolution: {integrity: sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==}
+    dependencies:
+      cookie: 0.5.0
+    dev: true
+
+  /@bundled-es-modules/statuses@1.0.1:
+    resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==}
+    dependencies:
+      statuses: 2.0.1
+    dev: true
+
   /@canvas/image-data@1.0.0:
     resolution: {integrity: sha512-BxOqI5LgsIQP1odU5KMwV9yoijleOPzHL18/YvNqF9KFSGF2K/DLlYAbDQsWqd/1nbaFuSkYD/191dpMtNh4vw==}
     dev: false
 
-  /@cbor-extract/cbor-extract-darwin-arm64@2.1.1:
-    resolution: {integrity: sha512-blVBy5MXz6m36Vx0DfLd7PChOQKEs8lK2bD1WJn/vVgG4FXZiZmZb2GECHFvVPA5T7OnODd9xZiL3nMCv6QUhA==}
-    cpu: [arm64]
-    os: [darwin]
-    requiresBuild: true
-    dev: false
-    optional: true
-
-  /@cbor-extract/cbor-extract-darwin-x64@2.1.1:
-    resolution: {integrity: sha512-h6KFOzqk8jXTvkOftyRIWGrd7sKQzQv2jVdTL9nKSf3D2drCvQB/LHUxAOpPXo3pv2clDtKs3xnHalpEh3rDsw==}
-    cpu: [x64]
-    os: [darwin]
-    requiresBuild: true
-    dev: false
-    optional: true
-
-  /@cbor-extract/cbor-extract-linux-arm64@2.1.1:
-    resolution: {integrity: sha512-SxAaRcYf8S0QHaMc7gvRSiTSr7nUYMqbUdErBEu+HYA4Q6UNydx1VwFE68hGcp1qvxcy9yT5U7gA+a5XikfwSQ==}
-    cpu: [arm64]
-    os: [linux]
-    requiresBuild: true
-    dev: false
-    optional: true
-
-  /@cbor-extract/cbor-extract-linux-arm@2.1.1:
-    resolution: {integrity: sha512-ds0uikdcIGUjPyraV4oJqyVE5gl/qYBpa/Wnh6l6xLE2lj/hwnjT2XcZCChdXwW/YFZ1LUHs6waoYN8PmK0nKQ==}
-    cpu: [arm]
-    os: [linux]
-    requiresBuild: true
-    dev: false
-    optional: true
-
-  /@cbor-extract/cbor-extract-linux-x64@2.1.1:
-    resolution: {integrity: sha512-GVK+8fNIE9lJQHAlhOROYiI0Yd4bAZ4u++C2ZjlkS3YmO6hi+FUxe6Dqm+OKWTcMpL/l71N6CQAmaRcb4zyJuA==}
-    cpu: [x64]
-    os: [linux]
-    requiresBuild: true
-    dev: false
-    optional: true
-
-  /@cbor-extract/cbor-extract-win32-x64@2.1.1:
-    resolution: {integrity: sha512-2Niq1C41dCRIDeD8LddiH+mxGlO7HJ612Ll3D/E73ZWBmycued+8ghTr/Ho3CMOWPUEr08XtyBMVXAjqF+TcKw==}
-    cpu: [x64]
-    os: [win32]
-    requiresBuild: true
-    dev: false
-    optional: true
-
   /@colors/colors@1.5.0:
     resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==}
     engines: {node: '>=0.1.90'}
@@ -3526,21 +3688,28 @@ packages:
     engines: {node: '>=10.0.0'}
     dev: true
 
-  /@emotion/use-insertion-effect-with-fallbacks@1.0.0(react@18.2.0):
-    resolution: {integrity: sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==}
+  /@emnapi/runtime@0.45.0:
+    resolution: {integrity: sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w==}
+    requiresBuild: true
+    dependencies:
+      tslib: 2.6.2
+    dev: false
+    optional: true
+
+  /@emotion/use-insertion-effect-with-fallbacks@1.0.1(react@18.2.0):
+    resolution: {integrity: sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw==}
     peerDependencies:
       react: '>=16.8.0'
     dependencies:
       react: 18.2.0
     dev: true
 
-  /@esbuild/android-arm64@0.18.17:
-    resolution: {integrity: sha512-9np+YYdNDed5+Jgr1TdWBsozZ85U1Oa3xW0c7TWqH0y2aGghXtZsuT8nYRbzOMcl0bXZXjOGbksoTtVOlWrRZg==}
+  /@esbuild/aix-ppc64@0.19.11:
+    resolution: {integrity: sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==}
     engines: {node: '>=12'}
-    cpu: [arm64]
-    os: [android]
+    cpu: [ppc64]
+    os: [aix]
     requiresBuild: true
-    dev: true
     optional: true
 
   /@esbuild/android-arm64@0.18.20:
@@ -3552,23 +3721,14 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/android-arm64@0.19.9:
-    resolution: {integrity: sha512-q4cR+6ZD0938R19MyEW3jEsMzbb/1rulLXiNAJQADD/XYp7pT+rOS5JGxvpRW8dFDEfjW4wLgC/3FXIw4zYglQ==}
+  /@esbuild/android-arm64@0.19.11:
+    resolution: {integrity: sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [android]
     requiresBuild: true
     optional: true
 
-  /@esbuild/android-arm@0.18.17:
-    resolution: {integrity: sha512-wHsmJG/dnL3OkpAcwbgoBTTMHVi4Uyou3F5mf58ZtmUyIKfcdA7TROav/6tCzET4A3QW2Q2FC+eFneMU+iyOxg==}
-    engines: {node: '>=12'}
-    cpu: [arm]
-    os: [android]
-    requiresBuild: true
-    dev: true
-    optional: true
-
   /@esbuild/android-arm@0.18.20:
     resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==}
     engines: {node: '>=12'}
@@ -3578,23 +3738,14 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/android-arm@0.19.9:
-    resolution: {integrity: sha512-jkYjjq7SdsWuNI6b5quymW0oC83NN5FdRPuCbs9HZ02mfVdAP8B8eeqLSYU3gb6OJEaY5CQabtTFbqBf26H3GA==}
+  /@esbuild/android-arm@0.19.11:
+    resolution: {integrity: sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==}
     engines: {node: '>=12'}
     cpu: [arm]
     os: [android]
     requiresBuild: true
     optional: true
 
-  /@esbuild/android-x64@0.18.17:
-    resolution: {integrity: sha512-O+FeWB/+xya0aLg23hHEM2E3hbfwZzjqumKMSIqcHbNvDa+dza2D0yLuymRBQQnC34CWrsJUXyH2MG5VnLd6uw==}
-    engines: {node: '>=12'}
-    cpu: [x64]
-    os: [android]
-    requiresBuild: true
-    dev: true
-    optional: true
-
   /@esbuild/android-x64@0.18.20:
     resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==}
     engines: {node: '>=12'}
@@ -3604,23 +3755,14 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/android-x64@0.19.9:
-    resolution: {integrity: sha512-KOqoPntWAH6ZxDwx1D6mRntIgZh9KodzgNOy5Ebt9ghzffOk9X2c1sPwtM9P+0eXbefnDhqYfkh5PLP5ULtWFA==}
+  /@esbuild/android-x64@0.19.11:
+    resolution: {integrity: sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [android]
     requiresBuild: true
     optional: true
 
-  /@esbuild/darwin-arm64@0.18.17:
-    resolution: {integrity: sha512-M9uJ9VSB1oli2BE/dJs3zVr9kcCBBsE883prage1NWz6pBS++1oNn/7soPNS3+1DGj0FrkSvnED4Bmlu1VAE9g==}
-    engines: {node: '>=12'}
-    cpu: [arm64]
-    os: [darwin]
-    requiresBuild: true
-    dev: true
-    optional: true
-
   /@esbuild/darwin-arm64@0.18.20:
     resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==}
     engines: {node: '>=12'}
@@ -3630,23 +3772,14 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/darwin-arm64@0.19.9:
-    resolution: {integrity: sha512-KBJ9S0AFyLVx2E5D8W0vExqRW01WqRtczUZ8NRu+Pi+87opZn5tL4Y0xT0mA4FtHctd0ZgwNoN639fUUGlNIWw==}
+  /@esbuild/darwin-arm64@0.19.11:
+    resolution: {integrity: sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [darwin]
     requiresBuild: true
     optional: true
 
-  /@esbuild/darwin-x64@0.18.17:
-    resolution: {integrity: sha512-XDre+J5YeIJDMfp3n0279DFNrGCXlxOuGsWIkRb1NThMZ0BsrWXoTg23Jer7fEXQ9Ye5QjrvXpxnhzl3bHtk0g==}
-    engines: {node: '>=12'}
-    cpu: [x64]
-    os: [darwin]
-    requiresBuild: true
-    dev: true
-    optional: true
-
   /@esbuild/darwin-x64@0.18.20:
     resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==}
     engines: {node: '>=12'}
@@ -3656,23 +3789,14 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/darwin-x64@0.19.9:
-    resolution: {integrity: sha512-vE0VotmNTQaTdX0Q9dOHmMTao6ObjyPm58CHZr1UK7qpNleQyxlFlNCaHsHx6Uqv86VgPmR4o2wdNq3dP1qyDQ==}
+  /@esbuild/darwin-x64@0.19.11:
+    resolution: {integrity: sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [darwin]
     requiresBuild: true
     optional: true
 
-  /@esbuild/freebsd-arm64@0.18.17:
-    resolution: {integrity: sha512-cjTzGa3QlNfERa0+ptykyxs5A6FEUQQF0MuilYXYBGdBxD3vxJcKnzDlhDCa1VAJCmAxed6mYhA2KaJIbtiNuQ==}
-    engines: {node: '>=12'}
-    cpu: [arm64]
-    os: [freebsd]
-    requiresBuild: true
-    dev: true
-    optional: true
-
   /@esbuild/freebsd-arm64@0.18.20:
     resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==}
     engines: {node: '>=12'}
@@ -3682,23 +3806,14 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/freebsd-arm64@0.19.9:
-    resolution: {integrity: sha512-uFQyd/o1IjiEk3rUHSwUKkqZwqdvuD8GevWF065eqgYfexcVkxh+IJgwTaGZVu59XczZGcN/YMh9uF1fWD8j1g==}
+  /@esbuild/freebsd-arm64@0.19.11:
+    resolution: {integrity: sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [freebsd]
     requiresBuild: true
     optional: true
 
-  /@esbuild/freebsd-x64@0.18.17:
-    resolution: {integrity: sha512-sOxEvR8d7V7Kw8QqzxWc7bFfnWnGdaFBut1dRUYtu+EIRXefBc/eIsiUiShnW0hM3FmQ5Zf27suDuHsKgZ5QrA==}
-    engines: {node: '>=12'}
-    cpu: [x64]
-    os: [freebsd]
-    requiresBuild: true
-    dev: true
-    optional: true
-
   /@esbuild/freebsd-x64@0.18.20:
     resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==}
     engines: {node: '>=12'}
@@ -3708,23 +3823,14 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/freebsd-x64@0.19.9:
-    resolution: {integrity: sha512-WMLgWAtkdTbTu1AWacY7uoj/YtHthgqrqhf1OaEWnZb7PQgpt8eaA/F3LkV0E6K/Lc0cUr/uaVP/49iE4M4asA==}
+  /@esbuild/freebsd-x64@0.19.11:
+    resolution: {integrity: sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [freebsd]
     requiresBuild: true
     optional: true
 
-  /@esbuild/linux-arm64@0.18.17:
-    resolution: {integrity: sha512-c9w3tE7qA3CYWjT+M3BMbwMt+0JYOp3vCMKgVBrCl1nwjAlOMYzEo+gG7QaZ9AtqZFj5MbUc885wuBBmu6aADQ==}
-    engines: {node: '>=12'}
-    cpu: [arm64]
-    os: [linux]
-    requiresBuild: true
-    dev: true
-    optional: true
-
   /@esbuild/linux-arm64@0.18.20:
     resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==}
     engines: {node: '>=12'}
@@ -3734,23 +3840,14 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/linux-arm64@0.19.9:
-    resolution: {integrity: sha512-PiPblfe1BjK7WDAKR1Cr9O7VVPqVNpwFcPWgfn4xu0eMemzRp442hXyzF/fSwgrufI66FpHOEJk0yYdPInsmyQ==}
+  /@esbuild/linux-arm64@0.19.11:
+    resolution: {integrity: sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@esbuild/linux-arm@0.18.17:
-    resolution: {integrity: sha512-2d3Lw6wkwgSLC2fIvXKoMNGVaeY8qdN0IC3rfuVxJp89CRfA3e3VqWifGDfuakPmp90+ZirmTfye1n4ncjv2lg==}
-    engines: {node: '>=12'}
-    cpu: [arm]
-    os: [linux]
-    requiresBuild: true
-    dev: true
-    optional: true
-
   /@esbuild/linux-arm@0.18.20:
     resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==}
     engines: {node: '>=12'}
@@ -3760,23 +3857,14 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/linux-arm@0.19.9:
-    resolution: {integrity: sha512-C/ChPohUYoyUaqn1h17m/6yt6OB14hbXvT8EgM1ZWaiiTYz7nWZR0SYmMnB5BzQA4GXl3BgBO1l8MYqL/He3qw==}
+  /@esbuild/linux-arm@0.19.11:
+    resolution: {integrity: sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==}
     engines: {node: '>=12'}
     cpu: [arm]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@esbuild/linux-ia32@0.18.17:
-    resolution: {integrity: sha512-1DS9F966pn5pPnqXYz16dQqWIB0dmDfAQZd6jSSpiT9eX1NzKh07J6VKR3AoXXXEk6CqZMojiVDSZi1SlmKVdg==}
-    engines: {node: '>=12'}
-    cpu: [ia32]
-    os: [linux]
-    requiresBuild: true
-    dev: true
-    optional: true
-
   /@esbuild/linux-ia32@0.18.20:
     resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==}
     engines: {node: '>=12'}
@@ -3786,23 +3874,14 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/linux-ia32@0.19.9:
-    resolution: {integrity: sha512-f37i/0zE0MjDxijkPSQw1CO/7C27Eojqb+r3BbHVxMLkj8GCa78TrBZzvPyA/FNLUMzP3eyHCVkAopkKVja+6Q==}
+  /@esbuild/linux-ia32@0.19.11:
+    resolution: {integrity: sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==}
     engines: {node: '>=12'}
     cpu: [ia32]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@esbuild/linux-loong64@0.18.17:
-    resolution: {integrity: sha512-EvLsxCk6ZF0fpCB6w6eOI2Fc8KW5N6sHlIovNe8uOFObL2O+Mr0bflPHyHwLT6rwMg9r77WOAWb2FqCQrVnwFg==}
-    engines: {node: '>=12'}
-    cpu: [loong64]
-    os: [linux]
-    requiresBuild: true
-    dev: true
-    optional: true
-
   /@esbuild/linux-loong64@0.18.20:
     resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==}
     engines: {node: '>=12'}
@@ -3812,23 +3891,14 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/linux-loong64@0.19.9:
-    resolution: {integrity: sha512-t6mN147pUIf3t6wUt3FeumoOTPfmv9Cc6DQlsVBpB7eCpLOqQDyWBP1ymXn1lDw4fNUSb/gBcKAmvTP49oIkaA==}
+  /@esbuild/linux-loong64@0.19.11:
+    resolution: {integrity: sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==}
     engines: {node: '>=12'}
     cpu: [loong64]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@esbuild/linux-mips64el@0.18.17:
-    resolution: {integrity: sha512-e0bIdHA5p6l+lwqTE36NAW5hHtw2tNRmHlGBygZC14QObsA3bD4C6sXLJjvnDIjSKhW1/0S3eDy+QmX/uZWEYQ==}
-    engines: {node: '>=12'}
-    cpu: [mips64el]
-    os: [linux]
-    requiresBuild: true
-    dev: true
-    optional: true
-
   /@esbuild/linux-mips64el@0.18.20:
     resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==}
     engines: {node: '>=12'}
@@ -3838,23 +3908,14 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/linux-mips64el@0.19.9:
-    resolution: {integrity: sha512-jg9fujJTNTQBuDXdmAg1eeJUL4Jds7BklOTkkH80ZgQIoCTdQrDaHYgbFZyeTq8zbY+axgptncko3v9p5hLZtw==}
+  /@esbuild/linux-mips64el@0.19.11:
+    resolution: {integrity: sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==}
     engines: {node: '>=12'}
     cpu: [mips64el]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@esbuild/linux-ppc64@0.18.17:
-    resolution: {integrity: sha512-BAAilJ0M5O2uMxHYGjFKn4nJKF6fNCdP1E0o5t5fvMYYzeIqy2JdAP88Az5LHt9qBoUa4tDaRpfWt21ep5/WqQ==}
-    engines: {node: '>=12'}
-    cpu: [ppc64]
-    os: [linux]
-    requiresBuild: true
-    dev: true
-    optional: true
-
   /@esbuild/linux-ppc64@0.18.20:
     resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==}
     engines: {node: '>=12'}
@@ -3864,23 +3925,14 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/linux-ppc64@0.19.9:
-    resolution: {integrity: sha512-tkV0xUX0pUUgY4ha7z5BbDS85uI7ABw3V1d0RNTii7E9lbmV8Z37Pup2tsLV46SQWzjOeyDi1Q7Wx2+QM8WaCQ==}
+  /@esbuild/linux-ppc64@0.19.11:
+    resolution: {integrity: sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==}
     engines: {node: '>=12'}
     cpu: [ppc64]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@esbuild/linux-riscv64@0.18.17:
-    resolution: {integrity: sha512-Wh/HW2MPnC3b8BqRSIme/9Zhab36PPH+3zam5pqGRH4pE+4xTrVLx2+XdGp6fVS3L2x+DrsIcsbMleex8fbE6g==}
-    engines: {node: '>=12'}
-    cpu: [riscv64]
-    os: [linux]
-    requiresBuild: true
-    dev: true
-    optional: true
-
   /@esbuild/linux-riscv64@0.18.20:
     resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==}
     engines: {node: '>=12'}
@@ -3890,23 +3942,14 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/linux-riscv64@0.19.9:
-    resolution: {integrity: sha512-DfLp8dj91cufgPZDXr9p3FoR++m3ZJ6uIXsXrIvJdOjXVREtXuQCjfMfvmc3LScAVmLjcfloyVtpn43D56JFHg==}
+  /@esbuild/linux-riscv64@0.19.11:
+    resolution: {integrity: sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==}
     engines: {node: '>=12'}
     cpu: [riscv64]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@esbuild/linux-s390x@0.18.17:
-    resolution: {integrity: sha512-j/34jAl3ul3PNcK3pfI0NSlBANduT2UO5kZ7FCaK33XFv3chDhICLY8wJJWIhiQ+YNdQ9dxqQctRg2bvrMlYgg==}
-    engines: {node: '>=12'}
-    cpu: [s390x]
-    os: [linux]
-    requiresBuild: true
-    dev: true
-    optional: true
-
   /@esbuild/linux-s390x@0.18.20:
     resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==}
     engines: {node: '>=12'}
@@ -3916,23 +3959,14 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/linux-s390x@0.19.9:
-    resolution: {integrity: sha512-zHbglfEdC88KMgCWpOl/zc6dDYJvWGLiUtmPRsr1OgCViu3z5GncvNVdf+6/56O2Ca8jUU+t1BW261V6kp8qdw==}
+  /@esbuild/linux-s390x@0.19.11:
+    resolution: {integrity: sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==}
     engines: {node: '>=12'}
     cpu: [s390x]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@esbuild/linux-x64@0.18.17:
-    resolution: {integrity: sha512-QM50vJ/y+8I60qEmFxMoxIx4de03pGo2HwxdBeFd4nMh364X6TIBZ6VQ5UQmPbQWUVWHWws5MmJXlHAXvJEmpQ==}
-    engines: {node: '>=12'}
-    cpu: [x64]
-    os: [linux]
-    requiresBuild: true
-    dev: true
-    optional: true
-
   /@esbuild/linux-x64@0.18.20:
     resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==}
     engines: {node: '>=12'}
@@ -3942,23 +3976,14 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/linux-x64@0.19.9:
-    resolution: {integrity: sha512-JUjpystGFFmNrEHQnIVG8hKwvA2DN5o7RqiO1CVX8EN/F/gkCjkUMgVn6hzScpwnJtl2mPR6I9XV1oW8k9O+0A==}
+  /@esbuild/linux-x64@0.19.11:
+    resolution: {integrity: sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@esbuild/netbsd-x64@0.18.17:
-    resolution: {integrity: sha512-/jGlhWR7Sj9JPZHzXyyMZ1RFMkNPjC6QIAan0sDOtIo2TYk3tZn5UDrkE0XgsTQCxWTTOcMPf9p6Rh2hXtl5TQ==}
-    engines: {node: '>=12'}
-    cpu: [x64]
-    os: [netbsd]
-    requiresBuild: true
-    dev: true
-    optional: true
-
   /@esbuild/netbsd-x64@0.18.20:
     resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==}
     engines: {node: '>=12'}
@@ -3968,23 +3993,14 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/netbsd-x64@0.19.9:
-    resolution: {integrity: sha512-GThgZPAwOBOsheA2RUlW5UeroRfESwMq/guy8uEe3wJlAOjpOXuSevLRd70NZ37ZrpO6RHGHgEHvPg1h3S1Jug==}
+  /@esbuild/netbsd-x64@0.19.11:
+    resolution: {integrity: sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [netbsd]
     requiresBuild: true
     optional: true
 
-  /@esbuild/openbsd-x64@0.18.17:
-    resolution: {integrity: sha512-rSEeYaGgyGGf4qZM2NonMhMOP/5EHp4u9ehFiBrg7stH6BYEEjlkVREuDEcQ0LfIl53OXLxNbfuIj7mr5m29TA==}
-    engines: {node: '>=12'}
-    cpu: [x64]
-    os: [openbsd]
-    requiresBuild: true
-    dev: true
-    optional: true
-
   /@esbuild/openbsd-x64@0.18.20:
     resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==}
     engines: {node: '>=12'}
@@ -3994,23 +4010,14 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/openbsd-x64@0.19.9:
-    resolution: {integrity: sha512-Ki6PlzppaFVbLnD8PtlVQfsYw4S9n3eQl87cqgeIw+O3sRr9IghpfSKY62mggdt1yCSZ8QWvTZ9jo9fjDSg9uw==}
+  /@esbuild/openbsd-x64@0.19.11:
+    resolution: {integrity: sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [openbsd]
     requiresBuild: true
     optional: true
 
-  /@esbuild/sunos-x64@0.18.17:
-    resolution: {integrity: sha512-Y7ZBbkLqlSgn4+zot4KUNYst0bFoO68tRgI6mY2FIM+b7ZbyNVtNbDP5y8qlu4/knZZ73fgJDlXID+ohY5zt5g==}
-    engines: {node: '>=12'}
-    cpu: [x64]
-    os: [sunos]
-    requiresBuild: true
-    dev: true
-    optional: true
-
   /@esbuild/sunos-x64@0.18.20:
     resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==}
     engines: {node: '>=12'}
@@ -4020,23 +4027,14 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/sunos-x64@0.19.9:
-    resolution: {integrity: sha512-MLHj7k9hWh4y1ddkBpvRj2b9NCBhfgBt3VpWbHQnXRedVun/hC7sIyTGDGTfsGuXo4ebik2+3ShjcPbhtFwWDw==}
+  /@esbuild/sunos-x64@0.19.11:
+    resolution: {integrity: sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [sunos]
     requiresBuild: true
     optional: true
 
-  /@esbuild/win32-arm64@0.18.17:
-    resolution: {integrity: sha512-bwPmTJsEQcbZk26oYpc4c/8PvTY3J5/QK8jM19DVlEsAB41M39aWovWoHtNm78sd6ip6prilxeHosPADXtEJFw==}
-    engines: {node: '>=12'}
-    cpu: [arm64]
-    os: [win32]
-    requiresBuild: true
-    dev: true
-    optional: true
-
   /@esbuild/win32-arm64@0.18.20:
     resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==}
     engines: {node: '>=12'}
@@ -4046,23 +4044,14 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/win32-arm64@0.19.9:
-    resolution: {integrity: sha512-GQoa6OrQ8G08guMFgeXPH7yE/8Dt0IfOGWJSfSH4uafwdC7rWwrfE6P9N8AtPGIjUzdo2+7bN8Xo3qC578olhg==}
+  /@esbuild/win32-arm64@0.19.11:
+    resolution: {integrity: sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==}
     engines: {node: '>=12'}
     cpu: [arm64]
     os: [win32]
     requiresBuild: true
     optional: true
 
-  /@esbuild/win32-ia32@0.18.17:
-    resolution: {integrity: sha512-H/XaPtPKli2MhW+3CQueo6Ni3Avggi6hP/YvgkEe1aSaxw+AeO8MFjq8DlgfTd9Iz4Yih3QCZI6YLMoyccnPRg==}
-    engines: {node: '>=12'}
-    cpu: [ia32]
-    os: [win32]
-    requiresBuild: true
-    dev: true
-    optional: true
-
   /@esbuild/win32-ia32@0.18.20:
     resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==}
     engines: {node: '>=12'}
@@ -4072,23 +4061,14 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/win32-ia32@0.19.9:
-    resolution: {integrity: sha512-UOozV7Ntykvr5tSOlGCrqU3NBr3d8JqPes0QWN2WOXfvkWVGRajC+Ym0/Wj88fUgecUCLDdJPDF0Nna2UK3Qtg==}
+  /@esbuild/win32-ia32@0.19.11:
+    resolution: {integrity: sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==}
     engines: {node: '>=12'}
     cpu: [ia32]
     os: [win32]
     requiresBuild: true
     optional: true
 
-  /@esbuild/win32-x64@0.18.17:
-    resolution: {integrity: sha512-fGEb8f2BSA3CW7riJVurug65ACLuQAzKq0SSqkY2b2yHHH0MzDfbLyKIGzHwOI/gkHcxM/leuSW6D5w/LMNitA==}
-    engines: {node: '>=12'}
-    cpu: [x64]
-    os: [win32]
-    requiresBuild: true
-    dev: true
-    optional: true
-
   /@esbuild/win32-x64@0.18.20:
     resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==}
     engines: {node: '>=12'}
@@ -4098,8 +4078,8 @@ packages:
     dev: true
     optional: true
 
-  /@esbuild/win32-x64@0.19.9:
-    resolution: {integrity: sha512-oxoQgglOP7RH6iasDrhY+R/3cHrfwIDvRlT4CGChflq6twk8iENeVvMJjmvBb94Ik1Z+93iGO27err7w6l54GQ==}
+  /@esbuild/win32-x64@0.19.11:
+    resolution: {integrity: sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==}
     engines: {node: '>=12'}
     cpu: [x64]
     os: [win32]
@@ -4126,13 +4106,13 @@ packages:
       eslint-visitor-keys: 3.4.3
     dev: true
 
-  /@eslint-community/eslint-utils@4.4.0(eslint@8.56.0):
+  /@eslint-community/eslint-utils@4.4.0(eslint@8.57.0):
     resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     peerDependencies:
       eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
     dependencies:
-      eslint: 8.56.0
+      eslint: 8.57.0
       eslint-visitor-keys: 3.4.3
     dev: true
 
@@ -4185,8 +4165,8 @@ packages:
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dev: true
 
-  /@eslint/js@8.56.0:
-    resolution: {integrity: sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==}
+  /@eslint/js@8.57.0:
+    resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dev: true
 
@@ -4226,8 +4206,8 @@ packages:
     engines: {node: '>=14'}
     dev: true
 
-  /@fastify/cookie@9.2.0:
-    resolution: {integrity: sha512-fkg1yjjQRHPFAxSHeLC8CqYuNzvR6Lwlj/KjrzQcGjNBK+K82nW+UfCjfN71g1GkoVoc1GTOgIWkFJpcMfMkHQ==}
+  /@fastify/cookie@9.3.1:
+    resolution: {integrity: sha512-h1NAEhB266+ZbZ0e9qUE6NnNR07i7DnNXWG9VbbZ8uC6O/hxHpl+Zoe5sw1yfdZ2U6XhToUGDnzQtWJdCaPwfg==}
     dependencies:
       cookie-signature: 1.2.1
       fastify-plugin: 4.5.0
@@ -4244,10 +4224,6 @@ packages:
     resolution: {integrity: sha512-J8TOSBq3SoZbDhM9+R/u77hP93gz/rajSA+K2kGyijPpORPWUXHUpTaleoj+92As0S9uPRP7Oi8IqMf0u+ro6A==}
     dev: false
 
-  /@fastify/error@3.2.0:
-    resolution: {integrity: sha512-KAfcLa+CnknwVi5fWogrLXgidLic+GXnLjijXdpl8pvkvbXU5BGa37iZO9FGvsh9ZL4y+oFi5cbHBm5UOG+dmQ==}
-    dev: false
-
   /@fastify/error@3.4.0:
     resolution: {integrity: sha512-e/mafFwbK3MNqxUcFBLgHhgxsF8UT1m8aj0dAlqEa2nJEgPsRtpHTZ3ObgrgkZ2M1eJHPTwgyUl/tXkvabsZdQ==}
     dev: false
@@ -4273,25 +4249,21 @@ packages:
       '@fastify/reply-from': 9.0.1
       fast-querystring: 1.1.2
       fastify-plugin: 4.5.0
-      ws: 8.15.1(bufferutil@4.0.7)(utf-8-validate@6.0.3)
+      ws: 8.16.0(bufferutil@4.0.7)(utf-8-validate@6.0.3)
     transitivePeerDependencies:
       - bufferutil
       - utf-8-validate
     dev: false
 
-  /@fastify/multipart@8.0.0:
-    resolution: {integrity: sha512-xaH1pGIqYnIJjYs5qG6ryhPSFnWuJIfSXYqEUtzmcyREkMk0SwONd2y+SZ9JXfDmETAC/Ogtc/SRbz+AjZhCkw==}
+  /@fastify/multipart@8.1.0:
+    resolution: {integrity: sha512-sRX9X4ZhAqRbe2kDvXY2NK7i6Wf1Rm2g/CjpGYYM7+Np8E6uWQXcj761j08qPfPO8PJXM+vJ7yrKbK1GPB+OeQ==}
     dependencies:
       '@fastify/busboy': 1.1.0
       '@fastify/deepmerge': 1.3.0
-      '@fastify/error': 3.2.0
-      '@fastify/swagger': 8.6.0
-      '@fastify/swagger-ui': 1.9.0
+      '@fastify/error': 3.4.0
       fastify-plugin: 4.5.0
       secure-json-parse: 2.7.0
       stream-wormhole: 1.1.0
-    transitivePeerDependencies:
-      - supports-color
     dev: false
 
   /@fastify/reply-from@9.0.1:
@@ -4327,28 +4299,6 @@ packages:
       p-limit: 3.1.0
     dev: false
 
-  /@fastify/swagger-ui@1.9.0:
-    resolution: {integrity: sha512-7RTq2bI2cg4k6WsY69k8MZ8GnH6VUSbczJGnTotUKH+fOY9Cg3y8NEvPUREfwRzguI+3N+v8gp6H0UAohayldA==}
-    dependencies:
-      '@fastify/static': 6.12.0
-      fastify-plugin: 4.5.0
-      openapi-types: 12.1.3
-      rfdc: 1.3.0
-      yaml: 2.3.1
-    dev: false
-
-  /@fastify/swagger@8.6.0:
-    resolution: {integrity: sha512-PGde7ryn0nsX/BpSrjP4Ade8RK2M0uBIU4Iow3Qt3kWa/70p1fM7AW28kS3dKERnwT0VwrUdxU3ftrRI+DsNTw==}
-    dependencies:
-      fastify-plugin: 4.5.0
-      json-schema-resolver: 2.0.0
-      openapi-types: 12.1.3
-      rfdc: 1.3.0
-      yaml: 2.3.1
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
-
   /@fastify/view@8.2.0:
     resolution: {integrity: sha512-hBSiBofCnJNlPHEMZWpO1SL84eqOaqujJ1hR3jntFyZZCkweH5jMs12DKYyGesjVll7SJFRRxPUBB8kmUmneRQ==}
     dependencies:
@@ -4356,34 +4306,6 @@ packages:
       hashlru: 2.3.0
     dev: false
 
-  /@floating-ui/core@1.4.1:
-    resolution: {integrity: sha512-jk3WqquEJRlcyu7997NtR5PibI+y5bi+LS3hPmguVClypenMsCY3CBa3LAQnozRCtCrYWSEtAdiskpamuJRFOQ==}
-    dependencies:
-      '@floating-ui/utils': 0.1.1
-    dev: true
-
-  /@floating-ui/dom@1.5.1:
-    resolution: {integrity: sha512-KwvVcPSXg6mQygvA1TjbN/gh///36kKtllIF8SUm0qpFj8+rvYrpvlYdL1JoA71SHpDqgSSdGOSoQ0Mp3uY5aw==}
-    dependencies:
-      '@floating-ui/core': 1.4.1
-      '@floating-ui/utils': 0.1.1
-    dev: true
-
-  /@floating-ui/react-dom@2.0.2(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==}
-    peerDependencies:
-      react: '>=16.8.0'
-      react-dom: '>=16.8.0'
-    dependencies:
-      '@floating-ui/dom': 1.5.1
-      react: 18.2.0
-      react-dom: 18.2.0(react@18.2.0)
-    dev: true
-
-  /@floating-ui/utils@0.1.1:
-    resolution: {integrity: sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw==}
-    dev: true
-
   /@github/webauthn-json@2.1.1:
     resolution: {integrity: sha512-XrftRn4z75SnaJOmZQbt7Mk+IIjqVHw+glDGOxuHwXkZBZh/MBoRS7MHjSZMDaLhT4RjN2VqiEU7EOYleuJWSQ==}
     hasBin: true
@@ -4440,15 +4362,223 @@ packages:
       - supports-color
     dev: true
 
+  /@humanwhocodes/config-array@0.11.14:
+    resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==}
+    engines: {node: '>=10.10.0'}
+    dependencies:
+      '@humanwhocodes/object-schema': 2.0.2
+      debug: 4.3.4(supports-color@8.1.1)
+      minimatch: 3.1.2
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /@humanwhocodes/module-importer@1.0.1:
     resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
     engines: {node: '>=12.22'}
     dev: true
 
+  /@humanwhocodes/momoa@2.0.4:
+    resolution: {integrity: sha512-RE815I4arJFtt+FVeU1Tgp9/Xvecacji8w/V6XtXsWWH/wz/eNkNbhb+ny/+PlVZjV0rxQpRSQKNKE3lcktHEA==}
+    engines: {node: '>=10.10.0'}
+    dev: true
+
   /@humanwhocodes/object-schema@2.0.1:
     resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==}
     dev: true
 
+  /@humanwhocodes/object-schema@2.0.2:
+    resolution: {integrity: sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==}
+    dev: true
+
+  /@img/sharp-darwin-arm64@0.33.2:
+    resolution: {integrity: sha512-itHBs1rPmsmGF9p4qRe++CzCgd+kFYktnsoR1sbIAfsRMrJZau0Tt1AH9KVnufc2/tU02Gf6Ibujx+15qRE03w==}
+    engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+    cpu: [arm64]
+    os: [darwin]
+    requiresBuild: true
+    optionalDependencies:
+      '@img/sharp-libvips-darwin-arm64': 1.0.1
+    dev: false
+    optional: true
+
+  /@img/sharp-darwin-x64@0.33.2:
+    resolution: {integrity: sha512-/rK/69Rrp9x5kaWBjVN07KixZanRr+W1OiyKdXcbjQD6KbW+obaTeBBtLUAtbBsnlTTmWthw99xqoOS7SsySDg==}
+    engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+    cpu: [x64]
+    os: [darwin]
+    requiresBuild: true
+    optionalDependencies:
+      '@img/sharp-libvips-darwin-x64': 1.0.1
+    dev: false
+    optional: true
+
+  /@img/sharp-libvips-darwin-arm64@1.0.1:
+    resolution: {integrity: sha512-kQyrSNd6lmBV7O0BUiyu/OEw9yeNGFbQhbxswS1i6rMDwBBSX+e+rPzu3S+MwAiGU3HdLze3PanQ4Xkfemgzcw==}
+    engines: {macos: '>=11', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+    cpu: [arm64]
+    os: [darwin]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@img/sharp-libvips-darwin-x64@1.0.1:
+    resolution: {integrity: sha512-eVU/JYLPVjhhrd8Tk6gosl5pVlvsqiFlt50wotCvdkFGf+mDNBJxMh+bvav+Wt3EBnNZWq8Sp2I7XfSjm8siog==}
+    engines: {macos: '>=10.13', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+    cpu: [x64]
+    os: [darwin]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@img/sharp-libvips-linux-arm64@1.0.1:
+    resolution: {integrity: sha512-bnGG+MJjdX70mAQcSLxgeJco11G+MxTz+ebxlz8Y3dxyeb3Nkl7LgLI0mXupoO+u1wRNx/iRj5yHtzA4sde1yA==}
+    engines: {glibc: '>=2.26', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@img/sharp-libvips-linux-arm@1.0.1:
+    resolution: {integrity: sha512-FtdMvR4R99FTsD53IA3LxYGghQ82t3yt0ZQ93WMZ2xV3dqrb0E8zq4VHaTOuLEAuA83oDawHV3fd+BsAPadHIQ==}
+    engines: {glibc: '>=2.28', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+    cpu: [arm]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@img/sharp-libvips-linux-s390x@1.0.1:
+    resolution: {integrity: sha512-3+rzfAR1YpMOeA2zZNp+aYEzGNWK4zF3+sdMxuCS3ey9HhDbJ66w6hDSHDMoap32DueFwhhs3vwooAB2MaK4XQ==}
+    engines: {glibc: '>=2.28', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+    cpu: [s390x]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@img/sharp-libvips-linux-x64@1.0.1:
+    resolution: {integrity: sha512-3NR1mxFsaSgMMzz1bAnnKbSAI+lHXVTqAHgc1bgzjHuXjo4hlscpUxc0vFSAPKI3yuzdzcZOkq7nDPrP2F8Jgw==}
+    engines: {glibc: '>=2.26', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@img/sharp-libvips-linuxmusl-arm64@1.0.1:
+    resolution: {integrity: sha512-5aBRcjHDG/T6jwC3Edl3lP8nl9U2Yo8+oTl5drd1dh9Z1EBfzUKAJFUDTDisDjUwc7N4AjnPGfCA3jl3hY8uDg==}
+    engines: {musl: '>=1.2.2', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@img/sharp-libvips-linuxmusl-x64@1.0.1:
+    resolution: {integrity: sha512-dcT7inI9DBFK6ovfeWRe3hG30h51cBAP5JXlZfx6pzc/Mnf9HFCQDLtYf4MCBjxaaTfjCCjkBxcy3XzOAo5txw==}
+    engines: {musl: '>=1.2.2', npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@img/sharp-linux-arm64@0.33.2:
+    resolution: {integrity: sha512-pz0NNo882vVfqJ0yNInuG9YH71smP4gRSdeL09ukC2YLE6ZyZePAlWKEHgAzJGTiOh8Qkaov6mMIMlEhmLdKew==}
+    engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    optionalDependencies:
+      '@img/sharp-libvips-linux-arm64': 1.0.1
+    dev: false
+    optional: true
+
+  /@img/sharp-linux-arm@0.33.2:
+    resolution: {integrity: sha512-Fndk/4Zq3vAc4G/qyfXASbS3HBZbKrlnKZLEJzPLrXoJuipFNNwTes71+Ki1hwYW5lch26niRYoZFAtZVf3EGA==}
+    engines: {glibc: '>=2.28', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+    cpu: [arm]
+    os: [linux]
+    requiresBuild: true
+    optionalDependencies:
+      '@img/sharp-libvips-linux-arm': 1.0.1
+    dev: false
+    optional: true
+
+  /@img/sharp-linux-s390x@0.33.2:
+    resolution: {integrity: sha512-MBoInDXDppMfhSzbMmOQtGfloVAflS2rP1qPcUIiITMi36Mm5YR7r0ASND99razjQUpHTzjrU1flO76hKvP5RA==}
+    engines: {glibc: '>=2.28', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+    cpu: [s390x]
+    os: [linux]
+    requiresBuild: true
+    optionalDependencies:
+      '@img/sharp-libvips-linux-s390x': 1.0.1
+    dev: false
+    optional: true
+
+  /@img/sharp-linux-x64@0.33.2:
+    resolution: {integrity: sha512-xUT82H5IbXewKkeF5aiooajoO1tQV4PnKfS/OZtb5DDdxS/FCI/uXTVZ35GQ97RZXsycojz/AJ0asoz6p2/H/A==}
+    engines: {glibc: '>=2.26', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    optionalDependencies:
+      '@img/sharp-libvips-linux-x64': 1.0.1
+    dev: false
+    optional: true
+
+  /@img/sharp-linuxmusl-arm64@0.33.2:
+    resolution: {integrity: sha512-F+0z8JCu/UnMzg8IYW1TMeiViIWBVg7IWP6nE0p5S5EPQxlLd76c8jYemG21X99UzFwgkRo5yz2DS+zbrnxZeA==}
+    engines: {musl: '>=1.2.2', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    optionalDependencies:
+      '@img/sharp-libvips-linuxmusl-arm64': 1.0.1
+    dev: false
+    optional: true
+
+  /@img/sharp-linuxmusl-x64@0.33.2:
+    resolution: {integrity: sha512-+ZLE3SQmSL+Fn1gmSaM8uFusW5Y3J9VOf+wMGNnTtJUMUxFhv+P4UPaYEYT8tqnyYVaOVGgMN/zsOxn9pSsO2A==}
+    engines: {musl: '>=1.2.2', node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    optionalDependencies:
+      '@img/sharp-libvips-linuxmusl-x64': 1.0.1
+    dev: false
+    optional: true
+
+  /@img/sharp-wasm32@0.33.2:
+    resolution: {integrity: sha512-fLbTaESVKuQcpm8ffgBD7jLb/CQLcATju/jxtTXR1XCLwbOQt+OL5zPHSDMmp2JZIeq82e18yE0Vv7zh6+6BfQ==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+    cpu: [wasm32]
+    requiresBuild: true
+    dependencies:
+      '@emnapi/runtime': 0.45.0
+    dev: false
+    optional: true
+
+  /@img/sharp-win32-ia32@0.33.2:
+    resolution: {integrity: sha512-okBpql96hIGuZ4lN3+nsAjGeggxKm7hIRu9zyec0lnfB8E7Z6p95BuRZzDDXZOl2e8UmR4RhYt631i7mfmKU8g==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+    cpu: [ia32]
+    os: [win32]
+    requiresBuild: true
+    dev: false
+    optional: true
+
+  /@img/sharp-win32-x64@0.33.2:
+    resolution: {integrity: sha512-E4magOks77DK47FwHUIGH0RYWSgRBfGdK56kIHSVeB9uIS4pPFr4N2kIVsXdQQo4LzOsENKV5KAhRlRL7eMAdg==}
+    engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0', yarn: '>=3.2.0'}
+    cpu: [x64]
+    os: [win32]
+    requiresBuild: true
+    dev: false
+    optional: true
+
   /@ioredis/commands@1.2.0:
     resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==}
     dev: false
@@ -4485,7 +4615,7 @@ packages:
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
       '@jest/types': 29.6.3
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
       chalk: 4.1.2
       jest-message-util: 29.7.0
       jest-util: 29.7.0
@@ -4506,14 +4636,14 @@ packages:
       '@jest/test-result': 29.7.0
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
       ansi-escapes: 4.3.2
       chalk: 4.1.2
       ci-info: 3.9.0
       exit: 0.1.2
       graceful-fs: 4.2.11
       jest-changed-files: 29.7.0
-      jest-config: 29.7.0(@types/node@20.10.5)
+      jest-config: 29.7.0(@types/node@20.11.22)
       jest-haste-map: 29.7.0
       jest-message-util: 29.7.0
       jest-regex-util: 29.6.3
@@ -4535,11 +4665,11 @@ packages:
       - ts-node
     dev: true
 
-  /@jest/create-cache-key-function@27.5.1:
-    resolution: {integrity: sha512-dmH1yW+makpTSURTy8VzdUwFnfQh1G8R+DxO2Ho2FFmBbKFEVm+3jWdvFhE2VqB/LATCTokkP0dotjyQyw5/AQ==}
-    engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
+  /@jest/create-cache-key-function@29.7.0:
+    resolution: {integrity: sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==}
+    engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
-      '@jest/types': 27.5.1
+      '@jest/types': 29.6.3
     dev: true
 
   /@jest/environment@29.7.0:
@@ -4548,7 +4678,7 @@ packages:
     dependencies:
       '@jest/fake-timers': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
       jest-mock: 29.7.0
     dev: true
 
@@ -4574,7 +4704,7 @@ packages:
     dependencies:
       '@jest/types': 29.6.3
       '@sinonjs/fake-timers': 10.3.0
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
       jest-message-util: 29.7.0
       jest-mock: 29.7.0
       jest-util: 29.7.0
@@ -4607,7 +4737,7 @@ packages:
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
       '@jridgewell/trace-mapping': 0.3.20
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
       chalk: 4.1.2
       collect-v8-coverage: 1.0.2
       exit: 0.1.2
@@ -4629,13 +4759,6 @@ packages:
       - supports-color
     dev: true
 
-  /@jest/schemas@28.1.3:
-    resolution: {integrity: sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==}
-    engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
-    dependencies:
-      '@sinclair/typebox': 0.24.51
-    dev: true
-
   /@jest/schemas@29.6.3:
     resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -4675,7 +4798,7 @@ packages:
     resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       '@jest/types': 29.6.3
       '@jridgewell/trace-mapping': 0.3.20
       babel-plugin-istanbul: 6.1.1
@@ -4698,9 +4821,9 @@ packages:
     resolution: {integrity: sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==}
     engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
     dependencies:
-      '@types/istanbul-lib-coverage': 2.0.4
-      '@types/istanbul-reports': 3.0.1
-      '@types/node': 20.10.5
+      '@types/istanbul-lib-coverage': 2.0.6
+      '@types/istanbul-reports': 3.0.4
+      '@types/node': 20.11.22
       '@types/yargs': 16.0.5
       chalk: 4.1.2
     dev: true
@@ -4712,11 +4835,11 @@ packages:
       '@jest/schemas': 29.6.3
       '@types/istanbul-lib-coverage': 2.0.6
       '@types/istanbul-reports': 3.0.4
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
       '@types/yargs': 17.0.32
       chalk: 4.1.2
 
-  /@joshwooding/vite-plugin-react-docgen-typescript@0.3.0(typescript@5.3.3)(vite@5.0.10):
+  /@joshwooding/vite-plugin-react-docgen-typescript@0.3.0(typescript@5.3.3)(vite@5.1.4):
     resolution: {integrity: sha512-2D6y7fNvFmsLmRt6UCOFJPvFoPMJGT0Uh1Wg0RaigUp7kdQPs6yYn8Dmx6GZkOH/NW0yMTwRz/p0SRMMRo50vA==}
     peerDependencies:
       typescript: '>= 4.3.x'
@@ -4730,16 +4853,7 @@ packages:
       magic-string: 0.27.0
       react-docgen-typescript: 2.2.2(typescript@5.3.3)
       typescript: 5.3.3
-      vite: 5.0.10(@types/node@20.10.5)(sass@1.69.5)(terser@5.26.0)
-    dev: true
-
-  /@jridgewell/gen-mapping@0.3.2:
-    resolution: {integrity: sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==}
-    engines: {node: '>=6.0.0'}
-    dependencies:
-      '@jridgewell/set-array': 1.1.2
-      '@jridgewell/sourcemap-codec': 1.4.15
-      '@jridgewell/trace-mapping': 0.3.18
+      vite: 5.1.4(@types/node@20.11.22)(sass@1.71.1)(terser@5.28.1)
     dev: true
 
   /@jridgewell/gen-mapping@0.3.3:
@@ -4750,11 +4864,6 @@ packages:
       '@jridgewell/sourcemap-codec': 1.4.15
       '@jridgewell/trace-mapping': 0.3.20
 
-  /@jridgewell/resolve-uri@3.1.0:
-    resolution: {integrity: sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==}
-    engines: {node: '>=6.0.0'}
-    dev: true
-
   /@jridgewell/resolve-uri@3.1.1:
     resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==}
     engines: {node: '>=6.0.0'}
@@ -4769,20 +4878,9 @@ packages:
       '@jridgewell/gen-mapping': 0.3.3
       '@jridgewell/trace-mapping': 0.3.20
 
-  /@jridgewell/sourcemap-codec@1.4.14:
-    resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==}
-    dev: true
-
   /@jridgewell/sourcemap-codec@1.4.15:
     resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
 
-  /@jridgewell/trace-mapping@0.3.18:
-    resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==}
-    dependencies:
-      '@jridgewell/resolve-uri': 3.1.0
-      '@jridgewell/sourcemap-codec': 1.4.14
-    dev: true
-
   /@jridgewell/trace-mapping@0.3.20:
     resolution: {integrity: sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==}
     dependencies:
@@ -4793,45 +4891,37 @@ packages:
     resolution: {integrity: sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==}
     dev: true
 
-  /@juggle/resize-observer@3.4.0:
-    resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==}
-    dev: true
-
   /@kurkle/color@0.3.2:
     resolution: {integrity: sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw==}
     dev: false
 
+  /@levischuck/tiny-cbor@0.2.2:
+    resolution: {integrity: sha512-f5CnPw997Y2GQ8FAvtuVVC19FX8mwNNC+1XJcIi16n/LTJifKO6QBgGLgN3YEmqtGMk17SKSuoWES3imJVxAVw==}
+    dev: false
+
   /@lukeed/csprng@1.0.1:
     resolution: {integrity: sha512-uSvJdwQU5nK+Vdf6zxcWAY2A8r7uqe+gePwLWzJ+fsQehq18pc0I2hJKwypZ2aLM90+Er9u1xn4iLJPZ+xlL4g==}
     engines: {node: '>=8'}
-    dev: false
 
   /@lukeed/ms@2.0.1:
     resolution: {integrity: sha512-Xs/4RZltsAL7pkvaNStUQt7netTkyxrS0K+RILcVr3TRMS/ToOg4I6uNfhB9SlGsnWBym4U+EaXq0f0cEMNkHA==}
     engines: {node: '>=8'}
     dev: false
 
-  /@mapbox/node-pre-gyp@1.0.11:
-    resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==}
-    hasBin: true
-    dependencies:
-      detect-libc: 2.0.2
-      https-proxy-agent: 5.0.1
-      make-dir: 3.1.0
-      node-fetch: 2.7.0
-      nopt: 5.0.0
-      npmlog: 5.0.1
-      rimraf: 3.0.2
-      semver: 7.5.4
-      tar: 6.1.13
-    transitivePeerDependencies:
-      - encoding
-      - supports-color
+  /@mcaptcha/core-glue@0.1.0-alpha-5:
+    resolution: {integrity: sha512-16qWm5O5X0Y9LXULULaAks8Vf9FNlUUBcR5KDt49aWhFhG5++JzxNmCwQM9EJSHNU7y0U+FdyAWcGmjfKlkRLA==}
     dev: false
 
-  /@mdx-js/react@2.3.0(react@18.2.0):
-    resolution: {integrity: sha512-zQH//gdOmuu7nt2oJR29vFhDv88oGPmVw6BggmrHeMI+xgEkp1B2dX9/bMBSYtK0dyLX/aOmesKS09g222K1/g==}
+  /@mcaptcha/vanilla-glue@0.1.0-alpha-3:
+    resolution: {integrity: sha512-GT6TJBgmViGXcXiT5VOr+h/6iOnThSlZuCoOWncubyTZU9R3cgU5vWPkF7G6Ob6ee2CBe3yqBxxk24CFVGTVXw==}
+    dependencies:
+      '@mcaptcha/core-glue': 0.1.0-alpha-5
+    dev: false
+
+  /@mdx-js/react@3.0.1(@types/react@18.0.28)(react@18.2.0):
+    resolution: {integrity: sha512-9ZrPIU4MGf6et1m1ov3zKf+q9+deetI51zprKB1D/z3NOb+rUxxtEl3mCjW5wTGh6VhRdwPueh1oRzi6ezkA8A==}
     peerDependencies:
+      '@types/react': '>=16'
       react: '>=16'
     dependencies:
       '@types/mdx': 2.0.3
@@ -4839,24 +4929,24 @@ packages:
       react: 18.2.0
     dev: true
 
-  /@microsoft/api-extractor-model@7.28.3(@types/node@20.10.5):
-    resolution: {integrity: sha512-wT/kB2oDbdZXITyDh2SQLzaWwTOFbV326fP0pUwNW00WeliARs0qjmXBWmGWardEzp2U3/axkO3Lboqun6vrig==}
+  /@microsoft/api-extractor-model@7.28.4(@types/node@20.11.22):
+    resolution: {integrity: sha512-vucgyPmgHrJ/D4/xQywAmjTmSfxAx2/aDmD6TkIoLu51FdsAfuWRbijWA48AePy60OO+l+mmy9p2P/CEeBZqig==}
     dependencies:
       '@microsoft/tsdoc': 0.14.2
       '@microsoft/tsdoc-config': 0.16.2
-      '@rushstack/node-core-library': 3.62.0(@types/node@20.10.5)
+      '@rushstack/node-core-library': 3.63.0(@types/node@20.11.22)
     transitivePeerDependencies:
       - '@types/node'
     dev: true
 
-  /@microsoft/api-extractor@7.38.5(@types/node@20.10.5):
-    resolution: {integrity: sha512-c/w2zfqBcBJxaCzpJNvFoouWewcYrUOfeu5ZkWCCIXTF9a/gXM85RGevEzlMAIEGM/kssAAZSXRJIZ3Q5vLFow==}
+  /@microsoft/api-extractor@7.39.1(@types/node@20.11.22):
+    resolution: {integrity: sha512-V0HtCufWa8hZZvSmlEzQZfINcJkHAU/bmpyJQj6w+zpI87EkR8DuBOW6RWrO9c7mUYFZoDaNgUTyKo83ytv+QQ==}
     hasBin: true
     dependencies:
-      '@microsoft/api-extractor-model': 7.28.3(@types/node@20.10.5)
+      '@microsoft/api-extractor-model': 7.28.4(@types/node@20.11.22)
       '@microsoft/tsdoc': 0.14.2
       '@microsoft/tsdoc-config': 0.16.2
-      '@rushstack/node-core-library': 3.62.0(@types/node@20.10.5)
+      '@rushstack/node-core-library': 3.63.0(@types/node@20.11.22)
       '@rushstack/rig-package': 0.5.1
       '@rushstack/ts-command-line': 4.17.1
       colors: 1.2.5
@@ -4864,7 +4954,7 @@ packages:
       resolve: 1.22.8
       semver: 7.5.4
       source-map: 0.6.1
-      typescript: 5.0.4
+      typescript: 5.3.3
     transitivePeerDependencies:
       - '@types/node'
     dev: true
@@ -4882,6 +4972,72 @@ packages:
     resolution: {integrity: sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==}
     dev: true
 
+  /@misskey-dev/browser-image-resizer@2024.1.0:
+    resolution: {integrity: sha512-4EnO0zLW5NDtng3Gaz5MuT761uiuoOuplwX18wBqgj8w56LTU5BjLn/vbHwDIIe0j2gwqDYhMb7bDjmr1/Fomg==}
+    dev: false
+
+  /@misskey-dev/eslint-plugin@1.0.0(@typescript-eslint/eslint-plugin@6.11.0)(@typescript-eslint/parser@6.11.0)(eslint-plugin-import@2.29.1)(eslint@8.53.0):
+    resolution: {integrity: sha512-dh6UbcrNDVg5DD8k8Qh4ab30OPpuEYIlJCqaBV/lkIV8wNN/AfCJ2V7iTP8V8KjryM4t+sf5IqzQLQnT0mWI4A==}
+    peerDependencies:
+      '@typescript-eslint/eslint-plugin': '>= 6'
+      '@typescript-eslint/parser': '>= 6'
+      eslint: '>= 3'
+      eslint-plugin-import: '>= 2'
+    dependencies:
+      '@typescript-eslint/eslint-plugin': 6.11.0(@typescript-eslint/parser@6.11.0)(eslint@8.53.0)(typescript@5.3.3)
+      '@typescript-eslint/parser': 6.11.0(eslint@8.53.0)(typescript@5.3.3)
+      eslint: 8.53.0
+      eslint-plugin-import: 2.29.1(@typescript-eslint/parser@6.11.0)(eslint@8.53.0)
+    dev: true
+
+  /@misskey-dev/eslint-plugin@1.0.0(@typescript-eslint/eslint-plugin@6.21.0)(@typescript-eslint/parser@7.1.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0):
+    resolution: {integrity: sha512-dh6UbcrNDVg5DD8k8Qh4ab30OPpuEYIlJCqaBV/lkIV8wNN/AfCJ2V7iTP8V8KjryM4t+sf5IqzQLQnT0mWI4A==}
+    peerDependencies:
+      '@typescript-eslint/eslint-plugin': '>= 6'
+      '@typescript-eslint/parser': '>= 6'
+      eslint: '>= 3'
+      eslint-plugin-import: '>= 2'
+    dependencies:
+      '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@7.1.0)(eslint@8.57.0)(typescript@5.3.3)
+      '@typescript-eslint/parser': 7.1.0(eslint@8.57.0)(typescript@5.3.3)
+      eslint: 8.57.0
+      eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.1.0)(eslint@8.57.0)
+    dev: true
+
+  /@misskey-dev/eslint-plugin@1.0.0(@typescript-eslint/eslint-plugin@7.1.0)(@typescript-eslint/parser@7.1.0)(eslint-plugin-import@2.29.1)(eslint@8.57.0):
+    resolution: {integrity: sha512-dh6UbcrNDVg5DD8k8Qh4ab30OPpuEYIlJCqaBV/lkIV8wNN/AfCJ2V7iTP8V8KjryM4t+sf5IqzQLQnT0mWI4A==}
+    peerDependencies:
+      '@typescript-eslint/eslint-plugin': '>= 6'
+      '@typescript-eslint/parser': '>= 6'
+      eslint: '>= 3'
+      eslint-plugin-import: '>= 2'
+    dependencies:
+      '@typescript-eslint/eslint-plugin': 7.1.0(@typescript-eslint/parser@7.1.0)(eslint@8.57.0)(typescript@5.3.3)
+      '@typescript-eslint/parser': 7.1.0(eslint@8.57.0)(typescript@5.3.3)
+      eslint: 8.57.0
+      eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.1.0)(eslint@8.57.0)
+    dev: true
+
+  /@misskey-dev/sharp-read-bmp@1.2.0:
+    resolution: {integrity: sha512-er4pRakXzHYfEgOFAFfQagqDouG+wLm+kwNq1I30oSdIHDa0wM3KjFpfIGQ25Fks4GcmOl1s7Zh6xoQu5dNjTw==}
+    dependencies:
+      decode-bmp: 0.2.1
+      decode-ico: 0.4.1
+      sharp: 0.33.2
+    dev: false
+
+  /@misskey-dev/summaly@5.0.3:
+    resolution: {integrity: sha512-jVkuLEDrq2FaeHL8VY51LTqB6j0Jv5L7s0nmKGKMnE0jPBpSj6flswnZgntGmz5mbdCj47utEqu8FY43kH7PVg==}
+    dependencies:
+      cheerio: 1.0.0-rc.12
+      escape-regexp: 0.0.1
+      got: 12.6.1
+      html-entities: 2.3.2
+      iconv-lite: 0.6.3
+      jschardet: 3.0.0
+      private-ip: 2.3.3
+      trace-redirect: 1.0.6
+
   /@mole-inc/bin-wrapper@8.0.1:
     resolution: {integrity: sha512-sTGoeZnjI8N4KS+sW2AN95gDBErhAguvkw/tWdCjeM8bvxpz5lqrnd0vOJABA1A+Ic3zED7PYoLP/RANLgVotA==}
     engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -4944,28 +5100,21 @@ packages:
     dev: false
     optional: true
 
-  /@mswjs/cookies@0.2.2:
-    resolution: {integrity: sha512-mlN83YSrcFgk7Dm1Mys40DLssI1KdJji2CMKN8eOlBqsTADYzj2+jWzsANsUTFbxDMWPD5e9bfA1RGqBpS3O1g==}
-    engines: {node: '>=14'}
-    dependencies:
-      '@types/set-cookie-parser': 2.4.3
-      set-cookie-parser: 2.6.0
+  /@mswjs/cookies@1.1.0:
+    resolution: {integrity: sha512-0ZcCVQxifZmhwNBoQIrystCb+2sWBY2Zw8lpfJBPCHGCA/HWqehITeCRVIv4VMy8MPlaHo2w2pTHFV2pFfqKPw==}
+    engines: {node: '>=18'}
     dev: true
 
-  /@mswjs/interceptors@0.17.10:
-    resolution: {integrity: sha512-N8x7eSLGcmUFNWZRxT1vsHvypzIRgQYdG0rJey/rZCy6zT/30qDt8Joj7FxzGNLSwXbeZqJOMqDurp7ra4hgbw==}
-    engines: {node: '>=14'}
+  /@mswjs/interceptors@0.25.16:
+    resolution: {integrity: sha512-8QC8JyKztvoGAdPgyZy49c9vSHHAZjHagwl4RY9E8carULk8ym3iTaiawrT1YoLF/qb449h48f71XDPgkUSOUg==}
+    engines: {node: '>=18'}
     dependencies:
-      '@open-draft/until': 1.0.3
-      '@types/debug': 4.1.7
-      '@xmldom/xmldom': 0.8.6
-      debug: 4.3.4(supports-color@8.1.1)
-      headers-polyfill: 3.2.5
-      outvariant: 1.4.0
-      strict-event-emitter: 0.2.8
-      web-encoding: 1.1.5
-    transitivePeerDependencies:
-      - supports-color
+      '@open-draft/deferred-promise': 2.2.0
+      '@open-draft/logger': 0.3.0
+      '@open-draft/until': 2.1.0
+      is-node-process: 1.2.0
+      outvariant: 1.4.2
+      strict-event-emitter: 0.5.1
     dev: true
 
   /@ndelangen/get-tarball@3.0.7:
@@ -4976,12 +5125,12 @@ packages:
       tar-fs: 2.1.1
     dev: true
 
-  /@nestjs/common@10.2.10(reflect-metadata@0.1.14)(rxjs@7.8.1):
-    resolution: {integrity: sha512-fwAk931rjW8CNH2Mgwawq/7HWHH1dxkOLdcgs7U52ddLk8CtHXjejm1cbNahewlSbNhvlOl7y1STLHutE6sUqw==}
+  /@nestjs/common@10.3.3(reflect-metadata@0.2.1)(rxjs@7.8.1):
+    resolution: {integrity: sha512-LAkTe8/CF0uNWM0ecuDwUNTHCi1lVSITmmR4FQ6Ftz1E7ujQCnJ5pMRzd8JRN14vdBkxZZ8VbVF0BDUKoKNxMQ==}
     peerDependencies:
       class-transformer: '*'
       class-validator: '*'
-      reflect-metadata: ^0.1.12
+      reflect-metadata: ^0.1.12 || ^0.2.0
       rxjs: ^7.1.0
     peerDependenciesMeta:
       class-transformer:
@@ -4990,21 +5139,20 @@ packages:
         optional: true
     dependencies:
       iterare: 1.2.1
-      reflect-metadata: 0.1.14
+      reflect-metadata: 0.2.1
       rxjs: 7.8.1
       tslib: 2.6.2
       uid: 2.0.2
-    dev: false
 
-  /@nestjs/core@10.2.10(@nestjs/common@10.2.10)(reflect-metadata@0.1.14)(rxjs@7.8.1):
-    resolution: {integrity: sha512-+ckOI6BPi2ZMHikT9MCG4ctHDc4OnjhoIytrn7f2AYMMXI4bnutJhqyQKc30VDka5x3Wq6QAD57pgSP7y+JjJg==}
+  /@nestjs/core@10.3.3(@nestjs/common@10.3.3)(@nestjs/platform-express@10.3.3)(reflect-metadata@0.2.1)(rxjs@7.8.1):
+    resolution: {integrity: sha512-kxJWggQAPX3RuZx9JVec69eSLaYLNIox2emkZJpfBJ5Qq7cAq7edQIt1r4LGjTKq6kFubNTPsqhWf5y7yFRBPw==}
     requiresBuild: true
     peerDependencies:
       '@nestjs/common': ^10.0.0
       '@nestjs/microservices': ^10.0.0
       '@nestjs/platform-express': ^10.0.0
       '@nestjs/websockets': ^10.0.0
-      reflect-metadata: ^0.1.12
+      reflect-metadata: ^0.1.12 || ^0.2.0
       rxjs: ^7.1.0
     peerDependenciesMeta:
       '@nestjs/microservices':
@@ -5014,21 +5162,37 @@ packages:
       '@nestjs/websockets':
         optional: true
     dependencies:
-      '@nestjs/common': 10.2.10(reflect-metadata@0.1.14)(rxjs@7.8.1)
+      '@nestjs/common': 10.3.3(reflect-metadata@0.2.1)(rxjs@7.8.1)
+      '@nestjs/platform-express': 10.3.3(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)
       '@nuxtjs/opencollective': 0.3.2
       fast-safe-stringify: 2.1.1
       iterare: 1.2.1
       path-to-regexp: 3.2.0
-      reflect-metadata: 0.1.14
+      reflect-metadata: 0.2.1
       rxjs: 7.8.1
       tslib: 2.6.2
       uid: 2.0.2
     transitivePeerDependencies:
       - encoding
-    dev: false
 
-  /@nestjs/testing@10.2.10(@nestjs/common@10.2.10)(@nestjs/core@10.2.10):
-    resolution: {integrity: sha512-IVLUnPz/+fkBtPATYfqTIP+phN9yjkXejmj+JyhmcfPJZpxBmD1i9VSMqa4u54l37j0xkGPscQ0IXpbhqMYUKw==}
+  /@nestjs/platform-express@10.3.3(@nestjs/common@10.3.3)(@nestjs/core@10.3.3):
+    resolution: {integrity: sha512-GGKSEU48Os7nYFIsUM0nutuFUGn5AbeP8gzFBiBCAtiuJWrXZXpZ58pMBYxAbMf7IrcOZFInHEukjHGAQU0OZw==}
+    peerDependencies:
+      '@nestjs/common': ^10.0.0
+      '@nestjs/core': ^10.0.0
+    dependencies:
+      '@nestjs/common': 10.3.3(reflect-metadata@0.2.1)(rxjs@7.8.1)
+      '@nestjs/core': 10.3.3(@nestjs/common@10.3.3)(@nestjs/platform-express@10.3.3)(reflect-metadata@0.2.1)(rxjs@7.8.1)
+      body-parser: 1.20.2
+      cors: 2.8.5
+      express: 4.18.2
+      multer: 1.4.4-lts.1
+      tslib: 2.6.2
+    transitivePeerDependencies:
+      - supports-color
+
+  /@nestjs/testing@10.3.3(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)(@nestjs/platform-express@10.3.3):
+    resolution: {integrity: sha512-kX20GfjAImL5grd/i69uD/x7sc00BaqGcP2dRG3ilqshQUuy5DOmspLCr3a2C8xmVU7kzK4spT0oTxhe6WcCAA==}
     peerDependencies:
       '@nestjs/common': ^10.0.0
       '@nestjs/core': ^10.0.0
@@ -5040,8 +5204,9 @@ packages:
       '@nestjs/platform-express':
         optional: true
     dependencies:
-      '@nestjs/common': 10.2.10(reflect-metadata@0.1.14)(rxjs@7.8.1)
-      '@nestjs/core': 10.2.10(@nestjs/common@10.2.10)(reflect-metadata@0.1.14)(rxjs@7.8.1)
+      '@nestjs/common': 10.3.3(reflect-metadata@0.2.1)(rxjs@7.8.1)
+      '@nestjs/core': 10.3.3(@nestjs/common@10.3.3)(@nestjs/platform-express@10.3.3)(reflect-metadata@0.2.1)(rxjs@7.8.1)
+      '@nestjs/platform-express': 10.3.3(@nestjs/common@10.3.3)(@nestjs/core@10.3.3)
       tslib: 2.6.2
     dev: false
 
@@ -5093,54 +5258,64 @@ packages:
       node-fetch: 2.7.0
     transitivePeerDependencies:
       - encoding
-    dev: false
 
   /@one-ini/wasm@0.1.1:
     resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==}
     dev: true
 
-  /@open-draft/until@1.0.3:
-    resolution: {integrity: sha512-Aq58f5HiWdyDlFffbbSjAlv596h/cOnt2DO1w3DOC7OJ5EHs0hd/nycJfiu9RJbT6Yk6F1knnRRXNSpxoIVZ9Q==}
+  /@open-draft/deferred-promise@2.2.0:
+    resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==}
     dev: true
 
-  /@peculiar/asn1-android@2.3.6:
-    resolution: {integrity: sha512-zkYh4DsiRhiNfg6tWaUuRc+huwlb9XJbmeZLrjTz9v76UK1Ehq3EnfJFED6P3sdznW/nqWe46LoM9JrqxcD58g==}
+  /@open-draft/logger@0.3.0:
+    resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==}
     dependencies:
-      '@peculiar/asn1-schema': 2.3.6
+      is-node-process: 1.2.0
+      outvariant: 1.4.2
+    dev: true
+
+  /@open-draft/until@2.1.0:
+    resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==}
+    dev: true
+
+  /@peculiar/asn1-android@2.3.10:
+    resolution: {integrity: sha512-z9Rx9cFJv7UUablZISe7uksNbFJCq13hO0yEAOoIpAymALTLlvUOSLnGiQS7okPaM5dP42oTLhezH6XDXRXjGw==}
+    dependencies:
+      '@peculiar/asn1-schema': 2.3.8
       asn1js: 3.0.5
       tslib: 2.6.2
     dev: false
 
-  /@peculiar/asn1-ecc@2.3.6:
-    resolution: {integrity: sha512-Hu1xzMJQWv8/GvzOiinaE6XiD1/kEhq2C/V89UEoWeZ2fLUcGNIvMxOr/pMyL0OmpRWj/mhCTXOZp4PP+a0aTg==}
+  /@peculiar/asn1-ecc@2.3.8:
+    resolution: {integrity: sha512-Ah/Q15y3A/CtxbPibiLM/LKcMbnLTdUdLHUgdpB5f60sSvGkXzxJCu5ezGTFHogZXWNX3KSmYqilCrfdmBc6pQ==}
     dependencies:
-      '@peculiar/asn1-schema': 2.3.6
-      '@peculiar/asn1-x509': 2.3.6
+      '@peculiar/asn1-schema': 2.3.8
+      '@peculiar/asn1-x509': 2.3.8
       asn1js: 3.0.5
       tslib: 2.6.2
     dev: false
 
-  /@peculiar/asn1-rsa@2.3.6:
-    resolution: {integrity: sha512-DswjJyAXZnvESuImGNTvbNKvh1XApBVqU+r3UmrFFTAI23gv62byl0f5OFKWTNhCf66WQrd3sklpsCZc/4+jwA==}
+  /@peculiar/asn1-rsa@2.3.8:
+    resolution: {integrity: sha512-ES/RVEHu8VMYXgrg3gjb1m/XG0KJWnV4qyZZ7mAg7rrF3VTmRbLxO8mk+uy0Hme7geSMebp+Wvi2U6RLLEs12Q==}
     dependencies:
-      '@peculiar/asn1-schema': 2.3.6
-      '@peculiar/asn1-x509': 2.3.6
+      '@peculiar/asn1-schema': 2.3.8
+      '@peculiar/asn1-x509': 2.3.8
       asn1js: 3.0.5
       tslib: 2.6.2
     dev: false
 
-  /@peculiar/asn1-schema@2.3.6:
-    resolution: {integrity: sha512-izNRxPoaeJeg/AyH8hER6s+H7p4itk+03QCa4sbxI3lNdseQYCuxzgsuNK8bTXChtLTjpJz6NmXKA73qLa3rCA==}
+  /@peculiar/asn1-schema@2.3.8:
+    resolution: {integrity: sha512-ULB1XqHKx1WBU/tTFIA+uARuRoBVZ4pNdOA878RDrRbBfBGcSzi5HBkdScC6ZbHn8z7L8gmKCgPC1LHRrP46tA==}
     dependencies:
       asn1js: 3.0.5
       pvtsutils: 1.3.5
       tslib: 2.6.2
     dev: false
 
-  /@peculiar/asn1-x509@2.3.6:
-    resolution: {integrity: sha512-dRwX31R1lcbIdzbztiMvLNTDoGptxdV7HocNx87LfKU0fEWh7fTWJjx4oV+glETSy6heF/hJHB2J4RGB3vVSYg==}
+  /@peculiar/asn1-x509@2.3.8:
+    resolution: {integrity: sha512-voKxGfDU1c6r9mKiN5ZUsZWh3Dy1BABvTM3cimf0tztNwyMJPhiXY94eRTgsMQe6ViLfT6EoXxkWVzcm3mFAFw==}
     dependencies:
-      '@peculiar/asn1-schema': 2.3.6
+      '@peculiar/asn1-schema': 2.3.8
       asn1js: 3.0.5
       ipaddr.js: 2.1.0
       pvtsutils: 1.3.5
@@ -5171,60 +5346,7 @@ packages:
     requiresBuild: true
     optional: true
 
-  /@radix-ui/number@1.0.1:
-    resolution: {integrity: sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==}
-    dependencies:
-      '@babel/runtime': 7.23.2
-    dev: true
-
-  /@radix-ui/primitive@1.0.1:
-    resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==}
-    dependencies:
-      '@babel/runtime': 7.23.2
-    dev: true
-
-  /@radix-ui/react-arrow@1.0.3(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==}
-    peerDependencies:
-      '@types/react': '*'
-      '@types/react-dom': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-      react-dom: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-      '@types/react-dom':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.23.2
-      '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0)
-      react: 18.2.0
-      react-dom: 18.2.0(react@18.2.0)
-    dev: true
-
-  /@radix-ui/react-collection@1.0.3(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==}
-    peerDependencies:
-      '@types/react': '*'
-      '@types/react-dom': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-      react-dom: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-      '@types/react-dom':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.23.2
-      '@radix-ui/react-compose-refs': 1.0.1(react@18.2.0)
-      '@radix-ui/react-context': 1.0.1(react@18.2.0)
-      '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0)
-      '@radix-ui/react-slot': 1.0.2(react@18.2.0)
-      react: 18.2.0
-      react-dom: 18.2.0(react@18.2.0)
-    dev: true
-
-  /@radix-ui/react-compose-refs@1.0.1(react@18.2.0):
+  /@radix-ui/react-compose-refs@1.0.1(@types/react@18.0.28)(react@18.2.0):
     resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==}
     peerDependencies:
       '@types/react': '*'
@@ -5233,259 +5355,12 @@ packages:
       '@types/react':
         optional: true
     dependencies:
-      '@babel/runtime': 7.23.2
+      '@babel/runtime': 7.23.4
+      '@types/react': 18.0.28
       react: 18.2.0
     dev: true
 
-  /@radix-ui/react-context@1.0.1(react@18.2.0):
-    resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==}
-    peerDependencies:
-      '@types/react': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.23.2
-      react: 18.2.0
-    dev: true
-
-  /@radix-ui/react-direction@1.0.1(react@18.2.0):
-    resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==}
-    peerDependencies:
-      '@types/react': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.23.2
-      react: 18.2.0
-    dev: true
-
-  /@radix-ui/react-dismissable-layer@1.0.4(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-7UpBa/RKMoHJYjie1gkF1DlK8l1fdU/VKDpoS3rCCo8YBJR294GwcEHyxHw72yvphJ7ld0AXEcSLAzY2F/WyCg==}
-    peerDependencies:
-      '@types/react': '*'
-      '@types/react-dom': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-      react-dom: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-      '@types/react-dom':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.23.2
-      '@radix-ui/primitive': 1.0.1
-      '@radix-ui/react-compose-refs': 1.0.1(react@18.2.0)
-      '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0)
-      '@radix-ui/react-use-callback-ref': 1.0.1(react@18.2.0)
-      '@radix-ui/react-use-escape-keydown': 1.0.3(react@18.2.0)
-      react: 18.2.0
-      react-dom: 18.2.0(react@18.2.0)
-    dev: true
-
-  /@radix-ui/react-focus-guards@1.0.1(react@18.2.0):
-    resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==}
-    peerDependencies:
-      '@types/react': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.23.2
-      react: 18.2.0
-    dev: true
-
-  /@radix-ui/react-focus-scope@1.0.3(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-upXdPfqI4islj2CslyfUBNlaJCPybbqRHAi1KER7Isel9Q2AtSJ0zRBZv8mWQiFXD2nyAJ4BhC3yXgZ6kMBSrQ==}
-    peerDependencies:
-      '@types/react': '*'
-      '@types/react-dom': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-      react-dom: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-      '@types/react-dom':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.23.2
-      '@radix-ui/react-compose-refs': 1.0.1(react@18.2.0)
-      '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0)
-      '@radix-ui/react-use-callback-ref': 1.0.1(react@18.2.0)
-      react: 18.2.0
-      react-dom: 18.2.0(react@18.2.0)
-    dev: true
-
-  /@radix-ui/react-id@1.0.1(react@18.2.0):
-    resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==}
-    peerDependencies:
-      '@types/react': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.23.2
-      '@radix-ui/react-use-layout-effect': 1.0.1(react@18.2.0)
-      react: 18.2.0
-    dev: true
-
-  /@radix-ui/react-popper@1.1.2(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-1CnGGfFi/bbqtJZZ0P/NQY20xdG3E0LALJaLUEoKwPLwl6PPPfbeiCqMVQnhoFRAxjJj4RpBRJzDmUgsex2tSg==}
-    peerDependencies:
-      '@types/react': '*'
-      '@types/react-dom': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-      react-dom: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-      '@types/react-dom':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.23.2
-      '@floating-ui/react-dom': 2.0.2(react-dom@18.2.0)(react@18.2.0)
-      '@radix-ui/react-arrow': 1.0.3(react-dom@18.2.0)(react@18.2.0)
-      '@radix-ui/react-compose-refs': 1.0.1(react@18.2.0)
-      '@radix-ui/react-context': 1.0.1(react@18.2.0)
-      '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0)
-      '@radix-ui/react-use-callback-ref': 1.0.1(react@18.2.0)
-      '@radix-ui/react-use-layout-effect': 1.0.1(react@18.2.0)
-      '@radix-ui/react-use-rect': 1.0.1(react@18.2.0)
-      '@radix-ui/react-use-size': 1.0.1(react@18.2.0)
-      '@radix-ui/rect': 1.0.1
-      react: 18.2.0
-      react-dom: 18.2.0(react@18.2.0)
-    dev: true
-
-  /@radix-ui/react-portal@1.0.3(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-xLYZeHrWoPmA5mEKEfZZevoVRK/Q43GfzRXkWV6qawIWWK8t6ifIiLQdd7rmQ4Vk1bmI21XhqF9BN3jWf+phpA==}
-    peerDependencies:
-      '@types/react': '*'
-      '@types/react-dom': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-      react-dom: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-      '@types/react-dom':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.23.2
-      '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0)
-      react: 18.2.0
-      react-dom: 18.2.0(react@18.2.0)
-    dev: true
-
-  /@radix-ui/react-primitive@1.0.3(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==}
-    peerDependencies:
-      '@types/react': '*'
-      '@types/react-dom': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-      react-dom: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-      '@types/react-dom':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.23.2
-      '@radix-ui/react-slot': 1.0.2(react@18.2.0)
-      react: 18.2.0
-      react-dom: 18.2.0(react@18.2.0)
-    dev: true
-
-  /@radix-ui/react-roving-focus@1.0.4(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==}
-    peerDependencies:
-      '@types/react': '*'
-      '@types/react-dom': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-      react-dom: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-      '@types/react-dom':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.23.2
-      '@radix-ui/primitive': 1.0.1
-      '@radix-ui/react-collection': 1.0.3(react-dom@18.2.0)(react@18.2.0)
-      '@radix-ui/react-compose-refs': 1.0.1(react@18.2.0)
-      '@radix-ui/react-context': 1.0.1(react@18.2.0)
-      '@radix-ui/react-direction': 1.0.1(react@18.2.0)
-      '@radix-ui/react-id': 1.0.1(react@18.2.0)
-      '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0)
-      '@radix-ui/react-use-callback-ref': 1.0.1(react@18.2.0)
-      '@radix-ui/react-use-controllable-state': 1.0.1(react@18.2.0)
-      react: 18.2.0
-      react-dom: 18.2.0(react@18.2.0)
-    dev: true
-
-  /@radix-ui/react-select@1.2.2(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-zI7McXr8fNaSrUY9mZe4x/HC0jTLY9fWNhO1oLWYMQGDXuV4UCivIGTxwioSzO0ZCYX9iSLyWmAh/1TOmX3Cnw==}
-    peerDependencies:
-      '@types/react': '*'
-      '@types/react-dom': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-      react-dom: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-      '@types/react-dom':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.23.2
-      '@radix-ui/number': 1.0.1
-      '@radix-ui/primitive': 1.0.1
-      '@radix-ui/react-collection': 1.0.3(react-dom@18.2.0)(react@18.2.0)
-      '@radix-ui/react-compose-refs': 1.0.1(react@18.2.0)
-      '@radix-ui/react-context': 1.0.1(react@18.2.0)
-      '@radix-ui/react-direction': 1.0.1(react@18.2.0)
-      '@radix-ui/react-dismissable-layer': 1.0.4(react-dom@18.2.0)(react@18.2.0)
-      '@radix-ui/react-focus-guards': 1.0.1(react@18.2.0)
-      '@radix-ui/react-focus-scope': 1.0.3(react-dom@18.2.0)(react@18.2.0)
-      '@radix-ui/react-id': 1.0.1(react@18.2.0)
-      '@radix-ui/react-popper': 1.1.2(react-dom@18.2.0)(react@18.2.0)
-      '@radix-ui/react-portal': 1.0.3(react-dom@18.2.0)(react@18.2.0)
-      '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0)
-      '@radix-ui/react-slot': 1.0.2(react@18.2.0)
-      '@radix-ui/react-use-callback-ref': 1.0.1(react@18.2.0)
-      '@radix-ui/react-use-controllable-state': 1.0.1(react@18.2.0)
-      '@radix-ui/react-use-layout-effect': 1.0.1(react@18.2.0)
-      '@radix-ui/react-use-previous': 1.0.1(react@18.2.0)
-      '@radix-ui/react-visually-hidden': 1.0.3(react-dom@18.2.0)(react@18.2.0)
-      aria-hidden: 1.2.3
-      react: 18.2.0
-      react-dom: 18.2.0(react@18.2.0)
-      react-remove-scroll: 2.5.5(react@18.2.0)
-    dev: true
-
-  /@radix-ui/react-separator@1.0.3(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw==}
-    peerDependencies:
-      '@types/react': '*'
-      '@types/react-dom': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-      react-dom: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-      '@types/react-dom':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.23.2
-      '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0)
-      react: 18.2.0
-      react-dom: 18.2.0(react@18.2.0)
-    dev: true
-
-  /@radix-ui/react-slot@1.0.2(react@18.2.0):
+  /@radix-ui/react-slot@1.0.2(@types/react@18.0.28)(react@18.2.0):
     resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==}
     peerDependencies:
       '@types/react': '*'
@@ -5494,203 +5369,55 @@ packages:
       '@types/react':
         optional: true
     dependencies:
-      '@babel/runtime': 7.23.2
-      '@radix-ui/react-compose-refs': 1.0.1(react@18.2.0)
+      '@babel/runtime': 7.23.4
+      '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.0.28)(react@18.2.0)
+      '@types/react': 18.0.28
       react: 18.2.0
     dev: true
 
-  /@radix-ui/react-toggle-group@1.0.4(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-Uaj/M/cMyiyT9Bx6fOZO0SAG4Cls0GptBWiBmBxofmDbNVnYYoyRWj/2M/6VCi/7qcXFWnHhRUfdfZFvvkuu8A==}
+  /@readme/better-ajv-errors@1.6.0(ajv@8.12.0):
+    resolution: {integrity: sha512-9gO9rld84Jgu13kcbKRU+WHseNhaVt76wYMeRDGsUGYxwJtI3RmEJ9LY9dZCYQGI8eUZLuxb5qDja0nqklpFjQ==}
+    engines: {node: '>=14'}
     peerDependencies:
-      '@types/react': '*'
-      '@types/react-dom': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-      react-dom: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-      '@types/react-dom':
-        optional: true
+      ajv: 4.11.8 - 8
     dependencies:
-      '@babel/runtime': 7.23.2
-      '@radix-ui/primitive': 1.0.1
-      '@radix-ui/react-context': 1.0.1(react@18.2.0)
-      '@radix-ui/react-direction': 1.0.1(react@18.2.0)
-      '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0)
-      '@radix-ui/react-roving-focus': 1.0.4(react-dom@18.2.0)(react@18.2.0)
-      '@radix-ui/react-toggle': 1.0.3(react-dom@18.2.0)(react@18.2.0)
-      '@radix-ui/react-use-controllable-state': 1.0.1(react@18.2.0)
-      react: 18.2.0
-      react-dom: 18.2.0(react@18.2.0)
+      '@babel/code-frame': 7.23.4
+      '@babel/runtime': 7.23.4
+      '@humanwhocodes/momoa': 2.0.4
+      ajv: 8.12.0
+      chalk: 4.1.2
+      json-to-ast: 2.1.0
+      jsonpointer: 5.0.1
+      leven: 3.1.0
     dev: true
 
-  /@radix-ui/react-toggle@1.0.3(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-Pkqg3+Bc98ftZGsl60CLANXQBBQ4W3mTFS9EJvNxKMZ7magklKV69/id1mlAlOFDDfHvlCms0fx8fA4CMKDJHg==}
+  /@readme/json-schema-ref-parser@1.2.0:
+    resolution: {integrity: sha512-Bt3QVovFSua4QmHa65EHUmh2xS0XJ3rgTEUPH998f4OW4VVJke3BuS16f+kM0ZLOGdvIrzrPRqwihuv5BAjtrA==}
+    dependencies:
+      '@jsdevtools/ono': 7.1.3
+      '@types/json-schema': 7.0.15
+      call-me-maybe: 1.0.2
+      js-yaml: 4.1.0
+    dev: true
+
+  /@readme/openapi-parser@2.5.0(openapi-types@12.1.3):
+    resolution: {integrity: sha512-IbymbOqRuUzoIgxfAAR7XJt2FWl6n2yqN09fF5adacGm7W03siA3bj1Emql0X9D2T+RpBYz3x9zDsMhuoMP62A==}
+    engines: {node: '>=14'}
     peerDependencies:
-      '@types/react': '*'
-      '@types/react-dom': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-      react-dom: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-      '@types/react-dom':
-        optional: true
+      openapi-types: '>=7'
     dependencies:
-      '@babel/runtime': 7.23.2
-      '@radix-ui/primitive': 1.0.1
-      '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0)
-      '@radix-ui/react-use-controllable-state': 1.0.1(react@18.2.0)
-      react: 18.2.0
-      react-dom: 18.2.0(react@18.2.0)
+      '@apidevtools/openapi-schemas': 2.1.0
+      '@apidevtools/swagger-methods': 3.0.2
+      '@jsdevtools/ono': 7.1.3
+      '@readme/better-ajv-errors': 1.6.0(ajv@8.12.0)
+      '@readme/json-schema-ref-parser': 1.2.0
+      ajv: 8.12.0
+      ajv-draft-04: 1.0.0(ajv@8.12.0)
+      call-me-maybe: 1.0.2
+      openapi-types: 12.1.3
     dev: true
 
-  /@radix-ui/react-toolbar@1.0.4(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-tBgmM/O7a07xbaEkYJWYTXkIdU/1pW4/KZORR43toC/4XWyBCURK0ei9kMUdp+gTPPKBgYLxXmRSH1EVcIDp8Q==}
-    peerDependencies:
-      '@types/react': '*'
-      '@types/react-dom': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-      react-dom: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-      '@types/react-dom':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.23.2
-      '@radix-ui/primitive': 1.0.1
-      '@radix-ui/react-context': 1.0.1(react@18.2.0)
-      '@radix-ui/react-direction': 1.0.1(react@18.2.0)
-      '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0)
-      '@radix-ui/react-roving-focus': 1.0.4(react-dom@18.2.0)(react@18.2.0)
-      '@radix-ui/react-separator': 1.0.3(react-dom@18.2.0)(react@18.2.0)
-      '@radix-ui/react-toggle-group': 1.0.4(react-dom@18.2.0)(react@18.2.0)
-      react: 18.2.0
-      react-dom: 18.2.0(react@18.2.0)
-    dev: true
-
-  /@radix-ui/react-use-callback-ref@1.0.1(react@18.2.0):
-    resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==}
-    peerDependencies:
-      '@types/react': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.23.2
-      react: 18.2.0
-    dev: true
-
-  /@radix-ui/react-use-controllable-state@1.0.1(react@18.2.0):
-    resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==}
-    peerDependencies:
-      '@types/react': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.23.2
-      '@radix-ui/react-use-callback-ref': 1.0.1(react@18.2.0)
-      react: 18.2.0
-    dev: true
-
-  /@radix-ui/react-use-escape-keydown@1.0.3(react@18.2.0):
-    resolution: {integrity: sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==}
-    peerDependencies:
-      '@types/react': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.23.2
-      '@radix-ui/react-use-callback-ref': 1.0.1(react@18.2.0)
-      react: 18.2.0
-    dev: true
-
-  /@radix-ui/react-use-layout-effect@1.0.1(react@18.2.0):
-    resolution: {integrity: sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==}
-    peerDependencies:
-      '@types/react': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.23.2
-      react: 18.2.0
-    dev: true
-
-  /@radix-ui/react-use-previous@1.0.1(react@18.2.0):
-    resolution: {integrity: sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==}
-    peerDependencies:
-      '@types/react': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.23.2
-      react: 18.2.0
-    dev: true
-
-  /@radix-ui/react-use-rect@1.0.1(react@18.2.0):
-    resolution: {integrity: sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==}
-    peerDependencies:
-      '@types/react': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.23.2
-      '@radix-ui/rect': 1.0.1
-      react: 18.2.0
-    dev: true
-
-  /@radix-ui/react-use-size@1.0.1(react@18.2.0):
-    resolution: {integrity: sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==}
-    peerDependencies:
-      '@types/react': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.23.2
-      '@radix-ui/react-use-layout-effect': 1.0.1(react@18.2.0)
-      react: 18.2.0
-    dev: true
-
-  /@radix-ui/react-visually-hidden@1.0.3(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==}
-    peerDependencies:
-      '@types/react': '*'
-      '@types/react-dom': '*'
-      react: ^16.8 || ^17.0 || ^18.0
-      react-dom: ^16.8 || ^17.0 || ^18.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-      '@types/react-dom':
-        optional: true
-    dependencies:
-      '@babel/runtime': 7.23.2
-      '@radix-ui/react-primitive': 1.0.3(react-dom@18.2.0)(react@18.2.0)
-      react: 18.2.0
-      react-dom: 18.2.0(react@18.2.0)
-    dev: true
-
-  /@radix-ui/rect@1.0.1:
-    resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==}
-    dependencies:
-      '@babel/runtime': 7.23.2
-    dev: true
-
-  /@rollup/plugin-json@6.1.0(rollup@4.9.1):
+  /@rollup/plugin-json@6.1.0(rollup@4.12.0):
     resolution: {integrity: sha512-EGI2te5ENk1coGeADSIwZ7G2Q8CJS2sF120T7jLw4xFw9n7wIOXHo+kIYRAoVpJAN+kmqZSoO3Fp4JtoNF4ReA==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
@@ -5699,11 +5426,11 @@ packages:
       rollup:
         optional: true
     dependencies:
-      '@rollup/pluginutils': 5.1.0(rollup@4.9.1)
-      rollup: 4.9.1
+      '@rollup/pluginutils': 5.1.0(rollup@4.12.0)
+      rollup: 4.12.0
     dev: false
 
-  /@rollup/plugin-replace@5.0.5(rollup@4.9.1):
+  /@rollup/plugin-replace@5.0.5(rollup@4.12.0):
     resolution: {integrity: sha512-rYO4fOi8lMaTg/z5Jb+hKnrHHVn8j2lwkqwyS4kTRhKyWOLf2wST2sWXr4WzWiTcoHTp2sTjqUbqIj2E39slKQ==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
@@ -5712,12 +5439,12 @@ packages:
       rollup:
         optional: true
     dependencies:
-      '@rollup/pluginutils': 5.1.0(rollup@4.9.1)
-      magic-string: 0.30.5
-      rollup: 4.9.1
+      '@rollup/pluginutils': 5.1.0(rollup@4.12.0)
+      magic-string: 0.30.7
+      rollup: 4.12.0
     dev: false
 
-  /@rollup/pluginutils@5.1.0(rollup@4.9.1):
+  /@rollup/pluginutils@5.1.0(rollup@4.12.0):
     resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==}
     engines: {node: '>=14.0.0'}
     peerDependencies:
@@ -5729,108 +5456,108 @@ packages:
       '@types/estree': 1.0.5
       estree-walker: 2.0.2
       picomatch: 2.3.1
-      rollup: 4.9.1
+      rollup: 4.12.0
 
-  /@rollup/rollup-android-arm-eabi@4.9.1:
-    resolution: {integrity: sha512-6vMdBZqtq1dVQ4CWdhFwhKZL6E4L1dV6jUjuBvsavvNJSppzi6dLBbuV+3+IyUREaj9ZFvQefnQm28v4OCXlig==}
+  /@rollup/rollup-android-arm-eabi@4.12.0:
+    resolution: {integrity: sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==}
     cpu: [arm]
     os: [android]
     requiresBuild: true
     optional: true
 
-  /@rollup/rollup-android-arm64@4.9.1:
-    resolution: {integrity: sha512-Jto9Fl3YQ9OLsTDWtLFPtaIMSL2kwGyGoVCmPC8Gxvym9TCZm4Sie+cVeblPO66YZsYH8MhBKDMGZ2NDxuk/XQ==}
+  /@rollup/rollup-android-arm64@4.12.0:
+    resolution: {integrity: sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==}
     cpu: [arm64]
     os: [android]
     requiresBuild: true
     optional: true
 
-  /@rollup/rollup-darwin-arm64@4.9.1:
-    resolution: {integrity: sha512-LtYcLNM+bhsaKAIGwVkh5IOWhaZhjTfNOkGzGqdHvhiCUVuJDalvDxEdSnhFzAn+g23wgsycmZk1vbnaibZwwA==}
+  /@rollup/rollup-darwin-arm64@4.12.0:
+    resolution: {integrity: sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==}
     cpu: [arm64]
     os: [darwin]
     requiresBuild: true
     optional: true
 
-  /@rollup/rollup-darwin-x64@4.9.1:
-    resolution: {integrity: sha512-KyP/byeXu9V+etKO6Lw3E4tW4QdcnzDG/ake031mg42lob5tN+5qfr+lkcT/SGZaH2PdW4Z1NX9GHEkZ8xV7og==}
+  /@rollup/rollup-darwin-x64@4.12.0:
+    resolution: {integrity: sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==}
     cpu: [x64]
     os: [darwin]
     requiresBuild: true
     optional: true
 
-  /@rollup/rollup-linux-arm-gnueabihf@4.9.1:
-    resolution: {integrity: sha512-Yqz/Doumf3QTKplwGNrCHe/B2p9xqDghBZSlAY0/hU6ikuDVQuOUIpDP/YcmoT+447tsZTmirmjgG3znvSCR0Q==}
+  /@rollup/rollup-linux-arm-gnueabihf@4.12.0:
+    resolution: {integrity: sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==}
     cpu: [arm]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@rollup/rollup-linux-arm64-gnu@4.9.1:
-    resolution: {integrity: sha512-u3XkZVvxcvlAOlQJ3UsD1rFvLWqu4Ef/Ggl40WAVCuogf4S1nJPHh5RTgqYFpCOvuGJ7H5yGHabjFKEZGExk5Q==}
+  /@rollup/rollup-linux-arm64-gnu@4.12.0:
+    resolution: {integrity: sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==}
     cpu: [arm64]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@rollup/rollup-linux-arm64-musl@4.9.1:
-    resolution: {integrity: sha512-0XSYN/rfWShW+i+qjZ0phc6vZ7UWI8XWNz4E/l+6edFt+FxoEghrJHjX1EY/kcUGCnZzYYRCl31SNdfOi450Aw==}
+  /@rollup/rollup-linux-arm64-musl@4.12.0:
+    resolution: {integrity: sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==}
     cpu: [arm64]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@rollup/rollup-linux-riscv64-gnu@4.9.1:
-    resolution: {integrity: sha512-LmYIO65oZVfFt9t6cpYkbC4d5lKHLYv5B4CSHRpnANq0VZUQXGcCPXHzbCXCz4RQnx7jvlYB1ISVNCE/omz5cw==}
+  /@rollup/rollup-linux-riscv64-gnu@4.12.0:
+    resolution: {integrity: sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==}
     cpu: [riscv64]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@rollup/rollup-linux-x64-gnu@4.9.1:
-    resolution: {integrity: sha512-kr8rEPQ6ns/Lmr/hiw8sEVj9aa07gh1/tQF2Y5HrNCCEPiCBGnBUt9tVusrcBBiJfIt1yNaXN6r1CCmpbFEDpg==}
+  /@rollup/rollup-linux-x64-gnu@4.12.0:
+    resolution: {integrity: sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==}
     cpu: [x64]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@rollup/rollup-linux-x64-musl@4.9.1:
-    resolution: {integrity: sha512-t4QSR7gN+OEZLG0MiCgPqMWZGwmeHhsM4AkegJ0Kiy6TnJ9vZ8dEIwHw1LcZKhbHxTY32hp9eVCMdR3/I8MGRw==}
+  /@rollup/rollup-linux-x64-musl@4.12.0:
+    resolution: {integrity: sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==}
     cpu: [x64]
     os: [linux]
     requiresBuild: true
     optional: true
 
-  /@rollup/rollup-win32-arm64-msvc@4.9.1:
-    resolution: {integrity: sha512-7XI4ZCBN34cb+BH557FJPmh0kmNz2c25SCQeT9OiFWEgf8+dL6ZwJ8f9RnUIit+j01u07Yvrsuu1rZGxJCc51g==}
+  /@rollup/rollup-win32-arm64-msvc@4.12.0:
+    resolution: {integrity: sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==}
     cpu: [arm64]
     os: [win32]
     requiresBuild: true
     optional: true
 
-  /@rollup/rollup-win32-ia32-msvc@4.9.1:
-    resolution: {integrity: sha512-yE5c2j1lSWOH5jp+Q0qNL3Mdhr8WuqCNVjc6BxbVfS5cAS6zRmdiw7ktb8GNpDCEUJphILY6KACoFoRtKoqNQg==}
+  /@rollup/rollup-win32-ia32-msvc@4.12.0:
+    resolution: {integrity: sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==}
     cpu: [ia32]
     os: [win32]
     requiresBuild: true
     optional: true
 
-  /@rollup/rollup-win32-x64-msvc@4.9.1:
-    resolution: {integrity: sha512-PyJsSsafjmIhVgaI1Zdj7m8BB8mMckFah/xbpplObyHfiXzKcI5UOUXRyOdHW7nz4DpMCuzLnF7v5IWHenCwYA==}
+  /@rollup/rollup-win32-x64-msvc@4.12.0:
+    resolution: {integrity: sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==}
     cpu: [x64]
     os: [win32]
     requiresBuild: true
     optional: true
 
-  /@rushstack/node-core-library@3.62.0(@types/node@20.10.5):
-    resolution: {integrity: sha512-88aJn2h8UpSvdwuDXBv1/v1heM6GnBf3RjEy6ZPP7UnzHNCqOHA2Ut+ScYUbXcqIdfew9JlTAe3g+cnX9xQ/Aw==}
+  /@rushstack/node-core-library@3.63.0(@types/node@20.11.22):
+    resolution: {integrity: sha512-Q7B3dVpBQF1v+mUfxNcNZh5uHVR8ntcnkN5GYjbBLrxUYHBGKbnCM+OdcN+hzCpFlLBH6Ob0dEHhZ0spQwf24A==}
     peerDependencies:
       '@types/node': '*'
     peerDependenciesMeta:
       '@types/node':
         optional: true
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
       colors: 1.2.5
       fs-extra: 7.0.1
       import-lazy: 4.0.0
@@ -5856,10 +5583,8 @@ packages:
       string-argv: 0.3.1
     dev: true
 
-  /@sharkey/sfm-js@0.24.3:
-    resolution: {integrity: sha512-Fd2LWPYNVmnTg9AKdJm3MLMvYdxQafq/0eQlmJhUnQheRVm3f1xHrFFY12+yUWIq7rS0uxrKEmrVLnPzRqYG6Q==, tarball: https://git.joinsharkey.org/api/packages/Sharkey/npm/%40sharkey%2Fsfm-js/-/0.24.3/sfm-js-0.24.3.tgz}
-    dependencies:
-      '@twemoji/parser': 15.0.0
+  /@shikijs/core@1.1.7:
+    resolution: {integrity: sha512-gTYLUIuD1UbZp/11qozD3fWpUTuMqPSf3svDMMrL0UmlGU7D9dPw/V1FonwAorCUJBltaaESxq90jrSjQyGixg==}
     dev: false
 
   /@sideway/address@4.1.4:
@@ -5876,29 +5601,25 @@ packages:
     resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==}
     dev: true
 
-  /@simplewebauthn/server@8.3.5:
-    resolution: {integrity: sha512-Y6FkggTkzUdPk3cG3LLCiv7rqPQ3QI7g//RU9937G1pxogChvx12Y7/AZdWeMoeP+LFl0fPpdc1bIE0etJOxGA==}
+  /@simplewebauthn/server@9.0.3:
+    resolution: {integrity: sha512-FMZieoBosrVLFxCnxPFD9Enhd1U7D8nidVDT4MsHc6l4fdVcjoeHjDueeXCloO1k5O/fZg1fsSXXPKbY2XTzDA==}
     engines: {node: '>=16.0.0'}
     dependencies:
       '@hexagon/base64': 1.1.27
-      '@peculiar/asn1-android': 2.3.6
-      '@peculiar/asn1-ecc': 2.3.6
-      '@peculiar/asn1-rsa': 2.3.6
-      '@peculiar/asn1-schema': 2.3.6
-      '@peculiar/asn1-x509': 2.3.6
-      '@simplewebauthn/typescript-types': 8.3.4
-      cbor-x: 1.5.4
+      '@levischuck/tiny-cbor': 0.2.2
+      '@peculiar/asn1-android': 2.3.10
+      '@peculiar/asn1-ecc': 2.3.8
+      '@peculiar/asn1-rsa': 2.3.8
+      '@peculiar/asn1-schema': 2.3.8
+      '@peculiar/asn1-x509': 2.3.8
+      '@simplewebauthn/types': 9.0.1
       cross-fetch: 4.0.0
     transitivePeerDependencies:
       - encoding
     dev: false
 
-  /@simplewebauthn/typescript-types@8.3.4:
-    resolution: {integrity: sha512-38xtca0OqfRVNloKBrFB5LEM6PN5vzFbJG6rAutPVrtGHFYxPdiV3btYWq0eAZAZmP+dqFPYJxJWeJrGfmYHng==}
-
-  /@sinclair/typebox@0.24.51:
-    resolution: {integrity: sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==}
-    dev: true
+  /@simplewebauthn/types@9.0.1:
+    resolution: {integrity: sha512-tGSRP1QvsAvsJmnOlRQyw/mvK9gnPtjEc5fg2+m8n+QUa+D7rvrKkOYyfpy42GTs90X3RDOnqJgfHt+qO67/+w==}
 
   /@sinclair/typebox@0.27.8:
     resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
@@ -5917,12 +5638,6 @@ packages:
     engines: {node: '>=16'}
     dev: false
 
-  /@sinonjs/commons@1.8.6:
-    resolution: {integrity: sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==}
-    dependencies:
-      type-detect: 4.0.8
-    dev: true
-
   /@sinonjs/commons@2.0.0:
     resolution: {integrity: sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==}
     dependencies:
@@ -5946,14 +5661,8 @@ packages:
       '@sinonjs/commons': 3.0.0
     dev: false
 
-  /@sinonjs/fake-timers@9.1.2:
-    resolution: {integrity: sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==}
-    dependencies:
-      '@sinonjs/commons': 1.8.6
-    dev: true
-
-  /@sinonjs/samsam@7.0.1:
-    resolution: {integrity: sha512-zsAk2Jkiq89mhZovB2LLOdTCxJF4hqqTToGP0ASWlhp4I1hqOjcfmZGafXntCN7MDC6yySH0mFHrYtHceOeLmw==}
+  /@sinonjs/samsam@8.0.0:
+    resolution: {integrity: sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==}
     dependencies:
       '@sinonjs/commons': 2.0.0
       lodash.get: 4.4.2
@@ -6427,121 +6136,110 @@ packages:
     resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==}
     dev: false
 
-  /@storybook/addon-actions@7.6.5:
-    resolution: {integrity: sha512-lW/m9YcaNfBZk+TZLxyzHdd563mBWpsUIveOKYjcPdl/q0FblWWZrRsFHqwLK1ldZ4AZXs8J/47G8CBr6Ew2uQ==}
+  /@storybook/addon-actions@8.0.0-beta.6:
+    resolution: {integrity: sha512-g+X2M6Awg21vkXzRP7hWBYCdbXnxJ3BJWsP7BblYmPo2J7eJDzhQascNyTmSr0pb1/7nv+tworGviXThgvlUgw==}
     dependencies:
-      '@storybook/core-events': 7.6.5
+      '@storybook/core-events': 8.0.0-beta.6
       '@storybook/global': 5.0.0
-      '@types/uuid': 9.0.7
+      '@types/uuid': 9.0.8
       dequal: 2.0.3
       polished: 4.2.2
       uuid: 9.0.1
     dev: true
 
-  /@storybook/addon-backgrounds@7.6.5:
-    resolution: {integrity: sha512-wZZOL19vg4TTRtOTl71XKqPe5hQx3XUh9Fle0wOi91FiFrBdqusrppnyS89wPS8RQG5lXEOFEUvYcMmdCcdZfw==}
+  /@storybook/addon-backgrounds@8.0.0-beta.6:
+    resolution: {integrity: sha512-C8MS635knAOSat5JbkpZXOiAqkDm1bKWvuVqiQfbX2into45/aAuyN3mYxveGIRTRjPJCv/UpostkLSNvfH/NQ==}
     dependencies:
       '@storybook/global': 5.0.0
       memoizerific: 1.11.3
       ts-dedent: 2.2.0
     dev: true
 
-  /@storybook/addon-controls@7.6.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-EdSZ2pYf74mOXZGGJ22lrDvdvL0YKc95iWv9FFEhUFOloMy/0OZPB2ybYmd2KVCy3SeIE4Zfeiw8pDXdCUniOQ==}
+  /@storybook/addon-controls@8.0.0-beta.6(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-G96MH7yU/KShq3lTrkgtU1IbNQXLVc3BG7miaLqzQgWFN8SSAivlu3vk1Vffui3+3Dv52WZhMKi3hueNfnM1Xw==}
     dependencies:
-      '@storybook/blocks': 7.6.5(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/blocks': 8.0.0-beta.6(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)
       lodash: 4.17.21
       ts-dedent: 2.2.0
     transitivePeerDependencies:
       - '@types/react'
-      - '@types/react-dom'
       - encoding
       - react
       - react-dom
       - supports-color
     dev: true
 
-  /@storybook/addon-docs@7.6.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-D9tZyD41IujCHiPYdfS2bKtZRJPNwO4EydzyqODXppomluhFbY3uTEaf0H1UFnJLQxWNXZ7rr3aS0V3O6yu8pA==}
-    peerDependencies:
-      react: ^16.8.0 || ^17.0.0 || ^18.0.0
-      react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
+  /@storybook/addon-docs@8.0.0-beta.6:
+    resolution: {integrity: sha512-VLys4EuL8XVhmu1QxUiUG5keID8v/FsC5L71Y0Wcf5D+ll6ZD8vCqEtbMY3TiJJ9NqqNIcmcG3bG6JVXOYcD8g==}
     dependencies:
-      '@jest/transform': 29.7.0
-      '@mdx-js/react': 2.3.0(react@18.2.0)
-      '@storybook/blocks': 7.6.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/client-logger': 7.6.5
-      '@storybook/components': 7.6.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/csf-plugin': 7.6.5
-      '@storybook/csf-tools': 7.6.5
+      '@babel/core': 7.24.0
+      '@mdx-js/react': 3.0.1(@types/react@18.0.28)(react@18.2.0)
+      '@storybook/blocks': 8.0.0-beta.6(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/client-logger': 8.0.0-beta.6
+      '@storybook/components': 8.0.0-beta.6(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/csf-plugin': 8.0.0-beta.6
+      '@storybook/csf-tools': 8.0.0-beta.6
       '@storybook/global': 5.0.0
-      '@storybook/mdx2-csf': 1.0.0
-      '@storybook/node-logger': 7.6.5
-      '@storybook/postinstall': 7.6.5
-      '@storybook/preview-api': 7.6.5
-      '@storybook/react-dom-shim': 7.6.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/theming': 7.6.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.6.5
+      '@storybook/node-logger': 8.0.0-beta.6
+      '@storybook/preview-api': 8.0.0-beta.6
+      '@storybook/react-dom-shim': 8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/theming': 8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 8.0.0-beta.6
+      '@types/react': 18.0.28
       fs-extra: 11.1.1
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
-      remark-external-links: 8.0.0
-      remark-slug: 6.1.0
+      rehype-external-links: 3.0.0
+      rehype-slug: 6.0.0
       ts-dedent: 2.2.0
     transitivePeerDependencies:
-      - '@types/react'
-      - '@types/react-dom'
       - encoding
       - supports-color
     dev: true
 
-  /@storybook/addon-essentials@7.6.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-VCLj1JAEpGoqF5iFJOo1CZFFck/tg4m/98DLdQuNuXvxT6jqaF0NI9UUQuJLIGteDCR7NKRbTFc1hV3/Ev+Ziw==}
-    peerDependencies:
-      react: ^16.8.0 || ^17.0.0 || ^18.0.0
-      react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
+  /@storybook/addon-essentials@8.0.0-beta.6(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-6Vjf03c0oIavXqOK9DIN0UeH0iJFmBoVrFt1mTwydMxchyJBSP785MSd9DuFhLdYZPQTMHaR4/JhOIjdDV8mbA==}
     dependencies:
-      '@storybook/addon-actions': 7.6.5
-      '@storybook/addon-backgrounds': 7.6.5
-      '@storybook/addon-controls': 7.6.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/addon-docs': 7.6.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/addon-highlight': 7.6.5
-      '@storybook/addon-measure': 7.6.5
-      '@storybook/addon-outline': 7.6.5
-      '@storybook/addon-toolbars': 7.6.5
-      '@storybook/addon-viewport': 7.6.5
-      '@storybook/core-common': 7.6.5
-      '@storybook/manager-api': 7.6.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/node-logger': 7.6.5
-      '@storybook/preview-api': 7.6.5
-      react: 18.2.0
-      react-dom: 18.2.0(react@18.2.0)
+      '@storybook/addon-actions': 8.0.0-beta.6
+      '@storybook/addon-backgrounds': 8.0.0-beta.6
+      '@storybook/addon-controls': 8.0.0-beta.6(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/addon-docs': 8.0.0-beta.6
+      '@storybook/addon-highlight': 8.0.0-beta.6
+      '@storybook/addon-measure': 8.0.0-beta.6
+      '@storybook/addon-outline': 8.0.0-beta.6
+      '@storybook/addon-toolbars': 8.0.0-beta.6
+      '@storybook/addon-viewport': 8.0.0-beta.6
+      '@storybook/core-common': 8.0.0-beta.6
+      '@storybook/manager-api': 8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/node-logger': 8.0.0-beta.6
+      '@storybook/preview-api': 8.0.0-beta.6
       ts-dedent: 2.2.0
     transitivePeerDependencies:
       - '@types/react'
-      - '@types/react-dom'
       - encoding
+      - react
+      - react-dom
       - supports-color
     dev: true
 
-  /@storybook/addon-highlight@7.6.5:
-    resolution: {integrity: sha512-CxzmIb30F9nLPQwT0lCPYhOAwGlGF4IkgkO8hYA7VfGCGUkJZEyyN/YkP/ZCUSdCIRChDBouR3KiFFd4mDFKzg==}
+  /@storybook/addon-highlight@8.0.0-beta.6:
+    resolution: {integrity: sha512-U+qz4TNLrw24t1eZ2Zmhl2FZKZKiwHbibq4qR5ruAFe9W5/aMHqPuBB0POroaGu3P+tyDP2G46dckMNXVraiWA==}
     dependencies:
       '@storybook/global': 5.0.0
     dev: true
 
-  /@storybook/addon-interactions@7.6.5:
-    resolution: {integrity: sha512-8Hzt9u1DQzFvtGER/hCGIvGpCoVwzVoqpM98f2KAIVx/NMFmRW7UyKihXzw1j2t4q2ZaF2jZDYWCBqlP+iwILA==}
+  /@storybook/addon-interactions@8.0.0-beta.6:
+    resolution: {integrity: sha512-KSigq+7vCA1tnj31MjhM7xaqickR1guZdjyXVRx7gi7qbdhSuCQv52gAkVpDapwlEuvGFCCYxzt7tmcn6dkLZQ==}
     dependencies:
       '@storybook/global': 5.0.0
-      '@storybook/types': 7.6.5
+      '@storybook/types': 8.0.0-beta.6
       jest-mock: 27.5.1
       polished: 4.2.2
       ts-dedent: 2.2.0
     dev: true
 
-  /@storybook/addon-links@7.6.5(react@18.2.0):
-    resolution: {integrity: sha512-Lx4Ng+iXt0YpIrKGr+nOZlpN9ypOoEDoP/7bZ6m7GXuVAkDm3JrRCBp7e2ZKSKcTxPdjPuO9HVKkIjtqjINlpw==}
+  /@storybook/addon-links@8.0.0-beta.6(react@18.2.0):
+    resolution: {integrity: sha512-+5knw5CHEb23n6Bm9Xp9nmoLRqWZ3QVGb1gNI3mGwmkpLwesohFR4fW7OrdRmzYHpS0PyYToZyfTCMYrmjBDvg==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
     peerDependenciesMeta:
@@ -6554,71 +6252,76 @@ packages:
       ts-dedent: 2.2.0
     dev: true
 
-  /@storybook/addon-measure@7.6.5:
-    resolution: {integrity: sha512-tlUudVQSrA+bwI4dhO8J7nYHtYdylcBZ86ybnqMmdTthsnyc7jnaFVQwbb6bbQJpPxvEvoNds5bVGUFocuvymQ==}
+  /@storybook/addon-mdx-gfm@8.0.0-beta.6:
+    resolution: {integrity: sha512-b4pb59rrX+C/oYFeEiHb8jJn0h9WZSkHVkLIgaj0G64Nd9OpyKZXMbGpDxwMq4LTi1w65Wddi1UUQbUVVDNHRw==}
+    dependencies:
+      '@storybook/node-logger': 8.0.0-beta.6
+      remark-gfm: 4.0.0
+      ts-dedent: 2.2.0
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@storybook/addon-measure@8.0.0-beta.6:
+    resolution: {integrity: sha512-D+KzWRULcbwR8/ysD7Qbw4uWBn9gwNm9s3IeVuhupawUb3u+H4XfVCOW2rA5qry/x8aroKOhAmyKd9v4i+l3pg==}
     dependencies:
       '@storybook/global': 5.0.0
       tiny-invariant: 1.3.1
     dev: true
 
-  /@storybook/addon-outline@7.6.5:
-    resolution: {integrity: sha512-P7X4+Z9L/l/RZW9UvvM+iuK2SUHD22KPc+dbYOifRXDovUqhfmcKVh1CUqTDMyZrg2ZAbropehMz1eI9BlQfxg==}
+  /@storybook/addon-outline@8.0.0-beta.6:
+    resolution: {integrity: sha512-U+5TFTj+gtkIiIJCk6h7zbrP588CUipzVVsiDTSLl4pc+H3ylGTGncq3ZGtOyl+DCoBsQCgKxy2YWQtKHrESOw==}
     dependencies:
       '@storybook/global': 5.0.0
       ts-dedent: 2.2.0
     dev: true
 
-  /@storybook/addon-storysource@7.6.5:
-    resolution: {integrity: sha512-mlGReftuGxfyfLXsnw4GF03G79w3rKKRclNasOVPuAR2vlSTRyltoglZ8TcXfxNQ+RzywtEZkjD7SeJZsuvBbQ==}
+  /@storybook/addon-storysource@8.0.0-beta.6:
+    resolution: {integrity: sha512-J9sCZ5/KQW2hbfKsom8LmgSWJxw+Kp/7LjIHGevFfov/i9DR8i9xbh5htUwC9fx+vWGR87tez03b+oUJbyHPog==}
     dependencies:
-      '@storybook/source-loader': 7.6.5
+      '@storybook/source-loader': 8.0.0-beta.6
       estraverse: 5.3.0
       tiny-invariant: 1.3.1
     dev: true
 
-  /@storybook/addon-toolbars@7.6.5:
-    resolution: {integrity: sha512-/zqWbVNE/SHc8I5Prnd2Q8U57RGEIYvHfeXjfkuLcE2Quc4Iss4x/9eU7SKu4jm+IOO2s0wlN6HcqI3XEf2XxA==}
+  /@storybook/addon-toolbars@8.0.0-beta.6:
+    resolution: {integrity: sha512-ClT5spwh6S1rUvyFEIFQndE3VK6tpwI2cyIW4E20LajtfUmj3dOfJQX/ZbnhEH3sDBsCm97ysZ/mNR0mbBHZrg==}
     dev: true
 
-  /@storybook/addon-viewport@7.6.5:
-    resolution: {integrity: sha512-9ghKTaduIUvQ6oShmWLuwMeTjtMR4RgKeKHrTJ7THMqvE/ydDPCYeL7ugF65ocXZSEz/QmxdK7uL686ZMKsqNA==}
+  /@storybook/addon-viewport@8.0.0-beta.6:
+    resolution: {integrity: sha512-KNYGM6nVrz/Ej25W3lcpaxxJDYVXBYeGl60FWN/WlqRnjo4c4Fyufl6Xev2plQ3eI8jIvWEdGNC/Z/NQnDx1+Q==}
     dependencies:
       memoizerific: 1.11.3
     dev: true
 
-  /@storybook/addons@7.6.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-v+d8io1MsgTd7rruYInfKXY0c1uXn+ADLxAppUI0PUwPFYwg9tLn3cvwgt5SVum9E5IkVQwXoW6JNkDC5fC8XQ==}
-    dependencies:
-      '@storybook/manager-api': 7.6.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.6.5
-      '@storybook/types': 7.6.5
-    transitivePeerDependencies:
-      - react
-      - react-dom
-    dev: true
-
-  /@storybook/blocks@7.6.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-/NjuYkPks5w9lKn47KLgVC5cBkwfc+ERAp0CY0Xe//BQJkP+bcI8lE8d9Qc9IXFbOTvYEULeQrFgCkesk5BmLg==}
+  /@storybook/blocks@8.0.0-beta.6(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-QkrWT0BELNv3UGv/dtNuB/ROZn0f9VpERbadhXLE/oNXMJLalyjEbRGM635l0lDeoqjYnWHl+tuM6DTe1Xpk2w==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
+    peerDependenciesMeta:
+      react:
+        optional: true
+      react-dom:
+        optional: true
     dependencies:
-      '@storybook/channels': 7.6.5
-      '@storybook/client-logger': 7.6.5
-      '@storybook/components': 7.6.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-events': 7.6.5
+      '@storybook/channels': 8.0.0-beta.6
+      '@storybook/client-logger': 8.0.0-beta.6
+      '@storybook/components': 8.0.0-beta.6(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-events': 8.0.0-beta.6
       '@storybook/csf': 0.1.2
-      '@storybook/docs-tools': 7.6.5
+      '@storybook/docs-tools': 8.0.0-beta.6
       '@storybook/global': 5.0.0
-      '@storybook/manager-api': 7.6.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.6.5
-      '@storybook/theming': 7.6.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.6.5
+      '@storybook/icons': 1.2.5(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/manager-api': 8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 8.0.0-beta.6
+      '@storybook/theming': 8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 8.0.0-beta.6
       '@types/lodash': 4.14.191
       color-convert: 2.0.1
       dequal: 2.0.3
       lodash: 4.17.21
-      markdown-to-jsx: 7.2.0(react@18.2.0)
+      markdown-to-jsx: 7.3.2(react@18.2.0)
       memoizerific: 1.11.3
       polished: 4.2.2
       react: 18.2.0
@@ -6630,27 +6333,24 @@ packages:
       util-deprecate: 1.0.2
     transitivePeerDependencies:
       - '@types/react'
-      - '@types/react-dom'
       - encoding
       - supports-color
     dev: true
 
-  /@storybook/builder-manager@7.6.5:
-    resolution: {integrity: sha512-FQyI+tfzMam2XKXq7k921YVafIJs9Vqvos5qx8vyRnRffo55UU8tgunwjGn0PswtbMm6sThVqE0C0ZzVr7RG8A==}
+  /@storybook/builder-manager@8.0.0-beta.6:
+    resolution: {integrity: sha512-bB/gSsPIpU22Tc6YTjPZdw1RM6nrsuJJ9aYXGqEJTqA4l4lBUN7fwIZQ1x/pS+5LbeUO0J9lAhGXurS+m8rI2A==}
     dependencies:
       '@fal-works/esbuild-plugin-global-externals': 2.1.2
-      '@storybook/core-common': 7.6.5
-      '@storybook/manager': 7.6.5
-      '@storybook/node-logger': 7.6.5
+      '@storybook/core-common': 8.0.0-beta.6
+      '@storybook/manager': 8.0.0-beta.6
+      '@storybook/node-logger': 8.0.0-beta.6
       '@types/ejs': 3.1.2
-      '@types/find-cache-dir': 3.2.1
-      '@yarnpkg/esbuild-plugin-pnp': 3.0.0-rc.15(esbuild@0.18.17)
+      '@yarnpkg/esbuild-plugin-pnp': 3.0.0-rc.15(esbuild@0.18.20)
       browser-assert: 1.2.1
       ejs: 3.1.9
-      esbuild: 0.18.17
+      esbuild: 0.18.20
       esbuild-plugin-alias: 0.2.1
       express: 4.18.2
-      find-cache-dir: 3.3.2
       fs-extra: 11.1.1
       process: 0.11.10
       util: 0.12.5
@@ -6659,12 +6359,12 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/builder-vite@7.6.5(typescript@5.3.3)(vite@5.0.10):
-    resolution: {integrity: sha512-VbAYTGr92lgCWTwO2Z7NgSW3f5/K4Vr0Qxa2IlTgMCymWdDbWdIQiREcmCP0vjAGM2ftq1+vxngohVgx/r7pUw==}
+  /@storybook/builder-vite@8.0.0-beta.6(typescript@5.3.3)(vite@5.1.4):
+    resolution: {integrity: sha512-3P5uTZqwwcUW64Hep/VtJXpQYi5vTkmqAjwZvr8gmzr37NYq3YT/PiSGn4CaZswSx5Z/lSYq3In8oIwmj/a1/g==}
     peerDependencies:
       '@preact/preset-vite': '*'
       typescript: '>= 4.3.x'
-      vite: ^3.0.0 || ^4.0.0 || ^5.0.0
+      vite: ^4.0.0 || ^5.0.0
       vite-plugin-glimmerx: '*'
     peerDependenciesMeta:
       '@preact/preset-vite':
@@ -6674,57 +6374,57 @@ packages:
       vite-plugin-glimmerx:
         optional: true
     dependencies:
-      '@storybook/channels': 7.6.5
-      '@storybook/client-logger': 7.6.5
-      '@storybook/core-common': 7.6.5
-      '@storybook/csf-plugin': 7.6.5
-      '@storybook/node-logger': 7.6.5
-      '@storybook/preview': 7.6.5
-      '@storybook/preview-api': 7.6.5
-      '@storybook/types': 7.6.5
+      '@storybook/channels': 8.0.0-beta.6
+      '@storybook/client-logger': 8.0.0-beta.6
+      '@storybook/core-common': 8.0.0-beta.6
+      '@storybook/core-events': 8.0.0-beta.6
+      '@storybook/csf-plugin': 8.0.0-beta.6
+      '@storybook/node-logger': 8.0.0-beta.6
+      '@storybook/preview': 8.0.0-beta.6
+      '@storybook/preview-api': 8.0.0-beta.6
+      '@storybook/types': 8.0.0-beta.6
       '@types/find-cache-dir': 3.2.1
       browser-assert: 1.2.1
       es-module-lexer: 0.9.3
       express: 4.18.2
       find-cache-dir: 3.3.2
       fs-extra: 11.1.1
-      magic-string: 0.30.5
-      rollup: 3.29.4
+      magic-string: 0.30.7
+      ts-dedent: 2.2.0
       typescript: 5.3.3
-      vite: 5.0.10(@types/node@20.10.5)(sass@1.69.5)(terser@5.26.0)
+      vite: 5.1.4(@types/node@20.11.22)(sass@1.71.1)(terser@5.28.1)
     transitivePeerDependencies:
       - encoding
       - supports-color
     dev: true
 
-  /@storybook/channels@7.6.5:
-    resolution: {integrity: sha512-FIlNkyfQy9uHoJfAFL2/wO3ASGJELFvBzURBE2rcEF/TS7GcUiqWnBfiDxAbwSEjSOm2F0eEq3UXhaZEjpJHDw==}
+  /@storybook/channels@8.0.0-beta.6:
+    resolution: {integrity: sha512-DjwJhty45gQifo+TvGqddLX+NX1iGTmZyGLxlqPMpdp+x/yq8WwVZ316Q7tLt6z6fyAmsroc3ma5p1iLhqpV7g==}
     dependencies:
-      '@storybook/client-logger': 7.6.5
-      '@storybook/core-events': 7.6.5
+      '@storybook/client-logger': 8.0.0-beta.6
+      '@storybook/core-events': 8.0.0-beta.6
       '@storybook/global': 5.0.0
       qs: 6.11.1
       telejson: 7.2.0
       tiny-invariant: 1.3.1
     dev: true
 
-  /@storybook/cli@7.6.5:
-    resolution: {integrity: sha512-w+Y8dx5oCLQVESOVmpsQuFksr/ewARKrnSKl9kwnVMN4sMgjOgoZ3zmV66J7SKexvwyuwlOjf840pmEglGdPPg==}
+  /@storybook/cli@8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-sREQYnPds2bwQS7FLbRy7oaxGvOmYhPEYVf93pWKyo/qwSWyXEXbqGCGT6bNhSl/xzqXX7VryLDmuOoHmVTh1g==}
     hasBin: true
     dependencies:
-      '@babel/core': 7.23.3
-      '@babel/preset-env': 7.23.6(@babel/core@7.23.3)
-      '@babel/types': 7.23.4
+      '@babel/core': 7.24.0
+      '@babel/types': 7.24.0
       '@ndelangen/get-tarball': 3.0.7
-      '@storybook/codemod': 7.6.5
-      '@storybook/core-common': 7.6.5
-      '@storybook/core-events': 7.6.5
-      '@storybook/core-server': 7.6.5
-      '@storybook/csf-tools': 7.6.5
-      '@storybook/node-logger': 7.6.5
-      '@storybook/telemetry': 7.6.5
-      '@storybook/types': 7.6.5
-      '@types/semver': 7.5.6
+      '@storybook/codemod': 8.0.0-beta.6
+      '@storybook/core-common': 8.0.0-beta.6
+      '@storybook/core-events': 8.0.0-beta.6
+      '@storybook/core-server': 8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/csf-tools': 8.0.0-beta.6
+      '@storybook/node-logger': 8.0.0-beta.6
+      '@storybook/telemetry': 8.0.0-beta.6
+      '@storybook/types': 8.0.0-beta.6
+      '@types/semver': 7.5.8
       '@yarnpkg/fslib': 2.10.3
       '@yarnpkg/libzip': 2.3.0
       chalk: 4.1.2
@@ -6733,103 +6433,95 @@ packages:
       detect-indent: 6.1.0
       envinfo: 7.8.1
       execa: 5.1.1
-      express: 4.18.2
       find-up: 5.0.0
       fs-extra: 11.1.1
       get-npm-tarball-url: 2.0.3
-      get-port: 5.1.1
       giget: 1.1.2
       globby: 11.1.0
       jscodeshift: 0.15.1(@babel/preset-env@7.23.6)
       leven: 3.1.0
       ora: 5.4.1
-      prettier: 2.8.8
+      prettier: 3.2.5
       prompts: 2.4.2
-      puppeteer-core: 2.1.1
       read-pkg-up: 7.0.1
-      semver: 7.5.4
-      simple-update-notifier: 2.0.0
+      semver: 7.6.0
       strip-json-comments: 3.1.1
       tempy: 1.0.1
+      tiny-invariant: 1.3.1
       ts-dedent: 2.2.0
-      util-deprecate: 1.0.2
     transitivePeerDependencies:
+      - '@babel/preset-env'
       - bufferutil
       - encoding
+      - react
+      - react-dom
       - supports-color
       - utf-8-validate
     dev: true
 
-  /@storybook/client-logger@7.6.5:
-    resolution: {integrity: sha512-S5aROWgssqg7tcs9lgW5wmCAz4SxMAtioiyVj5oFecmPCbQtFVIAREYzeoxE4GfJL+plrfRkum4BzziANn8EhQ==}
+  /@storybook/client-logger@8.0.0-beta.6:
+    resolution: {integrity: sha512-XX9CSWt9NDO/1K8tTYV+yuj0ur4HznM1Vc5mY5AwT5xh0RP5HtWZ+VoJfrWYXlBoRXaj0gf8si+FO+lSW82DcQ==}
     dependencies:
       '@storybook/global': 5.0.0
     dev: true
 
-  /@storybook/codemod@7.6.5:
-    resolution: {integrity: sha512-K5C9ltBClZ0aSyujGt3RJFtRicrUZy8nzhHrcADUj27rrQD26jH/p+Y05jWKj9JcI8SyMg978GN5X/1aw2Y31A==}
+  /@storybook/codemod@8.0.0-beta.6:
+    resolution: {integrity: sha512-ttQYDkhKmtU6Qbg+Kgn4K2XXf8XMpa2euuC6PmYffBD7/qLiGfABfBc4FHKRv4yScnvKK7Ehy7K0lvipfg6tXw==}
     dependencies:
-      '@babel/core': 7.23.3
-      '@babel/preset-env': 7.23.6(@babel/core@7.23.3)
-      '@babel/types': 7.23.4
+      '@babel/core': 7.24.0
+      '@babel/preset-env': 7.23.6(@babel/core@7.24.0)
+      '@babel/types': 7.24.0
       '@storybook/csf': 0.1.2
-      '@storybook/csf-tools': 7.6.5
-      '@storybook/node-logger': 7.6.5
-      '@storybook/types': 7.6.5
+      '@storybook/csf-tools': 8.0.0-beta.6
+      '@storybook/node-logger': 8.0.0-beta.6
+      '@storybook/types': 8.0.0-beta.6
       '@types/cross-spawn': 6.0.2
       cross-spawn: 7.0.3
       globby: 11.1.0
       jscodeshift: 0.15.1(@babel/preset-env@7.23.6)
       lodash: 4.17.21
-      prettier: 2.8.8
-      recast: 0.23.1
+      prettier: 3.2.5
+      recast: 0.23.4
+      tiny-invariant: 1.3.1
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@storybook/components@7.6.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-w4ZucbBBZ+NKMWlJKVj2I/bMBBq7gzDp9lzc4+8QaQ3vUPXKqc1ilIPYo/7UR5oxwDVMZocmMSgl9L8lvf7+Mw==}
+  /@storybook/components@8.0.0-beta.6(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-J3aJtPgaSco0sefvRMBLFsWbslhKMhaS3U+5baRqlV5bjPLZN+d4P18gP1RMaw/coh6DiKEQJZuHRoPIOdt4CA==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
     dependencies:
-      '@radix-ui/react-select': 1.2.2(react-dom@18.2.0)(react@18.2.0)
-      '@radix-ui/react-toolbar': 1.0.4(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/client-logger': 7.6.5
+      '@radix-ui/react-slot': 1.0.2(@types/react@18.0.28)(react@18.2.0)
+      '@storybook/client-logger': 8.0.0-beta.6
       '@storybook/csf': 0.1.2
       '@storybook/global': 5.0.0
-      '@storybook/theming': 7.6.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.6.5
+      '@storybook/icons': 1.2.5(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/theming': 8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 8.0.0-beta.6
       memoizerific: 1.11.3
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
-      use-resize-observer: 9.1.0(react-dom@18.2.0)(react@18.2.0)
       util-deprecate: 1.0.2
     transitivePeerDependencies:
       - '@types/react'
-      - '@types/react-dom'
     dev: true
 
-  /@storybook/core-client@7.6.5:
-    resolution: {integrity: sha512-6FtyJcz8MSl+JYwNJZ53FM6rkT27pFHWcJPdtw/9229Ec8as9RpkNeZ/NBZjRTeDkn9Ki0VOiVAefNie9tZ/8Q==}
+  /@storybook/core-common@8.0.0-beta.6:
+    resolution: {integrity: sha512-Mah4Kx/VBNhHaX6neYHTiVwfD93yf3LVVfLTS9WcJFOpek74EAAqbARV3vzOn/utOI75N7yu2PCVoKi5KkDoVw==}
     dependencies:
-      '@storybook/client-logger': 7.6.5
-      '@storybook/preview-api': 7.6.5
-    dev: true
-
-  /@storybook/core-common@7.6.5:
-    resolution: {integrity: sha512-z4EgzZSIVbID6Ib0jhh3jimKeaDWU8OOhoZYfn3galFmgQWowWOv1oMgipWiXfRLWw9DaLFQiCHIdLANH+VO2g==}
-    dependencies:
-      '@storybook/core-events': 7.6.5
-      '@storybook/node-logger': 7.6.5
-      '@storybook/types': 7.6.5
-      '@types/find-cache-dir': 3.2.1
-      '@types/node': 18.17.15
-      '@types/node-fetch': 2.6.4
-      '@types/pretty-hrtime': 1.0.1
+      '@storybook/core-events': 8.0.0-beta.6
+      '@storybook/csf-tools': 8.0.0-beta.6
+      '@storybook/node-logger': 8.0.0-beta.6
+      '@storybook/types': 8.0.0-beta.6
+      '@yarnpkg/fslib': 2.10.3
+      '@yarnpkg/libzip': 2.3.0
       chalk: 4.1.2
-      esbuild: 0.18.17
-      esbuild-register: 3.5.0(esbuild@0.18.17)
+      cross-spawn: 7.0.3
+      esbuild: 0.18.20
+      esbuild-register: 3.5.0(esbuild@0.18.20)
+      execa: 5.1.1
       file-system-cache: 2.3.0
       find-cache-dir: 3.3.2
       find-up: 5.0.0
@@ -6842,40 +6534,46 @@ packages:
       pkg-dir: 5.0.0
       pretty-hrtime: 1.0.3
       resolve-from: 5.0.0
+      semver: 7.5.4
+      tempy: 1.0.1
+      tiny-invariant: 1.3.1
       ts-dedent: 2.2.0
+      util: 0.12.5
     transitivePeerDependencies:
       - encoding
       - supports-color
     dev: true
 
-  /@storybook/core-events@7.6.5:
-    resolution: {integrity: sha512-zk2q/qicYXAzHA4oV3GDbIql+Kd4TOHUgDE8e4jPCOPp856z2ScqEKUAbiJizs6eEJOH4nW9Db1kuzgrBVEykQ==}
+  /@storybook/core-events@8.0.0-beta.6:
+    resolution: {integrity: sha512-ZyEVkOJ5gGGTfHjyasyeZgNGoeVJwVkLFRpV6cUl8hzOT29R5iDsf5PbJdrpF1x2pm1oLumeRckYQ7sYhr+R/w==}
     dependencies:
       ts-dedent: 2.2.0
     dev: true
 
-  /@storybook/core-server@7.6.5:
-    resolution: {integrity: sha512-BfKzK/ObTjUcPvE5/r1pogCifM/4nLRhOUYJl7XekwHkOQwn19e6H3/ku1W3jDoYXBu642Dc9X7l/ERjKTqxFg==}
+  /@storybook/core-server@8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-0ciJTWZs+mCnQOUzB3WuSfkhwXKpO033M5iYK92PKu9A6KSrwdc/WCwIJHeBNnIpmxC0GEh9j6/CgIsWehwJvg==}
     dependencies:
       '@aw-web-design/x-default-browser': 1.4.126
+      '@babel/core': 7.24.0
       '@discoveryjs/json-ext': 0.5.7
-      '@storybook/builder-manager': 7.6.5
-      '@storybook/channels': 7.6.5
-      '@storybook/core-common': 7.6.5
-      '@storybook/core-events': 7.6.5
+      '@storybook/builder-manager': 8.0.0-beta.6
+      '@storybook/channels': 8.0.0-beta.6
+      '@storybook/core-common': 8.0.0-beta.6
+      '@storybook/core-events': 8.0.0-beta.6
       '@storybook/csf': 0.1.2
-      '@storybook/csf-tools': 7.6.5
-      '@storybook/docs-mdx': 0.1.0
+      '@storybook/csf-tools': 8.0.0-beta.6
+      '@storybook/docs-mdx': 3.0.0
       '@storybook/global': 5.0.0
-      '@storybook/manager': 7.6.5
-      '@storybook/node-logger': 7.6.5
-      '@storybook/preview-api': 7.6.5
-      '@storybook/telemetry': 7.6.5
-      '@storybook/types': 7.6.5
+      '@storybook/manager': 8.0.0-beta.6
+      '@storybook/manager-api': 8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/node-logger': 8.0.0-beta.6
+      '@storybook/preview-api': 8.0.0-beta.6
+      '@storybook/telemetry': 8.0.0-beta.6
+      '@storybook/types': 8.0.0-beta.6
       '@types/detect-port': 1.3.2
       '@types/node': 18.17.15
       '@types/pretty-hrtime': 1.0.1
-      '@types/semver': 7.5.6
+      '@types/semver': 7.5.8
       better-opn: 3.0.2
       chalk: 4.1.2
       cli-table3: 0.6.3
@@ -6884,7 +6582,7 @@ packages:
       express: 4.18.2
       fs-extra: 11.1.1
       globby: 11.1.0
-      ip: 2.0.0
+      ip: 2.0.1
       lodash: 4.17.21
       open: 8.4.2
       pretty-hrtime: 1.0.3
@@ -6897,34 +6595,36 @@ packages:
       util: 0.12.5
       util-deprecate: 1.0.2
       watchpack: 2.4.0
-      ws: 8.15.1(bufferutil@4.0.7)(utf-8-validate@6.0.3)
+      ws: 8.16.0(bufferutil@4.0.7)(utf-8-validate@6.0.3)
     transitivePeerDependencies:
       - bufferutil
       - encoding
+      - react
+      - react-dom
       - supports-color
       - utf-8-validate
     dev: true
 
-  /@storybook/csf-plugin@7.6.5:
-    resolution: {integrity: sha512-iQ8Y/Qq1IUhHRddjDVicWJA2sM7OZA1FR97OvWUT2240WjCuQSCfy32JD8TQlYjqXgEolJeLPv3zW4qH5om4LQ==}
+  /@storybook/csf-plugin@8.0.0-beta.6:
+    resolution: {integrity: sha512-cYI/4OndODf0utV0DxJs8AOKbmjCG+pEgxQGcmPtGnkSmEuieUwpQpN7v+fEIN7IPUQLYvs0wspR0njZQAIzyA==}
     dependencies:
-      '@storybook/csf-tools': 7.6.5
+      '@storybook/csf-tools': 8.0.0-beta.6
       unplugin: 1.5.1
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@storybook/csf-tools@7.6.5:
-    resolution: {integrity: sha512-1iaCh7nt+WE7Q5UwRhLLc5flMNoAV/vBr0tvDSCKiHaO+D3dZzlZOe/U+S6wegdyN2QNcvT2xs179CcrX6Qp6w==}
+  /@storybook/csf-tools@8.0.0-beta.6:
+    resolution: {integrity: sha512-wwzbE6f8ykrvIeZlXYTba0IA8D5GPSyZ4L0+PqRAYHm3ozu0DXqtm4USDHKrjYAzuD+W+fG/6qIOQmsWYbNmpA==}
     dependencies:
-      '@babel/generator': 7.23.4
-      '@babel/parser': 7.23.4
-      '@babel/traverse': 7.23.4
-      '@babel/types': 7.23.4
+      '@babel/generator': 7.23.6
+      '@babel/parser': 7.24.0
+      '@babel/traverse': 7.24.0
+      '@babel/types': 7.24.0
       '@storybook/csf': 0.1.2
-      '@storybook/types': 7.6.5
+      '@storybook/types': 8.0.0-beta.6
       fs-extra: 11.1.1
-      recast: 0.23.1
+      recast: 0.23.4
       ts-dedent: 2.2.0
     transitivePeerDependencies:
       - supports-color
@@ -6936,16 +6636,16 @@ packages:
       type-fest: 2.19.0
     dev: true
 
-  /@storybook/docs-mdx@0.1.0:
-    resolution: {integrity: sha512-JDaBR9lwVY4eSH5W8EGHrhODjygPd6QImRbwjAuJNEnY0Vw4ie3bPkeGfnacB3OBW6u/agqPv2aRlR46JcAQLg==}
+  /@storybook/docs-mdx@3.0.0:
+    resolution: {integrity: sha512-NmiGXl2HU33zpwTv1XORe9XG9H+dRUC1Jl11u92L4xr062pZtrShLmD4VKIsOQujxhhOrbxpwhNOt+6TdhyIdQ==}
     dev: true
 
-  /@storybook/docs-tools@7.6.5:
-    resolution: {integrity: sha512-UyHkHu5Af6jMpYsR4lZ69D32GQGeA0pLAn7jaBbQndgAjBdK1ykZcifiUC7Wz1hG7+YpuYspEGuDEddOh+X8FQ==}
+  /@storybook/docs-tools@8.0.0-beta.6:
+    resolution: {integrity: sha512-fSKXEu0vegzqC2HT1RaOKqi0+W/vIn+qa5D+dZHkj2BnceYxWAGYsX9ZZPHW6DUvvwp0WZp1vz57nPUhsLvcQg==}
     dependencies:
-      '@storybook/core-common': 7.6.5
-      '@storybook/preview-api': 7.6.5
-      '@storybook/types': 7.6.5
+      '@storybook/core-common': 8.0.0-beta.6
+      '@storybook/preview-api': 8.0.0-beta.6
+      '@storybook/types': 8.0.0-beta.6
       '@types/doctrine': 0.0.3
       assert: 2.1.0
       doctrine: 3.0.0
@@ -6955,44 +6655,47 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/expect@28.1.3-5:
-    resolution: {integrity: sha512-lS1oJnY1qTAxnH87C765NdfvGhksA6hBcbUVI5CHiSbNsEtr456wtg/z+dT9XlPriq1D5t2SgfNL9dBAoIGyIA==}
-    dependencies:
-      '@types/jest': 28.1.3
-    dev: true
-
   /@storybook/global@5.0.0:
     resolution: {integrity: sha512-FcOqPAXACP0I3oJ/ws6/rrPT9WGhu915Cg8D02a9YxLo0DE9zI+a9A5gRGvmQ09fiWPukqI8ZAEoQEdWUKMQdQ==}
     dev: true
 
-  /@storybook/jest@0.2.3(vitest@0.34.6):
-    resolution: {integrity: sha512-ov5izrmbAFObzKeh9AOC5MlmFxAcf0o5i6YFGae9sDx6DGh6alXsRM+chIbucVkUwVHVlSzdfbLDEFGY/ShaYw==}
+  /@storybook/icons@1.2.5(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-m3jnuE+zmkZy6K+cdUDzAoUuCJyl0fWCAXPCji7VZCH1TzFohyvnPqhc9JMkQpanej2TOW3wWXaplPzHghcBSg==}
+    engines: {node: '>=14.0.0'}
+    peerDependencies:
+      react: ^16.8.0 || ^17.0.0 || ^18.0.0
+      react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
     dependencies:
-      '@storybook/expect': 28.1.3-5
-      '@testing-library/jest-dom': 6.1.2(@types/jest@28.1.3)(vitest@0.34.6)
-      '@types/jest': 28.1.3
-      jest-mock: 27.5.1
-    transitivePeerDependencies:
-      - '@jest/globals'
-      - jest
-      - vitest
+      react: 18.2.0
+      react-dom: 18.2.0(react@18.2.0)
     dev: true
 
-  /@storybook/manager-api@7.6.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-tE3OShOcs6A3XtI3NJd6hYQOZLaP++Fn0dCtowBwYh/vS1EN/AyroVmL97tsxn1DZTyoRt0GidwbB6dvLMBOwA==}
+  /@storybook/instrumenter@8.0.0-beta.6:
+    resolution: {integrity: sha512-xJ3qkvj8dce7nJEa6hmp4PDDZJMBuP5UlSKPidiMAfEsB0MeUbDulTFNDb0t1DwcH9ywinDl8TilSzG4+r1kDA==}
     dependencies:
-      '@storybook/channels': 7.6.5
-      '@storybook/client-logger': 7.6.5
-      '@storybook/core-events': 7.6.5
+      '@storybook/channels': 8.0.0-beta.6
+      '@storybook/client-logger': 8.0.0-beta.6
+      '@storybook/core-events': 8.0.0-beta.6
+      '@storybook/global': 5.0.0
+      '@storybook/preview-api': 8.0.0-beta.6
+      '@vitest/utils': 0.34.6
+      util: 0.12.5
+    dev: true
+
+  /@storybook/manager-api@8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-kOGOT/yGFgKzld9IL1HREouFwZ0LpFuXZZOHBih5ydK8XT+bkWF6e3SiqthB3qtqpd0eVLAbNiPfY9R8t3qfWg==}
+    dependencies:
+      '@storybook/channels': 8.0.0-beta.6
+      '@storybook/client-logger': 8.0.0-beta.6
+      '@storybook/core-events': 8.0.0-beta.6
       '@storybook/csf': 0.1.2
       '@storybook/global': 5.0.0
-      '@storybook/router': 7.6.5
-      '@storybook/theming': 7.6.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.6.5
+      '@storybook/router': 8.0.0-beta.6
+      '@storybook/theming': 8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 8.0.0-beta.6
       dequal: 2.0.3
       lodash: 4.17.21
       memoizerific: 1.11.3
-      semver: 7.5.4
       store2: 2.14.2
       telejson: 7.2.0
       ts-dedent: 2.2.0
@@ -7001,47 +6704,39 @@ packages:
       - react-dom
     dev: true
 
-  /@storybook/manager@7.6.5:
-    resolution: {integrity: sha512-y1KLH0O1PGPyMxGMvOhppzFSO7r4ibjTve5iqsI0JZwxUjNuBKRLYbrhXdAyC2iacvxYNrHgevae1k9XdD+FQw==}
+  /@storybook/manager@8.0.0-beta.6:
+    resolution: {integrity: sha512-FeQ2/CIasSOgcTMEE3QYMFa92KeMnfEMyUVO4hHEmPh3SqPsz6OOv8p0bQvN0SWWBgZarbhFR0dKC3W10yYrXg==}
     dev: true
 
-  /@storybook/mdx2-csf@1.0.0:
-    resolution: {integrity: sha512-dBAnEL4HfxxJmv7LdEYUoZlQbWj9APZNIbOaq0tgF8XkxiIbzqvgB0jhL/9UOrysSDbQWBiCRTu2wOVxedGfmw==}
+  /@storybook/node-logger@8.0.0-beta.6:
+    resolution: {integrity: sha512-nmBlmZ8wzJiU1/ubhUmFeWQaJPBv6l6s0Cndk04omPSjROa+O1whoPhDTVGvWC28zm17tmAYVcQRujkdoi+YBA==}
     dev: true
 
-  /@storybook/node-logger@7.6.5:
-    resolution: {integrity: sha512-xKw6IH1wLkIssekdBv3bd13xYKUF1t8EwqDR8BYcN8AVjZlqJMTifssqG4bYV+G/B7J3tz4ugJ5nmtWg6RQ0Qw==}
-    dev: true
-
-  /@storybook/postinstall@7.6.5:
-    resolution: {integrity: sha512-12WxfpqGKsk7GQ3KWiZSbamsYK8vtRmhOTkavZ9IQkcJ/zuVfmqK80/Mds+njJMudUPzuREuSFGWACczo17EDA==}
-    dev: true
-
-  /@storybook/preview-api@7.6.5:
-    resolution: {integrity: sha512-9XzuDXXgNuA6dDZ3DXsUwEG6ElxeTbzLuYuzcjtS1FusSICZ2iYmxfS0GfSud9MjPPYOJYoSOvMdIHjorjgByA==}
+  /@storybook/preview-api@8.0.0-beta.6:
+    resolution: {integrity: sha512-V07MF1ArjBGi2EPSjrEW8pjCoW/TIwxNDilcO9cD12LHrDQGXuo/iKyR47TGUYmcJ/u1I2Eu9cjyVj9DVyppag==}
     dependencies:
-      '@storybook/channels': 7.6.5
-      '@storybook/client-logger': 7.6.5
-      '@storybook/core-events': 7.6.5
+      '@storybook/channels': 8.0.0-beta.6
+      '@storybook/client-logger': 8.0.0-beta.6
+      '@storybook/core-events': 8.0.0-beta.6
       '@storybook/csf': 0.1.2
       '@storybook/global': 5.0.0
-      '@storybook/types': 7.6.5
+      '@storybook/types': 8.0.0-beta.6
       '@types/qs': 6.9.7
       dequal: 2.0.3
       lodash: 4.17.21
       memoizerific: 1.11.3
       qs: 6.11.1
-      synchronous-promise: 2.0.17
+      tiny-invariant: 1.3.1
       ts-dedent: 2.2.0
       util-deprecate: 1.0.2
     dev: true
 
-  /@storybook/preview@7.6.5:
-    resolution: {integrity: sha512-zmLa7C7yFGTYhgGZXoecdww9rx0Z5HpNi/GDBRWoNSK+FEdE8Jj2jF5NJ2ncldtYIyegz9ku29JFMKbhMj9K5Q==}
+  /@storybook/preview@8.0.0-beta.6:
+    resolution: {integrity: sha512-tp3Wyvjsbf5r5RhbCQSafArQWJAir1bmIJWGG2S4o2E3YT6TlHFpR078tNJtgXqsPyG0yhF9vhRRkDczrPX/Gw==}
     dev: true
 
-  /@storybook/react-dom-shim@7.6.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-Qp3N3zENdvx20ikHmz5yI03z+mAWF8bUAwUofqXarVtZUkBNtvfTfUwgAezOAF0eClClH+ktIziIKd976tLSPw==}
+  /@storybook/react-dom-shim@8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-l14oDKAW2jyrXynHKP6SoNGal78gXcWCgj0zLwSDWpKgAFWC7SuIneuxLv6weU1D4+f9Y9FBrz+K3CCaMgMtOA==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
@@ -7050,24 +6745,23 @@ packages:
       react-dom: 18.2.0(react@18.2.0)
     dev: true
 
-  /@storybook/react-vite@7.6.5(react-dom@18.2.0)(react@18.2.0)(rollup@4.9.1)(typescript@5.3.3)(vite@5.0.10):
-    resolution: {integrity: sha512-fIoSBbou3rQdOo6qX/nD5givb3qIOSwXeZWjAqRB6560cqmeSQFlRGtKUJ0nzQYADwJ0/iNHz3nOvJOOSnPepA==}
-    engines: {node: '>=16'}
+  /@storybook/react-vite@8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0)(rollup@4.12.0)(typescript@5.3.3)(vite@5.1.4):
+    resolution: {integrity: sha512-Tvz25pTXmhncDxprjIYsnXc68Lfa9idDybpRTRRbtvjsJyVpZogUdgz2/kddGNTuX3mqz6vmTMWiLiIVh+ytQA==}
+    engines: {node: '>=18.0.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
-      vite: ^3.0.0 || ^4.0.0 || ^5.0.0
+      vite: ^4.0.0 || ^5.0.0
     dependencies:
-      '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@5.3.3)(vite@5.0.10)
-      '@rollup/pluginutils': 5.1.0(rollup@4.9.1)
-      '@storybook/builder-vite': 7.6.5(typescript@5.3.3)(vite@5.0.10)
-      '@storybook/react': 7.6.5(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3)
-      '@vitejs/plugin-react': 3.1.0(vite@5.0.10)
-      magic-string: 0.30.5
+      '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@5.3.3)(vite@5.1.4)
+      '@rollup/pluginutils': 5.1.0(rollup@4.12.0)
+      '@storybook/builder-vite': 8.0.0-beta.6(typescript@5.3.3)(vite@5.1.4)
+      '@storybook/react': 8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3)
+      magic-string: 0.30.7
       react: 18.2.0
       react-docgen: 7.0.1
       react-dom: 18.2.0(react@18.2.0)
-      vite: 5.0.10(@types/node@20.10.5)(sass@1.69.5)(terser@5.26.0)
+      vite: 5.1.4(@types/node@20.11.22)(sass@1.71.1)(terser@5.28.1)
     transitivePeerDependencies:
       - '@preact/preset-vite'
       - encoding
@@ -7077,24 +6771,23 @@ packages:
       - vite-plugin-glimmerx
     dev: true
 
-  /@storybook/react@7.6.5(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3):
-    resolution: {integrity: sha512-z0l5T+gL//VekMXnHi+lW5qr7OQ8X7WoeIRMk38e62ppSpGUZRfoxRmmhU/9YcIFAlCgMaoLSYmhOceKGRZuVw==}
-    engines: {node: '>=16.0.0'}
+  /@storybook/react@8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0)(typescript@5.3.3):
+    resolution: {integrity: sha512-69B0c08HDYHEgZRRnkB+3z4dY/HO/GMSiRzRCNpzI0SBQzk1YwDzG9MOtkNgGqzdLK3e3DveSXb5Uyy1cB0ZiQ==}
+    engines: {node: '>=18.0.0'}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
-      typescript: '*'
+      typescript: '>= 4.2.x'
     peerDependenciesMeta:
       typescript:
         optional: true
     dependencies:
-      '@storybook/client-logger': 7.6.5
-      '@storybook/core-client': 7.6.5
-      '@storybook/docs-tools': 7.6.5
+      '@storybook/client-logger': 8.0.0-beta.6
+      '@storybook/docs-tools': 8.0.0-beta.6
       '@storybook/global': 5.0.0
-      '@storybook/preview-api': 7.6.5
-      '@storybook/react-dom-shim': 7.6.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.6.5
+      '@storybook/preview-api': 8.0.0-beta.6
+      '@storybook/react-dom-shim': 8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 8.0.0-beta.6
       '@types/escodegen': 0.0.6
       '@types/estree': 0.0.51
       '@types/node': 18.17.15
@@ -7108,6 +6801,7 @@ packages:
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
       react-element-to-jsx-string: 15.0.0(react-dom@18.2.0)(react@18.2.0)
+      semver: 7.5.4
       ts-dedent: 2.2.0
       type-fest: 2.19.0
       typescript: 5.3.3
@@ -7117,30 +6811,30 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/router@7.6.5:
-    resolution: {integrity: sha512-QiTC86gRuoepzzmS6HNJZTwfz/n27NcqtaVEIxJi1Yvsx2/kLa9NkRhylNkfTuZ1gEry9stAlKWanMsB2aKyjQ==}
+  /@storybook/router@8.0.0-beta.6:
+    resolution: {integrity: sha512-JjLyDaVzCH3kmNsOkuJ8/U2bPIoReZZ/QsgHJdfvm22T2wKNjQ+lfNrQptBgNybfi1o/Tmn9VbCdRqurSlh9Dw==}
     dependencies:
-      '@storybook/client-logger': 7.6.5
+      '@storybook/client-logger': 8.0.0-beta.6
       memoizerific: 1.11.3
       qs: 6.11.1
     dev: true
 
-  /@storybook/source-loader@7.6.5:
-    resolution: {integrity: sha512-3GpXJY9GUOOl3Uq/xcsJ12XWLBNZJwUWzwkBm4Eev1xl5eg/ygeyJflwM5egsA1NfkV77hNxtjQcbfw4cBtqdg==}
+  /@storybook/source-loader@8.0.0-beta.6:
+    resolution: {integrity: sha512-cYtjnuJZgm8MS9SsNsbuhuFz2d7j6BKRLZByBUqELrK+ftup0qqOWM+78w26qn3nPgA8myZXWxGa+V/Pjxio5w==}
     dependencies:
       '@storybook/csf': 0.1.2
-      '@storybook/types': 7.6.5
+      '@storybook/types': 8.0.0-beta.6
       estraverse: 5.3.0
       lodash: 4.17.21
-      prettier: 2.8.8
+      prettier: 3.2.5
     dev: true
 
-  /@storybook/telemetry@7.6.5:
-    resolution: {integrity: sha512-FiLRh9k9LoGphqgBqPYySWdGqplihiZyDwqdo+Qs19RcQ/eiKg0W7fdA09nStcdcsHmDl/1cMfRhz9KUiMtwOw==}
+  /@storybook/telemetry@8.0.0-beta.6:
+    resolution: {integrity: sha512-3CU5Sdj8eVm0tb35GriMkDrxJyTpdGcfU/hgUnsuw+I4eHYdZsc4Boh9uXWTVNsaBaoqbD/MP1aqbfxkElqPxQ==}
     dependencies:
-      '@storybook/client-logger': 7.6.5
-      '@storybook/core-common': 7.6.5
-      '@storybook/csf-tools': 7.6.5
+      '@storybook/client-logger': 8.0.0-beta.6
+      '@storybook/core-common': 8.0.0-beta.6
+      '@storybook/csf-tools': 8.0.0-beta.6
       chalk: 4.1.2
       detect-package-manager: 2.0.1
       fetch-retry: 5.0.4
@@ -7151,86 +6845,104 @@ packages:
       - supports-color
     dev: true
 
-  /@storybook/testing-library@0.2.2:
-    resolution: {integrity: sha512-L8sXFJUHmrlyU2BsWWZGuAjv39Jl1uAqUHdxmN42JY15M4+XCMjGlArdCCjDe1wpTSW6USYISA9axjZojgtvnw==}
+  /@storybook/test@8.0.0-beta.6(vitest@0.34.6):
+    resolution: {integrity: sha512-GcV76EX3U77G+k8+0V+jAa/sJQZEuNb/4W+g/RaqGLRCEG73UADzkgRuFm60UQUBGtltvvRZU9sIPVbFTJFxuA==}
     dependencies:
-      '@testing-library/dom': 9.2.0
-      '@testing-library/user-event': 14.4.3(@testing-library/dom@9.2.0)
-      ts-dedent: 2.2.0
+      '@storybook/client-logger': 8.0.0-beta.6
+      '@storybook/core-events': 8.0.0-beta.6
+      '@storybook/instrumenter': 8.0.0-beta.6
+      '@storybook/preview-api': 8.0.0-beta.6
+      '@testing-library/dom': 9.3.3
+      '@testing-library/jest-dom': 6.4.2(vitest@0.34.6)
+      '@testing-library/user-event': 14.5.2(@testing-library/dom@9.3.3)
+      '@vitest/expect': 1.1.3
+      '@vitest/spy': 1.3.0
+      chai: 4.3.10
+      util: 0.12.5
+    transitivePeerDependencies:
+      - '@jest/globals'
+      - '@types/bun'
+      - '@types/jest'
+      - jest
+      - vitest
     dev: true
 
-  /@storybook/theming@7.6.5(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-RpcWT0YEgiobO41McVPDfQQHHFnjyr1sJnNTPJIvOUgSfURdgSj17mQVxtD5xcXcPWUdle5UhIOrCixHbL/NNw==}
+  /@storybook/theming@8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-WXvDbV257fKbHM5jHd7hOHefRSBnyZec08NGpcVOG6muJjLu8nPjazcYgISqFc97MkFmxvEDPFfX8CvBEeefzA==}
     peerDependencies:
       react: ^16.8.0 || ^17.0.0 || ^18.0.0
       react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0
+    peerDependenciesMeta:
+      react:
+        optional: true
+      react-dom:
+        optional: true
     dependencies:
-      '@emotion/use-insertion-effect-with-fallbacks': 1.0.0(react@18.2.0)
-      '@storybook/client-logger': 7.6.5
+      '@emotion/use-insertion-effect-with-fallbacks': 1.0.1(react@18.2.0)
+      '@storybook/client-logger': 8.0.0-beta.6
       '@storybook/global': 5.0.0
       memoizerific: 1.11.3
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
     dev: true
 
-  /@storybook/types@7.6.5:
-    resolution: {integrity: sha512-Q757v+fYZZSaEpks/zDL5YgXRozxkgKakXFc+BoQHK5q5sVhJ+0jvpLJiAQAniIIaMIkqY/G24Kd6Uo6UdKBCg==}
+  /@storybook/types@8.0.0-beta.6:
+    resolution: {integrity: sha512-w3jq8mBcxir4P0RK3gQePeUJ0rXbnUbCKg91YBOKeitmU0+4jSr4e1EwTWOYgsyz7KtikzSNr8JXtMQn2TJD5A==}
     dependencies:
-      '@storybook/channels': 7.6.5
-      '@types/babel__core': 7.20.5
+      '@storybook/channels': 8.0.0-beta.6
       '@types/express': 4.17.17
       file-system-cache: 2.3.0
     dev: true
 
-  /@storybook/vue3-vite@7.6.5(@vue/compiler-core@3.3.12)(typescript@5.3.3)(vite@5.0.10)(vue@3.3.12):
-    resolution: {integrity: sha512-7wUCq2Lrjlekftd5ha3hG0GSGbbzuc370cKkBqSmwFuOfI38z5+VeYt7nDtAlncxcpVSH7DejTGRuKTlC7NyYg==}
-    engines: {node: ^14.18 || >=16}
+  /@storybook/vue3-vite@8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0)(vite@5.1.4)(vue@3.4.21):
+    resolution: {integrity: sha512-Pf9W7hcHjx1FE3JmhY1iSxGq9k/Tp5n/obOCd4FJGUdIttPYFclG9km49DrCJtNfhK7M6+d2QTZ6Uds4ORWZPg==}
+    engines: {node: '>=18.0.0'}
     peerDependencies:
-      vite: ^3.0.0 || ^4.0.0 || ^5.0.0
+      vite: ^4.0.0 || ^5.0.0
     dependencies:
-      '@storybook/builder-vite': 7.6.5(typescript@5.3.3)(vite@5.0.10)
-      '@storybook/core-server': 7.6.5
-      '@storybook/vue3': 7.6.5(@vue/compiler-core@3.3.12)(vue@3.3.12)
-      '@vitejs/plugin-vue': 4.5.2(vite@5.0.10)(vue@3.3.12)
-      magic-string: 0.30.5
-      vite: 5.0.10(@types/node@20.10.5)(sass@1.69.5)(terser@5.26.0)
-      vue-docgen-api: 4.64.1(vue@3.3.12)
+      '@storybook/builder-vite': 8.0.0-beta.6(typescript@5.3.3)(vite@5.1.4)
+      '@storybook/core-server': 8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/vue3': 8.0.0-beta.6(vue@3.4.21)
+      find-package-json: 1.2.0
+      magic-string: 0.30.7
+      typescript: 5.3.3
+      vite: 5.1.4(@types/node@20.11.22)(sass@1.71.1)(terser@5.28.1)
+      vue-component-meta: 1.8.27(typescript@5.3.3)
+      vue-docgen-api: 4.75.1(vue@3.4.21)
     transitivePeerDependencies:
       - '@preact/preset-vite'
-      - '@vue/compiler-core'
       - bufferutil
       - encoding
+      - react
+      - react-dom
       - supports-color
-      - typescript
       - utf-8-validate
       - vite-plugin-glimmerx
       - vue
     dev: true
 
-  /@storybook/vue3@7.6.5(@vue/compiler-core@3.3.12)(vue@3.3.12):
-    resolution: {integrity: sha512-tv/9rVc3XXDOJu5hfZtKhrhM8x4GTLKon62Rmaxlq06weqkGlfBi/V/g1EZ7OE71Pi+woKS/TX7p9qbRrvgahg==}
-    engines: {node: '>=16.0.0'}
+  /@storybook/vue3@8.0.0-beta.6(vue@3.4.21):
+    resolution: {integrity: sha512-027KDM1f6y0XzMK1yE5W4JKY/VsbGpr1kj0mvEKxaPUYgBJV9wTHADWgmluiJS/e/MWrCCZql5mE+D9lVJUjoA==}
+    engines: {node: '>=18.0.0'}
     peerDependencies:
-      '@vue/compiler-core': ^3.0.0
       vue: ^3.0.0
     dependencies:
-      '@storybook/core-client': 7.6.5
-      '@storybook/docs-tools': 7.6.5
+      '@storybook/docs-tools': 8.0.0-beta.6
       '@storybook/global': 5.0.0
-      '@storybook/preview-api': 7.6.5
-      '@storybook/types': 7.6.5
-      '@vue/compiler-core': 3.3.12
+      '@storybook/preview-api': 8.0.0-beta.6
+      '@storybook/types': 8.0.0-beta.6
+      '@vue/compiler-core': 3.4.18
       lodash: 4.17.21
       ts-dedent: 2.2.0
       type-fest: 2.19.0
-      vue: 3.3.12(typescript@5.3.3)
+      vue: 3.4.21(typescript@5.3.3)
       vue-component-type-helpers: 1.8.27
     transitivePeerDependencies:
       - encoding
       - supports-color
     dev: true
 
-  /@swc/cli@0.1.63(@swc/core@1.3.100)(chokidar@3.5.3):
+  /@swc/cli@0.1.63(@swc/core@1.3.105):
     resolution: {integrity: sha512-EM9oxxHzmmsprYRbGqsS2M4M/Gr5Gkcl0ROYYIdlUyTkhOiX822EQiRCpPCwdutdnzH2GyaTN7wc6i0Y+CKd3A==}
     engines: {node: '>= 12.13'}
     hasBin: true
@@ -7242,7 +6954,27 @@ packages:
         optional: true
     dependencies:
       '@mole-inc/bin-wrapper': 8.0.1
-      '@swc/core': 1.3.100
+      '@swc/core': 1.3.105
+      commander: 7.2.0
+      fast-glob: 3.3.2
+      semver: 7.5.4
+      slash: 3.0.0
+      source-map: 0.7.4
+    dev: false
+
+  /@swc/cli@0.1.63(@swc/core@1.3.107)(chokidar@3.5.3):
+    resolution: {integrity: sha512-EM9oxxHzmmsprYRbGqsS2M4M/Gr5Gkcl0ROYYIdlUyTkhOiX822EQiRCpPCwdutdnzH2GyaTN7wc6i0Y+CKd3A==}
+    engines: {node: '>= 12.13'}
+    hasBin: true
+    peerDependencies:
+      '@swc/core': ^1.2.66
+      chokidar: 3.5.3
+    peerDependenciesMeta:
+      chokidar:
+        optional: true
+    dependencies:
+      '@mole-inc/bin-wrapper': 8.0.1
+      '@swc/core': 1.3.107
       chokidar: 3.5.3
       commander: 7.2.0
       fast-glob: 3.3.2
@@ -7262,8 +6994,16 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-darwin-arm64@1.3.100:
-    resolution: {integrity: sha512-XVWFsKe6ei+SsDbwmsuRkYck1SXRpO60Hioa4hoLwR8fxbA9eVp6enZtMxzVVMBi8ej5seZ4HZQeAWepbukiBw==}
+  /@swc/core-darwin-arm64@1.3.105:
+    resolution: {integrity: sha512-buWeweLVDXXmcnfIemH4PGnpjwsDTUGitnPchdftb0u1FU8zSSP/lw/pUCBDG/XvWAp7c/aFxgN4CyG0j7eayA==}
+    engines: {node: '>=10'}
+    cpu: [arm64]
+    os: [darwin]
+    requiresBuild: true
+    optional: true
+
+  /@swc/core-darwin-arm64@1.3.107:
+    resolution: {integrity: sha512-47tD/5vSXWxPd0j/ZllyQUg4bqalbQTsmqSw0J4dDdS82MWqCAwUErUrAZPRjBkjNQ6Kmrf5rpCWaGTtPw+ngw==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [darwin]
@@ -7279,8 +7019,16 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-darwin-x64@1.3.100:
-    resolution: {integrity: sha512-KF/MXrnH1nakm1wbt4XV8FS7kvqD9TGmVxeJ0U4bbvxXMvzeYUurzg3AJUTXYmXDhH/VXOYJE5N5RkwZZPs5iA==}
+  /@swc/core-darwin-x64@1.3.105:
+    resolution: {integrity: sha512-hFmXPApqjA/8sy/9NpljHVaKi1OvL9QkJ2MbbTCCbJERuHMpMUeMBUWipHRfepGHFhU+9B9zkEup/qJaJR4XIg==}
+    engines: {node: '>=10'}
+    cpu: [x64]
+    os: [darwin]
+    requiresBuild: true
+    optional: true
+
+  /@swc/core-darwin-x64@1.3.107:
+    resolution: {integrity: sha512-hwiLJ2ulNkBGAh1m1eTfeY1417OAYbRGcb/iGsJ+LuVLvKAhU/itzsl535CvcwAlt2LayeCFfcI8gdeOLeZa9A==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [darwin]
@@ -7307,6 +7055,22 @@ packages:
     dev: false
     optional: true
 
+  /@swc/core-linux-arm-gnueabihf@1.3.105:
+    resolution: {integrity: sha512-mwXyMC41oMKkKrPpL8uJpOxw7fyfQoVtIw3Y5p0Blabk+espNYqix0E8VymHdRKuLmM//z5wVmMsuHdGBHvZeg==}
+    engines: {node: '>=10'}
+    cpu: [arm]
+    os: [linux]
+    requiresBuild: true
+    optional: true
+
+  /@swc/core-linux-arm-gnueabihf@1.3.107:
+    resolution: {integrity: sha512-I2wzcC0KXqh0OwymCmYwNRgZ9nxX7DWnOOStJXV3pS0uB83TXAkmqd7wvMBuIl9qu4Hfomi9aDM7IlEEn9tumQ==}
+    engines: {node: '>=10'}
+    cpu: [arm]
+    os: [linux]
+    requiresBuild: true
+    optional: true
+
   /@swc/core-linux-arm-gnueabihf@1.3.56:
     resolution: {integrity: sha512-LWwPo6NnJkH01+ukqvkoNIOpMdw+Zundm4vBeicwyVrkP+mC3kwVfi03TUFpQUz3kRKdw/QEnxGTj+MouCPbtw==}
     engines: {node: '>=10'}
@@ -7316,8 +7080,16 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-linux-arm64-gnu@1.3.100:
-    resolution: {integrity: sha512-p8hikNnAEJrw5vHCtKiFT4hdlQxk1V7vqPmvUDgL/qe2menQDK/i12tbz7/3BEQ4UqUPnvwpmVn2d19RdEMNxw==}
+  /@swc/core-linux-arm64-gnu@1.3.105:
+    resolution: {integrity: sha512-H7yEIVydnUtqBSUxwmO6vpIQn7j+Rr0DF6ZOORPyd/SFzQJK9cJRtmJQ3ZMzlJ1Bb+1gr3MvjgLEnmyCYEm2Hg==}
+    engines: {node: '>=10'}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    optional: true
+
+  /@swc/core-linux-arm64-gnu@1.3.107:
+    resolution: {integrity: sha512-HWgnn7JORYlOYnGsdunpSF8A+BCZKPLzLtEUA27/M/ZuANcMZabKL9Zurt7XQXq888uJFAt98Gy+59PU90aHKg==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [linux]
@@ -7333,8 +7105,16 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-linux-arm64-musl@1.3.100:
-    resolution: {integrity: sha512-BWx/0EeY89WC4q3AaIaBSGfQxkYxIlS3mX19dwy2FWJs/O+fMvF9oLk/CyJPOZzbp+1DjGeeoGFuDYpiNO91JA==}
+  /@swc/core-linux-arm64-musl@1.3.105:
+    resolution: {integrity: sha512-Jg7RTFT3pGFdGt5elPV6oDkinRy7q9cXpenjXnJnM2uvx3jOwnsAhexPyCDHom8SHL0j+9kaLLC66T3Gz1E4UA==}
+    engines: {node: '>=10'}
+    cpu: [arm64]
+    os: [linux]
+    requiresBuild: true
+    optional: true
+
+  /@swc/core-linux-arm64-musl@1.3.107:
+    resolution: {integrity: sha512-vfPF74cWfAm8hyhS8yvYI94ucMHIo8xIYU+oFOW9uvDlGQRgnUf/6DEVbLyt/3yfX5723Ln57U8uiMALbX5Pyw==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [linux]
@@ -7350,8 +7130,16 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-linux-x64-gnu@1.3.100:
-    resolution: {integrity: sha512-XUdGu3dxAkjsahLYnm8WijPfKebo+jHgHphDxaW0ovI6sTdmEGFDew7QzKZRlbYL2jRkUuuKuDGvD6lO5frmhA==}
+  /@swc/core-linux-x64-gnu@1.3.105:
+    resolution: {integrity: sha512-DJghplpyusAmp1X5pW/y93MmS/u83Sx5GrpJxI6KLPa82+NItTgMcl8KBQmW5GYAJpVKZyaIvBanS5TdR8aN2w==}
+    engines: {node: '>=10'}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    optional: true
+
+  /@swc/core-linux-x64-gnu@1.3.107:
+    resolution: {integrity: sha512-uBVNhIg0ip8rH9OnOsCARUFZ3Mq3tbPHxtmWk9uAa5u8jQwGWeBx5+nTHpDOVd3YxKb6+5xDEI/edeeLpha/9g==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [linux]
@@ -7367,8 +7155,16 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-linux-x64-musl@1.3.100:
-    resolution: {integrity: sha512-PhoXKf+f0OaNW/GCuXjJ0/KfK9EJX7z2gko+7nVnEA0p3aaPtbP6cq1Ubbl6CMoPL+Ci3gZ7nYumDqXNc3CtLQ==}
+  /@swc/core-linux-x64-musl@1.3.105:
+    resolution: {integrity: sha512-wD5jL2dZH/5nPNssBo6jhOvkI0lmWnVR4vnOXWjuXgjq1S0AJpO5jdre/6pYLmf26hft3M42bteDnjR4AAZ38w==}
+    engines: {node: '>=10'}
+    cpu: [x64]
+    os: [linux]
+    requiresBuild: true
+    optional: true
+
+  /@swc/core-linux-x64-musl@1.3.107:
+    resolution: {integrity: sha512-mvACkUvzSIB12q1H5JtabWATbk3AG+pQgXEN95AmEX2ZA5gbP9+B+mijsg7Sd/3tboHr7ZHLz/q3SHTvdFJrEw==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [linux]
@@ -7384,8 +7180,16 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-win32-arm64-msvc@1.3.100:
-    resolution: {integrity: sha512-PwLADZN6F9cXn4Jw52FeP/MCLVHm8vwouZZSOoOScDtihjY495SSjdPnlosMaRSR4wJQssGwiD/4MbpgQPqbAw==}
+  /@swc/core-win32-arm64-msvc@1.3.105:
+    resolution: {integrity: sha512-UqJtwILUHRw2+3UTPnRkZrzM/bGdQtbR4UFdp79mZQYfryeOUVNg7aJj/bWUTkKtLiZ3o+FBNrM/x2X1mJX5bA==}
+    engines: {node: '>=10'}
+    cpu: [arm64]
+    os: [win32]
+    requiresBuild: true
+    optional: true
+
+  /@swc/core-win32-arm64-msvc@1.3.107:
+    resolution: {integrity: sha512-J3P14Ngy/1qtapzbguEH41kY109t6DFxfbK4Ntz9dOWNuVY3o9/RTB841ctnJk0ZHEG+BjfCJjsD2n8H5HcaOA==}
     engines: {node: '>=10'}
     cpu: [arm64]
     os: [win32]
@@ -7401,8 +7205,16 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-win32-ia32-msvc@1.3.100:
-    resolution: {integrity: sha512-0f6nicKSLlDKlyPRl2JEmkpBV4aeDfRQg6n8mPqgL7bliZIcDahG0ej+HxgNjZfS3e0yjDxsNRa6sAqWU2Z60A==}
+  /@swc/core-win32-ia32-msvc@1.3.105:
+    resolution: {integrity: sha512-Z95C6vZgBEJ1snidYyjVKnVWiy/ZpPiIFIXGWkDr4ZyBgL3eZX12M6LzZ+NApHKffrbO4enbFyFomueBQgS2oA==}
+    engines: {node: '>=10'}
+    cpu: [ia32]
+    os: [win32]
+    requiresBuild: true
+    optional: true
+
+  /@swc/core-win32-ia32-msvc@1.3.107:
+    resolution: {integrity: sha512-ZBUtgyjTHlz8TPJh7kfwwwFma+ktr6OccB1oXC8fMSopD0AxVnQasgun3l3099wIsAB9eEsJDQ/3lDkOLs1gBA==}
     engines: {node: '>=10'}
     cpu: [ia32]
     os: [win32]
@@ -7418,8 +7230,16 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core-win32-x64-msvc@1.3.100:
-    resolution: {integrity: sha512-b7J0rPoMkRTa3XyUGt8PwCaIBuYWsL2DqbirrQKRESzgCvif5iNpqaM6kjIjI/5y5q1Ycv564CB51YDpiS8EtQ==}
+  /@swc/core-win32-x64-msvc@1.3.105:
+    resolution: {integrity: sha512-3J8fkyDPFsS3mszuYUY4Wfk7/B2oio9qXUwF3DzOs2MK+XgdyMLIptIxL7gdfitXJBH8k39uVjrIw1JGJDjyFA==}
+    engines: {node: '>=10'}
+    cpu: [x64]
+    os: [win32]
+    requiresBuild: true
+    optional: true
+
+  /@swc/core-win32-x64-msvc@1.3.107:
+    resolution: {integrity: sha512-Eyzo2XRqWOxqhE1gk9h7LWmUf4Bp4Xn2Ttb0ayAXFp6YSTxQIThXcT9kipXZqcpxcmDwoq8iWbbf2P8XL743EA==}
     engines: {node: '>=10'}
     cpu: [x64]
     os: [win32]
@@ -7435,8 +7255,8 @@ packages:
     dev: false
     optional: true
 
-  /@swc/core@1.3.100:
-    resolution: {integrity: sha512-7dKgTyxJjlrMwFZYb1auj3Xq0D8ZBe+5oeIgfMlRU05doXZypYJe0LAk0yjj3WdbwYzpF+T1PLxwTWizI0pckw==}
+  /@swc/core@1.3.105:
+    resolution: {integrity: sha512-me2VZyr3OjqRpFrYQJJYy7x/zbFSl9nt+MAGnIcBtjDsN00iTVqEaKxBjPBFQV9BDAgPz2SRWes/DhhVm5SmMw==}
     engines: {node: '>=10'}
     requiresBuild: true
     peerDependencies:
@@ -7448,27 +7268,63 @@ packages:
       '@swc/counter': 0.1.2
       '@swc/types': 0.1.5
     optionalDependencies:
-      '@swc/core-darwin-arm64': 1.3.100
-      '@swc/core-darwin-x64': 1.3.100
-      '@swc/core-linux-arm64-gnu': 1.3.100
-      '@swc/core-linux-arm64-musl': 1.3.100
-      '@swc/core-linux-x64-gnu': 1.3.100
-      '@swc/core-linux-x64-musl': 1.3.100
-      '@swc/core-win32-arm64-msvc': 1.3.100
-      '@swc/core-win32-ia32-msvc': 1.3.100
-      '@swc/core-win32-x64-msvc': 1.3.100
+      '@swc/core-darwin-arm64': 1.3.105
+      '@swc/core-darwin-x64': 1.3.105
+      '@swc/core-linux-arm-gnueabihf': 1.3.105
+      '@swc/core-linux-arm64-gnu': 1.3.105
+      '@swc/core-linux-arm64-musl': 1.3.105
+      '@swc/core-linux-x64-gnu': 1.3.105
+      '@swc/core-linux-x64-musl': 1.3.105
+      '@swc/core-win32-arm64-msvc': 1.3.105
+      '@swc/core-win32-ia32-msvc': 1.3.105
+      '@swc/core-win32-x64-msvc': 1.3.105
+
+  /@swc/core@1.3.107:
+    resolution: {integrity: sha512-zKhqDyFcTsyLIYK1iEmavljZnf4CCor5pF52UzLAz4B6Nu/4GLU+2LQVAf+oRHjusG39PTPjd2AlRT3f3QWfsQ==}
+    engines: {node: '>=10'}
+    requiresBuild: true
+    peerDependencies:
+      '@swc/helpers': ^0.5.0
+    peerDependenciesMeta:
+      '@swc/helpers':
+        optional: true
+    dependencies:
+      '@swc/counter': 0.1.2
+      '@swc/types': 0.1.5
+    optionalDependencies:
+      '@swc/core-darwin-arm64': 1.3.107
+      '@swc/core-darwin-x64': 1.3.107
+      '@swc/core-linux-arm-gnueabihf': 1.3.107
+      '@swc/core-linux-arm64-gnu': 1.3.107
+      '@swc/core-linux-arm64-musl': 1.3.107
+      '@swc/core-linux-x64-gnu': 1.3.107
+      '@swc/core-linux-x64-musl': 1.3.107
+      '@swc/core-win32-arm64-msvc': 1.3.107
+      '@swc/core-win32-ia32-msvc': 1.3.107
+      '@swc/core-win32-x64-msvc': 1.3.107
 
   /@swc/counter@0.1.2:
     resolution: {integrity: sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw==}
 
-  /@swc/jest@0.2.29(@swc/core@1.3.100):
-    resolution: {integrity: sha512-8reh5RvHBsSikDC3WGCd5ZTd2BXKkyOdK7QwynrCH58jk2cQFhhHhFBg/jvnWZehUQe/EoOImLENc9/DwbBFow==}
+  /@swc/jest@0.2.31(@swc/core@1.3.105):
+    resolution: {integrity: sha512-Gh0Ste380O8KUY1IqsKr+aOvqqs2Loa+WcWWVNwl+lhXqOWK1iTFAP1K0IDfLqAuFP68+D/PxcpBJn21e6Quvw==}
     engines: {npm: '>= 7.0.0'}
     peerDependencies:
       '@swc/core': '*'
     dependencies:
-      '@jest/create-cache-key-function': 27.5.1
-      '@swc/core': 1.3.100
+      '@jest/create-cache-key-function': 29.7.0
+      '@swc/core': 1.3.105
+      jsonc-parser: 3.2.0
+    dev: true
+
+  /@swc/jest@0.2.31(@swc/core@1.3.107):
+    resolution: {integrity: sha512-Gh0Ste380O8KUY1IqsKr+aOvqqs2Loa+WcWWVNwl+lhXqOWK1iTFAP1K0IDfLqAuFP68+D/PxcpBJn21e6Quvw==}
+    engines: {npm: '>= 7.0.0'}
+    peerDependencies:
+      '@swc/core': '*'
+    dependencies:
+      '@jest/create-cache-key-function': 29.7.0
+      '@swc/core': 1.3.107
       jsonc-parser: 3.2.0
     dev: true
 
@@ -7481,8 +7337,8 @@ packages:
     dev: false
     optional: true
 
-  /@syuilo/aiscript@0.16.0:
-    resolution: {integrity: sha512-CXvoWOq6kmOSUQtKv0IEf7Ebfkk5PO1LxAgLqgRRPgssPvDvINCXu/gFNXKdapkFMkmX+Gj8qjemKR1vnUS4ZA==}
+  /@syuilo/aiscript@0.17.0:
+    resolution: {integrity: sha512-3JtQ1rWJHMxQ3153zLCXMUOwrOgjPPYGBl0dPHhR0ohm4tn7okMQRugxMCT0t3YxByemb9FfiM6TUjd0tEGxdA==}
     dependencies:
       seedrandom: 3.0.5
       stringz: 2.1.0
@@ -7502,26 +7358,12 @@ packages:
     dependencies:
       defer-to-connect: 2.0.1
 
-  /@testing-library/dom@9.2.0:
-    resolution: {integrity: sha512-xTEnpUKiV/bMyEsE5bT4oYA0x0Z/colMtxzUY8bKyPXBNLn/e0V4ZjBZkEhms0xE4pv9QsPfSRu9AWS4y5wGvA==}
-    engines: {node: '>=14'}
-    dependencies:
-      '@babel/code-frame': 7.22.13
-      '@babel/runtime': 7.21.0
-      '@types/aria-query': 5.0.1
-      aria-query: 5.1.3
-      chalk: 4.1.2
-      dom-accessibility-api: 0.5.16
-      lz-string: 1.5.0
-      pretty-format: 27.5.1
-    dev: true
-
   /@testing-library/dom@9.3.3:
     resolution: {integrity: sha512-fB0R+fa3AUqbLHWyxXa2kGVtf1Fe1ZZFr0Zp6AIbIAzXb2mKbEXl+PCQNUOaq5lbTab5tfctfXRNsWXxa2f7Aw==}
     engines: {node: '>=14'}
     dependencies:
       '@babel/code-frame': 7.23.4
-      '@babel/runtime': 7.23.2
+      '@babel/runtime': 7.23.4
       '@types/aria-query': 5.0.1
       aria-query: 5.1.3
       chalk: 4.1.2
@@ -7530,17 +7372,20 @@ packages:
       pretty-format: 27.5.1
     dev: true
 
-  /@testing-library/jest-dom@6.1.2(@types/jest@28.1.3)(vitest@0.34.6):
-    resolution: {integrity: sha512-NP9jl1Q2qDDtx+cqogowtQtmgD2OVs37iMSIsTv5eN5ETRkf26Kj6ugVwA93/gZzzFWQAsgkKkcftDe91BJCkQ==}
+  /@testing-library/jest-dom@6.4.2(vitest@0.34.6):
+    resolution: {integrity: sha512-CzqH0AFymEMG48CpzXFriYYkOjk6ZGPCLMhW9e9jg3KMCn5OfJecF8GtGW7yGfR/IgCe3SX8BSwjdzI6BBbZLw==}
     engines: {node: '>=14', npm: '>=6', yarn: '>=1'}
     peerDependencies:
       '@jest/globals': '>= 28'
+      '@types/bun': latest
       '@types/jest': '>= 28'
       jest: '>= 28'
       vitest: '>= 0.32'
     peerDependenciesMeta:
       '@jest/globals':
         optional: true
+      '@types/bun':
+        optional: true
       '@types/jest':
         optional: true
       jest:
@@ -7548,39 +7393,41 @@ packages:
       vitest:
         optional: true
     dependencies:
-      '@adobe/css-tools': 4.3.1
-      '@babel/runtime': 7.23.2
-      '@types/jest': 28.1.3
+      '@adobe/css-tools': 4.3.3
+      '@babel/runtime': 7.23.4
       aria-query: 5.1.3
       chalk: 3.0.0
       css.escape: 1.5.1
-      dom-accessibility-api: 0.5.16
+      dom-accessibility-api: 0.6.3
       lodash: 4.17.21
       redent: 3.0.0
-      vitest: 0.34.6(happy-dom@10.0.3)(sass@1.69.5)(terser@5.26.0)
+      vitest: 0.34.6(happy-dom@13.6.2)(sass@1.71.1)(terser@5.28.1)
     dev: true
 
-  /@testing-library/user-event@14.4.3(@testing-library/dom@9.2.0):
-    resolution: {integrity: sha512-kCUc5MEwaEMakkO5x7aoD+DLi02ehmEM2QCGWvNqAS1dV/fAvORWEjnjsEIvml59M7Y5kCkWN6fCCyPOe8OL6Q==}
+  /@testing-library/user-event@14.5.2(@testing-library/dom@9.3.3):
+    resolution: {integrity: sha512-YAh82Wh4TIrxYLmfGcixwD18oIjyC1pFQC2Y01F2lzV2HTMiYrI0nze0FD0ocB//CKS/7jIUgae+adPqxK5yCQ==}
     engines: {node: '>=12', npm: '>=6'}
     peerDependencies:
       '@testing-library/dom': '>=7.21.4'
     dependencies:
-      '@testing-library/dom': 9.2.0
+      '@testing-library/dom': 9.3.3
     dev: true
 
-  /@testing-library/vue@8.0.1(@vue/compiler-sfc@3.3.12)(vue@3.3.12):
-    resolution: {integrity: sha512-l51ZEpjTQ6glq3wM+asQ1GbKJMGcxwgHEygETx0aCRN4TjFEGvMZy4YdWKs/y7bu4bmLrxcxhbEPP7iPSW/2OQ==}
+  /@testing-library/vue@8.0.2(@vue/compiler-sfc@3.4.21)(vue@3.4.21):
+    resolution: {integrity: sha512-A8wWX+qQn0o0izpQWnGCpwQt8wAdpsVP8vPP2h5Q/jcGhZ5yKXz9PPUqhQv+45LTFaWlyRf8bArTVaB/KFFd5A==}
     engines: {node: '>=14'}
     peerDependencies:
       '@vue/compiler-sfc': '>= 3'
       vue: '>= 3'
+    peerDependenciesMeta:
+      '@vue/compiler-sfc':
+        optional: true
     dependencies:
-      '@babel/runtime': 7.23.2
+      '@babel/runtime': 7.23.4
       '@testing-library/dom': 9.3.3
-      '@vue/compiler-sfc': 3.3.12
-      '@vue/test-utils': 2.4.1(vue@3.3.12)
-      vue: 3.3.12(typescript@5.3.3)
+      '@vue/compiler-sfc': 3.4.21
+      '@vue/test-utils': 2.4.1(vue@3.4.21)
+      vue: 3.4.21(typescript@5.3.3)
     transitivePeerDependencies:
       - '@vue/server-renderer'
     dev: true
@@ -7589,6 +7436,12 @@ packages:
     resolution: {integrity: sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==}
     dev: false
 
+  /@transfem-org/sfm-js@0.24.4:
+    resolution: {integrity: sha1-0wEXqL5UJseGFO4GGFRrES6NCDk=, tarball: https://activitypub.software/api/v4/projects/2/packages/npm/@transfem-org/sfm-js/-/@transfem-org/sfm-js-0.24.4.tgz}
+    dependencies:
+      '@twemoji/parser': 15.0.0
+    dev: false
+
   /@trysound/sax@0.2.0:
     resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
     engines: {node: '>=10.13.0'}
@@ -7606,7 +7459,7 @@ packages:
   /@types/accepts@1.3.7:
     resolution: {integrity: sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==}
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
     dev: true
 
   /@types/archiver@6.0.2:
@@ -7626,8 +7479,8 @@ packages:
   /@types/babel__core@7.20.5:
     resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
     dependencies:
-      '@babel/parser': 7.23.4
-      '@babel/types': 7.23.4
+      '@babel/parser': 7.23.6
+      '@babel/types': 7.24.0
       '@types/babel__generator': 7.6.7
       '@types/babel__template': 7.4.4
       '@types/babel__traverse': 7.20.4
@@ -7636,20 +7489,20 @@ packages:
   /@types/babel__generator@7.6.7:
     resolution: {integrity: sha512-6Sfsq+EaaLrw4RmdFWE9Onp63TOUue71AWb4Gpa6JxzgTYtimbM086WnYTy2U67AofR++QKCo08ZP6pwx8YFHQ==}
     dependencies:
-      '@babel/types': 7.23.4
+      '@babel/types': 7.24.0
     dev: true
 
   /@types/babel__template@7.4.4:
     resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
     dependencies:
-      '@babel/parser': 7.23.4
-      '@babel/types': 7.23.4
+      '@babel/parser': 7.23.6
+      '@babel/types': 7.24.0
     dev: true
 
   /@types/babel__traverse@7.20.4:
     resolution: {integrity: sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA==}
     dependencies:
-      '@babel/types': 7.23.4
+      '@babel/types': 7.24.0
     dev: true
 
   /@types/bcryptjs@2.4.6:
@@ -7660,7 +7513,7 @@ packages:
     resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==}
     dependencies:
       '@types/connect': 3.4.35
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
     dev: true
 
   /@types/braces@3.0.1:
@@ -7672,24 +7525,18 @@ packages:
     dependencies:
       '@types/http-cache-semantics': 4.0.4
       '@types/keyv': 3.1.4
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
       '@types/responselike': 1.0.0
     dev: false
 
-  /@types/cbor@6.0.0:
-    resolution: {integrity: sha512-mGQ1lbYOwVti5Xlarn1bTeBZqgY0kstsdjnkoEovgohYKdBjGejHyNGXHdMBeqyQazIv32Jjp33+5pBEaSRy2w==}
+  /@types/chai-subset@1.3.5:
+    resolution: {integrity: sha512-c2mPnw+xHtXDoHmdtcCXGwyLMiauiAyxWMzhGpqHC4nqI/Y5G2XhTampslK2rb59kpcuHon03UH8W6iYUzw88A==}
     dependencies:
-      cbor: 9.0.1
+      '@types/chai': 4.3.11
     dev: true
 
-  /@types/chai-subset@1.3.3:
-    resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==}
-    dependencies:
-      '@types/chai': 4.3.5
-    dev: true
-
-  /@types/chai@4.3.5:
-    resolution: {integrity: sha512-mEo1sAde+UCE6b2hxn332f1g1E8WfYRu6p5SvTKr2ZKC1f7gFJXk4h5PyGP9Dt6gCaG8y8XhwnXWC6Iy2cmBng==}
+  /@types/chai@4.3.11:
+    resolution: {integrity: sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ==}
     dev: true
 
   /@types/color-convert@2.0.3:
@@ -7705,15 +7552,15 @@ packages:
   /@types/connect@3.4.35:
     resolution: {integrity: sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==}
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
     dev: true
 
   /@types/content-disposition@0.5.8:
     resolution: {integrity: sha512-QVSSvno3dE0MgO76pJhmv4Qyi/j0Yk9pBp0Y7TJ2Tlj+KCgJWY6qX7nnxCOLkZ3VYRSIk1WTxCvwUSdx6CCLdg==}
     dev: true
 
-  /@types/cookie@0.4.1:
-    resolution: {integrity: sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==}
+  /@types/cookie@0.6.0:
+    resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
     dev: true
 
   /@types/core-js@2.5.8:
@@ -7723,11 +7570,11 @@ packages:
   /@types/cross-spawn@6.0.2:
     resolution: {integrity: sha512-KuwNhp3eza+Rhu8IFI5HUXRP0LIhqH5cAjubUvGXXthh4YYBuP2ntwEX+Cz8GJoZUHlKo247wPWOfA9LYEq4cw==}
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
     dev: true
 
-  /@types/debug@4.1.7:
-    resolution: {integrity: sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==}
+  /@types/debug@4.1.12:
+    resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==}
     dependencies:
       '@types/ms': 0.7.34
     dev: true
@@ -7781,7 +7628,7 @@ packages:
   /@types/express-serve-static-core@4.17.33:
     resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==}
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
       '@types/qs': 6.9.7
       '@types/range-parser': 1.2.4
     dev: true
@@ -7802,7 +7649,7 @@ packages:
   /@types/fluent-ffmpeg@2.1.24:
     resolution: {integrity: sha512-g5oQO8Jgi2kFS3tTub7wLvfLztr1s8tdXmRd8PiL/hLMLzTIAyMR2sANkTggM/rdEDAg3d63nYRRVepwBiCw5A==}
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
     dev: true
 
   /@types/form-data@2.5.0:
@@ -7816,13 +7663,23 @@ packages:
     resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
     dependencies:
       '@types/minimatch': 5.1.2
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
     dev: true
 
   /@types/graceful-fs@4.1.9:
     resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==}
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
+    dev: true
+
+  /@types/hast@3.0.4:
+    resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
+    dependencies:
+      '@types/unist': 2.0.6
+    dev: true
+
+  /@types/htmlescape@1.1.3:
+    resolution: {integrity: sha512-tuC81YJXGUe0q8WRtBNW+uyx79rkkzWK651ALIXXYq5/u/IxjX4iHneGF2uUqzsNp+F+9J2mFZOv9jiLTtIq0w==}
     dev: true
 
   /@types/http-cache-semantics@4.0.4:
@@ -7831,45 +7688,22 @@ packages:
   /@types/http-link-header@1.0.5:
     resolution: {integrity: sha512-AxhIKR8UbyoqCTNp9rRepkktHuUOw3DjfOfDCaO9kwI8AYzjhxyrvZq4+mRw/2daD3hYDknrtSeV6SsPwmc71w==}
     dependencies:
-      '@types/node': 20.10.5
-    dev: true
-
-  /@types/istanbul-lib-coverage@2.0.4:
-    resolution: {integrity: sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==}
+      '@types/node': 20.11.22
     dev: true
 
   /@types/istanbul-lib-coverage@2.0.6:
     resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==}
 
-  /@types/istanbul-lib-report@3.0.0:
-    resolution: {integrity: sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==}
-    dependencies:
-      '@types/istanbul-lib-coverage': 2.0.4
-    dev: true
-
   /@types/istanbul-lib-report@3.0.3:
     resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==}
     dependencies:
       '@types/istanbul-lib-coverage': 2.0.6
 
-  /@types/istanbul-reports@3.0.1:
-    resolution: {integrity: sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==}
-    dependencies:
-      '@types/istanbul-lib-report': 3.0.0
-    dev: true
-
   /@types/istanbul-reports@3.0.4:
     resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==}
     dependencies:
       '@types/istanbul-lib-report': 3.0.3
 
-  /@types/jest@28.1.3:
-    resolution: {integrity: sha512-Tsbjk8Y2hkBaY/gJsataeb4q9Mubw9EOz7+4RjPkzD5KjTvHHs7cpws22InaoXxAVAhF5HfFbzJjo6oKWqSZLw==}
-    dependencies:
-      jest-matcher-utils: 28.1.3
-      pretty-format: 28.1.3
-    dev: true
-
   /@types/jest@29.5.10:
     resolution: {integrity: sha512-tE4yxKEphEyxj9s4inideLHktW/x6DwesIwWZ9NN1FKf9zbJYsnhBoA9vrHA/IuIOKwPa5PcFBNV4lpMIOEzyQ==}
     dependencies:
@@ -7884,8 +7718,11 @@ packages:
       pretty-format: 29.7.0
     dev: true
 
-  /@types/js-levenshtein@1.1.1:
-    resolution: {integrity: sha512-qC4bCqYGy1y/NP7dDVr7KJarn+PbX1nSpwA7JXdu0HxT3QYjO8MJ+cntENtHFVy2dRAyBV23OZ6MxsW1AM1L8g==}
+  /@types/jest@29.5.12:
+    resolution: {integrity: sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==}
+    dependencies:
+      expect: 29.7.0
+      pretty-format: 29.7.0
     dev: true
 
   /@types/js-yaml@4.0.9:
@@ -7895,7 +7732,7 @@ packages:
   /@types/jsdom@21.1.6:
     resolution: {integrity: sha512-/7kkMsC+/kMs7gAYmmBR9P0vGTnOoLhQhyhQJSlXGI5bzTHp6xdo0TtKWQAsz6pmSAeVqKSbqeyP6hytqr9FDw==}
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
       '@types/tough-cookie': 4.0.2
       parse5: 7.1.2
     dev: true
@@ -7919,15 +7756,21 @@ packages:
   /@types/keyv@3.1.4:
     resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==}
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
     dev: false
 
   /@types/lodash@4.14.191:
     resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==}
     dev: true
 
-  /@types/matter-js@0.19.5:
-    resolution: {integrity: sha512-pTVB5krRGb01hr8L6BJqWGoSriqUbbvJ9Fd0Qp0eAOE//w/lFvkaVHkVB8J3wXr9U3lZDzmAjJPPQn7x4wzbWg==}
+  /@types/matter-js@0.19.6:
+    resolution: {integrity: sha512-ffk6tqJM5scla+ThXmnox+mdfCo3qYk6yMjQsNcrbo6eQ5DqorVdtnaL+1agCoYzxUjmHeiNB7poBMAmhuLY7w==}
+    dev: true
+
+  /@types/mdast@4.0.3:
+    resolution: {integrity: sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==}
+    dependencies:
+      '@types/unist': 2.0.6
     dev: true
 
   /@types/mdx@2.0.3:
@@ -7960,13 +7803,6 @@ packages:
     resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==}
     dev: true
 
-  /@types/node-fetch@2.6.4:
-    resolution: {integrity: sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==}
-    dependencies:
-      '@types/node': 20.10.5
-      form-data: 3.0.1
-    dev: true
-
   /@types/node-fetch@3.0.3:
     resolution: {integrity: sha512-HhggYPH5N+AQe/OmN6fmhKmRRt2XuNJow+R3pQwJxOOF9GuwM7O2mheyGeIrs5MOIeNjDEdgdoyHBOrFeJBR3g==}
     dependencies:
@@ -7977,12 +7813,17 @@ packages:
     resolution: {integrity: sha512-2yrWpBk32tvV/JAd3HNHWuZn/VDN1P+72hWirHnvsvTGSqbANi+kSeuQR9yAHnbvaBvHDsoTdXV0Fe+iRtHLKA==}
     dev: true
 
-  /@types/node@20.10.5:
-    resolution: {integrity: sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==}
-    requiresBuild: true
+  /@types/node@20.11.22:
+    resolution: {integrity: sha512-/G+IxWxma6V3E+pqK1tSl2Fo1kl41pK1yeCyDsgkF9WlVAme4j5ISYM2zR11bgLFJGLN5sVK40T4RJNuiZbEjA==}
     dependencies:
       undici-types: 5.26.5
 
+  /@types/node@20.11.5:
+    resolution: {integrity: sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w==}
+    dependencies:
+      undici-types: 5.26.5
+    dev: true
+
   /@types/node@20.9.1:
     resolution: {integrity: sha512-HhmzZh5LSJNS5O8jQKpJ/3ZcrrlG6L70hpGqMIAoM9YVD0YBRNWYsfwcXq8VnSjlNpCpgLzMXdiPo+dxcvSmiA==}
     dependencies:
@@ -7992,7 +7833,7 @@ packages:
   /@types/nodemailer@6.4.14:
     resolution: {integrity: sha512-fUWthHO9k9DSdPCSPRqcu6TWhYyxTBg382vlNIttSe9M7XfsT06y0f24KHXtbnijPGGRIcVvdKHTNikOI6qiHA==}
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
     dev: true
 
   /@types/normalize-package-data@2.4.1:
@@ -8009,13 +7850,13 @@ packages:
     resolution: {integrity: sha512-Ali0fUUn+zgr4Yy/pCTFbuiaiJpq7l7OQwFnxYVchNbNGIx0c4Wkcdje6WO89I91RAaYF+gVc1pOaizA4YKZmA==}
     dependencies:
       '@types/express': 4.17.17
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
     dev: true
 
   /@types/oauth@0.9.4:
     resolution: {integrity: sha512-qk9orhti499fq5XxKCCEbd0OzdPZuancneyse3KtR+vgMiHRbh+mn8M4G6t64ob/Fg+GZGpa565MF/2dKWY32A==}
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
 
   /@types/object-assign-deep@0.4.3:
     resolution: {integrity: sha512-d9Gxaj5j1hzrxJ61EFEg13B4g4FgrT/DYtcDWFXPehR8DF2SUZbVMFtZIs8exkVRiqrqBpdTc/lUUZjncsPpMw==}
@@ -8025,10 +7866,10 @@ packages:
     resolution: {integrity: sha512-ffLAxD6Xqcf2gSbtEJehj8yJ5R/2OZqD4liodQvQQ+hhO4kg1mk9ToEZQPMtNTm/zIQj2GNleQbsjPp9+UQm4Q==}
     dev: false
 
-  /@types/pg@8.10.9:
-    resolution: {integrity: sha512-UksbANNE/f8w0wOMxVKKIrLCbEMV+oM1uKejmwXr39olg4xqcfBDbXxObJAt6XxHbDa4XTKOlUEcEltXDX+XLQ==}
+  /@types/pg@8.11.2:
+    resolution: {integrity: sha512-G2Mjygf2jFMU/9hCaTYxJrwdObdcnuQde1gndooZSOHsNSaCehAuwc7EIuSA34Do8Jx2yZ19KtvW8P0j4EuUXw==}
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
       pg-protocol: 1.6.0
       pg-types: 4.0.1
     dev: true
@@ -8045,14 +7886,14 @@ packages:
     resolution: {integrity: sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA==}
     dev: true
 
-  /@types/punycode@2.1.3:
-    resolution: {integrity: sha512-dFkH9Mz0yY5UfQVSrpj1grQyqRwe4TohTLlHFx4Gli8/fsaNyoOVUAsiEBZk5JBwbEJVZ49W6st8D5g6dRJb/w==}
+  /@types/punycode@2.1.4:
+    resolution: {integrity: sha512-trzh6NzBnq8yw5e35f8xe8VTYjqM3NE7bohBtvDVf/dtUer3zYTLK1Ka3DG3p7bdtoaOHZucma6FfVKlQ134pQ==}
     dev: true
 
   /@types/qrcode@1.5.5:
     resolution: {integrity: sha512-CdfBi/e3Qk+3Z/fXYShipBT13OJ2fDO2Q2w5CIP5anLTLIndQG9z6P1cnm+8zCWSpm5dnxMFd/uREtb0EXuQzg==}
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
     dev: true
 
   /@types/qs@6.9.7:
@@ -8076,13 +7917,13 @@ packages:
     dependencies:
       '@types/prop-types': 15.7.5
       '@types/scheduler': 0.16.2
-      csstype: 3.1.2
+      csstype: 3.1.3
     dev: true
 
   /@types/readdir-glob@1.1.1:
     resolution: {integrity: sha512-ImM6TmoF8bgOwvehGviEj3tRdRBbQujr1N+0ypaln/GWjaerOB26jb93vsRHmdMtvVQZQebOlqt2HROark87mQ==}
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
     dev: true
 
   /@types/rename@1.0.7:
@@ -8096,11 +7937,11 @@ packages:
   /@types/responselike@1.0.0:
     resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==}
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
     dev: false
 
-  /@types/sanitize-html@2.9.5:
-    resolution: {integrity: sha512-2Sr1vd8Dw+ypsg/oDDfZ57OMSG2Befs+l2CMyCC5bVSK3CpE7lTB2aNlbbWzazgVA+Qqfuholwom6x/mWd1qmw==}
+  /@types/sanitize-html@2.11.0:
+    resolution: {integrity: sha512-7oxPGNQHXLHE48r/r/qjn7q0hlrs3kL7oZnGj0Wf/h9tj/6ibFyRkNbsDxaBBZ4XUZ0Dx5LGCyDJ04ytSofacQ==}
     dependencies:
       htmlparser2: 8.0.2
     dev: true
@@ -8109,34 +7950,25 @@ packages:
     resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==}
     dev: true
 
-  /@types/semver@7.5.6:
-    resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==}
+  /@types/seedrandom@3.0.8:
+    resolution: {integrity: sha512-TY1eezMU2zH2ozQoAFAQFOPpvP15g+ZgSfTZt31AUUH/Rxtnz3H+A/Sv1Snw2/amp//omibc+AEkTaA8KUeOLQ==}
+    dev: true
+
+  /@types/semver@7.5.8:
+    resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==}
     dev: true
 
   /@types/serve-static@1.15.1:
     resolution: {integrity: sha512-NUo5XNiAdULrJENtJXZZ3fHtfMolzZwczzBbnAeBbqBwG+LaG6YaJtuwzwGSQZ2wsCrxjEhNNjAkKigy3n8teQ==}
     dependencies:
       '@types/mime': 3.0.1
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
     dev: true
 
   /@types/serviceworker@0.0.67:
     resolution: {integrity: sha512-7TCH7iNsCSNb+aUD9M/36TekrWFSLCjNK8zw/3n5kOtRjbLtDfGYMXTrDnGhSfqXNwpqmt9Vd90w5C/ad1tX6Q==}
     dev: true
 
-  /@types/set-cookie-parser@2.4.3:
-    resolution: {integrity: sha512-7QhnH7bi+6KAhBB+Auejz1uV9DHiopZqu7LfR/5gZZTkejJV5nYeZZpgfFoE0N8aDsXuiYpfKyfyMatCwQhyTQ==}
-    dependencies:
-      '@types/node': 20.10.5
-    dev: true
-
-  /@types/sharp@0.32.0:
-    resolution: {integrity: sha512-OOi3kL+FZDnPhVzsfD37J88FNeZh6gQsGcLc95NbeURRGvmSjeXiDcyWzF2o3yh/gQAUn2uhh/e+CPCa5nwAxw==}
-    deprecated: This is a stub types definition. sharp provides its own type definitions, so you do not need this installed.
-    dependencies:
-      sharp: 0.32.6
-    dev: true
-
   /@types/simple-oauth2@5.0.7:
     resolution: {integrity: sha512-8JbWVJbiTSBQP/7eiyGKyXWAqp3dKQZpaA+pdW16FCi32ujkzRMG8JfjoAzdWt6W8U591ZNdHcPtP2D7ILTKuA==}
     dev: true
@@ -8162,6 +7994,10 @@ packages:
   /@types/stack-utils@2.0.3:
     resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==}
 
+  /@types/statuses@2.0.4:
+    resolution: {integrity: sha512-eqNDvZsCNY49OAXB0Firg/Sc2BgoWsntsLUdybGFOhAfCD6QJ2n9HXUIHGqt5qjrxmMv4wS8WLAw43ZkKcJ8Pw==}
+    dev: true
+
   /@types/throttle-debounce@5.0.2:
     resolution: {integrity: sha512-pDzSNulqooSKvSNcksnV72nk8p7gRqN8As71Sp28nov1IgmPKWbOEIwAWvBME5pPTtaXJAvG3O4oc76HlQ4kqQ==}
     dev: true
@@ -8182,33 +8018,38 @@ packages:
     resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==}
     dev: true
 
+  /@types/unist@3.0.2:
+    resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==}
+    dev: true
+
   /@types/uuid@9.0.4:
     resolution: {integrity: sha512-zAuJWQflfx6dYJM62vna+Sn5aeSWhh3OB+wfUEACNcqUSc0AGc5JKl+ycL1vrH7frGTXhJchYjE1Hak8L819dA==}
     dev: true
 
   /@types/uuid@9.0.7:
     resolution: {integrity: sha512-WUtIVRUZ9i5dYXefDEAI7sh9/O7jGvHg7Df/5O/gtH3Yabe5odI3UWopVR1qbPXQtvOxWu3mM4XxlYeZtMWF4g==}
+    dev: false
+
+  /@types/uuid@9.0.8:
+    resolution: {integrity: sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==}
+    dev: true
 
   /@types/vary@1.1.3:
     resolution: {integrity: sha512-XJT8/ZQCL7NUut9QDLf6l24JfAEl7bnNdgxfj50cHIpEPRJLHHDDFOAq6i+GsEmeFfH7NamhBE4c4Thtb2egWg==}
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
     dev: true
 
   /@types/web-push@3.6.3:
     resolution: {integrity: sha512-v3oT4mMJsHeJ/rraliZ+7TbZtr5bQQuxcgD7C3/1q/zkAj29c8RE0F9lVZVu3hiQe5Z9fYcBreV7TLnfKR+4mg==}
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
     dev: true
 
   /@types/ws@8.5.10:
     resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==}
     dependencies:
-      '@types/node': 20.10.5
-
-  /@types/yargs-parser@21.0.0:
-    resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==}
-    dev: true
+      '@types/node': 20.11.22
 
   /@types/yargs-parser@21.0.3:
     resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==}
@@ -8216,7 +8057,7 @@ packages:
   /@types/yargs@16.0.5:
     resolution: {integrity: sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==}
     dependencies:
-      '@types/yargs-parser': 21.0.0
+      '@types/yargs-parser': 21.0.3
     dev: true
 
   /@types/yargs@17.0.32:
@@ -8228,7 +8069,7 @@ packages:
     resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
     requiresBuild: true
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
     dev: true
     optional: true
 
@@ -8261,8 +8102,8 @@ packages:
       - supports-color
     dev: true
 
-  /@typescript-eslint/eslint-plugin@6.12.0(@typescript-eslint/parser@6.12.0)(eslint@8.54.0)(typescript@5.1.6):
-    resolution: {integrity: sha512-XOpZ3IyJUIV1b15M7HVOpgQxPPF7lGXgsfcEIu3yDxFPaf/xZKt7s9QO/pbk7vpWQyVulpJbu4E5LwpZiQo4kA==}
+  /@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.54.0)(typescript@5.1.6):
+    resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==}
     engines: {node: ^16.0.0 || >=18.0.0}
     peerDependencies:
       '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha
@@ -8273,25 +8114,25 @@ packages:
         optional: true
     dependencies:
       '@eslint-community/regexpp': 4.10.0
-      '@typescript-eslint/parser': 6.12.0(eslint@8.54.0)(typescript@5.1.6)
-      '@typescript-eslint/scope-manager': 6.12.0
-      '@typescript-eslint/type-utils': 6.12.0(eslint@8.54.0)(typescript@5.1.6)
-      '@typescript-eslint/utils': 6.12.0(eslint@8.54.0)(typescript@5.1.6)
-      '@typescript-eslint/visitor-keys': 6.12.0
+      '@typescript-eslint/parser': 6.21.0(eslint@8.54.0)(typescript@5.1.6)
+      '@typescript-eslint/scope-manager': 6.21.0
+      '@typescript-eslint/type-utils': 6.21.0(eslint@8.54.0)(typescript@5.1.6)
+      '@typescript-eslint/utils': 6.21.0(eslint@8.54.0)(typescript@5.1.6)
+      '@typescript-eslint/visitor-keys': 6.21.0
       debug: 4.3.4(supports-color@8.1.1)
       eslint: 8.54.0
       graphemer: 1.4.0
       ignore: 5.3.0
       natural-compare: 1.4.0
-      semver: 7.5.4
+      semver: 7.6.0
       ts-api-utils: 1.0.3(typescript@5.1.6)
       typescript: 5.1.6
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@typescript-eslint/eslint-plugin@6.14.0(@typescript-eslint/parser@6.14.0)(eslint@8.56.0)(typescript@5.3.3):
-    resolution: {integrity: sha512-1ZJBykBCXaSHG94vMMKmiHoL0MhNHKSVlcHVYZNw+BKxufhqQVTOawNpwwI1P5nIFZ/4jLVop0mcY6mJJDFNaw==}
+  /@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@7.1.0)(eslint@8.57.0)(typescript@5.3.3):
+    resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==}
     engines: {node: ^16.0.0 || >=18.0.0}
     peerDependencies:
       '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha
@@ -8302,17 +8143,46 @@ packages:
         optional: true
     dependencies:
       '@eslint-community/regexpp': 4.10.0
-      '@typescript-eslint/parser': 6.14.0(eslint@8.56.0)(typescript@5.3.3)
-      '@typescript-eslint/scope-manager': 6.14.0
-      '@typescript-eslint/type-utils': 6.14.0(eslint@8.56.0)(typescript@5.3.3)
-      '@typescript-eslint/utils': 6.14.0(eslint@8.56.0)(typescript@5.3.3)
-      '@typescript-eslint/visitor-keys': 6.14.0
+      '@typescript-eslint/parser': 7.1.0(eslint@8.57.0)(typescript@5.3.3)
+      '@typescript-eslint/scope-manager': 6.21.0
+      '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.0)(typescript@5.3.3)
+      '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@5.3.3)
+      '@typescript-eslint/visitor-keys': 6.21.0
       debug: 4.3.4(supports-color@8.1.1)
-      eslint: 8.56.0
+      eslint: 8.57.0
       graphemer: 1.4.0
       ignore: 5.3.0
       natural-compare: 1.4.0
-      semver: 7.5.4
+      semver: 7.6.0
+      ts-api-utils: 1.0.3(typescript@5.3.3)
+      typescript: 5.3.3
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@typescript-eslint/eslint-plugin@7.1.0(@typescript-eslint/parser@7.1.0)(eslint@8.57.0)(typescript@5.3.3):
+    resolution: {integrity: sha512-j6vT/kCulhG5wBmGtstKeiVr1rdXE4nk+DT1k6trYkwlrvW9eOF5ZbgKnd/YR6PcM4uTEXa0h6Fcvf6X7Dxl0w==}
+    engines: {node: ^16.0.0 || >=18.0.0}
+    peerDependencies:
+      '@typescript-eslint/parser': ^7.0.0
+      eslint: ^8.56.0
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@eslint-community/regexpp': 4.10.0
+      '@typescript-eslint/parser': 7.1.0(eslint@8.57.0)(typescript@5.3.3)
+      '@typescript-eslint/scope-manager': 7.1.0
+      '@typescript-eslint/type-utils': 7.1.0(eslint@8.57.0)(typescript@5.3.3)
+      '@typescript-eslint/utils': 7.1.0(eslint@8.57.0)(typescript@5.3.3)
+      '@typescript-eslint/visitor-keys': 7.1.0
+      debug: 4.3.4(supports-color@8.1.1)
+      eslint: 8.57.0
+      graphemer: 1.4.0
+      ignore: 5.3.0
+      natural-compare: 1.4.0
+      semver: 7.6.0
       ts-api-utils: 1.0.3(typescript@5.3.3)
       typescript: 5.3.3
     transitivePeerDependencies:
@@ -8340,8 +8210,8 @@ packages:
       - supports-color
     dev: true
 
-  /@typescript-eslint/parser@6.12.0(eslint@8.54.0)(typescript@5.1.6):
-    resolution: {integrity: sha512-s8/jNFPKPNRmXEnNXfuo1gemBdVmpQsK1pcu+QIvuNJuhFzGrpD7WjOcvDc/+uEdfzSYpNu7U/+MmbScjoQ6vg==}
+  /@typescript-eslint/parser@6.21.0(eslint@8.54.0)(typescript@5.1.6):
+    resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==}
     engines: {node: ^16.0.0 || >=18.0.0}
     peerDependencies:
       eslint: ^7.0.0 || ^8.0.0
@@ -8350,10 +8220,10 @@ packages:
       typescript:
         optional: true
     dependencies:
-      '@typescript-eslint/scope-manager': 6.12.0
-      '@typescript-eslint/types': 6.12.0
-      '@typescript-eslint/typescript-estree': 6.12.0(typescript@5.1.6)
-      '@typescript-eslint/visitor-keys': 6.12.0
+      '@typescript-eslint/scope-manager': 6.21.0
+      '@typescript-eslint/types': 6.21.0
+      '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.1.6)
+      '@typescript-eslint/visitor-keys': 6.21.0
       debug: 4.3.4(supports-color@8.1.1)
       eslint: 8.54.0
       typescript: 5.1.6
@@ -8361,22 +8231,22 @@ packages:
       - supports-color
     dev: true
 
-  /@typescript-eslint/parser@6.14.0(eslint@8.56.0)(typescript@5.3.3):
-    resolution: {integrity: sha512-QjToC14CKacd4Pa7JK4GeB/vHmWFJckec49FR4hmIRf97+KXole0T97xxu9IFiPxVQ1DBWrQ5wreLwAGwWAVQA==}
+  /@typescript-eslint/parser@7.1.0(eslint@8.57.0)(typescript@5.3.3):
+    resolution: {integrity: sha512-V1EknKUubZ1gWFjiOZhDSNToOjs63/9O0puCgGS8aDOgpZY326fzFu15QAUjwaXzRZjf/qdsdBrckYdv9YxB8w==}
     engines: {node: ^16.0.0 || >=18.0.0}
     peerDependencies:
-      eslint: ^7.0.0 || ^8.0.0
+      eslint: ^8.56.0
       typescript: '*'
     peerDependenciesMeta:
       typescript:
         optional: true
     dependencies:
-      '@typescript-eslint/scope-manager': 6.14.0
-      '@typescript-eslint/types': 6.14.0
-      '@typescript-eslint/typescript-estree': 6.14.0(typescript@5.3.3)
-      '@typescript-eslint/visitor-keys': 6.14.0
+      '@typescript-eslint/scope-manager': 7.1.0
+      '@typescript-eslint/types': 7.1.0
+      '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3)
+      '@typescript-eslint/visitor-keys': 7.1.0
       debug: 4.3.4(supports-color@8.1.1)
-      eslint: 8.56.0
+      eslint: 8.57.0
       typescript: 5.3.3
     transitivePeerDependencies:
       - supports-color
@@ -8390,20 +8260,20 @@ packages:
       '@typescript-eslint/visitor-keys': 6.11.0
     dev: true
 
-  /@typescript-eslint/scope-manager@6.12.0:
-    resolution: {integrity: sha512-5gUvjg+XdSj8pcetdL9eXJzQNTl3RD7LgUiYTl8Aabdi8hFkaGSYnaS6BLc0BGNaDH+tVzVwmKtWvu0jLgWVbw==}
+  /@typescript-eslint/scope-manager@6.21.0:
+    resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==}
     engines: {node: ^16.0.0 || >=18.0.0}
     dependencies:
-      '@typescript-eslint/types': 6.12.0
-      '@typescript-eslint/visitor-keys': 6.12.0
+      '@typescript-eslint/types': 6.21.0
+      '@typescript-eslint/visitor-keys': 6.21.0
     dev: true
 
-  /@typescript-eslint/scope-manager@6.14.0:
-    resolution: {integrity: sha512-VT7CFWHbZipPncAZtuALr9y3EuzY1b1t1AEkIq2bTXUPKw+pHoXflGNG5L+Gv6nKul1cz1VH8fz16IThIU0tdg==}
+  /@typescript-eslint/scope-manager@7.1.0:
+    resolution: {integrity: sha512-6TmN4OJiohHfoOdGZ3huuLhpiUgOGTpgXNUPJgeZOZR3DnIpdSgtt83RS35OYNNXxM4TScVlpVKC9jyQSETR1A==}
     engines: {node: ^16.0.0 || >=18.0.0}
     dependencies:
-      '@typescript-eslint/types': 6.14.0
-      '@typescript-eslint/visitor-keys': 6.14.0
+      '@typescript-eslint/types': 7.1.0
+      '@typescript-eslint/visitor-keys': 7.1.0
     dev: true
 
   /@typescript-eslint/type-utils@6.11.0(eslint@8.53.0)(typescript@5.3.3):
@@ -8426,8 +8296,8 @@ packages:
       - supports-color
     dev: true
 
-  /@typescript-eslint/type-utils@6.12.0(eslint@8.54.0)(typescript@5.1.6):
-    resolution: {integrity: sha512-WWmRXxhm1X8Wlquj+MhsAG4dU/Blvf1xDgGaYCzfvStP2NwPQh6KBvCDbiOEvaE0filhranjIlK/2fSTVwtBng==}
+  /@typescript-eslint/type-utils@6.21.0(eslint@8.54.0)(typescript@5.1.6):
+    resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==}
     engines: {node: ^16.0.0 || >=18.0.0}
     peerDependencies:
       eslint: ^7.0.0 || ^8.0.0
@@ -8436,8 +8306,8 @@ packages:
       typescript:
         optional: true
     dependencies:
-      '@typescript-eslint/typescript-estree': 6.12.0(typescript@5.1.6)
-      '@typescript-eslint/utils': 6.12.0(eslint@8.54.0)(typescript@5.1.6)
+      '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.1.6)
+      '@typescript-eslint/utils': 6.21.0(eslint@8.54.0)(typescript@5.1.6)
       debug: 4.3.4(supports-color@8.1.1)
       eslint: 8.54.0
       ts-api-utils: 1.0.3(typescript@5.1.6)
@@ -8446,8 +8316,8 @@ packages:
       - supports-color
     dev: true
 
-  /@typescript-eslint/type-utils@6.14.0(eslint@8.56.0)(typescript@5.3.3):
-    resolution: {integrity: sha512-x6OC9Q7HfYKqjnuNu5a7kffIYs3No30isapRBJl1iCHLitD8O0lFbRcVGiOcuyN837fqXzPZ1NS10maQzZMKqw==}
+  /@typescript-eslint/type-utils@6.21.0(eslint@8.57.0)(typescript@5.3.3):
+    resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==}
     engines: {node: ^16.0.0 || >=18.0.0}
     peerDependencies:
       eslint: ^7.0.0 || ^8.0.0
@@ -8456,10 +8326,30 @@ packages:
       typescript:
         optional: true
     dependencies:
-      '@typescript-eslint/typescript-estree': 6.14.0(typescript@5.3.3)
-      '@typescript-eslint/utils': 6.14.0(eslint@8.56.0)(typescript@5.3.3)
+      '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3)
+      '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@5.3.3)
       debug: 4.3.4(supports-color@8.1.1)
-      eslint: 8.56.0
+      eslint: 8.57.0
+      ts-api-utils: 1.0.3(typescript@5.3.3)
+      typescript: 5.3.3
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@typescript-eslint/type-utils@7.1.0(eslint@8.57.0)(typescript@5.3.3):
+    resolution: {integrity: sha512-UZIhv8G+5b5skkcuhgvxYWHjk7FW7/JP5lPASMEUoliAPwIH/rxoUSQPia2cuOj9AmDZmwUl1usKm85t5VUMew==}
+    engines: {node: ^16.0.0 || >=18.0.0}
+    peerDependencies:
+      eslint: ^8.56.0
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3)
+      '@typescript-eslint/utils': 7.1.0(eslint@8.57.0)(typescript@5.3.3)
+      debug: 4.3.4(supports-color@8.1.1)
+      eslint: 8.57.0
       ts-api-utils: 1.0.3(typescript@5.3.3)
       typescript: 5.3.3
     transitivePeerDependencies:
@@ -8471,13 +8361,13 @@ packages:
     engines: {node: ^16.0.0 || >=18.0.0}
     dev: true
 
-  /@typescript-eslint/types@6.12.0:
-    resolution: {integrity: sha512-MA16p/+WxM5JG/F3RTpRIcuOghWO30//VEOvzubM8zuOOBYXsP+IfjoCXXiIfy2Ta8FRh9+IO9QLlaFQUU+10Q==}
+  /@typescript-eslint/types@6.21.0:
+    resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==}
     engines: {node: ^16.0.0 || >=18.0.0}
     dev: true
 
-  /@typescript-eslint/types@6.14.0:
-    resolution: {integrity: sha512-uty9H2K4Xs8E47z3SnXEPRNDfsis8JO27amp2GNCnzGETEW3yTqEIVg5+AI7U276oGF/tw6ZA+UesxeQ104ceA==}
+  /@typescript-eslint/types@7.1.0:
+    resolution: {integrity: sha512-qTWjWieJ1tRJkxgZYXx6WUYtWlBc48YRxgY2JN1aGeVpkhmnopq+SUC8UEVGNXIvWH7XyuTjwALfG6bFEgCkQA==}
     engines: {node: ^16.0.0 || >=18.0.0}
     dev: true
 
@@ -8502,8 +8392,8 @@ packages:
       - supports-color
     dev: true
 
-  /@typescript-eslint/typescript-estree@6.12.0(typescript@5.1.6):
-    resolution: {integrity: sha512-vw9E2P9+3UUWzhgjyyVczLWxZ3GuQNT7QpnIY3o5OMeLO/c8oHljGc8ZpryBMIyympiAAaKgw9e5Hl9dCWFOYw==}
+  /@typescript-eslint/typescript-estree@6.21.0(typescript@5.1.6):
+    resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==}
     engines: {node: ^16.0.0 || >=18.0.0}
     peerDependencies:
       typescript: '*'
@@ -8511,20 +8401,21 @@ packages:
       typescript:
         optional: true
     dependencies:
-      '@typescript-eslint/types': 6.12.0
-      '@typescript-eslint/visitor-keys': 6.12.0
+      '@typescript-eslint/types': 6.21.0
+      '@typescript-eslint/visitor-keys': 6.21.0
       debug: 4.3.4(supports-color@8.1.1)
       globby: 11.1.0
       is-glob: 4.0.3
-      semver: 7.5.4
+      minimatch: 9.0.3
+      semver: 7.6.0
       ts-api-utils: 1.0.3(typescript@5.1.6)
       typescript: 5.1.6
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /@typescript-eslint/typescript-estree@6.14.0(typescript@5.3.3):
-    resolution: {integrity: sha512-yPkaLwK0yH2mZKFE/bXkPAkkFgOv15GJAUzgUVonAbv0Hr4PK/N2yaA/4XQbTZQdygiDkpt5DkxPELqHguNvyw==}
+  /@typescript-eslint/typescript-estree@6.21.0(typescript@5.3.3):
+    resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==}
     engines: {node: ^16.0.0 || >=18.0.0}
     peerDependencies:
       typescript: '*'
@@ -8532,12 +8423,35 @@ packages:
       typescript:
         optional: true
     dependencies:
-      '@typescript-eslint/types': 6.14.0
-      '@typescript-eslint/visitor-keys': 6.14.0
+      '@typescript-eslint/types': 6.21.0
+      '@typescript-eslint/visitor-keys': 6.21.0
       debug: 4.3.4(supports-color@8.1.1)
       globby: 11.1.0
       is-glob: 4.0.3
-      semver: 7.5.4
+      minimatch: 9.0.3
+      semver: 7.6.0
+      ts-api-utils: 1.0.3(typescript@5.3.3)
+      typescript: 5.3.3
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /@typescript-eslint/typescript-estree@7.1.0(typescript@5.3.3):
+    resolution: {integrity: sha512-k7MyrbD6E463CBbSpcOnwa8oXRdHzH1WiVzOipK3L5KSML92ZKgUBrTlehdi7PEIMT8k0bQixHUGXggPAlKnOQ==}
+    engines: {node: ^16.0.0 || >=18.0.0}
+    peerDependencies:
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@typescript-eslint/types': 7.1.0
+      '@typescript-eslint/visitor-keys': 7.1.0
+      debug: 4.3.4(supports-color@8.1.1)
+      globby: 11.1.0
+      is-glob: 4.0.3
+      minimatch: 9.0.3
+      semver: 7.6.0
       ts-api-utils: 1.0.3(typescript@5.3.3)
       typescript: 5.3.3
     transitivePeerDependencies:
@@ -8552,7 +8466,7 @@ packages:
     dependencies:
       '@eslint-community/eslint-utils': 4.4.0(eslint@8.53.0)
       '@types/json-schema': 7.0.15
-      '@types/semver': 7.5.6
+      '@types/semver': 7.5.8
       '@typescript-eslint/scope-manager': 6.11.0
       '@typescript-eslint/types': 6.11.0
       '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.3.3)
@@ -8563,39 +8477,58 @@ packages:
       - typescript
     dev: true
 
-  /@typescript-eslint/utils@6.12.0(eslint@8.54.0)(typescript@5.1.6):
-    resolution: {integrity: sha512-LywPm8h3tGEbgfyjYnu3dauZ0U7R60m+miXgKcZS8c7QALO9uWJdvNoP+duKTk2XMWc7/Q3d/QiCuLN9X6SWyQ==}
+  /@typescript-eslint/utils@6.21.0(eslint@8.54.0)(typescript@5.1.6):
+    resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==}
     engines: {node: ^16.0.0 || >=18.0.0}
     peerDependencies:
       eslint: ^7.0.0 || ^8.0.0
     dependencies:
       '@eslint-community/eslint-utils': 4.4.0(eslint@8.54.0)
       '@types/json-schema': 7.0.15
-      '@types/semver': 7.5.6
-      '@typescript-eslint/scope-manager': 6.12.0
-      '@typescript-eslint/types': 6.12.0
-      '@typescript-eslint/typescript-estree': 6.12.0(typescript@5.1.6)
+      '@types/semver': 7.5.8
+      '@typescript-eslint/scope-manager': 6.21.0
+      '@typescript-eslint/types': 6.21.0
+      '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.1.6)
       eslint: 8.54.0
-      semver: 7.5.4
+      semver: 7.6.0
     transitivePeerDependencies:
       - supports-color
       - typescript
     dev: true
 
-  /@typescript-eslint/utils@6.14.0(eslint@8.56.0)(typescript@5.3.3):
-    resolution: {integrity: sha512-XwRTnbvRr7Ey9a1NT6jqdKX8y/atWG+8fAIu3z73HSP8h06i3r/ClMhmaF/RGWGW1tHJEwij1uEg2GbEmPYvYg==}
+  /@typescript-eslint/utils@6.21.0(eslint@8.57.0)(typescript@5.3.3):
+    resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==}
     engines: {node: ^16.0.0 || >=18.0.0}
     peerDependencies:
       eslint: ^7.0.0 || ^8.0.0
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0)
+      '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
       '@types/json-schema': 7.0.15
-      '@types/semver': 7.5.6
-      '@typescript-eslint/scope-manager': 6.14.0
-      '@typescript-eslint/types': 6.14.0
-      '@typescript-eslint/typescript-estree': 6.14.0(typescript@5.3.3)
-      eslint: 8.56.0
-      semver: 7.5.4
+      '@types/semver': 7.5.8
+      '@typescript-eslint/scope-manager': 6.21.0
+      '@typescript-eslint/types': 6.21.0
+      '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3)
+      eslint: 8.57.0
+      semver: 7.6.0
+    transitivePeerDependencies:
+      - supports-color
+      - typescript
+    dev: true
+
+  /@typescript-eslint/utils@7.1.0(eslint@8.57.0)(typescript@5.3.3):
+    resolution: {integrity: sha512-WUFba6PZC5OCGEmbweGpnNJytJiLG7ZvDBJJoUcX4qZYf1mGZ97mO2Mps6O2efxJcJdRNpqweCistDbZMwIVHw==}
+    engines: {node: ^16.0.0 || >=18.0.0}
+    peerDependencies:
+      eslint: ^8.56.0
+    dependencies:
+      '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
+      '@types/json-schema': 7.0.15
+      '@types/semver': 7.5.8
+      '@typescript-eslint/scope-manager': 7.1.0
+      '@typescript-eslint/types': 7.1.0
+      '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3)
+      eslint: 8.57.0
+      semver: 7.6.0
     transitivePeerDependencies:
       - supports-color
       - typescript
@@ -8609,19 +8542,19 @@ packages:
       eslint-visitor-keys: 3.4.3
     dev: true
 
-  /@typescript-eslint/visitor-keys@6.12.0:
-    resolution: {integrity: sha512-rg3BizTZHF1k3ipn8gfrzDXXSFKyOEB5zxYXInQ6z0hUvmQlhaZQzK+YmHmNViMA9HzW5Q9+bPPt90bU6GQwyw==}
+  /@typescript-eslint/visitor-keys@6.21.0:
+    resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==}
     engines: {node: ^16.0.0 || >=18.0.0}
     dependencies:
-      '@typescript-eslint/types': 6.12.0
+      '@typescript-eslint/types': 6.21.0
       eslint-visitor-keys: 3.4.3
     dev: true
 
-  /@typescript-eslint/visitor-keys@6.14.0:
-    resolution: {integrity: sha512-fB5cw6GRhJUz03MrROVuj5Zm/Q+XWlVdIsFj+Zb1Hvqouc8t+XP2H5y53QYU/MGtd2dPg6/vJJlhoX3xc2ehfw==}
+  /@typescript-eslint/visitor-keys@7.1.0:
+    resolution: {integrity: sha512-FhUqNWluiGNzlvnDZiXad4mZRhtghdoKW6e98GoEOYSu5cND+E39rG5KwJMUzeENwm1ztYBRqof8wMLP+wNPIA==}
     engines: {node: ^16.0.0 || >=18.0.0}
     dependencies:
-      '@typescript-eslint/types': 6.14.0
+      '@typescript-eslint/types': 7.1.0
       eslint-visitor-keys: 3.4.3
     dev: true
 
@@ -8629,31 +8562,16 @@ packages:
     resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
     dev: true
 
-  /@vitejs/plugin-react@3.1.0(vite@5.0.10):
-    resolution: {integrity: sha512-AfgcRL8ZBhAlc3BFdigClmTUMISmmzHn7sB2h9U1odvc5U/MjWXsAaz18b/WoppUTDBzxOJwo2VdClfUcItu9g==}
-    engines: {node: ^14.18.0 || >=16.0.0}
+  /@vitejs/plugin-vue@5.0.4(vite@5.1.4)(vue@3.4.21):
+    resolution: {integrity: sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==}
+    engines: {node: ^18.0.0 || >=20.0.0}
     peerDependencies:
-      vite: ^4.1.0-beta.0
-    dependencies:
-      '@babel/core': 7.23.3
-      '@babel/plugin-transform-react-jsx-self': 7.21.0(@babel/core@7.23.3)
-      '@babel/plugin-transform-react-jsx-source': 7.19.6(@babel/core@7.23.3)
-      magic-string: 0.27.0
-      react-refresh: 0.14.0
-      vite: 5.0.10(@types/node@20.10.5)(sass@1.69.5)(terser@5.26.0)
-    transitivePeerDependencies:
-      - supports-color
-    dev: true
-
-  /@vitejs/plugin-vue@4.5.2(vite@5.0.10)(vue@3.3.12):
-    resolution: {integrity: sha512-UGR3DlzLi/SaVBPX0cnSyE37vqxU3O6chn8l0HJNzQzDia6/Au2A4xKv+iIJW8w2daf80G7TYHhi1pAUjdZ0bQ==}
-    engines: {node: ^14.18.0 || >=16.0.0}
-    peerDependencies:
-      vite: ^4.0.0 || ^5.0.0
+      vite: ^5.0.0
       vue: ^3.2.25
     dependencies:
-      vite: 5.0.10(@types/node@20.10.5)(sass@1.69.5)(terser@5.26.0)
-      vue: 3.3.12(typescript@5.3.3)
+      vite: 5.1.4(@types/node@20.11.22)(sass@1.71.1)(terser@5.28.1)
+      vue: 3.4.21(typescript@5.3.3)
+    dev: false
 
   /@vitest/coverage-v8@0.34.6(vitest@0.34.6):
     resolution: {integrity: sha512-fivy/OK2d/EsJFoEoxHFEnNGTg+MmdZBAVK9Ka4qhXR2K3J0DS08vcGVwzDtXSuUMabLv4KtPcpSKkcMXFDViw==}
@@ -8662,16 +8580,16 @@ packages:
     dependencies:
       '@ampproject/remapping': 2.2.1
       '@bcoe/v8-coverage': 0.2.3
-      istanbul-lib-coverage: 3.2.0
+      istanbul-lib-coverage: 3.2.2
       istanbul-lib-report: 3.0.1
       istanbul-lib-source-maps: 4.0.1
-      istanbul-reports: 3.1.5
-      magic-string: 0.30.3
+      istanbul-reports: 3.1.6
+      magic-string: 0.30.5
       picocolors: 1.0.0
-      std-env: 3.3.3
+      std-env: 3.7.0
       test-exclude: 6.0.0
-      v8-to-istanbul: 9.1.0
-      vitest: 0.34.6(happy-dom@10.0.3)(sass@1.69.5)(terser@5.26.0)
+      v8-to-istanbul: 9.2.0
+      vitest: 0.34.6(happy-dom@13.6.2)(sass@1.71.1)(terser@5.28.1)
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -8684,33 +8602,62 @@ packages:
       chai: 4.3.10
     dev: true
 
+  /@vitest/expect@1.1.3:
+    resolution: {integrity: sha512-MnJqsKc1Ko04lksF9XoRJza0bGGwTtqfbyrsYv5on4rcEkdo+QgUdITenBQBUltKzdxW7K3rWh+nXRULwsdaVg==}
+    dependencies:
+      '@vitest/spy': 1.1.3
+      '@vitest/utils': 1.1.3
+      chai: 4.3.10
+    dev: true
+
   /@vitest/runner@0.34.6:
     resolution: {integrity: sha512-1CUQgtJSLF47NnhN+F9X2ycxUP0kLHQ/JWvNHbeBfwW8CzEGgeskzNnHDyv1ieKTltuR6sdIHV+nmR6kPxQqzQ==}
     dependencies:
       '@vitest/utils': 0.34.6
       p-limit: 4.0.0
-      pathe: 1.1.1
+      pathe: 1.1.2
     dev: true
 
   /@vitest/snapshot@0.34.6:
     resolution: {integrity: sha512-B3OZqYn6k4VaN011D+ve+AA4whM4QkcwcrwaKwAbyyvS/NB1hCWjFIBQxAQQSQir9/RtyAAGuq+4RJmbn2dH4w==}
     dependencies:
       magic-string: 0.30.5
-      pathe: 1.1.1
+      pathe: 1.1.2
       pretty-format: 29.7.0
     dev: true
 
   /@vitest/spy@0.34.6:
     resolution: {integrity: sha512-xaCvneSaeBw/cz8ySmF7ZwGvL0lBjfvqc1LpQ/vcdHEvpLn3Ff1vAvjw+CoGn0802l++5L/pxb7whwcWAw+DUQ==}
     dependencies:
-      tinyspy: 2.1.1
+      tinyspy: 2.2.0
+    dev: true
+
+  /@vitest/spy@1.1.3:
+    resolution: {integrity: sha512-Ec0qWyGS5LhATFQtldvChPTAHv08yHIOZfiNcjwRQbFPHpkih0md9KAbs7TfeIfL7OFKoe7B/6ukBTqByubXkQ==}
+    dependencies:
+      tinyspy: 2.2.0
+    dev: true
+
+  /@vitest/spy@1.3.0:
+    resolution: {integrity: sha512-AkCU0ThZunMvblDpPKgjIi025UxR8V7MZ/g/EwmAGpjIujLVV2X6rGYGmxE2D4FJbAy0/ijdROHMWa2M/6JVMw==}
+    dependencies:
+      tinyspy: 2.2.0
     dev: true
 
   /@vitest/utils@0.34.6:
     resolution: {integrity: sha512-IG5aDD8S6zlvloDsnzHw0Ut5xczlF+kv2BOTo+iXfPr54Yhi5qbVOgGB1hZaVq4iJ4C/MZ2J0y15IlsV/ZcI0A==}
     dependencies:
       diff-sequences: 29.6.3
-      loupe: 2.3.6
+      loupe: 2.3.7
+      pretty-format: 29.7.0
+    dev: true
+
+  /@vitest/utils@1.1.3:
+    resolution: {integrity: sha512-Dyt3UMcdElTll2H75vhxfpZu03uFpXRCHxWnzcrFjZxT1kTbq8ALUYIeBgGolo1gldVdI0YSlQRacsqxTwNqwg==}
+    dependencies:
+      diff-sequences: 29.6.3
+      estree-walker: 3.0.3
+      loupe: 2.3.7
       pretty-format: 29.7.0
     dev: true
 
@@ -8733,58 +8680,59 @@ packages:
       path-browserify: 1.0.1
     dev: true
 
-  /@vue/compiler-core@3.3.12:
-    resolution: {integrity: sha512-qAtjyG3GBLG0chzp5xGCyRLLe6wFCHmjI82aGzwuGKyznNP+GJJMxjc0wOYWDB2YKfho7niJFdoFpo0CZZQg9w==}
+  /@vue/compiler-core@3.4.18:
+    resolution: {integrity: sha512-F7YK8lMK0iv6b9/Gdk15A67wM0KKZvxDxed0RR60C1z9tIJTKta+urs4j0RTN5XqHISzI3etN3mX0uHhjmoqjQ==}
     dependencies:
-      '@babel/parser': 7.23.6
-      '@vue/shared': 3.3.12
-      estree-walker: 2.0.2
-      source-map-js: 1.0.2
-
-  /@vue/compiler-core@3.3.8:
-    resolution: {integrity: sha512-hN/NNBUECw8SusQvDSqqcVv6gWq8L6iAktUR0UF3vGu2OhzRqcOiAno0FmBJWwxhYEXRlQJT5XnoKsVq1WZx4g==}
-    dependencies:
-      '@babel/parser': 7.23.4
-      '@vue/shared': 3.3.8
+      '@babel/parser': 7.23.9
+      '@vue/shared': 3.4.18
+      entities: 4.5.0
       estree-walker: 2.0.2
       source-map-js: 1.0.2
     dev: true
 
-  /@vue/compiler-dom@3.3.12:
-    resolution: {integrity: sha512-RdJU9oEYaoPKUdGXCy0l+i4clesdDeLmbvRlszoc9iagsnBnMmQtYfCPVQ5BHB6o7K4SCucDdJM2Dh3oXB0D6g==}
+  /@vue/compiler-core@3.4.21:
+    resolution: {integrity: sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==}
     dependencies:
-      '@vue/compiler-core': 3.3.12
-      '@vue/shared': 3.3.12
-
-  /@vue/compiler-dom@3.3.8:
-    resolution: {integrity: sha512-+PPtv+p/nWDd0AvJu3w8HS0RIm/C6VGBIRe24b9hSyNWOAPEUosFZ5diwawwP8ip5sJ8n0Pe87TNNNHnvjs0FQ==}
-    dependencies:
-      '@vue/compiler-core': 3.3.8
-      '@vue/shared': 3.3.8
-    dev: true
-
-  /@vue/compiler-sfc@3.3.12:
-    resolution: {integrity: sha512-yy5b9e7b79dsGbMmglCe/YnhCQgBkHO7Uf6JfjWPSf2/5XH+MKn18LhzhHyxbHdJgnA4lZCqtXzLaJz8Pd8lMw==}
-    dependencies:
-      '@babel/parser': 7.23.6
-      '@vue/compiler-core': 3.3.12
-      '@vue/compiler-dom': 3.3.12
-      '@vue/compiler-ssr': 3.3.12
-      '@vue/reactivity-transform': 3.3.12
-      '@vue/shared': 3.3.12
+      '@babel/parser': 7.23.9
+      '@vue/shared': 3.4.21
+      entities: 4.5.0
       estree-walker: 2.0.2
-      magic-string: 0.30.5
-      postcss: 8.4.32
       source-map-js: 1.0.2
 
-  /@vue/compiler-ssr@3.3.12:
-    resolution: {integrity: sha512-adCiMJPznfWcQyk/9HSuXGja859IaMV+b8UNSVzDatqv7h0PvT9BEeS22+gjkWofDiSg5d78/ZLls3sLA+cn3A==}
+  /@vue/compiler-dom@3.4.18:
+    resolution: {integrity: sha512-24Eb8lcMfInefvQ6YlEVS18w5Q66f4+uXWVA+yb7praKbyjHRNuKVWGuinfSSjM0ZIiPi++QWukhkgznBaqpEA==}
     dependencies:
-      '@vue/compiler-dom': 3.3.12
-      '@vue/shared': 3.3.12
+      '@vue/compiler-core': 3.4.18
+      '@vue/shared': 3.4.18
+    dev: true
 
-  /@vue/language-core@1.8.25(typescript@5.3.3):
-    resolution: {integrity: sha512-NJk/5DnAZlpvXX8BdWmHI45bWGLViUaS3R/RMrmFSvFMSbJKuEODpM4kR0F0Ofv5SFzCWuNiMhxameWpVdQsnA==}
+  /@vue/compiler-dom@3.4.21:
+    resolution: {integrity: sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==}
+    dependencies:
+      '@vue/compiler-core': 3.4.21
+      '@vue/shared': 3.4.21
+
+  /@vue/compiler-sfc@3.4.21:
+    resolution: {integrity: sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==}
+    dependencies:
+      '@babel/parser': 7.23.9
+      '@vue/compiler-core': 3.4.21
+      '@vue/compiler-dom': 3.4.21
+      '@vue/compiler-ssr': 3.4.21
+      '@vue/shared': 3.4.21
+      estree-walker: 2.0.2
+      magic-string: 0.30.7
+      postcss: 8.4.35
+      source-map-js: 1.0.2
+
+  /@vue/compiler-ssr@3.4.21:
+    resolution: {integrity: sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==}
+    dependencies:
+      '@vue/compiler-dom': 3.4.21
+      '@vue/shared': 3.4.21
+
+  /@vue/language-core@1.8.27(typescript@5.3.3):
+    resolution: {integrity: sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==}
     peerDependencies:
       typescript: '*'
     peerDependenciesMeta:
@@ -8793,8 +8741,8 @@ packages:
     dependencies:
       '@volar/language-core': 1.11.1
       '@volar/source-map': 1.11.1
-      '@vue/compiler-dom': 3.3.8
-      '@vue/shared': 3.3.8
+      '@vue/compiler-dom': 3.4.18
+      '@vue/shared': 3.4.18
       computeds: 0.0.1
       minimatch: 9.0.3
       muggle-string: 0.3.1
@@ -8803,50 +8751,41 @@ packages:
       vue-template-compiler: 2.7.14
     dev: true
 
-  /@vue/reactivity-transform@3.3.12:
-    resolution: {integrity: sha512-g5TijmML7FyKkLt6QnpqNmA4KD7K/T5SbXa88Bhq+hydNQEkzA8veVXWAQuNqg9rjaFYD0rPf0a9NofKA0ENgg==}
+  /@vue/reactivity@3.4.21:
+    resolution: {integrity: sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw==}
     dependencies:
-      '@babel/parser': 7.23.6
-      '@vue/compiler-core': 3.3.12
-      '@vue/shared': 3.3.12
-      estree-walker: 2.0.2
-      magic-string: 0.30.5
+      '@vue/shared': 3.4.21
 
-  /@vue/reactivity@3.3.12:
-    resolution: {integrity: sha512-vOJORzO8DlIx88cgTnMLIf2GlLYpoXAKsuoQsK6SGdaqODjxO129pVPTd2s/N/Mb6KKZEFIHIEwWGmtN4YPs+g==}
+  /@vue/runtime-core@3.4.21:
+    resolution: {integrity: sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA==}
     dependencies:
-      '@vue/shared': 3.3.12
+      '@vue/reactivity': 3.4.21
+      '@vue/shared': 3.4.21
 
-  /@vue/runtime-core@3.3.12:
-    resolution: {integrity: sha512-5iL4w7MZrSGKEZU2wFAYhDZdZmgn+s//73EfgDXW1M+ZUOl36md7tlWp1QFK/ladiq4FvQ82shVjo0KiPDPr0A==}
+  /@vue/runtime-dom@3.4.21:
+    resolution: {integrity: sha512-gvf+C9cFpevsQxbkRBS1NpU8CqxKw0ebqMvLwcGQrNpx6gqRDodqKqA+A2VZZpQ9RpK2f9yfg8VbW/EpdFUOJw==}
     dependencies:
-      '@vue/reactivity': 3.3.12
-      '@vue/shared': 3.3.12
-
-  /@vue/runtime-dom@3.3.12:
-    resolution: {integrity: sha512-8mMzqiIdl+IYa/OXwKwk6/4ebLq7cYV1pUcwCSwBK2KerUa6cwGosen5xrCL9f8o2DJ9TfPFwbPEvH7OXzUpoA==}
-    dependencies:
-      '@vue/runtime-core': 3.3.12
-      '@vue/shared': 3.3.12
+      '@vue/runtime-core': 3.4.21
+      '@vue/shared': 3.4.21
       csstype: 3.1.3
 
-  /@vue/server-renderer@3.3.12(vue@3.3.12):
-    resolution: {integrity: sha512-OZ0IEK5TU5GXb5J8/wSplyxvGGdIcwEmS8EIO302Vz8K6fGSgSJTU54X0Sb6PaefzZdiN3vHsLXO8XIeF8crQQ==}
+  /@vue/server-renderer@3.4.21(vue@3.4.21):
+    resolution: {integrity: sha512-aV1gXyKSN6Rz+6kZ6kr5+Ll14YzmIbeuWe7ryJl5muJ4uwSwY/aStXTixx76TwkZFJLm1aAlA/HSWEJ4EyiMkg==}
     peerDependencies:
-      vue: 3.3.12
+      vue: 3.4.21
     dependencies:
-      '@vue/compiler-ssr': 3.3.12
-      '@vue/shared': 3.3.12
-      vue: 3.3.12(typescript@5.3.3)
+      '@vue/compiler-ssr': 3.4.21
+      '@vue/shared': 3.4.21
+      vue: 3.4.21(typescript@5.3.3)
 
-  /@vue/shared@3.3.12:
-    resolution: {integrity: sha512-6p0Yin0pclvnER7BLNOQuod9Z+cxSYh8pSh7CzHnWNjAIP6zrTlCdHRvSCb1aYEx6i3Q3kvfuWU7nG16CgG1ag==}
-
-  /@vue/shared@3.3.8:
-    resolution: {integrity: sha512-8PGwybFwM4x8pcfgqEQFy70NaQxASvOC5DJwLQfpArw1UDfUXrJkdxD3BhVTMS+0Lef/TU7YO0Jvr0jJY8T+mw==}
+  /@vue/shared@3.4.18:
+    resolution: {integrity: sha512-CxouGFxxaW5r1WbrSmWwck3No58rApXgRSBxrqgnY1K+jk20F6DrXJkHdH9n4HVT+/B6G2CAn213Uq3npWiy8Q==}
     dev: true
 
-  /@vue/test-utils@2.4.1(vue@3.3.12):
+  /@vue/shared@3.4.21:
+    resolution: {integrity: sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g==}
+
+  /@vue/test-utils@2.4.1(vue@3.4.21):
     resolution: {integrity: sha512-VO8nragneNzUZUah6kOjiFmD/gwRjUauG9DROh6oaOeFwX1cZRUNHhdeogE8635cISigXFTtGLUQWx5KCb0xeg==}
     peerDependencies:
       '@vue/server-renderer': ^3.0.1
@@ -8856,22 +8795,17 @@ packages:
         optional: true
     dependencies:
       js-beautify: 1.14.9
-      vue: 3.3.12(typescript@5.3.3)
+      vue: 3.4.21(typescript@5.3.3)
       vue-component-type-helpers: 1.8.4
     dev: true
 
-  /@xmldom/xmldom@0.8.6:
-    resolution: {integrity: sha512-uRjjusqpoqfmRkTaNuLJ2VohVr67Q5YwDATW3VU7PfzTj6IRaihGrYI7zckGZjxQPBIp63nfvJbM+Yu5ICh0Bg==}
-    engines: {node: '>=10.0.0'}
-    dev: true
-
-  /@yarnpkg/esbuild-plugin-pnp@3.0.0-rc.15(esbuild@0.18.17):
+  /@yarnpkg/esbuild-plugin-pnp@3.0.0-rc.15(esbuild@0.18.20):
     resolution: {integrity: sha512-kYzDJO5CA9sy+on/s2aIW0411AklfCi8Ck/4QDivOqsMKpStZA2SsR+X27VTggGwpStWaLrjJcDcdDMowtG8MA==}
     engines: {node: '>=14.15.0'}
     peerDependencies:
       esbuild: '>=0.10.0'
     dependencies:
-      esbuild: 0.18.17
+      esbuild: 0.18.20
       tslib: 2.6.2
     dev: true
 
@@ -8891,14 +8825,9 @@ packages:
       tslib: 1.14.1
     dev: true
 
-  /@zxing/text-encoding@0.9.0:
-    resolution: {integrity: sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==}
-    requiresBuild: true
-    dev: true
-    optional: true
-
   /abbrev@1.1.1:
     resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==}
+    dev: true
 
   /abbrev@2.0.0:
     resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==}
@@ -8931,12 +8860,12 @@ packages:
       acorn: 7.4.1
     dev: true
 
-  /acorn-jsx@5.3.2(acorn@8.11.2):
+  /acorn-jsx@5.3.2(acorn@8.11.3):
     resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
     peerDependencies:
       acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
     dependencies:
-      acorn: 8.11.2
+      acorn: 8.11.3
     dev: true
 
   /acorn-walk@7.2.0:
@@ -8944,8 +8873,8 @@ packages:
     engines: {node: '>=0.4.0'}
     dev: true
 
-  /acorn-walk@8.2.0:
-    resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==}
+  /acorn-walk@8.3.2:
+    resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==}
     engines: {node: '>=0.4.0'}
     dev: true
 
@@ -8954,8 +8883,8 @@ packages:
     engines: {node: '>=0.4.0'}
     hasBin: true
 
-  /acorn@8.11.2:
-    resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==}
+  /acorn@8.11.3:
+    resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==}
     engines: {node: '>=0.4.0'}
     hasBin: true
 
@@ -8964,11 +8893,6 @@ packages:
     engines: {node: '>= 10.0.0'}
     dev: true
 
-  /agent-base@5.1.1:
-    resolution: {integrity: sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==}
-    engines: {node: '>= 6.0.0'}
-    dev: true
-
   /agent-base@6.0.2:
     resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
     engines: {node: '>= 6.0.0'}
@@ -8976,6 +8900,7 @@ packages:
       debug: 4.3.4(supports-color@8.1.1)
     transitivePeerDependencies:
       - supports-color
+    dev: true
 
   /agent-base@7.1.0:
     resolution: {integrity: sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==}
@@ -8993,6 +8918,14 @@ packages:
       clean-stack: 2.2.0
       indent-string: 4.0.0
 
+  /aggregate-error@5.0.0:
+    resolution: {integrity: sha512-gOsf2YwSlleG6IjRYG2A7k0HmBMEo6qVNk9Bp/EaLgAJT5ngH6PXbqa4ItvnEwCm/velL5jAnQgsHsWnjhGmvw==}
+    engines: {node: '>=18'}
+    dependencies:
+      clean-stack: 5.2.0
+      indent-string: 5.0.0
+    dev: true
+
   /ajv-draft-04@1.0.0(ajv@8.12.0):
     resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==}
     peerDependencies:
@@ -9054,6 +8987,7 @@ packages:
 
   /ansi-sequence-parser@1.1.1:
     resolution: {integrity: sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==}
+    dev: true
 
   /ansi-styles@3.2.1:
     resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
@@ -9097,11 +9031,6 @@ packages:
 
   /append-field@1.0.0:
     resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==}
-    dev: false
-
-  /aproba@2.0.0:
-    resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==}
-    dev: false
 
   /arch@2.2.0:
     resolution: {integrity: sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==}
@@ -9135,30 +9064,18 @@ packages:
     resolution: {integrity: sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==}
     dev: false
 
-  /are-we-there-yet@2.0.0:
-    resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==}
-    engines: {node: '>=10'}
-    requiresBuild: true
-    dependencies:
-      delegates: 1.0.0
-      readable-stream: 3.6.2
-    dev: false
-
   /arg@5.0.2:
     resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
     dev: true
 
-  /argon2@0.31.1:
-    resolution: {integrity: sha512-ik2xnJrLXazya7m4Nz1XfBSRjXj8Koq8qF9PsQC8059p20ifWc9zx/hgU3ItZh/3TnwXkv0RbhvjodPkmFf0bg==}
-    engines: {node: '>=14.0.0'}
+  /argon2@0.40.1:
+    resolution: {integrity: sha512-DjtHDwd7pm12qeWyfihHoM8Bn5vGcgH6sKwgPqwNYroRmxlrzadHEvMyuvQxN/V8YSyRRKD5x6ito09q1e9OyA==}
+    engines: {node: '>=16.17.0'}
     requiresBuild: true
     dependencies:
-      '@mapbox/node-pre-gyp': 1.0.11
       '@phc/format': 1.0.0
-      node-addon-api: 7.0.0
-    transitivePeerDependencies:
-      - encoding
-      - supports-color
+      node-addon-api: 7.1.0
+      node-gyp-build: 4.8.0
     dev: false
 
   /argparse@1.0.10:
@@ -9170,13 +9087,6 @@ packages:
   /argparse@2.0.1:
     resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
 
-  /aria-hidden@1.2.3:
-    resolution: {integrity: sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==}
-    engines: {node: '>=10'}
-    dependencies:
-      tslib: 2.6.2
-    dev: true
-
   /aria-query@5.1.3:
     resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==}
     dependencies:
@@ -9289,15 +9199,6 @@ packages:
     resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==}
     engines: {node: '>=0.8'}
 
-  /assert@2.0.0:
-    resolution: {integrity: sha512-se5Cd+js9dXJnu6Ag2JFc00t+HmHOen+8Q+L7O9zI0PqQXr20uk2J0XQqMxZEeo5U50o8Nvmmx7dZrl+Ufr35A==}
-    dependencies:
-      es6-object-assign: 1.1.0
-      is-nan: 1.3.2
-      object-is: 1.1.5
-      util: 0.12.5
-    dev: true
-
   /assert@2.1.0:
     resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==}
     dependencies:
@@ -9312,20 +9213,6 @@ packages:
     resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
     dev: true
 
-  /ast-types@0.14.2:
-    resolution: {integrity: sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==}
-    engines: {node: '>=4'}
-    dependencies:
-      tslib: 2.6.2
-    dev: true
-
-  /ast-types@0.15.2:
-    resolution: {integrity: sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==}
-    engines: {node: '>=4'}
-    dependencies:
-      tslib: 2.6.2
-    dev: true
-
   /ast-types@0.16.1:
     resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==}
     engines: {node: '>=4'}
@@ -9343,14 +9230,10 @@ packages:
     hasBin: true
     dev: false
 
-  /async-limiter@1.0.1:
-    resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==}
-    dev: true
-
-  /async-mutex@0.4.0:
-    resolution: {integrity: sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==}
+  /async-mutex@0.4.1:
+    resolution: {integrity: sha512-WfoBo4E/TbCX1G95XTjbWTE3X2XLG0m1Xbv2cwOtuPdyH9CZvnaA5nCt1ucjaKEgW2A5IF71hxrRhr83Je5xjA==}
     dependencies:
-      tslib: 2.6.1
+      tslib: 2.6.2
     dev: false
 
   /async@3.2.4:
@@ -9384,12 +9267,12 @@ packages:
       - supports-color
     dev: false
 
-  /aws-sdk-client-mock@3.0.0:
-    resolution: {integrity: sha512-4mBiWhuLYLZe1+K/iB8eYy5SAZyW2se+Keyh5u9QouMt6/qJ5SRZhss68xvUX5g3ApzROJ06QPRziYHP6buuvQ==}
+  /aws-sdk-client-mock@3.0.1:
+    resolution: {integrity: sha512-9VAzJLl8mz99KP9HjOm/93d8vznRRUTpJooPBOunRdUAnVYopCe9xmMuu7eVemu8fQ+w6rP7o5bBK1kAFkB2KQ==}
     dependencies:
       '@types/sinon': 10.0.13
-      sinon: 14.0.2
-      tslib: 2.5.3
+      sinon: 16.1.3
+      tslib: 2.6.2
     dev: true
 
   /aws-sign2@0.7.0:
@@ -9430,13 +9313,14 @@ packages:
 
   /b4a@1.6.4:
     resolution: {integrity: sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==}
+    dev: false
 
-  /babel-core@7.0.0-bridge.0(@babel/core@7.23.3):
+  /babel-core@7.0.0-bridge.0(@babel/core@7.24.0):
     resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==}
     peerDependencies:
       '@babel/core': ^7.0.0-0
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
     dev: true
 
   /babel-jest@29.7.0(@babel/core@7.23.3):
@@ -9474,44 +9358,44 @@ packages:
     resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
-      '@babel/template': 7.22.15
-      '@babel/types': 7.23.4
+      '@babel/template': 7.24.0
+      '@babel/types': 7.24.0
       '@types/babel__core': 7.20.5
       '@types/babel__traverse': 7.20.4
     dev: true
 
-  /babel-plugin-polyfill-corejs2@0.4.7(@babel/core@7.23.3):
+  /babel-plugin-polyfill-corejs2@0.4.7(@babel/core@7.24.0):
     resolution: {integrity: sha512-LidDk/tEGDfuHW2DWh/Hgo4rmnw3cduK6ZkOI1NPFceSK3n/yAGeOsNT7FLnSGHkXj3RHGSEVkN3FsCTY6w2CQ==}
     peerDependencies:
       '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
     dependencies:
       '@babel/compat-data': 7.23.5
-      '@babel/core': 7.23.3
-      '@babel/helper-define-polyfill-provider': 0.4.4(@babel/core@7.23.3)
+      '@babel/core': 7.24.0
+      '@babel/helper-define-polyfill-provider': 0.4.4(@babel/core@7.24.0)
       semver: 6.3.1
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /babel-plugin-polyfill-corejs3@0.8.7(@babel/core@7.23.3):
+  /babel-plugin-polyfill-corejs3@0.8.7(@babel/core@7.24.0):
     resolution: {integrity: sha512-KyDvZYxAzkC0Aj2dAPyDzi2Ym15e5JKZSK+maI7NAwSqofvuFglbSsxE7wUOvTg9oFVnHMzVzBKcqEb4PJgtOA==}
     peerDependencies:
       '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
     dependencies:
-      '@babel/core': 7.23.3
-      '@babel/helper-define-polyfill-provider': 0.4.4(@babel/core@7.23.3)
+      '@babel/core': 7.24.0
+      '@babel/helper-define-polyfill-provider': 0.4.4(@babel/core@7.24.0)
       core-js-compat: 3.34.0
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /babel-plugin-polyfill-regenerator@0.5.4(@babel/core@7.23.3):
+  /babel-plugin-polyfill-regenerator@0.5.4(@babel/core@7.24.0):
     resolution: {integrity: sha512-S/x2iOCvDaCASLYsOOgWOq4bCfKYVqvO/uxjkaYyZ3rVsVE3CeAI/c84NpyuBBymEgNvHgjEot3a9/Z/kXvqsg==}
     peerDependencies:
       '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0
     dependencies:
-      '@babel/core': 7.23.3
-      '@babel/helper-define-polyfill-provider': 0.4.4(@babel/core@7.23.3)
+      '@babel/core': 7.24.0
+      '@babel/helper-define-polyfill-provider': 0.4.4(@babel/core@7.24.0)
     transitivePeerDependencies:
       - supports-color
     dev: true
@@ -9551,7 +9435,11 @@ packages:
     resolution: {integrity: sha512-GAwkz0AihzY5bkwIY5QDR+LvsRQgB/B+1foMPvi0FZPMl5fjD7ICiznUiBdLYMH1QYe6vqu4gWYytZOccLouFw==}
     engines: {node: '>= 10.0.0'}
     dependencies:
-      '@babel/types': 7.22.17
+      '@babel/types': 7.24.0
+
+  /bail@2.0.2:
+    resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==}
+    dev: true
 
   /balanced-match@1.0.2:
     resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
@@ -9575,6 +9463,12 @@ packages:
       open: 8.4.2
     dev: true
 
+  /bidi-js@1.0.3:
+    resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
+    dependencies:
+      require-from-string: 2.0.2
+    dev: false
+
   /big-integer@1.6.51:
     resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==}
     engines: {node: '>=0.6'}
@@ -9615,6 +9509,7 @@ packages:
       buffer: 5.7.1
       inherits: 2.0.4
       readable-stream: 3.6.2
+    dev: true
 
   /blob-util@2.0.2:
     resolution: {integrity: sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==}
@@ -9669,7 +9564,6 @@ packages:
       unpipe: 1.0.0
     transitivePeerDependencies:
       - supports-color
-    dev: false
 
   /boolbase@1.0.0:
     resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==}
@@ -9727,21 +9621,32 @@ packages:
     hasBin: true
     dependencies:
       caniuse-lite: 1.0.30001564
-      electron-to-chromium: 1.4.591
+      electron-to-chromium: 1.4.686
       node-releases: 2.0.13
       update-browserslist-db: 1.0.13(browserslist@4.22.1)
+    dev: true
 
   /browserslist@4.22.2:
     resolution: {integrity: sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==}
     engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
     hasBin: true
     dependencies:
-      caniuse-lite: 1.0.30001571
-      electron-to-chromium: 1.4.616
+      caniuse-lite: 1.0.30001591
+      electron-to-chromium: 1.4.686
       node-releases: 2.0.14
       update-browserslist-db: 1.0.13(browserslist@4.22.2)
     dev: true
 
+  /browserslist@4.23.0:
+    resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==}
+    engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+    hasBin: true
+    dependencies:
+      caniuse-lite: 1.0.30001591
+      electron-to-chromium: 1.4.686
+      node-releases: 2.0.14
+      update-browserslist-db: 1.0.13(browserslist@4.23.0)
+
   /bs-logger@0.2.6:
     resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==}
     engines: {node: '>= 6'}
@@ -9782,6 +9687,7 @@ packages:
     dependencies:
       base64-js: 1.5.1
       ieee754: 1.2.1
+    dev: true
 
   /buffer@6.0.3:
     resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
@@ -9797,14 +9703,15 @@ packages:
     dependencies:
       node-gyp-build: 4.6.0
 
-  /bullmq@4.15.4:
-    resolution: {integrity: sha512-lrEtiERjYPPfb0OEXva5G8cF6kbtJFy/BmIL0yjmO+kj2eSVjCpAVVeYikxoqKtaYyEnNFal7ERVLanEvp+1Lw==}
+  /bullmq@5.4.0:
+    resolution: {integrity: sha512-QNOT+Vp/OFctwKa1/LYvrfIMXqb6Hu8a1VwXxQpa/JXoFAQ9E4ZcqW4fyEjx9iYrXakpV6cAGPbmdgWaKTGXOQ==}
     dependencies:
       cron-parser: 4.8.1
-      glob: 8.1.0
+      fast-glob: 3.3.2
       ioredis: 5.3.2
       lodash: 4.17.21
-      msgpackr: 1.9.2
+      minimatch: 9.0.3
+      msgpackr: 1.10.1
       node-abort-controller: 3.1.1
       semver: 7.5.4
       tslib: 2.6.2
@@ -9822,7 +9729,6 @@ packages:
     engines: {node: '>=10.16.0'}
     dependencies:
       streamsearch: 1.1.0
-    dev: false
 
   /bytes@3.0.0:
     resolution: {integrity: sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==}
@@ -9852,7 +9758,7 @@ packages:
       minipass-pipeline: 1.2.4
       p-map: 4.0.0
       ssri: 10.0.4
-      tar: 6.1.13
+      tar: 6.2.0
       unique-filename: 3.0.0
     dev: false
 
@@ -9931,58 +9837,41 @@ packages:
   /caniuse-api@3.0.0:
     resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==}
     dependencies:
-      browserslist: 4.22.1
-      caniuse-lite: 1.0.30001564
+      browserslist: 4.23.0
+      caniuse-lite: 1.0.30001591
       lodash.memoize: 4.1.2
       lodash.uniq: 4.5.0
     dev: false
 
   /caniuse-lite@1.0.30001564:
     resolution: {integrity: sha512-DqAOf+rhof+6GVx1y+xzbFPeOumfQnhYzVnZD6LAXijR77yPtm9mfOcqOnT3mpnJiZVT+kwLAFnRlZcIz+c6bg==}
-
-  /caniuse-lite@1.0.30001571:
-    resolution: {integrity: sha512-tYq/6MoXhdezDLFZuCO/TKboTzuQ/xR5cFdgXPfDtM7/kchBO3b4VWghE/OAi/DV7tTdhmLjZiZBZi1fA/GheQ==}
     dev: true
 
+  /caniuse-lite@1.0.30001591:
+    resolution: {integrity: sha512-PCzRMei/vXjJyL5mJtzNiUCKP59dm8Apqc3PH8gJkMnMXZGox93RbE76jHsmLwmIo6/3nsYIpJtx0O7u5PqFuQ==}
+
   /canonicalize@1.0.8:
     resolution: {integrity: sha512-0CNTVCLZggSh7bc5VkX5WWPWO+cyZbNd07IHIsSXLia/eAq+r836hgk+8BKoEh7949Mda87VUOitx5OddVj64A==}
     dev: false
 
-  /canvas-confetti@1.6.1:
-    resolution: {integrity: sha512-CgGR5DL9+dkne4AwcpvWQc0LIQq43yDIxlwdZcyrq3yklricNfuPHoOSoM6Ya7yCQ+sXmZ2iNV2feiKjVG8C1g==}
+  /canvas-confetti@1.9.2:
+    resolution: {integrity: sha512-6Xi7aHHzKwxZsem4mCKoqP6YwUG3HamaHHAlz1hTNQPCqXhARFpSXnkC9TWlahHY5CG6hSL5XexNjxK8irVErg==}
     dev: false
 
   /caseless@0.12.0:
     resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==}
     dev: true
 
-  /cbor-extract@2.1.1:
-    resolution: {integrity: sha512-1UX977+L+zOJHsp0mWFG13GLwO6ucKgSmSW6JTl8B9GUvACvHeIVpFqhU92299Z6PfD09aTXDell5p+lp1rUFA==}
-    hasBin: true
-    requiresBuild: true
-    dependencies:
-      node-gyp-build-optional-packages: 5.0.3
-    optionalDependencies:
-      '@cbor-extract/cbor-extract-darwin-arm64': 2.1.1
-      '@cbor-extract/cbor-extract-darwin-x64': 2.1.1
-      '@cbor-extract/cbor-extract-linux-arm': 2.1.1
-      '@cbor-extract/cbor-extract-linux-arm64': 2.1.1
-      '@cbor-extract/cbor-extract-linux-x64': 2.1.1
-      '@cbor-extract/cbor-extract-win32-x64': 2.1.1
-    dev: false
-    optional: true
-
-  /cbor-x@1.5.4:
-    resolution: {integrity: sha512-PVKILDn+Rf6MRhhcyzGXi5eizn1i0i3F8Fe6UMMxXBnWkalq9+C5+VTmlIjAYM4iF2IYF2N+zToqAfYOp+3rfw==}
-    optionalDependencies:
-      cbor-extract: 2.1.1
-    dev: false
-
-  /cbor@9.0.1:
-    resolution: {integrity: sha512-/TQOWyamDxvVIv+DY9cOLNuABkoyz8K/F3QE56539pGVYohx0+MEA1f4lChFTX79dBTBS7R1PF6ovH7G+VtBfQ==}
+  /cbor@9.0.2:
+    resolution: {integrity: sha512-JPypkxsB10s9QOWwa6zwPzqE1Md3vqpPc+cai4sAecuCsRyAtAl/pMyhPlMbT/xtPnm2dznJZYRLui57qiRhaQ==}
     engines: {node: '>=16'}
     dependencies:
       nofilter: 3.1.0
+    dev: false
+
+  /ccount@2.0.1:
+    resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
+    dev: true
 
   /chai@4.3.10:
     resolution: {integrity: sha512-0UXG04VuVbruMUYbJ6JctvH0YnC/4q3/AkT18q4NaITo91CUm0liMS9VqzT9vZhVQ/1eqPanMWjBM+Juhfb/9g==}
@@ -9992,7 +9881,7 @@ packages:
       check-error: 1.0.3
       deep-eql: 4.1.3
       get-func-name: 2.0.2
-      loupe: 2.3.6
+      loupe: 2.3.7
       pathval: 1.1.1
       type-detect: 4.0.8
     dev: true
@@ -10036,6 +9925,10 @@ packages:
     resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==}
     engines: {node: '>=10'}
 
+  /character-entities@2.0.2:
+    resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==}
+    dev: true
+
   /character-parser@2.2.0:
     resolution: {integrity: sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==}
     dependencies:
@@ -10045,45 +9938,45 @@ packages:
     resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==}
     dev: true
 
-  /chart.js@4.4.1:
-    resolution: {integrity: sha512-C74QN1bxwV1v2PEujhmKjOZ7iUM4w6BWs23Md/6aOZZSlwMzeCIDGuZay++rBgChYru7/+QFeoQW0fQoP534Dg==}
-    engines: {pnpm: '>=7'}
+  /chart.js@4.4.2:
+    resolution: {integrity: sha512-6GD7iKwFpP5kbSD4MeRRRlTnQvxfQREy36uEtm1hzHzcOqwWx0YEHuspuoNlslu+nciLIB7fjjsHkUv/FzFcOg==}
+    engines: {pnpm: '>=8'}
     dependencies:
       '@kurkle/color': 0.3.2
     dev: false
 
-  /chartjs-adapter-date-fns@3.0.0(chart.js@4.4.1)(date-fns@2.30.0):
+  /chartjs-adapter-date-fns@3.0.0(chart.js@4.4.2)(date-fns@2.30.0):
     resolution: {integrity: sha512-Rs3iEB3Q5pJ973J93OBTpnP7qoGwvq3nUnoMdtxO+9aoJof7UFcRbWcIDteXuYd1fgAvct/32T9qaLyLuZVwCg==}
     peerDependencies:
       chart.js: '>=2.8.0'
       date-fns: '>=2.0.0'
     dependencies:
-      chart.js: 4.4.1
+      chart.js: 4.4.2
       date-fns: 2.30.0
     dev: false
 
-  /chartjs-chart-matrix@2.0.1(chart.js@4.4.1):
+  /chartjs-chart-matrix@2.0.1(chart.js@4.4.2):
     resolution: {integrity: sha512-BGfeY+/PHnITyDlc7WfnKJ1RyOfgOzIqWp/gxzzl7pUjyoGzHDcw51qd2xJF9gdT9Def7ZwOnOMm8GJUXDxI0w==}
     peerDependencies:
       chart.js: '>=3.0.0'
     dependencies:
-      chart.js: 4.4.1
+      chart.js: 4.4.2
     dev: false
 
-  /chartjs-plugin-gradient@0.6.1(chart.js@4.4.1):
+  /chartjs-plugin-gradient@0.6.1(chart.js@4.4.2):
     resolution: {integrity: sha512-TGHNIh8KqQMLdb+UfY80cBHYRyOC47eeokmgkeajRdKGbFt462lJiyiq4ZJ25fiM7BGsmzoBLhmVyEw4B3gQxw==}
     peerDependencies:
       chart.js: '>=2.6.0'
     dependencies:
-      chart.js: 4.4.1
+      chart.js: 4.4.2
     dev: false
 
-  /chartjs-plugin-zoom@2.0.1(chart.js@4.4.1):
+  /chartjs-plugin-zoom@2.0.1(chart.js@4.4.2):
     resolution: {integrity: sha512-ogOmLu6e+Q7E1XWOCOz9YwybMslz9qNfGV2a+qjfmqJYpsw5ZMoRHZBUyW+NGhkpQ5PwwPA/+rikHpBZb7PZuA==}
     peerDependencies:
       chart.js: '>=3.2.0'
     dependencies:
-      chart.js: 4.4.1
+      chart.js: 4.4.2
       hammerjs: 2.0.8
     dev: false
 
@@ -10136,15 +10029,24 @@ packages:
 
   /chownr@1.1.4:
     resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
+    dev: true
 
   /chownr@2.0.0:
     resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
     engines: {node: '>=10'}
     requiresBuild: true
 
-  /chromatic@10.1.0:
-    resolution: {integrity: sha512-S+ztO8f1k/LckuzJKCqaTs6AfUQ0eLNT9kEoyCUwX7gkJnveQo5JStCfY55v30zogjRkHJpwqzEfSXl6AwO2tQ==}
+  /chromatic@11.0.0:
+    resolution: {integrity: sha512-utzRVqdMrpzYwZNf7dHWU0z0/rx6SH/FUVNozQxYHDtQfYUdEj+Ro4OSch5+Wsk2FoUmznJyLkaC2J839z1N7A==}
     hasBin: true
+    peerDependencies:
+      '@chromatic-com/cypress': ^0.5.2 || ^1.0.0
+      '@chromatic-com/playwright': ^0.5.2 || ^1.0.0
+    peerDependenciesMeta:
+      '@chromatic-com/cypress':
+        optional: true
+      '@chromatic-com/playwright':
+        optional: true
     dev: false
 
   /ci-info@3.9.0:
@@ -10159,6 +10061,13 @@ packages:
     resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==}
     engines: {node: '>=6'}
 
+  /clean-stack@5.2.0:
+    resolution: {integrity: sha512-TyUIUJgdFnCISzG5zu3291TAsE77ddchd0bepon1VVQrKLGKFED4iXFEDQ24mIPdPBbyE16PK3F8MYE1CmcBEQ==}
+    engines: {node: '>=14.16'}
+    dependencies:
+      escape-string-regexp: 5.0.0
+    dev: true
+
   /cli-cursor@3.1.0:
     resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
     engines: {node: '>=8'}
@@ -10259,6 +10168,11 @@ packages:
     engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
     dev: true
 
+  /code-error-fragment@0.0.230:
+    resolution: {integrity: sha512-cadkfKp6932H8UkhzE/gcUqhRMNf8jHzkAN7+5Myabswaghu4xABTgPHDCjW+dBAJxj/SpkTYokpzDqY4pCzQw==}
+    engines: {node: '>= 4'}
+    dev: true
+
   /collect-v8-coverage@1.0.1:
     resolution: {integrity: sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==}
     dev: true
@@ -10289,10 +10203,6 @@ packages:
     dependencies:
       color-name: 1.1.4
       simple-swizzle: 0.2.2
-
-  /color-support@1.1.3:
-    resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==}
-    hasBin: true
     dev: false
 
   /color@4.2.3:
@@ -10301,6 +10211,7 @@ packages:
     dependencies:
       color-convert: 2.0.1
       color-string: 1.9.1
+    dev: false
 
   /colord@2.9.3:
     resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==}
@@ -10408,7 +10319,6 @@ packages:
       inherits: 2.0.4
       readable-stream: 2.3.8
       typedarray: 0.0.6
-    dev: true
 
   /concat-stream@2.0.0:
     resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==}
@@ -10429,17 +10339,12 @@ packages:
 
   /consola@2.15.3:
     resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==}
-    dev: false
-
-  /console-control-strings@1.1.0:
-    resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==}
-    dev: false
 
   /constantinople@4.0.1:
     resolution: {integrity: sha512-vCrqcSIq4//Gx74TXXCGnHpulY1dskqLTFGDmhrGxzeXL8lF8kvXv6mpNWlJj1uD4DW23D4ljAqbY4RRaaUZIw==}
     dependencies:
       '@babel/parser': 7.22.16
-      '@babel/types': 7.22.17
+      '@babel/types': 7.24.0
 
   /content-disposition@0.5.4:
     resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==}
@@ -10451,10 +10356,6 @@ packages:
     resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==}
     engines: {node: '>= 0.6'}
 
-  /convert-source-map@1.9.0:
-    resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==}
-    dev: true
-
   /convert-source-map@2.0.0:
     resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
     dev: true
@@ -10467,21 +10368,10 @@ packages:
     engines: {node: '>=6.6.0'}
     dev: false
 
-  /cookie@0.4.2:
-    resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==}
-    engines: {node: '>= 0.6'}
-    dev: true
-
   /cookie@0.5.0:
     resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
     engines: {node: '>= 0.6'}
 
-  /core-js-compat@3.31.1:
-    resolution: {integrity: sha512-wIDWd2s5/5aJSdpOJHfSibxNODxoGoWOBHt8JSPB41NOE94M7kuTPZCYLOlTtuoXTsBPKobpJ6T+y0SSy5L9SA==}
-    dependencies:
-      browserslist: 4.22.1
-    dev: true
-
   /core-js-compat@3.34.0:
     resolution: {integrity: sha512-4ZIyeNbW/Cn1wkMMDy+mvrRUxrwFNjKwbhCfQpDd+eLgYipDqp8oGFGtLmhh18EDPKA0g3VUBYOxQGGwvWLVpA==}
     dependencies:
@@ -10494,6 +10384,13 @@ packages:
   /core-util-is@1.0.3:
     resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
 
+  /cors@2.8.5:
+    resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
+    engines: {node: '>= 0.10'}
+    dependencies:
+      object-assign: 4.1.1
+      vary: 1.1.2
+
   /crc-32@1.2.2:
     resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==}
     engines: {node: '>=0.8'}
@@ -10508,7 +10405,7 @@ packages:
       readable-stream: 3.6.2
     dev: false
 
-  /create-jest@29.7.0(@types/node@20.10.5):
+  /create-jest@29.7.0(@types/node@20.11.22):
     resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     hasBin: true
@@ -10517,7 +10414,7 @@ packages:
       chalk: 4.1.2
       exit: 0.1.2
       graceful-fs: 4.2.11
-      jest-config: 29.7.0(@types/node@20.10.5)
+      jest-config: 29.7.0(@types/node@20.11.22)
       jest-util: 29.7.0
       prompts: 2.4.2
     transitivePeerDependencies:
@@ -10562,7 +10459,6 @@ packages:
       node-fetch: 2.7.0
     transitivePeerDependencies:
       - encoding
-    dev: false
 
   /cross-fetch@4.0.0:
     resolution: {integrity: sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==}
@@ -10593,13 +10489,13 @@ packages:
     engines: {node: '>=8'}
     dev: true
 
-  /css-declaration-sorter@7.1.1(postcss@8.4.32):
+  /css-declaration-sorter@7.1.1(postcss@8.4.35):
     resolution: {integrity: sha512-dZ3bVTEEc1vxr3Bek9vGwfB5Z6ESPULhcRvO472mfjVnj8jRcTnKO8/JTczlvxM10Myb+wBM++1MtdO76eWcaQ==}
     engines: {node: ^14 || ^16 || >=18}
     peerDependencies:
       postcss: ^8.0.9
     dependencies:
-      postcss: 8.4.32
+      postcss: 8.4.35
     dev: false
 
   /css-select@5.1.0:
@@ -10639,62 +10535,62 @@ packages:
     engines: {node: '>=4'}
     hasBin: true
 
-  /cssnano-preset-default@6.0.2(postcss@8.4.32):
-    resolution: {integrity: sha512-VnZybFeZ63AiVqIUNlxqMxpj9VU8B5j0oKgP7WyVt/7mkyf97KsYkNzsPTV/RVmy54Pg7cBhOK4WATbdCB44gw==}
+  /cssnano-preset-default@6.0.5(postcss@8.4.35):
+    resolution: {integrity: sha512-M+qRDEr5QZrfNl0B2ySdbTLGyNb8kBcSjuwR7WBamYBOEREH9t2efnB/nblekqhdGLZdkf4oZNetykG2JWRdZQ==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      css-declaration-sorter: 7.1.1(postcss@8.4.32)
-      cssnano-utils: 4.0.1(postcss@8.4.32)
-      postcss: 8.4.32
-      postcss-calc: 9.0.1(postcss@8.4.32)
-      postcss-colormin: 6.0.1(postcss@8.4.32)
-      postcss-convert-values: 6.0.1(postcss@8.4.32)
-      postcss-discard-comments: 6.0.1(postcss@8.4.32)
-      postcss-discard-duplicates: 6.0.1(postcss@8.4.32)
-      postcss-discard-empty: 6.0.1(postcss@8.4.32)
-      postcss-discard-overridden: 6.0.1(postcss@8.4.32)
-      postcss-merge-longhand: 6.0.1(postcss@8.4.32)
-      postcss-merge-rules: 6.0.2(postcss@8.4.32)
-      postcss-minify-font-values: 6.0.1(postcss@8.4.32)
-      postcss-minify-gradients: 6.0.1(postcss@8.4.32)
-      postcss-minify-params: 6.0.1(postcss@8.4.32)
-      postcss-minify-selectors: 6.0.1(postcss@8.4.32)
-      postcss-normalize-charset: 6.0.1(postcss@8.4.32)
-      postcss-normalize-display-values: 6.0.1(postcss@8.4.32)
-      postcss-normalize-positions: 6.0.1(postcss@8.4.32)
-      postcss-normalize-repeat-style: 6.0.1(postcss@8.4.32)
-      postcss-normalize-string: 6.0.1(postcss@8.4.32)
-      postcss-normalize-timing-functions: 6.0.1(postcss@8.4.32)
-      postcss-normalize-unicode: 6.0.1(postcss@8.4.32)
-      postcss-normalize-url: 6.0.1(postcss@8.4.32)
-      postcss-normalize-whitespace: 6.0.1(postcss@8.4.32)
-      postcss-ordered-values: 6.0.1(postcss@8.4.32)
-      postcss-reduce-initial: 6.0.1(postcss@8.4.32)
-      postcss-reduce-transforms: 6.0.1(postcss@8.4.32)
-      postcss-svgo: 6.0.1(postcss@8.4.32)
-      postcss-unique-selectors: 6.0.1(postcss@8.4.32)
+      css-declaration-sorter: 7.1.1(postcss@8.4.35)
+      cssnano-utils: 4.0.1(postcss@8.4.35)
+      postcss: 8.4.35
+      postcss-calc: 9.0.1(postcss@8.4.35)
+      postcss-colormin: 6.0.3(postcss@8.4.35)
+      postcss-convert-values: 6.0.4(postcss@8.4.35)
+      postcss-discard-comments: 6.0.1(postcss@8.4.35)
+      postcss-discard-duplicates: 6.0.2(postcss@8.4.35)
+      postcss-discard-empty: 6.0.2(postcss@8.4.35)
+      postcss-discard-overridden: 6.0.1(postcss@8.4.35)
+      postcss-merge-longhand: 6.0.3(postcss@8.4.35)
+      postcss-merge-rules: 6.0.4(postcss@8.4.35)
+      postcss-minify-font-values: 6.0.2(postcss@8.4.35)
+      postcss-minify-gradients: 6.0.2(postcss@8.4.35)
+      postcss-minify-params: 6.0.3(postcss@8.4.35)
+      postcss-minify-selectors: 6.0.2(postcss@8.4.35)
+      postcss-normalize-charset: 6.0.1(postcss@8.4.35)
+      postcss-normalize-display-values: 6.0.1(postcss@8.4.35)
+      postcss-normalize-positions: 6.0.1(postcss@8.4.35)
+      postcss-normalize-repeat-style: 6.0.1(postcss@8.4.35)
+      postcss-normalize-string: 6.0.1(postcss@8.4.35)
+      postcss-normalize-timing-functions: 6.0.1(postcss@8.4.35)
+      postcss-normalize-unicode: 6.0.3(postcss@8.4.35)
+      postcss-normalize-url: 6.0.1(postcss@8.4.35)
+      postcss-normalize-whitespace: 6.0.1(postcss@8.4.35)
+      postcss-ordered-values: 6.0.1(postcss@8.4.35)
+      postcss-reduce-initial: 6.0.3(postcss@8.4.35)
+      postcss-reduce-transforms: 6.0.1(postcss@8.4.35)
+      postcss-svgo: 6.0.2(postcss@8.4.35)
+      postcss-unique-selectors: 6.0.2(postcss@8.4.35)
     dev: false
 
-  /cssnano-utils@4.0.1(postcss@8.4.32):
+  /cssnano-utils@4.0.1(postcss@8.4.35):
     resolution: {integrity: sha512-6qQuYDqsGoiXssZ3zct6dcMxiqfT6epy7x4R0TQJadd4LWO3sPR6JH6ZByOvVLoZ6EdwPGgd7+DR1EmX3tiXQQ==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      postcss: 8.4.32
+      postcss: 8.4.35
     dev: false
 
-  /cssnano@6.0.2(postcss@8.4.32):
-    resolution: {integrity: sha512-Tu9wv8UdN6CoiQnIVkCNvi+0rw/BwFWOJBlg2bVfEyKaadSuE3Gq/DD8tniVvggTJGwK88UjqZp7zL5sv6t1aA==}
+  /cssnano@6.0.5(postcss@8.4.35):
+    resolution: {integrity: sha512-tpTp/ukgrElwu3ESFY4IvWnGn8eTt8cJhC2aAbtA3lvUlxp6t6UPv8YCLjNnEGiFreT1O0LiOM1U3QyTBVFl2A==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      cssnano-preset-default: 6.0.2(postcss@8.4.32)
-      lilconfig: 3.0.0
-      postcss: 8.4.32
+      cssnano-preset-default: 6.0.5(postcss@8.4.35)
+      lilconfig: 3.1.1
+      postcss: 8.4.35
     dev: false
 
   /csso@5.0.5:
@@ -10704,29 +10600,24 @@ packages:
       css-tree: 2.2.1
     dev: false
 
-  /cssstyle@3.0.0:
-    resolution: {integrity: sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==}
-    engines: {node: '>=14'}
+  /cssstyle@4.0.1:
+    resolution: {integrity: sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==}
+    engines: {node: '>=18'}
     dependencies:
       rrweb-cssom: 0.6.0
     dev: false
 
-  /csstype@3.1.2:
-    resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==}
-    dev: true
-
   /csstype@3.1.3:
     resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
 
-  /cypress@13.6.1:
-    resolution: {integrity: sha512-k1Wl5PQcA/4UoTffYKKaxA0FJKwg8yenYNYRzLt11CUR0Kln+h7Udne6mdU1cUIdXBDTVZWtmiUjzqGs7/pEpw==}
+  /cypress@13.6.6:
+    resolution: {integrity: sha512-S+2S9S94611hXimH9a3EAYt81QM913ZVA03pUmGDfLTFa5gyp85NJ8dJGSlEAEmyRsYkioS1TtnWtbv/Fzt11A==}
     engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0}
     hasBin: true
     requiresBuild: true
     dependencies:
       '@cypress/request': 3.0.0
       '@cypress/xvfb': 1.2.4(supports-color@8.1.1)
-      '@types/node': 18.17.15
       '@types/sinonjs__fake-timers': 8.1.1
       '@types/sizzle': 2.3.3
       arch: 2.2.0
@@ -10764,7 +10655,7 @@ packages:
       request-progress: 3.0.0
       semver: 7.5.4
       supports-color: 8.1.1
-      tmp: 0.2.1
+      tmp: 0.2.2
       untildify: 4.0.0
       yauzl: 2.10.0
     dev: true
@@ -10880,6 +10771,12 @@ packages:
       to-data-view: 1.1.0
     dev: false
 
+  /decode-named-character-reference@1.0.2:
+    resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==}
+    dependencies:
+      character-entities: 2.0.2
+    dev: true
+
   /decompress-response@6.0.0:
     resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
     engines: {node: '>=10'}
@@ -10918,7 +10815,7 @@ packages:
     dependencies:
       call-bind: 1.0.2
       es-get-iterator: 1.1.3
-      get-intrinsic: 1.2.0
+      get-intrinsic: 1.2.1
       is-arguments: 1.1.1
       is-array-buffer: 3.0.2
       is-date-object: 1.0.5
@@ -10928,17 +10825,13 @@ packages:
       object-is: 1.1.5
       object-keys: 1.1.1
       object.assign: 4.1.4
-      regexp.prototype.flags: 1.4.3
+      regexp.prototype.flags: 1.5.0
       side-channel: 1.0.4
       which-boxed-primitive: 1.0.2
       which-collection: 1.0.1
-      which-typed-array: 1.1.9
+      which-typed-array: 1.1.11
     dev: true
 
-  /deep-extend@0.6.0:
-    resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==}
-    engines: {node: '>=4.0.0'}
-
   /deep-is@0.1.4:
     resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
     dev: true
@@ -10984,8 +10877,8 @@ packages:
       object-keys: 1.1.1
     dev: true
 
-  /defu@6.1.2:
-    resolution: {integrity: sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ==}
+  /defu@6.1.4:
+    resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
     dev: true
 
   /del@6.1.1:
@@ -11006,10 +10899,6 @@ packages:
     resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
     engines: {node: '>=0.4.0'}
 
-  /delegates@1.0.0:
-    resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==}
-    dev: false
-
   /denque@2.1.0:
     resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
     engines: {node: '>=0.10'}
@@ -11036,16 +10925,13 @@ packages:
   /detect-libc@2.0.2:
     resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==}
     engines: {node: '>=8'}
+    dev: false
 
   /detect-newline@3.1.0:
     resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==}
     engines: {node: '>=8'}
     dev: true
 
-  /detect-node-es@1.1.0:
-    resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==}
-    dev: true
-
   /detect-package-manager@2.0.1:
     resolution: {integrity: sha512-j/lJHyoLlWi6G1LDdLgvUtz60Zo5GEj+sVYtTVXnYLDPuzgC3llMxonXym9zIwhhUII8vjdw0LXxavpLqTbl1A==}
     engines: {node: '>=12'}
@@ -11063,15 +10949,16 @@ packages:
       - supports-color
     dev: true
 
+  /devlop@1.1.0:
+    resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
+    dependencies:
+      dequal: 2.0.3
+    dev: true
+
   /diff-match-patch@1.0.5:
     resolution: {integrity: sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw==}
     dev: false
 
-  /diff-sequences@28.1.1:
-    resolution: {integrity: sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==}
-    engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
-    dev: true
-
   /diff-sequences@29.6.3:
     resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -11115,6 +11002,10 @@ packages:
     resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==}
     dev: true
 
+  /dom-accessibility-api@0.6.3:
+    resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
+    dev: true
+
   /dom-serializer@2.0.0:
     resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
     dependencies:
@@ -11204,12 +11095,8 @@ packages:
     dependencies:
       jake: 10.8.5
 
-  /electron-to-chromium@1.4.591:
-    resolution: {integrity: sha512-vLv/P7wwAPKQoY+CVMyyI6rsTp+A14KGtPXx92oz1FY41AAqa9l6Wkizcixg0LDuJgyeo8xgNN9+9hsnGp66UA==}
-
-  /electron-to-chromium@1.4.616:
-    resolution: {integrity: sha512-1n7zWYh8eS0L9Uy+GskE0lkBUNK83cXTVJI0pU3mGprFsbfSdAc15VTFbo+A+Bq4pwstmL30AVcEU3Fo463lNg==}
-    dev: true
+  /electron-to-chromium@1.4.686:
+    resolution: {integrity: sha512-3avY1B+vUzNxEgkBDpKOP8WarvUAEwpRaiCL0He5OKWEFxzaOFiq4WoZEZe7qh0ReS7DiWoHMnYoQCKxNZNzSg==}
 
   /emittery@0.13.1:
     resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==}
@@ -11366,55 +11253,21 @@ packages:
       is-symbol: 1.0.4
     dev: true
 
-  /es6-object-assign@1.1.0:
-    resolution: {integrity: sha512-MEl9uirslVwqQU369iHNWZXsI8yaZYGg/D65aOgZkeyFJwHYSxilf7rQzXKI7DdDuBPrBXbfk3sl9hJhmd5AUw==}
-    dev: true
-
   /esbuild-plugin-alias@0.2.1:
     resolution: {integrity: sha512-jyfL/pwPqaFXyKnj8lP8iLk6Z0m099uXR45aSN8Av1XD4vhvQutxxPzgA2bTcAwQpa1zCXDcWOlhFgyP3GKqhQ==}
     dev: true
 
-  /esbuild-register@3.5.0(esbuild@0.18.17):
+  /esbuild-register@3.5.0(esbuild@0.18.20):
     resolution: {integrity: sha512-+4G/XmakeBAsvJuDugJvtyF1x+XJT4FMocynNpxrvEBViirpfUn2PgNpCHedfWhF4WokNsO/OvMKrmJOIJsI5A==}
     peerDependencies:
       esbuild: '>=0.12 <1'
     dependencies:
       debug: 4.3.4(supports-color@8.1.1)
-      esbuild: 0.18.17
+      esbuild: 0.18.20
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /esbuild@0.18.17:
-    resolution: {integrity: sha512-1GJtYnUxsJreHYA0Y+iQz2UEykonY66HNWOb0yXYZi9/kNrORUEHVg87eQsCtqh59PEJ5YVZJO98JHznMJSWjg==}
-    engines: {node: '>=12'}
-    hasBin: true
-    requiresBuild: true
-    optionalDependencies:
-      '@esbuild/android-arm': 0.18.17
-      '@esbuild/android-arm64': 0.18.17
-      '@esbuild/android-x64': 0.18.17
-      '@esbuild/darwin-arm64': 0.18.17
-      '@esbuild/darwin-x64': 0.18.17
-      '@esbuild/freebsd-arm64': 0.18.17
-      '@esbuild/freebsd-x64': 0.18.17
-      '@esbuild/linux-arm': 0.18.17
-      '@esbuild/linux-arm64': 0.18.17
-      '@esbuild/linux-ia32': 0.18.17
-      '@esbuild/linux-loong64': 0.18.17
-      '@esbuild/linux-mips64el': 0.18.17
-      '@esbuild/linux-ppc64': 0.18.17
-      '@esbuild/linux-riscv64': 0.18.17
-      '@esbuild/linux-s390x': 0.18.17
-      '@esbuild/linux-x64': 0.18.17
-      '@esbuild/netbsd-x64': 0.18.17
-      '@esbuild/openbsd-x64': 0.18.17
-      '@esbuild/sunos-x64': 0.18.17
-      '@esbuild/win32-arm64': 0.18.17
-      '@esbuild/win32-ia32': 0.18.17
-      '@esbuild/win32-x64': 0.18.17
-    dev: true
-
   /esbuild@0.18.20:
     resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==}
     engines: {node: '>=12'}
@@ -11445,34 +11298,35 @@ packages:
       '@esbuild/win32-x64': 0.18.20
     dev: true
 
-  /esbuild@0.19.9:
-    resolution: {integrity: sha512-U9CHtKSy+EpPsEBa+/A2gMs/h3ylBC0H0KSqIg7tpztHerLi6nrrcoUJAkNCEPumx8yJ+Byic4BVwHgRbN0TBg==}
+  /esbuild@0.19.11:
+    resolution: {integrity: sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==}
     engines: {node: '>=12'}
     hasBin: true
     requiresBuild: true
     optionalDependencies:
-      '@esbuild/android-arm': 0.19.9
-      '@esbuild/android-arm64': 0.19.9
-      '@esbuild/android-x64': 0.19.9
-      '@esbuild/darwin-arm64': 0.19.9
-      '@esbuild/darwin-x64': 0.19.9
-      '@esbuild/freebsd-arm64': 0.19.9
-      '@esbuild/freebsd-x64': 0.19.9
-      '@esbuild/linux-arm': 0.19.9
-      '@esbuild/linux-arm64': 0.19.9
-      '@esbuild/linux-ia32': 0.19.9
-      '@esbuild/linux-loong64': 0.19.9
-      '@esbuild/linux-mips64el': 0.19.9
-      '@esbuild/linux-ppc64': 0.19.9
-      '@esbuild/linux-riscv64': 0.19.9
-      '@esbuild/linux-s390x': 0.19.9
-      '@esbuild/linux-x64': 0.19.9
-      '@esbuild/netbsd-x64': 0.19.9
-      '@esbuild/openbsd-x64': 0.19.9
-      '@esbuild/sunos-x64': 0.19.9
-      '@esbuild/win32-arm64': 0.19.9
-      '@esbuild/win32-ia32': 0.19.9
-      '@esbuild/win32-x64': 0.19.9
+      '@esbuild/aix-ppc64': 0.19.11
+      '@esbuild/android-arm': 0.19.11
+      '@esbuild/android-arm64': 0.19.11
+      '@esbuild/android-x64': 0.19.11
+      '@esbuild/darwin-arm64': 0.19.11
+      '@esbuild/darwin-x64': 0.19.11
+      '@esbuild/freebsd-arm64': 0.19.11
+      '@esbuild/freebsd-x64': 0.19.11
+      '@esbuild/linux-arm': 0.19.11
+      '@esbuild/linux-arm64': 0.19.11
+      '@esbuild/linux-ia32': 0.19.11
+      '@esbuild/linux-loong64': 0.19.11
+      '@esbuild/linux-mips64el': 0.19.11
+      '@esbuild/linux-ppc64': 0.19.11
+      '@esbuild/linux-riscv64': 0.19.11
+      '@esbuild/linux-s390x': 0.19.11
+      '@esbuild/linux-x64': 0.19.11
+      '@esbuild/netbsd-x64': 0.19.11
+      '@esbuild/openbsd-x64': 0.19.11
+      '@esbuild/sunos-x64': 0.19.11
+      '@esbuild/win32-arm64': 0.19.11
+      '@esbuild/win32-ia32': 0.19.11
+      '@esbuild/win32-x64': 0.19.11
 
   /escalade@3.1.1:
     resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
@@ -11499,7 +11353,6 @@ packages:
   /escape-string-regexp@5.0.0:
     resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==}
     engines: {node: '>=12'}
-    dev: false
 
   /escodegen@2.1.0:
     resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==}
@@ -11546,7 +11399,7 @@ packages:
       - supports-color
     dev: true
 
-  /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.14.0)(eslint-import-resolver-node@0.3.9)(eslint@8.56.0):
+  /eslint-module-utils@2.8.0(@typescript-eslint/parser@6.11.0)(eslint-import-resolver-node@0.3.9)(eslint@8.53.0):
     resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==}
     engines: {node: '>=4'}
     peerDependencies:
@@ -11567,15 +11420,44 @@ packages:
       eslint-import-resolver-webpack:
         optional: true
     dependencies:
-      '@typescript-eslint/parser': 6.14.0(eslint@8.56.0)(typescript@5.3.3)
+      '@typescript-eslint/parser': 6.11.0(eslint@8.53.0)(typescript@5.3.3)
       debug: 3.2.7(supports-color@8.1.1)
-      eslint: 8.56.0
+      eslint: 8.53.0
       eslint-import-resolver-node: 0.3.9
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.14.0)(eslint@8.56.0):
+  /eslint-module-utils@2.8.0(@typescript-eslint/parser@7.1.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0):
+    resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==}
+    engines: {node: '>=4'}
+    peerDependencies:
+      '@typescript-eslint/parser': '*'
+      eslint: '*'
+      eslint-import-resolver-node: '*'
+      eslint-import-resolver-typescript: '*'
+      eslint-import-resolver-webpack: '*'
+    peerDependenciesMeta:
+      '@typescript-eslint/parser':
+        optional: true
+      eslint:
+        optional: true
+      eslint-import-resolver-node:
+        optional: true
+      eslint-import-resolver-typescript:
+        optional: true
+      eslint-import-resolver-webpack:
+        optional: true
+    dependencies:
+      '@typescript-eslint/parser': 7.1.0(eslint@8.57.0)(typescript@5.3.3)
+      debug: 3.2.7(supports-color@8.1.1)
+      eslint: 8.57.0
+      eslint-import-resolver-node: 0.3.9
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /eslint-plugin-import@2.29.1(@typescript-eslint/parser@6.11.0)(eslint@8.53.0):
     resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==}
     engines: {node: '>=4'}
     peerDependencies:
@@ -11585,16 +11467,16 @@ packages:
       '@typescript-eslint/parser':
         optional: true
     dependencies:
-      '@typescript-eslint/parser': 6.14.0(eslint@8.56.0)(typescript@5.3.3)
+      '@typescript-eslint/parser': 6.11.0(eslint@8.53.0)(typescript@5.3.3)
       array-includes: 3.1.7
       array.prototype.findlastindex: 1.2.3
       array.prototype.flat: 1.3.2
       array.prototype.flatmap: 1.3.2
       debug: 3.2.7(supports-color@8.1.1)
       doctrine: 2.1.0
-      eslint: 8.56.0
+      eslint: 8.53.0
       eslint-import-resolver-node: 0.3.9
-      eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.14.0)(eslint-import-resolver-node@0.3.9)(eslint@8.56.0)
+      eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.11.0)(eslint-import-resolver-node@0.3.9)(eslint@8.53.0)
       hasown: 2.0.0
       is-core-module: 2.13.1
       is-glob: 4.0.3
@@ -11610,19 +11492,54 @@ packages:
       - supports-color
     dev: true
 
-  /eslint-plugin-vue@9.19.2(eslint@8.56.0):
-    resolution: {integrity: sha512-CPDqTOG2K4Ni2o4J5wixkLVNwgctKXFu6oBpVJlpNq7f38lh9I80pRTouZSJ2MAebPJlINU/KTFSXyQfBUlymA==}
+  /eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.1.0)(eslint@8.57.0):
+    resolution: {integrity: sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==}
+    engines: {node: '>=4'}
+    peerDependencies:
+      '@typescript-eslint/parser': '*'
+      eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8
+    peerDependenciesMeta:
+      '@typescript-eslint/parser':
+        optional: true
+    dependencies:
+      '@typescript-eslint/parser': 7.1.0(eslint@8.57.0)(typescript@5.3.3)
+      array-includes: 3.1.7
+      array.prototype.findlastindex: 1.2.3
+      array.prototype.flat: 1.3.2
+      array.prototype.flatmap: 1.3.2
+      debug: 3.2.7(supports-color@8.1.1)
+      doctrine: 2.1.0
+      eslint: 8.57.0
+      eslint-import-resolver-node: 0.3.9
+      eslint-module-utils: 2.8.0(@typescript-eslint/parser@7.1.0)(eslint-import-resolver-node@0.3.9)(eslint@8.57.0)
+      hasown: 2.0.0
+      is-core-module: 2.13.1
+      is-glob: 4.0.3
+      minimatch: 3.1.2
+      object.fromentries: 2.0.7
+      object.groupby: 1.0.1
+      object.values: 1.1.7
+      semver: 6.3.1
+      tsconfig-paths: 3.15.0
+    transitivePeerDependencies:
+      - eslint-import-resolver-typescript
+      - eslint-import-resolver-webpack
+      - supports-color
+    dev: true
+
+  /eslint-plugin-vue@9.22.0(eslint@8.57.0):
+    resolution: {integrity: sha512-7wCXv5zuVnBtZE/74z4yZ0CM8AjH6bk4MQGm7hZjUC2DBppKU5ioeOk5LGSg/s9a1ZJnIsdPLJpXnu1Rc+cVHg==}
     engines: {node: ^14.17.0 || >=16.0.0}
     peerDependencies:
       eslint: ^6.2.0 || ^7.0.0 || ^8.0.0
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0)
-      eslint: 8.56.0
+      '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
+      eslint: 8.57.0
       natural-compare: 1.4.0
       nth-check: 2.1.1
-      postcss-selector-parser: 6.0.13
-      semver: 7.5.4
-      vue-eslint-parser: 9.3.2(eslint@8.56.0)
+      postcss-selector-parser: 6.0.15
+      semver: 7.6.0
+      vue-eslint-parser: 9.4.2(eslint@8.57.0)
       xml-name-validator: 4.0.0
     transitivePeerDependencies:
       - supports-color
@@ -11739,16 +11656,16 @@ packages:
       - supports-color
     dev: true
 
-  /eslint@8.56.0:
-    resolution: {integrity: sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==}
+  /eslint@8.57.0:
+    resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     hasBin: true
     dependencies:
-      '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0)
+      '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.0)
       '@eslint-community/regexpp': 4.10.0
       '@eslint/eslintrc': 2.1.4
-      '@eslint/js': 8.56.0
-      '@humanwhocodes/config-array': 0.11.13
+      '@eslint/js': 8.57.0
+      '@humanwhocodes/config-array': 0.11.14
       '@humanwhocodes/module-importer': 1.0.1
       '@nodelib/fs.walk': 1.2.8
       '@ungap/structured-clone': 1.2.0
@@ -11790,8 +11707,8 @@ packages:
     resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
     dependencies:
-      acorn: 8.11.2
-      acorn-jsx: 5.3.2(acorn@8.11.2)
+      acorn: 8.11.3
+      acorn-jsx: 5.3.2(acorn@8.11.3)
       eslint-visitor-keys: 3.4.3
     dev: true
 
@@ -11801,13 +11718,6 @@ packages:
     hasBin: true
     dev: true
 
-  /esquery@1.4.2:
-    resolution: {integrity: sha512-JVSoLdTlTDkmjFmab7H/9SL9qGSyjElT3myyKp7krqjVFQCDLmj1QFaCLRFBszBKI0XVZaiiXvuPIX3ZwHe1Ng==}
-    engines: {node: '>=0.10'}
-    dependencies:
-      estraverse: 5.3.0
-    dev: true
-
   /esquery@1.5.0:
     resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==}
     engines: {node: '>=0.10'}
@@ -11834,7 +11744,6 @@ packages:
     resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
     dependencies:
       '@types/estree': 1.0.5
-    dev: false
 
   /esutils@2.0.3:
     resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
@@ -11877,6 +11786,7 @@ packages:
   /events@3.3.0:
     resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
     engines: {node: '>=0.8.x'}
+    dev: false
 
   /execa@0.7.0:
     resolution: {integrity: sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==}
@@ -11920,6 +11830,21 @@ packages:
       signal-exit: 3.0.7
       strip-final-newline: 2.0.0
 
+  /execa@6.1.0:
+    resolution: {integrity: sha512-QVWlX2e50heYJcCPG0iWtf8r0xjEYfz/OYLGDYH+IyjWezzPNxz63qNFOu0l4YftGWuizFVZHHs8PrLU5p2IDA==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dependencies:
+      cross-spawn: 7.0.3
+      get-stream: 6.0.1
+      human-signals: 3.0.1
+      is-stream: 3.0.0
+      merge-stream: 2.0.0
+      npm-run-path: 5.1.0
+      onetime: 6.0.0
+      signal-exit: 3.0.7
+      strip-final-newline: 3.0.0
+    dev: true
+
   /execa@8.0.1:
     resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==}
     engines: {node: '>=16.17'}
@@ -11945,10 +11870,6 @@ packages:
     engines: {node: '>= 0.8.0'}
     dev: true
 
-  /expand-template@2.0.3:
-    resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
-    engines: {node: '>=6'}
-
   /expect@29.7.0:
     resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -12029,18 +11950,6 @@ packages:
       tmp: 0.0.33
     dev: true
 
-  /extract-zip@1.7.0:
-    resolution: {integrity: sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==}
-    hasBin: true
-    dependencies:
-      concat-stream: 1.6.2
-      debug: 2.6.9
-      mkdirp: 0.5.6
-      yauzl: 2.10.0
-    transitivePeerDependencies:
-      - supports-color
-    dev: true
-
   /extract-zip@2.0.1(supports-color@8.1.1):
     resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==}
     engines: {node: '>= 10.17.0'}
@@ -12072,6 +11981,7 @@ packages:
 
   /fast-fifo@1.3.0:
     resolution: {integrity: sha512-IgfweLvEpwyA4WgiQe9Nx6VV2QkML2NkvZnk1oKnIzXgXdWxuhF7zw4DvLTPZJn6PIUneiAXPF24QmoEqHTjyw==}
+    dev: false
 
   /fast-glob@3.3.2:
     resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
@@ -12102,12 +12012,6 @@ packages:
     resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
     dev: true
 
-  /fast-querystring@1.1.0:
-    resolution: {integrity: sha512-LWkjBCZlxjnSanuPpZ6mHswjy8hQv3VcPJsQB3ltUF2zjvrycr0leP3TSTEEfvQ1WEMSRl5YNsGqaft9bjLqEw==}
-    dependencies:
-      fast-decode-uri-component: 1.0.1
-    dev: false
-
   /fast-querystring@1.1.2:
     resolution: {integrity: sha512-g6KuKWmFXc0fID8WWH0jit4g0AGBoJhCkJMb1RmbsSEUNvQ+ZC8D6CUZ+GtF8nMzSPXnhiePyyqqipzNNEnHjg==}
     dependencies:
@@ -12121,7 +12025,6 @@ packages:
 
   /fast-safe-stringify@2.1.1:
     resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
-    dev: false
 
   /fast-uri@2.2.0:
     resolution: {integrity: sha512-cIusKBIt/R/oI6z/1nyfe2FvGKVTohVRfvkOhvx0nCEW+xf5NoCXjAHcWp93uOUBchzYcsvPlrapAdX1uW+YGg==}
@@ -12167,8 +12070,8 @@ packages:
       secure-json-parse: 2.7.0
     dev: false
 
-  /fastify@4.24.3:
-    resolution: {integrity: sha512-6HHJ+R2x2LS3y1PqxnwEIjOTZxFl+8h4kSC/TuDPXtA+v2JnV9yEtOsNSKK1RMD7sIR2y1ZsA4BEFaid/cK5pg==}
+  /fastify@4.25.2:
+    resolution: {integrity: sha512-SywRouGleDHvRh054onj+lEZnbC1sBCLkR0UY3oyJwjD4BdZJUrxBqfkfCaqn74pVCwBaRHGuL3nEWeHbHzAfw==}
     dependencies:
       '@fastify/ajv-compiler': 3.5.0
       '@fastify/error': 3.4.0
@@ -12179,8 +12082,8 @@ packages:
       fast-json-stringify: 5.8.0
       find-my-way: 7.7.0
       light-my-request: 5.11.0
-      pino: 8.16.0
-      process-warning: 2.2.0
+      pino: 8.17.2
+      process-warning: 3.0.0
       proxy-addr: 2.0.7
       rfdc: 1.3.0
       secure-json-parse: 2.7.0
@@ -12255,9 +12158,9 @@ packages:
       token-types: 5.0.1
     dev: false
 
-  /file-type@18.7.0:
-    resolution: {integrity: sha512-ihHtXRzXEziMrQ56VSgU7wkxh55iNchFkosu7Y9/S+tXHdKyrGjVK0ujbqNnsxzea+78MaLhN6PGmfYSAv1ACw==}
-    engines: {node: '>=14.16'}
+  /file-type@19.0.0:
+    resolution: {integrity: sha512-s7cxa7/leUWLiXO78DVVfBVse+milos9FitauDLG1pI7lNaJ2+5lzPnr2N24ym+84HVwJL6hVuGfgVE+ALvU8Q==}
+    engines: {node: '>=18'}
     dependencies:
       readable-web-to-node-stream: 3.0.2
       strtok3: 7.0.0
@@ -12326,10 +12229,14 @@ packages:
     engines: {node: '>=14'}
     dependencies:
       fast-deep-equal: 3.1.3
-      fast-querystring: 1.1.0
+      fast-querystring: 1.1.2
       safe-regex2: 2.0.0
     dev: false
 
+  /find-package-json@1.2.0:
+    resolution: {integrity: sha512-+SOGcLGYDJHtyqHd87ysBhmaeQ95oWspDKnMXBrnQ9Eq4OkLNqejgoaD8xVWu6GPa0B6roa6KinCMEMcVeqONw==}
+    dev: true
+
   /find-up@3.0.0:
     resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==}
     engines: {node: '>=6'}
@@ -12359,6 +12266,18 @@ packages:
       semver-regex: 4.0.5
     dev: false
 
+  /fkill@9.0.0:
+    resolution: {integrity: sha512-MdYSsbdCaIRjzo5edthZtWmEZVMfr1qrtYZUHIdO3swCE+CoZA8S5l0s4jDsYlTa9ZiXv0pTgpzE7s4N8NeUOA==}
+    engines: {node: '>=18'}
+    dependencies:
+      aggregate-error: 5.0.0
+      execa: 8.0.1
+      pid-port: 1.0.0
+      process-exists: 5.0.0
+      ps-list: 8.1.1
+      taskkill: 5.0.0
+    dev: true
+
   /flat-cache@3.0.4:
     resolution: {integrity: sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==}
     engines: {node: ^10.12.0 || >=12.0.0}
@@ -12430,16 +12349,6 @@ packages:
       mime-types: 2.1.35
     dev: true
 
-  /form-data@3.0.1:
-    resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==}
-    engines: {node: '>= 6'}
-    requiresBuild: true
-    dependencies:
-      asynckit: 0.4.0
-      combined-stream: 1.0.8
-      mime-types: 2.1.35
-    dev: true
-
   /form-data@4.0.0:
     resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
     engines: {node: '>= 6'}
@@ -12468,6 +12377,7 @@ packages:
 
   /fs-constants@1.0.0:
     resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
+    dev: true
 
   /fs-extra@11.1.1:
     resolution: {integrity: sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==}
@@ -12550,22 +12460,6 @@ packages:
     resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
     dev: true
 
-  /gauge@3.0.2:
-    resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==}
-    engines: {node: '>=10'}
-    requiresBuild: true
-    dependencies:
-      aproba: 2.0.0
-      color-support: 1.1.3
-      console-control-strings: 1.1.0
-      has-unicode: 2.0.1
-      object-assign: 4.1.1
-      signal-exit: 3.0.7
-      string-width: 4.2.3
-      strip-ansi: 6.0.1
-      wide-align: 1.1.5
-    dev: false
-
   /gensync@1.0.0-beta.2:
     resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
     engines: {node: '>=6.9.0'}
@@ -12579,14 +12473,6 @@ packages:
     resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==}
     dev: true
 
-  /get-intrinsic@1.2.0:
-    resolution: {integrity: sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==}
-    dependencies:
-      function-bind: 1.1.1
-      has: 1.0.3
-      has-symbols: 1.0.3
-    dev: true
-
   /get-intrinsic@1.2.1:
     resolution: {integrity: sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==}
     dependencies:
@@ -12595,11 +12481,6 @@ packages:
       has-proto: 1.0.1
       has-symbols: 1.0.3
 
-  /get-nonce@1.0.1:
-    resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==}
-    engines: {node: '>=6'}
-    dev: true
-
   /get-npm-tarball-url@2.0.3:
     resolution: {integrity: sha512-R/PW6RqyaBQNWYaSyfrh54/qtcnOp22FHCCiRhSSZj0FP3KQWCsxxt0DzIdVTbwTqe9CtQfvl/FPD4UIPt4pqw==}
     engines: {node: '>=12.17'}
@@ -12610,11 +12491,6 @@ packages:
     engines: {node: '>=8.0.0'}
     dev: true
 
-  /get-port@5.1.1:
-    resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==}
-    engines: {node: '>=8'}
-    dev: true
-
   /get-stream@3.0.0:
     resolution: {integrity: sha512-GlhdIUuVakc8SJ6kK0zAFbiGzRFzNnY4jUuEbV9UROo4Y+0Ny4fjvcZFVTeDA4odpFyOQzaw6hXukJSq/f28sQ==}
     engines: {node: '>=4'}
@@ -12664,21 +12540,18 @@ packages:
     hasBin: true
     dependencies:
       colorette: 2.0.19
-      defu: 6.1.2
+      defu: 6.1.4
       https-proxy-agent: 5.0.1
       mri: 1.2.0
       node-fetch-native: 1.0.2
-      pathe: 1.1.1
-      tar: 6.1.13
+      pathe: 1.1.2
+      tar: 6.2.0
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /github-from-package@0.0.0:
-    resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
-
-  /github-slugger@1.5.0:
-    resolution: {integrity: sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==}
+  /github-slugger@2.0.0:
+    resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==}
     dev: true
 
   /glob-parent@5.1.2:
@@ -12728,6 +12601,7 @@ packages:
       minimatch: 3.1.2
       once: 1.4.0
       path-is-absolute: 1.0.1
+    dev: true
 
   /glob@8.1.0:
     resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
@@ -12815,8 +12689,8 @@ packages:
       p-cancelable: 3.0.0
       responselike: 3.0.0
 
-  /got@14.0.0:
-    resolution: {integrity: sha512-X01vTgaX9SwaMq5DfImvS+3GMQFFs5HtrrlS9CuzUSzkxAf/tWGEyynuI+Qy7BjciMczZGjyVSmawYbP4eYhYA==}
+  /got@14.2.0:
+    resolution: {integrity: sha512-dBq2KkHcQl3AwPoIWsLsQScCPpUgRulz1qZVthjPYKYOPmYfBnekR3vxecjZbm91Vc3JUGnV9mqFX7B+Fe2quw==}
     engines: {node: '>=20'}
     dependencies:
       '@sindresorhus/is': 6.1.0
@@ -12835,6 +12709,10 @@ packages:
   /graceful-fs@4.2.11:
     resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
 
+  /grapheme-splitter@1.0.4:
+    resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
+    dev: true
+
   /graphemer@1.4.0:
     resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
     dev: true
@@ -12844,10 +12722,6 @@ packages:
     engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0}
     dev: true
 
-  /gsap@3.12.4:
-    resolution: {integrity: sha512-1ByAq8dD0W4aBZ/JArgaQvc0gyUfkGkP8mgAQa0qZGdpOKlSOhOf+WNXjoLimKaKG3Z4Iu6DKZtnyszqQeyqWQ==}
-    dev: false
-
   /gunzip-maybe@1.4.2:
     resolution: {integrity: sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==}
     hasBin: true
@@ -12887,6 +12761,16 @@ packages:
       webidl-conversions: 7.0.0
       whatwg-encoding: 2.0.0
       whatwg-mimetype: 3.0.0
+    dev: false
+
+  /happy-dom@13.6.2:
+    resolution: {integrity: sha512-Ku+wDqcF/KwFA0dI+xIMZd9Jn020RXjuSil/Vz7gu2yhDC3FsDYZ55qqV9k+SGC4opwb4acisXqVSRxUJMlPbQ==}
+    engines: {node: '>=16.0.0'}
+    dependencies:
+      entities: 4.5.0
+      webidl-conversions: 7.0.0
+      whatwg-mimetype: 3.0.0
+    dev: true
 
   /hard-rejection@2.1.0:
     resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==}
@@ -12925,10 +12809,6 @@ packages:
     dependencies:
       has-symbols: 1.0.3
 
-  /has-unicode@2.0.1:
-    resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==}
-    dev: false
-
   /has@1.0.3:
     resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
     engines: {node: '>= 0.4.0'}
@@ -12949,21 +12829,39 @@ packages:
     dependencies:
       function-bind: 1.1.2
 
+  /hast-util-heading-rank@3.0.0:
+    resolution: {integrity: sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==}
+    dependencies:
+      '@types/hast': 3.0.4
+    dev: true
+
+  /hast-util-is-element@3.0.0:
+    resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==}
+    dependencies:
+      '@types/hast': 3.0.4
+    dev: true
+
+  /hast-util-to-string@3.0.0:
+    resolution: {integrity: sha512-OGkAxX1Ua3cbcW6EJ5pT/tslVb90uViVkcJ4ZZIMW/R33DX/AkcJcRrPebPwJkHYwlDHXz4aIwvAAaAdtrACFA==}
+    dependencies:
+      '@types/hast': 3.0.4
+    dev: true
+
   /he@1.2.0:
     resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
     hasBin: true
     dev: true
 
-  /headers-polyfill@3.2.5:
-    resolution: {integrity: sha512-tUCGvt191vNSQgttSyJoibR+VO+I6+iCHIUdhzEMJKE+EAL8BwCN7fUOZlY4ofOelNHsK+gEjxB/B+9N3EWtdA==}
+  /headers-polyfill@4.0.2:
+    resolution: {integrity: sha512-EWGTfnTqAO2L/j5HZgoM/3z82L7necsJ0pO9Tp0X1wil3PDLrkypTBRgVO2ExehEEvUycejZD3FuRaXpZZc3kw==}
     dev: true
 
   /highlight.js@10.7.3:
     resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==}
     dev: false
 
-  /highlight.js@11.8.0:
-    resolution: {integrity: sha512-MedQhoqVdr0U6SSnWPzfiadUcDHfN/Wzq25AkXiQv9oiOO/sG0S7XkvpFIqWBl9Yq1UYyYOOVORs5UW2XlPyzg==}
+  /highlight.js@11.9.0:
+    resolution: {integrity: sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==}
     engines: {node: '>=12.0.0'}
     dev: false
 
@@ -13002,6 +12900,11 @@ packages:
     engines: {node: '>=8'}
     dev: true
 
+  /htmlescape@1.1.1:
+    resolution: {integrity: sha512-eVcrzgbR4tim7c7soKQKtxa/kQM4TzjnlU83rcZ9bHU6t31ehfV7SktN6McWgwPWg+JYMA/O3qpGxBvFq1z2Jg==}
+    engines: {node: '>=0.10'}
+    dev: false
+
   /htmlparser2@8.0.1:
     resolution: {integrity: sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==}
     dependencies:
@@ -13032,8 +12935,8 @@ packages:
       statuses: 2.0.1
       toidentifier: 1.0.1
 
-  /http-link-header@1.1.1:
-    resolution: {integrity: sha512-mW3N/rTYpCn99s1do0zx6nzFZSwLH9HGfUM4ZqLWJ16ylmYaC2v5eYGqrNTQlByx8AzUgGI+V/32gXPugs1+Sw==}
+  /http-link-header@1.1.2:
+    resolution: {integrity: sha512-6qz1XhMq/ryde52SZGzVhzi3jcG2KqO16KITkupyQxvW6u7iylm0Fq7r3OpCYsc0S0ELlCiFpuxDcccUwjbEqA==}
     engines: {node: '>=6.0.0'}
     dev: false
 
@@ -13071,23 +12974,11 @@ packages:
       quick-lru: 5.1.1
       resolve-alpn: 1.2.1
 
-  /http_ece@1.1.0:
-    resolution: {integrity: sha512-bptAfCDdPJxOs5zYSe7Y3lpr772s1G346R4Td5LgRUeCwIGpCGDUTJxRrhTNcAXbx37spge0kWEIH7QAYWNTlA==}
-    engines: {node: '>=4'}
-    dependencies:
-      urlsafe-base64: 1.0.0
+  /http_ece@1.2.0:
+    resolution: {integrity: sha512-JrF8SSLVmcvc5NducxgyOrKXe3EsyHMgBFgSaIUGmArKe+rwr0uphRkRXvwiom3I+fpIfoItveHrfudL8/rxuA==}
+    engines: {node: '>=16'}
     dev: false
 
-  /https-proxy-agent@4.0.0:
-    resolution: {integrity: sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==}
-    engines: {node: '>= 6.0.0'}
-    dependencies:
-      agent-base: 5.1.1
-      debug: 4.3.4(supports-color@8.1.1)
-    transitivePeerDependencies:
-      - supports-color
-    dev: true
-
   /https-proxy-agent@5.0.1:
     resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
     engines: {node: '>= 6'}
@@ -13096,16 +12987,7 @@ packages:
       debug: 4.3.4(supports-color@8.1.1)
     transitivePeerDependencies:
       - supports-color
-
-  /https-proxy-agent@7.0.0:
-    resolution: {integrity: sha512-0euwPCRyAPSgGdzD1IVN9nJYHtBhJwb6XPfbpQcYbPCwrBidX6GzxmchnaF4sfF/jPb74Ojx5g4yTg3sixlyPw==}
-    engines: {node: '>= 14'}
-    dependencies:
-      agent-base: 7.1.0
-      debug: 4.3.4(supports-color@8.1.1)
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
+    dev: true
 
   /https-proxy-agent@7.0.2:
     resolution: {integrity: sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==}
@@ -13126,6 +13008,11 @@ packages:
     resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
     engines: {node: '>=10.17.0'}
 
+  /human-signals@3.0.1:
+    resolution: {integrity: sha512-rQLskxnM/5OCldHo+wNXbpVgDn5A17CUoKX+7Sokwaknlq7CdSnphy0W39GU8dw59XiCXmFXDg4fRuckQRKewQ==}
+    engines: {node: '>=12.20.0'}
+    dev: true
+
   /human-signals@5.0.0:
     resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
     engines: {node: '>=16.17.0'}
@@ -13153,6 +13040,13 @@ packages:
     resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==}
     dev: true
 
+  /ignore-walk@6.0.4:
+    resolution: {integrity: sha512-t7sv42WkwFkyKbivUCglsQW5YWMskWtbEf4MNKX5u/CCWHKSPzN4FtBQGsQZgCLbxOzpVlcbWVK5KB3auIOjSw==}
+    engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+    dependencies:
+      minimatch: 9.0.3
+    dev: false
+
   /ignore@5.3.0:
     resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==}
     engines: {node: '>= 4'}
@@ -13190,6 +13084,11 @@ packages:
     resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
     engines: {node: '>=8'}
 
+  /indent-string@5.0.0:
+    resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==}
+    engines: {node: '>=12'}
+    dev: true
+
   /inflight@1.0.6:
     resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
     dependencies:
@@ -13201,6 +13100,7 @@ packages:
 
   /ini@1.3.8:
     resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==}
+    dev: true
 
   /ini@2.0.0:
     resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==}
@@ -13250,12 +13150,6 @@ packages:
     resolution: {integrity: sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==}
     dev: true
 
-  /invariant@2.2.4:
-    resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
-    dependencies:
-      loose-envify: 1.4.0
-    dev: true
-
   /ioredis@5.3.2:
     resolution: {integrity: sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==}
     engines: {node: '>=12.22.0'}
@@ -13295,6 +13189,11 @@ packages:
 
   /ip@2.0.0:
     resolution: {integrity: sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==}
+    dev: false
+
+  /ip@2.0.1:
+    resolution: {integrity: sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ==}
+    dev: true
 
   /ipaddr.js@1.9.1:
     resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==}
@@ -13309,9 +13208,9 @@ packages:
     engines: {node: '>=8'}
     dev: true
 
-  /is-absolute-url@3.0.3:
-    resolution: {integrity: sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==}
-    engines: {node: '>=8'}
+  /is-absolute-url@4.0.1:
+    resolution: {integrity: sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
     dev: true
 
   /is-arguments@1.1.1:
@@ -13336,6 +13235,7 @@ packages:
 
   /is-arrayish@0.3.2:
     resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
+    dev: false
 
   /is-bigint@1.0.4:
     resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==}
@@ -13501,6 +13401,11 @@ packages:
     resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==}
     engines: {node: '>=0.10.0'}
 
+  /is-plain-obj@4.1.0:
+    resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
+    engines: {node: '>=12'}
+    dev: true
+
   /is-plain-object@2.0.4:
     resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==}
     engines: {node: '>=0.10.0'}
@@ -13642,11 +13547,6 @@ packages:
     resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==}
     dev: true
 
-  /istanbul-lib-coverage@3.2.0:
-    resolution: {integrity: sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==}
-    engines: {node: '>=8'}
-    dev: true
-
   /istanbul-lib-coverage@3.2.2:
     resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==}
     engines: {node: '>=8'}
@@ -13657,7 +13557,7 @@ packages:
     engines: {node: '>=8'}
     dependencies:
       '@babel/core': 7.23.3
-      '@babel/parser': 7.23.4
+      '@babel/parser': 7.23.6
       '@istanbuljs/schema': 0.1.3
       istanbul-lib-coverage: 3.2.2
       semver: 6.3.1
@@ -13670,7 +13570,7 @@ packages:
     engines: {node: '>=10'}
     dependencies:
       '@babel/core': 7.23.3
-      '@babel/parser': 7.23.4
+      '@babel/parser': 7.23.6
       '@istanbuljs/schema': 0.1.3
       istanbul-lib-coverage: 3.2.2
       semver: 7.5.4
@@ -13682,7 +13582,7 @@ packages:
     resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==}
     engines: {node: '>=10'}
     dependencies:
-      istanbul-lib-coverage: 3.2.0
+      istanbul-lib-coverage: 3.2.2
       make-dir: 4.0.0
       supports-color: 7.2.0
     dev: true
@@ -13692,20 +13592,12 @@ packages:
     engines: {node: '>=10'}
     dependencies:
       debug: 4.3.4(supports-color@8.1.1)
-      istanbul-lib-coverage: 3.2.0
+      istanbul-lib-coverage: 3.2.2
       source-map: 0.6.1
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /istanbul-reports@3.1.5:
-    resolution: {integrity: sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==}
-    engines: {node: '>=8'}
-    dependencies:
-      html-escaper: 2.0.2
-      istanbul-lib-report: 3.0.1
-    dev: true
-
   /istanbul-reports@3.1.6:
     resolution: {integrity: sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==}
     engines: {node: '>=8'}
@@ -13717,7 +13609,6 @@ packages:
   /iterare@1.2.1:
     resolution: {integrity: sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==}
     engines: {node: '>=6'}
-    dev: false
 
   /jackspeak@2.3.6:
     resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==}
@@ -13754,7 +13645,7 @@ packages:
       '@jest/expect': 29.7.0
       '@jest/test-result': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
       chalk: 4.1.2
       co: 4.6.0
       dedent: 1.5.1
@@ -13775,7 +13666,7 @@ packages:
       - supports-color
     dev: true
 
-  /jest-cli@29.7.0(@types/node@20.10.5):
+  /jest-cli@29.7.0(@types/node@20.11.22):
     resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     hasBin: true
@@ -13789,10 +13680,10 @@ packages:
       '@jest/test-result': 29.7.0
       '@jest/types': 29.6.3
       chalk: 4.1.2
-      create-jest: 29.7.0(@types/node@20.10.5)
+      create-jest: 29.7.0(@types/node@20.11.22)
       exit: 0.1.2
       import-local: 3.1.0
-      jest-config: 29.7.0(@types/node@20.10.5)
+      jest-config: 29.7.0(@types/node@20.11.22)
       jest-util: 29.7.0
       jest-validate: 29.7.0
       yargs: 17.6.2
@@ -13803,7 +13694,7 @@ packages:
       - ts-node
     dev: true
 
-  /jest-config@29.7.0(@types/node@20.10.5):
+  /jest-config@29.7.0(@types/node@20.11.22):
     resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     peerDependencies:
@@ -13818,7 +13709,7 @@ packages:
       '@babel/core': 7.23.3
       '@jest/test-sequencer': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
       babel-jest: 29.7.0(@babel/core@7.23.3)
       chalk: 4.1.2
       ci-info: 3.9.0
@@ -13843,16 +13734,6 @@ packages:
       - supports-color
     dev: true
 
-  /jest-diff@28.1.3:
-    resolution: {integrity: sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==}
-    engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
-    dependencies:
-      chalk: 4.1.2
-      diff-sequences: 28.1.1
-      jest-get-type: 28.0.2
-      pretty-format: 28.1.3
-    dev: true
-
   /jest-diff@29.6.4:
     resolution: {integrity: sha512-9F48UxR9e4XOEZvoUXEHSWY4qC4zERJaOfrbBg9JpbJOO43R1vN76REt/aMGZoY6GD5g84nnJiBIVlscegefpw==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -13897,7 +13778,7 @@ packages:
       '@jest/environment': 29.7.0
       '@jest/fake-timers': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
       jest-mock: 29.7.0
       jest-util: 29.7.0
     dev: true
@@ -13911,11 +13792,6 @@ packages:
       - encoding
     dev: true
 
-  /jest-get-type@28.0.2:
-    resolution: {integrity: sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==}
-    engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
-    dev: true
-
   /jest-get-type@29.6.3:
     resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -13926,7 +13802,7 @@ packages:
     dependencies:
       '@jest/types': 29.6.3
       '@types/graceful-fs': 4.1.9
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
       anymatch: 3.1.3
       fb-watchman: 2.0.2
       graceful-fs: 4.2.11
@@ -13947,16 +13823,6 @@ packages:
       pretty-format: 29.7.0
     dev: true
 
-  /jest-matcher-utils@28.1.3:
-    resolution: {integrity: sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==}
-    engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
-    dependencies:
-      chalk: 4.1.2
-      jest-diff: 28.1.3
-      jest-get-type: 28.0.2
-      pretty-format: 28.1.3
-    dev: true
-
   /jest-matcher-utils@29.7.0:
     resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -13985,7 +13851,7 @@ packages:
     engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
     dependencies:
       '@jest/types': 27.5.1
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
     dev: true
 
   /jest-mock@29.7.0:
@@ -13993,7 +13859,7 @@ packages:
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
       '@jest/types': 29.6.3
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
       jest-util: 29.7.0
     dev: true
 
@@ -14048,7 +13914,7 @@ packages:
       '@jest/test-result': 29.7.0
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
       chalk: 4.1.2
       emittery: 0.13.1
       graceful-fs: 4.2.11
@@ -14079,7 +13945,7 @@ packages:
       '@jest/test-result': 29.7.0
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
       chalk: 4.1.2
       cjs-module-lexer: 1.2.2
       collect-v8-coverage: 1.0.1
@@ -14103,10 +13969,10 @@ packages:
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
       '@babel/core': 7.23.3
-      '@babel/generator': 7.23.4
+      '@babel/generator': 7.23.6
       '@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.23.3)
       '@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.23.3)
-      '@babel/types': 7.23.4
+      '@babel/types': 7.24.0
       '@jest/expect-utils': 29.7.0
       '@jest/transform': 29.7.0
       '@jest/types': 29.6.3
@@ -14131,7 +13997,7 @@ packages:
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
       '@jest/types': 29.6.3
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
       chalk: 4.1.2
       ci-info: 3.9.0
       graceful-fs: 4.2.11
@@ -14155,7 +14021,7 @@ packages:
     dependencies:
       '@jest/test-result': 29.7.0
       '@jest/types': 29.6.3
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
       ansi-escapes: 4.3.2
       chalk: 4.1.2
       emittery: 0.13.1
@@ -14174,13 +14040,13 @@ packages:
     resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     dependencies:
-      '@types/node': 20.10.5
+      '@types/node': 20.11.22
       jest-util: 29.7.0
       merge-stream: 2.0.0
       supports-color: 8.1.1
     dev: true
 
-  /jest@29.7.0(@types/node@20.10.5):
+  /jest@29.7.0(@types/node@20.11.22):
     resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     hasBin: true
@@ -14193,7 +14059,7 @@ packages:
       '@jest/core': 29.7.0
       '@jest/types': 29.6.3
       import-local: 3.1.0
-      jest-cli: 29.7.0(@types/node@20.10.5)
+      jest-cli: 29.7.0(@types/node@20.11.22)
     transitivePeerDependencies:
       - '@types/node'
       - babel-plugin-macros
@@ -14240,11 +14106,6 @@ packages:
       nopt: 6.0.0
     dev: true
 
-  /js-levenshtein@1.1.6:
-    resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==}
-    engines: {node: '>=0.10.0'}
-    dev: true
-
   /js-stringify@1.0.2:
     resolution: {integrity: sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==}
 
@@ -14284,18 +14145,18 @@ packages:
       '@babel/preset-env':
         optional: true
     dependencies:
-      '@babel/core': 7.23.3
-      '@babel/parser': 7.23.4
-      '@babel/plugin-transform-class-properties': 7.22.5(@babel/core@7.23.3)
-      '@babel/plugin-transform-modules-commonjs': 7.23.3(@babel/core@7.23.3)
-      '@babel/plugin-transform-nullish-coalescing-operator': 7.23.4(@babel/core@7.23.3)
-      '@babel/plugin-transform-optional-chaining': 7.23.4(@babel/core@7.23.3)
-      '@babel/plugin-transform-private-methods': 7.22.5(@babel/core@7.23.3)
-      '@babel/preset-env': 7.23.6(@babel/core@7.23.3)
-      '@babel/preset-flow': 7.23.3(@babel/core@7.23.3)
-      '@babel/preset-typescript': 7.23.3(@babel/core@7.23.3)
-      '@babel/register': 7.22.15(@babel/core@7.23.3)
-      babel-core: 7.0.0-bridge.0(@babel/core@7.23.3)
+      '@babel/core': 7.24.0
+      '@babel/parser': 7.24.0
+      '@babel/plugin-transform-class-properties': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-modules-commonjs': 7.23.3(@babel/core@7.24.0)
+      '@babel/plugin-transform-nullish-coalescing-operator': 7.23.4(@babel/core@7.24.0)
+      '@babel/plugin-transform-optional-chaining': 7.23.4(@babel/core@7.24.0)
+      '@babel/plugin-transform-private-methods': 7.23.3(@babel/core@7.24.0)
+      '@babel/preset-env': 7.23.6(@babel/core@7.24.0)
+      '@babel/preset-flow': 7.23.3(@babel/core@7.24.0)
+      '@babel/preset-typescript': 7.23.3(@babel/core@7.24.0)
+      '@babel/register': 7.22.15(@babel/core@7.24.0)
+      babel-core: 7.0.0-bridge.0(@babel/core@7.24.0)
       chalk: 4.1.2
       flow-parser: 0.202.0
       graceful-fs: 4.2.11
@@ -14309,8 +14170,8 @@ packages:
       - supports-color
     dev: true
 
-  /jsdom@23.0.1(bufferutil@4.0.7)(utf-8-validate@6.0.3):
-    resolution: {integrity: sha512-2i27vgvlUsGEBO9+/kJQRbtqtm+191b5zAZrU/UezVmnC2dlDAFLgDYJvAEi94T4kjsRKkezEtLQTgsNEsW2lQ==}
+  /jsdom@23.2.0(bufferutil@4.0.7)(utf-8-validate@6.0.3):
+    resolution: {integrity: sha512-L88oL7D/8ufIES+Zjz7v0aes+oBMh2Xnh3ygWvL0OaICOomKEPKuPnIfBJekiXr+BHbbMjrWn/xqrDQuxFTeyA==}
     engines: {node: '>=18'}
     peerDependencies:
       canvas: ^2.11.2
@@ -14318,7 +14179,8 @@ packages:
       canvas:
         optional: true
     dependencies:
-      cssstyle: 3.0.0
+      '@asamuzakjp/dom-selector': 2.0.2
+      cssstyle: 4.0.1
       data-urls: 5.0.0
       decimal.js: 10.4.3
       form-data: 4.0.0
@@ -14326,7 +14188,6 @@ packages:
       http-proxy-agent: 7.0.0
       https-proxy-agent: 7.0.2
       is-potential-custom-element-name: 1.0.1
-      nwsapi: 2.2.7
       parse5: 7.1.2
       rrweb-cssom: 0.6.0
       saxes: 6.0.0
@@ -14337,7 +14198,7 @@ packages:
       whatwg-encoding: 3.1.1
       whatwg-mimetype: 4.0.0
       whatwg-url: 14.0.0
-      ws: 8.15.1(bufferutil@4.0.7)(utf-8-validate@6.0.3)
+      ws: 8.16.0(bufferutil@4.0.7)(utf-8-validate@6.0.3)
       xml-name-validator: 5.0.0
     transitivePeerDependencies:
       - bufferutil
@@ -14363,17 +14224,6 @@ packages:
     resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
     dev: true
 
-  /json-schema-resolver@2.0.0:
-    resolution: {integrity: sha512-pJ4XLQP4Q9HTxl6RVDLJ8Cyh1uitSs0CzDBAz1uoJ4sRD/Bk7cFSXL1FUXDW3zJ7YnfliJx6eu8Jn283bpZ4Yg==}
-    engines: {node: '>=10'}
-    dependencies:
-      debug: 4.3.4(supports-color@8.1.1)
-      rfdc: 1.3.0
-      uri-js: 4.4.1
-    transitivePeerDependencies:
-      - supports-color
-    dev: false
-
   /json-schema-traverse@0.4.1:
     resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
     dev: true
@@ -14391,6 +14241,14 @@ packages:
   /json-stringify-safe@5.0.1:
     resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
 
+  /json-to-ast@2.1.0:
+    resolution: {integrity: sha512-W9Lq347r8tA1DfMvAGn9QNcgYm4Wm7Yc+k8e6vezpMnRT+NHbtlxgNBXRVjXe9YM6eTn6+p/MKOlV/aABJcSnQ==}
+    engines: {node: '>= 4'}
+    dependencies:
+      code-error-fragment: 0.0.230
+      grapheme-splitter: 1.0.4
+    dev: true
+
   /json5@1.0.2:
     resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==}
     hasBin: true
@@ -14404,6 +14262,7 @@ packages:
 
   /jsonc-parser@3.2.0:
     resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==}
+    dev: true
 
   /jsonfile@4.0.0:
     resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
@@ -14438,6 +14297,11 @@ packages:
       - web-streams-polyfill
     dev: false
 
+  /jsonpointer@5.0.1:
+    resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==}
+    engines: {node: '>=0.10.0'}
+    dev: true
+
   /jsprim@1.4.2:
     resolution: {integrity: sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==}
     engines: {node: '>=0.6.0'}
@@ -14458,8 +14322,8 @@ packages:
       verror: 1.10.0
     dev: true
 
-  /jsrsasign@10.9.0:
-    resolution: {integrity: sha512-QWLUikj1SBJGuyGK8tjKSx3K7Y69KYJnrs/pQ1KZ6wvZIkHkWjZ1PJDpuvc1/28c1uP0KW9qn1eI1LzHQqDOwQ==}
+  /jsrsasign@11.1.0:
+    resolution: {integrity: sha512-Ov74K9GihaK9/9WncTe1mPmvrO7Py665TUfUKvraXBpu+xcTWitrtuOwcjf4KMU9maPaYn0OuaWy0HOzy/GBXg==}
     dev: false
 
   /jssha@3.3.1:
@@ -14575,8 +14439,8 @@ packages:
       set-cookie-parser: 2.6.0
     dev: false
 
-  /lilconfig@3.0.0:
-    resolution: {integrity: sha512-K2U4W2Ff5ibV7j7ydLr+zLAkIg5JJ4lPn1Ltsdt+Tz/IjQ8buJ55pZAxoP34lqIiwtF9iAvtLv3JGv7CAyAg+g==}
+  /lilconfig@3.1.1:
+    resolution: {integrity: sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==}
     engines: {node: '>=14'}
     dev: false
 
@@ -14685,6 +14549,10 @@ packages:
       wrap-ansi: 6.2.0
     dev: true
 
+  /longest-streak@3.1.0:
+    resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
+    dev: true
+
   /loose-envify@1.4.0:
     resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
     hasBin: true
@@ -14692,8 +14560,8 @@ packages:
       js-tokens: 4.0.0
     dev: true
 
-  /loupe@2.3.6:
-    resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==}
+  /loupe@2.3.7:
+    resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==}
     dependencies:
       get-func-name: 2.0.2
     dev: true
@@ -14756,15 +14624,15 @@ packages:
       '@jridgewell/sourcemap-codec': 1.4.15
     dev: true
 
-  /magic-string@0.30.3:
-    resolution: {integrity: sha512-B7xGbll2fG/VjP+SWg4sX3JynwIU0mjoTc6MPpKNuIvftk6u6vqhDnk1R80b8C2GBR6ywqy+1DcKBrevBg+bmw==}
+  /magic-string@0.30.5:
+    resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==}
     engines: {node: '>=12'}
     dependencies:
       '@jridgewell/sourcemap-codec': 1.4.15
     dev: true
 
-  /magic-string@0.30.5:
-    resolution: {integrity: sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA==}
+  /magic-string@0.30.7:
+    resolution: {integrity: sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==}
     engines: {node: '>=12'}
     dependencies:
       '@jridgewell/sourcemap-codec': 1.4.15
@@ -14786,6 +14654,7 @@ packages:
     engines: {node: '>=8'}
     dependencies:
       semver: 6.3.1
+    dev: true
 
   /make-dir@4.0.0:
     resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==}
@@ -14841,8 +14710,12 @@ packages:
     resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==}
     dev: true
 
-  /markdown-to-jsx@7.2.0(react@18.2.0):
-    resolution: {integrity: sha512-3l4/Bigjm4bEqjCR6Xr+d4DtM1X6vvtGsMGSjJYyep8RjjIvcWtrXBS8Wbfe1/P+atKNMccpsraESIaWVplzVg==}
+  /markdown-table@3.0.3:
+    resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==}
+    dev: true
+
+  /markdown-to-jsx@7.3.2(react@18.2.0):
+    resolution: {integrity: sha512-B+28F5ucp83aQm+OxNrPkS8z0tMKaeHiy0lHJs3LqCyDQFtWuenaIrkaVTgAm1pf1AU85LXltva86hlaT17i8Q==}
     engines: {node: '>= 10'}
     peerDependencies:
       react: '>= 0.14.0'
@@ -14860,14 +14733,127 @@ packages:
     resolution: {integrity: sha512-v2huwvQGOHTGOkMqtHd2hercCG3f6QAObTisPPHg8TZqq2lz7eIY/5i/5YUV8Ibf3mEioFEmwibcPUF2/fnKKQ==}
     dev: false
 
-  /mdast-util-definitions@4.0.0:
-    resolution: {integrity: sha512-k8AJ6aNnUkB7IE+5azR9h81O5EQ/cTDXtWdMq9Kk5KcEW/8ritU5CeLg/9HhOC++nALHBlaogJ5jz0Ybk3kPMQ==}
+  /mdast-util-find-and-replace@3.0.1:
+    resolution: {integrity: sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==}
     dependencies:
-      unist-util-visit: 2.0.3
+      '@types/mdast': 4.0.3
+      escape-string-regexp: 5.0.0
+      unist-util-is: 6.0.0
+      unist-util-visit-parents: 6.0.1
     dev: true
 
-  /mdast-util-to-string@1.1.0:
-    resolution: {integrity: sha512-jVU0Nr2B9X3MU4tSK7JP1CMkSvOj7X5l/GboG1tKRw52lLF1x2Ju92Ms9tNetCcbfX3hzlM73zYo2NKkWSfF/A==}
+  /mdast-util-from-markdown@2.0.0:
+    resolution: {integrity: sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA==}
+    dependencies:
+      '@types/mdast': 4.0.3
+      '@types/unist': 3.0.2
+      decode-named-character-reference: 1.0.2
+      devlop: 1.1.0
+      mdast-util-to-string: 4.0.0
+      micromark: 4.0.0
+      micromark-util-decode-numeric-character-reference: 2.0.1
+      micromark-util-decode-string: 2.0.0
+      micromark-util-normalize-identifier: 2.0.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+      unist-util-stringify-position: 4.0.0
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /mdast-util-gfm-autolink-literal@2.0.0:
+    resolution: {integrity: sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==}
+    dependencies:
+      '@types/mdast': 4.0.3
+      ccount: 2.0.1
+      devlop: 1.1.0
+      mdast-util-find-and-replace: 3.0.1
+      micromark-util-character: 2.1.0
+    dev: true
+
+  /mdast-util-gfm-footnote@2.0.0:
+    resolution: {integrity: sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==}
+    dependencies:
+      '@types/mdast': 4.0.3
+      devlop: 1.1.0
+      mdast-util-from-markdown: 2.0.0
+      mdast-util-to-markdown: 2.1.0
+      micromark-util-normalize-identifier: 2.0.0
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /mdast-util-gfm-strikethrough@2.0.0:
+    resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==}
+    dependencies:
+      '@types/mdast': 4.0.3
+      mdast-util-from-markdown: 2.0.0
+      mdast-util-to-markdown: 2.1.0
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /mdast-util-gfm-table@2.0.0:
+    resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==}
+    dependencies:
+      '@types/mdast': 4.0.3
+      devlop: 1.1.0
+      markdown-table: 3.0.3
+      mdast-util-from-markdown: 2.0.0
+      mdast-util-to-markdown: 2.1.0
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /mdast-util-gfm-task-list-item@2.0.0:
+    resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==}
+    dependencies:
+      '@types/mdast': 4.0.3
+      devlop: 1.1.0
+      mdast-util-from-markdown: 2.0.0
+      mdast-util-to-markdown: 2.1.0
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /mdast-util-gfm@3.0.0:
+    resolution: {integrity: sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==}
+    dependencies:
+      mdast-util-from-markdown: 2.0.0
+      mdast-util-gfm-autolink-literal: 2.0.0
+      mdast-util-gfm-footnote: 2.0.0
+      mdast-util-gfm-strikethrough: 2.0.0
+      mdast-util-gfm-table: 2.0.0
+      mdast-util-gfm-task-list-item: 2.0.0
+      mdast-util-to-markdown: 2.1.0
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /mdast-util-phrasing@4.1.0:
+    resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==}
+    dependencies:
+      '@types/mdast': 4.0.3
+      unist-util-is: 6.0.0
+    dev: true
+
+  /mdast-util-to-markdown@2.1.0:
+    resolution: {integrity: sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==}
+    dependencies:
+      '@types/mdast': 4.0.3
+      '@types/unist': 3.0.2
+      longest-streak: 3.1.0
+      mdast-util-phrasing: 4.1.0
+      mdast-util-to-string: 4.0.0
+      micromark-util-decode-string: 2.0.0
+      unist-util-visit: 5.0.0
+      zwitch: 2.0.4
+    dev: true
+
+  /mdast-util-to-string@4.0.0:
+    resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==}
+    dependencies:
+      '@types/mdast': 4.0.3
     dev: true
 
   /mdn-data@2.0.28:
@@ -14882,8 +14868,8 @@ packages:
     resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==}
     engines: {node: '>= 0.6'}
 
-  /meilisearch@0.36.0:
-    resolution: {integrity: sha512-swcvEYrct0/zsGj3jlbPm1OYxbH14IURnlysKlXywNicIQ5EMkSYLYCLCwOuBKAaGcdISWdgdylH9TXVLegmOQ==}
+  /meilisearch@0.37.0:
+    resolution: {integrity: sha512-LdbK6JmRghCawrmWKJSEQF0OiE82md+YqJGE/U2JcCD8ROwlhTx0KM6NX4rQt0u0VpV0QZVG9umYiu3CSSIJAQ==}
     dependencies:
       cross-fetch: 3.1.6
     transitivePeerDependencies:
@@ -14935,6 +14921,253 @@ packages:
       parse5: 7.1.2
     dev: false
 
+  /micromark-core-commonmark@2.0.0:
+    resolution: {integrity: sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA==}
+    dependencies:
+      decode-named-character-reference: 1.0.2
+      devlop: 1.1.0
+      micromark-factory-destination: 2.0.0
+      micromark-factory-label: 2.0.0
+      micromark-factory-space: 2.0.0
+      micromark-factory-title: 2.0.0
+      micromark-factory-whitespace: 2.0.0
+      micromark-util-character: 2.1.0
+      micromark-util-chunked: 2.0.0
+      micromark-util-classify-character: 2.0.0
+      micromark-util-html-tag-name: 2.0.0
+      micromark-util-normalize-identifier: 2.0.0
+      micromark-util-resolve-all: 2.0.0
+      micromark-util-subtokenize: 2.0.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+    dev: true
+
+  /micromark-extension-gfm-autolink-literal@2.0.0:
+    resolution: {integrity: sha512-rTHfnpt/Q7dEAK1Y5ii0W8bhfJlVJFnJMHIPisfPK3gpVNuOP0VnRl96+YJ3RYWV/P4gFeQoGKNlT3RhuvpqAg==}
+    dependencies:
+      micromark-util-character: 2.1.0
+      micromark-util-sanitize-uri: 2.0.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+    dev: true
+
+  /micromark-extension-gfm-footnote@2.0.0:
+    resolution: {integrity: sha512-6Rzu0CYRKDv3BfLAUnZsSlzx3ak6HAoI85KTiijuKIz5UxZxbUI+pD6oHgw+6UtQuiRwnGRhzMmPRv4smcz0fg==}
+    dependencies:
+      devlop: 1.1.0
+      micromark-core-commonmark: 2.0.0
+      micromark-factory-space: 2.0.0
+      micromark-util-character: 2.1.0
+      micromark-util-normalize-identifier: 2.0.0
+      micromark-util-sanitize-uri: 2.0.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+    dev: true
+
+  /micromark-extension-gfm-strikethrough@2.0.0:
+    resolution: {integrity: sha512-c3BR1ClMp5fxxmwP6AoOY2fXO9U8uFMKs4ADD66ahLTNcwzSCyRVU4k7LPV5Nxo/VJiR4TdzxRQY2v3qIUceCw==}
+    dependencies:
+      devlop: 1.1.0
+      micromark-util-chunked: 2.0.0
+      micromark-util-classify-character: 2.0.0
+      micromark-util-resolve-all: 2.0.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+    dev: true
+
+  /micromark-extension-gfm-table@2.0.0:
+    resolution: {integrity: sha512-PoHlhypg1ItIucOaHmKE8fbin3vTLpDOUg8KAr8gRCF1MOZI9Nquq2i/44wFvviM4WuxJzc3demT8Y3dkfvYrw==}
+    dependencies:
+      devlop: 1.1.0
+      micromark-factory-space: 2.0.0
+      micromark-util-character: 2.1.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+    dev: true
+
+  /micromark-extension-gfm-tagfilter@2.0.0:
+    resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==}
+    dependencies:
+      micromark-util-types: 2.0.0
+    dev: true
+
+  /micromark-extension-gfm-task-list-item@2.0.1:
+    resolution: {integrity: sha512-cY5PzGcnULaN5O7T+cOzfMoHjBW7j+T9D2sucA5d/KbsBTPcYdebm9zUd9zzdgJGCwahV+/W78Z3nbulBYVbTw==}
+    dependencies:
+      devlop: 1.1.0
+      micromark-factory-space: 2.0.0
+      micromark-util-character: 2.1.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+    dev: true
+
+  /micromark-extension-gfm@3.0.0:
+    resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==}
+    dependencies:
+      micromark-extension-gfm-autolink-literal: 2.0.0
+      micromark-extension-gfm-footnote: 2.0.0
+      micromark-extension-gfm-strikethrough: 2.0.0
+      micromark-extension-gfm-table: 2.0.0
+      micromark-extension-gfm-tagfilter: 2.0.0
+      micromark-extension-gfm-task-list-item: 2.0.1
+      micromark-util-combine-extensions: 2.0.0
+      micromark-util-types: 2.0.0
+    dev: true
+
+  /micromark-factory-destination@2.0.0:
+    resolution: {integrity: sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==}
+    dependencies:
+      micromark-util-character: 2.1.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+    dev: true
+
+  /micromark-factory-label@2.0.0:
+    resolution: {integrity: sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==}
+    dependencies:
+      devlop: 1.1.0
+      micromark-util-character: 2.1.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+    dev: true
+
+  /micromark-factory-space@2.0.0:
+    resolution: {integrity: sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==}
+    dependencies:
+      micromark-util-character: 2.1.0
+      micromark-util-types: 2.0.0
+    dev: true
+
+  /micromark-factory-title@2.0.0:
+    resolution: {integrity: sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==}
+    dependencies:
+      micromark-factory-space: 2.0.0
+      micromark-util-character: 2.1.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+    dev: true
+
+  /micromark-factory-whitespace@2.0.0:
+    resolution: {integrity: sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==}
+    dependencies:
+      micromark-factory-space: 2.0.0
+      micromark-util-character: 2.1.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+    dev: true
+
+  /micromark-util-character@2.1.0:
+    resolution: {integrity: sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==}
+    dependencies:
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+    dev: true
+
+  /micromark-util-chunked@2.0.0:
+    resolution: {integrity: sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==}
+    dependencies:
+      micromark-util-symbol: 2.0.0
+    dev: true
+
+  /micromark-util-classify-character@2.0.0:
+    resolution: {integrity: sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==}
+    dependencies:
+      micromark-util-character: 2.1.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+    dev: true
+
+  /micromark-util-combine-extensions@2.0.0:
+    resolution: {integrity: sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==}
+    dependencies:
+      micromark-util-chunked: 2.0.0
+      micromark-util-types: 2.0.0
+    dev: true
+
+  /micromark-util-decode-numeric-character-reference@2.0.1:
+    resolution: {integrity: sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==}
+    dependencies:
+      micromark-util-symbol: 2.0.0
+    dev: true
+
+  /micromark-util-decode-string@2.0.0:
+    resolution: {integrity: sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==}
+    dependencies:
+      decode-named-character-reference: 1.0.2
+      micromark-util-character: 2.1.0
+      micromark-util-decode-numeric-character-reference: 2.0.1
+      micromark-util-symbol: 2.0.0
+    dev: true
+
+  /micromark-util-encode@2.0.0:
+    resolution: {integrity: sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==}
+    dev: true
+
+  /micromark-util-html-tag-name@2.0.0:
+    resolution: {integrity: sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==}
+    dev: true
+
+  /micromark-util-normalize-identifier@2.0.0:
+    resolution: {integrity: sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==}
+    dependencies:
+      micromark-util-symbol: 2.0.0
+    dev: true
+
+  /micromark-util-resolve-all@2.0.0:
+    resolution: {integrity: sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==}
+    dependencies:
+      micromark-util-types: 2.0.0
+    dev: true
+
+  /micromark-util-sanitize-uri@2.0.0:
+    resolution: {integrity: sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==}
+    dependencies:
+      micromark-util-character: 2.1.0
+      micromark-util-encode: 2.0.0
+      micromark-util-symbol: 2.0.0
+    dev: true
+
+  /micromark-util-subtokenize@2.0.0:
+    resolution: {integrity: sha512-vc93L1t+gpR3p8jxeVdaYlbV2jTYteDje19rNSS/H5dlhxUYll5Fy6vJ2cDwP8RnsXi818yGty1ayP55y3W6fg==}
+    dependencies:
+      devlop: 1.1.0
+      micromark-util-chunked: 2.0.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+    dev: true
+
+  /micromark-util-symbol@2.0.0:
+    resolution: {integrity: sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==}
+    dev: true
+
+  /micromark-util-types@2.0.0:
+    resolution: {integrity: sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==}
+    dev: true
+
+  /micromark@4.0.0:
+    resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==}
+    dependencies:
+      '@types/debug': 4.1.12
+      debug: 4.3.4(supports-color@8.1.1)
+      decode-named-character-reference: 1.0.2
+      devlop: 1.1.0
+      micromark-core-commonmark: 2.0.0
+      micromark-factory-space: 2.0.0
+      micromark-util-character: 2.1.0
+      micromark-util-chunked: 2.0.0
+      micromark-util-combine-extensions: 2.0.0
+      micromark-util-decode-numeric-character-reference: 2.0.1
+      micromark-util-encode: 2.0.0
+      micromark-util-normalize-identifier: 2.0.0
+      micromark-util-resolve-all: 2.0.0
+      micromark-util-sanitize-uri: 2.0.0
+      micromark-util-subtokenize: 2.0.0
+      micromark-util-symbol: 2.0.0
+      micromark-util-types: 2.0.0
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
   /micromatch@4.0.5:
     resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
     engines: {node: '>=8.6'}
@@ -14957,12 +15190,6 @@ packages:
     engines: {node: '>=4'}
     hasBin: true
 
-  /mime@2.6.0:
-    resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==}
-    engines: {node: '>=4.0.0'}
-    hasBin: true
-    dev: true
-
   /mime@3.0.0:
     resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==}
     engines: {node: '>=10.0.0'}
@@ -15084,15 +15311,9 @@ packages:
     dependencies:
       yallist: 4.0.0
 
-  /minipass@4.2.5:
-    resolution: {integrity: sha512-+yQl7SX3bIT83Lhb4BVorMAHVuqsskxRdlmO9kTpyukp8vsm2Sn/fUOV9xlnG8/a5JsypJzap21lz/y3FBMJ8Q==}
-    engines: {node: '>=8'}
-    requiresBuild: true
-
   /minipass@5.0.0:
     resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==}
     engines: {node: '>=8'}
-    dev: false
 
   /minipass@7.0.4:
     resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==}
@@ -15107,13 +15328,13 @@ packages:
 
   /mkdirp-classic@0.5.3:
     resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
+    dev: true
 
   /mkdirp@0.5.6:
     resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
     hasBin: true
     dependencies:
       minimist: 1.2.8
-    dev: true
 
   /mkdirp@1.0.4:
     resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
@@ -15127,13 +15348,13 @@ packages:
     hasBin: true
     dev: false
 
-  /mlly@1.4.0:
-    resolution: {integrity: sha512-ua8PAThnTwpprIaU47EPeZ/bPUVp2QYBbWMphUQpVdBI3Lgqzm5KZQ45Agm3YJedHXaIHl6pBGabaLSUPPSptg==}
+  /mlly@1.5.0:
+    resolution: {integrity: sha512-NPVQvAY1xr1QoVeG0cy8yUYC7FQcOx6evl/RjT1wL5FvzPnzOysoqB/jmx/DhssT2dYa8nxECLAaFI/+gVLhDQ==}
     dependencies:
-      acorn: 8.11.2
-      pathe: 1.1.1
+      acorn: 8.11.3
+      pathe: 1.1.2
       pkg-types: 1.0.3
-      ufo: 1.1.2
+      ufo: 1.3.2
     dev: true
 
   /mnemonist@0.39.6:
@@ -15182,61 +15403,69 @@ packages:
     dev: false
     optional: true
 
-  /msgpackr@1.9.2:
-    resolution: {integrity: sha512-xtDgI3Xv0AAiZWLRGDchyzBwU6aq0rwJ+W+5Y4CZhEWtkl/hJtFFLc+3JtGTw7nz1yquxs7nL8q/yA2aqpflIQ==}
+  /msgpackr@1.10.1:
+    resolution: {integrity: sha512-r5VRLv9qouXuLiIBrLpl2d5ZvPt8svdQTl5/vMvE4nzDMyEX4sgW5yWhuBBj5UmgwOTWj8CIdSXn5sAfsHAWIQ==}
     optionalDependencies:
       msgpackr-extract: 3.0.2
     dev: false
 
-  /msw-storybook-addon@1.10.0(msw@1.3.2):
-    resolution: {integrity: sha512-soCTMTf7DnLeaMnFHPrtVgbyeFTJALVvnDHpzzXpJad+HOzJgQdwU4EAzVfDs1q+X5cVEgxOdAhSMC7ljvnSXg==}
+  /msw-storybook-addon@2.0.0-beta.1(msw@2.1.7):
+    resolution: {integrity: sha512-DRyIAMK3waEfC+pKTyiIq68OZfiZ4WZGUVAn6J4YwCRpDdoCvLzzoC2spN0Jgegx4dEmJ7589ATnS14NxqeBig==}
     peerDependencies:
-      msw: '>=0.35.0 <2.0.0'
+      msw: ^2.0.0
     dependencies:
       is-node-process: 1.2.0
-      msw: 1.3.2(typescript@5.3.3)
+      msw: 2.1.7(typescript@5.3.3)
     dev: true
 
-  /msw@1.3.2(typescript@5.3.3):
-    resolution: {integrity: sha512-wKLhFPR+NitYTkQl5047pia0reNGgf0P6a1eTnA5aNlripmiz0sabMvvHcicE8kQ3/gZcI0YiPFWmYfowfm3lA==}
-    engines: {node: '>=14'}
+  /msw@2.1.7(typescript@5.3.3):
+    resolution: {integrity: sha512-yTIYqEMqDSrdbVMrfmqP6rTKQsnIbglTvVmAHDWwNegyXPXRcV+RjsaFEqubRS266gwWCDLm9YdOkWSKLdDvJQ==}
+    engines: {node: '>=18'}
     hasBin: true
     requiresBuild: true
     peerDependencies:
-      typescript: '>= 4.4.x <= 5.2.x'
+      typescript: '>= 4.7.x <= 5.3.x'
     peerDependenciesMeta:
       typescript:
         optional: true
     dependencies:
-      '@mswjs/cookies': 0.2.2
-      '@mswjs/interceptors': 0.17.10
-      '@open-draft/until': 1.0.3
-      '@types/cookie': 0.4.1
-      '@types/js-levenshtein': 1.1.1
+      '@bundled-es-modules/cookie': 2.0.0
+      '@bundled-es-modules/statuses': 1.0.1
+      '@mswjs/cookies': 1.1.0
+      '@mswjs/interceptors': 0.25.16
+      '@open-draft/until': 2.1.0
+      '@types/cookie': 0.6.0
+      '@types/statuses': 2.0.4
       chalk: 4.1.2
       chokidar: 3.5.3
-      cookie: 0.4.2
       graphql: 16.8.1
-      headers-polyfill: 3.2.5
+      headers-polyfill: 4.0.2
       inquirer: 8.2.5
       is-node-process: 1.2.0
-      js-levenshtein: 1.1.6
-      node-fetch: 2.7.0
-      outvariant: 1.4.0
+      outvariant: 1.4.2
       path-to-regexp: 6.2.1
-      strict-event-emitter: 0.4.6
-      type-fest: 2.19.0
+      strict-event-emitter: 0.5.1
+      type-fest: 4.9.0
       typescript: 5.3.3
-      yargs: 17.6.2
-    transitivePeerDependencies:
-      - encoding
-      - supports-color
+      yargs: 17.7.2
     dev: true
 
   /muggle-string@0.3.1:
     resolution: {integrity: sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==}
     dev: true
 
+  /multer@1.4.4-lts.1:
+    resolution: {integrity: sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg==}
+    engines: {node: '>= 6.0.0'}
+    dependencies:
+      append-field: 1.0.0
+      busboy: 1.6.0
+      concat-stream: 1.6.2
+      mkdirp: 0.5.6
+      object-assign: 4.1.1
+      type-is: 1.6.18
+      xtend: 4.0.2
+
   /mute-stream@0.0.8:
     resolution: {integrity: sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==}
     dev: true
@@ -15258,26 +15487,17 @@ packages:
     resolution: {integrity: sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==}
     dev: false
 
-  /nanoid@3.3.6:
-    resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==}
-    engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
-    hasBin: true
-    dev: false
-
   /nanoid@3.3.7:
     resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
     engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
     hasBin: true
 
-  /nanoid@5.0.4:
-    resolution: {integrity: sha512-vAjmBf13gsmhXSgBrtIclinISzFFy22WwCYoyilZlsrRXNIHSwgFQ1bEdjRwMT3aoadeIF6HMuDRlOxzfXV8ig==}
+  /nanoid@5.0.6:
+    resolution: {integrity: sha512-rRq0eMHoGZxlvaFOUdK1Ev83Bd1IgzzR+WJ3IbDJ7QOSdAxYjlurSPqFs9s4lJg29RT6nPwizFtJhQS6V5xgiA==}
     engines: {node: ^18 || >=20}
     hasBin: true
     dev: false
 
-  /napi-build-utils@1.0.2:
-    resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
-
   /natural-compare@1.4.0:
     resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
     dev: true
@@ -15324,25 +15544,13 @@ packages:
       path-to-regexp: 1.8.0
     dev: true
 
-  /node-abi@3.31.0:
-    resolution: {integrity: sha512-eSKV6s+APenqVh8ubJyiu/YhZgxQpGP66ntzUb3lY1xB9ukSRaGnx0AIxI+IM+1+IVYC1oWobgG5L3Lt9ARykQ==}
-    engines: {node: '>=10'}
-    dependencies:
-      semver: 7.5.4
-
   /node-abort-controller@3.1.1:
     resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==}
     dev: false
 
-  /node-addon-api@5.1.0:
-    resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==}
-    dev: false
-
-  /node-addon-api@6.1.0:
-    resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==}
-
-  /node-addon-api@7.0.0:
-    resolution: {integrity: sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==}
+  /node-addon-api@7.1.0:
+    resolution: {integrity: sha512-mNcltoe1R8o7STTegSOHdnJNN7s5EUvhoS7ShnTHDyOSd+8H+UdWODq6qSv67PjC8Zc5JRT8+oLAMCr0SIXw7g==}
+    engines: {node: ^16 || ^18 || >= 20}
     dev: false
 
   /node-dir@0.1.17:
@@ -15391,13 +15599,6 @@ packages:
       fetch-blob: 3.2.0
       formdata-polyfill: 4.0.10
 
-  /node-gyp-build-optional-packages@5.0.3:
-    resolution: {integrity: sha512-k75jcVzk5wnnc/FMxsf4udAoTEUv2jY3ycfdSd3yWu6Cnd1oee6/CfZJApyscA4FJOmdoixWwiwOyf16RzD5JA==}
-    hasBin: true
-    requiresBuild: true
-    dev: false
-    optional: true
-
   /node-gyp-build-optional-packages@5.0.7:
     resolution: {integrity: sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w==}
     hasBin: true
@@ -15410,6 +15611,11 @@ packages:
     hasBin: true
     requiresBuild: true
 
+  /node-gyp-build@4.8.0:
+    resolution: {integrity: sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==}
+    hasBin: true
+    dev: false
+
   /node-gyp@10.0.1:
     resolution: {integrity: sha512-gg3/bHehQfZivQVfqIyy8wTdSymF9yTyP4CJifK73imyNMU8AIGQE2pUa7dNWfmMeG9cDVF2eehiRMv0LC1iAg==}
     engines: {node: ^16.14.0 || >=18.0.0}
@@ -15423,7 +15629,7 @@ packages:
       nopt: 7.2.0
       proc-log: 3.0.0
       semver: 7.5.4
-      tar: 6.1.13
+      tar: 6.2.0
       which: 4.0.0
     transitivePeerDependencies:
       - supports-color
@@ -15435,13 +15641,13 @@ packages:
 
   /node-releases@2.0.13:
     resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==}
+    dev: true
 
   /node-releases@2.0.14:
     resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
-    dev: true
 
-  /nodemailer@6.9.7:
-    resolution: {integrity: sha512-rUtR77ksqex/eZRLmQ21LKVH5nAAsVicAtAYudK7JgwenEDZ0UIQ1adUGqErz7sMkWYxWTTU1aeP2Jga6WQyJw==}
+  /nodemailer@6.9.10:
+    resolution: {integrity: sha512-qtoKfGFhvIFW5kLfrkw2R6Nm6Ur4LNUMykyqu6n9BRKJuyQrqEGwdXXUAbwWEKt33dlWUGXb7rzmJP/p4+O+CA==}
     engines: {node: '>=6.0.0'}
     dev: false
 
@@ -15462,9 +15668,27 @@ packages:
       undefsafe: 2.0.5
     dev: true
 
+  /nodemon@3.1.0:
+    resolution: {integrity: sha512-xqlktYlDMCepBJd43ZQhjWwMw2obW/JRvkrLxq5RCNcuDDX1DbcPT+qT1IlIIdf+DhnWs90JpTMe+Y5KxOchvA==}
+    engines: {node: '>=10'}
+    hasBin: true
+    dependencies:
+      chokidar: 3.5.3
+      debug: 4.3.4(supports-color@5.5.0)
+      ignore-by-default: 1.0.1
+      minimatch: 3.1.2
+      pstree.remy: 1.1.8
+      semver: 7.5.4
+      simple-update-notifier: 2.0.0
+      supports-color: 5.5.0
+      touch: 3.1.0
+      undefsafe: 2.0.5
+    dev: true
+
   /nofilter@3.1.0:
     resolution: {integrity: sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==}
     engines: {node: '>=12.19'}
+    dev: false
 
   /nopt@1.0.10:
     resolution: {integrity: sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==}
@@ -15473,15 +15697,6 @@ packages:
       abbrev: 1.1.1
     dev: true
 
-  /nopt@5.0.0:
-    resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==}
-    engines: {node: '>=6'}
-    hasBin: true
-    requiresBuild: true
-    dependencies:
-      abbrev: 1.1.1
-    dev: false
-
   /nopt@6.0.0:
     resolution: {integrity: sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==}
     engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
@@ -15549,25 +15764,11 @@ packages:
     dependencies:
       path-key: 4.0.0
 
-  /npmlog@5.0.1:
-    resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==}
-    requiresBuild: true
-    dependencies:
-      are-we-there-yet: 2.0.0
-      console-control-strings: 1.1.0
-      gauge: 3.0.2
-      set-blocking: 2.0.0
-    dev: false
-
   /nth-check@2.1.1:
     resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==}
     dependencies:
       boolbase: 1.0.0
 
-  /nwsapi@2.2.7:
-    resolution: {integrity: sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==}
-    dev: false
-
   /oauth2orize-pkce@0.1.2:
     resolution: {integrity: sha512-grto2UYhXHi9GLE3IBgBBbV87xci55+bCyjpVuxKyzol6I5Rg0K1MiTuXE+JZk54R86SG2wqXODMiZYHraPpxw==}
     dev: false
@@ -15705,9 +15906,10 @@ packages:
 
   /openapi-types@12.1.3:
     resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==}
+    dev: true
 
-  /openapi-typescript@6.7.1:
-    resolution: {integrity: sha512-Q3Ltt0KUm2smcPrsaR8qKmSwQ1KM4yGDJVoQdpYa0yvKPeN8huDx5utMT7DvwvJastHHzUxajjivK3WN2+fobg==}
+  /openapi-typescript@6.7.3:
+    resolution: {integrity: sha512-es3mGcDXV6TKPo6n3aohzHm0qxhLyR39MhF6mkD1FwFGjhxnqMqfSIgM0eCpInZvqatve4CxmXcMZw3jnnsaXw==}
     hasBin: true
     dependencies:
       ansi-colors: 4.1.3
@@ -15770,14 +15972,14 @@ packages:
     resolution: {integrity: sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==}
     dev: true
 
-  /otpauth@9.2.1:
-    resolution: {integrity: sha512-/MRvcm63pzK20NCsIOe8Btun42/yWNylPbUo/h5dMpSRJpoAJstWodEUjm4zUDeT1+Vbqif2E8IcP4trl1U4gQ==}
+  /otpauth@9.2.2:
+    resolution: {integrity: sha512-2VcnYRUmq1dNckIfySNYP32ITWp1bvTeAEW0BSCR6G3GBf3a5zb9E+ubY62t3Dma9RjoHlvd7QpmzHfJZRkiNg==}
     dependencies:
       jssha: 3.3.1
     dev: false
 
-  /outvariant@1.4.0:
-    resolution: {integrity: sha512-AlWY719RF02ujitly7Kk/0QlV+pXGFDHrHf9O2OKqyqgBieaPOIeuSkL8sRK6j2WK+/ZAURq2kZsY0d8JapUiw==}
+  /outvariant@1.4.2:
+    resolution: {integrity: sha512-Ou3dJ6bA/UJ5GVHxah4LnqDwZRwAmWxrG3wtrHrbGnP4RnLCtA64A4F+ae7Y8ww660JaddSoArUR5HjipWSHAQ==}
     dev: true
 
   /p-cancelable@2.1.1:
@@ -15943,6 +16145,7 @@ packages:
   /path-is-absolute@1.0.1:
     resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
     engines: {node: '>=0.10.0'}
+    dev: true
 
   /path-key@2.0.1:
     resolution: {integrity: sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==}
@@ -15978,7 +16181,6 @@ packages:
 
   /path-to-regexp@3.2.0:
     resolution: {integrity: sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA==}
-    dev: false
 
   /path-to-regexp@6.2.1:
     resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==}
@@ -15988,8 +16190,8 @@ packages:
     resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
     engines: {node: '>=8'}
 
-  /pathe@1.1.1:
-    resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==}
+  /pathe@1.1.2:
+    resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==}
     dev: true
 
   /pathval@1.1.1:
@@ -16115,6 +16317,13 @@ packages:
     resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
     engines: {node: '>=8.6'}
 
+  /pid-port@1.0.0:
+    resolution: {integrity: sha512-LSNBeKChRPA4Xlrs6+zV588G1hSrFvANtPV5rt/5MPfSPK3V9XPWxx1d29svsrOjngT9ifLisXWCLS7DvO9ZhQ==}
+    engines: {node: '>=18'}
+    dependencies:
+      execa: 8.0.1
+    dev: true
+
   /pify@2.3.0:
     resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
     engines: {node: '>=0.10.0'}
@@ -16135,8 +16344,8 @@ packages:
     resolution: {integrity: sha512-KO0m2f1HkrPe9S0ldjx7za9BJjeHqBku5Ch8JyxETxT8dEFGz1PwgrHaOQupVYitpzbFSYm7nnljxD8dik2c+g==}
     dev: false
 
-  /pino@8.16.0:
-    resolution: {integrity: sha512-UUmvQ/7KTZt/vHjhRrnyS7h+J7qPBQnpG80V56xmIC+o9IqYmQOw/UIny9S9zYDfRBR0ClouCr464EkBMIT7Fw==}
+  /pino@8.17.2:
+    resolution: {integrity: sha512-LA6qKgeDMLr2ux2y/YiUt47EfgQ+S9LznBWOJdN3q1dx2sv0ziDLUBeVpyVv17TEcGCBuWf0zNtg3M5m1NhhWQ==}
     hasBin: true
     dependencies:
       atomic-sleep: 1.0.0
@@ -16144,7 +16353,7 @@ packages:
       on-exit-leak-free: 2.1.0
       pino-abstract-transport: 1.1.0
       pino-std-serializers: 6.1.0
-      process-warning: 2.2.0
+      process-warning: 3.0.0
       quick-format-unescaped: 4.0.4
       real-require: 0.2.0
       safe-stable-stringify: 2.4.2
@@ -16157,8 +16366,8 @@ packages:
     engines: {node: '>= 6'}
     dev: true
 
-  /pkce-challenge@4.0.1:
-    resolution: {integrity: sha512-WGmtS1stcStsvRwNXix3iR1ujFcDaJR+sEODRa2ZFruT0lM4lhPAFTL5SUpqD5vTJdRlgtuMQhcp1kIEJx4LUw==}
+  /pkce-challenge@4.1.0:
+    resolution: {integrity: sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ==}
     engines: {node: '>=16.20.0'}
     dev: false
 
@@ -16187,8 +16396,8 @@ packages:
     resolution: {integrity: sha512-nN7pYi0AQqJnoLPC9eHFQ8AcyaixBUOwvqc5TDnIKCMEE6I0y8P7OKA7fPexsXGCGxQDl/cmrLAp26LhcwxZ4A==}
     dependencies:
       jsonc-parser: 3.2.0
-      mlly: 1.4.0
-      pathe: 1.1.1
+      mlly: 1.5.0
+      pathe: 1.1.2
     dev: true
 
   /plimit-lit@1.5.0:
@@ -16218,313 +16427,304 @@ packages:
     resolution: {integrity: sha512-Sz2Lkdxz6F2Pgnpi9U5Ng/WdWAUZxmHrNPoVlm3aAemxoy2Qy7LGjQg4uf8qKelDAUW94F4np3iH2YPf2qefcQ==}
     engines: {node: '>=10'}
     dependencies:
-      '@babel/runtime': 7.23.2
+      '@babel/runtime': 7.23.4
     dev: true
 
-  /postcss-calc@9.0.1(postcss@8.4.32):
+  /postcss-calc@9.0.1(postcss@8.4.35):
     resolution: {integrity: sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.2.2
     dependencies:
-      postcss: 8.4.32
-      postcss-selector-parser: 6.0.13
+      postcss: 8.4.35
+      postcss-selector-parser: 6.0.15
       postcss-value-parser: 4.2.0
     dev: false
 
-  /postcss-colormin@6.0.1(postcss@8.4.32):
-    resolution: {integrity: sha512-Tb9aR2wCJCzKuNjIeMzVNd0nXjQy25HDgFmmaRsHnP0eP/k8uQWE4S8voX5S2coO5CeKrp+USFs1Ayv9Tpxx6w==}
+  /postcss-colormin@6.0.3(postcss@8.4.35):
+    resolution: {integrity: sha512-ECpkS+UZRyAtu/kjive2/1mihP+GNtgC8kcdU8ueWZi1ZVxMNnRziCLdhrWECJhEtSWijfX2Cl9XTTCK/hjGaA==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      browserslist: 4.22.1
+      browserslist: 4.23.0
       caniuse-api: 3.0.0
       colord: 2.9.3
-      postcss: 8.4.32
+      postcss: 8.4.35
       postcss-value-parser: 4.2.0
     dev: false
 
-  /postcss-convert-values@6.0.1(postcss@8.4.32):
-    resolution: {integrity: sha512-zTd4Vh0HxGkhg5aHtfCogcRHzGkvblfdWlQ53lIh1cJhYcGyIxh2hgtKoVh40AMktRERet+JKdB04nNG19kjmA==}
+  /postcss-convert-values@6.0.4(postcss@8.4.35):
+    resolution: {integrity: sha512-YT2yrGzPXoQD3YeA2kBo/696qNwn7vI+15AOS2puXWEvSWqdCqlOyDWRy5GNnOc9ACRGOkuQ4ESQEqPJBWt/GA==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      browserslist: 4.22.1
-      postcss: 8.4.32
+      browserslist: 4.23.0
+      postcss: 8.4.35
       postcss-value-parser: 4.2.0
     dev: false
 
-  /postcss-discard-comments@6.0.1(postcss@8.4.32):
+  /postcss-discard-comments@6.0.1(postcss@8.4.35):
     resolution: {integrity: sha512-f1KYNPtqYLUeZGCHQPKzzFtsHaRuECe6jLakf/RjSRqvF5XHLZnM2+fXLhb8Qh/HBFHs3M4cSLb1k3B899RYIg==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      postcss: 8.4.32
+      postcss: 8.4.35
     dev: false
 
-  /postcss-discard-duplicates@6.0.1(postcss@8.4.32):
-    resolution: {integrity: sha512-1hvUs76HLYR8zkScbwyJ8oJEugfPV+WchpnA+26fpJ7Smzs51CzGBHC32RS03psuX/2l0l0UKh2StzNxOrKCYg==}
+  /postcss-discard-duplicates@6.0.2(postcss@8.4.35):
+    resolution: {integrity: sha512-U2rsj4w6pAGROCCcD13LP2eBIi1whUsXs4kgE6xkIuGfkbxCBSKhkCTWyowFd66WdVlLv0uM1euJKIgmdmZObg==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      postcss: 8.4.32
+      postcss: 8.4.35
     dev: false
 
-  /postcss-discard-empty@6.0.1(postcss@8.4.32):
-    resolution: {integrity: sha512-yitcmKwmVWtNsrrRqGJ7/C0YRy53i0mjexBDQ9zYxDwTWVBgbU4+C9jIZLmQlTDT9zhml+u0OMFJh8+31krmOg==}
+  /postcss-discard-empty@6.0.2(postcss@8.4.35):
+    resolution: {integrity: sha512-rj6pVC2dVCJrP0Y2RkYTQEbYaCf4HEm+R/2StQgJqGHxAa3+KcYslNQhcRqjLHtl/4wpzipJluaJLqBj6d5eDQ==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      postcss: 8.4.32
+      postcss: 8.4.35
     dev: false
 
-  /postcss-discard-overridden@6.0.1(postcss@8.4.32):
+  /postcss-discard-overridden@6.0.1(postcss@8.4.35):
     resolution: {integrity: sha512-qs0ehZMMZpSESbRkw1+inkf51kak6OOzNRaoLd/U7Fatp0aN2HQ1rxGOrJvYcRAN9VpX8kUF13R2ofn8OlvFVA==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      postcss: 8.4.32
+      postcss: 8.4.35
     dev: false
 
-  /postcss-merge-longhand@6.0.1(postcss@8.4.32):
-    resolution: {integrity: sha512-vmr/HZQzaPXc45FRvSctqFTF05UaDnTn5ABX+UtQPJznDWT/QaFbVc/pJ5C2YPxx2J2XcfmWowlKwtCDwiQ5hA==}
+  /postcss-merge-longhand@6.0.3(postcss@8.4.35):
+    resolution: {integrity: sha512-kF/y3DU8CRt+SX3tP/aG+2gkZI2Z7OXDsPU7FgxIJmuyhQQ1EHceIYcsp/alvzCm2P4c37Sfdu8nNrHc+YeyLg==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      postcss: 8.4.32
+      postcss: 8.4.35
       postcss-value-parser: 4.2.0
-      stylehacks: 6.0.1(postcss@8.4.32)
+      stylehacks: 6.0.3(postcss@8.4.35)
     dev: false
 
-  /postcss-merge-rules@6.0.2(postcss@8.4.32):
-    resolution: {integrity: sha512-6lm8bl0UfriSfxI+F/cezrebqqP8w702UC6SjZlUlBYwuRVNbmgcJuQU7yePIvD4MNT53r/acQCUAyulrpgmeQ==}
+  /postcss-merge-rules@6.0.4(postcss@8.4.35):
+    resolution: {integrity: sha512-97iF3UJ5v8N1BWy38y+0l+Z8o5/9uGlEgtWic2PJPzoRrLB6Gxg8TVG93O0EK52jcLeMsywre26AUlX1YAYeHA==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      browserslist: 4.22.1
+      browserslist: 4.23.0
       caniuse-api: 3.0.0
-      cssnano-utils: 4.0.1(postcss@8.4.32)
-      postcss: 8.4.32
-      postcss-selector-parser: 6.0.13
+      cssnano-utils: 4.0.1(postcss@8.4.35)
+      postcss: 8.4.35
+      postcss-selector-parser: 6.0.15
     dev: false
 
-  /postcss-minify-font-values@6.0.1(postcss@8.4.32):
-    resolution: {integrity: sha512-tIwmF1zUPoN6xOtA/2FgVk1ZKrLcCvE0dpZLtzyyte0j9zUeB8RTbCqrHZGjJlxOvNWKMYtunLrrl7HPOiR46w==}
+  /postcss-minify-font-values@6.0.2(postcss@8.4.35):
+    resolution: {integrity: sha512-IedzbVMoX0a7VZWjSYr5qJ6C37rws8kl8diPBeMZLJfWKkgXuMFY5R/OxPegn/q9tK9ztd0XRH3aR0u2t+A7uQ==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      postcss: 8.4.32
+      postcss: 8.4.35
       postcss-value-parser: 4.2.0
     dev: false
 
-  /postcss-minify-gradients@6.0.1(postcss@8.4.32):
-    resolution: {integrity: sha512-M1RJWVjd6IOLPl1hYiOd5HQHgpp6cvJVLrieQYS9y07Yo8itAr6jaekzJphaJFR0tcg4kRewCk3kna9uHBxn/w==}
+  /postcss-minify-gradients@6.0.2(postcss@8.4.35):
+    resolution: {integrity: sha512-vP5mF7iI6/5fcpv+rSfwWQekOE+8I1i7/7RjZPGuIjj6eUaZVeG4XZYZrroFuw1WQd51u2V32wyQFZ+oYdE7CA==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
       colord: 2.9.3
-      cssnano-utils: 4.0.1(postcss@8.4.32)
-      postcss: 8.4.32
+      cssnano-utils: 4.0.1(postcss@8.4.35)
+      postcss: 8.4.35
       postcss-value-parser: 4.2.0
     dev: false
 
-  /postcss-minify-params@6.0.1(postcss@8.4.32):
-    resolution: {integrity: sha512-eFvGWArqh4khPIgPDu6SZNcaLctx97nO7c59OXnRtGntAp5/VS4gjMhhW9qUFsK6mQ27pEZGt2kR+mPizI+Z9g==}
+  /postcss-minify-params@6.0.3(postcss@8.4.35):
+    resolution: {integrity: sha512-j4S74d3AAeCK5eGdQndXSrkxusV2ekOxbXGnlnZthMyZBBvSDiU34CihTASbJxuVB3bugudmwolS7+Dgs5OyOQ==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      browserslist: 4.22.1
-      cssnano-utils: 4.0.1(postcss@8.4.32)
-      postcss: 8.4.32
+      browserslist: 4.23.0
+      cssnano-utils: 4.0.1(postcss@8.4.35)
+      postcss: 8.4.35
       postcss-value-parser: 4.2.0
     dev: false
 
-  /postcss-minify-selectors@6.0.1(postcss@8.4.32):
-    resolution: {integrity: sha512-mfReq5wrS6vkunxvJp6GDuOk+Ak6JV7134gp8L+ANRnV9VwqzTvBtX6lpohooVU750AR0D3pVx2Zn6uCCwOAfQ==}
+  /postcss-minify-selectors@6.0.2(postcss@8.4.35):
+    resolution: {integrity: sha512-0b+m+w7OAvZejPQdN2GjsXLv5o0jqYHX3aoV0e7RBKPCsB7TYG5KKWBFhGnB/iP3213Ts8c5H4wLPLMm7z28Sg==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      postcss: 8.4.32
-      postcss-selector-parser: 6.0.13
+      postcss: 8.4.35
+      postcss-selector-parser: 6.0.15
     dev: false
 
-  /postcss-normalize-charset@6.0.1(postcss@8.4.32):
+  /postcss-normalize-charset@6.0.1(postcss@8.4.35):
     resolution: {integrity: sha512-aW5LbMNRZ+oDV57PF9K+WI1Z8MPnF+A8qbajg/T8PP126YrGX1f9IQx21GI2OlGz7XFJi/fNi0GTbY948XJtXg==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      postcss: 8.4.32
+      postcss: 8.4.35
     dev: false
 
-  /postcss-normalize-display-values@6.0.1(postcss@8.4.32):
+  /postcss-normalize-display-values@6.0.1(postcss@8.4.35):
     resolution: {integrity: sha512-mc3vxp2bEuCb4LgCcmG1y6lKJu1Co8T+rKHrcbShJwUmKJiEl761qb/QQCfFwlrvSeET3jksolCR/RZuMURudw==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      postcss: 8.4.32
+      postcss: 8.4.35
       postcss-value-parser: 4.2.0
     dev: false
 
-  /postcss-normalize-positions@6.0.1(postcss@8.4.32):
+  /postcss-normalize-positions@6.0.1(postcss@8.4.35):
     resolution: {integrity: sha512-HRsq8u/0unKNvm0cvwxcOUEcakFXqZ41fv3FOdPn916XFUrympjr+03oaLkuZENz3HE9RrQE9yU0Xv43ThWjQg==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      postcss: 8.4.32
+      postcss: 8.4.35
       postcss-value-parser: 4.2.0
     dev: false
 
-  /postcss-normalize-repeat-style@6.0.1(postcss@8.4.32):
+  /postcss-normalize-repeat-style@6.0.1(postcss@8.4.35):
     resolution: {integrity: sha512-Gbb2nmCy6tTiA7Sh2MBs3fj9W8swonk6lw+dFFeQT68B0Pzwp1kvisJQkdV6rbbMSd9brMlS8I8ts52tAGWmGQ==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      postcss: 8.4.32
+      postcss: 8.4.35
       postcss-value-parser: 4.2.0
     dev: false
 
-  /postcss-normalize-string@6.0.1(postcss@8.4.32):
+  /postcss-normalize-string@6.0.1(postcss@8.4.35):
     resolution: {integrity: sha512-5Fhx/+xzALJD9EI26Aq23hXwmv97Zfy2VFrt5PLT8lAhnBIZvmaT5pQk+NuJ/GWj/QWaKSKbnoKDGLbV6qnhXg==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      postcss: 8.4.32
+      postcss: 8.4.35
       postcss-value-parser: 4.2.0
     dev: false
 
-  /postcss-normalize-timing-functions@6.0.1(postcss@8.4.32):
+  /postcss-normalize-timing-functions@6.0.1(postcss@8.4.35):
     resolution: {integrity: sha512-4zcczzHqmCU7L5dqTB9rzeqPWRMc0K2HoR+Bfl+FSMbqGBUcP5LRfgcH4BdRtLuzVQK1/FHdFoGT3F7rkEnY+g==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      postcss: 8.4.32
+      postcss: 8.4.35
       postcss-value-parser: 4.2.0
     dev: false
 
-  /postcss-normalize-unicode@6.0.1(postcss@8.4.32):
-    resolution: {integrity: sha512-ok9DsI94nEF79MkvmLfHfn8ddnKXA7w+8YuUoz5m7b6TOdoaRCpvu/QMHXQs9+DwUbvp+ytzz04J55CPy77PuQ==}
+  /postcss-normalize-unicode@6.0.3(postcss@8.4.35):
+    resolution: {integrity: sha512-T2Bb3gXz0ASgc3ori2dzjv6j/P2IantreaC6fT8tWjqYUiqMAh5jGIkdPwEV2FaucjQlCLeFJDJh2BeSugE1ig==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      browserslist: 4.22.1
-      postcss: 8.4.32
+      browserslist: 4.23.0
+      postcss: 8.4.35
       postcss-value-parser: 4.2.0
     dev: false
 
-  /postcss-normalize-url@6.0.1(postcss@8.4.32):
+  /postcss-normalize-url@6.0.1(postcss@8.4.35):
     resolution: {integrity: sha512-jEXL15tXSvbjm0yzUV7FBiEXwhIa9H88JOXDGQzmcWoB4mSjZIsmtto066s2iW9FYuIrIF4k04HA2BKAOpbsaQ==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      postcss: 8.4.32
+      postcss: 8.4.35
       postcss-value-parser: 4.2.0
     dev: false
 
-  /postcss-normalize-whitespace@6.0.1(postcss@8.4.32):
+  /postcss-normalize-whitespace@6.0.1(postcss@8.4.35):
     resolution: {integrity: sha512-76i3NpWf6bB8UHlVuLRxG4zW2YykF9CTEcq/9LGAiz2qBuX5cBStadkk0jSkg9a9TCIXbMQz7yzrygKoCW9JuA==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      postcss: 8.4.32
+      postcss: 8.4.35
       postcss-value-parser: 4.2.0
     dev: false
 
-  /postcss-ordered-values@6.0.1(postcss@8.4.32):
+  /postcss-ordered-values@6.0.1(postcss@8.4.35):
     resolution: {integrity: sha512-XXbb1O/MW9HdEhnBxitZpPFbIvDgbo9NK4c/5bOfiKpnIGZDoL2xd7/e6jW5DYLsWxBbs+1nZEnVgnjnlFViaA==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      cssnano-utils: 4.0.1(postcss@8.4.32)
-      postcss: 8.4.32
+      cssnano-utils: 4.0.1(postcss@8.4.35)
+      postcss: 8.4.35
       postcss-value-parser: 4.2.0
     dev: false
 
-  /postcss-reduce-initial@6.0.1(postcss@8.4.32):
-    resolution: {integrity: sha512-cgzsI2ThG1PMSdSyM9A+bVxiiVgPIVz9f5c6H+TqEv0CA89iCOO81mwLWRWLgOKFtQkKob9nNpnkxG/1RlgFcA==}
+  /postcss-reduce-initial@6.0.3(postcss@8.4.35):
+    resolution: {integrity: sha512-w4QIR9pEa1N4xMx3k30T1vLZl6udVK2RmNqrDXhBXX9L0mBj2a8ADs8zkbaEH7eUy1m30Wyr5EBgHN31Yq1JvA==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      browserslist: 4.22.1
+      browserslist: 4.23.0
       caniuse-api: 3.0.0
-      postcss: 8.4.32
+      postcss: 8.4.35
     dev: false
 
-  /postcss-reduce-transforms@6.0.1(postcss@8.4.32):
+  /postcss-reduce-transforms@6.0.1(postcss@8.4.35):
     resolution: {integrity: sha512-fUbV81OkUe75JM+VYO1gr/IoA2b/dRiH6HvMwhrIBSUrxq3jNZQZitSnugcTLDi1KkQh1eR/zi+iyxviUNBkcQ==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      postcss: 8.4.32
+      postcss: 8.4.35
       postcss-value-parser: 4.2.0
     dev: false
 
-  /postcss-selector-parser@6.0.13:
-    resolution: {integrity: sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==}
+  /postcss-selector-parser@6.0.15:
+    resolution: {integrity: sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==}
     engines: {node: '>=4'}
     dependencies:
       cssesc: 3.0.0
       util-deprecate: 1.0.2
 
-  /postcss-svgo@6.0.1(postcss@8.4.32):
-    resolution: {integrity: sha512-eWV4Rrqa06LzTgqirOv5Ln6WTGyU7Pbeqj9WEyKo9tpnWixNATVJMeaEcOHOW1ZYyjcG8wSJwX/28DvU3oy3HA==}
+  /postcss-svgo@6.0.2(postcss@8.4.35):
+    resolution: {integrity: sha512-IH5R9SjkTkh0kfFOQDImyy1+mTCb+E830+9SV1O+AaDcoHTvfsvt6WwJeo7KwcHbFnevZVCsXhDmjFiGVuwqFQ==}
     engines: {node: ^14 || ^16 || >= 18}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      postcss: 8.4.32
+      postcss: 8.4.35
       postcss-value-parser: 4.2.0
-      svgo: 3.1.0
+      svgo: 3.2.0
     dev: false
 
-  /postcss-unique-selectors@6.0.1(postcss@8.4.32):
-    resolution: {integrity: sha512-/KCCEpNNR7oXVJ38/Id7GC9Nt0zxO1T3zVbhVaq6F6LSG+3gU3B7+QuTHfD0v8NPEHlzewAout29S0InmB78EQ==}
+  /postcss-unique-selectors@6.0.2(postcss@8.4.35):
+    resolution: {integrity: sha512-8IZGQ94nechdG7Y9Sh9FlIY2b4uS8/k8kdKRX040XHsS3B6d1HrJAkXrBSsSu4SuARruSsUjW3nlSw8BHkaAYQ==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      postcss: 8.4.32
-      postcss-selector-parser: 6.0.13
+      postcss: 8.4.35
+      postcss-selector-parser: 6.0.15
     dev: false
 
   /postcss-value-parser@4.2.0:
     resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
     dev: false
 
-  /postcss@8.4.31:
-    resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
-    engines: {node: ^10 || ^12 || >=14}
-    dependencies:
-      nanoid: 3.3.6
-      picocolors: 1.0.0
-      source-map-js: 1.0.2
-    dev: false
-
-  /postcss@8.4.32:
-    resolution: {integrity: sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==}
+  /postcss@8.4.35:
+    resolution: {integrity: sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==}
     engines: {node: ^10 || ^12 || >=14}
     dependencies:
       nanoid: 3.3.7
@@ -16579,43 +16779,19 @@ packages:
     resolution: {integrity: sha512-VdlZoocy5lCP0c/t66xAfclglEapXPCIVhqqJRncYpvbCgImF0w67aPKfbqUMr72tO2k5q0TdTZwCLjPTI6C9g==}
     dev: true
 
-  /prebuild-install@7.1.1:
-    resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==}
-    engines: {node: '>=10'}
-    hasBin: true
-    dependencies:
-      detect-libc: 2.0.2
-      expand-template: 2.0.3
-      github-from-package: 0.0.0
-      minimist: 1.2.8
-      mkdirp-classic: 0.5.3
-      napi-build-utils: 1.0.2
-      node-abi: 3.31.0
-      pump: 3.0.0
-      rc: 1.2.8
-      simple-get: 4.0.1
-      tar-fs: 2.1.1
-      tunnel-agent: 0.6.0
-
   /prelude-ls@1.2.1:
     resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
     engines: {node: '>= 0.8.0'}
     dev: true
 
-  /prettier@2.8.8:
-    resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==}
-    engines: {node: '>=10.13.0'}
-    hasBin: true
-    dev: true
-
   /prettier@3.1.0:
     resolution: {integrity: sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==}
     engines: {node: '>=14'}
     hasBin: true
     dev: true
 
-  /prettier@3.1.1:
-    resolution: {integrity: sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==}
+  /prettier@3.2.5:
+    resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==}
     engines: {node: '>=14'}
     hasBin: true
     dev: true
@@ -16634,16 +16810,6 @@ packages:
       react-is: 17.0.2
     dev: true
 
-  /pretty-format@28.1.3:
-    resolution: {integrity: sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==}
-    engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0}
-    dependencies:
-      '@jest/schemas': 28.1.3
-      ansi-regex: 5.0.1
-      ansi-styles: 5.2.0
-      react-is: 18.2.0
-    dev: true
-
   /pretty-format@29.7.0:
     resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
@@ -16680,6 +16846,13 @@ packages:
     engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
     dev: false
 
+  /process-exists@5.0.0:
+    resolution: {integrity: sha512-6QPRh5fyHD8MaXr4GYML8K/YY0Sq5dKHGIOrAKS3cYpHQdmygFCcijIu1dVoNKAZ0TWAMoeh8KDK9dF8auBkJA==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dependencies:
+      ps-list: 8.1.1
+    dev: true
+
   /process-nextick-args@2.0.1:
     resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==}
 
@@ -16687,15 +16860,14 @@ packages:
     resolution: {integrity: sha512-/1WZ8+VQjR6avWOgHeEPd7SDQmFQ1B5mC1eRXsCm5TarlNmx/wCsa5GEaxGm05BORRtyG/Ex/3xq3TuRvq57qg==}
     dev: false
 
+  /process-warning@3.0.0:
+    resolution: {integrity: sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==}
+    dev: false
+
   /process@0.11.10:
     resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
     engines: {node: '>= 0.6.0'}
 
-  /progress@2.0.3:
-    resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
-    engines: {node: '>=0.4.0'}
-    dev: true
-
   /promise-limit@2.7.0:
     resolution: {integrity: sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==}
     dev: false
@@ -16751,6 +16923,11 @@ packages:
   /proxy-from-env@1.1.0:
     resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
 
+  /ps-list@8.1.1:
+    resolution: {integrity: sha512-OPS9kEJYVmiO48u/B9qneqhkMvgCxT+Tm28VCEJpheTpl8cJ0ffZRRNgS5mrQRTrX5yRTpaJ+hRDeefXYmmorQ==}
+    engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+    dev: true
+
   /ps-tree@1.2.0:
     resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==}
     engines: {node: '>= 0.10'}
@@ -16874,26 +17051,6 @@ packages:
     resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
     engines: {node: '>=6'}
 
-  /puppeteer-core@2.1.1:
-    resolution: {integrity: sha512-n13AWriBMPYxnpbb6bnaY5YoY6rGj8vPLrz6CZF3o0qJNEwlcfJVxBzYZ0NJsQ21UbdJoijPCDrM++SUVEz7+w==}
-    engines: {node: '>=8.16.0'}
-    dependencies:
-      '@types/mime-types': 2.1.4
-      debug: 4.3.4(supports-color@8.1.1)
-      extract-zip: 1.7.0
-      https-proxy-agent: 4.0.0
-      mime: 2.6.0
-      mime-types: 2.1.35
-      progress: 2.0.3
-      proxy-from-env: 1.1.0
-      rimraf: 2.7.1
-      ws: 6.2.2
-    transitivePeerDependencies:
-      - bufferutil
-      - supports-color
-      - utf-8-validate
-    dev: true
-
   /pure-rand@6.0.4:
     resolution: {integrity: sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==}
     dev: true
@@ -16961,6 +17118,7 @@ packages:
 
   /queue-tick@1.0.1:
     resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==}
+    dev: false
 
   /quick-format-unescaped@4.0.4:
     resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
@@ -17011,16 +17169,6 @@ packages:
       http-errors: 2.0.0
       iconv-lite: 0.4.24
       unpipe: 1.0.0
-    dev: false
-
-  /rc@1.2.8:
-    resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
-    hasBin: true
-    dependencies:
-      deep-extend: 0.6.0
-      ini: 1.3.8
-      minimist: 1.2.8
-      strip-json-comments: 2.0.1
 
   /rdf-canonize@3.4.0:
     resolution: {integrity: sha512-fUeWjrkOO0t1rg7B2fdyDTvngj+9RlUyL92vOdiB7c0FPguWVsniIMjEtHH+meLBO9rzkUlUzBVXgWrjI8P9LA==}
@@ -17063,8 +17211,8 @@ packages:
     engines: {node: '>=16.14.0'}
     dependencies:
       '@babel/core': 7.23.3
-      '@babel/traverse': 7.23.4
-      '@babel/types': 7.23.4
+      '@babel/traverse': 7.24.0
+      '@babel/types': 7.24.0
       '@types/babel__core': 7.20.5
       '@types/babel__traverse': 7.20.4
       '@types/doctrine': 0.0.9
@@ -17114,60 +17262,6 @@ packages:
   /react-is@18.2.0:
     resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==}
 
-  /react-refresh@0.14.0:
-    resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==}
-    engines: {node: '>=0.10.0'}
-    dev: true
-
-  /react-remove-scroll-bar@2.3.4(react@18.2.0):
-    resolution: {integrity: sha512-63C4YQBUt0m6ALadE9XV56hV8BgJWDmmTPY758iIJjfQKt2nYwoUrPk0LXRXcB/yIj82T1/Ixfdpdk68LwIB0A==}
-    engines: {node: '>=10'}
-    peerDependencies:
-      '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
-      react: ^16.8.0 || ^17.0.0 || ^18.0.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-    dependencies:
-      react: 18.2.0
-      react-style-singleton: 2.2.1(react@18.2.0)
-      tslib: 2.6.2
-    dev: true
-
-  /react-remove-scroll@2.5.5(react@18.2.0):
-    resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==}
-    engines: {node: '>=10'}
-    peerDependencies:
-      '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
-      react: ^16.8.0 || ^17.0.0 || ^18.0.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-    dependencies:
-      react: 18.2.0
-      react-remove-scroll-bar: 2.3.4(react@18.2.0)
-      react-style-singleton: 2.2.1(react@18.2.0)
-      tslib: 2.6.2
-      use-callback-ref: 1.3.0(react@18.2.0)
-      use-sidecar: 1.1.2(react@18.2.0)
-    dev: true
-
-  /react-style-singleton@2.2.1(react@18.2.0):
-    resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==}
-    engines: {node: '>=10'}
-    peerDependencies:
-      '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
-      react: ^16.8.0 || ^17.0.0 || ^18.0.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-    dependencies:
-      get-nonce: 1.0.1
-      invariant: 2.2.4
-      react: 18.2.0
-      tslib: 2.6.2
-    dev: true
-
   /react@18.2.0:
     resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
     engines: {node: '>=0.10.0'}
@@ -17236,7 +17330,7 @@ packages:
     resolution: {integrity: sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==}
     engines: {node: '>=8'}
     dependencies:
-      readable-stream: 3.6.0
+      readable-stream: 3.6.2
     dev: false
 
   /readdir-glob@1.1.2:
@@ -17256,33 +17350,11 @@ packages:
     engines: {node: '>= 12.13.0'}
     dev: false
 
-  /recast@0.22.0:
-    resolution: {integrity: sha512-5AAx+mujtXijsEavc5lWXBPQqrM4+Dl5qNH96N2aNeuJFUzpiiToKPsxQD/zAIJHspz7zz0maX0PCtCTFVlixQ==}
-    engines: {node: '>= 4'}
-    dependencies:
-      assert: 2.0.0
-      ast-types: 0.15.2
-      esprima: 4.0.1
-      source-map: 0.6.1
-      tslib: 2.6.2
-    dev: true
-
-  /recast@0.23.1:
-    resolution: {integrity: sha512-RokaBcoxSjXUDzz1TXSZmZsSW6ZpLmlA3GGqJ8uuTrQ9hZhEz+4Tpsc+gRvYRJ2BU4H+ZyUlg91eSGDw7bwy7g==}
-    engines: {node: '>= 4'}
-    dependencies:
-      assert: 2.0.0
-      ast-types: 0.16.1
-      esprima: 4.0.1
-      source-map: 0.6.1
-      tslib: 2.6.2
-    dev: true
-
   /recast@0.23.4:
     resolution: {integrity: sha512-qtEDqIZGVcSZCHniWwZWbRy79Dc6Wp3kT/UmDA2RJKBPg7+7k51aQBZirHmUGn5uvHf2rg8DkjizrN26k61ATw==}
     engines: {node: '>= 4'}
     dependencies:
-      assert: 2.0.0
+      assert: 2.1.0
       ast-types: 0.16.1
       esprima: 4.0.1
       source-map: 0.6.1
@@ -17324,9 +17396,8 @@ packages:
       redis-errors: 1.2.0
     dev: false
 
-  /reflect-metadata@0.1.14:
-    resolution: {integrity: sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==}
-    dev: false
+  /reflect-metadata@0.2.1:
+    resolution: {integrity: sha512-i5lLI6iw9AU3Uu4szRNPPEkomnkjRTaVt9hy/bn5g/oSzekBSMeLZblcjP74AW0vBabqERLLIrz+gR8QYR54Tw==}
 
   /regenerate-unicode-properties@10.1.0:
     resolution: {integrity: sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==}
@@ -17341,6 +17412,7 @@ packages:
 
   /regenerator-runtime@0.13.11:
     resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
+    dev: false
 
   /regenerator-runtime@0.14.0:
     resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==}
@@ -17348,16 +17420,7 @@ packages:
   /regenerator-transform@0.15.2:
     resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==}
     dependencies:
-      '@babel/runtime': 7.23.2
-    dev: true
-
-  /regexp.prototype.flags@1.4.3:
-    resolution: {integrity: sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==}
-    engines: {node: '>= 0.4'}
-    dependencies:
-      call-bind: 1.0.2
-      define-properties: 1.2.0
-      functions-have-names: 1.2.3
+      '@babel/runtime': 7.23.4
     dev: true
 
   /regexp.prototype.flags@1.5.0:
@@ -17388,22 +17451,57 @@ packages:
       jsesc: 0.5.0
     dev: true
 
-  /remark-external-links@8.0.0:
-    resolution: {integrity: sha512-5vPSX0kHoSsqtdftSHhIYofVINC8qmp0nctkeU9YoJwV3YfiBRiI6cbFRJ0oI/1F9xS+bopXG0m2KS8VFscuKA==}
+  /rehype-external-links@3.0.0:
+    resolution: {integrity: sha512-yp+e5N9V3C6bwBeAC4n796kc86M4gJCdlVhiMTxIrJG5UHDMh+PJANf9heqORJbt1nrCbDwIlAZKjANIaVBbvw==}
     dependencies:
-      extend: 3.0.2
-      is-absolute-url: 3.0.3
-      mdast-util-definitions: 4.0.0
-      space-separated-tokens: 1.1.5
-      unist-util-visit: 2.0.3
+      '@types/hast': 3.0.4
+      '@ungap/structured-clone': 1.2.0
+      hast-util-is-element: 3.0.0
+      is-absolute-url: 4.0.1
+      space-separated-tokens: 2.0.2
+      unist-util-visit: 5.0.0
     dev: true
 
-  /remark-slug@6.1.0:
-    resolution: {integrity: sha512-oGCxDF9deA8phWvxFuyr3oSJsdyUAxMFbA0mZ7Y1Sas+emILtO+e5WutF9564gDsEN4IXaQXm5pFo6MLH+YmwQ==}
+  /rehype-slug@6.0.0:
+    resolution: {integrity: sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==}
     dependencies:
-      github-slugger: 1.5.0
-      mdast-util-to-string: 1.1.0
-      unist-util-visit: 2.0.3
+      '@types/hast': 3.0.4
+      github-slugger: 2.0.0
+      hast-util-heading-rank: 3.0.0
+      hast-util-to-string: 3.0.0
+      unist-util-visit: 5.0.0
+    dev: true
+
+  /remark-gfm@4.0.0:
+    resolution: {integrity: sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==}
+    dependencies:
+      '@types/mdast': 4.0.3
+      mdast-util-gfm: 3.0.0
+      micromark-extension-gfm: 3.0.0
+      remark-parse: 11.0.0
+      remark-stringify: 11.0.0
+      unified: 11.0.4
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /remark-parse@11.0.0:
+    resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==}
+    dependencies:
+      '@types/mdast': 4.0.3
+      mdast-util-from-markdown: 2.0.0
+      micromark-util-types: 2.0.0
+      unified: 11.0.4
+    transitivePeerDependencies:
+      - supports-color
+    dev: true
+
+  /remark-stringify@11.0.0:
+    resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
+    dependencies:
+      '@types/mdast': 4.0.3
+      mdast-util-to-markdown: 2.1.0
+      unified: 11.0.4
     dev: true
 
   /rename@1.0.4:
@@ -17532,45 +17630,40 @@ packages:
       glob: 7.2.3
     dev: true
 
-  /rimraf@2.7.1:
-    resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
-    hasBin: true
-    dependencies:
-      glob: 7.2.3
-    dev: true
-
   /rimraf@3.0.2:
     resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
     hasBin: true
     dependencies:
       glob: 7.2.3
-
-  /rollup@3.29.4:
-    resolution: {integrity: sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==}
-    engines: {node: '>=14.18.0', npm: '>=8.0.0'}
-    hasBin: true
-    optionalDependencies:
-      fsevents: 2.3.3
     dev: true
 
-  /rollup@4.9.1:
-    resolution: {integrity: sha512-pgPO9DWzLoW/vIhlSoDByCzcpX92bKEorbgXuZrqxByte3JFk2xSW2JEeAcyLc9Ru9pqcNNW+Ob7ntsk2oT/Xw==}
+  /rimraf@5.0.5:
+    resolution: {integrity: sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==}
+    engines: {node: '>=14'}
+    hasBin: true
+    dependencies:
+      glob: 10.3.10
+
+  /rollup@4.12.0:
+    resolution: {integrity: sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==}
     engines: {node: '>=18.0.0', npm: '>=8.0.0'}
     hasBin: true
+    dependencies:
+      '@types/estree': 1.0.5
     optionalDependencies:
-      '@rollup/rollup-android-arm-eabi': 4.9.1
-      '@rollup/rollup-android-arm64': 4.9.1
-      '@rollup/rollup-darwin-arm64': 4.9.1
-      '@rollup/rollup-darwin-x64': 4.9.1
-      '@rollup/rollup-linux-arm-gnueabihf': 4.9.1
-      '@rollup/rollup-linux-arm64-gnu': 4.9.1
-      '@rollup/rollup-linux-arm64-musl': 4.9.1
-      '@rollup/rollup-linux-riscv64-gnu': 4.9.1
-      '@rollup/rollup-linux-x64-gnu': 4.9.1
-      '@rollup/rollup-linux-x64-musl': 4.9.1
-      '@rollup/rollup-win32-arm64-msvc': 4.9.1
-      '@rollup/rollup-win32-ia32-msvc': 4.9.1
-      '@rollup/rollup-win32-x64-msvc': 4.9.1
+      '@rollup/rollup-android-arm-eabi': 4.12.0
+      '@rollup/rollup-android-arm64': 4.12.0
+      '@rollup/rollup-darwin-arm64': 4.12.0
+      '@rollup/rollup-darwin-x64': 4.12.0
+      '@rollup/rollup-linux-arm-gnueabihf': 4.12.0
+      '@rollup/rollup-linux-arm64-gnu': 4.12.0
+      '@rollup/rollup-linux-arm64-musl': 4.12.0
+      '@rollup/rollup-linux-riscv64-gnu': 4.12.0
+      '@rollup/rollup-linux-x64-gnu': 4.12.0
+      '@rollup/rollup-linux-x64-musl': 4.12.0
+      '@rollup/rollup-win32-arm64-msvc': 4.12.0
+      '@rollup/rollup-win32-ia32-msvc': 4.12.0
+      '@rollup/rollup-win32-x64-msvc': 4.12.0
       fsevents: 2.3.3
 
   /rrweb-cssom@0.6.0:
@@ -17637,19 +17730,19 @@ packages:
   /safer-buffer@2.1.2:
     resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
 
-  /sanitize-html@2.11.0:
-    resolution: {integrity: sha512-BG68EDHRaGKqlsNjJ2xUB7gpInPA8gVx/mvjO743hZaeMCZ2DwzW7xvsqZ+KNU4QKwj86HJ3uu2liISf2qBBUA==}
+  /sanitize-html@2.12.1:
+    resolution: {integrity: sha512-Plh+JAn0UVDpBRP/xEjsk+xDCoOvMBwQUf/K+/cBAVuTbtX8bj2VB7S1sL1dssVpykqp0/KPSesHrqXtokVBpA==}
     dependencies:
       deepmerge: 4.2.2
       escape-string-regexp: 4.0.0
       htmlparser2: 8.0.1
       is-plain-object: 5.0.0
       parse-srcset: 1.0.2
-      postcss: 8.4.31
+      postcss: 8.4.35
     dev: false
 
-  /sass@1.69.5:
-    resolution: {integrity: sha512-qg2+UCJibLr2LCVOt3OlPhr/dqVHWOa9XtZf2OjbLs/T4VPSJ00udtgJxH3neXZm+QqX8B+3cU7RaLqp1iVfcQ==}
+  /sass@1.71.1:
+    resolution: {integrity: sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==}
     engines: {node: '>=14.0.0'}
     hasBin: true
     dependencies:
@@ -17710,6 +17803,14 @@ packages:
     dependencies:
       lru-cache: 6.0.0
 
+  /semver@7.6.0:
+    resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==}
+    engines: {node: '>=10'}
+    hasBin: true
+    dependencies:
+      lru-cache: 6.0.0
+    dev: true
+
   /send@0.18.0:
     resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
     engines: {node: '>= 0.8.0'}
@@ -17747,6 +17848,7 @@ packages:
 
   /set-cookie-parser@2.6.0:
     resolution: {integrity: sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ==}
+    dev: false
 
   /setimmediate@1.0.5:
     resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==}
@@ -17770,35 +17872,36 @@ packages:
       kind-of: 6.0.3
     dev: true
 
-  /sharp@0.31.3:
-    resolution: {integrity: sha512-XcR4+FCLBFKw1bdB+GEhnUNXNXvnt0tDo4WsBsraKymuo/IAuPuCBVAL2wIkUw2r/dwFW5Q5+g66Kwl2dgDFVg==}
-    engines: {node: '>=14.15.0'}
+  /sharp@0.33.2:
+    resolution: {integrity: sha512-WlYOPyyPDiiM07j/UO+E720ju6gtNtHjEGg5vovUk1Lgxyjm2LFO+37Nt/UI3MMh2l6hxTWQWi7qk3cXJTutcQ==}
+    engines: {libvips: '>=8.15.1', node: ^18.17.0 || ^20.3.0 || >=21.0.0}
     requiresBuild: true
     dependencies:
       color: 4.2.3
       detect-libc: 2.0.2
-      node-addon-api: 5.1.0
-      prebuild-install: 7.1.1
       semver: 7.5.4
-      simple-get: 4.0.1
-      tar-fs: 2.1.1
-      tunnel-agent: 0.6.0
+    optionalDependencies:
+      '@img/sharp-darwin-arm64': 0.33.2
+      '@img/sharp-darwin-x64': 0.33.2
+      '@img/sharp-libvips-darwin-arm64': 1.0.1
+      '@img/sharp-libvips-darwin-x64': 1.0.1
+      '@img/sharp-libvips-linux-arm': 1.0.1
+      '@img/sharp-libvips-linux-arm64': 1.0.1
+      '@img/sharp-libvips-linux-s390x': 1.0.1
+      '@img/sharp-libvips-linux-x64': 1.0.1
+      '@img/sharp-libvips-linuxmusl-arm64': 1.0.1
+      '@img/sharp-libvips-linuxmusl-x64': 1.0.1
+      '@img/sharp-linux-arm': 0.33.2
+      '@img/sharp-linux-arm64': 0.33.2
+      '@img/sharp-linux-s390x': 0.33.2
+      '@img/sharp-linux-x64': 0.33.2
+      '@img/sharp-linuxmusl-arm64': 0.33.2
+      '@img/sharp-linuxmusl-x64': 0.33.2
+      '@img/sharp-wasm32': 0.33.2
+      '@img/sharp-win32-ia32': 0.33.2
+      '@img/sharp-win32-x64': 0.33.2
     dev: false
 
-  /sharp@0.32.6:
-    resolution: {integrity: sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==}
-    engines: {node: '>=14.15.0'}
-    requiresBuild: true
-    dependencies:
-      color: 4.2.3
-      detect-libc: 2.0.2
-      node-addon-api: 6.1.0
-      prebuild-install: 7.1.1
-      semver: 7.5.4
-      simple-get: 4.0.1
-      tar-fs: 3.0.4
-      tunnel-agent: 0.6.0
-
   /shebang-command@1.2.0:
     resolution: {integrity: sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==}
     engines: {node: '>=0.10.0'}
@@ -17828,6 +17931,13 @@ packages:
       jsonc-parser: 3.2.0
       vscode-oniguruma: 1.7.0
       vscode-textmate: 8.0.0
+    dev: true
+
+  /shiki@1.1.7:
+    resolution: {integrity: sha512-9kUTMjZtcPH3i7vHunA6EraTPpPOITYTdA5uMrvsJRexktqP0s7P3s9HVK80b4pP42FRVe03D7fT3NmJv2yYhw==}
+    dependencies:
+      '@shikijs/core': 1.1.7
+    dev: false
 
   /side-channel@1.0.4:
     resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
@@ -17847,16 +17957,6 @@ packages:
     resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
     engines: {node: '>=14'}
 
-  /simple-concat@1.0.1:
-    resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
-
-  /simple-get@4.0.1:
-    resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
-    dependencies:
-      decompress-response: 6.0.0
-      once: 1.4.0
-      simple-concat: 1.0.1
-
   /simple-oauth2@5.0.0:
     resolution: {integrity: sha512-8291lo/z5ZdpmiOFzOs1kF3cxn22bMj5FFH+DNUppLJrpoIlM1QnFiE7KpshHu3J3i21TVcx4yW+gXYjdCKDLQ==}
     dependencies:
@@ -17872,6 +17972,7 @@ packages:
     resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
     dependencies:
       is-arrayish: 0.3.2
+    dev: false
 
   /simple-update-notifier@2.0.0:
     resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==}
@@ -17880,12 +17981,12 @@ packages:
       semver: 7.5.4
     dev: true
 
-  /sinon@14.0.2:
-    resolution: {integrity: sha512-PDpV0ZI3ZCS3pEqx0vpNp6kzPhHrLx72wA0G+ZLaaJjLIYeE0n8INlgaohKuGy7hP0as5tbUd23QWu5U233t+w==}
+  /sinon@16.1.3:
+    resolution: {integrity: sha512-mjnWWeyxcAf9nC0bXcPmiDut+oE8HYridTNzBbF98AYVLmWwGRp2ISEpyhYflG1ifILT+eNn3BmKUJPxjXUPlA==}
     dependencies:
-      '@sinonjs/commons': 2.0.0
-      '@sinonjs/fake-timers': 9.1.2
-      '@sinonjs/samsam': 7.0.1
+      '@sinonjs/commons': 3.0.0
+      '@sinonjs/fake-timers': 10.3.0
+      '@sinonjs/samsam': 8.0.0
       diff: 5.1.0
       nise: 5.1.4
       supports-color: 7.2.0
@@ -18126,8 +18227,8 @@ packages:
     engines: {node: '>= 8'}
     dev: false
 
-  /space-separated-tokens@1.1.5:
-    resolution: {integrity: sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==}
+  /space-separated-tokens@2.0.2:
+    resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==}
     dev: true
 
   /spdx-correct@3.1.1:
@@ -18228,8 +18329,8 @@ packages:
     resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
     engines: {node: '>= 0.8'}
 
-  /std-env@3.3.3:
-    resolution: {integrity: sha512-Rz6yejtVyWnVjC1RFvNmYL10kgjC49EOghxWn0RFqlCHGFpQx+Xe7yW3I4ceK1SGrWIGMjD5Kbue8W/udkbMJg==}
+  /std-env@3.7.0:
+    resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==}
     dev: true
 
   /stop-iteration-iterator@1.0.0:
@@ -18243,14 +18344,17 @@ packages:
     resolution: {integrity: sha512-siT1RiqlfQnGqgT/YzXVUNsom9S0H1OX+dpdGN1xkyYATo4I6sep5NmsRD/40s3IIOvlCq6akxkqG82urIZW1w==}
     dev: true
 
-  /storybook@7.6.5:
-    resolution: {integrity: sha512-uHPrL+g/0v6iIVtDA8J0uWd3jDZcdr51lCR/vPXTkrCY1uVaFjswzl8EMy5PR05I7jMpKUzkJWZtFbgbh9e1Bw==}
+  /storybook@8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0):
+    resolution: {integrity: sha512-8d9gpKPDY9Ix64f0560rXIifmnuoswDdvSdTz4NXHGvPt7WrKNmaDTvWGyt1/fbTbv2dvvVp7bsWPgq1KGbrcg==}
     hasBin: true
     dependencies:
-      '@storybook/cli': 7.6.5
+      '@storybook/cli': 8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0)
     transitivePeerDependencies:
+      - '@babel/preset-env'
       - bufferutil
       - encoding
+      - react
+      - react-dom
       - supports-color
       - utf-8-validate
     dev: true
@@ -18288,26 +18392,20 @@ packages:
   /streamsearch@1.1.0:
     resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
     engines: {node: '>=10.0.0'}
-    dev: false
 
   /streamx@2.15.0:
     resolution: {integrity: sha512-HcxY6ncGjjklGs1xsP1aR71INYcsXFJet5CU1CHqihQ2J5nOsbd4OjgjHO42w/4QNv9gZb3BueV+Vxok5pLEXg==}
     dependencies:
       fast-fifo: 1.3.0
       queue-tick: 1.0.1
+    dev: false
 
   /strict-event-emitter-types@2.0.0:
     resolution: {integrity: sha512-Nk/brWYpD85WlOgzw5h173aci0Teyv8YdIAEtV+N88nDB0dLlazZyJMIsN6eo1/AR61l+p6CJTG1JIyFaoNEEA==}
     dev: false
 
-  /strict-event-emitter@0.2.8:
-    resolution: {integrity: sha512-KDf/ujU8Zud3YaLtMCcTI4xkZlZVIYxTLr+XIULexP+77EEVWixeXroLUXQXiVtH4XH2W7jr/3PT1v3zBuvc3A==}
-    dependencies:
-      events: 3.3.0
-    dev: true
-
-  /strict-event-emitter@0.4.6:
-    resolution: {integrity: sha512-12KWeb+wixJohmnwNFerbyiBrAlq5qJLwIt38etRtKtmmHyDSoGlIqFE9wx+4IwG0aDjI7GV8tc8ZccjWZZtTg==}
+  /strict-event-emitter@0.5.1:
+    resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==}
     dev: true
 
   /string-argv@0.3.1:
@@ -18428,19 +18526,15 @@ packages:
       min-indent: 1.0.1
     dev: true
 
-  /strip-json-comments@2.0.1:
-    resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
-    engines: {node: '>=0.10.0'}
-
   /strip-json-comments@3.1.1:
     resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
     engines: {node: '>=8'}
     dev: true
 
-  /strip-literal@1.0.1:
-    resolution: {integrity: sha512-QZTsipNpa2Ppr6v1AmJHESqJ3Uz247MUS0OjrnnZjFAvEoWqxuyFuXn2xLgMtRnijJShAa1HL0gtJyUs7u7n3Q==}
+  /strip-literal@1.3.0:
+    resolution: {integrity: sha512-PugKzOsyXpArk0yWmUwqOZecSO0GH0bPoctLcqNDH9J04pVW3lflYE0ujElBGTloevcxF5MofAOZ7C5l2b+wLg==}
     dependencies:
-      acorn: 8.11.2
+      acorn: 8.11.3
     dev: true
 
   /strip-outer@2.0.0:
@@ -18460,15 +18554,15 @@ packages:
       peek-readable: 5.0.0
     dev: false
 
-  /stylehacks@6.0.1(postcss@8.4.32):
-    resolution: {integrity: sha512-jTqG2aIoX2fYg0YsGvqE4ooE/e75WmaEjnNiP6Ag7irLtHxML8NJRxRxS0HyDpde8DRGuEXTFVHVfR5Tmbxqzg==}
+  /stylehacks@6.0.3(postcss@8.4.35):
+    resolution: {integrity: sha512-KzBqjnqktc8/I0ERCb+lGq06giF/JxDbw2r9kEVhen9noHeIDRtMWUp9r62sOk+/2bbX6sFG1GhsS7ToXG0PEg==}
     engines: {node: ^14 || ^16 || >=18.0}
     peerDependencies:
       postcss: ^8.4.31
     dependencies:
-      browserslist: 4.22.1
-      postcss: 8.4.32
-      postcss-selector-parser: 6.0.13
+      browserslist: 4.23.0
+      postcss: 8.4.35
+      postcss-selector-parser: 6.0.15
     dev: false
 
   /supports-color@5.5.0:
@@ -18506,8 +18600,8 @@ packages:
     resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
     engines: {node: '>= 0.4'}
 
-  /svgo@3.1.0:
-    resolution: {integrity: sha512-R5SnNA89w1dYgNv570591F66v34b3eQShpIBcQtZtM5trJwm1VvxbIoMpRYY3ybTAutcKTLEmTsdnaknOHbiQA==}
+  /svgo@3.2.0:
+    resolution: {integrity: sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ==}
     engines: {node: '>=14.0.0'}
     hasBin: true
     dependencies:
@@ -18524,12 +18618,8 @@ packages:
     resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
     dev: false
 
-  /synchronous-promise@2.0.17:
-    resolution: {integrity: sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==}
-    dev: true
-
-  /systeminformation@5.21.20:
-    resolution: {integrity: sha512-AyS1fNc+MDoAJtFknFbbo587H8h6yejJwM+H9rVusnOToIEkiMehMyD5JM7o3j55Cto20MawIZrcgNMgd4BfOQ==}
+  /systeminformation@5.22.0:
+    resolution: {integrity: sha512-oAP80ymt8ssrAzjX8k3frbL7ys6AotqC35oikG6/SG15wBw+tG9nCk4oPaXIhEaAOAZ8XngxUv3ORq2IuR3r4Q==}
     engines: {node: '>=8.0.0'}
     os: [darwin, linux, win32, freebsd, openbsd, netbsd, sunos, android]
     hasBin: true
@@ -18542,13 +18632,7 @@ packages:
       mkdirp-classic: 0.5.3
       pump: 3.0.0
       tar-stream: 2.2.0
-
-  /tar-fs@3.0.4:
-    resolution: {integrity: sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==}
-    dependencies:
-      mkdirp-classic: 0.5.3
-      pump: 3.0.0
-      tar-stream: 3.1.6
+    dev: true
 
   /tar-stream@2.2.0:
     resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
@@ -18559,6 +18643,7 @@ packages:
       fs-constants: 1.0.0
       inherits: 2.0.4
       readable-stream: 3.6.2
+    dev: true
 
   /tar-stream@3.1.6:
     resolution: {integrity: sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==}
@@ -18566,18 +18651,26 @@ packages:
       b4a: 1.6.4
       fast-fifo: 1.3.0
       streamx: 2.15.0
+    dev: false
 
-  /tar@6.1.13:
-    resolution: {integrity: sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==}
+  /tar@6.2.0:
+    resolution: {integrity: sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==}
     engines: {node: '>=10'}
     dependencies:
       chownr: 2.0.0
       fs-minipass: 2.1.0
-      minipass: 4.2.5
+      minipass: 5.0.0
       minizlib: 2.1.2
       mkdirp: 1.0.4
       yallist: 4.0.0
 
+  /taskkill@5.0.0:
+    resolution: {integrity: sha512-+HRtZ40Vc+6YfCDWCeAsixwxJgMbPY4HHuTgzPYH3JXvqHWUlsCfy+ylXlAKhFNcuLp4xVeWeFBUhDk+7KYUvQ==}
+    engines: {node: '>=14.16'}
+    dependencies:
+      execa: 6.1.0
+    dev: true
+
   /telejson@7.2.0:
     resolution: {integrity: sha512-1QTEcJkJEhc8OnStBx/ILRu5J2p0GjvWsBx56bmZRqnrkdBMUe+nX92jxV+p3dB4CP6PZCdJMQJwCggkNBMzkQ==}
     dependencies:
@@ -18607,13 +18700,13 @@ packages:
       unique-string: 2.0.0
     dev: true
 
-  /terser@5.26.0:
-    resolution: {integrity: sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ==}
+  /terser@5.28.1:
+    resolution: {integrity: sha512-wM+bZp54v/E9eRRGXb5ZFDvinrJIOaTapx3WUokyVGZu5ucVCK55zEgGd5Dl2fSr3jUo5sDiERErUWLY6QPFyA==}
     engines: {node: '>=10'}
     hasBin: true
     dependencies:
       '@jridgewell/source-map': 0.3.5
-      acorn: 8.11.2
+      acorn: 8.11.3
       commander: 2.20.3
       source-map-support: 0.5.21
 
@@ -18657,8 +18750,8 @@ packages:
       real-require: 0.2.0
     dev: false
 
-  /three@0.159.0:
-    resolution: {integrity: sha512-eCmhlLGbBgucuo4VEA9IO3Qpc7dh8Bd4VKzr7WfW4+8hMcIfoAVi1ev0pJYN9PTTsCslbcKgBwr2wNZ1EvLInA==}
+  /three@0.162.0:
+    resolution: {integrity: sha512-xfCYj4RnlozReCmUd+XQzj6/5OjDNHBy5nT6rVwrOKGENAvpXe2z1jL+DZYaMu4/9pNsjH/4Os/VvS9IrH7IOQ==}
     dev: false
 
   /throttle-debounce@5.0.0:
@@ -18690,8 +18783,8 @@ packages:
     engines: {node: '>=6'}
     dev: false
 
-  /tinybench@2.5.0:
-    resolution: {integrity: sha512-kRwSG8Zx4tjF9ZiyH4bhaebu+EDz1BOx9hOigYHlUW4xxI/wKIUQUqo018UlU4ar6ATPBsaMrdbKZ+tmPdohFA==}
+  /tinybench@2.6.0:
+    resolution: {integrity: sha512-N8hW3PG/3aOoZAN5V/NSAEDz0ZixDSSt5b/a05iqtpgfLWMSVuCo7w0k2vVvEjdrIoeGqZzweX2WlyioNIHchA==}
     dev: true
 
   /tinycolor2@1.6.0:
@@ -18703,8 +18796,8 @@ packages:
     engines: {node: '>=14.0.0'}
     dev: true
 
-  /tinyspy@2.1.1:
-    resolution: {integrity: sha512-XPJL2uSzcOyBMky6OFrusqWlzfFrXtE0hPuMgW8A2HmaqrPo4ZQHRN/V0QXN3FSjKxpsbRrFc5LI7KOwBsT1/w==}
+  /tinyspy@2.2.0:
+    resolution: {integrity: sha512-d2eda04AN/cPOR89F7Xv5bK/jrQEhmcLFe6HFldoeO9AJtps+fqEnh486vnT/8y4bw38pSyxDcTCAq+Ks2aJTg==}
     engines: {node: '>=14.0.0'}
     dev: true
 
@@ -18715,11 +18808,11 @@ packages:
       os-tmpdir: 1.0.2
     dev: true
 
-  /tmp@0.2.1:
-    resolution: {integrity: sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==}
-    engines: {node: '>=8.17.0'}
+  /tmp@0.2.2:
+    resolution: {integrity: sha512-ETcvHhaIc9J2MDEAH6N67j9bvBvu/3Gb764qaGhwtFvjtvhegqoqSpofgeyq1Sc24mW5pdyUDs9HP5j3ehkxRw==}
+    engines: {node: '>=14'}
     dependencies:
-      rimraf: 3.0.2
+      rimraf: 5.0.5
 
   /tmpl@1.0.5:
     resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==}
@@ -18805,6 +18898,10 @@ packages:
       escape-string-regexp: 5.0.0
     dev: false
 
+  /trough@2.2.0:
+    resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==}
+    dev: true
+
   /ts-api-utils@1.0.3(typescript@5.1.6):
     resolution: {integrity: sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==}
     engines: {node: '>=16.13.0'}
@@ -18833,7 +18930,7 @@ packages:
     engines: {node: '>=6.10'}
     dev: true
 
-  /ts-jest@29.1.1(@babel/core@7.23.3)(jest@29.7.0)(typescript@5.1.6):
+  /ts-jest@29.1.1(@babel/core@7.24.0)(jest@29.7.0)(typescript@5.1.6):
     resolution: {integrity: sha512-D6xjnnbP17cC85nliwGiL+tpoKN0StpgE0TeOjXQTU6MVCfsB4v7aW05CgQ/1OywGb0x/oy9hHFnN+sczTiRaA==}
     engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
     hasBin: true
@@ -18854,10 +18951,10 @@ packages:
       esbuild:
         optional: true
     dependencies:
-      '@babel/core': 7.23.3
+      '@babel/core': 7.24.0
       bs-logger: 0.2.6
       fast-json-stable-stringify: 2.1.0
-      jest: 29.7.0(@types/node@20.10.5)
+      jest: 29.7.0(@types/node@20.11.22)
       jest-util: 29.7.0
       json5: 2.2.3
       lodash.memoize: 4.1.2
@@ -18901,8 +18998,8 @@ packages:
       strip-bom: 3.0.0
     dev: false
 
-  /tsd@0.30.0:
-    resolution: {integrity: sha512-aHL4rEuf3wwRzKCH8yqsE1oMAJYn7SAQ2JfWSgjr1e5/fqr+ggohQazECMpSoRAqSQeM/iIFugoyL/0eFwdTcA==}
+  /tsd@0.30.7:
+    resolution: {integrity: sha512-oTiJ28D6B/KXoU3ww/Eji+xqHJojiuPVMwA12g4KYX1O72N93Nb6P3P3h2OAhhf92Xl8NIhb/xFmBZd5zw/xUw==}
     engines: {node: '>=14.16'}
     hasBin: true
     dependencies:
@@ -18921,13 +19018,6 @@ packages:
   /tslib@2.5.0:
     resolution: {integrity: sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==}
 
-  /tslib@2.5.3:
-    resolution: {integrity: sha512-mSxlJJwl3BMEQCUNnxXBU9jP4JBktcEGhURcPR6VQVlnP0FdDEsIaz0C35dXNGLyRfrATNofF0F5p2KPxQgB+w==}
-
-  /tslib@2.6.1:
-    resolution: {integrity: sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==}
-    dev: false
-
   /tslib@2.6.2:
     resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
 
@@ -18946,6 +19036,7 @@ packages:
     resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
     dependencies:
       safe-buffer: 5.2.1
+    dev: true
 
   /tweetnacl@0.14.5:
     resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==}
@@ -18996,6 +19087,11 @@ packages:
     engines: {node: '>=12.20'}
     dev: true
 
+  /type-fest@4.9.0:
+    resolution: {integrity: sha512-KS/6lh/ynPGiHD/LnAobrEFq3Ad4pBzOlJ1wAnJx9N4EYoqFhMfLIBjUT2UEx4wg5ZE+cC1ob6DCSpppVo+rtg==}
+    engines: {node: '>=16'}
+    dev: true
+
   /type-is@1.6.18:
     resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
     engines: {node: '>= 0.6'}
@@ -19058,20 +19154,20 @@ packages:
       typescript: 5.1.6
     dev: true
 
-  /typeorm@0.3.17(ioredis@5.3.2)(pg@8.11.3):
-    resolution: {integrity: sha512-UDjUEwIQalO9tWw9O2A4GU+sT3oyoUXheHJy4ft+RFdnRdQctdQ34L9SqE2p7LdwzafHx1maxT+bqXON+Qnmig==}
-    engines: {node: '>= 12.9.0'}
+  /typeorm@0.3.20(ioredis@5.3.2)(pg@8.11.3):
+    resolution: {integrity: sha512-sJ0T08dV5eoZroaq9uPKBoNcGslHBR4E4y+EBHs//SiGbblGe7IeduP/IH4ddCcj0qp3PHwDwGnuvqEAnKlq/Q==}
+    engines: {node: '>=16.13.0'}
     hasBin: true
     peerDependencies:
       '@google-cloud/spanner': ^5.18.0
       '@sap/hana-client': ^2.12.25
-      better-sqlite3: ^7.1.2 || ^8.0.0
+      better-sqlite3: ^7.1.2 || ^8.0.0 || ^9.0.0
       hdb-pool: ^0.1.6
       ioredis: ^5.0.4
-      mongodb: ^5.2.0
-      mssql: ^9.1.1
+      mongodb: ^5.8.0
+      mssql: ^9.1.1 || ^10.0.1
       mysql2: ^2.2.5 || ^3.0.1
-      oracledb: ^5.1.0
+      oracledb: ^6.3.0
       pg: ^8.5.1
       pg-native: ^3.0.0
       pg-query-stream: ^4.0.0
@@ -19121,28 +19217,22 @@ packages:
       buffer: 6.0.3
       chalk: 4.1.2
       cli-highlight: 2.1.11
-      date-fns: 2.30.0
+      dayjs: 1.11.10
       debug: 4.3.4(supports-color@8.1.1)
       dotenv: 16.0.3
-      glob: 8.1.0
+      glob: 10.3.10
       ioredis: 5.3.2
       mkdirp: 2.1.6
       pg: 8.11.3
-      reflect-metadata: 0.1.14
+      reflect-metadata: 0.2.1
       sha.js: 2.4.11
-      tslib: 2.5.3
+      tslib: 2.6.2
       uuid: 9.0.1
-      yargs: 17.6.2
+      yargs: 17.7.2
     transitivePeerDependencies:
       - supports-color
     dev: false
 
-  /typescript@5.0.4:
-    resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==}
-    engines: {node: '>=12.20'}
-    hasBin: true
-    dev: true
-
   /typescript@5.1.6:
     resolution: {integrity: sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==}
     engines: {node: '>=14.17'}
@@ -19153,8 +19243,8 @@ packages:
     engines: {node: '>=14.17'}
     hasBin: true
 
-  /ufo@1.1.2:
-    resolution: {integrity: sha512-TrY6DsjTQQgyS3E3dBaOXf0TpPD8u9FVrVYmKVegJuFw51n/YB9XPt+U6ydzFG5ZIN7+DIjPbNmXoBj9esYhgQ==}
+  /ufo@1.3.2:
+    resolution: {integrity: sha512-o+ORpgGwaYQXgqGDwd+hkS4PuZ3QnmqMMxRuajK/a38L6fTpcE5GPIfrf+L/KemFzfUpeUQc1rRS1iDBozvnFA==}
     dev: true
 
   /uglify-js@3.17.4:
@@ -19174,7 +19264,6 @@ packages:
     engines: {node: '>=8'}
     dependencies:
       '@lukeed/csprng': 1.0.1
-    dev: false
 
   /ulid@2.3.0:
     resolution: {integrity: sha512-keqHubrlpvT6G2wH0OEfSW4mquYRcbe/J8NMmveoQOjUqmo+hXtO+ORCpWhdbZ7k72UtY61BL7haGxW6enBnjw==}
@@ -19234,6 +19323,18 @@ packages:
     engines: {node: '>=4'}
     dev: true
 
+  /unified@11.0.4:
+    resolution: {integrity: sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==}
+    dependencies:
+      '@types/unist': 3.0.2
+      bail: 2.0.2
+      devlop: 1.1.0
+      extend: 3.0.2
+      is-plain-obj: 4.1.0
+      trough: 2.2.0
+      vfile: 6.0.1
+    dev: true
+
   /unique-filename@3.0.0:
     resolution: {integrity: sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==}
     engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
@@ -19255,23 +19356,31 @@ packages:
       crypto-random-string: 2.0.0
     dev: true
 
-  /unist-util-is@4.1.0:
-    resolution: {integrity: sha512-ZOQSsnce92GrxSqlnEEseX0gi7GH9zTJZ0p9dtu87WRb/37mMPO2Ilx1s/t9vBHrFhbgweUwb+t7cIn5dxPhZg==}
+  /unist-util-is@6.0.0:
+    resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==}
+    dependencies:
+      '@types/unist': 3.0.2
     dev: true
 
-  /unist-util-visit-parents@3.1.1:
-    resolution: {integrity: sha512-1KROIZWo6bcMrZEwiH2UrXDyalAa0uqzWCxCJj6lPOvTve2WkfgCytoDTPaMnodXh1WrXOq0haVYHj99ynJlsg==}
+  /unist-util-stringify-position@4.0.0:
+    resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}
     dependencies:
-      '@types/unist': 2.0.6
-      unist-util-is: 4.1.0
+      '@types/unist': 3.0.2
     dev: true
 
-  /unist-util-visit@2.0.3:
-    resolution: {integrity: sha512-iJ4/RczbJMkD0712mGktuGpm/U4By4FfDonL7N/9tATGIF4imikjOuagyMY53tnZq3NP6BcmlrHhEKAfGWjh7Q==}
+  /unist-util-visit-parents@6.0.1:
+    resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==}
     dependencies:
-      '@types/unist': 2.0.6
-      unist-util-is: 4.1.0
-      unist-util-visit-parents: 3.1.1
+      '@types/unist': 3.0.2
+      unist-util-is: 6.0.0
+    dev: true
+
+  /unist-util-visit@5.0.0:
+    resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}
+    dependencies:
+      '@types/unist': 3.0.2
+      unist-util-is: 6.0.0
+      unist-util-visit-parents: 6.0.1
     dev: true
 
   /universalify@0.1.2:
@@ -19298,7 +19407,7 @@ packages:
   /unplugin@1.5.1:
     resolution: {integrity: sha512-0QkvG13z6RD+1L1FoibQqnvTwVBXvS4XSPwAyinVgoOCl2jAgwzdUKmEj05o4Lt8xwQI85Hb6mSyYkcAGwZPew==}
     dependencies:
-      acorn: 8.11.2
+      acorn: 8.11.3
       chokidar: 3.5.3
       webpack-sources: 3.2.3
       webpack-virtual-modules: 0.6.1
@@ -19318,6 +19427,7 @@ packages:
       browserslist: 4.22.1
       escalade: 3.1.1
       picocolors: 1.0.0
+    dev: true
 
   /update-browserslist-db@1.0.13(browserslist@4.22.2):
     resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==}
@@ -19330,6 +19440,16 @@ packages:
       picocolors: 1.0.0
     dev: true
 
+  /update-browserslist-db@1.0.13(browserslist@4.23.0):
+    resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==}
+    hasBin: true
+    peerDependencies:
+      browserslist: '>= 4.21.0'
+    dependencies:
+      browserslist: 4.23.0
+      escalade: 3.1.1
+      picocolors: 1.0.0
+
   /uri-js@4.4.1:
     resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
     dependencies:
@@ -19341,50 +19461,6 @@ packages:
       querystringify: 2.2.0
       requires-port: 1.0.0
 
-  /urlsafe-base64@1.0.0:
-    resolution: {integrity: sha512-RtuPeMy7c1UrHwproMZN9gN6kiZ0SvJwRaEzwZY0j9MypEkFqyBaKv176jvlPtg58Zh36bOkS0NFABXMHvvGCA==}
-    dev: false
-
-  /use-callback-ref@1.3.0(react@18.2.0):
-    resolution: {integrity: sha512-3FT9PRuRdbB9HfXhEq35u4oZkvpJ5kuYbpqhCfmiZyReuRgpnhDlbr2ZEnnuS0RrJAPn6l23xjFg9kpDM+Ms7w==}
-    engines: {node: '>=10'}
-    peerDependencies:
-      '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0
-      react: ^16.8.0 || ^17.0.0 || ^18.0.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-    dependencies:
-      react: 18.2.0
-      tslib: 2.6.2
-    dev: true
-
-  /use-resize-observer@9.1.0(react-dom@18.2.0)(react@18.2.0):
-    resolution: {integrity: sha512-R25VqO9Wb3asSD4eqtcxk8sJalvIOYBqS8MNZlpDSQ4l4xMQxC/J7Id9HoTqPq8FwULIn0PVW+OAqF2dyYbjow==}
-    peerDependencies:
-      react: 16.8.0 - 18
-      react-dom: 16.8.0 - 18
-    dependencies:
-      '@juggle/resize-observer': 3.4.0
-      react: 18.2.0
-      react-dom: 18.2.0(react@18.2.0)
-    dev: true
-
-  /use-sidecar@1.1.2(react@18.2.0):
-    resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==}
-    engines: {node: '>=10'}
-    peerDependencies:
-      '@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0
-      react: ^16.8.0 || ^17.0.0 || ^18.0.0
-    peerDependenciesMeta:
-      '@types/react':
-        optional: true
-    dependencies:
-      detect-node-es: 1.1.0
-      react: 18.2.0
-      tslib: 2.6.2
-    dev: true
-
   /utf-8-validate@6.0.3:
     resolution: {integrity: sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==}
     engines: {node: '>=6.14.2'}
@@ -19417,8 +19493,8 @@ packages:
     resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
     hasBin: true
 
-  /v-code-diff@1.7.2(vue@3.3.12):
-    resolution: {integrity: sha512-y+q8ZHf8GfphYLhcZbjAKcId/h6vZujS71Ryq5u+dI6Jg4ZLTdLrBNVSzYpHywHSSFFfBMdilm6XvVryEaH4+A==}
+  /v-code-diff@1.9.0(vue@3.4.21):
+    resolution: {integrity: sha512-alg6krCxFvwTob/rJq+3LzjdIbLb/ni8tS8YmBbI0wckOkbJuN1cShFJ6XEkm82tMgpv5NYEeWLEWhggeV7BDg==}
     requiresBuild: true
     peerDependencies:
       '@vue/composition-api': ^1.4.9
@@ -19429,20 +19505,11 @@ packages:
     dependencies:
       diff: 5.1.0
       diff-match-patch: 1.0.5
-      highlight.js: 11.8.0
-      vue: 3.3.12(typescript@5.3.3)
-      vue-demi: 0.13.11(vue@3.3.12)
+      highlight.js: 11.9.0
+      vue: 3.4.21(typescript@5.3.3)
+      vue-demi: 0.14.7(vue@3.4.21)
     dev: false
 
-  /v8-to-istanbul@9.1.0:
-    resolution: {integrity: sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==}
-    engines: {node: '>=10.12.0'}
-    dependencies:
-      '@jridgewell/trace-mapping': 0.3.18
-      '@types/istanbul-lib-coverage': 2.0.4
-      convert-source-map: 1.9.0
-    dev: true
-
   /v8-to-istanbul@9.2.0:
     resolution: {integrity: sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA==}
     engines: {node: '>=10.12.0'}
@@ -19476,17 +19543,32 @@ packages:
       core-util-is: 1.0.2
       extsprintf: 1.3.0
 
-  /vite-node@0.34.6(@types/node@20.10.5)(sass@1.69.5)(terser@5.26.0):
+  /vfile-message@4.0.2:
+    resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==}
+    dependencies:
+      '@types/unist': 3.0.2
+      unist-util-stringify-position: 4.0.0
+    dev: true
+
+  /vfile@6.0.1:
+    resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==}
+    dependencies:
+      '@types/unist': 3.0.2
+      unist-util-stringify-position: 4.0.0
+      vfile-message: 4.0.2
+    dev: true
+
+  /vite-node@0.34.6(@types/node@20.11.22)(sass@1.71.1)(terser@5.28.1):
     resolution: {integrity: sha512-nlBMJ9x6n7/Amaz6F3zJ97EBwR2FkzhBRxF5e+jE6LA3yi6Wtc2lyTij1OnDMIr34v5g/tVQtsVAzhT0jc5ygA==}
     engines: {node: '>=v14.18.0'}
     hasBin: true
     dependencies:
       cac: 6.7.14
       debug: 4.3.4(supports-color@8.1.1)
-      mlly: 1.4.0
-      pathe: 1.1.1
+      mlly: 1.5.0
+      pathe: 1.1.2
       picocolors: 1.0.0
-      vite: 5.0.10(@types/node@20.10.5)(sass@1.69.5)(terser@5.26.0)
+      vite: 5.1.4(@types/node@20.11.22)(sass@1.71.1)(terser@5.28.1)
     transitivePeerDependencies:
       - '@types/node'
       - less
@@ -19502,8 +19584,8 @@ packages:
     resolution: {integrity: sha512-p4D8CFVhZS412SyQX125qxyzOgIFouwOcvjZWk6bQbNPR1wtaEzFT6jZxAjf1dejlGqa6fqHcuCvQea6EWUkUA==}
     dev: true
 
-  /vite@5.0.10(@types/node@20.10.5)(sass@1.69.5)(terser@5.26.0):
-    resolution: {integrity: sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==}
+  /vite@5.1.4(@types/node@20.11.22)(sass@1.71.1)(terser@5.28.1):
+    resolution: {integrity: sha512-n+MPqzq+d9nMVTKyewqw6kSt+R3CkvF9QAKY8obiQn8g1fwTscKxyfaYnC632HtBXAQGc1Yjomphwn1dtwGAHg==}
     engines: {node: ^18.0.0 || >=20.0.0}
     hasBin: true
     peerDependencies:
@@ -19530,12 +19612,12 @@ packages:
       terser:
         optional: true
     dependencies:
-      '@types/node': 20.10.5
-      esbuild: 0.19.9
-      postcss: 8.4.32
-      rollup: 4.9.1
-      sass: 1.69.5
-      terser: 5.26.0
+      '@types/node': 20.11.22
+      esbuild: 0.19.11
+      postcss: 8.4.35
+      rollup: 4.12.0
+      sass: 1.71.1
+      terser: 5.28.1
     optionalDependencies:
       fsevents: 2.3.3
 
@@ -19545,13 +19627,13 @@ packages:
     peerDependencies:
       vitest: '>=0.16.0'
     dependencies:
-      cross-fetch: 3.1.5
-      vitest: 0.34.6(happy-dom@10.0.3)(sass@1.69.5)(terser@5.26.0)
+      cross-fetch: 3.1.6
+      vitest: 0.34.6(happy-dom@13.6.2)(sass@1.71.1)(terser@5.28.1)
     transitivePeerDependencies:
       - encoding
     dev: true
 
-  /vitest@0.34.6(happy-dom@10.0.3)(sass@1.69.5)(terser@5.26.0):
+  /vitest@0.34.6(happy-dom@13.6.2)(sass@1.71.1)(terser@5.28.1):
     resolution: {integrity: sha512-+5CALsOvbNKnS+ZHMXtuUC7nL8/7F1F2DnHGjSsszX8zCjWSSviphCb/NuS9Nzf4Q03KyyDRBAXhF/8lffME4Q==}
     engines: {node: '>=v14.18.0'}
     hasBin: true
@@ -19582,30 +19664,30 @@ packages:
       webdriverio:
         optional: true
     dependencies:
-      '@types/chai': 4.3.5
-      '@types/chai-subset': 1.3.3
-      '@types/node': 20.10.5
+      '@types/chai': 4.3.11
+      '@types/chai-subset': 1.3.5
+      '@types/node': 20.11.22
       '@vitest/expect': 0.34.6
       '@vitest/runner': 0.34.6
       '@vitest/snapshot': 0.34.6
       '@vitest/spy': 0.34.6
       '@vitest/utils': 0.34.6
-      acorn: 8.11.2
-      acorn-walk: 8.2.0
+      acorn: 8.11.3
+      acorn-walk: 8.3.2
       cac: 6.7.14
       chai: 4.3.10
       debug: 4.3.4(supports-color@8.1.1)
-      happy-dom: 10.0.3
+      happy-dom: 13.6.2
       local-pkg: 0.4.3
-      magic-string: 0.30.3
-      pathe: 1.1.1
+      magic-string: 0.30.5
+      pathe: 1.1.2
       picocolors: 1.0.0
-      std-env: 3.3.3
-      strip-literal: 1.0.1
-      tinybench: 2.5.0
+      std-env: 3.7.0
+      strip-literal: 1.3.0
+      tinybench: 2.6.0
       tinypool: 0.7.0
-      vite: 5.0.10(@types/node@20.10.5)(sass@1.69.5)(terser@5.26.0)
-      vite-node: 0.34.6(@types/node@20.10.5)(sass@1.69.5)(terser@5.26.0)
+      vite: 5.1.4(@types/node@20.11.22)(sass@1.71.1)(terser@5.28.1)
+      vite-node: 0.34.6(@types/node@20.11.22)(sass@1.71.1)(terser@5.28.1)
       why-is-node-running: 2.2.2
     transitivePeerDependencies:
       - less
@@ -19621,11 +19703,64 @@ packages:
     resolution: {integrity: sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==}
     engines: {node: '>=0.10.0'}
 
+  /vscode-jsonrpc@8.2.0:
+    resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==}
+    engines: {node: '>=14.0.0'}
+    dev: false
+
+  /vscode-languageclient@9.0.1:
+    resolution: {integrity: sha512-JZiimVdvimEuHh5olxhxkht09m3JzUGwggb5eRUkzzJhZ2KjCN0nh55VfiED9oez9DyF8/fz1g1iBV3h+0Z2EA==}
+    engines: {vscode: ^1.82.0}
+    dependencies:
+      minimatch: 5.1.6
+      semver: 7.5.4
+      vscode-languageserver-protocol: 3.17.5
+    dev: false
+
+  /vscode-languageserver-protocol@3.17.5:
+    resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==}
+    dependencies:
+      vscode-jsonrpc: 8.2.0
+      vscode-languageserver-types: 3.17.5
+    dev: false
+
+  /vscode-languageserver-textdocument@1.0.11:
+    resolution: {integrity: sha512-X+8T3GoiwTVlJbicx/sIAF+yuJAqz8VvwJyoMVhwEMoEKE/fkDmrqUgDMyBECcM2A2frVZIUj5HI/ErRXCfOeA==}
+    dev: false
+
+  /vscode-languageserver-types@3.17.5:
+    resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==}
+    dev: false
+
+  /vscode-languageserver@9.0.1:
+    resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==}
+    hasBin: true
+    dependencies:
+      vscode-languageserver-protocol: 3.17.5
+    dev: false
+
   /vscode-oniguruma@1.7.0:
     resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==}
+    dev: true
 
   /vscode-textmate@8.0.0:
     resolution: {integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==}
+    dev: true
+
+  /vue-component-meta@1.8.27(typescript@5.3.3):
+    resolution: {integrity: sha512-j3WJsyQHP4TDlvnjHc/eseo0/eVkf0FaCpkqGwez5zD+Tj31onBzWZEXTnWKs8xRj0n3dMNYdy3SpiS6NubSvg==}
+    peerDependencies:
+      typescript: '*'
+    peerDependenciesMeta:
+      typescript:
+        optional: true
+    dependencies:
+      '@volar/typescript': 1.11.1
+      '@vue/language-core': 1.8.27(typescript@5.3.3)
+      path-browserify: 1.0.1
+      typescript: 5.3.3
+      vue-component-type-helpers: 1.8.27
+    dev: true
 
   /vue-component-type-helpers@1.8.27:
     resolution: {integrity: sha512-0vOfAtI67UjeO1G6UiX5Kd76CqaQ67wrRZiOe7UAb9Jm6GzlUr/fC7CV90XfwapJRjpCMaZFhv1V0ajWRmE9Dg==}
@@ -19635,8 +19770,8 @@ packages:
     resolution: {integrity: sha512-6bnLkn8O0JJyiFSIF0EfCogzeqNXpnjJ0vW/SZzNHfe6sPx30lTtTXlE5TFs2qhJlAtDFybStVNpL73cPe3OMQ==}
     dev: true
 
-  /vue-demi@0.13.11(vue@3.3.12):
-    resolution: {integrity: sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==}
+  /vue-demi@0.14.7(vue@3.4.21):
+    resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==}
     engines: {node: '>=12'}
     hasBin: true
     requiresBuild: true
@@ -19647,51 +19782,52 @@ packages:
       '@vue/composition-api':
         optional: true
     dependencies:
-      vue: 3.3.12(typescript@5.3.3)
+      vue: 3.4.21(typescript@5.3.3)
     dev: false
 
-  /vue-docgen-api@4.64.1(vue@3.3.12):
-    resolution: {integrity: sha512-jbOf7ByE3Zvtuk+429Jorl+eIeh2aB2Fx1GUo3xJd1aByJWE8KDlSEa6b11PB1ze8f0sRUBraRDinICCk0KY7g==}
+  /vue-docgen-api@4.75.1(vue@3.4.21):
+    resolution: {integrity: sha512-MECZ3uExz+ssmhD/2XrFoQQs93y17IVO1KDYTp8nr6i9GNrk67AAto6QAtilW1H/pTDPMkQxJ7w/25ZIqVtfAA==}
+    peerDependencies:
+      vue: '>=2'
     dependencies:
-      '@babel/parser': 7.23.4
-      '@babel/types': 7.23.4
-      '@vue/compiler-dom': 3.3.8
-      '@vue/compiler-sfc': 3.3.12
-      ast-types: 0.14.2
+      '@babel/parser': 7.24.0
+      '@babel/types': 7.24.0
+      '@vue/compiler-dom': 3.4.18
+      '@vue/compiler-sfc': 3.4.21
+      ast-types: 0.16.1
       hash-sum: 2.0.0
       lru-cache: 8.0.4
       pug: 3.0.2
-      recast: 0.22.0
+      recast: 0.23.4
       ts-map: 1.0.3
-      vue-inbrowser-compiler-independent-utils: 4.64.1(vue@3.3.12)
-    transitivePeerDependencies:
-      - vue
+      vue: 3.4.21(typescript@5.3.3)
+      vue-inbrowser-compiler-independent-utils: 4.71.1(vue@3.4.21)
     dev: true
 
-  /vue-eslint-parser@9.3.2(eslint@8.56.0):
-    resolution: {integrity: sha512-q7tWyCVaV9f8iQyIA5Mkj/S6AoJ9KBN8IeUSf3XEmBrOtxOZnfTg5s4KClbZBCK3GtnT/+RyCLZyDHuZwTuBjg==}
+  /vue-eslint-parser@9.4.2(eslint@8.57.0):
+    resolution: {integrity: sha512-Ry9oiGmCAK91HrKMtCrKFWmSFWvYkpGglCeFAIqDdr9zdXmMMpJOmUJS7WWsW7fX81h6mwHmUZCQQ1E0PkSwYQ==}
     engines: {node: ^14.17.0 || >=16.0.0}
     peerDependencies:
       eslint: '>=6.0.0'
     dependencies:
       debug: 4.3.4(supports-color@8.1.1)
-      eslint: 8.56.0
+      eslint: 8.57.0
       eslint-scope: 7.2.2
       eslint-visitor-keys: 3.4.3
       espree: 9.6.1
-      esquery: 1.4.2
+      esquery: 1.5.0
       lodash: 4.17.21
       semver: 7.5.4
     transitivePeerDependencies:
       - supports-color
     dev: true
 
-  /vue-inbrowser-compiler-independent-utils@4.64.1(vue@3.3.12):
-    resolution: {integrity: sha512-Hn32n07XZ8j9W8+fmOXPQL+i+W2e/8i6mkH4Ju3H6nR0+cfvmWM95GhczYi5B27+Y8JlCKgAo04IUiYce4mKAw==}
+  /vue-inbrowser-compiler-independent-utils@4.71.1(vue@3.4.21):
+    resolution: {integrity: sha512-K3wt3iVmNGaFEOUR4JIThQRWfqokxLfnPslD41FDZB2ajXp789+wCqJyGYlIFsvEQ2P61PInw6/ph5iiqg51gg==}
     peerDependencies:
       vue: '>=2'
     dependencies:
-      vue: 3.3.12(typescript@5.3.3)
+      vue: 3.4.21(typescript@5.3.3)
     dev: true
 
   /vue-template-compiler@2.7.14:
@@ -19701,40 +19837,40 @@ packages:
       he: 1.2.0
     dev: true
 
-  /vue-tsc@1.8.25(typescript@5.3.3):
-    resolution: {integrity: sha512-lHsRhDc/Y7LINvYhZ3pv4elflFADoEOo67vfClAfF2heVHpHmVquLSjojgCSIwzA4F0Pc4vowT/psXCYcfk+iQ==}
+  /vue-tsc@1.8.27(typescript@5.3.3):
+    resolution: {integrity: sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==}
     hasBin: true
     peerDependencies:
       typescript: '*'
     dependencies:
       '@volar/typescript': 1.11.1
-      '@vue/language-core': 1.8.25(typescript@5.3.3)
+      '@vue/language-core': 1.8.27(typescript@5.3.3)
       semver: 7.5.4
       typescript: 5.3.3
     dev: true
 
-  /vue@3.3.12(typescript@5.3.3):
-    resolution: {integrity: sha512-jYNv2QmET2OTHsFzfWHMnqgCfqL4zfo97QwofdET+GBRCHhSCHuMTTvNIgeSn0/xF3JRT5OGah6MDwUFN7MPlg==}
+  /vue@3.4.21(typescript@5.3.3):
+    resolution: {integrity: sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA==}
     peerDependencies:
       typescript: '*'
     peerDependenciesMeta:
       typescript:
         optional: true
     dependencies:
-      '@vue/compiler-dom': 3.3.12
-      '@vue/compiler-sfc': 3.3.12
-      '@vue/runtime-dom': 3.3.12
-      '@vue/server-renderer': 3.3.12(vue@3.3.12)
-      '@vue/shared': 3.3.12
+      '@vue/compiler-dom': 3.4.21
+      '@vue/compiler-sfc': 3.4.21
+      '@vue/runtime-dom': 3.4.21
+      '@vue/server-renderer': 3.4.21(vue@3.4.21)
+      '@vue/shared': 3.4.21
       typescript: 5.3.3
 
-  /vuedraggable@4.1.0(vue@3.3.12):
+  /vuedraggable@4.1.0(vue@3.4.21):
     resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==}
     peerDependencies:
       vue: ^3.0.1
     dependencies:
       sortablejs: 1.14.0
-      vue: 3.3.12(typescript@5.3.3)
+      vue: 3.4.21(typescript@5.3.3)
     dev: false
 
   /w3c-xmlserializer@5.0.0:
@@ -19778,22 +19914,14 @@ packages:
       defaults: 1.0.4
     dev: true
 
-  /web-encoding@1.1.5:
-    resolution: {integrity: sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==}
-    dependencies:
-      util: 0.12.5
-    optionalDependencies:
-      '@zxing/text-encoding': 0.9.0
-    dev: true
-
-  /web-push@3.6.6:
-    resolution: {integrity: sha512-SyteEck9fiCskNmPxs/GFhJsZrIyLfRvjWNmcUwULLJyCU0f1oxo2sWTokXA1mDAq9vxk4e4gVcb/8agq73NkQ==}
+  /web-push@3.6.7:
+    resolution: {integrity: sha512-OpiIUe8cuGjrj3mMBFWY+e4MMIkW3SVT+7vEIjvD9kejGUypv8GPDf84JdPWskK8zMRIJ6xYGm+Kxr8YkPyA0A==}
     engines: {node: '>= 16'}
     hasBin: true
     dependencies:
       asn1.js: 5.4.1
-      http_ece: 1.1.0
-      https-proxy-agent: 7.0.0
+      http_ece: 1.2.0
+      https-proxy-agent: 7.0.2
       jws: 4.0.0
       minimist: 1.2.8
     transitivePeerDependencies:
@@ -19826,6 +19954,7 @@ packages:
     engines: {node: '>=12'}
     dependencies:
       iconv-lite: 0.6.3
+    dev: false
 
   /whatwg-encoding@3.1.1:
     resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
@@ -19891,18 +20020,6 @@ packages:
       has-tostringtag: 1.0.0
     dev: true
 
-  /which-typed-array@1.1.9:
-    resolution: {integrity: sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==}
-    engines: {node: '>= 0.4'}
-    dependencies:
-      available-typed-arrays: 1.0.5
-      call-bind: 1.0.2
-      for-each: 0.3.3
-      gopd: 1.0.1
-      has-tostringtag: 1.0.0
-      is-typed-array: 1.1.10
-    dev: true
-
   /which@1.3.1:
     resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
     hasBin: true
@@ -19934,18 +20051,12 @@ packages:
       stackback: 0.0.2
     dev: true
 
-  /wide-align@1.1.5:
-    resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==}
-    dependencies:
-      string-width: 4.2.3
-    dev: false
-
   /with@7.0.2:
     resolution: {integrity: sha512-RNGKj82nUPg3g5ygxkQl0R937xLyho1J24ItRCBTr/m1YnZkzJy1hUiHUJrc/VlsDQzsCnInEGSg3bci0Lmd4w==}
     engines: {node: '>= 10.0.0'}
     dependencies:
       '@babel/parser': 7.22.16
-      '@babel/types': 7.22.17
+      '@babel/types': 7.24.0
       assert-never: 1.2.1
       babel-walk: 3.0.0-canary-5
 
@@ -19996,20 +20107,6 @@ packages:
       signal-exit: 3.0.7
     dev: true
 
-  /ws@6.2.2:
-    resolution: {integrity: sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==}
-    peerDependencies:
-      bufferutil: ^4.0.1
-      utf-8-validate: ^5.0.2
-    peerDependenciesMeta:
-      bufferutil:
-        optional: true
-      utf-8-validate:
-        optional: true
-    dependencies:
-      async-limiter: 1.0.1
-    dev: true
-
   /ws@8.14.2:
     resolution: {integrity: sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==}
     engines: {node: '>=10.0.0'}
@@ -20023,8 +20120,8 @@ packages:
         optional: true
     dev: false
 
-  /ws@8.15.1(bufferutil@4.0.7)(utf-8-validate@6.0.3):
-    resolution: {integrity: sha512-W5OZiCjXEmk0yZ66ZN82beM5Sz7l7coYxpRkzS+p9PP+ToQry8szKh+61eNktr7EA9DOwvFGhfC605jDHbP6QQ==}
+  /ws@8.16.0(bufferutil@4.0.7)(utf-8-validate@6.0.3):
+    resolution: {integrity: sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==}
     engines: {node: '>=10.0.0'}
     peerDependencies:
       bufferutil: ^4.0.1
@@ -20098,11 +20195,6 @@ packages:
   /yallist@4.0.0:
     resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
 
-  /yaml@2.3.1:
-    resolution: {integrity: sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==}
-    engines: {node: '>= 14'}
-    dev: false
-
   /yargs-parser@18.1.3:
     resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
     engines: {node: '>=6'}
@@ -20160,6 +20252,19 @@ packages:
       string-width: 4.2.3
       y18n: 5.0.8
       yargs-parser: 21.1.1
+    dev: true
+
+  /yargs@17.7.2:
+    resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
+    engines: {node: '>=12'}
+    dependencies:
+      cliui: 8.0.1
+      escalade: 3.1.1
+      get-caller-file: 2.0.5
+      require-directory: 2.1.1
+      string-width: 4.2.3
+      y18n: 5.0.8
+      yargs-parser: 21.1.1
 
   /yauzl@2.10.0:
     resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
@@ -20198,30 +20303,34 @@ packages:
       readable-stream: 3.6.0
     dev: false
 
-  github.com/aiscript-dev/aiscript-vscode/b5a8aa0ad927831a0b867d1c183460a14e6c48cd:
-    resolution: {tarball: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/b5a8aa0ad927831a0b867d1c183460a14e6c48cd}
-    name: aiscript-vscode
-    version: 0.0.6
-    engines: {vscode: ^1.83.0}
-    dev: false
+  /zwitch@2.0.4:
+    resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
+    dev: true
 
-  github.com/misskey-dev/browser-image-resizer/0227e860621e55cbed0aabe6dc601096a7748c4a:
-    resolution: {tarball: https://codeload.github.com/misskey-dev/browser-image-resizer/tar.gz/0227e860621e55cbed0aabe6dc601096a7748c4a}
-    name: browser-image-resizer
-    version: 2.2.1-misskey.3
-    dev: false
-
-  github.com/misskey-dev/sharp-read-bmp/02d9dc189fa7df0c4bea09330be26741772dac01:
-    resolution: {tarball: https://codeload.github.com/misskey-dev/sharp-read-bmp/tar.gz/02d9dc189fa7df0c4bea09330be26741772dac01}
-    name: sharp-read-bmp
-    version: 1.0.0
+  '@github.com/aiscript-dev/aiscript-languageserver/releases/download/0.1.5/aiscript-dev-aiscript-languageserver-0.1.5.tgz':
+    resolution: {tarball: https://github.com/aiscript-dev/aiscript-languageserver/releases/download/0.1.5/aiscript-dev-aiscript-languageserver-0.1.5.tgz}
+    name: '@aiscript-dev/aiscript-languageserver'
+    version: 0.1.5
+    hasBin: true
     dependencies:
-      decode-bmp: 0.2.1
-      decode-ico: 0.4.1
-      sharp: 0.31.3
+      seedrandom: 3.0.5
+      stringz: 2.1.0
+      uuid: 9.0.1
+      vscode-languageserver: 9.0.1
+      vscode-languageserver-textdocument: 1.0.11
     dev: false
 
-  github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@7.6.5)(@storybook/components@7.6.5)(@storybook/core-events@7.6.5)(@storybook/manager-api@7.6.5)(@storybook/preview-api@7.6.5)(@storybook/theming@7.6.5)(@storybook/types@7.6.5)(react-dom@18.2.0)(react@18.2.0):
+  github.com/aiscript-dev/aiscript-vscode/793211d40243c8775f6b85f015c221c82cbffb07:
+    resolution: {tarball: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/793211d40243c8775f6b85f015c221c82cbffb07}
+    name: aiscript-vscode
+    version: 0.1.2
+    engines: {vscode: ^1.83.0}
+    dependencies:
+      '@aiscript-dev/aiscript-languageserver': '@github.com/aiscript-dev/aiscript-languageserver/releases/download/0.1.5/aiscript-dev-aiscript-languageserver-0.1.5.tgz'
+      vscode-languageclient: 9.0.1
+    dev: false
+
+  github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@8.0.0-beta.6)(@storybook/components@8.0.0-beta.6)(@storybook/core-events@8.0.0-beta.6)(@storybook/manager-api@8.0.0-beta.6)(@storybook/preview-api@8.0.0-beta.6)(@storybook/theming@8.0.0-beta.6)(@storybook/types@8.0.0-beta.6)(react-dom@18.2.0)(react@18.2.0):
     resolution: {tarball: https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640}
     id: github.com/misskey-dev/storybook-addon-misskey-theme/cf583db098365b2ccc81a82f63ca9c93bc32b640
     name: storybook-addon-misskey-theme
@@ -20242,27 +20351,13 @@ packages:
       react-dom:
         optional: true
     dependencies:
-      '@storybook/blocks': 7.6.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/components': 7.6.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/core-events': 7.6.5
-      '@storybook/manager-api': 7.6.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/preview-api': 7.6.5
-      '@storybook/theming': 7.6.5(react-dom@18.2.0)(react@18.2.0)
-      '@storybook/types': 7.6.5
+      '@storybook/blocks': 8.0.0-beta.6(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/components': 8.0.0-beta.6(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/core-events': 8.0.0-beta.6
+      '@storybook/manager-api': 8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/preview-api': 8.0.0-beta.6
+      '@storybook/theming': 8.0.0-beta.6(react-dom@18.2.0)(react@18.2.0)
+      '@storybook/types': 8.0.0-beta.6
       react: 18.2.0
       react-dom: 18.2.0(react@18.2.0)
     dev: true
-
-  github.com/misskey-dev/summaly/d2a3e07205c3c9769bc5a7b42031c8884b5a25c8:
-    resolution: {tarball: https://codeload.github.com/misskey-dev/summaly/tar.gz/d2a3e07205c3c9769bc5a7b42031c8884b5a25c8}
-    name: summaly
-    version: 4.0.2
-    dependencies:
-      cheerio: 1.0.0-rc.12
-      escape-regexp: 0.0.1
-      got: 12.6.1
-      html-entities: 2.3.2
-      iconv-lite: 0.6.3
-      jschardet: 3.0.0
-      private-ip: 2.3.3
-      trace-redirect: 1.0.6
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index b69ec692c8..6b19c64e82 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -4,4 +4,6 @@ packages:
  - 'packages/sw'
  - 'packages/misskey-js'
  - 'packages/misskey-js/generator'
- - 'packages/megalodon'
\ No newline at end of file
+ - 'packages/megalodon'
+ - 'packages/misskey-reversi'
+ - 'packages/misskey-bubble-game'
diff --git a/scripts/build-assets.mjs b/scripts/build-assets.mjs
index 22f0871caa..7d0ab2fc80 100644
--- a/scripts/build-assets.mjs
+++ b/scripts/build-assets.mjs
@@ -1,20 +1,34 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
 import * as fs from 'node:fs/promises';
 import * as path from 'node:path';
+import { fileURLToPath } from 'node:url';
 import cssnano from 'cssnano';
+import * as yaml from 'js-yaml';
 import postcss from 'postcss';
 import * as terser from 'terser';
 
 import { build as buildLocales } from '../locales/index.js';
 import generateDTS from '../locales/generateDTS.js';
 import meta from '../package.json' assert { type: "json" };
+import buildTarball from './tarball.mjs';
+
+const configDir = fileURLToPath(new URL('../.config', import.meta.url));
+const configPath = process.env.MISSKEY_CONFIG_YML
+	? path.resolve(configDir, process.env.MISSKEY_CONFIG_YML)
+	: process.env.NODE_ENV === 'test'
+		? path.resolve(configDir, 'test.yml')
+		: path.resolve(configDir, 'default.yml');
 
 let locales = buildLocales();
 
+async function loadConfig() {
+	return fs.readFile(configPath, 'utf-8').then(data => yaml.load(data)).catch(() => null);
+}
+
 async function copyFrontendFonts() {
   await fs.cp('./packages/frontend/node_modules/three/examples/fonts', './built/_frontend_dist_/fonts', { dereference: true, recursive: true });
 }
@@ -35,13 +49,6 @@ async function copyFrontendLocales() {
   }
 }
 
-async function copyFrontendShikiAssets() {
-  await fs.cp('./packages/frontend/node_modules/shiki/dist', './built/_frontend_dist_/shiki/dist', { dereference: true, recursive: true });
-  await fs.cp('./packages/frontend/node_modules/shiki/languages', './built/_frontend_dist_/shiki/languages', { dereference: true, recursive: true });
-  await fs.cp('./packages/frontend/node_modules/aiscript-vscode/aiscript/syntaxes', './built/_frontend_dist_/shiki/languages', { dereference: true, recursive: true });
-  await fs.cp('./packages/frontend/node_modules/shiki/themes', './built/_frontend_dist_/shiki/themes', { dereference: true, recursive: true });
-}
-
 async function copyBackendViews() {
   await fs.cp('./packages/backend/src/server/web/views', './packages/backend/built/server/web/views', { recursive: true });
 }
@@ -81,16 +88,16 @@ async function build() {
     copyFrontendFonts(),
     copyFrontendTablerIcons(),
     copyFrontendLocales(),
-    copyFrontendShikiAssets(),
     copyBackendViews(),
     buildBackendScript(),
     buildBackendStyle(),
+		loadConfig().then(config => config?.publishTarballInsteadOfProvideRepositoryUrl && buildTarball()),
   ]);
 }
 
 await build();
 
-if (process.argv.includes("--watch")) {
+if (process.argv.includes('--watch')) {
 	const watcher = fs.watch('./locales');
 	for await (const event of watcher) {
 		const filename = event.filename?.replaceAll('\\', '/');
diff --git a/scripts/build-pre.js b/scripts/build-pre.js
index ed75aa6553..a90d53c75d 100644
--- a/scripts/build-pre.js
+++ b/scripts/build-pre.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
diff --git a/scripts/clean-all.js b/scripts/clean-all.js
index e4f5acae0d..3df2f2ceff 100644
--- a/scripts/clean-all.js
+++ b/scripts/clean-all.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -17,6 +17,15 @@ const fs = require('fs');
 	fs.rmSync(__dirname + '/../packages/sw/node_modules', { recursive: true, force: true });
 
 	fs.rmSync(__dirname + '/../packages/megalodon/lib', { recursive: true, force: true });
+	
+	fs.rmSync(__dirname + '/../packages/misskey-js/built', { recursive: true, force: true });
+	fs.rmSync(__dirname + '/../packages/misskey-js/node_modules', { recursive: true, force: true });
+
+	fs.rmSync(__dirname + '/../packages/misskey-reversi/built', { recursive: true, force: true });
+	fs.rmSync(__dirname + '/../packages/misskey-reversi/node_modules', { recursive: true, force: true });
+
+	fs.rmSync(__dirname + '/../packages/misskey-bubble-game/built', { recursive: true, force: true });
+	fs.rmSync(__dirname + '/../packages/misskey-bubble-game/node_modules', { recursive: true, force: true });
 
 	fs.rmSync(__dirname + '/../built', { recursive: true, force: true });
 	fs.rmSync(__dirname + '/../node_modules', { recursive: true, force: true });
diff --git a/scripts/clean.js b/scripts/clean.js
index df1d33888d..6d8182fec2 100644
--- a/scripts/clean.js
+++ b/scripts/clean.js
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -9,6 +9,9 @@ const fs = require('fs');
 	fs.rmSync(__dirname + '/../packages/backend/built', { recursive: true, force: true });
 	fs.rmSync(__dirname + '/../packages/frontend/built', { recursive: true, force: true });
 	fs.rmSync(__dirname + '/../packages/sw/built', { recursive: true, force: true });
+	fs.rmSync(__dirname + '/../packages/misskey-js/built', { recursive: true, force: true });
+	fs.rmSync(__dirname + '/../packages/misskey-reversi/built', { recursive: true, force: true });
+	fs.rmSync(__dirname + '/../packages/misskey-bubble-game/built', { recursive: true, force: true });
 	fs.rmSync(__dirname + '/../built', { recursive: true, force: true });
 	fs.rmSync(__dirname + '/../packages/megalodon/lib', { recursive: true, force: true });
 })();
diff --git a/scripts/dev.mjs b/scripts/dev.mjs
index 43d9496f3c..e00b2e254d 100644
--- a/scripts/dev.mjs
+++ b/scripts/dev.mjs
@@ -1,5 +1,5 @@
 /*
- * SPDX-FileCopyrightText: syuilo and other misskey contributors
+ * SPDX-FileCopyrightText: syuilo and misskey-project
  * SPDX-License-Identifier: AGPL-3.0-only
  */
 
@@ -28,7 +28,7 @@ await execa('pnpm', ['build-assets'], {
 	stderr: process.stderr,
 });
 
-await execa('pnpm', ['--filter', 'misskey-js', 'build'], {
+await execa('pnpm', ['--filter', 'misskey-js', 'ts'], {
 	cwd: _dirname + '/../',
 	stdout: process.stdout,
 	stderr: process.stderr,
@@ -40,6 +40,18 @@ await execa("pnpm", ['--filter', 'megalodon', 'build'], {
 	stderr: process.stderr,
 });
 
+await execa('pnpm', ['--filter', 'misskey-reversi', 'build:tsc'], {
+	cwd: _dirname + '/../',
+	stdout: process.stdout,
+	stderr: process.stderr,
+});
+
+await execa('pnpm', ['--filter', 'misskey-bubble-game', 'build:tsc'], {
+	cwd: _dirname + '/../',
+	stdout: process.stdout,
+	stderr: process.stderr,
+});
+
 execa('pnpm', ['build-pre', '--watch'], {
 	cwd: _dirname + '/../',
 	stdout: process.stdout,
@@ -58,7 +70,7 @@ execa('pnpm', ['--filter', 'backend', 'dev'], {
 	stderr: process.stderr,
 });
 
-execa('pnpm', ['--filter', 'frontend', 'dev'], {
+execa('pnpm', ['--filter', 'frontend', process.env.MK_DEV_PREFER === 'backend' ? 'watch' : 'dev'], {
 	cwd: _dirname + '/../',
 	stdout: process.stdout,
 	stderr: process.stderr,
@@ -75,3 +87,15 @@ execa('pnpm', ['--filter', 'misskey-js', 'watch'], {
 	stdout: process.stdout,
 	stderr: process.stderr,
 });
+
+execa('pnpm', ['--filter', 'misskey-reversi', 'watch'], {
+	cwd: _dirname + '/../',
+	stdout: process.stdout,
+	stderr: process.stderr,
+});
+
+execa('pnpm', ['--filter', 'misskey-bubble-game', 'watch'], {
+	cwd: _dirname + '/../',
+	stdout: process.stdout,
+	stderr: process.stderr,
+});
diff --git a/scripts/tarball.mjs b/scripts/tarball.mjs
new file mode 100644
index 0000000000..fbb833d94e
--- /dev/null
+++ b/scripts/tarball.mjs
@@ -0,0 +1,32 @@
+import { createWriteStream } from 'node:fs';
+import { mkdir } from 'node:fs/promises';
+import { resolve } from 'node:path';
+import { fileURLToPath } from 'node:url';
+import glob from 'fast-glob';
+import walk from 'ignore-walk';
+import Pack from 'tar/lib/pack.js';
+import meta from '../package.json' assert { type: "json" };
+
+const cwd = fileURLToPath(new URL('..', import.meta.url));
+const ignore = [
+	'**/.git/**/*',
+	'**/*ignore',
+	'**/.gitmodules',
+	// Exclude files you don't want to include in the tarball here
+];
+
+export default async function build() {
+	const mkdirPromise = mkdir(resolve(cwd, 'built', 'tarball'), { recursive: true });
+	const pack = new Pack({ cwd, gzip: true });
+	const patterns = await walk({ path: cwd, ignoreFiles: ['.gitignore'] });
+
+	for await (const entry of glob.stream(patterns, { cwd, ignore, dot: true })) {
+		pack.add(entry);
+	}
+
+	pack.end();
+
+	await mkdirPromise;
+
+	pack.pipe(createWriteStream(resolve(cwd, 'built', 'tarball', `sharkey-${meta.version}.tar.gz`)));
+}
diff --git a/tossface-emojis b/tossface-emojis
new file mode 160000
index 0000000000..3c0ac3f7bd
--- /dev/null
+++ b/tossface-emojis
@@ -0,0 +1 @@
+Subproject commit 3c0ac3f7bdd794cc334363bf834e58079ca00dd2