@opencosmos/ui 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (260) hide show
  1. package/.claude/CLAUDE.md +239 -0
  2. package/README.md +161 -0
  3. package/dist/cli.mjs +151 -0
  4. package/dist/dates.d.mts +20 -0
  5. package/dist/dates.d.ts +20 -0
  6. package/dist/dates.js +240 -0
  7. package/dist/dates.js.map +1 -0
  8. package/dist/dates.mjs +203 -0
  9. package/dist/dates.mjs.map +1 -0
  10. package/dist/dnd.d.mts +126 -0
  11. package/dist/dnd.d.ts +126 -0
  12. package/dist/dnd.js +274 -0
  13. package/dist/dnd.js.map +1 -0
  14. package/dist/dnd.mjs +250 -0
  15. package/dist/dnd.mjs.map +1 -0
  16. package/dist/fontThemes-Dh8mtXES.d.mts +868 -0
  17. package/dist/fontThemes-Dh8mtXES.d.ts +868 -0
  18. package/dist/forms.d.mts +38 -0
  19. package/dist/forms.d.ts +38 -0
  20. package/dist/forms.js +198 -0
  21. package/dist/forms.js.map +1 -0
  22. package/dist/forms.mjs +159 -0
  23. package/dist/forms.mjs.map +1 -0
  24. package/dist/hooks-1b8WaQf1.d.mts +225 -0
  25. package/dist/hooks-CKW8vE9H.d.ts +225 -0
  26. package/dist/hooks.d.mts +3 -0
  27. package/dist/hooks.d.ts +3 -0
  28. package/dist/hooks.js +971 -0
  29. package/dist/hooks.js.map +1 -0
  30. package/dist/hooks.mjs +943 -0
  31. package/dist/hooks.mjs.map +1 -0
  32. package/dist/index-DscTIrZ2.d.mts +29 -0
  33. package/dist/index-DscTIrZ2.d.ts +29 -0
  34. package/dist/index.d.mts +3382 -0
  35. package/dist/index.d.ts +3382 -0
  36. package/dist/index.js +15146 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/index.mjs +14802 -0
  39. package/dist/index.mjs.map +1 -0
  40. package/dist/providers-CXPDMsl7.d.mts +30 -0
  41. package/dist/providers-Dn_Msjvz.d.ts +30 -0
  42. package/dist/providers.d.mts +3 -0
  43. package/dist/providers.d.ts +3 -0
  44. package/dist/providers.js +1885 -0
  45. package/dist/providers.js.map +1 -0
  46. package/dist/providers.mjs +1859 -0
  47. package/dist/providers.mjs.map +1 -0
  48. package/dist/tables.d.mts +10 -0
  49. package/dist/tables.d.ts +10 -0
  50. package/dist/tables.js +248 -0
  51. package/dist/tables.js.map +1 -0
  52. package/dist/tables.mjs +218 -0
  53. package/dist/tables.mjs.map +1 -0
  54. package/dist/tokens.d.mts +1065 -0
  55. package/dist/tokens.d.ts +1065 -0
  56. package/dist/tokens.js +2637 -0
  57. package/dist/tokens.js.map +1 -0
  58. package/dist/tokens.mjs +2555 -0
  59. package/dist/tokens.mjs.map +1 -0
  60. package/dist/utils-CIIM7dAC.d.ts +986 -0
  61. package/dist/utils-Cs04sxth.d.mts +986 -0
  62. package/dist/utils.d.mts +4 -0
  63. package/dist/utils.d.ts +4 -0
  64. package/dist/utils.js +874 -0
  65. package/dist/utils.js.map +1 -0
  66. package/dist/utils.mjs +806 -0
  67. package/dist/utils.mjs.map +1 -0
  68. package/dist/validation-Bj1ye-v_.d.mts +114 -0
  69. package/dist/validation-Bj1ye-v_.d.ts +114 -0
  70. package/dist/webgl.d.mts +104 -0
  71. package/dist/webgl.d.ts +104 -0
  72. package/dist/webgl.js +226 -0
  73. package/dist/webgl.js.map +1 -0
  74. package/dist/webgl.mjs +195 -0
  75. package/dist/webgl.mjs.map +1 -0
  76. package/package.json +267 -0
  77. package/src/cli.ts +206 -0
  78. package/src/component-registry.ts +183 -0
  79. package/src/components/actions/Button.test.tsx +61 -0
  80. package/src/components/actions/Button.tsx +70 -0
  81. package/src/components/actions/Link.tsx +78 -0
  82. package/src/components/actions/Magnetic.tsx +68 -0
  83. package/src/components/actions/Toggle.test.tsx +40 -0
  84. package/src/components/actions/Toggle.tsx +47 -0
  85. package/src/components/actions/ToggleGroup.tsx +70 -0
  86. package/src/components/actions/index.ts +5 -0
  87. package/src/components/backgrounds/FaultyTerminal.tsx +426 -0
  88. package/src/components/backgrounds/OrbBackground.tsx +424 -0
  89. package/src/components/backgrounds/WarpBackground.tsx +358 -0
  90. package/src/components/backgrounds/index.ts +3 -0
  91. package/src/components/blocks/Hero.tsx +142 -0
  92. package/src/components/blocks/social/OpenGraphCard.tsx +243 -0
  93. package/src/components/cursor/SplashCursor.tsx +1315 -0
  94. package/src/components/cursor/TargetCursor.tsx +187 -0
  95. package/src/components/cursor/index.ts +2 -0
  96. package/src/components/data-display/AspectImage.tsx +73 -0
  97. package/src/components/data-display/Avatar.test.tsx +35 -0
  98. package/src/components/data-display/Avatar.tsx +55 -0
  99. package/src/components/data-display/Badge.test.tsx +43 -0
  100. package/src/components/data-display/Badge.tsx +84 -0
  101. package/src/components/data-display/Brand.tsx +123 -0
  102. package/src/components/data-display/Calendar.tsx +70 -0
  103. package/src/components/data-display/Card.test.tsx +92 -0
  104. package/src/components/data-display/Card.tsx +115 -0
  105. package/src/components/data-display/Code.tsx +210 -0
  106. package/src/components/data-display/CollapsibleCodeBlock.tsx +238 -0
  107. package/src/components/data-display/DataTable.tsx +119 -0
  108. package/src/components/data-display/DescriptionList.tsx +41 -0
  109. package/src/components/data-display/GitHubIcon.tsx +44 -0
  110. package/src/components/data-display/Heading.test.tsx +36 -0
  111. package/src/components/data-display/Heading.tsx +83 -0
  112. package/src/components/data-display/StatCard.tsx +195 -0
  113. package/src/components/data-display/Table.tsx +133 -0
  114. package/src/components/data-display/Text.test.tsx +48 -0
  115. package/src/components/data-display/Text.tsx +144 -0
  116. package/src/components/data-display/Timeline.tsx +194 -0
  117. package/src/components/data-display/TreeView.tsx +226 -0
  118. package/src/components/data-display/Typewriter.tsx +119 -0
  119. package/src/components/data-display/VariableWeightText.tsx +130 -0
  120. package/src/components/data-display/index.ts +19 -0
  121. package/src/components/feedback/Alert.test.tsx +44 -0
  122. package/src/components/feedback/Alert.tsx +65 -0
  123. package/src/components/feedback/EmptyState.tsx +113 -0
  124. package/src/components/feedback/Progress.test.tsx +60 -0
  125. package/src/components/feedback/Progress.tsx +30 -0
  126. package/src/components/feedback/ProgressBar.tsx +158 -0
  127. package/src/components/feedback/Skeleton.test.tsx +39 -0
  128. package/src/components/feedback/Skeleton.tsx +45 -0
  129. package/src/components/feedback/Sonner.tsx +28 -0
  130. package/src/components/feedback/Spinner.test.tsx +33 -0
  131. package/src/components/feedback/Spinner.tsx +99 -0
  132. package/src/components/feedback/Stepper.tsx +307 -0
  133. package/src/components/feedback/Toast/Toast.tsx +243 -0
  134. package/src/components/feedback/Toast/index.ts +2 -0
  135. package/src/components/feedback/index.ts +9 -0
  136. package/src/components/forms/Checkbox.test.tsx +40 -0
  137. package/src/components/forms/Checkbox.tsx +31 -0
  138. package/src/components/forms/ColorPicker.tsx +118 -0
  139. package/src/components/forms/Combobox.tsx +96 -0
  140. package/src/components/forms/DragDrop.tsx +440 -0
  141. package/src/components/forms/FileUpload.tsx +252 -0
  142. package/src/components/forms/FilterButton.tsx +65 -0
  143. package/src/components/forms/Form.tsx +197 -0
  144. package/src/components/forms/Input.test.tsx +46 -0
  145. package/src/components/forms/Input.tsx +43 -0
  146. package/src/components/forms/InputOTP.tsx +81 -0
  147. package/src/components/forms/Label.test.tsx +20 -0
  148. package/src/components/forms/Label.tsx +25 -0
  149. package/src/components/forms/RadioGroup.tsx +51 -0
  150. package/src/components/forms/SearchBar.tsx +215 -0
  151. package/src/components/forms/Select.test.tsx +118 -0
  152. package/src/components/forms/Select.tsx +274 -0
  153. package/src/components/forms/Slider.tsx +29 -0
  154. package/src/components/forms/Switch.test.tsx +76 -0
  155. package/src/components/forms/Switch.tsx +30 -0
  156. package/src/components/forms/TextField.tsx +152 -0
  157. package/src/components/forms/Textarea.test.tsx +41 -0
  158. package/src/components/forms/Textarea.tsx +29 -0
  159. package/src/components/forms/ThemeSwitcher.tsx +290 -0
  160. package/src/components/forms/ThemeToggle.tsx +151 -0
  161. package/src/components/forms/index.ts +19 -0
  162. package/src/components/layout/Accordion.test.tsx +66 -0
  163. package/src/components/layout/Accordion.tsx +64 -0
  164. package/src/components/layout/AspectRatio.tsx +7 -0
  165. package/src/components/layout/Carousel.tsx +277 -0
  166. package/src/components/layout/Collapsible.test.tsx +40 -0
  167. package/src/components/layout/Collapsible.tsx +31 -0
  168. package/src/components/layout/Container.test.tsx +45 -0
  169. package/src/components/layout/Container.tsx +99 -0
  170. package/src/components/layout/CustomizerPanel.tsx +400 -0
  171. package/src/components/layout/DatePicker.tsx +57 -0
  172. package/src/components/layout/Footer/Footer.tsx +175 -0
  173. package/src/components/layout/Footer/index.ts +2 -0
  174. package/src/components/layout/GlassSurface.tsx +82 -0
  175. package/src/components/layout/Grid.test.tsx +31 -0
  176. package/src/components/layout/Grid.tsx +130 -0
  177. package/src/components/layout/Header/Header.tsx +450 -0
  178. package/src/components/layout/Header/index.ts +2 -0
  179. package/src/components/layout/PageLayout.tsx +180 -0
  180. package/src/components/layout/PageTemplate.tsx +158 -0
  181. package/src/components/layout/Resizable.tsx +48 -0
  182. package/src/components/layout/ScrollArea.tsx +53 -0
  183. package/src/components/layout/Separator.test.tsx +28 -0
  184. package/src/components/layout/Separator.tsx +29 -0
  185. package/src/components/layout/Sidebar.tsx +171 -0
  186. package/src/components/layout/Stack.test.tsx +41 -0
  187. package/src/components/layout/Stack.tsx +89 -0
  188. package/src/components/layout/glass-surface.css +60 -0
  189. package/src/components/layout/index.ts +18 -0
  190. package/src/components/motion/AnimatedBeam.tsx +159 -0
  191. package/src/components/navigation/Breadcrumb.test.tsx +57 -0
  192. package/src/components/navigation/Breadcrumb.tsx +119 -0
  193. package/src/components/navigation/Breadcrumbs.tsx +221 -0
  194. package/src/components/navigation/Command.tsx +159 -0
  195. package/src/components/navigation/Menubar.tsx +115 -0
  196. package/src/components/navigation/NavLink.tsx +55 -0
  197. package/src/components/navigation/NavigationMenu.tsx +125 -0
  198. package/src/components/navigation/Pagination.tsx +121 -0
  199. package/src/components/navigation/SecondaryNav.tsx +100 -0
  200. package/src/components/navigation/Tabs.test.tsx +47 -0
  201. package/src/components/navigation/Tabs.tsx +60 -0
  202. package/src/components/navigation/TertiaryNav.tsx +90 -0
  203. package/src/components/navigation/index.ts +10 -0
  204. package/src/components/overlays/AlertDialog.test.tsx +69 -0
  205. package/src/components/overlays/AlertDialog.tsx +166 -0
  206. package/src/components/overlays/ContextMenu.tsx +243 -0
  207. package/src/components/overlays/Dialog.test.tsx +79 -0
  208. package/src/components/overlays/Dialog.tsx +158 -0
  209. package/src/components/overlays/Drawer.tsx +128 -0
  210. package/src/components/overlays/Dropdown.tsx +253 -0
  211. package/src/components/overlays/DropdownMenu.tsx +242 -0
  212. package/src/components/overlays/HoverCard.tsx +32 -0
  213. package/src/components/overlays/Modal.tsx +250 -0
  214. package/src/components/overlays/NotificationCenter.tsx +364 -0
  215. package/src/components/overlays/Popover.test.tsx +40 -0
  216. package/src/components/overlays/Popover.tsx +46 -0
  217. package/src/components/overlays/Sheet.tsx +163 -0
  218. package/src/components/overlays/Tooltip.test.tsx +33 -0
  219. package/src/components/overlays/Tooltip.tsx +32 -0
  220. package/src/components/overlays/index.ts +12 -0
  221. package/src/dates.ts +2 -0
  222. package/src/dnd.ts +1 -0
  223. package/src/forms.ts +1 -0
  224. package/src/globals.css +187 -0
  225. package/src/hooks/index.ts +6 -0
  226. package/src/hooks/useForm.ts +247 -0
  227. package/src/hooks/useMotionPreference.test.ts +102 -0
  228. package/src/hooks/useMotionPreference.ts +78 -0
  229. package/src/hooks/useTheme.ts +58 -0
  230. package/src/hooks.ts +9 -0
  231. package/src/index.ts +168 -0
  232. package/src/lib/animations.ts +356 -0
  233. package/src/lib/breadcrumbs.ts +94 -0
  234. package/src/lib/colors.ts +493 -0
  235. package/src/lib/store/customizer.ts +482 -0
  236. package/src/lib/store/index.ts +3 -0
  237. package/src/lib/store/theme.ts +55 -0
  238. package/src/lib/syntax-parser/index.ts +50 -0
  239. package/src/lib/syntax-parser/patterns.ts +64 -0
  240. package/src/lib/syntax-parser/tokenizer.ts +117 -0
  241. package/src/lib/syntax-parser/types.ts +27 -0
  242. package/src/lib/utils.ts +6 -0
  243. package/src/lib/validation.ts +204 -0
  244. package/src/lib/webgl/Color.ts +11 -0
  245. package/src/lib/webgl/Mesh.ts +41 -0
  246. package/src/lib/webgl/Program.ts +118 -0
  247. package/src/lib/webgl/Renderer.ts +51 -0
  248. package/src/lib/webgl/Triangle.ts +27 -0
  249. package/src/lib/webgl/Vec3.ts +18 -0
  250. package/src/lib/webgl/index.ts +13 -0
  251. package/src/nativewind-env.d.ts +1 -0
  252. package/src/providers/ThemeProvider.tsx +461 -0
  253. package/src/providers/index.ts +1 -0
  254. package/src/providers.ts +7 -0
  255. package/src/tables.ts +1 -0
  256. package/src/test/setup.ts +39 -0
  257. package/src/theme.css +158 -0
  258. package/src/tokens.ts +7 -0
  259. package/src/utils.ts +12 -0
  260. package/src/webgl.ts +1 -0
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Syntax Tokenizer
3
+ * Core tokenization logic for parsing code into syntax tokens
4
+ */
5
+
6
+ import { SyntaxToken, Language } from './types';
7
+ import { TOKEN_PATTERNS } from './patterns';
8
+
9
+ interface Match {
10
+ text: string;
11
+ type: SyntaxToken['type'];
12
+ index: number;
13
+ }
14
+
15
+ /**
16
+ * Tokenizes source code into an array of syntax tokens
17
+ *
18
+ * @param code - The source code to tokenize
19
+ * @param language - The programming language (currently only affects metadata, all use same patterns)
20
+ * @returns Array of syntax tokens with text and type information
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * const tokens = tokenize('const greeting = "Hello";');
25
+ * // Returns:
26
+ * // [
27
+ * // { text: 'const', type: 'keyword' },
28
+ * // { text: ' ', type: 'plain' },
29
+ * // { text: 'greeting', type: 'plain' },
30
+ * // { text: ' ', type: 'plain' },
31
+ * // { text: '=', type: 'operator' },
32
+ * // { text: ' ', type: 'plain' },
33
+ * // { text: '"Hello"', type: 'string' },
34
+ * // { text: ';', type: 'punctuation' },
35
+ * // ]
36
+ * ```
37
+ */
38
+ export function tokenize(code: string, language: Language = 'typescript'): SyntaxToken[] {
39
+ // If empty, return plain token
40
+ if (!code || code.trim() === '') {
41
+ return [{ text: code, type: 'plain' }];
42
+ }
43
+
44
+ // Collect all matches from all patterns
45
+ const matches: Match[] = [];
46
+
47
+ for (const { type, pattern } of TOKEN_PATTERNS) {
48
+ // Reset regex lastIndex
49
+ pattern.lastIndex = 0;
50
+
51
+ let match;
52
+ while ((match = pattern.exec(code)) !== null) {
53
+ matches.push({
54
+ text: match[0],
55
+ type,
56
+ index: match.index,
57
+ });
58
+ }
59
+ }
60
+
61
+ // Sort matches by position
62
+ matches.sort((a, b) => a.index - b.index);
63
+
64
+ // Remove overlapping matches (keep first match at each position)
65
+ const nonOverlapping: Match[] = [];
66
+ let lastEnd = 0;
67
+
68
+ for (const match of matches) {
69
+ if (match.index >= lastEnd) {
70
+ nonOverlapping.push(match);
71
+ lastEnd = match.index + match.text.length;
72
+ }
73
+ }
74
+
75
+ // Build tokens array, filling gaps with plain text
76
+ const tokens: SyntaxToken[] = [];
77
+ let position = 0;
78
+
79
+ for (const match of nonOverlapping) {
80
+ // Add plain text before this match
81
+ if (match.index > position) {
82
+ const plainText = code.slice(position, match.index);
83
+ tokens.push({ text: plainText, type: 'plain' });
84
+ }
85
+
86
+ // Add the matched token
87
+ tokens.push({ text: match.text, type: match.type });
88
+ position = match.index + match.text.length;
89
+ }
90
+
91
+ // Add any remaining plain text
92
+ if (position < code.length) {
93
+ tokens.push({ text: code.slice(position), type: 'plain' });
94
+ }
95
+
96
+ return tokens;
97
+ }
98
+
99
+ /**
100
+ * Detects the language from code content or file extension
101
+ * Currently returns 'typescript' for all cases as patterns support TS/JS/JSX/TSX
102
+ *
103
+ * @param code - The source code
104
+ * @param filename - Optional filename for extension detection
105
+ * @returns Detected language
106
+ */
107
+ export function detectLanguage(code: string, filename?: string): Language {
108
+ // For now, treat everything as TypeScript since our patterns support all variants
109
+ // Future: Could detect based on JSX syntax or file extension
110
+ if (filename) {
111
+ if (filename.endsWith('.tsx')) return 'tsx';
112
+ if (filename.endsWith('.jsx')) return 'jsx';
113
+ if (filename.endsWith('.js')) return 'javascript';
114
+ }
115
+
116
+ return 'typescript';
117
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Syntax Parser Types
3
+ * Type definitions for the syntax parsing system
4
+ */
5
+
6
+ export type SyntaxType =
7
+ | 'comment'
8
+ | 'keyword'
9
+ | 'function'
10
+ | 'string'
11
+ | 'number'
12
+ | 'boolean'
13
+ | 'operator'
14
+ | 'property'
15
+ | 'className'
16
+ | 'tag'
17
+ | 'attribute'
18
+ | 'variable'
19
+ | 'punctuation'
20
+ | 'plain';
21
+
22
+ export interface SyntaxToken {
23
+ text: string;
24
+ type: SyntaxType;
25
+ }
26
+
27
+ export type Language = 'typescript' | 'javascript' | 'tsx' | 'jsx';
@@ -0,0 +1,6 @@
1
+ import { type ClassValue, clsx } from "clsx";
2
+ import { twMerge } from "tailwind-merge";
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs));
6
+ }
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Form Validation Utilities
3
+ *
4
+ * Simple validation helpers for form fields.
5
+ * Works seamlessly with FormField and other form components.
6
+ */
7
+
8
+ export type ValidationRule = {
9
+ validate: (value: any) => boolean;
10
+ message: string;
11
+ };
12
+
13
+ export type FieldValidation = {
14
+ required?: boolean | string;
15
+ minLength?: { value: number; message: string };
16
+ maxLength?: { value: number; message: string };
17
+ pattern?: { value: RegExp; message: string };
18
+ custom?: ValidationRule[];
19
+ };
20
+
21
+ export type FormErrors = Record<string, string | undefined>;
22
+
23
+ /**
24
+ * Common validation patterns
25
+ */
26
+ export const patterns = {
27
+ email: {
28
+ value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
29
+ message: 'Invalid email address',
30
+ },
31
+ url: {
32
+ value: /^https?:\/\/.+\..+/,
33
+ message: 'Invalid URL',
34
+ },
35
+ phone: {
36
+ value: /^[\d\s\-\+\(\)]+$/,
37
+ message: 'Invalid phone number',
38
+ },
39
+ alphanumeric: {
40
+ value: /^[a-zA-Z0-9]+$/,
41
+ message: 'Only letters and numbers allowed',
42
+ },
43
+ noSpaces: {
44
+ value: /^\S+$/,
45
+ message: 'Spaces are not allowed',
46
+ },
47
+ };
48
+
49
+ /**
50
+ * Validate a single field value against validation rules
51
+ *
52
+ * @param value - The field value to validate
53
+ * @param rules - Validation rules to apply
54
+ * @returns Error message if validation fails, undefined if valid
55
+ *
56
+ * @example
57
+ * ```tsx
58
+ * const error = validateField(email, {
59
+ * required: true,
60
+ * pattern: patterns.email
61
+ * });
62
+ * ```
63
+ */
64
+ export function validateField(
65
+ value: any,
66
+ rules: FieldValidation
67
+ ): string | undefined {
68
+ // Required check
69
+ if (rules.required) {
70
+ const isEmpty =
71
+ value === undefined ||
72
+ value === null ||
73
+ value === '' ||
74
+ (Array.isArray(value) && value.length === 0);
75
+
76
+ if (isEmpty) {
77
+ return typeof rules.required === 'string'
78
+ ? rules.required
79
+ : 'This field is required';
80
+ }
81
+ }
82
+
83
+ // Skip other validations if value is empty and not required
84
+ if (!value && !rules.required) {
85
+ return undefined;
86
+ }
87
+
88
+ // Min length check
89
+ if (rules.minLength && value.length < rules.minLength.value) {
90
+ return rules.minLength.message;
91
+ }
92
+
93
+ // Max length check
94
+ if (rules.maxLength && value.length > rules.maxLength.value) {
95
+ return rules.maxLength.message;
96
+ }
97
+
98
+ // Pattern check
99
+ if (rules.pattern && !rules.pattern.value.test(value)) {
100
+ return rules.pattern.message;
101
+ }
102
+
103
+ // Custom validations
104
+ if (rules.custom) {
105
+ for (const rule of rules.custom) {
106
+ if (!rule.validate(value)) {
107
+ return rule.message;
108
+ }
109
+ }
110
+ }
111
+
112
+ return undefined;
113
+ }
114
+
115
+ /**
116
+ * Validate all fields in a form
117
+ *
118
+ * @param values - Object containing all form field values
119
+ * @param validations - Object containing validation rules for each field
120
+ * @returns Object containing errors for each field
121
+ *
122
+ * @example
123
+ * ```tsx
124
+ * const errors = validateForm(
125
+ * { email: 'test', password: '123' },
126
+ * {
127
+ * email: { required: true, pattern: patterns.email },
128
+ * password: { required: true, minLength: { value: 8, message: 'Min 8 chars' } }
129
+ * }
130
+ * );
131
+ * // errors = { email: 'Invalid email address', password: 'Min 8 chars' }
132
+ * ```
133
+ */
134
+ export function validateForm(
135
+ values: Record<string, any>,
136
+ validations: Record<string, FieldValidation>
137
+ ): FormErrors {
138
+ const errors: FormErrors = {};
139
+
140
+ for (const [field, rules] of Object.entries(validations)) {
141
+ const error = validateField(values[field], rules);
142
+ if (error) {
143
+ errors[field] = error;
144
+ }
145
+ }
146
+
147
+ return errors;
148
+ }
149
+
150
+ /**
151
+ * Check if form has any errors
152
+ *
153
+ * @param errors - Form errors object
154
+ * @returns true if there are any errors
155
+ *
156
+ * @example
157
+ * ```tsx
158
+ * if (hasErrors(errors)) {
159
+ * console.log('Form has errors');
160
+ * }
161
+ * ```
162
+ */
163
+ export function hasErrors(errors: FormErrors): boolean {
164
+ return Object.values(errors).some((error) => error !== undefined);
165
+ }
166
+
167
+ /**
168
+ * Common validation rule builders
169
+ */
170
+ export const rules = {
171
+ required: (message = 'This field is required'): FieldValidation => ({
172
+ required: message,
173
+ }),
174
+
175
+ email: (message = 'Invalid email address'): FieldValidation => ({
176
+ pattern: { value: patterns.email.value, message },
177
+ }),
178
+
179
+ minLength: (length: number, message?: string): FieldValidation => ({
180
+ minLength: {
181
+ value: length,
182
+ message: message || `Minimum ${length} characters required`,
183
+ },
184
+ }),
185
+
186
+ maxLength: (length: number, message?: string): FieldValidation => ({
187
+ maxLength: {
188
+ value: length,
189
+ message: message || `Maximum ${length} characters allowed`,
190
+ },
191
+ }),
192
+
193
+ match: (
194
+ otherValue: any,
195
+ message = 'Values do not match'
196
+ ): FieldValidation => ({
197
+ custom: [
198
+ {
199
+ validate: (value) => value === otherValue,
200
+ message,
201
+ },
202
+ ],
203
+ }),
204
+ };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Minimal Color implementation for WebGL shader uniforms.
3
+ * Replaces OGL's Color — stores three floats (r, g, b).
4
+ */
5
+ export class Color {
6
+ data: Float32Array;
7
+
8
+ constructor(r = 0, g = 0, b = 0) {
9
+ this.data = new Float32Array([r, g, b]);
10
+ }
11
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Minimal Mesh that binds geometry attributes and draws.
3
+ * Replaces OGL's Mesh — combines a Triangle geometry with a Program.
4
+ */
5
+ import type { Triangle } from './Triangle';
6
+ import type { Program } from './Program';
7
+
8
+ export class Mesh {
9
+ gl: WebGLRenderingContext;
10
+ geometry: Triangle;
11
+ program: Program;
12
+
13
+ constructor(gl: WebGLRenderingContext, { geometry, program }: { geometry: Triangle; program: Program }) {
14
+ this.gl = gl;
15
+ this.geometry = geometry;
16
+ this.program = program;
17
+ }
18
+
19
+ draw(): void {
20
+ const { gl, geometry, program } = this;
21
+
22
+ program.use();
23
+ program.uploadUniforms();
24
+
25
+ const posLoc = program.getAttribLocation('position');
26
+ if (posLoc >= 0) {
27
+ gl.bindBuffer(gl.ARRAY_BUFFER, geometry.positionBuffer);
28
+ gl.enableVertexAttribArray(posLoc);
29
+ gl.vertexAttribPointer(posLoc, geometry.positionSize, gl.FLOAT, false, 0, 0);
30
+ }
31
+
32
+ const uvLoc = program.getAttribLocation('uv');
33
+ if (uvLoc >= 0) {
34
+ gl.bindBuffer(gl.ARRAY_BUFFER, geometry.uvBuffer);
35
+ gl.enableVertexAttribArray(uvLoc);
36
+ gl.vertexAttribPointer(uvLoc, geometry.uvSize, gl.FLOAT, false, 0, 0);
37
+ }
38
+
39
+ gl.drawArrays(gl.TRIANGLES, 0, geometry.count);
40
+ }
41
+ }
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Minimal shader Program for compiling GLSL and managing uniforms.
3
+ * Replaces OGL's Program — handles compilation, linking, and uniform uploads.
4
+ *
5
+ * Supported uniform value types:
6
+ * number → gl.uniform1f
7
+ * boolean → gl.uniform1i (0 or 1, for GLSL bool)
8
+ * Float32Array → gl.uniform{2,3,4}fv (by length)
9
+ * Vec3 / Color → gl.uniform3fv (they extend Float32Array)
10
+ */
11
+
12
+ interface UniformDef {
13
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
14
+ value: any;
15
+ }
16
+
17
+ interface ProgramOptions {
18
+ vertex: string;
19
+ fragment: string;
20
+ uniforms?: Record<string, UniformDef>;
21
+ }
22
+
23
+ function compile(gl: WebGLRenderingContext, type: number, source: string): WebGLShader {
24
+ const shader = gl.createShader(type)!;
25
+ gl.shaderSource(shader, source);
26
+ gl.compileShader(shader);
27
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
28
+ const info = gl.getShaderInfoLog(shader);
29
+ gl.deleteShader(shader);
30
+ throw new Error(`Shader compile error: ${info}`);
31
+ }
32
+ return shader;
33
+ }
34
+
35
+ export class Program {
36
+ gl: WebGLRenderingContext;
37
+ program: WebGLProgram;
38
+ uniforms: Record<string, UniformDef>;
39
+ private uniformLocations: Map<string, WebGLUniformLocation>;
40
+ private attributeLocations: Map<string, number>;
41
+
42
+ constructor(gl: WebGLRenderingContext, { vertex, fragment, uniforms = {} }: ProgramOptions) {
43
+ this.gl = gl;
44
+ this.uniforms = uniforms;
45
+
46
+ const vs = compile(gl, gl.VERTEX_SHADER, vertex);
47
+ const fs = compile(gl, gl.FRAGMENT_SHADER, fragment);
48
+
49
+ this.program = gl.createProgram()!;
50
+ gl.attachShader(this.program, vs);
51
+ gl.attachShader(this.program, fs);
52
+ gl.linkProgram(this.program);
53
+
54
+ if (!gl.getProgramParameter(this.program, gl.LINK_STATUS)) {
55
+ const info = gl.getProgramInfoLog(this.program);
56
+ throw new Error(`Program link error: ${info}`);
57
+ }
58
+
59
+ gl.deleteShader(vs);
60
+ gl.deleteShader(fs);
61
+
62
+ // Cache uniform locations (only for uniforms the shader actually uses)
63
+ this.uniformLocations = new Map();
64
+ for (const name in uniforms) {
65
+ const loc = gl.getUniformLocation(this.program, name);
66
+ if (loc !== null) {
67
+ this.uniformLocations.set(name, loc);
68
+ }
69
+ }
70
+
71
+ // Cache attribute locations
72
+ this.attributeLocations = new Map();
73
+ const numAttribs = gl.getProgramParameter(this.program, gl.ACTIVE_ATTRIBUTES);
74
+ for (let i = 0; i < numAttribs; i++) {
75
+ const attrib = gl.getActiveAttrib(this.program, i);
76
+ if (attrib) {
77
+ this.attributeLocations.set(attrib.name, gl.getAttribLocation(this.program, attrib.name));
78
+ }
79
+ }
80
+ }
81
+
82
+ use(): void {
83
+ this.gl.useProgram(this.program);
84
+ }
85
+
86
+ getAttribLocation(name: string): number {
87
+ return this.attributeLocations.get(name) ?? -1;
88
+ }
89
+
90
+ uploadUniforms(): void {
91
+ const gl = this.gl;
92
+ for (const [name, loc] of this.uniformLocations) {
93
+ const value = this.uniforms[name]?.value;
94
+ if (value === undefined || value === null) continue;
95
+
96
+ if (typeof value === 'boolean') {
97
+ gl.uniform1i(loc, value ? 1 : 0);
98
+ } else if (typeof value === 'number') {
99
+ gl.uniform1f(loc, value);
100
+ } else if (value instanceof Float32Array) {
101
+ switch (value.length) {
102
+ case 1: gl.uniform1fv(loc, value); break;
103
+ case 2: gl.uniform2fv(loc, value); break;
104
+ case 3: gl.uniform3fv(loc, value); break;
105
+ case 4: gl.uniform4fv(loc, value); break;
106
+ }
107
+ } else if (value && value.data instanceof Float32Array) {
108
+ // Vec3, Color — classes that wrap a Float32Array in .data
109
+ const arr = value.data as Float32Array;
110
+ switch (arr.length) {
111
+ case 2: gl.uniform2fv(loc, arr); break;
112
+ case 3: gl.uniform3fv(loc, arr); break;
113
+ case 4: gl.uniform4fv(loc, arr); break;
114
+ }
115
+ }
116
+ }
117
+ }
118
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Minimal WebGL Renderer that creates a canvas and context.
3
+ * Replaces OGL's Renderer — provides context creation, resize, and render dispatch.
4
+ */
5
+ import type { Mesh } from './Mesh';
6
+
7
+ interface RendererOptions {
8
+ alpha?: boolean;
9
+ premultipliedAlpha?: boolean;
10
+ dpr?: number;
11
+ }
12
+
13
+ /** WebGLRenderingContext with canvas narrowed to HTMLCanvasElement. */
14
+ export type GL = WebGLRenderingContext & { readonly canvas: HTMLCanvasElement };
15
+
16
+ export class Renderer {
17
+ gl: GL;
18
+ private dpr: number;
19
+
20
+ constructor({ alpha = false, premultipliedAlpha = true, dpr = 1 }: RendererOptions = {}) {
21
+ const canvas = document.createElement('canvas');
22
+ this.dpr = dpr;
23
+
24
+ const attrs: WebGLContextAttributes = {
25
+ alpha,
26
+ premultipliedAlpha,
27
+ antialias: false,
28
+ preserveDrawingBuffer: false,
29
+ };
30
+
31
+ const gl =
32
+ (canvas.getContext('webgl2', attrs) as GL | null) ??
33
+ (canvas.getContext('webgl', attrs) as GL | null);
34
+
35
+ if (!gl) throw new Error('WebGL not supported');
36
+ this.gl = gl;
37
+ }
38
+
39
+ setSize(width: number, height: number): void {
40
+ const canvas = this.gl.canvas;
41
+ canvas.width = width * this.dpr;
42
+ canvas.height = height * this.dpr;
43
+ canvas.style.width = width + 'px';
44
+ canvas.style.height = height + 'px';
45
+ this.gl.viewport(0, 0, canvas.width, canvas.height);
46
+ }
47
+
48
+ render({ scene }: { scene: Mesh }): void {
49
+ scene.draw();
50
+ }
51
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Full-screen triangle geometry for fragment-shader-only rendering.
3
+ * Replaces OGL's Triangle — a single oversized triangle that clips to the viewport.
4
+ */
5
+ export class Triangle {
6
+ gl: WebGLRenderingContext;
7
+ positionBuffer: WebGLBuffer;
8
+ uvBuffer: WebGLBuffer;
9
+ positionSize = 2;
10
+ uvSize = 2;
11
+ count = 3;
12
+
13
+ constructor(gl: WebGLRenderingContext) {
14
+ this.gl = gl;
15
+
16
+ // Oversized triangle covering the full viewport when clipped
17
+ const positions = new Float32Array([-1, -1, 3, -1, -1, 3]);
18
+ this.positionBuffer = gl.createBuffer()!;
19
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.positionBuffer);
20
+ gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
21
+
22
+ const uvs = new Float32Array([0, 0, 2, 0, 0, 2]);
23
+ this.uvBuffer = gl.createBuffer()!;
24
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.uvBuffer);
25
+ gl.bufferData(gl.ARRAY_BUFFER, uvs, gl.STATIC_DRAW);
26
+ }
27
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Minimal Vec3 implementation for WebGL shader uniforms.
3
+ * Replaces OGL's Vec3 — only the constructor and .set() are needed.
4
+ */
5
+ export class Vec3 {
6
+ data: Float32Array;
7
+
8
+ constructor(x = 0, y = 0, z = 0) {
9
+ this.data = new Float32Array([x, y, z]);
10
+ }
11
+
12
+ set(x: number, y: number, z: number): this {
13
+ this.data[0] = x;
14
+ this.data[1] = y;
15
+ this.data[2] = z;
16
+ return this;
17
+ }
18
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Minimal WebGL utilities — drop-in replacement for the OGL subset used
3
+ * by the background shader components (OrbBackground, FaultyTerminal, WarpBackground).
4
+ *
5
+ * Only the classes actually consumed are implemented:
6
+ * Renderer, Program, Mesh, Triangle, Vec3, Color
7
+ */
8
+ export { Renderer, type GL } from './Renderer';
9
+ export { Program } from './Program';
10
+ export { Mesh } from './Mesh';
11
+ export { Triangle } from './Triangle';
12
+ export { Vec3 } from './Vec3';
13
+ export { Color } from './Color';
@@ -0,0 +1 @@
1
+ /// <reference types="nativewind/types" />