diff --git a/README.md b/README.md index 1c5f01b..a410f7c 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,6 @@ The build runs all three guards before it writes anything: check-rules: OK — 87 rules, 21 files scanned, 3 aliases + 41 constant sets checked, 275 formulas verified check-kits: OK — 25 roles, 10 trades, 218 catalogue items, every kit key resolves, every posting can use what it carries, every loadout distinct check-lang: OK — en.json, 945 keys, no leaf/branch collisions -check-behaviour: OK — 45 behavioural tests +check-behaviour: OK — 46 behavioural tests ``` diff --git a/packs/armour/LOG b/packs/armour/LOG index 49cdcec..62e92c6 100644 --- a/packs/armour/LOG +++ b/packs/armour/LOG @@ -1 +1 @@ -2026/08/12-19:24:52.815175 17303f000 Delete type=3 #1 +2026/08/12-19:31:16.133590 1737ab000 Delete type=3 #1 diff --git a/packs/expedition/LOG b/packs/expedition/LOG index 25f92e0..916c687 100644 --- a/packs/expedition/LOG +++ b/packs/expedition/LOG @@ -1 +1 @@ -2026/08/12-19:24:52.889991 17384b000 Delete type=3 #1 +2026/08/12-19:31:16.207708 171f87000 Delete type=3 #1 diff --git a/packs/gear/LOG b/packs/gear/LOG index e397aed..a7e471f 100644 --- a/packs/gear/LOG +++ b/packs/gear/LOG @@ -1 +1 @@ -2026/08/12-19:24:52.817229 17303f000 Delete type=3 #1 +2026/08/12-19:31:16.135472 171f87000 Delete type=3 #1 diff --git a/packs/lastadmission/LOG b/packs/lastadmission/LOG index 15d4892..34c2e3c 100644 --- a/packs/lastadmission/LOG +++ b/packs/lastadmission/LOG @@ -1 +1 @@ -2026/08/12-19:24:52.871528 174057000 Delete type=3 #1 +2026/08/12-19:31:16.192039 171f87000 Delete type=3 #1 diff --git a/packs/npcs/LOG b/packs/npcs/LOG index f965d77..2ffe2d5 100644 --- a/packs/npcs/LOG +++ b/packs/npcs/LOG @@ -1 +1 @@ -2026/08/12-19:24:52.853027 17303f000 Delete type=3 #1 +2026/08/12-19:31:16.173317 172f9f000 Delete type=3 #1 diff --git a/packs/powers/LOG b/packs/powers/LOG index c333a23..b0caab7 100644 --- a/packs/powers/LOG +++ b/packs/powers/LOG @@ -1 +1 @@ -2026/08/12-19:24:52.884011 174057000 Delete type=3 #1 +2026/08/12-19:31:16.202720 1737ab000 Delete type=3 #1 diff --git a/packs/pregens/LOG b/packs/pregens/LOG index 469709f..4b050e3 100644 --- a/packs/pregens/LOG +++ b/packs/pregens/LOG @@ -1 +1 @@ -2026/08/12-19:24:52.837710 17303f000 Delete type=3 #1 +2026/08/12-19:31:16.157576 172793000 Delete type=3 #1 diff --git a/packs/primer/LOG b/packs/primer/LOG index e6e7721..1afa076 100644 --- a/packs/primer/LOG +++ b/packs/primer/LOG @@ -1 +1 @@ -2026/08/12-19:24:52.880282 174057000 Delete type=3 #1 +2026/08/12-19:31:16.199567 172793000 Delete type=3 #1 diff --git a/packs/prologue/LOG b/packs/prologue/LOG index ba708a6..1aebe39 100644 --- a/packs/prologue/LOG +++ b/packs/prologue/LOG @@ -1 +1 @@ -2026/08/12-19:24:52.876362 172833000 Delete type=3 #1 +2026/08/12-19:31:16.195999 1737ab000 Delete type=3 #1 diff --git a/packs/rules/LOG b/packs/rules/LOG index d956fbf..05c133f 100644 --- a/packs/rules/LOG +++ b/packs/rules/LOG @@ -1 +1 @@ -2026/08/12-19:24:52.888015 174057000 Delete type=3 #1 +2026/08/12-19:31:16.206279 172f9f000 Delete type=3 #1 diff --git a/packs/skills/LOG b/packs/skills/LOG index 6ce03b1..602b21d 100644 --- a/packs/skills/LOG +++ b/packs/skills/LOG @@ -1 +1 @@ -2026/08/12-19:24:52.808706 17303f000 Delete type=3 #1 +2026/08/12-19:31:16.127123 171f87000 Delete type=3 #1 diff --git a/packs/slice/LOG b/packs/slice/LOG index bb2c129..70ce6a3 100644 --- a/packs/slice/LOG +++ b/packs/slice/LOG @@ -1 +1 @@ -2026/08/12-19:24:52.882429 174057000 Delete type=3 #1 +2026/08/12-19:31:16.201269 172793000 Delete type=3 #1 diff --git a/packs/starter/LOG b/packs/starter/LOG index 319db93..c72f337 100644 --- a/packs/starter/LOG +++ b/packs/starter/LOG @@ -1 +1 @@ -2026/08/12-19:24:52.863812 17384b000 Delete type=3 #1 +2026/08/12-19:31:16.184597 172793000 Delete type=3 #1 diff --git a/packs/tables/LOG b/packs/tables/LOG index 869173d..ba42942 100644 --- a/packs/tables/LOG +++ b/packs/tables/LOG @@ -1 +1 @@ -2026/08/12-19:24:52.857469 17384b000 Delete type=3 #1 +2026/08/12-19:31:16.178030 172f9f000 Delete type=3 #1 diff --git a/packs/talents/LOG b/packs/talents/LOG index 665b9da..cd25dda 100644 --- a/packs/talents/LOG +++ b/packs/talents/LOG @@ -1 +1 @@ -2026/08/12-19:24:52.811553 17303f000 Delete type=3 #1 +2026/08/12-19:31:16.129805 171f87000 Delete type=3 #1 diff --git a/packs/vehicles/LOG b/packs/vehicles/LOG index 57c5462..a2a793e 100644 --- a/packs/vehicles/LOG +++ b/packs/vehicles/LOG @@ -1 +1 @@ -2026/08/12-19:24:52.819322 17303f000 Delete type=3 #1 +2026/08/12-19:31:16.137478 1737ab000 Delete type=3 #1 diff --git a/packs/weapons/LOG b/packs/weapons/LOG index dea0860..25a1911 100644 --- a/packs/weapons/LOG +++ b/packs/weapons/LOG @@ -1 +1 @@ -2026/08/12-19:24:52.813317 17384b000 Delete type=3 #1 +2026/08/12-19:31:16.131540 171f87000 Delete type=3 #1 diff --git a/styles/ringbrp.css b/styles/ringbrp.css index 1210f8f..2b1e9a4 100644 --- a/styles/ringbrp.css +++ b/styles/ringbrp.css @@ -1688,100 +1688,103 @@ /* ============================================================ THE PAUSE — a uniselector, stepping ============================================================ - Foundry's pause is a
holding an and a -
. Both are replaced here without touching the markup. + Foundry's pause is
holding an and a
, and core + hides it with `#pause { display: none }` / shows it with `#pause.paused { display: + flex }`. The first version of this styled the bare #pause with display:flex and so + overrode the hide — the panel never went away when the game unpaused. EVERY rule here + that could affect visibility or layout is therefore scoped to `.paused`, and `display` + is not set anywhere. + + Core also already centres it: a full-width band, 180px tall, at top calc(50vh - 100px), + column flex, centred both ways, pointer-events none. That layout is correct and is + left alone — only its size and its looks change here. The first version repositioned + it with left/bottom and pushed it off centre for the trouble. A uniselector is the stepping switch inside a telephone exchange: an arm that clicks - round one contact at a time, hunting for a free line. It is the right object for this - game — the department's whole business is a machine that says where things belong, - and OPEN DAY is set inside one. Eleven contacts, because eleven minutes is the - interval this setting keeps coming back to. + round one contact at a time, hunting for a free line. The department's whole business + is a machine that says where things belong, and OPEN DAY is set inside one. Eleven + contacts, because eleven minutes is the interval this setting keeps returning to. - It STEPS rather than sweeps. A continuous spin reads as a loading throbber; a - discrete click every 700ms reads as a machine working through a list, which is what - the department is doing while the table is out of the room. - - No new text: Foundry's own caption is localised and this only restyles it. A string - hardcoded here would be one the lang file could never reach. + It STEPS rather than sweeps — a contact every 700ms. A continuous spin reads as a + loading throbber; a discrete click reads as a machine working through a list. ============================================================ */ -#pause { - /* Core pins this with BOTH top and bottom, so an auto height stretches it down the - whole screen — 180px tall for a 16px caption. Pin it once, here, and size to - content. The arm is rotated, so its bounding box is the diagonal of its own square: - the panel is laid out from the flex basis, never from getBoundingClientRect. */ - position: fixed; - left: 50%; bottom: 14vh; top: auto; right: auto; - transform: translateX(-50%); - height: auto; width: max-content; - display: flex; align-items: center; gap: 16px; - min-width: 320px; padding: 14px 22px 14px 16px; - background: color-mix(in srgb, var(--rb-void) 88%, transparent); - border: 1px solid var(--rb-line); - border-top: 2px solid var(--rb-yellow); - border-radius: 0; - box-shadow: 0 10px 40px rgba(0,0,0,.55); +/* Taller than core's 180px, and moved up by exactly half the extra so it stays centred. */ +#pause.paused { + height: 300px; + top: calc(50vh - 150px); + gap: 28px; + background: linear-gradient(to right, + transparent 0%, + color-mix(in srgb, var(--rb-void) 82%, transparent) 38%, + color-mix(in srgb, var(--rb-void) 82%, transparent) 62%, + transparent 100%); } -/* the dial: eleven contacts, standing still */ -#pause::before { +/* the dial: eleven contacts, standing still, behind the arm */ +#pause.paused::before { content: ""; - position: absolute; left: 16px; top: 50%; - width: 52px; height: 52px; margin-top: -26px; + position: absolute; + top: 24px; left: 50%; margin-left: -85px; + width: 170px; height: 170px; border-radius: 50%; - border: 1px solid var(--rb-hairline); background: repeating-conic-gradient(from -90deg, - var(--rb-line) 0deg 2.4deg, transparent 2.4deg 32.727deg); - -webkit-mask: radial-gradient(circle, transparent 0 15px, #000 16px); - mask: radial-gradient(circle, transparent 0 15px, #000 16px); - opacity: .95; + var(--rb-line) 0deg 1.6deg, transparent 1.6deg 32.727deg); + -webkit-mask: radial-gradient(circle, transparent 0 52px, #000 54px); + mask: radial-gradient(circle, transparent 0 52px, #000 54px); + opacity: .9; } /* the arm. The keeps its box and loses its pixels: object-position walks the - raster out of the frame, which leaves a paintable element without a second wrapper. */ -#pause img { - position: relative; z-index: 1; - flex: 0 0 52px; width: 52px; height: 52px; margin: 0; - object-fit: none; object-position: -999px -999px; - border: none; filter: none; + raster out of frame, which leaves a paintable element without adding a wrapper. */ +#pause.paused img { + width: 170px; height: 170px; + object-fit: none; object-position: -9999px -9999px; + border: none; box-shadow: none; filter: none; /* the arm, and behind it the contact it has just left — without the trailing sector - a 700ms step is easy to miss entirely, and the whole point is that it is stepping */ - background: conic-gradient(from -92deg, - var(--rb-yellow) 0deg 5deg, - var(--rb-yellow-dim) 5deg 30deg, - transparent 30deg 360deg); - -webkit-mask: radial-gradient(circle, #000 0 22px, transparent 23px); - mask: radial-gradient(circle, #000 0 22px, transparent 23px); + a 700ms step is easy to miss, and the stepping is the whole idea */ + background: conic-gradient(from -91deg, + var(--rb-yellow) 0deg 3.5deg, + var(--rb-yellow-dim) 3.5deg 28deg, + transparent 28deg 360deg); + -webkit-mask: radial-gradient(circle, #000 0 78px, transparent 80px); + mask: radial-gradient(circle, #000 0 78px, transparent 80px); animation: rb-uniselector 7.7s steps(11, end) infinite; } -/* Foundry adds fa-spin to that img; ours is the only rotation that should run. */ -#pause img.fa-spin { animation: rb-uniselector 7.7s steps(11, end) infinite; } +/* core adds fa-spin to that img; ours must be the only rotation running */ +#pause.paused img.fa-spin { animation: rb-uniselector 7.7s steps(11, end) infinite; } /* the hub, and the amber that says the record is being written */ -#pause::after { +#pause.paused::after { content: ""; - position: absolute; left: 38px; top: 50%; - width: 8px; height: 8px; margin-top: -4px; + position: absolute; + top: 101px; left: 50%; margin-left: -8px; + width: 16px; height: 16px; border-radius: 50%; background: var(--rb-yellow); animation: rb-pause-pip 2.6s ease-in-out infinite; } -#pause figcaption { +#pause.paused figcaption { font-family: var(--rb-display); - font-size: 13px; letter-spacing: .22em; text-transform: uppercase; + font-size: 24px; + letter-spacing: .3em; + text-transform: uppercase; color: var(--rb-yellow); - text-shadow: none; padding: 0; margin: 0; - border-left: 1px solid var(--rb-hairline); padding-left: 16px; + line-height: 1.2; + text-shadow: 0 2px 18px var(--rb-void); } @keyframes rb-uniselector { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } @keyframes rb-pause-pip { 0%, 100% { opacity: .25; } 50% { opacity: 1; } } -/* A stepping dial in the corner of the eye is exactly the sort of thing a reduced-motion - preference is asking us not to do. Keep the object, stop the movement. */ +/* A stepping dial in the corner of the eye is exactly what this preference is about. + Keep the object, stop the movement — including core's own pulse on the whole figure. */ @media (prefers-reduced-motion: reduce) { - #pause img, #pause img.fa-spin, #pause::after { animation: none; } - #pause img { transform: rotate(-32deg); } + #pause.paused, + #pause.paused img, + #pause.paused img.fa-spin, + #pause.paused::after { animation: none; } + #pause.paused img { transform: rotate(-32.7deg); } } diff --git a/system.json b/system.json index 44e2169..04aaa95 100644 --- a/system.json +++ b/system.json @@ -2,7 +2,7 @@ "id": "ringbrp", "title": "The Custodians", "description": "A Basic Roleplaying game of agents who cross the crossing to contain what should not be there — and who come back a little less from here each time.", - "version": "1.0.4", + "version": "1.0.5", "compatibility": { "minimum": 13, "verified": "14.364" diff --git a/tools/check-behaviour.mjs b/tools/check-behaviour.mjs index 2595d33..fa97fad 100644 --- a/tools/check-behaviour.mjs +++ b/tools/check-behaviour.mjs @@ -426,6 +426,28 @@ test("every declared document type has a label and a sheet section", async () => } }); +// Core hides the pause overlay with `#pause { display: none }` and shows it with +// `#pause.paused { display: flex }`. A system rule on the BARE #pause that sets display +// wins on specificity and the overlay never goes away — the game unpauses, play carries +// on, and GAME PAUSED sits over the table for the rest of the session. Shipped exactly +// that in 1.0.4. Anything targeting #pause must be scoped to .paused. +test("no stylesheet rule sets display on a bare #pause", async () => { + const { readFile } = await import("node:fs/promises"); + const raw = await readFile(new URL("../styles/ringbrp.css", import.meta.url), "utf8"); + // Comments first, or this guard reports the comment that explains the guard — which + // is precisely what the template guard did the last time round. + const css = raw.replace(/\/\*[\s\S]*?\*\//g, ""); + // every rule whose selector mentions #pause, with its declaration block + for (const m of css.matchAll(/([^{}]*#pause[^{}]*)\{([^{}]*)\}/g)) { + const selector = m[1].trim().replace(/\s+/g, " "); + const body = m[2]; + const scoped = selector.split(",").every(sel => !sel.includes("#pause") || /#pause[^,]*\.paused/.test(sel)); + if (scoped) continue; + assert.ok(!/(^|;)\s*display\s*:/.test(body), + `"${selector}" sets display on an unscoped #pause — the overlay will not hide on unpause`); + } +}); + /* ---------------------------------------------------------------- */ await runAll();