@aditokmo/react-setup-cli 0.1.1 → 0.1.3

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 CHANGED
@@ -2,19 +2,13 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@aditokmo/react-setup-cli?color=blue)](https://www.npmjs.com/package/@aditokmo/react-setup-cli)
4
4
 
5
- A React CLI built with Vite that helps you build and structure your projects in seconds. It eliminates manual setup by configuring your favorite tools into a **clean, modular architecture** automatically.
5
+ A React CLI built on top of Vite that helps you build and structure projects in seconds. It eliminates manual setup by configuring your favorite tools into a **clean, modular architecture** automatically.
6
6
 
7
- **Note:** This package is a CLI tool. Do not install it via `npm i`. Instead check usage down below.
8
-
9
- ## What it does
10
-
11
- - **Automated Installation:** Installs all selected libraries (listed below) for you.
12
- - **Structuring:** Automatically generates a scalable folder structure based on your choices.
13
- - **Boilerplate Injection:** Pre-configures Providers, Router paths, etc., so you can start coding features immediately
7
+ **Note:** This package is a CLI tool. Do not install it with `npm i`. Instead check `Quick Start` down below.
14
8
 
15
9
  ---
16
10
 
17
- ## Usage
11
+ ## Quick Start
18
12
 
19
13
  Run the following command in your terminal to start CLI
20
14
 
@@ -31,23 +25,55 @@ yarn dlx @aditokmo/react-setup-cli
31
25
 
32
26
  ---
33
27
 
34
- ## Features
28
+ ## How it saves your time
35
29
 
36
- | Category | Options |
37
- | :------------------- | :-------------------------------------- |
38
- | **Folder Structure** | Feature-based |
39
- | **Modules** | Common, Auth |
40
- | **Routing** | React Router, TanStack Router |
41
- | **Data Fetching** | TanStack Query (React Query) & Axios |
42
- | **State Management** | Zustand |
43
- | **Form** | React Hook Form, TanStack Form |
44
- | **Schema** | Zod, Yup |
45
- | **Styling** | CSS, SCSS, Tailwind CSS |
46
- | **UI Components** | Shadcn |
47
- | **Icons** | React Icons, Font Awesome |
48
- | **Toast** | React Toastify, React Hot Toast, Sonner |
49
- | **Custom Hooks** | |
50
- | **Helpers** | |
30
+ - **Minimal Installation:** No more manual `npm install` for 10 different packages. The CLI detects your package manager and handles everything.
31
+ - **Architecture:** Instead of a messy folder structure, you get a **modular (feature-based)** structure that is ready for large-scale production.
32
+ - **Smart Boilerplate Injection:** It doesn't just create files it also wires them. It sets up Axios interceptors, TanStack Query providers, and Router wrappers so you can jump straight into the code that actually matters.
33
+
34
+ ---
35
+
36
+ ## Use Cases when this CLI is useful:
37
+
38
+ - **Starting a Professional Project:** When you need a project that follows "Clean Architecture" and industry standards from the very first commit.
39
+ - **Focusing on a Specific Feature:** When you want to test a new library or a specific piece of logic, but you need a proper environment to do it. This CLI lets you focus on what you are testing instead of wasting time on a setup.
40
+ - **Prototyping & MVP:** When you have a startup idea and want to to build actual features right away without sacrificing code quality.
41
+ - **Hackathons:** When every second counts. You can get all your configuration and setup ready before the competition start.
42
+
43
+ ---
44
+
45
+ ## About Arhitecture
46
+
47
+ ### Feature-Based Structure
48
+
49
+ This architecture uses a modular approach to help you build large-scale projects. Instead of mixing all components and hooks into global folders, everything is grouped by domain, such as Auth or Dashboard. This method makes it much easier to navigate the codebase and ensures your project remains maintainable as it grows.
50
+
51
+ ### Global State Management (Zustand)
52
+
53
+ Zustand is currently the only option in CLI for global state management. It is the most popular and easiest-to-use library today. I believe Redux is overkill for most modern projects. Most "global state" is now handled as server-state by TanStack Query. Global state should be reserved for things like authentication and UI stuff, and Zustand handles this perfectly with zero boilerplate.
54
+
55
+ ### Axios
56
+
57
+ The CLI generates a pre-configured Axios client that serves as your central API bridge. It includes ready-to-use interceptors for handling authorization tokens and global error responses, saving you from writing the same repetitive setup every time. Axios is set by default beacause it is better choice then fetch, Axios is more user-friendly, has better error handling out of the box, and is overall a safer and more robust choice for production apps.
58
+
59
+ ### TanStack Query
60
+
61
+ TanStack Query is integrated to handle server-state management. It is optional, but if selected, the CLI automatically wires up the necessary providers and configurations so you can start fetching data immediately. If you select to not use react query, you still get a traditional boilerplate for manual data handling.
62
+
63
+ ### Styling
64
+
65
+ You can choose between CSS, SCSS (soon), or Tailwind CSS. While I personally recommend Tailwind for modern and faster development, the CLI ensures that regardless of your choice, the project is configured with a global styles directory and a consistent entry point. If you select TailwindCSS you will also have option to use Shadcn/UI, and with that you will have option to choose components that you want to install instead of doing it manually.
66
+
67
+ ### Routing
68
+
69
+ The CLI offers two options: React Router and TanStack Router.
70
+
71
+ - React Router is the industry standard that most developers are familiar with.
72
+ - TanStack Router is included for those who want a fully type-safe routing experience with built-in data loading capabilities. Whichever you choose, the CLI doesn't just install the library it will generate a `routes/` directory system to help you easily separate your public pages from protected pages.
73
+
74
+ ### Package Manager Detection
75
+
76
+ To make the workflow even smoother, the tool has an automatic package manager detector. It identifies whether you are using npm, pnpm, or yarn based on the command you used to execute the CLI, and it handles all installations using your preferred package manager to ensure consistency and avoid conflicts.
51
77
 
52
78
  ---
53
79
 
@@ -67,6 +93,9 @@ src/
67
93
  │ ├── MainLayout.tsx
68
94
  │ ├── AuthLayout.tsx
69
95
  ├── modules/ # Feature-based modules (The core of your app)
96
+ | ├── common/ # Shared components & pages (404 Page, Navbar, Sidebar)
97
+ │ │ ├── pages/
98
+ │ │ └── components/
70
99
  │ └── auth/ # Example: Auth module
71
100
  │ ├── components/
72
101
  │ ├── hooks/
@@ -81,6 +110,41 @@ src/
81
110
 
82
111
  ---
83
112
 
113
+ ## Features
114
+
115
+ | Category | Options |
116
+ | :------------------- | :-------------------------------------- |
117
+ | **Folder Structure** | Feature-based |
118
+ | **Modules** | Common, Auth |
119
+ | **Routing** | React Router, TanStack Router |
120
+ | **Data Fetching** | TanStack Query (React Query) & Axios |
121
+ | **State Management** | Zustand |
122
+ | **Form** | React Hook Form, TanStack Form |
123
+ | **Schema** | Zod, Yup |
124
+ | **Styling** | CSS, SCSS, Tailwind CSS |
125
+ | **UI Components** | Shadcn |
126
+ | **Icons** | React Icons, Font Awesome |
127
+ | **Toast** | React Toastify, React Hot Toast, Sonner |
128
+ | **Custom Hooks** | |
129
+ | **Helpers** | |
130
+
131
+ ---
132
+
133
+ ## Future of CLI
134
+
135
+ Some of my ideas
136
+
137
+ - Options to choose between React, Next.js and TanStack Start
138
+ - Testing tools
139
+ - i18next pre-setup.
140
+ - Supabase & Firebase integration templates
141
+ - Global custom hooks & helper functions
142
+ - Pre-commit linters
143
+ - Github Action workflow
144
+ - TanStack Table (if your app has some kind of tables)
145
+
146
+ ---
147
+
84
148
  ## Local Setup
85
149
 
86
150
  To do your own changes and use this CLI locally:
@@ -128,7 +192,3 @@ react-setup-cli
128
192
  - `templates/` - Pre-defined boilerplates and configurations.
129
193
 
130
194
  ---
131
-
132
- <p align="center">
133
- Developed with ❤️ by <a href="https://github.com/aditokmo">aditokmo</a>
134
- </p>
package/dist/index.js CHANGED
@@ -3,9 +3,10 @@ import fs from 'fs-extra';
3
3
  import path from 'path';
4
4
  import { execSync } from 'child_process';
5
5
  import { askQuestions } from './questions.js';
6
- import { copyTemplate, patchViteConfig, finalizeViteConfig, patchAppFile, finalizeAppFile, detectPackageManager } from './utils.js';
6
+ import { copyTemplate, patchViteConfig, finalizeViteConfig, patchAppFile, finalizeAppFile, detectPackageManager, patchIndexHTMLFile } from './utils.js';
7
7
  import { collectDependencies } from './installers.js';
8
8
  import { fileURLToPath } from 'url';
9
+ import { FONT_QUERIES } from './mapper.js';
9
10
  const __filename = fileURLToPath(import.meta.url);
10
11
  const __dirname = path.dirname(__filename);
11
12
  async function main() {
@@ -19,6 +20,7 @@ async function main() {
19
20
  projectDir = path.join(process.cwd(), answers.projectName);
20
21
  const templateRoot = path.join(__dirname, '../templates');
21
22
  const appFilePath = path.join(projectDir, 'src', 'App.tsx');
23
+ const indexHTMLPath = path.join(projectDir, 'src', 'index.html');
22
24
  const viteConfigPath = path.join(projectDir, 'vite.config.ts');
23
25
  fs.ensureDirSync(projectDir);
24
26
  copyTemplate(path.join(templateRoot, 'base'), projectDir);
@@ -30,6 +32,27 @@ async function main() {
30
32
  if (answers.style === 'css') {
31
33
  copyTemplate(path.join(templateRoot, 'styles', 'css', 'src'), path.join(projectDir, 'src/styles'));
32
34
  }
35
+ if (answers.style === 'scss') {
36
+ copyTemplate(path.join(templateRoot, 'styles', 'scss', 'src'), path.join(projectDir, 'src/styles'));
37
+ patchIndexHTMLFile(path.join(projectDir, 'index.html'), '<link rel="stylesheet" href="./src/styles/main.css" />', '<link rel="stylesheet" href="./src/styles/main.scss" />');
38
+ }
39
+ // Fonts
40
+ if (answers?.fonts && answers.fonts.length > 0) {
41
+ const fontString = answers.fonts
42
+ .map(name => FONT_QUERIES[name])
43
+ .join('&');
44
+ const url = `https://fonts.googleapis.com/css2?${fontString}&display=swap`;
45
+ const content = `
46
+ <link rel="preconnect" href="https://fonts.googleapis.com">
47
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
48
+ <link href="${url}" rel="stylesheet">`;
49
+ patchIndexHTMLFile(path.join(projectDir, 'index.html'), '<!-- [HEAD_LINK_IMPORT] -->', content);
50
+ }
51
+ else {
52
+ let html = fs.readFileSync(indexHTMLPath, 'utf-8');
53
+ html = html.replace('', '');
54
+ fs.writeFileSync(indexHTMLPath, html);
55
+ }
33
56
  // State Management
34
57
  if (answers.reactQuery) {
35
58
  copyTemplate(path.join(templateRoot, 'state', 'react-query', 'src', 'provider'), path.join(projectDir, 'src/providers'));
@@ -4,7 +4,7 @@ export function collectDependencies(answers, packageManager) {
4
4
  const dependency = new Set();
5
5
  const devDependency = new Set();
6
6
  const cmd = [];
7
- const dlx = packageManager === 'npm' ? 'npx --yes' : `${packageManager} dlx --yes`;
7
+ const dlx = packageManager === 'npm' ? 'npx --yes' : `${packageManager} dlx`;
8
8
  // Default base packages
9
9
  axiosInstaller.dependency?.forEach(d => dependency.add(d));
10
10
  // Packages from answers
package/dist/mapper.js CHANGED
@@ -1,10 +1,11 @@
1
- import { fontAwesomeIconsInstaller, phosphorIconsInstaller, reactFormHookInstaller, reactHotToastInstaller, reactIconsInstaller, reactQueryInstaller, reactRouterInstaller, reactToastifyInstaller, sonnerInstaller, tailwindInstaller, tanstackFormInstaller, tanstackRouterInstaller, yupInstaller, zodInstaller, zustandInstaller } from './packages.js';
1
+ import { fontAwesomeIconsInstaller, phosphorIconsInstaller, reactFormHookInstaller, reactHotToastInstaller, reactIconsInstaller, reactQueryInstaller, reactRouterInstaller, reactToastifyInstaller, scssInstaller, sonnerInstaller, tailwindInstaller, tanstackFormInstaller, tanstackRouterInstaller, yupInstaller, zodInstaller, zustandInstaller } from './packages.js';
2
2
  export const installers = {
3
3
  'reactQuery': reactQueryInstaller,
4
4
  'zustand': zustandInstaller,
5
5
  'react-router': reactRouterInstaller,
6
6
  'tanstack-router': tanstackRouterInstaller,
7
7
  'tailwind': tailwindInstaller,
8
+ 'scss': scssInstaller,
8
9
  'react-icons': reactIconsInstaller,
9
10
  'font-awesome': fontAwesomeIconsInstaller,
10
11
  'phosphor-icons': phosphorIconsInstaller,
@@ -16,3 +17,12 @@ export const installers = {
16
17
  'zod': zodInstaller,
17
18
  'yup': yupInstaller,
18
19
  };
20
+ export const FONT_QUERIES = {
21
+ "geist": "family=Geist:wght@100..900",
22
+ "inter": "family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900",
23
+ "lato": "family=Lato:ital,wght@0,100;0,300;0,400;0,700;0,900;1,100;1,300;1,400;1,700;1,900",
24
+ "montserrat": "family=Montserrat:ital,wght@0,100..900;1,100..900",
25
+ "open-sans": "family=Open+Sans:ital,wght@0,300..800;1,300..800",
26
+ "poppins": "family=Poppins:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900",
27
+ "roboto": "family=Roboto:ital,wght@0,100..900;1,100..900"
28
+ };
package/dist/packages.js CHANGED
@@ -11,6 +11,9 @@ export const zustandInstaller = {
11
11
  export const tailwindInstaller = {
12
12
  dependency: ['tailwindcss', '@tailwindcss/vite'],
13
13
  };
14
+ export const scssInstaller = {
15
+ devDependency: ['sass']
16
+ };
14
17
  // Icons
15
18
  export const reactIconsInstaller = {
16
19
  dependency: ['react-icons'],
package/dist/questions.js CHANGED
@@ -11,6 +11,7 @@ export async function askQuestions() {
11
11
  options: [
12
12
  { value: 'tailwind', label: 'Tailwind' },
13
13
  { value: 'css', label: 'CSS' },
14
+ { value: 'scss', label: 'SCSS' },
14
15
  ],
15
16
  }),
16
17
  shadcn: ({ results }) => {
@@ -38,6 +39,19 @@ export async function askQuestions() {
38
39
  required: false,
39
40
  });
40
41
  },
42
+ fonts: () => multiselect({
43
+ message: 'Use space to select your fonts',
44
+ options: [
45
+ { value: 'geist', label: 'Geist' },
46
+ { value: 'inter', label: 'Inter' },
47
+ { value: 'lato', label: 'Lato' },
48
+ { value: 'montserrat', label: 'Montserrat' },
49
+ { value: 'open-sans', label: 'Open Sans' },
50
+ { value: 'poppins', label: 'Poppins' },
51
+ { value: 'roboto', label: 'Roboto' }
52
+ ],
53
+ required: false
54
+ }),
41
55
  icons: () => select({
42
56
  message: 'Choose icon library:',
43
57
  options: [
@@ -95,7 +109,8 @@ export async function askQuestions() {
95
109
  isCancel(results.shadcnComponents) ||
96
110
  isCancel(results.icons) ||
97
111
  isCancel(results.toast) ||
98
- isCancel(results.reactQuery)) {
112
+ isCancel(results.reactQuery) ||
113
+ isCancel(results.fonts)) {
99
114
  cancel('Setup cancelled.');
100
115
  process.exit(0);
101
116
  }
@@ -103,5 +118,6 @@ export async function askQuestions() {
103
118
  ...results,
104
119
  shadcn: results.shadcn,
105
120
  shadcnComponents: (results.shadcnComponents ?? []),
121
+ fonts: (results.fonts ?? [])
106
122
  };
107
123
  }
package/dist/utils.js CHANGED
@@ -6,6 +6,11 @@ export function copyTemplate(src, path) {
6
6
  }
7
7
  fs.copySync(src, path, { overwrite: true });
8
8
  }
9
+ export function patchIndexHTMLFile(filePath, importLine, newContent) {
10
+ let htmlContent = fs.readFileSync(filePath, 'utf-8');
11
+ htmlContent = htmlContent.replace(importLine, newContent);
12
+ fs.writeFileSync(filePath, htmlContent);
13
+ }
9
14
  export function patchAppFile(filePath, importLine, openTag, closeTag) {
10
15
  if (!fs.existsSync(filePath))
11
16
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aditokmo/react-setup-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "A fast React CLI to jumpstart your projects. It sets up your libraries and organizes a scalable folder structure so you can skip the configuration and go straight to coding.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -1,4 +1,4 @@
1
- <!DOCTYPE html>
1
+ <!doctype html>
2
2
  <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8" />
@@ -6,6 +6,7 @@
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
7
  <link rel="stylesheet" href="./src/styles/main.css" />
8
8
  <title>vite-project</title>
9
+ <!-- [HEAD_LINK_IMPORT] -->
9
10
  </head>
10
11
  <body>
11
12
  <div id="root"></div>
@@ -1,4 +1,4 @@
1
- import '@/styles/404.css';
1
+ import '@/styles/404.scss';
2
2
 
3
3
  export const NotFound = () => {
4
4
  return (
@@ -1,3 +1,5 @@
1
+ @import "./variables.css";
2
+
1
3
  .not-found-wrapper {
2
4
  display: flex;
3
5
  flex-direction: column;
@@ -6,25 +8,25 @@
6
8
  min-height: 100vh;
7
9
  padding: 20px;
8
10
  text-align: center;
9
- font-family: "Poppins", sans-serif;
10
- background: #e6edfc;
11
+ font-family: var(--font-main);
12
+ background: var(--bg-light);
11
13
  }
12
14
 
13
15
  .not-found-content {
14
16
  padding: 100px 70px;
15
- background: #fff;
17
+ background: var(--white);
16
18
  max-width: 700px;
17
19
  width: 100%;
18
20
  display: flex;
19
21
  flex-direction: column;
20
22
  justify-content: center;
21
23
  align-items: center;
22
- border-radius: 24px;
23
- box-shadow: 0px 0px 14px -14px rgba(0, 0, 0, 0.75);
24
+ border-radius: var(--radius-lg);
25
+ box-shadow: var(--shadow-main);
24
26
  }
25
27
 
26
28
  .not-found-content span {
27
- color: #666;
29
+ color: var(--text-muted);
28
30
  font-weight: 700;
29
31
  font-size: 6rem;
30
32
  line-height: 1;
@@ -34,12 +36,12 @@
34
36
  font-size: 2.5rem;
35
37
  font-weight: 600;
36
38
  margin: 0;
37
- color: #111;
39
+ color: var(--text-dark);
38
40
  }
39
41
 
40
42
  .not-found-content p {
41
43
  font-size: 1rem;
42
- color: #666;
44
+ color: var(--text-muted);
43
45
  max-width: 360px;
44
46
  width: 100%;
45
47
  margin: 10px 0;
@@ -47,11 +49,11 @@
47
49
 
48
50
  .not-found-content a {
49
51
  font-size: 0.9rem;
50
- color: #fff;
51
- background-color: #37538a;
52
+ color: var(--white);
53
+ background-color: var(--primary-blue);
52
54
  padding: 10px 30px;
53
55
  margin-top: 20px;
54
- border-radius: 10px;
56
+ border-radius: var(--radius-sm);
55
57
  transition: 0.2s;
56
58
  cursor: pointer;
57
59
  text-decoration: none;
@@ -59,7 +61,7 @@
59
61
  }
60
62
 
61
63
  .not-found-content a:hover {
62
- background: #263c67;
64
+ background: var(--primary-blue-hover);
63
65
  }
64
66
 
65
67
  @media (max-width: 768px) {
@@ -67,12 +69,10 @@
67
69
  padding: 60px 40px;
68
70
  max-width: 90%;
69
71
  }
70
-
71
72
  .not-found-content span {
72
73
  font-size: 4.5rem;
73
74
  margin-bottom: 20px;
74
75
  }
75
-
76
76
  .not-found-content h1 {
77
77
  font-size: 2rem;
78
78
  }
@@ -81,22 +81,18 @@
81
81
  @media (max-width: 480px) {
82
82
  .not-found-content {
83
83
  padding: 40px 20px;
84
- border-radius: 16px;
84
+ border-radius: var(--radius-md);
85
85
  }
86
-
87
86
  .not-found-content span {
88
87
  font-size: 3.5rem;
89
88
  margin-bottom: 20px;
90
89
  }
91
-
92
90
  .not-found-content h1 {
93
91
  font-size: 1.5rem;
94
92
  }
95
-
96
93
  .not-found-content p {
97
94
  font-size: 0.9rem;
98
95
  }
99
-
100
96
  .not-found-content a {
101
97
  width: 100%;
102
98
  box-sizing: border-box;
@@ -0,0 +1,70 @@
1
+ @import "./variables.css";
2
+
3
+ * {
4
+ margin: 0;
5
+ padding: 0;
6
+ box-sizing: border-box;
7
+ }
8
+
9
+ html {
10
+ scroll-behavior: smooth;
11
+ }
12
+
13
+ body {
14
+ font-family: var(--font-main);
15
+ background-color: var(--white);
16
+ color: var(--text-dark);
17
+ line-height: 1.6;
18
+ min-height: 100vh;
19
+ -webkit-font-smoothing: antialiased;
20
+ -moz-osx-font-smoothing: grayscale;
21
+ }
22
+
23
+ h1,
24
+ h2,
25
+ h3,
26
+ h4,
27
+ h5,
28
+ h6 {
29
+ color: var(--text-dark);
30
+ font-weight: 600;
31
+ line-height: 1.2;
32
+ }
33
+
34
+ p {
35
+ color: var(--text-muted);
36
+ }
37
+
38
+ .container {
39
+ width: 100%;
40
+ max-width: 1200px;
41
+ margin: 0 auto;
42
+ padding: 0 20px;
43
+ }
44
+
45
+ .flex-center {
46
+ display: flex;
47
+ align-items: center;
48
+ justify-content: center;
49
+ }
50
+
51
+ a {
52
+ text-decoration: none;
53
+ color: var(--primary-blue);
54
+ transition: 0.3s ease;
55
+ }
56
+
57
+ a:hover {
58
+ color: var(--primary-blue-hover);
59
+ }
60
+
61
+ button {
62
+ font-family: inherit;
63
+ cursor: pointer;
64
+ }
65
+
66
+ img {
67
+ max-width: 100%;
68
+ height: auto;
69
+ display: block;
70
+ }
@@ -0,0 +1,15 @@
1
+ :root {
2
+ --primary-blue: #37538a;
3
+ --primary-blue-hover: #263c67;
4
+ --bg-light: #e6edfc;
5
+ --white: #ffffff;
6
+ --text-dark: #111111;
7
+ --text-muted: #666666;
8
+
9
+ --font-main: "Arial", sans-serif;
10
+
11
+ --shadow-main: 0px 0px 14px -14px rgba(0, 0, 0, 0.75);
12
+ --radius-lg: 24px;
13
+ --radius-md: 16px;
14
+ --radius-sm: 10px;
15
+ }
@@ -0,0 +1,103 @@
1
+ @use "./index.scss" as *;
2
+
3
+ .not-found-wrapper {
4
+ display: flex;
5
+ flex-direction: column;
6
+ align-items: center;
7
+ justify-content: center;
8
+ min-height: 100vh;
9
+ padding: 20px;
10
+ text-align: center;
11
+ font-family: $font-main;
12
+ background: $bg-light;
13
+
14
+ .not-found-content {
15
+ padding: 100px 70px;
16
+ background: $white;
17
+ max-width: 700px;
18
+ width: 100%;
19
+ display: flex;
20
+ flex-direction: column;
21
+ justify-content: center;
22
+ align-items: center;
23
+ border-radius: $radius-lg;
24
+ box-shadow: $shadow-main;
25
+
26
+ span {
27
+ color: $text-muted;
28
+ font-weight: 700;
29
+ font-size: 6rem;
30
+ line-height: 1;
31
+ }
32
+
33
+ h1 {
34
+ font-size: 2.5rem;
35
+ font-weight: 600;
36
+ margin: 0;
37
+ color: $text-dark;
38
+ }
39
+
40
+ p {
41
+ font-size: 1rem;
42
+ color: $text-muted;
43
+ max-width: 360px;
44
+ width: 100%;
45
+ margin: 10px 0;
46
+ }
47
+
48
+ a {
49
+ font-size: 0.9rem;
50
+ color: $white;
51
+ background-color: $primary-blue;
52
+ padding: 10px 30px;
53
+ margin-top: 20px;
54
+ border-radius: $radius-sm;
55
+ transition: 0.2s;
56
+ cursor: pointer;
57
+ text-decoration: none;
58
+ display: inline-block;
59
+
60
+ &:hover {
61
+ background: $primary-blue-hover;
62
+ }
63
+ }
64
+
65
+ @media (max-width: 768px) {
66
+ padding: 60px 40px;
67
+ max-width: 90%;
68
+
69
+ span {
70
+ font-size: 4.5rem;
71
+ margin-bottom: 20px;
72
+ }
73
+
74
+ h1 {
75
+ font-size: 2rem;
76
+ }
77
+ }
78
+
79
+ @media (max-width: 480px) {
80
+ padding: 40px 20px;
81
+ border-radius: $radius-md;
82
+
83
+ span {
84
+ font-size: 3.5rem;
85
+ margin-bottom: 20px;
86
+ }
87
+
88
+ h1 {
89
+ font-size: 1.5rem;
90
+ }
91
+
92
+ p {
93
+ font-size: 0.9rem;
94
+ }
95
+
96
+ a {
97
+ width: 100%;
98
+ box-sizing: border-box;
99
+ text-align: center;
100
+ }
101
+ }
102
+ }
103
+ }
@@ -0,0 +1,2 @@
1
+ @forward "variables";
2
+ @forward "mixins";
@@ -0,0 +1,27 @@
1
+ $breakpoint-tablet: 768px;
2
+ $breakpoint-mobile: 480px;
3
+
4
+ @mixin tablet {
5
+ @media (max-width: #{$breakpoint-tablet}) {
6
+ @content;
7
+ }
8
+ }
9
+
10
+ @mixin mobile {
11
+ @media (max-width: #{$breakpoint-mobile}) {
12
+ @content;
13
+ }
14
+ }
15
+
16
+ // helpers
17
+ @mixin flex-center {
18
+ display: flex;
19
+ align-items: center;
20
+ justify-content: center;
21
+ }
22
+
23
+ @mixin text-truncate {
24
+ overflow: hidden;
25
+ text-overflow: ellipsis;
26
+ white-space: nowrap;
27
+ }
@@ -0,0 +1,16 @@
1
+ // Brand Colors
2
+ $primary: #37538a;
3
+ $primary-dark: #263c67;
4
+ $accent: #e6edfc;
5
+
6
+ // Neutrals
7
+ $white: #ffffff;
8
+ $black: #111111;
9
+ $gray: #666666;
10
+
11
+ // Fonts
12
+ $font-main: "Arial", sans-serif;
13
+
14
+ // Breakpoints
15
+ $breakpoint-tablet: 768px;
16
+ $breakpoint-mobile: 480px;
@@ -0,0 +1,20 @@
1
+ @use "index" as *;
2
+
3
+ * {
4
+ margin: 0;
5
+ padding: 0;
6
+ box-sizing: border-box;
7
+ }
8
+
9
+ body {
10
+ font-family: $font-main;
11
+ background-color: $white;
12
+ color: $black;
13
+ line-height: 1.5;
14
+ -webkit-font-smoothing: antialiased;
15
+ }
16
+
17
+ a {
18
+ text-decoration: none;
19
+ color: inherit;
20
+ }