Add a colour spec choice to material:scheme

The scheme script always generated with material-color-utilities' 2025 spec.
An application whose palette was generated with M3's original 2021 colour
(ReStride) could not reproduce it. The script now takes `spec` ('2025' by
default, or '2021') and refuses anything else, and the command takes
`--spec`, validated before Node runs.

Colour profiles may set their own `spec`, `success`, `warning` and `info`;
without them the command's options apply. The stylesheet header writes out
every option that differs from its default, so the recorded command
regenerates the file, and a profile whose spec differs from the header's
names it. Default output is byte-for-byte unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RHoXZSHc8gGpZjFmA5fPc2
This commit is contained in:
Andreas Reinhold / reini
2026-09-13 17:52:50 +02:00
co-authored by Claude Opus 5
parent db179e6b4e
commit 3a7aa96ec7
7 changed files with 147 additions and 24 deletions
+8 -3
View File
@@ -6,7 +6,8 @@
* nothing but `node`. (The published library imports without file extensions, which
* plain Node refuses, so it cannot be run unbundled anyway.)
*
* Input: one JSON argument — {seed, variant, contrast, success, warning, info}.
* Input: one JSON argument — {seed, variant, spec, contrast, success, warning, info}. `spec` is
* the colour spec, '2025' (M3 Expressive, the default) or '2021' (M3 as it first shipped).
* Output: JSON on stdout — {seed, variant, spec, contrast, light: {role: hex}, dark: {role: hex}}.
*/
import {
@@ -56,9 +57,12 @@ try {
const hex = /^#[0-9a-f]{6}$/i
const Scheme = VARIANTS[input.variant]
const SPECS = ['2021', '2025']
const spec = input.spec ?? '2025'
if (!hex.test(input.seed ?? '')) fail(`The seed must be a #rrggbb colour, "${input.seed}" given.`)
if (!Scheme) fail(`Unknown variant "${input.variant}". Use one of: ${Object.keys(VARIANTS).join(', ')}.`)
if (!SPECS.includes(spec)) fail(`Unknown spec "${spec}". Use one of: ${SPECS.join(', ')}.`)
for (const state of ['success', 'warning', 'info']) {
if (!hex.test(input[state] ?? '')) fail(`The ${state} colour must be a #rrggbb colour, "${input[state]}" given.`)
@@ -73,8 +77,9 @@ const colors = new MaterialDynamicColors()
function roles(isDark) {
// The 2025 spec is M3 Expressive's colour; the library falls back to 2021 for the
// variants the new spec does not define (fidelity, content, monochrome, …).
const scheme = new Scheme(source, isDark, contrast, '2025')
// variants the new spec does not define (fidelity, content, monochrome, …). 2021 is
// M3's original colour, for an application whose palette was generated with it.
const scheme = new Scheme(source, isDark, contrast, spec)
const out = {}
for (const color of colors.allColors) {