aboutsummaryrefslogtreecommitdiff
path: root/scripts/theme-studio
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/theme-studio')
-rw-r--r--scripts/theme-studio/README.md11
-rw-r--r--scripts/theme-studio/app.js23
-rwxr-xr-xscripts/theme-studio/screenshot-previews.sh51
-rw-r--r--scripts/theme-studio/theme-studio.html23
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'.