diff --git a/app/igneum-app/ui/app.css b/app/igneum-app/ui/app.css index ca4f63c7b..cd608c44b 100644 --- a/app/igneum-app/ui/app.css +++ b/app/igneum-app/ui/app.css @@ -474,18 +474,21 @@ table{border-collapse:collapse;width:100%;font-size:var(--t-base)} .row{display:flex;gap:10px;align-items:center;min-width:0} .row.wrap{flex-wrap:wrap} .row .addr-input{margin-top:0;min-width:0} -.switch{display:flex;align-items:flex-start;gap:var(--s-3);font-size:var(--t-md);cursor:pointer;padding:9px 0;line-height:1.4} +/* the custom switch (Prove page fix, 7 October 2026): the label is the positioned ancestor, the native input is hidden + the accessible way (1 px, clipped, still focusable, the label association kept), the knob lives inside the track at + both states; the size class is the switch's own (big), never the DAG legend's swatch class */ +.switch{display:flex;align-items:flex-start;gap:var(--s-3);font-size:var(--t-md);cursor:pointer;padding:9px 0;line-height:1.4;position:relative} .switch+.switch{border-top:1px solid var(--line)} -.switch input{position:absolute;opacity:0;width:0;height:0} +.switch input{position:absolute;width:1px;height:1px;margin:-1px;padding:0;border:0;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap} .switch .track{width:40px;height:22px;border-radius:999px;background:var(--line-2);position:relative;flex:0 0 40px;transition:background .15s ease;margin-top:1px} .switch .track::after{content:"";position:absolute;top:3px;left:3px;width:16px;height:16px;border-radius:50%;background:#F4F1EC;transition:transform .15s ease} .switch input:checked+.track{background:var(--ember)} .switch input:checked+.track::after{transform:translateX(18px)} .switch input:focus-visible+.track{outline:2px solid var(--ember);outline-offset:3px} .switch input:disabled+.track{opacity:.4} -.switch.lg .track{width:52px;height:30px;flex-basis:52px} -.switch.lg .track::after{width:24px;height:24px} -.switch.lg input:checked+.track::after{transform:translateX(22px)} +.switch.big .track{width:52px;height:30px;flex-basis:52px} +.switch.big .track::after{width:24px;height:24px} +.switch.big input:checked+.track::after{transform:translateX(22px)} .sw-text{min-width:0} .sw-text b{font-weight:600} .sw-text .dim{font-size:var(--t-base)} @@ -664,13 +667,14 @@ body.drawer-open .toast{bottom:calc(var(--drawer-h) + 16px)} .dag-chip{position:absolute;right:10px;top:10px;font-size:var(--t-xs);color:var(--ash);background:var(--graphite);border:1px solid var(--line-2);border-radius:999px;padding:3px 10px;pointer-events:none;z-index:2} .legend{display:flex;flex-wrap:wrap;gap:8px 16px;margin-top:var(--s-3);font-size:var(--t-sm);color:var(--ash);align-items:center} .legend span{display:inline-flex;align-items:center;gap:7px} -.lg{width:10px;height:10px;border-radius:3px;display:inline-block;border:1.5px solid var(--ash);background:var(--row)} -.lg.you{border-color:var(--molten);box-shadow:0 0 0 2px var(--molten-40)} -.lg.pending{border-color:var(--ash)} -.lg.included{border-color:var(--ember)} -.lg.excluded{border-color:var(--ash);opacity:.45} -.lg.proven{background:var(--ember);border-color:var(--ember)} -.lg.locked{border-color:var(--ember);border-radius:50%;box-shadow:0 0 0 2px var(--ember-40)} +/* the DAG legend swatches, scoped to the legend (a bare .lg reached label.switch.lg until 7 October 2026) */ +.legend .lg{width:10px;height:10px;border-radius:3px;display:inline-block;border:1.5px solid var(--ash);background:var(--row)} +.legend .lg.you{border-color:var(--molten);box-shadow:0 0 0 2px var(--molten-40)} +.legend .lg.pending{border-color:var(--ash)} +.legend .lg.included{border-color:var(--ember)} +.legend .lg.excluded{border-color:var(--ash);opacity:.45} +.legend .lg.proven{background:var(--ember);border-color:var(--ember)} +.legend .lg.locked{border-color:var(--ember);border-radius:50%;box-shadow:0 0 0 2px var(--ember-40)} /* Cards: the Tuning strip above the list */ .tune-strip{padding:var(--s-4) var(--card-pad)} .ts-row{display:flex;align-items:center;gap:var(--s-4);flex-wrap:wrap} @@ -799,9 +803,10 @@ body.drawer-open .toast{bottom:calc(var(--drawer-h) + 16px)} .proof-scene{height:180px;border:1px solid var(--line);border-radius:6px;background:var(--row);margin:10px 0;overflow:hidden} .proof-scene canvas{display:block;width:100%;height:100%} .proof-head{display:flex;justify-content:space-between;align-items:baseline;font-weight:500;color:var(--bone);margin-bottom:8px} -.track{display:flex;gap:3px;height:3px;margin-bottom:10px} -.track i{flex:1 1 0;background:var(--line);border-radius:2px} -.track i.proving{background:var(--ember)}.track i.verified{background:var(--bone)}.track i.paid{background:var(--molten)} +/* the inspector's shard track, scoped to its element (a bare .track reached every switch's track until 7 October 2026) */ +#i-track{display:flex;gap:3px;height:3px;margin-bottom:10px} +#i-track i{flex:1 1 0;background:var(--line);border-radius:2px} +#i-track i.proving{background:var(--ember)}#i-track i.verified{background:var(--bone)}#i-track i.paid{background:var(--molten)} .shards{list-style:none;margin:0 0 12px;padding:0;font-size:var(--t-xs);line-height:1.7;max-height:120px;overflow:auto} .shards li{display:flex;justify-content:space-between;gap:8px;color:var(--ink-2)} .shards li::before{content:"\25CF";font-size:7px;margin-right:6px;color:var(--line-2)} diff --git a/app/igneum-app/ui/index.html b/app/igneum-app/ui/index.html index 4e74d13c2..3e05e8f9f 100644 --- a/app/igneum-app/ui/index.html +++ b/app/igneum-app/ui/index.html @@ -429,7 +429,7 @@

Prove on this machine

Every block is turned into a short proof, in pieces called shards. Your cards prove shards and earn IGN for each one.

- +

diff --git a/app/igneum-app/ui/view.test.mjs b/app/igneum-app/ui/view.test.mjs index 216680f2b..a67e94c8a 100644 --- a/app/igneum-app/ui/view.test.mjs +++ b/app/igneum-app/ui/view.test.mjs @@ -811,3 +811,49 @@ test('the light theme contrast ratio of every vendor colour on its well is at le assert.equal(/\.badge\.(nvidia|amd|intel|apple|gpu)\{[^}]*var\(--ember/.test(css), false, 'no badge rule tints with ember'); assert.equal(/\.badge[^{]*\{[^}]*animation/.test(css), false, 'nothing on the mark animates'); }); + +// ---------- the custom switch (the Prove page fix, 7 October 2026) ---------- +// the project lead's screenshot from the live 0.3.19 Mac app: a thin white rectangle above-left of the "Prove on this machine" +// switch and the knob drawn outside the track's left edge at off. Two generic classes reached the switch (the DAG +// legend swatch .lg hit label.switch.lg, the shard track .track hit .switch .track) and the native input was an +// unpositioned absolute. This test reads app.css: no bare .lg or .track rule may exist (every use is scoped), the +// native input is hidden the accessible way (1 px clip, still focusable), and the knob's box sits inside the track's +// box at off and at on for both sizes, in the base rules and untouched by both theme blocks. +const cssRules = (text) => { const out = []; const re = /([^{}]+)\{([^{}]*)\}/g; let m; text = text.replace(/\/\*[\s\S]*?\*\//g, ''); while ((m = re.exec(text))) out.push({ sel: m[1].trim(), body: m[2] }); return out; }; +const ruleOf = (sel) => cssRules(css).filter((r) => r.sel.split(',').map((x) => x.trim()).includes(sel)).map((r) => r.body).join(';'); +const px = (body, prop) => { const m = new RegExp('(?:^|;)\\s*' + prop + ':\\s*(-?[\\d.]+)px').exec(body); return m ? parseFloat(m[1]) : null; }; +const translate = (body) => { const m = /transform:\s*translateX\((-?[\d.]+)px\)/.exec(body); return m ? parseFloat(m[1]) : 0; }; + +test('no generic .lg or .track rule reaches a custom switch: every use of those class names is scoped (known-failed on the 0.3.19 CSS)', () => { + const bare = cssRules(css).flatMap((r) => r.sel.split(',').map((x) => x.trim())).filter((sel) => /^\.(lg|track)(\.[\w-]+)*(\s+[\w.:-]+)*$/.test(sel) && !/^\.switch\b/.test(sel)); + assert.deepEqual(bare, [], 'selectors that start with a bare .lg or .track: ' + bare.join(' | ')); + assert.equal(/