From 1b58bd9bf5d5d9e41bccca0b476bd13bc83a5084 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Mon, 8 Jun 2026 08:59:14 -0500 Subject: fix(theme-studio): make a face-row click visibly flash its preview element Clicking a UI or package face already called the flash, but two things hid it: the flashtok animation was scoped to #codepre and .ex cells, so the class landed on the mock-frame and package-preview spans without ever animating; and the flash never scrolled its target into view, so an element below the fold of a preview's scroll box flashed unseen. The animation is now global, and the flash scrolls its element into view (the first of several, via a new flashEls helper). Clicking a face in either tier now lands the viewport on the matching preview element and lights it up. --- scripts/theme-studio/generate.py | 13 ++++++++----- scripts/theme-studio/theme-studio.html | 13 ++++++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/scripts/theme-studio/generate.py b/scripts/theme-studio/generate.py index 405ce150..c34b34b6 100644 --- a/scripts/theme-studio/generate.py +++ b/scripts/theme-studio/generate.py @@ -429,7 +429,7 @@ HTML = """theme-studio @keyframes flashcell{0%,55%{background:#e8bd3066}100%{background:transparent}} tr.flash td{animation:flashcell 1.1s ease-out} @keyframes flashtok{0%,55%{background:#e8bd30aa;color:#000}100%{background:transparent}} - #codepre .flashtok,.ex.flashtok{animation:flashtok 1.1s ease-out;border-radius:2px} + .flashtok{animation:flashtok 1.1s ease-out;border-radius:2px}

Untitled: theme

@@ -662,13 +662,16 @@ function applyGround(){document.querySelectorAll('pre').forEach(p=>p.style.backg function uf(f){return UIMAP[f]||{};} function udeco(o){return `font-weight:${o.bold?'bold':'normal'};font-style:${o.italic?'italic':'normal'};text-decoration:${(o.underline?'underline ':'')+(o.strike?'line-through':'')||'none'}`;} function flashRow(tr){if(!tr)return;tr.scrollIntoView({block:'center',behavior:'smooth'});tr.classList.remove('flash');void tr.offsetWidth;tr.classList.add('flash');} -function flashEl(el){if(!el)return;el.classList.remove('flashtok');void el.offsetWidth;el.classList.add('flashtok');} -function flashTokens(kind){const sp=document.querySelectorAll('#codepre [data-k="'+kind+'"]');if(sp.length){sp.forEach(flashEl);return;}const row=document.querySelector('#legbody tr[data-kind="'+kind+'"]');if(row)flashEl(row.querySelector('.ex'));} +function flashEl(el){if(!el)return;el.scrollIntoView({block:'nearest',inline:'nearest',behavior:'smooth'});el.classList.remove('flashtok');void el.offsetWidth;el.classList.add('flashtok');} +// Flash every matching element but scroll only the first into view, so a face +// that maps to several preview spans still lands the viewport on the first. +function flashEls(els){els=[...els];if(!els.length)return;els[0].scrollIntoView({block:'nearest',inline:'nearest',behavior:'smooth'});els.forEach(el=>{el.classList.remove('flashtok');void el.offsetWidth;el.classList.add('flashtok');});} +function flashTokens(kind){const sp=document.querySelectorAll('#codepre [data-k="'+kind+'"]');if(sp.length){flashEls(sp);return;}const row=document.querySelector('#legbody tr[data-kind="'+kind+'"]');if(row)flashEl(row.querySelector('.ex'));} function flashAssign(k){flashRow(document.querySelector(`#legbody tr[data-kind="${k}"]`));} function flashUi(f){flashRow(document.querySelector(`#uibody tr[data-face="${f}"]`));} -function flashUiPreview(f){const sp=document.querySelectorAll(`#mockframe [data-face="${f}"]`);if(sp.length){sp.forEach(flashEl);return;}const cell=document.getElementById('uiprev-'+f);if(cell)flashEl(cell);} +function flashUiPreview(f){const sp=document.querySelectorAll(`#mockframe [data-face="${f}"]`);if(sp.length){flashEls(sp);return;}const cell=document.getElementById('uiprev-'+f);if(cell)flashEl(cell);} function flashPkg(f){flashRow(document.querySelector(`#pkgbody tr[data-face="${f}"]`));} -function flashPkgPreview(f){const sp=document.querySelectorAll(`#pkgpreview [data-face="${f}"]`);if(sp.length){sp.forEach(flashEl);return;}const row=document.querySelector(`#pkgbody tr[data-face="${f}"]`);if(row)flashEl(row.querySelector('.cat'));} +function flashPkgPreview(f){const sp=document.querySelectorAll(`#pkgpreview [data-face="${f}"]`);if(sp.length){flashEls(sp);return;}const row=document.querySelector(`#pkgbody tr[data-face="${f}"]`);if(row)flashEl(row.querySelector('.cat'));} function mockSpan(k,t){return `${esc(t)}`;} function syncMockHeight(){const t=document.getElementById('uitable'),m=document.getElementById('mockframe');if(!t||!m)return;const lb=m.previousElementSibling,lbh=lb?lb.getBoundingClientRect().height+10:30;m.style.height=Math.max(t.getBoundingClientRect().height-lbh,220)+'px';} function buildMockFrame(){ diff --git a/scripts/theme-studio/theme-studio.html b/scripts/theme-studio/theme-studio.html index 61466d42..37be4128 100644 --- a/scripts/theme-studio/theme-studio.html +++ b/scripts/theme-studio/theme-studio.html @@ -58,7 +58,7 @@ @keyframes flashcell{0%,55%{background:#e8bd3066}100%{background:transparent}} tr.flash td{animation:flashcell 1.1s ease-out} @keyframes flashtok{0%,55%{background:#e8bd30aa;color:#000}100%{background:transparent}} - #codepre .flashtok,.ex.flashtok{animation:flashtok 1.1s ease-out;border-radius:2px} + .flashtok{animation:flashtok 1.1s ease-out;border-radius:2px}

Untitled: theme

@@ -291,13 +291,16 @@ function applyGround(){document.querySelectorAll('pre').forEach(p=>p.style.backg function uf(f){return UIMAP[f]||{};} function udeco(o){return `font-weight:${o.bold?'bold':'normal'};font-style:${o.italic?'italic':'normal'};text-decoration:${(o.underline?'underline ':'')+(o.strike?'line-through':'')||'none'}`;} function flashRow(tr){if(!tr)return;tr.scrollIntoView({block:'center',behavior:'smooth'});tr.classList.remove('flash');void tr.offsetWidth;tr.classList.add('flash');} -function flashEl(el){if(!el)return;el.classList.remove('flashtok');void el.offsetWidth;el.classList.add('flashtok');} -function flashTokens(kind){const sp=document.querySelectorAll('#codepre [data-k="'+kind+'"]');if(sp.length){sp.forEach(flashEl);return;}const row=document.querySelector('#legbody tr[data-kind="'+kind+'"]');if(row)flashEl(row.querySelector('.ex'));} +function flashEl(el){if(!el)return;el.scrollIntoView({block:'nearest',inline:'nearest',behavior:'smooth'});el.classList.remove('flashtok');void el.offsetWidth;el.classList.add('flashtok');} +// Flash every matching element but scroll only the first into view, so a face +// that maps to several preview spans still lands the viewport on the first. +function flashEls(els){els=[...els];if(!els.length)return;els[0].scrollIntoView({block:'nearest',inline:'nearest',behavior:'smooth'});els.forEach(el=>{el.classList.remove('flashtok');void el.offsetWidth;el.classList.add('flashtok');});} +function flashTokens(kind){const sp=document.querySelectorAll('#codepre [data-k="'+kind+'"]');if(sp.length){flashEls(sp);return;}const row=document.querySelector('#legbody tr[data-kind="'+kind+'"]');if(row)flashEl(row.querySelector('.ex'));} function flashAssign(k){flashRow(document.querySelector(`#legbody tr[data-kind="${k}"]`));} function flashUi(f){flashRow(document.querySelector(`#uibody tr[data-face="${f}"]`));} -function flashUiPreview(f){const sp=document.querySelectorAll(`#mockframe [data-face="${f}"]`);if(sp.length){sp.forEach(flashEl);return;}const cell=document.getElementById('uiprev-'+f);if(cell)flashEl(cell);} +function flashUiPreview(f){const sp=document.querySelectorAll(`#mockframe [data-face="${f}"]`);if(sp.length){flashEls(sp);return;}const cell=document.getElementById('uiprev-'+f);if(cell)flashEl(cell);} function flashPkg(f){flashRow(document.querySelector(`#pkgbody tr[data-face="${f}"]`));} -function flashPkgPreview(f){const sp=document.querySelectorAll(`#pkgpreview [data-face="${f}"]`);if(sp.length){sp.forEach(flashEl);return;}const row=document.querySelector(`#pkgbody tr[data-face="${f}"]`);if(row)flashEl(row.querySelector('.cat'));} +function flashPkgPreview(f){const sp=document.querySelectorAll(`#pkgpreview [data-face="${f}"]`);if(sp.length){flashEls(sp);return;}const row=document.querySelector(`#pkgbody tr[data-face="${f}"]`);if(row)flashEl(row.querySelector('.cat'));} function mockSpan(k,t){return `${esc(t)}`;} function syncMockHeight(){const t=document.getElementById('uitable'),m=document.getElementById('mockframe');if(!t||!m)return;const lb=m.previousElementSibling,lbh=lb?lb.getBoundingClientRect().height+10:30;m.style.height=Math.max(t.getBoundingClientRect().height-lbh,220)+'px';} function buildMockFrame(){ -- cgit v1.2.3