From: Benjamin Auder <benjamin.auder@somewhere>
Date: Mon, 20 Apr 2020 22:57:50 +0000 (+0200)
Subject: Some fixes, draw lines on board, add 7 variants
X-Git-Url: https://git.auder.net/%7B%7B%20asset%28%27mixstore/images/assets/current/doc/%7B%7B?a=commitdiff_plain;h=107dc1bd5361e2538b1551bdcc37c1e90a444b83;p=vchess.git

Some fixes, draw lines on board, add 7 variants
---

diff --git a/TODO b/TODO
index c1197fa8..2fc316ba 100644
--- a/TODO
+++ b/TODO
@@ -1,45 +1,31 @@
 Issue: embedded rules language not updated when language is set (in Analyse, Game and Problems)
 
-Chakart :)
-https://www.chessvariants.com/d.betza/chessvar/trapdoor.html
 https://www.chessvariants.com/crossover.dir/koopachess.html
 --> Can a stunned piece capture? Maybe not. ...recover? After 5 moves? Never?
-
-Bicolour Chess (Gabriel Authier, 1958). v1 et v2 : (Roméo Bédoni, 1958) 
-Kings are subject to check and checkmate by
-own as well as opponent’s pieces. The Q and
-QN are interchanged in the array
-.
-v2: y but a player may capture his
-own men (TODO: only v2?)
-
-Berolina Grid Chess, also known as
-Gridolina (originator not noted). A
-combination of Berolina and Grid Chess.
-Better than Grid Chess since Berolina pawns
-cross grid lines more easily. Described in
-World Game Review 10 as the most popular of
-the NOST combination games. (Nost-algia
-150, also Nost-algia 112 ‘not seen’) 
---> pourquoi pas, mais faudra pouvoir tracer des lignes sur plateau (Ball, Koth, Sittuyin, celle-là, Rococo)
++ Chakart :)
 
 https://www.chessvariants.com/diffmove.dir/checkers.html --> move forward (Multhopp)
 in 1974 by Hans Multhopp
-https://www.chessvariants.com/diffmove.dir/checkers.html
+https://www.chessvariants.com/diffmove.dir/checkers.html --> "Forward"
 
+Clorange:
 Clockwork Orange Chess (Fergus Duniho,
 1999). https://www.chessvariants.com/other.dir/clockworkorange.html
 implem : pieces code, yellow/red, easy
 
-http://abrobecker.free.fr/chess/fairyblitz.htm#football
-Le gagnant est le premier joueur à marquer un but, càd celui qui arrive à installer une de ses pièces dans les cages adverses: d8,e8 pour les blancs et d1,e1 pour les noirs.
+https://www.chessvariants.com/difftaking.dir/replacement.html
+
+https://www.chessvariants.com/other.dir/pocket.html
+https://www.chessvariants.com/other.dir/fugue.html
+https://www.chessvariants.com/rules/spartan-chess
+https://www.chessvariants.com/mvopponent.dir/avalanche.html
+
+https://www.chessvariants.com/mvopponent.dir/hypnotic-chess.html
+https://www.chessvariants.com/mvopponent.dir/mesmer-chess.html
 
-Recycle1 et Recycle2 (--> celle-là)
-http://abrobecker.free.fr/chess/fairyblitz.htm#deplaceurdevivants
-Philippe Rouzaud, Phénix 151-152, mai 2006): Un camp peut, à la place d'un coup orthodoxe, capturer une de ses pièces et replacer la pièce capturée immédiatement sur l'échiquier. Un roi peut rester en échec durant cette action. Une pièce déplacée sur l'échiquier peut mater. Un pion ne peut pas être déplacé en première ou dernière rangée. Un roi peut déplacer et être déplacé, y compris pour se soustraire à un échec. Le roque ne peut se faire que de manière orthodoxe.
-Rouzaud-Banaddou: 1.Fxb2 (=f5) Dxc7 (=e6) 2.fxe6 dxe6 3.Txb1 (=b5) Dxe7 (=c7) 4.Txb2 (=a3) Dxf7 (=d6) 5.Txg1 (=g5) Dxg7 (=f6) 6.Txf1 (=f7)+ Rxf8 (=h4) 7.Fxb2 (=e8) Rxg7 (=c5) 8.Rxd1 (=f8)+ Rxh8 (=g7) 9.Dxg8+ Txg8 10.Txg8#
---> Implémenté comme Dynamo, déplacement d'une pièce éventuellement avec self-capture, puis capture (sur case vide), forcée si premier coup illégal.
+=====
 
-http://abrobecker.free.fr/chess/fairyblitz.htm#madrasi
-Madrasi Chess, Abdul J. Karwathar, 1979): Deux pièces de même nature (excepté les rois) qui s'observent, se paralysent mutuellement en perdant tout pouvoir (déplacement, prise, donner échec ou mat) sauf celui de paralyser une autre pièce.
-1.e4 e5 2.Cf3 Cc6 3.Fc4 Fc5 4.Cxe5 d6 5.d4 (5.Fxf7+ Rxf7 est légal; ou 5.Dh5 Fe6 immobilisant le Fc4) 5...Fxd4 6.Dxd4 dxe5 et la Dame blanche est perdue.
+weiqi, go13, go9, gomoku, reversi
+avalam, qoridor, xiangqi, draughts, draughts8
+(puis quand hexaboards peut-être: hexavariants + Hex)
+Byo-yomi possible: 1h+b15,5m (15 pierres 5 minutes)
diff --git a/client/public/images/pieces/Absorption/ba.svg b/client/public/images/pieces/Absorption/ba.svg
new file mode 100644
index 00000000..0215b879
--- /dev/null
+++ b/client/public/images/pieces/Absorption/ba.svg
@@ -0,0 +1,232 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   height="26"
+   width="26"
+   version="1.0"
+   id="svg25"
+   sodipodi:docname="Chess_Adt26.svg"
+   inkscape:version="0.92.4 5da689c313, 2019-01-14">
+  <defs
+     id="defs29">
+    <filter
+       style="color-interpolation-filters:sRGB;"
+       inkscape:label="Colorize"
+       id="filter283">
+      <feComposite
+         in2="SourceGraphic"
+         operator="arithmetic"
+         k1="0"
+         k2="1"
+         result="composite1"
+         id="feComposite269" />
+      <feColorMatrix
+         in="composite1"
+         values="1"
+         type="saturate"
+         result="colormatrix1"
+         id="feColorMatrix271" />
+      <feFlood
+         flood-opacity="1"
+         flood-color="rgb(26,23,21)"
+         result="flood1"
+         id="feFlood273" />
+      <feBlend
+         in="flood1"
+         in2="colormatrix1"
+         mode="multiply"
+         result="blend1"
+         id="feBlend275" />
+      <feBlend
+         in2="blend1"
+         mode="screen"
+         result="blend2"
+         id="feBlend277" />
+      <feColorMatrix
+         in="blend2"
+         values="1"
+         type="saturate"
+         result="colormatrix2"
+         id="feColorMatrix279" />
+      <feComposite
+         in="colormatrix2"
+         in2="SourceGraphic"
+         operator="in"
+         k2="1"
+         result="composite2"
+         id="feComposite281" />
+    </filter>
+    <filter
+       style="color-interpolation-filters:sRGB;"
+       inkscape:label="Colorize"
+       id="filter299">
+      <feComposite
+         in2="SourceGraphic"
+         operator="arithmetic"
+         k1="0"
+         k2="1"
+         result="composite1"
+         id="feComposite285" />
+      <feColorMatrix
+         in="composite1"
+         values="1"
+         type="saturate"
+         result="colormatrix1"
+         id="feColorMatrix287" />
+      <feFlood
+         flood-opacity="1"
+         flood-color="rgb(26,23,21)"
+         result="flood1"
+         id="feFlood289" />
+      <feBlend
+         in="flood1"
+         in2="colormatrix1"
+         mode="multiply"
+         result="blend1"
+         id="feBlend291" />
+      <feBlend
+         in2="blend1"
+         mode="screen"
+         result="blend2"
+         id="feBlend293" />
+      <feColorMatrix
+         in="blend2"
+         values="1"
+         type="saturate"
+         result="colormatrix2"
+         id="feColorMatrix295" />
+      <feComposite
+         in="colormatrix2"
+         in2="SourceGraphic"
+         operator="in"
+         k2="1"
+         result="composite2"
+         id="feComposite297" />
+    </filter>
+  </defs>
+  <sodipodi:namedview
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1"
+     objecttolerance="10"
+     gridtolerance="10"
+     guidetolerance="10"
+     inkscape:pageopacity="0"
+     inkscape:pageshadow="2"
+     inkscape:window-width="960"
+     inkscape:window-height="1060"
+     id="namedview27"
+     showgrid="false"
+     inkscape:zoom="25.673415"
+     inkscape:cx="13.220339"
+     inkscape:cy="13"
+     inkscape:window-x="0"
+     inkscape:window-y="20"
+     inkscape:window-maximized="0"
+     inkscape:current-layer="svg25" />
+  <metadata
+     id="metadata2">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     transform="matrix(0.75757574,0,0,0.75757574,2.8791515,-7.9393932)"
+     id="g10"
+     style="filter:url(#filter283)">
+    <path
+       stroke-linejoin="miter"
+       d="m14.44,29.5c0.222,1.68-3.09,4.06-4.5,5-1.73,1.16-1.74,2.7-3,2.5-0.602-0.545,0.817-2.02,0-2-0.58,0,0.19,1.1-0.5,1.5-0.58,0-2.5,0.4-2.5-2.5,0-1.2,3.5-6.5,3.5-6.5s0.934-1.08,1-2c-0.419-0.575-0.258-1.48,0-2,0.365-0.731,1.5,1.5,1.5,1.5h1s0.5-1.5,1.5-2c0.517-0.258,0.5,2,0.5,2,6.07,0.578,9.29,4.37,9,16.5h-12.5c0-4.9,4.8-3.7,4.5-10.5"
+       fill-rule="evenodd"
+       stroke="#000"
+       stroke-linecap="round"
+       stroke-miterlimit="4"
+       stroke-dasharray="none"
+       stroke-width="1.31999993"
+       fill="#FFF"
+       id="path4" />
+    <path
+       fill="#000"
+       d="m9,23.5a0.5,0.5,0,1,1,-1,0,0.5,0.5,0,1,1,1,0z"
+       transform="translate(-3.4999999,10)"
+       id="path6" />
+    <path
+       fill="#000"
+       d="m9.6058,30.28a1.0281,1.4729,30,0,1,-1.7807,-1.028,1.0281,1.4729,30,0,1,1.7807,1.028z"
+       id="path8" />
+  </g>
+  <g
+     transform="translate(0,-4)"
+     id="g23"
+     style="filter:url(#filter299)">
+    <path
+       stroke-linejoin="round"
+       d="m18,18,4-8-4,4,0-6-3,5.125-2-5-2,5l-3-5.125v6l-4-4,3,8s2.0349-2.5,6-2.5,5,2.5,5,2.5z"
+       fill-rule="evenodd"
+       stroke="#000"
+       stroke-linecap="butt"
+       stroke-miterlimit="4"
+       stroke-dasharray="none"
+       stroke-width="0.80000001"
+       fill="#FFF"
+       id="path12" />
+    <path
+       id="path3804"
+       stroke-linejoin="miter"
+       d="m5.5,10a1.5,1.5,0,0,1,-3,0,1.5,1.5,0,1,1,3,0z"
+       stroke-dashoffset="0"
+       stroke="#000"
+       stroke-linecap="butt"
+       stroke-miterlimit="4"
+       stroke-dasharray="none"
+       stroke-width="0.75"
+       fill="#FFF" />
+    <use
+       xlink:href="#path3804"
+       transform="translate(4,-2)"
+       height="26"
+       width="26"
+       y="0"
+       x="0"
+       id="use15" />
+    <use
+       xlink:href="#path3804"
+       transform="translate(9,-2)"
+       height="26"
+       width="26"
+       y="0"
+       x="0"
+       id="use17" />
+    <use
+       xlink:href="#path3804"
+       transform="matrix(-1,0,0,1,26,0)"
+       height="26"
+       width="26"
+       y="0"
+       x="0"
+       id="use19" />
+    <use
+       xlink:href="#path3804"
+       transform="matrix(-1,0,0,1,22,-2)"
+       height="26"
+       width="26"
+       y="0"
+       x="0"
+       id="use21" />
+  </g>
+</svg>
diff --git a/client/public/images/pieces/Absorption/be.svg b/client/public/images/pieces/Absorption/be.svg
new file mode 100644
index 00000000..fd548016
--- /dev/null
+++ b/client/public/images/pieces/Absorption/be.svg
@@ -0,0 +1,205 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="45"
+   height="45"
+   id="svg3128"
+   sodipodi:version="0.32"
+   inkscape:version="0.92.2 2405546, 2018-03-11"
+   version="1.0"
+   sodipodi:docname="bm.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape">
+  <defs
+     id="defs3130">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 22.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="45 : 22.5 : 1"
+       inkscape:persp3d-origin="22.5 : 15 : 1"
+       id="perspective15" />
+    <inkscape:perspective
+       id="perspective12"
+       inkscape:persp3d-origin="22.5 : 15 : 1"
+       inkscape:vp_z="45 : 22.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 22.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective14"
+       inkscape:persp3d-origin="22.5 : 15 : 1"
+       inkscape:vp_z="45 : 22.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 22.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     gridtolerance="10000"
+     guidetolerance="10"
+     objecttolerance="10"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="8.0000004"
+     inkscape:cx="-3.9305949"
+     inkscape:cy="32.150177"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     height="45px"
+     width="45px"
+     inkscape:grid-points="true"
+     showgrid="false"
+     inkscape:window-width="1920"
+     inkscape:window-height="1180"
+     inkscape:window-x="0"
+     inkscape:window-y="20"
+     inkscape:window-maximized="0">
+    <inkscape:grid
+       id="GridFromPre046Settings"
+       type="xygrid"
+       originx="0"
+       originy="0"
+       spacingx="0.5"
+       spacingy="0.5"
+       color="#0000ff"
+       empcolor="#0000ff"
+       opacity="0.2"
+       empopacity="0.4"
+       empspacing="5" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata3133">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     style="display:inline">
+    <g
+       id="g3481"
+       transform="translate(0,-0.3093592)">
+      <path
+         sodipodi:nodetypes="cccc"
+         id="path3127"
+         d="M 34,35.130223 L 31,32.130223 L 14,32.130223 L 11,35.130223"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <g
+         id="g3476">
+        <path
+           style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="M 11,35.130223 L 11,40.130223 L 15,40.130223 L 15,38.130223 L 20,38.130223 L 20,40.130223 L 25,40.130223 L 25,38.130223 L 30,38.130223 L 30,40.130223 L 34,40.130223 L 34,35.130223"
+           id="path3129"
+           sodipodi:nodetypes="cccccccccccc" />
+        <path
+           style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="M 31.552703,32.417761 L 31.552703,30.302822 L 13.447297,30.302822 L 13.447297,32.417761"
+           id="path3125"
+           sodipodi:nodetypes="cccc" />
+        <path
+           style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+           d="M 11,35.130223 L 34,35.130223"
+           id="path5175" />
+      </g>
+    </g>
+    <g
+       id="g3323"
+       inkscape:label="Layer 1"
+       transform="matrix(-0.7654224,0,0,-0.7654224,37.205613,35.181827)">
+      <path
+         sodipodi:nodetypes="cccc"
+         id="path3491"
+         d="M 22.028958,36 C 11.528958,35 5.5289585,28 6.0289585,7 L 29.028958,7 C 29.028958,16 19.028958,13.5 21.028958,28"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="csccccccccccc"
+         id="path3495"
+         d="M 20.028958,28 C 19.644497,25.088722 25.581894,20.631376 28.028958,19 C 31.028958,17 30.848156,14.657108 33.028958,15 C 34.070678,15.944016 31.615529,18.037549 33.028958,18 C 34.028958,18 32.841626,16.768273 34.028958,16 C 35.028958,16 38.032119,15.000001 38.028958,20 C 38.028958,22 32.028958,32 32.028958,32 C 32.028958,32 30.143092,33.902129 30.028958,35.5 C 30.755005,36.494369 30.528958,37.5 30.528958,38.5 C 29.528958,39.5 27.528958,36 27.528958,36 L 25.528958,36 C 25.528958,36 24.747177,37.991926 23.028958,39 C 22.028958,39 22.028958,36 22.028958,36"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <path
+         transform="matrix(-1,0,0,-1,43.528958,44)"
+         d="M 9,23.5 A 0.5,0.5 0 0 1 8.5,24 0.5,0.5 0 0 1 8,23.5 0.5,0.5 0 0 1 8.5,23 0.5,0.5 0 0 1 9,23.5 Z"
+         sodipodi:ry="0.5"
+         sodipodi:rx="0.5"
+         sodipodi:cy="23.5"
+         sodipodi:cx="8.5"
+         id="path3499"
+         style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         sodipodi:type="arc" />
+      <path
+         transform="matrix(-0.866025,-0.5,0.5,-0.866025,34.336326,51.173394)"
+         d="M 15,15.5 A 0.5,1.5 0 0 1 14.5,17 0.5,1.5 0 0 1 14,15.5 0.5,1.5 0 0 1 14.5,14 0.5,1.5 0 0 1 15,15.5 Z"
+         sodipodi:ry="1.5"
+         sodipodi:rx="0.5"
+         sodipodi:cy="15.5"
+         sodipodi:cx="14.5"
+         id="path3501"
+         style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1.50000048;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         sodipodi:type="arc" />
+      <path
+         sodipodi:nodetypes="cc"
+         id="path8049"
+         d="M 7.0289585,7 C 6.0289585,27 12.528958,34.5 19.028958,35.5"
+         style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+    </g>
+    <path
+       style="fill:none;fill-opacity:0.75000000000000000;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 30,30 L 15,30"
+       id="path4590"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="stroke-width:0.12499999"
+       d="m 13.619404,13.174823 c -0.394017,-0.394017 -0.07405,-1.518272 0.664899,-2.336195 0.472422,-0.522914 0.760886,-0.615838 1.193319,-0.384407 0.55498,0.297016 0.02426,2.108462 -0.783498,2.67424 -0.332984,0.233231 -0.864904,0.256177 -1.07472,0.04636 z"
+       id="path25"
+       inkscape:connector-curvature="0" />
+    <path
+       style="stroke-width:0.12499999;fill:#ffffff"
+       d="m 13.619404,13.174823 c -0.394017,-0.394017 -0.07405,-1.518272 0.664899,-2.336195 0.472422,-0.522914 0.760886,-0.615838 1.193319,-0.384407 0.55498,0.297016 0.02426,2.108462 -0.783498,2.67424 -0.332984,0.233231 -0.864904,0.256177 -1.07472,0.04636 z"
+       id="path27"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:#ffffff;stroke-width:0.12499999"
+       d="M 9.7284769,19.995465 C 9.4282138,19.613743 9.4216123,19.566478 9.6106193,19.151653 c 0.4273267,-0.937881 1.8246537,-0.466056 1.6234227,0.54817 -0.132757,0.669112 -1.067423,0.852649 -1.5055651,0.295642 z"
+       id="path29"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:#000000;stroke-width:0.12499999"
+       d="m 15.741855,28.543572 c 0.220099,-1.274598 0.748632,-2.099833 2.525183,-3.942745 0.908491,-0.942427 1.837053,-1.984211 2.063469,-2.315073 0.967777,-1.414215 1.459247,-3.035319 1.585296,-5.229071 0.04816,-0.838108 0.158833,-1.500805 0.313986,-1.880033 0.240047,-0.586729 0.316295,-1.54212 0.143951,-1.803729 -0.156549,-0.237632 -0.593727,-0.307477 -0.832366,-0.13298 -0.136596,0.09988 -0.268141,0.423918 -0.329394,0.811403 -0.06397,0.40468 -0.269976,0.898212 -0.552737,1.32421 -0.616097,0.928189 -2.618232,2.816246 -4.187887,3.949269 -1.62137,1.170351 -2.353141,1.832373 -3.212442,2.90625 -0.755071,0.943617 -1.048909,1.097233 -0.66705,0.34873 0.513068,-1.005698 0.229767,-1.75498 -0.663552,-1.75498 -0.62307,0 -0.958908,0.350228 -0.958908,1 v 0.5 h -0.693468 c -0.5844284,0 -0.7403493,-0.05058 -0.9916167,-0.321708 -0.2968137,-0.320266 -0.5649148,-1.236132 -0.5649148,-1.929817 0,-0.476339 0.3438318,-1.335582 1.2999005,-3.248475 1.178757,-2.358448 2.893963,-5.320082 3.709346,-6.404903 0.897668,-1.1942942 1.247717,-2.1583929 1.00137,-2.7579542 -0.09031,-0.2198034 -0.193544,-0.5683925 -0.229404,-0.7746425 -0.06507,-0.3742752 -0.06469,-0.3745422 0.200365,-0.138169 0.146061,0.130257 0.440822,0.5099445 0.655025,0.84375 l 0.389458,0.6069189 h 1.172364 1.172363 l 0.305041,-0.5937499 c 0.353086,-0.6872679 1.014018,-1.5036985 1.127519,-1.3927915 0.04275,0.041777 0.112612,0.4494412 0.155239,0.9059202 0.04263,0.456479 0.139695,0.8814382 0.215708,0.9443539 0.07601,0.062916 0.700705,0.2099987 1.388205,0.3268512 2.952147,0.5017683 5.182687,1.6247709 6.373431,3.2088089 2.595633,3.452956 3.812697,8.42151 3.813765,15.569356 l 3.03e-4,2.03125 h -7.920436 -7.920434 z m -4.647451,-8.343749 c 0.318616,-0.318617 0.326565,-1.002752 0.0161,-1.386153 -0.460686,-0.568922 -1.5082252,-0.285298 -1.6950147,0.458931 -0.082225,0.32761 0.1938501,0.987469 0.4575301,1.09356 0.3816186,0.153543 0.9834636,0.07158 1.2213796,-0.166338 z m 3.746555,-7.078783 c 0.883721,-0.743602 1.303705,-2.321731 0.725632,-2.726629 -0.397788,-0.278621 -0.772927,-0.240934 -1.202877,0.120846 -0.480899,0.404649 -1.01931,1.483794 -1.01931,2.043017 0,0.878253 0.775188,1.169756 1.496555,0.562766 z"
+       id="path33"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:#000000;stroke-width:0.12499999"
+       d="m 13.531904,33.449822 0.868295,-0.875 h 8.131705 8.131704 l 0.868295,0.875 0.868295,0.875 h -9.868294 -9.868295 z"
+       id="path39"
+       inkscape:connector-curvature="0" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="M 11.374999,36.875 33.124998,37"
+       id="path41"
+       inkscape:connector-curvature="0" />
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer2"
+     inkscape:label="Layer 1#1"
+     style="display:inline" />
+</svg>
diff --git a/client/public/images/pieces/Absorption/bs.svg b/client/public/images/pieces/Absorption/bs.svg
new file mode 100644
index 00000000..afc27f05
--- /dev/null
+++ b/client/public/images/pieces/Absorption/bs.svg
@@ -0,0 +1,156 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="45"
+   height="45"
+   id="svg3128"
+   sodipodi:version="0.32"
+   inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+   version="1.0"
+   sodipodi:docname="Chess_adt45.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape">
+  <defs
+     id="defs3130">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 22.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="45 : 22.5 : 1"
+       inkscape:persp3d-origin="22.5 : 15 : 1"
+       id="perspective15" />
+    <inkscape:perspective
+       id="perspective12"
+       inkscape:persp3d-origin="22.5 : 15 : 1"
+       inkscape:vp_z="45 : 22.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 22.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     gridtolerance="10000"
+     guidetolerance="10"
+     objecttolerance="10"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="4.0000002"
+     inkscape:cx="70.546042"
+     inkscape:cy="-5.7955825"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     height="45px"
+     width="45px"
+     inkscape:grid-points="true"
+     showgrid="true"
+     inkscape:window-width="960"
+     inkscape:window-height="1060"
+     inkscape:window-x="0"
+     inkscape:window-y="20"
+     inkscape:window-maximized="0">
+    <inkscape:grid
+       id="GridFromPre046Settings"
+       type="xygrid"
+       originx="0"
+       originy="0"
+       spacingx="0.5"
+       spacingy="0.5"
+       color="#0000ff"
+       empcolor="#0000ff"
+       opacity="0.2"
+       empopacity="0.4"
+       empspacing="5" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata3133">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     style="display:inline">
+    <path
+       style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.50000000000000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 36,36 C 32.614745,35.027671 25.885256,36.430821 22.5,34 C 19.114744,36.430821 12.385255,35.027671 9,36 C 9,36 7.354102,36.541507 6,38 C 6.677051,38.972328 7.645898,38.986164 9,38.5 C 12.385255,37.527672 19.114744,38.958493 22.5,37.5 C 25.885256,38.958493 32.614745,37.527672 36,38.5 C 37.354102,38.986164 38.322949,38.972328 39,38 C 37.645898,36.055343 36,36 36,36 z"
+       id="path4582"
+       sodipodi:nodetypes="ccccccccc" />
+    <path
+       style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.50000000000000000;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 30,32 C 27.5,34.5 17.5,34.5 15,32 C 14.5,30.5 15,30 15,30 L 30,30 C 30,30 30.5,30.5 30,32 z"
+       id="path4584"
+       sodipodi:nodetypes="ccccc" />
+    <path
+       style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 30,30 L 15,30"
+       id="path4590"
+       sodipodi:nodetypes="cc" />
+    <g
+       id="g3323"
+       inkscape:label="Layer 1"
+       transform="matrix(-0.7654224,0,0,-0.7654224,37.205613,35.181827)"
+       style="fill:#000000;fill-opacity:1">
+      <path
+         sodipodi:nodetypes="cccc"
+         id="path3491"
+         d="M 22.028958,36 C 11.528958,35 5.5289585,28 6.0289585,7 L 29.028958,7 C 29.028958,16 19.028958,13.5 21.028958,28"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.50000000000000000;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="csccccccccccc"
+         id="path3495"
+         d="M 20.028958,28 C 19.644497,25.088722 25.581894,20.631376 28.028958,19 C 31.028958,17 30.848156,14.657108 33.028958,15 C 34.070678,15.944016 31.615529,18.037549 33.028958,18 C 34.028958,18 32.841626,16.768273 34.028958,16 C 35.028958,16 38.032119,15.000001 38.028958,20 C 38.028958,22 32.028958,32 32.028958,32 C 32.028958,32 30.143092,33.902129 30.028958,35.5 C 30.755005,36.494369 30.528958,37.5 30.528958,38.5 C 29.528958,39.5 27.528958,36 27.528958,36 L 25.528958,36 C 25.528958,36 24.747177,37.991926 23.028958,39 C 22.028958,39 22.028958,36 22.028958,36"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.50000000000000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <path
+         transform="matrix(-1,0,0,-1,43.528958,44)"
+         d="M 9,23.5 A 0.5,0.5 0 0 1 8.5,24 0.5,0.5 0 0 1 8,23.5 0.5,0.5 0 0 1 8.5,23 0.5,0.5 0 0 1 9,23.5 Z"
+         sodipodi:ry="0.5"
+         sodipodi:rx="0.5"
+         sodipodi:cy="23.5"
+         sodipodi:cx="8.5"
+         id="path3499"
+         style="opacity:1;fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:1.50000000000000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         sodipodi:type="arc" />
+      <path
+         transform="matrix(-0.866025,-0.5,0.5,-0.866025,34.336326,51.173394)"
+         d="M 15,15.5 A 0.5,1.5 0 0 1 14.5,17 0.5,1.5 0 0 1 14,15.5 0.5,1.5 0 0 1 14.5,14 0.5,1.5 0 0 1 15,15.5 Z"
+         sodipodi:ry="1.5"
+         sodipodi:rx="0.5"
+         sodipodi:cy="15.5"
+         sodipodi:cx="14.5"
+         id="path3501"
+         style="opacity:1;fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:1.50000048000000000;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         sodipodi:type="arc" />
+      <path
+         sodipodi:nodetypes="cc"
+         id="path8049"
+         d="M 7.0289585,7 C 6.0289585,27 12.528958,34.5 19.028958,35.5"
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+    </g>
+    <path
+       style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+       d="M 15,30 L 30,30"
+       id="path3369" />
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer2"
+     inkscape:label="Layer 1#1"
+     style="display:inline" />
+</svg>
diff --git a/client/public/images/pieces/Absorption/wa.svg b/client/public/images/pieces/Absorption/wa.svg
new file mode 100644
index 00000000..beeaaea1
--- /dev/null
+++ b/client/public/images/pieces/Absorption/wa.svg
@@ -0,0 +1,25 @@
+<!-- Created with Inkscape (http://www.inkscape.org/) -->

+<svg xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="26" width="26" version="1.0" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/">

+ <metadata>

+  <rdf:RDF>

+   <cc:Work rdf:about="">

+    <dc:format>image/svg+xml</dc:format>

+    <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>

+    <dc:title/>

+   </cc:Work>

+  </rdf:RDF>

+ </metadata>

+ <g transform="matrix(0.75757574,0,0,0.75757574,2.8791515,-7.9393932)">

+  <path stroke-linejoin="miter" d="m14.44,29.5c0.222,1.68-3.09,4.06-4.5,5-1.73,1.16-1.74,2.7-3,2.5-0.602-0.545,0.817-2.02,0-2-0.58,0,0.19,1.1-0.5,1.5-0.58,0-2.5,0.4-2.5-2.5,0-1.2,3.5-6.5,3.5-6.5s0.934-1.08,1-2c-0.419-0.575-0.258-1.48,0-2,0.365-0.731,1.5,1.5,1.5,1.5h1s0.5-1.5,1.5-2c0.517-0.258,0.5,2,0.5,2,6.07,0.578,9.29,4.37,9,16.5h-12.5c0-4.9,4.8-3.7,4.5-10.5" fill-rule="evenodd" stroke="#000" stroke-linecap="round" stroke-miterlimit="4" stroke-dasharray="none" stroke-width="1.31999993" fill="#FFF"/>

+  <path fill="#000" d="m9,23.5a0.5,0.5,0,1,1,-1,0,0.5,0.5,0,1,1,1,0z" transform="translate(-3.4999999,10)"/>

+  <path fill="#000" d="m9.6058,30.28a1.0281,1.4729,30,0,1,-1.7807,-1.028,1.0281,1.4729,30,0,1,1.7807,1.028z"/>

+ </g>

+ <g transform="translate(0,-4)">

+  <path stroke-linejoin="round" d="m18,18,4-8-4,4,0-6-3,5.125-2-5-2,5l-3-5.125v6l-4-4,3,8s2.0349-2.5,6-2.5,5,2.5,5,2.5z" fill-rule="evenodd" stroke="#000" stroke-linecap="butt" stroke-miterlimit="4" stroke-dasharray="none" stroke-width="0.80000001" fill="#FFF"/>

+  <path id="path3804" stroke-linejoin="miter" d="m5.5,10a1.5,1.5,0,0,1,-3,0,1.5,1.5,0,1,1,3,0z" stroke-dashoffset="0" stroke="#000" stroke-linecap="butt" stroke-miterlimit="4" stroke-dasharray="none" stroke-width="0.75" fill="#FFF"/>

+  <use xlink:href="#path3804" transform="translate(4,-2)" height="26" width="26" y="0" x="0"/>

+  <use xlink:href="#path3804" transform="translate(9,-2)" height="26" width="26" y="0" x="0"/>

+  <use xlink:href="#path3804" transform="matrix(-1,0,0,1,26,0)" height="26" width="26" y="0" x="0"/>

+  <use xlink:href="#path3804" transform="matrix(-1,0,0,1,22,-2)" height="26" width="26" y="0" x="0"/>

+ </g>

+</svg>

diff --git a/client/public/images/pieces/Absorption/we.svg b/client/public/images/pieces/Absorption/we.svg
new file mode 100644
index 00000000..fd0288db
--- /dev/null
+++ b/client/public/images/pieces/Absorption/we.svg
@@ -0,0 +1,175 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="45"
+   height="45"
+   id="svg3128"
+   sodipodi:version="0.32"
+   inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+   version="1.0"
+   sodipodi:docname="Chess_clt45.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape">
+  <defs
+     id="defs3130">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 22.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="45 : 22.5 : 1"
+       inkscape:persp3d-origin="22.5 : 15 : 1"
+       id="perspective15" />
+    <inkscape:perspective
+       id="perspective12"
+       inkscape:persp3d-origin="22.5 : 15 : 1"
+       inkscape:vp_z="45 : 22.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 22.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective14"
+       inkscape:persp3d-origin="22.5 : 15 : 1"
+       inkscape:vp_z="45 : 22.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 22.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     gridtolerance="10000"
+     guidetolerance="10"
+     objecttolerance="10"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="8.0000004"
+     inkscape:cx="-4.1805949"
+     inkscape:cy="32.150177"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     height="45px"
+     width="45px"
+     inkscape:grid-points="true"
+     showgrid="false"
+     inkscape:window-width="960"
+     inkscape:window-height="1060"
+     inkscape:window-x="0"
+     inkscape:window-y="20"
+     inkscape:window-maximized="0">
+    <inkscape:grid
+       id="GridFromPre046Settings"
+       type="xygrid"
+       originx="0"
+       originy="0"
+       spacingx="0.5"
+       spacingy="0.5"
+       color="#0000ff"
+       empcolor="#0000ff"
+       opacity="0.2"
+       empopacity="0.4"
+       empspacing="5" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata3133">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     style="display:inline">
+    <g
+       id="g3481"
+       transform="translate(0,-0.3093592)">
+      <path
+         sodipodi:nodetypes="cccc"
+         id="path3127"
+         d="M 34,35.130223 L 31,32.130223 L 14,32.130223 L 11,35.130223"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <g
+         id="g3476">
+        <path
+           style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="M 11,35.130223 L 11,40.130223 L 15,40.130223 L 15,38.130223 L 20,38.130223 L 20,40.130223 L 25,40.130223 L 25,38.130223 L 30,38.130223 L 30,40.130223 L 34,40.130223 L 34,35.130223"
+           id="path3129"
+           sodipodi:nodetypes="cccccccccccc" />
+        <path
+           style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+           d="M 31.552703,32.417761 L 31.552703,30.302822 L 13.447297,30.302822 L 13.447297,32.417761"
+           id="path3125"
+           sodipodi:nodetypes="cccc" />
+        <path
+           style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:round;stroke-linejoin:miter;stroke-opacity:1"
+           d="M 11,35.130223 L 34,35.130223"
+           id="path5175" />
+      </g>
+    </g>
+    <g
+       id="g3323"
+       inkscape:label="Layer 1"
+       transform="matrix(-0.7654224,0,0,-0.7654224,37.205613,35.181827)">
+      <path
+         sodipodi:nodetypes="cccc"
+         id="path3491"
+         d="M 22.028958,36 C 11.528958,35 5.5289585,28 6.0289585,7 L 29.028958,7 C 29.028958,16 19.028958,13.5 21.028958,28"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="csccccccccccc"
+         id="path3495"
+         d="M 20.028958,28 C 19.644497,25.088722 25.581894,20.631376 28.028958,19 C 31.028958,17 30.848156,14.657108 33.028958,15 C 34.070678,15.944016 31.615529,18.037549 33.028958,18 C 34.028958,18 32.841626,16.768273 34.028958,16 C 35.028958,16 38.032119,15.000001 38.028958,20 C 38.028958,22 32.028958,32 32.028958,32 C 32.028958,32 30.143092,33.902129 30.028958,35.5 C 30.755005,36.494369 30.528958,37.5 30.528958,38.5 C 29.528958,39.5 27.528958,36 27.528958,36 L 25.528958,36 C 25.528958,36 24.747177,37.991926 23.028958,39 C 22.028958,39 22.028958,36 22.028958,36"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <path
+         transform="matrix(-1,0,0,-1,43.528958,44)"
+         d="M 9,23.5 A 0.5,0.5 0 0 1 8.5,24 0.5,0.5 0 0 1 8,23.5 0.5,0.5 0 0 1 8.5,23 0.5,0.5 0 0 1 9,23.5 Z"
+         sodipodi:ry="0.5"
+         sodipodi:rx="0.5"
+         sodipodi:cy="23.5"
+         sodipodi:cx="8.5"
+         id="path3499"
+         style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         sodipodi:type="arc" />
+      <path
+         transform="matrix(-0.866025,-0.5,0.5,-0.866025,34.336326,51.173394)"
+         d="M 15,15.5 A 0.5,1.5 0 0 1 14.5,17 0.5,1.5 0 0 1 14,15.5 0.5,1.5 0 0 1 14.5,14 0.5,1.5 0 0 1 15,15.5 Z"
+         sodipodi:ry="1.5"
+         sodipodi:rx="0.5"
+         sodipodi:cy="15.5"
+         sodipodi:cx="14.5"
+         id="path3501"
+         style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1.50000048;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         sodipodi:type="arc" />
+      <path
+         sodipodi:nodetypes="cc"
+         id="path8049"
+         d="M 7.0289585,7 C 6.0289585,27 12.528958,34.5 19.028958,35.5"
+         style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+    </g>
+    <path
+       style="fill:none;fill-opacity:0.75000000000000000;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 30,30 L 15,30"
+       id="path4590"
+       sodipodi:nodetypes="cc" />
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer2"
+     inkscape:label="Layer 1#1"
+     style="display:inline" />
+</svg>
diff --git a/client/public/images/pieces/Absorption/ws.svg b/client/public/images/pieces/Absorption/ws.svg
new file mode 100644
index 00000000..b45ea509
--- /dev/null
+++ b/client/public/images/pieces/Absorption/ws.svg
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="45"
+   height="45"
+   id="svg3128"
+   sodipodi:version="0.32"
+   inkscape:version="0.92.2 5c3e80d, 2017-08-06"
+   version="1.0"
+   sodipodi:docname="Chess_alt45.svg"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape">
+  <defs
+     id="defs3130">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 22.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="45 : 22.5 : 1"
+       inkscape:persp3d-origin="22.5 : 15 : 1"
+       id="perspective15" />
+    <inkscape:perspective
+       id="perspective12"
+       inkscape:persp3d-origin="22.5 : 15 : 1"
+       inkscape:vp_z="45 : 22.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 22.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     gridtolerance="10000"
+     guidetolerance="10"
+     objecttolerance="10"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="2.0000001"
+     inkscape:cx="59.308779"
+     inkscape:cy="58.114787"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     height="45px"
+     width="45px"
+     inkscape:grid-points="true"
+     showgrid="false"
+     inkscape:window-width="960"
+     inkscape:window-height="1060"
+     inkscape:window-x="0"
+     inkscape:window-y="20"
+     inkscape:window-maximized="0">
+    <inkscape:grid
+       id="GridFromPre046Settings"
+       type="xygrid"
+       originx="0"
+       originy="0"
+       spacingx="0.5"
+       spacingy="0.5"
+       color="#0000ff"
+       empcolor="#0000ff"
+       opacity="0.2"
+       empopacity="0.4"
+       empspacing="5" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata3133">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     style="display:inline">
+    <path
+       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 36,36 C 32.614745,35.027671 25.885256,36.430821 22.5,34 C 19.114744,36.430821 12.385255,35.027671 9,36 C 9,36 7.354102,36.541507 6,38 C 6.677051,38.972328 7.645898,38.986164 9,38.5 C 12.385255,37.527672 19.114744,38.958493 22.5,37.5 C 25.885256,38.958493 32.614745,37.527672 36,38.5 C 37.354102,38.986164 38.322949,38.972328 39,38 C 37.645898,36.055343 36,36 36,36 z"
+       id="path4582"
+       sodipodi:nodetypes="ccccccccc" />
+    <path
+       style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 30,32 C 27.5,34.5 17.5,34.5 15,32 C 14.5,30.5 15,30 15,30 L 30,30 C 30,30 30.5,30.5 30,32 z"
+       id="path4584"
+       sodipodi:nodetypes="ccccc" />
+    <path
+       style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 30,30 L 15,30"
+       id="path4590"
+       sodipodi:nodetypes="cc" />
+    <g
+       id="g3323"
+       inkscape:label="Layer 1"
+       transform="matrix(-0.7654224,0,0,-0.7654224,37.205613,35.181827)">
+      <path
+         sodipodi:nodetypes="cccc"
+         id="path3491"
+         d="M 22.028958,36 C 11.528958,35 5.5289585,28 6.0289585,7 L 29.028958,7 C 29.028958,16 19.028958,13.5 21.028958,28"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <path
+         sodipodi:nodetypes="csccccccccccc"
+         id="path3495"
+         d="M 20.028958,28 C 19.644497,25.088722 25.581894,20.631376 28.028958,19 C 31.028958,17 30.848156,14.657108 33.028958,15 C 34.070678,15.944016 31.615529,18.037549 33.028958,18 C 34.028958,18 32.841626,16.768273 34.028958,16 C 35.028958,16 38.032119,15.000001 38.028958,20 C 38.028958,22 32.028958,32 32.028958,32 C 32.028958,32 30.143092,33.902129 30.028958,35.5 C 30.755005,36.494369 30.528958,37.5 30.528958,38.5 C 29.528958,39.5 27.528958,36 27.528958,36 L 25.528958,36 C 25.528958,36 24.747177,37.991926 23.028958,39 C 22.028958,39 22.028958,36 22.028958,36"
+         style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+      <path
+         transform="matrix(-1,0,0,-1,43.528958,44)"
+         d="M 9,23.5 A 0.5,0.5 0 0 1 8.5,24 0.5,0.5 0 0 1 8,23.5 0.5,0.5 0 0 1 8.5,23 0.5,0.5 0 0 1 9,23.5 Z"
+         sodipodi:ry="0.5"
+         sodipodi:rx="0.5"
+         sodipodi:cy="23.5"
+         sodipodi:cx="8.5"
+         id="path3499"
+         style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         sodipodi:type="arc" />
+      <path
+         transform="matrix(-0.866025,-0.5,0.5,-0.866025,34.336326,51.173394)"
+         d="M 15,15.5 A 0.5,1.5 0 0 1 14.5,17 0.5,1.5 0 0 1 14,15.5 0.5,1.5 0 0 1 14.5,14 0.5,1.5 0 0 1 15,15.5 Z"
+         sodipodi:ry="1.5"
+         sodipodi:rx="0.5"
+         sodipodi:cy="15.5"
+         sodipodi:cx="14.5"
+         id="path3501"
+         style="opacity:1;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1.50000048;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+         sodipodi:type="arc" />
+      <path
+         sodipodi:nodetypes="cc"
+         id="path8049"
+         d="M 7.0289585,7 C 6.0289585,27 12.528958,34.5 19.028958,35.5"
+         style="fill:none;fill-opacity:0.75;fill-rule:evenodd;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+    </g>
+  </g>
+  <g
+     inkscape:groupmode="layer"
+     id="layer2"
+     inkscape:label="Layer 1#1"
+     style="display:inline" />
+</svg>
diff --git a/client/src/base_rules.js b/client/src/base_rules.js
index 53001f63..99de6265 100644
--- a/client/src/base_rules.js
+++ b/client/src/base_rules.js
@@ -93,6 +93,25 @@ export const ChessRules = class ChessRules {
     return V.CanFlip;
   }
 
+  // For (generally old) variants without checkered board
+  static get Monochrome() {
+    return false;
+  }
+
+  // Some variants require lines drawing
+  static get Lines() {
+    if (V.Monochrome) {
+      let lines = [];
+      // Draw all inter-squares lines
+      for (let i = 0; i <= V.size.x; i++)
+        lines.push([[i, 0], [i, V.size.y]]);
+      for (let j = 0; j <= V.size.y; j++)
+        lines.push([[0, j], [V.size.x, j]]);
+      return lines;
+    }
+    return null;
+  }
+
   // Some variants use click infos:
   doClick() {
     return null;
@@ -998,9 +1017,8 @@ export const ChessRules = class ChessRules {
         if (this.board[i][j] != V.EMPTY && this.getColor(i, j) == color) {
           const moves = this.getPotentialMovesFrom([i, j]);
           if (moves.length > 0) {
-            for (let k = 0; k < moves.length; k++) {
+            for (let k = 0; k < moves.length; k++)
               if (this.filterValid([moves[k]]).length > 0) return true;
-            }
           }
         }
       }
@@ -1042,21 +1060,15 @@ export const ChessRules = class ChessRules {
   }
 
   // Is square x,y attacked by 'color' pawns ?
-  isAttackedByPawn([x, y], color) {
+  isAttackedByPawn(sq, color) {
     const pawnShift = (color == "w" ? 1 : -1);
-    if (x + pawnShift >= 0 && x + pawnShift < V.size.x) {
-      for (let i of [-1, 1]) {
-        if (
-          y + i >= 0 &&
-          y + i < V.size.y &&
-          this.getPiece(x + pawnShift, y + i) == V.PAWN &&
-          this.getColor(x + pawnShift, y + i) == color
-        ) {
-          return true;
-        }
-      }
-    }
-    return false;
+    return this.isAttackedBySlideNJump(
+      sq,
+      color,
+      V.PAWN,
+      [[pawnShift, 1], [pawnShift, -1]],
+      "oneStep"
+    );
   }
 
   // Is square x,y attacked by 'color' rooks ?
diff --git a/client/src/components/BaseGame.vue b/client/src/components/BaseGame.vue
index e309c5df..70a304b8 100644
--- a/client/src/components/BaseGame.vue
+++ b/client/src/components/BaseGame.vue
@@ -55,7 +55,7 @@ div#baseGame
         @showrules="showRules"
         @analyze="toggleAnalyze"
         @goto-move="gotoMove"
-        @reset-arrows="resetArrows"
+        @redraw-board="redrawBoard"
       )
     .clearer
 </template>
@@ -200,9 +200,8 @@ export default {
       if (e.deltaY < 0) this.undo();
       else if (e.deltaY > 0) this.play();
     },
-    resetArrows: function() {
-      // TODO: make arrows scale with board, and remove this
-      this.$refs["board"].cancelResetArrows();
+    redrawBoard: function() {
+      this.$refs["board"].re_setDrawings();
     },
     showRules: function() {
       // The button is here only on Game page:
diff --git a/client/src/components/Board.vue b/client/src/components/Board.vue
index 8a42d34e..29c00645 100644
--- a/client/src/components/Board.vue
+++ b/client/src/components/Board.vue
@@ -22,10 +22,11 @@ export default {
       mobileBrowser: ("ontouchstart" in window),
       possibleMoves: [], //filled after each valid click/dragstart
       choices: [], //promotion pieces, or checkered captures... (as moves)
+      containerPos: null,
       selectedPiece: null, //moving piece (or clicked piece)
       start: null, //pixels coordinates + id of starting square (click or drag)
       startArrow: null,
-      movingArrow: { x: -1, y: -1 },
+      movingArrow: null,
       arrows: [], //object of {start: x,y / end: x,y}
       circles: {}, //object of squares' ID --> true (TODO: use a set?)
       click: "",
@@ -36,11 +37,10 @@ export default {
   render(h) {
     if (!this.vr) {
       // Return empty div of class 'game' to avoid error when setting size
-      return h("div", {
-        class: {
-          game: true
-        }
-      });
+      return h(
+        "div",
+        { "class": { game: true } }
+      );
     }
     const [sizeX, sizeY] = [V.size.x, V.size.y];
     // Precompute hints squares to facilitate rendering
@@ -100,6 +100,7 @@ export default {
     const gameDiv = h(
       "div",
       {
+        attrs: { id: "gamePosition" },
         "class": {
           game: true,
           clearer: true
@@ -173,8 +174,8 @@ export default {
                 "class": {
                   board: true,
                   ["board" + sizeY]: true,
-                  "light-square": lightSquare,
-                  "dark-square": !lightSquare,
+                  "light-square": lightSquare && !V.Monochrome,
+                  "dark-square": !lightSquare || !!V.Monochrome,
                   [this.settings.bcolor]: true,
                   "in-shadow": inShadow(ci, cj),
                   "highlight-light": inHighlight(ci, cj) && lightSquare,
@@ -197,6 +198,8 @@ export default {
     if (!!this.vr.reserve) {
       const playingColor = this.userColor || "w"; //default for an observer
       const shiftIdx = playingColor == "w" ? 0 : 1;
+      // Some variants have more than sizeY reserve pieces (Clorange: 10)
+      const reserveSquareNb = Math.max(sizeY, V.RESERVE_PIECES.length);
       let myReservePiecesArray = [];
       for (let i = 0; i < V.RESERVE_PIECES.length; i++) {
         const qty = this.vr.reserve[playingColor][V.RESERVE_PIECES[i]];
@@ -204,7 +207,7 @@ export default {
           h(
             "div",
             {
-              "class": { board: true, ["board" + sizeY]: true },
+              "class": { board: true, ["board" + reserveSquareNb]: true },
               attrs: { id: getSquareId({ x: sizeX + shiftIdx, y: i }) },
               style: { opacity: qty > 0 ? 1 : 0.35 }
             },
@@ -231,7 +234,7 @@ export default {
           h(
             "div",
             {
-              "class": { board: true, ["board" + sizeY]: true },
+              "class": { board: true, ["board" + reserveSquareNb]: true },
               attrs: { id: getSquareId({ x: sizeX + (1 - shiftIdx), y: i }) },
               style: { opacity: qty > 0 ? 1 : 0.35 }
             },
@@ -256,7 +259,8 @@ export default {
       );
       // Center reserves, assuming same number of pieces for each side:
       const nbReservePieces = myReservePiecesArray.length;
-      const marginLeft = ((100 - nbReservePieces * (100 / sizeY)) / 2) + "%";
+      const marginLeft =
+        ((100 - nbReservePieces * (100 / reserveSquareNb)) / 2) + "%";
       const reserveTop =
         h(
           "div",
@@ -311,12 +315,10 @@ export default {
     }
     elementArray.push(gameDiv);
     if (!!this.vr.reserve) elementArray.push(reserveBottom);
-    const boardElt = document.querySelector(".game");
-    // boardElt might be undefine (at first drawing),
-    // but it won't be used in this case.
-    const squareWidth = (!!boardElt ? boardElt.offsetWidth / sizeY : 42);
+    const boardElt = document.getElementById("gamePosition");
+    // boardElt might be undefine (at first drawing)
     if (this.choices.length > 0 && !!boardElt) {
-      // No choices to show at first drawing
+      const squareWidth = boardElt.offsetWidth / sizeY;
       const offset = [boardElt.offsetTop, boardElt.offsetLeft];
       const maxNbeltsPerRow = Math.min(this.choices.length, sizeY);
       let topOffset = offset[0] + (sizeY / 2) * squareWidth - squareWidth / 2;
@@ -391,98 +393,6 @@ export default {
       );
       elementArray.unshift(choices);
     }
-    if (
-      !this.mobileBrowser &&
-      (this.arrows.length > 0 || this.movingArrow.x >= 0)
-    ) {
-      let svgArrows = [];
-      const arrowWidth = squareWidth / 4;
-      this.arrows.forEach(a => {
-        const endPoint = this.adjustEndArrow(a.start, a.end, squareWidth);
-        svgArrows.push(
-          h(
-            "path",
-            {
-              "class": { "svg-arrow": true },
-              attrs: {
-                d: (
-                  "M" + a.start.x + "," + a.start.y + " " +
-                  "L" + endPoint.x + "," + endPoint.y
-                ),
-                style: "stroke-width:" + arrowWidth + "px"
-              }
-            }
-          )
-        );
-      });
-      if (this.movingArrow.x >= 0) {
-        const endPoint =
-          this.adjustEndArrow(this.startArrow, this.movingArrow, squareWidth);
-        svgArrows.push(
-          h(
-            "path",
-            {
-              "class": { "svg-arrow": true },
-              attrs: {
-                d: (
-                  "M" + this.startArrow.x + "," + this.startArrow.y + " " +
-                  "L" + endPoint.x + "," + endPoint.y
-                ),
-                style: "stroke-width:" + arrowWidth + "px"
-              }
-            }
-          )
-        );
-      }
-      // Add SVG element for drawing arrows
-      elementArray.push(
-        h(
-          "svg",
-          {
-            attrs: {
-              id: "arrowCanvas",
-              stroke: "none"
-            }
-          },
-          [
-            h(
-              "defs",
-              {},
-              [
-                h(
-                  "marker",
-                  {
-                    attrs: {
-                      id: "arrow",
-                      markerWidth: (2 * arrowWidth) + "px",
-                      markerHeight: (3 * arrowWidth) + "px",
-                      markerUnits: "userSpaceOnUse",
-                      refX: "0",
-                      refY: (1.5 * arrowWidth) + "px",
-                      orient: "auto"
-                    }
-                  },
-                  [
-                    h(
-                      "path",
-                      {
-                        "class": { "arrow-head": true },
-                        attrs: {
-                          d: (
-                            "M0,0 L0," + (3 * arrowWidth) + " L" +
-                            (2 * arrowWidth) + "," + (1.5 * arrowWidth) + " z"
-                          )
-                        }
-                      }
-                    )
-                  ]
-                )
-              ]
-            )
-          ].concat(svgArrows)
-        )
-      );
-    }
     let onEvents = {};
     // NOTE: click = mousedown + mouseup
     if (this.mobileBrowser) {
@@ -503,7 +413,16 @@ export default {
         }
       };
     }
-    return h("div", onEvents, elementArray);
+    return (
+      h(
+        "div",
+        Object.assign({ attrs: { id: "rootBoardElement" } }, onEvents),
+        elementArray
+      )
+    );
+  },
+  updated: function() {
+    this.re_setDrawings();
   },
   methods: {
     blockContextMenu: function(e) {
@@ -515,23 +434,166 @@ export default {
       this.startArrow = null;
       this.arrows = [];
       this.circles = {};
+      const curCanvas = document.getElementById("arrowCanvas");
+      if (!!curCanvas) curCanvas.parentNode.removeChild(curCanvas);
+    },
+    coordsToXY: function(coords, top, left, squareWidth) {
+      return {
+        // [1] for x and [0] for y because conventions in rules are inversed.
+        x: (
+          left + window.scrollX +
+          (
+            squareWidth *
+            (this.orientation == 'w' ? coords[1] : (V.size.y - coords[1]))
+          )
+        ),
+        y: (
+          top + window.scrollY +
+          (
+            squareWidth *
+            (this.orientation == 'w' ? coords[0] : (V.size.x - coords[0]))
+          )
+        )
+      };
     },
-    adjustEndArrow: function(start, end, squareWidth) {
+    computeEndArrow: function(start, end, top, left, squareWidth) {
+      const endCoords = this.coordsToXY(end, top, left, squareWidth);
+      const delta = [endCoords.x - start.x, endCoords.y - start.y];
+      const dist = Math.sqrt(delta[0] * delta[0] + delta[1] * delta[1]);
       // Simple heuristic for now, just remove 1/3 square.
       // TODO: should depend on the orientation.
-      const delta = [end.x - start.x, end.y - start.y];
-      const dist = Math.sqrt(delta[0] * delta[0] + delta[1] * delta[1]);
       const fracSqWidth = squareWidth / 3;
       return {
-        x: end.x - delta[0] * fracSqWidth / dist,
-        y: end.y - delta[1] * fracSqWidth / dist
+        x: endCoords.x - delta[0] * fracSqWidth / dist,
+        y: endCoords.y - delta[1] * fracSqWidth / dist
       };
     },
+    drawCurrentArrow: function() {
+      const boardElt = document.getElementById("gamePosition");
+      const squareWidth = boardElt.offsetWidth / V.size.y;
+      const bPos = boardElt.getBoundingClientRect();
+      const aStart =
+        this.coordsToXY(
+          [this.startArrow[0] + 0.5, this.startArrow[1] + 0.5],
+          bPos.top, bPos.left, squareWidth);
+      const aEnd =
+        this.computeEndArrow(
+          aStart, [this.movingArrow[0] + 0.5, this.movingArrow[1] + 0.5],
+          bPos.top, bPos.left, squareWidth);
+      let currentArrow = document.getElementById("currentArrow");
+      const d =
+        "M" + aStart.x + "," + aStart.y + " " + "L" + aEnd.x + "," + aEnd.y;
+      const arrowWidth = squareWidth / 4;
+      if (!!currentArrow) currentArrow.setAttribute("d", d);
+      else {
+        let domArrow =
+          document.createElementNS("http://www.w3.org/2000/svg", "path");
+        domArrow.classList.add("svg-arrow");
+        domArrow.id = "currentArrow";
+        domArrow.setAttribute("d", d);
+        domArrow.style = "stroke-width:" + arrowWidth + "px";
+        document.getElementById("arrowCanvas")
+          .insertAdjacentElement("beforeend", domArrow);
+      }
+    },
+    addArrow: function(arrow) {
+      this.arrows.push(arrow);
+      // Also add to DOM:
+      const boardElt = document.getElementById("gamePosition");
+      const squareWidth = boardElt.offsetWidth / V.size.y;
+      const bPos = boardElt.getBoundingClientRect();
+      const newArrow =
+        this.getSvgArrow(arrow, bPos.top, bPos.left, squareWidth);
+      document.getElementById("arrowCanvas")
+        .insertAdjacentElement("beforeend", newArrow);
+    },
+    getSvgArrow: function(arrow, top, left, squareWidth) {
+      const aStart =
+        this.coordsToXY(
+          [arrow.start[0] + 0.5, arrow.start[1] + 0.5],
+          top, left, squareWidth);
+      const aEnd =
+        this.computeEndArrow(
+          aStart, [arrow.end[0] + 0.5, arrow.end[1] + 0.5],
+          top, left, squareWidth);
+      const arrowWidth = squareWidth / 4;
+      let path =
+        document.createElementNS("http://www.w3.org/2000/svg", "path");
+      path.classList.add("svg-arrow");
+      path.setAttribute(
+        "d",
+        "M" + aStart.x + "," + aStart.y + " " + "L" + aEnd.x + "," + aEnd.y
+      );
+      path.style = "stroke-width:" + arrowWidth + "px";
+      return path;
+    },
+    re_setDrawings: function() {
+      // Remove current canvas, if any
+      const curCanvas = document.getElementById("arrowCanvas");
+      if (!!curCanvas) curCanvas.parentNode.removeChild(curCanvas);
+      // Add some drawing on board (for some variants + arrows and circles)
+      const boardElt = document.getElementById("gamePosition");
+      const squareWidth = boardElt.offsetWidth / V.size.y;
+      const bPos = boardElt.getBoundingClientRect();
+      let svgArrows = [];
+      this.arrows.forEach(a => {
+        svgArrows.push(this.getSvgArrow(a, bPos.top, bPos.left, squareWidth));
+      });
+      let vLines = [];
+      if (!!V.Lines) {
+        V.Lines.forEach(line => {
+          const lStart =
+            this.coordsToXY(line[0], bPos.top, bPos.left, squareWidth);
+          const lEnd =
+            this.coordsToXY(line[1], bPos.top, bPos.left, squareWidth);
+          let path =
+            document.createElementNS("http://www.w3.org/2000/svg", "path");
+          path.classList.add("svg-line");
+          path.setAttribute(
+            "d",
+            "M" + lStart.x + "," + lStart.y + " " +
+              "L" + lEnd.x + "," + lEnd.y
+          );
+          vLines.push(path);
+        });
+      }
+      let arrowCanvas =
+        document.createElementNS("http://www.w3.org/2000/svg", "svg");
+      arrowCanvas.id = "arrowCanvas";
+      arrowCanvas.setAttribute("stroke", "none");
+      let defs =
+        document.createElementNS("http://www.w3.org/2000/svg", "defs");
+      const arrowWidth = squareWidth / 4;
+      let marker =
+        document.createElementNS("http://www.w3.org/2000/svg", "marker");
+      marker.id = "arrow";
+      marker.setAttribute("markerWidth", (2 * arrowWidth) + "px");
+      marker.setAttribute("markerHeight", (3 * arrowWidth) + "px");
+      marker.setAttribute("markerUnits", "userSpaceOnUse");
+      marker.setAttribute("refX", "0");
+      marker.setAttribute("refY", (1.5 * arrowWidth) + "px");
+      marker.setAttribute("orient", "auto");
+      let head =
+        document.createElementNS("http://www.w3.org/2000/svg", "path");
+      head.classList.add("arrow-head");
+      head.setAttribute(
+        "d",
+        "M0,0 L0," + (3 * arrowWidth) + " L" +
+          (2 * arrowWidth) + "," + (1.5 * arrowWidth) + " z"
+      );
+      marker.appendChild(head);
+      defs.appendChild(marker);
+      arrowCanvas.appendChild(defs);
+      svgArrows.concat(vLines).forEach(av => arrowCanvas.appendChild(av));
+      document.getElementById("rootBoardElement").appendChild(arrowCanvas);
+    },
     mousedown: function(e) {
       e.preventDefault();
       if (!this.mobileBrowser && e.which != 3)
         // Cancel current drawing and circles, if any
         this.cancelResetArrows();
+      this.containerPos =
+        document.getElementById("boardContainer").getBoundingClientRect();
       if (this.mobileBrowser || e.which == 1) {
         // Mouse left button
         if (!this.start) {
@@ -579,24 +641,34 @@ export default {
         let elem = e.target;
         // Next loop because of potential marks
         while (elem.tagName == "IMG") elem = elem.parentNode;
-        // To center the arrow in square:
-        const rect = elem.getBoundingClientRect();
-        this.startArrow = {
-          x: rect.x + rect.width / 2,
-          y: rect.y + rect.width / 2,
-          id: elem.id
-        };
+        this.startArrow = getSquareFromId(elem.id);
       }
     },
     mousemove: function(e) {
       if (!this.selectedPiece && !this.startArrow) return;
+      // Cancel if off boardContainer
+      const [offsetX, offsetY] =
+        this.mobileBrowser
+          ?
+            [
+              e.changedTouches[0].pageX,
+              // TODO: fixing attempt for smartphones, removing window.scrollY
+              e.changedTouches[0].pageY - window.scrollY
+            ]
+          : [e.clientX, e.clientY];
+      if (
+        offsetX < this.containerPos.left ||
+        offsetX > this.containerPos.right ||
+        offsetY < this.containerPos.top ||
+        offsetY > this.containerPos.bottom
+      ) {
+        this.selectedPiece = null;
+        this.startArrow = null;
+        return;
+      }
       e.preventDefault();
       if (!!this.selectedPiece) {
         // There is an active element: move it around
-        const [offsetX, offsetY] =
-          this.mobileBrowser
-            ? [e.changedTouches[0].pageX, e.changedTouches[0].pageY]
-            : [e.clientX, e.clientY];
         Object.assign(
           this.selectedPiece.style,
           {
@@ -610,12 +682,13 @@ export default {
         // Next loop because of potential marks
         while (elem.tagName == "IMG") elem = elem.parentNode;
         // To center the arrow in square:
-        if (elem.id != this.startArrow.id) {
-          const rect = elem.getBoundingClientRect();
-          this.movingArrow = {
-            x: rect.x + rect.width / 2,
-            y: rect.y + rect.width / 2
-          };
+        const movingCoords = getSquareFromId(elem.id);
+        if (
+          movingCoords[0] != this.startArrow[0] ||
+          movingCoords[1] != this.startArrow[1]
+        ) {
+          this.movingArrow = movingCoords;
+          this.drawCurrentArrow();
         }
       }
     },
@@ -630,7 +703,7 @@ export default {
         this.processMoveAttempt(e);
       } else if (e.which == 3) {
         // Mouse right button
-        this.movingArrow = { x: -1, y: -1 };
+        this.movingArrow = null;
         this.processArrowAttempt(e);
       }
     },
@@ -645,7 +718,11 @@ export default {
       // Obtain the move from start and end squares
       const [offsetX, offsetY] =
         this.mobileBrowser
-          ? [e.changedTouches[0].pageX, e.changedTouches[0].pageY]
+          ?
+            [
+              e.changedTouches[0].pageX,
+              e.changedTouches[0].pageY - window.scrollY
+            ]
           : [e.clientX, e.clientY];
       let landing = document.elementFromPoint(offsetX, offsetY);
       // Next condition: classList.contains(piece) fails because of marks
@@ -676,21 +753,21 @@ export default {
       let landing = document.elementFromPoint(offsetX, offsetY);
       // Next condition: classList.contains(piece) fails because of marks
       while (landing.tagName == "IMG") landing = landing.parentNode;
-      if (this.startArrow.id == landing.id)
+      const landingCoords = getSquareFromId(landing.id);
+      if (
+        this.startArrow[0] == landingCoords[0] &&
+        this.startArrow[1] == landingCoords[1]
+      ) {
         // Draw (or erase) a circle
         this.$set(this.circles, landing.id, !this.circles[landing.id]);
+      }
       else {
         // OK: add arrow, landing is a new square
-        const rect = landing.getBoundingClientRect();
-        this.arrows.push({
-          start: {
-            x: this.startArrow.x,
-            y: this.startArrow.y
-          },
-          end: {
-            x: rect.x + rect.width / 2,
-            y: rect.y + rect.width / 2
-          }
+        const currentArrow = document.getElementById("currentArrow");
+        currentArrow.parentNode.removeChild(currentArrow);
+        this.addArrow({
+          start: this.startArrow,
+          end: landingCoords
         });
       }
       this.startArrow = null;
@@ -710,6 +787,29 @@ export default {
 };
 </script>
 
+<style lang="sass">
+// SVG dynamically added, so not scoped
+#arrowCanvas
+  pointer-events: none
+  position: absolute
+  top: 0
+  left: 0
+  width: 100%
+  height: 100%
+
+.svg-arrow
+  opacity: 0.65
+  stroke: #5f0e78
+  fill: none
+  marker-end: url(#arrow)
+
+.svg-line
+  stroke: black
+
+.arrow-head
+  fill: #5f0e78
+</style>
+
 <style lang="sass" scoped>
 @import "@/styles/_board_squares_img.sass";
 
@@ -753,23 +853,6 @@ img.ghost
   opacity: 0.5
   top: 0
 
-#arrowCanvas
-  pointer-events: none
-  position: absolute
-  top: 0
-  left: 0
-  width: 100%
-  height: 100%
-
-.svg-arrow
-  opacity: 0.65
-  stroke: #5f0e78
-  fill: none
-  marker-end: url(#arrow)
-
-.arrow-head
-  fill: #5f0e78
-
 .incheck-light
   background-color: rgba(204, 51, 0, 0.7) !important
 .incheck-dark
diff --git a/client/src/components/MoveList.vue b/client/src/components/MoveList.vue
index 1f94d638..9ec968f8 100644
--- a/client/src/components/MoveList.vue
+++ b/client/src/components/MoveList.vue
@@ -93,15 +93,10 @@ export default {
     gameContainer.style.width = boardSize + movesWidth + "px";
     document.getElementById("boardSize").value =
       (boardSize * 100) / (window.innerWidth - movesWidth);
-    // timeout to avoid calling too many time the adjust method
-    let timeoutLaunched = false;
-    window.addEventListener("resize", () => {
-      if (!timeoutLaunched) {
-        timeoutLaunched = true;
-        this.adjustBoard();
-        setTimeout(() => { timeoutLaunched = false; }, 500);
-      }
-    });
+    window.addEventListener("resize", this.adjustBoard);
+  },
+  beforeDestroy: function() {
+    window.removeEventListener("resize", this.adjustBoard);
   },
   watch: {
     cursor: function(newCursor) {
@@ -149,14 +144,12 @@ export default {
       return { tooltip: !("ontouchstart" in window) };
     },
     gotoMove: function(index) {
-      this.$emit("goto-move", index);
+      // Goto move except if click on current move:
+      if (this.cursor != index) this.$emit("goto-move", index);
     },
     adjustBoard: function() {
       const boardContainer = document.getElementById("boardContainer");
       if (!boardContainer) return; //no board on page
-      let arrows = document.getElementById("arrowCanvas");
-      // TODO: arrows on board don't scale
-      if (!!arrows) this.$emit("reset-arrows");
       const k = document.getElementById("boardSize").value;
       const movesWidth = window.innerWidth >= 768 ? 280 : 0;
       const minBoardWidth = 240; //TODO: these 240 and 280 are arbitrary...
@@ -168,6 +161,7 @@ export default {
       boardContainer.style.width = boardSize + "px";
       document.getElementById("gameContainer").style.width =
         boardSize + movesWidth + "px";
+      this.$emit("redraw-board");
     }
   }
 };
diff --git a/client/src/styles/_rules.sass b/client/src/styles/_rules.sass
index 40397a8d..2f765184 100644
--- a/client/src/styles/_rules.sass
+++ b/client/src/styles/_rules.sass
@@ -6,24 +6,24 @@ figure.diagram-container
   .diagram
     display: block
     width: 50%
-    min-width: 240px
     margin-left: auto
     margin-right: auto
+    @media screen and (max-width: 630px)
+      width: 75%
   .diag12
     float: left
-    width: 40%
-    margin-left: calc(10% - 20px)
-    margin-right: 40px
+    width: 42%
+    margin: 0 2% 0 6%
     @media screen and (max-width: 630px)
-      float: none
-      margin: 0 auto 10px auto
+      width: 49%
+      margin: 0 1% 0 0
   .diag22
     float: left
-    width: 40%
-    margin-right: calc(10% - 20px)
+    width: 42%
+    margin: 0 6% 0 2%
     @media screen and (max-width: 630px)
-      float: none
-      margin: 0 auto
+      width: 49%
+      margin: 0 0 0 1%
   figcaption
     display: block
     clear: both
diff --git a/client/src/translations/en.js b/client/src/translations/en.js
index 375bb4d4..938e96b0 100644
--- a/client/src/translations/en.js
+++ b/client/src/translations/en.js
@@ -162,6 +162,7 @@ export const translations = {
   "64 pieces on the board": "64 pieces on the board",
   "A pawns cloud": "A pawns cloud",
   "A wizard in the corner": "A wizard in the corner",
+  "Absorb powers": "Absorb powers",
   "All of the same color": "All of the same color",
   "Ancient rules": "Ancient rules",
   "Attract opposite king": "Attract opposite king",
@@ -187,7 +188,9 @@ export const translations = {
   "Interweaved colorbound teams": "Interweaved colorbound teams",
   "Get strong at self-mate": "Get strong at self-mate",
   "Give three checks": "Give three checks",
+  "Harassed kings": "Harassed kings",
   "Japanese Chess": "Japanese Chess",
+  "Jump the borders": "Jump the borders",
   "Keep antiking in check (v1)": "Keep antiking in check (v1)",
   "Keep antiking in check (v2)": "Keep antiking in check (v2)",
   "King of the Hill": "King of the Hill",
@@ -196,6 +199,7 @@ export const translations = {
   "Landing on the board": "Landing on the board",
   "Laws of attraction": "Laws of attraction",
   "Long jumps over pieces": "Long jumps over pieces",
+  "Long live the Queen": "Long live the Queen",
   "Lose all pieces": "Lose all pieces",
   "Mandatory captures": "Mandatory captures",
   "Mate any piece (v1)": "Mate any piece (v1)",
@@ -209,6 +213,7 @@ export const translations = {
   "No-check mode": "No-check mode",
   "Non-conformism and utopia": "Non-conformism and utopia",
   "Occupy the enemy palace": "Occupy the enemy palace",
+  "Paralyzed pieces": "Paralyzed pieces",
   "Pawns move diagonally": "Pawns move diagonally",
   "Play at the same time": "Play at the same time",
   "Play opponent's pieces": "Play opponent's pieces",
@@ -216,6 +221,7 @@ export const translations = {
   "Prolongated captures": "Prolongated captures",
   "Push and pull": "Push and pull",
   "Queen disguised as a pawn": "Queen disguised as a pawn",
+  "Reposition pieces": "Reposition pieces",
   "Reuse pieces": "Reuse pieces",
   "Reverse captures": "Reverse captures",
   "Rotating board": "Rotating board",
diff --git a/client/src/translations/es.js b/client/src/translations/es.js
index 932bc2b5..75dbf59b 100644
--- a/client/src/translations/es.js
+++ b/client/src/translations/es.js
@@ -162,6 +162,7 @@ export const translations = {
   "64 pieces on the board": "64 piezas en el tablero",
   "A pawns cloud": "Une nube de peones",
   "A wizard in the corner": "Un mago en la esquina",
+  "Absorb powers": "Absorber poderes",
   "All of the same color": "Todo el mismo color",
   "Ancient rules": "Viejas reglas",
   "Attract opposite king": "Atraer al rey contrario",
@@ -187,7 +188,9 @@ export const translations = {
   "Interweaved colorbound teams": "Equipos unicolores entrelazados",
   "Get strong at self-mate": "Progreso en mates asistidos",
   "Give three checks": "Dar tres jaques",
+  "Harassed kings": "Reyes acosados",
   "Japanese Chess": "Ajedrez japonés",
+  "Jump the borders": "Saltar las fronteras",
   "Keep antiking in check (v1)": "Mantener el antirey en jaque (v1)",
   "Keep antiking in check (v2)": "Mantener el antirey en jaque (v2)",
   "King of the Hill": "Rey de la Colina",
@@ -196,6 +199,7 @@ export const translations = {
   "Landing on the board": "Aterrizando en el tablero",
   "Laws of attraction": "Las leyes de las atracciones",
   "Long jumps over pieces": "Saltos largos sobre las piezas",
+  "Long live the Queen": "Larga vida a la reina",
   "Lose all pieces": "Perder todas las piezas",
   "Mandatory captures": "Capturas obligatorias",
   "Mate any piece (v1)": "Matar cualquier pieza (v1)",
@@ -209,6 +213,7 @@ export const translations = {
   "No-check mode": "Modo sin jaque",
   "Non-conformism and utopia": "No-conformismo y utopía",
   "Occupy the enemy palace": "Ocupar el palacio enemigo",
+  "Paralyzed pieces": "Piezas paralizadas",
   "Pawns move diagonally": "Peones se mueven en diagonal",
   "Play at the same time": "Jugar al mismo tiempo",
   "Play opponent's pieces": "Jugar piezas opuestas",
@@ -216,6 +221,7 @@ export const translations = {
   "Prolongated captures": "Capturas extendidas",
   "Push and pull": "Empujar y tirar",
   "Queen disguised as a pawn": "Reina disfrazada de peón",
+  "Reposition pieces": "Reposicionar las piezas",
   "Reuse pieces": "Reutilizar piezas",
   "Reverse captures": "Capturas invertidas",
   "Rotating board": "Tablero giratorio",
diff --git a/client/src/translations/fr.js b/client/src/translations/fr.js
index 9243c987..e6e8aa6c 100644
--- a/client/src/translations/fr.js
+++ b/client/src/translations/fr.js
@@ -162,6 +162,7 @@ export const translations = {
   "64 pieces on the board": "64 pièces sur l'échiquier",
   "A pawns cloud": "Une nuée de pions",
   "A wizard in the corner": "Un sorcier dans le coin",
+  "Absorb powers": "Absorber les pouvoirs",
   "All of the same color": "Tout de la même couleur",
   "Ancient rules": "Règles anciennes",
   "Attract opposite king": "Attirer le roi adverse",
@@ -187,7 +188,9 @@ export const translations = {
   "Interweaved colorbound teams": "Équipes unicolores entremêlées",
   "Get strong at self-mate": "Progressez en mats aidés",
   "Give three checks": "Donnez trois échecs",
+  "Harassed kings": "Rois harcelés",
   "Japanese Chess": "Échecs japonais",
+  "Jump the borders": "Sauter les frontières",
   "Keep antiking in check (v1)": "Gardez l'antiroi en échec (v1)",
   "Keep antiking in check (v2)": "Gardez l'antiroi en échec (v2)",
   "King of the Hill": "Roi de la Colline",
@@ -196,6 +199,7 @@ export const translations = {
   "Landing on the board": "Débarquement sur l'échiquier",
   "Laws of attraction": "Les lois de l'attraction",
   "Long jumps over pieces": "Sauts longs par dessus les pièces",
+  "Long live the Queen": "Long vie à la Reine",
   "Lose all pieces": "Perdez toutes les pièces",
   "Mandatory captures": "Captures obligatoires",
   "Mate any piece (v1)": "Matez n'importe quelle pièce (v1)",
@@ -209,6 +213,7 @@ export const translations = {
   "No-check mode": "Mode sans échec",
   "Non-conformism and utopia": "Non-conformisme et utopie",
   "Occupy the enemy palace": "Occuper le palais ennemi",
+  "Paralyzed pieces": "Pièces paralysées",
   "Pawns move diagonally": "Les pions vont en diagonale",
   "Play at the same time": "Jouer en même temps",
   "Play opponent's pieces": "Jouez les pièces adverses",
@@ -216,6 +221,7 @@ export const translations = {
   "Prolongated captures": "Captures prolongées",
   "Push and pull": "Pousser et tirer",
   "Queen disguised as a pawn": "Reine déguisée en pion",
+  "Reposition pieces": "Replacer les pièces",
   "Reuse pieces": "Réutiliser les pièces",
   "Reverse captures": "Captures inversées",
   "Rotating board": "Échiquier tournant",
diff --git a/client/src/translations/rules/Absorption/en.pug b/client/src/translations/rules/Absorption/en.pug
new file mode 100644
index 00000000..7e8da9b4
--- /dev/null
+++ b/client/src/translations/rules/Absorption/en.pug
@@ -0,0 +1,25 @@
+p.boxed.
+  Capturing pieces absorb the movement abilities of the captured pieces,
+  except for pawns and kings.
+
+p.
+  When one non-royal piece (not a king or a pawn) captures another piece
+  (not a pawn), it acquires the movement abilities of the captured piece.
+  A pawn capturing a piece becomes a piece of that kind.
+
+p.
+  Three new pieces are likely to appear during the game: from left to right
+  the Princess, Empress and Amazon, adding the knight movement abilities
+  to the bishop, rook and queen.
+
+figure.showPieces.text-center
+  img(src="/images/pieces/Absorption/ws.svg")
+  img(src="/images/pieces/Absorption/we.svg")
+  img(src="/images/pieces/Absorption/wa.svg")
+
+h3 Source
+
+p
+  a(href="https://www.chessvariants.com/play/erf/Absorber.html")
+    | Absorption Chess
+  | &nbsp;on chessvariants.com.
diff --git a/client/src/translations/rules/Absorption/es.pug b/client/src/translations/rules/Absorption/es.pug
new file mode 100644
index 00000000..d44ff3cc
--- /dev/null
+++ b/client/src/translations/rules/Absorption/es.pug
@@ -0,0 +1,26 @@
+p.boxed.
+  Las piezas que capturan absorben las capacidades de movimiento de las piezas
+  capturadas, a excepción de los peones y reyes.
+
+p.
+  Cuando una pieza no real (no un rey o un peón) captura une otra pieza
+  (no un peón), adquiere la capacidad de mover como la pieza capturada.
+  Un peón que captura una pieza se convierte en una pieza de este tipo.
+
+p.
+  Es probable que aparezcan tres piezas nuevas durante el juego:
+  de izquierda a derecha la Princesa, la Emperatriz y la Amazona agregando
+  la capacidades de movimiento del caballo al alfil, torre y dama.
+
+figure.showPieces.text-center
+  img(src="/images/pieces/Absorption/ws.svg")
+  img(src="/images/pieces/Absorption/we.svg")
+  img(src="/images/pieces/Absorption/wa.svg")
+
+h3 Fuente
+
+p
+  | La 
+  a(href="https://www.chessvariants.com/play/erf/Absorber.html")
+    | variante Absorción
+  | &nbsp;en chessvariants.com.
diff --git a/client/src/translations/rules/Absorption/fr.pug b/client/src/translations/rules/Absorption/fr.pug
new file mode 100644
index 00000000..5cef3416
--- /dev/null
+++ b/client/src/translations/rules/Absorption/fr.pug
@@ -0,0 +1,26 @@
+p.boxed.
+  Les pièces capturantes absorbent les capacités de mouvement des pièces
+  capturées, sauf concernant les pions et les rois.
+
+p.
+  Quand une pièce non royale (pas un roi ou un pion) capture une autre pièce
+  (pas un pion), elle acquière les capacités de déplacement de la pièce
+  capturée. Un pion capturant une pièce devient une pièce de ce type.
+
+p.
+  Trois nouvelles pièces sont susceptibles d'apparaître au cours du jeu : de
+  gauche à droite la Princesse, l'Impératrice et l'Amazone, ajoutant les
+  capacités de déplacement du cavalier au fou, à la tour et à la dame.
+
+figure.showPieces.text-center
+  img(src="/images/pieces/Absorption/ws.svg")
+  img(src="/images/pieces/Absorption/we.svg")
+  img(src="/images/pieces/Absorption/wa.svg")
+
+h3 Source
+
+p
+  | La 
+  a(href="https://www.chessvariants.com/play/erf/Absorber.html")
+    | variante Absorption
+  | &nbsp;sur chessvariants.com.
diff --git a/client/src/translations/rules/Bicolour/en.pug b/client/src/translations/rules/Bicolour/en.pug
new file mode 100644
index 00000000..30c63bf4
--- /dev/null
+++ b/client/src/translations/rules/Bicolour/en.pug
@@ -0,0 +1,30 @@
+p.boxed.
+  Kings are subject to check and checkmate by own as well as opponent's pieces.
+
+p
+  Kings are attacked by all pieces, and can capture any piece.
+  For example, on the following diagram the king can escape check by
+  capturing a rook.
+
+figure.diagram-container
+  .diagram
+    | fen:8/8/8/8/8/4q4/8/3RKR2:
+  figcaption Not a checkmate: Kxf1 or Kxd1.
+
+p.
+  Here however, each capture of the queen would lead to a self-check: although
+  the queen appears vulnerable this is a checkmate.
+
+figure.diagram-container
+  .diagram
+    | fen:1r1nkbr1/1p2Qnp1/p2p1p1p/2p4P/P1P1P3/1P6/R2P1KP1/2B2BNR:
+  figcaption Checkmate.
+
+h3 Source
+
+p
+  a(href="https://www.jsbeasley.co.uk/encyc.htm")
+    | The Classified Encyclopedia of Chess Variants.
+  | &nbsp;by David B. Pritchard (2007).
+
+p Inventors: Gabriel Authier and Roméo Bédoni (1958)
diff --git a/client/src/translations/rules/Bicolour/es.pug b/client/src/translations/rules/Bicolour/es.pug
new file mode 100644
index 00000000..25abe0b4
--- /dev/null
+++ b/client/src/translations/rules/Bicolour/es.pug
@@ -0,0 +1,31 @@
+p.boxed.
+  Los reyes pueden estar en jaque y jaque mate por tus piezas o las de
+  el oponente.
+
+p.
+  Los reyes son atacados por todas las piezas y pueden capturar cualquier
+  pieza. Por ejemplo, en el siguiente diagrama, el rey puede evadirse
+  del jaque capturando una torre.
+
+figure.diagram-container
+  .diagram
+    | fen:8/8/8/8/8/4q4/8/3RKR2:
+  figcaption No es un jaque mate: Kxf1 o Kxd1.
+
+p.
+  Aquí, sin embargo, cada captura de la dama llevaría a un auto-jaque: aunque
+  la dama parece vulnerable, es un jaque mate.
+
+figure.diagram-container
+  .diagram
+    | fen:1r1nkbr1/1p2Qnp1/p2p1p1p/2p4P/P1P1P3/1P6/R2P1KP1/2B2BNR:
+  figcaption jaque mate.
+
+h3 Fuente
+
+p
+  a(href="https://www.jsbeasley.co.uk/encyc.htm")
+    | The Classified Encyclopedia of Chess Variants.
+  | &nbsp;por David B. Pritchard (2007).
+
+p Inventores: Gabriel Authier y Roméo Bédoni (1958)
diff --git a/client/src/translations/rules/Bicolour/fr.pug b/client/src/translations/rules/Bicolour/fr.pug
new file mode 100644
index 00000000..2bbfdef8
--- /dev/null
+++ b/client/src/translations/rules/Bicolour/fr.pug
@@ -0,0 +1,31 @@
+p.boxed.
+  Les rois peuvent être en échec et matés par vos pièces ou celles de
+  l'adversaire.
+
+p.
+  Les rois sont attaqués par toutes les pièces, et peuvent capturer n'importe
+  quelle pièce. Par exemple, sur le diagramme ci-dessous le roi peut se
+  soustraire à l'échec en capturant une tour.
+
+figure.diagram-container
+  .diagram
+    | fen:8/8/8/8/8/4q4/8/3RKR2:
+  figcaption Pas un mat : Kxf1 ou Kxd1.
+
+p.
+  Ici cependant, chaque capture de la dame mènerait à un auto-échec : bien que
+  la dame paraisse vulnérable, c'est un échec et mat.
+
+figure.diagram-container
+  .diagram
+    | fen:1r1nkbr1/1p2Qnp1/p2p1p1p/2p4P/P1P1P3/1P6/R2P1KP1/2B2BNR:
+  figcaption Échec et mat.
+
+h3 Source
+
+p
+  a(href="https://www.jsbeasley.co.uk/encyc.htm")
+    | The Classified Encyclopedia of Chess Variants.
+  | &nbsp;par David B. Pritchard (2007).
+
+p Inventeurs : Gabriel Authier et Roméo Bédoni (1958)
diff --git a/client/src/translations/rules/Coronation/en.pug b/client/src/translations/rules/Coronation/en.pug
new file mode 100644
index 00000000..9a31a359
--- /dev/null
+++ b/client/src/translations/rules/Coronation/en.pug
@@ -0,0 +1,21 @@
+p.boxed.
+  Rook and bishop can fusion to become a queen, if the queen disappeared.
+
+p.
+  If a side lacks a queen, then a special fusion move is allowed by "capturing"
+  a rook with a bishop (and conversely).
+  The result of the capture is a queen, hence the variant name.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:1R6/1pr2k2/p1p1p2p/3p3p/2nP3P/1NP3P1/PPK1P3/2R3B1:
+  .diagram.diag22
+    | fen:1R6/1pr2k2/p1p1p2p/3p3p/2nP3P/1NP3P1/PPK1P3/6Q1:
+  figcaption Left: before Rxg1=Q. Right: after the move.
+
+h3 Source
+
+p
+  | This variant is 
+  a(href="https://www.chessvariants.com/play/erf/CoronatC.html") mentioned here
+  | .
diff --git a/client/src/translations/rules/Coronation/es.pug b/client/src/translations/rules/Coronation/es.pug
new file mode 100644
index 00000000..513b36d8
--- /dev/null
+++ b/client/src/translations/rules/Coronation/es.pug
@@ -0,0 +1,23 @@
+p.boxed.
+  Torre y alfil pueden fusionar para convertirse en una reina,
+  si la dama falta.
+
+p.
+  Si un lado ha perdido a su dama, entonces puede hacer un movimiento especial
+  "capturando" una torre con un alfil (y viceversa).
+  El resultado de la captura es una reina, de ahí el nombre de la variante.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:1R6/1pr2k2/p1p1p2p/3p3p/2nP3P/1NP3P1/PPK1P3/2R3B1:
+  .diagram.diag22
+    | fen:1R6/1pr2k2/p1p1p2p/3p3p/2nP3P/1NP3P1/PPK1P3/6Q1:
+  figcaption Izquierda: antes de Rxg1=Q. Derecha: después del movimiento.
+
+h3 Fuente
+
+p
+  | Esta variante es 
+  a(href="https://www.chessvariants.com/play/erf/CoronatC.html")
+    | mencionada aquí
+  | .
diff --git a/client/src/translations/rules/Coronation/fr.pug b/client/src/translations/rules/Coronation/fr.pug
new file mode 100644
index 00000000..bff0ee54
--- /dev/null
+++ b/client/src/translations/rules/Coronation/fr.pug
@@ -0,0 +1,21 @@
+p.boxed.
+  Tour et fou peuvent fusionner pour devenir une reine, si la dame a disparu.
+
+p.
+  Si un camp a perdu sa dame, alors il peut effectuer un coup spécial en
+  "capturant" une tour avec un fou (et inversement).
+  Le résultat de la capture est une reine, d'où le nom de la variante.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:1R6/1pr2k2/p1p1p2p/3p3p/2nP3P/1NP3P1/PPK1P3/2R3B1:
+  .diagram.diag22
+    | fen:1R6/1pr2k2/p1p1p2p/3p3p/2nP3P/1NP3P1/PPK1P3/6Q1:
+  figcaption Gauche : avant Rxg1=Q. Droite : après le coup.
+
+h3 Source
+
+p
+  | Cette variante est 
+  a(href="https://www.chessvariants.com/play/erf/CoronatC.html") mentionnée ici
+  | .
diff --git a/client/src/translations/rules/Football/en.pug b/client/src/translations/rules/Football/en.pug
new file mode 100644
index 00000000..34fb52b9
--- /dev/null
+++ b/client/src/translations/rules/Football/en.pug
@@ -0,0 +1,21 @@
+p.boxed.
+  Win by playing a move on the middle of last rank.
+  The king has no royal status.
+
+p.
+  The goal for White (resp. Black) are the squares d8 and e8 (resp. d1 and
+  e1) in the middle of the opposing rank. The first player to settle a
+  piece there wins, even if it is attacked.
+
+figure.diagram-container
+  .diagram
+    | fen:2r5/p1p1b2p/1pbp4/5K2/P1PP4/1P6/4nPPP/3R1NR1 c3:
+  figcaption 1...Nc3 and 2...N(x)d1# wins for Black.
+
+h3 Source
+
+p
+  | This variant is mentioned 
+  a(href="http://abrobecker.free.fr/chess/fairyblitz.htm#football")
+    | on this page
+  | . It isn't specified if the winning piece needs to be safe from attacks.
diff --git a/client/src/translations/rules/Football/es.pug b/client/src/translations/rules/Football/es.pug
new file mode 100644
index 00000000..2ca6e47a
--- /dev/null
+++ b/client/src/translations/rules/Football/es.pug
@@ -0,0 +1,21 @@
+p.boxed.
+  Gana haciendo un movimiento en el centro de la última fila.
+  El rey no tiene estatus real.
+
+p.
+  El objetivo de las blancas (resp. negras) son las casillas d8 y e8
+  (resp. d1 y e1) en el medio de la fila opuesta.
+  El primer jugador que instala una pieza ahí gana, incluso si es atacada.
+
+figure.diagram-container
+  .diagram
+    | fen:2r5/p1p1b2p/1pbp4/5K2/P1PP4/1P6/4nPPP/3R1NR1 c3:
+  figcaption 1...Nc3 y 2...N(x)d1# gana para las negras.
+
+h3 Fuente
+
+p
+  | Esta variante se menciona 
+  a(href="http://abrobecker.free.fr/chess/fairyblitz.htm#football")
+    | en esta pagina
+  | . No está claro si la pieza debe estar a salvo de los ataques.
diff --git a/client/src/translations/rules/Football/fr.pug b/client/src/translations/rules/Football/fr.pug
new file mode 100644
index 00000000..3af7b57d
--- /dev/null
+++ b/client/src/translations/rules/Football/fr.pug
@@ -0,0 +1,21 @@
+p.boxed.
+  Gagnez en jouant un coup au centre de la dernière rangée.
+  Le roi n'a pas de statut royal.
+
+p.
+  Le but des blancs (resp. des noirs) sont les cases d8 et e8 (resp. d1 et e1)
+  au milieu de la rangée opposée. Le premier joueur à y installer une pièce
+  gagne, même si celle-ci est attaquée.
+
+figure.diagram-container
+  .diagram
+    | fen:2r5/p1p1b2p/1pbp4/5K2/P1PP4/1P6/4nPPP/3R1NR1 c3:
+  figcaption 1...Nc3 et 2...N(x)d1# gagne pour les noirs.
+
+h3 Source
+
+p
+  | Cette variante est mentionnée 
+  a(href="http://abrobecker.free.fr/chess/fairyblitz.htm#football")
+    | sur cette page
+  | . Il n'est pas précisé si la pièce doit être à l'abri des attaques.
diff --git a/client/src/translations/rules/Gridolina/en.pug b/client/src/translations/rules/Gridolina/en.pug
new file mode 100644
index 00000000..95a6a15b
--- /dev/null
+++ b/client/src/translations/rules/Gridolina/en.pug
@@ -0,0 +1,28 @@
+p.boxed.
+  The board is divided into 16 square cells of size 2x2.
+  A piece must change cell at each move.
+
+p.
+  The board is decomposed into 16 little 2x2 squares, starting from
+  a1,b1,a2,b2 until g7,h7,g8,h8. When a piece moves, it must cross at least
+  one boundary: it cannot remain in the same cell.
+  So, pieces in the same cell don't attack each other.
+
+figure.diagram-container
+  .diagram
+    | fen:1r1kr3/3nbbQ1/6p1/4p3/1qp1p3/PR1P1NPP/4nP2/2B1KR1B:
+  figcaption.
+    The white king under check cannot take the knight,
+    and Rxb4 is not possible.
+
+p.
+  Pawns are Berolina pawns: they move one square diagonally forward and
+  capture one square orthogonally forward.
+
+h3 Source
+
+p
+  | This variant is mentioned in 
+  a(href="https://www.jsbeasley.co.uk/encyc.htm")
+    | The Classified Encyclopedia of Chess Variants
+  | .
diff --git a/client/src/translations/rules/Gridolina/es.pug b/client/src/translations/rules/Gridolina/es.pug
new file mode 100644
index 00000000..6e5d59ca
--- /dev/null
+++ b/client/src/translations/rules/Gridolina/es.pug
@@ -0,0 +1,28 @@
+p.boxed.
+  El tablero de ajedrez se divide en 16 celdas cuadradas de tamaño 2x2.
+  Una pieza debe cambiar de celda cada jugada.
+
+p.
+  El tablero se divide en 16 cuadrados pequeños de 2x2, comenzando en
+  a1,b1,a2,b2 para terminar en g7,h7,g8,h8. Cuando una pieza se mueve, debe
+  cruzar al menos un borde: no puede permanecer en la misma celda.
+  Por lo tanto, las piezas de la misma célula no se atacan.
+
+figure.diagram-container
+  .diagram
+    | fen:1r1kr3/3nbbQ1/6p1/4p3/1qp1p3/PR1P1NPP/4nP2/2B1KR1B:
+  figcaption.
+    El rey blanco en jaque no puede tomar al caballo,
+    y Rxb4 no es posible.
+
+p.
+  Los peones son peones Berolina: se mueven un espacio
+  diagonalmente hacia adelante y capturar en la casilla frente a ellos.
+
+h3 Fuente
+
+p
+  | Esta variante se menciona en 
+  a(href="https://www.jsbeasley.co.uk/encyc.htm")
+    | The Classified Encyclopedia of Chess Variants
+  | .
diff --git a/client/src/translations/rules/Gridolina/fr.pug b/client/src/translations/rules/Gridolina/fr.pug
new file mode 100644
index 00000000..a48033f1
--- /dev/null
+++ b/client/src/translations/rules/Gridolina/fr.pug
@@ -0,0 +1,28 @@
+p.boxed.
+  L'échiquier est divisé en 16 cellules carrées de taille 2x2.
+  Une pièce doit changer de cellule à chaque coup.
+
+p.
+  L'échiquier est décomposé en 16 petits carrés 2x2, démarrant en a1,b1,a2,b2
+  pour finir en g7,h7,g8,h8. Quand une pièce se déplace, elle doit traverser
+  au moins une frontière : elle ne peut rester dans la même cellule.
+  Ainsi, les pièces d'une même cellule ne s'attaquent pas.
+
+figure.diagram-container
+  .diagram
+    | fen:1r1kr3/3nbbQ1/6p1/4p3/1qp1p3/PR1P1NPP/4nP2/2B1KR1B:
+  figcaption.
+    Le roi blanc en échec ne peut pas prendre le cavalier,
+    et Rxb4 n'est pas possible.
+
+p.
+  Les pions sont des pions Berolina : ils se déplacent d'une case en
+  diagonale vers l'avant et capturent sur la case devant eux.
+
+h3 Source
+
+p
+  | Cette variante est mentionnée dans 
+  a(href="https://www.jsbeasley.co.uk/encyc.htm")
+    | The Classified Encyclopedia of Chess Variants
+  | .
diff --git a/client/src/translations/rules/Koth/en.pug b/client/src/translations/rules/Koth/en.pug
index 422e8faf..02e7e890 100644
--- a/client/src/translations/rules/Koth/en.pug
+++ b/client/src/translations/rules/Koth/en.pug
@@ -1,12 +1,10 @@
 p.boxed
-  | Bring the king to the middle to win. Giving check is forbidden.
+  | Bring the king to the middle to win. Checkmate still wins too.
 
-p Orthodox rules apply, with two changes:
-ul
-  li.
-    The goal is to bring the king on one of the central squares
-    d4, e4, d5 or e5. "Koth" stands indeed for "King of the Hill".
-  li Giving check is forbidden (thus no checkmate is possible).
+p.
+  Orthodox rules apply, with only one change: the game can also be won
+  by playing the king on one of the central squares d4, e4, d5 or e5.
+  "Koth" stands indeed for "King of the Hill".
 
 figure.diagram-container
   .diagram
@@ -16,6 +14,5 @@ figure.diagram-container
 h3 Source
 
 p
-  | Modified from Koth as playable 
-  a(href="https://lichess.org/variant/kingOfTheHill") on lichess
-  | . Experimental change: I'd like to see how it plays without checks :)
+  a(href="https://lichess.org/variant/kingOfTheHill") King of the Hill
+  | &nbsp;on lichess.org.
diff --git a/client/src/translations/rules/Koth/es.pug b/client/src/translations/rules/Koth/es.pug
index 5e84f938..2fe1566b 100644
--- a/client/src/translations/rules/Koth/es.pug
+++ b/client/src/translations/rules/Koth/es.pug
@@ -1,12 +1,10 @@
 p.boxed
-  | Lleva el rey al centro para ganar. Prohibido de dar jaque.
+  | Lleva el rey al centro para ganar. El jaque mate también gana.
 
-p Se aplican las reglas ortodoxas, con dos cambios:
-ul
-  li.
-    El objetivo es llevar el rey a uno de los espacios centrales d4, e4, d5
-    o e5. "Koth" es de hecho la abreviatura inglesa para "Rey de la Colina".
-  li Los jaques son prohibidos (por lo tanto, el jaque mate es imposible).
+p.
+  Se aplican las reglas ortodoxas, con un solo cambio: puedes ganar
+  jugando el rey en uno de los espacios centrales d4, e4, d5 o e5.
+  "Koth" es de hecho la abreviatura inglesa para "Rey de la Colina".
 
 figure.diagram-container
   .diagram
@@ -16,7 +14,6 @@ figure.diagram-container
 h3 Fuente
 
 p
-  | Cambiado de la variante Koth jugable 
-  a(href="https://lichess.org/variant/kingOfTheHill") en lichess
-  | . Cambio experimental: me gustaría ver los efectos de la condición que
-  | previene jaques :)
+  | La 
+  a(href="https://lichess.org/variant/kingOfTheHill") variante KotH
+  | &nbsp;en lichess.org.
diff --git a/client/src/translations/rules/Koth/fr.pug b/client/src/translations/rules/Koth/fr.pug
index 79c0272b..ddc73ca8 100644
--- a/client/src/translations/rules/Koth/fr.pug
+++ b/client/src/translations/rules/Koth/fr.pug
@@ -1,12 +1,10 @@
 p.boxed
-  | Amenez le roi au centre pour gagner. Interdit de donner échec.
+  | Amenez le roi au centre pour gagner. L'échec et mat gagne aussi.
 
-p Les règles orthodoxes s'appliquent, avec deux changements :
-ul
-  li.
-    L'objectif est d'amener le roi sur l'une des cases centrales d4, e4, d5
-    ou e5. "Koth" est en effet l'abbréviation anglaise de "Roi de la Colline".
-  li Les échecs sont interdits (l'échec et mat est donc imppossible).
+p.
+  Les règles orthodoxes s'appliquent, avec un seul changement : on peut
+  gagner en jouant le roi sur l'une des cases centrales d4, e4, d5 ou e5.
+  "Koth" est en effet l'abbréviation anglaise de "Roi de la Colline".
 
 figure.diagram-container
   .diagram
@@ -16,7 +14,6 @@ figure.diagram-container
 h3 Source
 
 p
-  | Modifié depuis la variante Koth jouable 
-  a(href="https://lichess.org/variant/kingOfTheHill") sur lichess
-  | . Changement expérimental : j'aimerais voir les effets de la condition
-  | empêchant les échecs :)
+  | La 
+  a(href="https://lichess.org/variant/kingOfTheHill") variante KotH
+  | &nbsp;sur lichess.org.
diff --git a/client/src/translations/rules/Madrasi/en.pug b/client/src/translations/rules/Madrasi/en.pug
new file mode 100644
index 00000000..6de3c00c
--- /dev/null
+++ b/client/src/translations/rules/Madrasi/en.pug
@@ -0,0 +1,28 @@
+p.boxed.
+  Pieces of the same kind mutually attacked are immobilized.
+
+p.
+  Two pieces of the same kind which both attack the other are paralysed.
+  They cannot move and they don't give check.
+  This rule also applies to kings, which would then stay in the same position
+  until the end of the game.
+
+figure.diagram-container
+  .diagram
+    | fen:bN2kq1r/p1pp4/2n2Qpr/4RpNp/1pPb1P1P/P2K2P1/1P1R1B2/7B:
+  figcaption e5 rook is safe and give check. 1...Kd8 is possible.
+
+p.
+  The diagram look like a checkmate because both black knight and bishop are
+  immobilized, but since the queens are paralysed the king can escape to d8.
+
+h3 Source
+
+p
+  | This variant appears on a number of web pages, like 
+  a(href="https://echekk.fr/spip.php?page=article&id_article=1") this one
+  | &nbsp;or the 
+  a(href="https://en.wikipedia.org/wiki/Madrasi_chess") Wikipedia page
+  | .
+
+p Inventor: Abdul J. Karwathar (1979)
diff --git a/client/src/translations/rules/Madrasi/es.pug b/client/src/translations/rules/Madrasi/es.pug
new file mode 100644
index 00000000..2b405b67
--- /dev/null
+++ b/client/src/translations/rules/Madrasi/es.pug
@@ -0,0 +1,28 @@
+p.boxed.
+  Piezas similares que se atacan mutuamente están inmovilizadas.
+
+p.
+  Dos piezas del mismo tipo que se observan se paralizan mutuamente.
+  No pueden moverse y no dan jaque.
+  Esta regla también se aplica a los reyes, que luego permanecerían en el
+  misma posición hasta el final del juego.
+
+figure.diagram-container
+  .diagram
+    | fen:bN2kq1r/p1pp4/2n2Qpr/4RpNp/1pPb1P1P/P2K2P1/1P1R1B2/7B:
+  figcaption La torre e5 es segura y da jaque. 1...Kd8 es posible.
+
+p.
+  El diagrama parece un jaque mate porque el alfil y el caballo negros son
+  inmovilizados, pero como las damas están paralizadas el rey puede huir en d8.
+
+h3 Fuente
+
+p
+  | Esta variante aparece en muchas páginas web, como 
+  a(href="https://echekk.fr/spip.php?page=article&id_article=1") este
+  | &nbsp;o la 
+  a(href="https://en.wikipedia.org/wiki/Madrasi_chess") página Wikipedia
+  | .
+
+p Inventor: Abdul J. Karwathar (1979)
diff --git a/client/src/translations/rules/Madrasi/fr.pug b/client/src/translations/rules/Madrasi/fr.pug
new file mode 100644
index 00000000..fec36ab7
--- /dev/null
+++ b/client/src/translations/rules/Madrasi/fr.pug
@@ -0,0 +1,28 @@
+p.boxed.
+  Des pièces de même nature s'attaquant mutuellement sont immobilisées.
+
+p.
+  Deux pièces de même nature qui s'observent se paralysent mutuellement.
+  Elles ne peuvent pas se déplacer et ne donnent pas échec.
+  Cette règle s'applique aussi aux rois, qui resteraient ensuite dans la
+  même position jusqu'à la fin de la partie.
+
+figure.diagram-container
+  .diagram
+    | fen:bN2kq1r/p1pp4/2n2Qpr/4RpNp/1pPb1P1P/P2K2P1/1P1R1B2/7B:
+  figcaption La tour e5 est en scurité et donne échec. 1...Kd8 est possible.
+
+p.
+  Le diagramme ressemble à un mat car le cavalier et le fou noirs sont
+  immobilisés, mais puisque les dames sont paralysées le roi peut fuire en d8.
+
+h3 Source
+
+p
+  | Cette variante apparaît sur pas mal de pages web, comme 
+  a(href="https://echekk.fr/spip.php?page=article&id_article=1") celle-ci
+  | &nbsp;ou la 
+  a(href="https://en.wikipedia.org/wiki/Madrasi_chess") page Wikipedia
+  | .
+
+p Inventeur : Abdul J. Karwathar (1979)
diff --git a/client/src/translations/rules/Teleport/en.pug b/client/src/translations/rules/Teleport/en.pug
new file mode 100644
index 00000000..0d1442f0
--- /dev/null
+++ b/client/src/translations/rules/Teleport/en.pug
@@ -0,0 +1,35 @@
+p.boxed.
+  You can capture your pieces to teleport them instead of a regular move.
+
+p.
+  Instead of playing an orthodox move, it is possible to capture one of
+  our own pieces before repositioning it immediatly elsewhere on the board.
+  A king may remain under check while the move isn't complete.
+  A repositioned piece can give checkmate.
+  A pawn cannot be placed on the last rank.
+  A king can reposition a piece or be repositioned,
+  possibly to escape from a check.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:7k/6b1/8/4Q2N/8/8/8/K7:
+  .diagram.diag22
+    | fen:7k/6b1/5N2/7Q/8/8/8/K7:
+  figcaption Not a pin, nor a mate in 2.
+
+p.
+  1.Qxh5,N@f6+ is played from the diagram position, showing that the queen
+  isn't really pinned. Then, instead of 1...Bh6 black can either reposition
+  the bishop or the king to escape from check. 1...Kxg7,B@d4+ for example.
+
+p To reposition a piece, just click on an empty square after a self-capture.
+
+h3 Source
+
+p
+  | This variant is 
+  a(href="http://abrobecker.free.fr/chess/fairyblitz.htm#deplaceurdevivants")
+    | mentioned here
+  | .
+
+p Inventor: Philippe Rouzaud (2006)
diff --git a/client/src/translations/rules/Teleport/es.pug b/client/src/translations/rules/Teleport/es.pug
new file mode 100644
index 00000000..8b7f5a9a
--- /dev/null
+++ b/client/src/translations/rules/Teleport/es.pug
@@ -0,0 +1,38 @@
+p.boxed.
+  Puedes capturar tus piezas para teletransportarlas en lugar de jugar
+  un movimiento normal.
+
+p.
+  Un campamento puede, en lugar de un movimiento ortodoxo, capturar una de
+  sus piezas y reemplace la pieza capturada inmediatamente en el tablero.
+  Un rey puede quedarse en jaque durante esta acción.
+  Una pieza movida en el tablero puede dar jaque mate.
+  Un peón no se puede mover a la última fila.
+  Un rey puede moverse y ser movido, incluso para evitar jaques.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:7k/6b1/8/4Q2N/8/8/8/K7:
+  .diagram.diag22
+    | fen:7k/6b1/5N2/7Q/8/8/8/K7:
+  figcaption No es un clavado, ni un jaque mate en 2.
+
+p.
+  1.Qxh5,N@f6+ se juega desde la posición del diagrama, mostrando que la dama
+  no está realmente clavada. Luego, para evadir el jaque las negras
+  pueden reemplazar a su alfil o rey en lugar de jugar 1...Bd6.
+  1...Kxg7,B@d4+ por ejemplo.
+
+p.
+  Para reposicionar una pieza, simplemente haga clic en una casilla vacía
+  después de una auto-captura.
+
+h3 Fuente
+
+p
+  | Esta variante es 
+  a(href="http://abrobecker.free.fr/chess/fairyblitz.htm#deplaceurdevivants")
+    | mencionada aquí
+  | .
+
+p Inventor: Philippe Rouzaud (2006)
diff --git a/client/src/translations/rules/Teleport/fr.pug b/client/src/translations/rules/Teleport/fr.pug
new file mode 100644
index 00000000..0d191e45
--- /dev/null
+++ b/client/src/translations/rules/Teleport/fr.pug
@@ -0,0 +1,39 @@
+p.boxed.
+  Vous pouvez capturer vos pièces pour les téléporter au lieu de jouer
+  un coup normal.
+
+p.
+  Un camp peut, à la place d'un coup orthodoxe, capturer une de ses pièces et
+  replacer la pièce capturée immédiatement sur l'échiquier. Un roi peut rester
+  en échec durant cette action.
+  Une pièce déplacée sur l'échiquier peut mater.
+  Un pion ne peut pas être déplacé en dernière rangée.
+  Un roi peut déplacer et être déplacé,
+  y compris pour se soustraire à un échec.
+
+figure.diagram-container
+  .diagram.diag12
+    | fen:7k/6b1/8/4Q2N/8/8/8/K7:
+  .diagram.diag22
+    | fen:7k/6b1/5N2/7Q/8/8/8/K7:
+  figcaption Pas un clouage, ni un mat en 2.
+
+p.
+  1.Qxh5,N@f6+ est joué depuis la position du diagramme, montrant que la dame
+  n'est pas vraiment clouée. Ensuite, pour se soustraire à l'échec les noirs
+  peuvent replacer leur fou ou leur roi au lieu de jouer 1...Bh6.
+  1...Kxg7,B@d4+ par exemple.
+
+p.
+  Pour repositionner une pièce, cliquez simplement sur une case vide après
+  une auto-capture.
+
+h3 Source
+
+p
+  | Cette variante est 
+  a(href="http://abrobecker.free.fr/chess/fairyblitz.htm#deplaceurdevivants")
+    | mentionnée ici
+  | .
+
+p Inventeur : Philippe Rouzaud (2006)
diff --git a/client/src/variants/Absorption.js b/client/src/variants/Absorption.js
new file mode 100644
index 00000000..efa01036
--- /dev/null
+++ b/client/src/variants/Absorption.js
@@ -0,0 +1,134 @@
+import { ChessRules } from "@/base_rules";
+
+export class AbsorptionRules extends ChessRules {
+  getPpath(b) {
+    if ([V.BN, V.RN, V.QN].includes(b[1])) return "Absorption/" + b;
+    return b;
+  }
+
+  // Three new pieces: rook+knight, bishop+knight and queen+knight
+  static get RN() {
+    // Empress
+    return 'e';
+  }
+  static get BN() {
+    // Princess
+    return 's';
+  }
+  static get QN() {
+    // Amazon
+    return 'a';
+  }
+
+  static get PIECES() {
+    return ChessRules.PIECES.concat([V.RN, V.BN, V.QN]);
+  }
+
+  static get MergeComposed() {
+    return {
+      "be": "a",
+      "bs": "s",
+      "er": "e",
+      "rs": "a",
+      "eq": "a",
+      "qs": "a",
+      "ee": "e",
+      "es": "a",
+      "ss": "s"
+    };
+  }
+
+  static Fusion(p1, p2) {
+    if (p1 == V.KING) return p1;
+    if (p1 == V.PAWN) return p2;
+    if (p2 == V.PAWN) return p1;
+    if ([p1, p2].includes(V.KNIGHT)) {
+      if ([p1, p2].includes(V.QUEEN)) return V.QN;
+      if ([p1, p2].includes(V.ROOK)) return V.RN;
+      if ([p1, p2].includes(V.BISHOP)) return V.BN;
+      // p1 or p2 already have knight + other piece
+      return (p1 == V.KNIGHT ? p2 : p1);
+    }
+    for (let p of [p1, p2]) {
+      if (p == V.QN) return V.QN;
+      if ([V.BN, V.RN].includes(p))
+        return V.MergeComposed[[p1, p2].sort().join("")];
+    }
+    // bishop + rook, or queen + [bishop or rook]
+    return V.QUEEN;
+  }
+
+  getPotentialMovesFrom(sq) {
+    let moves = [];
+    const piece = this.getPiece(sq[0], sq[1]);
+    switch (piece) {
+      case V.RN:
+        moves =
+          super.getPotentialRookMoves(sq).concat(
+          super.getPotentialKnightMoves(sq));
+        break;
+      case V.BN:
+        moves =
+          super.getPotentialBishopMoves(sq).concat(
+          super.getPotentialKnightMoves(sq));
+        break;
+      case V.QN:
+        moves =
+          super.getPotentialQueenMoves(sq).concat(
+          super.getPotentialKnightMoves(sq));
+        break;
+      default:
+        moves = super.getPotentialMovesFrom(sq);
+    }
+    moves.forEach(m => {
+      if (m.vanish.length == 2) {
+        // Augment pieces abilities in case of captures
+        const piece2 = m.vanish[1].p;
+        if (piece != piece2) m.appear[0].p = V.Fusion(piece, piece2);
+      }
+    });
+    return moves;
+  }
+
+  isAttacked(sq, color) {
+    return (
+      super.isAttacked(sq, color) ||
+      this.isAttackedByBN(sq, color) ||
+      this.isAttackedByRN(sq, color) ||
+      this.isAttackedByQN(sq, color)
+    );
+  }
+
+  isAttackedByBN(sq, color) {
+    return (
+      this.isAttackedBySlideNJump(sq, color, V.BN, V.steps[V.BISHOP]) ||
+      this.isAttackedBySlideNJump(
+        sq, color, V.BN, V.steps[V.KNIGHT], "oneStep")
+    );
+  }
+
+  isAttackedByRN(sq, color) {
+    return (
+      this.isAttackedBySlideNJump(sq, color, V.RN, V.steps[V.ROOK]) ||
+      this.isAttackedBySlideNJump(
+        sq, color, V.RN, V.steps[V.KNIGHT], "oneStep")
+    );
+  }
+
+  isAttackedByQN(sq, color) {
+    return (
+      this.isAttackedBySlideNJump(
+        sq, color, V.QN, V.steps[V.BISHOP].concat(V.steps[V.ROOK])) ||
+      this.isAttackedBySlideNJump(
+        sq, color, V.QN, V.steps[V.KNIGHT], "oneStep")
+    );
+  }
+
+  getNotation(move) {
+    let notation = super.getNotation(move);
+    if (move.vanish[0].p != V.PAWN && move.appear[0].p != move.vanish[0].p)
+      // Fusion (not from a pawn: handled in ChessRules)
+      notation += "=" + move.appear[0].p.toUpperCase();
+    return notation;
+  }
+};
diff --git a/client/src/variants/Ball.js b/client/src/variants/Ball.js
index e4a0b2fa..ceddb61a 100644
--- a/client/src/variants/Ball.js
+++ b/client/src/variants/Ball.js
@@ -3,6 +3,21 @@ import { ArrayFun } from "@/utils/array";
 import { shuffle } from "@/utils/alea";
 
 export class BallRules extends ChessRules {
+  static get Lines() {
+    return [
+      // White goal:
+      [[0, 3], [0, 6]],
+      [[0, 6], [1, 6]],
+      [[1, 6], [1, 3]],
+      [[1, 3], [0, 3]],
+      // Black goal:
+      [[9, 3], [9, 6]],
+      [[9, 6], [8, 6]],
+      [[8, 6], [8, 3]],
+      [[8, 3], [9, 3]]
+    ];
+  }
+
   static get PawnSpecs() {
     return Object.assign(
       {},
diff --git a/client/src/variants/Bicolour.js b/client/src/variants/Bicolour.js
new file mode 100644
index 00000000..eaa66d17
--- /dev/null
+++ b/client/src/variants/Bicolour.js
@@ -0,0 +1,111 @@
+import { ChessRules } from "@/base_rules";
+import { randInt } from "@/utils/alea";
+import { ArrayFun } from "@/utils/array";
+
+export class BicolourRules extends ChessRules {
+  static get HasFlags() {
+    return false;
+  }
+
+  canTake([x1, y1], [x2, y2]) {
+    return (
+      this.getPiece(x1, y1) == V.KING || super.canTake([x1, y1], [x2, y2])
+    );
+  }
+
+  static GenRandInitFen(randomness) {
+    if (randomness == 0)
+      return "rqbnkbnr/pppppppp/8/8/8/8/PPPPPPPP/RQBNKBNR w 0 -";
+
+    // Place pieces at random but the king cannot be next to a rook or queen.
+    // => One bishop and one knight should surround the king.
+    let pieces = { w: new Array(8), b: new Array(8) };
+    let flags = "";
+    for (let c of ["w", "b"]) {
+      if (c == 'b' && randomness == 1) {
+        pieces['b'] = pieces['w'];
+        break;
+      }
+
+      let positions = ArrayFun.range(8);
+
+      const kingPos = randInt(8);
+      let toRemove = [kingPos];
+      let knight1Pos = undefined;
+      let bishop1Pos = undefined;
+      if (kingPos == 0) {
+        if (Math.random() < 0.5) knight1Pos = 1;
+        else bishop1Pos = 1;
+        toRemove.push(1);
+      }
+      else if (kingPos == V.size.y - 1) {
+        if (Math.random() < 0.5) knight1Pos = V.size.y - 2;
+        else bishop1Pos = V.size.y - 2;
+        toRemove.push(V.size.y - 2);
+      }
+      else {
+        knight1Pos = kingPos + (Math.random() < 0.5 ? 1 : -1);
+        bishop1Pos = kingPos + (knight1Pos < kingPos ? 1 : -1);
+        toRemove.push(knight1Pos, bishop1Pos);
+      }
+      const firstPieces = [kingPos, knight1Pos, bishop1Pos]
+        .filter(elt => elt !== undefined);
+      firstPieces
+        .sort((a, b) => b - a)
+        .forEach(elt => positions.splice(elt, 1));
+
+      let randIndex = undefined;
+      if (bishop1Pos === undefined) {
+        const posWithIdx = positions.map((e,i) => { return { e: e, i: i }; });
+        let availableSquares = posWithIdx.filter(p => p.e % 2 == 0);
+        randIndex = randInt(availableSquares.length);
+        bishop1Pos = availableSquares[randIndex].e;
+        positions.splice(availableSquares[randIndex].i, 1);
+      }
+      const posWithIdx = positions.map((e,i) => { return { e: e, i: i }; });
+      const rem1B = bishop1Pos % 2;
+      let availableSquares = posWithIdx.filter(p => p.e % 2 == 1 - rem1B);
+      randIndex = randInt(availableSquares.length);
+      const bishop2Pos = availableSquares[randIndex].e;
+      positions.splice(availableSquares[randIndex].i, 1);
+
+      if (knight1Pos === undefined) {
+        randIndex = randInt(5);
+        knight1Pos = positions[randIndex];
+        positions.splice(randIndex, 1);
+      }
+      randIndex = randInt(4);
+      const knight2Pos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      randIndex = randInt(3);
+      const queenPos = positions[randIndex];
+      positions.splice(randIndex, 1);
+
+      const rook1Pos = positions[0];
+      const rook2Pos = positions[1];
+
+      pieces[c][rook1Pos] = "r";
+      pieces[c][knight1Pos] = "n";
+      pieces[c][bishop1Pos] = "b";
+      pieces[c][queenPos] = "q";
+      pieces[c][kingPos] = "k";
+      pieces[c][bishop2Pos] = "b";
+      pieces[c][knight2Pos] = "n";
+      pieces[c][rook2Pos] = "r";
+    }
+    return (
+      pieces["b"].join("") +
+      "/pppppppp/8/8/8/8/PPPPPPPP/" +
+      pieces["w"].join("").toUpperCase() +
+      " w 0 -"
+    );
+  }
+
+  underCheck(color) {
+    return (
+      this.isAttacked(this.kingPos[color], 'w') ||
+      this.isAttacked(this.kingPos[color], 'b')
+    );
+  }
+};
diff --git a/client/src/variants/Chakart.js b/client/src/variants/Chakart.js
index e8b95cb1..a1caed45 100644
--- a/client/src/variants/Chakart.js
+++ b/client/src/variants/Chakart.js
@@ -94,7 +94,7 @@ export class ChakartRules extends ChessRules {
     // A click to promote a piece on subTurn 2 would trigger this.
     // For now it would then return [NaN, NaN] because surrounding squares
     // have no IDs in the promotion modal. TODO: improve this?
-    if (!square[0]) return null;
+    if (isNaN(square[0])) return null;
     // If subTurn == 2:
     // if square is empty && firstMove is compatible,
     // complete the move (banana or bomb).
diff --git a/client/src/variants/Coronation.js b/client/src/variants/Coronation.js
new file mode 100644
index 00000000..9e12ea10
--- /dev/null
+++ b/client/src/variants/Coronation.js
@@ -0,0 +1,50 @@
+import { ChessRules, Move, PiPo } from "@/base_rules";
+
+export class CoronationRules extends ChessRules {
+  getPotentialMovesFrom([x, y]) {
+    let moves = super.getPotentialMovesFrom([x, y]);
+    // If no queen on board, allow rook+bishop fusions:
+    const color = this.turn;
+    const piece = this.getPiece(x, y);
+    if (
+      [V.ROOK, V.BISHOP].includes(piece) &&
+      this.board.every(b => b.every(cell =>
+        (cell == V.EMPTY || cell[0] != color || cell[1] != V.QUEEN)
+      ))
+    ) {
+      const fusionWith = [V.ROOK, V.BISHOP][1 - "rb".indexOf(piece)];
+      // Can I "self-capture" fusionWith ?
+      for (let step of V.steps[piece]) {
+        let [i, j] = [x + step[0], y + step[1]];
+        while (V.OnBoard(i, j) && this.board[i][j] == V.EMPTY) {
+          i += step[0];
+          j += step[1];
+        }
+        if (
+          V.OnBoard(i, j) &&
+          this.getColor(i, j) == color &&
+          this.getPiece(i, j) == fusionWith
+        ) {
+          moves.push(
+            new Move({
+              appear: [new PiPo({ x: i, y: j, p: V.QUEEN, c: color })],
+              vanish: [
+                new PiPo({ x: x, y: y, p: piece, c: color }),
+                new PiPo({ x: i, y: j, p: fusionWith, c: color })
+              ]
+            })
+          );
+        }
+      }
+    }
+    return moves;
+  }
+
+  getNotation(move) {
+    let notation = super.getNotation(move);
+    if (move.appear[0].p == V.QUEEN && move.vanish[0].p != V.QUEEN)
+      // Coronation
+      notation += "=Q";
+    return notation;
+  }
+};
diff --git a/client/src/variants/Dynamo.js b/client/src/variants/Dynamo.js
index 15cfe95f..cdb02c6a 100644
--- a/client/src/variants/Dynamo.js
+++ b/client/src/variants/Dynamo.js
@@ -43,7 +43,6 @@ export class DynamoRules extends ChessRules {
       });
       this.amoves.push(move);
     }
-    this.subTurn = 1;
     // Stack "first moves" (on subTurn 1) to merge and check opposite moves
     this.firstMove = [];
   }
@@ -750,7 +749,7 @@ export class DynamoRules extends ChessRules {
     // A click to promote a piece on subTurn 2 would trigger this.
     // For now it would then return [NaN, NaN] because surrounding squares
     // have no IDs in the promotion modal. TODO: improve this?
-    if (!square[0]) return null;
+    if (isNaN(square[0])) return null;
     // If subTurn == 2 && square is empty && !underCheck && !isOpposite,
     // then return an empty move, allowing to "pass" subTurn2
     const La = this.amoves.length;
diff --git a/client/src/variants/Football.js b/client/src/variants/Football.js
new file mode 100644
index 00000000..2acb24f4
--- /dev/null
+++ b/client/src/variants/Football.js
@@ -0,0 +1,80 @@
+import { ChessRules } from "@/base_rules";
+
+export class FootballRules extends ChessRules {
+  static get HasFlags() {
+    return false;
+  }
+
+  static get PawnSpecs() {
+    return Object.assign(
+      {},
+      ChessRules.PawnSpecs,
+      { promotions: ChessRules.PawnSpecs.promotions.concat([V.KING]) }
+    );
+  }
+
+  static get Lines() {
+    return [
+      // White goal:
+      [[0, 3], [0, 5]],
+      [[0, 5], [1, 5]],
+      [[1, 5], [1, 3]],
+      [[1, 3], [0, 3]],
+      // Black goal:
+      [[8, 3], [8, 5]],
+      [[8, 5], [7, 5]],
+      [[7, 5], [7, 3]],
+      [[7, 3], [8, 3]]
+    ];
+  }
+
+  static IsGoodPosition(position) {
+    if (position.length == 0) return false;
+    const rows = position.split("/");
+    if (rows.length != V.size.x) return false;
+    // Just check that at least one piece of each color is there:
+    let pieces = { "w": 0, "b": 0 };
+    for (let row of rows) {
+      let sumElts = 0;
+      for (let i = 0; i < row.length; i++) {
+        const lowerRi = row[i].toLowerCase();
+        if (V.PIECES.includes(lowerRi)) {
+          pieces[row[i] == lowerRi ? "b" : "w"]++;
+          sumElts++;
+        } else {
+          const num = parseInt(row[i]);
+          if (isNaN(num)) return false;
+          sumElts += num;
+        }
+      }
+      if (sumElts != V.size.y) return false;
+    }
+    if (Object.values(pieces).some(v => v == 0)) return false;
+    return true;
+  }
+
+  scanKings() {}
+
+  filterValid(moves) {
+    return moves;
+  }
+
+  getCheckSquares() {
+    return [];
+  }
+
+  // No variables update because no royal king + no castling
+  prePlay() {}
+  postPlay() {}
+  preUndo() {}
+  postUndo() {}
+
+  getCurrentScore() {
+    const oppCol = V.GetOppCol(this.turn);
+    const goal = (oppCol == 'w' ? 0 : 7);
+    if (this.board[goal].slice(3, 5).some(b => b[0] == oppCol))
+      return oppCol == 'w' ? "1-0" : "0-1";
+    if (this.atLeastOneMove()) return "*";
+    return "1/2";
+  }
+};
diff --git a/client/src/variants/Gridolina.js b/client/src/variants/Gridolina.js
new file mode 100644
index 00000000..fd43be3f
--- /dev/null
+++ b/client/src/variants/Gridolina.js
@@ -0,0 +1,37 @@
+import { ChessRules } from "@/base_rules";
+import { BerolinaRules } from "@/variants/Berolina";
+
+export class GridolinaRules extends BerolinaRules {
+  static get Lines() {
+    return [
+      [[2, 0], [2, 8]],
+      [[4, 0], [4, 8]],
+      [[6, 0], [6, 8]],
+      [[0, 2], [8, 2]],
+      [[0, 4], [8, 4]],
+      [[0, 6], [8, 6]]
+    ];
+  }
+
+  static OnDifferentGrids([x1, y1], [x2, y2]) {
+    return (
+        Math.abs(Math.floor(x1 / 2) - Math.floor(x2 / 2)) >= 1 ||
+        Math.abs(Math.floor(y1 / 2) - Math.floor(y2 / 2)) >= 1
+    );
+  }
+
+  canTake([x1, y1], [x2, y2]) {
+    return (
+      V.OnDifferentGrids([x1, y1], [x2, y2]) &&
+      super.canTake([x1, y1], [x2, y2])
+    );
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    return (
+      super.getPotentialMovesFrom([x, y]).filter(m => {
+        return V.OnDifferentGrids([x, y], [m.end.x, m.end.y]);
+      })
+    );
+  }
+};
diff --git a/client/src/variants/Koth.js b/client/src/variants/Koth.js
index e286862d..42ffe7bb 100644
--- a/client/src/variants/Koth.js
+++ b/client/src/variants/Koth.js
@@ -1,17 +1,13 @@
 import { ChessRules } from "@/base_rules";
 
 export class KothRules extends ChessRules {
-  filterValid(moves) {
-    if (moves.length == 0) return [];
-    const color = this.turn;
-    const oppCol = V.GetOppCol(color);
-    return moves.filter(m => {
-      this.play(m);
-      // Giving check is forbidden as well:
-      const res = !this.underCheck(color) && !this.underCheck(oppCol);
-      this.undo(m);
-      return res;
-    });
+  static get Lines() {
+    return [
+      [[3, 3], [3, 5]],
+      [[3, 3], [5, 3]],
+      [[3, 5], [5, 5]],
+      [[5, 3], [5, 5]]
+    ];
   }
 
   getCurrentScore() {
@@ -24,9 +20,7 @@ export class KothRules extends ChessRules {
       // The middle is reached!
       return color == "w" ? "1-0" : "0-1";
     }
-    if (this.atLeastOneMove()) return "*";
-    // Stalemate (will probably never happen)
-    return "1/2";
+    return super.getCurrentScore();
   }
 
   evalPosition() {
diff --git a/client/src/variants/Madrasi.js b/client/src/variants/Madrasi.js
new file mode 100644
index 00000000..54c1862b
--- /dev/null
+++ b/client/src/variants/Madrasi.js
@@ -0,0 +1,64 @@
+import { ChessRules } from "@/base_rules";
+
+export class MadrasiRules extends ChessRules {
+  isImmobilized(sq) {
+    const oppCol = V.GetOppCol(this.turn);
+    const piece = this.getPiece(sq[0], sq[1]);
+    let steps = [];
+    switch (piece) {
+      // NOTE: cannot use super.isAttackedByXXX
+      // because it would call the redefined isAttackedBySlideNJump
+      // => Infinite recursive calls.
+      case V.PAWN: {
+        const forward = (oppCol == 'w' ? 1 : -1);
+        steps = [[forward, 1], [forward, -1]];
+        break;
+      }
+      case V.ROOK:
+      case V.BISHOP:
+      case V.KNIGHT:
+        steps = V.steps[piece];
+        break;
+      case V.KING:
+      case V.QUEEN:
+        steps = V.steps[V.ROOK].concat(V.steps[V.BISHOP]);
+        break;
+    }
+    return super.isAttackedBySlideNJump(
+      sq, oppCol, piece, steps, [V.KING, V.PAWN, V.KNIGHT].includes(piece))
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    return (
+      !this.isImmobilized([x, y])
+        ? super.getPotentialMovesFrom([x, y])
+        : []
+    );
+  }
+
+  isAttackedBySlideNJump([x, y], color, piece, steps, oneStep) {
+    for (let step of steps) {
+      let rx = x + step[0],
+          ry = y + step[1];
+      while (V.OnBoard(rx, ry) && this.board[rx][ry] == V.EMPTY && !oneStep) {
+        rx += step[0];
+        ry += step[1];
+      }
+      if (
+        V.OnBoard(rx, ry) &&
+        this.getPiece(rx, ry) == piece &&
+        this.getColor(rx, ry) == color &&
+        // If enemy is immobilized, it doesn't attack:
+        !this.isImmobilized([rx, ry])
+      ) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  isAttackedByKing() {
+    // Connected kings paralyze each other
+    return false;
+  }
+};
diff --git a/client/src/variants/Makruk.js b/client/src/variants/Makruk.js
index 2eeeaeaf..6205f230 100644
--- a/client/src/variants/Makruk.js
+++ b/client/src/variants/Makruk.js
@@ -11,6 +11,10 @@ export class MakrukRules extends ChessRules {
     return false;
   }
 
+  static get Monochrome() {
+    return true;
+  }
+
   static get PawnSpecs() {
     return Object.assign(
       {},
diff --git a/client/src/variants/Rococo.js b/client/src/variants/Rococo.js
index f075fe84..e0838e14 100644
--- a/client/src/variants/Rococo.js
+++ b/client/src/variants/Rococo.js
@@ -15,6 +15,15 @@ export class RococoRules extends ChessRules {
     return ChessRules.PIECES.concat([V.IMMOBILIZER]);
   }
 
+  static get Lines() {
+    return [
+      [[1, 1], [1, 9]],
+      [[1, 9], [9, 9]],
+      [[9, 9], [9, 1]],
+      [[9, 1], [1, 1]]
+    ];
+  }
+
   getPpath(b) {
     if (b[1] == "m")
       //'m' for Immobilizer (I is too similar to 1)
diff --git a/client/src/variants/Shatranj.js b/client/src/variants/Shatranj.js
index 7d76b838..d8e4460e 100644
--- a/client/src/variants/Shatranj.js
+++ b/client/src/variants/Shatranj.js
@@ -9,6 +9,10 @@ export class ShatranjRules extends ChessRules {
     return false;
   }
 
+  static get Monochrome() {
+    return true;
+  }
+
   static get PawnSpecs() {
     return Object.assign(
       {},
diff --git a/client/src/variants/Shogi.js b/client/src/variants/Shogi.js
index 50d273f2..1b2ba03a 100644
--- a/client/src/variants/Shogi.js
+++ b/client/src/variants/Shogi.js
@@ -11,6 +11,10 @@ export class ShogiRules extends ChessRules {
     return false;
   }
 
+  static get Monochrome() {
+    return true;
+  }
+
   static IsGoodFen(fen) {
     if (!ChessRules.IsGoodFen(fen)) return false;
     const fenParsed = V.ParseFen(fen);
diff --git a/client/src/variants/Sittuyin.js b/client/src/variants/Sittuyin.js
index c93c3f07..61bab569 100644
--- a/client/src/variants/Sittuyin.js
+++ b/client/src/variants/Sittuyin.js
@@ -10,6 +10,17 @@ export class SittuyinRules extends ChessRules {
     return false;
   }
 
+  static get Monochrome() {
+    return true;
+  }
+
+  static get Lines() {
+    return ChessRules.Lines.concat([
+      [[0, 0], [8, 8]],
+      [[0, 8], [8, 0]]
+    ]);
+  }
+
   static get PawnSpecs() {
     return Object.assign(
       {},
diff --git a/client/src/variants/Teleport.js b/client/src/variants/Teleport.js
new file mode 100644
index 00000000..b4e6d7fa
--- /dev/null
+++ b/client/src/variants/Teleport.js
@@ -0,0 +1,342 @@
+import { ChessRules, Move, PiPo } from "@/base_rules";
+import { randInt } from "@/utils/alea";
+
+export class TeleportRules extends ChessRules {
+  setOtherVariables(fen) {
+    super.setOtherVariables(fen);
+    this.subTurn = 1;
+    this.firstMove = [];
+  }
+
+  canTake([x1, y1], [x2, y2]) {
+    return this.subTurn == 1;
+  }
+
+  getPPpath(m) {
+    if (
+      m.vanish.length == 2 &&
+      m.appear.length == 1 &&
+      m.vanish[0].c == m.vanish[1].c &&
+      m.appear[0].p == V.KING
+    ) {
+      // Rook teleportation with the king
+      return this.getPpath(m.vanish[1].c + m.vanish[1].p);
+    }
+    return this.getPpath(m.appear[0].c + m.appear[0].p);
+  }
+
+  getPotentialMovesFrom([x, y]) {
+    if (this.subTurn == 1) return super.getPotentialMovesFrom([x, y]);
+    // subTurn == 2: a move is a click, not handled here
+    return [];
+  }
+
+  filterValid(moves) {
+    if (this.subTurn == 2) return super.filterValid(moves);
+    const color = this.turn;
+    return moves.filter(m => {
+      this.play(m);
+      let res = false;
+      if (
+        m.vanish.length == 1 ||
+        m.appear.length == 2 ||
+        m.vanish[0].c != m.vanish[1].c
+      ) {
+        // Standard check:
+        res = !this.underCheck(color);
+      }
+      else {
+        // Self-capture: find landing square not resulting in check
+        outerLoop: for (let i=0; i<8; i++) {
+          for (let j=0; j<8; j++) {
+            if (
+              this.board[i][j] == V.EMPTY &&
+              (
+                m.vanish[1].p != V.PAWN ||
+                i != (color == 'w' ? 0 : 7)
+              )
+            ) {
+              const tMove = new Move({
+                appear: [
+                  new PiPo({
+                    x: i,
+                    y: j,
+                    c: color,
+                    // The dropped piece nature has no importance:
+                    p: V.KNIGHT
+                  })
+                ],
+                vanish: [],
+                start: { x: -1, y: -1 }
+              });
+              this.play(tMove);
+              const moveOk = !this.underCheck(color);
+              this.undo(tMove);
+              if (moveOk) {
+                res = true;
+                break outerLoop;
+              }
+            }
+          }
+        }
+      }
+      this.undo(m);
+      return res;
+    });
+  }
+
+  getAllValidMoves() {
+    if (this.subTurn == 1) return super.getAllValidMoves();
+    // Subturn == 2: only teleportations
+    let moves = [];
+    const L = this.firstMove.length;
+    const color = this.turn;
+    for (let i=0; i<8; i++) {
+      for (let j=0; j<8; j++) {
+        if (
+          this.board[i][j] == V.EMPTY &&
+          (
+            this.firstMove[L-1].vanish[1].p != V.PAWN ||
+            i != (color == 'w' ? 0 : 7)
+          )
+        ) {
+          const tMove = new Move({
+            appear: [
+              new PiPo({
+                x: i,
+                y: j,
+                c: color,
+                p: this.firstMove[L-1].vanish[1].p
+              })
+            ],
+            vanish: [],
+            start: { x: -1, y: -1 }
+          });
+          this.play(tMove);
+          const moveOk = !this.underCheck(color);
+          this.undo(tMove);
+          if (moveOk) moves.push(tMove);
+        }
+      }
+    }
+    return moves;
+  }
+
+  underCheck(color) {
+    if (this.kingPos[color][0] < 0)
+      // King is being moved:
+      return false;
+    return super.underCheck(color);
+  }
+
+  getCurrentScore() {
+    if (this.subTurn == 2)
+      // Move not over
+      return "*";
+    return super.getCurrentScore();
+  }
+
+  doClick(square) {
+    if (isNaN(square[0])) return null;
+    // If subTurn == 2 && square is empty && !underCheck, then teleport
+    if (this.subTurn == 2 && this.board[square[0]][square[1]] == V.EMPTY) {
+      const L = this.firstMove.length;
+      const color = this.turn;
+      if (
+        this.firstMove[L-1].vanish[1].p == V.PAWN &&
+        square[0] == (color == 'w' ? 0 : 7)
+      ) {
+        // Pawns cannot be teleported on last rank
+        return null;
+      }
+      const tMove = new Move({
+        appear: [
+          new PiPo({
+            x: square[0],
+            y: square[1],
+            c: color,
+            p: this.firstMove[L-1].vanish[1].p
+          })
+        ],
+        vanish: [],
+        start: { x: -1, y: -1 }
+      });
+      this.play(tMove);
+      const moveOk = !this.underCheck(color);
+      this.undo(tMove);
+      if (moveOk) return tMove;
+    }
+    return null;
+  }
+
+  play(move) {
+    move.flags = JSON.stringify(this.aggregateFlags());
+    if (move.vanish.length > 0) {
+      this.epSquares.push(this.getEpSquare(move));
+      this.firstMove.push(move);
+    }
+    V.PlayOnBoard(this.board, move);
+    if (
+      this.subTurn == 2 ||
+      move.vanish.length == 1 ||
+      move.appear.length == 2 ||
+      move.vanish[0].c != move.vanish[1].c
+    ) {
+      this.turn = V.GetOppCol(this.turn);
+      this.subTurn = 1;
+      this.movesCount++;
+    }
+    else this.subTurn = 2;
+    this.postPlay(move);
+  }
+
+  postPlay(move) {
+    if (move.vanish.length == 2 && move.vanish[1].p == V.KING)
+      // A king is moved: temporarily off board
+      this.kingPos[move.vanish[1].c] = [-1, -1];
+    else if (move.appear[0].p == V.KING)
+      this.kingPos[move.appear[0].c] = [move.appear[0].x, move.appear[0].y];
+    this.updateCastleFlags(move);
+  }
+
+  // NOTE: no need to update if castleFlags already off
+  updateCastleFlags(move) {
+    if (move.vanish.length == 0) return;
+    const c = move.vanish[0].c;
+    if (
+      move.vanish.length == 2 &&
+      move.appear.length == 1 &&
+      move.vanish[0].c == move.vanish[1].c
+    ) {
+      // Self-capture: of the king or a rook?
+      if (move.vanish[1].p == V.KING)
+        this.castleFlags[c] = [V.size.y, V.size.y];
+      else if (move.vanish[1].p == V.ROOK) {
+        const firstRank = (c == "w" ? V.size.x - 1 : 0);
+        if (
+          move.end.x == firstRank &&
+          this.castleFlags[c].includes(move.end.y)
+        ) {
+          const flagIdx = (move.end.y == this.castleFlags[c][0] ? 0 : 1);
+          this.castleFlags[c][flagIdx] = V.size.y;
+        }
+      }
+    }
+    else {
+      // Normal move
+      const firstRank = (c == "w" ? V.size.x - 1 : 0);
+      const oppCol = V.GetOppCol(c);
+      const oppFirstRank = V.size.x - 1 - firstRank;
+      if (move.vanish[0].p == V.KING && move.appear.length > 0)
+        this.castleFlags[c] = [V.size.y, V.size.y];
+      else if (
+        move.start.x == firstRank &&
+        this.castleFlags[c].includes(move.start.y)
+      ) {
+        const flagIdx = (move.start.y == this.castleFlags[c][0] ? 0 : 1);
+        this.castleFlags[c][flagIdx] = V.size.y;
+      }
+      if (
+        move.end.x == oppFirstRank &&
+        this.castleFlags[oppCol].includes(move.end.y)
+      ) {
+        const flagIdx = (move.end.y == this.castleFlags[oppCol][0] ? 0 : 1);
+        this.castleFlags[oppCol][flagIdx] = V.size.y;
+      }
+    }
+  }
+
+  undo(move) {
+    this.disaggregateFlags(JSON.parse(move.flags));
+    if (move.vanish.length > 0) {
+      this.epSquares.pop();
+      this.firstMove.pop();
+    }
+    V.UndoOnBoard(this.board, move);
+    if (this.subTurn == 2) this.subTurn = 1;
+    else {
+      this.turn = V.GetOppCol(this.turn);
+      this.movesCount--;
+      this.subTurn = (move.vanish.length > 0 ? 1 : 2);
+    }
+    this.postUndo(move);
+  }
+
+  postUndo(move) {
+    if (move.vanish.length == 0) {
+      if (move.appear[0].p == V.KING)
+        // A king was teleported
+        this.kingPos[move.appear[0].c] = [-1, -1];
+    }
+    else if (move.vanish.length == 2 && move.vanish[1].p == V.KING)
+      // A king was (self-)taken
+      this.kingPos[move.vanish[1].c] = [move.end.x, move.end.y];
+    else super.postUndo(move);
+  }
+
+  getComputerMove() {
+    let moves = this.getAllValidMoves();
+    if (moves.length == 0) return null;
+    // Custom "search" at depth 1 (for now. TODO?)
+    const maxeval = V.INFINITY;
+    const color = this.turn;
+    const initEval = this.evalPosition();
+    moves.forEach(m => {
+      this.play(m);
+      m.eval = (color == "w" ? -1 : 1) * maxeval;
+      if (
+        m.vanish.length == 2 &&
+        m.appear.length == 1 &&
+        m.vanish[0].c == m.vanish[1].c
+      ) {
+        const moves2 = this.getAllValidMoves();
+        m.next = moves2[0];
+        moves2.forEach(m2 => {
+          this.play(m2);
+          const score = this.getCurrentScore();
+          const mvEval =
+            ["1-0", "0-1"].includes(score)
+              ? (score == "1-0" ? 1 : -1) * maxeval
+              : (score == "1/2" ? 0 : initEval);
+          if (
+            (color == 'w' && mvEval > m.eval) ||
+            (color == 'b' && mvEval < m.eval)
+          ) {
+            // TODO: if many second moves have the same eval, only the
+            // first is kept. Could be randomized.
+            m.eval = mvEval;
+            m.next = m2;
+          }
+          this.undo(m2);
+        });
+      }
+      else {
+        const score = this.getCurrentScore();
+        if (score != "1/2") {
+          if (score != "*") m.eval = (score == "1-0" ? 1 : -1) * maxeval;
+          else m.eval = this.evalPosition();
+        }
+      }
+      this.undo(m);
+    });
+    moves.sort((a, b) => {
+      return (color == "w" ? 1 : -1) * (b.eval - a.eval);
+    });
+    let candidates = [0];
+    for (let i = 1; i < moves.length && moves[i].eval == moves[0].eval; i++)
+      candidates.push(i);
+    const mIdx = candidates[randInt(candidates.length)];
+    if (!moves[mIdx].next) return moves[mIdx];
+    const move2 = moves[mIdx].next;
+    delete moves[mIdx]["next"];
+    return [moves[mIdx], move2];
+  }
+
+  getNotation(move) {
+    if (move.vanish.length > 0) return super.getNotation(move);
+    // Teleportation:
+    const piece =
+      move.appear[0].p != V.PAWN ? move.appear[0].p.toUpperCase() : "";
+    return piece + "@" + V.CoordsToSquare(move.end);
+  }
+};
diff --git a/client/src/views/Hall.vue b/client/src/views/Hall.vue
index b3172aca..71d77640 100644
--- a/client/src/views/Hall.vue
+++ b/client/src/views/Hall.vue
@@ -1367,7 +1367,7 @@ button.player-action
   margin-left: 32px
 
 .somethingnew
-  background-color: #D6EAF8 !important
+  background-color: #90C4EC !important
 
 .tabbtn
   background-color: #f9faee
diff --git a/client/src/views/MyGames.vue b/client/src/views/MyGames.vue
index eb23bc4e..a29e0e98 100644
--- a/client/src/views/MyGames.vue
+++ b/client/src/views/MyGames.vue
@@ -405,7 +405,7 @@ button#loadMoreBtn
   margin: 0 auto
 
 .somethingnew
-  background-color: #D6EAF8 !important
+  background-color: #90C4EC !important
 </style>
 
 <!-- Not scoped because acting on GameList -->
diff --git a/server/db/populate.sql b/server/db/populate.sql
index 51dae3fe..a3eb2827 100644
--- a/server/db/populate.sql
+++ b/server/db/populate.sql
@@ -8,6 +8,7 @@ insert or ignore into Variants (name, description, noProblems) values
   ('Synchrone', 'Play at the same time', true);
 
 insert or ignore into Variants (name, description) values
+  ('Absorption', 'Absorb powers'),
   ('Alice', 'Both sides of the mirror'),
   ('Allmate1', 'Mate any piece (v1)'),
   ('Allmate2', 'Mate any piece (v2)'),
@@ -21,6 +22,7 @@ insert or ignore into Variants (name, description) values
   ('Baroque', 'Exotic captures'),
   ('Benedict', 'Change colors'),
   ('Berolina', 'Pawns move diagonally'),
+  ('Bicolour', 'Harassed kings'),
   ('Cannibal', 'Capture powers'),
   ('Capture', 'Mandatory captures'),
   ('Checkered1', 'Shared pieces (v1)'),
@@ -30,6 +32,7 @@ insert or ignore into Variants (name, description) values
   ('Circular', 'Run forward'),
   ('Colorbound', 'The colorbound clobberers'),
   ('Coregal', 'Two royal pieces'),
+  ('Coronation', 'Long live the Queen'),
   ('Crazyhouse', 'Captures reborn'),
   ('Cylinder', 'Neverending rows'),
   ('Diamond', 'Rotating board'),
@@ -40,8 +43,10 @@ insert or ignore into Variants (name, description) values
   ('Eightpieces', 'Each piece is unique'),
   ('Enpassant', 'Capture en passant'),
   ('Extinction', 'Capture all of a kind'),
+  ('Football', 'Score a goal'),
   ('Grand', 'Big board'),
   ('Grasshopper', 'Long jumps over pieces'),
+  ('Gridolina', 'Jump the borders'),
   ('Hamilton', 'Walk on a graph'),
   ('Horde', 'A pawns cloud'),
   ('Interweave', 'Interweaved colorbound teams'),
@@ -50,6 +55,7 @@ insert or ignore into Variants (name, description) values
   ('Knightrelay2', 'Move like a knight (v2)'),
   ('Koth', 'King of the Hill'),
   ('Losers', 'Get strong at self-mate'),
+  ('Madrasi', 'Paralyzed pieces'),
   ('Magnetic', 'Laws of attraction'),
   ('Makruk', 'Thai Chess'),
   ('Maxima', 'Occupy the enemy palace'),
@@ -74,6 +80,7 @@ insert or ignore into Variants (name, description) values
   ('Suicide', 'Lose all pieces'),
   ('Suction', 'Attract opposite king'),
   ('Takenmake', 'Prolongated captures'),
+  ('Teleport', 'Reposition pieces'),
   ('Tencubed', 'Four new pieces'),
   ('Threechecks', 'Give three checks'),
   ('Twokings', 'Two kings'),