aboutsummaryrefslogtreecommitdiff
path: root/scripts/theme-studio/app-core.js
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-10 01:25:49 -0500
committerCraig Jennings <c@cjennings.net>2026-06-10 01:25:49 -0500
commitf6ab000116b1a498df34b84c013fc0d077475c28 (patch)
tree9aad280916af59dd7ddcdf0d710603954ae9c335 /scripts/theme-studio/app-core.js
parent77783126c8e35d5880a3e16a0014fc727f59b00a (diff)
downloaddotemacs-f6ab000116b1a498df34b84c013fc0d077475c28.tar.gz
dotemacs-f6ab000116b1a498df34b84c013fc0d077475c28.zip
feat(theme-studio): add the live per-family count control
Each chromatic family column gets a count input (0-4) showing its current per-side reach. Setting N regenerates the family as a symmetric base ±N ramp from its most-saturated color, replacing the family's current members. A reference to a surviving step (matched by signed lightness rank) follows the new hex through repointHex; a reference to a step removed by lowering N is left on its old hex, which is no longer in the palette and renders as "(gone)" — never silently reassigned. The neutral and ground strips get no control. I also fixed the neutral threshold curve: it was flat-high through the darks, which pulled a chroma-eased dark ramp step (a dark desaturated blue) into the neutral column and broke the family. The curve now tapers toward both lightness extremes, peaking near mid, so dark and light tints both keep their hue while mid grays stay neutral. This is the symmetric form of the Munsell scaling and a strict improvement. Phase 4 of the color-families spec. A #counttest gate covers count-up adding symmetric steps, count-down dropping the extremes, the surviving-step repoint, and the removed-step "(gone)".
Diffstat (limited to 'scripts/theme-studio/app-core.js')
-rw-r--r--scripts/theme-studio/app-core.js6
1 files changed, 3 insertions, 3 deletions
diff --git a/scripts/theme-studio/app-core.js b/scripts/theme-studio/app-core.js
index 0d1ce999..cf3e7ff8 100644
--- a/scripts/theme-studio/app-core.js
+++ b/scripts/theme-studio/app-core.js
@@ -138,9 +138,9 @@ function nameOfHex(palette,hex){const p=palette.find(p=>p[0].toLowerCase()===hex
function nearestAnchor(H){let best=HUE_ANCHORS[0],bd=999;for(const a of HUE_ANCHORS){let d=Math.abs(H-a);d=Math.min(d,360-d);if(d<bd){bd=d;best=a;}}return best;}
// A color reads as neutral below this chroma. Lightness-scaled (the Munsell
// insight): the mid-tones need more chroma to read as a hue, so a faint warm gray
-// at mid lightness is neutral while an equally-faint pale tint at high lightness
-// keeps its hue. Highest near mid lightness, tapering toward the light end.
-function neutralThreshold(L){const HI=0.6,LO=0.85,CMAX=0.04,CMIN=0.015;if(L<=HI)return CMAX;if(L>=LO)return CMIN;return CMAX-(L-HI)/(LO-HI)*(CMAX-CMIN);}
+// at mid lightness is neutral while an equally-faint tint near either extreme keeps
+// its hue. A tent peaking near mid lightness and tapering toward both ends.
+function neutralThreshold(L){const PK=0.6,MAX=0.035,d=L<PK?(PK-L)/PK:(L-PK)/(1-PK);return MAX*(1-Math.min(1,d));}
// A family from its members: base is the most-saturated member (tie toward
// mid-lightness), the anchor for a generated ramp.
function makeFamily(ms,neutral){