create-tigra 2.6.5 → 2.6.8

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 (39) hide show
  1. package/package.json +1 -1
  2. package/template/_claude/rules/client/01-project-structure.md +2 -5
  3. package/template/_claude/rules/client/04-design-system.md +48 -43
  4. package/template/_claude/rules/client/core.md +2 -2
  5. package/template/client/src/app/globals.css +12 -12
  6. package/template/client/src/app/layout.tsx +1 -1
  7. package/template/client/src/app/page.tsx +5 -5
  8. package/template/client/src/app/providers.tsx +1 -1
  9. package/template/client/src/components/common/ThemeToggle.tsx +59 -0
  10. package/template/client/src/features/admin/hooks/useAdminSessions.ts +68 -0
  11. package/template/client/src/features/admin/hooks/useAdminStats.ts +27 -0
  12. package/template/client/src/features/admin/hooks/useAdminUsers.ts +132 -0
  13. package/template/client/src/features/admin/services/admin.service.ts +94 -0
  14. package/template/client/src/features/admin/types/admin.types.ts +65 -0
  15. package/template/client/src/features/auth/components/AuthInitializer.tsx +18 -1
  16. package/template/client/src/lib/api/axios.config.ts +20 -1
  17. package/template/client/src/lib/constants/api-endpoints.ts +9 -0
  18. package/template/client/src/lib/constants/app.constants.ts +3 -1
  19. package/template/client/src/lib/constants/routes.ts +6 -0
  20. package/template/client/src/lib/env.ts +35 -0
  21. package/template/client/src/styles/themes/default.css +92 -0
  22. package/template/server/package.json +1 -0
  23. package/template/server/postman/collection.json +168 -50
  24. package/template/server/prisma/schema.prisma +2 -0
  25. package/template/server/src/jobs/cleanup-deleted-accounts.job.ts +14 -4
  26. package/template/server/src/libs/prisma.ts +13 -0
  27. package/template/server/src/modules/admin/admin.controller.ts +130 -1
  28. package/template/server/src/modules/admin/admin.repo.ts +289 -0
  29. package/template/server/src/modules/admin/admin.routes.ts +113 -7
  30. package/template/server/src/modules/admin/admin.schemas.ts +49 -0
  31. package/template/server/src/modules/admin/admin.service.ts +154 -0
  32. package/template/server/src/modules/auth/auth.repo.ts +5 -18
  33. package/template/server/src/modules/auth/auth.service.ts +20 -28
  34. package/template/server/src/modules/auth/session.repo.ts +10 -5
  35. package/template/client/src/components/common/ThemeSwitcher.tsx +0 -112
  36. package/template/client/src/styles/themes/electric-indigo.css +0 -90
  37. package/template/client/src/styles/themes/ocean-teal.css +0 -90
  38. package/template/client/src/styles/themes/rose-pink.css +0 -90
  39. package/template/client/src/styles/themes/warm-orange.css +0 -90
@@ -103,16 +103,17 @@ export async function register(
103
103
  const refreshToken = generateRefreshToken();
104
104
  const refreshTokenExpiresAt = getRefreshTokenExpiresAt();
105
105
 
106
- await authRepo.createRefreshToken({
107
- token: refreshToken,
106
+ const session = await sessionRepository.createSession({
108
107
  userId: user.id,
108
+ deviceInfo,
109
+ ipAddress,
109
110
  expiresAt: refreshTokenExpiresAt,
110
111
  });
111
112
 
112
- await sessionRepository.createSession({
113
+ await authRepo.createRefreshToken({
114
+ token: refreshToken,
113
115
  userId: user.id,
114
- deviceInfo,
115
- ipAddress,
116
+ sessionId: session.id,
116
117
  expiresAt: refreshTokenExpiresAt,
117
118
  });
118
119
 
@@ -188,16 +189,17 @@ export async function login(
188
189
  const refreshToken = generateRefreshToken();
189
190
  const refreshTokenExpiresAt = getRefreshTokenExpiresAt();
190
191
 
191
- await authRepo.createRefreshToken({
192
- token: refreshToken,
192
+ const session = await sessionRepository.createSession({
193
193
  userId: user.id,
194
+ deviceInfo,
195
+ ipAddress,
194
196
  expiresAt: refreshTokenExpiresAt,
195
197
  });
196
198
 
197
- await sessionRepository.createSession({
199
+ await authRepo.createRefreshToken({
200
+ token: refreshToken,
198
201
  userId: user.id,
199
- deviceInfo,
200
- ipAddress,
202
+ sessionId: session.id,
201
203
  expiresAt: refreshTokenExpiresAt,
202
204
  });
203
205
 
@@ -235,6 +237,7 @@ export async function refresh(refreshToken: string): Promise<{ accessToken: stri
235
237
  const rotated = await authRepo.rotateRefreshToken(refreshToken, {
236
238
  token: newRefreshToken,
237
239
  userId: user.id,
240
+ sessionId: storedToken.sessionId ?? undefined,
238
241
  expiresAt: getRefreshTokenExpiresAt(),
239
242
  });
240
243
 
@@ -249,25 +252,14 @@ export async function refresh(refreshToken: string): Promise<{ accessToken: stri
249
252
  }
250
253
 
251
254
  export async function logout(refreshToken: string): Promise<void> {
252
- try {
253
- // Find the token before deleting so we can match the corresponding session
254
- const storedToken = await authRepo.findRefreshToken(refreshToken);
255
- await authRepo.deleteRefreshToken(refreshToken);
256
-
257
- if (storedToken) {
258
- // Match session by creation time — token and session are created together during login
259
- const sessions = await sessionRepository.getUserSessions(storedToken.userId);
260
- const tokenCreatedMs = storedToken.createdAt.getTime();
261
- const matchingSession = sessions.find(
262
- (s) => Math.abs(s.createdAt.getTime() - tokenCreatedMs) < 5000,
263
- );
255
+ // Find the token before deleting so we can delete the linked session.
256
+ // Both deleteRefreshToken and deleteSession handle "already deleted" (P2025)
257
+ // gracefully, so no catch needed for concurrent request / cleanup job races.
258
+ const storedToken = await authRepo.findRefreshToken(refreshToken);
259
+ await authRepo.deleteRefreshToken(refreshToken);
264
260
 
265
- if (matchingSession) {
266
- await sessionRepository.deleteSession(matchingSession.id);
267
- }
268
- }
269
- } catch {
270
- // Token may already be deleted by concurrent request or cleanup job
261
+ if (storedToken?.sessionId) {
262
+ await sessionRepository.deleteSession(storedToken.sessionId);
271
263
  }
272
264
  }
273
265
 
@@ -1,4 +1,4 @@
1
- import { prisma } from '@libs/prisma.js';
1
+ import { prisma, isPrismaNotFound } from '@libs/prisma.js';
2
2
  import type { Session } from '@prisma/client';
3
3
 
4
4
  export class SessionRepository {
@@ -51,12 +51,17 @@ export class SessionRepository {
51
51
  }
52
52
 
53
53
  /**
54
- * Delete a specific session
54
+ * Delete a specific session (no-op if already deleted)
55
55
  */
56
56
  async deleteSession(sessionId: string): Promise<void> {
57
- await prisma.session.delete({
58
- where: { id: sessionId },
59
- });
57
+ try {
58
+ await prisma.session.delete({
59
+ where: { id: sessionId },
60
+ });
61
+ } catch (error) {
62
+ if (isPrismaNotFound(error)) return;
63
+ throw error;
64
+ }
60
65
  }
61
66
 
62
67
  /**
@@ -1,112 +0,0 @@
1
- 'use client';
2
-
3
- import { useCallback, useEffect, useRef, useState } from 'react';
4
-
5
- import { cn } from '@/lib/utils';
6
-
7
- type ThemeName = 'warm-orange' | 'electric-indigo' | 'ocean-teal' | 'rose-pink';
8
-
9
- interface PaletteOption {
10
- name: ThemeName;
11
- label: string;
12
- previewColor: string;
13
- }
14
-
15
- const PALETTES: PaletteOption[] = [
16
- { name: 'warm-orange', label: 'Warm Orange', previewColor: 'oklch(0.66 0.135 39.9)' },
17
- { name: 'electric-indigo', label: 'Electric Indigo', previewColor: 'oklch(0.62 0.22 270)' },
18
- { name: 'ocean-teal', label: 'Ocean Teal', previewColor: 'oklch(0.65 0.15 195)' },
19
- { name: 'rose-pink', label: 'Rose Pink', previewColor: 'oklch(0.68 0.2 350)' },
20
- ];
21
-
22
- const STORAGE_KEY = 'theme-palette';
23
- const TRANSITION_DURATION = 400;
24
- const TRANSITION_CLASS = 'theme-transitioning';
25
-
26
- interface ThemeSwitcherProps {
27
- smooth?: boolean;
28
- }
29
-
30
- export function ThemeSwitcher({ smooth = true }: ThemeSwitcherProps): React.ReactElement {
31
- const [active, setActive] = useState<ThemeName>('warm-orange');
32
- const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
33
-
34
- useEffect(() => {
35
- const stored = localStorage.getItem(STORAGE_KEY) as ThemeName | null;
36
- if (stored && PALETTES.some((p) => p.name === stored)) {
37
- setActive(stored);
38
- if (stored !== 'warm-orange') {
39
- document.documentElement.setAttribute('data-theme', stored);
40
- }
41
- }
42
- }, []);
43
-
44
- const handleSelect = useCallback((palette: PaletteOption): void => {
45
- setActive(palette.name);
46
- const root = document.documentElement;
47
-
48
- if (smooth) {
49
- root.classList.add(TRANSITION_CLASS);
50
- if (timeoutRef.current) clearTimeout(timeoutRef.current);
51
- timeoutRef.current = setTimeout(() => {
52
- root.classList.remove(TRANSITION_CLASS);
53
- }, TRANSITION_DURATION);
54
- }
55
-
56
- if (palette.name === 'warm-orange') {
57
- root.removeAttribute('data-theme');
58
- } else {
59
- root.setAttribute('data-theme', palette.name);
60
- }
61
-
62
- localStorage.setItem(STORAGE_KEY, palette.name);
63
- }, [smooth]);
64
-
65
- useEffect(() => {
66
- return (): void => {
67
- if (timeoutRef.current) clearTimeout(timeoutRef.current);
68
- document.documentElement.classList.remove(TRANSITION_CLASS);
69
- };
70
- }, []);
71
-
72
- return (
73
- <>
74
- {smooth && (
75
- <style>{`
76
- .theme-transitioning,
77
- .theme-transitioning *,
78
- .theme-transitioning *::before,
79
- .theme-transitioning *::after {
80
- transition: background-color 0.4s ease, color 0.4s ease, border-color 0.4s ease, box-shadow 0.4s ease !important;
81
- }
82
- `}</style>
83
- )}
84
- <div className="flex flex-col items-center gap-3">
85
- <p className="text-xs font-medium tracking-wider text-muted-foreground uppercase">
86
- Theme Palette
87
- </p>
88
- <div className="flex gap-2">
89
- {PALETTES.map((palette) => (
90
- <button
91
- key={palette.name}
92
- type="button"
93
- onClick={() => handleSelect(palette)}
94
- aria-label={`Switch to ${palette.label} theme`}
95
- className={cn(
96
- 'flex min-h-11 min-w-11 items-center justify-center rounded-xl border-2 p-2.5 transition-all duration-200 active:scale-[0.95]',
97
- active === palette.name
98
- ? 'border-foreground/40 bg-muted shadow-md'
99
- : 'border-transparent bg-muted/50 md:hover:bg-muted md:hover:border-border',
100
- )}
101
- >
102
- <div
103
- className="h-5 w-5 rounded-full shadow-sm ring-1 ring-foreground/10"
104
- style={{ backgroundColor: palette.previewColor }}
105
- />
106
- </button>
107
- ))}
108
- </div>
109
- </div>
110
- </>
111
- );
112
- }
@@ -1,90 +0,0 @@
1
- /*
2
- * Theme: Electric Indigo
3
- * Accent: Deep vibrant indigo-violet
4
- * Vibe: Modern, bold, tech-forward — inspired by Linear, Figma
5
- *
6
- * Activated via data-theme="electric-indigo" attribute on <html>
7
- */
8
-
9
- [data-theme="electric-indigo"] {
10
- --radius: 0.625rem;
11
- /* Cool gray background with electric indigo accent */
12
- --background: oklch(0.98 0.002 265);
13
- --foreground: oklch(0.17 0.02 265);
14
- --card: oklch(1 0 0);
15
- --card-foreground: oklch(0.17 0.02 265);
16
- --popover: oklch(1 0 0);
17
- --popover-foreground: oklch(0.17 0.02 265);
18
- --primary: oklch(0.5 0.22 270);
19
- --primary-foreground: oklch(0.98 0.002 265);
20
- --secondary: oklch(0.95 0.005 265);
21
- --secondary-foreground: oklch(0.28 0.02 265);
22
- --muted: oklch(0.95 0.005 265);
23
- --muted-foreground: oklch(0.55 0.015 265);
24
- --accent: oklch(0.95 0.005 265);
25
- --accent-foreground: oklch(0.28 0.02 265);
26
- --destructive: oklch(0.577 0.245 27.325);
27
- --border: oklch(0.91 0.005 265);
28
- --input: oklch(0.91 0.005 265);
29
- --ring: oklch(0.5 0.22 270);
30
- --success: oklch(0.52 0.17 155);
31
- --success-foreground: oklch(1 0 0);
32
- --warning: oklch(0.75 0.15 70);
33
- --warning-foreground: oklch(0.25 0.015 70);
34
- --info: oklch(0.55 0.15 240);
35
- --info-foreground: oklch(1 0 0);
36
- --chart-1: oklch(0.5 0.22 270);
37
- --chart-2: oklch(0.6 0.15 190);
38
- --chart-3: oklch(0.65 0.18 330);
39
- --chart-4: oklch(0.7 0.15 85);
40
- --chart-5: oklch(0.55 0.15 30);
41
- --sidebar: oklch(0.97 0.003 265);
42
- --sidebar-foreground: oklch(0.17 0.02 265);
43
- --sidebar-primary: oklch(0.28 0.02 265);
44
- --sidebar-primary-foreground: oklch(0.98 0.002 265);
45
- --sidebar-accent: oklch(0.95 0.005 265);
46
- --sidebar-accent-foreground: oklch(0.28 0.02 265);
47
- --sidebar-border: oklch(0.91 0.005 265);
48
- --sidebar-ring: oklch(0.5 0.22 270);
49
- }
50
-
51
- .dark[data-theme="electric-indigo"] {
52
- /* Dark mode: deep cool gray with brighter indigo */
53
- --background: oklch(0.16 0.015 265);
54
- --foreground: oklch(0.93 0.005 265);
55
- --card: oklch(0.21 0.015 265);
56
- --card-foreground: oklch(0.93 0.005 265);
57
- --popover: oklch(0.21 0.015 265);
58
- --popover-foreground: oklch(0.93 0.005 265);
59
- --primary: oklch(0.62 0.22 270);
60
- --primary-foreground: oklch(0.16 0.015 265);
61
- --secondary: oklch(0.26 0.015 265);
62
- --secondary-foreground: oklch(0.93 0.005 265);
63
- --muted: oklch(0.26 0.015 265);
64
- --muted-foreground: oklch(0.6 0.015 265);
65
- --accent: oklch(0.26 0.015 265);
66
- --accent-foreground: oklch(0.93 0.005 265);
67
- --destructive: oklch(0.704 0.191 22.216);
68
- --border: oklch(1 0.005 265 / 12%);
69
- --input: oklch(1 0.005 265 / 15%);
70
- --ring: oklch(0.62 0.22 270);
71
- --success: oklch(0.6 0.17 155);
72
- --success-foreground: oklch(1 0 0);
73
- --warning: oklch(0.8 0.15 70);
74
- --warning-foreground: oklch(0.25 0.015 70);
75
- --info: oklch(0.65 0.15 240);
76
- --info-foreground: oklch(1 0 0);
77
- --chart-1: oklch(0.62 0.22 270);
78
- --chart-2: oklch(0.7 0.15 190);
79
- --chart-3: oklch(0.75 0.18 330);
80
- --chart-4: oklch(0.8 0.15 85);
81
- --chart-5: oklch(0.65 0.15 30);
82
- --sidebar: oklch(0.21 0.015 265);
83
- --sidebar-foreground: oklch(0.93 0.005 265);
84
- --sidebar-primary: oklch(0.62 0.22 270);
85
- --sidebar-primary-foreground: oklch(0.93 0.005 265);
86
- --sidebar-accent: oklch(0.26 0.015 265);
87
- --sidebar-accent-foreground: oklch(0.93 0.005 265);
88
- --sidebar-border: oklch(1 0.005 265 / 12%);
89
- --sidebar-ring: oklch(0.62 0.22 270);
90
- }
@@ -1,90 +0,0 @@
1
- /*
2
- * Theme: Ocean Teal
3
- * Accent: Deep teal-cyan
4
- * Vibe: Calm, professional, trustworthy — inspired by Stripe, Vercel
5
- *
6
- * Activated via data-theme="ocean-teal" attribute on <html>
7
- */
8
-
9
- [data-theme="ocean-teal"] {
10
- --radius: 0.625rem;
11
- /* Cool neutral background with ocean teal accent */
12
- --background: oklch(0.98 0.003 200);
13
- --foreground: oklch(0.18 0.015 210);
14
- --card: oklch(1 0 0);
15
- --card-foreground: oklch(0.18 0.015 210);
16
- --popover: oklch(1 0 0);
17
- --popover-foreground: oklch(0.18 0.015 210);
18
- --primary: oklch(0.55 0.15 195);
19
- --primary-foreground: oklch(1 0 0);
20
- --secondary: oklch(0.95 0.006 200);
21
- --secondary-foreground: oklch(0.3 0.015 210);
22
- --muted: oklch(0.95 0.006 200);
23
- --muted-foreground: oklch(0.55 0.012 210);
24
- --accent: oklch(0.95 0.006 200);
25
- --accent-foreground: oklch(0.3 0.015 210);
26
- --destructive: oklch(0.577 0.245 27.325);
27
- --border: oklch(0.91 0.006 200);
28
- --input: oklch(0.91 0.006 200);
29
- --ring: oklch(0.55 0.15 195);
30
- --success: oklch(0.52 0.17 155);
31
- --success-foreground: oklch(1 0 0);
32
- --warning: oklch(0.75 0.15 70);
33
- --warning-foreground: oklch(0.25 0.015 70);
34
- --info: oklch(0.55 0.15 240);
35
- --info-foreground: oklch(1 0 0);
36
- --chart-1: oklch(0.55 0.15 195);
37
- --chart-2: oklch(0.6 0.12 160);
38
- --chart-3: oklch(0.5 0.1 250);
39
- --chart-4: oklch(0.7 0.12 95);
40
- --chart-5: oklch(0.6 0.15 320);
41
- --sidebar: oklch(0.97 0.004 200);
42
- --sidebar-foreground: oklch(0.18 0.015 210);
43
- --sidebar-primary: oklch(0.3 0.015 210);
44
- --sidebar-primary-foreground: oklch(0.98 0.003 200);
45
- --sidebar-accent: oklch(0.95 0.006 200);
46
- --sidebar-accent-foreground: oklch(0.3 0.015 210);
47
- --sidebar-border: oklch(0.91 0.006 200);
48
- --sidebar-ring: oklch(0.55 0.15 195);
49
- }
50
-
51
- .dark[data-theme="ocean-teal"] {
52
- /* Dark mode: deep blue-gray with brighter teal */
53
- --background: oklch(0.16 0.012 210);
54
- --foreground: oklch(0.93 0.006 200);
55
- --card: oklch(0.21 0.012 210);
56
- --card-foreground: oklch(0.93 0.006 200);
57
- --popover: oklch(0.21 0.012 210);
58
- --popover-foreground: oklch(0.93 0.006 200);
59
- --primary: oklch(0.65 0.15 195);
60
- --primary-foreground: oklch(0.16 0.012 210);
61
- --secondary: oklch(0.26 0.012 210);
62
- --secondary-foreground: oklch(0.93 0.006 200);
63
- --muted: oklch(0.26 0.012 210);
64
- --muted-foreground: oklch(0.6 0.012 210);
65
- --accent: oklch(0.26 0.012 210);
66
- --accent-foreground: oklch(0.93 0.006 200);
67
- --destructive: oklch(0.704 0.191 22.216);
68
- --border: oklch(1 0.006 200 / 12%);
69
- --input: oklch(1 0.006 200 / 15%);
70
- --ring: oklch(0.65 0.15 195);
71
- --success: oklch(0.6 0.17 155);
72
- --success-foreground: oklch(1 0 0);
73
- --warning: oklch(0.8 0.15 70);
74
- --warning-foreground: oklch(0.25 0.015 70);
75
- --info: oklch(0.65 0.15 240);
76
- --info-foreground: oklch(1 0 0);
77
- --chart-1: oklch(0.65 0.15 195);
78
- --chart-2: oklch(0.7 0.12 160);
79
- --chart-3: oklch(0.6 0.1 250);
80
- --chart-4: oklch(0.8 0.12 95);
81
- --chart-5: oklch(0.7 0.15 320);
82
- --sidebar: oklch(0.21 0.012 210);
83
- --sidebar-foreground: oklch(0.93 0.006 200);
84
- --sidebar-primary: oklch(0.65 0.15 195);
85
- --sidebar-primary-foreground: oklch(0.93 0.006 200);
86
- --sidebar-accent: oklch(0.26 0.012 210);
87
- --sidebar-accent-foreground: oklch(0.93 0.006 200);
88
- --sidebar-border: oklch(1 0.006 200 / 12%);
89
- --sidebar-ring: oklch(0.65 0.15 195);
90
- }
@@ -1,90 +0,0 @@
1
- /*
2
- * Theme: Rose Pink
3
- * Accent: Soft rose-magenta
4
- * Vibe: Elegant, creative, premium — inspired by Dribbble, Notion
5
- *
6
- * Activated via data-theme="rose-pink" attribute on <html>
7
- */
8
-
9
- [data-theme="rose-pink"] {
10
- --radius: 0.625rem;
11
- /* Warm neutral background with rose-pink accent */
12
- --background: oklch(0.98 0.004 350);
13
- --foreground: oklch(0.18 0.015 340);
14
- --card: oklch(1 0 0);
15
- --card-foreground: oklch(0.18 0.015 340);
16
- --popover: oklch(1 0 0);
17
- --popover-foreground: oklch(0.18 0.015 340);
18
- --primary: oklch(0.58 0.2 350);
19
- --primary-foreground: oklch(1 0 0);
20
- --secondary: oklch(0.95 0.006 350);
21
- --secondary-foreground: oklch(0.3 0.015 340);
22
- --muted: oklch(0.95 0.006 350);
23
- --muted-foreground: oklch(0.55 0.012 340);
24
- --accent: oklch(0.95 0.006 350);
25
- --accent-foreground: oklch(0.3 0.015 340);
26
- --destructive: oklch(0.577 0.245 27.325);
27
- --border: oklch(0.91 0.006 350);
28
- --input: oklch(0.91 0.006 350);
29
- --ring: oklch(0.58 0.2 350);
30
- --success: oklch(0.52 0.17 155);
31
- --success-foreground: oklch(1 0 0);
32
- --warning: oklch(0.75 0.15 70);
33
- --warning-foreground: oklch(0.25 0.015 70);
34
- --info: oklch(0.55 0.15 240);
35
- --info-foreground: oklch(1 0 0);
36
- --chart-1: oklch(0.58 0.2 350);
37
- --chart-2: oklch(0.55 0.15 270);
38
- --chart-3: oklch(0.6 0.12 195);
39
- --chart-4: oklch(0.7 0.13 85);
40
- --chart-5: oklch(0.5 0.1 30);
41
- --sidebar: oklch(0.97 0.004 350);
42
- --sidebar-foreground: oklch(0.18 0.015 340);
43
- --sidebar-primary: oklch(0.3 0.015 340);
44
- --sidebar-primary-foreground: oklch(0.98 0.004 350);
45
- --sidebar-accent: oklch(0.95 0.006 350);
46
- --sidebar-accent-foreground: oklch(0.3 0.015 340);
47
- --sidebar-border: oklch(0.91 0.006 350);
48
- --sidebar-ring: oklch(0.58 0.2 350);
49
- }
50
-
51
- .dark[data-theme="rose-pink"] {
52
- /* Dark mode: deep warm gray with brighter rose */
53
- --background: oklch(0.16 0.012 340);
54
- --foreground: oklch(0.93 0.005 350);
55
- --card: oklch(0.21 0.012 340);
56
- --card-foreground: oklch(0.93 0.005 350);
57
- --popover: oklch(0.21 0.012 340);
58
- --popover-foreground: oklch(0.93 0.005 350);
59
- --primary: oklch(0.68 0.2 350);
60
- --primary-foreground: oklch(0.16 0.012 340);
61
- --secondary: oklch(0.26 0.012 340);
62
- --secondary-foreground: oklch(0.93 0.005 350);
63
- --muted: oklch(0.26 0.012 340);
64
- --muted-foreground: oklch(0.6 0.012 340);
65
- --accent: oklch(0.26 0.012 340);
66
- --accent-foreground: oklch(0.93 0.005 350);
67
- --destructive: oklch(0.704 0.191 22.216);
68
- --border: oklch(1 0.005 350 / 12%);
69
- --input: oklch(1 0.005 350 / 15%);
70
- --ring: oklch(0.68 0.2 350);
71
- --success: oklch(0.6 0.17 155);
72
- --success-foreground: oklch(1 0 0);
73
- --warning: oklch(0.8 0.15 70);
74
- --warning-foreground: oklch(0.25 0.015 70);
75
- --info: oklch(0.65 0.15 240);
76
- --info-foreground: oklch(1 0 0);
77
- --chart-1: oklch(0.68 0.2 350);
78
- --chart-2: oklch(0.65 0.15 270);
79
- --chart-3: oklch(0.7 0.12 195);
80
- --chart-4: oklch(0.8 0.13 85);
81
- --chart-5: oklch(0.6 0.1 30);
82
- --sidebar: oklch(0.21 0.012 340);
83
- --sidebar-foreground: oklch(0.93 0.005 350);
84
- --sidebar-primary: oklch(0.68 0.2 350);
85
- --sidebar-primary-foreground: oklch(0.93 0.005 350);
86
- --sidebar-accent: oklch(0.26 0.012 340);
87
- --sidebar-accent-foreground: oklch(0.93 0.005 350);
88
- --sidebar-border: oklch(1 0.005 350 / 12%);
89
- --sidebar-ring: oklch(0.68 0.2 350);
90
- }
@@ -1,90 +0,0 @@
1
- /*
2
- * Theme: Warm Orange
3
- * Accent: Warm terracotta orange — inspired by Claude's palette
4
- * Vibe: Earthy, warm, approachable
5
- *
6
- * To activate: in globals.css, set @import "../styles/themes/warm-orange.css";
7
- */
8
-
9
- :root {
10
- --radius: 0.625rem;
11
- /* Warm cream background with terracotta orange accent */
12
- --background: oklch(0.964 0.007 97.5);
13
- --foreground: oklch(0.205 0.015 92);
14
- --card: oklch(1 0 0);
15
- --card-foreground: oklch(0.205 0.015 92);
16
- --popover: oklch(1 0 0);
17
- --popover-foreground: oklch(0.205 0.015 92);
18
- --primary: oklch(0.597 0.135 39.9);
19
- --primary-foreground: oklch(1 0 0);
20
- --secondary: oklch(0.935 0.009 97.5);
21
- --secondary-foreground: oklch(0.3 0.015 92);
22
- --muted: oklch(0.935 0.009 97.5);
23
- --muted-foreground: oklch(0.748 0.017 91.6);
24
- --accent: oklch(0.935 0.009 97.5);
25
- --accent-foreground: oklch(0.3 0.015 92);
26
- --destructive: oklch(0.577 0.245 27.325);
27
- --border: oklch(0.895 0.012 97.5);
28
- --input: oklch(0.895 0.012 97.5);
29
- --ring: oklch(0.597 0.135 39.9);
30
- --success: oklch(0.52 0.17 155);
31
- --success-foreground: oklch(1 0 0);
32
- --warning: oklch(0.75 0.15 70);
33
- --warning-foreground: oklch(0.25 0.015 92);
34
- --info: oklch(0.55 0.15 240);
35
- --info-foreground: oklch(1 0 0);
36
- --chart-1: oklch(0.597 0.135 39.9);
37
- --chart-2: oklch(0.6 0.118 184.704);
38
- --chart-3: oklch(0.398 0.07 227.392);
39
- --chart-4: oklch(0.828 0.12 84);
40
- --chart-5: oklch(0.748 0.017 91.6);
41
- --sidebar: oklch(0.95 0.007 97.5);
42
- --sidebar-foreground: oklch(0.205 0.015 92);
43
- --sidebar-primary: oklch(0.3 0.015 92);
44
- --sidebar-primary-foreground: oklch(0.964 0.007 97.5);
45
- --sidebar-accent: oklch(0.935 0.009 97.5);
46
- --sidebar-accent-foreground: oklch(0.3 0.015 92);
47
- --sidebar-border: oklch(0.895 0.012 97.5);
48
- --sidebar-ring: oklch(0.597 0.135 39.9);
49
- }
50
-
51
- .dark {
52
- /* Dark mode: inverted with same warm undertone */
53
- --background: oklch(0.185 0.012 92);
54
- --foreground: oklch(0.93 0.007 97.5);
55
- --card: oklch(0.235 0.012 92);
56
- --card-foreground: oklch(0.93 0.007 97.5);
57
- --popover: oklch(0.235 0.012 92);
58
- --popover-foreground: oklch(0.93 0.007 97.5);
59
- --primary: oklch(0.66 0.135 39.9);
60
- --primary-foreground: oklch(0.185 0.012 92);
61
- --secondary: oklch(0.28 0.012 92);
62
- --secondary-foreground: oklch(0.93 0.007 97.5);
63
- --muted: oklch(0.28 0.012 92);
64
- --muted-foreground: oklch(0.65 0.015 91.6);
65
- --accent: oklch(0.28 0.012 92);
66
- --accent-foreground: oklch(0.93 0.007 97.5);
67
- --destructive: oklch(0.704 0.191 22.216);
68
- --border: oklch(1 0.007 97.5 / 12%);
69
- --input: oklch(1 0.007 97.5 / 15%);
70
- --ring: oklch(0.66 0.135 39.9);
71
- --success: oklch(0.6 0.17 155);
72
- --success-foreground: oklch(1 0 0);
73
- --warning: oklch(0.8 0.15 70);
74
- --warning-foreground: oklch(0.25 0.015 92);
75
- --info: oklch(0.65 0.15 240);
76
- --info-foreground: oklch(1 0 0);
77
- --chart-1: oklch(0.66 0.135 39.9);
78
- --chart-2: oklch(0.696 0.17 162.48);
79
- --chart-3: oklch(0.769 0.12 70);
80
- --chart-4: oklch(0.627 0.18 303.9);
81
- --chart-5: oklch(0.645 0.17 16.439);
82
- --sidebar: oklch(0.235 0.012 92);
83
- --sidebar-foreground: oklch(0.93 0.007 97.5);
84
- --sidebar-primary: oklch(0.66 0.135 39.9);
85
- --sidebar-primary-foreground: oklch(0.93 0.007 97.5);
86
- --sidebar-accent: oklch(0.28 0.012 92);
87
- --sidebar-accent-foreground: oklch(0.93 0.007 97.5);
88
- --sidebar-border: oklch(1 0.007 97.5 / 12%);
89
- --sidebar-ring: oklch(0.66 0.135 39.9);
90
- }