@code-of-chaos/dither 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/README.md CHANGED
@@ -1,3 +1,105 @@
1
- # Temporary Holding Version
1
+ # Dither.Js
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
+ Animated bitmap-dithered gradient backgrounds for React, with a tileable
4
+ Perlin/fBm noise engine for generating your own dither tiles.
5
+
6
+ The texture is a real Bayer-dithered noise bitmap (Perlin islands + fine
7
+ detail), generated once on a canvas and scrolled with a GPU-friendly CSS
8
+ transform. Everything visual - colors, grid, fade, speed, direction - is a
9
+ prop, driven through CSS custom properties so it can be animated at runtime.
10
+
11
+ ## Install
12
+
13
+ ```sh
14
+ npm install @code-of-chaos/dither
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```tsx
20
+ import { DitherGridBackground } from "@code-of-chaos/dither";
21
+ import "@code-of-chaos/dither/style.css";
22
+
23
+ export function Page() {
24
+ return (
25
+ <DitherGridBackground className="min-h-screen w-full">
26
+ <h1>Hello world</h1>
27
+ </DitherGridBackground>
28
+ );
29
+ }
30
+ ```
31
+
32
+ Every visual property is optional (defaults in parentheses):
33
+
34
+ | Prop | Purpose |
35
+ | --------------------------------------------------------------- | --------------------------------------------------------------------------- |
36
+ | `backgroundColor` | Base fill, any CSS color or gradient |
37
+ | `gridColor`, `gridSize`, `gridOpacity` | Structural grid lines (steel, 41px, 0.23) |
38
+ | `noiseColor`, `noiseOpacity` | Dither tile color and layer opacity (scarlet, 0.5) |
39
+ | `noiseSize` | Displayed size of each dither pixel in px (3) |
40
+ | `noiseDensity`, `noiseContrast`, `noiseMaxFill`, `noiseJitter` | Dither character: fill amount, hardness, density cap, Bayer/random mix |
41
+ | `noiseScale`, `noiseOctaves` | Island field (clump size, fBm depth) |
42
+ | `noiseDetailScale`, `noiseDetailOctaves`, `noiseDetailStrength` | Fine detail field layered on the islands |
43
+ | `noiseClampLow`, `noiseClampHigh` | Soft-clamp the noise: clean voids low, no saturation high |
44
+ | `noiseTileCells`, `noiseSeed` | Tile resolution and reproducible randomness |
45
+ | `noiseSpeed`, `noiseAngle`, `noiseTravel` | Scroll animation: seconds per loop, direction in degrees, distance in tiles |
46
+ | `fadeStart`, `fadeEnd` | Vertical fade of the dither layer (35%, 82%) |
47
+ | `animateNoise`, `animateGlow` | Enable the scroll / ambient glow (both on) |
48
+
49
+ Colors accept `var(--token, fallback)` references; they are resolved against
50
+ the document root, so theme variables work as parameters.
51
+
52
+ ## Lower-level pieces
53
+
54
+ - `DitherTile` - generates a seamless Bayer-dithered PNG data URL from the
55
+ noise fields; use it for static overlays and textures.
56
+ - `PerlinNoise` / `FractalBrownianNoise` - tileable gradient noise.
57
+ - `MathUtils` / `RgbColor` - helpers used by the engine.
58
+
59
+ ## Development
60
+
61
+ ```sh
62
+ npm install
63
+ npm run dev # playground at demo/
64
+ npm test # vitest
65
+ npm run lint # biome
66
+ npm run build # dist/ (ESM + types + style.css)
67
+ ```
68
+
69
+ ## Releasing
70
+
71
+ Releases use npm staged publishing with a stage-only token: CI stages the
72
+ version but can never publish directly (a leaked token cannot release a
73
+ version on its own).
74
+
75
+ 1. Bump `version` in `package.json` (semver).
76
+ 2. Tag and push: `git tag vX.Y.Z && git push origin vX.Y.Z`.
77
+ 3. The release workflow verifies the tag matches the package version, runs
78
+ `npm stage publish --provenance` (needs an `NPM_TOKEN` repository secret
79
+ holding a stage-only token) and creates a GitHub release.
80
+ 4. A maintainer reviews and approves the staged version - with 2FA, either:
81
+ - CLI: `npm stage list` to find the stage id, then
82
+ `npm stage approve <stage-id>`
83
+ - or the "Staged Packages" tab on npmjs.com and click Approve
84
+
85
+ The version becomes installable only after approval. Discard a bad stage
86
+ with `npm stage reject <stage-id>`.
87
+
88
+ Notes:
89
+
90
+ - `npm stage publish` does not prompt for 2FA - proof of presence is deferred
91
+ to the approval step. Requires npm CLI 11.15+ (Node 22.14+).
92
+ - The first release creates the package (shown as a `0.0.0-stage` placeholder)
93
+ until the staged version is approved.
94
+ - Direct `npm publish` with the stage-only token fails with `E_STAGE_REQUIRED`.
95
+
96
+ ## Notes
97
+
98
+ - The tile is generated in the browser (canvas), so the component is
99
+ client-side only.
100
+ - `prefers-reduced-motion` disables the animations.
101
+ - React 18+; class component, no runtime dependencies besides React.
102
+
103
+ ## License
104
+
105
+ GPL-3.0-only (see `LICENSE`).
@@ -0,0 +1,53 @@
1
+ import type { HTMLAttributes, ReactNode } from "react";
2
+ import { Component } from "react";
3
+ import "./DitherGridBackground.css";
4
+ export interface DitherGridBackgroundProps extends Omit<HTMLAttributes<HTMLDivElement>, "color"> {
5
+ backgroundColor?: string;
6
+ gridColor?: string;
7
+ gridSize?: number;
8
+ gridOpacity?: number;
9
+ noiseColor?: string;
10
+ noiseOpacity?: number;
11
+ noiseSize?: number;
12
+ noiseTileCells?: number;
13
+ noiseDensity?: number;
14
+ noiseContrast?: number;
15
+ noiseScale?: number;
16
+ noiseOctaves?: number;
17
+ noiseDetailScale?: number;
18
+ noiseDetailOctaves?: number;
19
+ noiseDetailStrength?: number;
20
+ noiseClampLow?: number;
21
+ noiseClampHigh?: number;
22
+ noiseSeed?: number;
23
+ noiseMaxFill?: number;
24
+ noiseJitter?: number;
25
+ noiseSpeed?: number;
26
+ noiseAngle?: number;
27
+ noiseTravel?: number;
28
+ fadeStart?: number;
29
+ fadeEnd?: number;
30
+ animateNoise?: boolean;
31
+ animateGlow?: boolean;
32
+ children?: ReactNode;
33
+ }
34
+ interface DitherGridBackgroundState {
35
+ noiseImage: string;
36
+ }
37
+ /**
38
+ * Layered page background: a scrolling dither texture, a static grid and an
39
+ * optional ambient glow. Every visual property is forwarded as a CSS custom
40
+ * property, so it can be animated at runtime (React state, CSS transitions)
41
+ * without regenerating the texture.
42
+ *
43
+ * The dither tile is generated once and cached in state; it is only rebuilt
44
+ * when one of the tile inputs (TILE_INPUTS) changes.
45
+ */
46
+ export declare class DitherGridBackground extends Component<DitherGridBackgroundProps, DitherGridBackgroundState> {
47
+ constructor(props: DitherGridBackgroundProps);
48
+ componentDidUpdate(prev: DitherGridBackgroundProps): void;
49
+ private createNoiseImage;
50
+ render(): import("react").JSX.Element;
51
+ }
52
+ export {};
53
+ //# sourceMappingURL=DitherGridBackground.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DitherGridBackground.d.ts","sourceRoot":"","sources":["../src/DitherGridBackground.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACtE,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAElC,OAAO,4BAA4B,CAAC;AAMpC,MAAM,WAAW,yBACb,SAAQ,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC;IACrD,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB,QAAQ,CAAC,EAAE,SAAS,CAAC;CACxB;AAED,UAAU,yBAAyB;IAC/B,UAAU,EAAE,MAAM,CAAC;CACtB;AAoBD;;;;;;;;GAQG;AACH,qBAAa,oBAAqB,SAAQ,SAAS,CAC/C,yBAAyB,EACzB,yBAAyB,CAC5B;IACG,YAAY,KAAK,EAAE,yBAAyB,EAG3C;IAEQ,kBAAkB,CAAC,IAAI,EAAE,yBAAyB,GAAG,IAAI,CAIjE;IAED,OAAO,CAAC,gBAAgB;IAmBf,MAAM,gCAmGd;CACJ"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * All visual knobs of the tile. `scale`/`octaves` shape the large island
3
+ * field, `detail*` the fine detail field; `clampLow`/`clampHigh` soft-clip
4
+ * the blend (clean voids at the low end, no saturation at the high end),
5
+ * `maxFill` caps how dense the dither can get, and `jitter` blends the
6
+ * Bayer threshold with per-pixel random values for an organic look.
7
+ */
8
+ export type DitherTileOptions = {
9
+ cells: number;
10
+ color: string;
11
+ density: number;
12
+ contrast: number;
13
+ scale: number;
14
+ octaves: number;
15
+ detailScale: number;
16
+ detailOctaves: number;
17
+ detailStrength: number;
18
+ clampLow: number;
19
+ clampHigh: number;
20
+ seed: number;
21
+ maxFill: number;
22
+ jitter: number;
23
+ };
24
+ /**
25
+ * Renders a seamless dithered noise tile as a PNG data URL.
26
+ *
27
+ * Per pixel: the island and detail noise fields are blended into a density
28
+ * value, soft-clamped, then thresholded against a Bayer matrix mixed with
29
+ * per-pixel jitter to decide whether the pixel is inked. Because the noise
30
+ * fields tile seamlessly, the resulting image can be repeated and scrolled
31
+ * forever without visible seams.
32
+ */
33
+ export declare class DitherTile {
34
+ private readonly options;
35
+ static readonly DEFAULT_CELLS = 512;
36
+ private static readonly BAYER_4X4;
37
+ private readonly islands;
38
+ private readonly detail;
39
+ constructor(options: DitherTileOptions);
40
+ toDataURL(): string;
41
+ }
42
+ //# sourceMappingURL=DitherTile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DitherTile.d.ts","sourceRoot":"","sources":["../src/DitherTile.ts"],"names":[],"mappings":"AAIA;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;;;GAQG;AACH,qBAAa,UAAU;IAaP,OAAO,CAAC,QAAQ,CAAC,OAAO;IAZpC,MAAM,CAAC,QAAQ,CAAC,aAAa,OAAO;IAEpC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAKc;IAE/C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAuB;IAC/C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAuB;IAE9C,YAA6B,OAAO,EAAE,iBAAiB,EAatD;IAED,SAAS,IAAI,MAAM,CA+DlB;CACJ"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Fractal Brownian motion: stacks PerlinNoise octaves with halved amplitude
3
+ * and doubled frequency per octave. Coordinates are expressed in tile cells,
4
+ * keeping every octave (and therefore the whole field) seamlessly tileable.
5
+ * Output is normalized to [0, 1].
6
+ */
7
+ export declare class FractalBrownianNoise {
8
+ private readonly octaves;
9
+ private readonly totalAmplitude;
10
+ constructor(cells: number, scale: number, octaves: number, seed: number);
11
+ sample(x: number, y: number): number;
12
+ }
13
+ //# sourceMappingURL=FractalBrownianNoise.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"FractalBrownianNoise.d.ts","sourceRoot":"","sources":["../src/FractalBrownianNoise.ts"],"names":[],"mappings":"AAWA;;;;;GAKG;AACH,qBAAa,oBAAoB;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAgB;IACxC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAS;IAExC,YAAY,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAkBtE;IAED,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAanC;CACJ"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Small math helpers used by the noise and dither pipeline.
3
+ * The standard Math library has no clamp/lerp/smoothstep/mod equivalents.
4
+ */
5
+ export declare class MathUtils {
6
+ static clamp(value: number, min: number, max: number): number;
7
+ static lerp(a: number, b: number, t: number): number;
8
+ static smoothstep(edge0: number, edge1: number, x: number): number;
9
+ static mod(value: number, period: number): number;
10
+ static hash2(x: number, y: number, seed: number): number;
11
+ }
12
+ //# sourceMappingURL=MathUtils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MathUtils.d.ts","sourceRoot":"","sources":["../src/MathUtils.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,SAAS;IAClB,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAE5D;IAED,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAEnD;IAED,MAAM,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAGjE;IAED,MAAM,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEhD;IAED,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAQvD;CACJ"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Tileable 2D Perlin gradient noise. Lattice coordinates wrap at `period`,
3
+ * so sampling the [0, period) cell range produces a seamless tile.
4
+ * Raw output is roughly in [-1, 1] (see FractalBrownianNoise for normalized values).
5
+ */
6
+ export declare class PerlinNoise {
7
+ private readonly period;
8
+ private readonly seed;
9
+ private static readonly GRADIENTS;
10
+ constructor(period: number, seed: number);
11
+ sample(x: number, y: number): number;
12
+ private gradientDot;
13
+ }
14
+ //# sourceMappingURL=PerlinNoise.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PerlinNoise.d.ts","sourceRoot":"","sources":["../src/PerlinNoise.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,qBAAa,WAAW;IAShB,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,IAAI;IATzB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAK9B;IAEH,YACqB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EAC7B;IAEJ,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAwBnC;IAED,OAAO,CAAC,WAAW;CAetB"}
@@ -0,0 +1,18 @@
1
+ /** Immutable RGB triple with CSS color parsing. */
2
+ export declare class RgbColor {
3
+ readonly r: number;
4
+ readonly g: number;
5
+ readonly b: number;
6
+ constructor(r: number, g: number, b: number);
7
+ /**
8
+ * Parses any CSS color (named, hex, rgb()/rgba()) by letting the canvas
9
+ * resolve it: assigning to fillStyle normalizes the value to a canonical
10
+ * string which is then read back. Falls back to white on invalid input.
11
+ *
12
+ * A single var(--token, fallback) reference is resolved against the
13
+ * document root first, so theme variables can be used as color parameters.
14
+ */
15
+ static fromCss(ctx: CanvasRenderingContext2D, css: string): RgbColor;
16
+ private static resolveVariable;
17
+ }
18
+ //# sourceMappingURL=RgbColor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RgbColor.d.ts","sourceRoot":"","sources":["../src/RgbColor.ts"],"names":[],"mappings":"AAAA,mDAAmD;AACnD,qBAAa,QAAQ;IAEb,QAAQ,CAAC,CAAC,EAAE,MAAM;IAClB,QAAQ,CAAC,CAAC,EAAE,MAAM;IAClB,QAAQ,CAAC,CAAC,EAAE,MAAM;IAHtB,YACa,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EACT,CAAC,EAAE,MAAM,EAClB;IAEJ;;;;;;;OAOG;IACH,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,wBAAwB,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ,CAgCnE;IAED,OAAO,CAAC,MAAM,CAAC,eAAe;CAWjC"}
@@ -0,0 +1,9 @@
1
+ export type { DitherGridBackgroundProps } from "./DitherGridBackground";
2
+ export { DitherGridBackground } from "./DitherGridBackground";
3
+ export type { DitherTileOptions } from "./DitherTile";
4
+ export { DitherTile } from "./DitherTile";
5
+ export { FractalBrownianNoise } from "./FractalBrownianNoise";
6
+ export { MathUtils } from "./MathUtils";
7
+ export { PerlinNoise } from "./PerlinNoise";
8
+ export { RgbColor } from "./RgbColor";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,225 @@
1
+ import { Component as e } from "react";
2
+ import { jsx as t, jsxs as n } from "react/jsx-runtime";
3
+ // src/MathUtils.ts
4
+ var r = class e {
5
+ static clamp(e, t, n) {
6
+ return Math.min(Math.max(e, t), n);
7
+ }
8
+ static lerp(e, t, n) {
9
+ return e + (t - e) * n;
10
+ }
11
+ static smoothstep(t, n, r) {
12
+ let i = e.clamp((r - t) / (n - t), 0, 1);
13
+ return i * i * (3 - 2 * i);
14
+ }
15
+ static mod(e, t) {
16
+ return (e % t + t) % t;
17
+ }
18
+ static hash2(e, t, n) {
19
+ let r = Math.imul(e, 374761393) ^ Math.imul(t, 668265263) ^ Math.imul(n, 2246822519);
20
+ return r = Math.imul(r ^ r >>> 13, 1274126177), r ^= r >>> 16, (r >>> 0) / 4294967296;
21
+ }
22
+ }, i = class e {
23
+ period;
24
+ seed;
25
+ static GRADIENTS = Array.from({ length: 16 }, (e, t) => {
26
+ let n = t / 16 * Math.PI * 2;
27
+ return [Math.cos(n), Math.sin(n)];
28
+ });
29
+ constructor(e, t) {
30
+ this.period = e, this.seed = t;
31
+ }
32
+ sample(e, t) {
33
+ let n = Math.floor(e), i = Math.floor(t), a = e - n, o = t - i, s = a * a * a * (a * (a * 6 - 15) + 10), c = o * o * o * (o * (o * 6 - 15) + 10), l = r.mod(n, this.period), u = r.mod(i, this.period), d = r.mod(n + 1, this.period), f = r.mod(i + 1, this.period), p = this.gradientDot(l, u, a, o), m = this.gradientDot(d, u, a - 1, o), h = this.gradientDot(l, f, a, o - 1), g = this.gradientDot(d, f, a - 1, o - 1);
34
+ return r.lerp(r.lerp(p, m, s), r.lerp(h, g, s), c);
35
+ }
36
+ gradientDot(t, n, i, a) {
37
+ let o = e.GRADIENTS, s = o[Math.floor(r.hash2(t, n, this.seed) * o.length) % o.length];
38
+ return s[0] * i + s[1] * a;
39
+ }
40
+ }, a = class {
41
+ noise;
42
+ frequency;
43
+ amplitude;
44
+ constructor(e, t, n) {
45
+ this.noise = e, this.frequency = t, this.amplitude = n;
46
+ }
47
+ }, o = class {
48
+ octaves = [];
49
+ totalAmplitude;
50
+ constructor(e, t, n, r) {
51
+ let o = 1, s = 0;
52
+ for (let c = 0; c < n; c++) {
53
+ let n = Math.max(1, Math.round(e / (t * 2 ** c)));
54
+ this.octaves.push(new a(new i(n, r + c * 1013), n / e, o)), s += o, o *= .5;
55
+ }
56
+ this.totalAmplitude = s;
57
+ }
58
+ sample(e, t) {
59
+ let n = 0;
60
+ for (let r of this.octaves) n += r.amplitude * r.noise.sample(e * r.frequency, t * r.frequency);
61
+ return r.clamp(n / this.totalAmplitude * .6 + .5, 0, 1);
62
+ }
63
+ }, s = class e {
64
+ r;
65
+ g;
66
+ b;
67
+ constructor(e, t, n) {
68
+ this.r = e, this.g = t, this.b = n;
69
+ }
70
+ static fromCss(t, n) {
71
+ let r = e.resolveVariable(n);
72
+ t.fillStyle = "#000000", t.fillStyle = r;
73
+ let i = t.fillStyle;
74
+ if (i.startsWith("#")) {
75
+ let t = i.slice(1), n = t.length === 3 ? t.split("").map((e) => e + e).join("") : t;
76
+ return new e(parseInt(n.slice(0, 2), 16), parseInt(n.slice(2, 4), 16), parseInt(n.slice(4, 6), 16));
77
+ }
78
+ let a = i.match(/rgba?\(([^)]+)\)/);
79
+ if (a) {
80
+ let [t, n, r] = a[1].split(/[,/\s]+/).filter(Boolean).map(Number);
81
+ return new e(t, n, r);
82
+ }
83
+ return new e(255, 255, 255);
84
+ }
85
+ static resolveVariable(e) {
86
+ let t = e.match(/^\s*var\(\s*(--[\w-]+)\s*(?:,\s*([^()]+))?\s*\)\s*$/);
87
+ return t && (getComputedStyle(document.documentElement).getPropertyValue(t[1]).trim() || t[2]?.trim()) || e;
88
+ }
89
+ }, c = class e {
90
+ options;
91
+ static DEFAULT_CELLS = 512;
92
+ static BAYER_4X4 = [
93
+ [
94
+ 0,
95
+ 8,
96
+ 2,
97
+ 10
98
+ ],
99
+ [
100
+ 12,
101
+ 4,
102
+ 14,
103
+ 6
104
+ ],
105
+ [
106
+ 3,
107
+ 11,
108
+ 1,
109
+ 9
110
+ ],
111
+ [
112
+ 15,
113
+ 7,
114
+ 13,
115
+ 5
116
+ ]
117
+ ].map((e) => e.map((e) => (e + .5) / 16));
118
+ islands;
119
+ detail;
120
+ constructor(e) {
121
+ this.options = e, this.islands = new o(e.cells, e.scale, e.octaves, e.seed), this.detail = new o(e.cells, e.detailScale, e.detailOctaves, e.seed + 31337);
122
+ }
123
+ toDataURL() {
124
+ let { cells: t, color: n, density: i, contrast: a, detailStrength: o, clampLow: c, clampHigh: l, seed: u, maxFill: d, jitter: f } = this.options, p = document.createElement("canvas");
125
+ p.width = t, p.height = t;
126
+ let m = p.getContext("2d");
127
+ if (!m) return "";
128
+ let h = s.fromCss(m, n), g = m.createImageData(t, t), _ = g.data;
129
+ for (let n = 0; n < t; n++) for (let s = 0; s < t; s++) {
130
+ let p = this.islands.sample(s, n), m = this.detail.sample(s, n), g = r.clamp(p + (m - .5) * o, 0, 1), v = r.smoothstep(c, Math.max(l, c + .001), g), y = r.clamp((v - .5) * a + i, 0, d);
131
+ if (e.BAYER_4X4[n % 4][s % 4] * (1 - f) + r.hash2(s, n, u + 991) * f < y) {
132
+ let e = (n * t + s) * 4;
133
+ _[e] = h.r, _[e + 1] = h.g, _[e + 2] = h.b, _[e + 3] = Math.round(255 * r.clamp(.4 + .6 * v, 0, 1));
134
+ }
135
+ }
136
+ return m.putImageData(g, 0, 0), p.toDataURL("image/png");
137
+ }
138
+ }, l = [
139
+ "noiseTileCells",
140
+ "noiseColor",
141
+ "noiseDensity",
142
+ "noiseContrast",
143
+ "noiseScale",
144
+ "noiseOctaves",
145
+ "noiseDetailScale",
146
+ "noiseDetailOctaves",
147
+ "noiseDetailStrength",
148
+ "noiseClampLow",
149
+ "noiseClampHigh",
150
+ "noiseSeed",
151
+ "noiseMaxFill",
152
+ "noiseJitter"
153
+ ], u = class extends e {
154
+ constructor(e) {
155
+ super(e), this.state = { noiseImage: this.createNoiseImage() };
156
+ }
157
+ componentDidUpdate(e) {
158
+ l.some((t) => e[t] !== this.props[t]) && this.setState({ noiseImage: this.createNoiseImage() });
159
+ }
160
+ createNoiseImage() {
161
+ return new c({
162
+ cells: this.props.noiseTileCells ?? c.DEFAULT_CELLS,
163
+ color: this.props.noiseColor ?? "var(--color-scarlet-600)",
164
+ density: this.props.noiseDensity ?? .55,
165
+ contrast: this.props.noiseContrast ?? 1.8,
166
+ scale: this.props.noiseScale ?? 32,
167
+ octaves: this.props.noiseOctaves ?? 3,
168
+ detailScale: this.props.noiseDetailScale ?? 5,
169
+ detailOctaves: this.props.noiseDetailOctaves ?? 3,
170
+ detailStrength: this.props.noiseDetailStrength ?? .2,
171
+ clampLow: this.props.noiseClampLow ?? 0,
172
+ clampHigh: this.props.noiseClampHigh ?? .9,
173
+ seed: this.props.noiseSeed ?? 69,
174
+ maxFill: this.props.noiseMaxFill ?? .6,
175
+ jitter: this.props.noiseJitter ?? .3
176
+ }).toDataURL();
177
+ }
178
+ render() {
179
+ let { backgroundColor: e = "linear-gradient(to bottom, var(--color-wine-950) 0%, var(--color-ink-950) 65%)", gridColor: r = "var(--color-steel-500)", gridSize: i = 41, gridOpacity: a = .23, noiseColor: o = "var(--color-scarlet-600)", noiseOpacity: s = .5, noiseSize: l = 3, noiseTileCells: u = c.DEFAULT_CELLS, noiseDensity: d = .55, noiseContrast: f = 1.8, noiseScale: p = 32, noiseOctaves: m = 3, noiseDetailScale: h = 5, noiseDetailOctaves: g = 3, noiseDetailStrength: _ = .2, noiseClampLow: v = 0, noiseClampHigh: y = .9, noiseSeed: b = 69, noiseMaxFill: x = .6, noiseJitter: S = .3, noiseSpeed: C = 180, noiseAngle: w = 100, noiseTravel: T = 1, fadeStart: E = 0, fadeEnd: D = 90, animateNoise: O = !0, animateGlow: k = !0, children: A, className: j = "", style: M, ...N } = this.props, P = u * l, F = w * Math.PI / 180, I = Math.round(Math.cos(F) * T) * P, L = Math.round(Math.sin(F) * T) * P, R = Math.max(Math.abs(I), Math.abs(L)) + P, z = {
180
+ "--background-color": e,
181
+ "--grid-color": r,
182
+ "--grid-size": `${i}px`,
183
+ "--grid-opacity": a,
184
+ "--noise-image": `url("${this.state.noiseImage}")`,
185
+ "--noise-tile-size": `${P}px`,
186
+ "--noise-color": o,
187
+ "--noise-opacity": s,
188
+ "--noise-speed": `${C}s`,
189
+ "--noise-x": `${I}px`,
190
+ "--noise-y": `${L}px`,
191
+ "--noise-overscan": `${R}px`,
192
+ "--fade-start": `${E}%`,
193
+ "--fade-end": `${D}%`,
194
+ ...M
195
+ };
196
+ return /* @__PURE__ */ n("div", {
197
+ className: [
198
+ "dither-grid-background",
199
+ O && "dither-grid-background--animated",
200
+ k && "dither-grid-background--glow",
201
+ j
202
+ ].filter(Boolean).join(" "),
203
+ style: z,
204
+ ...N,
205
+ children: [
206
+ /* @__PURE__ */ t("div", {
207
+ className: "dither-grid-background__noise",
208
+ "aria-hidden": "true",
209
+ children: /* @__PURE__ */ t("div", { className: "dither-grid-background__noise-texture" })
210
+ }),
211
+ /* @__PURE__ */ t("div", {
212
+ className: "dither-grid-background__grid",
213
+ "aria-hidden": "true"
214
+ }),
215
+ /* @__PURE__ */ t("div", {
216
+ className: "dither-grid-background__content",
217
+ children: A
218
+ })
219
+ ]
220
+ });
221
+ }
222
+ };
223
+ export { u as DitherGridBackground, c as DitherTile, o as FractalBrownianNoise, r as MathUtils, i as PerlinNoise, s as RgbColor };
224
+
225
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["MathUtils","value","min","max","a","b","t","x","edge0","edge1","period","h","y","seed","PerlinNoise","angle","i","x0","y0","fx","fy","u","v","ix0","iy0","ix1","iy1","n00","n10","n01","n11","gradients","g","ix","iy","dx","dy","Octave","noise","frequency","amplitude","FractalBrownianNoise","total","o","octaves","cells","scale","sum","octave","RgbColor","r","resolved","css","normalized","ctx","hex","full","c","match","DitherTile","row","options","canvas","rgb","color","image","data","island","detail","combined","detailStrength","n","clampLow","clampHigh","p","contrast","density","maxFill","jitter","TILE_INPUTS","DitherGridBackground","Component","props","prev","key","tileSize","noiseTileCells","noiseSize","noiseAngle","travelX","noiseTravel","travelY","overscan","variables","backgroundColor","gridColor","gridSize","gridOpacity","noiseColor","noiseOpacity","noiseSpeed","fadeStart","fadeEnd","style","animateNoise","animateGlow","className"],"sources":["../src/MathUtils.ts","../src/PerlinNoise.ts","../src/FractalBrownianNoise.ts","../src/RgbColor.ts","../src/DitherTile.ts","../src/DitherGridBackground.tsx"],"sourcesContent":["/**\n * Small math helpers used by the noise and dither pipeline.\n * The standard Math library has no clamp/lerp/smoothstep/mod equivalents.\n */\nexport class MathUtils {\n static clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n }\n\n static lerp(a: number, b: number, t: number): number {\n return a + (b - a) * t;\n }\n\n static smoothstep(edge0: number, edge1: number, x: number): number {\n const t = MathUtils.clamp((x - edge0) / (edge1 - edge0), 0, 1);\n return t * t * (3 - 2 * t);\n }\n\n static mod(value: number, period: number): number {\n return ((value % period) + period) % period;\n }\n\n static hash2(x: number, y: number, seed: number): number {\n let h =\n Math.imul(x, 374761393)\n ^ Math.imul(y, 668265263)\n ^ Math.imul(seed, 2246822519);\n h = Math.imul(h ^ (h >>> 13), 1274126177);\n h ^= h >>> 16;\n return (h >>> 0) / 4294967296;\n }\n}\n","import { MathUtils } from \"./MathUtils\";\n\n/**\n * Tileable 2D Perlin gradient noise. Lattice coordinates wrap at `period`,\n * so sampling the [0, period) cell range produces a seamless tile.\n * Raw output is roughly in [-1, 1] (see FractalBrownianNoise for normalized values).\n */\nexport class PerlinNoise {\n private static readonly GRADIENTS: ReadonlyArray<\n readonly [number, number]\n > = Array.from({ length: 16 }, (_, i) => {\n const angle = (i / 16) * Math.PI * 2;\n return [Math.cos(angle), Math.sin(angle)] as const;\n });\n\n constructor(\n private readonly period: number,\n private readonly seed: number,\n ) {}\n\n sample(x: number, y: number): number {\n const x0 = Math.floor(x);\n const y0 = Math.floor(y);\n const fx = x - x0;\n const fy = y - y0;\n\n const u = fx * fx * fx * (fx * (fx * 6 - 15) + 10);\n const v = fy * fy * fy * (fy * (fy * 6 - 15) + 10);\n\n const ix0 = MathUtils.mod(x0, this.period);\n const iy0 = MathUtils.mod(y0, this.period);\n const ix1 = MathUtils.mod(x0 + 1, this.period);\n const iy1 = MathUtils.mod(y0 + 1, this.period);\n\n const n00 = this.gradientDot(ix0, iy0, fx, fy);\n const n10 = this.gradientDot(ix1, iy0, fx - 1, fy);\n const n01 = this.gradientDot(ix0, iy1, fx, fy - 1);\n const n11 = this.gradientDot(ix1, iy1, fx - 1, fy - 1);\n\n return MathUtils.lerp(\n MathUtils.lerp(n00, n10, u),\n MathUtils.lerp(n01, n11, u),\n v,\n );\n }\n\n private gradientDot(\n ix: number,\n iy: number,\n dx: number,\n dy: number,\n ): number {\n const gradients = PerlinNoise.GRADIENTS;\n const g =\n gradients[\n Math.floor(\n MathUtils.hash2(ix, iy, this.seed) * gradients.length,\n ) % gradients.length\n ];\n return g[0] * dx + g[1] * dy;\n }\n}\n","import { MathUtils } from \"./MathUtils\";\nimport { PerlinNoise } from \"./PerlinNoise\";\n\nclass Octave {\n constructor(\n readonly noise: PerlinNoise,\n readonly frequency: number,\n readonly amplitude: number,\n ) {}\n}\n\n/**\n * Fractal Brownian motion: stacks PerlinNoise octaves with halved amplitude\n * and doubled frequency per octave. Coordinates are expressed in tile cells,\n * keeping every octave (and therefore the whole field) seamlessly tileable.\n * Output is normalized to [0, 1].\n */\nexport class FractalBrownianNoise {\n private readonly octaves: Octave[] = [];\n private readonly totalAmplitude: number;\n\n constructor(cells: number, scale: number, octaves: number, seed: number) {\n let amplitude = 1;\n let total = 0;\n\n for (let o = 0; o < octaves; o++) {\n const period = Math.max(1, Math.round(cells / (scale * 2 ** o)));\n this.octaves.push(\n new Octave(\n new PerlinNoise(period, seed + o * 1013),\n period / cells,\n amplitude,\n ),\n );\n total += amplitude;\n amplitude *= 0.5;\n }\n\n this.totalAmplitude = total;\n }\n\n sample(x: number, y: number): number {\n let sum = 0;\n\n for (const octave of this.octaves) {\n sum +=\n octave.amplitude\n * octave.noise.sample(\n x * octave.frequency,\n y * octave.frequency,\n );\n }\n\n return MathUtils.clamp((sum / this.totalAmplitude) * 0.6 + 0.5, 0, 1);\n }\n}\n","/** Immutable RGB triple with CSS color parsing. */\nexport class RgbColor {\n constructor(\n readonly r: number,\n readonly g: number,\n readonly b: number,\n ) {}\n\n /**\n * Parses any CSS color (named, hex, rgb()/rgba()) by letting the canvas\n * resolve it: assigning to fillStyle normalizes the value to a canonical\n * string which is then read back. Falls back to white on invalid input.\n *\n * A single var(--token, fallback) reference is resolved against the\n * document root first, so theme variables can be used as color parameters.\n */\n static fromCss(ctx: CanvasRenderingContext2D, css: string): RgbColor {\n const resolved = RgbColor.resolveVariable(css);\n ctx.fillStyle = \"#000000\";\n ctx.fillStyle = resolved;\n const normalized = ctx.fillStyle;\n\n if (normalized.startsWith(\"#\")) {\n const hex = normalized.slice(1);\n const full =\n hex.length === 3\n ? hex\n .split(\"\")\n .map((c) => c + c)\n .join(\"\")\n : hex;\n return new RgbColor(\n parseInt(full.slice(0, 2), 16),\n parseInt(full.slice(2, 4), 16),\n parseInt(full.slice(4, 6), 16),\n );\n }\n\n const match = normalized.match(/rgba?\\(([^)]+)\\)/);\n if (match) {\n const [r, g, b] = match[1]\n .split(/[,/\\s]+/)\n .filter(Boolean)\n .map(Number);\n return new RgbColor(r, g, b);\n }\n\n return new RgbColor(255, 255, 255);\n }\n\n private static resolveVariable(css: string): string {\n const match = css.match(\n /^\\s*var\\(\\s*(--[\\w-]+)\\s*(?:,\\s*([^()]+))?\\s*\\)\\s*$/,\n );\n if (!match) return css;\n\n const value = getComputedStyle(document.documentElement)\n .getPropertyValue(match[1])\n .trim();\n return value || match[2]?.trim() || css;\n }\n}\n","import { FractalBrownianNoise } from \"./FractalBrownianNoise\";\nimport { MathUtils } from \"./MathUtils\";\nimport { RgbColor } from \"./RgbColor\";\n\n/**\n * All visual knobs of the tile. `scale`/`octaves` shape the large island\n * field, `detail*` the fine detail field; `clampLow`/`clampHigh` soft-clip\n * the blend (clean voids at the low end, no saturation at the high end),\n * `maxFill` caps how dense the dither can get, and `jitter` blends the\n * Bayer threshold with per-pixel random values for an organic look.\n */\nexport type DitherTileOptions = {\n cells: number;\n color: string;\n density: number;\n contrast: number;\n scale: number;\n octaves: number;\n detailScale: number;\n detailOctaves: number;\n detailStrength: number;\n clampLow: number;\n clampHigh: number;\n seed: number;\n maxFill: number;\n jitter: number;\n};\n\n/**\n * Renders a seamless dithered noise tile as a PNG data URL.\n *\n * Per pixel: the island and detail noise fields are blended into a density\n * value, soft-clamped, then thresholded against a Bayer matrix mixed with\n * per-pixel jitter to decide whether the pixel is inked. Because the noise\n * fields tile seamlessly, the resulting image can be repeated and scrolled\n * forever without visible seams.\n */\nexport class DitherTile {\n static readonly DEFAULT_CELLS = 512;\n\n private static readonly BAYER_4X4 = [\n [0, 8, 2, 10],\n [12, 4, 14, 6],\n [3, 11, 1, 9],\n [15, 7, 13, 5],\n ].map((row) => row.map((v) => (v + 0.5) / 16));\n\n private readonly islands: FractalBrownianNoise;\n private readonly detail: FractalBrownianNoise;\n\n constructor(private readonly options: DitherTileOptions) {\n this.islands = new FractalBrownianNoise(\n options.cells,\n options.scale,\n options.octaves,\n options.seed,\n );\n this.detail = new FractalBrownianNoise(\n options.cells,\n options.detailScale,\n options.detailOctaves,\n options.seed + 31337,\n );\n }\n\n toDataURL(): string {\n const {\n cells,\n color,\n density,\n contrast,\n detailStrength,\n clampLow,\n clampHigh,\n seed,\n maxFill,\n jitter,\n } = this.options;\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = cells;\n canvas.height = cells;\n\n const ctx = canvas.getContext(\"2d\");\n if (!ctx) return \"\";\n\n const rgb = RgbColor.fromCss(ctx, color);\n const image = ctx.createImageData(cells, cells);\n const data = image.data;\n\n for (let y = 0; y < cells; y++) {\n for (let x = 0; x < cells; x++) {\n const island = this.islands.sample(x, y);\n const detail = this.detail.sample(x, y);\n const combined = MathUtils.clamp(\n island + (detail - 0.5) * detailStrength,\n 0,\n 1,\n );\n const n = MathUtils.smoothstep(\n clampLow,\n Math.max(clampHigh, clampLow + 0.001),\n combined,\n );\n const p = MathUtils.clamp(\n (n - 0.5) * contrast + density,\n 0,\n maxFill,\n );\n const bayer = DitherTile.BAYER_4X4[y % 4][x % 4];\n const threshold =\n bayer * (1 - jitter)\n + MathUtils.hash2(x, y, seed + 991) * jitter;\n\n if (threshold < p) {\n const i = (y * cells + x) * 4;\n data[i] = rgb.r;\n data[i + 1] = rgb.g;\n data[i + 2] = rgb.b;\n data[i + 3] = Math.round(\n 255 * MathUtils.clamp(0.4 + 0.6 * n, 0, 1),\n );\n }\n }\n }\n\n ctx.putImageData(image, 0, 0);\n return canvas.toDataURL(\"image/png\");\n }\n}\n","import type { CSSProperties, HTMLAttributes, ReactNode } from \"react\";\nimport { Component } from \"react\";\nimport { DitherTile } from \"./DitherTile\";\nimport \"./DitherGridBackground.css\";\n\ntype CSSVars = CSSProperties & {\n [key: `--${string}`]: string | number | undefined;\n};\n\nexport interface DitherGridBackgroundProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"color\"> {\n backgroundColor?: string;\n\n gridColor?: string;\n gridSize?: number;\n gridOpacity?: number;\n\n noiseColor?: string;\n noiseOpacity?: number;\n noiseSize?: number;\n noiseTileCells?: number;\n noiseDensity?: number;\n noiseContrast?: number;\n noiseScale?: number;\n noiseOctaves?: number;\n noiseDetailScale?: number;\n noiseDetailOctaves?: number;\n noiseDetailStrength?: number;\n noiseClampLow?: number;\n noiseClampHigh?: number;\n noiseSeed?: number;\n noiseMaxFill?: number;\n noiseJitter?: number;\n noiseSpeed?: number;\n noiseAngle?: number;\n noiseTravel?: number;\n\n fadeStart?: number;\n fadeEnd?: number;\n\n animateNoise?: boolean;\n animateGlow?: boolean;\n\n children?: ReactNode;\n}\n\ninterface DitherGridBackgroundState {\n noiseImage: string;\n}\n\n/** Props that affect the generated tile; all others only set CSS variables. */\nconst TILE_INPUTS: ReadonlyArray<keyof DitherGridBackgroundProps> = [\n \"noiseTileCells\",\n \"noiseColor\",\n \"noiseDensity\",\n \"noiseContrast\",\n \"noiseScale\",\n \"noiseOctaves\",\n \"noiseDetailScale\",\n \"noiseDetailOctaves\",\n \"noiseDetailStrength\",\n \"noiseClampLow\",\n \"noiseClampHigh\",\n \"noiseSeed\",\n \"noiseMaxFill\",\n \"noiseJitter\",\n];\n\n/**\n * Layered page background: a scrolling dither texture, a static grid and an\n * optional ambient glow. Every visual property is forwarded as a CSS custom\n * property, so it can be animated at runtime (React state, CSS transitions)\n * without regenerating the texture.\n *\n * The dither tile is generated once and cached in state; it is only rebuilt\n * when one of the tile inputs (TILE_INPUTS) changes.\n */\nexport class DitherGridBackground extends Component<\n DitherGridBackgroundProps,\n DitherGridBackgroundState\n> {\n constructor(props: DitherGridBackgroundProps) {\n super(props);\n this.state = { noiseImage: this.createNoiseImage() };\n }\n\n override componentDidUpdate(prev: DitherGridBackgroundProps): void {\n if (TILE_INPUTS.some((key) => prev[key] !== this.props[key])) {\n this.setState({ noiseImage: this.createNoiseImage() });\n }\n }\n\n private createNoiseImage(): string {\n return new DitherTile({\n cells: this.props.noiseTileCells ?? DitherTile.DEFAULT_CELLS,\n color: this.props.noiseColor ?? \"var(--color-scarlet-600)\",\n density: this.props.noiseDensity ?? 0.55,\n contrast: this.props.noiseContrast ?? 1.8,\n scale: this.props.noiseScale ?? 32,\n octaves: this.props.noiseOctaves ?? 3,\n detailScale: this.props.noiseDetailScale ?? 5,\n detailOctaves: this.props.noiseDetailOctaves ?? 3,\n detailStrength: this.props.noiseDetailStrength ?? 0.2,\n clampLow: this.props.noiseClampLow ?? 0,\n clampHigh: this.props.noiseClampHigh ?? 0.9,\n seed: this.props.noiseSeed ?? 69,\n maxFill: this.props.noiseMaxFill ?? 0.6,\n jitter: this.props.noiseJitter ?? 0.3,\n }).toDataURL();\n }\n\n override render() {\n const {\n backgroundColor = \"linear-gradient(to bottom, var(--color-wine-950) 0%, var(--color-ink-950) 65%)\",\n\n gridColor = \"var(--color-steel-500)\",\n gridSize = 41,\n gridOpacity = 0.23,\n\n noiseColor = \"var(--color-scarlet-600)\",\n noiseOpacity = 0.5,\n noiseSize = 3,\n noiseTileCells = DitherTile.DEFAULT_CELLS,\n noiseDensity = 0.55,\n noiseContrast = 1.8,\n noiseScale = 32,\n noiseOctaves = 3,\n noiseDetailScale = 5,\n noiseDetailOctaves = 3,\n noiseDetailStrength = 0.2,\n noiseClampLow = 0,\n noiseClampHigh = 0.9,\n noiseSeed = 69,\n noiseMaxFill = 0.6,\n noiseJitter = 0.3,\n noiseSpeed = 180,\n noiseAngle = 100, // Yes this is slightly higher than 90\n noiseTravel = 1,\n\n fadeStart = 0,\n fadeEnd = 90,\n\n animateNoise = true,\n animateGlow = true,\n\n children,\n className = \"\",\n style,\n ...props\n } = this.props;\n\n const tileSize = noiseTileCells * noiseSize;\n const angle = (noiseAngle * Math.PI) / 180;\n const travelX = Math.round(Math.cos(angle) * noiseTravel) * tileSize;\n const travelY = Math.round(Math.sin(angle) * noiseTravel) * tileSize;\n const overscan =\n Math.max(Math.abs(travelX), Math.abs(travelY)) + tileSize;\n\n const variables: CSSVars = {\n \"--background-color\": backgroundColor,\n\n \"--grid-color\": gridColor,\n \"--grid-size\": `${gridSize}px`,\n \"--grid-opacity\": gridOpacity,\n\n \"--noise-image\": `url(\"${this.state.noiseImage}\")`,\n \"--noise-tile-size\": `${tileSize}px`,\n \"--noise-color\": noiseColor,\n \"--noise-opacity\": noiseOpacity,\n \"--noise-speed\": `${noiseSpeed}s`,\n \"--noise-x\": `${travelX}px`,\n \"--noise-y\": `${travelY}px`,\n \"--noise-overscan\": `${overscan}px`,\n\n \"--fade-start\": `${fadeStart}%`,\n \"--fade-end\": `${fadeEnd}%`,\n\n ...style,\n };\n\n return (\n <div\n className={[\n \"dither-grid-background\",\n animateNoise && \"dither-grid-background--animated\",\n animateGlow && \"dither-grid-background--glow\",\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n style={variables}\n {...props}\n >\n <div\n className=\"dither-grid-background__noise\"\n aria-hidden=\"true\"\n >\n <div className=\"dither-grid-background__noise-texture\" />\n </div>\n\n <div\n className=\"dither-grid-background__grid\"\n aria-hidden=\"true\"\n />\n\n <div className=\"dither-grid-background__content\">\n {children}\n </div>\n </div>\n );\n }\n}\n"],"mappings":"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"}
package/dist/style.css ADDED
@@ -0,0 +1,2 @@
1
+ .dither-grid-background{isolation:isolate;background:var(--background-color);min-height:100%;position:relative;overflow:hidden}.dither-grid-background__noise{z-index:0;pointer-events:none;opacity:var(--noise-opacity);-webkit-mask-image:linear-gradient(to bottom, black 0%, black var(--fade-start), #000000b3 55%, transparent var(--fade-end));-webkit-mask-image:linear-gradient(to bottom, black 0%, black var(--fade-start), #000000b3 55%, transparent var(--fade-end));mask-image:linear-gradient(to bottom, black 0%, black var(--fade-start), #000000b3 55%, transparent var(--fade-end));position:absolute;inset:0;overflow:hidden}.dither-grid-background__noise-texture{inset:calc(-1 * var(--noise-overscan));background-image:var(--noise-image);background-size:var(--noise-tile-size) var(--noise-tile-size);image-rendering:pixelated;background-repeat:repeat;position:absolute}.dither-grid-background--animated .dither-grid-background__noise-texture{will-change:transform;animation:dither-noise-scroll var(--noise-speed) linear infinite}@keyframes dither-noise-scroll{0%{transform:translate(0,0)}to{transform:translate3d(var(--noise-x), var(--noise-y), 0)}}.dither-grid-background__grid{z-index:0;pointer-events:none;background-image:linear-gradient(to right, var(--grid-color) 1px, transparent 1px), linear-gradient(to bottom, var(--grid-color) 1px, transparent 1px);background-size:var(--grid-size) var(--grid-size);opacity:var(--grid-opacity);position:absolute;inset:0}.dither-grid-background--glow:before{content:"";z-index:0;pointer-events:none;background:radial-gradient(ellipse at 50% 0%, var(--noise-color) 0%, transparent 65%);opacity:.15;animation:12s ease-in-out infinite alternate dither-glow;position:absolute;inset:-20%}@keyframes dither-glow{0%{opacity:.08;transform:translateY(-2%)}to{opacity:.22;transform:translateY(2%)}}.dither-grid-background__content{z-index:1;min-height:inherit;position:relative}@media (prefers-reduced-motion:reduce){.dither-grid-background--animated .dither-grid-background__noise-texture,.dither-grid-background--glow:before{animation:none}}
2
+ /*$vite$:1*/