@citron-systems/citron-ds 1.0.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/README.md +149 -0
- package/dist/ai/inkblot-ai-reference.json +570 -0
- package/dist/ai/inkblot-tokens-resolved.json +199 -0
- package/dist/ai/inkblot-tokens-schema.json +54 -0
- package/dist/css/inkblot-variables.css +203 -0
- package/dist/js/inkblot-tokens.js +264 -0
- package/dist/js/inkblot-tokens.json +199 -0
- package/dist/scss/_inkblot-variables.scss +200 -0
- package/package.json +61 -0
- package/tokens/primitive/breakpoint.tokens.json +14 -0
- package/tokens/primitive/color.tokens.json +71 -0
- package/tokens/primitive/duration.tokens.json +38 -0
- package/tokens/primitive/grid.tokens.json +34 -0
- package/tokens/primitive/radius.tokens.json +26 -0
- package/tokens/primitive/shadow.tokens.json +41 -0
- package/tokens/primitive/spacing.tokens.json +38 -0
- package/tokens/primitive/typography.tokens.json +60 -0
- package/tokens/primitive/zindex.tokens.json +19 -0
- package/tokens/semantic/dark.tokens.json +43 -0
- package/tokens/semantic/inkblot.semantic.tokens.json +140 -0
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://inkblot.studio/ai/v1/reference.schema.json",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"identity": "Citron — warmly minimal, quietly distinctive. Citrus interpreted through restraint, intelligence, and taste.",
|
|
5
|
+
"purpose": "Single-file reference for AI agents to produce complex components and systems without ambiguity. Include this file in your AI context.",
|
|
6
|
+
|
|
7
|
+
"tokenMap": {
|
|
8
|
+
"description": "CSS variable names mapped to categories. AI: prefer semantic tokens. Brand accent is citron (warm amber-citrus, not blue).",
|
|
9
|
+
"cssVariables": {
|
|
10
|
+
"color": {
|
|
11
|
+
"background": {
|
|
12
|
+
"primary": "var(--inkblot-semantic-color-background-primary)",
|
|
13
|
+
"secondary": "var(--inkblot-semantic-color-background-secondary)",
|
|
14
|
+
"tertiary": "var(--inkblot-semantic-color-background-tertiary)"
|
|
15
|
+
},
|
|
16
|
+
"text": {
|
|
17
|
+
"primary": "var(--inkblot-semantic-color-text-primary)",
|
|
18
|
+
"secondary": "var(--inkblot-semantic-color-text-secondary)",
|
|
19
|
+
"tertiary": "var(--inkblot-semantic-color-text-tertiary)",
|
|
20
|
+
"inverse": "var(--inkblot-semantic-color-text-inverse)",
|
|
21
|
+
"link": "var(--inkblot-semantic-color-text-link)",
|
|
22
|
+
"linkHover": "var(--inkblot-semantic-color-text-link-hover)"
|
|
23
|
+
},
|
|
24
|
+
"border": {
|
|
25
|
+
"default": "var(--inkblot-semantic-color-border-default)",
|
|
26
|
+
"strong": "var(--inkblot-semantic-color-border-strong)",
|
|
27
|
+
"focus": "var(--inkblot-semantic-color-border-focus)"
|
|
28
|
+
},
|
|
29
|
+
"interactive": {
|
|
30
|
+
"primary": "var(--inkblot-semantic-color-interactive-primary)",
|
|
31
|
+
"primaryHover": "var(--inkblot-semantic-color-interactive-primary-hover)",
|
|
32
|
+
"primaryActive": "var(--inkblot-semantic-color-interactive-primary-active)",
|
|
33
|
+
"secondary": "var(--inkblot-semantic-color-interactive-secondary)",
|
|
34
|
+
"secondaryHover": "var(--inkblot-semantic-color-interactive-secondary-hover)"
|
|
35
|
+
},
|
|
36
|
+
"status": {
|
|
37
|
+
"success": "var(--inkblot-semantic-color-status-success)",
|
|
38
|
+
"warning": "var(--inkblot-semantic-color-status-warning)",
|
|
39
|
+
"error": "var(--inkblot-semantic-color-status-error)",
|
|
40
|
+
"info": "var(--inkblot-semantic-color-status-info)"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"spacing": "var(--inkblot-spacing-{n}) for n in [0,1,2,3,4,5,6,8,10,12,16,20,24,32]",
|
|
44
|
+
"typography": {
|
|
45
|
+
"fontFamily": {
|
|
46
|
+
"sans": "var(--inkblot-typography-font-family-sans)",
|
|
47
|
+
"mono": "var(--inkblot-typography-font-family-mono)",
|
|
48
|
+
"display": "var(--inkblot-typography-font-family-display)"
|
|
49
|
+
},
|
|
50
|
+
"fontSize": "var(--inkblot-typography-font-size-{xs|sm|base|md|lg|xl|2xl|3xl|4xl|5xl})",
|
|
51
|
+
"fontWeight": "var(--inkblot-typography-font-weight-{regular|medium|semibold|bold})",
|
|
52
|
+
"semantic": {
|
|
53
|
+
"heading1": "var(--inkblot-semantic-typography-heading-1)",
|
|
54
|
+
"heading2": "var(--inkblot-semantic-typography-heading-2)",
|
|
55
|
+
"heading3": "var(--inkblot-semantic-typography-heading-3)",
|
|
56
|
+
"heading4": "var(--inkblot-semantic-typography-heading-4)",
|
|
57
|
+
"body": "var(--inkblot-semantic-typography-body-default)",
|
|
58
|
+
"bodySmall": "var(--inkblot-semantic-typography-body-small)",
|
|
59
|
+
"bodyLarge": "var(--inkblot-semantic-typography-body-large)",
|
|
60
|
+
"label": "var(--inkblot-semantic-typography-label-default)"
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"radius": "var(--inkblot-radius-{none|sm|md|lg|xl|2xl|full})",
|
|
64
|
+
"shadow": "var(--inkblot-shadow-{none|xs|sm|md|lg|xl})",
|
|
65
|
+
"borderWidth": "var(--inkblot-border-width-{none|thin|medium|thick})",
|
|
66
|
+
"duration": "var(--inkblot-duration-{instant|fast|normal|slow|slower})",
|
|
67
|
+
"easing": {
|
|
68
|
+
"default": "cubic-bezier(0.25, 0.1, 0.25, 1)",
|
|
69
|
+
"in": "cubic-bezier(0.32, 0, 0.67, 0)",
|
|
70
|
+
"out": "cubic-bezier(0.33, 1, 0.68, 1)",
|
|
71
|
+
"inOut": "cubic-bezier(0.65, 0, 0.35, 1)",
|
|
72
|
+
"expressive": "cubic-bezier(0.22, 1, 0.36, 1)"
|
|
73
|
+
},
|
|
74
|
+
"zIndex": "var(--inkblot-z-index-{base|raised|dropdown|sticky|overlay|modal|popover|toast})",
|
|
75
|
+
"size": {
|
|
76
|
+
"touchTargetMin": "var(--inkblot-size-touch-target-min)",
|
|
77
|
+
"touchTargetComfortable": "var(--inkblot-size-touch-target-comfortable)",
|
|
78
|
+
"iconSm": "var(--inkblot-size-icon-sm)",
|
|
79
|
+
"iconMd": "var(--inkblot-size-icon-md)",
|
|
80
|
+
"iconLg": "var(--inkblot-size-icon-lg)"
|
|
81
|
+
},
|
|
82
|
+
"opacity": {
|
|
83
|
+
"disabled": "var(--inkblot-opacity-disabled)",
|
|
84
|
+
"overlay": "var(--inkblot-opacity-overlay)"
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
"breakpoints": {
|
|
90
|
+
"description": "Mobile-first. Use min-width in media queries.",
|
|
91
|
+
"sm": "640px",
|
|
92
|
+
"md": "768px",
|
|
93
|
+
"lg": "1024px",
|
|
94
|
+
"xl": "1280px",
|
|
95
|
+
"2xl": "1536px",
|
|
96
|
+
"cssExample": "@media (min-width: 1024px) { /* desktop styles */ }"
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
"grid": {
|
|
100
|
+
"description": "Column count per breakpoint. Gutter = gap between columns.",
|
|
101
|
+
"columnsByBreakpoint": {
|
|
102
|
+
"<640px": 4,
|
|
103
|
+
"640-767px": 4,
|
|
104
|
+
"768-1023px": 8,
|
|
105
|
+
"1024-1279px": 12,
|
|
106
|
+
"1280-1535px": 12,
|
|
107
|
+
">=1536px": 16
|
|
108
|
+
},
|
|
109
|
+
"maxWidth": {
|
|
110
|
+
"content": "768px",
|
|
111
|
+
"container": "1200px",
|
|
112
|
+
"wide": "1440px"
|
|
113
|
+
},
|
|
114
|
+
"cssGrid": "display: grid; grid-template-columns: repeat(12, 1fr); gap: var(--inkblot-spacing-6); max-width: 1200px; margin-inline: auto; padding-inline: var(--inkblot-spacing-8);"
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
"compositePatterns": {
|
|
118
|
+
"description": "How to assemble complex components. AI: follow these structures.",
|
|
119
|
+
"formField": {
|
|
120
|
+
"structure": ["label", "input | select | textarea", "helpText | errorMessage"],
|
|
121
|
+
"spacing": "gap: var(--inkblot-spacing-1) between label and input; var(--inkblot-spacing-1) between input and help/error",
|
|
122
|
+
"labelRequired": true,
|
|
123
|
+
"errorReplacesHelp": true
|
|
124
|
+
},
|
|
125
|
+
"form": {
|
|
126
|
+
"structure": ["formField[]", "actions (button group)"],
|
|
127
|
+
"spacing": "var(--inkblot-spacing-6) between fields; var(--inkblot-spacing-8) before actions",
|
|
128
|
+
"actionsAlign": "flex-end | flex-start | center",
|
|
129
|
+
"primaryAction": "First in actions array"
|
|
130
|
+
},
|
|
131
|
+
"cardWithActions": {
|
|
132
|
+
"structure": ["header? | image?", "body", "footer with buttons"],
|
|
133
|
+
"padding": "var(--inkblot-spacing-6)",
|
|
134
|
+
"actionGap": "var(--inkblot-spacing-3)"
|
|
135
|
+
},
|
|
136
|
+
"modalWithForm": {
|
|
137
|
+
"structure": ["overlay", "modal[header, body=form, footer=form.actions]"],
|
|
138
|
+
"focusTrap": true,
|
|
139
|
+
"escapeCloses": true
|
|
140
|
+
},
|
|
141
|
+
"dataTable": {
|
|
142
|
+
"structure": ["table[thead, tbody]", "pagination?"],
|
|
143
|
+
"headerStyle": "uppercase, label typography, secondary color",
|
|
144
|
+
"rowHover": "background: var(--inkblot-semantic-color-background-secondary)",
|
|
145
|
+
"cellPadding": "var(--inkblot-spacing-3) var(--inkblot-spacing-4)"
|
|
146
|
+
},
|
|
147
|
+
"listItem": {
|
|
148
|
+
"structure": ["avatar? | icon?", "content[title, subtitle?]", "trailing? (badge, button, chevron)"],
|
|
149
|
+
"minHeight": "var(--inkblot-size-touch-target-comfortable)",
|
|
150
|
+
"padding": "var(--inkblot-spacing-3) var(--inkblot-spacing-4)"
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
"iconSemantics": {
|
|
155
|
+
"description": "Complete icon-to-meaning mapping. Library: Lucide (free, ISC). Format: kebab-case | PascalCase (React). Full spec in system/icons.json.",
|
|
156
|
+
"library": "Lucide (https://lucide.dev). Install: npm install lucide-react. 24×24 stroke icons, strokeWidth: 2.",
|
|
157
|
+
"sizes": {
|
|
158
|
+
"xs": "12px",
|
|
159
|
+
"sm": "16px → var(--inkblot-size-icon-sm)",
|
|
160
|
+
"md": "20px → var(--inkblot-size-icon-md)",
|
|
161
|
+
"lg": "24px → var(--inkblot-size-icon-lg)",
|
|
162
|
+
"xl": "32px",
|
|
163
|
+
"2xl": "48px (empty states)"
|
|
164
|
+
},
|
|
165
|
+
"status": {
|
|
166
|
+
"success": { "icon": "check-circle | CheckCircle", "color": "var(--inkblot-semantic-color-status-success)" },
|
|
167
|
+
"error": { "icon": "x-circle | XCircle", "color": "var(--inkblot-semantic-color-status-error)" },
|
|
168
|
+
"warning": { "icon": "alert-triangle | AlertTriangle", "color": "var(--inkblot-semantic-color-status-warning)" },
|
|
169
|
+
"info": { "icon": "info | Info", "color": "var(--inkblot-semantic-color-status-info)" },
|
|
170
|
+
"loading": { "icon": "loader-2 | Loader2", "animation": "spin 1s linear infinite" },
|
|
171
|
+
"pending": { "icon": "clock | Clock" },
|
|
172
|
+
"verified": { "icon": "badge-check | BadgeCheck" }
|
|
173
|
+
},
|
|
174
|
+
"actions": {
|
|
175
|
+
"close": "x | X", "add": "plus | Plus", "delete": "trash-2 | Trash2",
|
|
176
|
+
"edit": "pencil | Pencil", "save": "save | Save", "copy": "copy | Copy",
|
|
177
|
+
"share": "share-2 | Share2", "download": "download | Download", "upload": "upload | Upload",
|
|
178
|
+
"search": "search | Search", "filter": "sliders-horizontal | SlidersHorizontal",
|
|
179
|
+
"refresh": "refresh-cw | RefreshCw", "more": "more-horizontal | MoreHorizontal",
|
|
180
|
+
"settings": "settings | Settings", "undo": "undo-2 | Undo2", "redo": "redo-2 | Redo2",
|
|
181
|
+
"lock": "lock | Lock", "unlock": "unlock | Unlock", "link": "link | Link",
|
|
182
|
+
"externalLink": "external-link | ExternalLink", "archive": "archive | Archive",
|
|
183
|
+
"bookmark": "bookmark | Bookmark", "like": "heart | Heart", "flag": "flag | Flag",
|
|
184
|
+
"print": "printer | Printer", "login": "log-in | LogIn", "logout": "log-out | LogOut"
|
|
185
|
+
},
|
|
186
|
+
"navigation": {
|
|
187
|
+
"menu": "menu | Menu", "back": "arrow-left | ArrowLeft", "forward": "arrow-right | ArrowRight",
|
|
188
|
+
"home": "home | Home", "chevronDown": "chevron-down | ChevronDown",
|
|
189
|
+
"chevronRight": "chevron-right | ChevronRight", "chevronLeft": "chevron-left | ChevronLeft",
|
|
190
|
+
"chevronUp": "chevron-up | ChevronUp",
|
|
191
|
+
"firstPage": "chevrons-left | ChevronsLeft", "lastPage": "chevrons-right | ChevronsRight"
|
|
192
|
+
},
|
|
193
|
+
"entities": {
|
|
194
|
+
"user": "user | User", "users": "users | Users",
|
|
195
|
+
"notification": "bell | Bell", "chat": "message-circle | MessageCircle",
|
|
196
|
+
"mail": "mail | Mail", "phone": "phone | Phone",
|
|
197
|
+
"file": "file | File", "fileText": "file-text | FileText",
|
|
198
|
+
"folder": "folder | Folder", "calendar": "calendar | Calendar",
|
|
199
|
+
"clock": "clock | Clock", "tag": "tag | Tag",
|
|
200
|
+
"location": "map-pin | MapPin", "globe": "globe | Globe",
|
|
201
|
+
"database": "database | Database", "code": "code | Code",
|
|
202
|
+
"key": "key | Key", "shield": "shield | Shield",
|
|
203
|
+
"creditCard": "credit-card | CreditCard", "cart": "shopping-cart | ShoppingCart",
|
|
204
|
+
"chart": "bar-chart-3 | BarChart3", "star": "star | Star",
|
|
205
|
+
"help": "help-circle | HelpCircle", "image": "image | Image"
|
|
206
|
+
},
|
|
207
|
+
"componentIcons": {
|
|
208
|
+
"button": { "submit": "check", "cancel": "x", "save": "save", "delete": "trash-2", "loading": "loader-2 (spin)" },
|
|
209
|
+
"input": { "email": "mail", "password": "lock (leading) + eye/eye-off (trailing toggle)", "search": "search (leading) + x (clear trailing)", "url": "globe", "phone": "phone", "date": "calendar", "time": "clock", "currency": "dollar-sign", "location": "map-pin", "validation": { "valid": "check-circle (green)", "invalid": "alert-circle (red)" } },
|
|
210
|
+
"select": { "trigger": "chevron-down (rotate 180° when open)", "selected": "check (trailing)", "clear": "x" },
|
|
211
|
+
"modal": { "close": "x (top-right)", "info": "info (lg)", "warning": "alert-triangle (lg)", "error": "x-circle (lg)", "success": "check-circle (lg)" },
|
|
212
|
+
"toast": { "info": "info", "success": "check-circle", "warning": "alert-triangle", "error": "x-circle", "dismiss": "x (sm)" },
|
|
213
|
+
"card": { "like": "heart", "share": "share-2", "bookmark": "bookmark", "more": "more-horizontal", "expand": "chevron-down" },
|
|
214
|
+
"listItem": { "disclosure": "chevron-right", "edit": "pencil (sm)", "delete": "trash-2 (sm)", "more": "more-vertical (sm)", "drag": "grip-vertical (sm)" },
|
|
215
|
+
"table": { "sortAsc": "arrow-up", "sortDesc": "arrow-down", "sortNeutral": "arrow-up-down", "rowExpand": "chevron-right", "rowActions": "more-horizontal" },
|
|
216
|
+
"chip": { "remove": "x (sm)" },
|
|
217
|
+
"emptyState": { "noData": "inbox (2xl)", "noResults": "search-x (2xl)", "error": "alert-circle (2xl)", "noPermission": "shield-off (2xl)" },
|
|
218
|
+
"nav": { "hamburger": "menu", "close": "x" },
|
|
219
|
+
"tooltip": { "trigger": "help-circle (sm)" }
|
|
220
|
+
},
|
|
221
|
+
"togglePairs": {
|
|
222
|
+
"visibility": ["eye", "eye-off"],
|
|
223
|
+
"notification": ["bell", "bell-off"],
|
|
224
|
+
"sound": ["volume-2", "volume-x"],
|
|
225
|
+
"lock": ["lock", "unlock"],
|
|
226
|
+
"darkMode": ["moon", "sun"],
|
|
227
|
+
"favorite": ["star (filled)", "star (stroke)"],
|
|
228
|
+
"like": ["heart (filled)", "heart (stroke)"],
|
|
229
|
+
"sidebar": ["panel-left", "panel-left-close"],
|
|
230
|
+
"viewMode": ["layout-grid", "list"]
|
|
231
|
+
},
|
|
232
|
+
"positioning": {
|
|
233
|
+
"leading": "Before text. Default for status, entity, nav icons.",
|
|
234
|
+
"trailing": "After text. For actions, disclosure, external links, sort arrows.",
|
|
235
|
+
"standalone": "No text. Must have aria-label. 44px min touch target.",
|
|
236
|
+
"inset": "Inside input field. Position absolute, vertically centered."
|
|
237
|
+
},
|
|
238
|
+
"spacing": "Icon-to-text gap: var(--inkblot-spacing-2). Compact: var(--inkblot-spacing-1). Relaxed: var(--inkblot-spacing-3).",
|
|
239
|
+
"a11y": "Decorative icons: aria-hidden='true'. Icon-only buttons: aria-label required. Never rely on color alone — use distinct shapes."
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
"cssSnippets": {
|
|
243
|
+
"focusRing": "outline: 2px solid var(--inkblot-semantic-color-border-focus); outline-offset: 2px;",
|
|
244
|
+
"focusVisible": ":focus-visible { outline: 2px solid var(--inkblot-semantic-color-border-focus); outline-offset: 2px; }",
|
|
245
|
+
"disabled": "opacity: var(--inkblot-opacity-disabled); pointer-events: none; cursor: not-allowed;",
|
|
246
|
+
"transitionDefault": "transition: all var(--inkblot-duration-fast) cubic-bezier(0.25, 0.1, 0.25, 1);",
|
|
247
|
+
"truncateSingle": "overflow: hidden; text-overflow: ellipsis; white-space: nowrap;",
|
|
248
|
+
"truncateLines": "display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;",
|
|
249
|
+
"screenReaderOnly": "position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;"
|
|
250
|
+
},
|
|
251
|
+
|
|
252
|
+
"componentSpacing": {
|
|
253
|
+
"description": "Standard spacing between composed elements.",
|
|
254
|
+
"betweenFormFields": "var(--inkblot-spacing-4)",
|
|
255
|
+
"betweenSections": "var(--inkblot-spacing-8)",
|
|
256
|
+
"betweenRelatedItems": "var(--inkblot-spacing-4)",
|
|
257
|
+
"cardInternalPadding": "var(--inkblot-spacing-6)",
|
|
258
|
+
"buttonGroupGap": "var(--inkblot-spacing-3)",
|
|
259
|
+
"listItemGap": "var(--inkblot-spacing-3)"
|
|
260
|
+
},
|
|
261
|
+
|
|
262
|
+
"validationRules": {
|
|
263
|
+
"required": "Show * after label. aria-required='true'. Validate on blur.",
|
|
264
|
+
"email": "type='email'. Pattern optional. Validate format.",
|
|
265
|
+
"password": "type='password'. Min 8 chars. Show strength indicator optional.",
|
|
266
|
+
"numeric": "type='number'. Set min, max, step."
|
|
267
|
+
},
|
|
268
|
+
|
|
269
|
+
"ariaPatterns": {
|
|
270
|
+
"button": "role='button'. aria-label if icon-only.",
|
|
271
|
+
"link": "role='link' or <a>. aria-current if active.",
|
|
272
|
+
"input": "aria-invalid, aria-required, aria-describedby→help|error id.",
|
|
273
|
+
"modal": "role='dialog' aria-modal='true' aria-labelledby='title-id'.",
|
|
274
|
+
"toast": "role='status' aria-live='polite' (info) | 'assertive' (error).",
|
|
275
|
+
"loading": "aria-busy='true' aria-live='polite'.",
|
|
276
|
+
"tabs": "role='tablist' on container, role='tab' on each tab, role='tabpanel' on content. aria-selected, aria-controls.",
|
|
277
|
+
"accordion": "button[aria-expanded] controls panel[role='region']. aria-controls → panel id.",
|
|
278
|
+
"combobox": "role='combobox' on input. aria-expanded, aria-activedescendant on option highlight.",
|
|
279
|
+
"navigation": "<nav aria-label='Main'> for primary nav. aria-current='page' on active link.",
|
|
280
|
+
"breadcrumb": "<nav aria-label='Breadcrumb'> with <ol>. aria-current='page' on last item.",
|
|
281
|
+
"alertDialog": "role='alertdialog' aria-modal='true'. Requires at least one action button. Cannot dismiss by clicking overlay."
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
"componentMotion": {
|
|
285
|
+
"description": "Exact transition values per interactive state. AI: apply these in CSS.",
|
|
286
|
+
"button": {
|
|
287
|
+
"hover": "background 150ms cubic-bezier(0.25,0.1,0.25,1), box-shadow 150ms cubic-bezier(0.25,0.1,0.25,1)",
|
|
288
|
+
"active": "transform 100ms cubic-bezier(0.25,0.1,0.25,1)",
|
|
289
|
+
"focus": "outline 0ms (instant)",
|
|
290
|
+
"loading": "opacity 150ms cubic-bezier(0.25,0.1,0.25,1); spinner appears with fadeIn 150ms",
|
|
291
|
+
"css": "transition: background var(--inkblot-duration-fast) var(--inkblot-easing-default), box-shadow var(--inkblot-duration-fast) var(--inkblot-easing-default), transform 100ms var(--inkblot-easing-default);"
|
|
292
|
+
},
|
|
293
|
+
"input": {
|
|
294
|
+
"hover": "border-color 150ms cubic-bezier(0.25,0.1,0.25,1)",
|
|
295
|
+
"focus": "border-color 150ms, box-shadow 150ms (focus ring fades in)",
|
|
296
|
+
"error": "border-color 150ms (instant feel, but smooth)",
|
|
297
|
+
"css": "transition: border-color var(--inkblot-duration-fast) var(--inkblot-easing-default), box-shadow var(--inkblot-duration-fast) var(--inkblot-easing-default);"
|
|
298
|
+
},
|
|
299
|
+
"card": {
|
|
300
|
+
"hover": "transform 150ms ease (translateY -2px), box-shadow 150ms ease",
|
|
301
|
+
"active": "transform 100ms (scale 0.99)",
|
|
302
|
+
"css": "transition: transform var(--inkblot-duration-fast) var(--inkblot-easing-default), box-shadow var(--inkblot-duration-fast) var(--inkblot-easing-default);"
|
|
303
|
+
},
|
|
304
|
+
"toggle": {
|
|
305
|
+
"stateChange": "thumb: transform 120ms expressive ease-out; track: background 120ms ease",
|
|
306
|
+
"css": ".toggle-thumb { transition: transform var(--inkblot-duration-fast) var(--inkblot-easing-expressive); } .toggle-track { transition: background var(--inkblot-duration-fast) var(--inkblot-easing-default); }"
|
|
307
|
+
},
|
|
308
|
+
"modal": {
|
|
309
|
+
"enter": "overlay: opacity 0→1 250ms ease-out; container: scale(0.95)→scale(1) + opacity 0→1 250ms ease-out",
|
|
310
|
+
"exit": "overlay: opacity 1→0 150ms ease-in; container: scale(1)→scale(0.97) + opacity 1→0 150ms ease-in",
|
|
311
|
+
"css": ".modal-overlay { transition: opacity var(--inkblot-duration-normal) var(--inkblot-easing-out); } .modal { transition: transform var(--inkblot-duration-normal) var(--inkblot-easing-out), opacity var(--inkblot-duration-normal) var(--inkblot-easing-out); }"
|
|
312
|
+
},
|
|
313
|
+
"dropdown": {
|
|
314
|
+
"open": "opacity 0→1 + translateY(-8px)→translateY(0) 200ms ease-out",
|
|
315
|
+
"close": "opacity 1→0 + translateY(0)→translateY(-4px) 150ms ease-in",
|
|
316
|
+
"css": ".dropdown[data-state='open'] { animation: dropdownIn var(--inkblot-duration-normal) var(--inkblot-easing-out); } @keyframes dropdownIn { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: translateY(0); } }"
|
|
317
|
+
},
|
|
318
|
+
"toast": {
|
|
319
|
+
"enter": "translateX(100%)→translateX(0) + opacity 0→1, 300ms ease-out",
|
|
320
|
+
"exit": "opacity 1→0, 150ms ease-in",
|
|
321
|
+
"css": ".toast-enter { animation: toastIn var(--inkblot-duration-normal) var(--inkblot-easing-out); } @keyframes toastIn { from { opacity: 0; transform: translateX(100%); } to { opacity: 1; transform: translateX(0); } }"
|
|
322
|
+
},
|
|
323
|
+
"tooltip": {
|
|
324
|
+
"show": "opacity 0→1 + scale(0.96)→scale(1), 150ms ease-out, 300ms delay",
|
|
325
|
+
"hide": "opacity 1→0, 100ms ease-in, 0ms delay"
|
|
326
|
+
},
|
|
327
|
+
"accordion": {
|
|
328
|
+
"expand": "height 0→auto 250ms ease-in-out (use max-height or grid-template-rows: 0fr→1fr)",
|
|
329
|
+
"collapse": "height auto→0 200ms ease-in-out"
|
|
330
|
+
},
|
|
331
|
+
"skeleton": {
|
|
332
|
+
"shimmer": "background-position 200% 0 → -200% 0, 1500ms ease-in-out infinite",
|
|
333
|
+
"css": "@keyframes shimmer { from { background-position: 200% 0; } to { background-position: -200% 0; } }"
|
|
334
|
+
},
|
|
335
|
+
"reducedMotion": {
|
|
336
|
+
"rule": "When prefers-reduced-motion: reduce, set all durations to 0.01ms. Remove transforms. Allow opacity ≤100ms only.",
|
|
337
|
+
"css": "@media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; } }"
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
|
|
341
|
+
"responsiveBehavior": {
|
|
342
|
+
"description": "How composite components adapt at each breakpoint. AI: apply these rules when generating responsive layouts.",
|
|
343
|
+
"form": {
|
|
344
|
+
"mobile": "Single column. Fields stack vertically. Full-width inputs. Actions stack: primary on top, secondary below.",
|
|
345
|
+
"tablet": "Single column. Fields remain stacked. Actions row: primary right-aligned.",
|
|
346
|
+
"desktop": "Optional 2-column layout for short forms. Long forms stay single-column. Actions row right-aligned."
|
|
347
|
+
},
|
|
348
|
+
"modal": {
|
|
349
|
+
"mobile": "Full-screen (width: 100vw, height: 100vh, border-radius: 0). No overlay visible. Slide up from bottom.",
|
|
350
|
+
"tablet": "Centered. Width: 560px max. Max-height: 85vh. Rounded corners.",
|
|
351
|
+
"desktop": "Centered. Width per size token (sm/md/lg). Max-height: 85vh."
|
|
352
|
+
},
|
|
353
|
+
"sidebar": {
|
|
354
|
+
"mobile": "Hidden. Accessed via hamburger menu → off-canvas drawer (z-index: modal, slide from left).",
|
|
355
|
+
"tablet": "Collapsed rail (64px wide, icons only). Expand on hover or click.",
|
|
356
|
+
"desktop": "Fixed 280px sidebar. Always visible."
|
|
357
|
+
},
|
|
358
|
+
"table": {
|
|
359
|
+
"mobile": "Convert to card layout: each row becomes a card with label:value pairs stacked vertically. Hide low-priority columns.",
|
|
360
|
+
"tablet": "Horizontal scroll with sticky first column. Show all columns.",
|
|
361
|
+
"desktop": "Full table. No scroll needed."
|
|
362
|
+
},
|
|
363
|
+
"cardGrid": {
|
|
364
|
+
"mobile": "1 column. Cards full width.",
|
|
365
|
+
"tablet": "2 columns. Min card width: 280px.",
|
|
366
|
+
"desktop": "3-4 columns via auto-fill minmax(280px, 1fr)."
|
|
367
|
+
},
|
|
368
|
+
"navigation": {
|
|
369
|
+
"mobile": "Bottom tab bar (max 5 items) or hamburger → drawer.",
|
|
370
|
+
"tablet": "Top horizontal nav. Overflow → 'More' dropdown.",
|
|
371
|
+
"desktop": "Top horizontal nav or sidebar. All items visible."
|
|
372
|
+
},
|
|
373
|
+
"hero": {
|
|
374
|
+
"mobile": "Stack: image above content. Full-bleed image. Reduced heading size.",
|
|
375
|
+
"tablet": "Split: image left, content right. Or stacked with larger heading.",
|
|
376
|
+
"desktop": "Split 50/50 or centered text over background image."
|
|
377
|
+
},
|
|
378
|
+
"toast": {
|
|
379
|
+
"mobile": "Full-width, bottom of screen, 8px margin.",
|
|
380
|
+
"tablet": "Fixed width (380px), bottom-right.",
|
|
381
|
+
"desktop": "Fixed width (420px), bottom-right, 24px margin."
|
|
382
|
+
}
|
|
383
|
+
},
|
|
384
|
+
|
|
385
|
+
"stateCombinations": {
|
|
386
|
+
"description": "Multi-state resolution rules. When multiple states co-exist, apply in this priority order (highest wins).",
|
|
387
|
+
"priorityOrder": ["disabled", "loading", "error", "active", "focus", "hover", "default"],
|
|
388
|
+
"rules": {
|
|
389
|
+
"disabled+hover": "Disabled wins. No hover effect. Cursor: not-allowed.",
|
|
390
|
+
"disabled+focus": "Disabled wins visually, BUT keep focus ring for keyboard users (accessibility). aria-disabled='true', NOT disabled attribute.",
|
|
391
|
+
"disabled+error": "Disabled wins. Error styling suppressed. Error message still visible but dimmed.",
|
|
392
|
+
"loading+hover": "Loading wins. No hover cursor change. Cursor: wait.",
|
|
393
|
+
"loading+focus": "Show focus ring on top of loading state. User can Tab away.",
|
|
394
|
+
"error+focus": "Both visible: error border + focus ring. Use box-shadow for focus to not conflict with error border.",
|
|
395
|
+
"error+hover": "Error border persists. Subtle hover background allowed.",
|
|
396
|
+
"error+focus+hover": "Error border + focus ring + hover background. All three visible."
|
|
397
|
+
},
|
|
398
|
+
"cssStrategy": {
|
|
399
|
+
"description": "Use CSS specificity to layer states correctly.",
|
|
400
|
+
"example": ".input { border-color: var(--inkblot-semantic-color-border-default); } .input:hover { border-color: var(--inkblot-semantic-color-border-strong); } .input:focus-visible { border-color: var(--inkblot-semantic-color-border-focus); box-shadow: 0 0 0 2px rgba(0,122,255,0.15); } .input[aria-invalid='true'] { border-color: var(--inkblot-semantic-color-status-error); } .input[aria-invalid='true']:focus-visible { border-color: var(--inkblot-semantic-color-status-error); box-shadow: 0 0 0 2px rgba(230,53,43,0.15); } .input[aria-disabled='true'] { opacity: var(--inkblot-opacity-disabled); pointer-events: none; }"
|
|
401
|
+
}
|
|
402
|
+
},
|
|
403
|
+
|
|
404
|
+
"interactionFlows": {
|
|
405
|
+
"description": "Common event chains. AI: use these to auto-compose interaction logic.",
|
|
406
|
+
"formSubmission": {
|
|
407
|
+
"trigger": "click submit button OR press Enter in last field",
|
|
408
|
+
"steps": [
|
|
409
|
+
"1. Validate all required fields. Mark invalid fields with aria-invalid='true'.",
|
|
410
|
+
"2. If errors: scroll to first error field. Set focus on it. Announce via aria-live.",
|
|
411
|
+
"3. If valid: set submit button to loading state (aria-busy='true').",
|
|
412
|
+
"4. Send data. On success: show success toast, redirect or reset form.",
|
|
413
|
+
"5. On failure: show error toast (assertive), restore submit button, preserve inputs."
|
|
414
|
+
]
|
|
415
|
+
},
|
|
416
|
+
"modalOpen": {
|
|
417
|
+
"trigger": "click trigger button",
|
|
418
|
+
"steps": [
|
|
419
|
+
"1. Record trigger element for focus return.",
|
|
420
|
+
"2. Render modal + overlay. Animate in (scaleIn 250ms).",
|
|
421
|
+
"3. Lock body scroll (overflow: hidden on <body>).",
|
|
422
|
+
"4. Move focus to first focusable element inside modal.",
|
|
423
|
+
"5. Activate focus trap."
|
|
424
|
+
]
|
|
425
|
+
},
|
|
426
|
+
"modalClose": {
|
|
427
|
+
"trigger": "click close button OR click overlay OR press Escape",
|
|
428
|
+
"steps": [
|
|
429
|
+
"1. Animate out (150ms).",
|
|
430
|
+
"2. Remove focus trap.",
|
|
431
|
+
"3. Unlock body scroll.",
|
|
432
|
+
"4. Return focus to original trigger element.",
|
|
433
|
+
"5. Remove modal from DOM (or hide)."
|
|
434
|
+
]
|
|
435
|
+
},
|
|
436
|
+
"tooltipShow": {
|
|
437
|
+
"trigger": "mouseenter OR focus (keyboard)",
|
|
438
|
+
"steps": [
|
|
439
|
+
"1. Wait 300ms delay (cancel if mouse leaves before delay).",
|
|
440
|
+
"2. Calculate position (prefer top, auto-flip if near edge).",
|
|
441
|
+
"3. Render tooltip at 8px offset from trigger.",
|
|
442
|
+
"4. Animate in (fadeIn + scaleIn 150ms).",
|
|
443
|
+
"5. Set aria-describedby on trigger → tooltip id."
|
|
444
|
+
]
|
|
445
|
+
},
|
|
446
|
+
"tooltipHide": {
|
|
447
|
+
"trigger": "mouseleave OR blur OR Escape",
|
|
448
|
+
"steps": [
|
|
449
|
+
"1. Animate out (100ms).",
|
|
450
|
+
"2. Remove aria-describedby from trigger.",
|
|
451
|
+
"3. Remove tooltip from DOM."
|
|
452
|
+
]
|
|
453
|
+
},
|
|
454
|
+
"dropdownToggle": {
|
|
455
|
+
"trigger": "click trigger OR Enter/Space/ArrowDown on trigger",
|
|
456
|
+
"open": [
|
|
457
|
+
"1. Set aria-expanded='true' on trigger.",
|
|
458
|
+
"2. Render dropdown (position: absolute, z-index: dropdown).",
|
|
459
|
+
"3. Animate in (slideDown 200ms).",
|
|
460
|
+
"4. Move focus to first option (or highlighted option)."
|
|
461
|
+
],
|
|
462
|
+
"navigate": "ArrowDown/Up moves highlight. Type-ahead filters. Home/End jump to first/last.",
|
|
463
|
+
"select": "Enter or click selects. Set trigger value. Close dropdown. Return focus to trigger.",
|
|
464
|
+
"close": "Escape or click outside. Set aria-expanded='false'. Return focus to trigger."
|
|
465
|
+
},
|
|
466
|
+
"infiniteScroll": {
|
|
467
|
+
"trigger": "scroll position within 200px of container bottom",
|
|
468
|
+
"steps": [
|
|
469
|
+
"1. Show loading spinner at bottom of list.",
|
|
470
|
+
"2. Fetch next page of data.",
|
|
471
|
+
"3. Append items to list (no layout shift).",
|
|
472
|
+
"4. Remove spinner.",
|
|
473
|
+
"5. If no more data: show 'End of list' message. Remove scroll listener."
|
|
474
|
+
]
|
|
475
|
+
},
|
|
476
|
+
"searchWithDebounce": {
|
|
477
|
+
"trigger": "keyup in search input",
|
|
478
|
+
"steps": [
|
|
479
|
+
"1. Wait 300ms debounce (restart timer on each keystroke).",
|
|
480
|
+
"2. If input empty: clear results, show default state.",
|
|
481
|
+
"3. If input non-empty: show loading indicator in results area.",
|
|
482
|
+
"4. Fetch results.",
|
|
483
|
+
"5. Render results. Announce count via aria-live: '{n} results found'."
|
|
484
|
+
]
|
|
485
|
+
}
|
|
486
|
+
},
|
|
487
|
+
|
|
488
|
+
"contentRules": {
|
|
489
|
+
"description": "Text, image, and content handling rules for AI rendering.",
|
|
490
|
+
"truncation": {
|
|
491
|
+
"singleLine": {
|
|
492
|
+
"css": "overflow: hidden; text-overflow: ellipsis; white-space: nowrap;",
|
|
493
|
+
"usage": "Table cells, nav items, breadcrumbs, single-line labels"
|
|
494
|
+
},
|
|
495
|
+
"multiLine": {
|
|
496
|
+
"css": "display: -webkit-box; -webkit-line-clamp: {n}; -webkit-box-orient: vertical; overflow: hidden;",
|
|
497
|
+
"defaults": { "cardTitle": 2, "cardDescription": 3, "listItemSubtitle": 1, "toastMessage": 2 },
|
|
498
|
+
"rule": "Always expose full text via title attribute or expand action."
|
|
499
|
+
},
|
|
500
|
+
"never": ["headings (h1-h4)", "error messages", "form labels", "button labels"]
|
|
501
|
+
},
|
|
502
|
+
"wordWrapping": {
|
|
503
|
+
"longWords": "overflow-wrap: break-word; on all text containers.",
|
|
504
|
+
"urls": "word-break: break-all; on URL-displaying elements.",
|
|
505
|
+
"code": "white-space: pre-wrap; overflow-x: auto; on code blocks."
|
|
506
|
+
},
|
|
507
|
+
"iconAlignment": {
|
|
508
|
+
"inline": "vertical-align: -0.125em (aligns center of icon with text baseline).",
|
|
509
|
+
"flexRow": "align-items: center; gap: var(--inkblot-spacing-2);",
|
|
510
|
+
"sizeMatchText": "Match icon size to line-height of adjacent text. Body text → 20px icon. Label → 16px icon."
|
|
511
|
+
},
|
|
512
|
+
"imageAspectRatios": {
|
|
513
|
+
"hero": "21:9",
|
|
514
|
+
"landscape": "16:9",
|
|
515
|
+
"standard": "4:3",
|
|
516
|
+
"square": "1:1",
|
|
517
|
+
"portrait": "3:4",
|
|
518
|
+
"avatar": "1:1 (always circular via border-radius: full)",
|
|
519
|
+
"thumbnail": "1:1 (square with object-fit: cover)"
|
|
520
|
+
},
|
|
521
|
+
"imageFallbacks": {
|
|
522
|
+
"loading": "Show skeleton placeholder matching aspect ratio.",
|
|
523
|
+
"error": "Show neutral gray background (#f5f5f5) with subtle image-off icon centered.",
|
|
524
|
+
"lazyRule": "All images below fold: loading='lazy'. Above fold: loading='eager'."
|
|
525
|
+
},
|
|
526
|
+
"emptyValues": {
|
|
527
|
+
"strings": "Show — (em-dash)",
|
|
528
|
+
"numbers": "Show — (em-dash)",
|
|
529
|
+
"dates": "Show — (em-dash)",
|
|
530
|
+
"images": "Show skeleton or placeholder",
|
|
531
|
+
"lists": "Show empty state with title + description + action button",
|
|
532
|
+
"rule": "Never show 'undefined', 'null', 'NaN', 'N/A', or empty string in rendered UI."
|
|
533
|
+
}
|
|
534
|
+
},
|
|
535
|
+
|
|
536
|
+
"performanceHints": {
|
|
537
|
+
"description": "Optimization flags for AI-generated code. Apply these to improve runtime performance.",
|
|
538
|
+
"lazyLoading": {
|
|
539
|
+
"images": "loading='lazy' on all <img> below the fold.",
|
|
540
|
+
"components": "React.lazy() + Suspense for modals, drawers, heavy charts, settings panels. Show skeleton as fallback.",
|
|
541
|
+
"routes": "Code-split every route. Dynamic import().",
|
|
542
|
+
"rule": "Anything not visible on first paint should be lazy-loaded."
|
|
543
|
+
},
|
|
544
|
+
"virtualizedLists": {
|
|
545
|
+
"threshold": "Virtualize any list with > 50 items.",
|
|
546
|
+
"libraries": "react-window or @tanstack/virtual",
|
|
547
|
+
"rule": "Render only visible items + 5 item overscan buffer."
|
|
548
|
+
},
|
|
549
|
+
"tables": {
|
|
550
|
+
"largeDatasets": "Virtualize rows if > 100 rows. Paginate if > 500 total items.",
|
|
551
|
+
"sorting": "Client-side sort for ≤ 1000 rows. Server-side sort for > 1000.",
|
|
552
|
+
"rule": "Never render > 100 DOM rows simultaneously."
|
|
553
|
+
},
|
|
554
|
+
"animations": {
|
|
555
|
+
"cssPreferred": "Use CSS transitions and @keyframes instead of JS animation when possible.",
|
|
556
|
+
"willChange": "Add will-change: transform on elements that animate transform/opacity. Remove after animation completes.",
|
|
557
|
+
"compositorOnly": "Prefer transform and opacity. Avoid animating width, height, top, left, margin, padding (triggers layout)."
|
|
558
|
+
},
|
|
559
|
+
"rendering": {
|
|
560
|
+
"ssr": "Default for content pages, marketing, SEO-critical pages.",
|
|
561
|
+
"csr": "Dashboards, authenticated views, real-time data.",
|
|
562
|
+
"memoization": "React.memo() on list items, table rows, and any component receiving stable props."
|
|
563
|
+
},
|
|
564
|
+
"assets": {
|
|
565
|
+
"images": "Use WebP/AVIF format. Serve responsive sizes via srcset. Max width: 1920px.",
|
|
566
|
+
"fonts": "System font stack (no custom font download). If custom: preload, font-display: swap.",
|
|
567
|
+
"icons": "Inline SVG for interactive icons. SVG sprite for decorative icons."
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|