create-nara 1.0.44 → 1.0.46

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.

Potentially problematic release.


This version of create-nara might be problematic. Click here for more details.

package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nara",
3
- "version": "1.0.44",
3
+ "version": "1.0.46",
4
4
  "description": "CLI to scaffold NARA projects",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ }
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { fly, fade } from 'svelte/transition';
3
3
  import { page, router, inertia } from '@inertiajs/svelte';
4
- import { clickOutside, Toast } from '../components/helper';
4
+ import { clickOutside, apiFetch } from '../components/helper';
5
5
  import DarkModeToggle from '../components/DarkModeToggle.svelte';
6
6
 
7
7
  interface User {
@@ -35,22 +35,12 @@
35
35
  $: visibleMenuLinks = menuLinks.filter((item) => item.show);
36
36
 
37
37
  async function handleLogout(): Promise<void> {
38
- try {
39
- const response = await fetch('/api/auth/logout', {
40
- method: 'POST',
41
- headers: { 'Content-Type': 'application/json' }
42
- });
43
- const result = await response.json();
38
+ const result = await apiFetch('/api/auth/logout', {
39
+ method: 'POST'
40
+ });
44
41
 
45
- if (result.success) {
46
- Toast(result.message || 'Logged out successfully', 'success');
47
- router.visit(result.data?.redirect || '/login');
48
- } else {
49
- Toast(result.message || 'Logout failed', 'error');
50
- }
51
- } catch (err: unknown) {
52
- const errorMessage = err instanceof Error ? err.message : 'Network error';
53
- Toast(errorMessage, 'error');
42
+ if (result.success || result.redirected) {
43
+ router.visit(result.redirectUrl || '/login');
54
44
  }
55
45
  }
56
46
  </script>
@@ -68,6 +68,18 @@ interface ApiOptions {
68
68
  showErrorToast?: boolean;
69
69
  }
70
70
 
71
+ /**
72
+ * Fetch API response type
73
+ */
74
+ export interface FetchApiResponse<T = unknown> {
75
+ success: boolean;
76
+ data?: T;
77
+ message: string;
78
+ redirected?: boolean;
79
+ redirectUrl?: string;
80
+ errors?: Record<string, string[]>;
81
+ }
82
+
71
83
  /**
72
84
  * Creates a click outside event listener for a DOM node
73
85
  */
@@ -252,6 +264,138 @@ export async function api<T = unknown>(
252
264
  }
253
265
  }
254
266
 
267
+ /**
268
+ * Reusable fetch API helper that handles JSON responses, redirects, and errors
269
+ *
270
+ * @param url - The URL to fetch
271
+ * @param options - Fetch options (method, body, headers, etc.)
272
+ * @param apiOptions - API options for toast notifications
273
+ * @returns FetchApiResponse with success, data, message, and redirect info
274
+ *
275
+ * @example
276
+ * // POST request with JSON body
277
+ * const result = await apiFetch('/api/auth/logout', { method: 'POST' });
278
+ * if (result.success || result.redirected) {
279
+ * router.visit(result.redirectUrl || '/login');
280
+ * }
281
+ *
282
+ * @example
283
+ * // POST request with body
284
+ * const result = await apiFetch('/api/auth/login', {
285
+ * method: 'POST',
286
+ * body: JSON.stringify({ email, password })
287
+ * });
288
+ */
289
+ export async function apiFetch<T = unknown>(
290
+ url: string,
291
+ options: RequestInit = {},
292
+ apiOptions: ApiOptions = {}
293
+ ): Promise<FetchApiResponse<T>> {
294
+ const { showSuccessToast = true, showErrorToast = true } = apiOptions;
295
+
296
+ try {
297
+ const response = await fetch(url, {
298
+ ...options,
299
+ headers: {
300
+ 'Content-Type': 'application/json',
301
+ ...options.headers,
302
+ },
303
+ });
304
+
305
+ // Check if the response was a redirect (fetch follows redirects automatically)
306
+ // We detect this by checking if the final URL is different from the requested URL
307
+ // or if the URL matches common redirect targets
308
+ const isRedirected = response.redirected;
309
+ const finalUrl = response.url;
310
+
311
+ // Handle non-JSON responses (redirects, HTML error pages)
312
+ const contentType = response.headers.get('content-type');
313
+ if (!contentType || !contentType.includes('application/json')) {
314
+ // If redirected to a success page, treat as success
315
+ if (isRedirected || finalUrl.endsWith('/dashboard') || finalUrl.endsWith('/login')) {
316
+ const successMsg = finalUrl.includes('login') ? 'Logged out successfully' : 'Success';
317
+ if (showSuccessToast) {
318
+ Toast(successMsg, 'success');
319
+ }
320
+ return {
321
+ success: true,
322
+ message: successMsg,
323
+ redirected: true,
324
+ redirectUrl: finalUrl,
325
+ };
326
+ }
327
+
328
+ // Handle error status codes
329
+ if (!response.ok) {
330
+ const errorMsg = getHttpErrorMessage(response.status, response.statusText);
331
+ if (showErrorToast) {
332
+ Toast(errorMsg, 'error');
333
+ }
334
+ return {
335
+ success: false,
336
+ message: errorMsg,
337
+ };
338
+ }
339
+
340
+ // Unknown non-JSON response
341
+ return {
342
+ success: true,
343
+ message: 'Success',
344
+ redirected: isRedirected,
345
+ redirectUrl: finalUrl,
346
+ };
347
+ }
348
+
349
+ // Parse JSON response
350
+ const result = await response.json() as ApiResponse<T>;
351
+
352
+ if (result.success) {
353
+ if (showSuccessToast && result.message) {
354
+ Toast(result.message, 'success');
355
+ }
356
+ return {
357
+ success: true,
358
+ message: result.message,
359
+ data: result.data,
360
+ redirected: isRedirected,
361
+ redirectUrl: finalUrl,
362
+ };
363
+ } else {
364
+ if (showErrorToast) {
365
+ const errorMsg = result.errors
366
+ ? formatValidationErrors(result.errors) || result.message
367
+ : result.message;
368
+ if (errorMsg) Toast(errorMsg, 'error');
369
+ }
370
+ return {
371
+ success: false,
372
+ message: result.message,
373
+ errors: result.errors,
374
+ };
375
+ }
376
+ } catch (err: unknown) {
377
+ // Network or parsing error
378
+ let errorMessage: string;
379
+
380
+ if (err instanceof SyntaxError) {
381
+ errorMessage = 'Server returned an invalid response. Please try again.';
382
+ } else if (err instanceof TypeError && (err.message.includes('fetch') || err.message.includes('network'))) {
383
+ errorMessage = 'Unable to connect to server. Please check your connection.';
384
+ } else {
385
+ errorMessage = err instanceof Error ? err.message : 'An unexpected error occurred.';
386
+ }
387
+
388
+ if (showErrorToast) {
389
+ Toast(errorMessage, 'error');
390
+ }
391
+
392
+ return {
393
+ success: false,
394
+ message: errorMessage,
395
+ };
396
+ }
397
+ }
398
+
255
399
  type ToastType = 'success' | 'error' | 'warning' | 'info';
256
400
 
257
401
  /**
@@ -1,233 +1,94 @@
1
1
  @import url('https://rsms.me/inter/inter.css');
2
2
  @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:ital,wght@0,400;0,600;0,700;1,400&display=swap');
3
- @import "tailwindcss";
4
-
5
- /* Custom theme colors using CSS variables */
6
- @theme {
7
- --color-primary-50: #ecfdf5;
8
- --color-primary-100: #d1fae5;
9
- --color-primary-200: #a7f3d0;
10
- --color-primary-300: #6ee7b7;
11
- --color-primary-400: #34d399;
12
- --color-primary-500: #10b981;
13
- --color-primary-600: #059669;
14
- --color-primary-700: #047857;
15
- --color-primary-800: #065f46;
16
- --color-primary-900: #064e3b;
17
- --color-primary-950: #022c22;
18
-
19
- --color-secondary-50: #fffbeb;
20
- --color-secondary-100: #fef3c7;
21
- --color-secondary-200: #fde68a;
22
- --color-secondary-300: #fcd34d;
23
- --color-secondary-400: #fbbf24;
24
- --color-secondary-500: #f59e0b;
25
- --color-secondary-600: #d97706;
26
- --color-secondary-700: #b45309;
27
- --color-secondary-800: #92400e;
28
- --color-secondary-900: #78350f;
29
- --color-secondary-950: #451a03;
30
-
31
- --color-accent-50: #faf5ff;
32
- --color-accent-100: #f3e8ff;
33
- --color-accent-200: #e9d5ff;
34
- --color-accent-300: #d8b4fe;
35
- --color-accent-400: #c084fc;
36
- --color-accent-500: #a855f7;
37
- --color-accent-600: #9333ea;
38
- --color-accent-700: #7c3aed;
39
- --color-accent-800: #6b21a8;
40
- --color-accent-900: #581c87;
41
- --color-accent-950: #3b0764;
42
-
43
- --color-info-50: #ecfeff;
44
- --color-info-100: #cffafe;
45
- --color-info-200: #a5f3fc;
46
- --color-info-300: #67e8f9;
47
- --color-info-400: #22d3ee;
48
- --color-info-500: #06b6d4;
49
- --color-info-600: #0891b2;
50
- --color-info-700: #0e7490;
51
- --color-info-800: #155e75;
52
- --color-info-900: #164e63;
53
- --color-info-950: #083344;
54
-
55
- --color-warning-50: #fff7ed;
56
- --color-warning-100: #ffedd5;
57
- --color-warning-200: #fed7aa;
58
- --color-warning-300: #fdba74;
59
- --color-warning-400: #fb923c;
60
- --color-warning-500: #f97316;
61
- --color-warning-600: #ea580c;
62
- --color-warning-700: #c2410c;
63
- --color-warning-800: #9a3412;
64
- --color-warning-900: #7c2d12;
65
- --color-warning-950: #431407;
66
-
67
- --color-danger-50: #fef2f2;
68
- --color-danger-100: #fee2e2;
69
- --color-danger-200: #fecaca;
70
- --color-danger-300: #fca5a5;
71
- --color-danger-400: #f87171;
72
- --color-danger-500: #ef4444;
73
- --color-danger-600: #dc2626;
74
- --color-danger-700: #b91c1c;
75
- --color-danger-800: #991b1b;
76
- --color-danger-900: #7f1d1d;
77
- --color-danger-950: #450a0a;
78
-
79
- --color-success-50: #ecfdf5;
80
- --color-success-100: #d1fae5;
81
- --color-success-200: #a7f3d0;
82
- --color-success-300: #6ee7b7;
83
- --color-success-400: #34d399;
84
- --color-success-500: #10b981;
85
- --color-success-600: #059669;
86
- --color-success-700: #047857;
87
- --color-success-800: #065f46;
88
- --color-success-900: #064e3b;
89
- --color-success-950: #022c22;
90
-
91
- --color-surface-light: #f8f8f8;
92
- --color-surface-dark: #0a0a0a;
93
- --color-surface-card-light: #f1f5f9;
94
- --color-surface-card-dark: #0f0f0f;
95
-
96
- --font-family-sans: 'Inter var', 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
97
- --font-family-serif: 'Playfair Display', Georgia, Cambria, 'Times New Roman', Times, serif;
98
-
99
- --shadow-soft: 0 2px 15px -3px rgba(0, 0, 0, 0.07), 0 10px 20px -2px rgba(0, 0, 0, 0.04);
100
- }
101
-
102
- /* Base styles */
103
- html {
104
- font-family: var(--font-family-sans);
105
- }
3
+ @tailwind base;
4
+ @tailwind components;
5
+ @tailwind utilities;
106
6
 
107
- body::-webkit-scrollbar {
108
- width: 4px;
109
- }
110
-
111
- body::-webkit-scrollbar-track {
112
- background: transparent;
113
- }
7
+ @layer base {
8
+ html {
9
+ font-family: 'Inter', system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
10
+ }
114
11
 
115
- body::-webkit-scrollbar-thumb {
116
- background-color: var(--color-primary-500);
117
- border-radius: 3px;
118
- }
12
+ body::-webkit-scrollbar {
13
+ width: 4px;
14
+ }
119
15
 
120
- /* Component utilities */
121
- @utility card-hover {
122
- transition-property: all;
123
- transition-duration: 300ms;
124
- &:hover {
125
- box-shadow: var(--shadow-soft);
126
- transform: translateY(-0.25rem);
16
+ body::-webkit-scrollbar-track {
17
+ background: transparent;
127
18
  }
128
- }
129
19
 
130
- @utility nav-link {
131
- padding: 0.5rem 1rem;
132
- color: rgb(75 85 99);
133
- border-radius: 0.5rem;
134
- transition-property: color, background-color;
135
- transition-duration: 300ms;
136
- &:hover {
137
- color: rgb(17 24 39);
138
- background-color: rgb(243 244 246);
20
+ body::-webkit-scrollbar-thumb {
21
+ background-color: theme('colors.primary.500');
22
+ border-radius: 3px;
139
23
  }
140
24
  }
141
25
 
142
- @utility gradient-text {
143
- background-clip: text;
144
- -webkit-background-clip: text;
145
- color: transparent;
146
- background-image: linear-gradient(to right, var(--color-primary-600), var(--color-primary-400));
147
- }
26
+ @layer components {
27
+ .card-hover {
28
+ @apply transition-all duration-300 hover:shadow-soft hover:-translate-y-1;
29
+ }
148
30
 
149
- @utility btn-primary {
150
- background-color: var(--color-primary-600);
151
- color: white;
152
- padding: 0.5rem 1rem;
153
- border-radius: 0.5rem;
154
- transition-property: all;
155
- transition-duration: 300ms;
156
- &:hover {
157
- background-color: var(--color-primary-700);
31
+ .nav-link {
32
+ @apply px-4 py-2 text-gray-600 dark:text-gray-200 hover:text-gray-900 hover:bg-gray-100 rounded-lg transition-colors duration-300;
158
33
  }
159
- &:active {
160
- background-color: var(--color-primary-800);
34
+
35
+ .nav-link.active {
36
+ @apply bg-primary-50 dark:bg-gray-800 text-primary-600;
161
37
  }
162
- &:disabled {
163
- opacity: 0.5;
164
- cursor: not-allowed;
38
+
39
+ .gradient-text {
40
+ @apply bg-clip-text text-transparent bg-gradient-to-r from-primary-600 to-primary-400;
165
41
  }
166
- &:focus {
167
- outline: none;
168
- box-shadow: 0 0 0 2px white, 0 0 0 4px var(--color-primary-500);
42
+
43
+ .mobile-nav-link {
44
+ @apply block px-3 py-2 rounded-md text-base font-medium text-gray-700 hover:text-gray-900 hover:bg-gray-50;
169
45
  }
170
- }
171
46
 
172
- @utility btn-secondary {
173
- background-color: white;
174
- color: rgb(17 24 39);
175
- padding: 0.5rem 1rem;
176
- border-radius: 0.5rem;
177
- border: 1px solid rgb(209 213 219);
178
- transition-property: all;
179
- transition-duration: 300ms;
180
- &:hover {
181
- background-color: rgb(249 250 251);
47
+ .mobile-nav-link.active {
48
+ @apply text-primary-600 font-semibold;
182
49
  }
183
- &:active {
184
- background-color: rgb(243 244 246);
50
+
51
+ .btn-primary {
52
+ @apply bg-primary-600 text-white px-4 py-2 rounded-lg hover:bg-primary-700
53
+ transition-all duration-300 active:bg-primary-800
54
+ disabled:opacity-50 disabled:cursor-not-allowed
55
+ focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2;
185
56
  }
186
- &:disabled {
187
- opacity: 0.5;
188
- cursor: not-allowed;
57
+
58
+ .btn-secondary {
59
+ @apply bg-white text-gray-900 px-4 py-2 rounded-lg border border-gray-300
60
+ hover:bg-gray-50 transition-all duration-300 active:bg-gray-100
61
+ disabled:opacity-50 disabled:cursor-not-allowed
62
+ focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2;
189
63
  }
190
- &:focus {
191
- outline: none;
192
- box-shadow: 0 0 0 2px white, 0 0 0 4px rgb(107 114 128);
64
+
65
+ .btn-danger {
66
+ @apply bg-danger-600 text-white px-4 py-2 rounded-lg hover:bg-danger-700
67
+ transition-all duration-300 active:bg-danger-800
68
+ disabled:opacity-50 disabled:cursor-not-allowed
69
+ focus:outline-none focus:ring-2 focus:ring-danger-500 focus:ring-offset-2;
193
70
  }
194
- }
195
71
 
196
- @utility btn-danger {
197
- background-color: var(--color-danger-600);
198
- color: white;
199
- padding: 0.5rem 1rem;
200
- border-radius: 0.5rem;
201
- transition-property: all;
202
- transition-duration: 300ms;
203
- &:hover {
204
- background-color: var(--color-danger-700);
72
+ .card {
73
+ @apply bg-white rounded-xl shadow-soft p-6 transition-all duration-300
74
+ shadow-lg hover:shadow-xl hover:-translate-y-0.5;
205
75
  }
206
- &:active {
207
- background-color: var(--color-danger-800);
76
+
77
+ /* Surface backgrounds */
78
+ .bg-surface {
79
+ @apply bg-surface-light dark:bg-surface-dark;
208
80
  }
209
- &:disabled {
210
- opacity: 0.5;
211
- cursor: not-allowed;
81
+
82
+ .bg-surface-card {
83
+ @apply bg-surface-card-light dark:bg-surface-card-dark;
212
84
  }
213
- &:focus {
214
- outline: none;
215
- box-shadow: 0 0 0 2px white, 0 0 0 4px var(--color-danger-500);
85
+
86
+ /* Gradient text utilities */
87
+ .gradient-text-accent {
88
+ @apply bg-clip-text text-transparent bg-gradient-to-r from-accent-600 to-accent-400;
216
89
  }
217
- }
218
90
 
219
- @utility card {
220
- background-color: white;
221
- border-radius: 0.75rem;
222
- box-shadow: var(--shadow-soft);
223
- padding: 1.5rem;
224
- transition-property: all;
225
- transition-duration: 300ms;
226
- &:hover {
227
- box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
228
- transform: translateY(-0.125rem);
91
+ .gradient-text-info {
92
+ @apply bg-clip-text text-transparent bg-gradient-to-r from-info-600 to-info-400;
229
93
  }
230
94
  }
231
-
232
- /* Surface utilities - use standard Tailwind classes instead */
233
- /* bg-surface-light, bg-surface-dark, bg-surface-card-light, bg-surface-card-dark are available */
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { onMount } from 'svelte';
3
3
  import { inertia, router } from '@inertiajs/svelte'
4
- import { Toast } from '../../components/helper';
4
+ import { apiFetch, Toast } from '../../components/helper';
5
5
  import NaraIcon from '../../components/NaraIcon.svelte';
6
6
  import { fade, fly } from 'svelte/transition';
7
7
 
@@ -28,63 +28,15 @@
28
28
 
29
29
  async function submitForm(): Promise<void> {
30
30
  loading = true;
31
- try {
32
- const response = await fetch('/api/auth/login', {
33
- method: 'POST',
34
- headers: { 'Content-Type': 'application/json' },
35
- body: JSON.stringify({ email: form.email, password: form.password })
36
- });
37
-
38
- // Check if login was successful via redirect (backend returns 302 on success)
39
- // fetch follows redirects automatically, so we check if we ended up at dashboard
40
- if (response.redirected || response.url.endsWith('/dashboard')) {
41
- Toast('Login successful', 'success');
42
- router.visit('/dashboard');
43
- return;
44
- }
45
-
46
- // Handle non-JSON responses (server errors returning HTML)
47
- const contentType = response.headers.get('content-type');
48
- if (!contentType || !contentType.includes('application/json')) {
49
- const statusMessages: Record<number, string> = {
50
- 401: 'Invalid email or password.',
51
- 403: 'Access denied.',
52
- 404: 'Login service not available.',
53
- 500: 'Server error. Please try again later.',
54
- 502: 'Server is temporarily unavailable.',
55
- 503: 'Service unavailable. Please try again later.',
56
- };
57
- Toast(statusMessages[response.status] || `Server error (${response.status})`, 'error');
58
- return;
59
- }
60
-
61
- const result = await response.json();
62
-
63
- if (result.success) {
64
- Toast(result.message || 'Login successful', 'success');
65
- router.visit(result.data?.redirect || '/dashboard');
66
- } else {
67
- // Handle validation errors
68
- if (result.errors) {
69
- const errorMessages = Object.values(result.errors).flat() as string[];
70
- Toast(errorMessages[0] || result.message || 'Invalid email or password.', 'error');
71
- } else {
72
- Toast(result.message || 'Invalid email or password.', 'error');
73
- }
74
- }
75
- } catch (err: unknown) {
76
- // Network or parsing error
77
- if (err instanceof SyntaxError) {
78
- Toast('Server returned an invalid response. Please try again.', 'error');
79
- } else if (err instanceof TypeError && (err.message.includes('fetch') || err.message.includes('network'))) {
80
- Toast('Unable to connect to server. Please check your connection.', 'error');
81
- } else {
82
- const errorMessage = err instanceof Error ? err.message : 'An unexpected error occurred.';
83
- Toast(errorMessage, 'error');
84
- }
85
- } finally {
86
- loading = false;
31
+ const result = await apiFetch('/api/auth/login', {
32
+ method: 'POST',
33
+ body: JSON.stringify({ email: form.email, password: form.password })
34
+ });
35
+
36
+ if (result.success || result.redirected) {
37
+ router.visit(result.redirectUrl || '/dashboard');
87
38
  }
39
+ loading = false;
88
40
  }
89
41
  </script>
90
42
 
@@ -0,0 +1,122 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ content: [
4
+ "./resources/**/*.{svelte,html,js,ts}",
5
+ ],
6
+ darkMode: 'class',
7
+ theme: {
8
+ extend: {
9
+ fontFamily: {
10
+ sans: ['Inter var', 'Inter', 'system-ui', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'Helvetica Neue', 'Arial', 'sans-serif'],
11
+ serif: ['Playfair Display', 'Georgia', 'Cambria', 'Times New Roman', 'Times', 'serif'],
12
+ },
13
+ colors: {
14
+ primary: {
15
+ 50: '#ecfdf5',
16
+ 100: '#d1fae5',
17
+ 200: '#a7f3d0',
18
+ 300: '#6ee7b7',
19
+ 400: '#34d399',
20
+ 500: '#10b981',
21
+ 600: '#059669',
22
+ 700: '#047857',
23
+ 800: '#065f46',
24
+ 900: '#064e3b',
25
+ 950: '#022c22',
26
+ },
27
+ secondary: {
28
+ 50: '#fffbeb',
29
+ 100: '#fef3c7',
30
+ 200: '#fde68a',
31
+ 300: '#fcd34d',
32
+ 400: '#fbbf24',
33
+ 500: '#f59e0b',
34
+ 600: '#d97706',
35
+ 700: '#b45309',
36
+ 800: '#92400e',
37
+ 900: '#78350f',
38
+ 950: '#451a03',
39
+ },
40
+ accent: {
41
+ 50: '#faf5ff',
42
+ 100: '#f3e8ff',
43
+ 200: '#e9d5ff',
44
+ 300: '#d8b4fe',
45
+ 400: '#c084fc',
46
+ 500: '#a855f7',
47
+ 600: '#9333ea',
48
+ 700: '#7c3aed',
49
+ 800: '#6b21a8',
50
+ 900: '#581c87',
51
+ 950: '#3b0764',
52
+ },
53
+ info: {
54
+ 50: '#ecfeff',
55
+ 100: '#cffafe',
56
+ 200: '#a5f3fc',
57
+ 300: '#67e8f9',
58
+ 400: '#22d3ee',
59
+ 500: '#06b6d4',
60
+ 600: '#0891b2',
61
+ 700: '#0e7490',
62
+ 800: '#155e75',
63
+ 900: '#164e63',
64
+ 950: '#083344',
65
+ },
66
+ warning: {
67
+ 50: '#fff7ed',
68
+ 100: '#ffedd5',
69
+ 200: '#fed7aa',
70
+ 300: '#fdba74',
71
+ 400: '#fb923c',
72
+ 500: '#f97316',
73
+ 600: '#ea580c',
74
+ 700: '#c2410c',
75
+ 800: '#9a3412',
76
+ 900: '#7c2d12',
77
+ 950: '#431407',
78
+ },
79
+ danger: {
80
+ 50: '#fef2f2',
81
+ 100: '#fee2e2',
82
+ 200: '#fecaca',
83
+ 300: '#fca5a5',
84
+ 400: '#f87171',
85
+ 500: '#ef4444',
86
+ 600: '#dc2626',
87
+ 700: '#b91c1c',
88
+ 800: '#991b1b',
89
+ 900: '#7f1d1d',
90
+ 950: '#450a0a',
91
+ },
92
+ success: {
93
+ 50: '#ecfdf5',
94
+ 100: '#d1fae5',
95
+ 200: '#a7f3d0',
96
+ 300: '#6ee7b7',
97
+ 400: '#34d399',
98
+ 500: '#10b981',
99
+ 600: '#059669',
100
+ 700: '#047857',
101
+ 800: '#065f46',
102
+ 900: '#064e3b',
103
+ 950: '#022c22',
104
+ },
105
+ surface: {
106
+ light: '#f8f8f8',
107
+ dark: '#0a0a0a',
108
+ card: {
109
+ light: '#f1f5f9',
110
+ dark: '#0f0f0f',
111
+ }
112
+ },
113
+ },
114
+ boxShadow: {
115
+ 'soft': '0 2px 15px -3px rgba(0, 0, 0, 0.07), 0 10px 20px -2px rgba(0, 0, 0, 0.04)',
116
+ },
117
+ },
118
+ },
119
+ plugins: [
120
+ require('@tailwindcss/typography'),
121
+ ],
122
+ }
@@ -1,6 +1,5 @@
1
1
  import { defineConfig, loadEnv } from 'vite';
2
2
  import { svelte } from '@sveltejs/vite-plugin-svelte';
3
- import tailwindcss from '@tailwindcss/vite';
4
3
  import path from 'path';
5
4
 
6
5
  export default defineConfig(({ mode }) => {
@@ -8,7 +7,7 @@ export default defineConfig(({ mode }) => {
8
7
  const vitePort = parseInt(env.VITE_PORT || '5173');
9
8
 
10
9
  return {
11
- plugins: [svelte(), tailwindcss()],
10
+ plugins: [svelte()],
12
11
  resolve: {
13
12
  alias: {
14
13
  '@': path.resolve(__dirname, './resources/js')