@zen-ui-capri/system 0.0.0-stage → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/.agents/skills/capri-zen-ui/SKILL.md +59 -0
  2. package/AGENTS.md +57 -0
  3. package/CLAUDE.md +7 -0
  4. package/LICENSE +21 -0
  5. package/README.md +93 -2
  6. package/bin/capri.mjs +1187 -0
  7. package/dist/lib/audio/ZenAudioController.d.ts +10 -0
  8. package/dist/lib/audio/ZenAudioController.d.ts.map +1 -0
  9. package/dist/lib/audio/useZenAudioSynth.d.ts +26 -0
  10. package/dist/lib/audio/useZenAudioSynth.d.ts.map +1 -0
  11. package/dist/lib/capri/capri-engine.d.ts +127 -0
  12. package/dist/lib/capri/capri-engine.d.ts.map +1 -0
  13. package/dist/lib/components/ZenAIConstruct.d.ts +24 -0
  14. package/dist/lib/components/ZenAIConstruct.d.ts.map +1 -0
  15. package/dist/lib/components/ZenAmbientBackground.d.ts +8 -0
  16. package/dist/lib/components/ZenAmbientBackground.d.ts.map +1 -0
  17. package/dist/lib/components/ZenBadge.d.ts +21 -0
  18. package/dist/lib/components/ZenBadge.d.ts.map +1 -0
  19. package/dist/lib/components/ZenButton.d.ts +24 -0
  20. package/dist/lib/components/ZenButton.d.ts.map +1 -0
  21. package/dist/lib/components/ZenCard.d.ts +36 -0
  22. package/dist/lib/components/ZenCard.d.ts.map +1 -0
  23. package/dist/lib/components/ZenDataDisplay.d.ts +50 -0
  24. package/dist/lib/components/ZenDataDisplay.d.ts.map +1 -0
  25. package/dist/lib/components/ZenDataTable.d.ts +20 -0
  26. package/dist/lib/components/ZenDataTable.d.ts.map +1 -0
  27. package/dist/lib/components/ZenDossierPanel.d.ts +33 -0
  28. package/dist/lib/components/ZenDossierPanel.d.ts.map +1 -0
  29. package/dist/lib/components/ZenDropdown.d.ts +19 -0
  30. package/dist/lib/components/ZenDropdown.d.ts.map +1 -0
  31. package/dist/lib/components/ZenFeedback.d.ts +24 -0
  32. package/dist/lib/components/ZenFeedback.d.ts.map +1 -0
  33. package/dist/lib/components/ZenForms.d.ts +84 -0
  34. package/dist/lib/components/ZenForms.d.ts.map +1 -0
  35. package/dist/lib/components/ZenHeading.d.ts +15 -0
  36. package/dist/lib/components/ZenHeading.d.ts.map +1 -0
  37. package/dist/lib/components/ZenIcon.d.ts +24 -0
  38. package/dist/lib/components/ZenIcon.d.ts.map +1 -0
  39. package/dist/lib/components/ZenInput.d.ts +12 -0
  40. package/dist/lib/components/ZenInput.d.ts.map +1 -0
  41. package/dist/lib/components/ZenModal.d.ts +12 -0
  42. package/dist/lib/components/ZenModal.d.ts.map +1 -0
  43. package/dist/lib/components/ZenNavbar.d.ts +11 -0
  44. package/dist/lib/components/ZenNavbar.d.ts.map +1 -0
  45. package/dist/lib/components/ZenNavigation.d.ts +52 -0
  46. package/dist/lib/components/ZenNavigation.d.ts.map +1 -0
  47. package/dist/lib/components/ZenOverlays.d.ts +48 -0
  48. package/dist/lib/components/ZenOverlays.d.ts.map +1 -0
  49. package/dist/lib/components/ZenSegmentedControl.d.ts +18 -0
  50. package/dist/lib/components/ZenSegmentedControl.d.ts.map +1 -0
  51. package/dist/lib/components/ZenSwitch.d.ts +11 -0
  52. package/dist/lib/components/ZenSwitch.d.ts.map +1 -0
  53. package/dist/lib/components/ZenTabs.d.ts +17 -0
  54. package/dist/lib/components/ZenTabs.d.ts.map +1 -0
  55. package/dist/lib/components/ZenTimelineCard.d.ts +14 -0
  56. package/dist/lib/components/ZenTimelineCard.d.ts.map +1 -0
  57. package/dist/lib/components/ZenToast.d.ts +18 -0
  58. package/dist/lib/components/ZenToast.d.ts.map +1 -0
  59. package/dist/lib/cosmos/ZenCosmosCanvas.d.ts +10 -0
  60. package/dist/lib/cosmos/ZenCosmosCanvas.d.ts.map +1 -0
  61. package/dist/lib/cosmos/ZenTelemetryHud.d.ts +45 -0
  62. package/dist/lib/cosmos/ZenTelemetryHud.d.ts.map +1 -0
  63. package/dist/lib/index.d.ts +33 -0
  64. package/dist/lib/index.d.ts.map +1 -0
  65. package/dist/lib/index.js +3922 -0
  66. package/dist/lib/theme/ZenThemeProvider.d.ts +47 -0
  67. package/dist/lib/theme/ZenThemeProvider.d.ts.map +1 -0
  68. package/dist/lib/tokens.css +888 -0
  69. package/dist/lib/universal/zen-universal.d.ts +77 -0
  70. package/dist/lib/universal/zen-universal.d.ts.map +1 -0
  71. package/dist/lib/universal/zen-universal.js +18 -0
  72. package/dist/lib/zen-universal-C0PD8AI9.js +1352 -0
  73. package/package.json +78 -4
  74. package/src/lib/audio/ZenAudioController.tsx +114 -0
  75. package/src/lib/audio/useZenAudioSynth.ts +289 -0
  76. package/src/lib/capri/capri-engine.ts +939 -0
  77. package/src/lib/components/ZenAIConstruct.tsx +594 -0
  78. package/src/lib/components/ZenAmbientBackground.tsx +210 -0
  79. package/src/lib/components/ZenBadge.tsx +111 -0
  80. package/src/lib/components/ZenButton.tsx +127 -0
  81. package/src/lib/components/ZenCard.tsx +180 -0
  82. package/src/lib/components/ZenDataDisplay.tsx +349 -0
  83. package/src/lib/components/ZenDataTable.tsx +143 -0
  84. package/src/lib/components/ZenDossierPanel.tsx +120 -0
  85. package/src/lib/components/ZenDropdown.tsx +208 -0
  86. package/src/lib/components/ZenFeedback.tsx +137 -0
  87. package/src/lib/components/ZenForms.tsx +664 -0
  88. package/src/lib/components/ZenHeading.tsx +63 -0
  89. package/src/lib/components/ZenIcon.tsx +220 -0
  90. package/src/lib/components/ZenInput.tsx +120 -0
  91. package/src/lib/components/ZenModal.tsx +103 -0
  92. package/src/lib/components/ZenNavbar.tsx +106 -0
  93. package/src/lib/components/ZenNavigation.tsx +300 -0
  94. package/src/lib/components/ZenOverlays.tsx +386 -0
  95. package/src/lib/components/ZenSegmentedControl.tsx +113 -0
  96. package/src/lib/components/ZenSwitch.tsx +63 -0
  97. package/src/lib/components/ZenTabs.tsx +103 -0
  98. package/src/lib/components/ZenTimelineCard.tsx +64 -0
  99. package/src/lib/components/ZenToast.tsx +96 -0
  100. package/src/lib/cosmos/ZenCosmosCanvas.tsx +931 -0
  101. package/src/lib/cosmos/ZenTelemetryHud.tsx +385 -0
  102. package/src/lib/index.ts +293 -0
  103. package/src/lib/styles/zen-tokens.css +888 -0
  104. package/src/lib/theme/ZenThemeProvider.tsx +328 -0
  105. package/src/lib/universal/zen-universal.ts +743 -0
@@ -0,0 +1,63 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export type ZenHeadingLevel = 'h1' | 'h2' | 'h3' | 'h4';
4
+ export type ZenHeadingSize = 'hero' | 'display' | 'section' | 'title' | 'subtitle';
5
+
6
+ export interface ZenHeadingProps {
7
+ readonly as?: ZenHeadingLevel;
8
+ readonly size?: ZenHeadingSize;
9
+ readonly eyebrow?: ReactNode;
10
+ readonly eyebrowIcon?: ReactNode;
11
+ readonly accentText?: ReactNode;
12
+ readonly subtitle?: ReactNode;
13
+ readonly children: ReactNode;
14
+ readonly className?: string;
15
+ }
16
+
17
+ const SIZE_CLASSES: Record<ZenHeadingSize, string> = {
18
+ hero: 'text-4xl sm:text-5xl lg:text-6xl font-extrabold tracking-tight leading-[1.03]',
19
+ display: 'text-3xl sm:text-5xl font-bold tracking-tight leading-[1.04]',
20
+ section: 'text-2xl sm:text-4xl font-bold tracking-tight leading-tight',
21
+ title: 'text-xl sm:text-2xl font-bold tracking-tight leading-snug',
22
+ subtitle: 'text-lg sm:text-xl font-bold tracking-tight leading-snug',
23
+ };
24
+
25
+ export function ZenHeading({
26
+ as: Tag = 'h2',
27
+ size = 'display',
28
+ eyebrow,
29
+ eyebrowIcon,
30
+ accentText,
31
+ subtitle,
32
+ children,
33
+ className = '',
34
+ }: ZenHeadingProps) {
35
+ return (
36
+ <div className={className}>
37
+ {eyebrow && (
38
+ <div className="inline-flex items-center gap-2 zen-telemetry-label text-[var(--zen-accent-primary)] mb-2.5">
39
+ {eyebrowIcon && <span className="shrink-0">{eyebrowIcon}</span>}
40
+ <span>{eyebrow}</span>
41
+ </div>
42
+ )}
43
+ <Tag
44
+ className={`zen-font-display text-[var(--zen-ink-primary)] ${SIZE_CLASSES[size]}`}
45
+ >
46
+ {children}
47
+ {accentText && (
48
+ <>
49
+ {' '}
50
+ <span className="zen-font-serif italic font-normal text-[var(--zen-accent-primary)]">
51
+ {accentText}
52
+ </span>
53
+ </>
54
+ )}
55
+ </Tag>
56
+ {subtitle && (
57
+ <div className="zen-font-serif italic text-xl sm:text-2xl text-[var(--zen-accent-primary)] mt-2">
58
+ {subtitle}
59
+ </div>
60
+ )}
61
+ </div>
62
+ );
63
+ }
@@ -0,0 +1,220 @@
1
+ import type { SVGAttributes } from 'react';
2
+
3
+ export type ZenIconName =
4
+ | 'search'
5
+ | 'plus'
6
+ | 'minus'
7
+ | 'close'
8
+ | 'check'
9
+ | 'copy'
10
+ | 'chevron-down'
11
+ | 'chevron-right'
12
+ | 'chevron-left'
13
+ | 'external-link'
14
+ | 'bolt'
15
+ | 'sparkles'
16
+ | 'terminal'
17
+ | 'cpu'
18
+ | 'database'
19
+ | 'layers'
20
+ | 'shield'
21
+ | 'sliders'
22
+ | 'command'
23
+ | 'code'
24
+ | 'activity'
25
+ | 'info'
26
+ | 'success'
27
+ | 'warning'
28
+ | 'danger'
29
+ | 'trend-up'
30
+ | 'trend-down'
31
+ | 'radio'
32
+ | 'sun'
33
+ | 'moon'
34
+ | 'eye'
35
+ | 'upload'
36
+ | 'trash'
37
+ | 'refresh'
38
+ | 'menu'
39
+ | 'panel-right'
40
+ | 'user'
41
+ | 'lock'
42
+ | 'globe'
43
+ | 'folder';
44
+
45
+ export type ZenIconSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
46
+
47
+ export type ZenIconTone =
48
+ | 'inherit'
49
+ | 'primary'
50
+ | 'secondary'
51
+ | 'muted'
52
+ | 'accent'
53
+ | 'success'
54
+ | 'warning'
55
+ | 'danger';
56
+
57
+ export const ZEN_ICON_SIZE_PX: Record<ZenIconSize, number> = {
58
+ xs: 12,
59
+ sm: 14,
60
+ md: 16,
61
+ lg: 20,
62
+ xl: 24,
63
+ };
64
+
65
+ const TONE_COLOR_MAP: Record<ZenIconTone, string> = {
66
+ inherit: 'currentColor',
67
+ primary: 'var(--zen-ink-primary)',
68
+ secondary: 'var(--zen-ink-secondary)',
69
+ muted: 'var(--zen-ink-muted)',
70
+ accent: 'var(--zen-accent-primary)',
71
+ success: 'var(--zen-status-success)',
72
+ warning: 'var(--zen-status-warning)',
73
+ danger: 'var(--zen-status-danger)',
74
+ };
75
+
76
+ export const ZEN_ICON_SVG_INNER: Record<ZenIconName, string> = {
77
+ search:
78
+ '<circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/>',
79
+ plus:
80
+ '<path d="M5 12h14"/><path d="M12 5v14"/>',
81
+ minus:
82
+ '<path d="M5 12h14"/>',
83
+ close:
84
+ '<path d="M18 6 6 18"/><path d="m6 6 12 12"/>',
85
+ check:
86
+ '<path d="M20 6 9 17l-5-5"/>',
87
+ copy:
88
+ '<rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/>',
89
+ 'chevron-down':
90
+ '<path d="m6 9 6 6 6-6"/>',
91
+ 'chevron-right':
92
+ '<path d="m9 18 6-6-6-6"/>',
93
+ 'chevron-left':
94
+ '<path d="m15 18-6-6 6-6"/>',
95
+ 'external-link':
96
+ '<path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/>',
97
+ bolt:
98
+ '<path d="M13 2 3 14h9l-1 8 10-12h-9l1-8z"/>',
99
+ sparkles:
100
+ '<path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/><path d="M20 3v4"/><path d="M22 5h-4"/>',
101
+ terminal:
102
+ '<polyline points="4 17 10 11 4 5"/><line x1="12" x2="20" y1="19" y2="19"/>',
103
+ cpu:
104
+ '<rect width="16" height="16" x="4" y="4" rx="2"/><rect width="6" height="6" x="9" y="9" rx="1"/><path d="M15 2v2"/><path d="M15 20v2"/><path d="M2 15h2"/><path d="M2 9h2"/><path d="M20 15h2"/><path d="M20 9h2"/><path d="M9 2v2"/><path d="M9 20v2"/>',
105
+ database:
106
+ '<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14a9 3 0 0 0 18 0V5"/><path d="M3 12a9 3 0 0 0 18 0"/>',
107
+ layers:
108
+ '<path d="m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z"/><path d="m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65"/><path d="m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65"/>',
109
+ shield:
110
+ '<path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/>',
111
+ sliders:
112
+ '<line x1="4" x2="4" y1="21" y2="14"/><line x1="4" x2="4" y1="10" y2="3"/><line x1="12" x2="12" y1="21" y2="12"/><line x1="12" x2="12" y1="8" y2="3"/><line x1="20" x2="20" y1="21" y2="16"/><line x1="20" x2="20" y1="12" y2="3"/><line x1="2" x2="6" y1="14" y2="14"/><line x1="10" x2="14" y1="8" y2="8"/><line x1="18" x2="22" y1="16" y2="16"/>',
113
+ command:
114
+ '<path d="M15 6v12a3 3 0 1 0 3-3H6a3 3 0 1 0 3 3V6a3 3 0 1 0-3 3h12a3 3 0 1 0-3-3"/>',
115
+ code:
116
+ '<polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/>',
117
+ activity:
118
+ '<path d="M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2"/>',
119
+ info:
120
+ '<circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/>',
121
+ success:
122
+ '<circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/>',
123
+ warning:
124
+ '<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/>',
125
+ danger:
126
+ '<path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z"/><path d="M12 8v4"/><path d="M12 16h.01"/>',
127
+ 'trend-up':
128
+ '<polyline points="22 7 13.5 15.5 8.5 10.5 2 17"/><polyline points="16 7 22 7 22 13"/>',
129
+ 'trend-down':
130
+ '<polyline points="22 17 13.5 8.5 8.5 13.5 2 7"/><polyline points="16 17 22 17 22 11"/>',
131
+ radio:
132
+ '<path d="M4.9 19.1C1 15.2 1 8.8 4.9 4.9"/><path d="M7.8 16.2c-2.3-2.3-2.3-6.1 0-8.5"/><circle cx="12" cy="12" r="2"/><path d="M16.2 7.8c2.3 2.3 2.3 6.1 0 8.5"/><path d="M19.1 4.9C23 8.8 23 15.1 19.1 19"/>',
133
+ sun:
134
+ '<circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/>',
135
+ moon:
136
+ '<path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"/>',
137
+ eye:
138
+ '<path d="M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0"/><circle cx="12" cy="12" r="3"/>',
139
+ upload:
140
+ '<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" x2="12" y1="3" y2="15"/>',
141
+ trash:
142
+ '<path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/>',
143
+ refresh:
144
+ '<path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M8 16H3v5"/>',
145
+ menu:
146
+ '<line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="18" y2="18"/>',
147
+ 'panel-right':
148
+ '<rect width="18" height="18" x="3" y="3" rx="2"/><path d="M15 3v18"/>',
149
+ user:
150
+ '<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/>',
151
+ lock:
152
+ '<rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>',
153
+ globe:
154
+ '<circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/>',
155
+ folder:
156
+ '<path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/>',
157
+ };
158
+
159
+ export const ZEN_ICON_NAMES: readonly ZenIconName[] = Object.keys(
160
+ ZEN_ICON_SVG_INNER
161
+ ) as readonly ZenIconName[];
162
+
163
+ export function renderZenIconSvgMarkup(options: {
164
+ readonly name: ZenIconName;
165
+ readonly size?: ZenIconSize;
166
+ readonly tone?: ZenIconTone;
167
+ readonly strokeWidth?: number;
168
+ readonly spin?: boolean;
169
+ }): string {
170
+ const px = ZEN_ICON_SIZE_PX[options.size ?? 'md'];
171
+ const color = TONE_COLOR_MAP[options.tone ?? 'inherit'];
172
+ const sw = options.strokeWidth ?? 2;
173
+ const inner = ZEN_ICON_SVG_INNER[options.name] ?? ZEN_ICON_SVG_INNER.sparkles;
174
+ const spinStyle = options.spin ? 'animation: spin 1s linear infinite;' : '';
175
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${px}" height="${px}" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="${sw}" stroke-linecap="round" stroke-linejoin="round" style="display:inline-block;vertical-align:middle;flex-shrink:0;${spinStyle}">${inner}</svg>`;
176
+ }
177
+
178
+ export interface ZenIconProps extends Omit<SVGAttributes<SVGSVGElement>, 'name'> {
179
+ readonly name: ZenIconName;
180
+ readonly size?: ZenIconSize;
181
+ readonly tone?: ZenIconTone;
182
+ readonly strokeWidth?: number;
183
+ readonly spin?: boolean;
184
+ readonly ariaLabel?: string;
185
+ }
186
+
187
+ export function ZenIcon({
188
+ name,
189
+ size = 'md',
190
+ tone = 'inherit',
191
+ strokeWidth = 2,
192
+ spin = false,
193
+ ariaLabel,
194
+ className = '',
195
+ ...rest
196
+ }: ZenIconProps) {
197
+ const px = ZEN_ICON_SIZE_PX[size];
198
+ const strokeColor = TONE_COLOR_MAP[tone];
199
+ const innerMarkup = ZEN_ICON_SVG_INNER[name] ?? ZEN_ICON_SVG_INNER.sparkles;
200
+
201
+ return (
202
+ <svg
203
+ xmlns="http://www.w3.org/2000/svg"
204
+ width={px}
205
+ height={px}
206
+ viewBox="0 0 24 24"
207
+ fill="none"
208
+ stroke={strokeColor}
209
+ strokeWidth={strokeWidth}
210
+ strokeLinecap="round"
211
+ strokeLinejoin="round"
212
+ role={ariaLabel ? 'img' : undefined}
213
+ aria-label={ariaLabel}
214
+ aria-hidden={ariaLabel ? undefined : 'true'}
215
+ className={`inline-block shrink-0 ${spin ? 'animate-spin' : ''} ${className}`.trim()}
216
+ dangerouslySetInnerHTML={{ __html: innerMarkup }}
217
+ {...rest}
218
+ />
219
+ );
220
+ }
@@ -0,0 +1,120 @@
1
+ import {
2
+ useId,
3
+ useState,
4
+ type ChangeEvent,
5
+ type InputHTMLAttributes,
6
+ type ReactNode,
7
+ } from 'react';
8
+ import { capri } from '../capri/capri-engine';
9
+
10
+ export interface ZenInputProps
11
+ extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size'> {
12
+ readonly label: string;
13
+ readonly hint?: string;
14
+ readonly error?: string;
15
+ readonly iconLeft?: ReactNode;
16
+ readonly iconRight?: ReactNode;
17
+ readonly mono?: boolean;
18
+ readonly capriGuard?: boolean;
19
+ }
20
+
21
+ export function ZenInput({
22
+ label,
23
+ hint,
24
+ error,
25
+ iconLeft,
26
+ iconRight,
27
+ mono = false,
28
+ capriGuard = true,
29
+ id,
30
+ value,
31
+ defaultValue,
32
+ onChange,
33
+ className = '',
34
+ ...rest
35
+ }: ZenInputProps) {
36
+ const generatedId = useId();
37
+ const inputId = id ?? generatedId;
38
+ const descriptionId = `${inputId}-desc`;
39
+ const [internalVal, setInternalVal] = useState<string>(
40
+ typeof defaultValue === 'string' ? defaultValue : ''
41
+ );
42
+
43
+ const liveText =
44
+ typeof value === 'string'
45
+ ? value
46
+ : typeof value === 'number'
47
+ ? String(value)
48
+ : internalVal;
49
+
50
+ const securityScan = capriGuard ? capri.scanInputSecurity(liveText) : null;
51
+ const guardError =
52
+ securityScan && !securityScan.safe && securityScan.threats[0]
53
+ ? `CAPRI GUARD BLOCKED [${securityScan.threats[0].category}]: ${securityScan.threats[0].patternName}`
54
+ : undefined;
55
+ const activeError = error || guardError;
56
+ const hasDescription = Boolean(activeError || hint);
57
+
58
+ const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
59
+ if (value === undefined) {
60
+ setInternalVal(e.target.value);
61
+ }
62
+ onChange?.(e);
63
+ };
64
+
65
+ return (
66
+ <div data-zen-occlude="true" className={`flex flex-col gap-1.5 ${className}`.trim()}>
67
+ <label
68
+ htmlFor={inputId}
69
+ className="zen-telemetry-label text-[var(--zen-ink-secondary)]"
70
+ >
71
+ {label}
72
+ </label>
73
+
74
+ <div
75
+ className={`flex items-center gap-2.5 px-3.5 py-2.5 rounded-xl bg-[var(--zen-surface-1)] border transition-colors ${
76
+ activeError
77
+ ? 'border-[var(--zen-status-danger)]'
78
+ : 'border-[var(--zen-border-default)] focus-within:border-[var(--zen-accent-primary)]'
79
+ }`}
80
+ >
81
+ {iconLeft && (
82
+ <span className="text-[var(--zen-ink-muted)] shrink-0">{iconLeft}</span>
83
+ )}
84
+ <input
85
+ id={inputId}
86
+ value={value}
87
+ defaultValue={defaultValue}
88
+ onChange={handleChange}
89
+ aria-invalid={Boolean(activeError)}
90
+ aria-describedby={hasDescription ? descriptionId : undefined}
91
+ data-capri-threat={
92
+ securityScan && !securityScan.safe && securityScan.threats[0]
93
+ ? securityScan.threats[0].category
94
+ : undefined
95
+ }
96
+ className={`w-full bg-transparent text-sm text-[var(--zen-ink-primary)] placeholder:text-[var(--zen-ink-muted)] focus:outline-none ${
97
+ mono ? 'zen-font-mono' : 'zen-font-sans'
98
+ }`}
99
+ {...rest}
100
+ />
101
+ {iconRight && (
102
+ <span className="text-[var(--zen-ink-muted)] shrink-0">{iconRight}</span>
103
+ )}
104
+ </div>
105
+
106
+ {hasDescription && (
107
+ <span
108
+ id={descriptionId}
109
+ className={`zen-font-mono text-[11px] ${
110
+ activeError
111
+ ? 'text-[var(--zen-status-danger)]'
112
+ : 'text-[var(--zen-ink-muted)]'
113
+ }`}
114
+ >
115
+ {activeError ?? hint}
116
+ </span>
117
+ )}
118
+ </div>
119
+ );
120
+ }
@@ -0,0 +1,103 @@
1
+ import { useEffect, useId, useRef, type ReactNode } from 'react';
2
+ import { X } from 'lucide-react';
3
+
4
+ export interface ZenModalProps {
5
+ readonly open: boolean;
6
+ readonly onClose: () => void;
7
+ readonly eyebrow?: string;
8
+ readonly title: ReactNode;
9
+ readonly subtitle?: ReactNode;
10
+ readonly footer?: ReactNode;
11
+ readonly children: ReactNode;
12
+ }
13
+
14
+ export function ZenModal({
15
+ open,
16
+ onClose,
17
+ eyebrow = 'SYSTEM DIALOG',
18
+ title,
19
+ subtitle,
20
+ footer,
21
+ children,
22
+ }: ZenModalProps) {
23
+ const titleId = useId();
24
+ const closeButtonRef = useRef<HTMLButtonElement>(null);
25
+
26
+ useEffect(() => {
27
+ if (!open) return;
28
+ const previousFocus = document.activeElement as HTMLElement | null;
29
+ closeButtonRef.current?.focus();
30
+
31
+ const handleKeyDown = (e: KeyboardEvent) => {
32
+ if (e.key === 'Escape') {
33
+ e.preventDefault();
34
+ onClose();
35
+ }
36
+ };
37
+ window.addEventListener('keydown', handleKeyDown);
38
+ return () => {
39
+ window.removeEventListener('keydown', handleKeyDown);
40
+ previousFocus?.focus();
41
+ };
42
+ }, [open, onClose]);
43
+
44
+ if (!open) return null;
45
+
46
+ return (
47
+ <div
48
+ data-zen-occlude="true"
49
+ className="fixed inset-0 z-50 flex items-center justify-center p-3 sm:p-6 bg-black/70 backdrop-blur-md"
50
+ onMouseDown={(e) => {
51
+ if (e.target === e.currentTarget) {
52
+ onClose();
53
+ }
54
+ }}
55
+ >
56
+ <div
57
+ role="dialog"
58
+ aria-modal="true"
59
+ aria-labelledby={titleId}
60
+ className="w-full max-w-xl max-h-[90dvh] overflow-y-auto rounded-3xl zen-surface-selected p-5 sm:p-8 shadow-2xl"
61
+ >
62
+ <div className="flex items-start justify-between gap-4 mb-4">
63
+ <div className="min-w-0">
64
+ <div className="zen-telemetry-label text-[var(--zen-accent-primary)] mb-1">
65
+ {eyebrow}
66
+ </div>
67
+ <h2
68
+ id={titleId}
69
+ className="zen-font-display text-xl sm:text-3xl font-bold text-[var(--zen-ink-primary)]"
70
+ >
71
+ {title}
72
+ </h2>
73
+ {subtitle && (
74
+ <div className="zen-font-serif italic text-base sm:text-lg text-[var(--zen-accent-primary)] mt-0.5">
75
+ {subtitle}
76
+ </div>
77
+ )}
78
+ </div>
79
+
80
+ <button
81
+ ref={closeButtonRef}
82
+ type="button"
83
+ aria-label="Close dialog"
84
+ onClick={onClose}
85
+ className="zen-focus-ring p-2 rounded-full border border-[var(--zen-border-default)] hover:border-[var(--zen-accent-primary)] text-[var(--zen-ink-secondary)] hover:text-[var(--zen-ink-primary)] cursor-pointer transition-colors shrink-0"
86
+ >
87
+ <X className="w-4 h-4" />
88
+ </button>
89
+ </div>
90
+
91
+ <div className="text-sm text-[var(--zen-ink-secondary)] leading-relaxed">
92
+ {children}
93
+ </div>
94
+
95
+ {footer && (
96
+ <div className="mt-6 pt-4 border-t border-[var(--zen-border-subtle)] flex flex-wrap items-center justify-end gap-3">
97
+ {footer}
98
+ </div>
99
+ )}
100
+ </div>
101
+ </div>
102
+ );
103
+ }
@@ -0,0 +1,106 @@
1
+ import { useState, useEffect, type ReactNode } from 'react';
2
+ import { Menu, X } from 'lucide-react';
3
+
4
+ export interface ZenNavbarProps {
5
+ readonly brandTitle: ReactNode;
6
+ readonly brandSubtitle?: ReactNode;
7
+ readonly navSlot?: ReactNode;
8
+ readonly actionsSlot?: ReactNode;
9
+ readonly mobileActionsSlot?: ReactNode;
10
+ readonly className?: string;
11
+ }
12
+
13
+ export function ZenNavbar({
14
+ brandTitle,
15
+ brandSubtitle,
16
+ navSlot,
17
+ actionsSlot,
18
+ mobileActionsSlot,
19
+ className = '',
20
+ }: ZenNavbarProps) {
21
+ const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
22
+
23
+ useEffect(() => {
24
+ if (!mobileMenuOpen) return;
25
+ const onKeyDown = (e: globalThis.KeyboardEvent) => {
26
+ if (e.key === 'Escape') {
27
+ setMobileMenuOpen(false);
28
+ }
29
+ };
30
+ window.addEventListener('keydown', onKeyDown);
31
+ return () => window.removeEventListener('keydown', onKeyDown);
32
+ }, [mobileMenuOpen]);
33
+
34
+ return (
35
+ <header
36
+ data-zen-occlude="true"
37
+ className={`fixed top-0 inset-x-0 z-30 border-b border-[var(--zen-border-subtle)] bg-[var(--zen-surface-header)] backdrop-blur-xl ${className}`.trim()}
38
+ >
39
+ <div className="max-w-[90rem] mx-auto px-3 sm:px-6 lg:px-8 h-16 sm:h-20 flex items-center justify-between gap-2 sm:gap-4">
40
+ <div className="flex items-center gap-3 min-w-0">
41
+ <span className="zen-font-serif italic text-base min-[360px]:text-lg sm:text-xl lg:text-2xl tracking-tight text-[var(--zen-ink-primary)] inline-flex items-center shrink-0">
42
+ {brandTitle}
43
+ </span>
44
+ {brandSubtitle && (
45
+ <span className="hidden xl:inline-block zen-telemetry-label truncate min-w-0">
46
+ {brandSubtitle}
47
+ </span>
48
+ )}
49
+ </div>
50
+
51
+ {navSlot && (
52
+ <nav
53
+ aria-label="Primary navigation"
54
+ className="hidden lg:flex items-center gap-1.5 shrink-0"
55
+ >
56
+ {navSlot}
57
+ </nav>
58
+ )}
59
+
60
+ <div className="flex items-center gap-1.5 sm:gap-2 shrink-0">
61
+ {actionsSlot}
62
+
63
+ {navSlot && (
64
+ <button
65
+ type="button"
66
+ aria-label={
67
+ mobileMenuOpen ? 'Close navigation menu' : 'Open navigation menu'
68
+ }
69
+ aria-expanded={mobileMenuOpen}
70
+ onClick={() => setMobileMenuOpen((prev) => !prev)}
71
+ className="zen-focus-ring lg:hidden inline-flex items-center justify-center w-8 h-8 sm:w-9 sm:h-9 rounded-xl border border-[var(--zen-border-default)] bg-[var(--zen-surface-1)] text-[var(--zen-ink-primary)] cursor-pointer shrink-0"
72
+ >
73
+ {mobileMenuOpen ? (
74
+ <X className="w-4 h-4 text-[var(--zen-accent-primary)]" />
75
+ ) : (
76
+ <Menu className="w-4 h-4 text-[var(--zen-accent-primary)]" />
77
+ )}
78
+ </button>
79
+ )}
80
+ </div>
81
+ </div>
82
+
83
+ {navSlot && mobileMenuOpen && (
84
+ <div className="lg:hidden border-t border-[var(--zen-border-subtle)] bg-[var(--zen-bg-elevated)] px-4 py-3.5 space-y-3 shadow-xl">
85
+ {brandSubtitle && (
86
+ <div className="px-2 pb-2 border-b border-[var(--zen-border-subtle)] zen-telemetry-label text-[var(--zen-ink-muted)] truncate">
87
+ {brandSubtitle}
88
+ </div>
89
+ )}
90
+ <nav
91
+ aria-label="Mobile navigation"
92
+ onClick={() => setMobileMenuOpen(false)}
93
+ className="flex flex-col gap-1"
94
+ >
95
+ {navSlot}
96
+ </nav>
97
+ {mobileActionsSlot && (
98
+ <div className="pt-2 border-t border-[var(--zen-border-subtle)] flex flex-wrap items-center gap-2">
99
+ {mobileActionsSlot}
100
+ </div>
101
+ )}
102
+ </div>
103
+ )}
104
+ </header>
105
+ );
106
+ }