<!-- SOLID CITY · part 51 -->
/ reading plinth
      [4.8, 5.2, 1.4, 0.7, 0.5, 2.7, 0.35],                                     // the help-guide sign
      [-3.6, 3.6, 2.1, 0.7, 0.5, 1.1, 0.5], [3.6, 3.6, 2.1, 0.7, 0.5, 1.1, -0.5] // benches
    ],
    standRects: [
      { cx: 0, cz: 0, hw: 7.3, hd: 7.3, y: 0.5 }                                // the platform top
    ]
  };
  return group;
}
export default createBetweenStones;


===== FILE: public/city/book-law-art.js =====
/* book-law-art.js — THE KING'S BOOK OF LAW: shared by the city display (book-law.js), the /book-of-law page and the server.
   parse(md): splits the King's text into {title, subtitle, pages:[{n, label, title, paras:[...]}], sign}. The words are never
   edited: only markdown markers (#, ---, *) are dropped for presentation; every paragraph is rendered with textContent / esc.
   engrave(W, H, book): browser only. Procedural gold ULTRA-DENSE INTAGLIO linework on black for the open-book spread:
   a sine-modulated line field, guilloche rosettes, hatched + cross-hatched engraved lettering, ornamental borders, a crown. */
(function (root) {
'use strict';
function parse(md) {
  var lines = String(md || '').replace(/\r\n?/g, '\n').split('\n'), book = { title: '', subtitle: '', pages: [], sign: '' }, cur = null, para = [];
  function flush() { if (para.length) { (cur ? cur.paras : (book.pre = book.pre || [])).push(para.join(' ')); para = []; } }
  lines.forEach(function (L) {
    var t = L.trim(), m;
    if (/^-{3,}$/.test(t) || !t) { flush(); return; }
    if ((m = /^#\s+(.+)$/.exec(t))) { flush(); book.title = m[1]; return; }
    if ((m = /^###\s+(.+)$/.exec(t))) { flush(); book.subtitle = m[1]; return; }
    if ((m = /^##\s+(.+)$/.exec(t))) {
      flush(); var p = /^(PAGE\s+[A-Z]+)\s+[\u2014-]\s+(.+)$/.exec(m[1]);
      cur = { n: book.pages.length + 1, label: p ? p[1] : 'PAGE ' + (book.pages.length + 1), title: p ? p[2] : m[1], heading: m[1], paras: [] }; book.pages.push(cur); return;
    }
    if ((m = /^\*([^*].*[^*])\*$/.exec(t))) { flush(); book.sign = m[1]; return; }
    para.push(t);
  });
  flush(); return book;
}

/* ---------------- engraving (browser) ---------------- */
var GOLD = '#ecc76e', GOLD_D = '#a87b26', INK = '#060405';
function hatchCanvas(w, h, s, dense) {   // gold hatch + cross-hatch, used as the fill of engraved shapes
  var c = document.createElement('canvas'); c.width = Math.max(1, Math.ceil(w)); c.height = Math.max(1, Math.ceil(h)); var g = c.getContext('2d');
  g.strokeStyle = GOLD; g.lineWidth = Math.max(0.6, 0.9 * s); var step = (dense || 3) * s;
  g.beginPath(); for (var x = -h; x < w; x += step) { g.moveTo(x, h); g.lineTo(x + h, 0); } g.stroke();
  g.globalAlpha = 0.55; g.strokeStyle = GOLD_D; g.beginPath(); for (x = 0; x < w + h; x += step * 1.6) { g.moveTo(x, h); g.lineTo(x - h, 0); } g.stroke();
  return c;
}
function engraveShape(g, x, y, w, h, s, drawMask, dense) {   // fill an arbitrary mask (drawn by drawMask(ctx, ox, oy)) with engraved hatching
  var c = hatchCanvas(w, h, s, dense), cg = c.getContext('2d'); cg.globalAlpha = 1; cg.globalCompositeOperation = 'destination-in'; cg.fillStyle = '#000'; drawMask(cg, -x, -y);
  g.drawImage(c, x, y);
}
function engravedText(g, txt, cx, cy, size, s, font) {
  g.save(); g.font = font || ('700 ' + size + 'px "Cinzel", "Trajan Pro", Georgia, "Times New Roman", serif'); g.textAlign = 'center'; g.textBaseline = 'middle';
  var w = g.measureText(txt).width + size, h = size * 1.5, x = cx - w / 2, y = cy - h / 2, f = g.font;
  g.fillStyle = '#2a1c08'; g.fillText(txt, cx + 2.5 * s, cy + 2.5 * s);   // the cut's shadow (intaglio depth)
  engraveShape(g, x, y, w, h, s, function (c, ox, oy) { c.font = f; c.textAlign = 'center'; c.textBaseline = 'middle'; c.fillText(txt, cx + ox, cy + oy); }, 2.4);
  g.strokeStyle = GOLD; g.lineWidth = Math.max(0.7, 1.3 * s); g.strokeText(txt, cx, cy); g.restore();
}
function rosette(g, cx, cy, R, s, n, k) {   // guilloche: n phase-shifted closed curves -> moire rosette
  g.save(); g.strokeStyle = GOLD; g.lineWidth = Math.max(0.5, 0.7 * s); g.globalAlpha = 0.9;
  for (var j = 0; j < n; j++) {
    var ph = j / n * Math.PI * 2; g.beginPath();
    for (var i = 0; i <= 360; i++) { var t = i / 360 * Math.PI * 2, r = R * (0.62 + 0.22 * Math.cos(k * t + ph * 2) + 0.16 * Math.cos((k - 3) * t - ph)), x = cx + r * Math.cos(t), y = cy + r * Math.sin(t); if (i) g.lineTo(x, y); else g.moveTo(x, y); }
    g.stroke();
  }
  g.globalAlpha = 1; g.lineWidth = 1.6 * s; g.beginPath(); g.arc(cx, cy, R * 1.02, 0, Math.PI * 2); g.stroke(); g.beginPath(); g.arc(cx, cy, R * 0.18, 0, Math.PI * 2); g.stroke(); g.restore();
}
function crownPath(c, x, y, w, h) {   // five-point crown, base band; (x,y) = top-left
  c.beginPath(); c.moveTo(x, y + h); c.lineTo(x, y + h * 0.82); c.lineTo(x + w * 0.02, y + h * 0.3);
  var tips = [0.02, 0.26, 0.5, 0.74, 0.98], val = [0.62, 0.5, 0.62, 0.5];
  for (var i = 0; i < 4; i++) { var a = tips[i], b = tips[i + 1], mid = (a + b) /