From 3f29a0382b303b371f2f63e17714eea094dd9c20 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Thu, 2 Aug 2018 22:54:52 +0900
Subject: [PATCH 1/6] wip

---
 .../components/games/reversi/reversi.room.vue | 143 ++++++++++--------
 .../components/games/reversi/reversi.vue      |   2 +-
 .../app/common/views/components/index.ts      |   2 +
 .../views/components/ui/form/button.vue       |  79 ++++++++++
 4 files changed, 165 insertions(+), 61 deletions(-)
 create mode 100644 src/client/app/common/views/components/ui/form/button.vue

diff --git a/src/client/app/common/views/components/games/reversi/reversi.room.vue b/src/client/app/common/views/components/games/reversi/reversi.room.vue
index 94b36d0870..f04d8d0873 100644
--- a/src/client/app/common/views/components/games/reversi/reversi.room.vue
+++ b/src/client/app/common/views/components/games/reversi/reversi.room.vue
@@ -5,8 +5,8 @@
 	<div>
 		<p>%i18n:@settings-of-the-game%</p>
 
-		<el-card class="map">
-			<div slot="header">
+		<div class="card">
+			<header>
 				<el-select :class="$style.mapSelect" v-model="mapName" placeholder="%i18n:@choose-map%" @change="onMapChange">
 					<el-option label="%i18n:@random%" :value="null"/>
 					<el-option-group v-for="c in mapCategories" :key="c" :label="c">
@@ -16,63 +16,80 @@
 						</el-option>
 					</el-option-group>
 				</el-select>
-			</div>
-			<div :class="$style.board" v-if="game.settings.map != null" :style="{ 'grid-template-rows': `repeat(${ game.settings.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.settings.map[0].length }, 1fr)` }">
-				<div v-for="(x, i) in game.settings.map.join('')"
-					:data-none="x == ' '"
-					@click="onPixelClick(i, x)"
-				>
-					<template v-if="x == 'b'">%fa:circle%</template>
-					<template v-if="x == 'w'">%fa:circle R%</template>
+			</header>
+
+			<div>
+				<div :class="$style.board" v-if="game.settings.map != null" :style="{ 'grid-template-rows': `repeat(${ game.settings.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.settings.map[0].length }, 1fr)` }">
+					<div v-for="(x, i) in game.settings.map.join('')"
+							:data-none="x == ' '"
+							@click="onPixelClick(i, x)">
+						<template v-if="x == 'b'">%fa:circle%</template>
+						<template v-if="x == 'w'">%fa:circle R%</template>
+					</div>
 				</div>
 			</div>
-		</el-card>
+		</div>
 
-		<el-card class="bw">
-			<div slot="header">
+		<div class="card">
+			<header>
 				<span>%i18n:@black-or-white%</span>
-			</div>
-			<el-radio v-model="game.settings.bw" label="random" @change="updateSettings">%i18n:@random%</el-radio>
-			<el-radio v-model="game.settings.bw" :label="1" @change="updateSettings">{{ '%i18n:@black-is%'.split('{}')[0] }}{{ game.user1 | userName }}{{ '%i18n:@black-is%'.split('{}')[1] }}</el-radio>
-			<el-radio v-model="game.settings.bw" :label="2" @change="updateSettings">{{ '%i18n:@black-is%'.split('{}')[0] }}{{ game.user2 | userName }}{{ '%i18n:@black-is%'.split('{}')[1] }}</el-radio>
-		</el-card>
+			</header>
 
-		<el-card class="rules">
-			<div slot="header">
+			<div>
+				<el-radio v-model="game.settings.bw" label="random" @change="updateSettings">%i18n:@random%</el-radio>
+				<el-radio v-model="game.settings.bw" :label="1" @change="updateSettings">{{ '%i18n:@black-is%'.split('{}')[0] }}{{ game.user1 | userName }}{{ '%i18n:@black-is%'.split('{}')[1] }}</el-radio>
+				<el-radio v-model="game.settings.bw" :label="2" @change="updateSettings">{{ '%i18n:@black-is%'.split('{}')[0] }}{{ game.user2 | userName }}{{ '%i18n:@black-is%'.split('{}')[1] }}</el-radio>
+			</div>
+		</div>
+
+		<div class="card">
+			<header>
 				<span>%i18n:@rules%</span>
-			</div>
-			<mk-switch v-model="game.settings.isLlotheo" @change="updateSettings" text="%i18n:@is-llotheo%"/>
-			<mk-switch v-model="game.settings.loopedBoard" @change="updateSettings" text="%i18n:@looped-map%"/>
-			<mk-switch v-model="game.settings.canPutEverywhere" @change="updateSettings" text="%i18n:@can-put-everywhere%"/>
-		</el-card>
+			</header>
 
-		<el-card class="bot-form" v-if="form">
-			<div slot="header">
+			<div>
+				<mk-switch v-model="game.settings.isLlotheo" @change="updateSettings" text="%i18n:@is-llotheo%"/>
+				<mk-switch v-model="game.settings.loopedBoard" @change="updateSettings" text="%i18n:@looped-map%"/>
+				<mk-switch v-model="game.settings.canPutEverywhere" @change="updateSettings" text="%i18n:@can-put-everywhere%"/>
+			</div>
+		</div>
+
+		<div class="card" v-if="form">
+			<header>
 				<span>%i18n:@settings-of-the-bot%</span>
+			</header>
+
+			<div>
+				<el-alert v-for="message in messages"
+						:title="message.text"
+						:type="message.type"
+						:key="message.id"/>
+
+				<template v-for="item in form">
+					<mk-switch v-if="item.type == 'button'" v-model="item.value" :key="item.id" :text="item.label" @change="onChangeForm($event, item)">{{ item.desc || '' }}</mk-switch>
+
+					<div class="card" v-if="item.type == 'radio'" :key="item.id">
+						<header>
+							<span>{{ item.label }}</span>
+						</header>
+
+						<div>
+							<el-radio v-for="(r, i) in item.items" :key="item.id + ':' + i" v-model="item.value" :label="r.value" @change="onChangeForm($event, item)">{{ r.label }}</el-radio>
+						</div>
+					</div>
+
+					<div class="card" v-if="item.type == 'textbox'" :key="item.id">
+						<header>
+							<span>{{ item.label }}</span>
+						</header>
+
+						<div>
+							<el-input v-model="item.value" @change="onChangeForm($event, item)"/>
+						</div>
+					</div>
+				</template>
 			</div>
-			<el-alert v-for="message in messages"
-				:title="message.text"
-				:type="message.type"
-				:key="message.id"
-			/>
-			<template v-for="item in form">
-				<mk-switch v-if="item.type == 'button'" v-model="item.value" :key="item.id" :text="item.label" @change="onChangeForm($event, item)">{{ item.desc || '' }}</mk-switch>
-
-				<el-card v-if="item.type == 'radio'" :key="item.id">
-					<div slot="header">
-						<span>{{ item.label }}</span>
-					</div>
-					<el-radio v-for="(r, i) in item.items" :key="item.id + ':' + i" v-model="item.value" :label="r.value" @change="onChangeForm($event, item)">{{ r.label }}</el-radio>
-				</el-card>
-
-				<el-card v-if="item.type == 'textbox'" :key="item.id">
-					<div slot="header">
-						<span>{{ item.label }}</span>
-					</div>
-					<el-input v-model="item.value" @change="onChangeForm($event, item)"/>
-				</el-card>
-			</template>
-		</el-card>
+		</div>
 	</div>
 
 	<footer>
@@ -244,13 +261,24 @@ export default Vue.extend({
 	> div
 		padding 0 16px
 
-		> .map
-		> .bw
-		> .rules
-		> .bot-form
-			max-width 400px
+		> .card
 			margin 0 auto 16px auto
 
+		.card
+			max-width 400px
+			border-radius 4px
+			border 1px solid #ebeef5
+			background #fff
+			color #303133
+			box-shadow 0 2px 12px 0 rgba(#000, 0.1)
+
+			> header
+				padding 18px 20px
+				border-bottom 1px solid #ebeef5
+
+			> div
+				padding 20px
+
 	> footer
 		position sticky
 		bottom 0
@@ -290,8 +318,3 @@ export default Vue.extend({
 			border-color transparent
 
 </style>
-
-<style lang="stylus">
-.el-alert__content
-	position initial !important
-</style>
diff --git a/src/client/app/common/views/components/games/reversi/reversi.vue b/src/client/app/common/views/components/games/reversi/reversi.vue
index 43f1c6656a..e3a62ff627 100644
--- a/src/client/app/common/views/components/games/reversi/reversi.vue
+++ b/src/client/app/common/views/components/games/reversi/reversi.vue
@@ -14,7 +14,7 @@
 		<p>%i18n:@sub-title%</p>
 		<div class="play">
 			<!--<el-button round>フリーマッチ(準備中)</el-button>-->
-			<el-button type="primary" round @click="match">%i18n:@invite%</el-button>
+			<form-button primary round @click="match">%i18n:@invite%</form-button>
 			<details>
 				<summary>%i18n:@rule%</summary>
 				<div>
diff --git a/src/client/app/common/views/components/index.ts b/src/client/app/common/views/components/index.ts
index c18a1c3b68..7075a8fc0b 100644
--- a/src/client/app/common/views/components/index.ts
+++ b/src/client/app/common/views/components/index.ts
@@ -37,6 +37,7 @@ import uiTextarea from './ui/textarea.vue';
 import uiSwitch from './ui/switch.vue';
 import uiRadio from './ui/radio.vue';
 import uiSelect from './ui/select.vue';
+import formButton from './ui/form/button.vue';
 
 Vue.component('mk-analog-clock', analogClock);
 Vue.component('mk-menu', menu);
@@ -75,3 +76,4 @@ Vue.component('ui-textarea', uiTextarea);
 Vue.component('ui-switch', uiSwitch);
 Vue.component('ui-radio', uiRadio);
 Vue.component('ui-select', uiSelect);
+Vue.component('form-button', formButton);
diff --git a/src/client/app/common/views/components/ui/form/button.vue b/src/client/app/common/views/components/ui/form/button.vue
new file mode 100644
index 0000000000..a9d9dfef27
--- /dev/null
+++ b/src/client/app/common/views/components/ui/form/button.vue
@@ -0,0 +1,79 @@
+<template>
+<div class="nvemkhtwcnnpkdrwfcbzuwhfulejhmzg" :class="{ round, primary }">
+	<button :type="type" @click="$emit('click')">
+		<slot></slot>
+	</button>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+export default Vue.extend({
+	props: {
+		round: {
+			type: Boolean,
+			required: false,
+			default: false
+		},
+		primary: {
+			type: Boolean,
+			required: false,
+			default: false
+		}
+	}
+});
+</script>
+
+<style lang="stylus" scoped>
+@import '~const.styl'
+
+root(isDark)
+	> button
+		display inline-block
+		margin 0
+		padding 12px 20px
+		font-size 14px
+		border 1px solid #dcdfe6
+		border-radius 4px
+		outline none
+		box-shadow none
+		color #606266
+		transition 0.1s
+
+		&:hover
+		&:focus
+			color $theme-color
+			background rgba($theme-color, 0.12)
+			border-color rgba($theme-color, 0.3)
+
+		&:active
+			color darken($theme-color, 20%)
+			background rgba($theme-color, 0.12)
+			border-color $theme-color
+			transition all 0s
+
+	&.primary
+		> button
+			border none
+			background $theme-color
+			color $theme-color-foreground
+
+			&:hover
+			&:focus
+				background lighten($theme-color, 20%)
+
+			&:active
+				background darken($theme-color, 20%)
+				transition all 0s
+
+	&.round
+		> button
+			border-radius 64px
+
+.nvemkhtwcnnpkdrwfcbzuwhfulejhmzg[data-darkmode]
+	root(true)
+
+.nvemkhtwcnnpkdrwfcbzuwhfulejhmzg:not([data-darkmode])
+	root(false)
+
+</style>

From 1e3cff6174d2c3a144aa5e82481b7cda8c23a87b Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Fri, 3 Aug 2018 00:36:29 +0900
Subject: [PATCH 2/6] wip

---
 .../components/games/reversi/reversi.room.vue | 48 ++++++++++++-------
 .../components/games/reversi/reversi.vue      |  2 +-
 .../views/components/ui/form/button.vue       |  2 +-
 3 files changed, 32 insertions(+), 20 deletions(-)

diff --git a/src/client/app/common/views/components/games/reversi/reversi.room.vue b/src/client/app/common/views/components/games/reversi/reversi.room.vue
index f04d8d0873..ca85924c25 100644
--- a/src/client/app/common/views/components/games/reversi/reversi.room.vue
+++ b/src/client/app/common/views/components/games/reversi/reversi.room.vue
@@ -1,21 +1,19 @@
 <template>
-<div class="root">
+<div class="urbixznjwwuukfsckrwzwsqzsxornqij">
 	<header><b>{{ game.user1 | userName }}</b> vs <b>{{ game.user2 | userName }}</b></header>
 
 	<div>
 		<p>%i18n:@settings-of-the-game%</p>
 
-		<div class="card">
+		<div class="card map">
 			<header>
-				<el-select :class="$style.mapSelect" v-model="mapName" placeholder="%i18n:@choose-map%" @change="onMapChange">
-					<el-option label="%i18n:@random%" :value="null"/>
-					<el-option-group v-for="c in mapCategories" :key="c" :label="c">
-						<el-option v-for="m in maps" v-if="m.category == c" :key="m.name" :label="m.name" :value="m.name">
-							<span style="float: left">{{ m.name }}</span>
-							<span style="float: right; color: #8492a6; font-size: 13px" v-if="m.author">(by <i>{{ m.author }}</i>)</span>
-						</el-option>
-					</el-option-group>
-				</el-select>
+				<select v-model="mapName" placeholder="%i18n:@choose-map%" @change="onMapChange">
+					<option label="-Custom-" :value="mapName" v-if="mapName == '-Custom-'"/>
+					<option label="%i18n:@random%" :value="null"/>
+					<optgroup v-for="c in mapCategories" :key="c" :label="c">
+						<option v-for="m in maps" v-if="m.category == c" :key="m.name" :label="m.name" :value="m.name">{{ m.name }}</option>
+					</optgroup>
+				</select>
 			</header>
 
 			<div>
@@ -219,11 +217,11 @@ export default Vue.extend({
 			});
 		},
 
-		onMapChange(v) {
-			if (v == null) {
+		onMapChange() {
+			if (this.mapName == null) {
 				this.game.settings.map = null;
 			} else {
-				this.game.settings.map = Object.values(maps).find(x => x.name == v).data;
+				this.game.settings.map = Object.values(maps).find(x => x.name == this.mapName).data;
 			}
 			this.$forceUpdate();
 			this.updateSettings();
@@ -250,7 +248,7 @@ export default Vue.extend({
 <style lang="stylus" scoped>
 @import '~const.styl'
 
-.root
+.urbixznjwwuukfsckrwzwsqzsxornqij
 	text-align center
 	background #f9f9f9
 
@@ -264,6 +262,23 @@ export default Vue.extend({
 		> .card
 			margin 0 auto 16px auto
 
+			&.map
+				> header
+					> select
+						width 100%
+						padding 12px 16px
+						border 1px solid #dcdfe6
+						border-radius 4px
+						color #606266
+						transition border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1)
+
+						&:hover
+							border-color #c0c4cc
+
+						&:focus
+						&:active
+							border-color $theme-color
+
 		.card
 			max-width 400px
 			border-radius 4px
@@ -291,9 +306,6 @@ export default Vue.extend({
 </style>
 
 <style lang="stylus" module>
-.mapSelect
-	width 100%
-
 .board
 	display grid
 	grid-gap 4px
diff --git a/src/client/app/common/views/components/games/reversi/reversi.vue b/src/client/app/common/views/components/games/reversi/reversi.vue
index e3a62ff627..dd3423a938 100644
--- a/src/client/app/common/views/components/games/reversi/reversi.vue
+++ b/src/client/app/common/views/components/games/reversi/reversi.vue
@@ -6,7 +6,7 @@
 	<div class="matching" v-else-if="matching">
 		<h1>{{ '%i18n:@matching.waiting-for%'.split('{}')[0] }}<b>{{ matching | userName }}</b>{{ '%i18n:@matching.waiting-for%'.split('{}')[1] }}<mk-ellipsis/></h1>
 		<div class="cancel">
-			<el-button round @click="cancel">%i18n:@matching.cancel%</el-button>
+			<form-button round @click="cancel">%i18n:@matching.cancel%</form-button>
 		</div>
 	</div>
 	<div class="index" v-else>
diff --git a/src/client/app/common/views/components/ui/form/button.vue b/src/client/app/common/views/components/ui/form/button.vue
index a9d9dfef27..2c3aa07830 100644
--- a/src/client/app/common/views/components/ui/form/button.vue
+++ b/src/client/app/common/views/components/ui/form/button.vue
@@ -1,6 +1,6 @@
 <template>
 <div class="nvemkhtwcnnpkdrwfcbzuwhfulejhmzg" :class="{ round, primary }">
-	<button :type="type" @click="$emit('click')">
+	<button @click="$emit('click')">
 		<slot></slot>
 	</button>
 </div>

From a88942f58ab7dee82d855e719f3216db397a0381 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Fri, 3 Aug 2018 09:39:03 +0900
Subject: [PATCH 3/6] wip

---
 .../components/games/reversi/reversi.room.vue |  23 ++--
 .../app/common/views/components/index.ts      |   2 +
 .../views/components/ui/form/button.vue       |   9 +-
 .../common/views/components/ui/form/radio.vue | 122 ++++++++++++++++++
 4 files changed, 147 insertions(+), 9 deletions(-)
 create mode 100644 src/client/app/common/views/components/ui/form/radio.vue

diff --git a/src/client/app/common/views/components/games/reversi/reversi.room.vue b/src/client/app/common/views/components/games/reversi/reversi.room.vue
index ca85924c25..cd1b12ab2a 100644
--- a/src/client/app/common/views/components/games/reversi/reversi.room.vue
+++ b/src/client/app/common/views/components/games/reversi/reversi.room.vue
@@ -34,9 +34,9 @@
 			</header>
 
 			<div>
-				<el-radio v-model="game.settings.bw" label="random" @change="updateSettings">%i18n:@random%</el-radio>
-				<el-radio v-model="game.settings.bw" :label="1" @change="updateSettings">{{ '%i18n:@black-is%'.split('{}')[0] }}{{ game.user1 | userName }}{{ '%i18n:@black-is%'.split('{}')[1] }}</el-radio>
-				<el-radio v-model="game.settings.bw" :label="2" @change="updateSettings">{{ '%i18n:@black-is%'.split('{}')[0] }}{{ game.user2 | userName }}{{ '%i18n:@black-is%'.split('{}')[1] }}</el-radio>
+				<form-radio v-model="game.settings.bw" value="random" @change="updateSettings">%i18n:@random%</form-radio>
+				<form-radio v-model="game.settings.bw" :value="1" @change="updateSettings">{{ '%i18n:@black-is%'.split('{}')[0] }}{{ game.user1 | userName }}{{ '%i18n:@black-is%'.split('{}')[1] }}</form-radio>
+				<form-radio v-model="game.settings.bw" :value="2" @change="updateSettings">{{ '%i18n:@black-is%'.split('{}')[0] }}{{ game.user2 | userName }}{{ '%i18n:@black-is%'.split('{}')[1] }}</form-radio>
 			</div>
 		</div>
 
@@ -99,9 +99,9 @@
 		</p>
 
 		<div class="actions">
-			<el-button @click="exit">%i18n:@cancel%</el-button>
-			<el-button type="primary" @click="accept" v-if="!isAccepted">%i18n:@ready%</el-button>
-			<el-button type="primary" @click="cancel" v-if="isAccepted">%i18n:@cancel-ready%</el-button>
+			<form-button @click="exit">%i18n:@cancel%</form-button>
+			<form-button primary @click="accept" v-if="!isAccepted">%i18n:@ready%</form-button>
+			<form-button primary @click="cancel" v-if="isAccepted">%i18n:@cancel-ready%</form-button>
 		</div>
 	</footer>
 </div>
@@ -248,7 +248,7 @@ export default Vue.extend({
 <style lang="stylus" scoped>
 @import '~const.styl'
 
-.urbixznjwwuukfsckrwzwsqzsxornqij
+root(isDark)
 	text-align center
 	background #f9f9f9
 
@@ -283,7 +283,7 @@ export default Vue.extend({
 			max-width 400px
 			border-radius 4px
 			border 1px solid #ebeef5
-			background #fff
+			background isDark ? #282C37 : #fff
 			color #303133
 			box-shadow 0 2px 12px 0 rgba(#000, 0.1)
 
@@ -303,6 +303,13 @@ export default Vue.extend({
 
 		> .status
 			margin 0 0 16px 0
+
+.urbixznjwwuukfsckrwzwsqzsxornqij[data-darkmode]
+	root(true)
+
+.urbixznjwwuukfsckrwzwsqzsxornqij:not([data-darkmode])
+	root(false)
+
 </style>
 
 <style lang="stylus" module>
diff --git a/src/client/app/common/views/components/index.ts b/src/client/app/common/views/components/index.ts
index 7075a8fc0b..422a3da050 100644
--- a/src/client/app/common/views/components/index.ts
+++ b/src/client/app/common/views/components/index.ts
@@ -38,6 +38,7 @@ import uiSwitch from './ui/switch.vue';
 import uiRadio from './ui/radio.vue';
 import uiSelect from './ui/select.vue';
 import formButton from './ui/form/button.vue';
+import formRadio from './ui/form/radio.vue';
 
 Vue.component('mk-analog-clock', analogClock);
 Vue.component('mk-menu', menu);
@@ -77,3 +78,4 @@ Vue.component('ui-switch', uiSwitch);
 Vue.component('ui-radio', uiRadio);
 Vue.component('ui-select', uiSelect);
 Vue.component('form-button', formButton);
+Vue.component('form-radio', formRadio);
diff --git a/src/client/app/common/views/components/ui/form/button.vue b/src/client/app/common/views/components/ui/form/button.vue
index 2c3aa07830..3d81a83496 100644
--- a/src/client/app/common/views/components/ui/form/button.vue
+++ b/src/client/app/common/views/components/ui/form/button.vue
@@ -28,6 +28,11 @@ export default Vue.extend({
 @import '~const.styl'
 
 root(isDark)
+	display inline-block
+
+	& + .nvemkhtwcnnpkdrwfcbzuwhfulejhmzg
+		margin-left 12px
+
 	> button
 		display inline-block
 		margin 0
@@ -54,16 +59,18 @@ root(isDark)
 
 	&.primary
 		> button
-			border none
+			border 1px solid $theme-color
 			background $theme-color
 			color $theme-color-foreground
 
 			&:hover
 			&:focus
 				background lighten($theme-color, 20%)
+				border-color lighten($theme-color, 20%)
 
 			&:active
 				background darken($theme-color, 20%)
+				border-color darken($theme-color, 20%)
 				transition all 0s
 
 	&.round
diff --git a/src/client/app/common/views/components/ui/form/radio.vue b/src/client/app/common/views/components/ui/form/radio.vue
new file mode 100644
index 0000000000..1b337c3bea
--- /dev/null
+++ b/src/client/app/common/views/components/ui/form/radio.vue
@@ -0,0 +1,122 @@
+<template>
+<div
+	class="uywduthvrdnlpsvsjkqigicixgyfctto"
+	:class="{ disabled, checked }"
+	:aria-checked="checked"
+	:aria-disabled="disabled"
+	@click="toggle"
+>
+	<input type="radio"
+		:disabled="disabled"
+	>
+	<span class="button">
+		<span></span>
+	</span>
+	<span class="label"><slot></slot></span>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+export default Vue.extend({
+	model: {
+		prop: 'model',
+		event: 'change'
+	},
+	props: {
+		model: {
+			required: false
+		},
+		value: {
+			required: false
+		},
+		disabled: {
+			type: Boolean,
+			default: false
+		}
+	},
+	computed: {
+		checked(): boolean {
+			return this.model === this.value;
+		}
+	},
+	methods: {
+		toggle() {
+			this.$emit('change', this.value);
+		}
+	}
+});
+</script>
+
+<style lang="stylus" scoped>
+@import '~const.styl'
+
+root(isDark)
+	display inline-flex
+	margin 0 16px 0 0
+	cursor pointer
+	transition all 0.3s
+
+	> *
+		user-select none
+
+	&.disabled
+		opacity 0.6
+		cursor not-allowed
+
+	&.checked
+		> .button
+			border-color $theme-color
+
+			&:after
+				background-color $theme-color
+				transform scale(1)
+				opacity 1
+
+		> .label
+			color $theme-color
+
+	> input
+		position absolute
+		width 0
+		height 0
+		opacity 0
+		margin 0
+
+	> .button
+		display inline-block
+		flex-shrink 0
+		width 20px
+		height 20px
+		background none
+		border solid 2px isDark ? rgba(#fff, 0.7) : rgba(#000, 0.54)
+		border-radius 100%
+		transition inherit
+
+		&:after
+			content ''
+			display block
+			position absolute
+			top 3px
+			right 3px
+			bottom 3px
+			left 3px
+			border-radius 100%
+			opacity 0
+			transform scale(0)
+			transition 0.4s cubic-bezier(0.25, 0.8, 0.25, 1)
+
+	> .label
+		margin-left 8px
+		display block
+		font-size 14px
+		line-height 20px
+		cursor pointer
+
+.uywduthvrdnlpsvsjkqigicixgyfctto[data-darkmode]
+	root(true)
+
+.uywduthvrdnlpsvsjkqigicixgyfctto:not([data-darkmode])
+	root(false)
+
+</style>

From cd6829ca64f71b36378d45dd29133e6ff04b592b Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Fri, 3 Aug 2018 15:59:24 +0900
Subject: [PATCH 4/6] wip

---
 .../components/games/reversi/reversi.game.vue | 23 +++--
 .../components/games/reversi/reversi.room.vue | 89 ++++++++++---------
 .../components/games/reversi/reversi.vue      | 35 ++++----
 .../views/components/ui/form/button.vue       |  8 +-
 .../common/views/components/ui/form/radio.vue |  6 +-
 5 files changed, 92 insertions(+), 69 deletions(-)

diff --git a/src/client/app/common/views/components/games/reversi/reversi.game.vue b/src/client/app/common/views/components/games/reversi/reversi.game.vue
index 34e9705dd4..57bdf7417b 100644
--- a/src/client/app/common/views/components/games/reversi/reversi.game.vue
+++ b/src/client/app/common/views/components/games/reversi/reversi.game.vue
@@ -1,5 +1,5 @@
 <template>
-<div class="root">
+<div class="xqnhankfuuilcwvhgsopeqncafzsquya">
 	<header><b>{{ blackUser | userName }}</b>(%i18n:common.reversi.black%) vs <b>{{ whiteUser | userName }}</b>(%i18n:common.reversi.white%)</header>
 
 	<div style="overflow: hidden">
@@ -258,12 +258,12 @@ export default Vue.extend({
 <style lang="stylus" scoped>
 @import '~const.styl'
 
-.root
+root(isDark)
 	text-align center
 
 	> header
 		padding 8px
-		border-bottom dashed 1px #c4cdd4
+		border-bottom dashed 1px isDark ? #4c5761 : #c4cdd4
 
 	> .board
 		width calc(100% - 16px)
@@ -327,16 +327,16 @@ export default Vue.extend({
 						user-select none
 
 					&.empty
-						border solid 2px #eee
+						border solid 2px isDark ? #51595f : #eee
 
 					&.empty.can
-						background #eee
+						background isDark ? #51595f : #eee
 
 					&.empty.myTurn
-						border-color #ddd
+						border-color isDark ? #6a767f : #ddd
 
 						&.can
-							background #eee
+							background isDark ? #51595f : #eee
 							cursor pointer
 
 							&:hover
@@ -350,7 +350,7 @@ export default Vue.extend({
 						box-shadow 0 0 0 4px rgba($theme-color, 0.7)
 
 					&.isEnded
-						border-color #ddd
+						border-color isDark ? #6a767f : #ddd
 
 					&.none
 						border-color transparent !important
@@ -388,4 +388,11 @@ export default Vue.extend({
 			display inline-block
 			margin 0 8px
 			min-width 70px
+
+.xqnhankfuuilcwvhgsopeqncafzsquya[data-darkmode]
+	root(true)
+
+.xqnhankfuuilcwvhgsopeqncafzsquya:not([data-darkmode])
+	root(false)
+
 </style>
diff --git a/src/client/app/common/views/components/games/reversi/reversi.room.vue b/src/client/app/common/views/components/games/reversi/reversi.room.vue
index cd1b12ab2a..de5040f630 100644
--- a/src/client/app/common/views/components/games/reversi/reversi.room.vue
+++ b/src/client/app/common/views/components/games/reversi/reversi.room.vue
@@ -17,12 +17,13 @@
 			</header>
 
 			<div>
-				<div :class="$style.board" v-if="game.settings.map != null" :style="{ 'grid-template-rows': `repeat(${ game.settings.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.settings.map[0].length }, 1fr)` }">
+				<div class="random" v-if="game.settings.map == null">%fa:dice%</div>
+				<div class="board" v-else :style="{ 'grid-template-rows': `repeat(${ game.settings.map.length }, 1fr)`, 'grid-template-columns': `repeat(${ game.settings.map[0].length }, 1fr)` }">
 					<div v-for="(x, i) in game.settings.map.join('')"
 							:data-none="x == ' '"
 							@click="onPixelClick(i, x)">
-						<template v-if="x == 'b'">%fa:circle%</template>
-						<template v-if="x == 'w'">%fa:circle R%</template>
+						<template v-if="x == 'b'"><template v-if="$store.state.device.darkmode">%fa:circle R%</template><template v-else>%fa:circle%</template></template>
+						<template v-if="x == 'w'"><template v-if="$store.state.device.darkmode">%fa:circle%</template><template v-else>%fa:circle R%</template></template>
 					</div>
 				</div>
 			</div>
@@ -35,8 +36,8 @@
 
 			<div>
 				<form-radio v-model="game.settings.bw" value="random" @change="updateSettings">%i18n:@random%</form-radio>
-				<form-radio v-model="game.settings.bw" :value="1" @change="updateSettings">{{ '%i18n:@black-is%'.split('{}')[0] }}{{ game.user1 | userName }}{{ '%i18n:@black-is%'.split('{}')[1] }}</form-radio>
-				<form-radio v-model="game.settings.bw" :value="2" @change="updateSettings">{{ '%i18n:@black-is%'.split('{}')[0] }}{{ game.user2 | userName }}{{ '%i18n:@black-is%'.split('{}')[1] }}</form-radio>
+				<form-radio v-model="game.settings.bw" :value="1" @change="updateSettings">{{ '%i18n:@black-is%'.split('{}')[0] }}<b>{{ game.user1 | userName }}</b>{{ '%i18n:@black-is%'.split('{}')[1] }}</form-radio>
+				<form-radio v-model="game.settings.bw" :value="2" @change="updateSettings">{{ '%i18n:@black-is%'.split('{}')[0] }}<b>{{ game.user2 | userName }}</b>{{ '%i18n:@black-is%'.split('{}')[1] }}</form-radio>
 			</div>
 		</div>
 
@@ -250,7 +251,7 @@ export default Vue.extend({
 
 root(isDark)
 	text-align center
-	background #f9f9f9
+	background isDark ? #191b22 : #f9f9f9
 
 	> header
 		padding 8px
@@ -266,40 +267,72 @@ root(isDark)
 				> header
 					> select
 						width 100%
-						padding 12px 16px
-						border 1px solid #dcdfe6
+						padding 12px 14px
+						background isDark ? #282C37 : #fff
+						border 1px solid isDark ? #6a707d : #dcdfe6
 						border-radius 4px
-						color #606266
+						color isDark ? #fff : #606266
+						cursor pointer
 						transition border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1)
 
 						&:hover
-							border-color #c0c4cc
+							border-color isDark ? #a7aebd : #c0c4cc
 
 						&:focus
 						&:active
 							border-color $theme-color
 
+				> div
+					> .random
+						padding 32px 0
+						font-size 64px
+						color isDark ? #4e5961 : #d8d8d8
+
+					> .board
+						display grid
+						grid-gap 4px
+						width 300px
+						height 300px
+						margin 0 auto
+						color isDark ? #fff : #444
+
+						> div
+							background transparent
+							border solid 2px isDark ? #6a767f : #ddd
+							border-radius 6px
+							overflow hidden
+							cursor pointer
+
+							*
+								pointer-events none
+								user-select none
+								width 100%
+								height 100%
+
+							&[data-none]
+								border-color transparent
+
 		.card
 			max-width 400px
 			border-radius 4px
-			border 1px solid #ebeef5
 			background isDark ? #282C37 : #fff
-			color #303133
+			color isDark ? #fff : #303133
 			box-shadow 0 2px 12px 0 rgba(#000, 0.1)
 
 			> header
 				padding 18px 20px
-				border-bottom 1px solid #ebeef5
+				border-bottom 1px solid isDark ? #1c2023 : #ebeef5
 
 			> div
 				padding 20px
+				color isDark ? #fff : #606266
 
 	> footer
 		position sticky
 		bottom 0
 		padding 16px
-		background rgba(255, 255, 255, 0.9)
-		border-top solid 1px #c4cdd4
+		background rgba(isDark ? #191b22 : #fff, 0.9)
+		border-top solid 1px isDark ? #606266 : #c4cdd4
 
 		> .status
 			margin 0 0 16px 0
@@ -311,29 +344,3 @@ root(isDark)
 	root(false)
 
 </style>
-
-<style lang="stylus" module>
-.board
-	display grid
-	grid-gap 4px
-	width 300px
-	height 300px
-	margin 0 auto
-
-	> div
-		background transparent
-		border solid 2px #ddd
-		border-radius 6px
-		overflow hidden
-		cursor pointer
-
-		*
-			pointer-events none
-			user-select none
-			width 100%
-			height 100%
-
-		&[data-none]
-			border-color transparent
-
-</style>
diff --git a/src/client/app/common/views/components/games/reversi/reversi.vue b/src/client/app/common/views/components/games/reversi/reversi.vue
index dd3423a938..b6b13bdd97 100644
--- a/src/client/app/common/views/components/games/reversi/reversi.vue
+++ b/src/client/app/common/views/components/games/reversi/reversi.vue
@@ -1,5 +1,5 @@
 <template>
-<div class="mk-reversi">
+<div class="vchtoekanapleubgzioubdtmlkribzfd">
 	<div v-if="game">
 		<x-gameroom :game="game"/>
 	</div>
@@ -200,9 +200,9 @@ export default Vue.extend({
 <style lang="stylus" scoped>
 @import '~const.styl'
 
-.mk-reversi
-	color #677f84
-	background #fff
+root(isDark)
+	color isDark ? #fff : #677f84
+	background isDark ? #191b22 : #fff
 
 	> .matching
 		> h1
@@ -227,7 +227,7 @@ export default Vue.extend({
 			text-align center
 			font-weight normal
 			color #fff
-			background linear-gradient(to bottom, #8bca3e, #d6cf31)
+			background linear-gradient(to bottom, isDark ? #45730e : #8bca3e, isDark ? #464300 : #d6cf31)
 
 			& + p
 				margin 0
@@ -235,7 +235,7 @@ export default Vue.extend({
 				margin-bottom 12px
 				text-align center
 				font-size 14px
-				border-bottom solid 1px #d3d9dc
+				border-bottom solid 1px isDark ? #535f65 : #d3d9dc
 
 		> .play
 			margin 0 auto
@@ -250,14 +250,14 @@ export default Vue.extend({
 					padding 16px
 					font-size 14px
 					text-align left
-					background #f5f5f5
+					background isDark ? #282c37 : #f5f5f5
 					border-radius 8px
 
 		> section
 			margin 0 auto
 			padding 0 16px 16px 16px
 			max-width 500px
-			border-top solid 1px #d3d9dc
+			border-top solid 1px isDark ? #535f65 : #d3d9dc
 
 			> h2
 				margin 0
@@ -298,8 +298,9 @@ export default Vue.extend({
 		display block
 		margin 8px 0
 		padding 8px
-		color #677f84
-		border solid 1px #e1e5e8
+		color isDark ? #fff : #677f84
+		background isDark ? #282c37 : #fff
+		box-shadow 0 2px 16px rgba(#000, isDark ? 0.7 : 0.15)
 		border-radius 6px
 		cursor pointer
 
@@ -307,14 +308,11 @@ export default Vue.extend({
 			pointer-events none
 			user-select none
 
-		&:focus
-			border-color $theme-color
-
 		&:hover
-			background #f5f5f5
+			background isDark ? #313543 : #f5f5f5
 
 		&:active
-			background #eee
+			background isDark ? #1e222b : #eee
 
 		> .avatar
 			width 32px
@@ -324,4 +322,11 @@ export default Vue.extend({
 		> span
 			margin 0 8px
 			line-height 32px
+
+.vchtoekanapleubgzioubdtmlkribzfd[data-darkmode]
+	root(true)
+
+.vchtoekanapleubgzioubdtmlkribzfd:not([data-darkmode])
+	root(false)
+
 </style>
diff --git a/src/client/app/common/views/components/ui/form/button.vue b/src/client/app/common/views/components/ui/form/button.vue
index 3d81a83496..6e1475bc38 100644
--- a/src/client/app/common/views/components/ui/form/button.vue
+++ b/src/client/app/common/views/components/ui/form/button.vue
@@ -38,18 +38,18 @@ root(isDark)
 		margin 0
 		padding 12px 20px
 		font-size 14px
-		border 1px solid #dcdfe6
+		border 1px solid isDark ? #6d727d : #dcdfe6
 		border-radius 4px
 		outline none
 		box-shadow none
-		color #606266
+		color isDark ? #fff : #606266
 		transition 0.1s
 
 		&:hover
 		&:focus
 			color $theme-color
-			background rgba($theme-color, 0.12)
-			border-color rgba($theme-color, 0.3)
+			background rgba($theme-color, isDark ? 0.2 : 0.12)
+			border-color rgba($theme-color, isDark ? 0.5 : 0.3)
 
 		&:active
 			color darken($theme-color, 20%)
diff --git a/src/client/app/common/views/components/ui/form/radio.vue b/src/client/app/common/views/components/ui/form/radio.vue
index 1b337c3bea..831981bb3e 100644
--- a/src/client/app/common/views/components/ui/form/radio.vue
+++ b/src/client/app/common/views/components/ui/form/radio.vue
@@ -60,6 +60,10 @@ root(isDark)
 	> *
 		user-select none
 
+	&:hover
+		> .button
+			border solid 2px isDark ? rgba(#fff, 0.7) : rgba(#000, 0.54)
+
 	&.disabled
 		opacity 0.6
 		cursor not-allowed
@@ -89,7 +93,7 @@ root(isDark)
 		width 20px
 		height 20px
 		background none
-		border solid 2px isDark ? rgba(#fff, 0.7) : rgba(#000, 0.54)
+		border solid 2px isDark ? rgba(#fff, 0.6) : rgba(#000, 0.4)
 		border-radius 100%
 		transition inherit
 

From c016d212edac0bce7be84d23b9cc5df52fc8ade7 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Fri, 3 Aug 2018 21:11:53 +0900
Subject: [PATCH 5/6] Fix bug

---
 .../common/views/components/games/reversi/reversi.game.vue  | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/client/app/common/views/components/games/reversi/reversi.game.vue b/src/client/app/common/views/components/games/reversi/reversi.game.vue
index 57bdf7417b..bbfec2c1cc 100644
--- a/src/client/app/common/views/components/games/reversi/reversi.game.vue
+++ b/src/client/app/common/views/components/games/reversi/reversi.game.vue
@@ -3,12 +3,12 @@
 	<header><b>{{ blackUser | userName }}</b>(%i18n:common.reversi.black%) vs <b>{{ whiteUser | userName }}</b>(%i18n:common.reversi.white%)</header>
 
 	<div style="overflow: hidden">
-		<p class="turn" v-if="!iAmPlayer && !game.isEnded">{{ '%i18n:common.reversi.turn-of%'.replace('{}', Vue.filter('userName')(turnUser)) }}<mk-ellipsis/></p>
-		<p class="turn" v-if="logPos != logs.length">{{ '%i18n:common.reversi.past-turn-of%'.replace('{}', Vue.filter('userName')(turnUser)) }}</p>
+		<p class="turn" v-if="!iAmPlayer && !game.isEnded">{{ '%i18n:common.reversi.turn-of%'.replace('{}', $options.filters.userName(turnUser)) }}<mk-ellipsis/></p>
+		<p class="turn" v-if="logPos != logs.length">{{ '%i18n:common.reversi.past-turn-of%'.replace('{}', $options.filters.userName(turnUser)) }}</p>
 		<p class="turn1" v-if="iAmPlayer && !game.isEnded && !isMyTurn">%i18n:common.reversi.opponent-turn%<mk-ellipsis/></p>
 		<p class="turn2" v-if="iAmPlayer && !game.isEnded && isMyTurn" v-animate-css="{ classes: 'tada', iteration: 'infinite' }">%i18n:common.reversi.my-turn%</p>
 		<p class="result" v-if="game.isEnded && logPos == logs.length">
-			<template v-if="game.winner">{{ '%i18n:common.reversi.won%'.replace('{}', Vue.filter('userName')(game.winner)) }}{{ game.settings.isLlotheo ? ' (ロセオ)' : '' }}</template>
+			<template v-if="game.winner">{{ '%i18n:common.reversi.won%'.replace('{}', $options.filters.userName(game.winner)) }}{{ game.settings.isLlotheo ? ' (ロセオ)' : '' }}</template>
 			<template v-else>%i18n:common.reversi.drawn%</template>
 		</p>
 	</div>

From 000635252c6e0339764c386913504acc9d044ff2 Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Fri, 3 Aug 2018 21:30:44 +0900
Subject: [PATCH 6/6] wip

---
 locales/ja.yml                                |   8 +-
 .../games/reversi/reversi.index.vue           | 258 ++++++++++++++++++
 .../components/games/reversi/reversi.vue      | 208 +-------------
 3 files changed, 272 insertions(+), 202 deletions(-)
 create mode 100644 src/client/app/common/views/components/games/reversi/reversi.index.vue

diff --git a/locales/ja.yml b/locales/ja.yml
index 7e245d46a9..5ad0da28aa 100644
--- a/locales/ja.yml
+++ b/locales/ja.yml
@@ -132,6 +132,11 @@ common:
     pop-right: "右に出す"
 
 common/views/components/games/reversi/reversi.vue:
+  matching:
+    waiting-for: "{}を待っています"
+    cancel: "キャンセル"
+
+common/views/components/games/reversi/reversi.index.vue:
   title: "Misskey Reversi"
   sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"
   invite: "招待"
@@ -146,9 +151,6 @@ common/views/components/games/reversi/reversi.vue:
   game-state:
     ended: "終了"
     playing: "進行中"
-  matching:
-    waiting-for: "{}を待っています"
-    cancel: "キャンセル"
 
 common/views/components/games/reversi/reversi.room.vue:
   settings-of-the-game: "ゲームの設定"
diff --git a/src/client/app/common/views/components/games/reversi/reversi.index.vue b/src/client/app/common/views/components/games/reversi/reversi.index.vue
new file mode 100644
index 0000000000..026159a0fd
--- /dev/null
+++ b/src/client/app/common/views/components/games/reversi/reversi.index.vue
@@ -0,0 +1,258 @@
+<template>
+<div class="phgnkghfpyvkrvwiajkiuoxyrdaqpzcx">
+	<h1>%i18n:@title%</h1>
+	<p>%i18n:@sub-title%</p>
+	<div class="play">
+		<!--<el-button round>フリーマッチ(準備中)</el-button>-->
+		<form-button primary round @click="match">%i18n:@invite%</form-button>
+		<details>
+			<summary>%i18n:@rule%</summary>
+			<div>
+				<p>%i18n:@rule-desc%</p>
+				<dl>
+					<dt><b>%i18n:@mode-invite%</b></dt>
+					<dd>%i18n:@mode-invite-desc%</dd>
+				</dl>
+			</div>
+		</details>
+	</div>
+	<section v-if="invitations.length > 0">
+		<h2>%i18n:@invitations%</h2>
+		<div class="invitation" v-for="i in invitations" tabindex="-1" @click="accept(i)">
+			<mk-avatar class="avatar" :user="i.parent"/>
+			<span class="name"><b>{{ i.parent | userName }}</b></span>
+			<span class="username">@{{ i.parent.username }}</span>
+			<mk-time :time="i.createdAt"/>
+		</div>
+	</section>
+	<section v-if="myGames.length > 0">
+		<h2>%i18n:@my-games%</h2>
+		<a class="game" v-for="g in myGames" tabindex="-1" @click.prevent="go(g)" :href="`/reversi/${g.id}`">
+			<mk-avatar class="avatar" :user="g.user1"/>
+			<mk-avatar class="avatar" :user="g.user2"/>
+			<span><b>{{ g.user1 | userName }}</b> vs <b>{{ g.user2 | userName }}</b></span>
+			<span class="state">{{ g.isEnded ? '%i18n:@game-state.ended%' : '%i18n:@game-state.playing%' }}</span>
+		</a>
+	</section>
+	<section v-if="games.length > 0">
+		<h2>%i18n:@all-games%</h2>
+		<a class="game" v-for="g in games" tabindex="-1" @click.prevent="go(g)" :href="`/reversi/${g.id}`">
+			<mk-avatar class="avatar" :user="g.user1"/>
+			<mk-avatar class="avatar" :user="g.user2"/>
+			<span><b>{{ g.user1 | userName }}</b> vs <b>{{ g.user2 | userName }}</b></span>
+			<span class="state">{{ g.isEnded ? '%i18n:@game-state.ended%' : '%i18n:@game-state.playing%' }}</span>
+		</a>
+	</section>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+
+export default Vue.extend({
+	data() {
+		return {
+			games: [],
+			gamesFetching: true,
+			gamesMoreFetching: false,
+			myGames: [],
+			matching: null,
+			invitations: [],
+			connection: null,
+			connectionId: null
+		};
+	},
+
+	mounted() {
+		if (this.$store.getters.isSignedIn) {
+			this.connection = (this as any).os.streams.reversiStream.getConnection();
+			this.connectionId = (this as any).os.streams.reversiStream.use();
+
+			this.connection.on('invited', this.onInvited);
+
+			(this as any).api('games/reversi/games', {
+				my: true
+			}).then(games => {
+				this.myGames = games;
+			});
+
+			(this as any).api('games/reversi/invitations').then(invitations => {
+				this.invitations = this.invitations.concat(invitations);
+			});
+		}
+
+		(this as any).api('games/reversi/games').then(games => {
+			this.games = games;
+			this.gamesFetching = false;
+		});
+	},
+
+	beforeDestroy() {
+		if (this.connection) {
+			this.connection.off('invited', this.onInvited);
+			(this as any).os.streams.reversiStream.dispose(this.connectionId);
+		}
+	},
+
+	methods: {
+		go(game) {
+			(this as any).api('games/reversi/games/show', {
+				gameId: game.id
+			}).then(game => {
+				this.$emit('go', game);
+			});
+		},
+
+		match() {
+			(this as any).apis.input({
+				title: '%i18n:@enter-username%'
+			}).then(username => {
+				(this as any).api('users/show', {
+					username
+				}).then(user => {
+					(this as any).api('games/reversi/match', {
+						userId: user.id
+					}).then(res => {
+						if (res == null) {
+							this.$emit('matching', user);
+						} else {
+							this.$emit('go', res);
+						}
+					});
+				});
+			});
+		},
+
+		accept(invitation) {
+			(this as any).api('games/reversi/match', {
+				userId: invitation.parent.id
+			}).then(game => {
+				if (game) {
+					this.$emit('go', game);
+				}
+			});
+		},
+
+		onInvited(invite) {
+			this.invitations.unshift(invite);
+		}
+	}
+});
+</script>
+
+<style lang="stylus" scoped>
+@import '~const.styl'
+
+root(isDark)
+	> h1
+		margin 0
+		padding 24px
+		font-size 24px
+		text-align center
+		font-weight normal
+		color #fff
+		background linear-gradient(to bottom, isDark ? #45730e : #8bca3e, isDark ? #464300 : #d6cf31)
+
+		& + p
+			margin 0
+			padding 12px
+			margin-bottom 12px
+			text-align center
+			font-size 14px
+			border-bottom solid 1px isDark ? #535f65 : #d3d9dc
+
+	> .play
+		margin 0 auto
+		padding 0 16px
+		max-width 500px
+		text-align center
+
+		> details
+			margin 8px 0
+
+			> div
+				padding 16px
+				font-size 14px
+				text-align left
+				background isDark ? #282c37 : #f5f5f5
+				border-radius 8px
+
+	> section
+		margin 0 auto
+		padding 0 16px 16px 16px
+		max-width 500px
+		border-top solid 1px isDark ? #535f65 : #d3d9dc
+
+		> h2
+			margin 0
+			padding 16px 0 8px 0
+			font-size 16px
+			font-weight bold
+
+	.invitation
+		margin 8px 0
+		padding 8px
+		color isDark ? #fff : #677f84
+		background isDark ? #282c37 : #fff
+		box-shadow 0 2px 16px rgba(#000, isDark ? 0.7 : 0.15)
+		border-radius 6px
+		cursor pointer
+
+		*
+			pointer-events none
+			user-select none
+
+		&:focus
+			border-color $theme-color
+
+		&:hover
+			background isDark ? #313543 : #f5f5f5
+
+		&:active
+			background isDark ? #1e222b : #eee
+
+		> .avatar
+			width 32px
+			height 32px
+			border-radius 100%
+
+		> span
+			margin 0 8px
+			line-height 32px
+
+	.game
+		display block
+		margin 8px 0
+		padding 8px
+		color isDark ? #fff : #677f84
+		background isDark ? #282c37 : #fff
+		box-shadow 0 2px 16px rgba(#000, isDark ? 0.7 : 0.15)
+		border-radius 6px
+		cursor pointer
+
+		*
+			pointer-events none
+			user-select none
+
+		&:hover
+			background isDark ? #313543 : #f5f5f5
+
+		&:active
+			background isDark ? #1e222b : #eee
+
+		> .avatar
+			width 32px
+			height 32px
+			border-radius 100%
+
+		> span
+			margin 0 8px
+			line-height 32px
+
+.phgnkghfpyvkrvwiajkiuoxyrdaqpzcx[data-darkmode]
+	root(true)
+
+.phgnkghfpyvkrvwiajkiuoxyrdaqpzcx:not([data-darkmode])
+	root(false)
+
+</style>
diff --git a/src/client/app/common/views/components/games/reversi/reversi.vue b/src/client/app/common/views/components/games/reversi/reversi.vue
index b6b13bdd97..4169a5465a 100644
--- a/src/client/app/common/views/components/games/reversi/reversi.vue
+++ b/src/client/app/common/views/components/games/reversi/reversi.vue
@@ -10,49 +10,7 @@
 		</div>
 	</div>
 	<div class="index" v-else>
-		<h1>%i18n:@title%</h1>
-		<p>%i18n:@sub-title%</p>
-		<div class="play">
-			<!--<el-button round>フリーマッチ(準備中)</el-button>-->
-			<form-button primary round @click="match">%i18n:@invite%</form-button>
-			<details>
-				<summary>%i18n:@rule%</summary>
-				<div>
-					<p>%i18n:@rule-desc%</p>
-					<dl>
-						<dt><b>%i18n:@mode-invite%</b></dt>
-						<dd>%i18n:@mode-invite-desc%</dd>
-					</dl>
-				</div>
-			</details>
-		</div>
-		<section v-if="invitations.length > 0">
-			<h2>%i18n:@invitations%</h2>
-			<div class="invitation" v-for="i in invitations" tabindex="-1" @click="accept(i)">
-				<mk-avatar class="avatar" :user="i.parent"/>
-				<span class="name"><b>{{ i.parent | userName }}</b></span>
-				<span class="username">@{{ i.parent.username }}</span>
-				<mk-time :time="i.createdAt"/>
-			</div>
-		</section>
-		<section v-if="myGames.length > 0">
-			<h2>%i18n:@my-games%</h2>
-			<a class="game" v-for="g in myGames" tabindex="-1" @click.prevent="go(g)" :href="`/reversi/${g.id}`">
-				<mk-avatar class="avatar" :user="g.user1"/>
-				<mk-avatar class="avatar" :user="g.user2"/>
-				<span><b>{{ g.user1 | userName }}</b> vs <b>{{ g.user2 | userName }}</b></span>
-				<span class="state">{{ g.isEnded ? '%i18n:@game-state.ended%' : '%i18n:@game-state.playing%' }}</span>
-			</a>
-		</section>
-		<section v-if="games.length > 0">
-			<h2>%i18n:@all-games%</h2>
-			<a class="game" v-for="g in games" tabindex="-1" @click.prevent="go(g)" :href="`/reversi/${g.id}`">
-				<mk-avatar class="avatar" :user="g.user1"/>
-				<mk-avatar class="avatar" :user="g.user2"/>
-				<span><b>{{ g.user1 | userName }}</b> vs <b>{{ g.user2 | userName }}</b></span>
-				<span class="state">{{ g.isEnded ? '%i18n:@game-state.ended%' : '%i18n:@game-state.playing%' }}</span>
-			</a>
-		</section>
+		<x-index @go="onGo" @matching="onMatching"/>
 	</div>
 </div>
 </template>
@@ -60,10 +18,12 @@
 <script lang="ts">
 import Vue from 'vue';
 import XGameroom from './reversi.gameroom.vue';
+import XIndex from './reversi.index.vue';
 
 export default Vue.extend({
 	components: {
-		XGameroom
+		XGameroom,
+		XIndex
 	},
 
 	props: ['initGame'],
@@ -71,12 +31,7 @@ export default Vue.extend({
 	data() {
 		return {
 			game: null,
-			games: [],
-			gamesFetching: true,
-			gamesMoreFetching: false,
-			myGames: [],
 			matching: null,
-			invitations: [],
 			connection: null,
 			connectionId: null,
 			pingClock: null
@@ -101,17 +56,6 @@ export default Vue.extend({
 			this.connectionId = (this as any).os.streams.reversiStream.use();
 
 			this.connection.on('matched', this.onMatched);
-			this.connection.on('invited', this.onInvited);
-
-			(this as any).api('games/reversi/games', {
-				my: true
-			}).then(games => {
-				this.myGames = games;
-			});
-
-			(this as any).api('games/reversi/invitations').then(invitations => {
-				this.invitations = this.invitations.concat(invitations);
-			});
 
 			this.pingClock = setInterval(() => {
 				if (this.matching) {
@@ -122,17 +66,11 @@ export default Vue.extend({
 				}
 			}, 3000);
 		}
-
-		(this as any).api('games/reversi/games').then(games => {
-			this.games = games;
-			this.gamesFetching = false;
-		});
 	},
 
 	beforeDestroy() {
 		if (this.connection) {
 			this.connection.off('matched', this.onMatched);
-			this.connection.off('invited', this.onInvited);
 			(this as any).os.streams.reversiStream.dispose(this.connectionId);
 
 			clearInterval(this.pingClock);
@@ -140,33 +78,13 @@ export default Vue.extend({
 	},
 
 	methods: {
-		go(game) {
-			(this as any).api('games/reversi/games/show', {
-				gameId: game.id
-			}).then(game => {
-				this.matching = null;
-				this.game = game;
-			});
+		onGo(game) {
+			this.matching = null;
+			this.game = game;
 		},
 
-		match() {
-			(this as any).apis.input({
-				title: '%i18n:@enter-username%'
-			}).then(username => {
-				(this as any).api('users/show', {
-					username
-				}).then(user => {
-					(this as any).api('games/reversi/match', {
-						userId: user.id
-					}).then(res => {
-						if (res == null) {
-							this.matching = user;
-						} else {
-							this.game = res;
-						}
-					});
-				});
-			});
+		onMatching(user) {
+			this.matching = user;
 		},
 
 		cancel() {
@@ -188,10 +106,6 @@ export default Vue.extend({
 		onMatched(game) {
 			this.matching = null;
 			this.game = game;
-		},
-
-		onInvited(invite) {
-			this.invitations.unshift(invite);
 		}
 	}
 });
@@ -219,110 +133,6 @@ root(isDark)
 			text-align center
 			border-top dashed 1px #c4cdd4
 
-	> .index
-		> h1
-			margin 0
-			padding 24px
-			font-size 24px
-			text-align center
-			font-weight normal
-			color #fff
-			background linear-gradient(to bottom, isDark ? #45730e : #8bca3e, isDark ? #464300 : #d6cf31)
-
-			& + p
-				margin 0
-				padding 12px
-				margin-bottom 12px
-				text-align center
-				font-size 14px
-				border-bottom solid 1px isDark ? #535f65 : #d3d9dc
-
-		> .play
-			margin 0 auto
-			padding 0 16px
-			max-width 500px
-			text-align center
-
-			> details
-				margin 8px 0
-
-				> div
-					padding 16px
-					font-size 14px
-					text-align left
-					background isDark ? #282c37 : #f5f5f5
-					border-radius 8px
-
-		> section
-			margin 0 auto
-			padding 0 16px 16px 16px
-			max-width 500px
-			border-top solid 1px isDark ? #535f65 : #d3d9dc
-
-			> h2
-				margin 0
-				padding 16px 0 8px 0
-				font-size 16px
-				font-weight bold
-
-	.invitation
-		margin 8px 0
-		padding 8px
-		border solid 1px #e1e5e8
-		border-radius 6px
-		cursor pointer
-
-		*
-			pointer-events none
-			user-select none
-
-		&:focus
-			border-color $theme-color
-
-		&:hover
-			background #f5f5f5
-
-		&:active
-			background #eee
-
-		> .avatar
-			width 32px
-			height 32px
-			border-radius 100%
-
-		> span
-			margin 0 8px
-			line-height 32px
-
-	.game
-		display block
-		margin 8px 0
-		padding 8px
-		color isDark ? #fff : #677f84
-		background isDark ? #282c37 : #fff
-		box-shadow 0 2px 16px rgba(#000, isDark ? 0.7 : 0.15)
-		border-radius 6px
-		cursor pointer
-
-		*
-			pointer-events none
-			user-select none
-
-		&:hover
-			background isDark ? #313543 : #f5f5f5
-
-		&:active
-			background isDark ? #1e222b : #eee
-
-		> .avatar
-			width 32px
-			height 32px
-			border-radius 100%
-
-		> span
-			margin 0 8px
-			line-height 32px
-
 .vchtoekanapleubgzioubdtmlkribzfd[data-darkmode]
 	root(true)