lovable-stack 1.0.3 → 1.1.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.
- package/README.md +113 -0
- package/bin/index.js +85 -8
- package/package.json +8 -4
- package/template/bun.lock +1 -1
- package/template/src/App.css +42 -0
- package/template/src/App.tsx +15 -4
- package/template/src/components/NavLink.tsx +29 -0
- package/template/src/index.css +119 -23
- package/template/src/pages/Index.tsx +97 -0
- package/template/src/pages/NotFound.tsx +24 -0
- package/template/tailwind.config.ts +91 -0
package/README.md
CHANGED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
|
|
2
|
+
Lovable-Stack
|
|
3
|
+
Build Lovable AI projects locally without configuration stress.
|
|
4
|
+
Lovable-Stack is a Bun powered project generator that recreates the working stack behind projects generated by Lovable AI, so you can run, edit, and fully own your code locally.
|
|
5
|
+
It does the heavy setup for you.
|
|
6
|
+
You focus on building.
|
|
7
|
+
|
|
8
|
+
Why Lovable-Stack?
|
|
9
|
+
AI builders are amazing for speed.
|
|
10
|
+
You can:
|
|
11
|
+
• Generate a full demo site in minutes
|
|
12
|
+
• Connect GitHub
|
|
13
|
+
• Ship ideas fast
|
|
14
|
+
But when you clone that project locally, you may face:
|
|
15
|
+
• Dependency issues
|
|
16
|
+
• Runtime conflicts
|
|
17
|
+
• TypeScript strict errors
|
|
18
|
+
• Missing configuration alignment
|
|
19
|
+
• Bun lock visible packages
|
|
20
|
+
Lovable-Stack fixes that.
|
|
21
|
+
It scaffolds a clean, aligned local environment so your project runs properly on your machine.
|
|
22
|
+
|
|
23
|
+
What You Get
|
|
24
|
+
Lovable-Stack creates a fully configured project using:
|
|
25
|
+
• Bun
|
|
26
|
+
• React-Vite
|
|
27
|
+
• TypeScript
|
|
28
|
+
• Tailwind CSS
|
|
29
|
+
• shadcn/ui
|
|
30
|
+
• Radix UI
|
|
31
|
+
Everything is preconfigured and version-aligned.
|
|
32
|
+
No manual wiring.
|
|
33
|
+
No rebuilding the stack from scratch.
|
|
34
|
+
|
|
35
|
+
Requirements
|
|
36
|
+
Before using Lovable-Stack, install:
|
|
37
|
+
• Bun (latest version)
|
|
38
|
+
• Basic knowledge of React & TypeScript
|
|
39
|
+
Install Bun MacOS/Linux
|
|
40
|
+
curl -fsSL https://bun.sh/install | bash
|
|
41
|
+
|
|
42
|
+
Install Bun Windows
|
|
43
|
+
powershell -c "irm bun.sh/install.ps1|iex"
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
YOU CAN AS WELL VISIT BUN WEBSITE:
|
|
47
|
+
|
|
48
|
+
https://bun.com/docs/installation
|
|
49
|
+
|
|
50
|
+
Quick Start
|
|
51
|
+
1. Create a new project
|
|
52
|
+
bunx lovable-stack my-app
|
|
53
|
+
2. Enter the project folder
|
|
54
|
+
cd my-app
|
|
55
|
+
3. Install dependencies
|
|
56
|
+
bun install
|
|
57
|
+
4. Start development server
|
|
58
|
+
bun run dev
|
|
59
|
+
Your app is now running locally.
|
|
60
|
+
|
|
61
|
+
How To Use With Lovable AI
|
|
62
|
+
1. Generate your project using Lovable AI.
|
|
63
|
+
2. Copy your generated components (e.g. header.tsx, hero.tsx).
|
|
64
|
+
3. Paste them into /src/components inside your LovableStack project.
|
|
65
|
+
4. Continue building locally.
|
|
66
|
+
Now you own your code and environment.
|
|
67
|
+
|
|
68
|
+
Project Structure
|
|
69
|
+
my-app/
|
|
70
|
+
├── src/
|
|
71
|
+
│ ├── components/
|
|
72
|
+
│ ├── pages/
|
|
73
|
+
│ └── main.tsx
|
|
74
|
+
├── public/
|
|
75
|
+
├── tsconfig.json
|
|
76
|
+
├── vite.config.ts
|
|
77
|
+
└── bun.lock
|
|
78
|
+
Everything is already wired together.
|
|
79
|
+
Common Errors & Fixes
|
|
80
|
+
TypeScript “must be imported as type” error
|
|
81
|
+
If you see an error about importing types incorrectly, use:
|
|
82
|
+
import { NavLink as RouterNavLink } from "react-router-dom";
|
|
83
|
+
import type { NavLinkProps } from "react-router-dom";
|
|
84
|
+
Instead of:
|
|
85
|
+
import { NavLinkProps } from "react-router-dom"";
|
|
86
|
+
This project uses strict TypeScript settings.
|
|
87
|
+
|
|
88
|
+
Reporting Issues
|
|
89
|
+
If you encounter an error not listed here:
|
|
90
|
+
1. Open a GitHub Issue
|
|
91
|
+
2. Include:
|
|
92
|
+
o Your OS
|
|
93
|
+
o Bun version
|
|
94
|
+
o Full error message
|
|
95
|
+
o Screenshot (if possible)
|
|
96
|
+
This helps improve Lovable-Stack for everyone.
|
|
97
|
+
|
|
98
|
+
Philosophy
|
|
99
|
+
AI helps you move fast.
|
|
100
|
+
Lovable-Stack helps you move correctly.
|
|
101
|
+
It’s built for developers who want:
|
|
102
|
+
• Speed without losing control
|
|
103
|
+
• AI assistance without dependency confusion
|
|
104
|
+
• A stable local development environment
|
|
105
|
+
This project is designed to work alongside Lovable AI projects.
|
|
106
|
+
It is not affiliated with or endorsed by Lovable.
|
|
107
|
+
|
|
108
|
+
Contributing
|
|
109
|
+
Contributions are welcome.
|
|
110
|
+
1. Fork the repository
|
|
111
|
+
2. Create a new branch
|
|
112
|
+
3. Submit a pull request
|
|
113
|
+
If you’ve solved an issue or improved the developer experience, feel free to contribute.
|
package/bin/index.js
CHANGED
|
@@ -1,13 +1,90 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
const fs = require("fs-extra");
|
|
3
|
-
const path = require("path");
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
import fs from "fs-extra";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import inquirer from "inquirer";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import ora from "ora";
|
|
8
|
+
import { fileURLToPath } from "url";
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Recreate __dirname in ESM (cross-platform safe)
|
|
12
|
+
*/
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = path.dirname(__filename);
|
|
10
15
|
|
|
11
|
-
|
|
16
|
+
async function run() {
|
|
17
|
+
const args = process.argv.slice(2);
|
|
18
|
+
let projectName = args[0];
|
|
12
19
|
|
|
13
|
-
|
|
20
|
+
// 1️⃣ Ask for project name if missing
|
|
21
|
+
if (!projectName) {
|
|
22
|
+
const answers = await inquirer.prompt([
|
|
23
|
+
{
|
|
24
|
+
type: "input",
|
|
25
|
+
name: "projectName",
|
|
26
|
+
message: "What is your project name?",
|
|
27
|
+
default: "my-new-project",
|
|
28
|
+
},
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
projectName = answers.projectName.trim();
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!projectName) {
|
|
35
|
+
console.log(chalk.red("Project name cannot be empty."));
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const templatePath = path.resolve(__dirname, "../template");
|
|
40
|
+
const targetPath = path.resolve(process.cwd(), projectName);
|
|
41
|
+
|
|
42
|
+
// Safety check: template must exist
|
|
43
|
+
if (!fs.existsSync(templatePath)) {
|
|
44
|
+
console.log(chalk.red("Template folder not found."));
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 2️⃣ Check if folder exists
|
|
49
|
+
if (fs.existsSync(targetPath)) {
|
|
50
|
+
const { overwrite } = await inquirer.prompt([
|
|
51
|
+
{
|
|
52
|
+
type: "confirm",
|
|
53
|
+
name: "overwrite",
|
|
54
|
+
message: `Folder "${projectName}" already exists. Overwrite?`,
|
|
55
|
+
default: false,
|
|
56
|
+
},
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
if (!overwrite) {
|
|
60
|
+
console.log(chalk.yellow("Operation cancelled."));
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
await fs.remove(targetPath);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 3️⃣ Spinner while copying
|
|
68
|
+
const spinner = ora("Creating project...").start();
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
await fs.copy(templatePath, targetPath);
|
|
72
|
+
spinner.succeed("Project created successfully!");
|
|
73
|
+
} catch (err) {
|
|
74
|
+
spinner.fail("Failed to create project.");
|
|
75
|
+
console.error(err);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 4️⃣ Final message
|
|
80
|
+
console.log(`
|
|
81
|
+
${chalk.green("Success!")} Your project is ready.
|
|
82
|
+
|
|
83
|
+
Next steps:
|
|
84
|
+
cd ${projectName}
|
|
85
|
+
bun install
|
|
86
|
+
bun run dev
|
|
87
|
+
`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
run();
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lovable-stack",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"type": "module",
|
|
4
5
|
"bin": {
|
|
5
|
-
"lovable-stack": "bin/index.
|
|
6
|
+
"lovable-stack": "bin/index.mjs"
|
|
6
7
|
},
|
|
7
8
|
"files": [
|
|
8
9
|
"bin/",
|
|
@@ -10,6 +11,9 @@
|
|
|
10
11
|
"README.md"
|
|
11
12
|
],
|
|
12
13
|
"dependencies": {
|
|
13
|
-
"
|
|
14
|
+
"chalk": "^5.6.2",
|
|
15
|
+
"fs-extra": "^11.3.3",
|
|
16
|
+
"inquirer": "^13.3.0",
|
|
17
|
+
"ora": "6"
|
|
14
18
|
}
|
|
15
|
-
}
|
|
19
|
+
}
|
package/template/bun.lock
CHANGED
package/template/src/App.css
CHANGED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#root {
|
|
2
|
+
max-width: 1280px;
|
|
3
|
+
margin: 0 auto;
|
|
4
|
+
padding: 2rem;
|
|
5
|
+
text-align: center;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.logo {
|
|
9
|
+
height: 6em;
|
|
10
|
+
padding: 1.5em;
|
|
11
|
+
will-change: filter;
|
|
12
|
+
transition: filter 300ms;
|
|
13
|
+
}
|
|
14
|
+
.logo:hover {
|
|
15
|
+
filter: drop-shadow(0 0 2em #646cffaa);
|
|
16
|
+
}
|
|
17
|
+
.logo.react:hover {
|
|
18
|
+
filter: drop-shadow(0 0 2em #61dafbaa);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@keyframes logo-spin {
|
|
22
|
+
from {
|
|
23
|
+
transform: rotate(0deg);
|
|
24
|
+
}
|
|
25
|
+
to {
|
|
26
|
+
transform: rotate(360deg);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
31
|
+
a:nth-of-type(2) .logo {
|
|
32
|
+
animation: logo-spin infinite 20s linear;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.card {
|
|
37
|
+
padding: 2em;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.read-the-docs {
|
|
41
|
+
color: #888;
|
|
42
|
+
}
|
package/template/src/App.tsx
CHANGED
|
@@ -3,14 +3,25 @@ import { Toaster as Sonner } from "@/components/ui/sonner";
|
|
|
3
3
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
|
4
4
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
5
5
|
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
|
6
|
-
|
|
6
|
+
import Index from "./pages/Index";
|
|
7
|
+
import NotFound from "./pages/NotFound";
|
|
7
8
|
|
|
8
9
|
const queryClient = new QueryClient();
|
|
9
10
|
|
|
10
11
|
const App = () => (
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
<QueryClientProvider client={queryClient}>
|
|
13
|
+
<TooltipProvider>
|
|
14
|
+
<Toaster />
|
|
15
|
+
<Sonner />
|
|
16
|
+
<BrowserRouter>
|
|
17
|
+
<Routes>
|
|
18
|
+
<Route path="/" element={<Index />} />
|
|
19
|
+
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
|
|
20
|
+
<Route path="*" element={<NotFound />} />
|
|
21
|
+
</Routes>
|
|
22
|
+
</BrowserRouter>
|
|
23
|
+
</TooltipProvider>
|
|
24
|
+
</QueryClientProvider>
|
|
14
25
|
);
|
|
15
26
|
|
|
16
27
|
export default App;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { NavLink as RouterNavLink } from "react-router-dom";
|
|
2
|
+
import type { NavLinkProps } from "react-router-dom";
|
|
3
|
+
import { forwardRef } from "react";
|
|
4
|
+
import { cn } from "@/lib/utils";
|
|
5
|
+
|
|
6
|
+
interface NavLinkCompatProps extends Omit<NavLinkProps, "className"> {
|
|
7
|
+
className?: string;
|
|
8
|
+
activeClassName?: string;
|
|
9
|
+
pendingClassName?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const NavLink = forwardRef<HTMLAnchorElement, NavLinkCompatProps>(
|
|
13
|
+
({ className, activeClassName, pendingClassName, to, ...props }, ref) => {
|
|
14
|
+
return (
|
|
15
|
+
<RouterNavLink
|
|
16
|
+
ref={ref}
|
|
17
|
+
to={to}
|
|
18
|
+
className={({ isActive, isPending }) =>
|
|
19
|
+
cn(className, isActive && activeClassName, isPending && pendingClassName)
|
|
20
|
+
}
|
|
21
|
+
{...props}
|
|
22
|
+
/>
|
|
23
|
+
);
|
|
24
|
+
},
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
NavLink.displayName = "NavLink";
|
|
28
|
+
|
|
29
|
+
export { NavLink };
|
package/template/src/index.css
CHANGED
|
@@ -1,43 +1,139 @@
|
|
|
1
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
|
1
2
|
@tailwind base;
|
|
2
3
|
@tailwind components;
|
|
3
4
|
@tailwind utilities;
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
|
|
7
|
+
@layer base {
|
|
8
|
+
:root {
|
|
9
|
+
--background: 240 20% 97%;
|
|
10
|
+
--foreground: 230 25% 18%;
|
|
11
|
+
|
|
12
|
+
--card: 0 0% 100%;
|
|
13
|
+
--card-foreground: 230 25% 18%;
|
|
14
|
+
|
|
15
|
+
--popover: 0 0% 100%;
|
|
16
|
+
--popover-foreground: 230 25% 18%;
|
|
17
|
+
|
|
18
|
+
--primary: 340 65% 60%;
|
|
19
|
+
--primary-foreground: 0 0% 100%;
|
|
20
|
+
|
|
21
|
+
--secondary: 240 15% 93%;
|
|
22
|
+
--secondary-foreground: 230 25% 18%;
|
|
23
|
+
|
|
24
|
+
--muted: 240 12% 92%;
|
|
25
|
+
--muted-foreground: 230 10% 46%;
|
|
26
|
+
|
|
27
|
+
--accent: 260 30% 94%;
|
|
28
|
+
--accent-foreground: 230 25% 18%;
|
|
29
|
+
|
|
30
|
+
--destructive: 0 84.2% 60.2%;
|
|
31
|
+
--destructive-foreground: 210 40% 98%;
|
|
32
|
+
|
|
33
|
+
--border: 240 12% 90%;
|
|
34
|
+
--input: 240 12% 90%;
|
|
35
|
+
--ring: 340 65% 60%;
|
|
36
|
+
|
|
37
|
+
--radius: 0.75rem;
|
|
38
|
+
|
|
39
|
+
--gradient-hero: linear-gradient(135deg, hsl(240 20% 97%) 0%, hsl(330 30% 95%) 50%, hsl(260 25% 95%) 100%);
|
|
40
|
+
--shadow-soft: 0 4px 24px -4px hsl(230 25% 18% / 0.06);
|
|
41
|
+
--shadow-card: 0 1px 12px -2px hsl(230 25% 18% / 0.05);
|
|
42
|
+
|
|
43
|
+
--sidebar-background: 0 0% 98%;
|
|
44
|
+
--sidebar-foreground: 240 5.3% 26.1%;
|
|
45
|
+
--sidebar-primary: 240 5.9% 10%;
|
|
46
|
+
--sidebar-primary-foreground: 0 0% 98%;
|
|
47
|
+
--sidebar-accent: 240 4.8% 95.9%;
|
|
48
|
+
--sidebar-accent-foreground: 240 5.9% 10%;
|
|
49
|
+
--sidebar-border: 220 13% 91%;
|
|
50
|
+
--sidebar-ring: 217.2 91.2% 59.8%;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.dark {
|
|
54
|
+
--background: 230 20% 8%;
|
|
55
|
+
--foreground: 220 20% 92%;
|
|
56
|
+
--card: 230 18% 12%;
|
|
57
|
+
--card-foreground: 220 20% 92%;
|
|
58
|
+
--popover: 230 18% 12%;
|
|
59
|
+
--popover-foreground: 220 20% 92%;
|
|
60
|
+
--primary: 340 65% 60%;
|
|
61
|
+
--primary-foreground: 0 0% 100%;
|
|
62
|
+
--secondary: 230 15% 16%;
|
|
63
|
+
--secondary-foreground: 220 20% 92%;
|
|
64
|
+
--muted: 230 12% 18%;
|
|
65
|
+
--muted-foreground: 220 10% 55%;
|
|
66
|
+
--accent: 260 20% 18%;
|
|
67
|
+
--accent-foreground: 220 20% 92%;
|
|
68
|
+
--destructive: 0 62.8% 30.6%;
|
|
69
|
+
--destructive-foreground: 210 40% 98%;
|
|
70
|
+
--border: 230 12% 20%;
|
|
71
|
+
--input: 230 12% 20%;
|
|
72
|
+
--ring: 340 65% 60%;
|
|
73
|
+
--gradient-hero: linear-gradient(135deg, hsl(230 20% 8%) 0%, hsl(330 15% 12%) 50%, hsl(260 15% 12%) 100%);
|
|
74
|
+
--shadow-soft: 0 4px 24px -4px hsl(0 0% 0% / 0.3);
|
|
75
|
+
--shadow-card: 0 1px 12px -2px hsl(0 0% 0% / 0.2);
|
|
76
|
+
--sidebar-background: 240 5.9% 10%;
|
|
77
|
+
--sidebar-foreground: 240 4.8% 95.9%;
|
|
78
|
+
--sidebar-primary: 224.3 76.3% 48%;
|
|
79
|
+
--sidebar-primary-foreground: 0 0% 100%;
|
|
80
|
+
--sidebar-accent: 240 3.7% 15.9%;
|
|
81
|
+
--sidebar-accent-foreground: 240 4.8% 95.9%;
|
|
82
|
+
--sidebar-border: 240 3.7% 15.9%;
|
|
83
|
+
--sidebar-ring: 217.2 91.2% 59.8%;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
6
87
|
@layer base {
|
|
7
88
|
* {
|
|
8
|
-
|
|
89
|
+
@apply border-border;
|
|
9
90
|
}
|
|
10
|
-
|
|
11
91
|
body {
|
|
12
|
-
|
|
92
|
+
@apply bg-background text-foreground;
|
|
93
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
13
94
|
}
|
|
14
95
|
}
|
|
15
96
|
|
|
16
|
-
@
|
|
17
|
-
|
|
18
|
-
|
|
97
|
+
@keyframes glow-pulse {
|
|
98
|
+
0%, 100% {
|
|
99
|
+
filter: drop-shadow(0 0 8px hsl(340 65% 60% / 0.6)) drop-shadow(0 0 20px hsl(340 65% 60% / 0.3));
|
|
100
|
+
transform: scale(1);
|
|
19
101
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
.animate-pulse-soft {
|
|
26
|
-
animation: pulseSoft 2s ease-in-out infinite;
|
|
102
|
+
50% {
|
|
103
|
+
filter: drop-shadow(0 0 16px hsl(340 65% 60% / 0.8)) drop-shadow(0 0 40px hsl(340 65% 60% / 0.5));
|
|
104
|
+
transform: scale(1.1);
|
|
27
105
|
}
|
|
28
106
|
}
|
|
29
107
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
108
|
+
.glow-heart {
|
|
109
|
+
display: inline-block;
|
|
110
|
+
animation: glow-pulse 2s ease-in-out infinite;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@keyframes typing {
|
|
114
|
+
from { width: 0; }
|
|
115
|
+
to { width: 100%; }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@keyframes blink-caret {
|
|
119
|
+
from, to { border-color: transparent; }
|
|
120
|
+
50% { border-color: hsl(var(--primary)); }
|
|
33
121
|
}
|
|
34
122
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
123
|
+
.typing-text {
|
|
124
|
+
display: inline-block;
|
|
125
|
+
overflow: hidden;
|
|
126
|
+
white-space: nowrap;
|
|
127
|
+
border-right: 2px solid hsl(var(--primary));
|
|
128
|
+
width: 0;
|
|
129
|
+
animation:
|
|
130
|
+
typing-loop 7s steps(26, end) infinite,
|
|
131
|
+
blink-caret 0.75s step-end infinite;
|
|
38
132
|
}
|
|
39
133
|
|
|
40
|
-
@keyframes
|
|
41
|
-
0
|
|
42
|
-
|
|
134
|
+
@keyframes typing-loop {
|
|
135
|
+
0% { width: 0; }
|
|
136
|
+
28% { width: 100%; } /* typed by ~2s */
|
|
137
|
+
86% { width: 100%; } /* hold until ~6s */
|
|
138
|
+
100% { width: 0; } /* reset */
|
|
43
139
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const steps = [
|
|
2
|
+
{ num: "1️⃣", text: "Go to Lovable AI" },
|
|
3
|
+
{ num: "2️⃣", text: "Generate your UI" },
|
|
4
|
+
{ num: "3️⃣", text: "Copy the generated source files" },
|
|
5
|
+
{ num: "4️⃣", text: "Replace the matching files in your local project" },
|
|
6
|
+
{ num: "5️⃣", text: "Run the project again" },
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
const editableFiles = [
|
|
10
|
+
"src/components/*",
|
|
11
|
+
"src/pages/*",
|
|
12
|
+
"src/App.tsx",
|
|
13
|
+
"src/App.css",
|
|
14
|
+
"src/index.css",
|
|
15
|
+
"index.html",
|
|
16
|
+
"tailwind.config.ts",
|
|
17
|
+
"package.json (name only)",
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
const Index = () => {
|
|
21
|
+
return (
|
|
22
|
+
<div className="min-h-screen" style={{ background: "var(--gradient-hero)" }}>
|
|
23
|
+
<div className="mx-auto max-w-3xl px-6 py-20 space-y-16">
|
|
24
|
+
|
|
25
|
+
{/* Hero */}
|
|
26
|
+
<section className="text-center space-y-5 pt-12 pb-4">
|
|
27
|
+
<div className="text-4xl sm:text-5xl font-bold tracking-tight text-foreground">
|
|
28
|
+
<span className="glow-heart text-5xl sm:text-6xl">💖</span>
|
|
29
|
+
<h1 className="typing-text text-4xl sm:text-5xl font-bold tracking-tight text-foreground mx-auto mt-3" style={{ maxWidth: 'max-content' }}>
|
|
30
|
+
Welcome to Lovable Stack
|
|
31
|
+
</h1>
|
|
32
|
+
</div>
|
|
33
|
+
<p className="text-lg text-muted-foreground font-medium">
|
|
34
|
+
AI-assisted building, fully editable by you.
|
|
35
|
+
</p>
|
|
36
|
+
<p className="text-sm text-muted-foreground leading-relaxed max-w-md mx-auto">
|
|
37
|
+
Lovable AI generated your initial layout.
|
|
38
|
+
<br />
|
|
39
|
+
Now it's yours to shape.
|
|
40
|
+
</p>
|
|
41
|
+
</section>
|
|
42
|
+
|
|
43
|
+
{/* How It Works */}
|
|
44
|
+
<section className="rounded-2xl bg-card p-8 sm:p-10 space-y-6" style={{ boxShadow: "var(--shadow-soft)" }}>
|
|
45
|
+
<h2 className="text-2xl font-semibold text-foreground">
|
|
46
|
+
💖 How Lovable AI Works With Your Project
|
|
47
|
+
</h2>
|
|
48
|
+
<p className="text-sm text-muted-foreground">
|
|
49
|
+
Lovable Stack ships with a starter demo template. When you generate a project using Lovable AI:
|
|
50
|
+
</p>
|
|
51
|
+
<ol className="space-y-3">
|
|
52
|
+
{steps.map((s) => (
|
|
53
|
+
<li key={s.num} className="flex items-start gap-3 text-sm text-foreground">
|
|
54
|
+
<span className="shrink-0 text-base">{s.num}</span>
|
|
55
|
+
<span>{s.text}</span>
|
|
56
|
+
</li>
|
|
57
|
+
))}
|
|
58
|
+
</ol>
|
|
59
|
+
<p className="text-sm font-medium text-primary">Done.</p>
|
|
60
|
+
</section>
|
|
61
|
+
|
|
62
|
+
{/* Editable Files */}
|
|
63
|
+
<section className="rounded-2xl bg-card p-8 sm:p-10 space-y-6" style={{ boxShadow: "var(--shadow-soft)" }}>
|
|
64
|
+
<h2 className="text-2xl font-semibold text-foreground">
|
|
65
|
+
🧩 Editable Files
|
|
66
|
+
</h2>
|
|
67
|
+
<p className="text-sm text-muted-foreground">
|
|
68
|
+
Only these files should be replaced when updating from Lovable AI:
|
|
69
|
+
</p>
|
|
70
|
+
<ul className="grid gap-2">
|
|
71
|
+
{editableFiles.map((f) => (
|
|
72
|
+
<li key={f} className="rounded-lg bg-secondary px-4 py-2.5 text-sm font-mono text-secondary-foreground">
|
|
73
|
+
{f}
|
|
74
|
+
</li>
|
|
75
|
+
))}
|
|
76
|
+
</ul>
|
|
77
|
+
<div className="rounded-xl bg-muted p-5 space-y-2 mt-4">
|
|
78
|
+
<p className="text-sm text-foreground font-medium">Architecture note</p>
|
|
79
|
+
<p className="text-sm text-muted-foreground leading-relaxed">
|
|
80
|
+
The base template handles tooling, configuration, and environment setup.
|
|
81
|
+
Lovable AI only replaces UI and layout logic.
|
|
82
|
+
This separation keeps your project stable.
|
|
83
|
+
</p>
|
|
84
|
+
</div>
|
|
85
|
+
</section>
|
|
86
|
+
|
|
87
|
+
<footer className="text-center pb-8">
|
|
88
|
+
<p className="text-xs text-muted-foreground">
|
|
89
|
+
Built with Lovable Stack
|
|
90
|
+
</p>
|
|
91
|
+
</footer>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export default Index;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useLocation } from "react-router-dom";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
|
|
4
|
+
const NotFound = () => {
|
|
5
|
+
const location = useLocation();
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
console.error("404 Error: User attempted to access non-existent route:", location.pathname);
|
|
9
|
+
}, [location.pathname]);
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<div className="flex min-h-screen items-center justify-center bg-muted">
|
|
13
|
+
<div className="text-center">
|
|
14
|
+
<h1 className="mb-4 text-4xl font-bold">404</h1>
|
|
15
|
+
<p className="mb-4 text-xl text-muted-foreground">Oops! Page not found</p>
|
|
16
|
+
<a href="/" className="text-primary underline hover:text-primary/90">
|
|
17
|
+
Return to Home
|
|
18
|
+
</a>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export default NotFound;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { Config } from "tailwindcss";
|
|
2
|
+
|
|
3
|
+
export default {
|
|
4
|
+
darkMode: ["class"],
|
|
5
|
+
content: ["./pages/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}", "./app/**/*.{ts,tsx}", "./src/**/*.{ts,tsx}"],
|
|
6
|
+
prefix: "",
|
|
7
|
+
theme: {
|
|
8
|
+
container: {
|
|
9
|
+
center: true,
|
|
10
|
+
padding: "2rem",
|
|
11
|
+
screens: {
|
|
12
|
+
"2xl": "1400px",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
extend: {
|
|
16
|
+
colors: {
|
|
17
|
+
border: "hsl(var(--border))",
|
|
18
|
+
input: "hsl(var(--input))",
|
|
19
|
+
ring: "hsl(var(--ring))",
|
|
20
|
+
background: "hsl(var(--background))",
|
|
21
|
+
foreground: "hsl(var(--foreground))",
|
|
22
|
+
primary: {
|
|
23
|
+
DEFAULT: "hsl(var(--primary))",
|
|
24
|
+
foreground: "hsl(var(--primary-foreground))",
|
|
25
|
+
},
|
|
26
|
+
secondary: {
|
|
27
|
+
DEFAULT: "hsl(var(--secondary))",
|
|
28
|
+
foreground: "hsl(var(--secondary-foreground))",
|
|
29
|
+
},
|
|
30
|
+
destructive: {
|
|
31
|
+
DEFAULT: "hsl(var(--destructive))",
|
|
32
|
+
foreground: "hsl(var(--destructive-foreground))",
|
|
33
|
+
},
|
|
34
|
+
muted: {
|
|
35
|
+
DEFAULT: "hsl(var(--muted))",
|
|
36
|
+
foreground: "hsl(var(--muted-foreground))",
|
|
37
|
+
},
|
|
38
|
+
accent: {
|
|
39
|
+
DEFAULT: "hsl(var(--accent))",
|
|
40
|
+
foreground: "hsl(var(--accent-foreground))",
|
|
41
|
+
},
|
|
42
|
+
popover: {
|
|
43
|
+
DEFAULT: "hsl(var(--popover))",
|
|
44
|
+
foreground: "hsl(var(--popover-foreground))",
|
|
45
|
+
},
|
|
46
|
+
card: {
|
|
47
|
+
DEFAULT: "hsl(var(--card))",
|
|
48
|
+
foreground: "hsl(var(--card-foreground))",
|
|
49
|
+
},
|
|
50
|
+
sidebar: {
|
|
51
|
+
DEFAULT: "hsl(var(--sidebar-background))",
|
|
52
|
+
foreground: "hsl(var(--sidebar-foreground))",
|
|
53
|
+
primary: "hsl(var(--sidebar-primary))",
|
|
54
|
+
"primary-foreground": "hsl(var(--sidebar-primary-foreground))",
|
|
55
|
+
accent: "hsl(var(--sidebar-accent))",
|
|
56
|
+
"accent-foreground": "hsl(var(--sidebar-accent-foreground))",
|
|
57
|
+
border: "hsl(var(--sidebar-border))",
|
|
58
|
+
ring: "hsl(var(--sidebar-ring))",
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
borderRadius: {
|
|
62
|
+
lg: "var(--radius)",
|
|
63
|
+
md: "calc(var(--radius) - 2px)",
|
|
64
|
+
sm: "calc(var(--radius) - 4px)",
|
|
65
|
+
},
|
|
66
|
+
keyframes: {
|
|
67
|
+
"accordion-down": {
|
|
68
|
+
from: {
|
|
69
|
+
height: "0",
|
|
70
|
+
},
|
|
71
|
+
to: {
|
|
72
|
+
height: "var(--radix-accordion-content-height)",
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
"accordion-up": {
|
|
76
|
+
from: {
|
|
77
|
+
height: "var(--radix-accordion-content-height)",
|
|
78
|
+
},
|
|
79
|
+
to: {
|
|
80
|
+
height: "0",
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
animation: {
|
|
85
|
+
"accordion-down": "accordion-down 0.2s ease-out",
|
|
86
|
+
"accordion-up": "accordion-up 0.2s ease-out",
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
plugins: [require("tailwindcss-animate")],
|
|
91
|
+
} satisfies Config;
|