revine 0.3.4 → 0.4.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.
@@ -2,7 +2,7 @@ import path from "path";
2
2
  import { fileURLToPath } from "url";
3
3
  import { updatePackageJson } from "../config/package.js";
4
4
  import { updateReadme } from "../config/readme.js";
5
- import { installDependencies } from "../installers/dependencies.js";
5
+ import { installDependencies } from "../setup/dependencies.js";
6
6
  import { askForTailwindSetup } from "../prompts/tailwind.js";
7
7
  import { setupTailwind } from "../setup/tailwind.js";
8
8
  import { copyTemplate } from "../utils/file.js";
@@ -2,8 +2,8 @@ import fs from "fs-extra";
2
2
  export async function updateViteConfig(filePath) {
3
3
  let viteConfigContent = await fs.readFile(filePath, "utf-8");
4
4
  // Insert Tailwind import after the React plugin import.
5
- viteConfigContent = viteConfigContent.replace("import react from '@vitejs/plugin-react';", "import react from '@vitejs/plugin-react';\nimport tailwindcss from '@tailwindcss/vite';");
5
+ viteConfigContent = viteConfigContent.replace('import react from "@vitejs/plugin-react";', "import react from '@vitejs/plugin-react';\nimport tailwindcss from '@tailwindcss/vite';");
6
6
  // Insert Tailwind plugin into the plugins array.
7
- viteConfigContent = viteConfigContent.replace("plugins: [react()]", "plugins: [\n react(),\n tailwindcss()\n ]");
7
+ viteConfigContent = viteConfigContent.replace("plugins: [react(), revineLoggerPlugin()]", "plugins: [\n react(),\n revineLoggerPlugin(),\n tailwindcss()\n ]");
8
8
  await fs.writeFile(filePath, viteConfigContent);
9
9
  }
@@ -0,0 +1,15 @@
1
+ import { spawnSync } from "child_process";
2
+ import { logError, logInfo } from "../utils/logger.js";
3
+ export async function installDependencies(projectDir) {
4
+ const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
5
+ const installResult = spawnSync(npmCmd, ["install"], {
6
+ stdio: "inherit",
7
+ cwd: projectDir,
8
+ shell: true,
9
+ });
10
+ if (installResult.error || installResult.status !== 0) {
11
+ logError("Error installing dependencies:", installResult.error);
12
+ logInfo("Try running manually: npm install");
13
+ process.exit(1);
14
+ }
15
+ }
@@ -5,16 +5,126 @@ import { logInfo } from "../utils/logger.js";
5
5
  export async function setupTailwind(projectDir) {
6
6
  logInfo("\nSetting up Tailwind CSS...");
7
7
  // Point to the hidden Vite config
8
- const viteConfigPath = path.join(projectDir, ".revine", "bundler", "vite.config.ts");
9
- // Now use existing updateViteConfig logic on this new path
8
+ const viteConfigPath = path.join(projectDir, ".revine", "bundler", "defaults", "vite.ts");
9
+ // Use existing updateViteConfig logic on this new path
10
10
  await updateViteConfig(viteConfigPath);
11
- // Creating the CSS directory and file that imports Tailwind
12
- const cssDir = path.join(projectDir, "src", "styles");
13
- await fs.ensureDir(cssDir);
14
- await fs.writeFile(path.join(cssDir, "global.css"), "@import 'tailwindcss';\n");
15
- // Prepend the CSS import to src/main.tsx
16
- const rootTsxPath = path.join(projectDir, "src", "root.tsx");
17
- const mainContent = `import './styles/global.css';\n` +
18
- (await fs.readFile(rootTsxPath, "utf-8"));
19
- await fs.writeFile(rootTsxPath, mainContent);
11
+ // Write the Tailwind CSS import into the existing global.css file
12
+ const cssFile = path.join(projectDir, "src", "styles", "global.css");
13
+ const cssFileContent = `@import 'tailwindcss';\n`;
14
+ await fs.writeFile(cssFile, cssFileContent);
15
+ // Update the starter file for Tailwind classes
16
+ const starterFile = path.join(projectDir, "src", "pages", "index.tsx");
17
+ const starterFileContent = `
18
+ export default function HomePage() {
19
+ return (
20
+ <main className="flex min-h-screen flex-col items-center justify-center p-6 bg-gradient-to-b from-white via-white to-indigo-200">
21
+ {/* Hero Section */}
22
+ <div className="max-w-screen-lg w-full text-center space-y-6">
23
+ <h1 className="text-4xl sm:text-6xl font-extrabold text-gray-900 tracking-tight leading-tight">
24
+ Welcome to{" "}
25
+ <span className="text-transparent bg-clip-text bg-gradient-to-r from-indigo-500 via-indigo-600 to-black">
26
+ Revine
27
+ </span>
28
+ </h1>
29
+ <p className="text-xl sm:text-2xl text-gray-600 font-light">
30
+ The modern, powerful, and streamlined React framework.
31
+ </p>
32
+ </div>
33
+
34
+ {/* CTA Buttons */}
35
+ <div className="mt-8 flex space-x-4">
36
+ <a
37
+ href="#get-started"
38
+ className="rounded-md bg-indigo-600 px-6 py-3 text-white font-semibold shadow-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition-colors"
39
+ >
40
+ Get Started
41
+ </a>
42
+ <a
43
+ href="#docs"
44
+ className="rounded-md bg-white px-6 py-3 text-indigo-600 font-semibold border border-indigo-200 shadow hover:bg-gray-100 transition-colors focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
45
+ >
46
+ Read Docs
47
+ </a>
48
+ </div>
49
+
50
+ {/* Features Section */}
51
+ <div className="mt-12 grid gap-6 sm:grid-cols-2 lg:grid-cols-3 max-w-screen-lg w-full">
52
+ <a
53
+ href="#fast"
54
+ className="block rounded-xl border border-gray-200 bg-white p-6 shadow transition hover:shadow-xl hover:-translate-y-1"
55
+ >
56
+ <h3 className="text-lg font-semibold text-gray-900 mb-2">
57
+ Lightning Fast
58
+ </h3>
59
+ <p className="text-gray-600">
60
+ Built on Vite for ultra-fast development and instant HMR.
61
+ </p>
62
+ </a>
63
+
64
+ <a
65
+ href="#routing"
66
+ className="block rounded-xl border border-gray-200 bg-white p-6 shadow transition hover:shadow-xl hover:-translate-y-1"
67
+ >
68
+ <h3 className="text-lg font-semibold text-gray-900 mb-2">
69
+ Simple File-based Routing
70
+ </h3>
71
+ <p className="text-gray-600">
72
+ Create pages in <code>src/pages</code> and Revine will handle the
73
+ rest.
74
+ </p>
75
+ </a>
76
+
77
+ <a
78
+ href="#tailwind"
79
+ className="block rounded-xl border border-gray-200 bg-white p-6 shadow transition hover:shadow-xl hover:-translate-y-1"
80
+ >
81
+ <h3 className="text-lg font-semibold text-gray-900 mb-2">
82
+ Tailwind Integration
83
+ </h3>
84
+ <p className="text-gray-600">
85
+ Pre-configured for Tailwind CSS, so you can style quickly and
86
+ easily.
87
+ </p>
88
+ </a>
89
+
90
+ <a
91
+ href="#dev-experience"
92
+ className="block rounded-xl border border-gray-200 bg-white p-6 shadow transition hover:shadow-xl hover:-translate-y-1"
93
+ >
94
+ <h3 className="text-lg font-semibold text-gray-900 mb-2">Great DX</h3>
95
+ <p className="text-gray-600">
96
+ Minimal config, fast builds, custom logging, and more.
97
+ </p>
98
+ </a>
99
+
100
+ <a
101
+ href="#abstract"
102
+ className="block rounded-xl border border-gray-200 bg-white p-6 shadow transition hover:shadow-xl hover:-translate-y-1"
103
+ >
104
+ <h3 className="text-lg font-semibold text-gray-900 mb-2">
105
+ Abstracted Internals
106
+ </h3>
107
+ <p className="text-gray-600">
108
+ A .revine folder houses the complex Vite config. Keep your root
109
+ clean.
110
+ </p>
111
+ </a>
112
+
113
+ <a
114
+ href="#customize"
115
+ className="block rounded-xl border border-gray-200 bg-white p-6 shadow transition hover:shadow-xl hover:-translate-y-1"
116
+ >
117
+ <h3 className="text-lg font-semibold text-gray-900 mb-2">
118
+ Fully Customizable
119
+ </h3>
120
+ <p className="text-gray-600">
121
+ Easily extend or override settings in <code>revine.config.ts</code>.
122
+ </p>
123
+ </a>
124
+ </div>
125
+ </main>
126
+ );
127
+ }
128
+ `;
129
+ await fs.writeFile(starterFile, starterFileContent);
20
130
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "revine",
3
- "version": "0.3.4",
3
+ "version": "0.4.0",
4
4
  "description": "A react framework, but better.",
5
5
  "license": "ISC",
6
6
  "author": "Rachit Bharadwaj",
@@ -2,7 +2,7 @@ import path from "path";
2
2
  import { fileURLToPath } from "url";
3
3
  import { updatePackageJson } from "../config/package.js";
4
4
  import { updateReadme } from "../config/readme.js";
5
- import { installDependencies } from "../installers/dependencies.js";
5
+ import { installDependencies } from "../setup/dependencies.js";
6
6
  import { askForTailwindSetup } from "../prompts/tailwind.js";
7
7
  import { setupTailwind } from "../setup/tailwind.js";
8
8
  import { copyTemplate } from "../utils/file.js";
@@ -5,14 +5,14 @@ export async function updateViteConfig(filePath: string) {
5
5
 
6
6
  // Insert Tailwind import after the React plugin import.
7
7
  viteConfigContent = viteConfigContent.replace(
8
- "import react from '@vitejs/plugin-react';",
8
+ 'import react from "@vitejs/plugin-react";',
9
9
  "import react from '@vitejs/plugin-react';\nimport tailwindcss from '@tailwindcss/vite';"
10
10
  );
11
11
 
12
12
  // Insert Tailwind plugin into the plugins array.
13
13
  viteConfigContent = viteConfigContent.replace(
14
- "plugins: [react()]",
15
- "plugins: [\n react(),\n tailwindcss()\n ]"
14
+ "plugins: [react(), revineLoggerPlugin()]",
15
+ "plugins: [\n react(),\n revineLoggerPlugin(),\n tailwindcss()\n ]"
16
16
  );
17
17
 
18
18
  await fs.writeFile(filePath, viteConfigContent);
@@ -11,24 +11,131 @@ export async function setupTailwind(projectDir: string) {
11
11
  projectDir,
12
12
  ".revine",
13
13
  "bundler",
14
- "vite.config.ts"
14
+ "defaults",
15
+ "vite.ts"
15
16
  );
16
17
 
17
- // Now use existing updateViteConfig logic on this new path
18
+ // Use existing updateViteConfig logic on this new path
18
19
  await updateViteConfig(viteConfigPath);
19
20
 
20
- // Creating the CSS directory and file that imports Tailwind
21
- const cssDir = path.join(projectDir, "src", "styles");
22
- await fs.ensureDir(cssDir);
23
- await fs.writeFile(
24
- path.join(cssDir, "global.css"),
25
- "@import 'tailwindcss';\n"
26
- );
21
+ // Write the Tailwind CSS import into the existing global.css file
22
+ const cssFile = path.join(projectDir, "src", "styles", "global.css");
23
+ const cssFileContent = `@import 'tailwindcss';\n`;
24
+ await fs.writeFile(cssFile, cssFileContent);
25
+
26
+ // Update the starter file for Tailwind classes
27
+ const starterFile = path.join(projectDir, "src", "pages", "index.tsx");
28
+ const starterFileContent = `
29
+ export default function HomePage() {
30
+ return (
31
+ <main className="flex min-h-screen flex-col items-center justify-center p-6 bg-gradient-to-b from-white via-white to-indigo-200">
32
+ {/* Hero Section */}
33
+ <div className="max-w-screen-lg w-full text-center space-y-6">
34
+ <h1 className="text-4xl sm:text-6xl font-extrabold text-gray-900 tracking-tight leading-tight">
35
+ Welcome to{" "}
36
+ <span className="text-transparent bg-clip-text bg-gradient-to-r from-indigo-500 via-indigo-600 to-black">
37
+ Revine
38
+ </span>
39
+ </h1>
40
+ <p className="text-xl sm:text-2xl text-gray-600 font-light">
41
+ The modern, powerful, and streamlined React framework.
42
+ </p>
43
+ </div>
44
+
45
+ {/* CTA Buttons */}
46
+ <div className="mt-8 flex space-x-4">
47
+ <a
48
+ href="#get-started"
49
+ className="rounded-md bg-indigo-600 px-6 py-3 text-white font-semibold shadow-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 transition-colors"
50
+ >
51
+ Get Started
52
+ </a>
53
+ <a
54
+ href="#docs"
55
+ className="rounded-md bg-white px-6 py-3 text-indigo-600 font-semibold border border-indigo-200 shadow hover:bg-gray-100 transition-colors focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
56
+ >
57
+ Read Docs
58
+ </a>
59
+ </div>
60
+
61
+ {/* Features Section */}
62
+ <div className="mt-12 grid gap-6 sm:grid-cols-2 lg:grid-cols-3 max-w-screen-lg w-full">
63
+ <a
64
+ href="#fast"
65
+ className="block rounded-xl border border-gray-200 bg-white p-6 shadow transition hover:shadow-xl hover:-translate-y-1"
66
+ >
67
+ <h3 className="text-lg font-semibold text-gray-900 mb-2">
68
+ Lightning Fast
69
+ </h3>
70
+ <p className="text-gray-600">
71
+ Built on Vite for ultra-fast development and instant HMR.
72
+ </p>
73
+ </a>
74
+
75
+ <a
76
+ href="#routing"
77
+ className="block rounded-xl border border-gray-200 bg-white p-6 shadow transition hover:shadow-xl hover:-translate-y-1"
78
+ >
79
+ <h3 className="text-lg font-semibold text-gray-900 mb-2">
80
+ Simple File-based Routing
81
+ </h3>
82
+ <p className="text-gray-600">
83
+ Create pages in <code>src/pages</code> and Revine will handle the
84
+ rest.
85
+ </p>
86
+ </a>
27
87
 
28
- // Prepend the CSS import to src/main.tsx
29
- const rootTsxPath = path.join(projectDir, "src", "root.tsx");
30
- const mainContent =
31
- `import './styles/global.css';\n` +
32
- (await fs.readFile(rootTsxPath, "utf-8"));
33
- await fs.writeFile(rootTsxPath, mainContent);
88
+ <a
89
+ href="#tailwind"
90
+ className="block rounded-xl border border-gray-200 bg-white p-6 shadow transition hover:shadow-xl hover:-translate-y-1"
91
+ >
92
+ <h3 className="text-lg font-semibold text-gray-900 mb-2">
93
+ Tailwind Integration
94
+ </h3>
95
+ <p className="text-gray-600">
96
+ Pre-configured for Tailwind CSS, so you can style quickly and
97
+ easily.
98
+ </p>
99
+ </a>
100
+
101
+ <a
102
+ href="#dev-experience"
103
+ className="block rounded-xl border border-gray-200 bg-white p-6 shadow transition hover:shadow-xl hover:-translate-y-1"
104
+ >
105
+ <h3 className="text-lg font-semibold text-gray-900 mb-2">Great DX</h3>
106
+ <p className="text-gray-600">
107
+ Minimal config, fast builds, custom logging, and more.
108
+ </p>
109
+ </a>
110
+
111
+ <a
112
+ href="#abstract"
113
+ className="block rounded-xl border border-gray-200 bg-white p-6 shadow transition hover:shadow-xl hover:-translate-y-1"
114
+ >
115
+ <h3 className="text-lg font-semibold text-gray-900 mb-2">
116
+ Abstracted Internals
117
+ </h3>
118
+ <p className="text-gray-600">
119
+ A .revine folder houses the complex Vite config. Keep your root
120
+ clean.
121
+ </p>
122
+ </a>
123
+
124
+ <a
125
+ href="#customize"
126
+ className="block rounded-xl border border-gray-200 bg-white p-6 shadow transition hover:shadow-xl hover:-translate-y-1"
127
+ >
128
+ <h3 className="text-lg font-semibold text-gray-900 mb-2">
129
+ Fully Customizable
130
+ </h3>
131
+ <p className="text-gray-600">
132
+ Easily extend or override settings in <code>revine.config.ts</code>.
133
+ </p>
134
+ </a>
135
+ </div>
136
+ </main>
137
+ );
138
+ }
139
+ `;
140
+ await fs.writeFile(starterFile, starterFileContent);
34
141
  }
@@ -1,9 +1,8 @@
1
1
  import react from "@vitejs/plugin-react";
2
2
  import { revineLoggerPlugin } from "../viteLoggerPlugin";
3
- import tailwindcss from "@tailwindcss/vite";
4
3
 
5
4
  export const defaultViteConfig = {
6
- plugins: [react(), revineLoggerPlugin(), tailwindcss()],
5
+ plugins: [react(), revineLoggerPlugin()],
7
6
  logLevel: "silent",
8
7
 
9
8
  server: {
@@ -1,18 +1,18 @@
1
- // .revine/bundler/generateConfig.ts
2
- import { merge } from 'lodash-es';
3
- import { defaultViteConfig } from './defaults/vite.js';
4
- import { loadUserConfig } from './utils/loadUserConfig.js';
5
- import tailwindcss from '@tailwindcss/vite'; // For direct plugin usage
1
+ import { merge } from "lodash-es";
2
+ import { defaultViteConfig } from "./defaults/vite.js";
3
+ import { loadUserConfig } from "./utils/loadUserConfig.js";
4
+
5
+ interface UserConfig {
6
+ vite?: Record<string, unknown>;
7
+ }
6
8
 
7
9
  export async function generateRevineViteConfig() {
8
10
  // Load the user's revine.config.ts
9
- const userConfig = await loadUserConfig();
11
+ const userConfig = (await loadUserConfig()) as UserConfig;
12
+
10
13
 
11
14
  // Merge user "vite" overrides with your default config
12
15
  const finalConfig = merge({}, defaultViteConfig, userConfig.vite || {});
13
16
 
14
- // Insert the Tailwind plugin, if desired, automatically or conditionally:
15
- // finalConfig.plugins.push(tailwindcss());
16
-
17
17
  return finalConfig;
18
18
  }
@@ -1,7 +1,7 @@
1
1
  export async function loadUserConfig() {
2
2
  try {
3
3
  // relative path to the user's revine.config.ts
4
- const configModule = await import("../../../revine.config.ts");
4
+ const configModule = await import("../../../revine.config");
5
5
  return configModule.default || {};
6
6
  } catch (error) {
7
7
  console.error(
@@ -1,5 +1,5 @@
1
1
  import { defineConfig } from "vite";
2
- import { generateRevineViteConfig } from "./generateConfig.js";
2
+ import { generateRevineViteConfig } from "./generateConfig";
3
3
 
4
4
  // Vite supports async config. We can do:
5
5
  export default defineConfig(async () => {
@@ -19,6 +19,7 @@
19
19
  "vite": "^5.0.12",
20
20
  "@vitejs/plugin-react": "^4.2.1",
21
21
  "lodash-es": "^4.17.21",
22
+ "@types/lodash-es": "^4.17.9",
22
23
  "@tailwindcss/vite": "^4.0.0"
23
24
  }
24
25
  }
@@ -1,8 +1,62 @@
1
1
  export default function HomePage() {
2
2
  return (
3
- <div>
4
- <h1>Welcome to Revine!</h1>
5
- <p>Start editing this page in src/pages/index.tsx</p>
6
- </div>
3
+ <main>
4
+ {/* Hero Section */}
5
+ <div>
6
+ <h1>
7
+ Welcome to <span>Revine</span>
8
+ </h1>
9
+ <p>The modern, powerful, and streamlined React framework.</p>
10
+ </div>
11
+
12
+ {/* CTA Buttons */}
13
+ <div className="cta">
14
+ <a href="#get-started" className="primary-btn">
15
+ Get Started
16
+ </a>
17
+ <a href="#docs" className="secondary-btn">
18
+ Read Docs
19
+ </a>
20
+ </div>
21
+
22
+ {/* Features Section */}
23
+ <div className="features">
24
+ <a href="#fast">
25
+ <h3>Lightning Fast</h3>
26
+ <p>Built on Vite for ultra-fast development and instant HMR.</p>
27
+ </a>
28
+ <a href="#routing">
29
+ <h3>Simple File-based Routing</h3>
30
+ <p>
31
+ Create pages in <code>src/pages</code> and Revine will handle the
32
+ rest.
33
+ </p>
34
+ </a>
35
+ <a href="#tailwind">
36
+ <h3>Tailwind Integration</h3>
37
+ <p>
38
+ Pre-configured for Tailwind CSS, so you can style quickly and
39
+ easily.
40
+ </p>
41
+ </a>
42
+ <a href="#dev-experience">
43
+ <h3>Great DX</h3>
44
+ <p>Minimal config, fast builds, custom logging, and more.</p>
45
+ </a>
46
+ <a href="#abstract">
47
+ <h3>Abstracted Internals</h3>
48
+ <p>
49
+ A .revine folder houses the complex Vite config. Keep your root
50
+ clean.
51
+ </p>
52
+ </a>
53
+ <a href="#customize">
54
+ <h3>Fully Customizable</h3>
55
+ <p>
56
+ Easily extend or override settings in <code>revine.config.ts</code>.
57
+ </p>
58
+ </a>
59
+ </div>
60
+ </main>
7
61
  );
8
62
  }
@@ -1,6 +1,7 @@
1
1
  import React from "react";
2
2
  import { createRoot } from "react-dom/client";
3
3
  import App from "./App";
4
+ import './styles/global.css';
4
5
 
5
6
  const container = document.getElementById("root")!;
6
7
  const root = createRoot(container);
@@ -0,0 +1,150 @@
1
+ :root {
2
+ --gradient-from: #ffffff;
3
+ --gradient-via: #ffffff;
4
+ --gradient-to: #d1d5db;
5
+
6
+ --text-primary: #111827;
7
+ --text-secondary: #4b5563;
8
+ --background-primary: #ffffff;
9
+ --border-color: #d1d5db;
10
+
11
+ --primary-color: #4f46e5;
12
+ --primary-hover: #4338ca;
13
+ --button-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
14
+ --button-hover-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
15
+ }
16
+
17
+ body {
18
+ margin: 0;
19
+ font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
20
+ "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif;
21
+ line-height: 1.5;
22
+ }
23
+
24
+ main {
25
+ display: flex;
26
+ min-height: 100vh;
27
+ flex-direction: column;
28
+ align-items: center;
29
+ justify-content: center;
30
+ padding: 1.5rem;
31
+ background: linear-gradient(
32
+ to bottom,
33
+ var(--gradient-from),
34
+ var(--gradient-via),
35
+ var(--gradient-to)
36
+ );
37
+ }
38
+
39
+ h1 {
40
+ font-size: 3rem;
41
+ line-height: 1.25;
42
+ font-weight: 800;
43
+ color: var(--text-primary);
44
+ text-align: center;
45
+ }
46
+
47
+ h1 span {
48
+ background: linear-gradient(
49
+ to right,
50
+ var(--primary-color),
51
+ var(--primary-hover),
52
+ #000
53
+ );
54
+ -webkit-background-clip: text;
55
+ color: transparent;
56
+ }
57
+
58
+ p {
59
+ font-size: 1.25rem;
60
+ color: var(--text-secondary);
61
+ text-align: center;
62
+ }
63
+
64
+ .cta {
65
+ display: flex;
66
+ gap: 1rem;
67
+ margin-top: 2rem;
68
+ }
69
+
70
+ .cta a {
71
+ padding: 0.75rem 1.5rem;
72
+ border-radius: 0.375rem;
73
+ font-weight: 600;
74
+ text-align: center;
75
+ text-decoration: none;
76
+ display: inline-block;
77
+ }
78
+
79
+ .cta .primary-btn {
80
+ background-color: var(--primary-color);
81
+ color: white;
82
+ box-shadow: var(--button-shadow);
83
+ transition: background-color 0.2s, box-shadow 0.2s;
84
+ }
85
+
86
+ .cta .primary-btn:hover {
87
+ background-color: var(--primary-hover);
88
+ box-shadow: var(--button-hover-shadow);
89
+ }
90
+
91
+ .cta .secondary-btn {
92
+ background-color: var(--background-primary);
93
+ color: var(--primary-color);
94
+ border: 1px solid var(--border-color);
95
+ box-shadow: var(--button-shadow);
96
+ transition: background-color 0.2s, box-shadow 0.2s;
97
+ }
98
+
99
+ .cta .secondary-btn:hover {
100
+ background-color: #f9fafb;
101
+ box-shadow: var(--button-hover-shadow);
102
+ }
103
+
104
+ .features {
105
+ display: grid;
106
+ grid-template-columns: repeat(1, 1fr);
107
+ gap: 1.5rem;
108
+ margin-top: 3rem;
109
+ max-width: 64rem;
110
+ width: 100%;
111
+ }
112
+
113
+ @media (min-width: 640px) {
114
+ .features {
115
+ grid-template-columns: repeat(2, 1fr);
116
+ }
117
+ }
118
+
119
+ @media (min-width: 1024px) {
120
+ .features {
121
+ grid-template-columns: repeat(3, 1fr);
122
+ }
123
+ }
124
+
125
+ .features a {
126
+ display: block;
127
+ padding: 1.5rem;
128
+ border: 1px solid var(--border-color);
129
+ border-radius: 0.75rem;
130
+ background-color: var(--background-primary);
131
+ text-decoration: none;
132
+ transition: box-shadow 0.2s, transform 0.2s;
133
+ }
134
+
135
+ .features a:hover {
136
+ box-shadow: var(--button-hover-shadow);
137
+ transform: translateY(-0.25rem);
138
+ }
139
+
140
+ .features h3 {
141
+ margin-bottom: 0.5rem;
142
+ font-size: 1.125rem;
143
+ font-weight: 600;
144
+ color: var(--text-primary);
145
+ }
146
+
147
+ .features p {
148
+ font-size: 0.875rem;
149
+ color: var(--text-secondary);
150
+ }
File without changes