create-tigra 2.7.0 → 2.7.2
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/bin/create-tigra.js +445 -437
- package/package.json +1 -1
- package/template/_claude/skills/clean-ui/SKILL.md +63 -0
- package/template/_claude/skills/theme/SKILL.md +109 -0
- package/template/client/package.json +47 -47
- package/template/client/src/components/common/Pagination.tsx +10 -2
- package/template/client/src/features/auth/components/LoginForm.tsx +15 -2
- package/template/client/src/features/auth/hooks/useAuth.ts +11 -8
- package/template/client/src/styles/themes/default.css +92 -92
- package/template/server/.env.example +8 -2
- package/template/server/package.json +1 -0
- package/template/server/src/app.ts +1 -0
package/package.json
CHANGED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: clean-ui
|
|
3
|
+
description: Remove the starter welcome page UI and replace with a blank canvas, preserving all client-side functionality (auth, hooks, services, store, middleware, utils)
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
The user wants to remove the starter/welcome UI from the scaffolded client and start with a blank page.
|
|
7
|
+
|
|
8
|
+
## What this skill does
|
|
9
|
+
|
|
10
|
+
Replaces the demo welcome page (`src/app/page.tsx`) with the absolute bare minimum — just a centered "Ready to build." text. **Nothing else is touched** — all functional infrastructure remains intact.
|
|
11
|
+
|
|
12
|
+
## What gets replaced
|
|
13
|
+
|
|
14
|
+
| File | Action | Reason |
|
|
15
|
+
|------|--------|--------|
|
|
16
|
+
| `src/app/page.tsx` | **Replace** with blank canvas | Remove ALL demo content — hero, ambient glow, GitHub links, buttons, everything |
|
|
17
|
+
|
|
18
|
+
## What is NOT touched (preserved as-is)
|
|
19
|
+
|
|
20
|
+
- `src/components/layout/` — Header, Footer, MainLayout
|
|
21
|
+
- `src/app/layout.tsx` — root layout with providers
|
|
22
|
+
- `src/app/providers.tsx` — Redux, React Query, themes, AuthInitializer
|
|
23
|
+
- `src/app/error.tsx`, `src/app/not-found.tsx`, `src/app/loading.tsx`
|
|
24
|
+
- `src/middleware.ts` — route protection
|
|
25
|
+
- `src/features/auth/**` — entire auth system
|
|
26
|
+
- `src/components/common/**` — ThemeToggle, EmptyState, LoadingSpinner, etc.
|
|
27
|
+
- `src/components/ui/**` — all shadcn/ui components
|
|
28
|
+
- `src/hooks/**`, `src/store/**`, `src/lib/**`, `src/styles/**`
|
|
29
|
+
- All config files
|
|
30
|
+
|
|
31
|
+
## Steps
|
|
32
|
+
|
|
33
|
+
1. Read `src/app/page.tsx` to confirm it exists.
|
|
34
|
+
2. Replace its contents with the clean page below. Use the **exact** template — do not add anything.
|
|
35
|
+
3. Confirm to the user what was done.
|
|
36
|
+
|
|
37
|
+
## Clean page template
|
|
38
|
+
|
|
39
|
+
Replace `src/app/page.tsx` with **exactly** this — no additions, no modifications:
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import type React from 'react';
|
|
43
|
+
|
|
44
|
+
export default function HomePage(): React.ReactElement {
|
|
45
|
+
return (
|
|
46
|
+
<main className="flex min-h-dvh items-center justify-center">
|
|
47
|
+
<p className="text-sm text-muted-foreground">Ready to build.</p>
|
|
48
|
+
</main>
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
**CRITICAL**: Do NOT add anything beyond what is in the template above. No heading, no links, no buttons, no metadata, no imports beyond React. The entire point is a blank canvas.
|
|
54
|
+
|
|
55
|
+
## Response format
|
|
56
|
+
|
|
57
|
+
After completing, respond with:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
Starter UI cleaned. `src/app/page.tsx` is now a blank canvas.
|
|
61
|
+
|
|
62
|
+
Everything else is untouched — auth, hooks, services, store, middleware, components, and design system are all intact.
|
|
63
|
+
```
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: theme
|
|
3
|
+
description: Change the color palette in the client theme file. Only swaps HEX values — never touches variable names, file structure, or conventions.
|
|
4
|
+
argument-hint: "[color description or specific HEX values]"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Theme Palette Changer
|
|
8
|
+
|
|
9
|
+
The user wants to change the color palette. Their input: **$ARGUMENTS**
|
|
10
|
+
|
|
11
|
+
## Your ONLY job
|
|
12
|
+
|
|
13
|
+
Replace HEX color values in `src/styles/themes/default.css` (or the equivalent theme file in the active project's client directory). That's it.
|
|
14
|
+
|
|
15
|
+
## What you MUST NOT do
|
|
16
|
+
|
|
17
|
+
- **DO NOT** rename, add, or remove any CSS variable names (e.g., `--primary`, `--background`, `--muted`)
|
|
18
|
+
- **DO NOT** change the file structure, move files, or create new files
|
|
19
|
+
- **DO NOT** touch `globals.css`, components, or any other file
|
|
20
|
+
- **DO NOT** change `--radius` or any non-color value
|
|
21
|
+
- **DO NOT** convert HEX to OKLCH, HSL, or any other format — stay in HEX
|
|
22
|
+
- **DO NOT** remove the `.dark` selector or the `:root` selector
|
|
23
|
+
- **DO NOT** remove or rewrite comments unless updating the palette description at the top
|
|
24
|
+
- **DO NOT** change `rgba()` values to HEX — keep `rgba()` where it already exists (e.g., dark mode `--border`, `--input`)
|
|
25
|
+
- **DO NOT** touch any code outside the theme file — no components, no Tailwind config, no globals
|
|
26
|
+
|
|
27
|
+
## Before making changes — ask these questions if not answered
|
|
28
|
+
|
|
29
|
+
You MUST gather enough information before editing. If the user's input doesn't cover these, ask BEFORE making any changes:
|
|
30
|
+
|
|
31
|
+
### Required information
|
|
32
|
+
|
|
33
|
+
1. **Primary/brand color** — What is the main accent color? (at minimum, you need this)
|
|
34
|
+
|
|
35
|
+
### Clarifying questions (ask if not addressed)
|
|
36
|
+
|
|
37
|
+
2. **Dark mode** — one of:
|
|
38
|
+
- "Will you provide dark mode colors yourself, or should I generate them from your palette?"
|
|
39
|
+
- Skip if user explicitly says "light mode only" or provides both sets
|
|
40
|
+
|
|
41
|
+
3. **Palette vibe/mood** — if the user only gave a single color or vague description:
|
|
42
|
+
- "What's the mood? (e.g., warm, cool, corporate, playful, minimal, luxury)"
|
|
43
|
+
- This helps you pick complementary background, muted, secondary, and accent colors
|
|
44
|
+
|
|
45
|
+
4. **Background preference** — if not obvious from context:
|
|
46
|
+
- "Do you want a light cream/warm background, a cool/gray background, or pure white?"
|
|
47
|
+
|
|
48
|
+
5. **Destructive/success/warning/info** — if the user provides a full custom palette but skips these:
|
|
49
|
+
- "Should I keep the current red/green/amber/blue for status colors, or adjust them to match your new palette?"
|
|
50
|
+
|
|
51
|
+
### When you have enough
|
|
52
|
+
|
|
53
|
+
- User gives a full palette with explicit HEX values for most tokens → just apply them, generate any missing ones
|
|
54
|
+
- User gives a brand color + mood → generate a cohesive palette and present it for approval before applying
|
|
55
|
+
- User gives a full set of colors for both light and dark → apply directly
|
|
56
|
+
|
|
57
|
+
## How to apply changes
|
|
58
|
+
|
|
59
|
+
1. **Read** the current `default.css` theme file first
|
|
60
|
+
2. **Locate** the correct theme file:
|
|
61
|
+
- In a scaffolded project: `client/src/styles/themes/default.css`
|
|
62
|
+
- In the template: `template/client/src/styles/themes/default.css`
|
|
63
|
+
- Check which one exists in the current working directory
|
|
64
|
+
3. **Present** your proposed palette to the user in a readable table BEFORE editing:
|
|
65
|
+
|
|
66
|
+
| Token | Current | New (Light) | New (Dark) |
|
|
67
|
+
|-------|---------|-------------|------------|
|
|
68
|
+
| --background | #f4f3ee | #... | #... |
|
|
69
|
+
| --primary | #c15f3c | #... | #... |
|
|
70
|
+
| ... | ... | ... | ... |
|
|
71
|
+
|
|
72
|
+
4. **Wait for user approval** — do not edit until they confirm
|
|
73
|
+
5. **Edit** the file using the Edit tool — only change HEX values
|
|
74
|
+
6. **Update** the comment block at the top to reflect the new palette name/vibe (e.g., "Ocean blue palette" instead of "Claude-inspired warm palette")
|
|
75
|
+
7. **Confirm** what was changed in a brief summary
|
|
76
|
+
|
|
77
|
+
## Palette generation guidelines
|
|
78
|
+
|
|
79
|
+
When generating colors from a brand color, follow these principles:
|
|
80
|
+
|
|
81
|
+
- **Background**: Very desaturated, light tint of the brand hue (light mode) / very dark shade (dark mode)
|
|
82
|
+
- **Foreground**: Near-black with a hint of the brand hue (light mode) / near-white (dark mode)
|
|
83
|
+
- **Primary**: The brand color itself (light) / slightly lighter/more vibrant version (dark)
|
|
84
|
+
- **Primary-foreground**: White or near-white for contrast against primary
|
|
85
|
+
- **Secondary/muted/accent**: Desaturated, low-contrast versions of the brand palette
|
|
86
|
+
- **Card/popover**: White or very slight tint (light) / slightly elevated dark shade (dark)
|
|
87
|
+
- **Border/input**: Very subtle, low-contrast separator colors
|
|
88
|
+
- **Ring**: Same as primary (focus ring should match brand)
|
|
89
|
+
- **Destructive**: Red family (#e7000b light / #ff6467 dark) — adjust warmth/coolness to match palette
|
|
90
|
+
- **Success**: Green family — adjust to match palette temperature
|
|
91
|
+
- **Warning**: Amber/yellow family — adjust to match palette temperature
|
|
92
|
+
- **Info**: Blue family — adjust to match palette temperature
|
|
93
|
+
- **Chart colors**: 5 distinct, harmonious colors for data visualization
|
|
94
|
+
- **Sidebar**: Slightly different shade than main background for visual separation
|
|
95
|
+
|
|
96
|
+
### Dark mode rules
|
|
97
|
+
|
|
98
|
+
- Increase brightness of the primary color (not just invert)
|
|
99
|
+
- Background should be very dark (not pure black) with a hint of the brand hue
|
|
100
|
+
- Borders use `rgba()` for subtle transparency — keep this pattern
|
|
101
|
+
- Foreground colors should be off-white, not pure #ffffff
|
|
102
|
+
- Reduce contrast slightly compared to light mode to reduce eye strain
|
|
103
|
+
|
|
104
|
+
## Edge cases
|
|
105
|
+
|
|
106
|
+
- If user says "make it blue" → ask for a specific shade or suggest 3 options (e.g., ocean #0066cc, royal #4169e1, navy #1a237e)
|
|
107
|
+
- If user provides only RGB or HSL → convert to HEX yourself, don't ask them to convert
|
|
108
|
+
- If user wants to keep some colors and change others → only change the ones they specified
|
|
109
|
+
- If the theme file doesn't exist → tell the user to scaffold the project first, don't create the file
|
|
@@ -1,47 +1,47 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "{{PROJECT_NAME}}-client",
|
|
3
|
-
"version": "0.1.0",
|
|
4
|
-
"private": true,
|
|
5
|
-
"scripts": {
|
|
6
|
-
"dev": "next dev",
|
|
7
|
-
"build": "next build",
|
|
8
|
-
"start": "next start",
|
|
9
|
-
"lint": "eslint src/",
|
|
10
|
-
"generate:env": "node -e \"import('fs').then(f=>{if(f.existsSync('.env'))console.log('.env already exists, skipping');else{f.copyFileSync('.env.example','.env');console.log('.env created from .env.example')}})\""
|
|
11
|
-
},
|
|
12
|
-
"dependencies": {
|
|
13
|
-
"@hookform/resolvers": "^5.2.2",
|
|
14
|
-
"@reduxjs/toolkit": "^2.11.2",
|
|
15
|
-
"@tanstack/react-query": "^5.90.21",
|
|
16
|
-
"axios": "^1.13.5",
|
|
17
|
-
"class-variance-authority": "^0.7.1",
|
|
18
|
-
"clsx": "^2.1.1",
|
|
19
|
-
"lucide-react": "^0.563.0",
|
|
20
|
-
"next": "16.1.6",
|
|
21
|
-
"next-themes": "^0.4.6",
|
|
22
|
-
"radix-ui": "^1.4.3",
|
|
23
|
-
"react": "19.2.3",
|
|
24
|
-
"react-dom": "19.2.3",
|
|
25
|
-
"react-hook-form": "^7.71.1",
|
|
26
|
-
"react-redux": "^9.2.0",
|
|
27
|
-
"sonner": "^2.0.7",
|
|
28
|
-
"tailwind-merge": "^3.4.0",
|
|
29
|
-
"tailwindcss-animate": "^1.0.7",
|
|
30
|
-
"zod": "^4.3.6"
|
|
31
|
-
},
|
|
32
|
-
"overrides": {
|
|
33
|
-
"minimatch": ">=10.2.1"
|
|
34
|
-
},
|
|
35
|
-
"devDependencies": {
|
|
36
|
-
"@tailwindcss/postcss": "^4",
|
|
37
|
-
"@types/node": "^20",
|
|
38
|
-
"@types/react": "^19",
|
|
39
|
-
"@types/react-dom": "^19",
|
|
40
|
-
"eslint": "^9",
|
|
41
|
-
"eslint-config-next": "16.1.6",
|
|
42
|
-
"shadcn": "^3.8.4",
|
|
43
|
-
"tailwindcss": "^4",
|
|
44
|
-
"tw-animate-css": "^1.4.0",
|
|
45
|
-
"typescript": "^5"
|
|
46
|
-
}
|
|
47
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}-client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "next start",
|
|
9
|
+
"lint": "eslint src/",
|
|
10
|
+
"generate:env": "node -e \"import('fs').then(f=>{if(f.existsSync('.env'))console.log('.env already exists, skipping');else{f.copyFileSync('.env.example','.env');console.log('.env created from .env.example')}})\""
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@hookform/resolvers": "^5.2.2",
|
|
14
|
+
"@reduxjs/toolkit": "^2.11.2",
|
|
15
|
+
"@tanstack/react-query": "^5.90.21",
|
|
16
|
+
"axios": "^1.13.5",
|
|
17
|
+
"class-variance-authority": "^0.7.1",
|
|
18
|
+
"clsx": "^2.1.1",
|
|
19
|
+
"lucide-react": "^0.563.0",
|
|
20
|
+
"next": "16.1.6",
|
|
21
|
+
"next-themes": "^0.4.6",
|
|
22
|
+
"radix-ui": "^1.4.3",
|
|
23
|
+
"react": "19.2.3",
|
|
24
|
+
"react-dom": "19.2.3",
|
|
25
|
+
"react-hook-form": "^7.71.1",
|
|
26
|
+
"react-redux": "^9.2.0",
|
|
27
|
+
"sonner": "^2.0.7",
|
|
28
|
+
"tailwind-merge": "^3.4.0",
|
|
29
|
+
"tailwindcss-animate": "^1.0.7",
|
|
30
|
+
"zod": "^4.3.6"
|
|
31
|
+
},
|
|
32
|
+
"overrides": {
|
|
33
|
+
"minimatch": ">=10.2.1"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@tailwindcss/postcss": "^4",
|
|
37
|
+
"@types/node": "^20",
|
|
38
|
+
"@types/react": "^19",
|
|
39
|
+
"@types/react-dom": "^19",
|
|
40
|
+
"eslint": "^9",
|
|
41
|
+
"eslint-config-next": "16.1.6",
|
|
42
|
+
"shadcn": "^3.8.4",
|
|
43
|
+
"tailwindcss": "^4",
|
|
44
|
+
"tw-animate-css": "^1.4.0",
|
|
45
|
+
"typescript": "^5"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import type React from 'react';
|
|
4
|
-
import { useCallback } from 'react';
|
|
4
|
+
import { Suspense, useCallback } from 'react';
|
|
5
5
|
|
|
6
6
|
import { useRouter, useSearchParams, usePathname } from 'next/navigation';
|
|
7
7
|
import { ChevronLeft, ChevronRight } from 'lucide-react';
|
|
@@ -13,7 +13,7 @@ interface PaginationProps {
|
|
|
13
13
|
totalPages: number;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
const PaginationInner = ({ page, totalPages }: PaginationProps): React.ReactElement => {
|
|
17
17
|
const router = useRouter();
|
|
18
18
|
const pathname = usePathname();
|
|
19
19
|
const searchParams = useSearchParams();
|
|
@@ -53,3 +53,11 @@ export const Pagination = ({ page, totalPages }: PaginationProps): React.ReactEl
|
|
|
53
53
|
</div>
|
|
54
54
|
);
|
|
55
55
|
};
|
|
56
|
+
|
|
57
|
+
export const Pagination = ({ page, totalPages }: PaginationProps): React.ReactElement => {
|
|
58
|
+
return (
|
|
59
|
+
<Suspense>
|
|
60
|
+
<PaginationInner page={page} totalPages={totalPages} />
|
|
61
|
+
</Suspense>
|
|
62
|
+
);
|
|
63
|
+
};
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import type React from 'react';
|
|
4
|
+
import { Suspense } from 'react';
|
|
4
5
|
|
|
5
6
|
import Link from 'next/link';
|
|
7
|
+
import { useSearchParams } from 'next/navigation';
|
|
6
8
|
import { useForm } from 'react-hook-form';
|
|
7
9
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
8
10
|
import { z } from 'zod';
|
|
@@ -22,8 +24,11 @@ const loginSchema = z.object({
|
|
|
22
24
|
|
|
23
25
|
type LoginFormData = z.infer<typeof loginSchema>;
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
const LoginFormInner = (): React.ReactElement => {
|
|
26
28
|
const { login, isLoggingIn } = useAuth();
|
|
29
|
+
const searchParams = useSearchParams();
|
|
30
|
+
const from = searchParams.get('from');
|
|
31
|
+
const redirectTo = from && from.startsWith('/') && !from.startsWith('//') ? from : undefined;
|
|
27
32
|
|
|
28
33
|
const {
|
|
29
34
|
register,
|
|
@@ -34,7 +39,7 @@ export const LoginForm = (): React.ReactElement => {
|
|
|
34
39
|
});
|
|
35
40
|
|
|
36
41
|
const onSubmit = (data: LoginFormData): void => {
|
|
37
|
-
login(data);
|
|
42
|
+
login(data, redirectTo);
|
|
38
43
|
};
|
|
39
44
|
|
|
40
45
|
return (
|
|
@@ -105,3 +110,11 @@ export const LoginForm = (): React.ReactElement => {
|
|
|
105
110
|
</div>
|
|
106
111
|
);
|
|
107
112
|
};
|
|
113
|
+
|
|
114
|
+
export const LoginForm = (): React.ReactElement => {
|
|
115
|
+
return (
|
|
116
|
+
<Suspense>
|
|
117
|
+
<LoginFormInner />
|
|
118
|
+
</Suspense>
|
|
119
|
+
);
|
|
120
|
+
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { useCallback } from 'react';
|
|
3
|
+
import { useCallback, useRef } from 'react';
|
|
4
4
|
|
|
5
5
|
import { useMutation } from '@tanstack/react-query';
|
|
6
|
-
import { useRouter
|
|
6
|
+
import { useRouter } from 'next/navigation';
|
|
7
7
|
import { toast } from 'sonner';
|
|
8
8
|
|
|
9
9
|
import { useAppDispatch, useAppSelector } from '@/store/hooks';
|
|
@@ -18,7 +18,7 @@ interface UseAuthReturn {
|
|
|
18
18
|
user: IUser | null;
|
|
19
19
|
isAuthenticated: boolean;
|
|
20
20
|
isInitializing: boolean;
|
|
21
|
-
login: (data: ILoginRequest) => void;
|
|
21
|
+
login: (data: ILoginRequest, redirectTo?: string) => void;
|
|
22
22
|
register: (data: IRegisterRequest) => void;
|
|
23
23
|
logout: () => Promise<void>;
|
|
24
24
|
isLoggingIn: boolean;
|
|
@@ -29,17 +29,15 @@ interface UseAuthReturn {
|
|
|
29
29
|
export const useAuth = (): UseAuthReturn => {
|
|
30
30
|
const dispatch = useAppDispatch();
|
|
31
31
|
const router = useRouter();
|
|
32
|
-
const searchParams = useSearchParams();
|
|
33
32
|
const { user, isAuthenticated, isInitializing, isLoggingOut } = useAppSelector((state) => state.auth);
|
|
33
|
+
const pendingRedirectRef = useRef<string>(ROUTES.DASHBOARD);
|
|
34
34
|
|
|
35
35
|
const loginMutation = useMutation({
|
|
36
36
|
mutationFn: (data: ILoginRequest) => authService.login(data),
|
|
37
37
|
onSuccess: (data) => {
|
|
38
38
|
dispatch(setUser(data.user));
|
|
39
39
|
toast.success('Signed in successfully');
|
|
40
|
-
|
|
41
|
-
const redirectTo = from && from.startsWith('/') && !from.startsWith('//') ? from : ROUTES.DASHBOARD;
|
|
42
|
-
router.push(redirectTo);
|
|
40
|
+
router.push(pendingRedirectRef.current);
|
|
43
41
|
},
|
|
44
42
|
onError: (error) => {
|
|
45
43
|
if (isErrorCode(error, ERROR_CODES.ACCOUNT_NOT_ACTIVE)) {
|
|
@@ -83,7 +81,12 @@ export const useAuth = (): UseAuthReturn => {
|
|
|
83
81
|
user,
|
|
84
82
|
isAuthenticated,
|
|
85
83
|
isInitializing,
|
|
86
|
-
login:
|
|
84
|
+
login: (data: ILoginRequest, redirectTo?: string) => {
|
|
85
|
+
if (redirectTo) {
|
|
86
|
+
pendingRedirectRef.current = redirectTo;
|
|
87
|
+
}
|
|
88
|
+
loginMutation.mutate(data);
|
|
89
|
+
},
|
|
87
90
|
register: registerMutation.mutate,
|
|
88
91
|
logout,
|
|
89
92
|
isLoggingIn: loginMutation.isPending,
|
|
@@ -1,92 +1,92 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Default Theme — Claude-inspired warm palette
|
|
3
|
-
* Accent: Terracotta orange
|
|
4
|
-
* Vibe: Earthy, warm, approachable
|
|
5
|
-
*
|
|
6
|
-
* This is the single theme file. Colors use HEX values.
|
|
7
|
-
* Light mode: :root selectors. Dark mode: .dark selectors.
|
|
8
|
-
* To customize: edit the HEX values below.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
:root {
|
|
12
|
-
--radius: 0.625rem;
|
|
13
|
-
/* Warm cream background with terracotta orange accent */
|
|
14
|
-
--background: #f4f3ee;
|
|
15
|
-
--foreground: #1a170f;
|
|
16
|
-
--card: #ffffff;
|
|
17
|
-
--card-foreground: #1a170f;
|
|
18
|
-
--popover: #ffffff;
|
|
19
|
-
--popover-foreground: #1a170f;
|
|
20
|
-
--primary: #c15f3c;
|
|
21
|
-
--primary-foreground: #ffffff;
|
|
22
|
-
--secondary: #ebeae3;
|
|
23
|
-
--secondary-foreground: #302e25;
|
|
24
|
-
--muted: #ebeae3;
|
|
25
|
-
--muted-foreground: #b1ada1;
|
|
26
|
-
--accent: #ebeae3;
|
|
27
|
-
--accent-foreground: #302e25;
|
|
28
|
-
--destructive: #e7000b;
|
|
29
|
-
--border: #deddd4;
|
|
30
|
-
--input: #deddd4;
|
|
31
|
-
--ring: #c15f3c;
|
|
32
|
-
--success: #008339;
|
|
33
|
-
--success-foreground: #ffffff;
|
|
34
|
-
--warning: #e99b2a;
|
|
35
|
-
--warning-foreground: #242119;
|
|
36
|
-
--info: #0079bf;
|
|
37
|
-
--info-foreground: #ffffff;
|
|
38
|
-
--chart-1: #c15f3c;
|
|
39
|
-
--chart-2: #009689;
|
|
40
|
-
--chart-3: #104e64;
|
|
41
|
-
--chart-4: #ebc065;
|
|
42
|
-
--chart-5: #b1ada1;
|
|
43
|
-
--sidebar: #f0efe9;
|
|
44
|
-
--sidebar-foreground: #1a170f;
|
|
45
|
-
--sidebar-primary: #302e25;
|
|
46
|
-
--sidebar-primary-foreground: #f4f3ee;
|
|
47
|
-
--sidebar-accent: #ebeae3;
|
|
48
|
-
--sidebar-accent-foreground: #302e25;
|
|
49
|
-
--sidebar-border: #deddd4;
|
|
50
|
-
--sidebar-ring: #c15f3c;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
.dark {
|
|
54
|
-
/* Dark mode: inverted with same warm undertone */
|
|
55
|
-
--background: #15130d;
|
|
56
|
-
--foreground: #e9e8e3;
|
|
57
|
-
--card: #201e18;
|
|
58
|
-
--card-foreground: #e9e8e3;
|
|
59
|
-
--popover: #201e18;
|
|
60
|
-
--popover-foreground: #e9e8e3;
|
|
61
|
-
--primary: #d6724f;
|
|
62
|
-
--primary-foreground: #15130d;
|
|
63
|
-
--secondary: #2b2922;
|
|
64
|
-
--secondary-foreground: #e9e8e3;
|
|
65
|
-
--muted: #2b2922;
|
|
66
|
-
--muted-foreground: #928f85;
|
|
67
|
-
--accent: #2b2922;
|
|
68
|
-
--accent-foreground: #e9e8e3;
|
|
69
|
-
--destructive: #ff6467;
|
|
70
|
-
--border: rgba(255, 255, 250, 0.12);
|
|
71
|
-
--input: rgba(255, 255, 250, 0.15);
|
|
72
|
-
--ring: #d6724f;
|
|
73
|
-
--success: #009c50;
|
|
74
|
-
--success-foreground: #ffffff;
|
|
75
|
-
--warning: #faab3f;
|
|
76
|
-
--warning-foreground: #242119;
|
|
77
|
-
--info: #0099e0;
|
|
78
|
-
--info-foreground: #ffffff;
|
|
79
|
-
--chart-1: #d6724f;
|
|
80
|
-
--chart-2: #00bc7d;
|
|
81
|
-
--chart-3: #e5a658;
|
|
82
|
-
--chart-4: #a066df;
|
|
83
|
-
--chart-5: #e25969;
|
|
84
|
-
--sidebar: #201e18;
|
|
85
|
-
--sidebar-foreground: #e9e8e3;
|
|
86
|
-
--sidebar-primary: #d6724f;
|
|
87
|
-
--sidebar-primary-foreground: #e9e8e3;
|
|
88
|
-
--sidebar-accent: #2b2922;
|
|
89
|
-
--sidebar-accent-foreground: #e9e8e3;
|
|
90
|
-
--sidebar-border: rgba(255, 255, 250, 0.12);
|
|
91
|
-
--sidebar-ring: #d6724f;
|
|
92
|
-
}
|
|
1
|
+
/*
|
|
2
|
+
* Default Theme — Claude-inspired warm palette
|
|
3
|
+
* Accent: Terracotta orange
|
|
4
|
+
* Vibe: Earthy, warm, approachable
|
|
5
|
+
*
|
|
6
|
+
* This is the single theme file. Colors use HEX values.
|
|
7
|
+
* Light mode: :root selectors. Dark mode: .dark selectors.
|
|
8
|
+
* To customize: edit the HEX values below.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
:root {
|
|
12
|
+
--radius: 0.625rem;
|
|
13
|
+
/* Warm cream background with terracotta orange accent */
|
|
14
|
+
--background: #f4f3ee;
|
|
15
|
+
--foreground: #1a170f;
|
|
16
|
+
--card: #ffffff;
|
|
17
|
+
--card-foreground: #1a170f;
|
|
18
|
+
--popover: #ffffff;
|
|
19
|
+
--popover-foreground: #1a170f;
|
|
20
|
+
--primary: #c15f3c;
|
|
21
|
+
--primary-foreground: #ffffff;
|
|
22
|
+
--secondary: #ebeae3;
|
|
23
|
+
--secondary-foreground: #302e25;
|
|
24
|
+
--muted: #ebeae3;
|
|
25
|
+
--muted-foreground: #b1ada1;
|
|
26
|
+
--accent: #ebeae3;
|
|
27
|
+
--accent-foreground: #302e25;
|
|
28
|
+
--destructive: #e7000b;
|
|
29
|
+
--border: #deddd4;
|
|
30
|
+
--input: #deddd4;
|
|
31
|
+
--ring: #c15f3c;
|
|
32
|
+
--success: #008339;
|
|
33
|
+
--success-foreground: #ffffff;
|
|
34
|
+
--warning: #e99b2a;
|
|
35
|
+
--warning-foreground: #242119;
|
|
36
|
+
--info: #0079bf;
|
|
37
|
+
--info-foreground: #ffffff;
|
|
38
|
+
--chart-1: #c15f3c;
|
|
39
|
+
--chart-2: #009689;
|
|
40
|
+
--chart-3: #104e64;
|
|
41
|
+
--chart-4: #ebc065;
|
|
42
|
+
--chart-5: #b1ada1;
|
|
43
|
+
--sidebar: #f0efe9;
|
|
44
|
+
--sidebar-foreground: #1a170f;
|
|
45
|
+
--sidebar-primary: #302e25;
|
|
46
|
+
--sidebar-primary-foreground: #f4f3ee;
|
|
47
|
+
--sidebar-accent: #ebeae3;
|
|
48
|
+
--sidebar-accent-foreground: #302e25;
|
|
49
|
+
--sidebar-border: #deddd4;
|
|
50
|
+
--sidebar-ring: #c15f3c;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.dark {
|
|
54
|
+
/* Dark mode: inverted with same warm undertone */
|
|
55
|
+
--background: #15130d;
|
|
56
|
+
--foreground: #e9e8e3;
|
|
57
|
+
--card: #201e18;
|
|
58
|
+
--card-foreground: #e9e8e3;
|
|
59
|
+
--popover: #201e18;
|
|
60
|
+
--popover-foreground: #e9e8e3;
|
|
61
|
+
--primary: #d6724f;
|
|
62
|
+
--primary-foreground: #15130d;
|
|
63
|
+
--secondary: #2b2922;
|
|
64
|
+
--secondary-foreground: #e9e8e3;
|
|
65
|
+
--muted: #2b2922;
|
|
66
|
+
--muted-foreground: #928f85;
|
|
67
|
+
--accent: #2b2922;
|
|
68
|
+
--accent-foreground: #e9e8e3;
|
|
69
|
+
--destructive: #ff6467;
|
|
70
|
+
--border: rgba(255, 255, 250, 0.12);
|
|
71
|
+
--input: rgba(255, 255, 250, 0.15);
|
|
72
|
+
--ring: #d6724f;
|
|
73
|
+
--success: #009c50;
|
|
74
|
+
--success-foreground: #ffffff;
|
|
75
|
+
--warning: #faab3f;
|
|
76
|
+
--warning-foreground: #242119;
|
|
77
|
+
--info: #0099e0;
|
|
78
|
+
--info-foreground: #ffffff;
|
|
79
|
+
--chart-1: #d6724f;
|
|
80
|
+
--chart-2: #00bc7d;
|
|
81
|
+
--chart-3: #e5a658;
|
|
82
|
+
--chart-4: #a066df;
|
|
83
|
+
--chart-5: #e25969;
|
|
84
|
+
--sidebar: #201e18;
|
|
85
|
+
--sidebar-foreground: #e9e8e3;
|
|
86
|
+
--sidebar-primary: #d6724f;
|
|
87
|
+
--sidebar-primary-foreground: #e9e8e3;
|
|
88
|
+
--sidebar-accent: #2b2922;
|
|
89
|
+
--sidebar-accent-foreground: #e9e8e3;
|
|
90
|
+
--sidebar-border: rgba(255, 255, 250, 0.12);
|
|
91
|
+
--sidebar-ring: #d6724f;
|
|
92
|
+
}
|
|
@@ -93,10 +93,16 @@ MAX_FILE_SIZE_MB=10
|
|
|
93
93
|
# Without this, all uploaded files are lost on every redeployment.
|
|
94
94
|
|
|
95
95
|
# ===================================================================
|
|
96
|
-
# DOCKER PORTS
|
|
96
|
+
# DOCKER PORTS — LOCAL DEVELOPMENT ONLY
|
|
97
97
|
# ===================================================================
|
|
98
|
+
#
|
|
99
|
+
# These ports are used by docker-compose to expose MySQL, Redis, and
|
|
100
|
+
# their admin UIs on your local machine. They are NOT needed in
|
|
101
|
+
# production — production connects via DATABASE_URL and REDIS_URL
|
|
102
|
+
# (typically over a private network), not through exposed ports.
|
|
103
|
+
#
|
|
104
|
+
# Change these if they conflict with other services on your machine.
|
|
98
105
|
|
|
99
|
-
# Change these if they conflict with other services on your machine
|
|
100
106
|
MYSQL_PORT={{MYSQL_PORT}}
|
|
101
107
|
PHPMYADMIN_PORT={{PHPMYADMIN_PORT}}
|
|
102
108
|
REDIS_PORT={{REDIS_PORT}}
|