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<{