@intility/bifrost-experimental 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/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
# @intility/bifrost-experimental
|
|
2
2
|
|
|
3
|
-
## 0.
|
|
3
|
+
## 0.3.0
|
|
4
4
|
|
|
5
5
|
### Minor Changes
|
|
6
6
|
|
|
7
|
-
-
|
|
7
|
+
- 7ed86c6: `TwinkleBackground` now covers the top 35% of the viewport height instead of the top 40% of its container, so it looks the same on every front page, however tall the page is, but at least 240px. If the container is shorter than that, the dots cover the whole container. The first row of dots now always sits 8px from the top, where it before moved between 8px and 16px depending on the height. `fill` is unchanged. You can remove any CSS that overrides the height of the canvas.
|
|
8
|
+
|
|
9
|
+
## 0.2.0 - 2026-10-28
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- d272c33: `TwinkleBackground` has a new `fill` prop for using it inside a `Box` or a card. The dots then cover the whole container, evenly, with no fade. The dot grid is now also centered with a margin to the edges, so the edge of the container no longer cuts a dot in half.
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- 75f2bf7: `Backdrop` no longer uses `filter: blur()` for its soft edges. Safari on some Macs drew a dark or light halo around the shade when switching between light and dark mode. The edges now fade with a gradient mask instead.
|
|
18
|
+
|
|
19
|
+
## 0.1.0 - 2026-10-27
|
|
20
|
+
|
|
21
|
+
### First release
|
|
22
|
+
|
|
23
|
+
- 2b88573: New package for experimental, AI-generated components. Adds `TwinkleBackground`, an animated dot-grid background in the Bifrost theme color, `Backdrop`, a soft shaded area that keeps text readable on a busy background, and `FrontpageHeader`, a logo, title and subtitle for the front page of a Platform Manager module. #998
|
|
@@ -17,14 +17,48 @@ span.bfx-backdrop {
|
|
|
17
17
|
z-index: -1;
|
|
18
18
|
pointer-events: none;
|
|
19
19
|
|
|
20
|
-
/*
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
/* How far the shade reaches past the content, which is also the width of its
|
|
21
|
+
fade. The horizontal reach is no wider than the page padding, so the shade
|
|
22
|
+
can't reach past the screen edge on small screens. */
|
|
23
|
+
--bfx-backdrop-x: min(var(--bfs32), var(--bfl-page-padding));
|
|
24
|
+
--bfx-backdrop-y: var(--bfs24);
|
|
25
25
|
|
|
26
|
-
/*
|
|
27
|
-
|
|
26
|
+
/* Larger than the content without changing the layout */
|
|
27
|
+
inset: calc(-1 * var(--bfx-backdrop-y)) calc(-1 * var(--bfx-backdrop-x));
|
|
28
|
+
opacity: 0.7;
|
|
29
|
+
|
|
30
|
+
/* Soft edges, so it doesn't look like a box. Each edge eases out over the
|
|
31
|
+
reach, like the tail of a blur, and the two masks are multiplied so the
|
|
32
|
+
corners fade too. A mask rather than filter: blur(), which Safari renders
|
|
33
|
+
with a dark or light halo on some Macs. */
|
|
34
|
+
mask-image:
|
|
35
|
+
linear-gradient(
|
|
36
|
+
to bottom,
|
|
37
|
+
transparent,
|
|
38
|
+
rgba(0, 0, 0, 0.1) calc(var(--bfx-backdrop-y) * 0.25),
|
|
39
|
+
rgba(0, 0, 0, 0.5) calc(var(--bfx-backdrop-y) * 0.5),
|
|
40
|
+
rgba(0, 0, 0, 0.9) calc(var(--bfx-backdrop-y) * 0.75),
|
|
41
|
+
#000 var(--bfx-backdrop-y),
|
|
42
|
+
#000 calc(100% - var(--bfx-backdrop-y)),
|
|
43
|
+
rgba(0, 0, 0, 0.9) calc(100% - var(--bfx-backdrop-y) * 0.75),
|
|
44
|
+
rgba(0, 0, 0, 0.5) calc(100% - var(--bfx-backdrop-y) * 0.5),
|
|
45
|
+
rgba(0, 0, 0, 0.1) calc(100% - var(--bfx-backdrop-y) * 0.25),
|
|
46
|
+
transparent
|
|
47
|
+
),
|
|
48
|
+
linear-gradient(
|
|
49
|
+
to right,
|
|
50
|
+
transparent,
|
|
51
|
+
rgba(0, 0, 0, 0.1) calc(var(--bfx-backdrop-x) * 0.25),
|
|
52
|
+
rgba(0, 0, 0, 0.5) calc(var(--bfx-backdrop-x) * 0.5),
|
|
53
|
+
rgba(0, 0, 0, 0.9) calc(var(--bfx-backdrop-x) * 0.75),
|
|
54
|
+
#000 var(--bfx-backdrop-x),
|
|
55
|
+
#000 calc(100% - var(--bfx-backdrop-x)),
|
|
56
|
+
rgba(0, 0, 0, 0.9) calc(100% - var(--bfx-backdrop-x) * 0.75),
|
|
57
|
+
rgba(0, 0, 0, 0.5) calc(100% - var(--bfx-backdrop-x) * 0.5),
|
|
58
|
+
rgba(0, 0, 0, 0.1) calc(100% - var(--bfx-backdrop-x) * 0.25),
|
|
59
|
+
transparent
|
|
60
|
+
);
|
|
61
|
+
mask-composite: intersect;
|
|
28
62
|
}
|
|
29
63
|
|
|
30
64
|
/* A stacking context, so the title's and subtitle's backdrops sit behind both
|
|
@@ -63,7 +97,13 @@ overlap the title. */
|
|
|
63
97
|
|
|
64
98
|
/* A canvas is a replaced element, so inset alone doesn't stretch it */
|
|
65
99
|
width: 100%;
|
|
66
|
-
|
|
100
|
+
|
|
101
|
+
/* 35% of the viewport, not of the container, so every front page looks the
|
|
102
|
+
same however tall it is. svh and not dvh: the dvh changes while a mobile
|
|
103
|
+
browser's toolbar shows and hides, which would lay the dots out again while
|
|
104
|
+
scrolling. At least 240px, so a short viewport still gets a fade that reads
|
|
105
|
+
as one. 100% keeps it inside a container shorter than that. */
|
|
106
|
+
height: min(max(35svh, 240px), 100%);
|
|
67
107
|
z-index: -1;
|
|
68
108
|
pointer-events: none;
|
|
69
109
|
|
|
@@ -76,6 +116,10 @@ overlap the title. */
|
|
|
76
116
|
);
|
|
77
117
|
}
|
|
78
118
|
|
|
119
|
+
.bfx-twinkle-background-fill {
|
|
120
|
+
height: 100%;
|
|
121
|
+
}
|
|
122
|
+
|
|
79
123
|
/* Red and green aren't themes. The theme colors have a lightness of 46% to
|
|
80
124
|
55% in light mode and 71% to 90% in dark mode, so the lightness is moved
|
|
81
125
|
into that range where alert (55% dark) and success (85% light) are far off. */
|
|
@@ -5,13 +5,23 @@ export type TwinkleBackgroundProps = {
|
|
|
5
5
|
* @default "theme"
|
|
6
6
|
*/
|
|
7
7
|
color?: "theme" | "teal" | "purple" | "pink" | "yellow" | "red" | "green";
|
|
8
|
+
/**
|
|
9
|
+
* Fills the whole container with evenly spread dots, with no fade, for a
|
|
10
|
+
* background inside a card or a `Box`. By default the dots cover the top 35%
|
|
11
|
+
* of the viewport height, at least 240px, or the whole container if that is
|
|
12
|
+
* shorter, and fade out down the page.
|
|
13
|
+
* @default false
|
|
14
|
+
*/
|
|
15
|
+
fill?: boolean;
|
|
8
16
|
};
|
|
9
17
|
/**
|
|
10
18
|
* Animated dot grid in the Bifrost theme color, for the front page of a
|
|
11
19
|
* Platform Manager module. It fades out down the page and reacts to hover and
|
|
12
20
|
* clicks. Render it as the first child of a container with
|
|
13
|
-
* `position: relative` and `isolation: isolate`; it
|
|
14
|
-
*
|
|
21
|
+
* `position: relative` and `isolation: isolate`; it covers the top 35% of the
|
|
22
|
+
* viewport height, at least 240px (the whole container if that is shorter),
|
|
23
|
+
* behind its content, or all of the container with `fill`. No dot is cut off by the edge
|
|
24
|
+
* of the container. Experimental: AI-generated, not reviewed line
|
|
15
25
|
* by line.
|
|
16
26
|
*
|
|
17
27
|
* @see https://bifrost.intility.com/react/experimental/twinkle-background
|
|
@@ -24,5 +34,5 @@ export type TwinkleBackgroundProps = {
|
|
|
24
34
|
* </Backdrop>
|
|
25
35
|
* </div>
|
|
26
36
|
*/
|
|
27
|
-
declare const TwinkleBackground: ({ color }: TwinkleBackgroundProps) => import("react").JSX.Element;
|
|
37
|
+
declare const TwinkleBackground: ({ color, fill, }: TwinkleBackgroundProps) => import("react").JSX.Element;
|
|
28
38
|
export default TwinkleBackground;
|
|
@@ -47,8 +47,10 @@ const within = (rect, x, y) => x >= 0 && x <= rect.width && y >= 0 && y <= rect.
|
|
|
47
47
|
* Animated dot grid in the Bifrost theme color, for the front page of a
|
|
48
48
|
* Platform Manager module. It fades out down the page and reacts to hover and
|
|
49
49
|
* clicks. Render it as the first child of a container with
|
|
50
|
-
* `position: relative` and `isolation: isolate`; it
|
|
51
|
-
*
|
|
50
|
+
* `position: relative` and `isolation: isolate`; it covers the top 35% of the
|
|
51
|
+
* viewport height, at least 240px (the whole container if that is shorter),
|
|
52
|
+
* behind its content, or all of the container with `fill`. No dot is cut off by the edge
|
|
53
|
+
* of the container. Experimental: AI-generated, not reviewed line
|
|
52
54
|
* by line.
|
|
53
55
|
*
|
|
54
56
|
* @see https://bifrost.intility.com/react/experimental/twinkle-background
|
|
@@ -62,18 +64,20 @@ const within = (rect, x, y) => x >= 0 && x <= rect.width && y >= 0 && y <= rect.
|
|
|
62
64
|
* </div>
|
|
63
65
|
*/
|
|
64
66
|
const TwinkleBackground = t0 => {
|
|
65
|
-
const $ = _c(
|
|
67
|
+
const $ = _c(6);
|
|
66
68
|
const {
|
|
67
|
-
color: t1
|
|
69
|
+
color: t1,
|
|
70
|
+
fill: t2
|
|
68
71
|
} = t0;
|
|
69
72
|
const color = t1 === undefined ? "theme" : t1;
|
|
73
|
+
const fill = t2 === undefined ? false : t2;
|
|
70
74
|
const canvasRef = useRef(null);
|
|
71
75
|
const still = useReducedMotion();
|
|
72
76
|
const colorClass = color === "red" || color === "green" ? ` bfx-twinkle-background-${color}` : color === "theme" ? "" : ` bf-theme-${color}`;
|
|
73
|
-
let t2;
|
|
74
77
|
let t3;
|
|
75
|
-
|
|
76
|
-
|
|
78
|
+
let t4;
|
|
79
|
+
if ($[0] !== fill || $[1] !== still) {
|
|
80
|
+
t3 = () => {
|
|
77
81
|
const canvas = canvasRef.current;
|
|
78
82
|
const ctx = canvas?.getContext("2d");
|
|
79
83
|
if (!canvas || !ctx) {
|
|
@@ -93,7 +97,10 @@ const TwinkleBackground = t0 => {
|
|
|
93
97
|
const clicks = [];
|
|
94
98
|
let w = 0;
|
|
95
99
|
let h = 0;
|
|
96
|
-
let cols =
|
|
100
|
+
let cols = 1;
|
|
101
|
+
let offsetX = 0;
|
|
102
|
+
let offsetY = 0;
|
|
103
|
+
let span = 0;
|
|
97
104
|
let rand = new Float32Array(0);
|
|
98
105
|
const seed = Math.random() * 4294967296 | 0;
|
|
99
106
|
const noise = (col, row, k) => {
|
|
@@ -106,12 +113,12 @@ const TwinkleBackground = t0 => {
|
|
|
106
113
|
let last = 0;
|
|
107
114
|
let raf = 0;
|
|
108
115
|
let visible = false;
|
|
109
|
-
let
|
|
116
|
+
let dotColor = "";
|
|
110
117
|
let base = OPACITY.light;
|
|
111
118
|
let styleAt = -Infinity;
|
|
112
119
|
const readStyle = () => {
|
|
113
120
|
const style = getComputedStyle(canvas);
|
|
114
|
-
|
|
121
|
+
dotColor = style.color;
|
|
115
122
|
base = style.colorScheme.includes("dark") ? OPACITY.dark : OPACITY.light;
|
|
116
123
|
};
|
|
117
124
|
const batches = Array.from({
|
|
@@ -122,12 +129,12 @@ const TwinkleBackground = t0 => {
|
|
|
122
129
|
if (forcedColors.matches || h <= 0) {
|
|
123
130
|
return;
|
|
124
131
|
}
|
|
125
|
-
ctx.fillStyle =
|
|
132
|
+
ctx.fillStyle = dotColor;
|
|
126
133
|
for (let i = 0; i < rand.length; i = i + 2, i) {
|
|
127
134
|
const r1 = rand[i];
|
|
128
135
|
const r2 = rand[i + 1];
|
|
129
|
-
const x = i / 2 % cols * SPACING;
|
|
130
|
-
const y = Math.floor(i / 2 / cols) * SPACING;
|
|
136
|
+
const x = offsetX + i / 2 % cols * SPACING;
|
|
137
|
+
const y = offsetY + Math.floor(i / 2 / cols) * SPACING;
|
|
131
138
|
const tw = 0.5 + 0.5 * Math.sin(clock * 0.48 * (1 + r1 * 3) + r2 * TAU);
|
|
132
139
|
let alpha = MIN_ALPHA + (1 - MIN_ALPHA) * tw * tw;
|
|
133
140
|
let size = 1.4 * (1 - 0.3 * r2) * (0.8 + 0.4 * tw);
|
|
@@ -144,7 +151,7 @@ const TwinkleBackground = t0 => {
|
|
|
144
151
|
alpha = alpha + ring * 2.6;
|
|
145
152
|
size = size + ring * 1.4;
|
|
146
153
|
}
|
|
147
|
-
const fade = y /
|
|
154
|
+
const fade = fill || span <= 0 ? 0 : (y - offsetY) / span;
|
|
148
155
|
alpha = alpha * (1 - fade * fade) * base;
|
|
149
156
|
const step = Math.round(Math.min(1, alpha) * ALPHA_STEPS);
|
|
150
157
|
if (step < 1) {
|
|
@@ -221,9 +228,13 @@ const TwinkleBackground = t0 => {
|
|
|
221
228
|
canvas.width = Math.round(w * dpr);
|
|
222
229
|
canvas.height = Math.round(h * dpr);
|
|
223
230
|
ctx.setTransform(w ? canvas.width / w : 1, 0, 0, h ? canvas.height / h : 1, 0, 0);
|
|
224
|
-
cols = Math.
|
|
231
|
+
cols = Math.max(1, Math.floor(w / SPACING));
|
|
232
|
+
const rows = Math.max(1, Math.floor(h / SPACING));
|
|
233
|
+
span = (rows - 1) * SPACING;
|
|
234
|
+
offsetX = (w - (cols - 1) * SPACING) / 2;
|
|
235
|
+
offsetY = fill ? (h - span) / 2 : SPACING / 2;
|
|
225
236
|
rand = Float32Array.from({
|
|
226
|
-
length: cols *
|
|
237
|
+
length: cols * rows * 2
|
|
227
238
|
}, (_, i_1) => noise((i_1 >> 1) % cols, Math.floor(i_1 / 2 / cols), i_1 & 1));
|
|
228
239
|
redraw();
|
|
229
240
|
};
|
|
@@ -299,8 +310,8 @@ const TwinkleBackground = t0 => {
|
|
|
299
310
|
});
|
|
300
311
|
}
|
|
301
312
|
}
|
|
302
|
-
const visibility = new IntersectionObserver(
|
|
303
|
-
const [entry] =
|
|
313
|
+
const visibility = new IntersectionObserver(t5 => {
|
|
314
|
+
const [entry] = t5;
|
|
304
315
|
visible = entry.isIntersecting;
|
|
305
316
|
if (visible) {
|
|
306
317
|
play();
|
|
@@ -341,29 +352,30 @@ const TwinkleBackground = t0 => {
|
|
|
341
352
|
window.removeEventListener("blur", onLeave);
|
|
342
353
|
};
|
|
343
354
|
};
|
|
344
|
-
|
|
345
|
-
$[0] =
|
|
346
|
-
$[1] =
|
|
355
|
+
t4 = [still, fill];
|
|
356
|
+
$[0] = fill;
|
|
357
|
+
$[1] = still;
|
|
347
358
|
$[2] = t3;
|
|
359
|
+
$[3] = t4;
|
|
348
360
|
} else {
|
|
349
|
-
t2 = $[1];
|
|
350
361
|
t3 = $[2];
|
|
362
|
+
t4 = $[3];
|
|
351
363
|
}
|
|
352
|
-
useEffect(
|
|
353
|
-
const
|
|
354
|
-
let
|
|
355
|
-
if ($[
|
|
356
|
-
|
|
364
|
+
useEffect(t3, t4);
|
|
365
|
+
const t5 = `bfx-twinkle-background${fill ? " bfx-twinkle-background-fill" : ""}${colorClass}`;
|
|
366
|
+
let t6;
|
|
367
|
+
if ($[4] !== t5) {
|
|
368
|
+
t6 = /*#__PURE__*/_jsx("canvas", {
|
|
357
369
|
ref: canvasRef,
|
|
358
370
|
"aria-hidden": true,
|
|
359
|
-
className:
|
|
371
|
+
className: t5
|
|
360
372
|
});
|
|
361
|
-
$[3] = t4;
|
|
362
373
|
$[4] = t5;
|
|
374
|
+
$[5] = t6;
|
|
363
375
|
} else {
|
|
364
|
-
|
|
376
|
+
t6 = $[5];
|
|
365
377
|
}
|
|
366
|
-
return
|
|
378
|
+
return t6;
|
|
367
379
|
};
|
|
368
380
|
export default TwinkleBackground;
|
|
369
381
|
function _temp() {
|
package/package.json
CHANGED