From 74db9a526503c9cc0c273f4523ad7ef76b61fb64 Mon Sep 17 00:00:00 2001 From: Craig Jennings Date: Wed, 10 Jun 2026 00:19:34 -0500 Subject: feat(theme-studio): add color-family sort sortFamilies orders the strips for display: neutrals first by lightness, then chromatic families by base hue, ties broken by base lightness then base hex. Each family's members come back sorted dark to light. Hue is compared rounded so a sub-degree hue hair from gamut quantization doesn't outrank lightness. Sorting is display-only; the stored palette order is untouched. Phase 2 of the color-families spec, pure logic. Four node tests cover the hue order, the neutral pin, within-family lightness order, and the (hue, then lightness) ordering invariant. Suite 91 to 95 green. --- scripts/theme-studio/app-core.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) (limited to 'scripts/theme-studio/app-core.js') diff --git a/scripts/theme-studio/app-core.js b/scripts/theme-studio/app-core.js index 70054255..e6d90039 100644 --- a/scripts/theme-studio/app-core.js +++ b/scripts/theme-studio/app-core.js @@ -208,4 +208,22 @@ function stepRepointPlan(oldRanked,newMembers){ return {map,removed}; } -export { nameToHex, buildPkgmap, packagesForExport, mergePackagesInto, effResolve, optList, slugify, ramp, fgSetFor, floor, lMax, COVERED_FACES, familiesFromPalette, regenFamily, rankByLightness, stepRepointPlan }; +// Order a family's members dark to light by OKLCH lightness. +function sortFamilyMembers(fam){return Object.assign({},fam,{members:[...fam.members].sort((a,b)=>oklchOf(a.hex).L-oklchOf(b.hex).L)});} +// Order families for display: neutrals first (by base lightness), then chromatic +// by base hue, ties broken by base lightness then base hex. Each family's members +// are lightness-sorted. Display-only — the stored palette order is untouched. +function sortFamilies(families){ + const keyed=families.map(f=>{const c=oklchOf(f.base);return {f,neutral:!!f.neutral,H:c.H,L:c.L,base:f.base};}); + keyed.sort((a,b)=>{ + if(a.neutral!==b.neutral)return a.neutral?-1:1; + if(a.neutral&&b.neutral)return a.L-b.L; + const ah=Math.round(a.H),bh=Math.round(b.H); // a hue hair shouldn't outrank lightness + if(ah!==bh)return ah-bh; + if(a.L!==b.L)return a.L-b.L; + return a.base.toLowerCase()b.base.toLowerCase()?1:0; + }); + return keyed.map(k=>sortFamilyMembers(k.f)); +} + +export { nameToHex, buildPkgmap, packagesForExport, mergePackagesInto, effResolve, optList, slugify, ramp, fgSetFor, floor, lMax, COVERED_FACES, familiesFromPalette, regenFamily, rankByLightness, stepRepointPlan, sortFamilies, sortFamilyMembers }; -- cgit v1.2.3