diff options
Diffstat (limited to 'scripts/theme-studio')
| -rw-r--r-- | scripts/theme-studio/app.js | 13 | ||||
| -rw-r--r-- | scripts/theme-studio/browser-gates.js | 5 | ||||
| -rw-r--r-- | scripts/theme-studio/palette-actions.js | 8 | ||||
| -rw-r--r-- | scripts/theme-studio/theme-studio.html | 26 |
4 files changed, 40 insertions, 12 deletions
diff --git a/scripts/theme-studio/app.js b/scripts/theme-studio/app.js index c3a68448..14c94f4b 100644 --- a/scripts/theme-studio/app.js +++ b/scripts/theme-studio/app.js @@ -238,9 +238,10 @@ function paintPicker(){const sv=document.getElementById('sv');if(!sv)return; function pkReadout(h){const e=document.getElementById('pkhex');if(e)e.textContent=h;const c=document.getElementById('pkcon');if(c){const r=contrast(h,MAP['bg']);c.textContent=r.toFixed(1)+' '+rating(r);c.style.color=ratingColor(r);} const o=document.getElementById('pkoklch');if(o){const lch=oklab2oklch(srgb2oklab(h));o.textContent='OKLCH '+lch.L.toFixed(3)+' '+lch.C.toFixed(3)+' '+Math.round(lch.H)+'\u00b0';} const a=document.getElementById('pkapca');if(a){const lc=apca(h,MAP['bg']);a.textContent='APCA Lc '+lc.toFixed(0);a.title='APCA Lc '+lc.toFixed(1)+' (APCA-W3 0.1.9), text on the ground color. Positive = dark text on a light background, negative = light text on a dark background.';}} -function syncHex(){const v=normHex(document.getElementById('newhexstr').value);if(!v)return;document.getElementById('swatch').style.background=v;[pkH,pkS,pkV]=rgb2hsv(...hex2rgb(v));if(pickerOn)paintPicker();pkReadout(v);} -function setHex(h){h=normHex(h)||h;document.getElementById('newhexstr').value=h;document.getElementById('swatch').style.background=h;[pkH,pkS,pkV]=rgb2hsv(...hex2rgb(h));if(pickerOn)paintPicker();pkReadout(h);} -function pkSet(){const hex=rgb2hex(...hsv2rgb(pkH,pkS,pkV));document.getElementById('newhexstr').value=hex;document.getElementById('swatch').style.background=hex;paintPicker();pkReadout(hex);if(pkModel==='oklch')oklchInputsFromHex(hex);} +function previewPickerHex(hex){if(pickerOn&&selectedIdx!==null)previewSelectedChip(hex);} +function syncHex(){const v=normHex(document.getElementById('newhexstr').value);if(!v)return;document.getElementById('swatch').style.background=v;[pkH,pkS,pkV]=rgb2hsv(...hex2rgb(v));if(pickerOn)paintPicker();pkReadout(v);previewPickerHex(v);} +function setHex(h){h=normHex(h)||h;document.getElementById('newhexstr').value=h;document.getElementById('swatch').style.background=h;[pkH,pkS,pkV]=rgb2hsv(...hex2rgb(h));if(pickerOn)paintPicker();pkReadout(h);previewPickerHex(h);} +function pkSet(){const hex=rgb2hex(...hsv2rgb(pkH,pkS,pkV));document.getElementById('newhexstr').value=hex;document.getElementById('swatch').style.background=hex;paintPicker();pkReadout(hex);previewPickerHex(hex);if(pkModel==='oklch')oklchInputsFromHex(hex);} // --- OKLCH editing model (Phase 4a): L/C/H dials orthogonal to the HSV square --- function setOklchInputs(L,C,H){ const put=(id,v)=>{const e=document.getElementById(id);if(e)e.value=v;}; @@ -251,15 +252,15 @@ function readOklch(){return [parseFloat(document.getElementById('okL').value)||0 function pkClampStatus(on){const s=document.getElementById('pkclamp');if(!s)return;s.classList.toggle('show',on);s.textContent=on?'chroma clamped to sRGB':'';} function pkOklchSet(){const [L,C,H]=readOklch();const {hex,clamped}=oklch2hex(L,C,H); document.getElementById('newhexstr').value=hex;document.getElementById('swatch').style.background=hex; - [pkH,pkS,pkV]=rgb2hsv(...hex2rgb(hex));paintPicker();pkReadout(hex); + [pkH,pkS,pkV]=rgb2hsv(...hex2rgb(hex));paintPicker();pkReadout(hex);previewPickerHex(hex); if(clamped)oklchInputsFromHex(hex); // snap the dials to the reachable color pkClampStatus(clamped);} function setPkModel(m){pkModel=m;document.querySelectorAll('.pmodel button').forEach(x=>x.classList.toggle('on',x.dataset.pm===m)); const oc=document.getElementById('oklchctl');if(oc)oc.classList.toggle('show',m==='oklch'); if(m==='oklch')oklchInputsFromHex(curHex());else pkClampStatus(false);} function buildPkChips(){const c=document.getElementById('pkchips');if(!c)return;c.innerHTML='';const T=pkThresh();PALETTE.forEach(([hex,name])=>{const s=document.createElement('div');s.className='pc';s.style.background=hex;s.title=name+' '+hex;const ok=!T||contrast(hex,MAP['bg'])>=T;if(!ok){s.style.opacity='0.22';s.title+=' (below '+pkMode.toUpperCase()+')';}s.onclick=()=>{if(ok)setHex(hex);};c.appendChild(s);});} -function openPicker(){pickerOn=true;[pkH,pkS,pkV]=rgb2hsv(...hex2rgb(curHex()));buildPkChips();document.getElementById('picker').style.display='block';setPkModel(pkModel);paintPicker();pkReadout(curHex());setTimeout(()=>document.addEventListener('pointerdown',pkOutside),0);} -function closePicker(){if(!pickerOn)return;pickerOn=false;const p=document.getElementById('picker');if(p)p.style.display='none';document.removeEventListener('pointerdown',pkOutside);} +function openPicker(){pickerOn=true;[pkH,pkS,pkV]=rgb2hsv(...hex2rgb(curHex()));buildPkChips();document.getElementById('picker').style.display='block';setPkModel(pkModel);paintPicker();pkReadout(curHex());previewPickerHex(curHex());setTimeout(()=>document.addEventListener('pointerdown',pkOutside),0);} +function closePicker(){if(!pickerOn)return;restoreSelectedChip();pickerOn=false;const p=document.getElementById('picker');if(p)p.style.display='none';document.removeEventListener('pointerdown',pkOutside);} function pkOutside(e){if(!e.target.closest('#picker')&&!e.target.closest('#swatch'))closePicker();} function pkDrag(el,fn){el.addEventListener('pointerdown',e=>{e.preventDefault();fn(e);const mv=ev=>fn(ev),up=()=>{document.removeEventListener('pointermove',mv);document.removeEventListener('pointerup',up);};document.addEventListener('pointermove',mv);document.addEventListener('pointerup',up);});} function initPicker(){const sw=document.getElementById('swatch');if(!sw)return;sw.style.background=curHex();sw.onclick=()=>pickerOn?closePicker():openPicker(); diff --git a/scripts/theme-studio/browser-gates.js b/scripts/theme-studio/browser-gates.js index 54f591e9..1e47b5ee 100644 --- a/scripts/theme-studio/browser-gates.js +++ b/scripts/theme-studio/browser-gates.js @@ -333,6 +333,11 @@ if(location.hash==='#columntest'||location.hash==='#familytest'){let ok=true;con if(redChip){ const redName=redChip.querySelector('.nm');selectedIdx=null;redName.click(); A(selectedIdx!==null&&PALETTE[selectedIdx][1]==='red','single-clicking a tile name selects the whole tile'); + const chipHex=chip=>rgb2hex(...getComputedStyle(chip).backgroundColor.match(/\d+/g).slice(0,3).map(Number)); + openPicker();setHex('#00ff00'); + A(chipHex(redChip)==='#00ff00','picker edits preview on the selected palette chip'); + closePicker(); + A(chipHex(redChip)==='#c0402a'&&PALETTE[selectedIdx][0]==='#c0402a','closing picker restores selected chip without mutating palette'); A(redName.readOnly===true&&!redName.classList.contains('editing'),'single-clicking a tile name does not enter name edit mode'); redName.dispatchEvent(new MouseEvent('dblclick',{bubbles:true,cancelable:true})); A(redName.readOnly===false&&redName.classList.contains('editing'),'double-clicking a tile name enters edit mode'); diff --git a/scripts/theme-studio/palette-actions.js b/scripts/theme-studio/palette-actions.js index db91ed0a..e9b68f67 100644 --- a/scripts/theme-studio/palette-actions.js +++ b/scripts/theme-studio/palette-actions.js @@ -87,6 +87,7 @@ function paletteChip(i,nearest){ 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.dataset.paletteIndex=String(i); d.title=name+' '+hex+(nde===Infinity||nde===undefined?'':' — nearest ΔE '+nde.toFixed(3)); 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}" readonly style="color:${tc}"><div class="hx" style="color:${tc}">${hex}</div>`; @@ -101,6 +102,13 @@ function paletteChip(i,nearest){ d.onclick=(e)=>{if(e.target.closest('.rm'))return;selectColor(i);}; return d; } +function setChipPreviewColor(i,hex){ + const chip=document.querySelector('#pals .pchip[data-palette-index="'+i+'"]');if(!chip)return; + const tc=textOn(hex);chip.style.background=hex; + chip.querySelectorAll('.nm,.hx,.rm,.lock').forEach(e=>e.style.color=tc); +} +function previewSelectedChip(hex){if(selectedIdx===null)return;setChipPreviewColor(selectedIdx,hex);} +function restoreSelectedChip(){if(selectedIdx===null||!PALETTE[selectedIdx])return;setChipPreviewColor(selectedIdx,PALETTE[selectedIdx][0]);} function paletteIndexByHexName(hex,name){ for(let i=0;i<PALETTE.length;i++)if(PALETTE[i][0]===hex&&PALETTE[i][1]===name)return i; return -1; diff --git a/scripts/theme-studio/theme-studio.html b/scripts/theme-studio/theme-studio.html index ac8c285f..51c358b8 100644 --- a/scripts/theme-studio/theme-studio.html +++ b/scripts/theme-studio/theme-studio.html @@ -968,6 +968,7 @@ function paletteChip(i,nearest){ 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.dataset.paletteIndex=String(i); d.title=name+' '+hex+(nde===Infinity||nde===undefined?'':' — nearest ΔE '+nde.toFixed(3)); 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}" readonly style="color:${tc}"><div class="hx" style="color:${tc}">${hex}</div>`; @@ -982,6 +983,13 @@ function paletteChip(i,nearest){ d.onclick=(e)=>{if(e.target.closest('.rm'))return;selectColor(i);}; return d; } +function setChipPreviewColor(i,hex){ + const chip=document.querySelector('#pals .pchip[data-palette-index="'+i+'"]');if(!chip)return; + const tc=textOn(hex);chip.style.background=hex; + chip.querySelectorAll('.nm,.hx,.rm,.lock').forEach(e=>e.style.color=tc); +} +function previewSelectedChip(hex){if(selectedIdx===null)return;setChipPreviewColor(selectedIdx,hex);} +function restoreSelectedChip(){if(selectedIdx===null||!PALETTE[selectedIdx])return;setChipPreviewColor(selectedIdx,PALETTE[selectedIdx][0]);} function paletteIndexByHexName(hex,name){ for(let i=0;i<PALETTE.length;i++)if(PALETTE[i][0]===hex&&PALETTE[i][1]===name)return i; return -1; @@ -1190,9 +1198,10 @@ function paintPicker(){const sv=document.getElementById('sv');if(!sv)return; function pkReadout(h){const e=document.getElementById('pkhex');if(e)e.textContent=h;const c=document.getElementById('pkcon');if(c){const r=contrast(h,MAP['bg']);c.textContent=r.toFixed(1)+' '+rating(r);c.style.color=ratingColor(r);} const o=document.getElementById('pkoklch');if(o){const lch=oklab2oklch(srgb2oklab(h));o.textContent='OKLCH '+lch.L.toFixed(3)+' '+lch.C.toFixed(3)+' '+Math.round(lch.H)+'\u00b0';} const a=document.getElementById('pkapca');if(a){const lc=apca(h,MAP['bg']);a.textContent='APCA Lc '+lc.toFixed(0);a.title='APCA Lc '+lc.toFixed(1)+' (APCA-W3 0.1.9), text on the ground color. Positive = dark text on a light background, negative = light text on a dark background.';}} -function syncHex(){const v=normHex(document.getElementById('newhexstr').value);if(!v)return;document.getElementById('swatch').style.background=v;[pkH,pkS,pkV]=rgb2hsv(...hex2rgb(v));if(pickerOn)paintPicker();pkReadout(v);} -function setHex(h){h=normHex(h)||h;document.getElementById('newhexstr').value=h;document.getElementById('swatch').style.background=h;[pkH,pkS,pkV]=rgb2hsv(...hex2rgb(h));if(pickerOn)paintPicker();pkReadout(h);} -function pkSet(){const hex=rgb2hex(...hsv2rgb(pkH,pkS,pkV));document.getElementById('newhexstr').value=hex;document.getElementById('swatch').style.background=hex;paintPicker();pkReadout(hex);if(pkModel==='oklch')oklchInputsFromHex(hex);} +function previewPickerHex(hex){if(pickerOn&&selectedIdx!==null)previewSelectedChip(hex);} +function syncHex(){const v=normHex(document.getElementById('newhexstr').value);if(!v)return;document.getElementById('swatch').style.background=v;[pkH,pkS,pkV]=rgb2hsv(...hex2rgb(v));if(pickerOn)paintPicker();pkReadout(v);previewPickerHex(v);} +function setHex(h){h=normHex(h)||h;document.getElementById('newhexstr').value=h;document.getElementById('swatch').style.background=h;[pkH,pkS,pkV]=rgb2hsv(...hex2rgb(h));if(pickerOn)paintPicker();pkReadout(h);previewPickerHex(h);} +function pkSet(){const hex=rgb2hex(...hsv2rgb(pkH,pkS,pkV));document.getElementById('newhexstr').value=hex;document.getElementById('swatch').style.background=hex;paintPicker();pkReadout(hex);previewPickerHex(hex);if(pkModel==='oklch')oklchInputsFromHex(hex);} // --- OKLCH editing model (Phase 4a): L/C/H dials orthogonal to the HSV square --- function setOklchInputs(L,C,H){ const put=(id,v)=>{const e=document.getElementById(id);if(e)e.value=v;}; @@ -1203,15 +1212,15 @@ function readOklch(){return [parseFloat(document.getElementById('okL').value)||0 function pkClampStatus(on){const s=document.getElementById('pkclamp');if(!s)return;s.classList.toggle('show',on);s.textContent=on?'chroma clamped to sRGB':'';} function pkOklchSet(){const [L,C,H]=readOklch();const {hex,clamped}=oklch2hex(L,C,H); document.getElementById('newhexstr').value=hex;document.getElementById('swatch').style.background=hex; - [pkH,pkS,pkV]=rgb2hsv(...hex2rgb(hex));paintPicker();pkReadout(hex); + [pkH,pkS,pkV]=rgb2hsv(...hex2rgb(hex));paintPicker();pkReadout(hex);previewPickerHex(hex); if(clamped)oklchInputsFromHex(hex); // snap the dials to the reachable color pkClampStatus(clamped);} function setPkModel(m){pkModel=m;document.querySelectorAll('.pmodel button').forEach(x=>x.classList.toggle('on',x.dataset.pm===m)); const oc=document.getElementById('oklchctl');if(oc)oc.classList.toggle('show',m==='oklch'); if(m==='oklch')oklchInputsFromHex(curHex());else pkClampStatus(false);} function buildPkChips(){const c=document.getElementById('pkchips');if(!c)return;c.innerHTML='';const T=pkThresh();PALETTE.forEach(([hex,name])=>{const s=document.createElement('div');s.className='pc';s.style.background=hex;s.title=name+' '+hex;const ok=!T||contrast(hex,MAP['bg'])>=T;if(!ok){s.style.opacity='0.22';s.title+=' (below '+pkMode.toUpperCase()+')';}s.onclick=()=>{if(ok)setHex(hex);};c.appendChild(s);});} -function openPicker(){pickerOn=true;[pkH,pkS,pkV]=rgb2hsv(...hex2rgb(curHex()));buildPkChips();document.getElementById('picker').style.display='block';setPkModel(pkModel);paintPicker();pkReadout(curHex());setTimeout(()=>document.addEventListener('pointerdown',pkOutside),0);} -function closePicker(){if(!pickerOn)return;pickerOn=false;const p=document.getElementById('picker');if(p)p.style.display='none';document.removeEventListener('pointerdown',pkOutside);} +function openPicker(){pickerOn=true;[pkH,pkS,pkV]=rgb2hsv(...hex2rgb(curHex()));buildPkChips();document.getElementById('picker').style.display='block';setPkModel(pkModel);paintPicker();pkReadout(curHex());previewPickerHex(curHex());setTimeout(()=>document.addEventListener('pointerdown',pkOutside),0);} +function closePicker(){if(!pickerOn)return;restoreSelectedChip();pickerOn=false;const p=document.getElementById('picker');if(p)p.style.display='none';document.removeEventListener('pointerdown',pkOutside);} function pkOutside(e){if(!e.target.closest('#picker')&&!e.target.closest('#swatch'))closePicker();} function pkDrag(el,fn){el.addEventListener('pointerdown',e=>{e.preventDefault();fn(e);const mv=ev=>fn(ev),up=()=>{document.removeEventListener('pointermove',mv);document.removeEventListener('pointerup',up);};document.addEventListener('pointermove',mv);document.addEventListener('pointerup',up);});} function initPicker(){const sw=document.getElementById('swatch');if(!sw)return;sw.style.background=curHex();sw.onclick=()=>pickerOn?closePicker():openPicker(); @@ -2096,6 +2105,11 @@ if(location.hash==='#columntest'||location.hash==='#familytest'){let ok=true;con if(redChip){ const redName=redChip.querySelector('.nm');selectedIdx=null;redName.click(); A(selectedIdx!==null&&PALETTE[selectedIdx][1]==='red','single-clicking a tile name selects the whole tile'); + const chipHex=chip=>rgb2hex(...getComputedStyle(chip).backgroundColor.match(/\d+/g).slice(0,3).map(Number)); + openPicker();setHex('#00ff00'); + A(chipHex(redChip)==='#00ff00','picker edits preview on the selected palette chip'); + closePicker(); + A(chipHex(redChip)==='#c0402a'&&PALETTE[selectedIdx][0]==='#c0402a','closing picker restores selected chip without mutating palette'); A(redName.readOnly===true&&!redName.classList.contains('editing'),'single-clicking a tile name does not enter name edit mode'); redName.dispatchEvent(new MouseEvent('dblclick',{bubbles:true,cancelable:true})); A(redName.readOnly===false&&redName.classList.contains('editing'),'double-clicking a tile name enters edit mode'); |
