@nukleas/cyberdesign 0.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 (208) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +38 -0
  3. package/build.ts +286 -0
  4. package/dist/cyberdesign-vars-only.css +116 -0
  5. package/dist/cyberdesign.css +8377 -0
  6. package/dist/fonts/JetBrainsMono-Variable.woff2 +0 -0
  7. package/dist/fonts/OFL.txt +93 -0
  8. package/dist/react/Accordion.d.ts +34 -0
  9. package/dist/react/Accordion.js +13 -0
  10. package/dist/react/Alert.d.ts +19 -0
  11. package/dist/react/Alert.js +6 -0
  12. package/dist/react/AugButton.d.ts +12 -0
  13. package/dist/react/AugButton.js +6 -0
  14. package/dist/react/AugPanel.d.ts +10 -0
  15. package/dist/react/AugPanel.js +6 -0
  16. package/dist/react/Avatar.d.ts +28 -0
  17. package/dist/react/Avatar.js +11 -0
  18. package/dist/react/Badge.d.ts +10 -0
  19. package/dist/react/Badge.js +6 -0
  20. package/dist/react/Breadcrumb.d.ts +19 -0
  21. package/dist/react/Breadcrumb.js +10 -0
  22. package/dist/react/Button.d.ts +12 -0
  23. package/dist/react/Button.js +6 -0
  24. package/dist/react/Checkbox.d.ts +9 -0
  25. package/dist/react/Checkbox.js +9 -0
  26. package/dist/react/Dropdown.d.ts +35 -0
  27. package/dist/react/Dropdown.js +20 -0
  28. package/dist/react/Field.d.ts +13 -0
  29. package/dist/react/Field.js +6 -0
  30. package/dist/react/Gauge.d.ts +19 -0
  31. package/dist/react/Gauge.js +6 -0
  32. package/dist/react/GlowCard.d.ts +12 -0
  33. package/dist/react/GlowCard.js +4 -0
  34. package/dist/react/HudBar.d.ts +34 -0
  35. package/dist/react/HudBar.js +10 -0
  36. package/dist/react/HudFrame.d.ts +19 -0
  37. package/dist/react/HudFrame.js +6 -0
  38. package/dist/react/Input.d.ts +11 -0
  39. package/dist/react/Input.js +6 -0
  40. package/dist/react/Interference.d.ts +18 -0
  41. package/dist/react/Interference.js +6 -0
  42. package/dist/react/Kbd.d.ts +18 -0
  43. package/dist/react/Kbd.js +11 -0
  44. package/dist/react/Label.d.ts +8 -0
  45. package/dist/react/Label.js +6 -0
  46. package/dist/react/Modal.d.ts +19 -0
  47. package/dist/react/Modal.js +9 -0
  48. package/dist/react/Pagination.d.ts +23 -0
  49. package/dist/react/Pagination.js +28 -0
  50. package/dist/react/PanelHeader.d.ts +9 -0
  51. package/dist/react/PanelHeader.js +6 -0
  52. package/dist/react/Progress.d.ts +13 -0
  53. package/dist/react/Progress.js +6 -0
  54. package/dist/react/Radio.d.ts +9 -0
  55. package/dist/react/Radio.js +9 -0
  56. package/dist/react/Scanner.d.ts +11 -0
  57. package/dist/react/Scanner.js +6 -0
  58. package/dist/react/Select.d.ts +13 -0
  59. package/dist/react/Select.js +6 -0
  60. package/dist/react/Separator.d.ts +11 -0
  61. package/dist/react/Separator.js +7 -0
  62. package/dist/react/Skeleton.d.ts +11 -0
  63. package/dist/react/Skeleton.js +13 -0
  64. package/dist/react/Slider.d.ts +9 -0
  65. package/dist/react/Slider.js +9 -0
  66. package/dist/react/Spinner.d.ts +11 -0
  67. package/dist/react/Spinner.js +6 -0
  68. package/dist/react/Stat.d.ts +20 -0
  69. package/dist/react/Stat.js +11 -0
  70. package/dist/react/Switch.d.ts +9 -0
  71. package/dist/react/Switch.js +6 -0
  72. package/dist/react/Table.d.ts +55 -0
  73. package/dist/react/Table.js +36 -0
  74. package/dist/react/Tabs.d.ts +28 -0
  75. package/dist/react/Tabs.js +16 -0
  76. package/dist/react/Terminal.d.ts +33 -0
  77. package/dist/react/Terminal.js +6 -0
  78. package/dist/react/Textarea.d.ts +7 -0
  79. package/dist/react/Textarea.js +6 -0
  80. package/dist/react/Ticker.d.ts +13 -0
  81. package/dist/react/Ticker.js +7 -0
  82. package/dist/react/Toast.d.ts +15 -0
  83. package/dist/react/Toast.js +6 -0
  84. package/dist/react/Tooltip.d.ts +11 -0
  85. package/dist/react/Tooltip.js +6 -0
  86. package/dist/react/index.d.ts +39 -0
  87. package/dist/react/index.js +46 -0
  88. package/dist/tailwind-theme.css +171 -0
  89. package/dist/tokens/base.d.ts +33 -0
  90. package/dist/tokens/base.js +41 -0
  91. package/dist/tokens/index.d.ts +7 -0
  92. package/dist/tokens/index.js +7 -0
  93. package/dist/tokens/semantic.d.ts +1 -0
  94. package/dist/tokens/semantic.js +54 -0
  95. package/dist/tokens/themes/cyan.d.ts +1 -0
  96. package/dist/tokens/themes/cyan.js +15 -0
  97. package/dist/tokens/themes/green.d.ts +1 -0
  98. package/dist/tokens/themes/green.js +15 -0
  99. package/dist/tokens/themes/orange.d.ts +1 -0
  100. package/dist/tokens/themes/orange.js +15 -0
  101. package/dist/tokens/themes/red.d.ts +1 -0
  102. package/dist/tokens/themes/red.js +15 -0
  103. package/dist/tokens/themes/steel.d.ts +1 -0
  104. package/dist/tokens/themes/steel.js +15 -0
  105. package/package.json +73 -0
  106. package/src/css/a11y.css +67 -0
  107. package/src/css/animations.css +111 -0
  108. package/src/css/base.css +34 -0
  109. package/src/css/components/accordion.css +148 -0
  110. package/src/css/components/alert.css +116 -0
  111. package/src/css/components/app-patterns.css +894 -0
  112. package/src/css/components/augmented.css +474 -0
  113. package/src/css/components/avatar.css +105 -0
  114. package/src/css/components/badge.css +46 -0
  115. package/src/css/components/breadcrumb.css +75 -0
  116. package/src/css/components/button.css +130 -0
  117. package/src/css/components/calendar.css +135 -0
  118. package/src/css/components/card.css +68 -0
  119. package/src/css/components/carousel.css +69 -0
  120. package/src/css/components/charts.css +464 -0
  121. package/src/css/components/checkbox.css +136 -0
  122. package/src/css/components/command.css +162 -0
  123. package/src/css/components/dropdown.css +187 -0
  124. package/src/css/components/form.css +280 -0
  125. package/src/css/components/gauge.css +156 -0
  126. package/src/css/components/glow-card.css +32 -0
  127. package/src/css/components/hud.css +165 -0
  128. package/src/css/components/image.css +164 -0
  129. package/src/css/components/input-otp.css +53 -0
  130. package/src/css/components/input.css +182 -0
  131. package/src/css/components/interference.css +217 -0
  132. package/src/css/components/kbd.css +54 -0
  133. package/src/css/components/layout-extras.css +256 -0
  134. package/src/css/components/modal.css +146 -0
  135. package/src/css/components/navigation-extras.css +280 -0
  136. package/src/css/components/pagination.css +122 -0
  137. package/src/css/components/panel-header.css +55 -0
  138. package/src/css/components/popover.css +112 -0
  139. package/src/css/components/scanner.css +125 -0
  140. package/src/css/components/select.css +99 -0
  141. package/src/css/components/sheet.css +149 -0
  142. package/src/css/components/skeleton.css +77 -0
  143. package/src/css/components/slider.css +116 -0
  144. package/src/css/components/spinner.css +101 -0
  145. package/src/css/components/spreadsheet.css +314 -0
  146. package/src/css/components/svg.css +104 -0
  147. package/src/css/components/switch.css +114 -0
  148. package/src/css/components/table.css +115 -0
  149. package/src/css/components/tabs.css +159 -0
  150. package/src/css/components/terminal.css +259 -0
  151. package/src/css/components/ticker.css +100 -0
  152. package/src/css/components/toast.css +137 -0
  153. package/src/css/components/toaster.css +48 -0
  154. package/src/css/components/toggle.css +99 -0
  155. package/src/css/components/typography.css +122 -0
  156. package/src/css/components/vectorbg.css +57 -0
  157. package/src/css/utilities.css +165 -0
  158. package/src/fonts/JetBrainsMono-Variable.woff2 +0 -0
  159. package/src/fonts/OFL.txt +93 -0
  160. package/src/js/charts.js +533 -0
  161. package/src/react/Accordion.tsx +85 -0
  162. package/src/react/Alert.tsx +58 -0
  163. package/src/react/AugButton.tsx +40 -0
  164. package/src/react/AugPanel.tsx +36 -0
  165. package/src/react/Avatar.tsx +69 -0
  166. package/src/react/Badge.tsx +28 -0
  167. package/src/react/Breadcrumb.tsx +63 -0
  168. package/src/react/Button.tsx +39 -0
  169. package/src/react/Checkbox.tsx +31 -0
  170. package/src/react/Dropdown.tsx +104 -0
  171. package/src/react/Field.tsx +36 -0
  172. package/src/react/Gauge.tsx +57 -0
  173. package/src/react/GlowCard.tsx +25 -0
  174. package/src/react/HudBar.tsx +86 -0
  175. package/src/react/HudFrame.tsx +52 -0
  176. package/src/react/Input.tsx +38 -0
  177. package/src/react/Interference.tsx +53 -0
  178. package/src/react/Kbd.tsx +62 -0
  179. package/src/react/Label.tsx +31 -0
  180. package/src/react/Modal.tsx +63 -0
  181. package/src/react/Pagination.tsx +113 -0
  182. package/src/react/PanelHeader.tsx +28 -0
  183. package/src/react/Progress.tsx +47 -0
  184. package/src/react/Radio.tsx +31 -0
  185. package/src/react/Scanner.tsx +37 -0
  186. package/src/react/Select.tsx +43 -0
  187. package/src/react/Separator.tsx +40 -0
  188. package/src/react/Skeleton.tsx +50 -0
  189. package/src/react/Slider.tsx +28 -0
  190. package/src/react/Spinner.tsx +39 -0
  191. package/src/react/Stat.tsx +52 -0
  192. package/src/react/Switch.tsx +25 -0
  193. package/src/react/Table.tsx +144 -0
  194. package/src/react/Tabs.tsx +97 -0
  195. package/src/react/Terminal.tsx +95 -0
  196. package/src/react/Textarea.tsx +20 -0
  197. package/src/react/Ticker.tsx +46 -0
  198. package/src/react/Toast.tsx +43 -0
  199. package/src/react/Tooltip.tsx +32 -0
  200. package/src/react/index.ts +52 -0
  201. package/src/tokens/base.ts +48 -0
  202. package/src/tokens/index.ts +7 -0
  203. package/src/tokens/semantic.ts +63 -0
  204. package/src/tokens/themes/cyan.ts +16 -0
  205. package/src/tokens/themes/green.ts +16 -0
  206. package/src/tokens/themes/orange.ts +16 -0
  207. package/src/tokens/themes/red.ts +16 -0
  208. package/src/tokens/themes/steel.ts +16 -0
@@ -0,0 +1,533 @@
1
+ /**
2
+ * cyberdesign/charts.js
3
+ * Lightweight canvas + SVG chart helpers. No dependencies.
4
+ * All colors read from --cd-* CSS variables so they respond to .theme-orange / .theme-cyan.
5
+ */
6
+
7
+ // ── Utility ──────────────────────────────────────────────
8
+
9
+ function css(el, prop) {
10
+ return getComputedStyle(el).getPropertyValue(prop).trim();
11
+ }
12
+
13
+ function colors(canvas) {
14
+ const el = canvas.closest('[class*="theme-"]') || document.documentElement;
15
+ return {
16
+ bg: css(el, '--cd-bg-panel') || '#0b0f18',
17
+ bgDark: css(el, '--cd-bg') || '#05060a',
18
+ border: css(el, '--cd-border') || '#26324c',
19
+ borderDim:css(el, '--cd-border-dim') || '#1e1e24',
20
+ text: css(el, '--cd-text') || '#e8e4e0',
21
+ muted: css(el, '--cd-text-muted') || '#8a8078',
22
+ accent: css(el, '--cd-accent') || '#ff8800',
23
+ secondary:css(el, '--cd-accent-secondary') || '#ff2bd6',
24
+ green: css(el, '--cd-neon-green') || '#52ff9f',
25
+ red: css(el, '--cd-neon-red') || '#ff456c',
26
+ yellow: css(el, '--cd-neon-yellow') || '#f7ff5a',
27
+ blue: css(el, '--cd-neon-blue') || '#6aa8ff',
28
+ };
29
+ }
30
+
31
+ function dpr() { return window.devicePixelRatio || 1; }
32
+
33
+ function setupCanvas(canvas) {
34
+ const parent = canvas.parentElement;
35
+ const rect = parent.getBoundingClientRect();
36
+ const ratio = dpr();
37
+ const w = rect.width || 300;
38
+ const h = rect.height || 200;
39
+ canvas.width = w * ratio;
40
+ canvas.height = h * ratio;
41
+ canvas.style.width = w + 'px';
42
+ canvas.style.height = h + 'px';
43
+ const ctx = canvas.getContext('2d');
44
+ ctx.scale(ratio, ratio);
45
+ return { ctx, w, h };
46
+ }
47
+
48
+ // ── Line / Area Chart ────────────────────────────────────
49
+ /**
50
+ * CdLineChart(canvas, opts) → { update(opts), destroy() }
51
+ *
52
+ * opts: {
53
+ * series: [{ data: number[], color?: string, fill?: boolean, label?: string }]
54
+ * min?: number, // y-axis min (auto if null)
55
+ * max?: number, // y-axis max (auto if null)
56
+ * gridLines?: number,
57
+ * xLabels?: string[],
58
+ * yFormat?: (v: number) => string,
59
+ * pad?: { top, right, bottom, left }
60
+ * }
61
+ */
62
+ export function CdLineChart(canvas, opts = {}) {
63
+ let o = {
64
+ series: [],
65
+ min: null,
66
+ max: null,
67
+ gridLines: 4,
68
+ xLabels: [],
69
+ yFormat: v => Math.round(v),
70
+ pad: { top: 16, right: 12, bottom: 28, left: 36 },
71
+ ...opts,
72
+ };
73
+
74
+ let { ctx, w, h } = setupCanvas(canvas);
75
+ const font = `700 10px ${css(document.documentElement, '--cd-font-mono') || 'monospace'}`;
76
+
77
+ function draw() {
78
+ const c = colors(canvas);
79
+ const { top, right, bottom, left } = o.pad;
80
+ const pw = w - left - right;
81
+ const ph = h - top - bottom;
82
+
83
+ ctx.clearRect(0, 0, w, h);
84
+ ctx.fillStyle = c.bg;
85
+ ctx.fillRect(0, 0, w, h);
86
+
87
+ if (!o.series.length || !o.series[0].data.length) return;
88
+
89
+ const allVals = o.series.flatMap(s => s.data).filter(Number.isFinite);
90
+ let lo = o.min ?? Math.min(...allVals);
91
+ let hi = o.max ?? Math.max(...allVals);
92
+ if (lo === hi) { lo -= 1; hi += 1; }
93
+ const range = hi - lo;
94
+ const maxLen = Math.max(...o.series.map(s => s.data.length));
95
+
96
+ function xp(i) { return left + (i / Math.max(maxLen - 1, 1)) * pw; }
97
+ function yp(v) { return top + ph - ((v - lo) / range) * ph; }
98
+
99
+ // Grid
100
+ ctx.setLineDash([2, 5]);
101
+ ctx.lineWidth = 1;
102
+ ctx.font = font;
103
+ ctx.textAlign = 'right';
104
+ for (let g = 0; g <= o.gridLines; g++) {
105
+ const y = top + (g / o.gridLines) * ph;
106
+ const v = hi - (g / o.gridLines) * range;
107
+ ctx.strokeStyle = c.borderDim;
108
+ ctx.beginPath();
109
+ ctx.moveTo(left, y);
110
+ ctx.lineTo(left + pw, y);
111
+ ctx.stroke();
112
+ ctx.fillStyle = c.muted;
113
+ ctx.fillText(o.yFormat(v), left - 5, y + 3);
114
+ }
115
+ ctx.setLineDash([]);
116
+
117
+ // X labels
118
+ if (o.xLabels.length) {
119
+ ctx.textAlign = 'center';
120
+ ctx.fillStyle = c.muted;
121
+ const every = Math.ceil(o.xLabels.length / 8);
122
+ for (let i = 0; i < o.xLabels.length; i += every) {
123
+ ctx.fillText(o.xLabels[i], xp(i), top + ph + 16);
124
+ }
125
+ }
126
+
127
+ // Plot border
128
+ ctx.strokeStyle = c.border;
129
+ ctx.lineWidth = 1;
130
+ ctx.strokeRect(left, top, pw, ph);
131
+
132
+ // Clip to plot area
133
+ ctx.save();
134
+ ctx.beginPath();
135
+ ctx.rect(left, top, pw, ph);
136
+ ctx.clip();
137
+
138
+ for (const s of o.series) {
139
+ if (s.data.length < 2) continue;
140
+ const col = s.color || c.accent;
141
+
142
+ // Build smooth bezier path
143
+ function buildPath() {
144
+ ctx.beginPath();
145
+ ctx.moveTo(xp(0), yp(s.data[0]));
146
+ for (let i = 1; i < s.data.length; i++) {
147
+ const cpx = (xp(i - 1) + xp(i)) / 2;
148
+ ctx.bezierCurveTo(cpx, yp(s.data[i - 1]), cpx, yp(s.data[i]), xp(i), yp(s.data[i]));
149
+ }
150
+ }
151
+
152
+ // Fill
153
+ if (s.fill) {
154
+ buildPath();
155
+ ctx.lineTo(xp(s.data.length - 1), top + ph);
156
+ ctx.lineTo(xp(0), top + ph);
157
+ ctx.closePath();
158
+ const grad = ctx.createLinearGradient(0, top, 0, top + ph);
159
+ grad.addColorStop(0, col + '40');
160
+ grad.addColorStop(1, col + '00');
161
+ ctx.fillStyle = grad;
162
+ ctx.fill();
163
+ }
164
+
165
+ // Glow pass
166
+ buildPath();
167
+ ctx.strokeStyle = col + '28';
168
+ ctx.lineWidth = 5;
169
+ ctx.stroke();
170
+
171
+ // Main line
172
+ buildPath();
173
+ ctx.strokeStyle = col;
174
+ ctx.lineWidth = 1.5;
175
+ ctx.stroke();
176
+
177
+ // End-point dot
178
+ const ex = xp(s.data.length - 1);
179
+ const ey = yp(s.data[s.data.length - 1]);
180
+ ctx.beginPath();
181
+ ctx.arc(ex, ey, 3, 0, Math.PI * 2);
182
+ ctx.fillStyle = col;
183
+ ctx.fill();
184
+ // dot glow
185
+ ctx.beginPath();
186
+ ctx.arc(ex, ey, 6, 0, Math.PI * 2);
187
+ ctx.fillStyle = col + '30';
188
+ ctx.fill();
189
+ }
190
+
191
+ ctx.restore();
192
+ }
193
+
194
+ const ro = new ResizeObserver(() => {
195
+ ({ ctx, w, h } = setupCanvas(canvas));
196
+ draw();
197
+ });
198
+ ro.observe(canvas.parentElement);
199
+ draw();
200
+
201
+ return {
202
+ update(newOpts) { o = { ...o, ...newOpts }; draw(); },
203
+ redraw() { draw(); },
204
+ destroy() { ro.disconnect(); },
205
+ };
206
+ }
207
+
208
+ // ── Waveform / Oscilloscope ──────────────────────────────
209
+ /**
210
+ * CdWaveform(canvas) → { push(Float32Array | Uint8Array), demo(), destroy() }
211
+ *
212
+ * push() feeds a time-domain sample buffer (Web Audio AnalyserNode).
213
+ * demo() starts an animated sine wave for testing without audio.
214
+ */
215
+ export function CdWaveform(canvas) {
216
+ let { ctx, w, h } = setupCanvas(canvas);
217
+ let buf = null;
218
+ let rafId = null;
219
+ let demoPhase = 0;
220
+ let demoActive = false;
221
+
222
+ function drawBuf(data, isFloat) {
223
+ const c = colors(canvas);
224
+ const col = c.yellow;
225
+
226
+ ctx.clearRect(0, 0, w, h);
227
+ ctx.fillStyle = c.bgDark;
228
+ ctx.fillRect(0, 0, w, h);
229
+
230
+ // Centre line
231
+ ctx.strokeStyle = c.borderDim;
232
+ ctx.lineWidth = 1;
233
+ ctx.setLineDash([2, 6]);
234
+ ctx.beginPath();
235
+ ctx.moveTo(0, h / 2);
236
+ ctx.lineTo(w, h / 2);
237
+ ctx.stroke();
238
+ ctx.setLineDash([]);
239
+
240
+ // Waveform
241
+ const step = Math.max(1, Math.floor(data.length / w));
242
+ ctx.beginPath();
243
+ for (let i = 0; i < w; i++) {
244
+ const idx = i * step;
245
+ const raw = isFloat ? data[idx] : (data[idx] / 128) - 1;
246
+ const y = (h / 2) + raw * (h / 2) * 0.85;
247
+ i === 0 ? ctx.moveTo(i, y) : ctx.lineTo(i, y);
248
+ }
249
+
250
+ // Glow stroke
251
+ ctx.strokeStyle = col + '35';
252
+ ctx.lineWidth = 4;
253
+ ctx.stroke();
254
+ // Main stroke
255
+ ctx.strokeStyle = col;
256
+ ctx.lineWidth = 1.5;
257
+ ctx.stroke();
258
+ }
259
+
260
+ function demoTick(ts) {
261
+ if (!demoActive) return;
262
+ demoPhase = (ts / 1200) * Math.PI * 2;
263
+ const samples = new Float32Array(512);
264
+ for (let i = 0; i < 512; i++) {
265
+ const t = i / 512;
266
+ samples[i] =
267
+ Math.sin(demoPhase + t * Math.PI * 4) * 0.55 +
268
+ Math.sin(demoPhase * 2.3 + t * Math.PI * 12) * 0.2 +
269
+ Math.sin(demoPhase * 0.7 + t * Math.PI * 1.5) * 0.25;
270
+ }
271
+ drawBuf(samples, true);
272
+ rafId = requestAnimationFrame(demoTick);
273
+ }
274
+
275
+ const ro = new ResizeObserver(() => {
276
+ ({ ctx, w, h } = setupCanvas(canvas));
277
+ if (buf) drawBuf(buf.data, buf.isFloat);
278
+ });
279
+ ro.observe(canvas.parentElement);
280
+
281
+ return {
282
+ push(data) {
283
+ demoActive = false;
284
+ cancelAnimationFrame(rafId);
285
+ const isFloat = data instanceof Float32Array;
286
+ buf = { data, isFloat };
287
+ drawBuf(data, isFloat);
288
+ },
289
+ demo() {
290
+ demoActive = true;
291
+ rafId = requestAnimationFrame(demoTick);
292
+ },
293
+ destroy() {
294
+ demoActive = false;
295
+ cancelAnimationFrame(rafId);
296
+ ro.disconnect();
297
+ },
298
+ };
299
+ }
300
+
301
+ // ── Radar / Spider Chart (SVG) ───────────────────────────
302
+ /**
303
+ * CdRadar(svgEl, opts) → { update(opts), destroy() }
304
+ *
305
+ * opts: {
306
+ * axes: string[],
307
+ * series: [{ values: number[], color?: string, label?: string }],
308
+ * max?: number, // defaults to 1.0
309
+ * rings?: number, // concentric grid rings (default 4)
310
+ * }
311
+ */
312
+ export function CdRadar(svg, opts = {}) {
313
+ let o = { axes: [], series: [], max: 1, rings: 4, ...opts };
314
+
315
+ function draw() {
316
+ const c = colors(svg);
317
+ const W = svg.clientWidth || 240;
318
+ const H = svg.clientHeight || 240;
319
+ const cx = W / 2;
320
+ const cy = H / 2;
321
+ const r = Math.min(cx, cy) * 0.72;
322
+ const n = o.axes.length;
323
+ if (n < 3) return;
324
+
325
+ function angle(i) { return (i / n) * Math.PI * 2 - Math.PI / 2; }
326
+ function pt(i, frac) {
327
+ const a = angle(i);
328
+ return [cx + Math.cos(a) * r * frac, cy + Math.sin(a) * r * frac];
329
+ }
330
+ function polygon(fracs) {
331
+ return fracs.map((f, i) => pt(i, f).join(',')).join(' ');
332
+ }
333
+
334
+ svg.setAttribute('viewBox', `0 0 ${W} ${H}`);
335
+ svg.innerHTML = '';
336
+
337
+ const ns = 'http://www.w3.org/2000/svg';
338
+ function el(tag, attrs, parent = svg) {
339
+ const e = document.createElementNS(ns, tag);
340
+ for (const [k, v] of Object.entries(attrs)) e.setAttribute(k, v);
341
+ parent.appendChild(e);
342
+ return e;
343
+ }
344
+
345
+ // Grid rings
346
+ for (let ring = 1; ring <= o.rings; ring++) {
347
+ const frac = ring / o.rings;
348
+ el('polygon', {
349
+ points: polygon(Array(n).fill(frac)),
350
+ fill: 'none',
351
+ stroke: c.borderDim,
352
+ 'stroke-width': '1',
353
+ });
354
+ }
355
+
356
+ // Spokes
357
+ for (let i = 0; i < n; i++) {
358
+ const [x, y] = pt(i, 1);
359
+ el('line', { x1: cx, y1: cy, x2: x, y2: y, stroke: c.borderDim, 'stroke-width': '1' });
360
+ }
361
+
362
+ // Axis labels
363
+ for (let i = 0; i < n; i++) {
364
+ const [x, y] = pt(i, 1.14);
365
+ const t = el('text', {
366
+ x, y,
367
+ 'text-anchor': 'middle',
368
+ 'dominant-baseline': 'middle',
369
+ fill: c.muted,
370
+ 'font-size': '10',
371
+ 'font-family': 'monospace',
372
+ 'font-weight': '700',
373
+ 'letter-spacing': '0.1em',
374
+ 'text-transform': 'uppercase',
375
+ });
376
+ t.textContent = o.axes[i].toUpperCase();
377
+ }
378
+
379
+ // Series
380
+ for (const s of o.series) {
381
+ const col = s.color || c.accent;
382
+ const fracs = s.values.map(v => Math.min(v / o.max, 1));
383
+
384
+ // Fill
385
+ el('polygon', {
386
+ points: polygon(fracs),
387
+ fill: col + '22',
388
+ stroke: 'none',
389
+ });
390
+
391
+ // Stroke
392
+ el('polygon', {
393
+ points: polygon(fracs),
394
+ fill: 'none',
395
+ stroke: col,
396
+ 'stroke-width': '1.5',
397
+ });
398
+
399
+ // Glow stroke
400
+ el('polygon', {
401
+ points: polygon(fracs),
402
+ fill: 'none',
403
+ stroke: col + '30',
404
+ 'stroke-width': '5',
405
+ });
406
+
407
+ // Data point dots
408
+ for (let i = 0; i < n; i++) {
409
+ const [x, y] = pt(i, fracs[i]);
410
+ el('circle', { cx: x, cy: y, r: '3', fill: col });
411
+ el('circle', { cx: x, cy: y, r: '6', fill: col + '30' });
412
+ }
413
+ }
414
+ }
415
+
416
+ const ro = new ResizeObserver(draw);
417
+ ro.observe(svg);
418
+ draw();
419
+
420
+ return {
421
+ update(newOpts) { o = { ...o, ...newOpts }; draw(); },
422
+ destroy() { ro.disconnect(); },
423
+ };
424
+ }
425
+
426
+ // ── Bar Chart (canvas) ───────────────────────────────────
427
+ /**
428
+ * CdBarChart(canvas, opts) → { update(opts), destroy() }
429
+ *
430
+ * opts: {
431
+ * bars: [{ label: string, value: number, color?: string }],
432
+ * max?: number, // y-axis max (auto if null)
433
+ * pad?: { top, right, bottom, left }
434
+ * barGap?: number, // 0–1, fraction of bar width that is gap
435
+ * }
436
+ */
437
+ export function CdBarChart(canvas, opts = {}) {
438
+ let o = {
439
+ bars: [],
440
+ max: null,
441
+ pad: { top: 16, right: 12, bottom: 28, left: 36 },
442
+ barGap: 0.25,
443
+ yFormat: v => Math.round(v),
444
+ gridLines: 4,
445
+ ...opts,
446
+ };
447
+
448
+ let { ctx, w, h } = setupCanvas(canvas);
449
+ const font = `700 10px ${css(document.documentElement, '--cd-font-mono') || 'monospace'}`;
450
+
451
+ function draw() {
452
+ const c = colors(canvas);
453
+ const { top, right, bottom, left } = o.pad;
454
+ const pw = w - left - right;
455
+ const ph = h - top - bottom;
456
+
457
+ ctx.clearRect(0, 0, w, h);
458
+ ctx.fillStyle = c.bg;
459
+ ctx.fillRect(0, 0, w, h);
460
+
461
+ if (!o.bars.length) return;
462
+
463
+ const hi = o.max ?? Math.max(...o.bars.map(b => b.value));
464
+ const lo = 0;
465
+ const range = hi - lo || 1;
466
+
467
+ function yp(v) { return top + ph - ((v - lo) / range) * ph; }
468
+
469
+ // Grid
470
+ ctx.font = font;
471
+ ctx.textAlign = 'right';
472
+ for (let g = 0; g <= o.gridLines; g++) {
473
+ const y = top + (g / o.gridLines) * ph;
474
+ const v = hi - (g / o.gridLines) * range;
475
+ ctx.strokeStyle = c.borderDim;
476
+ ctx.lineWidth = 1;
477
+ ctx.setLineDash([2, 5]);
478
+ ctx.beginPath();
479
+ ctx.moveTo(left, y);
480
+ ctx.lineTo(left + pw, y);
481
+ ctx.stroke();
482
+ ctx.setLineDash([]);
483
+ ctx.fillStyle = c.muted;
484
+ ctx.fillText(o.yFormat(v), left - 5, y + 3);
485
+ }
486
+
487
+ ctx.strokeStyle = c.border;
488
+ ctx.lineWidth = 1;
489
+ ctx.strokeRect(left, top, pw, ph);
490
+
491
+ const n = o.bars.length;
492
+ const slotW = pw / n;
493
+ const gap = slotW * o.barGap;
494
+ const barW = slotW - gap;
495
+
496
+ for (let i = 0; i < n; i++) {
497
+ const bar = o.bars[i];
498
+ const col = bar.color || c.accent;
499
+ const bx = left + i * slotW + gap / 2;
500
+ const by = yp(bar.value);
501
+ const bh = top + ph - by;
502
+
503
+ // Bar fill
504
+ const grad = ctx.createLinearGradient(0, by, 0, by + bh);
505
+ grad.addColorStop(0, col + 'cc');
506
+ grad.addColorStop(1, col + '44');
507
+ ctx.fillStyle = grad;
508
+ ctx.fillRect(bx, by, barW, bh);
509
+
510
+ // Top glow line
511
+ ctx.fillStyle = col;
512
+ ctx.fillRect(bx, by, barW, 2);
513
+
514
+ // Label
515
+ ctx.textAlign = 'center';
516
+ ctx.fillStyle = c.muted;
517
+ ctx.fillText(bar.label.toUpperCase().slice(0, 8), bx + barW / 2, top + ph + 16);
518
+ }
519
+ }
520
+
521
+ const ro = new ResizeObserver(() => {
522
+ ({ ctx, w, h } = setupCanvas(canvas));
523
+ draw();
524
+ });
525
+ ro.observe(canvas.parentElement);
526
+ draw();
527
+
528
+ return {
529
+ update(newOpts) { o = { ...o, ...newOpts }; draw(); },
530
+ redraw() { draw(); },
531
+ destroy() { ro.disconnect(); },
532
+ };
533
+ }
@@ -0,0 +1,85 @@
1
+ import type { HTMLAttributes, DetailsHTMLAttributes, ReactNode } from 'react';
2
+
3
+ const cx = (...parts: Array<string | false | null | undefined>) =>
4
+ parts.filter(Boolean).join(' ');
5
+
6
+ export interface AccordionItemData {
7
+ /** Trigger heading text. */
8
+ title: ReactNode;
9
+ /** Body content revealed when open. */
10
+ content: ReactNode;
11
+ /** Optional icon glyph before the title. */
12
+ icon?: ReactNode;
13
+ /** Render this item open initially. */
14
+ defaultOpen?: boolean;
15
+ }
16
+
17
+ export interface AccordionItemProps
18
+ extends Omit<DetailsHTMLAttributes<HTMLDetailsElement>, 'title'> {
19
+ /** Trigger heading text. */
20
+ title: ReactNode;
21
+ /** Optional icon glyph before the title. */
22
+ icon?: ReactNode;
23
+ /** Body content. */
24
+ children?: ReactNode;
25
+ }
26
+
27
+ /** Single `<details>` accordion row — for manual composition under `<Accordion>`. */
28
+ function AccordionItem({ title, icon, children, className = '', ...rest }: AccordionItemProps) {
29
+ return (
30
+ <details className={cx('cd-accordion__item', className)} {...rest}>
31
+ <summary className="cd-accordion__trigger">
32
+ {icon != null && <span className="cd-accordion__icon">{icon}</span>}
33
+ {title}
34
+ </summary>
35
+ <div className="cd-accordion__body">
36
+ <div className="cd-accordion__content">
37
+ <div className="cd-accordion__content-inner">{children}</div>
38
+ </div>
39
+ </div>
40
+ </details>
41
+ );
42
+ }
43
+
44
+ export interface AccordionProps extends HTMLAttributes<HTMLDivElement> {
45
+ /** Data-driven rows. Omit to compose `<Accordion.Item>` children manually. */
46
+ items?: AccordionItemData[];
47
+ /** `sep` adds gaps between items; `flush` drops side borders. */
48
+ variant?: 'default' | 'sep' | 'flush';
49
+ children?: ReactNode;
50
+ }
51
+
52
+ /** Disclosure accordion — native `<details>` rows, data-driven or composed via `Accordion.Item`. */
53
+ function AccordionRoot({
54
+ items,
55
+ variant = 'default',
56
+ className = '',
57
+ children,
58
+ ...rest
59
+ }: AccordionProps) {
60
+ return (
61
+ <div
62
+ className={cx(
63
+ 'cd-accordion',
64
+ variant !== 'default' && `cd-accordion--${variant}`,
65
+ className,
66
+ )}
67
+ {...rest}
68
+ >
69
+ {items
70
+ ? items.map((item, i) => (
71
+ <AccordionItem
72
+ key={i}
73
+ title={item.title}
74
+ icon={item.icon}
75
+ open={item.defaultOpen}
76
+ >
77
+ {item.content}
78
+ </AccordionItem>
79
+ ))
80
+ : children}
81
+ </div>
82
+ );
83
+ }
84
+
85
+ export const Accordion = Object.assign(AccordionRoot, { Item: AccordionItem });
@@ -0,0 +1,58 @@
1
+ import type { HTMLAttributes, ReactNode } from 'react';
2
+
3
+ const cx = (...parts: Array<string | false | null | undefined>) =>
4
+ parts.filter(Boolean).join(' ');
5
+
6
+ export interface AlertProps extends Omit<HTMLAttributes<HTMLDivElement>, 'title'> {
7
+ /** Semantic color. `accent` follows the active theme accent. */
8
+ variant?: 'info' | 'success' | 'warning' | 'error' | 'accent';
9
+ /** Compact padding/type. */
10
+ size?: 'sm';
11
+ /** Drop the left accent bar for a flatter inline banner. */
12
+ inline?: boolean;
13
+ /** Bold uppercase heading line. */
14
+ title?: ReactNode;
15
+ /** Leading glyph (e.g. ℹ ✓ ⚠ ✕ ⬡). */
16
+ icon?: ReactNode;
17
+ /** When provided, renders a dismiss ✕ that calls this on click. */
18
+ onDismiss?: () => void;
19
+ /** Body text. */
20
+ children?: ReactNode;
21
+ }
22
+
23
+ /** Banner / alert with semantic color, optional icon, title and dismiss. */
24
+ export function Alert({
25
+ variant = 'info',
26
+ size,
27
+ inline = false,
28
+ title,
29
+ icon,
30
+ onDismiss,
31
+ className = '',
32
+ children,
33
+ ...rest
34
+ }: AlertProps) {
35
+ return (
36
+ <div
37
+ className={cx(
38
+ 'cd-alert',
39
+ `cd-alert--${variant}`,
40
+ size === 'sm' && 'cd-alert--sm',
41
+ inline && 'cd-alert--inline',
42
+ className,
43
+ )}
44
+ {...rest}
45
+ >
46
+ {icon != null && <span className="cd-alert__icon">{icon}</span>}
47
+ <div className="cd-alert__content">
48
+ {title != null && <div className="cd-alert__title">{title}</div>}
49
+ {children != null && <div className="cd-alert__body">{children}</div>}
50
+ </div>
51
+ {onDismiss && (
52
+ <button className="cd-alert__dismiss" onClick={onDismiss} aria-label="Dismiss">
53
+
54
+ </button>
55
+ )}
56
+ </div>
57
+ );
58
+ }
@@ -0,0 +1,40 @@
1
+ import type { ButtonHTMLAttributes, ReactNode } from 'react';
2
+
3
+ const cx = (...parts: Array<string | false | null | undefined>) =>
4
+ parts.filter(Boolean).join(' ');
5
+
6
+ export interface AugButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
7
+ /** `primary` fills with the accent-subtle background and a drop-shadow glow. */
8
+ variant?: 'default' | 'primary';
9
+ /** `sm` and `lg` adjust height, padding and clip size; `md` is the default. */
10
+ size?: 'sm' | 'md' | 'lg';
11
+ /** Reverse the diagonal clip to cut the TR+BL corners instead of TL+BR. */
12
+ reverse?: boolean;
13
+ children?: ReactNode;
14
+ }
15
+
16
+ /** Augmented clip-path button — neon-bordered diagonal-corner button matching cd-aug-btn. */
17
+ export function AugButton({
18
+ variant = 'default',
19
+ size = 'md',
20
+ reverse = false,
21
+ className = '',
22
+ children,
23
+ ...rest
24
+ }: AugButtonProps) {
25
+ return (
26
+ <button
27
+ className={cx(
28
+ 'cd-aug-btn',
29
+ variant === 'primary' && 'cd-aug-btn--primary',
30
+ size === 'sm' && 'cd-aug-btn--sm',
31
+ size === 'lg' && 'cd-aug-btn--lg',
32
+ reverse && 'cd-aug-btn--rev',
33
+ className,
34
+ )}
35
+ {...rest}
36
+ >
37
+ {children}
38
+ </button>
39
+ );
40
+ }