diff --git a/packages/frontend/src/components/MkVisitorDashboard.vue b/packages/frontend/src/components/MkVisitorDashboard.vue
new file mode 100644
index 000000000..a32de01d9
--- /dev/null
+++ b/packages/frontend/src/components/MkVisitorDashboard.vue
@@ -0,0 +1,224 @@
+<template>
+<div v-if="meta" :class="$style.root">
+	<div :class="$style.main">
+		<img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" :class="$style.mainIcon"/>
+		<button class="_button _acrylic" :class="$style.mainMenu" @click="showMenu"><i class="ti ti-dots"></i></button>
+		<div :class="$style.mainFg">
+			<h1 :class="$style.mainTitle">
+				<!-- 背景色によってはロゴが見えなくなるのでとりあえず無効に -->
+				<!-- <img class="logo" v-if="meta.logoImageUrl" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span> -->
+				<span>{{ instanceName }}</span>
+			</h1>
+			<div :class="$style.mainAbout">
+				<!-- eslint-disable-next-line vue/no-v-html -->
+				<div v-html="meta.description || i18n.ts.headlineMisskey"></div>
+			</div>
+			<div v-if="instance.disableRegistration" :class="$style.mainWarn">
+				<MkInfo warn>{{ i18n.ts.invitationRequiredToRegister }}</MkInfo>
+			</div>
+			<div class="_gaps_s" :class="$style.mainActions">
+				<MkButton :class="$style.mainAction" full rounded gradate data-cy-signup style="margin-right: 12px;" @click="signup()">{{ i18n.ts.joinThisServer }}</MkButton>
+				<MkButton :class="$style.mainAction" full rounded @click="exploreOtherServers()">{{ i18n.ts.exploreOtherServers }}</MkButton>
+				<MkButton :class="$style.mainAction" full rounded data-cy-signin @click="signin()">{{ i18n.ts.login }}</MkButton>
+			</div>
+		</div>
+	</div>
+	<div v-if="stats" :class="$style.stats">
+		<div :class="$style.statsItem">
+			<div :class="$style.statsItemLabel">{{ i18n.ts.users }}</div>
+			<div :class="$style.statsItemCount"><MkNumber :value="stats.originalUsersCount"/></div>
+		</div>
+		<div :class="$style.statsItem">
+			<div :class="$style.statsItemLabel">{{ i18n.ts.notes }}</div>
+			<div :class="$style.statsItemCount"><MkNumber :value="stats.originalNotesCount"/></div>
+		</div>
+	</div>
+	<div v-if="instance.policies.ltlAvailable" :class="$style.tl">
+		<div :class="$style.tlHeader">{{ i18n.ts.letsLookAtTimeline }}</div>
+		<div :class="$style.tlBody">
+			<MkTimeline src="local"/>
+		</div>
+	</div>
+</div>
+</template>
+
+<script lang="ts" setup>
+import { } from 'vue';
+import { Instance } from 'misskey-js/built/entities';
+import XTimeline from './welcome.timeline.vue';
+import XSigninDialog from '@/components/MkSigninDialog.vue';
+import XSignupDialog from '@/components/MkSignupDialog.vue';
+import MkButton from '@/components/MkButton.vue';
+import MkTimeline from '@/components/MkTimeline.vue';
+import MkInfo from '@/components/MkInfo.vue';
+import { instanceName } from '@/config';
+import * as os from '@/os';
+import { i18n } from '@/i18n';
+import { instance } from '@/instance';
+import number from '@/filters/number';
+import MkNumber from '@/components/MkNumber.vue';
+
+let meta = $ref<Instance>();
+let stats = $ref(null);
+
+os.api('meta', { detail: true }).then(_meta => {
+	meta = _meta;
+});
+
+os.api('stats', {
+}).then((res) => {
+	stats = res;
+});
+
+function signin() {
+	os.popup(XSigninDialog, {
+		autoSet: true,
+	}, {}, 'closed');
+}
+
+function signup() {
+	os.popup(XSignupDialog, {
+		autoSet: true,
+	}, {}, 'closed');
+}
+
+function showMenu(ev) {
+	os.popupMenu([{
+		text: i18n.ts.instanceInfo,
+		icon: 'ti ti-info-circle',
+		action: () => {
+			os.pageWindow('/about');
+		},
+	}, {
+		text: i18n.ts.aboutMisskey,
+		icon: 'ti ti-info-circle',
+		action: () => {
+			os.pageWindow('/about-misskey');
+		},
+	}, null, {
+		text: i18n.ts.help,
+		icon: 'ti ti-help-circle',
+		action: () => {
+			window.open('https://misskey-hub.net/help.md', '_blank');
+		},
+	}], ev.currentTarget ?? ev.target);
+}
+
+function exploreOtherServers() {
+	// TODO: 言語をよしなに
+	window.open('https://join.misskey.page/ja-JP/instances', '_blank');
+}
+</script>
+
+<style lang="scss" module>
+.root {
+	position: relative;
+	display: flex;
+	flex-direction: column;
+	gap: 16px;
+	padding: 32px 0 0 0;
+}
+
+.main {
+	position: relative;
+	background: var(--panel);
+	border-radius: var(--radius);
+	box-shadow: 0 12px 32px rgb(0 0 0 / 25%);
+	text-align: center;
+}
+
+.mainIcon {
+	width: 85px;
+	margin-top: -47px;
+	border-radius: 100%;
+	vertical-align: bottom;
+}
+
+.mainMenu {
+	position: absolute;
+	top: 16px;
+	right: 16px;
+	width: 32px;
+	height: 32px;
+	border-radius: 8px;
+	font-size: 18px;
+}
+
+.mainFg {
+	position: relative;
+	z-index: 1;
+}
+
+.mainTitle {
+	display: block;
+	margin: 0;
+	padding: 16px 32px 24px 32px;
+	font-size: 1.4em;
+}
+
+.mainLogo {
+	vertical-align: bottom;
+	max-height: 120px;
+	max-width: min(100%, 300px);
+}
+
+.mainAbout {
+	padding: 0 32px;
+}
+
+.mainWarn {
+	padding: 32px 32px 0 32px;
+}
+
+.mainActions {
+	padding: 32px;
+}
+
+.mainAction {
+	line-height: 28px;
+}
+
+.stats {
+	display: grid;
+	grid-template-columns: 1fr 1fr;
+	grid-gap: 16px;
+}
+
+.statsItem {
+	position: relative;
+	background: var(--panel);
+	border-radius: var(--radius);
+	overflow: clip;
+	box-shadow: 0 12px 32px rgb(0 0 0 / 25%);
+	padding: 16px;
+}
+
+.statsItemLabel {
+	color: var(--fgTransparentWeak);
+	font-size: 0.9em;
+}
+
+.statsItemCount {
+	font-weight: bold;
+	font-size: 1.2em;
+	color: var(--accent);
+}
+
+.tl {
+	position: relative;
+	background: var(--panel);
+	border-radius: var(--radius);
+	overflow: clip;
+	box-shadow: 0 12px 32px rgb(0 0 0 / 25%);
+}
+
+.tlHeader {
+	padding: 12px 16px;
+	border-bottom: solid 1px var(--divider);
+}
+
+.tlBody {
+	height: 350px;
+	overflow: auto;
+}
+</style>
diff --git a/packages/frontend/src/pages/welcome.entrance.a.vue b/packages/frontend/src/pages/welcome.entrance.a.vue
index 4e716b9b3..929152bd5 100644
--- a/packages/frontend/src/pages/welcome.entrance.a.vue
+++ b/packages/frontend/src/pages/welcome.entrance.a.vue
@@ -13,35 +13,7 @@
 		<MkEmoji :normal="true" :no-style="true" emoji="🍮"/>
 	</div>
 	<div class="contents">
-		<div class="main">
-			<img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" class="icon"/>
-			<button class="_button _acrylic menu" @click="showMenu"><i class="ti ti-dots"></i></button>
-			<div class="fg">
-				<h1>
-					<!-- 背景色によってはロゴが見えなくなるのでとりあえず無効に -->
-					<!-- <img class="logo" v-if="meta.logoImageUrl" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span> -->
-					<span class="text">{{ instanceName }}</span>
-				</h1>
-				<div class="about">
-					<!-- eslint-disable-next-line vue/no-v-html -->
-					<div class="desc" v-html="meta.description || i18n.ts.headlineMisskey"></div>
-				</div>
-				<div v-if="instance.disableRegistration" class="warn">
-					<MkInfo warn>{{ i18n.ts.invitationRequiredToRegister }}</MkInfo>
-				</div>
-				<div class="action _gaps_s">
-					<MkButton full rounded gradate data-cy-signup style="margin-right: 12px;" @click="signup()">{{ i18n.ts.joinThisServer }}</MkButton>
-					<MkButton full rounded @click="exploreOtherServers()">{{ i18n.ts.exploreOtherServers }}</MkButton>
-					<MkButton full rounded data-cy-signin @click="signin()">{{ i18n.ts.login }}</MkButton>
-				</div>
-			</div>
-		</div>
-		<div v-if="instance.policies.ltlAvailable" class="tl">
-			<div class="title">{{ i18n.ts.letsLookAtTimeline }}</div>
-			<div class="body">
-				<MkTimeline src="local"/>
-			</div>
-		</div>
+		<MkVisitorDashboard/>
 	</div>
 	<div v-if="instances && instances.length > 0" class="federation">
 		<MarqueeText :duration="40">
@@ -60,16 +32,15 @@ import { } from 'vue';
 import { Instance } from 'misskey-js/built/entities';
 import XTimeline from './welcome.timeline.vue';
 import MarqueeText from '@/components/MkMarquee.vue';
-import XSigninDialog from '@/components/MkSigninDialog.vue';
-import XSignupDialog from '@/components/MkSignupDialog.vue';
-import MkButton from '@/components/MkButton.vue';
 import MkFeaturedPhotos from '@/components/MkFeaturedPhotos.vue';
-import MkTimeline from '@/components/MkTimeline.vue';
 import MkInfo from '@/components/MkInfo.vue';
 import { instanceName } from '@/config';
 import * as os from '@/os';
 import { i18n } from '@/i18n';
 import { instance } from '@/instance';
+import number from '@/filters/number';
+import MkNumber from '@/components/MkNumber.vue';
+import MkVisitorDashboard from '@/components/MkVisitorDashboard.vue';
 
 let meta = $ref<Instance>();
 let instances = $ref<any[]>();
@@ -84,45 +55,6 @@ os.apiGet('federation/instances', {
 }).then(_instances => {
 	instances = _instances;
 });
-
-function signin() {
-	os.popup(XSigninDialog, {
-		autoSet: true,
-	}, {}, 'closed');
-}
-
-function signup() {
-	os.popup(XSignupDialog, {
-		autoSet: true,
-	}, {}, 'closed');
-}
-
-function showMenu(ev) {
-	os.popupMenu([{
-		text: i18n.ts.instanceInfo,
-		icon: 'ti ti-info-circle',
-		action: () => {
-			os.pageWindow('/about');
-		},
-	}, {
-		text: i18n.ts.aboutMisskey,
-		icon: 'ti ti-info-circle',
-		action: () => {
-			os.pageWindow('/about-misskey');
-		},
-	}, null, {
-		text: i18n.ts.help,
-		icon: 'ti ti-help-circle',
-		action: () => {
-			window.open('https://misskey-hub.net/help.md', '_blank');
-		},
-	}], ev.currentTarget ?? ev.target);
-}
-
-function exploreOtherServers() {
-	// TODO: 言語をよしなに
-	window.open('https://join.misskey.page/ja-JP/instances', '_blank');
-}
 </script>
 
 <style lang="scss" scoped>
@@ -202,89 +134,11 @@ function exploreOtherServers() {
 		position: relative;
 		width: min(430px, calc(100% - 32px));
 		margin-left: 128px;
-		padding: 150px 0 100px 0;
+		padding: 100px 0 100px 0;
 
 		@media (max-width: 1200px) {
 			margin: auto;
 		}
-
-		> .main {
-			position: relative;
-			background: var(--panel);
-			border-radius: var(--radius);
-			box-shadow: 0 12px 32px rgb(0 0 0 / 25%);
-			text-align: center;
-		
-			> .icon {
-				width: 85px;
-				margin-top: -47px;
-				border-radius: 100%;
-				vertical-align: bottom;
-			}
-
-			> .menu {
-				position: absolute;
-				top: 16px;
-				right: 16px;
-				width: 32px;
-				height: 32px;
-				border-radius: 8px;
-				font-size: 18px;
-			}
-
-			> .fg {
-				position: relative;
-				z-index: 1;
-
-				> h1 {
-					display: block;
-					margin: 0;
-					padding: 16px 32px 24px 32px;
-					font-size: 1.4em;
-
-					> .logo {
-						vertical-align: bottom;
-						max-height: 120px;
-						max-width: min(100%, 300px);
-					}
-				}
-
-				> .about {
-					padding: 0 32px;
-				}
-
-				> .warn {
-					padding: 32px 32px 0 32px;
-				}
-
-				> .action {
-					padding: 32px;
-
-					> * {
-						line-height: 28px;
-					}
-				}
-			}
-		}
-
-		> .tl {
-			position: relative;
-			background: var(--panel);
-			border-radius: var(--radius);
-			overflow: clip;
-			box-shadow: 0 12px 32px rgb(0 0 0 / 25%);
-			margin-top: 16px;
-
-			> .title {
-				padding: 12px 16px;
-				border-bottom: solid 1px var(--divider);
-			}
-
-			> .body {
-				height: 350px;
-				overflow: auto;
-			}
-		}
 	}
 
 	> .federation {
diff --git a/packages/frontend/src/ui/visitor/header.vue b/packages/frontend/src/ui/visitor.header.vue
similarity index 100%
rename from packages/frontend/src/ui/visitor/header.vue
rename to packages/frontend/src/ui/visitor.header.vue
diff --git a/packages/frontend/src/ui/visitor.vue b/packages/frontend/src/ui/visitor.vue
index 6c96440eb..46520353a 100644
--- a/packages/frontend/src/ui/visitor.vue
+++ b/packages/frontend/src/ui/visitor.vue
@@ -1,19 +1,283 @@
 <template>
-<DesignB/>
+<div class="mk-app">
+	<a v-if="root" href="https://github.com/misskey-dev/misskey" target="_blank" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:var(--panel); color:var(--fg); position: fixed; z-index: 10; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>
+
+	<div v-if="!narrow && !root" class="side">
+		<div class="banner" :style="{ backgroundImage: instance.backgroundImageUrl ? `url(${ instance.backgroundImageUrl })` : 'none' }"></div>
+		<div class="dashboard">
+			<MkVisitorDashboard/>
+		</div>
+	</div>
+
+	<div class="main">
+		<XKanban v-if="narrow && !root" class="banner" :powered-by="root"/>
+
+		<div class="contents">
+			<XHeader v-if="!root" class="header"/>
+			<main v-if="!root" style="container-type: inline-size;">
+				<RouterView/>
+			</main>
+			<main v-else>
+				<RouterView/>
+			</main>
+			<div v-if="!root" class="powered-by">
+				<b><MkA to="/">{{ host }}</MkA></b>
+				<small>Powered by <a href="https://github.com/misskey-dev/misskey" target="_blank">Misskey</a></small>
+			</div>
+		</div>
+	</div>
+
+	<Transition :name="'tray-back'">
+		<div
+			v-if="showMenu"
+			class="menu-back _modalBg"
+			@click="showMenu = false"
+			@touchstart.passive="showMenu = false"
+		></div>
+	</Transition>
+
+	<Transition :name="'tray'">
+		<div v-if="showMenu" class="menu">
+			<MkA to="/" class="link" active-class="active"><i class="ti ti-home icon"></i>{{ i18n.ts.home }}</MkA>
+			<MkA v-if="isTimelineAvailable" to="/timeline" class="link" active-class="active"><i class="ti ti-message icon"></i>{{ i18n.ts.timeline }}</MkA>
+			<MkA to="/explore" class="link" active-class="active"><i class="ti ti-hash icon"></i>{{ i18n.ts.explore }}</MkA>
+			<MkA to="/announcements" class="link" active-class="active"><i class="ti ti-speakerphone icon"></i>{{ i18n.ts.announcements }}</MkA>
+			<MkA to="/channels" class="link" active-class="active"><i class="ti ti-device-tv icon"></i>{{ i18n.ts.channel }}</MkA>
+			<div class="divider"></div>
+			<MkA to="/pages" class="link" active-class="active"><i class="ti ti-news icon"></i>{{ i18n.ts.pages }}</MkA>
+			<MkA to="/play" class="link" active-class="active"><i class="ti ti-player-play icon"></i>Play</MkA>
+			<MkA to="/gallery" class="link" active-class="active"><i class="ti ti-icons icon"></i>{{ i18n.ts.gallery }}</MkA>
+			<div class="action">
+				<button class="_buttonPrimary" @click="signup()">{{ i18n.ts.signup }}</button>
+				<button class="_button" @click="signin()">{{ i18n.ts.login }}</button>
+			</div>
+		</div>
+	</Transition>
+</div>
 <XCommon/>
 </template>
 
-<script lang="ts">
-import { defineComponent } from 'vue';
-//import DesignA from './visitor/a.vue';
-import DesignB from './visitor/b.vue';
+<script lang="ts" setup>
+import { ComputedRef, onMounted, provide } from 'vue';
 import XCommon from './_common_/common.vue';
+import XHeader from './visitor.header.vue';
+import { host, instanceName } from '@/config';
+import * as os from '@/os';
+import { instance } from '@/instance';
+import XSigninDialog from '@/components/MkSigninDialog.vue';
+import XSignupDialog from '@/components/MkSignupDialog.vue';
+import { ColdDeviceStorage, defaultStore } from '@/store';
+import { mainRouter } from '@/router';
+import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata';
+import { i18n } from '@/i18n';
+import MkVisitorDashboard from '@/components/MkVisitorDashboard.vue';
 
-export default defineComponent({
-	components: {
-		XCommon,
-		//DesignA,
-		DesignB,
-	},
+const DESKTOP_THRESHOLD = 1100;
+
+let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
+
+provide('router', mainRouter);
+provideMetadataReceiver((info) => {
+	pageMetadata = info;
+	if (pageMetadata.value) {
+		document.title = `${pageMetadata.value.title} | ${instanceName}`;
+	}
+});
+
+const announcements = {
+	endpoint: 'announcements',
+	limit: 10,
+};
+
+const isTimelineAvailable = $ref(instance.policies?.ltlAvailable || instance.policies?.gtlAvailable);
+
+let showMenu = $ref(false);
+let isDesktop = $ref(window.innerWidth >= DESKTOP_THRESHOLD);
+let narrow = $ref(window.innerWidth < 1280);
+let meta = $ref();
+
+const keymap = $computed(() => {
+	return {
+		'd': () => {
+			if (ColdDeviceStorage.get('syncDeviceDarkMode')) return;
+			defaultStore.set('darkMode', !defaultStore.state.darkMode);
+		},
+		's': () => {
+			mainRouter.push('/search');
+		},
+	};
+});
+
+const root = $computed(() => mainRouter.currentRoute.value.name === 'index');
+
+os.api('meta', { detail: true }).then(res => {
+	meta = res;
+});
+
+function signin() {
+	os.popup(XSigninDialog, {
+		autoSet: true,
+	}, {}, 'closed');
+}
+
+function signup() {
+	os.popup(XSignupDialog, {
+		autoSet: true,
+	}, {}, 'closed');
+}
+
+onMounted(() => {
+	if (!isDesktop) {
+		window.addEventListener('resize', () => {
+			if (window.innerWidth >= DESKTOP_THRESHOLD) isDesktop = true;
+		}, { passive: true });
+	}
+});
+
+defineExpose({
+	showMenu: $$(showMenu),
 });
 </script>
+
+<style>
+.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}
+</style>
+
+<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 {
+	display: flex;
+	min-height: 100vh;
+
+	> .side {
+		position: sticky;
+		top: 0;
+		left: 0;
+		width: 500px;
+		height: 100vh;
+		background: var(--accent);
+
+		> .banner {
+			position: absolute;
+			top: 0;
+			left: 0;
+			width: 100%;
+			aspect-ratio: 1.5;
+			background-position: center;
+			background-size: cover;
+			-webkit-mask-image: linear-gradient(rgba(0, 0, 0, 1.0), transparent);
+			mask-image: linear-gradient(rgba(0, 0, 0, 1.0), transparent);
+		}
+
+		> .dashboard {
+			position: relative;
+			padding: 32px;
+			box-sizing: border-box;
+			max-height: 100%;
+			overflow: auto;
+		}
+	}
+
+	> .main {
+		flex: 1;
+		min-width: 0;
+
+		> .banner {
+		}
+
+		> .contents {
+			position: relative;
+			z-index: 1;
+
+			> .powered-by {
+				padding: 28px;
+				font-size: 14px;
+				text-align: center;
+				border-top: 1px solid var(--divider);
+
+				> small {
+					display: block;
+					margin-top: 8px;
+					opacity: 0.5;
+				}
+			}
+		}
+	}
+
+	> .menu-back {
+		position: fixed;
+		z-index: 1001;
+		top: 0;
+		left: 0;
+		width: 100vw;
+		height: 100vh;
+	}
+
+	> .menu {
+		position: fixed;
+		z-index: 1001;
+		top: 0;
+		left: 0;
+		width: 240px;
+		height: 100vh;
+		background: var(--panel);
+
+		> .link {
+			display: block;
+			padding: 16px;
+
+			> .icon {
+				margin-right: 1em;
+			}
+		}
+
+		> .divider {
+			margin: 8px auto;
+			width: calc(100% - 32px);
+			border-top: solid 0.5px var(--divider);
+		}
+
+		> .action {
+			padding: 16px;
+
+			> button {
+				display: block;
+				width: 100%;
+				padding: 10px;
+				box-sizing: border-box;
+				text-align: center;
+				border-radius: 999px;
+
+				&._button {
+					background: var(--panel);
+				}
+
+				&:first-child {
+					margin-bottom: 16px;
+				}
+			}
+		}
+	}
+}
+</style>
diff --git a/packages/frontend/src/ui/visitor/a.vue b/packages/frontend/src/ui/visitor/a.vue
deleted file mode 100644
index 476103607..000000000
--- a/packages/frontend/src/ui/visitor/a.vue
+++ /dev/null
@@ -1,263 +0,0 @@
-<template>
-<div class="mk-app">
-	<div v-if="mainRouter.currentRoute?.name === 'index'" class="banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }">
-		<div>
-			<h1 v-if="meta"><img v-if="meta.logoImageUrl" class="logo" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span></h1>
-			<div v-if="meta" class="about">
-				<!-- eslint-disable-next-line vue/no-v-html -->
-				<div class="desc" v-html="meta.description || i18n.ts.introMisskey"></div>
-			</div>
-			<div class="action">
-				<button class="_button primary" @click="signup()">{{ i18n.ts.signup }}</button>
-				<button class="_button" @click="signin()">{{ i18n.ts.login }}</button>
-			</div>
-		</div>
-	</div>
-	<div v-else class="banner-mini" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }">
-		<div>
-			<h1 v-if="meta"><img v-if="meta.logoImageUrl" class="logo" :src="meta.logoImageUrl"><span v-else class="text">{{ instanceName }}</span></h1>
-		</div>
-	</div>
-
-	<div class="main">
-		<div ref="contents" class="contents" :class="{ wallpaper }">
-			<header v-show="mainRouter.currentRoute?.name !== 'index'" ref="header" class="header">
-				<XHeader :info="pageInfo"/>
-			</header>
-			<main ref="main" style="container-type: inline-size;">
-				<RouterView/>
-			</main>
-			<div class="powered-by">
-				<b><MkA to="/">{{ host }}</MkA></b>
-				<small>Powered by <a href="https://github.com/misskey-dev/misskey" target="_blank">Misskey</a></small>
-			</div>
-		</div>
-	</div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import XHeader from './header.vue';
-import { host, instanceName } from '@/config';
-import * as os from '@/os';
-import MkButton from '@/components/MkButton.vue';
-import { defaultStore, ColdDeviceStorage } from '@/store';
-import { mainRouter } from '@/router';
-import { instance } from '@/instance';
-import { i18n } from '@/i18n';
-
-const DESKTOP_THRESHOLD = 1100;
-
-export default defineComponent({
-	components: {
-		XHeader,
-		MkButton,
-	},
-
-	data() {
-		return {
-			host,
-			instanceName,
-			pageInfo: null,
-			meta: null,
-			narrow: window.innerWidth < 1280,
-			announcements: {
-				endpoint: 'announcements',
-				limit: 10,
-			},
-			mainRouter,
-			isDesktop: window.innerWidth >= DESKTOP_THRESHOLD,
-			defaultStore,
-			instance,
-			i18n,
-		};
-	},
-
-	computed: {
-		keymap(): any {
-			return {
-				'd': () => {
-					if (ColdDeviceStorage.get('syncDeviceDarkMode')) return;
-					this.defaultStore.set('darkMode', !this.defaultStore.state.darkMode);
-				},
-				's': () => {
-					mainRouter.push('/search');
-				},
-				'h|/': this.help,
-			};
-		},
-	},
-
-	created() {
-		document.documentElement.style.overflowY = 'scroll';
-
-		os.api('meta', { detail: true }).then(meta => {
-			this.meta = meta;
-		});
-	},
-
-	mounted() {
-		if (!this.isDesktop) {
-			window.addEventListener('resize', () => {
-				if (window.innerWidth >= DESKTOP_THRESHOLD) this.isDesktop = true;
-			}, { passive: true });
-		}
-	},
-
-	methods: {
-		// @ThatOneCalculator: Are these methods even used?
-		// I can't find references to them anywhere else in the code...
-
-		// setParallax(el) {
-		// 	new simpleParallax(el);
-		// },
-
-		changePage(page) {
-			if (page == null) return;
-			// eslint-disable-next-line no-undef
-			if (page[symbols.PAGE_INFO]) {
-				// eslint-disable-next-line no-undef
-				this.pageInfo = page[symbols.PAGE_INFO];
-			}
-		},
-
-		top() {
-			window.scroll({ top: 0, behavior: 'smooth' });
-		},
-
-		help() {
-			window.open('https://misskey-hub.net/docs/keyboard-shortcut.md', '_blank');
-		},
-	},
-});
-</script>
-
-<style lang="scss" scoped>
-.mk-app {
-	min-height: 100vh;
-
-	> .banner {
-		position: relative;
-		width: 100%;
-		text-align: center;
-		background-position: center;
-		background-size: cover;
-
-		> div {
-			height: 100%;
-			background: rgba(0, 0, 0, 0.3);
-
-			* {
-				color: #fff;
-			}
-					
-			> h1 {
-				margin: 0;
-				padding: 96px 32px 0 32px;
-				text-shadow: 0 0 8px black;
-
-				> .logo {
-					vertical-align: bottom;
-					max-height: 150px;
-				}
-			}
-
-			> .about {
-				padding: 32px;
-				max-width: 580px;
-				margin: 0 auto;
-				box-sizing: border-box;
-				text-shadow: 0 0 8px black;
-			}
-
-			> .action {
-				padding-bottom: 64px;
-				
-				> button {
-					display: inline-block;
-					padding: 10px 20px;
-					box-sizing: border-box;
-					text-align: center;
-					border-radius: 999px;
-					background: var(--panel);
-					color: var(--fg);
-
-					&.primary {
-						background: var(--accent);
-						color: #fff;
-					}
-
-					&:first-child {
-						margin-right: 16px;
-					}
-				}
-			}
-		}
-	}
-
-	> .banner-mini {
-		position: relative;
-		width: 100%;
-		text-align: center;
-		background-position: center;
-		background-size: cover;
-
-		> div {
-			position: relative;
-			z-index: 1;
-			height: 100%;
-			background: rgba(0, 0, 0, 0.3);
-
-			* {
-				color: #fff !important;
-			}
-
-			> header {
-				
-			}
-					
-			> h1 {
-				margin: 0;
-				padding: 32px;
-				text-shadow: 0 0 8px black;
-
-				> .logo {
-					vertical-align: bottom;
-					max-height: 100px;
-				}
-			}
-		}
-	}
-
-	> .main {
-		> .contents {
-			position: relative;
-			z-index: 1;
-
-			> .header {
-				position: sticky;
-				top: 0;
-				left: 0;
-				z-index: 1000;
-			}
-
-			> .powered-by {
-				padding: 28px;
-				font-size: 14px;
-				text-align: center;
-				border-top: 1px solid var(--divider);
-
-				> small {
-					display: block;
-					margin-top: 8px;
-					opacity: 0.5;
-				}
-			}
-		}
-	}
-}
-</style>
-
-<style lang="scss">
-</style>
diff --git a/packages/frontend/src/ui/visitor/b.vue b/packages/frontend/src/ui/visitor/b.vue
deleted file mode 100644
index 5287a670c..000000000
--- a/packages/frontend/src/ui/visitor/b.vue
+++ /dev/null
@@ -1,266 +0,0 @@
-<template>
-<div class="mk-app">
-	<a v-if="root" href="https://github.com/misskey-dev/misskey" target="_blank" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewBox="0 0 250 250" style="fill:var(--panel); color:var(--fg); position: fixed; z-index: 10; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path></svg></a>
-
-	<div v-if="!narrow && !root" class="side">
-		<XKanban class="kanban" full/>
-	</div>
-
-	<div class="main">
-		<XKanban v-if="narrow && !root" class="banner" :powered-by="root"/>
-
-		<div class="contents">
-			<XHeader v-if="!root" class="header"/>
-			<main v-if="!root" style="container-type: inline-size;">
-				<RouterView/>
-			</main>
-			<main v-else>
-				<RouterView/>
-			</main>
-			<div v-if="!root" class="powered-by">
-				<b><MkA to="/">{{ host }}</MkA></b>
-				<small>Powered by <a href="https://github.com/misskey-dev/misskey" target="_blank">Misskey</a></small>
-			</div>
-		</div>
-	</div>
-
-	<Transition :name="'tray-back'">
-		<div
-			v-if="showMenu"
-			class="menu-back _modalBg"
-			@click="showMenu = false"
-			@touchstart.passive="showMenu = false"
-		></div>
-	</Transition>
-
-	<Transition :name="'tray'">
-		<div v-if="showMenu" class="menu">
-			<MkA to="/" class="link" active-class="active"><i class="ti ti-home icon"></i>{{ i18n.ts.home }}</MkA>
-			<MkA v-if="isTimelineAvailable" to="/timeline" class="link" active-class="active"><i class="ti ti-message icon"></i>{{ i18n.ts.timeline }}</MkA>
-			<MkA to="/explore" class="link" active-class="active"><i class="ti ti-hash icon"></i>{{ i18n.ts.explore }}</MkA>
-			<MkA to="/announcements" class="link" active-class="active"><i class="ti ti-speakerphone icon"></i>{{ i18n.ts.announcements }}</MkA>
-			<MkA to="/channels" class="link" active-class="active"><i class="ti ti-device-tv icon"></i>{{ i18n.ts.channel }}</MkA>
-			<div class="divider"></div>
-			<MkA to="/pages" class="link" active-class="active"><i class="ti ti-news icon"></i>{{ i18n.ts.pages }}</MkA>
-			<MkA to="/play" class="link" active-class="active"><i class="ti ti-player-play icon"></i>Play</MkA>
-			<MkA to="/gallery" class="link" active-class="active"><i class="ti ti-icons icon"></i>{{ i18n.ts.gallery }}</MkA>
-			<div class="action">
-				<button class="_buttonPrimary" @click="signup()">{{ i18n.ts.signup }}</button>
-				<button class="_button" @click="signin()">{{ i18n.ts.login }}</button>
-			</div>
-		</div>
-	</Transition>
-</div>
-</template>
-
-<script lang="ts" setup>
-import { ComputedRef, onMounted, provide } from 'vue';
-import XHeader from './header.vue';
-import XKanban from './kanban.vue';
-import { host, instanceName } from '@/config';
-import * as os from '@/os';
-import { instance } from '@/instance';
-import XSigninDialog from '@/components/MkSigninDialog.vue';
-import XSignupDialog from '@/components/MkSignupDialog.vue';
-import { ColdDeviceStorage, defaultStore } from '@/store';
-import { mainRouter } from '@/router';
-import { PageMetadata, provideMetadataReceiver } from '@/scripts/page-metadata';
-import { i18n } from '@/i18n';
-
-const DESKTOP_THRESHOLD = 1100;
-
-let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
-
-provide('router', mainRouter);
-provideMetadataReceiver((info) => {
-	pageMetadata = info;
-	if (pageMetadata.value) {
-		document.title = `${pageMetadata.value.title} | ${instanceName}`;
-	}
-});
-
-const announcements = {
-	endpoint: 'announcements',
-	limit: 10,
-};
-
-const isTimelineAvailable = $ref(instance.policies?.ltlAvailable || instance.policies?.gtlAvailable);
-
-let showMenu = $ref(false);
-let isDesktop = $ref(window.innerWidth >= DESKTOP_THRESHOLD);
-let narrow = $ref(window.innerWidth < 1280);
-let meta = $ref();
-
-const keymap = $computed(() => {
-	return {
-		'd': () => {
-			if (ColdDeviceStorage.get('syncDeviceDarkMode')) return;
-			defaultStore.set('darkMode', !defaultStore.state.darkMode);
-		},
-		's': () => {
-			mainRouter.push('/search');
-		},
-	};
-});
-
-const root = $computed(() => mainRouter.currentRoute.value.name === 'index');
-
-os.api('meta', { detail: true }).then(res => {
-	meta = res;
-});
-
-function signin() {
-	os.popup(XSigninDialog, {
-		autoSet: true,
-	}, {}, 'closed');
-}
-
-function signup() {
-	os.popup(XSignupDialog, {
-		autoSet: true,
-	}, {}, 'closed');
-}
-
-onMounted(() => {
-	if (!isDesktop) {
-		window.addEventListener('resize', () => {
-			if (window.innerWidth >= DESKTOP_THRESHOLD) isDesktop = true;
-		}, { passive: true });
-	}
-});
-
-defineExpose({
-	showMenu: $$(showMenu),
-});
-</script>
-
-<style>
-.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}
-</style>
-
-<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 {
-	display: flex;
-	min-height: 100vh;
-	background-position: center;
-	background-size: cover;
-	background-attachment: fixed;
-
-	> .side {
-		width: 500px;
-		height: 100vh;
-
-		> .kanban {
-			position: fixed;
-			top: 0;
-			left: 0;
-			width: 500px;
-			height: 100vh;
-			overflow: auto;
-		}
-	}
-
-	> .main {
-		flex: 1;
-		min-width: 0;
-
-		> .banner {
-		}
-
-		> .contents {
-			position: relative;
-			z-index: 1;
-
-			> .powered-by {
-				padding: 28px;
-				font-size: 14px;
-				text-align: center;
-				border-top: 1px solid var(--divider);
-
-				> small {
-					display: block;
-					margin-top: 8px;
-					opacity: 0.5;
-				}
-			}
-		}
-	}
-
-	> .menu-back {
-		position: fixed;
-		z-index: 1001;
-		top: 0;
-		left: 0;
-		width: 100vw;
-		height: 100vh;
-	}
-
-	> .menu {
-		position: fixed;
-		z-index: 1001;
-		top: 0;
-		left: 0;
-		width: 240px;
-		height: 100vh;
-		background: var(--panel);
-
-		> .link {
-			display: block;
-			padding: 16px;
-
-			> .icon {
-				margin-right: 1em;
-			}
-		}
-
-		> .divider {
-			margin: 8px auto;
-			width: calc(100% - 32px);
-			border-top: solid 0.5px var(--divider);
-		}
-
-		> .action {
-			padding: 16px;
-
-			> button {
-				display: block;
-				width: 100%;
-				padding: 10px;
-				box-sizing: border-box;
-				text-align: center;
-				border-radius: 999px;
-
-				&._button {
-					background: var(--panel);
-				}
-
-				&:first-child {
-					margin-bottom: 16px;
-				}
-			}
-		}
-	}
-}
-</style>
diff --git a/packages/frontend/src/ui/visitor/kanban.vue b/packages/frontend/src/ui/visitor/kanban.vue
deleted file mode 100644
index ce7fcfe94..000000000
--- a/packages/frontend/src/ui/visitor/kanban.vue
+++ /dev/null
@@ -1,261 +0,0 @@
-<!-- eslint-disable vue/no-v-html -->
-<template>
-<div class="rwqkcmrc" :style="{ backgroundImage: transparent ? 'none' : `url(${ instance.backgroundImageUrl })` }">
-	<div class="back" :class="{ transparent }"></div>
-	<div class="contents">
-		<div class="wrapper">
-			<h1 v-if="meta" :class="{ full }">
-				<MkA to="/" class="link"><img v-if="meta.logoImageUrl" class="logo" :src="meta.logoImageUrl" alt="logo"><span v-else class="text">{{ instanceName }}</span></MkA>
-			</h1>
-			<template v-if="full">
-				<div v-if="meta" class="about">
-					<div class="desc" v-html="meta.description || i18n.ts.introMisskey"></div>
-				</div>
-				<div class="action">
-					<button class="_buttonPrimary" @click="signup()">{{ i18n.ts.signup }}</button>
-					<button class="_button" @click="signin()">{{ i18n.ts.login }}</button>
-				</div>
-				<div class="announcements panel">
-					<header>{{ i18n.ts.announcements }}</header>
-					<MkPagination v-slot="{items}" :pagination="announcements" class="list">
-						<section v-for="announcement in items" :key="announcement.id" class="item">
-							<div class="title">{{ announcement.title }}</div>
-							<div class="content">
-								<Mfm :text="announcement.text"/>
-								<img v-if="announcement.imageUrl" :src="announcement.imageUrl" alt="announcement image"/>
-							</div>
-						</section>
-					</MkPagination>
-				</div>
-				<div v-if="poweredBy" class="powered-by">
-					<b><MkA to="/">{{ host }}</MkA></b>
-					<small>Powered by <a href="https://github.com/misskey-dev/misskey" target="_blank">Misskey</a></small>
-				</div>
-			</template>
-		</div>
-	</div>
-</div>
-</template>
-
-<script lang="ts">
-import { defineComponent } from 'vue';
-import { host, instanceName } from '@/config';
-import * as os from '@/os';
-import MkPagination from '@/components/MkPagination.vue';
-import XSigninDialog from '@/components/MkSigninDialog.vue';
-import XSignupDialog from '@/components/MkSignupDialog.vue';
-import MkButton from '@/components/MkButton.vue';
-import { instance } from '@/instance';
-import { i18n } from '@/i18n';
-
-export default defineComponent({
-	components: {
-		MkPagination,
-		MkButton,
-	},
-
-	props: {
-		full: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-		transparent: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-		poweredBy: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-	},
-
-	data() {
-		return {
-			host,
-			instanceName,
-			pageInfo: null,
-			meta: null,
-			narrow: window.innerWidth < 1280,
-			announcements: {
-				endpoint: 'announcements',
-				limit: 10,
-			},
-			instance,
-			i18n,
-		};
-	},
-
-	created() {
-		os.api('meta', { detail: true }).then(meta => {
-			this.meta = meta;
-		});
-	},
-
-	methods: {
-		signin() {
-			os.popup(XSigninDialog, {
-				autoSet: true,
-			}, {}, 'closed');
-		},
-
-		signup() {
-			os.popup(XSignupDialog, {
-				autoSet: true,
-			}, {}, 'closed');
-		},
-	},
-});
-</script>
-
-<style lang="scss" scoped>
-.rwqkcmrc {
-	position: relative;
-	text-align: center;
-	background-position: center;
-	background-size: cover;
-	// TODO: パララックスにしたい
-
-	> .back {
-		position: absolute;
-		top: 0;
-		left: 0;
-		width: 100%;
-		height: 100%;
-		background: rgba(0, 0, 0, 0.3);
-
-		&.transparent {
-			-webkit-backdrop-filter: var(--blur, blur(12px));
-			backdrop-filter: var(--blur, blur(12px));
-		}
-	}
-
-	> .contents {
-		position: relative;
-		z-index: 1;
-		height: inherit;
-		overflow: auto;
-
-		> .wrapper {
-			max-width: 380px;
-			padding: 0 16px;
-			box-sizing: border-box;
-			margin: 0 auto;
-
-			> .panel {
-				-webkit-backdrop-filter: var(--blur, blur(8px));
-				backdrop-filter: var(--blur, blur(8px));
-				background: rgba(0, 0, 0, 0.5);
-				border-radius: var(--radius);
-
-				&, * {
-					color: #fff !important;
-				}
-			}
-
-			> h1 {
-				display: block;
-				margin: 0;
-				padding: 32px 0 32px 0;
-				color: #fff;
-
-				&.full {
-					padding: 64px 0 0 0;
-
-					> .link {
-						> ::v-deep(.logo) {
-							max-height: 130px;
-						}
-					}
-				}
-
-				> .link {
-					display: block;
-
-					> ::v-deep(.logo) {
-						vertical-align: bottom;
-						max-height: 100px;
-					}
-				}
-			}
-
-			> .about {
-				display: block;
-				margin: 24px 0;
-				text-align: center;
-				box-sizing: border-box;
-				text-shadow: 0 0 8px black;
-				color: #fff;
-			}
-
-			> .action {
-				> button {
-					display: block;
-					width: 100%;
-					padding: 10px;
-					box-sizing: border-box;
-					text-align: center;
-					border-radius: 999px;
-
-					&._button {
-						background: var(--panel);
-					}
-
-					&:first-child {
-						margin-bottom: 16px;
-					}
-				}
-			}
-
-			> .announcements {
-				margin: 32px 0;
-				text-align: left;
-
-				> header {
-					padding: 12px 16px;
-					border-bottom: solid 1px rgba(255, 255, 255, 0.5);
-				}
-
-				> .list {
-					max-height: 300px;
-					overflow: auto;
-
-					> .item {
-						padding: 12px 16px;
-
-						& + .item {
-							border-top: solid 1px rgba(255, 255, 255, 0.5);
-						}
-
-						> .title {
-							font-weight: bold;
-						}
-
-						> .content {
-							> img {
-								max-width: 100%;
-							}
-						}
-					}
-				}
-			}
-
-			> .powered-by {
-				padding: 28px;
-				font-size: 14px;
-				text-align: center;
-				border-top: 1px solid rgba(255, 255, 255, 0.5);
-				color: #fff;
-
-				> small {
-					display: block;
-					margin-top: 8px;
-					opacity: 0.5;
-				}
-			}
-		}
-	}
-}
-</style>