@intility/bifrost-experimental 0.1.0 → 0.2.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,17 @@
1
1
  # @intility/bifrost-experimental
2
2
 
3
- ## 0.1.0
3
+ ## 0.2.0
4
4
 
5
5
  ### Minor Changes
6
6
 
7
- - 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.
7
+ - 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.
8
+
9
+ ### Patch Changes
10
+
11
+ - 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.
12
+
13
+ ## 0.1.0 - 2026-10-27
14
+
15
+ ### First release
16
+
17
+ - 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
- /* Larger than the content without changing the layout, but no wider than the
21
- page padding, so it can't reach past the screen edge on small screens */
22
- inset: calc(-1 * var(--bfs12))
23
- calc(-1 * min(var(--bfs24), var(--bfl-page-padding)));
24
- opacity: 0.85;
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
- /* Soft edges, so it doesn't look like a box */
27
- filter: blur(10px);
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
@@ -76,6 +110,10 @@ overlap the title. */
76
110
  );
77
111
  }
78
112
 
113
+ .bfx-twinkle-background-fill {
114
+ height: 100%;
115
+ }
116
+
79
117
  /* Red and green aren't themes. The theme colors have a lightness of 46% to
80
118
  55% in light mode and 71% to 90% in dark mode, so the lightness is moved
81
119
  into that range where alert (55% dark) and success (85% light) are far off. */
@@ -5,13 +5,21 @@ 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 40%
11
+ * of the container and fade out down the page.
12
+ * @default false
13
+ */
14
+ fill?: boolean;
8
15
  };
9
16
  /**
10
17
  * Animated dot grid in the Bifrost theme color, for the front page of a
11
18
  * Platform Manager module. It fades out down the page and reacts to hover and
12
19
  * clicks. Render it as the first child of a container with
13
20
  * `position: relative` and `isolation: isolate`; it fills the top 40% of that
14
- * container, behind its content. Experimental: AI-generated, not reviewed line
21
+ * container, behind its content, or all of it with `fill`. No dot is cut off
22
+ * by the edge of the container. Experimental: AI-generated, not reviewed line
15
23
  * by line.
16
24
  *
17
25
  * @see https://bifrost.intility.com/react/experimental/twinkle-background
@@ -24,5 +32,5 @@ export type TwinkleBackgroundProps = {
24
32
  * </Backdrop>
25
33
  * </div>
26
34
  */
27
- declare const TwinkleBackground: ({ color }: TwinkleBackgroundProps) => import("react").JSX.Element;
35
+ declare const TwinkleBackground: ({ color, fill, }: TwinkleBackgroundProps) => import("react").JSX.Element;
28
36
  export default TwinkleBackground;
@@ -48,7 +48,8 @@ const within = (rect, x, y) => x >= 0 && x <= rect.width && y >= 0 && y <= rect.
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
50
  * `position: relative` and `isolation: isolate`; it fills the top 40% of that
51
- * container, behind its content. Experimental: AI-generated, not reviewed line
51
+ * container, behind its content, or all of it with `fill`. No dot is cut off
52
+ * by the edge of the container. Experimental: AI-generated, not reviewed line
52
53
  * by line.
53
54
  *
54
55
  * @see https://bifrost.intility.com/react/experimental/twinkle-background
@@ -62,18 +63,20 @@ const within = (rect, x, y) => x >= 0 && x <= rect.width && y >= 0 && y <= rect.
62
63
  * </div>
63
64
  */
64
65
  const TwinkleBackground = t0 => {
65
- const $ = _c(5);
66
+ const $ = _c(6);
66
67
  const {
67
- color: t1
68
+ color: t1,
69
+ fill: t2
68
70
  } = t0;
69
71
  const color = t1 === undefined ? "theme" : t1;
72
+ const fill = t2 === undefined ? false : t2;
70
73
  const canvasRef = useRef(null);
71
74
  const still = useReducedMotion();
72
75
  const colorClass = color === "red" || color === "green" ? ` bfx-twinkle-background-${color}` : color === "theme" ? "" : ` bf-theme-${color}`;
73
- let t2;
74
76
  let t3;
75
- if ($[0] !== still) {
76
- t2 = () => {
77
+ let t4;
78
+ if ($[0] !== fill || $[1] !== still) {
79
+ t3 = () => {
77
80
  const canvas = canvasRef.current;
78
81
  const ctx = canvas?.getContext("2d");
79
82
  if (!canvas || !ctx) {
@@ -93,7 +96,10 @@ const TwinkleBackground = t0 => {
93
96
  const clicks = [];
94
97
  let w = 0;
95
98
  let h = 0;
96
- let cols = 0;
99
+ let cols = 1;
100
+ let offsetX = 0;
101
+ let offsetY = 0;
102
+ let span = 0;
97
103
  let rand = new Float32Array(0);
98
104
  const seed = Math.random() * 4294967296 | 0;
99
105
  const noise = (col, row, k) => {
@@ -106,12 +112,12 @@ const TwinkleBackground = t0 => {
106
112
  let last = 0;
107
113
  let raf = 0;
108
114
  let visible = false;
109
- let fill = "";
115
+ let dotColor = "";
110
116
  let base = OPACITY.light;
111
117
  let styleAt = -Infinity;
112
118
  const readStyle = () => {
113
119
  const style = getComputedStyle(canvas);
114
- fill = style.color;
120
+ dotColor = style.color;
115
121
  base = style.colorScheme.includes("dark") ? OPACITY.dark : OPACITY.light;
116
122
  };
117
123
  const batches = Array.from({
@@ -122,12 +128,12 @@ const TwinkleBackground = t0 => {
122
128
  if (forcedColors.matches || h <= 0) {
123
129
  return;
124
130
  }
125
- ctx.fillStyle = fill;
131
+ ctx.fillStyle = dotColor;
126
132
  for (let i = 0; i < rand.length; i = i + 2, i) {
127
133
  const r1 = rand[i];
128
134
  const r2 = rand[i + 1];
129
- const x = i / 2 % cols * SPACING;
130
- const y = Math.floor(i / 2 / cols) * SPACING;
135
+ const x = offsetX + i / 2 % cols * SPACING;
136
+ const y = offsetY + Math.floor(i / 2 / cols) * SPACING;
131
137
  const tw = 0.5 + 0.5 * Math.sin(clock * 0.48 * (1 + r1 * 3) + r2 * TAU);
132
138
  let alpha = MIN_ALPHA + (1 - MIN_ALPHA) * tw * tw;
133
139
  let size = 1.4 * (1 - 0.3 * r2) * (0.8 + 0.4 * tw);
@@ -144,7 +150,7 @@ const TwinkleBackground = t0 => {
144
150
  alpha = alpha + ring * 2.6;
145
151
  size = size + ring * 1.4;
146
152
  }
147
- const fade = y / h;
153
+ const fade = fill || span <= 0 ? 0 : (y - offsetY) / span;
148
154
  alpha = alpha * (1 - fade * fade) * base;
149
155
  const step = Math.round(Math.min(1, alpha) * ALPHA_STEPS);
150
156
  if (step < 1) {
@@ -221,9 +227,13 @@ const TwinkleBackground = t0 => {
221
227
  canvas.width = Math.round(w * dpr);
222
228
  canvas.height = Math.round(h * dpr);
223
229
  ctx.setTransform(w ? canvas.width / w : 1, 0, 0, h ? canvas.height / h : 1, 0, 0);
224
- cols = Math.ceil(w / SPACING) + 1;
230
+ cols = Math.max(1, Math.floor(w / SPACING));
231
+ const rows = Math.max(1, Math.floor(h / SPACING));
232
+ span = (rows - 1) * SPACING;
233
+ offsetX = (w - (cols - 1) * SPACING) / 2;
234
+ offsetY = (h - span) / 2;
225
235
  rand = Float32Array.from({
226
- length: cols * (Math.ceil(h / SPACING) + 1) * 2
236
+ length: cols * rows * 2
227
237
  }, (_, i_1) => noise((i_1 >> 1) % cols, Math.floor(i_1 / 2 / cols), i_1 & 1));
228
238
  redraw();
229
239
  };
@@ -299,8 +309,8 @@ const TwinkleBackground = t0 => {
299
309
  });
300
310
  }
301
311
  }
302
- const visibility = new IntersectionObserver(t4 => {
303
- const [entry] = t4;
312
+ const visibility = new IntersectionObserver(t5 => {
313
+ const [entry] = t5;
304
314
  visible = entry.isIntersecting;
305
315
  if (visible) {
306
316
  play();
@@ -341,29 +351,30 @@ const TwinkleBackground = t0 => {
341
351
  window.removeEventListener("blur", onLeave);
342
352
  };
343
353
  };
344
- t3 = [still];
345
- $[0] = still;
346
- $[1] = t2;
354
+ t4 = [still, fill];
355
+ $[0] = fill;
356
+ $[1] = still;
347
357
  $[2] = t3;
358
+ $[3] = t4;
348
359
  } else {
349
- t2 = $[1];
350
360
  t3 = $[2];
361
+ t4 = $[3];
351
362
  }
352
- useEffect(t2, t3);
353
- const t4 = `bfx-twinkle-background${colorClass}`;
354
- let t5;
355
- if ($[3] !== t4) {
356
- t5 = /*#__PURE__*/_jsx("canvas", {
363
+ useEffect(t3, t4);
364
+ const t5 = `bfx-twinkle-background${fill ? " bfx-twinkle-background-fill" : ""}${colorClass}`;
365
+ let t6;
366
+ if ($[4] !== t5) {
367
+ t6 = /*#__PURE__*/_jsx("canvas", {
357
368
  ref: canvasRef,
358
369
  "aria-hidden": true,
359
- className: t4
370
+ className: t5
360
371
  });
361
- $[3] = t4;
362
372
  $[4] = t5;
373
+ $[5] = t6;
363
374
  } else {
364
- t5 = $[4];
375
+ t6 = $[5];
365
376
  }
366
- return t5;
377
+ return t6;
367
378
  };
368
379
  export default TwinkleBackground;
369
380
  function _temp() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intility/bifrost-experimental",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Experimental, AI-generated React components for Intility's design system, Bifrost.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",