@nadicodeai/ui 4.0.4 → 5.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/AGENTS.md +4 -0
- package/README.md +4 -0
- package/dist/components/activity-orb.d.ts +21 -0
- package/dist/components/activity-orb.d.ts.map +1 -0
- package/dist/components/activity-orb.js +26 -0
- package/dist/components/threshold/canvas-budget.d.ts +10 -0
- package/dist/components/threshold/canvas-budget.d.ts.map +1 -0
- package/dist/components/threshold/canvas-budget.js +54 -0
- package/dist/components/threshold/index.d.ts +6 -0
- package/dist/components/threshold/index.d.ts.map +1 -0
- package/dist/components/threshold/index.js +5 -0
- package/dist/components/threshold/orb.d.ts +14 -0
- package/dist/components/threshold/orb.d.ts.map +1 -0
- package/dist/components/threshold/orb.js +17 -0
- package/dist/components/threshold/presence.d.ts +27 -0
- package/dist/components/threshold/presence.d.ts.map +1 -0
- package/dist/components/threshold/presence.js +159 -0
- package/dist/components/threshold/room.d.ts +33 -0
- package/dist/components/threshold/room.d.ts.map +1 -0
- package/dist/components/threshold/room.js +168 -0
- package/dist/components/threshold/threshold.css +228 -0
- package/dist/components/threshold/typed-line.d.ts +8 -0
- package/dist/components/threshold/typed-line.d.ts.map +1 -0
- package/dist/components/threshold/typed-line.js +43 -0
- package/dist/components/threshold/use-reduced-motion.d.ts +10 -0
- package/dist/components/threshold/use-reduced-motion.d.ts.map +1 -0
- package/dist/components/threshold/use-reduced-motion.js +32 -0
- package/dist/eslint/index.js +3 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/internal/activity-orb.d.ts +27 -0
- package/dist/internal/activity-orb.d.ts.map +1 -0
- package/dist/internal/activity-orb.js +150 -0
- package/dist/internal/agent-visual/color.d.ts.map +1 -1
- package/dist/internal/agent-visual/color.js +13 -7
- package/dist/threshold.d.ts +2 -0
- package/dist/threshold.d.ts.map +1 -0
- package/dist/threshold.js +13 -0
- package/docs/activity.md +120 -0
- package/docs/contract.md +8 -2
- package/docs/threshold.md +221 -0
- package/package.json +8 -2
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @nadicodeai/ui/threshold.css
|
|
3
|
+
*
|
|
4
|
+
* The threshold primitives' stylesheet. It is a separate import rather than a
|
|
5
|
+
* part of globals.css because the surface that needs it most, the Nadia
|
|
6
|
+
* desktop app, runs inside a shell that does not load Tailwind: it takes this
|
|
7
|
+
* file and the design-system token layer, and nothing else from this package.
|
|
8
|
+
*
|
|
9
|
+
* A threshold is a fixed dark surface. It is true black in every theme, so the
|
|
10
|
+
* ladders below read the dark-mode tokens directly instead of branching on the
|
|
11
|
+
* app's mode; that is the fixed-presentation escape the package contract
|
|
12
|
+
* names, taken once, coherently, for the whole surface.
|
|
13
|
+
*
|
|
14
|
+
* The room's two colours are shader uniforms, not CSS, and live in room.tsx.
|
|
15
|
+
* The only colour written here is the still field the room falls back to.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/* The room ---------------------------------------------------------------- */
|
|
19
|
+
|
|
20
|
+
/*
|
|
21
|
+
* Absolute inside its own relative, isolated parent, never fixed against the
|
|
22
|
+
* viewport: a Portal panel holds a room without covering the page. Its still
|
|
23
|
+
* field is a token-built radial that needs no GPU, and the canvas covers it
|
|
24
|
+
* once the shader is painting.
|
|
25
|
+
*/
|
|
26
|
+
.nc-threshold-room {
|
|
27
|
+
position: absolute;
|
|
28
|
+
inset: 0;
|
|
29
|
+
z-index: 0;
|
|
30
|
+
overflow: hidden;
|
|
31
|
+
pointer-events: none;
|
|
32
|
+
background: radial-gradient(
|
|
33
|
+
ellipse 55% 46% at 50% 36%,
|
|
34
|
+
color-mix(in oklab, var(--nc-identity-verde) 30%, var(--nc-identity-ink)),
|
|
35
|
+
transparent 90%
|
|
36
|
+
);
|
|
37
|
+
mask-image: radial-gradient(ellipse 65% 58% at 50% 35%, black 10%, transparent 95%);
|
|
38
|
+
transition: opacity var(--nc-duration-open) var(--nc-ease-emphasized);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.nc-threshold-room canvas {
|
|
42
|
+
opacity: 0.65;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Repair and error states quiet the room; they never remove it. */
|
|
46
|
+
.nc-threshold-room[data-dimmed] {
|
|
47
|
+
opacity: 0.22;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* The presence ------------------------------------------------------------ */
|
|
51
|
+
|
|
52
|
+
.nc-threshold-presence {
|
|
53
|
+
position: relative;
|
|
54
|
+
flex: none;
|
|
55
|
+
width: clamp(180px, 38vh, 320px);
|
|
56
|
+
aspect-ratio: 462 / 520;
|
|
57
|
+
transition: opacity var(--nc-duration-confirm) var(--nc-ease-emphasized);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/* Her lower edge dissolves into the room rather than ending on a rectangle. */
|
|
61
|
+
.nc-threshold-presence-figure {
|
|
62
|
+
position: relative;
|
|
63
|
+
width: 100%;
|
|
64
|
+
height: 100%;
|
|
65
|
+
mask-image: linear-gradient(black 55%, transparent 98%);
|
|
66
|
+
transition: opacity var(--nc-duration-confirm) var(--nc-ease-emphasized);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.nc-threshold-presence-portrait {
|
|
70
|
+
display: block;
|
|
71
|
+
width: 100%;
|
|
72
|
+
height: 100%;
|
|
73
|
+
object-fit: contain;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* While the water is painting her, the plain portrait beneath it is redundant. */
|
|
77
|
+
.nc-threshold-presence-figure[data-surface="water"] .nc-threshold-presence-portrait {
|
|
78
|
+
visibility: hidden;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/*
|
|
82
|
+
* The water canvas is cut to her own silhouette. Water's highlight term is
|
|
83
|
+
* added to the whole frame, not only to the sampled image, so an unmasked
|
|
84
|
+
* canvas paints a faintly lit rectangle over the room: the visible tile the
|
|
85
|
+
* rendered comparisons rejected. The mask matches the shader's own `contain`
|
|
86
|
+
* fit and centred origin, so the two agree pixel for pixel.
|
|
87
|
+
*/
|
|
88
|
+
.nc-threshold-presence-figure canvas {
|
|
89
|
+
position: absolute;
|
|
90
|
+
inset: 0;
|
|
91
|
+
mask-image: var(--threshold-portrait);
|
|
92
|
+
mask-position: center;
|
|
93
|
+
mask-repeat: no-repeat;
|
|
94
|
+
mask-size: contain;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.nc-threshold-presence[data-state="error"] {
|
|
98
|
+
opacity: 0.62;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/*
|
|
102
|
+
* Success: one short verde vivo beat over her own silhouette, then nothing.
|
|
103
|
+
* Verde vivo is Nadia's presence accent and this is the only place a threshold
|
|
104
|
+
* spends it, which is why the buttons beside it stay neutral.
|
|
105
|
+
*/
|
|
106
|
+
.nc-threshold-presence::after {
|
|
107
|
+
content: "";
|
|
108
|
+
position: absolute;
|
|
109
|
+
inset: 0;
|
|
110
|
+
background: var(--nc-dark-verde-vivo);
|
|
111
|
+
mask-image: var(--threshold-portrait);
|
|
112
|
+
mask-position: center;
|
|
113
|
+
mask-repeat: no-repeat;
|
|
114
|
+
mask-size: contain;
|
|
115
|
+
opacity: 0;
|
|
116
|
+
pointer-events: none;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.nc-threshold-presence[data-state="ready"]::after {
|
|
120
|
+
animation: nc-threshold-arrival 360ms var(--nc-ease-out-strong) 1;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@keyframes nc-threshold-arrival {
|
|
124
|
+
0% {
|
|
125
|
+
opacity: 0;
|
|
126
|
+
}
|
|
127
|
+
35% {
|
|
128
|
+
opacity: 0.22;
|
|
129
|
+
}
|
|
130
|
+
100% {
|
|
131
|
+
opacity: 0;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* The orb ----------------------------------------------------------------- */
|
|
136
|
+
|
|
137
|
+
.nc-threshold-orb {
|
|
138
|
+
display: block;
|
|
139
|
+
flex: none;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* The line ---------------------------------------------------------------- */
|
|
143
|
+
|
|
144
|
+
/*
|
|
145
|
+
* A hidden copy of the whole sentence holds the width while the visible one
|
|
146
|
+
* fills in, so the orb beside it does not travel with each character.
|
|
147
|
+
*/
|
|
148
|
+
.nc-threshold-line {
|
|
149
|
+
display: grid;
|
|
150
|
+
position: relative;
|
|
151
|
+
margin: 0;
|
|
152
|
+
font-family: var(--nc-type-display-md-font-family);
|
|
153
|
+
font-size: var(--nc-type-display-md-font-size);
|
|
154
|
+
font-weight: var(--nc-type-display-md-font-weight);
|
|
155
|
+
line-height: var(--nc-type-display-md-line-height);
|
|
156
|
+
letter-spacing: -0.01em;
|
|
157
|
+
text-align: center;
|
|
158
|
+
text-wrap: balance;
|
|
159
|
+
color: var(--nc-dark-ink);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.nc-threshold-line-measure {
|
|
163
|
+
grid-area: 1 / 1;
|
|
164
|
+
visibility: hidden;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.nc-threshold-line-spoken {
|
|
168
|
+
grid-area: 1 / 1;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.nc-threshold-caret {
|
|
172
|
+
display: inline-block;
|
|
173
|
+
width: 2px;
|
|
174
|
+
height: 1em;
|
|
175
|
+
margin-left: 2px;
|
|
176
|
+
vertical-align: -0.15em;
|
|
177
|
+
background: currentcolor;
|
|
178
|
+
visibility: hidden;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.nc-threshold-line[data-typing] .nc-threshold-caret {
|
|
182
|
+
visibility: visible;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/* Shared ------------------------------------------------------------------ */
|
|
186
|
+
|
|
187
|
+
/*
|
|
188
|
+
* The threshold ships its own visually-hidden helper: the desktop shell does
|
|
189
|
+
* not load this package's Tailwind layer, and a progress value nobody can read
|
|
190
|
+
* is not an accessible progress value.
|
|
191
|
+
*/
|
|
192
|
+
.nc-threshold-sr {
|
|
193
|
+
position: absolute;
|
|
194
|
+
width: 1px;
|
|
195
|
+
height: 1px;
|
|
196
|
+
overflow: hidden;
|
|
197
|
+
clip-path: inset(50%);
|
|
198
|
+
white-space: nowrap;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
@media (prefers-reduced-motion: reduce) {
|
|
202
|
+
.nc-threshold-room,
|
|
203
|
+
.nc-threshold-presence,
|
|
204
|
+
.nc-threshold-presence-figure {
|
|
205
|
+
transition: none;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.nc-threshold-presence[data-state="ready"]::after {
|
|
209
|
+
animation: none;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.nc-threshold-caret {
|
|
213
|
+
visibility: hidden;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/*
|
|
218
|
+
* The components also carry the preference as `data-still`, which is what a
|
|
219
|
+
* shell that toggles reduced motion without the media query (an in-app
|
|
220
|
+
* setting) sets. Same result, one rule.
|
|
221
|
+
*/
|
|
222
|
+
.nc-threshold-presence[data-still] {
|
|
223
|
+
transition: none;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.nc-threshold-presence[data-still][data-state="ready"]::after {
|
|
227
|
+
animation: none;
|
|
228
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export type TypedLineProps = {
|
|
2
|
+
/** Milliseconds per character. The threshold's cadence is 44. */
|
|
3
|
+
cadenceMs?: number;
|
|
4
|
+
className?: string;
|
|
5
|
+
text: string;
|
|
6
|
+
};
|
|
7
|
+
export declare function TypedLine({ cadenceMs, className, text }: TypedLineProps): import("react").JSX.Element;
|
|
8
|
+
//# sourceMappingURL=typed-line.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"typed-line.d.ts","sourceRoot":"","sources":["../../../src/components/threshold/typed-line.tsx"],"names":[],"mappings":"AAwBA,MAAM,MAAM,cAAc,GAAG;IAC3B,iEAAiE;IACjE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,wBAAgB,SAAS,CAAC,EAAE,SAAsB,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,cAAc,+BAwCpF"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useState } from "react";
|
|
4
|
+
import { cn } from "../../lib/utils.js";
|
|
5
|
+
import { useReducedMotion } from "./use-reduced-motion.js";
|
|
6
|
+
/*
|
|
7
|
+
* The line Nadia says while a threshold is open. It types itself, because a
|
|
8
|
+
* first run is the one moment in the product where waiting is the content.
|
|
9
|
+
*
|
|
10
|
+
* Three decisions the captures settled:
|
|
11
|
+
*
|
|
12
|
+
* - the count comes from elapsed time, not from how many callbacks arrived, so
|
|
13
|
+
* a software renderer or a busy main thread cannot stretch a one second
|
|
14
|
+
* sentence into five;
|
|
15
|
+
* - a hidden copy of the whole sentence reserves its width, so nothing beside
|
|
16
|
+
* the line travels sideways with each character;
|
|
17
|
+
* - the live region receives the complete sentence at once, so a screen reader
|
|
18
|
+
* is never read one letter at a time.
|
|
19
|
+
*/
|
|
20
|
+
const CADENCE_MS = 44;
|
|
21
|
+
export function TypedLine({ cadenceMs = CADENCE_MS, className, text }) {
|
|
22
|
+
const still = useReducedMotion();
|
|
23
|
+
const [shown, setShown] = useState("");
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (still) {
|
|
26
|
+
setShown(text);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
setShown("");
|
|
30
|
+
const startedAt = performance.now();
|
|
31
|
+
let timer = 0;
|
|
32
|
+
const tick = () => {
|
|
33
|
+
const count = Math.min(text.length, Math.floor((performance.now() - startedAt) / cadenceMs));
|
|
34
|
+
setShown(text.slice(0, count));
|
|
35
|
+
if (count < text.length)
|
|
36
|
+
timer = window.setTimeout(tick, cadenceMs);
|
|
37
|
+
};
|
|
38
|
+
timer = window.setTimeout(tick, cadenceMs);
|
|
39
|
+
return () => window.clearTimeout(timer);
|
|
40
|
+
}, [cadenceMs, still, text]);
|
|
41
|
+
const typing = shown.length < text.length;
|
|
42
|
+
return (_jsxs("p", { className: cn("nc-threshold-line", className), "data-typing": typing || undefined, children: [_jsx("span", { "aria-hidden": "true", className: "nc-threshold-line-measure", children: text }), _jsxs("span", { "aria-hidden": "true", className: "nc-threshold-line-spoken", children: [_jsx("span", { children: shown }), _jsx("span", { className: "nc-threshold-caret" })] }), _jsx("span", { "aria-live": "polite", className: "nc-threshold-sr", children: text })] }));
|
|
43
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live `prefers-reduced-motion`, the one reader the threshold primitives share.
|
|
3
|
+
*
|
|
4
|
+
* The value is read from the media query on every render rather than cached in
|
|
5
|
+
* state, so a preference changed while a threshold is open reaches the room,
|
|
6
|
+
* the presence, the orb and the typed line in the same commit. Server renders
|
|
7
|
+
* report false and the first client render corrects it.
|
|
8
|
+
*/
|
|
9
|
+
export declare function useReducedMotion(): boolean;
|
|
10
|
+
//# sourceMappingURL=use-reduced-motion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-reduced-motion.d.ts","sourceRoot":"","sources":["../../../src/components/threshold/use-reduced-motion.ts"],"names":[],"mappings":"AA2BA;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAE1C"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useSyncExternalStore } from "react";
|
|
3
|
+
const REDUCED_MOTION_QUERY = "(prefers-reduced-motion: reduce)";
|
|
4
|
+
function query() {
|
|
5
|
+
return typeof window === "undefined" || typeof window.matchMedia !== "function"
|
|
6
|
+
? undefined
|
|
7
|
+
: window.matchMedia(REDUCED_MOTION_QUERY);
|
|
8
|
+
}
|
|
9
|
+
function subscribe(onChange) {
|
|
10
|
+
const list = query();
|
|
11
|
+
if (!list)
|
|
12
|
+
return () => { };
|
|
13
|
+
list.addEventListener("change", onChange);
|
|
14
|
+
return () => list.removeEventListener("change", onChange);
|
|
15
|
+
}
|
|
16
|
+
function getSnapshot() {
|
|
17
|
+
return query()?.matches ?? false;
|
|
18
|
+
}
|
|
19
|
+
function getServerSnapshot() {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Live `prefers-reduced-motion`, the one reader the threshold primitives share.
|
|
24
|
+
*
|
|
25
|
+
* The value is read from the media query on every render rather than cached in
|
|
26
|
+
* state, so a preference changed while a threshold is open reaches the room,
|
|
27
|
+
* the presence, the orb and the typed line in the same commit. Server renders
|
|
28
|
+
* report false and the first client render corrects it.
|
|
29
|
+
*/
|
|
30
|
+
export function useReducedMotion() {
|
|
31
|
+
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
|
32
|
+
}
|
package/dist/eslint/index.js
CHANGED
|
@@ -22,13 +22,12 @@ import { shadcnComposition } from "./rules/shadcn-composition.js";
|
|
|
22
22
|
* `@nadicodeai/ui`, whose version the release gate already forces to move in
|
|
23
23
|
* lockstep, so that manifest is the one home of the number and this reads it.
|
|
24
24
|
* The published copy of this file sits at `<ui>/dist/eslint/index.js` and the
|
|
25
|
-
* source sits at `
|
|
25
|
+
* source sits at `tools/lint/eslint/index.js`, which puts the
|
|
26
26
|
* manifest at one of two places relative to here; the `name` field says which
|
|
27
|
-
* candidate is the right one, so
|
|
28
|
-
* levels up — cannot answer by accident.
|
|
27
|
+
* candidate is the right one, so an unrelated manifest cannot answer by accident.
|
|
29
28
|
*/
|
|
30
29
|
const PUBLISHING_PACKAGE = "@nadicodeai/ui";
|
|
31
|
-
const MANIFEST_CANDIDATES = ["../../package.json", "
|
|
30
|
+
const MANIFEST_CANDIDATES = ["../../package.json", "../../../packages/ui/package.json"];
|
|
32
31
|
|
|
33
32
|
const version = readPublishedVersion();
|
|
34
33
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from "./components/accordion.js";
|
|
2
2
|
export * from "./components/action-band.js";
|
|
3
3
|
export * from "./components/action-status.js";
|
|
4
|
+
export * from "./components/activity-orb.js";
|
|
4
5
|
export * from "./components/alert.js";
|
|
5
6
|
export * from "./components/alert-dialog.js";
|
|
6
7
|
export * from "./components/agent-card.js";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export * from "./components/accordion.js";
|
|
2
2
|
export * from "./components/action-band.js";
|
|
3
3
|
export * from "./components/action-status.js";
|
|
4
|
+
export * from "./components/activity-orb.js";
|
|
4
5
|
export * from "./components/alert.js";
|
|
5
6
|
export * from "./components/alert-dialog.js";
|
|
6
7
|
export * from "./components/agent-card.js";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Product activities, not the vendor's agent vocabulary. */
|
|
2
|
+
declare const ACTIVITY_RECIPES: {
|
|
3
|
+
readonly loading: {
|
|
4
|
+
readonly state: "composing";
|
|
5
|
+
readonly speed: 0.92;
|
|
6
|
+
readonly still: 0.6;
|
|
7
|
+
};
|
|
8
|
+
readonly connecting: {
|
|
9
|
+
readonly state: "connecting";
|
|
10
|
+
readonly speed: 0.86;
|
|
11
|
+
readonly still: 0.6;
|
|
12
|
+
};
|
|
13
|
+
readonly processing: {
|
|
14
|
+
readonly state: "working";
|
|
15
|
+
readonly speed: 0.92;
|
|
16
|
+
readonly still: 0.6;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
export type ActivityKind = keyof typeof ACTIVITY_RECIPES;
|
|
20
|
+
export type ActivityOrbSize = 20 | 64 | 88;
|
|
21
|
+
/** Own canvas lifetime; callers supply activity and accessible meaning only. */
|
|
22
|
+
export declare function mountActivityOrb(canvas: HTMLCanvasElement, activity: ActivityKind, size: ActivityOrbSize, initiallyPaused?: boolean): {
|
|
23
|
+
destroy: () => void;
|
|
24
|
+
setPaused: (_paused: boolean) => void;
|
|
25
|
+
};
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=activity-orb.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activity-orb.d.ts","sourceRoot":"","sources":["../../src/internal/activity-orb.ts"],"names":[],"mappings":"AAEA,6DAA6D;AAC7D,QAAA,MAAM,gBAAgB;;;;;;;;;;;;;;;;CAIZ,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,gBAAgB,CAAC;AACzD,MAAM,MAAM,eAAe,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;AAoE3C,gFAAgF;AAChF,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,iBAAiB,EACzB,QAAQ,EAAE,YAAY,EACtB,IAAI,EAAE,eAAe,EACrB,eAAe,UAAQ;;yBAEuC,OAAO;EA0FtE"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { MODE_FRAMES, resolvePreset } from "thinking-orbs/engine";
|
|
2
|
+
/** Product activities, not the vendor's agent vocabulary. */
|
|
3
|
+
const ACTIVITY_RECIPES = {
|
|
4
|
+
loading: { state: "composing", speed: 0.92, still: 0.6 },
|
|
5
|
+
connecting: { state: "connecting", speed: 0.86, still: 0.6 },
|
|
6
|
+
processing: { state: "working", speed: 0.92, still: 0.6 },
|
|
7
|
+
};
|
|
8
|
+
/** Preserve the library's geometry, depth ordering and optical size profiles. */
|
|
9
|
+
function activityFrame(activity, size, seconds) {
|
|
10
|
+
const recipe = ACTIVITY_RECIPES[activity];
|
|
11
|
+
const preset = resolvePreset(recipe.state, size === 20 ? 20 : 64);
|
|
12
|
+
return MODE_FRAMES[preset.mode](size, seconds * preset.speed * recipe.speed, preset.opts);
|
|
13
|
+
}
|
|
14
|
+
function colourAt(palette, white, opacity = 1) {
|
|
15
|
+
const depth = Math.min(1, Math.max(0, white));
|
|
16
|
+
// The foreground particles carry cobalto; the supporting structure keeps
|
|
17
|
+
// inherited ink. Opacity lets the same material work on inverted controls.
|
|
18
|
+
const accent = Math.min(1, Math.max(0, (0.4 - depth) / 0.32)) * 0.75;
|
|
19
|
+
const channels = palette.ink.map((ink, i) => Math.round(ink + ((palette.accent[i] ?? ink) - ink) * accent));
|
|
20
|
+
// eslint-disable-next-line no-restricted-syntax -- Canvas serializes channels resolved from currentColor and --nc-info; this adapter defines no palette literals.
|
|
21
|
+
return `rgba(${channels.join(",")},${opacity * (1 - depth)})`;
|
|
22
|
+
}
|
|
23
|
+
function paintActivityFrame(context, activity, size, seconds, palette) {
|
|
24
|
+
const frame = activityFrame(activity, size, seconds);
|
|
25
|
+
context.clearRect(0, 0, size, size);
|
|
26
|
+
for (const line of frame.lines) {
|
|
27
|
+
context.strokeStyle = colourAt(palette, line.white, line.a);
|
|
28
|
+
context.lineWidth = line.w;
|
|
29
|
+
context.beginPath();
|
|
30
|
+
context.moveTo(line.x1, line.y1);
|
|
31
|
+
context.lineTo(line.x2, line.y2);
|
|
32
|
+
context.stroke();
|
|
33
|
+
}
|
|
34
|
+
for (const dot of frame.dots) {
|
|
35
|
+
context.fillStyle = colourAt(palette, dot.white, dot.a);
|
|
36
|
+
context.beginPath();
|
|
37
|
+
context.arc(dot.x, dot.y, dot.r, 0, Math.PI * 2);
|
|
38
|
+
context.fill();
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function readPalette(canvas) {
|
|
42
|
+
const probe = document.createElement("canvas");
|
|
43
|
+
probe.width = 1;
|
|
44
|
+
probe.height = 1;
|
|
45
|
+
const context = probe.getContext("2d", { willReadFrequently: true });
|
|
46
|
+
if (!context)
|
|
47
|
+
return null;
|
|
48
|
+
const style = getComputedStyle(canvas);
|
|
49
|
+
const read = (value) => {
|
|
50
|
+
context.clearRect(0, 0, 1, 1);
|
|
51
|
+
context.fillStyle = value;
|
|
52
|
+
context.fillRect(0, 0, 1, 1);
|
|
53
|
+
const { data } = context.getImageData(0, 0, 1, 1);
|
|
54
|
+
return [data[0] ?? 0, data[1] ?? 0, data[2] ?? 0];
|
|
55
|
+
};
|
|
56
|
+
const ink = read(style.color);
|
|
57
|
+
const accent = read(style.getPropertyValue("--nc-info").trim() || style.color);
|
|
58
|
+
return { ink, accent };
|
|
59
|
+
}
|
|
60
|
+
/** Own canvas lifetime; callers supply activity and accessible meaning only. */
|
|
61
|
+
export function mountActivityOrb(canvas, activity, size, initiallyPaused = false) {
|
|
62
|
+
const unavailable = { destroy: () => { }, setPaused: (_paused) => { } };
|
|
63
|
+
const context = canvas.getContext("2d");
|
|
64
|
+
if (!context)
|
|
65
|
+
return unavailable;
|
|
66
|
+
let palette = readPalette(canvas);
|
|
67
|
+
if (!palette)
|
|
68
|
+
return unavailable;
|
|
69
|
+
const query = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
70
|
+
let reduced = query.matches;
|
|
71
|
+
let paused = initiallyPaused;
|
|
72
|
+
let visible = true;
|
|
73
|
+
let disposed = false;
|
|
74
|
+
let request = 0;
|
|
75
|
+
// Each recipe retains a representative pose under reduced motion.
|
|
76
|
+
const still = ACTIVITY_RECIPES[activity].still;
|
|
77
|
+
let seconds = still;
|
|
78
|
+
let previous = null;
|
|
79
|
+
let ratio = 1;
|
|
80
|
+
function paint() {
|
|
81
|
+
if (!palette || !context)
|
|
82
|
+
return;
|
|
83
|
+
context.setTransform(ratio, 0, 0, ratio, 0, 0);
|
|
84
|
+
paintActivityFrame(context, activity, size, reduced ? still : seconds, palette);
|
|
85
|
+
}
|
|
86
|
+
function tick(now) {
|
|
87
|
+
request = 0;
|
|
88
|
+
if (disposed || !visible || document.hidden || reduced || paused)
|
|
89
|
+
return;
|
|
90
|
+
if (previous !== null)
|
|
91
|
+
seconds += Math.min((now - previous) / 1000, 0.1);
|
|
92
|
+
previous = now;
|
|
93
|
+
paint();
|
|
94
|
+
request = requestAnimationFrame(tick);
|
|
95
|
+
}
|
|
96
|
+
function play() {
|
|
97
|
+
cancelAnimationFrame(request);
|
|
98
|
+
previous = null;
|
|
99
|
+
paint();
|
|
100
|
+
if (!disposed && visible && !document.hidden && !reduced && !paused) {
|
|
101
|
+
request = requestAnimationFrame(tick);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function refresh() {
|
|
105
|
+
ratio = Math.min(window.devicePixelRatio || 1, 2);
|
|
106
|
+
canvas.width = Math.round(size * ratio);
|
|
107
|
+
canvas.height = Math.round(size * ratio);
|
|
108
|
+
palette = readPalette(canvas);
|
|
109
|
+
play();
|
|
110
|
+
}
|
|
111
|
+
function motionChanged(event) {
|
|
112
|
+
reduced = event.matches;
|
|
113
|
+
play();
|
|
114
|
+
}
|
|
115
|
+
const visibility = typeof IntersectionObserver === "undefined"
|
|
116
|
+
? null
|
|
117
|
+
: new IntersectionObserver(([entry]) => {
|
|
118
|
+
visible = entry?.isIntersecting ?? false;
|
|
119
|
+
play();
|
|
120
|
+
});
|
|
121
|
+
visibility?.observe(canvas);
|
|
122
|
+
// Observe ancestors only: changing unrelated content must not repaint this
|
|
123
|
+
// canvas or install a document-wide subtree observer per inline indicator.
|
|
124
|
+
const theme = new MutationObserver(refresh);
|
|
125
|
+
for (let ancestor = canvas; ancestor; ancestor = ancestor.parentElement) {
|
|
126
|
+
theme.observe(ancestor, {
|
|
127
|
+
attributes: true,
|
|
128
|
+
attributeFilter: ["class", "style", "data-theme"],
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
query.addEventListener("change", motionChanged);
|
|
132
|
+
document.addEventListener("visibilitychange", play);
|
|
133
|
+
window.addEventListener("resize", refresh);
|
|
134
|
+
refresh();
|
|
135
|
+
return {
|
|
136
|
+
setPaused(value) {
|
|
137
|
+
paused = value;
|
|
138
|
+
play();
|
|
139
|
+
},
|
|
140
|
+
destroy() {
|
|
141
|
+
disposed = true;
|
|
142
|
+
cancelAnimationFrame(request);
|
|
143
|
+
visibility?.disconnect();
|
|
144
|
+
theme.disconnect();
|
|
145
|
+
query.removeEventListener("change", motionChanged);
|
|
146
|
+
document.removeEventListener("visibilitychange", play);
|
|
147
|
+
window.removeEventListener("resize", refresh);
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"color.d.ts","sourceRoot":"","sources":["../../../src/internal/agent-visual/color.ts"],"names":[],"mappings":"AAAA,KAAK,WAAW,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"color.d.ts","sourceRoot":"","sources":["../../../src/internal/agent-visual/color.ts"],"names":[],"mappings":"AAAA,KAAK,WAAW,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AAmFpD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAG3F"}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
const clamp = (value) => Math.min(Math.max(value, 0), 1);
|
|
2
|
+
// CSS compilers may serialize numeric channels as percentages. CSS Color 4
|
|
3
|
+
// gives each channel its own 100% value (OKLCH L: 1, C: 0.4; Lab a/b: 125).
|
|
4
|
+
function numberOrPercentage(value, percentRange) {
|
|
5
|
+
const number = Number.parseFloat(value ?? "");
|
|
6
|
+
return value?.endsWith("%") ? (number / 100) * percentRange : number;
|
|
7
|
+
}
|
|
2
8
|
function linearToSrgb(value) {
|
|
3
9
|
const channel = clamp(value);
|
|
4
10
|
return channel <= 0.0031308 ? channel * 12.92 : 1.055 * channel ** (1 / 2.4) - 0.055;
|
|
@@ -12,10 +18,10 @@ function parseOklch(value) {
|
|
|
12
18
|
return null;
|
|
13
19
|
const [channels, alphaValue] = value.slice(start + 1, end).split("/");
|
|
14
20
|
const [lightnessValue, chromaValue, hueValue] = channels?.trim().split(/\s+/) ?? [];
|
|
15
|
-
const lightness =
|
|
16
|
-
const chroma =
|
|
21
|
+
const lightness = numberOrPercentage(lightnessValue, 1);
|
|
22
|
+
const chroma = numberOrPercentage(chromaValue, 0.4);
|
|
17
23
|
const hue = Number.parseFloat(hueValue ?? "");
|
|
18
|
-
const alpha = alphaValue ?
|
|
24
|
+
const alpha = alphaValue ? numberOrPercentage(alphaValue.trim(), 1) : 1;
|
|
19
25
|
if (![lightness, chroma, hue, alpha].every(Number.isFinite))
|
|
20
26
|
return null;
|
|
21
27
|
const radians = (hue * Math.PI) / 180;
|
|
@@ -43,10 +49,10 @@ function parseLab(value) {
|
|
|
43
49
|
return null;
|
|
44
50
|
const [channels, alphaValue] = value.slice(start + 1, end).split("/");
|
|
45
51
|
const [lightnessValue, aValue, bValue] = channels?.trim().split(/\s+/) ?? [];
|
|
46
|
-
const lightness =
|
|
47
|
-
const a =
|
|
48
|
-
const b =
|
|
49
|
-
const alpha = alphaValue ?
|
|
52
|
+
const lightness = numberOrPercentage(lightnessValue, 100);
|
|
53
|
+
const a = numberOrPercentage(aValue, 125);
|
|
54
|
+
const b = numberOrPercentage(bValue, 125);
|
|
55
|
+
const alpha = alphaValue ? numberOrPercentage(alphaValue.trim(), 1) : 1;
|
|
50
56
|
if (![lightness, a, b, alpha].every(Number.isFinite))
|
|
51
57
|
return null;
|
|
52
58
|
const delta = 6 / 29;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"threshold.d.ts","sourceRoot":"","sources":["../src/threshold.ts"],"names":[],"mappings":"AAYA,cAAc,8BAA8B,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* `@nadicodeai/ui/threshold`: the primitives every threshold is drawn from.
|
|
3
|
+
*
|
|
4
|
+
* A threshold is the moment before the product is usable: a first run, a
|
|
5
|
+
* sign-in, a repair. The Nadia desktop app draws its room, presence, status
|
|
6
|
+
* orb and typed line from here, so the brand logic lives in one place.
|
|
7
|
+
*
|
|
8
|
+
* The screens themselves, and the state machines behind them, stay in the
|
|
9
|
+
* consuming app. This entry ships no screen.
|
|
10
|
+
*
|
|
11
|
+
* The stylesheet is a separate import: `@nadicodeai/ui/threshold.css`.
|
|
12
|
+
*/
|
|
13
|
+
export * from "./components/threshold/index.js";
|