@nadicodeai/design-system 10.1.0 → 12.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 +1 -1
- package/DESIGN.md +381 -635
- package/GLOSSARY.md +22 -19
- package/README.md +40 -23
- package/data/reserved-agent-identifiers.json +1 -0
- package/dist/css/host.css +88 -102
- package/dist/css/index.css +154 -353
- package/dist/css/tokens.css +53 -100
- package/dist/tailwind/nadicode.tailwind.json +13 -33
- package/dist/tokens/nadicode.dtcg-resolved.json +1500 -2242
- package/dist/tokens/nadicode.dtcg.json +648 -1833
- package/package.json +6 -4
package/DESIGN.md
CHANGED
|
@@ -2,13 +2,11 @@
|
|
|
2
2
|
version: alpha
|
|
3
3
|
name: nadicode Design System
|
|
4
4
|
description: >-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
package token, CSS, and asset exports they consume.
|
|
5
|
+
nadicode's design system and its only design authority: the tokens and the
|
|
6
|
+
rules every component, website section, and product screen in the website,
|
|
7
|
+
the Portal, and the Nadia desktop app builds on.
|
|
9
8
|
|
|
10
9
|
colors:
|
|
11
|
-
on-primary: "#ffffff"
|
|
12
10
|
action: "#111111"
|
|
13
11
|
action-foreground: "#ffffff"
|
|
14
12
|
action-hover: "#2e2e2e"
|
|
@@ -21,16 +19,17 @@ colors:
|
|
|
21
19
|
canvas-soft-2: "#f5f5f5"
|
|
22
20
|
material-workspace: "#f7f7f7"
|
|
23
21
|
material-solid: "#ffffff"
|
|
22
|
+
material-resting: "#ffffff"
|
|
23
|
+
material-raised: "#ffffff"
|
|
24
|
+
material-floating: "#ffffff"
|
|
25
|
+
material-modal: "#ffffff"
|
|
24
26
|
line: "#e5e5e5"
|
|
25
27
|
input: "#757575"
|
|
26
|
-
seam: "#d4d4d4"
|
|
27
|
-
cross: "#999999"
|
|
28
28
|
focus-ring: "#111111"
|
|
29
29
|
scrim: "#111111"
|
|
30
30
|
link: "#00713d"
|
|
31
31
|
link-deep: "#00522c"
|
|
32
32
|
link-bg-soft: "#c6efd9"
|
|
33
|
-
accent: "#ff5a1f"
|
|
34
33
|
info: "#1e3cff"
|
|
35
34
|
info-soft: "#e8ecff"
|
|
36
35
|
info-deep: "#0e1d8a"
|
|
@@ -43,8 +42,6 @@ colors:
|
|
|
43
42
|
warning: "#ffc220"
|
|
44
43
|
warning-soft: "#fff3d1"
|
|
45
44
|
warning-deep: "#7a5800"
|
|
46
|
-
selection-bg: "#111111"
|
|
47
|
-
selection-fg: "#fafafa"
|
|
48
45
|
navigation-selection-bg: "#ededed"
|
|
49
46
|
navigation-selection-fg: "#111111"
|
|
50
47
|
navigation-selection-hover: "#e5e5e5"
|
|
@@ -68,30 +65,25 @@ colors:
|
|
|
68
65
|
chart-4: "#d3302f"
|
|
69
66
|
chart-5: "#0096a7"
|
|
70
67
|
chart-6: "#6f42c1"
|
|
71
|
-
chart-seq-1: "#7d99f2"
|
|
72
|
-
chart-seq-2: "#4f6ae6"
|
|
73
|
-
chart-seq-3: "#2743d8"
|
|
74
|
-
chart-seq-4: "#1d33ad"
|
|
75
|
-
chart-seq-5: "#16277f"
|
|
76
|
-
chart-div-warm: "#e0a600"
|
|
77
|
-
chart-div-mid: "#6b6b6b"
|
|
78
|
-
chart-div-cool: "#4d6bff"
|
|
79
68
|
dark-canvas: "#000000"
|
|
80
69
|
dark-canvas-soft: "#111111"
|
|
81
70
|
dark-canvas-soft-2: "#1a1a1a"
|
|
82
71
|
dark-material-workspace: "#000000"
|
|
83
72
|
dark-material-solid: "#000000"
|
|
73
|
+
dark-material-resting: "#0c0c0c"
|
|
74
|
+
dark-material-raised: "#121212"
|
|
75
|
+
dark-material-floating: "#181818"
|
|
76
|
+
dark-material-modal: "#1c1c1c"
|
|
84
77
|
dark-ink: "#ededed"
|
|
85
78
|
dark-body: "#c7c7c7"
|
|
86
79
|
dark-muted: "#8f8f8f"
|
|
87
80
|
dark-line: "#262626"
|
|
88
81
|
dark-input: "#696969"
|
|
89
|
-
dark-seam: "#1a1a1a"
|
|
90
82
|
dark-on-primary: "#0a0a0a"
|
|
91
|
-
dark-action: "#
|
|
83
|
+
dark-action: "#ededed"
|
|
92
84
|
dark-action-foreground: "#0a0a0a"
|
|
93
|
-
dark-action-hover: "#
|
|
94
|
-
dark-action-active: "#
|
|
85
|
+
dark-action-hover: "#d6d6d6"
|
|
86
|
+
dark-action-active: "#c7c7c7"
|
|
95
87
|
dark-link: "#4ecf8c"
|
|
96
88
|
dark-link-deep: "#93e3b8"
|
|
97
89
|
dark-link-bg-soft: "#00351c"
|
|
@@ -115,26 +107,15 @@ colors:
|
|
|
115
107
|
dark-state-blocked: "#d3302f"
|
|
116
108
|
dark-state-complete: "#00a85a"
|
|
117
109
|
dark-logo-dot: "#4d6bff"
|
|
118
|
-
dark-selection-bg: "#
|
|
119
|
-
dark-selection-fg: "#0a0a0a"
|
|
120
|
-
dark-navigation-selection-bg: "#1a1a1a"
|
|
110
|
+
dark-navigation-selection-bg: "#1f1f1f"
|
|
121
111
|
dark-navigation-selection-fg: "#ededed"
|
|
122
112
|
dark-navigation-selection-hover: "#262626"
|
|
123
|
-
dark-cross: "#555555"
|
|
124
113
|
dark-chart-1: "#4d6bff"
|
|
125
114
|
dark-chart-2: "#00a85a"
|
|
126
115
|
dark-chart-3: "#ffd866"
|
|
127
116
|
dark-chart-4: "#e8615f"
|
|
128
117
|
dark-chart-5: "#33d1cb"
|
|
129
118
|
dark-chart-6: "#9575cd"
|
|
130
|
-
dark-chart-seq-1: "#4f6ae6"
|
|
131
|
-
dark-chart-seq-2: "#7d99f2"
|
|
132
|
-
dark-chart-seq-3: "#a9befa"
|
|
133
|
-
dark-chart-seq-4: "#cdd9fd"
|
|
134
|
-
dark-chart-seq-5: "#eef2ff"
|
|
135
|
-
dark-chart-div-warm: "#ffd866"
|
|
136
|
-
dark-chart-div-mid: "#e5e5e5"
|
|
137
|
-
dark-chart-div-cool: "#b3c1ff"
|
|
138
119
|
|
|
139
120
|
typography:
|
|
140
121
|
display-xl:
|
|
@@ -219,8 +200,6 @@ rounded:
|
|
|
219
200
|
xl: 10px
|
|
220
201
|
2xl: 12px
|
|
221
202
|
3xl: 16px
|
|
222
|
-
pill-sm: 64px
|
|
223
|
-
pill: 100px
|
|
224
203
|
full: 9999px
|
|
225
204
|
|
|
226
205
|
spacing:
|
|
@@ -248,489 +227,343 @@ spacing:
|
|
|
248
227
|
|
|
249
228
|
## Overview
|
|
250
229
|
|
|
251
|
-
|
|
230
|
+
This file is nadicode's design system and its only design authority. The tokens above are the values; the sections below are the rules for applying them. Every component in `@nadicodeai/ui`, every website section, and every screen in the Portal and in Nadia builds on it. Another document may say how one product applies a rule, and links here for the rule; it never restates one. A rule that is missing here is added here first.
|
|
252
231
|
|
|
253
|
-
|
|
232
|
+
nadicode puts AI Agents to work inside small and mid-sized companies. Its sites and products read as a technology company with a real product: the interface is neutral and exact, the product is shown working, and colour and depth give the page its energy. Three ideas carry every screen:
|
|
254
233
|
|
|
255
|
-
|
|
234
|
+
- **Neutral structure.** Chrome, controls and text sit on an achromatic spine, so a desaturated screen loses nothing structural. The strongest contrast on a screen belongs to the action.
|
|
235
|
+
- **Colour as stage.** The five identity hues are full-strength grounds for sections, stages behind product views and glass, and the colour of Agents. They carry the brand; they never decorate a control.
|
|
236
|
+
- **Real depth.** Surfaces sit at four heights. In light, shadow shows the height; in dark, tone shows it. Glass floats over colour and moving Agents.
|
|
256
237
|
|
|
257
|
-
|
|
238
|
+
The brand is still: only Agents move on their own ("Motion").
|
|
258
239
|
|
|
259
|
-
|
|
240
|
+
One foundation serves three registers. The foundation is every value in this file. A register is one product's way of applying it: the website, the Portal and the Nadia desktop app ("Registers"). A register adds rules and chooses which values it uses; it never redefines a value or mints one. A value one register needs is authored here under `register.<name>`, and a value two registers share moves to `core`.
|
|
260
241
|
|
|
261
242
|
## Colors
|
|
262
243
|
|
|
263
|
-
|
|
244
|
+
### Neutral structure
|
|
264
245
|
|
|
265
|
-
|
|
246
|
+
The neutral spine carries grounds, chrome, text and every control: `{colors.ink}`, `{colors.body}`, `{colors.muted}`, `{colors.canvas}`, `{colors.canvas-soft}`, `{colors.canvas-soft-2}`, `{colors.line}`, the material tones, and their dark pairs. Every neutral is an equal-channel grey (OKLCH chroma 0) in both themes, so no tint paints a surface. Colours are authored here as sRGB hex and emitted as `oklch()`.
|
|
266
247
|
|
|
267
|
-
|
|
248
|
+
- **Action** (`{colors.action}`, `{colors.action-foreground}`, `{colors.action-hover}`, `{colors.action-active}`): the primary control is ink with a white label in light, and dark ink `{colors.dark-action}` with a near-black `{colors.dark-action-foreground}` label in dark, never pure white. The shadcn `primary` role maps here.
|
|
249
|
+
- **Text**: `{colors.ink}` for headings and primary text, `{colors.body}` for supporting text, which clears AA on every canvas. `{colors.muted}` is for placeholders and disabled or inactive text only, never for descriptions, labels or help.
|
|
250
|
+
- **Lines**: `{colors.line}` is the one decorative 1 px divider and is deliberately quiet. `{colors.input}` is the boundary of a field or any control whose edge identifies it (an input, a select, a checkbox, a radio), and clears the 3:1 non-text floor. A button is identified by its label, so an outline or secondary button's edge is the `{colors.line}` hairline, never `{colors.input}`. Never darken a decorative line locally to make it act as a control edge.
|
|
251
|
+
- **Focus** (`{colors.focus-ring}`): a full-opacity outline of `{spacing.focus-outline-width}` at `{spacing.focus-outline-offset}`, the maximum-contrast neutral on each ground, never a box-shadow ring and never a hue.
|
|
252
|
+
- **Interaction states** are contrast steps on the spine. Hover takes the `accent` role (`{colors.canvas-soft-2}`) and a selected row the `muted` role (`{colors.canvas-soft}`); a control's own hover is a step inside its family, as `{colors.action-hover}` is. Persistent navigation marks the current destination with `{colors.navigation-selection-bg}` and `{colors.navigation-selection-fg}`, and local tabs mark the current view with the same pair through the `selection` role.
|
|
253
|
+
- **Scrim** (`{colors.scrim}`): the modal overlay at the `scrim-opacity` material value, never blurred.
|
|
268
254
|
|
|
269
|
-
|
|
270
|
-
reveals a visible, full-width neutral fill. Use the full soft-canvas step
|
|
271
|
-
(`bg-accent` in React), without reducing its opacity. The same row treatment
|
|
272
|
-
accompanies keyboard focus inside a row and an open row menu; keep the focused
|
|
273
|
-
control's own outline. Header and footer rows remain stable. Preserve text,
|
|
274
|
-
status, and selection meaning, with no layout shift.
|
|
255
|
+
### Colour as stage
|
|
275
256
|
|
|
276
|
-
|
|
277
|
-
Disabled controls do not advertise an available action. Row highlighting also
|
|
278
|
-
helps scan read-only data: only give the whole row a pointer cursor when it has
|
|
279
|
-
an actual row action. Touch access must not depend on hover. Shared React
|
|
280
|
-
behavior belongs to `TableRow` and the UI stylesheet, consumed by all product
|
|
281
|
-
adapters. Verify light and dark themes, keyboard focus, menu opening and closing,
|
|
282
|
-
and compact widths in the rendered product.
|
|
257
|
+
The identity palette is the brand's colour: cobalto `{colors.identity-cobalto}`, verde `{colors.identity-verde}`, arancio `{colors.identity-arancio}`, giallo `{colors.identity-giallo}` and rosso `{colors.identity-rosso}`, with `{colors.identity-ink}` and `{colors.identity-white}` as the poles type sits on. Azzurro `{colors.identity-azzurro}` has one job: the first stripe of Nadia's ribbon.
|
|
283
258
|
|
|
284
|
-
|
|
259
|
+
The hues appear at full strength and flat, with no tints, steps, gradients or hover variants, in these places:
|
|
285
260
|
|
|
286
|
-
|
|
261
|
+
- **Section grounds on the website.** A section may take one identity hue as its whole ground. Colour sections alternate with white, soft and black ones, and two neighbouring sections never share a ground.
|
|
262
|
+
- **Stages.** The ground behind a floating product view, and the colour a glass surface shows through ("Elevation & Depth").
|
|
263
|
+
- **Accents on the website.** A 2 px rule that marks one item of a set, one hue per item.
|
|
264
|
+
- **Agents.** The orbs ("Agent orbs").
|
|
265
|
+
- **The logo.** The dot of the wordmark and the icon tile ("Brand media authority").
|
|
266
|
+
- **Imagery.** Art direction reads these tokens and keeps no private palette.
|
|
287
267
|
|
|
288
|
-
|
|
289
|
-
- **Canvas / Line / Input / Cross / Seam** (`{colors.canvas}`, `{colors.line}`, `{colors.input}`, `{colors.cross}`, `{colors.seam}`): white content cells, the universal decorative 1 px divider, the resting-control boundary, the stronger mark/crosshair gray, and the dashed connector guide. `{colors.line}` owns ordinary seams and is deliberately quiet — a decorative hairline, not held to a non-text contrast floor. `{colors.input}` owns fields and other interactive boundaries and clears the 3:1 non-text floor against its canvas. A labeled filter or search control already identified by readable text and its icon may use the quiet line role for an optional perimeter, configured by its composition owner. Form fields whose boundary identifies the input keep the input role. Focus remains a separate full-contrast state. Never darken a decorative border locally to make it behave like a control.
|
|
290
|
-
- **Body / Muted** (`{colors.body}`, `{colors.muted}`): `{colors.body}` is readable supporting copy and clears AA on canvas; `{colors.muted}` is restricted to disabled, inactive, and placeholder content. Do not use muted text for ordinary descriptions, labels, or help text.
|
|
291
|
-
- **Focus ring** (`{colors.focus-ring}` / `{colors.dark-focus-ring}`): the ink keyboard-focus indicator, a full-opacity `outline` of `{spacing.focus-outline-width}` at `{spacing.focus-outline-offset}` offset, never a box-shadow. Focus is a state of material, so the indicator is the maximum-contrast neutral on each ground rather than a hue; the offset keeps it independent of the resting border, and it clears the 3:1 non-text floor against the adjacent canvas.
|
|
292
|
-
- **Scrim** (`{colors.scrim}` / `{colors.dark-scrim}`): the semantic modal-overlay foreground. Its opacity is the generated material token declared in `Elevation & Depth`; it is never combined with backdrop blur.
|
|
293
|
-
- **Link** (`{colors.link}`, `{colors.link-deep}`, `{colors.link-bg-soft}`): inline links, navigational accents, approved/example proof states, and the green entity-status tone. Green, keeping the Italian signal without turning the interface into flag decoration; `link` and `link-deep` clear AA as text on canvas, and `link-bg-soft` is the tinted green ground for link and tag chrome only: a message or conversation plane is a neutral surface, never a link-tinted one.
|
|
294
|
-
- **Navigation selection** (`{colors.navigation-selection-bg}`, `{colors.navigation-selection-fg}`, `{colors.navigation-selection-hover}`): the selected destination in persistent product navigation, a neutral selection field. Selection is a place, not a meaning, so it holds by contrast on the neutral spine; cobalto belongs to the semantic info and running roles only. The dark counterparts are tuned independently for the black console. Resting and hover pairs clear AA for text, and the offset focus outline stays visible against both the selection fill and the adjacent canvas.
|
|
295
|
-
- **Semantic feedback** (`{colors.success}` = verde with `{colors.success-soft}`/`{colors.success-deep}`; `{colors.error}` = rosso funzionale `#d3302f` with `{colors.error-soft}`/`{colors.error-deep}`; `{colors.warning}` = giallo `#ffc220` with `{colors.warning-soft}`/`{colors.warning-deep}`; `{colors.info}` = cobalto `#1e3cff` with `{colors.info-soft}`/`{colors.info-deep}`): validation, caution, approval, and operational feedback. In each pair the `-soft` value is the tinted tag/banner ground and the `-deep` value is the AA-clearing text on it. On a solid `error` fill (the shadcn `destructive` role) text is white; on a solid `warning` or `success` fill text is ink. The success pair intentionally reuses the link pair's AA-safe verde values; its feedback meaning remains distinct from inline navigation and entity-status semantics.
|
|
296
|
-
- **Accent** (`{colors.accent}` = arancio `#ff5a1f`): a small emphasis mark for exception flags and highlights; it appears as a mark, carries no text, and paints no fill or background. The identically-named shadcn surface role is a different thing — a neutral (`{colors.canvas-soft-2}` light, `{colors.dark-canvas-soft-2}` dark) with no hue.
|
|
297
|
-
- **Workflow States** (`{colors.state-ready}` neutral, `{colors.state-running}` cobalto, `{colors.state-review}` giallo, `{colors.state-blocked}` rosso, `{colors.state-complete}` verde): the left-border/state vocabulary for agentic work surfaces — cobalto means in motion, verde means done. The dark counterparts (`{colors.dark-state-ready}`, `{colors.dark-state-running}`, `{colors.dark-state-review}`, `{colors.dark-state-blocked}`, `{colors.dark-state-complete}`) keep the same functional identity. These colours are carried by non-text state accents only — meter fills, delta marks, and the brand book's own rails; label text stays on the neutral foreground roles for contrast in both themes. Status is not one of their jobs: every status the product renders is the Badge status vocabulary (see `## Status Vocabulary`), which reads from the semantic feedback and link pairs, and the two token sets never cross.
|
|
268
|
+
Text on a colour ground is the pole that clears AA on that hue: `{colors.identity-white}` on cobalto and rosso; `{colors.identity-ink}` on verde, arancio and giallo. A control on a colour ground uses the button's on-colour variants ("Components"). Product chrome in the Portal and Nadia stays neutral: the identity hues enter a product screen only as Agents, charts and the feedback roles below.
|
|
298
269
|
|
|
299
|
-
###
|
|
270
|
+
### Meaning
|
|
300
271
|
|
|
301
|
-
- **
|
|
302
|
-
- **
|
|
272
|
+
- **Feedback** pairs a hue with a soft ground and a deep text step: success (verde), warning (giallo `{colors.warning}`), error (rosso `{colors.error}`), info (cobalto `{colors.info}`). The `-soft` value is the tinted ground of a tag or banner and the `-deep` value is the AA text on it; a component reaches the pair through the `destructive-soft`, `warning-soft`, `success-soft` and `info-soft` roles and their foregrounds. On a solid fill, error takes white text and warning and success take ink in light; in dark all three take `{colors.dark-on-primary}`.
|
|
273
|
+
- **Link** (`{colors.link}`, `{colors.link-deep}`, `{colors.link-bg-soft}`): inline links and the green status tone. Text green is always the link green; verde as a mark is always identity verde.
|
|
274
|
+
- **Workflow states** (`{colors.state-ready}`, `{colors.state-running}`, `{colors.state-review}`, `{colors.state-blocked}`, `{colors.state-complete}`) colour non-text marks only: meter fills and trend icons. Cobalto means in motion and verde means done. A state's word stays on the neutral text roles.
|
|
275
|
+
- **Status** is always a Badge tone with a mark and a word ("Components").
|
|
303
276
|
|
|
304
|
-
###
|
|
277
|
+
### Dark mode
|
|
305
278
|
|
|
306
|
-
|
|
279
|
+
Every token `X` with a `dark-X` pair takes the dark value under a `.dark` ancestor; a component uses the same role in both modes and never ships a `dark:` utility or a mode branch of its own. The dark page is true black, `{colors.dark-canvas}`. Elevated surfaces lift from it by tone ("Elevation & Depth"); the soft steps `{colors.dark-canvas-soft}` and `{colors.dark-canvas-soft-2}` are interaction fills and never become a page, section or panel ground. On a lifted surface the hover and muted fills step up from that surface's own tone. Dark feedback and chart values are brightened so they read on black; `{colors.dark-input}` stays brighter than `{colors.dark-line}` so a field's edge clears 3:1. A surface that is dark by nature, such as Nadia's entry screen, a terminal console, a black website section or a black brand plane inside a product (`BrandField`), stays dark in every theme as one fixed treatment: it scopes `.dark`, so every role and control inside it takes its dark value.
|
|
307
280
|
|
|
308
|
-
###
|
|
281
|
+
### Roles
|
|
309
282
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
This achromatic-neutral discipline is not dark-specific: it governs the generated shadcn surface-role bridge in both the light `:root` and dark `.dark` maps. The neutral surface family — `background`, `card`, `popover`, `muted`, `secondary`, `sidebar`, `sidebar-accent`, their neutral foreground/border variants, plus `border` and `input` — resolves only to exact equal-channel grays (OKLCH chroma 0) in both themes. Hue in the role bridge is reserved for the explicit semantic roles `destructive`, `success`, and `warning`; `primary` resolves to the neutral action family. The focus rings (`ring`/`sidebar-ring`) and the `sidebar-primary*` navigation-selection family resolve to equal-channel grays like the surface family: focus and selection are material states, and no interaction state carries a hue. In dark product shells, `background`, `card`, `popover`, and `sidebar` share `{colors.dark-canvas}` so page chrome, cards, tables, overlays, and nav rails read as one console. `muted`, `secondary`, and `accent` retain the `dark-canvas-soft` steps for small interaction states such as hover, selected rows, keycaps, and inline controls. Large product surfaces never take a tinted or soft-panel fill by default.
|
|
283
|
+
The shared colour roles are authored once, in the token extension's `semantic` group: each role (`background`, `card`, `popover`, `primary`, `muted`, `accent`, `border`, `input`, `ring`, `sidebar`, the feedback roles `destructive`, `warning`, `success` and `info` with their `-soft` pairs, the chart roles, and the rest) names one core token per mode. The Portal, the website and `@nadicodeai/ui` paint with these roles; Nadia's theme translates Hermes's role names onto the same ones and adds the shared roles Hermes has no name for. A product needing a role that does not exist adds it here; it never picks a colour locally.
|
|
313
284
|
|
|
314
285
|
## Data Visualization
|
|
315
286
|
|
|
316
287
|
### Chart source
|
|
317
288
|
|
|
318
|
-
Every chart and KPI card is a stock shadcn component
|
|
319
|
-
|
|
320
|
-
These are the deviations from what the registry ships. The list is complete: a change to a pulled chart that is not here is drift, and `packages/ui/tests/guards/chart-kit.test.ts` holds each one.
|
|
321
|
-
|
|
322
|
-
1. **Value formatting.** `valueFormatter` prints the exact figure in the tooltip, in the units the surface uses. `axisFormatter` prints the axis as a scale (`1.6B`, `800M`, `$7.5`), because an axis is read for magnitude and a full figure on every tick crowds it.
|
|
323
|
-
2. **Color by entity.** A series takes its color from the entity-color map below, never from its position in the series array. See "Entity-color assignment".
|
|
324
|
-
3. **Value axis.** Always drawn, with five ticks handed to the axis on a 1, 2, 2.5, 5, or 10 step. Left to itself Recharts chooses its own count, and a rounded label then sits on an unrounded position.
|
|
325
|
-
4. **Bar width.** Bars cap at 48px. Stock has no cap, and one category drew a bar the width of the card.
|
|
326
|
-
5. **Reading aids.** The grid is dashed in both directions. The tooltip uses the stock line indicator, with a gap between a row's name and its value so a long name cannot run into the figure. Every multi-series chart has a legend below the plot, left-aligned, a round dot and a name, in the order of the series it names; the stock area and grouped-bar blocks ship without one, and Recharts sorts legend items by key.
|
|
327
|
-
6. **Share of a total.** `chart-bar-stacked` has a `share` layout: one bar split to 100% (`stackOffset="expand"`), for a breakdown with no time axis.
|
|
328
|
-
7. **A chart is a named section.** The card title is a heading at the level the page gives it, and it names the plot through `aria-labelledby`. The plot shows a focus ring while it has keyboard focus: stock hides the outline of the svg that Recharts' `accessibilityLayer` makes focusable.
|
|
329
|
-
8. **Signed category comparisons.** `chart-bar-horizontal` starts from the shadcn block of that name. Its numeric axis retains negative values and a visible zero reference so Customer losses and gains share one baseline; an all-positive or all-negative reading uses only its occupied side. Native positive and negative series give the stock legend semantic gain/loss colors. The stock tooltip's formatter slot preserves the original decimal reading and full category label when the axis abbreviates it. The compact ranking owner remains available for ordinary entity comparisons.
|
|
330
|
-
|
|
331
|
-
Two things beside the charts also differ from stock. `Progress` takes a `tone` (default, warning, critical) and paints over-cap as a full critical bar. The pulled charts share one card frame, `chart-card`, in place of each file repeating it.
|
|
332
|
-
|
|
333
|
-
Everything else is the registry's: bar geometry and corner radius, tooltip and legend content, animation, and the recharts `accessibilityLayer` as shadcn ships it. This contract mandates no custom mark geometry, no segment gap, and no hidden table twin of a chart.
|
|
289
|
+
Every chart and KPI card is a stock shadcn registry component owned by `@nadicodeai/ui`; a page composes them and never assembles a chart card or reaches into a chart's internals. The package keeps a short, tested list of deliberate deviations from the registry. The visual ones are rules:
|
|
334
290
|
|
|
335
|
-
|
|
291
|
+
- The tooltip prints the exact figure in the surface's units; the axis prints a scale (`1.6B`, `800M`).
|
|
292
|
+
- The value axis is always drawn, with five ticks on a 1, 2, 2.5, 5 or 10 step.
|
|
293
|
+
- Bars are capped in width, the grid is dashed in both directions, and plot heights are fixed.
|
|
294
|
+
- A multi-series chart has a legend below the plot, left-aligned and wrapping: a round dot and the entity name, in series order.
|
|
295
|
+
- A signed comparison keeps a visible zero line so losses and gains share one baseline, and colours each bar by its consequence: `success` where the value is favourable, `destructive` where it is not.
|
|
296
|
+
- A chart is a named section: its card title is the heading that names the plot.
|
|
336
297
|
|
|
337
|
-
|
|
298
|
+
No pie, donut, radar, radial or dual-axis chart, and no mark or logo inside a legend.
|
|
338
299
|
|
|
339
|
-
###
|
|
300
|
+
### Palette and assignment
|
|
340
301
|
|
|
341
|
-
|
|
342
|
-
- **Diverging pair** (`{colors.chart-div-warm}` giallo/amber, `{colors.chart-div-mid}` a neutral mid, `{colors.chart-div-cool}` cobalto; dark counterparts `{colors.dark-chart-div-warm}`, `{colors.dark-chart-div-mid}`, `{colors.dark-chart-div-cool}` remapped in the `.dark` scope the same way): the warm and cool poles carry the sign, the neutral midpoint never carries hue. Meter tracks use a lighter step of the same ramp as their fill.
|
|
302
|
+
Six categorical series colour data and nothing else: `{colors.chart-1}` cobalto through `{colors.chart-6}` viola. Same-family neighbours (chart-3 and chart-4, chart-2 and chart-5) are held apart in normal and deuteranopia-simulated vision by the build. The light chart-3 sits below 3:1 on white, so a chart relies on its always-on legend and never on hue alone. Dark steps (`{colors.dark-chart-1}` and the rest) clear 3:1 on black.
|
|
343
303
|
|
|
344
|
-
|
|
304
|
+
A hue follows its entity, never its rank or position: one assignment per surface, the same in a chart and its table, stable under filtering and sorting. Past six entities the tail folds into one neutral "other" in `{colors.muted}`.
|
|
345
305
|
|
|
346
|
-
|
|
347
|
-
one `tone` variant: default, warning, or critical. There is no second meter
|
|
348
|
-
geometry, no cap tick, and no separate overflow segment. A value over its cap
|
|
349
|
-
reads as a full bar in the critical tone beside the printed figure, which is
|
|
350
|
-
what states the overage.
|
|
306
|
+
### Meters, deltas and labels
|
|
351
307
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
the chosen scope; a ratio reaching its end does not independently establish the
|
|
356
|
-
state of another capability. The foundation does not choose a product's color
|
|
357
|
-
entry screens or reinterpret a recorded balance as an access guarantee.
|
|
308
|
+
- **Meter**: the stock `Progress` in one tone, default, warning or critical, with an accessible name and the real value printed beside it. Over its cap it is a full bar in the critical tone; the printed figure states the overage.
|
|
309
|
+
- **Delta**: an outline Badge with a trend icon (`TrendingUp`, `TrendingDown`, `Minus`) and the signed value, followed by a muted "vs {period}". Direction crossed with whether up is good sets the tone; a neutral direction stays neutral. Never bare signed text, colour alone, or a ▲▼ glyph. A figure with no comparison shows no badge.
|
|
310
|
+
- **Labels**: legend, axis and label text are ink roles and never wear the series colour. A single series is titled, not legended. Tooltips enhance a chart and never hold the only copy of a value; where a page needs the numbers as text, it shows a real table built from the same data.
|
|
358
311
|
|
|
359
|
-
|
|
360
|
-
values bounded to the meter maximum. Color reinforces its defined meaning; it
|
|
361
|
-
is never the only explanation.
|
|
312
|
+
## Typography
|
|
362
313
|
|
|
363
|
-
|
|
314
|
+
Geist and Geist Mono are the two families. The twelve tokens are named by role, never by size; strong is a weight, so a 500 run of body copy is `body-md` at weight 500. `code` and `label-mono` are Geist Mono; every other token is Geist.
|
|
364
315
|
|
|
365
|
-
|
|
316
|
+
| Token | Use |
|
|
317
|
+
| --- | --- |
|
|
318
|
+
| `{typography.display-xl}` | The largest page title. |
|
|
319
|
+
| `{typography.display-lg}` | A hero headline and the one statement of a page. |
|
|
320
|
+
| `{typography.display-md}` | A section heading. |
|
|
321
|
+
| `{typography.heading-lg}` | A standard section title inside a page or panel. |
|
|
322
|
+
| `{typography.heading-md}` | A card, artifact or panel title. |
|
|
323
|
+
| `{typography.heading-sm}` | The smallest heading, above a group or a step. |
|
|
324
|
+
| `{typography.body-lg}` | A lead paragraph. |
|
|
325
|
+
| `{typography.body-md}` | Default body copy; large control labels at 500. |
|
|
326
|
+
| `{typography.body-sm}` | Secondary copy, navigation, compact cells; control labels at 500. |
|
|
327
|
+
| `{typography.caption}` | Badges, compact chrome labels, footer lines. |
|
|
328
|
+
| `{typography.code}` | Tool calls, snippets, traces. |
|
|
329
|
+
| `{typography.label-mono}` | Opaque identifiers, codes and mono eyebrows; never a person's, Agent's or computer's display name. |
|
|
330
|
+
|
|
331
|
+
A token has one size at every width; nothing is sized in `vw` or `vh`. Only the three display tokens carry negative tracking. Card, panel and section titles are sentence-case Geist. The website and the CSS components name the tokens through the `nc-type-*` classes; product UI in `@nadicodeai/ui`, the Portal and Nadia sizes text with Tailwind's `text-xs`, `text-sm` and `text-base`, which match `caption`, `body-sm` and `body-md` in size.
|
|
366
332
|
|
|
367
|
-
|
|
333
|
+
## Layout
|
|
368
334
|
|
|
369
|
-
|
|
370
|
-
- **Accessible alternative**: the chart's accessibility comes from the stock `accessibilityLayer`. A chart ships no visually hidden table twin of itself. Where a page needs the numbers as text, it shows a real table or list that the reader can see, built from the same result the chart reads.
|
|
335
|
+
Every gap, pad and stack step a page, section or composition sets is a rung of the spacing ladder, `{spacing.xxs}` 4 px through `{spacing.6xl}` 128 px; a layout picks the rungs it needs and never interpolates between them. Inside a component, the stock shadcn registry's own spacing stands. Every visible line has one owner: a seam is `{spacing.guide}` in `{colors.line}`, a control edge is `{colors.input}`, and focus is the focus outline.
|
|
371
336
|
|
|
372
|
-
|
|
337
|
+
Control geometry is the same in every product:
|
|
373
338
|
|
|
374
|
-
|
|
339
|
+
| Token | Owns |
|
|
340
|
+
| --- | --- |
|
|
341
|
+
| `{spacing.control-height}` | The height of every control in product chrome |
|
|
342
|
+
| `{spacing.control-padding-inline}` | A control's inline padding |
|
|
343
|
+
| `{spacing.touch-target}` | The minimum target on coarse or non-hover input, whatever the visual size |
|
|
344
|
+
| `{spacing.icon-stroke}`, `{spacing.icon-stroke-sm}` | The icon line ("Iconography") |
|
|
345
|
+
| `{spacing.focus-outline-width}`, `{spacing.focus-outline-offset}` | The focus outline |
|
|
375
346
|
|
|
376
|
-
|
|
347
|
+
Product chrome uses the 32 px control. A page header, a toolbar, a card's header and footer, a tab row, a filter row and a collection row's actions take `{spacing.control-height}` with a `{typography.body-sm}` label at 500; a button's `sm` and `xs` sizes are for a control nested inside dense content, such as a composer or an input group, and never for chrome.
|
|
377
348
|
|
|
378
|
-
|
|
349
|
+
### The website column
|
|
379
350
|
|
|
380
|
-
|
|
381
|
-
| --------------------------- | ---- | ------ | ----------- | ---------- | ---------------------------------------------------------------------- |
|
|
382
|
-
| `{typography.display-xl}` | 72px | 600 | 74px | `-2.52px` | Hero headline at the widest breakpoint. |
|
|
383
|
-
| `{typography.display-lg}` | 52px | 600 | 54px | `-1.5px` | Compact hero and major landing headlines. |
|
|
384
|
-
| `{typography.display-md}` | 40px | 600 | 42px | `-1px` | Major section headings and the smallest display line. |
|
|
385
|
-
| `{typography.heading-lg}` | 30px | 600 | 32px | `0px` | Standard section title. |
|
|
386
|
-
| `{typography.heading-md}` | 24px | 600 | 32px | `0px` | Cell, artifact and panel titles; action bands and plan names. |
|
|
387
|
-
| `{typography.heading-sm}` | 20px | 600 | 28px | `0px` | The smallest heading, inline above a group. |
|
|
388
|
-
| `{typography.body-lg}` | 18px | 400 | 27px | `0px` | Hero and lead paragraphs. |
|
|
389
|
-
| `{typography.body-md}` | 16px | 400 | 25px | `0px` | Default body copy, and large control labels at weight 500. |
|
|
390
|
-
| `{typography.body-sm}` | 14px | 400 | 20px | `0px` | Secondary copy, nav, compact cell copy, control labels at weight 500. |
|
|
391
|
-
| `{typography.caption}` | 12px | 400 | 16px | `0px` | Footer secondary lines, badge labels, compact chrome labels. |
|
|
392
|
-
| `{typography.code}` | 13px | 400 | 20px | `0px` | Tool calls, snippets, traces. Geist Mono. |
|
|
393
|
-
| `{typography.label-mono}` | 12px | 500 | 16px | `0px` | Mono eyebrows, identifiers and low-priority metadata. Geist Mono. |
|
|
351
|
+
A website page is a sequence of full-bleed sections, each centring its content in one column, `.nc-page`: `content-max` 1200 px wide with a side gutter of 20 px below 640 px, 40 px from 640 px and 64 px from 1024 px. From 1024 px the column's two edges are drawn as 1 px rails in `line` through every section, and each section closes with a 1 px line across the full width, so the page reads on one visible grid. Sections sit one step apart, 64 px below 1024 px and 96 px from it (`.nc-section-step`); a full-bleed picture that closes a section takes the step on its leading edge only (`.nc-section-step-lead`). Inside the column a section composes a heading, a split, a bento, a row of cards, a step rail or a logo row, and a composition may change its column count at 768 px. Nothing else sets a page width, and the page never scrolls sideways: horizontal overflow is clipped at the root, never on an inner surface.
|
|
394
352
|
|
|
395
|
-
|
|
353
|
+
These values are the `register.website.layout` tokens; the breakpoints live in `foundation.css`, which collapses them into `--nc-gutter` and `--nc-section-step`.
|
|
396
354
|
|
|
397
|
-
|
|
355
|
+
### Product shells
|
|
398
356
|
|
|
399
|
-
The
|
|
357
|
+
The Portal and Nadia are product shells: persistent navigation on the left, the selected destination on the right, on the workspace ground ("Elevation & Depth"). They use the spacing ladder and the control geometry, not the website's column.
|
|
400
358
|
|
|
401
|
-
|
|
359
|
+
- **Navigation names stable destinations.** An object opened from search or a link still shows its parent in a breadcrumb; local tabs directly below the top bar ask different questions about the same object, and they are always the package's Tabs ("Controls"); a return link remembers a visit and never replaces the parent. A scope selector keeps the current tab when the next object has it, and otherwise opens its overview.
|
|
360
|
+
- **Compact screens keep the same relationships.** Tabs scroll horizontally, primary content stacks before supporting facts, and no second sidebar appears.
|
|
402
361
|
|
|
403
|
-
###
|
|
362
|
+
### Page compositions
|
|
404
363
|
|
|
405
|
-
|
|
364
|
+
A product page is one of five compositions, each answering one question first:
|
|
406
365
|
|
|
407
|
-
|
|
|
408
|
-
| --- | --- |
|
|
409
|
-
| `{spacing.xxs}` | 4 px |
|
|
410
|
-
| `{spacing.xs}` | 8 px |
|
|
411
|
-
| `{spacing.sm}` | 12 px |
|
|
412
|
-
| `{spacing.md}` | 16 px |
|
|
413
|
-
| `{spacing.lg}` | 24 px |
|
|
414
|
-
| `{spacing.xl}` | 32 px |
|
|
415
|
-
| `{spacing.2xl}` | 40 px |
|
|
416
|
-
| `{spacing.3xl}` | 48 px |
|
|
417
|
-
| `{spacing.4xl}` | 64 px |
|
|
418
|
-
| `{spacing.5xl}` | 96 px |
|
|
419
|
-
| `{spacing.6xl}` | 128 px |
|
|
420
|
-
|
|
421
|
-
Control geometry is foundation, so a control has the same physical size and the same focus treatment in every register.
|
|
422
|
-
|
|
423
|
-
| Token | Value | Owns |
|
|
366
|
+
| Composition | First answer | Structure |
|
|
424
367
|
| --- | --- | --- |
|
|
425
|
-
|
|
|
426
|
-
|
|
|
427
|
-
|
|
|
428
|
-
|
|
|
429
|
-
|
|
|
430
|
-
| `{spacing.control-height}` | 32 px | Minimum height of every default control |
|
|
431
|
-
| `{spacing.control-padding-inline}` | 10 px | Inline padding of a control |
|
|
432
|
-
| `{spacing.touch-target}` | 44 px | Minimum target on coarse or non-hover input |
|
|
433
|
-
|
|
434
|
-
The website's column and its section rhythm belong to the website register and are authored in `register.website.layout`; the filter-chip sizes belong to the Portal register and are authored in `register.portal.density`. Both emit today's `--nc-*` names, and the values live in the one `json design-tokens` fence ("CSS Architecture & Token Pipeline"). Six tokens are the whole of the website's geometry; a page that needs a seventh is asking for a section kind the register does not have.
|
|
435
|
-
|
|
436
|
-
| Token | Owns | Register |
|
|
437
|
-
| --- | --- | --- |
|
|
438
|
-
| `content-max` | 1200 px: the widest the reading column ever grows | Website |
|
|
439
|
-
| `gutter-mobile` | 20 px: the side gutter beside that column below 600 px | Website |
|
|
440
|
-
| `gutter-tablet` | 40 px: the same gutter from 600 px | Website |
|
|
441
|
-
| `gutter-desktop` | 64 px: the same gutter from 960 px | Website |
|
|
442
|
-
| `section-step-mobile` | 64 px: the one gap between two sections below 960 px | Website |
|
|
443
|
-
| `section-step-desktop` | 96 px: the same gap from 960 px | Website |
|
|
444
|
-
| `filter-chip-gap`, `filter-chip-height`, `filter-chip-icon` | The Portal's filter chip | Portal |
|
|
445
|
-
|
|
446
|
-
### Responsive Rules
|
|
368
|
+
| Overview | What needs attention? | Scoped facts, the exceptions that matter, links to their owners. Never a stack of previews of every section. |
|
|
369
|
+
| Collection | Which object? | Search and filters, compact identity rows, comparable facts, trailing actions. One row is one hover and one open target. |
|
|
370
|
+
| Object | What is this, where does it belong, what can I do? | The parent in the breadcrumb, local tabs, identity and grouped facts, the primary action beside its section. |
|
|
371
|
+
| Configuration | What can I change? | A centred reading column of titled sections, help beside each field, a local save and recovery. |
|
|
372
|
+
| Library | What reusable thing can I use? | Kind, text identity, description, source and revision; detail shows content and requirements. |
|
|
447
373
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
Page shells must prevent horizontal scrollbars when a full-bleed section uses viewport width. Clip horizontal overflow at the shell or root level; do not hide overflow on an inner surface to mask bad math.
|
|
451
|
-
|
|
452
|
-
Compositions arrange approved primitives for a content role. They do not redefine the content max-width, the gutters, or the step between sections. If a composition needs structural geometry this section does not name, promote that geometry here first.
|
|
374
|
+
A section earns a card when it owns a decision, not for every label and value. A row links to its object; overflow menus hold occasional commands, and a frequent next action stays visible. A summary on an overview opens the same owner as its tab. Supporting detail collapses only behind a summary that keeps a count, state or consequence.
|
|
453
375
|
|
|
454
376
|
## Elevation & Depth
|
|
455
377
|
|
|
456
|
-
Product UI
|
|
457
|
-
|
|
458
|
-
Glass is a material variant, not an elevation step. Floating chrome may be glass: menus, popovers, selects, comboboxes, the command palette, and bars, the surfaces that stand over the room and show it through themselves. Content surfaces stay solid: cards, KPIs, tables, inspectors, and dialog, sheet, and drawer bodies, because glass floats but never carries content. Inside the window, glass composites only over our own content; a product whose window the operating system composites states its window material in its register. Glass keeps exactly one separator, the integrated edge ring of its elevation falloff, never ring plus shadow plus border stacked. Reduced-transparency mode replaces glass with the same solid material at the same elevation.
|
|
378
|
+
Product UI sits on one continuous workspace ground, `{colors.material-workspace}` (true black in dark), and solid objects rise above it at four heights: `nc-elevation-resting` for resting content, then `nc-elevation-raised`, `nc-elevation-floating` and `nc-elevation-modal` when an object is physically raised, floating or modal. Physical role chooses the height, never the importance of the content. Each recipe carries its own edge, so an elevated surface never adds a border, a ring or a second shadow.
|
|
459
379
|
|
|
460
|
-
|
|
380
|
+
**In light, shadow carries height.** Every surface is white. Each recipe is a 1 px black edge at 8%, a tight contact shadow where the object meets the surface below, and an ambient shadow pushed down with negative spread so it stays under the object. The ambient shadow grows with the height, from 8 px of blur at resting to 128 px at modal.
|
|
461
381
|
|
|
462
|
-
|
|
463
|
-
| ------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
464
|
-
| Workspace | `dotted-field` on `{colors.material-workspace}` / `{colors.dark-material-workspace}`; no edge or shadow | The continuous work area behind product objects |
|
|
465
|
-
| Resting | Solid fill; `nc-elevation-resting` with its own ring; `{rounded.md}` (6 px) | KPI, card, chart card, table, setting, and persistent content surfaces |
|
|
466
|
-
| Raised | Same solid fill; `nc-elevation-raised`; `{rounded.md}` (6 px) | Active work, a selected object, or an attached inspector above resting siblings |
|
|
467
|
-
| Floating | Same solid fill; `nc-elevation-floating`; `{rounded.md}` (6 px) | Menus, popovers, dropdowns, selects, anchored surfaces, tooltips, and toasts |
|
|
468
|
-
| Modal | Same solid fill; `nc-elevation-modal`; `{rounded.md}` (6 px) | Dialog, sheet, drawer, and takeover |
|
|
469
|
-
| Glass control | Translucent solid fill at 76%, 28 px backdrop blur, and the rung for its physical level with that rung's ring | Floating chrome above our own content: menus, popovers, selects, comboboxes, the command palette, and bars; never a content container |
|
|
382
|
+
**In dark, tone carries height.** Shadows cannot show on true black, so each height's surface is one fixed step lighter than the page: resting `{colors.dark-material-resting}` #0c0c0c, raised `{colors.dark-material-raised}` #121212, floating `{colors.dark-material-floating}` #181818, modal `{colors.dark-material-modal}` #1c1c1c. The steps are equal-channel greys with nothing between or above them, so dark surfaces still read as black. `{colors.dark-ink}` keeps at least 14.5:1 and `{colors.dark-muted}` at least 5.2:1 on the lightest step. Each dark recipe adds an inset white hairline and a 1 px top highlight that strengthen with the height, and an outer black edge that separates a surface from a lighter one below it. A component names only its height and its role (`bg-card`, `bg-popover`); `@nadicodeai/ui/globals.css` sets those roles to the height's tone on the element.
|
|
470
383
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
384
|
+
| Surface | Fill | Height | Use |
|
|
385
|
+
| --- | --- | --- | --- |
|
|
386
|
+
| Workspace | `{colors.material-workspace}` | None | The work area behind product objects. |
|
|
387
|
+
| Resting | `{colors.material-resting}` | `nc-elevation-resting` | Cards, KPIs, chart cards, tables, settings. The default. |
|
|
388
|
+
| Raised | `{colors.material-raised}` | `nc-elevation-raised` | Active or selected work, an attached inspector. |
|
|
389
|
+
| Floating | `{colors.material-floating}` | `nc-elevation-floating` | Menus, popovers, selects, tooltips, toasts. |
|
|
390
|
+
| Modal | `{colors.material-modal}` | `nc-elevation-modal` | Dialogs, sheets, drawers. |
|
|
391
|
+
| Outlined | `{colors.material-solid}` | None; one `line` border | A flat panel: fact cells inside a record, the website's explanatory cards. |
|
|
392
|
+
| Plain | `{colors.material-solid}` | None | A structural card with no edge of its own. |
|
|
474
393
|
|
|
475
|
-
|
|
394
|
+
**Glass** is a material, not a height: the floating fill at 60% over a 20 px backdrop blur at 180% saturation, so colour behind it comes through saturated rather than grey. Its edge is the floating recipe, whose inset top highlight and inner hairline show once the fill turns translucent, and a 5% monochrome grain keeps the blurred colour from banding. Glass may go where something behind it moves or carries colour:
|
|
476
395
|
|
|
477
|
-
|
|
396
|
+
- floating chrome over our own content: popovers, selects, comboboxes, context menus, the menubar, navigation popups, the command palette and control bars;
|
|
397
|
+
- the website header as the page scrolls under it;
|
|
398
|
+
- a small floating card over a colour ground or moving Agents.
|
|
478
399
|
|
|
479
|
-
|
|
400
|
+
Content stays solid: cards, KPIs, tables, chat, forms, and dialog, sheet and drawer bodies. A dropdown menu stays solid because it opens over dense rows its labels would compete with. Glass over a plain white or black ground shows nothing, so it never goes there. Reduced transparency replaces glass with the same solid surface at the same height.
|
|
480
401
|
|
|
481
|
-
|
|
402
|
+
**Product views on the website** are the product itself, built from the real components with example data that is labelled as such; never a screenshot, a device mockup or an invented interface. A product window takes the raised height on its ground; a card that overlaps it floats; its chrome may be glass over a colour or black ground; in a black section it renders in dark mode.
|
|
482
403
|
|
|
483
|
-
|
|
404
|
+
Absence follows its container. A page or region with nothing in it is an opaque region with a dashed boundary; an empty state inside a card is unframed, because the card already owns the edge. A persistent notice sits in the section it concerns, action feedback is a toast, and field validation stays with its field.
|
|
484
405
|
|
|
485
|
-
|
|
486
|
-
- Use `{rounded.none}` for full-bleed sections, structural cells, and structural modules.
|
|
487
|
-
- Use `{rounded.full}` only for intrinsically circular identity/avatar chrome and compact control marks whose geometry is inherently round or pill-shaped. It never turns a content surface, button, badge, or navigation item into a pill by default.
|
|
406
|
+
Modal overlays use the scrim with no blur. Focus is not elevation: it is always the focus outline.
|
|
488
407
|
|
|
489
|
-
|
|
408
|
+
## Shapes
|
|
490
409
|
|
|
491
|
-
|
|
410
|
+
Every surface and control takes `{rounded.md}` (6 px): cards, tables, menus, popovers, dialogs, sheets, tooltips, toasts, buttons, inputs, chips and badges. A surface never earns a larger corner by sitting higher. Full-bleed sections take `{rounded.none}`. `{rounded.full}` is for things that are circles by nature: avatars, switch and radio marks, slider thumbs, status dots. The other radii are for inner marks and geometry a component owns, such as a progress track.
|
|
492
411
|
|
|
493
412
|
## Motion
|
|
494
413
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
Only Agents move on their own. An idle orb holds still, and under reduced
|
|
498
|
-
motion a thinking or working orb breathes in opacity, 1.6 s ease-in-out from
|
|
499
|
-
full ink to 0.45, so busy still reads as busy while every other orb is still
|
|
500
|
-
([agent orbs](../ui/docs/agent-orb.md)). Opening an Agent in the Portal is the
|
|
501
|
-
one view transition: the orb travels from the list row into the Agent's page
|
|
502
|
-
on `transition` with `in-out-strong`, over a root crossfade on `close`, and
|
|
503
|
-
reduced motion navigates instantly; the Portal's `portal.css` owns those rules
|
|
504
|
-
because that one navigation is the only place the product moves a page.
|
|
505
|
-
|
|
506
|
-
**One default transition in every product**: `confirm`, 180 ms, `out-strong`. It is set once, on `--default-transition-duration` and `--default-transition-timing-function` in `src/css/motion.css`, so a bounded Tailwind transition utility inherits the cadence and no product declares a default of its own.
|
|
507
|
-
|
|
508
|
-
**One reduced-motion policy**, stated once in `motion.css` and repeated nowhere: under `prefers-reduced-motion: reduce`, movement stops and paint continues. Transforms and keyframed movement go to their end state; opacity and colour transitions are kept, because a fade or a colour change is the state change itself rather than decoration around it, and an Agent that is thinking or working still has to read as busy. A component never answers reduced motion by turning every transition off.
|
|
414
|
+
Only Agents move on their own. The interface answers a person's action, briefly, and stops: motion follows the action, grows from the element that was pressed, and settles, within 300 ms; sheets and drawers that follow the pointer take their own durations. Keyboard actions and anything used a hundred times a day are instant. While a person waits for something they asked for, one waiting line moves and nothing else ("System activity"). Nothing on the website moves but Agents and the product at work: no parallax, scroll reveal, marquee or shimmer.
|
|
509
415
|
|
|
510
|
-
The
|
|
416
|
+
The default transition in every product is `confirm`, 180 ms, `out-strong`. Under reduced motion, movement stops and paint continues: transforms and keyframed movement go to their end state, and opacity and colour transitions stay, because a fade or a colour change is the state change itself. A component never answers reduced motion by turning every transition off.
|
|
511
417
|
|
|
512
418
|
| Token | Use |
|
|
513
419
|
| --- | --- |
|
|
514
|
-
| `
|
|
515
|
-
| `
|
|
516
|
-
| `
|
|
517
|
-
| `
|
|
518
|
-
| `
|
|
519
|
-
| `
|
|
520
|
-
| `
|
|
521
|
-
| `wait` 1.6 s | The one waiting line, and nothing else |
|
|
522
|
-
|
|
523
|
-
The easings are `emphasized`, `out-strong`, `in-out-strong`, and `drawer`. Every remaining duration has a job these rules allow, which is why `spin`, `typing` and `shimmer` retired with the spinner, the typewriter line and the three shimmer systems: a busy spinner is not an Agent, and ambient decoration is not an answer to anything a person did.
|
|
524
|
-
|
|
525
|
-
## Registers
|
|
526
|
-
|
|
527
|
-
One foundation, three products. The foundation is every `core` value in this contract: owned by no single product, available to every product, and foundation whether one product renders it or three. A register is one product's way of applying it: direction, density, the rungs of the ladder it uses and where, the tiers of the type scale it speaks, its dark-mode stance, its structural grammar, its motion budget.
|
|
528
|
-
|
|
529
|
-
A register adds rules. It never redefines a foundation value. A value one register's physics need and the foundation lacks is authored here under `register.<name>`; a register never consumes another register's token; a value two registers share moves to `core`; no product mints a value of its own.
|
|
420
|
+
| `control` 100 ms | Hover and press on a control in a dense surface |
|
|
421
|
+
| `close` 140 ms, `open` 220 ms | Floating chrome and overlays leaving and arriving |
|
|
422
|
+
| `confirm` 180 ms | Confirmation, instant state flips, the default transition |
|
|
423
|
+
| `msg-in` 240 ms | A message arriving in a conversation |
|
|
424
|
+
| `transition` 240 ms | Property transitions on a working surface, and the Portal's one page transition, which carries an Agent's orb from a list row into its page |
|
|
425
|
+
| `drawer-swipe` 400 ms, `drawer` 450 ms | Sheets and drawers, pointer-driven and programmatic |
|
|
426
|
+
| `wait` 1.6 s | The waiting line, and nothing else |
|
|
530
427
|
|
|
531
|
-
|
|
532
|
-
| --- | --- | --- | --- |
|
|
533
|
-
| Website | `apps/website/docs/design-doctrine.md` over `## Layout` here | `register.website.layout`: `content-max`, `gutter-*`, `section-step-*`. It speaks `display-xl`, `display-lg`, and `title-lg` | Light only |
|
|
534
|
-
| Portal | `apps/portal/docs/information-architecture.md` for direction; `apps/portal/docs/design-doctrine.md` for implementation | `register.portal.density`: `filter-chip-gap`, `filter-chip-height`, `filter-chip-icon` | System, light, dark, on the `.dark` class |
|
|
535
|
-
| Nadia desktop app | `apps/nadia/docs/design-doctrine.md` | `register.nadia.*`: colour-role and radius aliases; six deprecated elevation recipes and the legacy overlay border remain as compatibility data. New surfaces consume the foundation's material roles | Follows the operating system, on the `.dark` class |
|
|
536
|
-
|
|
537
|
-
The website is full-bleed sections on one column between two hairline rails, on four grounds: white, the soft canvas, black, and at most one flat, full-strength colour section per page for its single statement. One content max-width with growing side gutters centres the content, a section composes the outlined card, the bento, the step rail and the split, and one spacing step separates two sections (`## Layout`).
|
|
538
|
-
|
|
539
|
-
The website's depth is flat and architectural: the four section grounds, the hairlines, contrast, large Geist headlines, the live product and the cut-out figures make hierarchy. Its cards are the outlined surface, never an elevated one, and it has no page frame, no diagonal fill and no dotted field; the dotted field is the Portal's work surface. No negative letter spacing outside the two display tiers.
|
|
540
|
-
|
|
541
|
-
The Portal and the desktop are product shells on the material ladder above, each by its rulebook. The desktop's window material is the operating system's where the OS composites the window; inside the window the foundation's glass law holds.
|
|
428
|
+
The easings are `emphasized`, `out-strong`, `in-out-strong` and `drawer`; the waiting line alone travels at constant speed (`linear`). A CSS transition or animation takes its duration and easing from these tokens; the design system's own stylesheets are held to it by a guard.
|
|
542
429
|
|
|
543
430
|
## Components
|
|
544
431
|
|
|
545
|
-
|
|
432
|
+
Every product builds from `@nadicodeai/ui`: stock shadcn components on Base UI, the chart kit, the assistant-ui conversation interface, and the compositions below. A component styles itself only through the roles and tokens in this file; it carries no raw colour, no `dark:` utility and no theme of its own. A page arranges and sizes a component and never restyles it: a new appearance is a typed variant or slot added to the component, and a missing composition is added to the package once two products need it. Visible controls always come from the package.
|
|
546
433
|
|
|
547
|
-
###
|
|
434
|
+
### Controls
|
|
548
435
|
|
|
549
|
-
|
|
436
|
+
- **Buttons.** Every button is `{spacing.control-height}` high with a `{typography.body-sm}` label at 500 and `{rounded.md}` corners ("Layout" says where the smaller sizes may go). `default` is the action (ink, white label; `{colors.dark-action}` with a `{colors.dark-action-foreground}` label in dark); `outline` and `secondary` are the quieter steps, edged with the `{colors.line}` hairline; `ghost` has no edge; `destructive` is the error pair; `link` is inline. On a colour or black ground a control takes a variant whose label clears AA there: `brandSolid` (white, ink label) or `brandGhost` (white label, no fill) on cobalto, rosso and black, and the default ink button on verde, arancio and giallo.
|
|
437
|
+
- **Tabs** switch between one object's views. They are pills in one row directly under the top bar: `{spacing.control-height}` high, a `{typography.body-sm}` label at 500, `{spacing.sm}` inline padding and `{rounded.md}` corners. The current tab is filled with the `selection` role (`{colors.navigation-selection-bg}` with `{colors.navigation-selection-fg}`, `{colors.dark-navigation-selection-bg}` in dark); the others have no fill and `{colors.muted}` text, which turns ink on hover. A tab that opens a URL is a link in `TabsNav` with the same look; the `line` variant is for tabs inside a panel.
|
|
438
|
+
- **Fields** keep the `{colors.input}` edge, `{rounded.md}`, no shadow and at least `{spacing.control-height}`. Help sits beside its field, and validation stays with it.
|
|
439
|
+
- **Focus** is drawn once, by the base layer, on every `:focus-visible` element. A component never repaints it; it may move it to a proxy that stands for the focused element, as a field group does for its input.
|
|
440
|
+
- Enabled buttons show a pointer; a disabled control advertises nothing. On coarse or non-hover input every target is at least `{spacing.touch-target}`, while a checkbox or radio mark stays compact and its label supplies the target.
|
|
550
441
|
|
|
551
|
-
|
|
442
|
+
### Cards and sections
|
|
552
443
|
|
|
553
|
-
|
|
444
|
+
`Card` is the product's container, at the resting height by default ("Elevation & Depth"). A row of cards is a `CardRow`: siblings share one height, and the row collapses to one column below its breakpoint. A card or settings section is composed around a real task, collection or form; its header names the content and may explain scope or consequence, and it is never a card holding only an introduction. A settings section owns its heading, a one-line description of what the setting changes, its fields and its local save. Its footer is a strip one tone below the card body, the `recessed` role (`{colors.canvas-soft}`, `{colors.dark-canvas}` in dark), behind a `{colors.line}` seam: an optional hint on the left, such as the plan a feature needs or a link to learn more, and the section's action on the right; a facts section shows labelled facts in two equal columns, stacked on compact screens, with every label kept in place when a value is missing. A KPI card is one figure: label, value, the change as a badge in the top right, and a footer with the comparison.
|
|
554
445
|
|
|
555
|
-
|
|
446
|
+
### Menus and commands
|
|
556
447
|
|
|
557
|
-
|
|
448
|
+
A record's actions sit behind one three-dot trigger, a `{spacing.control-height}` ghost icon button that opens an end-aligned menu. In a collection every row carries that one trigger for its occasional commands, renaming and disconnecting included; a row shows a visible button only for its one frequent next action. Menu rows put the label first and a decorative icon at the trailing edge. A collection's filters are chips, an Add filter button and one outline chip per active filter with its own remove control, never a dropdown above the table. The scope a page works in, such as the company or the account it reports on, is a scope switcher: a ghost trigger reading the current name and ⇅ that opens a popover with a Find search, the choices with the current one checked, and a Create row at the bottom when the scope can be created there. The account menu names who is signed in, carries one gear to the account section, and holds the Theme and Language choices as compact inline rows. The command palette is glass and fills its dialog.
|
|
558
449
|
|
|
559
|
-
|
|
450
|
+
### Tables and missing values
|
|
560
451
|
|
|
561
|
-
|
|
452
|
+
A table is native table structure at one density: 36 px header rows and 40 px body rows. A body row takes the full `accent` fill on hover, on keyboard focus within it and while its menu is open; a selected row takes `muted`; headers and footers never highlight. A collection fits its container without sideways scrolling: identity and actions keep their columns, and supporting facts move into a detail row inside the same record before the table gets cramped.
|
|
562
453
|
|
|
563
|
-
|
|
454
|
+
An unknown or not-applicable value in a table, a comparison list or a set of facts is a muted `—` with screen-reader text, in the same slot as a present value; zero stays zero. An absent report is not `None`: `None` is only for a complete report that found nothing. A stale value keeps its last reading with a short `Stale` qualifier. Loading, failure, missing permission and actionable setup each keep their own visible state and never turn into a dash. Essential reasons and actions never live only in a hover tooltip.
|
|
564
455
|
|
|
565
|
-
|
|
456
|
+
### Status
|
|
566
457
|
|
|
567
|
-
|
|
458
|
+
A status is one Badge: a mark and a word in one of five tones, so it reads without colour.
|
|
568
459
|
|
|
569
|
-
|
|
460
|
+
| Tone | Means | Pair |
|
|
461
|
+
| --- | --- | --- |
|
|
462
|
+
| Gray | Default, and every terminal state that needs no action | `{colors.canvas-soft-2}` / `{colors.body}` |
|
|
463
|
+
| Green | Healthy, complete | `{colors.link-bg-soft}` / `{colors.link-deep}` |
|
|
464
|
+
| Red | Unusable, failed | `{colors.error-soft}` / `{colors.error-deep}` |
|
|
465
|
+
| Amber | Waiting on a person's decision | `{colors.warning-soft}` / `{colors.warning-deep}` |
|
|
466
|
+
| Blue | A step in motion | `{colors.info-soft}` / `{colors.info-deep}` |
|
|
570
467
|
|
|
571
|
-
|
|
468
|
+
Each app keeps its whole status-to-tone map in one module; nothing else chooses a status colour. A status never moves.
|
|
572
469
|
|
|
573
|
-
###
|
|
470
|
+
### System activity
|
|
574
471
|
|
|
575
|
-
|
|
472
|
+
Who does the work decides how it looks. An Agent's work is that Agent's orb in its state and a sentence saying what it does. The system's own work never takes an orb:
|
|
576
473
|
|
|
577
|
-
|
|
474
|
+
| The wait | What shows |
|
|
475
|
+
| --- | --- |
|
|
476
|
+
| Under about a second | The control's own label changes to its pending word; its place, size and icon stay. |
|
|
477
|
+
| Past a second | One 2 px waiting line of ink beside the work, a 32% segment crossing it on `wait`, and nothing else moves. |
|
|
478
|
+
| Measurable | A progress line with a written count. Never an invented percentage. |
|
|
479
|
+
| A page load | Still skeleton placeholders in the shape of the content, never pulsing and never paired with a waiting line. |
|
|
578
480
|
|
|
579
|
-
|
|
481
|
+
One wait gets one indicator. A request waiting for a person is not a wait: queued, paused, blocked, failed and done take the status or feedback treatment. A wait is ink; info blue never marks one. Under reduced motion the waiting line breathes in opacity instead of travelling.
|
|
580
482
|
|
|
581
|
-
|
|
483
|
+
### Agent orbs
|
|
582
484
|
|
|
583
|
-
|
|
485
|
+
An Agent is its orb, in every product; there is no other Agent picture. An orb is one of seven forms, `ribbon`, `lattice`, `orbit`, `globe`, `network`, `braid` and `morph`, in one colour a person chose: cobalto, verde or arancio, or none, which draws it in the text colour. Giallo is not offered because it fails 3:1 on white; rosso is not offered because it is the error red. Nadia alone is her striped ribbon, azzurro, verde, the text colour and rosso along its whole length, the same at every size and in every state.
|
|
584
486
|
|
|
585
|
-
|
|
487
|
+
- **Stored look.** An Agent's form is stored as `orb:<form>` and its colour as the identity hex or nothing; a template declares its starting look the same way. The root Agent always draws as Nadia. An unknown form draws `orbit`, an unknown colour the text colour, and a stored rosso draws arancio. The look is a person's choice and is never derived from a name, id or role.
|
|
488
|
+
- **Sizes** are 20, 24, 32, 40, 64, 96 and 160 px. On a photograph an orb takes the stronger `over-picture` weight; everywhere else it is `plain`.
|
|
489
|
+
- **States.** Idle holds one still pose. Working and thinking move, waiting holds, blocked makes one interrupted effort and stops, and done celebrates once and rests. A surface that only knows a machine checked in draws idle. No state draws a circle around the orb, and a label names the state wherever the exact state matters.
|
|
490
|
+
- **Status and ambient.** An orb that stands for an Agent shows its real state. An orb in decorative artwork, such as a sign-in picture, takes ambient motion: its form moves at its own pace with no state pose or cue, and it claims no activity.
|
|
491
|
+
- **Reduced motion.** Movement stops; a thinking or working orb breathes between full ink and 0.45 opacity so busy still reads as busy, and an ambient orb holds still.
|
|
586
492
|
|
|
587
|
-
|
|
493
|
+
Text sits beside an orb, never on it.
|
|
588
494
|
|
|
589
|
-
|
|
495
|
+
### Agent card
|
|
590
496
|
|
|
591
|
-
|
|
592
|
-
key is revoked, an invitation is waiting, a run is blocked. It is said one way:
|
|
593
|
-
one badge carrying an icon and a word, in one of five semantic tones that only
|
|
594
|
-
reinforce both, so a status is legible without colour and no surface invents a
|
|
595
|
-
second grammar.
|
|
497
|
+
An Agent is shown as one card shape wherever it appears as an object, in the Portal's Library and Nadia's gallery alike: the orb in a 96 px box on the left; the name, the job and a status row beside it; and a footer with facts and one action when either exists. On an Agent's own page the name is the page heading.
|
|
596
498
|
|
|
597
|
-
|
|
598
|
-
pair: gray on the neutral spine for the default and every terminal, no-action
|
|
599
|
-
state; green on the link pair for healthy and complete; red on the error pair
|
|
600
|
-
for unusable and failed; amber on the warning pair for waiting on a decision;
|
|
601
|
-
blue on the info pair for a step in motion. One mark per tone, so the shape
|
|
602
|
-
repeats the tone rather than adding a second axis.
|
|
499
|
+
### Identity marks
|
|
603
500
|
|
|
604
|
-
|
|
605
|
-
[`packages/ui/docs/contract.md`](../ui/docs/contract.md). This package ships no
|
|
606
|
-
status class; a static surface shows a status as ordinary text. Which statuses
|
|
607
|
-
exist and which tone each takes belong to the consuming app, which keeps its
|
|
608
|
-
whole map in one module.
|
|
501
|
+
A recognised app, platform or model maker is drawn with its genuine mark beside its readable name, in a tile that owns the box; an unknown one keeps its name and a text monogram, never a guessed logo. A mark identifies and never replaces the label, and its colour identifies the maker, never a connection's health. Marks are used nominatively, to name a platform, an integration or a provider, never next to wording that implies endorsement. A brand mark never stands in for a UI icon, and a UI icon never stands in for a brand. Skills are text, without icons. In a chart, a model is named by its label, and marks stay out of legends.
|
|
609
502
|
|
|
610
|
-
|
|
503
|
+
### Product views
|
|
611
504
|
|
|
612
|
-
|
|
613
|
-
in its state and a sentence saying what it is doing; Nadia connecting is
|
|
614
|
-
Nadia's ribbon, working. Everything below is the system's own work, and an orb
|
|
615
|
-
means an Agent, so no system wait takes one.
|
|
505
|
+
A product view on the website is a live, inert fragment of the product built from its real components, captioned, with example data marked as example ("Elevation & Depth"). It takes its product's register: the Nadia window shows her Agents on the left and the selected conversation on the right; the Portal shows its sidebar and the page. A view is cropped to what it shows, never scaled below legibility, and never framed in a device.
|
|
616
506
|
|
|
617
|
-
|
|
618
|
-
own word changes, it holds its place and its icon, and it carries `disabled`
|
|
619
|
-
and `aria-busy`. Past a second, one 2 px waiting line of ink moves beside the
|
|
620
|
-
work, a 32% segment crossing its track linearly on `wait`; nothing else on the
|
|
621
|
-
screen moves while it does. Measurable work is a progress line with a written
|
|
622
|
-
count. A page load shows still placeholders in the shape of the content.
|
|
623
|
-
Reduced motion drops the travel and breathes the whole line in opacity.
|
|
507
|
+
### CSS components
|
|
624
508
|
|
|
625
|
-
|
|
626
|
-
activity particles and marks no system wait now. Success, warning and error
|
|
627
|
-
keep their meanings; waiting for a person or a failed operation uses the
|
|
628
|
-
feedback treatment, not an activity one.
|
|
509
|
+
These four ship as framework-neutral CSS; `@nadicodeai/ui` wraps the last three for React.
|
|
629
510
|
|
|
630
|
-
The
|
|
631
|
-
`@nadicodeai/ui`. The one-second delay, the geometry, the ink and the motion
|
|
632
|
-
are owned by [`packages/ui/docs/activity.md`](../ui/docs/activity.md); the
|
|
633
|
-
motion vocabulary owns `.nc-anim-wait` and its reduced-motion law; a consuming
|
|
634
|
-
app chooses the wait and supplies its readable words.
|
|
511
|
+
**`dotted-field`** — the ground of a canvas where a person or an Agent places and arranges objects: a diagram of an Agent's work, a board of its outputs. The dots say that things here can be moved, so the field never sits behind a dashboard, a list, settings or a website section, and it never stands in for elevation.
|
|
635
512
|
|
|
636
|
-
|
|
513
|
+
**`usage-meter`** — consumption shown against its allowance: tokens, cost, latency or runs, with the figure printed.
|
|
637
514
|
|
|
638
|
-
|
|
515
|
+
**`run-timeline`** — the vertical thread of an Agent's run, a handoff or a workflow, one step per row.
|
|
639
516
|
|
|
640
|
-
-
|
|
641
|
-
- Every visible line has one owner: ordinary seams are `{spacing.guide}` at `{colors.line}`, tuned only at the token contract; required control boundaries are `{colors.input}` / `{colors.dark-input}`; named collection controls may use the optional-perimeter rule above; keyboard focus is the dedicated focus-ring roles.
|
|
642
|
-
- Page structure is architectural: hairlines, luminance, spacing, and the section grammar above. One material role per product container; structural cells carry no elevation; the four `nc-elevation-*` rungs are the complete shadow vocabulary; modal scrims stay unblurred.
|
|
643
|
-
- Typography uses fixed token sizes stepped by breakpoint; a component's height comes from its content and the spacing ladder, never from a fixed height of its own.
|
|
644
|
-
- Agent orbs identify an Agent; conversations, artifacts, review states, and work output belong to assistant-ui and the agentic work surfaces. Text sits on the canvas beside an identity mark, never on it.
|
|
645
|
-
- Components are named as nadicode-owned primitives, and this file carries contract only: tokens, semantics, and application rules — component prop APIs, state matrices, screenshots, and page-specific material live with their owners.
|
|
646
|
-
- Each register keeps its own structure and chrome: the three section kinds on the website, the material ladder in the Portal and the desktop, the desktop window following the operating system.
|
|
517
|
+
**`memory-item`** — one fact an Agent remembers about the company or its work.
|
|
647
518
|
|
|
648
519
|
## Iconography
|
|
649
520
|
|
|
650
|
-
|
|
651
|
-
discipline below, the sizes, the stroke, and the decorative-versus-named rule,
|
|
652
|
-
is foundation and holds in every register.
|
|
653
|
-
|
|
654
|
-
The website, the Portal, and `@nadicodeai/ui` draw from Lucide (ISC). Lucide's
|
|
655
|
-
shapes are never edited; the nadicode house render changes only how they are
|
|
656
|
-
drawn: a line of `{spacing.icon-stroke}` on screen at every size, held there by
|
|
657
|
-
`vector-effect: non-scaling-stroke` on each shape, round caps, round joins, no
|
|
658
|
-
fill, and `currentColor` inheritance. A 12 px icon takes
|
|
659
|
-
`{spacing.icon-stroke-sm}`. The width is measured on screen, not on Lucide's
|
|
660
|
-
24-unit grid: a grid-unit stroke thins as the icon shrinks, so a 16 px icon
|
|
661
|
-
drew a lighter line than the 14 px text beside it. The package bakes the
|
|
662
|
-
complete pinned `lucide-static` set through that house render; a surface draws
|
|
663
|
-
a small, disciplined vocabulary from it rather than reaching across the whole
|
|
664
|
-
set.
|
|
665
|
-
|
|
666
|
-
Sizes are sm / md / nav / lg = 12 / 16 / 20 / 24 px, md the default. Persistent
|
|
667
|
-
navigation, the Portal sidebar among it, uses nav so its entries read before
|
|
668
|
-
the content beside them; a collapsed icon rail keeps md.
|
|
669
|
-
|
|
670
|
-
Three icons are the house's own, drawn on Lucide's 24-unit grid and rendered
|
|
671
|
-
the same way: `arch`, the company and its doors; `approvals`, the arch with a
|
|
672
|
-
check, for work waiting on a person; and `agent`, the orb seen face-on with the
|
|
673
|
-
Agent as the dot on its path, the dot of the wordmark. `@nadicodeai/ui`
|
|
674
|
-
exports them from `components/house-icons` as Lucide components, and the
|
|
675
|
-
static set carries them beside Lucide's. They name those three concepts
|
|
676
|
-
wherever an icon does, and no Lucide glyph stands in for them.
|
|
677
|
-
|
|
678
|
-
Static and React icon sources stay version-aligned so a glyph has one
|
|
679
|
-
silhouette everywhere. Text-paired icons are decorative; icon-only controls
|
|
680
|
-
carry an accessible name.
|
|
681
|
-
|
|
682
|
-
## Brand media authority
|
|
683
|
-
|
|
684
|
-
The company is **nadicode**, lowercase ([ADR](../../docs/adr/2026-09-23-the-nadicode-design-system.md)). Its logo has three approved forms:
|
|
521
|
+
Every product draws Lucide's shapes, unedited, in the house render: a 1.5 px line (`{spacing.icon-stroke}`) measured on screen at every size and 1.25 px (`{spacing.icon-stroke-sm}`) at 12 px, round caps and joins, no fill, and the text colour. Sizes are 12, 16, 20 and 24 px, 16 by default, and inside a stock component the registry's own icon size stands; persistent navigation uses 20 so its entries read before the content beside them, and a collapsed rail keeps 16. A surface draws a small, disciplined set, never the whole library.
|
|
685
522
|
|
|
686
|
-
|
|
687
|
-
2. **Mark.** The plain n cut from the wordmark, never with a dot or any other shape added. It stands alone where the name does not fit: compact navigation, headers inside the product.
|
|
688
|
-
3. **Icon.** The white n on a `{colors.identity-cobalto}` tile with rounded corners: app icons, favicons and avatars. The tile is the one place the brand colour fills a field.
|
|
523
|
+
Three icons are the house's own, drawn on Lucide's grid in the same render: `arch` for the company and its doors, `approvals`, the arch with a check, for work waiting on a person, and `agent`, the orb seen face-on with the Agent as the dot on its path. They name those three things wherever an icon does, and no Lucide glyph stands in for them. An icon beside text is decorative; an icon-only control carries an accessible name.
|
|
689
524
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
Agent orbs, Nadia's included, and generated imagery are not nadicode company logos. The orbs live in [`packages/ui/docs/agent-orb.md`](../ui/docs/agent-orb.md); editorial imagery routes through [`skills/marketing/blog-imagegen/references/editorial-register.md`](../../skills/marketing/blog-imagegen/references/editorial-register.md). This contract owns only the package delivery interface.
|
|
693
|
-
|
|
694
|
-
All forms derive from one authored, framework-neutral geometry source exported as `@nadicodeai/design-system/assets/logo-geometry`: on a 100-unit x-height, an 18-unit stroke with flat ends, bowls and the n's arch of radius 41, and the dot of radius 14 centred 79 units above the baseline over the i's stem. No SVG, React component, favicon, document, or app re-authors, traces, typesets, or rearranges those shapes. The eight reference files beside the ADR are the target each generated output matches, byte for byte, and the package guard fails on any output that drifts from them.
|
|
695
|
-
|
|
696
|
-
On `{colors.canvas}` and the soft neutral canvases, the letters are ink and the dot is `{colors.logo-dot}`. On `{colors.identity-ink}` and other dark grounds, the letters are `{colors.identity-white}` and the dot is `{colors.dark-logo-dot}`, a brighter cobalt, so it does not sink into black. `{colors.logo-dot}` is the logo's own colour role and the only token a consumer paints the dot with; the generated mode layer flips it under any dark ancestor, so React draws one form on both grounds. A one-ink form, letters and dot in one colour, exists only where a single ink prints: embossing, stamps, one-colour print. On photography, place the logo in a quiet area or over a scrim that keeps every part legible; when neither is available, mount it on an approved solid field. One surface uses one treatment.
|
|
697
|
-
|
|
698
|
-
The clear-space unit is one stroke width of the rendered wordmark or mark. Keep at least one unit free on every side, with no text, rule, image, or container edge inside it. Minimum rendered sizes are 72 CSS pixels wide for the wordmark, 12 CSS pixels for the mark, and 16 CSS pixels for the favicon. Browser and operating-system icon slots use the supplied favicon or app-icon files at the platform's required size. Never compress, crop, or distort a logo to fit.
|
|
699
|
-
|
|
700
|
-
For a meaningful standalone image, use the accessible name `nadicode`; the accessible name is the label that identifies an image, link, or control to assistive technology. When a containing link or control already has that accessible name, hide the logo image from assistive technology with empty alt text. Never repeat the same accessible name on both the container and the image.
|
|
701
|
-
|
|
702
|
-
Do not redraw, retype, reorder, stretch, crop, rotate, recolor, outline, shadow, animate, or add effects or gradients to a logo form. Do not add a dot or any shape to the mark, move the dot off the i, or substitute Nadia imagery for the logo.
|
|
703
|
-
|
|
704
|
-
The package build renders and publicly exports seven files as SVG and transparent PNG from the geometry and the current `colors.*` contract: `assets/logo` and `assets/logo-white`, `assets/logo-one-ink` and `assets/logo-one-ink-white`, `assets/mark` and `assets/mark-white`, and `assets/app-icon`. React consumers use `BrandMark` or `BrandWordmark` according to the roles above; there is no lockup adapter, because the wordmark is the logo and the mark is already inside it, so no surface places the two together. The operator-only favicon generator draws the same icon tile at the sizes a browser and a platform ask for; it does not substitute another mark. Private geometry templates do not ship. A contract or geometry change is completed by regeneration, never by repainting an output or updating a fallback by hand.
|
|
525
|
+
## Brand media authority
|
|
705
526
|
|
|
706
|
-
|
|
527
|
+
The company is **nadicode**, lowercase. Its logo has three forms:
|
|
707
528
|
|
|
708
|
-
|
|
529
|
+
1. **Wordmark.** nadicode in one geometric line with one dot over the i. The dot is the only colour in the logo, where an Agent sits. Use it wherever the company is named.
|
|
530
|
+
2. **Mark.** The plain n cut from the wordmark, with nothing added, where the name does not fit: compact navigation, headers inside the product.
|
|
531
|
+
3. **Icon.** The white n on a `{colors.identity-cobalto}` tile with rounded corners, for app icons, favicons and avatars; the tile is the one place the brand colour fills a logo's field. Nadia's desktop icon is the one exception: the `{colors.ink}` n on a white tile with its construction guides.
|
|
709
532
|
|
|
710
|
-
|
|
533
|
+
On `{colors.canvas}` and the soft canvases the letters are ink and the dot is `{colors.logo-dot}`; on black and other dark grounds the letters are `{colors.identity-white}` and the dot `{colors.dark-logo-dot}`, a brighter cobalt. `logo-dot` is the only colour a consumer paints the dot with. A one-ink form exists only where a single ink prints. On a photograph, the logo sits in a quiet area or over a scrim that keeps it legible, or on an approved solid field.
|
|
711
534
|
|
|
712
|
-
|
|
535
|
+
Every form is drawn from one geometry source, `@nadicodeai/design-system/assets/logo-geometry`: on a 100-unit x-height, an 18-unit stroke with flat ends, bowls and the n's arch of radius 41, and a dot of radius 14 centred 79 units above the baseline over the i's stem. Nothing re-authors, traces, typesets or rearranges it. React draws `BrandWordmark` or `BrandMark`; there is no lockup, because the mark is already inside the wordmark.
|
|
713
536
|
|
|
714
|
-
|
|
715
|
-
- React consumers use `@nadicodeai/ui/globals.css`. That React adapter composes the complete design-system CSS interface with Tailwind v4 and shadcn. Narrow protocol adapters may translate a third-party class vocabulary, but they do not declare another token hierarchy.
|
|
537
|
+
The clear space is one stroke width of the rendered logo on every side. The minimum sizes are 72 px wide for the wordmark, 12 px for the mark and 16 px for the favicon on screen, and 25 mm and 5 mm in print. A meaningful standalone logo is named `nadicode`; inside a link or control that already has the name, the logo is hidden from assistive technology. Never redraw, retype, stretch, crop, rotate, recolour, outline, shadow, animate or add a gradient to a logo; never add a shape to the mark or move the dot. Other companies' marks, Agent orbs and generated imagery are not nadicode logos.
|
|
716
538
|
|
|
717
|
-
|
|
539
|
+
## Registers
|
|
718
540
|
|
|
719
|
-
|
|
541
|
+
| Register | Its rulebook | Its own tokens | Dark mode |
|
|
542
|
+
| --- | --- | --- | --- |
|
|
543
|
+
| Website | `apps/website/docs/design-doctrine.md` | `register.website.layout` | A light page; a black section scopes `.dark` for the product views in it |
|
|
544
|
+
| Portal | `apps/portal/docs/information-architecture.md` and `apps/portal/docs/design-doctrine.md` | None | System, light or dark, on the `.dark` class |
|
|
545
|
+
| Nadia | `apps/nadia/docs/design-doctrine.md` | None | System by default, light or dark, on the `.dark` class |
|
|
720
546
|
|
|
721
|
-
|
|
547
|
+
- **Website.** The page grammar is "The website column": full-bleed sections on white, soft, black and colour grounds, alternating, with product views floating on them. Its cards are outlined; its product views take their real heights.
|
|
548
|
+
- **Portal.** A product shell on the workspace ground, built from the page compositions and the package components, with `globals.css` giving every elevated surface its tone.
|
|
549
|
+
- **Nadia.** The desktop runs inside Hermes's shell, which owns Tailwind and its own surface layering. Nadia's theme paints Hermes's roles with the shared `semantic` roles and core radii, and imports `@nadicodeai/ui/components.css` with the design system's host stylesheet, so the package components render with the same recipes and glass. The window follows the operating system's material where the system composites it.
|
|
722
550
|
|
|
723
|
-
|
|
724
|
-
- Type utilities: the `.nc-type-*` classes live in `foundation.css`.
|
|
725
|
-
- The website's content column, `.nc-page`, and the responsive `@media` switching behind it: `foundation.css`.
|
|
726
|
-
- CSS primitives and components: `primitives.css` plus `components/*`. Every shipped component is named once in `## Components`; the two-way guard derives the CSS relationship from that catalog.
|
|
727
|
-
- Specimens: `examples/`, which only assemble already-defined tiers.
|
|
551
|
+
## Do's and Don'ts
|
|
728
552
|
|
|
729
|
-
|
|
553
|
+
- Do keep the brand still: only Agents, and the product at work, move. Don't animate sections, pictures or scrolling.
|
|
554
|
+
- Do use the identity hues at full strength as grounds, stages and Agents, with the text pole that clears AA. Don't tint them, grade them, or paint a control, chrome or a status with them.
|
|
555
|
+
- Do choose a surface's height by its physical role, and let shadow show it in light and tone in dark. Don't add a border, ring or second shadow to an elevated surface.
|
|
556
|
+
- Do put glass on floating chrome over colour or motion. Don't make content glass, or put glass over a plain ground.
|
|
557
|
+
- Do show the product with its real components. Don't use screenshots, device frames or invented interfaces.
|
|
558
|
+
- Do round every surface and control at `{rounded.md}`. Don't give a higher surface a bigger corner.
|
|
559
|
+
- Do give every line one owner, and draw focus only with the focus outline.
|
|
560
|
+
- Do say a status with a Badge tone, a mark and a word. Don't use colour alone or a bare dot.
|
|
561
|
+
- Do use the dotted field only on a canvas where things are arranged. Don't put it behind lists, dashboards, settings or website sections.
|
|
562
|
+
- Do add a rule here before a product needs it. Don't restate a rule in another document; link to it.
|
|
730
563
|
|
|
731
|
-
|
|
564
|
+
## Token extension
|
|
732
565
|
|
|
733
|
-
|
|
566
|
+
The YAML front matter holds the four groups the DESIGN.md format defines: colors, typography, rounded and spacing. Everything else is authored in the one `json design-tokens` fence below, in the W3C design-tokens format: `core.material` (the scrim and glass values), `core.elevation` (four recipes per mode), `core.motion` (durations and easings), `semantic` (the shared colour roles) and `register.website` (the website column). An alias is written `{core.color.canvas}`. In CSS a token is `--nc-<name>`, an elevation is reached only through `nc-elevation-<height>`, and Tailwind names the values through the `nc-` and `nadicode-` namespaces. How the build turns this file into CSS and token files is described in `packages/design-system/README.md`.
|
|
734
567
|
|
|
735
568
|
```json design-tokens
|
|
736
569
|
{
|
|
@@ -738,93 +571,79 @@ An alias is written in the short `{core.color.canvas}` form here; the build rewr
|
|
|
738
571
|
"material": {
|
|
739
572
|
"$type": "number",
|
|
740
573
|
"scrim-opacity": {"$value":0.1},
|
|
741
|
-
"glass-opacity": {"$value":0.
|
|
742
|
-
"glass-saturation": {"$value":1.
|
|
743
|
-
"glass-blur": {"$type":"dimension","$value":{"value":
|
|
574
|
+
"glass-opacity": {"$value":0.6},
|
|
575
|
+
"glass-saturation": {"$value":1.8},
|
|
576
|
+
"glass-blur": {"$type":"dimension","$value":{"value":20,"unit":"px"}}
|
|
744
577
|
},
|
|
745
578
|
"elevation": {
|
|
746
579
|
"$type": "shadow",
|
|
747
580
|
"light": {
|
|
748
581
|
"resting": {
|
|
749
582
|
"$value": [
|
|
750
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
751
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
752
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
753
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":2.79,"unit":"px"},"blur":{"value":3.72,"unit":"px"},"spread":{"value":-2,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.01}},
|
|
754
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":1.16,"unit":"px"},"blur":{"value":1.5,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.01}},
|
|
755
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.05}}
|
|
583
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":1,"unit":"px"},"blur":{"value":2,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.05}},
|
|
584
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":2,"unit":"px"},"blur":{"value":8,"unit":"px"},"spread":{"value":-2,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.06}},
|
|
585
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.08}}
|
|
756
586
|
]
|
|
757
587
|
},
|
|
758
588
|
"raised": {
|
|
759
589
|
"$value": [
|
|
760
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
761
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
762
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
763
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
764
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":1.5,"unit":"px"},"blur":{"value":3,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.02}},
|
|
765
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.05}}
|
|
590
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":1,"unit":"px"},"blur":{"value":2,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.06}},
|
|
591
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":6,"unit":"px"},"blur":{"value":12,"unit":"px"},"spread":{"value":-4,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.08}},
|
|
592
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":16,"unit":"px"},"blur":{"value":32,"unit":"px"},"spread":{"value":-8,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.1}},
|
|
593
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.08}}
|
|
766
594
|
]
|
|
767
595
|
},
|
|
768
596
|
"floating": {
|
|
769
597
|
"$value": [
|
|
770
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
771
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
772
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
773
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
774
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
775
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.
|
|
598
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":1,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[1,1,1],"alpha":0.75},"inset":true},
|
|
599
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[1,1,1],"alpha":0.35},"inset":true},
|
|
600
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":2,"unit":"px"},"blur":{"value":4,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.06}},
|
|
601
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":12,"unit":"px"},"blur":{"value":24,"unit":"px"},"spread":{"value":-6,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.12}},
|
|
602
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":32,"unit":"px"},"blur":{"value":64,"unit":"px"},"spread":{"value":-16,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.16}},
|
|
603
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.08}}
|
|
776
604
|
]
|
|
777
605
|
},
|
|
778
606
|
"modal": {
|
|
779
607
|
"$value": [
|
|
780
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
781
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
782
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
783
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
784
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":3,"unit":"px"},"blur":{"value":6,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.03}},
|
|
785
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.05}}
|
|
608
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":2,"unit":"px"},"blur":{"value":4,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.06}},
|
|
609
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":24,"unit":"px"},"blur":{"value":48,"unit":"px"},"spread":{"value":-12,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.18}},
|
|
610
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":64,"unit":"px"},"blur":{"value":128,"unit":"px"},"spread":{"value":-32,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.26}},
|
|
611
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.08}}
|
|
786
612
|
]
|
|
787
613
|
}
|
|
788
614
|
},
|
|
789
615
|
"dark": {
|
|
790
616
|
"resting": {
|
|
791
617
|
"$value": [
|
|
792
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
793
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
794
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
795
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
796
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":1.16,"unit":"px"},"blur":{"value":1.5,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.01}},
|
|
797
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[1,1,1],"alpha":0.12}}
|
|
618
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":1,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[1,1,1],"alpha":0.05},"inset":true},
|
|
619
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[1,1,1],"alpha":0.07},"inset":true},
|
|
620
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":1,"unit":"px"},"blur":{"value":2,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.6}},
|
|
621
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.8}}
|
|
798
622
|
]
|
|
799
623
|
},
|
|
800
624
|
"raised": {
|
|
801
625
|
"$value": [
|
|
802
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
803
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
804
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
805
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
806
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":1.5,"unit":"px"},"blur":{"value":3,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.02}},
|
|
807
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[1,1,1],"alpha":0.16}}
|
|
626
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":1,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[1,1,1],"alpha":0.07},"inset":true},
|
|
627
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[1,1,1],"alpha":0.09},"inset":true},
|
|
628
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":16,"unit":"px"},"blur":{"value":32,"unit":"px"},"spread":{"value":-8,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.6}},
|
|
629
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.8}}
|
|
808
630
|
]
|
|
809
631
|
},
|
|
810
632
|
"floating": {
|
|
811
633
|
"$value": [
|
|
812
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
813
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
814
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
815
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
816
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
817
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[1,1,1],"alpha":0.22}}
|
|
634
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":1,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[1,1,1],"alpha":0.09},"inset":true},
|
|
635
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[1,1,1],"alpha":0.11},"inset":true},
|
|
636
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":12,"unit":"px"},"blur":{"value":24,"unit":"px"},"spread":{"value":-6,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.6}},
|
|
637
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":32,"unit":"px"},"blur":{"value":64,"unit":"px"},"spread":{"value":-16,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.7}},
|
|
638
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.8}}
|
|
818
639
|
]
|
|
819
640
|
},
|
|
820
641
|
"modal": {
|
|
821
642
|
"$value": [
|
|
822
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
823
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
824
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
825
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":
|
|
826
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":3,"unit":"px"},"blur":{"value":6,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.03}},
|
|
827
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[1,1,1],"alpha":0.28}}
|
|
643
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":1,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[1,1,1],"alpha":0.11},"inset":true},
|
|
644
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[1,1,1],"alpha":0.13},"inset":true},
|
|
645
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":64,"unit":"px"},"blur":{"value":128,"unit":"px"},"spread":{"value":-32,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.8}},
|
|
646
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.8}}
|
|
828
647
|
]
|
|
829
648
|
}
|
|
830
649
|
}
|
|
@@ -832,7 +651,6 @@ An alias is written in the short `{core.color.canvas}` form here; the build rewr
|
|
|
832
651
|
"motion": {
|
|
833
652
|
"duration": {
|
|
834
653
|
"$type": "duration",
|
|
835
|
-
"stagger": {"$value":{"value":60,"unit":"ms"}},
|
|
836
654
|
"control": {"$value":{"value":100,"unit":"ms"}},
|
|
837
655
|
"close": {"$value":{"value":140,"unit":"ms"}},
|
|
838
656
|
"confirm": {"$value":{"value":180,"unit":"ms"}},
|
|
@@ -852,6 +670,124 @@ An alias is written in the short `{core.color.canvas}` form here; the build rewr
|
|
|
852
670
|
}
|
|
853
671
|
}
|
|
854
672
|
},
|
|
673
|
+
"semantic": {
|
|
674
|
+
"radius": {"$type":"dimension","$value":"{core.rounded.md}"},
|
|
675
|
+
"color": {
|
|
676
|
+
"$type": "color",
|
|
677
|
+
"light": {
|
|
678
|
+
"background": {"$value":"{core.color.canvas}"},
|
|
679
|
+
"foreground": {"$value":"{core.color.ink}"},
|
|
680
|
+
"card": {"$value":"{core.color.material-solid}"},
|
|
681
|
+
"card-foreground": {"$value":"{core.color.ink}"},
|
|
682
|
+
"popover": {"$value":"{core.color.material-solid}"},
|
|
683
|
+
"popover-foreground": {"$value":"{core.color.ink}"},
|
|
684
|
+
"primary": {"$value":"{core.color.action}"},
|
|
685
|
+
"primary-foreground": {"$value":"{core.color.action-foreground}"},
|
|
686
|
+
"primary-hover": {"$value":"{core.color.action-hover}"},
|
|
687
|
+
"primary-active": {"$value":"{core.color.action-active}"},
|
|
688
|
+
"secondary": {"$value":"{core.color.canvas-soft}"},
|
|
689
|
+
"secondary-foreground": {"$value":"{core.color.ink}"},
|
|
690
|
+
"muted": {"$value":"{core.color.canvas-soft}"},
|
|
691
|
+
"muted-foreground": {"$value":"{core.color.body}"},
|
|
692
|
+
"placeholder": {"$value":"{core.color.muted}"},
|
|
693
|
+
"accent": {"$value":"{core.color.canvas-soft-2}"},
|
|
694
|
+
"accent-foreground": {"$value":"{core.color.ink}"},
|
|
695
|
+
"destructive": {"$value":"{core.color.error}"},
|
|
696
|
+
"destructive-foreground": {"$value":"{core.color.identity-white}"},
|
|
697
|
+
"warning": {"$value":"{core.color.warning}"},
|
|
698
|
+
"warning-foreground": {"$value":"{core.color.ink}"},
|
|
699
|
+
"success": {"$value":"{core.color.success}"},
|
|
700
|
+
"success-foreground": {"$value":"{core.color.ink}"},
|
|
701
|
+
"info": {"$value":"{core.color.info}"},
|
|
702
|
+
"info-foreground": {"$value":"{core.color.identity-white}"},
|
|
703
|
+
"destructive-soft": {"$value":"{core.color.error-soft}"},
|
|
704
|
+
"destructive-soft-foreground": {"$value":"{core.color.error-deep}"},
|
|
705
|
+
"warning-soft": {"$value":"{core.color.warning-soft}"},
|
|
706
|
+
"warning-soft-foreground": {"$value":"{core.color.warning-deep}"},
|
|
707
|
+
"success-soft": {"$value":"{core.color.success-soft}"},
|
|
708
|
+
"success-soft-foreground": {"$value":"{core.color.success-deep}"},
|
|
709
|
+
"info-soft": {"$value":"{core.color.info-soft}"},
|
|
710
|
+
"info-soft-foreground": {"$value":"{core.color.info-deep}"},
|
|
711
|
+
"border": {"$value":"{core.color.line}"},
|
|
712
|
+
"input": {"$value":"{core.color.input}"},
|
|
713
|
+
"ring": {"$value":"{core.color.focus-ring}"},
|
|
714
|
+
"scrim": {"$value":"{core.color.scrim}"},
|
|
715
|
+
"chart-1": {"$value":"{core.color.chart-1}"},
|
|
716
|
+
"chart-2": {"$value":"{core.color.chart-2}"},
|
|
717
|
+
"chart-3": {"$value":"{core.color.chart-3}"},
|
|
718
|
+
"chart-4": {"$value":"{core.color.chart-4}"},
|
|
719
|
+
"chart-5": {"$value":"{core.color.chart-5}"},
|
|
720
|
+
"selection": {"$value":"{core.color.navigation-selection-bg}"},
|
|
721
|
+
"selection-foreground": {"$value":"{core.color.navigation-selection-fg}"},
|
|
722
|
+
"recessed": {"$value":"{core.color.canvas-soft}"},
|
|
723
|
+
"sidebar": {"$value":"{core.color.canvas-soft}"},
|
|
724
|
+
"sidebar-foreground": {"$value":"{core.color.ink}"},
|
|
725
|
+
"sidebar-primary": {"$value":"{core.color.navigation-selection-bg}"},
|
|
726
|
+
"sidebar-primary-foreground": {"$value":"{core.color.navigation-selection-fg}"},
|
|
727
|
+
"sidebar-primary-hover": {"$value":"{core.color.navigation-selection-hover}"},
|
|
728
|
+
"sidebar-accent": {"$value":"{core.color.canvas-soft-2}"},
|
|
729
|
+
"sidebar-accent-foreground": {"$value":"{core.color.ink}"},
|
|
730
|
+
"sidebar-border": {"$value":"{core.color.line}"},
|
|
731
|
+
"sidebar-ring": {"$value":"{core.color.focus-ring}"}
|
|
732
|
+
},
|
|
733
|
+
"dark": {
|
|
734
|
+
"background": {"$value":"{core.color.dark-canvas}"},
|
|
735
|
+
"foreground": {"$value":"{core.color.dark-ink}"},
|
|
736
|
+
"card": {"$value":"{core.color.dark-material-solid}"},
|
|
737
|
+
"card-foreground": {"$value":"{core.color.dark-ink}"},
|
|
738
|
+
"popover": {"$value":"{core.color.dark-material-solid}"},
|
|
739
|
+
"popover-foreground": {"$value":"{core.color.dark-ink}"},
|
|
740
|
+
"primary": {"$value":"{core.color.dark-action}"},
|
|
741
|
+
"primary-foreground": {"$value":"{core.color.dark-action-foreground}"},
|
|
742
|
+
"primary-hover": {"$value":"{core.color.dark-action-hover}"},
|
|
743
|
+
"primary-active": {"$value":"{core.color.dark-action-active}"},
|
|
744
|
+
"secondary": {"$value":"{core.color.dark-canvas-soft}"},
|
|
745
|
+
"secondary-foreground": {"$value":"{core.color.dark-ink}"},
|
|
746
|
+
"muted": {"$value":"{core.color.dark-canvas-soft}"},
|
|
747
|
+
"muted-foreground": {"$value":"{core.color.dark-body}"},
|
|
748
|
+
"placeholder": {"$value":"{core.color.dark-muted}"},
|
|
749
|
+
"accent": {"$value":"{core.color.dark-canvas-soft-2}"},
|
|
750
|
+
"accent-foreground": {"$value":"{core.color.dark-ink}"},
|
|
751
|
+
"destructive": {"$value":"{core.color.dark-error}"},
|
|
752
|
+
"destructive-foreground": {"$value":"{core.color.dark-on-primary}"},
|
|
753
|
+
"warning": {"$value":"{core.color.dark-warning}"},
|
|
754
|
+
"warning-foreground": {"$value":"{core.color.dark-on-primary}"},
|
|
755
|
+
"success": {"$value":"{core.color.dark-success}"},
|
|
756
|
+
"success-foreground": {"$value":"{core.color.dark-on-primary}"},
|
|
757
|
+
"info": {"$value":"{core.color.dark-info}"},
|
|
758
|
+
"info-foreground": {"$value":"{core.color.dark-on-primary}"},
|
|
759
|
+
"destructive-soft": {"$value":"{core.color.dark-error-soft}"},
|
|
760
|
+
"destructive-soft-foreground": {"$value":"{core.color.dark-error-deep}"},
|
|
761
|
+
"warning-soft": {"$value":"{core.color.dark-warning-soft}"},
|
|
762
|
+
"warning-soft-foreground": {"$value":"{core.color.dark-warning-deep}"},
|
|
763
|
+
"success-soft": {"$value":"{core.color.dark-success-soft}"},
|
|
764
|
+
"success-soft-foreground": {"$value":"{core.color.dark-success-deep}"},
|
|
765
|
+
"info-soft": {"$value":"{core.color.dark-info-soft}"},
|
|
766
|
+
"info-soft-foreground": {"$value":"{core.color.dark-info-deep}"},
|
|
767
|
+
"border": {"$value":"{core.color.dark-line}"},
|
|
768
|
+
"input": {"$value":"{core.color.dark-input}"},
|
|
769
|
+
"ring": {"$value":"{core.color.dark-focus-ring}"},
|
|
770
|
+
"scrim": {"$value":"{core.color.dark-scrim}"},
|
|
771
|
+
"chart-1": {"$value":"{core.color.dark-chart-1}"},
|
|
772
|
+
"chart-2": {"$value":"{core.color.dark-chart-2}"},
|
|
773
|
+
"chart-3": {"$value":"{core.color.dark-chart-3}"},
|
|
774
|
+
"chart-4": {"$value":"{core.color.dark-chart-4}"},
|
|
775
|
+
"chart-5": {"$value":"{core.color.dark-chart-5}"},
|
|
776
|
+
"selection": {"$value":"{core.color.dark-navigation-selection-bg}"},
|
|
777
|
+
"selection-foreground": {"$value":"{core.color.dark-navigation-selection-fg}"},
|
|
778
|
+
"recessed": {"$value":"{core.color.dark-canvas}"},
|
|
779
|
+
"sidebar": {"$value":"{core.color.dark-canvas}"},
|
|
780
|
+
"sidebar-foreground": {"$value":"{core.color.dark-ink}"},
|
|
781
|
+
"sidebar-primary": {"$value":"{core.color.dark-navigation-selection-bg}"},
|
|
782
|
+
"sidebar-primary-foreground": {"$value":"{core.color.dark-navigation-selection-fg}"},
|
|
783
|
+
"sidebar-primary-hover": {"$value":"{core.color.dark-navigation-selection-hover}"},
|
|
784
|
+
"sidebar-accent": {"$value":"{core.color.dark-canvas-soft-2}"},
|
|
785
|
+
"sidebar-accent-foreground": {"$value":"{core.color.dark-ink}"},
|
|
786
|
+
"sidebar-border": {"$value":"{core.color.dark-line}"},
|
|
787
|
+
"sidebar-ring": {"$value":"{core.color.dark-focus-ring}"}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
},
|
|
855
791
|
"register": {
|
|
856
792
|
"website": {
|
|
857
793
|
"$extensions": {"ai.nadicode.targets":["css","data"]},
|
|
@@ -864,197 +800,7 @@ An alias is written in the short `{core.color.canvas}` form here; the build rewr
|
|
|
864
800
|
"section-step-mobile": {"$value":{"value":64,"unit":"px"}},
|
|
865
801
|
"section-step-desktop": {"$value":{"value":96,"unit":"px"}}
|
|
866
802
|
}
|
|
867
|
-
},
|
|
868
|
-
"portal": {
|
|
869
|
-
"$extensions": {"ai.nadicode.targets":["css","data"]},
|
|
870
|
-
"density": {
|
|
871
|
-
"$type": "dimension",
|
|
872
|
-
"filter-chip-gap": {"$value":{"value":6,"unit":"px"}},
|
|
873
|
-
"filter-chip-height": {"$value":{"value":28,"unit":"px"}},
|
|
874
|
-
"filter-chip-icon": {"$value":{"value":14,"unit":"px"}}
|
|
875
|
-
}
|
|
876
|
-
},
|
|
877
|
-
"nadia": {
|
|
878
|
-
"$extensions": {"ai.nadicode.targets":["data"]},
|
|
879
|
-
"color": {
|
|
880
|
-
"light": {
|
|
881
|
-
"$type": "color",
|
|
882
|
-
"background": {"$value":"{core.color.canvas}"},
|
|
883
|
-
"foreground": {"$value":"{core.color.ink}"},
|
|
884
|
-
"card": {"$value":"{core.color.canvas}"},
|
|
885
|
-
"card-foreground": {"$value":"{core.color.ink}"},
|
|
886
|
-
"muted": {"$value":"{core.color.canvas-soft-2}"},
|
|
887
|
-
"muted-foreground": {"$value":"{core.color.body}"},
|
|
888
|
-
"popover": {"$value":"{core.color.canvas}"},
|
|
889
|
-
"popover-foreground": {"$value":"{core.color.ink}"},
|
|
890
|
-
"primary": {"$value":"{core.color.action}"},
|
|
891
|
-
"primary-foreground": {"$value":"{core.color.action-foreground}"},
|
|
892
|
-
"secondary": {"$value":"{core.color.canvas-soft}"},
|
|
893
|
-
"secondary-foreground": {"$value":"{core.color.ink}"},
|
|
894
|
-
"accent": {"$value":"{core.color.canvas-soft-2}"},
|
|
895
|
-
"accent-foreground": {"$value":"{core.color.ink}"},
|
|
896
|
-
"border": {"$value":"{core.color.line}"},
|
|
897
|
-
"input": {"$value":"{core.color.input}"},
|
|
898
|
-
"ring": {"$value":"{core.color.focus-ring}"},
|
|
899
|
-
"midground": {"$value":"{core.color.body}"},
|
|
900
|
-
"composer-ring": {"$value":"{core.color.focus-ring}"},
|
|
901
|
-
"destructive": {"$value":"{core.color.error}"},
|
|
902
|
-
"destructive-foreground": {"$value":"{core.color.on-primary}"},
|
|
903
|
-
"sidebar-background": {"$value":"{core.color.canvas-soft}"},
|
|
904
|
-
"sidebar-border": {"$value":"{core.color.line}"},
|
|
905
|
-
"user-bubble": {"$value":"{core.color.canvas-soft-2}"},
|
|
906
|
-
"user-bubble-border": {"$value":"{core.color.line}"}
|
|
907
|
-
},
|
|
908
|
-
"dark": {
|
|
909
|
-
"$type": "color",
|
|
910
|
-
"background": {"$value":"{core.color.dark-canvas}"},
|
|
911
|
-
"foreground": {"$value":"{core.color.dark-ink}"},
|
|
912
|
-
"card": {"$value":"{core.color.dark-canvas}"},
|
|
913
|
-
"card-foreground": {"$value":"{core.color.dark-ink}"},
|
|
914
|
-
"muted": {"$value":"{core.color.dark-canvas-soft-2}"},
|
|
915
|
-
"muted-foreground": {"$value":"{core.color.dark-muted}"},
|
|
916
|
-
"popover": {"$value":"{core.color.dark-canvas}"},
|
|
917
|
-
"popover-foreground": {"$value":"{core.color.dark-ink}"},
|
|
918
|
-
"primary": {"$value":"{core.color.dark-action}"},
|
|
919
|
-
"primary-foreground": {"$value":"{core.color.dark-action-foreground}"},
|
|
920
|
-
"secondary": {"$value":"{core.color.dark-canvas-soft-2}"},
|
|
921
|
-
"secondary-foreground": {"$value":"{core.color.dark-body}"},
|
|
922
|
-
"accent": {"$value":"{core.color.dark-canvas-soft-2}"},
|
|
923
|
-
"accent-foreground": {"$value":"{core.color.dark-ink}"},
|
|
924
|
-
"border": {"$value":"{core.color.dark-line}"},
|
|
925
|
-
"input": {"$value":"{core.color.dark-input}"},
|
|
926
|
-
"ring": {"$value":"{core.color.dark-focus-ring}"},
|
|
927
|
-
"midground": {"$value":"{core.color.dark-muted}"},
|
|
928
|
-
"composer-ring": {"$value":"{core.color.dark-focus-ring}"},
|
|
929
|
-
"destructive": {"$value":"{core.color.error}"},
|
|
930
|
-
"destructive-foreground": {"$value":"{core.color.on-primary}"},
|
|
931
|
-
"sidebar-background": {"$value":"{core.color.dark-canvas}"},
|
|
932
|
-
"sidebar-border": {"$value":"{core.color.dark-seam}"},
|
|
933
|
-
"user-bubble": {"$value":"{core.color.dark-canvas-soft-2}"},
|
|
934
|
-
"user-bubble-border": {"$value":"{core.color.dark-line}"}
|
|
935
|
-
}
|
|
936
|
-
},
|
|
937
|
-
"radius": {
|
|
938
|
-
"$type": "dimension",
|
|
939
|
-
"xs": {"$value":"{core.rounded.xs}"},
|
|
940
|
-
"sm": {"$value":"{core.rounded.sm}"},
|
|
941
|
-
"md": {"$value":"{core.rounded.md}"},
|
|
942
|
-
"lg": {"$value":"{core.rounded.lg}"},
|
|
943
|
-
"xl": {"$value":"{core.rounded.xl}"},
|
|
944
|
-
"2xl": {"$value":"{core.rounded.2xl}"},
|
|
945
|
-
"3xl": {"$value":"{core.rounded.3xl}"},
|
|
946
|
-
"4xl": {"$value":"{core.rounded.3xl}"}
|
|
947
|
-
},
|
|
948
|
-
"elevation": {
|
|
949
|
-
"$deprecated": true,
|
|
950
|
-
"$description": "Compatibility data for older Nadia consumers. New surfaces use core.elevation's four material roles; resting controls have no shadow.",
|
|
951
|
-
"light": {
|
|
952
|
-
"$type": "shadow",
|
|
953
|
-
"control": {
|
|
954
|
-
"$value": [
|
|
955
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.05}},
|
|
956
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":1,"unit":"px"},"blur":{"value":2,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.04}}
|
|
957
|
-
]
|
|
958
|
-
},
|
|
959
|
-
"panel": {
|
|
960
|
-
"$value": [
|
|
961
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.05}},
|
|
962
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":1,"unit":"px"},"blur":{"value":3,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.05}}
|
|
963
|
-
]
|
|
964
|
-
},
|
|
965
|
-
"raised": {
|
|
966
|
-
"$value": [
|
|
967
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.05}},
|
|
968
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":4,"unit":"px"},"blur":{"value":10,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.07}},
|
|
969
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":10,"unit":"px"},"blur":{"value":22,"unit":"px"},"spread":{"value":-14,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.16}}
|
|
970
|
-
]
|
|
971
|
-
},
|
|
972
|
-
"overlay": {
|
|
973
|
-
"$value": [
|
|
974
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":8,"unit":"px"},"blur":{"value":18,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.09}},
|
|
975
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":20,"unit":"px"},"blur":{"value":36,"unit":"px"},"spread":{"value":-20,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.22}}
|
|
976
|
-
]
|
|
977
|
-
},
|
|
978
|
-
"modal": {
|
|
979
|
-
"$value": [
|
|
980
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.05}},
|
|
981
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":14,"unit":"px"},"blur":{"value":30,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.13}},
|
|
982
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":32,"unit":"px"},"blur":{"value":60,"unit":"px"},"spread":{"value":-24,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.3}}
|
|
983
|
-
]
|
|
984
|
-
},
|
|
985
|
-
"composer": {
|
|
986
|
-
"$value": [
|
|
987
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":1,"unit":"px"},"blur":{"value":2,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.05}}
|
|
988
|
-
]
|
|
989
|
-
},
|
|
990
|
-
"overlay-border": {
|
|
991
|
-
"$type": "border",
|
|
992
|
-
"$value": {"width":{"value":1,"unit":"px"},"style":"solid","color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.05}}
|
|
993
|
-
}
|
|
994
|
-
},
|
|
995
|
-
"dark": {
|
|
996
|
-
"$type": "shadow",
|
|
997
|
-
"control": {
|
|
998
|
-
"$value": [
|
|
999
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[1,1,1],"alpha":0.18}},
|
|
1000
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":1,"unit":"px"},"blur":{"value":2,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.5}}
|
|
1001
|
-
]
|
|
1002
|
-
},
|
|
1003
|
-
"panel": {
|
|
1004
|
-
"$value": [
|
|
1005
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[1,1,1],"alpha":0.18}},
|
|
1006
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":2,"unit":"px"},"blur":{"value":6,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.6}}
|
|
1007
|
-
]
|
|
1008
|
-
},
|
|
1009
|
-
"raised": {
|
|
1010
|
-
"$value": [
|
|
1011
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[1,1,1],"alpha":0.18}},
|
|
1012
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":6,"unit":"px"},"blur":{"value":16,"unit":"px"},"spread":{"value":-10,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.85}}
|
|
1013
|
-
]
|
|
1014
|
-
},
|
|
1015
|
-
"overlay": {
|
|
1016
|
-
"$value": [
|
|
1017
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":12,"unit":"px"},"blur":{"value":26,"unit":"px"},"spread":{"value":-12,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.9}}
|
|
1018
|
-
]
|
|
1019
|
-
},
|
|
1020
|
-
"modal": {
|
|
1021
|
-
"$value": [
|
|
1022
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":0,"unit":"px"},"blur":{"value":0,"unit":"px"},"spread":{"value":1,"unit":"px"},"color":{"colorSpace":"srgb","components":[1,1,1],"alpha":0.18}},
|
|
1023
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":16,"unit":"px"},"blur":{"value":32,"unit":"px"},"spread":{"value":-14,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.95}}
|
|
1024
|
-
]
|
|
1025
|
-
},
|
|
1026
|
-
"composer": {
|
|
1027
|
-
"$value": [
|
|
1028
|
-
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":1,"unit":"px"},"blur":{"value":2,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.5}}
|
|
1029
|
-
]
|
|
1030
|
-
},
|
|
1031
|
-
"overlay-border": {
|
|
1032
|
-
"$type": "border",
|
|
1033
|
-
"$value": {"width":{"value":1,"unit":"px"},"style":"solid","color":{"colorSpace":"srgb","components":[1,1,1],"alpha":0.18}}
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
}
|
|
1037
803
|
}
|
|
1038
804
|
}
|
|
1039
805
|
}
|
|
1040
806
|
```
|
|
1041
|
-
|
|
1042
|
-
### Generated outputs
|
|
1043
|
-
|
|
1044
|
-
`scripts/build.ts` runs the pipeline once. The `@google/design.md` CLI exports the YAML front matter, `scripts/sd/design-tokens-fence.ts` reads the one `json design-tokens` fence, and `scripts/sd/compose-token-graph.ts` composes the two into the single `nadicode` graph. That composer is the only code that creates the root: it merges without collision, rewrites every short alias to its rooted public form, and checks ownership and reference direction (`core` reaches outside nothing, a register reaches `core` and itself, never another register).
|
|
1045
|
-
|
|
1046
|
-
The composer returns three views of one validated object. The canonical graph keeps every alias and every `$extensions` entry and ships as `dist/tokens/nadicode.dtcg.json` (`@nadicodeai/design-system/tokens/dtcg`), the public contract a conformance tool or a theme generator reads. The resolved graph replaces each alias with its value and ships as `dist/tokens/nadicode.dtcg-resolved.json` (`./tokens/dtcg-resolved`), for data-only consumers that want literals. Both are validated against the vendored DTCG 2025.10 schema (`scripts/sd/schemas/`) before they are written. The third view is the CSS projection: the leaves of every group whose `ai.nadicode.targets` includes `css`, which is `core` plus `register.website` and `register.portal`; `register.nadia` is data only and reaches no stylesheet.
|
|
1047
|
-
|
|
1048
|
-
Style Dictionary (`scripts/sd/`) is the exporter over that projection, not the validator. It emits three CSS files:
|
|
1049
|
-
|
|
1050
|
-
1. The token layer `tokens.generated.css`: a complete projection of every export token, with each emitted `--nc-*` name appearing once. Never an allowlist or subset, and never containing `@media` or `calc()`; responsive and composed runtime variables live only in their owning layer and reference emitted `--nc-*` tokens. Names map per group: `core.color` and `core.spacing` emit `--nc-<name>`, as do `register.website.layout` and `register.portal.density`, so a moved token keeps the name it had; `core.rounded` emits `--nc-rounded-<name>`; `core.material` emits `--nc-material-<name>`; `core.elevation.light.<role>` emits `--nc-elevation-<role>` in `:root` and `core.elevation.dark.<role>` the same name inside `.dark`; `core.motion.duration` emits `--nc-duration-<name>` and `core.motion.ease` emits `--nc-ease-<name>`; and each composite `typography` scale expands per property into `--nc-type-<scale>-font-family`, `-font-size`, `-font-weight`, `-line-height`, and `-letter-spacing`, never the CSS `font:` shorthand, which drops the line-height unit. Values serialize deterministically: authored hex colors emit as `oklch()`, dimensions keep their authored value and unit, typography line-height comes from this contract's own unit-preserving Tailwind export because the DTCG composite export carries it as a bare number, durations emit as authored, easings as `cubic-bezier()`, elevation recipes as the complete multi-layer `box-shadow` for their mode, and the material ratios as the percentages their callers read. Emitted-but-unreferenced contract tokens remain part of the package vocabulary; private compatibility variables with no caller do not.
|
|
1051
|
-
2. The mode layer `modes.generated.css`: derives every color pair named `X` and `dark-X` from the complete token graph, then remaps `--nc-X` inside `.dark`. There is no component list or token allowlist to maintain. A dark-only token without an `X` counterpart remains available by its explicit name and is not remapped.
|
|
1052
|
-
3. The theme layer `theme.generated.css`: emits the standard shadcn `:root` and `.dark` role maps, the Tailwind v4 `@theme inline` semantic bridge, and the raw contract projection. Standard `--color-*` utilities are reserved for semantic shadcn roles. Raw contract colors use the explicit `--color-nc-*` namespace; the remaining Tailwind token families keep their generated font, type, radius, and spacing namespaces. Every emitted value derives from this contract, and the formatter holds no raw `--nc-*` mode-remap table. The projection fills `--ease-nc-<name>` for every easing; it fills no `--shadow-*` entry, because the four elevation rungs reach React only through the owned `nc-elevation-*` utilities, one spelling per rung; Tailwind v4 owns no duration namespace, so a call site writes `duration-(--nc-duration-open)`. The raw `@theme` block also carries a small hand-maintained alias set — `--outline-width-focus`, `--outline-offset-focus`, `--opacity-scrim`, `--spacing-control-height`, `--spacing-control-padding-inline`, `--spacing-touch-target` — each a `var()` reference onto a generated token, and each must have a live caller or be removed.
|
|
1053
|
-
|
|
1054
|
-
The private source entry imports all three generated files, and the package build compiles the complete graph into an import-free public artifact. Raw mode behavior and the Tailwind semantic bridge therefore belong to the framework-neutral design-system module, not to the React adapter or individual CSS components. `@nadicodeai/ui/globals.css` composes that complete module with Tailwind and owns only narrow technology integration, such as ANSI class translation; React components consume the generated shadcn roles or namespaced raw utilities directly.
|
|
1055
|
-
|
|
1056
|
-
`npm run build` also writes the two DTCG artifacts above and `dist/tailwind/nadicode.tailwind.json` as generated, tracked data artifacts, and bakes `dist/icons/` from the pinned `lucide-static` dependency. The Tailwind export stays for what it uniquely supplies: the typography line-height with its declared unit, which the DTCG composite normaliser strips, and the logo paint lookup.
|
|
1057
|
-
|
|
1058
|
-
### Enforcement
|
|
1059
|
-
|
|
1060
|
-
Correctness is structural, not a byte snapshot: `scripts/sd/design-tokens-fence.test.ts` verifies the fence reader, one fence, strict JSON, typed leaves, legal names; `scripts/sd/compose-token-graph.test.ts` verifies the composition, one construction of the root, alias rewriting, ownership and reference direction, extension preservation, and the CSS projection; `tests/guards/dtcg-shape.test.ts` verifies both published artifacts against the vendored DTCG schema and the Nadicode profile; `tests/guards/token-structural-parity.test.ts` verifies the complete `--nc-*` projection; `tests/guards/tailwind-v4-theme.test.ts` verifies mode-pair derivation, `@theme` values, and light/dark role integrity; `tests/guards/component-contract-matches-css.test.ts` verifies the retained CSS component surface against this contract; `tests/guards/css-bundle.test.ts` verifies the public artifact is flat, complete, and contains no shipped partials; and `tests/guards/consumer-css-discipline.test.ts` verifies reference integrity, the absence of raw color literals in the hand-authored kit CSS and of raw color and radius literals in `examples/`, second value sources, and DTCG drift. These guards read freshly built output (the test global-setup rebuilds before any of them run), so they prove the generator; a hand-edited or stale committed generated file is caught by the CI cleanliness step in the `design-system` job, which requires a fresh build to reproduce the committed bytes exactly, with `dist/favicon/**` outside it, regenerated only by `generate:favicons` and fenced by review rather than machinery. Cross-package guards verify that React callers use only the React stylesheet interface.
|