@getvitops/utils 0.9.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +217 -0
- package/dist/color/index.d.mts +74 -3
- package/dist/color/index.d.mts.map +1 -1
- package/dist/color/index.mjs +127 -123
- package/dist/color/index.mjs.map +1 -1
- package/dist/favicon.d.mts +13 -0
- package/dist/favicon.d.mts.map +1 -1
- package/dist/favicon.mjs +40 -28
- package/dist/favicon.mjs.map +1 -1
- package/dist/index.d.mts +216 -21
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +369 -29
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,222 @@
|
|
|
1
1
|
# @getvitops/utils
|
|
2
2
|
|
|
3
|
+
## 2.0.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 04a51d8: Icons: one semantic vocabulary across icon sets, with the bundle derived from your source.
|
|
8
|
+
|
|
9
|
+
Name icons by meaning — `<Icon name="menu" />` resolves to `fa7-solid:bars`, `ph:list` or
|
|
10
|
+
`lucide:menu` depending on the set you configure, so swapping sets is a config edit rather than a
|
|
11
|
+
find-and-replace. A name containing `:` still passes through untouched, which is the escape hatch
|
|
12
|
+
for a set-specific glyph.
|
|
13
|
+
|
|
14
|
+
**New `icons` option on `getvitops()`.** Configures the sets once per site and, under
|
|
15
|
+
`output: 'server'`, derives astro-icon's `include` map by scanning your source — the list most
|
|
16
|
+
projects end up maintaining by hand. On a static build no `include` is passed at all, because
|
|
17
|
+
astro-icon is already zero-config there and a list could only drop a glyph the scan couldn't see.
|
|
18
|
+
Names you declare that don't resolve fail the build; names only the scan found just warn; names
|
|
19
|
+
computed at runtime are reported with file and line so you can declare them.
|
|
20
|
+
|
|
21
|
+
**New `<Icon />` component**, and a real fix with it: `Popover`, `Details` and `Drawer` imported
|
|
22
|
+
`astro-icon/components` at module scope, so the _optional_ peer was resolved whether or not an icon
|
|
23
|
+
ever rendered — hard-failing anyone who hadn't installed it. Engines now load dynamically. The
|
|
24
|
+
per-component `iconResolver` prop still works but is deprecated in favour of the integration option.
|
|
25
|
+
|
|
26
|
+
**New SVG sprite** (`icons.sprite: true` in your site config) for consumers that can't run an icon
|
|
27
|
+
integration — Bricks/WordPress, EmDash renderers, plain HTML. `<use href="…/icons.svg#ph--list">`,
|
|
28
|
+
no JavaScript and no icon-API request. Every semantic name also gets a set-independent `icon-<name>`
|
|
29
|
+
alias, so sprite markup survives an icon-set change. WordPress gets `vitops_icon()`, a
|
|
30
|
+
`[vitops_icon]` shortcode and a **Vitops → Icon** Bricks element.
|
|
31
|
+
|
|
32
|
+
**New `vitops icons`** command: reports which icons your source uses, which names couldn't be
|
|
33
|
+
resolved, and which are computed at runtime; `--sprite` builds the sprite, `--json` for CI.
|
|
34
|
+
|
|
35
|
+
**Renamed, and completed to all four directions.** Chevrons and arrows are the
|
|
36
|
+
one family whose _meaning is_ its direction, and that direction is physical, not
|
|
37
|
+
logical — a chevron in a details marker points down in every writing mode. So they
|
|
38
|
+
are named for where they point: `expand-more`/`expand-less` →
|
|
39
|
+
`chevron-down`/`chevron-up`, and `arrow-forward`/`arrow-back` →
|
|
40
|
+
`arrow-right`/`arrow-left`. `chevron-left`/`chevron-right` keep their names.
|
|
41
|
+
`arrow-up`/`arrow-down` are new, so both families now cover all four directions.
|
|
42
|
+
`lightning` is new.
|
|
43
|
+
|
|
44
|
+
If you passed any of the old names to `<Icon />`, `resolveIcon` or an `icons`
|
|
45
|
+
config, update them. They fail loudly rather than silently: an unresolvable
|
|
46
|
+
declared name throws, and `vitops icons` reports scanned ones.
|
|
47
|
+
|
|
48
|
+
**Fixed three Font Awesome mappings that named no real glyph** and so rendered an
|
|
49
|
+
empty box: `login`/`logout` were mapped to `login`/`logout` (Font Awesome calls
|
|
50
|
+
them `right-to-bracket`/`right-from-bracket`) and `backup` to `backup`
|
|
51
|
+
(`cloud-arrow-up`). Every value in all four UI sets is now checked against the
|
|
52
|
+
installed collection.
|
|
53
|
+
|
|
54
|
+
**Phosphor (`ph`) joins the semantic map**, with all 83 names verified against the real icon set.
|
|
55
|
+
Phosphor keeps every weight in one collection and varies the name (`list`, `list-bold`), unlike Font
|
|
56
|
+
Awesome's per-weight collections, so `resolveIcon` and `generateIconInclude` gained a `weight`
|
|
57
|
+
option for sets shaped that way.
|
|
58
|
+
|
|
59
|
+
Fixes: `site.icons` was a closed object, so any icon collection not in its hand-written key list was
|
|
60
|
+
silently dropped during validation — your config passed and the icons never bundled. It accepts any
|
|
61
|
+
collection name now.
|
|
62
|
+
|
|
63
|
+
- bf453b0: Maskable favicons are now opaque, so they stop rendering as a logo in a black box.
|
|
64
|
+
|
|
65
|
+
`icon-mask.png` is declared `purpose: "maskable"` and `apple-touch-icon.png` is linked on every
|
|
66
|
+
page. Both sit a deliberately-inset logo on a larger canvas — that inset **is** the maskable safe
|
|
67
|
+
zone and was always correct — but the canvas was filled with `alpha: 0`. From any transparent
|
|
68
|
+
source that left 36% of `icon-mask.png` and 40% of `apple-touch-icon.png` transparent, on two files
|
|
69
|
+
whose entire contract is full bleed: the OS crops them to its own shape and composites the rest onto
|
|
70
|
+
whatever it likes, usually black. iOS discards alpha on the apple-touch-icon outright.
|
|
71
|
+
|
|
72
|
+
`backgroundColor` was already the obvious input for this and already plumbed — as far as the web
|
|
73
|
+
manifest, and no further. So the raster and the manifest disagreed about the very colour meant to
|
|
74
|
+
sit behind the icon.
|
|
75
|
+
|
|
76
|
+
Now:
|
|
77
|
+
- those two outputs are composited onto `backgroundColor`, defaulting to `#ffffff` — the same
|
|
78
|
+
default the manifest's `background_color` already used;
|
|
79
|
+
- `favicon.svg`, `icon-{16,32,192,512}.png` and `favicon.ico` keep the source's transparency, since
|
|
80
|
+
none of them is maskable;
|
|
81
|
+
- a transparent source with no `backgroundColor` set now warns, rather than silently inheriting
|
|
82
|
+
white — a dark logo would lose against it;
|
|
83
|
+
- `icon-192`/`icon-512` declare `purpose: "any"` explicitly. Omitting it was legal (unset means
|
|
84
|
+
"any"), but with a maskable present and nothing claiming "any", some launchers picked the maskable
|
|
85
|
+
— the one with the safe-zone inset — for slots that wanted a plain icon.
|
|
86
|
+
|
|
87
|
+
`vitops favicon` gained `--background <hex>`; it previously had no way to set this at all.
|
|
88
|
+
`getvitops({ favicon })` and the Vite plugin now forward the option they were already accepting.
|
|
89
|
+
|
|
90
|
+
- bf453b0: A chroma-0 seed now produces an actual neutral instead of a pink one.
|
|
91
|
+
|
|
92
|
+
Beyond a ramp's outermost authored colour, chroma decays towards a small endpoint value (0.008
|
|
93
|
+
light / 0.015 dark) so the near-white and near-black ends keep a whisper of the hue. That was
|
|
94
|
+
written as a lerp _target_, and the interpolation factor reaches exactly 1 at steps 50 and 950 for
|
|
95
|
+
every anchor position — so the endpoints were those constants **unconditionally**. Seeds at chroma
|
|
96
|
+
0.001, 0.002, 0.05 and 0.2 all produced a byte-identical `#f5f9fe` at step 50.
|
|
97
|
+
|
|
98
|
+
There was therefore no seed that yielded a plain neutral, and chroma 0 was the worst case: a true
|
|
99
|
+
achromatic colour has no hue (colorjs returns NaN), which collapses to 0 — red — so asking for grey
|
|
100
|
+
gave you `#fdf6f8`, a pink. A brand wanting plain white with grey panels had to discover `anchors`
|
|
101
|
+
to get there.
|
|
102
|
+
|
|
103
|
+
The endpoint is now a **ceiling** rather than a target, so a seed below it keeps its own chroma:
|
|
104
|
+
|
|
105
|
+
| seed chroma | step 50 before | step 50 after |
|
|
106
|
+
| ----------- | -------------- | ------------- |
|
|
107
|
+
| 0 | `#fdf6f8` 🩷 | `#f8f8f8` |
|
|
108
|
+
| 0.001 | `#f5f9fe` | `#f8f8f9` |
|
|
109
|
+
| 0.003 | `#f5f9fe` | `#f7f8fa` |
|
|
110
|
+
| 0.05 | `#f5f9fe` | `#f5f9fe` |
|
|
111
|
+
|
|
112
|
+
`seed: "#808080"` now gives a real grey ramp — `#f8f8f8` / `#eee` / `#808080` / `#2b2b2b` / `#181818`.
|
|
113
|
+
|
|
114
|
+
**No existing palette moves.** The ceiling only binds below itself, and every ordinary brand hue
|
|
115
|
+
sits above it; the repo's own `tokens.json` is byte-identical across this change.
|
|
116
|
+
|
|
117
|
+
## 1.0.0
|
|
118
|
+
|
|
119
|
+
### Major Changes
|
|
120
|
+
|
|
121
|
+
- bb92a14: **The colour system is rebuilt on a target-prefixed grammar and a shared lightness ladder.**
|
|
122
|
+
This is a breaking change to every colour token and utility class.
|
|
123
|
+
|
|
124
|
+
## Why
|
|
125
|
+
|
|
126
|
+
Two axes shared one class namespace — functional _planes_ (`--<role>-bg-muted`) and
|
|
127
|
+
appearance-relative _stops_ (`--color-<role>-muted`) — arbitrated by a "plane wins" rule. The
|
|
128
|
+
result was not a scale. On the shipped palette, light mode:
|
|
129
|
+
|
|
130
|
+
| class | resolved to | step |
|
|
131
|
+
| ---------------------------------- | ------------ | --------------------------------------------- |
|
|
132
|
+
| `bg-ui-accent-x-muted` | stop | 100 |
|
|
133
|
+
| `bg-ui-accent-muted` | plane | 100 — identical, so `x-muted` was a dead rung |
|
|
134
|
+
| `bg-ui-accent` | plane | 50 — _lighter_ than both its "muted" rungs |
|
|
135
|
+
| `border-ui-accent-muted` / `-bold` | stop / plane | 300 / 300 — identical |
|
|
136
|
+
| `text-ui-accent-bold` | stop | 700 — _lighter_ than `text-…-muted` (800) |
|
|
137
|
+
|
|
138
|
+
Every family was non-monotonic, two had duplicate rungs, and `--color-<role>-muted` was
|
|
139
|
+
unreachable through any `bg-` class.
|
|
140
|
+
|
|
141
|
+
## The new grammar
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
--color-<target>-<role>[-<variant>] target ∈ bg | text | icon | border
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The target is **inside** the name, so `bg-danger-muted` and `text-danger-muted` are different
|
|
148
|
+
tokens and there is nothing left to arbitrate. **The class name is the token name minus
|
|
149
|
+
`--color-`** — one vocabulary instead of two.
|
|
150
|
+
|
|
151
|
+
Variants are ordinal (`xx-muted` < `x-muted` < `muted` < bare < `bold` < `x-bold`) and the
|
|
152
|
+
tables are sparse: only cells that hold their contrast target exist.
|
|
153
|
+
|
|
154
|
+
## Role kinds
|
|
155
|
+
|
|
156
|
+
`colors.roles` values may now be `{ hue, kind }` as well as a bare hue string:
|
|
157
|
+
|
|
158
|
+
```jsonc
|
|
159
|
+
"roles": {
|
|
160
|
+
"danger": "rust", // shorthand => chromatic
|
|
161
|
+
"surface": { "hue": "navy", "kind": "surface" }
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
- **`surface`** — a page/panel colour: `bg-<role>` is the card, `bg-<role>-muted` the page
|
|
166
|
+
behind it, `bg-<role>-x-muted` a well, `bg-<role>-bold` the inverse surface. Full text scale.
|
|
167
|
+
- **`chromatic`** (default) — a signal colour: tints (`bg-<role>-x-muted`/`-muted`) and solids
|
|
168
|
+
(`bg-<role>-solid[-bold|-x-bold]`), with **no bare `bg-<role>`** — say how loud you mean.
|
|
169
|
+
`text-on-<role>` is the guaranteed foreground for the solid family.
|
|
170
|
+
|
|
171
|
+
## Palette generation
|
|
172
|
+
|
|
173
|
+
Every ramp now sits on one **fixed lightness ladder** (50 → L 0.98 … 950 → L 0.21); only chroma
|
|
174
|
+
and hue vary. That is what makes a step mean the same lightness in every hue. Previously each
|
|
175
|
+
seed transposed the curve, so relative luminance at step 300 ranged from 0.253 to 0.384 across
|
|
176
|
+
the shipped palette — the same variant read differently depending on the role.
|
|
177
|
+
|
|
178
|
+
Authored colours are still reproduced **exactly**. A `seed`, an `anchors` entry or a `tones`
|
|
179
|
+
value is pinned verbatim at its nearest step; every other step takes the ladder. Snapping brand
|
|
180
|
+
colours to the ladder was measured and rejected: 11 of 18 real brand hexes moved beyond a
|
|
181
|
+
just-noticeable difference (worst ΔE-OK 0.050 — Facebook's `#1877F2` → `#0067e1`) and six left
|
|
182
|
+
sRGB gamut. Deviation is now bounded and local to pinned steps, and warns past ~0.03 L.
|
|
183
|
+
|
|
184
|
+
Two tones that claim the same step now **error** instead of one silently overwriting the other;
|
|
185
|
+
the record form (`tones: { "600": "…", "700": "…" }`) is how you resolve it.
|
|
186
|
+
|
|
187
|
+
## New
|
|
188
|
+
- **`icon-<role>`** — a non-text tier, so a glyph may run more vivid than text. `icon` is now a
|
|
189
|
+
default utility family alongside `bg`/`text`/`border`.
|
|
190
|
+
- **`--color-border-focus`** — the focus-ring tone, taken from `ui-primary`'s solid.
|
|
191
|
+
- **Contrast is enforced at build time**, not only in tests: text ≥ APCA Lc 75 on its primary
|
|
192
|
+
background, ≥ 60 on secondary planes, icons and surface boundaries ≥ 45, both appearances.
|
|
193
|
+
A violation now fails `generate`. Chromatic text is checked against the _surface_ planes it
|
|
194
|
+
actually sits on, not only its own tints. `text-<role>-x-muted` (placeholder) and `-xx-muted`
|
|
195
|
+
(disabled) are explicitly exempt; nothing else is.
|
|
196
|
+
|
|
197
|
+
## Migrating
|
|
198
|
+
|
|
199
|
+
Rename `--<role>-<suffix>` → `--color-<target>-<role>[-<variant>]`, and the same for classes:
|
|
200
|
+
|
|
201
|
+
| before | after |
|
|
202
|
+
| --------------------------------------- | ---------------------------------------------------------------- |
|
|
203
|
+
| `--<role>-bg` / `bg-<role>` (chromatic) | `--color-bg-<role>-x-muted` / `bg-<role>-x-muted` |
|
|
204
|
+
| `--<role>-bg-muted` | `--color-bg-<role>-muted` |
|
|
205
|
+
| `--<role>-solid` / `-solid-bold` | `--color-bg-<role>-solid` / `-solid-bold` |
|
|
206
|
+
| `--<role>-on-solid` / `text-on-<role>` | `--color-text-on-<role>` (class unchanged) |
|
|
207
|
+
| `--<role>-text` / `-text-muted` | `--color-text-<role>` / `-muted` |
|
|
208
|
+
| `--<role>-border` / `-border-bold` | `--color-border-<role>` / `-bold` |
|
|
209
|
+
| `--color-<role>-muted` (stop) | judge by use: `--color-bg-<role>-muted` or `--color-text-<role>` |
|
|
210
|
+
|
|
211
|
+
**`surface` background names rotate**, value-preserving: what was `--surface-bg` (the page) is
|
|
212
|
+
now `--color-bg-surface-muted`; what was `--surface-bg-bold` (raised) is now
|
|
213
|
+
`--color-bg-surface`. Elevation is expressed by which token you reach for — page `bg-muted`,
|
|
214
|
+
card `bg` — rather than a raised/sunken pair, which is what lets a future surfaces axis flatten
|
|
215
|
+
it without touching markup.
|
|
216
|
+
|
|
217
|
+
`vitops lint` reports role classes that no longer resolve, and now derives its suggestions from
|
|
218
|
+
what the generator actually emits rather than a hand-maintained list.
|
|
219
|
+
|
|
3
220
|
## 0.9.0
|
|
4
221
|
|
|
5
222
|
## 0.8.0
|
package/dist/color/index.d.mts
CHANGED
|
@@ -68,6 +68,44 @@ interface HarmonyDefinition {
|
|
|
68
68
|
primaryIndex: number;
|
|
69
69
|
}
|
|
70
70
|
declare const HARMONY_DEFINITIONS: Record<HarmonyType, HarmonyDefinition>;
|
|
71
|
+
/**
|
|
72
|
+
* The shared lightness ladder every ramp is built on (lightest → darkest).
|
|
73
|
+
*
|
|
74
|
+
* Fixed, NOT seed-derived. Two hues at the same step have to read equally light,
|
|
75
|
+
* otherwise one contrast table cannot serve every hue and a `muted` variant means
|
|
76
|
+
* something different depending on which role you ask. Only chroma and hue vary
|
|
77
|
+
* between ramps.
|
|
78
|
+
*
|
|
79
|
+
* The previous engine transposed this curve onto each seed, which let relative
|
|
80
|
+
* luminance at step 300 range from 0.253 (rust) to 0.384 (amber) across the
|
|
81
|
+
* shipped palette — the drift this replaces.
|
|
82
|
+
*
|
|
83
|
+
* Anchored steps are the one deliberate exception: see `generateOklchPalette`.
|
|
84
|
+
*/
|
|
85
|
+
declare const LIGHTNESS_LADDER: Record<StepName, number>;
|
|
86
|
+
/**
|
|
87
|
+
* Chroma a ramp decays *towards* beyond its outermost anchors, so the near-white
|
|
88
|
+
* and near-black ends keep a whisper of the hue instead of going flat grey. The
|
|
89
|
+
* dark end carries slightly more because chroma reads weaker against black.
|
|
90
|
+
*
|
|
91
|
+
* It is a CEILING, not a target — `Math.min(ENDPOINT_CHROMA.x, anchor.c)`. That
|
|
92
|
+
* distinction is the whole reason a neutral is authorable. The interpolation
|
|
93
|
+
* factor reaches exactly 1 at steps 50 and 950 for every anchor position, so as a
|
|
94
|
+
* target it made the endpoints these constants *unconditionally*: seeds at chroma
|
|
95
|
+
* 0.001, 0.002, 0.05 and 0.2 all produced a byte-identical step 50. A brand
|
|
96
|
+
* wanting plain white with grey panels had no seed that worked, and chroma 0 was
|
|
97
|
+
* worst of all — colorjs returns NaN hue for a true achromatic colour, which
|
|
98
|
+
* `hexToOklch` collapses to 0, so the "neutral" came out pink.
|
|
99
|
+
*
|
|
100
|
+
* As a ceiling, a low-chroma seed simply stays low and a chroma-0 seed stays 0 at
|
|
101
|
+
* every unanchored step, which makes the NaN-hue collapse invisible rather than
|
|
102
|
+
* something `hexToOklch` has to solve (its `|| 0` is load-bearing for
|
|
103
|
+
* `getColorName`).
|
|
104
|
+
*/
|
|
105
|
+
declare const ENDPOINT_CHROMA: {
|
|
106
|
+
readonly light: 0.008;
|
|
107
|
+
readonly dark: 0.015;
|
|
108
|
+
};
|
|
71
109
|
interface OklchColor {
|
|
72
110
|
l: number;
|
|
73
111
|
c: number;
|
|
@@ -80,8 +118,36 @@ interface OklchColor {
|
|
|
80
118
|
*/
|
|
81
119
|
declare function parseOklch(oklchString: string): OklchColor;
|
|
82
120
|
/**
|
|
83
|
-
*
|
|
84
|
-
*
|
|
121
|
+
* The ladder step whose lightness is closest to `lightness` — i.e. where a
|
|
122
|
+
* supplied colour naturally belongs.
|
|
123
|
+
*/
|
|
124
|
+
declare function assignStepByLightness(lightness: number): StepName;
|
|
125
|
+
/**
|
|
126
|
+
* How far an anchored colour sits from its step's ladder lightness.
|
|
127
|
+
*
|
|
128
|
+
* The generator surfaces this as a warning: a large deviation is legal (the
|
|
129
|
+
* caller asked for that exact colour) but it means the hue will read heavier or
|
|
130
|
+
* lighter than its siblings at that step, which is worth knowing rather than
|
|
131
|
+
* discovering later.
|
|
132
|
+
*/
|
|
133
|
+
declare function ladderDeviation(step: StepName, lightness: number): number;
|
|
134
|
+
/**
|
|
135
|
+
* Build a complete 11-step OKLCH ramp from one or more anchor colours.
|
|
136
|
+
*
|
|
137
|
+
* **Ladder-first with exact anchors.** Every anchor is reproduced *verbatim* at
|
|
138
|
+
* its step; every other step takes its lightness from `LIGHTNESS_LADDER`, with
|
|
139
|
+
* chroma and hue interpolated between the flanking anchors and decayed toward
|
|
140
|
+
* `ENDPOINT_CHROMA` beyond the outermost ones.
|
|
141
|
+
*
|
|
142
|
+
* The consequence is the point: a client's brand hex survives byte-for-byte,
|
|
143
|
+
* while the other ten steps stay directly comparable to every other hue. The
|
|
144
|
+
* deviation from the ladder is bounded and *local to anchored steps* — unlike
|
|
145
|
+
* the previous engine, which warped the whole ramp to fit its anchors so every
|
|
146
|
+
* step drifted.
|
|
147
|
+
*
|
|
148
|
+
* Chroma may come out beyond the sRGB gamut here; that is deliberate.
|
|
149
|
+
* `oklchStringToHex` gamut-maps with the CSS algorithm, which reduces chroma
|
|
150
|
+
* while holding lightness, so the ladder survives the conversion.
|
|
85
151
|
*/
|
|
86
152
|
declare function generateOklchPalette(input: string | string[] | Record<string, string>): Record<StepName, string>;
|
|
87
153
|
/** APCA lightness-contrast (Lc) magnitude between a foreground and background. */
|
|
@@ -95,6 +161,11 @@ declare function pickOn(bg: string, hue?: number): string;
|
|
|
95
161
|
* The tinted off-white / off-black endpoints for a hue — white/black with a
|
|
96
162
|
* whisper of the hue mixed in. These are the text/background tints every ramp
|
|
97
163
|
* needs (and the one exception strict brand kits allow).
|
|
164
|
+
*
|
|
165
|
+
* Derived from `LIGHTNESS_LADDER` and `ENDPOINT_CHROMA` rather than its own
|
|
166
|
+
* literals, so a strict tone kit's endpoints land on the same ladder rungs a
|
|
167
|
+
* generated ramp would have used. This is the single definition — callers must
|
|
168
|
+
* not hand-write their own near-white/near-black.
|
|
98
169
|
*/
|
|
99
170
|
declare function tintedEndpoints(hue: number, chroma?: number): {
|
|
100
171
|
light: string;
|
|
@@ -140,5 +211,5 @@ declare const DEFAULT_FUNCTIONAL_HUES: {
|
|
|
140
211
|
*/
|
|
141
212
|
declare function suggestFunctionalColorHue(type: keyof typeof DEFAULT_FUNCTIONAL_HUES, existingHues: number[]): number;
|
|
142
213
|
//#endregion
|
|
143
|
-
export { COLOR_GROUPS, ColorGroup, DEFAULT_FUNCTIONAL_HUES, HARMONY_DEFINITIONS, HARMONY_STRATEGIES, HARMONY_TYPES, HarmonyDefinition, HarmonyOption, HarmonyStrategy, HarmonyType, RgbResult, STEP_NAMES, StepName, applyHarmonyStrategy, contrastLc, formatColorName, gamutMapOklchToHex, generateOklchPalette, getColorName, getColorNameWithHue, getHarmonySuggestions, hexToOklch, oklchStringToHex, oklchToHex, oklchToP3, oklchToSrgb, parseOklch, pickOn, suggestFunctionalColorHue, tintedEndpoints };
|
|
214
|
+
export { COLOR_GROUPS, ColorGroup, DEFAULT_FUNCTIONAL_HUES, ENDPOINT_CHROMA, HARMONY_DEFINITIONS, HARMONY_STRATEGIES, HARMONY_TYPES, HarmonyDefinition, HarmonyOption, HarmonyStrategy, HarmonyType, LIGHTNESS_LADDER, RgbResult, STEP_NAMES, StepName, applyHarmonyStrategy, assignStepByLightness, contrastLc, formatColorName, gamutMapOklchToHex, generateOklchPalette, getColorName, getColorNameWithHue, getHarmonySuggestions, hexToOklch, ladderDeviation, oklchStringToHex, oklchToHex, oklchToP3, oklchToSrgb, parseOklch, pickOn, suggestFunctionalColorHue, tintedEndpoints };
|
|
144
215
|
//# sourceMappingURL=index.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/color/colors.ts","../../src/color/color-harmony.ts"],"mappings":";UAkBiB,SAAA;EACf,CAAA;EACA,CAAA;EACA,CAAA;EACA,OAAA;EAHA;EAKA,aAAA;AAAA;;;;AAAa;iBAOC,WAAA,CAAY,CAAA,UAAW,CAAA,UAAW,CAAA,WAAY,SAAS;;;;;iBAmBvD,SAAA,CAAU,CAAA,UAAW,CAAA,UAAW,CAAA,WAAY,SAAS;;;;AAnBE;iBAsCvD,kBAAA,CACd,CAAA,UACA,CAAA,UACA,CAAA,UACA,KAAA;AAAA,cAUW,UAAA;AAAA,KACD,QAAA,WAAmB,UAAU;AAAA,cAE5B,YAAA;AAAA,KAWD,UAAA,WAAqB,YAAY;;;;iBAK7B,UAAA,CAAW,GAAA;EAAgB,CAAA;EAAW,CAAA;EAAW,CAAA;AAAA;;;;iBAajD,UAAA,CAAW,KAAA;EAAS,CAAA;EAAW,CAAA;EAAW,CAAA;AAAA;;;;AAhC+B;iBA8CzE,gBAAA,CAAiB,WAAmB;;;;AA7CX;iBA8DzB,eAAA,CAAgB,IAAY;;;;iBA+G5B,YAAA,CAAa,GAAW;AAhKxC;;;AAAA,iBAsLgB,mBAAA,CAAoB,GAAW;AAAA,cAclC,aAAA;AAAA,KASD,WAAA,WAAsB,aAAa;AAAA,UAE9B,iBAAA;EACf,KAAA;EACA,OAAA;EACA,YAAA;AAAA;AAAA,cAGW,mBAAA,EAAqB,MAAA,CAAO,WAAA,EAAa,iBAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/color/colors.ts","../../src/color/color-harmony.ts"],"mappings":";UAkBiB,SAAA;EACf,CAAA;EACA,CAAA;EACA,CAAA;EACA,OAAA;EAHA;EAKA,aAAA;AAAA;;;;AAAa;iBAOC,WAAA,CAAY,CAAA,UAAW,CAAA,UAAW,CAAA,WAAY,SAAS;;;;;iBAmBvD,SAAA,CAAU,CAAA,UAAW,CAAA,UAAW,CAAA,WAAY,SAAS;;;;AAnBE;iBAsCvD,kBAAA,CACd,CAAA,UACA,CAAA,UACA,CAAA,UACA,KAAA;AAAA,cAUW,UAAA;AAAA,KACD,QAAA,WAAmB,UAAU;AAAA,cAE5B,YAAA;AAAA,KAWD,UAAA,WAAqB,YAAY;;;;iBAK7B,UAAA,CAAW,GAAA;EAAgB,CAAA;EAAW,CAAA;EAAW,CAAA;AAAA;;;;iBAajD,UAAA,CAAW,KAAA;EAAS,CAAA;EAAW,CAAA;EAAW,CAAA;AAAA;;;;AAhC+B;iBA8CzE,gBAAA,CAAiB,WAAmB;;;;AA7CX;iBA8DzB,eAAA,CAAgB,IAAY;;;;iBA+G5B,YAAA,CAAa,GAAW;AAhKxC;;;AAAA,iBAsLgB,mBAAA,CAAoB,GAAW;AAAA,cAclC,aAAA;AAAA,KASD,WAAA,WAAsB,aAAa;AAAA,UAE9B,iBAAA;EACf,KAAA;EACA,OAAA;EACA,YAAA;AAAA;AAAA,cAGW,mBAAA,EAAqB,MAAA,CAAO,WAAA,EAAa,iBAAA;;;AAhNY;AAalE;;;;;;;;;AAAqE;AAcrE;cAyOa,gBAAA,EAAkB,MAAM,CAAC,QAAA;;;AAzOc;AAiBpD;;;;AAA4C;AA+G5C;;;;AAAwC;AAsBxC;;;;AAA+C;AAc/C;cAsGa,eAAA;EAAA,SAAwD,KAAA;EAAA,SAAA,IAAA;AAAA;AAAA,UAE3D,UAAA;EACR,CAAA;EACA,CAAA;EACA,CAAA;AAAA;AAhGF;;;;;AAAA,iBAwGgB,UAAA,CAAW,WAAA,WAAsB,UAAU;;;AArG7C;AAGd;iBAkIgB,qBAAA,CAAsB,SAAA,WAAoB,QAAQ;;;;;;;;;iBAuBlD,eAAA,CAAgB,IAAA,EAAM,QAAQ,EAAE,SAAA;;AAzJuB;AAoDvE;;;;AAA8C;AAiC9C;;;;;AAAsE;;;;;;iBA6FtD,oBAAA,CACd,KAAA,sBAA2B,MAAA,mBAC1B,MAAA,CAAO,QAAA;;iBAsFM,UAAA,CAAW,EAAA,UAAY,EAAU;AAxKjD;;;;AAAA,iBAgLgB,MAAA,CAAO,EAAA,UAAY,GAAY;AAhJ/C;;;;AAAkE;AAuBlE;;;;;AAvBA,iBAiKgB,eAAA,CAAgB,GAAA,UAAa,MAAA;EAAoB,KAAA;EAAe,IAAA;AAAA;;;;AAvkBhF;;KCdY,eAAA;AAAA,UAWK,aAAA;EACf,EAAA,EAAI,eAAe;EACnB,KAAA;EACA,WAAA;EACA,QAAA;AAAA;AAAA,cAGW,kBAAA,EAAoB,aAAa;ADE/B;AAOf;;AAPe,iBC0DC,oBAAA,CAAqB,OAAA,UAAiB,QAAA,EAAU,eAAe;;;;iBAgB/D,qBAAA,CACd,OAAA,WACC,KAAK;EAAG,QAAA,EAAU,eAAA;EAAiB,GAAA;EAAa,KAAA;EAAe,WAAA;AAAA;;;;cAYrD,uBAAA;EAAA;;;;;;;;iBAUG,yBAAA,CACd,IAAA,eAAmB,uBAAuB,EAC1C,YAAA"}
|
package/dist/color/index.mjs
CHANGED
|
@@ -332,18 +332,55 @@ const HARMONY_DEFINITIONS = {
|
|
|
332
332
|
primaryIndex: 1
|
|
333
333
|
}
|
|
334
334
|
};
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
335
|
+
/**
|
|
336
|
+
* The shared lightness ladder every ramp is built on (lightest → darkest).
|
|
337
|
+
*
|
|
338
|
+
* Fixed, NOT seed-derived. Two hues at the same step have to read equally light,
|
|
339
|
+
* otherwise one contrast table cannot serve every hue and a `muted` variant means
|
|
340
|
+
* something different depending on which role you ask. Only chroma and hue vary
|
|
341
|
+
* between ramps.
|
|
342
|
+
*
|
|
343
|
+
* The previous engine transposed this curve onto each seed, which let relative
|
|
344
|
+
* luminance at step 300 range from 0.253 (rust) to 0.384 (amber) across the
|
|
345
|
+
* shipped palette — the drift this replaces.
|
|
346
|
+
*
|
|
347
|
+
* Anchored steps are the one deliberate exception: see `generateOklchPalette`.
|
|
348
|
+
*/
|
|
349
|
+
const LIGHTNESS_LADDER = {
|
|
350
|
+
50: .98,
|
|
351
|
+
100: .95,
|
|
352
|
+
200: .9,
|
|
353
|
+
300: .83,
|
|
354
|
+
400: .74,
|
|
355
|
+
500: .65,
|
|
356
|
+
600: .54,
|
|
357
|
+
700: .47,
|
|
358
|
+
800: .38,
|
|
359
|
+
900: .29,
|
|
360
|
+
950: .21
|
|
361
|
+
};
|
|
362
|
+
/**
|
|
363
|
+
* Chroma a ramp decays *towards* beyond its outermost anchors, so the near-white
|
|
364
|
+
* and near-black ends keep a whisper of the hue instead of going flat grey. The
|
|
365
|
+
* dark end carries slightly more because chroma reads weaker against black.
|
|
366
|
+
*
|
|
367
|
+
* It is a CEILING, not a target — `Math.min(ENDPOINT_CHROMA.x, anchor.c)`. That
|
|
368
|
+
* distinction is the whole reason a neutral is authorable. The interpolation
|
|
369
|
+
* factor reaches exactly 1 at steps 50 and 950 for every anchor position, so as a
|
|
370
|
+
* target it made the endpoints these constants *unconditionally*: seeds at chroma
|
|
371
|
+
* 0.001, 0.002, 0.05 and 0.2 all produced a byte-identical step 50. A brand
|
|
372
|
+
* wanting plain white with grey panels had no seed that worked, and chroma 0 was
|
|
373
|
+
* worst of all — colorjs returns NaN hue for a true achromatic colour, which
|
|
374
|
+
* `hexToOklch` collapses to 0, so the "neutral" came out pink.
|
|
375
|
+
*
|
|
376
|
+
* As a ceiling, a low-chroma seed simply stays low and a chroma-0 seed stays 0 at
|
|
377
|
+
* every unanchored step, which makes the NaN-hue collapse invisible rather than
|
|
378
|
+
* something `hexToOklch` has to solve (its `|| 0` is load-bearing for
|
|
379
|
+
* `getColorName`).
|
|
380
|
+
*/
|
|
381
|
+
const ENDPOINT_CHROMA = {
|
|
382
|
+
light: .008,
|
|
383
|
+
dark: .015
|
|
347
384
|
};
|
|
348
385
|
/**
|
|
349
386
|
* Parses an OKLCH CSS string into numerical components.
|
|
@@ -367,69 +404,21 @@ function lerp(start, end, factor) {
|
|
|
367
404
|
return start + factor * (end - start);
|
|
368
405
|
}
|
|
369
406
|
/**
|
|
370
|
-
*
|
|
407
|
+
* Interpolate hue the short way round the wheel. A ramp anchored at 350° and 10°
|
|
408
|
+
* spans 20°, not 340° — the naive lerp sent it the long way through cyan.
|
|
371
409
|
*/
|
|
372
|
-
function
|
|
373
|
-
|
|
374
|
-
if (sortedAnchorSteps.length === 1) {
|
|
375
|
-
const anchorStep = sortedAnchorSteps[0];
|
|
376
|
-
const anchorL = parsedAnchors[anchorStep].l;
|
|
377
|
-
const defaultAnchorL = DEFAULT_LIGHTNESS_SCALE[anchorStep];
|
|
378
|
-
for (const step of STEP_NAMES) {
|
|
379
|
-
if (step === anchorStep) {
|
|
380
|
-
transposedScale[step] = anchorL;
|
|
381
|
-
continue;
|
|
382
|
-
}
|
|
383
|
-
const defaultStepL = DEFAULT_LIGHTNESS_SCALE[step];
|
|
384
|
-
if (defaultStepL > defaultAnchorL) transposedScale[step] = lerp(anchorL, .97, (defaultStepL - defaultAnchorL) / (1 - defaultAnchorL));
|
|
385
|
-
else transposedScale[step] = lerp(.05, anchorL, defaultStepL / defaultAnchorL);
|
|
386
|
-
}
|
|
387
|
-
return transposedScale;
|
|
388
|
-
}
|
|
389
|
-
for (const step of STEP_NAMES) {
|
|
390
|
-
const anchored = parsedAnchors[step];
|
|
391
|
-
if (anchored) {
|
|
392
|
-
transposedScale[step] = anchored.l;
|
|
393
|
-
continue;
|
|
394
|
-
}
|
|
395
|
-
let lowerStep = null;
|
|
396
|
-
let upperStep = null;
|
|
397
|
-
for (let i = sortedAnchorSteps.length - 1; i >= 0; i--) {
|
|
398
|
-
const candidate = sortedAnchorSteps[i];
|
|
399
|
-
if (candidate < step) {
|
|
400
|
-
lowerStep = candidate;
|
|
401
|
-
break;
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
for (const anchorStep of sortedAnchorSteps) if (anchorStep > step) {
|
|
405
|
-
upperStep = anchorStep;
|
|
406
|
-
break;
|
|
407
|
-
}
|
|
408
|
-
if (lowerStep !== null && upperStep !== null) {
|
|
409
|
-
const lowerL = parsedAnchors[lowerStep].l;
|
|
410
|
-
const upperL = parsedAnchors[upperStep].l;
|
|
411
|
-
const span = upperStep - lowerStep;
|
|
412
|
-
transposedScale[step] = lerp(lowerL, upperL, (step - lowerStep) / span);
|
|
413
|
-
} else if (lowerStep !== null) {
|
|
414
|
-
const lowerL = parsedAnchors[lowerStep].l;
|
|
415
|
-
const defaultLowerL = DEFAULT_LIGHTNESS_SCALE[lowerStep];
|
|
416
|
-
transposedScale[step] = lerp(.05, lowerL, DEFAULT_LIGHTNESS_SCALE[step] / defaultLowerL);
|
|
417
|
-
} else if (upperStep !== null) {
|
|
418
|
-
const upperL = parsedAnchors[upperStep].l;
|
|
419
|
-
const defaultUpperL = DEFAULT_LIGHTNESS_SCALE[upperStep];
|
|
420
|
-
transposedScale[step] = lerp(upperL, .97, (DEFAULT_LIGHTNESS_SCALE[step] - defaultUpperL) / (1 - defaultUpperL));
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
return transposedScale;
|
|
410
|
+
function lerpHue(start, end, factor) {
|
|
411
|
+
return (start + (((end - start) % 360 + 540) % 360 - 180) * factor + 360) % 360;
|
|
424
412
|
}
|
|
425
413
|
/**
|
|
426
|
-
*
|
|
414
|
+
* The ladder step whose lightness is closest to `lightness` — i.e. where a
|
|
415
|
+
* supplied colour naturally belongs.
|
|
427
416
|
*/
|
|
428
417
|
function assignStepByLightness(lightness) {
|
|
429
418
|
let closestStep = 500;
|
|
430
|
-
let minDiff = Math.abs(lightness -
|
|
419
|
+
let minDiff = Math.abs(lightness - LIGHTNESS_LADDER[500]);
|
|
431
420
|
for (const step of STEP_NAMES) {
|
|
432
|
-
const diff = Math.abs(lightness -
|
|
421
|
+
const diff = Math.abs(lightness - LIGHTNESS_LADDER[step]);
|
|
433
422
|
if (diff < minDiff) {
|
|
434
423
|
minDiff = diff;
|
|
435
424
|
closestStep = step;
|
|
@@ -438,68 +427,78 @@ function assignStepByLightness(lightness) {
|
|
|
438
427
|
return closestStep;
|
|
439
428
|
}
|
|
440
429
|
/**
|
|
441
|
-
*
|
|
442
|
-
*
|
|
430
|
+
* How far an anchored colour sits from its step's ladder lightness.
|
|
431
|
+
*
|
|
432
|
+
* The generator surfaces this as a warning: a large deviation is legal (the
|
|
433
|
+
* caller asked for that exact colour) but it means the hue will read heavier or
|
|
434
|
+
* lighter than its siblings at that step, which is worth knowing rather than
|
|
435
|
+
* discovering later.
|
|
436
|
+
*/
|
|
437
|
+
function ladderDeviation(step, lightness) {
|
|
438
|
+
return lightness - LIGHTNESS_LADDER[step];
|
|
439
|
+
}
|
|
440
|
+
/** Absolute chroma ceiling — beyond this nothing survives gamut mapping anyway. */
|
|
441
|
+
const MAX_CHROMA = .37;
|
|
442
|
+
/**
|
|
443
|
+
* Build a complete 11-step OKLCH ramp from one or more anchor colours.
|
|
444
|
+
*
|
|
445
|
+
* **Ladder-first with exact anchors.** Every anchor is reproduced *verbatim* at
|
|
446
|
+
* its step; every other step takes its lightness from `LIGHTNESS_LADDER`, with
|
|
447
|
+
* chroma and hue interpolated between the flanking anchors and decayed toward
|
|
448
|
+
* `ENDPOINT_CHROMA` beyond the outermost ones.
|
|
449
|
+
*
|
|
450
|
+
* The consequence is the point: a client's brand hex survives byte-for-byte,
|
|
451
|
+
* while the other ten steps stay directly comparable to every other hue. The
|
|
452
|
+
* deviation from the ladder is bounded and *local to anchored steps* — unlike
|
|
453
|
+
* the previous engine, which warped the whole ramp to fit its anchors so every
|
|
454
|
+
* step drifted.
|
|
455
|
+
*
|
|
456
|
+
* Chroma may come out beyond the sRGB gamut here; that is deliberate.
|
|
457
|
+
* `oklchStringToHex` gamut-maps with the CSS algorithm, which reduces chroma
|
|
458
|
+
* while holding lightness, so the ladder survives the conversion.
|
|
443
459
|
*/
|
|
444
460
|
function generateOklchPalette(input) {
|
|
445
461
|
let anchors = {};
|
|
446
|
-
if (typeof input === "string")
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
} else if (Array.isArray(input)) for (const colorStr of input) {
|
|
450
|
-
const step = assignStepByLightness(parseOklch(colorStr).l);
|
|
451
|
-
anchors[step] = colorStr;
|
|
452
|
-
}
|
|
453
|
-
else if (typeof input === "object") anchors = input;
|
|
462
|
+
if (typeof input === "string") anchors[assignStepByLightness(parseOklch(input).l)] = input;
|
|
463
|
+
else if (Array.isArray(input)) for (const colorStr of input) anchors[assignStepByLightness(parseOklch(colorStr).l)] = colorStr;
|
|
464
|
+
else if (typeof input === "object" && input !== null) anchors = { ...input };
|
|
454
465
|
else throw new Error("Input must be a color string, array of colors, or object with step assignments");
|
|
455
466
|
const parsedAnchors = {};
|
|
456
|
-
for (const key
|
|
457
|
-
const
|
|
458
|
-
const
|
|
467
|
+
for (const [key, value] of Object.entries(anchors)) parsedAnchors[Number(key)] = parseOklch(value);
|
|
468
|
+
const anchorSteps = Object.keys(parsedAnchors).map(Number).sort((a, b) => a - b);
|
|
469
|
+
const idx = (step) => STEP_NAMES.indexOf(step);
|
|
470
|
+
const lastIdx = STEP_NAMES.length - 1;
|
|
459
471
|
const palette = {};
|
|
460
472
|
for (const step of STEP_NAMES) {
|
|
461
473
|
if (parsedAnchors[step]) {
|
|
462
474
|
palette[step] = anchors[step];
|
|
463
475
|
continue;
|
|
464
476
|
}
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
const
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
h_int = lerp(startColor.h, endColor.h, factor);
|
|
486
|
-
} else if (startStep !== null) {
|
|
487
|
-
const a = parsedAnchors[startStep];
|
|
488
|
-
c_int = a.c;
|
|
489
|
-
h_int = a.h;
|
|
490
|
-
} else if (endStep !== null) {
|
|
491
|
-
const a = parsedAnchors[endStep];
|
|
492
|
-
c_int = a.c;
|
|
493
|
-
h_int = a.h;
|
|
477
|
+
const lighter = [...anchorSteps].reverse().find((s) => s < step);
|
|
478
|
+
const darker = anchorSteps.find((s) => s > step);
|
|
479
|
+
let chroma;
|
|
480
|
+
let hue;
|
|
481
|
+
if (lighter !== void 0 && darker !== void 0) {
|
|
482
|
+
const a = parsedAnchors[lighter];
|
|
483
|
+
const b = parsedAnchors[darker];
|
|
484
|
+
const factor = (idx(step) - idx(lighter)) / (idx(darker) - idx(lighter));
|
|
485
|
+
chroma = lerp(a.c, b.c, factor);
|
|
486
|
+
hue = lerpHue(a.h, b.h, factor);
|
|
487
|
+
} else if (lighter !== void 0) {
|
|
488
|
+
const a = parsedAnchors[lighter];
|
|
489
|
+
const factor = (idx(step) - idx(lighter)) / (lastIdx - idx(lighter));
|
|
490
|
+
chroma = lerp(a.c, Math.min(ENDPOINT_CHROMA.dark, a.c), factor);
|
|
491
|
+
hue = a.h;
|
|
492
|
+
} else if (darker !== void 0) {
|
|
493
|
+
const b = parsedAnchors[darker];
|
|
494
|
+
const factor = (idx(darker) - idx(step)) / idx(darker);
|
|
495
|
+
chroma = lerp(b.c, Math.min(ENDPOINT_CHROMA.light, b.c), factor);
|
|
496
|
+
hue = b.h;
|
|
494
497
|
} else {
|
|
495
|
-
|
|
496
|
-
|
|
498
|
+
chroma = .1;
|
|
499
|
+
hue = 0;
|
|
497
500
|
}
|
|
498
|
-
|
|
499
|
-
const dampeningFactor = l_int < .25 ? .3 + l_int / .25 * .7 : l_int > .75 ? .3 + (1 - l_int) / .25 * .7 : 1;
|
|
500
|
-
c_int *= dampeningFactor;
|
|
501
|
-
c_int = Math.min(c_int, .37);
|
|
502
|
-
palette[step] = `oklch(${(l_int * 100).toFixed(2) + "%"} ${c_int.toFixed(4)} ${h_int.toFixed(2)})`;
|
|
501
|
+
palette[step] = `oklch(${(LIGHTNESS_LADDER[step] * 100).toFixed(2) + "%"} ${Math.min(chroma, MAX_CHROMA).toFixed(4)} ${hue.toFixed(2)})`;
|
|
503
502
|
}
|
|
504
503
|
return palette;
|
|
505
504
|
}
|
|
@@ -529,17 +528,22 @@ function pickOn(bg, hue) {
|
|
|
529
528
|
* The tinted off-white / off-black endpoints for a hue — white/black with a
|
|
530
529
|
* whisper of the hue mixed in. These are the text/background tints every ramp
|
|
531
530
|
* needs (and the one exception strict brand kits allow).
|
|
531
|
+
*
|
|
532
|
+
* Derived from `LIGHTNESS_LADDER` and `ENDPOINT_CHROMA` rather than its own
|
|
533
|
+
* literals, so a strict tone kit's endpoints land on the same ladder rungs a
|
|
534
|
+
* generated ramp would have used. This is the single definition — callers must
|
|
535
|
+
* not hand-write their own near-white/near-black.
|
|
532
536
|
*/
|
|
533
|
-
function tintedEndpoints(hue, chroma
|
|
537
|
+
function tintedEndpoints(hue, chroma) {
|
|
534
538
|
return {
|
|
535
539
|
light: oklchToHex({
|
|
536
|
-
l:
|
|
537
|
-
c: Math.min(chroma, .02),
|
|
540
|
+
l: LIGHTNESS_LADDER[50],
|
|
541
|
+
c: Math.min(chroma ?? ENDPOINT_CHROMA.light, .02),
|
|
538
542
|
h: hue
|
|
539
543
|
}),
|
|
540
544
|
dark: oklchToHex({
|
|
541
|
-
l:
|
|
542
|
-
c: Math.min(chroma * 1.5, .03),
|
|
545
|
+
l: LIGHTNESS_LADDER[950],
|
|
546
|
+
c: Math.min((chroma ?? ENDPOINT_CHROMA.dark) * 1.5, .03),
|
|
543
547
|
h: hue
|
|
544
548
|
})
|
|
545
549
|
};
|
|
@@ -647,5 +651,5 @@ function suggestFunctionalColorHue(type, existingHues) {
|
|
|
647
651
|
}
|
|
648
652
|
|
|
649
653
|
//#endregion
|
|
650
|
-
export { COLOR_GROUPS, DEFAULT_FUNCTIONAL_HUES, HARMONY_DEFINITIONS, HARMONY_STRATEGIES, HARMONY_TYPES, STEP_NAMES, applyHarmonyStrategy, contrastLc, formatColorName, gamutMapOklchToHex, generateOklchPalette, getColorName, getColorNameWithHue, getHarmonySuggestions, hexToOklch, oklchStringToHex, oklchToHex, oklchToP3, oklchToSrgb, parseOklch, pickOn, suggestFunctionalColorHue, tintedEndpoints };
|
|
654
|
+
export { COLOR_GROUPS, DEFAULT_FUNCTIONAL_HUES, ENDPOINT_CHROMA, HARMONY_DEFINITIONS, HARMONY_STRATEGIES, HARMONY_TYPES, LIGHTNESS_LADDER, STEP_NAMES, applyHarmonyStrategy, assignStepByLightness, contrastLc, formatColorName, gamutMapOklchToHex, generateOklchPalette, getColorName, getColorNameWithHue, getHarmonySuggestions, hexToOklch, ladderDeviation, oklchStringToHex, oklchToHex, oklchToP3, oklchToSrgb, parseOklch, pickOn, suggestFunctionalColorHue, tintedEndpoints };
|
|
651
655
|
//# sourceMappingURL=index.mjs.map
|