nexa-ui-kit 0.6.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 (114) hide show
  1. package/dist/NBadge.nexa +40 -0
  2. package/dist/NBottomSheet.nexa +124 -0
  3. package/dist/NButton.nexa +123 -0
  4. package/dist/NCard.nexa +74 -0
  5. package/dist/NInput.nexa +116 -0
  6. package/dist/NModal.nexa +165 -0
  7. package/dist/NSelect.nexa +169 -0
  8. package/dist/NToastContainer.nexa +86 -0
  9. package/dist/NTooltip.nexa +115 -0
  10. package/dist/components/NAlert.js +134 -0
  11. package/dist/components/NAlert.nexa +115 -0
  12. package/dist/components/NAutocomplete.js +94 -0
  13. package/dist/components/NAutocomplete.nexa +58 -0
  14. package/dist/components/NAvatar.js +75 -0
  15. package/dist/components/NAvatar.nexa +67 -0
  16. package/dist/components/NBadge.js +74 -0
  17. package/dist/components/NBadge.nexa +61 -0
  18. package/dist/components/NBottomSheet.js +149 -0
  19. package/dist/components/NBottomSheet.nexa +145 -0
  20. package/dist/components/NButton.js +284 -0
  21. package/dist/components/NButton.nexa +275 -0
  22. package/dist/components/NCard.js +117 -0
  23. package/dist/components/NCard.nexa +100 -0
  24. package/dist/components/NCheckbox.js +108 -0
  25. package/dist/components/NCheckbox.nexa +90 -0
  26. package/dist/components/NChips.js +72 -0
  27. package/dist/components/NChips.nexa +57 -0
  28. package/dist/components/NDataTable.js +252 -0
  29. package/dist/components/NDataTable.nexa +186 -0
  30. package/dist/components/NDatepicker.js +379 -0
  31. package/dist/components/NDatepicker.nexa +367 -0
  32. package/dist/components/NForm.js +132 -0
  33. package/dist/components/NForm.nexa +133 -0
  34. package/dist/components/NFormField.js +173 -0
  35. package/dist/components/NFormField.nexa +171 -0
  36. package/dist/components/NInput.js +311 -0
  37. package/dist/components/NInput.nexa +311 -0
  38. package/dist/components/NInputNumber.js +202 -0
  39. package/dist/components/NInputNumber.nexa +199 -0
  40. package/dist/components/NModal.js +221 -0
  41. package/dist/components/NModal.nexa +221 -0
  42. package/dist/components/NMultiSelect.js +156 -0
  43. package/dist/components/NMultiSelect.nexa +77 -0
  44. package/dist/components/NPaginator.js +117 -0
  45. package/dist/components/NPaginator.nexa +77 -0
  46. package/dist/components/NPassword.js +193 -0
  47. package/dist/components/NPassword.nexa +178 -0
  48. package/dist/components/NProgressBar.js +127 -0
  49. package/dist/components/NProgressBar.nexa +111 -0
  50. package/dist/components/NRadio.js +96 -0
  51. package/dist/components/NRadio.nexa +81 -0
  52. package/dist/components/NSelect.js +468 -0
  53. package/dist/components/NSelect.nexa +452 -0
  54. package/dist/components/NSkeleton.js +98 -0
  55. package/dist/components/NSkeleton.nexa +74 -0
  56. package/dist/components/NSwitch.js +92 -0
  57. package/dist/components/NSwitch.nexa +76 -0
  58. package/dist/components/NTabs.js +129 -0
  59. package/dist/components/NTabs.nexa +113 -0
  60. package/dist/components/NTag.js +108 -0
  61. package/dist/components/NTag.nexa +93 -0
  62. package/dist/components/NToastContainer.js +242 -0
  63. package/dist/components/NToastContainer.nexa +221 -0
  64. package/dist/components/NTooltip.js +163 -0
  65. package/dist/components/NTooltip.nexa +166 -0
  66. package/dist/components/NTreeMenu.js +151 -0
  67. package/dist/components/NTreeMenu.nexa +142 -0
  68. package/dist/index.d.ts +32 -0
  69. package/dist/index.js +34 -0
  70. package/dist/services/FloatingOverlay.d.ts +27 -0
  71. package/dist/services/FloatingOverlay.js +98 -0
  72. package/dist/services/FormValidation.d.ts +8 -0
  73. package/dist/services/FormValidation.js +46 -0
  74. package/dist/services/ToastService.d.ts +16 -0
  75. package/dist/services/ToastService.js +26 -0
  76. package/dist/styles/theme.d.ts +1 -0
  77. package/dist/styles/theme.js +144 -0
  78. package/package.json +32 -0
  79. package/src/components/NAlert.nexa +115 -0
  80. package/src/components/NAutocomplete.nexa +58 -0
  81. package/src/components/NAvatar.nexa +67 -0
  82. package/src/components/NBadge.nexa +61 -0
  83. package/src/components/NBottomSheet.nexa +145 -0
  84. package/src/components/NButton.nexa +275 -0
  85. package/src/components/NCard.nexa +100 -0
  86. package/src/components/NCheckbox.nexa +90 -0
  87. package/src/components/NChips.nexa +57 -0
  88. package/src/components/NDataTable.nexa +186 -0
  89. package/src/components/NDatepicker.nexa +367 -0
  90. package/src/components/NForm.nexa +133 -0
  91. package/src/components/NFormField.nexa +171 -0
  92. package/src/components/NInput.nexa +311 -0
  93. package/src/components/NInputNumber.nexa +199 -0
  94. package/src/components/NModal.nexa +221 -0
  95. package/src/components/NMultiSelect.nexa +77 -0
  96. package/src/components/NPaginator.nexa +77 -0
  97. package/src/components/NPassword.nexa +178 -0
  98. package/src/components/NProgressBar.nexa +111 -0
  99. package/src/components/NRadio.nexa +81 -0
  100. package/src/components/NSelect.nexa +452 -0
  101. package/src/components/NSkeleton.nexa +74 -0
  102. package/src/components/NSwitch.nexa +76 -0
  103. package/src/components/NTabs.nexa +113 -0
  104. package/src/components/NTag.nexa +93 -0
  105. package/src/components/NToastContainer.nexa +221 -0
  106. package/src/components/NTooltip.nexa +166 -0
  107. package/src/components/NTreeMenu.nexa +142 -0
  108. package/src/index.ts +36 -0
  109. package/src/services/FloatingOverlay.ts +133 -0
  110. package/src/services/FormValidation.ts +44 -0
  111. package/src/services/ToastService.ts +41 -0
  112. package/src/shims.d.ts +5 -0
  113. package/src/styles/theme.ts +146 -0
  114. package/src/styles/tokens.css +170 -0
@@ -0,0 +1,133 @@
1
+ export type FloatingPlacement = 'auto' | 'top' | 'bottom' | 'left' | 'right'
2
+ export type FloatingAlign = 'start' | 'center' | 'end'
3
+
4
+ export type FloatingResult = {
5
+ style: Record<string, string | number>
6
+ placement: Exclude<FloatingPlacement, 'auto'>
7
+ }
8
+
9
+ type ComputeOptions = {
10
+ placement?: FloatingPlacement
11
+ align?: FloatingAlign
12
+ gap?: number
13
+ margin?: number
14
+ matchWidth?: boolean
15
+ minWidth?: number
16
+ maxWidth?: number
17
+ zIndex?: number
18
+ }
19
+
20
+ const clamp = (value: number, min: number, max: number) => Math.min(Math.max(value, min), max)
21
+
22
+ export const computeFloatingOverlay = (anchorEl: HTMLElement, popupEl: HTMLElement, options: ComputeOptions = {}): FloatingResult => {
23
+ const rect = anchorEl.getBoundingClientRect()
24
+ const viewportWidth = window.innerWidth
25
+ const viewportHeight = window.innerHeight
26
+
27
+ const margin = options.margin ?? 8
28
+ const gap = options.gap ?? 6
29
+ const align = options.align ?? 'start'
30
+ const maxWidth = options.maxWidth ?? viewportWidth - margin * 2
31
+
32
+ const popupWidth = popupEl.offsetWidth || popupEl.getBoundingClientRect().width || 0
33
+ const popupHeight = popupEl.offsetHeight || popupEl.getBoundingClientRect().height || 0
34
+
35
+ const desiredWidth = options.matchWidth ? rect.width : popupWidth
36
+ const width = clamp(
37
+ Math.ceil(desiredWidth),
38
+ options.minWidth ?? 240,
39
+ Math.max(options.minWidth ?? 240, Math.min(maxWidth, viewportWidth - margin * 2)),
40
+ )
41
+
42
+ let placement: Exclude<FloatingPlacement, 'auto'> = 'bottom'
43
+ const desiredPlacement = options.placement ?? 'auto'
44
+
45
+ if (desiredPlacement === 'top' || desiredPlacement === 'bottom' || desiredPlacement === 'left' || desiredPlacement === 'right') {
46
+ placement = desiredPlacement
47
+ } else {
48
+ const spaceBelow = viewportHeight - rect.bottom
49
+ const spaceAbove = rect.top
50
+ const fitsBottom = spaceBelow >= popupHeight + margin
51
+ const fitsTop = spaceAbove >= popupHeight + margin
52
+ if (!fitsBottom && fitsTop) placement = 'top'
53
+ else if (!fitsTop && fitsBottom) placement = 'bottom'
54
+ else placement = spaceBelow >= spaceAbove ? 'bottom' : 'top'
55
+ }
56
+
57
+ let left = rect.left
58
+ let top = rect.bottom + gap
59
+
60
+ if (placement === 'top' || placement === 'bottom') {
61
+ if (align === 'center') {
62
+ left = rect.left + rect.width / 2 - width / 2
63
+ } else if (align === 'end') {
64
+ left = rect.right - width
65
+ } else {
66
+ left = rect.left
67
+ }
68
+ left = clamp(left, margin, viewportWidth - width - margin)
69
+
70
+ top = placement === 'top' ? rect.top - gap - popupHeight : rect.bottom + gap
71
+ top = clamp(top, margin, viewportHeight - popupHeight - margin)
72
+ } else {
73
+ left = placement === 'left' ? rect.left - gap - width : rect.right + gap
74
+ left = clamp(left, margin, viewportWidth - width - margin)
75
+
76
+ if (align === 'center') {
77
+ top = rect.top + rect.height / 2 - popupHeight / 2
78
+ } else if (align === 'end') {
79
+ top = rect.bottom - popupHeight
80
+ } else {
81
+ top = rect.top
82
+ }
83
+ top = clamp(top, margin, viewportHeight - popupHeight - margin)
84
+ }
85
+
86
+ const style: Record<string, string | number> = {
87
+ position: 'fixed',
88
+ top: `${Math.round(top)}px`,
89
+ left: `${Math.round(left)}px`,
90
+ width: `${Math.round(width)}px`,
91
+ zIndex: options.zIndex ?? 9999,
92
+ }
93
+
94
+ return { style, placement }
95
+ }
96
+
97
+ type TrackOptions = ComputeOptions & {
98
+ isOpen: () => boolean
99
+ getAnchor: () => HTMLElement | null
100
+ getPopup: () => HTMLElement | null
101
+ onUpdate: (result: FloatingResult) => void
102
+ isEventInside?: (event: Event) => boolean
103
+ onOutside?: (event: Event) => void
104
+ }
105
+
106
+ export const trackFloatingOverlay = (options: TrackOptions): (() => void) => {
107
+ const update = () => {
108
+ if (!options.isOpen()) return
109
+ const anchor = options.getAnchor()
110
+ const popup = options.getPopup()
111
+ if (!anchor || !popup) return
112
+ options.onUpdate(computeFloatingOverlay(anchor, popup, options))
113
+ }
114
+
115
+ const onScrollOrResize = () => update()
116
+ const onPointerDown = (e: Event) => {
117
+ if (!options.isOpen()) return
118
+ if (options.isEventInside && options.isEventInside(e)) return
119
+ options.onOutside?.(e)
120
+ }
121
+
122
+ update()
123
+ requestAnimationFrame(update)
124
+ window.addEventListener('resize', onScrollOrResize)
125
+ window.addEventListener('scroll', onScrollOrResize, true)
126
+ document.addEventListener('mousedown', onPointerDown)
127
+
128
+ return () => {
129
+ window.removeEventListener('resize', onScrollOrResize)
130
+ window.removeEventListener('scroll', onScrollOrResize, true)
131
+ document.removeEventListener('mousedown', onPointerDown)
132
+ }
133
+ }
@@ -0,0 +1,44 @@
1
+ export type FormRule = (value: any, values: Record<string, any>) => string | null | undefined | Promise<string | null | undefined>
2
+
3
+ export const required = (message: string = 'Required'): FormRule => (value) => {
4
+ if (value === null || value === undefined) return message
5
+ if (typeof value === 'string' && value.trim() === '') return message
6
+ if (Array.isArray(value) && value.length === 0) return message
7
+ return null
8
+ }
9
+
10
+ export const minLength = (length: number, message?: string): FormRule => (value) => {
11
+ const str = value === null || value === undefined ? '' : String(value)
12
+ if (str.length < length) return message || `Must be at least ${length} characters`
13
+ return null
14
+ }
15
+
16
+ export const maxLength = (length: number, message?: string): FormRule => (value) => {
17
+ const str = value === null || value === undefined ? '' : String(value)
18
+ if (str.length > length) return message || `Must be at most ${length} characters`
19
+ return null
20
+ }
21
+
22
+ export const pattern = (re: RegExp, message: string = 'Invalid format'): FormRule => (value) => {
23
+ const str = value === null || value === undefined ? '' : String(value)
24
+ if (!re.test(str)) return message
25
+ return null
26
+ }
27
+
28
+ export const email = (message: string = 'Invalid email'): FormRule => (value) => {
29
+ const str = value === null || value === undefined ? '' : String(value).trim()
30
+ if (!str) return null
31
+ const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
32
+ if (!re.test(str)) return message
33
+ return null
34
+ }
35
+
36
+ export const sameAs = (otherField: string, message?: string): FormRule => (value, values) => {
37
+ if (value !== values[otherField]) return message || `Must match ${otherField}`
38
+ return null
39
+ }
40
+
41
+ export const asyncRule = (rule: FormRule, delayMs: number = 0): FormRule => async (value, values) => {
42
+ if (delayMs > 0) await new Promise(r => setTimeout(r, delayMs))
43
+ return rule(value, values)
44
+ }
@@ -0,0 +1,41 @@
1
+ import { signal } from 'nexa-framework'
2
+
3
+ export type ToastType = 'success' | 'info' | 'warning' | 'error'
4
+
5
+ export interface Toast {
6
+ id: number
7
+ message: string
8
+ type: ToastType
9
+ duration?: number
10
+ }
11
+
12
+ const toasts = signal<Toast[]>([])
13
+ let nextId = 1
14
+
15
+ export const useToast = () => {
16
+ const add = (message: string, type: ToastType = 'info', duration: number = 3000) => {
17
+ const id = nextId++
18
+ const toast: Toast = { id, message, type, duration }
19
+ toasts.value = [...toasts.value, toast]
20
+
21
+ if (duration > 0) {
22
+ setTimeout(() => remove(id), duration)
23
+ }
24
+
25
+ return id
26
+ }
27
+
28
+ const remove = (id: number) => {
29
+ toasts.value = toasts.value.filter((t: any) => t.id !== id)
30
+ }
31
+
32
+ return {
33
+ toasts,
34
+ add,
35
+ remove,
36
+ success: (msg: string, dur?: number) => add(msg, 'success', dur),
37
+ error: (msg: string, dur?: number) => add(msg, 'error', dur),
38
+ info: (msg: string, dur?: number) => add(msg, 'info', dur),
39
+ warning: (msg: string, dur?: number) => add(msg, 'warning', dur)
40
+ }
41
+ }
package/src/shims.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ declare module '*.nexa' {
2
+ import { type Component } from 'nexa-framework'
3
+ const component: Component
4
+ export default component
5
+ }
@@ -0,0 +1,146 @@
1
+ let installed = false
2
+
3
+ export function installTheme(): void {
4
+ if (installed) return
5
+ installed = true
6
+
7
+ const tokenCSS = `
8
+ :root {
9
+ --n-color-primary: #3b82f6;
10
+ --n-color-primary-hover: #2563eb;
11
+ --n-color-primary-active: #1d4ed8;
12
+ --n-color-primary-light: rgba(59, 130, 246, 0.12);
13
+ --n-color-primary-glow: rgba(59, 130, 246, 0.3);
14
+ --n-color-success: #10b981;
15
+ --n-color-success-hover: #059669;
16
+ --n-color-success-light: rgba(16, 185, 129, 0.12);
17
+ --n-color-warning: #f59e0b;
18
+ --n-color-warning-hover: #d97706;
19
+ --n-color-warning-light: rgba(245, 158, 11, 0.12);
20
+ --n-color-danger: #ef4444;
21
+ --n-color-danger-hover: #dc2626;
22
+ --n-color-danger-light: rgba(239, 68, 68, 0.12);
23
+ --n-color-info: #06b6d4;
24
+ --n-color-info-hover: #0891b2;
25
+ --n-color-info-light: rgba(6, 182, 212, 0.12);
26
+ --n-color-surface: #0f172a;
27
+ --n-color-surface-alt: #1e293b;
28
+ --n-color-surface-hover: #334155;
29
+ --n-color-surface-elevated: #1e293b;
30
+ --n-color-bg: #020617;
31
+ --n-color-bg-alt: #0f172a;
32
+ --n-color-text: #f8fafc;
33
+ --n-color-text-secondary: #94a3b8;
34
+ --n-color-text-muted: #64748b;
35
+ --n-color-text-inverse: #0f172a;
36
+ --n-color-border: rgba(255, 255, 255, 0.08);
37
+ --n-color-border-hover: rgba(255, 255, 255, 0.15);
38
+ --n-color-border-active: rgba(255, 255, 255, 0.25);
39
+ --n-color-overlay: rgba(2, 6, 17, 0.7);
40
+ --n-color-glass: rgba(255, 255, 255, 0.04);
41
+ --n-color-glass-border: rgba(255, 255, 255, 0.08);
42
+ --n-color-glass-hover: rgba(255, 255, 255, 0.08);
43
+ --n-font-sans: 'Outfit', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
44
+ --n-font-mono: 'JetBrains Mono', 'Fira Code', monospace;
45
+ --n-text-xs: 0.75rem;
46
+ --n-text-sm: 0.875rem;
47
+ --n-text-base: 1rem;
48
+ --n-text-lg: 1.125rem;
49
+ --n-text-xl: 1.25rem;
50
+ --n-text-2xl: 1.5rem;
51
+ --n-text-3xl: 2rem;
52
+ --n-weight-normal: 400;
53
+ --n-weight-medium: 500;
54
+ --n-weight-semibold: 600;
55
+ --n-weight-bold: 700;
56
+ --n-weight-extrabold: 800;
57
+ --n-leading-tight: 1.25;
58
+ --n-leading-normal: 1.5;
59
+ --n-leading-relaxed: 1.75;
60
+ --n-tracking-tight: -0.025em;
61
+ --n-tracking-normal: 0;
62
+ --n-tracking-wide: 0.05em;
63
+ --n-space-1: 0.25rem;
64
+ --n-space-2: 0.5rem;
65
+ --n-space-3: 0.75rem;
66
+ --n-space-4: 1rem;
67
+ --n-space-5: 1.25rem;
68
+ --n-space-6: 1.5rem;
69
+ --n-space-8: 2rem;
70
+ --n-space-10: 2.5rem;
71
+ --n-space-12: 3rem;
72
+ --n-space-16: 4rem;
73
+ --n-radius-sm: 6px;
74
+ --n-radius-md: 10px;
75
+ --n-radius-lg: 14px;
76
+ --n-radius-xl: 20px;
77
+ --n-radius-2xl: 28px;
78
+ --n-radius-full: 9999px;
79
+ --n-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3);
80
+ --n-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.2);
81
+ --n-shadow-lg: 0 10px 25px -5px rgba(0, 0, 0, 0.3);
82
+ --n-shadow-xl: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
83
+ --n-shadow-glow-primary: 0 4px 15px -3px rgba(37, 99, 235, 0.3);
84
+ --n-shadow-glow-danger: 0 4px 15px -3px rgba(220, 38, 38, 0.3);
85
+ --n-transition-fast: 0.15s ease;
86
+ --n-transition-normal: 0.25s cubic-bezier(0.4, 0, 0.2, 1);
87
+ --n-transition-slow: 0.4s cubic-bezier(0.4, 0, 0.2, 1);
88
+ --n-transition-spring: 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
89
+ --n-z-dropdown: 100;
90
+ --n-z-sticky: 200;
91
+ --n-z-overlay: 500;
92
+ --n-z-modal: 2000;
93
+ --n-z-toast: 3000;
94
+ --n-z-tooltip: 1000;
95
+ }
96
+ [data-theme="light"] {
97
+ --n-color-primary: #2563eb;
98
+ --n-color-primary-hover: #1d4ed8;
99
+ --n-color-primary-active: #1e40af;
100
+ --n-color-primary-light: rgba(37, 99, 235, 0.08);
101
+ --n-color-primary-glow: rgba(37, 99, 235, 0.2);
102
+ --n-color-success: #059669;
103
+ --n-color-success-light: rgba(5, 150, 105, 0.08);
104
+ --n-color-warning: #d97706;
105
+ --n-color-warning-light: rgba(217, 119, 6, 0.08);
106
+ --n-color-danger: #dc2626;
107
+ --n-color-danger-light: rgba(220, 38, 38, 0.08);
108
+ --n-color-info: #0891b2;
109
+ --n-color-info-light: rgba(8, 145, 178, 0.08);
110
+ --n-color-surface: #ffffff;
111
+ --n-color-surface-alt: #f8fafc;
112
+ --n-color-surface-hover: #f1f5f9;
113
+ --n-color-surface-elevated: #ffffff;
114
+ --n-color-bg: #f8fafc;
115
+ --n-color-bg-alt: #f1f5f9;
116
+ --n-color-text: #0f172a;
117
+ --n-color-text-secondary: #475569;
118
+ --n-color-text-muted: #94a3b8;
119
+ --n-color-text-inverse: #f8fafc;
120
+ --n-color-border: rgba(0, 0, 0, 0.08);
121
+ --n-color-border-hover: rgba(0, 0, 0, 0.15);
122
+ --n-color-border-active: rgba(0, 0, 0, 0.25);
123
+ --n-color-overlay: rgba(15, 23, 42, 0.5);
124
+ --n-color-glass: rgba(255, 255, 255, 0.6);
125
+ --n-color-glass-border: rgba(0, 0, 0, 0.06);
126
+ --n-color-glass-hover: rgba(255, 255, 255, 0.8);
127
+ --n-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08);
128
+ --n-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.06);
129
+ --n-shadow-lg: 0 10px 25px -5px rgba(0, 0, 0, 0.08);
130
+ --n-shadow-xl: 0 25px 50px -12px rgba(0, 0, 0, 0.12);
131
+ --n-shadow-glow-primary: 0 4px 15px -3px rgba(37, 99, 235, 0.2);
132
+ --n-shadow-glow-danger: 0 4px 15px -3px rgba(220, 38, 38, 0.2);
133
+ }
134
+ `
135
+
136
+ if (typeof document !== 'undefined') {
137
+ const styleId = 'nexa-ui-tokens'
138
+ let el = document.getElementById(styleId) as HTMLStyleElement
139
+ if (!el) {
140
+ el = document.createElement('style')
141
+ el.id = styleId
142
+ document.head.appendChild(el)
143
+ }
144
+ el.textContent = tokenCSS
145
+ }
146
+ }
@@ -0,0 +1,170 @@
1
+ :root {
2
+ /* ─── Color Palette ─── */
3
+ --n-color-primary: #3b82f6;
4
+ --n-color-primary-hover: #2563eb;
5
+ --n-color-primary-active: #1d4ed8;
6
+ --n-color-primary-light: rgba(59, 130, 246, 0.12);
7
+ --n-color-primary-glow: rgba(59, 130, 246, 0.3);
8
+
9
+ --n-color-success: #10b981;
10
+ --n-color-success-hover: #059669;
11
+ --n-color-success-light: rgba(16, 185, 129, 0.12);
12
+
13
+ --n-color-warning: #f59e0b;
14
+ --n-color-warning-hover: #d97706;
15
+ --n-color-warning-light: rgba(245, 158, 11, 0.12);
16
+
17
+ --n-color-danger: #ef4444;
18
+ --n-color-danger-hover: #dc2626;
19
+ --n-color-danger-light: rgba(239, 68, 68, 0.12);
20
+
21
+ --n-color-info: #06b6d4;
22
+ --n-color-info-hover: #0891b2;
23
+ --n-color-info-light: rgba(6, 182, 212, 0.12);
24
+
25
+ --n-color-surface: #0f172a;
26
+ --n-color-surface-alt: #1e293b;
27
+ --n-color-surface-hover: #334155;
28
+ --n-color-surface-elevated: #1e293b;
29
+
30
+ --n-color-bg: #020617;
31
+ --n-color-bg-alt: #0f172a;
32
+
33
+ --n-color-text: #f8fafc;
34
+ --n-color-text-secondary: #94a3b8;
35
+ --n-color-text-muted: #64748b;
36
+ --n-color-text-inverse: #0f172a;
37
+
38
+ --n-color-border: rgba(255, 255, 255, 0.08);
39
+ --n-color-border-hover: rgba(255, 255, 255, 0.15);
40
+ --n-color-border-active: rgba(255, 255, 255, 0.25);
41
+
42
+ --n-color-overlay: rgba(2, 6, 17, 0.7);
43
+ --n-color-glass: rgba(255, 255, 255, 0.04);
44
+ --n-color-glass-border: rgba(255, 255, 255, 0.08);
45
+ --n-color-glass-hover: rgba(255, 255, 255, 0.08);
46
+
47
+ /* ─── Typography ─── */
48
+ --n-font-sans: 'Outfit', 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
49
+ --n-font-mono: 'JetBrains Mono', 'Fira Code', monospace;
50
+
51
+ --n-text-xs: 0.75rem;
52
+ --n-text-sm: 0.875rem;
53
+ --n-text-base: 1rem;
54
+ --n-text-lg: 1.125rem;
55
+ --n-text-xl: 1.25rem;
56
+ --n-text-2xl: 1.5rem;
57
+ --n-text-3xl: 2rem;
58
+
59
+ --n-weight-normal: 400;
60
+ --n-weight-medium: 500;
61
+ --n-weight-semibold: 600;
62
+ --n-weight-bold: 700;
63
+ --n-weight-extrabold: 800;
64
+
65
+ --n-leading-tight: 1.25;
66
+ --n-leading-normal: 1.5;
67
+ --n-leading-relaxed: 1.75;
68
+
69
+ --n-tracking-tight: -0.025em;
70
+ --n-tracking-normal: 0;
71
+ --n-tracking-wide: 0.05em;
72
+
73
+ /* ─── Spacing (4px base) ─── */
74
+ --n-space-1: 0.25rem;
75
+ --n-space-2: 0.5rem;
76
+ --n-space-3: 0.75rem;
77
+ --n-space-4: 1rem;
78
+ --n-space-5: 1.25rem;
79
+ --n-space-6: 1.5rem;
80
+ --n-space-8: 2rem;
81
+ --n-space-10: 2.5rem;
82
+ --n-space-12: 3rem;
83
+ --n-space-16: 4rem;
84
+
85
+ /* ─── Border Radius ─── */
86
+ --n-radius-sm: 6px;
87
+ --n-radius-md: 10px;
88
+ --n-radius-lg: 14px;
89
+ --n-radius-xl: 20px;
90
+ --n-radius-2xl: 28px;
91
+ --n-radius-full: 9999px;
92
+
93
+ /* ─── Shadows ─── */
94
+ --n-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3);
95
+ --n-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.2);
96
+ --n-shadow-lg: 0 10px 25px -5px rgba(0, 0, 0, 0.3);
97
+ --n-shadow-xl: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
98
+ --n-shadow-glow-primary: 0 4px 15px -3px rgba(37, 99, 235, 0.3);
99
+ --n-shadow-glow-success: 0 4px 15px -3px rgba(16, 185, 129, 0.3);
100
+ --n-shadow-glow-warning: 0 4px 15px -3px rgba(245, 158, 11, 0.3);
101
+ --n-shadow-glow-info: 0 4px 15px -3px rgba(6, 182, 212, 0.3);
102
+ --n-shadow-glow-danger: 0 4px 15px -3px rgba(220, 38, 38, 0.3);
103
+
104
+ /* ─── Transitions ─── */
105
+ --n-transition-fast: 0.15s ease;
106
+ --n-transition-normal: 0.25s cubic-bezier(0.4, 0, 0.2, 1);
107
+ --n-transition-slow: 0.4s cubic-bezier(0.4, 0, 0.2, 1);
108
+ --n-transition-spring: 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
109
+
110
+ /* ─── Z-Index Scale ─── */
111
+ --n-z-dropdown: 100;
112
+ --n-z-sticky: 200;
113
+ --n-z-overlay: 500;
114
+ --n-z-modal: 2000;
115
+ --n-z-toast: 3000;
116
+ --n-z-tooltip: 1000;
117
+ }
118
+
119
+ /* ─── Light Theme ─── */
120
+ [data-theme="light"] {
121
+ --n-color-primary: #2563eb;
122
+ --n-color-primary-hover: #1d4ed8;
123
+ --n-color-primary-active: #1e40af;
124
+ --n-color-primary-light: rgba(37, 99, 235, 0.08);
125
+ --n-color-primary-glow: rgba(37, 99, 235, 0.2);
126
+
127
+ --n-color-success: #059669;
128
+ --n-color-success-light: rgba(5, 150, 105, 0.08);
129
+
130
+ --n-color-warning: #d97706;
131
+ --n-color-warning-light: rgba(217, 119, 6, 0.08);
132
+
133
+ --n-color-danger: #dc2626;
134
+ --n-color-danger-light: rgba(220, 38, 38, 0.08);
135
+
136
+ --n-color-info: #0891b2;
137
+ --n-color-info-light: rgba(8, 145, 178, 0.08);
138
+
139
+ --n-color-surface: #ffffff;
140
+ --n-color-surface-alt: #f8fafc;
141
+ --n-color-surface-hover: #f1f5f9;
142
+ --n-color-surface-elevated: #ffffff;
143
+
144
+ --n-color-bg: #f8fafc;
145
+ --n-color-bg-alt: #f1f5f9;
146
+
147
+ --n-color-text: #0f172a;
148
+ --n-color-text-secondary: #475569;
149
+ --n-color-text-muted: #94a3b8;
150
+ --n-color-text-inverse: #f8fafc;
151
+
152
+ --n-color-border: rgba(0, 0, 0, 0.08);
153
+ --n-color-border-hover: rgba(0, 0, 0, 0.15);
154
+ --n-color-border-active: rgba(0, 0, 0, 0.25);
155
+
156
+ --n-color-overlay: rgba(15, 23, 42, 0.5);
157
+ --n-color-glass: rgba(255, 255, 255, 0.6);
158
+ --n-color-glass-border: rgba(0, 0, 0, 0.06);
159
+ --n-color-glass-hover: rgba(255, 255, 255, 0.8);
160
+
161
+ --n-shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.08);
162
+ --n-shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.06);
163
+ --n-shadow-lg: 0 10px 25px -5px rgba(0, 0, 0, 0.08);
164
+ --n-shadow-xl: 0 25px 50px -12px rgba(0, 0, 0, 0.12);
165
+ --n-shadow-glow-primary: 0 4px 15px -3px rgba(37, 99, 235, 0.2);
166
+ --n-shadow-glow-success: 0 4px 15px -3px rgba(5, 150, 105, 0.2);
167
+ --n-shadow-glow-warning: 0 4px 15px -3px rgba(217, 119, 6, 0.2);
168
+ --n-shadow-glow-info: 0 4px 15px -3px rgba(8, 145, 178, 0.2);
169
+ --n-shadow-glow-danger: 0 4px 15px -3px rgba(220, 38, 38, 0.2);
170
+ }