From 1b7043fa79df1cf96e381550c4e5541abd7f3570 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sat, 7 Jan 2023 19:57:48 +0900
Subject: [PATCH 01/68] :art:

---
 packages/frontend/src/pages/announcements.vue | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/packages/frontend/src/pages/announcements.vue b/packages/frontend/src/pages/announcements.vue
index b06bd30245..131f6d11ea 100644
--- a/packages/frontend/src/pages/announcements.vue
+++ b/packages/frontend/src/pages/announcements.vue
@@ -53,14 +53,14 @@ definePageMetadata({
 <style lang="scss" scoped>
 .ruryvtyk {
 	> .announcement {
+		padding: 16px;
+
 		> .header {
-			padding: 16px;
+			margin-bottom: 16px;
 			font-weight: bold;
 		}
 
 		> .content {
-			padding: 0 16px;
-		
 			> img {
 				display: block;
 				max-height: 300px;
@@ -69,7 +69,7 @@ definePageMetadata({
 		}
 
 		> .footer {
-			padding: 16px;
+			margin-top: 16px;
 		}
 	}
 }

From 8709574f3d4690873cc4f66053f8139ea4855fdf Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 09:58:35 +0900
Subject: [PATCH 02/68] :art:

---
 .../frontend/src/components/MkContextMenu.vue |  2 +-
 packages/frontend/src/components/MkModal.vue  | 24 +++++++++++++++----
 2 files changed, 21 insertions(+), 5 deletions(-)

diff --git a/packages/frontend/src/components/MkContextMenu.vue b/packages/frontend/src/components/MkContextMenu.vue
index 9d9abc5d09..bb4a716565 100644
--- a/packages/frontend/src/components/MkContextMenu.vue
+++ b/packages/frontend/src/components/MkContextMenu.vue
@@ -74,7 +74,7 @@ function onMousedown(evt: Event) {
 }
 
 .fade-enter-active, .fade-leave-active {
-	transition: opacity 0.5s cubic-bezier(0.16, 1, 0.3, 1), transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);
+	transition: opacity 0.3s cubic-bezier(0.16, 1, 0.3, 1), transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
 	transform-origin: left top;
 }
 
diff --git a/packages/frontend/src/components/MkModal.vue b/packages/frontend/src/components/MkModal.vue
index f323de605f..83b40fd01f 100644
--- a/packages/frontend/src/components/MkModal.vue
+++ b/packages/frontend/src/components/MkModal.vue
@@ -74,8 +74,24 @@ const type = $computed<ModalTypes>(() => {
 		return props.preferType!;
 	}
 });
-let transitionName = $ref(defaultStore.state.animation ? (type === 'drawer') ? 'modal-drawer' : (type === 'popup') ? 'modal-popup' : 'modal' : '');
-let transitionDuration = $ref(defaultStore.state.animation ? 200 : 0);
+let transitionName = $computed((() =>
+	defaultStore.state.animation
+		? (type === 'drawer')
+			? 'modal-drawer'
+			: (type === 'popup')
+				? 'modal-popup'
+				: 'modal'
+		: ''
+));
+let transitionDuration = $computed((() =>
+	transitionName === 'modal-popup'
+		? 100
+		: transitionName === 'modal'
+			? 200
+			: transitionName === 'modal-drawer'
+				? 200
+				: 0
+));
 
 let contentClicking = false;
 
@@ -308,12 +324,12 @@ defineExpose({
 
 .modal-popup-enter-active, .modal-popup-leave-active {
 	> .bg {
-		transition: opacity 0.2s !important;
+		transition: opacity 0.1s !important;
 	}
 
 	> .content {
 		transform-origin: var(--transformOrigin);
-		transition: opacity 0.2s cubic-bezier(0, 0, 0.2, 1), transform 0.2s cubic-bezier(0, 0, 0.2, 1) !important;
+		transition: opacity 0.1s cubic-bezier(0, 0, 0.2, 1), transform 0.1s cubic-bezier(0, 0, 0.2, 1) !important;
 	}
 }
 .modal-popup-enter-from, .modal-popup-leave-to {

From c550dafb815374cd050904d8959e62c8150889d9 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 10:20:28 +0900
Subject: [PATCH 03/68] tweak note component

---
 packages/frontend/src/components/MkMenu.vue   |  7 +-
 packages/frontend/src/components/MkNote.vue   | 65 +++++++++++-
 .../src/components/MkNoteDetailed.vue         | 65 +++++++++++-
 .../src/components/MkRenoteButton.vue         | 99 -------------------
 4 files changed, 124 insertions(+), 112 deletions(-)
 delete mode 100644 packages/frontend/src/components/MkRenoteButton.vue

diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue
index 31a3f7f1c1..abae272e8f 100644
--- a/packages/frontend/src/components/MkMenu.vue
+++ b/packages/frontend/src/components/MkMenu.vue
@@ -251,17 +251,18 @@ onBeforeUnmount(() => {
 				color: #fff;
 
 				&:before {
-					background: #d42e2e;
+					background: #d42e2e !important;
 				}
 			}
 		}
 
+		&:active,
 		&.active {
-			color: var(--fgOnAccent);
+			color: var(--fgOnAccent) !important;
 			opacity: 1;
 
 			&:before {
-				background: var(--accent);
+				background: var(--accent) !important;
 			}
 		}
 
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 5a97204bc5..16d8a6c269 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -75,14 +75,26 @@
 					<i class="ti ti-arrow-back-up"></i>
 					<p v-if="appearNote.repliesCount > 0" class="count">{{ appearNote.repliesCount }}</p>
 				</button>
-				<MkRenoteButton ref="renoteButton" class="button" :note="appearNote" :count="appearNote.renoteCount"/>
-				<button v-if="appearNote.myReaction == null" ref="reactButton" class="button _button" @click="react()">
+				<button
+					v-if="canRenote"
+					ref="renoteButton"
+					class="button _button"
+					@click="renote()"
+					@mousedown="renote()"
+				>
+					<i class="ti ti-repeat"></i>
+					<p v-if="appearNote.renoteCount > 0" class="count">{{ appearNote.renoteCount }}</p>
+				</button>
+				<button v-else class="button _button" disabled>
+					<i class="ti ti-ban"></i>
+				</button>
+				<button v-if="appearNote.myReaction == null" ref="reactButton" class="button _button" @click="react()" @mousedown="react()">
 					<i class="ti ti-plus"></i>
 				</button>
 				<button v-if="appearNote.myReaction != null" ref="reactButton" class="button _button reacted" @click="undoReact(appearNote)">
 					<i class="ti ti-minus"></i>
 				</button>
-				<button ref="menuButton" class="button _button" @click="menu()">
+				<button ref="menuButton" class="button _button" @click="menu()" @mousedown="menu()">
 					<i class="ti ti-dots"></i>
 				</button>
 			</footer>
@@ -111,7 +123,7 @@ import MkReactionsViewer from '@/components/MkReactionsViewer.vue';
 import MkMediaList from '@/components/MkMediaList.vue';
 import MkCwButton from '@/components/MkCwButton.vue';
 import MkPoll from '@/components/MkPoll.vue';
-import MkRenoteButton from '@/components/MkRenoteButton.vue';
+import MkUsersTooltip from '@/components/MkUsersTooltip.vue';
 import MkUrlPreview from '@/components/MkUrlPreview.vue';
 import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
 import MkVisibility from '@/components/MkVisibility.vue';
@@ -128,6 +140,7 @@ import { i18n } from '@/i18n';
 import { getNoteMenu } from '@/scripts/get-note-menu';
 import { useNoteCapture } from '@/scripts/use-note-capture';
 import { deepClone } from '@/scripts/clone';
+import { useTooltip } from '@/scripts/use-tooltip';
 
 const props = defineProps<{
 	note: misskey.entities.Note;
@@ -158,7 +171,7 @@ const isRenote = (
 
 const el = shallowRef<HTMLElement>();
 const menuButton = shallowRef<HTMLElement>();
-const renoteButton = shallowRef<InstanceType<typeof MkRenoteButton>>();
+const renoteButton = shallowRef<HTMLElement>();
 const renoteTime = shallowRef<HTMLElement>();
 const reactButton = shallowRef<HTMLElement>();
 let appearNote = $computed(() => isRenote ? note.renote as misskey.entities.Note : note);
@@ -175,6 +188,7 @@ const translation = ref(null);
 const translating = ref(false);
 const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)) : null;
 const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance);
+const canRenote = computed(() => ['public', 'home'].includes(appearNote.visibility) || appearNote.userId === $i.id);
 
 const keymap = {
 	'r': () => reply(true),
@@ -193,6 +207,47 @@ useNoteCapture({
 	isDeletedRef: isDeleted,
 });
 
+useTooltip(renoteButton, async (showing) => {
+	const renotes = await os.api('notes/renotes', {
+		noteId: appearNote.id,
+		limit: 11,
+	});
+
+	const users = renotes.map(x => x.user);
+
+	if (users.length < 1) return;
+
+	os.popup(MkUsersTooltip, {
+		showing,
+		users,
+		count: appearNote.renoteCount,
+		targetElement: renoteButton.value,
+	}, {}, 'closed');
+});
+
+function renote(viaKeyboard = false) {
+	pleaseLogin();
+	os.popupMenu([{
+		text: i18n.ts.renote,
+		icon: 'ti ti-repeat',
+		action: () => {
+			os.api('notes/create', {
+				renoteId: appearNote.id,
+			});
+		},
+	}, {
+		text: i18n.ts.quote,
+		icon: 'ti ti-quote',
+		action: () => {
+			os.post({
+				renote: appearNote,
+			});
+		},
+	}], renoteButton.value, {
+		viaKeyboard,
+	});
+}
+
 function reply(viaKeyboard = false): void {
 	pleaseLogin();
 	os.post({
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index c0e1ca7215..2d2830f08c 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -85,14 +85,26 @@
 					<i class="ti ti-arrow-back-up"></i>
 					<p v-if="appearNote.repliesCount > 0" class="count">{{ appearNote.repliesCount }}</p>
 				</button>
-				<MkRenoteButton ref="renoteButton" class="button" :note="appearNote" :count="appearNote.renoteCount"/>
-				<button v-if="appearNote.myReaction == null" ref="reactButton" class="button _button" @click="react()">
+				<button
+					v-if="canRenote"
+					ref="renoteButton"
+					class="button _button"
+					@click="renote()"
+					@mousedown="renote()"
+				>
+					<i class="ti ti-repeat"></i>
+					<p v-if="appearNote.renoteCount > 0" class="count">{{ appearNote.renoteCount }}</p>
+				</button>
+				<button v-else class="button _button" disabled>
+					<i class="ti ti-ban"></i>
+				</button>
+				<button v-if="appearNote.myReaction == null" ref="reactButton" class="button _button" @click="react()" @mousedown="react()">
 					<i class="ti ti-plus"></i>
 				</button>
 				<button v-if="appearNote.myReaction != null" ref="reactButton" class="button _button reacted" @click="undoReact(appearNote)">
 					<i class="ti ti-minus"></i>
 				</button>
-				<button ref="menuButton" class="button _button" @click="menu()">
+				<button ref="menuButton" class="button _button" @click="menu()" @mousedown="menu()">
 					<i class="ti ti-dots"></i>
 				</button>
 			</footer>
@@ -121,7 +133,7 @@ import MkReactionsViewer from '@/components/MkReactionsViewer.vue';
 import MkMediaList from '@/components/MkMediaList.vue';
 import MkCwButton from '@/components/MkCwButton.vue';
 import MkPoll from '@/components/MkPoll.vue';
-import MkRenoteButton from '@/components/MkRenoteButton.vue';
+import MkUsersTooltip from '@/components/MkUsersTooltip.vue';
 import MkUrlPreview from '@/components/MkUrlPreview.vue';
 import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
 import MkVisibility from '@/components/MkVisibility.vue';
@@ -138,6 +150,7 @@ import { i18n } from '@/i18n';
 import { getNoteMenu } from '@/scripts/get-note-menu';
 import { useNoteCapture } from '@/scripts/use-note-capture';
 import { deepClone } from '@/scripts/clone';
+import { useTooltip } from '@/scripts/use-tooltip';
 
 const props = defineProps<{
 	note: misskey.entities.Note;
@@ -168,7 +181,7 @@ const isRenote = (
 
 const el = shallowRef<HTMLElement>();
 const menuButton = shallowRef<HTMLElement>();
-const renoteButton = shallowRef<InstanceType<typeof MkRenoteButton>>();
+const renoteButton = shallowRef<HTMLElement>();
 const renoteTime = shallowRef<HTMLElement>();
 const reactButton = shallowRef<HTMLElement>();
 let appearNote = $computed(() => isRenote ? note.renote as misskey.entities.Note : note);
@@ -182,6 +195,7 @@ const urls = appearNote.text ? extractUrlFromMfm(mfm.parse(appearNote.text)) : n
 const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultStore.state.instanceTicker === 'remote' && appearNote.user.instance);
 const conversation = ref<misskey.entities.Note[]>([]);
 const replies = ref<misskey.entities.Note[]>([]);
+const canRenote = computed(() => ['public', 'home'].includes(appearNote.visibility) || appearNote.userId === $i.id);
 
 const keymap = {
 	'r': () => reply(true),
@@ -198,6 +212,47 @@ useNoteCapture({
 	isDeletedRef: isDeleted,
 });
 
+useTooltip(renoteButton, async (showing) => {
+	const renotes = await os.api('notes/renotes', {
+		noteId: appearNote.id,
+		limit: 11,
+	});
+
+	const users = renotes.map(x => x.user);
+
+	if (users.length < 1) return;
+
+	os.popup(MkUsersTooltip, {
+		showing,
+		users,
+		count: appearNote.renoteCount,
+		targetElement: renoteButton.value,
+	}, {}, 'closed');
+});
+
+function renote(viaKeyboard = false) {
+	pleaseLogin();
+	os.popupMenu([{
+		text: i18n.ts.renote,
+		icon: 'ti ti-repeat',
+		action: () => {
+			os.api('notes/create', {
+				renoteId: appearNote.id,
+			});
+		},
+	}, {
+		text: i18n.ts.quote,
+		icon: 'ti ti-quote',
+		action: () => {
+			os.post({
+				renote: appearNote,
+			});
+		},
+	}], renoteButton.value, {
+		viaKeyboard,
+	});
+}
+
 function reply(viaKeyboard = false): void {
 	pleaseLogin();
 	os.post({
diff --git a/packages/frontend/src/components/MkRenoteButton.vue b/packages/frontend/src/components/MkRenoteButton.vue
deleted file mode 100644
index e84d4a3faa..0000000000
--- a/packages/frontend/src/components/MkRenoteButton.vue
+++ /dev/null
@@ -1,99 +0,0 @@
-<template>
-<button
-	v-if="canRenote"
-	ref="buttonRef"
-	class="eddddedb _button canRenote"
-	@click="renote()"
->
-	<i class="ti ti-repeat"></i>
-	<p v-if="count > 0" class="count">{{ count }}</p>
-</button>
-<button v-else class="eddddedb _button">
-	<i class="ti ti-ban"></i>
-</button>
-</template>
-
-<script lang="ts" setup>
-import { computed, ref, shallowRef } from 'vue';
-import * as misskey from 'misskey-js';
-import XDetails from '@/components/MkUsersTooltip.vue';
-import { pleaseLogin } from '@/scripts/please-login';
-import * as os from '@/os';
-import { $i } from '@/account';
-import { useTooltip } from '@/scripts/use-tooltip';
-import { i18n } from '@/i18n';
-
-const props = defineProps<{
-	note: misskey.entities.Note;
-	count: number;
-}>();
-
-const buttonRef = shallowRef<HTMLElement>();
-
-const canRenote = computed(() => ['public', 'home'].includes(props.note.visibility) || props.note.userId === $i.id);
-
-useTooltip(buttonRef, async (showing) => {
-	const renotes = await os.api('notes/renotes', {
-		noteId: props.note.id,
-		limit: 11,
-	});
-
-	const users = renotes.map(x => x.user);
-
-	if (users.length < 1) return;
-
-	os.popup(XDetails, {
-		showing,
-		users,
-		count: props.count,
-		targetElement: buttonRef.value,
-	}, {}, 'closed');
-});
-
-const renote = (viaKeyboard = false) => {
-	pleaseLogin();
-	os.popupMenu([{
-		text: i18n.ts.renote,
-		icon: 'ti ti-repeat',
-		action: () => {
-			os.api('notes/create', {
-				renoteId: props.note.id,
-			});
-		},
-	}, {
-		text: i18n.ts.quote,
-		icon: 'ti ti-quote',
-		action: () => {
-			os.post({
-				renote: props.note,
-			});
-		},
-	}], buttonRef.value, {
-		viaKeyboard,
-	});
-};
-</script>
-
-<style lang="scss" scoped>
-.eddddedb {
-	display: inline-block;
-	height: 32px;
-	margin: 2px;
-	padding: 0 6px;
-	border-radius: 4px;
-
-	&:not(.canRenote) {
-		cursor: default;
-	}
-
-	&.renoted {
-		background: var(--accent);
-	}
-
-	> .count {
-		display: inline;
-		margin-left: 8px;
-		opacity: 0.7;
-	}
-}
-</style>

From b93e56d2e57a13e2657bf3e7bb7aa56985232ce0 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 10:24:30 +0900
Subject: [PATCH 04/68] :art:

---
 packages/frontend/src/components/MkSelect.vue | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/packages/frontend/src/components/MkSelect.vue b/packages/frontend/src/components/MkSelect.vue
index 4b5a14f5be..e39cc2a5ce 100644
--- a/packages/frontend/src/components/MkSelect.vue
+++ b/packages/frontend/src/components/MkSelect.vue
@@ -1,7 +1,7 @@
 <template>
 <div class="vblkjoeq">
 	<div class="label" @click="focus"><slot name="label"></slot></div>
-	<div ref="container" class="input" :class="{ inline, disabled, focused }" @click.prevent="onClick">
+	<div ref="container" class="input" :class="{ inline, disabled, focused }" @click.prevent="show" @mousedown.prevent="show">
 		<div ref="prefixEl" class="prefix"><slot name="prefix"></slot></div>
 		<select
 			ref="inputEl"
@@ -118,7 +118,7 @@ onMounted(() => {
 	});
 });
 
-const onClick = (ev: MouseEvent) => {
+function show(ev: MouseEvent) {
 	focused.value = true;
 	opening.value = true;
 
@@ -166,7 +166,7 @@ const onClick = (ev: MouseEvent) => {
 	}).then(() => {
 		focused.value = false;
 	});
-};
+}
 </script>
 
 <style lang="scss" scoped>
@@ -285,7 +285,7 @@ const onClick = (ev: MouseEvent) => {
 
 <style lang="scss" module>
 .chevron {
-	transition: transform 0.5s ease;
+	transition: transform 0.1s ease-out;
 }
 
 .chevronOpening {

From 4594fb11de74bc2a67b2c75268efe75feaacf785 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 10:32:37 +0900
Subject: [PATCH 05/68] :art:

---
 packages/frontend/src/components/MkModal.vue | 2 +-
 packages/frontend/src/components/MkNote.vue  | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/frontend/src/components/MkModal.vue b/packages/frontend/src/components/MkModal.vue
index 83b40fd01f..a83545ac15 100644
--- a/packages/frontend/src/components/MkModal.vue
+++ b/packages/frontend/src/components/MkModal.vue
@@ -1,7 +1,7 @@
 <template>
 <Transition :name="transitionName" :duration="transitionDuration" appear @after-leave="emit('closed')" @enter="emit('opening')" @after-enter="onOpened">
 	<div v-show="manualShowing != null ? manualShowing : showing" v-hotkey.global="keymap" class="qzhlnise" :class="{ drawer: type === 'drawer', dialog: type === 'dialog' || type === 'dialog:top', popup: type === 'popup' }" :style="{ zIndex, pointerEvents: (manualShowing != null ? manualShowing : showing) ? 'auto' : 'none', '--transformOrigin': transformOrigin }">
-		<div class="bg _modalBg" :class="{ transparent: transparentBg && (type === 'popup') }" :style="{ zIndex }" @click="onBgClick" @contextmenu.prevent.stop="() => {}"></div>
+		<div class="bg _modalBg" :class="{ transparent: transparentBg && (type === 'popup') }" :style="{ zIndex }" @click="onBgClick" @mousedown="onBgClick" @contextmenu.prevent.stop="() => {}"></div>
 		<div ref="content" class="content" :class="{ fixed, top: type === 'dialog:top' }" :style="{ zIndex }" @click.self="onBgClick">
 			<slot :max-height="maxHeight" :type="type"></slot>
 		</div>
diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 16d8a6c269..3aa9e516f0 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -44,7 +44,7 @@
 					<div class="text">
 						<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
 						<MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
-						<Mfm v-if="appearNote.text" :text="appearNote.text" :author="appearNote.user" :i="$i"/>
+						<Mfm v-if="appearNote.text" v-once :text="appearNote.text" :author="appearNote.user" :i="$i"/>
 						<a v-if="appearNote.renote != null" class="rp">RN:</a>
 						<div v-if="translating || translation" class="translation">
 							<MkLoading v-if="translating" mini/>

From 49f3090edd63a201e6093e1d1efeac362bf86d55 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 10:48:44 +0900
Subject: [PATCH 06/68] tweak note componsnt

---
 packages/frontend/src/components/MkNote.vue   | 12 +++--
 .../src/components/MkNoteDetailed.vue         | 15 ++++--
 .../frontend/src/components/MkNoteHeader.vue  | 11 +++--
 .../src/components/MkSubNoteContent.vue       |  2 +-
 .../frontend/src/components/MkVisibility.vue  | 48 -------------------
 5 files changed, 29 insertions(+), 59 deletions(-)
 delete mode 100644 packages/frontend/src/components/MkVisibility.vue

diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 3aa9e516f0..5be8df5285 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -13,7 +13,7 @@
 	<div v-if="appearNote._prId_" class="info"><i class="fas fa-bullhorn"></i> {{ i18n.ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.ts.hideThisNote }} <i class="ti ti-x"></i></button></div>
 	<div v-if="appearNote._featuredId_" class="info"><i class="ti ti-bolt"></i> {{ i18n.ts.featured }}</div>
 	<div v-if="isRenote" class="renote">
-		<MkAvatar class="avatar" :user="note.user"/>
+		<MkAvatar v-once class="avatar" :user="note.user"/>
 		<i class="ti ti-repeat"></i>
 		<I18n :src="i18n.ts.renotedBy" tag="span">
 			<template #user>
@@ -27,11 +27,16 @@
 				<i v-if="isMyRenote" class="ti ti-dots dropdownIcon"></i>
 				<MkTime :time="note.createdAt"/>
 			</button>
-			<MkVisibility :note="note"/>
+			<span v-if="note.visibility !== 'public'" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility[note.visibility]">
+				<i v-if="note.visibility === 'home'" class="ti ti-home"></i>
+				<i v-else-if="note.visibility === 'followers'" class="ti ti-lock-open"></i>
+				<i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
+			</span>
+			<span v-if="note.localOnly" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span>
 		</div>
 	</div>
 	<article class="article" @contextmenu.stop="onContextmenu">
-		<MkAvatar class="avatar" :user="appearNote.user"/>
+		<MkAvatar v-once class="avatar" :user="appearNote.user"/>
 		<div class="main">
 			<MkNoteHeader class="header" :note="appearNote" :mini="true"/>
 			<MkInstanceTicker v-if="showTicker" class="ticker" :instance="appearNote.user.instance"/>
@@ -126,7 +131,6 @@ import MkPoll from '@/components/MkPoll.vue';
 import MkUsersTooltip from '@/components/MkUsersTooltip.vue';
 import MkUrlPreview from '@/components/MkUrlPreview.vue';
 import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
-import MkVisibility from '@/components/MkVisibility.vue';
 import { pleaseLogin } from '@/scripts/please-login';
 import { focusPrev, focusNext } from '@/scripts/focus';
 import { checkWordMute } from '@/scripts/check-word-mute';
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index 2d2830f08c..d803253b2f 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -25,7 +25,12 @@
 				<i v-if="isMyRenote" class="ti ti-dots dropdownIcon"></i>
 				<MkTime :time="note.createdAt"/>
 			</button>
-			<MkVisibility :note="note"/>
+			<span v-if="note.visibility !== 'public'" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility[note.visibility]">
+				<i v-if="note.visibility === 'home'" class="ti ti-home"></i>
+				<i v-else-if="note.visibility === 'followers'" class="ti ti-lock-open"></i>
+				<i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
+			</span>
+			<span v-if="note.localOnly" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span>
 		</div>
 	</div>
 	<article class="article" @contextmenu.stop="onContextmenu">
@@ -38,7 +43,12 @@
 					</MkA>
 					<span v-if="appearNote.user.isBot" class="is-bot">bot</span>
 					<div class="info">
-						<MkVisibility :note="appearNote"/>
+						<span v-if="appearNote.visibility !== 'public'" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility[appearNote.visibility]">
+							<i v-if="appearNote.visibility === 'home'" class="ti ti-home"></i>
+							<i v-else-if="appearNote.visibility === 'followers'" class="ti ti-lock-open"></i>
+							<i v-else-if="appearNote.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
+						</span>
+						<span v-if="appearNote.localOnly" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span>
 					</div>
 				</div>
 				<div class="username"><MkAcct :user="appearNote.user"/></div>
@@ -136,7 +146,6 @@ import MkPoll from '@/components/MkPoll.vue';
 import MkUsersTooltip from '@/components/MkUsersTooltip.vue';
 import MkUrlPreview from '@/components/MkUrlPreview.vue';
 import MkInstanceTicker from '@/components/MkInstanceTicker.vue';
-import MkVisibility from '@/components/MkVisibility.vue';
 import { pleaseLogin } from '@/scripts/please-login';
 import { checkWordMute } from '@/scripts/check-word-mute';
 import { userPage } from '@/filters/user';
diff --git a/packages/frontend/src/components/MkNoteHeader.vue b/packages/frontend/src/components/MkNoteHeader.vue
index 333c3ddbd9..61d07f85e5 100644
--- a/packages/frontend/src/components/MkNoteHeader.vue
+++ b/packages/frontend/src/components/MkNoteHeader.vue
@@ -1,6 +1,6 @@
 <template>
 <header class="kkwtjztg">
-	<MkA v-user-preview="note.user.id" class="name" :to="userPage(note.user)">
+	<MkA v-once v-user-preview="note.user.id" class="name" :to="userPage(note.user)">
 		<MkUserName :user="note.user"/>
 	</MkA>
 	<div v-if="note.user.isBot" class="is-bot">bot</div>
@@ -9,7 +9,12 @@
 		<MkA class="created-at" :to="notePage(note)">
 			<MkTime :time="note.createdAt"/>
 		</MkA>
-		<MkVisibility :note="note"/>
+		<span v-if="note.visibility !== 'public'" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility[note.visibility]">
+			<i v-if="note.visibility === 'home'" class="ti ti-home"></i>
+			<i v-else-if="note.visibility === 'followers'" class="ti ti-lock-open"></i>
+			<i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
+		</span>
+		<span v-if="note.localOnly" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span>
 	</div>
 </header>
 </template>
@@ -17,7 +22,7 @@
 <script lang="ts" setup>
 import { } from 'vue';
 import * as misskey from 'misskey-js';
-import MkVisibility from '@/components/MkVisibility.vue';
+import { i18n } from '@/i18n';
 import { notePage } from '@/filters/note';
 import { userPage } from '@/filters/user';
 
diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue
index 55b04e6686..79eb9ccf45 100644
--- a/packages/frontend/src/components/MkSubNoteContent.vue
+++ b/packages/frontend/src/components/MkSubNoteContent.vue
@@ -4,7 +4,7 @@
 		<span v-if="note.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
 		<span v-if="note.deletedAt" style="opacity: 0.5">({{ i18n.ts.deleted }})</span>
 		<MkA v-if="note.replyId" class="reply" :to="`/notes/${note.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
-		<Mfm v-if="note.text" :text="note.text" :author="note.user" :i="$i"/>
+		<Mfm v-if="note.text" v-once :text="note.text" :author="note.user" :i="$i"/>
 		<MkA v-if="note.renoteId" class="rp" :to="`/notes/${note.renoteId}`">RN: ...</MkA>
 	</div>
 	<details v-if="note.files.length > 0">
diff --git a/packages/frontend/src/components/MkVisibility.vue b/packages/frontend/src/components/MkVisibility.vue
deleted file mode 100644
index 2becb69d5a..0000000000
--- a/packages/frontend/src/components/MkVisibility.vue
+++ /dev/null
@@ -1,48 +0,0 @@
-<template>
-<span v-if="note.visibility !== 'public'" :class="$style.visibility" :title="i18n.ts._visibility[note.visibility]">
-	<i v-if="note.visibility === 'home'" class="ti ti-home"></i>
-	<i v-else-if="note.visibility === 'followers'" class="ti ti-lock-open"></i>
-	<i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
-</span>
-<span v-if="note.localOnly" :class="$style.localOnly" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span>
-</template>
-
-<script lang="ts" setup>
-import { ref } from 'vue';
-import XDetails from '@/components/MkUsersTooltip.vue';
-import * as os from '@/os';
-import { useTooltip } from '@/scripts/use-tooltip';
-import { i18n } from '@/i18n';
-
-const props = defineProps<{
-	note: {
-		visibility: string;
-		localOnly?: boolean;
-		visibleUserIds?: string[];
-	},
-}>();
-
-const specified = $shallowRef<HTMLElement>();
-
-if (props.note.visibility === 'specified') {
-	useTooltip($$(specified), async (showing) => {
-		const users = await os.api('users/show', {
-			userIds: props.note.visibleUserIds,
-			limit: 10,
-		});
-
-		os.popup(XDetails, {
-			showing,
-			users,
-			count: props.note.visibleUserIds.length,
-			targetElement: specified,
-		}, {}, 'closed');
-	});
-}
-</script>
-
-<style lang="scss" module>
-.visibility, .localOnly {
-	margin-left: 0.5em;
-}
-</style>

From 2e2ed1385fbae6c102156a08fce2defb642e52c5 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 10:54:45 +0900
Subject: [PATCH 07/68] delete pollVote notification

---
 CHANGELOG.md                                  |  1 +
 locales/ja-JP.yml                             |  2 -
 packages/backend/src/core/PollService.ts      |  7 --
 .../entities/NotificationEntityService.ts     |  2 +-
 .../src/models/entities/Notification.ts       | 10 +--
 .../server/api/endpoints/notes/polls/vote.ts  |  7 --
 .../src/components/MkNotification.vue         | 12 ----
 .../sw/src/scripts/create-notification.ts     | 68 ++++++++-----------
 8 files changed, 37 insertions(+), 72 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 78caef3252..e437881129 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -30,6 +30,7 @@ You should also include the user name that made the change.
 
 #### For users
 - ノートのウォッチ機能が削除されました
+- アンケートに投票された際に通知が作成されなくなりました
 - 新たに動的なPagesを作ることはできなくなりました
 	- 代わりにAiScriptを用いてより柔軟に動的なコンテンツを作成できるMisskey Play機能が実装されています。
 - AiScriptが0.12.2にアップデートされました
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index b49d872a0b..3445e58356 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1550,7 +1550,6 @@ _notification:
   youGotReply: "{name}からのリプライ"
   youGotQuote: "{name}による引用"
   youRenoted: "{name}がRenoteしました"
-  youGotPoll: "{name}が投票しました"
   youGotMessagingMessageFromUser: "{name}からのチャットがあります"
   youGotMessagingMessageFromGroup: "{name}のチャットがあります"
   youWereFollowed: "フォローされました"
@@ -1569,7 +1568,6 @@ _notification:
     renote: "Renote"
     quote: "引用"
     reaction: "リアクション"
-    pollVote: "アンケートに投票された"
     pollEnded: "アンケートが終了"
     receiveFollowRequest: "フォロー申請を受け取った"
     followRequestAccepted: "フォローが受理された"
diff --git a/packages/backend/src/core/PollService.ts b/packages/backend/src/core/PollService.ts
index 3cc9b0cc9b..abc598ab76 100644
--- a/packages/backend/src/core/PollService.ts
+++ b/packages/backend/src/core/PollService.ts
@@ -92,13 +92,6 @@ export class PollService {
 			choice: choice,
 			userId: user.id,
 		});
-	
-		// Notify
-		this.createNotificationService.createNotification(note.userId, 'pollVote', {
-			notifierId: user.id,
-			noteId: note.id,
-			choice: choice,
-		});
 	}
 
 	@bindThis
diff --git a/packages/backend/src/core/entities/NotificationEntityService.ts b/packages/backend/src/core/entities/NotificationEntityService.ts
index 208d653877..a1c2c9cffb 100644
--- a/packages/backend/src/core/entities/NotificationEntityService.ts
+++ b/packages/backend/src/core/entities/NotificationEntityService.ts
@@ -98,7 +98,7 @@ export class NotificationEntityService implements OnModuleInit {
 				}),
 				reaction: notification.reaction,
 			} : {}),
-			...(notification.type === 'pollVote' ? {
+			...(notification.type === 'pollVote' ? { // TODO: そのうち消す
 				note: this.noteEntityService.pack(notification.note ?? notification.noteId!, { id: notification.notifieeId }, {
 					detail: true,
 					_hint_: options._hintForEachNotes_,
diff --git a/packages/backend/src/models/entities/Notification.ts b/packages/backend/src/models/entities/Notification.ts
index 53a7dda43a..6679cdb809 100644
--- a/packages/backend/src/models/entities/Notification.ts
+++ b/packages/backend/src/models/entities/Notification.ts
@@ -55,11 +55,11 @@ export class Notification {
 	 * 通知の種類。
 	 * follow - フォローされた
 	 * mention - 投稿で自分が言及された
-	 * reply - (自分または自分がWatchしている)投稿が返信された
-	 * renote - (自分または自分がWatchしている)投稿がRenoteされた
-	 * quote - (自分または自分がWatchしている)投稿が引用Renoteされた
-	 * reaction - (自分または自分がWatchしている)投稿にリアクションされた
-	 * pollVote - (自分または自分がWatchしている)投稿のアンケートに投票された
+	 * reply - 投稿に返信された
+	 * renote - 投稿がRenoteされた
+	 * quote - 投稿が引用Renoteされた
+	 * reaction - 投稿にリアクションされた
+	 * pollVote - 投稿のアンケートに投票された (廃止)
 	 * pollEnded - 自分のアンケートもしくは自分が投票したアンケートが終了した
 	 * receiveFollowRequest - フォローリクエストされた
 	 * followRequestAccepted - 自分の送ったフォローリクエストが承認された
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 793d7c5408..d583dfb936 100644
--- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
+++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
@@ -162,13 +162,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				userId: me.id,
 			});
 
-			// Notify
-			this.createNotificationService.createNotification(note.userId, 'pollVote', {
-				notifierId: me.id,
-				noteId: note.id,
-				choice: ps.choice,
-			});
-
 			// リモート投票の場合リプライ送信
 			if (note.userHost != null) {
 				const pollOwner = await this.usersRepository.findOneByOrFail({ id: note.userId }) as IRemoteUser;
diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue
index a21a9e12a1..2b9f9a2bb8 100644
--- a/packages/frontend/src/components/MkNotification.vue
+++ b/packages/frontend/src/components/MkNotification.vue
@@ -13,7 +13,6 @@
 			<i v-else-if="notification.type === 'reply'" class="ti ti-arrow-back-up"></i>
 			<i v-else-if="notification.type === 'mention'" class="ti ti-at"></i>
 			<i v-else-if="notification.type === 'quote'" class="ti ti-quote"></i>
-			<i v-else-if="notification.type === 'pollVote'" class="ti ti-chart-arrows"></i>
 			<i v-else-if="notification.type === 'pollEnded'" class="ti ti-chart-arrows"></i>
 			<!-- notification.reaction が null になることはまずないが、ここでoptional chaining使うと一部ブラウザで刺さるので念の為 -->
 			<XReactionIcon
@@ -51,11 +50,6 @@
 		<MkA v-if="notification.type === 'quote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
 			<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
 		</MkA>
-		<MkA v-if="notification.type === 'pollVote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
-			<i class="ti ti-quote"></i>
-			<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
-			<i class="ti ti-quote"></i>
-		</MkA>
 		<MkA v-if="notification.type === 'pollEnded'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
 			<i class="ti ti-quote"></i>
 			<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
@@ -239,12 +233,6 @@ useTooltip(reactionRef, (showing) => {
 				pointer-events: none;
 			}
 
-			&.pollVote {
-				padding: 3px;
-				background: #88a6b7;
-				pointer-events: none;
-			}
-
 			&.pollEnded {
 				padding: 3px;
 				background: #88a6b7;
diff --git a/packages/sw/src/scripts/create-notification.ts b/packages/sw/src/scripts/create-notification.ts
index a324851fe2..4b006e0f5a 100644
--- a/packages/sw/src/scripts/create-notification.ts
+++ b/packages/sw/src/scripts/create-notification.ts
@@ -51,8 +51,8 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
 						actions: userDetail.isFollowing ? [] : [
 							{
 								action: 'follow',
-								title: t('_notification._actions.followBack')
-							}
+								title: t('_notification._actions.followBack'),
+							},
 						],
 					}];
 				}
@@ -66,8 +66,8 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
 						actions: [
 							{
 								action: 'reply',
-								title: t('_notification._actions.reply')
-							}
+								title: t('_notification._actions.reply'),
+							},
 						],
 					}];
 
@@ -80,8 +80,8 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
 						actions: [
 							{
 								action: 'reply',
-								title: t('_notification._actions.reply')
-							}
+								title: t('_notification._actions.reply'),
+							},
 						],
 					}];
 
@@ -94,8 +94,8 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
 						actions: [
 							{
 								action: 'showUser',
-								title: getUserName(data.body.user)
-							}
+								title: getUserName(data.body.user),
+							},
 						],
 					}];
 
@@ -108,14 +108,14 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
 						actions: [
 							{
 								action: 'reply',
-								title: t('_notification._actions.reply')
+								title: t('_notification._actions.reply'),
 							},
 							...((data.body.note.visibility === 'public' || data.body.note.visibility === 'home') ? [
-							{
-								action: 'renote',
-								title: t('_notification._actions.renote')
-							}
-							] : [])
+								{
+									action: 'renote',
+									title: t('_notification._actions.renote'),
+								},
+							] : []),
 						],
 					}];
 
@@ -141,7 +141,7 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
 								const dummy = `${encodeURIComponent(`${u.host}${u.pathname}`)}.png`;
 								badge = `${origin}/proxy/${dummy}?${url.query({
 									url: u.href,
-									badge: '1'
+									badge: '1',
 								})}`;
 							}
 						}
@@ -162,20 +162,12 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
 						actions: [
 							{
 								action: 'showUser',
-								title: getUserName(data.body.user)
-							}
+								title: getUserName(data.body.user),
+							},
 						],
 					}];
 				}
 
-				case 'pollVote':
-					return [t('_notification.youGotPoll', { name: getUserName(data.body.user) }), {
-						body: data.body.note.text || '',
-						icon: data.body.user.avatarUrl,
-						badge: iconUrl('poll-h'),
-						data,
-					}];
-
 				case 'pollEnded':
 					return [t('_notification.pollEnded'), {
 						body: data.body.note.text || '',
@@ -192,12 +184,12 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
 						actions: [
 							{
 								action: 'accept',
-								title: t('accept')
+								title: t('accept'),
 							},
 							{
 								action: 'reject',
-								title: t('reject')
-							}
+								title: t('reject'),
+							},
 						],
 					}];
 
@@ -217,21 +209,21 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
 						actions: [
 							{
 								action: 'accept',
-								title: t('accept')
+								title: t('accept'),
 							},
 							{
 								action: 'reject',
-								title: t('reject')
-							}
+								title: t('reject'),
+							},
 						],
 					}];
 
 				case 'app':
-						return [data.body.header || data.body.body, {
-							body: data.body.header && data.body.body,
-							icon: data.body.icon,
-							data
-						}];
+					return [data.body.header || data.body.body, {
+						body: data.body.header && data.body.body,
+						icon: data.body.icon,
+						data,
+					}];
 
 				default:
 					return null;
@@ -279,7 +271,7 @@ export async function createEmptyNotification() {
 				silent: true,
 				badge: iconUrl('null'),
 				tag: 'read_notification',
-			}
+			},
 		);
 
 		res();
@@ -288,7 +280,7 @@ export async function createEmptyNotification() {
 			for (const n of
 				[
 					...(await self.registration.getNotifications({ tag: 'user_visible_auto_notification' })),
-					...(await self.registration.getNotifications({ tag: 'read_notification' }))
+					...(await self.registration.getNotifications({ tag: 'read_notification' })),
 				]
 			) {
 				n.close();

From 91356b1805f255ecaf5efc3e169d36280ddaf9d4 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 11:12:11 +0900
Subject: [PATCH 08/68] tweak

---
 packages/frontend/src/components/MkNote.vue         | 5 ++---
 packages/frontend/src/components/MkNoteDetailed.vue | 5 ++---
 packages/frontend/src/components/MkSelect.vue       | 2 +-
 3 files changed, 5 insertions(+), 7 deletions(-)

diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 5be8df5285..86f176a693 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -84,7 +84,6 @@
 					v-if="canRenote"
 					ref="renoteButton"
 					class="button _button"
-					@click="renote()"
 					@mousedown="renote()"
 				>
 					<i class="ti ti-repeat"></i>
@@ -93,13 +92,13 @@
 				<button v-else class="button _button" disabled>
 					<i class="ti ti-ban"></i>
 				</button>
-				<button v-if="appearNote.myReaction == null" ref="reactButton" class="button _button" @click="react()" @mousedown="react()">
+				<button v-if="appearNote.myReaction == null" ref="reactButton" class="button _button" @mousedown="react()">
 					<i class="ti ti-plus"></i>
 				</button>
 				<button v-if="appearNote.myReaction != null" ref="reactButton" class="button _button reacted" @click="undoReact(appearNote)">
 					<i class="ti ti-minus"></i>
 				</button>
-				<button ref="menuButton" class="button _button" @click="menu()" @mousedown="menu()">
+				<button ref="menuButton" class="button _button" @mousedown="menu()">
 					<i class="ti ti-dots"></i>
 				</button>
 			</footer>
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index d803253b2f..7c57a64b09 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -99,7 +99,6 @@
 					v-if="canRenote"
 					ref="renoteButton"
 					class="button _button"
-					@click="renote()"
 					@mousedown="renote()"
 				>
 					<i class="ti ti-repeat"></i>
@@ -108,13 +107,13 @@
 				<button v-else class="button _button" disabled>
 					<i class="ti ti-ban"></i>
 				</button>
-				<button v-if="appearNote.myReaction == null" ref="reactButton" class="button _button" @click="react()" @mousedown="react()">
+				<button v-if="appearNote.myReaction == null" ref="reactButton" class="button _button" @mousedown="react()">
 					<i class="ti ti-plus"></i>
 				</button>
 				<button v-if="appearNote.myReaction != null" ref="reactButton" class="button _button reacted" @click="undoReact(appearNote)">
 					<i class="ti ti-minus"></i>
 				</button>
-				<button ref="menuButton" class="button _button" @click="menu()" @mousedown="menu()">
+				<button ref="menuButton" class="button _button" @mousedown="menu()">
 					<i class="ti ti-dots"></i>
 				</button>
 			</footer>
diff --git a/packages/frontend/src/components/MkSelect.vue b/packages/frontend/src/components/MkSelect.vue
index e39cc2a5ce..c0ab781b06 100644
--- a/packages/frontend/src/components/MkSelect.vue
+++ b/packages/frontend/src/components/MkSelect.vue
@@ -1,7 +1,7 @@
 <template>
 <div class="vblkjoeq">
 	<div class="label" @click="focus"><slot name="label"></slot></div>
-	<div ref="container" class="input" :class="{ inline, disabled, focused }" @click.prevent="show" @mousedown.prevent="show">
+	<div ref="container" class="input" :class="{ inline, disabled, focused }" @mousedown.prevent="show">
 		<div ref="prefixEl" class="prefix"><slot name="prefix"></slot></div>
 		<select
 			ref="inputEl"

From ecbefce2aaad4ab53861d50d93c4d339ad2597e4 Mon Sep 17 00:00:00 2001
From: Soni L <EnderMoneyMod@gmail.com>
Date: Sat, 7 Jan 2023 23:15:54 -0300
Subject: [PATCH 09/68] Support remote objects in search (#9479)

* Support remote objects in search

Closes #9428

* Use account instead of localStorage

* Use useRouter instead of mainRouter

Co-authored-by: Chaos <chaoticryptidz@owo.monster>
---
 packages/frontend/src/pages/search.vue | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/packages/frontend/src/pages/search.vue b/packages/frontend/src/pages/search.vue
index c080b763bb..7918f9f577 100644
--- a/packages/frontend/src/pages/search.vue
+++ b/packages/frontend/src/pages/search.vue
@@ -12,12 +12,37 @@ import { computed } from 'vue';
 import XNotes from '@/components/MkNotes.vue';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
+import * as os from '@/os';
+import { useRouter } from '@/router';
+import { $i } from '@/account';
+
+const router = useRouter();
 
 const props = defineProps<{
 	query: string;
 	channel?: string;
 }>();
 
+const query = props.query;
+
+if ($i != null) {
+	if (query.startsWith('https://') || (query.startsWith('@') && !query.includes(' '))) {
+		const promise = os.api('ap/show', {
+			uri: props.query,
+		});
+
+		os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject);
+
+		const res = await promise;
+
+		if (res.type === 'User') {
+			router.replace(`/@${res.object.username}@${res.object.host}`);
+		} else if (res.type === 'Note') {
+			router.replace(`/notes/${res.object.id}`);
+		}
+	}
+}
+
 const pagination = {
 	endpoint: 'notes/search' as const,
 	limit: 10,

From 49a95c34bf1cdead34b87a82f4a0ca311e6c08bd Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 11:16:26 +0900
Subject: [PATCH 10/68] Update CHANGELOG.md

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index e437881129..c172a766e1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -78,6 +78,7 @@ You should also include the user name that made the change.
 - Client: Improve RSS widget @tamaina
 - Client: show Unicode emoji tooltip with its name in MkReactionsViewer.reaction @saschanaz
 - Client: OpenSearch support @SoniEx2 @chaoticryptidz
+- Client: Support remote objects in search @SoniEx2
 - Client: add user list widget @syuilo
 - Client: add heatmap of daily active users to about page @syuilo
 - Client: introduce fluent emoji @syuilo

From a3e282bc75ec11757018e88d246ce9a205b8bfac Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 11:16:36 +0900
Subject: [PATCH 11/68] New Crowdin updates (#9478)

* New translations ja-JP.yml (Thai)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Slovak)
---
 locales/de-DE.yml |  4 ++++
 locales/en-US.yml |  4 ++++
 locales/it-IT.yml |  2 +-
 locales/ko-KR.yml |  4 ++++
 locales/sk-SK.yml |  4 ++++
 locales/th-TH.yml | 12 ++++++++++++
 locales/zh-TW.yml | 14 ++++++++++++++
 7 files changed, 43 insertions(+), 1 deletion(-)

diff --git a/locales/de-DE.yml b/locales/de-DE.yml
index db6bd9ab05..3ab2e6abc3 100644
--- a/locales/de-DE.yml
+++ b/locales/de-DE.yml
@@ -920,6 +920,10 @@ like: "Gefällt mir"
 unlike: "\"Gefällt mir\" entfernen"
 numberOfLikes: "\"Gefällt mir\"-Anzahl"
 show: "Anzeigen"
+neverShow: "Nicht wieder anzeigen"
+remindMeLater: "Vielleicht später"
+didYouLikeMisskey: "Gefällt dir Misskey?"
+pleaseDonate: "Misskey ist die kostenlose Software, die von {host} verwendet wird. Wir würden uns über Spenden freuen, damit dessen Entwicklung weitergeführt werden kann!"
 _sensitiveMediaDetection:
   description: "Ermöglicht eine Erleichterung der Servermoderation durch die automatische Erkennungen von NSFW-Medien unter Verwendung von Machine Learning. Hierdurch wird die Serverlast etwas erhöht."
   sensitivity: "Erkennungssensitivität"
diff --git a/locales/en-US.yml b/locales/en-US.yml
index e2a7b32be8..5da7032eda 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -920,6 +920,10 @@ like: "Like"
 unlike: "Unlike"
 numberOfLikes: "Likes"
 show: "Show"
+neverShow: "Don't show again"
+remindMeLater: "Maybe later"
+didYouLikeMisskey: "Have you taken a liking to Misskey?"
+pleaseDonate: "{host} uses the free software, Misskey. We would highly appreciate your donations so development of Misskey can continue!"
 _sensitiveMediaDetection:
   description: "Reduces the effort of server moderation through automatically recognizing NSFW media via Machine Learning. This will slightly increase the load on the server."
   sensitivity: "Detection sensitivity"
diff --git a/locales/it-IT.yml b/locales/it-IT.yml
index 3fba19985e..10e8c98177 100644
--- a/locales/it-IT.yml
+++ b/locales/it-IT.yml
@@ -28,7 +28,7 @@ timeline: "Timeline"
 noAccountDescription: "L'utente non ha ancora scritto niente nella biografia di profilo."
 login: "Accedi"
 loggingIn: "Accesso in corso..."
-logout: "Esci"
+logout: "Uscita"
 signup: "Iscriviti"
 uploading: "Caricamento..."
 save: "Salva"
diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml
index d3a4a40b49..522f1f4aa7 100644
--- a/locales/ko-KR.yml
+++ b/locales/ko-KR.yml
@@ -920,6 +920,10 @@ like: "좋아요!"
 unlike: "좋아요 취소"
 numberOfLikes: "좋아요 수"
 show: "표시"
+neverShow: "다시 보지 않기"
+remindMeLater: "나중에 알림"
+didYouLikeMisskey: "Misskey가 마음에 드시나요?"
+pleaseDonate: "{host}은(는) 무료 소프트웨어 Misskey를 사용합니다. 후원을 통해 저희의 개발이 이어질 수 있게 도와주세요!"
 _sensitiveMediaDetection:
   description: "기계학습을 통해 자동으로 민감한 미디어를 탐지하여, 모더레이션에 참고할 수 있도록 합니다. 서버의 부하를 약간 증가시킵니다."
   sensitivity: "탐지 민감도"
diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml
index 3abfd8609d..35afc91918 100644
--- a/locales/sk-SK.yml
+++ b/locales/sk-SK.yml
@@ -913,6 +913,10 @@ tools: "Nástroje"
 cannotLoad: "Nedá sa načítať."
 like: "Páči sa mi"
 show: "Zobraziť"
+neverShow: "Nabudúce nezobrazovať"
+remindMeLater: "Pripomenúť neskôr"
+didYouLikeMisskey: "Páči sa vám Misskey?"
+pleaseDonate: "Misskey je bezplatný softvér, ktorý používa {host}. Prosím, prispejte, aby sme ho mohli ďalej rozvíjať!"
 _sensitiveMediaDetection:
   description: "Strojové učenie sa použije na automatickú detekciu citlivých médií na účely ich moderovania. Mierne sa zvýši zaťaženie servera."
   sensitivity: "Citlivosť detekcie"
diff --git a/locales/th-TH.yml b/locales/th-TH.yml
index 58deeff6f1..5a3400feb1 100644
--- a/locales/th-TH.yml
+++ b/locales/th-TH.yml
@@ -917,6 +917,8 @@ tools: "เครื่องมือ"
 cannotLoad: "ไม่สามารถโหลดได้"
 numberOfProfileView: "มุมมองโปรไฟล์"
 like: "ชื่นชอบ"
+unlike: "ไม่ชอบ"
+numberOfLikes: "จำนวนไลค์"
 show: "แสดงผล"
 _sensitiveMediaDetection:
   description: "ลดความพยายามในการดูแลเซิร์ฟเวอร์ผ่านการจดจำสื่อ NSFW โดยอัตโนมัติผ่านการเรียนรู้ของเครื่อง การทำสิ่งนี้อาจจะเพิ่มภาระบนเซิร์ฟเวอร์เล็กน้อย"
@@ -1317,6 +1319,7 @@ _widgets:
   jobQueue: "คิวงาน"
   serverMetric: "ตัวชี้วัดเซิร์ฟเวอร์"
   aiscript: "AiScript คอนโซล"
+  aiscriptApp: "AiScript แอพ"
   aichan: "เอไอ"
   userList: "รายชื่อผู้ใช้"
   _userList:
@@ -1423,7 +1426,16 @@ _timelines:
   social: "โซเชี่ยล"
   global: "ทั่วโลก"
 _play:
+  new: "สร้างการเล่น"
+  edit: "แก้ไขเล่น"
+  created: "สร้างการเล่นแล้ว"
+  updated: "แก้ไขการเล่นแล้ว"
+  deleted: "ลบการเล่นแล้ว"
+  pageSetting: "ตั้งค่าการเล่น"
+  editThisPage: "แก้ไข Play นี้"
   viewSource: "ดูต้นฉบับ"
+  my: "มาย เพลย์"
+  liked: "ไลค์ เพลย์"
   featured: "เป็นที่นิยม"
   title: "หัวข้อ"
   script: "สคริปต์"
diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml
index 661325d506..de12ef1fbf 100644
--- a/locales/zh-TW.yml
+++ b/locales/zh-TW.yml
@@ -918,7 +918,11 @@ cannotLoad: "無法載入"
 numberOfProfileView: "個人檔案檢視次數"
 like: "讚"
 unlike: "收回讚"
+numberOfLikes: "讚數"
 show: "檢視"
+neverShow: "不再顯示"
+remindMeLater: "以後再說"
+didYouLikeMisskey: "您是否喜愛Misskey呢?"
 _sensitiveMediaDetection:
   description: "您可以使用機器學習自動檢測敏感媒體並將其用於審核。 伺服器的負荷會稍微增加。"
   sensitivity: "檢測敏感度"
@@ -1318,6 +1322,7 @@ _widgets:
   jobQueue: "佇列"
   serverMetric: "服務器指標 "
   aiscript: "AiScript控制台"
+  aiscriptApp: "AiScript App"
   aichan: "小藍"
   userList: "使用者列表"
   _userList:
@@ -1424,7 +1429,16 @@ _timelines:
   social: "社群"
   global: "公開"
 _play:
+  new: "新增Play"
+  edit: "編輯Play"
+  created: "已新增Play"
+  updated: "已更新Play"
+  deleted: "已刪除Play"
+  pageSetting: "Play設定"
+  editThisPage: "編輯這個Play"
   viewSource: "檢視原始碼"
+  my: "自己的Play"
+  liked: "按了讚的Play"
   featured: "人氣"
   title: "標題"
   script: "腳本"

From f20d7cba74d044700388da61f3d959b455f3f9f5 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 11:17:02 +0900
Subject: [PATCH 12/68] 13.0.0-beta.28

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index e3d429ec63..185e338e63 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "misskey",
-	"version": "13.0.0-beta.27",
+	"version": "13.0.0-beta.28",
 	"codename": "indigo",
 	"repository": {
 		"type": "git",

From 244ea9593a799348d2c36b6846df652ba3a3f1bc Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 11:30:40 +0900
Subject: [PATCH 13/68] tweak components

---
 .../src/components/MkNotification.vue         | 96 ++++++++++---------
 packages/frontend/src/pages/about.emojis.vue  |  2 +-
 .../frontend/src/pages/about.federation.vue   |  2 +-
 packages/frontend/src/pages/admin/users.vue   |  2 +-
 4 files changed, 53 insertions(+), 49 deletions(-)

diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue
index 2b9f9a2bb8..d5638813b4 100644
--- a/packages/frontend/src/components/MkNotification.vue
+++ b/packages/frontend/src/components/MkNotification.vue
@@ -1,6 +1,6 @@
 <template>
 <div ref="elRef" class="qglefbjs" :class="notification.type">
-	<div class="head">
+	<div v-once class="head">
 		<MkAvatar v-if="notification.type === 'pollEnded'" class="icon" :user="notification.note.user"/>
 		<MkAvatar v-else-if="notification.user" class="icon" :user="notification.user"/>
 		<img v-else-if="notification.icon" class="icon" :src="notification.icon" alt=""/>
@@ -31,37 +31,39 @@
 			<span v-else>{{ notification.header }}</span>
 			<MkTime v-if="withTime" :time="notification.createdAt" class="time"/>
 		</header>
-		<MkA v-if="notification.type === 'reaction'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
-			<i class="ti ti-quote"></i>
-			<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
-			<i class="ti ti-quote"></i>
-		</MkA>
-		<MkA v-if="notification.type === 'renote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note.renote)">
-			<i class="ti ti-quote"></i>
-			<Mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :nowrap="!full"/>
-			<i class="ti ti-quote"></i>
-		</MkA>
-		<MkA v-if="notification.type === 'reply'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
-			<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
-		</MkA>
-		<MkA v-if="notification.type === 'mention'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
-			<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
-		</MkA>
-		<MkA v-if="notification.type === 'quote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
-			<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
-		</MkA>
-		<MkA v-if="notification.type === 'pollEnded'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
-			<i class="ti ti-quote"></i>
-			<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
-			<i class="ti ti-quote"></i>
-		</MkA>
-		<span v-if="notification.type === 'follow'" class="text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}<div v-if="full"><MkFollowButton :user="notification.user" :full="true"/></div></span>
-		<span v-if="notification.type === 'followRequestAccepted'" class="text" style="opacity: 0.6;">{{ i18n.ts.followRequestAccepted }}</span>
-		<span v-if="notification.type === 'receiveFollowRequest'" class="text" style="opacity: 0.6;">{{ i18n.ts.receiveFollowRequest }}<div v-if="full && !followRequestDone"><button class="_textButton" @click="acceptFollowRequest()">{{ i18n.ts.accept }}</button> | <button class="_textButton" @click="rejectFollowRequest()">{{ i18n.ts.reject }}</button></div></span>
-		<span v-if="notification.type === 'groupInvited'" class="text" style="opacity: 0.6;">{{ i18n.ts.groupInvited }}: <b>{{ notification.invitation.group.name }}</b><div v-if="full && !groupInviteDone"><button class="_textButton" @click="acceptGroupInvitation()">{{ i18n.ts.accept }}</button> | <button class="_textButton" @click="rejectGroupInvitation()">{{ i18n.ts.reject }}</button></div></span>
-		<span v-if="notification.type === 'app'" class="text">
-			<Mfm :text="notification.body" :nowrap="!full"/>
-		</span>
+		<div v-once class="content">
+			<MkA v-if="notification.type === 'reaction'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
+				<i class="ti ti-quote"></i>
+				<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
+				<i class="ti ti-quote"></i>
+			</MkA>
+			<MkA v-else-if="notification.type === 'renote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note.renote)">
+				<i class="ti ti-quote"></i>
+				<Mfm :text="getNoteSummary(notification.note.renote)" :plain="true" :nowrap="!full"/>
+				<i class="ti ti-quote"></i>
+			</MkA>
+			<MkA v-else-if="notification.type === 'reply'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
+				<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
+			</MkA>
+			<MkA v-else-if="notification.type === 'mention'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
+				<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
+			</MkA>
+			<MkA v-else-if="notification.type === 'quote'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
+				<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
+			</MkA>
+			<MkA v-else-if="notification.type === 'pollEnded'" class="text" :to="notePage(notification.note)" :title="getNoteSummary(notification.note)">
+				<i class="ti ti-quote"></i>
+				<Mfm :text="getNoteSummary(notification.note)" :plain="true" :nowrap="!full"/>
+				<i class="ti ti-quote"></i>
+			</MkA>
+			<span v-else-if="notification.type === 'follow'" class="text" style="opacity: 0.6;">{{ i18n.ts.youGotNewFollower }}<div v-if="full"><MkFollowButton :user="notification.user" :full="true"/></div></span>
+			<span v-else-if="notification.type === 'followRequestAccepted'" class="text" style="opacity: 0.6;">{{ i18n.ts.followRequestAccepted }}</span>
+			<span v-else-if="notification.type === 'receiveFollowRequest'" class="text" style="opacity: 0.6;">{{ i18n.ts.receiveFollowRequest }}<div v-if="full && !followRequestDone"><button class="_textButton" @click="acceptFollowRequest()">{{ i18n.ts.accept }}</button> | <button class="_textButton" @click="rejectFollowRequest()">{{ i18n.ts.reject }}</button></div></span>
+			<span v-else-if="notification.type === 'groupInvited'" class="text" style="opacity: 0.6;">{{ i18n.ts.groupInvited }}: <b>{{ notification.invitation.group.name }}</b><div v-if="full && !groupInviteDone"><button class="_textButton" @click="acceptGroupInvitation()">{{ i18n.ts.accept }}</button> | <button class="_textButton" @click="rejectGroupInvitation()">{{ i18n.ts.reject }}</button></div></span>
+			<span v-else-if="notification.type === 'app'" class="text">
+				<Mfm :text="notification.body" :nowrap="!full"/>
+			</span>
+		</div>
 	</div>
 </div>
 </template>
@@ -263,23 +265,25 @@ useTooltip(reactionRef, (showing) => {
 			}
 		}
 
-		> .text {
-			white-space: nowrap;
-			overflow: hidden;
-			text-overflow: ellipsis;
+		> .content {
+			> .text {
+				white-space: nowrap;
+				overflow: hidden;
+				text-overflow: ellipsis;
 
-			> i {
-				vertical-align: super;
-				font-size: 50%;
-				opacity: 0.5;
-			}
+				> i {
+					vertical-align: super;
+					font-size: 50%;
+					opacity: 0.5;
+				}
 
-			> i:first-child {
-				margin-right: 4px;
-			}
+				> i:first-child {
+					margin-right: 4px;
+				}
 
-			> i:last-child {
-				margin-left: 4px;
+				> i:last-child {
+					margin-left: 4px;
+				}
 			}
 		}
 	}
diff --git a/packages/frontend/src/pages/about.emojis.vue b/packages/frontend/src/pages/about.emojis.vue
index fef5cfff6a..669b6f1264 100644
--- a/packages/frontend/src/pages/about.emojis.vue
+++ b/packages/frontend/src/pages/about.emojis.vue
@@ -22,7 +22,7 @@
 	<MkFolder v-for="category in customEmojiCategories" :key="category" class="emojis">
 		<template #header>{{ category || $ts.other }}</template>
 		<div class="zuvgdzyt">
-			<XEmoji v-for="emoji in customEmojis.filter(e => e.category === category)" :key="emoji.name" class="emoji" :emoji="emoji"/>
+			<XEmoji v-for="emoji in customEmojis.filter(e => e.category === category)" v-once :key="emoji.name" class="emoji" :emoji="emoji"/>
 		</div>
 	</MkFolder>
 </div>
diff --git a/packages/frontend/src/pages/about.federation.vue b/packages/frontend/src/pages/about.federation.vue
index 101ea2297b..e530bb49dd 100644
--- a/packages/frontend/src/pages/about.federation.vue
+++ b/packages/frontend/src/pages/about.federation.vue
@@ -36,7 +36,7 @@
 
 	<MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination">
 		<div class="dqokceoi">
-			<MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" class="instance" :to="`/instance-info/${instance.host}`">
+			<MkA v-for="instance in items" v-once :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" class="instance" :to="`/instance-info/${instance.host}`">
 				<MkInstanceCardMini :instance="instance"/>
 			</MkA>
 		</div>
diff --git a/packages/frontend/src/pages/admin/users.vue b/packages/frontend/src/pages/admin/users.vue
index babe76e4ec..c198dfdb46 100644
--- a/packages/frontend/src/pages/admin/users.vue
+++ b/packages/frontend/src/pages/admin/users.vue
@@ -41,7 +41,7 @@
 					</div>
 
 					<MkPagination v-slot="{items}" ref="paginationComponent" :pagination="pagination" class="users">
-						<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" class="user" :to="`/user-info/${user.id}`">
+						<MkA v-for="user in items" v-once :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" class="user" :to="`/user-info/${user.id}`">
 							<MkUserCardMini :user="user"/>
 						</MkA>
 					</MkPagination>

From 192add376c8350bc9edff54c21be6796162877dd Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 11:47:16 +0900
Subject: [PATCH 14/68] fix MkModal animation

---
 packages/frontend/src/components/MkModal.vue | 30 +++++++++++---------
 1 file changed, 17 insertions(+), 13 deletions(-)

diff --git a/packages/frontend/src/components/MkModal.vue b/packages/frontend/src/components/MkModal.vue
index a83545ac15..492c281e98 100644
--- a/packages/frontend/src/components/MkModal.vue
+++ b/packages/frontend/src/components/MkModal.vue
@@ -63,6 +63,7 @@ let transformOrigin = $ref('center');
 let showing = $ref(true);
 let content = $shallowRef<HTMLElement>();
 const zIndex = os.claimZIndex(props.zPriority);
+let useSendAnime = $ref(false);
 const type = $computed<ModalTypes>(() => {
 	if (props.preferType === 'auto') {
 		if (!defaultStore.state.disableDrawer && isTouchUsing && deviceKind === 'smartphone') {
@@ -76,29 +77,32 @@ const type = $computed<ModalTypes>(() => {
 });
 let transitionName = $computed((() =>
 	defaultStore.state.animation
-		? (type === 'drawer')
-			? 'modal-drawer'
-			: (type === 'popup')
-				? 'modal-popup'
-				: 'modal'
+		? useSendAnime
+			? 'send'
+			: type === 'drawer'
+				? 'modal-drawer'
+				: type === 'popup'
+					? 'modal-popup'
+					: 'modal'
 		: ''
 ));
 let transitionDuration = $computed((() =>
-	transitionName === 'modal-popup'
-		? 100
-		: transitionName === 'modal'
-			? 200
-			: transitionName === 'modal-drawer'
+	transitionName === 'send'
+		? 400
+		: transitionName === 'modal-popup'
+			? 100
+			: transitionName === 'modal'
 				? 200
-				: 0
+				: transitionName === 'modal-drawer'
+					? 200
+					: 0
 ));
 
 let contentClicking = false;
 
 function close(opts: { useSendAnimation?: boolean } = {}) {
 	if (opts.useSendAnimation) {
-		transitionName = 'send';
-		transitionDuration = 400;
+		useSendAnime = true;
 	}
 
 	// eslint-disable-next-line vue/no-mutating-props

From 8b1fdb5a3b12a9a04edf5b1d4b4fe37ffa0feb5d Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 11:55:37 +0900
Subject: [PATCH 15/68] enhance(client): add theme

---
 CHANGELOG.md                                  |  1 +
 packages/frontend/src/scripts/theme.ts        |  1 +
 .../frontend/src/themes/l-botanical.json5     | 29 +++++++++++++++++++
 3 files changed, 31 insertions(+)
 create mode 100644 packages/frontend/src/themes/l-botanical.json5

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c172a766e1..76caefd453 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -82,6 +82,7 @@ You should also include the user name that made the change.
 - Client: add user list widget @syuilo
 - Client: add heatmap of daily active users to about page @syuilo
 - Client: introduce fluent emoji @syuilo
+- Client: add new theme @syuilo
 - Client: show fireworks when visit user who today is birthday @syuilo
 - Client: show bot warning on screen when logged in as bot account @syuilo
 - Client: improve overall performance of client @syuilo
diff --git a/packages/frontend/src/scripts/theme.ts b/packages/frontend/src/scripts/theme.ts
index 42cb00265d..28284c7bcf 100644
--- a/packages/frontend/src/scripts/theme.ts
+++ b/packages/frontend/src/scripts/theme.ts
@@ -24,6 +24,7 @@ export const getBuiltinThemes = () => Promise.all(
 		'l-coffee',
 		'l-apricot',
 		'l-rainy',
+		'l-botanical',
 		'l-vivid',
 		'l-cherry',
 		'l-sushi',
diff --git a/packages/frontend/src/themes/l-botanical.json5 b/packages/frontend/src/themes/l-botanical.json5
new file mode 100644
index 0000000000..2ea9a7d6c9
--- /dev/null
+++ b/packages/frontend/src/themes/l-botanical.json5
@@ -0,0 +1,29 @@
+{
+	id: '1100673c-f902-4ccd-93aa-7cb88be56178',
+
+	name: 'Mi Botanical Light',
+	author: 'ThinaticSystem',
+
+  base: 'light',
+	
+	props: {
+		accent: '#77b58c',
+		bg: 'e2deda',
+		fg: '#3d3d3d',
+		fgHighlighted: '#6bc9a0',
+		divider: '#cfcfcf',
+		panel: '@X14',
+		panelHeaderBg: '@panel',
+		panelHeaderDivider: '@divider',
+		header: ':alpha<0.7<@panel',
+		navBg: '@X14',
+		renote: '#229e92',
+		mention: '#da6d35',
+		mentionMe: '#d44c4c',
+		hashtag: '#4cb8d4',
+		link: '@accent',
+		buttonGradateB: ':hue<-70<@accent',
+		success: '#86b300',
+    X14: '#ebe7e5'
+	},
+}

From 01652b72b3274d4d55f29c026df49260cb459ca9 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 11:57:34 +0900
Subject: [PATCH 16/68] :art:

---
 packages/frontend/src/widgets/calendar.vue | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/packages/frontend/src/widgets/calendar.vue b/packages/frontend/src/widgets/calendar.vue
index 99bd36e2fc..1bd431259a 100644
--- a/packages/frontend/src/widgets/calendar.vue
+++ b/packages/frontend/src/widgets/calendar.vue
@@ -11,19 +11,19 @@
 	</div>
 	<div class="info">
 		<div>
-			<p>{{ i18n.ts.today }}: <b>{{ dayP.toFixed(1) }}%</b></p>
+			<p>{{ i18n.ts.today }}<b>{{ dayP.toFixed(1) }}%</b></p>
 			<div class="meter">
 				<div class="val" :style="{ width: `${dayP}%` }"></div>
 			</div>
 		</div>
 		<div>
-			<p>{{ i18n.ts.thisMonth }}: <b>{{ monthP.toFixed(1) }}%</b></p>
+			<p>{{ i18n.ts.thisMonth }}<b>{{ monthP.toFixed(1) }}%</b></p>
 			<div class="meter">
 				<div class="val" :style="{ width: `${monthP}%` }"></div>
 			</div>
 		</div>
 		<div>
-			<p>{{ i18n.ts.thisYear }}: <b>{{ yearP.toFixed(1) }}%</b></p>
+			<p>{{ i18n.ts.thisYear }}<b>{{ yearP.toFixed(1) }}%</b></p>
 			<div class="meter">
 				<div class="val" :style="{ width: `${yearP}%` }"></div>
 			</div>
@@ -168,13 +168,14 @@ defineExpose<WidgetComponentExpose>({
 			}
 
 			> p {
+				display: flex;
 				margin: 0 0 2px 0;
 				font-size: 0.75em;
 				line-height: 18px;
 				opacity: 0.8;
 
 				> b {
-					margin-left: 2px;
+					margin-left: auto;
 				}
 			}
 

From 132e45dff456d610fee60aa7e77f8eb422ac45ff Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 11:58:00 +0900
Subject: [PATCH 17/68] 13.0.0-beta.29

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 185e338e63..65b8cf510c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "misskey",
-	"version": "13.0.0-beta.28",
+	"version": "13.0.0-beta.29",
 	"codename": "indigo",
 	"repository": {
 		"type": "git",

From 4ffbbbe6d8c178f37b4294aa492f4214106c2dec Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 13:10:01 +0900
Subject: [PATCH 18/68] :art:

---
 .../frontend/src/pages/settings/accounts.vue  | 24 ++++++++++---------
 1 file changed, 13 insertions(+), 11 deletions(-)

diff --git a/packages/frontend/src/pages/settings/accounts.vue b/packages/frontend/src/pages/settings/accounts.vue
index c66cc12925..c2d511e744 100644
--- a/packages/frontend/src/pages/settings/accounts.vue
+++ b/packages/frontend/src/pages/settings/accounts.vue
@@ -1,18 +1,20 @@
 <template>
-<div class="_gaps_m">
+<div class="">
 	<FormSuspense :p="init">
-		<MkButton primary @click="addAccount"><i class="ti ti-plus"></i> {{ i18n.ts.addAccount }}</MkButton>
+		<div class="_gaps">
+			<MkButton primary @click="addAccount"><i class="ti ti-plus"></i> {{ i18n.ts.addAccount }}</MkButton>
 
-		<div v-for="account in accounts" :key="account.id" class="_panel _button lcjjdxlm" @click="menu(account, $event)">
-			<div class="avatar">
-				<MkAvatar :user="account" class="avatar"/>
-			</div>
-			<div class="body">
-				<div class="name">
-					<MkUserName :user="account"/>
+			<div v-for="account in accounts" :key="account.id" class="_panel _button lcjjdxlm" @click="menu(account, $event)">
+				<div class="avatar">
+					<MkAvatar :user="account" class="avatar"/>
 				</div>
-				<div class="acct">
-					<MkAcct :user="account"/>
+				<div class="body">
+					<div class="name">
+						<MkUserName :user="account"/>
+					</div>
+					<div class="acct">
+						<MkAcct :user="account"/>
+					</div>
 				</div>
 			</div>
 		</div>

From 5320f2301718e3e3b8f69f979c5dccefffe7689d Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 14:17:56 +0900
Subject: [PATCH 19/68] enhance(client): improve user activity page

---
 packages/frontend/src/components/MkFolder.vue |  10 +-
 packages/frontend/src/components/MkSignin.vue |   4 +-
 .../src/pages/user/activity.following.vue     | 174 ++++++++++++++++++
 .../src/pages/user/activity.notes.vue         | 174 ++++++++++++++++++
 .../frontend/src/pages/user/activity.pv.vue   |  78 +++-----
 packages/frontend/src/pages/user/activity.vue |  14 +-
 6 files changed, 386 insertions(+), 68 deletions(-)
 create mode 100644 packages/frontend/src/pages/user/activity.following.vue
 create mode 100644 packages/frontend/src/pages/user/activity.notes.vue

diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue
index dc10c7d3f3..aa2d9aac25 100644
--- a/packages/frontend/src/components/MkFolder.vue
+++ b/packages/frontend/src/components/MkFolder.vue
@@ -1,7 +1,7 @@
 <template>
 <div class="ssazuxis">
 	<header class="_button" :style="{ background: bg }" @click="showBody = !showBody">
-		<div class="title"><slot name="header"></slot></div>
+		<div class="title"><div><slot name="header"></slot></div></div>
 		<div class="divider"></div>
 		<button class="_button">
 			<template v-if="showBody"><i class="ti ti-chevron-up"></i></template>
@@ -127,14 +127,6 @@ export default defineComponent({
 			place-content: center;
 			margin: 0;
 			padding: 12px 16px 12px 0;
-
-			> i {
-				margin-right: 6px;
-			}
-
-			&:empty {
-				display: none;
-			}
 		}
 
 		> .divider {
diff --git a/packages/frontend/src/components/MkSignin.vue b/packages/frontend/src/components/MkSignin.vue
index 56e6f938f8..d70ae13ffd 100644
--- a/packages/frontend/src/components/MkSignin.vue
+++ b/packages/frontend/src/components/MkSignin.vue
@@ -14,7 +14,7 @@
 				<template #prefix><i class="ti ti-lock"></i></template>
 				<template #caption><button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button></template>
 			</MkInput>
-			<MkButton type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
+			<MkButton type="submit" large primary rounded :disabled="signing" style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
 		</div>
 		<div v-if="totpLogin" class="2fa-signin" :class="{ securityKeys: user && user.securityKeys }">
 			<div v-if="user && user.securityKeys" class="twofa-group tap-group">
@@ -36,7 +36,7 @@
 					<template #label>{{ i18n.ts.token }}</template>
 					<template #prefix><i class="ti ti-123"></i></template>
 				</MkInput>
-				<MkButton type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
+				<MkButton type="submit" :disabled="signing" large primary rounded style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
 			</div>
 		</div>
 	</div>
diff --git a/packages/frontend/src/pages/user/activity.following.vue b/packages/frontend/src/pages/user/activity.following.vue
new file mode 100644
index 0000000000..b7a51d5b69
--- /dev/null
+++ b/packages/frontend/src/pages/user/activity.following.vue
@@ -0,0 +1,174 @@
+<template>
+<div>
+	<MkLoading v-if="fetching"/>
+	<div v-show="!fetching" :class="$style.root" class="_panel">
+		<canvas ref="chartEl"></canvas>
+		<MkChartLegend ref="legendEl" style="margin-top: 8px;"/>
+	</div>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick } from 'vue';
+import { Chart, ChartDataset } from 'chart.js';
+import tinycolor from 'tinycolor2';
+import * as misskey from 'misskey-js';
+import gradient from 'chartjs-plugin-gradient';
+import { satisfies } from 'compare-versions';
+import * as os from '@/os';
+import { defaultStore } from '@/store';
+import { useChartTooltip } from '@/scripts/use-chart-tooltip';
+import { chartVLine } from '@/scripts/chart-vline';
+import { alpha } from '@/scripts/color';
+import { initChart } from '@/scripts/init-chart';
+import { chartLegend } from '@/scripts/chart-legend';
+import MkChartLegend from '@/components/MkChartLegend.vue';
+
+initChart();
+
+const props = defineProps<{
+	user: misskey.entities.User;
+}>();
+
+const chartEl = $shallowRef<HTMLCanvasElement>(null);
+let legendEl = $shallowRef<InstanceType<typeof MkChartLegend>>();
+const now = new Date();
+let chartInstance: Chart = null;
+const chartLimit = 50;
+let fetching = $ref(true);
+
+const { handler: externalTooltipHandler } = useChartTooltip();
+
+async function renderChart() {
+	if (chartInstance) {
+		chartInstance.destroy();
+	}
+
+	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) => ({
+			x: getDate(i).getTime(),
+			y: v,
+		}));
+	};
+
+	const raw = await os.api('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)';
+
+	const colorFollowLocal = '#008FFB';
+	const colorFollowRemote = '#008FFB88';
+	const colorFollowedLocal = '#2ecc71';
+	const colorFollowedRemote = '#2ecc7188';
+
+	function makeDataset(label: string, data: ChartDataset['data'], extra: Partial<ChartDataset> = {}): ChartDataset {
+		return Object.assign({
+			label: label,
+			data: data,
+			parsing: false,
+			pointRadius: 0,
+			borderWidth: 0,
+			borderJoinStyle: 'round',
+			borderRadius: 4,
+			barPercentage: 0.9,
+			fill: true,
+		} satisfies ChartDataset, extra);
+	}
+
+	chartInstance = new Chart(chartEl, {
+		type: 'bar',
+		data: {
+			datasets: [
+				makeDataset('Follow (local)', format(raw.local.followings.inc).slice().reverse(), { backgroundColor: colorFollowLocal }),
+				makeDataset('Follow (remote)', format(raw.remote.followings.inc).slice().reverse(), { backgroundColor: colorFollowRemote }),
+				makeDataset('Followed (local)', format(raw.local.followers.inc).slice().reverse(), { backgroundColor: colorFollowedLocal }),
+				makeDataset('Followed (remote)', format(raw.remote.followers.inc).slice().reverse(), { backgroundColor: colorFollowedRemote }),
+			],
+		},
+		options: {
+			aspectRatio: 3,
+			layout: {
+				padding: {
+					left: 0,
+					right: 8,
+					top: 0,
+					bottom: 0,
+				},
+			},
+			scales: {
+				x: {
+					type: 'time',
+					offset: true,
+					stacked: true,
+					time: {
+						stepSize: 1,
+						unit: 'day',
+						displayFormats: {
+							day: 'M/d',
+							month: 'Y/M',
+						},
+					},
+					grid: {
+						display: false,
+					},
+					ticks: {
+						display: true,
+						maxRotation: 0,
+						autoSkipPadding: 8,
+					},
+				},
+				y: {
+					position: 'left',
+					stacked: true,
+					suggestedMax: 10,
+					grid: {
+						display: true,
+					},
+					ticks: {
+						display: true,
+						//mirror: true,
+					},
+				},
+			},
+			interaction: {
+				intersect: false,
+				mode: 'index',
+			},
+			plugins: {
+				legend: {
+					display: false,
+				},
+				tooltip: {
+					enabled: false,
+					mode: 'index',
+					animation: {
+						duration: 0,
+					},
+					external: externalTooltipHandler,
+				},
+				gradient,
+			},
+		},
+		plugins: [chartVLine(vLineColor), chartLegend(legendEl)],
+	});
+
+	fetching = false;
+}
+
+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
new file mode 100644
index 0000000000..f2103b152c
--- /dev/null
+++ b/packages/frontend/src/pages/user/activity.notes.vue
@@ -0,0 +1,174 @@
+<template>
+<div>
+	<MkLoading v-if="fetching"/>
+	<div v-show="!fetching" :class="$style.root" class="_panel">
+		<canvas ref="chartEl"></canvas>
+		<MkChartLegend ref="legendEl" style="margin-top: 8px;"/>
+	</div>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick } from 'vue';
+import { Chart, ChartDataset } from 'chart.js';
+import tinycolor from 'tinycolor2';
+import * as misskey from 'misskey-js';
+import gradient from 'chartjs-plugin-gradient';
+import { satisfies } from 'compare-versions';
+import * as os from '@/os';
+import { defaultStore } from '@/store';
+import { useChartTooltip } from '@/scripts/use-chart-tooltip';
+import { chartVLine } from '@/scripts/chart-vline';
+import { alpha } from '@/scripts/color';
+import { initChart } from '@/scripts/init-chart';
+import { chartLegend } from '@/scripts/chart-legend';
+import MkChartLegend from '@/components/MkChartLegend.vue';
+
+initChart();
+
+const props = defineProps<{
+	user: misskey.entities.User;
+}>();
+
+const chartEl = $shallowRef<HTMLCanvasElement>(null);
+let legendEl = $shallowRef<InstanceType<typeof MkChartLegend>>();
+const now = new Date();
+let chartInstance: Chart = null;
+const chartLimit = 50;
+let fetching = $ref(true);
+
+const { handler: externalTooltipHandler } = useChartTooltip();
+
+async function renderChart() {
+	if (chartInstance) {
+		chartInstance.destroy();
+	}
+
+	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) => ({
+			x: getDate(i).getTime(),
+			y: v,
+		}));
+	};
+
+	const raw = await os.api('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)';
+
+	const colorNormal = '#008FFB';
+	const colorReply = '#FEB019';
+	const colorRenote = '#00E396';
+	const colorFile = '#e300db';
+
+	function makeDataset(label: string, data: ChartDataset['data'], extra: Partial<ChartDataset> = {}): ChartDataset {
+		return Object.assign({
+			label: label,
+			data: data,
+			parsing: false,
+			pointRadius: 0,
+			borderWidth: 0,
+			borderJoinStyle: 'round',
+			borderRadius: 4,
+			barPercentage: 0.9,
+			fill: true,
+		} satisfies ChartDataset, extra);
+	}
+
+	chartInstance = new Chart(chartEl, {
+		type: 'bar',
+		data: {
+			datasets: [
+				makeDataset('Normal', format(raw.diffs.normal).slice().reverse(), { backgroundColor: colorNormal }),
+				makeDataset('Reply', format(raw.diffs.reply).slice().reverse(), { backgroundColor: colorReply }),
+				makeDataset('Renote', format(raw.diffs.renote).slice().reverse(), { backgroundColor: colorRenote }),
+				makeDataset('File', format(raw.diffs.withFile).slice().reverse(), { backgroundColor: colorFile }),
+			],
+		},
+		options: {
+			aspectRatio: 3,
+			layout: {
+				padding: {
+					left: 0,
+					right: 8,
+					top: 0,
+					bottom: 0,
+				},
+			},
+			scales: {
+				x: {
+					type: 'time',
+					offset: true,
+					stacked: true,
+					time: {
+						stepSize: 1,
+						unit: 'day',
+						displayFormats: {
+							day: 'M/d',
+							month: 'Y/M',
+						},
+					},
+					grid: {
+						display: false,
+					},
+					ticks: {
+						display: true,
+						maxRotation: 0,
+						autoSkipPadding: 8,
+					},
+				},
+				y: {
+					position: 'left',
+					stacked: true,
+					suggestedMax: 10,
+					grid: {
+						display: true,
+					},
+					ticks: {
+						display: true,
+						//mirror: true,
+					},
+				},
+			},
+			interaction: {
+				intersect: false,
+				mode: 'index',
+			},
+			plugins: {
+				legend: {
+					display: false,
+				},
+				tooltip: {
+					enabled: false,
+					mode: 'index',
+					animation: {
+						duration: 0,
+					},
+					external: externalTooltipHandler,
+				},
+				gradient,
+			},
+		},
+		plugins: [chartVLine(vLineColor), chartLegend(legendEl)],
+	});
+
+	fetching = false;
+}
+
+onMounted(async () => {
+	renderChart();
+});
+</script>
+
+<style lang="scss" module>
+.root {
+	padding: 20px;
+}
+</style>
diff --git a/packages/frontend/src/pages/user/activity.pv.vue b/packages/frontend/src/pages/user/activity.pv.vue
index d74b641dac..4be6978da0 100644
--- a/packages/frontend/src/pages/user/activity.pv.vue
+++ b/packages/frontend/src/pages/user/activity.pv.vue
@@ -10,7 +10,7 @@
 
 <script lang="ts" setup>
 import { markRaw, version as vueVersion, onMounted, onBeforeUnmount, nextTick } from 'vue';
-import { Chart } from 'chart.js';
+import { Chart, ChartDataset } from 'chart.js';
 import tinycolor from 'tinycolor2';
 import * as misskey from 'misskey-js';
 import gradient from 'chartjs-plugin-gradient';
@@ -67,65 +67,33 @@ async function renderChart() {
 	const colorUser2 = '#3498db88';
 	const colorVisitor2 = '#2ecc7188';
 
+	function makeDataset(label: string, data: ChartDataset['data'], extra: Partial<ChartDataset> = {}): ChartDataset {
+		return Object.assign({
+			label: label,
+			data: data,
+			parsing: false,
+			pointRadius: 0,
+			borderWidth: 0,
+			borderJoinStyle: 'round',
+			borderRadius: 4,
+			barPercentage: 0.7,
+			categoryPercentage: 0.7,
+			fill: true,
+		} satisfies ChartDataset, extra);
+	}
+
 	chartInstance = new Chart(chartEl, {
 		type: 'bar',
 		data: {
-			datasets: [{
-				parsing: false,
-				label: 'UPV (user)',
-				data: format(raw.upv.user).slice().reverse(),
-				pointRadius: 0,
-				borderWidth: 0,
-				borderJoinStyle: 'round',
-				borderRadius: 4,
-				backgroundColor: colorUser,
-				barPercentage: 0.7,
-				categoryPercentage: 0.7,
-				fill: true,
-				stack: 'u',
-			}, {
-				parsing: false,
-				label: 'UPV (visitor)',
-				data: format(raw.upv.visitor).slice().reverse(),
-				pointRadius: 0,
-				borderWidth: 0,
-				borderJoinStyle: 'round',
-				borderRadius: 4,
-				backgroundColor: colorVisitor,
-				barPercentage: 0.7,
-				categoryPercentage: 0.7,
-				fill: true,
-				stack: 'u',
-			}, {
-				parsing: false,
-				label: 'NPV (user)',
-				data: format(raw.pv.user).slice().reverse(),
-				pointRadius: 0,
-				borderWidth: 0,
-				borderJoinStyle: 'round',
-				borderRadius: 4,
-				backgroundColor: colorUser2,
-				barPercentage: 0.7,
-				categoryPercentage: 0.7,
-				fill: true,
-				stack: 'n',
-			}, {
-				parsing: false,
-				label: 'NPV (visitor)',
-				data: format(raw.pv.visitor).slice().reverse(),
-				pointRadius: 0,
-				borderWidth: 0,
-				borderJoinStyle: 'round',
-				borderRadius: 4,
-				backgroundColor: colorVisitor2,
-				barPercentage: 0.7,
-				categoryPercentage: 0.7,
-				fill: true,
-				stack: 'n',
-			}],
+			datasets: [
+				makeDataset('UPV (user)', format(raw.upv.user).slice().reverse(), { backgroundColor: colorUser, stack: 'u' }),
+				makeDataset('UPV (visitor)', format(raw.upv.visitor).slice().reverse(), { backgroundColor: colorVisitor, stack: 'u' }),
+				makeDataset('NPV (user)', format(raw.pv.user).slice().reverse(), { backgroundColor: colorUser2, stack: 'n' }),
+				makeDataset('UPV (visitor)', format(raw.pv.visitor).slice().reverse(), { backgroundColor: colorVisitor2, stack: 'n' }),
+			],
 		},
 		options: {
-			aspectRatio: 2.5,
+			aspectRatio: 3,
 			layout: {
 				padding: {
 					left: 0,
diff --git a/packages/frontend/src/pages/user/activity.vue b/packages/frontend/src/pages/user/activity.vue
index 3def414674..e74b82fb27 100644
--- a/packages/frontend/src/pages/user/activity.vue
+++ b/packages/frontend/src/pages/user/activity.vue
@@ -2,11 +2,19 @@
 <MkSpacer :content-max="700">
 	<div class="_gaps">
 		<MkFolder class="item">
-			<template #header>Heatmap</template>
+			<template #header><i class="ti ti-activity"></i> Heatmap</template>
 			<XHeatmap :user="user" :src="'notes'"/>
 		</MkFolder>
 		<MkFolder class="item">
-			<template #header>PV</template>
+			<template #header><i class="ti ti-pencil"></i> Notes</template>
+			<XNotes :user="user"/>
+		</MkFolder>
+		<MkFolder class="item">
+			<template #header><i class="ti ti-users"></i> Following</template>
+			<XFollowing :user="user"/>
+		</MkFolder>
+		<MkFolder class="item">
+			<template #header><i class="ti ti-eye"></i> PV</template>
 			<XPv :user="user"/>
 		</MkFolder>
 	</div>
@@ -18,6 +26,8 @@ import { computed } from 'vue';
 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 MkFolder from '@/components/MkFolder.vue';
 
 const props = defineProps<{

From 25f4ee70306fff43cc78fbbbb036e33a64813f7e Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 14:19:32 +0900
Subject: [PATCH 20/68] Update CHANGELOG.md

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 76caefd453..611d5d986d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -79,6 +79,7 @@ You should also include the user name that made the change.
 - Client: show Unicode emoji tooltip with its name in MkReactionsViewer.reaction @saschanaz
 - Client: OpenSearch support @SoniEx2 @chaoticryptidz
 - Client: Support remote objects in search @SoniEx2
+- Client: user activity page @syuilo
 - Client: add user list widget @syuilo
 - Client: add heatmap of daily active users to about page @syuilo
 - Client: introduce fluent emoji @syuilo

From 1cfdd4c41a92d8734b13e6fcc12176c8d0bec7b2 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 14:22:04 +0900
Subject: [PATCH 21/68] refactor

---
 packages/frontend/src/components/MkNotification.vue           | 4 ++--
 packages/frontend/src/components/MkPlusOneEffect.vue          | 4 ++--
 packages/frontend/src/components/MkReactionTooltip.vue        | 4 ++--
 .../frontend/src/components/MkReactionsViewer.details.vue     | 4 ++--
 .../frontend/src/components/MkReactionsViewer.reaction.vue    | 4 ++--
 5 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue
index d5638813b4..7f21bb298a 100644
--- a/packages/frontend/src/components/MkNotification.vue
+++ b/packages/frontend/src/components/MkNotification.vue
@@ -15,7 +15,7 @@
 			<i v-else-if="notification.type === 'quote'" class="ti ti-quote"></i>
 			<i v-else-if="notification.type === 'pollEnded'" class="ti ti-chart-arrows"></i>
 			<!-- notification.reaction が null になることはまずないが、ここでoptional chaining使うと一部ブラウザで刺さるので念の為 -->
-			<XReactionIcon
+			<MkReactionIcon
 				v-else-if="notification.type === 'reaction'"
 				ref="reactionRef"
 				:reaction="notification.reaction ? notification.reaction.replace(/^:(\w+):$/, ':$1@.:') : notification.reaction"
@@ -71,7 +71,7 @@
 <script lang="ts" setup>
 import { ref, shallowRef, onMounted, onUnmounted, watch } from 'vue';
 import * as misskey from 'misskey-js';
-import XReactionIcon from '@/components/MkReactionIcon.vue';
+import MkReactionIcon from '@/components/MkReactionIcon.vue';
 import MkFollowButton from '@/components/MkFollowButton.vue';
 import XReactionTooltip from '@/components/MkReactionTooltip.vue';
 import { getNoteSummary } from '@/scripts/get-note-summary';
diff --git a/packages/frontend/src/components/MkPlusOneEffect.vue b/packages/frontend/src/components/MkPlusOneEffect.vue
index 3e4f2059fd..af3a6c0e41 100644
--- a/packages/frontend/src/components/MkPlusOneEffect.vue
+++ b/packages/frontend/src/components/MkPlusOneEffect.vue
@@ -1,7 +1,7 @@
 <template>
 <div :class="$style.root" :style="{ zIndex, top: `${y - 64}px`, left: `${x - 64}px` }">
 	<span class="text" :class="{ up }">
-		<XReactionIcon class="icon" :reaction="reaction"/>
+		<MkReactionIcon class="icon" :reaction="reaction"/>
 	</span>
 </div>
 </template>
@@ -9,7 +9,7 @@
 <script lang="ts" setup>
 import { onMounted } from 'vue';
 import * as os from '@/os';
-import XReactionIcon from '@/components/MkReactionIcon.vue';
+import MkReactionIcon from '@/components/MkReactionIcon.vue';
 
 const props = withDefaults(defineProps<{
 	reaction: string;
diff --git a/packages/frontend/src/components/MkReactionTooltip.vue b/packages/frontend/src/components/MkReactionTooltip.vue
index 34ebc4da2d..3b3c2c1a1d 100644
--- a/packages/frontend/src/components/MkReactionTooltip.vue
+++ b/packages/frontend/src/components/MkReactionTooltip.vue
@@ -1,7 +1,7 @@
 <template>
 <MkTooltip ref="tooltip" :showing="showing" :target-element="targetElement" :max-width="340" @closed="emit('closed')">
 	<div class="beeadbfb">
-		<XReactionIcon :reaction="reaction" class="icon" :no-style="true"/>
+		<MkReactionIcon :reaction="reaction" class="icon" :no-style="true"/>
 		<div class="name">{{ reaction.replace('@.', '') }}</div>
 	</div>
 </MkTooltip>
@@ -10,7 +10,7 @@
 <script lang="ts" setup>
 import { } from 'vue';
 import MkTooltip from './MkTooltip.vue';
-import XReactionIcon from '@/components/MkReactionIcon.vue';
+import MkReactionIcon from '@/components/MkReactionIcon.vue';
 
 defineProps<{
 	showing: boolean;
diff --git a/packages/frontend/src/components/MkReactionsViewer.details.vue b/packages/frontend/src/components/MkReactionsViewer.details.vue
index aae647b6af..6ec227b0ed 100644
--- a/packages/frontend/src/components/MkReactionsViewer.details.vue
+++ b/packages/frontend/src/components/MkReactionsViewer.details.vue
@@ -2,7 +2,7 @@
 <MkTooltip ref="tooltip" :showing="showing" :target-element="targetElement" :max-width="340" @closed="emit('closed')">
 	<div class="bqxuuuey">
 		<div class="reaction">
-			<XReactionIcon :reaction="reaction" class="icon" :no-style="true"/>
+			<MkReactionIcon :reaction="reaction" class="icon" :no-style="true"/>
 			<div class="name">{{ getReactionName(reaction) }}</div>
 		</div>
 		<div class="users">
@@ -19,7 +19,7 @@
 <script lang="ts" setup>
 import { } from 'vue';
 import MkTooltip from './MkTooltip.vue';
-import XReactionIcon from '@/components/MkReactionIcon.vue';
+import MkReactionIcon from '@/components/MkReactionIcon.vue';
 import { getEmojiName } from '@/scripts/emojilist';
 
 defineProps<{
diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
index e63192a629..1d5c402765 100644
--- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue
+++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
@@ -6,7 +6,7 @@
 	:class="{ reacted: note.myReaction == reaction, canToggle }"
 	@click="toggleReaction()"
 >
-	<XReactionIcon class="icon" :reaction="reaction"/>
+	<MkReactionIcon class="icon" :reaction="reaction"/>
 	<span class="count">{{ count }}</span>
 </button>
 </template>
@@ -15,7 +15,7 @@
 import { computed, onMounted, ref, shallowRef, watch } from 'vue';
 import * as misskey from 'misskey-js';
 import XDetails from '@/components/MkReactionsViewer.details.vue';
-import XReactionIcon from '@/components/MkReactionIcon.vue';
+import MkReactionIcon from '@/components/MkReactionIcon.vue';
 import * as os from '@/os';
 import { useTooltip } from '@/scripts/use-tooltip';
 import { $i } from '@/account';

From dcca2350ddff11e3b5f0d38784b437504c055af1 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 14:28:14 +0900
Subject: [PATCH 22/68] :art:

---
 .../src/components/MkReactionsViewer.reaction.vue  | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
index 1d5c402765..321ce7ab2c 100644
--- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue
+++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
@@ -1,6 +1,6 @@
 <template>
 <button
-	ref="buttonRef"
+	ref="buttonEl"
 	v-ripple="canToggle"
 	class="hkzvhatu _button"
 	:class="{ reacted: note.myReaction == reaction, canToggle }"
@@ -28,7 +28,7 @@ const props = defineProps<{
 	note: misskey.entities.Note;
 }>();
 
-const buttonRef = shallowRef<HTMLElement>();
+const buttonEl = shallowRef<HTMLElement>();
 
 const canToggle = computed(() => !props.reaction.match(/@\w/) && $i);
 
@@ -58,9 +58,9 @@ const toggleReaction = () => {
 const anime = () => {
 	if (document.hidden) return;
 
-	const rect = buttonRef.value.getBoundingClientRect();
-	const x = rect.left + (buttonRef.value.offsetWidth / 2);
-	const y = rect.top + (buttonRef.value.offsetHeight / 2);
+	const rect = buttonEl.value.getBoundingClientRect();
+	const x = rect.left + 16;
+	const y = rect.top + (buttonEl.value.offsetHeight / 2);
 	os.popup(MkPlusOneEffect, { reaction: props.reaction, x, y }, {}, 'end');
 };
 
@@ -72,7 +72,7 @@ onMounted(() => {
 	if (!props.isInitial) anime();
 });
 
-useTooltip(buttonRef, async (showing) => {
+useTooltip(buttonEl, async (showing) => {
 	const reactions = await os.apiGet('notes/reactions', {
 		noteId: props.note.id,
 		type: props.reaction,
@@ -87,7 +87,7 @@ useTooltip(buttonRef, async (showing) => {
 		reaction: props.reaction,
 		users,
 		count: props.count,
-		targetElement: buttonRef.value,
+		targetElement: buttonEl.value,
 	}, {}, 'closed');
 }, 100);
 </script>

From 0d7ee20a77d731ce93aa1cde8388aece53377fd0 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 14:29:22 +0900
Subject: [PATCH 23/68] :art:

---
 packages/frontend/src/pages/flash/flash-index.vue | 2 +-
 packages/frontend/src/pages/pages.vue             | 2 +-
 packages/frontend/src/ui/visitor/b.vue            | 2 +-
 packages/frontend/src/ui/visitor/header.vue       | 2 +-
 4 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/packages/frontend/src/pages/flash/flash-index.vue b/packages/frontend/src/pages/flash/flash-index.vue
index fb377be579..7a1080d3f0 100644
--- a/packages/frontend/src/pages/flash/flash-index.vue
+++ b/packages/frontend/src/pages/flash/flash-index.vue
@@ -69,7 +69,7 @@ const headerActions = $computed(() => [{
 const headerTabs = $computed(() => [{
 	key: 'featured',
 	title: i18n.ts._play.featured,
-	icon: 'fas fa-fire-alt',
+	icon: 'ti ti-flare',
 }, {
 	key: 'my',
 	title: i18n.ts._play.my,
diff --git a/packages/frontend/src/pages/pages.vue b/packages/frontend/src/pages/pages.vue
index b077180df8..af5f631caf 100644
--- a/packages/frontend/src/pages/pages.vue
+++ b/packages/frontend/src/pages/pages.vue
@@ -63,7 +63,7 @@ const headerActions = $computed(() => [{
 const headerTabs = $computed(() => [{
 	key: 'featured',
 	title: i18n.ts._pages.featured,
-	icon: 'fas fa-fire-alt',
+	icon: 'ti ti-flare',
 }, {
 	key: 'my',
 	title: i18n.ts._pages.my,
diff --git a/packages/frontend/src/ui/visitor/b.vue b/packages/frontend/src/ui/visitor/b.vue
index c8b32bb98f..9a2320da88 100644
--- a/packages/frontend/src/ui/visitor/b.vue
+++ b/packages/frontend/src/ui/visitor/b.vue
@@ -34,7 +34,7 @@
 		<div v-if="showMenu" class="menu">
 			<MkA to="/" class="link" active-class="active"><i class="ti ti-home icon"></i>{{ $ts.home }}</MkA>
 			<MkA to="/explore" class="link" active-class="active"><i class="ti ti-hash icon"></i>{{ $ts.explore }}</MkA>
-			<MkA to="/featured" class="link" active-class="active"><i class="fas fa-fire-alt icon"></i>{{ $ts.featured }}</MkA>
+			<MkA to="/featured" class="link" active-class="active"><i class="ti ti-flare icon"></i>{{ $ts.featured }}</MkA>
 			<MkA to="/channels" class="link" active-class="active"><i class="ti ti-device-tv icon"></i>{{ $ts.channel }}</MkA>
 			<div class="action">
 				<button class="_buttonPrimary" @click="signup()">{{ $ts.signup }}</button>
diff --git a/packages/frontend/src/ui/visitor/header.vue b/packages/frontend/src/ui/visitor/header.vue
index 7300b12a75..07a40c0378 100644
--- a/packages/frontend/src/ui/visitor/header.vue
+++ b/packages/frontend/src/ui/visitor/header.vue
@@ -4,7 +4,7 @@
 		<div class="content">
 			<MkA to="/" class="link" active-class="active"><i class="ti ti-home icon"></i>{{ $ts.home }}</MkA>
 			<MkA to="/explore" class="link" active-class="active"><i class="ti ti-hash icon"></i>{{ $ts.explore }}</MkA>
-			<MkA to="/featured" class="link" active-class="active"><i class="fas fa-fire-alt icon"></i>{{ $ts.featured }}</MkA>
+			<MkA to="/featured" class="link" active-class="active"><i class="ti ti-flare icon"></i>{{ $ts.featured }}</MkA>
 			<MkA to="/channels" class="link" active-class="active"><i class="ti ti-device-tv icon"></i>{{ $ts.channel }}</MkA>
 			<div v-if="info" class="page active link">
 				<div class="title">

From 1f6a41cea7b1015029e3bf04528e4edb0de76af4 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 14:30:00 +0900
Subject: [PATCH 24/68] 13.0.0-beta.30

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 65b8cf510c..46e96d8716 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "misskey",
-	"version": "13.0.0-beta.29",
+	"version": "13.0.0-beta.30",
 	"codename": "indigo",
 	"repository": {
 		"type": "git",

From 7c2d2676f77310c3a4269c532f5e1d17262a5608 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 16:17:42 +0900
Subject: [PATCH 25/68] refactor

---
 .../components/{MkPlusOneEffect.vue => MkReactionEffect.vue}  | 0
 .../frontend/src/components/MkReactionsViewer.reaction.vue    | 4 ++--
 2 files changed, 2 insertions(+), 2 deletions(-)
 rename packages/frontend/src/components/{MkPlusOneEffect.vue => MkReactionEffect.vue} (100%)

diff --git a/packages/frontend/src/components/MkPlusOneEffect.vue b/packages/frontend/src/components/MkReactionEffect.vue
similarity index 100%
rename from packages/frontend/src/components/MkPlusOneEffect.vue
rename to packages/frontend/src/components/MkReactionEffect.vue
diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
index 321ce7ab2c..cc6e87e1c4 100644
--- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue
+++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue
@@ -19,7 +19,7 @@ import MkReactionIcon from '@/components/MkReactionIcon.vue';
 import * as os from '@/os';
 import { useTooltip } from '@/scripts/use-tooltip';
 import { $i } from '@/account';
-import MkPlusOneEffect from '@/components/MkPlusOneEffect.vue';
+import MkReactionEffect from '@/components/MkReactionEffect.vue';
 
 const props = defineProps<{
 	reaction: string;
@@ -61,7 +61,7 @@ const anime = () => {
 	const rect = buttonEl.value.getBoundingClientRect();
 	const x = rect.left + 16;
 	const y = rect.top + (buttonEl.value.offsetHeight / 2);
-	os.popup(MkPlusOneEffect, { reaction: props.reaction, x, y }, {}, 'end');
+	os.popup(MkReactionEffect, { reaction: props.reaction, x, y }, {}, 'end');
 };
 
 watch(() => props.count, (newCount, oldCount) => {

From b442c38f4148d0825189c646cfc249ec4a4c212e Mon Sep 17 00:00:00 2001
From: tamaina <tamaina@hotmail.co.jp>
Date: Sun, 8 Jan 2023 16:47:57 +0900
Subject: [PATCH 26/68] enhance: Push Notification badges to Tabler Icons
 (#9406)

* enhance: Push Notification badges to Tabler Icons

* add receiveFollowRequest icon
---
 .../assets/notification-badges/LICENSE        |   5 ---
 .../backend/assets/notification-badges/at.png | Bin 1752 -> 0 bytes
 .../assets/notification-badges/check.png      | Bin 577 -> 0 bytes
 .../clipboard-check-solid.png                 | Bin 1402 -> 0 bytes
 .../assets/notification-badges/clock.png      | Bin 1131 -> 0 bytes
 .../assets/notification-badges/comments.png   | Bin 1134 -> 0 bytes
 .../notification-badges/id-card-alt.png       | Bin 844 -> 0 bytes
 .../assets/notification-badges/plus.png       | Bin 507 -> 0 bytes
 .../assets/notification-badges/poll-h.png     | Bin 689 -> 0 bytes
 .../notification-badges/quote-right.png       | Bin 772 -> 0 bytes
 .../assets/notification-badges/reply.png      | Bin 930 -> 0 bytes
 .../assets/notification-badges/retweet.png    | Bin 798 -> 0 bytes
 .../assets/notification-badges/satellite.png  | Bin 1743 -> 0 bytes
 .../assets/notification-badges/user-plus.png  | Bin 991 -> 0 bytes
 packages/backend/assets/tabler-badges/LICENSE |  24 +++++++++++++
 .../backend/assets/tabler-badges/antenna.png  | Bin 0 -> 516 bytes
 .../assets/tabler-badges/arrow-back-up.png    | Bin 0 -> 952 bytes
 packages/backend/assets/tabler-badges/at.png  | Bin 0 -> 2909 bytes
 .../assets/tabler-badges/chart-arrows.png     | Bin 0 -> 829 bytes
 .../assets/tabler-badges/circle-check.png     | Bin 0 -> 2307 bytes
 .../backend/assets/tabler-badges/messages.png | Bin 0 -> 1056 bytes
 .../null.png                                  | Bin
 .../backend/assets/tabler-badges/plus.png     | Bin 0 -> 414 bytes
 .../backend/assets/tabler-badges/quote.png    | Bin 0 -> 1011 bytes
 .../backend/assets/tabler-badges/repeat.png   | Bin 0 -> 1206 bytes
 .../assets/tabler-badges/user-plus.png        | Bin 0 -> 1431 bytes
 .../backend/assets/tabler-badges/users.png    | Bin 0 -> 1911 bytes
 .../sw/src/scripts/create-notification.ts     |  32 +++++++++++-------
 packages/sw/src/types.ts                      |  15 ++++++++
 29 files changed, 59 insertions(+), 17 deletions(-)
 delete mode 100644 packages/backend/assets/notification-badges/LICENSE
 delete mode 100644 packages/backend/assets/notification-badges/at.png
 delete mode 100644 packages/backend/assets/notification-badges/check.png
 delete mode 100644 packages/backend/assets/notification-badges/clipboard-check-solid.png
 delete mode 100644 packages/backend/assets/notification-badges/clock.png
 delete mode 100644 packages/backend/assets/notification-badges/comments.png
 delete mode 100644 packages/backend/assets/notification-badges/id-card-alt.png
 delete mode 100644 packages/backend/assets/notification-badges/plus.png
 delete mode 100644 packages/backend/assets/notification-badges/poll-h.png
 delete mode 100644 packages/backend/assets/notification-badges/quote-right.png
 delete mode 100644 packages/backend/assets/notification-badges/reply.png
 delete mode 100644 packages/backend/assets/notification-badges/retweet.png
 delete mode 100644 packages/backend/assets/notification-badges/satellite.png
 delete mode 100644 packages/backend/assets/notification-badges/user-plus.png
 create mode 100644 packages/backend/assets/tabler-badges/LICENSE
 create mode 100644 packages/backend/assets/tabler-badges/antenna.png
 create mode 100644 packages/backend/assets/tabler-badges/arrow-back-up.png
 create mode 100644 packages/backend/assets/tabler-badges/at.png
 create mode 100644 packages/backend/assets/tabler-badges/chart-arrows.png
 create mode 100644 packages/backend/assets/tabler-badges/circle-check.png
 create mode 100644 packages/backend/assets/tabler-badges/messages.png
 rename packages/backend/assets/{notification-badges => tabler-badges}/null.png (100%)
 create mode 100644 packages/backend/assets/tabler-badges/plus.png
 create mode 100644 packages/backend/assets/tabler-badges/quote.png
 create mode 100644 packages/backend/assets/tabler-badges/repeat.png
 create mode 100644 packages/backend/assets/tabler-badges/user-plus.png
 create mode 100644 packages/backend/assets/tabler-badges/users.png

diff --git a/packages/backend/assets/notification-badges/LICENSE b/packages/backend/assets/notification-badges/LICENSE
deleted file mode 100644
index 841c4c682b..0000000000
--- a/packages/backend/assets/notification-badges/LICENSE
+++ /dev/null
@@ -1,5 +0,0 @@
-Font Awesome Icons
--------------------------
-
-Ⓒ Font Awesome
-CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/)
diff --git a/packages/backend/assets/notification-badges/at.png b/packages/backend/assets/notification-badges/at.png
deleted file mode 100644
index d1492856de2b2f837a24a439eaa13631283aa87a..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 1752
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

diff --git a/packages/backend/assets/notification-badges/check.png b/packages/backend/assets/notification-badges/check.png
deleted file mode 100644
index baeb76babfa3f9f8dff025f7a007a0be3eeb69b8..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 577
zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7Ro>VB+v}aSW-L^Y%`>FSDaa>qq5<
zLK7NVv>7=q9Gz4{n1CD>l@O=O8~4~xeA;eYT=uPQf68I$xj*?Bojg>5#(+S*=%l_$
z^VZA1U%OoMiNZ^jmzzGx^gh>mxZtUFoo=0-{<h0+@8mx+c2ckJJkQblSKUt8q{zF*
zZAW1DkDd?yx{sfKw)w&){94d{etYBh8A0cN)M)>5eE&n-^LgU-nP)nHjQquVCX)j{
z{&d`vl=5fukM5W3iyzzb?Yyeek?&HY{772xe#Z~DBj!r`E?)WQeChI6rsdb>*ED24
zmf!Lx|8(WkR=>4zOE#~3Jio5u;Po*5YbVz|&aZ6|`ncW7Y2}Y->4hnO*7gKg?W=Cd
zeC&OC{VZL6i>j8DkL^pWUaSyY*0=s#`+M6%Up_8<8n0X>x92*??hgI0@(Yf0?C#Xx
z`k(PmbxZ8g^W42Ve7Xh8#O+;=cW;qO-2O56PQinF%5UuIy7Kv+NiFpGBXa8;$J0m4
zC)BrW`WSq|-09~JO_Ms7NA*Hd)=VG6TaNWQ{Lydu^YGL$f5rL(ASLpfa{h>XV%L?n
zZ~c>a8YKJxEW8UWtPc`?2oipDyR{=<Txnly$B#1noNM=aT7EFgZrLMy?(|nnuU*A*
d9FQ2ycq{w-4nxDepeZ{+0-mmZF6*2UngEjs688WA

diff --git a/packages/backend/assets/notification-badges/clipboard-check-solid.png b/packages/backend/assets/notification-badges/clipboard-check-solid.png
deleted file mode 100644
index d8cdfa9da4562d7a247ab7c734933be48bc68fc6..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 1402
zcmV-=1%>*FP)<h;3K|Lk000e1NJLTq003YB003YJ1^@s6;+S_h000F+Nkl<ZcwX(D
zfpOD75JgD`(1FuIr~p#|p#qW)LIsct2o>N|03Bc|ARXWipaa;SIn5YH((PZJbbFfJ
znHvZ4Wu5-py*tU4a`vpPcrM+&l=e;<e%_?+ALJG7hqRrvKdl_rStA{V*&n1`CF675
zz7xUc1SO}f`ui3DY{j9kO5;z>d@KL)t*XPBoqGpxlQJI_V2$fEELpdobvw@hreuCI
zmEaWsGX9rw*y+nyOv*1k&Iw)tV8Z=axb}DQei)8ig+~C|?0-lZjqJWiEobhzw<_=m
z02A-05hr2ztu!n(kK;=I@g+mxeb06|XtVy_BLFNpak4cOV<uudY(ErOl45D<COiW8
zIWnAJ*ui8v7MQ#@0|Hdf3pegdM?3-;GjJz;&P}8kzA%jS#cBa;<!E0?!w`V%n{whb
zmY|v;WDLO6<Ojfmw7s-@X{g9aF|`7^YcxX{H>q<y8*4H+H;0t$6BYuPlgu#%0G56@
zSY-T?F?M|?Hwn+tPb3BUgsPy!3ILh^T^b-P9XMcQ-R@+^+msM<4S!?Dsj1A~?X3Xj
z9A~vaF&`X~80ObVhU30!=BFpeW&u#6%(YNz>*&kh765Zxr>SjRsr_SE%q^4u+m{8v
z*oji918Mbr=()fGVC+PxRpUzadl3MCOZ9f8??WXuB~FF~fbq<sJf*||KvlDpr<6DV
z1OSx-l|@uiQ{t!q6<W$uN*n+JfXac&A}XmVaa4c`E#)aC4gdi_<v?W-mDH3tDnNyn
z@{|$>fB>L!pt6WcYDydxph8P|N{ItN08lwlSwtl@C5{SEp`|>f!~q}xs2r#)qLP{t
zudo8}A$sX=(ovpL;uQga@wd`;((rT1Z~9T5QsR{WfZ^BD@KwU&C<TG?loGE1z>xVJ
zh!PMePbqQZ0CMJ+f<Spni5mu>HNOM|%2P_*C;-;{4gzj@y)rCmDNiYJg8;1NcfMP`
z2TOTMi5mkj)qEVT<$g2DQ%c+rfD-281kLim5Xw_Z+z5bD=3@@P-9T(mo>Jl&0F=HI
zP}_W1M0rYy3j^56`{B8657KU=jXj`4lZ774ZwA1f^z`%w=dJu7Zhf_c7xS9{&}V*U
zx&?wK^W6g&Gv5NioB6H*Xv}9Icr@QF0LFX`1h3}11OT(nrQwy0zB$3O`7QzAgO4eX
z2|I=W^UY5tN%V1sYdy)*BoMW<r^IRRmfKWO0S8U*2Q+|y>oTvU;k>~bbDQt70?;X#
zmV4Hl)l}_vOPKE#Ky3(1neQ3^I=4^5h&F3W=$16!J%FVlz<kUdm_wEdYF?$p=?E;7
z@ae%XQ3)`=#$(kePbqOh0CQJDE%Tcgf{v?Uj!LL$escgyS3+&`n*>k_f(P@P1uzwY
z7xSA2V1=+rs~x<02x?(Ni3_iShRk9nW^d*<UjZEy4Fr$ouLeMFIPz-#ssIc@@NE9-
z0CW&=N6SIlRvJD`@iO7|&12NON{JVmgoh?)e5P>WVVRTZ1LY|tt_+|7?;pEJc}j@`
zfGP(lPbqN#2mmSvDvPM3ro>SJDzucRlsEtc0F?ukMO0E#;-~->TFO&OoOb_PUUB_B
zqTd0^Q%aon7k|WOIQ0(lD|`)89Tos39%!NNU}Hl1i^vuLxE_Nlh<NJ2B7evYyS3aw
zzII?cIY3;srNUAH*a0SCRKbSY+j_<3n^3F(U?qm48h_5#8^%d@>`VdB8DV{_i#H=J
z{n^V#822r=Up?-x1OU9xrL;HFwksc&U^E&&9J-f=RnMgO2d_sLaN2p@od5s;07*qo
IM6N<$g3e8FJOBUy

diff --git a/packages/backend/assets/notification-badges/clock.png b/packages/backend/assets/notification-badges/clock.png
deleted file mode 100644
index 9323f8f3070dd4ef538f7df33d54975c449d7cf6..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 1131
zcmV-x1eE)UP)<h;3K|Lk000e1NJLTq003YB003YJ1^@s6;+S_h000CsNkl<Zc%1EB
z(UGhm5bO{Pfe;LV5H<uuxC{tkLokFFf+1`OhF}Q3546tRLq!&u2JNc;ySi$+br?`w
z;KmJ4C;&Bp$HWG}&%|b;o~TlbD+v{>i3Y$w051RofH8pm^gqn>KX0r+!CuK7!p-*O
z9EeIVE^1V;W#ff&4c}r18~mkE19;;6EBz>+DEzL_;56P<qSPr42%&;CUfS5BY>{~o
zbB$-e;`f;498p7da*fT`G^Q+|k^fb{#z#Dx&cP4LHGigw=2k>hu#=XwIjPviu`LcN
z*w#(jl+=8Ipi8P@_gkdRkm3Nr$+<@P_1>4sL#S}=EB})N5|*Q@*8DxbzA^O(q_!0X
z*fw|Tc30EiNQKb(nivYn!I?~Sp0;(-Kz7nq2eej#I!BzaVrJ-gjzUuFh=`q01zX#s
z2XqwDdiwy)PDs|%oC6pUVLnihFFiR2@b&tjB^X4`0gSL~rXbV=;!6l%MBs$n5(qCP
zfWvoz0#EHPA%GDF=Yp0%aA^UwzQQZ;UHv5lFv9aLVCnly48Ut$&=CkNHGqCkG+XOg
zS^y(<$`EV$DJcN6C7St3*HK98UE}7OfU)~2Er1cW)BFQXAnYmuXqRB-`d?ZA#wD1!
z{+AYja~|jkgk2?o!6o<(KyW;YK+x3!czZ^m2?Sj&0KF8e2?Sj&fMzMSArN%60N!>+
z8v;R>4uE|TR$>5VDYnD_*vIr1KtmuXb^rkPdF8hf^dJzlN9PWrCm27K&@2V!5Wu%@
z8*73wtOmd)&czr9A1d&aq_;B!(gzxR*dK9v2Zmrg>QfZ+)3BMrFS<G)ea89EoW^o%
z?y>Q<cpp+HK6{rK_6IUc#y$v_dhDG`1Y@hVH;8Xq(<M^Rflrr+99q#&4v6<X(3HBJ
zrzmsigw|;MJYGfWi%+cy=@JWx$rr*(QtDHno1Zi=35A@&kdo9U*66JNGjc9NOPZ}g
zE+DaEs`o_og2u(<-WRPNNWb{>`l3ohU|uW^aCaOEGAn<x>HLIJ%FvQe*sX{8)A+rR
z4&@U7z<Ee7cOKfuQUroaqdwq2fcq|p$)H_aMIgKs>O;;ikkbw+E5CY%QDR4wl2JbF
z%Km;0GfjZsPV@wW$&EeYJ+GiXz|F|D75G=s1Ov;3O_0ML$nCME1UI*vW8iL0*V!Us
z-QPkkudt+K=o7ghuzu7@iXuPcaCb((=8OHh62mv8FKhk`rZ<jLlHx~vmbg-P)FI)7
zdjtmZ=A6wZNX?(2A#c(d9B|?aH#Vf-y^qZo2qxPQ<i3?X|8Nj>$=O4VJfAOj$ml0*
zeV~y?5XBk)3C25Ozy*x|L<L)4B^PlWZ0md}<6Y3OMs!GXrQfFS;DO&jdW0L^Pygcy
x8*<j&j5HK%FwI1>;LpSc*68^~+#5Hx@gEPy`5mNRh?f8W002ovPDHLkV1iQt4c7nw

diff --git a/packages/backend/assets/notification-badges/comments.png b/packages/backend/assets/notification-badges/comments.png
deleted file mode 100644
index bc8a1c35b4423bc08af88c3c72d4d29c6e81c6bf..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 1134
zcmV-!1d;oRP)<h;3K|Lk000e1NJLTq003YB003YJ1^@s6;+S_h000CvNkl<Zc%1E>
z>yfK4428cED1j1E0wtsbN=OHkuq9B!mOu%VKnawv_lMzz&0?N@2%|G|e(X#(v5$o;
z%ZdUDg+ifFC=?2XLZMJ7!XX24-~l{=V)#3YHlhTPVT64F4bTD|&;vg!?1$HW17Da$
z(#2W9D0qbtdyCL}*#R|XQFTEEWEjyq+zY<OZ_J|K5(UOR_^|Z7Y=P3}WF?`%OgOga
zHdfwd9vK<N;$sWE^@(`+ks%HW;`sQp{PLhhP-K{mF1C<o*mKd1I>W^8u?2L+!l^o*
zk#kL#v&Pb$ME>v~P4P#*+)3pgIWpq<TRsV?`TRAC3sWoNixQHZ8`<2T;2y5$liv=s
zJB<w4sRiGBo|o_(D&mU}ll^%K&!Ho}2(cM$Lw;@?VCt;T3V9S)&-1useoo5$$hl|a
zV`K0Wa_L~uDKU6R`f@bzR2V!OsvQkH71|b{!d&b)kCb!+{>!;im#L>{aky7>0wyet
z#f(n2Q2q2mXaw|^b7_gCvr@;2)b^e8KQ<OKI+Yv#ujRlwq7g7*X`B;rPqbLBX#NYe
zfC)>YEehHju5T`dxk`~$D7F@zjzo*ilJFxm5%-(6lW=$N{WdLvMnZ4SML^lf_q07#
z|4|E=xCHjhAHrOCiNzdKAz<<nSXuOOl`UAj_SEyheE6B+)<s)$NreCbV$$X!O2?nG
zxF6g#G1p_aOdp=X>WXp!V#J^=q2?<@?`#X;^FyW5=6ZbBNiA;3wE$s4u*3c}SKk?e
z)vM1=fIvRkW4FQ(USRR+GbioS@sjwi!_FmI-B8L2x{l?XeaN}bW=wO^wf?mWlh73+
zv-?1*XMu+0z7jFYbN(F4Qmhf`ST8+~HSb>=aZXyUr^oo%wQed^<Z%LNd=Y!fhHV8@
zvBm%Y1RUA<r;~gU|J=|If^HFBiovy`czY(fWL;0fpQ0P~HEt@A<7yjNx7<<mz1^vv
zyt`((8gM_n-;wrZ4`f*Vlr_Bl;5fN<7-%&N+Eq)iC&v0J%z`$U#h)6$*duNn!`LgX
z!rWIJ8B2yNtDjQzxAo6rY@sIkrZ4w3`IvYhr-j-QS1Tp1gt1d&gM8sfRz8FXg=uTd
zD~UeRR{kOmpPh9p{PiNemGu|Aqn!p5?bPh=tb0X@v3_YWc_@krJu~8`FG90+qMSn%
z#1|zLTZFitPu!slW~uN?1X8>R`dk3<NVyQ;2SJ<f5=!C=b?c`9((YGUIn>E3GSV%N
z6ulXLq9nbip>ruvC{)B3&LwNN@`Qtg%m0zhFrK3!bo4fvXHXKS#X^s<-Z?+McOk?d
z&bFe&Z(^^*oKXt4r4mZE8^>ir)2$dU{=Yc(gmk<M0bZQ5&}<mb(pZwZmb77dsZ;P?
zq+;NMa6g|oj$`_>jS7WAp-?Ck3WY+UP}~Ur0DhV)Yg-p@t^fc407*qoM6N<$f)7F>
A<^TWy

diff --git a/packages/backend/assets/notification-badges/id-card-alt.png b/packages/backend/assets/notification-badges/id-card-alt.png
deleted file mode 100644
index 67e1410e34cb2a6ec1a17ebd89dc15fdfff87205..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 844
zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7Ro>U>5dtaSW-L^LFmT-qi*IEz4QF
z7PL%p;M8msy42_mV`?-CRoLg;bL9{)Y>V9d+f#Y&BIh&jCdjn$vP-lj0@0cPrV7Rk
zhCTHcv~#juf)1G1G8o8B*nR6qY{kQf$GR=dQ!MJ9oDYqAVP732_jhvK^sot*`Ahg0
z?A_9IW+TIi2!<3L1|u;B$*YVu5BzUNG00vrPg<wCj_1LfU%xiwx65WvlzJ)daEp0H
zcB$<pp)(mg2`nFG&bCN?n#q{+`oHzUBVXmM#Tvpn3;OHcGFmizznRa-;=h;G;qn2q
z1-1==EsWd_aSWvy5nh(u3H1!&PV49AHSp}M+r?<~<=0EL6Bk}DmsW6IKR?%D_Y1Zy
z-Ff&Z1vlnDSK`?1lP`0BSfbRx|L5#1rqecu-*2eBr`Nz(SCh8(WATl}X4|<IG|Nl;
zo65dUQg2@=1M@feuihdP{(o=GOYr`>UN(M#P=Nb1#wCB8SgU{OpJq7K;kG`IV}<%^
zhFvSn@;?g(gqty%6x!Z9!ns0wHp8nGV)?<`AlA8xb<VmA^sX{`#L8ag)p^<T{)-vY
zt5a$XitCQg_SmcAXue_DjMw(rAxy7M>NVIcx(Kv%H=BU$few?`jOq*ea+oCw-z<K?
z_^y0GzTkn59hWajeVRSrmqGbYq|eU9A6Zl4|DG{x@Z|lo_r7M2(w`W`v(0h345^Ex
zFX-)Jd<&;$WH;<Dxo}|q|LZ@hJ09rY-n~9|_J>(Vj8%3uzCZSMW?gmLGpir%Ra<^=
zzuuW#o%x;h1>3O;CzP2Z4EG3Ep0{gYyKY|Z<iwFR+x{S5<F?0(i|4OQINyJ%?2V9#
zzNqOGR)wc8<|pjq*08os{_;UXUV|&4WzGMrkcRCZi&-xmj6a+n#KM0@+G30BY#jzc
zkgGN-S4&-CENTxkUBUX~+}Z32&I`m7+-6tR#PY6Sk+`%>J?#pUPv(V^=lK(3=B=Nw
z+U|yBPRzmm%2G?}`}UjpeqSv=p_KK80ncHBlF$6IQhO)62ZpQ%<`M=^S3j3^P6<r_
DXhU;v

diff --git a/packages/backend/assets/notification-badges/plus.png b/packages/backend/assets/notification-badges/plus.png
deleted file mode 100644
index 05362c122bd2162d5c5ed95c7e8f2d40d9e7eff4..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 507
zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7Ro>U_9jM;uumf=k48txwjnz94=})
zs_syken99-qgNoOR-$(Rr;elQ4faWPGaJgLKZxi3$NX%X#N?k=5^iRl6I47e-Pou6
zNKo*6$B&ycEB~4bZ%*=?9?`d}{L9|Tb^F8TM)mFYW&Gxw+V$;mzVX~W#ag!W|K-Xp
z&!2lj<}YI$koI{0aeJUL^8p?PV}>&f2Bb1JR6Q?j*?dNZ-A*X8R_3A3<-Z<B9)I^=
zx$jq&RKiak<Ivc;r_2BE$gEN^;@?m)4M;ob&&^$Rkvn~M)|W#!mr3m{*tXJ5e-2dW
zGlO^f%w_q`@0oSxrFuPmtDN+cQ)6$%w4I^%{`pHL{PdqMBx^6Yw?(k3|9@!pm;W)>
z^}k6y;5YrX=UQ~E@<Drv(kp>iPLS>mnCG9pu;2H8Vsv};7j_L$APM%n<N;})&VBy>
zkN+1fjC!p+QHb5P@4}<G?n*1=+&w0#JpIaT{mt&>^TzTwz({5AboFyt=akR{03trk
AK>z>%

diff --git a/packages/backend/assets/notification-badges/poll-h.png b/packages/backend/assets/notification-badges/poll-h.png
deleted file mode 100644
index 3b7ded66597679a8d19ef9eccc09834fa7a4f222..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 689
zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7Ro>V5;zRaSW-L^LDOd;o|@S7wIMi
zjwVGN7Uu|#!~~8;Ale|nbWngr(bA)R&8e-?w)K<d*<W4%D%SY#a^d4$bA%Fl439}9
z|2wbGV9xNK!Hhv(O#ZmuJ;psuJ6xv!)}Qvcdo{mR_xg>d_RpCf1U?AZyfd->s6^7i
ze~XveomrFj=hXjW`qq#4U5vKhA;xk4sP@ikhTpqoOF!t{J9(G+Lw5bLh4)$enGWuj
z&~lL7HQVIXfxAlr+25)4S{0aQUTNIzxtQ;cuT<%Syh|&X%O|_Z?wD;7*=)|B|6`f8
ztNOL%X;n9ua_w+!;}6=%GHcDj?35o0-UivHnL<{Gzhmmsv~J+~dMMVxFqJ)EVcZUG
zjb(2bKuYSQe;u8E;)7$2V%ewVUJ(p`+|KgzU4~nL;fBekKh)oMq;6FDAJwFm{Xy4g
z564W^3n{#D%7?or{8Z<ay7?e!2ZN!WLl3vWW2PfO=4J5>Zf)WVkKPVAD!Wl9DyHLX
z#G&##k&9329AFE;>b(?Rxt+?}7Cl>jXPMRmG0D0`&YVD>UO(|cSkykp+LtkMh4>35
zweYeBI#;+$7$<967l^%L-PLe%s%%9skNv~#3z^gI^n7f+vEZVR*8lU@Ls)MxVE6+w
zEM?c(X?s3y|MBFfLOK5d{=(BHUktW;H$I5vu|2_iV|H-+{JYkBzv}CJ_<Y3ePM<8_
z!{axPO_#5GS$S;lfrV)w`Gxj<o?m!0?#SW;irPD6tt*{BmG1>57axB+#(+&H7xe92
SI188*89ZJ6T-G@yGywp^oij%O

diff --git a/packages/backend/assets/notification-badges/quote-right.png b/packages/backend/assets/notification-badges/quote-right.png
deleted file mode 100644
index 0fa483765481dd43d88a470c45614c6a782c748b..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 772
zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7Ro>U^?yT;uumf=k46Y!rKl4ZuT5a
z2^?(-9Bl#|i9iyF0yx?dIGQH>n~-+t<BqN0N~){%@0xG^f9Yj)+|fdj$Z3;QJV7Ym
z&2#dS-Ai0AP5-QO`+w=wl7I7ex4rG&d-UGf(*8A!F4rS3EoMGx-#_o;UFF@(F4q@)
zjaH3%H}#F6L9A^}^}pOaYxy_f3cK`VPwbi9{WNfSsjqCup4m6Q-Qd1@^>Oi&&r|;B
z94N8168IRwuuMBD>3u}!x4U;mbtiB)^i0jTAH$Gxh_#4u!bYLDv$-35b~xxU7=<mW
zEoPj+DSm?0;h5K!e4xlfrkxBYfEv%TI;h1ch%-oPU)%>2Y2kjtq>!w7%UqmcW<ev+
zs5LIX-@VKF=+@f7ny`yw`iH4H441EdR=wjc*6`A*T;btHhLG!PnJ3=jb|~e2^*`&8
zP(bY7y))MyW10|K%pPPa-tcBu&LLY_OVb}KWB;=#XR%yv6*O&nd8k9`U`e7&Z^ABv
zMQ#SM5`oGy^m<kZ`iQ$-;aJ9vU70}aa<57J`GLynj=PR|Eh-1H7AwRCt6sbr4b=2H
z&~wYT-kAQT7pp3-A37O)g=5j`Z@j_YdC@N!mu0Wh*qQUtw&BIEH5xycCYPUE>-jyl
zxkm4AN8K;SlHN}{r7AD}^7a>5Z1qNJTfNPz--iXVxZQ02mfZfjux!4?N1Mg2K=ZUe
zy-GU2SpH(@@8$bS_t<%}T^6mJQt_{=?`2oj<k{b~<$c1h1U~w^Oa8K`;>|~E)R(Q^
z`Xojvl405ErB7nt`t3;E73DS0OO^XT$<_&bR{mX?<}-c!lai1P0zT8HKUnnD#z$m^
z9{<6}jmxL2285rzym9}N-<Ai(3}P=k?mQa3K_F!M*Nn(5`<Q+N*w=g1v#xsm*|Xwe
uRpO=E$-DC6jonVjK3HC+3Qm9wKX|7khxaGNYvce^H-o3EpUXO@geCwhN@g?w

diff --git a/packages/backend/assets/notification-badges/reply.png b/packages/backend/assets/notification-badges/reply.png
deleted file mode 100644
index 77021f71a7af666a33db652aaaf997508200c769..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 930
zcmV;T16}-yP)<h;3K|Lk000e1NJLTq003YB003YJ1^@s6;+S_h000ANNkl<Zc%1E>
zO_i)L5QVP<Bp{(BL;@0!fCD771SGTsB(#J`K*G!-@HVd}ou5i7!2Qm-yIEA~hDs%Y
z9)J)+2qA>%(gBzNya8AMF94%vKzE{pOZd+Ry%giR1rN9x|0(&0X0_-pjsdX2pZuTw
zZ-_7x!jgZ82r?l&@iah?4#0x=<kJ8Fe4P#rC`?GFLjwvSk$f7E$bh&`hXx1|+h)=L
zAvyq0$R(ZzM3CF*(0~j&04vHQp9XwEs?(tX2^h#Owlp9D=}w0RL{OsBp#cHp9H`}J
zg1e#|;Wl_?*JD?|$JP}Ybt9B)Go@wsSQoe(wGqy+UN$B5sL|=v)(ftb4ru(zF{roL
zYHEWsW!}f!1T{OI7Nz=nOv=^aRkgvDt3D^8p=fnFXHn6o;!K^6wk%wg5aWuSP7S@`
zI;kv}{Cq9ZS>KVoI=s8yaFaQhp2;tw;F81zx17ch18f6<URhsFpaJFOEk{cO0bX4j
zJUi&VfdKEI^)CYeck-tl;2q@MH9r~%@apoGqQeB+K%iIF;xhnnhjH%)0==rfJ_QHd
zpq0}cVgOM8)swp@>Z?!Qij`3x;Q`y!0-9qC09=Q}Q&Tx5eSEIjAsuhM^}8N$Pv!u3
z;5EU`ss(P>X%&+jGo~a~g<x|S;0!v<(N6p!lQXEZznnYlW6&(C*%$hA#b8S^K<89=
zkqF*Y3bqEd--lYI0<X~_)#QOXYsFhK=pJP|Bn{@s7-MCn11|Zcq!z{#Q?82r5Y<v3
zSWO1VkEf^sfc)dDjr4e`j7xInw1CCQ3;^V=I$OX%Hu<(z;8*&=CdsEjv<>}6?m=EF
zjF2;6tsA-{^{TTKI>;IDtQ`A4QV#N3LTbaA{ZU5_u~(ffA$1bmSC0EX48j7nO7Z>0
zjQr$RU4VUr9OTVv(S1f@f1_6aKjG_;UTE)U1SAYFwSx{}f-5&0T#GiLz2pP=1~gNC
z&Wb^v0j-tP(=j4F`5W4Ns!v7E+k%|^0;KK(IQxZ2Jx<~57s4Vw`4*i0BBUP1sh;~>
ziIfW_{x38^Vmlx%yu!pRz#CY4#EDse+4Tq$Q~wvG7%xp|{a?YlUxbkQ&*T^6Tm5J9
z3*$532`0Zd2A^9^(ccawenz-bbKKgo&nJWsLI{cG7r>uR1s%&lUjP6A07*qoM6N<$
Ef-jw?4FCWD

diff --git a/packages/backend/assets/notification-badges/retweet.png b/packages/backend/assets/notification-badges/retweet.png
deleted file mode 100644
index dc6106048107a8459bd6efc921835d9bf2e25d9b..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 798
zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7Ro>V0!B5;uumf=k4s=jh_qzTKid4
z6c!3;Efkv2z^oF(Ikf@IpK#%~@^<6e%HJDjr)=h(lb-hfV9qz2DgBceS_GUp6p;x@
z-iB+{g7-a`AMhp2xq7wKHQt3e;lTcf!A^?X4lQR%R(TlQBslAXrlPUo!{r_sj9(by
zCfe#||A>w@3_8?5@xr4S2lYD*)ZKqXaqj#zRpavZ8*?6Ji@lLuwXBZivrD4jK9@r$
zH>`L4y2+w6qD_Bdf#JK_3`W&M(>*Fgq7E-)nZolwSS3aH0Z^UO`z^~BG1(lR?(u@*
z&c8O@NLLma#hOkQ{xhs|7SzwL)tskXD|&Xdx1@&gGbXFV$(28N5B2k(e9XAFrTUb^
zOhfSkQL~wc=Eu#IVr+P)B^|_e>P+IghRPoeaSRI6KZJ%eY>+=w{lj4n149K@)z+2F
zsY^N<gs(MMD&7&RP!DtP(f9A-V3^i?lS>7{;heEUsioTa$F|LGT@9V5*Ic(uJUdN9
zTtT(HL23a*y2b%Xu7WnEISbL44Z??Xr%QkPU+XQp;N{XqVT`)}?tl61!jaV}!Z>%+
z^e!pZEfMz%|Hf4Mt!daBcTy-zxZv2ovhS*#hD=h9>)l^DY~`%5KdH&<rtKi~FJ$_Z
z#w_6t-@{+bV(De@{h@q_z1Re(z(MXzOjF1K$zNx4!745|iJ#;6*;D@Fu>6D{jxn<t
z6k<L_af)r-a@wUg<beOfU5VAv-P>Dq9x@j(#$C-{bJyEx+lR)-+!7oA#_rzIvgRRk
zDThT@%bSPHUpatcrRSrXJbyUEGR0YMj_@?#mS5q0pyTh{@|~(P+W24amsMO-n4>wJ
z<?{!1lked+-okFHT9O_zKU`iie@DXVS?4w$mHE{@HNfPLzq@whLurMn2R`uLoP6K|
z_Xf4bhvHBETD%v&t=`cgDC_hga@#cY6#41qA12wRZPvS3xb1=Ip25@A&t;ucLK6TV
C7*n4B

diff --git a/packages/backend/assets/notification-badges/satellite.png b/packages/backend/assets/notification-badges/satellite.png
deleted file mode 100644
index 0e1831e8a04a1c92fbcba2d48ee62a94c4ad0a3b..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 1743
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

diff --git a/packages/backend/assets/notification-badges/user-plus.png b/packages/backend/assets/notification-badges/user-plus.png
deleted file mode 100644
index 9d376d04d666f856fc927a82cf78ddadc4629e8b..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 991
zcmV<510ei~P)<h;3K|Lk000e1NJLTq003kF003kN1^@s6aN?Cz000A~Nkl<Zc%1E>
zYq6_95QJL-5|Ds|NH`LZ5C=$z1SCWP5|DrdB;@{BwnF6sS=gQJ3Cvg3Rrzs}Gc(P;
zcr1Wn7=~dOhG7_nVHidM;11vcpaakY7$@}ro&ehEZ%R&x=cfSB;9dOJ4t3LajRbfj
z_L{J!N8a$y#SOrKG)Bxd0{F|9#yPknU8`7=bz=b(a09ZW5uf9Wu|ZBV%#<(Ok>3oj
zdxrdeia*GY8Q)|B{X_w`zF?a1f;%7#xY-Wd46jBwSM?O&cU7OQq?AlpJDOUDwGy}~
zC)<FfR&q&7@Qc5uQY>r5FxMU5bjfu|3x46(^c2inEl=<Zzot$wZ?$Mx46f;t1N#7e
z;mwd4nihmhlBC9Z4>5o=snFbI4T5=Bfic_!V_2jM42+>(Fz?Xmej^yeB9Fkp7-|Lc
zZUSS-Zb%&%7(<f9P-{pL7#Ks9VBTsC0d!7%CC3jyxgi?I5$QsxHbh&umE!`HYD4w}
zc!t&i{D2_^bc`kA6pJ+K4FTM$lx;{YoTKL_3<0<UeC%EEH@Fpe6QC|IrY`XJ8VF9)
z8P`Z!#C26UZVV4m9g)K(IZf#hKW+>e<K2?zC4(kI0uQ7a#Mws7CCUvlUBQU7&6{mP
zHbXqA?oJuuT3+g@E!%|bh5+wp4Tl3k_S1Juq1f0aPz;$<!L^zv+@S1D8mG_x(dY)U
zXKg)f6X=F$<4CKO&?RroSfU2sj29HF>@`s#d{x~ctiaul-^|f9pOyx8#H&KBA-<V^
zZXz=e>WOrVxB_mm*?#;VKRffA)<|~&stoZa9Jxu(o3m$Ow`M|!dboq7Jd)@JDhw&$
zl1q6i<nyS0OrO_UTZ~c~Lp4Og2&Pi(CPXu&fbM5Nj;cr8`7nwh74)oNB{bmpz@Oca
z3g_vfwlw0HDOnBCvQ0)#Q}%XW*$g@BnDI^B&N^j#J|xunGivYbYwN~{5ec54(za&_
zLuG6cQ=;*v>D+3gtC)xq);he(s7fZH0lyGh)JE1a2?O|r&?5R})X*v1l~l$QG#1S}
z`mjXZNvK^3>dK<-By`1<`8Rb@6^&$A3F^wC4(w)#GDFzOYZVQ4;#x(6ow!!fU?;9s
zG}wu26%BUcT1A7MxK`1)(hUuvlHT5F6T4FQzuKsZMsiyS=<1@sI>}BjzNKQ%crDv2
zv8C$mJh@*8sZAXenK7q^8|cqI`<lNhyiQOz!!QiPFbu;m48t%^$3I+7t|l<5&T9Yw
N002ovPDHLkV1n>o#FPL4

diff --git a/packages/backend/assets/tabler-badges/LICENSE b/packages/backend/assets/tabler-badges/LICENSE
new file mode 100644
index 0000000000..cab2551f67
--- /dev/null
+++ b/packages/backend/assets/tabler-badges/LICENSE
@@ -0,0 +1,24 @@
+Tabler Icons  
+https://github.com/tabler/tabler-icons/blob/master/LICENSE
+====
+MIT License
+
+Copyright (c) 2020-2022 Paweł Kuna
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
\ No newline at end of file
diff --git a/packages/backend/assets/tabler-badges/antenna.png b/packages/backend/assets/tabler-badges/antenna.png
new file mode 100644
index 0000000000000000000000000000000000000000..013c7f4e61cec955ccd453551b9c056ed9ed901b
GIT binary patch
literal 516
zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%xcgI14-?iy0WWg+Z8+Vb&Z81_s7$
zo-U3d6}R5bcI-Q3z~gFr*GOpZfjbF#eMh!BBr)FIDB0yaalwMX+ec3JAKkuGC@JIf
zoKv2U-CCR$255+Qnf(kZ)$V=udATvax%p`?(?6>|K6@DOSv&gX>b$Kg8Z8$5{AcYw
z^u@2nc?+~u@V9?*ldhX)e|7UTvz-h@6&8QqN0*sr*W16_;<iRPNRPigZQWA~CW|az
z5r#vD(-<`P`M+I0Q5VJ6@Oc9#!==M)3=#a&j1O$)@HBh_3T$ee$#APWMM^2?cUj||
zZ$}v{_@%!d_`x#w&x?)=S%0JYwf|ld-Kx&WqyBQsvxRpj>u$fx!m!efVY+zjejh#S
z>W1wC`3wE|7r$dSS{}^#eX`~A%bC~tP2S0C=E}WV$RKuFE1}^^>)MxWY4J0=<D=HF
zDU=nK^EpgBdiRFhtZxYipMISoH;X;Mu}Qg1FEq4EQ*6O2mHT%zf^uh-dP=M@x|l0^
zJaN5L1fR=<>9#)ZL1$jGvM4YxayT$B2{bUEF#{M{gIrjCurthh>?K~%cbaWMQ*X{o
z4$&M{AIBAS#?MWfn%{1@EO_!`TN+nuki)udZl8DiUdh}Aj8+CuS3j3^P6<r_*}l#y

literal 0
HcmV?d00001

diff --git a/packages/backend/assets/tabler-badges/arrow-back-up.png b/packages/backend/assets/tabler-badges/arrow-back-up.png
new file mode 100644
index 0000000000000000000000000000000000000000..a253384c72bce2068c2b4518b7403b7dcce4cdaf
GIT binary patch
literal 952
zcmV;p14sOcP)<h;3K|Lk000e1NJLTq003YB003YJ1ONa4NRhv@00009a7bBm000XU
z000XU0RWnu7ytkRYDq*vRCwC$TD@))K@k4V4g^FjK#G8xA{F!`5NL^j8jz9)2|Xmx
zp(H#2X^<lso&#PWHU(4!u=0xn0mo-%_RiMXJ{yahU2esEBkP=4zMJ`Gc6WXZC=?2X
zLZMJ7{(A%@-l9N~yn3fTbAN_}{otJmWAwU$3O<um=pFnrfz`i`9kdXVv{3iU1XlkB
zc1iJHXp$V!yRtt9MC`NrO?tJA7h2fDgjow7v40$^@09`l?6Wj{386IhBU840Sj6e<
z^ZPyA^h5o0_V+Oxf9@&!xBO6F*;n?huz$kT16kxBu+Qi24yJ`R&LTaf=#qU_|2yu`
zXOlRaNlG}Kpr9yyjucYakK~9lp92b_Y*NQZyrUH45oT~IQ6h<{f0Yu1ZuafTdo1Cy
zNd}8@29!;{pZ>s5M4IH?21ytP<_laj!Iaq#e1i!JlPKa{NR!N9Uw_2|2@~8K5P7v>
zp0{S!LXbU#HU@@JC@8(2;3vMJ{V8K{<Z2POLnlcNn^4K_g<;$150PzjA3x*!9rqZ)
zCajvZVCf(`#GJrEHi;?R#$#-nyLQvWe=W$Eke*6x<C<6vc9M3UT3lHtv#l*$%HAgU
zQyG~pnJbt~h-oNcy@w_?XC=E2hNaVc^nZzTI*5&U+SjW2MnNXv=fPgsHC`AKIr*9C
zmt7{X50)i-;KB<L&*xE1(8Sjo#%0=NF1*0^nC}at8L7>}#aaNR)td>DIEWVI%GO~!
z(yRzi9PP`A4VAC2WLANj)*>r<H$f0T<!#417QMhn|Av48{QW62Z3sKme8`^IxofWn
zCP6vq7!aBYp(Ne5w>RK$WI+0e$(s;LdO>=~$BX!$-9_AOg7lcx`Zn6@aM?k3bdXi;
z9b{=HNdDA-)m*TbN(|;L5F&$<xmr&$SCvZUY$Hmi*Zb1xC3`DC(uYw&^UPL3BOh|3
zmXy!)#((-^AIHPgqW+WVlm>ZHKiYQBCE6-;6y?<cZI$_a@n7g1e4{nCR`RHRy{}zt
zv9%~~K-tW8nj<uKU)xFGY`5#%?e*2Xu|J9d?NIuH`w*GYxCx=|5z`^oa3P=S4_?G=
zso{z2kk7?7TBmA@$E=H(mLIdaILIsyx?QCb7Jq7BKbiCThvN!`LZMJ76bgkxp-?FP
a62Ae_OM*7Fm(cV80000<MNUMnLSTY&8N&wv

literal 0
HcmV?d00001

diff --git a/packages/backend/assets/tabler-badges/at.png b/packages/backend/assets/tabler-badges/at.png
new file mode 100644
index 0000000000000000000000000000000000000000..cbf8df4925a5211183e17e6d4f64e46d75300509
GIT binary patch
literal 2909
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
Hu0mjfQIC$?

literal 0
HcmV?d00001

diff --git a/packages/backend/assets/tabler-badges/chart-arrows.png b/packages/backend/assets/tabler-badges/chart-arrows.png
new file mode 100644
index 0000000000000000000000000000000000000000..b2b8a2d99367e27fc9eeb5a7ccd964a3a150b32b
GIT binary patch
literal 829
zcmV-D1H$}?P)<h;3K|Lk000e1NJLTq003YB003YJ1ONa4NRhv@00009a7bBm000XU
z000XU0RWnu7ytkQ?@2^KRCwC$oXt+cKoG~rf+tPn;MF(a?g7xF@l_?^5qup?jDeCE
z5rH~wk<X@ScmFd}YIh?vP3Z2g`!(~QU5T31q$breU2s0zas$r$-&zS&Zjii#E6(xz
z2o$RiDL4LMmqzr)dBj(wgm7AJ%l$F$6w#V<#8)~9$K`%dkMFeI9yH_!%xT4W&R0V<
zfR}B#3%;7tLwSU_4_?zac@NR%bIf;0ct$!dH)4p0Ce$yB5I^XKf4C1Y;XC>O5iOHe
z7?eY>0@!t-#TFoKg^~y<!1MrMss_;Ip_^7aI_O9x5bOX_gnrWc%ac|pJe(~7(kk{6
zgw+YcV=;tmfX-%>l+Tx3(jXHdEY=FPE%4Wo?vt^wc4>tyfROIkI_dCzoqWr6c&5(`
zgaWN_43I*Ed`C0Sct!ZytNadOMo;;6K^8!Da*Xhutj<9>fVD}^Lipjo*Srx~?G`9t
z8PpV@=|Z-HxJuJ#Saupc`-Q9)rbH2AuC-AR;%_NqZXjpOp>!lMb2=XYr03*1Gy^$@
z2BqiZxs)Ze2{!<Mo@3?Iqx2lGtl|UURxnuDa_K^zO^=ioT?VnYo`aeUT&GcQJtuQI
zcF?BX7BavBgjx_?O$l#32Nhy+H|9KzFjSDw$DH;ugONJDyF=rl=STvmxPr0(3eI@7
zTR<_=Q&WI{--V0^F=84Gt)|hlU&tg(3DJE>62{!VS0KI*N!FZJ*oTC6Xb#^#Mn6(?
zDf=hY2kZy3a_Uj~k&&zdlTECIEtqg+CoQ7%BQtRqr5~xt>upi}$S}Zx(vJ*7Oep=x
zFvy3~kIcfXqGq>Fpj%KsvfXL`a6L6Qdx$vy)|)-N<(HZQG+oGc5Nl4OA$l4;`-Q9)
zrUY{HxA%S^+ZHf0r!fGK{XljAp#4C_03;3psXFy|099E<;vk%wuqAgCQAt{qI}EBM
z?q=&efzWfsf=*I_2*rceQlTK#qwZ3{IIDj)(4;0csjBH0Uo$Sgqc*{V00000NkvXX
Hu0mjfq%C$X

literal 0
HcmV?d00001

diff --git a/packages/backend/assets/tabler-badges/circle-check.png b/packages/backend/assets/tabler-badges/circle-check.png
new file mode 100644
index 0000000000000000000000000000000000000000..6464d5133cc838edc4f68f9a98e711b0fae44fe5
GIT binary patch
literal 2307
zcmV+e3H<hnP)<h;3K|Lk000e1NJLTq003YB003YJ1ONa4NRhv@00009a7bBm000XU
z000XU0RWnu7ytkWwMj%lRCwC$T<dQYMHK(-z2%Vr)fNzZd{LpOyo`w-c;hz##emc*
z1Tp*rJmgXM4-`L%LJ1EshVb}c6l1^-il`sK1hhawh=9_TrY+cr(B6CToSEJ2?(E!s
z?=2*qiI?rYJLfg$oH=vmjKE+UY=dpEX`7++hot3@M-_EL!w`R;r#FlTQ{GIR;bLVt
zMxvVfNP5b--py-S95B-JHhDQbjYZgky*Q53xQsS*QE$T)`s+CMppkx^fFX|AG6r7m
zl7!KC6T5H*|Kff?zl&xxVJXHa5C%l@jCwd0;vhPRw2~mOfIjL46h$vlmrvIBCG*Wr
z9Kiw_-4CgvO{z^T*5JHJ5EEE5negk(5Hli2UBG(OR9c;Kg7|7|zzv(AVj$sOMhYgw
zZDbqOHrxuxo+b4T8KxwnP?kV<*S}RTmFfzXo63~A(E`unTU&xU1u2*+qLSKY%iP_I
zCKJa9GO{#kImPdyn~-^xrX)yol6Mb1xJA8(o}BMlrmQk?pCGTwPL&wwFzmLGxzSss
z_mWv2BAs4_8JLK1WS}+lIg$QaM&BNyU;At}ik>;*Z+2m*i7jQ#2gc(UA^Vb60q)@h
z`J^cVefc&NQ?Zubyr)&dl3?#=Vj1aL0|T3cOCIt|Ch=|f0M9$#=BOtpq8{1#Sw=!u
zdx1u@IUu-MxQMzmSztg@@s}W9^&t1rPN74sZQ{8Qs%V*M4L0MZMkN``b(+hRSYVQ#
z!!-^0{NE>Hx}EV*_^6fH7)ua}CNwt-*J#dDumuM2I8CXWd^yqQFf-8_YK0K9C^}M=
zli|0J)g@%X5T55Jg?u^X`!GUy?OZ|u@yaq1pV?l>$-+s(jwI^>>tK_Id`7d@lzn<}
z)mj_2lZB=vbHL=S_K?pGvp(e&Q?BsKDAd01VPSP#W6lZJ*=>i(R(#Wo#ThL4l7-g^
zddY>4SLeETv%mx%b;)0XZlLdnWD=?{)CL<*{~R2R!vIg(O2xhL^Ex5@tQtdJufe(^
zsu~$0-oND1NCvbw?Im6-xV<hGiehE1j^>RR%qExmFqq$?f{E$4tMQvlbWFv@D57H^
zW5n89bQn_+^D>veCA|3pbIBpRs~8<#czKT|^3%teYQJg;+dcT#g%zL7G&C#n{~J$a
zu0Bo*o;3H{{d3S9c2&*`f2^ACrj+IHpDkrfjUH2eE!-Ti$35sUV3xuSk%mf3{??YU
z{MJ#%sjZGVU}_G8VL%#ihca|qismbmyCXy>Xq07vv29bt*dE4^dEin-|NAgKwOuMJ
z#}s@?yRkO<oKhApfBT{!-)RR+f(2>Cp9P2hf1jHAREg-n%MKShd{|(NKPZ&!2&Y8O
zWu#z+&iRzcpDn_}UJ*ER?0KXt1FAD;iq3>F;E4io$yT5e?T6D)ASwNBMLk<-Goznh
z#yHJR1<TL>Fu|V|=6aC>1^;_qpf*yIe!&$imuJ8b@#zJHng#yaz-YEQY@d-G4y8cB
ze%&p7nFRAwA&K>fgJq_w(SJRvGGoC$$B@l|OIVO!*GXTp(&=Nwa@-b$n%(}?uoS;j
zEWbX0fgr!Gmp(t=!dzdC=8<nH)Ex6uXG4Jd)kY<aXNIo|<ve~pA5!|=#FoEu@^3R8
zl<=rif=a_LjJx7u;4XQk@=}uIci4WtQxH`ZLi*jrR<LS<_QUsF3|ux3rOGle(#%*|
z4D^WPlHr5mh+vcbdatb(RRPk!g*hRDLqjB#dtCIimGxToZm82W;I=ryGJF_rRhD~8
zwtDFoLX$7y*AN9xdOH1fg~=Ik5(xU#>;7zn3(aFO5Ko1A6(aw^%Xmqk)_b0CJBwH<
zG@Q&Nsu6c;2yLgXGu>GcHb$|G*ud$?Zq#e2-jm6w(vRgv<qyXrE@XOR)sa63(YB?L
z7czY)(HMwU?=F)`KV~SQ4k?)7Rz3tO?=19_e><}Db-`RAI5OiQ3uA@T<z_*e?%WvU
z#|o#}Bqz|eA<l+IHw*ElFELJcm~f~KjcjL?EoIo8n7U<@p~bwI8KqcTN;zz2>`lG8
zY43_DeM^N-MBmiw(kg`rX%%nUBp&%Ptx|}dR*{LVn+XzI;SyJ2G~uH-i7h!)O)a)Y
zN-YMb4#TL=SVvSAA+=a8xw|={G9)W?)hBlsg=0V-Zma3_8x>BkKjqnHG>1R*)*o<e
zujm2Cryl$f@`KWluX>J{=2z^9=@rir)1Og}m?V&m19r&wh)aPb6&s=nPi`(RdQ5pA
zDsjxZ-($Wl7!_4?3}}Vs$ay`oKInd5gLQcv!LbkjT2pH9HY#0e@KwC*xZII;X^K7G
zKvu;bi$&R!yV#@4cUK;$M(=47BfVl4(l57Z@RZvy=2c1T&WcUF1yAddcEQs+ZNbxy
zWNt&Q$|toYXZuBlCasdQ&xO3zs<|IU6{?X80T=zXXp8>NiyWJgxae;V&S{JOE+jAd
zvsjoU)@5(mWs#;hS=r@d1VLYRc{OgOmfjL3<GO|gMtu`o#WEkg@U@m=+zz{dRxNy;
zjFfA4>r6$`l^(a_L$Uj^-tNC}WtAT1mD1x$G@{+7^mrmNsGc5YR*>q8zkA8p*J5g5
z@%J?HHz$3IzfY24We~FD1o~Zp<@YT(j4i0gOq#Rtq$aiWSx0}>ld2u2UxUi;o3bgt
z@82S>6sxoD0z_Y;3lM!`)m@29@6yOSF3;eb46mSMew#@mXuuz-UIMx->QRjfY?$m2
zViXx;l51E+``+0NY!dN)g|1;a<Chw&5vyp)R*^6l;R=q6aOVlaqbbH!F#>^MSb#4a
zsj#4{h*p@~fdhEe%rxW&?mlJzs=AaM;91-WoPj&^)=n&OTw59N8?AYJ`t#(W7NQY*
z$fNLe+*WZNx0Rkw(Kov(bXZ7l<dt~nfoKo$g=jY$Ir|cpE{J=8ucYg<qYvT#6kxCo
dw!v26_CFIKNCv3Y0l@$O002ovPDHLkV1k3lW77Zt

literal 0
HcmV?d00001

diff --git a/packages/backend/assets/tabler-badges/messages.png b/packages/backend/assets/tabler-badges/messages.png
new file mode 100644
index 0000000000000000000000000000000000000000..fa5072ebba50da94e0ca42d23f5756579ec610ab
GIT binary patch
literal 1056
zcmV+*1mF9KP)<h;3K|Lk000e1NJLTq003YB003YJ1ONa4NRhv@00009a7bBm000XU
z000XU0RWnu7ytkR(n&-?RCwC$o6SxeF%W<!8)y!R#3If-K{)`YRwZrKWBYDR;4zBS
zQ+bG1eT@QA1W7oN9|y+XpKNyRjd$0x@v649IV8$>KL3pEnOOi=Tye#fOlcXewH6QB
z#$~oqYE3=wg%DjhFjTv-%vL?>pTQOUg<F^ys<$ZZ63*!nxbS@#!UA4kg^#()r7f{r
zI$<kh>c4>-(bNJThRvF(Oi1M2a)T|9A@GGe#75?Zdw)F&bNn2JEfH8Az*#yMJ@!IW
zR|4WZY>lY@7pVqK)U`w|eLyh=E%E$8@r<nhUqb1>;}#7A^w8WTt<MUkMvUk*A(I3A
z!+n7S-~bzo0Qlz=j<G4f(iY~UgkzMHlsm!ii2%mN8<KU*m=0i)0`SAVvwY>&#?Q#D
z4S;3@9DoCG01m(bH~<IW035*o1EA-rv=l(?<MdK~0Sf`tK2ATplX^A-<oh`9;U}DI
zcxNmF2%LmhuvGXs`FEoY1k+c_N&ssg=R)D*Wbll?{>T8ByVOT(tQ3HbkCXp$2fbL3
zNw-RJjg<h<^Kr@m%sIBAHP$WzpyT6|Pa$)zMXu%}^hLCxAb_4C*+mSnhtI%}bC(H6
z`UE}-$%O&7Plc=*h0yHIjJemDQ1uf^Jt_*YZ86F=vS{)R3<fxuX!|{M;HfCUc5~=S
zLJWcV{Z%={w2ANl-_TrC8Ufjcl(&OiRtoZE5ym(%ldG=;fc}OQOsrFcC>Lf8lx0dF
za)4@q?n9jKpV5G(5o!g<KZfgs3j1OWE|mtn1_1R%XnW=R$p?YJb_-CxaD)rRC^P~9
zaz?=l0G;d~)hO&7z>95^S85ct2Ki_Kf@GPl;wYjs3Ofbh3ZLLH@LqZt)EkBLJ)}l}
zK>bm?w~n+Cf>D?wL~swe&ifxNz%@#H)>9-k3Xi(rl<tvU>QtFK6;cS=Q;*iaGcK7j
zP+;-f2~fcN+UzFRN`G~J{w4r4I&H=)nJ>8G#HFaA$ej7Mr;f=1)*DL0lJ^3D{lu1T
zdHSRrAbF@Vlfe2tJSNlta1^W0jHM9I;0y>-h-Cmz(1Ca2yQRS-ZjuCIIlvu!73X+X
z*>TQ7uPI>~eHwt*ndD}=st~$Zz>w}O02zSHKgprWzDjZmgs+mQ0MdbnFSsraZG4$W
z4j_x0l(iqkuT$j%1TmAou-}%x(3S_V?)^Qmwx|ID_4{_KuLfXo*wbqDvzPDe+=u1?
zESoU*RSu{qPI%D_^|LO(c~j=T+6nuB*O&&WpLK)3ZJPSU0}+-Y=!Sx<wn!~-#T8dv
ap)aqtud1s^8Kjp00000<MNUMnLSTaM_2>Hl

literal 0
HcmV?d00001

diff --git a/packages/backend/assets/notification-badges/null.png b/packages/backend/assets/tabler-badges/null.png
similarity index 100%
rename from packages/backend/assets/notification-badges/null.png
rename to packages/backend/assets/tabler-badges/null.png
diff --git a/packages/backend/assets/tabler-badges/plus.png b/packages/backend/assets/tabler-badges/plus.png
new file mode 100644
index 0000000000000000000000000000000000000000..f13a86f4cdfad66e7b19b22d411d86335cf21e3e
GIT binary patch
literal 414
zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%xcgI14-?iy0WWg+Z8+Vb&Z81_s6e
zPZ!6Kid%1IU*u{s5NY$jC-#K<3!81F<NDAGtyR~3Z$$>5QYqLmW%IuO?NW=M)SO%P
zH%Ey>u|>d%qxPa#ZrIFUfA7Zpe0OL4)jS!a>Zjdro~np{^^8v9J~+L8s#{j+V%=np
zbypUP)lJ_0YuVbxsto0C^Pl{&`S1I?>YCg}Bjue&+k?I^aJ^+rh-=uy9>4^2Co=f3
zo?$h^?jI`h3{M_i-u1C>#;26-9)~49TTfdQ>n6@A{`<vmm7K#;+l9wFCav6Obk68R
zXmri<sOm#~0;<8a=PhQ>lvNEbjknz*nJ)Vx(J}Gc%vXtA2hxx2JR;$9D&#EljQuS_
zKLlsbcN5+*QT2ny!~=(A{<9-H0K}dl+7Q}(nrTMdkF2_vg-qT1&+700&auqgp@QXd
nbigjjlONO+KmnroCE1oiRCd9k^K*jqK^i?>{an^LB{Ts5&U~dD

literal 0
HcmV?d00001

diff --git a/packages/backend/assets/tabler-badges/quote.png b/packages/backend/assets/tabler-badges/quote.png
new file mode 100644
index 0000000000000000000000000000000000000000..e0fc6f3fb42884f7a7a1f94b5706a4b2a2ea368b
GIT binary patch
literal 1011
zcmV<P0}T9$P)<h;3K|Lk000e1NJLTq003YB003YJ1ONa4NRhv@00009a7bBm000XU
z000XU0RWnu7ytkRrAb6VRCwC$n!RolK@i8su>(Q|5)^0<k*JUm2tf*J=;#nnK!rpD
z67U2_G*osdsHp>@B1qJP!iX23L@042knHdwh~u-F-8<Xo^VzpM{^yd2nbp}+y!V@(
z+1<I>xe%$SsHmu@sHmu@sHprGQpjj|bG&4RikW?i+$%4Rr{`xP<z<>1)5`rmg<a=}
zhJS^g(I?uV=9sp@zb@0#1l1^x_wb2ksY*N4r4C=?oB6fP!#f<^wL2Wa@DI_dMegtw
zFh0Enwuc$sD&LV*jpBHV*>F{cwKBXm@Lr2QtI?f!j(6lCUX3m5nXY5&SmjI6-B^y;
z|2qkAS{w8*y(>e$LvaM_zd#=u6FB>=8}+XB2{?|(fTJAN;!&eRTdBcsE7r#-Ugvlo
zHvqvA88FAto?`R4MT?A_AA0o6^IsPEmw3M>|2=L1f+IAbkqJy2U}MC9={b-YXtJNe
zXXK!nC^0|MZ}JRCI3NSuJeO^NKSvEv1JnRD;6R4a251Hv`(p+UTq+?+!qN;taB@qh
z(+ogx1XALw;(ZVqRgB`~a->XCjN%9nl@*V}_&CKVj<Brq#-SWCSuu(uHeuPJAT?Pr
ziX$#9US{RvOM))p55<8OcikUAC=R&1?w10fIKUSi{z({$12YTbxg2I-yyAt#+Wk()
zMFt=^A_Gp-ufAewMGeIfB^(!H@!cr?EU4|HIHGjpM(pv_=4(w*ltpnwIqUscu4%qk
z1Z8m)N3?`qMIP%Zei996FF2wYw4RvyTi%{SaYQ@dcFb=^{ZBgU{b~rs5#>p>m}2w&
zUa;{{98a0$>)z05*R0$J-YXZy5yXwF^gFh0!^gkx#G^Q#Q*OL=p1zLw?YY;+Lvg%Q
zYvY;gw%QX69YKE3IcLA2I9`4)u~M_Kj?q*5Ha$qZ*?sKfHx$Ry?=w+UI7JWXi;WaX
zwKY1D897iK@8KKVNxDfd=$oD9_R!%xu<sNvWllSaBg!~m+ZuCs#AjjD-sV&+YuZs9
z(eOim1w6&cvWS`VKkQSt;ZM&I8L-0ezbO@+z^bj&HHU`|iX$qs*Ha2oJukkaGY(!u
zaX<#Nqs_NTcWh0em>Wm9gkIXI52Akw3#*uQj@AqPz?dIrs!bgFZJR#OT{>a?T6B)r
zD9#jNbd$aHmLAcCNLf*IaG#zdevp-U6%Ueu!X9$Osf771O|@209C3AYKXFu4R8&+{
hR8&+{R8$VG`~!+s)<=HFD3t&J002ovPDHLkV1i|W+1>yE

literal 0
HcmV?d00001

diff --git a/packages/backend/assets/tabler-badges/repeat.png b/packages/backend/assets/tabler-badges/repeat.png
new file mode 100644
index 0000000000000000000000000000000000000000..ab548043f73bed11cce7cdad74a8f7bf0ab2fbf3
GIT binary patch
literal 1206
zcmV;n1WEgeP)<h;3K|Lk000e1NJLTq003YB003YJ1ONa4NRhv@00009a7bBm000XU
z000XU0RWnu7ytkSXh}ptRCwC$Tib3EK@4@aTd4)2zLd)k079Z%A1c%fyns}RcP<|Q
zv_De#11NuxO8p1Q3kAd<(8mS{ZIVj8sM&0=XE)K_Qgvs>vpXnbMU4XS`q&=ZV^07^
zYqUmdv^1;=lC{f0ivG1ueA4}SB(j5JpZEYbVFP|4ZQwhAG5f@CKo5MR9_+##e&X5%
zpoW4ULl@};-?d=T9th<77oaZm-xJiuR}<!Kfsh8!15a3C!4?R87djaL2&`ZYgkTfz
zhGO?(j${#pc@#Hrp12h(+0G>5oPf|xL3qdop`w+>k>f%pgtl%gR4`Av3}qKN(yZ7E
zKA#nwNRpp`DokmmY2>;D2N@9BI<_-up7eeRUcwrDhHXs$EzPu#8Q~}*d@{8;D!QjH
z-++%PBb%VEsF7ymA<);(!CS0XNO%uA`09${i-WQtbnvw<3xNFo8vGEN=or_2K=jl)
z79Eg3e31C}VMl187~z!wI70jN(1>xCCS8asrCNR$BD9n6H6^|mOZp|=#N<Bj65{(|
zuJkZEn+c<yY#62geoHX)6XI{dEBu#fV~MOOcg!UHuf_CFj1S>8OaCUjl<?%wXC&km
z_!D7Be-GfXVCITeh>2pZC40Oi{lAFS==5$GKONU@f$VZx@n}h3`Ves-V*Mp-L76(x
zG*w$Hn-Qjc{ksg}GaM-^qF~-0u=NFmJ8BhS&O^OYYlQ$*!+u#GKwAVly)MMoRw2ey
z$R-M)g`e;P(A3y61%R!!XchoNbJrYztoFLA$2Uy@U>R_j1#tR^Nosax8o)3?rDtbF
z0MP1MWdM-g+>T8ElHs+H4?+N-83WKX4q$zd;Ewb5g^C7{EHQGoRg@WM3FAr%R5W2Z
z04QvtG9*%^FjkIAptwsI+QPW@8l{Llq_svVM<V5SEmi=ofz8bbAn2m)F^5++0PdiR
zb}~qCHG2T0dzf4$1`7Z;qld`}U>f#g02)wLc}{i*xx?f_#P6<nl0Rs7{e%g509o4a
z&#pK&F+Fc3(OUkb)AyBTS7&k|2eu;5BTK&rg-OA%{yoR#ykK6lk{Yf&1F0LOr(H&(
z9_F~#QIjLaR{&4oFfrWv4VPthK(_XS9_B1lf$hL@Vm3++v?infZ0!fbz9kXpbPrl@
z!yEV}p64fKqY|Madv+Dj5k~)ghC#O%dc2<q)vNjSPvXy{W>@^wNvNwM2J4d`b<JZv
zF}pH<)q}*}fvX^O%_BLxGJnM;5dWUc_AMF!2~V#3U1)zywi>Aobs;V%>V_BEe9@6w
z6~3=<Pp*hf9N7FX#{KK6XYSL&=rKzeZD7T@i8ar2xF{qT({cXFPGpDhlp&E-MZpZA
z@LIC`@KRzA9x&CRuDa&WR?M~Gk@jMarByb(oNoUQ_xl%(Hhmeauxq$)t=S4(hk9dH
zFg<$GZUxH#m}@PvTVb!D*_k#7Ma|B%^$o240W*O0KOygzT^9d{f3!wxv`V!80VRT4
U7IWCp`v3p{07*qoM6N<$g1CPtg#Z8m

literal 0
HcmV?d00001

diff --git a/packages/backend/assets/tabler-badges/user-plus.png b/packages/backend/assets/tabler-badges/user-plus.png
new file mode 100644
index 0000000000000000000000000000000000000000..2ae96f0b7379d9896a6570c91b42c1c08f63b20b
GIT binary patch
literal 1431
zcmV;I1!($-P)<h;3K|Lk000e1NJLTq003YB003YJ1ONa4NRhv@00009a7bBm000XU
z000XU0RWnu7ytkTNl8RORCwC$TFXupK@`0`LlB}yh(y_t7>&5`2NXX*qI-p)-{B*1
z$3tBygL`3PAfn%)A0QwJxIu-5;4AW&8Sky?Vfr;)kM61(vHJ$->FFx&sk&8l>ec}$
zuHq`L|Nim<_K#g<=)j-!CvAF_0&bR|8&0Bi<6RR-shvZHJJ$_Y;2tc%dsv4pv~|3j
zhevP?x_n?>dxj0Ubsi>R73wYQBTT^s2DRL7ZBGuJg$n#cH*0A7Xa`Vb_f@>3&ou_(
zH@txUL%?n1{s??U_p0b<wPg=9zTJe;_JQEB*srjbPjkLuHNP)r*jVt~yHA7m4LY<J
zbG{a^Xix0n@3&YI+(O{3&)u&j220W>4vs@8yX^&v{Tb$d*!cthZWngYcKuHW5fD_W
z;@9HL{kzPupj!v5FP2~&uERw*jW&pP<M@QyJ^F4P?zn<L9p4Y1S&KJa+Q*+&xDB1L
z_dD_FivLEluj*JsznhUjtyYORL@hdt<bhV(4n`aJC?O_9tMo~ji}V<ko_5g(<jy63
z*$TS<l(l9@LW_Dnh{quaRQd-78QA7XW#5(kkozwMbd6KJ*$@P(Vaj$NQ2TZvvuVaZ
z{auD$VN24tz4&Ih<O&?KI}XT=q0sQ979GQ<9m2Bo##k(juG$SEa^hhifC$J3MwUIv
z-dhU5r&{hyfzNdWyb1uIzZ24NOm9s^O24u>3Ox4nJ`e`fmN%3D+zj*p)$;DJW-)om
ziNLKNx<E1f2UlI$D5kdtu|#7Erdrk{h+WCAzm#>fcx*}9I&^@3??9jQJVI~v!S_)4
zO}6}bsqWLa$I+Ub*IWUdHVS|oz6^`-1#J=UGR;}}gwU=+2yyqf{5?h;?=nG19>g~d
z4q|e3fi+VVjYiKxwmwLjMkkF;qXg>&<Gsz0`;1RAtlhNaJcul0OG*pbvcf_}B~HP&
z5P)y^Bm;n~RN{^jl`sH+x8jO6nTwZ9BXb<Ub_l?BHUO>Y^sE$}o{Q1x^zmZ^fc}Q&
ziSmZV2msz<(+OM3Ipr<ovxN2!o-I(xo)~~0e|6SAgW#L`jl@m;Xu_uc7>p!t>NntQ
zq%EUdN45u)IZ`>cH3_CwCSVvtgQr!7G0K-)q*cZu?cd-$J?J909{e<bWF<^p24e4M
zaHK|NLO-!J9dp0IdA38g6UqG5@JaX$Esn$^^tE<saWRtM4RW5kqG@s@PV5$QO77CX
zp7%LVEn@YQ+W9F2ain>5j>LQ6xpI2F5=*Z~Igd^sXmO<A0Y^>pfa6xe0f#dH4G3p&
zNQ#e`e!&czIFrt2oP<#LknbE$pC4Pc(F%ThKIM?l!5|LzL-3e&9=AMe?3i`UKV}`0
zIA*mxjkcm|jeDJgZmSDf>u|~<HLyk{WF}Z$v4;gRXEzsafKDGl0G@)i831)PkV**y
zoBFhA^OV5U^+U!0B<q8=1*WbaGLYD6QipVXkjP~9L%F9G)Ad0jQ`ZmWp4^qy2Z>Bx
zKa^v7T~!~HmwqUx0}fezkjQlOLpdBV$?Ah-Lq}ad<YmvsiS$9DY1E;9NJbyjCtS$-
zT<M2Y^+BS`x_(GrA0#p-`XTjny0}NJ>WB37L2^{kf__L}A0*SW7W6||4)tUX{fz5}
zvgm{4de^XiD4RY=rgo$Hp{)8K834NaAq4{WNn6|@Gs1NALrMU$=!0bHSJw}<QCh=z
z+N1zb04M+)2>^z2lNRbf8UPIBCau+PA~$Kd{=9RO*6W9yrN0j9hg`+iPU?po<?oK_
l&m%YKy#C@UuHyPvuD=)4f|M;X+^_%u002ovPDHLkV1lkst@r={

literal 0
HcmV?d00001

diff --git a/packages/backend/assets/tabler-badges/users.png b/packages/backend/assets/tabler-badges/users.png
new file mode 100644
index 0000000000000000000000000000000000000000..7862963327a034ab20da73c84ca18aea46080375
GIT binary patch
literal 1911
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

literal 0
HcmV?d00001

diff --git a/packages/sw/src/scripts/create-notification.ts b/packages/sw/src/scripts/create-notification.ts
index 4b006e0f5a..6744687fcc 100644
--- a/packages/sw/src/scripts/create-notification.ts
+++ b/packages/sw/src/scripts/create-notification.ts
@@ -3,14 +3,21 @@
  */
 import { swLang } from '@/scripts/lang';
 import { cli } from '@/scripts/operations';
-import { pushNotificationDataMap } from '@/types';
+import { badgeNames, pushNotificationDataMap } from '@/types';
 import getUserName from '@/scripts/get-user-name';
 import { I18n } from '@/scripts/i18n';
 import { getAccountFromId } from '@/scripts/get-account-from-id';
 import { char2fileName } from '@/scripts/twemoji-base';
 import * as url from '@/scripts/url';
 
-const iconUrl = (name: string) => `/static-assets/notification-badges/${name}.png`;
+const iconUrl = (name: badgeNames) => `/static-assets/tabler-badges/${name}.png`;
+/* How to add a new badge:
+ * 1. Find the icon and download png from https://tabler-icons.io/
+ * 2. vips resize ~/Downloads/icon-name.png vipswork.png 0.4; vips scRGB2BW vipswork.png ~/icon-name.png"[compression=9,strip]"; rm vipswork.png;
+ * 3. mv ~/icon-name.png ~/misskey/packages/backend/assets/tabler-badges/
+ * 4. Add 'icon-name' to badgeNames
+ * 5. Add `badge: iconUrl('icon-name'),`
+ */
 
 export async function createNotification<K extends keyof pushNotificationDataMap>(data: pushNotificationDataMap[K]) {
 	const n = await composeNotification(data);
@@ -75,7 +82,7 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
 					return [t('_notification.youGotReply', { name: getUserName(data.body.user) }), {
 						body: data.body.note.text || '',
 						icon: data.body.user.avatarUrl,
-						badge: iconUrl('reply'),
+						badge: iconUrl('arrow-back-up'),
 						data,
 						actions: [
 							{
@@ -89,7 +96,7 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
 					return [t('_notification.youRenoted', { name: getUserName(data.body.user) }), {
 						body: data.body.note.text || '',
 						icon: data.body.user.avatarUrl,
-						badge: iconUrl('retweet'),
+						badge: iconUrl('repeat'),
 						data,
 						actions: [
 							{
@@ -103,7 +110,7 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
 					return [t('_notification.youGotQuote', { name: getUserName(data.body.user) }), {
 						body: data.body.note.text || '',
 						icon: data.body.user.avatarUrl,
-						badge: iconUrl('quote-right'),
+						badge: iconUrl('quote'),
 						data,
 						actions: [
 							{
@@ -171,7 +178,8 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
 				case 'pollEnded':
 					return [t('_notification.pollEnded'), {
 						body: data.body.note.text || '',
-						badge: iconUrl('clipboard-check-solid'),
+						badge: iconUrl('chart-arrows'),
+						tag: `poll:${data.body.note.id}`,
 						data,
 					}];
 
@@ -179,7 +187,7 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
 					return [t('_notification.youReceivedFollowRequest'), {
 						body: getUserName(data.body.user),
 						icon: data.body.user.avatarUrl,
-						badge: iconUrl('clock'),
+						badge: iconUrl('user-plus'),
 						data,
 						actions: [
 							{
@@ -197,14 +205,14 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
 					return [t('_notification.yourFollowRequestAccepted'), {
 						body: getUserName(data.body.user),
 						icon: data.body.user.avatarUrl,
-						badge: iconUrl('check'),
+						badge: iconUrl('circle-check'),
 						data,
 					}];
 
 				case 'groupInvited':
 					return [t('_notification.youWereInvitedToGroup', { userName: getUserName(data.body.user) }), {
 						body: data.body.invitation.group.name,
-						badge: iconUrl('id-card-alt'),
+						badge: iconUrl('users'),
 						data,
 						actions: [
 							{
@@ -232,7 +240,7 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
 			if (data.body.groupId === null) {
 				return [t('_notification.youGotMessagingMessageFromUser', { name: getUserName(data.body.user) }), {
 					icon: data.body.user.avatarUrl,
-					badge: iconUrl('comments'),
+					badge: iconUrl('messages'),
 					tag: `messaging:user:${data.body.userId}`,
 					data,
 					renotify: true,
@@ -240,7 +248,7 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
 			}
 			return [t('_notification.youGotMessagingMessageFromGroup', { name: data.body.group.name }), {
 				icon: data.body.user.avatarUrl,
-				badge: iconUrl('comments'),
+				badge: iconUrl('messages'),
 				tag: `messaging:group:${data.body.groupId}`,
 				data,
 				renotify: true,
@@ -249,7 +257,7 @@ async function composeNotification<K extends keyof pushNotificationDataMap>(data
 			return [t('_notification.unreadAntennaNote', { name: data.body.antenna.name }), {
 				body: `${getUserName(data.body.note.user)}: ${data.body.note.text || ''}`,
 				icon: data.body.note.user.avatarUrl,
-				badge: iconUrl('satellite'),
+				badge: iconUrl('antenna'),
 				tag: `antenna:${data.body.antenna.id}`,
 				data,
 				renotify: true,
diff --git a/packages/sw/src/types.ts b/packages/sw/src/types.ts
index 2e23de8e1d..3b35de4079 100644
--- a/packages/sw/src/types.ts
+++ b/packages/sw/src/types.ts
@@ -36,3 +36,18 @@ export type pushNotificationData<K extends keyof pushNotificationDataSourceMap>
 export type pushNotificationDataMap = {
 	[K in keyof pushNotificationDataSourceMap]: pushNotificationData<K>;
 };
+
+export type badgeNames = 
+	'null'
+	| 'antenna'
+	| 'arrow-back-up'
+	| 'at'
+	| 'chart-arrows'
+	| 'circle-check'
+	| 'messages'
+	| 'plus'
+	| 'quote'
+	| 'repeat'
+	| 'user-plus'
+	| 'users'
+	;

From fceeb1b10882add478fee620111f7015bf5f4af7 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 17:38:33 +0900
Subject: [PATCH 27/68] :art:

---
 packages/frontend/src/components/MkReactionEffect.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/frontend/src/components/MkReactionEffect.vue b/packages/frontend/src/components/MkReactionEffect.vue
index af3a6c0e41..ff2d18ed80 100644
--- a/packages/frontend/src/components/MkReactionEffect.vue
+++ b/packages/frontend/src/components/MkReactionEffect.vue
@@ -23,7 +23,7 @@ const emit = defineEmits<{
 }>();
 
 let up = $ref(false);
-const zIndex = os.claimZIndex('veryLow');
+const zIndex = os.claimZIndex('middle');
 const angle = (90 - (Math.random() * 180)) + 'deg';
 
 onMounted(() => {

From 27c2ca50488680595c114dfae6f8de2ec3c48b32 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 17:41:09 +0900
Subject: [PATCH 28/68] =?UTF-8?q?feat(client):=20=F0=9F=8D=AA=F0=9F=91=88?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CHANGELOG.md                                  |   1 +
 locales/ja-JP.yml                             |   1 +
 packages/frontend/assets/cookie.png           | Bin 0 -> 38365 bytes
 .../frontend/src/components/MkClickerGame.vue |  70 ++++++++++++++++++
 .../src/components/MkPlusOneEffect.vue        |  69 +++++++++++++++++
 .../frontend/src/directives/click-anime.ts    |   3 +
 packages/frontend/src/pages/clicker.vue       |  24 ++++++
 packages/frontend/src/router.ts               |   4 +
 packages/frontend/src/scripts/clicker-game.ts |  46 ++++++++++++
 packages/frontend/src/ui/_common_/common.ts   |   5 ++
 packages/frontend/src/widgets/clicker.vue     |  44 +++++++++++
 packages/frontend/src/widgets/index.ts        |   2 +
 12 files changed, 269 insertions(+)
 create mode 100644 packages/frontend/assets/cookie.png
 create mode 100644 packages/frontend/src/components/MkClickerGame.vue
 create mode 100644 packages/frontend/src/components/MkPlusOneEffect.vue
 create mode 100644 packages/frontend/src/pages/clicker.vue
 create mode 100644 packages/frontend/src/scripts/clicker-game.ts
 create mode 100644 packages/frontend/src/widgets/clicker.vue

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 611d5d986d..1a911f63b7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -88,6 +88,7 @@ You should also include the user name that made the change.
 - Client: show bot warning on screen when logged in as bot account @syuilo
 - Client: improve overall performance of client @syuilo
 - Client: ui tweaks @syuilo
+- Client: clicker game @syuilo
 
 ### Bugfixes
 - Server: 引用内の文章がnyaizeされてしまう問題を修正 @kabo2468
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 3445e58356..e42f9babe1 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1361,6 +1361,7 @@ _widgets:
   userList: "ユーザーリスト"
   _userList:
     chooseList: "リストを選択"
+  clicker: "クリッカー"
 
 _cw:
   hide: "隠す"
diff --git a/packages/frontend/assets/cookie.png b/packages/frontend/assets/cookie.png
new file mode 100644
index 0000000000000000000000000000000000000000..4a7f04061333e8f22c5a732f1dea9d692c0f59f8
GIT binary patch
literal 38365
zcmb?hWmB9@)4g`F#XS(*-6c3I79ePFx0?Wg;BL#JL4yT%*93w)iv|r2!QDb|_czaf
zct1?_Ox09P_e@V$_vsU<p{9V1L52YU0Jf5%oF)K({!Kvu1ofXVb}h61C(xZ04cq_#
zlkk571Y~59{%?z$rh*Lc{TIc-zXh_5w5l`!{D{MRv_JuXWLG6QX>D)NQ3qN(rJjdS
z>vfiE=J#Qpx~LBZwcD$)2kW?>LV`kxPBFI`zi!i`BZHM_!iX4Y8K6|0ATVqEb}E$T
zE<)*+LWSR)2bG*;In&Ch|IgK*6t`bhLVlh`_D2$XrUrUrlFFi9)}Gw<PxFf(u7S%k
z1&KfJ<9ufdsjmJXr(3Z^m;2?=KNXeV?K=OaEAQrZSdS%I(~}>-$zD^~a+JxwA^)0@
z{GSbr77tJ{mpiL%hN>~H%{fgGPof?AQT9txL^f~eCyo)j!mza<HMARbwZ=wvl(qrp
z#WpJaE{hQoM{n;vk9iXzgYD>vLc$DRz&Uv0L#l~x;0impey8QoW&_gIVutTGm>uVc
z?gv5R5@`0+r#EjgKu%V}?jL>L-v@f@4g`6%p3_3h*LOPtkhe-0sqhVtJpF9G{Ne5M
z*ZOj+Sh->#wJ=QW@4xjOP4fIi=u`VfIQim4;a1MS<0z)k*3Oja_e|UW^mKRLqcmiQ
zR)4qK6Ub<2xH5dBY!JkWvN&9#DKpq1g>O|%2L-tA@g6@B1;lYtfRR93eL>xfhH#{M
zG2z8u7Y}0D*Cr^!%OajNZVz!({<n>0&ttep!BmubGf43NRGeCGHwTngFB9DqcCn;N
zt4;mXI!d?>oM!ZF(ucF~?B8PrkE1N3*Pfs6U-|qkDMPr?tk+hR;R6VG5DLSWD3Lr!
zJpDFd(vgBIg1Vk*N$6;y<;5@e8PWA}tJSo+`0pV8KOn^W5)WX(fcoL-;?*z8_V#Ln
zt}26Ssiw)nUCnPf_!gxye+B&UU|bVXwoX9B72zDb>xNa^5Hb~)P+>_MOsdF?xvU{0
zU72+9u|ae#+qTK+cZ1v7e5dbK+p)2piV`&L3)vO0&NA_Nt^;-~QmJPmGV!Us{r&iN
z+ZlW1TFr?TroA2qI5dgSlY4(hFcd9sW7G{Iy9wYwYd0g~6+J4dbW5|2AP7(u>Oazj
z=gV2pLMxM<KJFY<e~DA~zuRr2YJJ8&xjm)YpTsYw0w`$jw{yEb4wa9jK5awmp6i|h
zu1H=HdXA~k;yZHvra~^WE`=3`keBMgp$w;eZ01mlr}m_RIjM;j;Dr4%AaklCq*ie%
zM@}-S!l%KWpWJI@U*OT(J@o6vXZvhyzVi|LFb7UGCVT~$qaJK^J=kC9=DJLcUkce(
zzutEulEL-h{@dS0bGe%#LTCbMt^E%q>Be21aZ3U3P_~1?a6f8Ex$b>@fX<ll-YT}C
zpDKQ%q{E{Jt<<4Mi`4&QGF)SW<p%5rVZZDVJ3WuAOccJ(|7R6b$Ng;e_oqi<d#+qD
zfz$iWw@=Ca46E?z%XAX=f+O~Ub8Yj3x|#E3f-*2NY+S&*|2B6eNs5A6+iL{MOMN|h
z@wEtLg<Fu6W`ZUU4iBQ@W3k;6ej_>A>Uq+M^m3UqzI;1t|GlLINy2yPpZhT@y_|NM
znYH?F1PkHd2*Ao026g9%d^bJ>=Zb*zmPgSdMgzP)EM^*_?C{S{8GADdd(VIMKpZQ<
z=x|>N$whB5Pnn;EW|~=6G1#!?WwS#Z??t$enT?LH(S9<$SX>Moy_^P{dEdO+pQP2o
z1#l81A2!xty9EgbPUQRc?G-#{nFbpvAJ$cUrJ}1Xv8j<IZ$a9a0XOJUY+q$=`vU|N
zXih+EsxWKbQ>n>`JT@wJS$~U<C6FQFBP4>6JgrfZnAoPy8jqWxIPop!)oY~v$@+3M
zU^>quOvUW>Y|XF#w5rqV`bIFPw4o!j+p8x>DX8fcz#t&y9gVn?{e-STn+ykL=oFi!
zKT)a#oVbP6U*SC~b|;@NsooaeAt#&n@Zkkt=DVT!Zcc!L$*J~}L(hihV*Re;0@gE5
zI_|pWza4V2s>w?L`!c@%FPPk>0avFJh?5&3`#oEvuNa*q&sd@9>L{kX*uPK$Dk3Gn
zmurEm=xD1*A3iH;Ehk<6t=kmptEQvC%~8xv<wUMDSJ<FXeKl1{Nu^T1=ykLpcD*Wf
z-&P&iDWtBUDgh{J<cri|lDs_6_J)i}9iLQYCnSrVWlhLV+#2AmS15JbIj9d@W8}FS
zbAKNl7j==|J5T~0!*Hvxpwl<7Zdng}bH@053<1S&`m{tZT1wWWaLZaAp!?S9VKv}^
z`T4R=$np8H)#K3jRZt!k;H)|C|LA2Z$|rET_cZpmhvv3tX1r{H8#!GcY`&6ynh7<%
zxNnZfgAu&7`>W`kZmxrk>5oAS%g;KK(ALMSuZkb7BoH7%W6LQj=<bX4bDj<jf3&h-
zeH;u&`!DHe$@A!Sz|8`;&xOYXy}ATYbIBceH+Mn8neh<xFYXfW#K6J^#>-03%rmHg
zG!%d>e@BPow^~vA0T&3=JfJJxN?SacK2d=Y)O6O{F<!1&Qz%S))(V!FU$DLVkB7!b
zg8&D1hZm!>&H?Ij)p&f%Y&h?<+<sv){+Pbjisbw62i!cba{uZwpFNi%C$sG=FUd<u
zCiFK!rVf5h{cS^UMfTl@o^epHy!pd69?iz9;}*|vWCZd0Gw4O-zxqr==_QGM`j6{>
z>FnGz#2XJM1pNko;rYbT!()+V1ZC01UVKnsmh>2ZihJ5n4)j@>zQ^t_<p;`M9&g)B
zrTlD917gXz+(&#&Px<Npnx58$o{H^OwW8T!t6X64vhWB2WqdawIu;k#$HV|lZaE@&
zW{v>|L1Y~O$(*^h^Ud2*=0uU~Z<r#368^Fc6s3S7A?Gn;h%I@YNZg9qn?17U29+Lc
zFsUm8x1~3oAC8ow_W~^Q4Ez!@7leAM`)aA4JV+kBb#o1oW_wILJ~*mq;9OBPn=5rU
zJ~OcJ;Oi1^3{nSSb8x83;vY%Li<BZGC8&|e2<gFhB^A(+tq)ESx7$Kd7Twjvy}@Yf
zAK$9811#1-b<wX>+Z}9c&h}!lQ2E<(xW^tj7;h0Pma4gU-a5RiH>2O^+)U9eHo%lc
zKYVT1|KmT{lBsSsXGZ3|mDa{NP<S^dpH~T&F8+qD<Ip|m8eJeK$Bh2;O9mUj+eI~`
z6mRx=xovwn$cziTJg7H3tp~x?U;3ByxJ(2d)1SkAErK5YU8oLkcPh~%1}zCV*YHr?
z)WmQ%fyZY!79znQ!0L|AGY?U+z~;yct>@dtvnUSqmBCG;QRo?4>|QNJK#QvB2*c+)
zGUDMuKadfI$OCK|zKN$sht#W=_cmR-H*XNr@SInG^OyqcVQ+Vg>Q*lQYLD;RNkbr$
z>9~&4h1A@oafB9loHOY36LtQgXqmO|_io~%dQ1Nk!irbM@WG<*1TqIC$;F!9L8;2c
zL2(1of6CaPm;|Kp>&_w~w&5uK+(NJ<hvho^`Tgo_&$~=XG<6r>cSwkTdC8w3L3_PG
z_c`cRDu2b*!s^qEw-ltwphsD}*Bu!a#jaQhZBEB*+j|v+i$F<5?;wz&6|j#`$x&)$
zSUBF;x;0MIa{RG=abND9UHHfjNcUsq7;{3A0L5KY(f+4Zq^&@&=`pY8f!VIRd|vOU
zDXmw)^E3CV|6)|wh2&aUrK_yE{%RfnqfQU5+!k|g%X%qtPIzpXF|S4JW4j(nevNiR
z3{!4PDxOT@8{CrHx^XF3o<(rFI#*O`h!F;xPq9aA6)NlUyE(11+6Yk%3~rMACgkOI
z_;ajky_4Uki|a~m*p8DVvbwb9DN)gQz-hMLuiWXI1>bP`nQdIZ8x%m1_QtVi`KIUH
z$}|lRB@huNJ5aoFL^us&W3@duaQPXk!G1Q1q$nMgW59$MQJ;zqHR``A*+|SwBRa)^
z#uF3oOm5qt{N>9B0heybJIEH~UXBYt9vW;+?@|soSl_-D@86et-fddzbT(Z}Jv5Ni
z%y=?<2>Cs%{G4nx^9FD!9%YdGB0ki$!Abzzz=?Ekx8I4X5B?q+Y4i6SYuX#Nl9Bf;
zhQWYrz@Zxni|rMH>M88g2-QjlbIIZK5fq$QRG9Lfqx0N^;xoOunfF|+tB`&<!>9zb
zE`OKXNZ@?uYVE$QsaGq8NL*f#y85YBsPneB4vowSr|uD<@?77do*QKQV8=PG;!li=
zd7B*3G~=z}##%D@y94(ffO%V&m?tCf!TZZJ);OsVUU_>Oxpv#64e@Y137f}B=lkDX
zFEsT_4uf`rq-1|Tx43<_$m>F{KccZ<4HY1~DZl;?z>0s1A>&yM&m|u`;_BI4WZgye
zJRyJiE#-g2?_}0=Tz<BZREi8#8Qqk0E2oUU{Dsm#3}J~5F-kshODvRXt;<ly-Vn+h
zgJ@|_RR-ySf9qi65><>wyz_cECWy)LowwqKj(C0=uQo~@OPSgTQ>oz9y@F6LrI$EX
zT-6%5u#m7g(<rE%xa(~l4kpR3plLND44YgJHV#8!l1&RDfZ8--Bd81a=u>e2x;&T(
zf`oUQ4+kMhNH@QjA1X_=Z*9y!_ElGjL70WJPk}t#O=Fio?cCTi8$NGulUEk@>S3s7
zp}AjJIYG4R#5b(KdJkv%o6eNLHd}tSff%pi@Gk<5?@k*k@HAJireq>kDtbg^i~_AT
z8XeZK%{q^ne%HXrx3k-g?e$vKmpd^#knID>Ahj8&Ayvr1Bc|RU4r3m)W@k4*wJ~lJ
zKs?%tZYMG^EJaWP&8bm~Yb__wKh5qQb6+|L@8MH~WPs$opOb#`?o3kWR8@DFW4b#2
zSg!ouStK?5X0JzFf;R3mm{qlyMxtDY_HqTFAvo>h_d?xe_Z2gow-I97c#BRO^tumw
zWA%YLj2m{g6gH5{Ay|4n5`nPMT^|eh)-DPyn<c`+5?GHK;SQ}E7e}dRkZ_=PyjPe@
zvP_N}{zWdEpcHP7zHk2%{4W*QQ}B{_+*dYpzB+OGO!!spw-O@mo8N_!y_(5+taUhv
zb(&GocHPr6wci_}-$pvlK8fs>%p(i~XaT~75UsnR8Mz!SRlstV-)G&;DM83@AXEr-
zoJaY~CUb$2mr%&_1U>mihJEMzm@DSXCa^;gmN}V*V9)njUl2HUQ_uL&qii9UZJB8a
z<NM2fw(|=Txz7))Af;|zSLM)Z2I66TNj5N_^pEDD?q}<xf;zqI^D8IV^AmG7`a5O@
zz;>+5?jWoBP2D4f#760|rrG5CRvA;V5X+a~>-d6FHhC!zvwR_1bUd~z;cTK$={)IJ
za*5HA8aguY&+BhJM~kO1tZOmFd{52u@49qspLJOu_YpM|Uq0qn6N63Jt4$x~?dCAb
zlFc>P17qn7kvi|P{mf5tkQmIY5L;@<=V9k*@_K~(5=B7_N<{TUnMQ0eM5KQ!v;9{8
ze3%b>xHvI^Fw<l^19?9mOS5Zl*~50F6fAzF+*)2xrKzaMzA&w5VG#s@bnl8eB3Y+W
zD;!0^My<>gKNcwquR1VBg6?URlBmY)gP>+k?Poaus<3*2WMx<Ltk-yhEO=!ZH4wY&
z44?Rk6vT~VXIv`ohle6*L|88b8Ly-f!oq4}nIfxfgT8N4Oosf+&|_Gy^P)PC7gvo(
zG#`MDUsnr%IBxJAt(GDw)UKI|*{#0)IS_#X=F$!V7*KrKG&xLpCQZ25C<~43f=VWK
zmx<Irt8T*4AViLvdr2-YJv2cTvuBY?%~uMzq+_}4VuH3z4+xCTwv>Q`$;ibRxc30m
zqIav!eD8ZeCs#?9_R@%NxCO?N9-NrQIpS4NZnO5TL}GsDVwg=f?7ZRSYRDtR89a%O
z1RQd;Rl&{*62`CR&+ah9tV800_pv4M4A<iSIJ%dP+9*t0#~f((J0pepB_nzTR^GRY
zkxT5L8CUY-H{~P@puNOc{q2H1F$vcMwrBC;UeWjRF6pTqK8TRY|6F@L5vfbb^%Lv3
z<;LZ7H&A9t4;1WXPIK^7f&{Mrv_4`?hU_Z|{jigTFgM$}OU>!JuREMG(fv#qszwS3
zzm#&{DF!0JYKdSwolSf7f$t74l)B8R0T&T%nfx7G-fUSELKShI4f$gUY#M|dMO6f0
z71G8}1cPw{AbI^;ci(-`iXvV~s{iNoFFH)Hb#F+R5(Y-U)kg1O(<?;cQajDm^=_~n
zf-?;tFJkda5WsY=sJ40Pn(h;Hnz(SPalst8Gdv$is)i>@3#@10KJ6~q98ki-PTAQ*
zGo<;dRaKgc&QlkuwZgl2){b;M@b$lKT%U+RGMoOwvEwA)KQ<yt#+EF`SCRUE3+)~B
z%<R0wp{`iY@+(Hr$B~7_g^{sO5r#|4ukNcJQHovPDs_3E%7%W%O3=5K%!+;;j*RwG
ztj1%BO4HWQ<{MrLY8?@e=KU1HBB$IlSxy>(iw0Z<)w+eFt=2w=cLkzl0q)D0Y%lkE
zU1y0RApz7Hzpv|_qV)gM-U>;(LleEmZ6Xex<2N3=6%;>N3;^axdOBUUJbyO^;MOyV
zGH!)=!1$$SKFyeK^!U_R_FL+E{L$3s3B*AvPb;aR9j2qf+{=ADCv&+t?w9We^usUq
zOEy0x$}3kP)1;a{d7rL_CM!NTNELyr2l+#JkZBSz7Q<Mf=F_{`HCAU4v8MGBiLkdJ
z$JTWTK3s<<Y}Q?G$XZxQ^!gnfrenxS5huhNYP7^i)1wq@HMUl_Pq?tBB(G|pvucWY
zZam6)AkXZ8@-@|?5XzCSk0-VBn7Y@r$c1SFj^!vw&73I7jd(<&t<liIdp%#(`KBYT
zr_sT0?01SSuKXK}16s~k&s*7QAObN9x9%#b<~vjU8fUeZj;m3AwIJ&{{9#RLwyT&G
zyigw3ZDK&??U~S*ZuZ3QZv+Qtagkzxx6(zE`C)4#`ragF2K+g89`yg|4=fm71|{j(
zoQrWm*K(Pv&ePk(Fy}OXYtaBjD@E&Cnjlj5mw8u0=|D2r+2ca))7i_@e(_%CUkBV?
zmv@4~YD(0*KU^0QUp|)k>$EJFBDlLk9XejS2b7~N_tCVF%;Tk)ViI<d{#^CB@x9Hh
zUd6asZNk4kB4;0HG(=>JP}HZ?@>xxdwYU8@{39gk)Ry;6=UxtWzm=}3AzDKQcIhVi
zJH|9s#$x~)swEzJs*HTpGyUbTl0|cT9hLIt6|^Pbp|c=%^i!AFc^&_Ky6f?PyQglF
zreiVmPWY1XJD&$OQMQusNmg>cT2y1$rx3gWgHSeP9G86^rV6+<ARh8t9si`@5eexD
zX}s$;hWN#QOs_N0xKrGH*$}AI%|1_fn_@P|{IuOg<DGy9BAIYBlr)Y-{e9lYbvw{>
z%ra2-Fp^|(d5};NH(81^_lSi>+`5`N3XbCs!~N2*UUIUA=|uuP4c^N(!@;%uQ!rM{
zw)rOTFWN73gC(9j)z@f)*cZa@?_$=&k?1eN4ccpp%7#5TTtFs7h2^|n(i4WW7CO>K
z+AUP~$#XiUj_h-eADU~)RZy~$&5sZ85TP!;C>NjZ7mSVl=s_M+jx(bB*7!Vl6L~4A
zJCqzLXlAEbn39^pUFctAMY)(kFGo*Kfn)#hu&(5#P0V${<~vqecdot}BJ-d)vz47W
zF{)7R$33r&NQZ|@r5|Af=rMz;28{LK&y4>?eqH)HJjMk}Qg#Vk{}-EXlBp3wj<@*r
zMh@>KiK9YwikXYw-YdsWe=wx82#soI%Z}vMSl~WM<;*tA=-FL>tF}-h((im~3XC&{
z>02IyC_XHaOaCF-O?_@)TIv_X^=Sjstl+2pCivUiN@L+IP2p3>CzL^2L_WY@wv?EY
zOu^$<^AafK-@Dd<`p=qUV4d&~=CQN95J1h%{6upDJ{eem3QD<8KQ42F(SCLv8}e*<
zIksYU25hbHn5rBnkEYU_hvC&4f88u*!?d5TeL4;K3u8~I|60-eg!?Jn{^iimdXvNC
zT{S1te`?aY-kP)hESwk7HMBjD+Pk0jPV1F+DQYGyL}an8cLJhh4x`9YO@=Ft-`axP
zxI;UTSC%22un-<6uVJ%NKzUS5SELjKQTmYZS1TSIOBd@bZyJaW7B<1F5ktLdhn^V;
zd(HiHy5BUb3f+qN!VXO2ZkaYZU)%hNANN@DCkz+t%P{jG@qCx&&BE2R{7kd#y42fW
zWHxc_!i9jd*xnCPVFiXYY<e}#{@HQ*`ThOvRH{?(j~|f-#3UAm!tq~!4cFuG)J8;v
zM*DBo0FIu4b5+v%ev6uGUaOb$y6{)d5^<Xx?3lG=pWN6lcO`GS)M#(-*W<6Ztw2E1
z#C%n9@D7UbkGZ(=SV<}(Tk*r)DFT`V_X`W3D-na@yq+7z%&Skw89O6Vg<YzkpZJt|
zVLr2UdhOq;OH2nt&C;IS*`4eNV%Cz_b=->7uX1BEy%0B1KYZF^&wRbU=1kxAU;y%=
z+089}Vjs~xZOxflM#-HYqG*G1K7DH18vp&%{B0T^ZI*yGn{lF9M>{jYpK!XNc4G0H
zmOUa}xP||J-7M_4*6k-KorIxl5OKclc`~z?#UxH7Ac>GC&?-%zmPJ4kLM!jI$q$F)
zNqu3LhQH|*kcNhl3q_XSK(MZa%x=R+)M4Zl3hv7QqIC5krQu%*b@JYaeA$}X&akwh
zi^z`{-D$^~%C9=?_;9V2l8Nb}rZ1I&@z`6$`#zr{g71~d4<1?Y)AiCX3ABw8>SfGn
z=8^Rugzg?#@9K<dO<-w*^nJ4p;yw#>&FjHBySE-k-%|%JZJGsudQLndcs$9R?w!36
zo~?biP)}|X#+X^m%@Sc!zZ2MSYfC=uRhDm*&&8`^)SDY#gck~ksu_%Io6WoVLm(j^
z-Z7gQ`qmoz>CWNhDnZaWYvK$HEGYGc<PPTX?|a>se@z%qg*CfS8SCSzzE?oOnLB(C
z^Q1C|e=n=Sn3f_t99eE<K}}*`c-)+-O845N_TEPK{(E+$#iLun_0dKp7}S!~gFRa-
z5x|GIPGrvoDX0G88t7^%|B=%)-3uo`jxtk5l1WH?^{fl(L;?P}eEwauNZPerCDHSo
zHwf4wluuLIKdJ0Ju-5~~+x)QAKsdDJvqSAbrmS;b0+BoI5i30J1Ns>7RehPguODK4
zy4`;v_2XDLp<}B6FZW?x%yfQUgZtbl;M>sn_)$oxAf6)QaYg;t!uB+s&a3OswBtxp
z$O*bUjI^=R=c(fV+4PQ(z^u}ZDUZ5I*Sh4pl;(rV1$hA>g&(aE9RA)Bb6ASPIR&FU
z@6EOyk)EDTFQjhh^lROl0npRAE(m$Y$57te9`u!l$KcU0Y?>isP0kboD}D4}ilT*D
zJnwzeef@$mE>#T#m3#}QI3yQZy&eq5{FRL^?riZ^5f0DfN3h``nz5BG;DIBP`!V^?
z_t9ov|3Vec7bMYL;B>X=ar1GbbN4}8?fQ<sPv-kJX<$XU)LG2g)4f7dcTNY|P^LYd
znm~@aG;RbBomktGK(iJq{TnqAd*jcOKi(|6t!?o*$gs^rEGyPfEH=u8bZId^+(*$!
zs?VWrz<y;}c=sND553?vM7?_3R%TY|+|X8j>8>Ecofm|7#sCm04G_a@g1m<Gyl)Z8
zIWD5c&Z{3gRV<M4LP;Z`_isS2Um-&#cub&xT!0p=HrpgqpOBA&3Ct7tYr+h6azRZI
z^q<O43gDM<-M;!En;B$VpS$8r=aaWyR0qj?Xie$yiJx;J$#XoUiE-a3%tF5ano~-G
zuZf4}3YN1CXdE6#TMl@6D@7Z@Bk>N?$K0waN{;ux3ind^B9kalY%Cna`19q(#>-Pb
zNzd@CTi$9G3*5Zml#PKZ>W<3cDw#jT7B1~{&gzqo=RX@{(Cg9shj=`6#LQobC62^U
zOB4%28UuWN02}Ks(jcqEf5(GDdXXvH;DFkSJ~9xc6C_{CmHT4j9P^RP^Eja}@OHW>
zr|VPqI_K-gKle}1HJ=J-B>Sd2FDrvJ?#F~Y+Zojtui!MHhA)%g>+-;73<?T|k6SZU
zqGhhIRWG73c2Pxbl(8c{R)nL`><hokdf3|aJDk3%%)%PGd%fyZG%Gu<;iaV0s%YKr
z*Y+vuys6TPKa^9=*<LHE9%v-L@f?c&JlS*8M<_OOb1x)bOWf$Rq^EM*qPgHXUQ_dm
zh{NCw?k{nBl&rw+k37N?Ha(#@S5NzX#$la17P8p}`mfZAvYoeHqkcPnQd)J96>;VP
z^kDzxk$N?{&BC^S9HnMm5IS`reZi-k{)WKZ#q!UC-nWJra*tl8)&lTlADP3@zUUPr
zCJ#y-amSTLa;7NW5{Xf=GGz4T^&;)Xm0cUIsN|uK3?G_uHIHOfk#On4h))r+M(Z)t
zDRp)X{6%pyf|TVmQRP06y&^f^_g_}P6AXmvtuW;jE)FI%5^($PcH0K86BY__VN)vV
zlC?Neioc%|5*BupwQvSi2d+_aYgLC{t?@+&t|ZfOBL$rpbYZ83bNY;R2!zh58;k_q
zE<BQkv~=uzU^+ImM!t4n0zT6E2125^LhcLt-6S<`jnRKbd|AL-&s}?IOuZ6Z$B?+@
zZjgdn-FwZ;4F5$U`950|@uY8C00uPl9o{<YP{dUbl7Ny#`s8}W+=!;Ey@nKaj(?E-
zUF0`}Nu%XumeIO5tgyG!mpUr@MdmS8Ik5lDAz`%VzZ2u!zV9zfW=}=@(HU8v*=+Tm
zgb0v(SXy74i8}>~A_ErJNE1KA-;#$TGB0RFqpqR4IMn#Go~izmAB=Z6-j3bW-Dm2F
zUtX!3p!`!A{U0obwy+!&bn}%T+L*A<ySl^wFv-ki%jScfL1l%-zAol@96hi@yH)hO
z`H@!{VMj)W9u%t(OK3x72dSxDyg|K0WlYTKkd8>{)~B;r;))h8ju!<<M=%rqpv%WV
zt#(b4{lT`xlqk~TsE$m{2my?K6>dlXb(N~QAe4?Ma9A({6k3lyCEVEQnK)tqYUpvZ
zx<=$uS5gN5WSHg;Uv=@Q3nw{G4ZxawsGlg!EuL^w*5PqJ3q0uzfirMn#{E+DXyDYI
zB|NX>21@|+ME##P{IK}EEf1-f>{;OS>C_;UDgs^BwO~9s-{l6GzgjK?u;k<4#g>5o
z3RS!LogdpoJ&npBF9<B-O&A<tO9SmKsw{})!o(3cB<Vc%QLYaE52`>3T*6)~DKFzj
zC<7y8Zd+DUi9!WvQ;7WwjlH+cYDI+Kd`u+|lgKA%DG5g*zLQ7S=b<gYw7_I1xMNr(
z!3z1iSeMHX%u)GWMzO%0p>KmkBziyo%Nq$xi|MB!CHy;Fu$<W^8FVlln2Zr8Hrxuw
zQi#W7MbRar8mfS@V4Z@^1tAl)jFls)D0`!?Z|OP7oxdKGAtBVI-1=u@$+9e%`j>yN
z{i<M(pn)=e0(+IxHQh!UKsF#`wg(^H6f27UQr9W6T+srgPZBWgKIk*_#9Zp=pPD<b
zBfRf0IFfwNl?@Nkd*qDwLf!{=cO?dt4ie@^lj|b)Me)fBLW-7IYBZtUa^CmHNV)Xu
zdh<dQ!vF9FV6rBUq2Qcj{6^@|rt^hUkII96y$p<KrRy>XXz)7x=Jnj?O-DHV;vy8q
zvQsV)L5ijA!kvM~M&`{B%;*X&nT_!CD2li4!;E4jaXW@Onnm}Ycig0iaRXY6Md+fm
zYTl~&lv-3VL(`$jk<-u$th`o+;Ky+KSA-0{xde7t>sC&}kiG+#iyYsCF=;Lns&zal
zK!c9VrXq5H-n|g=)K$IrbDd@C{n)3g2iO+z7}ofk{{o4&o)6Mqv|?TuEq2_AE(H&v
z<HQ0RvZRc=L>hfJj-;h=P-U`E<2!G0>=K7zJ!`R@vw0jW0^;bLNUkj@OD64MR+%2d
z{&>ENzF~nW(|kdFSp2(aT;+m?B5Q?s*P-Kt;}@?h+5>4j`go#G0IU%xl7`!Guz`l&
zXj+l-{MHlOBguVf;{IHys6Y%@-`sQ%4nIA+o~uW6RRn_~jERZ4whdb{8F?!8oph%b
zX{^$MaOEl(6;m{fFwSVeM}k&hX<@h0rxjY#qjLi%0`Nc@>5w<Wi4IC{g#qj&+iHr&
z|LZ~<%J;$4MpWHC3r!Vq6f$RS3~;_NYn>G2O|1YpFaC6J`r1b?2<ndQVoA4L4IanE
z#cs#^LPseG-2Rjb7~y~Tb1J@r1HYJYD9Il0t+dKI<_8!p{z^I>b951D$zzSYPI5iW
zJCtyvKZ_p~(J$fq{NH|I^<sa63C!>N`)X;DmF(d@B<XjOAYrM`!v^{J;Lnl598^Vu
zcLGt-kLrvN8uSnqJS_yx$d@7abdUAwK}7fRd%X1%OgJ)#EVs2zxNptm==;Fv`Q<+(
z%g5_wUV%HTP)-3G_}9ep&-A$n={!OsPO_7ur*FWmK*?HThK6Ct(Yi;!8vU{4+1qI9
z^j&$NnuxCd{1O(m%lZwI7vHv_kns832Dc|oLVTSQm#8c6V;NGlEysTRSD{yL-$El6
z87R3EKukTl3_Vr~;j!XJoEQlHFBF9STR|k(Q-WX++bP~=gvDp1f5{mh0SJMC$MQtn
zr~74z1is_c{kX9P|EC}duzK@di0jC{OfN`coRa{3$GwBt<8NZxiEs<X#MqaJj|+)+
z*LkQdi`=U28>Z-}Tv)1cNTKhFOS9|W&kT+IHp2{T(^=&<<RO`AQT3|BO^nwro8<Sm
zGNFmM1HdfA*76KB<?e)$7yqg;+E4M+73B`iUx(s~sw9O;tX@-v4pAfMZk3hI?`h!4
zR~$+$(V3XG?SBTwJGniqE@P(OgW$8#d!4ksYSR&DYp_B(VP_ba0neQ6{mn7NMCo&;
zm^kEOZ|s}~fvkBhoJ@%q6U}Q*!&*6{@x!pi3uUy3x~7*~tpB%J2%HLBrQaE`9UD3X
zKYL%>3P~7M0JY~rRBEFw<*{kbs|G`SMo_OE*uzkSA-C(|CX1~8%j!|mp-CZtQtrn9
zQE|9_S;AZ#lKfw_9^H5&{AmhJtfvh2)bT(h3KY>zAWul>Qs_??ManZ)IqQF-78_j2
zy8<rCpCcMB4k}Yffx)i;=);3Lh+21wRz{RWW&8CC;WJO&v-NJ5I7X#IXjfM?XyhML
zK{2w~%746mBAY--9!AQT^=0cd1#O~h*4q+k4EB*;AOxLIi6krunUX*V4n)~$nnQKF
z05CEIO}*EjP+Hw42(qGy+|$ZZi7%;!KB&uI;0-FJ3vgQ=ooZ@2?p$0gj*PD}$^8M}
z{WC^x;x=Outu|i@Y^Cu%ef0$!`299b+~9d~Z4g&GOB`<X@0x*JJZ-ATZ#K_k={JVA
z4JdFFAj@NyHH>L6crz}P^Oa;h**80>E}=X6KvUzzl-1OXMSu^Qg%QQ2luop-ndHKg
z0HBW~MoB{r38ZVZdXW_Yr1^Wr>0}xxrs-8o<o#fyb1z_zaye_yzV=W;VuGWP_C*-L
zy1MD;q<Em`qolA`I+kHYK|A2FJXh!FA1ckaWIe_53T=20C!U_cNMw>kwqDPEm-_Na
z^fG<0@NO4A{oIoN)YP-~X>Q}uO&4iAZNz~9mxj%bPlW-&AibTuy`fY<T8QkT#M6G!
z?TCW%o}}{^cdsj689-pY=_Zr`K|%GBXBt%8jCQVijC8JFu7_2z4jK@ef0_^pd8+<Q
zucAN%Ja2lLiB%_bvxA!NoE$YXMB+1_nAE3)hRg+2gv5G+CK>l~QO8=(si}%-gFlr`
z#BKX`(QG4bB{Gnl5qT?a^vE0H{%#mte{!F0*BME2agd(IXwp<z6l;~re;ywZjCf6P
zi$j8FO%ZtC=%iH&psmI<VaX-*A^6>lwF;_^OfxKrQKPU#G}Yc#{tq})nrW6Y6HWhR
zkyL+00@0_9{o^;iJe{p&qbbCHVF9WPoTt?1wI%b6d&~4QN6ZA#RkKd>t*_mgk!?tl
zf=F`OYAL*0l0FA(5a#7$saf@2GTsrrc>)FCKsAGb$wx;ND0dQ<sd(wcb1AwHL60Jw
zHe<tU!+}(aA$}twkVuL)k2IZ<kJRBS3!fZi0+}X_myL}2)LZ;l^l-?BV%;9?rLcuz
zS((ETy-*X={b{r^R1ltn?0rJ>dl8oKQl!1?hCihnkSTl=xUWgaT=kQz#^W@xCtKh2
zYgBjjSV=b$2b9rVF>`4E3*t?)9<0I%Hs_O8^)Edl_VUX8`6una{BzCIi>bdq3$pL;
z`I<N7Q@@&ZZh*07Lu1w|LtGYAA6k&5G1^@e(s?0-5(zZwZx~NGzN(bjFTO|BPhgG=
zuBVXWxjU!Ejjpd_<zZNn74Z%xAeEC)uJw0nLOo21&y4-s;Ea3_0Ydy-y}M7kw`(8~
z-SE=xI8lcLTw;Bc?ACr)uDy?iT(kGK14qx)Q<|5^*^KrTVWKT6&wdyg!vFl85A9E5
z+d#YXX|ta&q;d2>Ua8;-*6zK2?pD&>G0O0I)MKu`@2t%-d-%5uG<~jLon_hZp-iM7
zeocMjlZKapr(%a714D(Nve3T|;g&F(Dr8L@rpjLx1iC4aAscqn$lzbgY>*vb{a}JG
z{92<5dzlA1tx<Q^;{@|vQF0F5|EK(;Jbkrx+I+GDGP8CNQQtrpJ_T#%M!Dgh7s{(k
zuc@C&tL_(FIse*^vaenkNgR6)&33DIqnobxpa?0kpe9&LR9B=;SJ>WatoO0oc<^i~
zSA&0$aYT9xhXD4|5D!3?y^hRBnf|63Ryg`DOnVn?U66Gg0>$vx6N+&Rki;J}r}%vP
z<LN~W!2<QhLkM%<YlFb+NphMLi&TJ%Onp^-hsa$EfO4f%%JHBiEaTn><W+x%%~9Sl
zs=`F)C9j<Gg6Z-b_ag;z50Y^F<`$DS7E-fxsT7XXSg%W8=qOR~c${^ipN$*La$a|j
zW77Z+qTdBkE`R(EyAB-SpX&+R`-XlMKWdpR(FIL?lkaNsI-CQY&pLL@%?ke`6U{v|
z91Go?j!LjU77t9KohS2dgc~`Wh?JHp@zm@2qq8a6n+1g>8GW$!4t>-Ioi#6U3bb6|
zlL;2+BH^PQG?4$n;GfcRZ^)|r9uD_%_yTX8h#Kv8&U3*pi~k9miglv>!MR@GiUt;H
z$sdDSlJyRcPdLxq&@3UY<0C0C(8Bj2v6-HL*^<{G^6O?jAtGyY9D*h(EIl+lMw*X6
zjY?jXPoBV_m5BI*h)qv%Y8L)5n})Ky5HIU)+yT$ZCbJ5CGFIW#^E;0~)MQ}>y*WL`
zKWx*<t5H&QJnivOj~4ATI7N_lrl|xNTT$VDH(wMkpG5TJd0F!@zQKshe__M5N$OJe
zJ8Q-d$tQ8WSkw4FfVmujlVMA6VfhKJ@`$|*EZb4^51v0movHt-op--7S8%>Gwqc7z
z3*^ZZ78V6*nZDk59jUW^qRb9_J8g<amqOtV80vQ(3bW_D)tXhE&39J6JsXpVcYIr7
z5%e?l;O$+6uqNh+b6N!RsIfKEh(H8mJlQWE&M%g>2!YnY`C_Hz1vbd!IV?tfO4{P}
zU{YB8fXDHhFDkhy$==A4%--*{D(-sR)Wql%K2zfZ|9Zk5?G#G|6c1h`W_JYf70q#*
zLp-13)v69uBK5wBqr;wOx#9ftT*0l+joEX*hmUM{uq>+JJOa+L;2kDlL{x+@PYa`S
z*wFbrs^!#8uM3!~Vmk3tTwoNZub|(N@^lU4LB+6U!dyVixG@N%aWVL$^wtvnus~{K
zK&Fz%#;3`_i3bLP=rSPn$IvCDKF$2NLwY&z*dB_d*J@<Z`rwG+takXCYe)WZ1O*Y_
zmMq)@rHrHPj|g?8w4^cBqs7?e>7Q?YXt~Wwb<{+s)hECiQp9H@s0#O<+VN5DZ=PZ=
zwzQDz0(ERrhgkys7FXJO!=xIgH4yu)<2d{w1OHT3<*V4qMA|)@y{xWNw-PgU_lMMM
z>~Yv``h?U3R?9<eLA6t&HU=c2@95Q+(7oAuM>X#Ri3ex&>BZ&3ac!k(6UooRQICYy
z&F*{eBaI5wlNC)Er{a6jh*kVwe>sc8F#hnH|GnNJqGt#!*9*w>X*yqE^#@NDbQ7E1
zPhbnVOV2V(V_2t2HXM(UWQp^+XZ}%1IZS&_GJ-wgsvz$4b1@{YyvAOK3NYTP>>Ds^
z1RDBu+p!((wZHwzw7#K`16QD4!ciChLBcd3_V%z%1T;>h0fy=#4hJ7;g+KIz_3Nxz
zIZTC9`E&lHNU2nMX}ik~Vbt(Ny)9N@U@Y+T^K>BNZR;qtFdkS=Evh7t|Lzz^<0)Pi
zMaH?pG?@j*u-NxUt_;DFh06TkA;4nG;j9ix46E_+<q!(4R{#8mNcQd1Y>`^G&5h%4
z_l;t`t_ROF>5Mb=5KhwLu4RX35Hr?v<UU)j>whqaT@;ZHokTK*)@!L(uoZ**m7t4p
z1A}MxtLwNg-JKn17MJVDDr$`*S5LkY>|Gy(?7e}vyVxYUav>nXBssZX--<?eIEQ8W
ztO{rbtMR6<Ep<yVt7X8pz3k^zWnBdhQE<m{-4=gh4P^wD(AU?`W$OcM-T09+sNVFq
z_cZ0)w=%R1n#K~uxJcjS>H|jpCV!(2Z)NY7;w~{3t+$wHYvEDM=-vC~rqf7ijP#(U
z(f*QP`c2Tfj?<_a(q{YY|KvJxh7AZNn^?~^nsn0!ZDY{YlhA5@byrAZ>KSyGdyaZ=
zl_KsR!I-xGb!vV_UePFCF<gwl`jOZOM$ZSeg8El;^F2<C5HA84iinES^)zCoO+0i-
z!(DfAG2VsXjtAVH?jDScG}~nJQZJ6{E5UaNaw@XW{QT#hSHr>aHTMSV=>*6s7IbN;
z>n!og`w$$%Uk2+$boY|jwXqD}P9>yovZ3{EQpbKF(q+jfxJjv3X!QX~Kt!|NnNvF2
zDdT0Qucv{_9s9p0dJi<;Zbp;$#}td);39_<N6@(IHCY*-Ivm%f%N*(v;KPVTDi7qc
z{=qb@KE&0QV>*(^zxpdgX}jn^D8o-tVYnGH9|bi7q&7VBY8<JS>M7qiE||g;Twb1e
z_ub>+iQuLWI$cWTQh&?M%%^&@J}AHHiQ2)mc#e?pjHSaoV&E1>tmy9`r@=q5$H5Dp
zIAT~<O^e?$1#EO{ycgHJrb6P8DGNuZLY^s3Z$#Aw3uwTB55`5r@CiZ4nD0@GlkaqY
zta`woxmTljszz^1WM7((IY~HQzi!rPFtUEER(bl(+{C$cIwZ;NTWrLXlT7R``OIyi
zjkmxPR0?5X>Cp@7E;DC8S9tT5;&XFFl(;sXN`JG|+5RChp$&l~C>0~K^)ug>cX!0c
zNnt60rwOXgC$e$9Da@YIZLvGU#T)$-yMWafCVr1GzAe4!CzHoXo7ela6Y2Kq;rw$|
zEQoyE+fb8PD3_;d13qRsL1&a-jRMMhe|f&G0Gp9_4~fmZIYs`JwuWa}AQt$aNa!?o
zOTdR^|98uyNaD3Fd-5dhf0~>^UKhM>0IabEbGOu<U9c*xYz#ItRHRXa7`9w3Z?V`c
zw93n;WXgWYsZM`03ZB8T-m+BG?}ki?+SqZ164b^P5qs;G>`qo|KKS2I3PYM!uJD%K
zUj7S<tRC~DQQnVI9VN4FwC+^69ko;VU>ZB&68$mnNKC<Ee(a|E+dn4UUQPo6m$;V)
zFY{E9pdtdg{MxmGflLEJ-$Sl9S@YS>%rb3aBaS?$MUq`L@X`%+;H%dGyjp1fJdvH#
z>LEY09-vVgZc6Nr-h>@z1?|0R`pFCk*f-d)&zRMATm9sTBBg%Z9uZ)`qD#uS62L{(
z`iKXwzm5Z1*<%=^_sI)}AHBvSx7+!dYWsipqQ7~|3MIWDB-E<5dpx~<*!blqgz)}R
zK-&;dQ^H68n2ztS1M6reJ+c{CG3g!W<@gHbm5fAzh->PTWNw6wi;(DY7$Iv38JBS&
zy&-BS4v?eLTwnjWK|~6n`m7{no)m;bxtt}9z_+{CVn{%j;kkJF4Tl5J!QXng>8EY=
z<PG<A(Vx45{=Eqb9Q?w~IBW*_@(%OAZC!k|zV}LXN>O&hoSwO_R-5y*!aigvC|ihT
zsjKBzJEk|zHrJ|{{)c2yyPn`3fiy91blLSl<&V`FaA8vOf(SMJk4rG=KOxaa*Tq%L
z03S1crmim0o6(b_!S>f!>${=XaXZc{LQWyd<5JjMGt5+xo)vZz8s~jA-M+Pv8Vsfw
ziH=Z|HD8v>GiiOT1eGL#U=1<OlbVI^z`l9JsPDQPXq^xB^ngy|ugkA{J(UJ=B<pzV
zPt5CLu!C9pSPml;Do}|6(F$UvZiy|N#8JyS@^4SQ)HyQI=^mS=j)koW9c{l|3!_Cl
z_Hkf%!akQ~SLOIlQpA7X9W`|W?Q2k8F2<;PUY_nbYiv%0>!Vy?Z8b|`&%*p;X$Gvf
z?`DS|s_@(c%vui~VvI3wM#DXx4ZbzccljdOT`^h3!pa1r&0NB&aADp*eVcc_palpX
zgqGUmZpS%oeY1OWKqa4Vp(Hk08yXjOpJEMjtQE+jiFr)_y70DkrLTGwS5a7jsO(es
zLuRZ_;7hkx3<7I1#sDCxHgwIOBHw-wI~SA2->>9?i5FDxUHQ~}rOOVUximKDwDVP(
z6i#tTW0el|eTRv}W9b^xEat-56BDbB2!!qU)wo=!*?KL%MziRtn0fY$ae@u29f#SV
z;kaKA;ziS{Y%hAJ^8pDhIqV1)r*4NENu1tZB<K5zolgIQ0p$iD{(}k%g}lyHR*-{V
zb$37`DL04!bG3MH{!+;}nUCI|mGfu8GHza&)F?@)55SOu(zFmPZgI3=pJJxJ45PG$
zTVnyJ_hckr>l8vW4;V^o$E*BG>HBo%waq0Xwq#1JPThB>(XcYO@!ldu%oP%_iq*vz
zBO#c9WJPeOXC8ig;Jr_+Ai7!maa)bcG?(p;E>=~IXx$p+=iShi%$My2^!7v^_16m|
zrZ0a(^Ic^jCfkOSa)P(daa7@qm<q%HEf6vMA=P<Rputvr4(gd=9e71)Xi-WdeHLo4
z)$inQS!;~3^23e$?}Ct_<D*^Eh3kQva}D(_NhRe-3Kxsi8?EZuPg)ZqNH}0enF(BU
zl(m8y)a8ls>`IYkA#V_c6QH5JDPHn~E1$?F&{%<Cbt3WLfkePmWCEs>KaopWSAoqk
zKK*GnURTP2veOonTG0{<<sXeDp};rqD%U!(sn+TV4SkbJh~Jg|khTp#*-p3h=ANNi
z|2pf8$Y`#r<PRLFwF=^XhsSg0We5>^+6Q?LVRGYnM6<EyB%dhPKc&o|n_@1p^x%Hf
zyk2GX=DfTSKP6o@dVJ%wV1kwG3Q^8433z-|IIEnu=drm4#J38j*9+Wqlh+Y~VtN|x
zf^g73WI@b)OLE2UbrvSS96u@`A25E_R;Nv3pgfqKDAOmvHp#_+T@$OzSr0KuCE;wU
z;8N6KJrz!7-C}Z)6Si)Q(jVmelC_)!rhU-7Ehjernc^i?13KnPxxMu6@)wh}b#<yG
zJfeQg+9%+FD5W}trc6F%rJw{khtu6F4UA%F@X`VX8=W5yP5(7Vxb$ni8HmV%)<aw?
z@Ue>5q(43ly@P`M%H+kTw&`%{$9>rC^S7Jc{r#e+FAbE}`WD=Ka+lTC$90=gVY_Qm
zIH%t-se=@Rj)L@zVVzXfM%Yfwz>q7g88#0}flgw_iA#Qj*~m-`2oa+KHuYTiaAla1
z&1E-{&9LT)xkXi@%#)>Nt)(%$J>_1mtj~BMUG*jS{4?}x3&gfsd#lxNV(Z!EcJAf`
zqla7NIqwPy(rf{rempjV)7YSscE8!9fZomp5&FK2u8e43E<-y^A@2!~n@@PnKBdYs
zsuoI#BiCNLVcUw!r1zA3Uh%u>wvoKiX@1W1A|fmz3~SJ?vR~ME$D3&Y@#QUF*bnjL
zO*i0X5<Cn*(m3IU1%ynnz^*!J%ZTSy#RfJig;von1^?6HS=w`y!5i|&8CFA!*Uix)
zziKtC{DAZC_aOtOu%RL5c_OS9nbw^u#Wq!=-=)$J;s{naUF*+5qAb3`;Zed3x(S)G
zJ}%J^3SPK5>gzakiO`zh?m+LKNHx_Os+3;gw3B~Sem^1S3c7&W8S!R{F`Urp=R3Ki
zoP%>^n@{qjFvdW#>5k>{<TWid;A?79!JX?DC>#x(D9*|<U>@mW_@;wM{|}zu_3`Jx
z=jHj0$)BFdy+y|3Hul}zg6#%l6Fp}3`}T$ZXag<W%U1y=ZGzc;)Z&dEL{zX(ZJY|W
zrTRlOJcMh+35`zB$Xc^M25%1IUUElLda)*A07T-Q+dN!WQ=K&kV!GOs3L>RT<e8EP
zW7Cm&om{F+QAN-!w>b1z(PK%5tf={$7!8kxm+CU+SbzgrdS>zEx=n~^#Gfjo+x}`a
z_KCP`a2dA*HmLl=<D-;-o8EYHiVrVturg3Cj=m5Ad>sihoS=fav!7`8MU~KjIdckN
z$wzO8KjW~_a(4}0_Hzf~HlB!^FTF!5Slu$Ohv{<IScjo#OzT1qbh$@NGG06b<!VYs
zYx4Aw!o*J2z^pY`GO<M`N=6I5xA^o<@Tvd0&ll`1P;K7=dHHtsj|}0=J1-UG1d4;T
z3?7PSETvI?ip$E@VjOrt*#L-bW0pw9T$HzTgMwZmWAb+83BIxzjqw{fg?id1VWKk1
z-ea0JfuH^NchVM4WvVLh-f?C=h0**!_V7|+eIaW3E$=5Q)$oG5vQiM8g#X={L>%HC
zdnqbCK*#R6*5R+P)I~5AW?LpXdeJ8zm8UmOVgTAt=8jC{H!8{RQ*zNzk#-=m1`gPx
zz!(G&s9P-8<X6bF(lFloydF;Ml6D<(?9vw2`%#!l8fXp1j)Lnb^<|)oB>B?rN=vwb
z`UYMhq5S8#=J8SpOQoh9|11LV4S0#&{Ox#*XlpCw|4M3w9?$E^V{|7D<;roW2;(_o
z8`mL?Bd@Hj)(U;hP5m}8;9mGe#Jvz{?O`LJf17#{T}sC;@U+Ji=_Q|(PkQ+L5^e)!
zY+Cc1P!3!2I!0PE)vil&Wgs1t2U^dRP~4=ErptMd1n2<`4x3v_ctVS1bpk3$&$q;!
z4XR2yEuv&cnw1E@&>9!6AIN$xMMRs&#~*XPD~5a}H|R*94WXcST52dY?|iZj?3y{B
z`m1MYLLDTfM{IiP5c*MVS}ea*kDcR#Rk0b<@Nk1?xGzt?dU$t551vMWuGahTZ%bai
zjSdfw6iWKf^<uQrKXeEe-*!U2OSat2WZq3wwyf28X~PSTlr`0guH@GdaA`1}Y}^d_
zpIZ>TKzoGfD4M}y-o_f|y^|Hl@*h`Aj*4{czo_WSoFqGn>0=GXzrPuOY0Ht#@?xCV
znqS784i>5t8Dh6z-_WVezqH`iVIb}Y`k|@VUZqbqs`fUK8XMyYhOFQJRv5oqZgk^T
z5=iKLl-@Lu+r!yk8f=&j|Jj&QE#b!=N%?ZDE30UP*FEm=<Z486^Ft2_d~}|4;wu$2
zMBqaXLi^<OgN7g{{~wfch`~dj1n2+pH4l40%eIpJJQZI?f3T!&01v8zbFQO9hY*#|
zpxPc}kzYf&7?~-Nrb2(?(GgruRBjeP*_;l`*ea|p*k9Q2u$HhH;*(ixAvCrv3PKJ|
z`WwiiJYM>XZd>{^#P8cBhDxZLF84z2nd+~kC({*MjWF9_l)5{`HEs42zc4(HB{e{K
z@8g&=P<{R@Pzj<Bw=Z>Ay7{f6f)V=q{{X~5JHKxWl)`TSuFOpb)ga(DBUygZLx8+M
z;M~rGge`{hG$C3!UUnp{zzsgsr}?gc8KA?q0Is^~v}U7L8==|(#BK69^2T&uiK_t7
z3J;MUyN?@|#DG+&L=rqkvTo=ZCUW<2!_03*U=od_hor2;B5+nzt6;pX+xM1}e_r?%
zaMNE2!6u>=1R+&+A`G;u=nLS>=$jyS8qQh)xtR;dOYLcg0!6xe_l-ibHISO`6+du5
z;<84PR+u-TLZrj*-8V*tN%q?gUiiaj-NRR%h&5B1{JFh$%K3!yTC{(sRagM+7BuYE
zVBcrHUBVSvyNO0c!N->B_1gTauDYrZ4wDWW0M1=<*8HN08U{lt8^yw*=(D^slEN?*
z5~Tc~It`TEEAsUh2?DZQG!F8lh#%5UOx72uY=>v0AOVb-C1W6AmR)=H!DK7<O=O6g
z^;WY19oZ@oj#8!GCH3OM@0s2Su9h+<M05y{JE6^sWy=<{^dh{kpX{q?5RK!d9!TGz
zFP|<$yKPT)cAt7H<5}U)U2p+De#~$0I~EA^uWbZhU-c*7hll!YT&M&AfY8_A+bvW9
z7|)t}EpxAZ-pPmkR^VX|0nS>vY{}G}WDq{SC;my32!NSSVn4O8L&+_5Lt>Iv+|%%c
z<N+lCz%V@|@ExiWGDAV2yFrUYa(UBIp%0!&{%A(pwda60YlC<b)7D!9K!yjl+^kqc
z+bi&G<hufB6D!}0WlYzkw#eZf0cEtmJ~mw=LYY8b3R|ZTpowD4+^AQ4u3}0oz^HAQ
zyL58p_GCWm=D+8dfzkY`Tr?~fP0K6gE5)11)Wui>8FXOY+#%8cCZK--2*?|Opa5AR
zk(b^Ah<kNiU(Z@|s{XRf7B~z#YyfE1nj;YaP}#E2Y2gOKDKc<EL=y3xz}o|vK#uE8
zJR$5!cj`sM03=^*sB(umoA^;ygt}KG76E}{7k3}R?A<?RorG|M<JkZVrU(w;6bH5n
ztfpgp+Hd~2a8^qs3fNi#I$Nzl^#mJ&ok+_|q0q;x>pi7$Q%ouS>982Pqc0G^tib^T
zj$Z34bJUpy0W)u5z_b*8)L(%kABF+SS~-}dPW@}c(5yPln?2;Srh@O06DYq{)P-0F
z!D>JlX!gy6!=%FofT9gXAebcpDZ!sEMFNgO>yv==SS1?PH>8UPK2amHN(e5^6o3MV
z@loADAVNFfX#pt?ND!dciuQ>7ktj4|$(Y~><Kq*eDXE560T@&a5TQL)m%dDOY-rE$
ziWRwc08mb#naA9IIjR&Ivtgk;05ep1s<t^K4`Nw`F!Sfl0>3fJ4F$CDYi#OR004Rd
zu#KKsf%NQR(#0Z@wg7bGQ<g6-Pe$@@f+7lbwS0*VfN3p|U45}>+jQFV;V|j20iaP=
z3&87NIS)W65^m^j4G=ps$Sgvv;W20>W}5~kO|1k(@32OIAPSB)oe1Rsf(4|6pc7`q
zWUKgSX}-mZd?+?h3&6C6o&@v)PL=^(`Fz=gkf+2Nl5bS1xM;LnAl-__gvC(g+z3Q5
zcs8F**WVgYiUV6EAt?=chaPBiVj4^^gw$Vy*01$;IyR=A_`{Ap+O}i*J-6{nt)Cf1
zVpzezgArt%pP3Y_*AUJ)ZK;RA00N<qUOxfaXUCVas<wX_+AC-@8w=nt>F_N8mCbgu
zB_)eRJ&#ba#s?KFR}sdVnCv4BG-)TO=wHF4uUc}34{0VrfSG2ZoL=jfwTDQn+x+5c
zBl%0?C@Rg>O+ftV#u6`ZUhs$nvbMDVz?Sv+Rf%R{d5C3O;}oCFTQQmg*Z$4|Fj-o`
zv72aW1s|2;e~c<$`N-<bf?zrl^6QGF3t+In0sBWy;Y%*gO<UOo01sYz^pX+2^QT{%
z2@Y^W097n;&Kd59)u)_9_$7cqP)Nw;PsGa>p(f1P0%*~Q^$cdiVbWo*0u+coi#}7p
zjkO77P86L%pD_`hhM`v^PCR{-=_G|nlnG>B5XJS631(zLtNf>%P~blEh@#wyXj<Fo
zr6<KU^BBPo$|V;57%+f04WzP5^_lz8r8%-ZM>h!B8FXzj>lt&c{rmfxKECIC`kNME
z`cXLR+ScoJIQ#Ua<@XFqH#!$!Kku$B`qHXzo6SDuwM}RKbF=2(F_w(p*LdlL_D~Q&
zHs=te8NC~OOOaI80gsC(`@T@VLvWaM*Z@#;-w-uTlY|L}3gJRbaUF6H@M(}n_0N)9
zQel9&K?EF8IitLRmjM&*U>EFUkuZ@gq{GlWQI-v*pDPYRU!OaBUoq|M+)VlkswJBM
zLKKFM88~1dB#MV|0JoG@uAkerbS*gjef9E4twe#QphUwZ*#Jo$?9iwHoo8RN2AWM(
z3T|1WT-ixLI}|WO`trm(v)d1e;0GYkuRZIj!o>?`!3F1>%30L@aY7yXEc7Ae7e8q#
z6Cu3T?sR6sVbbAm1uiBd$vq$Yvd&Mqb#bS&o%{+!&;jULdHhVSE_{MBLYX6OkLrT4
z==x4gctOmG+!>Q`fW^V8HUkihZD`J{0ng%KC%Bx!YMN0D{9OxFmW2#*Fm4;?q%bPF
ze|FZ3*$+UifV|ZH+I$|FJIkksq;2RrJS2Y*|3n#~!bwBqyg9@0yl1Y4TGlZ~n7cGf
zAb`>mfYJi6K%lJv4+O>{a359Z@5|t-7he#7%n7X*aKed5cn2^97ZpLq$Dutk#C|wT
zI{YC3SOAFm0xGyEk~4{hAo&~6Yd~(*i}0i&d3qIBARQy3#1%rpT$rem2!|kqS8}ME
zUq;{QNLe72pA@XpL9wLu(o?2VLofhl0nm93+U8yQMUlK{7nt!gu;%9=7lhagzuh-q
zP5>5yw`l$x03pdpS{5K~!8D<`N-yVbCrXRobDyym&OCj7VYW|}>-WmdRS-*{91^sP
zs|5lL0${iL>e_3=>#7%@4@*xPA$`v`|L`_O(T)fd&@f^bH{s|yBO8Fjq{9Y)EQ7x8
zhgVkIJBK8LBgEVUyph~9F_XGpS*rp_5a7K7-3de9>9D~uHo<}AkJ5>DJP=?(sBTM!
zG73iK%z|d4=8Rlh1~(Vr?!n)iq@^F7^bA9Rkd+c)MA>L~4BC$Fq|2QJpuZ+vB(-Gm
zd;l*0U4K&f^ZYR#&y}6Em@41<|Ea4kh6~SI47K`1QJ%@d)!F^%cQJxH?MR#Zc{vAG
z%pc@1JS&5rdf7#A&gmz|9Y}bMgCY2eu;C)mXCz?&=o^hiEe?At@Zqlll#3#H?12#4
zxh$>X_tZZM4`enP!7bZ~NAs*?_@Zx$8Jk_Z_rvaeqcAoxSvFcNtCe4`9UdAeOukt#
zG|+E@D?W>FYvnVmEF^KWLgLFK)1r?e>ePfWcY7uQpEvCTJ(Ip<!5nyG(_Xt;!YBP0
zPs)W6dhgmAM02=|ReI9UEpe5w;#Ne!&3Mz{SvjaK$IboybvS9^T&PSiYAmuGA&Pxy
zWGblZ)~Gk&<yTw)YgTW8Z-4J0*s)E&C!j^7RyIUvMKjZ%)pF3EIy~#lli&r<Jrm{@
zmX~iLS7*E9vRnah8PSGjyI2Wm1nB2=#8BI!Eo*R?Gy`t~Dw_r=Jn4nxk`R+*@Sxxs
z1j4QYWgABMJ*Gey5CWbQ*NN67+<D)l@W6&GFgD%>H_2mOesSNj)L*Vu<sNOl2E20l
zVpy_hr0C*WT-YR|qpTTsr{x4MrX(~)tQ2ALw!$H}GcQ%tYXNN7w5Nmx#e#{Kd@uoj
zq+MBY6o{=Dp&p*`l>`cxHhMuY8mzRhOAV_sgi}{6F25-3<`|T$AFm!%wFSyxP!(1b
z&{kHz{(JiBWw3VD5_oLWPI%~%ZLoREzVf9ZeKfc}N^NMc0gD#ShErB7fYaA3Ed`%9
zGHC?YMpWhj$<}H4aS#B$9B2oKw%x$rmkAyR0HCT2D~S1=SOO92MC#pk5Zl-j2Y3Cr
zefM6t{>D3D-{>TO|G<U$W%krtouuB+tu^AF(H87`=n1(0k*zR$RzIA*=43d1)v{s?
zSc6DN38Qn60H_9ngkMk%kYvh);4n|50ikNyxJ+dA$&2B}TOKwORjH&bFcJeMu}lcu
zT4UNu5$sm1?7p=W0DtG*WuDug(f)e1?5s5>vnH()SzgaX0NQQ#Zxz&XjRXN2OGQzG
zl`9q%z_iGR&{fgAq>lmjp93e#!FMff2j#%e?E~-)1AL<!@;ZQtNBbS=AB5_5I0Tx3
zRRA=vZ^iuS{2}G+A8;x+(g-dOr$xYA_2;d>e8cUqd;cV~^Q=7GC7gq2&v+|a-W=Ab
z&!E$%(L${Wm^EPE!4BMf=Ogf?uipaOckP8#F=;Qb$r+E#anYmLw0@~yoIr5IE=~k&
z96QdLH3%m!nOows*W~WiRwwwrE<xgGb&$2W^Nh<Z!~61CId@}dps&;BcGFDZv25`?
zJ^|<o!P=KkmXeB-HW8JuX`n1yL@NF%a&U?=-viLfAuB)lA+zQEgS=znwnDnbkN(-Y
zxrtIgPk^kme23)a?u5gm88{IL-6l#%OE?e?lK03_jYdNr9~pCS_Zxq7e<8&ByY@;j
zwd*MZ1pc@u_p!_J#Mfr|;mWKr2nQwrzWUv};qfgyeAZn}QRzjB7<+V$4P6t)E`AYN
zASf93f-_c>8(a(skhYM5@A)gJPPCwcD4szwF~oIGFfkK+C;a8y48SukFy~vwLMnKO
zl@((T87GlU6r8nMcnH>p>Yg5pjldd(loN%b;JgnIKu%<WZU_*9p$7(a>;a&Y=jEA4
zQJV;PW;dz8DSt#B->2z04R-C>59>GXWGCN-4sjqrt{&mtH}35RADVqo?;ix!Hw53k
zV?FHNdw}OJDwcLPKPAd5{bSDx4n<7zb7fRtYoobVE?WqT7YvoJ-6}eSa=YEE&~q>#
z%XtV4EXr$ca`4s!^04K-)BOHEEd7D|6&_hJk#<@!)?T`3gjdH2PO9#->fM@jIy9Z|
z3X($z&euWUJ%5mUDfpbf>9h0a;Nd(uh?E<tCg3pXumM1y<51a<*yS1d)wEX_Cn9Mi
zY61qc&fGup`1WE&CVcUR{m_b|SoR9naPFH10{x)}{goHh9DrNy+5o;&qhELC45lhX
z%*m3VDvz3X-Iy6u9_dl8Hd~)}(Hf{{Xm+`=`4}aB5Z;4#CkZ2IGh6tiz=MmE00KmQ
zl8)B?V8;G>GVOVnoDmXq7DM0y<{o7LP>r*xrkl1SZ&#c$T<8|04=f(l;s|udE3yW4
zRY<i#4sJqFKjxjz7#t?ez&?O%EKHxof}vRJAqm3WJdp<D;2>_S0BzpB7f_yU=hL9V
zAHGw^g)ErNdRS1a*UQg7?L06EPwdzO)f`0-nWmTIMvNYn3<A)F$td#&Qc=6nA&>d3
zSTY~ZShc9M0y2B>HPE)P0$%QYclT^)poH9!VL@)waNmS+;NgGqn7`EgVBVJdU(Y=U
z1_%0LOe5`-#9Hqk0`mC*4E~jED%-BhbC-CLjTrSwVwmgwLnXue5|pPfZUfp_3dA|;
zhfarY0U(Z+SExEe4q{Lc-Zr7&$Up#DXCvu+-B^3{7a0}R`sC`9R2)PQBJ<V>kh2D|
z!Wvk=aR*FUf1n8yN8u!;-2I&{uN^9xRxooPFMP)7FmJeCn%sK*n$6cZfd>`t-R?IB
z3OVp+(SGtij8oB@|IW0~nm^0*%QCCs)a8pvzjhH;k+$@c7|ZWOAuBSe?1w`E5T^O*
z97(w6njb1;z<8i@!x$v=wOEfBg~O!7p9ECufV;)$rot^Uk)qxUOa{W=HmD}OlWe?Y
zSGrYVCf6~wk89ucrsG$ysURFvt*wCq1fJNrk5~*6ibWN80eRI#xj7>W&E@NgUGsEq
zY2!cm=w|8f>w}kFb{_OM_51e5{8oMq(FwV07jpGNulNfx?moyhgs=X9KmX5dlXWJ6
z-dUGr9XRKVWpK%PtE0Lg@b&JhpPvMi4P%2~Rg-w0!YFQaJ5oQ4g*}$ym~TaRq?%tA
z#|t}np$~Nq@$@xEJOGDDGcgCSPb{a#_s&83vqlVI_}ExR4aiuoaqEXHRSh}zCw=9%
zbLK+Mh{>vHe7sfuFsg?TP9eKA(^NC0ejFPrSvZ8LWOF5M&0dH_^Jl}$FFObN8X5%J
zr7$XA7cv)+$5+gQnQykif`45$fC8BHAP1^m``ZXs_|H9aIb8P4v+N!MQfb-c$u{MU
z;DjUsRwVJ$QF(aDn{mLY65_&4DncjuC|@HHcz-gq)9?i7Qt{j`@8tX8FzK)Xpw({g
zVCFV!6o{D_5oE|DKTAiU4fUnm-Is+gxv&)gFYsLJn|2Pf;6Sy1Cmi$atby`#RoIOc
zoS<{SqV0hZCu4O|Q&A!eskPV*;4C9E$8pNi1@Kdsp9ixB_2#Hn;np_R0N4Xve20Xg
z03aV4cuRqd^@05Smw-D~>!Z%24fT2lo^|nRxa?VHiOn+zObt?#*R>u4T1+<w%JvOH
zB|;`2A*G<~4aX^r7&oQyF|rT@FB1W0X?p-emQKKckk-M;&UQFVI(!QNa2K<mFc~P(
zxE>ZmO0t9fGxDS@L+D*udfz+!#$B}J$MU7?+6e4pmHrh^d$>cpQ@VFLR;?k#mTBA;
z+*5{SYAH?(SvnAht5S<*+(N`Y)d{AJmYp;LuX)8KaO%m!#Txu%;nXtLfO6}0W^&O3
z2>}8`0SMkg01u`C6|{xXDge3-b7nW-C$GE+F7;+Vf&}xV#%5t?yAq%P$uEhj9|{Om
zGVnB>9bY3UCvj#%?}Lzf`0hp4DD-t`2Zs9EXy8sbOgikXz?1EKmtntYP&so!%yK$(
zQ0Z``7j185;VPIt*au^+Njn8#4wUqwfK~%qKyYGG9x0}oT6@{!^Ng?;@%4H7H7w6{
z*Oq`1;~Rhauml%c6_uz|1rHiqm{(HChQSn5rV??Fs@!DbN`QW!xd}6C)&RWn@(bXh
z4O`*+w?7KI_e_MBUdo^QJQfF1I(ZxFh_+Qq1b|ZTl`;4A>#^saeKI`b{8hz$BQyn#
z@0&@5W{}L}gheIzOedj`TRs??#V6<8(DQ*0E5j-2NDA#66<y<!XT|CkPO1hL*KY~7
z?&#7dRghp|@W2YcP3Yj*<Zd`jI&1*AXVa!Fi_cii9Z3fS2io9-u_+(`eYRmDX5hjw
zGJ62F?Q4~rdN8M1s*r^+kWF58&0CqPo=?ydW4UJFj{au6c@?zTtP7#F&0mE|K7Cgo
zcc)SzBVaJK##CZy>f+66?U<E#t~vE2SbfSOSpWDAxck8;VB_X}(AML6bRZD+SE!^^
zqHTw~6#DWFxh#j_LgSygW*MA&#>vHSgPd96ga#DvUIKvb7HPR1z@ZZEp(v(HQy-^Q
zQCNVGUnJ-*J!RLmm>&-q`ilSM6;VRy6=!|KMEQaZNaqLqGn(%d+`VDbVSN)o(_sU^
zkH3A-&dW|;-GcJ#Kn~U{ML<B@nUbRoM?fG-Bj)xcBg1h2rv3iqi~tZ;^Sc)=EDM(}
zu{cITYrfi)^RmOtgq8ldqi!7i+Hk}mAp1>pVoLKT0V9#{lcE#~sMwD=nFsyd`_-o`
zE`Rz%mXB}Q1)HDP3tM*_fZcn?psio3^g}9}jO`0|Lhe9c4HnEFf~AY+z{$(zm#_6^
zSzO1^)fDX%FJ?;SeEXtEM^r4yZ+6K{Rkd@XEYgozC&)B<^!B4T=o87?e#RY%B@n`~
z{QL@y<xDaJ@BooR>kBu3``8Zwu6yXA$zRR$U4_nAj2`erPoTjxMPJ^p8V<PdG95CW
zq8ecN!Z}b^o1mpv0y0I0g_dcxkXxZIpK}We?&A*t<W~#>%HGo2zYTNe4MM$^nX>!0
z6GP@PquN``;3@?Kr3Ym?+Ezi20CBCPZbJh_+W})gQYQDP&r4Xl@}%-tHdSE(9UPx5
zCPF7+VzOO+MoTlHo_K9EYK7o8V7M4g^!GQ4=FEIzN)(YUIWmC(@MbQ1OU{O)WlOM>
z_?WA6SN!7}tRVDJF+Or-3FQF(+OuMC4{R7{>u2gl6j9e919;0I)E^*BGP>0P2vZ@}
z8qm*^?at2Yu6yXPw*t?^*MSRuSC1AoSPXy=SwSo$@Npc4a?<h21qAB_SYbLV7%G@{
z+5B1X*!HnPo-+tXG+TjJ0R#}uYC*{)_Tvu*1PpNGWhc1_tQ?jvo);8^B76LaK)Kgq
z<FeTsVHu!wj=~}dooGW+_vw-6z{rOwADXF!E`5l~@Zdmsh>CKazFX*hDq9Ygp)9o9
z6%S4i74+>M^N=e?B>%SkynvECuqvD=2Mb6uPYSRI=fOZR1(vo5fXfQ<N+N9-2or$j
zr&G=!gBhS1cpI=pu}z}<RUyGcS~4ugaj{!^hR!{8p%LpWyzxgZ0Ke~_u6LI+*jbpx
zb|=2J%^{VwqL)^kyf|czkXE>e5WVR|tH#PdMP+cBzmWPgNRey!1y@z+=bRKvCtd7r
z<zY>O!h?8Xs(CO9790V2qR4M66U9^klbu^ppj0}TC^RV#ndxUD;KPItcJ0aEs0`LX
zd6Q=wVFqYM0HByJe<WfMbO@Mc478O-`w+KfqSKN|6-!&iFkt!o0drUb0RcrOvWy9z
zdBI03{NT=iw}(8MH`dy4+KPoRYp5T1tJBRVaq$ADCJKtcIvdkm6h)~PyJ?V0J1KD&
z`*GLwks1{OgA$_x<BMSAnKFM;;zKZ51PlU`%!`GaB20uH+#N~9;+J{iG77HtJWsVc
zD-QxhCm@5}?1rIT^kXa6GI#hiGXRjQ^(h{q^^5rh6Adk%v5O!vQt;e!SHM6G{YyRi
z2NndB`|f-BvoqZ8+3#dS`<iO25d0ckc=k$Qz(hOWmmkyRAsv>e0cjJI4#}X0Od>g|
z->0M~Fixrar*^aM6da`tV&awoDiIn9vIt2F0aM{*7#U3f1hPM=LK8QX&kzF|?Q)=U
zO+f`iGL}z#80$3I$=AaS(98@0I_(Fi#a^~D+>NysL=1%8(h86IjK3FMv;z9Fygc(y
z&u(X`ZomNSPYb^l{PHu<enX4dvWkW9_ce03>{(|(zgxGaj6`@5WkC?3YKTx}75L_0
zKZGfevpZqRX_{%MDH2u{?jYqt?2H6b6DCYj3nB=GC?b=`MB^o`eVSjCuuMZ_iXu}W
z0u3N60vePHJTf23Pcd&GwN$+6FXsRX3t+PS5X=D02mq{qV%G!cOhrtwZK?-_H}ew?
z75q!*%=E~0*`m4d(r29t!+qx4^LnCR&rWCd@DDyTaC10X>+9{R6EJ%i;3Y3S3l`2B
zCd_I}!59f5{&0?1IXnPhnqVqoav?w=Vw^;SCN~xx2B|61z$!;xXH0~Rv^S|EH`^=H
z!c=~|uV@&QWkQh{%EEPVkMbtYDC#fx30e2K`T^^6B=reds$#iz&jT<6G$R1;mHR%o
zv9O*Ggo;lU!!-zLfk@)gQ8<x_K1++|55rHt=ybSX?Hm|rwu{NUcDXqv)3dMcby$6p
zQsEBz>~}Q|&AxFs_skJ^<rU|_f_bw@**K#FUKC6b2|Ve{Wc9faPuX0MVs=uNs9fe^
z*Eb+pNJ^?&zWL%8R6ePxY9Xx}0b&Vd=rh7pTB-CPmPaq}vM)uMkVH-bgfEP&ISO`8
z3w>Ja6AkZ6K_>oumR$RHI{ATr`_YFVhZ&$5Pz{Ez{qx_rwO@5E%E|*D$WT&&U-ocI
zE^ZRaU&+!Fuw^yn8PkU*Q`EHZDff0Uo4;x6Uf8nz0PNa30S6DZ3O6jz$r~JOz{s3_
zSh{#NtXMh%eZ`u#QZx^lpDY`QWiXX+<D|N1QI$%#t4nuMm97|t=%(BiyAhLyBA!Zw
z39AS^!EV(}CF3%P_L!(dP!;!yiakZ$2<IqS4!LJQvAT!j7L|w>`-xj_#)5~U$n@{%
z&yH*1FO*%u#y?v7bANC#%mB^AK7f*n+4H*!$-0O$8F&PzOS~n~#ZbCCL_=J(E=z7K
zh@^=wVd)oz>acRzd^mO40>%{VNM|b4Hg9ur9YH!<cLrr9Yk9CuOK9?d<fJGvpyGZB
zZJXlkX%Z%5AUtVeov4T#31Y1dmZ|J7*GyE%Xt9*Yie%@sqN0%;e^v5~$&AJG)ljTV
z>Ggl}$807G=~6cJJYqZ1lwWanCLPRJoaBwtd_lSt=qv$MOu84cHbXRH08kj!cLFgu
zt67x_a_k)4C!a9}0aO*4ssPFIO4IgN+fiW~m~~PCuRt;(*BmZR>7=Rz9U~PKwHxy4
z0bqq~kZ$qnK9GsK2;F1Q2u!0+6&g`!P+(1$s&fPo3~0LT;v-=cPc$T>5^?y8JQ1*X
zzOeCGvm*fkQfRN!q#p!fr(qM?AAp<x+NG1@oiIZ*BXa<n##;I9IBf>9<dBV2yu6AA
z0dXszldO-COLYPvSMTM^z#K(+kj9naofn3m%-MyP<JlBZFIg_07(98BnM@}N0vpTw
zzKc^%CW)R6AXOBkaijJY?Z(FE7?mxVnCJ{dOcw+onhQV~pG*w$?!n4A0LmZc!s}AL
zs_sIg*%Lc9K5;wD5X~3>+_YiS?YZ7Zi>j;K8aL#AqTebbPxVYF2}PK?64OgE3#2;K
zGKx&oyJB_yB}2-xMGT5jp%EjVO$3kQRZ-gFz*M2ClZrGgXKXKAKulVQg*WNvC&c)(
zOD_Uekst9OaYBqFU}1&OQ(mFrWHuBayc=*m5TqJtn|V#|!2rSsRZQ^CjdyRo4Q7ZA
zQDt|hYv1$M2m2MCRwEOE&RTNAE0>|76DA-h0IMek6*1vM`otg=eo-1Kq@h9tV0+t%
zeaXh`N;H_rn*?JHU1!*piru<8_|i^ED!Pdtrpz-c9hnEHC>l&NTTwnF5A;KtaU|X)
zafV6+Og{s#Njra|qIOG94oaSbh%Y05>YhYi^qSs*o_;??|2nxjO#NV6J$S}z|7b1D
z5Y3ncps5(^{Gc35fdExZ22LRFH14M#pi6k5Z65n0d8YoDN+Z^iC@SCmT>(sMEgDE<
z5j##%e-@0#n+}k$gqY?^<d0HoOLqWTSGHtI+TB<SFeQ&52y20Prk+KaAhr}FOGwKO
zNnC&Ht0gTHjIB1-9vI>wu(o5%f7Yv5#QGu3Ak8QMOm^@l19x_k#s8@y6oJGuN_X2C
zqF)b*7?XvxL#NUp@z`E&Ma3%N!c?}T(^(lnGy!tPmI)=Cxuaw31QAD7m0&XcL{gN`
zE6PgOxq~A_O~sQzQU?UN5sD2JP}DO4JSgfWpP@2;dBG%)06>g2&JjaAJ`X_J2SXh(
z8U{cBhN8z>t^6jKL7GtjI50Z#z3y8=aC*)--FrO5@FEjTcqTiav8d>LOeX`h_Yy?n
zNA=*@K_oFg{WyuZ(otfB`J_)uGP~5l#lq^WqWrMhgMmfLG7@e;K>$U$Q|(3CSw2ha
z0BIVSuCU|-R=F<hh3IO8EC33rk6!^+u=Bv^_h1HTMgibs-?;V1?Hu<&H=YSC;2eT`
zw`bZ}m0UK>`;@onJkA`z%TB}Du*^(EnTPGr3PI!yNSHpARw!T;UOsloEOt&31vk!|
zv490p?H-nDJc#`T7|=A>kO8CsaS&iH0x{txZ6!+1gqhvCGO?xs+3pCUJ{9!<)>qu3
z3C$wg0-)8wJ^%jcZ~Pc$kY*GBZoT!^RsjItO)UosfVGaoEy0Ms{i6V(!;l7qE@{&6
zAn$=O4GL!9o$oq~q>BkrNY)ow7y!YGA_6kOfHZ%~f{@2*<((3|WTJrGqDN@~6Sai5
zcl5P$e<b3etiLF4*~WPskF`H-EFMO_x50;(1YoEeje~+ukPcZ`Rf#?mf=$}pTLzK#
zH;2_scJl9js*o|5L7Gtj(3IzRgMJOyowAb_Uh#|=!q^5y>kiC4jta7qNt)>h(g`cm
z3;aSEie@1h7GfqxWM!fJ!qNuPSW!AOcHoxs`0g&@{JQ`MUDL&i^jX`&)J0;^y*H^F
z6=d;4*<MJ5N@`V_mPj9^wvXDF78)Sxf7)I)7H<zAdAl<6=q!w|N__QLCIrY>o6tDL
z_<u70Cd@F+H~{Pzo%mWb4^TdEtH5_ScYrXq4UOXiAPXq&Wy;;c%6Oqek^M=9<M~A(
zA)>N?C>b#oHZV*w9Tt+BmnnZ*D$$Mz=_<+;Y+ukxsyG3Jxa_1{D0!O-O$fjy!o=r>
z&Fk9XJ7lcxAgll?bg2$wc>^mTvNA#3H!M2(zGpy>iEL1|j|&(0A=3OgZ9)ZG_Z;{d
z%rMP30KDk~e|Kjw4A{a}0=iOyF%w=ajBl_+I#_*=(S4#rX<QMGdth7*CoHm)6XBRv
z1c;f21g5jZDZ)@Go(QHxGDc*~n2M9gkAy{1Z<(yD2lwF24$PF#eLHF(!1A~zBm5;0
zfc*|x@k0*?;WI@B7;=a!CM`|c52SLU+?de*2q|8P8z)R`B5@XdYTY~tVJNKw%VLFX
z%T2%a!5JO@Ynril0SM)-9KY7c)GwGl&@oF#W-HrCrc6V)&C~8hA^C!ae0hku0Sn8|
zcz2zq#GY*x!UK{$;K0Hm-4Nug21PcT$T(3JD&a;M(6vd$3O!}e%44h@H`?|A#j2P{
zl--5?QP6Fp0Ma-qAFR%YJu6}rSt(H>2XiR1-Vl?+p8W^mz`+R^pJ<nF$<=GM@<ij=
zL;Wykc(8m<f2~@P(vmh(ycYi5h0@!V1o;7?zW;dNx%n@4Y`N_N_fZlb*yiQy=&LWx
ze^pKZes!Js^2&_TkpKYwwV?kV?5q8P12wjrE~UjVcn1(5&+N@AG@UCOn4Z&4-F%WV
zU10aToZO+jrRB3gK;P(cn#BpZVInR-rs^p20gzkNOmV58+cZILAS3Lg$PPN5I0R;s
zyYtf0O++Cf#H37=_Z7r#TZW@)Bmkg1C6|`d>OL#*`u@Pdad>3Yc6fBlF4(qf6k2WH
z+U55Lls*Bfn0L}AK{cDT@=S=8OGk>|d>HKSi})qnnnH&chXNx8_H)83`X$L508A-J
zzQ&LT{R>Xm0fNO~g}=08FoUtN$uGkZpd$hR_ddSq`b$q+IhjG;G%i&d!4VNo0RMWe
z>lokgk*23on{cDOW8;~Ns<s4@oeeSQP~i}o-7q%M8jj@ar#5?&I<WMZC=<pJ5=;_u
zEJd|f6hUCE><~rr2Z&=Q5|Ym-Jd?tY;hCA!=2wDzq3Jho-vf6%u(1&QJz4{n{7`<0
zgq-e_*`wZT%6#UuQ_PV+wsjOXZaV-!xN9S<T0R2jp0)($%^oU`f#a{`kt+k@05A*)
zl>`PLyb{`uMhQSE>mRP@l?p<n{q^g=g-*SD>*nj>2#|s!MA!bwuU|i))D`8?T$U-#
zFED6)4aHGnkA651L|n}Sgp1;C<d3_jd%H=fXwP=neda9wlMO@N<HJUH7{%}61QK^V
z{puLgBl=2o{K7s~0DKkWSO0eH+YjHp;}O`fd3SjL2D+1-bsh9skHt?P@s<^)zW&wh
z&shS>f5V|u6tfs2s2tXuJOY=TwH*4JeYQCqM*;{g;B`(mcH;exihhpTf>;XF3J5}v
z=0nS9P6O@~^&V}(SI>RppIr$@fR2a-ps851{EvY~{fabGSQ37ItnU20W>%5bH}7(*
z*g7c9N5xGwW;FoQ{`><D(yZrSit$9g?1L02Lb5;)2(ik8gi(q2;4%7W3Nm0q-fSpe
zTH*fD2sknH!W8gzA*ZQCcHmf9MWsmPT_yNtBJZvTABP+7*Z>nPeF9D~mM7;qdf|71
zU#}N}UoY;n5&%j-D2D`@t(l!5?6l#5%?DuPj{D$w=dFO{i{=s&$0M33xAH|dhny(h
z63CSc)A&H~5qU>rXfGg|-va<R&2KCo-ACi?)_=eeq8VQWaI}8M_G@#cI@YL*17PB|
z49pux1~SYa%*WuLzW0+XByB{WP~bnGCl?eqZVyNeJPx`%$Pba^5z;{<=~=Xo<Hf>)
zsd*EKR~vXz(zJ$T8US)en*O@Qk}1nHfL53mO#F<H=6M^wbL;)^^&dVA2gh@0<+Vcl
z>7(8CuU-gsqY&`$tJU?jZiT*X?~7mIUeo@KCe)h)Q12Ur#=tD74-CTu^usrAdmQfD
zu$8nO`cCD|Ec-le6VFnJQT@U{AOg+Xh>!P?G>0AkWBHA$_WI3xuZ1H-H8?`_)gRt-
z;8icU^aV`?t8^=5$_)XOFueG^H~hV+tb_@#x`cuB&>*@8h+pxP&pu#<3D3i`+v&Io
zOr&Lu!>B|VMA$gBw4aZQQ^Y)6B7)_fJ&GHR4_5PQuK)gzAAuj<v#AjLV$i6J;H%QC
zua^Q|YI-N=))FXBeD*_vnx7XazXxH50~xUvG;kCM0h@O0hk?TOoj+$N;zs%xM4Khs
zCN=r0ssPuu18o%y0OCLZjP}=q=do7)%@_UV2R{x+h>n;AplPz*`7GLxMw&DD32J&u
zX{eyp5JV(`*ug3w$czHDfxCy~QDje^SwMtTaWSFMs@#yeC7yX1g*IV@q-7RvkSJs*
zk?<mI$}~1~Q~cEwr9~MTL>_j%-i0eB`_^yT3O~5}aU=Yom+)$(xvvV%ufOe}1z(j`
zfcfA{`Oz2u!51?eG+L8r*xYriGQglgL|X-Y{ZQ+h1-Cx53%2jx4}@{VUySOPw8U(^
zh`x`yEA$11UMY)7c*Hjyxi4xs(ZYX&BS=RS03P1C=l`@(jj<Et%0{u{EBA*nC0IYg
z?2f2FAOt}oZay5jN9LXqBIg`B5JkQy1y+V3Zagk88t0K9hzvY{fDQ#TngCFt#i$%r
z%vVh`T}+y+Gzb+6IoJq74=l|9435!rn4Fw|Z{G57F=wR82`?@DM##1B+k0@|d$gZ+
zb8|ATxy-DY0RjDeNBt}B^Acd3NnL7jJw(tGfz3g<>7FNwCB+WmiO}{*An@hJWZwg8
zCTiY;073!<-k%Q%wE5r8VeElNcYhI%ARVzGfTpkC^5gMWzu+0?G?h9>&i;pcX><{z
zOql-uNy(vtVtWmeLj#tEycc;#CcH?8dnVmPvcXMSmtq`LnIElFL~$EVMQXY;e^U`w
zJP_eW_H0dmG&}rmyZ<q`|M5N0E|=(xmM=|yXXZN--+kakS&a?~+(%#)<szl1dLctY
zuyK_iDohl@U&|+9(YzsuO@G;%sm>NoYN`gJ!y)j(UfvU6(UMY{|84xj6~FVre}yAR
zN7Mq)G&<h?47!(tWD5n(kdExu1jFD+wVO>NC}*M>Sy-9|5F`cy^dv*c*fcyXA0`zp
zDW->iaEKp0Q_V~6L#ims7`K@cjFt`uQT^i|bpJ)kW84tFPsk8}wxKoGTdhgB{edlZ
zsZX2!Zn@8n=bf3a1)P1^+2v!?L#y$v1>k?Vwg&R>YnyA?=6V^i(FgZ!+*_F0EmASr
zhQO@>*+#SlQtfG40=xxg<MX!wsqmZ0Kiw94N5}sOjxZf@0Qk>Gc7Cl`(b*8pes^RT
zpM2p&8a@(Q>*INjxB+ns3HW|<X`oD#WXlDTWC<d^5ZsWi5NKaU5{^CtdC3Kg$(rYG
zk9%ZXlj0H%1i_?9MvlX?dUXvi>KYm=ku(`P*!H6B<Hx<_*nbD?AM2Fb4%}zkN|Rsz
zt9byRpD<UXIDRU@O)KPR+b=Hxp%nIxeeRfcb$(?UMShd22@h@D1=YhHBtn{K=SbLD
zZxKOveGj1x*w3u}n_)mP_V2)kzy9uHGyd|Aq$9Quz|l3=T+{h~PXG14Z#J^`=t{Cl
zXnQFiQJT`u$9j6RST~-E_xG1t#O~q85Wh?}@sSU|-`njeaOHAwd>LF>xe4g7MG-e&
zR1oLjW3U{;D-OnG=blm6uw^Gav1>o<JunXAlkGBMqfv)Bv-)7+oLR7J@mx5$Si5f2
zYNXM8;`So93iBsm?&Qg~SF}A@rNjG*%T!T-4tb8O94)PdFh213PUvXC^`<->@9P4v
zZ%mo!)HK((xeca!KPP1@hZvdl&g~}p48rW79WE5hiw|wy2WOqKu*^49-S-$UAS7jo
zO#h0u0f<e13I=o%&<v0ij83%vUds*~VLIXfplQpVgP)$YaP}V+Bbz2#0iNpMFy$Ll
z*}lip45T}E<IO$FBMG`Z{01TLKw<7(W+n{FQ<+X+?|Ik<xd9HP8*vAu+j7xor+ptO
zwBL{J-w4|a0MHv(O4B#9W7OigQ*{00zDd}ze>dE}X*cvWYOwa?`Ec&3OJK0SX*W7a
zskP-6Zc*VVAuX2+p@>6rU~A*Z^pRay7^?*x^S9b<czoMFaAtq`^W%G=`7u54E(Km$
zsVR2}x_c8OFKvs0>+Gf}7t|CcD>n^k06Z|EiWPy;VlH49g0QLt5V%|9)nsi4fYOlw
zh=%|=JU}28Mh)7aCLh_p?bC1+=!i}NI=b!$x9z|B`Inwq&(yj8xNyR#ZXb1O3Wjgn
zCE*nHqZ5a5_{w_#HhBmtW_Oi3w!1<mnAK1cl03})p3#GF-S_W-+wb2D2gcignK6IC
zZgW*+T|3ij*Y<~nX}n|KI6P1QN396HP_JQE7DjauTUOd>g`iM_N!&)k?_ARb&F{b_
zhpoHz!krIqD>wV-x@F|&=K%cAP2QRQFcW~ZZ^Si;7p{LXgz-hop_#Q|>HJ|xbD>O9
z#r1ayD)I;^D~P~=(Ei$0FN2P?;B!~J^P~R+M}dxt1)yoizWpCvJ~HREy0JN91<Xtl
z0x|WSMz^=AH~-ugVHTcfcK_O1%G~$G2uQ$(2Rtd(>CBI+1oT>e{gxf@wOby5$u@e;
zXV{sJfinvPS=jbk-e)CF=%j92U#wy88H4AYeKIuawNPf#YTgY)OUsr`Q`&w&cIO)>
zj*euq%L9X$6DY>vI|_~K_W3K;7U5qgKW%UD%^tellnGtgaum&B$Jo}IC<g+i5uiLD
zY}q*)`kHYlfap;$b{2>xrPdD;OpLWZn+vc_;>wp7oxE$`=ttow(Gi~nbo9oLeEQ~z
zPJW{~Doio+$(da4Ir?#%lEe){i{#!<+_-z7v;F*@bsX(96mFtSesYibp=UNkI@y=Q
z1CKoc*WGkqF~-+ldd(~V=sH^qfSAI59PB{B0zs|bEZ4B>{R6ONv;*J#@#EzekV7Fv
z`Dp!kgLWaD;uq@4qqzwz2!6JW5Ji~cw|8``<O8sRu6+HeTewHTA1M36-;?%tD+7AU
zLGK5!`vcg7V5d{M?HO&AdjgPULfQm*JGcqH${#HllF3h6KD4VBh;{<7So)j98(;i8
zANm0tB|2&Vpy@!X^#N_}p+#c(Mhs;s#$H}<DFZq$%8VEZaU}R4?}eZ+;SP*|DcKR7
zPC>v#GD}XIw(o(j-|`^n*VuDKOnz(L0#H+zPJo4Ru(J}%HS%Kls@^vMyC(o{x^D~5
zTNE-9cUndt?+|~a{!H+ln{VT^5NPMoF*Dg2>T2p(zOw82LzWxBTLnn6BS7H$+GUjo
zAn>aJdUZfA3AWv;K&O1&SRXRN;sWwIPuV7M!Ax;5@LA4h{;g9f{CYC<!1%=b;V9Bk
z1%Uhi;uHTpnd4pPPg!yNLT%_SBpW+oM&j=8NjR#xgcd_7K&*3JXHq7X!h=BnxiAxx
zlkn9a-Vd$ZZ0-o!%)OiLKp+T0_(3cP79{lEcD=22^Zr&bi@ghy4HapfSca#;)etdu
z=s4Ou%nuPDEvTj-t+syqk61il*ZRp7B@no30R(}f_oQpgDk>2+zfZsbK&R0Bold&|
zfi`r?{Q(GL<1JQ3mo7#?pJ*$_4?)X&AaH#F0MZI5moP!U^@n$T{Wt&Wh#dS;MMo6?
z))n1yV50S21iCROJ?X+V2TcV76F}&6^AxxP2|)0B2%XFFq0CG=%e#S;t5BYVaJbI>
z;eC(7zG7A<w`M+yb?ub7>wl=IF%jOMv0$NUg*DLZhdUqJT?{Q+01~`Fv>T}a!?6mU
zj)4vd6p}bXG7*710|D32=9!(i?Y88ne7*^5AkH?F#|l4K^PgG)4g}C{4C-hrplyZ$
z?M}|?3#_AgOGfRN015gGsiA@iR*wCP$-jN0t@o}w0>^%JqoWD{n!fPx_Rmk|aG#qB
zG|ijXCCI=5d1r&@B>7x<U-3-RWc^J#kxLcrPykFxQ}H`6HU@V+vP~R`66!{Gx&a*)
zp-N?ElFeOtpqwbwuf^uYTJ}AUZ3ixuB!Pre!XcJ_`HRwt+Z1V;nN$Mns)1%BZ0V)*
zA*2mb<9^;1iur-l^dQ`r6%aNCVPOTJ7Jhv#ewk7P;AmZN8b64HKI9MneFV=lxiS6C
zo}_Z)&%GbLdBf-7DAQ2~0KK_k|3vFg%5MjtKe^s_D1tzHPlA~Nnf&`uAu+CbQt|AQ
z)MUuC9`N|m4;^q|qzUd>zqK&^F$llnnqF009b+S7d<-wVSk30%bkpy^uiv_#Ob9}S
z)FzsgFz%E;8i|1?ZF`a@vSuais$h7qX~o-Z^`*j3vkIW82yghd7DXx_^U$Z{Byl?r
zz=r~6i*Tm|hW<uP^e@-cRNI@nx%m(N&lQkb1F3ss`fKG-hdpDHe{wW7{zy9N0HEo<
z_kHa1lO1(ixdRN%szBvXz!Lhu6bJ$bB4twH$C%Z1EQpQ<F4)K(5U4<=ksnA{-N7BW
z|MBf*2ZJJ7URfZp<Nh%I4}wn_-)G}ZS$Cg%ZKq##Z581+?br)kSVaRy0-Xrgl@MYq
z2~6z^48YqvK?`ON64*=4dnleIA_GL<Mx<*wU?&8w1knHl<y(Vo(~f$#=&XUhpfm&!
z$TllmJ`eQ|Jd)7<(X-Y50Ocm7u~vTDH-GB`*T6BLV*&u{ihA!JJ@^OcO=H*A90(}7
z$HM6XA!jn=rd!ypMl`!mm!<<;C>Wy!KV;BArGZ2!t~>S|fc?eTAESxASknhL09XGq
zsayg5K86?kPVUXw(xY5ZsKMqP`-w1c#!ONQ6so|jQt*N23BZI91hBgLE}A<8jm#PU
zbmm`00uJ<ue+X&#ymFE?<rDgC5x)|E<h9{292l9^2hDm7xYn1$ztA>eTn{`f0SXF0
zKHZ$AuMa<|!>-ZEcdt9jV}D1-1OPPs{0BdI-B<^%$7p=)_($=MrbHMModXqo;7m^i
zpK5x;Ovuwh>4vd}Ng_V~ZQ8M~ocMEZLb`e=H-YDb-j0Jk!vmiKaLntMkDLbUgddh0
zUozOf_h2NH7zsHfgt>}PRy-66A?^<!g@Q2nb_Cl3n)-@i%hLISW&o%l0H9j|&_Lh?
z+quyqP(cjEqStUw#^LTIA-6A}V*;&MI2+i|k5n*)jUWXc0}lo;0N`K}#{GFX@#p#%
z|L*^H9ULP%W&ltSZrgkCT^*HmN)Yh#0R#-xbW#Ce5=t4a!jB>VQa7aY0W^;6Yyb*E
zv0x!=D=Yv5ZjR_u9{}6whSDN1Ap%1yVgXPFc5k^E%{DqVSuAbkocTe@JVGqY$l5>#
ziGHqikToDgpptBRQ(rywS-W&DWNvX(@zswIUd3Pj0%s`@i-8oME&&1oO?u7CAC~d!
zMFCD*I*$n~2l~_xh|mWi=WbN{ySM$o^Lh^T_O|^8-wDT%jwJxVFa7l=?>N}%e9|uq
zM#4bMVuTpk?E|?5%8ZeAa3UbW%J}S=@;8$WDJZV<jd1QcFb@6&7YMWdatIKOf29B*
z&)CfY<ZZO;*Jc?D%eCzsCR-gy3pmD)qCTOy_&_JP2*!4Vyb)3KgRhqWxYdQw4K)GG
ziT)N8k$@r!;b)+jzM5{4?Zqts_j6@gb<!{l5A|^#V1iA5(vJ!~03Yw8hyxY~O5un7
z6YWo3_3n?{3CEC*DFA4?XXEZaDi&b2xlJLCKjbw@R6v}Qc=oLhG%OWz@=V;Kr9Twu
zAr%~qnB+jQuA}#wd!bjJ#UTiPxTj-aDkP<r%as>=U<Y61`a5jUDTH3HXP3;C=OBSA
z(O?kJ09a#7kVWAJsU&@52*NMAAOsHetrzna=bt(sGHjc+C?`k(fM|I1K%o4}hpb=F
z`rwsFB)B;dV1KWQp$NbwYfl0of|>?)(q4oQLJ%ZCz`?-?J=mo`bNERs&$d1G`1U`9
zV@$^!0Dk|U|9R)$@rmDcTZH*AKxjrmNbgDKpmE@Qh;m$tjPZle(=-X7PzGRbv^2&|
zYvQ&RP`So01wH8xke&yCV8*%$tU~BJt#<j-_nOP<Sq4(hD}SQgqD3Jo@wDxb#wP)2
zyVAyFeQ1_2&sjMi<_>BwY%@S)+r?3g|8o%iBZ`tE?>h>dMELc)Ko_r_53{uyLis1D
zy@dq?T<EFb0|AmS@fXJZdehI|@yXx0`a}P)3yv`za{$ov>i7T6XU6mV8>PTt76SlI
zu`)n?nS1<9`5ZwCF+d9KV^P3Fc<@*K0ZXs-?Vf*c?jw2It&HO|K79jDSOc_3WM`XO
z#>!h}Lrb%+%C`fh`JU8bl|K~iCgO6`WQ~K7@YA;C!tbqsS{+_+_DO|EOcnx}muDi#
zz7JYmTLEE$&@Tffs>!Mj#Ns1EuTvKH!+C`TLM=CpK%Q1vWCTEf0f1}&Fy_y_MWU~I
z;t#*^f_Hu3pWry4;{pH*!fpEw{%RZQ2mRZE{*6Il5nw7daG|HS7@PMrPB2*=iiaSQ
ztN0qPObb1;1#>n&(u$A!$xARG<ck3WHG|Q78M|$T-@eqMhXeD6&DuIt$a}iMSmOZ~
z+6t0UphEaTB!#|azK;6evIWC%;i<Dh;Y6t)l>**or2by${jkV_M4k|Qwr>JFU+GjA
z%+BC{U9i-q&&wWZeMk@UkN`iQ@S|EEi1sf`|ANQ2jE?>)Ox?;yg^tRBAk(31Z@zWc
z&phkWgMIb-OM~dUQ+<G#hp{e!if0vlXhS|H(oDG}3Hg<PhK~2+$96)eSmQ;e^~uS$
zkT~y71?zzS1K>jh2pT6hTUr&i;OtZ8!~EGpP)(}K(~~ERSXs0QR^&=<m(Ck3CQc8+
zp3zSE@eX%3ARPw!c@(P2eIAOOA{VTxkdd**^mgBQvlP7Snag0PzX1>(_>wALTI(RW
zm-uJEL)j6C5q``XuxG6E&MV&i!T$@#2^}vMKuLGJ|HB_0@4&a*39(4Wf+?{_j{1_g
zxep*sd#}Y2Vk-fIStnA3NLw<~(5&(gmV4cmKjMmOv+%4OI3TU3Vp`T{IUrkF0m6y5
zSgvX5U92nIH}6u4otE8}H~gyz-i?Shl*#*hm}=(6geZZqn7jC2=PiL%iwcm0iK5-|
z@OrfWCV<^J3dAC^!va4<=mg7veuKn)OL6|Nf>%6iS-BOtbkDnJf23o1uKAJhiRT}o
z{r#jLn(e;hZTQyL$KUtYa2(NbV*zNop=h(`oqgU7^9BcAS2WvzJok=6K-!%>t5}z_
z?*~MYQ*P-z7)c>0{%qQH0QOGk2Qw+Bki7OvUV<r^9O+Uae+J@HmV-&F;qa_5EKfgq
z4y;%_m(=me>i2Rm0_phDojfCQXLn17Nj`l5X$hEiQE=+wSp}#~!uEZ|GODWC4}B^C
z_ST?@#5jOze6p12T2If7u3X%Jmp)^O(flD}V3vd^T2vwO9F#_wU?!u2QuAv?ma9fF
z#o5?>-}cd~-uj;J?}p=ujw=Ar^yOP_+4B>ZUi|1lqwy;Dg<ql<J;{SORmHu3b{)+;
z<g>e^g@?O}0x%FLmI23Guw`GXJnznFE-&;91gN3UV1PXv5D^)o)`cAyXm;TF=dCcO
z23LYt1t2_GoWN<gW3_(@zTMe>dWYcBd3D&mf3!S7iM~I<tSb2)g-?%wr_yg8B0TG?
zVR-J@3(XN~D&&_a1_xPEaAqZzS0eai7*KBV37dUN0N8e5;*GETvk%?~#~B@W0HEob
z@7;XYtDgOwWzAacVtE={LVuWN2?(v7_d6Bt|Hlsw2S=!kkk#8L31u9@Ikt@KRg{^%
zXXZgh2-$LEudE{yksXqq9AqAQ9eZ`==Um^v;d}pd|NOe{aoyMJem)*Moz#OY3htuR
zRtb;m?&7bv%761fNw$&_sMNdTAcu<y5Jn*08XL>ydq;2fmZ~{zH=@AN(B(#f@kmJ!
zOn{}Vm-RfP@h)JfO+J1XO(q8{L%tkZ7|)=iX!1MV+1`yrQv!u-+)F&7Umcv;urJfe
zD|(`CUqWUJ*@xdz9jzv@cMpN%@wdU`U)5Dmv$j)DCe^zjfW{Rjh_OlNwljLU<7n}a
z1eRsosp6&&sq)`byzC-8+xi%w1^XlB@0rMhe|h5Bi;rQBS)$?4EE-M};<hC{lQYhJ
z^6r}jnA+aWX2}31nl~L&2LfwMEI7A(#KN6F%p?<Dgy(ZLs33gAi&&ZM8LN8FVD|kI
z`60ip#=|Ck_Vl{bW!0_wDWBx2ny!2Q$A87j=J)&QWcuBLdML~JpZ>m$hLvs$u*|{j
z_`1;N4Q^861=s&Z>@#i5hb43N?)Pwt=FLZLfN_b-d{b8ydy`)~?z>XXW{Cz-j{MwB
zA6!4#m4ch;VUxG!JRlw&B*=<vIY%V%KTXofCZzDIgTvM4<3No@JB$vZ-L~fa$5)>j
zDsKn{m$MdC)$<YQDBk9$7b($1+WaaR?6eHzjodq2wv<qPh%VDdh%8g0X*#)Z(V;?@
zU#yC6hxpbygQV$da-_(;9Wn9omP(Yi6}$3&>79fS9!9OAVvJy%mhn=P?9cj*LCGYo
z3A2J6X-^imoCpLmU(%qrx8Yi^{&l3Yu;A%hR#N5T_jH}OS_oUVwT^V4=dAqkNMXb{
zh$n6>GsO)s;Xf+B<g*c7+$ru9-0lhJ7OlSPMw#Q_)Lbs4cTuOUXigiIz9V45v*zot
zF*$?aouj_=n0aUHT=KO?^s7HL1?^o4>R>bA!pRMw+Z-^z!<Sj?W&2O}{9->2Q$Crx
z0yDAU9YaXpa0}R_iB|Gqr@msPwieFt-ez&SN@10yw({FH`;xB7q|SiGP%~|`c+eMA
zamIObOG-)?{#O8RD^H63W3es3-4~~XLm1vT=Ve=0*JRiyj!V0HN@+z(>=H<CW4Z*S
z9nob7*qHfCR%eT`dwUkR&uxAoe|b>K-kk0uSv6NKCdmsp%av$XvAKcLG;2<L=mUD`
zy%c5<dZJ*kh-!GV_Jz<M+Ha<M@=g2o=hcF8ec*?uM-&p?B@v0U+PXjAm}cg28GhQo
z4}JnHu*sGr1g!fmAV++y3(ouMk1xDl5C;$CORO(G(&dSvc>S;^hxoB&hUumjhu8;o
zzj8Evj|w@Y!EFwAnM3YTQlpD*6l5ir+=$3sb%%;ha;~B4?7g=J=wxS6xUtc_aoeM9
z8C+}YL6KDHar26J9-?PwIDREFBMuDa(?NXxliX&GvgcfcnKFmnAZjIxQ_`;WWC;qk
zCQ@p~a=jpCNe<A3MaCR3wQEGBTv9BgErks5HbH0Sf5C`&*y}^f6=2cY5~cT-uam72
z$Bi58QjPom^)B}$GsM>tw1l*OYu7VmY%;7G_5bh(dL3vwO52RCM&!;THQ!=REL_9g
zbHA=MiHehPR@E<voXu<_9NXQE*QuTI7h-hu3#pt6AAvSUcP|gwk4gkZ*#+ap0LGjE
z)^kOHUf~^337?4J=FbG@a5jx&tnvM-#wO#p_DN4=QiIP|W0nif++(`vO3BY)WCW(&
ztJStI&fd3oy!f~6#l>WK0PMJ;XRDxT1)fJ=h-~T1{ySNKt@^%Mc1o!XHr}PDpKhf5
zyi{xAC1CX!j(I$Hz@lSyFxq3MRZ&rdOjCw8y<C`oqsk)v$^{)WQL{S)W7P?6nU@Nl
z^M*{>atlkFH2fs^Qz$#+G*aT#0W@3}6XJUFBQEncnJzyO2TqS(uT+#I&mpBr7B6vQ
zm{jQTZ|i@5Dj9p}RD@Xo&e$s?;qUimzb$y(r^oDBsuc3KZTR>uK=<=0fqz)cl%khw
zxEODuKo!mG07O}d+2EgpRXJe12#b!3+JT!$wMd`t8*dtN^U*?@8bAwyTh8@JsTu-%
zArbyGiF92kya6o<h=QMW4|wE|@WN0ZE6f=pQ7@N2vQ&K8clb)sZH%;vfA&WFf&aSQ
zn(H#V8ox=liMb>cE%S7=s=|9CC{UKUqAFScw?lx+tv5ip4BUu;hl$Q;qgphi1W(RV
zQ8NIhYgk@%^ZTY3US(4&zukH;IjZ0@3QI`lCHD%uSz)BfRG!mI98;EKkyGlm@cmIQ
z=*~O2|ExU#1*P7N+v5#tw1FR(@dEsSG9l0>>0GnCg{J|>Is-Kg3QwYSZNZoq)Rbx6
z;G?QB{cLsaM<9VIRSuPEuU(pMjH}=cqXe}zsVrQL(z<;CtK_m#))RBUztUT$#%M1K
zG?Mh1knLgql5m^~qagc$8^~8A@A45UwlZJp5S};jjn-FDc!H5{#p!q2A)lI@#+HfT
z{)<dKb0+G=sjWxj)Q>vyU#W_`ePl57qxkP?s%LBw4`q-^!e<sCI&i$|)Z~RBbL`@^
zYyPLC#9V@B<M{2ZSkyjmJT{eQ@#|^1IqBT`|J2T{{D5*dFH%pB)5lPh^eNU+u`+vF
zk4|wfXspSJZyYei>bic+)I<NI-|$Z4p9Nz}pg_p<u;{8By`||=y)saCgjtR{;^#g6
zn?<ELYUu*{iH+vCkgtdxNH0<v4tr-$kZ1Cm!#2)@?c2+vffZ!ewbYJW)Yh0hVL5(l
z9NJ=7^U?DH1>(*y$XwSF{HL0?^IrEqJr8Yf1DBS1KBa|V3uap_Ia5;#qYbSYS8dzc
zhTRTf9!87z8w)Nm8`O>Z)s_spLQ(sMv6v3ME7PV4Je7A*M)=Mk0zx|Dg8myKct=ms
zF#+<3xF}1@8m?V%5?kS;u4i6W)r(csD?db{sZ6H6Q060ExmJFypahwwrD!YaYyKR4
zs@?r6*92-zzH{W%7gN}Z>jpD=Zz_~A+kVpjM+fuH2#-A~Wg})|XL%#G;vmFPIDQbQ
zK)s!Nfk`VO5l3NRxcp8m$YiM+G1Mb-C8*M9vb{;AOXa;k!vcAi8+PIuCvO@nW10Z0
zWbx*BX<Eh~2lyPQZvi!a4m38-+`N8^^Y(L&3j58e_Y1Dx^TGC`yaTvk)Xb9fBoQ;b
zjH~30s$^E&04PndFCVv?j~#MhTR6XNs!~uI9I#5%0R0gjTEuT*HdEa$bt!Zd5<a(N
z%YaKzwH={msD7o<9uD#PYC>9nbKKTL+vo*v<@LEb$|taOVm#mMBHfh!lM%wn2Lv7~
z3Ni_w7|N4xN!I&*@rw8LxDUZ@JfpahEZWBAK)TkxWW;Mimm|kwX@DbR%CD^X%?^?>
z9GWBPyM#=8B8w*%Qh42{B`R2j{I#9aRil`d=u+>kS%3tGC;G8Z($a$@e+Wwca?d(t
zvQuJmxn;Wvjhx0hHZtK~cXe58)+}CZM@`lsi4)dPpuTd5_6SAUb`lIvtUt{byW0BJ
z#4hgoCyzb8<>*a4gFZUIijV&-Ps-bY<TyqVM#2Arq-RV`pWFG?-YJ0VzRG9hoZ=rM
zzDtzR$zl?_3h79+iV|JW(ab{Oi4%Gc=>M)mqP+%iM?nx+18%wt&jfG2R#4*a5ON?}
zD&9d@L~`u#;w`dUHYfR`<|NidDaj~1L|m_o-I%FWWcw&L;A&JJ%Ds{QN&+pxYEZS?
z`srx$jo)J_1hMrM=elaV3DRKnF#F%gme$?|KhMT%Yw4iF+iqR|1mVpGen%{omDJ>t
zo*_HKH-625VN;bS>?%BQK${@uXoNr+xHpmxu>A<OvT)Na$;$Z!r`lk^<fgU)&SCYP
za>7~L!1ZI(tU;D_w=z2&1*0`d>003ge)v1p*$1mudv1{SPUM+d;UxZMFvgNA&;w3z
zBVP1zt+*)Z&+$i=xq1B~7QE5fA`Q<qv0*$>2yp`ux0pT|emPlh7G8N3)j@$4{&L~D
zS}kO6M@Esbyn{_@F8w>^8L_9k?-i)3OhE#7Bfndq6>-7|HxiyIEG30QN5WOR>-N~A
zy=E@tT~}6?=)z_oOEbZzmR|pcZ|=qx^cC+M^%|!T&&`*<InM0|!{>9*N%_)6@zgz>
zwDHMD2`T_j?g=LyUD<D(`yvg!i>ew9bVr6hzaBqPi<PCMuPKu^%?j7p(fX;FbTcZF
zUMI}oybxN=U5i3q>z1uXdO;;F+>qGqP@b4d4#fu)-RanqdE)lD9~MMvq^VP<pdCS%
zMm*s&r~i>|%fKLWyiTQ8x7hyeK0o%lO&L!G+tR1lc9BvJJHFZrC0A()C0728T3DQy
zb{hxrWZV1IAhwxUlEXEBnbH!mmB>UmcCv{B2@BIJdc*<lVOtPg0XV-g*-P??8yAN-
z?*vmUJhKeDtCV`c;3&$fzIw1$_O?v&DZi<yXO=$g;tyYusE<ZP#|9Df3u#VxdVIv=
z;OoK%$REYM13zYZ3vx7(A3lZ|<l(~0zoj<!TsYsy%|De7!td)x;Ke<oD<3L;d;riW
z#AU%0;6wYxmzVdx8zEV2mKxh>H`Mr5W}nic=az=uOsOQ5O+DRHK1EKoaK6<P(aRYW
zOEC@^l`YpW8?heo;-fiIcRbh|Rd7_*mm@=)dTv$k1sx#5w(gVFoqF4bOaOuKof*=4
z^)~_mzBF<ICN%Itl$hgawsV+<79J_t$#(1gm*@IzqFmLCo@{qy1+^hA7Y9IN=Mu8T
zcGcoqQ;kiZSJP-CyupV%R1eS7XgjNSZic$HU_Ru(z8r?)2WOO170v;C%6s&TAs1fX
zx2CC*v2BhtPD)S;>~{(ex=2MoCEg=s%;>PbK(?8Z-B<de;fL#sLhO}jC`Mw;wP-~^
zRZkElscI3+c*_Q*@b^RbqXCGDSKx70w6*Thy>J`QpM%AjJp2>_dZt-uy>$sE^BtG~
zZ4))tgx$YfIWPRry*n;&*Rl%Vu->Qfo$mgb@r8EVTgmm(g<f7j^sPuzQ4-0&?}#r^
zcSsJbp6097;@V3O%G35{QsD*M0rwt~bAu~f$?zFsAa0x#v4)vp`4hz>_z47<Dhi;s
ze1Sz>eh+I!9@keGjr*xmeTh%^WnQVg-hWE*S@3<tR^+aN&XA9SwQDB2!k$ByBPF)i
zrB$oTf7jHWa1>Z@XGh6>Gl$o#F0@CSX=?;MK$U^HTt1tDA8iCECc^~J0V~WqU@vT+
zlNcvYgqx%>a;#xd%1n74|BJ3KQ=Km=@;MEflPcz{hL5_p%|KL)j%Ja&Vk3(-cK=%&
z&}FdoLT_TU<9gWTxph;5<|{`zeZ;?9X(4b=m*eI3tR{TbYlZ*_k@DE3=*}pE2TvV}
zKZClBaUn!-8ya!<b@+}>f!v`1@tH#1-PJI$v)|7F=z~oL=8BR7q0TVB&mz>dtxP+&
z>A~t$h3oFnnG2<*iTh4xO&Xt$P3!0|-^m%>!7jM-@|Zs9H>?vbdA9wYT)23xkb;$f
zB=Od;z^21%{V`~L#|fSZrN}W6CG77s3vJPaF(z-wQ`1wL0i@lw_s<GNf#2B>)|iCP
zp)4^1(ISDTHa5_>l9>bL>%}dOol6}bYc^-q3amqmrimjk&kllH$1wp-UImWtp~Sz-
zA6o*^3xzwwuCSTfPs54)D%_Z|Vj@E>wC%Qp?(Gva1uJ&P5*TLZ?K-UF8X%Xfc|%97
z*{*(+8MnzTR0nwWyg%{wm-hQU5{ILniY+nb;xR9I`=^~#xBj&}_c-^u#8{E~A*A_)
z^W)^T4<P`u@x~q*v8Cno;KkXmkf#Lqht@|biq=J!H#4&-EQY(ID71(9B9+f2qOfFW
zUWJG`$LwCy??!Q9Hd+gl+b0}G26s9W1~Iq#$)4oU*%G&iW~1%XS{b>yIIm{kb6&0s
zul9>;h5x$gf)9wtdWry9`m^G~5ZuD4-m?((!s?Eaf*#gphV0BfprX^tCYTIt?u#WS
zH*TVZ*(i;o)NdB(w)%DRwNxj4hGK>Y?+w4_Yf>-9mcxYi|9pq6g_t_yWgIO3%0&b)
z$Xj>reHN~V55gl}Go5X}HDUs!JgU1tOQ<Dv9DI*e?aP^AeQ73o9||#+o!OU>%BFCY
zPy*2$Y(J8707P!{*tpi%(0Bd1`$_pNJ&JPj<mHqlrvCdgUa1bA@gM`tJ@=BsuZH1%
zTUR|u!iX?wdvL<CO}_dMRiwSHSKHO<fQ}gPgP$f%-nffHvlIKBlXrHHd;%x{A3i)8
z!8nxgcyQTB{C8+`uQli<a~tMyZ9&PY`$WGI;Gv5!+>yOt@<jXD1LSD18>+NA=y@>l
zNy;3Pute96lG}c}H4)t$MAjWxT27SOYmjTmxbp@{EKl=euj^>&npEgwt~*VPZSZq@
z_Vp{xKhXwM6crVSVe=!Ey&xY2&6gFWI!ZDw3o8D4&IwXIUJLrzV64%d5XaQ(`6Ucw
z`{m7RcuF%b`?tm5qN&ccB@d>B3&ae*kM8n#r1CL&O9P9CR~oT<FG;l<!!(*G8LsoG
zM0DRLj_PT3Cb9c5q|A^9T=(jeZMb6+)CHE6B{F9dZr0(RVd@w5hLyLI&ar0l<3Vi9
z#}@TA_SZ>if%Pc;O-#HD=I?noEc^h+<rT^GccOHyAOvv4Qe30gUVYfuWHt^9oof_K
zu6=0K7gjduE_&DhWM4&ae77pBs)3ot)nd>58Ol7DLE5xayvDj@2pn6N$g8&*99lcE
z4ssLWdf;vQh3a4DaiEjtwdpY*=floWaw;FPa0-)0lH=T!KXQCR{*>~ZS7|El8m9ig
dPE#OQ62&g8!RUi%EdoFuTIzag^(r>e{{thz2G0Nh

literal 0
HcmV?d00001

diff --git a/packages/frontend/src/components/MkClickerGame.vue b/packages/frontend/src/components/MkClickerGame.vue
new file mode 100644
index 0000000000..6ae202cb63
--- /dev/null
+++ b/packages/frontend/src/components/MkClickerGame.vue
@@ -0,0 +1,70 @@
+<template>
+<div>
+	<div v-if="game.ready" :class="$style.game">
+		<div :class="$style.count" class=""><i class="ti ti-cookie" style="font-size: 70%;"></i> {{ number(cookies) }}</div>
+		<button v-click-anime class="_button" :class="$style.button" @click="onClick">
+			<img src="/client-assets/cookie.png" :class="$style.img">
+		</button>
+	</div>
+	<div v-else>
+		<MkLoading/>
+	</div>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { computed, defineAsyncComponent, onMounted, onUnmounted } from 'vue';
+import MkPlusOneEffect from '@/components/MkPlusOneEffect.vue';
+import * as os from '@/os';
+import { useInterval } from '@/scripts/use-interval';
+import * as game from '@/scripts/clicker-game';
+import number from '@/filters/number';
+
+defineProps<{
+}>();
+
+const saveData = game.saveData;
+const cookies = computed(() => saveData.value?.cookies);
+
+function onClick(ev: MouseEvent) {
+	saveData.value!.cookies++;
+	saveData.value!.clicked++;
+
+	const x = ev.clientX;
+	const y = ev.clientY;
+	os.popup(MkPlusOneEffect, { x, y }, {}, 'end');
+}
+
+useInterval(game.save, 1000 * 5, {
+	immediate: false,
+	afterMounted: true,
+});
+
+onMounted(async () => {
+	await game.load();
+});
+
+onUnmounted(() => {
+	game.save();
+});
+</script>
+
+<style lang="scss" module>
+.game {
+	padding: 16px;
+	text-align: center;
+}
+
+.count {
+	font-size: 1.3em;
+	margin-bottom: 6px;
+}
+
+.button {
+
+}
+
+.img {
+	max-width: 90px;
+}
+</style>
diff --git a/packages/frontend/src/components/MkPlusOneEffect.vue b/packages/frontend/src/components/MkPlusOneEffect.vue
new file mode 100644
index 0000000000..6a09669a68
--- /dev/null
+++ b/packages/frontend/src/components/MkPlusOneEffect.vue
@@ -0,0 +1,69 @@
+<template>
+<div :class="$style.root" :style="{ zIndex, top: `${y - 64}px`, left: `${x - 64}px` }">
+	<span class="text" :class="{ up }">+1</span>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { onMounted } from 'vue';
+import * as os from '@/os';
+
+const props = withDefaults(defineProps<{
+	x: number;
+	y: number;
+}>(), {
+});
+
+const emit = defineEmits<{
+	(ev: 'end'): void;
+}>();
+
+let up = $ref(false);
+const zIndex = os.claimZIndex('middle');
+const angle = (45 - (Math.random() * 90)) + 'deg';
+
+onMounted(() => {
+	window.setTimeout(() => {
+		up = true;
+	}, 10);
+
+	window.setTimeout(() => {
+		emit('end');
+	}, 1100);
+});
+</script>
+
+<style lang="scss" module>
+.root {
+	pointer-events: none;
+	position: fixed;
+	width: 128px;
+	height: 128px;
+
+	&:global {
+		> .text {
+			display: block;
+			height: 1em;
+			text-align: center;
+			position: absolute;
+			top: 0;
+			left: 0;
+			right: 0;
+			bottom: 0;
+			margin: auto;
+			color: #fff;
+			text-shadow: 0 0 6px #000;
+			font-size: 18px;
+			font-weight: bold;
+			transform: translateY(0px);
+			transition: transform 1s cubic-bezier(0,.5,0,1), opacity 1s cubic-bezier(.5,0,1,.5);
+			will-change: opacity, transform;
+
+			&.up {
+				opacity: 0;
+				transform: translateY(-50px) rotateZ(v-bind(angle));
+			}
+		}
+	}
+}
+</style>
diff --git a/packages/frontend/src/directives/click-anime.ts b/packages/frontend/src/directives/click-anime.ts
index 83ec08543f..3d070177bd 100644
--- a/packages/frontend/src/directives/click-anime.ts
+++ b/packages/frontend/src/directives/click-anime.ts
@@ -12,6 +12,9 @@ export default {
 		target.classList.add('_anime_bounce_standBy');
 
 		el.addEventListener('mousedown', () => {
+			target.classList.remove('_anime_bounce_ready');
+			target.classList.remove('_anime_bounce');
+
 			target.classList.add('_anime_bounce_standBy');
 			target.classList.add('_anime_bounce_ready');
 
diff --git a/packages/frontend/src/pages/clicker.vue b/packages/frontend/src/pages/clicker.vue
new file mode 100644
index 0000000000..082a303e6f
--- /dev/null
+++ b/packages/frontend/src/pages/clicker.vue
@@ -0,0 +1,24 @@
+<template>
+<MkStickyContainer>
+	<template #header><MkPageHeader/></template>
+	<MkSpacer :content-max="800">
+		<MkClickerGame/>
+	</MkSpacer>
+</MkStickyContainer>
+</template>
+
+<script lang="ts" setup>
+import { ref } from 'vue';
+import MkClickerGame from '@/components/MkClickerGame.vue';
+import { i18n } from '@/i18n';
+import { definePageMetadata } from '@/scripts/page-metadata';
+
+definePageMetadata({
+	title: '🍪👈',
+	icon: 'ti ti-cookie',
+});
+</script>
+
+<style lang="scss" module>
+
+</style>
diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts
index 63c753de22..bfa4a3ceab 100644
--- a/packages/frontend/src/router.ts
+++ b/packages/frontend/src/router.ts
@@ -460,6 +460,10 @@ export const routes = [{
 	path: '/timeline/antenna/:antennaId',
 	component: page(() => import('./pages/antenna-timeline.vue')),
 	loginRequired: true,
+}, {
+	path: '/clicker',
+	component: page(() => import('./pages/clicker.vue')),
+	loginRequired: true,
 }, {
 	name: 'index',
 	path: '/',
diff --git a/packages/frontend/src/scripts/clicker-game.ts b/packages/frontend/src/scripts/clicker-game.ts
new file mode 100644
index 0000000000..77206cc8e2
--- /dev/null
+++ b/packages/frontend/src/scripts/clicker-game.ts
@@ -0,0 +1,46 @@
+import { ref, computed } from 'vue';
+import * as os from '@/os';
+
+type SaveData = {
+	gameVersion: number;
+	cookies: number;
+	clicked: number;
+};
+
+export const saveData = ref<SaveData>();
+export const ready = computed(() => saveData.value != null);
+
+let prev = '';
+
+export async function load() {
+	try {
+		saveData.value = await os.api('i/registry/get', {
+			scope: ['clickerGame'],
+			key: 'saveData',
+		});
+	} catch (err) {
+		if (err.code === 'NO_SUCH_KEY') {
+			saveData.value = {
+				gameVersion: 1,
+				cookies: 0,
+				clicked: 0,
+			};
+			save();
+			return;
+		}
+		throw err;
+	}
+}
+
+export async function save() {
+	const current = JSON.stringify(saveData.value);
+	if (current === prev) return;
+
+	await os.api('i/registry/set', {
+		scope: ['clickerGame'],
+		key: 'saveData',
+		value: saveData.value,
+	});
+
+	prev = current;
+}
diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts
index dfdf324bcf..c3b22cd9e1 100644
--- a/packages/frontend/src/ui/_common_/common.ts
+++ b/packages/frontend/src/ui/_common_/common.ts
@@ -41,6 +41,11 @@ export function openInstanceMenu(ev: MouseEvent) {
 			to: '/api-console',
 			text: 'API Console',
 			icon: 'ti ti-terminal-2',
+		}, {
+			type: 'link',
+			to: '/clicker',
+			text: '🍪👈',
+			icon: 'ti ti-cookie',
 		}],
 	}, null, {
 		type: 'parent',
diff --git a/packages/frontend/src/widgets/clicker.vue b/packages/frontend/src/widgets/clicker.vue
new file mode 100644
index 0000000000..77d1777e97
--- /dev/null
+++ b/packages/frontend/src/widgets/clicker.vue
@@ -0,0 +1,44 @@
+<template>
+<MkContainer :show-header="widgetProps.showHeader" class="mkw-clicker">
+	<template #header><i class="ti ti-cookie"></i>Clicker</template>
+	<MkClickerGame/>
+</MkContainer>
+</template>
+
+<script lang="ts" setup>
+import { onMounted, onUnmounted, Ref, ref, watch } from 'vue';
+import { useWidgetPropsManager, Widget, WidgetComponentEmits, WidgetComponentExpose, WidgetComponentProps } from './widget';
+import { GetFormResultType } from '@/scripts/form';
+import { $i } from '@/account';
+import MkContainer from '@/components/MkContainer.vue';
+import MkClickerGame from '@/components/MkClickerGame.vue';
+
+const name = 'clicker';
+
+const widgetPropsDef = {
+	showHeader: {
+		type: 'boolean' as const,
+		default: true,
+	},
+};
+
+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 { widgetProps, configure } = useWidgetPropsManager(name,
+	widgetPropsDef,
+	props,
+	emit,
+);
+
+defineExpose<WidgetComponentExpose>({
+	name,
+	configure,
+	id: props.widget ? props.widget.id : null,
+});
+</script>
diff --git a/packages/frontend/src/widgets/index.ts b/packages/frontend/src/widgets/index.ts
index 3966649da4..eba4abd2f7 100644
--- a/packages/frontend/src/widgets/index.ts
+++ b/packages/frontend/src/widgets/index.ts
@@ -25,6 +25,7 @@ export default function(app: App) {
 	app.component('MkwAiscriptApp', defineAsyncComponent(() => import('./aiscript-app.vue')));
 	app.component('MkwAichan', defineAsyncComponent(() => import('./aichan.vue')));
 	app.component('MkwUserList', defineAsyncComponent(() => import('./user-list.vue')));
+	app.component('MkwClicker', defineAsyncComponent(() => import('./clicker.vue')));
 }
 
 export const widgets = [
@@ -52,4 +53,5 @@ export const widgets = [
 	'aiscriptApp',
 	'aichan',
 	'userList',
+	'clicker',
 ];

From 416dcf884db0be69693a078d0a87116399028f63 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 17:42:51 +0900
Subject: [PATCH 29/68] :art:

---
 packages/frontend/src/pages/user/activity.notes.vue | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/packages/frontend/src/pages/user/activity.notes.vue b/packages/frontend/src/pages/user/activity.notes.vue
index f2103b152c..8763997f8e 100644
--- a/packages/frontend/src/pages/user/activity.notes.vue
+++ b/packages/frontend/src/pages/user/activity.notes.vue
@@ -86,10 +86,10 @@ async function renderChart() {
 		type: 'bar',
 		data: {
 			datasets: [
-				makeDataset('Normal', format(raw.diffs.normal).slice().reverse(), { backgroundColor: colorNormal }),
-				makeDataset('Reply', format(raw.diffs.reply).slice().reverse(), { backgroundColor: colorReply }),
-				makeDataset('Renote', format(raw.diffs.renote).slice().reverse(), { backgroundColor: colorRenote }),
 				makeDataset('File', format(raw.diffs.withFile).slice().reverse(), { backgroundColor: colorFile }),
+				makeDataset('Renote', format(raw.diffs.renote).slice().reverse(), { backgroundColor: colorRenote }),
+				makeDataset('Reply', format(raw.diffs.reply).slice().reverse(), { backgroundColor: colorReply }),
+				makeDataset('Normal', format(raw.diffs.normal).slice().reverse(), { backgroundColor: colorNormal }),
 			],
 		},
 		options: {

From 4c8dbcc20d3d8ca5ab998e8f0bd948be5e0dac84 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 17:44:24 +0900
Subject: [PATCH 30/68] 13.0.0-beta.31

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 46e96d8716..277389de52 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "misskey",
-	"version": "13.0.0-beta.30",
+	"version": "13.0.0-beta.31",
 	"codename": "indigo",
 	"repository": {
 		"type": "git",

From 809400ff23df8b42dedba3acf9170426e79bb4d5 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 18:08:43 +0900
Subject: [PATCH 31/68] Update CHANGELOG.md

---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1a911f63b7..a5db9bc963 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,7 +12,7 @@ You should also include the user name that made the change.
 ## 13.0.0 (unreleased)
 
 ### TL;DR
-- New features (Play, new widgets, new charts, etc)
+- New features (Play, new widgets, new charts, 🍪👈, etc)
 - Rewriten backend
 - Better performance (backend and frontend)
 - Various usability improvements

From 8977d87021f83b3f35698a267fd37cf84cb70f66 Mon Sep 17 00:00:00 2001
From: marihachi <marihachi0620@gmail.com>
Date: Sun, 8 Jan 2023 19:59:05 +0900
Subject: [PATCH 32/68] fix typo (#9492)

---
 packages/frontend/src/pages/user/activity.pv.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/packages/frontend/src/pages/user/activity.pv.vue b/packages/frontend/src/pages/user/activity.pv.vue
index 4be6978da0..d23f89a31e 100644
--- a/packages/frontend/src/pages/user/activity.pv.vue
+++ b/packages/frontend/src/pages/user/activity.pv.vue
@@ -89,7 +89,7 @@ async function renderChart() {
 				makeDataset('UPV (user)', format(raw.upv.user).slice().reverse(), { backgroundColor: colorUser, stack: 'u' }),
 				makeDataset('UPV (visitor)', format(raw.upv.visitor).slice().reverse(), { backgroundColor: colorVisitor, stack: 'u' }),
 				makeDataset('NPV (user)', format(raw.pv.user).slice().reverse(), { backgroundColor: colorUser2, stack: 'n' }),
-				makeDataset('UPV (visitor)', format(raw.pv.visitor).slice().reverse(), { backgroundColor: colorVisitor2, stack: 'n' }),
+				makeDataset('NPV (visitor)', format(raw.pv.visitor).slice().reverse(), { backgroundColor: colorVisitor2, stack: 'n' }),
 			],
 		},
 		options: {

From 1d7e0293a8beb68aadaf4d0870822d12cbb45d64 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 20:02:07 +0900
Subject: [PATCH 33/68] fix following chart

---
 .../frontend/src/pages/user/activity.following.vue  | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/packages/frontend/src/pages/user/activity.following.vue b/packages/frontend/src/pages/user/activity.following.vue
index b7a51d5b69..500995e392 100644
--- a/packages/frontend/src/pages/user/activity.following.vue
+++ b/packages/frontend/src/pages/user/activity.following.vue
@@ -34,7 +34,7 @@ const chartEl = $shallowRef<HTMLCanvasElement>(null);
 let legendEl = $shallowRef<InstanceType<typeof MkChartLegend>>();
 const now = new Date();
 let chartInstance: Chart = null;
-const chartLimit = 50;
+const chartLimit = 30;
 let fetching = $ref(true);
 
 const { handler: externalTooltipHandler } = useChartTooltip();
@@ -77,7 +77,8 @@ async function renderChart() {
 			borderWidth: 0,
 			borderJoinStyle: 'round',
 			borderRadius: 4,
-			barPercentage: 0.9,
+			barPercentage: 0.7,
+			categoryPercentage: 0.7,
 			fill: true,
 		} satisfies ChartDataset, extra);
 	}
@@ -86,10 +87,10 @@ async function renderChart() {
 		type: 'bar',
 		data: {
 			datasets: [
-				makeDataset('Follow (local)', format(raw.local.followings.inc).slice().reverse(), { backgroundColor: colorFollowLocal }),
-				makeDataset('Follow (remote)', format(raw.remote.followings.inc).slice().reverse(), { backgroundColor: colorFollowRemote }),
-				makeDataset('Followed (local)', format(raw.local.followers.inc).slice().reverse(), { backgroundColor: colorFollowedLocal }),
-				makeDataset('Followed (remote)', format(raw.remote.followers.inc).slice().reverse(), { backgroundColor: colorFollowedRemote }),
+				makeDataset('Follow (local)', format(raw.local.followings.inc).slice().reverse(), { backgroundColor: colorFollowLocal, stack: 'follow' }),
+				makeDataset('Follow (remote)', format(raw.remote.followings.inc).slice().reverse(), { backgroundColor: colorFollowRemote, stack: 'follow' }),
+				makeDataset('Followed (local)', format(raw.local.followers.inc).slice().reverse(), { backgroundColor: colorFollowedLocal, stack: 'followed' }),
+				makeDataset('Followed (remote)', format(raw.remote.followers.inc).slice().reverse(), { backgroundColor: colorFollowedRemote, stack: 'followed' }),
 			],
 		},
 		options: {

From 5d13e2744fc6fb8ca0f79223ed06d7d5c25b9555 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 20:21:32 +0900
Subject: [PATCH 34/68] :art:

---
 packages/frontend/src/directives/click-anime.ts | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/packages/frontend/src/directives/click-anime.ts b/packages/frontend/src/directives/click-anime.ts
index 3d070177bd..5cd54c7323 100644
--- a/packages/frontend/src/directives/click-anime.ts
+++ b/packages/frontend/src/directives/click-anime.ts
@@ -12,7 +12,6 @@ export default {
 		target.classList.add('_anime_bounce_standBy');
 
 		el.addEventListener('mousedown', () => {
-			target.classList.remove('_anime_bounce_ready');
 			target.classList.remove('_anime_bounce');
 
 			target.classList.add('_anime_bounce_standBy');
@@ -25,10 +24,10 @@ export default {
 
 		el.addEventListener('click', () => {
 			target.classList.add('_anime_bounce');
+			target.classList.remove('_anime_bounce_ready');
 		});
 
 		el.addEventListener('animationend', () => {
-			target.classList.remove('_anime_bounce_ready');
 			target.classList.remove('_anime_bounce');
 			target.classList.add('_anime_bounce_standBy');
 		});

From 7ed905f76b1a7c3e9a60a777bed9a17803455dd9 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 20:30:19 +0900
Subject: [PATCH 35/68] :cookie: cps

---
 .../frontend/src/components/MkClickerGame.vue | 20 +++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/packages/frontend/src/components/MkClickerGame.vue b/packages/frontend/src/components/MkClickerGame.vue
index 6ae202cb63..6d0b2236b4 100644
--- a/packages/frontend/src/components/MkClickerGame.vue
+++ b/packages/frontend/src/components/MkClickerGame.vue
@@ -1,6 +1,7 @@
 <template>
 <div>
 	<div v-if="game.ready" :class="$style.game">
+		<div :class="$style.cps" class="">{{ number(cps) }}cps</div>
 		<div :class="$style.count" class=""><i class="ti ti-cookie" style="font-size: 70%;"></i> {{ number(cookies) }}</div>
 		<button v-click-anime class="_button" :class="$style.button" @click="onClick">
 			<img src="/client-assets/cookie.png" :class="$style.img">
@@ -25,6 +26,8 @@ defineProps<{
 
 const saveData = game.saveData;
 const cookies = computed(() => saveData.value?.cookies);
+let cps = $ref(0);
+let prevCookies = $ref(0);
 
 function onClick(ev: MouseEvent) {
 	saveData.value!.cookies++;
@@ -35,6 +38,15 @@ function onClick(ev: MouseEvent) {
 	os.popup(MkPlusOneEffect, { x, y }, {}, 'end');
 }
 
+useInterval(() => {
+	const diff = saveData.value!.cookies - prevCookies;
+	cps = diff;
+	prevCookies = saveData.value!.cookies;
+}, 1000, {
+	immediate: false,
+	afterMounted: true,
+});
+
 useInterval(game.save, 1000 * 5, {
 	immediate: false,
 	afterMounted: true,
@@ -42,6 +54,7 @@ useInterval(game.save, 1000 * 5, {
 
 onMounted(async () => {
 	await game.load();
+	prevCookies = saveData.value!.cookies;
 });
 
 onUnmounted(() => {
@@ -55,6 +68,13 @@ onUnmounted(() => {
 	text-align: center;
 }
 
+.cps {
+	position: absolute;
+	top: 12px;
+	left: 12px;
+	opacity: 0.5;
+}
+
 .count {
 	font-size: 1.3em;
 	margin-bottom: 6px;

From 10e526ba5682fef9488d1d38ba5dfcda38619673 Mon Sep 17 00:00:00 2001
From: MeiMei <30769358+mei23@users.noreply.github.com>
Date: Sun, 8 Jan 2023 20:32:17 +0900
Subject: [PATCH 36/68] fix: Escape SQL LIKE (#9493)

* SQL LIKE escape

* CHANGELOG
---
 CHANGELOG.md                                          |  1 +
 packages/backend/src/misc/sql-like-escape.ts          |  3 +++
 .../server/api/endpoints/admin/emoji/list-remote.ts   |  3 ++-
 .../src/server/api/endpoints/admin/emoji/list.ts      |  3 ++-
 .../src/server/api/endpoints/admin/show-users.ts      |  3 ++-
 .../src/server/api/endpoints/federation/instances.ts  |  3 ++-
 .../src/server/api/endpoints/hashtags/search.ts       |  3 ++-
 .../backend/src/server/api/endpoints/notes/search.ts  |  3 ++-
 .../endpoints/users/search-by-username-and-host.ts    | 11 ++++++-----
 .../backend/src/server/api/endpoints/users/search.ts  |  9 +++++----
 10 files changed, 27 insertions(+), 15 deletions(-)
 create mode 100644 packages/backend/src/misc/sql-like-escape.ts

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a5db9bc963..d19545c7d9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -99,6 +99,7 @@ You should also include the user name that made the change.
 - Server: アンテナの作成数上限を追加 @syuilo
 - Server: pages/likeのエラーIDが重複しているのを修正 @syuilo
 - Server: pages/updateのパラメータによってはsummaryの値が更新されないのを修正 @syuilo
+- Server: Escape SQL LIKE @mei23
 - Client: case insensitive emoji search @saschanaz
 - Client: InAppウィンドウが操作できなくなることがあるのを修正 @tamaina
 - Client: use proxied image for instance icon @syuilo
diff --git a/packages/backend/src/misc/sql-like-escape.ts b/packages/backend/src/misc/sql-like-escape.ts
new file mode 100644
index 0000000000..8470dca3de
--- /dev/null
+++ b/packages/backend/src/misc/sql-like-escape.ts
@@ -0,0 +1,3 @@
+export function sqlLikeEscape(s: string) {
+	return s.replace(/([%_])/g, '\\$1');
+}
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 c03d27878c..ed60efd7b4 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
@@ -5,6 +5,7 @@ import { QueryService } from '@/core/QueryService.js';
 import { UtilityService } from '@/core/UtilityService.js';
 import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
 import { DI } from '@/di-symbols.js';
+import { sqlLikeEscape } from '@/misc/sql-like-escape';
 
 export const meta = {
 	tags: ['admin'],
@@ -92,7 +93,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			}
 
 			if (ps.query) {
-				q.andWhere('emoji.name like :query', { query: '%' + ps.query + '%' });
+				q.andWhere('emoji.name like :query', { query: '%' + sqlLikeEscape(ps.query) + '%' });
 			}
 
 			const emojis = await q
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 271b142126..f357e45a52 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts
@@ -5,6 +5,7 @@ import type { Emoji } from '@/models/entities/Emoji.js';
 import { QueryService } from '@/core/QueryService.js';
 import { DI } from '@/di-symbols.js';
 import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
+//import { sqlLikeEscape } from '@/misc/sql-like-escape';
 
 export const meta = {
 	tags: ['admin'],
@@ -82,7 +83,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			let emojis: Emoji[];
 
 			if (ps.query) {
-				//q.andWhere('emoji.name ILIKE :q', { q: `%${ps.query}%` });
+				//q.andWhere('emoji.name ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` });
 				//const emojis = await q.take(ps.limit).getMany();
 
 				emojis = await q.getMany();
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 33e1be8041..722e284dde 100644
--- a/packages/backend/src/server/api/endpoints/admin/show-users.ts
+++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts
@@ -3,6 +3,7 @@ import type { UsersRepository } from '@/models/index.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { DI } from '@/di-symbols.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { sqlLikeEscape } from '@/misc/sql-like-escape';
 
 export const meta = {
 	tags: ['admin'],
@@ -68,7 +69,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			}
 
 			if (ps.username) {
-				query.andWhere('user.usernameLower like :username', { username: ps.username.toLowerCase() + '%' });
+				query.andWhere('user.usernameLower like :username', { username: sqlLikeEscape(ps.username.toLowerCase()) + '%' });
 			}
 
 			if (ps.hostname) {
diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts
index 5e2f204661..726979309f 100644
--- a/packages/backend/src/server/api/endpoints/federation/instances.ts
+++ b/packages/backend/src/server/api/endpoints/federation/instances.ts
@@ -4,6 +4,7 @@ import type { InstancesRepository } from '@/models/index.js';
 import { InstanceEntityService } from '@/core/entities/InstanceEntityService.js';
 import { MetaService } from '@/core/MetaService.js';
 import { DI } from '@/di-symbols.js';
+import { sqlLikeEscape } from '@/misc/sql-like-escape';
 
 export const meta = {
 	tags: ['federation'],
@@ -120,7 +121,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			}
 
 			if (ps.host) {
-				query.andWhere('instance.host like :host', { host: '%' + ps.host.toLowerCase() + '%' });
+				query.andWhere('instance.host like :host', { host: '%' + sqlLikeEscape(ps.host.toLowerCase()) + '%' });
 			}
 
 			const instances = await query.take(ps.limit).skip(ps.offset).getMany();
diff --git a/packages/backend/src/server/api/endpoints/hashtags/search.ts b/packages/backend/src/server/api/endpoints/hashtags/search.ts
index 7f787ea38f..6c56ef5da2 100644
--- a/packages/backend/src/server/api/endpoints/hashtags/search.ts
+++ b/packages/backend/src/server/api/endpoints/hashtags/search.ts
@@ -2,6 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import type { HashtagsRepository } from '@/models/index.js';
 import { DI } from '@/di-symbols.js';
+import { sqlLikeEscape } from '@/misc/sql-like-escape';
 
 export const meta = {
 	tags: ['hashtags'],
@@ -37,7 +38,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const hashtags = await this.hashtagsRepository.createQueryBuilder('tag')
-				.where('tag.name like :q', { q: ps.query.toLowerCase() + '%' })
+				.where('tag.name like :q', { q: sqlLikeEscape(ps.query.toLowerCase()) + '%' })
 				.orderBy('tag.count', 'DESC')
 				.groupBy('tag.id')
 				.take(ps.limit)
diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts
index 27b477e141..02701ffe1e 100644
--- a/packages/backend/src/server/api/endpoints/notes/search.ts
+++ b/packages/backend/src/server/api/endpoints/notes/search.ts
@@ -6,6 +6,7 @@ import { QueryService } from '@/core/QueryService.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import type { Config } from '@/config.js';
 import { DI } from '@/di-symbols.js';
+import { sqlLikeEscape } from '@/misc/sql-like-escape';
 
 export const meta = {
 	tags: ['notes'],
@@ -70,7 +71,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			}
 
 			query
-				.andWhere('note.text ILIKE :q', { q: `%${ps.query}%` })
+				.andWhere('note.text ILIKE :q', { q: `%${ sqlLikeEscape(ps.query) }%` })
 				.innerJoinAndSelect('note.user', 'user')
 				.leftJoinAndSelect('user.avatar', 'avatar')
 				.leftJoinAndSelect('user.banner', 'banner')
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 f13df3ee9d..029b1e91c3 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
@@ -6,6 +6,7 @@ import type { User } from '@/models/entities/User.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { DI } from '@/di-symbols.js';
+import { sqlLikeEscape } from '@/misc/sql-like-escape';
 
 export const meta = {
 	tags: ['users'],
@@ -59,10 +60,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			if (ps.host) {
 				const q = this.usersRepository.createQueryBuilder('user')
 					.where('user.isSuspended = FALSE')
-					.andWhere('user.host LIKE :host', { host: ps.host.toLowerCase() + '%' });
+					.andWhere('user.host LIKE :host', { host: sqlLikeEscape(ps.host.toLowerCase()) + '%' });
 
 				if (ps.username) {
-					q.andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' });
+					q.andWhere('user.usernameLower LIKE :username', { username: sqlLikeEscape(ps.username.toLowerCase()) + '%' });
 				}
 
 				q.andWhere('user.updatedAt IS NOT NULL');
@@ -83,7 +84,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 						.where(`user.id IN (${ followingQuery.getQuery() })`)
 						.andWhere('user.id != :meId', { meId: me.id })
 						.andWhere('user.isSuspended = FALSE')
-						.andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' })
+						.andWhere('user.usernameLower LIKE :username', { username: sqlLikeEscape(ps.username.toLowerCase()) + '%' })
 						.andWhere(new Brackets(qb => { qb
 							.where('user.updatedAt IS NULL')
 							.orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
@@ -101,7 +102,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 							.where(`user.id NOT IN (${ followingQuery.getQuery() })`)
 							.andWhere('user.id != :meId', { meId: me.id })
 							.andWhere('user.isSuspended = FALSE')
-							.andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' })
+							.andWhere('user.usernameLower LIKE :username', { username: sqlLikeEscape(ps.username.toLowerCase()) + '%' })
 							.andWhere('user.updatedAt IS NOT NULL');
 
 						otherQuery.setParameters(followingQuery.getParameters());
@@ -116,7 +117,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				} else {
 					users = await this.usersRepository.createQueryBuilder('user')
 						.where('user.isSuspended = FALSE')
-						.andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' })
+						.andWhere('user.usernameLower LIKE :username', { username: sqlLikeEscape(ps.username.toLowerCase()) + '%' })
 						.andWhere('user.updatedAt IS NOT NULL')
 						.orderBy('user.updatedAt', 'DESC')
 						.take(ps.limit - users.length)
diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts
index ba07714972..25bd621269 100644
--- a/packages/backend/src/server/api/endpoints/users/search.ts
+++ b/packages/backend/src/server/api/endpoints/users/search.ts
@@ -5,6 +5,7 @@ import type { User } from '@/models/entities/User.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { DI } from '@/di-symbols.js';
+import { sqlLikeEscape } from '@/misc/sql-like-escape';
 
 export const meta = {
 	tags: ['users'],
@@ -57,7 +58,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 
 			if (isUsername) {
 				const usernameQuery = this.usersRepository.createQueryBuilder('user')
-					.where('user.usernameLower LIKE :username', { username: ps.query.replace('@', '').toLowerCase() + '%' })
+					.where('user.usernameLower LIKE :username', { username: sqlLikeEscape(ps.query.replace('@', '').toLowerCase()) + '%' })
 					.andWhere(new Brackets(qb => { qb
 						.where('user.updatedAt IS NULL')
 						.orWhere('user.updatedAt > :activeThreshold', { activeThreshold: activeThreshold });
@@ -78,11 +79,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			} else {
 				const nameQuery = this.usersRepository.createQueryBuilder('user')
 					.where(new Brackets(qb => { 
-						qb.where('user.name ILIKE :query', { query: '%' + ps.query + '%' });
+						qb.where('user.name ILIKE :query', { query: '%' + sqlLikeEscape(ps.query) + '%' });
 
 						// Also search username if it qualifies as username
 						if (this.userEntityService.validateLocalUsername(ps.query)) {
-							qb.orWhere('user.usernameLower LIKE :username', { username: '%' + ps.query.toLowerCase() + '%' });
+							qb.orWhere('user.usernameLower LIKE :username', { username: '%' + sqlLikeEscape(ps.query.toLowerCase()) + '%' });
 						}
 					}))
 					.andWhere(new Brackets(qb => { qb
@@ -106,7 +107,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				if (users.length < ps.limit) {
 					const profQuery = this.userProfilesRepository.createQueryBuilder('prof')
 						.select('prof.userId')
-						.where('prof.description ILIKE :query', { query: '%' + ps.query + '%' });
+						.where('prof.description ILIKE :query', { query: '%' + sqlLikeEscape(ps.query) + '%' });
 
 					if (ps.origin === 'local') {
 						profQuery.andWhere('prof.userHost IS NULL');

From dd78ac089c54847b916e54ff746ad915d9d061b7 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Sun, 8 Jan 2023 20:42:45 +0900
Subject: [PATCH 37/68] :cookie:

---
 .../frontend/src/components/MkClickerGame.vue |  2 ++
 packages/frontend/src/scripts/clicker-game.ts | 24 ++++++++++++++++++-
 2 files changed, 25 insertions(+), 1 deletion(-)

diff --git a/packages/frontend/src/components/MkClickerGame.vue b/packages/frontend/src/components/MkClickerGame.vue
index 6d0b2236b4..03736ac5e4 100644
--- a/packages/frontend/src/components/MkClickerGame.vue
+++ b/packages/frontend/src/components/MkClickerGame.vue
@@ -31,6 +31,8 @@ let prevCookies = $ref(0);
 
 function onClick(ev: MouseEvent) {
 	saveData.value!.cookies++;
+	saveData.value!.totalCookies++;
+	saveData.value!.totalHandmadeCookies++;
 	saveData.value!.clicked++;
 
 	const x = ev.clientX;
diff --git a/packages/frontend/src/scripts/clicker-game.ts b/packages/frontend/src/scripts/clicker-game.ts
index 77206cc8e2..d3b0f9d1e2 100644
--- a/packages/frontend/src/scripts/clicker-game.ts
+++ b/packages/frontend/src/scripts/clicker-game.ts
@@ -4,7 +4,11 @@ import * as os from '@/os';
 type SaveData = {
 	gameVersion: number;
 	cookies: number;
+	totalCookies: number;
+	totalHandmadeCookies: number;
 	clicked: number;
+	achievements: any[];
+	facilities: any[];
 };
 
 export const saveData = ref<SaveData>();
@@ -21,15 +25,33 @@ export async function load() {
 	} catch (err) {
 		if (err.code === 'NO_SUCH_KEY') {
 			saveData.value = {
-				gameVersion: 1,
+				gameVersion: 2,
 				cookies: 0,
+				totalCookies: 0,
+				totalHandmadeCookies: 0,
 				clicked: 0,
+				achievements: [],
+				facilities: [],
 			};
 			save();
 			return;
 		}
 		throw err;
 	}
+
+	// migration
+	if (saveData.value.gameVersion === 1) {
+		saveData.value = {
+			gameVersion: 2,
+			cookies: saveData.value.cookies,
+			totalCookies: saveData.value.cookies,
+			totalHandmadeCookies: saveData.value.cookies,
+			clicked: saveData.value.clicked,
+			achievements: [],
+			facilities: [],
+		};
+		save();
+	}
 }
 
 export async function save() {

From 91ced90fb2e724e77afc3ef59456165ea6dbd5c5 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 08:58:16 +0900
Subject: [PATCH 38/68] fix imports

---
 .../backend/src/server/api/endpoints/admin/emoji/list-remote.ts | 2 +-
 packages/backend/src/server/api/endpoints/admin/emoji/list.ts   | 2 +-
 packages/backend/src/server/api/endpoints/admin/show-users.ts   | 2 +-
 .../backend/src/server/api/endpoints/federation/instances.ts    | 2 +-
 packages/backend/src/server/api/endpoints/hashtags/search.ts    | 2 +-
 packages/backend/src/server/api/endpoints/notes/search.ts       | 2 +-
 .../server/api/endpoints/users/search-by-username-and-host.ts   | 2 +-
 packages/backend/src/server/api/endpoints/users/search.ts       | 2 +-
 8 files changed, 8 insertions(+), 8 deletions(-)

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 ed60efd7b4..08ffc73fea 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
@@ -5,7 +5,7 @@ import { QueryService } from '@/core/QueryService.js';
 import { UtilityService } from '@/core/UtilityService.js';
 import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
 import { DI } from '@/di-symbols.js';
-import { sqlLikeEscape } from '@/misc/sql-like-escape';
+import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
 
 export const meta = {
 	tags: ['admin'],
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 f357e45a52..6bcd4973d6 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts
@@ -5,7 +5,7 @@ import type { Emoji } from '@/models/entities/Emoji.js';
 import { QueryService } from '@/core/QueryService.js';
 import { DI } from '@/di-symbols.js';
 import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
-//import { sqlLikeEscape } from '@/misc/sql-like-escape';
+//import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
 
 export const meta = {
 	tags: ['admin'],
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 722e284dde..5a67cf522a 100644
--- a/packages/backend/src/server/api/endpoints/admin/show-users.ts
+++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts
@@ -3,7 +3,7 @@ import type { UsersRepository } from '@/models/index.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { DI } from '@/di-symbols.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
-import { sqlLikeEscape } from '@/misc/sql-like-escape';
+import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
 
 export const meta = {
 	tags: ['admin'],
diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts
index 726979309f..4c19988eb8 100644
--- a/packages/backend/src/server/api/endpoints/federation/instances.ts
+++ b/packages/backend/src/server/api/endpoints/federation/instances.ts
@@ -4,7 +4,7 @@ import type { InstancesRepository } from '@/models/index.js';
 import { InstanceEntityService } from '@/core/entities/InstanceEntityService.js';
 import { MetaService } from '@/core/MetaService.js';
 import { DI } from '@/di-symbols.js';
-import { sqlLikeEscape } from '@/misc/sql-like-escape';
+import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
 
 export const meta = {
 	tags: ['federation'],
diff --git a/packages/backend/src/server/api/endpoints/hashtags/search.ts b/packages/backend/src/server/api/endpoints/hashtags/search.ts
index 6c56ef5da2..4f5f979767 100644
--- a/packages/backend/src/server/api/endpoints/hashtags/search.ts
+++ b/packages/backend/src/server/api/endpoints/hashtags/search.ts
@@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import type { HashtagsRepository } from '@/models/index.js';
 import { DI } from '@/di-symbols.js';
-import { sqlLikeEscape } from '@/misc/sql-like-escape';
+import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
 
 export const meta = {
 	tags: ['hashtags'],
diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts
index 02701ffe1e..8eb031dfe3 100644
--- a/packages/backend/src/server/api/endpoints/notes/search.ts
+++ b/packages/backend/src/server/api/endpoints/notes/search.ts
@@ -6,7 +6,7 @@ import { QueryService } from '@/core/QueryService.js';
 import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
 import type { Config } from '@/config.js';
 import { DI } from '@/di-symbols.js';
-import { sqlLikeEscape } from '@/misc/sql-like-escape';
+import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
 
 export const meta = {
 	tags: ['notes'],
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 029b1e91c3..95491211bc 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
@@ -6,7 +6,7 @@ import type { User } from '@/models/entities/User.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { DI } from '@/di-symbols.js';
-import { sqlLikeEscape } from '@/misc/sql-like-escape';
+import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
 
 export const meta = {
 	tags: ['users'],
diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts
index 25bd621269..d7a60f0437 100644
--- a/packages/backend/src/server/api/endpoints/users/search.ts
+++ b/packages/backend/src/server/api/endpoints/users/search.ts
@@ -5,7 +5,7 @@ import type { User } from '@/models/entities/User.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
 import { DI } from '@/di-symbols.js';
-import { sqlLikeEscape } from '@/misc/sql-like-escape';
+import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
 
 export const meta = {
 	tags: ['users'],

From 8524e9d73596e28e1e9f6b267bcfc6ea16e3a577 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 09:04:35 +0900
Subject: [PATCH 39/68] tweak client

---
 packages/frontend/src/components/MkNote.vue         | 4 ++--
 packages/frontend/src/components/MkNoteDetailed.vue | 8 ++++----
 packages/frontend/src/components/MkNoteHeader.vue   | 4 ++--
 packages/frontend/src/pages/about.emojis.vue        | 4 ++--
 packages/frontend/src/pages/about.federation.vue    | 4 ++--
 packages/frontend/src/pages/admin/users.vue         | 4 ++--
 packages/frontend/src/widgets/clicker.vue           | 2 +-
 7 files changed, 15 insertions(+), 15 deletions(-)

diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 86f176a693..cb97f19d31 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -27,12 +27,12 @@
 				<i v-if="isMyRenote" class="ti ti-dots dropdownIcon"></i>
 				<MkTime :time="note.createdAt"/>
 			</button>
-			<span v-if="note.visibility !== 'public'" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility[note.visibility]">
+			<span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[note.visibility]">
 				<i v-if="note.visibility === 'home'" class="ti ti-home"></i>
 				<i v-else-if="note.visibility === 'followers'" class="ti ti-lock-open"></i>
 				<i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
 			</span>
-			<span v-if="note.localOnly" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span>
+			<span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span>
 		</div>
 	</div>
 	<article class="article" @contextmenu.stop="onContextmenu">
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index 7c57a64b09..4dc1358b24 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -25,12 +25,12 @@
 				<i v-if="isMyRenote" class="ti ti-dots dropdownIcon"></i>
 				<MkTime :time="note.createdAt"/>
 			</button>
-			<span v-if="note.visibility !== 'public'" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility[note.visibility]">
+			<span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[note.visibility]">
 				<i v-if="note.visibility === 'home'" class="ti ti-home"></i>
 				<i v-else-if="note.visibility === 'followers'" class="ti ti-lock-open"></i>
 				<i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
 			</span>
-			<span v-if="note.localOnly" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span>
+			<span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span>
 		</div>
 	</div>
 	<article class="article" @contextmenu.stop="onContextmenu">
@@ -43,12 +43,12 @@
 					</MkA>
 					<span v-if="appearNote.user.isBot" class="is-bot">bot</span>
 					<div class="info">
-						<span v-if="appearNote.visibility !== 'public'" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility[appearNote.visibility]">
+						<span v-if="appearNote.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[appearNote.visibility]">
 							<i v-if="appearNote.visibility === 'home'" class="ti ti-home"></i>
 							<i v-else-if="appearNote.visibility === 'followers'" class="ti ti-lock-open"></i>
 							<i v-else-if="appearNote.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
 						</span>
-						<span v-if="appearNote.localOnly" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span>
+						<span v-if="appearNote.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span>
 					</div>
 				</div>
 				<div class="username"><MkAcct :user="appearNote.user"/></div>
diff --git a/packages/frontend/src/components/MkNoteHeader.vue b/packages/frontend/src/components/MkNoteHeader.vue
index 61d07f85e5..ddd25e784f 100644
--- a/packages/frontend/src/components/MkNoteHeader.vue
+++ b/packages/frontend/src/components/MkNoteHeader.vue
@@ -9,12 +9,12 @@
 		<MkA class="created-at" :to="notePage(note)">
 			<MkTime :time="note.createdAt"/>
 		</MkA>
-		<span v-if="note.visibility !== 'public'" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility[note.visibility]">
+		<span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[note.visibility]">
 			<i v-if="note.visibility === 'home'" class="ti ti-home"></i>
 			<i v-else-if="note.visibility === 'followers'" class="ti ti-lock-open"></i>
 			<i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
 		</span>
-		<span v-if="note.localOnly" style="{ margin-left: 0.5em; }" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span>
+		<span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span>
 	</div>
 </header>
 </template>
diff --git a/packages/frontend/src/pages/about.emojis.vue b/packages/frontend/src/pages/about.emojis.vue
index 669b6f1264..5a3f58cebe 100644
--- a/packages/frontend/src/pages/about.emojis.vue
+++ b/packages/frontend/src/pages/about.emojis.vue
@@ -19,10 +19,10 @@
 		</div>
 	</MkFolder>
 	
-	<MkFolder v-for="category in customEmojiCategories" :key="category" class="emojis">
+	<MkFolder v-for="category in customEmojiCategories" v-once :key="category" class="emojis">
 		<template #header>{{ category || $ts.other }}</template>
 		<div class="zuvgdzyt">
-			<XEmoji v-for="emoji in customEmojis.filter(e => e.category === category)" v-once :key="emoji.name" class="emoji" :emoji="emoji"/>
+			<XEmoji v-for="emoji in customEmojis.filter(e => e.category === category)" :key="emoji.name" class="emoji" :emoji="emoji"/>
 		</div>
 	</MkFolder>
 </div>
diff --git a/packages/frontend/src/pages/about.federation.vue b/packages/frontend/src/pages/about.federation.vue
index e530bb49dd..a4e449911c 100644
--- a/packages/frontend/src/pages/about.federation.vue
+++ b/packages/frontend/src/pages/about.federation.vue
@@ -36,8 +36,8 @@
 
 	<MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination">
 		<div class="dqokceoi">
-			<MkA v-for="instance in items" v-once :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" class="instance" :to="`/instance-info/${instance.host}`">
-				<MkInstanceCardMini :instance="instance"/>
+			<MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" class="instance" :to="`/instance-info/${instance.host}`">
+				<MkInstanceCardMini v-once :instance="instance"/>
 			</MkA>
 		</div>
 	</MkPagination>
diff --git a/packages/frontend/src/pages/admin/users.vue b/packages/frontend/src/pages/admin/users.vue
index c198dfdb46..445a0a3a71 100644
--- a/packages/frontend/src/pages/admin/users.vue
+++ b/packages/frontend/src/pages/admin/users.vue
@@ -41,8 +41,8 @@
 					</div>
 
 					<MkPagination v-slot="{items}" ref="paginationComponent" :pagination="pagination" class="users">
-						<MkA v-for="user in items" v-once :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" class="user" :to="`/user-info/${user.id}`">
-							<MkUserCardMini :user="user"/>
+						<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" class="user" :to="`/user-info/${user.id}`">
+							<MkUserCardMini v-once :user="user"/>
 						</MkA>
 					</MkPagination>
 				</div>
diff --git a/packages/frontend/src/widgets/clicker.vue b/packages/frontend/src/widgets/clicker.vue
index 77d1777e97..168f05bccd 100644
--- a/packages/frontend/src/widgets/clicker.vue
+++ b/packages/frontend/src/widgets/clicker.vue
@@ -18,7 +18,7 @@ const name = 'clicker';
 const widgetPropsDef = {
 	showHeader: {
 		type: 'boolean' as const,
-		default: true,
+		default: false,
 	},
 };
 

From e633c3b84b96fb1eb0e6cc864c391a00adc20bd8 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 09:41:25 +0900
Subject: [PATCH 40/68] refactor

---
 packages/frontend/src/components/MkAsUi.vue   |   6 +-
 .../src/components/MkFoldableSection.vue      | 154 ++++++++++
 packages/frontend/src/components/MkFolder.vue | 270 ++++++++++--------
 .../src/components/MkInstanceStats.vue        |  18 +-
 .../frontend/src/components/form/folder.vue   | 175 ------------
 packages/frontend/src/pages/about.emojis.vue  |  12 +-
 .../frontend/src/pages/admin/integrations.vue |  14 +-
 .../frontend/src/pages/admin/overview.vue     |  46 +--
 .../frontend/src/pages/admin/security.vue     |  22 +-
 packages/frontend/src/pages/explore.users.vue |  40 +--
 packages/frontend/src/pages/explore.vue       |   4 +-
 packages/frontend/src/pages/flash/flash.vue   |   6 +-
 packages/frontend/src/pages/gallery/index.vue |  10 +-
 .../src/pages/settings/import-export.vue      |  42 +--
 .../frontend/src/pages/settings/privacy.vue   |   6 +-
 .../frontend/src/pages/settings/profile.vue   |  10 +-
 .../frontend/src/pages/settings/sounds.vue    |   6 +-
 .../frontend/src/pages/settings/statusbar.vue |   6 +-
 packages/frontend/src/pages/theme-editor.vue  |  22 +-
 packages/frontend/src/pages/user-info.vue     |  14 +-
 packages/frontend/src/pages/user/activity.vue |  18 +-
 packages/frontend/src/pages/user/home.vue     |   2 +-
 22 files changed, 453 insertions(+), 450 deletions(-)
 create mode 100644 packages/frontend/src/components/MkFoldableSection.vue
 delete mode 100644 packages/frontend/src/components/form/folder.vue

diff --git a/packages/frontend/src/components/MkAsUi.vue b/packages/frontend/src/components/MkAsUi.vue
index b6b49725d2..5844a18633 100644
--- a/packages/frontend/src/components/MkAsUi.vue
+++ b/packages/frontend/src/components/MkAsUi.vue
@@ -33,12 +33,12 @@
 		<option v-for="item in c.items" :key="item.value" :value="item.value">{{ item.text }}</option>
 	</MkSelect>
 	<MkButton v-else-if="c.type === 'postFormButton'" :primary="c.primary" :rounded="c.rounded" :small="size === 'small'" @click="openPostForm">{{ c.text }}</MkButton>
-	<FormFolder v-else-if="c.type === 'folder'" :default-open="c.opened">
+	<MkFolder v-else-if="c.type === 'folder'" :default-open="c.opened">
 		<template #label>{{ c.title }}</template>
 		<template v-for="child in c.children" :key="child">
 			<MkAsUi v-if="!g(child).hidden" :component="g(child)" :components="props.components" :size="size"/>
 		</template>
-	</FormFolder>
+	</MkFolder>
 	<div v-else-if="c.type === 'container'" :class="[$style.container, { [$style.fontSerif]: c.font === 'serif', [$style.fontMonospace]: c.font === 'monospace', [$style.containerCenter]: c.align === 'center' }]" :style="{ 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 }">
 		<template v-for="child in c.children" :key="child">
 			<MkAsUi v-if="!g(child).hidden" :component="g(child)" :components="props.components" :size="size"/>
@@ -56,7 +56,7 @@ import MkSwitch from '@/components/MkSwitch.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
 import MkSelect from '@/components/MkSelect.vue';
 import { AsUiComponent } from '@/scripts/aiscript/ui';
-import FormFolder from '@/components/form/folder.vue';
+import MkFolder from '@/components/MkFolder.vue';
 
 const props = withDefaults(defineProps<{
 	component: AsUiComponent;
diff --git a/packages/frontend/src/components/MkFoldableSection.vue b/packages/frontend/src/components/MkFoldableSection.vue
new file mode 100644
index 0000000000..aa2d9aac25
--- /dev/null
+++ b/packages/frontend/src/components/MkFoldableSection.vue
@@ -0,0 +1,154 @@
+<template>
+<div class="ssazuxis">
+	<header class="_button" :style="{ background: bg }" @click="showBody = !showBody">
+		<div class="title"><div><slot name="header"></slot></div></div>
+		<div class="divider"></div>
+		<button class="_button">
+			<template v-if="showBody"><i class="ti ti-chevron-up"></i></template>
+			<template v-else><i class="ti ti-chevron-down"></i></template>
+		</button>
+	</header>
+	<Transition
+		:name="$store.state.animation ? 'folder-toggle' : ''"
+		@enter="enter"
+		@after-enter="afterEnter"
+		@leave="leave"
+		@after-leave="afterLeave"
+	>
+		<div v-show="showBody">
+			<slot></slot>
+		</div>
+	</Transition>
+</div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+import tinycolor from 'tinycolor2';
+import { miLocalStorage } from '@/local-storage';
+
+const miLocalStoragePrefix = 'ui:folder:' as const;
+
+export default defineComponent({
+	props: {
+		expanded: {
+			type: Boolean,
+			required: false,
+			default: true,
+		},
+		persistKey: {
+			type: String,
+			required: false,
+			default: null,
+		},
+	},
+	data() {
+		return {
+			bg: null,
+			showBody: (this.persistKey && miLocalStorage.getItem(`${miLocalStoragePrefix}${this.persistKey}`)) ? (miLocalStorage.getItem(`${miLocalStoragePrefix}${this.persistKey}`) === 't') : this.expanded,
+		};
+	},
+	watch: {
+		showBody() {
+			if (this.persistKey) {
+				miLocalStorage.setItem(`${miLocalStoragePrefix}${this.persistKey}`, this.showBody ? 't' : 'f');
+			}
+		},
+	},
+	mounted() {
+		function getParentBg(el: Element | null): string {
+			if (el == null || el.tagName === 'BODY') return 'var(--bg)';
+			const bg = el.style.background || el.style.backgroundColor;
+			if (bg) {
+				return bg;
+			} else {
+				return getParentBg(el.parentElement);
+			}
+		}
+		const rawBg = getParentBg(this.$el);
+		const bg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg);
+		bg.setAlpha(0.85);
+		this.bg = bg.toRgbString();
+	},
+	methods: {
+		toggleContent(show: boolean) {
+			this.showBody = show;
+		},
+
+		enter(el) {
+			const elementHeight = el.getBoundingClientRect().height;
+			el.style.height = 0;
+			el.offsetHeight; // reflow
+			el.style.height = elementHeight + 'px';
+		},
+		afterEnter(el) {
+			el.style.height = null;
+		},
+		leave(el) {
+			const elementHeight = el.getBoundingClientRect().height;
+			el.style.height = elementHeight + 'px';
+			el.offsetHeight; // reflow
+			el.style.height = 0;
+		},
+		afterLeave(el) {
+			el.style.height = null;
+		},
+	},
+});
+</script>
+
+<style lang="scss" scoped>
+.folder-toggle-enter-active, .folder-toggle-leave-active {
+	overflow-y: clip;
+	transition: opacity 0.5s, height 0.5s !important;
+}
+.folder-toggle-enter-from {
+	opacity: 0;
+}
+.folder-toggle-leave-to {
+	opacity: 0;
+}
+
+.ssazuxis {
+	position: relative;
+
+	> header {
+		display: flex;
+		position: relative;
+		z-index: 10;
+		position: sticky;
+		top: var(--stickyTop, 0px);
+		padding: var(--x-padding);
+		-webkit-backdrop-filter: var(--blur, blur(8px));
+		backdrop-filter: var(--blur, blur(20px));
+
+		> .title {
+			display: grid;
+			place-content: center;
+			margin: 0;
+			padding: 12px 16px 12px 0;
+		}
+
+		> .divider {
+			flex: 1;
+			margin: auto;
+			height: 1px;
+			background: var(--divider);
+		}
+
+		> button {
+			padding: 12px 0 12px 16px;
+		}
+	}
+}
+
+@container (max-width: 500px) {
+	.ssazuxis {
+		> header {
+			> .title {
+				padding: 8px 10px 8px 0;
+			}
+		}
+	}
+}
+</style>
diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue
index aa2d9aac25..7fb0720d3d 100644
--- a/packages/frontend/src/components/MkFolder.vue
+++ b/packages/frontend/src/components/MkFolder.vue
@@ -1,153 +1,177 @@
 <template>
-<div class="ssazuxis">
-	<header class="_button" :style="{ background: bg }" @click="showBody = !showBody">
-		<div class="title"><div><slot name="header"></slot></div></div>
-		<div class="divider"></div>
-		<button class="_button">
-			<template v-if="showBody"><i class="ti ti-chevron-up"></i></template>
-			<template v-else><i class="ti ti-chevron-down"></i></template>
-		</button>
-	</header>
-	<Transition
-		:name="$store.state.animation ? 'folder-toggle' : ''"
-		@enter="enter"
-		@after-enter="afterEnter"
-		@leave="leave"
-		@after-leave="afterLeave"
-	>
-		<div v-show="showBody">
-			<slot></slot>
-		</div>
-	</Transition>
+<div ref="rootEl" class="dwzlatin" :class="{ opened }">
+	<div class="header _button" @click="toggle">
+		<span class="icon"><slot name="icon"></slot></span>
+		<span class="text"><slot name="label"></slot></span>
+		<span class="right">
+			<span class="text"><slot name="suffix"></slot></span>
+			<i v-if="opened" class="ti ti-chevron-up icon"></i>
+			<i v-else class="ti ti-chevron-down icon"></i>
+		</span>
+	</div>
+	<div v-if="openedAtLeastOnce" class="body" :class="{ bgSame }" :style="{ maxHeight: maxHeight ? `${maxHeight}px` : null }">
+		<Transition
+			:name="$store.state.animation ? 'folder-toggle' : ''"
+			@enter="enter"
+			@after-enter="afterEnter"
+			@leave="leave"
+			@after-leave="afterLeave"
+		>
+			<KeepAlive>
+				<div v-show="opened">
+					<MkSpacer :margin-min="14" :margin-max="22">
+						<slot></slot>
+					</MkSpacer>
+				</div>
+			</KeepAlive>
+		</Transition>
+	</div>
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
-import tinycolor from 'tinycolor2';
-import { miLocalStorage } from '@/local-storage';
+<script lang="ts" setup>
+import { nextTick, onMounted } from 'vue';
 
-const miLocalStoragePrefix = 'ui:folder:' as const;
+const props = withDefaults(defineProps<{
+	defaultOpen: boolean;
+	maxHeight: number | null;
+}>(), {
+	defaultOpen: false,
+	maxHeight: null,
+});
 
-export default defineComponent({
-	props: {
-		expanded: {
-			type: Boolean,
-			required: false,
-			default: true,
-		},
-		persistKey: {
-			type: String,
-			required: false,
-			default: null,
-		},
-	},
-	data() {
-		return {
-			bg: null,
-			showBody: (this.persistKey && miLocalStorage.getItem(`${miLocalStoragePrefix}${this.persistKey}`)) ? (miLocalStorage.getItem(`${miLocalStoragePrefix}${this.persistKey}`) === 't') : this.expanded,
-		};
-	},
-	watch: {
-		showBody() {
-			if (this.persistKey) {
-				miLocalStorage.setItem(`${miLocalStoragePrefix}${this.persistKey}`, this.showBody ? 't' : 'f');
-			}
-		},
-	},
-	mounted() {
-		function getParentBg(el: Element | null): string {
-			if (el == null || el.tagName === 'BODY') return 'var(--bg)';
-			const bg = el.style.background || el.style.backgroundColor;
-			if (bg) {
-				return bg;
-			} else {
-				return getParentBg(el.parentElement);
-			}
-		}
-		const rawBg = getParentBg(this.$el);
-		const bg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg);
-		bg.setAlpha(0.85);
-		this.bg = bg.toRgbString();
-	},
-	methods: {
-		toggleContent(show: boolean) {
-			this.showBody = show;
-		},
+const getBgColor = (el: HTMLElement) => {
+	const style = window.getComputedStyle(el);
+	if (style.backgroundColor && !['rgba(0, 0, 0, 0)', 'rgba(0,0,0,0)', 'transparent'].includes(style.backgroundColor)) {
+		return style.backgroundColor;
+	} else {
+		return el.parentElement ? getBgColor(el.parentElement) : 'transparent';
+	}
+};
 
-		enter(el) {
-			const elementHeight = el.getBoundingClientRect().height;
-			el.style.height = 0;
-			el.offsetHeight; // reflow
-			el.style.height = elementHeight + 'px';
-		},
-		afterEnter(el) {
-			el.style.height = null;
-		},
-		leave(el) {
-			const elementHeight = el.getBoundingClientRect().height;
-			el.style.height = elementHeight + 'px';
-			el.offsetHeight; // reflow
-			el.style.height = 0;
-		},
-		afterLeave(el) {
-			el.style.height = null;
-		},
-	},
+let rootEl = $ref<HTMLElement>();
+let bgSame = $ref(false);
+let opened = $ref(props.defaultOpen);
+let openedAtLeastOnce = $ref(props.defaultOpen);
+
+function enter(el) {
+	const elementHeight = el.getBoundingClientRect().height;
+	el.style.height = 0;
+	el.offsetHeight; // reflow
+	el.style.height = Math.min(elementHeight, props.maxHeight ?? Infinity) + 'px';
+}
+
+function afterEnter(el) {
+	el.style.height = null;
+}
+
+function leave(el) {
+	const elementHeight = el.getBoundingClientRect().height;
+	el.style.height = elementHeight + 'px';
+	el.offsetHeight; // reflow
+	el.style.height = 0;
+}
+
+function afterLeave(el) {
+	el.style.height = null;
+}
+
+function toggle() {
+	if (!opened) {
+		openedAtLeastOnce = true;
+	}
+
+	nextTick(() => {
+		opened = !opened;
+	});
+}
+
+onMounted(() => {
+	const computedStyle = getComputedStyle(document.documentElement);
+	const parentBg = getBgColor(rootEl.parentElement);
+	const myBg = computedStyle.getPropertyValue('--panel');
+	bgSame = parentBg === myBg;
 });
 </script>
 
 <style lang="scss" scoped>
 .folder-toggle-enter-active, .folder-toggle-leave-active {
 	overflow-y: clip;
-	transition: opacity 0.5s, height 0.5s !important;
+	transition: opacity 0.3s, height 0.3s, transform 0.3s !important;
 }
-.folder-toggle-enter-from {
-	opacity: 0;
-}
-.folder-toggle-leave-to {
+.folder-toggle-enter-from, .folder-toggle-leave-to {
 	opacity: 0;
 }
 
-.ssazuxis {
-	position: relative;
+.dwzlatin {
+	display: block;
 
-	> header {
+	> .header {
 		display: flex;
-		position: relative;
-		z-index: 10;
-		position: sticky;
-		top: var(--stickyTop, 0px);
-		padding: var(--x-padding);
-		-webkit-backdrop-filter: var(--blur, blur(8px));
-		backdrop-filter: var(--blur, blur(20px));
+		align-items: center;
+		width: 100%;
+		box-sizing: border-box;
+		padding: 10px 14px 10px 14px;
+		background: var(--buttonBg);
+		border-radius: 6px;
 
-		> .title {
-			display: grid;
-			place-content: center;
-			margin: 0;
-			padding: 12px 16px 12px 0;
+		&:hover {
+			text-decoration: none;
+			background: var(--buttonHoverBg);
 		}
 
-		> .divider {
-			flex: 1;
-			margin: auto;
-			height: 1px;
-			background: var(--divider);
+		&.active {
+			color: var(--accent);
+			background: var(--buttonHoverBg);
 		}
 
-		> button {
-			padding: 12px 0 12px 16px;
+		> .icon {
+			margin-right: 0.75em;
+			flex-shrink: 0;
+			text-align: center;
+			opacity: 0.8;
+
+			&:empty {
+				display: none;
+
+				& + .text {
+					padding-left: 4px;
+				}
+			}
+		}
+
+		> .text {
+			white-space: nowrap;
+			text-overflow: ellipsis;
+			overflow: hidden;
+			padding-right: 12px;
+		}
+
+		> .right {
+			margin-left: auto;
+			opacity: 0.7;
+			white-space: nowrap;
+
+			> .text:not(:empty) {
+				margin-right: 0.75em;
+			}
 		}
 	}
-}
 
-@container (max-width: 500px) {
-	.ssazuxis {
-		> header {
-			> .title {
-				padding: 8px 10px 8px 0;
-			}
+	> .body {
+		background: var(--panel);
+		border-radius: 0 0 6px 6px;
+		container-type: inline-size;
+		overflow: auto;
+
+		&.bgSame {
+			background: var(--bg);
+		}
+	}
+
+	&.opened {
+		> .header {
+			border-radius: 6px 6px 0 0;
 		}
 	}
 }
diff --git a/packages/frontend/src/components/MkInstanceStats.vue b/packages/frontend/src/components/MkInstanceStats.vue
index 05aea5c731..0f87fef6b1 100644
--- a/packages/frontend/src/components/MkInstanceStats.vue
+++ b/packages/frontend/src/components/MkInstanceStats.vue
@@ -1,6 +1,6 @@
 <template>
 <div :class="$style.root">
-	<MkFolder class="item">
+	<MkFoldableSection class="item">
 		<template #header>Chart</template>
 		<div :class="$style.chart">
 			<div class="selects">
@@ -34,9 +34,9 @@
 				<MkChart :src="chartSrc" :span="chartSpan" :limit="chartLimit" :detailed="true"></MkChart>
 			</div>
 		</div>
-	</MkFolder>
+	</MkFoldableSection>
 
-	<MkFolder class="item">
+	<MkFoldableSection class="item">
 		<template #header>Active users heatmap</template>
 		<MkSelect v-model="heatmapSrc" style="margin: 0 0 12px 0;">
 			<option value="active-users">Active users</option>
@@ -48,16 +48,16 @@
 		<div class="_panel" :class="$style.heatmap">
 			<MkHeatmap :src="heatmapSrc"/>
 		</div>
-	</MkFolder>
+	</MkFoldableSection>
 
-	<MkFolder class="item">
+	<MkFoldableSection class="item">
 		<template #header>Retention rate</template>
 		<div class="_panel" :class="$style.retention">
 			<MkRetentionHeatmap/>
 		</div>
-	</MkFolder>
+	</MkFoldableSection>
 
-	<MkFolder class="item">
+	<MkFoldableSection class="item">
 		<template #header>Federation</template>
 		<div :class="$style.federation">
 			<div class="pies">
@@ -71,7 +71,7 @@
 				</div>
 			</div>
 		</div>
-	</MkFolder>
+	</MkFoldableSection>
 </div>
 </template>
 
@@ -84,7 +84,7 @@ import { useChartTooltip } from '@/scripts/use-chart-tooltip';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
 import MkHeatmap from '@/components/MkHeatmap.vue';
-import MkFolder from '@/components/MkFolder.vue';
+import MkFoldableSection from '@/components/MkFoldableSection.vue';
 import MkRetentionHeatmap from '@/components/MkRetentionHeatmap.vue';
 import { initChart } from '@/scripts/init-chart';
 
diff --git a/packages/frontend/src/components/form/folder.vue b/packages/frontend/src/components/form/folder.vue
deleted file mode 100644
index 961f1c1cac..0000000000
--- a/packages/frontend/src/components/form/folder.vue
+++ /dev/null
@@ -1,175 +0,0 @@
-<template>
-<div ref="rootEl" class="dwzlatin" :class="{ opened }">
-	<div class="header _button" @click="toggle">
-		<span class="icon"><slot name="icon"></slot></span>
-		<span class="text"><slot name="label"></slot></span>
-		<span class="right">
-			<span class="text"><slot name="suffix"></slot></span>
-			<i v-if="opened" class="ti ti-chevron-up icon"></i>
-			<i v-else class="ti ti-chevron-down icon"></i>
-		</span>
-	</div>
-	<div v-if="openedAtLeastOnce" class="body" :class="{ bgSame }">
-		<Transition
-			:name="$store.state.animation ? 'folder-toggle' : ''"
-			@enter="enter"
-			@after-enter="afterEnter"
-			@leave="leave"
-			@after-leave="afterLeave"
-		>
-			<KeepAlive>
-				<div v-show="opened">
-					<MkSpacer :margin-min="14" :margin-max="22">
-						<slot></slot>
-					</MkSpacer>
-				</div>
-			</KeepAlive>
-		</Transition>
-	</div>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { nextTick, onMounted } from 'vue';
-
-const props = withDefaults(defineProps<{
-	defaultOpen: boolean;
-}>(), {
-	defaultOpen: false,
-});
-
-const getBgColor = (el: HTMLElement) => {
-	const style = window.getComputedStyle(el);
-	if (style.backgroundColor && !['rgba(0, 0, 0, 0)', 'rgba(0,0,0,0)', 'transparent'].includes(style.backgroundColor)) {
-		return style.backgroundColor;
-	} else {
-		return el.parentElement ? getBgColor(el.parentElement) : 'transparent';
-	}
-};
-
-let rootEl = $ref<HTMLElement>();
-let bgSame = $ref(false);
-let opened = $ref(props.defaultOpen);
-let openedAtLeastOnce = $ref(props.defaultOpen);
-
-function enter(el) {
-	const elementHeight = el.getBoundingClientRect().height;
-	el.style.height = 0;
-	el.offsetHeight; // reflow
-	el.style.height = elementHeight + 'px';
-}
-
-function afterEnter(el) {
-	el.style.height = null;
-}
-
-function leave(el) {
-	const elementHeight = el.getBoundingClientRect().height;
-	el.style.height = elementHeight + 'px';
-	el.offsetHeight; // reflow
-	el.style.height = 0;
-}
-
-function afterLeave(el) {
-	el.style.height = null;
-}
-
-function toggle() {
-	if (!opened) {
-		openedAtLeastOnce = true;
-	}
-
-	nextTick(() => {
-		opened = !opened;
-	});
-}
-
-onMounted(() => {
-	const computedStyle = getComputedStyle(document.documentElement);
-	const parentBg = getBgColor(rootEl.parentElement);
-	const myBg = computedStyle.getPropertyValue('--panel');
-	bgSame = parentBg === myBg;
-});
-</script>
-
-<style lang="scss" scoped>
-.folder-toggle-enter-active, .folder-toggle-leave-active {
-	overflow-y: clip;
-	transition: opacity 0.3s, height 0.3s, transform 0.3s !important;
-}
-.folder-toggle-enter-from, .folder-toggle-leave-to {
-	opacity: 0;
-}
-
-.dwzlatin {
-	display: block;
-
-	> .header {
-		display: flex;
-		align-items: center;
-		width: 100%;
-		box-sizing: border-box;
-		padding: 10px 14px 10px 14px;
-		background: var(--buttonBg);
-		border-radius: 6px;
-
-		&:hover {
-			text-decoration: none;
-			background: var(--buttonHoverBg);
-		}
-
-		&.active {
-			color: var(--accent);
-			background: var(--buttonHoverBg);
-		}
-
-		> .icon {
-			margin-right: 0.75em;
-			flex-shrink: 0;
-			text-align: center;
-			opacity: 0.8;
-
-			&:empty {
-				display: none;
-
-				& + .text {
-					padding-left: 4px;
-				}
-			}
-		}
-
-		> .text {
-			white-space: nowrap;
-			text-overflow: ellipsis;
-			overflow: hidden;
-			padding-right: 12px;
-		}
-
-		> .right {
-			margin-left: auto;
-			opacity: 0.7;
-			white-space: nowrap;
-
-			> .text:not(:empty) {
-				margin-right: 0.75em;
-			}
-		}
-	}
-
-	> .body {
-		background: var(--panel);
-		border-radius: 0 0 6px 6px;
-		container-type: inline-size;
-
-		&.bgSame {
-			background: var(--bg);
-		}
-	}
-
-	&.opened {
-		> .header {
-			border-radius: 6px 6px 0 0;
-		}
-	}
-}
-</style>
diff --git a/packages/frontend/src/pages/about.emojis.vue b/packages/frontend/src/pages/about.emojis.vue
index 5a3f58cebe..e15a2b1bdf 100644
--- a/packages/frontend/src/pages/about.emojis.vue
+++ b/packages/frontend/src/pages/about.emojis.vue
@@ -12,19 +12,19 @@
 		-->
 	</div>
 
-	<MkFolder v-if="searchEmojis" class="emojis">
+	<MkFoldableSection v-if="searchEmojis" class="emojis">
 		<template #header>{{ $ts.searchResult }}</template>
 		<div class="zuvgdzyt">
 			<XEmoji v-for="emoji in searchEmojis" :key="emoji.name" class="emoji" :emoji="emoji"/>
 		</div>
-	</MkFolder>
+	</MkFoldableSection>
 	
-	<MkFolder v-for="category in customEmojiCategories" v-once :key="category" class="emojis">
+	<MkFoldableSection v-for="category in customEmojiCategories" v-once :key="category" class="emojis">
 		<template #header>{{ category || $ts.other }}</template>
 		<div class="zuvgdzyt">
 			<XEmoji v-for="emoji in customEmojis.filter(e => e.category === category)" :key="emoji.name" class="emoji" :emoji="emoji"/>
 		</div>
-	</MkFolder>
+	</MkFoldableSection>
 </div>
 </template>
 
@@ -34,7 +34,7 @@ import XEmoji from './emojis.emoji.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkSelect from '@/components/MkSelect.vue';
-import MkFolder from '@/components/MkFolder.vue';
+import MkFoldableSection from '@/components/MkFoldableSection.vue';
 import MkTab from '@/components/MkTab.vue';
 import * as os from '@/os';
 import { emojiCategories, emojiTags } from '@/instance';
@@ -44,7 +44,7 @@ export default defineComponent({
 		MkButton,
 		MkInput,
 		MkSelect,
-		MkFolder,
+		MkFoldableSection,
 		MkTab,
 		XEmoji,
 	},
diff --git a/packages/frontend/src/pages/admin/integrations.vue b/packages/frontend/src/pages/admin/integrations.vue
index e319d48617..6888a492f6 100644
--- a/packages/frontend/src/pages/admin/integrations.vue
+++ b/packages/frontend/src/pages/admin/integrations.vue
@@ -4,24 +4,24 @@
 	<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
 		<FormSuspense :p="init">
 			<div class="_gaps_m">
-				<FormFolder>
+				<MkFolder>
 					<template #icon><i class="ti ti-brand-twitter"></i></template>
 					<template #label>Twitter</template>
 					<template #suffix>{{ enableTwitterIntegration ? i18n.ts.enabled : i18n.ts.disabled }}</template>
 					<XTwitter/>
-				</FormFolder>
-				<FormFolder>
+				</MkFolder>
+				<MkFolder>
 					<template #icon><i class="ti ti-brand-github"></i></template>
 					<template #label>GitHub</template>
 					<template #suffix>{{ enableGithubIntegration ? i18n.ts.enabled : i18n.ts.disabled }}</template>
 					<XGithub/>
-				</FormFolder>
-				<FormFolder>
+				</MkFolder>
+				<MkFolder>
 					<template #icon><i class="ti ti-brand-discord"></i></template>
 					<template #label>Discord</template>
 					<template #suffix>{{ enableDiscordIntegration ? i18n.ts.enabled : i18n.ts.disabled }}</template>
 					<XDiscord/>
-				</FormFolder>
+				</MkFolder>
 			</div>
 		</FormSuspense>
 	</MkSpacer>
@@ -34,7 +34,7 @@ import XTwitter from './integrations.twitter.vue';
 import XGithub from './integrations.github.vue';
 import XDiscord from './integrations.discord.vue';
 import FormSuspense from '@/components/form/suspense.vue';
-import FormFolder from '@/components/form/folder.vue';
+import MkFolder from '@/components/MkFolder.vue';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
diff --git a/packages/frontend/src/pages/admin/overview.vue b/packages/frontend/src/pages/admin/overview.vue
index 2e0b49c5a3..0166724e01 100644
--- a/packages/frontend/src/pages/admin/overview.vue
+++ b/packages/frontend/src/pages/admin/overview.vue
@@ -1,60 +1,60 @@
 <template>
 <MkSpacer :content-max="1000">
 	<div ref="rootEl" class="edbbcaef">
-		<MkFolder class="item">
+		<MkFoldableSection class="item">
 			<template #header>Stats</template>
 			<XStats/>
-		</MkFolder>
+		</MkFoldableSection>
 
-		<MkFolder class="item">
+		<MkFoldableSection class="item">
 			<template #header>Active users</template>
 			<XActiveUsers/>
-		</MkFolder>
+		</MkFoldableSection>
 
-		<MkFolder class="item">
+		<MkFoldableSection class="item">
 			<template #header>Heatmap</template>
 			<XHeatmap/>
-		</MkFolder>
+		</MkFoldableSection>
 
-		<MkFolder class="item">
+		<MkFoldableSection class="item">
 			<template #header>Retention rate</template>
 			<XRetention/>
-		</MkFolder>
+		</MkFoldableSection>
 
-		<MkFolder class="item">
+		<MkFoldableSection class="item">
 			<template #header>Moderators</template>
 			<XModerators/>
-		</MkFolder>
+		</MkFoldableSection>
 
-		<MkFolder class="item">
+		<MkFoldableSection class="item">
 			<template #header>Federation</template>
 			<XFederation/>
-		</MkFolder>
+		</MkFoldableSection>
 		
-		<MkFolder class="item">
+		<MkFoldableSection class="item">
 			<template #header>Instances</template>
 			<XInstances/>
-		</MkFolder>
+		</MkFoldableSection>
 
-		<MkFolder class="item">
+		<MkFoldableSection class="item">
 			<template #header>Ap requests</template>
 			<XApRequests/>
-		</MkFolder>
+		</MkFoldableSection>
 
-		<MkFolder class="item">
+		<MkFoldableSection class="item">
 			<template #header>New users</template>
 			<XUsers/>
-		</MkFolder>
+		</MkFoldableSection>
 
-		<MkFolder class="item">
+		<MkFoldableSection class="item">
 			<template #header>Deliver queue</template>
 			<XQueue domain="deliver"/>
-		</MkFolder>
+		</MkFoldableSection>
 
-		<MkFolder class="item">
+		<MkFoldableSection class="item">
 			<template #header>Inbox queue</template>
 			<XQueue domain="inbox"/>
-		</MkFolder>
+		</MkFoldableSection>
 	</div>
 </MkSpacer>
 </template>
@@ -79,7 +79,7 @@ import { i18n } from '@/i18n';
 import { definePageMetadata } from '@/scripts/page-metadata';
 import { defaultStore } from '@/store';
 import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue';
-import MkFolder from '@/components/MkFolder.vue';
+import MkFoldableSection from '@/components/MkFoldableSection.vue';
 
 const rootEl = $shallowRef<HTMLElement>();
 let serverInfo: any = $ref(null);
diff --git a/packages/frontend/src/pages/admin/security.vue b/packages/frontend/src/pages/admin/security.vue
index b510f5522e..191da506e9 100644
--- a/packages/frontend/src/pages/admin/security.vue
+++ b/packages/frontend/src/pages/admin/security.vue
@@ -4,7 +4,7 @@
 	<MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
 		<FormSuspense :p="init">
 			<div class="_gaps_m">
-				<FormFolder>
+				<MkFolder>
 					<template #icon><i class="ti ti-shield"></i></template>
 					<template #label>{{ i18n.ts.botProtection }}</template>
 					<template v-if="enableHcaptcha" #suffix>hCaptcha</template>
@@ -13,9 +13,9 @@
 					<template v-else #suffix>{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</template>
 
 					<XBotProtection/>
-				</FormFolder>
+				</MkFolder>
 
-				<FormFolder>
+				<MkFolder>
 					<template #icon><i class="ti ti-eye-off"></i></template>
 					<template #label>{{ i18n.ts.sensitiveMediaDetection }}</template>
 					<template v-if="sensitiveMediaDetection === 'all'" #suffix>{{ i18n.ts.all }}</template>
@@ -56,9 +56,9 @@
 
 						<MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
 					</div>
-				</FormFolder>
+				</MkFolder>
 
-				<FormFolder>
+				<MkFolder>
 					<template #label>Active Email Validation</template>
 					<template v-if="enableActiveEmailValidation" #suffix>Enabled</template>
 					<template v-else #suffix>Disabled</template>
@@ -69,9 +69,9 @@
 							<template #label>Enable</template>
 						</MkSwitch>
 					</div>
-				</FormFolder>
+				</MkFolder>
 
-				<FormFolder>
+				<MkFolder>
 					<template #label>Log IP address</template>
 					<template v-if="enableIpLogging" #suffix>Enabled</template>
 					<template v-else #suffix>Disabled</template>
@@ -81,9 +81,9 @@
 							<template #label>Enable</template>
 						</MkSwitch>
 					</div>
-				</FormFolder>
+				</MkFolder>
 
-				<FormFolder>
+				<MkFolder>
 					<template #label>Summaly Proxy</template>
 
 					<div class="_gaps_m">
@@ -94,7 +94,7 @@
 
 						<MkButton primary @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
 					</div>
-				</FormFolder>
+				</MkFolder>
 			</div>
 		</FormSuspense>
 	</MkSpacer>
@@ -105,7 +105,7 @@
 import { } from 'vue';
 import XBotProtection from './bot-protection.vue';
 import XHeader from './_header_.vue';
-import FormFolder from '@/components/form/folder.vue';
+import MkFolder from '@/components/MkFolder.vue';
 import MkRadios from '@/components/MkRadios.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import FormInfo from '@/components/MkInfo.vue';
diff --git a/packages/frontend/src/pages/explore.users.vue b/packages/frontend/src/pages/explore.users.vue
index f1bb0cc62e..3a74e8518d 100644
--- a/packages/frontend/src/pages/explore.users.vue
+++ b/packages/frontend/src/pages/explore.users.vue
@@ -6,52 +6,52 @@
 	</MkTab>
 	<div v-if="origin === 'local'">
 		<template v-if="tag == null">
-			<MkFolder class="_margin" persist-key="explore-pinned-users">
+			<MkFoldableSection class="_margin" persist-key="explore-pinned-users">
 				<template #header><i class="fas fa-bookmark ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.pinnedUsers }}</template>
 				<XUserList :pagination="pinnedUsers"/>
-			</MkFolder>
-			<MkFolder class="_margin" persist-key="explore-popular-users">
+			</MkFoldableSection>
+			<MkFoldableSection class="_margin" persist-key="explore-popular-users">
 				<template #header><i class="fas fa-chart-line ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.popularUsers }}</template>
 				<XUserList :pagination="popularUsers"/>
-			</MkFolder>
-			<MkFolder class="_margin" persist-key="explore-recently-updated-users">
+			</MkFoldableSection>
+			<MkFoldableSection class="_margin" persist-key="explore-recently-updated-users">
 				<template #header><i class="fas fa-comment-alt ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyUpdatedUsers }}</template>
 				<XUserList :pagination="recentlyUpdatedUsers"/>
-			</MkFolder>
-			<MkFolder class="_margin" persist-key="explore-recently-registered-users">
+			</MkFoldableSection>
+			<MkFoldableSection class="_margin" persist-key="explore-recently-registered-users">
 				<template #header><i class="ti ti-plus ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyRegisteredUsers }}</template>
 				<XUserList :pagination="recentlyRegisteredUsers"/>
-			</MkFolder>
+			</MkFoldableSection>
 		</template>
 	</div>
 	<div v-else>
-		<MkFolder ref="tagsEl" :foldable="true" :expanded="false" class="_margin">
+		<MkFoldableSection ref="tagsEl" :foldable="true" :expanded="false" class="_margin">
 			<template #header><i class="ti ti-hash ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.popularTags }}</template>
 
 			<div class="vxjfqztj">
 				<MkA v-for="tag in tagsLocal" :key="'local:' + tag.tag" :to="`/explore/tags/${tag.tag}`" class="local">{{ tag.tag }}</MkA>
 				<MkA v-for="tag in tagsRemote" :key="'remote:' + tag.tag" :to="`/explore/tags/${tag.tag}`">{{ tag.tag }}</MkA>
 			</div>
-		</MkFolder>
+		</MkFoldableSection>
 
-		<MkFolder v-if="tag != null" :key="`${tag}`" class="_margin">
+		<MkFoldableSection v-if="tag != null" :key="`${tag}`" class="_margin">
 			<template #header><i class="ti ti-hash ti-fw" style="margin-right: 0.5em;"></i>{{ tag }}</template>
 			<XUserList :pagination="tagUsers"/>
-		</MkFolder>
+		</MkFoldableSection>
 
 		<template v-if="tag == null">
-			<MkFolder class="_margin">
+			<MkFoldableSection class="_margin">
 				<template #header><i class="fas fa-chart-line ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.popularUsers }}</template>
 				<XUserList :pagination="popularUsersF"/>
-			</MkFolder>
-			<MkFolder class="_margin">
+			</MkFoldableSection>
+			<MkFoldableSection class="_margin">
 				<template #header><i class="fas fa-comment-alt ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyUpdatedUsers }}</template>
 				<XUserList :pagination="recentlyUpdatedUsersF"/>
-			</MkFolder>
-			<MkFolder class="_margin">
+			</MkFoldableSection>
+			<MkFoldableSection class="_margin">
 				<template #header><i class="fas fa-rocket ti-fw" style="margin-right: 0.5em;"></i>{{ i18n.ts.recentlyDiscoveredUsers }}</template>
 				<XUserList :pagination="recentlyRegisteredUsersF"/>
-			</MkFolder>
+			</MkFoldableSection>
 		</template>
 	</div>
 </MkSpacer>
@@ -60,7 +60,7 @@
 <script lang="ts" setup>
 import { computed, watch } from 'vue';
 import XUserList from '@/components/MkUserList.vue';
-import MkFolder from '@/components/MkFolder.vue';
+import MkFoldableSection from '@/components/MkFoldableSection.vue';
 import MkTab from '@/components/MkTab.vue';
 import number from '@/filters/number';
 import * as os from '@/os';
@@ -72,7 +72,7 @@ const props = defineProps<{
 }>();
 
 let origin = $ref('local');
-let tagsEl = $shallowRef<InstanceType<typeof MkFolder>>();
+let tagsEl = $shallowRef<InstanceType<typeof MkFoldableSection>>();
 let tagsLocal = $ref([]);
 let tagsRemote = $ref([]);
 
diff --git a/packages/frontend/src/pages/explore.vue b/packages/frontend/src/pages/explore.vue
index 12e93b826d..57df58bbb8 100644
--- a/packages/frontend/src/pages/explore.vue
+++ b/packages/frontend/src/pages/explore.vue
@@ -33,7 +33,7 @@
 import { computed, watch } from 'vue';
 import XFeatured from './explore.featured.vue';
 import XUsers from './explore.users.vue';
-import MkFolder from '@/components/MkFolder.vue';
+import MkFoldableSection from '@/components/MkFoldableSection.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkRadios from '@/components/MkRadios.vue';
 import number from '@/filters/number';
@@ -51,7 +51,7 @@ const props = withDefaults(defineProps<{
 });
 
 let tab = $ref(props.initialTab);
-let tagsEl = $shallowRef<InstanceType<typeof MkFolder>>();
+let tagsEl = $shallowRef<InstanceType<typeof MkFoldableSection>>();
 let searchQuery = $ref(null);
 let searchOrigin = $ref('combined');
 
diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue
index c1afaa2d29..ebd470ca4f 100644
--- a/packages/frontend/src/pages/flash/flash.vue
+++ b/packages/frontend/src/pages/flash/flash.vue
@@ -27,12 +27,12 @@
 						</div>
 					</div>
 				</Transition>
-				<FormFolder class="_margin">
+				<MkFolder class="_margin">
 					<template #icon><i class="ti ti-code"></i></template>
 					<template #label>{{ i18n.ts._play.viewSource }}</template>
 
 					<MkTextarea :model-value="flash.script" readonly tall class="_monospace" spellcheck="false"></MkTextarea>
-				</FormFolder>
+				</MkFolder>
 				<div :class="$style.footer">
 					<Mfm :text="`By @${flash.user.username}`"/>
 					<div class="date">
@@ -65,7 +65,7 @@ import { definePageMetadata } from '@/scripts/page-metadata';
 import MkAsUi from '@/components/MkAsUi.vue';
 import { AsUiComponent, AsUiRoot, patch, registerAsUiLib, render } from '@/scripts/aiscript/ui';
 import { createAiScriptEnv } from '@/scripts/aiscript/api';
-import FormFolder from '@/components/form/folder.vue';
+import MkFolder from '@/components/MkFolder.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
 
 const props = defineProps<{
diff --git a/packages/frontend/src/pages/gallery/index.vue b/packages/frontend/src/pages/gallery/index.vue
index 2fc2a8205a..1001c3b2e7 100644
--- a/packages/frontend/src/pages/gallery/index.vue
+++ b/packages/frontend/src/pages/gallery/index.vue
@@ -4,22 +4,22 @@
 	<MkSpacer :content-max="1400">
 		<div class="_root">
 			<div v-if="tab === 'explore'">
-				<MkFolder class="_margin">
+				<MkFoldableSection class="_margin">
 					<template #header><i class="ti ti-clock"></i>{{ i18n.ts.recentPosts }}</template>
 					<MkPagination v-slot="{items}" :pagination="recentPostsPagination" :disable-auto-load="true">
 						<div class="vfpdbgtk">
 							<MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/>
 						</div>
 					</MkPagination>
-				</MkFolder>
-				<MkFolder class="_margin">
+				</MkFoldableSection>
+				<MkFoldableSection class="_margin">
 					<template #header><i class="ti ti-comet"></i>{{ i18n.ts.popularPosts }}</template>
 					<MkPagination v-slot="{items}" :pagination="popularPostsPagination" :disable-auto-load="true">
 						<div class="vfpdbgtk">
 							<MkGalleryPostPreview v-for="post in items" :key="post.id" :post="post" class="post"/>
 						</div>
 					</MkPagination>
-				</MkFolder>
+				</MkFoldableSection>
 			</div>
 			<div v-else-if="tab === 'liked'">
 				<MkPagination v-slot="{items}" :pagination="likedPostsPagination">
@@ -44,7 +44,7 @@
 <script lang="ts" setup>
 import { computed, defineComponent, watch } from 'vue';
 import XUserList from '@/components/MkUserList.vue';
-import MkFolder from '@/components/MkFolder.vue';
+import MkFoldableSection from '@/components/MkFoldableSection.vue';
 import MkInput from '@/components/MkInput.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkTab from '@/components/MkTab.vue';
diff --git a/packages/frontend/src/pages/settings/import-export.vue b/packages/frontend/src/pages/settings/import-export.vue
index e04fe37ac8..a8274f5601 100644
--- a/packages/frontend/src/pages/settings/import-export.vue
+++ b/packages/frontend/src/pages/settings/import-export.vue
@@ -2,24 +2,24 @@
 <div class="_gaps_m">
 	<FormSection first>
 		<template #label><i class="ti ti-pencil"></i> {{ i18n.ts._exportOrImport.allNotes }}</template>
-		<FormFolder>
+		<MkFolder>
 			<template #label>{{ i18n.ts.export }}</template>
 			<template #icon><i class="ti ti-download"></i></template>
 			<MkButton primary :class="$style.button" inline @click="exportNotes()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
-		</FormFolder>
+		</MkFolder>
 	</FormSection>
 	<FormSection>
 		<template #label><i class="ti ti-star"></i> {{ i18n.ts._exportOrImport.favoritedNotes }}</template>
-		<FormFolder>
+		<MkFolder>
 			<template #label>{{ i18n.ts.export }}</template>
 			<template #icon><i class="ti ti-download"></i></template>
 			<MkButton primary :class="$style.button" inline @click="exportFavorites()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
-		</FormFolder>
+		</MkFolder>
 	</FormSection>
 	<FormSection>
 		<template #label><i class="ti ti-users"></i> {{ i18n.ts._exportOrImport.followingList }}</template>
 		<div class="_gaps_s">
-			<FormFolder>
+			<MkFolder>
 				<template #label>{{ i18n.ts.export }}</template>
 				<template #icon><i class="ti ti-download"></i></template>
 				<div class="_gaps_s">
@@ -31,57 +31,57 @@
 					</MkSwitch>
 					<MkButton primary :class="$style.button" inline @click="exportFollowing()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
 				</div>
-			</FormFolder>
-			<FormFolder>
+			</MkFolder>
+			<MkFolder>
 				<template #label>{{ i18n.ts.import }}</template>
 				<template #icon><i class="ti ti-upload"></i></template>
 				<MkButton primary :class="$style.button" inline @click="importFollowing($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
-			</FormFolder>
+			</MkFolder>
 		</div>
 	</FormSection>
 	<FormSection>
 		<template #label><i class="ti ti-users"></i> {{ i18n.ts._exportOrImport.userLists }}</template>
 		<div class="_gaps_s">
-			<FormFolder>
+			<MkFolder>
 				<template #label>{{ i18n.ts.export }}</template>
 				<template #icon><i class="ti ti-download"></i></template>
 				<MkButton primary :class="$style.button" inline @click="exportUserLists()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
-			</FormFolder>
-			<FormFolder>
+			</MkFolder>
+			<MkFolder>
 				<template #label>{{ i18n.ts.import }}</template>
 				<template #icon><i class="ti ti-upload"></i></template>
 				<MkButton primary :class="$style.button" inline @click="importUserLists($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
-			</FormFolder>
+			</MkFolder>
 		</div>
 	</FormSection>
 	<FormSection>
 		<template #label><i class="ti ti-user-off"></i> {{ i18n.ts._exportOrImport.muteList }}</template>
 		<div class="_gaps_s">
-			<FormFolder>
+			<MkFolder>
 				<template #label>{{ i18n.ts.export }}</template>
 				<template #icon><i class="ti ti-download"></i></template>
 				<MkButton primary :class="$style.button" inline @click="exportMuting()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
-			</FormFolder>
-			<FormFolder>
+			</MkFolder>
+			<MkFolder>
 				<template #label>{{ i18n.ts.import }}</template>
 				<template #icon><i class="ti ti-upload"></i></template>
 				<MkButton primary :class="$style.button" inline @click="importMuting($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
-			</FormFolder>
+			</MkFolder>
 		</div>
 	</FormSection>
 	<FormSection>
 		<template #label><i class="ti ti-user-off"></i> {{ i18n.ts._exportOrImport.blockingList }}</template>
 		<div class="_gaps_s">
-			<FormFolder>
+			<MkFolder>
 				<template #label>{{ i18n.ts.export }}</template>
 				<template #icon><i class="ti ti-download"></i></template>
 				<MkButton primary :class="$style.button" inline @click="exportBlocking()"><i class="ti ti-download"></i> {{ i18n.ts.export }}</MkButton>
-			</FormFolder>
-			<FormFolder>
+			</MkFolder>
+			<MkFolder>
 				<template #label>{{ i18n.ts.import }}</template>
 				<template #icon><i class="ti ti-upload"></i></template>
 				<MkButton primary :class="$style.button" inline @click="importBlocking($event)"><i class="ti ti-upload"></i> {{ i18n.ts.import }}</MkButton>
-			</FormFolder>
+			</MkFolder>
 		</div>
 	</FormSection>
 </div>
@@ -91,7 +91,7 @@
 import { ref } from 'vue';
 import MkButton from '@/components/MkButton.vue';
 import FormSection from '@/components/form/section.vue';
-import FormFolder from '@/components/form/folder.vue';
+import MkFolder from '@/components/MkFolder.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import * as os from '@/os';
 import { selectFile } from '@/scripts/select-file';
diff --git a/packages/frontend/src/pages/settings/privacy.vue b/packages/frontend/src/pages/settings/privacy.vue
index 9ed53931b8..5692ce80cb 100644
--- a/packages/frontend/src/pages/settings/privacy.vue
+++ b/packages/frontend/src/pages/settings/privacy.vue
@@ -32,7 +32,7 @@
 	<FormSection>
 		<div class="_gaps_m">
 			<MkSwitch v-model="rememberNoteVisibility" @update:model-value="save()">{{ i18n.ts.rememberNoteVisibility }}</MkSwitch>
-			<FormFolder v-if="!rememberNoteVisibility">
+			<MkFolder v-if="!rememberNoteVisibility">
 				<template #label>{{ i18n.ts.defaultNoteVisibility }}</template>
 				<template v-if="defaultNoteVisibility === 'public'" #suffix>{{ i18n.ts._visibility.public }}</template>
 				<template v-else-if="defaultNoteVisibility === 'home'" #suffix>{{ i18n.ts._visibility.home }}</template>
@@ -48,7 +48,7 @@
 					</MkSelect>
 					<MkSwitch v-model="defaultNoteLocalOnly">{{ i18n.ts._visibility.localOnly }}</MkSwitch>
 				</div>
-			</FormFolder>
+			</MkFolder>
 		</div>
 	</FormSection>
 
@@ -61,7 +61,7 @@ import { } from 'vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkSelect from '@/components/MkSelect.vue';
 import FormSection from '@/components/form/section.vue';
-import FormFolder from '@/components/form/folder.vue';
+import MkFolder from '@/components/MkFolder.vue';
 import * as os from '@/os';
 import { defaultStore } from '@/store';
 import { i18n } from '@/i18n';
diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue
index eb4b929c6f..a9d735c07f 100644
--- a/packages/frontend/src/pages/settings/profile.vue
+++ b/packages/frontend/src/pages/settings/profile.vue
@@ -33,7 +33,7 @@
 	</MkSelect>
 
 	<FormSlot>
-		<FormFolder>
+		<MkFolder>
 			<template #icon><i class="ti ti-list"></i></template>
 			<template #label>{{ i18n.ts._profile.metadataEdit }}</template>
 
@@ -51,18 +51,18 @@
 					<MkButton inline primary @click="saveFields"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
 				</div>
 			</div>
-		</FormFolder>
+		</MkFolder>
 		<template #caption>{{ i18n.ts._profile.metadataDescription }}</template>
 	</FormSlot>
 
-	<FormFolder>
+	<MkFolder>
 		<template #label>{{ i18n.ts.advancedSettings }}</template>
 
 		<div class="_gaps_m">
 			<MkSwitch v-model="profile.isCat">{{ i18n.ts.flagAsCat }}<template #caption>{{ i18n.ts.flagAsCatDescription }}</template></MkSwitch>
 			<MkSwitch v-model="profile.isBot">{{ i18n.ts.flagAsBot }}<template #caption>{{ i18n.ts.flagAsBotDescription }}</template></MkSwitch>
 		</div>
-	</FormFolder>
+	</MkFolder>
 
 	<MkSwitch v-model="profile.showTimelineReplies">{{ i18n.ts.flagShowTimelineReplies }}<template #caption>{{ i18n.ts.flagShowTimelineRepliesDescription }} {{ i18n.ts.reflectMayTakeTime }}</template></MkSwitch>
 </div>
@@ -76,7 +76,7 @@ import MkTextarea from '@/components/MkTextarea.vue';
 import MkSwitch from '@/components/MkSwitch.vue';
 import MkSelect from '@/components/MkSelect.vue';
 import FormSplit from '@/components/form/split.vue';
-import FormFolder from '@/components/form/folder.vue';
+import MkFolder from '@/components/MkFolder.vue';
 import FormSlot from '@/components/form/slot.vue';
 import { host } from '@/config';
 import { selectFile } from '@/scripts/select-file';
diff --git a/packages/frontend/src/pages/settings/sounds.vue b/packages/frontend/src/pages/settings/sounds.vue
index d93bdc2b1f..f9e301aef9 100644
--- a/packages/frontend/src/pages/settings/sounds.vue
+++ b/packages/frontend/src/pages/settings/sounds.vue
@@ -7,12 +7,12 @@
 	<FormSection>
 		<template #label>{{ i18n.ts.sounds }}</template>
 		<div class="_gaps_s">
-			<FormFolder v-for="type in Object.keys(sounds)" :key="type">
+			<MkFolder v-for="type in Object.keys(sounds)" :key="type">
 				<template #label>{{ $t('_sfx.' + type) }}</template>
 				<template #suffix>{{ sounds[type].type ?? i18n.ts.none }}</template>
 
 				<XSound :type="sounds[type].type" :volume="sounds[type].volume" @update="(res) => updated(type, res)"/>
-			</FormFolder>
+			</MkFolder>
 		</div>
 	</FormSection>
 
@@ -27,7 +27,7 @@ import MkRange from '@/components/MkRange.vue';
 import MkButton from '@/components/MkButton.vue';
 import FormLink from '@/components/form/link.vue';
 import FormSection from '@/components/form/section.vue';
-import FormFolder from '@/components/form/folder.vue';
+import MkFolder from '@/components/MkFolder.vue';
 import * as os from '@/os';
 import { ColdDeviceStorage } from '@/store';
 import { playFile } from '@/scripts/sound';
diff --git a/packages/frontend/src/pages/settings/statusbar.vue b/packages/frontend/src/pages/settings/statusbar.vue
index 37dd701fc8..ab081964c2 100644
--- a/packages/frontend/src/pages/settings/statusbar.vue
+++ b/packages/frontend/src/pages/settings/statusbar.vue
@@ -1,10 +1,10 @@
 <template>
 <div class="_gaps_m">
-	<FormFolder v-for="x in statusbars" :key="x.id">
+	<MkFolder v-for="x in statusbars" :key="x.id">
 		<template #label>{{ x.type ?? i18n.ts.notSet }}</template>
 		<template #suffix>{{ x.name }}</template>
 		<XStatusbar :_id="x.id" :user-lists="userLists"/>
-	</FormFolder>
+	</MkFolder>
 	<MkButton primary @click="add">{{ i18n.ts.add }}</MkButton>
 </div>
 </template>
@@ -14,7 +14,7 @@ import { computed, onMounted, ref, watch } from 'vue';
 import { v4 as uuid } from 'uuid';
 import XStatusbar from './statusbar.statusbar.vue';
 import MkRadios from '@/components/MkRadios.vue';
-import FormFolder from '@/components/form/folder.vue';
+import MkFolder from '@/components/MkFolder.vue';
 import MkButton from '@/components/MkButton.vue';
 import * as os from '@/os';
 import { defaultStore } from '@/store';
diff --git a/packages/frontend/src/pages/theme-editor.vue b/packages/frontend/src/pages/theme-editor.vue
index 9875fb6163..56fdfdf782 100644
--- a/packages/frontend/src/pages/theme-editor.vue
+++ b/packages/frontend/src/pages/theme-editor.vue
@@ -3,7 +3,7 @@
 	<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template>
 	<MkSpacer :content-max="800" :margin-min="16" :margin-max="32">
 		<div class="cwepdizn _gaps_m">
-			<FormFolder :default-open="true">
+			<MkFolder :default-open="true">
 				<template #label>{{ i18n.ts.backgroundColor }}</template>
 				<div class="cwepdizn-colors">
 					<div class="row">
@@ -17,9 +17,9 @@
 						</button>
 					</div>
 				</div>
-			</FormFolder>
+			</MkFolder>
 
-			<FormFolder :default-open="true">
+			<MkFolder :default-open="true">
 				<template #label>{{ i18n.ts.accentColor }}</template>
 				<div class="cwepdizn-colors">
 					<div class="row">
@@ -28,9 +28,9 @@
 						</button>
 					</div>
 				</div>
-			</FormFolder>
+			</MkFolder>
 
-			<FormFolder :default-open="true">
+			<MkFolder :default-open="true">
 				<template #label>{{ i18n.ts.textColor }}</template>
 				<div class="cwepdizn-colors">
 					<div class="row">
@@ -39,9 +39,9 @@
 						</button>
 					</div>
 				</div>
-			</FormFolder>
+			</MkFolder>
 
-			<FormFolder :default-open="false">
+			<MkFolder :default-open="false">
 				<template #icon><i class="ti ti-code"></i></template>
 				<template #label>{{ i18n.ts.editCode }}</template>
 
@@ -51,9 +51,9 @@
 					</MkTextarea>
 					<MkButton primary @click="applyThemeCode">{{ i18n.ts.apply }}</MkButton>
 				</div>
-			</FormFolder>
+			</MkFolder>
 
-			<FormFolder :default-open="false">
+			<MkFolder :default-open="false">
 				<template #label>{{ i18n.ts.addDescription }}</template>
 
 				<div class="_gaps_m">
@@ -61,7 +61,7 @@
 						<template #label>{{ i18n.ts._theme.description }}</template>
 					</MkTextarea>
 				</div>
-			</FormFolder>
+			</MkFolder>
 		</div>
 	</MkSpacer>
 </MkStickyContainer>
@@ -76,7 +76,7 @@ import JSON5 from 'json5';
 
 import MkButton from '@/components/MkButton.vue';
 import MkTextarea from '@/components/MkTextarea.vue';
-import FormFolder from '@/components/form/folder.vue';
+import MkFolder from '@/components/MkFolder.vue';
 
 import { $i } from '@/account';
 import { Theme, applyTheme } from '@/scripts/theme';
diff --git a/packages/frontend/src/pages/user-info.vue b/packages/frontend/src/pages/user-info.vue
index 216e1385b0..3d1742cb22 100644
--- a/packages/frontend/src/pages/user-info.vue
+++ b/packages/frontend/src/pages/user-info.vue
@@ -77,12 +77,12 @@
 
 						<MkButton v-if="user.host != null" @click="updateRemoteUser"><i class="ti ti-refresh"></i> {{ i18n.ts.updateRemoteUser }}</MkButton>
 
-						<FormFolder>
+						<MkFolder>
 							<template #label>Raw</template>
 
 							<MkObjectView v-if="ap" tall :value="ap">
 							</MkObjectView>
-						</FormFolder>
+						</MkFolder>
 					</div>
 				</FormSection>
 			</div>
@@ -98,7 +98,7 @@
 				<MkTextarea v-model="moderationNote" manual-save>
 					<template #label>Moderation note</template>
 				</MkTextarea>
-				<FormFolder>
+				<MkFolder>
 					<template #label>IP</template>
 					<MkInfo v-if="!iAmAdmin" warn>{{ i18n.ts.requireAdminForView }}</MkInfo>
 					<MkInfo v-else>The date is the IP address was first acknowledged.</MkInfo>
@@ -108,12 +108,12 @@
 							<span class="ip">{{ record.ip }}</span>
 						</div>
 					</template>
-				</FormFolder>
-				<FormFolder>
+				</MkFolder>
+				<MkFolder>
 					<template #label>{{ i18n.ts.files }}</template>
 
 					<MkFileListForAdmin :pagination="filesPagination" view-mode="grid"/>
-				</FormFolder>
+				</MkFolder>
 				<FormSection>
 					<template #label>Drive Capacity Override</template>
 
@@ -165,7 +165,7 @@ import FormSection from '@/components/form/section.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
 import FormSplit from '@/components/form/split.vue';
-import FormFolder from '@/components/form/folder.vue';
+import MkFolder from '@/components/MkFolder.vue';
 import MkKeyValue from '@/components/MkKeyValue.vue';
 import MkSelect from '@/components/MkSelect.vue';
 import FormSuspense from '@/components/form/suspense.vue';
diff --git a/packages/frontend/src/pages/user/activity.vue b/packages/frontend/src/pages/user/activity.vue
index e74b82fb27..6d7c7e7722 100644
--- a/packages/frontend/src/pages/user/activity.vue
+++ b/packages/frontend/src/pages/user/activity.vue
@@ -1,22 +1,22 @@
 <template>
 <MkSpacer :content-max="700">
 	<div class="_gaps">
-		<MkFolder class="item">
+		<MkFoldableSection class="item">
 			<template #header><i class="ti ti-activity"></i> Heatmap</template>
 			<XHeatmap :user="user" :src="'notes'"/>
-		</MkFolder>
-		<MkFolder class="item">
+		</MkFoldableSection>
+		<MkFoldableSection class="item">
 			<template #header><i class="ti ti-pencil"></i> Notes</template>
 			<XNotes :user="user"/>
-		</MkFolder>
-		<MkFolder class="item">
+		</MkFoldableSection>
+		<MkFoldableSection class="item">
 			<template #header><i class="ti ti-users"></i> Following</template>
 			<XFollowing :user="user"/>
-		</MkFolder>
-		<MkFolder class="item">
+		</MkFoldableSection>
+		<MkFoldableSection class="item">
 			<template #header><i class="ti ti-eye"></i> PV</template>
 			<XPv :user="user"/>
-		</MkFolder>
+		</MkFoldableSection>
 	</div>
 </MkSpacer>
 </template>
@@ -28,7 +28,7 @@ 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 MkFolder from '@/components/MkFolder.vue';
+import MkFoldableSection from '@/components/MkFoldableSection.vue';
 
 const props = defineProps<{
 	user: misskey.entities.User;
diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue
index 53ae6a2c53..07d34a794d 100644
--- a/packages/frontend/src/pages/user/home.vue
+++ b/packages/frontend/src/pages/user/home.vue
@@ -115,7 +115,7 @@ import XUserTimeline from './index.timeline.vue';
 import XNote from '@/components/MkNote.vue';
 import MkFollowButton from '@/components/MkFollowButton.vue';
 import MkContainer from '@/components/MkContainer.vue';
-import MkFolder from '@/components/MkFolder.vue';
+import MkFoldableSection from '@/components/MkFoldableSection.vue';
 import MkRemoteCaution from '@/components/MkRemoteCaution.vue';
 import MkTab from '@/components/MkTab.vue';
 import MkInfo from '@/components/MkInfo.vue';

From 2817ca03f59a53f70d81dd7718cc2dcbd2c85ed8 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 09:42:11 +0900
Subject: [PATCH 41/68] Update queue.chart.vue

---
 .../frontend/src/pages/admin/queue.chart.vue  | 73 +++++++++++--------
 1 file changed, 43 insertions(+), 30 deletions(-)

diff --git a/packages/frontend/src/pages/admin/queue.chart.vue b/packages/frontend/src/pages/admin/queue.chart.vue
index 7f1dc2cd77..100d1ea545 100644
--- a/packages/frontend/src/pages/admin/queue.chart.vue
+++ b/packages/frontend/src/pages/admin/queue.chart.vue
@@ -1,12 +1,10 @@
 <template>
-<div class="pumxzjhg">
-	<div class="_table status">
-		<div class="_row">
-			<div class="_cell"><div class="_label">Process</div>{{ number(activeSincePrevTick) }}</div>
-			<div class="_cell"><div class="_label">Active</div>{{ number(active) }}</div>
-			<div class="_cell"><div class="_label">Waiting</div>{{ number(waiting) }}</div>
-			<div class="_cell"><div class="_label">Delayed</div>{{ number(delayed) }}</div>
-		</div>
+<div class="pumxzjhg _gaps">
+	<div :class="$style.status">
+		<div class="item _panel"><div class="label">Process</div>{{ number(activeSincePrevTick) }}</div>
+		<div class="item _panel"><div class="label">Active</div>{{ number(active) }}</div>
+		<div class="item _panel"><div class="label">Waiting</div>{{ number(waiting) }}</div>
+		<div class="item _panel"><div class="label">Delayed</div>{{ number(delayed) }}</div>
 	</div>
 	<div class="charts">
 		<div class="chart">
@@ -26,15 +24,21 @@
 			<XChart ref="chartWaiting" type="waiting"/>
 		</div>
 	</div>
-	<div class="jobs">
-		<div v-if="jobs.length > 0">
-			<div v-for="job in jobs" :key="job[0]">
-				<span>{{ job[0] }}</span>
-				<span style="margin-left: 8px; opacity: 0.7;">({{ number(job[1]) }} jobs)</span>
+	<MkFolder :default-open="true" :max-height="250">
+		<template #icon><i class="ti ti-alert-triangle"></i></template>
+		<template #label>Errored instances</template>
+		<template #suffix>({{ number(jobs.reduce((a, b) => a + b[1], 0)) }} jobs)</template>
+		
+		<div :class="$style.jobs">
+			<div v-if="jobs.length > 0">
+				<div v-for="job in jobs" :key="job[0]">
+					<MkA :to="`/instance-info/${job[0]}`" behavior="window">{{ job[0] }}</MkA>
+					<span style="margin-left: 8px; opacity: 0.7;">({{ number(job[1]) }} jobs)</span>
+				</div>
 			</div>
+			<span v-else style="opacity: 0.5;">{{ i18n.ts.noJobs }}</span>
 		</div>
-		<span v-else style="opacity: 0.5;">{{ i18n.ts.noJobs }}</span>
-	</div>
+	</MkFolder>
 </div>
 </template>
 
@@ -45,6 +49,7 @@ import number from '@/filters/number';
 import * as os from '@/os';
 import { stream } from '@/stream';
 import { i18n } from '@/i18n';
+import MkFolder from '@/components/MkFolder.vue';
 
 const connection = markRaw(stream.useChannel('queueStats'));
 
@@ -115,14 +120,10 @@ onUnmounted(() => {
 
 <style lang="scss" scoped>
 .pumxzjhg {
-	> .status {
-		padding: 16px;
-	}
-
 	> .charts {
 		display: grid;
 		grid-template-columns: 1fr 1fr;
-		gap: 16px;
+		gap: 10px;
 
 		> .chart {
 			min-width: 0;
@@ -135,15 +136,27 @@ onUnmounted(() => {
 			}
 		}
 	}
-
-	> .jobs {
-		margin-top: 16px;
-		padding: 16px;
-		max-height: 180px;
-		overflow: auto;
-		background: var(--panel);
-		border-radius: var(--radius);
-	}
-
+}
+</style>
+
+<style lang="scss" module>
+.status {
+	display: grid;
+	grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
+	grid-gap: 10px;
+
+	&:global {
+		> .item {
+			padding: 12px 16px;
+
+			> .label {
+				font-size: 80%;
+				opacity: 0.6;
+			}
+		}
+	}
+}
+
+.jobs {
 }
 </style>

From 5ce56886a16ee773e60a44ec4f6a8d5f17c77635 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 09:43:28 +0900
Subject: [PATCH 42/68] fix

---
 packages/frontend/src/pages/about.federation.vue | 2 +-
 packages/frontend/src/pages/admin/users.vue      | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/frontend/src/pages/about.federation.vue b/packages/frontend/src/pages/about.federation.vue
index a4e449911c..101ea2297b 100644
--- a/packages/frontend/src/pages/about.federation.vue
+++ b/packages/frontend/src/pages/about.federation.vue
@@ -37,7 +37,7 @@
 	<MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination">
 		<div class="dqokceoi">
 			<MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" class="instance" :to="`/instance-info/${instance.host}`">
-				<MkInstanceCardMini v-once :instance="instance"/>
+				<MkInstanceCardMini :instance="instance"/>
 			</MkA>
 		</div>
 	</MkPagination>
diff --git a/packages/frontend/src/pages/admin/users.vue b/packages/frontend/src/pages/admin/users.vue
index 445a0a3a71..babe76e4ec 100644
--- a/packages/frontend/src/pages/admin/users.vue
+++ b/packages/frontend/src/pages/admin/users.vue
@@ -42,7 +42,7 @@
 
 					<MkPagination v-slot="{items}" ref="paginationComponent" :pagination="pagination" class="users">
 						<MkA v-for="user in items" :key="user.id" v-tooltip.mfm="`Last posted: ${dateString(user.updatedAt)}`" class="user" :to="`/user-info/${user.id}`">
-							<MkUserCardMini v-once :user="user"/>
+							<MkUserCardMini :user="user"/>
 						</MkA>
 					</MkPagination>
 				</div>

From 13aa4b64b44d7fe2a44c3e2722e2edfcecb57449 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 10:07:37 +0900
Subject: [PATCH 43/68] tweak client

---
 .../frontend/src/pages/admin/federation.vue   | 123 ++++++++++++++++++
 packages/frontend/src/pages/admin/index.vue   |   2 +-
 packages/frontend/src/router.ts               |   4 +
 3 files changed, 128 insertions(+), 1 deletion(-)
 create mode 100644 packages/frontend/src/pages/admin/federation.vue

diff --git a/packages/frontend/src/pages/admin/federation.vue b/packages/frontend/src/pages/admin/federation.vue
new file mode 100644
index 0000000000..96bd087e66
--- /dev/null
+++ b/packages/frontend/src/pages/admin/federation.vue
@@ -0,0 +1,123 @@
+<template>
+<div>
+	<MkStickyContainer>
+		<template #header><XHeader :actions="headerActions"/></template>
+		<MkSpacer :content-max="900">
+			<div class="taeiyrib">
+				<div class="query">
+					<MkInput v-model="host" :debounce="true" class="">
+						<template #prefix><i class="ti ti-search"></i></template>
+						<template #label>{{ i18n.ts.host }}</template>
+					</MkInput>
+					<FormSplit style="margin-top: var(--margin);">
+						<MkSelect v-model="state">
+							<template #label>{{ i18n.ts.state }}</template>
+							<option value="all">{{ i18n.ts.all }}</option>
+							<option value="federating">{{ i18n.ts.federating }}</option>
+							<option value="subscribing">{{ i18n.ts.subscribing }}</option>
+							<option value="publishing">{{ i18n.ts.publishing }}</option>
+							<option value="suspended">{{ i18n.ts.suspended }}</option>
+							<option value="blocked">{{ i18n.ts.blocked }}</option>
+							<option value="notResponding">{{ i18n.ts.notResponding }}</option>
+						</MkSelect>
+						<MkSelect v-model="sort">
+							<template #label>{{ i18n.ts.sort }}</template>
+							<option value="+pubSub">{{ i18n.ts.pubSub }} ({{ i18n.ts.descendingOrder }})</option>
+							<option value="-pubSub">{{ i18n.ts.pubSub }} ({{ i18n.ts.ascendingOrder }})</option>
+							<option value="+notes">{{ i18n.ts.notes }} ({{ i18n.ts.descendingOrder }})</option>
+							<option value="-notes">{{ i18n.ts.notes }} ({{ i18n.ts.ascendingOrder }})</option>
+							<option value="+users">{{ i18n.ts.users }} ({{ i18n.ts.descendingOrder }})</option>
+							<option value="-users">{{ i18n.ts.users }} ({{ i18n.ts.ascendingOrder }})</option>
+							<option value="+following">{{ i18n.ts.following }} ({{ i18n.ts.descendingOrder }})</option>
+							<option value="-following">{{ i18n.ts.following }} ({{ i18n.ts.ascendingOrder }})</option>
+							<option value="+followers">{{ i18n.ts.followers }} ({{ i18n.ts.descendingOrder }})</option>
+							<option value="-followers">{{ i18n.ts.followers }} ({{ i18n.ts.ascendingOrder }})</option>
+							<option value="+caughtAt">{{ i18n.ts.registeredAt }} ({{ i18n.ts.descendingOrder }})</option>
+							<option value="-caughtAt">{{ i18n.ts.registeredAt }} ({{ i18n.ts.ascendingOrder }})</option>
+						</MkSelect>
+					</FormSplit>
+				</div>
+
+				<MkPagination v-slot="{items}" ref="instances" :key="host + state" :pagination="pagination">
+					<div class="dqokceoj">
+						<MkA v-for="instance in items" :key="instance.id" v-tooltip.mfm="`Status: ${getStatus(instance)}`" class="instance" :to="`/instance-info/${instance.host}`">
+							<MkInstanceCardMini :instance="instance"/>
+						</MkA>
+					</div>
+				</MkPagination>
+			</div>
+		</MkSpacer>
+	</MkStickyContainer>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { computed } from 'vue';
+import XHeader from './_header_.vue';
+import MkButton from '@/components/MkButton.vue';
+import MkInput from '@/components/MkInput.vue';
+import MkSelect from '@/components/MkSelect.vue';
+import MkPagination from '@/components/MkPagination.vue';
+import MkInstanceCardMini from '@/components/MkInstanceCardMini.vue';
+import FormSplit from '@/components/form/split.vue';
+import * as os from '@/os';
+import { i18n } from '@/i18n';
+import { dateString } from '@/filters/date';
+import { definePageMetadata } from '@/scripts/page-metadata';
+
+let host = $ref('');
+let state = $ref('federating');
+let sort = $ref('+pubSub');
+const pagination = {
+	endpoint: 'federation/instances' as const,
+	limit: 10,
+	offsetMode: true,
+	params: computed(() => ({
+		sort: sort,
+		host: host !== '' ? host : null,
+		...(
+			state === 'federating' ? { federating: true } :
+			state === 'subscribing' ? { subscribing: true } :
+			state === 'publishing' ? { publishing: true } :
+			state === 'suspended' ? { suspended: true } :
+			state === 'blocked' ? { blocked: true } :
+			state === 'notResponding' ? { notResponding: true } :
+			{}),
+	})),
+};
+
+function getStatus(instance) {
+	if (instance.isSuspended) return 'Suspended';
+	if (instance.isBlocked) return 'Blocked';
+	if (instance.isNotResponding) return 'Error';
+	return 'Alive';
+}
+
+const headerActions = $computed(() => []);
+
+const headerTabs = $computed(() => []);
+
+definePageMetadata(computed(() => ({
+	title: i18n.ts.federation,
+	icon: 'ti ti-whirl',
+})));
+</script>
+
+<style lang="scss" scoped>
+.taeiyrib {
+	> .query {
+		background: var(--bg);
+		margin-bottom: 16px;
+	}
+}
+
+.dqokceoj {
+	display: grid;
+	grid-template-columns: repeat(auto-fill, minmax(270px, 1fr));
+	grid-gap: 12px;
+
+	> .instance:hover {
+		text-decoration: none;
+	}
+}
+</style>
diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue
index c71ebd4e2e..c90a1c1b00 100644
--- a/packages/frontend/src/pages/admin/index.vue
+++ b/packages/frontend/src/pages/admin/index.vue
@@ -104,7 +104,7 @@ const menuDef = $computed(() => [{
 	}, {
 		icon: 'ti ti-whirl',
 		text: i18n.ts.federation,
-		to: '/about#federation',
+		to: '/admin/federation',
 		active: currentPage?.route.name === 'federation',
 	}, {
 		icon: 'ti ti-clock-play',
diff --git a/packages/frontend/src/router.ts b/packages/frontend/src/router.ts
index bfa4a3ceab..4b9f49f8fd 100644
--- a/packages/frontend/src/router.ts
+++ b/packages/frontend/src/router.ts
@@ -339,6 +339,10 @@ export const routes = [{
 		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',

From 962373cf06f233f7a3c3a5dbe25c558752d8b2d7 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 13:26:42 +0900
Subject: [PATCH 44/68] =?UTF-8?q?fix(server):=20=E9=9D=9E=E5=85=AC?=
 =?UTF-8?q?=E9=96=8B=E3=81=AE=E3=82=AF=E3=83=AA=E3=83=83=E3=83=97=E3=81=AE?=
 =?UTF-8?q?URL=E3=81=A7OGP=E3=83=AC=E3=83=B3=E3=83=80=E3=83=AA=E3=83=B3?=
 =?UTF-8?q?=E3=82=B0=E3=81=95=E3=82=8C=E3=82=8B=E5=95=8F=E9=A1=8C=E3=82=92?=
 =?UTF-8?q?=E4=BF=AE=E6=AD=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Fix #9129
---
 CHANGELOG.md                                           | 1 +
 packages/backend/src/server/web/ClientServerService.ts | 5 ++---
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index d19545c7d9..f468bc3cbb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -100,6 +100,7 @@ You should also include the user name that made the change.
 - Server: pages/likeのエラーIDが重複しているのを修正 @syuilo
 - Server: pages/updateのパラメータによってはsummaryの値が更新されないのを修正 @syuilo
 - Server: Escape SQL LIKE @mei23
+- Server: 非公開のクリップのURLでOGPレンダリングされる問題を修正 @syuilo
 - Client: case insensitive emoji search @saschanaz
 - Client: InAppウィンドウが操作できなくなることがあるのを修正 @tamaina
 - Client: use proxied image for instance icon @syuilo
diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts
index c69975fd79..5c29224019 100644
--- a/packages/backend/src/server/web/ClientServerService.ts
+++ b/packages/backend/src/server/web/ClientServerService.ts
@@ -312,7 +312,7 @@ export class ClientServerService {
 		fastify.get('/opensearch.xml', async (request, reply) => {
 			const meta = await this.metaService.fetch();
 
-			const name = meta.name || 'Misskey';
+			const name = meta.name ?? 'Misskey';
 			let content = '';
 			content += '<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/" xmlns:moz="http://www.mozilla.org/2006/browser/search/">';
 			content += `<ShortName>${name}</ShortName>`;
@@ -533,13 +533,12 @@ export class ClientServerService {
 		});
 
 		// Clip
-		// TODO: 非publicなclipのハンドリング
 		fastify.get<{ Params: { clip: string; } }>('/clips/:clip', async (request, reply) => {
 			const clip = await this.clipsRepository.findOneBy({
 				id: request.params.clip,
 			});
 
-			if (clip) {
+			if (clip && clip.isPublic) {
 				const _clip = await this.clipEntityService.pack(clip);
 				const profile = await this.userProfilesRepository.findOneByOrFail({ userId: clip.userId });
 				const meta = await this.metaService.fetch();

From dfee79f8416da0966276d83654165a1adfc424b9 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 13:32:48 +0900
Subject: [PATCH 45/68] :art:

Resolve #9498
---
 packages/frontend/src/components/MkNote.vue             | 2 +-
 packages/frontend/src/components/MkNoteDetailed.vue     | 4 ++--
 packages/frontend/src/components/MkNoteHeader.vue       | 2 +-
 packages/frontend/src/components/MkPostForm.vue         | 2 +-
 packages/frontend/src/components/MkVisibilityPicker.vue | 2 +-
 5 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index cb97f19d31..45aba1d24b 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -29,7 +29,7 @@
 			</button>
 			<span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[note.visibility]">
 				<i v-if="note.visibility === 'home'" class="ti ti-home"></i>
-				<i v-else-if="note.visibility === 'followers'" class="ti ti-lock-open"></i>
+				<i v-else-if="note.visibility === 'followers'" class="ti ti-lock"></i>
 				<i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
 			</span>
 			<span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span>
diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue
index 4dc1358b24..f0493ba357 100644
--- a/packages/frontend/src/components/MkNoteDetailed.vue
+++ b/packages/frontend/src/components/MkNoteDetailed.vue
@@ -27,7 +27,7 @@
 			</button>
 			<span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[note.visibility]">
 				<i v-if="note.visibility === 'home'" class="ti ti-home"></i>
-				<i v-else-if="note.visibility === 'followers'" class="ti ti-lock-open"></i>
+				<i v-else-if="note.visibility === 'followers'" class="ti ti-lock"></i>
 				<i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
 			</span>
 			<span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span>
@@ -45,7 +45,7 @@
 					<div class="info">
 						<span v-if="appearNote.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[appearNote.visibility]">
 							<i v-if="appearNote.visibility === 'home'" class="ti ti-home"></i>
-							<i v-else-if="appearNote.visibility === 'followers'" class="ti ti-lock-open"></i>
+							<i v-else-if="appearNote.visibility === 'followers'" class="ti ti-lock"></i>
 							<i v-else-if="appearNote.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
 						</span>
 						<span v-if="appearNote.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span>
diff --git a/packages/frontend/src/components/MkNoteHeader.vue b/packages/frontend/src/components/MkNoteHeader.vue
index ddd25e784f..38bd33ff74 100644
--- a/packages/frontend/src/components/MkNoteHeader.vue
+++ b/packages/frontend/src/components/MkNoteHeader.vue
@@ -11,7 +11,7 @@
 		</MkA>
 		<span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[note.visibility]">
 			<i v-if="note.visibility === 'home'" class="ti ti-home"></i>
-			<i v-else-if="note.visibility === 'followers'" class="ti ti-lock-open"></i>
+			<i v-else-if="note.visibility === 'followers'" class="ti ti-lock"></i>
 			<i v-else-if="note.visibility === 'specified'" ref="specified" class="ti ti-mail"></i>
 		</span>
 		<span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span>
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index ff3b7ec1f5..4904440533 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -18,7 +18,7 @@
 			<button ref="visibilityButton" v-tooltip="i18n.ts.visibility" class="_button visibility" :disabled="channel != null" @click="setVisibility">
 				<span v-if="visibility === 'public'"><i class="ti ti-world"></i></span>
 				<span v-if="visibility === 'home'"><i class="ti ti-home"></i></span>
-				<span v-if="visibility === 'followers'"><i class="ti ti-lock-open"></i></span>
+				<span v-if="visibility === 'followers'"><i class="ti ti-lock"></i></span>
 				<span v-if="visibility === 'specified'"><i class="ti ti-mail"></i></span>
 			</button>
 			<button v-tooltip="i18n.ts.previewNoteText" class="_button preview" :class="{ active: showPreview }" @click="showPreview = !showPreview"><i class="ti ti-eye"></i></button>
diff --git a/packages/frontend/src/components/MkVisibilityPicker.vue b/packages/frontend/src/components/MkVisibilityPicker.vue
index 4c78659f5c..b05590e5ee 100644
--- a/packages/frontend/src/components/MkVisibilityPicker.vue
+++ b/packages/frontend/src/components/MkVisibilityPicker.vue
@@ -16,7 +16,7 @@
 			</div>
 		</button>
 		<button key="followers" class="_button item" :class="{ active: v === 'followers' }" data-index="3" @click="choose('followers')">
-			<div class="icon"><i class="ti ti-lock-open"></i></div>
+			<div class="icon"><i class="ti ti-lock"></i></div>
 			<div class="body">
 				<span>{{ i18n.ts._visibility.followers }}</span>
 				<span>{{ i18n.ts._visibility.followersDescription }}</span>

From 4c9b93a12f1d9b4b8659d7663eb5e12d218560fe Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 13:39:16 +0900
Subject: [PATCH 46/68] :art:

---
 packages/frontend/src/ui/_common_/common.ts | 16 +++-------------
 1 file changed, 3 insertions(+), 13 deletions(-)

diff --git a/packages/frontend/src/ui/_common_/common.ts b/packages/frontend/src/ui/_common_/common.ts
index c3b22cd9e1..c63e962a8d 100644
--- a/packages/frontend/src/ui/_common_/common.ts
+++ b/packages/frontend/src/ui/_common_/common.ts
@@ -48,21 +48,11 @@ export function openInstanceMenu(ev: MouseEvent) {
 			icon: 'ti ti-cookie',
 		}],
 	}, null, {
-		type: 'parent',
 		text: i18n.ts.help,
 		icon: 'ti ti-question-circle',
-		children: [{
-			type: 'link',
-			to: '/mfm-cheat-sheet',
-			text: i18n.ts._mfm.cheatSheet,
-			icon: 'ti ti-code',
-		}, null, {
-			text: i18n.ts.document,
-			icon: 'ti ti-question-circle',
-			action: () => {
-				window.open('https://misskey-hub.net/help.html', '_blank');
-			},
-		}],
+		action: () => {
+			window.open('https://misskey-hub.net/help.html', '_blank');
+		},
 	}, {
 		type: 'link',
 		text: i18n.ts.aboutMisskey,

From eba6b326fac926259d064e93182c2bede206d12c Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 13:45:04 +0900
Subject: [PATCH 47/68] 13.0.0-beta.32

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 277389de52..4e72612e0d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "misskey",
-	"version": "13.0.0-beta.31",
+	"version": "13.0.0-beta.32",
 	"codename": "indigo",
 	"repository": {
 		"type": "git",

From 402b234d158b3a9436765782d514d8bafa7e0245 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 13:56:30 +0900
Subject: [PATCH 48/68] :art:

---
 packages/frontend/src/pages/emojis.emoji.vue | 1 +
 1 file changed, 1 insertion(+)

diff --git a/packages/frontend/src/pages/emojis.emoji.vue b/packages/frontend/src/pages/emojis.emoji.vue
index f27d2af858..3393675a3c 100644
--- a/packages/frontend/src/pages/emojis.emoji.vue
+++ b/packages/frontend/src/pages/emojis.emoji.vue
@@ -49,6 +49,7 @@ function menu(ev) {
 	> .img {
 		width: 42px;
 		height: 42px;
+		object-fit: contain;
 	}
 
 	> .body {

From 403849805aa222e584f83116df9a25fbecc19080 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 14:00:04 +0900
Subject: [PATCH 49/68] enhance(client): force lazy load some images

---
 packages/frontend/src/components/MkInstanceCardMini.vue | 2 +-
 packages/frontend/src/pages/emojis.emoji.vue            | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/packages/frontend/src/components/MkInstanceCardMini.vue b/packages/frontend/src/components/MkInstanceCardMini.vue
index 4625de40af..3de3f8de65 100644
--- a/packages/frontend/src/components/MkInstanceCardMini.vue
+++ b/packages/frontend/src/components/MkInstanceCardMini.vue
@@ -1,6 +1,6 @@
 <template>
 <div :class="[$style.root, { yellow: instance.isNotResponding, red: instance.isBlocked, gray: instance.isSuspended }]">
-	<img class="icon" :src="getInstanceIcon(instance)" alt=""/>
+	<img class="icon" :src="getInstanceIcon(instance)" alt="" loading="lazy"/>
 	<div class="body">
 		<span class="host">{{ instance.name ?? instance.host }}</span>
 		<span class="sub _monospace"><b>{{ instance.host }}</b> / {{ instance.softwareName || '?' }} {{ instance.softwareVersion }}</span>
diff --git a/packages/frontend/src/pages/emojis.emoji.vue b/packages/frontend/src/pages/emojis.emoji.vue
index 3393675a3c..fb3bce570c 100644
--- a/packages/frontend/src/pages/emojis.emoji.vue
+++ b/packages/frontend/src/pages/emojis.emoji.vue
@@ -1,6 +1,6 @@
 <template>
 <button class="zuvgdzyu _button" @click="menu">
-	<img :src="`/emoji/${emoji.name}.webp`" class="img" :alt="emoji.name"/>
+	<img :src="`/emoji/${emoji.name}.webp`" class="img" loading="lazy"/>
 	<div class="body">
 		<div class="name _monospace">{{ emoji.name }}</div>
 		<div class="info">{{ emoji.aliases.join(' ') }}</div>

From 3cfd017538d87230ab36c42fe1392a1880ec2297 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 14:03:22 +0900
Subject: [PATCH 50/68] =?UTF-8?q?fix(server):=20=E7=89=B9=E5=AE=9A?=
 =?UTF-8?q?=E3=81=AEPNG=E7=94=BB=E5=83=8F=E3=81=AE=E3=82=A2=E3=83=83?=
 =?UTF-8?q?=E3=83=97=E3=83=AD=E3=83=BC=E3=83=89=E3=81=AB=E5=A4=B1=E6=95=97?=
 =?UTF-8?q?=E3=81=99=E3=82=8B=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-Authored-By: haru <64310155+usbharu@users.noreply.github.com>
---
 CHANGELOG.md                                 | 1 +
 packages/backend/src/core/FileInfoService.ts | 4 ++--
 2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index f468bc3cbb..44b5c30dcd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -100,6 +100,7 @@ You should also include the user name that made the change.
 - Server: pages/likeのエラーIDが重複しているのを修正 @syuilo
 - Server: pages/updateのパラメータによってはsummaryの値が更新されないのを修正 @syuilo
 - Server: Escape SQL LIKE @mei23
+- Server: 特定のPNG画像のアップロードに失敗する問題を修正 @usbharu
 - Server: 非公開のクリップのURLでOGPレンダリングされる問題を修正 @syuilo
 - Client: case insensitive emoji search @saschanaz
 - Client: InAppウィンドウが操作できなくなることがあるのを修正 @tamaina
diff --git a/packages/backend/src/core/FileInfoService.ts b/packages/backend/src/core/FileInfoService.ts
index dad94da421..67337b5056 100644
--- a/packages/backend/src/core/FileInfoService.ts
+++ b/packages/backend/src/core/FileInfoService.ts
@@ -398,13 +398,13 @@ export class FileInfoService {
 				.raw()
 				.ensureAlpha()
 				.resize(64, 64, { fit: 'inside' })
-				.toBuffer((err, buffer, { width, height }) => {
+				.toBuffer((err, buffer, info) => {
 					if (err) return reject(err);
 
 					let hash;
 
 					try {
-						hash = encode(new Uint8ClampedArray(buffer), width, height, 5, 5);
+						hash = encode(new Uint8ClampedArray(buffer), info.width, info.height, 5, 5);
 					} catch (e) {
 						return reject(e);
 					}

From e4144a17a44b98229d0c0b18969d3d86a768a43d Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 14:12:42 +0900
Subject: [PATCH 51/68] =?UTF-8?q?fix(server):=20=E3=82=A2=E3=83=B3?=
 =?UTF-8?q?=E3=83=86=E3=83=8A=E3=82=BF=E3=82=A4=E3=83=A0=E3=83=A9=E3=82=A4?=
 =?UTF-8?q?=E3=83=B3=EF=BC=88=E3=82=B9=E3=83=88=E3=83=AA=E3=83=BC=E3=83=9F?=
 =?UTF-8?q?=E3=83=B3=E3=82=B0=EF=BC=89=E3=81=8C=E3=80=81=E3=83=95=E3=82=A9?=
 =?UTF-8?q?=E3=83=AD=E3=83=BC=E3=81=97=E3=81=A6=E3=81=84=E3=81=AA=E3=81=84?=
 =?UTF-8?q?=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC=E3=81=AE=E9=8D=B5=E6=8A=95?=
 =?UTF-8?q?=E7=A8=BF=E3=82=82=E6=8B=BE=E3=81=A3=E3=81=A6=E3=81=97=E3=81=BE?=
 =?UTF-8?q?=E3=81=86=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Fix #9025
---
 CHANGELOG.md                                |  1 +
 packages/backend/src/core/AntennaService.ts | 20 ++++++--------------
 2 files changed, 7 insertions(+), 14 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 44b5c30dcd..269f8b1000 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -102,6 +102,7 @@ You should also include the user name that made the change.
 - Server: Escape SQL LIKE @mei23
 - Server: 特定のPNG画像のアップロードに失敗する問題を修正 @usbharu
 - Server: 非公開のクリップのURLでOGPレンダリングされる問題を修正 @syuilo
+- Server: アンテナタイムライン(ストリーミング)が、フォローしていないユーザーの鍵投稿も拾ってしまう @syuilo
 - Client: case insensitive emoji search @saschanaz
 - Client: InAppウィンドウが操作できなくなることがあるのを修正 @tamaina
 - Client: use proxied image for instance icon @syuilo
diff --git a/packages/backend/src/core/AntennaService.ts b/packages/backend/src/core/AntennaService.ts
index d6f326f616..1b5abce29a 100644
--- a/packages/backend/src/core/AntennaService.ts
+++ b/packages/backend/src/core/AntennaService.ts
@@ -15,8 +15,8 @@ import type { Packed } from '@/misc/schema.js';
 import { DI } from '@/di-symbols.js';
 import type { MutingsRepository, BlockingsRepository, NotesRepository, AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository, UserListJoiningsRepository } from '@/models/index.js';
 import { UtilityService } from '@/core/UtilityService.js';
-import type { OnApplicationShutdown } from '@nestjs/common';
 import { bindThis } from '@/decorators.js';
+import type { OnApplicationShutdown } from '@nestjs/common';
 
 @Injectable()
 export class AntennaService implements OnApplicationShutdown {
@@ -135,7 +135,7 @@ export class AntennaService implements OnApplicationShutdown {
 					this.globalEventServie.publishMainStream(antenna.userId, 'unreadAntenna', antenna);
 					this.pushNotificationService.pushNotification(antenna.userId, 'unreadAntennaNote', {
 						antenna: { id: antenna.id, name: antenna.name },
-						note: await this.noteEntityService.pack(note)
+						note: await this.noteEntityService.pack(note),
 					});
 				}
 			}, 2000);
@@ -144,27 +144,19 @@ export class AntennaService implements OnApplicationShutdown {
 
 	// NOTE: フォローしているユーザーのノート、リストのユーザーのノート、グループのユーザーのノート指定はパフォーマンス上の理由で無効になっている
 
-	/**
-	 * noteUserFollowers / antennaUserFollowing はどちらか一方が指定されていればよい
-	 */
 	@bindThis
-	public async checkHitAntenna(antenna: Antenna, note: (Note | Packed<'Note'>), noteUser: { id: User['id']; username: string; host: string | null; }, noteUserFollowers?: User['id'][], antennaUserFollowing?: User['id'][]): Promise<boolean> {
+	public async checkHitAntenna(antenna: Antenna, note: (Note | Packed<'Note'>), noteUser: { id: User['id']; username: string; host: string | null; }): Promise<boolean> {
 		if (note.visibility === 'specified') return false;
-	
+		if (note.visibility === 'followers') return false;
+
 		// アンテナ作成者がノート作成者にブロックされていたらスキップ
 		const blockings = await this.blockingCache.fetch(noteUser.id, () => this.blockingsRepository.findBy({ blockerId: noteUser.id }).then(res => res.map(x => x.blockeeId)));
 		if (blockings.some(blocking => blocking === antenna.userId)) return false;
 	
-		if (note.visibility === 'followers') {
-			if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId)) return false;
-			if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId)) return false;
-		}
-	
 		if (!antenna.withReplies && note.replyId != null) return false;
 	
 		if (antenna.src === 'home') {
-			if (noteUserFollowers && !noteUserFollowers.includes(antenna.userId)) return false;
-			if (antennaUserFollowing && !antennaUserFollowing.includes(note.userId)) return false;
+			// TODO
 		} else if (antenna.src === 'list') {
 			const listUsers = (await this.userListJoiningsRepository.findBy({
 				userListId: antenna.userListId!,

From 462acc9eee3f89a926fd4b46ffed0b066519759b Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 15:50:25 +0900
Subject: [PATCH 52/68] =?UTF-8?q?=E3=82=AB=E3=82=B9=E3=82=BF=E3=83=A0?=
 =?UTF-8?q?=E7=B5=B5=E6=96=87=E5=AD=97=E4=B8=80=E8=A6=A7=E6=83=85=E5=A0=B1?=
 =?UTF-8?q?=E3=82=92meta=E3=81=8B=E3=82=89=E5=88=86=E9=9B=A2?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CHANGELOG.md                                  |  2 +
 .../src/core/entities/EmojiEntityService.ts   |  8 +-
 packages/backend/src/models/schema/emoji.ts   |  8 +-
 .../backend/src/server/api/EndpointsModule.ts |  4 +
 packages/backend/src/server/api/endpoints.ts  |  2 +
 .../src/server/api/endpoints/emojis.ts        | 91 +++++++++++++++++++
 .../backend/src/server/api/endpoints/meta.ts  | 57 ------------
 .../src/components/MkAutocomplete.vue         |  5 +-
 .../frontend/src/components/MkEmojiPicker.vue |  9 +-
 packages/frontend/src/custom-emojis.ts        | 48 ++++++++++
 packages/frontend/src/instance.ts             | 24 +----
 packages/frontend/src/local-storage.ts        |  2 +
 packages/frontend/src/pages/about.emojis.vue  | 87 +++++++-----------
 .../src/pages/admin/emoji-edit-dialog.vue     |  4 +-
 .../src/pages/admin/overview.stats.vue        |  5 +-
 .../frontend/src/pages/mfm-cheat-sheet.vue    |  5 +-
 .../frontend/src/pages/settings/index.vue     |  2 +
 17 files changed, 212 insertions(+), 151 deletions(-)
 create mode 100644 packages/backend/src/server/api/endpoints/emojis.ts
 create mode 100644 packages/frontend/src/custom-emojis.ts

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 269f8b1000..43cf0ef6e9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -40,6 +40,8 @@ You should also include the user name that made the change.
 - Firefox109以下はサポートされなくなりました
 
 #### For app developers
+- API: metaのレスポンスに`emojis`プロパティが含まれなくなりました
+	- カスタム絵文字一覧情報を取得するには、`emojis`エンドポイントにリクエストします
 - API: カスタム絵文字エンティティに`url`プロパティが含まれなくなりました
 	- 絵文字画像を表示するには、`<instance host>/emoji/<emoji name>.webp`にリクエストすると画像が返ります。
 	- e.g. `https://p1.a9z.dev/emoji/misskey.webp`
diff --git a/packages/backend/src/core/entities/EmojiEntityService.ts b/packages/backend/src/core/entities/EmojiEntityService.ts
index 8a2dc70eda..2a4e09519f 100644
--- a/packages/backend/src/core/entities/EmojiEntityService.ts
+++ b/packages/backend/src/core/entities/EmojiEntityService.ts
@@ -22,23 +22,25 @@ export class EmojiEntityService {
 	@bindThis
 	public async pack(
 		src: Emoji['id'] | Emoji,
+		opts: { omitHost?: boolean; omitId?: boolean; } = {},
 	): Promise<Packed<'Emoji'>> {
 		const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src });
 
 		return {
-			id: emoji.id,
+			id: opts.omitId ? undefined : emoji.id,
 			aliases: emoji.aliases,
 			name: emoji.name,
 			category: emoji.category,
-			host: emoji.host,
+			host: opts.omitHost ? undefined : emoji.host,
 		};
 	}
 
 	@bindThis
 	public packMany(
 		emojis: any[],
+		opts: { omitHost?: boolean; omitId?: boolean; } = {},
 	) {
-		return Promise.all(emojis.map(x => this.pack(x)));
+		return Promise.all(emojis.map(x => this.pack(x, opts)));
 	}
 }
 
diff --git a/packages/backend/src/models/schema/emoji.ts b/packages/backend/src/models/schema/emoji.ts
index 9a52609b68..d897a0fc05 100644
--- a/packages/backend/src/models/schema/emoji.ts
+++ b/packages/backend/src/models/schema/emoji.ts
@@ -3,7 +3,7 @@ export const packedEmojiSchema = {
 	properties: {
 		id: {
 			type: 'string',
-			optional: false, nullable: false,
+			optional: true, nullable: false,
 			format: 'id',
 			example: 'xxxxxxxxxx',
 		},
@@ -26,12 +26,8 @@ export const packedEmojiSchema = {
 		},
 		host: {
 			type: 'string',
-			optional: false, nullable: true,
+			optional: true, nullable: true,
 			description: 'The local host is represented with `null`.',
 		},
-		url: {
-			type: 'string',
-			optional: true, nullable: false,
-		},
 	},
 } as const;
diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts
index 60beca4f47..ab9349966d 100644
--- a/packages/backend/src/server/api/EndpointsModule.ts
+++ b/packages/backend/src/server/api/EndpointsModule.ts
@@ -220,6 +220,7 @@ import * as ep___messaging_messages_create from './endpoints/messaging/messages/
 import * as ep___messaging_messages_delete from './endpoints/messaging/messages/delete.js';
 import * as ep___messaging_messages_read from './endpoints/messaging/messages/read.js';
 import * as ep___meta from './endpoints/meta.js';
+import * as ep___emojis from './endpoints/emojis.js';
 import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
 import * as ep___mute_create from './endpoints/mute/create.js';
 import * as ep___mute_delete from './endpoints/mute/delete.js';
@@ -550,6 +551,7 @@ const $messaging_messages_create: Provider = { provide: 'ep:messaging/messages/c
 const $messaging_messages_delete: Provider = { provide: 'ep:messaging/messages/delete', useClass: ep___messaging_messages_delete.default };
 const $messaging_messages_read: Provider = { provide: 'ep:messaging/messages/read', useClass: ep___messaging_messages_read.default };
 const $meta: Provider = { provide: 'ep:meta', useClass: ep___meta.default };
+const $emojis: Provider = { provide: 'ep:emojis', useClass: ep___emojis.default };
 const $miauth_genToken: Provider = { provide: 'ep:miauth/gen-token', useClass: ep___miauth_genToken.default };
 const $mute_create: Provider = { provide: 'ep:mute/create', useClass: ep___mute_create.default };
 const $mute_delete: Provider = { provide: 'ep:mute/delete', useClass: ep___mute_delete.default };
@@ -884,6 +886,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$messaging_messages_delete,
 		$messaging_messages_read,
 		$meta,
+		$emojis,
 		$miauth_genToken,
 		$mute_create,
 		$mute_delete,
@@ -1212,6 +1215,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
 		$messaging_messages_delete,
 		$messaging_messages_read,
 		$meta,
+		$emojis,
 		$miauth_genToken,
 		$mute_create,
 		$mute_delete,
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index d4f8be5b85..f9749ad660 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -219,6 +219,7 @@ import * as ep___messaging_messages_create from './endpoints/messaging/messages/
 import * as ep___messaging_messages_delete from './endpoints/messaging/messages/delete.js';
 import * as ep___messaging_messages_read from './endpoints/messaging/messages/read.js';
 import * as ep___meta from './endpoints/meta.js';
+import * as ep___emojis from './endpoints/emojis.js';
 import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
 import * as ep___mute_create from './endpoints/mute/create.js';
 import * as ep___mute_delete from './endpoints/mute/delete.js';
@@ -547,6 +548,7 @@ const eps = [
 	['messaging/messages/delete', ep___messaging_messages_delete],
 	['messaging/messages/read', ep___messaging_messages_read],
 	['meta', ep___meta],
+	['emojis', ep___emojis],
 	['miauth/gen-token', ep___miauth_genToken],
 	['mute/create', ep___mute_create],
 	['mute/delete', ep___mute_delete],
diff --git a/packages/backend/src/server/api/endpoints/emojis.ts b/packages/backend/src/server/api/endpoints/emojis.ts
new file mode 100644
index 0000000000..0a16268229
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/emojis.ts
@@ -0,0 +1,91 @@
+import { IsNull, MoreThan } from 'typeorm';
+import { Inject, Injectable } from '@nestjs/common';
+import type { EmojisRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
+import type { Config } from '@/config.js';
+import { DI } from '@/di-symbols.js';
+
+export const meta = {
+	tags: ['meta'],
+
+	requireCredential: false,
+
+	res: {
+		type: 'object',
+		optional: false, nullable: false,
+		properties: {
+			emojis: {
+				type: 'array',
+				optional: false, nullable: false,
+				items: {
+					type: 'object',
+					optional: false, nullable: false,
+					properties: {
+						id: {
+							type: 'string',
+							optional: false, nullable: false,
+							format: 'id',
+						},
+						aliases: {
+							type: 'array',
+							optional: false, nullable: false,
+							items: {
+								type: 'string',
+								optional: false, nullable: false,
+							},
+						},
+						category: {
+							type: 'string',
+							optional: false, nullable: true,
+						},
+					},
+				},
+			},
+		},
+	},
+} as const;
+
+export const paramDef = {
+	type: 'object',
+	properties: {
+	},
+	required: [],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+	constructor(
+		@Inject(DI.config)
+		private config: Config,
+	
+		@Inject(DI.emojisRepository)
+		private emojisRepository: EmojisRepository,
+
+		private emojiEntityService: EmojiEntityService,
+	) {
+		super(meta, paramDef, async (ps, me) => {
+			const emojis = await this.emojisRepository.find({
+				where: {
+					host: IsNull(),
+				},
+				order: {
+					category: 'ASC',
+					name: 'ASC',
+				},
+				cache: {
+					id: 'meta_emojis',
+					milliseconds: 3600000,	// 1 hour
+				},
+			});
+
+			return {
+				emojis: await this.emojiEntityService.packMany(emojis, {
+					omitId: true,
+					omitHost: true,
+				}),
+			};
+		});
+	}
+}
diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts
index 05da011979..c44d63d64b 100644
--- a/packages/backend/src/server/api/endpoints/meta.ts
+++ b/packages/backend/src/server/api/endpoints/meta.ts
@@ -4,7 +4,6 @@ import type { AdsRepository, EmojisRepository, UsersRepository } from '@/models/
 import { MAX_NOTE_TEXT_LENGTH, DB_MAX_NOTE_TEXT_LENGTH } from '@/const.js';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import { UserEntityService } from '@/core/entities/UserEntityService.js';
-import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
 import { MetaService } from '@/core/MetaService.js';
 import type { Config } from '@/config.js';
 import { DI } from '@/di-symbols.js';
@@ -152,43 +151,6 @@ export const meta = {
 				type: 'number',
 				optional: false, nullable: false,
 			},
-			emojis: {
-				type: 'array',
-				optional: false, nullable: false,
-				items: {
-					type: 'object',
-					optional: false, nullable: false,
-					properties: {
-						id: {
-							type: 'string',
-							optional: false, nullable: false,
-							format: 'id',
-						},
-						aliases: {
-							type: 'array',
-							optional: false, nullable: false,
-							items: {
-								type: 'string',
-								optional: false, nullable: false,
-							},
-						},
-						category: {
-							type: 'string',
-							optional: false, nullable: true,
-						},
-						host: {
-							type: 'string',
-							optional: false, nullable: true,
-							description: 'The local host is represented with `null`.',
-						},
-						url: {
-							type: 'string',
-							optional: false, nullable: false,
-							format: 'url',
-						},
-					},
-				},
-			},
 			ads: {
 				type: 'array',
 				optional: false, nullable: false,
@@ -326,30 +288,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 		@Inject(DI.adsRepository)
 		private adsRepository: AdsRepository,
 
-		@Inject(DI.emojisRepository)
-		private emojisRepository: EmojisRepository,
-
 		private userEntityService: UserEntityService,
-		private emojiEntityService: EmojiEntityService,
 		private metaService: MetaService,
 	) {
 		super(meta, paramDef, async (ps, me) => {
 			const instance = await this.metaService.fetch(true);
 
-			const emojis = await this.emojisRepository.find({
-				where: {
-					host: IsNull(),
-				},
-				order: {
-					category: 'ASC',
-					name: 'ASC',
-				},
-				cache: {
-					id: 'meta_emojis',
-					milliseconds: 3600000,	// 1 hour
-				},
-			});
-
 			const ads = await this.adsRepository.find({
 				where: {
 					expiresAt: MoreThan(new Date()),
@@ -390,7 +334,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				backgroundImageUrl: instance.backgroundImageUrl,
 				logoImageUrl: instance.logoImageUrl,
 				maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため
-				emojis: await this.emojiEntityService.packMany(emojis),
 				defaultLightTheme: instance.defaultLightTheme,
 				defaultDarkTheme: instance.defaultDarkTheme,
 				ads: ads.map(ad => ({
diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue
index 8ed60bc5dc..0d0c5edbae 100644
--- a/packages/frontend/src/components/MkAutocomplete.vue
+++ b/packages/frontend/src/components/MkAutocomplete.vue
@@ -47,6 +47,9 @@ import { emojilist } from '@/scripts/emojilist';
 import { instance } from '@/instance';
 import { i18n } from '@/i18n';
 import { miLocalStorage } from '@/local-storage';
+import { getCustomEmojis } from '@/custom-emojis';
+
+const customEmojis = await getCustomEmojis();
 
 type EmojiDef = {
 	emoji: string;
@@ -86,7 +89,6 @@ for (const x of lib) {
 emjdb.sort((a, b) => a.name.length - b.name.length);
 
 //#region Construct Emoji DB
-const customEmojis = instance.emojis;
 const emojiDefinitions: EmojiDef[] = [];
 
 for (const x of customEmojis) {
@@ -117,7 +119,6 @@ export default {
 	emojiDb,
 	emojiDefinitions,
 	emojilist,
-	customEmojis,
 };
 </script>
 
diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue
index f3b3ac0b50..8df01f6c25 100644
--- a/packages/frontend/src/components/MkEmojiPicker.vue
+++ b/packages/frontend/src/components/MkEmojiPicker.vue
@@ -6,7 +6,7 @@
 			<div v-if="searchResultCustom.length > 0" class="body">
 				<button
 					v-for="emoji in searchResultCustom"
-					:key="emoji.id"
+					:key="emoji.name"
 					class="_button item"
 					:title="emoji.name"
 					tabindex="0"
@@ -85,9 +85,10 @@ import MkRippleEffect from '@/components/MkRippleEffect.vue';
 import * as os from '@/os';
 import { isTouchUsing } from '@/scripts/touch';
 import { deviceKind } from '@/scripts/device-kind';
-import { emojiCategories, instance } from '@/instance';
+import { instance } from '@/instance';
 import { i18n } from '@/i18n';
 import { defaultStore } from '@/store';
+import { getCustomEmojiCategories, getCustomEmojis } from '@/custom-emojis';
 
 const props = withDefaults(defineProps<{
 	showPinned?: boolean;
@@ -103,6 +104,7 @@ const emit = defineEmits<{
 	(ev: 'chosen', v: string): void;
 }>();
 
+const customEmojis = await getCustomEmojis();
 const search = shallowRef<HTMLInputElement>();
 const emojis = shallowRef<HTMLDivElement>();
 
@@ -118,8 +120,7 @@ const {
 const size = computed(() => props.asReactionPicker ? reactionPickerSize.value : 1);
 const width = computed(() => props.asReactionPicker ? reactionPickerWidth.value : 3);
 const height = computed(() => props.asReactionPicker ? reactionPickerHeight.value : 2);
-const customEmojiCategories = emojiCategories;
-const customEmojis = instance.emojis;
+const customEmojiCategories = await getCustomEmojiCategories();
 const q = ref<string>('');
 const searchResultCustom = ref<Misskey.entities.CustomEmoji[]>([]);
 const searchResultUnicode = ref<UnicodeEmojiDef[]>([]);
diff --git a/packages/frontend/src/custom-emojis.ts b/packages/frontend/src/custom-emojis.ts
new file mode 100644
index 0000000000..2a52753f84
--- /dev/null
+++ b/packages/frontend/src/custom-emojis.ts
@@ -0,0 +1,48 @@
+import { api } from './os';
+import { miLocalStorage } from './local-storage';
+
+const storageCache = miLocalStorage.getItem('emojis');
+let cached = storageCache ? JSON.parse(storageCache) : null;
+export async function getCustomEmojis() {
+	const now = Date.now();
+	const lastFetchedAt = miLocalStorage.getItem('lastEmojisFetchedAt');
+	if (cached && lastFetchedAt && (now - parseInt(lastFetchedAt)) < 1000 * 60 * 60) return cached;
+
+	const res = await api('emojis', {});
+
+	cached = res.emojis;
+	miLocalStorage.setItem('emojis', JSON.stringify(cached));
+	miLocalStorage.setItem('lastEmojisFetchedAt', now.toString());
+}
+
+let cachedCategories;
+export async function getCustomEmojiCategories() {
+	if (cachedCategories) return cachedCategories;
+
+	const customEmojis = await getCustomEmojis();
+
+	const categories = new Set();
+	for (const emoji of customEmojis) {
+		categories.add(emoji.category);
+	}
+	const res = Array.from(categories);
+	cachedCategories = res;
+	return res;
+}
+
+let cachedTags;
+export async function getCustomEmojiTags() {
+	if (cachedTags) return cachedTags;
+
+	const customEmojis = await getCustomEmojis();
+
+	const tags = new Set();
+	for (const emoji of customEmojis) {
+		for (const tag of emoji.aliases) {
+			tags.add(tag);
+		}
+	}
+	const res = Array.from(tags);
+	cachedTags = res;
+	return res;
+}
diff --git a/packages/frontend/src/instance.ts b/packages/frontend/src/instance.ts
index 82d3e7aea2..08dbd9737c 100644
--- a/packages/frontend/src/instance.ts
+++ b/packages/frontend/src/instance.ts
@@ -5,11 +5,11 @@ import { miLocalStorage } from './local-storage';
 
 // TODO: 他のタブと永続化されたstateを同期
 
-const instanceData = miLocalStorage.getItem('instance');
+const cached = miLocalStorage.getItem('instance');
 
 // TODO: instanceをリアクティブにするかは再考の余地あり
 
-export const instance: Misskey.entities.InstanceMetadata = reactive(instanceData ? JSON.parse(instanceData) : {
+export const instance: Misskey.entities.InstanceMetadata = reactive(cached ? JSON.parse(cached) : {
 	// TODO: set default values
 });
 
@@ -24,23 +24,3 @@ export async function fetchInstance() {
 
 	miLocalStorage.setItem('instance', JSON.stringify(instance));
 }
-
-export const emojiCategories = computed(() => {
-	if (instance.emojis == null) return [];
-	const categories = new Set();
-	for (const emoji of instance.emojis) {
-		categories.add(emoji.category);
-	}
-	return Array.from(categories);
-});
-
-export const emojiTags = computed(() => {
-	if (instance.emojis == null) return [];
-	const tags = new Set();
-	for (const emoji of instance.emojis) {
-		for (const tag of emoji.aliases) {
-			tags.add(tag);
-		}
-	}
-	return Array.from(tags);
-});
diff --git a/packages/frontend/src/local-storage.ts b/packages/frontend/src/local-storage.ts
index 50e28d621f..bb8192e980 100644
--- a/packages/frontend/src/local-storage.ts
+++ b/packages/frontend/src/local-storage.ts
@@ -2,6 +2,8 @@ type Keys =
 	'v' |
 	'lastVersion' |
 	'instance' |
+	'emojis' | // TODO: indexed db
+	'lastEmojisFetchedAt' |
 	'account' |
 	'accounts' |
 	'latestDonationInfoShownAt' |
diff --git a/packages/frontend/src/pages/about.emojis.vue b/packages/frontend/src/pages/about.emojis.vue
index e15a2b1bdf..acf6237c87 100644
--- a/packages/frontend/src/pages/about.emojis.vue
+++ b/packages/frontend/src/pages/about.emojis.vue
@@ -7,7 +7,7 @@
 
 		<!-- たくさんあると邪魔
 		<div class="tags">
-			<span class="tag _button" v-for="tag in tags" :class="{ active: selectedTags.has(tag) }" @click="toggleTag(tag)">{{ tag }}</span>
+			<span class="tag _button" v-for="tag in customEmojiTags" :class="{ active: selectedTags.has(tag) }" @click="toggleTag(tag)">{{ tag }}</span>
 		</div>
 		-->
 	</div>
@@ -28,8 +28,8 @@
 </div>
 </template>
 
-<script lang="ts">
-import { defineComponent, computed } from 'vue';
+<script lang="ts" setup>
+import { defineComponent, computed, watch } from 'vue';
 import XEmoji from './emojis.emoji.vue';
 import MkButton from '@/components/MkButton.vue';
 import MkInput from '@/components/MkInput.vue';
@@ -37,62 +37,43 @@ import MkSelect from '@/components/MkSelect.vue';
 import MkFoldableSection from '@/components/MkFoldableSection.vue';
 import MkTab from '@/components/MkTab.vue';
 import * as os from '@/os';
-import { emojiCategories, emojiTags } from '@/instance';
+import { getCustomEmojis, getCustomEmojiCategories, getCustomEmojiTags } from '@/custom-emojis';
 
-export default defineComponent({
-	components: {
-		MkButton,
-		MkInput,
-		MkSelect,
-		MkFoldableSection,
-		MkTab,
-		XEmoji,
-	},
+const customEmojis = await getCustomEmojis();
+const customEmojiCategories = await getCustomEmojiCategories();
+const customEmojiTags = await getCustomEmojiTags();
+let q = $ref('');
+let searchEmojis = $ref(null);
+let selectedTags = $ref(new Set());
 
-	data() {
-		return {
-			q: '',
-			customEmojiCategories: emojiCategories,
-			customEmojis: this.$instance.emojis,
-			tags: emojiTags,
-			selectedTags: new Set(),
-			searchEmojis: null,
-		};
-	},
+function search() {
+	if ((q === '' || q == null) && selectedTags.size === 0) {
+		searchEmojis = null;
+		return;
+	}
 
-	watch: {
-		q() { this.search(); },
-		selectedTags: {
-			handler() {
-				this.search();
-			},
-			deep: true,
-		},
-	},
+	if (selectedTags.size === 0) {
+		searchEmojis = customEmojis.filter(emoji => emoji.name.includes(q) || emoji.aliases.includes(q));
+	} else {
+		searchEmojis = customEmojis.filter(emoji => (emoji.name.includes(q) || emoji.aliases.includes(q)) && [...selectedTags].every(t => emoji.aliases.includes(t)));
+	}
+}
 
-	methods: {
-		search() {
-			if ((this.q === '' || this.q == null) && this.selectedTags.size === 0) {
-				this.searchEmojis = null;
-				return;
-			}
+function toggleTag(tag) {
+	if (selectedTags.has(tag)) {
+		selectedTags.delete(tag);
+	} else {
+		selectedTags.add(tag);
+	}
+}
 
-			if (this.selectedTags.size === 0) {
-				this.searchEmojis = this.customEmojis.filter(emoji => emoji.name.includes(this.q) || emoji.aliases.includes(this.q));
-			} else {
-				this.searchEmojis = this.customEmojis.filter(emoji => (emoji.name.includes(this.q) || emoji.aliases.includes(this.q)) && [...this.selectedTags].every(t => emoji.aliases.includes(t)));
-			}
-		},
-
-		toggleTag(tag) {
-			if (this.selectedTags.has(tag)) {
-				this.selectedTags.delete(tag);
-			} else {
-				this.selectedTags.add(tag);
-			}
-		},
-	},
+watch($$(q), () => {
+	search();
 });
+
+watch($$(selectedTags), () => {
+	search();
+}, { deep: true });
 </script>
 
 <style lang="scss" scoped>
diff --git a/packages/frontend/src/pages/admin/emoji-edit-dialog.vue b/packages/frontend/src/pages/admin/emoji-edit-dialog.vue
index c0a997d7b4..0b6a5e1557 100644
--- a/packages/frontend/src/pages/admin/emoji-edit-dialog.vue
+++ b/packages/frontend/src/pages/admin/emoji-edit-dialog.vue
@@ -36,7 +36,7 @@ import MkInput from '@/components/MkInput.vue';
 import * as os from '@/os';
 import { unique } from '@/scripts/array';
 import { i18n } from '@/i18n';
-import { emojiCategories } from '@/instance';
+import { getCustomEmojiCategories } from '@/custom-emojis';
 
 const props = defineProps<{
 	emoji: any,
@@ -46,7 +46,7 @@ let dialog = $ref(null);
 let name: string = $ref(props.emoji.name);
 let category: string = $ref(props.emoji.category);
 let aliases: string = $ref(props.emoji.aliases.join(' '));
-let categories: string[] = $ref(emojiCategories);
+const categories = await getCustomEmojiCategories();
 
 const emit = defineEmits<{
 	(ev: 'done', v: { deleted?: boolean, updated?: any }): void,
diff --git a/packages/frontend/src/pages/admin/overview.stats.vue b/packages/frontend/src/pages/admin/overview.stats.vue
index 43a735cbf9..08f31676f3 100644
--- a/packages/frontend/src/pages/admin/overview.stats.vue
+++ b/packages/frontend/src/pages/admin/overview.stats.vue
@@ -36,7 +36,7 @@
 				<div class="icon"><i class="ti ti-icons"></i></div>
 				<div class="body">
 					<div class="value">
-						<MkNumber :value="$instance.emojis.length" style="margin-right: 0.5em;"/>
+						<MkNumber :value="customEmojis.length" style="margin-right: 0.5em;"/>
 					</div>
 					<div class="label">Custom emojis</div>
 				</div>
@@ -63,6 +63,9 @@ import number from '@/filters/number';
 import MkNumberDiff from '@/components/MkNumberDiff.vue';
 import MkNumber from '@/components/MkNumber.vue';
 import { i18n } from '@/i18n';
+import { getCustomEmojis } from '@/custom-emojis';
+
+const customEmojis = await getCustomEmojis();
 
 let stats: any = $ref(null);
 let usersComparedToThePrevDay = $ref<number>();
diff --git a/packages/frontend/src/pages/mfm-cheat-sheet.vue b/packages/frontend/src/pages/mfm-cheat-sheet.vue
index 697a692743..f49b6959c8 100644
--- a/packages/frontend/src/pages/mfm-cheat-sheet.vue
+++ b/packages/frontend/src/pages/mfm-cheat-sheet.vue
@@ -317,12 +317,15 @@ import MkTextarea from '@/components/MkTextarea.vue';
 import { definePageMetadata } from '@/scripts/page-metadata';
 import { i18n } from '@/i18n';
 import { instance } from '@/instance';
+import { getCustomEmojis } from '@/custom-emojis';
+
+const customEmojis = await getCustomEmojis();
 
 let preview_mention = $ref('@example');
 let preview_hashtag = $ref('#test');
 let preview_url = $ref('https://example.com');
 let preview_link = $ref(`[${i18n.ts._mfm.dummy}](https://example.com)`);
-let preview_emoji = $ref(instance.emojis.length ? `:${instance.emojis[0].name}:` : ':emojiname:');
+let preview_emoji = $ref(customEmojis.length ? `:${customEmojis[0].name}:` : ':emojiname:');
 let preview_bold = $ref(`**${i18n.ts._mfm.dummy}**`);
 let preview_small = $ref(`<small>${i18n.ts._mfm.dummy}</small>`);
 let preview_center = $ref(`<center>${i18n.ts._mfm.dummy}</center>`);
diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue
index 3468d44e00..e1e050ee70 100644
--- a/packages/frontend/src/pages/settings/index.vue
+++ b/packages/frontend/src/pages/settings/index.vue
@@ -183,6 +183,8 @@ const menuDef = computed(() => [{
 		action: () => {
 			miLocalStorage.removeItem('locale');
 			miLocalStorage.removeItem('theme');
+			miLocalStorage.removeItem('emojis');
+			miLocalStorage.removeItem('lastEmojisFetchedAt');
 			unisonReload();
 		},
 	}, {

From c438bd2e277a9e0400d1a0773389f788e6634dbf Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 15:51:36 +0900
Subject: [PATCH 53/68] Update vite.config.ts

---
 packages/frontend/vite.config.ts | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts
index 80f50b6cfb..40daf8b7cb 100644
--- a/packages/frontend/vite.config.ts
+++ b/packages/frontend/vite.config.ts
@@ -41,11 +41,6 @@ export default defineConfig(({ command, mode }) => {
 		},
 
 		build: {
-			target: [
-				'chrome100',
-				'firefox100',
-				'safari15',
-			],
 			manifest: 'manifest.json',
 			rollupOptions: {
 				input: {

From 6a18360269ce5d4a53db02c898efb8f4b1d1ff2a Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 16:07:14 +0900
Subject: [PATCH 54/68] update mfm-js

---
 packages/backend/package.json  |  2 +-
 packages/frontend/package.json |  2 +-
 yarn.lock                      | 12 ++++++------
 3 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/packages/backend/package.json b/packages/backend/package.json
index 31dac2156d..293b630ad6 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -72,7 +72,7 @@
 		"json5-loader": "4.0.1",
 		"jsonld": "8.1.0",
 		"jsrsasign": "10.6.1",
-		"mfm-js": "0.23.0",
+		"mfm-js": "0.23.1",
 		"mime-types": "2.1.35",
 		"misskey-js": "0.0.14",
 		"ms": "3.0.0-canary.1",
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index 234490e506..72a9532c61 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -38,7 +38,7 @@
 		"json5": "2.2.3",
 		"katex": "0.16.4",
 		"matter-js": "0.18.0",
-		"mfm-js": "0.23.0",
+		"mfm-js": "0.23.1",
 		"misskey-js": "0.0.14",
 		"photoswipe": "5.3.4",
 		"prismjs": "1.29.0",
diff --git a/yarn.lock b/yarn.lock
index 35c9103280..c46e744c3d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4232,7 +4232,7 @@ __metadata:
     json5-loader: 4.0.1
     jsonld: 8.1.0
     jsrsasign: 10.6.1
-    mfm-js: 0.23.0
+    mfm-js: 0.23.1
     mime-types: 2.1.35
     misskey-js: 0.0.14
     ms: 3.0.0-canary.1
@@ -8092,7 +8092,7 @@ __metadata:
     json5: 2.2.3
     katex: 0.16.4
     matter-js: 0.18.0
-    mfm-js: 0.23.0
+    mfm-js: 0.23.1
     misskey-js: 0.0.14
     photoswipe: 5.3.4
     prismjs: 1.29.0
@@ -11592,12 +11592,12 @@ __metadata:
   languageName: node
   linkType: hard
 
-"mfm-js@npm:0.23.0":
-  version: 0.23.0
-  resolution: "mfm-js@npm:0.23.0"
+"mfm-js@npm:0.23.1":
+  version: 0.23.1
+  resolution: "mfm-js@npm:0.23.1"
   dependencies:
     twemoji-parser: 14.0.0
-  checksum: 0351c22be1074c83fa02fae5f34e481c7f509fe012022f767ea505fb9b633e50e9717cb47683b62dc2130993fd470110f48ebddd9b33f39416fd4151220dfe3e
+  checksum: 1c489ee30db7b4a2abd979f5e6453edb0c2e5ebb45e303cf4275215bb606620cf7df4b22442dfc560f6d2724986ed03153a301684a19a5decd72e8f8b5b3b81b
   languageName: node
   linkType: hard
 

From c65957853b372c0812982aa83cc0653bd954c5ac Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 16:08:25 +0900
Subject: [PATCH 55/68] 13.0.0-beta.33

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index 4e72612e0d..eb1ab711ea 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "misskey",
-	"version": "13.0.0-beta.32",
+	"version": "13.0.0-beta.33",
 	"codename": "indigo",
 	"repository": {
 		"type": "git",

From ed43369797c023e7094163326edded0577c78a72 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 16:09:40 +0900
Subject: [PATCH 56/68] Update CHANGELOG.md

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 43cf0ef6e9..5247f5b05c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -105,6 +105,7 @@ You should also include the user name that made the change.
 - Server: 特定のPNG画像のアップロードに失敗する問題を修正 @usbharu
 - Server: 非公開のクリップのURLでOGPレンダリングされる問題を修正 @syuilo
 - Server: アンテナタイムライン(ストリーミング)が、フォローしていないユーザーの鍵投稿も拾ってしまう @syuilo
+- Client: 日付形式の文字列などがカスタム絵文字として表示されるのを修正 @syuilo
 - Client: case insensitive emoji search @saschanaz
 - Client: InAppウィンドウが操作できなくなることがあるのを修正 @tamaina
 - Client: use proxied image for instance icon @syuilo

From 6071e962f4cac2a2f0c930e1ddbdf70b8117dbef Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 16:16:00 +0900
Subject: [PATCH 57/68] Revert "Update vite.config.ts"

This reverts commit c438bd2e277a9e0400d1a0773389f788e6634dbf.
---
 packages/frontend/vite.config.ts | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts
index 40daf8b7cb..80f50b6cfb 100644
--- a/packages/frontend/vite.config.ts
+++ b/packages/frontend/vite.config.ts
@@ -41,6 +41,11 @@ export default defineConfig(({ command, mode }) => {
 		},
 
 		build: {
+			target: [
+				'chrome100',
+				'firefox100',
+				'safari15',
+			],
 			manifest: 'manifest.json',
 			rollupOptions: {
 				input: {

From 3ece2dc9909be2e25c60c3a33a32356715a8acec Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 16:16:25 +0900
Subject: [PATCH 58/68] 13.0.0-beta.34

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index eb1ab711ea..ca4d44c397 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "misskey",
-	"version": "13.0.0-beta.33",
+	"version": "13.0.0-beta.34",
 	"codename": "indigo",
 	"repository": {
 		"type": "git",

From 4d66077f85d7d7a3e274c4027429475e6bda7dd2 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 16:27:09 +0900
Subject: [PATCH 59/68] Update MkEmojiPicker.vue

---
 .../frontend/src/components/MkEmojiPicker.vue | 19 +++++++++++++------
 1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue
index 8df01f6c25..18950b18d3 100644
--- a/packages/frontend/src/components/MkEmojiPicker.vue
+++ b/packages/frontend/src/components/MkEmojiPicker.vue
@@ -1,7 +1,7 @@
 <template>
 <div class="omfetrab" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer, asWindow }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }">
 	<input ref="search" :value="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" type="search" @input="input()" @paste.stop="paste" @keyup.enter="done()">
-	<div ref="emojis" class="emojis">
+	<div v-if="customEmojis != null && customEmojiCategories != null" ref="emojisEl" class="emojis">
 		<section class="result">
 			<div v-if="searchResultCustom.length > 0" class="body">
 				<button
@@ -104,9 +104,17 @@ const emit = defineEmits<{
 	(ev: 'chosen', v: string): void;
 }>();
 
-const customEmojis = await getCustomEmojis();
+let customEmojis = $ref(null);
+getCustomEmojis().then((x) => {
+	customEmojis = x;
+});
+let customEmojiCategories = $ref(null);
+getCustomEmojiCategories().then((x) => {
+	customEmojiCategories = x;
+});
+
 const search = shallowRef<HTMLInputElement>();
-const emojis = shallowRef<HTMLDivElement>();
+const emojisEl = shallowRef<HTMLDivElement>();
 
 const {
 	reactions: pinned,
@@ -120,14 +128,13 @@ const {
 const size = computed(() => props.asReactionPicker ? reactionPickerSize.value : 1);
 const width = computed(() => props.asReactionPicker ? reactionPickerWidth.value : 3);
 const height = computed(() => props.asReactionPicker ? reactionPickerHeight.value : 2);
-const customEmojiCategories = await getCustomEmojiCategories();
 const q = ref<string>('');
 const searchResultCustom = ref<Misskey.entities.CustomEmoji[]>([]);
 const searchResultUnicode = ref<UnicodeEmojiDef[]>([]);
 const tab = ref<'index' | 'custom' | 'unicode' | 'tags'>('index');
 
 watch(q, () => {
-	if (emojis.value) emojis.value.scrollTop = 0;
+	if (emojisEl.value) emojisEl.value.scrollTop = 0;
 
 	if (q.value === '') {
 		searchResultCustom.value = [];
@@ -276,7 +283,7 @@ function focus() {
 }
 
 function reset() {
-	if (emojis.value) emojis.value.scrollTop = 0;
+	if (emojisEl.value) emojisEl.value.scrollTop = 0;
 	q.value = '';
 }
 

From 8cc80faf2067470489006f8cce8493e253424033 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 16:27:27 +0900
Subject: [PATCH 60/68] 13.0.0-beta.35

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index ca4d44c397..ddba184f3d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "misskey",
-	"version": "13.0.0-beta.34",
+	"version": "13.0.0-beta.35",
 	"codename": "indigo",
 	"repository": {
 		"type": "git",

From 39c3995c74380a5b9b473583594966c26c008777 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 16:45:05 +0900
Subject: [PATCH 61/68] refactor

---
 .../src/components/MkAutocomplete.vue         |  4 +---
 .../frontend/src/components/MkEmojiPicker.vue | 14 +++----------
 packages/frontend/src/custom-emojis.ts        | 21 +++++++++----------
 packages/frontend/src/pages/about.emojis.vue  |  7 +++----
 .../src/pages/admin/emoji-edit-dialog.vue     |  2 +-
 .../src/pages/admin/overview.stats.vue        |  4 +---
 .../frontend/src/pages/mfm-cheat-sheet.vue    |  4 +---
 7 files changed, 20 insertions(+), 36 deletions(-)

diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue
index 0d0c5edbae..b184e4489e 100644
--- a/packages/frontend/src/components/MkAutocomplete.vue
+++ b/packages/frontend/src/components/MkAutocomplete.vue
@@ -47,9 +47,7 @@ import { emojilist } from '@/scripts/emojilist';
 import { instance } from '@/instance';
 import { i18n } from '@/i18n';
 import { miLocalStorage } from '@/local-storage';
-import { getCustomEmojis } from '@/custom-emojis';
-
-const customEmojis = await getCustomEmojis();
+import { customEmojis } from '@/custom-emojis';
 
 type EmojiDef = {
 	emoji: string;
diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue
index 18950b18d3..a34326822f 100644
--- a/packages/frontend/src/components/MkEmojiPicker.vue
+++ b/packages/frontend/src/components/MkEmojiPicker.vue
@@ -1,7 +1,7 @@
 <template>
 <div class="omfetrab" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer, asWindow }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }">
 	<input ref="search" :value="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" type="search" @input="input()" @paste.stop="paste" @keyup.enter="done()">
-	<div v-if="customEmojis != null && customEmojiCategories != null" ref="emojisEl" class="emojis">
+	<div ref="emojisEl" class="emojis">
 		<section class="result">
 			<div v-if="searchResultCustom.length > 0" class="body">
 				<button
@@ -88,7 +88,7 @@ import { deviceKind } from '@/scripts/device-kind';
 import { instance } from '@/instance';
 import { i18n } from '@/i18n';
 import { defaultStore } from '@/store';
-import { getCustomEmojiCategories, getCustomEmojis } from '@/custom-emojis';
+import { getCustomEmojiCategories, customEmojis } from '@/custom-emojis';
 
 const props = withDefaults(defineProps<{
 	showPinned?: boolean;
@@ -104,15 +104,7 @@ const emit = defineEmits<{
 	(ev: 'chosen', v: string): void;
 }>();
 
-let customEmojis = $ref(null);
-getCustomEmojis().then((x) => {
-	customEmojis = x;
-});
-let customEmojiCategories = $ref(null);
-getCustomEmojiCategories().then((x) => {
-	customEmojiCategories = x;
-});
-
+const customEmojiCategories = getCustomEmojiCategories();
 const search = shallowRef<HTMLInputElement>();
 const emojisEl = shallowRef<HTMLDivElement>();
 
diff --git a/packages/frontend/src/custom-emojis.ts b/packages/frontend/src/custom-emojis.ts
index 2a52753f84..19469999b6 100644
--- a/packages/frontend/src/custom-emojis.ts
+++ b/packages/frontend/src/custom-emojis.ts
@@ -2,25 +2,26 @@ import { api } from './os';
 import { miLocalStorage } from './local-storage';
 
 const storageCache = miLocalStorage.getItem('emojis');
-let cached = storageCache ? JSON.parse(storageCache) : null;
-export async function getCustomEmojis() {
+export let customEmojis = storageCache ? JSON.parse(storageCache) : [];
+
+fetchCustomEmojis();
+
+export async function fetchCustomEmojis() {
 	const now = Date.now();
 	const lastFetchedAt = miLocalStorage.getItem('lastEmojisFetchedAt');
-	if (cached && lastFetchedAt && (now - parseInt(lastFetchedAt)) < 1000 * 60 * 60) return cached;
+	if (lastFetchedAt && (now - parseInt(lastFetchedAt)) < 1000 * 60 * 60) return;
 
 	const res = await api('emojis', {});
 
-	cached = res.emojis;
-	miLocalStorage.setItem('emojis', JSON.stringify(cached));
+	customEmojis = res.emojis;
+	miLocalStorage.setItem('emojis', JSON.stringify(customEmojis));
 	miLocalStorage.setItem('lastEmojisFetchedAt', now.toString());
 }
 
 let cachedCategories;
-export async function getCustomEmojiCategories() {
+export function getCustomEmojiCategories() {
 	if (cachedCategories) return cachedCategories;
 
-	const customEmojis = await getCustomEmojis();
-
 	const categories = new Set();
 	for (const emoji of customEmojis) {
 		categories.add(emoji.category);
@@ -31,11 +32,9 @@ export async function getCustomEmojiCategories() {
 }
 
 let cachedTags;
-export async function getCustomEmojiTags() {
+export function getCustomEmojiTags() {
 	if (cachedTags) return cachedTags;
 
-	const customEmojis = await getCustomEmojis();
-
 	const tags = new Set();
 	for (const emoji of customEmojis) {
 		for (const tag of emoji.aliases) {
diff --git a/packages/frontend/src/pages/about.emojis.vue b/packages/frontend/src/pages/about.emojis.vue
index acf6237c87..2c56b111c6 100644
--- a/packages/frontend/src/pages/about.emojis.vue
+++ b/packages/frontend/src/pages/about.emojis.vue
@@ -37,11 +37,10 @@ import MkSelect from '@/components/MkSelect.vue';
 import MkFoldableSection from '@/components/MkFoldableSection.vue';
 import MkTab from '@/components/MkTab.vue';
 import * as os from '@/os';
-import { getCustomEmojis, getCustomEmojiCategories, getCustomEmojiTags } from '@/custom-emojis';
+import { customEmojis, getCustomEmojiCategories, getCustomEmojiTags } from '@/custom-emojis';
 
-const customEmojis = await getCustomEmojis();
-const customEmojiCategories = await getCustomEmojiCategories();
-const customEmojiTags = await getCustomEmojiTags();
+const customEmojiCategories = getCustomEmojiCategories();
+const customEmojiTags = getCustomEmojiTags();
 let q = $ref('');
 let searchEmojis = $ref(null);
 let selectedTags = $ref(new Set());
diff --git a/packages/frontend/src/pages/admin/emoji-edit-dialog.vue b/packages/frontend/src/pages/admin/emoji-edit-dialog.vue
index 0b6a5e1557..b2880b60b1 100644
--- a/packages/frontend/src/pages/admin/emoji-edit-dialog.vue
+++ b/packages/frontend/src/pages/admin/emoji-edit-dialog.vue
@@ -46,7 +46,7 @@ let dialog = $ref(null);
 let name: string = $ref(props.emoji.name);
 let category: string = $ref(props.emoji.category);
 let aliases: string = $ref(props.emoji.aliases.join(' '));
-const categories = await getCustomEmojiCategories();
+const categories = getCustomEmojiCategories();
 
 const emit = defineEmits<{
 	(ev: 'done', v: { deleted?: boolean, updated?: any }): void,
diff --git a/packages/frontend/src/pages/admin/overview.stats.vue b/packages/frontend/src/pages/admin/overview.stats.vue
index 08f31676f3..ce68222fbb 100644
--- a/packages/frontend/src/pages/admin/overview.stats.vue
+++ b/packages/frontend/src/pages/admin/overview.stats.vue
@@ -63,9 +63,7 @@ import number from '@/filters/number';
 import MkNumberDiff from '@/components/MkNumberDiff.vue';
 import MkNumber from '@/components/MkNumber.vue';
 import { i18n } from '@/i18n';
-import { getCustomEmojis } from '@/custom-emojis';
-
-const customEmojis = await getCustomEmojis();
+import { customEmojis } from '@/custom-emojis';
 
 let stats: any = $ref(null);
 let usersComparedToThePrevDay = $ref<number>();
diff --git a/packages/frontend/src/pages/mfm-cheat-sheet.vue b/packages/frontend/src/pages/mfm-cheat-sheet.vue
index f49b6959c8..4dde3f2666 100644
--- a/packages/frontend/src/pages/mfm-cheat-sheet.vue
+++ b/packages/frontend/src/pages/mfm-cheat-sheet.vue
@@ -317,9 +317,7 @@ import MkTextarea from '@/components/MkTextarea.vue';
 import { definePageMetadata } from '@/scripts/page-metadata';
 import { i18n } from '@/i18n';
 import { instance } from '@/instance';
-import { getCustomEmojis } from '@/custom-emojis';
-
-const customEmojis = await getCustomEmojis();
+import { customEmojis } from '@/custom-emojis';
 
 let preview_mention = $ref('@example');
 let preview_hashtag = $ref('#test');

From 1bb2c2249356b083ef67212c6b6e4ec132ae9e8e Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 16:45:18 +0900
Subject: [PATCH 62/68] Update vite.config.ts

---
 packages/frontend/vite.config.ts | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts
index 80f50b6cfb..850a10838d 100644
--- a/packages/frontend/vite.config.ts
+++ b/packages/frontend/vite.config.ts
@@ -42,9 +42,9 @@ export default defineConfig(({ command, mode }) => {
 
 		build: {
 			target: [
-				'chrome100',
-				'firefox100',
-				'safari15',
+				'chrome108',
+				'firefox109',
+				'safari16',
 			],
 			manifest: 'manifest.json',
 			rollupOptions: {

From b7dec6e87d6865478d2aaf0991d8559a67798302 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 17:04:52 +0900
Subject: [PATCH 63/68] refactor

---
 packages/frontend/src/components/MkEmojiPicker.vue | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue
index a34326822f..9c6d62ce8b 100644
--- a/packages/frontend/src/components/MkEmojiPicker.vue
+++ b/packages/frontend/src/components/MkEmojiPicker.vue
@@ -1,6 +1,6 @@
 <template>
 <div class="omfetrab" :class="['s' + size, 'w' + width, 'h' + height, { asDrawer, asWindow }]" :style="{ maxHeight: maxHeight ? maxHeight + 'px' : undefined }">
-	<input ref="search" :value="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" type="search" @input="input()" @paste.stop="paste" @keyup.enter="done()">
+	<input ref="searchEl" :value="q" class="search" data-prevent-emoji-insert :class="{ filled: q != null && q != '' }" :placeholder="i18n.ts.search" type="search" @input="input()" @paste.stop="paste" @keyup.enter="done()">
 	<div ref="emojisEl" class="emojis">
 		<section class="result">
 			<div v-if="searchResultCustom.length > 0" class="body">
@@ -105,7 +105,7 @@ const emit = defineEmits<{
 }>();
 
 const customEmojiCategories = getCustomEmojiCategories();
-const search = shallowRef<HTMLInputElement>();
+const searchEl = shallowRef<HTMLInputElement>();
 const emojisEl = shallowRef<HTMLDivElement>();
 
 const {
@@ -268,7 +268,7 @@ watch(q, () => {
 
 function focus() {
 	if (!['smartphone', 'tablet'].includes(deviceKind) && !isTouchUsing) {
-		search.value?.focus({
+		searchEl.value?.focus({
 			preventScroll: true,
 		});
 	}
@@ -308,7 +308,7 @@ function input(): void {
 	// Using custom input event instead of v-model to respond immediately on
 	// Android, where composition happens on all languages
 	// (v-model does not update during composition)
-	q.value = search.value?.value.trim() ?? '';
+	q.value = searchEl.value?.value.trim() ?? '';
 }
 
 function paste(event: ClipboardEvent): void {

From 8f28ff63f15531927d57d9aa4e3fde570bb66488 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 17:08:58 +0900
Subject: [PATCH 64/68] =?UTF-8?q?=E7=B5=B5=E6=96=87=E5=AD=97=E3=83=94?=
 =?UTF-8?q?=E3=83=83=E3=82=AB=E3=83=BC=E3=81=AB=E3=83=95=E3=82=A9=E3=83=BC?=
 =?UTF-8?q?=E3=82=AB=E3=82=B9=E3=81=95=E3=82=8C=E3=81=AA=E3=81=84=E3=81=AE?=
 =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 packages/frontend/src/components/MkEmojiPickerDialog.vue | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/packages/frontend/src/components/MkEmojiPickerDialog.vue b/packages/frontend/src/components/MkEmojiPickerDialog.vue
index da68ecd809..c568d4ed5c 100644
--- a/packages/frontend/src/components/MkEmojiPickerDialog.vue
+++ b/packages/frontend/src/components/MkEmojiPickerDialog.vue
@@ -59,6 +59,11 @@ function chosen(emoji: any) {
 function opening() {
 	picker.value?.reset();
 	picker.value?.focus();
+
+	// 何故かちょっと待たないとフォーカスされない
+	setTimeout(() => {
+		picker.value?.focus();
+	}, 10);
 }
 </script>
 

From 96ccf550b1c7322eb80d97dd42c2c7c630fc97cd Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 17:13:25 +0900
Subject: [PATCH 65/68] New Crowdin updates (#9490)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Arabic)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Italian)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (Portuguese)

* New translations ja-JP.yml (Russian)

* New translations ja-JP.yml (Slovak)

* New translations ja-JP.yml (Ukrainian)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Vietnamese)

* New translations ja-JP.yml (Indonesian)

* New translations ja-JP.yml (Bengali)

* New translations ja-JP.yml (Thai)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Chinese Traditional)

* New translations ja-JP.yml (Thai)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Thai)

* New translations ja-JP.yml (Swedish)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Thai)

* New translations ja-JP.yml (Chinese Traditional)
---
 locales/ar-SA.yml |   2 -
 locales/bn-BD.yml |   2 -
 locales/de-DE.yml |   3 +-
 locales/en-US.yml |   3 +-
 locales/es-ES.yml |   2 -
 locales/fr-FR.yml |   2 -
 locales/id-ID.yml |   2 -
 locales/it-IT.yml |   2 -
 locales/ja-KS.yml |   2 -
 locales/ko-KR.yml |   3 +-
 locales/pl-PL.yml |   2 -
 locales/pt-PT.yml |   2 -
 locales/ru-RU.yml |   2 -
 locales/sk-SK.yml |   2 -
 locales/sv-SE.yml | 119 ++++++++++++++++++++++++++++++++++++++++++++--
 locales/th-TH.yml |   9 ++--
 locales/uk-UA.yml |   2 -
 locales/vi-VN.yml |   2 -
 locales/zh-CN.yml |   5 +-
 locales/zh-TW.yml |   8 ++--
 20 files changed, 130 insertions(+), 46 deletions(-)

diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml
index 161a393bc2..773e239812 100644
--- a/locales/ar-SA.yml
+++ b/locales/ar-SA.yml
@@ -1294,7 +1294,6 @@ _notification:
   youGotReply: "ردّ عليك {name}"
   youGotQuote: "اقتبس منك {name}"
   youRenoted: "إعادت نشر من {name}"
-  youGotPoll: "شارك {name} في استطلاع الرأي"
   youGotMessagingMessageFromUser: "لقد تلقيت رسالة مِن {name}"
   youGotMessagingMessageFromGroup: "لقد أرسِلَت رسالة إلى الفريق {name}"
   youWereFollowed: "يتابعك"
@@ -1311,7 +1310,6 @@ _notification:
     renote: "أعد النشر"
     quote: "الاقتباسات"
     reaction: "التفاعلات"
-    pollVote: "مصوِت شارك في الاستطلاع"
     receiveFollowRequest: "طلبات المتابعة المتلقاة"
     followRequestAccepted: "طلبات المتابعة المقبولة"
     groupInvited: "دعوات الفريق"
diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml
index 593cbb1b32..ac0a0e1a51 100644
--- a/locales/bn-BD.yml
+++ b/locales/bn-BD.yml
@@ -1386,7 +1386,6 @@ _notification:
   youGotReply: "{name} আপনাকে জবাব দিয়েছে"
   youGotQuote: "{name} আপনাকে উদ্ধৃত করেছে"
   youRenoted: "{name} এর Renote"
-  youGotPoll: "{name} আপনার পোলে ভোট দিয়েছে"
   youGotMessagingMessageFromUser: "{name} আপনাকে মেসেজ করেছে"
   youGotMessagingMessageFromGroup: "{name} গ্রুপে একটি নতুন মেসেজ আছে"
   youWereFollowed: "আপনাকে অনুসরণ করছে"
@@ -1403,7 +1402,6 @@ _notification:
     renote: "রিনোট"
     quote: "উদ্ধৃতি"
     reaction: "প্রতিক্রিয়া"
-    pollVote: "পোলে ভোট আছে"
     pollEnded: "পোল শেষ"
     receiveFollowRequest: "প্রাপ্ত অনুসরণের অনুরোধসমূহ"
     followRequestAccepted: "গৃহীত অনুসরণের অনুরোধসমূহ"
diff --git a/locales/de-DE.yml b/locales/de-DE.yml
index 3ab2e6abc3..70d0acc264 100644
--- a/locales/de-DE.yml
+++ b/locales/de-DE.yml
@@ -1328,6 +1328,7 @@ _widgets:
   userList: "Benutzerliste"
   _userList:
     chooseList: "Liste auswählen"
+  clicker: "Klickzähler"
 _cw:
   hide: "Inhalt verbergen"
   show: "Inhalt anzeigen"
@@ -1503,7 +1504,6 @@ _notification:
   youGotReply: "{name} hat dir geantwortet"
   youGotQuote: "{name} hat dich zitiert"
   youRenoted: "Renote deiner Notiz von {name}"
-  youGotPoll: "{name} hat in deiner Umfrage abgestimmt"
   youGotMessagingMessageFromUser: "{name} hat dir eine Chatnachricht gesendet"
   youGotMessagingMessageFromGroup: "In die Gruppe {name} wurde eine Chatnachricht gesendet"
   youWereFollowed: "ist dir gefolgt"
@@ -1521,7 +1521,6 @@ _notification:
     renote: "Renotes"
     quote: "Zitationen"
     reaction: "Reaktionen"
-    pollVote: "Antworten auf Umfragen"
     pollEnded: "Ende von Umfragen"
     receiveFollowRequest: "Erhaltene Follow-Anfragen"
     followRequestAccepted: "Akzeptierte Follow-Anfragen"
diff --git a/locales/en-US.yml b/locales/en-US.yml
index 5da7032eda..ef42a8b97d 100644
--- a/locales/en-US.yml
+++ b/locales/en-US.yml
@@ -1328,6 +1328,7 @@ _widgets:
   userList: "User list"
   _userList:
     chooseList: "Select a list"
+  clicker: "Clicker"
 _cw:
   hide: "Hide"
   show: "Show content"
@@ -1503,7 +1504,6 @@ _notification:
   youGotReply: "{name} replied to you"
   youGotQuote: "{name} quoted you"
   youRenoted: "Renote from {name}"
-  youGotPoll: "{name} voted on your poll"
   youGotMessagingMessageFromUser: "{name} sent you a chat message"
   youGotMessagingMessageFromGroup: "A chat message was sent to the {name} group"
   youWereFollowed: "followed you"
@@ -1521,7 +1521,6 @@ _notification:
     renote: "Renotes"
     quote: "Quotes"
     reaction: "Reactions"
-    pollVote: "Votes on polls"
     pollEnded: "Polls ending"
     receiveFollowRequest: "Received follow requests"
     followRequestAccepted: "Accepted follow requests"
diff --git a/locales/es-ES.yml b/locales/es-ES.yml
index c328737c4b..5e48fb6d31 100644
--- a/locales/es-ES.yml
+++ b/locales/es-ES.yml
@@ -1487,7 +1487,6 @@ _notification:
   youGotReply: "Respuesta de {name}"
   youGotQuote: "Citado por {name}"
   youRenoted: "Renotado por {name}"
-  youGotPoll: "Encuestado por {name}"
   youGotMessagingMessageFromUser: "{name} comenzó un chat contigo"
   youGotMessagingMessageFromGroup: "Tienes un chat de {name}"
   youWereFollowed: "te ha seguido"
@@ -1505,7 +1504,6 @@ _notification:
     renote: "Renotar"
     quote: "Citar"
     reaction: "Reacción"
-    pollVote: "Votado en la encuesta"
     pollEnded: "La encuesta terminó"
     receiveFollowRequest: "Recibió una solicitud de seguimiento"
     followRequestAccepted: "El seguimiento fue aceptado"
diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml
index 0d7399533d..8c777ab513 100644
--- a/locales/fr-FR.yml
+++ b/locales/fr-FR.yml
@@ -1478,7 +1478,6 @@ _notification:
   youGotReply: "Réponse de {name}"
   youGotQuote: "Cité·e par {name}"
   youRenoted: "{name} vous a Renoté"
-  youGotPoll: "{name} a participé à votre sondage"
   youGotMessagingMessageFromUser: "{name} vous envoyé un message"
   youGotMessagingMessageFromGroup: "Un message a été envoyé au groupe {name}"
   youWereFollowed: "Vous suit"
@@ -1496,7 +1495,6 @@ _notification:
     renote: "Renotes"
     quote: "Citations"
     reaction: "Réactions"
-    pollVote: "Votes dans des sondages"
     pollEnded: "Sondages se cloturant"
     receiveFollowRequest: "Demande d'abonnement reçue"
     followRequestAccepted: "Demande d'abonnement acceptée"
diff --git a/locales/id-ID.yml b/locales/id-ID.yml
index 3a2bf69a72..eee647d0f6 100644
--- a/locales/id-ID.yml
+++ b/locales/id-ID.yml
@@ -1402,7 +1402,6 @@ _notification:
   youGotReply: "{name} membalas kamu"
   youGotQuote: "{name} mengutip kamu"
   youRenoted: "{name} me-renote kamu"
-  youGotPoll: "{name} memilih di angket kamu"
   youGotMessagingMessageFromUser: "{name} mengirimi kamu pesan"
   youGotMessagingMessageFromGroup: "Sebuah pesan telah dikirim ke grup {name}"
   youWereFollowed: "Mengikuti kamu"
@@ -1419,7 +1418,6 @@ _notification:
     renote: "Renote"
     quote: "Kutip"
     reaction: "Reaksi"
-    pollVote: "Memilih di angket"
     pollEnded: "Jajak pendapat berakhir"
     receiveFollowRequest: "Permintaan mengikuti diterima"
     followRequestAccepted: "Permintaan mengikuti disetujui"
diff --git a/locales/it-IT.yml b/locales/it-IT.yml
index 10e8c98177..013a1d2de4 100644
--- a/locales/it-IT.yml
+++ b/locales/it-IT.yml
@@ -1487,7 +1487,6 @@ _notification:
   youGotReply: "{name} ti ha risposto"
   youGotQuote: "{name} ha citato il tuo Nota e ha detto"
   youRenoted: "{name} ha rinotato"
-  youGotPoll: "{name} ha votato"
   youGotMessagingMessageFromUser: "{name} ti ha mandato un messaggio"
   youGotMessagingMessageFromGroup: "{name} ti ha mandato un messaggio nella chat"
   youWereFollowed: "Ha iniziato a seguirti"
@@ -1505,7 +1504,6 @@ _notification:
     renote: "Rinota"
     quote: "Cita"
     reaction: "Reazioni"
-    pollVote: "Voti ricevuti"
     pollEnded: "Sondaggio chiuso."
     receiveFollowRequest: "Richiesta di follow ricevuta"
     followRequestAccepted: "Richiesta di follow accettata"
diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml
index f8c045db00..4eff3268d4 100644
--- a/locales/ja-KS.yml
+++ b/locales/ja-KS.yml
@@ -1485,7 +1485,6 @@ _notification:
   youGotReply: "{name}からのリプライ"
   youGotQuote: "{name}による引用"
   youRenoted: "{name}がRenoteしたみたいやで"
-  youGotPoll: "{name}が投票したみたいやで"
   youGotMessagingMessageFromUser: "{name}からのチャットがあるで"
   youGotMessagingMessageFromGroup: "{name}のチャットがあるで"
   youWereFollowed: "フォローされたで"
@@ -1503,7 +1502,6 @@ _notification:
     renote: "Renote"
     quote: "引用"
     reaction: "リアクション"
-    pollVote: "アンケートに投票されたで"
     pollEnded: "アンケートが終了したで"
     receiveFollowRequest: "フォロー許可してほしいみたいやで"
     followRequestAccepted: "フォローが受理されたで"
diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml
index 522f1f4aa7..f2ba0e6c7b 100644
--- a/locales/ko-KR.yml
+++ b/locales/ko-KR.yml
@@ -1328,6 +1328,7 @@ _widgets:
   userList: "사용자 목록"
   _userList:
     chooseList: "리스트 선택"
+  clicker: "클리커"
 _cw:
   hide: "숨기기"
   show: "더 보기"
@@ -1503,7 +1504,6 @@ _notification:
   youGotReply: "{name}님이 답글함"
   youGotQuote: "{name}님이 인용함"
   youRenoted: "{name}님이 Renote"
-  youGotPoll: "{name}님이 투표함"
   youGotMessagingMessageFromUser: "{name} 님이 보낸 채팅이 있어요"
   youGotMessagingMessageFromGroup: "{name}에서 보낸 채팅이 있어요"
   youWereFollowed: "새로운 팔로워가 있습니다"
@@ -1521,7 +1521,6 @@ _notification:
     renote: "리노트"
     quote: "인용"
     reaction: "리액션"
-    pollVote: "투표 참여"
     pollEnded: "투표가 종료됨"
     receiveFollowRequest: "팔로우 요청을 받았을 때"
     followRequestAccepted: "팔로우 요청이 승인되었을 때"
diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml
index 712c05bb78..9043d2ab94 100644
--- a/locales/pl-PL.yml
+++ b/locales/pl-PL.yml
@@ -1380,7 +1380,6 @@ _notification:
   youGotReply: "{name} odpowiedział(a) Tobie"
   youGotQuote: "{name} zacytował(a) Ciebie"
   youRenoted: "{name} udostępnił(a) Twój wpis"
-  youGotPoll: "{name} zagłosował(a) w Twojej ankiecie"
   youGotMessagingMessageFromUser: "{name} wysłał(a) Ci wiadomość"
   youGotMessagingMessageFromGroup: "Została wysłana wiadomość do grupy {name}"
   youWereFollowed: "Zaobserwował(a) Cię"
@@ -1398,7 +1397,6 @@ _notification:
     renote: "Udostępnij"
     quote: "Cytuj"
     reaction: "Reakcja"
-    pollVote: "Głosy w ankietach"
     receiveFollowRequest: "Otrzymano prośbę o możliwość obserwacji"
     followRequestAccepted: "Przyjęto prośbę o możliwość obserwacji"
     groupInvited: "Zaproszono do grup"
diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml
index dd1c2954b7..ba4814c81b 100644
--- a/locales/pt-PT.yml
+++ b/locales/pt-PT.yml
@@ -524,7 +524,6 @@ _notification:
   youGotMention: "{name} te mencionou"
   youGotReply: "{name} te respondeu"
   youGotQuote: "{name} te citou"
-  youGotPoll: "{name} votou em sua enquete"
   youGotMessagingMessageFromUser: "{name} te mandou uma mensagem de bate-papo"
   youGotMessagingMessageFromGroup: "Uma mensagem foi mandada para o grupo {name}"
   youWereFollowed: "Você tem um novo seguidor"
@@ -541,7 +540,6 @@ _notification:
     renote: "Repostar"
     quote: "Citar"
     reaction: "Reações"
-    pollVote: "Votações em enquetes"
     pollEnded: "Enquetes terminando"
     receiveFollowRequest: "Recebeu pedidos de seguimento"
     followRequestAccepted: "Aceitou pedidos de seguimento"
diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml
index 553fa9e811..49cccc71ae 100644
--- a/locales/ru-RU.yml
+++ b/locales/ru-RU.yml
@@ -1399,7 +1399,6 @@ _notification:
   youGotReply: "{name} отвечает вам."
   youGotQuote: "{name} цитирует вас."
   youRenoted: "{name} передаёт вашу заметку."
-  youGotPoll: "{name} участвует в вашем опросе."
   youGotMessagingMessageFromUser: "{name} пишет вам."
   youGotMessagingMessageFromGroup: "Новое сообщение в группе «{name}»."
   youWereFollowed: "У вас новый подписчик."
@@ -1414,7 +1413,6 @@ _notification:
     renote: "Репосты"
     quote: "Цитаты"
     reaction: "Реакции"
-    pollVote: "Голосования"
     receiveFollowRequest: "Получен запрос на подписку"
     followRequestAccepted: "Запрос на подписку одобрен"
     groupInvited: "Приглашение в группы"
diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml
index 35afc91918..2627c70b67 100644
--- a/locales/sk-SK.yml
+++ b/locales/sk-SK.yml
@@ -1484,7 +1484,6 @@ _notification:
   youGotReply: "{name} vám odpovedal/a"
   youGotQuote: "{name} vás citoval/a"
   youRenoted: "{name} preposlal/a vašu poznámku"
-  youGotPoll: "{name} hlasoval/a"
   youGotMessagingMessageFromUser: "{name} vám poslal/a správu"
   youGotMessagingMessageFromGroup: "Prišla správa do skupiny {name}"
   youWereFollowed: "Máte nového sledujúceho"
@@ -1502,7 +1501,6 @@ _notification:
     renote: "Preposlať"
     quote: "Citovať"
     reaction: "Reakcie"
-    pollVote: "Hlasy v hlasovaniach"
     pollEnded: "Hlasovanie skončilo"
     receiveFollowRequest: "Doručené žiadosti o sledovanie"
     followRequestAccepted: "Schválené žiadosti o sledovanie"
diff --git a/locales/sv-SE.yml b/locales/sv-SE.yml
index 8b87e36acd..4e2f942686 100644
--- a/locales/sv-SE.yml
+++ b/locales/sv-SE.yml
@@ -1,7 +1,7 @@
 ---
 _lang_: "Svenska"
 headlineMisskey: "Ett nätverk kopplat av noter"
-introMisskey: "Välkommen! Misskey är en öppen och decentraliserad mikrobloggningstjänst.\nSkapa en \"not\" och dela dina tankar med alla runtomkring dig. 📡\nMed \"reaktioner\" kan du snabbt uttrycka dina känslor kring andras noter.👍\nLåt oss utforska en nya värld!🚀"
+introMisskey: "Välkommen! Misskey är en öppen och decentraliserad mikrobloggningstjänst.\nSkapa en \"not\" och dela dina tankar med alla runtomkring dig. 📡\nMed \"reaktioner\" kan du snabbt uttrycka dina känslor kring andras noter. 👍\nLåt oss utforska en ny värld! 🚀"
 poweredByMisskeyDescription: "{name} är en tjänst driven av den öppna källkodsplatformen <b>Misskey</b> (benämns \"Misskey instans\")."
 monthAndDay: "{day}/{month}"
 search: "Sök"
@@ -17,7 +17,7 @@ noThankYou: "Nej tack"
 enterUsername: "Ange användarnamn"
 renotedBy: "Omnoterad av {user}"
 noNotes: "Inga noteringar"
-noNotifications: "Inga aviseringar"
+noNotifications: "Inga notifikationer"
 instance: "Instanser"
 settings: "Inställningar"
 basicSettings: "Basinställningar"
@@ -30,13 +30,13 @@ login: "Logga in"
 loggingIn: "Loggar in"
 logout: "Logga ut"
 signup: "Registrera"
-uploading: "Uppladdning sker..."
+uploading: "Laddar upp..."
 save: "Spara"
 users: "Användare"
 addUser: "Lägg till användare"
 favorite: "Lägg till i favoriter"
 favorites: "Favoriter"
-unfavorite: "Avfavorisera"
+unfavorite: "Ta bort från favoriter"
 favorited: "Tillagd i favoriter."
 alreadyFavorited: "Redan tillagd i favoriter."
 cantFavorite: "Gick inte att lägga till i favoriter."
@@ -146,7 +146,7 @@ flagAsBotDescription: "Aktivera det här alternativet om kontot är kontrollerat
 flagAsCat: "Markera konto som katt"
 flagAsCatDescription: "Aktivera denna inställning för att markera kontot som en katt."
 flagShowTimelineReplies: "Visa svar i tidslinje"
-flagShowTimelineRepliesDescription: "Visar användarsvar till andra användares noter i tidslinjen om påslagen."
+flagShowTimelineRepliesDescription: "Visar användarsvar till andra användares noter i tidslinjen om aktiverad."
 autoAcceptFollowed: "Godkänn följarförfrågningar från användare du följer automatiskt"
 addAccount: "Lägg till konto"
 loginFailed: "Inloggningen misslyckades"
@@ -253,16 +253,111 @@ explore: "Utforska"
 messageRead: "Läs"
 noMoreHistory: "Det finns ingen mer historik"
 startMessaging: "Starta en chatt"
+nUsersRead: "läst av {n}"
+agreeTo: "Jag accepterar {0}"
+tos: "Användarvillkor"
+home: "Hem"
+remoteUserCaution: "Då denna användaren kommer från en fjärrinstans, kan informationen visad vara ofullständig."
+activity: "Aktivitet"
+images: "Bilder"
+birthday: "Födelsedag"
+yearsOld: "{age} år gammal"
+registeredDate: "Gick med"
+location: "Plats"
+theme: "Teman"
+themeForLightMode: "Tema att använda i Ljust Läge"
+themeForDarkMode: "Tema att använda i Mörkt Läge"
+light: "Ljust"
+dark: "Mörk"
+lightThemes: "Ljusa teman"
+darkThemes: "Mörka teman"
+syncDeviceDarkMode: "Synka Mörkt Läge med din enhets inställningar"
+drive: "Drive"
+fileName: "Filnamn"
+selectFile: "Välj en fil"
+selectFiles: "Välj filer"
+selectFolder: "Välj en mapp"
+selectFolders: "Välj mappar"
+renameFile: "Byt namn på filen"
+folderName: "Mappnamn"
+createFolder: "Skapa en mapp"
+renameFolder: "Byt namn på mappen"
+deleteFolder: "Ta bort mappen"
+addFile: "Lägg till fil"
+emptyDrive: "Din Drive är tom"
+emptyFolder: "Denna mappen är tom"
+unableToDelete: "Kunde inte ta bort"
+inputNewFileName: "Ange nytt filnamn"
+inputNewDescription: "Ange ny bildtext"
+inputNewFolderName: "Ange nytt mappnamn"
+circularReferenceFolder: "Destinationsmappen är en undermapp av mappen du vill flytta."
+hasChildFilesOrFolders: "Då denna mappen inte är tom, kan den inte tas bort."
+copyUrl: "Kopiera URL"
+rename: "Byt namn"
+avatar: "Profilbild"
+banner: "Banner"
 nsfw: "Känsligt innehåll"
+reload: "Ladda om"
+doNothing: "Ignorera"
+reloadConfirm: "Vill du ladda om tidslinjen?"
+accept: "Tillåt"
+reject: "Neka"
+normal: "Normal"
+instanceName: "Instansnamn"
+instanceDescription: "Instansbeskrivning"
+maintainerEmail: "Administratörens epost"
+tosUrl: "URL till användarvillkår"
+thisYear: "Detta året"
+thisMonth: "Denna månaden"
+today: "Idag"
+dayX: "{day}"
+monthX: "{month}"
+yearX: "{year}"
+pages: "Sidor"
+integration: "Integrationer"
+connectService: "Anslut"
+disconnectService: "Koppla från"
+enableLocalTimeline: "Aktivera lokal tidslinje"
+enableGlobalTimeline: "Aktivera global tidslinje"
+enableRegistration: "Aktivera registrering av nya användare"
+inMb: "I megabyte"
+iconUrl: "URL till profilbilden"
+bannerUrl: "URL till banner-bilden"
 pinnedNotes: "Fästad not"
+enableHcaptcha: "Aktivera hCaptcha"
+enableRecaptcha: "Aktivera reCAPTCHA"
+enableTurnstile: "Aktivera Turnstile"
+enableServiceworker: "Aktivera pushnotiser i denna webbläsaren"
+recentlyUpdatedUsers: "Nyligen aktiva användare"
+recentlyRegisteredUsers: "Nyligen registrerade användare"
 userList: "Listor"
+aboutMisskey: "Om Misskey"
+administrator: "Administratör"
+newPasswordIs: "Det nya lösenordet är \"{password}\""
+share: "Dela"
+enable: "Aktivera"
+serviceworkerInfo: "Måste vara aktiverad för pushnotiser."
+enableInfiniteScroll: "Ladda mer automatiskt"
+enablePlayer: "Öppna videospelare"
+enableAll: "Aktivera alla"
+enableEmail: "Aktivera epost-utskick"
 smtpHost: "Värd"
 smtpUser: "Användarnamn"
 smtpPass: "Lösenord"
 clearCache: "Rensa cache"
+enabled: "Aktiverad"
 user: "Användare"
+global: "Global"
+squareAvatars: "Visa fyrkantiga profilbilder"
 searchByGoogle: "Sök"
 file: "Filer"
+enableAutoSensitive: "Automatisk NSFW markering"
+enableAutoSensitiveDescription: "Tillåter automatiskt detektering och marketing av NSFW media genom Maskininlärning när möjligt. Även om denna inställningen är avaktiverad, kan det vara aktiverat på hela instansen."
+pushNotification: "Pushnotiser"
+subscribePushNotification: "Aktivera pushnotiser"
+unsubscribePushNotification: "Avaktivera pushnotiser"
+pushNotificationAlreadySubscribed: "Pushnotiser är redan aktiverade"
+pushNotificationNotSupported: "Din webbläsare eller instans har inte stöd för pushnotiser"
 _email:
   _follow:
     title: "följde dig"
@@ -271,6 +366,9 @@ _mfm:
   quote: "Citat"
   emoji: "Anpassa emoji"
   search: "Sök"
+_channel:
+  setBanner: "Välj banner"
+  removeBanner: "Ta bort banner"
 _theme:
   keys:
     mention: "Nämn"
@@ -282,6 +380,7 @@ _sfx:
 _widgets:
   notifications: "Notifikationer"
   timeline: "Tidslinje"
+  activity: "Aktivitet"
   federation: "Federation"
   jobQueue: "Jobbkö"
   _userList:
@@ -289,9 +388,12 @@ _widgets:
 _cw:
   show: "Ladda mer"
 _visibility:
+  home: "Hem"
   followers: "Följare"
 _profile:
   username: "Användarnamn"
+  changeAvatar: "Ändra profilbild"
+  changeBanner: "Ändra banner"
 _exportOrImport:
   followingList: "Följer"
   muteList: "Tysta"
@@ -299,8 +401,15 @@ _exportOrImport:
   userLists: "Listor"
 _charts:
   federation: "Federation"
+_timelines:
+  home: "Hem"
+  global: "Global"
+_pages:
+  blocks:
+    image: "Bilder"
 _notification:
   youWereFollowed: "följde dig"
+  unreadAntennaNote: "Antenn {name}"
   _types:
     follow: "Följer"
     mention: "Nämn"
diff --git a/locales/th-TH.yml b/locales/th-TH.yml
index 5a3400feb1..d86c3de0e9 100644
--- a/locales/th-TH.yml
+++ b/locales/th-TH.yml
@@ -8,7 +8,7 @@ search: "ค้นหา"
 notifications: "การเเจ้งเตือน"
 username: "ชื่อผู้ใช้"
 password: "รหัสผ่าน"
-forgotPassword: "ลืมรหัสผ่าน?"
+forgotPassword: "ลืมรหัสผ่านใช่ไหม"
 fetchingAsApObject: "กำลังดึงข้อมูล จาก เฟดิเวิร์ส..."
 ok: "โอเค"
 gotIt: "เข้าใจแล้ว !"
@@ -920,6 +920,10 @@ like: "ชื่นชอบ"
 unlike: "ไม่ชอบ"
 numberOfLikes: "จำนวนไลค์"
 show: "แสดงผล"
+neverShow: "ไม่ต้องแสดงข้อความนี้อีก"
+remindMeLater: "ไว้ครั้งหน้าแล้วกัน"
+didYouLikeMisskey: "คุณเคยชอบ Misskey ไหม?"
+pleaseDonate: "{host} ใช้ซอฟต์แวร์ฟรี Misskey เราขอขอบคุณการบริจาคของคุณอย่างสูงเพื่อให้การพัฒนา Misskey สามารถดำเนินต่อไปได้นะ!"
 _sensitiveMediaDetection:
   description: "ลดความพยายามในการดูแลเซิร์ฟเวอร์ผ่านการจดจำสื่อ NSFW โดยอัตโนมัติผ่านการเรียนรู้ของเครื่อง การทำสิ่งนี้อาจจะเพิ่มภาระบนเซิร์ฟเวอร์เล็กน้อย"
   sensitivity: "การตรวจจับความไว"
@@ -1324,6 +1328,7 @@ _widgets:
   userList: "รายชื่อผู้ใช้"
   _userList:
     chooseList: "เลือกรายการ"
+  clicker: "คลิกเกอร์"
 _cw:
   hide: "ซ่อน"
   show: "โหลดเพิ่มเติม"
@@ -1499,7 +1504,6 @@ _notification:
   youGotReply: "{name} ตอบกลับถึงคุณ"
   youGotQuote: "{name} อ้างถึงคุณ"
   youRenoted: "รีโน้ตจาก {name}"
-  youGotPoll: "{name} โหวตบนแบบสำรวจความคิดเห็นของคุณ"
   youGotMessagingMessageFromUser: "{name} ได้ส่งข้อความแชทถึงคุณ"
   youGotMessagingMessageFromGroup: "ข้อความแชทถูกส่งไปยัง {name} กลุ่ม"
   youWereFollowed: "ได้ติดตามคุณ"
@@ -1517,7 +1521,6 @@ _notification:
     renote: "รีโน้ต"
     quote: "อ้างคำพูด"
     reaction: "รีแอคชั่น"
-    pollVote: "จำนวนโหวตที่ได้รับ"
     pollEnded: "โพลนี้สิ้นสุดลงแล้ว"
     receiveFollowRequest: "ได้รับคำขอติดตาม\n"
     followRequestAccepted: "ยอมรับคำขอติดตาม"
diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml
index 352fb354ee..c6afcde112 100644
--- a/locales/uk-UA.yml
+++ b/locales/uk-UA.yml
@@ -1415,7 +1415,6 @@ _notification:
   youGotReply: "{name} відповідає"
   youGotQuote: "{name} цитує вас"
   youRenoted: "{name} поширює"
-  youGotPoll: "{name} бере участь в опитуванні"
   youGotMessagingMessageFromUser: "Повідомлення від {name}"
   youGotMessagingMessageFromGroup: "Нове повідомлення в групі {name}"
   youWereFollowed: "Новий підписник"
@@ -1430,7 +1429,6 @@ _notification:
     renote: "Поширення"
     quote: "Цитування"
     reaction: "Реакції"
-    pollVote: "Опитування"
     receiveFollowRequest: "Запити на підписку"
     followRequestAccepted: "Прийняті підписки"
     groupInvited: "Запрошення до груп"
diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml
index 0070af56f0..6824faaee1 100644
--- a/locales/vi-VN.yml
+++ b/locales/vi-VN.yml
@@ -1460,7 +1460,6 @@ _notification:
   youGotReply: "{name} trả lời bạn"
   youGotQuote: "{name} trích dẫn tút của bạn"
   youRenoted: "{name} đăng lại tút của bạn"
-  youGotPoll: "{name} bình chọn tút của bạn"
   youGotMessagingMessageFromUser: "{name} nhắn tin cho bạn"
   youGotMessagingMessageFromGroup: "Một tin nhắn trong nhóm {name}"
   youWereFollowed: "đã theo dõi bạn"
@@ -1477,7 +1476,6 @@ _notification:
     renote: "Đăng lại"
     quote: "Trích dẫn"
     reaction: "Biểu cảm"
-    pollVote: "Lượt bình chọn"
     pollEnded: "Bình chọn kết thúc"
     receiveFollowRequest: "Yêu cầu theo dõi"
     followRequestAccepted: "Yêu cầu theo dõi được chấp nhận"
diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml
index 7c3efec86c..8ebc3a1fb4 100644
--- a/locales/zh-CN.yml
+++ b/locales/zh-CN.yml
@@ -920,6 +920,9 @@ like: "点赞!"
 unlike: "取消赞"
 numberOfLikes: "点赞数"
 show: "显示"
+neverShow: "不再显示"
+remindMeLater: "稍后提醒我"
+didYouLikeMisskey: "你在Misskey玩得还开心吗?"
 _sensitiveMediaDetection:
   description: "可以使用机器学习技术自动检测敏感媒体,以便进行审核。服务器负载将略微增加。"
   sensitivity: "检测敏感度"
@@ -1499,7 +1502,6 @@ _notification:
   youGotReply: "来自{name}的回复"
   youGotQuote: "来自{name}的引用"
   youRenoted: "来自{name}的转发"
-  youGotPoll: "来自{name}的投票"
   youGotMessagingMessageFromUser: "来自{name}的聊天"
   youGotMessagingMessageFromGroup: "来自{name}的群聊"
   youWereFollowed: "关注了你。"
@@ -1517,7 +1519,6 @@ _notification:
     renote: "转发"
     quote: "引用"
     reaction: "回应"
-    pollVote: "问卷调查被投票"
     pollEnded: "问卷调查结束"
     receiveFollowRequest: "收到关注请求"
     followRequestAccepted: "关注请求已通过"
diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml
index de12ef1fbf..8330be0388 100644
--- a/locales/zh-TW.yml
+++ b/locales/zh-TW.yml
@@ -252,7 +252,7 @@ uploadFromUrlMayTakeTime: "還需要一些時間才能完成上傳。"
 explore: "探索"
 messageRead: "已讀"
 noMoreHistory: "沒有更多歷史紀錄"
-startMessaging: "開始傳送訊息"
+startMessaging: "開始聊天"
 nUsersRead: "{n}人已讀"
 agreeTo: "我同意{0}"
 tos: "使用條款"
@@ -923,6 +923,7 @@ show: "檢視"
 neverShow: "不再顯示"
 remindMeLater: "以後再說"
 didYouLikeMisskey: "您是否喜愛Misskey呢?"
+pleaseDonate: "Misskey是由{host}使用的免費軟體。請贊助我們,讓開發能夠持續!"
 _sensitiveMediaDetection:
   description: "您可以使用機器學習自動檢測敏感媒體並將其用於審核。 伺服器的負荷會稍微增加。"
   sensitivity: "檢測敏感度"
@@ -1191,7 +1192,7 @@ _sfx:
   note: "貼文"
   noteMy: "我的貼文"
   notification: "通知"
-  chat: "傳送訊息"
+  chat: "聊天"
   chatBg: "聊天背景"
   antenna: "天線接收"
   channel: "頻道通知"
@@ -1327,6 +1328,7 @@ _widgets:
   userList: "使用者列表"
   _userList:
     chooseList: "選擇清單"
+  clicker: "點擊器"
 _cw:
   hide: "隱藏"
   show: "瀏覽更多"
@@ -1502,7 +1504,6 @@ _notification:
   youGotReply: "{name}回覆了您"
   youGotQuote: "{name}引用了您"
   youRenoted: "{name} 轉發了你的貼文"
-  youGotPoll: "{name}已投票"
   youGotMessagingMessageFromUser: "{name}發送給您的訊息"
   youGotMessagingMessageFromGroup: "{name}發送給您的訊息"
   youWereFollowed: "您有新的追隨者"
@@ -1520,7 +1521,6 @@ _notification:
     renote: "轉發貼文"
     quote: "引用"
     reaction: "反應"
-    pollVote: "統計已投票數"
     pollEnded: "問卷調查結束"
     receiveFollowRequest: "已收到追隨請求"
     followRequestAccepted: "追隨請求已接受"

From 014c97fa85767ed287c8fcdc5ac329218cfee131 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 17:13:38 +0900
Subject: [PATCH 66/68] 13.0.0-beta.36

---
 package.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/package.json b/package.json
index ddba184f3d..5d99966a18 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
 	"name": "misskey",
-	"version": "13.0.0-beta.35",
+	"version": "13.0.0-beta.36",
 	"codename": "indigo",
 	"repository": {
 		"type": "git",

From b04155e7ba5147beb2630294074f9e2ba3403f88 Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 17:18:45 +0900
Subject: [PATCH 67/68] :art:

---
 packages/frontend/src/pages/admin/relays.vue | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)

diff --git a/packages/frontend/src/pages/admin/relays.vue b/packages/frontend/src/pages/admin/relays.vue
index eb2788fdeb..55d33e0158 100644
--- a/packages/frontend/src/pages/admin/relays.vue
+++ b/packages/frontend/src/pages/admin/relays.vue
@@ -2,15 +2,17 @@
 <MkStickyContainer>
 	<template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template>
 	<MkSpacer :content-max="800">
-		<div v-for="relay in relays" :key="relay.inbox" class="relaycxt _panel" style="padding: 16px;">
-			<div>{{ relay.inbox }}</div>
-			<div class="status">
-				<i v-if="relay.status === 'accepted'" class="ti ti-check icon accepted"></i>
-				<i v-else-if="relay.status === 'rejected'" class="ti ti-ban icon rejected"></i>
-				<i v-else class="ti ti-clock icon requesting"></i>
-				<span>{{ $t(`_relayStatus.${relay.status}`) }}</span>
+		<div class="_gaps">
+			<div v-for="relay in relays" :key="relay.inbox" class="relaycxt _panel" style="padding: 16px;">
+				<div>{{ relay.inbox }}</div>
+				<div class="status">
+					<i v-if="relay.status === 'accepted'" class="ti ti-check icon accepted"></i>
+					<i v-else-if="relay.status === 'rejected'" class="ti ti-ban icon rejected"></i>
+					<i v-else class="ti ti-clock icon requesting"></i>
+					<span>{{ $t(`_relayStatus.${relay.status}`) }}</span>
+				</div>
+				<MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton>
 			</div>
-			<MkButton class="button" inline danger @click="remove(relay.inbox)"><i class="ti ti-trash"></i> {{ i18n.ts.remove }}</MkButton>
 		</div>
 	</MkSpacer>
 </MkStickyContainer>

From 3bc0cdbfb77ad07b878022b91f06f252fcf906cc Mon Sep 17 00:00:00 2001
From: syuilo <Syuilotan@yahoo.co.jp>
Date: Mon, 9 Jan 2023 17:22:21 +0900
Subject: [PATCH 68/68] typo

---
 packages/backend/src/server/api/endpoints/emojis.ts | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/packages/backend/src/server/api/endpoints/emojis.ts b/packages/backend/src/server/api/endpoints/emojis.ts
index 0a16268229..97dcfde596 100644
--- a/packages/backend/src/server/api/endpoints/emojis.ts
+++ b/packages/backend/src/server/api/endpoints/emojis.ts
@@ -22,10 +22,9 @@ export const meta = {
 					type: 'object',
 					optional: false, nullable: false,
 					properties: {
-						id: {
+						name: {
 							type: 'string',
 							optional: false, nullable: false,
-							format: 'id',
 						},
 						aliases: {
 							type: 'array',