noecosystem-design 0.2.1 → 0.2.2

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.
@@ -0,0 +1,31 @@
1
+ # Official NOE brand theme
2
+
3
+ The NOEcosystem landing is the canonical visual identity for NOE products. Product
4
+ interfaces use the same language rather than a related palette.
5
+
6
+ ## Foundation
7
+
8
+ - Light canvas: paper `#f3f0ea`, raised paper `#f6f4f2` and bright reading surface.
9
+ - Dark canvas: graphite `#252423`, raised graphite `#302f2e`.
10
+ - Primary action: mint `#98e8cf`; hover mint `#abefd9`; the label is graphite.
11
+ - Supporting interaction and focus: deep teal `#0f6d5b` / `#0b6f6b`.
12
+ - Signal: coral `#ff5b57` / `#c43f3c`; cyan, amber and violet remain status/data
13
+ signals, not the default product CTA.
14
+ - Latin copy uses the SF system stack; Persian copy uses IRANYekanXFaNum.
15
+
16
+ ## Composition
17
+
18
+ Use large, confident headings; quiet metadata with tracked uppercase only for
19
+ Latin labels; generous spacing; and low-contrast graphite/paper layers. Interactive
20
+ boundaries use the semantic border token and the shared rounded geometry. Floating
21
+ chat and modal surfaces use `shadow.raised` or `shadow.overlay` plus the quiet mint
22
+ ambient gradient from the official reference. Do not create a separate app palette.
23
+
24
+ ## Product mapping
25
+
26
+ - Chat composer/send action: mint fill, graphite icon/text, deep-teal focus ring.
27
+ - Assistant/AI surfaces: graphite/paper raised surface with a quiet mint radial
28
+ ambient layer; do not use blue/cyan as the product identity.
29
+ - Admin, expert and profile shells: the same paper/graphite canvas, thin utility
30
+ dividers, and one mint primary action per region.
31
+ - Destructive paths continue to use coral and must retain explicit labels.
@@ -37,12 +37,14 @@ Radii are compact and semantic. Controls use shared heights. Cards are not neste
37
37
 
38
38
  ## Surface hierarchy
39
39
 
40
- NOE uses a flat surface hierarchy: page canvas and raised regions are separated by
41
- semantic background tone, spacing, and radius—not decorative borders or shadows.
40
+ NOE uses graphite/paper surface hierarchy: page canvas and raised regions are
41
+ separated first by semantic background tone, spacing, and radius. The official
42
+ NOEcosystem visual language adds soft ambient elevation only to floating chat,
43
+ drawers and modal layers; it is never used as chrome around every section.
42
44
  `Card` therefore defaults to a raised, borderless appearance. Its outlined
43
45
  appearance is only for controls or data boundaries with a meaningful selection
44
- state. See [Flat surface and navigation policy](surface-policy.md) for the
45
- product-level contract.
46
+ state. See [Official NOE brand theme](brand-theme.md) and [Flat surface and
47
+ navigation policy](surface-policy.md) for the product-level contract.
46
48
 
47
49
  ## Responsive behavior
48
50
 
@@ -14,8 +14,9 @@ passes caused by arbitrary shadows, card borders, or ambiguous mobile navigation
14
14
  `appearance="outlined"` is reserved for a selection, data-entry, or comparison
15
15
  boundary where a stroke conveys real meaning.
16
16
 
17
- NOE shadow tokens are intentionally `none`. Do not add local `box-shadow` values
18
- to product styles. A focus state is an outline/ring, not elevation.
17
+ Use the shared shadow tokens only for floating chat, drawer, popover and modal
18
+ layers. Sections and ordinary cards do not receive ad-hoc elevation. A focus state
19
+ is an outline/ring, not elevation.
19
20
 
20
21
  ## Actions
21
22
 
@@ -51,8 +52,8 @@ to product styles. A focus state is an outline/ring, not elevation.
51
52
 
52
53
  - Test every route in Persian/RTL and English/LTR, in light and dark themes.
53
54
  - Verify no page-level horizontal overflow at the mobile breakpoint.
54
- - Verify every section/card/panel has `box-shadow: none` and no visible outer
55
- stroke unless it is explicitly an outlined control.
55
+ - Verify sections/cards have no visible outer stroke or ad-hoc shadow unless they
56
+ are an explicitly outlined control or a floating overlay using `shadow.overlay`.
56
57
  - Verify interactive targets are at least 44 px on touch devices and have
57
58
  accessible names.
58
59
  - Test desktop sidebar and mobile drawer independently, including keyboard focus
package/llms.txt CHANGED
@@ -33,6 +33,7 @@
33
33
  - Components and catalog terms: `docs/design-system/components.md`
34
34
  - Tokens and foundations: `docs/design-system/foundations.md`, `docs/design-system/tokens.md`
35
35
  - Flat cards, action hierarchy and adaptive application navigation: `docs/design-system/surface-policy.md`
36
+ - Official NOEcosystem visual-language contract: `docs/design-system/brand-theme.md`
36
37
  - Locale, RTL, and bidi: `docs/design-system/i18n.md`, `docs/design-system/rtl.md`
37
38
  - Accessibility: `docs/design-system/accessibility.md`
38
39
  - Agent and tool-state UI: `docs/design-system/agentic-ui.md`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "noecosystem-design",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "private": false,
5
5
  "description": "Agent-readable NOE design-system source, registry metadata, and verification contracts.",
6
6
  "keywords": [
@@ -145,9 +145,9 @@
145
145
  --noe-color-muted-foreground: var(--noe-primitive-color-ink-500);
146
146
  --noe-color-border: rgba(23, 25, 24, 0.16);
147
147
  --noe-color-border-strong: var(--noe-primitive-color-ink-300);
148
- --noe-color-accent: var(--noe-primitive-color-cyan-500);
149
- --noe-color-accent-hover: var(--noe-primitive-color-cyan-500);
150
- --noe-color-accent-foreground: var(--noe-primitive-color-paper-0);
148
+ --noe-color-accent: var(--noe-primitive-color-mint-100);
149
+ --noe-color-accent-hover: var(--noe-primitive-color-mint-200);
150
+ --noe-color-accent-foreground: var(--noe-primitive-color-ink-900);
151
151
  --noe-color-signal: var(--noe-primitive-color-coral-500);
152
152
  --noe-color-success: var(--noe-primitive-color-green-600);
153
153
  --noe-color-success-foreground: var(--noe-primitive-color-paper-0);
@@ -159,8 +159,8 @@
159
159
  --noe-color-info-foreground: var(--noe-primitive-color-paper-0);
160
160
  --noe-color-focus-ring: var(--noe-primitive-color-alpha-focus-light);
161
161
  --noe-color-overlay: var(--noe-primitive-color-alpha-overlay-light);
162
- --noe-shadow-raised: none;
163
- --noe-shadow-overlay: none;
162
+ --noe-shadow-raised: 0 8px 20px rgba(23, 25, 24, 0.055);
163
+ --noe-shadow-overlay: 0 36px 100px rgba(23, 25, 24, 0.28);
164
164
  }
165
165
 
166
166
  /* Semantic tokens: explicit dark theme. */
@@ -175,8 +175,8 @@
175
175
  --noe-color-muted-foreground: var(--noe-primitive-color-ink-400);
176
176
  --noe-color-border: rgba(246, 244, 242, 0.14);
177
177
  --noe-color-border-strong: var(--noe-primitive-color-ink-600);
178
- --noe-color-accent: var(--noe-primitive-color-cyan-400);
179
- --noe-color-accent-hover: var(--noe-primitive-color-cyan-400);
178
+ --noe-color-accent: var(--noe-primitive-color-mint-100);
179
+ --noe-color-accent-hover: var(--noe-primitive-color-mint-200);
180
180
  --noe-color-accent-foreground: var(--noe-primitive-color-ink-950);
181
181
  --noe-color-signal: var(--noe-primitive-color-coral-400);
182
182
  --noe-color-success: var(--noe-primitive-color-green-500);
@@ -189,8 +189,8 @@
189
189
  --noe-color-info-foreground: var(--noe-primitive-color-ink-950);
190
190
  --noe-color-focus-ring: var(--noe-primitive-color-alpha-focus-dark);
191
191
  --noe-color-overlay: var(--noe-primitive-color-alpha-overlay-dark);
192
- --noe-shadow-raised: none;
193
- --noe-shadow-overlay: none;
192
+ --noe-shadow-raised: 0 8px 20px rgba(0, 0, 0, 0.14);
193
+ --noe-shadow-overlay: 0 36px 100px rgba(0, 0, 0, 0.42);
194
194
  }
195
195
 
196
196
  /* Semantic tokens: follow the OS only when no explicit theme is set. */
@@ -206,8 +206,8 @@
206
206
  --noe-color-muted-foreground: var(--noe-primitive-color-ink-400);
207
207
  --noe-color-border: rgba(246, 244, 242, 0.14);
208
208
  --noe-color-border-strong: var(--noe-primitive-color-ink-600);
209
- --noe-color-accent: var(--noe-primitive-color-cyan-400);
210
- --noe-color-accent-hover: var(--noe-primitive-color-cyan-400);
209
+ --noe-color-accent: var(--noe-primitive-color-mint-100);
210
+ --noe-color-accent-hover: var(--noe-primitive-color-mint-200);
211
211
  --noe-color-accent-foreground: var(--noe-primitive-color-ink-950);
212
212
  --noe-color-signal: var(--noe-primitive-color-coral-400);
213
213
  --noe-color-success: var(--noe-primitive-color-green-500);
@@ -220,8 +220,8 @@
220
220
  --noe-color-info-foreground: var(--noe-primitive-color-ink-950);
221
221
  --noe-color-focus-ring: var(--noe-primitive-color-alpha-focus-dark);
222
222
  --noe-color-overlay: var(--noe-primitive-color-alpha-overlay-dark);
223
- --noe-shadow-raised: none;
224
- --noe-shadow-overlay: none;
223
+ --noe-shadow-raised: 0 8px 20px rgba(0, 0, 0, 0.14);
224
+ --noe-shadow-overlay: 0 36px 100px rgba(0, 0, 0, 0.42);
225
225
  }
226
226
  }
227
227
 
@@ -111,9 +111,9 @@
111
111
  "mutedForeground": "{primitive.color.ink.500}",
112
112
  "border": "rgba(23, 25, 24, 0.16)",
113
113
  "borderStrong": "{primitive.color.ink.300}",
114
- "accent": "{primitive.color.cyan.500}",
115
- "accentHover": "{primitive.color.cyan.500}",
116
- "accentForeground": "{primitive.color.paper.0}",
114
+ "accent": "{primitive.color.mint.100}",
115
+ "accentHover": "{primitive.color.mint.200}",
116
+ "accentForeground": "{primitive.color.ink.900}",
117
117
  "signal": "{primitive.color.coral.500}",
118
118
  "success": "{primitive.color.green.600}",
119
119
  "successForeground": "{primitive.color.paper.0}",
@@ -136,8 +136,8 @@
136
136
  "mutedForeground": "{primitive.color.ink.400}",
137
137
  "border": "rgba(246, 244, 242, 0.14)",
138
138
  "borderStrong": "{primitive.color.ink.600}",
139
- "accent": "{primitive.color.cyan.400}",
140
- "accentHover": "{primitive.color.cyan.400}",
139
+ "accent": "{primitive.color.mint.100}",
140
+ "accentHover": "{primitive.color.mint.200}",
141
141
  "accentForeground": "{primitive.color.ink.950}",
142
142
  "signal": "{primitive.color.coral.400}",
143
143
  "success": "{primitive.color.green.500}",
@@ -154,12 +154,12 @@
154
154
  },
155
155
  "shadow": {
156
156
  "light": {
157
- "raised": "none",
158
- "overlay": "none"
157
+ "raised": "0 8px 20px rgba(23, 25, 24, 0.055)",
158
+ "overlay": "0 36px 100px rgba(23, 25, 24, 0.28)"
159
159
  },
160
160
  "dark": {
161
- "raised": "none",
162
- "overlay": "none"
161
+ "raised": "0 8px 20px rgba(0, 0, 0, 0.14)",
162
+ "overlay": "0 36px 100px rgba(0, 0, 0, 0.42)"
163
163
  }
164
164
  },
165
165
  "font": {
@@ -112,9 +112,9 @@ export const tokens = {
112
112
  "mutedForeground": "{primitive.color.ink.500}",
113
113
  "border": "rgba(23, 25, 24, 0.16)",
114
114
  "borderStrong": "{primitive.color.ink.300}",
115
- "accent": "{primitive.color.cyan.500}",
116
- "accentHover": "{primitive.color.cyan.500}",
117
- "accentForeground": "{primitive.color.paper.0}",
115
+ "accent": "{primitive.color.mint.100}",
116
+ "accentHover": "{primitive.color.mint.200}",
117
+ "accentForeground": "{primitive.color.ink.900}",
118
118
  "signal": "{primitive.color.coral.500}",
119
119
  "success": "{primitive.color.green.600}",
120
120
  "successForeground": "{primitive.color.paper.0}",
@@ -137,8 +137,8 @@ export const tokens = {
137
137
  "mutedForeground": "{primitive.color.ink.400}",
138
138
  "border": "rgba(246, 244, 242, 0.14)",
139
139
  "borderStrong": "{primitive.color.ink.600}",
140
- "accent": "{primitive.color.cyan.400}",
141
- "accentHover": "{primitive.color.cyan.400}",
140
+ "accent": "{primitive.color.mint.100}",
141
+ "accentHover": "{primitive.color.mint.200}",
142
142
  "accentForeground": "{primitive.color.ink.950}",
143
143
  "signal": "{primitive.color.coral.400}",
144
144
  "success": "{primitive.color.green.500}",
@@ -155,12 +155,12 @@ export const tokens = {
155
155
  },
156
156
  "shadow": {
157
157
  "light": {
158
- "raised": "none",
159
- "overlay": "none"
158
+ "raised": "0 8px 20px rgba(23, 25, 24, 0.055)",
159
+ "overlay": "0 36px 100px rgba(23, 25, 24, 0.28)"
160
160
  },
161
161
  "dark": {
162
- "raised": "none",
163
- "overlay": "none"
162
+ "raised": "0 8px 20px rgba(0, 0, 0, 0.14)",
163
+ "overlay": "0 36px 100px rgba(0, 0, 0, 0.42)"
164
164
  }
165
165
  },
166
166
  "font": {
@@ -1,9 +1,9 @@
1
1
  import type * as React from 'react';
2
2
  import { cn } from './lib/cn';
3
3
 
4
- // Default example value uses the semantic accent token (mint-500) not a raw product color.
5
- // Input type=color requires a hex for the native picker; the displayed hex is derived from the token.
6
- const DEFAULT_ACCENT_HEX = '#0b6f6b'; // primitive mint-500 / semantic accent — keep in sync with tokens.json
4
+ // The native picker needs a literal hex. Its swatch demonstrates deep mint while
5
+ // the adjacent value keeps the standard readable text foreground.
6
+ const DEFAULT_ACCENT_HEX = '#0b6f6b'; // primitive mint-500 — keep in sync with tokens.json
7
7
 
8
8
  export function ColorPicker({ className, ...props }: React.ComponentProps<'div'>) {
9
9
  return (
@@ -14,9 +14,7 @@ export function ColorPicker({ className, ...props }: React.ComponentProps<'div'>
14
14
  className="size-8 rounded-md border border-border"
15
15
  defaultValue={DEFAULT_ACCENT_HEX}
16
16
  />
17
- <span className="text-sm text-muted-foreground" style={{ color: 'var(--noe-color-accent)' }}>
18
- {DEFAULT_ACCENT_HEX}
19
- </span>
17
+ <span className="text-sm text-muted-foreground">{DEFAULT_ACCENT_HEX}</span>
20
18
  </div>
21
19
  );
22
20
  }
@@ -23,16 +23,22 @@ test('token CSS includes theme and reduced-motion contracts', async () => {
23
23
  assert.match(css, /--noe-primitive-color-mint-100:\s*#[0-9a-f]{6}/i);
24
24
  });
25
25
 
26
- test('surface elevation is intentionally shadow-free in both themes', async () => {
27
- assert.equal(tokens.shadow.light.raised, 'none');
28
- assert.equal(tokens.shadow.light.overlay, 'none');
29
- assert.equal(tokens.shadow.dark.raised, 'none');
30
- assert.equal(tokens.shadow.dark.overlay, 'none');
26
+ test('surface elevation follows the official NOE soft-depth contract', async () => {
27
+ assert.match(tokens.shadow.light.raised, /rgba\(23, 25, 24, 0\.055\)/);
28
+ assert.match(tokens.shadow.light.overlay, /rgba\(23, 25, 24, 0\.28\)/);
29
+ assert.match(tokens.shadow.dark.raised, /rgba\(0, 0, 0, 0\.14\)/);
30
+ assert.match(tokens.shadow.dark.overlay, /rgba\(0, 0, 0, 0\.42\)/);
31
31
 
32
32
  const css = await readFile(
33
33
  new URL('../packages/design-tokens/src/tokens.css', import.meta.url),
34
34
  'utf8',
35
35
  );
36
- assert.match(css, /\[data-theme="light"\][\s\S]*?--noe-shadow-raised:\s*none/);
37
- assert.match(css, /\[data-theme="dark"\][\s\S]*?--noe-shadow-overlay:\s*none/);
36
+ assert.match(
37
+ css,
38
+ /\[data-theme="light"\][\s\S]*?--noe-shadow-raised:\s*0 8px 20px rgba\(23, 25, 24, 0\.055\)/,
39
+ );
40
+ assert.match(
41
+ css,
42
+ /\[data-theme="dark"\][\s\S]*?--noe-shadow-overlay:\s*0 36px 100px rgba\(0, 0, 0, 0\.42\)/,
43
+ );
38
44
  });