@code-of-chaos/dither 0.1.0 → 0.3.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 +111 -11
- package/dist/DitherBackground-CrFqrwp8.js +304 -0
- package/dist/DitherBackground-CrFqrwp8.js.map +1 -0
- package/dist/DitherBackground.d.ts +37 -0
- package/dist/DitherBackground.d.ts.map +1 -0
- package/dist/DitherBackgroundView.d.ts +65 -0
- package/dist/DitherBackgroundView.d.ts.map +1 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -225
- package/dist/react.d.ts +24 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +45 -0
- package/dist/react.js.map +1 -0
- package/dist/style.css +1 -1
- package/package.json +11 -4
- package/dist/DitherGridBackground.d.ts +0 -53
- package/dist/DitherGridBackground.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# Dither.Js
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
Perlin/fBm noise engine for
|
|
3
|
+
Framework-agnostic animated bitmap-dithered gradient backgrounds - plain
|
|
4
|
+
TypeScript, DOM and CSS - with a tileable Perlin/fBm noise engine for
|
|
5
|
+
generating your own dither tiles.
|
|
5
6
|
|
|
6
7
|
The texture is a real Bayer-dithered noise bitmap (Perlin islands + fine
|
|
7
8
|
detail), generated once on a canvas and scrolled with a GPU-friendly CSS
|
|
8
|
-
transform. Everything visual - colors, grid, fade, speed, direction - is
|
|
9
|
-
|
|
9
|
+
transform. Everything visual - colors, grid, fade, speed, direction - is an
|
|
10
|
+
option, driven through CSS custom properties so it can be animated at runtime.
|
|
10
11
|
|
|
11
12
|
## Install
|
|
12
13
|
|
|
@@ -16,8 +17,31 @@ npm install @code-of-chaos/dither
|
|
|
16
17
|
|
|
17
18
|
## Usage
|
|
18
19
|
|
|
20
|
+
```ts
|
|
21
|
+
import { DitherBackground } from "@code-of-chaos/dither";
|
|
22
|
+
import "@code-of-chaos/dither/style.css";
|
|
23
|
+
|
|
24
|
+
const background = new DitherBackground({ noiseColor: "var(--accent)" });
|
|
25
|
+
background.mount(document.getElementById("root")!);
|
|
26
|
+
|
|
27
|
+
// Animate at runtime - cheap, the tile only rebuilds for tile inputs:
|
|
28
|
+
background.update({ gridSize: 64, noiseOpacity: 0.4 });
|
|
29
|
+
|
|
30
|
+
// Revert the element to its unmounted state:
|
|
31
|
+
background.destroy();
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`mount()` decorates the element: it prepends the hidden background layers
|
|
35
|
+
and applies the CSS custom properties. Anything else in the element renders
|
|
36
|
+
above the layers, so frameworks keep rendering their children normally.
|
|
37
|
+
|
|
38
|
+
### Framework recipes
|
|
39
|
+
|
|
40
|
+
React (and Next.js) ship as an adapter entry - `"use client"` is baked in, so
|
|
41
|
+
it drops into the Next.js App Router unchanged:
|
|
42
|
+
|
|
19
43
|
```tsx
|
|
20
|
-
import { DitherGridBackground } from "@code-of-chaos/dither";
|
|
44
|
+
import { DitherGridBackground } from "@code-of-chaos/dither/react";
|
|
21
45
|
import "@code-of-chaos/dither/style.css";
|
|
22
46
|
|
|
23
47
|
export function Page() {
|
|
@@ -29,9 +53,82 @@ export function Page() {
|
|
|
29
53
|
}
|
|
30
54
|
```
|
|
31
55
|
|
|
32
|
-
|
|
56
|
+
`DitherGridBackground` accepts every `DitherBackgroundOptions` key as a prop
|
|
57
|
+
plus normal div attributes and children. It is client-side only (the tile
|
|
58
|
+
needs a canvas) - in Next.js keep it inside client components.
|
|
59
|
+
|
|
60
|
+
Angular (standalone directive):
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
@Directive({ selector: "[ditherBackground]" })
|
|
64
|
+
export class DitherBackgroundDirective
|
|
65
|
+
implements AfterViewInit, OnChanges, OnDestroy
|
|
66
|
+
{
|
|
67
|
+
@Input() ditherBackground: DitherBackgroundOptions = {};
|
|
68
|
+
|
|
69
|
+
private background?: DitherBackground;
|
|
70
|
+
|
|
71
|
+
constructor(private el: ElementRef<HTMLElement>) {}
|
|
72
|
+
|
|
73
|
+
ngAfterViewInit(): void {
|
|
74
|
+
this.background = new DitherBackground(this.ditherBackground);
|
|
75
|
+
this.background.mount(this.el.nativeElement);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
ngOnChanges(): void {
|
|
79
|
+
this.background?.update(
|
|
80
|
+
new DitherBackgroundView(this.ditherBackground).options,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
ngOnDestroy(): void {
|
|
85
|
+
this.background?.destroy();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Vue (composable):
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
export function useDitherBackground(
|
|
94
|
+
el: Ref<HTMLElement | null>,
|
|
95
|
+
options: Ref<DitherBackgroundOptions>,
|
|
96
|
+
) {
|
|
97
|
+
let background: DitherBackground | undefined;
|
|
98
|
+
|
|
99
|
+
onMounted(() => {
|
|
100
|
+
background = new DitherBackground(options.value);
|
|
101
|
+
background.mount(el.value!);
|
|
102
|
+
});
|
|
103
|
+
watch(
|
|
104
|
+
options,
|
|
105
|
+
(next) => background?.update(new DitherBackgroundView(next).options),
|
|
106
|
+
{ deep: true },
|
|
107
|
+
);
|
|
108
|
+
onBeforeUnmount(() => background?.destroy());
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Svelte (action):
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
export function dither(
|
|
116
|
+
node: HTMLElement,
|
|
117
|
+
options: DitherBackgroundOptions = {},
|
|
118
|
+
) {
|
|
119
|
+
const background = new DitherBackground(options);
|
|
120
|
+
background.mount(node);
|
|
121
|
+
return {
|
|
122
|
+
update: (next: DitherBackgroundOptions) =>
|
|
123
|
+
background.update(new DitherBackgroundView(next).options),
|
|
124
|
+
destroy: () => background.destroy(),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Every visual option is optional (defaults in parentheses):
|
|
33
130
|
|
|
34
|
-
|
|
|
131
|
+
| Option | Purpose |
|
|
35
132
|
| --------------------------------------------------------------- | --------------------------------------------------------------------------- |
|
|
36
133
|
| `backgroundColor` | Base fill, any CSS color or gradient |
|
|
37
134
|
| `gridColor`, `gridSize`, `gridOpacity` | Structural grid lines (steel, 41px, 0.23) |
|
|
@@ -43,7 +140,7 @@ Every visual property is optional (defaults in parentheses):
|
|
|
43
140
|
| `noiseClampLow`, `noiseClampHigh` | Soft-clamp the noise: clean voids low, no saturation high |
|
|
44
141
|
| `noiseTileCells`, `noiseSeed` | Tile resolution and reproducible randomness |
|
|
45
142
|
| `noiseSpeed`, `noiseAngle`, `noiseTravel` | Scroll animation: seconds per loop, direction in degrees, distance in tiles |
|
|
46
|
-
| `fadeStart`, `fadeEnd` | Vertical fade of the dither layer (
|
|
143
|
+
| `fadeStart`, `fadeEnd` | Vertical fade of the dither layer (0%, 90%) |
|
|
47
144
|
| `animateNoise`, `animateGlow` | Enable the scroll / ambient glow (both on) |
|
|
48
145
|
|
|
49
146
|
Colors accept `var(--token, fallback)` references; they are resolved against
|
|
@@ -53,6 +150,8 @@ the document root, so theme variables work as parameters.
|
|
|
53
150
|
|
|
54
151
|
- `DitherTile` - generates a seamless Bayer-dithered PNG data URL from the
|
|
55
152
|
noise fields; use it for static overlays and textures.
|
|
153
|
+
- `DitherBackgroundView` - resolves options to defaults and computes the CSS
|
|
154
|
+
custom properties; useful when writing a thin framework wrapper.
|
|
56
155
|
- `PerlinNoise` / `FractalBrownianNoise` - tileable gradient noise.
|
|
57
156
|
- `MathUtils` / `RgbColor` - helpers used by the engine.
|
|
58
157
|
|
|
@@ -95,10 +194,11 @@ Notes:
|
|
|
95
194
|
|
|
96
195
|
## Notes
|
|
97
196
|
|
|
98
|
-
- The tile is generated in the browser (canvas), so
|
|
99
|
-
|
|
197
|
+
- The tile is generated in the browser (canvas), so mounting is client-side
|
|
198
|
+
only.
|
|
100
199
|
- `prefers-reduced-motion` disables the animations.
|
|
101
|
-
-
|
|
200
|
+
- Zero runtime dependencies; `react` (18+) is an optional peer dependency,
|
|
201
|
+
needed only for the `./react` entry.
|
|
102
202
|
|
|
103
203
|
## License
|
|
104
204
|
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
// src/MathUtils.ts
|
|
2
|
+
var e = class e {
|
|
3
|
+
static clamp(e, t, n) {
|
|
4
|
+
return Math.min(Math.max(e, t), n);
|
|
5
|
+
}
|
|
6
|
+
static lerp(e, t, n) {
|
|
7
|
+
return e + (t - e) * n;
|
|
8
|
+
}
|
|
9
|
+
static smoothstep(t, n, r) {
|
|
10
|
+
let i = e.clamp((r - t) / (n - t), 0, 1);
|
|
11
|
+
return i * i * (3 - 2 * i);
|
|
12
|
+
}
|
|
13
|
+
static mod(e, t) {
|
|
14
|
+
return (e % t + t) % t;
|
|
15
|
+
}
|
|
16
|
+
static hash2(e, t, n) {
|
|
17
|
+
let r = Math.imul(e, 374761393) ^ Math.imul(t, 668265263) ^ Math.imul(n, 2246822519);
|
|
18
|
+
return r = Math.imul(r ^ r >>> 13, 1274126177), r ^= r >>> 16, (r >>> 0) / 4294967296;
|
|
19
|
+
}
|
|
20
|
+
}, t = class t {
|
|
21
|
+
period;
|
|
22
|
+
seed;
|
|
23
|
+
static GRADIENTS = Array.from({ length: 16 }, (e, t) => {
|
|
24
|
+
let n = t / 16 * Math.PI * 2;
|
|
25
|
+
return [Math.cos(n), Math.sin(n)];
|
|
26
|
+
});
|
|
27
|
+
constructor(e, t) {
|
|
28
|
+
this.period = e, this.seed = t;
|
|
29
|
+
}
|
|
30
|
+
sample(t, n) {
|
|
31
|
+
let r = Math.floor(t), i = Math.floor(n), a = t - r, o = n - i, s = a * a * a * (a * (a * 6 - 15) + 10), c = o * o * o * (o * (o * 6 - 15) + 10), l = e.mod(r, this.period), u = e.mod(i, this.period), d = e.mod(r + 1, this.period), f = e.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);
|
|
32
|
+
return e.lerp(e.lerp(p, m, s), e.lerp(h, g, s), c);
|
|
33
|
+
}
|
|
34
|
+
gradientDot(n, r, i, a) {
|
|
35
|
+
let o = t.GRADIENTS, s = o[Math.floor(e.hash2(n, r, this.seed) * o.length) % o.length];
|
|
36
|
+
return s[0] * i + s[1] * a;
|
|
37
|
+
}
|
|
38
|
+
}, n = class {
|
|
39
|
+
noise;
|
|
40
|
+
frequency;
|
|
41
|
+
amplitude;
|
|
42
|
+
constructor(e, t, n) {
|
|
43
|
+
this.noise = e, this.frequency = t, this.amplitude = n;
|
|
44
|
+
}
|
|
45
|
+
}, r = class {
|
|
46
|
+
octaves = [];
|
|
47
|
+
totalAmplitude;
|
|
48
|
+
constructor(e, r, i, a) {
|
|
49
|
+
let o = 1, s = 0;
|
|
50
|
+
for (let c = 0; c < i; c++) {
|
|
51
|
+
let i = Math.max(1, Math.round(e / (r * 2 ** c)));
|
|
52
|
+
this.octaves.push(new n(new t(i, a + c * 1013), i / e, o)), s += o, o *= .5;
|
|
53
|
+
}
|
|
54
|
+
this.totalAmplitude = s;
|
|
55
|
+
}
|
|
56
|
+
sample(t, n) {
|
|
57
|
+
let r = 0;
|
|
58
|
+
for (let e of this.octaves) r += e.amplitude * e.noise.sample(t * e.frequency, n * e.frequency);
|
|
59
|
+
return e.clamp(r / this.totalAmplitude * .6 + .5, 0, 1);
|
|
60
|
+
}
|
|
61
|
+
}, i = class e {
|
|
62
|
+
r;
|
|
63
|
+
g;
|
|
64
|
+
b;
|
|
65
|
+
constructor(e, t, n) {
|
|
66
|
+
this.r = e, this.g = t, this.b = n;
|
|
67
|
+
}
|
|
68
|
+
static fromCss(t, n) {
|
|
69
|
+
let r = e.resolveVariable(n);
|
|
70
|
+
t.fillStyle = "#000000", t.fillStyle = r;
|
|
71
|
+
let i = t.fillStyle;
|
|
72
|
+
if (i.startsWith("#")) {
|
|
73
|
+
let t = i.slice(1), n = t.length === 3 ? t.split("").map((e) => e + e).join("") : t;
|
|
74
|
+
return new e(parseInt(n.slice(0, 2), 16), parseInt(n.slice(2, 4), 16), parseInt(n.slice(4, 6), 16));
|
|
75
|
+
}
|
|
76
|
+
let a = i.match(/rgba?\(([^)]+)\)/);
|
|
77
|
+
if (a) {
|
|
78
|
+
let [t, n, r] = a[1].split(/[,/\s]+/).filter(Boolean).map(Number);
|
|
79
|
+
return new e(t, n, r);
|
|
80
|
+
}
|
|
81
|
+
return new e(255, 255, 255);
|
|
82
|
+
}
|
|
83
|
+
static resolveVariable(e) {
|
|
84
|
+
let t = e.match(/^\s*var\(\s*(--[\w-]+)\s*(?:,\s*([^()]+))?\s*\)\s*$/);
|
|
85
|
+
return t && (getComputedStyle(document.documentElement).getPropertyValue(t[1]).trim() || t[2]?.trim()) || e;
|
|
86
|
+
}
|
|
87
|
+
}, a = class t {
|
|
88
|
+
options;
|
|
89
|
+
static DEFAULT_CELLS = 512;
|
|
90
|
+
static BAYER_4X4 = [
|
|
91
|
+
[
|
|
92
|
+
0,
|
|
93
|
+
8,
|
|
94
|
+
2,
|
|
95
|
+
10
|
|
96
|
+
],
|
|
97
|
+
[
|
|
98
|
+
12,
|
|
99
|
+
4,
|
|
100
|
+
14,
|
|
101
|
+
6
|
|
102
|
+
],
|
|
103
|
+
[
|
|
104
|
+
3,
|
|
105
|
+
11,
|
|
106
|
+
1,
|
|
107
|
+
9
|
|
108
|
+
],
|
|
109
|
+
[
|
|
110
|
+
15,
|
|
111
|
+
7,
|
|
112
|
+
13,
|
|
113
|
+
5
|
|
114
|
+
]
|
|
115
|
+
].map((e) => e.map((e) => (e + .5) / 16));
|
|
116
|
+
islands;
|
|
117
|
+
detail;
|
|
118
|
+
constructor(e) {
|
|
119
|
+
this.options = e, this.islands = new r(e.cells, e.scale, e.octaves, e.seed), this.detail = new r(e.cells, e.detailScale, e.detailOctaves, e.seed + 31337);
|
|
120
|
+
}
|
|
121
|
+
toDataURL() {
|
|
122
|
+
let { cells: n, color: r, density: a, contrast: o, detailStrength: s, clampLow: c, clampHigh: l, seed: u, maxFill: d, jitter: f } = this.options, p = document.createElement("canvas");
|
|
123
|
+
p.width = n, p.height = n;
|
|
124
|
+
let m = p.getContext("2d");
|
|
125
|
+
if (!m) return "";
|
|
126
|
+
let h = i.fromCss(m, r), g = m.createImageData(n, n), _ = g.data;
|
|
127
|
+
for (let r = 0; r < n; r++) for (let i = 0; i < n; i++) {
|
|
128
|
+
let p = this.islands.sample(i, r), m = this.detail.sample(i, r), g = e.clamp(p + (m - .5) * s, 0, 1), v = e.smoothstep(c, Math.max(l, c + .001), g), y = e.clamp((v - .5) * o + a, 0, d);
|
|
129
|
+
if (t.BAYER_4X4[r % 4][i % 4] * (1 - f) + e.hash2(i, r, u + 991) * f < y) {
|
|
130
|
+
let t = (r * n + i) * 4;
|
|
131
|
+
_[t] = h.r, _[t + 1] = h.g, _[t + 2] = h.b, _[t + 3] = Math.round(255 * e.clamp(.4 + .6 * v, 0, 1));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return m.putImageData(g, 0, 0), p.toDataURL("image/png");
|
|
135
|
+
}
|
|
136
|
+
}, o = class e {
|
|
137
|
+
static DEFAULTS = {
|
|
138
|
+
backgroundColor: "linear-gradient(to bottom, var(--color-wine-950) 0%, var(--color-ink-950) 65%)",
|
|
139
|
+
gridColor: "var(--color-steel-500)",
|
|
140
|
+
gridSize: 41,
|
|
141
|
+
gridOpacity: .23,
|
|
142
|
+
noiseColor: "var(--color-scarlet-600)",
|
|
143
|
+
noiseOpacity: .5,
|
|
144
|
+
noiseSize: 3,
|
|
145
|
+
noiseTileCells: a.DEFAULT_CELLS,
|
|
146
|
+
noiseDensity: .55,
|
|
147
|
+
noiseContrast: 1.8,
|
|
148
|
+
noiseScale: 32,
|
|
149
|
+
noiseOctaves: 3,
|
|
150
|
+
noiseDetailScale: 5,
|
|
151
|
+
noiseDetailOctaves: 3,
|
|
152
|
+
noiseDetailStrength: .2,
|
|
153
|
+
noiseClampLow: 0,
|
|
154
|
+
noiseClampHigh: .9,
|
|
155
|
+
noiseSeed: 69,
|
|
156
|
+
noiseMaxFill: .6,
|
|
157
|
+
noiseJitter: .3,
|
|
158
|
+
noiseSpeed: 180,
|
|
159
|
+
noiseAngle: 100,
|
|
160
|
+
noiseTravel: 1,
|
|
161
|
+
fadeStart: 0,
|
|
162
|
+
fadeEnd: 90,
|
|
163
|
+
animateNoise: !0,
|
|
164
|
+
animateGlow: !0
|
|
165
|
+
};
|
|
166
|
+
static TILE_INPUTS = [
|
|
167
|
+
"noiseTileCells",
|
|
168
|
+
"noiseColor",
|
|
169
|
+
"noiseDensity",
|
|
170
|
+
"noiseContrast",
|
|
171
|
+
"noiseScale",
|
|
172
|
+
"noiseOctaves",
|
|
173
|
+
"noiseDetailScale",
|
|
174
|
+
"noiseDetailOctaves",
|
|
175
|
+
"noiseDetailStrength",
|
|
176
|
+
"noiseClampLow",
|
|
177
|
+
"noiseClampHigh",
|
|
178
|
+
"noiseSeed",
|
|
179
|
+
"noiseMaxFill",
|
|
180
|
+
"noiseJitter"
|
|
181
|
+
];
|
|
182
|
+
options;
|
|
183
|
+
constructor(t = {}) {
|
|
184
|
+
this.options = {
|
|
185
|
+
...e.DEFAULTS,
|
|
186
|
+
...e.defined(t)
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
merge(t) {
|
|
190
|
+
return new e({
|
|
191
|
+
...this.options,
|
|
192
|
+
...e.defined(t)
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
rebuildsTile(t) {
|
|
196
|
+
return e.TILE_INPUTS.some((e) => this.options[e] !== t.options[e]);
|
|
197
|
+
}
|
|
198
|
+
tileOptions() {
|
|
199
|
+
let e = this.options;
|
|
200
|
+
return {
|
|
201
|
+
cells: e.noiseTileCells,
|
|
202
|
+
color: e.noiseColor,
|
|
203
|
+
density: e.noiseDensity,
|
|
204
|
+
contrast: e.noiseContrast,
|
|
205
|
+
scale: e.noiseScale,
|
|
206
|
+
octaves: e.noiseOctaves,
|
|
207
|
+
detailScale: e.noiseDetailScale,
|
|
208
|
+
detailOctaves: e.noiseDetailOctaves,
|
|
209
|
+
detailStrength: e.noiseDetailStrength,
|
|
210
|
+
clampLow: e.noiseClampLow,
|
|
211
|
+
clampHigh: e.noiseClampHigh,
|
|
212
|
+
seed: e.noiseSeed,
|
|
213
|
+
maxFill: e.noiseMaxFill,
|
|
214
|
+
jitter: e.noiseJitter
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
get tileSize() {
|
|
218
|
+
return this.options.noiseTileCells * this.options.noiseSize;
|
|
219
|
+
}
|
|
220
|
+
get travelX() {
|
|
221
|
+
let e = this.options.noiseAngle * Math.PI / 180;
|
|
222
|
+
return Math.round(Math.cos(e) * this.options.noiseTravel) * this.tileSize;
|
|
223
|
+
}
|
|
224
|
+
get travelY() {
|
|
225
|
+
let e = this.options.noiseAngle * Math.PI / 180;
|
|
226
|
+
return Math.round(Math.sin(e) * this.options.noiseTravel) * this.tileSize;
|
|
227
|
+
}
|
|
228
|
+
get overscan() {
|
|
229
|
+
return Math.max(Math.abs(this.travelX), Math.abs(this.travelY)) + this.tileSize;
|
|
230
|
+
}
|
|
231
|
+
cssVars(e) {
|
|
232
|
+
let t = this.options;
|
|
233
|
+
return {
|
|
234
|
+
"--background-color": t.backgroundColor,
|
|
235
|
+
"--grid-color": t.gridColor,
|
|
236
|
+
"--grid-size": `${t.gridSize}px`,
|
|
237
|
+
"--grid-opacity": String(t.gridOpacity),
|
|
238
|
+
"--noise-image": `url("${e}")`,
|
|
239
|
+
"--noise-tile-size": `${this.tileSize}px`,
|
|
240
|
+
"--noise-color": t.noiseColor,
|
|
241
|
+
"--noise-opacity": String(t.noiseOpacity),
|
|
242
|
+
"--noise-speed": `${t.noiseSpeed}s`,
|
|
243
|
+
"--noise-x": `${this.travelX}px`,
|
|
244
|
+
"--noise-y": `${this.travelY}px`,
|
|
245
|
+
"--noise-overscan": `${this.overscan}px`,
|
|
246
|
+
"--fade-start": `${t.fadeStart}%`,
|
|
247
|
+
"--fade-end": `${t.fadeEnd}%`
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
static defined(e) {
|
|
251
|
+
let t = Object.entries(e).filter(([, e]) => e !== void 0);
|
|
252
|
+
return Object.fromEntries(t);
|
|
253
|
+
}
|
|
254
|
+
}, s = class e {
|
|
255
|
+
static ROOT_CLASS = "dither-grid-background";
|
|
256
|
+
static ANIMATED_CLASS = "dither-grid-background--animated";
|
|
257
|
+
static GLOW_CLASS = "dither-grid-background--glow";
|
|
258
|
+
view;
|
|
259
|
+
noiseImage = "";
|
|
260
|
+
root = null;
|
|
261
|
+
layers = [];
|
|
262
|
+
constructor(e = {}) {
|
|
263
|
+
this.view = new o(e);
|
|
264
|
+
}
|
|
265
|
+
mount(e) {
|
|
266
|
+
if (this.root) throw Error("DitherBackground is already mounted");
|
|
267
|
+
this.root = e;
|
|
268
|
+
let t = document.createElement("div");
|
|
269
|
+
t.className = "dither-grid-background__noise-texture";
|
|
270
|
+
let n = document.createElement("div");
|
|
271
|
+
n.className = "dither-grid-background__noise", n.setAttribute("aria-hidden", "true"), n.append(t);
|
|
272
|
+
let r = document.createElement("div");
|
|
273
|
+
r.className = "dither-grid-background__grid", r.setAttribute("aria-hidden", "true"), this.layers = [n, r], e.prepend(...this.layers), this.noiseImage = this.createNoiseImage(), this.apply();
|
|
274
|
+
}
|
|
275
|
+
update(e) {
|
|
276
|
+
let t = this.view.merge(e), n = this.view.rebuildsTile(t);
|
|
277
|
+
this.view = t, n && (this.noiseImage = this.createNoiseImage()), this.apply();
|
|
278
|
+
}
|
|
279
|
+
destroy() {
|
|
280
|
+
let t = this.root;
|
|
281
|
+
if (t) {
|
|
282
|
+
for (let e of this.layers) e.remove();
|
|
283
|
+
this.layers = [], t.classList.remove(e.ROOT_CLASS, e.ANIMATED_CLASS, e.GLOW_CLASS);
|
|
284
|
+
for (let e of Object.keys(this.view.cssVars(""))) t.style.removeProperty(e);
|
|
285
|
+
this.root = null, this.noiseImage = "";
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
get element() {
|
|
289
|
+
return this.root;
|
|
290
|
+
}
|
|
291
|
+
apply() {
|
|
292
|
+
let t = this.root;
|
|
293
|
+
if (!t) return;
|
|
294
|
+
t.classList.add(e.ROOT_CLASS), t.classList.toggle(e.ANIMATED_CLASS, this.view.options.animateNoise), t.classList.toggle(e.GLOW_CLASS, this.view.options.animateGlow);
|
|
295
|
+
let n = this.view.cssVars(this.noiseImage);
|
|
296
|
+
for (let [e, r] of Object.entries(n)) t.style.setProperty(e, r);
|
|
297
|
+
}
|
|
298
|
+
createNoiseImage() {
|
|
299
|
+
return new a(this.view.tileOptions()).toDataURL();
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
export { r as a, i, o as n, t as o, a as r, e as s, s as t };
|
|
303
|
+
|
|
304
|
+
//# sourceMappingURL=DitherBackground-CrFqrwp8.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DitherBackground-CrFqrwp8.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","DitherBackgroundView","partial","key","other","noiseImage","entries","DitherBackground","container","texture","grid","next","rebuildTile","root","layer","name","vars"],"sources":["../src/MathUtils.ts","../src/PerlinNoise.ts","../src/FractalBrownianNoise.ts","../src/RgbColor.ts","../src/DitherTile.ts","../src/DitherBackgroundView.ts","../src/DitherBackground.ts"],"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 { DitherTileOptions } from \"./DitherTile\";\nimport { DitherTile } from \"./DitherTile\";\n\n/**\n * Every visual knob of a dither background. All fields are optional and fall\n * back to `DitherBackgroundView.DEFAULTS`. Values are applied as CSS custom\n * properties, so they can be animated at runtime without rebuilding the\n * background; only `DitherBackgroundView.TILE_INPUTS` regenerate the dither\n * bitmap itself.\n */\nexport type DitherBackgroundOptions = {\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\nexport type DitherBackgroundCssVars = Record<`--${string}`, string>;\n\n/**\n * Resolves `DitherBackgroundOptions` against the defaults and computes the\n * derived values (tile geometry, scroll travel, CSS custom properties). Used\n * internally by `DitherBackground`; framework wrappers can use it standalone.\n */\nexport class DitherBackgroundView {\n static readonly DEFAULTS: Required<DitherBackgroundOptions> = {\n backgroundColor:\n \"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,\n noiseTravel: 1,\n\n fadeStart: 0,\n fadeEnd: 90,\n\n animateNoise: true,\n animateGlow: true,\n };\n\n /** Options that affect the generated tile; all others only set CSS variables. */\n static readonly TILE_INPUTS: ReadonlyArray<keyof DitherBackgroundOptions> =\n [\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 readonly options: Required<DitherBackgroundOptions>;\n\n constructor(options: DitherBackgroundOptions = {}) {\n this.options = {\n ...DitherBackgroundView.DEFAULTS,\n ...DitherBackgroundView.defined(options),\n };\n }\n\n /** Returns a new view with `partial` merged in; `undefined` values are ignored. */\n merge(partial: DitherBackgroundOptions): DitherBackgroundView {\n return new DitherBackgroundView({\n ...this.options,\n ...DitherBackgroundView.defined(partial),\n });\n }\n\n /** True when the two views would produce different dither tiles. */\n rebuildsTile(other: DitherBackgroundView): boolean {\n return DitherBackgroundView.TILE_INPUTS.some(\n (key) => this.options[key] !== other.options[key],\n );\n }\n\n tileOptions(): DitherTileOptions {\n const o = this.options;\n return {\n cells: o.noiseTileCells,\n color: o.noiseColor,\n density: o.noiseDensity,\n contrast: o.noiseContrast,\n scale: o.noiseScale,\n octaves: o.noiseOctaves,\n detailScale: o.noiseDetailScale,\n detailOctaves: o.noiseDetailOctaves,\n detailStrength: o.noiseDetailStrength,\n clampLow: o.noiseClampLow,\n clampHigh: o.noiseClampHigh,\n seed: o.noiseSeed,\n maxFill: o.noiseMaxFill,\n jitter: o.noiseJitter,\n };\n }\n\n get tileSize(): number {\n return this.options.noiseTileCells * this.options.noiseSize;\n }\n\n /** Horizontal scroll distance in px, snapped to whole tiles (seamless loop). */\n get travelX(): number {\n const angle = (this.options.noiseAngle * Math.PI) / 180;\n return (\n Math.round(Math.cos(angle) * this.options.noiseTravel)\n * this.tileSize\n );\n }\n\n /** Vertical scroll distance in px, snapped to whole tiles (seamless loop). */\n get travelY(): number {\n const angle = (this.options.noiseAngle * Math.PI) / 180;\n return (\n Math.round(Math.sin(angle) * this.options.noiseTravel)\n * this.tileSize\n );\n }\n\n /** Oversizes the texture wrapper so the scroll never exposes its edges. */\n get overscan(): number {\n return (\n Math.max(Math.abs(this.travelX), Math.abs(this.travelY))\n + this.tileSize\n );\n }\n\n cssVars(noiseImage: string): DitherBackgroundCssVars {\n const o = this.options;\n return {\n \"--background-color\": o.backgroundColor,\n\n \"--grid-color\": o.gridColor,\n \"--grid-size\": `${o.gridSize}px`,\n \"--grid-opacity\": String(o.gridOpacity),\n\n \"--noise-image\": `url(\"${noiseImage}\")`,\n \"--noise-tile-size\": `${this.tileSize}px`,\n \"--noise-color\": o.noiseColor,\n \"--noise-opacity\": String(o.noiseOpacity),\n \"--noise-speed\": `${o.noiseSpeed}s`,\n \"--noise-x\": `${this.travelX}px`,\n \"--noise-y\": `${this.travelY}px`,\n \"--noise-overscan\": `${this.overscan}px`,\n\n \"--fade-start\": `${o.fadeStart}%`,\n \"--fade-end\": `${o.fadeEnd}%`,\n };\n }\n\n private static defined(\n options: DitherBackgroundOptions,\n ): DitherBackgroundOptions {\n const entries = Object.entries(options).filter(\n ([, value]) => value !== undefined,\n );\n return Object.fromEntries(entries) as DitherBackgroundOptions;\n }\n}\n","import \"./DitherBackground.css\";\nimport { DitherTile } from \"./DitherTile\";\nimport type {\n DitherBackgroundCssVars,\n DitherBackgroundOptions,\n} from \"./DitherBackgroundView\";\nimport { DitherBackgroundView } from \"./DitherBackgroundView\";\n\n/**\n * Framework-agnostic layered background: a scrolling Bayer-dithered noise\n * texture, a static grid and an optional ambient glow. Plain DOM — mount it\n * on any element from any framework (or none).\n *\n * `mount()` decorates the container: it prepends the hidden layer elements\n * and applies the base class plus the CSS custom properties. Anything else\n * in the container renders above the layers, so frameworks keep rendering\n * their children normally. Options map 1:1 to the CSS custom properties and\n * can be changed at any time via `update()`; the dither bitmap is only\n * regenerated when one of the tile inputs changes.\n *\n * The tile is generated on a canvas, so `mount()` requires a browser DOM.\n */\nexport class DitherBackground {\n static readonly ROOT_CLASS = \"dither-grid-background\";\n static readonly ANIMATED_CLASS = \"dither-grid-background--animated\";\n static readonly GLOW_CLASS = \"dither-grid-background--glow\";\n\n private view: DitherBackgroundView;\n private noiseImage = \"\";\n private root: HTMLElement | null = null;\n private layers: HTMLElement[] = [];\n\n constructor(options: DitherBackgroundOptions = {}) {\n this.view = new DitherBackgroundView(options);\n }\n\n /** Decorates `container` with the background layers. Idempotent per instance. */\n mount(container: HTMLElement): void {\n if (this.root) {\n throw new Error(\"DitherBackground is already mounted\");\n }\n this.root = container;\n\n const texture = document.createElement(\"div\");\n texture.className = \"dither-grid-background__noise-texture\";\n const noise = document.createElement(\"div\");\n noise.className = \"dither-grid-background__noise\";\n noise.setAttribute(\"aria-hidden\", \"true\");\n noise.append(texture);\n\n const grid = document.createElement(\"div\");\n grid.className = \"dither-grid-background__grid\";\n grid.setAttribute(\"aria-hidden\", \"true\");\n\n this.layers = [noise, grid];\n container.prepend(...this.layers);\n\n this.noiseImage = this.createNoiseImage();\n this.apply();\n }\n\n /** Merges new options; regenerates the tile only when tile inputs change. */\n update(partial: DitherBackgroundOptions): void {\n const next = this.view.merge(partial);\n const rebuildTile = this.view.rebuildsTile(next);\n this.view = next;\n if (rebuildTile) {\n this.noiseImage = this.createNoiseImage();\n }\n this.apply();\n }\n\n /** Removes the layers and reverts the container to its unmounted state. */\n destroy(): void {\n const root = this.root;\n if (!root) return;\n\n for (const layer of this.layers) layer.remove();\n this.layers = [];\n\n root.classList.remove(\n DitherBackground.ROOT_CLASS,\n DitherBackground.ANIMATED_CLASS,\n DitherBackground.GLOW_CLASS,\n );\n for (const name of Object.keys(this.view.cssVars(\"\"))) {\n root.style.removeProperty(name);\n }\n\n this.root = null;\n this.noiseImage = \"\";\n }\n\n /** The decorated container element, or `null` before mount / after destroy. */\n get element(): HTMLElement | null {\n return this.root;\n }\n\n private apply(): void {\n const root = this.root;\n if (!root) return;\n\n root.classList.add(DitherBackground.ROOT_CLASS);\n root.classList.toggle(\n DitherBackground.ANIMATED_CLASS,\n this.view.options.animateNoise,\n );\n root.classList.toggle(\n DitherBackground.GLOW_CLASS,\n this.view.options.animateGlow,\n );\n\n const vars: DitherBackgroundCssVars = this.view.cssVars(\n this.noiseImage,\n );\n for (const [name, value] of Object.entries(vars)) {\n root.style.setProperty(name, value);\n }\n }\n\n private createNoiseImage(): string {\n return new DitherTile(this.view.tileOptions()).toDataURL();\n }\n}\n"],"mappings":";AAIA,IAAaA,IAAb,MAAaA,EAAU;CACnB,OAAO,MAAM,GAAe,GAAa,GAAqB;EAC1D,OAAO,KAAK,IAAI,KAAK,IAAIC,GAAOC,CAAG,GAAGC,CAAG;CAC7C;CAEA,OAAO,KAAK,GAAW,GAAW,GAAmB;EACjD,OAAOC,KAAKC,IAAID,KAAKE;CACzB;CAEA,OAAO,WAAW,GAAe,GAAe,GAAmB;EAC/D,IAAMA,IAAIN,EAAU,OAAOO,IAAIC,MAAUC,IAAQD,IAAQ,GAAG,CAAC;EAC7D,OAAOF,IAAIA,KAAK,IAAI,IAAIA;CAC5B;CAEA,OAAO,IAAI,GAAe,GAAwB;EAC9C,QAASL,IAAQS,IAAUA,KAAUA;CACzC;CAEA,OAAO,MAAM,GAAW,GAAW,GAAsB;EACrD,IAAIC,IACA,KAAK,KAAKJ,GAAG,SAAS,IACpB,KAAK,KAAKK,GAAG,SAAS,IACtB,KAAK,KAAKC,GAAM,UAAU;EAGhC,OAFA,IAAI,KAAK,KAAKF,IAAKA,MAAM,IAAK,UAAU,GACxC,KAAKA,MAAM,KACHA,MAAM,KAAK;CACvB;AACJ,GCxBaG,IAAb,MAAaA,EAAY;CASA;CACA;CATrB,OAAwB,YAEpB,MAAM,KAAK,EAAE,QAAQ,GAAG,IAAI,GAAG,MAAM;EACrC,IAAMC,IAASC,IAAI,KAAM,KAAK,KAAK;EACnC,OAAO,CAAC,KAAK,IAAID,CAAK,GAAG,KAAK,IAAIA,CAAK,CAAC;CAC5C,CAAC;CAED,YACI,GACA,GACF;EADmB,AADA,KAAA,SAAAL,GACA,KAAA,OAAAG;CAClB;CAEH,OAAO,GAAW,GAAmB;EACjC,IAAMI,IAAK,KAAK,MAAMV,CAAC,GACjBW,IAAK,KAAK,MAAMN,CAAC,GACjBO,IAAKZ,IAAIU,GACTG,IAAKR,IAAIM,GAETG,IAAIF,IAAKA,IAAKA,KAAMA,KAAMA,IAAK,IAAI,MAAM,KACzCG,IAAIF,IAAKA,IAAKA,KAAMA,KAAMA,IAAK,IAAI,MAAM,KAEzCG,IAAMvB,EAAU,IAAIiB,GAAI,KAAK,MAAM,GACnCO,IAAMxB,EAAU,IAAIkB,GAAI,KAAK,MAAM,GACnCO,IAAMzB,EAAU,IAAIiB,IAAK,GAAG,KAAK,MAAM,GACvCS,IAAM1B,EAAU,IAAIkB,IAAK,GAAG,KAAK,MAAM,GAEvCS,IAAM,KAAK,YAAYJ,GAAKC,GAAKL,GAAIC,CAAE,GACvCQ,IAAM,KAAK,YAAYH,GAAKD,GAAKL,IAAK,GAAGC,CAAE,GAC3CS,IAAM,KAAK,YAAYN,GAAKG,GAAKP,GAAIC,IAAK,CAAC,GAC3CU,IAAM,KAAK,YAAYL,GAAKC,GAAKP,IAAK,GAAGC,IAAK,CAAC;EAErD,OAAOpB,EAAU,KACbA,EAAU,KAAK2B,GAAKC,GAAKP,CAAC,GAC1BrB,EAAU,KAAK6B,GAAKC,GAAKT,CAAC,GAC1BC,CACJ;CACJ;CAEA,YACI,GACA,GACA,GACA,GACM;EACN,IAAMS,IAAYjB,EAAY,WACxBkB,IACFD,EACI,KAAK,MACD/B,EAAU,MAAMiC,GAAIC,GAAI,KAAK,IAAI,IAAIH,EAAU,MACnD,IAAIA,EAAU;EAEtB,OAAOC,EAAE,KAAKG,IAAKH,EAAE,KAAKI;CAC9B;AACJ,GC1DMC,IAAN,MAAa;CAEI;CACA;CACA;CAHb,YACI,GACA,GACA,GACF;EADW,AAFA,KAAA,QAAAC,GACA,KAAA,YAAAC,GACA,KAAA,YAAAC;CACV;AACP,GAQaC,IAAb,MAAkC;CAC9B,UAAqC,CAAC;CACtC;CAEA,YAAY,GAAe,GAAe,GAAiB,GAAc;EACrE,IAAID,IAAY,GACZE,IAAQ;EAEZ,KAAK,IAAIC,IAAI,GAAGA,IAAIC,GAAS,KAAK;GAC9B,IAAMlC,IAAS,KAAK,IAAI,GAAG,KAAK,MAAMmC,KAASC,IAAQ,KAAKH,EAAE,CAAC;GAS/D,AARA,KAAK,QAAQ,KACT,IAAIN,EACA,IAAIvB,EAAYJ,GAAQG,IAAO8B,IAAI,IAAI,GACvCjC,IAASmC,GACTL,CACJ,CACJ,GACA,KAASA,GACT,KAAa;EACjB;EAEA,KAAK,iBAAiBE;CAC1B;CAEA,OAAO,GAAW,GAAmB;EACjC,IAAIK,IAAM;EAEV,KAAK,IAAMC,KAAU,KAAK,SACtB,KACIA,EAAO,YACLA,EAAO,MAAM,OACXzC,IAAIyC,EAAO,WACXpC,IAAIoC,EAAO,SACf;EAGR,OAAOhD,EAAU,MAAO+C,IAAM,KAAK,iBAAkB,KAAM,IAAK,GAAG,CAAC;CACxE;AACJ,GCtDaE,IAAb,MAAaA,EAAS;CAEL;CACA;CACA;CAHb,YACI,GACA,GACA,GACF;EADW,AAFA,KAAA,IAAAC,GACA,KAAA,IAAAlB,GACA,KAAA,IAAA3B;CACV;CAUH,OAAO,QAAQ,GAA+B,GAAuB;EACjE,IAAM8C,IAAWF,EAAS,gBAAgBG,CAAG;EAE7C,AADA,EAAI,YAAY,WAChB,EAAI,YAAYD;EAChB,IAAME,IAAaC,EAAI;EAEvB,IAAID,EAAW,WAAW,GAAG,GAAG;GAC5B,IAAME,IAAMF,EAAW,MAAM,CAAC,GACxBG,IACFD,EAAI,WAAW,IACTA,EACK,MAAM,EAAE,CAAC,CACT,KAAK,MAAME,IAAIA,CAAC,CAAC,CACjB,KAAK,EAAE,IACZF;GACV,OAAO,IAAIN,EACP,SAASO,EAAK,MAAM,GAAG,CAAC,GAAG,EAAE,GAC7B,SAASA,EAAK,MAAM,GAAG,CAAC,GAAG,EAAE,GAC7B,SAASA,EAAK,MAAM,GAAG,CAAC,GAAG,EAAE,CACjC;EACJ;EAEA,IAAME,IAAQL,EAAW,MAAM,kBAAkB;EACjD,IAAIK,GAAO;GACP,IAAM,CAACR,GAAGlB,GAAG3B,KAAKqD,EAAM,EAAE,CACrB,MAAM,SAAS,CAAC,CAChB,OAAO,OAAO,CAAC,CACf,IAAI,MAAM;GACf,OAAO,IAAIT,EAASC,GAAGlB,GAAG3B,CAAC;EAC/B;EAEA,OAAO,IAAI4C,EAAS,KAAK,KAAK,GAAG;CACrC;CAEA,OAAe,gBAAgB,GAAqB;EAChD,IAAMS,IAAQN,EAAI,MACd,qDACJ;EAMA,OALKM,MAES,iBAAiB,SAAS,eAAe,CAAC,CACnD,iBAAiBA,EAAM,EAAE,CAAC,CAC1B,KACE,KAASA,EAAM,EAAE,EAAE,KAAK,MAAKN;CACxC;AACJ,GCxBaO,IAAb,MAAaA,EAAW;CAaS;CAZ7B,OAAgB,gBAAgB;CAEhC,OAAwB,YAAY;EAChC;GAAC;GAAG;GAAG;GAAG;EAAE;EACZ;GAAC;GAAI;GAAG;GAAI;EAAC;EACb;GAAC;GAAG;GAAI;GAAG;EAAC;EACZ;GAAC;GAAI;GAAG;GAAI;EAAC;CACjB,CAAC,CAAC,KAAK,MAAQC,EAAI,KAAK,OAAOtC,IAAI,MAAO,EAAE,CAAC;CAE7C;CACA;CAEA,YAAY,GAA6C;EAOrD,AAPyB,KAAA,UAAAuC,GACzB,KAAK,UAAU,IAAIpB,EACfoB,EAAQ,OACRA,EAAQ,OACRA,EAAQ,SACRA,EAAQ,IACZ,GACA,KAAK,SAAS,IAAIpB,EACdoB,EAAQ,OACRA,EAAQ,aACRA,EAAQ,eACRA,EAAQ,OAAO,KACnB;CACJ;CAEA,YAAoB;EAChB,IAAM,EACF,UACA,UACA,YACA,aACA,mBACA,aACA,cACA,SACA,YACA,cACA,KAAK,SAEHC,IAAS,SAAS,cAAc,QAAQ;EAE9C,AADA,EAAO,QAAQjB,GACf,EAAO,SAASA;EAEhB,IAAMS,IAAMQ,EAAO,WAAW,IAAI;EAClC,IAAI,CAACR,GAAK,OAAO;EAEjB,IAAMS,IAAMd,EAAS,QAAQK,GAAKU,CAAK,GACjCC,IAAQX,EAAI,gBAAgBT,GAAOA,CAAK,GACxCqB,IAAOD,EAAM;EAEnB,KAAK,IAAIrD,IAAI,GAAGA,IAAIiC,GAAO,KACvB,KAAK,IAAItC,IAAI,GAAGA,IAAIsC,GAAO,KAAK;GAC5B,IAAMsB,IAAS,KAAK,QAAQ,OAAO5D,GAAGK,CAAC,GACjCwD,IAAS,KAAK,OAAO,OAAO7D,GAAGK,CAAC,GAChCyD,IAAWrE,EAAU,MACvBmE,KAAUC,IAAS,MAAOE,GAC1B,GACA,CACJ,GACMC,IAAIvE,EAAU,WAChBwE,GACA,KAAK,IAAIC,GAAWD,IAAW,IAAK,GACpCH,CACJ,GACMK,IAAI1E,EAAU,OACfuE,IAAI,MAAOI,IAAWC,GACvB,GACAC,CACJ;GAMA,IALclB,EAAW,UAAU/C,IAAI,EAAE,CAACL,IAAI,MAEjC,IAAIuE,KACX9E,EAAU,MAAMO,GAAGK,GAAGC,IAAO,GAAG,IAAIiE,IAE1BJ,GAAG;IACf,IAAM1D,KAAKJ,IAAIiC,IAAQtC,KAAK;IAI5B,AAHA,EAAKS,KAAK+C,EAAI,GACd,EAAK/C,IAAI,KAAK+C,EAAI,GAClB,EAAK/C,IAAI,KAAK+C,EAAI,GAClB,EAAK/C,IAAI,KAAK,KAAK,MACf,MAAMhB,EAAU,MAAM,KAAM,KAAMuE,GAAG,GAAG,CAAC,CAC7C;GACJ;EACJ;EAIJ,OADAjB,EAAI,aAAaW,GAAO,GAAG,CAAC,GACrBH,EAAO,UAAU,WAAW;CACvC;AACJ,GC9EaiB,IAAb,MAAaA,EAAqB;CAC9B,OAAgB,WAA8C;EAC1D,iBACI;EAEJ,WAAW;EACX,UAAU;EACV,aAAa;EAEb,YAAY;EACZ,cAAc;EACd,WAAW;EACX,gBAAgBpB,EAAW;EAC3B,cAAc;EACd,eAAe;EACf,YAAY;EACZ,cAAc;EACd,kBAAkB;EAClB,oBAAoB;EACpB,qBAAqB;EACrB,eAAe;EACf,gBAAgB;EAChB,WAAW;EACX,cAAc;EACd,aAAa;EACb,YAAY;EACZ,YAAY;EACZ,aAAa;EAEb,WAAW;EACX,SAAS;EAET,cAAc;EACd,aAAa;CACjB;CAGA,OAAgB,cACZ;EACI;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACJ;CAEJ;CAEA,YAAY,IAAmC,CAAC,GAAG;EAC/C,KAAK,UAAU;GACX,GAAGoB,EAAqB;GACxB,GAAGA,EAAqB,QAAQlB,CAAO;EAC3C;CACJ;CAGA,MAAM,GAAwD;EAC1D,OAAO,IAAIkB,EAAqB;GAC5B,GAAG,KAAK;GACR,GAAGA,EAAqB,QAAQC,CAAO;EAC3C,CAAC;CACL;CAGA,aAAa,GAAsC;EAC/C,OAAOD,EAAqB,YAAY,MACnC,MAAQ,KAAK,QAAQE,OAASC,EAAM,QAAQD,EACjD;CACJ;CAEA,cAAiC;EAC7B,IAAMtC,IAAI,KAAK;EACf,OAAO;GACH,OAAOA,EAAE;GACT,OAAOA,EAAE;GACT,SAASA,EAAE;GACX,UAAUA,EAAE;GACZ,OAAOA,EAAE;GACT,SAASA,EAAE;GACX,aAAaA,EAAE;GACf,eAAeA,EAAE;GACjB,gBAAgBA,EAAE;GAClB,UAAUA,EAAE;GACZ,WAAWA,EAAE;GACb,MAAMA,EAAE;GACR,SAASA,EAAE;GACX,QAAQA,EAAE;EACd;CACJ;CAEA,IAAI,WAAmB;EACnB,OAAO,KAAK,QAAQ,iBAAiB,KAAK,QAAQ;CACtD;CAGA,IAAI,UAAkB;EAClB,IAAM5B,IAAS,KAAK,QAAQ,aAAa,KAAK,KAAM;EACpD,OACI,KAAK,MAAM,KAAK,IAAIA,CAAK,IAAI,KAAK,QAAQ,WAAW,IACnD,KAAK;CAEf;CAGA,IAAI,UAAkB;EAClB,IAAMA,IAAS,KAAK,QAAQ,aAAa,KAAK,KAAM;EACpD,OACI,KAAK,MAAM,KAAK,IAAIA,CAAK,IAAI,KAAK,QAAQ,WAAW,IACnD,KAAK;CAEf;CAGA,IAAI,WAAmB;EACnB,OACI,KAAK,IAAI,KAAK,IAAI,KAAK,OAAO,GAAG,KAAK,IAAI,KAAK,OAAO,CAAC,IACrD,KAAK;CAEf;CAEA,QAAQ,GAA6C;EACjD,IAAM4B,IAAI,KAAK;EACf,OAAO;GACH,sBAAsBA,EAAE;GAExB,gBAAgBA,EAAE;GAClB,eAAe,GAAGA,EAAE,SAAS;GAC7B,kBAAkB,OAAOA,EAAE,WAAW;GAEtC,iBAAiB,QAAQwC,EAAW;GACpC,qBAAqB,GAAG,KAAK,SAAS;GACtC,iBAAiBxC,EAAE;GACnB,mBAAmB,OAAOA,EAAE,YAAY;GACxC,iBAAiB,GAAGA,EAAE,WAAW;GACjC,aAAa,GAAG,KAAK,QAAQ;GAC7B,aAAa,GAAG,KAAK,QAAQ;GAC7B,oBAAoB,GAAG,KAAK,SAAS;GAErC,gBAAgB,GAAGA,EAAE,UAAU;GAC/B,cAAc,GAAGA,EAAE,QAAQ;EAC/B;CACJ;CAEA,OAAe,QACX,GACuB;EACvB,IAAMyC,IAAU,OAAO,QAAQvB,CAAO,CAAC,CAAC,QACnC,GAAG5D,OAAWA,MAAU,KAAA,CAC7B;EACA,OAAO,OAAO,YAAYmF,CAAO;CACrC;AACJ,GC7LaC,IAAb,MAAaA,EAAiB;CAC1B,OAAgB,aAAa;CAC7B,OAAgB,iBAAiB;CACjC,OAAgB,aAAa;CAE7B;CACA,aAAqB;CACrB,OAAmC;CACnC,SAAgC,CAAC;CAEjC,YAAY,IAAmC,CAAC,GAAG;EAC/C,KAAK,OAAO,IAAIN,EAAqBlB,CAAO;CAChD;CAGA,MAAM,GAA8B;EAChC,IAAI,KAAK,MACL,MAAU,MAAM,qCAAqC;EAEzD,KAAK,OAAOyB;EAEZ,IAAMC,IAAU,SAAS,cAAc,KAAK;EAC5C,EAAQ,YAAY;EACpB,IAAMjD,IAAQ,SAAS,cAAc,KAAK;EAG1C,AAFA,EAAM,YAAY,iCAClBA,EAAM,aAAa,eAAe,MAAM,GACxCA,EAAM,OAAOiD,CAAO;EAEpB,IAAMC,IAAO,SAAS,cAAc,KAAK;EAQzC,AAPA,EAAK,YAAY,gCACjBA,EAAK,aAAa,eAAe,MAAM,GAEvC,KAAK,SAAS,CAAClD,GAAOkD,CAAI,GAC1BF,EAAU,QAAQ,GAAG,KAAK,MAAM,GAEhC,KAAK,aAAa,KAAK,iBAAiB,GACxC,KAAK,MAAM;CACf;CAGA,OAAO,GAAwC;EAC3C,IAAMG,IAAO,KAAK,KAAK,MAAMT,CAAO,GAC9BU,IAAc,KAAK,KAAK,aAAaD,CAAI;EAK/C,AAJA,KAAK,OAAOA,GACRC,MACA,KAAK,aAAa,KAAK,iBAAiB,IAE5C,KAAK,MAAM;CACf;CAGA,UAAgB;EACZ,IAAMC,IAAO,KAAK;EAClB,IAAKA,GAEL;QAAK,IAAMC,KAAS,KAAK,QAAQ,EAAM,OAAO;GAG9C,AAFA,KAAK,SAAS,CAAC,GAEfD,EAAK,UAAU,OACXN,EAAiB,YACjBA,EAAiB,gBACjBA,EAAiB,UACrB;GACA,KAAK,IAAMQ,KAAQ,OAAO,KAAK,KAAK,KAAK,QAAQ,EAAE,CAAC,GAChD,EAAK,MAAM,eAAeA,CAAI;GAIlC,AADA,KAAK,OAAO,MACZ,KAAK,aAAa;EAb4B;CAclD;CAGA,IAAI,UAA8B;EAC9B,OAAO,KAAK;CAChB;CAEA,QAAsB;EAClB,IAAMF,IAAO,KAAK;EAClB,IAAI,CAACA,GAAM;EAOX,AALAA,EAAK,UAAU,IAAIN,EAAiB,UAAU,GAC9CM,EAAK,UAAU,OACXN,EAAiB,gBACjB,KAAK,KAAK,QAAQ,YACtB,GACAM,EAAK,UAAU,OACXN,EAAiB,YACjB,KAAK,KAAK,QAAQ,WACtB;EAEA,IAAMS,IAAgC,KAAK,KAAK,QAC5C,KAAK,UACT;EACA,KAAK,IAAM,CAACD,GAAM5F,MAAU,OAAO,QAAQ6F,CAAI,GAC3C,EAAK,MAAM,YAAYD,GAAM5F,CAAK;CAE1C;CAEA,mBAAmC;EAC/B,OAAO,IAAI0D,EAAW,KAAK,KAAK,YAAY,CAAC,CAAC,CAAC,UAAU;CAC7D;AACJ"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import "./DitherBackground.css";
|
|
2
|
+
import type { DitherBackgroundOptions } from "./DitherBackgroundView";
|
|
3
|
+
/**
|
|
4
|
+
* Framework-agnostic layered background: a scrolling Bayer-dithered noise
|
|
5
|
+
* texture, a static grid and an optional ambient glow. Plain DOM — mount it
|
|
6
|
+
* on any element from any framework (or none).
|
|
7
|
+
*
|
|
8
|
+
* `mount()` decorates the container: it prepends the hidden layer elements
|
|
9
|
+
* and applies the base class plus the CSS custom properties. Anything else
|
|
10
|
+
* in the container renders above the layers, so frameworks keep rendering
|
|
11
|
+
* their children normally. Options map 1:1 to the CSS custom properties and
|
|
12
|
+
* can be changed at any time via `update()`; the dither bitmap is only
|
|
13
|
+
* regenerated when one of the tile inputs changes.
|
|
14
|
+
*
|
|
15
|
+
* The tile is generated on a canvas, so `mount()` requires a browser DOM.
|
|
16
|
+
*/
|
|
17
|
+
export declare class DitherBackground {
|
|
18
|
+
static readonly ROOT_CLASS = "dither-grid-background";
|
|
19
|
+
static readonly ANIMATED_CLASS = "dither-grid-background--animated";
|
|
20
|
+
static readonly GLOW_CLASS = "dither-grid-background--glow";
|
|
21
|
+
private view;
|
|
22
|
+
private noiseImage;
|
|
23
|
+
private root;
|
|
24
|
+
private layers;
|
|
25
|
+
constructor(options?: DitherBackgroundOptions);
|
|
26
|
+
/** Decorates `container` with the background layers. Idempotent per instance. */
|
|
27
|
+
mount(container: HTMLElement): void;
|
|
28
|
+
/** Merges new options; regenerates the tile only when tile inputs change. */
|
|
29
|
+
update(partial: DitherBackgroundOptions): void;
|
|
30
|
+
/** Removes the layers and reverts the container to its unmounted state. */
|
|
31
|
+
destroy(): void;
|
|
32
|
+
/** The decorated container element, or `null` before mount / after destroy. */
|
|
33
|
+
get element(): HTMLElement | null;
|
|
34
|
+
private apply;
|
|
35
|
+
private createNoiseImage;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=DitherBackground.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DitherBackground.d.ts","sourceRoot":"","sources":["../src/DitherBackground.ts"],"names":[],"mappings":"AAAA,OAAO,wBAAwB,CAAC;AAEhC,OAAO,KAAK,EAER,uBAAuB,EAC1B,MAAM,wBAAwB,CAAC;AAGhC;;;;;;;;;;;;;GAaG;AACH,qBAAa,gBAAgB;IACzB,MAAM,CAAC,QAAQ,CAAC,UAAU,4BAA4B;IACtD,MAAM,CAAC,QAAQ,CAAC,cAAc,sCAAsC;IACpE,MAAM,CAAC,QAAQ,CAAC,UAAU,kCAAkC;IAE5D,OAAO,CAAC,IAAI,CAAuB;IACnC,OAAO,CAAC,UAAU,CAAM;IACxB,OAAO,CAAC,IAAI,CAA4B;IACxC,OAAO,CAAC,MAAM,CAAqB;IAEnC,YAAY,OAAO,GAAE,uBAA4B,EAEhD;IAED,iFAAiF;IACjF,KAAK,CAAC,SAAS,EAAE,WAAW,GAAG,IAAI,CAsBlC;IAED,6EAA6E;IAC7E,MAAM,CAAC,OAAO,EAAE,uBAAuB,GAAG,IAAI,CAQ7C;IAED,2EAA2E;IAC3E,OAAO,IAAI,IAAI,CAkBd;IAED,+EAA+E;IAC/E,IAAI,OAAO,IAAI,WAAW,GAAG,IAAI,CAEhC;IAED,OAAO,CAAC,KAAK;IAsBb,OAAO,CAAC,gBAAgB;CAG3B"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { DitherTileOptions } from "./DitherTile";
|
|
2
|
+
/**
|
|
3
|
+
* Every visual knob of a dither background. All fields are optional and fall
|
|
4
|
+
* back to `DitherBackgroundView.DEFAULTS`. Values are applied as CSS custom
|
|
5
|
+
* properties, so they can be animated at runtime without rebuilding the
|
|
6
|
+
* background; only `DitherBackgroundView.TILE_INPUTS` regenerate the dither
|
|
7
|
+
* bitmap itself.
|
|
8
|
+
*/
|
|
9
|
+
export type DitherBackgroundOptions = {
|
|
10
|
+
backgroundColor?: string;
|
|
11
|
+
gridColor?: string;
|
|
12
|
+
gridSize?: number;
|
|
13
|
+
gridOpacity?: number;
|
|
14
|
+
noiseColor?: string;
|
|
15
|
+
noiseOpacity?: number;
|
|
16
|
+
noiseSize?: number;
|
|
17
|
+
noiseTileCells?: number;
|
|
18
|
+
noiseDensity?: number;
|
|
19
|
+
noiseContrast?: number;
|
|
20
|
+
noiseScale?: number;
|
|
21
|
+
noiseOctaves?: number;
|
|
22
|
+
noiseDetailScale?: number;
|
|
23
|
+
noiseDetailOctaves?: number;
|
|
24
|
+
noiseDetailStrength?: number;
|
|
25
|
+
noiseClampLow?: number;
|
|
26
|
+
noiseClampHigh?: number;
|
|
27
|
+
noiseSeed?: number;
|
|
28
|
+
noiseMaxFill?: number;
|
|
29
|
+
noiseJitter?: number;
|
|
30
|
+
noiseSpeed?: number;
|
|
31
|
+
noiseAngle?: number;
|
|
32
|
+
noiseTravel?: number;
|
|
33
|
+
fadeStart?: number;
|
|
34
|
+
fadeEnd?: number;
|
|
35
|
+
animateNoise?: boolean;
|
|
36
|
+
animateGlow?: boolean;
|
|
37
|
+
};
|
|
38
|
+
export type DitherBackgroundCssVars = Record<`--${string}`, string>;
|
|
39
|
+
/**
|
|
40
|
+
* Resolves `DitherBackgroundOptions` against the defaults and computes the
|
|
41
|
+
* derived values (tile geometry, scroll travel, CSS custom properties). Used
|
|
42
|
+
* internally by `DitherBackground`; framework wrappers can use it standalone.
|
|
43
|
+
*/
|
|
44
|
+
export declare class DitherBackgroundView {
|
|
45
|
+
static readonly DEFAULTS: Required<DitherBackgroundOptions>;
|
|
46
|
+
/** Options that affect the generated tile; all others only set CSS variables. */
|
|
47
|
+
static readonly TILE_INPUTS: ReadonlyArray<keyof DitherBackgroundOptions>;
|
|
48
|
+
readonly options: Required<DitherBackgroundOptions>;
|
|
49
|
+
constructor(options?: DitherBackgroundOptions);
|
|
50
|
+
/** Returns a new view with `partial` merged in; `undefined` values are ignored. */
|
|
51
|
+
merge(partial: DitherBackgroundOptions): DitherBackgroundView;
|
|
52
|
+
/** True when the two views would produce different dither tiles. */
|
|
53
|
+
rebuildsTile(other: DitherBackgroundView): boolean;
|
|
54
|
+
tileOptions(): DitherTileOptions;
|
|
55
|
+
get tileSize(): number;
|
|
56
|
+
/** Horizontal scroll distance in px, snapped to whole tiles (seamless loop). */
|
|
57
|
+
get travelX(): number;
|
|
58
|
+
/** Vertical scroll distance in px, snapped to whole tiles (seamless loop). */
|
|
59
|
+
get travelY(): number;
|
|
60
|
+
/** Oversizes the texture wrapper so the scroll never exposes its edges. */
|
|
61
|
+
get overscan(): number;
|
|
62
|
+
cssVars(noiseImage: string): DitherBackgroundCssVars;
|
|
63
|
+
private static defined;
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=DitherBackgroundView.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"DitherBackgroundView.d.ts","sourceRoot":"","sources":["../src/DitherBackgroundView.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAGtD;;;;;;GAMG;AACH,MAAM,MAAM,uBAAuB,GAAG;IAClC,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;CACzB,CAAC;AAEF,MAAM,MAAM,uBAAuB,GAAG,MAAM,CAAC,KAAK,MAAM,EAAE,EAAE,MAAM,CAAC,CAAC;AAEpE;;;;GAIG;AACH,qBAAa,oBAAoB;IAC7B,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAiCzD;IAEF,iFAAiF;IACjF,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC,MAAM,uBAAuB,CAAC,CAgBnE;IAEN,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,uBAAuB,CAAC,CAAC;IAEpD,YAAY,OAAO,GAAE,uBAA4B,EAKhD;IAED,mFAAmF;IACnF,KAAK,CAAC,OAAO,EAAE,uBAAuB,GAAG,oBAAoB,CAK5D;IAED,oEAAoE;IACpE,YAAY,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAIjD;IAED,WAAW,IAAI,iBAAiB,CAkB/B;IAED,IAAI,QAAQ,IAAI,MAAM,CAErB;IAED,gFAAgF;IAChF,IAAI,OAAO,IAAI,MAAM,CAMpB;IAED,8EAA8E;IAC9E,IAAI,OAAO,IAAI,MAAM,CAMpB;IAED,2EAA2E;IAC3E,IAAI,QAAQ,IAAI,MAAM,CAKrB;IAED,OAAO,CAAC,UAAU,EAAE,MAAM,GAAG,uBAAuB,CAqBnD;IAED,OAAO,CAAC,MAAM,CAAC,OAAO;CAQzB"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export
|
|
2
|
-
export {
|
|
1
|
+
export { DitherBackground } from "./DitherBackground";
|
|
2
|
+
export type { DitherBackgroundCssVars, DitherBackgroundOptions, } from "./DitherBackgroundView";
|
|
3
|
+
export { DitherBackgroundView } from "./DitherBackgroundView";
|
|
3
4
|
export type { DitherTileOptions } from "./DitherTile";
|
|
4
5
|
export { DitherTile } from "./DitherTile";
|
|
5
6
|
export { FractalBrownianNoise } from "./FractalBrownianNoise";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,YAAY,EACR,uBAAuB,EACvB,uBAAuB,GAC1B,MAAM,wBAAwB,CAAC;AAChC,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
CHANGED
|
@@ -1,225 +1 @@
|
|
|
1
|
-
|
|
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
|
|
1
|
+
export { t as DitherBackground, n as DitherBackgroundView, r as DitherTile, a as FractalBrownianNoise, s as MathUtils, o as PerlinNoise, i as RgbColor } from "./DitherBackground-CrFqrwp8.js";
|
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { HTMLAttributes, ReactNode } from "react";
|
|
2
|
+
import { Component } from "react";
|
|
3
|
+
import type { DitherBackgroundOptions } from "./DitherBackgroundView";
|
|
4
|
+
export interface DitherGridBackgroundProps extends Omit<HTMLAttributes<HTMLDivElement>, "color">, DitherBackgroundOptions {
|
|
5
|
+
children?: ReactNode;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* React wrapper around the framework-agnostic `DitherBackground`. Renders a
|
|
9
|
+
* decorated div with the layered background and its children on top; every
|
|
10
|
+
* visual prop maps 1:1 to `DitherBackgroundOptions` and is forwarded as CSS
|
|
11
|
+
* custom properties, so it can be animated at runtime without regenerating
|
|
12
|
+
* the texture. The tile is built on mount (client-side), so this component is
|
|
13
|
+
* client-only - in Next.js the `"use client"` entry makes it App Router safe.
|
|
14
|
+
*/
|
|
15
|
+
export declare class DitherGridBackground extends Component<DitherGridBackgroundProps> {
|
|
16
|
+
private readonly ref;
|
|
17
|
+
private background?;
|
|
18
|
+
componentDidMount(): void;
|
|
19
|
+
componentDidUpdate(prev: DitherGridBackgroundProps): void;
|
|
20
|
+
componentWillUnmount(): void;
|
|
21
|
+
private options;
|
|
22
|
+
render(): import("react").JSX.Element;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=react.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvD,OAAO,EAAE,SAAS,EAAa,MAAM,OAAO,CAAC;AAE7C,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AAGtE,MAAM,WAAW,yBACb,SAAQ,IAAI,CAAC,cAAc,CAAC,cAAc,CAAC,EAAE,OAAO,CAAC,EACjD,uBAAuB;IAC3B,QAAQ,CAAC,EAAE,SAAS,CAAC;CACxB;AAgCD;;;;;;;GAOG;AACH,qBAAa,oBAAqB,SAAQ,SAAS,CAAC,yBAAyB,CAAC;IAC1E,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA+B;IACnD,OAAO,CAAC,UAAU,CAAC,CAAmB;IAE7B,iBAAiB,IAAI,IAAI,CAKjC;IAEQ,kBAAkB,CAAC,IAAI,EAAE,yBAAyB,GAAG,IAAI,CAMjE;IAEQ,oBAAoB,IAAI,IAAI,CAGpC;IAED,OAAO,CAAC,OAAO;IAQN,MAAM,gCA+Bd;CACJ"}
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { n as e, t } from "./DitherBackground-CrFqrwp8.js";
|
|
3
|
+
import { Component as n, createRef as r } from "react";
|
|
4
|
+
import { jsx as i } from "react/jsx-runtime";
|
|
5
|
+
// src/react.tsx
|
|
6
|
+
var a = /* @__PURE__ */ "backgroundColor.gridColor.gridSize.gridOpacity.noiseColor.noiseOpacity.noiseSize.noiseTileCells.noiseDensity.noiseContrast.noiseScale.noiseOctaves.noiseDetailScale.noiseDetailOctaves.noiseDetailStrength.noiseClampLow.noiseClampHigh.noiseSeed.noiseMaxFill.noiseJitter.noiseSpeed.noiseAngle.noiseTravel.fadeStart.fadeEnd.animateNoise.animateGlow".split("."), o = class extends n {
|
|
7
|
+
ref = r();
|
|
8
|
+
background;
|
|
9
|
+
componentDidMount() {
|
|
10
|
+
let e = this.ref.current;
|
|
11
|
+
e && (this.background = new t(this.options(this.props)), this.background.mount(e));
|
|
12
|
+
}
|
|
13
|
+
componentDidUpdate(t) {
|
|
14
|
+
t !== this.props && this.background?.update(new e(this.options(this.props)).options);
|
|
15
|
+
}
|
|
16
|
+
componentWillUnmount() {
|
|
17
|
+
this.background?.destroy(), this.background = void 0;
|
|
18
|
+
}
|
|
19
|
+
options(e) {
|
|
20
|
+
let t = {};
|
|
21
|
+
for (let n of a) t[n] = e[n];
|
|
22
|
+
return t;
|
|
23
|
+
}
|
|
24
|
+
render() {
|
|
25
|
+
let { children: e, className: t = "", animateNoise: n, animateGlow: r, ...o } = this.props, s = { ...o };
|
|
26
|
+
for (let e of a) delete s[e];
|
|
27
|
+
return /* @__PURE__ */ i("div", {
|
|
28
|
+
ref: this.ref,
|
|
29
|
+
className: [
|
|
30
|
+
"dither-grid-background",
|
|
31
|
+
n !== !1 && "dither-grid-background--animated",
|
|
32
|
+
r !== !1 && "dither-grid-background--glow",
|
|
33
|
+
t
|
|
34
|
+
].filter(Boolean).join(" "),
|
|
35
|
+
...s,
|
|
36
|
+
children: /* @__PURE__ */ i("div", {
|
|
37
|
+
className: "dither-grid-background__content",
|
|
38
|
+
children: e
|
|
39
|
+
})
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
export { o as DitherGridBackground };
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=react.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react.js","names":["DitherBackgroundView","OPTION_KEYS","DitherGridBackground","Component","createRef","container","DitherBackground","prev","options","key","props","rest","domProps","animateNoise","animateGlow","className"],"sources":["../src/react.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactNode } from \"react\";\nimport { Component, createRef } from \"react\";\nimport { DitherBackground } from \"./DitherBackground\";\nimport type { DitherBackgroundOptions } from \"./DitherBackgroundView\";\nimport { DitherBackgroundView } from \"./DitherBackgroundView\";\n\nexport interface DitherGridBackgroundProps\n extends Omit<HTMLAttributes<HTMLDivElement>, \"color\">,\n DitherBackgroundOptions {\n children?: ReactNode;\n}\n\nconst OPTION_KEYS: ReadonlyArray<keyof DitherBackgroundOptions> = [\n \"backgroundColor\",\n \"gridColor\",\n \"gridSize\",\n \"gridOpacity\",\n \"noiseColor\",\n \"noiseOpacity\",\n \"noiseSize\",\n \"noiseTileCells\",\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 \"noiseSpeed\",\n \"noiseAngle\",\n \"noiseTravel\",\n \"fadeStart\",\n \"fadeEnd\",\n \"animateNoise\",\n \"animateGlow\",\n];\n\n/**\n * React wrapper around the framework-agnostic `DitherBackground`. Renders a\n * decorated div with the layered background and its children on top; every\n * visual prop maps 1:1 to `DitherBackgroundOptions` and is forwarded as CSS\n * custom properties, so it can be animated at runtime without regenerating\n * the texture. The tile is built on mount (client-side), so this component is\n * client-only - in Next.js the `\"use client\"` entry makes it App Router safe.\n */\nexport class DitherGridBackground extends Component<DitherGridBackgroundProps> {\n private readonly ref = createRef<HTMLDivElement>();\n private background?: DitherBackground;\n\n override componentDidMount(): void {\n const container = this.ref.current;\n if (!container) return;\n this.background = new DitherBackground(this.options(this.props));\n this.background.mount(container);\n }\n\n override componentDidUpdate(prev: DitherGridBackgroundProps): void {\n if (prev !== this.props) {\n this.background?.update(\n new DitherBackgroundView(this.options(this.props)).options,\n );\n }\n }\n\n override componentWillUnmount(): void {\n this.background?.destroy();\n this.background = undefined;\n }\n\n private options(props: DitherGridBackgroundProps): DitherBackgroundOptions {\n const options: Record<string, unknown> = {};\n for (const key of OPTION_KEYS) {\n options[key] = props[key];\n }\n return options as DitherBackgroundOptions;\n }\n\n override render() {\n const {\n children,\n className = \"\",\n animateNoise,\n animateGlow,\n ...rest\n } = this.props;\n\n const domProps: Record<string, unknown> = { ...rest };\n for (const key of OPTION_KEYS) delete domProps[key];\n\n return (\n <div\n ref={this.ref}\n className={[\n \"dither-grid-background\",\n animateNoise !== false\n && \"dither-grid-background--animated\",\n animateGlow !== false && \"dither-grid-background--glow\",\n className,\n ]\n .filter(Boolean)\n .join(\" \")}\n {...(domProps as HTMLAttributes<HTMLDivElement>)}\n >\n <div className=\"dither-grid-background__content\">\n {children}\n </div>\n </div>\n );\n }\n}\n"],"mappings":";;;;;AAYA,IAAMC,IAA4D,mXA4BlE,GAUaC,IAAb,cAA0CC,EAAqC;CAC3E,MAAuBC,EAA0B;CACjD;CAEA,oBAAmC;EAC/B,IAAMC,IAAY,KAAK,IAAI;EAC3B,AAAKA,MACL,KAAK,aAAa,IAAIC,EAAiB,KAAK,QAAQ,KAAK,KAAK,CAAC,GAC/D,KAAK,WAAW,MAAMD,CAAS;CACnC;CAEA,mBAA4B,GAAuC;EAC/D,AAAIE,MAAS,KAAK,SACd,KAAK,YAAY,OACb,IAAIP,EAAqB,KAAK,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,OACvD;CAER;CAEA,uBAAsC;EAElC,AADA,KAAK,YAAY,QAAQ,GACzB,KAAK,aAAa,KAAA;CACtB;CAEA,QAAgB,GAA2D;EACvE,IAAMQ,IAAmC,CAAC;EAC1C,KAAK,IAAMC,KAAOR,GACd,EAAQQ,KAAOC,EAAMD;EAEzB,OAAOD;CACX;CAEA,SAAkB;EACd,IAAM,EACF,aACA,eAAY,IACZ,iBACA,gBACA,GAAGG,MACH,KAAK,OAEHC,IAAoC,EAAE,GAAGD,EAAK;EACpD,KAAK,IAAMF,KAAOR,GAAa,OAAOW,EAASH;EAE/C,OACI,kBAAC,OAAD;GACI,KAAK,KAAK;GACV,WAAW;IACP;IACAI,MAAiB,MACV;IACPC,MAAgB,MAAS;IACzBC;GACJ,CAAC,CACI,OAAO,OAAO,CAAC,CACf,KAAK,GAAG;GACb,GAAKH;GAEL,UAAA,kBAAC,OAAD;IAAK,WAAU;IACV;GACA,CAAA;EACJ,CAAA;CAEb;AACJ"}
|
package/dist/style.css
CHANGED
|
@@ -1,2 +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
|
|
1
|
+
.dither-grid-background{isolation:isolate;background:var(--background-color);min-height:100%;position:relative;overflow:hidden}.dither-grid-background__noise{z-index:-1;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:-1;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:-1;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
2
|
/*$vite$:1*/
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-of-chaos/dither",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "Framework-agnostic animated bitmap-dithered gradient backgrounds, with a tileable Perlin/fBm noise engine for generating custom dither tiles.",
|
|
5
5
|
"license": "GPL-3.0-only",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"sideEffects": [
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
"types": "./dist/index.d.ts",
|
|
16
16
|
"import": "./dist/index.js"
|
|
17
17
|
},
|
|
18
|
+
"./react": {
|
|
19
|
+
"types": "./dist/react.d.ts",
|
|
20
|
+
"import": "./dist/react.js"
|
|
21
|
+
},
|
|
18
22
|
"./style.css": "./dist/style.css"
|
|
19
23
|
},
|
|
20
24
|
"types": "./dist/index.d.ts",
|
|
@@ -52,12 +56,15 @@
|
|
|
52
56
|
"peerDependencies": {
|
|
53
57
|
"react": ">=18"
|
|
54
58
|
},
|
|
59
|
+
"peerDependenciesMeta": {
|
|
60
|
+
"react": {
|
|
61
|
+
"optional": true
|
|
62
|
+
}
|
|
63
|
+
},
|
|
55
64
|
"devDependencies": {
|
|
56
65
|
"@biomejs/biome": "^2.6.5",
|
|
57
|
-
"@testing-library/dom": "^10.4.2",
|
|
58
66
|
"@testing-library/react": "^16.3.3",
|
|
59
67
|
"@types/react": "^19.3.0",
|
|
60
|
-
"@vitejs/plugin-react": "^6.2.1",
|
|
61
68
|
"happy-dom": "^21.0.4",
|
|
62
69
|
"react": "^19.2.0",
|
|
63
70
|
"react-dom": "^19.2.0",
|
|
@@ -1,53 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
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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"}
|