@nadicodeai/ui 6.6.2 → 7.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 -4
- package/LICENSES/LOBEHUB-ICONS-MIT.txt +21 -0
- package/NOTICE +14 -0
- package/dist/components/activity-orb.d.ts +1 -1
- package/dist/components/activity-orb.d.ts.map +1 -1
- package/dist/components/brand-icons/claude.d.ts +18 -0
- package/dist/components/brand-icons/claude.d.ts.map +1 -0
- package/dist/components/brand-icons/claude.js +22 -0
- package/dist/components/brand-icons/deepseek.d.ts +18 -0
- package/dist/components/brand-icons/deepseek.d.ts.map +1 -0
- package/dist/components/brand-icons/deepseek.js +22 -0
- package/dist/components/brand-icons/gemini.d.ts +18 -0
- package/dist/components/brand-icons/gemini.d.ts.map +1 -0
- package/dist/components/brand-icons/gemini.js +25 -0
- package/dist/components/brand-icons/grok.d.ts +18 -0
- package/dist/components/brand-icons/grok.d.ts.map +1 -0
- package/dist/components/brand-icons/grok.js +22 -0
- package/dist/components/brand-icons/meta.d.ts +18 -0
- package/dist/components/brand-icons/meta.d.ts.map +1 -0
- package/dist/components/brand-icons/meta.js +25 -0
- package/dist/components/brand-icons/mistral.d.ts +18 -0
- package/dist/components/brand-icons/mistral.d.ts.map +1 -0
- package/dist/components/brand-icons/mistral.js +22 -0
- package/dist/components/brand-icons/moonshot.d.ts +18 -0
- package/dist/components/brand-icons/moonshot.d.ts.map +1 -0
- package/dist/components/brand-icons/moonshot.js +22 -0
- package/dist/components/brand-icons/openai.d.ts +18 -0
- package/dist/components/brand-icons/openai.d.ts.map +1 -0
- package/dist/components/brand-icons/openai.js +22 -0
- package/dist/components/brand-icons/qwen.d.ts +18 -0
- package/dist/components/brand-icons/qwen.d.ts.map +1 -0
- package/dist/components/brand-icons/qwen.js +25 -0
- package/dist/components/brand-icons.d.ts +9 -0
- package/dist/components/brand-icons.d.ts.map +1 -1
- package/dist/components/brand-icons.js +9 -0
- package/dist/components/chart-breakdown-table.d.ts +26 -6
- package/dist/components/chart-breakdown-table.d.ts.map +1 -1
- package/dist/components/chart-breakdown-table.js +4 -4
- package/dist/components/chart-time-series.d.ts +68 -2
- package/dist/components/chart-time-series.d.ts.map +1 -1
- package/dist/components/chart-time-series.js +112 -11
- package/dist/components/chart.d.ts +2 -1
- package/dist/components/chart.d.ts.map +1 -1
- package/dist/components/chart.js +11 -2
- package/dist/components/model-mark.d.ts +19 -0
- package/dist/components/model-mark.d.ts.map +1 -0
- package/dist/components/model-mark.js +66 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/docs/activity.md +4 -5
- package/docs/contract.md +48 -5
- package/package.json +3 -7
- package/dist/components/threshold/canvas-budget.d.ts +0 -10
- package/dist/components/threshold/canvas-budget.d.ts.map +0 -1
- package/dist/components/threshold/canvas-budget.js +0 -54
- package/dist/components/threshold/index.d.ts +0 -6
- package/dist/components/threshold/index.d.ts.map +0 -1
- package/dist/components/threshold/index.js +0 -5
- package/dist/components/threshold/orb.d.ts +0 -14
- package/dist/components/threshold/orb.d.ts.map +0 -1
- package/dist/components/threshold/orb.js +0 -17
- package/dist/components/threshold/presence.d.ts +0 -27
- package/dist/components/threshold/presence.d.ts.map +0 -1
- package/dist/components/threshold/presence.js +0 -159
- package/dist/components/threshold/room.d.ts +0 -33
- package/dist/components/threshold/room.d.ts.map +0 -1
- package/dist/components/threshold/room.js +0 -168
- package/dist/components/threshold/threshold.css +0 -228
- package/dist/components/threshold/typed-line.d.ts +0 -8
- package/dist/components/threshold/typed-line.d.ts.map +0 -1
- package/dist/components/threshold/typed-line.js +0 -43
- package/dist/components/threshold/use-reduced-motion.d.ts +0 -10
- package/dist/components/threshold/use-reduced-motion.d.ts.map +0 -1
- package/dist/components/threshold/use-reduced-motion.js +0 -32
- package/dist/threshold.d.ts +0 -2
- package/dist/threshold.d.ts.map +0 -1
- package/dist/threshold.js +0 -13
- package/docs/threshold.md +0 -221
package/docs/contract.md
CHANGED
|
@@ -6,7 +6,7 @@ The normative invariants for the `@nadicodeai/ui` package: what may live here, h
|
|
|
6
6
|
|
|
7
7
|
- **Token-only, no raw literals or shadow aliases.** Components style through semantic shadcn utilities (`bg-background`, `text-foreground`, `border-input`, and the rest), or a canonical generated design-system token when no standard role is precise enough. The UI package does not mint screen-, component-, or product-specific color aliases. No raw `#hex` / `rgb()` / `hsl()` may appear in `.tsx`; a new source value goes to `packages/design-system/DESIGN.md` first and flows through generated output. A narrow protocol adapter may translate third-party classes directly to canonical tokens in `globals.css`, but it must not create another token vocabulary. Enforced by the raw-colour `no-restricted-syntax` selectors in the library ESLint config, `packages/ui/tests/guards/dark-semantic-roles.test.ts`, and `packages/ui/tests/guards/token-reference-integrity.test.ts`.
|
|
8
8
|
- **Dark-ready semantics, no component dark implementation.** Components use the same semantic role or canonical mode-aware token in every mode. They must not ship Tailwind `dark:` color utilities, component-local `.dark` blocks, OS dark-mode branching, or raw dark palette utilities such as `dark:bg-slate-950` and `dark:text-white`. An intentionally fixed-presentation technology surface, such as the dark terminal console, selects canonical fixed-mode tokens as one coherent component treatment rather than branching by app theme. Enforced by `packages/ui/tests/guards/no-dark.test.ts`, `packages/ui/tests/guards/dark-semantic-roles.test.ts`, and `packages/ui/tests/guards/token-reference-integrity.test.ts`. The generated mode and theme architecture lives only in [`packages/design-system/DESIGN.md`](../../design-system/DESIGN.md#css-architecture--token-pipeline).
|
|
9
|
-
- **Components inherit one complete React style interface.** `@nadicodeai/ui/globals.css` composes every style dependency owned by this package, including the complete design-system CSS module. Components carry no theme of their own, and React callers do not compose design-system stylesheets or partials beside it. Installation and source-scanning setup live in the [consumer setup guide](consuming-cross-repo.md). `@nadicodeai/ui/
|
|
9
|
+
- **Components inherit one complete React style interface.** `@nadicodeai/ui/globals.css` composes every style dependency owned by this package, including the complete design-system CSS module. Components carry no theme of their own, and React callers do not compose design-system stylesheets or partials beside it. Installation and source-scanning setup live in the [consumer setup guide](consuming-cross-repo.md). `@nadicodeai/ui/globals.css` is the only stylesheet this package ships; a surface that loads no Tailwind layer takes the design system's token artifact and writes its own rules against it.
|
|
10
10
|
- **Design-system geometry remains the foundation.** React components compose the kept `nc-*` page grammar and display CSS components; they do not redefine frame math, marker placement, token values, or raw CSS geometry. Use parent-paints-plus-gap for bordered cell grids and keep every visible seam single-owner (doctrine home: `packages/design-system/DESIGN.md` "## Layout").
|
|
11
11
|
|
|
12
12
|
### Persistent notices
|
|
@@ -102,6 +102,8 @@ page, not in repeated panels surrounding identical child tables.
|
|
|
102
102
|
`BreakdownTable` owns its responsive comparison layout: identity and value stay
|
|
103
103
|
in the main row; share moves into a labeled detail row when space is tight.
|
|
104
104
|
Its caption names the table. Callers supply data and labels, not column-hiding CSS.
|
|
105
|
+
An entity's optional `mark` prints between its color dot and its label, so a
|
|
106
|
+
ranked model row shows the maker's mark beside the name.
|
|
105
107
|
|
|
106
108
|
### Record action menus
|
|
107
109
|
|
|
@@ -139,20 +141,31 @@ gone, is [`packages/design-system/DESIGN.md`, "## Status Vocabulary"](../../desi
|
|
|
139
141
|
System activity uses `ActivityOrb` for loading, connecting and processing;
|
|
140
142
|
[activity.md](activity.md) owns selection, rendering and accessibility. It
|
|
141
143
|
composes with status feedback and measured progress without adding outcome
|
|
142
|
-
states to the orb.
|
|
143
|
-
this same renderer.
|
|
144
|
+
states to the orb.
|
|
144
145
|
|
|
145
146
|
- **Source-authored, built package.** Components are authored as shadcn-style source in `src/`, then emitted to `dist/` for consumption by other apps. Package exports must point at built JS, declarations, and CSS, never raw `src/*.tsx`, and `npm pack --dry-run` must include only intentional consumer artifacts.
|
|
146
147
|
- **Package surface is source-backed.** Public components stay flat under `src/components/`. Reviewed registry source under `src/internal/` is implementation, not a package namespace. Do not add top-level exports for recipes, blocks, agent, terminal, or vendor groupings unless a later package API decision adds concrete public source, built files, docs, and tests in the same change.
|
|
147
148
|
- **Agents is the client-only conversation interface.** `@nadicodeai/ui/agents` re-exports the complete `@assistant-ui/react` runtime and primitive API plus the reviewed, branded registry composition held under `src/internal/assistant-ui/`. Its built entry retains `"use client"`. Consumers receive Thread, attachments, Markdown, reasoning, tools, suggestions, providers, runtimes, hooks, and types from that one domain namespace. Agent identity components use their direct component subpaths so server compositions do not import the conversation runtime. The vendor name is not a public NadicodeAI subpath, and the package does not rename, wrap, or rebuild assistant-ui behavior.
|
|
148
149
|
- **Base UI is the active primitive base.** `components.json` must resolve through a Base style, currently `base-nova`, and `npx shadcn@latest info --json` must report `base: "base"`. Do not reintroduce `radix-ui`, `@radix-ui/*`, or `vaul` to package source, dependencies, or app optimizer configuration. Registry-owned shadcn primitives stay aligned with current `shadcn@latest` Base UI output; use Base UI `render` composition instead of Radix `asChild`. `cmdk` is allowed only for the stock shadcn Command component while the current `base-nova` registry declares that dependency; do not use it elsewhere or replace it without a shadcn/compatibility decision and tests.
|
|
149
|
-
- **Charts are shadcn charts.** `chart.tsx` is shadcn's `chart` component over Recharts (`ChartContainer`, `ChartTooltip`, `ChartLegend`, theme-keyed `ChartConfig`), and the chart kit (`chart-time-series`, `chart-breakdown-table`, `chart-sparkline`, `chart-bullet-bar`, `chart-kpi-stat`, `chart-delta`, `chart-date-range-control`, `chart-entity-colors`) is built on it. An app composes these and never imports `recharts` or draws SVG charts of its own; `recharts` is declared by this package alone. A chart the kit lacks is first looked up in the shadcn registry through the CLI or MCP, then added to the kit with NadicodeAI tokens and the chart accessibility contract. What a chart shows is decided by the product; how it is drawn comes from here.
|
|
150
|
+
- **Charts are shadcn charts.** `chart.tsx` is shadcn's `chart` component over Recharts (`ChartContainer`, `ChartTooltip`, `ChartLegend`, theme-keyed `ChartConfig`), and the chart kit (`chart-time-series`, `chart-breakdown-table`, `chart-sparkline`, `chart-bullet-bar`, `chart-kpi-stat`, `chart-delta`, `chart-date-range-control`, `chart-entity-colors`) is built on it. An app composes these and never imports `recharts` or draws SVG charts of its own; `recharts` is declared by this package alone. A chart the kit lacks is first looked up in the shadcn registry through the CLI or MCP, then added to the kit with NadicodeAI tokens and the chart accessibility contract. What a chart shows is decided by the product; how it is drawn comes from here. `TimeSeriesComposed` is that lookup performed: the registry holds no composed item, so the bars-plus-line composition for a rate over its measures is Recharts' `ComposedChart` through this same foundation, with the second axis existing only for a series that names it.
|
|
150
151
|
- **Named shadcn blocks stay named.** When a user, plan, or issue selects a shadcn block/example such as `login-02`, `signup-02`, `dashboard-01`, or `sidebar-*`, use that registry item as the source of truth through shadcn CLI/MCP. Do not replace it with a package-authored abstraction unless the plan explicitly records the substitution, the reason, and the true consumer route that proves it.
|
|
151
152
|
- **A change to what the package ships bumps the version.** Any change to exported components, `src/index.ts`, package `exports`, `globals.css`, theme behavior, package docs, or design-system token, CSS, or theme output this package consumes bumps both package versions in lockstep before handoff, and `npm pack --dry-run -w @nadicodeai/ui` shows exactly what consumers receive (procedure: `../../../docs/agents/publish.md`).
|
|
152
153
|
|
|
153
154
|
## Admission Rules
|
|
154
155
|
|
|
155
|
-
Assistant conversation and runtime APIs enter through `@nadicodeai/ui/agents`; Agent identity enters through `@nadicodeai/ui/components/agent-card`, `@nadicodeai/ui/components/agent-visual`, and the Forma avatars in `@nadicodeai/ui/components/forma-avatar` (owned by [`forma-avatar.md`](forma-avatar.md)). The consuming application still constructs the runtime and owns transport, persistence, permissions, tools, approvals, and failure policy. Shared non-Agent product components (a shell, a settings surface, a generic section, a diff, or a file preview) are admitted only with a named consuming surface landing in the same change. App-bespoke UI and behavior (window chrome, PTY/xterm embeds, queues, approvals, and persistence) stay in the consuming app; they never become package abstractions.
|
|
156
|
+
Assistant conversation and runtime APIs enter through `@nadicodeai/ui/agents`; Agent identity enters through `@nadicodeai/ui/components/agent-card`, `@nadicodeai/ui/components/agent-visual`, and the Forma avatars in `@nadicodeai/ui/components/forma-avatar` (owned by [`forma-avatar.md`](forma-avatar.md)). The consuming application still constructs the runtime and owns transport, persistence, permissions, tools, approvals, and failure policy. Shared non-Agent product components (a shell, a settings surface, a generic section, a diff, or a file preview) are admitted only with a named consuming surface landing in the same change. App-bespoke UI and behavior (window chrome, PTY/xterm embeds, queues, approvals, and persistence) stay in the consuming app; they never become package abstractions. A component left unconsumed after its target migration ships gets removed.
|
|
157
|
+
|
|
158
|
+
### Placement
|
|
159
|
+
|
|
160
|
+
`@nadicodeai/ui` and `@nadicodeai/design-system` hold the Foundation and the
|
|
161
|
+
components, assets and styles at least two products use; a component, asset or
|
|
162
|
+
style one product alone needs lives in that product's own directory. Nadia's
|
|
163
|
+
entry screen is the worked example: the room, the presence, the typed line and
|
|
164
|
+
their stylesheet were a package surface no other product imported, and they now
|
|
165
|
+
live in the desktop at `apps/nadia/hermes/apps/desktop/src/plugins/nadia/entry/`,
|
|
166
|
+
where [`apps/nadia/docs/entry-screen.md`](../../../apps/nadia/docs/entry-screen.md)
|
|
167
|
+
owns their contract. `packages/ui/tests/guards/product-placement.test.ts`
|
|
168
|
+
refuses a product's name in either package.
|
|
156
169
|
|
|
157
170
|
### Agent identity presentation
|
|
158
171
|
|
|
@@ -179,8 +192,38 @@ package.
|
|
|
179
192
|
- **Reusable sections and compositions live here after proof.** Bento grids, media mounts, demo surfaces, page frames, section intros, CTA bands, proof slots, benefits grids, generic shells, and homepage/use-case sections belong in `@nadicodeai/ui` when a second concrete consumer, a plan-approved shared component, or a named validation slice proves the package boundary. Website, portal, and sibling Nadia routes pass data and media children into existing compositions instead of rebuilding layout locally.
|
|
180
193
|
- **Brand renderers live here for React consumers.** Logo geometry, asset generation, and usage semantics are owned by [`packages/design-system/DESIGN.md`, "Brand media authority"](../../design-system/DESIGN.md#brand-media-authority). `BrandLockup` supplies the approved horizontal lockup. `BrandMark` serves tight spaces and `BrandWordmark` serves placements where the name must read by itself. All three render the package's public neutral geometry seam with React-scoped SVG ids, dynamic theme paint, and accessible names. Consuming routes use these adapters; they never import raw logo assets, draw brand SVG inline, mask or recolor assets, or create another lockup rule.
|
|
181
194
|
- **Third-party brand marks are generated, never hand-drawn and never hand-edited.** Other companies' marks live in `packages/ui/src/components/brand-icons/`, emitted from an explicit allow-list by `packages/ui/scripts/build-brand-icons.mjs` and exported through the `@nadicodeai/ui/components/brand-icons` subpath. They are identity images, not UI icons: a brand mark is exempt from the currentColor stroke system and from the raw-literal rule, it never stands in for a UI icon, and a UI icon never stands in for a brand. Marks drawn as one silhouette are stripped to `currentColor` by the generator; every other mark keeps its official colors verbatim. To add a brand, edit the generator's allow-list and run `npm run generate:brand-icons -w @nadicodeai/ui`; hand edits to the generated directory are lost and `packages/ui/tests/guards/brand-icons-codegen.test.ts` fails on them. Use is nominative only — naming a platform, an integration, or a provider — never adjacent to endorsement or partnership wording; that nominative posture is the licensing and trademark position, so a mark never appears where the copy implies endorsement.
|
|
195
|
+
|
|
196
|
+
The one script reads two explicit allow-lists: platform marks out of the Iconify `logos` set (CC0-1.0), and model makers' marks out of the LobeHub icon set (MIT, `@lobehub/icons-static-svg`), both as `devDependencies`. The emitted components are plain SVG, so neither icon data package belongs in the runtime dependency set or reaches a consumer bundle. The artwork itself does ship, so every upstream whose licence asks for one travels with a notice in `LICENSES/` and `NOTICE`, and `tests/guards/brand-icons-codegen.test.ts` holds the allow-list to that: a new source cannot ship unattributed.
|
|
182
197
|
- **No fake proof APIs.** Components may support future testimonials, logos, stats, and proof slots, but they must render nothing public when the proof is absent. Do not ship placeholder customer names, metrics, or logos.
|
|
183
198
|
|
|
199
|
+
### Model identity
|
|
200
|
+
|
|
201
|
+
`ModelMark` is a model's identity: its maker's mark, plus the model's name and
|
|
202
|
+
author when a caller supplies them. It takes `author` — the slug a usage read
|
|
203
|
+
reports — and resolves it against the generated model-maker marks (rule
|
|
204
|
+
`model-shown-with-logo`). An author off that allow-list gets the neutral tile,
|
|
205
|
+
so a model never falls back to a bare colour swatch. The tile owns the box and
|
|
206
|
+
scales the artwork inside it; `size` states that box, and the author slug prints
|
|
207
|
+
as reported.
|
|
208
|
+
|
|
209
|
+
One component has two readings: with `name`, the row a table cell or ranked
|
|
210
|
+
list reads; without one, the bare mark that stands in a chart legend beside that
|
|
211
|
+
legend's own text. `BreakdownTable` entities and the `chart-time-series` series
|
|
212
|
+
(`TimeSeriesBars`, `TimeSeriesArea`, `TimeSeriesComposed`) take an optional
|
|
213
|
+
`mark` for that second reading, placed beside the entity's or series' name. A
|
|
214
|
+
chart places it on each surface itself: the legend through the explicit `marks`
|
|
215
|
+
path on `ChartLegendContent`, and the tooltip row through the kit's own row
|
|
216
|
+
renderer, because `rule/model-shown-with-logo` names tooltips among the places a
|
|
217
|
+
model appears. A series' mark never travels through `ChartConfig`, whose label
|
|
218
|
+
the legend and the default tooltip item both read, so an identity parked there
|
|
219
|
+
would appear on a surface that never asked for it. No legend is forked. A mark is
|
|
220
|
+
decoration beside the label that already names it; one standing alone takes
|
|
221
|
+
`label` (`role="img"` with an accessible name) or stays hidden.
|
|
222
|
+
|
|
223
|
+
What this package does not own: which authors exist, which mark a maker gets
|
|
224
|
+
(the generator's allow-list), or what a surface shows a model for. The consuming
|
|
225
|
+
app supplies the author slug, the name, and the colors it pairs with them.
|
|
226
|
+
|
|
184
227
|
## Accessibility Invariant
|
|
185
228
|
|
|
186
229
|
**Tier-1 accessibility is tested, browser-free.** Rendering component tests use the package `expectAccessible` helper to check the rendered DOM. The jsdom tier is structural: rendered contrast, landmark context, focus, and keyboard behavior belong to real-browser Playwright coverage. The shared library ESLint configuration is the executable authority for static JSX accessibility rules, and the repository quality doctrine owns their policy; this contract does not copy the rule inventory.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nadicodeai/ui",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "7.1.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -19,11 +19,6 @@
|
|
|
19
19
|
"types": "./dist/agents.d.ts",
|
|
20
20
|
"import": "./dist/agents.js"
|
|
21
21
|
},
|
|
22
|
-
"./threshold": {
|
|
23
|
-
"types": "./dist/threshold.d.ts",
|
|
24
|
-
"import": "./dist/threshold.js"
|
|
25
|
-
},
|
|
26
|
-
"./threshold.css": "./dist/components/threshold/threshold.css",
|
|
27
22
|
"./activity": {
|
|
28
23
|
"types": "./dist/activity.d.ts",
|
|
29
24
|
"import": "./dist/activity.js"
|
|
@@ -70,7 +65,7 @@
|
|
|
70
65
|
"@assistant-ui/react": "0.15.18",
|
|
71
66
|
"@assistant-ui/react-markdown": "0.14.14",
|
|
72
67
|
"@base-ui/react": "^1.8.0",
|
|
73
|
-
"@nadicodeai/design-system": "
|
|
68
|
+
"@nadicodeai/design-system": "7.1.0",
|
|
74
69
|
"@paper-design/shaders": "0.0.80",
|
|
75
70
|
"class-variance-authority": "^0.7.1",
|
|
76
71
|
"clsx": "^2.1.1",
|
|
@@ -101,6 +96,7 @@
|
|
|
101
96
|
"devDependencies": {
|
|
102
97
|
"@iconify-json/logos": "^1.2.14",
|
|
103
98
|
"@iconify/utils": "^3.1.7",
|
|
99
|
+
"@lobehub/icons-static-svg": "^1.95.0",
|
|
104
100
|
"@nadicodeai/eslint-plugin": "*",
|
|
105
101
|
"@stryker-mutator/core": "10.0.0",
|
|
106
102
|
"@stryker-mutator/vitest-runner": "10.0.0",
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/** True while a primitive that outranks the room owns the window's canvas. */
|
|
2
|
-
export declare function canvasHeld(): boolean;
|
|
3
|
-
/**
|
|
4
|
-
* Take the window's canvas. Returns the release, which is idempotent: a
|
|
5
|
-
* teardown that runs twice releases once.
|
|
6
|
-
*/
|
|
7
|
-
export declare function holdCanvas(): () => void;
|
|
8
|
-
/** Whether the room must yield its canvas right now. Live. */
|
|
9
|
-
export declare function useCanvasHeld(): boolean;
|
|
10
|
-
//# sourceMappingURL=canvas-budget.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"canvas-budget.d.ts","sourceRoot":"","sources":["../../../src/components/threshold/canvas-budget.ts"],"names":[],"mappings":"AA0BA,8EAA8E;AAC9E,wBAAgB,UAAU,IAAI,OAAO,CAEpC;AAED;;;GAGG;AACH,wBAAgB,UAAU,IAAI,MAAM,IAAI,CAWvC;AAWD,8DAA8D;AAC9D,wBAAgB,aAAa,IAAI,OAAO,CAEvC"}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { useSyncExternalStore } from "react";
|
|
3
|
-
/*
|
|
4
|
-
* One shader canvas per window: the living-elements register's rule, held
|
|
5
|
-
* here so no screen can break it by composition.
|
|
6
|
-
*
|
|
7
|
-
* Two threshold primitives can paint with a shader, the room and the arriving
|
|
8
|
-
* presence, and a threshold mounts both. The presence's water outranks the
|
|
9
|
-
* room: while Nadia is arriving her water is the window's one live canvas and
|
|
10
|
-
* the room shows its still field; when she stands clear the water leaves and
|
|
11
|
-
* the room's canvas is the one that paints. The presence takes a hold for as
|
|
12
|
-
* long as its water is wanted, and the room yields while any hold is out.
|
|
13
|
-
*
|
|
14
|
-
* The budget is per window, so it is module state rather than a context: a
|
|
15
|
-
* provider would let a surface forget to wrap both primitives, and the rule
|
|
16
|
-
* would then hold only where someone remembered it.
|
|
17
|
-
*/
|
|
18
|
-
let holds = 0;
|
|
19
|
-
const listeners = new Set();
|
|
20
|
-
const notify = () => {
|
|
21
|
-
for (const listener of listeners)
|
|
22
|
-
listener();
|
|
23
|
-
};
|
|
24
|
-
/** True while a primitive that outranks the room owns the window's canvas. */
|
|
25
|
-
export function canvasHeld() {
|
|
26
|
-
return holds > 0;
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Take the window's canvas. Returns the release, which is idempotent: a
|
|
30
|
-
* teardown that runs twice releases once.
|
|
31
|
-
*/
|
|
32
|
-
export function holdCanvas() {
|
|
33
|
-
holds += 1;
|
|
34
|
-
notify();
|
|
35
|
-
let released = false;
|
|
36
|
-
return () => {
|
|
37
|
-
if (released)
|
|
38
|
-
return;
|
|
39
|
-
released = true;
|
|
40
|
-
holds -= 1;
|
|
41
|
-
notify();
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
function subscribe(listener) {
|
|
45
|
-
listeners.add(listener);
|
|
46
|
-
return () => {
|
|
47
|
-
listeners.delete(listener);
|
|
48
|
-
};
|
|
49
|
-
}
|
|
50
|
-
const serverSnapshot = () => false;
|
|
51
|
-
/** Whether the room must yield its canvas right now. Live. */
|
|
52
|
-
export function useCanvasHeld() {
|
|
53
|
-
return useSyncExternalStore(subscribe, canvasHeld, serverSnapshot);
|
|
54
|
-
}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export { Orb, type OrbProps, type OrbSize, type OrbState, type OrbTheme } from "./orb.js";
|
|
2
|
-
export { arrivalOpacity, NadiaPresence, type NadiaPresenceProps, type PresenceState, } from "./presence.js";
|
|
3
|
-
export { ThresholdRoom, type ThresholdRoomProps } from "./room.js";
|
|
4
|
-
export { TypedLine, type TypedLineProps } from "./typed-line.js";
|
|
5
|
-
export { useReducedMotion } from "./use-reduced-motion.js";
|
|
6
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/threshold/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,KAAK,QAAQ,EAAE,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,OAAO,CAAC;AACvF,OAAO,EACL,cAAc,EACd,aAAa,EACb,KAAK,kBAAkB,EACvB,KAAK,aAAa,GACnB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,aAAa,EAAE,KAAK,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AAChE,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { type ActivityOrbSize } from "../activity-orb.js";
|
|
2
|
-
export type OrbState = "working" | "connecting" | "absent";
|
|
3
|
-
export type OrbTheme = "auto" | "dark" | "light";
|
|
4
|
-
export type OrbSize = ActivityOrbSize;
|
|
5
|
-
export type OrbProps = {
|
|
6
|
-
className?: string;
|
|
7
|
-
label: string;
|
|
8
|
-
size?: OrbSize;
|
|
9
|
-
state: OrbState;
|
|
10
|
-
theme?: OrbTheme;
|
|
11
|
-
};
|
|
12
|
-
/** Preserve the threshold API while using the one shared activity renderer. */
|
|
13
|
-
export declare function Orb({ className, label, size, state, theme }: OrbProps): import("react").JSX.Element | null;
|
|
14
|
-
//# sourceMappingURL=orb.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"orb.d.ts","sourceRoot":"","sources":["../../../src/components/threshold/orb.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAe,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAG9E,MAAM,MAAM,QAAQ,GAAG,SAAS,GAAG,YAAY,GAAG,QAAQ,CAAC;AAC3D,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AACjD,MAAM,MAAM,OAAO,GAAG,eAAe,CAAC;AAEtC,MAAM,MAAM,QAAQ,GAAG;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,EAAE,QAAQ,CAAC;IAChB,KAAK,CAAC,EAAE,QAAQ,CAAC;CAClB,CAAC;AAUF,+EAA+E;AAC/E,wBAAgB,GAAG,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,IAAS,EAAE,KAAK,EAAE,KAAc,EAAE,EAAE,QAAQ,sCAYnF"}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { ActivityOrb } from "../activity-orb.js";
|
|
4
|
-
import { cn } from "../../lib/utils.js";
|
|
5
|
-
// A threshold deliberately fixes its appearance. Normal product surfaces
|
|
6
|
-
// inherit their ink; the shared renderer needs no knowledge of app themes.
|
|
7
|
-
const THEME_INK = {
|
|
8
|
-
auto: "inherit",
|
|
9
|
-
dark: "var(--nc-dark-ink)",
|
|
10
|
-
light: "var(--nc-ink)",
|
|
11
|
-
};
|
|
12
|
-
/** Preserve the threshold API while using the one shared activity renderer. */
|
|
13
|
-
export function Orb({ className, label, size = 88, state, theme = "dark" }) {
|
|
14
|
-
if (state === "absent")
|
|
15
|
-
return null;
|
|
16
|
-
return (_jsx(ActivityOrb, { activity: state === "working" ? "processing" : "connecting", className: cn("nc-threshold-orb", className), "data-state": state, label: label, size: size, style: { color: THEME_INK[theme] } }));
|
|
17
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
export type PresenceState = "arriving" | "present" | "ready" | "error";
|
|
2
|
-
export type NadiaPresenceProps = {
|
|
3
|
-
className?: string;
|
|
4
|
-
/** The accessible name of the arrival, read by the hidden progress bar. */
|
|
5
|
-
label: string;
|
|
6
|
-
/** 0 while she is behind the field, 1 when she stands clear. */
|
|
7
|
-
reveal: number;
|
|
8
|
-
/**
|
|
9
|
-
* The portrait master. The package imports no binary asset of its own, so
|
|
10
|
-
* the consuming surface resolves `@nadicodeai/design-system/assets/nadia-solid-surface`
|
|
11
|
-
* with its own bundler and passes the URL here.
|
|
12
|
-
*/
|
|
13
|
-
src: string;
|
|
14
|
-
state: PresenceState;
|
|
15
|
-
};
|
|
16
|
-
/** The arrival curve: a trace at the start, not a face already half-built. */
|
|
17
|
-
export declare function arrivalOpacity(reveal: number): number;
|
|
18
|
-
/**
|
|
19
|
-
* Nadia at a threshold.
|
|
20
|
-
*
|
|
21
|
-
* `reveal` drives one opacity, so nothing re-renders per frame and the value
|
|
22
|
-
* a Customer sees is the value assistive technology is told. The lower edge
|
|
23
|
-
* dissolves into the room instead of ending on a rectangle. At `state="ready"`
|
|
24
|
-
* she takes one short verde vivo beat, her presence accent, and settles.
|
|
25
|
-
*/
|
|
26
|
-
export declare function NadiaPresence({ className, label, reveal, src, state }: NadiaPresenceProps): import("react").JSX.Element;
|
|
27
|
-
//# sourceMappingURL=presence.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"presence.d.ts","sourceRoot":"","sources":["../../../src/components/threshold/presence.tsx"],"names":[],"mappings":"AA+CA,MAAM,MAAM,aAAa,GAAG,UAAU,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;AAEvE,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,aAAa,CAAC;CACtB,CAAC;AAIF,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAErD;AAgBD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,kBAAkB,+BAsIzF"}
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { defaultObjectSizing, ShaderFitOptions, ShaderMount, waterFragmentShader, } from "@paper-design/shaders";
|
|
4
|
-
import { useEffect, useRef, useState } from "react";
|
|
5
|
-
import { resolveIdentityColor } from "../../internal/agent-visual/color.js";
|
|
6
|
-
import { cn } from "../../lib/utils.js";
|
|
7
|
-
import { holdCanvas } from "./canvas-budget.js";
|
|
8
|
-
import { useReducedMotion } from "./use-reduced-motion.js";
|
|
9
|
-
/*
|
|
10
|
-
* Nadia's presence: the portrait master itself, arriving through the room. No
|
|
11
|
-
* medallion, no ring, no bar. How much of her you can see is the progress.
|
|
12
|
-
*
|
|
13
|
-
* While she is arriving the portrait carries Paper Shaders' Water at a
|
|
14
|
-
* restrained intensity, so the field she is coming through moves across her
|
|
15
|
-
* without taking her geometry apart. Every amplitude is scaled by how much of
|
|
16
|
-
* the arrival is left, so the water is gone by the time she stands clear. At
|
|
17
|
-
* reveal 1, and in every other state, this is the plain portrait.
|
|
18
|
-
*
|
|
19
|
-
* The water is the window's one shader canvas while it is wanted. The presence
|
|
20
|
-
* holds the canvas budget from the moment the water is asked for until it is
|
|
21
|
-
* torn down, and the room keeps its still field for exactly that long
|
|
22
|
-
* (`canvas-budget.ts`).
|
|
23
|
-
*/
|
|
24
|
-
const ARRIVAL_FLOOR = 0.025;
|
|
25
|
-
const ARRIVAL_CURVE = 1.25;
|
|
26
|
-
const WATER_CAUSTIC = 0.35;
|
|
27
|
-
const WATER_EDGES = 0.4;
|
|
28
|
-
const WATER_WAVES = 0.3;
|
|
29
|
-
const WATER_LAYERING = 0.4;
|
|
30
|
-
const WATER_HIGHLIGHTS = 0.12;
|
|
31
|
-
const WATER_SIZE = 1;
|
|
32
|
-
const WATER_SPEED = 1;
|
|
33
|
-
const WATER_FRAME = 0;
|
|
34
|
-
const WATER_MIN_PIXEL_RATIO = 1;
|
|
35
|
-
const WATER_MAX_PIXEL_COUNT = 1_024_000;
|
|
36
|
-
const PRESENCE_FIELD_TOKEN = "--nc-identity-verde";
|
|
37
|
-
const TRANSPARENT = [0, 0, 0, 0];
|
|
38
|
-
const clampUnit = (value) => Math.min(Math.max(value, 0), 1);
|
|
39
|
-
/** The arrival curve: a trace at the start, not a face already half-built. */
|
|
40
|
-
export function arrivalOpacity(reveal) {
|
|
41
|
-
return ARRIVAL_FLOOR + (1 - ARRIVAL_FLOOR) * clampUnit(reveal) ** ARRIVAL_CURVE;
|
|
42
|
-
}
|
|
43
|
-
function waterUniforms(reveal, field) {
|
|
44
|
-
const remaining = 1 - clampUnit(reveal);
|
|
45
|
-
return {
|
|
46
|
-
u_caustic: WATER_CAUSTIC * remaining,
|
|
47
|
-
u_colorBack: TRANSPARENT,
|
|
48
|
-
u_colorHighlight: field,
|
|
49
|
-
u_edges: WATER_EDGES * remaining,
|
|
50
|
-
u_highlights: WATER_HIGHLIGHTS,
|
|
51
|
-
u_layering: WATER_LAYERING,
|
|
52
|
-
u_size: WATER_SIZE,
|
|
53
|
-
u_waves: WATER_WAVES * remaining,
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Nadia at a threshold.
|
|
58
|
-
*
|
|
59
|
-
* `reveal` drives one opacity, so nothing re-renders per frame and the value
|
|
60
|
-
* a Customer sees is the value assistive technology is told. The lower edge
|
|
61
|
-
* dissolves into the room instead of ending on a rectangle. At `state="ready"`
|
|
62
|
-
* she takes one short verde vivo beat, her presence accent, and settles.
|
|
63
|
-
*/
|
|
64
|
-
export function NadiaPresence({ className, label, reveal, src, state }) {
|
|
65
|
-
const still = useReducedMotion();
|
|
66
|
-
const hostRef = useRef(null);
|
|
67
|
-
const mountRef = useRef(null);
|
|
68
|
-
const [surface, setSurface] = useState("portrait");
|
|
69
|
-
const value = clampUnit(reveal);
|
|
70
|
-
const arriving = state === "arriving";
|
|
71
|
-
const wet = arriving && value < 1 && !still;
|
|
72
|
-
useEffect(() => {
|
|
73
|
-
const host = hostRef.current;
|
|
74
|
-
if (!host || !wet)
|
|
75
|
-
return;
|
|
76
|
-
const field = resolveIdentityColor(host, PRESENCE_FIELD_TOKEN);
|
|
77
|
-
if (!field)
|
|
78
|
-
return;
|
|
79
|
-
let cancelled = false;
|
|
80
|
-
let canvas = null;
|
|
81
|
-
// Taken before the image decodes: the room stays still while her water is
|
|
82
|
-
// on its way, not only once it paints.
|
|
83
|
-
const releaseCanvas = holdCanvas();
|
|
84
|
-
const clearCanvases = () => {
|
|
85
|
-
for (const element of host.querySelectorAll("canvas"))
|
|
86
|
-
element.remove();
|
|
87
|
-
host.removeAttribute("data-paper-shader");
|
|
88
|
-
};
|
|
89
|
-
const teardown = () => {
|
|
90
|
-
canvas?.removeEventListener("webglcontextlost", handleContextLost);
|
|
91
|
-
canvas = null;
|
|
92
|
-
try {
|
|
93
|
-
mountRef.current?.dispose();
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
// The plain portrait is what stays; vendor cleanup failing is not
|
|
97
|
-
// allowed to remove Nadia from the screen.
|
|
98
|
-
}
|
|
99
|
-
mountRef.current = null;
|
|
100
|
-
clearCanvases();
|
|
101
|
-
// After disposal, so the room's canvas is never alive beside this one.
|
|
102
|
-
releaseCanvas();
|
|
103
|
-
};
|
|
104
|
-
function handleContextLost(event) {
|
|
105
|
-
event.preventDefault();
|
|
106
|
-
teardown();
|
|
107
|
-
setSurface("portrait");
|
|
108
|
-
}
|
|
109
|
-
const image = new Image();
|
|
110
|
-
image.decoding = "async";
|
|
111
|
-
image.src = src;
|
|
112
|
-
const mount = async () => {
|
|
113
|
-
try {
|
|
114
|
-
await image.decode();
|
|
115
|
-
if (cancelled)
|
|
116
|
-
return;
|
|
117
|
-
mountRef.current = new ShaderMount(host, waterFragmentShader, {
|
|
118
|
-
...waterUniforms(value, field),
|
|
119
|
-
u_fit: ShaderFitOptions.contain,
|
|
120
|
-
u_image: image,
|
|
121
|
-
u_offsetX: defaultObjectSizing.offsetX,
|
|
122
|
-
u_offsetY: defaultObjectSizing.offsetY,
|
|
123
|
-
u_originX: defaultObjectSizing.originX,
|
|
124
|
-
u_originY: defaultObjectSizing.originY,
|
|
125
|
-
u_rotation: defaultObjectSizing.rotation,
|
|
126
|
-
u_scale: defaultObjectSizing.scale,
|
|
127
|
-
u_worldHeight: defaultObjectSizing.worldHeight,
|
|
128
|
-
u_worldWidth: defaultObjectSizing.worldWidth,
|
|
129
|
-
}, undefined, WATER_SPEED, WATER_FRAME, WATER_MIN_PIXEL_RATIO, WATER_MAX_PIXEL_COUNT, ["u_image"]);
|
|
130
|
-
canvas = mountRef.current.canvasElement;
|
|
131
|
-
canvas.addEventListener("webglcontextlost", handleContextLost);
|
|
132
|
-
setSurface("water");
|
|
133
|
-
}
|
|
134
|
-
catch {
|
|
135
|
-
// No WebGL, no palette, or an image that will not decode: she arrives
|
|
136
|
-
// as the plain portrait, which is the whole design at reveal 1 anyway.
|
|
137
|
-
teardown();
|
|
138
|
-
setSurface("portrait");
|
|
139
|
-
}
|
|
140
|
-
};
|
|
141
|
-
void mount();
|
|
142
|
-
return () => {
|
|
143
|
-
cancelled = true;
|
|
144
|
-
teardown();
|
|
145
|
-
setSurface("portrait");
|
|
146
|
-
};
|
|
147
|
-
}, [src, wet]);
|
|
148
|
-
useEffect(() => {
|
|
149
|
-
const host = hostRef.current;
|
|
150
|
-
const mount = mountRef.current;
|
|
151
|
-
if (!host || !mount)
|
|
152
|
-
return;
|
|
153
|
-
const field = resolveIdentityColor(host, PRESENCE_FIELD_TOKEN);
|
|
154
|
-
if (!field)
|
|
155
|
-
return;
|
|
156
|
-
mount.setUniforms(waterUniforms(value, field));
|
|
157
|
-
}, [value]);
|
|
158
|
-
return (_jsxs("div", { className: cn("nc-threshold-presence", className), "data-state": state, "data-still": still || undefined, style: { "--threshold-portrait": `url("${src}")` }, children: [_jsx("div", { "aria-hidden": "true", className: "nc-threshold-presence-figure", "data-surface": surface, ref: hostRef, style: { opacity: arriving ? arrivalOpacity(value) : 1 }, children: _jsx("img", { alt: "", className: "nc-threshold-presence-portrait", draggable: false, src: src }) }), _jsx("div", { "aria-label": label, "aria-valuemax": 100, "aria-valuemin": 0, "aria-valuenow": Math.floor(value * 100), className: "nc-threshold-sr", role: "progressbar" })] }));
|
|
159
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
export type ThresholdRoomProps = {
|
|
2
|
-
className?: string;
|
|
3
|
-
/** Repair and error states quiet the room instead of hiding it. */
|
|
4
|
-
dimmed?: boolean;
|
|
5
|
-
/**
|
|
6
|
-
* How much of the room's pace is running, 0 to 1. The captured recipe is
|
|
7
|
-
* energy 1; the first-run screen holds it there while Nadia is still
|
|
8
|
-
* arriving and drops it to 0 when she has, which stills the field. Sign-in,
|
|
9
|
-
* success and every repair state pass 0.
|
|
10
|
-
*/
|
|
11
|
-
energy?: number;
|
|
12
|
-
/** Success: the field's colour becomes verde vivo, Nadia's presence accent. */
|
|
13
|
-
ready?: boolean;
|
|
14
|
-
};
|
|
15
|
-
/**
|
|
16
|
-
* The threshold's background field.
|
|
17
|
-
*
|
|
18
|
-
* It paints absolutely inside its own relative, isolated parent and never
|
|
19
|
-
* against the viewport, so a Portal panel can hold one without covering the
|
|
20
|
-
* page. It takes no pointer events and carries no words.
|
|
21
|
-
*
|
|
22
|
-
* When WebGL is unavailable, when the design-system tokens are not in the
|
|
23
|
-
* cascade above it, or when the context is lost after mounting, the element
|
|
24
|
-
* keeps its still field: a token-built radial gradient that needs no GPU. The
|
|
25
|
-
* room is never blank.
|
|
26
|
-
*
|
|
27
|
-
* The window carries one shader canvas. While Nadia's water is painting her
|
|
28
|
-
* arrival, the room holds this same still field and mounts nothing; the
|
|
29
|
-
* moment she stands clear the water leaves and the room's canvas is the one
|
|
30
|
-
* that paints (`canvas-budget.ts`).
|
|
31
|
-
*/
|
|
32
|
-
export declare function ThresholdRoom({ className, dimmed, energy, ready, }: ThresholdRoomProps): import("react").JSX.Element;
|
|
33
|
-
//# sourceMappingURL=room.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"room.d.ts","sourceRoot":"","sources":["../../../src/components/threshold/room.tsx"],"names":[],"mappings":"AAwCA,MAAM,MAAM,kBAAkB,GAAG;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,mEAAmE;IACnE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+EAA+E;IAC/E,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAIF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,aAAa,CAAC,EAC5B,SAAS,EACT,MAAc,EACd,MAAU,EACV,KAAa,GACd,EAAE,kBAAkB,+BA+IpB"}
|