├── assets ├── css │ ├── pageStyles │ │ ├── test.css │ │ ├── testSass.css.map │ │ ├── notFound.css │ │ └── testSass.css │ ├── sass │ │ └── testSass.scss │ ├── index.css │ └── tailwindOutput.css └── images │ └── lumina-icon.webp ├── .gitignore ├── api ├── middleware │ ├── 02-loggerTest.js │ ├── 03-loggerTest.js │ ├── 04-requestLogger.js │ ├── index.js │ └── 01-apiTokenAuth.js ├── models │ └── User.js └── routes │ └── user.js ├── components ├── layout │ ├── Loader.js │ ├── Layout.js │ ├── Footer.js │ └── Header.js ├── pages │ ├── TestPage.js │ ├── NotFoundPage.js │ └── HomePage.js └── module │ └── home │ ├── Comments.js │ ├── FAQ.js │ └── Price.js ├── routes.js ├── .npmignore ├── index.html ├── LICENSE ├── package.json ├── lumina.config.js ├── server.js ├── app.js ├── bin └── index.js └── README.md /assets/css/pageStyles/test.css: -------------------------------------------------------------------------------- 1 | /* CSS for Test Page */ 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Lumina 2 | .vscode 3 | node_modules 4 | /.env 5 | -------------------------------------------------------------------------------- /assets/images/lumina-icon.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kiarashAlizadeh/create-lumina/HEAD/assets/images/lumina-icon.webp -------------------------------------------------------------------------------- /api/middleware/02-loggerTest.js: -------------------------------------------------------------------------------- 1 | export const requestLogger = () => (req, res, next) => { 2 | // unComment to see in action 3 | console.log('< -- middleware logger 01 -- >'); 4 | next(); 5 | }; 6 | -------------------------------------------------------------------------------- /api/middleware/03-loggerTest.js: -------------------------------------------------------------------------------- 1 | export const requestLogger = () => (req, res, next) => { 2 | // unComment to see in action 3 | // console.log('< -- middleware logger 03 -- >'); 4 | next(); 5 | }; 6 | -------------------------------------------------------------------------------- /components/layout/Loader.js: -------------------------------------------------------------------------------- 1 | // Loader Component 2 | async function Loader() { 3 | return ` 4 |
5 |
6 |

7 | LUMINA 8 |

9 | 10 |
11 |
12 | `; 13 | } 14 | 15 | export default Loader; 16 | -------------------------------------------------------------------------------- /api/middleware/04-requestLogger.js: -------------------------------------------------------------------------------- 1 | export const requestLogger = () => (req, res, next) => { 2 | // unComment to see in action 3 | // console.log('< -- middleware logger 01 -- >'); 4 | // console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`); 5 | // console.log('Headers:', req.headers); 6 | // console.log('Body:', req.body); 7 | next(); 8 | }; 9 | -------------------------------------------------------------------------------- /routes.js: -------------------------------------------------------------------------------- 1 | // Define routes 2 | const mainRoutes = [ 3 | { path: '/', component: 'HomePage' }, 4 | { path: '/test', component: 'TestPage', css: ['test', 'testSass'] }, 5 | ]; 6 | 7 | // notFound Page route 8 | const notFoundRoute = { 9 | path: '/404', 10 | component: 'NotFoundPage', 11 | css: ['notFound'], 12 | }; 13 | 14 | export { mainRoutes, notFoundRoute }; 15 | -------------------------------------------------------------------------------- /assets/css/pageStyles/testSass.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sourceRoot":"","sources":["../sass/testSass.scss"],"names":[],"mappings":"AAAA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;AAGA;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAKJ;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAKJ;EACE;EACA;EACA;;AAEA;EACE;EACA;;AAKJ;EACE;EACA;EACA;EACA;;AAIF;EACE;EACA;EACA;;AAEA;EACE;;AAKJ;EACE;EACA;;AAGF;EACE;EACA;;AAIF;EACE;;AAGF;EACE;IACE;;EAEF;IACE;;EAEF;IACE","file":"testSass.css"} -------------------------------------------------------------------------------- /components/layout/Layout.js: -------------------------------------------------------------------------------- 1 | import Header from './Header.js'; 2 | import Footer from './Footer.js'; 3 | 4 | async function Layout(PageContent) { 5 | // load header and footer from header and footer components 6 | const HeaderContent = await Header(); 7 | const FooterContent = await Footer(); 8 | 9 | return `${HeaderContent}
${PageContent}
${FooterContent}`; 10 | // return '2'; 11 | } 12 | 13 | export default Layout; 14 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | package-lock.json 4 | 5 | # Testing 6 | test 7 | coverage 8 | *.test.js 9 | *.spec.js 10 | 11 | # Development files 12 | .git 13 | .gitignore 14 | .env 15 | .env.* 16 | .npmrc 17 | .eslintrc 18 | .prettier* 19 | .vscode 20 | .idea 21 | 22 | # Logs 23 | logs 24 | *.log 25 | npm-debug.log* 26 | 27 | # Build directories 28 | dist 29 | build 30 | .cache 31 | 32 | # OS generated files 33 | .DS_Store 34 | .DS_Store? 35 | ._* 36 | .Spotlight-V100 37 | .Trashes 38 | ehthumbs.db 39 | Thumbs.db 40 | -------------------------------------------------------------------------------- /components/pages/TestPage.js: -------------------------------------------------------------------------------- 1 | // Component for Test Page 2 | async function TestPage() { 3 | // set page title 4 | const title = 'Test | LUMINA'; 5 | document.title = title; 6 | 7 | // dynamic id in url 8 | const id = window.location.toString().split('#')[1]; 9 | 10 | if (id) { 11 | const res = await fetch( 12 | `http://localhost:3000/api/user/${id}?api_token=tk_live_abc123` 13 | ); 14 | 15 | const data = await res.json(); 16 | console.log('data: ', data); 17 | } 18 | 19 | // other JavaScript Logics 20 | 21 | // return page's html 22 | return ` 23 |
24 | Page Id is: ${id} 25 |
26 | `; 27 | } 28 | 29 | export default TestPage; 30 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | LUMINA 19 | 20 | 21 |
22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /api/models/User.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | const userSchema = new mongoose.Schema({ 4 | name: { 5 | type: String, 6 | required: true, 7 | }, 8 | familyName: { 9 | type: String, 10 | required: true, 11 | }, 12 | userName: { 13 | type: String, 14 | required: true, 15 | }, 16 | email: { 17 | type: String, 18 | required: true, 19 | }, 20 | birthDate: { 21 | type: String, 22 | required: true, 23 | }, 24 | pass: { 25 | type: String, 26 | required: true, 27 | }, 28 | gender: { 29 | type: String, 30 | required: true, 31 | }, 32 | phone: { 33 | type: String, 34 | required: true, 35 | }, 36 | bio: { 37 | type: String, 38 | default: '', 39 | }, 40 | role: { 41 | type: String, 42 | default: 'USER', 43 | }, 44 | createdAt: { 45 | type: Date, 46 | default: Date.now(), 47 | immutable: true, 48 | }, 49 | }); 50 | 51 | export default mongoose.model('User', userSchema); 52 | -------------------------------------------------------------------------------- /components/module/home/Comments.js: -------------------------------------------------------------------------------- 1 | function Comments() { 2 | return ` 3 | 4 |
5 |

What Our Users Say

6 |
7 |
8 |

"LUMINA has revolutionized the way we build SPAs. It's fast, intuitive, and the results are stunning!"

9 |

- Sarah Johnson, Frontend Developer

10 |
11 |
12 |

"I've never been able to create such beautiful and responsive SPAs so quickly. LUMINA is a game-changer!"

13 |

- Michael Chen, UX Designer

14 |
15 |
16 |
17 | `; 18 | } 19 | 20 | export default Comments; 21 | -------------------------------------------------------------------------------- /components/pages/NotFoundPage.js: -------------------------------------------------------------------------------- 1 | // Component for not found Page 2 | async function NotFoundPage() { 3 | // set page title 4 | const title = '404 | LUMINA'; 5 | document.title = title; 6 | 7 | // other JavaScript Logics 8 | 9 | // return page's html 10 | return ` 11 |
12 | Whoops! 13 |

404

14 |

Page Not Found

15 |

Looks like this page went on vacation.

16 |

The page you're looking for doesn't exist or has been moved.

17 |
18 | Go Back Home 19 |
20 |
21 | `; 22 | } 23 | 24 | export default NotFoundPage; 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Kiarash Alizadeh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "create-lumina", 3 | "version": "1.3.4", 4 | "description": "Lumina is an fullStack SPA maker", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "node server.js", 8 | "dev": "concurrently \"nodemon server.js\" \"npx @tailwindcss/cli -i ./assets/css/index.css -o ./assets/css/tailwindOutput.css --watch\" \"sass --watch assets/css/sass:assets/css/pageStyles\"" 9 | }, 10 | "bin": { 11 | "create-lumina": "bin/index.js" 12 | }, 13 | "homepage": "https://github.com/kiarashAlizadeh/create-lumina", 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/kiarashAlizadeh/create-lumina.git" 17 | }, 18 | "keywords": [ 19 | "frontend", 20 | "backend", 21 | "dev-server", 22 | "fullstack", 23 | "lumina" 24 | ], 25 | "type": "module", 26 | "author": "Kiarash Alizadeh", 27 | "license": "MIT", 28 | "files": [ 29 | "api", 30 | "assets", 31 | "components", 32 | "app.js", 33 | "index.html", 34 | "server.js", 35 | "LICENSE", 36 | "lumina.config.js", 37 | "README.md", 38 | "routes.js" 39 | ], 40 | "dependencies": { 41 | "cookie-parser": "^1.4.7", 42 | "cors": "^2.8.5", 43 | "dotenv": "^16.4.7", 44 | "express": "^4.21.2", 45 | "fs": "^0.0.1-security", 46 | "mongoose": "^8.9.5", 47 | "path": "^0.12.7" 48 | }, 49 | "devDependencies": { 50 | "@tailwindcss/cli": "^4.0.0", 51 | "concurrently": "^9.1.2", 52 | "nodemon": "^3.1.9", 53 | "sass": "^1.83.4", 54 | "tailwindcss": "^4.0.0" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /components/layout/Footer.js: -------------------------------------------------------------------------------- 1 | // Component for footer 2 | async function Footer() { 3 | return ` 4 | 35 | `; 36 | } 37 | 38 | export default Footer; 39 | -------------------------------------------------------------------------------- /components/module/home/FAQ.js: -------------------------------------------------------------------------------- 1 | function FAQ() { 2 | return ` 3 |
4 |

Frequently Asked Questions

5 |
6 |
7 |

What is LUMINA?

8 |

LUMINA is a powerful SPA (Single Page Application) maker that allows developers and designers to create fast, beautiful, and responsive web applications with ease.

9 |
10 |
11 |

Do I need coding experience to use LUMINA?

12 |

While coding experience can be helpful, LUMINA is designed to be user-friendly for both developers and non-developers alike. Our intuitive interface and drag-and-drop functionality make it easy for anyone to create stunning SPAs.

13 |
14 |
15 |

Can I export my projects?

16 |

Yes, LUMINA allows you to export your projects as standard web files, giving you full control over your code and the ability to host your SPA anywhere you choose.

17 |
18 |
19 |
`; 20 | } 21 | 22 | export default FAQ; 23 | -------------------------------------------------------------------------------- /lumina.config.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | async function DBConnect() { 4 | try { 5 | const DATABASE_URL = process.env.DATABASE_URL; 6 | if (!DATABASE_URL) { 7 | console.error('DATABASE_URL is not defined in .env file'); 8 | return; 9 | } 10 | 11 | // Connecting to the database 12 | await mongoose.connect(DATABASE_URL); 13 | 14 | console.log('Successfully connected to the Database!'); 15 | } catch (error) { 16 | console.error('Failed to connect to the Database:', error); 17 | } 18 | } 19 | 20 | // lumina port 21 | const port = process.env.PORT || 3000; 22 | 23 | // Store valid API tokens and their permissions 24 | // In a real application, these should be stored in a database 25 | const API_TOKENS = new Map([ 26 | [ 27 | 'tk_live_abc123', 28 | { 29 | name: 'Production App', 30 | permissions: ['read', 'write'], 31 | isActive: true, 32 | rateLimit: 1000, // requests per hour 33 | }, 34 | ], 35 | [ 36 | 'tk_test_xyz789', 37 | { 38 | name: 'Test App', 39 | permissions: ['read'], 40 | isActive: true, 41 | rateLimit: 100, 42 | }, 43 | ], 44 | ]); 45 | 46 | // # Request without token 47 | // curl http://localhost:3000/api/data 48 | 49 | // # Request with valid token 50 | // curl -H "X-API-Token: tk_live_abc123" http://localhost:3000/api/data 51 | 52 | // # Request with invalid token 53 | // curl -H "X-API-Token: invalid_token" http://localhost:3000/api/data 54 | 55 | // # Request with token as query parameter 56 | // curl "http://localhost:3000/api/data?api_token=tk_live_abc123" 57 | 58 | export { port, DBConnect, API_TOKENS }; 59 | -------------------------------------------------------------------------------- /assets/css/pageStyles/notFound.css: -------------------------------------------------------------------------------- 1 | /* CSS for NotFound Page */ 2 | main { 3 | margin-right: 18px; 4 | margin-left: 18px; 5 | } 6 | 7 | .not-found-page { 8 | display: flex; 9 | flex-direction: column; 10 | align-items: center; 11 | justify-content: center; 12 | text-align: center; 13 | min-height: 65vh; 14 | padding: 2rem; 15 | background-color: #f4f4f9; 16 | max-width: 450px; 17 | margin-right: auto; 18 | margin-left: auto; 19 | margin-bottom: 40px; 20 | border-radius: 25px; 21 | } 22 | 23 | .not-found-message { 24 | font-size: 2rem; 25 | font-weight: bold; 26 | color: #ff4d4d; 27 | } 28 | 29 | .not-found-title { 30 | font-size: 2.5rem; 31 | margin: 1rem 0; 32 | color: #222; 33 | } 34 | 35 | .not-found-description { 36 | font-size: 1.2rem; 37 | margin: 1rem 0 1.5rem; 38 | color: #555; 39 | } 40 | 41 | .back-home { 42 | display: inline-block; 43 | padding: 0.8rem 1.5rem; 44 | font-size: 1rem; 45 | font-weight: bold; 46 | color: #fff; 47 | background-color: #007bff; 48 | text-decoration: none; 49 | border-radius: 5px; 50 | transition: background-color 0.3s ease, transform 0.2s ease; 51 | } 52 | 53 | .back-home:hover { 54 | background-color: #0056b3; 55 | transform: translateY(-2px); 56 | } 57 | 58 | .back-home:active { 59 | transform: translateY(0); 60 | } 61 | 62 | /* Responsive for smaller screens */ 63 | @media (max-width: 768px) { 64 | .not-found-page { 65 | padding: 1.5rem; 66 | } 67 | 68 | .not-found-title { 69 | font-size: 2rem; 70 | } 71 | 72 | .not-found-description { 73 | font-size: 1rem; 74 | } 75 | 76 | .back-home { 77 | padding: 0.7rem 1.2rem; 78 | font-size: 0.9rem; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /assets/css/pageStyles/testSass.css: -------------------------------------------------------------------------------- 1 | .button { 2 | display: inline-block; 3 | padding: 12px 24px; 4 | font-size: 16px; 5 | font-weight: bold; 6 | text-align: center; 7 | border-radius: 8px; 8 | cursor: pointer; 9 | transition: all 0.3s ease-in-out; 10 | } 11 | .button.primary { 12 | background-color: #3498db; 13 | color: white; 14 | border: 2px solid #2980b9; 15 | } 16 | .button.primary:hover { 17 | background-color: #2980b9; 18 | box-shadow: 0 4px 10px rgba(41, 128, 185, 0.3); 19 | } 20 | .button.secondary { 21 | background-color: #2ecc71; 22 | color: white; 23 | border: 2px solid #27ae60; 24 | } 25 | .button.secondary:hover { 26 | background-color: #27ae60; 27 | box-shadow: 0 4px 10px rgba(39, 174, 96, 0.3); 28 | } 29 | .button.danger { 30 | background-color: #e74c3c; 31 | color: white; 32 | border: 2px solid #c0392b; 33 | } 34 | .button.danger:hover { 35 | background-color: #c0392b; 36 | box-shadow: 0 4px 10px rgba(192, 57, 43, 0.3); 37 | } 38 | .button.disabled { 39 | background-color: #bdc3c7; 40 | color: #7f8c8d; 41 | border: 2px solid #95a5a6; 42 | cursor: not-allowed; 43 | } 44 | .button.icon { 45 | display: flex; 46 | align-items: center; 47 | gap: 8px; 48 | } 49 | .button.icon i { 50 | font-size: 20px; 51 | } 52 | .button.small { 53 | padding: 8px 16px; 54 | font-size: 14px; 55 | } 56 | .button.large { 57 | padding: 16px 32px; 58 | font-size: 18px; 59 | } 60 | .button.pulse { 61 | animation: pulse 1.5s infinite; 62 | } 63 | @keyframes pulse { 64 | 0% { 65 | transform: scale(1); 66 | } 67 | 50% { 68 | transform: scale(1.05); 69 | } 70 | 100% { 71 | transform: scale(1); 72 | } 73 | } 74 | 75 | /*# sourceMappingURL=testSass.css.map */ 76 | -------------------------------------------------------------------------------- /components/layout/Header.js: -------------------------------------------------------------------------------- 1 | // Component for header 2 | async function Header() { 3 | let eventAdded = false; // Flag to check if the event listener is already added 4 | let isActive = false; // State to track whether the menu is open or not 5 | 6 | // Function to handle menu toggle 7 | window.menuToggleHandler = () => { 8 | const menuToggle = document.querySelector('.menu-toggle'); 9 | const navLinks = document.querySelector('.nav-links'); 10 | 11 | if (menuToggle && navLinks) { 12 | // For the first click, toggle the state and menu 13 | if (!eventAdded) { 14 | isActive = !isActive; // Toggle the state 15 | navLinks.classList.toggle('active', isActive); 16 | 17 | // Add event listener for subsequent clicks 18 | menuToggle.addEventListener('click', () => { 19 | isActive = !isActive; // Toggle the state 20 | navLinks.classList.toggle('active', isActive); 21 | }); 22 | 23 | eventAdded = true; // Mark event as added 24 | } 25 | } 26 | }; 27 | 28 | const testId = '507f1f77bcf86cd799439011'; 29 | 30 | return ` 31 |
32 | 47 |
48 | `; 49 | } 50 | 51 | export default Header; 52 | -------------------------------------------------------------------------------- /assets/css/sass/testSass.scss: -------------------------------------------------------------------------------- 1 | .button { 2 | display: inline-block; 3 | padding: 12px 24px; 4 | font-size: 16px; 5 | font-weight: bold; 6 | text-align: center; 7 | border-radius: 8px; 8 | cursor: pointer; 9 | transition: all 0.3s ease-in-out; 10 | 11 | // Primary button style 12 | &.primary { 13 | background-color: #3498db; 14 | color: white; 15 | border: 2px solid #2980b9; 16 | 17 | &:hover { 18 | background-color: #2980b9; 19 | box-shadow: 0 4px 10px rgba(41, 128, 185, 0.3); 20 | } 21 | } 22 | 23 | // Secondary button style 24 | &.secondary { 25 | background-color: #2ecc71; 26 | color: white; 27 | border: 2px solid #27ae60; 28 | 29 | &:hover { 30 | background-color: #27ae60; 31 | box-shadow: 0 4px 10px rgba(39, 174, 96, 0.3); 32 | } 33 | } 34 | 35 | // Danger button style 36 | &.danger { 37 | background-color: #e74c3c; 38 | color: white; 39 | border: 2px solid #c0392b; 40 | 41 | &:hover { 42 | background-color: #c0392b; 43 | box-shadow: 0 4px 10px rgba(192, 57, 43, 0.3); 44 | } 45 | } 46 | 47 | // Disabled button style 48 | &.disabled { 49 | background-color: #bdc3c7; 50 | color: #7f8c8d; 51 | border: 2px solid #95a5a6; 52 | cursor: not-allowed; 53 | } 54 | 55 | // Button with icon 56 | &.icon { 57 | display: flex; 58 | align-items: center; 59 | gap: 8px; 60 | 61 | i { 62 | font-size: 20px; 63 | } 64 | } 65 | 66 | // Button sizes 67 | &.small { 68 | padding: 8px 16px; 69 | font-size: 14px; 70 | } 71 | 72 | &.large { 73 | padding: 16px 32px; 74 | font-size: 18px; 75 | } 76 | 77 | // Button with animation 78 | &.pulse { 79 | animation: pulse 1.5s infinite; 80 | } 81 | 82 | @keyframes pulse { 83 | 0% { 84 | transform: scale(1); 85 | } 86 | 50% { 87 | transform: scale(1.05); 88 | } 89 | 100% { 90 | transform: scale(1); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /api/middleware/index.js: -------------------------------------------------------------------------------- 1 | // middleware/index.js 2 | import fs from 'fs/promises'; 3 | import path from 'path'; 4 | import { fileURLToPath } from 'url'; 5 | 6 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 7 | 8 | // Function to load all middlewares dynamically 9 | export const loadMiddlewares = async (app) => { 10 | try { 11 | // Get all files in the middleware directory 12 | const files = await fs.readdir(__dirname); 13 | 14 | // Sort files to ensure consistent loading order 15 | const middlewareFiles = files 16 | .filter((file) => file !== 'index.js' && file.endsWith('.js')) 17 | .sort(); 18 | 19 | // Load each middleware file 20 | for (const file of middlewareFiles) { 21 | const middlewareModule = await import(`./${file}`); 22 | const middlewareName = path.basename(file, '.js'); 23 | 24 | // If middleware exports multiple functions 25 | if (typeof middlewareModule === 'object') { 26 | Object.entries(middlewareModule).forEach(([key, middleware]) => { 27 | if (typeof middleware === 'function') { 28 | // Check if it's a middleware that needs configuration 29 | if (middleware.length === 0) { 30 | app.use(middleware()); 31 | } else { 32 | // Store middleware in app.locals for later use 33 | if (!app.locals.middlewares) { 34 | app.locals.middlewares = {}; 35 | } 36 | app.locals.middlewares[key] = middleware; 37 | } 38 | } 39 | }); 40 | } 41 | // If middleware exports a single function 42 | else if (typeof middlewareModule.default === 'function') { 43 | const middleware = middlewareModule.default; 44 | if (middleware.length === 0) { 45 | app.use(middleware()); 46 | } else { 47 | if (!app.locals.middlewares) { 48 | app.locals.middlewares = {}; 49 | } 50 | app.locals.middlewares[middlewareName] = middleware; 51 | } 52 | } 53 | } 54 | 55 | console.log('Middlewares loaded successfully'); 56 | } catch (error) { 57 | console.error('Error loading middlewares:', error); 58 | throw error; 59 | } 60 | }; 61 | -------------------------------------------------------------------------------- /components/module/home/Price.js: -------------------------------------------------------------------------------- 1 | function Price() { 2 | const plans = [ 3 | { 4 | name: 'Starter', 5 | price: '$19', 6 | priceSuffix: '/month', 7 | features: ['5 Projects', 'Basic Components', 'Community Support'], 8 | buttonText: 'Choose Plan', 9 | }, 10 | { 11 | name: 'Pro', 12 | price: '$49', 13 | priceSuffix: '/month', 14 | features: [ 15 | 'Unlimited Projects', 16 | 'Advanced Components', 17 | 'Priority Support', 18 | 'Custom Themes', 19 | ], 20 | buttonText: 'Choose Plan', 21 | }, 22 | { 23 | name: 'Enterprise', 24 | price: 'Custom', 25 | priceSuffix: '', 26 | features: [ 27 | 'All Pro Features', 28 | 'Dedicated Support', 29 | 'Custom Development', 30 | 'SLA', 31 | ], 32 | buttonText: 'Contact Us', 33 | }, 34 | ]; 35 | 36 | return ` 37 |
38 |

39 | Choose Your Plan 40 |

41 |
42 | ${plans 43 | .map( 44 | (plan, index) => 45 | `
50 |

51 | ${plan.name} 52 |

53 |

54 | ${plan.price} 55 | ${plan.priceSuffix} 56 |

57 |
    58 | ${plan.features 59 | .map( 60 | (feature, i) => 61 | `
  • 62 | ✅ ${feature} 63 |
  • ` 64 | ) 65 | .join('')} 66 |
67 | 71 | ${plan.buttonText} 72 | 73 |
` 74 | ) 75 | .join('')} 76 |
77 |
78 | `; 79 | } 80 | 81 | export default Price; 82 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import path from 'path'; 3 | import { fileURLToPath } from 'url'; 4 | import { port, DBConnect } from './lumina.config.js'; 5 | import cors from 'cors'; 6 | import cookieParser from 'cookie-parser'; 7 | import fs from 'fs'; 8 | import { loadMiddlewares } from './api/middleware/index.js'; 9 | import dotenv from 'dotenv'; 10 | 11 | dotenv.config(); 12 | 13 | // Get the directory path of the current file 14 | const __dirname = path.dirname(fileURLToPath(import.meta.url)); 15 | 16 | // Creating variable to configure WebService 17 | const app = express(); 18 | 19 | // Connect to database 20 | DBConnect(); 21 | 22 | // Enable CORS (Cross-Origin Resource Sharing) with credentials, allowing requests from frontend 23 | app.use( 24 | cors({ 25 | origin: `http://localhost:${port}`, // Frontend origin 26 | credentials: true, 27 | }) 28 | ); 29 | 30 | // Middleware to parse incoming request bodies as JSON 31 | app.use(express.json()); 32 | // Middleware to parse cookies from incoming requests 33 | app.use(cookieParser()); 34 | 35 | // Load all middlewares dynamically 36 | await loadMiddlewares(app); 37 | 38 | // Serve static files from the root directory (frontend part) 39 | app.use(express.static(__dirname)); // Serve static files from the root of the project 40 | 41 | // Dynamically load routes for API under /api/ 42 | const routePath = path.join(__dirname, 'api', 'routes'); 43 | fs.readdirSync(routePath).forEach((file) => { 44 | if (file.endsWith('.js')) { 45 | const endpoint = file.split('.')[0]; // Extract the endpoint from the file name 46 | import(`./api/routes/${file}`) 47 | .then((route) => { 48 | app.use(`/api/${endpoint}`, route.default); // Register the route dynamically 49 | }) 50 | .catch((error) => { 51 | console.error(`Failed to load route: ${file}`, error); 52 | }); 53 | } 54 | }); 55 | 56 | // Fallback route for all non-API requests (serve index.html) 57 | app.get('*', (req, res, next) => { 58 | // Ensure we are not matching /api routes before sending index.html 59 | if (!req.url.startsWith('/api')) { 60 | res.sendFile(path.join(__dirname, 'index.html')); // Serve the frontend (index.html) 61 | } else { 62 | // If it's an API route, let the API handler handle it 63 | next(); 64 | } 65 | }); 66 | 67 | // Start WebService (both frontend and backend on the same port) 68 | app.listen(port, () => { 69 | console.log( 70 | `Lumina is running on port ${port} | URL is: http://localhost:${port}/` 71 | ); 72 | }); 73 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | // Get DOM elements 2 | const root = document.getElementById('lumina__root'); 3 | 4 | // import lumina app routes 5 | import { mainRoutes, notFoundRoute } from './routes.js'; 6 | 7 | // Import Layout and Loader components 8 | import Layout from './components/layout/Layout.js'; 9 | import Loader from './components/layout/Loader.js'; 10 | 11 | // Wait for DOMContentLoaded event before executing the script 12 | document.addEventListener('DOMContentLoaded', () => { 13 | // Initial load of the content based on the current URL path 14 | navigateToPage(window.location.pathname); 15 | 16 | // Function to navigate to a page 17 | async function navigateToPage(path) { 18 | // find route object for this path 19 | const route = findRoute(path); 20 | 21 | // loadCssFile for this path 22 | loadCssFile(route.css); 23 | 24 | // Import the module dynamically based on the route 25 | await importModule(route.component); 26 | } 27 | 28 | // Function to find the route for a given path 29 | function findRoute(path) { 30 | // If there is a '#' in the path, ignore everything after it 31 | const cleanPath = path.split('#')[0]; 32 | 33 | // Find the route by comparing the cleaned path 34 | const matchedRoute = mainRoutes.find((route) => route.path === cleanPath); 35 | 36 | // Check if the matched route exists, otherwise return the notFoundRoute 37 | return matchedRoute || notFoundRoute; 38 | } 39 | 40 | // Function to load CSS file dynamically 41 | function loadCssFile(cssFiles) { 42 | // Remove all existing dynamically added CSS files 43 | document 44 | .querySelectorAll('link[rel="stylesheet"].dynamic-style') 45 | .forEach((link) => link.remove()); 46 | 47 | if (Array.isArray(cssFiles) && cssFiles.length > 0) { 48 | cssFiles.forEach((cssFile) => { 49 | const cssPath = `./assets/css/pageStyles/${cssFile}.css`; 50 | 51 | // Create and append new CSS file 52 | const style = document.createElement('link'); 53 | style.className = 'dynamic-style'; // Use class instead of ID to support multiple styles 54 | style.rel = 'stylesheet'; 55 | style.href = cssPath; 56 | document.head.appendChild(style); 57 | }); 58 | } 59 | } 60 | 61 | // Function to import module dynamically 62 | async function importModule(moduleFile) { 63 | // Show loader 64 | const loaderContent = await Loader(); 65 | root.innerHTML = loaderContent; 66 | 67 | // load the req page component 68 | const module = await import(`./components/pages/${moduleFile}.js`); 69 | 70 | // Dynamically call the function with the same name as the module file 71 | const PageContent = await (module[moduleFile] || module.default)(); 72 | 73 | const layout = await Layout(PageContent); 74 | 75 | // Set page content inside root container and remove loader 76 | root.innerHTML = layout; 77 | } 78 | 79 | // Handle navigation changes for spa 80 | document.addEventListener('click', (event) => { 81 | if ( 82 | event.target.tagName === 'A' && 83 | event.target.getAttribute('href').startsWith('/') 84 | ) { 85 | event.preventDefault(); 86 | const path = event.target.getAttribute('href'); 87 | navigateToPage(path); 88 | history.pushState(null, null, path); 89 | } 90 | }); 91 | 92 | // Handle back and forward buttons for spa 93 | window.addEventListener('popstate', () => { 94 | navigateToPage(window.location.pathname); 95 | }); 96 | }); 97 | -------------------------------------------------------------------------------- /api/middleware/01-apiTokenAuth.js: -------------------------------------------------------------------------------- 1 | import { API_TOKENS } from '../../lumina.config.js'; 2 | 3 | // Token request counter for rate limiting 4 | const tokenRequests = new Map(); 5 | 6 | // Reset counters every hour 7 | setInterval(() => { 8 | tokenRequests.clear(); 9 | }, 3600000); // 1 hour 10 | 11 | export const apiTokenAuth = () => (req, res, next) => { 12 | // Check if the request path starts with /api 13 | if (!req.path.startsWith('/api')) { 14 | return next(); // Skip this middleware for other paths 15 | } 16 | 17 | // Get token from header 18 | const apiKey = req.header('X-API-Token') || req.query.api_token; 19 | 20 | // Check if token exists 21 | if (!apiKey) { 22 | console.log('UnAuthorized access!'); 23 | 24 | return res.status(401).json({ 25 | error: 'API token is required', 26 | code: 'TOKEN_REQUIRED', 27 | }); 28 | } 29 | 30 | // Get token details 31 | const tokenDetails = API_TOKENS.get(apiKey); 32 | 33 | // Check if token is valid 34 | if (!tokenDetails) { 35 | console.log('UnAuthorized access!'); 36 | 37 | return res.status(401).json({ 38 | error: 'Invalid API token', 39 | code: 'INVALID_TOKEN', 40 | }); 41 | } 42 | 43 | // Check if token is active 44 | if (!tokenDetails.isActive) { 45 | console.log('UnAuthorized access!'); 46 | 47 | return res.status(403).json({ 48 | error: 'API token is inactive', 49 | code: 'INACTIVE_TOKEN', 50 | }); 51 | } 52 | 53 | // Rate limiting check 54 | const currentRequests = tokenRequests.get(apiKey) || 0; 55 | if (currentRequests >= tokenDetails.rateLimit) { 56 | console.log('UnAuthorized access!'); 57 | 58 | return res.status(429).json({ 59 | error: 'Rate limit exceeded', 60 | code: 'RATE_LIMIT_EXCEEDED', 61 | }); 62 | } 63 | 64 | // Increment request counter 65 | tokenRequests.set(apiKey, currentRequests + 1); 66 | 67 | // Check permission for specific endpoints (optional) 68 | const requestedOperation = getOperationType(req.method); 69 | if (!tokenDetails.permissions.includes(requestedOperation)) { 70 | console.log('UnAuthorized access!'); 71 | 72 | return res.status(403).json({ 73 | error: 'Insufficient permissions', 74 | code: 'INSUFFICIENT_PERMISSIONS', 75 | }); 76 | } 77 | 78 | // Attach token info to request object 79 | req.apiToken = { 80 | ...tokenDetails, 81 | key: apiKey, 82 | }; 83 | 84 | next(); 85 | }; 86 | 87 | // Helper function to map HTTP methods to operation types 88 | function getOperationType(method) { 89 | switch (method.toUpperCase()) { 90 | case 'GET': 91 | return 'read'; 92 | case 'POST': 93 | case 'PUT': 94 | case 'PATCH': 95 | case 'DELETE': 96 | return 'write'; 97 | default: 98 | return 'read'; 99 | } 100 | } 101 | 102 | // Optional: Middleware for specific permission checks 103 | export const requireApiPermission = (permission) => (req, res, next) => { 104 | if (!req.apiToken) { 105 | console.log('UnAuthorized access!'); 106 | 107 | return res.status(401).json({ 108 | error: 'API token authentication required', 109 | code: 'TOKEN_REQUIRED', 110 | }); 111 | } 112 | 113 | if (!req.apiToken.permissions.includes(permission)) { 114 | console.log('UnAuthorized access!'); 115 | 116 | return res.status(403).json({ 117 | error: `Permission '${permission}' required`, 118 | code: 'PERMISSION_REQUIRED', 119 | }); 120 | } 121 | 122 | next(); 123 | }; 124 | -------------------------------------------------------------------------------- /components/pages/HomePage.js: -------------------------------------------------------------------------------- 1 | import Comments from '../module/home/Comments.js'; 2 | import FAQ from '../module/home/FAQ.js'; 3 | import Price from '../module/home/Price.js'; 4 | 5 | // Component for Home Page 6 | async function HomePage() { 7 | // set page title 8 | const title = 'Home | LUMINA'; 9 | document.title = title; 10 | 11 | // Feature description for LUMINA 12 | const features = [ 13 | { 14 | title: 'Full-Stack SPA Builder', 15 | description: 16 | 'LUMINA is a complete solution for building Single Page Applications, handling both the frontend and backend seamlessly.', 17 | }, 18 | { 19 | title: 'Dynamic Routing', 20 | description: 21 | 'Easily implement dynamic routing without page reload, providing a smoother user experience in your SPA.', 22 | }, 23 | { 24 | title: 'Real-Time Data Handling', 25 | description: 26 | 'Handle real-time data processing with integrated WebSocket support, making your app highly interactive and responsive.', 27 | }, 28 | { 29 | title: 'Modular Backend', 30 | description: 31 | 'Create and manage your backend logic with modular components, making backend development faster and more organized.', 32 | }, 33 | { 34 | title: 'Customizable UI Components', 35 | description: 36 | 'LUMINA provides customizable UI components that you can easily integrate into your app, reducing development time.', 37 | }, 38 | { 39 | title: 'Powerful Database Integration', 40 | description: 41 | 'Connect your application to databases seamlessly, supporting both SQL and NoSQL integrations for scalable applications.', 42 | }, 43 | ]; 44 | 45 | // return page's html 46 | return ` 47 |
48 |
49 |

Create Stunning SPAs with LUMINA

50 |

The next-generation SPA maker for modern web applications

51 | Get Started 52 |
53 | 54 |
55 | ${features 56 | .map( 57 | (feature) => ` 58 |
59 |

${feature.title}

60 |

${feature.description}

61 |
62 | ` 63 | ) 64 | .join('')} 65 |
66 | 67 |
68 |

See LUMINA in Action

69 |
70 |

Video Placeholder

71 |
72 |
73 | 74 | 75 | ${Comments()} 76 | 77 | ${Price()} 78 | 79 | ${FAQ()} 80 | 81 |
82 |

Ready to Illuminate Your Web Projects?

83 |

Join thousands of developers who trust LUMINA for their SPA needs

84 | Start Coding 85 |
86 |
87 | `; 88 | } 89 | 90 | export default HomePage; 91 | -------------------------------------------------------------------------------- /api/routes/user.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | const router = express.Router(); 3 | import User from '../models/User.js'; 4 | 5 | // Getting all 6 | router.get('/', async (req, res) => { 7 | try { 8 | const users = await User.find(); 9 | res.json(users); 10 | } catch (err) { 11 | res.status(500).json({ message: err.message }); 12 | } 13 | }); 14 | 15 | // Getting One 16 | router.get('/:id', async (req, res) => { 17 | try { 18 | const user = await User.findById(req.params.id); 19 | if (!user) { 20 | return res.status(404).json({ message: 'user not found' }); 21 | } 22 | res.json(user); 23 | } catch (err) { 24 | res.status(500).json({ message: err.message }); 25 | } 26 | }); 27 | 28 | // Creating one 29 | router.post('/', async (req, res) => { 30 | try { 31 | const { 32 | name, 33 | familyName, 34 | userName, 35 | email, 36 | birthDate, 37 | pass, 38 | gender, 39 | phone, 40 | favoriteTeam, 41 | } = req.body; 42 | 43 | if ( 44 | !name || 45 | !familyName || 46 | !userName || 47 | !email || 48 | !birthDate || 49 | !pass || 50 | !gender || 51 | !favoriteTeam || 52 | !phone 53 | ) { 54 | // handel if data is empty 55 | return res.status(400).json({ message: 'please enter valid data' }); 56 | } 57 | 58 | const existingEmail = await User.findOne({ email }); 59 | const existingPhone = await User.findOne({ phone }); 60 | const existingUserName = await User.findOne({ userName }); 61 | if (existingEmail) { 62 | return res.status(400).json({ message: 'user already exist' }); 63 | } 64 | if (existingPhone) { 65 | return res.status(400).json({ message: 'user already exist' }); 66 | } 67 | if (existingUserName) { 68 | return res.status(400).json({ message: 'user already exist' }); 69 | } 70 | 71 | const newUser = await User.create({ 72 | name, 73 | familyName, 74 | userName, 75 | email, 76 | birthDate, 77 | pass, 78 | gender, 79 | phone, 80 | favoriteTeam, 81 | }); 82 | 83 | res.status(201).json({ message: 'user created' }); 84 | } catch (err) { 85 | res.status(400).json({ message: err.message }); 86 | } 87 | }); 88 | 89 | // Updating One 90 | router.patch('/:id', async (req, res) => { 91 | try { 92 | const user = await User.findById(req.params.id); 93 | if (!user) { 94 | return res.status(404).json({ message: 'user not found' }); 95 | } 96 | 97 | // Update fields based on request body 98 | if (req.body.name) { 99 | user.name = req.body.name; 100 | } 101 | if (req.body.familyName) { 102 | user.familyName = req.body.familyName; 103 | } 104 | if (req.body.userName) { 105 | user.userName = req.body.userName; 106 | } 107 | if (req.body.email) { 108 | user.email = req.body.email; 109 | } 110 | if (req.body.bio === '' || req.body.bio) { 111 | user.bio = req.body.bio; 112 | } 113 | if (req.body.yourLink === '' || req.body.yourLink) { 114 | user.yourLink = req.body.yourLink; 115 | } 116 | if (req.body.link === '' || req.body.link) { 117 | user.link = req.body.link; 118 | } 119 | if (req.body.birthDate) { 120 | user.birthDate = req.body.birthDate; 121 | } 122 | if (req.body.pass) { 123 | const isValid = await bcrypt.compare(req.body.oldPass, user.pass); 124 | if (!isValid) { 125 | return res.status(400).json({ message: 'Old password is wrong!' }); 126 | } 127 | user.pass = req.body.pass; 128 | } 129 | if (req.body.gender) { 130 | user.gender = req.body.gender; 131 | } 132 | if (req.body.phone) { 133 | user.phone = req.body.phone; 134 | } 135 | if (req.body.favoriteTeam) { 136 | user.favoriteTeam = req.body.favoriteTeam; 137 | } 138 | if (req.body.predictions) { 139 | user.predictions.push(req.body.predictions); 140 | } 141 | if (req.body.role) { 142 | user.role = req.body.role; 143 | } 144 | 145 | await user.save(); 146 | res.status(201).json({ message: 'user Updated' }); 147 | } catch (err) { 148 | res.status(400).json({ message: err.message }); 149 | } 150 | }); 151 | 152 | // Deleting One 153 | router.delete('/:id', async (req, res) => { 154 | try { 155 | const user = await User.findByIdAndDelete(req.params.id); 156 | if (!user) { 157 | return res.status(404).json({ message: 'user not found' }); 158 | } 159 | res.json({ message: 'Deleted User' }); 160 | } catch (err) { 161 | res.status(500).json({ message: err.message }); 162 | } 163 | }); 164 | 165 | export default router; 166 | -------------------------------------------------------------------------------- /bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import fs from 'fs'; 4 | import path from 'path'; 5 | import { execSync } from 'child_process'; 6 | import { fileURLToPath } from 'url'; 7 | 8 | const __filename = fileURLToPath(import.meta.url); 9 | const __dirname = path.dirname(__filename); 10 | 11 | // Get the project name from command line arguments 12 | const projectName = process.argv[2] || 'my-lumina-app'; 13 | 14 | // Define paths 15 | const currentDir = process.cwd(); 16 | const targetDir = path.join(currentDir, projectName); 17 | const sourceDir = path.join(__dirname, '..'); 18 | 19 | console.log('Welcome to lumina! 🫡'); 20 | 21 | if (!projectName) { 22 | console.error('⚠️ Please specify the project name:'); 23 | console.error(' npx create-lumina-app '); 24 | process.exit(1); 25 | } 26 | 27 | // Create target directory 28 | fs.mkdirSync(targetDir, { recursive: true }); 29 | 30 | // Template for package.json 31 | const packageJson = { 32 | name: projectName, 33 | version: '1.0.0', 34 | description: 'Lumina is an fullStack SPA maker', 35 | main: 'index.js', 36 | scripts: { 37 | start: 'node server.js', 38 | dev: 'concurrently "nodemon server.js" "npx @tailwindcss/cli -i ./assets/css/index.css -o ./assets/css/tailwindOutput.css --watch"', 39 | }, 40 | type: 'module', 41 | author: 'Kiarash Alizadeh', 42 | license: 'MIT', 43 | dependencies: { 44 | 'create-lumina': '^1.3.4', 45 | 'cookie-parser': '^1.4.7', 46 | cors: '^2.8.5', 47 | dotenv: '^16.4.7', 48 | express: '^4.21.2', 49 | fs: '^0.0.1-security', 50 | mongoose: '^8.9.5', 51 | path: '^0.12.7', 52 | }, 53 | devDependencies: { 54 | '@tailwindcss/cli': '^4.0.0', 55 | concurrently: '^9.1.2', 56 | nodemon: '^3.1.9', 57 | sass: '^1.83.4', 58 | tailwindcss: '^4.0.0', 59 | }, 60 | }; 61 | 62 | // Write package.json 63 | fs.writeFileSync( 64 | path.join(targetDir, 'package.json'), 65 | JSON.stringify(packageJson, null, 2) 66 | ); 67 | 68 | // Function to copy a directory recursively 69 | function copyDir(src, dest) { 70 | // Create destination directory 71 | fs.mkdirSync(dest, { recursive: true }); 72 | 73 | // Read source directory content 74 | const entries = fs.readdirSync(src, { withFileTypes: true }); 75 | 76 | for (const entry of entries) { 77 | const srcPath = path.join(src, entry.name); 78 | const destPath = path.join(dest, entry.name); 79 | 80 | if (entry.isDirectory()) { 81 | // Recursively copy directory 82 | copyDir(srcPath, destPath); 83 | } else { 84 | // Copy file 85 | fs.copyFileSync(srcPath, destPath); 86 | } 87 | } 88 | } 89 | 90 | // Function to copy specific folders and files 91 | async function copyTemplateFiles() { 92 | // Define what to copy 93 | const foldersToCopy = ['api', 'assets', 'components']; 94 | const filesToCopy = [ 95 | 'app.js', 96 | 'index.html', 97 | 'server.js', 98 | 'LICENSE', 99 | 'lumina.config.js', 100 | 'README.md', 101 | 'routes.js', 102 | ]; 103 | 104 | try { 105 | // Copy folders 106 | for (const folder of foldersToCopy) { 107 | const srcFolder = path.join(sourceDir, folder); 108 | const destFolder = path.join(targetDir, folder); 109 | 110 | if (fs.existsSync(srcFolder)) { 111 | copyDir(srcFolder, destFolder); 112 | } else { 113 | console.warn(`⚠️ Warning: Could not find folder: ${folder}`); 114 | } 115 | } 116 | 117 | // Copy individual files 118 | for (const file of filesToCopy) { 119 | const srcFile = path.join(sourceDir, file); 120 | const destFile = path.join(targetDir, file); 121 | 122 | if (fs.existsSync(srcFile)) { 123 | fs.copyFileSync(srcFile, destFile); 124 | } else { 125 | console.warn(`⚠️ Warning: Could not find file: ${file}`); 126 | } 127 | } 128 | 129 | // Create .env file 130 | const envContent = 131 | 'PORT="3000"\nDATABASE_URL="mongodb://127.0.0.1:27017/lumina"\n'; 132 | fs.writeFileSync(path.join(targetDir, '.env'), envContent); 133 | 134 | return true; 135 | } catch (error) { 136 | console.error('⚠️ Error copying template files:', error); 137 | return false; 138 | } 139 | } 140 | 141 | // Main execution 142 | try { 143 | console.log(`🏗️ Creating new Lumina project in ${targetDir}...`); 144 | 145 | // Copy template files 146 | const success = await copyTemplateFiles(); 147 | if (!success) { 148 | throw new Error('Failed to copy template files'); 149 | } 150 | 151 | // Install dependencies 152 | console.log('\n⏳ Installing dependencies...'); 153 | execSync(`cd ${projectName} && npm install`, { stdio: 'inherit' }); 154 | 155 | console.log('\n✨ Success! Your Lumina project is ready!'); 156 | console.log(`\n🏁 Created ${projectName} at ${targetDir}`); 157 | console.log('\nInside that directory, you can run these commands:'); 158 | console.log('\n✅ npm run dev'); 159 | console.log('\nStarts the development server.'); 160 | console.log('\nHappy coding with Lumina! 🎉'); 161 | } catch (error) { 162 | console.error('⚠️ Error creating project:', error); 163 | process.exit(1); 164 | } 165 | -------------------------------------------------------------------------------- /assets/css/index.css: -------------------------------------------------------------------------------- 1 | /* tailwind css config */ 2 | @import 'tailwindcss'; 3 | 4 | /* Main Styles */ 5 | 6 | /* Poppins Font From Google Fonts */ 7 | @import url('https://fonts.googleapis.com/css2?family=Poppins&display=swap'); 8 | 9 | /* Colors */ 10 | :root { 11 | --text-main: white; 12 | --text-main2: #215d3e; 13 | --text-secondary: white; 14 | } 15 | 16 | /* Css Reset */ 17 | /* * { 18 | margin: 0px; 19 | padding: 0px; 20 | box-sizing: border-box; 21 | } */ 22 | 23 | body { 24 | @apply bg-gradient-to-br from-purple-100 to-indigo-200; 25 | 26 | font-size: 16px; 27 | background-size: cover; 28 | background-repeat: no-repeat; 29 | font-family: Poppins; 30 | } 31 | 32 | #lumina__root { 33 | min-height: 100vh; 34 | display: flex; 35 | flex-direction: column; 36 | justify-content: space-between; 37 | } 38 | 39 | a { 40 | text-decoration: none; 41 | color: var(--text-main); 42 | cursor: pointer; 43 | } 44 | 45 | a:hover { 46 | opacity: 75%; 47 | } 48 | 49 | /* Loader Styles */ 50 | .loaderContainer { 51 | display: flex; 52 | justify-content: center; 53 | align-items: center; 54 | height: 100vh; 55 | } 56 | .loaderContainer .loaderChild { 57 | display: flex; 58 | flex-direction: column; 59 | justify-content: center; 60 | align-items: center; 61 | gap: 12px; 62 | padding: 24px 48px; 63 | border-radius: 12px; 64 | background: rgba(255, 255, 255, 0.258); 65 | } 66 | 67 | .loaderContainer .loaderChild h1 { 68 | font-weight: 900; 69 | color: #9f90ff; 70 | font-size: 36px; 71 | } 72 | 73 | .loader { 74 | width: 48px; 75 | height: 48px; 76 | border-radius: 50%; 77 | position: relative; 78 | animation: rotate 1s linear infinite; 79 | } 80 | .loader::before, 81 | .loader::after { 82 | content: ''; 83 | box-sizing: border-box; 84 | position: absolute; 85 | inset: 0px; 86 | border-radius: 50%; 87 | border: 5px solid #fff; 88 | animation: prixClipFix 2s linear infinite; 89 | } 90 | .loader::after { 91 | border-color: #afa4f9; 92 | animation: prixClipFix 2s linear infinite, rotate 0.5s linear infinite reverse; 93 | inset: 6px; 94 | } 95 | 96 | @keyframes rotate { 97 | 0% { 98 | transform: rotate(0deg); 99 | } 100 | 100% { 101 | transform: rotate(360deg); 102 | } 103 | } 104 | 105 | @keyframes prixClipFix { 106 | 0% { 107 | clip-path: polygon(50% 50%, 0 0, 0 0, 0 0, 0 0, 0 0); 108 | } 109 | 25% { 110 | clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 0, 100% 0, 100% 0); 111 | } 112 | 50% { 113 | clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 100% 100%, 100% 100%); 114 | } 115 | 75% { 116 | clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 100%); 117 | } 118 | 100% { 119 | clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 0); 120 | } 121 | } 122 | 123 | @keyframes spin { 124 | to { 125 | transform: rotate(360deg); 126 | } 127 | } 128 | 129 | /* Navbar styles */ 130 | .header { 131 | background-color: #ffffff; 132 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); 133 | position: sticky; 134 | top: 0; 135 | z-index: 1000; 136 | margin-bottom: 40px; 137 | } 138 | 139 | .nav { 140 | display: flex; 141 | align-items: center; 142 | justify-content: space-between; 143 | padding: 1rem 2rem; 144 | max-width: 1200px; 145 | margin: 0 auto; 146 | } 147 | 148 | .logo a { 149 | font-size: 1.5rem; 150 | font-weight: bold; 151 | color: #333; 152 | text-decoration: none; 153 | } 154 | 155 | .nav-links { 156 | display: flex; 157 | gap: 1.5rem; 158 | list-style: none; 159 | margin: 0; 160 | padding: 0; 161 | } 162 | 163 | .nav-links a { 164 | color: #333; 165 | text-decoration: none; 166 | font-size: 1rem; 167 | font-weight: 500; 168 | transition: color 0.3s ease; 169 | } 170 | 171 | .nav-links a:hover { 172 | color: #007bff; 173 | } 174 | 175 | .menu-toggle { 176 | display: none; 177 | font-size: 1.5rem; 178 | background: none; 179 | border: none; 180 | cursor: pointer; 181 | color: #333; 182 | } 183 | 184 | /* Responsive Design */ 185 | @media (max-width: 768px) { 186 | .nav-links { 187 | display: none; 188 | flex-direction: column; 189 | gap: 1rem; 190 | position: absolute; 191 | top: 100%; 192 | right: 0; 193 | background-color: #fff; 194 | box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1); 195 | padding: 1rem 2rem; 196 | border-radius: 0 0 10px 10px; 197 | } 198 | 199 | .nav-links.active { 200 | display: flex; 201 | } 202 | 203 | .menu-toggle { 204 | display: block; 205 | } 206 | } 207 | 208 | /* Footer styles */ 209 | .footer { 210 | background-color: #333; 211 | color: #fff; 212 | padding: 2rem 1rem; 213 | text-align: center; 214 | } 215 | 216 | .footer-container { 217 | display: flex; 218 | flex-wrap: wrap; 219 | justify-content: space-between; 220 | max-width: 1200px; 221 | margin: 0 auto; 222 | gap: 2rem; 223 | } 224 | 225 | .footer-section { 226 | flex: 1 1 calc(33.333% - 1rem); 227 | min-width: 200px; 228 | } 229 | 230 | .lumina-icon { 231 | width: 100px; 232 | border-radius: 12px; 233 | } 234 | 235 | .footer-title { 236 | font-size: 1.5rem; 237 | margin-bottom: 0.5rem; 238 | } 239 | 240 | .footer-description { 241 | font-size: 0.9rem; 242 | color: #ccc; 243 | } 244 | 245 | .footer-links { 246 | list-style: none; 247 | padding: 0; 248 | margin: 0; 249 | } 250 | 251 | .footer-links li { 252 | margin: 0.5rem 0; 253 | } 254 | 255 | .footer-links a { 256 | color: #fff; 257 | text-decoration: none; 258 | font-size: 1rem; 259 | transition: color 0.3s ease; 260 | } 261 | 262 | .footer-links a:hover { 263 | color: #007bff; 264 | } 265 | 266 | .social-links a { 267 | font-size: 1.5rem; 268 | color: #fff; 269 | margin: 0 0.5rem; 270 | transition: color 0.3s ease; 271 | } 272 | 273 | .social-links a:hover { 274 | color: #007bff; 275 | } 276 | 277 | .footer-bottom { 278 | margin-top: 2rem; 279 | border-top: 1px solid #444; 280 | padding-top: 1rem; 281 | font-size: 0.9rem; 282 | color: #ccc; 283 | } 284 | 285 | /* Responsive Design */ 286 | @media (max-width: 768px) { 287 | .footer-container { 288 | flex-direction: column; 289 | text-align: left; 290 | } 291 | 292 | .footer-section { 293 | margin-bottom: 1.5rem; 294 | } 295 | 296 | .footer-bottom { 297 | text-align: center; 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lumina - Full Stack SPA Maker (by Kiarash Alizadeh) 2 | 3 | Lumina is a lightweight and efficient framework for building full-stack single-page applications (SPAs) with ease. 4 | 5 | ## Installation and Setup 6 | 7 | To create a new Lumina project, run: 8 | 9 | ```bash 10 | npx create-lumina@latest my-lumina 11 | ``` 12 | 13 | - `my-lumina` is the folder name where the project will be created. 14 | - You can omit the folder name and use: 15 | 16 | ```sh 17 | npx create-lumina@latest 18 | ``` 19 | 20 | This will create a default folder named `my-lumina-app`. 21 | 22 | - To install Lumina in the current directory, use: 23 | 24 | ```sh 25 | npx create-lumina@latest . 26 | ``` 27 | 28 | ## Usage 29 | 30 | After the installation is complete, when you see: 31 | 32 | ```sh 33 | Happy coding with Lumina! 🎉 34 | ``` 35 | 36 | Run the following command to start the development server: 37 | 38 | ```bash 39 | npm run dev 40 | ``` 41 | 42 | Your Lumina app will be available at: 43 | 44 | ``` 45 | http://localhost:3000/ 46 | ``` 47 | 48 | ## Table of Contents 49 | 50 | - [Frontend Development](#frontend-development) 51 | - [Components](#components) 52 | - [Nested Components](#nested-components) 53 | - [Routing](#routing) 54 | - [Handling Undefined Routes](#handling-undefined-routes) 55 | - [Layouts](#layouts) 56 | - [Loading Screen](#loading-screen) 57 | - [Styling](#styling) 58 | - [Dynamic URLs](#dynamic-urls) 59 | - [SPA Navigation](#spa-navigation) 60 | - [Backend Development](#backend-development) 61 | - [Database Setup](#database-setup) 62 | - [HTTP and APIs](#http-and-apis) 63 | - [Models](#models) 64 | - [Middlewares](#middlewares) 65 | - [Environment Variables](#environment-variables) 66 | - [Deploy on Server](#deploy-on-server) 67 | 68 | ## Frontend Development 69 | 70 | ### Components 71 | 72 | Components in Lumina are JavaScript functions that return HTML. All pages are created with JS and inserted into a div with `id="lumina__root"` in the HTML file: 73 | 74 | ```html 75 |
76 | 77 |
78 | ``` 79 | 80 | Basic component example (HomePage.js): 81 | 82 | ```javascript 83 | async function HomePage() { 84 | // set page title 85 | const title = 'HomePage | LUMINA'; 86 | document.title = title; 87 | 88 | // other JavaScript Logics 89 | 90 | return ` 91 |
92 | Home page of Lumina 93 |
94 | `; 95 | } 96 | 97 | export default HomePage; 98 | ``` 99 | 100 | Component naming rules: 101 | 102 | - File name must match function name 103 | - Start with uppercase letter 104 | - Must use `export default` 105 | 106 | For components that need to fetch data: 107 | 108 | ```javascript 109 | async function TestPage() { 110 | // set page title 111 | const title = 'Test | LUMINA'; 112 | document.title = title; 113 | 114 | const res = await fetch( 115 | `http://localhost:3000/api/user?api_token=tk_live_abc123` 116 | ); 117 | 118 | const data = await res.json(); 119 | console.log('data: ', data); 120 | 121 | return ` 122 |
123 | hello world! 124 |
125 | `; 126 | } 127 | 128 | export default TestPage; 129 | ``` 130 | 131 | Note: Always include `?api_token=tk_live_abc123` in API fetch requests due to the apiTokenAuth middleware. 132 | 133 | ### Nested Components 134 | 135 | You can create reusable components in the `components/module` directory. For organization, group related components in subdirectories: 136 | 137 | Example of using nested components: 138 | 139 | ```javascript 140 | import Comments from '../module/home/Comments.js'; 141 | import FAQ from '../module/home/FAQ.js'; 142 | import Price from '../module/home/Price.js'; 143 | 144 | async function HomePage() { 145 | const title = 'Home | LUMINA'; 146 | document.title = title; 147 | 148 | return ` 149 |
150 |
151 |

Create Stunning SPAs with LUMINA

152 |

The next-generation SPA maker for modern web applications

153 | Get Started 154 |
155 | 156 | 157 | ${Comments()} 158 | ${Price()} 159 | ${FAQ()} 160 | 161 |
162 |

Ready to Illuminate Your Web Projects?

163 |

Join thousands of developers who trust LUMINA for their SPA needs

164 | Start Coding 165 |
166 |
167 | `; 168 | } 169 | 170 | export default HomePage; 171 | ``` 172 | 173 | Example nested component (Comments.js): 174 | 175 | ```javascript 176 | function Comments() { 177 | return ` 178 | 179 |
180 |

What Our Users Say

181 |
182 |
183 |

"LUMINA has revolutionized the way we build SPAs. It's fast, intuitive, and the results are stunning!"

184 |

- Sarah Johnson, Frontend Developer

185 |
186 |
187 |

"I've never been able to create such beautiful and responsive SPAs so quickly. LUMINA is a game-changer!"

188 |

- Michael Chen, UX Designer

189 |
190 |
191 |
192 | `; 193 | } 194 | 195 | export default Comments; 196 | ``` 197 | 198 | ### Routing 199 | 200 | Define routes in `routes.js`: 201 | 202 | ```javascript 203 | const mainRoutes = [ 204 | { path: '/', component: 'HomePage', css: ['home'] }, 205 | { path: '/test', component: 'TestPage', css: ['test', 'testSass'] }, 206 | ]; 207 | 208 | const notFoundRoute = { 209 | path: '/404', 210 | component: 'NotFoundPage', 211 | css: ['notFound'], 212 | }; 213 | 214 | export { mainRoutes, notFoundRoute }; 215 | ``` 216 | 217 | Route configuration: 218 | 219 | - `path`: URL path after domain 220 | - `component`: Component name (must exist in components/pages) 221 | - `css`: Optional array of CSS files to link 222 | 223 | ### Handling Undefined Routes 224 | 225 | Lumina automatically handles undefined routes and displays the 404 page. If a user navigates to a non-existent route, they will be redirected to the designated `NotFoundPage`. 226 | 227 | You can customize the 404 page component and styles to match your branding by modifying `NotFoundPage` and its associated styles in `assets/css/notFound.css`. 228 | 229 | The 404 page ensures a better user experience by informing users that the requested page does not exist, preventing confusion and improving navigation. 230 | 231 | ### Layouts 232 | 233 | Main layout configuration in `components/layout/layout.js`: 234 | 235 | ```javascript 236 | import Header from './Header.js'; 237 | import Footer from './Footer.js'; 238 | 239 | async function Layout(PageContent) { 240 | const HeaderContent = await Header(); 241 | const FooterContent = await Footer(); 242 | 243 | return `${HeaderContent}
${PageContent}
${FooterContent}`; 244 | } 245 | 246 | export default Layout; 247 | ``` 248 | 249 | Header and footer components are in: 250 | 251 | - `components/layout/header.js` 252 | - `components/layout/footer.js` 253 | 254 | It's recommended to put header and footer styles in `assets/css/index.css` to avoid duplicate styles. 255 | 256 | ### Loading Screen 257 | 258 | Lumina includes a default loading screen that appears while content is loading, especially useful during API fetches. Customize the loader in: 259 | 260 | - HTML: `components/layout/loader.js` 261 | - CSS: `assets/css/index.css` 262 | 263 | ### Styling 264 | 265 | Lumina supports multiple styling approaches: 266 | 267 | 1. **Global CSS**: Use `assets/css/index.css` for site-wide styles 268 | 269 | 2. **Page-specific CSS**: Create files in `assets/css/pageStyle/` and link them in routes.js 270 | 271 | 3. **Sass**: Write in `assets/sass/` - automatically compiled to CSS 272 | 273 | 4. **Tailwind CSS V4.0**: Pre-configured and ready to use: 274 | 275 | ```html 276 |
Lumina is using TailwindCSS!
277 | ``` 278 | 279 | Note: The `css` property in routes is optional, especially if you're using Tailwind CSS. 280 | 281 | ### SPA Navigation 282 | 283 | Use regular `` tags for navigation. Lumina handles route changes without page refreshes: 284 | 285 | ```html 286 | Home 287 | ``` 288 | 289 | ### Dynamic URLs 290 | 291 | Create dynamic URLs using hash fragments: 292 | 293 | ```html 294 | Test Page 295 | ``` 296 | 297 | Access the dynamic ID in your component: 298 | 299 | ```javascript 300 | async function TestPage() { 301 | const title = 'Test | LUMINA'; 302 | document.title = title; 303 | 304 | // dynamic id in url 305 | const id = window.location.toString().split('#')[1]; 306 | 307 | return ` 308 |
309 | test Id: ${id} 310 |
311 | `; 312 | } 313 | ``` 314 | 315 | ## Backend Development 316 | 317 | ### HTTP and APIs 318 | 319 | HTTP Methods: 320 | 321 | - `GET`: Retrieve data 322 | - `POST`: Create new data 323 | - `PUT`: Update entire resource 324 | - `PATCH`: Partial update 325 | - `DELETE`: Remove data 326 | 327 | HTTP Status Codes: 328 | 329 | - 200: OK (Success) 330 | - 201: Created 331 | - 400: Bad Request 332 | - 401: Unauthorized 333 | - 403: Forbidden 334 | - 404: Not Found 335 | - 500: Internal Server Error 336 | 337 | For complete status code reference, visit [MDN HTTP Status](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status). 338 | 339 | ### Database Setup 340 | 341 | Lumina uses MongoDB with Mongoose by default. Database connection is configured in `lumina.config.js`: 342 | 343 | ```javascript 344 | async function DBConnect() { 345 | try { 346 | const DATABASE_URL = process.env.DATABASE_URL; 347 | if (!DATABASE_URL) { 348 | console.error('DATABASE_URL is not defined in .env file'); 349 | return; 350 | } 351 | 352 | // Connecting to the database 353 | await mongoose.connect(DATABASE_URL); 354 | 355 | console.log('Successfully connected to the Database!'); 356 | } catch (error) { 357 | console.error('Failed to connect to the Database:', error); 358 | } 359 | } 360 | ``` 361 | 362 | ### Models 363 | 364 | Define models in `api/models/` with uppercase naming convention: 365 | 366 | ```javascript 367 | import mongoose from 'mongoose'; 368 | 369 | const userSchema = new mongoose.Schema({ 370 | name: { 371 | type: String, 372 | required: true, 373 | }, 374 | email: { 375 | type: String, 376 | required: true, 377 | }, 378 | pass: { 379 | type: String, 380 | required: true, 381 | }, 382 | role: { 383 | type: String, 384 | default: 'USER', 385 | }, 386 | createdAt: { 387 | type: Date, 388 | default: Date.now(), 389 | immutable: true, 390 | }, 391 | }); 392 | 393 | export default mongoose.model('User', userSchema); 394 | ``` 395 | 396 | ### API Routes 397 | 398 | Create API routes in `api/routes/`. The filename determines the endpoint (e.g., `user.js` creates `/api/user`): 399 | 400 | ```javascript 401 | import express from 'express'; 402 | const router = express.Router(); 403 | import User from '../models/User.js'; 404 | 405 | // Getting all 406 | router.get('/', async (req, res) => { 407 | try { 408 | const users = await User.find(); 409 | res.json(users); 410 | } catch (err) { 411 | res.status(500).json({ message: err.message }); 412 | } 413 | }); 414 | 415 | // Getting One 416 | router.get('/:id', async (req, res) => { 417 | try { 418 | const user = await User.findById(req.params.id); 419 | if (!user) { 420 | return res.status(404).json({ message: 'user not found' }); 421 | } 422 | res.json(user); 423 | } catch (err) { 424 | res.status(500).json({ message: err.message }); 425 | } 426 | }); 427 | 428 | // Creating one 429 | router.post('/', async (req, res) => { 430 | try { 431 | const { 432 | name, 433 | familyName, 434 | userName, 435 | email, 436 | birthDate, 437 | pass, 438 | gender, 439 | phone, 440 | favoriteTeam, 441 | } = req.body; 442 | 443 | const newUser = await User.create({ 444 | name, 445 | familyName, 446 | userName, 447 | email, 448 | birthDate, 449 | pass, 450 | gender, 451 | phone, 452 | favoriteTeam, 453 | }); 454 | 455 | res.status(201).json({ message: 'user created' }); 456 | } catch (err) { 457 | res.status(400).json({ message: err.message }); 458 | } 459 | }); 460 | 461 | // Updating One 462 | router.patch('/:id', async (req, res) => { 463 | try { 464 | const user = await User.findById(req.params.id); 465 | if (!user) { 466 | return res.status(404).json({ message: 'user not found' }); 467 | } 468 | 469 | // Update fields based on request body 470 | if (req.body.name) user.name = req.body.name; 471 | if (req.body.familyName) user.familyName = req.body.familyName; 472 | if (req.body.userName) user.userName = req.body.userName; 473 | if (req.body.email) user.email = req.body.email; 474 | 475 | await user.save(); 476 | res.status(201).json({ message: 'user Updated' }); 477 | } catch (err) { 478 | res.status(400).json({ message: err.message }); 479 | } 480 | }); 481 | 482 | // Deleting One 483 | router.delete('/:id', async (req, res) => { 484 | try { 485 | const user = await User.findByIdAndDelete(req.params.id); 486 | if (!user) { 487 | return res.status(404).json({ message: 'user not found' }); 488 | } 489 | res.json({ message: 'Deleted User' }); 490 | } catch (err) { 491 | res.status(500).json({ message: err.message }); 492 | } 493 | }); 494 | 495 | export default router; 496 | ``` 497 | 498 | ### Middlewares 499 | 500 | Middlewares are functions that run between request and response. They can: 501 | 502 | - Authenticate requests 503 | - Log information 504 | - Modify requests/responses 505 | - Handle errors 506 | 507 | Create middlewares in `api/middleware/` with numbered prefixes for execution order(e.g., `02-loggerTest.js` runs after `01-apiTokenAuth.js`): 508 | 509 | ```javascript 510 | export const requestLogger = () => (req, res, next) => { 511 | console.log('< -- middleware logger 01 -- >'); 512 | next(); 513 | }; 514 | ``` 515 | 516 | API Token Authentication: 517 | Configure tokens in `lumina.config.js`: 518 | 519 | ```javascript 520 | const API_TOKENS = new Map([ 521 | [ 522 | 'tk_live_abc123', 523 | { 524 | name: 'Production App', 525 | permissions: ['read', 'write'], 526 | isActive: true, 527 | rateLimit: 1000, // requests per hour 528 | }, 529 | ], 530 | [ 531 | 'tk_test_xyz789', 532 | { 533 | name: 'Test App', 534 | permissions: ['read'], 535 | isActive: true, 536 | rateLimit: 100, 537 | }, 538 | ], 539 | ]); 540 | ``` 541 | 542 | ### Environment Variables 543 | 544 | Store sensitive configuration in `.env`: 545 | 546 | ```plaintext 547 | DATABASE_URL="mongodb://127.0.0.1:27017/lumina" 548 | ``` 549 | 550 | The `.env` file should be in the project root and is used for: 551 | 552 | - Database connection strings 553 | - Server Port 554 | - API keys 555 | - Sensitive configuration 556 | - Environment-specific settings 557 | 558 | Never commit the `.env` file to version control. 559 | 560 | **Configuring the Port** 561 | By default, Lumina runs on port `3000`. You can change this by setting the `PORT` variable in your `.env` file or modifying the `lumina.config.js` file: 562 | 563 | ```javascript 564 | // lumina port 565 | const port = process.env.PORT || 3000; 566 | ``` 567 | 568 | This allows you to customize the server port based on your environment. 569 | 570 | ### Deploy on Server 571 | 572 | To deploy the application on a server where Node.js is already installed, follow these steps: 573 | 574 | 1. **Upload the project files** to the server. You can use FTP, SCP, or any other method. 575 | 2. **Navigate to the project directory** in the terminal: 576 | 577 | ```bash 578 | cd /path/to/your/project 579 | ``` 580 | 581 | 3. **Install dependencies**: 582 | 583 | ```bash 584 | npm install 585 | ``` 586 | 587 | 4. **Start the application**: 588 | 589 | ```bash 590 | npm start 591 | ``` 592 | 593 | The application will run on port `3000` by default or the port defined in the `.env` file (e.g., `PORT=3001`). 594 | 595 | 5. **Connect to a real database** (if applicable) by setting `DATABASE_URL` in the `.env` file. 596 | 597 | 6. **Access the application**: 598 | - If no domain is set, open `http://your-server-ip:3000` in a browser. 599 | - If a domain is connected to the server, simply type your domain in the browser. 600 | 601 | Your application is now live! 🚀 602 | 603 | --- 604 | 605 | Happy coding with Lumina! ❤️ 606 | -------------------------------------------------------------------------------- /assets/css/tailwindOutput.css: -------------------------------------------------------------------------------- 1 | /*! tailwindcss v4.0.0 | MIT License | https://tailwindcss.com */ 2 | @layer theme, base, components, utilities; 3 | @layer theme { 4 | :root { 5 | --font-sans: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", 6 | "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; 7 | --font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif; 8 | --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 9 | "Liberation Mono", "Courier New", monospace; 10 | --color-red-50: oklch(0.971 0.013 17.38); 11 | --color-red-100: oklch(0.936 0.032 17.717); 12 | --color-red-200: oklch(0.885 0.062 18.334); 13 | --color-red-300: oklch(0.808 0.114 19.571); 14 | --color-red-400: oklch(0.704 0.191 22.216); 15 | --color-red-500: oklch(0.637 0.237 25.331); 16 | --color-red-600: oklch(0.577 0.245 27.325); 17 | --color-red-700: oklch(0.505 0.213 27.518); 18 | --color-red-800: oklch(0.444 0.177 26.899); 19 | --color-red-900: oklch(0.396 0.141 25.723); 20 | --color-red-950: oklch(0.258 0.092 26.042); 21 | --color-orange-50: oklch(0.98 0.016 73.684); 22 | --color-orange-100: oklch(0.954 0.038 75.164); 23 | --color-orange-200: oklch(0.901 0.076 70.697); 24 | --color-orange-300: oklch(0.837 0.128 66.29); 25 | --color-orange-400: oklch(0.75 0.183 55.934); 26 | --color-orange-500: oklch(0.705 0.213 47.604); 27 | --color-orange-600: oklch(0.646 0.222 41.116); 28 | --color-orange-700: oklch(0.553 0.195 38.402); 29 | --color-orange-800: oklch(0.47 0.157 37.304); 30 | --color-orange-900: oklch(0.408 0.123 38.172); 31 | --color-orange-950: oklch(0.266 0.079 36.259); 32 | --color-amber-50: oklch(0.987 0.022 95.277); 33 | --color-amber-100: oklch(0.962 0.059 95.617); 34 | --color-amber-200: oklch(0.924 0.12 95.746); 35 | --color-amber-300: oklch(0.879 0.169 91.605); 36 | --color-amber-400: oklch(0.828 0.189 84.429); 37 | --color-amber-500: oklch(0.769 0.188 70.08); 38 | --color-amber-600: oklch(0.666 0.179 58.318); 39 | --color-amber-700: oklch(0.555 0.163 48.998); 40 | --color-amber-800: oklch(0.473 0.137 46.201); 41 | --color-amber-900: oklch(0.414 0.112 45.904); 42 | --color-amber-950: oklch(0.279 0.077 45.635); 43 | --color-yellow-50: oklch(0.987 0.026 102.212); 44 | --color-yellow-100: oklch(0.973 0.071 103.193); 45 | --color-yellow-200: oklch(0.945 0.129 101.54); 46 | --color-yellow-300: oklch(0.905 0.182 98.111); 47 | --color-yellow-400: oklch(0.852 0.199 91.936); 48 | --color-yellow-500: oklch(0.795 0.184 86.047); 49 | --color-yellow-600: oklch(0.681 0.162 75.834); 50 | --color-yellow-700: oklch(0.554 0.135 66.442); 51 | --color-yellow-800: oklch(0.476 0.114 61.907); 52 | --color-yellow-900: oklch(0.421 0.095 57.708); 53 | --color-yellow-950: oklch(0.286 0.066 53.813); 54 | --color-lime-50: oklch(0.986 0.031 120.757); 55 | --color-lime-100: oklch(0.967 0.067 122.328); 56 | --color-lime-200: oklch(0.938 0.127 124.321); 57 | --color-lime-300: oklch(0.897 0.196 126.665); 58 | --color-lime-400: oklch(0.841 0.238 128.85); 59 | --color-lime-500: oklch(0.768 0.233 130.85); 60 | --color-lime-600: oklch(0.648 0.2 131.684); 61 | --color-lime-700: oklch(0.532 0.157 131.589); 62 | --color-lime-800: oklch(0.453 0.124 130.933); 63 | --color-lime-900: oklch(0.405 0.101 131.063); 64 | --color-lime-950: oklch(0.274 0.072 132.109); 65 | --color-green-50: oklch(0.982 0.018 155.826); 66 | --color-green-100: oklch(0.962 0.044 156.743); 67 | --color-green-200: oklch(0.925 0.084 155.995); 68 | --color-green-300: oklch(0.871 0.15 154.449); 69 | --color-green-400: oklch(0.792 0.209 151.711); 70 | --color-green-500: oklch(0.723 0.219 149.579); 71 | --color-green-600: oklch(0.627 0.194 149.214); 72 | --color-green-700: oklch(0.527 0.154 150.069); 73 | --color-green-800: oklch(0.448 0.119 151.328); 74 | --color-green-900: oklch(0.393 0.095 152.535); 75 | --color-green-950: oklch(0.266 0.065 152.934); 76 | --color-emerald-50: oklch(0.979 0.021 166.113); 77 | --color-emerald-100: oklch(0.95 0.052 163.051); 78 | --color-emerald-200: oklch(0.905 0.093 164.15); 79 | --color-emerald-300: oklch(0.845 0.143 164.978); 80 | --color-emerald-400: oklch(0.765 0.177 163.223); 81 | --color-emerald-500: oklch(0.696 0.17 162.48); 82 | --color-emerald-600: oklch(0.596 0.145 163.225); 83 | --color-emerald-700: oklch(0.508 0.118 165.612); 84 | --color-emerald-800: oklch(0.432 0.095 166.913); 85 | --color-emerald-900: oklch(0.378 0.077 168.94); 86 | --color-emerald-950: oklch(0.262 0.051 172.552); 87 | --color-teal-50: oklch(0.984 0.014 180.72); 88 | --color-teal-100: oklch(0.953 0.051 180.801); 89 | --color-teal-200: oklch(0.91 0.096 180.426); 90 | --color-teal-300: oklch(0.855 0.138 181.071); 91 | --color-teal-400: oklch(0.777 0.152 181.912); 92 | --color-teal-500: oklch(0.704 0.14 182.503); 93 | --color-teal-600: oklch(0.6 0.118 184.704); 94 | --color-teal-700: oklch(0.511 0.096 186.391); 95 | --color-teal-800: oklch(0.437 0.078 188.216); 96 | --color-teal-900: oklch(0.386 0.063 188.416); 97 | --color-teal-950: oklch(0.277 0.046 192.524); 98 | --color-cyan-50: oklch(0.984 0.019 200.873); 99 | --color-cyan-100: oklch(0.956 0.045 203.388); 100 | --color-cyan-200: oklch(0.917 0.08 205.041); 101 | --color-cyan-300: oklch(0.865 0.127 207.078); 102 | --color-cyan-400: oklch(0.789 0.154 211.53); 103 | --color-cyan-500: oklch(0.715 0.143 215.221); 104 | --color-cyan-600: oklch(0.609 0.126 221.723); 105 | --color-cyan-700: oklch(0.52 0.105 223.128); 106 | --color-cyan-800: oklch(0.45 0.085 224.283); 107 | --color-cyan-900: oklch(0.398 0.07 227.392); 108 | --color-cyan-950: oklch(0.302 0.056 229.695); 109 | --color-sky-50: oklch(0.977 0.013 236.62); 110 | --color-sky-100: oklch(0.951 0.026 236.824); 111 | --color-sky-200: oklch(0.901 0.058 230.902); 112 | --color-sky-300: oklch(0.828 0.111 230.318); 113 | --color-sky-400: oklch(0.746 0.16 232.661); 114 | --color-sky-500: oklch(0.685 0.169 237.323); 115 | --color-sky-600: oklch(0.588 0.158 241.966); 116 | --color-sky-700: oklch(0.5 0.134 242.749); 117 | --color-sky-800: oklch(0.443 0.11 240.79); 118 | --color-sky-900: oklch(0.391 0.09 240.876); 119 | --color-sky-950: oklch(0.293 0.066 243.157); 120 | --color-blue-50: oklch(0.97 0.014 254.604); 121 | --color-blue-100: oklch(0.932 0.032 255.585); 122 | --color-blue-200: oklch(0.882 0.059 254.128); 123 | --color-blue-300: oklch(0.809 0.105 251.813); 124 | --color-blue-400: oklch(0.707 0.165 254.624); 125 | --color-blue-500: oklch(0.623 0.214 259.815); 126 | --color-blue-600: oklch(0.546 0.245 262.881); 127 | --color-blue-700: oklch(0.488 0.243 264.376); 128 | --color-blue-800: oklch(0.424 0.199 265.638); 129 | --color-blue-900: oklch(0.379 0.146 265.522); 130 | --color-blue-950: oklch(0.282 0.091 267.935); 131 | --color-indigo-50: oklch(0.962 0.018 272.314); 132 | --color-indigo-100: oklch(0.93 0.034 272.788); 133 | --color-indigo-200: oklch(0.87 0.065 274.039); 134 | --color-indigo-300: oklch(0.785 0.115 274.713); 135 | --color-indigo-400: oklch(0.673 0.182 276.935); 136 | --color-indigo-500: oklch(0.585 0.233 277.117); 137 | --color-indigo-600: oklch(0.511 0.262 276.966); 138 | --color-indigo-700: oklch(0.457 0.24 277.023); 139 | --color-indigo-800: oklch(0.398 0.195 277.366); 140 | --color-indigo-900: oklch(0.359 0.144 278.697); 141 | --color-indigo-950: oklch(0.257 0.09 281.288); 142 | --color-violet-50: oklch(0.969 0.016 293.756); 143 | --color-violet-100: oklch(0.943 0.029 294.588); 144 | --color-violet-200: oklch(0.894 0.057 293.283); 145 | --color-violet-300: oklch(0.811 0.111 293.571); 146 | --color-violet-400: oklch(0.702 0.183 293.541); 147 | --color-violet-500: oklch(0.606 0.25 292.717); 148 | --color-violet-600: oklch(0.541 0.281 293.009); 149 | --color-violet-700: oklch(0.491 0.27 292.581); 150 | --color-violet-800: oklch(0.432 0.232 292.759); 151 | --color-violet-900: oklch(0.38 0.189 293.745); 152 | --color-violet-950: oklch(0.283 0.141 291.089); 153 | --color-purple-50: oklch(0.977 0.014 308.299); 154 | --color-purple-100: oklch(0.946 0.033 307.174); 155 | --color-purple-200: oklch(0.902 0.063 306.703); 156 | --color-purple-300: oklch(0.827 0.119 306.383); 157 | --color-purple-400: oklch(0.714 0.203 305.504); 158 | --color-purple-500: oklch(0.627 0.265 303.9); 159 | --color-purple-600: oklch(0.558 0.288 302.321); 160 | --color-purple-700: oklch(0.496 0.265 301.924); 161 | --color-purple-800: oklch(0.438 0.218 303.724); 162 | --color-purple-900: oklch(0.381 0.176 304.987); 163 | --color-purple-950: oklch(0.291 0.149 302.717); 164 | --color-fuchsia-50: oklch(0.977 0.017 320.058); 165 | --color-fuchsia-100: oklch(0.952 0.037 318.852); 166 | --color-fuchsia-200: oklch(0.903 0.076 319.62); 167 | --color-fuchsia-300: oklch(0.833 0.145 321.434); 168 | --color-fuchsia-400: oklch(0.74 0.238 322.16); 169 | --color-fuchsia-500: oklch(0.667 0.295 322.15); 170 | --color-fuchsia-600: oklch(0.591 0.293 322.896); 171 | --color-fuchsia-700: oklch(0.518 0.253 323.949); 172 | --color-fuchsia-800: oklch(0.452 0.211 324.591); 173 | --color-fuchsia-900: oklch(0.401 0.17 325.612); 174 | --color-fuchsia-950: oklch(0.293 0.136 325.661); 175 | --color-pink-50: oklch(0.971 0.014 343.198); 176 | --color-pink-100: oklch(0.948 0.028 342.258); 177 | --color-pink-200: oklch(0.899 0.061 343.231); 178 | --color-pink-300: oklch(0.823 0.12 346.018); 179 | --color-pink-400: oklch(0.718 0.202 349.761); 180 | --color-pink-500: oklch(0.656 0.241 354.308); 181 | --color-pink-600: oklch(0.592 0.249 0.584); 182 | --color-pink-700: oklch(0.525 0.223 3.958); 183 | --color-pink-800: oklch(0.459 0.187 3.815); 184 | --color-pink-900: oklch(0.408 0.153 2.432); 185 | --color-pink-950: oklch(0.284 0.109 3.907); 186 | --color-rose-50: oklch(0.969 0.015 12.422); 187 | --color-rose-100: oklch(0.941 0.03 12.58); 188 | --color-rose-200: oklch(0.892 0.058 10.001); 189 | --color-rose-300: oklch(0.81 0.117 11.638); 190 | --color-rose-400: oklch(0.712 0.194 13.428); 191 | --color-rose-500: oklch(0.645 0.246 16.439); 192 | --color-rose-600: oklch(0.586 0.253 17.585); 193 | --color-rose-700: oklch(0.514 0.222 16.935); 194 | --color-rose-800: oklch(0.455 0.188 13.697); 195 | --color-rose-900: oklch(0.41 0.159 10.272); 196 | --color-rose-950: oklch(0.271 0.105 12.094); 197 | --color-slate-50: oklch(0.984 0.003 247.858); 198 | --color-slate-100: oklch(0.968 0.007 247.896); 199 | --color-slate-200: oklch(0.929 0.013 255.508); 200 | --color-slate-300: oklch(0.869 0.022 252.894); 201 | --color-slate-400: oklch(0.704 0.04 256.788); 202 | --color-slate-500: oklch(0.554 0.046 257.417); 203 | --color-slate-600: oklch(0.446 0.043 257.281); 204 | --color-slate-700: oklch(0.372 0.044 257.287); 205 | --color-slate-800: oklch(0.279 0.041 260.031); 206 | --color-slate-900: oklch(0.208 0.042 265.755); 207 | --color-slate-950: oklch(0.129 0.042 264.695); 208 | --color-gray-50: oklch(0.985 0.002 247.839); 209 | --color-gray-100: oklch(0.967 0.003 264.542); 210 | --color-gray-200: oklch(0.928 0.006 264.531); 211 | --color-gray-300: oklch(0.872 0.01 258.338); 212 | --color-gray-400: oklch(0.707 0.022 261.325); 213 | --color-gray-500: oklch(0.551 0.027 264.364); 214 | --color-gray-600: oklch(0.446 0.03 256.802); 215 | --color-gray-700: oklch(0.373 0.034 259.733); 216 | --color-gray-800: oklch(0.278 0.033 256.848); 217 | --color-gray-900: oklch(0.21 0.034 264.665); 218 | --color-gray-950: oklch(0.13 0.028 261.692); 219 | --color-zinc-50: oklch(0.985 0 0); 220 | --color-zinc-100: oklch(0.967 0.001 286.375); 221 | --color-zinc-200: oklch(0.92 0.004 286.32); 222 | --color-zinc-300: oklch(0.871 0.006 286.286); 223 | --color-zinc-400: oklch(0.705 0.015 286.067); 224 | --color-zinc-500: oklch(0.552 0.016 285.938); 225 | --color-zinc-600: oklch(0.442 0.017 285.786); 226 | --color-zinc-700: oklch(0.37 0.013 285.805); 227 | --color-zinc-800: oklch(0.274 0.006 286.033); 228 | --color-zinc-900: oklch(0.21 0.006 285.885); 229 | --color-zinc-950: oklch(0.141 0.005 285.823); 230 | --color-neutral-50: oklch(0.985 0 0); 231 | --color-neutral-100: oklch(0.97 0 0); 232 | --color-neutral-200: oklch(0.922 0 0); 233 | --color-neutral-300: oklch(0.87 0 0); 234 | --color-neutral-400: oklch(0.708 0 0); 235 | --color-neutral-500: oklch(0.556 0 0); 236 | --color-neutral-600: oklch(0.439 0 0); 237 | --color-neutral-700: oklch(0.371 0 0); 238 | --color-neutral-800: oklch(0.269 0 0); 239 | --color-neutral-900: oklch(0.205 0 0); 240 | --color-neutral-950: oklch(0.145 0 0); 241 | --color-stone-50: oklch(0.985 0.001 106.423); 242 | --color-stone-100: oklch(0.97 0.001 106.424); 243 | --color-stone-200: oklch(0.923 0.003 48.717); 244 | --color-stone-300: oklch(0.869 0.005 56.366); 245 | --color-stone-400: oklch(0.709 0.01 56.259); 246 | --color-stone-500: oklch(0.553 0.013 58.071); 247 | --color-stone-600: oklch(0.444 0.011 73.639); 248 | --color-stone-700: oklch(0.374 0.01 67.558); 249 | --color-stone-800: oklch(0.268 0.007 34.298); 250 | --color-stone-900: oklch(0.216 0.006 56.043); 251 | --color-stone-950: oklch(0.147 0.004 49.25); 252 | --color-black: #000; 253 | --color-white: #fff; 254 | --spacing: 0.25rem; 255 | --breakpoint-sm: 40rem; 256 | --breakpoint-md: 48rem; 257 | --breakpoint-lg: 64rem; 258 | --breakpoint-xl: 80rem; 259 | --breakpoint-2xl: 96rem; 260 | --container-3xs: 16rem; 261 | --container-2xs: 18rem; 262 | --container-xs: 20rem; 263 | --container-sm: 24rem; 264 | --container-md: 28rem; 265 | --container-lg: 32rem; 266 | --container-xl: 36rem; 267 | --container-2xl: 42rem; 268 | --container-3xl: 48rem; 269 | --container-4xl: 56rem; 270 | --container-5xl: 64rem; 271 | --container-6xl: 72rem; 272 | --container-7xl: 80rem; 273 | --text-xs: 0.75rem; 274 | --text-xs--line-height: calc(1 / 0.75); 275 | --text-sm: 0.875rem; 276 | --text-sm--line-height: calc(1.25 / 0.875); 277 | --text-base: 1rem; 278 | --text-base--line-height: calc(1.5 / 1); 279 | --text-lg: 1.125rem; 280 | --text-lg--line-height: calc(1.75 / 1.125); 281 | --text-xl: 1.25rem; 282 | --text-xl--line-height: calc(1.75 / 1.25); 283 | --text-2xl: 1.5rem; 284 | --text-2xl--line-height: calc(2 / 1.5); 285 | --text-3xl: 1.875rem; 286 | --text-3xl--line-height: calc(2.25 / 1.875); 287 | --text-4xl: 2.25rem; 288 | --text-4xl--line-height: calc(2.5 / 2.25); 289 | --text-5xl: 3rem; 290 | --text-5xl--line-height: 1; 291 | --text-6xl: 3.75rem; 292 | --text-6xl--line-height: 1; 293 | --text-7xl: 4.5rem; 294 | --text-7xl--line-height: 1; 295 | --text-8xl: 6rem; 296 | --text-8xl--line-height: 1; 297 | --text-9xl: 8rem; 298 | --text-9xl--line-height: 1; 299 | --font-weight-thin: 100; 300 | --font-weight-extralight: 200; 301 | --font-weight-light: 300; 302 | --font-weight-normal: 400; 303 | --font-weight-medium: 500; 304 | --font-weight-semibold: 600; 305 | --font-weight-bold: 700; 306 | --font-weight-extrabold: 800; 307 | --font-weight-black: 900; 308 | --tracking-tighter: -0.05em; 309 | --tracking-tight: -0.025em; 310 | --tracking-normal: 0em; 311 | --tracking-wide: 0.025em; 312 | --tracking-wider: 0.05em; 313 | --tracking-widest: 0.1em; 314 | --leading-tight: 1.25; 315 | --leading-snug: 1.375; 316 | --leading-normal: 1.5; 317 | --leading-relaxed: 1.625; 318 | --leading-loose: 2; 319 | --radius-xs: 0.125rem; 320 | --radius-sm: 0.25rem; 321 | --radius-md: 0.375rem; 322 | --radius-lg: 0.5rem; 323 | --radius-xl: 0.75rem; 324 | --radius-2xl: 1rem; 325 | --radius-3xl: 1.5rem; 326 | --radius-4xl: 2rem; 327 | --shadow-2xs: 0 1px rgb(0 0 0 / 0.05); 328 | --shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05); 329 | --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); 330 | --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 331 | 0 2px 4px -2px rgb(0 0 0 / 0.1); 332 | --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 333 | 0 4px 6px -4px rgb(0 0 0 / 0.1); 334 | --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 335 | 0 8px 10px -6px rgb(0 0 0 / 0.1); 336 | --shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25); 337 | --inset-shadow-2xs: inset 0 1px rgb(0 0 0 / 0.05); 338 | --inset-shadow-xs: inset 0 1px 1px rgb(0 0 0 / 0.05); 339 | --inset-shadow-sm: inset 0 2px 4px rgb(0 0 0 / 0.05); 340 | --drop-shadow-xs: 0 1px 1px rgb(0 0 0 / 0.05); 341 | --drop-shadow-sm: 0 1px 2px rgb(0 0 0 / 0.15); 342 | --drop-shadow-md: 0 3px 3px rgb(0 0 0 / 0.12); 343 | --drop-shadow-lg: 0 4px 4px rgb(0 0 0 / 0.15); 344 | --drop-shadow-xl: 0 9px 7px rgb(0 0 0 / 0.1); 345 | --drop-shadow-2xl: 0 25px 25px rgb(0 0 0 / 0.15); 346 | --ease-in: cubic-bezier(0.4, 0, 1, 1); 347 | --ease-out: cubic-bezier(0, 0, 0.2, 1); 348 | --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); 349 | --animate-spin: spin 1s linear infinite; 350 | --animate-ping: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; 351 | --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; 352 | --animate-bounce: bounce 1s infinite; 353 | --blur-xs: 4px; 354 | --blur-sm: 8px; 355 | --blur-md: 12px; 356 | --blur-lg: 16px; 357 | --blur-xl: 24px; 358 | --blur-2xl: 40px; 359 | --blur-3xl: 64px; 360 | --perspective-dramatic: 100px; 361 | --perspective-near: 300px; 362 | --perspective-normal: 500px; 363 | --perspective-midrange: 800px; 364 | --perspective-distant: 1200px; 365 | --aspect-video: 16 / 9; 366 | --default-transition-duration: 150ms; 367 | --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); 368 | --default-font-family: var(--font-sans); 369 | --default-font-feature-settings: var(--font-sans--font-feature-settings); 370 | --default-font-variation-settings: var( 371 | --font-sans--font-variation-settings 372 | ); 373 | --default-mono-font-family: var(--font-mono); 374 | --default-mono-font-feature-settings: var( 375 | --font-mono--font-feature-settings 376 | ); 377 | --default-mono-font-variation-settings: var( 378 | --font-mono--font-variation-settings 379 | ); 380 | } 381 | } 382 | @layer base { 383 | *, ::after, ::before, ::backdrop, ::file-selector-button { 384 | box-sizing: border-box; 385 | margin: 0; 386 | padding: 0; 387 | border: 0 solid; 388 | } 389 | html, :host { 390 | line-height: 1.5; 391 | -webkit-text-size-adjust: 100%; 392 | tab-size: 4; 393 | font-family: var( --default-font-family, ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji" ); 394 | font-feature-settings: var(--default-font-feature-settings, normal); 395 | font-variation-settings: var( --default-font-variation-settings, normal ); 396 | -webkit-tap-highlight-color: transparent; 397 | } 398 | body { 399 | line-height: inherit; 400 | } 401 | hr { 402 | height: 0; 403 | color: inherit; 404 | border-top-width: 1px; 405 | } 406 | abbr:where([title]) { 407 | -webkit-text-decoration: underline dotted; 408 | text-decoration: underline dotted; 409 | } 410 | h1, h2, h3, h4, h5, h6 { 411 | font-size: inherit; 412 | font-weight: inherit; 413 | } 414 | a { 415 | color: inherit; 416 | -webkit-text-decoration: inherit; 417 | text-decoration: inherit; 418 | } 419 | b, strong { 420 | font-weight: bolder; 421 | } 422 | code, kbd, samp, pre { 423 | font-family: var( --default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace ); 424 | font-feature-settings: var( --default-mono-font-feature-settings, normal ); 425 | font-variation-settings: var( --default-mono-font-variation-settings, normal ); 426 | font-size: 1em; 427 | } 428 | small { 429 | font-size: 80%; 430 | } 431 | sub, sup { 432 | font-size: 75%; 433 | line-height: 0; 434 | position: relative; 435 | vertical-align: baseline; 436 | } 437 | sub { 438 | bottom: -0.25em; 439 | } 440 | sup { 441 | top: -0.5em; 442 | } 443 | table { 444 | text-indent: 0; 445 | border-color: inherit; 446 | border-collapse: collapse; 447 | } 448 | :-moz-focusring { 449 | outline: auto; 450 | } 451 | progress { 452 | vertical-align: baseline; 453 | } 454 | summary { 455 | display: list-item; 456 | } 457 | ol, ul, menu { 458 | list-style: none; 459 | } 460 | img, svg, video, canvas, audio, iframe, embed, object { 461 | display: block; 462 | vertical-align: middle; 463 | } 464 | img, video { 465 | max-width: 100%; 466 | height: auto; 467 | } 468 | button, input, select, optgroup, textarea, ::file-selector-button { 469 | font: inherit; 470 | font-feature-settings: inherit; 471 | font-variation-settings: inherit; 472 | letter-spacing: inherit; 473 | color: inherit; 474 | border-radius: 0; 475 | background-color: transparent; 476 | opacity: 1; 477 | } 478 | :where(select:is([multiple], [size])) optgroup { 479 | font-weight: bolder; 480 | } 481 | :where(select:is([multiple], [size])) optgroup option { 482 | padding-inline-start: 20px; 483 | } 484 | ::file-selector-button { 485 | margin-inline-end: 4px; 486 | } 487 | ::placeholder { 488 | opacity: 1; 489 | color: color-mix(in oklab, currentColor 50%, transparent); 490 | } 491 | textarea { 492 | resize: vertical; 493 | } 494 | ::-webkit-search-decoration { 495 | -webkit-appearance: none; 496 | } 497 | ::-webkit-date-and-time-value { 498 | min-height: 1lh; 499 | text-align: inherit; 500 | } 501 | ::-webkit-datetime-edit { 502 | display: inline-flex; 503 | } 504 | ::-webkit-datetime-edit-fields-wrapper { 505 | padding: 0; 506 | } 507 | ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field { 508 | padding-block: 0; 509 | } 510 | :-moz-ui-invalid { 511 | box-shadow: none; 512 | } 513 | button, input:where([type="button"], [type="reset"], [type="submit"]), ::file-selector-button { 514 | appearance: button; 515 | } 516 | ::-webkit-inner-spin-button, ::-webkit-outer-spin-button { 517 | height: auto; 518 | } 519 | [hidden]:where(:not([hidden="until-found"])) { 520 | display: none !important; 521 | } 522 | } 523 | @layer utilities { 524 | .collapse { 525 | visibility: collapse; 526 | } 527 | .absolute { 528 | position: absolute; 529 | } 530 | .fixed { 531 | position: fixed; 532 | } 533 | .relative { 534 | position: relative; 535 | } 536 | .static { 537 | position: static; 538 | } 539 | .sticky { 540 | position: sticky; 541 | } 542 | .container { 543 | width: 100%; 544 | @media (width >= 40rem) { 545 | max-width: 40rem; 546 | } 547 | @media (width >= 48rem) { 548 | max-width: 48rem; 549 | } 550 | @media (width >= 64rem) { 551 | max-width: 64rem; 552 | } 553 | @media (width >= 80rem) { 554 | max-width: 80rem; 555 | } 556 | @media (width >= 96rem) { 557 | max-width: 96rem; 558 | } 559 | } 560 | .mx-auto { 561 | margin-inline: auto; 562 | } 563 | .mb-2 { 564 | margin-bottom: calc(var(--spacing) * 2); 565 | } 566 | .mb-3 { 567 | margin-bottom: calc(var(--spacing) * 3); 568 | } 569 | .mb-4 { 570 | margin-bottom: calc(var(--spacing) * 4); 571 | } 572 | .mb-6 { 573 | margin-bottom: calc(var(--spacing) * 6); 574 | } 575 | .mb-8 { 576 | margin-bottom: calc(var(--spacing) * 8); 577 | } 578 | .mb-10 { 579 | margin-bottom: calc(var(--spacing) * 10); 580 | } 581 | .mb-11 { 582 | margin-bottom: calc(var(--spacing) * 11); 583 | } 584 | .mb-16 { 585 | margin-bottom: calc(var(--spacing) * 16); 586 | } 587 | .block { 588 | display: block; 589 | } 590 | .flex { 591 | display: flex; 592 | } 593 | .grid { 594 | display: grid; 595 | } 596 | .inline-block { 597 | display: inline-block; 598 | } 599 | .inline-flex { 600 | display: inline-flex; 601 | } 602 | .list-item { 603 | display: list-item; 604 | } 605 | .table { 606 | display: table; 607 | } 608 | .h-96 { 609 | height: calc(var(--spacing) * 96); 610 | } 611 | .w-fit { 612 | width: fit-content; 613 | } 614 | .flex-grow { 615 | flex-grow: 1; 616 | } 617 | .border-collapse { 618 | border-collapse: collapse; 619 | } 620 | .transform { 621 | transform: var(--tw-rotate-x) var(--tw-rotate-y) var(--tw-rotate-z) var(--tw-skew-x) var(--tw-skew-y); 622 | } 623 | .resize { 624 | resize: both; 625 | } 626 | .grid-cols-1 { 627 | grid-template-columns: repeat(1, minmax(0, 1fr)); 628 | } 629 | .flex-col { 630 | flex-direction: column; 631 | } 632 | .flex-wrap { 633 | flex-wrap: wrap; 634 | } 635 | .items-center { 636 | align-items: center; 637 | } 638 | .justify-center { 639 | justify-content: center; 640 | } 641 | .gap-8 { 642 | gap: calc(var(--spacing) * 8); 643 | } 644 | .space-y-4 { 645 | :where(& > :not(:last-child)) { 646 | --tw-space-y-reverse: 0; 647 | margin-block-start: calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse)); 648 | margin-block-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse))); 649 | } 650 | } 651 | .rounded { 652 | border-radius: 0.25rem; 653 | } 654 | .rounded-3xl { 655 | border-radius: var(--radius-3xl); 656 | } 657 | .rounded-full { 658 | border-radius: calc(infinity * 1px); 659 | } 660 | .rounded-lg { 661 | border-radius: var(--radius-lg); 662 | } 663 | .border { 664 | border-style: var(--tw-border-style); 665 | border-width: 1px; 666 | } 667 | .border-2 { 668 | border-style: var(--tw-border-style); 669 | border-width: 2px; 670 | } 671 | .border-transparent { 672 | border-color: transparent; 673 | } 674 | .bg-amber-500 { 675 | background-color: var(--color-amber-500); 676 | } 677 | .bg-blue-500 { 678 | background-color: var(--color-blue-500); 679 | } 680 | .bg-gray-200 { 681 | background-color: var(--color-gray-200); 682 | } 683 | .bg-indigo-600 { 684 | background-color: var(--color-indigo-600); 685 | } 686 | .bg-white { 687 | background-color: var(--color-white); 688 | } 689 | .bg-white\/35 { 690 | background-color: color-mix(in oklab, var(--color-white) 35%, transparent); 691 | } 692 | .p-6 { 693 | padding: calc(var(--spacing) * 6); 694 | } 695 | .px-4 { 696 | padding-inline: calc(var(--spacing) * 4); 697 | } 698 | .px-8 { 699 | padding-inline: calc(var(--spacing) * 8); 700 | } 701 | .py-2 { 702 | padding-block: calc(var(--spacing) * 2); 703 | } 704 | .py-3 { 705 | padding-block: calc(var(--spacing) * 3); 706 | } 707 | .py-16 { 708 | padding-block: calc(var(--spacing) * 16); 709 | } 710 | .text-center { 711 | text-align: center; 712 | } 713 | .text-3xl { 714 | font-size: var(--text-3xl); 715 | line-height: var(--tw-leading, var(--text-3xl--line-height)); 716 | } 717 | .text-4xl { 718 | font-size: var(--text-4xl); 719 | line-height: var(--tw-leading, var(--text-4xl--line-height)); 720 | } 721 | .text-5xl { 722 | font-size: var(--text-5xl); 723 | line-height: var(--tw-leading, var(--text-5xl--line-height)); 724 | } 725 | .text-9xl { 726 | font-size: var(--text-9xl); 727 | line-height: var(--tw-leading, var(--text-9xl--line-height)); 728 | } 729 | .text-lg { 730 | font-size: var(--text-lg); 731 | line-height: var(--tw-leading, var(--text-lg--line-height)); 732 | } 733 | .text-sm { 734 | font-size: var(--text-sm); 735 | line-height: var(--tw-leading, var(--text-sm--line-height)); 736 | } 737 | .text-xl { 738 | font-size: var(--text-xl); 739 | line-height: var(--tw-leading, var(--text-xl--line-height)); 740 | } 741 | .font-bold { 742 | --tw-font-weight: var(--font-weight-bold); 743 | font-weight: var(--font-weight-bold); 744 | } 745 | .font-semibold { 746 | --tw-font-weight: var(--font-weight-semibold); 747 | font-weight: var(--font-weight-semibold); 748 | } 749 | .text-amber-500 { 750 | color: var(--color-amber-500); 751 | } 752 | .text-gray-500 { 753 | color: var(--color-gray-500); 754 | } 755 | .text-gray-600 { 756 | color: var(--color-gray-600); 757 | } 758 | .text-gray-800 { 759 | color: var(--color-gray-800); 760 | } 761 | .text-indigo-600 { 762 | color: var(--color-indigo-600); 763 | } 764 | .text-red-500 { 765 | color: var(--color-red-500); 766 | } 767 | .text-white { 768 | color: var(--color-white); 769 | } 770 | .uppercase { 771 | text-transform: uppercase; 772 | } 773 | .underline { 774 | text-decoration-line: underline; 775 | } 776 | .shadow-md { 777 | --tw-shadow: 0 4px 6px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 2px 4px -2px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); 778 | box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); 779 | } 780 | .outline { 781 | outline-style: var(--tw-outline-style); 782 | outline-width: 1px; 783 | } 784 | .filter { 785 | filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); 786 | } 787 | .backdrop-filter { 788 | -webkit-backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); 789 | backdrop-filter: var(--tw-backdrop-blur,) var(--tw-backdrop-brightness,) var(--tw-backdrop-contrast,) var(--tw-backdrop-grayscale,) var(--tw-backdrop-hue-rotate,) var(--tw-backdrop-invert,) var(--tw-backdrop-opacity,) var(--tw-backdrop-saturate,) var(--tw-backdrop-sepia,); 790 | } 791 | .transition { 792 | transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter; 793 | transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); 794 | transition-duration: var(--tw-duration, var(--default-transition-duration)); 795 | } 796 | .transition-all { 797 | transition-property: all; 798 | transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); 799 | transition-duration: var(--tw-duration, var(--default-transition-duration)); 800 | } 801 | .duration-500 { 802 | --tw-duration: 500ms; 803 | transition-duration: 500ms; 804 | } 805 | .hover\:scale-110 { 806 | &:hover { 807 | @media (hover: hover) { 808 | --tw-scale-x: 110%; 809 | --tw-scale-y: 110%; 810 | --tw-scale-z: 110%; 811 | scale: var(--tw-scale-x) var(--tw-scale-y); 812 | } 813 | } 814 | } 815 | .hover\:border-indigo-600 { 816 | &:hover { 817 | @media (hover: hover) { 818 | border-color: var(--color-indigo-600); 819 | } 820 | } 821 | } 822 | .hover\:bg-indigo-700 { 823 | &:hover { 824 | @media (hover: hover) { 825 | background-color: var(--color-indigo-700); 826 | } 827 | } 828 | } 829 | .md\:grid-cols-2 { 830 | @media (width >= 48rem) { 831 | grid-template-columns: repeat(2, minmax(0, 1fr)); 832 | } 833 | } 834 | .md\:grid-cols-3 { 835 | @media (width >= 48rem) { 836 | grid-template-columns: repeat(3, minmax(0, 1fr)); 837 | } 838 | } 839 | } 840 | @import url('https://fonts.googleapis.com/css2?family=Poppins&display=swap'); 841 | :root { 842 | --text-main: white; 843 | --text-main2: #215d3e; 844 | --text-secondary: white; 845 | } 846 | body { 847 | --tw-gradient-position: to bottom right in oklab,; 848 | background-image: linear-gradient(var(--tw-gradient-stops)); 849 | --tw-gradient-from: var(--color-purple-100); 850 | --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position,) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); 851 | --tw-gradient-to: var(--color-indigo-200); 852 | --tw-gradient-stops: var(--tw-gradient-via-stops, var(--tw-gradient-position,) var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position)); 853 | font-size: 16px; 854 | background-size: cover; 855 | background-repeat: no-repeat; 856 | font-family: Poppins; 857 | } 858 | #lumina__root { 859 | min-height: 100vh; 860 | display: flex; 861 | flex-direction: column; 862 | justify-content: space-between; 863 | } 864 | a { 865 | text-decoration: none; 866 | color: var(--text-main); 867 | cursor: pointer; 868 | } 869 | a:hover { 870 | opacity: 75%; 871 | } 872 | .loaderContainer { 873 | display: flex; 874 | justify-content: center; 875 | align-items: center; 876 | height: 100vh; 877 | } 878 | .loaderContainer .loaderChild { 879 | display: flex; 880 | flex-direction: column; 881 | justify-content: center; 882 | align-items: center; 883 | gap: 12px; 884 | padding: 24px 48px; 885 | border-radius: 12px; 886 | background: rgba(255, 255, 255, 0.258); 887 | } 888 | .loaderContainer .loaderChild h1 { 889 | font-weight: 900; 890 | color: #9f90ff; 891 | font-size: 36px; 892 | } 893 | .loader { 894 | width: 48px; 895 | height: 48px; 896 | border-radius: 50%; 897 | position: relative; 898 | animation: rotate 1s linear infinite; 899 | } 900 | .loader::before, .loader::after { 901 | content: ''; 902 | box-sizing: border-box; 903 | position: absolute; 904 | inset: 0px; 905 | border-radius: 50%; 906 | border: 5px solid #fff; 907 | animation: prixClipFix 2s linear infinite; 908 | } 909 | .loader::after { 910 | border-color: #afa4f9; 911 | animation: prixClipFix 2s linear infinite, rotate 0.5s linear infinite reverse; 912 | inset: 6px; 913 | } 914 | @keyframes rotate { 915 | 0% { 916 | transform: rotate(0deg); 917 | } 918 | 100% { 919 | transform: rotate(360deg); 920 | } 921 | } 922 | @keyframes prixClipFix { 923 | 0% { 924 | clip-path: polygon(50% 50%, 0 0, 0 0, 0 0, 0 0, 0 0); 925 | } 926 | 25% { 927 | clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 0, 100% 0, 100% 0); 928 | } 929 | 50% { 930 | clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 100% 100%, 100% 100%); 931 | } 932 | 75% { 933 | clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 100%); 934 | } 935 | 100% { 936 | clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 0); 937 | } 938 | } 939 | @keyframes spin { 940 | to { 941 | transform: rotate(360deg); 942 | } 943 | } 944 | .header { 945 | background-color: #ffffff; 946 | box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); 947 | position: sticky; 948 | top: 0; 949 | z-index: 1000; 950 | margin-bottom: 40px; 951 | } 952 | .nav { 953 | display: flex; 954 | align-items: center; 955 | justify-content: space-between; 956 | padding: 1rem 2rem; 957 | max-width: 1200px; 958 | margin: 0 auto; 959 | } 960 | .logo a { 961 | font-size: 1.5rem; 962 | font-weight: bold; 963 | color: #333; 964 | text-decoration: none; 965 | } 966 | .nav-links { 967 | display: flex; 968 | gap: 1.5rem; 969 | list-style: none; 970 | margin: 0; 971 | padding: 0; 972 | } 973 | .nav-links a { 974 | color: #333; 975 | text-decoration: none; 976 | font-size: 1rem; 977 | font-weight: 500; 978 | transition: color 0.3s ease; 979 | } 980 | .nav-links a:hover { 981 | color: #007bff; 982 | } 983 | .menu-toggle { 984 | display: none; 985 | font-size: 1.5rem; 986 | background: none; 987 | border: none; 988 | cursor: pointer; 989 | color: #333; 990 | } 991 | @media (max-width: 768px) { 992 | .nav-links { 993 | display: none; 994 | flex-direction: column; 995 | gap: 1rem; 996 | position: absolute; 997 | top: 100%; 998 | right: 0; 999 | background-color: #fff; 1000 | box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1); 1001 | padding: 1rem 2rem; 1002 | border-radius: 0 0 10px 10px; 1003 | } 1004 | .nav-links.active { 1005 | display: flex; 1006 | } 1007 | .menu-toggle { 1008 | display: block; 1009 | } 1010 | } 1011 | .footer { 1012 | background-color: #333; 1013 | color: #fff; 1014 | padding: 2rem 1rem; 1015 | text-align: center; 1016 | } 1017 | .footer-container { 1018 | display: flex; 1019 | flex-wrap: wrap; 1020 | justify-content: space-between; 1021 | max-width: 1200px; 1022 | margin: 0 auto; 1023 | gap: 2rem; 1024 | } 1025 | .footer-section { 1026 | flex: 1 1 calc(33.333% - 1rem); 1027 | min-width: 200px; 1028 | } 1029 | .lumina-icon { 1030 | width: 100px; 1031 | border-radius: 12px; 1032 | } 1033 | .footer-title { 1034 | font-size: 1.5rem; 1035 | margin-bottom: 0.5rem; 1036 | } 1037 | .footer-description { 1038 | font-size: 0.9rem; 1039 | color: #ccc; 1040 | } 1041 | .footer-links { 1042 | list-style: none; 1043 | padding: 0; 1044 | margin: 0; 1045 | } 1046 | .footer-links li { 1047 | margin: 0.5rem 0; 1048 | } 1049 | .footer-links a { 1050 | color: #fff; 1051 | text-decoration: none; 1052 | font-size: 1rem; 1053 | transition: color 0.3s ease; 1054 | } 1055 | .footer-links a:hover { 1056 | color: #007bff; 1057 | } 1058 | .social-links a { 1059 | font-size: 1.5rem; 1060 | color: #fff; 1061 | margin: 0 0.5rem; 1062 | transition: color 0.3s ease; 1063 | } 1064 | .social-links a:hover { 1065 | color: #007bff; 1066 | } 1067 | .footer-bottom { 1068 | margin-top: 2rem; 1069 | border-top: 1px solid #444; 1070 | padding-top: 1rem; 1071 | font-size: 0.9rem; 1072 | color: #ccc; 1073 | } 1074 | @media (max-width: 768px) { 1075 | .footer-container { 1076 | flex-direction: column; 1077 | text-align: left; 1078 | } 1079 | .footer-section { 1080 | margin-bottom: 1.5rem; 1081 | } 1082 | .footer-bottom { 1083 | text-align: center; 1084 | } 1085 | } 1086 | @keyframes spin { 1087 | to { 1088 | transform: rotate(360deg); 1089 | } 1090 | } 1091 | @keyframes ping { 1092 | 75%, 100% { 1093 | transform: scale(2); 1094 | opacity: 0; 1095 | } 1096 | } 1097 | @keyframes pulse { 1098 | 50% { 1099 | opacity: 0.5; 1100 | } 1101 | } 1102 | @keyframes bounce { 1103 | 0%, 100% { 1104 | transform: translateY(-25%); 1105 | animation-timing-function: cubic-bezier(0.8, 0, 1, 1); 1106 | } 1107 | 50% { 1108 | transform: none; 1109 | animation-timing-function: cubic-bezier(0, 0, 0.2, 1); 1110 | } 1111 | } 1112 | @property --tw-rotate-x { 1113 | syntax: "*"; 1114 | inherits: false; 1115 | initial-value: rotateX(0); 1116 | } 1117 | @property --tw-rotate-y { 1118 | syntax: "*"; 1119 | inherits: false; 1120 | initial-value: rotateY(0); 1121 | } 1122 | @property --tw-rotate-z { 1123 | syntax: "*"; 1124 | inherits: false; 1125 | initial-value: rotateZ(0); 1126 | } 1127 | @property --tw-skew-x { 1128 | syntax: "*"; 1129 | inherits: false; 1130 | initial-value: skewX(0); 1131 | } 1132 | @property --tw-skew-y { 1133 | syntax: "*"; 1134 | inherits: false; 1135 | initial-value: skewY(0); 1136 | } 1137 | @property --tw-space-y-reverse { 1138 | syntax: "*"; 1139 | inherits: false; 1140 | initial-value: 0; 1141 | } 1142 | @property --tw-border-style { 1143 | syntax: "*"; 1144 | inherits: false; 1145 | initial-value: solid; 1146 | } 1147 | @property --tw-font-weight { 1148 | syntax: "*"; 1149 | inherits: false; 1150 | } 1151 | @property --tw-shadow { 1152 | syntax: "*"; 1153 | inherits: false; 1154 | initial-value: 0 0 #0000; 1155 | } 1156 | @property --tw-shadow-color { 1157 | syntax: "*"; 1158 | inherits: false; 1159 | } 1160 | @property --tw-inset-shadow { 1161 | syntax: "*"; 1162 | inherits: false; 1163 | initial-value: 0 0 #0000; 1164 | } 1165 | @property --tw-inset-shadow-color { 1166 | syntax: "*"; 1167 | inherits: false; 1168 | } 1169 | @property --tw-ring-color { 1170 | syntax: "*"; 1171 | inherits: false; 1172 | } 1173 | @property --tw-ring-shadow { 1174 | syntax: "*"; 1175 | inherits: false; 1176 | initial-value: 0 0 #0000; 1177 | } 1178 | @property --tw-inset-ring-color { 1179 | syntax: "*"; 1180 | inherits: false; 1181 | } 1182 | @property --tw-inset-ring-shadow { 1183 | syntax: "*"; 1184 | inherits: false; 1185 | initial-value: 0 0 #0000; 1186 | } 1187 | @property --tw-ring-inset { 1188 | syntax: "*"; 1189 | inherits: false; 1190 | } 1191 | @property --tw-ring-offset-width { 1192 | syntax: ""; 1193 | inherits: false; 1194 | initial-value: 0px; 1195 | } 1196 | @property --tw-ring-offset-color { 1197 | syntax: "*"; 1198 | inherits: false; 1199 | initial-value: #fff; 1200 | } 1201 | @property --tw-ring-offset-shadow { 1202 | syntax: "*"; 1203 | inherits: false; 1204 | initial-value: 0 0 #0000; 1205 | } 1206 | @property --tw-outline-style { 1207 | syntax: "*"; 1208 | inherits: false; 1209 | initial-value: solid; 1210 | } 1211 | @property --tw-blur { 1212 | syntax: "*"; 1213 | inherits: false; 1214 | } 1215 | @property --tw-brightness { 1216 | syntax: "*"; 1217 | inherits: false; 1218 | } 1219 | @property --tw-contrast { 1220 | syntax: "*"; 1221 | inherits: false; 1222 | } 1223 | @property --tw-grayscale { 1224 | syntax: "*"; 1225 | inherits: false; 1226 | } 1227 | @property --tw-hue-rotate { 1228 | syntax: "*"; 1229 | inherits: false; 1230 | } 1231 | @property --tw-invert { 1232 | syntax: "*"; 1233 | inherits: false; 1234 | } 1235 | @property --tw-opacity { 1236 | syntax: "*"; 1237 | inherits: false; 1238 | } 1239 | @property --tw-saturate { 1240 | syntax: "*"; 1241 | inherits: false; 1242 | } 1243 | @property --tw-sepia { 1244 | syntax: "*"; 1245 | inherits: false; 1246 | } 1247 | @property --tw-backdrop-blur { 1248 | syntax: "*"; 1249 | inherits: false; 1250 | } 1251 | @property --tw-backdrop-brightness { 1252 | syntax: "*"; 1253 | inherits: false; 1254 | } 1255 | @property --tw-backdrop-contrast { 1256 | syntax: "*"; 1257 | inherits: false; 1258 | } 1259 | @property --tw-backdrop-grayscale { 1260 | syntax: "*"; 1261 | inherits: false; 1262 | } 1263 | @property --tw-backdrop-hue-rotate { 1264 | syntax: "*"; 1265 | inherits: false; 1266 | } 1267 | @property --tw-backdrop-invert { 1268 | syntax: "*"; 1269 | inherits: false; 1270 | } 1271 | @property --tw-backdrop-opacity { 1272 | syntax: "*"; 1273 | inherits: false; 1274 | } 1275 | @property --tw-backdrop-saturate { 1276 | syntax: "*"; 1277 | inherits: false; 1278 | } 1279 | @property --tw-backdrop-sepia { 1280 | syntax: "*"; 1281 | inherits: false; 1282 | } 1283 | @property --tw-duration { 1284 | syntax: "*"; 1285 | inherits: false; 1286 | } 1287 | @property --tw-scale-x { 1288 | syntax: "*"; 1289 | inherits: false; 1290 | initial-value: 1; 1291 | } 1292 | @property --tw-scale-y { 1293 | syntax: "*"; 1294 | inherits: false; 1295 | initial-value: 1; 1296 | } 1297 | @property --tw-scale-z { 1298 | syntax: "*"; 1299 | inherits: false; 1300 | initial-value: 1; 1301 | } 1302 | @property --tw-gradient-position { 1303 | syntax: "*"; 1304 | inherits: false; 1305 | } 1306 | @property --tw-gradient-from { 1307 | syntax: ""; 1308 | inherits: false; 1309 | initial-value: #0000; 1310 | } 1311 | @property --tw-gradient-via { 1312 | syntax: ""; 1313 | inherits: false; 1314 | initial-value: #0000; 1315 | } 1316 | @property --tw-gradient-to { 1317 | syntax: ""; 1318 | inherits: false; 1319 | initial-value: #0000; 1320 | } 1321 | @property --tw-gradient-stops { 1322 | syntax: "*"; 1323 | inherits: false; 1324 | } 1325 | @property --tw-gradient-via-stops { 1326 | syntax: "*"; 1327 | inherits: false; 1328 | } 1329 | @property --tw-gradient-from-position { 1330 | syntax: ""; 1331 | inherits: false; 1332 | initial-value: 0%; 1333 | } 1334 | @property --tw-gradient-via-position { 1335 | syntax: ""; 1336 | inherits: false; 1337 | initial-value: 50%; 1338 | } 1339 | @property --tw-gradient-to-position { 1340 | syntax: ""; 1341 | inherits: false; 1342 | initial-value: 100%; 1343 | } 1344 | --------------------------------------------------------------------------------