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.
- package/package.json +1 -1
- package/template/_claude/rules/client/01-project-structure.md +2 -5
- package/template/_claude/rules/client/04-design-system.md +48 -43
- package/template/_claude/rules/client/core.md +2 -2
- package/template/client/src/app/globals.css +12 -12
- package/template/client/src/app/layout.tsx +1 -1
- package/template/client/src/app/page.tsx +5 -5
- package/template/client/src/app/providers.tsx +1 -1
- package/template/client/src/components/common/ThemeToggle.tsx +59 -0
- package/template/client/src/features/admin/hooks/useAdminSessions.ts +68 -0
- package/template/client/src/features/admin/hooks/useAdminStats.ts +27 -0
- package/template/client/src/features/admin/hooks/useAdminUsers.ts +132 -0
- package/template/client/src/features/admin/services/admin.service.ts +94 -0
- package/template/client/src/features/admin/types/admin.types.ts +65 -0
- package/template/client/src/features/auth/components/AuthInitializer.tsx +18 -1
- package/template/client/src/lib/api/axios.config.ts +20 -1
- package/template/client/src/lib/constants/api-endpoints.ts +9 -0
- package/template/client/src/lib/constants/app.constants.ts +3 -1
- package/template/client/src/lib/constants/routes.ts +6 -0
- package/template/client/src/lib/env.ts +35 -0
- package/template/client/src/styles/themes/default.css +92 -0
- package/template/server/package.json +1 -0
- package/template/server/postman/collection.json +168 -50
- package/template/server/prisma/schema.prisma +2 -0
- package/template/server/src/jobs/cleanup-deleted-accounts.job.ts +14 -4
- package/template/server/src/libs/prisma.ts +13 -0
- package/template/server/src/modules/admin/admin.controller.ts +130 -1
- package/template/server/src/modules/admin/admin.repo.ts +289 -0
- package/template/server/src/modules/admin/admin.routes.ts +113 -7
- package/template/server/src/modules/admin/admin.schemas.ts +49 -0
- package/template/server/src/modules/admin/admin.service.ts +154 -0
- package/template/server/src/modules/auth/auth.repo.ts +5 -18
- package/template/server/src/modules/auth/auth.service.ts +20 -28
- package/template/server/src/modules/auth/session.repo.ts +10 -5
- package/template/client/src/components/common/ThemeSwitcher.tsx +0 -112
- package/template/client/src/styles/themes/electric-indigo.css +0 -90
- package/template/client/src/styles/themes/ocean-teal.css +0 -90
- package/template/client/src/styles/themes/rose-pink.css +0 -90
- 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
|
|
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
|
|
113
|
+
await authRepo.createRefreshToken({
|
|
114
|
+
token: refreshToken,
|
|
113
115
|
userId: user.id,
|
|
114
|
-
|
|
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
|
|
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
|
|
199
|
+
await authRepo.createRefreshToken({
|
|
200
|
+
token: refreshToken,
|
|
198
201
|
userId: user.id,
|
|
199
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
|
|
266
|
-
|
|
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
|
-
|
|
58
|
-
|
|
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
|
-
}
|