create-tigra 2.4.0 → 2.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 (42) hide show
  1. package/bin/create-tigra.js +5 -2
  2. package/package.json +1 -1
  3. package/template/_claude/rules/client/01-project-structure.md +6 -0
  4. package/template/_claude/rules/client/04-design-system.md +67 -44
  5. package/template/_claude/rules/client/core.md +3 -2
  6. package/template/_claude/rules/global/completion-reports.md +178 -0
  7. package/template/_claude/rules/global/core.md +6 -0
  8. package/template/client/.dockerignore +63 -0
  9. package/template/client/.env.example.production +5 -0
  10. package/template/client/Dockerfile +98 -0
  11. package/template/client/next.config.ts +1 -0
  12. package/template/client/src/app/error.tsx +1 -1
  13. package/template/client/src/app/globals.css +18 -83
  14. package/template/client/src/app/layout.tsx +1 -1
  15. package/template/client/src/app/page.tsx +10 -3
  16. package/template/client/src/app/providers.tsx +1 -1
  17. package/template/client/src/components/common/ThemeSwitcher.tsx +112 -0
  18. package/template/client/src/components/layout/Header.tsx +1 -1
  19. package/template/client/src/features/auth/components/AuthInitializer.tsx +8 -2
  20. package/template/client/src/features/auth/components/LoginForm.tsx +1 -1
  21. package/template/client/src/features/auth/components/RegisterForm.tsx +1 -1
  22. package/template/client/src/features/auth/services/auth.service.ts +0 -4
  23. package/template/client/src/lib/api/axios.config.ts +1 -1
  24. package/template/client/src/lib/constants/api-endpoints.ts +0 -2
  25. package/template/client/src/styles/themes/electric-indigo.css +90 -0
  26. package/template/client/src/styles/themes/ocean-teal.css +90 -0
  27. package/template/client/src/styles/themes/rose-pink.css +90 -0
  28. package/template/client/src/styles/themes/warm-orange.css +90 -0
  29. package/template/server/.dockerignore +0 -1
  30. package/template/server/.env.example +21 -0
  31. package/template/server/.env.example.production +26 -0
  32. package/template/server/Dockerfile +8 -0
  33. package/template/server/package.json +1 -1
  34. package/template/server/prisma/schema.prisma +0 -1
  35. package/template/server/src/modules/admin/admin.controller.ts +3 -9
  36. package/template/server/src/modules/admin/admin.routes.ts +7 -0
  37. package/template/server/src/modules/admin/admin.schemas.ts +13 -0
  38. package/template/server/src/modules/auth/auth.repo.ts +9 -8
  39. package/template/server/src/server.ts +1 -1
  40. package/template/server/tsconfig.build.json +4 -0
  41. package/template/server/tsconfig.json +1 -0
  42. package/template/server/postman_collection.json +0 -666
@@ -2,6 +2,24 @@
2
2
  @import "tw-animate-css";
3
3
  @import "shadcn/tailwind.css";
4
4
 
5
+ /*
6
+ * ============================================================
7
+ * THEME PRESETS — All themes loaded, switched via data-theme
8
+ * ============================================================
9
+ * warm-orange.css is the default (uses :root / .dark selectors).
10
+ * Other presets are scoped to [data-theme="<name>"] and activated
11
+ * by the ThemeSwitcher component setting data-theme on <html>.
12
+ *
13
+ * To create a custom theme: copy any preset, scope it under
14
+ * [data-theme="your-name"] / .dark[data-theme="your-name"],
15
+ * import it below, and add it to ThemeSwitcher's PALETTES array.
16
+ * ============================================================
17
+ */
18
+ @import "../styles/themes/warm-orange.css";
19
+ @import "../styles/themes/electric-indigo.css";
20
+ @import "../styles/themes/ocean-teal.css";
21
+ @import "../styles/themes/rose-pink.css";
22
+
5
23
  @custom-variant dark (&:is(.dark *));
6
24
 
7
25
  @theme inline {
@@ -53,89 +71,6 @@
53
71
  --radius-4xl: calc(var(--radius) + 16px);
54
72
  }
55
73
 
56
- :root {
57
- --radius: 0.625rem;
58
- /* Claude palette: #f4f3ee (cream), #ffffff (white), #b1ada1 (warm gray), #c15f3c (orange) */
59
- --background: oklch(0.964 0.007 97.5);
60
- --foreground: oklch(0.205 0.015 92);
61
- --card: oklch(1 0 0);
62
- --card-foreground: oklch(0.205 0.015 92);
63
- --popover: oklch(1 0 0);
64
- --popover-foreground: oklch(0.205 0.015 92);
65
- --primary: oklch(0.597 0.135 39.9);
66
- --primary-foreground: oklch(1 0 0);
67
- --secondary: oklch(0.935 0.009 97.5);
68
- --secondary-foreground: oklch(0.3 0.015 92);
69
- --muted: oklch(0.935 0.009 97.5);
70
- --muted-foreground: oklch(0.748 0.017 91.6);
71
- --accent: oklch(0.935 0.009 97.5);
72
- --accent-foreground: oklch(0.3 0.015 92);
73
- --destructive: oklch(0.577 0.245 27.325);
74
- --border: oklch(0.895 0.012 97.5);
75
- --input: oklch(0.895 0.012 97.5);
76
- --ring: oklch(0.597 0.135 39.9);
77
- --success: oklch(0.52 0.17 155);
78
- --success-foreground: oklch(1 0 0);
79
- --warning: oklch(0.75 0.15 70);
80
- --warning-foreground: oklch(0.25 0.015 92);
81
- --info: oklch(0.55 0.15 240);
82
- --info-foreground: oklch(1 0 0);
83
- --chart-1: oklch(0.597 0.135 39.9);
84
- --chart-2: oklch(0.6 0.118 184.704);
85
- --chart-3: oklch(0.398 0.07 227.392);
86
- --chart-4: oklch(0.828 0.12 84);
87
- --chart-5: oklch(0.748 0.017 91.6);
88
- --sidebar: oklch(0.95 0.007 97.5);
89
- --sidebar-foreground: oklch(0.205 0.015 92);
90
- --sidebar-primary: oklch(0.3 0.015 92);
91
- --sidebar-primary-foreground: oklch(0.964 0.007 97.5);
92
- --sidebar-accent: oklch(0.935 0.009 97.5);
93
- --sidebar-accent-foreground: oklch(0.3 0.015 92);
94
- --sidebar-border: oklch(0.895 0.012 97.5);
95
- --sidebar-ring: oklch(0.597 0.135 39.9);
96
- }
97
-
98
- .dark {
99
- /* Dark mode: inverted Claude palette with same warm undertone */
100
- --background: oklch(0.185 0.012 92);
101
- --foreground: oklch(0.93 0.007 97.5);
102
- --card: oklch(0.235 0.012 92);
103
- --card-foreground: oklch(0.93 0.007 97.5);
104
- --popover: oklch(0.235 0.012 92);
105
- --popover-foreground: oklch(0.93 0.007 97.5);
106
- --primary: oklch(0.66 0.135 39.9);
107
- --primary-foreground: oklch(0.185 0.012 92);
108
- --secondary: oklch(0.28 0.012 92);
109
- --secondary-foreground: oklch(0.93 0.007 97.5);
110
- --muted: oklch(0.28 0.012 92);
111
- --muted-foreground: oklch(0.65 0.015 91.6);
112
- --accent: oklch(0.28 0.012 92);
113
- --accent-foreground: oklch(0.93 0.007 97.5);
114
- --destructive: oklch(0.704 0.191 22.216);
115
- --border: oklch(1 0.007 97.5 / 12%);
116
- --input: oklch(1 0.007 97.5 / 15%);
117
- --ring: oklch(0.66 0.135 39.9);
118
- --success: oklch(0.6 0.17 155);
119
- --success-foreground: oklch(1 0 0);
120
- --warning: oklch(0.8 0.15 70);
121
- --warning-foreground: oklch(0.25 0.015 92);
122
- --info: oklch(0.65 0.15 240);
123
- --info-foreground: oklch(1 0 0);
124
- --chart-1: oklch(0.66 0.135 39.9);
125
- --chart-2: oklch(0.696 0.17 162.48);
126
- --chart-3: oklch(0.769 0.12 70);
127
- --chart-4: oklch(0.627 0.18 303.9);
128
- --chart-5: oklch(0.645 0.17 16.439);
129
- --sidebar: oklch(0.235 0.012 92);
130
- --sidebar-foreground: oklch(0.93 0.007 97.5);
131
- --sidebar-primary: oklch(0.66 0.135 39.9);
132
- --sidebar-primary-foreground: oklch(0.93 0.007 97.5);
133
- --sidebar-accent: oklch(0.28 0.012 92);
134
- --sidebar-accent-foreground: oklch(0.93 0.007 97.5);
135
- --sidebar-border: oklch(1 0.007 97.5 / 12%);
136
- --sidebar-ring: oklch(0.66 0.135 39.9);
137
- }
138
-
139
74
  @layer base {
140
75
  * {
141
76
  @apply border-border outline-ring/50;
@@ -27,7 +27,7 @@ export default function RootLayout({
27
27
  children: React.ReactNode;
28
28
  }>): React.ReactElement {
29
29
  return (
30
- <html lang="en" suppressHydrationWarning>
30
+ <html lang="en" className="dark" suppressHydrationWarning>
31
31
  <body className={`${geistSans.variable} ${geistMono.variable} font-sans antialiased`}>
32
32
  <Providers>{children}</Providers>
33
33
  </body>
@@ -3,6 +3,8 @@ import type { Metadata } from 'next';
3
3
 
4
4
  import Image from 'next/image';
5
5
 
6
+ import { ThemeSwitcher } from '@/components/common/ThemeSwitcher';
7
+
6
8
  import { APP_NAME } from '@/lib/constants/app.constants';
7
9
 
8
10
  export const metadata: Metadata = {
@@ -44,12 +46,17 @@ export default function WelcomePage(): React.ReactElement {
44
46
  <span className="font-semibold text-foreground">create-tigra</span>
45
47
  </p>
46
48
 
47
- <div className="mt-8 flex flex-col gap-3 sm:flex-row sm:gap-4">
49
+ {/* Theme Palette Switcher */}
50
+ <div className="mt-8 pb-6">
51
+ <ThemeSwitcher />
52
+ </div>
53
+
54
+ <div className="mt-4 flex flex-col gap-3 sm:flex-row sm:gap-4">
48
55
  <a
49
56
  href="https://github.com/BehzodKarimov/create-tigra"
50
57
  target="_blank"
51
58
  rel="noopener noreferrer"
52
- className="inline-flex min-h-11 items-center justify-center rounded-lg bg-primary px-5 py-2.5 text-sm font-medium text-primary-foreground shadow-sm transition-all duration-200 active:scale-[0.97] md:hover:brightness-110"
59
+ className="inline-flex min-h-11 items-center justify-center rounded-lg bg-primary px-5 py-2.5 text-sm font-medium text-primary-foreground shadow-sm transition-all duration-200 active:scale-[0.97] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 md:hover:brightness-110"
53
60
  >
54
61
  Documentation
55
62
  </a>
@@ -57,7 +64,7 @@ export default function WelcomePage(): React.ReactElement {
57
64
  href="https://github.com/BehzodKarimov/create-tigra/issues"
58
65
  target="_blank"
59
66
  rel="noopener noreferrer"
60
- className="inline-flex min-h-11 items-center justify-center rounded-lg border border-border bg-secondary px-5 py-2.5 text-sm font-medium text-secondary-foreground transition-all duration-200 active:scale-[0.97] md:hover:bg-accent"
67
+ className="inline-flex min-h-11 items-center justify-center rounded-lg border border-border bg-secondary px-5 py-2.5 text-sm font-medium text-secondary-foreground transition-all duration-200 active:scale-[0.97] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 md:hover:bg-accent"
61
68
  >
62
69
  Report an issue
63
70
  </a>
@@ -29,7 +29,7 @@ export function Providers({ children }: { children: React.ReactNode }): React.Re
29
29
  return (
30
30
  <ReduxProvider store={store}>
31
31
  <QueryClientProvider client={queryClient}>
32
- <ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
32
+ <ThemeProvider attribute="class" defaultTheme="dark" disableTransitionOnChange>
33
33
  <AuthInitializer>
34
34
  {children}
35
35
  </AuthInitializer>
@@ -0,0 +1,112 @@
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
+ }
@@ -52,7 +52,7 @@ export const Header = (): React.ReactElement => {
52
52
  <div className="container mx-auto flex h-16 items-center justify-between px-4 md:px-6 lg:px-8">
53
53
  <Link
54
54
  href={ROUTES.HOME}
55
- className="text-xl font-bold tracking-tight transition-colors duration-150 hover:text-primary"
55
+ className="text-xl font-bold tracking-tight transition-colors duration-150 active:opacity-70 md:hover:text-primary"
56
56
  >
57
57
  {APP_NAME}
58
58
  </Link>
@@ -10,12 +10,16 @@ import { ROUTES } from '@/lib/constants/routes';
10
10
  import { authService } from '../services/auth.service';
11
11
  import { setUser, setInitialized } from '../store/authSlice';
12
12
 
13
- const AUTH_PAGES: string[] = [ROUTES.LOGIN, ROUTES.REGISTER];
13
+ const PROTECTED_PATHS: string[] = [ROUTES.DASHBOARD, ROUTES.PROFILE, '/admin'];
14
14
 
15
15
  interface AuthInitializerProps {
16
16
  children: React.ReactNode;
17
17
  }
18
18
 
19
+ function isProtectedPath(pathname: string): boolean {
20
+ return PROTECTED_PATHS.some((path) => pathname.startsWith(path));
21
+ }
22
+
19
23
  export const AuthInitializer = ({ children }: AuthInitializerProps): React.ReactElement => {
20
24
  const dispatch = useAppDispatch();
21
25
  const pathname = usePathname();
@@ -23,10 +27,12 @@ export const AuthInitializer = ({ children }: AuthInitializerProps): React.React
23
27
  const { isAuthenticated, isLoggingOut } = useAppSelector((state) => state.auth);
24
28
 
25
29
  useEffect(() => {
26
- if (AUTH_PAGES.includes(pathname)) {
30
+ // On public pages, skip auth hydration — just mark as initialized
31
+ if (!isProtectedPath(pathname)) {
27
32
  dispatch(setInitialized());
28
33
  return;
29
34
  }
35
+
30
36
  if (isAuthenticated || isLoggingOut) return;
31
37
 
32
38
  let cancelled = false;
@@ -97,7 +97,7 @@ export const LoginForm = (): React.ReactElement => {
97
97
  Don&apos;t have an account?{' '}
98
98
  <Link
99
99
  href={ROUTES.REGISTER}
100
- className="font-medium text-primary transition-colors duration-150 hover:text-primary/80"
100
+ className="font-medium text-primary transition-colors duration-150 active:opacity-70 md:hover:text-primary/80"
101
101
  >
102
102
  Create account
103
103
  </Link>
@@ -168,7 +168,7 @@ export const RegisterForm = (): React.ReactElement => {
168
168
  Already have an account?{' '}
169
169
  <Link
170
170
  href={ROUTES.LOGIN}
171
- className="font-medium text-primary transition-colors duration-150 hover:text-primary/80"
171
+ className="font-medium text-primary transition-colors duration-150 active:opacity-70 md:hover:text-primary/80"
172
172
  >
173
173
  Sign in
174
174
  </Link>
@@ -36,10 +36,6 @@ class AuthService {
36
36
  return response.data.data;
37
37
  }
38
38
 
39
- async verifyEmail(token: string): Promise<void> {
40
- await apiClient.post(API_ENDPOINTS.AUTH.VERIFY_EMAIL, { token });
41
- }
42
-
43
39
  async requestPasswordReset(email: string): Promise<void> {
44
40
  await apiClient.post(API_ENDPOINTS.AUTH.REQUEST_PASSWORD_RESET, { email });
45
41
  }
@@ -45,7 +45,7 @@ apiClient.interceptors.response.use(
45
45
 
46
46
  // Don't retry auth endpoints that don't use tokens —
47
47
  // a 401 here means wrong credentials, not an expired token.
48
- const noRetryEndpoints = [
48
+ const noRetryEndpoints: string[] = [
49
49
  API_ENDPOINTS.AUTH.LOGIN,
50
50
  API_ENDPOINTS.AUTH.REGISTER,
51
51
  API_ENDPOINTS.AUTH.REFRESH,
@@ -5,8 +5,6 @@ export const API_ENDPOINTS = {
5
5
  LOGOUT: '/auth/logout',
6
6
  REFRESH: '/auth/refresh',
7
7
  ME: '/auth/me',
8
- VERIFY_EMAIL: '/auth/verify-email',
9
- RESEND_VERIFICATION: '/auth/resend-verification',
10
8
  REQUEST_PASSWORD_RESET: '/auth/request-password-reset',
11
9
  RESET_PASSWORD: '/auth/reset-password',
12
10
  },
@@ -0,0 +1,90 @@
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
+ }
@@ -0,0 +1,90 @@
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
+ }
@@ -0,0 +1,90 @@
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
+ }