@intility/bifrost-experimental 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @intility/bifrost-experimental
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
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.
package/README.md CHANGED
@@ -1,3 +1,57 @@
1
- # Temporary Holding Version
1
+ # @intility/bifrost-experimental
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Experimental, AI-generated React components for Intility's design system,
4
+ Bifrost. See [Experimental](https://bifrost.intility.com/react/experimental)
5
+ in the style guide.
6
+
7
+ ## What experimental means
8
+
9
+ The components are built with AI and aren't reviewed line by line. They are
10
+ front end only: they depend on nothing but React and Bifrost, and make no
11
+ network calls. Lint rules and tests catch common mistakes, but they aren't a
12
+ guarantee. Use the components in your apps, but expect them to change more
13
+ often than the Bifrost packages.
14
+
15
+ ## Install
16
+
17
+ ```shell
18
+ npm install @intility/bifrost-experimental
19
+ ```
20
+
21
+ It needs `react`, `react-dom` and `@intility/bifrost-react`. Import the CSS
22
+ once, after the Bifrost CSS:
23
+
24
+ ```tsx
25
+ import "@intility/bifrost-react/bifrost-app.css";
26
+ import "@intility/bifrost-experimental/bifrost-experimental.css";
27
+ ```
28
+
29
+ Import each component from its own path:
30
+
31
+ ```tsx
32
+ import TwinkleBackground from "@intility/bifrost-experimental/TwinkleBackground";
33
+ ```
34
+
35
+ ## Components
36
+
37
+ - [`Backdrop`](https://bifrost.intility.com/react/experimental/backdrop):
38
+ a soft shaded area that keeps text readable on a busy background
39
+ - [`FrontpageHeader`](https://bifrost.intility.com/react/experimental/frontpage-header):
40
+ logo, title and subtitle for the front page of a Platform Manager module
41
+ - [`TwinkleBackground`](https://bifrost.intility.com/react/experimental/twinkle-background):
42
+ an animated dot grid in the theme color, for the front page of a Platform
43
+ Manager module
44
+
45
+ ## Contributing
46
+
47
+ See
48
+ [AGENTS.md](https://github.com/intility/bifrost/blob/main/packages/bifrost-experimental/AGENTS.md)
49
+ for the rules and commands, whether you write the component yourself or with
50
+ an AI agent.
51
+
52
+ Members of the `bifrost-experimenters` team in the `intility` GitHub
53
+ organization can approve changes to the components, their docs pages
54
+ (`docs/app/react/experimental/`) and the Experimental sidebar group
55
+ (`docs/utils/experimentalNavigation.ts`). Changes to the package configs, the
56
+ guardrails and the rest of the docs need a Bifrost developer, as
57
+ [CODEOWNERS](https://github.com/intility/bifrost/blob/main/CODEOWNERS) defines.
@@ -0,0 +1,94 @@
1
+ /* No z-index or isolation: the shade has to join the stacking context around
2
+ it, so it sits above what is behind it but below all the text */
3
+ .bfx-backdrop {
4
+ display: block;
5
+ position: relative;
6
+ width: fit-content;
7
+ }
8
+
9
+ /* The wrapper only takes the text color from its bfc-*-bg class. The type
10
+ selector outranks the class, whatever order the stylesheets load in. */
11
+ span.bfx-backdrop {
12
+ background-color: transparent;
13
+ }
14
+
15
+ .bfx-backdrop-shade {
16
+ position: absolute;
17
+ z-index: -1;
18
+ pointer-events: none;
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;
25
+
26
+ /* Soft edges, so it doesn't look like a box */
27
+ filter: blur(10px);
28
+ }
29
+
30
+ /* A stacking context, so the title's and subtitle's backdrops sit behind both
31
+ lines and neither covers the other line */
32
+ .bfx-frontpage-header {
33
+ display: flex;
34
+ align-items: center;
35
+ gap: var(--bfs16);
36
+ isolation: isolate;
37
+ }
38
+
39
+ .bfx-frontpage-header-logo {
40
+ display: flex;
41
+ flex-shrink: 0;
42
+ height: 72px;
43
+ }
44
+
45
+ /* Stretching from the slot isn't enough in Safari, which sizes the slot from
46
+ the image's own width. An SVG with only a viewBox has none, so the logo would
47
+ overlap the title. */
48
+ .bfx-frontpage-header-logo > * {
49
+ height: 100%;
50
+ width: auto;
51
+ }
52
+
53
+ /* flex-start keeps each line only as wide as its text */
54
+ .bfx-frontpage-header-text {
55
+ display: flex;
56
+ flex-direction: column;
57
+ align-items: flex-start;
58
+ gap: var(--bfs8);
59
+ }
60
+ .bfx-twinkle-background {
61
+ position: absolute;
62
+ inset: 0 0 auto;
63
+
64
+ /* A canvas is a replaced element, so inset alone doesn't stretch it */
65
+ width: 100%;
66
+ height: 40%;
67
+ z-index: -1;
68
+ pointer-events: none;
69
+
70
+ /* The component reads this color back for the canvas. Light mode cuts the
71
+ chroma to a quarter, so the dots are only a hint of color. Browsers without
72
+ relative colors fall back to the text color. */
73
+ color: light-dark(
74
+ oklch(from var(--bfc-theme) l calc(c * 0.25) h),
75
+ var(--bfc-theme)
76
+ );
77
+ }
78
+
79
+ /* Red and green aren't themes. The theme colors have a lightness of 46% to
80
+ 55% in light mode and 71% to 90% in dark mode, so the lightness is moved
81
+ into that range where alert (55% dark) and success (85% light) are far off. */
82
+ .bfx-twinkle-background-red {
83
+ color: light-dark(
84
+ oklch(from var(--bfc-alert) l calc(c * 0.25) h),
85
+ oklch(from var(--bfc-alert) 66% c h)
86
+ );
87
+ }
88
+
89
+ .bfx-twinkle-background-green {
90
+ color: light-dark(
91
+ oklch(from var(--bfc-success) 52% calc(c * 0.25) h),
92
+ var(--bfc-success)
93
+ );
94
+ }
@@ -0,0 +1,31 @@
1
+ import type { BoxProps } from "@intility/bifrost-react/Box";
2
+ import type { ReactNode } from "react";
3
+ export type BackdropProps = {
4
+ children: ReactNode;
5
+ /**
6
+ * The background the backdrop sits on, which is the color it fades out to.
7
+ * Takes the same values as the `background` prop of Bifrost's Box, and also
8
+ * sets the matching text color.
9
+ * @example
10
+ * `"base"` is the page background, and the default.
11
+ * `true` and `"base-3"` match `.bfc-base-3-bg`.
12
+ * `"theme-fade"` matches `.bfc-theme-fade-bg`.
13
+ */
14
+ background?: BoxProps["background"];
15
+ };
16
+ /**
17
+ * Soft shaded area behind one line of text or one see-through control, so it
18
+ * stays readable on a busy background like the twinkle background. Wrap each
19
+ * line on its own, not a group of lines. Experimental: AI-generated, not
20
+ * reviewed line by line.
21
+ *
22
+ * @see https://bifrost.intility.com/react/experimental/backdrop
23
+ *
24
+ * @example
25
+ * <h1 className="bf-h1">
26
+ * <Backdrop>Platform Manager</Backdrop>
27
+ * </h1>
28
+ * <Backdrop background="base-3">On a base-3 background</Backdrop>
29
+ */
30
+ declare const Backdrop: ({ children, background }: BackdropProps) => import("react").JSX.Element;
31
+ export default Backdrop;
@@ -0,0 +1,53 @@
1
+ import { c as _c } from "react-compiler-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * Soft shaded area behind one line of text or one see-through control, so it
5
+ * stays readable on a busy background like the twinkle background. Wrap each
6
+ * line on its own, not a group of lines. Experimental: AI-generated, not
7
+ * reviewed line by line.
8
+ *
9
+ * @see https://bifrost.intility.com/react/experimental/backdrop
10
+ *
11
+ * @example
12
+ * <h1 className="bf-h1">
13
+ * <Backdrop>Platform Manager</Backdrop>
14
+ * </h1>
15
+ * <Backdrop background="base-3">On a base-3 background</Backdrop>
16
+ */
17
+ const Backdrop = t0 => {
18
+ const $ = _c(6);
19
+ const {
20
+ children,
21
+ background: t1
22
+ } = t0;
23
+ const background = t1 === undefined ? "base" : t1;
24
+ const bg = `bfc-${background === true ? "base-3" : background || "base"}-bg`;
25
+ const t2 = `bfx-backdrop ${bg}`;
26
+ const t3 = `bfx-backdrop-shade ${bg}`;
27
+ let t4;
28
+ if ($[0] !== t3) {
29
+ t4 = /*#__PURE__*/_jsx("span", {
30
+ "aria-hidden": true,
31
+ className: t3
32
+ });
33
+ $[0] = t3;
34
+ $[1] = t4;
35
+ } else {
36
+ t4 = $[1];
37
+ }
38
+ let t5;
39
+ if ($[2] !== children || $[3] !== t2 || $[4] !== t4) {
40
+ t5 = /*#__PURE__*/_jsxs("span", {
41
+ className: t2,
42
+ children: [t4, children]
43
+ });
44
+ $[2] = children;
45
+ $[3] = t2;
46
+ $[4] = t4;
47
+ $[5] = t5;
48
+ } else {
49
+ t5 = $[5];
50
+ }
51
+ return t5;
52
+ };
53
+ export default Backdrop;
@@ -0,0 +1,2 @@
1
+ export type * from "./Backdrop.js";
2
+ export { default } from "./Backdrop.js";
@@ -0,0 +1 @@
1
+ export { default } from "./Backdrop.js";
@@ -0,0 +1,33 @@
1
+ import type { ReactNode } from "react";
2
+ export type FrontpageHeaderProps = {
3
+ /**
4
+ * The name of the app or module. Rendered as the page's `h1`, so use the
5
+ * component once per page.
6
+ */
7
+ title: ReactNode;
8
+ /** One line under the title */
9
+ subtitle?: ReactNode;
10
+ /**
11
+ * Image shown to the left of the title, like `<img src="/logo.svg" alt="" />`.
12
+ * It is shown 72px high and hidden on small screens. Use an empty `alt` when
13
+ * the title already names the app.
14
+ */
15
+ logo?: ReactNode;
16
+ };
17
+ /**
18
+ * Header for the front page of a Platform Manager module: logo, title and
19
+ * subtitle. The title is the page's `h1`. To keep the title and subtitle
20
+ * readable on the twinkle background, wrap each in a `Backdrop`.
21
+ * Experimental: AI-generated, not reviewed line by line.
22
+ *
23
+ * @see https://bifrost.intility.com/react/experimental/frontpage-header
24
+ *
25
+ * @example
26
+ * <FrontpageHeader
27
+ * logo={<img src="/logo.svg" alt="" />}
28
+ * title={<Backdrop>Platform Manager</Backdrop>}
29
+ * subtitle={<Backdrop>An operational control plane</Backdrop>}
30
+ * />
31
+ */
32
+ declare const FrontpageHeader: ({ title, subtitle, logo }: FrontpageHeaderProps) => import("react").JSX.Element;
33
+ export default FrontpageHeader;
@@ -0,0 +1,84 @@
1
+ import { c as _c } from "react-compiler-runtime";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ /**
4
+ * Header for the front page of a Platform Manager module: logo, title and
5
+ * subtitle. The title is the page's `h1`. To keep the title and subtitle
6
+ * readable on the twinkle background, wrap each in a `Backdrop`.
7
+ * Experimental: AI-generated, not reviewed line by line.
8
+ *
9
+ * @see https://bifrost.intility.com/react/experimental/frontpage-header
10
+ *
11
+ * @example
12
+ * <FrontpageHeader
13
+ * logo={<img src="/logo.svg" alt="" />}
14
+ * title={<Backdrop>Platform Manager</Backdrop>}
15
+ * subtitle={<Backdrop>An operational control plane</Backdrop>}
16
+ * />
17
+ */
18
+ const FrontpageHeader = t0 => {
19
+ const $ = _c(12);
20
+ const {
21
+ title,
22
+ subtitle,
23
+ logo
24
+ } = t0;
25
+ let t1;
26
+ if ($[0] !== logo) {
27
+ t1 = logo && /*#__PURE__*/_jsx("span", {
28
+ className: "bfx-frontpage-header-logo from-small",
29
+ children: logo
30
+ });
31
+ $[0] = logo;
32
+ $[1] = t1;
33
+ } else {
34
+ t1 = $[1];
35
+ }
36
+ let t2;
37
+ if ($[2] !== title) {
38
+ t2 = /*#__PURE__*/_jsx("h1", {
39
+ className: "bf-h1",
40
+ children: title
41
+ });
42
+ $[2] = title;
43
+ $[3] = t2;
44
+ } else {
45
+ t2 = $[3];
46
+ }
47
+ let t3;
48
+ if ($[4] !== subtitle) {
49
+ t3 = subtitle && /*#__PURE__*/_jsx("div", {
50
+ className: "bf-large",
51
+ children: subtitle
52
+ });
53
+ $[4] = subtitle;
54
+ $[5] = t3;
55
+ } else {
56
+ t3 = $[5];
57
+ }
58
+ let t4;
59
+ if ($[6] !== t2 || $[7] !== t3) {
60
+ t4 = /*#__PURE__*/_jsxs("div", {
61
+ className: "bfx-frontpage-header-text",
62
+ children: [t2, t3]
63
+ });
64
+ $[6] = t2;
65
+ $[7] = t3;
66
+ $[8] = t4;
67
+ } else {
68
+ t4 = $[8];
69
+ }
70
+ let t5;
71
+ if ($[9] !== t1 || $[10] !== t4) {
72
+ t5 = /*#__PURE__*/_jsxs("div", {
73
+ className: "bfx-frontpage-header",
74
+ children: [t1, t4]
75
+ });
76
+ $[9] = t1;
77
+ $[10] = t4;
78
+ $[11] = t5;
79
+ } else {
80
+ t5 = $[11];
81
+ }
82
+ return t5;
83
+ };
84
+ export default FrontpageHeader;
@@ -0,0 +1,2 @@
1
+ export type * from "./FrontpageHeader.js";
2
+ export { default } from "./FrontpageHeader.js";
@@ -0,0 +1 @@
1
+ export { default } from "./FrontpageHeader.js";
@@ -0,0 +1,28 @@
1
+ export type TwinkleBackgroundProps = {
2
+ /**
3
+ * The color of the dots. `"theme"` follows the Bifrost theme of the app, and
4
+ * the other values override it.
5
+ * @default "theme"
6
+ */
7
+ color?: "theme" | "teal" | "purple" | "pink" | "yellow" | "red" | "green";
8
+ };
9
+ /**
10
+ * Animated dot grid in the Bifrost theme color, for the front page of a
11
+ * Platform Manager module. It fades out down the page and reacts to hover and
12
+ * clicks. Render it as the first child of a container with
13
+ * `position: relative` and `isolation: isolate`; it fills the top 40% of that
14
+ * container, behind its content. Experimental: AI-generated, not reviewed line
15
+ * by line.
16
+ *
17
+ * @see https://bifrost.intility.com/react/experimental/twinkle-background
18
+ *
19
+ * @example
20
+ * <div style={{ position: "relative", isolation: "isolate" }}>
21
+ * <TwinkleBackground />
22
+ * <Backdrop>
23
+ * <h1 className="bf-h1">Platform Manager</h1>
24
+ * </Backdrop>
25
+ * </div>
26
+ */
27
+ declare const TwinkleBackground: ({ color }: TwinkleBackgroundProps) => import("react").JSX.Element;
28
+ export default TwinkleBackground;
@@ -0,0 +1,378 @@
1
+ "use client";
2
+
3
+ import { c as _c } from "react-compiler-runtime";
4
+ import { useEffect, useRef, useSyncExternalStore } from "react";
5
+ import { jsx as _jsx } from "react/jsx-runtime";
6
+ // Not props, so every front page looks the same
7
+ const SPACING = 16;
8
+ // Opacity at the dimmest point of a twinkle
9
+ const MIN_ALPHA = 0.2;
10
+ // Hover glow reach in CSS pixels, ripple speed in CSS pixels per second and
11
+ // ripple life in seconds
12
+ const RADIUS = 240;
13
+ const RIPPLE_SPEED = 620;
14
+ const RIPPLE_LIFE = 1.8;
15
+ const CLICK_STRENGTH = 0.5;
16
+ // Resting opacity. Hover and ripples lift dots above it, up to fully opaque.
17
+ const OPACITY = {
18
+ light: 0.4,
19
+ dark: 0.8
20
+ };
21
+ // Dots are filled in batches of equal opacity, one path per batch instead of
22
+ // one per dot. The steps are too small to see.
23
+ const ALPHA_STEPS = 48;
24
+ // Reading the computed color forces a style recalculation, so not every frame
25
+ const STYLE_INTERVAL = 250;
26
+ // Caps the animation at about 60fps on high refresh rate screens
27
+ const FRAME_INTERVAL = 1000 / 60 - 2;
28
+ // Clicks on these don't start a ripple. tabindex="-1" is often set on a whole
29
+ // container like <main>, so it doesn't count.
30
+ const INTERACTIVE = 'a, button, input, select, textarea, label, summary, [tabindex]:not([tabindex="-1"]), [contenteditable],' + "[role=button], [role=link], [role=tab], [role=checkbox], [role=radio], [role=switch]," + "[role=menuitem], [role=option], [role=combobox], [role=slider]";
31
+ const TAU = Math.PI * 2;
32
+
33
+ // Reduced motion is the only way to stop the twinkle (WCAG 2.2.2), so it is
34
+ // followed live
35
+ const REDUCED_MOTION = "(prefers-reduced-motion: reduce)";
36
+ const subscribeReducedMotion = onChange => {
37
+ const query = window.matchMedia(REDUCED_MOTION);
38
+ query.addEventListener("change", onChange);
39
+ return () => query.removeEventListener("change", onChange);
40
+ };
41
+ const useReducedMotion = () => {
42
+ return useSyncExternalStore(subscribeReducedMotion, _temp, _temp2);
43
+ };
44
+ const within = (rect, x, y) => x >= 0 && x <= rect.width && y >= 0 && y <= rect.height;
45
+
46
+ /**
47
+ * Animated dot grid in the Bifrost theme color, for the front page of a
48
+ * Platform Manager module. It fades out down the page and reacts to hover and
49
+ * clicks. Render it as the first child of a container with
50
+ * `position: relative` and `isolation: isolate`; it fills the top 40% of that
51
+ * container, behind its content. Experimental: AI-generated, not reviewed line
52
+ * by line.
53
+ *
54
+ * @see https://bifrost.intility.com/react/experimental/twinkle-background
55
+ *
56
+ * @example
57
+ * <div style={{ position: "relative", isolation: "isolate" }}>
58
+ * <TwinkleBackground />
59
+ * <Backdrop>
60
+ * <h1 className="bf-h1">Platform Manager</h1>
61
+ * </Backdrop>
62
+ * </div>
63
+ */
64
+ const TwinkleBackground = t0 => {
65
+ const $ = _c(5);
66
+ const {
67
+ color: t1
68
+ } = t0;
69
+ const color = t1 === undefined ? "theme" : t1;
70
+ const canvasRef = useRef(null);
71
+ const still = useReducedMotion();
72
+ const colorClass = color === "red" || color === "green" ? ` bfx-twinkle-background-${color}` : color === "theme" ? "" : ` bf-theme-${color}`;
73
+ let t2;
74
+ let t3;
75
+ if ($[0] !== still) {
76
+ t2 = () => {
77
+ const canvas = canvasRef.current;
78
+ const ctx = canvas?.getContext("2d");
79
+ if (!canvas || !ctx) {
80
+ return;
81
+ }
82
+ const forcedColors = window.matchMedia("(forced-colors: active)");
83
+ const pointer = {
84
+ x: 0,
85
+ y: 0,
86
+ on: 0
87
+ };
88
+ const cursor = {
89
+ x: 0,
90
+ y: 0,
91
+ over: false
92
+ };
93
+ const clicks = [];
94
+ let w = 0;
95
+ let h = 0;
96
+ let cols = 0;
97
+ let rand = new Float32Array(0);
98
+ const seed = Math.random() * 4294967296 | 0;
99
+ const noise = (col, row, k) => {
100
+ let n = Math.imul(col + 1, 2654435761) ^ Math.imul(row + 1, 2246822507) ^ Math.imul(k + 1, 3266489909) ^ seed;
101
+ n = Math.imul(n ^ n >>> 15, 739982445);
102
+ n = Math.imul(n ^ n >>> 12, 695872825);
103
+ return ((n ^ n >>> 15) >>> 0) / 4294967296;
104
+ };
105
+ let clock = 0;
106
+ let last = 0;
107
+ let raf = 0;
108
+ let visible = false;
109
+ let fill = "";
110
+ let base = OPACITY.light;
111
+ let styleAt = -Infinity;
112
+ const readStyle = () => {
113
+ const style = getComputedStyle(canvas);
114
+ fill = style.color;
115
+ base = style.colorScheme.includes("dark") ? OPACITY.dark : OPACITY.light;
116
+ };
117
+ const batches = Array.from({
118
+ length: ALPHA_STEPS
119
+ }, _temp3);
120
+ const draw = () => {
121
+ ctx.clearRect(0, 0, w, h);
122
+ if (forcedColors.matches || h <= 0) {
123
+ return;
124
+ }
125
+ ctx.fillStyle = fill;
126
+ for (let i = 0; i < rand.length; i = i + 2, i) {
127
+ const r1 = rand[i];
128
+ const r2 = rand[i + 1];
129
+ const x = i / 2 % cols * SPACING;
130
+ const y = Math.floor(i / 2 / cols) * SPACING;
131
+ const tw = 0.5 + 0.5 * Math.sin(clock * 0.48 * (1 + r1 * 3) + r2 * TAU);
132
+ let alpha = MIN_ALPHA + (1 - MIN_ALPHA) * tw * tw;
133
+ let size = 1.4 * (1 - 0.3 * r2) * (0.8 + 0.4 * tw);
134
+ if (pointer.on > 0.001) {
135
+ const d = Math.hypot(x - pointer.x, y - pointer.y);
136
+ const near = d < RADIUS ? 1 - d / RADIUS : 0;
137
+ alpha = alpha * (1 - 0.3 * pointer.on + near * near * 1.8 * pointer.on);
138
+ }
139
+ for (const c of clicks) {
140
+ const age = clock - c.t;
141
+ const life = 1 - age / RIPPLE_LIFE;
142
+ const d_0 = Math.hypot(x - c.x, y - c.y);
143
+ const ring = Math.exp(-((d_0 - age * RIPPLE_SPEED) ** 2) / 4232) * life * life * CLICK_STRENGTH;
144
+ alpha = alpha + ring * 2.6;
145
+ size = size + ring * 1.4;
146
+ }
147
+ const fade = y / h;
148
+ alpha = alpha * (1 - fade * fade) * base;
149
+ const step = Math.round(Math.min(1, alpha) * ALPHA_STEPS);
150
+ if (step < 1) {
151
+ continue;
152
+ }
153
+ batches[step - 1].push(x, y, size);
154
+ }
155
+ batches.forEach((dots, i_0) => {
156
+ if (!dots.length) {
157
+ return;
158
+ }
159
+ ctx.globalAlpha = (i_0 + 1) / ALPHA_STEPS;
160
+ ctx.beginPath();
161
+ for (let j = 0; j < dots.length; j = j + 3, j) {
162
+ ctx.moveTo(dots[j] + dots[j + 2], dots[j + 1]);
163
+ ctx.arc(dots[j], dots[j + 1], dots[j + 2], 0, TAU);
164
+ }
165
+ ctx.fill();
166
+ dots.length = 0;
167
+ });
168
+ };
169
+ const redraw = () => {
170
+ if (h <= 0) {
171
+ return;
172
+ }
173
+ readStyle();
174
+ draw();
175
+ };
176
+ const tick = now => {
177
+ if (forcedColors.matches) {
178
+ ctx.clearRect(0, 0, w, h);
179
+ return;
180
+ }
181
+ raf = requestAnimationFrame(tick);
182
+ if (last && now - last < FRAME_INTERVAL) {
183
+ return;
184
+ }
185
+ if (now - styleAt > STYLE_INTERVAL) {
186
+ readStyle();
187
+ styleAt = now;
188
+ }
189
+ clock = clock + (last ? Math.min(0.05, (now - last) / 1000) : 0);
190
+ clock;
191
+ last = now;
192
+ while (clicks.length && clock - clicks[0].t > RIPPLE_LIFE) {
193
+ clicks.shift();
194
+ }
195
+ let inside = false;
196
+ if (cursor.over) {
197
+ const rect = canvas.getBoundingClientRect();
198
+ const x_0 = cursor.x - rect.left;
199
+ const y_0 = cursor.y - rect.top;
200
+ inside = within(rect, x_0, y_0);
201
+ if (pointer.on < 0.01) {
202
+ pointer.x = x_0;
203
+ pointer.y = y_0;
204
+ }
205
+ pointer.x = pointer.x + (x_0 - pointer.x) * 0.18;
206
+ pointer.y = pointer.y + (y_0 - pointer.y) * 0.18;
207
+ }
208
+ pointer.on = pointer.on + ((inside ? 1 : 0) - pointer.on) * 0.08;
209
+ draw();
210
+ };
211
+ const play = () => {
212
+ cancelAnimationFrame(raf);
213
+ last = 0;
214
+ raf = requestAnimationFrame(tick);
215
+ };
216
+ const resize = () => {
217
+ const rect_0 = canvas.getBoundingClientRect();
218
+ const dpr = window.devicePixelRatio || 1;
219
+ w = rect_0.width;
220
+ h = rect_0.height;
221
+ canvas.width = Math.round(w * dpr);
222
+ canvas.height = Math.round(h * dpr);
223
+ ctx.setTransform(w ? canvas.width / w : 1, 0, 0, h ? canvas.height / h : 1, 0, 0);
224
+ cols = Math.ceil(w / SPACING) + 1;
225
+ rand = Float32Array.from({
226
+ length: cols * (Math.ceil(h / SPACING) + 1) * 2
227
+ }, (_, i_1) => noise((i_1 >> 1) % cols, Math.floor(i_1 / 2 / cols), i_1 & 1));
228
+ redraw();
229
+ };
230
+ let unwatchDensity = _temp4;
231
+ const watchDensity = () => {
232
+ const query = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`);
233
+ const onChange = () => {
234
+ resize();
235
+ watchDensity();
236
+ };
237
+ query.addEventListener("change", onChange, {
238
+ once: true
239
+ });
240
+ unwatchDensity = () => query.removeEventListener("change", onChange);
241
+ };
242
+ const onMove = e => {
243
+ cursor.x = e.clientX;
244
+ cursor.y = e.clientY;
245
+ cursor.over = true;
246
+ };
247
+ const onLeave = () => {
248
+ cursor.over = false;
249
+ };
250
+ const onRelease = e_0 => {
251
+ if (e_0.pointerType !== "mouse") {
252
+ onLeave();
253
+ }
254
+ };
255
+ const onDown = e_1 => {
256
+ const el = e_1.target;
257
+ if (e_1.button !== 0 || !(el instanceof Element)) {
258
+ return;
259
+ }
260
+ if (!canvas.parentElement?.contains(el) || el.closest(INTERACTIVE)) {
261
+ return;
262
+ }
263
+ const rect_1 = canvas.getBoundingClientRect();
264
+ const x_1 = e_1.clientX - rect_1.left;
265
+ const y_1 = e_1.clientY - rect_1.top;
266
+ if (!within(rect_1, x_1, y_1)) {
267
+ return;
268
+ }
269
+ if (clicks.length >= 8) {
270
+ clicks.shift();
271
+ }
272
+ clicks.push({
273
+ x: x_1,
274
+ y: y_1,
275
+ t: clock
276
+ });
277
+ };
278
+ const resizeObserver = new ResizeObserver(resize);
279
+ resizeObserver.observe(canvas);
280
+ watchDensity();
281
+ const systemMode = window.matchMedia("(prefers-color-scheme: dark)");
282
+ const attributes = new MutationObserver(redraw);
283
+ const onForcedColors = () => {
284
+ if (still) {
285
+ redraw();
286
+ } else {
287
+ if (visible) {
288
+ play();
289
+ }
290
+ }
291
+ };
292
+ forcedColors.addEventListener("change", onForcedColors);
293
+ if (still) {
294
+ systemMode.addEventListener("change", redraw);
295
+ for (let el_0 = canvas; el_0; el_0 = el_0.parentElement) {
296
+ attributes.observe(el_0, {
297
+ attributes: true,
298
+ attributeFilter: ["class", "style", "data-bf-color-mode"]
299
+ });
300
+ }
301
+ }
302
+ const visibility = new IntersectionObserver(t4 => {
303
+ const [entry] = t4;
304
+ visible = entry.isIntersecting;
305
+ if (visible) {
306
+ play();
307
+ } else {
308
+ cancelAnimationFrame(raf);
309
+ }
310
+ });
311
+ if (!still) {
312
+ visibility.observe(canvas);
313
+ window.addEventListener("pointermove", onMove, {
314
+ passive: true
315
+ });
316
+ window.addEventListener("pointerdown", onDown, {
317
+ passive: true
318
+ });
319
+ window.addEventListener("pointerup", onRelease, {
320
+ passive: true
321
+ });
322
+ window.addEventListener("pointercancel", onLeave, {
323
+ passive: true
324
+ });
325
+ document.addEventListener("mouseleave", onLeave);
326
+ window.addEventListener("blur", onLeave);
327
+ }
328
+ return () => {
329
+ cancelAnimationFrame(raf);
330
+ resizeObserver.disconnect();
331
+ unwatchDensity();
332
+ systemMode.removeEventListener("change", redraw);
333
+ forcedColors.removeEventListener("change", onForcedColors);
334
+ attributes.disconnect();
335
+ visibility.disconnect();
336
+ window.removeEventListener("pointermove", onMove);
337
+ window.removeEventListener("pointerdown", onDown);
338
+ window.removeEventListener("pointerup", onRelease);
339
+ window.removeEventListener("pointercancel", onLeave);
340
+ document.removeEventListener("mouseleave", onLeave);
341
+ window.removeEventListener("blur", onLeave);
342
+ };
343
+ };
344
+ t3 = [still];
345
+ $[0] = still;
346
+ $[1] = t2;
347
+ $[2] = t3;
348
+ } else {
349
+ t2 = $[1];
350
+ t3 = $[2];
351
+ }
352
+ useEffect(t2, t3);
353
+ const t4 = `bfx-twinkle-background${colorClass}`;
354
+ let t5;
355
+ if ($[3] !== t4) {
356
+ t5 = /*#__PURE__*/_jsx("canvas", {
357
+ ref: canvasRef,
358
+ "aria-hidden": true,
359
+ className: t4
360
+ });
361
+ $[3] = t4;
362
+ $[4] = t5;
363
+ } else {
364
+ t5 = $[4];
365
+ }
366
+ return t5;
367
+ };
368
+ export default TwinkleBackground;
369
+ function _temp() {
370
+ return window.matchMedia(REDUCED_MOTION).matches;
371
+ }
372
+ function _temp2() {
373
+ return false;
374
+ }
375
+ function _temp3() {
376
+ return [];
377
+ }
378
+ function _temp4() {}
@@ -0,0 +1,2 @@
1
+ export type * from "./TwinkleBackground.js";
2
+ export { default } from "./TwinkleBackground.js";
@@ -0,0 +1 @@
1
+ export { default } from "./TwinkleBackground.js";
@@ -0,0 +1,3 @@
1
+ export { default as Backdrop } from "./components/Backdrop/index.js";
2
+ export { default as FrontpageHeader } from "./components/FrontpageHeader/index.js";
3
+ export { default as TwinkleBackground } from "./components/TwinkleBackground/index.js";
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { default as Backdrop } from "./components/Backdrop/index.js";
2
+ export { default as FrontpageHeader } from "./components/FrontpageHeader/index.js";
3
+ export { default as TwinkleBackground } from "./components/TwinkleBackground/index.js";
package/package.json CHANGED
@@ -1,6 +1,61 @@
1
1
  {
2
2
  "name": "@intility/bifrost-experimental",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Experimental, AI-generated React components for Intility's design system, Bifrost.",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "scripts": {
8
+ "build:css": "postcss src/bifrost-experimental.css --dir dist --use postcss-import --no-map",
9
+ "build:module": "babel ./src -d dist --extensions .ts,.tsx --ignore '**/*.test.*,**/*.d.ts' --root-mode upward",
10
+ "build:types": "tsc --project tsconfig.build.json",
11
+ "watch:css": "npm run build:css -- --watch",
12
+ "watch:module": "npm run build:module -- --watch",
13
+ "watch:types": "npm run build:types -- --watch --preserveWatchOutput",
14
+ "clean": "rimraf dist",
15
+ "lint": "eslint \"./src/**/*.{ts,tsx}\" && stylelint \"src/**/*.css\"",
16
+ "typecheck": "tsc --noEmit"
17
+ },
18
+ "exports": {
19
+ "./package.json": "./package.json",
20
+ "./bifrost-experimental.css": "./dist/bifrost-experimental.css",
21
+ ".": "./dist/index.js",
22
+ "./*": "./dist/components/*/index.js"
23
+ },
24
+ "homepage": "https://bifrost.intility.com/react/experimental",
25
+ "keywords": [
26
+ "react",
27
+ "typescript",
28
+ "intility",
29
+ "design system",
30
+ "bifrost",
31
+ "experimental"
32
+ ],
33
+ "author": "Intility AS",
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/intility/bifrost.git",
38
+ "directory": "packages/bifrost-experimental"
39
+ },
40
+ "publishConfig": {
41
+ "registry": "https://registry.npmjs.org/",
42
+ "access": "public"
43
+ },
44
+ "peerDependencies": {
45
+ "@intility/bifrost-react": "^6",
46
+ "react": "^17 || ^18 || ^19",
47
+ "react-dom": "^17 || ^18 || ^19"
48
+ },
49
+ "devDependencies": {
50
+ "@intility/bifrost-react": "^6.23.0"
51
+ },
52
+ "dependencies": {
53
+ "react-compiler-runtime": "^1.0.0"
54
+ },
55
+ "files": [
56
+ "CHANGELOG.md",
57
+ "README.md",
58
+ "dist/**/*",
59
+ "!dist/**/*.test.*"
60
+ ]
61
+ }