diff --git a/crates/cb-render-html/src/doc.rs b/crates/cb-render-html/src/doc.rs
index c436109..35c43a8 100644
--- a/crates/cb-render-html/src/doc.rs
+++ b/crates/cb-render-html/src/doc.rs
@@ -56,7 +56,7 @@ fn cards(list: &[games_ground::SolutionCard]) -> String {
/// The only JavaScript in the project. See the module docs.
pub const SCRIPT: &str = r#"
(function () {
- var down = null, held = null, marked = [], ghost = null;
+ var down = null, held = null, marked = [], ghost = null, ghostLabel = '';
function node(e) {
var n = e.target;
@@ -70,6 +70,19 @@ pub const SCRIPT: &str = r#"
// wrote. This matches on them. It does not compute, infer, filter or
// default one -- a script that pattern-matched ids to guess what is
// legal would be forbidden even though the visible result is identical.
+ // ADR-0010 D1 again: `data-descs` is written by Rust, in step with
+ // `data-targets`. The script pairs them by index and renders one. It
+ // does not compose a description from an id.
+ function describeOf(held, key) {
+ if (!held) { return null; }
+ var t = held.getAttribute('data-targets');
+ var d = held.getAttribute('data-descs');
+ if (!t || !d) { return null; }
+ var i = t.split(' ').indexOf(key);
+ var all = d.split('|');
+ return i >= 0 && i < all.length ? all[i] : null;
+ }
+
function mark(n) {
var spec = n.getAttribute('data-targets');
if (!spec) { return; }
@@ -88,6 +101,7 @@ pub const SCRIPT: &str = r#"
if (held) { held.classList.remove('held'); held = null; }
if (ghost && ghost.parentNode) { ghost.parentNode.removeChild(ghost); }
ghost = null;
+ ghostLabel = '';
down = null;
}
@@ -102,7 +116,8 @@ pub const SCRIPT: &str = r#"
if (n.getAttribute('data-targets')) {
ghost = document.createElement('div');
ghost.id = 'cb-ghost';
- ghost.textContent = n.textContent;
+ ghostLabel = n.textContent;
+ ghost.textContent = ghostLabel;
ghost.style.left = e.clientX + 'px';
ghost.style.top = e.clientY + 'px';
document.body.appendChild(ghost);
@@ -113,6 +128,11 @@ pub const SCRIPT: &str = r#"
if (!ghost) { return; }
ghost.style.left = e.clientX + 'px';
ghost.style.top = e.clientY + 'px';
+ // The explanation, beside the pointer and therefore beside the
+ // target it is over (CB-WP-0018 T03).
+ var over = describeOf(held, key(node(e)));
+ ghost.textContent = over || ghostLabel;
+ ghost.className = over ? 'over' : '';
});
document.addEventListener('pointercancel', clear);
@@ -165,11 +185,15 @@ h1,h2{font-size:1rem;margin:1.2rem 0 .4rem;color:#9cf}
.dropok{outline:2px dashed #9cf;outline-offset:3px;background:#1d2a33}
.dropok text{fill:#cfe}
/* The ghost that follows the pointer, so a drag is not invisible. */
+#cb-ghost.over{background:#1d3347;border-color:#9cf;color:#cfe}
#cb-ghost{position:fixed;pointer-events:none;z-index:9;padding:.3rem .5rem;
border-radius:6px;background:#243;border:1px solid #5a7;color:#dde;
font:13px ui-monospace,monospace;box-shadow:0 6px 16px #000b;
transform:translate(-50%,-140%)}
.k{color:#89a}
+.nil{color:#c88}
+.eff{color:#8c9}
+#cb-log{max-height:16rem;overflow-y:auto;font-size:13px}
#cb-status{margin-top:1rem;color:#fc9;min-height:1.2em}
svg{background:#1b1e26;border:1px solid #445;border-radius:6px}
";
@@ -405,6 +429,18 @@ pub fn document(
endpoint: &str,
seat: Option,
may_pass: bool,
+) -> String {
+ document_with_log(view, legal, endpoint, seat, may_pass, &[])
+}
+
+/// The table, plus the game log (CB-WP-0018 T02).
+pub fn document_with_log(
+ view: &GroundView,
+ legal: &[games_ground::GroundCommand],
+ endpoint: &str,
+ seat: Option,
+ may_pass: bool,
+ log: &[LogLine],
) -> String {
let mut s = String::with_capacity(8192);
let _ = write!(
@@ -431,6 +467,25 @@ pub fn document(
},
);
+ body(&mut s, view);
+ move_section(&mut s, legal, seat, may_pass);
+ log_section(&mut s, log);
+ let _ = write!(
+ s,
+ "ready
\
+ \
+