From da06a6eb0237123ce43fdb01cb06246b8b57f5e5 Mon Sep 17 00:00:00 2001
From: Benjamin Auder <benjamin.auder@somewhere>
Date: Sat, 15 Dec 2018 15:35:59 +0100
Subject: [PATCH] On the way to problems: saving state [not functional yet]

---
 .gitattributes                                |   1 +
 db/create.sql                                 |  29 +
 db/vchess.sqlite                              |   1 +
 package-lock.json                             | 573 +++++++++++++-----
 package.json                                  |   6 +-
 public/javascripts/components/game.js         |  12 +-
 .../javascripts/components/problemSummary.js  |  17 +
 public/javascripts/components/problems.js     |  94 ++-
 public/javascripts/components/rules.js        |  72 +--
 public/javascripts/utils/ajax.js              |  55 ++
 public/javascripts/utils/printDiagram.js      |  46 ++
 public/stylesheets/variant.sass               |  22 +
 routes/all.js                                 |  75 ++-
 sockets.js                                    | 222 ++++---
 variants.js                                   |  16 -
 views/index.pug                               |   1 -
 views/rules/Chess960.pug                      |   2 +-
 views/rules/Crazyhouse.pug                    |  15 +-
 views/rules/Switching.pug                     |   4 +
 views/rules/Ultima.pug                        |   6 +-
 views/variant.pug                             |   4 +
 21 files changed, 874 insertions(+), 399 deletions(-)
 create mode 100644 db/create.sql
 create mode 100644 db/vchess.sqlite
 create mode 100644 public/javascripts/components/problemSummary.js
 create mode 100644 public/javascripts/utils/ajax.js
 create mode 100644 public/javascripts/utils/printDiagram.js
 delete mode 100644 variants.js

diff --git a/.gitattributes b/.gitattributes
index d268ba5b..ac4ca112 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -4,3 +4,4 @@
 *.wav filter=fat
 *.mp3 filter=fat
 *.png filter=fat
+*.sqlite filter=fat
diff --git a/db/create.sql b/db/create.sql
new file mode 100644
index 00000000..aa2f3ae1
--- /dev/null
+++ b/db/create.sql
@@ -0,0 +1,29 @@
+create table Variants (
+	name varchar primary key,
+	description text
+);
+insert into Variants values
+	('Checkered', 'Shared pieces'),
+	('Zen', 'Reverse captures'),
+	('Atomic', 'Explosive captures'),
+	('Chess960', 'Standard rules'),
+	('Antiking', 'Keep antiking in check'),
+	('Magnetic', 'Laws of attraction'),
+	('Alice', 'Both sides of the mirror'),
+	('Grand', 'Big board'),
+	('Wildebeest', 'Balanced sliders & leapers'),
+	('Loser', 'Lose all pieces'),
+	('Crazyhouse', 'Captures reborn'),
+	('Switching', 'Exchange pieces positions'),
+	('Extinction', 'Capture all of a kind'),
+	('Ultima', 'Exotic captures');
+
+create table Problems (
+	added datetime,
+	variant varchar,
+	fen varchar,
+	instructions text,
+	solution text,
+	foreign key (variant) references Variants(name)
+);
+--PRAGMA foreign_keys = ON;
diff --git a/db/vchess.sqlite b/db/vchess.sqlite
new file mode 100644
index 00000000..9d7e4a2a
--- /dev/null
+++ b/db/vchess.sqlite
@@ -0,0 +1 @@
+#$# git-fat 25d1d22208da0bc58bf3076bfe684bbcb98894b2                16384
diff --git a/package-lock.json b/package-lock.json
index 88d9fd81..783e4095 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
-  "name": "vc",
-  "version": "0.0.0",
+  "name": "vchess",
+  "version": "0.1.0",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -304,6 +304,11 @@
         }
       }
     },
+    "array-uniq": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
+      "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY="
+    },
     "array-unique": {
       "version": "0.3.2",
       "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz",
@@ -816,6 +821,11 @@
         "upath": "^1.0.5"
       }
     },
+    "chownr": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz",
+      "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g=="
+    },
     "ci-info": {
       "version": "1.6.0",
       "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz",
@@ -935,7 +945,6 @@
       "version": "1.9.3",
       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
       "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
-      "dev": true,
       "requires": {
         "color-name": "1.1.3"
       }
@@ -943,8 +952,7 @@
     "color-name": {
       "version": "1.1.3",
       "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
-      "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
-      "dev": true
+      "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
     },
     "color-support": {
       "version": "1.1.3",
@@ -953,9 +961,9 @@
       "dev": true
     },
     "colors": {
-      "version": "1.3.2",
-      "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.2.tgz",
-      "integrity": "sha512-rhP0JSBGYvpcNQj4s5AdShMeE5ahMop96cTeDl/v9qQQm2fYClE2QXZRi8wLzc+GmXSxdIqqbOIAhyObEXDbfQ==",
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz",
+      "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==",
       "dev": true
     },
     "combined-stream": {
@@ -1154,8 +1162,7 @@
     "deep-extend": {
       "version": "0.6.0",
       "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
-      "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
-      "dev": true
+      "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
     },
     "default-compare": {
       "version": "1.0.0",
@@ -1262,11 +1269,54 @@
       "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=",
       "dev": true
     },
+    "detect-libc": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
+      "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
+    },
     "doctypes": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz",
       "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk="
     },
+    "dom-serializer": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
+      "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=",
+      "requires": {
+        "domelementtype": "~1.1.1",
+        "entities": "~1.1.1"
+      },
+      "dependencies": {
+        "domelementtype": {
+          "version": "1.1.3",
+          "resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz",
+          "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs="
+        }
+      }
+    },
+    "domelementtype": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
+      "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w=="
+    },
+    "domhandler": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
+      "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==",
+      "requires": {
+        "domelementtype": "1"
+      }
+    },
+    "domutils": {
+      "version": "1.7.0",
+      "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",
+      "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==",
+      "requires": {
+        "dom-serializer": "0",
+        "domelementtype": "1"
+      }
+    },
     "dot-prop": {
       "version": "4.2.0",
       "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
@@ -1276,12 +1326,6 @@
         "is-obj": "^1.0.0"
       }
     },
-    "duplexer": {
-      "version": "0.1.1",
-      "resolved": "http://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
-      "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=",
-      "dev": true
-    },
     "duplexer3": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
@@ -1338,6 +1382,11 @@
         "once": "^1.4.0"
       }
     },
+    "entities": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz",
+      "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w=="
+    },
     "error-ex": {
       "version": "1.3.2",
       "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -1410,22 +1459,6 @@
       "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
       "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
     },
-    "event-stream": {
-      "version": "3.3.6",
-      "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.6.tgz",
-      "integrity": "sha512-dGXNg4F/FgVzlApjzItL+7naHutA3fDqbV/zAZqDDlXTjiMnQmZKu+prImWKszeBM5UQeGvAl3u1wBiKeDh61g==",
-      "dev": true,
-      "requires": {
-        "duplexer": "^0.1.1",
-        "flatmap-stream": "^0.1.0",
-        "from": "^0.1.7",
-        "map-stream": "0.0.7",
-        "pause-stream": "^0.0.11",
-        "split": "^1.0.1",
-        "stream-combiner": "^0.2.2",
-        "through": "^2.3.8"
-      }
-    },
     "execa": {
       "version": "0.7.0",
       "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
@@ -1668,13 +1701,14 @@
       "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
     },
     "fancy-log": {
-      "version": "1.3.2",
-      "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.2.tgz",
-      "integrity": "sha1-9BEl49hPLn2JpD0G2VjI94vha+E=",
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.3.tgz",
+      "integrity": "sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw==",
       "dev": true,
       "requires": {
         "ansi-gray": "^0.1.1",
         "color-support": "^1.1.3",
+        "parse-node-version": "^1.0.0",
         "time-stamp": "^1.0.0"
       }
     },
@@ -1773,9 +1807,9 @@
       }
     },
     "fined": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/fined/-/fined-1.1.0.tgz",
-      "integrity": "sha1-s33IRLdqL15wgeiE98CuNE8VNHY=",
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/fined/-/fined-1.1.1.tgz",
+      "integrity": "sha512-jQp949ZmEbiYHk3gkbdtpJ0G1+kgtLQBNdP5edFP7Fh+WAYceLQz6yO1SBj72Xkg8GVyTB3bBzAYrHJVh5Xd5g==",
       "dev": true,
       "requires": {
         "expand-tilde": "^2.0.2",
@@ -1786,15 +1820,9 @@
       }
     },
     "flagged-respawn": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.0.tgz",
-      "integrity": "sha1-Tnmumy6zi/hrO7Vr8+ClaqX8q9c=",
-      "dev": true
-    },
-    "flatmap-stream": {
-      "version": "0.1.1",
-      "resolved": "https://registry.npmjs.org/flatmap-stream/-/flatmap-stream-0.1.1.tgz",
-      "integrity": "sha512-lAq4tLbm3sidmdCN8G3ExaxH7cUCtP5mgDvrYowsx84dcYkJJ4I28N7gkxA6+YlSXzaGLJYIDEi9WGfXzMiXdw==",
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-1.0.1.tgz",
+      "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==",
       "dev": true
     },
     "flush-write-stream": {
@@ -1856,11 +1884,13 @@
       "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
       "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac="
     },
-    "from": {
-      "version": "0.1.7",
-      "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz",
-      "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=",
-      "dev": true
+    "fs-minipass": {
+      "version": "1.2.5",
+      "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz",
+      "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==",
+      "requires": {
+        "minipass": "^2.2.1"
+      }
     },
     "fs-mkdirp-stream": {
       "version": "1.0.0",
@@ -1897,8 +1927,7 @@
         "ansi-regex": {
           "version": "2.1.1",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "aproba": {
           "version": "1.2.0",
@@ -1919,14 +1948,12 @@
         "balanced-match": {
           "version": "1.0.0",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "brace-expansion": {
           "version": "1.1.11",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "balanced-match": "^1.0.0",
             "concat-map": "0.0.1"
@@ -1941,20 +1968,17 @@
         "code-point-at": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "concat-map": {
           "version": "0.0.1",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "console-control-strings": {
           "version": "1.1.0",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "core-util-is": {
           "version": "1.0.2",
@@ -2071,8 +2095,7 @@
         "inherits": {
           "version": "2.0.3",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "ini": {
           "version": "1.3.5",
@@ -2084,7 +2107,6 @@
           "version": "1.0.0",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "number-is-nan": "^1.0.0"
           }
@@ -2099,7 +2121,6 @@
           "version": "3.0.4",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "brace-expansion": "^1.1.7"
           }
@@ -2107,14 +2128,12 @@
         "minimist": {
           "version": "0.0.8",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "minipass": {
           "version": "2.2.4",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "safe-buffer": "^5.1.1",
             "yallist": "^3.0.0"
@@ -2133,7 +2152,6 @@
           "version": "0.5.1",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "minimist": "0.0.8"
           }
@@ -2214,8 +2232,7 @@
         "number-is-nan": {
           "version": "1.0.1",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "object-assign": {
           "version": "4.1.1",
@@ -2227,7 +2244,6 @@
           "version": "1.4.0",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "wrappy": "1"
           }
@@ -2313,8 +2329,7 @@
         "safe-buffer": {
           "version": "5.1.1",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "safer-buffer": {
           "version": "2.1.2",
@@ -2350,7 +2365,6 @@
           "version": "1.0.2",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "code-point-at": "^1.0.0",
             "is-fullwidth-code-point": "^1.0.0",
@@ -2370,7 +2384,6 @@
           "version": "3.0.1",
           "bundled": true,
           "dev": true,
-          "optional": true,
           "requires": {
             "ansi-regex": "^2.0.0"
           }
@@ -2414,14 +2427,12 @@
         "wrappy": {
           "version": "1.0.2",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         },
         "yallist": {
           "version": "3.0.2",
           "bundled": true,
-          "dev": true,
-          "optional": true
+          "dev": true
         }
       }
     },
@@ -2547,13 +2558,15 @@
       }
     },
     "glob-watcher": {
-      "version": "5.0.1",
-      "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.1.tgz",
-      "integrity": "sha512-fK92r2COMC199WCyGUblrZKhjra3cyVMDiypDdqg1vsSDmexnbYivK1kNR4QItiNXLKmGlqan469ks67RtNa2g==",
+      "version": "5.0.3",
+      "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.3.tgz",
+      "integrity": "sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg==",
       "dev": true,
       "requires": {
+        "anymatch": "^2.0.0",
         "async-done": "^1.2.0",
         "chokidar": "^2.0.0",
+        "is-negated-glob": "^1.0.0",
         "just-debounce": "^1.0.0",
         "object.defaults": "^1.1.0"
       }
@@ -2602,9 +2615,9 @@
       }
     },
     "glogg": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.1.tgz",
-      "integrity": "sha512-ynYqXLoluBKf9XGR1gA59yEJisIL7YHEH4xr3ZziHB5/yl4qWfaK8Js9jGe6gBGCSCKVqiyO30WnRZADvemUNw==",
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz",
+      "integrity": "sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA==",
       "dev": true,
       "requires": {
         "sparkles": "^1.0.0"
@@ -2675,15 +2688,14 @@
       }
     },
     "gulp-nodemon": {
-      "version": "2.4.1",
-      "resolved": "https://registry.npmjs.org/gulp-nodemon/-/gulp-nodemon-2.4.1.tgz",
-      "integrity": "sha512-IZMEfUZggvXvYDCCbb4jq8xMSsS24OltlgL0KhumhDAbZJUHMm3tHEolHbNLLL9704Wm/DgZ5bdFqCb2hXG0bQ==",
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/gulp-nodemon/-/gulp-nodemon-2.4.2.tgz",
+      "integrity": "sha512-r8ShC9yzL3lK5qUsTStMeZRwqLG6t2m4lEBVcfUYzVkiYSeYXu9xYXG5rfvzBOPZOZ2dWugTKr+zeWbnMnzWDA==",
       "dev": true,
       "requires": {
         "colors": "^1.2.1",
-        "event-stream": "^3.3.4",
         "gulp": "^4.0.0",
-        "nodemon": "^1.17.5"
+        "nodemon": "^1.18.7"
       }
     },
     "gulplog": {
@@ -2728,8 +2740,7 @@
     "has-flag": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
-      "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
-      "dev": true
+      "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0="
     },
     "has-symbols": {
       "version": "1.0.0",
@@ -2788,6 +2799,31 @@
       "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz",
       "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w=="
     },
+    "htmlparser2": {
+      "version": "3.10.0",
+      "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.0.tgz",
+      "integrity": "sha512-J1nEUGv+MkXS0weHNWVKJJ+UrLfePxRWpN3C9bEi9fLxL2+ggW94DQvgYVXsaT30PGwYRIZKNZXuyMhp3Di4bQ==",
+      "requires": {
+        "domelementtype": "^1.3.0",
+        "domhandler": "^2.3.0",
+        "domutils": "^1.5.1",
+        "entities": "^1.1.1",
+        "inherits": "^2.0.1",
+        "readable-stream": "^3.0.6"
+      },
+      "dependencies": {
+        "readable-stream": {
+          "version": "3.0.6",
+          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.0.6.tgz",
+          "integrity": "sha512-9E1oLoOWfhSXHGv6QlwXJim7uNzd9EVlWK+21tCU9Ju/kR0/p2AZYPz4qSchgO8PlLIH4FpZYfzwS+rEksZjIg==",
+          "requires": {
+            "inherits": "^2.0.3",
+            "string_decoder": "^1.1.1",
+            "util-deprecate": "^1.0.1"
+          }
+        }
+      }
+    },
     "http-errors": {
       "version": "1.7.1",
       "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.1.tgz",
@@ -2831,6 +2867,14 @@
       "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=",
       "dev": true
     },
+    "ignore-walk": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz",
+      "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==",
+      "requires": {
+        "minimatch": "^3.0.4"
+      }
+    },
     "import-lazy": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz",
@@ -2873,8 +2917,7 @@
     "ini": {
       "version": "1.3.5",
       "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
-      "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
-      "dev": true
+      "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
     },
     "interpret": {
       "version": "1.1.0",
@@ -3353,6 +3396,21 @@
       "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=",
       "dev": true
     },
+    "lodash.escaperegexp": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
+      "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c="
+    },
+    "lodash.isplainobject": {
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+      "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs="
+    },
+    "lodash.isstring": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+      "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE="
+    },
     "lodash.mergewith": {
       "version": "4.6.1",
       "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz",
@@ -3432,12 +3490,6 @@
       "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
       "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0="
     },
-    "map-stream": {
-      "version": "0.0.7",
-      "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.0.7.tgz",
-      "integrity": "sha1-ih8HiW2CsQkmvTdEokIACfiJdKg=",
-      "dev": true
-    },
     "map-visit": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz",
@@ -3558,6 +3610,30 @@
       "resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
       "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
     },
+    "minipass": {
+      "version": "2.3.5",
+      "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz",
+      "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==",
+      "requires": {
+        "safe-buffer": "^5.1.2",
+        "yallist": "^3.0.0"
+      },
+      "dependencies": {
+        "yallist": {
+          "version": "3.0.3",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
+          "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A=="
+        }
+      }
+    },
+    "minizlib": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz",
+      "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==",
+      "requires": {
+        "minipass": "^2.2.1"
+      }
+    },
     "mixin-deep": {
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz",
@@ -3657,6 +3733,31 @@
         }
       }
     },
+    "needle": {
+      "version": "2.2.4",
+      "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.4.tgz",
+      "integrity": "sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA==",
+      "requires": {
+        "debug": "^2.1.2",
+        "iconv-lite": "^0.4.4",
+        "sax": "^1.2.4"
+      },
+      "dependencies": {
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+        }
+      }
+    },
     "negotiator": {
       "version": "0.6.1",
       "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz",
@@ -3694,6 +3795,53 @@
         }
       }
     },
+    "node-pre-gyp": {
+      "version": "0.10.3",
+      "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz",
+      "integrity": "sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A==",
+      "requires": {
+        "detect-libc": "^1.0.2",
+        "mkdirp": "^0.5.1",
+        "needle": "^2.2.1",
+        "nopt": "^4.0.1",
+        "npm-packlist": "^1.1.6",
+        "npmlog": "^4.0.2",
+        "rc": "^1.2.7",
+        "rimraf": "^2.6.1",
+        "semver": "^5.3.0",
+        "tar": "^4"
+      },
+      "dependencies": {
+        "nopt": {
+          "version": "4.0.1",
+          "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz",
+          "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=",
+          "requires": {
+            "abbrev": "1",
+            "osenv": "^0.1.4"
+          }
+        },
+        "tar": {
+          "version": "4.4.8",
+          "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.8.tgz",
+          "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==",
+          "requires": {
+            "chownr": "^1.1.1",
+            "fs-minipass": "^1.2.5",
+            "minipass": "^2.3.4",
+            "minizlib": "^1.1.1",
+            "mkdirp": "^0.5.0",
+            "safe-buffer": "^5.1.2",
+            "yallist": "^3.0.2"
+          }
+        },
+        "yallist": {
+          "version": "3.0.3",
+          "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz",
+          "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A=="
+        }
+      }
+    },
     "node-sass": {
       "version": "4.10.0",
       "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.10.0.tgz",
@@ -3730,21 +3878,21 @@
       }
     },
     "nodemon": {
-      "version": "1.18.6",
-      "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.18.6.tgz",
-      "integrity": "sha512-4pHQNYEZun+IkIC2jCaXEhkZnfA7rQe73i8RkdRyDJls/K+WxR7IpI5uNUsAvQ0zWvYcCDNGD+XVtw2ZG86/uQ==",
+      "version": "1.18.9",
+      "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.18.9.tgz",
+      "integrity": "sha512-oj/eEVTEI47pzYAjGkpcNw0xYwTl4XSTUQv2NPQI6PpN3b75PhpuYk3Vb3U80xHCyM2Jm+1j68ULHXl4OR3Afw==",
       "dev": true,
       "requires": {
         "chokidar": "^2.0.4",
         "debug": "^3.1.0",
         "ignore-by-default": "^1.0.1",
         "minimatch": "^3.0.4",
-        "pstree.remy": "^1.1.0",
+        "pstree.remy": "^1.1.6",
         "semver": "^5.5.0",
         "supports-color": "^5.2.0",
         "touch": "^3.1.0",
         "undefsafe": "^2.0.2",
-        "update-notifier": "^2.3.0"
+        "update-notifier": "^2.5.0"
       },
       "dependencies": {
         "debug": {
@@ -3804,6 +3952,20 @@
         "once": "^1.3.2"
       }
     },
+    "npm-bundled": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.5.tgz",
+      "integrity": "sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g=="
+    },
+    "npm-packlist": {
+      "version": "1.1.12",
+      "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.12.tgz",
+      "integrity": "sha512-WJKFOVMeAlsU/pjXuqVdzU0WfgtIBCupkEVwn+1Y0ERAbUfWw8R4GjgVbaKnUjRoD2FoQbHOCbOyT5Mbs9Lw4g==",
+      "requires": {
+        "ignore-walk": "^3.0.1",
+        "npm-bundled": "^1.0.1"
+      }
+    },
     "npm-run-path": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
@@ -4023,6 +4185,12 @@
         "error-ex": "^1.2.0"
       }
     },
+    "parse-node-version": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.0.tgz",
+      "integrity": "sha512-02GTVHD1u0nWc20n2G7WX/PgdhNFG04j5fi1OkaJzPWLTcf6vh6229Lta1wTmXG/7Dg42tCssgkccVt7qvd8Kg==",
+      "dev": true
+    },
     "parse-passwd": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz",
@@ -4106,15 +4274,6 @@
         "pinkie-promise": "^2.0.0"
       }
     },
-    "pause-stream": {
-      "version": "0.0.11",
-      "resolved": "http://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz",
-      "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=",
-      "dev": true,
-      "requires": {
-        "through": "~2.3"
-      }
-    },
     "performance-now": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
@@ -4144,6 +4303,49 @@
       "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=",
       "dev": true
     },
+    "postcss": {
+      "version": "7.0.6",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.6.tgz",
+      "integrity": "sha512-Nq/rNjnHFcKgCDDZYO0lNsl6YWe6U7tTy+ESN+PnLxebL8uBtYX59HZqvrj7YLK5UCyll2hqDsJOo3ndzEW8Ug==",
+      "requires": {
+        "chalk": "^2.4.1",
+        "source-map": "^0.6.1",
+        "supports-color": "^5.5.0"
+      },
+      "dependencies": {
+        "ansi-styles": {
+          "version": "3.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+          "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+          "requires": {
+            "color-convert": "^1.9.0"
+          }
+        },
+        "chalk": {
+          "version": "2.4.1",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
+          "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
+          "requires": {
+            "ansi-styles": "^3.2.1",
+            "escape-string-regexp": "^1.0.5",
+            "supports-color": "^5.3.0"
+          }
+        },
+        "source-map": {
+          "version": "0.6.1",
+          "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+          "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
+        },
+        "supports-color": {
+          "version": "5.5.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+          "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+          "requires": {
+            "has-flag": "^3.0.0"
+          }
+        }
+      }
+    },
     "prepend-http": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",
@@ -4178,15 +4380,6 @@
         "ipaddr.js": "1.8.0"
       }
     },
-    "ps-tree": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.1.0.tgz",
-      "integrity": "sha1-tCGyQUDWID8e08dplrRCewjowBQ=",
-      "dev": true,
-      "requires": {
-        "event-stream": "~3.3.0"
-      }
-    },
     "pseudomap": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
@@ -4198,13 +4391,10 @@
       "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ=="
     },
     "pstree.remy": {
-      "version": "1.1.0",
-      "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.0.tgz",
-      "integrity": "sha512-q5I5vLRMVtdWa8n/3UEzZX7Lfghzrg9eG2IKk2ENLSofKRCXVqMvMUHxCKgXNaqH/8ebhBxrqftHWnyTFweJ5Q==",
-      "dev": true,
-      "requires": {
-        "ps-tree": "^1.1.0"
-      }
+      "version": "1.1.6",
+      "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.6.tgz",
+      "integrity": "sha512-NdF35+QsqD7EgNEI5mkI/X+UwaxVEbQaz9f4IooEmMUv6ZPmlTQYGjBPJGgrlzNdjSvIy4MWMg6Q6vCgBO2K+w==",
+      "dev": true
     },
     "pug": {
       "version": "2.0.3",
@@ -4384,7 +4574,6 @@
       "version": "1.2.8",
       "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
       "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
-      "dev": true,
       "requires": {
         "deep-extend": "^0.6.0",
         "ini": "~1.3.0",
@@ -4395,8 +4584,7 @@
         "minimist": {
           "version": "1.2.0",
           "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
-          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
-          "dev": true
+          "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
         }
       }
     },
@@ -4670,6 +4858,51 @@
       "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
     },
+    "sanitize-html": {
+      "version": "1.19.3",
+      "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.19.3.tgz",
+      "integrity": "sha512-QpIjbF1rhUSQj9V7Wey/gv4DPqOso8KTebaI4rC97p0WCLnTpmhf7BJZUhS83MTtqRvUo8MuXH316CW2Nzd48w==",
+      "requires": {
+        "chalk": "^2.4.1",
+        "htmlparser2": "^3.10.0",
+        "lodash.clonedeep": "^4.5.0",
+        "lodash.escaperegexp": "^4.1.2",
+        "lodash.isplainobject": "^4.0.6",
+        "lodash.isstring": "^4.0.1",
+        "lodash.mergewith": "^4.6.1",
+        "postcss": "^7.0.5",
+        "srcset": "^1.0.0",
+        "xtend": "^4.0.1"
+      },
+      "dependencies": {
+        "ansi-styles": {
+          "version": "3.2.1",
+          "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+          "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+          "requires": {
+            "color-convert": "^1.9.0"
+          }
+        },
+        "chalk": {
+          "version": "2.4.1",
+          "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz",
+          "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==",
+          "requires": {
+            "ansi-styles": "^3.2.1",
+            "escape-string-regexp": "^1.0.5",
+            "supports-color": "^5.3.0"
+          }
+        },
+        "supports-color": {
+          "version": "5.5.0",
+          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+          "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+          "requires": {
+            "has-flag": "^3.0.0"
+          }
+        }
+      }
+    },
     "sass-graph": {
       "version": "2.2.4",
       "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-2.2.4.tgz",
@@ -4681,6 +4914,11 @@
         "yargs": "^7.0.0"
       }
     },
+    "sax": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
+      "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
+    },
     "scss-tokenizer": {
       "version": "0.2.3",
       "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz",
@@ -5026,15 +5264,6 @@
       "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.2.tgz",
       "integrity": "sha512-qky9CVt0lVIECkEsYbNILVnPvycuEBkXoMFLRWsREkomQLevYhtRKC+R91a5TOAQ3bCMjikRwhyaRqj1VYatYg=="
     },
-    "split": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz",
-      "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==",
-      "dev": true,
-      "requires": {
-        "through": "2"
-      }
-    },
     "split-string": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz",
@@ -5044,6 +5273,32 @@
         "extend-shallow": "^3.0.0"
       }
     },
+    "sqlite3": {
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-4.0.4.tgz",
+      "integrity": "sha512-CO8vZMyUXBPC+E3iXOCc7Tz2pAdq5BWfLcQmOokCOZW5S5sZ/paijiPOCdvzpdP83RroWHYa5xYlVqCxSqpnQg==",
+      "requires": {
+        "nan": "~2.10.0",
+        "node-pre-gyp": "^0.10.3",
+        "request": "^2.87.0"
+      },
+      "dependencies": {
+        "nan": {
+          "version": "2.10.0",
+          "resolved": "http://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
+          "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA=="
+        }
+      }
+    },
+    "srcset": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/srcset/-/srcset-1.0.0.tgz",
+      "integrity": "sha1-pWad4StC87HV6D7QPHEEb8SPQe8=",
+      "requires": {
+        "array-uniq": "^1.0.2",
+        "number-is-nan": "^1.0.0"
+      }
+    },
     "sshpk": {
       "version": "1.15.2",
       "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.2.tgz",
@@ -5100,16 +5355,6 @@
         "readable-stream": "^2.0.1"
       }
     },
-    "stream-combiner": {
-      "version": "0.2.2",
-      "resolved": "http://registry.npmjs.org/stream-combiner/-/stream-combiner-0.2.2.tgz",
-      "integrity": "sha1-rsjLrBd7Vrb0+kec7YwZEs7lKFg=",
-      "dev": true,
-      "requires": {
-        "duplexer": "~0.1.1",
-        "through": "~2.3.4"
-      }
-    },
     "stream-exhaust": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/stream-exhaust/-/stream-exhaust-1.0.2.tgz",
@@ -5173,8 +5418,7 @@
     "strip-json-comments": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
-      "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
-      "dev": true
+      "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
     },
     "supports-color": {
       "version": "2.0.0",
@@ -5210,12 +5454,6 @@
         "execa": "^0.7.0"
       }
     },
-    "through": {
-      "version": "2.3.8",
-      "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz",
-      "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
-      "dev": true
-    },
     "through2": {
       "version": "2.0.5",
       "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
@@ -5904,9 +6142,9 @@
       }
     },
     "ws": {
-      "version": "6.1.0",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.0.tgz",
-      "integrity": "sha512-H3dGVdGvW2H8bnYpIDc3u3LH8Wue3Qh+Zto6aXXFzvESkTVT6rAfKR6tR/+coaUvxs8yHtmNV0uioBF62ZGSTg==",
+      "version": "6.1.2",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.2.tgz",
+      "integrity": "sha512-rfUqzvz0WxmSXtJpPMX2EeASXabOrSMk1ruMOV3JBTBjo4ac2lDjGGsbQSyxj8Odhw5fBib8ZKEjDNvgouNKYw==",
       "requires": {
         "async-limiter": "~1.0.0"
       }
@@ -5920,8 +6158,7 @@
     "xtend": {
       "version": "4.0.1",
       "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz",
-      "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=",
-      "dev": true
+      "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68="
     },
     "y18n": {
       "version": "3.2.1",
diff --git a/package.json b/package.json
index 62092276..72a326fc 100644
--- a/package.json
+++ b/package.json
@@ -13,10 +13,12 @@
     "morgan": "~1.9.1",
     "node-sass-middleware": "~0.11.0",
     "pug": "~2.0.3",
+    "sanitize-html": "^1.19.3",
     "serve-favicon": "~2.5.0",
-    "ws": "~6.1.0"
+    "sqlite3": "^4.0.4",
+    "ws": "^6.1.2"
   },
   "devDependencies": {
-    "gulp-nodemon": "~2.4.1"
+    "gulp-nodemon": "^2.4.2"
   }
 }
diff --git a/public/javascripts/components/game.js b/public/javascripts/components/game.js
index 533bc0e2..278e0782 100644
--- a/public/javascripts/components/game.js
+++ b/public/javascripts/components/game.js
@@ -409,7 +409,7 @@ Vue.component('my-game', {
 					}),
 				h('div',
 					{
-						attrs: { "role": "dialog", "aria-labelledby": "modal-eog" },
+						attrs: { "role": "dialog", "aria-labelledby": "eogMessage" },
 					},
 					[
 						h('div',
@@ -425,6 +425,7 @@ Vue.component('my-game', {
 								),
 								h('h3',
 									{
+										attrs: { "id": "eogMessage" },
 										"class": { "section": true },
 										domProps: { innerHTML: eogMessage },
 									}
@@ -445,7 +446,7 @@ Vue.component('my-game', {
 				}),
 			h('div',
 				{
-					attrs: { "role": "dialog", "aria-labelledby": "modal-newgame" },
+					attrs: { "role": "dialog", "aria-labelledby": "newGameTxt" },
 				},
 				[
 					h('div',
@@ -461,6 +462,7 @@ Vue.component('my-game', {
 							),
 							h('h3',
 								{
+									attrs: { "id": "newGameTxt" },
 									"class": { "section": true },
 									domProps: { innerHTML: "New game" },
 								}
@@ -485,7 +487,7 @@ Vue.component('my-game', {
 				}),
 			h('div',
 				{
-					attrs: { "role": "dialog", "aria-labelledby": "modal-fenedit" },
+					attrs: { "role": "dialog", "aria-labelledby": "titleFenedit" },
 				},
 				[
 					h('div',
@@ -501,6 +503,7 @@ Vue.component('my-game', {
 							),
 							h('h3',
 								{
+									attrs: { "id": "titleFenedit" },
 									"class": { "section": true },
 									domProps: { innerHTML: "Position + flags (FEN):" },
 								}
@@ -551,7 +554,7 @@ Vue.component('my-game', {
 				}),
 			h('div',
 				{
-					attrs: { "role": "dialog", "aria-labelledby": "modal-settings" },
+					attrs: { "role": "dialog", "aria-labelledby": "settingsTitle" },
 				},
 				[
 					h('div',
@@ -567,6 +570,7 @@ Vue.component('my-game', {
 							),
 							h('h3',
 								{
+									attrs: { "id": "settingsTitle" },
 									"class": { "section": true },
 									domProps: { innerHTML: "Preferences" },
 								}
diff --git a/public/javascripts/components/problemSummary.js b/public/javascripts/components/problemSummary.js
new file mode 100644
index 00000000..6003f085
--- /dev/null
+++ b/public/javascripts/components/problemSummary.js
@@ -0,0 +1,17 @@
+// Show a problem summary on variant page
+Vue.component('my-problem-summary', {
+	props: ['prob'],
+	template: `
+		<div class="problem col-sm-12">
+			<div class="diagram">
+				{{ getDiagram(prob.fen) }}
+			</div>
+			<div class="problem-instructions">
+				{{ prob.instructions.substr(0,32) }}
+			</div>
+			<div class="problem-time">
+				{{ prob.added }}
+			</div>
+		</div>
+	`,
+})
diff --git a/public/javascripts/components/problems.js b/public/javascripts/components/problems.js
index dc320520..594d6e64 100644
--- a/public/javascripts/components/problems.js
+++ b/public/javascripts/components/problems.js
@@ -1,23 +1,81 @@
-//TODO: list problems as FEN (quickly rendered), by date, with possible filtering per variant(?)
-//click on a problem ==> land on variant page with mode==friend, FEN prefilled... ok
-
-// get 10 first problems, and buttons next<>previous send date + "before" or "after"
-// need database: sqlite !
-
-// form "new problem" fen(position/turn/flags[guess]), instructions, solution (mandatory)
-// ==> upload on server in sandbox
-//
-// Atomic rules, atomic game, atomic problems(list drawn position
-//   + summary(first chars of instructions) + timedate)... one big Vue ? with components
-//
-// click on problem ==> masque problems, affiche game tab, launch new game Friend with
-//   FEN + turn + flags + rappel instructions / solution on click sous l'échiquier
-
 Vue.component('my-problems', {
-	//props: ['vobj'],
+	data: function () {
+		return {
+			problems: problemArray //initial value
+		};
+	},
 	template: `
-		<div class="variant col-sm-12">
-			<p>Hello</p>
+		<div>
+			<button>Previous</button>
+			<button>Next</button>
+			<button @click="showNewproblemModal">New</button>
+			<my-problem-summary
+				v-for="(p,idx) in sortedProblems",
+				v-bind:prob="p",
+				v-bind:key="idx",
+				@click="showProblem(p)"
+			</my-problem-summary>
+			<input type="checkbox" id="modal-newproblem" class="modal">
+			<div role="dialog" aria-labelledby="newProblemTxt">
+				<div class="card newproblem">
+					<label for="modal-newproblem" class="modal-close"></label>
+					<h3 id="newProblemTxt">Add problem</h3>
+					<form @submit.prevent="postNewProblem">
+						<fieldset>
+							<label for="newpbFen">Fen</label>
+							<input type="text" id="newpbFen" placeholder="Position [+ flags [+ turn]]"/>
+						</fieldset>
+						<fieldset>
+							<p class="emphasis">
+								Allowed HTML tags:
+								&lt;p&gt;,&lt;br&gt;,&lt,ul&gt;,&lt;ol&gt;,&lt;li&gt;
+							</p>
+							<label for="newpbInstructions">Instructions</label>
+							<textarea id="newpbInstructions" placeholder="Explain the problem here"/>
+							<label for="newpbSolution">Solution</label>
+							<textarea id="newpbSolution" placeholder="How to solve the problem?"/>
+							<button class="center-btn">Send</button>
+						</fieldset>
+						<p class="mistake-newproblem">
+							Note: if you made a mistake, please let me know at
+							<a :href="mailErrProblem">contact@vchess.club</a>
+						</p>
+					</form>
+				</div>
+			</div>
 		</div>
 	`,
+	computed: {
+		sortedProblems: function() {
+			// Newest problem first
+			return problems.sort((p1,p2) => { return p2.added - p1.added; });
+		},
+		mailErrProblem: function() {
+			return "mailto:contact@vchess.club?subject=[" + variant + " problems] error";
+		},
+	},
+	methods: {
+		fetchProblems: function(direction) {
+			// TODO: ajax call return list of max 10 problems
+			// Do not do anything if no older problems (and store this result in cache!)
+			// TODO: ajax call return list of max 10 problems
+			// Do not do anything if no newer problems
+		},
+		showProblem: function(prob) {
+			//TODO: send event with object prob.fen, prob.instructions, prob.solution
+			//Event should propagate to game, which set mode=="problem" + other variables
+			//click on a problem ==> land on variant page with mode==friend, FEN prefilled... ok
+			// click on problem ==> masque problems, affiche game tab, launch new game Friend with
+			//   FEN + turn + flags + rappel instructions / solution on click sous l'échiquier
+		},
+		showNewproblemModal: function() {
+			document.getElementById("modal-newproblem").checked = true;
+		},
+		postNewProblem: function() {
+			const fen = document.getElementById("newpbFen").value;
+			const instructions = document.getElementById("newpbInstructions").value;
+			const solution = document.getElementById("newpbSolution").value;
+			
+		},
+	},
 })
diff --git a/public/javascripts/components/rules.js b/public/javascripts/components/rules.js
index c3107a67..660f0be9 100644
--- a/public/javascripts/components/rules.js
+++ b/public/javascripts/components/rules.js
@@ -6,66 +6,22 @@ Vue.component('my-rules', {
 	template: `<div v-html="content" class="section-content"></div>`,
 	mounted: function() {
 		// AJAX request to get rules content (plain text, HTML)
-		let xhr = new XMLHttpRequest();
-		let self = this;
-		xhr.onreadystatechange = function() {
-			if (this.readyState == 4 && this.status == 200)
-			{
-				let replaceByDiag = (match, p1, p2) => { return self.drawDiag(p2); };
-				self.content = xhr.responseText.replace(/(fen:)([^:]*):/g, replaceByDiag);
-			}
-		};
-		xhr.open("GET", "/rules/" + variant, true);
-		xhr.setRequestHeader('X-Requested-With', "XMLHttpRequest");
-		xhr.send();
+		ajax("/rules/" + variant, "GET", response => {
+			let replaceByDiag = (match, p1, p2) => {
+				const args = self.parseFen(p2);
+				return getDiagram(args);
+			};
+			self.content = response.replace(/(fen:)([^:]*):/g, replaceByDiag);
+		}
 	},
 	methods: {
-		drawDiag: function(fen) {
-			let [sizeX,sizeY] = [V.size.x,V.size.y];
-			let fenParts = fen.split(" ");
-			// Obtain array of pieces images names
-			let board = VariantRules.GetBoard(fenParts[0]);
-			let orientation = "w";
-			if (fenParts.length >= 2)
-				orientation = fenParts[1];
-			let markArray = [];
-			if (fenParts.length >= 3)
-			{
-				let marks_str = fenParts[2];
-				// Turn (human) marks into coordinates
-				markArray = doubleArray(sizeX, sizeY, false);
-				let marks = marks_str.split(",");
-				for (let i=0; i<marks.length; i++)
-				{
-					var res = /^([a-z]+)([0-9]+)$/i.exec(marks[i]);
-					let x = sizeX - parseInt(res[2]); //white at bottom, so counting is reversed
-					let y = res[1].charCodeAt(0)-97; //always one char: max 26, big enough
-					markArray[x][y] = true;
-				}
-			}
-			let boardDiv = "";
-			let [startX,startY,inc] = orientation == 'w'
-				? [0, 0, 1]
-				: [sizeX-1, sizeY-1, -1];
-			for (let i=startX; i>=0 && i<sizeX; i+=inc)
-			{
-				boardDiv += "<div class='row'>";
-				for (let j=startY; j>=0 && j<sizeY; j+=inc)
-				{
-					boardDiv += "<div class='board board" + sizeY + " " +
-						((i+j)%2==0 ? "light-square-diag" : "dark-square-diag") + "'>";
-					if (board[i][j] != VariantRules.EMPTY)
-					{
-						boardDiv += "<img src='/images/pieces/" +
-							VariantRules.getPpath(board[i][j]) + ".svg' class='piece'/>";
-					}
-					if (markArray.length>0 && markArray[i][j])
-						boardDiv += "<img src='/images/mark.svg' class='mark-square'/>";
-					boardDiv += "</div>";
-				}
-				boardDiv += "</div>";
-			}
-			return boardDiv;
+		parseFen(fen) {
+			const fenParts = fen.split(" ");
+			return {
+				position: fenParts[0],
+				marks: fenParts[1],
+				orientation: fenParts[2],
+			};
 		},
 	},
 })
diff --git a/public/javascripts/utils/ajax.js b/public/javascripts/utils/ajax.js
new file mode 100644
index 00000000..ab288bc7
--- /dev/null
+++ b/public/javascripts/utils/ajax.js
@@ -0,0 +1,55 @@
+// From JSON (encoded string values!) to "arg1=...&arg2=..."
+function toQueryString(data)
+{
+	let data_str = "";
+	Object.keys(data).forEach(k => {
+		data_str += k + "=" + data[k] + "&";
+	});
+	return data_str.slice(0, -1); //remove last "&"
+}
+
+// data, error: optional
+function ajax(url, method, data, success, error)
+{
+	let xhr = new XMLHttpRequest();
+	if (typeof(data) === "function") //no data
+	{
+		error = success;
+		success = data;
+		data = {};
+	}
+	if (!error)
+		error = errmsg => { alert(errmsg); };
+
+	xhr.onreadystatechange = function() {
+		if (this.readyState == 4 && this.status == 200)
+		{
+			try {
+				let res_json = JSON.parse(xhr.responseText);
+				if (!res_json.errmsg)
+					success(res_json);
+				else
+					error(res_json.errmsg);
+			} catch (e) {
+				// Plain text (e.g. for rules retrieval)
+				success(xhr.responseText);
+			}
+		}
+	};
+
+	if (["GET","DELETE"].includes(method) && !!data)
+	{
+		// Append query params to URL
+		url += "/?" + toQueryString(data);
+	}
+
+	xhr.open(method, url, true);
+	xhr.setRequestHeader('X-Requested-With', "XMLHttpRequest");
+	if (["POST","PUT"].includes(method))
+	{
+		xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
+		xhr.send(JSON.stringify(data));
+	}
+	else
+		xhr.send();
+}
diff --git a/public/javascripts/utils/printDiagram.js b/public/javascripts/utils/printDiagram.js
new file mode 100644
index 00000000..ef750490
--- /dev/null
+++ b/public/javascripts/utils/printDiagram.js
@@ -0,0 +1,46 @@
+// Assuming V(ariantRules) class is loaded.
+// args: object with position (mandatory), orientation, marks (optional)
+function getDiagram(args)
+{
+	const [sizeX,sizeY] = [V.size.x,V.size.y];
+	// Obtain array of pieces images names
+	const board = VariantRules.GetBoard(args.position);
+	const orientation = args.orientation || "w";
+	let markArray = [];
+	if (!!args.marks)
+	{
+		// Turn (human) marks into coordinates
+		markArray = doubleArray(sizeX, sizeY, false);
+		let squares = args.marks.split(",");
+		for (let i=0; i<squares.length; i++)
+		{
+			const res = /^([a-z]+)([0-9]+)$/i.exec(squares[i]);
+			const x = sizeX - parseInt(res[2]); //white at bottom, so counting is reversed
+			const y = res[1].charCodeAt(0)-97; //always one char: max 26, big enough
+			markArray[x][y] = true;
+		}
+	}
+	let boardDiv = "";
+	const [startX,startY,inc] = orientation == 'w'
+		? [0, 0, 1]
+		: [sizeX-1, sizeY-1, -1];
+	for (let i=startX; i>=0 && i<sizeX; i+=inc)
+	{
+		boardDiv += "<div class='row'>";
+		for (let j=startY; j>=0 && j<sizeY; j+=inc)
+		{
+			boardDiv += "<div class='board board" + sizeY + " " +
+				((i+j)%2==0 ? "light-square-diag" : "dark-square-diag") + "'>";
+			if (board[i][j] != V.EMPTY)
+			{
+				boardDiv += "<img src='/images/pieces/" +
+					V.getPpath(board[i][j]) + ".svg' class='piece'/>";
+			}
+			if (!!args.marks && markArray[i][j])
+				boardDiv += "<img src='/images/mark.svg' class='mark-square'/>";
+			boardDiv += "</div>";
+		}
+		boardDiv += "</div>";
+	}
+	return boardDiv;
+}
diff --git a/public/stylesheets/variant.sass b/public/stylesheets/variant.sass
index f5bffc3c..f8047560 100644
--- a/public/stylesheets/variant.sass
+++ b/public/stylesheets/variant.sass
@@ -245,3 +245,25 @@ ul:not(.browser-default) > li
 #fen-div > p
   margin-left: 0
   margin-right: 0
+
+.newproblem input, .newproblem textarea
+  width: 100%
+
+.emphasis
+  font-style: italic
+
+#newpbInstructions
+  margin-bottom: var(--universal-margin);
+
+.center-btn
+  margin-left: 40%
+
+//TODO?
+.center-inline
+  text-align: center
+.center-block
+  margin-left: auto
+  margin-right: auto
+
+.mistake-newproblem
+  color: #663300
diff --git a/routes/all.js b/routes/all.js
index 3e6dd001..f3e184e6 100644
--- a/routes/all.js
+++ b/routes/all.js
@@ -1,31 +1,78 @@
-var express = require('express');
-var router = express.Router();
-var createError = require('http-errors');
-
-const Variants = require("../variants");
+let express = require('express');
+let router = express.Router();
+const createError = require('http-errors');
+const sqlite3 = require('sqlite3');//.verbose();
+const db = new sqlite3.Database('db/vchess.sqlite');
+const sanitizeHtml = require('sanitize-html');
 
 // Home
 router.get('/', function(req, res, next) {
-  res.render('index', {
-		title: 'club',
-		variantArray: Variants, //JSON.stringify(Variants)
+	db.serialize(function() {
+		db.all("SELECT * FROM Variants", (err,variants) => {
+			if (!!err)
+				return next(err);
+			res.render('index', {
+				title: 'club',
+				variantArray: variants, //JSON.stringify(variants)
+			});
+		});
 	});
 });
 
 // Variant
 router.get("/:vname([a-zA-Z0-9]+)", (req,res,next) => {
 	const vname = req.params["vname"];
-	if (!Variants.some(v => { return (v.name == vname); }))
-		return next(createError(404));
-	res.render('variant', {
-		title: vname + ' Variant',
-		variant: vname,
+	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));
+			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,
+					});
+				}
+			);
+		});
 	});
 });
 
 // Load a rules page (AJAX)
 router.get("/rules/:variant([a-zA-Z0-9]+)", (req,res) => {
-  res.render("rules/" + req.params["variant"]);
+	if (!req.xhr)
+		return res.json({errmsg: "Unauthorized access"});
+	res.render("rules/" + req.params["variant"]);
+});
+
+// Fetch 10 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)
 });
 
+// Upload a problem (AJAX)
+router.post("/problems/:variant([a-zA-Z0-9]+)", (req,res) => {
+	if (!req.xhr)
+		return res.json({errmsg: "Unauthorized access"});
+	const vname = req.params["variant"];
+	
+	// TODO: get parameters and sanitize them
+	sanitizeHtml(req.body["fen"]); // [/a-z0-9 ]*
+	sanitizeHtml(req.body["instructions"]);
+	db.serialize(function() {
+		let stmt = db.prepare("INSERT INTO Problems VALUES (?,?,?,?,?)");
+		stmt.run(timestamp, vname, fen, instructions, solution);
+		stmt.finalize();
+	});
+  res.json({});
+});
+
+
 module.exports = router;
diff --git a/sockets.js b/sockets.js
index 675a80eb..f1354f36 100644
--- a/sockets.js
+++ b/sockets.js
@@ -1,124 +1,120 @@
 const url = require('url');
-const Variants = require("./variants");
+const sqlite3 = require('sqlite3');
+const db = new sqlite3.Database('db/vchess.sqlite');
 
 module.exports = function(wss) {
-
-	let clients = { "index": {} };
-	let games = {}; //pending games (player sid)
-	for (const v of Variants)
-		clients[v.name] = {};
-
-//	// Safety counter (TODO: is it necessary ?)
-//	setInterval(() => {
-//		Object.keys(clients).forEach(k => {
-//			Object.keys(clients[k]).forEach(ck => {
-//				if (!clients[k][ck] || clients[k][ck].readyState != 1)
-//					delete clients[k][ck];
-//			});
-//		});
-//	}, 60000); //every minute (will be lowered if a lot of users...)
-
-	// No-op function as a callback when sending messages
-	const noop = () => { };
-
-	wss.on("connection", (socket, req) => {
-		const params = new URL("http://localhost" + req.url).searchParams;
-		const sid = params.get("sid");
-		const page = params.get("page");
-		// Ignore duplicate connections:
-		if (!!clients[page][sid])
-		{
-			socket.send(JSON.stringify({code:"duplicate"}));
-			return;
-		}
-		clients[page][sid] = socket;
-		if (page == "index")
-		{
-			// Send counting info
-			const countings = {};
-			for (const v of Variants)
-				countings[v.name] = Object.keys(clients[v.name]).length;
-			socket.send(JSON.stringify({code:"counts",counts:countings}));
-		}
-		else
-		{
-			// Send to every client connected on index an update message for counts
-			Object.keys(clients["index"]).forEach( k => {
-				clients["index"][k].send(JSON.stringify({code:"increase",vname:page}), noop);
-			});
-			// Also notify potential opponents: hit all clients which check if sid corresponds
-			Object.keys(clients[page]).forEach( k => {
-				clients[page][k].send(JSON.stringify({code:"connect",id:sid}), noop);
-			});
-			socket.on("message", objtxt => {
-				let obj = JSON.parse(objtxt);
-				switch (obj.code)
+	db.serialize(function() {
+		db.all("SELECT * FROM Variants", (err,variants) => {
+			let clients = { "index": {} };
+			let games = {}; //pending games (player sid)
+			for (const v of variants)
+				clients[v.name] = {};
+			// No-op function as a callback when sending messages
+			const noop = () => { };
+			wss.on("connection", (socket, req) => {
+				const params = new URL("http://localhost" + req.url).searchParams;
+				const sid = params.get("sid");
+				const page = params.get("page");
+				// Ignore duplicate connections:
+				if (!!clients[page][sid])
 				{
-					case "newmove":
-						if (!!clients[page][obj.oppid])
-						{
-							clients[page][obj.oppid].send(
-								JSON.stringify({code:"newmove",move:obj.move}), noop);
-						}
-						break;
-					case "ping":
-						if (!!clients[page][obj.oppid])
-							socket.send(JSON.stringify({code:"pong"}));
-						break;
-					case "lastate":
-						if (!!clients[page][obj.oppid])
-						{
-							const oppId = obj.oppid;
-							obj.oppid = sid; //I'm oppid for my opponent
-							clients[page][oppId].send(JSON.stringify(obj), noop);
-						}
-						break;
-					case "newgame":
-						if (!!games[page])
+					socket.send(JSON.stringify({code:"duplicate"}));
+					return;
+				}
+				clients[page][sid] = socket;
+				if (page == "index")
+				{
+					// Send counting info
+					const countings = {};
+					for (const v of variants)
+						countings[v.name] = Object.keys(clients[v.name]).length;
+					socket.send(JSON.stringify({code:"counts",counts:countings}));
+				}
+				else
+				{
+					// Send to every client connected on index an update message for counts
+					Object.keys(clients["index"]).forEach( k => {
+						clients["index"][k].send(
+							JSON.stringify({code:"increase",vname:page}), noop);
+					});
+					// Also notify potential opponents:
+					// hit all clients which check if sid corresponds
+					Object.keys(clients[page]).forEach( k => {
+						clients[page][k].send(JSON.stringify({code:"connect",id:sid}), noop);
+					});
+					socket.on("message", objtxt => {
+						let obj = JSON.parse(objtxt);
+						switch (obj.code)
 						{
-							// Start a new game
-							const oppId = games[page]["id"];
-							const fen = games[page]["fen"];
-							delete games[page];
-							const mycolor = Math.random() < 0.5 ? 'w' : 'b';
-							socket.send(
-								JSON.stringify({code:"newgame",fen:fen,oppid:oppId,color:mycolor}));
-							if (!!clients[page][oppId])
-							{
-								clients[page][oppId].send(
-									JSON.stringify(
-										{code:"newgame",fen:fen,oppid:sid,color:mycolor=="w"?"b":"w"}),
-									noop);
-							}
+							case "newmove":
+								if (!!clients[page][obj.oppid])
+								{
+									clients[page][obj.oppid].send(
+										JSON.stringify({code:"newmove",move:obj.move}), noop);
+								}
+								break;
+							case "ping":
+								if (!!clients[page][obj.oppid])
+									socket.send(JSON.stringify({code:"pong"}));
+								break;
+							case "lastate":
+								if (!!clients[page][obj.oppid])
+								{
+									const oppId = obj.oppid;
+									obj.oppid = sid; //I'm oppid for my opponent
+									clients[page][oppId].send(JSON.stringify(obj), noop);
+								}
+								break;
+							case "newgame":
+								if (!!games[page])
+								{
+									// Start a new game
+									const oppId = games[page]["id"];
+									const fen = games[page]["fen"];
+									delete games[page];
+									const mycolor = Math.random() < 0.5 ? 'w' : 'b';
+									socket.send(JSON.stringify(
+										{code:"newgame",fen:fen,oppid:oppId,color:mycolor}));
+									if (!!clients[page][oppId])
+									{
+										clients[page][oppId].send(
+											JSON.stringify(
+												{code:"newgame",fen:fen,oppid:sid,color:mycolor=="w"?"b":"w"}),
+											noop);
+									}
+								}
+								else
+									games[page] = {id:sid, fen:obj.fen}; //wait for opponent
+								break;
+							case "cancelnewgame": //if a user cancel his seek
+								delete games[page];
+								break;
+							case "resign":
+								if (!!clients[page][obj.oppid])
+									clients[page][obj.oppid].send(JSON.stringify({code:"resign"}), noop);
+								break;
 						}
-						else
-							games[page] = {id:sid, fen:obj.fen}; //wait for opponent
-						break;
-					case "cancelnewgame": //if a user cancel his seek
-						delete games[page];
-						break;
-					case "resign":
-						if (!!clients[page][obj.oppid])
-							clients[page][obj.oppid].send(JSON.stringify({code:"resign"}), noop);
-						break;
+					});
 				}
-			});
-		}
-		socket.on("close", () => {
-			delete clients[page][sid];
-			// Remove potential pending game
-			if (!!games[page] && games[page]["id"] == sid)
-				delete games[page];
-			if (page != "index")
-			{
-				// Send to every client connected on index an update message for counts
-				Object.keys(clients["index"]).forEach( k => {
-					clients["index"][k].send(JSON.stringify({code:"decrease",vname:page}), noop);
+				socket.on("close", () => {
+					delete clients[page][sid];
+					// Remove potential pending game
+					if (!!games[page] && games[page]["id"] == sid)
+						delete games[page];
+					if (page != "index")
+					{
+						// Send to every client connected on index an update message for counts
+						Object.keys(clients["index"]).forEach( k => {
+							clients["index"][k].send(
+								JSON.stringify({code:"decrease",vname:page}), noop);
+						});
+					}
+					// Also notify potential opponents:
+					// hit all clients which check if sid corresponds
+					Object.keys(clients[page]).forEach( k => {
+						clients[page][k].send(JSON.stringify({code:"disconnect",id:sid}), noop);
+					});
 				});
-			}
-			// Also notify potential opponents: hit all clients which check if sid corresponds
-			Object.keys(clients[page]).forEach( k => {
-				clients[page][k].send(JSON.stringify({code:"disconnect",id:sid}), noop);
 			});
 		});
 	});
diff --git a/variants.js b/variants.js
deleted file mode 100644
index 2a20ffd9..00000000
--- a/variants.js
+++ /dev/null
@@ -1,16 +0,0 @@
-module.exports = [
-	{ "name": "Checkered", "description": "Shared pieces" },
-	{ "name": "Zen", "description": "Reverse captures" },
-	{ "name": "Atomic", "description": "Explosive captures" },
-	{ "name": "Chess960", "description": "Standard rules" },
-	{ "name": "Antiking", "description": "Keep antiking in check" },
-	{ "name": "Magnetic", "description": "Laws of attraction" },
-	{ "name": "Alice", "description": "Both sides of the mirror" },
-	{ "name": "Grand", "description": "Big board" },
-	{ "name": "Wildebeest", "description": "Balanced sliders & leapers" },
-	{ "name": "Loser", "description": "Lose all pieces" },
-	{ "name": "Crazyhouse", "description": "Captures reborn" },
-	{ "name": "Switching", "description": "Exchange pieces positions" },
-	{ "name": "Extinction", "description": "Capture all of a kind" },
-	{ "name": "Ultima", "description": "Non-standard captures" },
-];
diff --git a/views/index.pug b/views/index.pug
index 521c1258..9ef19675 100644
--- a/views/index.pug
+++ b/views/index.pug
@@ -99,7 +99,6 @@ block content
 block javascripts
 	script.
 		const variantArray = !{JSON.stringify(variantArray)};
-		//JSON.parse("!{variantArray}".replace(/\&quot;/g,'"'));
 	script(src="/javascripts/utils/misc.js")
 	script(src="/javascripts/utils/socket_url.js")
 	script(src="/javascripts/components/variantSummary.js")
diff --git a/views/rules/Chess960.pug b/views/rules/Chess960.pug
index 8b271c2f..331bc5ab 100644
--- a/views/rules/Chess960.pug
+++ b/views/rules/Chess960.pug
@@ -83,7 +83,7 @@ p.
 
 figure.diagram-container
 	.diagram
-		| fen:k7/2p5/5q2/2b5/4N3/2R3r1/3P4/7K w f6,d6,c5,f2,g3,g5:
+		| fen:k7/2p5/5q2/2b5/4N3/2R3r1/3P4/7K f6,d6,c5,f2,g3,g5:
 	figcaption Possible knight moves from e4.
 
 h4 Bishops
diff --git a/views/rules/Crazyhouse.pug b/views/rules/Crazyhouse.pug
index 7a3d91b7..004c0264 100644
--- a/views/rules/Crazyhouse.pug
+++ b/views/rules/Crazyhouse.pug
@@ -16,11 +16,20 @@ h3 Basics
 
 p
 	| Orthodox rules apply, with only one change:
-	| every time you capture a piece, your "reserve" of waiting pieces is augmented
-	| with this figure. At every move, you may choose to land one of your reserve
-	| pieces anywhere on the board (except last rank for pawns),
+	| every time a piece is captured, the capturer "reserve" of waiting pieces is
+	| augmented with this figure. At every move, you may choose to land one of your
+	| reserve pieces anywhere on the board (except last rank for pawns),
 	| instead of playing a regular move.
 
+p.
+	Move notation: an arobase '@' indicate piece landing. For example B@d4 means
+	a bishop rebirth on d4 square, and @f3 is a pawn landing on f3.
+
+figure.diagram-container
+	.diagram
+		| fen:qnbrkbnr/ppNppp1p/2p2p2/8/8/5N2/P1PPPPPP/BRNQKBNR:
+	figcaption After 1.b3 Nf6 2.Bxf6 gxf6 3.Nf3 c6?? 4.N@c7#
+
 p.
 	Note: when a promoted pawn is captured, capturing it put a pawn in the reserve,
 	not the promoted piece. This is to allow to gain material on last rank without
diff --git a/views/rules/Switching.pug b/views/rules/Switching.pug
index ce2fdbcc..8f1af19a 100644
--- a/views/rules/Switching.pug
+++ b/views/rules/Switching.pug
@@ -21,6 +21,10 @@ p
 	| Switching must involves two different units.
 	| Switching while the king is under check is not allowed.
 
+p.
+	Notation: a switch move is marked by the capital letter 'S' followed by
+	the exchanged squares. Example in diagram: Sb8c8
+
 figure.diagram-container
 	.diagram
 		| fen:1RB3k1/5ppp/8/8/8/8/8/K7:
diff --git a/views/rules/Ultima.pug b/views/rules/Ultima.pug
index b3856277..cf5da24f 100644
--- a/views/rules/Ultima.pug
+++ b/views/rules/Ultima.pug
@@ -60,7 +60,11 @@ p
 	| All other pieces capture passively: they land on a free square and captured
 	| units are determined by some characteristics of the movement.
 
-p Note: the immobilizer does not capture.
+p Note 1: the immobilizer does not capture.
+
+p.
+	Note 2: for passive captures, a 'X' is added at the end of the move notation,
+	to indicate that something was taken (replaying the game is necessary to know where).
 
 h4 Pawns/Pincers
 
diff --git a/views/variant.pug b/views/variant.pug
index a613a0b0..37bd5e7d 100644
--- a/views/variant.pug
+++ b/views/variant.pug
@@ -25,13 +25,17 @@ block javascripts
 	script(src="/javascripts/utils/socket_url.js")
 	script(src="/javascripts/utils/array.js")
 	script(src="/javascripts/utils/md5.js")
+	script(src="/javascripts/utils/printDiagram.js")
+	script(src="/javascripts/utils/ajax.js")
 	script(src="/javascripts/base_rules.js")
 	script(src="/javascripts/variants/" + variant + ".js")
 	script.
 		const VariantRules = #{variant}Rules;
 		const V = VariantRules; //because this variable is often used
 		const variant = "#{variant}";
+		const problemArray = !{JSON.stringify(problemArray)};
 	script(src="/javascripts/components/rules.js")
 	script(src="/javascripts/components/game.js")
+	script(src="/javascripts/components/problemSummary.js")
 	script(src="/javascripts/components/problems.js")
 	script(src="/javascripts/variant.js")
-- 
2.44.0