From a5d5668613d9a3d04c9a4f8b69122d02b7322137 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Sat, 22 Dec 2018 15:37:48 +0100
Subject: [PATCH] Almost finished: just translations TODO

---
 public/javascripts/components/game.js         |  99 +++++++------
 .../javascripts/components/problemSummary.js  |   6 +-
 public/javascripts/components/problems.js     |  31 +++--
 public/javascripts/components/rules.js        |   6 +-
 public/javascripts/variant.js                 |   9 +-
 public/javascripts/variants/Crazyhouse.js     |   9 +-
 public/stylesheets/index.sass                 |   4 -
 public/stylesheets/layout.sass                |   6 +
 public/stylesheets/variant.sass               | 131 +++++++++++-------
 routes/all.js                                 |  62 ++++++---
 views/index.pug                               |   8 +-
 views/variant.pug                             |  24 ++--
 views/welcome/en.pug                          |   2 +-
 views/welcome/fr.pug                          |   2 +-
 14 files changed, 246 insertions(+), 153 deletions(-)

diff --git a/public/javascripts/components/game.js b/public/javascripts/components/game.js
index 773bc1f1..e2e908a0 100644
--- a/public/javascripts/components/game.js
+++ b/public/javascripts/components/game.js
@@ -40,7 +40,6 @@ Vue.component('my-game', {
 	},
 	render(h) {
 		const [sizeX,sizeY] = [V.size.x,V.size.y];
-		const smallScreen = (window.innerWidth <= 420);
 		// Precompute hints squares to facilitate rendering
 		let hintSquares = doubleArray(sizeX, sizeY, false);
 		this.possibleMoves.forEach(m => { hintSquares[m.end.x][m.end.y] = true; });
@@ -56,10 +55,11 @@ Vue.component('my-game', {
 				attrs: { "aria-label": 'New online game' },
 				'class': {
 					"tooltip": true,
+					"play": true,
 					"bottom": true, //display below
 					"seek": this.seek,
 					"playing": this.mode == "human",
-					"small": smallScreen,
+					"spaceright": true,
 				},
 			},
 			[h('i', { 'class': { "material-icons": true } }, "accessibility")])
@@ -73,9 +73,10 @@ Vue.component('my-game', {
 					attrs: { "aria-label": 'New game VS computer' },
 					'class': {
 						"tooltip":true,
+						"play": true,
 						"bottom": true,
 						"playing": this.mode == "computer",
-						"small": smallScreen,
+						"spaceright": true,
 					},
 				},
 				[h('i', { 'class': { "material-icons": true } }, "computer")])
@@ -90,9 +91,10 @@ Vue.component('my-game', {
 					attrs: { "aria-label": 'New IRL game' },
 					'class': {
 						"tooltip":true,
+						"play": true,
 						"bottom": true,
 						"playing": this.mode == "friend",
-						"small": smallScreen,
+						"spaceright": true,
 					},
 				},
 				[h('i', { 'class': { "material-icons": true } }, "people")])
@@ -105,27 +107,33 @@ Vue.component('my-game', {
 				? parseFloat(window.getComputedStyle(square00).width.slice(0,-2))
 				: 0;
 			const settingsBtnElt = document.getElementById("settingsBtn");
-			const indicWidth = !!settingsBtnElt //-2 for border:
-				? parseFloat(window.getComputedStyle(settingsBtnElt).height.slice(0,-2)) - 2
-				: (smallScreen ? 31 : 37);
+			const settingsStyle = !!settingsBtnElt
+				? window.getComputedStyle(settingsBtnElt)
+				: {width:"46px", height:"26px"};
+			const [indicWidth,indicHeight] = //[44,24];
+			[
+				// NOTE: -2 for border
+				parseFloat(settingsStyle.width.slice(0,-2)) - 2,
+				parseFloat(settingsStyle.height.slice(0,-2)) - 2
+			];
+			let aboveBoardElts = [];
 			if (["chat","human"].includes(this.mode))
 			{
 				const connectedIndic = h(
 					'div',
 					{
 						"class": {
-							"topindicator": true,
 							"indic-left": true,
 							"connected": this.oppConnected,
 							"disconnected": !this.oppConnected,
 						},
 						style: {
 							"width": indicWidth + "px",
-							"height": indicWidth + "px",
+							"height": indicHeight + "px",
 						},
 					}
 				);
-				elementArray.push(connectedIndic);
+				aboveBoardElts.push(connectedIndic);
 			}
 			if (this.mode == "chat")
 			{
@@ -139,15 +147,14 @@ Vue.component('my-game', {
 						},
 						'class': {
 							"tooltip": true,
-							"topindicator": true,
+							"play": true,
+							"above-board": true,
 							"indic-left": true,
-							"settings-btn": !smallScreen,
-							"settings-btn-small": smallScreen,
 						},
 					},
 					[h('i', { 'class': { "material-icons": true } }, "chat")]
 				);
-				elementArray.push(chatButton);
+				aboveBoardElts.push(chatButton);
 			}
 			else if (this.mode == "computer")
 			{
@@ -161,32 +168,30 @@ Vue.component('my-game', {
 						},
 						'class': {
 							"tooltip": true,
-							"topindicator": true,
+							"play": true,
+							"above-board": true,
 							"indic-left": true,
-							"settings-btn": !smallScreen,
-							"settings-btn-small": smallScreen,
 						},
 					},
 					[h('i', { 'class': { "material-icons": true } }, "clear")]
 				);
-				elementArray.push(clearButton);
+				aboveBoardElts.push(clearButton);
 			}
 			const turnIndic = h(
 				'div',
 				{
 					"class": {
-						"topindicator": true,
 						"indic-right": true,
 						"white-turn": this.vr.turn=="w",
 						"black-turn": this.vr.turn=="b",
 					},
 					style: {
 						"width": indicWidth + "px",
-						"height": indicWidth + "px",
+						"height": indicHeight + "px",
 					},
 				}
 			);
-			elementArray.push(turnIndic);
+			aboveBoardElts.push(turnIndic);
 			const settingsBtn = h(
 				'button',
 				{
@@ -197,15 +202,20 @@ Vue.component('my-game', {
 					},
 					'class': {
 						"tooltip": true,
-						"topindicator": true,
+						"play": true,
+						"above-board": true,
 						"indic-right": true,
-						"settings-btn": !smallScreen,
-						"settings-btn-small": smallScreen,
 					},
 				},
 				[h('i', { 'class': { "material-icons": true } }, "settings")]
 			);
-			elementArray.push(settingsBtn);
+			aboveBoardElts.push(settingsBtn);
+			elementArray.push(
+				h('div',
+					{ "class": { "aboveboard-wrapper": true } },
+					aboveBoardElts
+				)
+			);
 			if (this.mode == "problem")
 			{
 				// Show problem instructions
@@ -273,7 +283,10 @@ Vue.component('my-game', {
 				(!["idle","chat"].includes(this.mode) || this.cursor==this.vr.moves.length);
 			const gameDiv = h('div',
 				{
-					'class': { 'game': true },
+					'class': {
+						'game': true,
+						'clearer': true,
+					},
 				},
 				[_.range(sizeX).map(i => {
 					let ci = (this.mycolor=='w' ? i : sizeX-i-1);
@@ -354,8 +367,8 @@ Vue.component('my-game', {
 							attrs: { "aria-label": 'Resign' },
 							'class': {
 								"tooltip":true,
+								"play": true,
 								"bottom": true,
-								"small": smallScreen,
 							},
 						},
 						[h('i', { 'class': { "material-icons": true } }, "flag")])
@@ -370,7 +383,7 @@ Vue.component('my-game', {
 							on: { click: e => this.undo() },
 							attrs: { "aria-label": 'Undo' },
 							"class": {
-								"small": smallScreen,
+								"play": true,
 								"spaceleft": true,
 							},
 						},
@@ -379,7 +392,10 @@ Vue.component('my-game', {
 						{
 							on: { click: e => this.play() },
 							attrs: { "aria-label": 'Play' },
-							"class": { "small": smallScreen },
+							"class": {
+								"play": true,
+								"spaceleft": true,
+							},
 						},
 						[h('i', { 'class': { "material-icons": true } }, "fast_forward")]),
 					]
@@ -394,7 +410,7 @@ Vue.component('my-game', {
 							on: { click: this.undoInGame },
 							attrs: { "aria-label": 'Undo' },
 							"class": {
-								"small": smallScreen,
+								"play": true,
 								"spaceleft": true,
 							},
 						},
@@ -404,7 +420,10 @@ Vue.component('my-game', {
 						{
 							on: { click: () => { this.mycolor = this.vr.getOppCol(this.mycolor) } },
 							attrs: { "aria-label": 'Flip' },
-							"class": { "small": smallScreen },
+							"class": {
+								"play": true,
+								"spaceleft": true,
+							},
 						},
 						[h('i', { 'class': { "material-icons": true } }, "cached")]
 					),
@@ -419,13 +438,13 @@ Vue.component('my-game', {
 				{
 					myReservePiecesArray.push(h('div',
 					{
-						'class': {'board':true, ['board'+sizeY]:true},
+						'class': {'board':true, ['board'+sizeY+'-reserve']:true},
 						attrs: { id: this.getSquareId({x:sizeX+shiftIdx,y:i}) }
 					},
 					[
 						h('img',
 						{
-							'class': {"piece":true},
+							'class': {"piece":true, "reserve":true},
 							attrs: {
 								"src": "/images/pieces/" +
 									this.vr.getReservePpath(this.mycolor,i) + ".svg",
@@ -443,13 +462,13 @@ Vue.component('my-game', {
 				{
 					oppReservePiecesArray.push(h('div',
 					{
-						'class': {'board':true, ['board'+sizeY]:true},
+						'class': {'board':true, ['board'+sizeY+'-reserve']:true},
 						attrs: { id: this.getSquareId({x:sizeX+(1-shiftIdx),y:i}) }
 					},
 					[
 						h('img',
 						{
-							'class': {"piece":true},
+							'class': {"piece":true, "reserve":true},
 							attrs: {
 								"src": "/images/pieces/" +
 									this.vr.getReservePpath(oppCol,i) + ".svg",
@@ -801,6 +820,7 @@ Vue.component('my-game', {
 			),
 			h('button',
 				{
+					attrs: { id: "sendChatBtn"},
 					on: { click: this.sendChat },
 					domProps: { innerHTML: "Send" },
 				}
@@ -883,6 +903,7 @@ Vue.component('my-game', {
 						[
 							h('h3',
 								{
+									"class": { clickable: true },
 									domProps: { innerHTML: "Show solution" },
 									on: { click: this.toggleShowSolution },
 								}
@@ -921,10 +942,10 @@ Vue.component('my-game', {
 			{
 				'class': {
 					"col-sm-12":true,
-					"col-md-8":true,
-					"col-md-offset-2":true,
-					"col-lg-6":true,
-					"col-lg-offset-3":true,
+					"col-md-10":true,
+					"col-md-offset-1":true,
+					"col-lg-8":true,
+					"col-lg-offset-2":true,
 				},
 				// NOTE: click = mousedown + mouseup
 				on: {
diff --git a/public/javascripts/components/problemSummary.js b/public/javascripts/components/problemSummary.js
index e7e1db7c..6b006cbb 100644
--- a/public/javascripts/components/problemSummary.js
+++ b/public/javascripts/components/problemSummary.js
@@ -2,11 +2,11 @@
 Vue.component('my-problem-summary', {
 	props: ['prob','preview'],
 	template: `
-		<div class="problem row" @click="showProblem()">
-			<div class="col-sm-12 col-md-6 col-lg-3 diagram"
+		<div class="row problem clickable" @click="showProblem()">
+			<div class="col-sm-6 diagram"
 				v-html="getDiagram(prob.fen)">
 			</div>
-			<div class="col-sm-12 col-md-6 col-lg-9">
+			<div class="col-sm-6">
 				<p v-html="prob.instructions"></p>
 				<p v-if="preview" v-html="prob.solution"></p>
 				<p v-else class="problem-time">{{ timestamp2date(prob.added) }}</p>
diff --git a/public/javascripts/components/problems.js b/public/javascripts/components/problems.js
index d9265a36..b67203a3 100644
--- a/public/javascripts/components/problems.js
+++ b/public/javascripts/components/problems.js
@@ -11,10 +11,21 @@ Vue.component('my-problems', {
 		};
 	},
 	template: `
-		<div>
-			<button @click="fetchProblems('backward')">Previous</button>
-			<button @click="fetchProblems('forward')">Next</button>
-			<button @click="showNewproblemModal">New</button>
+		<div class="col-sm-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2">
+			<div id="problemControls" class="button-group">
+				<button aria-label="Load previous problems" class="tooltip"
+						@click="fetchProblems('backward')">
+					<i class="material-icons">skip_previous</i>
+				</button>
+				<button aria-label="Add a problem" class="tooltip"
+						@click="showNewproblemModal">
+					New
+				</button>
+				<button aria-label="Load next problems" class="tooltip"
+						@click="fetchProblems('forward')">
+					<i class="material-icons">skip_next</i>
+				</button>
+			</div>
 			<my-problem-summary v-on:show-problem="bubbleUp(p)"
 				v-for="(p,idx) in sortedProblems"
 				v-bind:prob="p" v-bind:preview="false" v-bind:key="idx">
@@ -46,9 +57,9 @@ Vue.component('my-problems', {
 					<label for="modal-newproblem" class="modal-close"></label>
 					<my-problem-summary v-bind:prob="newProblem" v-bind:preview="true">
 					</my-problem-summary>
-					<div class="col-sm-12 col-md-6 col-lg-3 col-lg-offset-3 topspace">
-						<button @click="sendNewProblem()">Send</button>
+					<div class="button-group">
 						<button @click="newProblem.stage='nothing'">Cancel</button>
+						<button @click="sendNewProblem()">Send</button>
 					</div>
 				</div>
 			</div>
@@ -59,9 +70,6 @@ Vue.component('my-problems', {
 			// Newest problem first
 			return this.problems.sort((p1,p2) => { return p2.added - p1.added; });
 		},
-		mailErrProblem: function() {
-			return "mailto:contact@vchess.club?subject=[" + variant + " problems] error";
-		},
 	},
 	methods: {
 		// Propagate "show problem" event to parent component (my-variant)
@@ -69,7 +77,6 @@ Vue.component('my-problems', {
 			this.$emit('show-problem', JSON.stringify(problem));
 		},
 		fetchProblems: function(direction) {
-			return; //TODO: re-activate after server side is implemented (see routes/all.js)
 			if (this.problems.length == 0)
 				return; //what could we do?!
 			// Search for newest date (or oldest)
@@ -96,6 +103,10 @@ Vue.component('my-problems', {
 		previewNewProblem: function() {
 			if (!V.IsGoodFen(this.newProblem.fen))
 				return alert("Bad FEN string");
+			if (this.newProblem.instructions.length == 0)
+				return alert("Empty instructions");
+			if (this.newProblem.solution.length == 0)
+				return alert("Empty solution");
 			this.newProblem.stage = "preview";
 		},
 		sendNewProblem: function() {
diff --git a/public/javascripts/components/rules.js b/public/javascripts/components/rules.js
index 829bf3b1..d8aaa0fc 100644
--- a/public/javascripts/components/rules.js
+++ b/public/javascripts/components/rules.js
@@ -3,7 +3,11 @@ Vue.component('my-rules', {
 	data: function() {
 		return { content: "" };
 	},
-	template: `<div v-html="content" class="section-content"></div>`,
+	template: `
+		<div class="col-sm-12 col-md-10 col-md-offset-1 col-lg-8 col-lg-offset-2">
+			<div v-html="content" class="section-content"></div>
+		</div>
+	`,
 	mounted: function() {
 		// AJAX request to get rules content (plain text, HTML)
 		ajax("/rules/" + variant, "GET", response => {
diff --git a/public/javascripts/variant.js b/public/javascripts/variant.js
index f77be329..d617c60a 100644
--- a/public/javascripts/variant.js
+++ b/public/javascripts/variant.js
@@ -1,23 +1,26 @@
 new Vue({
 	el: "#variantPage",
 	data: {
-		display: "game", //default: play!
+		display: "play", //default: play!
 		problem: undefined, //current problem in view
 	},
 	created: function() {
 		const url = window.location.href;
 		const hashPos = url.indexOf("#");
+		console.log(hashPos + " " + url);
 		if (hashPos >= 0)
 			this.setDisplay(url.substr(hashPos+1));
 	},
 	methods: {
 		showProblem: function(problemTxt) {
 			this.problem = JSON.parse(problemTxt);
-			this.display = "game";
+			this.display = "play";
 		},
 		setDisplay: function(elt) {
 			this.display = elt;
-			document.getElementById("drawer-control").checked = false;
+			let menuToggle = document.getElementById("drawer-control");
+			if (!!menuToggle)
+				menuToggle.checked = false;
 		},
 	},
 });
diff --git a/public/javascripts/variants/Crazyhouse.js b/public/javascripts/variants/Crazyhouse.js
index 1fff1e91..f36bfcc4 100644
--- a/public/javascripts/variants/Crazyhouse.js
+++ b/public/javascripts/variants/Crazyhouse.js
@@ -11,10 +11,9 @@ class CrazyhouseRules extends ChessRules
 		// 6) Check promoted array
 		if (!fenParsed.promoted)
 			return false;
-		fenpromoted = fenParsed.promoted;
-		if (fenpromoted == "-")
+		if (fenParsed.promoted == "-")
 			return true; //no promoted piece on board
-		const squares = fenpromoted.split(",");
+		const squares = fenParsed.promoted.split(",");
 		for (let square of squares)
 		{
 			const c = V.SquareToCoords(square);
@@ -48,8 +47,8 @@ class CrazyhouseRules extends ChessRules
 
 	getReserveFen()
 	{
-		let counts = _.map(_.range(10), 0);
-		for (let i=0; i<V.PIECES.length; i++)
+		let counts = new Array(10);
+		for (let i=0; i<V.PIECES.length-1; i++) //-1: no king reserve
 		{
 			counts[i] = this.reserve["w"][V.PIECES[i]];
 			counts[5+i] = this.reserve["b"][V.PIECES[i]];
diff --git a/public/stylesheets/index.sass b/public/stylesheets/index.sass
index 0c6d92a5..0d7803f4 100644
--- a/public/stylesheets/index.sass
+++ b/public/stylesheets/index.sass
@@ -42,7 +42,6 @@
 
 #helpMenu
   float: right
-  cursor: pointer
   @media screen and (max-width: 767px)
     .info-container
       p
@@ -50,7 +49,6 @@
 
 #flagMenu
   float: right
-  cursor: pointer
   margin-right: 10px
   @media screen and (max-width: 767px)
     margin-right: 5px
@@ -82,7 +80,6 @@
   margin-top: 0
   color: var(--a-link-color)
   text-decoration: underline
-  cursor: pointer
 
 #welcome
   max-width: 767px
@@ -111,5 +108,4 @@
           text-align: left
           border: 0
   #disableMsg
-    cursor: pointer
     color: darkred
diff --git a/public/stylesheets/layout.sass b/public/stylesheets/layout.sass
index 38b78b1e..14892250 100644
--- a/public/stylesheets/layout.sass
+++ b/public/stylesheets/layout.sass
@@ -33,6 +33,12 @@ a
 .emphasis
   font-style: italic
 
+.clickable
+  cursor: pointer
+
+.clearer
+  clear: both
+
 .red
   color: #cc3300
 
diff --git a/public/stylesheets/variant.sass b/public/stylesheets/variant.sass
index da55577f..f5599964 100644
--- a/public/stylesheets/variant.sass
+++ b/public/stylesheets/variant.sass
@@ -2,20 +2,23 @@
 
 #menuBar
   background: linear-gradient(#e66465, #9198e5)
-  height: 77px
+  height: 29px
   margin-bottom: 10px
   @media screen and (max-width: 767px)
     height: 100%
     margin-bottom: 0
   @media screen and (min-width: 768px)
     width: 100%
+    overflow: hidden
 
 a#homeLink
-  margin: 27px 0 0 10px
+  margin-left: 10px
+  margin-top: 2px
+  color: black
   display: inline-block
   @media screen and (max-width: 767px)
-    margin: 10px 0 0 10px
     display: block
+    margin: 5px 0 0 12px
 
 .info-container
   display: inline-block
@@ -27,9 +30,9 @@ a#homeLink
     color: black
   a, p
     display: inline-block
-    padding: 3px
+    padding: 0
     border: 1px solid black;
-    margin: 25px 0 0 15px
+    margin: 1px 0 0 15px
     @media screen and (max-width: 767px)
       margin-top: 10px
       display: block
@@ -37,45 +40,70 @@ a#homeLink
 #helpMenu
   @media screen and (min-width: 768px)
     float: right
-  cursor: pointer
-  @media screen and (max-width: 767px)
-    .info-container
-      p
-        margin-right: 5px
+  .info-container
+    p
+      margin: 1px 0 0 15px
 
 #flagMenu
   @media screen and (min-width: 768px)
+    margin-top: 1px
     float: right
-  cursor: pointer
   margin: 0 15px
   @media screen and (max-width: 767px)
-    margin-right: 5px
+    margin: 25px 5px 0 15px
   img
     display: inline-block
-    height: 30px
-    margin-top: 25px
+    margin: 0
+    height: 25px
 
 label.drawer-toggle
   padding: 0
   &::before
-    font-size: 2.5em;
-    max-height: 43px;
-    top: -10px;
-    left: 10px
+    font-size: 2em;
+    max-height: 32px;
+    top: -7px;
+    left: 5px
 
 // Game section:
 
-.topindicator
-  position: relative
+button.play
+  height: 24px
+  margin: 0
+  padding: 0 10px 24px 10px
+  box-sizing: border-box
   border: 1px solid brown
+button.play.spaceleft
+  margin-left: 15px
+button.play.spaceright
+  margin-right: 15px
+
+.aboveboard-wrapper
+  width: 80vh
+  margin: 0 auto
+  @media screen and (max-width: 767px)
+    width: 100%
+    margin: 0
+
+button.above-board
+  margin-left: 15px
+  margin-right: 15px
+
+i.material-icons
+  font-size: 24px
 
 .indic-left
+  border: 1px solid brown
   float: left
-  margin: 0 0 var(--universal-margin) 20px
+  margin: 0 0 var(--universal-margin) 10vh
+  @media screen and (max-width: 767px)
+    margin-left: 20px
 
 .indic-right
+  border: 1px solid brown
   float: right
-  margin: 0 20px var(--universal-margin) 0
+  margin: 0 10vh var(--universal-margin) 0
+  @media screen and (max-width: 767px)
+    margin-right: 20px
 
 .my-chatmsg
   color: black
@@ -83,18 +111,16 @@ label.drawer-toggle
 .opp-chatmsg
   color: blue
 
+// TODO: this fix is not good (button height 0 if chat overflow window height)
+#sendChatBtn
+  min-height: 42px
+
 .connected
   background-color: green
 
 .disconnected
   background-color: red
 
-.settings-btn
-  padding: 6px 7px 0 7px
-
-.settings-btn-small
-  padding: 0 3px
-
 .white-turn
   background-color: white
 
@@ -111,12 +137,12 @@ button.seek
   &:hover
     background-color: #cc99ff
 
+.game.reserve-div
+  margin-bottom: 18px
+
 .reserve-count
   padding-left: 40%
 
-.reserve-div
-  margin-bottom: 20px
-
 .reserve-row-1
   margin-bottom: 15px
 
@@ -130,6 +156,10 @@ div.board8
   width: 12.5%
   padding-bottom: 12.5%
 
+div.board8-reserve
+  width: 10%
+  padding-bottom: 10%
+
 div.board10
   width: 10%
   padding-bottom: 10%
@@ -138,10 +168,16 @@ div.board11
   width: 9.09%
   padding-bottom: 9.1%
 
+// NOTE: no variants with reserve of size != 8
+
 .game
-  clear: both
+  width: 80vh
+  margin: 0 auto
   .board
     cursor: pointer
+  @media screen and (max-width: 767px)
+    width: 100%
+    margin: 0
 
 #choices
   margin: 0 auto 0 auto
@@ -226,6 +262,12 @@ img.ghost
   margin-left: 0
   margin-right: 0
 
+#modal-eog+div .card
+  overflow: hidden
+
+#actions
+  margin: 10px 0
+
 // Rules section:
 
 .warn
@@ -264,9 +306,6 @@ figure.diagram-container
     clear: both
     padding-top: 5px
 
-.spaceleft
-  margin-left: 30px
-
 p.boxed
   background-color: #FFCC66
   padding: 5px
@@ -323,21 +362,19 @@ ul:not(.browser-default) > li
 #problem-solution
   display: none
 
-.topspace
-  margin-top: 15px
-
-.problem
-  cursor: pointer
-  margin-bottom: 15px
-
 #solution-div h3
-  cursor: pointer
+  background-color: lightgrey
+  padding: 3px 5px
 
 .newproblem-form, .newproblem-preview
   max-width: 90%
 
-.clickable
-  cursor: pointer
+#problemControls
+  width: 75%
+  margin: 0 auto
+  @media screen and (max-width: 767px)
+    width: 100%
+    margin: 0
 
-.clearer
-  clear: both
+.problem
+  margin: 10px 0
diff --git a/routes/all.js b/routes/all.js
index 691662f1..49ca802f 100644
--- a/routes/all.js
+++ b/routes/all.js
@@ -4,6 +4,7 @@ const createError = require('http-errors');
 const sqlite3 = require('sqlite3');//.verbose();
 const db = new sqlite3.Database('db/vchess.sqlite');
 const sanitizeHtml = require('sanitize-html');
+const MaxNbProblems = 2;
 
 const supportedLang = ["fr","en"];
 function selectLanguage(req, res)
@@ -49,28 +50,30 @@ router.get('/', function(req, res, next) {
 });
 
 // Variant
-router.get("/:vname([a-zA-Z0-9]+)", (req,res,next) => {
-	const vname = req.params["vname"];
+router.get("/:variant([a-zA-Z0-9]+)", (req,res,next) => {
+	const vname = req.params["variant"];
 	db.serialize(function() {
 		db.all("SELECT * FROM Variants WHERE name='" + vname + "'", (err,variant) => {
 			if (!!err)
 				return next(err);
 			if (!variant || variant.length==0)
 				return next(createError(404));
-			// TODO (later...) get only n=100(?) most recent problems
-			db.all("SELECT * FROM Problems WHERE variant='" + vname + "'",
-				(err2,problems) => {
-					if (!!err2)
-						return next(err2);
-					res.render('variant', {
-						title: vname + ' Variant',
-						variant: vname,
-						problemArray: problems,
-						lang: selectLanguage(req, res),
-						languages: supportedLang,
-					});
-				}
-			);
+			// Get only N most recent problems
+			const query2 = "SELECT * FROM Problems " +
+				"WHERE variant='" + vname + "' " +
+				"ORDER BY added DESC " +
+				"LIMIT " + MaxNbProblems;
+			db.all(query2, (err2,problems) => {
+				if (!!err2)
+					return next(err2);
+				res.render('variant', {
+					title: vname + ' Variant',
+					variant: vname,
+					problemArray: problems,
+					lang: selectLanguage(req, res),
+					languages: supportedLang,
+				});
+			});
 		});
 	});
 });
@@ -83,13 +86,26 @@ router.get("/rules/:variant([a-zA-Z0-9]+)", (req,res) => {
 	res.render("rules/" + req.params["variant"] + "/" + lang);
 });
 
-// Fetch 10 previous or next problems (AJAX)
+// Fetch N previous or next problems (AJAX)
 router.get("/problems/:variant([a-zA-Z0-9]+)", (req,res) => {
 	if (!req.xhr)
 		return res.json({errmsg: "Unauthorized access"});
-	// TODO: next or previous: in params + timedate (of current oldest or newest)
+	const vname = req.params["variant"];
+	const directionStr = (req.query.direction == "forward" ? ">" : "<");
+	const lastDt = req.query.last_dt;
+	if (!lastDt.match(/[0-9]+/))
+		return res.json({errmsg: "Bad timestamp"});
 	db.serialize(function() {
-		//TODO
+		const query = "SELECT * FROM Problems " +
+			"WHERE variant='" + vname + "' " +
+			"  AND added " + directionStr + " " + lastDt + " " +
+			"ORDER BY added " + (directionStr=="<" ? "DESC " : "") +
+			"LIMIT " + MaxNbProblems;
+		db.all(query, (err,problems) => {
+			if (!!err)
+				return res.json(err);
+			return res.json({problems: problems});
+		});
 	});
 });
 
@@ -103,8 +119,12 @@ router.post("/problems/:variant([a-zA-Z0-9]+)", (req,res) => {
 	const fen = req.body["fen"];
 	if (!fen.match(/^[a-zA-Z0-9, /-]*$/))
 		return res.json({errmsg: "Bad characters in FEN string"});
-	const instructions = sanitizeHtml(req.body["instructions"]);
-	const solution = sanitizeHtml(req.body["solution"]);
+	const instructions = sanitizeHtml(req.body["instructions"]).trim();
+	const solution = sanitizeHtml(req.body["solution"]).trim();
+	if (instructions.length == 0)
+		return res.json({errmsg: "Empty instructions"});
+	if (solution.length == 0)
+		return res.json({errmsg: "Empty solution"});
 	db.serialize(function() {
 		let stmt = db.prepare("INSERT INTO Problems " +
 			"(added,variant,fen,instructions,solution) VALUES (?,?,?,?,?)");
diff --git a/views/index.pug b/views/index.pug
index c8c90c52..81aafdb1 100644
--- a/views/index.pug
+++ b/views/index.pug
@@ -12,9 +12,11 @@ block content
 					.info-container
 						p vchess.club
 					img(src="/images/index/wildebeest.svg")
-				#flagMenu(onClick="document.getElementById('modalLang').checked=true")
+				#flagMenu.clickable(
+						onClick="document.getElementById('modalLang').checked=true")
 					img(src="/images/flags/" + lang + ".svg")
-				#helpMenu(onClick="document.getElementById('modalHelp').checked=true")
+				#helpMenu.clickable(
+						onClick="document.getElementById('modalHelp').checked=true")
 					.info-container
 						p Help
 		.row
@@ -27,7 +29,7 @@ block content
 		div(role="dialog")
 			#b4welcome.card.text-center.small-modal
 				h3.blue First visit?
-				p#readThis(@click="showWelcomeMsg") >>> Please read this <<<
+				p#readThis.clickable(@click="showWelcomeMsg") >>> Please read this <<<
 		case lang
 			when "en"
 				include welcome/en.pug
diff --git a/views/variant.pug b/views/variant.pug
index a3b846f7..9400ba58 100644
--- a/views/variant.pug
+++ b/views/variant.pug
@@ -12,32 +12,26 @@ block content
 				input#drawer-control.drawer(type="checkbox")
 				#menuBar
 					label.drawer-close(for="drawer-control")
-					a#homeLink.conditional-jump(href="/")
+					a#homeLink(href="/")
 						i.material-icons home
 					.info-container
-						a.conditional-jump(href="#rules" @click="setDisplay('rules')")
+						a(href="#rules" @click="setDisplay('rules')")
 							| Rules
-						a.conditional-jump(href="#play" @click="setDisplay('game')")
+						a(href="#play" @click="setDisplay('play')")
 							| Play!
-						a.conditional-jump(href="#problems" @click="setDisplay('problems')") 
+						a(href="#problems" @click="setDisplay('problems')") 
 							| Problems
-					#flagMenu.conditional-jump(
+					#flagMenu.clickable(
 							onClick="document.getElementById('modalLang').checked=true")
 						img(src="/images/flags/" + lang + ".svg")
-					#helpMenu.conditional-jump(
+					#helpMenu.clickable(
 							onClick="document.getElementById('modalHelp').checked=true")
 						.info-container
 							p Help
 		.row
-			.col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2(
-					v-show="display=='rules'")
-				my-rules
-			.col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2(
-					v-show="display=='game'")
-				my-game(v-bind:problem="problem")
-			.col-sm-12.col-md-10.col-md-offset-1.col-lg-8.col-lg-offset-2(
-					v-show="display=='problems'")
-				my-problems(v-on:show-problem="showProblem($event)")
+			my-rules(v-show="display=='rules'")
+			my-game(v-show="display=='play'" v-bind:problem="problem")
+			my-problems(v-show="display=='problems'" v-on:show-problem="showProblem($event)")
 		// (Some) Modals:
 		include modal-help.pug
 		include modal-lang.pug
diff --git a/views/welcome/en.pug b/views/welcome/en.pug
index 28fb943e..dbaf234c 100644
--- a/views/welcome/en.pug
+++ b/views/welcome/en.pug
@@ -46,6 +46,6 @@ div(role="dialog")
 				For informations about hundreds (if not thousands) of variants, you
 				can visit the excellent
 				#[a(href="https://www.chessvariants.com/") chessvariants] website.
-		p#disableMsg(@click="markAsVisited")
+		p#disableMsg.clickable(@click="markAsVisited")
 			| Click here to not show this message next time
 		p.smallfont Image credit: #[a(href=wikipediaUrl) Wikipedia]
diff --git a/views/welcome/fr.pug b/views/welcome/fr.pug
index d1bff4bf..33b5fb6f 100644
--- a/views/welcome/fr.pug
+++ b/views/welcome/fr.pug
@@ -46,6 +46,6 @@ div(role="dialog")
 				Pour s'informer sur des centaines de variantes (au moins), je vous invite à
 				visiter l'excellent site
 				#[a(href="https://www.chessvariants.com/") chessvariants].
-		p#disableMsg(@click="markAsVisited")
+		p#disableMsg.clickable(@click="markAsVisited")
 			| Cliquer ici pour ne pas montrer ce message la prochaine fois
 		p.smallfont Crédit image: #[a(href=wikipediaUrl) Wikipedia]
-- 
2.44.0