@voltro/ui-shadcn 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 (72) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/LICENSE +57 -0
  3. package/README.md +26 -0
  4. package/SECURITY.md +56 -0
  5. package/THIRD-PARTY-NOTICES.md +3016 -0
  6. package/dist/brand.d.ts +73 -0
  7. package/dist/brand.js +177 -0
  8. package/dist/cn.d.ts +11 -0
  9. package/dist/cn.js +6 -0
  10. package/dist/index.d.ts +1183 -0
  11. package/dist/index.js +2636 -0
  12. package/dist/tokens.css +532 -0
  13. package/package.json +64 -0
  14. package/src/brand/README.md +60 -0
  15. package/src/brand/assets/voltro-favicon.svg +30 -0
  16. package/src/brand/assets/voltro-icon-dark.svg +26 -0
  17. package/src/brand/assets/voltro-icon.svg +14 -0
  18. package/src/brand/assets/voltro-mark-mono.svg +5 -0
  19. package/src/brand/assets/voltro-mark.svg +14 -0
  20. package/src/brand/voltroLogo.tsx +244 -0
  21. package/src/cn.ts +10 -0
  22. package/src/compositions/appShell.tsx +72 -0
  23. package/src/compositions/codeCompare.tsx +88 -0
  24. package/src/compositions/docShell.tsx +112 -0
  25. package/src/compositions/docsLayout.tsx +577 -0
  26. package/src/compositions/featureBento.tsx +103 -0
  27. package/src/compositions/featureGrid.tsx +41 -0
  28. package/src/compositions/heroSection.tsx +55 -0
  29. package/src/compositions/landingCta.tsx +85 -0
  30. package/src/compositions/landingHero.tsx +174 -0
  31. package/src/compositions/landingStats.tsx +99 -0
  32. package/src/compositions/loginCard.tsx +139 -0
  33. package/src/compositions/pageHeader.tsx +58 -0
  34. package/src/compositions/profileMenu.tsx +316 -0
  35. package/src/compositions/siteFooter.tsx +250 -0
  36. package/src/compositions/themeToggle.tsx +82 -0
  37. package/src/cookies.ts +109 -0
  38. package/src/index.ts +160 -0
  39. package/src/primitives/animatedNumber.tsx +73 -0
  40. package/src/primitives/avatar.tsx +39 -0
  41. package/src/primitives/badge.tsx +39 -0
  42. package/src/primitives/button.tsx +53 -0
  43. package/src/primitives/callout.tsx +97 -0
  44. package/src/primitives/card.tsx +68 -0
  45. package/src/primitives/checkbox.tsx +55 -0
  46. package/src/primitives/codeBlock.tsx +134 -0
  47. package/src/primitives/codeWindow.tsx +84 -0
  48. package/src/primitives/dialog.tsx +43 -0
  49. package/src/primitives/docCard.tsx +109 -0
  50. package/src/primitives/docIcons.tsx +268 -0
  51. package/src/primitives/dropdownMenu.tsx +162 -0
  52. package/src/primitives/gridOverlay.tsx +51 -0
  53. package/src/primitives/highlightedCode.tsx +112 -0
  54. package/src/primitives/input.tsx +25 -0
  55. package/src/primitives/label.tsx +19 -0
  56. package/src/primitives/localeSwitcher.tsx +90 -0
  57. package/src/primitives/meshBackdrop.tsx +62 -0
  58. package/src/primitives/scrollReveal.tsx +70 -0
  59. package/src/primitives/searchModal.tsx +304 -0
  60. package/src/primitives/select.tsx +24 -0
  61. package/src/primitives/separator.tsx +24 -0
  62. package/src/primitives/skeleton.tsx +12 -0
  63. package/src/primitives/sparkles.tsx +105 -0
  64. package/src/primitives/steps.tsx +55 -0
  65. package/src/primitives/tabs.tsx +102 -0
  66. package/src/primitives/textarea.tsx +24 -0
  67. package/src/primitives/toast.tsx +44 -0
  68. package/src/primitives/tocScrollSpy.tsx +110 -0
  69. package/src/primitives/toggle.tsx +50 -0
  70. package/src/primitives/toggleGroup.tsx +62 -0
  71. package/src/tokens.css +532 -0
  72. package/src/widgets.tsx +297 -0
@@ -0,0 +1,532 @@
1
+ /* @voltro/ui-shadcn design tokens — Tailwind v4 + tw-animate-css + the full
2
+ * shadcn palette (light AND dark values) + the kit's ambient animations.
3
+ *
4
+ * Wiring, in your app's src/globals.css (see the styling docs,
5
+ * `routing/styling`):
6
+ *
7
+ * @import "@voltro/ui-shadcn/tokens.css";
8
+ * @source "./"; (your app's src)
9
+ * @source "../node_modules/@voltro/ui-shadcn/src"; (the kit sources)
10
+ *
11
+ * (Directory form shown — the equivalent glob form ending in
12
+ * "…src/**/*.{tsx,ts,jsx,js}" works too; it just can't be spelled
13
+ * inside this CSS comment.)
14
+ *
15
+ * The SECOND @source (the kit path) is MANDATORY when you render kit
16
+ * components: @source paths resolve relative to the IMPORTING css file, so
17
+ * the kit's own @source below expands to YOUR app's src once imported —
18
+ * kit-only classes are silently dropped without the explicit kit path. The
19
+ * published package ships src/ precisely so this path matches.
20
+ *
21
+ * Keep every @theme block ABOVE any @layer rules in the same stylesheet —
22
+ * a later @theme is silently dropped. */
23
+
24
+ @import "tailwindcss";
25
+ @import "tw-animate-css";
26
+
27
+ /* Tailwind v4 derives content from the Vite module graph; the framework's
28
+ Vite root is `<app>/.framework/`, not the app src — this glob (rebased to
29
+ the importing stylesheet, see the header) points the scanner at the
30
+ consuming app's src/. */
31
+ @source "./**/*.{tsx,ts,jsx,js}";
32
+
33
+ /* Canonical shadcn-style dark-mode trigger: any descendant of an
34
+ element with class `dark` inherits the dark token set. */
35
+ @custom-variant dark (&:is(.dark *));
36
+
37
+ :root {
38
+ --background: oklch(0.99 0 0);
39
+ --foreground: oklch(0 0 0);
40
+ --card: oklch(1 0 0);
41
+ --card-foreground: oklch(0 0 0);
42
+ --popover: oklch(0.99 0 0);
43
+ --popover-foreground: oklch(0 0 0);
44
+ --primary: oklch(0 0 0);
45
+ --primary-foreground: oklch(1 0 0);
46
+ --secondary: oklch(0.94 0 0);
47
+ --secondary-foreground: oklch(0 0 0);
48
+ --muted: oklch(0.97 0 0);
49
+ --muted-foreground: oklch(0.44 0 0);
50
+ --accent: oklch(0.94 0 0);
51
+ --accent-foreground: oklch(0 0 0);
52
+ --destructive: oklch(0.63 0.19 23.03);
53
+ --destructive-foreground: oklch(1 0 0);
54
+ --border: oklch(0.92 0 0);
55
+ --input: oklch(0.94 0 0);
56
+ --ring: oklch(0 0 0);
57
+ --chart-1: oklch(0.81 0.17 75.35);
58
+ --chart-2: oklch(0.55 0.22 264.53);
59
+ --chart-3: oklch(0.72 0 0);
60
+ --chart-4: oklch(0.92 0 0);
61
+ --chart-5: oklch(0.56 0 0);
62
+ --sidebar: oklch(0.99 0 0);
63
+ --sidebar-foreground: oklch(0 0 0);
64
+ --sidebar-primary: oklch(0 0 0);
65
+ --sidebar-primary-foreground: oklch(1 0 0);
66
+ --sidebar-accent: oklch(0.94 0 0);
67
+ --sidebar-accent-foreground: oklch(0 0 0);
68
+ --sidebar-border: oklch(0.94 0 0);
69
+ --sidebar-ring: oklch(0 0 0);
70
+ --font-sans: Geist, sans-serif;
71
+ --font-serif: Georgia, serif;
72
+ --font-mono: Geist Mono, monospace;
73
+ --radius: 0.5rem;
74
+ --shadow-x: 0px;
75
+ --shadow-y: 1px;
76
+ --shadow-blur: 2px;
77
+ --shadow-spread: 0px;
78
+ --shadow-opacity: 0.18;
79
+ --shadow-color: hsl(0 0% 0%);
80
+ --shadow-2xs: 0px 1px 2px 0px hsl(0 0% 0% / 0.09);
81
+ --shadow-xs: 0px 1px 2px 0px hsl(0 0% 0% / 0.09);
82
+ --shadow-sm:
83
+ 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 1px 2px -1px hsl(0 0% 0% / 0.18);
84
+ --shadow:
85
+ 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 1px 2px -1px hsl(0 0% 0% / 0.18);
86
+ --shadow-md:
87
+ 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 2px 4px -1px hsl(0 0% 0% / 0.18);
88
+ --shadow-lg:
89
+ 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 4px 6px -1px hsl(0 0% 0% / 0.18);
90
+ --shadow-xl:
91
+ 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 8px 10px -1px hsl(0 0% 0% / 0.18);
92
+ --shadow-2xl: 0px 1px 2px 0px hsl(0 0% 0% / 0.45);
93
+ --tracking-normal: 0em;
94
+ --spacing: 0.25rem;
95
+ }
96
+
97
+ .dark {
98
+ --background: oklch(0 0 0);
99
+ --foreground: oklch(1 0 0);
100
+ --card: oklch(0.14 0 0);
101
+ --card-foreground: oklch(1 0 0);
102
+ --popover: oklch(0.18 0 0);
103
+ --popover-foreground: oklch(1 0 0);
104
+ --primary: oklch(1 0 0);
105
+ --primary-foreground: oklch(0 0 0);
106
+ --secondary: oklch(0.25 0 0);
107
+ --secondary-foreground: oklch(1 0 0);
108
+ --muted: oklch(0.23 0 0);
109
+ --muted-foreground: oklch(0.72 0 0);
110
+ --accent: oklch(0.32 0 0);
111
+ --accent-foreground: oklch(1 0 0);
112
+ --destructive: oklch(0.69 0.2 23.91);
113
+ --destructive-foreground: oklch(0 0 0);
114
+ --border: oklch(0.26 0 0);
115
+ --input: oklch(0.32 0 0);
116
+ --ring: oklch(0.72 0 0);
117
+ --chart-1: oklch(0.81 0.17 75.35);
118
+ --chart-2: oklch(0.58 0.21 260.84);
119
+ --chart-3: oklch(0.56 0 0);
120
+ --chart-4: oklch(0.44 0 0);
121
+ --chart-5: oklch(0.92 0 0);
122
+ --sidebar: oklch(0.18 0 0);
123
+ --sidebar-foreground: oklch(1 0 0);
124
+ --sidebar-primary: oklch(1 0 0);
125
+ --sidebar-primary-foreground: oklch(0 0 0);
126
+ --sidebar-accent: oklch(0.32 0 0);
127
+ --sidebar-accent-foreground: oklch(1 0 0);
128
+ --sidebar-border: oklch(0.32 0 0);
129
+ --sidebar-ring: oklch(0.72 0 0);
130
+ --font-sans: Geist, sans-serif;
131
+ --font-serif: Georgia, serif;
132
+ --font-mono: Geist Mono, monospace;
133
+ --radius: 0.5rem;
134
+ --shadow-x: 0px;
135
+ --shadow-y: 1px;
136
+ --shadow-blur: 2px;
137
+ --shadow-spread: 0px;
138
+ --shadow-opacity: 0.18;
139
+ --shadow-color: hsl(0 0% 0%);
140
+ --shadow-2xs: 0px 1px 2px 0px hsl(0 0% 0% / 0.09);
141
+ --shadow-xs: 0px 1px 2px 0px hsl(0 0% 0% / 0.09);
142
+ --shadow-sm:
143
+ 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 1px 2px -1px hsl(0 0% 0% / 0.18);
144
+ --shadow:
145
+ 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 1px 2px -1px hsl(0 0% 0% / 0.18);
146
+ --shadow-md:
147
+ 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 2px 4px -1px hsl(0 0% 0% / 0.18);
148
+ --shadow-lg:
149
+ 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 4px 6px -1px hsl(0 0% 0% / 0.18);
150
+ --shadow-xl:
151
+ 0px 1px 2px 0px hsl(0 0% 0% / 0.18), 0px 8px 10px -1px hsl(0 0% 0% / 0.18);
152
+ --shadow-2xl: 0px 1px 2px 0px hsl(0 0% 0% / 0.45);
153
+ }
154
+
155
+ /* ---- Map the tokens above into Tailwind v4's @theme so utility
156
+ classes like `bg-background`, `text-muted-foreground`, etc.
157
+ resolve correctly. The `inline` keyword means the values are
158
+ used verbatim — no @media (prefers-color-scheme) inversion. ---- */
159
+ @theme inline {
160
+ --color-background: var(--background);
161
+ --color-foreground: var(--foreground);
162
+ --color-card: var(--card);
163
+ --color-card-foreground: var(--card-foreground);
164
+ --color-popover: var(--popover);
165
+ --color-popover-foreground: var(--popover-foreground);
166
+ --color-primary: var(--primary);
167
+ --color-primary-foreground: var(--primary-foreground);
168
+ --color-secondary: var(--secondary);
169
+ --color-secondary-foreground: var(--secondary-foreground);
170
+ --color-muted: var(--muted);
171
+ --color-muted-foreground: var(--muted-foreground);
172
+ --color-accent: var(--accent);
173
+ --color-accent-foreground: var(--accent-foreground);
174
+ --color-destructive: var(--destructive);
175
+ --color-destructive-foreground: var(--destructive-foreground);
176
+ --color-border: var(--border);
177
+ --color-input: var(--input);
178
+ --color-ring: var(--ring);
179
+ --color-chart-1: var(--chart-1);
180
+ --color-chart-2: var(--chart-2);
181
+ --color-chart-3: var(--chart-3);
182
+ --color-chart-4: var(--chart-4);
183
+ --color-chart-5: var(--chart-5);
184
+ --color-sidebar: var(--sidebar);
185
+ --color-sidebar-foreground: var(--sidebar-foreground);
186
+ --color-sidebar-primary: var(--sidebar-primary);
187
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
188
+ --color-sidebar-accent: var(--sidebar-accent);
189
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
190
+ --color-sidebar-border: var(--sidebar-border);
191
+ --color-sidebar-ring: var(--sidebar-ring);
192
+
193
+ --font-sans: var(--font-sans);
194
+ --font-mono: var(--font-mono);
195
+ --font-serif: var(--font-serif);
196
+
197
+ --radius-sm: calc(var(--radius) - 4px);
198
+ --radius-md: calc(var(--radius) - 2px);
199
+ --radius-lg: var(--radius);
200
+ --radius-xl: calc(var(--radius) + 4px);
201
+
202
+ --shadow-2xs: var(--shadow-2xs);
203
+ --shadow-xs: var(--shadow-xs);
204
+ --shadow-sm: var(--shadow-sm);
205
+ --shadow: var(--shadow);
206
+ --shadow-md: var(--shadow-md);
207
+ --shadow-lg: var(--shadow-lg);
208
+ --shadow-xl: var(--shadow-xl);
209
+ --shadow-2xl: var(--shadow-2xl);
210
+
211
+ /* Animation utilities — Tailwind v4 generates `animate-<name>`
212
+ * utilities from `--animate-<name>` registrations here. Keyframe
213
+ * definitions live further down (CSS doesn't care about order between
214
+ * @keyframes and the rule that references them, so the @theme block
215
+ * stays consolidated at the top to avoid the multi-@theme merge bug
216
+ * where blocks after @layer get silently dropped). */
217
+ --animate-mesh-drift-a: ui-mesh-drift-a 70s ease-in-out infinite;
218
+ --animate-mesh-drift-b: ui-mesh-drift-b 90s ease-in-out infinite;
219
+ --animate-mesh-drift-c: ui-mesh-drift-c 60s ease-in-out infinite;
220
+ --animate-caret-blink: ui-caret-blink 1s steps(2) infinite;
221
+ --animate-gradient-pan: ui-gradient-pan 8s ease-in-out infinite;
222
+ --animate-pulse-ring: ui-pulse-ring 2s ease-out infinite;
223
+ --animate-shimmer: ui-shimmer 2.5s linear infinite;
224
+ --animate-twinkle: ui-twinkle 6s ease-in-out infinite;
225
+ }
226
+
227
+ /* Border-color reset in @layer components (NOT @layer base). Under Vite the
228
+ * consuming app's entry `@import "tailwindcss"` AND this kit's own
229
+ * `@import "tailwindcss"` inject Tailwind preflight TWICE; the duplicate
230
+ * `* { border: 0 solid }` (→ currentColor) lands AFTER a reset inside
231
+ * @layer base and wins, so every plain `border` renders as the text color
232
+ * (white in dark mode). @layer components beats @layer base (preflight) yet
233
+ * loses to @layer utilities, so `border-red-500` / `border-destructive` still
234
+ * override. (Bundler-dedup'd builds like Turbopack only ever see one
235
+ * preflight, so this is harmless there.) */
236
+ @layer components {
237
+ *,
238
+ ::before,
239
+ ::after {
240
+ border-color: var(--border);
241
+ }
242
+ }
243
+
244
+ @layer base {
245
+ html,
246
+ body {
247
+ background: var(--background);
248
+ color: var(--foreground);
249
+ font-family: var(--font-sans);
250
+ min-height: 100vh;
251
+ font-feature-settings:
252
+ "rlig" 1,
253
+ "calt" 1;
254
+ }
255
+ /* Reserve scrollbar gutter unconditionally. Without this, short
256
+ * pages have no vertical scrollbar and long pages do — the resulting
257
+ * ~15px viewport-width difference shifts every `mx-auto max-w-7xl`
258
+ * container by ~7-8px between page navigations. Centered brand
259
+ * marks and nav rows visibly jump on route change. `stable` reserves
260
+ * the gutter even when no scrollbar is rendered, so layout is
261
+ * page-length-independent. */
262
+ html {
263
+ scrollbar-gutter: stable;
264
+ }
265
+
266
+ /* Pointer cursor on every actionable element. Tailwind v4 dropped
267
+ * the Preflight rule that gave `<button>` `cursor: pointer`,
268
+ * matching macOS/Windows native button behaviour. Most apps in this
269
+ * repo expect web-style pointer affordance on anything clickable
270
+ * (buttons, role="button" divs, summary, label[for]). Re-enable
271
+ * here so every plain `<button>` in a consumer app picks it up
272
+ * without per-component opt-in. `:not(:disabled)` keeps disabled
273
+ * buttons at default (paired with shadcn's `disabled:pointer-events-none`). */
274
+ button:not(:disabled),
275
+ [role="button"]:not([aria-disabled="true"]),
276
+ [role="menuitem"]:not([aria-disabled="true"]),
277
+ summary,
278
+ label[for] {
279
+ cursor: pointer;
280
+ }
281
+ body {
282
+ margin: 0;
283
+ }
284
+
285
+ /* Cross-browser custom scrollbars. Thin, theme-aware, the thumb
286
+ * brightens on hover. Same visual outcome as a Radix ScrollArea
287
+ * but with zero JS + no extra DOM. Applies to every scrollable
288
+ * element (sidebars, code blocks, the page itself); add
289
+ * `.scrollbar-default` on any element to opt out. */
290
+ * {
291
+ /* Firefox */
292
+ scrollbar-width: thin;
293
+ scrollbar-color: var(--border) transparent;
294
+ }
295
+ *::-webkit-scrollbar {
296
+ width: 10px;
297
+ height: 10px;
298
+ }
299
+ *::-webkit-scrollbar-track {
300
+ background: transparent;
301
+ }
302
+ *::-webkit-scrollbar-thumb {
303
+ background: var(--border);
304
+ border-radius: 999px;
305
+ border: 2px solid transparent;
306
+ background-clip: padding-box;
307
+ transition: background-color 120ms ease;
308
+ }
309
+ *::-webkit-scrollbar-thumb:hover {
310
+ background-color: var(--muted-foreground);
311
+ background-clip: padding-box;
312
+ }
313
+ *::-webkit-scrollbar-corner {
314
+ background: transparent;
315
+ }
316
+ .scrollbar-default {
317
+ scrollbar-width: auto;
318
+ scrollbar-color: auto;
319
+ }
320
+ .scrollbar-default::-webkit-scrollbar {
321
+ width: revert;
322
+ height: revert;
323
+ }
324
+ .scrollbar-default::-webkit-scrollbar-thumb {
325
+ background: revert;
326
+ border: revert;
327
+ border-radius: revert;
328
+ }
329
+ }
330
+
331
+ .font-tabular {
332
+ font-variant-numeric: tabular-nums;
333
+ }
334
+
335
+ /* ---- Cinematic ambient animations ----
336
+ * Slow drifting blobs, blinking caret, gradient pan. All transform-
337
+ * only (GPU-composited), all motion-safe via the `motion-safe:`
338
+ * Tailwind variant on the consumer. Durations are deliberately long
339
+ * (45–90s) so motion feels ambient, not "loud". */
340
+
341
+ @keyframes ui-mesh-drift-a {
342
+ 0%,
343
+ 100% {
344
+ transform: translate3d(0, 0, 0) scale(1);
345
+ }
346
+ 33% {
347
+ transform: translate3d(8vw, 4vh, 0) scale(1.05);
348
+ }
349
+ 66% {
350
+ transform: translate3d(-4vw, 8vh, 0) scale(0.95);
351
+ }
352
+ }
353
+ @keyframes ui-mesh-drift-b {
354
+ 0%,
355
+ 100% {
356
+ transform: translate3d(0, 0, 0) scale(1);
357
+ }
358
+ 33% {
359
+ transform: translate3d(-6vw, -5vh, 0) scale(1.08);
360
+ }
361
+ 66% {
362
+ transform: translate3d(4vw, -8vh, 0) scale(0.92);
363
+ }
364
+ }
365
+ @keyframes ui-mesh-drift-c {
366
+ 0%,
367
+ 100% {
368
+ transform: translate(-50%, 0) scale(1);
369
+ }
370
+ 50% {
371
+ transform: translate(-50%, -8vh) scale(1.1);
372
+ }
373
+ }
374
+ @keyframes ui-caret-blink {
375
+ 0%,
376
+ 50% {
377
+ opacity: 1;
378
+ }
379
+ 51%,
380
+ 100% {
381
+ opacity: 0;
382
+ }
383
+ }
384
+ @keyframes ui-gradient-pan {
385
+ 0%,
386
+ 100% {
387
+ background-position: 0% 50%;
388
+ }
389
+ 50% {
390
+ background-position: 100% 50%;
391
+ }
392
+ }
393
+ @keyframes ui-pulse-ring {
394
+ 0% {
395
+ transform: scale(0.6);
396
+ opacity: 0;
397
+ }
398
+ 20% {
399
+ transform: scale(0.7);
400
+ opacity: 0.4;
401
+ }
402
+ 100% {
403
+ transform: scale(1.3);
404
+ opacity: 0;
405
+ }
406
+ }
407
+ @keyframes ui-shimmer {
408
+ 0% {
409
+ transform: translateX(-100%);
410
+ }
411
+ 100% {
412
+ transform: translateX(200%);
413
+ }
414
+ }
415
+ @keyframes ui-twinkle {
416
+ /* Twinkle = restrained opacity ramp + a hint of scale. Caps at
417
+ * opacity 0.7 so the dot never fully solidifies (would read as a
418
+ * UI element, not ambient sparkle). Stays at 0 for 30% of the
419
+ * cycle so different dots feel arrhythmic. */
420
+ 0%,
421
+ 30%,
422
+ 100% {
423
+ opacity: 0;
424
+ transform: scale(0.6);
425
+ }
426
+ 55% {
427
+ opacity: 0.7;
428
+ transform: scale(1);
429
+ }
430
+ 75% {
431
+ opacity: 0.3;
432
+ transform: scale(0.85);
433
+ }
434
+ }
435
+
436
+ /* ---- Shiki dual-theme switch ----
437
+ * `HighlightedCode` (and any direct codeToHtml caller using
438
+ * `themes: { light, dark }` + `defaultColor: false`) emits inline
439
+ * styles like `--shiki-light:#xxx; --shiki-dark:#yyy` on every
440
+ * token span. The rules below resolve those CSS vars to the actual
441
+ * color/bg, picking the LIGHT set by default and DARK under `.dark`.
442
+ *
443
+ * Background goes ONLY on the <pre> — putting it on inner spans too
444
+ * paints the per-line `<span class="line">` elements with the theme
445
+ * bg, which then visibly banded against the surrounding wrapper
446
+ * (CodeBlock figure / CodeWindow chrome) because the colours are
447
+ * close-but-not-identical. Spans keep the color rule so per-token
448
+ * syntax highlighting still works. */
449
+ pre.shiki {
450
+ background-color: var(--shiki-light-bg) !important;
451
+ color: var(--shiki-light) !important;
452
+ }
453
+ pre.shiki span {
454
+ color: var(--shiki-light) !important;
455
+ }
456
+ .dark pre.shiki {
457
+ background-color: var(--shiki-dark-bg) !important;
458
+ color: var(--shiki-dark) !important;
459
+ }
460
+ .dark pre.shiki span {
461
+ color: var(--shiki-dark) !important;
462
+ }
463
+
464
+ /* Heading with hand-painted gradient text. Use on display-size <h1>
465
+ * elements: `<h1 className="text-gradient">…</h1>`. Combines well
466
+ * with motion-safe animate-gradient-pan for the cinematic pan effect. */
467
+ .text-gradient {
468
+ background-image: linear-gradient(
469
+ 100deg,
470
+ var(--foreground) 0%,
471
+ oklch(0.75 0.18 290) 50%,
472
+ var(--foreground) 100%
473
+ );
474
+ background-size: 200% auto;
475
+ -webkit-background-clip: text;
476
+ background-clip: text;
477
+ -webkit-text-fill-color: transparent;
478
+ color: transparent;
479
+ }
480
+
481
+ /* ---- Stagger entrance for grids ----
482
+ * `.stagger-in > *` fades + slides up with a 40ms delay between
483
+ * children. Used by DocCards (and reusable for any grid). Respects
484
+ * prefers-reduced-motion. */
485
+ @keyframes ui-stagger-in {
486
+ from {
487
+ opacity: 0;
488
+ transform: translateY(8px);
489
+ }
490
+ to {
491
+ opacity: 1;
492
+ transform: translateY(0);
493
+ }
494
+ }
495
+ .stagger-in > * {
496
+ animation: ui-stagger-in 320ms cubic-bezier(0.2, 0.8, 0.2, 1) backwards;
497
+ }
498
+ .stagger-in > *:nth-child(1) {
499
+ animation-delay: 0ms;
500
+ }
501
+ .stagger-in > *:nth-child(2) {
502
+ animation-delay: 40ms;
503
+ }
504
+ .stagger-in > *:nth-child(3) {
505
+ animation-delay: 80ms;
506
+ }
507
+ .stagger-in > *:nth-child(4) {
508
+ animation-delay: 120ms;
509
+ }
510
+ .stagger-in > *:nth-child(5) {
511
+ animation-delay: 160ms;
512
+ }
513
+ .stagger-in > *:nth-child(6) {
514
+ animation-delay: 200ms;
515
+ }
516
+ .stagger-in > *:nth-child(7) {
517
+ animation-delay: 240ms;
518
+ }
519
+ .stagger-in > *:nth-child(8) {
520
+ animation-delay: 280ms;
521
+ }
522
+ .stagger-in > *:nth-child(9) {
523
+ animation-delay: 320ms;
524
+ }
525
+ .stagger-in > *:nth-child(n + 10) {
526
+ animation-delay: 360ms;
527
+ }
528
+ @media (prefers-reduced-motion: reduce) {
529
+ .stagger-in > * {
530
+ animation: none;
531
+ }
532
+ }
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@voltro/ui-shadcn",
3
+ "version": "0.1.0",
4
+ "description": "Voltro's first-party shadcn/ui kit: Tailwind v4 design tokens (light + dark), 30+ primitives, layout compositions, styled widgets for the @voltro/ui seam, and the canonical theme/language preference-cookie helpers.",
5
+ "keywords": [
6
+ "voltro",
7
+ "typescript",
8
+ "framework"
9
+ ],
10
+ "license": "SEE LICENSE IN LICENSE",
11
+ "homepage": "https://voltro.dev",
12
+ "bugs": {
13
+ "email": "support@voltro.dev"
14
+ },
15
+ "author": {
16
+ "name": "Voltro UG",
17
+ "url": "https://voltro.dev"
18
+ },
19
+ "type": "module",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js",
24
+ "default": "./dist/index.js"
25
+ },
26
+ "./cn": {
27
+ "types": "./dist/cn.d.ts",
28
+ "import": "./dist/cn.js",
29
+ "default": "./dist/cn.js"
30
+ },
31
+ "./brand": {
32
+ "types": "./dist/brand.d.ts",
33
+ "import": "./dist/brand.js",
34
+ "default": "./dist/brand.js"
35
+ },
36
+ "./tokens.css": "./dist/tokens.css"
37
+ },
38
+ "main": "./dist/index.js",
39
+ "module": "./dist/index.js",
40
+ "types": "./dist/index.d.ts",
41
+ "sideEffects": [
42
+ "*.css"
43
+ ],
44
+ "engines": {
45
+ "node": ">=24.0.0"
46
+ },
47
+ "dependencies": {
48
+ "@radix-ui/react-dropdown-menu": "^2.1.20",
49
+ "@radix-ui/react-toggle": "^1.1.14",
50
+ "@radix-ui/react-toggle-group": "^1.1.15",
51
+ "@voltro/ui": "0.1.0",
52
+ "class-variance-authority": "^0.7.1",
53
+ "clsx": "^2.1.1",
54
+ "shiki": "^4.3.1",
55
+ "tailwind-merge": "^3.6.0",
56
+ "tw-animate-css": "^1.2.5"
57
+ },
58
+ "peerDependencies": {
59
+ "react": "^19.0.0"
60
+ },
61
+ "publishConfig": {
62
+ "access": "public"
63
+ }
64
+ }
@@ -0,0 +1,60 @@
1
+ # Voltro brand mark
2
+
3
+ The company + framework logo: a faceted lightning bolt ("volt") that
4
+ also reads as the **V** of Voltro. On-brand violet
5
+ (`oklch(0.55 0.25 290)` ≈ `#7C3AED` — the same hue as the text-gradient
6
+ and mesh backdrop). One source, every app.
7
+
8
+ ## React (the usual path)
9
+
10
+ ```tsx
11
+ import { VoltroMark, VoltroIcon, VoltroWordmark } from '@voltro/ui-shadcn'
12
+
13
+ // Header lock-up — text uses the app's font + currentColor (light/dark safe)
14
+ <VoltroWordmark size={28} />
15
+
16
+ // The bolt alone
17
+ <VoltroMark size={32} /> // gradient (default)
18
+ <VoltroMark size={20} variant="mono" /> // currentColor — inherits text colour
19
+ <VoltroMark size={20} variant="white" /> // on a coloured surface
20
+
21
+ // App / advertising icon (bolt on a rounded squircle, own background)
22
+ <VoltroIcon size={40} /> // brand: violet squircle + white bolt
23
+ <VoltroIcon size={40} tone="dark" /> // near-black squircle + glowing bolt
24
+ ```
25
+
26
+ `variant="mono"` paints in `currentColor`, so it's automatically correct
27
+ in light **and** dark mode — no theme branching. The `gradient` and
28
+ `icon` variants carry their own colour and sit on any background.
29
+
30
+ ## Static assets (favicons, OG images, app-store / ad icons)
31
+
32
+ Plain `.svg` in [`./assets`](./assets) for non-React use:
33
+
34
+ | File | Use |
35
+ |---|---|
36
+ | `voltro-favicon.svg` | **Favicon** — light/dark **adaptive** (`prefers-color-scheme`): the two-tone violet bolt on a squircle that's light in light mode, dark in dark mode |
37
+ | `voltro-mark.svg` | Gradient bolt, transparent — inline / marketing |
38
+ | `voltro-mark-mono.svg` | `currentColor` bolt — set `color`/`fill` to theme it |
39
+ | `voltro-icon.svg` | **App-store / ad icon** — two-tone bolt on the solid violet squircle (fixed brand colour, not adaptive) |
40
+ | `voltro-icon-dark.svg` | Dark squircle + glowing bolt — dark-only OG / placements |
41
+
42
+ ```html
43
+ <!-- Adaptive favicon — follows the browser/OS theme (the framework's web
44
+ head auto-links /favicon.svg, so just drop voltro-favicon.svg there). -->
45
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
46
+ ```
47
+
48
+ Use the **adaptive `voltro-favicon.svg`** as the favicon (it stays readable
49
+ in both light and dark browser chrome). Use the **fixed `voltro-icon.svg`**
50
+ where the icon needs one consistent colour regardless of theme — app-store
51
+ listings, ads, OG images. For PNG app-store sizes (1024², 512², 192², 180², …)
52
+ rasterise `voltro-icon.svg` at the target resolution — it's
53
+ resolution-independent, so one source covers every size.
54
+
55
+ ## Colour
56
+
57
+ Primary violet `#7C3AED` (`VOLTRO_VIOLET`). The faceted variants run a
58
+ hue-292 family: lit facet `#A874FF → #7C3AED`, shaded facet
59
+ `#6D28D9 → #4C1D95`. Need the raw geometry? `VOLTRO_BOLT_PATH`
60
+ (`{ full, front, back }`) exposes the path data to compose your own SVG.
@@ -0,0 +1,30 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64" fill="none" role="img" aria-label="Voltro">
2
+ <!-- Adaptive favicon. The two-tone VIOLET Voltro bolt is the star; the
3
+ squircle behind it follows the browser/OS light–dark preference
4
+ (prefers-color-scheme) so the brand bolt stays visible in both modes —
5
+ light squircle in light mode, dark squircle (+ glow) in dark mode.
6
+ Browsers honour @media inside an SVG favicon. For a non-adaptive,
7
+ always-violet app/store icon use voltro-icon.svg instead. -->
8
+ <style>
9
+ .sq { fill: #F2F0FB; } /* light squircle */
10
+ .glow { opacity: 0; }
11
+ .lit { fill: #7C3AED; } /* violet front facet */
12
+ .shad { fill: #5B21B6; } /* deep violet back facet */
13
+ @media (prefers-color-scheme: dark) {
14
+ .sq { fill: #18141F; } /* dark squircle */
15
+ .glow { opacity: 1; }
16
+ .lit { fill: #C4A6FF; }
17
+ .shad { fill: #9D5BFF; }
18
+ }
19
+ </style>
20
+ <defs>
21
+ <radialGradient id="voltroFaviconGlow" cx="0.5" cy="0.46" r="0.5">
22
+ <stop stop-color="#7C3AED" stop-opacity="0.5"/>
23
+ <stop offset="1" stop-color="#7C3AED" stop-opacity="0"/>
24
+ </radialGradient>
25
+ </defs>
26
+ <rect class="sq" width="64" height="64" rx="14.5"/>
27
+ <ellipse class="glow" cx="32" cy="30" rx="26" ry="26" fill="url(#voltroFaviconGlow)"/>
28
+ <path class="lit" transform="translate(8 8)" d="M28 5 13 27 22 26 20 43Z"/>
29
+ <path class="shad" transform="translate(8 8)" d="M28 5 20 43 35 21 26 22Z"/>
30
+ </svg>