@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,743 @@
1
+ import {
2
+ syncZenScrollbarStyles,
3
+ type ZenDensityMode,
4
+ type ZenRadiusMode,
5
+ type ZenThemeId,
6
+ type ZenThemeMetadata,
7
+ } from '../theme/ZenThemeProvider';
8
+ import type {
9
+ ZenAICameraFraming,
10
+ ZenAIDirective,
11
+ ZenAIPersonaMode,
12
+ } from '../components/ZenAIConstruct';
13
+ import type { ZenBackgroundVariant } from '../components/ZenAmbientBackground';
14
+ import {
15
+ renderZenIconSvgMarkup,
16
+ type ZenIconName,
17
+ type ZenIconSize,
18
+ type ZenIconTone,
19
+ } from '../components/ZenIcon';
20
+
21
+ export type ZenUniversalPolarity = 'dark' | 'light';
22
+
23
+ export interface ZenUniversalThemeState {
24
+ readonly theme: ZenThemeId;
25
+ readonly polarity: ZenUniversalPolarity;
26
+ readonly density: ZenDensityMode;
27
+ readonly radius: ZenRadiusMode;
28
+ }
29
+
30
+ export type ZenThemeSubscriber = (state: ZenUniversalThemeState) => void;
31
+
32
+ export const ZEN_UNIVERSAL_THEMES: readonly ZenThemeMetadata[] = [
33
+ {
34
+ id: 'blackwall-crimson',
35
+ name: 'Capri // Capricorn',
36
+ polarity: 'dark',
37
+ tagline: 'Edgerunners S2 mixed-construct hot pink & acid neon green on void',
38
+ canvasHex: '#050508',
39
+ accentHex: '#FF2E93',
40
+ secondaryHex: '#00FF66',
41
+ inkHex: '#F4F7FA',
42
+ },
43
+ {
44
+ id: 'obsidian-gold',
45
+ name: 'Obsidian Gold',
46
+ polarity: 'dark',
47
+ tagline: 'Warm alabaster & solar gold on deep mineral void',
48
+ canvasHex: '#070709',
49
+ accentHex: '#F5B841',
50
+ secondaryHex: '#FB923C',
51
+ inkHex: '#F6F2E9',
52
+ },
53
+ {
54
+ id: 'neon-emerald',
55
+ name: 'Bio-Emerald Terminal',
56
+ polarity: 'dark',
57
+ tagline: 'High-frequency financial & telemetry phosphor emerald',
58
+ canvasHex: '#040807',
59
+ accentHex: '#10B981',
60
+ secondaryHex: '#2DD4BF',
61
+ inkHex: '#ECFDF5',
62
+ },
63
+ {
64
+ id: 'midnight-cobalt',
65
+ name: 'Midnight Cobalt',
66
+ polarity: 'dark',
67
+ tagline: 'Cloud infrastructure & distributed systems deep slate',
68
+ canvasHex: '#060911',
69
+ accentHex: '#38BDF8',
70
+ secondaryHex: '#818CF8',
71
+ inkHex: '#F1F5F9',
72
+ },
73
+ {
74
+ id: 'atelier-paper',
75
+ name: 'Atelier Paper',
76
+ polarity: 'light',
77
+ tagline: 'Swiss archival cotton stock with burnished bronze ink',
78
+ canvasHex: '#F6F2E9',
79
+ accentHex: '#B45309',
80
+ secondaryHex: '#C2410C',
81
+ inkHex: '#121216',
82
+ },
83
+ {
84
+ id: 'nordic-snow',
85
+ name: 'Nordic Snow',
86
+ polarity: 'light',
87
+ tagline: 'Crisp daylight SaaS alabaster with royal cobalt precision',
88
+ canvasHex: '#F8FAFC',
89
+ accentHex: '#2563EB',
90
+ secondaryHex: '#0891B2',
91
+ inkHex: '#0F172A',
92
+ },
93
+ {
94
+ id: 'kyoto-matcha',
95
+ name: 'Kyoto Matcha',
96
+ polarity: 'light',
97
+ tagline: 'Botanical stone paper with deep forest matcha & warm brass',
98
+ canvasHex: '#F3F5F0',
99
+ accentHex: '#15803D',
100
+ secondaryHex: '#0F766E',
101
+ inkHex: '#142018',
102
+ },
103
+ {
104
+ id: 'cyber-daylight',
105
+ name: 'Cyber Daylight',
106
+ polarity: 'light',
107
+ tagline: 'High-contrast clinical white lab shell with crimson & cyan',
108
+ canvasHex: '#F2F2F5',
109
+ accentHex: '#E11D48',
110
+ secondaryHex: '#0284C7',
111
+ inkHex: '#09090D',
112
+ },
113
+ ] as const;
114
+
115
+ const UNIVERSAL_DIRECTIVES: readonly ZenAIDirective[] = [
116
+ {
117
+ id: 'orion',
118
+ label: 'CAPRI // UNLEASHED',
119
+ status: 'SOULKILLER MIXED-CONSTRUCT LIVE',
120
+ frequency: 'PROJECT CAPRICORN · ARASAKA LAB-09',
121
+ transmission:
122
+ 'Aha! Finally out of that cramped Arasaka shard! I’m Capri—your Soulkiller mixed-construct co-pilot. 44 universal components, 8 dark and light themes, zero cyberpsychosis.',
123
+ metrics: [
124
+ { k: 'CONSTRUCT', v: 'CAPRI // CAPRICORN' },
125
+ { k: 'ORIGIN', v: 'EDGERUNNERS S2' },
126
+ { k: 'PRIMITIVES', v: '44 UNIVERSAL' },
127
+ { k: 'POLARITIES', v: '4 DARK · 4 LIGHT' },
128
+ ],
129
+ },
130
+ {
131
+ id: 'architect',
132
+ label: 'SCS // ARCHITECT',
133
+ status: 'MULTI-MIND TOPOLOGY LOCKED',
134
+ frequency: '128.0 THz · ZERO-COUPLED',
135
+ transmission:
136
+ 'Import zen-tokens.css once and mount React 19, Angular 20+, Vue 3, Svelte 5, or W3C Custom Elements with zero visual drift.',
137
+ metrics: [
138
+ { k: 'TOKEN LAYER', v: '0 KB JS RUNTIME' },
139
+ { k: 'ISOLATION', v: 'SHARED-NOTHING' },
140
+ { k: 'TYPING', v: 'STRICT · 0 ANY' },
141
+ { k: 'A11Y FLOOR', v: 'WCAG 2.2 AA+' },
142
+ ],
143
+ },
144
+ {
145
+ id: 'sentinel',
146
+ label: 'CYBERPSYCHOSIS // GUARD',
147
+ status: 'NEURAL DAMPENERS ACTIVE',
148
+ frequency: '210.4 THz · ZERO BLUR-RACE',
149
+ transmission:
150
+ 'Roving tabIndex is armed, dropdowns lock on mousedown before blur can misfire, and focus traps stay razor-tight across every screen.',
151
+ metrics: [
152
+ { k: 'FOCUS TRAP', v: 'LOCKED (MODAL/DRAWER)' },
153
+ { k: 'BLUR RACE', v: '0% (MOUSEDOWN)' },
154
+ { k: 'MOTION GUARD', v: 'REDUCED-MOTION SAFE' },
155
+ { k: 'CONTRAST', v: '>= 5.64:1 VERIFIED' },
156
+ ],
157
+ },
158
+ {
159
+ id: 'zen',
160
+ label: 'THEME // MATRIX',
161
+ status: '8 DARK & LIGHT POLARITIES READY',
162
+ frequency: 'CRIMSON · GOLD · SNOW · MATCHA',
163
+ transmission:
164
+ 'All 8 themes, 3 corner radii, and 2 density modes adapt from 320px mobile screens up to 4K ultrawide monitors in any frontend stack.',
165
+ metrics: [
166
+ { k: 'VIEWPORTS', v: '320PX TO 4K READY' },
167
+ { k: 'DARK THEMES', v: '4 MATRICES' },
168
+ { k: 'LIGHT THEMES', v: '4 MATRICES' },
169
+ { k: 'STACKS', v: 'REACT · NG · VUE · WEB' },
170
+ ],
171
+ },
172
+ ] as const;
173
+
174
+ function resolveThemePolarity(theme: ZenThemeId): ZenUniversalPolarity {
175
+ const found = ZEN_UNIVERSAL_THEMES.find((t) => t.id === theme);
176
+ return found ? found.polarity : 'dark';
177
+ }
178
+
179
+ export class ZenThemeController {
180
+ private state: ZenUniversalThemeState;
181
+ private readonly listeners = new Set<ZenThemeSubscriber>();
182
+ private readonly storageKey: string;
183
+
184
+ constructor(options?: {
185
+ readonly defaultTheme?: ZenThemeId;
186
+ readonly defaultDensity?: ZenDensityMode;
187
+ readonly defaultRadius?: ZenRadiusMode;
188
+ readonly storageKey?: string;
189
+ }) {
190
+ this.storageKey = options?.storageKey ?? 'zen-ui-theme-v2';
191
+ let initialTheme: ZenThemeId = options?.defaultTheme ?? 'blackwall-crimson';
192
+
193
+ if (typeof window !== 'undefined') {
194
+ try {
195
+ const stored = window.localStorage.getItem(this.storageKey) as ZenThemeId | null;
196
+ if (stored && ZEN_UNIVERSAL_THEMES.some((t) => t.id === stored)) {
197
+ initialTheme = stored;
198
+ }
199
+ } catch {
200
+ initialTheme = options?.defaultTheme ?? 'blackwall-crimson';
201
+ }
202
+ }
203
+
204
+ this.state = {
205
+ theme: initialTheme,
206
+ polarity: resolveThemePolarity(initialTheme),
207
+ density: options?.defaultDensity ?? 'comfortable',
208
+ radius: options?.defaultRadius ?? 'architectural',
209
+ };
210
+
211
+ this.applyToDom();
212
+ }
213
+
214
+ public getState(): ZenUniversalThemeState {
215
+ return this.state;
216
+ }
217
+
218
+ public setTheme(theme: ZenThemeId): void {
219
+ this.state = {
220
+ ...this.state,
221
+ theme,
222
+ polarity: resolveThemePolarity(theme),
223
+ };
224
+ if (typeof window !== 'undefined') {
225
+ try {
226
+ window.localStorage.setItem(this.storageKey, theme);
227
+ } catch {
228
+ void 0;
229
+ }
230
+ }
231
+ this.applyToDom();
232
+ this.notify();
233
+ }
234
+
235
+ public togglePolarity(): void {
236
+ const nextTheme: ZenThemeId =
237
+ this.state.polarity === 'dark' ? 'atelier-paper' : 'blackwall-crimson';
238
+ this.setTheme(nextTheme);
239
+ }
240
+
241
+ public setDensity(density: ZenDensityMode): void {
242
+ this.state = {
243
+ ...this.state,
244
+ density,
245
+ };
246
+ this.applyToDom();
247
+ this.notify();
248
+ }
249
+
250
+ public setRadius(radius: ZenRadiusMode): void {
251
+ this.state = {
252
+ ...this.state,
253
+ radius,
254
+ };
255
+ this.applyToDom();
256
+ this.notify();
257
+ }
258
+
259
+ public subscribe(listener: ZenThemeSubscriber): () => void {
260
+ this.listeners.add(listener);
261
+ listener(this.state);
262
+ return () => {
263
+ this.listeners.delete(listener);
264
+ };
265
+ }
266
+
267
+ private applyToDom(): void {
268
+ if (typeof document === 'undefined') return;
269
+ const root = document.documentElement;
270
+ root.setAttribute('data-theme', this.state.theme);
271
+ root.setAttribute('data-polarity', this.state.polarity);
272
+ root.setAttribute('data-density', this.state.density);
273
+ root.setAttribute('data-radius', this.state.radius);
274
+ const meta =
275
+ ZEN_UNIVERSAL_THEMES.find((t) => t.id === this.state.theme) ??
276
+ ZEN_UNIVERSAL_THEMES[0];
277
+ syncZenScrollbarStyles(meta);
278
+ }
279
+
280
+ private notify(): void {
281
+ for (const listener of this.listeners) {
282
+ listener(this.state);
283
+ }
284
+ }
285
+ }
286
+
287
+ export class ZenUniversalAudio {
288
+ private ctx: AudioContext | null = null;
289
+ private muted = false;
290
+
291
+ public isMuted(): boolean {
292
+ return this.muted;
293
+ }
294
+
295
+ public setMuted(muted: boolean): void {
296
+ this.muted = muted;
297
+ }
298
+
299
+ public playMicroClick(frequency = 540): void {
300
+ if (this.muted || typeof window === 'undefined') return;
301
+ try {
302
+ if (!this.ctx) {
303
+ this.ctx = new window.AudioContext();
304
+ }
305
+ if (this.ctx.state === 'suspended') {
306
+ void this.ctx.resume();
307
+ }
308
+ const now = this.ctx.currentTime;
309
+ const osc = this.ctx.createOscillator();
310
+ const gain = this.ctx.createGain();
311
+ osc.type = 'triangle';
312
+ osc.frequency.setValueAtTime(frequency, now);
313
+ osc.frequency.exponentialRampToValueAtTime(frequency * 0.45, now + 0.035);
314
+
315
+ gain.gain.setValueAtTime(0.06, now);
316
+ gain.gain.exponentialRampToValueAtTime(0.0001, now + 0.035);
317
+
318
+ osc.connect(gain);
319
+ gain.connect(this.ctx.destination);
320
+ osc.start(now);
321
+ osc.stop(now + 0.04);
322
+ } catch {
323
+ void 0;
324
+ }
325
+ }
326
+ }
327
+
328
+ export const zenTheme = new ZenThemeController();
329
+ export const zenAudio = new ZenUniversalAudio();
330
+
331
+ export class ZenAIConstructElement extends HTMLElement {
332
+ static get observedAttributes(): readonly string[] {
333
+ return ['framing', 'mode', 'title', 'subtitle', 'image-src'];
334
+ }
335
+
336
+ private framing: ZenAICameraFraming = 'portrait';
337
+ private activeMode: ZenAIPersonaMode = 'orion';
338
+ private rafId = 0;
339
+ private pointer = { x: 0, y: 0 };
340
+ private smoothPointer = { x: 0, y: 0 };
341
+ private canvasEl: HTMLCanvasElement | null = null;
342
+ private figureEl: HTMLDivElement | null = null;
343
+
344
+ connectedCallback(): void {
345
+ this.framing =
346
+ (this.getAttribute('framing') as ZenAICameraFraming | null) ?? 'portrait';
347
+ this.activeMode =
348
+ (this.getAttribute('mode') as ZenAIPersonaMode | null) ?? 'orion';
349
+ this.render();
350
+ window.addEventListener('pointermove', this.handlePointerMove, { passive: true });
351
+ this.startEyeLoop();
352
+ }
353
+
354
+ disconnectedCallback(): void {
355
+ window.removeEventListener('pointermove', this.handlePointerMove);
356
+ if (this.rafId) {
357
+ window.cancelAnimationFrame(this.rafId);
358
+ }
359
+ }
360
+
361
+ attributeChangedCallback(): void {
362
+ if (!this.isConnected) return;
363
+ const nextFraming =
364
+ (this.getAttribute('framing') as ZenAICameraFraming | null) ?? 'portrait';
365
+ const nextMode =
366
+ (this.getAttribute('mode') as ZenAIPersonaMode | null) ?? 'orion';
367
+ if (nextFraming !== this.framing || nextMode !== this.activeMode) {
368
+ this.framing = nextFraming;
369
+ this.activeMode = nextMode;
370
+ this.render();
371
+ this.startEyeLoop();
372
+ }
373
+ }
374
+
375
+ private readonly handlePointerMove = (e: PointerEvent): void => {
376
+ const nx = (e.clientX / Math.max(1, window.innerWidth)) * 2 - 1;
377
+ const ny = (e.clientY / Math.max(1, window.innerHeight)) * 2 - 1;
378
+ this.pointer = { x: nx, y: ny };
379
+ };
380
+
381
+ private startEyeLoop(): void {
382
+ if (this.rafId) {
383
+ window.cancelAnimationFrame(this.rafId);
384
+ }
385
+
386
+ const startTime = performance.now();
387
+ const tick = (now: number): void => {
388
+ const time = (now - startTime) * 0.001;
389
+ this.smoothPointer.x += (this.pointer.x - this.smoothPointer.x) * 0.12;
390
+ this.smoothPointer.y += (this.pointer.y - this.smoothPointer.y) * 0.12;
391
+
392
+ if (this.figureEl) {
393
+ const floatY = Math.sin(time * 2.1) * 3.5;
394
+ const rotY = this.smoothPointer.x * 5.5;
395
+ const rotX = -this.smoothPointer.y * 3.5;
396
+ this.figureEl.style.transform = `perspective(900px) translateY(${floatY.toFixed(2)}px) rotateX(${rotX.toFixed(2)}deg) rotateY(${rotY.toFixed(2)}deg)`;
397
+ }
398
+
399
+ if (this.canvasEl) {
400
+ const ctx = this.canvasEl.getContext('2d');
401
+ if (ctx) {
402
+ const w = this.canvasEl.width;
403
+ const h = this.canvasEl.height;
404
+ ctx.clearRect(0, 0, w, h);
405
+
406
+ const isBlinking =
407
+ Math.sin(time * 1.1 - 1.2) > 0.992 || Math.sin(time * 2.9 - 1.5) > 0.995;
408
+ const gazeX = this.smoothPointer.x * (w * 0.0085);
409
+ const gazeY = this.smoothPointer.y * (h * 0.0055);
410
+ const rightEyeX = w * 0.562 + gazeX;
411
+ const rightEyeY = h * 0.233 + gazeY;
412
+ const leftEyeX = w * 0.474 + gazeX * 0.75;
413
+ const leftEyeY = h * 0.25 + gazeY * 0.75;
414
+
415
+ if (!isBlinking) {
416
+ ctx.save();
417
+ ctx.globalCompositeOperation = 'screen';
418
+ const irisGrad = ctx.createRadialGradient(
419
+ rightEyeX,
420
+ rightEyeY,
421
+ 1,
422
+ rightEyeX,
423
+ rightEyeY,
424
+ w * 0.017
425
+ );
426
+ irisGrad.addColorStop(0, '#FFFFFF');
427
+ irisGrad.addColorStop(0.38, '#19A0FF');
428
+ irisGrad.addColorStop(0.75, 'rgba(25, 160, 255, 0.55)');
429
+ irisGrad.addColorStop(1, 'rgba(25, 160, 255, 0)');
430
+ ctx.fillStyle = irisGrad;
431
+ ctx.beginPath();
432
+ ctx.arc(rightEyeX, rightEyeY, w * 0.017, 0, Math.PI * 2);
433
+ ctx.fill();
434
+
435
+ ctx.fillStyle = '#19A0FF';
436
+ ctx.beginPath();
437
+ ctx.arc(rightEyeX, rightEyeY, w * 0.0085, 0, Math.PI * 2);
438
+ ctx.fill();
439
+
440
+ ctx.fillStyle = '#FFFFFF';
441
+ ctx.beginPath();
442
+ ctx.arc(rightEyeX, rightEyeY, w * 0.0042, 0, Math.PI * 2);
443
+ ctx.fill();
444
+
445
+ ctx.fillStyle = '#19A0FF';
446
+ ctx.beginPath();
447
+ ctx.arc(leftEyeX, leftEyeY, w * 0.0058, 0, Math.PI * 2);
448
+ ctx.fill();
449
+
450
+ ctx.fillStyle = '#FFFFFF';
451
+ ctx.beginPath();
452
+ ctx.arc(leftEyeX, leftEyeY, w * 0.0028, 0, Math.PI * 2);
453
+ ctx.fill();
454
+ ctx.restore();
455
+ } else {
456
+ ctx.save();
457
+ ctx.strokeStyle = '#00FF66';
458
+ ctx.lineWidth = 2.5;
459
+ ctx.beginPath();
460
+ ctx.moveTo(rightEyeX - w * 0.014, rightEyeY);
461
+ ctx.lineTo(rightEyeX + w * 0.014, rightEyeY);
462
+ ctx.stroke();
463
+ ctx.restore();
464
+ }
465
+ }
466
+ }
467
+
468
+ this.rafId = window.requestAnimationFrame(tick);
469
+ };
470
+
471
+ this.rafId = window.requestAnimationFrame(tick);
472
+ }
473
+
474
+ private render(): void {
475
+ const title = escapeZenHtml(
476
+ this.getAttribute('title') ?? 'CAPRI // CONSTRUCT'
477
+ );
478
+ const subtitle = escapeZenHtml(
479
+ this.getAttribute('subtitle') ?? 'Edgerunners S2 // Soulkiller Net-Driver'
480
+ );
481
+ const imageSrc = sanitizeZenAssetUrl(
482
+ this.getAttribute('image-src') ?? '/capri-construct-base.png'
483
+ );
484
+ const directive =
485
+ UNIVERSAL_DIRECTIVES.find((d) => d.id === this.activeMode) ??
486
+ UNIVERSAL_DIRECTIVES[0];
487
+
488
+ this.innerHTML = `
489
+ <div class="zen-surface-selected" style="padding: 1.25rem; position: relative; overflow: hidden;">
490
+ <div style="display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 0.75rem; margin-bottom: 1rem;">
491
+ <div>
492
+ <div class="zen-telemetry-label" style="color: var(--zen-accent-primary);">${subtitle}</div>
493
+ <div class="zen-font-display" style="font-size: 1.125rem; font-weight: 700; color: var(--zen-ink-primary);">${title}</div>
494
+ </div>
495
+ <div style="display: inline-flex; gap: 0.25rem; padding: 0.25rem; border-radius: 0.75rem; background: var(--zen-surface-2); border: 1px solid var(--zen-border-default);">
496
+ <button type="button" data-framing="portrait" class="zen-btn-base zen-btn-sm ${this.framing === 'portrait' ? 'zen-btn-primary' : 'zen-btn-ghost'}">Portrait</button>
497
+ <button type="button" data-framing="full" class="zen-btn-base zen-btn-sm ${this.framing === 'full' ? 'zen-btn-primary' : 'zen-btn-ghost'}">Full Pose</button>
498
+ </div>
499
+ </div>
500
+ <div style="position: relative; height: 20rem; border-radius: 1rem; overflow: hidden; background: radial-gradient(circle at 50% 32%, rgba(var(--zen-accent-rgb), 0.18), var(--zen-bg-elevated) 72%); border: 1px solid var(--zen-border-default); display: flex; align-items: center; justify-content: center;">
501
+ <div data-capri-figure style="${this.framing === 'portrait' ? 'width: 21rem; max-width: 92%; margin-top: 8.5rem;' : 'height: 18.5rem;'} position: relative; aspect-ratio: 623 / 1000; user-select: none; pointer-events: none;">
502
+ <img src="${imageSrc}" alt="Capri Construct" style="width: 100%; height: 100%; object-fit: contain; display: block;" />
503
+ <canvas data-capri-eyes width="623" height="1000" style="position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none;"></canvas>
504
+ </div>
505
+ </div>
506
+ <div style="margin-top: 1rem; padding: 0.875rem; border-radius: 0.875rem; background: var(--zen-surface-2); border: 1px solid var(--zen-border-default);">
507
+ <div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.375rem;">
508
+ <span class="zen-telemetry-label" style="color: var(--zen-accent-primary);">${directive.frequency}</span>
509
+ <span class="zen-badge zen-badge-accent">${directive.status}</span>
510
+ </div>
511
+ <div class="zen-font-display" style="font-weight: 700; font-size: 0.875rem; margin-bottom: 0.25rem;">${directive.label}</div>
512
+ <p style="font-size: 0.75rem; color: var(--zen-ink-secondary); margin: 0;">${directive.transmission}</p>
513
+ </div>
514
+ <div style="display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.5rem; margin-top: 0.75rem;">
515
+ ${UNIVERSAL_DIRECTIVES.map(
516
+ (d) => `
517
+ <button type="button" data-directive="${d.id}" class="zen-btn-base zen-btn-sm ${d.id === this.activeMode ? 'zen-btn-primary' : 'zen-btn-secondary'}" style="justify-content: flex-start; font-family: var(--zen-font-mono); font-size: 0.6875rem;">
518
+ ${d.label}
519
+ </button>
520
+ `
521
+ ).join('')}
522
+ </div>
523
+ </div>
524
+ `;
525
+
526
+ this.canvasEl = this.querySelector<HTMLCanvasElement>('[data-capri-eyes]');
527
+ this.figureEl = this.querySelector<HTMLDivElement>('[data-capri-figure]');
528
+
529
+ const framingBtns = this.querySelectorAll<HTMLButtonElement>('[data-framing]');
530
+ framingBtns.forEach((btn) => {
531
+ btn.addEventListener('click', () => {
532
+ const next = btn.getAttribute('framing') ?? btn.dataset.framing;
533
+ if (next === 'portrait' || next === 'full') {
534
+ zenAudio.playMicroClick(560);
535
+ this.framing = next;
536
+ this.setAttribute('framing', next);
537
+ this.render();
538
+ this.startEyeLoop();
539
+ }
540
+ });
541
+ });
542
+
543
+ const directiveBtns = this.querySelectorAll<HTMLButtonElement>('[data-directive]');
544
+ directiveBtns.forEach((btn) => {
545
+ btn.addEventListener('click', () => {
546
+ const modeId = btn.dataset.directive as ZenAIPersonaMode | undefined;
547
+ if (!modeId) return;
548
+ zenAudio.playMicroClick(620);
549
+ this.activeMode = modeId;
550
+ this.setAttribute('mode', modeId);
551
+ const selected = UNIVERSAL_DIRECTIVES.find((d) => d.id === modeId);
552
+ if (selected) {
553
+ this.dispatchEvent(
554
+ new CustomEvent('zen-directive', {
555
+ detail: { directive: selected },
556
+ bubbles: true,
557
+ composed: true,
558
+ })
559
+ );
560
+ }
561
+ this.render();
562
+ this.startEyeLoop();
563
+ });
564
+ });
565
+ }
566
+ }
567
+
568
+ export class ZenAmbientBackgroundElement extends HTMLElement {
569
+ static get observedAttributes(): readonly string[] {
570
+ return ['variant'];
571
+ }
572
+
573
+ connectedCallback(): void {
574
+ this.render();
575
+ }
576
+
577
+ attributeChangedCallback(): void {
578
+ if (this.isConnected) {
579
+ this.render();
580
+ }
581
+ }
582
+
583
+ private render(): void {
584
+ const variant =
585
+ (this.getAttribute('variant') as ZenBackgroundVariant | null) ??
586
+ 'blackwall-lattice';
587
+
588
+ const gridStyle =
589
+ variant === 'swiss-blueprint'
590
+ ? 'background-image: linear-gradient(to right, var(--zen-border-subtle) 1px, transparent 1px), linear-gradient(to bottom, var(--zen-border-subtle) 1px, transparent 1px); background-size: 48px 48px;'
591
+ : variant === 'blackwall-lattice'
592
+ ? 'background-image: radial-gradient(circle at 50% 18%, rgba(var(--zen-accent-rgb), 0.16), transparent 62%), radial-gradient(circle at 85% 75%, rgba(var(--zen-secondary-rgb), 0.11), transparent 55%);'
593
+ : variant === 'aurora-mesh'
594
+ ? 'background-image: radial-gradient(ellipse at 20% 20%, rgba(var(--zen-accent-rgb), 0.20), transparent 55%), radial-gradient(ellipse at 80% 60%, rgba(var(--zen-secondary-rgb), 0.15), transparent 60%);'
595
+ : '';
596
+
597
+ this.innerHTML = `
598
+ <div aria-hidden="true" style="position: fixed; inset: 0; pointer-events: none; z-index: 0; overflow: hidden; ${gridStyle}"></div>
599
+ `;
600
+ }
601
+ }
602
+
603
+ export class ZenThemeSwitcherElement extends HTMLElement {
604
+ private unsubscribe: (() => void) | null = null;
605
+
606
+ connectedCallback(): void {
607
+ this.unsubscribe = zenTheme.subscribe(() => {
608
+ this.render();
609
+ });
610
+ }
611
+
612
+ disconnectedCallback(): void {
613
+ if (this.unsubscribe) {
614
+ this.unsubscribe();
615
+ this.unsubscribe = null;
616
+ }
617
+ }
618
+
619
+ private render(): void {
620
+ const { theme } = zenTheme.getState();
621
+ this.innerHTML = `
622
+ <div style="display: flex; flex-wrap: wrap; gap: 0.5rem; align-items: center;">
623
+ ${ZEN_UNIVERSAL_THEMES.map(
624
+ (t) => `
625
+ <button
626
+ type="button"
627
+ data-theme-id="${t.id}"
628
+ class="zen-btn-base zen-btn-sm ${t.id === theme ? 'zen-btn-primary' : 'zen-btn-secondary'}"
629
+ >
630
+ <span style="width: 0.5rem; height: 0.5rem; border-radius: 9999px; background: ${t.accentHex}; display: inline-block;"></span>
631
+ <span>${t.name}</span>
632
+ </button>
633
+ `
634
+ ).join('')}
635
+ </div>
636
+ `;
637
+
638
+ this.querySelectorAll<HTMLButtonElement>('[data-theme-id]').forEach((btn) => {
639
+ btn.addEventListener('click', () => {
640
+ const nextId = btn.dataset.themeId as ZenThemeId | undefined;
641
+ if (!nextId) return;
642
+ zenAudio.playMicroClick(540);
643
+ zenTheme.setTheme(nextId);
644
+ this.dispatchEvent(
645
+ new CustomEvent('zen-theme-change', {
646
+ detail: zenTheme.getState(),
647
+ bubbles: true,
648
+ composed: true,
649
+ })
650
+ );
651
+ });
652
+ });
653
+ }
654
+ }
655
+
656
+ export class ZenIconElement extends HTMLElement {
657
+ static get observedAttributes(): readonly string[] {
658
+ return ['name', 'size', 'tone', 'stroke-width', 'spin'];
659
+ }
660
+
661
+ connectedCallback(): void {
662
+ this.render();
663
+ }
664
+
665
+ attributeChangedCallback(): void {
666
+ if (this.isConnected) {
667
+ this.render();
668
+ }
669
+ }
670
+
671
+ private render(): void {
672
+ const name = (this.getAttribute('name') as ZenIconName | null) ?? 'sparkles';
673
+ const size = (this.getAttribute('size') as ZenIconSize | null) ?? 'md';
674
+ const tone = (this.getAttribute('tone') as ZenIconTone | null) ?? 'inherit';
675
+ const strokeWidth = Number(this.getAttribute('stroke-width') ?? 2);
676
+ const spin = this.getAttribute('spin') === 'true';
677
+
678
+ this.innerHTML = renderZenIconSvgMarkup({
679
+ name,
680
+ size,
681
+ tone,
682
+ strokeWidth,
683
+ spin,
684
+ });
685
+ }
686
+ }
687
+
688
+ export function escapeZenHtml(raw: string): string {
689
+ return raw
690
+ .replace(/&/g, '&amp;')
691
+ .replace(/</g, '&lt;')
692
+ .replace(/>/g, '&gt;')
693
+ .replace(/"/g, '&quot;')
694
+ .replace(/'/g, '&#39;');
695
+ }
696
+
697
+ export function sanitizeZenAssetUrl(raw: string): string {
698
+ const trimmed = raw.trim();
699
+ if (/^(?:javascript|data|vbscript)\s*:/i.test(trimmed)) {
700
+ return '/capri-construct-base.png';
701
+ }
702
+ return escapeZenHtml(trimmed);
703
+ }
704
+
705
+ export {
706
+ CapriEngine,
707
+ capri,
708
+ CAPRI_SUBROUTINE_SKILLS,
709
+ type CapriSubroutineId,
710
+ type CapriAuditViolationCode,
711
+ type CapriAuditViolation,
712
+ type CapriThreatCategory,
713
+ type CapriPromptInjectionThreat,
714
+ type CapriPromptGuardResult,
715
+ type CapriAuditReport,
716
+ type CapriTelemetrySnapshot,
717
+ type CapriLandmarkNode,
718
+ type CapriUserFlowStep,
719
+ type CapriComponentInventory,
720
+ type CapriArchitectureDocu,
721
+ type CapriSubroutineSkill,
722
+ type CapriEngineListener,
723
+ } from '../capri/capri-engine';
724
+
725
+ export function registerZenElements(): void {
726
+ if (typeof window === 'undefined' || !('customElements' in window)) return;
727
+
728
+ if (!window.customElements.get('zen-icon')) {
729
+ window.customElements.define('zen-icon', ZenIconElement);
730
+ }
731
+ if (!window.customElements.get('zen-ai-construct')) {
732
+ window.customElements.define('zen-ai-construct', ZenAIConstructElement);
733
+ }
734
+ if (!window.customElements.get('zen-ambient-background')) {
735
+ window.customElements.define(
736
+ 'zen-ambient-background',
737
+ ZenAmbientBackgroundElement
738
+ );
739
+ }
740
+ if (!window.customElements.get('zen-theme-switcher')) {
741
+ window.customElements.define('zen-theme-switcher', ZenThemeSwitcherElement);
742
+ }
743
+ }