@zen-ui-capri/system 0.0.0-stage → 1.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 (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 +77 -3
  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,210 @@
1
+ import { useEffect, useRef } from 'react';
2
+ import { useZenTheme } from '../theme/ZenThemeProvider';
3
+
4
+ export type ZenBackgroundVariant =
5
+ | 'blackwall-lattice'
6
+ | 'aurora-mesh'
7
+ | 'swiss-blueprint'
8
+ | 'minimal-void';
9
+
10
+ export interface ZenAmbientBackgroundProps {
11
+ readonly variant?: ZenBackgroundVariant;
12
+ readonly interactive?: boolean;
13
+ readonly paused?: boolean;
14
+ }
15
+
16
+ interface DataNode {
17
+ x: number;
18
+ y: number;
19
+ vx: number;
20
+ vy: number;
21
+ size: number;
22
+ phase: number;
23
+ }
24
+
25
+ export function ZenAmbientBackground({
26
+ variant = 'blackwall-lattice',
27
+ interactive = true,
28
+ paused = false,
29
+ }: ZenAmbientBackgroundProps) {
30
+ const { themeMeta, reducedMotion } = useZenTheme();
31
+ const canvasRef = useRef<HTMLCanvasElement | null>(null);
32
+ const pointerRef = useRef<{ x: number; y: number }>({ x: 0.5, y: 0.35 });
33
+
34
+ useEffect(() => {
35
+ if (!interactive) return;
36
+ const onMove = (e: PointerEvent) => {
37
+ pointerRef.current = {
38
+ x: e.clientX / Math.max(1, window.innerWidth),
39
+ y: e.clientY / Math.max(1, window.innerHeight),
40
+ };
41
+ };
42
+ window.addEventListener('pointermove', onMove, { passive: true });
43
+ return () => window.removeEventListener('pointermove', onMove);
44
+ }, [interactive]);
45
+
46
+ useEffect(() => {
47
+ if (variant === 'minimal-void') return;
48
+ const canvas = canvasRef.current;
49
+ if (!canvas) return;
50
+ const ctx = canvas.getContext('2d');
51
+ if (!ctx) return;
52
+
53
+ const nodes: DataNode[] = Array.from({ length: 56 }, (_, idx) => ({
54
+ x: (idx * 73) % 1000,
55
+ y: (idx * 137) % 1000,
56
+ vx: ((idx % 5) - 2) * 0.22,
57
+ vy: -0.35 - (idx % 4) * 0.18,
58
+ size: 1.2 + (idx % 3) * 0.9,
59
+ phase: idx * 0.6,
60
+ }));
61
+
62
+ let rafId = 0;
63
+ let time = 0;
64
+
65
+ const render = () => {
66
+ const dpr = Math.min(window.devicePixelRatio || 1, 1.5);
67
+ const w = window.innerWidth;
68
+ const h = window.innerHeight;
69
+ if (canvas.width !== w * dpr || canvas.height !== h * dpr) {
70
+ canvas.width = w * dpr;
71
+ canvas.height = h * dpr;
72
+ }
73
+
74
+ ctx.save();
75
+ ctx.scale(dpr, dpr);
76
+ ctx.clearRect(0, 0, w, h);
77
+
78
+ if (!reducedMotion && !paused) {
79
+ time += 0.014;
80
+ }
81
+
82
+ const primary = themeMeta.accentHex;
83
+ const secondary = themeMeta.secondaryHex;
84
+ const isDark = themeMeta.polarity === 'dark';
85
+ const px = pointerRef.current.x * w;
86
+ const py = pointerRef.current.y * h;
87
+
88
+ if (variant === 'aurora-mesh') {
89
+ const g1 = ctx.createRadialGradient(
90
+ w * (0.25 + Math.sin(time * 0.7) * 0.12),
91
+ h * (0.28 + Math.cos(time * 0.5) * 0.1),
92
+ 20,
93
+ w * 0.35,
94
+ h * 0.35,
95
+ Math.max(w, h) * 0.55
96
+ );
97
+ g1.addColorStop(0, `${primary}${isDark ? '24' : '18'}`);
98
+ g1.addColorStop(1, 'transparent');
99
+ ctx.fillStyle = g1;
100
+ ctx.fillRect(0, 0, w, h);
101
+
102
+ const g2 = ctx.createRadialGradient(
103
+ w * (0.75 + Math.cos(time * 0.6) * 0.12),
104
+ h * (0.65 + Math.sin(time * 0.8) * 0.1),
105
+ 20,
106
+ w * 0.7,
107
+ h * 0.65,
108
+ Math.max(w, h) * 0.5
109
+ );
110
+ g2.addColorStop(0, `${secondary}${isDark ? '20' : '14'}`);
111
+ g2.addColorStop(1, 'transparent');
112
+ ctx.fillStyle = g2;
113
+ ctx.fillRect(0, 0, w, h);
114
+ } else if (variant === 'swiss-blueprint') {
115
+ const step = 48;
116
+ ctx.strokeStyle = isDark ? `${primary}14` : `${primary}16`;
117
+ ctx.lineWidth = 1;
118
+
119
+ for (let x = 0; x < w; x += step) {
120
+ ctx.beginPath();
121
+ ctx.moveTo(x, 0);
122
+ ctx.lineTo(x, h);
123
+ ctx.stroke();
124
+ }
125
+ for (let y = 0; y < h; y += step) {
126
+ ctx.beginPath();
127
+ ctx.moveTo(0, y);
128
+ ctx.lineTo(w, y);
129
+ ctx.stroke();
130
+ }
131
+
132
+ for (let x = step; x < w; x += step * 2) {
133
+ for (let y = step; y < h; y += step * 2) {
134
+ const dist = Math.hypot(x - px, y - py);
135
+ const highlight = Math.max(0, 1 - dist / 280);
136
+ ctx.fillStyle = highlight > 0.2 ? primary : secondary;
137
+ ctx.globalAlpha = (isDark ? 0.22 : 0.28) + highlight * 0.55;
138
+ ctx.fillRect(x - 1.5, y - 1.5, 3, 3);
139
+ }
140
+ }
141
+ } else {
142
+ const spot = ctx.createRadialGradient(px, py, 10, px, py, 460);
143
+ spot.addColorStop(0, `${primary}${isDark ? '1c' : '14'}`);
144
+ spot.addColorStop(0.5, `${secondary}${isDark ? '0d' : '08'}`);
145
+ spot.addColorStop(1, 'transparent');
146
+ ctx.fillStyle = spot;
147
+ ctx.fillRect(0, 0, w, h);
148
+
149
+ const horizonY = h * 0.52;
150
+ ctx.strokeStyle = `${primary}${isDark ? '1f' : '22'}`;
151
+ ctx.lineWidth = 1;
152
+
153
+ const cols = 18;
154
+ for (let i = -cols; i <= cols; i++) {
155
+ const topX = w * 0.5 + i * (w / cols) * 0.32;
156
+ const botX = w * 0.5 + i * (w / cols) * 1.45;
157
+ ctx.beginPath();
158
+ ctx.moveTo(topX, horizonY);
159
+ ctx.lineTo(botX, h);
160
+ ctx.stroke();
161
+ }
162
+
163
+ const rows = 10;
164
+ const offset = (time * 0.8) % 1;
165
+ for (let r = 0; r < rows; r++) {
166
+ const p = Math.pow((r + offset) / rows, 1.75);
167
+ const y = horizonY + p * (h - horizonY);
168
+ ctx.globalAlpha = p * (isDark ? 0.35 : 0.28);
169
+ ctx.beginPath();
170
+ ctx.moveTo(0, y);
171
+ ctx.lineTo(w, y);
172
+ ctx.stroke();
173
+ }
174
+
175
+ for (let i = 0; i < nodes.length; i++) {
176
+ const n = nodes[i];
177
+ if (!reducedMotion && !paused) {
178
+ n.x = (n.x + n.vx + 1000) % 1000;
179
+ n.y = (n.y + n.vy + 1000) % 1000;
180
+ }
181
+ const nx = (n.x / 1000) * w;
182
+ const ny = (n.y / 1000) * h;
183
+
184
+ ctx.globalAlpha =
185
+ (isDark ? 0.35 : 0.4) + Math.sin(time * 2 + n.phase) * 0.2;
186
+ ctx.fillStyle = i % 3 === 0 ? secondary : primary;
187
+ ctx.fillRect(nx, ny, n.size, n.size * 3.2);
188
+ }
189
+ }
190
+
191
+ ctx.restore();
192
+ rafId = window.requestAnimationFrame(render);
193
+ };
194
+
195
+ rafId = window.requestAnimationFrame(render);
196
+ return () => window.cancelAnimationFrame(rafId);
197
+ }, [variant, themeMeta, reducedMotion, paused]);
198
+
199
+ if (variant === 'minimal-void') {
200
+ return null;
201
+ }
202
+
203
+ return (
204
+ <canvas
205
+ ref={canvasRef}
206
+ aria-hidden="true"
207
+ className="fixed inset-0 z-0 pointer-events-none"
208
+ />
209
+ );
210
+ }
@@ -0,0 +1,111 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ export type ZenBadgeTone =
4
+ | 'gold'
5
+ | 'amber'
6
+ | 'emerald'
7
+ | 'coral'
8
+ | 'cyan'
9
+ | 'neutral';
10
+
11
+ export interface ZenBadgeProps {
12
+ readonly tone?: ZenBadgeTone;
13
+ readonly pulse?: boolean;
14
+ readonly icon?: ReactNode;
15
+ readonly children: ReactNode;
16
+ readonly className?: string;
17
+ }
18
+
19
+ const TONE_STYLES: Record<
20
+ ZenBadgeTone,
21
+ { readonly pill: string; readonly dot: string }
22
+ > = {
23
+ gold: {
24
+ pill: 'bg-[var(--zen-accent-subtle)] text-[var(--zen-accent-primary)] border-[var(--zen-accent-border)]',
25
+ dot: 'bg-[var(--zen-accent-primary)]',
26
+ },
27
+ amber: {
28
+ pill: 'bg-[color-mix(in_srgb,var(--zen-status-warning)_15%,transparent)] text-[var(--zen-status-warning)] border-[color-mix(in_srgb,var(--zen-status-warning)_45%,transparent)]',
29
+ dot: 'bg-[var(--zen-status-warning)]',
30
+ },
31
+ emerald: {
32
+ pill: 'bg-[color-mix(in_srgb,var(--zen-status-success)_15%,transparent)] text-[var(--zen-status-success)] border-[color-mix(in_srgb,var(--zen-status-success)_45%,transparent)]',
33
+ dot: 'bg-[var(--zen-status-success)]',
34
+ },
35
+ coral: {
36
+ pill: 'bg-[color-mix(in_srgb,var(--zen-status-danger)_15%,transparent)] text-[var(--zen-status-danger)] border-[color-mix(in_srgb,var(--zen-status-danger)_45%,transparent)]',
37
+ dot: 'bg-[var(--zen-status-danger)]',
38
+ },
39
+ cyan: {
40
+ pill: 'bg-[color-mix(in_srgb,var(--zen-status-info)_15%,transparent)] text-[var(--zen-status-info)] border-[color-mix(in_srgb,var(--zen-status-info)_45%,transparent)]',
41
+ dot: 'bg-[var(--zen-status-info)]',
42
+ },
43
+ neutral: {
44
+ pill: 'bg-[var(--zen-surface-subtle)] text-[var(--zen-ink-secondary)] border-[var(--zen-border-default)]',
45
+ dot: 'bg-[var(--zen-ink-muted)]',
46
+ },
47
+ };
48
+
49
+ export function ZenBadge({
50
+ tone = 'neutral',
51
+ pulse = false,
52
+ icon,
53
+ children,
54
+ className = '',
55
+ }: ZenBadgeProps) {
56
+ const style = TONE_STYLES[tone];
57
+ return (
58
+ <span
59
+ className={`inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full border zen-font-mono text-[11px] font-medium tracking-wide ${style.pill} ${className}`.trim()}
60
+ >
61
+ {pulse && (
62
+ <span
63
+ className={`w-1.5 h-1.5 rounded-full animate-pulse ${style.dot}`}
64
+ aria-hidden="true"
65
+ />
66
+ )}
67
+ {icon && <span className="shrink-0 inline-flex">{icon}</span>}
68
+ <span>{children}</span>
69
+ </span>
70
+ );
71
+ }
72
+
73
+ export interface ZenTagListProps {
74
+ readonly tags: readonly string[];
75
+ readonly className?: string;
76
+ }
77
+
78
+ export function ZenTagList({ tags, className = '' }: ZenTagListProps) {
79
+ return (
80
+ <div className={`flex flex-wrap gap-2 ${className}`.trim()}>
81
+ {tags.map((tag) => (
82
+ <span
83
+ key={tag}
84
+ className="px-3 py-1 rounded-full border border-[var(--zen-border-default)] bg-[var(--zen-surface-subtle)] zen-font-mono text-[11px] text-[var(--zen-ink-secondary)]"
85
+ >
86
+ {tag}
87
+ </span>
88
+ ))}
89
+ </div>
90
+ );
91
+ }
92
+
93
+ export interface ZenKbdProps {
94
+ readonly keys: readonly string[];
95
+ readonly className?: string;
96
+ }
97
+
98
+ export function ZenKbd({ keys, className = '' }: ZenKbdProps) {
99
+ return (
100
+ <span className={`inline-flex items-center gap-1 ${className}`.trim()}>
101
+ {keys.map((k) => (
102
+ <kbd
103
+ key={k}
104
+ className="px-1.5 py-0.5 rounded-md border border-[var(--zen-border-default)] bg-[var(--zen-surface-subtle)] zen-font-mono text-[11px] text-[var(--zen-ink-secondary)] shadow-xs"
105
+ >
106
+ {k}
107
+ </kbd>
108
+ ))}
109
+ </span>
110
+ );
111
+ }
@@ -0,0 +1,127 @@
1
+ import { useState, useRef, useEffect, type ButtonHTMLAttributes, type ReactNode } from 'react';
2
+ import { Check, Copy, Loader2 } from 'lucide-react';
3
+
4
+ export type ZenButtonVariant = 'primary' | 'secondary' | 'ghost' | 'danger';
5
+ export type ZenButtonSize = 'sm' | 'md' | 'lg';
6
+
7
+ export interface ZenButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
8
+ readonly variant?: ZenButtonVariant;
9
+ readonly size?: ZenButtonSize;
10
+ readonly pressed?: boolean;
11
+ readonly mono?: boolean;
12
+ readonly loading?: boolean;
13
+ readonly iconLeft?: ReactNode;
14
+ readonly iconRight?: ReactNode;
15
+ readonly children: ReactNode;
16
+ }
17
+
18
+ const SIZE_CLASSES: Record<ZenButtonSize, string> = {
19
+ sm: 'px-3.5 py-1.5 text-xs',
20
+ md: 'px-5 py-2.5 text-xs sm:text-sm',
21
+ lg: 'px-6 py-3.5 text-sm',
22
+ };
23
+
24
+ const VARIANT_CLASSES: Record<ZenButtonVariant, string> = {
25
+ primary: 'zen-btn-primary',
26
+ secondary: 'zen-btn-secondary',
27
+ ghost: 'zen-btn-ghost',
28
+ danger: 'zen-btn-danger',
29
+ };
30
+
31
+ export function ZenButton({
32
+ variant = 'primary',
33
+ size = 'md',
34
+ pressed,
35
+ mono = false,
36
+ loading = false,
37
+ iconLeft,
38
+ iconRight,
39
+ disabled,
40
+ className = '',
41
+ children,
42
+ ...rest
43
+ }: ZenButtonProps) {
44
+ const isPressed = pressed === true;
45
+ const pressedClasses =
46
+ isPressed && variant !== 'primary'
47
+ ? 'bg-[var(--zen-accent-primary)] text-[var(--zen-ink-on-accent)] border-[var(--zen-accent-primary)] shadow-[var(--zen-shadow-btn-primary)]'
48
+ : '';
49
+
50
+ return (
51
+ <button
52
+ type="button"
53
+ data-zen-occlude="true"
54
+ aria-pressed={pressed}
55
+ disabled={disabled || loading}
56
+ className={`zen-btn-base zen-focus-ring ${VARIANT_CLASSES[variant]} ${SIZE_CLASSES[size]} ${
57
+ mono ? 'zen-font-mono' : 'zen-font-sans'
58
+ } ${pressedClasses} ${className}`.trim()}
59
+ {...rest}
60
+ >
61
+ {loading ? (
62
+ <Loader2 className="w-4 h-4 animate-spin shrink-0" />
63
+ ) : (
64
+ iconLeft && <span className="shrink-0 inline-flex">{iconLeft}</span>
65
+ )}
66
+ <span>{children}</span>
67
+ {iconRight && <span className="shrink-0 inline-flex">{iconRight}</span>}
68
+ </button>
69
+ );
70
+ }
71
+
72
+ export interface ZenCopyButtonProps {
73
+ readonly value: string;
74
+ readonly label?: string;
75
+ readonly copiedLabel?: string;
76
+ readonly size?: ZenButtonSize;
77
+ readonly variant?: ZenButtonVariant;
78
+ readonly className?: string;
79
+ }
80
+
81
+ export function ZenCopyButton({
82
+ value,
83
+ label = 'COPY',
84
+ copiedLabel = 'COPIED',
85
+ size = 'md',
86
+ variant = 'secondary',
87
+ className = '',
88
+ }: ZenCopyButtonProps) {
89
+ const [copied, setCopied] = useState(false);
90
+ const timeoutRef = useRef<number | null>(null);
91
+
92
+ useEffect(() => {
93
+ return () => {
94
+ if (timeoutRef.current !== null) {
95
+ window.clearTimeout(timeoutRef.current);
96
+ }
97
+ };
98
+ }, []);
99
+
100
+ const handleCopy = () => {
101
+ void navigator.clipboard.writeText(value);
102
+ setCopied(true);
103
+ if (timeoutRef.current !== null) {
104
+ window.clearTimeout(timeoutRef.current);
105
+ }
106
+ timeoutRef.current = window.setTimeout(() => setCopied(false), 2000);
107
+ };
108
+
109
+ return (
110
+ <ZenButton
111
+ variant={variant}
112
+ size={size}
113
+ mono
114
+ onClick={handleCopy}
115
+ className={className}
116
+ iconLeft={
117
+ copied ? (
118
+ <Check className="w-3.5 h-3.5 text-[var(--zen-accent-primary)]" />
119
+ ) : (
120
+ <Copy className="w-3.5 h-3.5 text-[var(--zen-accent-primary)]" />
121
+ )
122
+ }
123
+ >
124
+ {copied ? copiedLabel : label}
125
+ </ZenButton>
126
+ );
127
+ }
@@ -0,0 +1,180 @@
1
+ import type { HTMLAttributes, KeyboardEvent, ReactNode } from 'react';
2
+ import { CheckCircle2 } from 'lucide-react';
3
+
4
+ export type ZenCardVariant = 'static' | 'interactive' | 'selected';
5
+ export type ZenCardPadding = 'none' | 'sm' | 'md' | 'lg';
6
+
7
+ export interface ZenCardProps extends HTMLAttributes<HTMLDivElement> {
8
+ readonly variant?: ZenCardVariant;
9
+ readonly selected?: boolean;
10
+ readonly disabled?: boolean;
11
+ readonly padding?: ZenCardPadding;
12
+ readonly onSelect?: () => void;
13
+ readonly selectOnHover?: boolean;
14
+ readonly children: ReactNode;
15
+ }
16
+
17
+ const PADDING_MAP: Record<ZenCardPadding, string> = {
18
+ none: 'p-0',
19
+ sm: 'p-3.5 sm:p-5',
20
+ md: 'p-4 sm:p-6 lg:p-8',
21
+ lg: 'p-5 sm:p-8 lg:p-12',
22
+ };
23
+
24
+ export function ZenCard({
25
+ variant = 'static',
26
+ selected = false,
27
+ disabled = false,
28
+ padding = 'md',
29
+ onSelect,
30
+ selectOnHover = false,
31
+ className = '',
32
+ children,
33
+ onClick,
34
+ onMouseEnter,
35
+ onKeyDown,
36
+ ...rest
37
+ }: ZenCardProps) {
38
+ const isInteractive = variant === 'interactive' || Boolean(onSelect);
39
+ const isSelected = selected || variant === 'selected';
40
+
41
+ const surfaceClass = isInteractive
42
+ ? 'zen-surface-interactive zen-focus-ring cursor-pointer'
43
+ : isSelected
44
+ ? 'zen-surface-selected'
45
+ : 'zen-surface-static';
46
+
47
+ const handleKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {
48
+ onKeyDown?.(e);
49
+ if (!disabled && isInteractive && (e.key === 'Enter' || e.key === ' ')) {
50
+ e.preventDefault();
51
+ onSelect?.();
52
+ }
53
+ };
54
+
55
+ return (
56
+ <div
57
+ data-zen-occlude="true"
58
+ data-selected={isSelected ? 'true' : 'false'}
59
+ data-disabled={disabled ? 'true' : 'false'}
60
+ role={isInteractive ? 'button' : undefined}
61
+ aria-pressed={isInteractive ? isSelected : undefined}
62
+ aria-disabled={disabled ? true : undefined}
63
+ tabIndex={isInteractive && !disabled ? 0 : undefined}
64
+ onClick={(e) => {
65
+ onClick?.(e);
66
+ if (!disabled) {
67
+ onSelect?.();
68
+ }
69
+ }}
70
+ onMouseEnter={(e) => {
71
+ onMouseEnter?.(e);
72
+ if (!disabled && selectOnHover) {
73
+ onSelect?.();
74
+ }
75
+ }}
76
+ onKeyDown={handleKeyDown}
77
+ className={`rounded-3xl ${surfaceClass} ${PADDING_MAP[padding]} ${
78
+ disabled ? 'opacity-50 pointer-events-none' : ''
79
+ } ${className}`.trim()}
80
+ {...rest}
81
+ >
82
+ {children}
83
+ </div>
84
+ );
85
+ }
86
+
87
+ export interface ZenMetricItem {
88
+ readonly value: string;
89
+ readonly label: string;
90
+ readonly accent?: boolean;
91
+ }
92
+
93
+ export interface ZenMetricStripProps {
94
+ readonly metrics: readonly ZenMetricItem[];
95
+ readonly columns?: 2 | 3 | 4;
96
+ readonly className?: string;
97
+ }
98
+
99
+ export function ZenMetricStrip({
100
+ metrics,
101
+ columns = 4,
102
+ className = '',
103
+ }: ZenMetricStripProps) {
104
+ const colClass =
105
+ columns === 2
106
+ ? 'grid-cols-2'
107
+ : columns === 3
108
+ ? 'grid-cols-2 sm:grid-cols-3'
109
+ : 'grid-cols-2 sm:grid-cols-4';
110
+
111
+ return (
112
+ <div
113
+ className={`grid ${colClass} gap-2.5 p-3 rounded-2xl bg-[var(--zen-surface-subtle)] border border-[var(--zen-border-subtle)] text-center ${className}`.trim()}
114
+ >
115
+ {metrics.map((m) => (
116
+ <div key={m.label}>
117
+ <div
118
+ className={`zen-font-display font-bold text-xs sm:text-sm ${
119
+ m.accent
120
+ ? 'text-[var(--zen-accent-primary)]'
121
+ : 'text-[var(--zen-ink-primary)]'
122
+ }`}
123
+ >
124
+ {m.value}
125
+ </div>
126
+ <div className="zen-telemetry-label mt-0.5">{m.label}</div>
127
+ </div>
128
+ ))}
129
+ </div>
130
+ );
131
+ }
132
+
133
+ export interface ZenArchitectureNoteProps {
134
+ readonly label?: string;
135
+ readonly children: ReactNode;
136
+ readonly className?: string;
137
+ }
138
+
139
+ export function ZenArchitectureNote({
140
+ label = 'SYSTEM ARCHITECTURE NOTE',
141
+ children,
142
+ className = '',
143
+ }: ZenArchitectureNoteProps) {
144
+ return (
145
+ <div
146
+ className={`p-4 rounded-2xl bg-[var(--zen-surface-subtle)] border border-[var(--zen-border-default)] ${className}`.trim()}
147
+ >
148
+ <div className="zen-telemetry-label text-[var(--zen-accent-secondary)] mb-1">
149
+ {label}
150
+ </div>
151
+ <p className="text-sm leading-relaxed text-[var(--zen-ink-secondary)]">
152
+ {children}
153
+ </p>
154
+ </div>
155
+ );
156
+ }
157
+
158
+ export interface ZenCheckListProps {
159
+ readonly items: readonly string[];
160
+ readonly className?: string;
161
+ }
162
+
163
+ export function ZenCheckList({ items, className = '' }: ZenCheckListProps) {
164
+ return (
165
+ <ul className={`space-y-2 ${className}`.trim()}>
166
+ {items.map((item) => (
167
+ <li
168
+ key={item}
169
+ className="flex items-start gap-2 text-xs sm:text-sm text-[var(--zen-ink-secondary)]"
170
+ >
171
+ <CheckCircle2
172
+ className="w-3.5 h-3.5 text-[var(--zen-accent-primary)] shrink-0 mt-0.5"
173
+ aria-hidden="true"
174
+ />
175
+ <span>{item}</span>
176
+ </li>
177
+ ))}
178
+ </ul>
179
+ );
180
+ }