From bb2aed2f4ea57bfd0468e683bc33e795a2bf4711 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Wed, 10 Jun 2026 15:23:50 -0500 Subject: fix(theme-studio): derive box bevel colors from the face background The released/pressed bevel was a flat translucent white/black overlay, which reads weaker than the box Emacs draws. reliefColors in colormath.js now ports Emacs 30's x_alloc_lighter_color: highlight = bg x1.2, shadow = bg x0.6, an additive boost for dark backgrounds, and the same-color fallback for pure black and white. boxCss takes the face's effective bg and derives both edges from it. Pressed swaps the pair, and the translucent pair remains only when no bg is known. Width stays 1px because dupre's :line-width -1 draws 1px lines in Emacs too. The gap was color strength, not width. Five node tests pin hand-computed fixtures from the C source, and a new #beveltest gate pins the wiring. --- scripts/theme-studio/app.js | 38 +++++++++++++++---- scripts/theme-studio/colormath.js | 29 ++++++++++++++- scripts/theme-studio/run-tests.sh | 2 +- scripts/theme-studio/test-colormath.mjs | 32 ++++++++++++++++ scripts/theme-studio/theme-studio.html | 65 +++++++++++++++++++++++++++++---- 5 files changed, 150 insertions(+), 16 deletions(-) (limited to 'scripts') diff --git a/scripts/theme-studio/app.js b/scripts/theme-studio/app.js index 57057328..aadfd5b7 100644 --- a/scripts/theme-studio/app.js +++ b/scripts/theme-studio/app.js @@ -408,9 +408,14 @@ function udeco(o){return `font-weight:${o.bold?'bold':'normal'};font-style:${o.i // box-shadow VALUE (or '' for no box). 'line' is a flat border in the box color // (or the face's own color when unset); 'released'/'pressed' are the 3D button // styles Emacs draws, derived from the background so they read on any color. -function boxCss(b){if(!b||!b.style)return '';const w=b.width||1; - if(b.style==='released')return `inset ${w}px ${w}px 0 #ffffff33,inset -${w}px -${w}px 0 #00000066`; - if(b.style==='pressed')return `inset ${w}px ${w}px 0 #00000066,inset -${w}px -${w}px 0 #ffffff33`; +function boxCss(b,bg){if(!b||!b.style)return '';const w=b.width||1; + if(b.style==='released'||b.style==='pressed'){ + // Emacs derives the 3D edges from the face's background (reliefColors, + // ported from xterm.c); the translucent pair is only the no-bg fallback. + const r=bg?reliefColors(bg):{hl:null,sh:null}; + const hl=r.hl||'#ffffff33',sh=r.sh||'#00000066'; + const [a,z]=b.style==='released'?[hl,sh]:[sh,hl]; + return `inset ${w}px ${w}px 0 ${a},inset -${w}px -${w}px 0 ${z}`;} return `inset 0 0 0 ${w}px ${b.color||'currentColor'}`;} // The per-row box control: none / line / raised / pressed. get()/set() read and // write the face's box object (null = no box). @@ -494,7 +499,7 @@ function buildMockFrame(){ buf+=`
${L.cont?'↪':''}${i+1}${cd||' '}
`; }); let html=`
${buf}
`; - const mlbx=boxCss(ml.box),mlibx=boxCss(mli.box); + const mlbx=boxCss(ml.box,ml.bg||bg),mlibx=boxCss(mli.box,mli.bg||bg); html+=`
init.el (Emacs Lisp) L5 git:main
`; html+=`
*Messages* (Fundamental)
`; html+=`
I-search: count zzz [no match]
`; @@ -539,7 +544,7 @@ function buildPkgTable(){ } applyTableSort('pkgbody'); } -function ofs(app,face){const f=PKGMAP[app][face]||{},fg=effFg(pkgEffFg(app,face)),bg=pkgEffBg(app,face);const dec=(f.underline?'underline ':'')+(f.strike?'line-through':'');const bx=boxCss(f.box);return `color:${fg};${bg?'background:'+bg+';':''}font-weight:${f.bold?'bold':'normal'};font-style:${f.italic?'italic':'normal'};text-decoration:${dec.trim()||'none'};font-size:${(f.height||1)}em${bx?';box-shadow:'+bx:''}`;} +function ofs(app,face){const f=PKGMAP[app][face]||{},fg=effFg(pkgEffFg(app,face)),bg=pkgEffBg(app,face);const dec=(f.underline?'underline ':'')+(f.strike?'line-through':'');const bx=boxCss(f.box,bg||MAP['bg']);return `color:${fg};${bg?'background:'+bg+';':''}font-weight:${f.bold?'bold':'normal'};font-style:${f.italic?'italic':'normal'};text-decoration:${dec.trim()||'none'};font-size:${(f.height||1)}em${bx?';box-shadow:'+bx:''}`;} function os(app,face,txt){return `${txt}`;} function renderOrgPreview(){const a='org-mode',L=[]; L.push(os(a,'org-document-info-keyword','#+TITLE:')+' '+os(a,'org-document-title','Project Notes')); @@ -863,7 +868,7 @@ function worstCellHtml(face){ // Repaint every covered overlay face (their floors depend on the syntax palette, // so a syntax-color edit has to refresh them even though it doesn't rebuild the table). function repaintCovered(){COVERED_FACES.forEach(f=>{if(UIMAP[f]&&document.getElementById('uicr-'+f))paintUI(f);});} -function paintUI(face){const pv=document.getElementById('uiprev-'+face);if(!pv)return;const o=UIMAP[face];pv.style.color=effFg(o.fg);pv.style.background=effBg(o.bg);pv.style.fontWeight=o.bold?'bold':'normal';pv.style.fontStyle=o.italic?'italic':'normal';pv.style.textDecoration=(o.underline?'underline ':'')+(o.strike?'line-through':'')||'none';pv.style.boxShadow=boxCss(o.box); +function paintUI(face){const pv=document.getElementById('uiprev-'+face);if(!pv)return;const o=UIMAP[face];pv.style.color=effFg(o.fg);pv.style.background=effBg(o.bg);pv.style.fontWeight=o.bold?'bold':'normal';pv.style.fontStyle=o.italic?'italic':'normal';pv.style.textDecoration=(o.underline?'underline ':'')+(o.strike?'line-through':'')||'none';pv.style.boxShadow=boxCss(o.box,effBg(o.bg)); const cr=document.getElementById('uicr-'+face);if(cr){const w=worstCellHtml(face);if(w!==null){cr.innerHTML=w;}else{const efg=effFg(o.fg),ebg=effBg(o.bg),r=contrast(efg,ebg);cr.innerHTML=crHtml(r);}}} function buildUITable(){ const tb=document.getElementById('uibody');tb.innerHTML=''; @@ -1076,7 +1081,7 @@ if(location.hash==='#contrasttest'){let ok=true;const notes=[];const A=(c,n)=>{i // two-color ratio alone, and must re-rate a ground-dependent face's cell. UIMAP['fringe']={fg:'#ddeeff',bg:null,bold:false,italic:false,underline:false,strike:false}; buildUITable(); - const gb=MAP['bg'];MAP['bg']='#440000';applyGround(); + MAP['bg']='#440000';applyGround(); const pv=document.getElementById('uiprev-mode-line'); A(pv&&pv.style.background==='rgb(170, 187, 204)','ground change keeps a face own preview bg: got '+(pv&&pv.style.background)); const twoAfter=document.getElementById('uicr-mode-line'); @@ -1100,6 +1105,25 @@ if(location.hash==='#contrasttest'){let ok=true;const notes=[];const A=(c,n)=>{i for(const k in MAP)delete MAP[k];Object.assign(MAP,saveMAP);for(const f in UIMAP)delete UIMAP[f];Object.assign(UIMAP,saveUI);buildUITable();applyGround(); document.title='CONTRASTTEST '+(ok?'PASS':'FAIL'); const d=document.createElement('div');d.id='contrasttest';d.textContent='CONTRASTTEST '+(ok?'PASS':'FAIL')+(notes.length?' | '+notes.join(' ; '):'');document.body.appendChild(d);} +// Bevel gate (open with #beveltest): released/pressed boxes derive their +// highlight and shadow from the face's effective bg per Emacs's relief +// algorithm, and pressed draws the shadow edge first. +if(location.hash==='#beveltest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c){ok=false;notes.push(n);}}; + const saveUI=JSON.parse(JSON.stringify(UIMAP)); + UIMAP['mode-line']={fg:'#d8dee9',bg:'#30343c',bold:false,italic:false,underline:false,strike:false,box:{style:'released',width:1,color:null}}; + buildUITable(); + const pv=document.getElementById('uiprev-mode-line'); + const bs=pv&&pv.style.boxShadow; + A(bs&&bs.includes('rgb(113, 118, 127)'),'released highlight derives from the face bg (#71767f): '+bs); + A(bs&&bs.includes('rgb(15, 17, 22)'),'released shadow derives from the face bg (#0f1116): '+bs); + UIMAP['mode-line'].box={style:'pressed',width:1,color:null};paintUI('mode-line'); + const bs2=pv&&pv.style.boxShadow; + A(bs2&&bs2.includes('rgb(15, 17, 22)')&&bs2.includes('rgb(113, 118, 127)')&&bs2.indexOf('rgb(15, 17, 22)'){if(!c){ok=false;notes.push(n);}}; diff --git a/scripts/theme-studio/colormath.js b/scripts/theme-studio/colormath.js index 167a5ea1..2a7328e5 100644 --- a/scripts/theme-studio/colormath.js +++ b/scripts/theme-studio/colormath.js @@ -190,4 +190,31 @@ function paletteWarnings(palette, threshold = 0.02, cap = 5) { return { warnings: pairs.slice(0, cap), overflow: Math.max(0, pairs.length - cap), nearest }; } -export { srgb2oklab, oklab2oklch, oklch2oklab, oklch2hex, apca, deltaE, hex2rgb, lin, rl, contrast, rating, hsv2rgb, rgb2hsv, rgb2hex, oklab2lrgb, inGamut, lrgb2hex, planeCell, paletteWarnings }; +// --- 3D-box relief colors, matching Emacs's renderer --------------------- +// Port of x_alloc_lighter_color (Emacs 30 xterm.c): highlight = bg x 1.2 +// (delta 0x8000), shadow = bg x 0.6 (delta 0x4000), both in 16-bit channel +// space. Backgrounds dimmer than 48000/65535 (by Emacs's 2R+3G+B/6 weighting) +// get an additive boost of delta*dimness*factor/2, because scaling alone +// barely moves a dark color. When the result still equals the background +// (pure black shadow, pure white highlight), Emacs retries with bg+delta. +function reliefColors(bgHex) { + const rgb = hex2rgb(bgHex); + if (rgb.some((c) => Number.isNaN(c))) return { hl: null, sh: null }; + const ch16 = rgb.map((c) => c * 257); + const one = (factor, delta) => { + let nw = ch16.map((c) => Math.min(0xffff, factor * c)); + const bright = (2 * ch16[0] + 3 * ch16[1] + ch16[2]) / 6; + if (bright < 48000) { + const md = delta * (1 - bright / 48000) * factor / 2; + nw = factor < 1 + ? nw.map((v) => Math.max(0, v - md)) + : nw.map((v) => Math.min(0xffff, v + md)); + } + if (nw.every((v, i) => Math.round(v) === ch16[i])) + nw = ch16.map((c) => Math.min(0xffff, c + delta)); + return '#' + nw.map((v) => Math.round(v / 257).toString(16).padStart(2, '0')).join(''); + }; + return { hl: one(1.2, 0x8000), sh: one(0.6, 0x4000) }; +} + +export { srgb2oklab, oklab2oklch, oklch2oklab, oklch2hex, apca, deltaE, hex2rgb, lin, rl, contrast, rating, hsv2rgb, rgb2hsv, rgb2hex, oklab2lrgb, inGamut, lrgb2hex, planeCell, paletteWarnings, reliefColors }; diff --git a/scripts/theme-studio/run-tests.sh b/scripts/theme-studio/run-tests.sh index c8e34fe9..0db7faa0 100755 --- a/scripts/theme-studio/run-tests.sh +++ b/scripts/theme-studio/run-tests.sh @@ -53,7 +53,7 @@ CHROME="" for c in google-chrome-stable google-chrome chromium chromium-browser; do if command -v "$c" >/dev/null 2>&1; then CHROME="$c"; break; fi done -HASHES="selftest cursortest readouttest deltatest oklchtest planetest locktest sorttest mocktest contrasttest safetest healtest familytest counttest baseedittest roundtriptest" +HASHES="selftest cursortest readouttest deltatest oklchtest planetest locktest sorttest mocktest contrasttest safetest healtest familytest counttest baseedittest roundtriptest beveltest" if [ "$NO_BROWSER" = 1 ]; then skip_msg "browser hash gates (--no-browser)" elif [ -z "$CHROME" ]; then diff --git a/scripts/theme-studio/test-colormath.mjs b/scripts/theme-studio/test-colormath.mjs index 58ce7829..992d35bc 100644 --- a/scripts/theme-studio/test-colormath.mjs +++ b/scripts/theme-studio/test-colormath.mjs @@ -13,6 +13,7 @@ import { srgb2oklab, oklab2oklch, oklch2oklab, oklch2hex, apca, deltaE, hex2rgb, rl, contrast, rating, hsv2rgb, rgb2hsv, rgb2hex, oklab2lrgb, inGamut, lrgb2hex, planeCell, paletteWarnings, + reliefColors, } from './colormath.js'; const close = (a, b, eps = 0.005) => Math.abs(a - b) <= eps; @@ -230,6 +231,37 @@ test('paletteWarnings: threshold is inclusive-exclusive at the boundary', () => assert.equal(paletteWarnings(pal, 0.007).warnings.length, 1, 'just above the pair distance'); }); +// Fixtures hand-computed from Emacs 30's xterm.c x_alloc_lighter_color +// (factor 1.2 / delta 0x8000 highlight, 0.6 / 0x4000 shadow, dark boost +// below brightness 48000/65535, same-color fallback adds delta). +test('reliefColors: dark mode-line bg gets the dark boost (Normal)', () => { + const { hl, sh } = reliefColors('#30343c'); + assert.equal(hl, '#71767f'); + assert.equal(sh, '#0f1116'); +}); + +test('reliefColors: grey75 brightness is above the boost limit (Normal)', () => { + const { hl, sh } = reliefColors('#bfbfbf'); + assert.equal(hl, '#e5e5e5'); // 1.2x only, no additive boost + assert.equal(sh, '#737373'); // 0.6x only +}); + +test('reliefColors: pure black hits the same-color fallback for the shadow (Boundary)', () => { + const { hl, sh } = reliefColors('#000000'); + assert.equal(hl, '#4d4d4d'); // boost lifts the highlight off black + assert.equal(sh, '#404040'); // 0.6x + boost still black -> fallback adds delta +}); + +test('reliefColors: pure white highlight saturates, shadow scales (Boundary)', () => { + const { hl, sh } = reliefColors('#ffffff'); + assert.equal(hl, '#ffffff'); // clamped, fallback also clamps to white + assert.equal(sh, '#999999'); +}); + +test('reliefColors: malformed hex returns null pair (Error)', () => { + assert.deepEqual(reliefColors('nonsense'), { hl: null, sh: null }); +}); + // Guards the one-source-of-truth contract: the page must carry colormath.js's // body (sans exports) verbatim, so the inlined copy and the tested module cannot // drift. Requires `python3 generate.py` to have run first. diff --git a/scripts/theme-studio/theme-studio.html b/scripts/theme-studio/theme-studio.html index 436e2b03..b85eead8 100644 --- a/scripts/theme-studio/theme-studio.html +++ b/scripts/theme-studio/theme-studio.html @@ -383,6 +383,33 @@ function paletteWarnings(palette, threshold = 0.02, cap = 5) { pairs.sort((a, b) => a.dE - b.dE); return { warnings: pairs.slice(0, cap), overflow: Math.max(0, pairs.length - cap), nearest }; } + +// --- 3D-box relief colors, matching Emacs's renderer --------------------- +// Port of x_alloc_lighter_color (Emacs 30 xterm.c): highlight = bg x 1.2 +// (delta 0x8000), shadow = bg x 0.6 (delta 0x4000), both in 16-bit channel +// space. Backgrounds dimmer than 48000/65535 (by Emacs's 2R+3G+B/6 weighting) +// get an additive boost of delta*dimness*factor/2, because scaling alone +// barely moves a dark color. When the result still equals the background +// (pure black shadow, pure white highlight), Emacs retries with bg+delta. +function reliefColors(bgHex) { + const rgb = hex2rgb(bgHex); + if (rgb.some((c) => Number.isNaN(c))) return { hl: null, sh: null }; + const ch16 = rgb.map((c) => c * 257); + const one = (factor, delta) => { + let nw = ch16.map((c) => Math.min(0xffff, factor * c)); + const bright = (2 * ch16[0] + 3 * ch16[1] + ch16[2]) / 6; + if (bright < 48000) { + const md = delta * (1 - bright / 48000) * factor / 2; + nw = factor < 1 + ? nw.map((v) => Math.max(0, v - md)) + : nw.map((v) => Math.min(0xffff, v + md)); + } + if (nw.every((v, i) => Math.round(v) === ch16[i])) + nw = ch16.map((c) => Math.min(0xffff, c + delta)); + return '#' + nw.map((v) => Math.round(v / 257).toString(16).padStart(2, '0')).join(''); + }; + return { hl: one(1.2, 0x8000), sh: one(0.6, 0x4000) }; +} // Pure package-model + dropdown logic, inlined verbatim from app-core.js. The // wrappers above (pname/seedPkgmap/ddList/pkgEffFg/pkgEffBg) delegate here. // Pure app logic — the package-face model and the dropdown option list — with no @@ -1044,9 +1071,14 @@ function udeco(o){return `font-weight:${o.bold?'bold':'normal'};font-style:${o.i // box-shadow VALUE (or '' for no box). 'line' is a flat border in the box color // (or the face's own color when unset); 'released'/'pressed' are the 3D button // styles Emacs draws, derived from the background so they read on any color. -function boxCss(b){if(!b||!b.style)return '';const w=b.width||1; - if(b.style==='released')return `inset ${w}px ${w}px 0 #ffffff33,inset -${w}px -${w}px 0 #00000066`; - if(b.style==='pressed')return `inset ${w}px ${w}px 0 #00000066,inset -${w}px -${w}px 0 #ffffff33`; +function boxCss(b,bg){if(!b||!b.style)return '';const w=b.width||1; + if(b.style==='released'||b.style==='pressed'){ + // Emacs derives the 3D edges from the face's background (reliefColors, + // ported from xterm.c); the translucent pair is only the no-bg fallback. + const r=bg?reliefColors(bg):{hl:null,sh:null}; + const hl=r.hl||'#ffffff33',sh=r.sh||'#00000066'; + const [a,z]=b.style==='released'?[hl,sh]:[sh,hl]; + return `inset ${w}px ${w}px 0 ${a},inset -${w}px -${w}px 0 ${z}`;} return `inset 0 0 0 ${w}px ${b.color||'currentColor'}`;} // The per-row box control: none / line / raised / pressed. get()/set() read and // write the face's box object (null = no box). @@ -1130,7 +1162,7 @@ function buildMockFrame(){ buf+=`
${L.cont?'↪':''}${i+1}${cd||' '}
`; }); let html=`
${buf}
`; - const mlbx=boxCss(ml.box),mlibx=boxCss(mli.box); + const mlbx=boxCss(ml.box,ml.bg||bg),mlibx=boxCss(mli.box,mli.bg||bg); html+=`
init.el (Emacs Lisp) L5 git:main
`; html+=`
*Messages* (Fundamental)
`; html+=`
I-search: count zzz [no match]
`; @@ -1175,7 +1207,7 @@ function buildPkgTable(){ } applyTableSort('pkgbody'); } -function ofs(app,face){const f=PKGMAP[app][face]||{},fg=effFg(pkgEffFg(app,face)),bg=pkgEffBg(app,face);const dec=(f.underline?'underline ':'')+(f.strike?'line-through':'');const bx=boxCss(f.box);return `color:${fg};${bg?'background:'+bg+';':''}font-weight:${f.bold?'bold':'normal'};font-style:${f.italic?'italic':'normal'};text-decoration:${dec.trim()||'none'};font-size:${(f.height||1)}em${bx?';box-shadow:'+bx:''}`;} +function ofs(app,face){const f=PKGMAP[app][face]||{},fg=effFg(pkgEffFg(app,face)),bg=pkgEffBg(app,face);const dec=(f.underline?'underline ':'')+(f.strike?'line-through':'');const bx=boxCss(f.box,bg||MAP['bg']);return `color:${fg};${bg?'background:'+bg+';':''}font-weight:${f.bold?'bold':'normal'};font-style:${f.italic?'italic':'normal'};text-decoration:${dec.trim()||'none'};font-size:${(f.height||1)}em${bx?';box-shadow:'+bx:''}`;} function os(app,face,txt){return `${txt}`;} function renderOrgPreview(){const a='org-mode',L=[]; L.push(os(a,'org-document-info-keyword','#+TITLE:')+' '+os(a,'org-document-title','Project Notes')); @@ -1499,7 +1531,7 @@ function worstCellHtml(face){ // Repaint every covered overlay face (their floors depend on the syntax palette, // so a syntax-color edit has to refresh them even though it doesn't rebuild the table). function repaintCovered(){COVERED_FACES.forEach(f=>{if(UIMAP[f]&&document.getElementById('uicr-'+f))paintUI(f);});} -function paintUI(face){const pv=document.getElementById('uiprev-'+face);if(!pv)return;const o=UIMAP[face];pv.style.color=effFg(o.fg);pv.style.background=effBg(o.bg);pv.style.fontWeight=o.bold?'bold':'normal';pv.style.fontStyle=o.italic?'italic':'normal';pv.style.textDecoration=(o.underline?'underline ':'')+(o.strike?'line-through':'')||'none';pv.style.boxShadow=boxCss(o.box); +function paintUI(face){const pv=document.getElementById('uiprev-'+face);if(!pv)return;const o=UIMAP[face];pv.style.color=effFg(o.fg);pv.style.background=effBg(o.bg);pv.style.fontWeight=o.bold?'bold':'normal';pv.style.fontStyle=o.italic?'italic':'normal';pv.style.textDecoration=(o.underline?'underline ':'')+(o.strike?'line-through':'')||'none';pv.style.boxShadow=boxCss(o.box,effBg(o.bg)); const cr=document.getElementById('uicr-'+face);if(cr){const w=worstCellHtml(face);if(w!==null){cr.innerHTML=w;}else{const efg=effFg(o.fg),ebg=effBg(o.bg),r=contrast(efg,ebg);cr.innerHTML=crHtml(r);}}} function buildUITable(){ const tb=document.getElementById('uibody');tb.innerHTML=''; @@ -1712,7 +1744,7 @@ if(location.hash==='#contrasttest'){let ok=true;const notes=[];const A=(c,n)=>{i // two-color ratio alone, and must re-rate a ground-dependent face's cell. UIMAP['fringe']={fg:'#ddeeff',bg:null,bold:false,italic:false,underline:false,strike:false}; buildUITable(); - const gb=MAP['bg'];MAP['bg']='#440000';applyGround(); + MAP['bg']='#440000';applyGround(); const pv=document.getElementById('uiprev-mode-line'); A(pv&&pv.style.background==='rgb(170, 187, 204)','ground change keeps a face own preview bg: got '+(pv&&pv.style.background)); const twoAfter=document.getElementById('uicr-mode-line'); @@ -1736,6 +1768,25 @@ if(location.hash==='#contrasttest'){let ok=true;const notes=[];const A=(c,n)=>{i for(const k in MAP)delete MAP[k];Object.assign(MAP,saveMAP);for(const f in UIMAP)delete UIMAP[f];Object.assign(UIMAP,saveUI);buildUITable();applyGround(); document.title='CONTRASTTEST '+(ok?'PASS':'FAIL'); const d=document.createElement('div');d.id='contrasttest';d.textContent='CONTRASTTEST '+(ok?'PASS':'FAIL')+(notes.length?' | '+notes.join(' ; '):'');document.body.appendChild(d);} +// Bevel gate (open with #beveltest): released/pressed boxes derive their +// highlight and shadow from the face's effective bg per Emacs's relief +// algorithm, and pressed draws the shadow edge first. +if(location.hash==='#beveltest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c){ok=false;notes.push(n);}}; + const saveUI=JSON.parse(JSON.stringify(UIMAP)); + UIMAP['mode-line']={fg:'#d8dee9',bg:'#30343c',bold:false,italic:false,underline:false,strike:false,box:{style:'released',width:1,color:null}}; + buildUITable(); + const pv=document.getElementById('uiprev-mode-line'); + const bs=pv&&pv.style.boxShadow; + A(bs&&bs.includes('rgb(113, 118, 127)'),'released highlight derives from the face bg (#71767f): '+bs); + A(bs&&bs.includes('rgb(15, 17, 22)'),'released shadow derives from the face bg (#0f1116): '+bs); + UIMAP['mode-line'].box={style:'pressed',width:1,color:null};paintUI('mode-line'); + const bs2=pv&&pv.style.boxShadow; + A(bs2&&bs2.includes('rgb(15, 17, 22)')&&bs2.includes('rgb(113, 118, 127)')&&bs2.indexOf('rgb(15, 17, 22)'){if(!c){ok=false;notes.push(n);}}; -- cgit v1.2.3