diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-13 16:41:45 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-13 16:41:45 -0500 |
| commit | a2bb7aa580dbc29e8f750cee98bec030ba1dcbce (patch) | |
| tree | 8daddba2e367ca00c8e724542a3d4fab876ecb14 /scripts/theme-studio/theme-studio.html | |
| parent | 8b05b943df3e76d621020d2d5885689f0781f78d (diff) | |
| download | dotemacs-a2bb7aa580dbc29e8f750cee98bec030ba1dcbce.tar.gz dotemacs-a2bb7aa580dbc29e8f750cee98bec030ba1dcbce.zip | |
Fix theme studio bg-like imported colors
Diffstat (limited to 'scripts/theme-studio/theme-studio.html')
| -rw-r--r-- | scripts/theme-studio/theme-studio.html | 64 |
1 files changed, 43 insertions, 21 deletions
diff --git a/scripts/theme-studio/theme-studio.html b/scripts/theme-studio/theme-studio.html index 9cdf58ed..9386ea82 100644 --- a/scripts/theme-studio/theme-studio.html +++ b/scripts/theme-studio/theme-studio.html @@ -551,10 +551,27 @@ function lMax(hue,chroma,fgSet,target){ // assignment re-point across a regenerate. function oklchOf(hex){return oklab2oklch(srgb2oklab(hex));} -function nameOfHex(palette,hex){const p=palette.find(p=>p[0].toLowerCase()===hex.toLowerCase());return p?p[1]:null;} function columnStem(name){name=name||'color';if(/^color-\d+$/.test(name))return name;name=name.replace(/[+-]\d+$/,'');return name.replace(/\d+$/,'')||'color';} function columnOffset(name){const m=(name||'').match(/([+-]\d+)$/);return m?parseInt(m[1],10):0;} function columnIdOf(entry){return (entry&&entry[2])||columnStem(entry&&entry[1]);} +function groundRoleOfEntry(entry,ground){ + if(!entry)return null; + const [hex,name]=entry,col=entry[2],n=(name||'').toLowerCase(),h=(hex||'').toLowerCase(); + const bg=(ground&&ground.bg||'').toLowerCase(),fg=(ground&&ground.fg||'').toLowerCase(); + if(/^ground-\d+$/i.test(name||''))return 'step'; + if(col==='ground'){ + if(bg&&h===bg)return 'bg'; + if(fg&&h===fg)return 'fg'; + return 'step'; + } + if(bg&&h===bg&&(n==='bg'||n==='ground'))return 'bg'; + if(fg&&h===fg&&n==='fg')return 'fg'; + return null; +} +function nameOfGroundRole(palette,ground,role){ + const found=palette.find(entry=>groundRoleOfEntry(entry,ground)===role); + return found?found[1]:null; +} // Group a flat palette into the ground strip plus structural columns. ground is // {bg,fg}; those endpoint hexes form the pinned ground column even when absent @@ -563,15 +580,13 @@ function columnIdOf(entry){return (entry&&entry[2])||columnStem(entry&&entry[1]) // Legacy two-field entries fall back to their generated-name stem until edited. function columnsFromPalette(palette,ground){ const bg=ground&&ground.bg,fg=ground&&ground.fg; - const gset=new Set([bg,fg].filter(Boolean).map(h=>h.toLowerCase())); const groundStrip=[]; - if(bg)groundStrip.push({hex:bg,role:'bg',name:nameOfHex(palette,bg)}); - if(fg)groundStrip.push({hex:fg,role:'fg',name:nameOfHex(palette,fg)}); + if(bg)groundStrip.push({hex:bg,role:'bg',name:nameOfGroundRole(palette,ground,'bg')}); + if(fg)groundStrip.push({hex:fg,role:'fg',name:nameOfGroundRole(palette,ground,'fg')}); const byColumn=new Map(),columns=[]; for(const entry of palette){ const [hex,name]=entry; - if(gset.has(hex.toLowerCase()))continue; - if(/^ground-\d+$/i.test(name||''))continue; + if(groundRoleOfEntry(entry,ground))continue; const column=columnIdOf(entry); if(!byColumn.has(column))byColumn.set(column,{column,members:[]}); byColumn.get(column).members.push({hex,name,offset:columnOffset(name),column}); @@ -633,7 +648,7 @@ function paletteOptionList(cur,palette,ground){ const add=(hex,name)=>{if(!hex)return;const key=hex.toLowerCase()+'|'+(name||'');if(seen.has(key))return;seen.add(key);out.push([hex,name||hex]);}; const grouped=columnsFromPalette(palette,ground||{}); const groundMembers=grouped.ground.map(g=>({hex:g.hex,name:g.name||g.role})) - .concat(palette.filter(([,name])=>/^ground-\d+$/i.test(name||'')).map(([hex,name])=>({hex,name}))); + .concat(palette.filter(entry=>groundRoleOfEntry(entry,ground)==='step').map(([hex,name])=>({hex,name}))); sortColumnMembers({base:(ground&&ground.bg)||'',members:groundMembers}).members.forEach(m=>add(m.hex,m.name)); sortColumns(grouped.columns).forEach(f=>f.members.forEach(m=>add(m.hex,m.name))); return out; @@ -803,12 +818,12 @@ function normalizePalette(){PALETTE=PALETTE.map(normalizePaletteEntry);} // endpoint, and generated ground-N steps live between them. function groundColumnMembers(){ const members=[]; - for(const [hex,name] of PALETTE)if(hex.toLowerCase()===MAP['bg'].toLowerCase()||hex.toLowerCase()===MAP['p'].toLowerCase()||/^ground-\d+$/i.test(name||''))members.push({hex,name}); - if(!members.some(m=>m.hex.toLowerCase()===MAP['bg'].toLowerCase()))members.push({hex:MAP['bg'],name:'bg'}); - if(!members.some(m=>m.hex.toLowerCase()===MAP['p'].toLowerCase()))members.push({hex:MAP['p'],name:'fg'}); + for(const entry of PALETTE)if(groundRoleOfEntry(entry,{bg:MAP['bg'],fg:MAP['p']}))members.push({hex:entry[0],name:entry[1]}); + if(!PALETTE.some(entry=>groundRoleOfEntry(entry,{bg:MAP['bg'],fg:MAP['p']})==='bg'))members.push({hex:MAP['bg'],name:'bg'}); + if(!PALETTE.some(entry=>groundRoleOfEntry(entry,{bg:MAP['bg'],fg:MAP['p']})==='fg'))members.push({hex:MAP['p'],name:'fg'}); return members.sort((a,b)=>oklab2oklch(srgb2oklab(a.hex)).L-oklab2oklch(srgb2oklab(b.hex)).L); } -function groundSpanCount(){return PALETTE.filter(([,name])=>/^ground-\d+$/i.test(name||'')).length;} +function groundSpanCount(){return PALETTE.filter(entry=>groundRoleOfEntry(entry,{bg:MAP['bg'],fg:MAP['p']})==='step').length;} function groundSpanControl(){ const d=document.createElement('div');d.className='fcount'; d.innerHTML=`<span title="number of ground colors between bg and fg">span <input type="number" min="0" max="8" value="${groundSpanCount()}"></span>`; @@ -816,7 +831,7 @@ function groundSpanControl(){ return d; } function setGroundSpan(n){ - const old=PALETTE.filter(([,name])=>/^ground-\d+$/i.test(name||'')); + const old=PALETTE.filter(entry=>groundRoleOfEntry(entry,{bg:MAP['bg'],fg:MAP['p']})==='step'); const bg=srgb2oklab(MAP['bg']),fg=srgb2oklab(MAP['p']); const entries=[]; for(let i=1;i<=n;i++){ @@ -828,8 +843,8 @@ function setGroundSpan(n){ const next=entries.find(([,name])=>name===oldName); if(next&&next[0].toLowerCase()!==oldHex.toLowerCase())repointHex(oldHex,next[0]); } - for(let i=PALETTE.length-1;i>=0;i--)if(/^ground-\d+$/i.test(PALETTE[i][1]||''))PALETTE.splice(i,1); - let at=PALETTE.findIndex(([hex])=>hex.toLowerCase()===MAP['bg'].toLowerCase()); + for(let i=PALETTE.length-1;i>=0;i--)if(groundRoleOfEntry(PALETTE[i],{bg:MAP['bg'],fg:MAP['p']})==='step')PALETTE.splice(i,1); + let at=PALETTE.findIndex(entry=>groundRoleOfEntry(entry,{bg:MAP['bg'],fg:MAP['p']})==='bg'); if(at<0)at=0; else at+=1; PALETTE.splice(Math.min(at,PALETTE.length),0,...entries); selectedIdx=null;renderPalette();buildTable();buildUITable();renderCode();applyGround(); @@ -850,10 +865,11 @@ function renderPaletteWarnings(warnings,overflow){ // Families sort deterministically, so the old move-arrow / drag reordering is gone. function paletteChip(i,nearest){ const [hex,name]=PALETTE[i],tc=textOn(hex),nde=nearest[i]; - const locked=(hex===MAP['bg']||hex===MAP['p']); + const role=groundRoleOfEntry(PALETTE[i],{bg:MAP['bg'],fg:MAP['p']}); + const locked=(role==='bg'||role==='fg'); const d=document.createElement('div');d.className='pchip'+(i===selectedIdx?' sel':'');d.style.background=hex; d.title=name+' '+hex+(nde===Infinity||nde===undefined?'':' — nearest ΔE '+nde.toFixed(3)); - const rm=locked?`<span class="lock" title="${hex===MAP['bg']?'background':'foreground'} — can't remove" style="color:${tc}">🔒</span>`:`<button class="rm" title="remove" style="color:${tc}">×</button>`; + const rm=locked?`<span class="lock" title="${role==='bg'?'background':'foreground'} — can't remove" style="color:${tc}">🔒</span>`:`<button class="rm" title="remove" style="color:${tc}">×</button>`; d.innerHTML=`${rm}<input class="nm" value="${name}" style="color:${tc}"><div class="hx" style="color:${tc}">${hex}</div>`; if(!locked)d.querySelector('.rm').onclick=(e)=>{e.stopPropagation();if(name)lastGone[name.toLowerCase()]=hex;PALETTE.splice(i,1);if(selectedIdx===i)selectedIdx=null;renderPalette();buildTable();buildUITable();}; d.querySelector('.nm').onchange=(e)=>{PALETTE[i][1]=e.target.value;buildTable();buildUITable();}; @@ -870,9 +886,7 @@ function selectColumnBase(f){ if(i>=0)selectColor(i); } function isGroundEntry(entry){ - const [hex,name]=entry; - const lower=(hex||'').toLowerCase(); - return lower===MAP['bg'].toLowerCase()||lower===MAP['p'].toLowerCase()||/^ground-\d+$/i.test(name||''); + return !!groundRoleOfEntry(entry,{bg:MAP['bg'],fg:MAP['p']}); } function moveColumn(columnId,dir){ normalizePalette(); @@ -983,7 +997,7 @@ function applyEdit(){if(selectedIdx!==null)updateColor();else addColor();} function selectColor(i){selectedIdx=i;const [hex,name]=PALETTE[i];setHex(hex);document.getElementById('newname').value=name;renderPalette();notify('editing "'+name+'" — change the value, then Enter (or Update selected) to save',false);} function updateColor(){ if(selectedIdx===null){notify('click a palette color to select it first',true);return;} - const i=selectedIdx,oldHex=PALETTE[i][0]; + const i=selectedIdx,oldHex=PALETTE[i][0],oldRole=groundRoleOfEntry(PALETTE[i],{bg:MAP['bg'],fg:MAP['p']}); const newHex=curHex(); const newName=(document.getElementById('newname').value.trim())||PALETTE[i][1]; if(PALETTE.some((p,j)=>j!==i&&p[1].toLowerCase()===newName.toLowerCase())){notify('another color is already named "'+newName+'" — names must be unique',true);return;} @@ -993,7 +1007,9 @@ function updateColor(){ const count=column?Math.max(0,...rankByLightness(column.members.map(m=>m.hex),column.base).map(m=>Math.abs(m.offset))):0; const columnId=PALETTE[i][2]||columnStem(PALETTE[i][1]); PALETTE[i]=[newHex,newName,columnId]; - repointHex(oldHex,newHex); + const duplicateOldHex=PALETTE.some((p,j)=>j!==i&&p[0].toLowerCase()===oldHex.toLowerCase()); + if(oldRole==='bg'||oldRole==='fg')repointHex(oldHex,newHex); + else if(!duplicateOldHex&&oldHex!==MAP['bg']&&oldHex!==MAP['p'])repointHex(oldHex,newHex); if(column&&count>0){ const oldHexes=column.members.map(m=>m.hex.toLowerCase()===oldHex.toLowerCase()?newHex:m.hex); regenColumnInPlace(oldHexes,newHex,newName,count,column.column||columnId); @@ -1936,6 +1952,12 @@ if(location.hash==='#columntest'||location.hash==='#familytest'){let ok=true;con const ri=PALETTE.findIndex(p=>p[1]==='red');PALETTE[ri][1]='zztop-absurd';renderPalette(); const renamed=[...document.querySelectorAll('#pals .pchip')].find(c=>c.querySelector('.nm')&&c.querySelector('.nm').value==='zztop-absurd'); A(!!renamed&&renamed.closest('.fstrip').dataset.column===redColumn,'a renamed color stays in the same strip'); + PALETTE=[['#0d0b0a','bg','ground'],['#f0fef0','fg','ground'],['#0d0b0a','bg2'],['#0d0b0a','bg-alt']];MAP['bg']='#0d0b0a';MAP['p']='#f0fef0';selectedIdx=null;renderPalette(); + const bg2Chip=[...document.querySelectorAll('#pals .pchip')].find(c=>c.querySelector('.nm')&&c.querySelector('.nm').value==='bg2'); + A(!!bg2Chip&&bg2Chip.closest('.fstrip').dataset.column==='bg'&&!!bg2Chip.querySelector('.rm')&&!bg2Chip.querySelector('.lock'),'same-hex bg2 remains a normal removable color column chip'); + 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(); 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);} |
