@jodijonatan/jo-frontend 1.0.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 (53) hide show
  1. package/index.js +129 -0
  2. package/package.json +21 -0
  3. package/templates/landing-page/README.md +73 -0
  4. package/templates/landing-page/eslint.config.js +23 -0
  5. package/templates/landing-page/index.html +13 -0
  6. package/templates/landing-page/package.json +32 -0
  7. package/templates/landing-page/public/favicon.svg +1 -0
  8. package/templates/landing-page/public/icons.svg +24 -0
  9. package/templates/landing-page/src/App.css +184 -0
  10. package/templates/landing-page/src/App.tsx +136 -0
  11. package/templates/landing-page/src/assets/hero.png +0 -0
  12. package/templates/landing-page/src/assets/react.svg +1 -0
  13. package/templates/landing-page/src/assets/vite.svg +1 -0
  14. package/templates/landing-page/src/index.css +29 -0
  15. package/templates/landing-page/src/main.tsx +10 -0
  16. package/templates/landing-page/tsconfig.app.json +25 -0
  17. package/templates/landing-page/tsconfig.json +7 -0
  18. package/templates/landing-page/tsconfig.node.json +24 -0
  19. package/templates/landing-page/vite.config.ts +7 -0
  20. package/templates/minimal/README.md +73 -0
  21. package/templates/minimal/eslint.config.js +23 -0
  22. package/templates/minimal/index.html +13 -0
  23. package/templates/minimal/package.json +32 -0
  24. package/templates/minimal/public/favicon.svg +1 -0
  25. package/templates/minimal/public/icons.svg +24 -0
  26. package/templates/minimal/src/App.css +184 -0
  27. package/templates/minimal/src/App.tsx +35 -0
  28. package/templates/minimal/src/assets/hero.png +0 -0
  29. package/templates/minimal/src/assets/react.svg +1 -0
  30. package/templates/minimal/src/assets/vite.svg +1 -0
  31. package/templates/minimal/src/index.css +29 -0
  32. package/templates/minimal/src/main.tsx +10 -0
  33. package/templates/minimal/tsconfig.app.json +25 -0
  34. package/templates/minimal/tsconfig.json +7 -0
  35. package/templates/minimal/tsconfig.node.json +24 -0
  36. package/templates/minimal/vite.config.ts +7 -0
  37. package/templates/portfolio/README.md +73 -0
  38. package/templates/portfolio/eslint.config.js +23 -0
  39. package/templates/portfolio/index.html +13 -0
  40. package/templates/portfolio/package.json +32 -0
  41. package/templates/portfolio/public/favicon.svg +1 -0
  42. package/templates/portfolio/public/icons.svg +24 -0
  43. package/templates/portfolio/src/App.css +184 -0
  44. package/templates/portfolio/src/App.tsx +133 -0
  45. package/templates/portfolio/src/assets/hero.png +0 -0
  46. package/templates/portfolio/src/assets/react.svg +1 -0
  47. package/templates/portfolio/src/assets/vite.svg +1 -0
  48. package/templates/portfolio/src/index.css +29 -0
  49. package/templates/portfolio/src/main.tsx +10 -0
  50. package/templates/portfolio/tsconfig.app.json +25 -0
  51. package/templates/portfolio/tsconfig.json +7 -0
  52. package/templates/portfolio/tsconfig.node.json +24 -0
  53. package/templates/portfolio/vite.config.ts +7 -0
package/index.js ADDED
@@ -0,0 +1,129 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { Command } from "commander";
4
+ import inquirer from "inquirer";
5
+ import fs from "fs-extra";
6
+ import path from "path";
7
+ import { fileURLToPath } from "url";
8
+ import { execa } from "execa";
9
+ import chalk from "chalk";
10
+ import ora from "ora";
11
+
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+ const program = new Command();
15
+
16
+ program
17
+ .name("create-jo-web")
18
+ .description("CLI untuk men-generate React + Tailwind website yang aesthetic")
19
+ .version("1.0.0");
20
+
21
+ program
22
+ .argument("[project-name]", "Nama project baru")
23
+ .action(async (projectName) => {
24
+ console.log(chalk.cyan.bold("\n✨ Welcome to Create JO Web ✨\n"));
25
+
26
+ // 1. Interactive Prompts
27
+ const answers = await inquirer.prompt([
28
+ {
29
+ type: "input",
30
+ name: "name",
31
+ message: "Masukkan nama project:",
32
+ default: projectName || "my-awesome-web",
33
+ when: !projectName,
34
+ },
35
+ {
36
+ type: "list",
37
+ name: "template",
38
+ message: "Pilih template website:",
39
+ choices: [
40
+ { name: "Minimalist (Base React + Vite + Tailwind)", value: "minimal" },
41
+ { name: "Portfolio (Framer Motion + Dark Theme)", value: "portfolio" },
42
+ { name: "Landing Page (Hero + Features + CTA)", value: "landing-page" },
43
+ ],
44
+ },
45
+ ]);
46
+
47
+ const finalProjectName = projectName || answers.name;
48
+ const selectedTemplate = answers.template;
49
+
50
+ const targetDir = path.join(process.cwd(), finalProjectName);
51
+ const templateDir = path.join(__dirname, "templates", selectedTemplate);
52
+
53
+ if (fs.existsSync(targetDir)) {
54
+ console.error(chalk.red(`\n❌ Error: Folder "${targetDir}" sudah ada!`));
55
+ process.exit(1);
56
+ }
57
+
58
+ console.log(chalk.blue(`\n🚀 Membuat project "${finalProjectName}" menggunakan template "${selectedTemplate}"...\n`));
59
+
60
+ const spinner = ora("1. Meng-copy template...").start();
61
+
62
+ try {
63
+ // 1. Copy template
64
+ await fs.copy(templateDir, targetDir, {
65
+ filter: (src) => !src.includes("node_modules") && !src.includes("package-lock.json"),
66
+ });
67
+ spinner.succeed("Template berhasil disalin!");
68
+
69
+ // Khusus NPM saat publish, file .gitignore diganti menjadi .npmignore.
70
+ // Kita kembalikan .npmignore menjadi .gitignore (jika ada) saat generate.
71
+ const npmIgnorePath = path.join(targetDir, ".npmignore");
72
+ const gitIgnorePath = path.join(targetDir, ".gitignore");
73
+ if (fs.existsSync(npmIgnorePath)) {
74
+ await fs.rename(npmIgnorePath, gitIgnorePath);
75
+ }
76
+
77
+ // 2. Sesuaikan package.json target
78
+ spinner.start("2. Mengkonfigurasi project...");
79
+ const pkgPath = path.join(targetDir, "package.json");
80
+ const pkg = await fs.readJson(pkgPath);
81
+ pkg.name = finalProjectName;
82
+ pkg.version = "0.0.0";
83
+
84
+ // Inject dependency tambahan berdasarkan template
85
+ pkg.dependencies = pkg.dependencies || {};
86
+ pkg.devDependencies = pkg.devDependencies || {};
87
+
88
+ // Core dependencies
89
+ pkg.dependencies["react"] = "^18.2.0";
90
+ pkg.dependencies["react-dom"] = "^18.2.0";
91
+ pkg.devDependencies["@types/react"] = "^18.2.66";
92
+ pkg.devDependencies["@types/react-dom"] = "^18.2.22";
93
+ pkg.devDependencies["@vitejs/plugin-react"] = "^4.2.1";
94
+ pkg.devDependencies["autoprefixer"] = "^10.4.19";
95
+ pkg.devDependencies["postcss"] = "^8.4.38";
96
+ pkg.devDependencies["tailwindcss"] = "^3.4.3";
97
+ pkg.devDependencies["typescript"] = "^5.2.2";
98
+ pkg.devDependencies["vite"] = "^5.2.0";
99
+
100
+ if (selectedTemplate === "portfolio" || selectedTemplate === "landing-page") {
101
+ pkg.dependencies["framer-motion"] = "^11.0.24";
102
+ pkg.dependencies["lucide-react"] = "^0.368.0";
103
+ }
104
+
105
+ await fs.writeJson(pkgPath, pkg, { spaces: 2 });
106
+ spinner.succeed("Konfigurasi project selesai!");
107
+
108
+ // 3. Install Dependencies
109
+ spinner.start("3. Menginstall dependencies... (ini mungkin butuh waktu)");
110
+ // execa tidak ditampilin stdio id-nya ke user agar tampilan rapi karena kita pakai spinner
111
+ await execa("npm", ["install"], { cwd: targetDir });
112
+ spinner.succeed("Dependencies berhasil diinstall!");
113
+
114
+ console.log(chalk.green(`\n✅ Project "${finalProjectName}" berhasil dibuat dengan sempurna!\n`));
115
+ console.log(chalk.cyan(`Coba jalankan perintah berikut untuk memulai:`));
116
+ console.log(chalk.white(` cd ${finalProjectName}`));
117
+ console.log(chalk.white(` npm run dev\n`));
118
+
119
+ if(selectedTemplate !== "minimal") {
120
+ console.log(chalk.magenta(`✨ Template ${selectedTemplate} sudah terpasang dengan Framer Motion & Lucide React!`));
121
+ }
122
+
123
+ } catch (err) {
124
+ spinner.fail("Terjadi kesalahan!");
125
+ console.error(chalk.red(err));
126
+ }
127
+ });
128
+
129
+ program.parse(process.argv);
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "@jodijonatan/jo-frontend",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "bin": {
6
+ "jo-frontend": "./index.js",
7
+ "create-jo-web": "./index.js"
8
+ },
9
+ "files": [
10
+ "index.js",
11
+ "templates"
12
+ ],
13
+ "dependencies": {
14
+ "chalk": "^5.3.0",
15
+ "commander": "^12.0.0",
16
+ "execa": "^9.0.0",
17
+ "fs-extra": "^11.2.0",
18
+ "inquirer": "^9.2.16",
19
+ "ora": "^8.2.0"
20
+ }
21
+ }
@@ -0,0 +1,73 @@
1
+ # React + TypeScript + Vite
2
+
3
+ This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
4
+
5
+ Currently, two official plugins are available:
6
+
7
+ - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs)
8
+ - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/)
9
+
10
+ ## React Compiler
11
+
12
+ The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).
13
+
14
+ ## Expanding the ESLint configuration
15
+
16
+ If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
17
+
18
+ ```js
19
+ export default defineConfig([
20
+ globalIgnores(['dist']),
21
+ {
22
+ files: ['**/*.{ts,tsx}'],
23
+ extends: [
24
+ // Other configs...
25
+
26
+ // Remove tseslint.configs.recommended and replace with this
27
+ tseslint.configs.recommendedTypeChecked,
28
+ // Alternatively, use this for stricter rules
29
+ tseslint.configs.strictTypeChecked,
30
+ // Optionally, add this for stylistic rules
31
+ tseslint.configs.stylisticTypeChecked,
32
+
33
+ // Other configs...
34
+ ],
35
+ languageOptions: {
36
+ parserOptions: {
37
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
38
+ tsconfigRootDir: import.meta.dirname,
39
+ },
40
+ // other options...
41
+ },
42
+ },
43
+ ])
44
+ ```
45
+
46
+ You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
47
+
48
+ ```js
49
+ // eslint.config.js
50
+ import reactX from 'eslint-plugin-react-x'
51
+ import reactDom from 'eslint-plugin-react-dom'
52
+
53
+ export default defineConfig([
54
+ globalIgnores(['dist']),
55
+ {
56
+ files: ['**/*.{ts,tsx}'],
57
+ extends: [
58
+ // Other configs...
59
+ // Enable lint rules for React
60
+ reactX.configs['recommended-typescript'],
61
+ // Enable lint rules for React DOM
62
+ reactDom.configs.recommended,
63
+ ],
64
+ languageOptions: {
65
+ parserOptions: {
66
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
67
+ tsconfigRootDir: import.meta.dirname,
68
+ },
69
+ // other options...
70
+ },
71
+ },
72
+ ])
73
+ ```
@@ -0,0 +1,23 @@
1
+ import js from '@eslint/js'
2
+ import globals from 'globals'
3
+ import reactHooks from 'eslint-plugin-react-hooks'
4
+ import reactRefresh from 'eslint-plugin-react-refresh'
5
+ import tseslint from 'typescript-eslint'
6
+ import { defineConfig, globalIgnores } from 'eslint/config'
7
+
8
+ export default defineConfig([
9
+ globalIgnores(['dist']),
10
+ {
11
+ files: ['**/*.{ts,tsx}'],
12
+ extends: [
13
+ js.configs.recommended,
14
+ tseslint.configs.recommended,
15
+ reactHooks.configs.flat.recommended,
16
+ reactRefresh.configs.vite,
17
+ ],
18
+ languageOptions: {
19
+ ecmaVersion: 2020,
20
+ globals: globals.browser,
21
+ },
22
+ },
23
+ ])
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>template</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.tsx"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "template",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc -b && vite build",
9
+ "lint": "eslint .",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "framer-motion": "^12.38.0",
14
+ "lucide-react": "^1.8.0",
15
+ "react": "^19.2.4",
16
+ "react-dom": "^19.2.4"
17
+ },
18
+ "devDependencies": {
19
+ "@eslint/js": "^9.39.4",
20
+ "@types/node": "^24.12.2",
21
+ "@types/react": "^19.2.14",
22
+ "@types/react-dom": "^19.2.3",
23
+ "@vitejs/plugin-react": "^6.0.1",
24
+ "eslint": "^9.39.4",
25
+ "eslint-plugin-react-hooks": "^7.0.1",
26
+ "eslint-plugin-react-refresh": "^0.5.2",
27
+ "globals": "^17.4.0",
28
+ "typescript": "~6.0.2",
29
+ "typescript-eslint": "^8.58.0",
30
+ "vite": "^8.0.4"
31
+ }
32
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="48" height="46" fill="none" viewBox="0 0 48 46"><path fill="#863bff" d="M25.946 44.938c-.664.845-2.021.375-2.021-.698V33.937a2.26 2.26 0 0 0-2.262-2.262H10.287c-.92 0-1.456-1.04-.92-1.788l7.48-10.471c1.07-1.497 0-3.578-1.842-3.578H1.237c-.92 0-1.456-1.04-.92-1.788L10.013.474c.214-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.471c-1.07 1.498 0 3.579 1.842 3.579h11.377c.943 0 1.473 1.088.89 1.83L25.947 44.94z" style="fill:#863bff;fill:color(display-p3 .5252 .23 1);fill-opacity:1"/><mask id="a" width="48" height="46" x="0" y="0" maskUnits="userSpaceOnUse" style="mask-type:alpha"><path fill="#000" d="M25.842 44.938c-.664.844-2.021.375-2.021-.698V33.937a2.26 2.26 0 0 0-2.262-2.262H10.183c-.92 0-1.456-1.04-.92-1.788l7.48-10.471c1.07-1.498 0-3.579-1.842-3.579H1.133c-.92 0-1.456-1.04-.92-1.787L9.91.473c.214-.297.556-.474.92-.474h28.894c.92 0 1.456 1.04.92 1.788l-7.48 10.471c-1.07 1.498 0 3.578 1.842 3.578h11.377c.943 0 1.473 1.088.89 1.832L25.843 44.94z" style="fill:#000;fill-opacity:1"/></mask><g mask="url(#a)"><g filter="url(#b)"><ellipse cx="5.508" cy="14.704" fill="#ede6ff" rx="5.508" ry="14.704" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -4.47 31.516)"/></g><g filter="url(#c)"><ellipse cx="10.399" cy="29.851" fill="#ede6ff" rx="10.399" ry="29.851" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -39.328 7.883)"/></g><g filter="url(#d)"><ellipse cx="5.508" cy="30.487" fill="#7e14ff" rx="5.508" ry="30.487" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.814 -25.913 -14.639)scale(1 -1)"/></g><g filter="url(#e)"><ellipse cx="5.508" cy="30.599" fill="#7e14ff" rx="5.508" ry="30.599" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.814 -32.644 -3.334)scale(1 -1)"/></g><g filter="url(#f)"><ellipse cx="5.508" cy="30.599" fill="#7e14ff" rx="5.508" ry="30.599" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="matrix(.00324 1 1 -.00324 -34.34 30.47)"/></g><g filter="url(#g)"><ellipse cx="14.072" cy="22.078" fill="#ede6ff" rx="14.072" ry="22.078" style="fill:#ede6ff;fill:color(display-p3 .9275 .9033 1);fill-opacity:1" transform="rotate(93.35 24.506 48.493)scale(-1 1)"/></g><g filter="url(#h)"><ellipse cx="3.47" cy="21.501" fill="#7e14ff" rx="3.47" ry="21.501" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.009 28.708 47.59)scale(-1 1)"/></g><g filter="url(#i)"><ellipse cx="3.47" cy="21.501" fill="#7e14ff" rx="3.47" ry="21.501" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(89.009 28.708 47.59)scale(-1 1)"/></g><g filter="url(#j)"><ellipse cx=".387" cy="8.972" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(39.51 .387 8.972)"/></g><g filter="url(#k)"><ellipse cx="47.523" cy="-6.092" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 47.523 -6.092)"/></g><g filter="url(#l)"><ellipse cx="41.412" cy="6.333" fill="#47bfff" rx="5.971" ry="9.665" style="fill:#47bfff;fill:color(display-p3 .2799 .748 1);fill-opacity:1" transform="rotate(37.892 41.412 6.333)"/></g><g filter="url(#m)"><ellipse cx="-1.879" cy="38.332" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 -1.88 38.332)"/></g><g filter="url(#n)"><ellipse cx="-1.879" cy="38.332" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 -1.88 38.332)"/></g><g filter="url(#o)"><ellipse cx="35.651" cy="29.907" fill="#7e14ff" rx="4.407" ry="29.108" style="fill:#7e14ff;fill:color(display-p3 .4922 .0767 1);fill-opacity:1" transform="rotate(37.892 35.651 29.907)"/></g><g filter="url(#p)"><ellipse cx="38.418" cy="32.4" fill="#47bfff" rx="5.971" ry="15.297" style="fill:#47bfff;fill:color(display-p3 .2799 .748 1);fill-opacity:1" transform="rotate(37.892 38.418 32.4)"/></g></g><defs><filter id="b" width="60.045" height="41.654" x="-19.77" y="16.149" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="c" width="90.34" height="51.437" x="-54.613" y="-7.533" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="d" width="79.355" height="29.4" x="-49.64" y="2.03" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="e" width="79.579" height="29.4" x="-45.045" y="20.029" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="f" width="79.579" height="29.4" x="-43.513" y="21.178" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="g" width="74.749" height="58.852" x="15.756" y="-17.901" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="7.659"/></filter><filter id="h" width="61.377" height="25.362" x="23.548" y="2.284" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="i" width="61.377" height="25.362" x="23.548" y="2.284" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="j" width="56.045" height="63.649" x="-27.636" y="-22.853" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="k" width="54.814" height="64.646" x="20.116" y="-38.415" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="l" width="33.541" height="35.313" x="24.641" y="-11.323" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="m" width="54.814" height="64.646" x="-29.286" y="6.009" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="n" width="54.814" height="64.646" x="-29.286" y="6.009" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="o" width="54.814" height="64.646" x="8.244" y="-2.416" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter><filter id="p" width="39.409" height="43.623" x="18.713" y="10.588" color-interpolation-filters="sRGB" filterUnits="userSpaceOnUse"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur result="effect1_foregroundBlur_2002_17158" stdDeviation="4.596"/></filter></defs></svg>
@@ -0,0 +1,24 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg">
2
+ <symbol id="bluesky-icon" viewBox="0 0 16 17">
3
+ <g clip-path="url(#bluesky-clip)"><path fill="#08060d" d="M7.75 7.735c-.693-1.348-2.58-3.86-4.334-5.097-1.68-1.187-2.32-.981-2.74-.79C.188 2.065.1 2.812.1 3.251s.241 3.602.398 4.13c.52 1.744 2.367 2.333 4.07 2.145-2.495.37-4.71 1.278-1.805 4.512 3.196 3.309 4.38-.71 4.987-2.746.608 2.036 1.307 5.91 4.93 2.746 2.72-2.746.747-4.143-1.747-4.512 1.702.189 3.55-.4 4.07-2.145.156-.528.397-3.691.397-4.13s-.088-1.186-.575-1.406c-.42-.19-1.06-.395-2.741.79-1.755 1.24-3.64 3.752-4.334 5.099"/></g>
4
+ <defs><clipPath id="bluesky-clip"><path fill="#fff" d="M.1.85h15.3v15.3H.1z"/></clipPath></defs>
5
+ </symbol>
6
+ <symbol id="discord-icon" viewBox="0 0 20 19">
7
+ <path fill="#08060d" d="M16.224 3.768a14.5 14.5 0 0 0-3.67-1.153c-.158.286-.343.67-.47.976a13.5 13.5 0 0 0-4.067 0c-.128-.306-.317-.69-.476-.976A14.4 14.4 0 0 0 3.868 3.77C1.546 7.28.916 10.703 1.231 14.077a14.7 14.7 0 0 0 4.5 2.306q.545-.748.965-1.587a9.5 9.5 0 0 1-1.518-.74q.191-.14.372-.293c2.927 1.369 6.107 1.369 8.999 0q.183.152.372.294-.723.437-1.52.74.418.838.963 1.588a14.6 14.6 0 0 0 4.504-2.308c.37-3.911-.63-7.302-2.644-10.309m-9.13 8.234c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.894 0 1.614.82 1.599 1.82.001 1-.705 1.82-1.6 1.82m5.91 0c-.878 0-1.599-.82-1.599-1.82 0-.998.705-1.82 1.6-1.82.893 0 1.614.82 1.599 1.82 0 1-.706 1.82-1.6 1.82"/>
8
+ </symbol>
9
+ <symbol id="documentation-icon" viewBox="0 0 21 20">
10
+ <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="m15.5 13.333 1.533 1.322c.645.555.967.833.967 1.178s-.322.623-.967 1.179L15.5 18.333m-3.333-5-1.534 1.322c-.644.555-.966.833-.966 1.178s.322.623.966 1.179l1.534 1.321"/>
11
+ <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M17.167 10.836v-4.32c0-1.41 0-2.117-.224-2.68-.359-.906-1.118-1.621-2.08-1.96-.599-.21-1.349-.21-2.848-.21-2.623 0-3.935 0-4.983.369-1.684.591-3.013 1.842-3.641 3.428C3 6.449 3 7.684 3 10.154v2.122c0 2.558 0 3.838.706 4.726q.306.383.713.671c.76.536 1.79.64 3.581.66"/>
12
+ <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M3 10a2.78 2.78 0 0 1 2.778-2.778c.555 0 1.209.097 1.748-.047.48-.129.854-.503.982-.982.145-.54.048-1.194.048-1.749a2.78 2.78 0 0 1 2.777-2.777"/>
13
+ </symbol>
14
+ <symbol id="github-icon" viewBox="0 0 19 19">
15
+ <path fill="#08060d" fill-rule="evenodd" d="M9.356 1.85C5.05 1.85 1.57 5.356 1.57 9.694a7.84 7.84 0 0 0 5.324 7.44c.387.079.528-.168.528-.376 0-.182-.013-.805-.013-1.454-2.165.467-2.616-.935-2.616-.935-.349-.91-.864-1.143-.864-1.143-.71-.48.051-.48.051-.48.787.051 1.2.805 1.2.805.695 1.194 1.817.857 2.268.649.064-.507.27-.857.49-1.052-1.728-.182-3.545-.857-3.545-3.87 0-.857.31-1.558.8-2.104-.078-.195-.349-1 .077-2.078 0 0 .657-.208 2.14.805a7.5 7.5 0 0 1 1.946-.26c.657 0 1.328.092 1.946.26 1.483-1.013 2.14-.805 2.14-.805.426 1.078.155 1.883.078 2.078.502.546.799 1.247.799 2.104 0 3.013-1.818 3.675-3.558 3.87.284.247.528.714.528 1.454 0 1.052-.012 1.896-.012 2.156 0 .208.142.455.528.377a7.84 7.84 0 0 0 5.324-7.441c.013-4.338-3.48-7.844-7.773-7.844" clip-rule="evenodd"/>
16
+ </symbol>
17
+ <symbol id="social-icon" viewBox="0 0 20 20">
18
+ <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M12.5 6.667a4.167 4.167 0 1 0-8.334 0 4.167 4.167 0 0 0 8.334 0"/>
19
+ <path fill="none" stroke="#aa3bff" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.35" d="M2.5 16.667a5.833 5.833 0 0 1 8.75-5.053m3.837.474.513 1.035c.07.144.257.282.414.309l.93.155c.596.1.736.536.307.965l-.723.73a.64.64 0 0 0-.152.531l.207.903c.164.715-.213.991-.84.618l-.872-.52a.63.63 0 0 0-.577 0l-.872.52c-.624.373-1.003.094-.84-.618l.207-.903a.64.64 0 0 0-.152-.532l-.723-.729c-.426-.43-.289-.864.306-.964l.93-.156a.64.64 0 0 0 .412-.31l.513-1.034c.28-.562.735-.562 1.012 0"/>
20
+ </symbol>
21
+ <symbol id="x-icon" viewBox="0 0 19 19">
22
+ <path fill="#08060d" fill-rule="evenodd" d="M1.893 1.98c.052.072 1.245 1.769 2.653 3.77l2.892 4.114c.183.261.333.48.333.486s-.068.089-.152.183l-.522.593-.765.867-3.597 4.087c-.375.426-.734.834-.798.905a1 1 0 0 0-.118.148c0 .01.236.017.664.017h.663l.729-.83c.4-.457.796-.906.879-.999a692 692 0 0 0 1.794-2.038c.034-.037.301-.34.594-.675l.551-.624.345-.392a7 7 0 0 1 .34-.374c.006 0 .93 1.306 2.052 2.903l2.084 2.965.045.063h2.275c1.87 0 2.273-.003 2.266-.021-.008-.02-1.098-1.572-3.894-5.547-2.013-2.862-2.28-3.246-2.273-3.266.008-.019.282-.332 2.085-2.38l2-2.274 1.567-1.782c.022-.028-.016-.03-.65-.03h-.674l-.3.342a871 871 0 0 1-1.782 2.025c-.067.075-.405.458-.75.852a100 100 0 0 1-.803.91c-.148.172-.299.344-.99 1.127-.304.343-.32.358-.345.327-.015-.019-.904-1.282-1.976-2.808L6.365 1.85H1.8zm1.782.91 8.078 11.294c.772 1.08 1.413 1.973 1.425 1.984.016.017.241.02 1.05.017l1.03-.004-2.694-3.766L7.796 5.75 5.722 2.852l-1.039-.004-1.039-.004z" clip-rule="evenodd"/>
23
+ </symbol>
24
+ </svg>
@@ -0,0 +1,184 @@
1
+ .counter {
2
+ font-size: 16px;
3
+ padding: 5px 10px;
4
+ border-radius: 5px;
5
+ color: var(--accent);
6
+ background: var(--accent-bg);
7
+ border: 2px solid transparent;
8
+ transition: border-color 0.3s;
9
+ margin-bottom: 24px;
10
+
11
+ &:hover {
12
+ border-color: var(--accent-border);
13
+ }
14
+ &:focus-visible {
15
+ outline: 2px solid var(--accent);
16
+ outline-offset: 2px;
17
+ }
18
+ }
19
+
20
+ .hero {
21
+ position: relative;
22
+
23
+ .base,
24
+ .framework,
25
+ .vite {
26
+ inset-inline: 0;
27
+ margin: 0 auto;
28
+ }
29
+
30
+ .base {
31
+ width: 170px;
32
+ position: relative;
33
+ z-index: 0;
34
+ }
35
+
36
+ .framework,
37
+ .vite {
38
+ position: absolute;
39
+ }
40
+
41
+ .framework {
42
+ z-index: 1;
43
+ top: 34px;
44
+ height: 28px;
45
+ transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg)
46
+ scale(1.4);
47
+ }
48
+
49
+ .vite {
50
+ z-index: 0;
51
+ top: 107px;
52
+ height: 26px;
53
+ width: auto;
54
+ transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg)
55
+ scale(0.8);
56
+ }
57
+ }
58
+
59
+ #center {
60
+ display: flex;
61
+ flex-direction: column;
62
+ gap: 25px;
63
+ place-content: center;
64
+ place-items: center;
65
+ flex-grow: 1;
66
+
67
+ @media (max-width: 1024px) {
68
+ padding: 32px 20px 24px;
69
+ gap: 18px;
70
+ }
71
+ }
72
+
73
+ #next-steps {
74
+ display: flex;
75
+ border-top: 1px solid var(--border);
76
+ text-align: left;
77
+
78
+ & > div {
79
+ flex: 1 1 0;
80
+ padding: 32px;
81
+ @media (max-width: 1024px) {
82
+ padding: 24px 20px;
83
+ }
84
+ }
85
+
86
+ .icon {
87
+ margin-bottom: 16px;
88
+ width: 22px;
89
+ height: 22px;
90
+ }
91
+
92
+ @media (max-width: 1024px) {
93
+ flex-direction: column;
94
+ text-align: center;
95
+ }
96
+ }
97
+
98
+ #docs {
99
+ border-right: 1px solid var(--border);
100
+
101
+ @media (max-width: 1024px) {
102
+ border-right: none;
103
+ border-bottom: 1px solid var(--border);
104
+ }
105
+ }
106
+
107
+ #next-steps ul {
108
+ list-style: none;
109
+ padding: 0;
110
+ display: flex;
111
+ gap: 8px;
112
+ margin: 32px 0 0;
113
+
114
+ .logo {
115
+ height: 18px;
116
+ }
117
+
118
+ a {
119
+ color: var(--text-h);
120
+ font-size: 16px;
121
+ border-radius: 6px;
122
+ background: var(--social-bg);
123
+ display: flex;
124
+ padding: 6px 12px;
125
+ align-items: center;
126
+ gap: 8px;
127
+ text-decoration: none;
128
+ transition: box-shadow 0.3s;
129
+
130
+ &:hover {
131
+ box-shadow: var(--shadow);
132
+ }
133
+ .button-icon {
134
+ height: 18px;
135
+ width: 18px;
136
+ }
137
+ }
138
+
139
+ @media (max-width: 1024px) {
140
+ margin-top: 20px;
141
+ flex-wrap: wrap;
142
+ justify-content: center;
143
+
144
+ li {
145
+ flex: 1 1 calc(50% - 8px);
146
+ }
147
+
148
+ a {
149
+ width: 100%;
150
+ justify-content: center;
151
+ box-sizing: border-box;
152
+ }
153
+ }
154
+ }
155
+
156
+ #spacer {
157
+ height: 88px;
158
+ border-top: 1px solid var(--border);
159
+ @media (max-width: 1024px) {
160
+ height: 48px;
161
+ }
162
+ }
163
+
164
+ .ticks {
165
+ position: relative;
166
+ width: 100%;
167
+
168
+ &::before,
169
+ &::after {
170
+ content: '';
171
+ position: absolute;
172
+ top: -4.5px;
173
+ border: 5px solid transparent;
174
+ }
175
+
176
+ &::before {
177
+ left: 0;
178
+ border-left-color: var(--border);
179
+ }
180
+ &::after {
181
+ right: 0;
182
+ border-right-color: var(--border);
183
+ }
184
+ }
@@ -0,0 +1,136 @@
1
+ import { motion } from "framer-motion";
2
+ import { ArrowRight, Github, Code2, Sparkles, Layout, Zap } from "lucide-react";
3
+
4
+ export default function App() {
5
+ const fadeIn = {
6
+ initial: { opacity: 0, y: 20 },
7
+ animate: { opacity: 1, y: 0 },
8
+ transition: { duration: 0.5 },
9
+ };
10
+
11
+ return (
12
+ <div className="min-h-screen bg-[#0a0a0a] text-zinc-100 selection:bg-indigo-500/30 font-sans">
13
+ {/* Background Decor */}
14
+ <div className="fixed inset-0 overflow-hidden pointer-events-none">
15
+ <div className="absolute -top-[25%] -left-[10%] w-[70%] h-[70%] bg-indigo-500/10 blur-[120px] rounded-full" />
16
+ <div className="absolute -bottom-[25%] -right-[10%] w-[60%] h-[60%] bg-purple-500/10 blur-[120px] rounded-full" />
17
+ </div>
18
+
19
+ {/* Navbar */}
20
+ <nav className="relative z-10 flex items-center justify-between p-6 max-w-7xl mx-auto">
21
+ <div className="flex items-center gap-2 font-bold text-xl tracking-tighter">
22
+ <div className="w-8 h-8 bg-white rounded-lg flex items-center justify-center">
23
+ <Code2 className="text-black w-5 h-5" />
24
+ </div>
25
+ <span>ProdSync</span>
26
+ </div>
27
+ <div className="hidden md:flex items-center gap-8 text-sm font-medium text-zinc-400">
28
+ <a href="#" className="hover:text-white transition">
29
+ Features
30
+ </a>
31
+ <a href="#" className="hover:text-white transition">
32
+ Showcase
33
+ </a>
34
+ <a href="#" className="hover:text-white transition">
35
+ Docs
36
+ </a>
37
+ </div>
38
+ <button className="bg-zinc-900 border border-zinc-800 px-4 py-2 rounded-full text-sm font-medium hover:bg-zinc-800 transition">
39
+ Launch App
40
+ </button>
41
+ </nav>
42
+
43
+ {/* Hero Section */}
44
+ <main className="relative z-10 max-w-7xl mx-auto px-6 pt-20 pb-32">
45
+ <motion.div
46
+ initial="initial"
47
+ animate="animate"
48
+ className="text-center space-y-8"
49
+ >
50
+ <motion.div
51
+ {...fadeIn}
52
+ className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-zinc-900 border border-zinc-800 text-xs font-medium text-zinc-400"
53
+ >
54
+ <Sparkles className="w-3 h-3 text-yellow-500" />
55
+ <span>New: Vercel-style deployment ready</span>
56
+ </motion.div>
57
+
58
+ <motion.h1
59
+ {...fadeIn}
60
+ transition={{ delay: 0.1 }}
61
+ className="text-6xl md:text-8xl font-bold tracking-tight bg-clip-text text-transparent bg-gradient-to-b from-white to-zinc-500"
62
+ >
63
+ Launch products <br /> faster than ever.
64
+ </motion.h1>
65
+
66
+ <motion.p
67
+ {...fadeIn}
68
+ transition={{ delay: 0.2 }}
69
+ className="text-zinc-400 text-lg md:text-xl max-w-2xl mx-auto leading-relaxed"
70
+ >
71
+ A powerful, modern landing page designed to convert. Build your next SaaS product with React, Framer Motion, and Tailwind CSS.
72
+ </motion.p>
73
+
74
+ <motion.div
75
+ {...fadeIn}
76
+ transition={{ delay: 0.3 }}
77
+ className="flex flex-col sm:flex-row gap-4 justify-center items-center"
78
+ >
79
+ <button className="group relative bg-white text-black px-8 py-3 rounded-full font-semibold flex items-center gap-2 hover:bg-zinc-200 transition overflow-hidden">
80
+ Get Started Free
81
+ <ArrowRight className="w-4 h-4 group-hover:translate-x-1 transition-transform" />
82
+ </button>
83
+ <button className="px-8 py-3 rounded-full font-semibold flex items-center gap-2 border border-zinc-800 hover:bg-zinc-900 transition">
84
+ <Github className="w-4 h-4" />
85
+ Star on GitHub
86
+ </button>
87
+ </motion.div>
88
+ </motion.div>
89
+
90
+ {/* Feature Grid */}
91
+ <motion.div
92
+ initial={{ opacity: 0, y: 40 }}
93
+ animate={{ opacity: 1, y: 0 }}
94
+ transition={{ delay: 0.5, duration: 0.8 }}
95
+ className="grid grid-cols-1 md:grid-cols-3 gap-6 mt-32"
96
+ >
97
+ <FeatureCard
98
+ icon={<Zap className="w-5 h-5 text-yellow-500" />}
99
+ title="Blazing Fast"
100
+ desc="Ditenagai oleh Vite untuk HMR yang instan dan build yang dioptimalkan."
101
+ />
102
+ <FeatureCard
103
+ icon={<Layout className="w-5 h-5 text-blue-500" />}
104
+ title="Modern Layout"
105
+ desc="Sistem grid dan flexbox yang responsif secara default."
106
+ />
107
+ <FeatureCard
108
+ icon={<Code2 className="w-5 h-5 text-purple-500" />}
109
+ title="Type Safe"
110
+ desc="Dukungan penuh TypeScript untuk meminimalisir bug saat pengembangan."
111
+ />
112
+ </motion.div>
113
+ </main>
114
+ </div>
115
+ );
116
+ }
117
+
118
+ function FeatureCard({
119
+ icon,
120
+ title,
121
+ desc,
122
+ }: {
123
+ icon: React.ReactNode;
124
+ title: string;
125
+ desc: string;
126
+ }) {
127
+ return (
128
+ <div className="p-8 rounded-3xl bg-zinc-900/50 border border-zinc-800 hover:border-zinc-700 transition group">
129
+ <div className="w-12 h-12 rounded-2xl bg-zinc-900 border border-zinc-800 flex items-center justify-center mb-6 group-hover:scale-110 transition-transform">
130
+ {icon}
131
+ </div>
132
+ <h3 className="text-xl font-bold mb-2">{title}</h3>
133
+ <p className="text-zinc-400 leading-relaxed text-sm">{desc}</p>
134
+ </div>
135
+ );
136
+ }