aboutsummaryrefslogtreecommitdiff
path: root/scripts/theme-studio
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-13 18:12:09 -0500
committerCraig Jennings <c@cjennings.net>2026-06-13 18:12:09 -0500
commitd0cf30bfa37864db12009c2f561c87f96bd66989 (patch)
tree55ce6289283f0367e75790151bea7fed84fcce1d /scripts/theme-studio
parent0495b16fb940e0f573d4655f0a392d5159bcdeff (diff)
downloaddotemacs-d0cf30bfa37864db12009c2f561c87f96bd66989.tar.gz
dotemacs-d0cf30bfa37864db12009c2f561c87f96bd66989.zip
Sort theme studio dropdown colors by lightness
Diffstat (limited to 'scripts/theme-studio')
-rw-r--r--scripts/theme-studio/README.md3
-rw-r--r--scripts/theme-studio/app-core.js10
-rw-r--r--scripts/theme-studio/test-app-core.mjs23
-rw-r--r--scripts/theme-studio/theme-studio.html10
4 files changed, 38 insertions, 8 deletions
diff --git a/scripts/theme-studio/README.md b/scripts/theme-studio/README.md
index 6c7e628b..32eb2f56 100644
--- a/scripts/theme-studio/README.md
+++ b/scripts/theme-studio/README.md
@@ -148,6 +148,9 @@ derived from hue, chroma, lightness, or the visible color name.
to that step is re-pointed to the new hex. A step *removed* by lowering the count
leaves its references showing "(gone)" — visible and recoverable, never a silent
jump to a different color.
+- **Dropdown order.** Color dropdowns show the default entry, then `bg` and `fg`,
+ then palette columns from left to right. Within each column's dropdown group,
+ colors are ordered lightest to darkest.
The standalone ramp generator is gone; fanning a color into a ramp is now "add the
color, then raise its column's count."
diff --git a/scripts/theme-studio/app-core.js b/scripts/theme-studio/app-core.js
index 264c7be9..3c53e6a9 100644
--- a/scripts/theme-studio/app-core.js
+++ b/scripts/theme-studio/app-core.js
@@ -273,10 +273,12 @@ function stepRepointPlan(oldRanked,newMembers){
// columns are emitted in first-seen palette order. No color sorting happens here.
function sortColumnMembers(column){return Object.assign({},column,{members:[...column.members]});}
function sortColumns(columns){return columns.map(sortColumnMembers);}
+function lightestFirstMembers(members){return [...members].sort((a,b)=>oklchOf(b.hex).L-oklchOf(a.hex).L);}
// Dropdown order for color selection mirrors the visual palette organization:
-// ground first, then structural columns in display order. Stored palette order
-// stays untouched; this is selection-only organization.
+// bg/fg first, then structural columns in display order. Within each group,
+// choices run lightest-to-darkest. Stored palette order stays untouched; this is
+// selection-only organization.
function paletteOptionList(cur,palette,ground){
const have=cur===''||palette.some(p=>p[0]===cur)||[ground&&ground.bg,ground&&ground.fg].filter(Boolean).includes(cur);
const out=[['','— default —']],seen=new Set();
@@ -285,8 +287,8 @@ function paletteOptionList(cur,palette,ground){
const grouped=columnsFromPalette(palette,ground||{});
const groundMembers=grouped.ground.map(g=>({hex:g.hex,name:g.name||g.role}))
.concat(palette.filter(entry=>groundRoleOfEntry(entry,ground)==='step').map(([hex,name])=>({hex,name})));
- sortColumnMembers({base:(ground&&ground.bg)||'',members:groundMembers}).members.forEach(m=>add(m.hex,m.name));
- sortColumns(grouped.columns).forEach(f=>f.members.forEach(m=>add(m.hex,m.name)));
+ groundMembers.forEach(m=>add(m.hex,m.name));
+ sortColumns(grouped.columns).forEach(f=>lightestFirstMembers(f.members).forEach(m=>add(m.hex,m.name)));
return out;
}
diff --git a/scripts/theme-studio/test-app-core.mjs b/scripts/theme-studio/test-app-core.mjs
index 059a50b2..44474d43 100644
--- a/scripts/theme-studio/test-app-core.mjs
+++ b/scripts/theme-studio/test-app-core.mjs
@@ -59,6 +59,29 @@ test('paletteOptionList: Normal — color choices follow visual column ordering'
assert.ok(list.findIndex(([, name]) => name === 'gray') < list.findIndex(([, name]) => name === 'red'), 'later columns stay later');
});
+test('paletteOptionList: Normal — colors within each column are lightest to darkest', () => {
+ const pal = [
+ ['#111111', 'bg', 'ground'],
+ ['#eeeeee', 'fg', 'ground'],
+ ['#444444', 'gray-dark', 'gray'],
+ ['#cccccc', 'gray-light', 'gray'],
+ ['#888888', 'gray-mid', 'gray'],
+ ['#330000', 'red-dark', 'red'],
+ ['#dd8888', 'red-light', 'red'],
+ ];
+ const list = paletteOptionList('', pal, { bg: '#111111', fg: '#eeeeee' });
+ assert.deepEqual(list.slice(0, 3).map(([, name]) => name), ['— default —', 'bg', 'fg']);
+ assert.deepEqual(
+ list.filter(([, name]) => name.startsWith('gray')).map(([, name]) => name),
+ ['gray-light', 'gray-mid', 'gray-dark'],
+ );
+ assert.ok(list.findIndex(([, name]) => name === 'gray-dark') < list.findIndex(([, name]) => name === 'red-light'), 'column order is still left-to-right');
+ assert.deepEqual(
+ list.filter(([, name]) => name.startsWith('red')).map(([, name]) => name),
+ ['red-light', 'red-dark'],
+ );
+});
+
test('paletteOptionList: Boundary — assignment-only ground colors are selectable', () => {
const list = paletteOptionList('', [['#67809c', 'blue']], { bg: '#0d0b0a', fg: '#f0fef0' });
assert.ok(list.some(([hex, name]) => hex === '#0d0b0a' && name === 'bg'));
diff --git a/scripts/theme-studio/theme-studio.html b/scripts/theme-studio/theme-studio.html
index b980f800..70726afa 100644
--- a/scripts/theme-studio/theme-studio.html
+++ b/scripts/theme-studio/theme-studio.html
@@ -694,10 +694,12 @@ function stepRepointPlan(oldRanked,newMembers){
// columns are emitted in first-seen palette order. No color sorting happens here.
function sortColumnMembers(column){return Object.assign({},column,{members:[...column.members]});}
function sortColumns(columns){return columns.map(sortColumnMembers);}
+function lightestFirstMembers(members){return [...members].sort((a,b)=>oklchOf(b.hex).L-oklchOf(a.hex).L);}
// Dropdown order for color selection mirrors the visual palette organization:
-// ground first, then structural columns in display order. Stored palette order
-// stays untouched; this is selection-only organization.
+// bg/fg first, then structural columns in display order. Within each group,
+// choices run lightest-to-darkest. Stored palette order stays untouched; this is
+// selection-only organization.
function paletteOptionList(cur,palette,ground){
const have=cur===''||palette.some(p=>p[0]===cur)||[ground&&ground.bg,ground&&ground.fg].filter(Boolean).includes(cur);
const out=[['','— default —']],seen=new Set();
@@ -706,8 +708,8 @@ function paletteOptionList(cur,palette,ground){
const grouped=columnsFromPalette(palette,ground||{});
const groundMembers=grouped.ground.map(g=>({hex:g.hex,name:g.name||g.role}))
.concat(palette.filter(entry=>groundRoleOfEntry(entry,ground)==='step').map(([hex,name])=>({hex,name})));
- sortColumnMembers({base:(ground&&ground.bg)||'',members:groundMembers}).members.forEach(m=>add(m.hex,m.name));
- sortColumns(grouped.columns).forEach(f=>f.members.forEach(m=>add(m.hex,m.name)));
+ groundMembers.forEach(m=>add(m.hex,m.name));
+ sortColumns(grouped.columns).forEach(f=>lightestFirstMembers(f.members).forEach(m=>add(m.hex,m.name)));
return out;
}
// Pure color/UI-boundary helpers (normHex, ratingColor, textOn), inlined from