From 6825bf9f233dbeaa0d2f861ec55629ba5c0d4362 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Sun, 7 Jun 2026 18:11:42 -0500 Subject: feat(theme-selector): click a preview token to flash its assignment row I added the reverse lookup. Clicking a token in the code sample flashes the matching code/color assignment row and scrolls it into view; clicking an element in the mock frame flashes its ui-faces row, or the assignment row when it's a code token. Each token carries its category and each mock element its face as a data attribute, so the lookup survives sorting, reassignment, and palette edits. Going color-first can't tell you which category owns a shared color, since operator, function, and punctuation all read as silver. Clicking the token answers it directly. --- scripts/theme-selector/generate.py | 39 ++++++++++++++++++------------ scripts/theme-selector/theme-selector.html | 39 ++++++++++++++++++------------ 2 files changed, 48 insertions(+), 30 deletions(-) diff --git a/scripts/theme-selector/generate.py b/scripts/theme-selector/generate.py index 2d16b67b..5eac534f 100644 --- a/scripts/theme-selector/generate.py +++ b/scripts/theme-selector/generate.py @@ -74,6 +74,9 @@ HTML = """theme-selector .mock .mbuf{flex:1} .mock .ln{display:flex;align-items:stretch;white-space:pre} .mock .fr{width:10px;flex:0 0 auto} .mock .num{width:36px;flex:0 0 auto;text-align:right;padding-right:10px} .mock .cd{flex:1;padding-left:8px} .mock .bar,.mock .echo{padding:4px 10px;white-space:pre} + #codepre [data-k],.mock [data-k],.mock [data-face]{cursor:pointer} + @keyframes flashcell{0%,55%{background:#e8bd3066}100%{background:transparent}} + tr.flash td{animation:flashcell 1.1s ease-out}

Untitled: theme

@@ -138,15 +141,16 @@ function renderCode(){ for(const line of SAMPLES[lang]){ if(line.length===0){html+='\\n';continue;} for(const [k,t] of line){const c=MAP[k]||'#cdced1';const w=BOLD[k]?'bold':'normal';const s=ITALIC[k]?'italic':'normal'; - html+=`${esc(t)}`;} + html+=`${esc(t)}`;} html+='\\n';} - document.getElementById('codepre').innerHTML=html; + const cp=document.getElementById('codepre');cp.innerHTML=html; + cp.onclick=(e)=>{const s=e.target.closest('[data-k]');if(s)flashAssign(s.dataset.k);}; buildMockFrame(); } function buildTable(){ const tb=document.getElementById('legbody');tb.innerHTML=''; for(const [kind,label,ex] of CATS){ - const tr=document.createElement('tr'); + const tr=document.createElement('tr');tr.dataset.kind=kind; const sel=document.createElement('select');sel.className='chip'; const cur=MAP[kind];const have=PALETTE.some(p=>p[0]===cur); const list=have?PALETTE:[[cur,'(gone) '+cur],...PALETTE]; @@ -219,7 +223,10 @@ function importFile(ev){const f=ev.target.files[0];if(!f)return;const r=new File r.readAsText(f);ev.target.value='';} function applyGround(){document.querySelectorAll('pre').forEach(p=>p.style.background=MAP['bg']);document.querySelectorAll('.ex').forEach(e=>e.style.background=MAP['bg']);} function uf(f){return UIMAP[f]||{};} -function mockSpan(k,t){return `${esc(t)}`;} +function flashRow(tr){if(!tr)return;tr.scrollIntoView({block:'center',behavior:'smooth'});tr.classList.remove('flash');void tr.offsetWidth;tr.classList.add('flash');} +function flashAssign(k){flashRow(document.querySelector(`#legbody tr[data-kind="${k}"]`));} +function flashUi(f){flashRow(document.querySelector(`#uibody tr[data-face="${f}"]`));} +function mockSpan(k,t){return `${esc(t)}`;} function buildMockFrame(){ const fr=document.getElementById('mockframe');if(!fr)return; const bg=MAP['bg'],fg=MAP['p']; @@ -240,19 +247,21 @@ function buildMockFrame(){ const nFg=isc?(lnc.fg||fg):(ln.fg||fg), nBg=isc?(lnc.bg||'transparent'):(ln.bg||'transparent'); const rowBg=isc?(hl.bg||'transparent'):'transparent'; let cd; - if(L.plain&&L.match){cd=`${esc(L.plain)}`;} - else if(L.plain&&L.lazy){cd=`${esc(L.plain)}`;} - else if(L.paren){cd=L.t.map(([k,t],j)=>j===L.t.length-1?`${esc(t)}`:mockSpan(k,t)).join('');} - else{cd=L.t.map(([k,t])=>mockSpan(k,t)).join('');if(L.region)cd=`${cd}`;} - if(isc)cd+=` `; - buf+=`
${i+1}${cd}
`; + if(L.plain&&L.match){cd=`${esc(L.plain)}`;} + else if(L.plain&&L.lazy){cd=`${esc(L.plain)}`;} + else if(L.paren){cd=L.t.map(([k,t],j)=>j===L.t.length-1?`${esc(t)}`:mockSpan(k,t)).join('');} + else{cd=L.t.map(([k,t])=>mockSpan(k,t)).join('');if(L.region)cd=`${cd}`;} + if(isc)cd+=` `; + const nFace=isc?'line-number-current-line':'line-number'; + buf+=`
${i+1}${cd}
`; }); let html=`
${buf}
`; - html+=`
init.el (Emacs Lisp) L3 git:main
`; - html+=`
*Messages* (Fundamental)
`; - html+=`
I-search: count
`; - html+=`
https://gnu.org error warning ok
`; + html+=`
init.el (Emacs Lisp) L3 git:main
`; + html+=`
*Messages* (Fundamental)
`; + html+=`
I-search: count
`; + html+=`
https://gnu.org error warning ok
`; fr.innerHTML=html;fr.style.background=bg;fr.style.color=fg; + fr.onclick=(e)=>{const u=e.target.closest('[data-face]');if(u){flashUi(u.dataset.face);return;}const k=e.target.closest('[data-k]');if(k)flashAssign(k.dataset.k);}; } function uiSelect(face,attr){ const sel=document.createElement('select');sel.className='chip'; @@ -268,7 +277,7 @@ function paintUI(face){const pv=document.getElementById('uiprev-'+face);if(!pv)r function buildUITable(){ const tb=document.getElementById('uibody');tb.innerHTML=''; for(const [face,label,ex] of UI_FACES){ - const tr=document.createElement('tr'); + const tr=document.createElement('tr');tr.dataset.face=face; const c0=document.createElement('td');c0.className='cat';c0.textContent=label; const cF=document.createElement('td');cF.appendChild(uiSelect(face,'fg')); const cB=document.createElement('td');cB.appendChild(uiSelect(face,'bg')); diff --git a/scripts/theme-selector/theme-selector.html b/scripts/theme-selector/theme-selector.html index dc9a7d97..e51687f3 100644 --- a/scripts/theme-selector/theme-selector.html +++ b/scripts/theme-selector/theme-selector.html @@ -34,6 +34,9 @@ .mock .mbuf{flex:1} .mock .ln{display:flex;align-items:stretch;white-space:pre} .mock .fr{width:10px;flex:0 0 auto} .mock .num{width:36px;flex:0 0 auto;text-align:right;padding-right:10px} .mock .cd{flex:1;padding-left:8px} .mock .bar,.mock .echo{padding:4px 10px;white-space:pre} + #codepre [data-k],.mock [data-k],.mock [data-face]{cursor:pointer} + @keyframes flashcell{0%,55%{background:#e8bd3066}100%{background:transparent}} + tr.flash td{animation:flashcell 1.1s ease-out}

Untitled: theme

@@ -98,15 +101,16 @@ function renderCode(){ for(const line of SAMPLES[lang]){ if(line.length===0){html+='\n';continue;} for(const [k,t] of line){const c=MAP[k]||'#cdced1';const w=BOLD[k]?'bold':'normal';const s=ITALIC[k]?'italic':'normal'; - html+=`${esc(t)}`;} + html+=`${esc(t)}`;} html+='\n';} - document.getElementById('codepre').innerHTML=html; + const cp=document.getElementById('codepre');cp.innerHTML=html; + cp.onclick=(e)=>{const s=e.target.closest('[data-k]');if(s)flashAssign(s.dataset.k);}; buildMockFrame(); } function buildTable(){ const tb=document.getElementById('legbody');tb.innerHTML=''; for(const [kind,label,ex] of CATS){ - const tr=document.createElement('tr'); + const tr=document.createElement('tr');tr.dataset.kind=kind; const sel=document.createElement('select');sel.className='chip'; const cur=MAP[kind];const have=PALETTE.some(p=>p[0]===cur); const list=have?PALETTE:[[cur,'(gone) '+cur],...PALETTE]; @@ -179,7 +183,10 @@ function importFile(ev){const f=ev.target.files[0];if(!f)return;const r=new File r.readAsText(f);ev.target.value='';} function applyGround(){document.querySelectorAll('pre').forEach(p=>p.style.background=MAP['bg']);document.querySelectorAll('.ex').forEach(e=>e.style.background=MAP['bg']);} function uf(f){return UIMAP[f]||{};} -function mockSpan(k,t){return `${esc(t)}`;} +function flashRow(tr){if(!tr)return;tr.scrollIntoView({block:'center',behavior:'smooth'});tr.classList.remove('flash');void tr.offsetWidth;tr.classList.add('flash');} +function flashAssign(k){flashRow(document.querySelector(`#legbody tr[data-kind="${k}"]`));} +function flashUi(f){flashRow(document.querySelector(`#uibody tr[data-face="${f}"]`));} +function mockSpan(k,t){return `${esc(t)}`;} function buildMockFrame(){ const fr=document.getElementById('mockframe');if(!fr)return; const bg=MAP['bg'],fg=MAP['p']; @@ -200,19 +207,21 @@ function buildMockFrame(){ const nFg=isc?(lnc.fg||fg):(ln.fg||fg), nBg=isc?(lnc.bg||'transparent'):(ln.bg||'transparent'); const rowBg=isc?(hl.bg||'transparent'):'transparent'; let cd; - if(L.plain&&L.match){cd=`${esc(L.plain)}`;} - else if(L.plain&&L.lazy){cd=`${esc(L.plain)}`;} - else if(L.paren){cd=L.t.map(([k,t],j)=>j===L.t.length-1?`${esc(t)}`:mockSpan(k,t)).join('');} - else{cd=L.t.map(([k,t])=>mockSpan(k,t)).join('');if(L.region)cd=`${cd}`;} - if(isc)cd+=` `; - buf+=`
${i+1}${cd}
`; + if(L.plain&&L.match){cd=`${esc(L.plain)}`;} + else if(L.plain&&L.lazy){cd=`${esc(L.plain)}`;} + else if(L.paren){cd=L.t.map(([k,t],j)=>j===L.t.length-1?`${esc(t)}`:mockSpan(k,t)).join('');} + else{cd=L.t.map(([k,t])=>mockSpan(k,t)).join('');if(L.region)cd=`${cd}`;} + if(isc)cd+=` `; + const nFace=isc?'line-number-current-line':'line-number'; + buf+=`
${i+1}${cd}
`; }); let html=`
${buf}
`; - html+=`
init.el (Emacs Lisp) L3 git:main
`; - html+=`
*Messages* (Fundamental)
`; - html+=`
I-search: count
`; - html+=`
https://gnu.org error warning ok
`; + html+=`
init.el (Emacs Lisp) L3 git:main
`; + html+=`
*Messages* (Fundamental)
`; + html+=`
I-search: count
`; + html+=`
https://gnu.org error warning ok
`; fr.innerHTML=html;fr.style.background=bg;fr.style.color=fg; + fr.onclick=(e)=>{const u=e.target.closest('[data-face]');if(u){flashUi(u.dataset.face);return;}const k=e.target.closest('[data-k]');if(k)flashAssign(k.dataset.k);}; } function uiSelect(face,attr){ const sel=document.createElement('select');sel.className='chip'; @@ -228,7 +237,7 @@ function paintUI(face){const pv=document.getElementById('uiprev-'+face);if(!pv)r function buildUITable(){ const tb=document.getElementById('uibody');tb.innerHTML=''; for(const [face,label,ex] of UI_FACES){ - const tr=document.createElement('tr'); + const tr=document.createElement('tr');tr.dataset.face=face; const c0=document.createElement('td');c0.className='cat';c0.textContent=label; const cF=document.createElement('td');cF.appendChild(uiSelect(face,'fg')); const cB=document.createElement('td');cB.appendChild(uiSelect(face,'bg')); -- cgit v1.2.3