Skip to content

Releases: tenphi/glaze

v0.10.1

11 May 08:44
4ffa6ec

Choose a tag to compare

Patch Changes

  • #52 1988ff8 Thanks @tenphi! - Fix srgbToOkhsl (and downstream glaze.color()) returning a bogus saturated hue/saturation for pure white (#FFFFFF) and other colors at the OKHSL lightness extremes. Floating-point residue from linearSrgbToOklab slipped past the existing chroma epsilon, sending the chromatic path through a degenerate gamut where saturation divides by ~zero. White now correctly resolves to okhsl(0 0% 100%) (light) / okhsl(0 0% 15%) (dark) instead of okhsl(89.88 55.83% 100%).

v0.10.0

08 May 09:52
b557b77

Choose a tag to compare

Minor Changes

  • #50 6e2d42d Thanks @tenphi! - Revamp glaze.color() with a value-shorthand overload, seed-anchored
    contrast solving, a per-call lightness-scaling argument, and a .css()
    export. glaze.shadow() now accepts the same value forms as glaze.color().

    New defaults for glaze.color() — split by input form so end-user
    string values (color picker / theme settings) get a natural light/dark
    inversion, while programmatic object / tuple / structured inputs keep
    predictable linear behavior:

    • String value-shorthand (hex, rgb(), hsl(), okhsl(),
      oklch()): mode: 'auto' with snapshotted scaling
      { lightLightness: false, darkLightness: [globalConfig.darkLightness[0], 100] }.
      Light preserves the input exactly; dark Möbius-inverts up to 100,
      so glaze.color('#000') renders as #fff in dark mode and
      glaze.color('#fff') falls to the dark lo floor (default 0.15).
      The dark lo is snapshotted from globalConfig at color-creation
      time, matching how an explicit scaling.darkLightness: [lo, hi]
      behaves.
    • Object / tuple value-shorthand ({ h, s, l }, [r, g, b]) and
      structured form: mode: 'fixed' with light preserved and dark
      linearly mapped into globalConfig.darkLightness (default [15, 95]),
      also snapshotted at create time so later glaze.configure() calls
      don't retroactively change already-created tokens.
    • Override per call via the new third positional argument
      GlazeColorScaling: { lightLightness?: false | [lo, hi]; darkLightness?: false | [lo, hi] }.
      false disables the remap, a tuple sets a custom window. To opt
      string inputs back into the previous fixed-linear default, pass
      { mode: 'fixed' } as the second arg or supply an explicit
      scaling.

    Behavior change (minor bump):

    • String value-shorthand callers will see a Möbius-inverted dark
      variant by default — glaze.color('#000').resolve().dark.l is now
      ≈ 1.0, not 0.15. To preserve the old fixed-linear behavior pass
      { mode: 'fixed' } as the second argument.
    • Structured callers without an explicit mode will see
      glaze.color({...}).resolve().light.l match the input lightness
      exactly instead of being remapped to globalConfig.lightLightness.
      To preserve the old behavior pass
      { lightLightness: globalConfig.lightLightness } as the second
      argument.
    • The default lightness windows for object / tuple / structured
      inputs are now snapshotted from globalConfig.darkLightness at
      color-creation time, matching the existing behavior for string
      inputs. Tokens created before a glaze.configure() call no longer
      pick up the new dark window on their next .resolve(). To get the
      old "live config" behavior, recreate the token after configure().

    Value shorthand additions:

    • Accepts hex (#rgb / #rrggbb / #rrggbbaa), the four CSS color
      functions Glaze itself emits (rgb(), hsl(), okhsl(), oklch()),
      OkhslColor objects ({ h, s, l }), and [r, g, b] (0–255) tuples
      as the first argument. Every string emitted by theme.tasty() / .json() / .css()
      round-trips back through glaze.color().
    • 8-digit hex and rgba() / hsla() / slash-alpha alpha components are
      parsed and dropped with a console.warn (standalone colors have no
      opacity field).
    • oklch() chroma now correctly interprets percent values per CSS Color 4
      (100% → 0.4).
    • OkhslColor and [r, g, b] inputs are validated up front with helpful
      error messages — passing 0–100-scale s/l throws with a hint to use
      the structured form, and out-of-range RGB tuples throw with the offending
      value in the message.

    Anchor model: by default, relative lightness: '+N' and
    contrast: <ratio> are anchored to the literal seed (the value passed
    to glaze.color()), so the contrast solver compares against the
    unmapped user-provided color across every variant. Pass
    overrides.base (a GlazeColorToken) to anchor against another
    color's resolved variant per scheme instead.

    Color pairing via base: GlazeColorOverrides.base lets one
    standalone color depend on another. Accepts either a GlazeColorToken
    or any GlazeColorValue (hex / rgb() / OkhslColor / [r, g, b]);
    raw values are auto-wrapped via glaze.color(value) and inherit the
    same string-vs-object defaults. When set:

    • contrast is solved per scheme against the base's resolved variant
      (light / dark / lightContrast / darkContrast).
    • Relative lightness: '+N' / '-N' is anchored to the base's
      lightness per scheme (matches theme behavior for dependent colors).
    • Relative hue: '+N' still anchors to the seed (the value passed to
      glaze.color()), not the base.
    • mode is the per-pair knob — pass mode: 'fixed' to disable Möbius
      inversion for the dependent color, mode: 'auto' to keep it.

    The base token's .resolve() is called lazily on first resolve and
    the result is captured by reference, matching existing snapshot
    semantics. Internally, resolveAllColors accepts pre-resolved
    external bases and seeds them into the resolution context;
    validateColorDefs and topoSort treat external base names as leaves.

    opacity and name on glaze.color():

    • GlazeColorOverrides.opacity (and the same field on
      GlazeColorInput) sets a fixed alpha 0–1 that surfaces in every
      scheme variant. Combining with contrast is not recommended (perceived
      lightness becomes unpredictable) — glaze emits a console.warn in
      that case.
    • GlazeColorOverrides.name (and the same field on GlazeColorInput)
      is a human-readable label that surfaces in error and warning messages
      in place of the internal "value" sentinel. Empty / whitespace-only
      names and reserved internal names ("value", "seed",
      "externalBase") are rejected with a clear error.

    Structured form parity: the glaze.color({...}) overload now
    accepts opacity, contrast, base, and name in addition to the
    existing hue, saturation, lightness, saturationFactor, and
    mode. contrast without base synthesizes a hidden static seed
    from the input's normal-mode lightness so the contrast solver always
    has an anchor (mirrors value-form behavior). hue (finite),
    saturation / lightness (0–100), saturationFactor (0–1), and
    opacity (0–1) are range-checked up front with helpful error
    messages — non-finite or out-of-range values fail at creation rather
    than producing a NaN-laden token.

    Contrast warning: when the contrast solver cannot meet the
    requested target (e.g. AAA against a mid-grey base — physically
    unreachable), glaze emits a single console.warn per
    (name, scheme, target) triple naming the affected color, scheme, and
    the actual achieved ratio. The token still resolves to the closest
    passing variant. Use the name override to make the warning easier to
    trace.

    Persisting standalone colors: token.export() returns a JSON-safe
    snapshot containing the original value (or structured input), the
    overrides, and the captured scaling. Token-typed base is
    recursively serialized; value-typed base is preserved as the raw
    value. Pass the result to glaze.colorFrom(data) to rehydrate a token
    that resolves byte-for-byte identically to the original — across
    glaze.configure() calls and across processes. The captured scaling
    snapshots both lightLightness and darkLightness from globalConfig
    at create time, so later glaze.configure() calls don't retroactively
    change exported tokens regardless of input form.

    .css({ name }) export: new method on the standalone color token
    reaches export parity with theme.css(). Existing
    .token() / .tasty() / .json() continue to work unchanged.

    glaze.shadow() upgrade: bg and fg now accept any
    GlazeColorValue form — hex, rgb() / hsl() / okhsl() / oklch()
    strings, OkhslColor objects, or [r, g, b] tuples — sharing the same
    parser as glaze.color().

    Internal: standalone color tokens now memoize the underlying resolve
    across .resolve() / .token() / .tasty() / .json() / .css() calls.

    Public type additions: GlazeColorValue, GlazeColorOverrides,
    GlazeColorOverridesExport, GlazeColorCssOptions,
    GlazeColorScaling, GlazeColorTokenExport, GlazeColorInputExport.
    New glaze.colorFrom(data) factory and token.export() method on
    GlazeColorToken. New hslToSrgb, oklabToOkhsl, and parseHexAlpha
    math helpers re-exported from the package root.

v0.9.3

08 Apr 07:27
405d9f3

Choose a tag to compare

Patch Changes

  • 762c204 Thanks @tenphi! - Unify contrast search overshoot to 1% in both lightness and mix solvers.

v0.9.2

30 Mar 15:57
470e128

Choose a tag to compare

Patch Changes

  • #46 85111ca Thanks @tenphi! - Add inherit flag to color definitions to prevent inheritance during extend()

v0.9.1

30 Mar 10:24
252f8f0

Choose a tag to compare

Patch Changes

  • #44 50340c4 Thanks @tenphi! - darkCurve now accepts a [normal, highContrast] pair for separate HC tuning.

v0.9.0

30 Mar 10:10
d594c23

Choose a tag to compare

Minor Changes

  • #43 0575838 Thanks @tenphi! - Move primary option from per-export to glaze.palette() creation, add collision detection (warn + first-write-wins) across all palette export methods.

Patch Changes

  • #40 c47cfd2 Thanks @tenphi! - Unify dark/HC lightness mapping into a single code path via lightnessWindow helper.

  • #42 941338a Thanks @tenphi! - darkCurve now accepts a [normal, highContrast] pair for separate HC tuning.

v0.8.0

27 Mar 16:42
a609740

Choose a tag to compare

Minor Changes

  • #37 8b0b62b Thanks @tenphi! - Bypass lightLightness and darkLightness window constraints in high-contrast mode, allowing colors to use the full 0–100 lightness spectrum for increased perceivable contrast.

Patch Changes

  • #39 f9f6def Thanks @tenphi! - Add darkCurve config option for perceptual dark-theme lightness inversion using a power curve. Expands subtle near-white distinctions in dark mode. Default 0.5; set to 1 for legacy linear behavior. Widen contrast solver search range to [0, 1] so contrast targets are met regardless of dark lightness window.

  • #39 f9f6def Thanks @tenphi! - Replace power-curve dark lightness mapping with Möbius transformation for proportional expansion of lightness deltas across all sizes.

v0.7.0

25 Mar 19:00
63ad69f

Choose a tag to compare

Minor Changes

  • #34 2278b4e Thanks @tenphi! - Add primary option to palette exports (tokens, tasty, css) that duplicates one theme's tokens without prefix. Palette prefix now defaults to true.

Patch Changes

  • #36 e9e6ef6 Thanks @tenphi! - Fix contrast solver undershooting WCAG targets when using OKLCH output format. Increase OKLCH hue precision to 2dp and widen solver overshoot margin.

v0.6.3

25 Mar 09:07
42bdd5e

Choose a tag to compare

Patch Changes

  • d148498 Thanks @tenphi! - Apply lightLightness mapping to dependent colors with absolute lightness, matching darkLightness behavior.

  • #32 90bd23c Thanks @tenphi! - Propagate scheme lightness range to contrast solver for dependent colors, preventing pure black/white output when contrast-solving against extreme lightness values.

v0.6.2

23 Mar 14:08
614eceb

Choose a tag to compare

Patch Changes

  • #30 06be989 Thanks @tenphi! - Use 1% proportional contrast overshoot to reliably meet WCAG targets after 8-bit RGB quantization.