@citron-systems/citron-ds 1.0.0 → 1.0.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.
Files changed (63) hide show
  1. package/README.md +218 -149
  2. package/cli/citron-mascot.mjs +117 -0
  3. package/dist/ai/inkblot-ai-reference.json +582 -570
  4. package/dist/ai/inkblot-tokens-resolved.json +25 -0
  5. package/dist/ai/inkblot-tokens-schema.json +54 -54
  6. package/dist/brand/citron-mascot-mono.svg +17 -0
  7. package/dist/brand/citron-mascot.svg +21 -0
  8. package/dist/brand/readme-hero.svg +61 -0
  9. package/dist/bundle/README.md +28 -0
  10. package/dist/bundle/ai-reference.json +582 -0
  11. package/dist/bundle/brand/citron-mascot-mono.svg +17 -0
  12. package/dist/bundle/brand/citron-mascot.svg +21 -0
  13. package/dist/bundle/brand/readme-hero.svg +61 -0
  14. package/dist/bundle/preview/mascot.html +72 -0
  15. package/dist/bundle/system/ai.json +168 -0
  16. package/dist/bundle/system/cli.json +340 -0
  17. package/dist/bundle/system/components.json +304 -0
  18. package/dist/bundle/system/content.json +168 -0
  19. package/dist/bundle/system/devtools.json +95 -0
  20. package/dist/bundle/system/foundations.json +121 -0
  21. package/dist/bundle/system/grid.json +101 -0
  22. package/dist/bundle/system/icons.json +507 -0
  23. package/dist/bundle/system/index.json +57 -0
  24. package/dist/bundle/system/inkblot-ai-reference.json +582 -0
  25. package/dist/bundle/system/motion.json +159 -0
  26. package/dist/bundle/tokens/primitive/breakpoint.tokens.json +14 -0
  27. package/dist/bundle/tokens/primitive/color.tokens.json +71 -0
  28. package/dist/bundle/tokens/primitive/devtools.palette.tokens.json +55 -0
  29. package/dist/bundle/tokens/primitive/duration.tokens.json +38 -0
  30. package/dist/bundle/tokens/primitive/grid.tokens.json +34 -0
  31. package/dist/bundle/tokens/primitive/radius.tokens.json +26 -0
  32. package/dist/bundle/tokens/primitive/shadow.tokens.json +41 -0
  33. package/dist/bundle/tokens/primitive/spacing.tokens.json +38 -0
  34. package/dist/bundle/tokens/primitive/typography.tokens.json +60 -0
  35. package/dist/bundle/tokens/primitive/zindex.tokens.json +19 -0
  36. package/dist/bundle/tokens/semantic/dark.tokens.json +43 -0
  37. package/dist/bundle/tokens/semantic/devtools.semantic.tokens.json +79 -0
  38. package/dist/bundle/tokens/semantic/inkblot.semantic.tokens.json +140 -0
  39. package/dist/bundle/tokens-schema.json +54 -0
  40. package/dist/bundle/tokens.flat.json +224 -0
  41. package/dist/bundle/tokens.resolved.json +224 -0
  42. package/dist/bundle/variables.css +228 -0
  43. package/dist/bundle/variables.scss +225 -0
  44. package/dist/css/inkblot-variables.css +25 -0
  45. package/dist/index.html +956 -0
  46. package/dist/js/inkblot-tokens.js +25 -0
  47. package/dist/js/inkblot-tokens.json +25 -0
  48. package/dist/preview/mascot.html +72 -0
  49. package/dist/scss/_inkblot-variables.scss +25 -0
  50. package/package.json +73 -61
  51. package/tokens/primitive/breakpoint.tokens.json +14 -14
  52. package/tokens/primitive/color.tokens.json +71 -71
  53. package/tokens/primitive/devtools.palette.tokens.json +55 -0
  54. package/tokens/primitive/duration.tokens.json +38 -38
  55. package/tokens/primitive/grid.tokens.json +34 -34
  56. package/tokens/primitive/radius.tokens.json +26 -26
  57. package/tokens/primitive/shadow.tokens.json +41 -41
  58. package/tokens/primitive/spacing.tokens.json +38 -38
  59. package/tokens/primitive/typography.tokens.json +60 -60
  60. package/tokens/primitive/zindex.tokens.json +19 -19
  61. package/tokens/semantic/dark.tokens.json +43 -43
  62. package/tokens/semantic/devtools.semantic.tokens.json +79 -0
  63. package/tokens/semantic/inkblot.semantic.tokens.json +140 -140
@@ -0,0 +1,61 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1280 320" role="img" aria-labelledby="rh-title rh-desc">
2
+ <title id="rh-title">Citron Design System</title>
3
+ <desc id="rh-desc">Citron brand mascot and wordmark on a warm dark background with citron accent.</desc>
4
+ <defs>
5
+ <linearGradient id="rh-bg" x1="0%" y1="0%" x2="100%" y2="100%">
6
+ <stop offset="0%" stop-color="#3d3111"/>
7
+ <stop offset="45%" stop-color="#1a1814"/>
8
+ <stop offset="100%" stop-color="#0c0b09"/>
9
+ </linearGradient>
10
+ <radialGradient id="rh-glow" cx="50%" cy="42%" r="55%">
11
+ <stop offset="0%" stop-color="#c4a030" stop-opacity="0.22"/>
12
+ <stop offset="55%" stop-color="#c4a030" stop-opacity="0.06"/>
13
+ <stop offset="100%" stop-color="#c4a030" stop-opacity="0"/>
14
+ </radialGradient>
15
+ </defs>
16
+ <rect width="1280" height="320" fill="url(#rh-bg)"/>
17
+ <rect width="1280" height="320" fill="url(#rh-glow)"/>
18
+ <rect x="0" y="316" width="1280" height="4" fill="#c4a030"/>
19
+ <g transform="translate(560 28) scale(1.6)">
20
+ <g fill="#c4a030" stroke="none">
21
+ <rect x="31" y="17" width="13" height="34"/>
22
+ <rect x="56" y="17" width="13" height="34"/>
23
+ </g>
24
+ <path
25
+ d="M 12 40 A 38 38 0 0 0 88 40"
26
+ fill="none"
27
+ stroke="#c4a030"
28
+ stroke-width="10"
29
+ stroke-linecap="butt"
30
+ stroke-linejoin="miter"
31
+ />
32
+ </g>
33
+ <text
34
+ x="640"
35
+ y="248"
36
+ text-anchor="middle"
37
+ font-family="system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, Cantarell, sans-serif"
38
+ font-size="30"
39
+ font-weight="600"
40
+ fill="#f5f0e6"
41
+ letter-spacing="-0.02em"
42
+ >Citron Design System</text>
43
+ <text
44
+ x="640"
45
+ y="282"
46
+ text-anchor="middle"
47
+ font-family="system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, Cantarell, sans-serif"
48
+ font-size="16"
49
+ font-weight="500"
50
+ fill="#c4a030"
51
+ letter-spacing="0.01em"
52
+ >@citron-systems/citron-ds</text>
53
+ <text
54
+ x="640"
55
+ y="304"
56
+ text-anchor="middle"
57
+ font-family="system-ui, -apple-system, 'Segoe UI', Roboto, Ubuntu, Cantarell, sans-serif"
58
+ font-size="13"
59
+ fill="#8a8274"
60
+ >Inkblot Studio � design tokens � AI-ready</text>
61
+ </svg>
@@ -0,0 +1,72 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Citron mascot</title>
7
+ <style>
8
+ *, *::before, *::after { box-sizing: border-box; margin: 0; }
9
+ html { -webkit-font-smoothing: antialiased; }
10
+ body {
11
+ min-height: 100vh;
12
+ display: flex;
13
+ flex-direction: column;
14
+ align-items: center;
15
+ justify-content: center;
16
+ gap: 1.5rem;
17
+ padding: 2rem;
18
+ font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
19
+ background: #fafaf7;
20
+ color: #1d1c19;
21
+ }
22
+ .panel {
23
+ background: #fff;
24
+ border: 1px solid #eae9e3;
25
+ border-radius: 16px;
26
+ padding: 2rem 2.5rem;
27
+ box-shadow: 0 4px 24px rgba(29, 28, 25, 0.06);
28
+ text-align: center;
29
+ max-width: 420px;
30
+ }
31
+ h1 {
32
+ font-size: 1.25rem;
33
+ font-weight: 600;
34
+ letter-spacing: -0.02em;
35
+ margin-bottom: 0.35rem;
36
+ }
37
+ p {
38
+ font-size: 0.875rem;
39
+ color: #6b6a63;
40
+ line-height: 1.5;
41
+ }
42
+ .svg-wrap {
43
+ width: min(240px, 70vw);
44
+ margin: 0 auto;
45
+ }
46
+ .svg-wrap svg { display: block; width: 100%; height: auto; }
47
+ code { font-size: 0.75rem; background: #f5f4f0; padding: 0.15rem 0.4rem; border-radius: 6px; }
48
+ </style>
49
+ </head>
50
+ <body>
51
+ <div class="panel">
52
+ <h1>Citron mascot</h1>
53
+ <p>Standalone preview — open this file in any browser (double-click). No server or terminal needed.</p>
54
+ <div class="svg-wrap" aria-hidden="true">
55
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" role="img" aria-labelledby="t d">
56
+ <title id="t">Citron</title>
57
+ <desc id="d">Geometric face: pause eyes and smile.</desc>
58
+ <g id="citron-mascot">
59
+ <g id="mascot-eyes" fill="#c4a030" stroke="none">
60
+ <g id="mascot-eye-left"><rect x="39" y="36" width="8" height="10" /></g>
61
+ <g id="mascot-eye-right"><rect x="53" y="36" width="8" height="10" /></g>
62
+ </g>
63
+ <g id="mascot-smile" fill="none" stroke="#c4a030" stroke-width="10" stroke-linecap="butt" stroke-linejoin="miter">
64
+ <path d="M 16 56 A 34 34 0 0 0 84 56" />
65
+ </g>
66
+ </g>
67
+ </svg>
68
+ </div>
69
+ <p style="margin-top: 1rem">Brand: <code>#c4a030</code> (citron 500)</p>
70
+ </div>
71
+ </body>
72
+ </html>
@@ -0,0 +1,168 @@
1
+ {
2
+ "$schema": "https://inkblot.studio/system/v1/ai.schema.json",
3
+ "name": "AI Agent Consumption Rules",
4
+ "version": "1.0.0",
5
+ "description": "Deterministic rules for AI agents to consume, interpret, and render Inkblot Studio components. No guessing. Every rule has one correct interpretation.",
6
+
7
+ "cliAndTui": {
8
+ "rule": "All terminal, TUI, and AI-prompt CLI experiences must follow system/cli.json — color roles, prompts, transcript layout, tool panels, NO_COLOR, and quality bar.",
9
+ "path": "system/cli.json"
10
+ },
11
+
12
+ "consumption": {
13
+ "tokenResolution": {
14
+ "rule": "Always resolve tokens through the semantic layer. Never use primitive tokens directly in component code.",
15
+ "correct": "var(--inkblot-semantic-color-text-primary)",
16
+ "incorrect": "var(--inkblot-color-neutral-gray-900)",
17
+ "exception": "Primitive tokens are acceptable only when creating new semantic mappings or in token definition files."
18
+ },
19
+ "colorMode": {
20
+ "rule": "All components must work in both light and dark mode. Use semantic tokens which auto-switch.",
21
+ "implementation": "Apply [data-theme='dark'] to <html> or <body>. Override CSS variables for dark values."
22
+ },
23
+ "spacing": {
24
+ "rule": "Use spacing tokens from the 4pt grid. Never use arbitrary pixel values.",
25
+ "scale": "0(0) 1(4px) 2(8px) 3(12px) 4(16px) 5(20px) 6(24px) 8(32px) 10(40px) 12(48px) 16(64px) 20(80px) 24(96px) 32(128px)"
26
+ }
27
+ },
28
+
29
+ "componentRendering": {
30
+ "description": "Step-by-step instructions for AI agents assembling UI components",
31
+
32
+ "button": {
33
+ "input": { "variant": "primary | secondary | ghost | danger", "size": "sm | md | lg", "label": "string (required)", "icon": "string | null", "iconPosition": "leading | trailing", "disabled": "boolean", "loading": "boolean", "onClick": "function" },
34
+ "output": "<button class='btn btn-{variant} btn-{size}' {disabled ? 'aria-disabled=\"true\"' : ''} {loading ? 'aria-live=\"polite\"' : ''}>{icon && iconPosition === 'leading' ? <Icon /> : null}<span>{label}</span>{icon && iconPosition === 'trailing' ? <Icon /> : null}{loading ? <Spinner size='sm' /> : null}</button>",
35
+ "validation": [
36
+ "label must be non-empty string",
37
+ "if icon-only: aria-label is required",
38
+ "if loading: disable click handler, show spinner",
39
+ "if disabled: set aria-disabled, not disabled attribute (keeps in tab order)"
40
+ ]
41
+ },
42
+
43
+ "input": {
44
+ "input": { "type": "text | email | password | number | tel | url | search", "label": "string (required)", "name": "string (required)", "placeholder": "string | null", "value": "string", "error": "string | null", "helpText": "string | null", "required": "boolean", "disabled": "boolean", "size": "sm | md | lg" },
45
+ "output": "<div class='field'><label for='{name}'>{label}{required ? <span aria-hidden='true'>*</span> : null}</label><input id='{name}' name='{name}' type='{type}' placeholder='{placeholder}' value='{value}' aria-required='{required}' aria-invalid='{!!error}' aria-describedby='{error ? name + \"-error\" : helpText ? name + \"-help\" : undefined}' {disabled ? 'aria-disabled=\"true\"' : ''} />{error ? <p id='{name}-error' class='field-error' role='alert'>{error}</p> : null}{helpText && !error ? <p id='{name}-help' class='field-help'>{helpText}</p> : null}</div>",
46
+ "validation": [
47
+ "label is always required — never rely on placeholder alone",
48
+ "error message replaces help text when present",
49
+ "aria-describedby points to error or help text element"
50
+ ]
51
+ },
52
+
53
+ "card": {
54
+ "input": { "variant": "elevated | outlined | filled", "interactive": "boolean", "href": "string | null", "image": "{ src, alt, aspectRatio } | null", "title": "string", "description": "string | null", "metadata": "string | null", "actions": "ButtonProps[] | null" },
55
+ "output": "<{interactive ? 'a href=\"{href}\"' : 'div'} class='card card-{variant}' {interactive ? '' : ''}>{image ? <img src='{image.src}' alt='{image.alt}' style='aspect-ratio: {image.aspectRatio}' loading='lazy' /> : null}<div class='card-body'><h3 class='card-title'>{title}</h3>{description ? <p class='card-desc'>{description}</p> : null}{metadata ? <span class='card-meta'>{metadata}</span> : null}</div>{actions ? <div class='card-actions'>{actions.map(a => <Button {...a} />)}</div> : null}</{interactive ? 'a' : 'div'}>",
56
+ "validation": [
57
+ "interactive cards must be wrapped in <a> or have role='link'",
58
+ "images must have alt text",
59
+ "truncate description to 3 lines, title to 2 lines"
60
+ ]
61
+ },
62
+
63
+ "modal": {
64
+ "input": { "title": "string (required)", "size": "sm | md | lg | full", "children": "ReactNode", "onClose": "function (required)", "footer": "ReactNode | null" },
65
+ "output": "<div class='modal-overlay' onClick='{onClose}' aria-hidden='true'></div><div class='modal modal-{size}' role='dialog' aria-modal='true' aria-labelledby='modal-title'><div class='modal-header'><h2 id='modal-title'>{title}</h2><button class='modal-close' onClick='{onClose}' aria-label='Close'>×</button></div><div class='modal-body'>{children}</div>{footer ? <div class='modal-footer'>{footer}</div> : null}</div>",
66
+ "validation": [
67
+ "title is required",
68
+ "onClose is required — user must always be able to dismiss",
69
+ "trap focus inside modal",
70
+ "lock body scroll",
71
+ "return focus to trigger on close"
72
+ ]
73
+ },
74
+
75
+ "toast": {
76
+ "input": { "variant": "info | success | warning | error", "message": "string (required)", "action": "{ label: string, onClick: function } | null", "autoDismiss": "boolean (default: true)", "duration": "number (default: 5000)" },
77
+ "validation": [
78
+ "error toasts: never auto-dismiss",
79
+ "message must be concise — 1-2 sentences max",
80
+ "max 3 visible toasts at once"
81
+ ]
82
+ }
83
+ },
84
+
85
+ "layoutAssembly": {
86
+ "description": "How AI agents should assemble page layouts",
87
+ "steps": [
88
+ "1. Determine page type: content page, dashboard, form, listing, detail, auth",
89
+ "2. Select layout pattern from system/grid.json: singleColumn, sidebarLayout, cardGrid, splitScreen, masonry",
90
+ "3. Apply container: standard (1200px) for most pages, content (768px) for text-heavy, wide (1440px) for dashboards",
91
+ "4. Place components on grid using span rules from breakpoint table",
92
+ "5. Apply spacing: var(--inkblot-spacing-8) between major sections, var(--inkblot-spacing-4) between related items",
93
+ "6. Add page-level elements: header (sticky, z-index: sticky), main content, footer",
94
+ "7. Ensure color mode support: test with both light and dark tokens",
95
+ "8. Validate accessibility: contrast ratios, focus order, screen reader flow"
96
+ ],
97
+ "examples": {
98
+ "dashboardPage": {
99
+ "layout": "sidebarLayout",
100
+ "container": "wide (1440px)",
101
+ "sidebar": { "width": "280px", "content": "nav links, user avatar, settings" },
102
+ "main": { "content": "cardGrid for metrics (span 3 each), table below for data" },
103
+ "header": "sticky, z-index: sticky, contains breadcrumb + page title + actions"
104
+ },
105
+ "articlePage": {
106
+ "layout": "singleColumn",
107
+ "container": "content (768px)",
108
+ "content": "heading-1 title, body-large intro, body-default paragraphs, images (16:9)",
109
+ "spacing": "var(--inkblot-spacing-8) between sections"
110
+ },
111
+ "authPage": {
112
+ "layout": "splitScreen",
113
+ "left": "Brand illustration or gradient, centered logo",
114
+ "right": "Form: heading-2, inputs, primary button, link to alternative auth"
115
+ }
116
+ }
117
+ },
118
+
119
+ "edgeCases": {
120
+ "missingData": {
121
+ "rule": "Never show undefined, null, NaN, or empty strings in the UI.",
122
+ "strings": "Show em-dash (—) for missing text values",
123
+ "numbers": "Show em-dash (—) for missing numeric values",
124
+ "images": "Show skeleton or neutral placeholder",
125
+ "lists": "Show empty state with action",
126
+ "dates": "Show em-dash (—)"
127
+ },
128
+ "longContent": {
129
+ "rule": "Truncate with ellipsis. Provide full content via tooltip or expand.",
130
+ "headings": "Never truncate. Allow wrapping.",
131
+ "descriptions": "Max 3 lines then truncate.",
132
+ "tableCells": "Single line truncate with tooltip.",
133
+ "userNames": "Max 20 characters then truncate."
134
+ },
135
+ "overflowingContainers": {
136
+ "rule": "Content must never break layout. Use overflow-hidden on containers.",
137
+ "horizontal": "overflow-x: hidden on page. Scroll on specific containers (tables, carousels).",
138
+ "vertical": "Modals, dropdowns: max-height with overflow-y: auto."
139
+ },
140
+ "dynamicContent": {
141
+ "rule": "All containers must handle variable content lengths without breaking.",
142
+ "minHeight": "Set min-height on cards, list items to prevent collapse when empty.",
143
+ "maxHeight": "Set max-height + overflow on expandable content to prevent page jump."
144
+ },
145
+ "errorRecovery": {
146
+ "networkError": "Show inline error with retry button. Preserve user input.",
147
+ "validationError": "Highlight invalid fields. Scroll to first error. Announce via aria-live.",
148
+ "notFound": "Show 404 page with search bar and navigation links.",
149
+ "unauthorized": "Redirect to login. Preserve intended destination for post-auth redirect."
150
+ }
151
+ },
152
+
153
+ "testing": {
154
+ "accessibility": {
155
+ "automated": "Run axe-core on every component. Zero violations for critical and serious.",
156
+ "manual": "Tab through every page. Verify focus order. Test with screen reader.",
157
+ "contrast": "Verify all text meets 7:1 (AAA). Use Chrome DevTools contrast checker."
158
+ },
159
+ "visual": {
160
+ "tool": "Chromatic or Percy for visual regression",
161
+ "coverage": "Screenshot every component in: default, hover, focus, active, disabled, error, loading, dark mode"
162
+ },
163
+ "responsive": {
164
+ "breakpoints": "Test at: 375px (iPhone), 768px (iPad), 1024px (laptop), 1440px (desktop), 1920px (wide)",
165
+ "rule": "No horizontal scroll at any breakpoint. No overlapping elements."
166
+ }
167
+ }
168
+ }
@@ -0,0 +1,340 @@
1
+ {
2
+ "$schema": "https://inkblot.studio/system/v1/cli.schema.json",
3
+ "name": "Citron CLI & TUI Design System",
4
+ "version": "1.0.0",
5
+ "description": "Deterministic rules for terminal apps, TUIs, and AI chat CLIs. Aligned with Citron visual language: warm restraint, impeccable hierarchy, and parity with web semantics. Agents and humans implement the same spec — no guesswork.",
6
+
7
+ "alignment": {
8
+ "webSystem": "system/components.json, system/content.json, system/ai.json",
9
+ "tokensResolved": "dist/ai/inkblot-tokens-resolved.json",
10
+ "brandAccentHex": "#c4a030",
11
+ "neutralTextHex": "#1d1c19",
12
+ "mutedHex": "#6b6a63",
13
+ "surfaceHex": "#fafaf7",
14
+ "rule": "CLI is not a second-class UI. Reuse semantic meaning from web tokens; map to ANSI only at the render layer."
15
+ },
16
+
17
+ "principles": [
18
+ {
19
+ "id": "calm-density",
20
+ "name": "Calm density",
21
+ "rule": "Prefer whitespace and alignment over borders. One strong accent (citron) per viewport; everything else is neutral or semantic status."
22
+ },
23
+ {
24
+ "id": "tty-truth",
25
+ "name": "TTY truth",
26
+ "rule": "Detect capabilities: isTTY, columns, color support (16 / 256 / truecolor), unicode width, emoji support. Degrade gracefully; never crash on dumb terminals."
27
+ },
28
+ {
29
+ "id": "no-color-sacred",
30
+ "name": "NO_COLOR is sacred",
31
+ "rule": "Honor NO_COLOR and CLICOLOR=0. When color is off, rely on weight, spacing, labels, and Unicode box drawing — never color as the only differentiator."
32
+ },
33
+ {
34
+ "id": "one-thing-per-line",
35
+ "name": "One decision per beat",
36
+ "rule": "Do not stack unrelated questions. Finish one thought before the next. AI prompts: separate system context from user task visually and in copy."
37
+ },
38
+ {
39
+ "id": "immutable-audit",
40
+ "name": "Immutable audit trail",
41
+ "rule": "For AI sessions, never mutate prior transcript styling retroactively. Append-only feel; corrections are new lines with clear attribution."
42
+ }
43
+ ],
44
+
45
+ "environment": {
46
+ "minimumColumns": 40,
47
+ "comfortableColumns": 72,
48
+ "maxProseWidth": 72,
49
+ "rule": "Hard-wrap prose at maxProseWidth for readability. Below minimumColumns: collapse side padding, hide non-essential chrome, prefer vertical lists over tables.",
50
+ "unicode": {
51
+ "rule": "Assume UTF-8. Test with Windows Terminal, iTerm2, Linux console. Provide ASCII fallbacks for box-drawing when unicode is unsupported.",
52
+ "preferredBoxDrawing": "light single (┌─┐│└┘) for panels; heavy only for modal emphasis"
53
+ },
54
+ "stdoutStderr": {
55
+ "rule": "Human-facing frames, prompts, and branded output → stdout. Diagnostics, logs, machine-readable errors → stderr. Never interleave both in one logical block without a delimiter."
56
+ }
57
+ },
58
+
59
+ "color": {
60
+ "truecolor": {
61
+ "sequenceTemplate": "\\x1b[38;2;{R};{G};{B}m",
62
+ "roles": {
63
+ "brand": { "hex": "#c4a030", "usage": "Headings, primary actions, key affordances, logo line" },
64
+ "brandDim": { "hex": "#a38427", "usage": "Secondary emphasis, dividers tinted to brand" },
65
+ "text": { "hex": "#1d1c19", "usage": "Default foreground on light terminal bg — if terminal is dark, invert via background detection or theme flag" },
66
+ "muted": { "hex": "#6b6a63", "usage": "Hints, timestamps, metadata, disabled copy" },
67
+ "success": { "hex": "#358c46", "usage": "Positive completion — always pair with ✓ or OK label" },
68
+ "warning": { "hex": "#c48c1a", "usage": "Recoverable issues" },
69
+ "error": { "hex": "#be3e35", "usage": "Failures, validation — pair with short imperative fix" },
70
+ "info": { "hex": "#5790ad", "usage": "Neutral informational" },
71
+ "inverse": { "hex": "#ffffff", "on": "#1d1c19", "usage": "Inverted pills, high-contrast badges on supported themes" }
72
+ }
73
+ },
74
+ "darkTerminalAdaptation": {
75
+ "rule": "When background is detected or forced dark, swap text to #fafaf7 and muted to #93928a; keep brand at #d9bc58 or #e8d38a for contrast.",
76
+ "never": "Do not use #c4a030 on #1d1c19 without checking contrast; prefer citron-300/400 from tokens for dark-on-dark."
77
+ },
78
+ "ansi256Fallback": {
79
+ "rule": "If truecolor unsupported, map to nearest xterm-256; if only 16 colors, map brand to yellow/bright-yellow, error to red, success to green, info to cyan, muted to bright black/gray."
80
+ },
81
+ "stripMarkers": {
82
+ "rule": "Offer --no-color and respect env. Strip OSC sequences for logging to files."
83
+ },
84
+ "devtoolsMode": {
85
+ "description": "Optional terminal palette when the CLI is explicitly in development or internal-tooling context. Matches web devtools tokens (cool blue slate). Not the product citron brand.",
86
+ "spec": "system/devtools.json",
87
+ "activateWhen": "Explicit flag (e.g. INKBLOT_DEVTOOLS_CLI=1) or dev subcommand — never default for customer-facing binaries.",
88
+ "truecolor": {
89
+ "dominant": "#2563EB",
90
+ "support": "#60A5FA",
91
+ "accentPositive": "#10B981",
92
+ "primary": "#2563EB",
93
+ "primaryHover": "#60A5FA",
94
+ "background": "#020617",
95
+ "surface": "#0B1220",
96
+ "text": "#F8FAFC",
97
+ "muted": "#CBD5E1",
98
+ "border": "#1E293B"
99
+ }
100
+ }
101
+ },
102
+
103
+ "typography": {
104
+ "hierarchy": [
105
+ { "level": "title", "rule": "Single H1 equivalent per screen: bold + brand color + 1 blank line below" },
106
+ { "level": "section", "rule": "Section headers: bold default color; optional brand left rule (│) in muted" },
107
+ { "level": "body", "rule": "Regular weight; 4–8 char left indent for nested explanations" },
108
+ { "level": "label", "rule": "Muted caps or small-caps sentence case; never shout" },
109
+ { "level": "code", "rule": "Monospace; pad inline code with single space; blocks: indented 2 + subtle left border character" }
110
+ ],
111
+ "wrapping": {
112
+ "rule": "Break at word boundaries; preserve leading indent on wrap; URLs may break with zero-width hints only if library supports",
113
+ "lists": "Hanging indent: marker column 2 chars + gap + body wraps aligned to body column"
114
+ },
115
+ "truncation": {
116
+ "rule": "Ellipsis … only at end; never truncate filenames mid-character without showing start and end (…foo.ts)",
117
+ "tables": "Truncate cells with padEnd visible width; full value in footer line on narrow terminals"
118
+ }
119
+ },
120
+
121
+ "layout": {
122
+ "margins": {
123
+ "screenHorizontal": 2,
124
+ "sectionVertical": 1,
125
+ "blockVertical": 1,
126
+ "rule": "Consistent left margin for entire session; AI chat: same gutter for user and assistant"
127
+ },
128
+ "dividers": {
129
+ "weak": "──────────────── (muted, full width of content area)",
130
+ "strong": "══════════════ (brand dim, only between major phases)",
131
+ "rule": "No more than one strong divider per viewport height"
132
+ },
133
+ "alignment": {
134
+ "tables": "Header row separator mandatory; numeric columns right-align; text left; booleans center on ·/×",
135
+ "banners": "Center only for splash / version; never center error text"
136
+ }
137
+ },
138
+
139
+ "frames": {
140
+ "panel": {
141
+ "rule": "Use box drawing for grouped settings, AI tool output, forms. Inner padding 1 line vertical, 2 chars horizontal minimum.",
142
+ "title": "Top border with optional centered title in muted; never empty top rule without label"
143
+ },
144
+ "callout": {
145
+ "variants": ["info", "success", "warning", "error"],
146
+ "rule": "Prefix line with variant tag [info] in semantic color; body indented; max 5 lines before fold with \"… (+N lines)\""
147
+ }
148
+ },
149
+
150
+ "components": {
151
+ "banner": {
152
+ "anatomy": ["optional mascot ascii line", "app name + version (muted)", "one-line value prop (optional)", "blank"],
153
+ "rule": "No animation on banner except optional one-shot fade via clear — avoid flicker"
154
+ },
155
+ "statusLine": {
156
+ "anatomy": ["context left", "spacer", "mode or latency right"],
157
+ "rule": "Dim; update in place only with full-width clear pattern to avoid jitter"
158
+ },
159
+ "prompt": {
160
+ "primary": {
161
+ "pattern": "{brand}▸ {label}",
162
+ "rule": "Trailing space after chevron; label is short verb phrase: \"Ask\", \"Command\", \"Path\""
163
+ },
164
+ "secondary": {
165
+ "pattern": "{muted}? {question} {defaultHint}",
166
+ "defaultHint": "(y/N) or (/path/to/default)",
167
+ "rule": "Defaults in parentheses; capital letter shows default Enter behavior"
168
+ },
169
+ "password": {
170
+ "rule": "Echo mask • or nothing; never echo plaintext; on error re-prompt with reason only once before abort count"
171
+ },
172
+ "multiline": {
173
+ "rule": "Show delimiter line (e.g. CTRL+D to finish) in muted; preserve blank lines; strip trailing CR"
174
+ },
175
+ "validation": {
176
+ "inline": "Error on new line under field with ├─ prefix; success never noisy — optional single ✓ muted"
177
+ }
178
+ },
179
+ "textField": {
180
+ "anatomy": ["label row", "hint row optional", "input line with cursor", "error row optional"],
181
+ "rule": "Label ends with colon; required * in brand; screen reader: announce errors with role text if TUI lib supports"
182
+ },
183
+ "selectMenu": {
184
+ "rule": "Numbers or letters for options; current selection inverted or brand arrow ▸; show scroll hint if > 7 items",
185
+ "search": "Filter line above list; no results state explicit"
186
+ },
187
+ "table": {
188
+ "rule": "Zebra optional; never required for clarity — alignment + header rule sufficient",
189
+ "sort": "Indicator in header ▲▼ muted; default sort documented in footer"
190
+ },
191
+ "progress": {
192
+ "determinate": "Bar width = min(40, cols-4); use █ and ░ or smooth unicode blocks",
193
+ "indeterminate": "Spinner (see motion) or single-line cycling dots — not both",
194
+ "eta": "Show only when stable; hide if jitter > 20%"
195
+ },
196
+ "spinner": {
197
+ "frames": ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"],
198
+ "intervalMs": 80,
199
+ "asciiFallback": ["|", "/", "-", "\\"],
200
+ "rule": "One spinner per viewport; stop cleanly with space clear"
201
+ },
202
+ "toastInline": {
203
+ "rule": "Single line; prefix emoji only if unicode allowed; prefer text tag [ok]"
204
+ },
205
+ "confirmation": {
206
+ "safe": "Question + (y/N)",
207
+ "destructive": "Red/error color word DELETE/RESET in message + require typed phrase or Y capital twice",
208
+ "rule": "Never default Yes on destructive"
209
+ },
210
+ "wizard": {
211
+ "rule": "Step x of y in muted right or below title; allow back command consistent (:back)"
212
+ },
213
+ "emptyState": {
214
+ "anatomy": ["short headline", "one sentence guidance", "primary command example"],
215
+ "rule": "Never blank screen with only cursor"
216
+ },
217
+ "errorPanel": {
218
+ "anatomy": ["error title", "one human sentence", "optional code in monospace", "suggested fix as bullet", "support hint muted"],
219
+ "rule": "No stack traces in primary panel — stderr only"
220
+ },
221
+ "logStream": {
222
+ "rule": "Levels: DEBUG muted, INFO default, WARN warning color, ERROR error color; timestamps aligned column"
223
+ },
224
+ "keyLegend": {
225
+ "rule": "Footer: ^Q quit · ^S save · ? help; align keys; use · separator"
226
+ }
227
+ },
228
+
229
+ "aiAssistantUx": {
230
+ "goals": [
231
+ "Surpass default Claude/OpenClaw terminal clutter: fewer ornaments, stronger hierarchy, faster scan",
232
+ "Make tool boundaries obvious without comic-book chrome",
233
+ "Preserve user focus: input area always visually anchored"
234
+ ],
235
+ "sessionChrome": {
236
+ "header": "App name · model (muted) · context % or tokens optional right",
237
+ "inputDock": "Blank line + rule + prompt; never place user typing above transcript without scroll lock",
238
+ "rule": "On resize, reflow transcript; keep last user message and prompt visible"
239
+ },
240
+ "roles": {
241
+ "user": {
242
+ "prefix": "You",
243
+ "style": "Bold label + indent body; brand chevron on continuation lines optional",
244
+ "rule": "Never collapse user messages into assistant color"
245
+ },
246
+ "assistant": {
247
+ "prefix": "Citron",
248
+ "style": "Default text; section subheadings muted; lists tight",
249
+ "rule": "No fake personality filler; lead with answer"
250
+ },
251
+ "system": {
252
+ "prefix": "System",
253
+ "style": "Muted frame; collapsible one-line summary + expand",
254
+ "rule": "System prompts not shown by default in consumer mode"
255
+ },
256
+ "tool": {
257
+ "prefix": "Tool",
258
+ "collapsed": "{muted}▸ {toolName}({shortArgs}) … done in {ms}ms",
259
+ "expanded": "Monospace panel; JSON pretty-print 2 spaces; max height with scroll",
260
+ "rule": "Green/red only for success/failure + text label"
261
+ }
262
+ },
263
+ "streaming": {
264
+ "cursor": "Dim block ▌ at end; remove on complete",
265
+ "partialMarkdown": "Render incrementally where safe; code fence only when closing fence seen",
266
+ "stall": "After 6s no token, show muted \"Still thinking…\" on same line once"
267
+ },
268
+ "composer": {
269
+ "rule": "Multi-line composer: delimiter visible; send shortcut in legend; show character soft limit warn at 80% in muted",
270
+ "paste": "Detect large paste; confirm before send if > threshold"
271
+ },
272
+ "markdownInTerminal": {
273
+ "headings": "H1 = bold + blank; H2 = bold; H3 = bold muted",
274
+ "lists": "• or - consistent; numbered for sequences",
275
+ "code": "Fenced blocks: full width panel; line numbers optional muted",
276
+ "links": "OSC 8 hyperlinks if supported; else full URL on own line",
277
+ "blockquotes": "│ prefix muted"
278
+ },
279
+ "citations": {
280
+ "rule": "Superscript numbers or bracket [1] in muted; sources listed compactly after message with dividers"
281
+ },
282
+ "safety": {
283
+ "rule": "When refusing, use neutral info color + clear reason + one alternative; never taunt or joke"
284
+ }
285
+ },
286
+
287
+ "copyTone": {
288
+ "voice": "Warm, precise, concise. Apple-adjacent calm, Citron-specific courtesy.",
289
+ "rules": [
290
+ "Imperative for actions (\"Run deploy\"), sentence case for descriptions",
291
+ "Error copy: what broke + how to fix + optional why",
292
+ "No exclamation marks in system messages except genuine success milestone",
293
+ "Avoid \"Something went wrong\" — name the subsystem"
294
+ ],
295
+ "forbidden": ["LOL", "Oops", "Hmm", "As an AI", "magic", "just"]
296
+ },
297
+
298
+ "keyboardAndShortcuts": {
299
+ "rule": "Document all global shortcuts in ? help; single-letter must not shadow readline without warning",
300
+ "accessibility": "Provide long-form commands mirroring shortcuts (/help vs ?)"
301
+ },
302
+
303
+ "accessibility": {
304
+ "contrast": "Treat terminal background as unknown; test on black, white, and solarized; ensure brand not sole focus indicator",
305
+ "colorBlind": "Pair success/error with shape and words",
306
+ "motion": "CLIMOTION=0 or global flag disables spinner and progress animations",
307
+ "screenReaders": "When using a TUI library, expose labels; plain CLI: structured text is easier than canvas hacks"
308
+ },
309
+
310
+ "performance": {
311
+ "rule": "Batch redraws; throttle resize; avoid clearing entire screen each keypress",
312
+ "aiStreaming": "Chunk render max 60fps equivalent; coalesce tiny deltas"
313
+ },
314
+
315
+ "securityDisplay": {
316
+ "secrets": "Mask API keys, tokens, cookies in all output; never repeat full secret in confirm",
317
+ "spoofing": "Show remote host and identity on first connect in frame"
318
+ },
319
+
320
+ "qualityBar": {
321
+ "versusDefaults": [
322
+ "Fewer decorative Unicode characters than typical chat CLIs",
323
+ "Stronger alignment and padding than default inquirer",
324
+ "Clearer tool boundaries than unstructured log interleaving",
325
+ "Calmer color palette than rainbow debug modes"
326
+ ],
327
+ "definitionOfDone": [
328
+ "Works at 40 cols in monochrome",
329
+ "NO_COLOR identical hierarchy to color",
330
+ "Every interactive flow has escape + help",
331
+ "AI session: roles visually distinct without garish borders"
332
+ ]
333
+ },
334
+
335
+ "implementationNotes": {
336
+ "libraries": "Ink/blessed/react-ink/orchard — map this spec to component props; keep theme object as single source",
337
+ "testing": "Snapshot tests at 40, 72, 120 cols; golden files for NO_COLOR",
338
+ "referenceCli": "cli/citron-mascot.mjs follows NO_COLOR and truecolor for brand line"
339
+ }
340
+ }