@cssints/rybitten 0.0.0-stage → 0.2.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/LICENSE ADDED
@@ -0,0 +1,47 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alexei Accio
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ---
24
+
25
+ This package depends on RYBitten (https://github.com/meodai/RYBitten), distributed under this licence:
26
+
27
+ MIT License
28
+
29
+ Copyright (c) 2024 David Aerne
30
+
31
+ Permission is hereby granted, free of charge, to any person obtaining a copy
32
+ of this software and associated documentation files (the "Software"), to deal
33
+ in the Software without restriction, including without limitation the rights
34
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
35
+ copies of the Software, and to permit persons to whom the Software is
36
+ furnished to do so, subject to the following conditions:
37
+
38
+ The above copyright notice and this permission notice shall be included in all
39
+ copies or substantial portions of the Software.
40
+
41
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
42
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
43
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
44
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
45
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
46
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
47
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,126 @@
1
- # Temporary Holding Version
1
+ # @cssints/rybitten
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ [RYBitten](https://rybitten.space/) colours for [cssints](../../README.md), computed at build time. RYBitten (by meodai, David Aerne in its
4
+ licence; MIT) turns red-yellow-blue coordinates into RGB through a _cube_: the eight corners of a colour model (white, red, yellow,
5
+ orange, blue, violet, green, black). It ships 36 of them, from Boutet (1708) and Goethe to Itten, Munsell and Albers, so
6
+ the same hue is another colour in each. `color(hue, options)` gives one hex colour, `wheel(steps, options)` and
7
+ `shades(hue, lightness, options)` return plain DTCG data (a colour group, steps `"1"` to `"N"`) that you pass to
8
+ `createTokens` yourself, and `overrides(group)` turns a group into the override object of `createGlobalTheme`. The package
9
+ has no runtime and no `scope()`: like [@cssints/ramp](../ramp/README.md) and the sets of
10
+ [@cssints/tokens](../tokens/README.md), a group is data, so it goes under any name, sits beside other tokens in one call,
11
+ and its steps get `@property` rules, themes and `check.contrast` from cssints.
12
+
13
+ ```ts
14
+ // tokens.ts
15
+ import { createGlobalTheme, createTokens, media } from "cssints" with { type: "cssints" };
16
+ import { overrides, shades, wheel } from "@cssints/rybitten";
17
+
18
+ export const t = createTokens({
19
+ hue: wheel(12, { cube: "itten" }),
20
+ paper: shades(0, [1, 0.96, 0], { cube: "itten", saturation: 0 }),
21
+ });
22
+
23
+ // the paper and the ink swap in the dark
24
+ createGlobalTheme(t, media("(prefers-color-scheme: dark)"), {
25
+ paper: overrides(shades(0, [0, 0.04, 1], { cube: "itten", saturation: 0 })),
26
+ });
27
+ ```
28
+
29
+ ```ts
30
+ // text.ts
31
+ import { bg, check, cn, color } from "cssints" with { type: "cssints" };
32
+
33
+ import { t } from "./tokens.ts";
34
+
35
+ check.contrast(t.paper._3, t.paper._1, { min: 12 }); // the ink on the paper, light and dark
36
+ export const text = cn(color(t.hue._9), bg(t.paper._1));
37
+ ```
38
+
39
+ The sheet of `text.ts`:
40
+
41
+ ```css
42
+ @property --hue-9 {
43
+ syntax: "<color>";
44
+ inherits: true;
45
+ initial-value: #1699da;
46
+ }
47
+ @property --paper-1 {
48
+ syntax: "<color>";
49
+ inherits: true;
50
+ initial-value: #fdf6ed;
51
+ }
52
+ @layer _.k {
53
+ @media (prefers-color-scheme: dark) {
54
+ :root {
55
+ --paper-1: #1d1c1c;
56
+ --paper-2: #1e1d1d;
57
+ --paper-3: #fdf6ed;
58
+ }
59
+ }
60
+ }
61
+ @layer _.a {
62
+ ._14bfx2y {
63
+ color: var(--hue-9);
64
+ }
65
+ ._1j4wng5 {
66
+ background-color: var(--paper-1);
67
+ }
68
+ }
69
+ ```
70
+
71
+ ## The functions
72
+
73
+ - **`color(hue, { cube?, saturation?, lightness? })`**: the hex colour of a hue. The hue is degrees on a painter's wheel,
74
+ not the HSL wheel: `0` red, `120` yellow, `240` blue (Itten: `#e32421`, `#f3e600`, `#1699da`), any number, `360` is `0`.
75
+ Saturation is `0` to `1` (default `1`; `0` gives the cube's greys). Lightness is `0` to `1` (default `0.5`): `1` is the
76
+ cube's white and `0` its black, because RYB is subtractive (RYBitten's `invertLightness`). The cube is a key of
77
+ RYBitten's `cubes` map (default `"itten"`) or your own eight corners.
78
+ - **`wheel(steps, { from?, cube?, saturation?, lightness? })`**: `steps` hues evenly round the wheel from `from` degrees
79
+ (default `0`): `wheel(12)` is Itten's twelve-part circle, red, red-orange, orange, ... `"1"` to `"12"`.
80
+ - **`shades(hue, lightness, { cube?, saturation? })`**: one hue at the lightnesses you list, in that order, `"1"` to
81
+ `"N"` for N values. With `saturation: 0` it is the cube's own grey scale, from its paper white to its ink black.
82
+ - **`overrides(group)`**: `{ _1: hex, … }` for `createGlobalTheme`, as cssints reads a key that starts with a digit.
83
+
84
+ A group is not a contrast ramp: the colours of a cube are what its makers painted, and no ratio is promised. For a
85
+ colour that must pass `check.contrast`, use a hue of the wheel as the base of an `@cssints/ramp` contrast ramp, which
86
+ finds the step with the ratio you need (the docs site does this for its accent, see below), or check the pair as above.
87
+
88
+ ## Cubes
89
+
90
+ `cubes` is RYBitten's own map (`import { cubes } from "rybitten/cubes"`): `itten` (the default), `munsell`, `bezold`,
91
+ `goethe`, `albers`, `apple90s`, `harris`, `cmy`, ... with title, author, year and reference. The hue `240` in four of them:
92
+
93
+ | Cube | Blue | Paper | Ink |
94
+ | --------- | --------- | --------- | --------- |
95
+ | `itten` | `#1699da` | `#fdf6ed` | `#1d1c1c` |
96
+ | `munsell` | `#3b8fab` | `#e4dac5` | `#2e2c26` |
97
+ | `albers` | `#1161aa` | `#e7ebed` | `#000001` |
98
+ | `goethe` | `#5f9dbf` | `#efebe1` | `#08090d` |
99
+
100
+ ## Limits
101
+
102
+ - **Pinned.** `rybitten` is a dependency at `1.3.0`: RYBitten's contract does not cover the numbers of a cube, which a
103
+ minor release may refine, so a colour can move with an upgrade (the tests pin Itten's corners).
104
+ - **Errors are thrown** when the module is evaluated, so they are build errors: a cube that is not in the map
105
+ (`@cssints/rybitten: no cube "nope" in RYBitten's cubes`), a hue that is not a number, a saturation or lightness outside
106
+ 0..1, `steps` that is not a whole number from 1.
107
+ - **sRGB hex only.** A cube holds sRGB corners, so the colours are in sRGB (trilinear interpolation stays inside the
108
+ cube), and a hex is what `check.contrast` reads exactly and the types accept cheaply.
109
+ - **No harmonies.** A complement is `hue + 180`, a triad `+ 120`: `color(h + 180)`, or the steps of a `wheel(3)`.
110
+ - **The editor shows `` `#${string}` ``.** A value is computed when the module runs, so its type is a hex string, not the
111
+ literal (as for @cssints/ramp). The steps are known (`t.hue._13` of a twelve-step wheel is a type error).
112
+ - **Type cost.** A step is a `` `#${string}` `` token like a step of @cssints/ramp: the whole type test (a wheel of twelve, a
113
+ run of four, their overrides in a theme) is 0.19 M instantiations in tsgo, as many as the type test of ramp.
114
+ - **No runtime.** A hue picked in the browser needs one of the steps built ahead (the docs site builds a theme per step of
115
+ a twelve-hue wheel and switches them with an attribute) or `rybitten` itself in the page, which this package does not ship.
116
+
117
+ ## Credits
118
+
119
+ Colour maths and the historical cubes are [RYBitten](https://github.com/meodai/RYBitten) by meodai (David Aerne; MIT), a
120
+ dependency of this package, whose licence is reproduced in LICENSE here. The cubes
121
+ cite their historical sources: see RYBitten's README.
122
+
123
+ ```bash
124
+ cd packages/rybitten && bun run test # the colours and the errors, then a module through the engine (check.contrast passes, a hue on paper fails)
125
+ cd packages/rybitten && bun run typecheck # the type test (test/types/rybitten.ts)
126
+ ```
@@ -0,0 +1,54 @@
1
+ import { ColorCube } from "rybitten/cubes";
2
+ //#region src/index.d.ts
3
+ /** A hex colour: cheap in the types (createTokens checks it without the walk of every colour syntax). */
4
+ export type Hex = `#${string}`;
5
+ /** The step names of an N-step group: `"1"` to `"N"` (`t.wheel._1` in code, `--wheel-1` in CSS). */
6
+ export type Keys<N extends number, A extends string[] = []> = number extends N ? `${number}` : A["length"] extends N ? A[number] : Keys<N, [...A, `${[...A, 0]["length"]}`]>;
7
+ /** A colour group: a DTCG group whose tokens are the steps. */
8
+ export type Palette<K extends string> = {
9
+ readonly $type: "color";
10
+ } & { readonly [P in K]: {
11
+ readonly $value: Hex;
12
+ }; };
13
+ /** The overrides of a group for createGlobalTheme: a step `"1"` is `_1`, as cssints reads a key that starts with a digit. */
14
+ export type PaletteOverrides<K extends string> = { readonly [P in K as `_${P}`]: Hex; };
15
+ /** Which colours a hue becomes. */
16
+ export interface Options {
17
+ /** A key of RYBitten's `cubes` (`"itten"`, `"munsell"`, `"albers"`, ...) or a cube of eight RGB corners; default `"itten"`. */
18
+ readonly cube?: string | ColorCube;
19
+ /** HSL saturation from 0 (the cube's greys) to 1; default 1. */
20
+ readonly saturation?: number;
21
+ /** HSL lightness from 0 (the cube's black) to 1 (its white); default 0.5. */
22
+ readonly lightness?: number;
23
+ }
24
+ /**
25
+ * The sRGB hex colour of a hue in a cube.
26
+ * @param {number} hue degrees on the RYB wheel, any number (360 is 0)
27
+ * @param {Options} options the cube, saturation and lightness
28
+ * @returns {Hex} `#rrggbb`
29
+ */
30
+ export declare const color: (hue: number, options?: Options) => `#${string}`;
31
+ /**
32
+ * `steps` hues evenly round the wheel, from `from` degrees (default 0, red), as a colour group `"1"` to `"N"`.
33
+ * @param {number} steps how many hues, a whole number from 1
34
+ * @param {Options & { from?: number }} options the cube, saturation and lightness of every step
35
+ * @returns {Palette<Keys<N>>} a DTCG colour group for createTokens
36
+ */
37
+ export declare const wheel: <const N extends number>(steps: N, { from, ...options }?: Options & {
38
+ readonly from?: number;
39
+ }) => Palette<Keys<N, []>>;
40
+ /**
41
+ * One hue at several lightnesses, one step per value, in the order given (1 is the cube's white, 0 its black).
42
+ * @param {number} hue degrees on the RYB wheel
43
+ * @param {readonly number[]} lightness the lightness of each step, from 0 to 1
44
+ * @param {Omit<Options, "lightness">} options the cube and the saturation
45
+ * @returns {Palette<Keys<R["length"]>>} a DTCG colour group for createTokens
46
+ */
47
+ export declare const shades: <const R extends readonly number[]>(hue: number, lightness: R, options?: Omit<Options, "lightness">) => Palette<Keys<R["length"], []>>;
48
+ /**
49
+ * A group as the overrides of a theme: `createGlobalTheme(t.wheel, dark, overrides(wheel(12, { cube: "munsell" })))`.
50
+ * @param {Palette<K>} p a group of `wheel` or `shades`
51
+ * @returns {PaletteOverrides<K>} its steps as `{ _1: hex, … }`
52
+ */
53
+ export declare const overrides: <K extends string>(p: Palette<K>) => PaletteOverrides<K>;
54
+ //#endregion
package/dist/index.mjs ADDED
@@ -0,0 +1,55 @@
1
+ import { rybHsl2rgb } from "rybitten";
2
+ import { cubes } from "rybitten/cubes";
3
+ //#region src/index.ts
4
+ const fail = (why) => {
5
+ throw new Error(`@cssints/rybitten: ${why}`);
6
+ };
7
+ const cubeOf = (cube = "itten") => typeof cube === "string" ? cubes.get(cube)?.cube ?? fail(`no cube "${cube}" in RYBitten's cubes`) : cube;
8
+ const unit = (x, what) => x >= 0 && x <= 1 ? x : fail(`${what} is from 0 to 1, got ${x}`);
9
+ const channel = (x) => Math.round(Math.min(1, Math.max(0, x)) * 255).toString(16).padStart(2, "0");
10
+ const hex = (rgb) => `#${rgb.map(channel).join("")}`;
11
+ /**
12
+ * The sRGB hex colour of a hue in a cube.
13
+ * @param {number} hue degrees on the RYB wheel, any number (360 is 0)
14
+ * @param {Options} options the cube, saturation and lightness
15
+ * @returns {Hex} `#rrggbb`
16
+ */
17
+ const color = (hue, options = {}) => {
18
+ const { cube, saturation = 1, lightness = .5 } = options;
19
+ if (!Number.isFinite(hue)) fail(`hue is a number of degrees, got ${hue}`);
20
+ return hex(rybHsl2rgb([
21
+ (hue % 360 + 360) % 360,
22
+ unit(saturation, "saturation"),
23
+ unit(lightness, "lightness")
24
+ ], { cube: cubeOf(cube) }));
25
+ };
26
+ const group = (colors) => Object.fromEntries([["$type", "color"], ...colors.map((c, i) => [String(i + 1), { $value: c }])]);
27
+ /**
28
+ * `steps` hues evenly round the wheel, from `from` degrees (default 0, red), as a colour group `"1"` to `"N"`.
29
+ * @param {number} steps how many hues, a whole number from 1
30
+ * @param {Options & { from?: number }} options the cube, saturation and lightness of every step
31
+ * @returns {Palette<Keys<N>>} a DTCG colour group for createTokens
32
+ */
33
+ const wheel = (steps, { from = 0, ...options } = {}) => {
34
+ if (!Number.isInteger(steps) || steps < 1) fail(`steps is a whole number from 1, got ${steps}`);
35
+ return group(Array.from({ length: steps }, (_, i) => color(from + 360 * i / steps, options)));
36
+ };
37
+ /**
38
+ * One hue at several lightnesses, one step per value, in the order given (1 is the cube's white, 0 its black).
39
+ * @param {number} hue degrees on the RYB wheel
40
+ * @param {readonly number[]} lightness the lightness of each step, from 0 to 1
41
+ * @param {Omit<Options, "lightness">} options the cube and the saturation
42
+ * @returns {Palette<Keys<R["length"]>>} a DTCG colour group for createTokens
43
+ */
44
+ const shades = (hue, lightness, options = {}) => group(lightness.map((l) => color(hue, {
45
+ ...options,
46
+ lightness: l
47
+ })));
48
+ /**
49
+ * A group as the overrides of a theme: `createGlobalTheme(t.wheel, dark, overrides(wheel(12, { cube: "munsell" })))`.
50
+ * @param {Palette<K>} p a group of `wheel` or `shades`
51
+ * @returns {PaletteOverrides<K>} its steps as `{ _1: hex, … }`
52
+ */
53
+ const overrides = (p) => Object.fromEntries(Object.entries(p).flatMap(([k, v]) => k === "$type" ? [] : [[`_${k}`, v.$value]]));
54
+ //#endregion
55
+ export { color, overrides, shades, wheel };
package/package.json CHANGED
@@ -1,6 +1,35 @@
1
1
  {
2
2
  "name": "@cssints/rybitten",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "RYB palettes for cssints as DTCG data: a hue wheel and ramps in RYBitten's historical colour cubes, at build time",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/alexeiaccio/css-in-ts-compiler.git",
9
+ "directory": "packages/rybitten"
10
+ },
11
+ "homepage": "https://cssints.dev",
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "type": "module",
16
+ "sideEffects": false,
17
+ "exports": {
18
+ ".": {
19
+ "types": "./dist/index.d.mts",
20
+ "default": "./dist/index.mjs"
21
+ }
22
+ },
23
+ "dependencies": {
24
+ "rybitten": "1.3.0"
25
+ },
26
+ "peerDependencies": {
27
+ "cssints": "^0.2.0"
28
+ },
29
+ "engines": {
30
+ "node": ">=22"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ }
35
+ }