diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue
index 45aba1d24ba81f57673ef31f6c0b6102c8706618..54afae952c548ee245777c0afb7b19704b6875e1 100644
--- a/packages/frontend/src/components/MkNote.vue
+++ b/packages/frontend/src/components/MkNote.vue
@@ -4,27 +4,26 @@
 	v-show="!isDeleted"
 	ref="el"
 	v-hotkey="keymap"
-	class="tkcbzcuz"
+	:class="[$style.root, { [$style.isRenote]: isRenote }]"
 	:tabindex="!isDeleted ? '-1' : null"
-	:class="{ renote: isRenote }"
 >
-	<MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" class="reply-to"/>
-	<div v-if="pinned" class="info"><i class="ti ti-pin"></i> {{ i18n.ts.pinnedNote }}</div>
-	<div v-if="appearNote._prId_" class="info"><i class="fas fa-bullhorn"></i> {{ i18n.ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.ts.hideThisNote }} <i class="ti ti-x"></i></button></div>
-	<div v-if="appearNote._featuredId_" class="info"><i class="ti ti-bolt"></i> {{ i18n.ts.featured }}</div>
-	<div v-if="isRenote" class="renote">
-		<MkAvatar v-once class="avatar" :user="note.user"/>
-		<i class="ti ti-repeat"></i>
-		<I18n :src="i18n.ts.renotedBy" tag="span">
+	<MkNoteSub v-if="appearNote.reply" :note="appearNote.reply" :class="$style.replyTo"/>
+	<div v-if="pinned" :class="$style.tip"><i class="ti ti-pin"></i> {{ i18n.ts.pinnedNote }}</div>
+	<!--<div v-if="appearNote._prId_" class="tip"><i class="fas fa-bullhorn"></i> {{ i18n.ts.promotion }}<button class="_textButton hide" @click="readPromo()">{{ i18n.ts.hideThisNote }} <i class="ti ti-x"></i></button></div>-->
+	<!--<div v-if="appearNote._featuredId_" class="tip"><i class="ti ti-bolt"></i> {{ i18n.ts.featured }}</div>-->
+	<div v-if="isRenote" :class="$style.renote">
+		<MkAvatar v-once :class="$style.renoteAvatar" :user="note.user"/>
+		<i class="ti ti-repeat" style="margin-right: 4px;"></i>
+		<I18n :src="i18n.ts.renotedBy" tag="span" :class="$style.renoteText">
 			<template #user>
-				<MkA v-user-preview="note.userId" class="name" :to="userPage(note.user)">
+				<MkA v-user-preview="note.userId" :class="$style.renoteUserName" :to="userPage(note.user)">
 					<MkUserName :user="note.user"/>
 				</MkA>
 			</template>
 		</I18n>
-		<div class="info">
-			<button ref="renoteTime" class="_button time" @click="showRenoteMenu()">
-				<i v-if="isMyRenote" class="ti ti-dots dropdownIcon"></i>
+		<div :class="$style.renoteInfo">
+			<button ref="renoteTime" :class="$style.renoteTime" class="_button" @click="showRenoteMenu()">
+				<i v-if="isMyRenote" class="ti ti-dots" :class="$style.renoteMenu"></i>
 				<MkTime :time="note.createdAt"/>
 			</button>
 			<span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[note.visibility]">
@@ -35,80 +34,80 @@
 			<span v-if="note.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['localOnly']"><i class="ti ti-world-off"></i></span>
 		</div>
 	</div>
-	<article class="article" @contextmenu.stop="onContextmenu">
-		<MkAvatar v-once class="avatar" :user="appearNote.user"/>
-		<div class="main">
-			<MkNoteHeader class="header" :note="appearNote" :mini="true"/>
-			<MkInstanceTicker v-if="showTicker" class="ticker" :instance="appearNote.user.instance"/>
-			<div class="body">
-				<p v-if="appearNote.cw != null" class="cw">
-					<Mfm v-if="appearNote.cw != ''" class="text" :text="appearNote.cw" :author="appearNote.user" :i="$i"/>
+	<article :class="$style.article" @contextmenu.stop="onContextmenu">
+		<MkAvatar v-once :class="$style.avatar" :user="appearNote.user"/>
+		<div :class="$style.main">
+			<MkNoteHeader :class="$style.header" :note="appearNote" :mini="true"/>
+			<MkInstanceTicker v-if="showTicker" :class="$style.ticker" :instance="appearNote.user.instance"/>
+			<div :class="$style.body">
+				<p v-if="appearNote.cw != null" :class="$style.cw">
+					<Mfm v-if="appearNote.cw != ''" :class="$style.cwText" :text="appearNote.cw" :author="appearNote.user" :i="$i"/>
 					<MkCwButton v-model="showContent" :note="appearNote"/>
 				</p>
-				<div v-show="appearNote.cw == null || showContent" class="content" :class="{ collapsed, isLong }">
-					<div class="text">
+				<div v-show="appearNote.cw == null || showContent" :class="[$style.content, { [$style.contentCollapsed]: collapsed, [$style.contentIsLong]: isLong }]">
+					<div :class="$style.text">
 						<span v-if="appearNote.isHidden" style="opacity: 0.5">({{ i18n.ts.private }})</span>
-						<MkA v-if="appearNote.replyId" class="reply" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
+						<MkA v-if="appearNote.replyId" :class="$style.replyIcon" :to="`/notes/${appearNote.replyId}`"><i class="ti ti-arrow-back-up"></i></MkA>
 						<Mfm v-if="appearNote.text" v-once :text="appearNote.text" :author="appearNote.user" :i="$i"/>
-						<a v-if="appearNote.renote != null" class="rp">RN:</a>
-						<div v-if="translating || translation" class="translation">
+						<div v-if="translating || translation" :class="$style.translation">
 							<MkLoading v-if="translating" mini/>
-							<div v-else class="translated">
+							<div v-else :class="$style.translated">
 								<b>{{ $t('translatedFrom', { x: translation.sourceLang }) }}: </b>
 								<Mfm :text="translation.text" :author="appearNote.user" :i="$i"/>
 							</div>
 						</div>
 					</div>
-					<div v-if="appearNote.files.length > 0" class="files">
+					<div v-if="appearNote.files.length > 0" :class="$style.files">
 						<MkMediaList :media-list="appearNote.files"/>
 					</div>
-					<MkPoll v-if="appearNote.poll" ref="pollViewer" :note="appearNote" class="poll"/>
-					<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" class="url-preview"/>
-					<div v-if="appearNote.renote" class="renote"><MkNoteSimple :note="appearNote.renote" class="note"/></div>
-					<button v-if="isLong && collapsed" class="fade _button" @click="collapsed = false">
-						<span>{{ i18n.ts.showMore }}</span>
+					<MkPoll v-if="appearNote.poll" ref="pollViewer" :note="appearNote" :class="$style.poll"/>
+					<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" :class="$style.urlPreview"/>
+					<div v-if="appearNote.renote" :class="$style.quote"><MkNoteSimple :note="appearNote.renote" :class="$style.quoteNote"/></div>
+					<button v-if="isLong && collapsed" :class="$style.collapsed" class="_button" @click="collapsed = false">
+						<span :class="$style.collapsedLabel">{{ i18n.ts.showMore }}</span>
 					</button>
-					<button v-else-if="isLong && !collapsed" class="showLess _button" @click="collapsed = true">
-						<span>{{ i18n.ts.showLess }}</span>
+					<button v-else-if="isLong && !collapsed" :class="$style.showLess" class="_button" @click="collapsed = true">
+						<span :class="$style.showLessLabel">{{ i18n.ts.showLess }}</span>
 					</button>
 				</div>
-				<MkA v-if="appearNote.channel && !inChannel" class="channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA>
+				<MkA v-if="appearNote.channel && !inChannel" :class="$style.channel" :to="`/channels/${appearNote.channel.id}`"><i class="ti ti-device-tv"></i> {{ appearNote.channel.name }}</MkA>
 			</div>
-			<footer class="footer">
+			<footer :class="$style.footer">
 				<MkReactionsViewer ref="reactionsViewer" :note="appearNote"/>
-				<button class="button _button" @click="reply()">
+				<button :class="$style.footerButton" class="_button" @click="reply()">
 					<i class="ti ti-arrow-back-up"></i>
-					<p v-if="appearNote.repliesCount > 0" class="count">{{ appearNote.repliesCount }}</p>
+					<p v-if="appearNote.repliesCount > 0" :class="$style.footerButtonCount">{{ appearNote.repliesCount }}</p>
 				</button>
 				<button
 					v-if="canRenote"
 					ref="renoteButton"
-					class="button _button"
+					:class="$style.footerButton"
+					class="_button"
 					@mousedown="renote()"
 				>
 					<i class="ti ti-repeat"></i>
-					<p v-if="appearNote.renoteCount > 0" class="count">{{ appearNote.renoteCount }}</p>
+					<p v-if="appearNote.renoteCount > 0" :class="$style.footerButtonCount">{{ appearNote.renoteCount }}</p>
 				</button>
-				<button v-else class="button _button" disabled>
+				<button v-else :class="$style.footerButton" class="_button" disabled>
 					<i class="ti ti-ban"></i>
 				</button>
-				<button v-if="appearNote.myReaction == null" ref="reactButton" class="button _button" @mousedown="react()">
+				<button v-if="appearNote.myReaction == null" ref="reactButton" :class="$style.footerButton" class="_button" @mousedown="react()">
 					<i class="ti ti-plus"></i>
 				</button>
-				<button v-if="appearNote.myReaction != null" ref="reactButton" class="button _button reacted" @click="undoReact(appearNote)">
+				<button v-if="appearNote.myReaction != null" ref="reactButton" :class="$style.footerButton" class="_button" @click="undoReact(appearNote)">
 					<i class="ti ti-minus"></i>
 				</button>
-				<button ref="menuButton" class="button _button" @mousedown="menu()">
+				<button ref="menuButton" :class="$style.footerButton" class="_button" @mousedown="menu()">
 					<i class="ti ti-dots"></i>
 				</button>
 			</footer>
 		</div>
 	</article>
 </div>
-<div v-else class="muted" @click="muted = false">
+<div v-else :class="$style.muted" @click="muted = false">
 	<I18n :src="i18n.ts.userSaysSomething" tag="small">
 		<template #name>
-			<MkA v-user-preview="appearNote.userId" class="name" :to="userPage(appearNote.user)">
+			<MkA v-user-preview="appearNote.userId" :to="userPage(appearNote.user)">
 				<MkUserName :user="appearNote.user"/>
 			</MkA>
 		</template>
@@ -349,8 +348,8 @@ function readPromo() {
 }
 </script>
 
-<style lang="scss" scoped>
-.tkcbzcuz {
+<style lang="scss" module>
+.root {
 	position: relative;
 	transition: box-shadow 0.1s ease;
 	font-size: 1.05em;
@@ -387,322 +386,273 @@ function readPromo() {
 		}
 	}
 
-	&:hover > .article > .main > .footer > .button {
+	&:hover > .article > .main > .footer > .footerButton {
 		opacity: 1;
 	}
+}
 
-	> .info {
-		display: flex;
-		align-items: center;
-		padding: 16px 32px 8px 32px;
-		line-height: 24px;
-		font-size: 90%;
-		white-space: pre;
-		color: #d28a3f;
-
-		> i {
-			margin-right: 4px;
-		}
+.tip {
+	display: flex;
+	align-items: center;
+	padding: 16px 32px 8px 32px;
+	line-height: 24px;
+	font-size: 90%;
+	white-space: pre;
+	color: #d28a3f;
+}
 
-		> .hide {
-			margin-left: auto;
-			color: inherit;
-		}
-	}
+.tip + .article {
+	padding-top: 8px;
+}
 
-	> .info + .article {
-		padding-top: 8px;
-	}
+.replyTo {
+	opacity: 0.7;
+	padding-bottom: 0;
+}
 
-	> .reply-to {
-		opacity: 0.7;
-		padding-bottom: 0;
-	}
+.renote {
+	display: flex;
+	align-items: center;
+	padding: 16px 32px 8px 32px;
+	line-height: 28px;
+	white-space: pre;
+	color: var(--renote);
+}
 
-	> .renote {
-		display: flex;
-		align-items: center;
-		padding: 16px 32px 8px 32px;
-		line-height: 28px;
-		white-space: pre;
-		color: var(--renote);
-
-		> .avatar {
-			flex-shrink: 0;
-			display: inline-block;
-			width: 28px;
-			height: 28px;
-			margin: 0 8px 0 0;
-			border-radius: 6px;
-		}
+.renoteAvatar {
+	flex-shrink: 0;
+	display: inline-block;
+	width: 28px;
+	height: 28px;
+	margin: 0 8px 0 0;
+	border-radius: 6px;
+}
 
-		> i {
-			margin-right: 4px;
-		}
+.renoteText {
+	overflow: hidden;
+	flex-shrink: 1;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+}
 
-		> span {
-			overflow: hidden;
-			flex-shrink: 1;
-			text-overflow: ellipsis;
-			white-space: nowrap;
+.renoteUserName {
+	font-weight: bold;
+}
 
-			> .name {
-				font-weight: bold;
-			}
-		}
+.renoteInfo {
+	margin-left: auto;
+	font-size: 0.9em;
+}
 
-		> .info {
-			margin-left: auto;
-			font-size: 0.9em;
+.renoteTime {
+	flex-shrink: 0;
+	color: inherit;
+}
 
-			> .time {
-				flex-shrink: 0;
-				color: inherit;
+.renoteMenu {
+	margin-right: 4px;
+}
 
-				> .dropdownIcon {
-					margin-right: 4px;
-				}
-			}
-		}
-	}
+.renoteInfo + .article {
+	padding-top: 8px;
+}
+
+.article {
+	display: flex;
+	padding: 28px 32px 18px;
+}
+
+.avatar {
+	flex-shrink: 0;
+	display: block;
+	margin: 0 14px 8px 0;
+	width: 58px;
+	height: 58px;
+	position: sticky;
+	top: calc(22px + var(--stickyTop, 0px));
+	left: 0;
+}
+
+.main {
+	flex: 1;
+	min-width: 0;
+}
+
+.body {
+	container-type: inline-size;
+}
+
+.cw {
+	cursor: default;
+	display: block;
+	margin: 0;
+	padding: 0;
+	overflow-wrap: break-word;
+}
+
+.cwText {
+	margin-right: 8px;
+}
+
+.content {
+}
+
+.contentIsLong {
+}
+
+.showLess {
+	width: 100%;
+	margin-top: 1em;
+	position: sticky;
+	bottom: 1em;
+}
+
+.howLessLabel {
+	display: inline-block;
+	background: var(--popup);
+	padding: 6px 10px;
+	font-size: 0.8em;
+	border-radius: 999px;
+	box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
+}
 
-	> .renote + .article {
-		padding-top: 8px;
+.contentCollapsed {
+	position: relative;
+	max-height: 9em;
+	overflow: clip;
+}
+
+.collapsed {
+	display: block;
+	position: absolute;
+	bottom: 0;
+	left: 0;
+	width: 100%;
+	height: 64px;
+	background: linear-gradient(0deg, var(--panel), var(--X15));
+
+	&:hover > .collapsedLabel {
+		background: var(--panelHighlight);
 	}
+}
 
-	> .article {
-		display: flex;
-		padding: 28px 32px 18px;
+.collapsedLabel {
+	display: inline-block;
+	background: var(--panel);
+	padding: 6px 10px;
+	font-size: 0.8em;
+	border-radius: 999px;
+	box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
+}
 
-		> .avatar {
-			flex-shrink: 0;
-			display: block;
-			margin: 0 14px 8px 0;
-			width: 58px;
-			height: 58px;
-			position: sticky;
-			top: calc(22px + var(--stickyTop, 0px));
-			left: 0;
-		}
+.text {
+	overflow-wrap: break-word;
+}
 
-		> .main {
-			flex: 1;
-			min-width: 0;
-
-			> .body {
-				container-type: inline-size;
-
-				> .cw {
-					cursor: default;
-					display: block;
-					margin: 0;
-					padding: 0;
-					overflow-wrap: break-word;
-
-					> .text {
-						margin-right: 8px;
-					}
-				}
-
-				> .content {
-					&.isLong {
-						> .showLess {
-							width: 100%;
-							margin-top: 1em;
-							position: sticky;
-							bottom: 1em;
-
-							> span {
-								display: inline-block;
-								background: var(--popup);
-								padding: 6px 10px;
-								font-size: 0.8em;
-								border-radius: 999px;
-								box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
-							}
-						}
-					}
-
-					&.collapsed {
-						position: relative;
-						max-height: 9em;
-						overflow: clip;
-
-						> .fade {
-							display: block;
-							position: absolute;
-							bottom: 0;
-							left: 0;
-							width: 100%;
-							height: 64px;
-							background: linear-gradient(0deg, var(--panel), var(--X15));
-
-							> span {
-								display: inline-block;
-								background: var(--panel);
-								padding: 6px 10px;
-								font-size: 0.8em;
-								border-radius: 999px;
-								box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
-							}
-
-							&:hover {
-								> span {
-									background: var(--panelHighlight);
-								}
-							}
-						}
-					}
-
-					> .text {
-						overflow-wrap: break-word;
-
-						> .reply {
-							color: var(--accent);
-							margin-right: 0.5em;
-						}
-
-						> .rp {
-							margin-left: 4px;
-							font-style: oblique;
-							color: var(--renote);
-						}
-
-						> .translation {
-							border: solid 0.5px var(--divider);
-							border-radius: var(--radius);
-							padding: 12px;
-							margin-top: 8px;
-						}
-					}
-
-					> .url-preview {
-						margin-top: 8px;
-					}
-
-					> .poll {
-						font-size: 80%;
-					}
-
-					> .renote {
-						padding: 8px 0;
-
-						> .note {
-							padding: 16px;
-							border: dashed 1px var(--renote);
-							border-radius: 8px;
-						}
-					}
-				}
-
-				> .channel {
-					opacity: 0.7;
-					font-size: 80%;
-				}
-			}
-
-			> .footer {
-				> .button {
-					margin: 0;
-					padding: 8px;
-					opacity: 0.7;
-
-					&:not(:last-child) {
-						margin-right: 28px;
-					}
-
-					&:hover {
-						color: var(--fgHighlighted);
-					}
-
-					> .count {
-						display: inline;
-						margin: 0 0 0 8px;
-						opacity: 0.7;
-					}
-
-					&.reacted {
-						color: var(--accent);
-					}
-				}
-			}
-		}
+.replyIcon {
+	color: var(--accent);
+	margin-right: 0.5em;
+}
+
+.translation {
+	border: solid 0.5px var(--divider);
+	border-radius: var(--radius);
+	padding: 12px;
+	margin-top: 8px;
+}
+
+.urlPreview {
+	margin-top: 8px;
+}
+
+.poll {
+	font-size: 80%;
+}
+
+.quote {
+	padding: 8px 0;
+}
+
+.quoteNote {
+	padding: 16px;
+	border: dashed 1px var(--renote);
+	border-radius: 8px;
+}
+
+.channel {
+	opacity: 0.7;
+	font-size: 80%;
+}
+
+.footerButton {
+	margin: 0;
+	padding: 8px;
+	opacity: 0.7;
+
+	&:not(:last-child) {
+		margin-right: 28px;
 	}
 
-	> .reply {
-		border-top: solid 0.5px var(--divider);
+	&:hover {
+		color: var(--fgHighlighted);
 	}
 }
 
+.footerButtonCount {
+	display: inline;
+	margin: 0 0 0 8px;
+	opacity: 0.7;
+}
+
 @container (max-width: 500px) {
-	.tkcbzcuz {
+	.root {
 		font-size: 0.9em;
+	}
 
-		> .article {
-			> .avatar {
-				width: 50px;
-				height: 50px;
-			}
-		}
+	.avatar {
+		width: 50px;
+		height: 50px;
 	}
 }
 
 @container (max-width: 450px) {
-	.tkcbzcuz {
-		> .renote {
-			padding: 8px 16px 0 16px;
-		}
+	.renote {
+		padding: 8px 16px 0 16px;
+	}
 
-		> .info {
-			padding: 8px 16px 0 16px;
-		}
+	.tip {
+		padding: 8px 16px 0 16px;
+	}
 
-		> .article {
-			padding: 14px 16px 9px;
+	.article {
+		padding: 14px 16px 9px;
+	}
 
-			> .avatar {
-				margin: 0 10px 8px 0;
-				width: 46px;
-				height: 46px;
-				top: calc(14px + var(--stickyTop, 0px));
-			}
-		}
+	.avatar {
+		margin: 0 10px 8px 0;
+		width: 46px;
+		height: 46px;
+		top: calc(14px + var(--stickyTop, 0px));
 	}
 }
 
 @container (max-width: 350px) {
-	.tkcbzcuz {
-		> .article {
-			> .main {
-				> .footer {
-					> .button {
-						&:not(:last-child) {
-							margin-right: 18px;
-						}
-					}
-				}
-			}
+	.footerButton {
+		&:not(:last-child) {
+			margin-right: 18px;
 		}
 	}
 }
 
 @container (max-width: 300px) {
-	.tkcbzcuz {
-		> .article {
-			> .avatar {
-				width: 44px;
-				height: 44px;
-			}
-
-			> .main {
-				> .footer {
-					> .button {
-						&:not(:last-child) {
-							margin-right: 12px;
-						}
-					}
-				}
-			}
+	.avatar {
+		width: 44px;
+		height: 44px;
+	}
+
+	.footerButton {
+		&:not(:last-child) {
+			margin-right: 12px;
 		}
 	}
 }
diff --git a/packages/frontend/src/components/MkNoteHeader.vue b/packages/frontend/src/components/MkNoteHeader.vue
index 38bd33ff7480bccf0547b7ff6e2b22399f5f6f3e..8771168a423ac358fb95fb8c3ab1e14703820776 100644
--- a/packages/frontend/src/components/MkNoteHeader.vue
+++ b/packages/frontend/src/components/MkNoteHeader.vue
@@ -1,12 +1,12 @@
 <template>
-<header class="kkwtjztg">
-	<MkA v-once v-user-preview="note.user.id" class="name" :to="userPage(note.user)">
+<header :class="$style.root">
+	<MkA v-once v-user-preview="note.user.id" :class="$style.name" :to="userPage(note.user)">
 		<MkUserName :user="note.user"/>
 	</MkA>
-	<div v-if="note.user.isBot" class="is-bot">bot</div>
-	<div class="username"><MkAcct :user="note.user"/></div>
-	<div class="info">
-		<MkA class="created-at" :to="notePage(note)">
+	<div v-if="note.user.isBot" :class="$style.isBot">bot</div>
+	<div :class="$style.username"><MkAcct :user="note.user"/></div>
+	<div :class="$style.info">
+		<MkA :to="notePage(note)">
 			<MkTime :time="note.createdAt"/>
 		</MkA>
 		<span v-if="note.visibility !== 'public'" style="margin-left: 0.5em;" :title="i18n.ts._visibility[note.visibility]">
@@ -32,49 +32,49 @@ defineProps<{
 }>();
 </script>
 
-<style lang="scss" scoped>
-.kkwtjztg {
+<style lang="scss" module>
+.root {
 	display: flex;
 	align-items: baseline;
 	white-space: nowrap;
+}
 
-	> .name {
-		flex-shrink: 1;
-		display: block;
-		margin: 0 .5em 0 0;
-		padding: 0;
-		overflow: hidden;
-		font-size: 1em;
-		font-weight: bold;
-		text-decoration: none;
-		text-overflow: ellipsis;
+.name {
+	flex-shrink: 1;
+	display: block;
+	margin: 0 .5em 0 0;
+	padding: 0;
+	overflow: hidden;
+	font-size: 1em;
+	font-weight: bold;
+	text-decoration: none;
+	text-overflow: ellipsis;
 
-		&:hover {
-			text-decoration: underline;
-		}
+	&:hover {
+		text-decoration: underline;
 	}
+}
 
-	> .is-bot {
-		flex-shrink: 0;
-		align-self: center;
-		margin: 0 .5em 0 0;
-		padding: 1px 6px;
-		font-size: 80%;
-		border: solid 0.5px var(--divider);
-		border-radius: 3px;
-	}
+.isBot {
+	flex-shrink: 0;
+	align-self: center;
+	margin: 0 .5em 0 0;
+	padding: 1px 6px;
+	font-size: 80%;
+	border: solid 0.5px var(--divider);
+	border-radius: 3px;
+}
 
-	> .username {
-		flex-shrink: 9999999;
-		margin: 0 .5em 0 0;
-		overflow: hidden;
-		text-overflow: ellipsis;
-	}
+.username {
+	flex-shrink: 9999999;
+	margin: 0 .5em 0 0;
+	overflow: hidden;
+	text-overflow: ellipsis;
+}
 
-	> .info {
-		flex-shrink: 0;
-		margin-left: auto;
-		font-size: 0.9em;
-	}
+.info {
+	flex-shrink: 0;
+	margin-left: auto;
+	font-size: 0.9em;
 }
 </style>
diff --git a/packages/frontend/src/components/global/MkAcct.vue b/packages/frontend/src/components/global/MkAcct.vue
index c3e806b5fb603c5a13827e7518fb3d544a53d5b7..2a43ded9e1ee3d70fedc43ffde62ccb0c4d7cac3 100644
--- a/packages/frontend/src/components/global/MkAcct.vue
+++ b/packages/frontend/src/components/global/MkAcct.vue
@@ -1,7 +1,7 @@
 <template>
-<span class="mk-acct">
-	<span class="name">@{{ user.username }}</span>
-	<span v-if="user.host || detail || $store.state.showFullAcct" class="host">@{{ user.host || host }}</span>
+<span>
+	<span>@{{ user.username }}</span>
+	<span v-if="user.host || detail || $store.state.showFullAcct" style="opacity: 0.5;">@{{ user.host || host }}</span>
 </span>
 </template>
 
@@ -18,10 +18,3 @@ defineProps<{
 const host = toUnicode(hostRaw);
 </script>
 
-<style lang="scss" scoped>
-.mk-acct {
-	> .host {
-		opacity: 0.5;
-	}
-}
-</style>
diff --git a/packages/frontend/src/components/global/MkAvatar.vue b/packages/frontend/src/components/global/MkAvatar.vue
index 2f8e7945fc993d0d25ac47ef7562e2072c4b81f7..0a42a29323f7d5667665caeb3e9acd1d376c87c7 100644
--- a/packages/frontend/src/components/global/MkAvatar.vue
+++ b/packages/frontend/src/components/global/MkAvatar.vue
@@ -1,11 +1,11 @@
 <template>
-<span v-if="disableLink" v-user-preview="disablePreview ? undefined : user.id" class="eiwwqkts _noSelect" :class="{ cat: user.isCat, square: $store.state.squareAvatars }" :style="{ color }" :title="acct(user)" @click="onClick">
-	<img class="inner" :src="url" decoding="async"/>
-	<MkUserOnlineIndicator v-if="showIndicator" class="indicator" :user="user"/>
+<span v-if="disableLink" v-user-preview="disablePreview ? undefined : user.id" :class="[$style.root, { [$style.cat]: user.isCat, [$style.square]: $store.state.squareAvatars }]" class="_noSelect" :style="{ color }" :title="acct(user)" @click="onClick">
+	<img :class="$style.inner" :src="url" decoding="async"/>
+	<MkUserOnlineIndicator v-if="showIndicator" :class="$style.indicator" :user="user"/>
 </span>
-<MkA v-else v-user-preview="disablePreview ? undefined : user.id" class="eiwwqkts _noSelect" :class="{ cat: user.isCat, square: $store.state.squareAvatars }" :style="{ color }" :to="userPage(user)" :title="acct(user)" :target="target">
-	<img class="inner" :src="url" decoding="async"/>
-	<MkUserOnlineIndicator v-if="showIndicator" class="indicator" :user="user"/>
+<MkA v-else v-user-preview="disablePreview ? undefined : user.id" class="_noSelect" :class="[$style.root, { [$style.cat]: user.isCat, [$style.square]: $store.state.squareAvatars }]" :style="{ color }" :to="userPage(user)" :title="acct(user)" :target="target">
+	<img :class="$style.inner" :src="url" decoding="async"/>
+	<MkUserOnlineIndicator v-if="showIndicator" :class="$style.indicator" :user="user"/>
 </MkA>
 </template>
 
@@ -68,75 +68,77 @@ watch(() => props.user.avatarBlurhash, () => {
 	75% { transform: rotate(0deg) skew(-30deg); }
 	to { transform: rotate(-37.6deg) skew(-30deg); }
 }
+</style>
 
-.eiwwqkts {
+<style lang="scss" module>
+.root {
 	position: relative;
 	display: inline-block;
 	vertical-align: bottom;
 	flex-shrink: 0;
 	border-radius: 100%;
 	line-height: 16px;
+}
+
+.inner {
+	position: absolute;
+	bottom: 0;
+	left: 0;
+	right: 0;
+	top: 0;
+	border-radius: 100%;
+	z-index: 1;
+	overflow: clip;
+	object-fit: cover;
+	width: 100%;
+	height: 100%;
+}
+
+.indicator {
+	position: absolute;
+	z-index: 1;
+	bottom: 0;
+	left: 0;
+	width: 20%;
+	height: 20%;
+}
+
+.square {
+	border-radius: 20%;
 
 	> .inner {
-		position: absolute;
-		bottom: 0;
-		left: 0;
-		right: 0;
-		top: 0;
-		border-radius: 100%;
-		z-index: 1;
-		overflow: clip;
-		object-fit: cover;
-		width: 100%;
-		height: 100%;
+		border-radius: 20%;
 	}
+}
 
-	> .indicator {
-		position: absolute;
-		z-index: 1;
-		bottom: 0;
-		left: 0;
-		width: 20%;
-		height: 20%;
+.cat {
+	&:before, &:after {
+		background: #df548f;
+		border: solid 4px currentColor;
+		box-sizing: border-box;
+		content: '';
+		display: inline-block;
+		height: 50%;
+		width: 50%;
 	}
 
-	&.square {
-		border-radius: 20%;
-
-		> .inner {
-			border-radius: 20%;
-		}
+	&:before {
+		border-radius: 0 75% 75%;
+		transform: rotate(37.5deg) skew(30deg);
 	}
 
-	&.cat {
-		&:before, &:after {
-			background: #df548f;
-			border: solid 4px currentColor;
-			box-sizing: border-box;
-			content: '';
-			display: inline-block;
-			height: 50%;
-			width: 50%;
-		}
+	&:after {
+		border-radius: 75% 0 75% 75%;
+		transform: rotate(-37.5deg) skew(-30deg);
+	}
 
+	&:hover {
 		&:before {
-			border-radius: 0 75% 75%;
-			transform: rotate(37.5deg) skew(30deg);
+			animation: earwiggleleft 1s infinite;
 		}
 
 		&:after {
-			border-radius: 75% 0 75% 75%;
-			transform: rotate(-37.5deg) skew(-30deg);
-		}
-
-		&:hover {
-			&:before {
-				animation: earwiggleleft 1s infinite;
-			}
-
-			&:after {
-				animation: earwiggleright 1s infinite;
-			}
+			animation: earwiggleright 1s infinite;
 		}
 	}
 }
diff --git a/packages/frontend/src/components/global/MkEmoji.vue b/packages/frontend/src/components/global/MkEmoji.vue
index 67e9ef428a9fe7dc5704bddbe41a6ed81fdd20f1..bc88cf3be4ebdec696ceeed68d203b1a1c4cca84 100644
--- a/packages/frontend/src/components/global/MkEmoji.vue
+++ b/packages/frontend/src/components/global/MkEmoji.vue
@@ -1,6 +1,6 @@
 <template>
-<img v-if="isCustom" class="mk-emoji custom" :class="{ normal, noStyle }" :src="url" :alt="alt" :title="alt" decoding="async"/>
-<img v-else-if="char && !useOsNativeEmojis" class="mk-emoji" :src="url" :alt="alt" decoding="async" @pointerenter="computeTitle"/>
+<img v-if="isCustom" :class="[$style.root, $style.custom, { [$style.normal]: normal, [$style.noStyle]: noStyle }]" :src="url" :alt="alt" :title="alt" decoding="async"/>
+<img v-else-if="char && !useOsNativeEmojis" :class="$style.root" :src="url" :alt="alt" decoding="async" @pointerenter="computeTitle"/>
 <span v-else-if="char && useOsNativeEmojis" :alt="alt" @pointerenter="computeTitle">{{ char }}</span>
 <span v-else>{{ emoji }}</span>
 </template>
@@ -47,32 +47,32 @@ function computeTitle(event: PointerEvent): void {
 }
 </script>
 
-<style lang="scss" scoped>
-.mk-emoji {
+<style lang="scss" module>
+.root {
 	height: 1.25em;
 	vertical-align: -0.25em;
+}
 
-	&.custom {
-		height: 2.5em;
-		vertical-align: middle;
-		transition: transform 0.2s ease;
+.custom {
+	height: 2.5em;
+	vertical-align: middle;
+	transition: transform 0.2s ease;
 
-		&:hover {
-			transform: scale(1.2);
-		}
+	&:hover {
+		transform: scale(1.2);
+	}
+}
 
-		&.normal {
-			height: 1.25em;
-			vertical-align: -0.25em;
+.normal {
+	height: 1.25em;
+	vertical-align: -0.25em;
 
-			&:hover {
-				transform: none;
-			}
-		}
+	&:hover {
+		transform: none;
 	}
+}
 
-	&.noStyle {
-		height: auto !important;
-	}
+.noStyle {
+	height: auto !important;
 }
 </style>