diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 5729da8da8..b06432fc16 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -710,6 +710,7 @@ typingUsers: "{users}が入力中"
 jumpToSpecifiedDate: "特定の日付にジャンプ"
 showingPastTimeline: "過去のタイムラインを表示しています"
 clear: "クリア"
+markAllAsRead: "全て既読にする"
 
 _email:
   _follow:
diff --git a/src/client/components/abuse-report-window.vue b/src/client/components/abuse-report-window.vue
index 7dbb9657bd..35732d5731 100644
--- a/src/client/components/abuse-report-window.vue
+++ b/src/client/components/abuse-report-window.vue
@@ -80,6 +80,6 @@ export default defineComponent({
 
 <style lang="scss" scoped>
 .dpvffvvy {
-	--section-padding: 16px;
+	--root-margin: 16px;
 }
 </style>
diff --git a/src/client/components/channel-preview.vue b/src/client/components/channel-preview.vue
index e222ad7ae7..4dc633bcb7 100644
--- a/src/client/components/channel-preview.vue
+++ b/src/client/components/channel-preview.vue
@@ -123,7 +123,7 @@ export default defineComponent({
 
 	> footer {
 		padding: 12px 16px;
-		border-top: solid 1px var(--divider);
+		border-top: solid 0.5px var(--divider);
 
 		> span {
 			opacity: 0.7;
diff --git a/src/client/components/date-separated-list.vue b/src/client/components/date-separated-list.vue
index be9f01ca1f..433655d6ed 100644
--- a/src/client/components/date-separated-list.vue
+++ b/src/client/components/date-separated-list.vue
@@ -37,14 +37,16 @@ export default defineComponent({
 			});
 		}
 
+		const noGap = [...document.querySelectorAll('._noGap_')].some(el => el.contains(this.$parent.$el));
+
 		return h(this.$store.state.animation ? TransitionGroup : 'div', this.$store.state.animation ? {
-			class: 'sqadhkmv _list_',
+			class: 'sqadhkmv' + (noGap ? ' _block' : ''),
 			name: 'list',
 			tag: 'div',
 			'data-direction': this.direction,
 			'data-reversed': this.reversed ? 'true' : 'false',
 		} : {
-			class: 'sqadhkmv _list_',
+			class: 'sqadhkmv',
 		}, this.items.map((item, i) => {
 			const el = this.$slots.default({
 				item: item
@@ -117,11 +119,7 @@ export default defineComponent({
 			transform: translateY(-64px);
 		}
 	}
-}
-</style>
 
-<style lang="scss">
-.sqadhkmv {
 	> .separator {
 		text-align: center;
 
@@ -155,4 +153,25 @@ export default defineComponent({
 		}
 	}
 }
+
+._noGap_ .sqadhkmv {
+	> * {
+		margin: 0 !important;
+		border: none;
+		border-radius: 0;
+		box-shadow: none;
+
+		&:not(:last-child) {
+			border-bottom: solid 0.5px var(--divider);
+		}
+	}
+}
+
+._inContainer_ .sqadhkmv > * {
+	margin: 0 !important;
+	border: none;
+	border-bottom: solid 0.5px var(--divider);
+	border-radius: 0;
+	box-shadow: none;
+}
 </style>
diff --git a/src/client/components/drive.file.vue b/src/client/components/drive.file.vue
index 03f2da008d..fb8b50d25a 100644
--- a/src/client/components/drive.file.vue
+++ b/src/client/components/drive.file.vue
@@ -330,8 +330,8 @@ export default defineComponent({
 	}
 
 	> .thumbnail {
-		width: 128px;
-		height: 128px;
+		width: 110px;
+		height: 110px;
 		margin: auto;
 	}
 
diff --git a/src/client/components/drive.vue b/src/client/components/drive.vue
index 150d0d8774..2c8c16c482 100644
--- a/src/client/components/drive.vue
+++ b/src/client/components/drive.vue
@@ -704,6 +704,7 @@ export default defineComponent({
 	> .main {
 		flex: 1;
 		overflow: auto;
+		padding: var(--margin);
 
 		&, * {
 			user-select: none;
@@ -735,7 +736,7 @@ export default defineComponent({
 				> .folder,
 				> .file {
 					flex-grow: 1;
-					width: 144px;
+					width: 128px;
 					margin: 4px;
 					box-sizing: border-box;
 				}
@@ -743,7 +744,7 @@ export default defineComponent({
 				> .padding {
 					flex-grow: 1;
 					pointer-events: none;
-					width: 144px + 8px;
+					width: 128px + 8px;
 				}
 			}
 
diff --git a/src/client/components/emoji-picker-dialog.vue b/src/client/components/emoji-picker-dialog.vue
index 5bdbc330ad..c4b12e2f61 100644
--- a/src/client/components/emoji-picker-dialog.vue
+++ b/src/client/components/emoji-picker-dialog.vue
@@ -123,7 +123,7 @@ export default defineComponent({
 		> .index {
 			min-height: var(--height);
 			position: relative;
-			border-bottom: solid 1px var(--divider);
+			border-bottom: solid 0.5px var(--divider);
 				
 			> .arrow {
 				position: absolute;
@@ -181,7 +181,7 @@ export default defineComponent({
 			}
 
 			&.result {
-				border-bottom: solid 1px var(--divider);
+				border-bottom: solid 0.5px var(--divider);
 
 				&:empty {
 					display: none;
diff --git a/src/client/components/emoji-picker-window.vue b/src/client/components/emoji-picker-window.vue
index 5504eaecd6..53b6ae6b32 100644
--- a/src/client/components/emoji-picker-window.vue
+++ b/src/client/components/emoji-picker-window.vue
@@ -119,7 +119,7 @@ export default defineComponent({
 		> .index {
 			min-height: var(--height);
 			position: relative;
-			border-bottom: solid 1px var(--divider);
+			border-bottom: solid 0.5px var(--divider);
 				
 			> .arrow {
 				position: absolute;
@@ -177,7 +177,7 @@ export default defineComponent({
 			}
 
 			&.result {
-				border-bottom: solid 1px var(--divider);
+				border-bottom: solid 0.5px var(--divider);
 
 				&:empty {
 					display: none;
diff --git a/src/client/components/emoji-picker.vue b/src/client/components/emoji-picker.vue
index 573833b9d3..a212c15049 100644
--- a/src/client/components/emoji-picker.vue
+++ b/src/client/components/emoji-picker.vue
@@ -402,7 +402,7 @@ export default defineComponent({
 		> .tab {
 			flex: 1;
 			height: 38px;
-			border-top: solid 1px var(--divider);
+			border-top: solid 0.5px var(--divider);
 
 			&.active {
 				border-top: solid 1px var(--accent);
@@ -425,7 +425,7 @@ export default defineComponent({
 		> div {
 			&:not(.index) {
 				padding: 4px 0 8px 0;
-				border-top: solid 1px var(--divider);
+				border-top: solid 0.5px var(--divider);
 			}
 
 			> header {
@@ -492,7 +492,7 @@ export default defineComponent({
 			}
 
 			&.result {
-				border-bottom: solid 1px var(--divider);
+				border-bottom: solid 0.5px var(--divider);
 
 				&:empty {
 					display: none;
diff --git a/src/client/components/launch-pad.vue b/src/client/components/launch-pad.vue
index a81320954c..7610b44eb5 100644
--- a/src/client/components/launch-pad.vue
+++ b/src/client/components/launch-pad.vue
@@ -146,7 +146,7 @@ export default defineComponent({
 	> .sub {
 		margin-top: 8px;
 		padding-top: 8px;
-		border-top: solid 1px var(--divider);
+		border-top: solid 0.5px var(--divider);
 	}
 }
 </style>
diff --git a/src/client/components/media-image.vue b/src/client/components/media-image.vue
index 41760d98d7..4de5daa84f 100644
--- a/src/client/components/media-image.vue
+++ b/src/client/components/media-image.vue
@@ -123,7 +123,7 @@ export default defineComponent({
 
 .gqnyydlz {
 	position: relative;
-	border: solid 1px var(--divider);
+	border: solid 0.5px var(--divider);
 
 	> i {
 		display: block;
diff --git a/src/client/components/note-detailed.vue b/src/client/components/note-detailed.vue
index 5124b2a88c..373c96e5a0 100644
--- a/src/client/components/note-detailed.vue
+++ b/src/client/components/note-detailed.vue
@@ -1,6 +1,6 @@
 <template>
 <div
-	class="note _panel"
+	class="note _block"
 	v-if="!muted"
 	v-show="!isDeleted"
 	:tabindex="!isDeleted ? '-1' : null"
@@ -1007,7 +1007,7 @@ export default defineComponent({
 						margin: 0 0.5em;
 						padding: 4px 6px;
 						font-size: 80%;
-						border: solid 1px var(--divider);
+						border: solid 0.5px var(--divider);
 						border-radius: 4px;
 					}
 
@@ -1110,7 +1110,7 @@ export default defineComponent({
 	}
 
 	> .reply {
-		border-top: solid 1px var(--divider);
+		border-top: solid 0.5px var(--divider);
 	}
 
 	&.max-width_500px {
diff --git a/src/client/components/note-header.vue b/src/client/components/note-header.vue
index a6e9b6fe56..ab40c5fd4a 100644
--- a/src/client/components/note-header.vue
+++ b/src/client/components/note-header.vue
@@ -78,7 +78,7 @@ export default defineComponent({
 		margin: 0 .5em 0 0;
 		padding: 1px 6px;
 		font-size: 80%;
-		border: solid 1px var(--divider);
+		border: solid 0.5px var(--divider);
 		border-radius: 3px;
 	}
 
diff --git a/src/client/components/note.sub.vue b/src/client/components/note.sub.vue
index 853d481406..899c4b2f16 100644
--- a/src/client/components/note.sub.vue
+++ b/src/client/components/note.sub.vue
@@ -139,7 +139,7 @@ export default defineComponent({
 	}
 
 	> .reply {
-		border-left: solid 1px var(--divider);
+		border-left: solid 0.5px var(--divider);
 		margin-top: 10px;
 	}
 }
diff --git a/src/client/components/note.vue b/src/client/components/note.vue
index a656ffc356..870f8a839b 100644
--- a/src/client/components/note.vue
+++ b/src/client/components/note.vue
@@ -1,6 +1,6 @@
 <template>
 <div
-	class="tkcbzcuz _panel"
+	class="tkcbzcuz"
 	v-if="!muted"
 	v-show="!isDeleted"
 	:tabindex="!isDeleted ? '-1' : null"
@@ -90,7 +90,7 @@
 		</div>
 	</article>
 </div>
-<div v-else class="_panel muted" @click="muted = false">
+<div v-else class="muted" @click="muted = false">
 	<I18n :src="$ts.userSaysSomething" tag="small">
 		<template #name>
 			<MkA class="name" :to="userPage(appearNote.user)" v-user-preview="appearNote.userId">
@@ -851,6 +851,7 @@ export default defineComponent({
 	position: relative;
 	transition: box-shadow 0.1s ease;
 	overflow: hidden;
+	overflow: clip;
 	contain: content;
 
 	// これらの指定はパフォーマンス向上には有効だが、ノートの高さは一定でないため、
@@ -981,11 +982,17 @@ export default defineComponent({
 		> .avatar {
 			flex-shrink: 0;
 			display: block;
-			//position: sticky;
-			//top: 72px;
 			margin: 0 14px 8px 0;
 			width: 58px;
 			height: 58px;
+			position: sticky;
+			top: calc(22px + var(--stickyTop, 0px));
+			left: 0;
+
+			/* iOSはoverflow: clipをサポートしていない影響でposition: stickyが動作しない */ 
+			@supports (-webkit-touch-callout: none) {
+				top: 0;
+			}
 		}
 
 		> .main {
@@ -1106,7 +1113,7 @@ export default defineComponent({
 	}
 
 	> .reply {
-		border-top: solid 1px var(--divider);
+		border-top: solid 0.5px var(--divider);
 	}
 
 	&.max-width_500px {
@@ -1129,6 +1136,12 @@ export default defineComponent({
 				margin: 0 10px 8px 0;
 				width: 50px;
 				height: 50px;
+				top: calc(14px + var(--stickyTop, 0px));
+
+				/* iOSはoverflow: clipをサポートしていない影響でposition: stickyが動作しない */ 
+				@supports (-webkit-touch-callout: none) {
+					top: 0;
+				}
 			}
 		}
 	}
diff --git a/src/client/components/notes.vue b/src/client/components/notes.vue
index 80a9502d5f..724131d0c2 100644
--- a/src/client/components/notes.vue
+++ b/src/client/components/notes.vue
@@ -1,5 +1,5 @@
 <template>
-<div class="_list_">
+<div>
 	<div class="_fullinfo" v-if="empty">
 		<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
 		<div>{{ $ts.noNotes }}</div>
@@ -15,7 +15,7 @@
 	</div>
 
 	<XList ref="notes" :items="notes" v-slot="{ item: note }" :direction="reversed ? 'up' : 'down'" :reversed="reversed">
-		<XNote :note="note" @update:note="updated(note, $event)" :key="note._featuredId_ || note._prId_ || note.id"/>
+		<XNote :note="note" class="_block _isolated" @update:note="updated(note, $event)" :key="note._featuredId_ || note._prId_ || note.id"/>
 	</XList>
 
 	<div v-show="more && !reversed" style="margin-top: var(--margin);">
diff --git a/src/client/components/page-window.vue b/src/client/components/page-window.vue
index ca6f4dd73e..e423757e0c 100644
--- a/src/client/components/page-window.vue
+++ b/src/client/components/page-window.vue
@@ -14,7 +14,7 @@
 		<button class="_button" @click="back()" v-if="history.length > 0"><Fa :icon="faChevronLeft"/></button>
 		<button class="_button" style="pointer-events: none;" v-else><!-- マージンのバランスを取るためのダミー --></button>
 	</template>
-	<div class="yrolvcoq" style="min-height: 100%; background: var(--bg);">
+	<div class="yrolvcoq _flat_">
 		<component :is="component" v-bind="props" :ref="changePage"/>
 	</div>
 </XWindow>
@@ -155,6 +155,7 @@ export default defineComponent({
 
 <style lang="scss" scoped>
 .yrolvcoq {
-	--section-padding: 16px;
+	min-height: 100%;
+	background: var(--bg);
 }
 </style>
diff --git a/src/client/components/poll.vue b/src/client/components/poll.vue
index af3b3804ab..6cf6a8e918 100644
--- a/src/client/components/poll.vue
+++ b/src/client/components/poll.vue
@@ -110,7 +110,7 @@ export default defineComponent({
 			position: relative;
 			margin: 4px 0;
 			padding: 4px 8px;
-			border: solid 1px var(--divider);
+			border: solid 0.5px var(--divider);
 			border-radius: 4px;
 			overflow: hidden;
 			cursor: pointer;
diff --git a/src/client/components/post-form.vue b/src/client/components/post-form.vue
index 765824b2ab..a67f3ed10a 100644
--- a/src/client/components/post-form.vue
+++ b/src/client/components/post-form.vue
@@ -767,7 +767,7 @@ export default defineComponent({
 		> .cw {
 			z-index: 1;
 			padding-bottom: 8px;
-			border-bottom: solid 1px var(--divider);
+			border-bottom: solid 0.5px var(--divider);
 		}
 
 		> .text {
diff --git a/src/client/components/signin.vue b/src/client/components/signin.vue
index b9d8fe6a77..193d5afecf 100755
--- a/src/client/components/signin.vue
+++ b/src/client/components/signin.vue
@@ -1,6 +1,6 @@
 <template>
-<form class="eppvobhk" :class="{ signing, totpLogin }" @submit.prevent="onSubmit">
-	<div class="auth _section">
+<form class="eppvobhk _root" :class="{ signing, totpLogin }" @submit.prevent="onSubmit">
+	<div class="auth">
 		<div class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null }" v-show="withAvatar"></div>
 		<div class="normal-signin" v-if="!totpLogin">
 			<MkInput v-model:value="username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required @update:value="onUsernameChange">
@@ -38,7 +38,8 @@
 			</div>
 		</div>
 	</div>
-	<div class="social _section">
+	<div class="_hr"></div>
+	<div class="social">
 		<a class="_borderButton _vMargin" v-if="meta && meta.enableTwitterIntegration" :href="`${apiUrl}/signin/twitter`"><Fa :icon="faTwitter" style="margin-right: 4px;"/>{{ $t('signinWith', { x: 'Twitter' }) }}</a>
 		<a class="_borderButton _vMargin" v-if="meta && meta.enableGithubIntegration" :href="`${apiUrl}/signin/github`"><Fa :icon="faGithub" style="margin-right: 4px;"/>{{ $t('signinWith', { x: 'GitHub' }) }}</a>
 		<a class="_borderButton _vMargin" v-if="meta && meta.enableDiscordIntegration" :href="`${apiUrl}/signin/discord`"><Fa :icon="faDiscord" style="margin-right: 4px;"/>{{ $t('signinWith', { x: 'Discord' }) }}</a>
diff --git a/src/client/components/signup-dialog.vue b/src/client/components/signup-dialog.vue
index 072a5ac19f..5015b77c75 100644
--- a/src/client/components/signup-dialog.vue
+++ b/src/client/components/signup-dialog.vue
@@ -7,7 +7,7 @@
 >
 	<template #header>{{ $ts.signup }}</template>
 
-	<div class="_section">
+	<div class="_root">
 		<XSignup :auto-set="autoSet" @signup="onSignup"/>
 	</div>
 </XModalWindow>
diff --git a/src/client/components/taskmanager.api-window.vue b/src/client/components/taskmanager.api-window.vue
index 9b6c3f16d0..c9b2c43413 100644
--- a/src/client/components/taskmanager.api-window.vue
+++ b/src/client/components/taskmanager.api-window.vue
@@ -9,7 +9,7 @@
 	<template #header>Req Viewer</template>
 
 	<div class="rlkneywz">
-		<MkTab v-model:value="tab" style="border-bottom: solid 1px var(--divider);">
+		<MkTab v-model:value="tab" style="border-bottom: solid 0.5px var(--divider);">
 			<option value="req">Request</option>
 			<option value="res">Response</option>
 		</MkTab>
diff --git a/src/client/components/taskmanager.vue b/src/client/components/taskmanager.vue
index af9033178e..1339e2e352 100644
--- a/src/client/components/taskmanager.vue
+++ b/src/client/components/taskmanager.vue
@@ -4,7 +4,7 @@
 		<Fa :icon="faTerminal" style="margin-right: 0.5em;"/>Task Manager
 	</template>
 	<div class="qljqmnzj _monospace">
-		<MkTab v-model:value="tab" style="border-bottom: solid 1px var(--divider);">
+		<MkTab v-model:value="tab" style="border-bottom: solid 0.5px var(--divider);">
 			<option value="windows">Windows</option>
 			<option value="stream">Stream</option>
 			<option value="streamPool">Stream (Pool)</option>
@@ -215,7 +215,7 @@ export default defineComponent({
 		width: 100%;
 		padding: 8px 16px;
 		box-sizing: border-box;
-		border-top: solid 1px var(--divider);
+		border-top: solid 0.5px var(--divider);
 		font-size: 0.9em;
 
 		> div {
diff --git a/src/client/components/ui/container.vue b/src/client/components/ui/container.vue
index c3353cca89..efcff7a36c 100644
--- a/src/client/components/ui/container.vue
+++ b/src/client/components/ui/container.vue
@@ -1,5 +1,5 @@
 <template>
-<div class="ukygtjoj _panel" :class="{ naked, hideHeader: !showHeader, scrollable, closed: !showBody }" v-size="{ max: [380] }">
+<div class="ukygtjoj _block _isolated" :class="{ naked, hideHeader: !showHeader, scrollable, closed: !showBody }" v-size="{ max: [380] }">
 	<header v-if="showHeader" ref="header">
 		<div class="title"><slot name="header"></slot></div>
 		<div class="sub">
@@ -136,7 +136,7 @@ export default defineComponent({
 		position: relative;
 		color: var(--panelHeaderFg);
 		background: var(--panelHeaderBg);
-		box-shadow: 0 1px 0 0 var(--panelHeaderDivider);
+		border-bottom: solid 0.5px var(--panelHeaderDivider);
 		z-index: 2;
 		line-height: 1.4em;
 
@@ -172,7 +172,7 @@ export default defineComponent({
 			padding: 24px;
 
 			& + ._content {
-				border-top: solid 1px var(--divider);
+				border-top: solid 0.5px var(--divider);
 			}
 		}
 	}
diff --git a/src/client/components/ui/folder.vue b/src/client/components/ui/folder.vue
index 1cd67c2521..523298f225 100644
--- a/src/client/components/ui/folder.vue
+++ b/src/client/components/ui/folder.vue
@@ -137,4 +137,8 @@ export default defineComponent({
 		}
 	}
 }
+
+._flat_ .ssazuxis {
+	margin: var(--margin);
+}
 </style>
diff --git a/src/client/components/ui/modal-window.vue b/src/client/components/ui/modal-window.vue
index 2cdf961379..ca17ae6093 100644
--- a/src/client/components/ui/modal-window.vue
+++ b/src/client/components/ui/modal-window.vue
@@ -94,10 +94,10 @@ export default defineComponent({
 	flex-direction: column;
 	contain: content;
 
-	--section-padding: 24px;
+	--root-margin: 24px;
 
 	@media (max-width: 500px) {
-		--section-padding: 16px;
+		--root-margin: 16px;
 	}
 
 	> .header {
diff --git a/src/client/components/user-info.vue b/src/client/components/user-info.vue
index 34ea38c3b4..ac2f9a75a6 100644
--- a/src/client/components/user-info.vue
+++ b/src/client/components/user-info.vue
@@ -104,7 +104,7 @@ export default defineComponent({
 	> .description {
 		padding: 16px;
 		font-size: 0.8em;
-		border-top: solid 1px var(--divider);
+		border-top: solid 0.5px var(--divider);
 
 		> .mfm {
 			display: -webkit-box;
@@ -116,7 +116,7 @@ export default defineComponent({
 
 	> .status {
 		padding: 10px 16px;
-		border-top: solid 1px var(--divider);
+		border-top: solid 0.5px var(--divider);
 
 		> div {
 			display: inline-block;
diff --git a/src/client/components/user-select-dialog.vue b/src/client/components/user-select-dialog.vue
index e21deea178..05a43402a8 100644
--- a/src/client/components/user-select-dialog.vue
+++ b/src/client/components/user-select-dialog.vue
@@ -153,7 +153,7 @@ export default defineComponent({
 		> .user {
 			display: flex;
 			align-items: center;
-			padding: 8px var(--section-padding);
+			padding: 8px var(--root-margin);
 			font-size: 14px;
 
 			&:hover {
diff --git a/src/client/components/visibility-picker.vue b/src/client/components/visibility-picker.vue
index 0b98d30b9d..caa2b116a6 100644
--- a/src/client/components/visibility-picker.vue
+++ b/src/client/components/visibility-picker.vue
@@ -97,7 +97,7 @@ export default defineComponent({
 
 	> .divider {
 		margin: 8px 0;
-		border-top: solid 1px var(--divider);
+		border-top: solid 0.5px var(--divider);
 	}
 
 	> button {
diff --git a/src/client/init.ts b/src/client/init.ts
index bacc839c29..e77e53dc64 100644
--- a/src/client/init.ts
+++ b/src/client/init.ts
@@ -161,6 +161,7 @@ const app = createApp(await (
 	ui === 'deck'                     ? import('@client/ui/deck.vue') :
 	ui === 'desktop'                  ? import('@client/ui/desktop.vue') :
 	ui === 'chat'                     ? import('@client/ui/chat/index.vue') :
+	ui === 'pope'                     ? import('@client/ui/universal.vue') :
 	import('@client/ui/default.vue')
 ).then(x => x.default));
 
diff --git a/src/client/pages/clip.vue b/src/client/pages/clip.vue
index c6e46a9834..9ae41c72f2 100644
--- a/src/client/pages/clip.vue
+++ b/src/client/pages/clip.vue
@@ -142,7 +142,7 @@ export default defineComponent({
 	> .user {
 		$height: 32px;
 		padding: 16px;
-		border-top: solid 1px var(--divider);
+		border-top: solid 0.5px var(--divider);
 		line-height: $height;
 
 		> .avatar {
diff --git a/src/client/pages/doc.vue b/src/client/pages/doc.vue
index 4341cd1f6e..8b8a89efc6 100644
--- a/src/client/pages/doc.vue
+++ b/src/client/pages/doc.vue
@@ -150,7 +150,7 @@ export default defineComponent({
 			font-size: 1.25em;
 			padding: 0 0 0.5em 0;
 			margin: 1.5em 0 1em 0;
-			border-bottom: solid 1px var(--divider);
+			border-bottom: solid 0.5px var(--divider);
 		}
 
 		::v-deep(table) {
@@ -170,7 +170,7 @@ export default defineComponent({
 		::v-deep(kbd.key) {
 			display: inline-block;
 			padding: 6px 8px;
-			border: solid 1px var(--divider);
+			border: solid 0.5px var(--divider);
 			border-radius: 4px;
 			box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
 		}
diff --git a/src/client/pages/drive.vue b/src/client/pages/drive.vue
index 754581a8b6..3ca7f60099 100644
--- a/src/client/pages/drive.vue
+++ b/src/client/pages/drive.vue
@@ -6,7 +6,7 @@
 
 <script lang="ts">
 import { computed, defineComponent } from 'vue';
-import { faCloud, faEllipsisH } from '@fortawesome/free-solid-svg-icons';
+import { faCloud } from '@fortawesome/free-solid-svg-icons';
 import XDrive from '@client/components/drive.vue';
 import * as os from '@client/os';
 
@@ -20,19 +20,10 @@ export default defineComponent({
 			INFO: {
 				title: computed(() => this.folder ? this.folder.name : this.$ts.drive),
 				icon: faCloud,
-				action: {
-					icon: faEllipsisH,
-					handler: this.menu
-				}
+				menu: () => this.$refs.drive.getMenu()
 			},
 			folder: null,
 		};
 	},
-
-	methods: {
-		menu(ev) {
-			os.modalMenu(this.$refs.drive.getMenu(), ev.currentTarget || ev.target);
-		}
-	}
 });
 </script>
diff --git a/src/client/pages/explore.vue b/src/client/pages/explore.vue
index da2eaffb84..dc3d424ba4 100644
--- a/src/client/pages/explore.vue
+++ b/src/client/pages/explore.vue
@@ -1,5 +1,5 @@
 <template>
-<div class="lznhrdub">
+<div class="lznhrdub _root">
 	<div class="_section">
 		<MkInput v-model:value="query" :debounce="true" type="search"><template #icon><Fa :icon="faSearch"/></template><span>{{ $ts.searchUser }}</span></MkInput>
 
diff --git a/src/client/pages/instance/instance.vue b/src/client/pages/instance/instance.vue
index 118e8eae6e..1adb3ab9d2 100644
--- a/src/client/pages/instance/instance.vue
+++ b/src/client/pages/instance/instance.vue
@@ -500,12 +500,12 @@ export default defineComponent({
 		}
 
 		&:not(:first-child) {
-			border-top: solid 1px var(--divider);
+			border-top: solid 0.5px var(--divider);
 		}
 	}
 
 	> .chart {
-		border-top: solid 1px var(--divider);
+		border-top: solid 0.5px var(--divider);
 		padding: 16px 0 12px 0;
 
 		> .header {
diff --git a/src/client/pages/messaging/index.vue b/src/client/pages/messaging/index.vue
index c906b968a4..114a01ec3c 100644
--- a/src/client/pages/messaging/index.vue
+++ b/src/client/pages/messaging/index.vue
@@ -1,40 +1,38 @@
 <template>
-<div class="_section">
-	<div class="mk-messaging _content" v-size="{ max: [400] }">
-		<MkButton @click="start" primary class="start"><Fa :icon="faPlus"/> {{ $ts.startMessaging }}</MkButton>
+<div class="yweeujhr _root" v-size="{ max: [400] }">
+	<MkButton @click="start" primary class="start"><Fa :icon="faPlus"/> {{ $ts.startMessaging }}</MkButton>
 
-		<div class="history" v-if="messages.length > 0">
-			<MkA v-for="(message, i) in messages"
-				class="message _panel"
-				:class="{ isMe: isMe(message), isRead: message.groupId ? message.reads.includes($i.id) : message.isRead }"
-				:to="message.groupId ? `/my/messaging/group/${message.groupId}` : `/my/messaging/${getAcct(isMe(message) ? message.recipient : message.user)}`"
-				:data-index="i"
-				:key="message.id"
-				v-anim="i"
-			>
-				<div>
-					<MkAvatar class="avatar" :user="message.groupId ? message.user : isMe(message) ? message.recipient : message.user"/>
-					<header v-if="message.groupId">
-						<span class="name">{{ message.group.name }}</span>
-						<MkTime :time="message.createdAt" class="time"/>
-					</header>
-					<header v-else>
-						<span class="name"><MkUserName :user="isMe(message) ? message.recipient : message.user"/></span>
-						<span class="username">@{{ acct(isMe(message) ? message.recipient : message.user) }}</span>
-						<MkTime :time="message.createdAt" class="time"/>
-					</header>
-					<div class="body">
-						<p class="text"><span class="me" v-if="isMe(message)">{{ $ts.you }}:</span>{{ message.text }}</p>
-					</div>
+	<div class="history" v-if="messages.length > 0">
+		<MkA v-for="(message, i) in messages"
+			class="message _block _isolated"
+			:class="{ isMe: isMe(message), isRead: message.groupId ? message.reads.includes($i.id) : message.isRead }"
+			:to="message.groupId ? `/my/messaging/group/${message.groupId}` : `/my/messaging/${getAcct(isMe(message) ? message.recipient : message.user)}`"
+			:data-index="i"
+			:key="message.id"
+			v-anim="i"
+		>
+			<div>
+				<MkAvatar class="avatar" :user="message.groupId ? message.user : isMe(message) ? message.recipient : message.user"/>
+				<header v-if="message.groupId">
+					<span class="name">{{ message.group.name }}</span>
+					<MkTime :time="message.createdAt" class="time"/>
+				</header>
+				<header v-else>
+					<span class="name"><MkUserName :user="isMe(message) ? message.recipient : message.user"/></span>
+					<span class="username">@{{ acct(isMe(message) ? message.recipient : message.user) }}</span>
+					<MkTime :time="message.createdAt" class="time"/>
+				</header>
+				<div class="body">
+					<p class="text"><span class="me" v-if="isMe(message)">{{ $ts.you }}:</span>{{ message.text }}</p>
 				</div>
-			</MkA>
-		</div>
-		<div class="_fullinfo" v-if="!fetching && messages.length == 0">
-			<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
-			<div>{{ $ts.noHistory }}</div>
-		</div>
-		<MkLoading v-if="fetching"/>
+			</div>
+		</MkA>
 	</div>
+	<div class="_fullinfo" v-if="!fetching && messages.length == 0">
+		<img src="https://xn--931a.moe/assets/info.jpg" class="_ghost"/>
+		<div>{{ $ts.noHistory }}</div>
+	</div>
+	<MkLoading v-if="fetching"/>
 </div>
 </template>
 
@@ -167,10 +165,10 @@ export default defineComponent({
 </script>
 
 <style lang="scss" scoped>
-.mk-messaging {
+.yweeujhr {
 
 	> .start {
-		margin: 0 auto var(--margin) auto;
+		margin: var(--margin) auto var(--margin) auto;
 	}
 
 	> .history {
diff --git a/src/client/pages/messaging/messaging-room.form.vue b/src/client/pages/messaging/messaging-room.form.vue
index 5826b8d0d2..c547e18850 100644
--- a/src/client/pages/messaging/messaging-room.form.vue
+++ b/src/client/pages/messaging/messaging-room.form.vue
@@ -1,5 +1,5 @@
 <template>
-<div class="mk-messaging-form _panel"
+<div class="pemppnzi _block"
 	@dragover.stop="onDragover"
 	@drop.stop="onDrop"
 >
@@ -230,7 +230,7 @@ export default defineComponent({
 </script>
 
 <style lang="scss" scoped>
-.mk-messaging-form {
+.pemppnzi {
 	position: relative;
 
 	> textarea {
diff --git a/src/client/pages/my-clips/index.vue b/src/client/pages/my-clips/index.vue
index b724dfb6b7..d29d147e87 100644
--- a/src/client/pages/my-clips/index.vue
+++ b/src/client/pages/my-clips/index.vue
@@ -96,7 +96,7 @@ export default defineComponent({
 				> .description {
 					margin-top: 8px;
 					padding-top: 8px;
-					border-top: solid 1px var(--divider);
+					border-top: solid 0.5px var(--divider);
 				}
 			}
 		}
diff --git a/src/client/pages/note.vue b/src/client/pages/note.vue
index eb46c49d8b..6debb611fd 100644
--- a/src/client/pages/note.vue
+++ b/src/client/pages/note.vue
@@ -1,12 +1,12 @@
 <template>
-<div class="fcuexfpr">
+<div class="fcuexfpr _root">
 	<div v-if="note" class="note" v-anim>
-		<div class="_section" v-if="showNext">
+		<div class="_vMargin" v-if="showNext">
 			<XNotes class="_content _noGap_" :pagination="next"/>
 		</div>
 
-		<div class="_section main">
-			<MkButton v-if="!showNext && hasNext" class="load next _content" @click="showNext = true"><Fa :icon="faChevronUp"/></MkButton>
+		<div class="main _vMargin">
+			<MkButton v-if="!showNext && hasNext" class="load next" @click="showNext = true"><Fa :icon="faChevronUp"/></MkButton>
 			<div class="_content _vMargin">
 				<MkRemoteCaution v-if="note.user.host != null" :href="note.url || note.uri" class="_vMargin"/>
 				<XNoteDetailed v-model:note="note" :key="note.id" class="_vMargin"/>
@@ -21,10 +21,10 @@
 					</div>
 				</MkA>
 			</div>
-			<MkButton v-if="!showPrev && hasPrev" class="load prev _content" @click="showPrev = true"><Fa :icon="faChevronDown"/></MkButton>
+			<MkButton v-if="!showPrev && hasPrev" class="load prev" @click="showPrev = true"><Fa :icon="faChevronDown"/></MkButton>
 		</div>
 
-		<div class="_section" v-if="showPrev">
+		<div class="_vMargin" v-if="showPrev">
 			<XNotes class="_content _noGap_" :pagination="prev"/>
 		</div>
 	</div>
@@ -137,6 +137,7 @@ export default defineComponent({
 		> .main {
 			> .load {
 				min-width: 0;
+				margin: 0 auto;
 				border-radius: 999px;
 
 				&.next {
@@ -165,7 +166,7 @@ export default defineComponent({
 					> .user {
 						$height: 32px;
 						padding-top: 16px;
-						border-top: solid 1px var(--divider);
+						border-top: solid 0.5px var(--divider);
 						line-height: $height;
 
 						> .avatar {
diff --git a/src/client/pages/notifications.vue b/src/client/pages/notifications.vue
index a6d73b4290..19b1726759 100644
--- a/src/client/pages/notifications.vue
+++ b/src/client/pages/notifications.vue
@@ -1,8 +1,6 @@
 <template>
-<div>
-	<div class="_section">
-		<XNotifications class="_content" @before="before" @after="after" page/>
-	</div>
+<div class="_root">
+	<XNotifications class="_content" @before="before" @after="after" page/>
 </div>
 </template>
 
diff --git a/src/client/pages/page-editor/page-editor.vue b/src/client/pages/page-editor/page-editor.vue
index 762ee774ed..79e3bfdf9a 100644
--- a/src/client/pages/page-editor/page-editor.vue
+++ b/src/client/pages/page-editor/page-editor.vue
@@ -1,87 +1,85 @@
 <template>
-<div class="_section">
-	<div class="_content">
-		<MkA class="view" v-if="pageId" :to="`/@${ author.username }/pages/${ currentName }`"><Fa :icon="faExternalLinkSquareAlt"/> {{ $ts._pages.viewPage }}</MkA>
+<div class="_root">
+	<MkA class="view" v-if="pageId" :to="`/@${ author.username }/pages/${ currentName }`"><Fa :icon="faExternalLinkSquareAlt"/> {{ $ts._pages.viewPage }}</MkA>
 
-		<div class="buttons" style="margin: 16px 0;">
-			<MkButton inline @click="save" primary class="save" v-if="!readonly"><Fa :icon="faSave"/> {{ $ts.save }}</MkButton>
-			<MkButton inline @click="duplicate" class="duplicate" v-if="pageId"><Fa :icon="faCopy"/> {{ $ts.duplicate }}</MkButton>
-			<MkButton inline @click="del" class="delete" v-if="pageId && !readonly"><Fa :icon="faTrashAlt"/> {{ $ts.delete }}</MkButton>
-		</div>
+	<div class="buttons" style="margin: 16px;">
+		<MkButton inline @click="save" primary class="save" v-if="!readonly"><Fa :icon="faSave"/> {{ $ts.save }}</MkButton>
+		<MkButton inline @click="duplicate" class="duplicate" v-if="pageId"><Fa :icon="faCopy"/> {{ $ts.duplicate }}</MkButton>
+		<MkButton inline @click="del" class="delete" v-if="pageId && !readonly"><Fa :icon="faTrashAlt"/> {{ $ts.delete }}</MkButton>
+	</div>
 
-		<MkContainer :body-togglable="true" :expanded="true" class="_vMargin">
-			<template #header><Fa :icon="faCog"/> {{ $ts._pages.pageSetting }}</template>
-			<div class="_section">
-				<MkInput v-model:value="title">
-					<span>{{ $ts._pages.title }}</span>
-				</MkInput>
+	<MkContainer :body-togglable="true" :expanded="true" class="_vMargin">
+		<template #header><Fa :icon="faCog"/> {{ $ts._pages.pageSetting }}</template>
+		<div style="padding: 16px;">
+			<MkInput v-model:value="title">
+				<span>{{ $ts._pages.title }}</span>
+			</MkInput>
 
-				<MkInput v-model:value="summary">
-					<span>{{ $ts._pages.summary }}</span>
-				</MkInput>
+			<MkInput v-model:value="summary">
+				<span>{{ $ts._pages.summary }}</span>
+			</MkInput>
 
-				<MkInput v-model:value="name">
-					<template #prefix>{{ url }}/@{{ author.username }}/pages/</template>
-					<span>{{ $ts._pages.url }}</span>
-				</MkInput>
+			<MkInput v-model:value="name">
+				<template #prefix>{{ url }}/@{{ author.username }}/pages/</template>
+				<span>{{ $ts._pages.url }}</span>
+			</MkInput>
 
-				<MkSwitch v-model:value="alignCenter">{{ $ts._pages.alignCenter }}</MkSwitch>
+			<MkSwitch v-model:value="alignCenter">{{ $ts._pages.alignCenter }}</MkSwitch>
 
-				<MkSelect v-model:value="font">
-					<template #label>{{ $ts._pages.font }}</template>
-					<option value="serif">{{ $ts._pages.fontSerif }}</option>
-					<option value="sans-serif">{{ $ts._pages.fontSansSerif }}</option>
-				</MkSelect>
+			<MkSelect v-model:value="font">
+				<template #label>{{ $ts._pages.font }}</template>
+				<option value="serif">{{ $ts._pages.fontSerif }}</option>
+				<option value="sans-serif">{{ $ts._pages.fontSansSerif }}</option>
+			</MkSelect>
 
-				<MkSwitch v-model:value="hideTitleWhenPinned">{{ $ts._pages.hideTitleWhenPinned }}</MkSwitch>
+			<MkSwitch v-model:value="hideTitleWhenPinned">{{ $ts._pages.hideTitleWhenPinned }}</MkSwitch>
 
-				<div class="eyeCatch">
-					<MkButton v-if="eyeCatchingImageId == null && !readonly" @click="setEyeCatchingImage"><Fa :icon="faPlus"/> {{ $ts._pages.eyeCatchingImageSet }}</MkButton>
-					<div v-else-if="eyeCatchingImage">
-						<img :src="eyeCatchingImage.url" :alt="eyeCatchingImage.name" style="max-width: 100%;"/>
-						<MkButton @click="removeEyeCatchingImage()" v-if="!readonly"><Fa :icon="faTrashAlt"/> {{ $ts._pages.eyeCatchingImageRemove }}</MkButton>
-					</div>
+			<div class="eyeCatch">
+				<MkButton v-if="eyeCatchingImageId == null && !readonly" @click="setEyeCatchingImage"><Fa :icon="faPlus"/> {{ $ts._pages.eyeCatchingImageSet }}</MkButton>
+				<div v-else-if="eyeCatchingImage">
+					<img :src="eyeCatchingImage.url" :alt="eyeCatchingImage.name" style="max-width: 100%;"/>
+					<MkButton @click="removeEyeCatchingImage()" v-if="!readonly"><Fa :icon="faTrashAlt"/> {{ $ts._pages.eyeCatchingImageRemove }}</MkButton>
 				</div>
 			</div>
-		</MkContainer>
+		</div>
+	</MkContainer>
 
-		<MkContainer :body-togglable="true" :expanded="true" class="_vMargin">
-			<template #header><Fa :icon="faStickyNote"/> {{ $ts._pages.contents }}</template>
-			<div class="_section">
-				<XBlocks class="content" v-model:value="content" :hpml="hpml"/>
+	<MkContainer :body-togglable="true" :expanded="true" class="_vMargin">
+		<template #header><Fa :icon="faStickyNote"/> {{ $ts._pages.contents }}</template>
+		<div style="padding: 16px;">
+			<XBlocks class="content" v-model:value="content" :hpml="hpml"/>
 
-				<MkButton @click="add()" v-if="!readonly"><Fa :icon="faPlus"/></MkButton>
-			</div>
-		</MkContainer>
+			<MkButton @click="add()" v-if="!readonly"><Fa :icon="faPlus"/></MkButton>
+		</div>
+	</MkContainer>
 
-		<MkContainer :body-togglable="true" class="_vMargin">
-			<template #header><Fa :icon="faMagic"/> {{ $ts._pages.variables }}</template>
-			<div class="qmuvgica">
-				<XDraggable tag="div" class="variables" v-show="variables.length > 0" v-model="variables" item-key="name" handle=".drag-handle" :group="{ name: 'variables' }" animation="150" swap-threshold="0.5">
-					<template #item="{element}">
-						<XVariable
-							:value="element"
-							:removable="true"
-							@remove="() => removeVariable(element)"
-							:hpml="hpml"
-							:name="element.name"
-							:title="element.name"
-							:draggable="true"
-						/>
-					</template>
-				</XDraggable>
+	<MkContainer :body-togglable="true" class="_vMargin">
+		<template #header><Fa :icon="faMagic"/> {{ $ts._pages.variables }}</template>
+		<div class="qmuvgica">
+			<XDraggable tag="div" class="variables" v-show="variables.length > 0" v-model="variables" item-key="name" handle=".drag-handle" :group="{ name: 'variables' }" animation="150" swap-threshold="0.5">
+				<template #item="{element}">
+					<XVariable
+						:value="element"
+						:removable="true"
+						@remove="() => removeVariable(element)"
+						:hpml="hpml"
+						:name="element.name"
+						:title="element.name"
+						:draggable="true"
+					/>
+				</template>
+			</XDraggable>
 
-				<MkButton @click="addVariable()" class="add" v-if="!readonly"><Fa :icon="faPlus"/></MkButton>
-			</div>
-		</MkContainer>
+			<MkButton @click="addVariable()" class="add" v-if="!readonly"><Fa :icon="faPlus"/></MkButton>
+		</div>
+	</MkContainer>
 
-		<MkContainer :body-togglable="true" :expanded="true" class="_vMargin">
-			<template #header><Fa :icon="faCode"/> {{ $ts.script }}</template>
-			<div>
-				<MkTextarea class="_code" v-model:value="script"/>
-			</div>
-		</MkContainer>
-	</div>
+	<MkContainer :body-togglable="true" :expanded="true" class="_vMargin">
+		<template #header><Fa :icon="faCode"/> {{ $ts.script }}</template>
+		<div>
+			<MkTextarea class="_code" v-model:value="script"/>
+		</div>
+	</MkContainer>
 </div>
 </template>
 
diff --git a/src/client/pages/reversi/index.vue b/src/client/pages/reversi/index.vue
index f40990b37c..b6a1db1161 100644
--- a/src/client/pages/reversi/index.vue
+++ b/src/client/pages/reversi/index.vue
@@ -259,7 +259,7 @@ export default defineComponent({
 		> footer {
 			display: flex;
 			align-items: baseline;
-			border-top: solid 1px var(--divider);
+			border-top: solid 0.5px var(--divider);
 			padding: 6px 8px;
 			font-size: 0.9em;
 
diff --git a/src/client/pages/timeline.vue b/src/client/pages/timeline.vue
index e0c0b2995a..0d8e5b0ec3 100644
--- a/src/client/pages/timeline.vue
+++ b/src/client/pages/timeline.vue
@@ -1,39 +1,37 @@
 <template>
-<div class="cmuxhskf" v-hotkey.global="keymap">
+<div class="cmuxhskf _root" v-hotkey.global="keymap">
 	<div class="new" v-if="queue > 0" :style="{ width: width + 'px' }"><button class="_buttonPrimary" @click="top()">{{ $ts.newNoteRecived }}</button></div>
 
-	<div class="_section">
-		<XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="tutorial _content _vMargin"/>
-		<XPostForm v-if="$store.reactiveState.showFixedPostForm.value" class="post-form _panel _content _vMargin" fixed/>
-		<div class="tabs _panel _vMargin">
-			<div class="left">
-				<button class="_button tab" @click="() => { src = 'home'; saveSrc(); }" :class="{ active: src === 'home' }" v-tooltip="$ts._timelines.home"><Fa :icon="faHome"/></button>
-				<button class="_button tab" @click="() => { src = 'local'; saveSrc(); }" :class="{ active: src === 'local' }" v-tooltip="$ts._timelines.local" v-if="isLocalTimelineAvailable"><Fa :icon="faComments"/></button>
-				<button class="_button tab" @click="() => { src = 'social'; saveSrc(); }" :class="{ active: src === 'social' }" v-tooltip="$ts._timelines.social" v-if="isLocalTimelineAvailable"><Fa :icon="faShareAlt"/></button>
-				<button class="_button tab" @click="() => { src = 'global'; saveSrc(); }" :class="{ active: src === 'global' }" v-tooltip="$ts._timelines.global" v-if="isGlobalTimelineAvailable"><Fa :icon="faGlobe"/></button>
-				<span class="divider"></span>
-				<button class="_button tab" @click="() => { src = 'mentions'; saveSrc(); }" :class="{ active: src === 'mentions' }" v-tooltip="$ts.mentions"><Fa :icon="faAt"/><Fa :icon="faCircle" class="i" v-if="$i.hasUnreadMentions"/></button>
-				<button class="_button tab" @click="() => { src = 'directs'; saveSrc(); }" :class="{ active: src === 'directs' }" v-tooltip="$ts.directNotes"><Fa :icon="faEnvelope"/><Fa :icon="faCircle" class="i" v-if="$i.hasUnreadSpecifiedNotes"/></button>
-			</div>
-			<div class="right">
-				<button class="_button tab" @click="chooseChannel" :class="{ active: src === 'channel' }" v-tooltip="$ts.channel"><Fa :icon="faSatelliteDish"/><Fa :icon="faCircle" class="i" v-if="$i.hasUnreadChannel"/></button>
-				<button class="_button tab" @click="chooseAntenna" :class="{ active: src === 'antenna' }" v-tooltip="$ts.antennas"><Fa :icon="faSatellite"/><Fa :icon="faCircle" class="i" v-if="$i.hasUnreadAntenna"/></button>
-				<button class="_button tab" @click="chooseList" :class="{ active: src === 'list' }" v-tooltip="$ts.lists"><Fa :icon="faListUl"/></button>
-			</div>
+	<XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="tutorial _block _isolated"/>
+	<XPostForm v-if="$store.reactiveState.showFixedPostForm.value" class="post-form _block _isolated" fixed/>
+	<div class="tabs _block _vMargin">
+		<div class="left">
+			<button class="_button tab" @click="() => { src = 'home'; saveSrc(); }" :class="{ active: src === 'home' }" v-tooltip="$ts._timelines.home"><Fa :icon="faHome"/></button>
+			<button class="_button tab" @click="() => { src = 'local'; saveSrc(); }" :class="{ active: src === 'local' }" v-tooltip="$ts._timelines.local" v-if="isLocalTimelineAvailable"><Fa :icon="faComments"/></button>
+			<button class="_button tab" @click="() => { src = 'social'; saveSrc(); }" :class="{ active: src === 'social' }" v-tooltip="$ts._timelines.social" v-if="isLocalTimelineAvailable"><Fa :icon="faShareAlt"/></button>
+			<button class="_button tab" @click="() => { src = 'global'; saveSrc(); }" :class="{ active: src === 'global' }" v-tooltip="$ts._timelines.global" v-if="isGlobalTimelineAvailable"><Fa :icon="faGlobe"/></button>
+			<span class="divider"></span>
+			<button class="_button tab" @click="() => { src = 'mentions'; saveSrc(); }" :class="{ active: src === 'mentions' }" v-tooltip="$ts.mentions"><Fa :icon="faAt"/><Fa :icon="faCircle" class="i" v-if="$i.hasUnreadMentions"/></button>
+			<button class="_button tab" @click="() => { src = 'directs'; saveSrc(); }" :class="{ active: src === 'directs' }" v-tooltip="$ts.directNotes"><Fa :icon="faEnvelope"/><Fa :icon="faCircle" class="i" v-if="$i.hasUnreadSpecifiedNotes"/></button>
+		</div>
+		<div class="right">
+			<button class="_button tab" @click="chooseChannel" :class="{ active: src === 'channel' }" v-tooltip="$ts.channel"><Fa :icon="faSatelliteDish"/><Fa :icon="faCircle" class="i" v-if="$i.hasUnreadChannel"/></button>
+			<button class="_button tab" @click="chooseAntenna" :class="{ active: src === 'antenna' }" v-tooltip="$ts.antennas"><Fa :icon="faSatellite"/><Fa :icon="faCircle" class="i" v-if="$i.hasUnreadAntenna"/></button>
+			<button class="_button tab" @click="chooseList" :class="{ active: src === 'list' }" v-tooltip="$ts.lists"><Fa :icon="faListUl"/></button>
 		</div>
-		<XTimeline ref="tl"
-			class="_content _vMargin"
-			:key="src === 'list' ? `list:${list.id}` : src === 'antenna' ? `antenna:${antenna.id}` : src === 'channel' ? `channel:${channel.id}` : src"
-			:src="src"
-			:list="list ? list.id : null"
-			:antenna="antenna ? antenna.id : null"
-			:channel="channel ? channel.id : null"
-			:sound="true"
-			@before="before()"
-			@after="after()"
-			@queue="queueUpdated"
-		/>
 	</div>
+	<XTimeline ref="tl"
+		class="_vMargin"
+		:key="src === 'list' ? `list:${list.id}` : src === 'antenna' ? `antenna:${antenna.id}` : src === 'channel' ? `channel:${channel.id}` : src"
+		:src="src"
+		:list="list ? list.id : null"
+		:antenna="antenna ? antenna.id : null"
+		:channel="channel ? channel.id : null"
+		:sound="true"
+		@before="before()"
+		@after="after()"
+		@queue="queueUpdated"
+	/>
 </div>
 </template>
 
@@ -200,6 +198,18 @@ export default defineComponent({
 			});
 		},
 
+		async timetravel() {
+			const { canceled, result: date } = await os.dialog({
+				title: this.$ts.date,
+				input: {
+					type: 'date'
+				}
+			});
+			if (canceled) return;
+
+			this.$refs.tl.timetravel(new Date(date));
+		},
+
 		focus() {
 			(this.$refs.tl as any).focus();
 		}
@@ -221,70 +231,65 @@ export default defineComponent({
 		}
 	}
 
-	> ._section {
-		> .tabs {
-			display: flex;
-			box-sizing: border-box;
-			padding: 0 8px;
-			max-width: var(--baseContentWidth);
+	> .tabs {
+		display: flex;
+		box-sizing: border-box;
+		padding: 0 8px;
+		white-space: nowrap;
+		overflow: auto;
+
+		// 影の都合上
+		position: relative;
+
+		> .right {
 			margin-left: auto;
-			margin-right: auto;
-			white-space: nowrap;
-			overflow: auto;
+		}
 
-			// 影の都合上
-			position: relative;
+		> .left, > .right {
+			> .tab {
+				position: relative;
+				height: 50px;
+				padding: 0 12px;
 
-			> .right {
-				margin-left: auto;
+				&:hover {
+					color: var(--fgHighlighted);
+				}
+
+				&.active {
+					color: var(--fgHighlighted);
+
+					&:after {
+						content: "";
+						display: block;
+						position: absolute;
+						bottom: 0;
+						left: 0;
+						right: 0;
+						margin: 0 auto;
+						width: calc(100% - 16px);
+						height: 4px;
+						background: var(--accent);
+						border-radius: 8px 8px 0 0;
+					}
+				}
+
+				> .i {
+					position: absolute;
+					top: 16px;
+					right: 8px;
+					color: var(--indicator);
+					font-size: 8px;
+					animation: blink 1s infinite;
+				}
 			}
 
-			> .left, > .right {
-				> .tab {
-					position: relative;
-					height: 50px;
-					padding: 0 12px;
-
-					&:hover {
-						color: var(--fgHighlighted);
-					}
-
-					&.active {
-						color: var(--fgHighlighted);
-
-						&:after {
-							content: "";
-							display: block;
-							position: absolute;
-							bottom: 0;
-							left: 0;
-							right: 0;
-							margin: 0 auto;
-							width: calc(100% - 16px);
-							height: 4px;
-							background: var(--accent);
-							border-radius: 8px 8px 0 0;
-						}
-					}
-
-					> .i {
-						position: absolute;
-						top: 16px;
-						right: 8px;
-						color: var(--indicator);
-						font-size: 8px;
-						animation: blink 1s infinite;
-					}
-				}
-
-				> .divider {
-					display: inline-block;
-					width: 1px;
-					height: 28px;
-					vertical-align: middle;
-					margin: 0 8px;
-					background: var(--divider);
-				}
+			> .divider {
+				display: inline-block;
+				width: 1px;
+				height: 28px;
+				vertical-align: middle;
+				margin: 0 8px;
+				background: var(--divider);
 			}
 		}
 	}
diff --git a/src/client/pages/user/index.timeline.vue b/src/client/pages/user/index.timeline.vue
index 8c824a2111..62f7f7d654 100644
--- a/src/client/pages/user/index.timeline.vue
+++ b/src/client/pages/user/index.timeline.vue
@@ -1,11 +1,11 @@
 <template>
 <div>
-	<MkTab v-model:value="with_" class="_vMargin">
+	<MkTab v-model:value="with_" class="_isolated _section">
 		<option :value="null">{{ $ts.notes }}</option>
 		<option value="replies">{{ $ts.notesAndReplies }}</option>
 		<option value="files">{{ $ts.withFiles }}</option>
 	</MkTab>
-	<XNotes ref="timeline" class="_vMargin" :pagination="pagination" @before="$emit('before')" @after="e => $emit('after', e)"/>
+	<XNotes ref="timeline" class="_section _noGap_" :pagination="pagination" @before="$emit('before')" @after="e => $emit('after', e)"/>
 </div>
 </template>
 
diff --git a/src/client/pages/user/index.vue b/src/client/pages/user/index.vue
index 5ca29a3e41..18f78d270a 100644
--- a/src/client/pages/user/index.vue
+++ b/src/client/pages/user/index.vue
@@ -93,15 +93,15 @@
 			</div>
 		</div>
 	</div>
-	<div class="ftskorzw narrow _section" v-else-if="user && narrow === true" v-size="{ max: [500] }">
+	<div class="ftskorzw narrow _root" v-else-if="user && narrow === true" v-size="{ max: [500] }">
 		<!-- TODO -->
 		<!-- <div class="punished" v-if="user.isSuspended"><Fa :icon="faExclamationTriangle" style="margin-right: 8px;"/> {{ $ts.userSuspended }}</div> -->
 		<!-- <div class="punished" v-if="user.isSilenced"><Fa :icon="faExclamationTriangle" style="margin-right: 8px;"/> {{ $ts.userSilenced }}</div> -->
 
-		<div class="profile _content _vMargin">
+		<div class="profile">
 			<MkRemoteCaution v-if="user.host != null" :href="user.url" class="_vMargin"/>
 
-			<div class="_vMargin _panel main" :key="user.id">
+			<div class="_vMargin _block main" :key="user.id">
 				<div class="banner-container" :style="style">
 					<div class="banner" ref="banner" :style="style"></div>
 					<div class="fade"></div>
@@ -177,37 +177,39 @@
 			</div>
 		</div>
 
-		<div class="nav _vMargin">
-			<MkA :to="userPage(user)" :class="{ active: page === 'index' }" class="link">
-				<Fa :icon="faCommentAlt" class="icon"/>
-				<span>{{ $ts.notes }}</span>
-			</MkA>
-			<MkA :to="userPage(user, 'clips')" :class="{ active: page === 'clips' }" class="link">
-				<Fa :icon="faPaperclip" class="icon"/>
-				<span>{{ $ts.clips }}</span>
-			</MkA>
-			<MkA :to="userPage(user, 'pages')" :class="{ active: page === 'pages' }" class="link">
-				<Fa :icon="faFileAlt" class="icon"/>
-				<span>{{ $ts.pages }}</span>
-			</MkA>
-		</div>
+		<div class="contents">
+			<div class="nav _isolated">
+				<MkA :to="userPage(user)" :class="{ active: page === 'index' }" class="link">
+					<Fa :icon="faCommentAlt" class="icon"/>
+					<span>{{ $ts.notes }}</span>
+				</MkA>
+				<MkA :to="userPage(user, 'clips')" :class="{ active: page === 'clips' }" class="link">
+					<Fa :icon="faPaperclip" class="icon"/>
+					<span>{{ $ts.clips }}</span>
+				</MkA>
+				<MkA :to="userPage(user, 'pages')" :class="{ active: page === 'pages' }" class="link">
+					<Fa :icon="faFileAlt" class="icon"/>
+					<span>{{ $ts.pages }}</span>
+				</MkA>
+			</div>
 
-		<template v-if="page === 'index'">
-			<div class="_content _vMargin">
-				<div v-if="user.pinnedNotes.length > 0" class="_vMargin">
-					<XNote v-for="note in user.pinnedNotes" class="note _vMargin" :note="note" @update:note="pinnedNoteUpdated(note, $event)" :key="note.id" :pinned="true"/>
+			<template v-if="page === 'index'">
+				<div>
+					<div v-if="user.pinnedNotes.length > 0">
+						<XNote v-for="note in user.pinnedNotes" class="note _block _isolated" :note="note" @update:note="pinnedNoteUpdated(note, $event)" :key="note.id" :pinned="true"/>
+					</div>
+					<XPhotos :user="user" :key="user.id"/>
+					<XActivity :user="user" :key="user.id"/>
 				</div>
-				<XPhotos :user="user" :key="user.id" class="_vMargin"/>
-				<XActivity :user="user" :key="user.id" class="_vMargin"/>
-			</div>
-			<div class="_content _vMargin">
-				<XUserTimeline :user="user" class="_content"/>
-			</div>
-		</template>
-		<XFollowList v-else-if="page === 'following'" type="following" :user="user" class="_content _vMargin"/>
-		<XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_content _vMargin"/>
-		<XClips v-else-if="page === 'clips'" :user="user" class="_vMargin"/>
-		<XPages v-else-if="page === 'pages'" :user="user" class="_vMargin"/>
+				<div>
+					<XUserTimeline :user="user"/>
+				</div>
+			</template>
+			<XFollowList v-else-if="page === 'following'" type="following" :user="user" class="_content _vMargin"/>
+			<XFollowList v-else-if="page === 'followers'" type="followers" :user="user" class="_content _vMargin"/>
+			<XClips v-else-if="page === 'clips'" :user="user" class="_vMargin"/>
+			<XPages v-else-if="page === 'pages'" :user="user" class="_vMargin"/>
+		</div>
 	</div>
 	<div v-else-if="error">
 		<MkError @retry="fetch()"/>
@@ -234,6 +236,7 @@ import { getUserMenu } from '@client/scripts/get-user-menu';
 import number from '../../filters/number';
 import { userPage, acct as getAcct } from '../../filters/user';
 import * as os from '@client/os';
+import { url } from '@client/config';
 
 export default defineComponent({
 	components: {
@@ -268,6 +271,10 @@ export default defineComponent({
 			INFO: computed(() => this.user ? {
 				userName: this.user,
 				avatar: this.user,
+				share: {
+					title: this.user.name,
+					url: `${url}/@${this.user.username}`
+				},
 				action: {
 					icon: faEllipsisH,
 					handler: this.menu
@@ -304,7 +311,7 @@ export default defineComponent({
 
 	mounted() {
 		window.requestAnimationFrame(this.parallaxLoop);
-		this.narrow = this.$el.clientWidth < 1000;
+		this.narrow = true; //this.$el.clientWidth < 1000;
 	},
 
 	beforeUnmount() {
@@ -415,7 +422,7 @@ export default defineComponent({
 					font-size: 80%;
 					padding: 8px 12px;
 					margin-bottom: 20px;
-					border: solid 1px var(--divider);
+					border: solid 0.5px var(--divider);
 					border-radius: 999px;
 				}
 			}
@@ -423,7 +430,7 @@ export default defineComponent({
 			> .status {
 				display: flex;
 				padding: 20px 16px;
-				border-top: solid 1px var(--divider);
+				border-top: solid 0.5px var(--divider);
 				font-size: 90%;
 
 				> a {
@@ -451,13 +458,13 @@ export default defineComponent({
 
 			> .description {
 				padding: 20px 16px;
-				border-top: solid 1px var(--divider);
+				border-top: solid 0.5px var(--divider);
 				font-size: 90%;
 			}
 
 			> .fields {
 				padding: 20px 16px;
-				border-top: solid 1px var(--divider);
+				border-top: solid 0.5px var(--divider);
 				font-size: 90%;
 
 				> .field {
@@ -540,9 +547,9 @@ export default defineComponent({
 }
 
 .ftskorzw.narrow {
-	max-width: 100vw;
 	box-sizing: border-box;
 	overflow: hidden;
+	overflow: clip;
 
 	> .punished {
 		font-size: 0.8em;
@@ -654,7 +661,7 @@ export default defineComponent({
 				text-align: center;
 				padding: 50px 8px 16px 8px;
 				font-weight: bold;
-				border-bottom: solid 1px var(--divider);
+				border-bottom: solid 0.5px var(--divider);
 
 				> .bottom {
 					> * {
@@ -689,7 +696,7 @@ export default defineComponent({
 			> .fields {
 				padding: 24px;
 				font-size: 0.9em;
-				border-top: solid 1px var(--divider);
+				border-top: solid 0.5px var(--divider);
 
 				> .field {
 					display: flex;
@@ -726,7 +733,7 @@ export default defineComponent({
 			> .status {
 				display: flex;
 				padding: 24px;
-				border-top: solid 1px var(--divider);
+				border-top: solid 0.5px var(--divider);
 
 				> a {
 					flex: 1;
@@ -753,41 +760,42 @@ export default defineComponent({
 		}
 	}
 
-	> .nav {
-		display: flex;
-		align-items: center;
-		margin-top: var(--margin);
-		//font-size: 120%;
-		font-weight: bold;
+	> .contents {
+		> .nav {
+			display: flex;
+			align-items: center;
+			//font-size: 120%;
+			font-weight: bold;
 
-		> .link {
-			flex: 1;
-			display: inline-block;
-			padding: 16px;
-			text-align: center;
-			border-bottom: solid 3px transparent;
+			> .link {
+				flex: 1;
+				display: inline-block;
+				padding: 16px;
+				text-align: center;
+				border-bottom: solid 3px transparent;
 
-			&:hover {
-				text-decoration: none;
-			}
+				&:hover {
+					text-decoration: none;
+				}
 
-			&.active {
-				color: var(--accent);
-				border-bottom-color: var(--accent);
-			}
+				&.active {
+					color: var(--accent);
+					border-bottom-color: var(--accent);
+				}
 
-			&:not(.active):hover {
-				color: var(--fgHighlighted);
-			}
+				&:not(.active):hover {
+					color: var(--fgHighlighted);
+				}
 
-			> .icon {
-				margin-right: 6px;
+				> .icon {
+					margin-right: 6px;
+				}
 			}
 		}
-	}
 
-	> .content {
-		margin-bottom: var(--margin);
+		> .content {
+			margin-bottom: var(--margin);
+		}
 	}
 
 	&.max-width_500px {
@@ -831,8 +839,10 @@ export default defineComponent({
 			}
 		}
 
-		> .nav {
-			font-size: 80%;
+		> .contents {
+			> .nav {
+				font-size: 80%;
+			}
 		}
 	}
 }
diff --git a/src/client/pages/welcome.entrance.a.vue b/src/client/pages/welcome.entrance.a.vue
index 01b3038312..7b02c44923 100644
--- a/src/client/pages/welcome.entrance.a.vue
+++ b/src/client/pages/welcome.entrance.a.vue
@@ -283,7 +283,7 @@ export default defineComponent({
 				}
 
 				> .status {
-					border-top: solid 1px var(--divider);
+					border-top: solid 0.5px var(--divider);
 					padding: 32px;
 					font-size: 90%;
 
@@ -291,7 +291,7 @@ export default defineComponent({
 						> span:not(:last-child) {
 							padding-right: 1em;
 							margin-right: 1em;
-							border-right: solid 1px var(--divider);
+							border-right: solid 0.5px var(--divider);
 						}
 					}
 
diff --git a/src/client/pages/welcome.entrance.c.vue b/src/client/pages/welcome.entrance.c.vue
index 4e4c0ed210..47ddf9e5ed 100644
--- a/src/client/pages/welcome.entrance.c.vue
+++ b/src/client/pages/welcome.entrance.c.vue
@@ -255,7 +255,7 @@ export default defineComponent({
 					}
 
 					> .status {
-						border-top: solid 1px var(--divider);
+						border-top: solid 0.5px var(--divider);
 						padding: 32px;
 						font-size: 90%;
 
@@ -263,7 +263,7 @@ export default defineComponent({
 							> span:not(:last-child) {
 								padding-right: 1em;
 								margin-right: 1em;
-								border-right: solid 1px var(--divider);
+								border-right: solid 0.5px var(--divider);
 							}
 						}
 
diff --git a/src/client/scripts/sticky-sidebar.ts b/src/client/scripts/sticky-sidebar.ts
index 9d46a7831f..18670bc037 100644
--- a/src/client/scripts/sticky-sidebar.ts
+++ b/src/client/scripts/sticky-sidebar.ts
@@ -1,40 +1,45 @@
 export class StickySidebar {
 	private lastScrollTop = 0;
+	private container: HTMLElement;
 	private el: HTMLElement;
 	private spacer: HTMLElement;
 	private marginTop: number;
 	private isTop = false;
 	private isBottom = false;
+	private offsetTop: number;
 
-	constructor(el: StickySidebar['el'], spacer: StickySidebar['spacer'], marginTop = 0) {
-		this.el = el;
-		this.spacer = spacer;
+	constructor(container: StickySidebar['container'], marginTop = 0) {
+		this.container = container;
+		this.el = this.container.children[0] as HTMLElement;
+		this.el.style.position = 'sticky';
+		this.spacer = document.createElement('div');
+		this.container.prepend(this.spacer);
 		this.marginTop = marginTop;
+		this.offsetTop = this.container.getBoundingClientRect().top;
 	}
 
 	public calc(scrollTop: number) {
 		if (scrollTop > this.lastScrollTop) { // downscroll
-			const overflow = this.el.clientHeight - window.innerHeight;
+			const overflow = Math.max(0, (this.el.clientHeight + this.marginTop) - window.innerHeight);
 			this.el.style.bottom = null;
-			this.el.style.top = `${-overflow}px`;
+			this.el.style.top = `${-overflow + this.marginTop}px`;
 
 			this.isBottom = (scrollTop + window.innerHeight) >= (this.el.offsetTop + this.el.clientHeight);
 
 			if (this.isTop) {
 				this.isTop = false;
-				this.spacer.style.marginTop = `${this.lastScrollTop}px`;
+				this.spacer.style.marginTop = `${Math.max(0, this.lastScrollTop + this.marginTop - this.offsetTop)}px`;
 			}
 		} else { // upscroll
-			const overflow = this.el.clientHeight - window.innerHeight;
+			const overflow = (this.el.clientHeight + this.marginTop) - window.innerHeight;
 			this.el.style.top = null;
-			this.el.style.bottom = `${-overflow - this.marginTop}px`;
+			this.el.style.bottom = `${-overflow}px`;
 
 			this.isTop = scrollTop <= this.el.offsetTop;
 
 			if (this.isBottom) {
 				this.isBottom = false;
-				const overflow = this.el.clientHeight - window.innerHeight;
-				this.spacer.style.marginTop = `${this.lastScrollTop - (overflow + this.marginTop)}px`;
+				this.spacer.style.marginTop = `${this.lastScrollTop + this.marginTop - this.offsetTop - overflow}px`;
 			}
 		}
 
diff --git a/src/client/scripts/theme.ts b/src/client/scripts/theme.ts
index c1580c6367..804d808683 100644
--- a/src/client/scripts/theme.ts
+++ b/src/client/scripts/theme.ts
@@ -17,6 +17,7 @@ export const themeProps = Object.keys(lightTheme.props).filter(key => !key.start
 export const builtinThemes = [
 	require('../themes/l-light.json5'),
 	require('../themes/l-apricot.json5'),
+	require('../themes/l-rainy.json5'),
 
 	require('../themes/d-dark.json5'),
 	require('../themes/d-persimmon.json5'),
diff --git a/src/client/sidebar.ts b/src/client/sidebar.ts
index 97036042cf..09b69fd815 100644
--- a/src/client/sidebar.ts
+++ b/src/client/sidebar.ts
@@ -142,6 +142,12 @@ export const sidebarDef = {
 					localStorage.setItem('ui', 'deck');
 					unisonReload();
 				}
+			}, {
+				text: 'pope',
+				action: () => {
+					localStorage.setItem('ui', 'pope');
+					unisonReload();
+				}
 			}, {
 				text: 'Chat (β)',
 				action: () => {
diff --git a/src/client/style.scss b/src/client/style.scss
index b6a83d967e..f2a3682e47 100644
--- a/src/client/style.scss
+++ b/src/client/style.scss
@@ -242,11 +242,21 @@ hr {
 	//border: var(--panelBorder);
 	box-shadow: var(--panelShadow);
 	overflow: hidden;
+	overflow: clip;
+}
+
+._block {
+	@extend ._panel;
+}
+
+._isolated {
+	margin: var(--margin) 0;
 }
 
 ._card {
 	@extend ._panel;
 
+	// TODO: _cardTitle に
 	> ._title {
 		margin: 0;
 		padding: 22px 32px;
@@ -262,6 +272,7 @@ hr {
 		}
 	}
 
+	// TODO: _cardContent に
 	> ._content {
 		padding: 32px;
 
@@ -274,12 +285,13 @@ hr {
 		}
 
 		& + ._content {
-			border-top: solid 1px var(--divider);
+			border-top: solid 0.5px var(--divider);
 		}
 	}
 
+	// TODO: _cardFooter に
 	> ._footer {
-		border-top: solid 1px var(--divider);
+		border-top: solid 0.5px var(--divider);
 		padding: 24px 32px;
 
 		@media (max-width: 500px) {
@@ -288,26 +300,6 @@ hr {
 	}
 }
 
-._noGap_ ._list_ {
-	@extend ._panel;
-
-	> * {
-		margin: 0 !important;
-		border: none;
-		border-bottom: solid 1px var(--divider);
-		border-radius: 0;
-		box-shadow: none;
-	}
-}
-
-._inContainer_ ._list_ > * {
-	margin: 0 !important;
-	border: none;
-	border-bottom: solid 1px var(--divider);
-	border-radius: 0;
-	box-shadow: none;
-}
-
 ._borderButton {
 	@extend ._button;
 	display: block;
@@ -315,7 +307,7 @@ hr {
 	padding: 10px;
 	box-sizing: border-box;
 	text-align: center;
-	border: solid 1px var(--divider);
+	border: solid 0.5px var(--divider);
 	border-radius: var(--radius);
 
 	&:active {
@@ -329,47 +321,36 @@ hr {
 	contain: content;
 }
 
-._section {
-	padding: var(--section-padding, 32px);
+._root {
+	box-sizing: border-box;
+	margin: var(--root-margin, 32px) auto;
+	max-width: min(var(--baseContentWidth), calc(100% - (var(--root-margin, 32px) * 2)));
 
-	&:empty {
-		display: none;
-	}
-
-	&:not(:empty) + ._section {
-		border-top: solid 1px var(--divider);
+	._hr {
+		margin: var(--margin) calc(var(--root-margin, 32px) * -1);
 	}
 
 	@media (max-width: 500px) {
-		padding: var(--section-padding, 10px);
-
-		> ._title {
-			font-size: 1.1em;
-			font-weight: bold;
-		}
-	}
-
-	> ._title,
-	> ._content {
-		box-sizing: border-box;
-		max-width: var(--baseContentWidth);
-		margin: 0 auto;
-	}
-
-	> ._title {
-		margin-bottom: 24px;
-		font-weight: bold;
-	}
-
-	&._fitBottom {
-		padding-bottom: 0;
+		--root-margin: 10px;
 	}
 }
 
-._narrow_ ._section {
-	> ._title {
-		padding: 8px;
-		font-size: 1em;
+._flat_ {
+	--root-margin: 0;
+	--baseContentWidth: 100%;
+	--panelShadow: none;
+
+	._block {
+		//border-top: solid 0.5px var(--divider);
+		//border-bottom: solid 0.5px var(--divider);
+		border-radius: 0;
+		box-shadow: none;
+	}
+
+	@media (max-width: 500px) {
+		._root {
+			--root-margin: 0;
+		}
 	}
 }
 
diff --git a/src/client/themes/_dark.json5 b/src/client/themes/_dark.json5
index 2fa4853e6f..6414a7ad42 100644
--- a/src/client/themes/_dark.json5
+++ b/src/client/themes/_dark.json5
@@ -30,7 +30,7 @@
 		panelShadow: '" 0 8px 24px rgba(0, 0, 0, 0.12)',
 		acrylicPanel: ':alpha<0.5<@panel',
 		shadow: 'rgba(0, 0, 0, 0.3)',
-		header: ':alpha<0.7<@bg',
+		header: ':alpha<0.7<@panel',
 		navBg: '@panel',
 		navFg: '@fg',
 		navHoverFg: ':lighten<17<@fg',
diff --git a/src/client/themes/_light.json5 b/src/client/themes/_light.json5
index 94e6977502..0438b54a49 100644
--- a/src/client/themes/_light.json5
+++ b/src/client/themes/_light.json5
@@ -30,7 +30,7 @@
 		panelShadow: '" 0 8px 24px rgb(21 43 75 / 8%)',
 		acrylicPanel: ':alpha<0.5<@panel',
 		shadow: 'rgba(0, 0, 0, 0.1)',
-		header: ':alpha<0.7<@bg',
+		header: ':alpha<0.7<@panel',
 		navBg: '@panel',
 		navFg: '@fg',
 		navHoverFg: ':darken<17<@fg',
diff --git a/src/client/themes/d-dark.json5 b/src/client/themes/d-dark.json5
index 7dd29b4a0f..337eaa6396 100644
--- a/src/client/themes/d-dark.json5
+++ b/src/client/themes/d-dark.json5
@@ -18,7 +18,7 @@
 		panelHeaderDivider: '@divider',
 		infoFg: '@accent',
 		infoBg: 'rgb(0, 0, 0)',
-		header: ':alpha<0.7<@bg',
+		header: ':alpha<0.7<@panel',
 		navBg: '#363636',
 		renote: '@accent',
 		mention: '#da6d35',
diff --git a/src/client/themes/d-persimmon.json5 b/src/client/themes/d-persimmon.json5
index 862ccc6cea..a1ebaf59eb 100644
--- a/src/client/themes/d-persimmon.json5
+++ b/src/client/themes/d-persimmon.json5
@@ -16,7 +16,6 @@
 		panelShadow: '" 0 8px 24px rgb(0 0 0 / 25%)',
 		infoFg: '@fg',
 		infoBg: '#333c3b',
-		header: ':alpha<0.7<@bg',
 		navBg: '#141714',
 		renote: '@accent',
 		mention: '@accent',
diff --git a/src/client/themes/l-light.json5 b/src/client/themes/l-light.json5
index 34be20fae0..fdc1700b95 100644
--- a/src/client/themes/l-light.json5
+++ b/src/client/themes/l-light.json5
@@ -9,9 +9,9 @@
 
 	props: {
 		bg: '#f9f9f9',
-		fg: '#636b71',
+		fg: '#676767',
 		divider: 'rgb(223, 223, 223)',
-		header: ':alpha<0.7<@bg',
+		header: ':alpha<0.7<@panel',
 		navBg: '#fff',
 		panel: '#fff',
 		panelShadow: '" 0 8px 24px rgb(21 43 75 / 8%)',
diff --git a/src/client/themes/l-rainy.json5 b/src/client/themes/l-rainy.json5
new file mode 100644
index 0000000000..1edde1cabf
--- /dev/null
+++ b/src/client/themes/l-rainy.json5
@@ -0,0 +1,21 @@
+{
+	id: 'a58a0abb-ff8c-476a-8dec-0ad7837e7e96',
+
+	name: 'Mi Rainy',
+	author: 'syuilo',
+
+	base: 'light',
+
+	props: {
+		accent: '#5db0da',
+		bg: 'rgb(246 248 249)',
+		fg: '#636b71',
+		panel: '#fff',
+		divider: 'rgb(230 233 234)',
+		panelHeaderDivider: '@divider',
+		renote: '@accent',
+		link: '@accent',
+		mention: '@accent',
+		hashtag: '@accent',
+	},
+}
diff --git a/src/client/ui/_common_/header.vue b/src/client/ui/_common_/header.vue
index f150653a84..4c914edbbd 100644
--- a/src/client/ui/_common_/header.vue
+++ b/src/client/ui/_common_/header.vue
@@ -12,14 +12,16 @@
 				<MkUserName v-else-if="info.userName" :user="info.userName" :nowrap="false" class="text"/>
 			</div>
 		</div>
-		<button class="_button action" v-if="info.action" @click.stop="info.action.handler"><Fa :icon="info.action.icon" :key="info.action.icon"/></button>
+		<button class="_button menu" @click.stop="menu"><Fa :icon="faEllipsisH"/></button>
+		<!--<button class="_button action" v-if="info.action" @click.stop="info.action.handler"><Fa :icon="info.action.icon" :key="info.action.icon"/></button>-->
 	</template>
 </div>
 </template>
 
 <script lang="ts">
 import { defineComponent } from 'vue';
-import { faChevronLeft, faCircle } from '@fortawesome/free-solid-svg-icons';
+import { faChevronLeft, faCircle, faShareAlt, faEllipsisH } from '@fortawesome/free-solid-svg-icons';
+import { modalMenu } from '@client/os';
 
 export default defineComponent({
 	props: {
@@ -42,7 +44,7 @@ export default defineComponent({
 		return {
 			canBack: false,
 			height: 0,
-			faChevronLeft, faCircle
+			faChevronLeft, faCircle, faShareAlt, faEllipsisH,
 		};
 	},
 
@@ -66,6 +68,23 @@ export default defineComponent({
 		back() {
 			if (this.canBack) this.$router.back();
 		},
+
+		share() {
+			navigator.share(this.info.share);
+		},
+
+		menu(ev) {
+			const menu = this.info.menu ? this.info.menu() : [];
+			if (this.info.share) {
+				if (menu.length > 0) menu.push(null);
+				menu.push({
+					text: this.$ts.share,
+					icon: faShareAlt,
+					action: this.share
+				});
+			}
+			modalMenu(menu, ev.currentTarget || ev.target);
+		}
 	}
 });
 </script>
@@ -74,59 +93,33 @@ export default defineComponent({
 .fdidabkb {
 	&.center {
 		text-align: center;
-	}
 
-	> .back {
-		height: var(--height);
-		width: var(--height);
-	}
-
-	> .action {
-		height: var(--height);
-		width: var(--height);
-	}
-
-	> .titleContainer {
-		width: calc(100% - (var(--height) * 2));
-
-		> .title {
-			height: var(--height);
-
-			> .avatar {
-				$size: 32px;
-				margin: calc((var(--height) - #{$size}) / 2) 8px calc((var(--height) - #{$size}) / 2) 0;
-				pointer-events: none;
-			}
-		}
-	}
-}
-</style>
-
-<style lang="scss" scoped>
-.fdidabkb {
-	> .back {
-		position: absolute;
-		z-index: 1;
-		top: 0;
-		left: 0;
-	}
-
-	> .action {
-		position: absolute;
-		z-index: 1;
-		top: 0;
-		right: 0;
-	}
-
-	&.center {
 		> .titleContainer {
 			margin: 0 auto;
 		}
 	}
 
+	> .back,
+	> .menu {
+		position: absolute;
+		z-index: 1;
+		top: 0;
+		height: var(--height);
+		width: var(--height);
+	}
+
+	> .back {
+		left: 0;
+	}
+
+	> .menu {
+		right: 0;
+	}
+
 	> .titleContainer {
 		overflow: auto;
 		white-space: nowrap;
+		width: calc(100% - (var(--height) * 2));
 
 		> .title {
 			display: inline-block;
@@ -136,16 +129,7 @@ export default defineComponent({
 			text-overflow: ellipsis;
 			padding: 0 16px;
 			position: relative;
-
-			> .indicator {
-				position: absolute;
-				top: initial;
-				right: 8px;
-				top: 8px;
-				color: var(--indicator);
-				font-size: 12px;
-				animation: blink 1s infinite;
-			}
+			height: var(--height);
 
 			> .icon + .text {
 				margin-left: 8px;
@@ -157,6 +141,8 @@ export default defineComponent({
 				width: $size;
 				height: $size;
 				vertical-align: bottom;
+				margin: calc((var(--height) - #{$size}) / 2) 8px calc((var(--height) - #{$size}) / 2) 0;
+				pointer-events: none;
 			}
 		}
 	}
diff --git a/src/client/components/sidebar.vue b/src/client/ui/_common_/sidebar.vue
similarity index 96%
rename from src/client/components/sidebar.vue
rename to src/client/ui/_common_/sidebar.vue
index 61439781b4..6243d6fcc2 100644
--- a/src/client/components/sidebar.vue
+++ b/src/client/ui/_common_/sidebar.vue
@@ -235,12 +235,12 @@ export default defineComponent({
 		},
 
 		more(ev) {
-			os.popup(import('./launch-pad.vue'), {}, {
+			os.popup(import('@client/components/launch-pad.vue'), {}, {
 			}, 'closed');
 		},
 
 		addAcount() {
-			os.popup(import('./signin-dialog.vue'), {}, {
+			os.popup(import('@client/components/signin-dialog.vue'), {}, {
 				done: res => {
 					addAccount(res.id, res.i);
 					os.success();
@@ -249,7 +249,7 @@ export default defineComponent({
 		},
 
 		createAccount() {
-			os.popup(import('./signup-dialog.vue'), {}, {
+			os.popup(import('@client/components/signup-dialog.vue'), {}, {
 				done: res => {
 					addAccount(res.id, res.i);
 					this.switchAccountWithToken(res.i);
@@ -380,7 +380,7 @@ export default defineComponent({
 
 			> .divider {
 				margin: 16px 0;
-				border-top: solid 1px var(--divider);
+				border-top: solid 0.5px var(--divider);
 			}
 
 			> .item {
@@ -443,13 +443,13 @@ export default defineComponent({
 				&:first-child {
 					top: 0;
 					margin-bottom: 16px;
-					border-bottom: solid 1px var(--divider);
+					border-bottom: solid 0.5px var(--divider);
 				}
 
 				&:last-child {
 					bottom: 0;
 					margin-top: 16px;
-					border-top: solid 1px var(--divider);
+					border-top: solid 0.5px var(--divider);
 				}
 			}
 		}
diff --git a/src/client/ui/chat/index.vue b/src/client/ui/chat/index.vue
index 91d3fb5c9d..d5c455d123 100644
--- a/src/client/ui/chat/index.vue
+++ b/src/client/ui/chat/index.vue
@@ -136,7 +136,7 @@ import { defineComponent, defineAsyncComponent } from 'vue';
 import { faLayerGroup, faBars, faHome, faCircle, faWindowMaximize, faColumns, faPencilAlt, faShareAlt, faSatelliteDish, faListUl, faSatellite, faCog, faSearch, faPlus, faStar, faAt, faLink, faEllipsisH, faGlobe } from '@fortawesome/free-solid-svg-icons';
 import { faBell, faStar as farStar, faEnvelope, faComments, faCalendarAlt } from '@fortawesome/free-regular-svg-icons';
 import { instanceName, url } from '@client/config';
-import XSidebar from '@client/components/sidebar.vue';
+import XSidebar from '@client/ui/_common_/sidebar.vue';
 import XWidgets from './widgets.vue';
 import XCommon from '../_common_/common.vue';
 import XSide from './side.vue';
@@ -354,7 +354,7 @@ export default defineComponent({
 		flex-direction: column;
 		width: 250px;
 		height: 100vh;
-		border-right: solid 1px var(--divider);
+		border-right: solid 0.5px var(--divider);
 
 		> .header, > .footer {
 			$padding: 8px;
@@ -367,11 +367,11 @@ export default defineComponent({
 			user-select: none;
 
 			&.header {
-				border-bottom: solid 1px var(--divider);
+				border-bottom: solid 0.5px var(--divider);
 			}
 
 			&.footer {
-				border-top: solid 1px var(--divider);
+				border-top: solid 0.5px var(--divider);
 			}
 
 			> .left, > .right {
@@ -526,7 +526,7 @@ export default defineComponent({
 			padding: $padding;
 			box-sizing: border-box;
 			background-color: var(--panel);
-			border-bottom: solid 1px var(--divider);
+			border-bottom: solid 0.5px var(--divider);
 			user-select: none;
 
 			> .left {
@@ -599,7 +599,7 @@ export default defineComponent({
 
 	> .side {
 		width: 350px;
-		border-left: solid 1px var(--divider);
+		border-left: solid 0.5px var(--divider);
 
 		&.widgets.sideViewOpening {
 			@media (max-width: 1400px) {
diff --git a/src/client/ui/chat/note-header.vue b/src/client/ui/chat/note-header.vue
index 55228c4c38..be08183d39 100644
--- a/src/client/ui/chat/note-header.vue
+++ b/src/client/ui/chat/note-header.vue
@@ -79,7 +79,7 @@ export default defineComponent({
 		margin: 0 .5em 0 0;
 		padding: 1px 6px;
 		font-size: 80%;
-		border: solid 1px var(--divider);
+		border: solid 0.5px var(--divider);
 		border-radius: 3px;
 	}
 
diff --git a/src/client/ui/chat/note.sub.vue b/src/client/ui/chat/note.sub.vue
index 6c778d1468..bb528dd936 100644
--- a/src/client/ui/chat/note.sub.vue
+++ b/src/client/ui/chat/note.sub.vue
@@ -130,7 +130,7 @@ export default defineComponent({
 	}
 
 	> .reply {
-		border-left: solid 1px var(--divider);
+		border-left: solid 0.5px var(--divider);
 		margin-top: 10px;
 	}
 }
diff --git a/src/client/ui/chat/note.vue b/src/client/ui/chat/note.vue
index f6789f214d..3764bbb65a 100644
--- a/src/client/ui/chat/note.vue
+++ b/src/client/ui/chat/note.vue
@@ -1127,7 +1127,7 @@ export default defineComponent({
 	}
 
 	> .reply {
-		border-top: solid 1px var(--divider);
+		border-top: solid 0.5px var(--divider);
 	}
 }
 
diff --git a/src/client/ui/chat/post-form.vue b/src/client/ui/chat/post-form.vue
index 9dd87edac4..bdf18cf290 100644
--- a/src/client/ui/chat/post-form.vue
+++ b/src/client/ui/chat/post-form.vue
@@ -615,7 +615,7 @@ export default defineComponent({
 <style lang="scss" scoped>
 .pxiwixjf {
 	position: relative;
-	border: solid 1px var(--divider);
+	border: solid 0.5px var(--divider);
 	border-radius: 8px;
 
 	> .form {
@@ -696,7 +696,7 @@ export default defineComponent({
 		> .cw {
 			z-index: 1;
 			padding-bottom: 8px;
-			border-bottom: solid 1px var(--divider);
+			border-bottom: solid 0.5px var(--divider);
 		}
 
 		> .text {
diff --git a/src/client/ui/chat/side.vue b/src/client/ui/chat/side.vue
index 2645874ce4..a3c03b6d06 100644
--- a/src/client/ui/chat/side.vue
+++ b/src/client/ui/chat/side.vue
@@ -117,7 +117,7 @@ export default defineComponent({
 .mrajymqm {
 	$header-height: 54px; // TODO: どこかに集約したい
 
-	--section-padding: 16px;
+	--root-margin: 16px;
 	--margin: var(--marginHalf);
 
 	height: 100%;
@@ -137,7 +137,7 @@ export default defineComponent({
 		-webkit-backdrop-filter: blur(32px);
 		backdrop-filter: blur(32px);
 		background-color: var(--header);
-		border-bottom: solid 1px var(--divider);
+		border-bottom: solid 0.5px var(--divider);
 		box-sizing: border-box;
 
 		> ._button {
diff --git a/src/client/ui/deck.vue b/src/client/ui/deck.vue
index a63db17b01..0429dbc9b1 100644
--- a/src/client/ui/deck.vue
+++ b/src/client/ui/deck.vue
@@ -36,7 +36,7 @@ import {  } from '@fortawesome/free-regular-svg-icons';
 import { v4 as uuid } from 'uuid';
 import { host } from '@client/config';
 import DeckColumnCore from '@client/ui/deck/column-core.vue';
-import XSidebar from '@client/components/sidebar.vue';
+import XSidebar from '@client/ui/_common_/sidebar.vue';
 import { getScrollContainer } from '@client/scripts/scroll';
 import * as os from '@client/os';
 import { sidebarDef } from '@client/sidebar';
diff --git a/src/client/ui/deck/column.vue b/src/client/ui/deck/column.vue
index 6a242c691a..3fae7c27ee 100644
--- a/src/client/ui/deck/column.vue
+++ b/src/client/ui/deck/column.vue
@@ -265,7 +265,7 @@ export default defineComponent({
 
 <style lang="scss" scoped>
 .dnpfarvg {
-	--section-padding: 10px;
+	--root-margin: 10px;
 
 	height: 100%;
 	overflow: hidden;
diff --git a/src/client/ui/deck/main-column.vue b/src/client/ui/deck/main-column.vue
index 4577b0b533..5a8c72d871 100644
--- a/src/client/ui/deck/main-column.vue
+++ b/src/client/ui/deck/main-column.vue
@@ -4,7 +4,7 @@
 		<XHeader :info="pageInfo"/>
 	</template>
 
-	<router-view v-slot="{ Component }">
+	<router-view v-slot="{ Component }" class="_flat_">
 		<transition>
 			<keep-alive :include="['timeline']">
 				<component :is="Component" :ref="changePage" @contextmenu.stop="onContextmenu"/>
diff --git a/src/client/ui/default.side.vue b/src/client/ui/default.side.vue
index 995f987a6a..3a32cb4e13 100644
--- a/src/client/ui/default.side.vue
+++ b/src/client/ui/default.side.vue
@@ -118,7 +118,7 @@ export default defineComponent({
 .qvzfzxam {
 	$header-height: 58px; // TODO: どこかに集約したい
 
-	--section-padding: 16px;
+	--root-margin: 16px;
 	--margin: var(--marginHalf);
 
 	> .container {
diff --git a/src/client/ui/default.sidebar.vue b/src/client/ui/default.sidebar.vue
new file mode 100644
index 0000000000..710a9b1f85
--- /dev/null
+++ b/src/client/ui/default.sidebar.vue
@@ -0,0 +1,362 @@
+<template>
+<div class="npcljfve" :class="{ iconOnly }">
+	<button class="item _button account" @click="openAccountMenu">
+		<MkAvatar :user="$i" class="avatar"/><MkAcct class="text" :user="$i"/>
+	</button>
+	<div class="post" @click="post">
+		<MkButton class="button" primary full>
+			<Fa :icon="faPencilAlt" fixed-width/><span class="text" v-if="!iconOnly">{{ $ts.note }}</span>
+		</MkButton>
+	</div>
+	<div class="divider"></div>
+	<MkA class="item index" active-class="active" to="/" exact>
+		<Fa :icon="faHome" fixed-width/><span class="text">{{ $ts.timeline }}</span>
+	</MkA>
+	<template v-for="item in menu">
+		<div v-if="item === '-'" class="divider"></div>
+		<component v-else-if="menuDef[item] && (menuDef[item].show !== false)" :is="menuDef[item].to ? 'MkA' : 'button'" class="item _button" :class="item" active-class="active" v-on="menuDef[item].action ? { click: menuDef[item].action } : {}" :to="menuDef[item].to">
+			<Fa :icon="menuDef[item].icon" fixed-width/><span class="text">{{ $ts[menuDef[item].title] }}</span>
+			<i v-if="menuDef[item].indicated"><Fa :icon="faCircle"/></i>
+		</component>
+	</template>
+	<div class="divider"></div>
+	<button class="item _button" :class="{ active: $route.path === '/instance' || $route.path.startsWith('/instance/') }" v-if="$i.isAdmin || $i.isModerator" @click="oepnInstanceMenu">
+		<Fa :icon="faServer" fixed-width/><span class="text">{{ $ts.instance }}</span>
+	</button>
+	<button class="item _button" @click="more">
+		<Fa :icon="faEllipsisH" fixed-width/><span class="text">{{ $ts.more }}</span>
+		<i v-if="otherNavItemIndicated"><Fa :icon="faCircle"/></i>
+	</button>
+	<MkA class="item" active-class="active" to="/settings">
+		<Fa :icon="faCog" fixed-width/><span class="text">{{ $ts.settings }}</span>
+	</MkA>
+</div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+import { faGripVertical, faChevronLeft, faHashtag, faBroadcastTower, faFireAlt, faEllipsisH, faPencilAlt, faBars, faTimes, faSearch, faUserCog, faCog, faUser, faHome, faStar, faCircle, faAt, faListUl, faPlus, faUserClock, faUsers, faTachometerAlt, faExchangeAlt, faGlobe, faChartBar, faCloud, faServer, faInfoCircle, faQuestionCircle, faProjectDiagram, faStream, faExclamationCircle } from '@fortawesome/free-solid-svg-icons';
+import { faBell, faEnvelope, faLaugh, faComments } from '@fortawesome/free-regular-svg-icons';
+import { host } from '@client/config';
+import { search } from '@client/scripts/search';
+import * as os from '@client/os';
+import { sidebarDef } from '@client/sidebar';
+import { getAccounts, addAccount, login } from '@client/account';
+import MkButton from '@client/components/ui/button.vue';
+import { StickySidebar } from '@client/scripts/sticky-sidebar';
+
+export default defineComponent({
+	components: {
+		MkButton
+	},
+
+	data() {
+		return {
+			host: host,
+			accounts: [],
+			connection: null,
+			menuDef: sidebarDef,
+			iconOnly: false,
+			faGripVertical, faChevronLeft, faComments, faHashtag, faBroadcastTower, faFireAlt, faEllipsisH, faPencilAlt, faBars, faTimes, faBell, faSearch, faUserCog, faCog, faUser, faHome, faStar, faCircle, faAt, faEnvelope, faListUl, faPlus, faUserClock, faLaugh, faUsers, faTachometerAlt, faExchangeAlt, faGlobe, faChartBar, faCloud, faServer, faProjectDiagram
+		};
+	},
+
+	computed: {
+		menu(): string[] {
+			return this.$store.state.menu;
+		},
+
+		otherNavItemIndicated(): boolean {
+			for (const def in this.menuDef) {
+				if (this.menu.includes(def)) continue;
+				if (this.menuDef[def].indicated) return true;
+			}
+			return false;
+		},
+	},
+
+	watch: {
+		'$store.reactiveState.sidebarDisplay.value'() {
+			this.calcViewState();
+		},
+
+		iconOnly() {
+			this.$nextTick(() => {
+				this.$emit('change-view-mode');
+			});
+		},
+	},
+
+	created() {
+		window.addEventListener('resize', this.calcViewState);
+		this.calcViewState();
+	},
+
+	mounted() {
+		const sticky = new StickySidebar(this.$el.parentElement, 16);
+		window.addEventListener('scroll', () => {
+			sticky.calc(window.scrollY);
+		}, { passive: true });
+	},
+
+	methods: {
+		calcViewState() {
+			this.iconOnly = (window.innerWidth <= 1400) || (this.$store.state.sidebarDisplay === 'icon');
+		},
+
+		post() {
+			os.post();
+		},
+
+		search() {
+			search();
+		},
+
+		async openAccountMenu(ev) {
+			const storedAccounts = getAccounts().filter(x => x.id !== this.$i.id);
+			const accountsPromise = os.api('users/show', { userIds: storedAccounts.map(x => x.id) });
+
+			const accountItemPromises = storedAccounts.map(a => new Promise(res => {
+				accountsPromise.then(accounts => {
+					const account = accounts.find(x => x.id === a.id);
+					if (account == null) return res(null);
+					res({
+						type: 'user',
+						user: account,
+						action: () => { this.switchAccount(account); }
+					});
+				});
+			}));
+
+			os.modalMenu([...[{
+				type: 'link',
+				text: this.$ts.profile,
+				to: `/@${ this.$i.username }`,
+				avatar: this.$i,
+			}, null, ...accountItemPromises, {
+				icon: faPlus,
+				text: this.$ts.addAcount,
+				action: () => {
+					os.modalMenu([{
+						text: this.$ts.existingAcount,
+						action: () => { this.addAcount(); },
+					}, {
+						text: this.$ts.createAccount,
+						action: () => { this.createAccount(); },
+					}], ev.currentTarget || ev.target);
+				},
+			}]], ev.currentTarget || ev.target, {
+				align: 'left'
+			});
+		},
+
+		oepnInstanceMenu(ev) {
+			os.modalMenu([{
+				type: 'link',
+				text: this.$ts.dashboard,
+				to: '/instance',
+				icon: faTachometerAlt,
+			}, null, this.$i.isAdmin ? {
+				type: 'link',
+				text: this.$ts.settings,
+				to: '/instance/settings',
+				icon: faCog,
+			} : undefined, {
+				type: 'link',
+				text: this.$ts.customEmojis,
+				to: '/instance/emojis',
+				icon: faLaugh,
+			}, {
+				type: 'link',
+				text: this.$ts.users,
+				to: '/instance/users',
+				icon: faUsers,
+			}, {
+				type: 'link',
+				text: this.$ts.files,
+				to: '/instance/files',
+				icon: faCloud,
+			}, {
+				type: 'link',
+				text: this.$ts.jobQueue,
+				to: '/instance/queue',
+				icon: faExchangeAlt,
+			}, {
+				type: 'link',
+				text: this.$ts.federation,
+				to: '/instance/federation',
+				icon: faGlobe,
+			}, {
+				type: 'link',
+				text: this.$ts.relays,
+				to: '/instance/relays',
+				icon: faProjectDiagram,
+			}, {
+				type: 'link',
+				text: this.$ts.announcements,
+				to: '/instance/announcements',
+				icon: faBroadcastTower,
+			}, {
+				type: 'link',
+				text: this.$ts.abuseReports,
+				to: '/instance/abuses',
+				icon: faExclamationCircle,
+			}, {
+				type: 'link',
+				text: this.$ts.logs,
+				to: '/instance/logs',
+				icon: faStream,
+			}], ev.currentTarget || ev.target);
+		},
+
+		more(ev) {
+			os.popup(import('@client/components/launch-pad.vue'), {}, {
+			}, 'closed');
+		},
+
+		addAcount() {
+			os.popup(import('@client/components/signin-dialog.vue'), {}, {
+				done: res => {
+					addAccount(res.id, res.i);
+					os.success();
+				},
+			}, 'closed');
+		},
+
+		createAccount() {
+			os.popup(import('@client/components/signup-dialog.vue'), {}, {
+				done: res => {
+					addAccount(res.id, res.i);
+					this.switchAccountWithToken(res.i);
+				},
+			}, 'closed');
+		},
+
+		switchAccount(account: any) {
+			const storedAccounts = getAccounts();
+			const token = storedAccounts.find(x => x.id === account.id).token;
+			this.switchAccountWithToken(token);
+		},
+
+		switchAccountWithToken(token: string) {
+			login(token);
+		},
+	}
+});
+</script>
+
+<style lang="scss" scoped>
+.npcljfve {
+	$ui-font-size: 1em; // TODO: どこかに集約したい
+	$nav-icon-only-width: 78px; // TODO: どこかに集約したい
+	$avatar-size: 32px;
+	$avatar-margin: 8px;
+
+	padding: 0 16px;
+	box-sizing: border-box;
+	width: 260px;
+
+	&.iconOnly {
+		flex: 0 0 $nav-icon-only-width;
+		width: $nav-icon-only-width !important;
+
+		> .divider {
+			margin: 8px auto;
+			width: calc(100% - 32px);
+		}
+
+		> .post {
+			> .button {
+				width: 46px;
+				height: 46px;
+				padding: 0;
+			}
+		}
+
+		> .item {
+			padding-left: 0;
+			width: 100%;
+			text-align: center;
+			font-size: $ui-font-size * 1.1;
+			line-height: 3.7rem;
+
+			> [data-icon],
+			> .avatar {
+				margin-right: 0;
+			}
+
+			> i {
+				left: 10px;
+			}
+
+			> .text {
+				display: none;
+			}
+
+		}
+	}
+
+	> .divider {
+		margin: 10px 0;
+		border-top: solid 0.5px var(--divider);
+	}
+
+	> .post {
+		position: sticky;
+		top: 0;
+		z-index: 1;
+		padding: 16px 0;
+		background: var(--bg);
+
+		> .button {
+			min-width: 0;
+		}
+	}
+
+	> .item {
+		position: relative;
+		display: block;
+		font-size: $ui-font-size;
+		line-height: 2.6rem;
+		text-overflow: ellipsis;
+		overflow: hidden;
+		white-space: nowrap;
+		width: 100%;
+		text-align: left;
+		box-sizing: border-box;
+
+		> [data-icon] {
+			width: 32px;
+		}
+
+		> [data-icon],
+		> .avatar {
+			margin-right: $avatar-margin;
+		}
+
+		> .avatar {
+			width: $avatar-size;
+			height: $avatar-size;
+			vertical-align: middle;
+		}
+
+		> i {
+			position: absolute;
+			top: 0;
+			left: 20px;
+			color: var(--navIndicator);
+			font-size: 8px;
+			animation: blink 1s infinite;
+		}
+
+		&:hover {
+			text-decoration: none;
+			color: var(--navHoverFg);
+		}
+
+		&.active {
+			color: var(--navActive);
+		}
+	}
+}
+</style>
diff --git a/src/client/ui/default.vue b/src/client/ui/default.vue
index 38f98f6365..84d6c01094 100644
--- a/src/client/ui/default.vue
+++ b/src/client/ui/default.vue
@@ -1,13 +1,15 @@
 <template>
-<div class="mk-app" :class="{ wallpaper }">
-	<XSidebar ref="nav" class="sidebar"/>
+<div class="mk-app" :class="{ wallpaper, isMobile }">
+	<div class="columns">
+		<div class="sidebar" ref="sidebar" v-if="!isMobile">
+			<XSidebar/>
+		</div>
 
-	<div class="contents" ref="contents" :class="{ withHeader: $store.state.titlebar }" @contextmenu.stop="onContextmenu">
-		<header v-if="$store.state.titlebar" class="header" ref="header" @click="onHeaderClick">
-			<XHeader :info="pageInfo"/>
-		</header>
-		<main ref="main">
-			<div class="content">
+		<main class="main _panel" @contextmenu.stop="onContextmenu">
+			<header v-if="$store.state.titlebar" class="header" @click="onHeaderClick">
+				<XHeader :info="pageInfo"/>
+			</header>
+			<div class="content _flat_">
 				<router-view v-slot="{ Component }">
 					<transition :name="$store.state.animation ? 'page' : ''" mode="out-in" @enter="onTransition">
 						<keep-alive :include="['timeline']">
@@ -16,26 +18,22 @@
 					</transition>
 				</router-view>
 			</div>
-			<div class="spacer"></div>
 		</main>
+
+		<div v-if="isDesktop" class="widgets" ref="widgets">
+			<XWidgets @mounted="attachSticky"/>
+		</div>
 	</div>
 
-	<XSide v-if="isDesktop" class="side" ref="side"/>
-
-	<div v-if="isDesktop" class="widgets">
-		<div ref="widgetsSpacer"></div>
-		<XWidgets @mounted="attachSticky"/>
-	</div>
-
-	<div class="buttons" :class="{ navHidden }">
-		<button class="button nav _button" @click="showNav" ref="navButton"><Fa :icon="faBars"/><i v-if="navIndicated"><Fa :icon="faCircle"/></i></button>
+	<div class="buttons" v-if="isMobile">
+		<button class="button nav _button" @click="showDrawerNav" ref="navButton"><Fa :icon="faBars"/><i v-if="navIndicated"><Fa :icon="faCircle"/></i></button>
 		<button class="button home _button" @click="$route.name === 'index' ? top() : $router.push('/')"><Fa :icon="faHome"/></button>
 		<button class="button notifications _button" @click="$router.push('/my/notifications')"><Fa :icon="faBell"/><i v-if="$i.hasUnreadNotification"><Fa :icon="faCircle"/></i></button>
 		<button class="button widget _button" @click="widgetsShowing = true"><Fa :icon="faLayerGroup"/></button>
 		<button class="button post _button" @click="post"><Fa :icon="faPencilAlt"/></button>
 	</div>
 
-	<button class="widgetButton _button" :class="{ navHidden }" @click="widgetsShowing = true"><Fa :icon="faLayerGroup"/></button>
+	<XDrawerSidebar ref="drawerNav" class="sidebar" v-if="isMobile"/>
 
 	<transition name="tray-back">
 		<div class="tray-back _modalBg"
@@ -59,38 +57,31 @@ import { faLayerGroup, faBars, faHome, faCircle, faWindowMaximize, faColumns, fa
 import { faBell } from '@fortawesome/free-regular-svg-icons';
 import { instanceName } from '@client/config';
 import { StickySidebar } from '@client/scripts/sticky-sidebar';
-import XSidebar from '@client/components/sidebar.vue';
+import XSidebar from './default.sidebar.vue';
+import XDrawerSidebar from '@client/ui/_common_/sidebar.vue';
 import XCommon from './_common_/common.vue';
 import XHeader from './_common_/header.vue';
-import XSide from './default.side.vue';
 import * as os from '@client/os';
 import { sidebarDef } from '@client/sidebar';
 
 const DESKTOP_THRESHOLD = 1100;
+const MOBILE_THRESHOLD = 600;
 
 export default defineComponent({
 	components: {
 		XCommon,
 		XSidebar,
+		XDrawerSidebar,
 		XHeader,
 		XWidgets: defineAsyncComponent(() => import('./default.widgets.vue')),
-		XSide, // NOTE: dynamic importするとAsyncComponentWrapperが間に入るせいでref取得できなくて面倒になる
-	},
-
-	provide() {
-		return {
-			sideViewHook: this.isDesktop ? (url) => {
-				this.$refs.side.navigate(url);
-			} : null
-		};
 	},
 
 	data() {
 		return {
 			pageInfo: null,
-			isDesktop: window.innerWidth >= DESKTOP_THRESHOLD,
 			menuDef: sidebarDef,
-			navHidden: false,
+			isMobile: window.innerWidth <= MOBILE_THRESHOLD,
+			isDesktop: window.innerWidth >= DESKTOP_THRESHOLD,
 			widgetsShowing: false,
 			wallpaper: localStorage.getItem('wallpaper') != null,
 			faLayerGroup, faBars, faBell, faHome, faCircle, faPencilAlt,
@@ -125,21 +116,10 @@ export default defineComponent({
 	},
 
 	mounted() {
-		this.adjustUI();
-
-		const ro = new ResizeObserver((entries, observer) => {
-			this.adjustUI();
-		});
-
-		ro.observe(this.$refs.contents);
-
-		window.addEventListener('resize', this.adjustUI, { passive: true });
-
-		if (!this.isDesktop) {
-			window.addEventListener('resize', () => {
-				if (window.innerWidth >= DESKTOP_THRESHOLD) this.isDesktop = true;
-			}, { passive: true });
-		}
+		window.addEventListener('resize', () => {
+			this.isMobile = (window.innerWidth <= MOBILE_THRESHOLD);
+			this.isDesktop = (window.innerWidth >= DESKTOP_THRESHOLD);
+		}, { passive: true });
 	},
 
 	methods: {
@@ -151,20 +131,8 @@ export default defineComponent({
 			}
 		},
 
-		adjustUI() {
-			const navWidth = this.$refs.nav.$el.offsetWidth;
-			this.navHidden = navWidth === 0;
-			if (this.$refs.contents == null) return;
-			const width = this.$refs.contents.offsetWidth;
-			if (this.$refs.header) this.$refs.header.style.width = `${width}px`;
-		},
-
-		showNav() {
-			this.$refs.nav.show();
-		},
-
-		attachSticky(el) {
-			const sticky = new StickySidebar(el, this.$refs.widgetsSpacer);
+		attachSticky() {
+			const sticky = new StickySidebar(this.$refs.widgets, 16);
 			window.addEventListener('scroll', () => {
 				sticky.calc(window.scrollY);
 			}, { passive: true });
@@ -178,6 +146,10 @@ export default defineComponent({
 			window.scroll({ top: 0, behavior: 'smooth' });
 		},
 
+		showDrawerNav() {
+			this.$refs.drawerNav.show();
+		},
+
 		onTransition() {
 			if (window._scroll) window._scroll();
 		},
@@ -200,12 +172,6 @@ export default defineComponent({
 			os.contextMenu([{
 				type: 'label',
 				text: path,
-			}, {
-				icon: faColumns,
-				text: this.$ts.openInSideView,
-				action: () => {
-					this.$refs.side.navigate(path);
-				}
 			}, {
 				icon: faWindowMaximize,
 				text: this.$ts.openInWindow,
@@ -242,99 +208,98 @@ export default defineComponent({
 }
 
 .mk-app {
-	$header-height: 58px; // TODO: どこかに集約したい
-	$ui-font-size: 1em; // TODO: どこかに集約したい
-	$widgets-hide-threshold: 1090px;
+	$header-height: 50px;
+	$ui-font-size: 1em;
+	$widgets-hide-threshold: 1200px;
+	$nav-icon-only-width: 78px; // TODO: どこかに集約したい
 
 	// ほんとは単に 100vh と書きたいところだが... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
 	min-height: calc(var(--vh, 1vh) * 100);
 	box-sizing: border-box;
-	display: flex;
 
 	&.wallpaper {
 		background: var(--wallpaperOverlay);
 		//backdrop-filter: blur(4px);
 	}
 
-	> .contents {
-		width: 100%;
-		min-width: 0;
+	&.isMobile {
+		> .columns {
+			display: block;
+			margin: 0;
 
-		&.withHeader {
-			padding-top: $header-height;
+			> .main {
+				margin: 0;
+				border: none;
+				width: 100%;
+				border-radius: 0;
+
+				> .header {
+					width: 100%;
+				}
+			}
 		}
+	}
 
-		> .header {
-			position: fixed;
-			z-index: 1000;
-			top: 0;
-			height: $header-height;
-			width: 100%;
-			line-height: $header-height;
-			text-align: center;
-			font-weight: bold;
-			//background-color: var(--panel);
-			-webkit-backdrop-filter: blur(32px);
-			backdrop-filter: blur(32px);
-			background-color: var(--header);
-			//border-bottom: solid 1px var(--divider);
-			user-select: none;
-		}
+	> .columns {
+		display: flex;
+		justify-content: center;
+		max-width: 100%;
+		margin: 32px 0;
 
-		> main {
-			min-width: 0;
+		> .main {
+			width: 750px;
+			margin: 0 16px 0 0;
+			background: var(--bg);
+			--margin: 12px;
+
+			> .header {
+				position: sticky;
+				z-index: 1000;
+				top: 0;
+				height: $header-height;
+				line-height: $header-height;
+				-webkit-backdrop-filter: blur(32px);
+				backdrop-filter: blur(32px);
+				background-color: var(--header);
+				border-bottom: solid 0.5px var(--divider);
+			}
 
 			> .content {
-				> * {
-					// ほんとは単に calc(100vh - #{$header-height}) と書きたいところだが... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
-					min-height: calc((var(--vh, 1vh) * 100) - #{$header-height});
-				}
+				background: var(--bg);
+				--stickyTop: #{$header-height};
 			}
 
-			> .spacer {
-				height: 82px;
+			@media (max-width: 850px) {
+				padding-top: $header-height;
 
-				@media (min-width: ($widgets-hide-threshold + 1px)) {
-					display: none;
+				> .header {
+					position: fixed;
+					width: calc(100% - #{$nav-icon-only-width});
 				}
 			}
 		}
-	}
 
-	> .side {
-		min-width: 370px;
-		max-width: 370px;
-		border-left: solid 1px var(--divider);
-	}
+		> .widgets {
+			//--panelShadow: none;
 
-	> .widgets {
-		padding: 0 var(--margin);
-		border-left: solid 1px var(--divider);
-
-		@media (max-width: $widgets-hide-threshold) {
-			display: none;
-		}
-	}
-
-	> .widgetButton {
-		display: block;
-		position: fixed;
-		z-index: 1000;
-		bottom: 32px;
-		right: 32px;
-		width: 64px;
-		height: 64px;
-		border-radius: 100%;
-		box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12);
-		font-size: 22px;
-		background: var(--panel);
-
-		&.navHidden {
-			display: none;
+			@media (max-width: $widgets-hide-threshold) {
+				display: none;
+			}
 		}
 
-		@media (min-width: ($widgets-hide-threshold + 1px)) {
-			display: none;
+		@media (max-width: 850px) {
+			margin: 0;
+
+			> .sidebar {
+				border-right: solid 0.5px var(--divider);
+			}
+
+			> .main {
+				margin: 0;
+				border-radius: 0;
+				box-shadow: none;
+				width: 100%;
+			}
 		}
 	}
 
@@ -349,10 +314,7 @@ export default defineComponent({
 		-webkit-backdrop-filter: blur(32px);
 		backdrop-filter: blur(32px);
 		background-color: var(--header);
-
-		&:not(.navHidden) {
-			display: none;
-		}
+		border-top: solid 0.5px var(--divider);
 
 		> .button {
 			position: relative;
@@ -429,6 +391,3 @@ export default defineComponent({
 	}
 }
 </style>
-
-<style lang="scss">
-</style>
diff --git a/src/client/ui/default.widgets.vue b/src/client/ui/default.widgets.vue
index 35d3442bb2..b12de841a7 100644
--- a/src/client/ui/default.widgets.vue
+++ b/src/client/ui/default.widgets.vue
@@ -61,8 +61,6 @@ export default defineComponent({
 .efzpzdvf {
 	position: sticky;
 	height: min-content;
-	min-height: 100vh;
-	padding: var(--margin) 0;
 	box-sizing: border-box;
 
 	> * {
diff --git a/src/client/ui/desktop.vue b/src/client/ui/desktop.vue
index 1480fd1840..a60aed6841 100644
--- a/src/client/ui/desktop.vue
+++ b/src/client/ui/desktop.vue
@@ -12,7 +12,7 @@ import { host } from '@client/config';
 import { search } from '@client/scripts/search';
 import XCommon from './_common_/common.vue';
 import * as os from '@client/os';
-import XSidebar from '@client/components/sidebar.vue';
+import XSidebar from '@client/ui/_common_/sidebar.vue';
 import { sidebarDef } from '@client/sidebar';
 import { ColdDeviceStorage } from '@client/store';
 
diff --git a/src/client/ui/universal.vue b/src/client/ui/universal.vue
new file mode 100644
index 0000000000..e1b368c25a
--- /dev/null
+++ b/src/client/ui/universal.vue
@@ -0,0 +1,433 @@
+<template>
+<div class="mk-app" :class="{ wallpaper }">
+	<XSidebar ref="nav" class="sidebar"/>
+
+	<div class="contents" ref="contents" :class="{ withHeader: $store.state.titlebar }" @contextmenu.stop="onContextmenu">
+		<header v-if="$store.state.titlebar" class="header" ref="header" @click="onHeaderClick">
+			<XHeader :info="pageInfo"/>
+		</header>
+		<main ref="main">
+			<div class="content">
+				<router-view v-slot="{ Component }">
+					<transition :name="$store.state.animation ? 'page' : ''" mode="out-in" @enter="onTransition">
+						<keep-alive :include="['timeline']">
+							<component :is="Component" :ref="changePage"/>
+						</keep-alive>
+					</transition>
+				</router-view>
+			</div>
+			<div class="spacer"></div>
+		</main>
+	</div>
+
+	<XSide v-if="isDesktop" class="side" ref="side"/>
+
+	<div v-if="isDesktop" class="widgets" ref="widgets">
+		<XWidgets @mounted="attachSticky"/>
+	</div>
+
+	<div class="buttons" :class="{ navHidden }">
+		<button class="button nav _button" @click="showNav" ref="navButton"><Fa :icon="faBars"/><i v-if="navIndicated"><Fa :icon="faCircle"/></i></button>
+		<button class="button home _button" @click="$route.name === 'index' ? top() : $router.push('/')"><Fa :icon="faHome"/></button>
+		<button class="button notifications _button" @click="$router.push('/my/notifications')"><Fa :icon="faBell"/><i v-if="$i.hasUnreadNotification"><Fa :icon="faCircle"/></i></button>
+		<button class="button widget _button" @click="widgetsShowing = true"><Fa :icon="faLayerGroup"/></button>
+		<button class="button post _button" @click="post"><Fa :icon="faPencilAlt"/></button>
+	</div>
+
+	<button class="widgetButton _button" :class="{ navHidden }" @click="widgetsShowing = true"><Fa :icon="faLayerGroup"/></button>
+
+	<transition name="tray-back">
+		<div class="tray-back _modalBg"
+			v-if="widgetsShowing"
+			@click="widgetsShowing = false"
+			@touchstart.passive="widgetsShowing = false"
+		></div>
+	</transition>
+
+	<transition name="tray">
+		<XWidgets v-if="widgetsShowing" class="tray"/>
+	</transition>
+
+	<XCommon/>
+</div>
+</template>
+
+<script lang="ts">
+import { defineComponent, defineAsyncComponent } from 'vue';
+import { faLayerGroup, faBars, faHome, faCircle, faWindowMaximize, faColumns, faPencilAlt } from '@fortawesome/free-solid-svg-icons';
+import { faBell } from '@fortawesome/free-regular-svg-icons';
+import { instanceName } from '@client/config';
+import { StickySidebar } from '@client/scripts/sticky-sidebar';
+import XSidebar from '@client/ui/_common_/sidebar.vue';
+import XCommon from './_common_/common.vue';
+import XHeader from './_common_/header.vue';
+import XSide from './default.side.vue';
+import * as os from '@client/os';
+import { sidebarDef } from '@client/sidebar';
+
+const DESKTOP_THRESHOLD = 1100;
+
+export default defineComponent({
+	components: {
+		XCommon,
+		XSidebar,
+		XHeader,
+		XWidgets: defineAsyncComponent(() => import('./universal.widgets.vue')),
+		XSide, // NOTE: dynamic importするとAsyncComponentWrapperが間に入るせいでref取得できなくて面倒になる
+	},
+
+	provide() {
+		return {
+			sideViewHook: this.isDesktop ? (url) => {
+				this.$refs.side.navigate(url);
+			} : null
+		};
+	},
+
+	data() {
+		return {
+			pageInfo: null,
+			isDesktop: window.innerWidth >= DESKTOP_THRESHOLD,
+			menuDef: sidebarDef,
+			navHidden: false,
+			widgetsShowing: false,
+			wallpaper: localStorage.getItem('wallpaper') != null,
+			faLayerGroup, faBars, faBell, faHome, faCircle, faPencilAlt,
+		};
+	},
+
+	computed: {
+		navIndicated(): boolean {
+			for (const def in this.menuDef) {
+				if (def === 'notifications') continue; // 通知は下にボタンとして表示されてるから
+				if (this.menuDef[def].indicated) return true;
+			}
+			return false;
+		}
+	},
+
+	created() {
+		document.documentElement.style.overflowY = 'scroll';
+
+		if (this.$store.state.widgets.length === 0) {
+			this.$store.set('widgets', [{
+				name: 'calendar',
+				id: 'a', place: 'right', data: {}
+			}, {
+				name: 'notifications',
+				id: 'b', place: 'right', data: {}
+			}, {
+				name: 'trends',
+				id: 'c', place: 'right', data: {}
+			}]);
+		}
+	},
+
+	mounted() {
+		this.adjustUI();
+
+		const ro = new ResizeObserver((entries, observer) => {
+			this.adjustUI();
+		});
+
+		ro.observe(this.$refs.contents);
+
+		window.addEventListener('resize', this.adjustUI, { passive: true });
+
+		if (!this.isDesktop) {
+			window.addEventListener('resize', () => {
+				if (window.innerWidth >= DESKTOP_THRESHOLD) this.isDesktop = true;
+			}, { passive: true });
+		}
+	},
+
+	methods: {
+		changePage(page) {
+			if (page == null) return;
+			if (page.INFO) {
+				this.pageInfo = page.INFO;
+				document.title = `${this.pageInfo.title} | ${instanceName}`;
+			}
+		},
+
+		adjustUI() {
+			const navWidth = this.$refs.nav.$el.offsetWidth;
+			this.navHidden = navWidth === 0;
+			if (this.$refs.contents == null) return;
+			const width = this.$refs.contents.offsetWidth;
+			if (this.$refs.header) this.$refs.header.style.width = `${width}px`;
+		},
+
+		showNav() {
+			this.$refs.nav.show();
+		},
+
+		attachSticky(el) {
+			const sticky = new StickySidebar(this.$refs.widgets);
+			window.addEventListener('scroll', () => {
+				sticky.calc(window.scrollY);
+			}, { passive: true });
+		},
+
+		post() {
+			os.post();
+		},
+
+		top() {
+			window.scroll({ top: 0, behavior: 'smooth' });
+		},
+
+		onTransition() {
+			if (window._scroll) window._scroll();
+		},
+
+		onHeaderClick() {
+			window.scroll({ top: 0, behavior: 'smooth' });
+		},
+
+		onContextmenu(e) {
+			const isLink = (el: HTMLElement) => {
+				if (el.tagName === 'A') return true;
+				if (el.parentElement) {
+					return isLink(el.parentElement);
+				}
+			};
+			if (isLink(e.target)) return;
+			if (['INPUT', 'TEXTAREA'].includes(e.target.tagName) || e.target.attributes['contenteditable']) return;
+			if (window.getSelection().toString() !== '') return;
+			const path = this.$route.path;
+			os.contextMenu([{
+				type: 'label',
+				text: path,
+			}, {
+				icon: faColumns,
+				text: this.$ts.openInSideView,
+				action: () => {
+					this.$refs.side.navigate(path);
+				}
+			}, {
+				icon: faWindowMaximize,
+				text: this.$ts.openInWindow,
+				action: () => {
+					os.pageWindow(path);
+				}
+			}], e);
+		},
+	}
+});
+</script>
+
+<style lang="scss" scoped>
+.tray-enter-active,
+.tray-leave-active {
+	opacity: 1;
+	transform: translateX(0);
+	transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
+}
+.tray-enter-from,
+.tray-leave-active {
+	opacity: 0;
+	transform: translateX(240px);
+}
+
+.tray-back-enter-active,
+.tray-back-leave-active {
+	opacity: 1;
+	transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
+}
+.tray-back-enter-from,
+.tray-back-leave-active {
+	opacity: 0;
+}
+
+.mk-app {
+	$header-height: 58px; // TODO: どこかに集約したい
+	$ui-font-size: 1em; // TODO: どこかに集約したい
+	$widgets-hide-threshold: 1090px;
+
+	// ほんとは単に 100vh と書きたいところだが... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
+	min-height: calc(var(--vh, 1vh) * 100);
+	box-sizing: border-box;
+	display: flex;
+
+	&.wallpaper {
+		background: var(--wallpaperOverlay);
+		//backdrop-filter: blur(4px);
+	}
+
+	> .contents {
+		width: 100%;
+		min-width: 0;
+
+		&.withHeader {
+			padding-top: $header-height;
+		}
+
+		> .header {
+			position: fixed;
+			z-index: 1000;
+			top: 0;
+			height: $header-height;
+			width: 100%;
+			line-height: $header-height;
+			text-align: center;
+			font-weight: bold;
+			//background-color: var(--panel);
+			-webkit-backdrop-filter: blur(32px);
+			backdrop-filter: blur(32px);
+			background-color: var(--header);
+			//border-bottom: solid 0.5px var(--divider);
+			user-select: none;
+		}
+
+		> main {
+			min-width: 0;
+
+			> .content {
+				> * {
+					// ほんとは単に calc(100vh - #{$header-height}) と書きたいところだが... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
+					min-height: calc((var(--vh, 1vh) * 100) - #{$header-height});
+				}
+			}
+
+			> .spacer {
+				height: 82px;
+
+				@media (min-width: ($widgets-hide-threshold + 1px)) {
+					display: none;
+				}
+			}
+		}
+	}
+
+	> .side {
+		min-width: 370px;
+		max-width: 370px;
+		border-left: solid 0.5px var(--divider);
+	}
+
+	> .widgets {
+		padding: 0 var(--margin);
+		border-left: solid 0.5px var(--divider);
+
+		@media (max-width: $widgets-hide-threshold) {
+			display: none;
+		}
+	}
+
+	> .widgetButton {
+		display: block;
+		position: fixed;
+		z-index: 1000;
+		bottom: 32px;
+		right: 32px;
+		width: 64px;
+		height: 64px;
+		border-radius: 100%;
+		box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12);
+		font-size: 22px;
+		background: var(--panel);
+
+		&.navHidden {
+			display: none;
+		}
+
+		@media (min-width: ($widgets-hide-threshold + 1px)) {
+			display: none;
+		}
+	}
+
+	> .buttons {
+		position: fixed;
+		z-index: 1000;
+		bottom: 0;
+		padding: 16px;
+		display: flex;
+		width: 100%;
+		box-sizing: border-box;
+		-webkit-backdrop-filter: blur(32px);
+		backdrop-filter: blur(32px);
+		background-color: var(--header);
+
+		&:not(.navHidden) {
+			display: none;
+		}
+
+		> .button {
+			position: relative;
+			flex: 1;
+			padding: 0;
+			margin: auto;
+			height: 64px;
+			border-radius: 8px;
+			background: var(--panel);
+			color: var(--fg);
+
+			&:not(:last-child) {
+				margin-right: 12px;
+			}
+
+			@media (max-width: 400px) {
+				height: 60px;
+
+				&:not(:last-child) {
+					margin-right: 8px;
+				}
+			}
+
+			&:hover {
+				background: var(--X2);
+			}
+
+			> i {
+				position: absolute;
+				top: 0;
+				left: 0;
+				color: var(--indicator);
+				font-size: 16px;
+				animation: blink 1s infinite;
+			}
+
+			&:first-child {
+				margin-left: 0;
+			}
+
+			&:last-child {
+				margin-right: 0;
+			}
+
+			> * {
+				font-size: 22px;
+			}
+
+			&:disabled {
+				cursor: default;
+
+				> * {
+					opacity: 0.5;
+				}
+			}
+		}
+	}
+
+	> .tray-back {
+		z-index: 1001;
+	}
+
+	> .tray {
+		position: fixed;
+		top: 0;
+		right: 0;
+		z-index: 1001;
+		// ほんとは単に 100vh と書きたいところだが... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
+		height: calc(var(--vh, 1vh) * 100);
+		padding: var(--margin);
+		box-sizing: border-box;
+		overflow: auto;
+		background: var(--bg);
+	}
+}
+</style>
+
+<style lang="scss">
+</style>
diff --git a/src/client/ui/universal.widgets.vue b/src/client/ui/universal.widgets.vue
new file mode 100644
index 0000000000..35d3442bb2
--- /dev/null
+++ b/src/client/ui/universal.widgets.vue
@@ -0,0 +1,81 @@
+<template>
+<div class="efzpzdvf">
+	<XWidgets :edit="editMode" :widgets="$store.reactiveState.widgets.value" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/>
+
+	<button v-if="editMode" @click="editMode = false" class="_textButton" style="font-size: 0.9em;"><Fa :icon="faCheck"/> {{ $ts.editWidgetsExit }}</button>
+	<button v-else @click="editMode = true" class="_textButton" style="font-size: 0.9em;"><Fa :icon="faPencilAlt"/> {{ $ts.editWidgets }}</button>
+</div>
+</template>
+
+<script lang="ts">
+import { defineComponent, defineAsyncComponent } from 'vue';
+import { faPencilAlt, faPlus, faBars, faTimes, faCheck } from '@fortawesome/free-solid-svg-icons';
+import XWidgets from '@client/components/widgets.vue';
+import * as os from '@client/os';
+
+export default defineComponent({
+	components: {
+		XWidgets
+	},
+
+	emits: ['mounted'],
+
+	data() {
+		return {
+			editMode: false,
+			faPencilAlt, faPlus, faBars, faTimes, faCheck,
+		};
+	},
+
+	mounted() {
+		this.$emit('mounted', this.$el);
+	},
+
+	methods: {
+		addWidget(widget) {
+			this.$store.set('widgets', [{
+				...widget,
+				place: null,
+			}, ...this.$store.state.widgets]);
+		},
+
+		removeWidget(widget) {
+			this.$store.set('widgets', this.$store.state.widgets.filter(w => w.id != widget.id));
+		},
+
+		updateWidget({ id, data }) {
+			this.$store.set('widgets', this.$store.state.widgets.map(w => w.id === id ? {
+				...w,
+				data: data
+			} : w));
+		},
+
+		updateWidgets(widgets) {
+			this.$store.set('widgets', widgets);
+		}
+	}
+});
+</script>
+
+<style lang="scss" scoped>
+.efzpzdvf {
+	position: sticky;
+	height: min-content;
+	min-height: 100vh;
+	padding: var(--margin) 0;
+	box-sizing: border-box;
+
+	> * {
+		margin: var(--margin) 0;
+		width: 300px;
+
+		&:first-child {
+			margin-top: 0;
+		}
+	}
+
+	> .add {
+		margin: 0 auto;
+	}
+}
+</style>
diff --git a/src/client/ui/zen.vue b/src/client/ui/zen.vue
index 9215a639b0..321eb1a025 100644
--- a/src/client/ui/zen.vue
+++ b/src/client/ui/zen.vue
@@ -94,7 +94,7 @@ export default defineComponent({
 			-webkit-backdrop-filter: blur(32px);
 			backdrop-filter: blur(32px);
 			background-color: var(--header);
-			border-bottom: solid 1px var(--divider);
+			border-bottom: solid 0.5px var(--divider);
 		}
 
 		> main {
diff --git a/src/client/widgets/aiscript.vue b/src/client/widgets/aiscript.vue
index 84390866bd..f24e033593 100644
--- a/src/client/widgets/aiscript.vue
+++ b/src/client/widgets/aiscript.vue
@@ -122,7 +122,7 @@ export default defineComponent({
 		color: var(--fg);
 		background: transparent;
 		border: none;
-		border-bottom: solid 1px var(--divider);
+		border-bottom: solid 0.5px var(--divider);
 		border-radius: 0;
 		box-sizing: border-box;
 		font: inherit;
@@ -147,7 +147,7 @@ export default defineComponent({
 	}
 
 	> .logs {
-		border-top: solid 1px var(--divider);
+		border-top: solid 0.5px var(--divider);
 		text-align: left;
 		padding: 16px;
 
diff --git a/src/client/widgets/federation.vue b/src/client/widgets/federation.vue
index 6eb656ce05..f0a79a31a6 100644
--- a/src/client/widgets/federation.vue
+++ b/src/client/widgets/federation.vue
@@ -100,7 +100,7 @@ export default defineComponent({
 			display: flex;
 			align-items: center;
 			padding: 14px 16px;
-			border-bottom: solid 1px var(--divider);
+			border-bottom: solid 0.5px var(--divider);
 
 			> img {
 				display: block;
diff --git a/src/client/widgets/job-queue.vue b/src/client/widgets/job-queue.vue
index aaf2b2fc8f..0b560ca56e 100644
--- a/src/client/widgets/job-queue.vue
+++ b/src/client/widgets/job-queue.vue
@@ -136,7 +136,7 @@ export default defineComponent({
 		padding: 16px;
 
 		&:not(:first-child) {
-			border-top: solid 1px var(--divider);
+			border-top: solid 0.5px var(--divider);
 		}
 
 		> .label {
diff --git a/src/client/widgets/memo.vue b/src/client/widgets/memo.vue
index 4a7786312a..c3ab2934a8 100644
--- a/src/client/widgets/memo.vue
+++ b/src/client/widgets/memo.vue
@@ -77,7 +77,7 @@ export default defineComponent({
 		color: var(--fg);
 		background: transparent;
 		border: none;
-		border-bottom: solid 1px var(--divider);
+		border-bottom: solid 0.5px var(--divider);
 		border-radius: 0;
 		box-sizing: border-box;
 		font: inherit;
diff --git a/src/client/widgets/trends.vue b/src/client/widgets/trends.vue
index 300e3b31de..cd7202f1f8 100644
--- a/src/client/widgets/trends.vue
+++ b/src/client/widgets/trends.vue
@@ -79,7 +79,7 @@ export default defineComponent({
 			display: flex;
 			align-items: center;
 			padding: 14px 16px;
-			border-bottom: solid 1px var(--divider);
+			border-bottom: solid 0.5px var(--divider);
 
 			> .tag {
 				flex: 1;