color-value-tools 1.0.1 → 1.1.1
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/README.md +343 -70
- package/dist/cjs/index.d.ts +397 -0
- package/dist/cjs/index.js +1410 -0
- package/dist/cli/bin/cli.js +179 -0
- package/dist/cli/src/index.js +1410 -0
- package/dist/esm/index.js +1310 -0
- package/dist/esm/package.json +1 -0
- package/package.json +57 -47
- package/dist/index.d.ts +0 -203
- package/dist/index.js +0 -590
package/README.md
CHANGED
|
@@ -1,70 +1,343 @@
|
|
|
1
|
-
# color-value-tools
|
|
2
|
-
|
|
3
|
-
A
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm install color-value-tools
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Usage
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
1
|
+
# color-value-tools
|
|
2
|
+
|
|
3
|
+
A comprehensive utility library for parsing, converting, manipulating, and analyzing color values across all major color models — hex, RGB, HSL, HSV, HWB, Lab, LCH, OKLAB, OKLCH, CMYK — plus CSS variables and named colors.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install color-value-tools
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
import {
|
|
15
|
+
normalizeColor, mixColors,
|
|
16
|
+
lighten, darken, saturate,
|
|
17
|
+
complement, triadic,
|
|
18
|
+
wcagLevel, bestTextColor,
|
|
19
|
+
colorDeltaE, randomColor,
|
|
20
|
+
} from 'color-value-tools';
|
|
21
|
+
|
|
22
|
+
// Parse any color format
|
|
23
|
+
normalizeColor('#3498db');
|
|
24
|
+
// { type: 'hex', hex: '#3498db', r: 52, g: 152, b: 219, h: 204, s: 70, l: 53, ... }
|
|
25
|
+
|
|
26
|
+
// Manipulate
|
|
27
|
+
lighten('#3498db', 15); // '#6ab4e8'
|
|
28
|
+
darken('#3498db', 15); // '#1a6da3'
|
|
29
|
+
saturate('#3498db', 20); // '#1a8fe8'
|
|
30
|
+
|
|
31
|
+
// Harmonies
|
|
32
|
+
complement('#3498db'); // '#db6034'
|
|
33
|
+
triadic('#3498db'); // ['#3498db', '#db3498', '#98db34']
|
|
34
|
+
|
|
35
|
+
// WCAG accessibility
|
|
36
|
+
wcagLevel('#ffffff', '#3498db'); // 'AA'
|
|
37
|
+
bestTextColor('#3498db'); // '#ffffff'
|
|
38
|
+
|
|
39
|
+
// Perceptual color distance (CIEDE2000)
|
|
40
|
+
colorDeltaE('#ff0000', '#fe0000'); // ~0.9
|
|
41
|
+
|
|
42
|
+
// Random color
|
|
43
|
+
randomColor({ hRange: [200, 260], sRange: [60, 80] });
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
CommonJS:
|
|
47
|
+
```js
|
|
48
|
+
const { normalizeColor, mixColors } = require('color-value-tools');
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## API Reference
|
|
54
|
+
|
|
55
|
+
### Detection
|
|
56
|
+
|
|
57
|
+
| Function | Description |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `getColorType(value)` | Returns `'hex'` \| `'css-var'` \| `'rgb'` \| `'hsl'` \| `'named'` \| `'oklch'` \| `'color'` \| `'unknown'` |
|
|
60
|
+
| `isHexColor(value)` | Detects 3-, 4-, 6- or 8-digit hex strings (with or without `#`) |
|
|
61
|
+
| `isRgbColor(value)` | Detects `rgb()` / `rgba()` strings |
|
|
62
|
+
| `isHslColor(value)` | Detects `hsl()` / `hsla()` strings |
|
|
63
|
+
| `isOklchColor(value)` | Detects `oklch()` / `oklcha()` strings |
|
|
64
|
+
| `isColorFunction(value)` | Detects `color(display-p3 ...)` / `color(srgb ...)` strings |
|
|
65
|
+
| `isCssVariable(value)` | Checks for `var(--name)` pattern |
|
|
66
|
+
| `extractCssVariableName(value)` | Extracts `--name` from `var(--name, fallback)` |
|
|
67
|
+
|
|
68
|
+
### Parsing & Normalization
|
|
69
|
+
|
|
70
|
+
| Function | Description |
|
|
71
|
+
|---|---|
|
|
72
|
+
| `normalizeColor(input)` | Universal parser. Accepts hex, `rgb()`, `hsl()`, `oklch()`, `color()`, named color, `{r,g,b}` / `{h,s,l}`. Returns `{ type, hex, r, g, b, a, h, s, l, v }` |
|
|
73
|
+
| `normalizeColorCached(input)` | Same as `normalizeColor` but uses an internal LRU-style cache |
|
|
74
|
+
| `normalizeHex(hex)` | Normalizes 3- or 6-digit hex to lowercase 6-digit with `#` |
|
|
75
|
+
| `rgbaStringToRgba(str)` | Parses `rgb()` / `rgba()` string to `{r, g, b, a}` |
|
|
76
|
+
| `hex8ToRgba(hex)` | Parses 8-digit hex (`#rrggbbaa`) to `{r, g, b, a}` |
|
|
77
|
+
| `shortHexToRgba(hex)` | Parses 4-digit hex (`#rgba`) to `{r, g, b, a}` — e.g. `#f0f0` → `{r:255,g:0,b:255,a:0}` |
|
|
78
|
+
| `parseHwbString(str)` | Parses `hwb()` string to `{H, W, B, alpha}` |
|
|
79
|
+
| `parseOklchString(str)` | Parses `oklch(L C H / alpha)` to `{L, C, H, alpha}` |
|
|
80
|
+
| `parseColorFn(str)` | Parses `color(display-p3 r g b / alpha)` to `{space, r, g, b, alpha}` |
|
|
81
|
+
| `parseCssVar(value)` | Parses `var(--name, fallback)` to `{variableName, fallback?}` |
|
|
82
|
+
|
|
83
|
+
### Conversions
|
|
84
|
+
|
|
85
|
+
| Function | Description |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `hexToRgb(hex)` | `→ [r, g, b]` |
|
|
88
|
+
| `hexToRgba(hex, opacity)` | `→ rgba(...)` string |
|
|
89
|
+
| `hexToHsl(hex)` | `→ [h, s, l]` |
|
|
90
|
+
| `hexToHsv(hex)` | `→ [h, s, v]` |
|
|
91
|
+
| `hslToHex(h, s, l)` | `→ #rrggbb` |
|
|
92
|
+
| `hslToRgb(h, s, l)` | `→ {r, g, b}` |
|
|
93
|
+
| `hsvToHex(h, s, v)` | `→ #rrggbb` |
|
|
94
|
+
| `hsvToRgb(h, s, v)` | `→ {r, g, b}` |
|
|
95
|
+
| `rgbToHex({r,g,b})` | `→ #rrggbb` |
|
|
96
|
+
| `rgbaToHex({r,g,b,a})` | `→ #rrggbbaa` |
|
|
97
|
+
| `rgbToRgbaString({r,g,b}, a)` | `→ rgba(...)` string |
|
|
98
|
+
| `rgbToHsl({r,g,b})` | `→ [h, s, l]` |
|
|
99
|
+
| `rgbToHsv({r,g,b})` | `→ [h, s, v]` |
|
|
100
|
+
| `rgbToHwb({r,g,b})` | `→ [H, W, B]` |
|
|
101
|
+
| `hwbToRgb(H, W, B)` | `→ {r, g, b}` |
|
|
102
|
+
| `rgbToCmyk({r,g,b})` | `→ {c, m, y, k}` (0–1 range) |
|
|
103
|
+
| `cmykToRgb({c,m,y,k})` | `→ {r, g, b}` |
|
|
104
|
+
| `rgbToLab({r,g,b})` | `→ {L, a, b}` (CIE Lab D65) |
|
|
105
|
+
| `labToRgb({L,a,b})` | `→ {r, g, b}` |
|
|
106
|
+
| `rgbToLch({r,g,b})` | `→ {L, C, H}` (CIE LCH) |
|
|
107
|
+
| `lchToRgb({L,C,H})` | `→ {r, g, b}` |
|
|
108
|
+
| `rgbToOklab({r,g,b})` | `→ {L, a, b}` (Oklab) |
|
|
109
|
+
| `oklabToRgb({L,a,b})` | `→ {r, g, b}` |
|
|
110
|
+
| `rgbToOklch({r,g,b})` | `→ {L, C, H}` (Oklch) |
|
|
111
|
+
| `oklchToRgb({L,C,H})` | `→ {r, g, b}` |
|
|
112
|
+
| `rgbToDisplayP3({r,g,b})` | `→ {r, g, b}` in Display P3 space (0–1 per channel) |
|
|
113
|
+
| `displayP3ToRgb({r,g,b})` | `→ {r, g, b}` sRGB (0–255) |
|
|
114
|
+
| `toDisplayP3Hex(color)` | Converts any color → Display P3 → hex |
|
|
115
|
+
| `rgbaToHex8({r,g,b,a})` | Alias for `rgbaToHex` |
|
|
116
|
+
|
|
117
|
+
### Manipulation
|
|
118
|
+
|
|
119
|
+
| Function | Description |
|
|
120
|
+
|---|---|
|
|
121
|
+
| `lighten(color, amount)` | Increase HSL lightness by `amount` (0–100) |
|
|
122
|
+
| `darken(color, amount)` | Decrease HSL lightness by `amount` (0–100) |
|
|
123
|
+
| `saturate(color, amount)` | Increase HSL saturation by `amount` (0–100) |
|
|
124
|
+
| `desaturate(color, amount)` | Decrease HSL saturation by `amount` (0–100) |
|
|
125
|
+
| `setAlpha(color, alpha)` | Returns `rgba(...)` with the given alpha (0–1) |
|
|
126
|
+
| `getAlpha(color)` | Returns the alpha channel value (0–1) |
|
|
127
|
+
| `invertColor(color)` | Inverts RGB channels |
|
|
128
|
+
| `grayscale(color)` | Converts to grayscale using ITU-R BT.709 weights |
|
|
129
|
+
| `rotateHue(hex, degrees)` | Rotates hue by degrees (supports negative values) |
|
|
130
|
+
| `adjustHexBrightness(hex, offsetPercent)` | Lightens (positive) or darkens (negative) by percentage |
|
|
131
|
+
| `mixColors(c1, c2, t, opts?)` | Interpolates between two colors. `t` = 0–1. `mode`: `'rgb'`\|`'hsl'`\|`'lab'`\|`'lch'`\|`'oklab'`\|`'oklch'`. `hueInterpolation`: `'shorter'`\|`'longer'`\|`'increasing'`\|`'decreasing'` |
|
|
132
|
+
|
|
133
|
+
### Interpolation & Scales
|
|
134
|
+
|
|
135
|
+
| Function | Description |
|
|
136
|
+
|---|---|
|
|
137
|
+
| `interpolateColors(c1, c2, steps, opts?)` | Returns array of `steps` colors from `c1` to `c2`. Same `space`/`format`/`hueInterpolation` options as `mixColors` |
|
|
138
|
+
| `createColorScale(anchors, steps, opts?)` | Generates a `steps`-color scale across multiple anchor colors with optional positions (0–1) |
|
|
139
|
+
| `midpointColor(c1, c2, opts?)` | Perceptual midpoint between two colors (default space: `'oklab'`) |
|
|
140
|
+
|
|
141
|
+
### Color Harmonies
|
|
142
|
+
|
|
143
|
+
| Function | Description |
|
|
144
|
+
|---|---|
|
|
145
|
+
| `complement(color)` | Complementary color (180° rotation) |
|
|
146
|
+
| `triadic(color)` | 3 colors evenly spaced 120° apart |
|
|
147
|
+
| `analogous(color, angle?)` | 3 neighboring colors (default ±30°) |
|
|
148
|
+
| `splitComplementary(color)` | Base + two colors at 150° and 210° |
|
|
149
|
+
| `tetradic(color)` | 4 colors evenly spaced 90° apart |
|
|
150
|
+
|
|
151
|
+
### Palette Generation
|
|
152
|
+
|
|
153
|
+
| Function | Description |
|
|
154
|
+
|---|---|
|
|
155
|
+
| `colorShades(color, steps?)` | Light-to-dark HSL scale (default 9 steps) |
|
|
156
|
+
| `monochromatic(color, steps?)` | Varying saturation at fixed lightness (default 5 steps) |
|
|
157
|
+
| `tints(color, steps?)` | Mix toward white in Oklab (default 5 steps) |
|
|
158
|
+
| `shades(color, steps?)` | Mix toward black in Oklab (default 5 steps) |
|
|
159
|
+
| `tones(color, steps?, gray?)` | Mix toward gray in Oklab (default 5 steps, gray `#808080`) |
|
|
160
|
+
|
|
161
|
+
### Accessibility (WCAG)
|
|
162
|
+
|
|
163
|
+
| Function | Description |
|
|
164
|
+
|---|---|
|
|
165
|
+
| `relativeLuminance(color)` | WCAG relative luminance (0–1) |
|
|
166
|
+
| `contrastRatio(c1, c2)` | WCAG contrast ratio (1–21) |
|
|
167
|
+
| `wcagLevel(fg, bg)` | Returns `'AAA'` \| `'AA'` \| `'AA-large'` \| `'fail'` |
|
|
168
|
+
| `bestTextColor(bg)` | Returns `'#000000'` or `'#ffffff'` for best contrast on `bg` |
|
|
169
|
+
| `bestContrastColor(bg, candidates)` | Picks the most readable color from an array of candidates |
|
|
170
|
+
| `bestContrastPalette(bg, palettes, opts?)` | Picks the palette with best overall contrast. Returns `{paletteIndex, palette, minContrastRatio, avgContrastRatio}` |
|
|
171
|
+
| `isReadableOnBackground(text, bg, opts?)` | Checks readability on solid, semi-transparent, or gradient backgrounds. Returns `{readable, minContrastRatio, wcagLevel}` |
|
|
172
|
+
| `isDark(color, threshold?)` | `true` if luminance is below threshold (default 0.5) |
|
|
173
|
+
| `isLight(color, threshold?)` | Inverse of `isDark` |
|
|
174
|
+
|
|
175
|
+
### Color Blindness Simulation
|
|
176
|
+
|
|
177
|
+
Simulates perception using Vienot 1999 matrices applied to linear RGB.
|
|
178
|
+
|
|
179
|
+
| Function | Description |
|
|
180
|
+
|---|---|
|
|
181
|
+
| `simulateProtanopia(color)` | No L-cones (red-blind) |
|
|
182
|
+
| `simulateDeuteranopia(color)` | No M-cones (green-blind) |
|
|
183
|
+
| `simulateTritanopia(color)` | No S-cones (blue-blind) |
|
|
184
|
+
| `simulateColorBlindness(color, type)` | Generic — `type`: `'protanopia'` \| `'deuteranopia'` \| `'tritanopia'` |
|
|
185
|
+
|
|
186
|
+
### Formatting
|
|
187
|
+
|
|
188
|
+
| Function | Description |
|
|
189
|
+
|---|---|
|
|
190
|
+
| `toHslString(h, s, l, alpha?)` | Formats as `hsl(...)` or `hsla(...)` |
|
|
191
|
+
| `toHwbString(H, W, B, alpha?)` | Formats as `hwb(...)` with optional alpha |
|
|
192
|
+
| `toOklchString(color, alpha?)` | Converts any color to `oklch(L C H)` CSS string |
|
|
193
|
+
| `toColorP3String(color, alpha?)` | Converts any color to `color(display-p3 r g b)` CSS string |
|
|
194
|
+
|
|
195
|
+
### Cache Management
|
|
196
|
+
|
|
197
|
+
| Function | Description |
|
|
198
|
+
|---|---|
|
|
199
|
+
| `normalizeColorCached(input)` | Cached version of `normalizeColor` for repeated calls |
|
|
200
|
+
| `clearColorCache()` | Clears the normalization cache |
|
|
201
|
+
| `getCacheStats()` | Returns `{size, hits}` |
|
|
202
|
+
| `enableCache()` / `disableCache()` | Toggle caching on/off |
|
|
203
|
+
|
|
204
|
+
### Generator Functions
|
|
205
|
+
|
|
206
|
+
Useful for large palettes and frame-by-frame animations without allocating full arrays.
|
|
207
|
+
|
|
208
|
+
| Function | Description |
|
|
209
|
+
|---|---|
|
|
210
|
+
| `generateGradientColors*(start, end, steps, opts?)` | Yields `steps` colors from `start` to `end` |
|
|
211
|
+
| `generateTints*(color, steps, opts?)` | Yields tints toward white |
|
|
212
|
+
| `generateShades*(color, steps, opts?)` | Yields shades toward black |
|
|
213
|
+
|
|
214
|
+
### Utilities
|
|
215
|
+
|
|
216
|
+
| Function | Description |
|
|
217
|
+
|---|---|
|
|
218
|
+
| `colorDeltaE(c1, c2)` | Perceptual color distance using CIEDE2000 |
|
|
219
|
+
| `randomColor(options?)` | Generates a random color. Options: `hRange`, `sRange`, `lRange` (each `[min, max]`) |
|
|
220
|
+
| `toNearestNamedColor(color)` | Returns the closest CSS named color name (all 148 standard colors) |
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## CLI
|
|
225
|
+
|
|
226
|
+
After installing globally or via `npx`, you can inspect colors directly from the terminal:
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
npm install -g color-value-tools
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
# Full info (default)
|
|
234
|
+
cvt "#3498db"
|
|
235
|
+
|
|
236
|
+
# All format conversions
|
|
237
|
+
cvt "#3498db" convert
|
|
238
|
+
|
|
239
|
+
# Contrast ratio and WCAG level
|
|
240
|
+
cvt "#3498db" contrast "#ffffff"
|
|
241
|
+
|
|
242
|
+
# Generate 7 shades
|
|
243
|
+
cvt "#3498db" shades 7
|
|
244
|
+
|
|
245
|
+
# All harmonies
|
|
246
|
+
cvt "#3498db" harmonies
|
|
247
|
+
|
|
248
|
+
# Nearest CSS named color
|
|
249
|
+
cvt "cornflowerblue" nearest
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Cookbook
|
|
255
|
+
|
|
256
|
+
### Generate an accessible button palette
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
import { colorShades, bestTextColor, wcagLevel } from 'color-value-tools';
|
|
260
|
+
|
|
261
|
+
function buttonPalette(base: string) {
|
|
262
|
+
const shades = colorShades(base, 9);
|
|
263
|
+
return shades.map(shade => ({
|
|
264
|
+
bg: shade,
|
|
265
|
+
text: bestTextColor(shade),
|
|
266
|
+
wcag: wcagLevel(bestTextColor(shade), shade),
|
|
267
|
+
}));
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
buttonPalette('#3498db');
|
|
271
|
+
// [{ bg: '#ffffff', text: '#000000', wcag: 'AAA' }, ...]
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Theme-aware dark/light color
|
|
275
|
+
|
|
276
|
+
```ts
|
|
277
|
+
import { isDark, lighten, darken } from 'color-value-tools';
|
|
278
|
+
|
|
279
|
+
function adaptToTheme(color: string, isDarkTheme: boolean): string {
|
|
280
|
+
return isDarkTheme ? lighten(color, 20) : darken(color, 10);
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Mix two brand colors at a midpoint
|
|
285
|
+
|
|
286
|
+
```ts
|
|
287
|
+
import { mixColors } from 'color-value-tools';
|
|
288
|
+
|
|
289
|
+
const mid = mixColors('#e74c3c', '#3498db', 0.5, { mode: 'hsl', format: 'hex' });
|
|
290
|
+
// Perceptually even blend between red and blue
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### Build a triadic color scheme and check contrast
|
|
294
|
+
|
|
295
|
+
```ts
|
|
296
|
+
import { triadic, contrastRatio } from 'color-value-tools';
|
|
297
|
+
|
|
298
|
+
const [base, second, third] = triadic('#6c3483');
|
|
299
|
+
console.log(contrastRatio(base, '#ffffff')); // e.g. 8.4
|
|
300
|
+
console.log(contrastRatio(second, '#ffffff'));
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Convert a CSS color string to all formats at once
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
import { normalizeColor, rgbToOklch, rgbToCmyk } from 'color-value-tools';
|
|
307
|
+
|
|
308
|
+
const n = normalizeColor('hsl(204, 70%, 53%)');
|
|
309
|
+
const oklch = rgbToOklch({ r: n.r!, g: n.g!, b: n.b! });
|
|
310
|
+
const cmyk = rgbToCmyk({ r: n.r!, g: n.g!, b: n.b! });
|
|
311
|
+
console.log(n.hex, oklch, cmyk);
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Find the perceptually nearest named CSS color
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
import { toNearestNamedColor } from 'color-value-tools';
|
|
318
|
+
|
|
319
|
+
toNearestNamedColor('#1a8ccc'); // 'steelblue'
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### Random palette within a hue range
|
|
323
|
+
|
|
324
|
+
```ts
|
|
325
|
+
import { randomColor, colorShades } from 'color-value-tools';
|
|
326
|
+
|
|
327
|
+
const accent = randomColor({ hRange: [200, 260], sRange: [60, 80], lRange: [40, 60] });
|
|
328
|
+
const palette = colorShades(accent, 5);
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## Author
|
|
334
|
+
|
|
335
|
+
Danil Lisin Vladimirovich aka Macrulez
|
|
336
|
+
|
|
337
|
+
GitHub: [macrulezru](https://github.com/macrulezru)
|
|
338
|
+
|
|
339
|
+
Website: [macrulez.ru](https://macrulez.ru/)
|
|
340
|
+
|
|
341
|
+
## License
|
|
342
|
+
|
|
343
|
+
MIT
|