@nadicodeai/design-system 1.1.1 → 2.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 +12 -5
- package/CONTEXT.md +16 -49
- package/DESIGN.md +507 -153
- package/LICENSES/shadow-plugin-MIT.txt +21 -0
- package/README.md +30 -1
- package/dist/css/index.css +134 -75
- package/dist/tailwind/nadicode.tailwind.json +9 -55
- package/dist/tokens/nadicode.dtcg-resolved.json +5092 -0
- package/dist/tokens/nadicode.dtcg.json +4602 -2169
- package/package.json +5 -1
package/DESIGN.md
CHANGED
|
@@ -2,18 +2,18 @@
|
|
|
2
2
|
version: alpha
|
|
3
3
|
name: NadicodeAI Design System
|
|
4
4
|
description: >-
|
|
5
|
-
Reusable design-system contract for NadicodeAI:
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
Reusable design-system contract for NadicodeAI: one foundation of colour,
|
|
6
|
+
type, spacing, radius, material, elevation, and motion, applied by three
|
|
7
|
+
registers, the website, the Portal, and the Nadia desktop app, plus the
|
|
8
|
+
package token, CSS, and asset exports they consume.
|
|
9
9
|
|
|
10
10
|
colors:
|
|
11
11
|
primary: "#007a3c"
|
|
12
12
|
on-primary: "#ffffff"
|
|
13
|
-
action: "#
|
|
13
|
+
action: "#111111"
|
|
14
14
|
action-foreground: "#ffffff"
|
|
15
|
-
action-hover: "#
|
|
16
|
-
action-active: "#
|
|
15
|
+
action-hover: "#2e2e2e"
|
|
16
|
+
action-active: "#000000"
|
|
17
17
|
ink: "#111111"
|
|
18
18
|
body: "#444444"
|
|
19
19
|
muted: "#6b6b6b"
|
|
@@ -45,6 +45,7 @@ colors:
|
|
|
45
45
|
warning-soft: "#fff3d1"
|
|
46
46
|
warning-deep: "#7a5800"
|
|
47
47
|
flag-red: "#d3302f"
|
|
48
|
+
verde-vivo: "#008c45"
|
|
48
49
|
selection-bg: "#111111"
|
|
49
50
|
selection-fg: "#fafafa"
|
|
50
51
|
navigation-selection-bg: "#ededed"
|
|
@@ -91,10 +92,10 @@ colors:
|
|
|
91
92
|
dark-seam: "#1a1a1a"
|
|
92
93
|
dark-primary: "#2fd584"
|
|
93
94
|
dark-on-primary: "#0a0a0a"
|
|
94
|
-
dark-action: "#
|
|
95
|
+
dark-action: "#ffffff"
|
|
95
96
|
dark-action-foreground: "#0a0a0a"
|
|
96
|
-
dark-action-hover: "#
|
|
97
|
-
dark-action-active: "#
|
|
97
|
+
dark-action-hover: "#e4e4e4"
|
|
98
|
+
dark-action-active: "#d6d6d6"
|
|
98
99
|
dark-link: "#4ecf8c"
|
|
99
100
|
dark-link-deep: "#93e3b8"
|
|
100
101
|
dark-link-bg-soft: "#00351c"
|
|
@@ -126,6 +127,7 @@ colors:
|
|
|
126
127
|
dark-navigation-selection-fg: "#ededed"
|
|
127
128
|
dark-navigation-selection-hover: "#262626"
|
|
128
129
|
dark-cross: "#555555"
|
|
130
|
+
dark-verde-vivo: "#62d494"
|
|
129
131
|
dark-chart-1: "#4d6bff"
|
|
130
132
|
dark-chart-2: "#00a85a"
|
|
131
133
|
dark-chart-3: "#ffd866"
|
|
@@ -266,7 +268,6 @@ rounded:
|
|
|
266
268
|
|
|
267
269
|
spacing:
|
|
268
270
|
xxs: 4px
|
|
269
|
-
filter-chip-gap: 6px
|
|
270
271
|
xs: 8px
|
|
271
272
|
sm: 12px
|
|
272
273
|
md: 16px
|
|
@@ -277,7 +278,6 @@ spacing:
|
|
|
277
278
|
4xl: 64px
|
|
278
279
|
5xl: 96px
|
|
279
280
|
6xl: 128px
|
|
280
|
-
section: 192px
|
|
281
281
|
guide: 1px
|
|
282
282
|
icon-stroke: 1.6px
|
|
283
283
|
focus-outline-width: 2px
|
|
@@ -285,75 +285,31 @@ spacing:
|
|
|
285
285
|
control-height: 32px
|
|
286
286
|
control-padding-inline: 10px
|
|
287
287
|
touch-target: 44px
|
|
288
|
-
page-pad: 24px
|
|
289
|
-
page-max-mobile: 448px
|
|
290
|
-
page-max-tablet: 624px
|
|
291
|
-
page-max-desktop: 1440px
|
|
292
|
-
frame-max: 1392px
|
|
293
|
-
frame-gap: 24px
|
|
294
|
-
cell-padding: 24px
|
|
295
|
-
section-gap: 48px
|
|
296
|
-
cross-size-mobile: 11px
|
|
297
|
-
cross-size-tablet: 15px
|
|
298
|
-
cross-size-desktop: 21px
|
|
299
|
-
h-strip-mobile: 72px
|
|
300
|
-
h-strip-tablet: 80px
|
|
301
|
-
h-strip-desktop: 88px
|
|
302
|
-
h-cell-sm: 120px
|
|
303
|
-
h-cell-md-mobile: 144px
|
|
304
|
-
h-cell-md-tablet: 160px
|
|
305
|
-
h-cell-md-desktop: 168px
|
|
306
|
-
h-cell-lg-mobile: 220px
|
|
307
|
-
h-cell-lg-desktop: 240px
|
|
308
|
-
h-visual-mobile: 276px
|
|
309
|
-
h-visual-tablet: 318px
|
|
310
|
-
h-visual-desktop: 360px
|
|
311
|
-
h-precision-mobile: 276px
|
|
312
|
-
h-precision-tablet: 480px
|
|
313
|
-
h-precision-desktop: 720px
|
|
314
|
-
h-hero-mobile: 560px
|
|
315
|
-
h-hero-desktop: 720px
|
|
316
|
-
h-action-strip-mobile: 20px
|
|
317
|
-
h-action-strip-desktop: 32px
|
|
318
|
-
h-action-body-mobile: 320px
|
|
319
|
-
h-action-body-tablet: 338px
|
|
320
|
-
h-action-band: 260px
|
|
321
|
-
h-feature-mobile: 360px
|
|
322
|
-
h-feature-tablet: 400px
|
|
323
|
-
h-feature-desktop: 456px
|
|
324
|
-
h-editorial-feature-mobile: 360px
|
|
325
|
-
h-editorial-feature-tablet: 440px
|
|
326
|
-
h-editorial-feature-desktop: 560px
|
|
327
|
-
h-bento-primary: 384px
|
|
328
|
-
h-bento-secondary: 240px
|
|
329
|
-
h-bento-mobile-copy: 248px
|
|
330
|
-
h-bento-mobile-main: 537px
|
|
331
|
-
h-bento-strip: 20px
|
|
332
|
-
filter-chip-height: 28px
|
|
333
|
-
filter-chip-icon: 14px
|
|
334
288
|
|
|
335
289
|
---
|
|
336
290
|
|
|
337
291
|
## Overview
|
|
338
292
|
|
|
339
|
-
NadicodeAI is an AI agency for SMBs. The design system
|
|
293
|
+
NadicodeAI is an AI agency for SMBs. The design system makes the website and the shipped product read as one family: a calm, high-contrast monochrome architecture in which the identity layer — Nadia's portrait, the Agent identity visuals, and the verde brand accent in its scoped duties — is the expressive element. Structure, chrome, and controls are neutral; a desaturated screen loses nothing structural. The interface helps a pragmatic buyer understand the work: repeated workflows, agents working beside people, inspectable outputs, human approval.
|
|
340
294
|
|
|
341
|
-
|
|
295
|
+
One foundation, three registers. The foundation is every `core` value in this contract: the colour roles and their dark pairs, the two type families and the type scale, the spacing ladder and the control geometry, the radius scale, the material values, the four-rung elevation ladder, the motion durations and easings, the icon render, and the brand marks. It is owned by no single product and available to every product; a value stays foundation whether one product renders it or three. A register is one product's way of applying it: its direction, its density, the rungs and steps it reaches for, its dark-mode stance, its structural grammar, its motion budget. The three registers are the website, the Portal, and the Nadia desktop app; `## Registers` names each one's rulebook, its own token families, and its dark-mode stance.
|
|
296
|
+
|
|
297
|
+
A register decides where a foundation value appears and adds rules of its own. It never redefines a foundation value and never mints one: a value its physics need and the foundation lacks is authored in this contract under `register.<name>`.
|
|
342
298
|
|
|
343
299
|
The kernel is the reusable implementation layer. `DESIGN.md` defines the reusable contract and exported scalar tokens; the `@nadicodeai/design-system/css` bundle (`src/css/**`) demonstrates and exercises that contract. Homepage copy, page-specific ordering, and temporary prototypes do not belong in this file.
|
|
344
300
|
|
|
345
301
|
## Colors
|
|
346
302
|
|
|
347
|
-
|
|
303
|
+
Neutrals carry the interface: grounds, chrome, hierarchy, and every control come from the achromatic spine, and the strongest contrast on any surface lands on the primary action. Chromatic colour appears where it says something a reader already knows how to read: the semantic feedback hues, the verde brand accent in its scoped duties (links, success, presence, identity marks), the chart palettes on data, and the Agent identity palette inside the Agent visuals. Chrome, sections, and controls stay unpainted.
|
|
348
304
|
|
|
349
|
-
Interaction states are material, not meaning: focus, hover, and selection read as contrast steps on the neutral spine
|
|
305
|
+
Interaction states are material, not meaning: focus, hover, and selection read as contrast steps on the neutral spine. A hover ground is a neutral step; the only change a control hover carries is a step inside its own family, as `{colors.action-hover}` does on the action control. Cobalto stays on info and running.
|
|
350
306
|
|
|
351
|
-
Every value a role needs is carried by the role token itself; the ten-step numeric scale families were removed deliberately and must not be reintroduced. Two neutral page grounds sit under the roles below: `{colors.background-100}` (page default, white) and `{colors.background-200}` (soft, used sparingly). The neutral spine — `{colors.ink}`, `{colors.body}`, `{colors.muted}`, `{colors.canvas}`, `{colors.canvas-soft}`, `{colors.canvas-soft-2}`, `{colors.line}`, `{colors.seam}` — is strictly achromatic: every neutral is an equal-channel gray (OKLCH chroma 0) in both themes. The five
|
|
307
|
+
Every value a role needs is carried by the role token itself; the ten-step numeric scale families were removed deliberately and must not be reintroduced. Two neutral page grounds sit under the roles below: `{colors.background-100}` (page default, white) and `{colors.background-200}` (soft, used sparingly). The neutral spine — `{colors.ink}`, `{colors.body}`, `{colors.muted}`, `{colors.canvas}`, `{colors.canvas-soft}`, `{colors.canvas-soft-2}`, `{colors.line}`, `{colors.seam}` — is strictly achromatic: every neutral is an equal-channel gray (OKLCH chroma 0) in both themes. The five identity-palette hues are cross-spectrum equals, so any neutral bias would flatter one and dirty another; hue arrives full-strength through the chromatic roles or not at all, and no tint ever paints an app surface. Colours are authored as sRGB hex here and emitted as `oklch()` in the generated CSS.
|
|
352
308
|
|
|
353
309
|
### Semantic roles (functional tier)
|
|
354
310
|
|
|
355
|
-
- **
|
|
356
|
-
- **
|
|
311
|
+
- **Action** (`{colors.action}` = ink `#111111`, `{colors.action-foreground}` = white, `{colors.action-hover}`, `{colors.action-active}`): the primary-control family is neutral — a white label on the ink field in light shells, and in dark shells the `dark-action-*` roles, a white control carrying a near-black label. Maximum contrast belongs to the thing that acts. The shadcn `primary` roles map here.
|
|
312
|
+
- **Brand accent — verde** (`{colors.primary}` = deep verde `#007a3c`, white `{colors.on-primary}` text on its field): identity marks, logo contexts, and brand accents. Verde's working duties are exactly the scoped roles below — links, success, and Nadia's presence — and the brighter verde `#00a85a` stays the imagery-tier anchor, never carrying normal-size white text.
|
|
357
313
|
- **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. Never darken a decorative border locally to make it behave like a control.
|
|
358
314
|
- **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.
|
|
359
315
|
- **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.
|
|
@@ -361,37 +317,27 @@ Every value a role needs is carried by the role token itself; the ten-step numer
|
|
|
361
317
|
- **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.
|
|
362
318
|
- **Navigation selection** (`{colors.navigation-selection-bg}`, `{colors.navigation-selection-fg}`, `{colors.navigation-selection-hover}`): the selected destination in persistent product navigation, a neutral ink pill. 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 pill fill and the adjacent canvas.
|
|
363
319
|
- **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.
|
|
364
|
-
- **Accent** (`{colors.accent}` = arancio `#ff5a1f`):
|
|
320
|
+
- **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.
|
|
365
321
|
- **Flag Red** (`{colors.flag-red}` = `#d3302f`): the identity-mark red alias. It shares its hex with `{colors.error}` but stays a distinct role name — `flag-red` is identity, `error` is validation feedback. The attention accent is arancio, not flag red.
|
|
322
|
+
- **Verde vivo** (`{colors.verde-vivo}` = `#008c45`, dark `{colors.dark-verde-vivo}` = `#62d494`): Nadia's presence accent. It paints the marks that say *Nadia herself is here*: the first-run medallion, a live-presence indicator beside her portrait. It is a mark colour, never a control fill, a text colour, a link, or a status; `action`, `link`, and `success` keep their jobs. It clears the 3:1 non-text floor on `{colors.canvas}` (4.3:1) and `{colors.dark-canvas}` (11.4:1) and carries no text of its own. White on it is 4.34:1, below AA for normal text, so a label never sits on a `verde-vivo` fill; the ring, halo, or portrait sits on it and the words sit on the canvas.
|
|
366
323
|
- **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 and bullet segments, 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.
|
|
367
324
|
|
|
368
325
|
### Chart categoricals (functional tier)
|
|
369
326
|
|
|
370
|
-
- **Charts** (`{colors.chart-1}`…`{colors.chart-6}`): the categorical series palette — cobalto, verde, giallo, rosso, plus a functional-only teal and viola.
|
|
327
|
+
- **Charts** (`{colors.chart-1}`…`{colors.chart-6}`): the categorical series palette — cobalto, verde, giallo, rosso, plus a functional-only teal and viola. Chart colours belong to data surfaces only; identity planes and generated imagery draw from the Agent identity palette. Two light slots are series-tuned so the set clears the categorical data bands on the white data surface: `chart-3` steps the giallo darker (the campo giallo `{colors.campo-giallo}` overshoots the categorical lightness band and reads too pale as a mark) and `chart-5` makes the teal more chromatic (the raw functional teal otherwise sits under the chroma floor and reads gray). The darker giallo still falls below the 3:1 mark-contrast line and is carried by the always-on legend/direct-label/table relief the charting method already mandates — never a per-surface patch. On the dark canvas all six series carry dark-tuned steps (`{colors.dark-chart-1}`…`{colors.dark-chart-6}`), remapped in the `.dark` scope so any consumer reading the semantic token gets the right hue in both modes. Clearing the 3:1 WCAG 1.4.11 data-mark floor is necessary but not sufficient on the black canvas: full-strength hues read as loud blocks there, so each dark step lightens or desaturates while keeping series identity — the dark giallo and teal deliberately stay bright to hold their per-series deuteranopia separation from rosso — and all six clear 3:1 against `{colors.dark-canvas}`.
|
|
371
328
|
- **Categorical distinguishability**: the same-family neighbours are held apart — `chart-3`/`chart-4` (giallo/rosso) and `chart-2`/`chart-5` (verde/teal) each clear a CIEDE2000 ≥ 20 normal / ≥ 15 deuteranopia-simulated floor, in the light set and the dark-tuned set alike. Two proximities are brand-locked and exempt from the numeric floor: `chart-2`/`chart-4` (verde/rosso, the flag pair, which collapses under red-green deficiency by identity) and `chart-1`/`chart-6` (cobalto/viola, adjacent blue-violet). Charts using the full six carry redundant encoding (direct labels, position, or pattern), never hue alone, so these two pairs stay readable under colour-vision deficiency.
|
|
372
329
|
|
|
373
|
-
###
|
|
374
|
-
|
|
375
|
-
The campi are full-strength named brand fields. They carry NO tints, NO steps, and NO hover variants; UI states always come from the functional roles, never from a campo.
|
|
376
|
-
|
|
377
|
-
- `{colors.campo-verde}` (Verde Bandiera), `{colors.campo-rosso}` (Rosso Bandiera), and `{colors.campo-cobalto}` pair with `{colors.campo-bianco}` (white) type.
|
|
378
|
-
- `{colors.campo-arancio}` (vermilion) and `{colors.campo-giallo}` (giallo) pair with `{colors.campo-inchiostro}` (ink) type.
|
|
379
|
-
- `{colors.campo-inchiostro}` pairs with white type; `{colors.campo-bianco}` pairs with ink type.
|
|
330
|
+
### Agent identity palette
|
|
380
331
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
1. **One campo per room.** A section owns exactly one field; adjacent sections must jump in luminance, so two mid-tone rooms never touch.
|
|
384
|
-
2. **Type on a campo is bianco or inchiostro only.** Arancio and Giallo carry ink; Verde, Rosso, Cobalto, and Inchiostro carry white. No tinted text on fields.
|
|
385
|
-
3. **Campi are planes, not chips.** The minimum duty of a campo is a section ground, poster field, or full media mount. UI states come from the functional roles, never campi.
|
|
386
|
-
4. **One paint box.** Generated editorial imagery draws its flat fields from these values; `brand/visual-design/EDITORIAL-ILLUSTRATION.md` references these tokens and keeps no private palette.
|
|
332
|
+
Five full-strength hues are the system's expressive chromatic range: verde `{colors.campo-verde}`, rosso `{colors.campo-rosso}`, cobalto `{colors.campo-cobalto}`, arancio `{colors.campo-arancio}`, and giallo `{colors.campo-giallo}`, with `{colors.campo-bianco}` and `{colors.campo-inchiostro}` as the white and ink poles that carry type beside them. They live in exactly three places: the Agent identity visuals (the metaball recipes in [`packages/ui/docs/agent-visual.md`](../ui/docs/agent-visual.md)), the brand book's identity planes, and generated editorial imagery (`skills/marketing/blog-imagegen/references/editorial-register.md` reads these tokens and keeps no private palette). They appear at full strength with no tints, steps, or hover variants; interface states come from the semantic roles. The `campo-*` spelling is the emitted compatibility token name; renaming this family across its consumers is its own tracked migration.
|
|
387
333
|
|
|
388
334
|
### Dark primitives
|
|
389
335
|
|
|
390
|
-
**Dark Primitives** (`{colors.dark-canvas}`, `{colors.dark-ink}`, `{colors.dark-line}`, and siblings) are the source tokens for the generated shadcn `.dark` role map — mode-bearing primitives, not a separate palette; consuming apps opt in by applying a `.dark` ancestor through their runtime theme provider. `dark-canvas` is true black: the only base plane for dark product shells. The remaining dark neutrals (`dark-canvas-soft`, `dark-canvas-soft-2`, `dark-ink`, `dark-body`, `dark-muted`, `dark-line`, `dark-input`, `dark-seam`, `dark-on-primary`, `dark-scrim`, and the neutral `dark-selection`/`dark-cross` marks) are exact equal-channel grays (OKLCH chroma 0), so no hue paints a surface. The soft canvas steps belong only to bounded interaction states such as hover, selection, keycaps, and inline controls; they never replace true black as a page, section, card, popover, sidebar, or large-panel ground. `dark-input` is the resting-control boundary, held brighter than the decorative `dark-line` border so a field edge clears the non-text floor before `{colors.dark-focus-ring}` appears. Chroma lives only in the intentional accents: `dark-primary
|
|
336
|
+
**Dark Primitives** (`{colors.dark-canvas}`, `{colors.dark-ink}`, `{colors.dark-line}`, and siblings) are the source tokens for the generated shadcn `.dark` role map — mode-bearing primitives, not a separate palette; consuming apps opt in by applying a `.dark` ancestor through their runtime theme provider. `dark-canvas` is true black: the only base plane for dark product shells. The remaining dark neutrals (`dark-canvas-soft`, `dark-canvas-soft-2`, `dark-ink`, `dark-body`, `dark-muted`, `dark-line`, `dark-input`, `dark-seam`, `dark-on-primary`, `dark-scrim`, and the neutral `dark-selection`/`dark-cross` marks) are exact equal-channel grays (OKLCH chroma 0), so no hue paints a surface. The soft canvas steps belong only to bounded interaction states such as hover, selection, keycaps, and inline controls; they never replace true black as a page, section, card, popover, sidebar, or large-panel ground. `dark-input` is the resting-control boundary, held brighter than the decorative `dark-line` border so a field edge clears the non-text floor before `{colors.dark-focus-ring}` appears. The `dark-action` family is a white control stepping through light grays, ink-labelled — the primary control stays neutral on black. Chroma lives only in the intentional accents: `dark-primary` is the brightened brand verde for identity marks, `dark-link`/`dark-link-deep` a lighter verde for links, and `dark-error`/`dark-error-deep`, `dark-warning`/`dark-warning-deep`, `dark-info`/`dark-info-deep`, `dark-success`/`dark-success-deep` the brightened feedback hues, each `*-soft` a deep tinted ground. `dark-navigation-selection-*` and `dark-focus-ring` sit on the neutral spine: selection and focus are material states, not meanings. The `dark-state-*` workflow family is not a text role; it preserves the matching ready/running/review/blocked/complete rail colours for 3 px state accents while text stays on neutral foreground roles. `dark-selection-bg`/`dark-selection-fg` invert the light selection, `dark-scrim` supplies the modal foreground, and `dark-cross` is the visible mark gray, brighter than the decorative `dark-line`. Never reintroduce chroma into the neutral set.
|
|
391
337
|
|
|
392
|
-
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 explicit semantic roles
|
|
338
|
+
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.
|
|
393
339
|
|
|
394
|
-
|
|
340
|
+
A colour leaves this contract only through an explicit palette migration decision; what the current website kernel happens to consume is not palette authority.
|
|
395
341
|
|
|
396
342
|
## Data Visualization
|
|
397
343
|
|
|
@@ -453,15 +399,60 @@ All body, title, caption, and code typography uses `letterSpacing: 0px`. Do not
|
|
|
453
399
|
|
|
454
400
|
## Layout
|
|
455
401
|
|
|
456
|
-
|
|
402
|
+
Layout is mathematical and single-owner in every register: one owner per visible line, every gap and height from the tokens below, never eyeballed.
|
|
403
|
+
|
|
404
|
+
The page grammar this section documents, the 12-column frame with its rows, its centered 10-column active lane, its stage and its markers, is the website register's structure. The Portal and the Nadia desktop app build their own structure by their rulebooks (`## Registers`) and reach for the spacing ladder and the control geometry, not for the frame.
|
|
405
|
+
|
|
406
|
+
### Spacing
|
|
407
|
+
|
|
408
|
+
One ladder carries every gap, pad, and stack step. A register picks the rungs it needs; it never interpolates between them.
|
|
409
|
+
|
|
410
|
+
| Token | Value |
|
|
411
|
+
| --- | --- |
|
|
412
|
+
| `{spacing.xxs}` | 4 px |
|
|
413
|
+
| `{spacing.xs}` | 8 px |
|
|
414
|
+
| `{spacing.sm}` | 12 px |
|
|
415
|
+
| `{spacing.md}` | 16 px |
|
|
416
|
+
| `{spacing.lg}` | 24 px |
|
|
417
|
+
| `{spacing.xl}` | 32 px |
|
|
418
|
+
| `{spacing.2xl}` | 40 px |
|
|
419
|
+
| `{spacing.3xl}` | 48 px |
|
|
420
|
+
| `{spacing.4xl}` | 64 px |
|
|
421
|
+
| `{spacing.5xl}` | 96 px |
|
|
422
|
+
| `{spacing.6xl}` | 128 px |
|
|
423
|
+
|
|
424
|
+
Control geometry is foundation, so a control has the same physical size and the same focus treatment in every register.
|
|
425
|
+
|
|
426
|
+
| Token | Value | Owns |
|
|
427
|
+
| --- | --- | --- |
|
|
428
|
+
| `{spacing.guide}` | 1 px | Every ordinary seam, and the crosshair weight |
|
|
429
|
+
| `{spacing.icon-stroke}` | 1.6 px | The house icon stroke |
|
|
430
|
+
| `{spacing.focus-outline-width}` | 2 px | The one keyboard focus outline |
|
|
431
|
+
| `{spacing.focus-outline-offset}` | 2 px | That outline's offset |
|
|
432
|
+
| `{spacing.control-height}` | 32 px | Minimum height of every default control |
|
|
433
|
+
| `{spacing.control-padding-inline}` | 10 px | Inline padding of a control |
|
|
434
|
+
| `{spacing.touch-target}` | 44 px | Minimum target on coarse or non-hover input |
|
|
435
|
+
|
|
436
|
+
Page-grammar geometry and the fixed component heights 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").
|
|
437
|
+
|
|
438
|
+
| Family | Tokens | Register |
|
|
439
|
+
| --- | --- | --- |
|
|
440
|
+
| Band rhythm | `section` | Website |
|
|
441
|
+
| Page shell | `page-pad`, `page-max-mobile`, `page-max-tablet`, `page-max-desktop` | Website |
|
|
442
|
+
| Frame | `frame-max`, `frame-gap`, `cell-padding`, `section-gap` | Website |
|
|
443
|
+
| Crosshair | `cross-size-mobile`, `cross-size-tablet`, `cross-size-desktop` | Website |
|
|
444
|
+
| Cell and strip heights | `h-cell-sm`, `h-cell-md-*`, `h-cell-lg-*`, `h-strip-*` | Website |
|
|
445
|
+
| Media and stage heights | `h-visual-*`, `h-precision-*`, `h-hero-*` | Website |
|
|
446
|
+
| Band and feature heights | `h-action-strip-*`, `h-action-body-*`, `h-action-band`, `h-feature-*`, `h-editorial-feature-*` | Website |
|
|
447
|
+
| Bento heights | `h-bento-primary`, `h-bento-secondary`, `h-bento-mobile-copy`, `h-bento-mobile-main`, `h-bento-strip` | Website |
|
|
448
|
+
| Filter chip | `filter-chip-gap`, `filter-chip-height`, `filter-chip-icon` | Portal |
|
|
457
449
|
|
|
458
450
|
### Frame System
|
|
459
451
|
|
|
460
|
-
-
|
|
461
|
-
- `page-
|
|
462
|
-
- `page-frame` is centered and capped at `{spacing.frame-max}`. It owns the outer left/right/top borders.
|
|
452
|
+
- `page-system` adds `--nc-page-pad` horizontal page padding and `{spacing.4xl}` vertical padding, so the frame floats on clear canvas between the sticky nav chrome and the footer instead of stacking against their hairlines.
|
|
453
|
+
- `page-frame` is centered and capped at `--nc-frame-max`. It owns the outer left/right/top borders.
|
|
463
454
|
- `page-row` is the only direct section unit inside `page-frame`. It owns the bottom border for each row.
|
|
464
|
-
- Desktop rows use 12 equal columns with
|
|
455
|
+
- Desktop rows use 12 equal columns with `--nc-frame-gap` gutters.
|
|
465
456
|
- `active-lane` spans columns 2 through 11 on desktop and all columns on smaller viewports.
|
|
466
457
|
- Full-lane rows span all 12 columns only when the content owns columns 1 and 12 explicitly through the `stage` geometry (`nc-stage-frame`/`nc-stage-fill`/`nc-stage-grid`/`nc-stage-active`).
|
|
467
458
|
- Columns 1 and 12 are the page's quiet margin: empty canvas by default. Their presence is the whitespace itself — the grid sizes them whether or not anything paints there, matching the reference container model where structure lives inside bounded modules and never as page-length gutter texture. The diagonal `side-fill` is a scarce structural accent a row opts into with `data-rails="hatch"`; reserve it for the page's anchor rows (the opening hero, the closing conversion row), never as a default on every row. When present, the fill is bounded on both sides — the frame perimeter outside, the fill's own inner rail inside — so the texture reads as a deliberate panel, not an unbounded wash.
|
|
@@ -488,7 +479,7 @@ Kernel compositions arrange approved primitives for a content role. They must no
|
|
|
488
479
|
|
|
489
480
|
Each reusable concern has one home and one authoring surface, so a member is documented and shipped in exactly one place:
|
|
490
481
|
|
|
491
|
-
- **Tokens** — exact reusable values live in this contract's `colors`, `spacing`, `rounded`, and `typography
|
|
482
|
+
- **Tokens** — exact reusable values live in this contract's `colors`, `spacing`, `rounded`, and `typography` front matter, and in the one `json design-tokens` fence for material, elevation, motion, and the register groups; they export to the generated token layer. No component or section may re-encode a token value.
|
|
492
483
|
- **Framework-agnostic CSS components** — the brand/display CSS components that remain in this package (agent-work artifacts, `image`, and the small label/badge/link metadata components). Their names and semantics live once in `## Components`; design-system CSS realises that catalog without a second recipe or component-token hierarchy.
|
|
493
484
|
- **Reusable compositions and sections** — patterns that arrange the page grammar and lower tiers for a content role. They ship as React shadcn compositions from `@nadicodeai/ui`, composed over the kept page grammar; they are not a pure-CSS section layer and do not add a second component catalog here.
|
|
494
485
|
- **Layout grammar** — the page-frame, row, lane, seam, stage, and marker system documented across this `## Layout` section, carried by the layout CSS layer.
|
|
@@ -500,7 +491,7 @@ The page grammar below is the `## Layout` home for the structural names; the sec
|
|
|
500
491
|
|
|
501
492
|
Page grammar is the NadicodeAI structural system. It defines how pages are built before any content surface appears. These names live in this layout section, not in the component catalog. Exact reusable values are exported through `colors`, `spacing`, `rounded`, and `typography`; the structural semantics live here as layout doctrine, not as interface components.
|
|
502
493
|
|
|
503
|
-
**`page-frame`** (`nc-page-frame`) — centered bordered frame capped by
|
|
494
|
+
**`page-frame`** (`nc-page-frame`) — centered bordered frame capped by `--nc-frame-max`. Owns the outer left/right/top seams.
|
|
504
495
|
|
|
505
496
|
**`page-row`** (`nc-page-row`) — one continuous section row inside `page-frame`. Owns the bottom seam and default row background.
|
|
506
497
|
|
|
@@ -510,7 +501,7 @@ Page grammar is the NadicodeAI structural system. It defines how pages are built
|
|
|
510
501
|
|
|
511
502
|
**`cell-grid`** (`nc-row-grid`) — repeated cell math. Uses `{colors.line}` parent background and `{spacing.guide}` gaps so the seam has one owner. Supports 2, 3, 4, 5, and 6 column desktop variants with responsive collapse.
|
|
512
503
|
|
|
513
|
-
**`panel-cell`** — a square structural content cell inside `cell-grid`. Uses
|
|
504
|
+
**`panel-cell`** — a square structural content cell inside `cell-grid`. Uses `--nc-cell-padding` and no local border.
|
|
514
505
|
|
|
515
506
|
**`marker`** — sparse crosshair marker for major section/frame corners. It marks intersections; it is not decorative chrome for every card. It is placed on a grid-field track intersection and derives its position from the grid, not from a frame-relative offset.
|
|
516
507
|
|
|
@@ -536,29 +527,20 @@ These live helpers are part of the kept page-grammar layout layer. They support
|
|
|
536
527
|
|
|
537
528
|
## Elevation & Depth
|
|
538
529
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
Product UI uses one shared material ladder. The workspace is one continuous ground (`{colors.material-workspace}` / `{colors.dark-material-workspace}`); every solid object uses the same fill (`{colors.material-solid}` / `{colors.dark-material-solid}`). In dark mode, both are true black. The workspace remains legible because it carries the existing `dotted-field` texture and solid objects occlude it. Elevation never means progressively greyer cards. Physical distance changes only the `shadow-plugin` falloff: `smooth-shadow-ring-md` for resting content, then `lg`, `xl`, and `2xl` when an object is physically raised, floating, or modal. Each utility already integrates the plugin's adaptive optical ring, including the plugin's 18% white dark-mode ring, so an elevated root never adds a border, second ring, local ring tint, or opacity override. Product code chooses only the semantic role.
|
|
530
|
+
Product UI uses one shared material ladder. The workspace is one continuous ground (`{colors.material-workspace}` / `{colors.dark-material-workspace}`); every solid object uses the same fill (`{colors.material-solid}` / `{colors.dark-material-solid}`). In dark mode, both are true black. The workspace remains legible because it carries the existing `dotted-field` texture and solid objects occlude it. Elevation never means progressively greyer cards. Physical distance changes only the falloff of the rung, and the four rungs are the four owned utilities: `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. Each rung is authored per mode, light and dark, and carries its own ring as the recipe's final layer, so an elevated root never adds a border, a second ring, a local ring tint, or an opacity override. Product code chooses only the semantic role.
|
|
542
531
|
|
|
543
|
-
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.
|
|
532
|
+
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.
|
|
544
533
|
|
|
545
|
-
The Google design schema
|
|
546
|
-
|
|
547
|
-
```css material-tokens
|
|
548
|
-
--nc-material-scrim-opacity: 10%;
|
|
549
|
-
--nc-material-glass-opacity: 76%;
|
|
550
|
-
--nc-material-glass-blur: 28px;
|
|
551
|
-
--nc-material-glass-saturation: 116%;
|
|
552
|
-
```
|
|
534
|
+
The Google design schema defines no opacity, elevation, or motion group. Those values are authored in the one `json design-tokens` fence under "CSS Architecture & Token Pipeline", "Authored token extension". `core.material` holds the four values this section applies and emits them as `--nc-material-*`: `scrim-opacity` becomes the theme layer's `--opacity-scrim`, and the three glass values are consumed by `@nadicodeai/ui/globals.css`. `core.elevation` holds the four rungs and emits `--nc-elevation-resting`, `-raised`, `-floating`, and `-modal`.
|
|
553
535
|
|
|
554
536
|
| Material role | Construction | Use |
|
|
555
537
|
| ------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
556
538
|
| Workspace | `dotted-field` on `{colors.material-workspace}` / `{colors.dark-material-workspace}`; no edge or shadow | The continuous work area behind product objects |
|
|
557
|
-
| Resting | Solid fill;
|
|
558
|
-
| Raised | Same solid fill
|
|
559
|
-
| Floating | Same solid fill
|
|
560
|
-
| Modal | Same solid fill
|
|
561
|
-
| Glass control | Translucent solid fill at 76%, 28 px backdrop blur,
|
|
539
|
+
| Resting | Solid fill; `nc-elevation-resting` with its own ring; `{rounded.3xl}` (16 px) | KPI, card, table, setting, and persistent content surfaces |
|
|
540
|
+
| Raised | Same solid fill; `nc-elevation-raised`; `{rounded.3xl}` (16 px) | Active work, a selected object, or an attached inspector above resting siblings |
|
|
541
|
+
| Floating | Same solid fill; `nc-elevation-floating`; role-specific corner radius | Compact menus, popovers, dropdowns, and anchored surfaces use `{rounded.2xl}` (12 px); small artifacts such as tooltips and toasts keep `{rounded.md}` (6 px); detached product surfaces may use `{rounded.3xl}` (16 px) |
|
|
542
|
+
| Modal | Same solid fill; `nc-elevation-modal`; `{rounded.3xl}` (16 px) | Dialog, sheet, drawer, and takeover |
|
|
543
|
+
| 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 |
|
|
562
544
|
|
|
563
545
|
`Card` is the fundamental product container. Its content does not create a second material family: KPIs, Agent cards, settings, and ordinary grouped content use the resting role unless the object is physically raised. Interaction variants may change state or emphasis without changing the material role.
|
|
564
546
|
|
|
@@ -584,7 +566,39 @@ Public page-grammar cells are not cards and remain square because their parent f
|
|
|
584
566
|
|
|
585
567
|
## Motion
|
|
586
568
|
|
|
587
|
-
Motion
|
|
569
|
+
Motion reports state: things move when something changes, briefly, and settle. Continuous, ambient motion is scarce and purposeful — the Agent identity visuals are its natural carrier — and repeated interactions stay fast and quiet. Motion preserves meaning when movement is reduced.
|
|
570
|
+
|
|
571
|
+
The durations and easings live in `core.motion` of the authored token extension ("CSS Architecture & Token Pipeline"); the Google design schema defines no motion group. The build emits every duration as `--nc-duration-*` and every easing as `--nc-ease-*`. `src/css/motion.css` owns the keyframes, the `nc-anim-*` and `nc-transition-*` classes, and the reduced-motion law, and references those names; it declares no duration, easing, or alias of its own. A register decides where motion appears; it never picks a duration outside this list.
|
|
572
|
+
|
|
573
|
+
| Token | Use |
|
|
574
|
+
| --- | --- |
|
|
575
|
+
| `control` | Hover and press on a control in a dense register |
|
|
576
|
+
| `confirm` | Confirmation feedback and instant state flips |
|
|
577
|
+
| `close` / `open` | Floating chrome and overlays leaving and arriving |
|
|
578
|
+
| `transition` | Property transitions on a working surface |
|
|
579
|
+
| `msg-in` | A transcript message arriving |
|
|
580
|
+
| `drawer` / `drawer-swipe` | Sheets and drawers, programmatic and pointer-driven |
|
|
581
|
+
| `stagger` | The step between siblings in one arrival |
|
|
582
|
+
| `spin` | One revolution of a busy spinner |
|
|
583
|
+
| `typing` / `pulse` / `shimmer` | Ambient loops; each has a reduced-motion fallback in `motion.css` |
|
|
584
|
+
|
|
585
|
+
The easings are `emphasized`, `out-strong`, `in-out-strong`, and `drawer`.
|
|
586
|
+
|
|
587
|
+
## Registers
|
|
588
|
+
|
|
589
|
+
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.
|
|
590
|
+
|
|
591
|
+
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.
|
|
592
|
+
|
|
593
|
+
| Register | Rulebook | Its own token families | Dark mode |
|
|
594
|
+
| --- | --- | --- | --- |
|
|
595
|
+
| Website | `skills/design/product-design/references/surfaces-website.md` over `## Layout` here | `register.website.layout`: `section`, `page-*`, `frame-*`, `cell-padding`, `section-gap`, `cross-size-*`, every `h-*` height. It speaks `display-xl`, `display-lg`, and `title-lg` | Light only |
|
|
596
|
+
| Portal | `apps/portal/docs/design-doctrine.md` | `register.portal.density`: `filter-chip-gap`, `filter-chip-height`, `filter-chip-icon` | System, light, dark, on the `.dark` class |
|
|
597
|
+
| Nadia desktop app | `skills/design/product-design/references/surfaces-nadia.md` | `register.nadia.*`: the colour-role aliases, the radius aliases, six elevation recipes, and the overlay border; data only, never projected to CSS | Follows the operating system, on the `.dark` class |
|
|
598
|
+
|
|
599
|
+
The website's depth is flat and architectural: hairlines, contrast, dotted fields, diagonal fills, and dark action bands make hierarchy, and the page frame and row grid stay dominant. It uses no rung of the material ladder and no floating card stack as page structure. Marketing bands may use `register.website.layout.section`, emitted as `--nc-section`, when they need the broader inherited hero rhythm. The website kernel uses no negative letter spacing outside the two display tiers.
|
|
600
|
+
|
|
601
|
+
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.
|
|
588
602
|
|
|
589
603
|
## Components
|
|
590
604
|
|
|
@@ -608,13 +622,13 @@ Keyboard focus has one owner: the kit's foundation layer draws the full-opacity
|
|
|
608
622
|
|
|
609
623
|
Content surfaces display specific content types inside the grammar. They are valid when the content exists; they do not replace the page frame, row model, or seam rules.
|
|
610
624
|
|
|
611
|
-
**`image`** — editorial media mount, the single home for art-directed imagery inside the grammar. A bounded media fill (img, picture, video, or inline SVG) with an optional mono tag chip overlaid on the media and an optional body-tone caption bar below it, both typed by the generated caption utilities applied in markup. Square-cornered (`{rounded.none}`): a mounted print, not a floating card — the parent frame supplies the visual system. With `data-frame="true"` it draws exactly one hairline (its own `{colors.line}` border), so it sits inside a borderless `panel-cell` or a visual slot without repairing or stacking onto any grid seam. The aspect ratio is the per-instance `--nc-image-ratio` knob (default 4 / 3) — set the prop on the element, never re-derive geometry inline. Variant: `data-strip="true"` is the full-width divider form for thin editorial bands between page rows (thread, texture, panorama) — the ratio unlocks and the height resolves from the responsive
|
|
625
|
+
**`image`** — editorial media mount, the single home for art-directed imagery inside the grammar. A bounded media fill (img, picture, video, or inline SVG) with an optional mono tag chip overlaid on the media and an optional body-tone caption bar below it, both typed by the generated caption utilities applied in markup. Square-cornered (`{rounded.none}`): a mounted print, not a floating card — the parent frame supplies the visual system. With `data-frame="true"` it draws exactly one hairline (its own `{colors.line}` border), so it sits inside a borderless `panel-cell` or a visual slot without repairing or stacking onto any grid seam. The aspect ratio is the per-instance `--nc-image-ratio` knob (default 4 / 3) — set the prop on the element, never re-derive geometry inline. Variant: `data-strip="true"` is the full-width divider form for thin editorial bands between page rows (thread, texture, panorama) — the ratio unlocks and the height resolves from the responsive `--nc-h-strip-*` tokens through the layout-layer collapsed alias. Use `image` for every illustrated image, photographic surface, or image divider. Conversation artifacts render through assistant-ui rather than a design-system CSS family.
|
|
612
626
|
|
|
613
627
|
### Agentic Work Surfaces
|
|
614
628
|
|
|
615
629
|
Agentic work surfaces are the NadicodeAI-specific display layer outside the conversation renderer. They show concrete work output or workflow state such as memory, state, run progress, identity, and handoffs. Conversation messages, tools, artifacts, approvals, and pending indicators belong to assistant-ui.
|
|
616
630
|
|
|
617
|
-
Agent identity is decorative product identity, not a work artifact or a status signal. Its approved metaball form, closed Agent-type recipes, four surface treatments, palette, still frames, and motion rules live in [`
|
|
631
|
+
Agent identity is decorative product identity, not a work artifact or a status signal. Its approved metaball form, closed Agent-type recipes, four surface treatments, palette, still frames, and motion rules live in [`packages/ui/docs/agent-visual.md`](../ui/docs/agent-visual.md). `@nadicodeai/ui` owns the typed React implementation. Consuming apps assign a recipe explicitly and own placement, data, actions, and accessible meaning.
|
|
618
632
|
|
|
619
633
|
**`artifact-surface`** — inspectable output area outside a conversation. Use for proposal previews, reports, plans, spreadsheets, schedules, run logs, and workflow maps.
|
|
620
634
|
|
|
@@ -656,45 +670,26 @@ whole map in one module and no surface picks a status colour anywhere else.
|
|
|
656
670
|
|
|
657
671
|
## Do's and Don'ts
|
|
658
672
|
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
-
|
|
662
|
-
-
|
|
663
|
-
-
|
|
664
|
-
-
|
|
665
|
-
-
|
|
666
|
-
-
|
|
667
|
-
-
|
|
668
|
-
-
|
|
669
|
-
- Do use fixed height tokens for default component/specimen height and let content grow only when required.
|
|
670
|
-
- Do separate different story beats into separate rows.
|
|
671
|
-
- Do use the full-lane `stage` geometry for one major use-case/artifact story, not as a container for every page concept.
|
|
672
|
-
- Do use Agent identity visuals only to identify an Agent or its explicit type; use assistant-ui and the agentic work surfaces above for conversations, artifacts, review states, and work output.
|
|
673
|
-
- Do keep ordinary CSS components compact and subordinate to the page grammar.
|
|
674
|
-
- Do use pricing, logo, banner, and showcase compositions only when the page truly contains that content type.
|
|
675
|
-
|
|
676
|
-
### Don't
|
|
677
|
-
|
|
678
|
-
- Don't create naked centered 10-column sections without visible ownership of columns 1 and 12.
|
|
679
|
-
- Don't draw the same seam from two adjacent elements.
|
|
680
|
-
- Don't add borders inside cells to fix visual seams; fix the owner layer.
|
|
681
|
-
- Don't use a 1 px decorative line as an input boundary, or locally increase card border weight to manufacture hierarchy.
|
|
682
|
-
- Don't stack borders or rings around an elevated surface, promote a resting card because its content is important, or blur modal scrims.
|
|
683
|
-
- Don't tune seam contrast with local opacity, raw colors, or component-specific border colors.
|
|
684
|
-
- Don't use viewport-scaled typography.
|
|
685
|
-
- Don't use negative letter spacing in the website kernel.
|
|
686
|
-
- Don't use gradients, decorative blobs, or atmospheric chrome as the page structure.
|
|
687
|
-
- Don't name NadicodeAI components after external reference brands.
|
|
688
|
-
- Don't promote homepage-specific copy, one-off page order, or prototype-only modules into this contract.
|
|
689
|
-
- Don't use one full-lane stage to present multiple unrelated use cases.
|
|
690
|
-
- Don't add component prop APIs, state matrices, screenshots, component implementation notes, page placement examples, or framework-specific instructions to this file. (The token-pipeline architecture section is contract, not component documentation.)
|
|
691
|
-
- Don't use pricing, logo, or banner compositions as placeholder credibility or decoration when the underlying content does not exist.
|
|
673
|
+
This section states the correct form; a complete positive definition is the whole rule.
|
|
674
|
+
|
|
675
|
+
- Pages are continuous `page-row` sequences inside one `page-frame`; centered content sits in the 10-column `active-lane` with columns 1 and 12 visibly owned — by empty canvas, `side-fill`, guide strips, or component-owned fillers.
|
|
676
|
+
- Every visible line has one owner: ordinary seams are `{spacing.guide}` at `{colors.line}`, tuned only at the token contract; control boundaries are `{colors.input}` / `{colors.dark-input}`; keyboard focus is the dedicated focus-ring roles; crosshairs are `{colors.cross}` at `{spacing.guide}` weight.
|
|
677
|
+
- Page structure is architectural: hairlines, luminance, spacing, and the grammar above. One material role per product container; structural page-grammar cells are border-only; the four `nc-elevation-*` rungs are the complete shadow vocabulary; modal scrims stay unblurred.
|
|
678
|
+
- Typography uses fixed token sizes stepped by breakpoint; components take their default heights from the exported height tokens and grow only when real content requires it.
|
|
679
|
+
- Each row carries one story beat; the full-lane `stage` presents one major use-case story; pricing, logo, banner, and showcase compositions appear when the page truly contains that content, with real, approved material.
|
|
680
|
+
- Agent identity visuals identify an Agent or its explicit type; conversations, artifacts, review states, and work output belong to assistant-ui and the agentic work surfaces. Text beside `{colors.verde-vivo}` sits on the canvas.
|
|
681
|
+
- Components are named as NadicodeAI-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.
|
|
682
|
+
- Each register keeps its own structure and chrome: the page grammar on the website, the material ladder in the Portal and the desktop, the desktop window following the operating system.
|
|
692
683
|
|
|
693
684
|
## Iconography
|
|
694
685
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
686
|
+
Which icon set a product draws from is a register decision; the render
|
|
687
|
+
discipline below, the sizes, the stroke, and the decorative-versus-named rule,
|
|
688
|
+
is foundation and holds in every register.
|
|
689
|
+
|
|
690
|
+
The website, the Portal, and `@nadicodeai/ui` draw from Lucide (ISC) with the
|
|
691
|
+
NadicodeAI house render: a `{spacing.icon-stroke}` round-cap, round-join
|
|
692
|
+
stroke, no fill, and `currentColor` inheritance.
|
|
698
693
|
The lighter stroke preserves the clarity of the accepted Portal concept while
|
|
699
694
|
round terminals keep Lucide's calm character. The package bakes the complete
|
|
700
695
|
pinned `lucide-static` set through that house render; a surface draws a small,
|
|
@@ -727,17 +722,376 @@ Each delivery tier (see Layout, "Delivery Tiers") has exactly one authoring file
|
|
|
727
722
|
- 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.
|
|
728
723
|
- Specimens: `examples/`, which only assemble already-defined tiers.
|
|
729
724
|
|
|
725
|
+
### Authored token extension
|
|
726
|
+
|
|
727
|
+
The YAML front matter carries the four groups the Google design schema defines: colors, typography, rounded, spacing. Everything else this contract owns is authored in the one `json design-tokens` fence below, as DTCG 2025.10. It holds `core.material`, `core.elevation` (four rungs per mode, each recipe ending in its own ring), `core.motion.duration` and `core.motion.ease`, and the register groups: `register.website.layout` (the page-grammar geometry), `register.portal.density` (the filter-chip sizes), and `register.nadia` (the desktop's colour and radius aliases onto `core`, plus its own six elevation recipes and overlay border).
|
|
728
|
+
|
|
729
|
+
An alias is written in the short `{core.color.canvas}` form here; the build rewrites it to the public rooted form `{nadicode.core.color.canvas}` in `dist/tokens/nadicode.dtcg.json`. `$extensions."ai.nadicode.targets"` on a register group lists where its values go: `css` puts them in the generated stylesheets, `data` keeps them in the token files only. A register group without that key fails the build. `core` carries no key and always reaches both.
|
|
730
|
+
|
|
731
|
+
```json design-tokens
|
|
732
|
+
{
|
|
733
|
+
"core": {
|
|
734
|
+
"material": {
|
|
735
|
+
"$type": "number",
|
|
736
|
+
"scrim-opacity": {"$value":0.1},
|
|
737
|
+
"glass-opacity": {"$value":0.76},
|
|
738
|
+
"glass-saturation": {"$value":1.16},
|
|
739
|
+
"glass-blur": {"$type":"dimension","$value":{"value":28,"unit":"px"}}
|
|
740
|
+
},
|
|
741
|
+
"elevation": {
|
|
742
|
+
"$type": "shadow",
|
|
743
|
+
"light": {
|
|
744
|
+
"resting": {
|
|
745
|
+
"$value": [
|
|
746
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":17.54,"unit":"px"},"blur":{"value":23.39,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.04}},
|
|
747
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":9.4,"unit":"px"},"blur":{"value":12.5,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.03}},
|
|
748
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":5.25,"unit":"px"},"blur":{"value":7,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.02}},
|
|
749
|
+
{"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}},
|
|
750
|
+
{"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}},
|
|
751
|
+
{"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}}
|
|
752
|
+
]
|
|
753
|
+
},
|
|
754
|
+
"raised": {
|
|
755
|
+
"$value": [
|
|
756
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":25,"unit":"px"},"blur":{"value":50,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.05}},
|
|
757
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":12,"unit":"px"},"blur":{"value":24,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.04}},
|
|
758
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":6,"unit":"px"},"blur":{"value":12,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.03}},
|
|
759
|
+
{"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.02}},
|
|
760
|
+
{"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}},
|
|
761
|
+
{"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}}
|
|
762
|
+
]
|
|
763
|
+
},
|
|
764
|
+
"floating": {
|
|
765
|
+
"$value": [
|
|
766
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":40,"unit":"px"},"blur":{"value":80,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.06}},
|
|
767
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":20,"unit":"px"},"blur":{"value":40,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.05}},
|
|
768
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":10,"unit":"px"},"blur":{"value":20,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.04}},
|
|
769
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":5,"unit":"px"},"blur":{"value":10,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.03}},
|
|
770
|
+
{"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.02}},
|
|
771
|
+
{"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}}
|
|
772
|
+
]
|
|
773
|
+
},
|
|
774
|
+
"modal": {
|
|
775
|
+
"$value": [
|
|
776
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":60,"unit":"px"},"blur":{"value":120,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.07}},
|
|
777
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":30,"unit":"px"},"blur":{"value":60,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.06}},
|
|
778
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":15,"unit":"px"},"blur":{"value":30,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.05}},
|
|
779
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":7.5,"unit":"px"},"blur":{"value":15,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.04}},
|
|
780
|
+
{"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}},
|
|
781
|
+
{"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}}
|
|
782
|
+
]
|
|
783
|
+
}
|
|
784
|
+
},
|
|
785
|
+
"dark": {
|
|
786
|
+
"resting": {
|
|
787
|
+
"$value": [
|
|
788
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":17.54,"unit":"px"},"blur":{"value":23.39,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.04}},
|
|
789
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":9.4,"unit":"px"},"blur":{"value":12.5,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.03}},
|
|
790
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":5.25,"unit":"px"},"blur":{"value":7,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.02}},
|
|
791
|
+
{"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}},
|
|
792
|
+
{"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}},
|
|
793
|
+
{"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}}
|
|
794
|
+
]
|
|
795
|
+
},
|
|
796
|
+
"raised": {
|
|
797
|
+
"$value": [
|
|
798
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":25,"unit":"px"},"blur":{"value":50,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.05}},
|
|
799
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":12,"unit":"px"},"blur":{"value":24,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.04}},
|
|
800
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":6,"unit":"px"},"blur":{"value":12,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.03}},
|
|
801
|
+
{"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.02}},
|
|
802
|
+
{"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}},
|
|
803
|
+
{"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}}
|
|
804
|
+
]
|
|
805
|
+
},
|
|
806
|
+
"floating": {
|
|
807
|
+
"$value": [
|
|
808
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":40,"unit":"px"},"blur":{"value":80,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.06}},
|
|
809
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":20,"unit":"px"},"blur":{"value":40,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.05}},
|
|
810
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":10,"unit":"px"},"blur":{"value":20,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.04}},
|
|
811
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":5,"unit":"px"},"blur":{"value":10,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.03}},
|
|
812
|
+
{"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.02}},
|
|
813
|
+
{"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}}
|
|
814
|
+
]
|
|
815
|
+
},
|
|
816
|
+
"modal": {
|
|
817
|
+
"$value": [
|
|
818
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":60,"unit":"px"},"blur":{"value":120,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.07}},
|
|
819
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":30,"unit":"px"},"blur":{"value":60,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.06}},
|
|
820
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":15,"unit":"px"},"blur":{"value":30,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.05}},
|
|
821
|
+
{"offsetX":{"value":0,"unit":"px"},"offsetY":{"value":7.5,"unit":"px"},"blur":{"value":15,"unit":"px"},"spread":{"value":0,"unit":"px"},"color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.04}},
|
|
822
|
+
{"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}},
|
|
823
|
+
{"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}}
|
|
824
|
+
]
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
},
|
|
828
|
+
"motion": {
|
|
829
|
+
"duration": {
|
|
830
|
+
"$type": "duration",
|
|
831
|
+
"stagger": {"$value":{"value":60,"unit":"ms"}},
|
|
832
|
+
"control": {"$value":{"value":100,"unit":"ms"}},
|
|
833
|
+
"close": {"$value":{"value":140,"unit":"ms"}},
|
|
834
|
+
"confirm": {"$value":{"value":180,"unit":"ms"}},
|
|
835
|
+
"open": {"$value":{"value":220,"unit":"ms"}},
|
|
836
|
+
"msg-in": {"$value":{"value":240,"unit":"ms"}},
|
|
837
|
+
"transition": {"$value":{"value":240,"unit":"ms"}},
|
|
838
|
+
"drawer-swipe": {"$value":{"value":400,"unit":"ms"}},
|
|
839
|
+
"drawer": {"$value":{"value":450,"unit":"ms"}},
|
|
840
|
+
"spin": {"$value":{"value":600,"unit":"ms"}},
|
|
841
|
+
"typing": {"$value":{"value":1,"unit":"s"}},
|
|
842
|
+
"pulse": {"$value":{"value":1.6,"unit":"s"}},
|
|
843
|
+
"shimmer": {"$value":{"value":2,"unit":"s"}}
|
|
844
|
+
},
|
|
845
|
+
"ease": {
|
|
846
|
+
"$type": "cubicBezier",
|
|
847
|
+
"emphasized": {"$value":[0.3,0.7,0.2,1]},
|
|
848
|
+
"out-strong": {"$value":[0.23,1,0.32,1]},
|
|
849
|
+
"in-out-strong": {"$value":[0.77,0,0.175,1]},
|
|
850
|
+
"drawer": {"$value":[0.32,0.72,0,1]}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
},
|
|
854
|
+
"register": {
|
|
855
|
+
"website": {
|
|
856
|
+
"$extensions": {"ai.nadicode.targets":["css","data"]},
|
|
857
|
+
"layout": {
|
|
858
|
+
"$type": "dimension",
|
|
859
|
+
"section": {"$value":{"value":192,"unit":"px"}},
|
|
860
|
+
"page-pad": {"$value":{"value":24,"unit":"px"}},
|
|
861
|
+
"page-max-mobile": {"$value":{"value":448,"unit":"px"}},
|
|
862
|
+
"page-max-tablet": {"$value":{"value":624,"unit":"px"}},
|
|
863
|
+
"page-max-desktop": {"$value":{"value":1440,"unit":"px"}},
|
|
864
|
+
"frame-max": {"$value":{"value":1392,"unit":"px"}},
|
|
865
|
+
"frame-gap": {"$value":{"value":24,"unit":"px"}},
|
|
866
|
+
"cell-padding": {"$value":{"value":24,"unit":"px"}},
|
|
867
|
+
"section-gap": {"$value":{"value":48,"unit":"px"}},
|
|
868
|
+
"cross-size-mobile": {"$value":{"value":11,"unit":"px"}},
|
|
869
|
+
"cross-size-tablet": {"$value":{"value":15,"unit":"px"}},
|
|
870
|
+
"cross-size-desktop": {"$value":{"value":21,"unit":"px"}},
|
|
871
|
+
"h-strip-mobile": {"$value":{"value":72,"unit":"px"}},
|
|
872
|
+
"h-strip-tablet": {"$value":{"value":80,"unit":"px"}},
|
|
873
|
+
"h-strip-desktop": {"$value":{"value":88,"unit":"px"}},
|
|
874
|
+
"h-cell-sm": {"$value":{"value":120,"unit":"px"}},
|
|
875
|
+
"h-cell-md-mobile": {"$value":{"value":144,"unit":"px"}},
|
|
876
|
+
"h-cell-md-tablet": {"$value":{"value":160,"unit":"px"}},
|
|
877
|
+
"h-cell-md-desktop": {"$value":{"value":168,"unit":"px"}},
|
|
878
|
+
"h-cell-lg-mobile": {"$value":{"value":220,"unit":"px"}},
|
|
879
|
+
"h-cell-lg-desktop": {"$value":{"value":240,"unit":"px"}},
|
|
880
|
+
"h-visual-mobile": {"$value":{"value":276,"unit":"px"}},
|
|
881
|
+
"h-visual-tablet": {"$value":{"value":318,"unit":"px"}},
|
|
882
|
+
"h-visual-desktop": {"$value":{"value":360,"unit":"px"}},
|
|
883
|
+
"h-precision-mobile": {"$value":{"value":276,"unit":"px"}},
|
|
884
|
+
"h-precision-tablet": {"$value":{"value":480,"unit":"px"}},
|
|
885
|
+
"h-precision-desktop": {"$value":{"value":720,"unit":"px"}},
|
|
886
|
+
"h-hero-mobile": {"$value":{"value":560,"unit":"px"}},
|
|
887
|
+
"h-hero-desktop": {"$value":{"value":720,"unit":"px"}},
|
|
888
|
+
"h-action-strip-mobile": {"$value":{"value":20,"unit":"px"}},
|
|
889
|
+
"h-action-strip-desktop": {"$value":{"value":32,"unit":"px"}},
|
|
890
|
+
"h-action-body-mobile": {"$value":{"value":320,"unit":"px"}},
|
|
891
|
+
"h-action-body-tablet": {"$value":{"value":338,"unit":"px"}},
|
|
892
|
+
"h-action-band": {"$value":{"value":260,"unit":"px"}},
|
|
893
|
+
"h-feature-mobile": {"$value":{"value":360,"unit":"px"}},
|
|
894
|
+
"h-feature-tablet": {"$value":{"value":400,"unit":"px"}},
|
|
895
|
+
"h-feature-desktop": {"$value":{"value":456,"unit":"px"}},
|
|
896
|
+
"h-editorial-feature-mobile": {"$value":{"value":360,"unit":"px"}},
|
|
897
|
+
"h-editorial-feature-tablet": {"$value":{"value":440,"unit":"px"}},
|
|
898
|
+
"h-editorial-feature-desktop": {"$value":{"value":560,"unit":"px"}},
|
|
899
|
+
"h-bento-primary": {"$value":{"value":384,"unit":"px"}},
|
|
900
|
+
"h-bento-secondary": {"$value":{"value":240,"unit":"px"}},
|
|
901
|
+
"h-bento-mobile-copy": {"$value":{"value":248,"unit":"px"}},
|
|
902
|
+
"h-bento-mobile-main": {"$value":{"value":537,"unit":"px"}},
|
|
903
|
+
"h-bento-strip": {"$value":{"value":20,"unit":"px"}}
|
|
904
|
+
}
|
|
905
|
+
},
|
|
906
|
+
"portal": {
|
|
907
|
+
"$extensions": {"ai.nadicode.targets":["css","data"]},
|
|
908
|
+
"density": {
|
|
909
|
+
"$type": "dimension",
|
|
910
|
+
"filter-chip-gap": {"$value":{"value":6,"unit":"px"}},
|
|
911
|
+
"filter-chip-height": {"$value":{"value":28,"unit":"px"}},
|
|
912
|
+
"filter-chip-icon": {"$value":{"value":14,"unit":"px"}}
|
|
913
|
+
}
|
|
914
|
+
},
|
|
915
|
+
"nadia": {
|
|
916
|
+
"$extensions": {"ai.nadicode.targets":["data"]},
|
|
917
|
+
"color": {
|
|
918
|
+
"light": {
|
|
919
|
+
"$type": "color",
|
|
920
|
+
"background": {"$value":"{core.color.canvas}"},
|
|
921
|
+
"foreground": {"$value":"{core.color.ink}"},
|
|
922
|
+
"card": {"$value":"{core.color.canvas}"},
|
|
923
|
+
"card-foreground": {"$value":"{core.color.ink}"},
|
|
924
|
+
"muted": {"$value":"{core.color.canvas-soft-2}"},
|
|
925
|
+
"muted-foreground": {"$value":"{core.color.body}"},
|
|
926
|
+
"popover": {"$value":"{core.color.canvas}"},
|
|
927
|
+
"popover-foreground": {"$value":"{core.color.ink}"},
|
|
928
|
+
"primary": {"$value":"{core.color.action}"},
|
|
929
|
+
"primary-foreground": {"$value":"{core.color.action-foreground}"},
|
|
930
|
+
"secondary": {"$value":"{core.color.canvas-soft}"},
|
|
931
|
+
"secondary-foreground": {"$value":"{core.color.ink}"},
|
|
932
|
+
"accent": {"$value":"{core.color.canvas-soft-2}"},
|
|
933
|
+
"accent-foreground": {"$value":"{core.color.ink}"},
|
|
934
|
+
"border": {"$value":"{core.color.line}"},
|
|
935
|
+
"input": {"$value":"{core.color.seam}"},
|
|
936
|
+
"ring": {"$value":"{core.color.focus-ring}"},
|
|
937
|
+
"midground": {"$value":"{core.color.body}"},
|
|
938
|
+
"composer-ring": {"$value":"{core.color.primary}"},
|
|
939
|
+
"destructive": {"$value":"{core.color.error}"},
|
|
940
|
+
"destructive-foreground": {"$value":"{core.color.on-primary}"},
|
|
941
|
+
"sidebar-background": {"$value":"{core.color.canvas-soft}"},
|
|
942
|
+
"sidebar-border": {"$value":"{core.color.line}"},
|
|
943
|
+
"user-bubble": {"$value":"{core.color.link-bg-soft}"},
|
|
944
|
+
"user-bubble-border": {"$value":"{core.color.link}"},
|
|
945
|
+
"medallion": {"$value":"{core.color.verde-vivo}"}
|
|
946
|
+
},
|
|
947
|
+
"dark": {
|
|
948
|
+
"$type": "color",
|
|
949
|
+
"background": {"$value":"{core.color.dark-canvas}"},
|
|
950
|
+
"foreground": {"$value":"{core.color.dark-ink}"},
|
|
951
|
+
"card": {"$value":"{core.color.dark-canvas}"},
|
|
952
|
+
"card-foreground": {"$value":"{core.color.dark-ink}"},
|
|
953
|
+
"muted": {"$value":"{core.color.dark-canvas-soft-2}"},
|
|
954
|
+
"muted-foreground": {"$value":"{core.color.dark-muted}"},
|
|
955
|
+
"popover": {"$value":"{core.color.dark-canvas}"},
|
|
956
|
+
"popover-foreground": {"$value":"{core.color.dark-ink}"},
|
|
957
|
+
"primary": {"$value":"{core.color.dark-action}"},
|
|
958
|
+
"primary-foreground": {"$value":"{core.color.dark-action-foreground}"},
|
|
959
|
+
"secondary": {"$value":"{core.color.dark-canvas-soft-2}"},
|
|
960
|
+
"secondary-foreground": {"$value":"{core.color.dark-body}"},
|
|
961
|
+
"accent": {"$value":"{core.color.dark-canvas-soft-2}"},
|
|
962
|
+
"accent-foreground": {"$value":"{core.color.dark-ink}"},
|
|
963
|
+
"border": {"$value":"{core.color.dark-line}"},
|
|
964
|
+
"input": {"$value":"{core.color.dark-input}"},
|
|
965
|
+
"ring": {"$value":"{core.color.dark-focus-ring}"},
|
|
966
|
+
"midground": {"$value":"{core.color.dark-muted}"},
|
|
967
|
+
"composer-ring": {"$value":"{core.color.dark-success}"},
|
|
968
|
+
"destructive": {"$value":"{core.color.flag-red}"},
|
|
969
|
+
"destructive-foreground": {"$value":"{core.color.on-primary}"},
|
|
970
|
+
"sidebar-background": {"$value":"{core.color.dark-canvas}"},
|
|
971
|
+
"sidebar-border": {"$value":"{core.color.dark-seam}"},
|
|
972
|
+
"user-bubble": {"$value":"{core.color.dark-link-bg-soft}"},
|
|
973
|
+
"user-bubble-border": {"$value":"{core.color.dark-link}"},
|
|
974
|
+
"medallion": {"$value":"{core.color.dark-verde-vivo}"}
|
|
975
|
+
}
|
|
976
|
+
},
|
|
977
|
+
"radius": {
|
|
978
|
+
"$type": "dimension",
|
|
979
|
+
"xs": {"$value":"{core.rounded.xs}"},
|
|
980
|
+
"sm": {"$value":"{core.rounded.sm}"},
|
|
981
|
+
"md": {"$value":"{core.rounded.md}"},
|
|
982
|
+
"lg": {"$value":"{core.rounded.lg}"},
|
|
983
|
+
"xl": {"$value":"{core.rounded.xl}"},
|
|
984
|
+
"2xl": {"$value":"{core.rounded.2xl}"},
|
|
985
|
+
"3xl": {"$value":"{core.rounded.3xl}"},
|
|
986
|
+
"4xl": {"$value":"{core.rounded.3xl}"}
|
|
987
|
+
},
|
|
988
|
+
"elevation": {
|
|
989
|
+
"light": {
|
|
990
|
+
"$type": "shadow",
|
|
991
|
+
"control": {
|
|
992
|
+
"$value": [
|
|
993
|
+
{"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}},
|
|
994
|
+
{"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}}
|
|
995
|
+
]
|
|
996
|
+
},
|
|
997
|
+
"panel": {
|
|
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":[0,0,0],"alpha":0.05}},
|
|
1000
|
+
{"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}}
|
|
1001
|
+
]
|
|
1002
|
+
},
|
|
1003
|
+
"raised": {
|
|
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":[0,0,0],"alpha":0.05}},
|
|
1006
|
+
{"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}},
|
|
1007
|
+
{"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}}
|
|
1008
|
+
]
|
|
1009
|
+
},
|
|
1010
|
+
"overlay": {
|
|
1011
|
+
"$value": [
|
|
1012
|
+
{"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}},
|
|
1013
|
+
{"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}}
|
|
1014
|
+
]
|
|
1015
|
+
},
|
|
1016
|
+
"modal": {
|
|
1017
|
+
"$value": [
|
|
1018
|
+
{"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}},
|
|
1019
|
+
{"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}},
|
|
1020
|
+
{"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}}
|
|
1021
|
+
]
|
|
1022
|
+
},
|
|
1023
|
+
"composer": {
|
|
1024
|
+
"$value": [
|
|
1025
|
+
{"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}}
|
|
1026
|
+
]
|
|
1027
|
+
},
|
|
1028
|
+
"overlay-border": {
|
|
1029
|
+
"$type": "border",
|
|
1030
|
+
"$value": {"width":{"value":1,"unit":"px"},"style":"solid","color":{"colorSpace":"srgb","components":[0,0,0],"alpha":0.05}}
|
|
1031
|
+
}
|
|
1032
|
+
},
|
|
1033
|
+
"dark": {
|
|
1034
|
+
"$type": "shadow",
|
|
1035
|
+
"control": {
|
|
1036
|
+
"$value": [
|
|
1037
|
+
{"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}},
|
|
1038
|
+
{"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}}
|
|
1039
|
+
]
|
|
1040
|
+
},
|
|
1041
|
+
"panel": {
|
|
1042
|
+
"$value": [
|
|
1043
|
+
{"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}},
|
|
1044
|
+
{"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}}
|
|
1045
|
+
]
|
|
1046
|
+
},
|
|
1047
|
+
"raised": {
|
|
1048
|
+
"$value": [
|
|
1049
|
+
{"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}},
|
|
1050
|
+
{"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}}
|
|
1051
|
+
]
|
|
1052
|
+
},
|
|
1053
|
+
"overlay": {
|
|
1054
|
+
"$value": [
|
|
1055
|
+
{"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}}
|
|
1056
|
+
]
|
|
1057
|
+
},
|
|
1058
|
+
"modal": {
|
|
1059
|
+
"$value": [
|
|
1060
|
+
{"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}},
|
|
1061
|
+
{"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}}
|
|
1062
|
+
]
|
|
1063
|
+
},
|
|
1064
|
+
"composer": {
|
|
1065
|
+
"$value": [
|
|
1066
|
+
{"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}}
|
|
1067
|
+
]
|
|
1068
|
+
},
|
|
1069
|
+
"overlay-border": {
|
|
1070
|
+
"$type": "border",
|
|
1071
|
+
"$value": {"width":{"value":1,"unit":"px"},"style":"solid","color":{"colorSpace":"srgb","components":[1,1,1],"alpha":0.18}}
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
```
|
|
1079
|
+
|
|
730
1080
|
### Generated outputs
|
|
731
1081
|
|
|
732
|
-
|
|
1082
|
+
`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).
|
|
1083
|
+
|
|
1084
|
+
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.
|
|
1085
|
+
|
|
1086
|
+
Style Dictionary (`scripts/sd/`) is the exporter over that projection, not the validator. It emits three CSS files:
|
|
733
1087
|
|
|
734
|
-
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: `
|
|
1088
|
+
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.
|
|
735
1089
|
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.
|
|
736
|
-
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 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.
|
|
1090
|
+
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.
|
|
737
1091
|
|
|
738
1092
|
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.
|
|
739
1093
|
|
|
740
|
-
`npm run build` also
|
|
1094
|
+
`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.
|
|
741
1095
|
|
|
742
1096
|
#### Brand media authority
|
|
743
1097
|
|
|
@@ -749,7 +1103,7 @@ The NadicodeAI logo has three approved forms:
|
|
|
749
1103
|
|
|
750
1104
|
Other companies' marks are not NadicodeAI company logos and are not governed by anything in this section. They are generated identity images owned by `@nadicodeai/ui/components/brand-icons`; [`packages/ui/docs/contract.md`](../ui/docs/contract.md) carries their rules. A third-party mark never enters a NadicodeAI logo form, composition, or clear-space measurement.
|
|
751
1105
|
|
|
752
|
-
Nadia's portrait, Agent identity visuals, and generated imagery are not NadicodeAI company logos. Nadia's identity and selection rules live in [`brand/
|
|
1106
|
+
Nadia's portrait, Agent identity visuals, and generated imagery are not NadicodeAI company logos. Nadia's identity and selection rules live in [`brand/AGENTS.md`](../../brand/AGENTS.md); the Agent identity system lives in [`packages/ui/docs/agent-visual.md`](../ui/docs/agent-visual.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.
|
|
753
1107
|
|
|
754
1108
|
The approved transparent Nadia master ships at `assets/nadia-solid-surface`. The package also delivers the three remaining material states as compatibility routes: `assets/nadia-ordered-points`, `assets/nadia-triangulated-wire`, and `assets/nadia-irregular-particles`. Package delivery does not grant brand approval; which Nadia image a surface may use stays with the identity document named above.
|
|
755
1109
|
|
|
@@ -767,4 +1121,4 @@ The package build renders and publicly exports full-colour, mono-ink, inverse, a
|
|
|
767
1121
|
|
|
768
1122
|
### Enforcement
|
|
769
1123
|
|
|
770
|
-
Correctness is structural, not a byte snapshot: `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.
|
|
1124
|
+
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.
|