engine/neumorphism library

The Neumorphism (Soft UI) palette builder (spec §5.3 #4; docs/m4-design.md §C; research theme-engine.md recommendation 7 and §7.2). Pure Dart: part of the generator, so every value here is part of the family's engine-v2 definition and must not change without a version bump.

Classic neumorphism fails WCAG because its keys are the background colour and their only edge is a blurred shadow (neumorphism.io's default reaches 1.41:1). This builder keeps the look and adds:

  • Surface: a monochrome, low-chroma base (light L 0.88–0.94, dark L 0.20–0.28, C ≤ 0.02) that is the background and every digit and function face.
  • Shadows: a light and a dark shadow derived in OKLCH from the surface (same hue; light L + 0.04–0.08 (dark themes + 0.03–0.06), dark L − 0.10–0.18 (dark themes − 0.08–0.12), C + 0.01), stored as palette.highlight / palette.shadow. The Accessible lock deepens the dark shadow.
  • Rim: a 1–1.5 dp stroke on the shadow side, its colour solved by OKLCH lightness bisection to the least change that reaches ≥ 3:1 against the surface (WCAG 1.4.11), stored as every group's KeyColors.edge.
  • Labels: near-black / near-white ink solved to ≥ 4.5:1 (7:1 under the Accessible lock and on dark faces), function keys in an accent-tinted ink.
  • Operators: an accent label on the surface face, or a tinted accent face ≥ 0.10 lightness from it (seeded), always with a label weight ≥ key weight + 200 — the recorded non-colour cue (OperatorCue.weight). = is a full accent face.
  • Pressed: besides the shadow swap, surface-faced keys show their label in the accent colour (solved ≥ the label floor on the surface) and the rim closes all round; accent-labelled operators swap to the digit ink.

Classes

SoftShadowRanges
Soft-shadow lightness offsets from the surface (× 1000 of OKLCH L).

Enums

SoftOperatorStyle
How the operators of a Neumorphism theme stand out (both add the heavier glyph).

Constants

softDarkShadows → const SoftShadowRanges
Dark themes: little headroom for highlights, so a smaller one.
softLightShadows → const SoftShadowRanges
Light themes (research §7.2.2).
softRimWidthsCdp → const List<int>
Rim widths (dp × 100) the style stream picks from; the Accessible lock always takes the widest. Inside softRimWidthMinCdp–softRimWidthMaxCdp (guardrails.dart).

Functions

buildNeumorphicCandidate(int seed, FamilyDefinition def, int engineVersion, {required String paletteTag, required bool accessible}) → ThemeSpec
One unvalidated Neumorphism candidate for seed (the generator adds repair, retries and the hero fallback). paletteTag is the palette stream of this attempt (palette, then palette/retry/n).
softOperatorStyleOf(ThemeSpec spec) → SoftOperatorStyle
Whether spec (a soft-style theme) gives operators an accent label on the surface face rather than a tinted face.
softRimFor(int surface, {double target = ratioLarge}) → int
The rim colour for surface: same hue, chroma + 0.01, the least lightness change from the surface that reaches target against it.