The one drift: the separator was a border on B, making B's stage 1px
narrower than A's; it is now a 1px column gap, so the stages are the same
size to the pixel. Tests now read what the contract promises instead of a
proxy: the strip's ring order, the full bakeoff sequence, 1:1 and Fit by
geometry, the 900px break from both sides, A wrapping, each form naming its
own item, a sibling-prefix symlink, both reveals, the strip's flag, the back
arrow unlinked, a one-axis picture, a focused player, two videos with no
toggle. The contract names .cmp-cap, a press on a stage, INV-4's URL-driven
picker and the redirect branch's isinstance check. r3.toml gains ten rows.
GET /b/{name}/compare with the conjunction 404 (containment AND the review
ring), both sides recorded as seen, view state (side, link) mapped from a
closed set onto every link, side-keyed regions, and back=compare in
_mark_redirect. compare.html: two stages sharing one set of rows, the strip
as picker (the side active now), linked and per-side stepping, X/L/Z/A/B/C
keys under the review's guards, synced pan by fraction with an echo guard,
per-side blur reveals, JS-off parity.
The stage machinery moves out of view.html into _stage_js.html
(BoothMode.bind, BoothStage.attach), shared by the review and compare. The
review gains a Compare control and a C key. At phone width a full top bar
wraps.
Tables: r2c's 15 stage rows re-pointed to _stage_js.html; r2b's phone
top-bar row re-anchored (the wrap made it vacuous alone); new r3.toml. The
contract records the wrap, equal stages and C on the compare page.