diff options
Diffstat (limited to 'scripts/theme-studio')
| -rw-r--r-- | scripts/theme-studio/README.md | 11 | ||||
| -rw-r--r-- | scripts/theme-studio/app.js | 23 | ||||
| -rwxr-xr-x | scripts/theme-studio/screenshot-previews.sh | 51 | ||||
| -rw-r--r-- | scripts/theme-studio/theme-studio.html | 23 |
4 files changed, 108 insertions, 0 deletions
diff --git a/scripts/theme-studio/README.md b/scripts/theme-studio/README.md index df3d9260..da9c7823 100644 --- a/scripts/theme-studio/README.md +++ b/scripts/theme-studio/README.md @@ -8,6 +8,17 @@ Reassign colors against the palette, judge legibility with live WCAG-contrast readouts, then export a `theme.json` that a build step turns into `themes/<name>-*.el`. +## Coverage policy + +The studio themes popular packages, not just installed ones. The goal is +generic themes that cover the wider ecosystem, so a package's face +definitions stay in the studio even when the package is removed from this +config (ghostel and all-the-icons are the standing examples). Never drop an +app because its package is uninstalled; pin its face list instead so it +survives inventory regeneration. Unloaded packages get marked as such in the +UI, and their live previews matter *more* than the loaded ones' — the studio +preview is the only place their theming can be seen at all. + For the color-assignment philosophy behind the tool — how to group syntax roles, what to share, where to spend chroma and bold — see [`theme-coloring-guide.org`](theme-coloring-guide.org). diff --git a/scripts/theme-studio/app.js b/scripts/theme-studio/app.js index 7f846e6c..24fd120d 100644 --- a/scripts/theme-studio/app.js +++ b/scripts/theme-studio/app.js @@ -727,4 +727,27 @@ function initApp(){ } initApp(); addEventListener('resize',()=>{syncPaneHeight('uitable','mockframe');syncPaneHeight('pkgtable','pkgpreview');}); +// #preview=<app-key>[&theme=<json-url>]: select that app on load and hide the +// topbar + palette so its face table + live preview render from the top of the +// page (headless screenshots can't scroll reliably). An optional theme URL is +// fetched and imported first, so shots show a real theme instead of untitled +// (fresh headless profiles have no localStorage; Chrome needs +// --allow-file-access-from-files for a file:// fetch). Drives the screenshot +// harness (screenshot-previews.sh), same hash-URL pattern as the browser gates. +// The title flip lets the harness confirm the selection landed. +if(location.hash.startsWith('#preview=')){ + const q=location.hash.slice(9).split('&theme='); + const k=decodeURIComponent(q[0]); + const showApp=()=>{ + if(!APPS[k])return; + const s=document.getElementById('viewsel'); + if(!s)return; + s.value=k;onViewChange();document.title='PREVIEW '+k; + document.querySelectorAll('.topbar, body > section') + .forEach(e=>{e.style.display='none';});}; + if(q[1])fetch(decodeURIComponent(q[1])).then(r=>r.text()) + .then(t=>{applyImported(t);showApp();}) + .catch(()=>{document.title='PREVIEW THEME-LOAD-FAILED';showApp();}); + else showApp(); +} BROWSER_GATES_J diff --git a/scripts/theme-studio/screenshot-previews.sh b/scripts/theme-studio/screenshot-previews.sh new file mode 100755 index 00000000..72894aa7 --- /dev/null +++ b/scripts/theme-studio/screenshot-previews.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# Screenshot each theme-studio app's page (face table + live preview) headlessly. +# +# ./screenshot-previews.sh OUTDIR [APP ...] +# THEME=dupre.json ./screenshot-previews.sh OUTDIR [APP ...] +# +# With no APP arguments, shoots every app in the studio. Rides the #preview=<app> +# hash handler in app.js (the same hash-URL pattern as the browser gates), so the +# page selects the app itself before Chrome takes the shot. THEME names a theme +# JSON next to this script (default dupre.json; THEME= empty shoots untitled) so +# shots show real theme colors -- fresh headless profiles carry no localStorage. +# Regenerate the page first (make gen) when sources changed; this script shoots +# what is on disk. +set -uo pipefail + +HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +OUT="${1:?usage: screenshot-previews.sh OUTDIR [APP ...]}" +shift || true +mkdir -p "$OUT" + +BROWSER="" +for b in google-chrome-stable google-chrome chromium chromium-browser; do + command -v "$b" >/dev/null 2>&1 && { BROWSER="$b"; break; } +done +[ -n "$BROWSER" ] || { echo "no Chromium-family browser found" >&2; exit 1; } + +if [ "$#" -gt 0 ]; then + APPS="$*" +else + APPS="$(cd "$HERE" && python3 -c 'import generate; print(" ".join(sorted(generate.APPS)))')" +fi + +THEME="${THEME-WIP.json}" +SUFFIX="" +if [ -n "$THEME" ]; then + [ -f "$HERE/$THEME" ] || { echo "theme not found: $HERE/$THEME" >&2; exit 1; } + SUFFIX="&theme=$THEME" +fi + +for app in $APPS; do + "$BROWSER" --headless=new --disable-gpu --hide-scrollbars \ + --allow-file-access-from-files \ + --window-size=1500,2000 --virtual-time-budget=6000 \ + --screenshot="$OUT/$app.png" \ + "file://$HERE/theme-studio.html#preview=$app$SUFFIX" >/dev/null 2>&1 + if [ -s "$OUT/$app.png" ]; then + echo " shot $app" + else + echo " FAILED $app" >&2 + fi +done
\ No newline at end of file diff --git a/scripts/theme-studio/theme-studio.html b/scripts/theme-studio/theme-studio.html index 8cb6cc02..8e219fcd 100644 --- a/scripts/theme-studio/theme-studio.html +++ b/scripts/theme-studio/theme-studio.html @@ -3499,6 +3499,29 @@ function initApp(){ } initApp(); addEventListener('resize',()=>{syncPaneHeight('uitable','mockframe');syncPaneHeight('pkgtable','pkgpreview');}); +// #preview=<app-key>[&theme=<json-url>]: select that app on load and hide the +// topbar + palette so its face table + live preview render from the top of the +// page (headless screenshots can't scroll reliably). An optional theme URL is +// fetched and imported first, so shots show a real theme instead of untitled +// (fresh headless profiles have no localStorage; Chrome needs +// --allow-file-access-from-files for a file:// fetch). Drives the screenshot +// harness (screenshot-previews.sh), same hash-URL pattern as the browser gates. +// The title flip lets the harness confirm the selection landed. +if(location.hash.startsWith('#preview=')){ + const q=location.hash.slice(9).split('&theme='); + const k=decodeURIComponent(q[0]); + const showApp=()=>{ + if(!APPS[k])return; + const s=document.getElementById('viewsel'); + if(!s)return; + s.value=k;onViewChange();document.title='PREVIEW '+k; + document.querySelectorAll('.topbar, body > section') + .forEach(e=>{e.style.display='none';});}; + if(q[1])fetch(decodeURIComponent(q[1])).then(r=>r.text()) + .then(t=>{applyImported(t);showApp();}) + .catch(()=>{document.title='PREVIEW THEME-LOAD-FAILED';showApp();}); + else showApp(); +} // Shared gate harness. Each call site keeps its literal location.hash==='#NAMEtest' // check (run-tests.sh greps it); gate() owns the ok/notes/A setup and the verdict // postamble. Note format standardized to ' fails=note1,note2'. |
