diff --git a/packages/frontend/assets/reversi/lose.mp3 b/packages/frontend/assets/reversi/lose.mp3
new file mode 100644
index 0000000000..b62d50baf7
Binary files /dev/null and b/packages/frontend/assets/reversi/lose.mp3 differ
diff --git a/packages/frontend/assets/reversi/win.mp3 b/packages/frontend/assets/reversi/win.mp3
new file mode 100644
index 0000000000..25402ce2a6
Binary files /dev/null and b/packages/frontend/assets/reversi/win.mp3 differ
diff --git a/packages/frontend/src/pages/reversi/game.board.vue b/packages/frontend/src/pages/reversi/game.board.vue
index 5e28f55902..6ad779c605 100644
--- a/packages/frontend/src/pages/reversi/game.board.vue
+++ b/packages/frontend/src/pages/reversi/game.board.vue
@@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 -->
 
 <template>
-<MkSpacer :contentMax="600">
+<MkSpacer :contentMax="500">
 	<div :class="$style.root" class="_gaps">
 		<div style="display: flex; align-items: center; justify-content: center; gap: 10px;">
 			<span>({{ i18n.ts._reversi.black }})</span>
@@ -35,53 +35,55 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</div>
 
 		<div :class="$style.board">
-			<div v-if="showBoardLabels" :class="$style.labelsX">
-				<span v-for="i in game.map[0].length" :class="$style.labelsXLabel">{{ String.fromCharCode(64 + i) }}</span>
-			</div>
-			<div style="display: flex;">
-				<div v-if="showBoardLabels" :class="$style.labelsY">
-					<div v-for="i in game.map.length" :class="$style.labelsYLabel">{{ i }}</div>
+			<div :class="$style.boardInner">
+				<div v-if="showBoardLabels" :class="$style.labelsX">
+					<span v-for="i in game.map[0].length" :class="$style.labelsXLabel">{{ String.fromCharCode(64 + i) }}</span>
 				</div>
-				<div :class="$style.boardCells" :style="cellsStyle">
-					<div
-						v-for="(stone, i) in engine.board"
-						:key="i"
-						v-tooltip="`${String.fromCharCode(65 + engine.posToXy(i)[0])}${engine.posToXy(i)[1] + 1}`"
-						:class="[$style.boardCell, {
-							[$style.boardCell_empty]: stone == null,
-							[$style.boardCell_none]: engine.map[i] === 'null',
-							[$style.boardCell_isEnded]: game.isEnded,
-							[$style.boardCell_myTurn]: !game.isEnded && isMyTurn,
-							[$style.boardCell_can]: turnUser ? engine.canPut(turnUser.id === blackUser.id, i) : null,
-							[$style.boardCell_prev]: engine.prevPos === i
-						}]"
-						@click="putStone(i)"
-					>
-						<Transition
-							:enterActiveClass="$style.transition_flip_enterActive"
-							:leaveActiveClass="$style.transition_flip_leaveActive"
-							:enterFromClass="$style.transition_flip_enterFrom"
-							:leaveToClass="$style.transition_flip_leaveTo"
-							mode="default"
+				<div style="display: flex;">
+					<div v-if="showBoardLabels" :class="$style.labelsY">
+						<div v-for="i in game.map.length" :class="$style.labelsYLabel">{{ i }}</div>
+					</div>
+					<div :class="$style.boardCells" :style="cellsStyle">
+						<div
+							v-for="(stone, i) in engine.board"
+							:key="i"
+							v-tooltip="`${String.fromCharCode(65 + engine.posToXy(i)[0])}${engine.posToXy(i)[1] + 1}`"
+							:class="[$style.boardCell, {
+								[$style.boardCell_empty]: stone == null,
+								[$style.boardCell_none]: engine.map[i] === 'null',
+								[$style.boardCell_isEnded]: game.isEnded,
+								[$style.boardCell_myTurn]: !game.isEnded && isMyTurn,
+								[$style.boardCell_can]: turnUser ? engine.canPut(turnUser.id === blackUser.id, i) : null,
+								[$style.boardCell_prev]: engine.prevPos === i
+							}]"
+							@click="putStone(i)"
 						>
-							<template v-if="useAvatarAsStone">
-								<img v-if="stone === true" :class="$style.boardCellStone" :src="blackUser.avatarUrl"/>
-								<img v-else-if="stone === false" :class="$style.boardCellStone" :src="whiteUser.avatarUrl"/>
-							</template>
-							<template v-else>
-								<img v-if="stone === true" :class="$style.boardCellStone" src="/client-assets/reversi/stone_b.png"/>
-								<img v-else-if="stone === false" :class="$style.boardCellStone" src="/client-assets/reversi/stone_w.png"/>
-							</template>
-						</Transition>
+							<Transition
+								:enterActiveClass="$style.transition_flip_enterActive"
+								:leaveActiveClass="$style.transition_flip_leaveActive"
+								:enterFromClass="$style.transition_flip_enterFrom"
+								:leaveToClass="$style.transition_flip_leaveTo"
+								mode="default"
+							>
+								<template v-if="useAvatarAsStone">
+									<img v-if="stone === true" :class="$style.boardCellStone" :src="blackUser.avatarUrl"/>
+									<img v-else-if="stone === false" :class="$style.boardCellStone" :src="whiteUser.avatarUrl"/>
+								</template>
+								<template v-else>
+									<img v-if="stone === true" :class="$style.boardCellStone" src="/client-assets/reversi/stone_b.png"/>
+									<img v-else-if="stone === false" :class="$style.boardCellStone" src="/client-assets/reversi/stone_w.png"/>
+								</template>
+							</Transition>
+						</div>
+					</div>
+					<div v-if="showBoardLabels" :class="$style.labelsY">
+						<div v-for="i in game.map.length" :class="$style.labelsYLabel">{{ i }}</div>
 					</div>
 				</div>
-				<div v-if="showBoardLabels" :class="$style.labelsY">
-					<div v-for="i in game.map.length" :class="$style.labelsYLabel">{{ i }}</div>
+				<div v-if="showBoardLabels" :class="$style.labelsX">
+					<span v-for="i in game.map[0].length" :class="$style.labelsXLabel">{{ String.fromCharCode(64 + i) }}</span>
 				</div>
 			</div>
-			<div v-if="showBoardLabels" :class="$style.labelsX">
-				<span v-for="i in game.map[0].length" :class="$style.labelsXLabel">{{ String.fromCharCode(64 + i) }}</span>
-			</div>
 		</div>
 
 		<div v-if="game.isEnded" class="_panel _gaps_s" style="padding: 16px;">
@@ -155,6 +157,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js';
 import { userPage } from '@/filters/user.js';
 import * as sound from '@/scripts/sound.js';
 import * as os from '@/os.js';
+import { confetti } from '@/scripts/confetti.js';
 
 const $i = signinRequired();
 
@@ -329,6 +332,22 @@ function onStreamLog(log: Reversi.Serializer.Log & { id: string | null }) {
 
 function onStreamEnded(x) {
 	game.value = deepClone(x.game);
+
+	if (game.value.winnerId === $i.id) {
+		confetti({
+			duration: 1000 * 3,
+		});
+
+		sound.playUrl('/client-assets/reversi/win.mp3', {
+			volume: 1,
+			playbackRate: 1,
+		});
+	} else {
+		sound.playUrl('/client-assets/reversi/lose.mp3', {
+			volume: 1,
+			playbackRate: 1,
+		});
+	}
 }
 
 function checkEnd() {
@@ -465,8 +484,27 @@ $gap: 4px;
 
 .board {
 	width: 100%;
-	max-width: 500px;
+	box-sizing: border-box;
 	margin: 0 auto;
+
+	padding: 7px;
+	background: #8C4F26;
+	box-shadow: 0 6px 16px #0007, 0 0 1px 1px #693410, inset 0 0 2px 1px #ce8a5c;
+	border-radius: 12px;
+}
+
+.boardInner {
+	padding: 32px;
+
+	background: var(--panel);
+	box-shadow: 0 0 2px 1px #ce8a5c, inset 0 0 1px 1px #693410;
+	border-radius: 8px;
+}
+
+@container (max-width: 400px) {
+	.boardInner {
+		padding: 16px;
+	}
 }
 
 .labelsX {
diff --git a/packages/frontend/src/pages/reversi/game.setting.vue b/packages/frontend/src/pages/reversi/game.setting.vue
index 9ca107278b..d69176e25a 100644
--- a/packages/frontend/src/pages/reversi/game.setting.vue
+++ b/packages/frontend/src/pages/reversi/game.setting.vue
@@ -8,72 +8,74 @@ SPDX-License-Identifier: AGPL-3.0-only
 	<MkSpacer :contentMax="600">
 		<div style="text-align: center;"><b><MkUserName :user="game.user1"/></b> vs <b><MkUserName :user="game.user2"/></b></div>
 
-		<div class="_gaps">
-			<div style="font-size: 1.5em; text-align: center;">{{ i18n.ts._reversi.gameSettings }}</div>
+		<div :class="{ [$style.disallow]: isReady }">
+			<div class="_gaps" :class="{ [$style.disallowInner]: isReady }">
+				<div style="font-size: 1.5em; text-align: center;">{{ i18n.ts._reversi.gameSettings }}</div>
 
-			<div class="_panel">
-				<div style="display: flex; align-items: center; padding: 16px; border-bottom: solid 1px var(--divider);">
-					<div>{{ mapName }}</div>
-					<MkButton style="margin-left: auto;" @click="chooseMap">{{ i18n.ts._reversi.chooseBoard }}</MkButton>
-				</div>
+				<div class="_panel">
+					<div style="display: flex; align-items: center; padding: 16px; border-bottom: solid 1px var(--divider);">
+						<div>{{ mapName }}</div>
+						<MkButton style="margin-left: auto;" @click="chooseMap">{{ i18n.ts._reversi.chooseBoard }}</MkButton>
+					</div>
 
-				<div style="padding: 16px;">
-					<div v-if="game.map == null"><i class="ti ti-dice"></i></div>
-					<div v-else :class="$style.board" :style="{ 'grid-template-rows': `repeat(${ game.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.map[0].length }, 1fr)` }">
-						<div v-for="(x, i) in game.map.join('')" :class="[$style.boardCell, { [$style.boardCellNone]: x == ' ' }]" @click="onMapCellClick(i, x)">
-							<i v-if="x === 'b' || x === 'w'" style="pointer-events: none; user-select: none;" :class="x === 'b' ? 'ti ti-circle-filled' : 'ti ti-circle'"></i>
+					<div style="padding: 16px;">
+						<div v-if="game.map == null"><i class="ti ti-dice"></i></div>
+						<div v-else :class="$style.board" :style="{ 'grid-template-rows': `repeat(${ game.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.map[0].length }, 1fr)` }">
+							<div v-for="(x, i) in game.map.join('')" :class="[$style.boardCell, { [$style.boardCellNone]: x == ' ' }]" @click="onMapCellClick(i, x)">
+								<i v-if="x === 'b' || x === 'w'" style="pointer-events: none; user-select: none;" :class="x === 'b' ? 'ti ti-circle-filled' : 'ti ti-circle'"></i>
+							</div>
 						</div>
 					</div>
 				</div>
+
+				<MkFolder :defaultOpen="true">
+					<template #label>{{ i18n.ts._reversi.blackOrWhite }}</template>
+
+					<MkRadios v-model="game.bw">
+						<option value="random">{{ i18n.ts.random }}</option>
+						<option :value="'1'">
+							<I18n :src="i18n.ts._reversi.blackIs" tag="span">
+								<template #name>
+									<b><MkUserName :user="game.user1"/></b>
+								</template>
+							</I18n>
+						</option>
+						<option :value="'2'">
+							<I18n :src="i18n.ts._reversi.blackIs" tag="span">
+								<template #name>
+									<b><MkUserName :user="game.user2"/></b>
+								</template>
+							</I18n>
+						</option>
+					</MkRadios>
+				</MkFolder>
+
+				<MkFolder :defaultOpen="true">
+					<template #label>{{ i18n.ts._reversi.timeLimitForEachTurn }}</template>
+					<template #suffix>{{ game.timeLimitForEachTurn }}{{ i18n.ts._time.second }}</template>
+
+					<MkRadios v-model="game.timeLimitForEachTurn">
+						<option :value="5">5{{ i18n.ts._time.second }}</option>
+						<option :value="10">10{{ i18n.ts._time.second }}</option>
+						<option :value="30">30{{ i18n.ts._time.second }}</option>
+						<option :value="60">60{{ i18n.ts._time.second }}</option>
+						<option :value="90">90{{ i18n.ts._time.second }}</option>
+						<option :value="120">120{{ i18n.ts._time.second }}</option>
+						<option :value="180">180{{ i18n.ts._time.second }}</option>
+						<option :value="3600">3600{{ i18n.ts._time.second }}</option>
+					</MkRadios>
+				</MkFolder>
+
+				<MkFolder :defaultOpen="true">
+					<template #label>{{ i18n.ts._reversi.rules }}</template>
+
+					<div class="_gaps_s">
+						<MkSwitch v-model="game.isLlotheo" @update:modelValue="updateSettings('isLlotheo')">{{ i18n.ts._reversi.isLlotheo }}</MkSwitch>
+						<MkSwitch v-model="game.loopedBoard" @update:modelValue="updateSettings('loopedBoard')">{{ i18n.ts._reversi.loopedMap }}</MkSwitch>
+						<MkSwitch v-model="game.canPutEverywhere" @update:modelValue="updateSettings('canPutEverywhere')">{{ i18n.ts._reversi.canPutEverywhere }}</MkSwitch>
+					</div>
+				</MkFolder>
 			</div>
-
-			<MkFolder :defaultOpen="true">
-				<template #label>{{ i18n.ts._reversi.blackOrWhite }}</template>
-
-				<MkRadios v-model="game.bw">
-					<option value="random">{{ i18n.ts.random }}</option>
-					<option :value="'1'">
-						<I18n :src="i18n.ts._reversi.blackIs" tag="span">
-							<template #name>
-								<b><MkUserName :user="game.user1"/></b>
-							</template>
-						</I18n>
-					</option>
-					<option :value="'2'">
-						<I18n :src="i18n.ts._reversi.blackIs" tag="span">
-							<template #name>
-								<b><MkUserName :user="game.user2"/></b>
-							</template>
-						</I18n>
-					</option>
-				</MkRadios>
-			</MkFolder>
-
-			<MkFolder :defaultOpen="true">
-				<template #label>{{ i18n.ts._reversi.timeLimitForEachTurn }}</template>
-				<template #suffix>{{ game.timeLimitForEachTurn }}{{ i18n.ts._time.second }}</template>
-
-				<MkRadios v-model="game.timeLimitForEachTurn">
-					<option :value="5">5{{ i18n.ts._time.second }}</option>
-					<option :value="10">10{{ i18n.ts._time.second }}</option>
-					<option :value="30">30{{ i18n.ts._time.second }}</option>
-					<option :value="60">60{{ i18n.ts._time.second }}</option>
-					<option :value="90">90{{ i18n.ts._time.second }}</option>
-					<option :value="120">120{{ i18n.ts._time.second }}</option>
-					<option :value="180">180{{ i18n.ts._time.second }}</option>
-					<option :value="3600">3600{{ i18n.ts._time.second }}</option>
-				</MkRadios>
-			</MkFolder>
-
-			<MkFolder :defaultOpen="true">
-				<template #label>{{ i18n.ts._reversi.rules }}</template>
-
-				<div class="_gaps_s">
-					<MkSwitch v-model="game.isLlotheo" @update:modelValue="updateSettings('isLlotheo')">{{ i18n.ts._reversi.isLlotheo }}</MkSwitch>
-					<MkSwitch v-model="game.loopedBoard" @update:modelValue="updateSettings('loopedBoard')">{{ i18n.ts._reversi.loopedMap }}</MkSwitch>
-					<MkSwitch v-model="game.canPutEverywhere" @update:modelValue="updateSettings('canPutEverywhere')">{{ i18n.ts._reversi.canPutEverywhere }}</MkSwitch>
-				</div>
-			</MkFolder>
 		</div>
 	</MkSpacer>
 	<template #footer>
@@ -123,7 +125,7 @@ const props = defineProps<{
 }>();
 
 const game = ref<Misskey.entities.ReversiGameDetailed>(deepClone(props.game));
-const isLlotheo = ref<boolean>(false);
+
 const mapName = computed(() => {
 	if (game.value.map == null) return 'Random';
 	const found = Object.values(Reversi.maps).find(x => x.data.join('') === game.value.map.join(''));
@@ -236,6 +238,15 @@ onUnmounted(() => {
 </script>
 
 <style lang="scss" module>
+.disallow {
+	cursor: not-allowed;
+}
+.disallowInner {
+	pointer-events: none;
+	user-select: none;
+	opacity: 0.7;
+}
+
 .board {
 	display: grid;
 	grid-gap: 4px;