@cssints/placeholder 0.0.0-stage → 0.1.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 +21 -0
- package/README.md +88 -2
- package/dist/index.d.mts +21 -0
- package/dist/index.mjs +194 -0
- package/package.json +35 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
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.
|
package/README.md
CHANGED
|
@@ -1,3 +1,89 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @cssints/placeholder
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Image placeholders for [cssints](../../README.md): `placeholder(path)` reads an image while the build runs and gives an
|
|
4
|
+
`<image>` to paint until the real one loads, either a gradient of the image's colours laid out as the image is
|
|
5
|
+
(unpic-placeholder's radial gradients) or the image's [ThumbHash](https://evanw.github.io/thumbhash/) as a tiny PNG data
|
|
6
|
+
URL. It is one value member of `cssints/plugin` `scope()`, so it goes into `backgroundImage` (or `maskImage`). No runtime.
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { backgroundImage, cn, backgroundSize } from "cssints" with { type: "cssints" };
|
|
10
|
+
import { placeholder } from "@cssints/placeholder" with { type: "cssints" };
|
|
11
|
+
|
|
12
|
+
// placeholder(path, { mode? }): the path is relative to the working directory
|
|
13
|
+
export const hero = backgroundImage(placeholder("src/hero.jpg"));
|
|
14
|
+
export const card = cn(
|
|
15
|
+
backgroundImage(placeholder("src/card.png", { mode: "thumbhash" })),
|
|
16
|
+
backgroundSize("cover"),
|
|
17
|
+
);
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
For a PNG of four quadrants (red, blue / green, white):
|
|
21
|
+
|
|
22
|
+
```css
|
|
23
|
+
/* mode "gradient" (the default): a 4 × 3 grid, a radial gradient per cell, brightest on top, over the average colour */
|
|
24
|
+
._11mb38d {
|
|
25
|
+
background-image:
|
|
26
|
+
radial-gradient(at 67% 100%, #ffffff, transparent 50%),
|
|
27
|
+
radial-gradient(at 100% 100%, #ffffff, transparent 50%),
|
|
28
|
+
radial-gradient(at 67% 50%, #bcbcff, transparent 50%),
|
|
29
|
+
/* … one per cell, 12 in all … */ radial-gradient(at 100% 0, #0000ff, transparent 50%),
|
|
30
|
+
linear-gradient(#bc96bc, #bc96bc);
|
|
31
|
+
}
|
|
32
|
+
/* mode "thumbhash": the ThumbHash rendered, 16 px on the long side, about 0.5 kB */
|
|
33
|
+
._179cifn {
|
|
34
|
+
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAM…");
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Formats
|
|
39
|
+
|
|
40
|
+
JPEG (baseline and progressive, through [jpeg-js](https://github.com/jpeg-js/jpeg-js)) and PNG (every colour type and bit
|
|
41
|
+
depth, through [pngjs](https://github.com/pngjs/pngjs)), both plain JavaScript: nothing to compile, no native binary
|
|
42
|
+
(sharp is not used). The format is read from the file's first bytes, not its name. WebP, AVIF, GIF and SVG are build
|
|
43
|
+
errors that name the format (`cssints: placeholder: src/hero.webp: a WebP image: JPEG and PNG are read`): give a JPEG or
|
|
44
|
+
PNG copy of the image (it can be small; only its colours are read).
|
|
45
|
+
|
|
46
|
+
## Rules
|
|
47
|
+
|
|
48
|
+
- **Arguments.** `path` is a string, relative to the working directory (`process.cwd()`, the project root under Vite),
|
|
49
|
+
as the `fs` provider of `@cssints/icons` reads it. `mode` is `"gradient"` (the default) or `"thumbhash"`. Both are
|
|
50
|
+
checked in the types and at build time.
|
|
51
|
+
- **`gradient`** averages the image to a grid of 4 × 3 cells (3 × 4 for a portrait image), in linear light and weighted
|
|
52
|
+
by alpha, so a transparent part adds no colour and a cell that straddles red and green is the light's mix, not a muddy
|
|
53
|
+
sRGB average. Each cell is a `radial-gradient(at x y, colour, transparent 50%)` at its place in the image (corners at
|
|
54
|
+
`0` and `100%`), sorted by luminance with the brightest on top (as unpic-placeholder does), over one
|
|
55
|
+
`linear-gradient()` of the whole image's average colour that fills what the gradients leave. A cell that is not opaque
|
|
56
|
+
is `#rrggbbaa`. The value is a list of 13 images, about 0.7 kB.
|
|
57
|
+
- **`thumbhash`** encodes the image (averaged to at most 100 px a side first) with ThumbHash, renders the hash, averages
|
|
58
|
+
that to 16 px on the long side and writes it as a deflated PNG: about 0.5 kB, where ThumbHash's own data URL (32 px,
|
|
59
|
+
uncompressed) is about 5 kB. The browser's smooth upscaling does the blur. It keeps the aspect ratio, so pair it with
|
|
60
|
+
`backgroundSize("cover")` or `"100% 100%"`.
|
|
61
|
+
- **Deterministic.** The same file gives the same CSS on every build. Results are cached for the process by the SHA-256
|
|
62
|
+
of the file's content and the mode, so an image used from many modules is decoded once.
|
|
63
|
+
- **Errors**, at the call: a file that cannot be read (`cssints: placeholder: cannot read src/nope.png (paths are
|
|
64
|
+
relative to /app)`), a format it does not read (above), a file that does not decode (`cssints: placeholder:
|
|
65
|
+
src/broken.png: …`, the decoder's message), a mode outside the grammar (`placeholder(2).mode: unexpected "blurhash"
|
|
66
|
+
…`), and a placeholder where an image does not go (`css.color(placeholder(…))`, an error in the types and at build
|
|
67
|
+
time).
|
|
68
|
+
|
|
69
|
+
## Limits
|
|
70
|
+
|
|
71
|
+
- In dev, an edited image updates the CSS of the modules that use it (`dependsOn` of `cssints/plugin`); a missing image
|
|
72
|
+
that you add after the build error needs an edit of the module or a reload.
|
|
73
|
+
- The `gradient` value is a list of images, so it fits a property that takes a list (`background-image`, `mask-image`);
|
|
74
|
+
a property that takes one `<image>` (`list-style-image`, `border-image-source`) rejects it at build time: use
|
|
75
|
+
`thumbhash` there.
|
|
76
|
+
- No WebP or AVIF decoder (a WASM one would add a few hundred kB to the build); the grid size and the thumbhash size are
|
|
77
|
+
fixed; no blurhash mode.
|
|
78
|
+
- The image is decoded whole: a very large JPEG (tens of megapixels) costs its pixels in memory once per build.
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
cd packages/placeholder && bun run test # colour extraction on synthetic pixels, fixtures through the engine (output, errors)
|
|
82
|
+
cd packages/placeholder && bun run typecheck # and the type test, test/types/placeholder.ts
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Credits
|
|
86
|
+
|
|
87
|
+
The gradient layout follows [unpic-placeholder](https://github.com/ascorbic/unpic-placeholder) (MIT, Matt Kane).
|
|
88
|
+
The thumbhash mode uses [thumbhash](https://github.com/evanw/thumbhash) (MIT, Evan Wallace), [jpeg-js](https://github.com/jpeg-js/jpeg-js)
|
|
89
|
+
(BSD-3-Clause) and [pngjs](https://github.com/pngjs/pngjs) (MIT), dependencies. No code is copied.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { CSSImageValue } from "cssints";
|
|
2
|
+
//#region src/index.d.ts
|
|
3
|
+
/** How the placeholder is drawn: a gradient of the colours (the default), or the thumbhash as a PNG data URL. */
|
|
4
|
+
export type Mode = "gradient" | "thumbhash";
|
|
5
|
+
declare const placeholders: import("cssints/plugin").Scope<"placeholder", {
|
|
6
|
+
readonly placeholder: {
|
|
7
|
+
args: readonly [import("cssints/plugin").Typed<string>, {
|
|
8
|
+
readonly "mode?": "gradient | thumbhash";
|
|
9
|
+
}];
|
|
10
|
+
label?: undefined;
|
|
11
|
+
value(args: never): CSSImageValue;
|
|
12
|
+
};
|
|
13
|
+
}>;
|
|
14
|
+
/**
|
|
15
|
+
* `placeholder(path, { mode? })`: a placeholder of the image at `path` (relative to the working directory), read at build
|
|
16
|
+
* time. `mode: "gradient"` (the default) is a list of radial gradients of the image's colours, each where its part of the
|
|
17
|
+
* image is; `mode: "thumbhash"` is the image's thumbhash as a tiny PNG `url()`. An `<image>` for `backgroundImage`. JPEG
|
|
18
|
+
* and PNG; another format or a missing file is a build error at the call.
|
|
19
|
+
*/
|
|
20
|
+
export declare const placeholder: ReturnType<typeof placeholders.export>["placeholder"];
|
|
21
|
+
//#endregion
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { CSSImageValue } from "cssints";
|
|
5
|
+
import { arg, dependsOn, fn, scope } from "cssints/plugin";
|
|
6
|
+
import jpeg from "jpeg-js";
|
|
7
|
+
import { PNG } from "pngjs";
|
|
8
|
+
import { rgbaToThumbHash, thumbHashToRGBA } from "thumbhash";
|
|
9
|
+
//#region src/pixels.ts
|
|
10
|
+
const starts = (bytes, sig, at = 0) => sig.every((b, i) => bytes[at + i] === b);
|
|
11
|
+
const ascii = (s) => [...s].map((c) => c.charCodeAt(0));
|
|
12
|
+
const kind = (bytes) => starts(bytes, [
|
|
13
|
+
137,
|
|
14
|
+
80,
|
|
15
|
+
78,
|
|
16
|
+
71,
|
|
17
|
+
13,
|
|
18
|
+
10,
|
|
19
|
+
26,
|
|
20
|
+
10
|
|
21
|
+
]) ? "png" : starts(bytes, [
|
|
22
|
+
255,
|
|
23
|
+
216,
|
|
24
|
+
255
|
|
25
|
+
]) ? "jpeg" : starts(bytes, ascii("RIFF")) && starts(bytes, ascii("WEBP"), 8) ? "a WebP image" : starts(bytes, ascii("ftyp"), 4) && /^(avif|avis)$/.test(String.fromCharCode(...bytes.slice(8, 12))) ? "an AVIF image" : starts(bytes, ascii("GIF8")) ? "a GIF image" : "not an image it knows";
|
|
26
|
+
/**
|
|
27
|
+
* The RGBA pixels of a JPEG or PNG; anything else throws, naming what it is.
|
|
28
|
+
* @param {Uint8Array} bytes the file
|
|
29
|
+
* @returns {Pixels} its pixels
|
|
30
|
+
*/
|
|
31
|
+
const decode = (bytes) => {
|
|
32
|
+
const k = kind(bytes);
|
|
33
|
+
if (k === "png") {
|
|
34
|
+
const png = PNG.sync.read(Buffer.from(bytes));
|
|
35
|
+
return {
|
|
36
|
+
width: png.width,
|
|
37
|
+
height: png.height,
|
|
38
|
+
data: png.data
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (k === "jpeg") {
|
|
42
|
+
const img = jpeg.decode(bytes, {
|
|
43
|
+
useTArray: true,
|
|
44
|
+
formatAsRGBA: true
|
|
45
|
+
});
|
|
46
|
+
return {
|
|
47
|
+
width: img.width,
|
|
48
|
+
height: img.height,
|
|
49
|
+
data: img.data
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
throw new Error(`${k}: JPEG and PNG are read`);
|
|
53
|
+
};
|
|
54
|
+
const LINEAR = Array.from({ length: 256 }, (_, i) => {
|
|
55
|
+
const c = i / 255;
|
|
56
|
+
return c <= .04045 ? c / 12.92 : ((c + .055) / 1.055) ** 2.4;
|
|
57
|
+
});
|
|
58
|
+
const toSrgb = (l) => {
|
|
59
|
+
const c = l <= .0031308 ? l * 12.92 : 1.055 * l ** (1 / 2.4) - .055;
|
|
60
|
+
return Math.round(Math.min(1, Math.max(0, c)) * 255);
|
|
61
|
+
};
|
|
62
|
+
const span = (i, n, size) => {
|
|
63
|
+
const from = Math.min(size - 1, Math.floor(i * size / n));
|
|
64
|
+
return [from, Math.max(from + 1, Math.floor((i + 1) * size / n))];
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* `img` averaged to `w` × `h` (a box filter over the pixels each cell covers), in linear light and weighted by alpha, so
|
|
68
|
+
* a transparent pixel gives no colour.
|
|
69
|
+
* @param {Pixels} img the image
|
|
70
|
+
* @param {number} w the width of the result
|
|
71
|
+
* @param {number} h the height of the result
|
|
72
|
+
* @returns {Pixels} `w` × `h` cells
|
|
73
|
+
*/
|
|
74
|
+
const downsample = (img, w, h) => {
|
|
75
|
+
const data = new Uint8Array(w * h * 4);
|
|
76
|
+
for (let cy = 0; cy < h; cy++) {
|
|
77
|
+
const [y0, y1] = span(cy, h, img.height);
|
|
78
|
+
for (let cx = 0; cx < w; cx++) {
|
|
79
|
+
const [x0, x1] = span(cx, w, img.width);
|
|
80
|
+
let [r, g, b, a] = [
|
|
81
|
+
0,
|
|
82
|
+
0,
|
|
83
|
+
0,
|
|
84
|
+
0
|
|
85
|
+
];
|
|
86
|
+
for (let y = y0; y < y1; y++) for (let x = x0; x < x1; x++) {
|
|
87
|
+
const i = (y * img.width + x) * 4;
|
|
88
|
+
const alpha = img.data[i + 3] / 255;
|
|
89
|
+
r += LINEAR[img.data[i]] * alpha;
|
|
90
|
+
g += LINEAR[img.data[i + 1]] * alpha;
|
|
91
|
+
b += LINEAR[img.data[i + 2]] * alpha;
|
|
92
|
+
a += alpha;
|
|
93
|
+
}
|
|
94
|
+
const c = (cy * w + cx) * 4;
|
|
95
|
+
if (a) data.set([
|
|
96
|
+
toSrgb(r / a),
|
|
97
|
+
toSrgb(g / a),
|
|
98
|
+
toSrgb(b / a)
|
|
99
|
+
], c);
|
|
100
|
+
data[c + 3] = Math.round(a / ((y1 - y0) * (x1 - x0)) * 255);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
width: w,
|
|
105
|
+
height: h,
|
|
106
|
+
data
|
|
107
|
+
};
|
|
108
|
+
};
|
|
109
|
+
const hex = (d, i) => `#${[...d.subarray(i, i + (d[i + 3] === 255 ? 3 : 4))].map((v) => v.toString(16).padStart(2, "0")).join("")}`;
|
|
110
|
+
const pct = (n) => n ? `${Math.round(n * 100)}%` : "0";
|
|
111
|
+
/**
|
|
112
|
+
* unpic-placeholder's gradient: a radial gradient per cell of a `columns` × `rows` grid, at the cell's place and fading out
|
|
113
|
+
* at 50%, brightest first (on top), over the image's average colour.
|
|
114
|
+
* @param {Pixels} img the image
|
|
115
|
+
* @param {number} columns the cells across
|
|
116
|
+
* @param {number} rows the cells down
|
|
117
|
+
* @returns {string} the layers, comma-separated (a `background-image` value)
|
|
118
|
+
*/
|
|
119
|
+
const gradients = (img, columns, rows) => {
|
|
120
|
+
const { data } = downsample(img, columns, rows);
|
|
121
|
+
const cells = Array.from({ length: columns * rows }, (_, c) => c);
|
|
122
|
+
const luma = (c) => .299 * data[c * 4] + .587 * data[c * 4 + 1] + .114 * data[c * 4 + 2];
|
|
123
|
+
cells.sort((a, b) => luma(b) - luma(a));
|
|
124
|
+
const layers = cells.map((c) => `radial-gradient(at ${pct(c % columns / (columns - 1))} ${pct(Math.floor(c / columns) / (rows - 1))}, ${hex(data, c * 4)}, transparent 50%)`);
|
|
125
|
+
const average = hex(downsample(img, 1, 1).data, 0);
|
|
126
|
+
return [...layers, `linear-gradient(${average}, ${average})`].join(", ");
|
|
127
|
+
};
|
|
128
|
+
/**
|
|
129
|
+
* The image's thumbhash (from a copy at most 100 px a side), rendered by thumbhash (32 px on the long side), averaged to
|
|
130
|
+
* 16 px and written as a deflated PNG data URL: about 0.5 kB, where thumbhash's own `thumbHashToDataURL` (32 px, stored
|
|
131
|
+
* uncompressed) is about 5 kB. The browser's smooth upscaling is the blur.
|
|
132
|
+
* @param {Pixels} img the image
|
|
133
|
+
* @returns {string} a `data:image/png;base64,…` URL
|
|
134
|
+
*/
|
|
135
|
+
const thumbhash = (img) => {
|
|
136
|
+
const fit = (p, side) => {
|
|
137
|
+
const scale = Math.min(1, side / Math.max(p.width, p.height));
|
|
138
|
+
return downsample(p, Math.max(1, Math.round(p.width * scale)), Math.max(1, Math.round(p.height * scale)));
|
|
139
|
+
};
|
|
140
|
+
const small = fit(img, 100);
|
|
141
|
+
const { w, h, rgba } = thumbHashToRGBA(rgbaToThumbHash(small.width, small.height, small.data));
|
|
142
|
+
const out = fit({
|
|
143
|
+
width: w,
|
|
144
|
+
height: h,
|
|
145
|
+
data: rgba
|
|
146
|
+
}, 16);
|
|
147
|
+
const png = new PNG({
|
|
148
|
+
width: out.width,
|
|
149
|
+
height: out.height
|
|
150
|
+
});
|
|
151
|
+
png.data = Buffer.from(out.data);
|
|
152
|
+
return `data:image/png;base64,${PNG.sync.write(png, { deflateLevel: 9 }).toString("base64")}`;
|
|
153
|
+
};
|
|
154
|
+
//#endregion
|
|
155
|
+
//#region src/index.ts
|
|
156
|
+
const g = globalThis;
|
|
157
|
+
const fail = (message, cause) => {
|
|
158
|
+
throw new Error(`cssints: placeholder: ${message}`, { cause });
|
|
159
|
+
};
|
|
160
|
+
const compute = (path, mode) => {
|
|
161
|
+
let bytes;
|
|
162
|
+
dependsOn(path);
|
|
163
|
+
try {
|
|
164
|
+
bytes = readFileSync(resolve(path));
|
|
165
|
+
} catch (e) {
|
|
166
|
+
return fail(`cannot read ${path} (paths are relative to ${process.cwd()})`, e);
|
|
167
|
+
}
|
|
168
|
+
const key = `${createHash("sha256").update(bytes).digest("hex")} ${mode}`;
|
|
169
|
+
const cache = g.__cssintsPlaceholder ??= /* @__PURE__ */ new Map();
|
|
170
|
+
const hit = cache.get(key);
|
|
171
|
+
if (hit !== void 0) return hit;
|
|
172
|
+
let css;
|
|
173
|
+
try {
|
|
174
|
+
const img = decode(bytes);
|
|
175
|
+
const portrait = img.height > img.width;
|
|
176
|
+
css = mode === "thumbhash" ? `url("${thumbhash(img)}")` : gradients(img, portrait ? 3 : 4, portrait ? 4 : 3);
|
|
177
|
+
} catch (e) {
|
|
178
|
+
return fail(`${path}: ${e instanceof Error ? e.message : String(e)}`, e);
|
|
179
|
+
}
|
|
180
|
+
cache.set(key, css);
|
|
181
|
+
return css;
|
|
182
|
+
};
|
|
183
|
+
/**
|
|
184
|
+
* `placeholder(path, { mode? })`: a placeholder of the image at `path` (relative to the working directory), read at build
|
|
185
|
+
* time. `mode: "gradient"` (the default) is a list of radial gradients of the image's colours, each where its part of the
|
|
186
|
+
* image is; `mode: "thumbhash"` is the image's thumbhash as a tiny PNG `url()`. An `<image>` for `backgroundImage`. JPEG
|
|
187
|
+
* and PNG; another format or a missing file is a build error at the call.
|
|
188
|
+
*/
|
|
189
|
+
const placeholder = scope("placeholder", { placeholder: fn({
|
|
190
|
+
args: [arg(), { "mode?": "gradient | thumbhash" }],
|
|
191
|
+
value: ([path, { mode = "gradient" }]) => new CSSImageValue(compute(path, mode))
|
|
192
|
+
}) }).export().placeholder;
|
|
193
|
+
//#endregion
|
|
194
|
+
export { placeholder };
|
package/package.json
CHANGED
|
@@ -1,6 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cssints/placeholder",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Image placeholders for cssints: a gradient of the dominant colours, or a thumbhash data URL, read from the image 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/placeholder"
|
|
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
|
+
"jpeg-js": "^0.4.4",
|
|
25
|
+
"pngjs": "^7.0.0",
|
|
26
|
+
"thumbhash": "^0.1.1"
|
|
27
|
+
},
|
|
28
|
+
"peerDependencies": {
|
|
29
|
+
"cssints": "^0.1.0"
|
|
30
|
+
},
|
|
31
|
+
"engines": {
|
|
32
|
+
"node": ">=22"
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
}
|
|
37
|
+
}
|