diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-13 17:06:39 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-13 17:06:39 -0500 |
| commit | c18d914c138d04157afe64a64b7cd47aaa3171b0 (patch) | |
| tree | b2e14a1848b074e94300303e0a6a528fe1683e35 /scripts | |
| parent | 7726880836bca46c84795728fc61ef69a68835b9 (diff) | |
| download | dotemacs-c18d914c138d04157afe64a64b7cd47aaa3171b0.tar.gz dotemacs-c18d914c138d04157afe64a64b7cd47aaa3171b0.zip | |
Add theme studio palette clear and lock toggles
Diffstat (limited to 'scripts')
| -rw-r--r-- | scripts/theme-studio/app.js | 55 | ||||
| -rw-r--r-- | scripts/theme-studio/theme-studio.html | 61 | ||||
| -rw-r--r-- | scripts/theme-studio/theme-studio.template.html | 6 |
3 files changed, 112 insertions, 10 deletions
diff --git a/scripts/theme-studio/app.js b/scripts/theme-studio/app.js index 50271f1c..25b63d56 100644 --- a/scripts/theme-studio/app.js +++ b/scripts/theme-studio/app.js @@ -83,7 +83,7 @@ function mkLockCell(lockKey,els){ (els||[]).forEach(el=>{if(!el)return; if(el.tagName==='SELECT'||el.tagName==='BUTTON'||el.tagName==='INPUT')el.disabled=on; else{el.dataset.locked=on?'1':'';el.classList.toggle('locked',on);}});} - lk.onclick=()=>{LOCKED.has(lockKey)?LOCKED.delete(lockKey):LOCKED.add(lockKey);paint();}; + lk.onclick=()=>{LOCKED.has(lockKey)?LOCKED.delete(lockKey):LOCKED.add(lockKey);paint();updateLockToggles();}; paint();td.appendChild(lk);return td;} // B/I/U/S style buttons shared by the UI and package tables. isOn(attr) reads the // current state of an attribute, onToggle(attr) flips it and repaints. Returns @@ -100,6 +100,23 @@ function mkStyleButtons(isOn,onToggle){ function clearUnlockedRows(items,keyFn,resetFn){ for(const it of items){const k=keyFn(it);if(k===null)continue;if(!LOCKED.has(k))resetFn(it);} } +function syntaxLockKeys(){return CATS.map(c=>c[0]);} +function uiLockKeys(){return UI_FACES.map(f=>'ui:'+f[0]);} +function pkgLockKeys(){const app=curApp();return APPS[app].faces.map(f=>'pkg:'+app+':'+f[0]);} +function tierLockKeys(tier){return tier==='syntax'?syntaxLockKeys():tier==='ui'?uiLockKeys():pkgLockKeys();} +function updateLockToggle(tier){ + const ids={syntax:'syntaxlocktoggle',ui:'uilocktoggle',pkg:'pkglocktoggle'},b=document.getElementById(ids[tier]);if(!b)return; + const keys=tierLockKeys(tier),all=keys.length&&keys.every(k=>LOCKED.has(k)); + b.textContent=all?'unlock all':'lock all'; +} +function updateLockToggles(){updateLockToggle('syntax');updateLockToggle('ui');updateLockToggle('pkg');} +function toggleAllLocks(tier){ + const keys=tierLockKeys(tier),all=keys.length&&keys.every(k=>LOCKED.has(k)); + keys.forEach(k=>all?LOCKED.delete(k):LOCKED.add(k)); + if(tier==='syntax')buildTable();else if(tier==='ui')buildUITable();else buildPkgTable(); + updateLockToggles(); + notify((all?'unlocked ':'locked ')+(tier==='pkg'?'package':tier)+' rows',false); +} function clearUnlocked(){ clearUnlockedRows(CATS,c=>(c[0]==='bg'||c[0]==='p')?null:c[0],c=>{MAP[c[0]]='';}); buildTable();renderCode();notify('cleared unlocked elements to default',false); @@ -113,6 +130,19 @@ function clearUnlockedPkg(){ clearUnlockedRows(APPS[app].faces,f=>'pkg:'+app+':'+f[0],f=>{PKGMAP[app][f[0]]=normalizePkgFace({source:'cleared'},'cleared');}); pkgChanged();notify('cleared unlocked '+app+' faces to default',false); } +function clearPalette(){ + normalizePalette(); + const keep=[],ground={bg:MAP['bg'],fg:MAP['p']}; + PALETTE.filter(entry=>!groundRoleOfEntry(entry,ground)).forEach(([hex,name])=>{if(name)lastGone[name.toLowerCase()]=hex;}); + const addEndpoint=(role,hex,name)=>{ + const found=PALETTE.find(entry=>groundRoleOfEntry(entry,ground)===role); + keep.push(found||[hex,name,'ground']); + }; + addEndpoint('bg',MAP['bg'],'bg');addEndpoint('fg',MAP['p'],'fg'); + PALETTE=keep;selectedIdx=null; + renderPalette();buildTable();buildUITable();if(document.getElementById('pkgbody'))buildPkgTable();renderCode();applyGround(); + notify('cleared palette to bg and fg',false); +} function buildTable(){ const tb=document.getElementById('legbody');tb.innerHTML=''; for(const [kind,label,ex] of CATS){ @@ -138,6 +168,7 @@ function buildTable(){ const c2=document.createElement('td');c2.className='cat';c2.textContent=label;c2.style.cursor='pointer';c2.title='flash this category in the code';c2.onclick=()=>flashTokens(kind); tr.appendChild(c2);tr.appendChild(lkTd);tr.appendChild(c0);tr.appendChild(stTd);tr.appendChild(crTd);tr.appendChild(exTd); tb.appendChild(tr);} + updateLockToggle('syntax'); } let selectedIdx=null; // When a named palette color is deleted, remember its hex keyed by name so that @@ -644,6 +675,7 @@ function buildPkgTable(){ tr.append(c0,cL,cf,cb,cw,cc,ci,ch,cx,cr);tb.appendChild(tr); } applyTableSort('pkgbody'); + updateLockToggle('pkg'); } 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 `<span data-face="${face}" style="${ofs(app,face)}">${txt}</span>`;} @@ -989,6 +1021,7 @@ function buildUITable(){ tr.appendChild(c0);tr.appendChild(cL);tr.appendChild(cF);tr.appendChild(cB);tr.appendChild(cS);tr.appendChild(cC);tr.appendChild(cP);tr.appendChild(cX);tb.appendChild(tr);paintUI(face); } applyTableSort('uibody'); + updateLockToggle('ui'); } // Generic header-click sort, shared by all three tables. Reads a swatch // dropdown's value, a select value, a numeric input, or cell text (numeric when @@ -1050,6 +1083,15 @@ if(location.hash==='#locktest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c {const app=curApp(),pf=APPS[app].faces.map(r=>r[0]),p1=pf[0],p2=pf[1]; PKGMAP[app][p1].fg='#111111';PKGMAP[app][p2].fg='#222222';LOCKED.clear();LOCKED.add('pkg:'+app+':'+p1);clearUnlockedPkg(); A(PKGMAP[app][p1].fg==='#111111','pkg-clear-keeps-locked');A(PKGMAP[app][p2].fg===null,'pkg-clear-wipes-unlocked');} + {LOCKED.clear();buildTable();const b=document.getElementById('syntaxlocktoggle');A(b&&b.textContent==='lock all','syntax toggle starts as lock all');b.click(); + A(syntaxLockKeys().every(k=>LOCKED.has(k))&&b.textContent==='unlock all','syntax lock-all locks every syntax row and flips label');b.click(); + A(syntaxLockKeys().every(k=>!LOCKED.has(k))&&b.textContent==='lock all','syntax unlock-all clears every syntax lock and flips label');} + {LOCKED.clear();buildUITable();const b=document.getElementById('uilocktoggle');A(b&&b.textContent==='lock all','ui toggle starts as lock all');b.click(); + A(uiLockKeys().every(k=>LOCKED.has(k))&&b.textContent==='unlock all','ui lock-all locks every UI row and flips label');b.click(); + A(uiLockKeys().every(k=>!LOCKED.has(k))&&b.textContent==='lock all','ui unlock-all clears every UI lock and flips label');} + {LOCKED.clear();buildPkgTable();const b=document.getElementById('pkglocktoggle');A(b&&b.textContent==='lock all','pkg toggle starts as lock all');b.click(); + A(pkgLockKeys().every(k=>LOCKED.has(k))&&b.textContent==='unlock all','pkg lock-all locks every current package row and flips label');b.click(); + A(pkgLockKeys().every(k=>!LOCKED.has(k))&&b.textContent==='lock all','pkg unlock-all clears every current package lock and flips label');} document.title='LOCKTEST '+(ok?'PASS':'FAIL'); const d=document.createElement('div');d.id='locktest';d.textContent='LOCKTEST '+(ok?'PASS':'FAIL')+(notes.length?' | '+notes.join(' ; '):'');document.body.appendChild(d);} // Sort gate (open with #sorttest): all three tables now share srtTable/cellVal. @@ -1294,7 +1336,7 @@ if(location.hash==='#healtest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c // ground column plus structural columns, chips keep their controls, and renaming // a color leaves it in the same strip because the column id is stable. if(location.hash==='#columntest'||location.hash==='#familytest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c){ok=false;notes.push(n);}}; - const saveP=PALETTE.slice(),saveM=Object.assign({},MAP),saveSel=selectedIdx; + const saveP=PALETTE.slice(),saveM=Object.assign({},MAP),saveG=Object.assign({},lastGone),saveSel=selectedIdx; MAP['bg']='#0d0b0a';MAP['p']='#f0fef0'; PALETTE=[['#0d0b0a','ground'],['#f0fef0','fg'],['#c0402a','red'],['#3a6ea5','blue'],['#808080','gray']];selectedIdx=null;renderPalette(); const strips=[...document.querySelectorAll('#pals .fstrip')]; @@ -1322,7 +1364,14 @@ if(location.hash==='#columntest'||location.hash==='#familytest'){let ok=true;con if(bg2Chip){bg2Chip.click();document.getElementById('newhexstr').value='#101820';document.getElementById('newname').value='bg2';updateColor();} A(MAP['bg']==='#0d0b0a','editing same-hex bg2 does not repoint the real bg assignment'); A(PALETTE.some(p=>p[1]==='bg2'&&p[0]==='#101820'),'editing same-hex bg2 updates only that palette tile'); - PALETTE=saveP;for(const k in MAP)delete MAP[k];Object.assign(MAP,saveM);selectedIdx=saveSel;renderPalette(); + PALETTE=[['#0d0b0a','bg','ground'],['#f0fef0','fg','ground'],['#c0402a','red','red'],['#3a6ea5','blue','blue'],['#92acc2','blue+1','blue']]; + MAP['kw']='#3a6ea5';selectedIdx=2;clearPalette(); + A(PALETTE.length===2&&PALETTE.every(p=>groundRoleOfEntry(p,{bg:MAP['bg'],fg:MAP['p']})),'clear palette leaves only bg and fg tiles'); + A(!PALETTE.some(p=>p[1]==='red'||p[1]==='blue'||p[1]==='blue+1'),'clear palette removes normal color columns and spans'); + A(MAP['kw']==='#3a6ea5','clear palette leaves existing assignments on gone hexes'); + A(lastGone['blue']==='#3a6ea5'&&lastGone['blue+1']==='#92acc2','clear palette records removed names for recovery'); + A(selectedIdx===null,'clear palette clears selected color'); + PALETTE=saveP;for(const k in MAP)delete MAP[k];Object.assign(MAP,saveM);lastGone=saveG;selectedIdx=saveSel;renderPalette(); document.title='COLUMNTEST '+(ok?'PASS':'FAIL'); const d=document.createElement('div');d.id='columntest';d.textContent='COLUMNTEST '+(ok?'PASS':'FAIL')+(notes.length?' | '+notes.join(' ; '):'');document.body.appendChild(d);} // Count-control gate (open with #counttest): the per-column count regenerates the diff --git a/scripts/theme-studio/theme-studio.html b/scripts/theme-studio/theme-studio.html index 9b1b79da..54e6af71 100644 --- a/scripts/theme-studio/theme-studio.html +++ b/scripts/theme-studio/theme-studio.html @@ -120,6 +120,7 @@ <input type="text" id="newname" placeholder="name" onkeydown="if(event.key==='Enter')applyEdit()"> <button onclick="addColor()">+ add color</button> <button onclick="updateColor()">↻ update selected</button> + <button onclick="clearPalette()" title="remove every palette color except the bg and fg tiles">clear palette</button> <span id="palmsg"></span> <div id="picker" class="picker"> <div class="prow"> @@ -146,7 +147,7 @@ <h1>code/color assignments</h1> <div class="cols"> <section class="pane"> - <div class="legctl"><button class="fbtn" onclick="clearUnlocked()" title="reset every unlocked element to default (reads as plain foreground text); locked rows are left untouched">clear unlocked</button></div> + <div class="legctl"><button id="syntaxlocktoggle" class="fbtn" onclick="toggleAllLocks('syntax')" title="lock or unlock every syntax row">lock all</button><button class="fbtn" onclick="clearUnlocked()" title="reset every unlocked element to default (reads as plain foreground text); locked rows are left untouched">clear unlocked</button></div> <table class="leg" id="legtable"><thead><tr><th onclick="srtTable('legbody',0)">elements △</th><th title="lock a decided element↔color association"></th><th onclick="srtTable('legbody',2)">color △</th><th>style</th><th title="WCAG contrast of this color on the background">contrast</th><th>example</th></tr></thead><tbody id="legbody"></tbody></table> </section> <section class="pane grow"> @@ -157,7 +158,7 @@ <h1>ui faces</h1> <div class="cols stretch"> <section class="pane"> - <div class="legctl"><button class="fbtn" onclick="clearUnlockedUI()" title="reset every unlocked UI face to default (no foreground/background); locked rows are left untouched">clear unlocked</button></div> + <div class="legctl"><button id="uilocktoggle" class="fbtn" onclick="toggleAllLocks('ui')" title="lock or unlock every UI face row">lock all</button><button class="fbtn" onclick="clearUnlockedUI()" title="reset every unlocked UI face to default (no foreground/background); locked rows are left untouched">clear unlocked</button></div> <table class="leg" id="uitable"><thead><tr><th onclick="srtTable('uibody',0)">face △</th><th title="lock a decided face"></th><th onclick="srtTable('uibody',2)">foreground △</th><th onclick="srtTable('uibody',3)">background △</th><th>style</th><th onclick="srtTable('uibody',5)" title="WCAG contrast: this face's foreground on its background (or the ground)">contrast △</th><th>preview</th><th title="face :box (border)">box</th></tr></thead><tbody id="uibody"></tbody></table> </section> <section class="pane grow" style="display:flex;flex-direction:column"> @@ -170,6 +171,7 @@ <label style="color:#b4b1a2">application</label><select id="appsel" class="chip" style="width:auto;font:bold 10pt monospace"></select> <label style="color:#b4b1a2">filter</label><input id="pkgfilter" type="text" placeholder="face name" oninput="buildPkgTable()" style="background:#161412;border:1px solid #252321;color:#cdced1;border-radius:4px;padding:5px 8px;font:10pt monospace;width:160px"> <button onclick="resetApp()">↻ reset all</button> + <button id="pkglocktoggle" class="fbtn" onclick="toggleAllLocks('pkg')" title="lock or unlock every face row in the current package">lock all</button> <button class="fbtn" onclick="clearUnlockedPkg()" title="reset every unlocked face in this app to default (no fg/bg); locked rows are left untouched">clear unlocked</button> </div> <div class="cols stretch"> @@ -739,7 +741,7 @@ function mkLockCell(lockKey,els){ (els||[]).forEach(el=>{if(!el)return; if(el.tagName==='SELECT'||el.tagName==='BUTTON'||el.tagName==='INPUT')el.disabled=on; else{el.dataset.locked=on?'1':'';el.classList.toggle('locked',on);}});} - lk.onclick=()=>{LOCKED.has(lockKey)?LOCKED.delete(lockKey):LOCKED.add(lockKey);paint();}; + lk.onclick=()=>{LOCKED.has(lockKey)?LOCKED.delete(lockKey):LOCKED.add(lockKey);paint();updateLockToggles();}; paint();td.appendChild(lk);return td;} // B/I/U/S style buttons shared by the UI and package tables. isOn(attr) reads the // current state of an attribute, onToggle(attr) flips it and repaints. Returns @@ -756,6 +758,23 @@ function mkStyleButtons(isOn,onToggle){ function clearUnlockedRows(items,keyFn,resetFn){ for(const it of items){const k=keyFn(it);if(k===null)continue;if(!LOCKED.has(k))resetFn(it);} } +function syntaxLockKeys(){return CATS.map(c=>c[0]);} +function uiLockKeys(){return UI_FACES.map(f=>'ui:'+f[0]);} +function pkgLockKeys(){const app=curApp();return APPS[app].faces.map(f=>'pkg:'+app+':'+f[0]);} +function tierLockKeys(tier){return tier==='syntax'?syntaxLockKeys():tier==='ui'?uiLockKeys():pkgLockKeys();} +function updateLockToggle(tier){ + const ids={syntax:'syntaxlocktoggle',ui:'uilocktoggle',pkg:'pkglocktoggle'},b=document.getElementById(ids[tier]);if(!b)return; + const keys=tierLockKeys(tier),all=keys.length&&keys.every(k=>LOCKED.has(k)); + b.textContent=all?'unlock all':'lock all'; +} +function updateLockToggles(){updateLockToggle('syntax');updateLockToggle('ui');updateLockToggle('pkg');} +function toggleAllLocks(tier){ + const keys=tierLockKeys(tier),all=keys.length&&keys.every(k=>LOCKED.has(k)); + keys.forEach(k=>all?LOCKED.delete(k):LOCKED.add(k)); + if(tier==='syntax')buildTable();else if(tier==='ui')buildUITable();else buildPkgTable(); + updateLockToggles(); + notify((all?'unlocked ':'locked ')+(tier==='pkg'?'package':tier)+' rows',false); +} function clearUnlocked(){ clearUnlockedRows(CATS,c=>(c[0]==='bg'||c[0]==='p')?null:c[0],c=>{MAP[c[0]]='';}); buildTable();renderCode();notify('cleared unlocked elements to default',false); @@ -769,6 +788,19 @@ function clearUnlockedPkg(){ clearUnlockedRows(APPS[app].faces,f=>'pkg:'+app+':'+f[0],f=>{PKGMAP[app][f[0]]=normalizePkgFace({source:'cleared'},'cleared');}); pkgChanged();notify('cleared unlocked '+app+' faces to default',false); } +function clearPalette(){ + normalizePalette(); + const keep=[],ground={bg:MAP['bg'],fg:MAP['p']}; + PALETTE.filter(entry=>!groundRoleOfEntry(entry,ground)).forEach(([hex,name])=>{if(name)lastGone[name.toLowerCase()]=hex;}); + const addEndpoint=(role,hex,name)=>{ + const found=PALETTE.find(entry=>groundRoleOfEntry(entry,ground)===role); + keep.push(found||[hex,name,'ground']); + }; + addEndpoint('bg',MAP['bg'],'bg');addEndpoint('fg',MAP['p'],'fg'); + PALETTE=keep;selectedIdx=null; + renderPalette();buildTable();buildUITable();if(document.getElementById('pkgbody'))buildPkgTable();renderCode();applyGround(); + notify('cleared palette to bg and fg',false); +} function buildTable(){ const tb=document.getElementById('legbody');tb.innerHTML=''; for(const [kind,label,ex] of CATS){ @@ -794,6 +826,7 @@ function buildTable(){ const c2=document.createElement('td');c2.className='cat';c2.textContent=label;c2.style.cursor='pointer';c2.title='flash this category in the code';c2.onclick=()=>flashTokens(kind); tr.appendChild(c2);tr.appendChild(lkTd);tr.appendChild(c0);tr.appendChild(stTd);tr.appendChild(crTd);tr.appendChild(exTd); tb.appendChild(tr);} + updateLockToggle('syntax'); } let selectedIdx=null; // When a named palette color is deleted, remember its hex keyed by name so that @@ -1300,6 +1333,7 @@ function buildPkgTable(){ tr.append(c0,cL,cf,cb,cw,cc,ci,ch,cx,cr);tb.appendChild(tr); } applyTableSort('pkgbody'); + updateLockToggle('pkg'); } 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 `<span data-face="${face}" style="${ofs(app,face)}">${txt}</span>`;} @@ -1645,6 +1679,7 @@ function buildUITable(){ tr.appendChild(c0);tr.appendChild(cL);tr.appendChild(cF);tr.appendChild(cB);tr.appendChild(cS);tr.appendChild(cC);tr.appendChild(cP);tr.appendChild(cX);tb.appendChild(tr);paintUI(face); } applyTableSort('uibody'); + updateLockToggle('ui'); } // Generic header-click sort, shared by all three tables. Reads a swatch // dropdown's value, a select value, a numeric input, or cell text (numeric when @@ -1706,6 +1741,15 @@ if(location.hash==='#locktest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c {const app=curApp(),pf=APPS[app].faces.map(r=>r[0]),p1=pf[0],p2=pf[1]; PKGMAP[app][p1].fg='#111111';PKGMAP[app][p2].fg='#222222';LOCKED.clear();LOCKED.add('pkg:'+app+':'+p1);clearUnlockedPkg(); A(PKGMAP[app][p1].fg==='#111111','pkg-clear-keeps-locked');A(PKGMAP[app][p2].fg===null,'pkg-clear-wipes-unlocked');} + {LOCKED.clear();buildTable();const b=document.getElementById('syntaxlocktoggle');A(b&&b.textContent==='lock all','syntax toggle starts as lock all');b.click(); + A(syntaxLockKeys().every(k=>LOCKED.has(k))&&b.textContent==='unlock all','syntax lock-all locks every syntax row and flips label');b.click(); + A(syntaxLockKeys().every(k=>!LOCKED.has(k))&&b.textContent==='lock all','syntax unlock-all clears every syntax lock and flips label');} + {LOCKED.clear();buildUITable();const b=document.getElementById('uilocktoggle');A(b&&b.textContent==='lock all','ui toggle starts as lock all');b.click(); + A(uiLockKeys().every(k=>LOCKED.has(k))&&b.textContent==='unlock all','ui lock-all locks every UI row and flips label');b.click(); + A(uiLockKeys().every(k=>!LOCKED.has(k))&&b.textContent==='lock all','ui unlock-all clears every UI lock and flips label');} + {LOCKED.clear();buildPkgTable();const b=document.getElementById('pkglocktoggle');A(b&&b.textContent==='lock all','pkg toggle starts as lock all');b.click(); + A(pkgLockKeys().every(k=>LOCKED.has(k))&&b.textContent==='unlock all','pkg lock-all locks every current package row and flips label');b.click(); + A(pkgLockKeys().every(k=>!LOCKED.has(k))&&b.textContent==='lock all','pkg unlock-all clears every current package lock and flips label');} document.title='LOCKTEST '+(ok?'PASS':'FAIL'); const d=document.createElement('div');d.id='locktest';d.textContent='LOCKTEST '+(ok?'PASS':'FAIL')+(notes.length?' | '+notes.join(' ; '):'');document.body.appendChild(d);} // Sort gate (open with #sorttest): all three tables now share srtTable/cellVal. @@ -1950,7 +1994,7 @@ if(location.hash==='#healtest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c // ground column plus structural columns, chips keep their controls, and renaming // a color leaves it in the same strip because the column id is stable. if(location.hash==='#columntest'||location.hash==='#familytest'){let ok=true;const notes=[];const A=(c,n)=>{if(!c){ok=false;notes.push(n);}}; - const saveP=PALETTE.slice(),saveM=Object.assign({},MAP),saveSel=selectedIdx; + const saveP=PALETTE.slice(),saveM=Object.assign({},MAP),saveG=Object.assign({},lastGone),saveSel=selectedIdx; MAP['bg']='#0d0b0a';MAP['p']='#f0fef0'; PALETTE=[['#0d0b0a','ground'],['#f0fef0','fg'],['#c0402a','red'],['#3a6ea5','blue'],['#808080','gray']];selectedIdx=null;renderPalette(); const strips=[...document.querySelectorAll('#pals .fstrip')]; @@ -1978,7 +2022,14 @@ if(location.hash==='#columntest'||location.hash==='#familytest'){let ok=true;con if(bg2Chip){bg2Chip.click();document.getElementById('newhexstr').value='#101820';document.getElementById('newname').value='bg2';updateColor();} A(MAP['bg']==='#0d0b0a','editing same-hex bg2 does not repoint the real bg assignment'); A(PALETTE.some(p=>p[1]==='bg2'&&p[0]==='#101820'),'editing same-hex bg2 updates only that palette tile'); - PALETTE=saveP;for(const k in MAP)delete MAP[k];Object.assign(MAP,saveM);selectedIdx=saveSel;renderPalette(); + PALETTE=[['#0d0b0a','bg','ground'],['#f0fef0','fg','ground'],['#c0402a','red','red'],['#3a6ea5','blue','blue'],['#92acc2','blue+1','blue']]; + MAP['kw']='#3a6ea5';selectedIdx=2;clearPalette(); + A(PALETTE.length===2&&PALETTE.every(p=>groundRoleOfEntry(p,{bg:MAP['bg'],fg:MAP['p']})),'clear palette leaves only bg and fg tiles'); + A(!PALETTE.some(p=>p[1]==='red'||p[1]==='blue'||p[1]==='blue+1'),'clear palette removes normal color columns and spans'); + A(MAP['kw']==='#3a6ea5','clear palette leaves existing assignments on gone hexes'); + A(lastGone['blue']==='#3a6ea5'&&lastGone['blue+1']==='#92acc2','clear palette records removed names for recovery'); + A(selectedIdx===null,'clear palette clears selected color'); + PALETTE=saveP;for(const k in MAP)delete MAP[k];Object.assign(MAP,saveM);lastGone=saveG;selectedIdx=saveSel;renderPalette(); document.title='COLUMNTEST '+(ok?'PASS':'FAIL'); const d=document.createElement('div');d.id='columntest';d.textContent='COLUMNTEST '+(ok?'PASS':'FAIL')+(notes.length?' | '+notes.join(' ; '):'');document.body.appendChild(d);} // Count-control gate (open with #counttest): the per-column count regenerates the diff --git a/scripts/theme-studio/theme-studio.template.html b/scripts/theme-studio/theme-studio.template.html index 7ad576da..bc043317 100644 --- a/scripts/theme-studio/theme-studio.template.html +++ b/scripts/theme-studio/theme-studio.template.html @@ -22,6 +22,7 @@ STYLES_CSS</style> <input type="text" id="newname" placeholder="name" onkeydown="if(event.key==='Enter')applyEdit()"> <button onclick="addColor()">+ add color</button> <button onclick="updateColor()">↻ update selected</button> + <button onclick="clearPalette()" title="remove every palette color except the bg and fg tiles">clear palette</button> <span id="palmsg"></span> <div id="picker" class="picker"> <div class="prow"> @@ -48,7 +49,7 @@ STYLES_CSS</style> <h1>code/color assignments</h1> <div class="cols"> <section class="pane"> - <div class="legctl"><button class="fbtn" onclick="clearUnlocked()" title="reset every unlocked element to default (reads as plain foreground text); locked rows are left untouched">clear unlocked</button></div> + <div class="legctl"><button id="syntaxlocktoggle" class="fbtn" onclick="toggleAllLocks('syntax')" title="lock or unlock every syntax row">lock all</button><button class="fbtn" onclick="clearUnlocked()" title="reset every unlocked element to default (reads as plain foreground text); locked rows are left untouched">clear unlocked</button></div> <table class="leg" id="legtable"><thead><tr><th onclick="srtTable('legbody',0)">elements △</th><th title="lock a decided element↔color association"></th><th onclick="srtTable('legbody',2)">color △</th><th>style</th><th title="WCAG contrast of this color on the background">contrast</th><th>example</th></tr></thead><tbody id="legbody"></tbody></table> </section> <section class="pane grow"> @@ -59,7 +60,7 @@ STYLES_CSS</style> <h1>ui faces</h1> <div class="cols stretch"> <section class="pane"> - <div class="legctl"><button class="fbtn" onclick="clearUnlockedUI()" title="reset every unlocked UI face to default (no foreground/background); locked rows are left untouched">clear unlocked</button></div> + <div class="legctl"><button id="uilocktoggle" class="fbtn" onclick="toggleAllLocks('ui')" title="lock or unlock every UI face row">lock all</button><button class="fbtn" onclick="clearUnlockedUI()" title="reset every unlocked UI face to default (no foreground/background); locked rows are left untouched">clear unlocked</button></div> <table class="leg" id="uitable"><thead><tr><th onclick="srtTable('uibody',0)">face △</th><th title="lock a decided face"></th><th onclick="srtTable('uibody',2)">foreground △</th><th onclick="srtTable('uibody',3)">background △</th><th>style</th><th onclick="srtTable('uibody',5)" title="WCAG contrast: this face's foreground on its background (or the ground)">contrast △</th><th>preview</th><th title="face :box (border)">box</th></tr></thead><tbody id="uibody"></tbody></table> </section> <section class="pane grow" style="display:flex;flex-direction:column"> @@ -72,6 +73,7 @@ STYLES_CSS</style> <label style="color:#b4b1a2">application</label><select id="appsel" class="chip" style="width:auto;font:bold 10pt monospace"></select> <label style="color:#b4b1a2">filter</label><input id="pkgfilter" type="text" placeholder="face name" oninput="buildPkgTable()" style="background:#161412;border:1px solid #252321;color:#cdced1;border-radius:4px;padding:5px 8px;font:10pt monospace;width:160px"> <button onclick="resetApp()">↻ reset all</button> + <button id="pkglocktoggle" class="fbtn" onclick="toggleAllLocks('pkg')" title="lock or unlock every face row in the current package">lock all</button> <button class="fbtn" onclick="clearUnlockedPkg()" title="reset every unlocked face in this app to default (no fg/bg); locked rows are left untouched">clear unlocked</button> </div> <div class="cols stretch"> |
