@neuravision/construct 2.0.3 → 2.1.1

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 (115) hide show
  1. package/CHANGELOG.md +272 -0
  2. package/README.md +148 -152
  3. package/behaviors/combobox.js +447 -0
  4. package/behaviors/drawer.js +18 -0
  5. package/behaviors/dropdown.js +296 -0
  6. package/behaviors/index.d.ts +333 -0
  7. package/behaviors/index.js +9 -0
  8. package/behaviors/internal/dialog-controller.js +351 -0
  9. package/behaviors/internal/dom.js +377 -0
  10. package/behaviors/internal/overlay.js +241 -0
  11. package/behaviors/internal/popup-layer.js +188 -0
  12. package/behaviors/modal.js +17 -0
  13. package/behaviors/popover.js +269 -0
  14. package/behaviors/select-menu.js +326 -0
  15. package/behaviors/tabs.js +230 -0
  16. package/behaviors/toggle-group.js +234 -0
  17. package/behaviors/tooltip.js +233 -0
  18. package/components/README.md +279 -83
  19. package/components/_shared.css +11 -10
  20. package/components/_shell-shared.css +20 -4
  21. package/components/app-shell-v2.css +21 -14
  22. package/components/app-shell.css +23 -14
  23. package/components/badge.css +15 -0
  24. package/components/button.css +103 -15
  25. package/components/card.css +53 -8
  26. package/components/checkbox.css +3 -2
  27. package/components/chip.css +1 -1
  28. package/components/datepicker.css +3 -1
  29. package/components/datum.css +204 -0
  30. package/components/index.css +5 -0
  31. package/components/meter.css +182 -0
  32. package/components/navbar.css +88 -3
  33. package/components/slider.css +2 -1
  34. package/components/status.css +107 -0
  35. package/components/switch.css +4 -3
  36. package/components/tooltip.css +4 -0
  37. package/docs/architecture.md +79 -0
  38. package/docs/behaviors.md +101 -0
  39. package/docs/fonts.md +70 -0
  40. package/docs/governance.md +85 -0
  41. package/docs/guidelines.md +42 -23
  42. package/examples/react/README.md +20 -0
  43. package/examples/react/SettingsModal.jsx +66 -0
  44. package/examples/react/construct.css +5 -0
  45. package/fonts.css +10 -0
  46. package/foundations.css +8 -1
  47. package/package.json +72 -21
  48. package/schemas/components.schema.json +107 -0
  49. package/schemas/examples/theme.example.json +27 -0
  50. package/schemas/primitives.schema.json +135 -0
  51. package/schemas/semantic.schema.json +223 -0
  52. package/schemas/theme.schema.json +63 -0
  53. package/scripts/build-tokens.mjs +1417 -0
  54. package/scripts/construct-theme.mjs +43 -0
  55. package/scripts/validate-tokens.mjs +25 -0
  56. package/standalone/accordion.css +3 -0
  57. package/standalone/alert.css +3 -0
  58. package/standalone/app-shell-v2.css +4 -0
  59. package/standalone/app-shell.css +4 -0
  60. package/standalone/avatar.css +3 -0
  61. package/standalone/badge.css +3 -0
  62. package/standalone/banner.css +3 -0
  63. package/standalone/breadcrumbs.css +3 -0
  64. package/standalone/button.css +3 -0
  65. package/standalone/card.css +3 -0
  66. package/standalone/chart.css +3 -0
  67. package/standalone/checkbox.css +3 -0
  68. package/standalone/chip.css +3 -0
  69. package/standalone/combobox.css +3 -0
  70. package/standalone/core.css +4 -0
  71. package/standalone/data-table.css +3 -0
  72. package/standalone/datepicker.css +3 -0
  73. package/standalone/datum.css +3 -0
  74. package/standalone/divider.css +3 -0
  75. package/standalone/drawer.css +3 -0
  76. package/standalone/dropdown.css +3 -0
  77. package/standalone/empty-state.css +3 -0
  78. package/standalone/field.css +3 -0
  79. package/standalone/file-upload.css +3 -0
  80. package/standalone/icon.css +3 -0
  81. package/standalone/input.css +3 -0
  82. package/standalone/list.css +3 -0
  83. package/standalone/meter.css +3 -0
  84. package/standalone/modal.css +3 -0
  85. package/standalone/navbar.css +3 -0
  86. package/standalone/pagination.css +3 -0
  87. package/standalone/popover.css +3 -0
  88. package/standalone/progress-bar.css +3 -0
  89. package/standalone/select-menu.css +3 -0
  90. package/standalone/select.css +3 -0
  91. package/standalone/sidebar.css +3 -0
  92. package/standalone/skeleton.css +3 -0
  93. package/standalone/skip-link.css +3 -0
  94. package/standalone/slider.css +3 -0
  95. package/standalone/spinner.css +3 -0
  96. package/standalone/status.css +3 -0
  97. package/standalone/switch.css +3 -0
  98. package/standalone/table.css +3 -0
  99. package/standalone/tabs.css +3 -0
  100. package/standalone/textarea.css +3 -0
  101. package/standalone/toast.css +3 -0
  102. package/standalone/toggle-group.css +3 -0
  103. package/standalone/toolbar.css +3 -0
  104. package/standalone/tooltip.css +3 -0
  105. package/standalone/tree.css +3 -0
  106. package/tokens/README.md +130 -126
  107. package/tokens/components.json +54 -0
  108. package/tokens/primitives.json +268 -0
  109. package/tokens/semantic.dark.json +93 -0
  110. package/tokens/semantic.high-contrast.json +93 -0
  111. package/tokens/semantic.light.json +178 -0
  112. package/tokens/tokens.css +532 -28
  113. package/tokens/tokens.js +335 -25
  114. package/tokens/tokens.json +764 -21
  115. package/tokens/tokens.ts +335 -25
@@ -1,13 +1,13 @@
1
1
  :root {
2
- --ct-control-bg: var(--color-bg-elevated);
3
- --ct-control-bg-hover: var(--color-bg-surface);
4
- --ct-control-bg-disabled: var(--color-bg-muted);
5
- --ct-control-border: var(--color-border-default);
6
- --ct-control-border-hover: var(--color-border-strong);
7
- --ct-control-border-focus: var(--color-focus-ring);
8
- --ct-control-text: var(--color-text-primary);
9
- --ct-control-placeholder: var(--color-text-muted);
10
- --ct-control-radius: var(--radius-control);
2
+ --ct-control-bg: var(--component-control-background);
3
+ --ct-control-bg-hover: var(--component-control-background-hover);
4
+ --ct-control-bg-disabled: var(--component-control-background-disabled);
5
+ --ct-control-border: var(--component-control-border);
6
+ --ct-control-border-hover: var(--component-control-border-hover);
7
+ --ct-control-border-focus: var(--component-control-border-focus);
8
+ --ct-control-text: var(--component-control-text);
9
+ --ct-control-placeholder: var(--component-control-placeholder);
10
+ --ct-control-radius: var(--component-control-radius);
11
11
  --ct-control-transition: border-color var(--duration-fast) var(--easing-standard),
12
12
  box-shadow var(--duration-fast) var(--easing-standard),
13
13
  background var(--duration-fast) var(--easing-standard),
@@ -106,11 +106,12 @@
106
106
  outline-color: var(--color-state-danger);
107
107
  }
108
108
 
109
- /* ── Touch-target minimums (WCAG 2.5.8: 44×44px) ────────────────── */
109
+ /* ── Construct 44×44 target policy (WCAG 2.2 SC 2.5.5 is AAA) ─── */
110
110
 
111
111
  @media (pointer: coarse) {
112
112
  :root {
113
113
  --control-height-sm: 44px;
114
+ --control-height-md: 44px;
114
115
  --control-height-calendar-day: 44px;
115
116
  }
116
117
  }
@@ -3,6 +3,20 @@
3
3
  /* Crosses component boundary: shells orchestrate child layout. */
4
4
  /* ─────────────────────────────────────────────────────────────── */
5
5
 
6
+ /* Both layouts expose the canonical `--ct-shell-*` sizing and motion
7
+ controls. Their historical variable names are compatibility aliases. */
8
+
9
+ :is(.ct-app-shell, .ct-app-shell-v2) {
10
+ min-width: 0;
11
+ }
12
+
13
+ :is(.ct-app-shell__main, .ct-app-shell-v2__main),
14
+ :is(.ct-app-shell__sidebar, .ct-app-shell-v2__sidebar),
15
+ :is(.ct-app-shell__panel, .ct-app-shell-v2__panel) {
16
+ min-width: 0;
17
+ min-height: 0;
18
+ }
19
+
6
20
 
7
21
  /* ── Collapsed: Hide Labels & Badges ── */
8
22
 
@@ -16,8 +30,8 @@
16
30
  white-space: nowrap;
17
31
  /* Collapsing: labels fade out first, then width snaps */
18
32
  transition:
19
- opacity 120ms ease-in-out,
20
- width 0ms linear 120ms;
33
+ opacity var(--ct-shell-motion-duration) var(--ct-shell-motion-easing),
34
+ width 0ms linear var(--ct-shell-motion-duration);
21
35
  }
22
36
 
23
37
 
@@ -30,7 +44,9 @@
30
44
  opacity: 1;
31
45
  width: auto;
32
46
  /* Expanding: labels fade in after sidebar width finishes growing */
33
- transition: opacity 120ms ease-in-out 250ms;
47
+ transition:
48
+ opacity var(--ct-shell-motion-duration) var(--ct-shell-motion-easing)
49
+ var(--ct-shell-motion-duration);
34
50
  }
35
51
 
36
52
 
@@ -44,7 +60,7 @@
44
60
 
45
61
  /* ── Tablet: Auto-Collapse to Rail ── */
46
62
 
47
- @media (max-width: 1199px) {
63
+ @media (max-width: 1199px) { /* < lg breakpoint (1200px) */
48
64
  :is(.ct-app-shell, .ct-app-shell-v2):not([data-sidebar-state]) .ct-nav-item__label,
49
65
  :is(.ct-app-shell, .ct-app-shell-v2):not([data-sidebar-state]) .ct-nav-item__badge,
50
66
  :is(.ct-app-shell, .ct-app-shell-v2):not([data-sidebar-state]) .ct-sidebar__header span,
@@ -17,24 +17,31 @@
17
17
  /* │ ╰────────╯ ╰───────────────────┴──────────────╯ │ */
18
18
  /* ╰─────────────────────────────────────────────────────╯ */
19
19
  /* */
20
- /* Shell breakpoints: */
21
- /* Mobile: <768px */
22
- /* Tablet: 768–1199px */
20
+ /* Shell breakpoints (from the public primitive scale): */
21
+ /* Compact: <900px (< md) */
22
+ /* Tablet: 900–1199px */
23
23
  /* Desktop: >=1200px */
24
24
  /* ─────────────────────────────────────────────────────────────── */
25
25
 
26
26
  .ct-app-shell-v2 {
27
- /* ── Public Tokens ── */
28
- --ct-v2-sidebar-width: 260px;
29
- --ct-v2-sidebar-rail-width: 64px;
30
- --ct-v2-panel-width: 340px;
27
+ /* ── Shared Public Tokens ── */
28
+ --ct-shell-sidebar-width: 260px;
29
+ --ct-shell-sidebar-rail-width: 64px;
30
+ --ct-shell-panel-width: 340px;
31
+ --ct-shell-motion-duration: 250ms;
32
+ --ct-shell-motion-easing: cubic-bezier(0.32, 0.72, 0, 1);
33
+
34
+ /* V2 aliases remain public for backward compatibility. */
35
+ --ct-v2-sidebar-width: var(--ct-shell-sidebar-width);
36
+ --ct-v2-sidebar-rail-width: var(--ct-shell-sidebar-rail-width);
37
+ --ct-v2-panel-width: var(--ct-shell-panel-width);
31
38
  --ct-v2-gap: var(--space-4);
32
39
  --ct-v2-radius: var(--radius-lg);
33
40
  --ct-v2-canvas-bg: var(--color-bg-muted);
34
41
  --ct-v2-surface-bg: var(--color-bg-canvas);
35
42
  --ct-v2-surface-shadow: var(--shadow-xs);
36
- --ct-v2-transition-duration: 250ms;
37
- --ct-v2-transition-easing: cubic-bezier(0.32, 0.72, 0, 1);
43
+ --ct-v2-transition-duration: var(--ct-shell-motion-duration);
44
+ --ct-v2-transition-easing: var(--ct-shell-motion-easing);
38
45
 
39
46
  /* ── Internal ── */
40
47
  --_v2-sidebar-w: var(--ct-v2-sidebar-width);
@@ -362,13 +369,13 @@
362
369
 
363
370
  /* Delay grid shrink on collapse so labels fade out first */
364
371
  .ct-app-shell-v2[data-sidebar-state='collapsed'] {
365
- transition-delay: 120ms;
372
+ transition-delay: var(--ct-shell-motion-duration);
366
373
  }
367
374
 
368
375
 
369
- /* ── Responsive: Tablet (768–1199px) ── */
376
+ /* ── Responsive: Tablet (900–1199px) ── */
370
377
 
371
- @media (max-width: 1199px) {
378
+ @media (max-width: 1199px) { /* < lg breakpoint (1200px) */
372
379
  /* Auto-collapse sidebar to rail when no explicit state */
373
380
  .ct-app-shell-v2:not([data-sidebar-state]) {
374
381
  --_v2-sidebar-w: var(--ct-v2-sidebar-rail-width);
@@ -390,9 +397,9 @@
390
397
  }
391
398
 
392
399
 
393
- /* ── Responsive: Mobile (<768px) ── */
400
+ /* ── Responsive: Compact (< md breakpoint, 900px) ── */
394
401
 
395
- @media (max-width: 767px) {
402
+ @media (max-width: 899px) { /* < md breakpoint (900px) */
396
403
  .ct-app-shell-v2 {
397
404
  --ct-v2-gap: var(--space-3);
398
405
  }
@@ -1,23 +1,32 @@
1
- /* ── App Shell ─────────────────────────────────────────────────── */
1
+ /* ── App Shell (compatibility layout) ─────────────────────────── */
2
2
  /* CSS Grid layout shell that orchestrates Navbar, Sidebar, */
3
3
  /* Main content, optional Panel, and optional Footer. */
4
+ /* New applications should prefer App Shell V2. `.ct-app-shell` */
5
+ /* remains supported as the non-floating compatibility contract. */
4
6
  /* */
5
- /* Shell breakpoints (component-specific, differ from --bp-*): */
6
- /* Mobile: <768px */
7
- /* Tablet: 768–1199px */
7
+ /* Shell breakpoints (from the public primitive scale): */
8
+ /* Compact: <900px (< md) */
9
+ /* Tablet: 900–1199px */
8
10
  /* Desktop: 1200–1439px */
9
11
  /* Wide: >=1440px */
10
12
  /* ────────────────────────────────────────────────────────────── */
11
13
 
12
14
  .ct-app-shell {
13
- /* ── Public Tokens ── */
14
- --ct-sidebar-width: 260px;
15
- --ct-sidebar-rail-width: 56px;
16
- --ct-panel-width: 320px;
15
+ /* ── Shared Public Tokens ── */
16
+ --ct-shell-sidebar-width: 260px;
17
+ --ct-shell-sidebar-rail-width: 56px;
18
+ --ct-shell-panel-width: 320px;
19
+ --ct-shell-motion-duration: 200ms;
20
+ --ct-shell-motion-easing: ease-out;
21
+
22
+ /* Legacy aliases remain public for backward compatibility. */
23
+ --ct-sidebar-width: var(--ct-shell-sidebar-width);
24
+ --ct-sidebar-rail-width: var(--ct-shell-sidebar-rail-width);
25
+ --ct-panel-width: var(--ct-shell-panel-width);
17
26
  --ct-header-height: auto;
18
27
  --ct-footer-height: auto;
19
- --ct-shell-transition-duration: 200ms;
20
- --ct-shell-transition-easing: ease-out;
28
+ --ct-shell-transition-duration: var(--ct-shell-motion-duration);
29
+ --ct-shell-transition-easing: var(--ct-shell-motion-easing);
21
30
 
22
31
  /* ── Internal Variables ── */
23
32
  --_sidebar-width: var(--ct-sidebar-width);
@@ -249,7 +258,7 @@
249
258
 
250
259
  /* Collapsing: delay grid shrink so labels fade out first */
251
260
  .ct-app-shell[data-sidebar-state='collapsed'] {
252
- transition-delay: 120ms;
261
+ transition-delay: var(--ct-shell-motion-duration);
253
262
  }
254
263
 
255
264
 
@@ -260,7 +269,7 @@
260
269
  }
261
270
 
262
271
 
263
- /* ── Responsive: Tablet (768–1199px) ── */
272
+ /* ── Responsive: Tablet (900–1199px) ── */
264
273
 
265
274
  @media (max-width: 1199px) { /* < lg breakpoint (1200px) */
266
275
  /* Auto-collapse sidebar to rail when no explicit state is set */
@@ -286,9 +295,9 @@
286
295
  }
287
296
 
288
297
 
289
- /* ── Responsive: Mobile (<768px) ── */
298
+ /* ── Responsive: Compact (< md breakpoint, 900px) ── */
290
299
 
291
- @media (max-width: 767px) { /* < tablet breakpoint (768px) */
300
+ @media (max-width: 899px) { /* < md breakpoint (900px) */
292
301
  /* Auto-hide sidebar when no explicit state is set */
293
302
  .ct-app-shell:not([data-sidebar-state]) {
294
303
  --_sidebar-width: 0px;
@@ -38,6 +38,21 @@
38
38
  background: var(--ct-badge-accent);
39
39
  }
40
40
 
41
+ /* Explicit neutral alias for generated/conditional variant class lists. */
42
+ .ct-badge--neutral {
43
+ --ct-badge-bg: var(--color-bg-muted);
44
+ --ct-badge-border: var(--color-border-subtle);
45
+ --ct-badge-color: var(--color-text-secondary);
46
+ --ct-badge-accent: var(--ct-badge-color);
47
+ }
48
+
49
+ .ct-badge--sm {
50
+ gap: var(--space-1);
51
+ padding: 0 var(--space-2);
52
+ font-size: var(--font-size-2xs);
53
+ line-height: var(--line-height-xs);
54
+ }
55
+
41
56
  .ct-badge--info {
42
57
  --ct-badge-bg: var(--color-state-info-surface);
43
58
  --ct-badge-border: var(--color-state-info-border);
@@ -1,14 +1,19 @@
1
1
  .ct-button {
2
- --ct-button-bg: var(--color-brand-primary);
3
- --ct-button-bg-hover: var(--color-brand-primary-hover);
4
- --ct-button-bg-active: var(--color-brand-primary-active);
5
- --ct-button-border: transparent;
6
- --ct-button-color: var(--color-text-on-primary);
7
- --ct-button-height: var(--control-height-md);
8
- --ct-button-padding-x: var(--space-6);
9
- --ct-button-font-size: var(--font-size-sm);
10
- --ct-button-line-height: var(--line-height-sm);
11
-
2
+ --ct-button-bg: var(--component-button-background);
3
+ --ct-button-bg-hover: var(--component-button-background-hover);
4
+ --ct-button-bg-active: var(--component-button-background-active);
5
+ --ct-button-border: var(--component-button-border);
6
+ --ct-button-color: var(--component-button-color);
7
+ --ct-button-height: var(--component-button-height);
8
+ --ct-button-padding-x: var(--component-button-padding-x);
9
+ --ct-button-font-size: var(--component-button-font-size);
10
+ --ct-button-line-height: var(--component-button-line-height);
11
+ --ct-button-font-weight: var(--component-button-font-weight);
12
+ --ct-button-radius: var(--component-button-radius);
13
+ --ct-button-gap: var(--component-button-gap);
14
+ --ct-button-loading-spinner-size: var(--component-button-loading-spinner-size);
15
+
16
+ position: relative;
12
17
  display: inline-flex;
13
18
  align-items: center;
14
19
  justify-content: center;
@@ -32,12 +37,12 @@
32
37
  }
33
38
 
34
39
  @media (hover: hover) {
35
- .ct-button:hover {
40
+ .ct-button:not(:disabled):not([aria-disabled='true']):not([aria-busy='true']):not([data-loading='true']):hover {
36
41
  background: var(--ct-button-bg-hover);
37
42
  }
38
43
  }
39
44
 
40
- .ct-button:active {
45
+ .ct-button:not(:disabled):not([aria-disabled='true']):not([aria-busy='true']):not([data-loading='true']):active {
41
46
  background: var(--ct-button-bg-active);
42
47
  transform: translateY(1px);
43
48
  }
@@ -52,6 +57,56 @@
52
57
  opacity: var(--opacity-disabled);
53
58
  transform: none;
54
59
  cursor: not-allowed;
60
+ pointer-events: none;
61
+ }
62
+
63
+ /* Loading contract:
64
+ - set `aria-busy="true"` and `data-loading="true"`;
65
+ - native buttons must also use `disabled` to block keyboard activation;
66
+ - wrap the stable accessible label in `.ct-button__content`;
67
+ - keep `.ct-button__loader` decorative (`aria-hidden="true"`). */
68
+ .ct-button__content {
69
+ display: inline-flex;
70
+ align-items: center;
71
+ justify-content: center;
72
+ gap: inherit;
73
+ opacity: 1;
74
+ transition: opacity var(--duration-fast) var(--easing-standard);
75
+ }
76
+
77
+ .ct-button__loader {
78
+ position: absolute;
79
+ inset: 0;
80
+ display: grid;
81
+ place-items: center;
82
+ opacity: 0;
83
+ pointer-events: none;
84
+ transition: opacity var(--duration-fast) var(--easing-standard);
85
+ }
86
+
87
+ .ct-button__loader::before {
88
+ content: '';
89
+ width: var(--ct-button-loading-spinner-size);
90
+ height: var(--ct-button-loading-spinner-size);
91
+ border: var(--border-medium) solid currentColor;
92
+ border-inline-end-color: transparent;
93
+ border-radius: var(--radius-round);
94
+ animation: ct-spin var(--ct-spinner-duration) linear infinite;
95
+ }
96
+
97
+ .ct-button:is([aria-busy='true'], [data-loading='true']) {
98
+ opacity: 1;
99
+ transform: none;
100
+ cursor: wait;
101
+ pointer-events: none;
102
+ }
103
+
104
+ .ct-button:is([aria-busy='true'], [data-loading='true']) .ct-button__content {
105
+ opacity: 0;
106
+ }
107
+
108
+ .ct-button:is([aria-busy='true'], [data-loading='true']) .ct-button__loader {
109
+ opacity: 1;
55
110
  }
56
111
 
57
112
  .ct-button--secondary {
@@ -88,8 +143,8 @@
88
143
 
89
144
  .ct-button--danger {
90
145
  --ct-button-bg: var(--color-state-danger);
91
- --ct-button-bg-hover: color-mix(in srgb, var(--color-state-danger) 85%, black);
92
- --ct-button-bg-active: color-mix(in srgb, var(--color-state-danger) 70%, black);
146
+ --ct-button-bg-hover: color-mix(in srgb, var(--color-state-danger) 85%, var(--color-state-danger-text));
147
+ --ct-button-bg-active: color-mix(in srgb, var(--color-state-danger) 70%, var(--color-state-danger-text));
93
148
  --ct-button-border: transparent;
94
149
  --ct-button-color: var(--color-state-on-solid);
95
150
  }
@@ -138,8 +193,41 @@
138
193
  justify-content: center;
139
194
  }
140
195
 
196
+ @media (pointer: coarse) {
197
+ .ct-button {
198
+ min-width: var(--control-height-sm);
199
+ min-height: var(--control-height-sm);
200
+ }
201
+
202
+ .ct-button--link {
203
+ min-height: var(--control-height-sm);
204
+ padding-inline: var(--space-2);
205
+ }
206
+ }
207
+
141
208
  @media (prefers-reduced-motion: reduce) {
142
- .ct-button:active {
209
+ .ct-button,
210
+ .ct-button__content,
211
+ .ct-button__loader {
212
+ transition: none;
213
+ }
214
+
215
+ .ct-button:not(:disabled):not([aria-disabled='true']):not([aria-busy='true']):not([data-loading='true']):active {
143
216
  transform: none;
144
217
  }
218
+
219
+ .ct-button__loader::before {
220
+ animation: none;
221
+ }
222
+ }
223
+
224
+ @media (forced-colors: active) {
225
+ .ct-button {
226
+ border-color: currentColor;
227
+ }
228
+
229
+ .ct-button__loader::before {
230
+ border-color: currentColor;
231
+ border-inline-end-color: transparent;
232
+ }
145
233
  }
@@ -1,10 +1,16 @@
1
1
  .ct-card {
2
- --ct-card-padding: var(--space-6);
3
- background: var(--color-bg-elevated);
4
- border: var(--border-thin) solid var(--color-border-subtle);
5
- border-radius: var(--radius-lg);
2
+ --ct-card-bg: var(--component-card-background);
3
+ --ct-card-border: var(--component-card-border);
4
+ --ct-card-radius: var(--component-card-radius);
5
+ --ct-card-padding: var(--component-card-padding);
6
+ --ct-card-shadow: var(--component-card-shadow);
7
+ --ct-card-shadow-hover: var(--component-card-shadow-hover);
8
+
9
+ background: var(--ct-card-bg);
10
+ border: var(--border-thin) solid var(--ct-card-border);
11
+ border-radius: var(--ct-card-radius);
6
12
  padding: var(--ct-card-padding);
7
- box-shadow: var(--shadow-sm);
13
+ box-shadow: var(--ct-card-shadow);
8
14
  }
9
15
 
10
16
  .ct-card--sm {
@@ -47,7 +53,7 @@
47
53
  @media (hover: hover) {
48
54
  :is(a, button, [role='button'], [role='link']).ct-card--interactive:hover {
49
55
  border-color: var(--color-border-strong);
50
- box-shadow: var(--shadow-md);
56
+ box-shadow: var(--ct-card-shadow-hover);
51
57
  }
52
58
  }
53
59
 
@@ -55,7 +61,7 @@
55
61
  outline: 2px solid var(--color-focus-ring);
56
62
  outline-offset: 2px;
57
63
  border-color: var(--color-border-strong);
58
- box-shadow: var(--shadow-md);
64
+ box-shadow: var(--ct-card-shadow-hover);
59
65
  }
60
66
 
61
67
  .ct-card--interactive[aria-selected='true'] {
@@ -64,9 +70,20 @@
64
70
  }
65
71
 
66
72
  .ct-card--interactive[aria-disabled='true'],
67
- .ct-card--interactive:disabled {
73
+ .ct-card--interactive:disabled,
74
+ .ct-card--interactive[data-state='disabled'] {
68
75
  opacity: var(--opacity-disabled);
69
76
  cursor: not-allowed;
77
+ pointer-events: none;
78
+ }
79
+
80
+ .ct-card[aria-busy='true'],
81
+ .ct-card[data-state='loading'] {
82
+ cursor: wait;
83
+ }
84
+
85
+ .ct-card[data-state='error'] {
86
+ border-color: var(--color-state-danger-border);
70
87
  }
71
88
 
72
89
  @media (prefers-reduced-motion: reduce) {
@@ -74,3 +91,31 @@
74
91
  transition: none;
75
92
  }
76
93
  }
94
+
95
+ /* ── Datum ──
96
+ The reference-line grammar applied to cards. `--datum` adds a corner
97
+ registration mark (like an engineering drawing); the active/current
98
+ state carries the same orange leading edge as list and sidebar items.
99
+ Both are opt-in and additive — they don't change default cards. */
100
+ .ct-card--datum {
101
+ position: relative;
102
+ }
103
+
104
+ .ct-card--datum::before {
105
+ content: '';
106
+ position: absolute;
107
+ top: calc(-1 * var(--border-thin));
108
+ left: calc(-1 * var(--border-thin));
109
+ width: 18px;
110
+ height: 18px;
111
+ border-top: var(--border-medium) solid var(--color-brand-accent);
112
+ border-left: var(--border-medium) solid var(--color-brand-accent);
113
+ border-top-left-radius: var(--radius-lg);
114
+ }
115
+
116
+ .ct-card--active,
117
+ .ct-card[data-state='active'] {
118
+ /* The datum: an orange leading edge marks the active/current card,
119
+ consistent with .ct-list__item and .ct-sidebar active states. */
120
+ box-shadow: inset var(--border-thick) 0 0 0 var(--color-brand-accent), var(--ct-card-shadow);
121
+ }
@@ -13,7 +13,7 @@
13
13
  appearance: none;
14
14
  width: var(--space-6);
15
15
  height: var(--space-6);
16
- border: var(--border-thin) solid var(--color-border-default);
16
+ border: var(--border-thin) solid var(--ct-control-border);
17
17
  background: var(--color-bg-elevated);
18
18
  cursor: pointer;
19
19
  display: grid;
@@ -24,7 +24,8 @@
24
24
  transform var(--duration-fast) var(--easing-standard);
25
25
  }
26
26
 
27
- /* Invisible touch-target expansion (WCAG 2.5.8: 44×44px minimum) */
27
+ /* Construct 44×44 target policy: meets the size in WCAG 2.2 SC 2.5.5
28
+ (AAA) and exceeds the AA minimum in SC 2.5.8. */
28
29
  @media (pointer: coarse) {
29
30
  .ct-check__input::after,
30
31
  .ct-radio__input::after {
@@ -340,7 +340,7 @@
340
340
  outline-offset: 2px;
341
341
  }
342
342
 
343
- /* ── Touch-target minimums (WCAG 2.5.8: 44×44px) ──────────── */
343
+ /* ── Construct 44×44 target policy (WCAG 2.2 SC 2.5.5 is AAA) ─── */
344
344
 
345
345
  @media (pointer: coarse) {
346
346
  .ct-chip {
@@ -1,6 +1,8 @@
1
1
  /* ─── Datepicker ────────────────────────────────────────────────── */
2
2
 
3
3
  .ct-datepicker {
4
+ --ct-datepicker-selected-surface: color-mix(in srgb, var(--color-brand-primary) 12%, transparent);
5
+
4
6
  position: relative;
5
7
  display: inline-block;
6
8
  }
@@ -150,7 +152,7 @@
150
152
  /* ── Range selection ────────────────────────────────────────────── */
151
153
 
152
154
  .ct-datepicker__day[data-in-range] {
153
- background: var(--color-brand-primary-surface, color-mix(in srgb, var(--color-brand-primary) 12%, transparent));
155
+ background: var(--ct-datepicker-selected-surface);
154
156
  border-radius: 0;
155
157
  }
156
158